Introduction


terse is an algebraic assembly language for x86 computers, I'm a fan of the language and enjoy programming in it. It takes away some of the annoying tasks when writing assembly, such as generating labels, and improves the readability and maintainability without removing any of the power. It is a chore to learn all the operators, and some of them are different from those I would have chosen, but the author justifies all his decisions and they mostly work well.

the only terse compiler is a proprietory DOS application, but with some work can run on linux and is indistinguishable from a native application. The code it generates is compatible with the NASM assembler and can be used to develop native linux applications.

This document describes how to achieve this using some open source software, and discusses some of the issues involved with development in terse.

Editing


TODO: finish vim syntax file for terse and add it here.

Dosemu


I've used the excellent dosemu with freedos to run terse on linux, once installed and configured, setting up terse is as simple as writing a quick wrapper script to invoke the compiler properly.

First, I mapped a drive to the location of the terse distribution.

I used F:, this is achieved by creating a directory somewhere and copying the contents of the terse distribution into that directory. Then create a symlink called `f` to the directory in the ~/.dosemu/drives directory.

Now, in order for terse to interact with the host filesystem correctly, we need to let it know the cwd. I achieved this by adding the directive `LREDIR G: LINUX\FS\PROC\SELF\CWD\` to autoexec.bat before the UNIX -e command.

$ terse
-------------------- The terse (tm) Compiler, Version 2.20 --------------------
             Copyright (C) Jim Neil 1989-1997, All Rights Reserved
Usage:
     terse [ { ifile.T | /Iifile } /Oofile /L?0000 /C; /N { /E | /D | /W } /? ]

Where:                                                               (DEFAULTS)
  ifile.T  INPUT file.  If no /O, OUTPUT file is ifile.ASM              (stdin)
  /Iifile  INPUT file.                                                  (stdin)
  /Oofile  OUTPUT file.                        (see above, ifile.ASM or stdout)
  /L?0000  LABEL control.  ? is leading char, 0000 is initial -1.       (?0000)
  /C;      COMMENT character.                                               (;)
  /N       Append source line NUMBERS to each line of code.        (no numbers)
  /E       Generate Jecxz.                             (Jcxz/Loop/Loopz/Loopnz)
  /D       Generate Jecxz/Loopd/Loopdz/Loopdnz.        (Jcxz/Loop/Loopz/Loopnz)
  /W       Generate Jcxz/Loopw/Loopwz/Loopwnz.         (Jcxz/Loop/Loopz/Loopnz)
  /?       Displays HELP message.                             (no HELP message)

           NOTE: Parameters may be enclosed in single or double quotation marks
Example:
     terse PROGRAM.T /L@F000 /N /C'/' /D
           Compiles input file PROGRAM.T, generates output file PROGRAM.ASM,
           labels start with '@', initial label is F001, append line numbers,
           comment char is '/', use Jecxz/Loopd/Loopdz/Loopdnz.

$ terse hello.t 
------- The terse (tm) Compiler, Version 2.20 -------
Copyright (C) Jim Neil 1989-1997, All Rights Reserved

    10 Lines In.
    14 Lines Out.

$ nasm -f elf hello.asm -o hello.o
$ ld -o hello hello.o 
$ ./hello 
hello world

Problems


As terse is not a native linux program, it does not return its status correctly to linux. This prevents it's use in a Makefile, in order to resolve this I added code to detect an error to the wrapper script using awk.

#!/bin/sh
if test $# -lt 1; then     # no arguments, print usage
    COMMAND="F:TERSE /?"
elif test ! -f "$1"; then  # no file as argument, parse args to terse
    COMMAND="F:TERSE"
else                       
    COMMAND="F:TERSE G:${1##*/}" # reformat as relative, then cd to dir
    if test "${1%/*}" != "${1}"; then cd ${1%/*}; fi
    shift                  # done with filename
fi

# start dosemu
COLUMNS=80 LINES=25 dosemu -quiet -dumb "$COMMAND $*" < /dev/null \
    | awk '{
        retval = retval || index($0, "\a");
        print;
    } END { exit retval }'

nasm doesnt understand the Dup() construct emitted by terse in certain declarative statements. for example, this will not work in linux:

    'foo[12];

these equivalent statements will:

    'foo = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
    \ or
    'foo times 12 = 0;

    \ or for uninitialised .bss buffers
    foo: resb 12;

multidimensional arrays dont work:

    'foo[1,2,3]

But these equivalent alternatives do:

    'foo times 1 times 2 times 3 = 0;
    \ or 
    'foo times 1*2*3 = 0;

It's unfortunate this doesnt work, but as you can see it's easy to workaround them.

Examples


Hello World

section .data;
    'msg = ("hello world", 0x0a);
section .text;
global _start;
_start:
    eax = 4; 
    ebx = 1;
    ecx = $msg;
    edx = 12; !80h;
    &eax+; &ebx; !80h;

Makefile Rules

%.asm: %.t
    terse $< /E /OG:$@
%.o: %.asm
    nasm -f elf -o $@ $<

Future


I enjoy programming with terse, it's basically a more readable and manageable form of assembly. It's unfortunate that there is no free implementation, so at some point I plan to rectify this with a free alternative possibly using bison, possibly using more C like operators where possible. If you're interested in getting involved with a project like this, let me know.

References


  1. Terse
  2. DosEMU
  3. NASM
  4. Home

Conclusion


Any flames, corrections, feedback please contact the author at <taviso (a) sdf lonestar org>.