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.
http://groups.google.com/group/comp.lang.asm.x86/browse_frm/thread/87fc99c06b0a1d75?hl=en#
60
;; File: MINITERP.NSM
;; modified for NASM syntax. s_dubrov...@yahoo.com 14OCT2009
;; -f bin -l MINITERP.LST -o MINITERP.COM MINITERP.NSM
;; Note: has some int21h calls, use a dos box or cmd.exe
;; Ref: http://www.ddj.com/184408206?pgno=3
;
; 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
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 [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 [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 [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.
Text_Out_Data:
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.
;
_@MOut_:
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]
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 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 [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.
;
_@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 [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
ret
;; OutputCharacter endp
;;
60
;; 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.
TDelay:
; mov ah, 0 ;; test code in lieu of timer. user keypress
; int 16h ;; to advance animation frame.
; RET
push cx
push dx
TDelay2:
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
RET
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 ==--