Please consider a donation to the Higher Intellect project. See https://preterhuman.net/donate.php or the Donate to Higher Intellect page for more info.

ROLL YOUR OWN MINILANGUAGES WITH MINI-INTERPRETERS

From Higher Intellect Vintage Wiki
Jump to navigation Jump to search
_ROLL YOUR OWN MINILANGUAGES WITH MINI-INTERPRETERS_
by Michael Abrash and Dan Illowsky



[LISTING ONE]

; This program demonstrates the use of a mini-interpreter to produce
; code that is compact, flexible and easy to modify. The mini-
; program draws and labels a maze and animates an arrow through
; the maze.
;
; Note: This program must be run in 80-column text mode.
;
; Tested with TASM 1.0 and MASM 5.0.
;
; By Dan Illowsky & Michael Abrash 2/18/89
; Public Domain
;
Stak    segment para stack 'stack'      ;allocate stack space
        db      200h dup (?)
Stak    ends
;
_TEXT   segment para public 'code'
        assume  cs:_TEXT, ds:_TEXT
;
; Overall animation delay. Selected for an AT: set higher to slow
; animation more for faster computers, lower to slow animation less
; for slower computers.
;
DELAY_COUNT     equ     30000
;
; Equates for mini-language commands, used in the data
; sequences that define mini-programs. The values of these
; equates are used by Interp as indexes into the jump table
; Function_Table in order to call the corresponding subroutines.
;
; Lines starting with ">>" describe the parameters that must
; follow the various commands.
;
Done      equ     0     ;Ends program or subprogram.
                        ;>>No parms.
SubProg   equ     1     ;Executes a subprogram.
                        ;>>Parm is offset of subprogram.
SetXY     equ     2     ;Sets the cursor location (the location at
                        ; which to output the next character).
                        ;>>Parms are X then Y coordinates (both
                        ; bytes).
SetXYInc  equ     3     ;Sets the distance to move after displaying
                        ; each character.
                        ;>>Parms are X then Y amount to move after
                        ; displaying character (both bytes).
SetX      equ     4     ;Sets the X part of the cursor location.
                        ;>>Parm is the X coordinate (byte).
SetY      equ     5     ;Sets the Y part of the cursor location.
                        ;>>Parm is the Y coordinate (byte).
SetXInc   equ     6     ;Sets the X part of the amount to move after
                        ; displaying each character.
                        ;>>Parm is the X amount to move after
                        ; character is displayed (byte).
SetYInc   equ     7     ;Sets the Y part of the amount to move after
                        ; displaying each character.
                        ;>>Parm is the Y amount to move after
                        ; character is displayed (byte).
SetAtt    equ     8     ;Sets the screen attribute of characters to
                        ; be displayed.
                        ;>>Parm is attribute (byte).
TextUp    equ     9     ;Displays a string on the screen.
                        ;>>Parm is an ASCII string of bytes,
                        ; which must be terminated by an EndO byte.
RepChar   equ    10     ;Displays a single character on the screen
                        ; a number of times.
                        ;>>Parms are char to be displayed followed
                        ; by byte count of times to output byte.
Cls       equ     11    ;Clears screen and makes text cursor
                        ; invisible.
                        ;>>No parms.
SetMStart equ     12    ;Sets location of maze start.
                        ;>>Parms are X then Y coords (both bytes).
Mup       equ     13    ;Draws maze wall upwards.
                        ;>>Parm is byte length to draw in characters.
Mrt       equ     14    ;Draws maze wall right.
                        ;>>Parm is byte length to draw in characters.
Mdn       equ     15    ;Draws maze wall downwards.
                        ;>>Parm is byte length to draw in characters.
Mlt       equ     16    ;Draws maze wall left.
                        ;>>Parm is byte length to draw in characters.
SetAStart equ     17    ;Sets arrow starting location.
                        ;>>Parms are X then Y coordinates
                        ; (both bytes).
Aup       equ     18    ;Animates arrow going up.
                        ;>>No parms.
Art       equ     19    ;Animates arrow going right.
                        ;>>No parms.
Adn       equ     20    ;Animates arrow going down.
                        ;>>No parms.
Alt       equ     21    ;Animates arrow going left.
                        ;>>No parms.
DoRep     equ     22    ;Repeats the command that follows
                        ; a specified number of times.
                        ;>>Parm is repetition count (one byte).
;
EndO            equ     0       ;used to indicate the end of a
                                ; string of text in a TextUp
                                ; command.
;********************************************************************
; The sequences of bytes and words between this line and the next
; line of stars are the entire mini-program that our interpreter will
; execute. This mini-program will initialize the screen, put text on
; the screen, draw a maze, and animate an arrow through the maze.
;
DemoScreen$ label byte  ;this is the main mini-program that our
                        ; interpreter will execute
