Author Topic: Mini Interpreter  (Read 8124 times)


  • Guest
Mini Interpreter
« on: October 15, 2009, 06:38:11 PM »
Steve posted this to C.L.A.X today.  It is a NASM-syntax translation of a mini-interpreter which appeared in a 1989 issue of DDJ.

Code: [Select]

;; modified for NASM syntax. 14OCT2009
;; Note: has some int21h calls, use a dos box or cmd.exe
;; Ref:
; 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.
; 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     0FFFFh
; 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.

[SECTION .cseg vstart=0100h] ;; .com section
;; --== M a i n ==--
; Program start point.
Start:   ;; proc    far
push    cs      ; code and data segments are the
pop     ds      ; same for this program
mov  sp,0FFFEh   ;; seed SP to top of segment
mov     si, 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
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.
pop     ax ; don't return to Interp
ret        ; done interpreting mini-program or subprogram
; so return to code that called Interp
; Executes a subprogram.
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.
mov     word [Cursor_X_Coordinate],ax
; Sets the amount by which the cursor will move after each
; character is output to the screen.
mov     word [Cursor_X_Increment],ax
; Sets individual X coordinate, Y coordinate, X movement after
; character is output to the screen,  Y movement, or character
; attribute depending on function number.
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
; Displays a string of text on the screen.
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.
lodsw                    ; get the character in AL
; and the count in AH
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
; Clears the screen and turns off the cursor.
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
; Sets the start coordinates for maze-drawing.
lodsw   ; get both X and Y coordinates and store
mov     word [Cursor_X_Coordinate], ax
mov     [Last_Maze_Direction], byte 0ffh  ; indicate no
ret                                 ; last direction

;; --== Data to Interpreter to interpret ==--
; 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.

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_
; 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.
sub     bx,Mup#+Mup#      ; find new direction word index
mov     ax,word [bx+XYIncTable]       ; set for new
mov     word [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]
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 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.
sub     bx,(Aup#+Aup#)                ; get word dir index
mov     ax, word [XYIncTable+bx]      ; set move direction
mov     word [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 [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 [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
call TDelay  ;;

pop     ax                         ; restore count and
; character
dec     ah                         ; count down
jne     NextPosition               ; if not done
ret                                ; do again
; Sets the animation start coordinates.
lodsw                               ; get both X & Y
mov     [AnimateLastCoordinates],ax ; coordinates and
ret                                 ; store
; Repeats the command that follows the count parameter count times.
lodsb                       ; get count parameter
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
pop     ax                  ; clear pointer to command to
; repeat from stack, leave
; SI pointing to the next
; command
;; 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 [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 [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 [Cursor_X_Coordinate], ax    ; store new  X & Y
; coordinates
;; OutputCharacter endp


;; my TDelay kludge reads the RTC seconds for a 1
;; second delay in the animation frame, alittle too slow,
;; but the original delay routine had too short of time
;; resolution so the animation's last frame was what got
;; displayed.

; mov  ah, 0  ;; test code in lieu of timer. user keypress
; int  16h    ;; to advance animation frame.

push cx
push dx

mov  ah, 2
int  1Ah     ;; read real-time clock values, CX, DX, AH affected.
;; CH`hrs_BCD,CL`minutes_BCD,DH`seconds_BCD,DL`tz_flg
cmp  [byte BCD_val], dh  ;; look for unequal as state change
jne  ETDelay
;; .else. same value, loop until changed.
jmp  TDelay2

ETDelay:       ;; .diff. so second rolled into next second, finally.
mov  [BCD_val], dh  ;; update, we'll be back here within 1 sec.

pop  dx
pop  cx

BCD_val: db 0

;; --== eof ==--
;;  -for nasm-
;; Valid chars in labels are letters, numbers, _, $, #, @, ~, ., and ?.
;; The only characters which may be used as the first character of an
;; identifier are letters, . (with special meaning: see section 3.9),
;; _ and ?. An identifier may also be prefixed with a $ to indicate
;; that it is intended to be read as an identifier and not a reserved
;; word; thus, if some other module you are linking with defines a
;; symbol called eax, you can refer to $eax in NASM code to distinguish
;; the symbol from the register. Maximum length of an identifier is
;; 4095 characters.
;; --== eof2 ==--