

                                 MACRO REFERENCE
                                 ---------------

     Macros exist as standard text files that are compiled and run by an
     application.  The library files MACRO1.ASM and MACRO2.ASM provide all
     the code necessary for adding a macro language to an application.  The
     routine MacAll allocates the memory required for compiling and running
     macros.  MacCom compiles macro source files to an executable program
     stored in memory.  MacRun executes a compiled macro.

     The default keywords of the macro language do not perform any
     meaningful tasks.  A table of application specific keywords must be
     created by the user before the macro language can be useful.

                              LANGUAGE DESCRIPTION
                              --------------------

     The macro language is stack based and resembles the Forth programming
     language.  The macro source code consists of a sequence of tokens
     separated by spaces and line boundaries.  For example:

       22 11 +
       33 =

     The tokens in this case are "22", "11", "+", "33", and "=".  The
     location of the line break is unimportant.  These tokens could all be
     placed on a single line with the same effect:

       22 11 + 33 =

     Each token performs some specific stack operation.  Numbers are pushed
     on the stack, so after the first two tokens are "executed," the
     numbers 22 and 11 are pushed on the stack.  A plus (+) adds the top
     two numbers of the stack together, so after the plus is executed, the
     22 and 11 on the stack have been replaced by a 33.  The fourth token
     results in 33 being pushed on the stack (on top of the 33 remaining
     after adding the 22 and 11).  Finally, the equal sign compares the top
     two stack numbers, replacing them with -1 (FFFF hex) if they are equal
     or 0 if they are not.  The net effect of this expression is to add 22
     and 11 and compare the result to 33.  The result of the comparison, -1
     in this case, is left of the stack.

     Numbers are 16 bit signed integers.  If no sign is provided, '+' is
     assumed.  Hexadecimal numbers are specified by a preceding dollar sign
     ($).  Characters in single quotes are translated to their ASCII value.
     Some example numbers are:

       1 -3 +12414 $ABCD 'a'

     Symbols identify locations in the macro program.  Symbols are used in
     branching structures and accessing data.  Symbols are defined using
     the colon (:) token:

       : a_symbol

     In this case, "a_symbol" is the defined symbol because it is the token
     after the colon.  Note that symbols and keywords are case-sensitive.
     Creating a symbol produces no machine code.  Using a symbol pushes its


     address on the stack:

       a_symbol JUMP

     In this case, the address of "a_symbol" is pushed on the stack and the
     JUMP keyword branches to this address.  Symbols may be used before
     they are defined (i.e., the macro compiler handles forward references
     properly).

     Data can be declared in two ways, strings and arrays.  Strings are
     created with the double quote (") token:

       " this is a string"

     The start of the string begins after the first delimiter after the
     double quote and ends with a double quote.  In this case, the letter
     't' is the first character of the string and the letter 'g' is the
     last character of the string.  A NUL character (ASCII code 0) is
     appended to the end of all strings.  Strings can contain two types of
     special characters, the carat (^) and the backslash (\).  The carat
     specifies a control character, for example:

        " this is a string^M^J"

     This string has a carriage return and linefeed are appended to the end
     of it.  The other special character, a backslash, masks special
     characters so they can be imbedded in a string, for instance:

        " this is a string \^ \" \\ "
          |                |  |  |  |
          |                |  |  |  end of string
          |                |  |  imbed backslash
          |                |  imbed double quote
          |                imbed carat
          start of string

     Arrays or variables (single element arrays) are created using the left
     and right curved bracket ({}) tokens.  All tokens within the curved
     brackets are stored as sequential 16 bit values.  The address of a
     symbol may be stored by placing the symbol between the brackets.  An
     example array:

       { 0 1 -6 'a' $ABCD a_symbol }

     Both strings and arrays are stored as inline data and must NOT be
     executed.  Strings and arrays should be placed at the end of a macro
     after the QUIT or branched around.  Strings and arrays are usually
     referred to with symbols defined at the start of the data:

        : a_variable { 55 }
        : an_array { 0 1 2 3 4 }

     Numbers can be stored at an address with the exclamation point (!)
     token or retrieved with the '@' token:

        0 a_variable !  // store zero to a_variable

        a_variable @    // load the value stored at a_variable


     There are four special keywords for branching.  JUMP unconditionally
     branches to a location stored on the stack.  The location on the stack
     usually comes from a symbol.  ?JUMP uses a location and a number on
     the stack to jump only if the number is non-zero, for instance:

       22 11 + 33 =      // add numbers and compare to 33, flag on stack
       a_symbol ?JUMP    // push address on stack, jump if flag non-zero

     This last sequence of tokens jumps to "a_symbol" if the result of
     adding 22 and 11 is equal to 33.

     The keywords CALL and ?CALL are similar to JUMP and ?JUMP except the
     current location is saved before branching.  The return token, a
     semicolon (;), returns from a CALL or ?CALL.  A routine to add 22 and
     11 then compare the result to 33 could be written as:

       : add_routine 22 11 + 33 = ;

     this routine is called as follows:

       add_routine CALL

     Comments can be included with the double slash (//) token.  All
     characters after a double slash token are ignored until the end of the
     line is reached:

       // push three numbers
       1 2  // push one and two
       3    // now push three

     Numbers and addresses are pushed and popped from the stack without any
     type checking.  Keywords that expect an address on the stack (like
     JUMP and CALL) have no way of knowing if the value it's using is valid
     address or not.  An easy way to crash a macro is running something
     like:

       1234 JUMP     // this will almost certainly crash

     Elements of an array can be accessed by incrementing the array address
     by two for each element to skip:

       an_array 4 + @                // load the third array element: 9

       : an_array { 7 8 9 }

     Array elements must be incremented by two because each element, being
     16 bit values, takes up two bytes.

     Accessing elements of a string are more complicated.  The dereference
     token '@' loads two bytes, which represents two characters of a
     string.  For this reason, the high byte of a dereferenced string
     element must be zeroed:

       a_string 8 + @                // load the ninth and tenth elements
       $FF AND                       // this zeros high byte; return 'a'

       : a_string " this is a string"



     A macro may be run by MacRun once it has been successfully compiled
     and loaded by MacCom.  Macros begin executing from the start of the
     compiled code.  Macros will terminate and return from MacRun whenever
     a QUIT or BREAK is encountered.  If the execution stops because of a
     BREAK, the state of the macro is preserved and the next call to MacRun
     continues execution after the BREAK.  A QUIT implies the final
     termination and MacRes must be called before restarting the macro.

     From a compiler standpoint, the macro language is easy to process. The
     assembly equivalent of the macro expression:

       22 11 + 33 =

     is something like:

        mov ax, 22   ; 22 token, push the number
        push ax      ;

        mov ax, 11   ; 11 token, push the number
        push ax      ;

        pop ax       ; addition (+) token, add top two numbers
        pop bx       ;
        add ax, bx   ;
        push ax      ;

        mov ax, 33   ; 33 token, push the number
        push ax      ;

        pop ax       ; compare (=) token, compare top two numbers
        pop dx       ;
        sub bx, bx   ;
        cmp ax, dx   ;
        jne xyz      ;
        dec bx       ;
       xyz push bx   ;

                                LANGUAGE KEYWORDS
                                -----------------

     This section lists all the built-in keywords to the macro language.
     Many of the keywords are identical to Forth keywords (or "words" as
     they're called in Forth).  Other keywords look like Forth but behave
     differently.

     There are two distinct types of arguments for the keywords.  The first
     is arguments that are taken from the source file during compilation.
     The keywords that use this type of arguments are :, ", {, //, ALLOC,
     and INCLUDE.  All the other keywords use the stack for their arguments
     during execution.  The syntax for stack arguments is something like:

       n1 n2 n3 - n4 n5

     In this case, the keyword expects three stack arguments and returns
     two stack arguments.  n1, n2, and n3 are the top three stack items
     before the keyword has executed.  n4 and n5 are the stack items
     remaining when the keyword is finished.  Some keywords expect no
     arguments or return no arguments, in which case the left or right side


     of the dash (-) will be blank.  Entry arguments (left side of dash)
     and exit arguments (right side of dash) are listed left to right as
     they would be pushed on the stack, so the leftmost argument is always
     the top stack element.  For instance, the syntax for ROT is:

       n1 n2 n3 - n2 n3 n1

     After a ROT (rotate), the original third item down becomes the new top
     item, the original top item becomes the new second item, and the
     original second item becomes the new third item:

       3 4 5 ROT     // after this the stack is 4 5 3, 3 is new top element

     The syntax for divide (/) is:

       n1 n2 - n3

     where n3 = n1 / n2, so:

       10 5 /        // this divides 10 by 5, leaving 2 on the stack

     Some keywords return different sets of arguments depending on the
     entry arguments.  In this case the alternate argument sets are
     separated by a vertical bar (|).

     Define/Declare
     --------------

       :        symbol               define a symbol

       "        ccccc"               declare string data
       {        n1, n2, ... }        declare array data
       ALLOC    n                    declare n bytes of data (zeros)

       !        n a -                store n to address a
       @        a - n                load n from address a

     Constants
     ---------

       FALSE    - 0                  load false (0)
       TRUE     - -1                 load true (NOT 0)
       HERE     - n                  load current address

     Arithmetic
     ----------

       +        n1 n2 - n3           addition, n3 = n1 + n2
       -        n1 n2 - n3           subtraction, n3 = n1 - n2
       *        n1 n2 - n3           multiplication, n3 = n1 * n2
       /        n1 n2 - n3           division, n3 = n1 / n2

       1+       n1 - n2              increment, n2 = n1 + 1
       1-       n1 - n2              decrement, n2 = n1 - 1
       2+       n1 - n2              add two, n2 = n1 + 2
       2-       n1 - n2              subtract two, n2 = n1 - 2
       2*       n1 - n2              multiply times two, n2 = n1 * 2
       2/       n1 - n2              divide by two, n2 = n1 / 2


       MOD      n1 n2 - n3           remainder, n3 = n1 MOD n2
       /MOD     n1 n2 - n3 n4        divide and remainder, n3 = n1 MOD n2
                                                           n4 = n1 / n2

       MIN      n1 n2 - n3           minimum, n3 is minimum of n1 and n2
       MAX      n1 n2 - n3           maximum, n3 is maximum of n1 and n2
       NEGATE   n1 - n2              negative, n2 = -n1
       ABS      n1 - n2              absolute value, n2 is abs of n1

     Stack
     -----

       DUP      n - n n              duplicate top item
       ?DUP     n - n | n n          duplicate top item if non-zero
       DROP     n -                  remove top item
       SWAP     n1 n2 - n2 n1        swap top two items
       OVER     n1 n2 - n1 n2 n1     copy second item to top
       PICK     ... i - n            copy i-th item to top
       ROT      n1 n2 n3 - n2 n3 n1  roll third item to top

     Conditional
     -----------

       =        n1 n2 - 0 | -1       return -1 if items equal, 0 if not
       >        n1 n2 - 0 | -1       return -1 if n1 > n2, 0 if not
       <        n1 n2 - 0 | -1       return -1 if n1 < n2, 0 if not

       0=       n - 0 | -1           return -1 if n = 0, 0 if not
       0>       n - 0 | -1           return -1 if n > 0, 0 if not
       0<       n - 0 | -1           return -1 if n < 0, 0 if not

       AND      n1 n2 - n3           logical AND, n3 = n1 AND n2
       NOT      n1 - n2              logical NOT, n2 = NOT n1
       OR       n1 n2 - n3           logical OR, n3 = n1 OR n2

     Branching
     ---------

       ;                             return from CALL or ?CALL
       CALL     a -                  call a
       ?CALL    f a -                call a if f
       JUMP     a -                  jump to a
       ?JUMP    f a -                jump to a if f

     Other
     -----

       //                            comment
       BREAK                         break into macro
       INCLUDE  f                    include macro file f
       QUIT                          terminate macro

                               LANGUAGE EXTENSIONS
                               -------------------

     Application specific keywords must be defined before macros can do
     anything useful.  The symbol MacUsr must exist to define a table
     containing a list of keyword entries.  Each keyword entry consists of


     an ASCIIZ string (the keyword) followed by the offset of the procedure
     to service that keyword.  The list of entries is terminated by a null
     string.

     The procedure to service a keyword should be FAR.  Upon entry, all the
     segment registers are pointing to CS (as usual).  All the other
     registers are undefined.  The registers SI, DI, BP, DS, and ES should
     be preserved.  Arguments are stored and retrieved from the macro stack
     with the MacSto and MacLoa routines.  Retrieved addresses are 16 bit
     offsets into the macro code/data segment.  MacLoa returns the macro
     code/data segment in DX so you can directly access macro data by
     loading DX into a segment register.

     The following is a simple example that adds two keywords to the macro
     language and then runs a macro called TEST.MAC:

       ; This program compiles and runs a macro file call TEST.MAC
       ; IMPORTANT: compile and allocation errors are not detected.  If
       ; there is an error, this program will crash.

               ; these are the library files needed

               INCLUDE 'file.asm'
               INCLUDE 'buffer1.asm'
               INCLUDE 'buffer2.asm'
               INCLUDE 'buffer4.asm'

               INCLUDE 'case1.asm'
               INCLUDE 'convert.asm'

               INCLUDE 'memory.asm'
               INCLUDE 'stack.asm'
               INCLUDE 'string.asm'

               ; start of program

               mov     ax, 1000H             ;
               mov     bx, 1000H             ;-- probably enough memory
               mov     cx, 1000H             ;
               call    MacAll                ;allocate macro memory
               mov     ax, OFFSET file
               call    MacCom                ;compile
               call    MacRun                ;run
               call    MacRel                ;release macro memory
               mov     ax, 4C00H             ;terminate function
               int     21H                   ;execute

       file    DB      'test.mac',0          ;name of macro file

       ; this adds the keywords TYPE and INPUT to the macro language

       MacUsr  LABEL   BYTE
               DB      'TYPE',0, OFFSET TypeChar
               DB      'INPUT',0, OFFSET InputChar
               DB      0



       ; this is the TYPE service routine

       TypeChar PROC   FAR
               call    MacLoa        ;load character
               mov     dl, al
               mov     ah, 2         ;DOS display function
               int     21H           ;display character
               ret
               ENDP

       ; this is the INPUT service routine

       InputChar PROC  FAR
               mov     ah, 8         ;DOS input function
               int     21H           ;input a character
               sub     ah, ah        ;zero high byte
               call    MacSto        ;return it
               ret
               ENDP

     The following is a short macro that uses the keywords created above.
     This macro should be copied to a file called TEST.MAC to be compiled
     and run by the program above:

       // this macro inputs and echoes keystrokes until ESC is pressed;
       // a CR is automatically followed by a LF; all control characters
       // except CR and ESC are ignored

       begin JUMP                    // jump over skip

       : skip
         DROP                        // drop the character

       : begin
         INPUT                       // input a character
         DUP 27 = exit ?JUMP         // jump to exit if ESC
         DUP 13 = newline ?JUMP      // jump to newline if CR
         DUP 32 < skip ?JUMP         // jump if skip if control character

         TYPE                        // type character
         begin JUMP                  // loop back

       : newline
         TYPE                        // type character (a CR)
         10 TYPE                     // type a linefeed
         begin JUMP                  // loop back

       : exit
         DROP QUIT                   // drop ESC and quit