; Initialize the screen
        db SubProg
        dw InitScreen$
; Put up words
        db SetXY,0,0, SetXYInc,0,1, TextUp,'START',EndO
        db SetXY,79,20, TextUp,'END',EndO
; Draw the maze
        db SetMstart,4,0, Mrt,8, Mdn,4, Mrt,4, Mup,3, Mrt,4, Mdn,3
        db Mrt,4, Mdn,8, Mrt,3, Mup,3, Mrt,5, Mup,9, Mrt,17, Mdn,9
        db Mrt,5, Mdn,3, Mrt,4, Mup,10, Mrt,12, Mdn,18, Mrt,6
        db SetXY,4,2, Mrt,4, Mdn,2, Mlt,4, Mdn,18, Mrt,12, Mup,4
        db Mrt,4, Mdn,4, Mrt,11, Mup,11, Mrt,5, Mup,9, Mrt,9, Mdn,9
        db Mrt,5, Mdn,11, Mrt,12, Mup,4, Mrt,4, Mdn,4, Mrt,10
        db SetXY,8,6, SubProg
        dw Box4x6$
        db SetXY,8,14, SubProg
        dw Box4x6$
        db SetXY,24,14, SubProg
        dw Box4x6$
        db SetXY,54,14, SubProg
        dw Box4x6$
        db SetXY,62,4, SubProg
        dw Box4x6$
        db SetXY,16,6, SubProg
        dw Box4x4$
        db SetXY,16,12, SubProg
        dw Box4x4$
        db SetXY,62,12, SubProg
        dw Box4x4$
; Animate the arrow through the maze.
        db SetAStart,3,0, Alt,2, Adn,2, Art,2, Aup,2
        db SetXY,0,0
        db DoRep,5,SubProg
        dw SpinAround$
        db Alt,2, Adn,1, Art,9, Adn,4, Alt,4, Adn,8, Art,8, Adn,8
        db Alt,8, Aup,8, Art,8, Aup,2, Art,8, Adn,2, Art,7, Aup,3
        db Art,5, Aup,9, Art,13, Adn,9, Art,5, Adn,11, Art,8, Aup,10
        db Art,8, Aup,8, Alt,8, Adn,8, Art,8, Adn,10, Art,8, Adn,1
        db Art,2, Aup,2, DoRep,5,SubProg
        dw SpinAround$
        db Alt,2, Adn,1, Art,1
        db Done
; Subprogram to clear the screen and initialize drawing variables.
InitScreen$ db  SetXY,0,0, SetAtt,7, SetXYInc,1,0, Cls, Done
; Subprograms to draw boxes.
Box4x4$    db Mrt,4, Mdn,4, Mlt,4, Mup,4, Mrt,2, Done
Box4x6$    db Mrt,4, Mdn,6, Mlt,4, Mup,6, Mrt,2, Done
; Subprogram to spin the arrow around a square.
SpinAround$ db Alt,2, Adn,2, Art,2, Aup,2, Done
;********************************************************************
; Data for outputting text characters to the screen.
Text_Out_Data   label byte
Cursor_X_Coordinate     db      0
Cursor_Y_Coordinate     db      0
Cursor_X_Increment      db      1
Cursor_Y_increment      db      0
Character_Attribute     db      7
Last_Maze_Direction     db      0ffh ;0-up, 1-rt, 2-dn, 3-lt
                                     ; 0ffh-starting
AnimateLastCoordinates  dw      0    ;low byte is X, high byte is Y
;
; Jump table used by Interp to call the subroutines associated
; with the various function numbers equated above. The functions
; called through this jump table constitute the mini-language
; used in this program.
;
Function_Table label word       ;list of function addresses
        dw      Done%           ; which correspond one for
        dw      SubProg%        ; one with the commands defined
        dw      SetXY%          ; with EQU above
        dw      SetXYInc%
        dw      Set%            ;Set%, MOut%, and Animate% all use
        dw      Set%            ; the function number to determine
        dw      Set%            ; which byte to set or which
        dw      Set%            ; direction is called for
        dw      Set%
        dw      TextUp%
        dw      RepChar%
        dw      Cls%
        dw      SetMStart%
        dw      MOut%
        dw      MOut%
        dw      MOut%
        dw      MOut%
        dw      SetAStart%
        dw      Animate%
        dw      Animate%
        dw      Animate%
        dw      Animate%
        dw      DoRep%
