_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