472 lines
20 KiB
Plaintext
472 lines
20 KiB
Plaintext
_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
|
||
|
||
|
||
|
||
|
||
|