;
; Program start point.
;
Start   proc    far
        push    cs      ;code and data segments are the
        pop     ds      ; same for this program
        mov     si,offset DemoScreen$   ;point to mini-program
        call    Interp                  ;execute it
        mov     ah,1    ;wait for a key before clearing the
        int     21h     ; the screen and ending
        mov     ah,15   ;get the current screen mode
        int     10h     ; so it can be set to force
        sub     ah,ah   ; the screen to clear and the
        int     10h     ; cursor to reset
        mov     ah,4ch
        int     21h     ;end the program
Start   endp
;
; Mini-interpreter main loop and dispatcher. Gets the next
; command and calls the associated function.
;
Interp  proc near
        cld
GetNextCommand:
        lodsb                           ;get the next command
        mov     bl,al
        xor     bh,bh                   ;convert to a word in BX
        shl     bx,1                    ;*2 for word lookup
        call    [bx+Function_Table]     ;call the corresponding
                                        ; function
        jmp short GetNextCommand        ;do the next command
;
; The remainder of the listing consists of functions that
; implement the commands supported by the mini-interpreter.
;
; Ends execution of mini-program and returns to code that
; called Interp.
;
Done%:
        pop     ax      ;don't return to Interp
        ret             ;done interpreting mini-program or subprogram
                        ; so return to code that called Interp
;
; Executes a subprogram.
;
SubProg%:
        lodsw                   ;get the address of the subprogram
        push    si              ;save pointer to where to
                                ; resume the present program
        mov     si,ax           ;address of subprogram
        call    Interp          ;call interpreter recursively
                                ; to execute the subprogram
        pop     si              ;restore pointer and resume
        ret                     ; the program
;
; Sets the screen coordinates at which text will be drawn.
;
SetXY%:
        lodsw
        mov     word ptr [Cursor_X_Coordinate],ax
        ret
;
; Sets the amount by which the cursor will move after each
; character is output to the screen.
;
SetXYInc%:
        lodsw
        mov     word ptr [Cursor_X_Increment],ax
        ret
;
; Sets individual X coordinate, Y coordinate, X movement after
; character is output to the screen,  Y movement, or character
; attribute depending on function number.
;
Set%:
        shr     bx,1            ;calculate the command number
        lodsb                   ; get the new value
        mov     [bx+Text_Out_Data-SetX],al ;store in location
                                           ; corresponding to
                                           ; the command number
Return:
        ret
;
; Displays a string of text on the screen.
;
TextUp%:
GetNextCharacter:
        lodsb                             ;get next text character
        or      al,al                     ;see if end of string
        je      Return                    ;if so, next command
        call    OutputCharacter           ;else output character
        jmp     short GetNextCharacter    ;next character
;
; Displays a single character on the screen multiple times.
;
RepChar%:
        lodsw                    ;get the character in AL
                                 ; and the count in AH
RepCharLoop:
        push    ax               ;save the character and count
        call    OutputCharacter  ;output it once
        pop     ax               ;restore count and character
        dec     ah               ;decrement count
        jne     RepCharLoop      ;jump if count not now 0
        ret
;
; Clears the screen and turns off the cursor.
;
Cls%:
        mov     ax,600h          ;BIOS clear screen parameters
        mov     bh,[Character_Attribute]
        xor     cx,cx
        mov     dx,184fh
        int     10h              ;clear the screen
        mov     ah,01            ;turn off cursor
        mov     cx,2000h         ; by setting bit 5 of the
        int     10h              ; cursor start parameter
        ret
;
; Sets the start coordinates for maze-drawing.
;
SetMStart%:
        lodsw   ;get both X and Y coordinates and store
        mov     word ptr [Cursor_X_coordinate],ax
        mov     [Last_Maze_Direction],0ffh  ;indicate no
        ret                                 ; last direction

;
; Maze-drawing tables.
;
XYincTable      db      0,-1, 1,0, 0,1, -1,0
                        ;X & Y increment pairs for the 4 directions
CharacterGivenDirectionTable db 179,196,179,196
                        ;vertical or horizontal line character to use
                        ; for a given direction
FirstCharGivenNewAndOldDirectionTable label byte
        db      179,218,179,191, 217,196,191,196 ;table of corner
        db      179,192,179,217, 192,196,218,196 ; characters
;
; Outputs a maze line to the screen.
;
MOut%:
        sub     bx,Mup+Mup      ;find new direction word index
        mov     ax,word ptr [bx+XYincTable]       ;set for new
        mov     word ptr [Cursor_X_Increment],ax  ; direction
        shr     bx,1    ;change to byte index from word index
        mov     al,[bx+CharacterGivenDirectionTable] ;get char for
                                                     ; this direction
        mov     ah,al                    ;move horizontal or vert
        mov     dl,[Last_Maze_Direction] ; character into AH
        mov     [Last_Maze_Direction],bl ;if last dir is 0ffh then
        or      dl,dl                    ; just use horiz or vert char
        js      OutputFirstCharacter     ;look up corner character
        shl     dl,1                     ; in table using last
        shl     dl,1                     ; direction*4 + new direction
        add     bl,dl                    ; as index
        mov     al,[bx+FirstCharGivenNewAndOldDirectionTable]
OutputFirstCharacter:
        push    ax                      ;AL has corner, AH side char
        call    OutputCharacter         ;put out corner character
        pop     ax                      ;restore side char to AH
        lodsb                           ;get count of chars for this
        dec     al                      ; side, minus 1 for corner
        xchg    al,ah                   ; already output
        jmp short RepCharLoop           ;put out side char n times
;
; Table of arrow characters pointing in four directions.
;
AnimateCharacterTable   db      24,26,25,27
;
; Animates an arrow moving in one of four directions.
;
Animate%:
        sub     bx,(Aup+Aup)                    ;get word dir index
        mov     ax,word ptr [XYIncTable+bx]     ;set move direction
        mov     word ptr [Cursor_X_Increment],ax
        lodsb                                   ;get move count
        shr     bx,1                            ;make into byte
        mov     ah,[bx+AnimateCharacterTable]   ; index and get
        xchg    al,ah                           ; char to animate
NextPosition:                                   ; into AL, AH count
        mov     dx,[AnimateLastCoordinates]     ;coords of last arrow
                                        ;move cursor to where last
                                        ; character was output
        mov     word ptr [Cursor_X_Coordinate],dx
        push    ax                      ;save char and count
        mov     al,20h                  ;output a space there
        call    OutputCharacter         ; to erase it
        pop     ax      ;restore char in AL, count in AH
        push    ax      ;save char and count
        mov     dx,word ptr [Cursor_X_Coordinate] ;store new coords
        mov     [AnimateLastCoordinates],dx       ; as last
        call    OutputCharacter                 ;output in new
        mov     cx,DELAY_COUNT                  ; location then
WaitSome:                                       ; wait so doesn't
        loop    WaitSome                        ; move too fast
        pop     ax                              ;restore count and
                                                ; character
        dec     ah                              ;count down
        jne     NextPosition                    ; if not done
        ret                                     ; do again
;
; Sets the animation start coordinates.
;
SetAStart%:
        lodsw                                   ;get both X & Y
        mov     [AnimateLastCoordinates],ax     ; coordinates and
        ret                                     ; store
;
; Repeats the command that follows the count parameter count times.
;
DoRep%:
        lodsb                           ;get count parameter
NextRep:
        push    si                      ;save pointer to command
                                        ; to repeat
        push    ax                      ;save count
        lodsb                           ;get command to repeat
        mov     bl,al                   ;convert command byte to
        xor     bh,bh                   ; word index in BX
        shl     bx,1                    ;
        call    [bx+Function_Table]     ;execute command once
        pop     ax                      ;get back the count
        dec     al                      ;see if it's time to stop
        je      DoneWithRep             ;jump if done all repetitions
        pop     si                      ;get back the pointer to the
                                        ; command to repeat, and
        jmp     NextRep                 ; do it again
DoneWithRep:
        pop     ax                      ;clear pointer to command to
                                        ; repeat from stack, leave
                                        ; SI pointing to the next
                                        ; command
        ret
;
Interp  endp
;
; Outputs a text character at the present cursor coordinates,
;  then advances the cursor coordinates according to the
;  X and Y increments.
;
OutputCharacter proc near
        push    ax              ;save the character to output
        mov     ah,2            ;set the cursor position
        mov     dx,word ptr [Cursor_X_Coordinate]
        xor     bx,bx           ;page 0
        int     10h             ;use BIOS to set cursor position
        pop     ax              ;restore character to be output
        mov     ah,9            ;write character BIOS function
        mov     bl,[Character_Attribute] ;set attribute
        mov     cx,1            ;write just one character
        int     10h             ;use BIOS to output character
                                ;advance X & Y coordinates
        mov     ax,word ptr [Cursor_X_Coordinate] ;both x & y Incs
        add     al,[Cursor_X_Increment]           ; can be negative
        add     ah,[Cursor_Y_Increment]           ; so must add bytes
                                                  ; separately
        mov     word ptr [Cursor_X_Coordinate],ax ;store new  X & Y
                                                  ; coordinates
        ret
OutputCharacter endp
;
_TEXT   ends
        end     Start                   ;start execution at Start