Subject: What flavor of ancient X86 assembler is this ?
Date: Tue, 12 Dec 2000 06:09:44 -0000
From: "jdm" <jdm1intx@DIE_SPAMBOT_DIEhome.com>
Organization: Excite@Home - The Leader in Broadband http://home.com/faster
Newsgroups: comp.lang.asm.x86

This is the venerable source for the X86 version of Tiny Basic, as
written by Michael Sullivan, lo these many years ago.  I am trying to
get it to assemble with TASM 5.0 (16 bit) and it doesn't like it very
much.  A lot of the problems I've been able to get around by adding
Borland-esque declarations and keywords, but 4 lines after the START:
label you'll see a prime example of code that I am having a hard time
deciphering.  I wonder if this code was written for some old shareware
assembler like A86 or something.  I'd really like to get this
converted to TASM-compatible code so I could step through it with the
debugger and learn more about it.  I'm learning all I can about
interpreters and compilers and this would really give me some valuable
insights I think.

Thanks,

John M.

;***************************************************************
;*
;*
;*       TINY BASIC FOR INTEL 8086
;*
;*
;*        VERSION: 1.1
;*
;*         BY
;*
;*        MICHAEL SULLIVAN
;*                              BASED
;*                               ON
;*                       LI-CHEN WANG'S
;*
;*                    8080 TINY BASIC
;*
;*
;*                    27 JUNE 1982
;*
;*  @COPYLEFT
;*  ALL WRONGS RESERVED
;*
;* NOTE:
;*  8080 REGISTERS HAVE BEEN MAPPED AS FOLLOWS:
;*
;*  8080  8086
;* -------------------------------------
;*
;*  BC <-> CX
;*  DE <-> DX
;*  HL <-> BX
;*
;*
;* VERS 1.1 - SUPPORT MS-DOS INTERUPT I/O
;*     IMPROVE RND ACTION
;*     SUPPORT TIME AND DATE FROM MS-DOS
;*
;**************************************************************
 ;
 ;
 ORG 100H ;STANDARD MS-DOS START ADDR.
START:
 MOV SP,STACK ;SET UP STACK
 MOV DX,MSG1 ;GET SIGN-ON MSG
 CALL PRTSTG ;SEND IT
 MOV B,[BUFFER-2],80H ;INIT CMD LINE BUFFER
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is the line I don't get.  What is B?  It isn't declared as a
variable, so it must be some kind of Assembler-specific shorthand for
something else.  There are other lines farther in the code that refer
to a "W", as in CMP W,[TXTUNF],TXTBGN.  Again, W is not declared as a
variable, so what is it?  TASM just tags it as an undeclared
reference.

;
; MAIN
;
; THIS IS THE MAIN LOOP THAT COLLECTS THE TINY BASIC PROGRAM
; AND STORES IT IN MEMORY.
;
; AT START, IT PRINTS OUT "(CR)OK(LF)", AND INITIALIZES THE
; STACK AND SOME OTHER INTERNAL VARIABLES. THEN IT PROMPTS
; ">" AND READS A LINE. IF THE LINE STARTS WITH A NONZERO
; NUMBER, THIS NUMBER IS THE LINE NUMBER. THE LINE NUMBER
; (IN 16 BIT BINARY) AND THE REST OF THE LINE (INCLUDING
; ITS (CR))IS STORED IN MEMORY. IF A LINE WITH THE SAME
; LINE NUMBER IS ALREADY THERE, IT IS REPLACED BY THE NEW
; ONE. IF THE REST OF THE LINE CONSISTS OF A (CR) ONLY, IT
; IS STORED AND ANY EXISTING LINE WITH THE SAME LINE
; NUMBER IS DELETED.
;
; AFTER A LINE IS INSERTED, REPLACED, OR DELETED, THE
; PROGRAM LOOPS BACK AND ASKS FOR ANOTHER LINE. THIS LOOP
; WILL BE TERMINATED WHEN IT READS A LINE WITH ZERO OR NO
; LINE NUMBER: CONTROL IS THEN TRANSFERED TO "DIRECT".
;
; THE TINY BASIC PROGRAM SAVE AREA STARTS AT THE MEMORY
; LOCATION LABELED "TXTBGN" AND ENDS AT "TXTEND". WE ALWAYS
; FILL THIS AREA STARTING AT "TXTBGN", THE UNFILLED PORTION
; POINTED TO BY THE CONTENTS OF THE MEMORY LOCATION LABELED
; "TXTUNF".
;
; THE MEMORY LOCATION "CURRNT" POINTS TO THE LINE NUMBER
; THAT IS CURRENTLY BEING INTERPRETED. WHILE WE AR IN THIS
; LOOP OR WHILE WE ARE INTERPRETING A DIRECT COMMAND
; (SEE NEXT SECTION), "CURRNT" SHOULD POINT TO A 0.
;
RSTART:
 MOV SP,STACK ;SET STACK POINTER
ST1:
 CALL CRLF
 MOV DX,OK ;DE->STRING
 SUB AL,AL
 CALL PRTSTG ;PRINT PROMPT
 MOV W,[CURRNT],0 ;CURRENT LINE # = 0
ST2:
 MOV W,[LOPVAR],0
 MOV W,[STKGOS],0
ST3:
 MOV AL,'>' ;PROMPT ">" NOW
 CALL GETLN ;READ A LINE
 PUSH DI ;DI -> END OF LINE
ST3A:
 MOV DX,BUFFER ;DX -> BEGINNING OF LINE
 CALL TSTNUM ;TEST IF IT'S A NUMBER
 MOV AH,0
 CALL IGNBLNK
 OR BX,BX ;BX:= VALUE OF # OR 0 IF NO # FOUND
 POP CX ;CX -> END OF LINE
 JNZ ST3B
 JMP DIRECT
ST3B:
 DEC DX
 DEC DX
 MOV AX,BX ;GET LINE #
 MOV DI,DX
 STOW  ;VALUE OF LINE # THERE
 PUSH CX
 PUSH DX ;BX,DX -> BEGIN,END
 MOV AX,CX
 SUB AX,DX
 PUSH AX ;AX:= # BYTES IN LINE
 CALL FNDLN ;FIND THIS LINE IN SAVE
 PUSH DX ;AREA, DX -> SAVE AREA
 JNZ ST4 ;NZ:NOT FOUND, INSERT
 PUSH DX ;Z:FOUND, DELERE IT
 CALL FNDNXT ;FIND NEXT LINE
   ;DE -> NEXT LIE
 POP CX ;CX -> LINE TO BE DELETED
 MOV BX,[TXTUNF] ;BX -> UNFILLED SAVE AREA
 CALL MVUP ;MOVE UP TO DELETE
 MOV BX,CX ;TXTUNF -> UNFILLED AREA
 MOV [TXTUNF],BX ;UPDATE
ST4:
 POP CX ;GET READY TO INSERT
 MOV BX,[TXTUNF] ;BUT FIRST CHECK IF
 POP AX ;AX = # CHARS IN LINE
 PUSH BX ;IS 3 (LINE # AND CR)
 CMP AL,3 ;THEN DO NOT INSERT
 JZ RSTART ;MUST CLEAR THE STACK
 ADD AX,BX ;COMPUTE NEW TSTUNF
 MOV BX,AX ;BX -> NEW UNFILLED AREA
ST4A:
 MOV DX,TXTEND ;CHECK TO SEE IF THERE
 CMP BX,DX ;IS ENOUGH SPACE
 JC ST4B ;SORRY, NO ROOM FOR IT
 JMP QSORRY
ST4B:
 MOV [TXTUNF],BX ;OK, UPDATE TXTUNF
 POP DX ;DX -> OLD UNFILLED AREA
 CALL MVDOWN
 POP DX ;DX -> BEGIN, BX -> END
 POP BX
 CALL MVUP ;MOVE NEW LINE TO SAVE AREA
 JP ST3

TSTV: MOV AH,64 ;TEST VARIABLES
 CALL IGNBLNK
 JC RET
TSTV1:
 JNZ TV1 ;NOT @ ARRAY
 CALL PARN ;@ SHOULD BE FOLLOWED
 ADD BX,BX
 JNC SS1B ;IS INDEX TOO BIG?
        JMP     QHOW
SS1B: PUSH DX ;WILL IT OVERWRITE
 XCHG DX,BX ;TEXT?
 CALL SIZE ;FIND SIZE OF FREE
 CMP BX,DX ;AND CHECK THAT
 JNC SS1A ;IFF SO, SAY "SORRY"
        JMP     ASORRY
SS1A:
 MOV BX,VARBGN ;IFF NOT, GET ADDRESS
 SUB BX,DX ;OF @(EXPR) AND PUT IT
 POP DX ;IN HL
 RET ;C FLAG IS CLEARED
TV1:
 CMP AL,27 ;NOT @, IS IT A TO Z?
CMC:
 ;IFF NOT, RETURN C FLAG
 JC RET ;IFF NOT, RETURN C FLAG
 INC DX
TV1A:
 MOV BX,VARBGN ;COMPUTE ADDRESS OF
 MOV AH,0 ;CLEAR UPPER BYTE
 ADD AX,AX ;AX:=AX*2 (WORD STORAGE)
 ADD BX,AX ;BX:=VARBGN+2*AL
 RET  ;USE CARRY AS ERROR INDICATOR
;
; TSTNUM - AT ENTRY DX -> BUFFER OF ASCII CHARACTERS
;
TSTNUM:
 MOV BX,0 ;****TSTNUM****
 MOV CH,BH ;TEST IFF THE TEXT IS
 MOV AH,0 ;FOR CMP IN IGNBLNK
 CALL IGNBLNK ;A NUMBER.
TN1:
 CMP AL,'0' ;IFF NOT, RETURN 0 IN
 JC RET ;B AND HL
 CMP AL,':' ;IFF NUMBERS, CONVERT
 JNC RET ;TO BINARY IN BX AND
 MOV AL,0F0H  ;SET AL TO # OF DIGITS
 AND AL,BH ;IFF BH>255, THERE IS NO
 JNZ QHOW ;ROOM FOR NEXT DIGIT
 INC CH ;CH COUNTS NUMBER OF DIGITS
 PUSH CX
 MOV AX,BX ;BX:=10*BX+(NEW DIGIT)
        MOV     CX,10
 PUSH DX ;SAVE DX
        MUL     AX,CX
        MOV     BX,AX   ;PARTIAL RESULT NOW IN BX
 POP DX ;RESTORE
 MOV SI,DX
 LODB         ;ASCII DIGIT IN AL NOW
        SUB     AL,48   ;CONVERT TO BINARY
        MOV     AH,0
        ADD     BX,AX   ;FULL RESULT NOW IN BX
 POP CX
 LODB            ;REPEAT FOR MORE DIGITS
 LAHF  ;SAVE FLAGS
 INC DX
 SAHF  ;RESTORE FLAGS
 JNS TN1 ;QUIT IF NO NUM OR OVERFLOW
QHOW:
 PUSH DX ;****ERROR: "HOW?"****
AHOW:
 MOV DX,HOW
 JMP ERROR
HOW:
 DB 'HOW?',0DH
OK:
 DB 'OK',0DH
WHAT:
 DB 'WHAT?',0DH
SORRY:
 DB 'SORRY',0DH
;
;*
;**********************************************************
;*
;* *** TABLES *** DIRECT *** & EXEC ***
;*
;* THIS SECTION OF THE CODE TESTS A STRING AGAINST A TABLE.
;* WHEN A MATCH IS FOUND, CONTROL IS TRANSFERRED TO THE SECTION
;* OF CODE ACCORDING TO THE TABLE.
;*
;* AT 'EXEC' DX SHOULD POINT TO THE STRING AND BX SHOULD POINT
;* TO THE TABLE-1. AT 'DIRECT', DX SHOULD POINT TO THE STRING,
;* BX WILL BE SET UP TO POINT TO TAB1-1, WHICH IS THE TABLE OF
;* ALL DIRECT AND STATEMENT COMMANDS.
;*
;* A '.' IN THE STRING WILL TERMINATE THE TEST AND THE PARTIAL
;* MATCH WILL BE CONSIDERED AS A MATCH. E.G., 'PR.',
;* 'PRI.', 'PRIN.', OR 'PRINT' WILL ALL MATCH 'PRINT'.
;*
;* THE TABLE CONSISTS OF ANY NUMBER OF ITEMS. EACH ITEM
;* IS A STRING OF CHARACTERS WITH BIT 7 SET TO 1 IN LAST CHAR
;* A JUMP ADDRESS IS STORED FOLLOWING EACH CHARACTER ENTRY.
;*
;* END OF TABLE IS AN ITEM WITH A JUMP ADDRESS ONLY. IF THE
;* STRING DOES NOT MATCH ANY OF THE OTHER ITEMS, IT WILL
;* MATCH THIS NULL ITEM AS DEFAULT. THE DEFAULT IS INDICATED
;* BY FOLLOWING THE 80H DEFAULT INDICATOR.
;*

TAB1:   EQU $ ;DIRECT COMMANDS
 DM 'LIST'
 DW LIST ;EXECUTION ADDRESSES
 DM 'EDIT'
 DW EDIT
 DM 'E'
 DW EDIT ;HAVE SHORT FORM DEFINED ALSO
 DM 'RUN'
 DW RUN
 DM 'NEW'
 DW NEW
 DM 'LOAD'
 DW DLOAD
 DM 'SAVE'
 DW DSAVE
 DM 'BYE' ;GO BACK TO DOS (EXIT TBASIC)
 DW BYE
TAB2: EQU $ ;DIRECT/STATEMENT
 DM 'NEXT'
 DW NEXT ;EXECUTION ADDRESSES
 DM 'LET'
 DW LET
 DM 'OUT'
 DW OUTCMD
 DM 'POKE'
 DW POKE
 DM 'WAIT'
 DW WAITCM
 DM 'IF'
 DW IFF
 DM 'GOTO'
 DW GOTO
 DM 'GOSUB'
 DW GOSUB
 DM 'RETURN'
 DW RETURN
 DM 'REM'
 DW REM
 DM 'FOR'
 DW FOR
 DM 'INPUT'
 DW INPUT
 DM 'PRINT'
 DW PRINT
 DM 'STOP'
 DW STOP
 DB 128 ;SIGNALS END
   ;REMEMBER TO MOVE DEFAULT DOWN.
 DW DEFLT ;LAST POSIBILITY
TAB4: EQU $ ;FUNCTIONS
 DM 'RND'
 DW RND
 DM 'INP'
 DW INP
 DM 'PEEK'
 DW PEEK
 DM 'USR'
 DW USR
 DM 'ABS'
 DW ABS
 DM 'SIZE'
 DW SIZE
 DB 128 ;SIGNALS END
   ;YOU CAN ADD MORE FUNCTIONS BUT REMEMBER
   ;TO MOVE XP40 DOWN
 DW XP40
TAB5: EQU $ ;"TO" IN "FOR"
 DM 'TO'
TAB5A: DW FR1
 DB 128
 DW QWHAT
TAB6: EQU $ ;"STEP" IN "FOR"
 DM 'STEP'
TAB6A: DW FR2
 DB 128
 DW FR3
TAB8: EQU $ ;RELATION OPERATORS
 DM '>='
 DW XP11 ;EXECUTION ADDRESS
 DM '#'
 DW XP12
 DM '>'
 DW XP13
 DM '='
 DW XP15
 DM '<='
 DW XP14
 DM '<'
 DW XP16
 DB 128
 DW XP17
;
; END OF PARSER ACTION TABLE
;
;
; AT ENTRY BX -> COMMAND TABLE (ABOVE)
;    DX -> COMMAND LINE (I.E. "BUFFER")
;
DIRECT:
 MOV BX,TAB1-1 ;***DIRECT***
 ;*
EXEC: EQU $ ;***EXEC***
EX0:
 MOV AH,0
 CALL IGNBLNK ;IGNORE LEADING BLANKS
 PUSH DX ;SAVE POINTER
 MOV SI,DX
EX1: LODB  ;GET CHAR WHERE DX ->
 INC DX ;PRESERVE POINTER
 CMP AL,'.' ;WE DECLARE A MATCH
 JZ EX4
 INC BX
 MOV AH,[BX]
 AND AH,127 ;STRIP BIT 7
 CMP AL,AH ;COMPARISON NOW EASY
 JZ EX2
 ; NO MATCH - CHECK NEXT ENTRY
EX0A: CMP B,[BX],128 ;BYTE COMPARE
 JNC EX0B
 INC BX
 JP EX0A
 ; AT THIS POINT HAVE LAST LETTER
EX0B: ADD BX,3 ;GET PAST EXECUTION ADDRESS
 CMP B,[BX],128 ;FOUND DEFAULT?
 JZ EX3A ;IF SO, EXECUTE DEFAULT
 DEC BX ;CORRECT FOR PRE-INCREMENT
 POP DX ;RESTORE POINTER
 JP EX0 ;LOOK SOME MORE FOR A MATCH
EX4: INC BX
 CMP B,[BX],128
 JC EX4
 JP EX3
;
EX3A: DEC SI
 JP EX3 ;CORRECT SI FOR DEFAULT EXECUTION
EX2: CMP B,[BX],128 ;END OF RESERVED WORD?
 JC EX1 ;NO - CHECK SOME MORE
 ; AT THIS POINT NEED TO GET EXECUTION ADDRESS

EX3: INC BX ;BX -> EXECUTION ADDRESS
 POP AX ;CLEAR STACK
 MOV DX,SI ;RESET POINTER
 JMP [BX] ;DO IT
;*
;
;
; WHAT FOLLOWS IS THE CODE TO ECECUTE DIRECT AND STATEMENT COM-
; MANDS. CONTROL IS TRANSFERED TO THESE POINTS VIA THE  COMMAND
; TABLE LOOKUP CODE OF 'DIRECT' AND 'EXEC' IN THE LAST SECTION.
; AFTER THE COMMAND IS EXECUTED,  CONTROL  IS  TRANSFERRED   TO
; OTHER SECTIONS AS FOLLOWS:
;
; FOR 'LIST','NEW', ANS 'STOP': GO BACK TO 'RSTART'
;
; FOR 'RUN',: GO EXECUTE THE FIRST STORED LINE IFF ANY; ELSE
;   GO BACK TO RSTART.
;
; FOR 'GOTO' AND 'GOSUB': GO EXECUTE THE TARGET LINE.
;
; FOR 'RETURN' AND 'NEXT': GO BACK TO SAVED RETURN LINE.
;
; FOR ALL OTHERS: IFF 'CURRNT' -> 0, GO TO 'RSTART', ELSE
;   GO EXECUTE NEXT COMMAND. (THIS IS DONE
;   IN 'FINISH'.)
;
;
; ****NEW****STOP****RUN (& FRIENDS)****GOTO****
;
; 'NEW(CR)' SETS 'TXTUNF' TO POINT TO 'TXTBGN'
;
; 'STOP(CR)' GOES BACK TO 'RSTART'
;
; 'RUN(CR)' FINDS THE FIRST STROED LINE, STORES ITS ADDRESS
; (IN 'CURRNT'), AND START TO EXECUTE IT. NOTE THAT ONLY
; THOSE COMMANDS IN TAB2 ARE LEGAL FOR STORED PROGRAMS.
;
; THERE ARE THREE MORE ENTRIES IN 'RUN':
;
; 'RUNNXL' FINDS NEXT LINE, STORES ITS ADDR AND EXEC IT.
; 'RUNTSL' STORES THE ADDRESS OF THIS LINE AND EXECUTES IT
; 'RUNSML' CONTINUES THE EXECUTION ON SAME LINE.
;
; 'GOTO(EXPR)' EVALUATES THE EXPRESSION, FINDS THE TARGET LINE,
; AND JUMPS TO 'RUNTSL' TO DO IT.
;
; 'DLOAD' LOADS A NAMES PROGRAM FROM DISK (ANYNAME.TBI)
;
; 'DSAVE' SAVES A NAMES PROGRAM ON DISK
;
; 'FCBSET' SETS UP THE MSDOS FILE CONTROL BLOCK FOR SUBSEQUENT
; DISK I/O.
;
;
NEW:
 MOV W,[TXTUNF],TXTBGN
 ;
STOP:
 CALL ENDCHK ;****STOP(CR)****
 JMP RSTART
 ;
RUN:
 CALL ENDCHK ;****RUN(CR)****
 MOV DX,TXTBGN ;FIRST SAVED LINE
 ;
RUNNXL:
 MOV BX,0 ;****RUNNXL****
 CALL FNDLNP ;FIND WHATEVER LINE
 JNC RUNTSL ;C: PASSED TXTUNF, QUIT
        JMP     RSTART
 ;
RUNTSL:
 XCHG DX,BX ;****RUNTSL****
 MOV [CURRNT],BX ;SET 'CURRNT"->LINE #
 XCHG DX,BX
 INC DX
 INC DX
 ;
RUNSML:
 CALL CHKIO ;****RUNSML****
 MOV BX,TAB2-1 ;FIND COMMAND IN TABLE 2
 JMP EXEC ;AND EXECUTE IT
 ;
GOTO:
 CALL EXP ;****GOTO(EXPR)****
 PUSH DX ;SAVE FOR ERROR ROUTINE
 CALL ENDCHK ;MUST FIND A 0DH (CR)
 CALL FNDLN ;FIND THE TARGET LINE
 JZ GT1 ;NO SUCH LINE #
        JMP     AHOW
GT1: POP AX
 JP RUNTSL ;GO DO IT
 ;
 ; BDOS EQUATES (FOR MS-DOS)
 ;
BYE: EQU 0 ;BDOS EXIT ADDRESS
FCB: EQU 5CH
SETDMA: EQU 26
OPEN: EQU 15
READD: EQU 20
WRITED: EQU 21
CLOSE: EQU 16
MAKE: EQU 22
BCONIN: EQU 10 ;BUFFERED CONSOLE INPUT
DELETE: EQU 19
CONOUT: EQU 2 ;CONSOLE OUTPUT
CONST: EQU 11 ;CONSOLE STATUS
 ;
 ;
DLOAD:
 MOV AH,0
 CALL IGNBLNK ;IGNORE BLANKS
 PUSH BX ;SAVE H
 CALL FCBSET ;SET UP FILE CONTROL BLOCK
 PUSH DX ;SAVE THE REST
 PUSH CX ;SAVE THE REST
 MOV DX,FCB ;GET FCB ADDR
 MOV AH,OPEN ;PREPARE TO OPEN FILE
 INT 33 ;CALL MS-DOS TO OPEN FILE
 CMP AL,0FFH ;IS IT THERE?
 JNZ DL1 ;NO, SEND ERROR
        JMP     QHOW
DL1: XOR AL,AL ;CLEAR A
 MOV [FCB+32],AL ;START AT RECORD 0
 MOV DX,TXTBGN ;GET BEGINNING
LOAD:
 PUSH DX ;SAVE DMA ADDRESS
 MOV AH,SETDMA
 INT 33 ;CALL MS-DOS TO SET DAM ADDR
 MOV AH,READD
 MOV DX,FCB
 INT 33 ;CALL MS-DOS TO READ SECTOR
 CMP AL,1 ;DONE?
 JC RDMORE ;NO, READ MORE
 JZ LL1
LOAD1: JMP QHOW ;BAD READ OR NO DELIMITER
LL1: MOV AH,CLOSE
 MOV DX,FCB
 INT 33 ;CALL MS-DOS TO CLOSE FILE
 POP BP ;DMA ADDR IN BP
 SUB BP,100H ;BACKUP
 MOV CX,100H ;MAX LOOPS
RDM1: INC BP ;PRE INC
 CMP W,[BP],0 ;FOUND DELIMITER?
 LOOPNZ RDM1 ;KEEP LOOKING
 CMP CL,0 ;MAC LOOPS EXECUTED?
 JZ LOAD1 ;GIVE ERROR IF SO
 MOV [TXTUNF],BP ;UPDATE POINTER
 POP CX ;GET OLD REG BACK
 POP DX ;GET OLD REG BACK
 POP BX ;GET OLD REG BACK
 CALL FINISH ;FINISH
RDMORE:
 POP DX ;GET DMA ADDR
 MOV BX,80H ;GET 128
 ADD BX,DX ;ADD IT TO DMA ADDR
 XCHG DX,BX ;BACK IN D
 JMP LOAD ;AND READ SOME MORE
 ;
DSAVE:
 CMP W,[TXTUNF],TXTBGN ;SEE IF ANYTHING TO SAVE
 JNZ DS1A
 JMP QWHAT
DS1A:
 MOV BP,[TXTUNF]
 MOV W,[BP],0 ;SET DELIMITER
 MOV AH,0
 CALL IGNBLNK ;IGNORE BLANKS
 PUSH BX ;SAVE BX
 CALL FCBSET ;SETUP FCB
 PUSH DX
 PUSH CX ;SAVE OTHERS
 MOV DX,FCB
 MOV AH,DELETE
 INT 33 ;CALL MS-DOS TO ERASE FILE
 MOV DX,FCB
 MOV AH,MAKE
 INT 33 ;CALL MS-DOS TO MAKE A NEW ONE
 CMP AL,0FFH ;IS THERE SPACE?
 JNZ DS1
        JMP     QHOW ;NO, ERROR
DS1: XOR AL,AL ;CLEAR A
 MOV [FCB+32],AL ;START AT RECORD 0
 MOV DX,TXTBGN ;GET BEGINNING
SAVE:
 PUSH DX ;SAVE DMA ADDR
 MOV AH,SETDMA
 INT 33 ;CALL MS-DOS TO SET DMA ADDR
 MOV AH,WRITED
 MOV DX,FCB
 INT 33 ;CALL MS-DOS TO WRITE SECTOR
 OR AL,AL ;SET FLAGS
 JZ SS1 ;IF NOT ZERO, ERROR
        JMP     QHOW
SS1: POP DX ;GET DMA ADDR BACK
 MOV AX,DX
 CMP AX,[TXTUNF] ;SEE IF DONE
 JZ SAVDON
 JNC SAVDON ;JUMP IF DONE
WRITMOR:
 MOV BX,80H
 ADD BX,DX
 XCHG DX,BX ;GET IT TO D
 JP SAVE
SAVDON:
 MOV AH,CLOSE
 MOV DX,FCB
 INT 33 ;CALL MS-DOS TO CLOSE FILE
 POP CX ;GET REGS BACK
 POP DX ;GET REGS BACK
 POP BX ;GET REGS BACK
 CALL FINISH
 ;
FCBSET:
 MOV BX,FCB ;GET FCB ADDR
 MOV B,[BX],0 ;CLEAR ENTRY TYPE
FNCLR:
 INC BX
 MOV B,[BX],' ' ;CLEAR TO SPACE
 MOV AX,FCB+8
 CMP AX,BX ;DONE?
 JNZ FNCLR ;NO, DO IT AGAIN
 INC BX
 MOV B,[BX],'T' ;SET FILE TYPE TO 'TBI'
 INC BX
 MOV B,[BX],'B'
 INC BX
 MOV B,[BX],'I'
EXRC:
 INC BX
 MOV B,[BX],0
 MOV AX,FCB+15
 CMP AX,BX
 JNZ EXRC ;NO, CONTINUE
 MOV BX,FCB+1 ;GET FILENAME START
FN:
 MOV SI,DX
 LODB ;GET CHAR
 CMP AL,0DH ;IS IT A 'CR'
 JZ RET ;YES, DONE
 CMP AL,'!' ;LEGAL CHAR?
 JNC     FN1 ;NO, SEND ERROR
        JMP     QWHAT
FN1: CMP AL,'[' ;AGAIN
 JC FN2 ;DITTO
        JMP     QWHAT
FN2: MOV [BX],AL ;SAVE IT IN FCB
 INC BX
 INC DX
 MOV AX,FCB+9
 CMP AX,BX ;LAST?
 JNZ FN ;NO, CONTINUE
 RET ;TRUNCATE AT EIGHT CHARS
;
;
; ****LIST**** AND ****PRINT**** AND ****EDIT****
;
; LIST HAS TWO FORMS:
; 'LIST(CR)' LISTS ALL SAVED LINES
; 'LIST #(CR)' START LIST AT THIS LINE #
; YOU CAN STOP LISTING BY CONTROL C KEY
;
; PRINT COMMAND IS 'PRINT ....;' OR 'PRINT ....(CR)'
; WHERE '....' IS A LIST OF EXPRESIONS, FORMATS, BACKARROWS, AND
; STRINGS. THESE ITEMS ARE SEPERATED BY COMMAS.
;
; A FORMAT IS A POUND SIGN FOLLOWED BY A NUMBER. IT CONTROLS THE
; NUMBER OF SPACES THE VALUE OF AN EXPRESSION IS TO BE PRINTED.
; TED. IT STAYS EFFECTIVE FOR THE REST OF THE PRINT, UNLESS
; CHANGED BY ANOTHER FORMAT. IF NO FORMAT SPEC, 6 POSITIONS
; WILL BE USED.
;
; A STRING IS QUOTED IN A PAIR OF SINGLE QUOTES OR DOUBLE
; QUOTES.
;
; A BACK-ARROW MEANS GENERATE A (CR) WITHOUT (LF).
;
; A (CRLF) IS GENERATED AFTER THE ENTIRE LIST HAS BEEN PRINT OR
; IF THE LIST IS A NULL LIST. HOWEVER IF THE LIST ENDED WITH A
; COMMA, NO (CR) IS GENERATED.
;
;
LIST:
 CALL TSTNUM ;TEST IFF THERE IS A #
 CALL ENDCHK ;IFF NO # WE GET A 0
 CALL FNDLN ;FIND THIS OR NEXT LINE
LS1:
 JNC LS2 ;C: PASSED TXTUNF
        JMP     RSTART
LS2: CALL PRTLN ;PRINT THE LINE
 CALL CHKIO ;SEE IF ^X OR ^C
 CALL FNDLNP ;FIND NEXT LINE
 JP LS1 ;LOOP BACK
 ;
;
EDIT:
 CALL TSTNUM ;TEST IF THERE IS A #
 CALL ENDCHK ;AT END?
 CALL FNDLN ;FIND SPEC LINE OR NEXT LINE
 PUSH DX ;SAVE LINE #
 JNC ED2 ;C: PASSED TXTUNF
 POP DX ;THROW AWAY LINE #
ED1: JMP RSTART
ED2:
 CALL PRTLN ;PRINT THE LINE
 POP DX ;GET LINE # BACK
 MOV B,[OCSW],0 ;DIRECT OUTPUT TO BUFFER
 MOV B,[BUFFER-1],0 ;CLEAR CHAR COUNT
 MOV B,[PRTLN1+1],4 ;PRINT ONE LESS SPACE
 MOV DI,BUFFER ;PREPARE TO MOVE
 CALL PRTLN
 MOV B,[OCSW],0FFH ;REDIRECT OUTPUT TO CONSOLE
 DEC [BUFFER-1] ;AVOID CR?
 MOV B,[PRTLN1+1],5 ;RESTORE PRTLN
 JMP ST3 ;PROMPT AND GETLINE ONLY
PRINT:
 MOV CL,6 ;C:= # OF SPACES
 MOV AH,';' ;CHECK FOR ';' IN IGNBLNK
 CALL IGNBLNK ;IGNORE BLANKS
 JNZ PR2 ;JUMP IF ';' NOT FOUND
 CALL CRLF ;GIVE CR,LF AND
 JMP RUNSML ;CONTINUE SAME LINE
PR2:
 MOV AH,0DH
 CALL IGNBLNK
 JNZ PR0
 CALL CRLF ;ALSO GIVE CRLF AND
 JMP RUNNXL ;GOTO NEXT LINE
PR0:
 MOV AH,'#'
 CALL IGNBLNK
 JNZ PR1
 CALL EXP ;YES, EVALUATE EXPR
 MOV CL,BL ;AND SAVE IT IN C
 JP  PR3 ;LOOK FOR MORE TO PRINT
PR1:
 CALL QTSTG ;OR IS IT A STRING?
 JP PR8 ;IFF NOT, MUST BE EXPRESSION
PR3:
 MOV AH,','
 CALL IGNBLNK
 JNZ PR6
 CALL FIN ;IN THE LIST
 JP PR0 ;LIST CONTINUES
PR6:
 CALL CRLF ;LIST ENDS
 CALL FINISH
PR8:
 CALL EXP ;EVAL THE EXPR
 PUSH CX
 CALL PRTNUM ;PRINT THE VALUE
 POP CX
 JP PR3 ;MORE TO PRINT?
;
;
; ****GOSUB**** AND ****RETURN****
;
; 'GOSUB (EXPR);' OR 'GOSUB EXPR(CR)' IS LIKE THE 'GOTO' COMMAND
; EXCEPT THAT THE CURRENT TEXT POINTER, STACK POINTER ETC.   ARE
; SAVED SO THAT EXECUTION CAN BE CONTINUED AFTER THE  SUBROUTINE
; 'RETURN'. IN ORDER THAT 'GOSUB' CAN BE NESTED (AND EVEN RECUR-
; SIVE), THE SAVE AREA MUST BE  STACKED.  THE  STACK  POINTER IS
; SAVED IN 'STKGOS'. THE OLD 'STKGOS' IS SAVED IN THE STACK. IF
; WE ARE IN THE MAIN ROUTINE, 'STKGOS' IS ZERO (THIS WAS DONE BY
; THE "MAIN" SECTION OF THE CODE),  BUT  WE  STILL  SAVE  IT  AS
; A FLAG FOR NO FURTHER RETURNS.
;
; 'RETURN(CR)' UNDOES EVERYTHING THAT 'GOSUB' DID, AND THUS  RE-
; TURNS THE EXECUTION TO THE COMMAND AFTER THE MOST RECENT  'GO-
; SUB'. IFF 'STKGOS' IS ZERO, IT INDICATES THAT WE NEVER  HAD  A
; 'GOSUB' AND IS THUS AN ERROR.
;
;
GOSUB:
 CALL PUSHA ;SAVE THE CURRENT 'FOR'
 CALL EXP ;PARAMETERS
 PUSH DX
 CALL FNDLN ;FIND THE TARGET LINE
 JZ GS1 ;NOT THERE, SAY "HOW?"
        JMP     AHOW
GS1: MOV BX,[CURRNT] ;FOUND IT, SAVE OLD
 PUSH BX ;'CURRNT' OLD 'STKGOS'
 MOV BX,[STKGOS]
 PUSH BX
 MOV BX,0 ;AND LOAD NEW ONES
 MOV [LOPVAR],BX
 ADD BX,SP
 MOV [STKGOS],BX
 JMP RUNTSL ;THEN RUN THAT LINE
RETURN:
 CALL ENDCHK ;THERE MUST BE A 0DH
 MOV BX,[STKGOS] ;OLD STACK POINTER
 OR BX,BX
 JNZ RET1 ;SO, WE SAY: "WHAT?"
        JMP     QWHAT
RET1: XCHG BX,SP  ;ELSE RESTORE IT
 POP BX ;ELSE RESTORE IT
 MOV [STKGOS],BX ;AND THE OLD 'STKGOS'
 POP BX
 MOV [CURRNT],BX ;AND THE OLD 'CURRNT'
 POP DX ;OLD TEXT POINTER
 CALL POPA ;OLD "FOR" PARAMETERS
 CALL FINISH ;AND WE ARE BACK HOME
;
;
; ****FOR**** AND ****NEXT****
;
;
; 'FOR' HAS TWO FORMS:
; 'FOR VAR=EXP1 TO EXP2 STEP EXP3'
; 'FOR VAR=EXP1 TO EXP2'
; THE SECOND FORM MEANS THE SAME AS THE FIRST FORM WITH EXP3=1.
;
; TBI WILL FIND THE VARIABLE VAR AND SET ITS VALUE TO THE CUR-
; RENT VALUE OF EXP1. IT ALSO  EVALUATES  EXP2  AND  EXP3  AND
; SAVES ALL OF THESE TOGETHER  WITH  THE  TEXT  POINTER ETC IN
; THE 'FOR' SAVE AREA, WHICH CONSISTS OF 'LOPVAR',   'LOPINC',
; 'LOPLMT', 'LOPLN', AND 'LOPPT'. IFF THERE IS ALREADY   SOME-
; THING IN THE SAVE AREA (THIS IS  INDICATED  BY  A   NON-ZERO
; 'LOPVAR'), THEN THE OLD SAVE AREA IS SAVED IN THE STACK  BE-
; FORE THE NEW ONE OVERWRITES IT.
;
; TBI WILL THEN DIG IN THE  STACK  AND  FIND  OUT IFF     THIS
; SAME VARIABLE WAS USED IN  ANOTHER  CURRENTLY  ACTIVE    FOR
; LOOP. IT THAT IS THE CASE THEN THE OLD 'FOR'   LOOP IS   DE-
; IVATED (PURGED FROM THE STACK).
;
; 'NEXT VAR' SERVES AS THE LOGICAL (NOT NECESSARILLY PHYSICAL)
; END OF THE 'FOR' LOOP. THE CONTROL VARIABLE VAR. IS  CHECKED
; WITH THE 'LOPVAR'. IFF THEY ARE NOT THE SAME, TBI DIGGS   IN
; THE STACK TO FIND THE RIGHT ONE  AND  PURGES  ALL THOSE THAT
; DID NOT MATCH. EITHER WAY, TBI THEN ADDS THE 'STEP' TO  THAT
; VARIABLE AND CHECKS THE RESULT WITH THE LIMIT.  IFF  IT   IS
; WITHIN THE LIMIT, CONTROL LOOPS BACK TO THE COMMAND  FOLLOW-
; ING THE 'FOR'. IFF OUTSIDE THE LIMIT, THE SAVE AREA IS PURG-
; ED AND EXECUTION CONTINUES.
;
;
FOR:
 CALL PUSHA ;SAVE THE OLD SAVE AREA
 CALL SETVAL ;SET THE CONTROL VAR.
 DEC BX
 MOV [LOPVAR],BX ;SAVE TGAT
 MOV BX,TAB5-1 ;USE 'EXEC' TO LOOK
 JMP EXEC ;FOR THE WORD 'TO'
FR1:
 CALL EXP ;EVALUATE THE LIMIT
 MOV [LOPLMT],BX ;SAVE THAT
 MOV BX,TAB6-1 ;USED 'EXEC' TO LOOK
 JMP EXEC ;FOR THE WORD 'STEP'
FR2:
 CALL EXP ;FOUND IT, GET STEP
 JP FR4 ;FOUND IT, GET STEP
FR3:
 MOV BX,1 ;NOT FOUND, SET TO ONE
FR4:
 MOV [LOPINC],BX ;SAVE THAT TOO
FR5:
 MOV BX,[CURRNT] ;SAVE CURRENT LINE #
 MOV [LOPLN],BX
 XCHG DX,BX ;AND TEXT POINTER
 MOV [LOPPT],BX
 MOV CX,10 ;DIG INTO STACK TO
 MOV BX,[LOPVAR] ;FIND 'LOPVAR'
 XCHG DX,BX
 MOV BX,CX ;BX:=10 NOW
 ADD BX,SP
 JP FR7A
FR7:
 ADD BX,CX
FR7A: MOV AX,[BX] ;GET THAT OLD 'LOPVAR'
 OR AX,AX
 JZ FR8 ;0 SAYS NO MORE IN IT
 CMP AX,DX ;SAME AS THIS ONE?
 JNZ FR7
 XCHG DX,BX
 MOV BX,0 ;THE OTHER HALF?
 ADD BX,SP
 MOV CX,BX
 MOV BX,10
 ADD BX,DX
 CALL MVDOWN ;AND PURGE 10 WORDS
 XCHG BX,SP  ;IN THE STACK
FR8:
 MOV BX,[LOPPT] ;JOB DONE, RESTORE DE
 XCHG DX,BX
 CALL FINISH ;AND CONTINUE
 ;
NEXT:
 CALL TSTV ;GET ADDR OF VAR
 JNC NX4 ;NO VARIABLE, "WHAT?"
        JMP     QWHAT
NX4: MOV [VARNXT],BX ;YES, SAVE IT
NX0:
 PUSH DX ;SAVE TEXT POINTER
 XCHG DX,BX
 MOV BX,[LOPVAR] ;GET VAR IN 'FOR'
 MOV AL,BH
 OR AL,BL ;0 SAY NEVER HAD ONE
 JNZ NX5 ;SO WE ASK: "WHAT?"
        JMP     AWHAT
NX5: CMP DX,BX ;ELSE WE CHECK THEM
 JZ NX3 ;OK, THEY AGREE
 POP DX ;NO, LET'S SEE
 CALL POPA ;PURGE CURRENT LOOP
 MOV BX,[VARNXT] ;AND POP ONE LEVEL
 JMP NX0 ;GO CHECK AGAIN
NX3:
 MOV DL,[BX] ;COME HERE WHEN AGREED
 INC BX
 MOV DH,[BX] ;DE = VAL OF VAR
 MOV BX,[LOPINC]
 PUSH BX
 ADD BX,DX
 XCHG DX,BX ;ADD ONE STEP
 MOV BX,[LOPVAR] ;PUT IT BACK
 MOV [BX],DL
 INC BX
 MOV [BX],DH
 MOV BX,[LOPLMT] ;HL-> LIMIT
 POP AX
 XCHG AH,AL
 OR AX,AX
 JNS NX1 ;STEP > 0
 XCHG DX,BX
NX1:
 CALL CKHLDE ;COMPARE WITH LIMIT
 POP DX ;RESTORE TEXT POINTER
 JC NX2 ;OUTSIDE LIMIT
 MOV BX,[LOPLN] ;WITHIN LIMIT, GO
 MOV [CURRNT],BX ;BACK TO THE SAVED
 MOV BX,[LOPPT] ;'CURRNT' AND TEXT
 XCHG DX,BX ;POINTER
 CALL FINISH ;POINTER
NX2:
 CALL POPA ;PURGE THIS LOOP
 CALL FINISH
;
;
; ****REM**** AND ****IF**** AND ****LET*****
;
;
; 'REM' CAN BE FOLLOWED BY ANYTHING AND IS IGNORED BY TBI. TBI
; TREATS IT LIKE AN 'IF' WITH A FALSE CONDITION.
;
; 'IF' IS FOLLOWED BY AN EXPR. AS A CONDITION AND ONE OR  MORE
; COMMANDS (INCLUDING OTHER 'IF'S) SEPERATED  BY  SEMI-COLONS.
; NOTE THAT THE WORD 'THEN' IS NOT USED.  TBI  EVALUATES   THE
; EXPR. IFF IT IS NON-ZERO, EXECUTION CONTINUES. IFF THE EXPR.
; IS ZERO, THE COMMANDS THAT FOLLOW ARE IGNORED AND  EXECUTION
; CONTINUES AT THE NEXT LINE.
;
; 'IPUT' COMMANS IS LIKE THE 'PRINT' COMMAND, AND IS  FOLLOWED
; BY A LIST OF ITEMS. IFF THE ITEM IS A  STRING  IN  SINGLE OR
; DOUBLE QUOTES, OR IS A BACK-ARROW, IT HAS THE SAME EFFEDT AS
; PRINTED OUT FOLLOWED BY A COLON. THEN TBI WAITS FOR AN EXPR.
; TO BE TYPEN IN. THE VARIABLE IS THEN  SET  TO  THE  VALUE OF
; THIS EXPR. IFF THE VARIABLE IS PROCEDED BY A STRING  PRINTED
; FOLLOWED BY A COLON. TBI THEN WAITS FOR INPUT EXPR. AND SETS
; THE VARIABLE TO THE VALUE OF THE EXPR.
;
; IFF THE INPUT EXPR. IS INVALID,  TBI  WILL  PRINT  "WHAT?" ,
; "HOW?",OR "SORRY" AND REPRINT THE PROMPT AND REDO THE INPUT.
; THE EXECUTION WILL NOT TERMINATE UNLESS YOU TYPE CONTROL-C .
; THIS IS HANDLED IN 'INPERR'.
;
; 'LET' IS FOLLOWED BY A LIST OF ITEMS SEPERATED  BY  COMMAS .
; EACH ITEM CONSISTS OF A VARIABLE,  AN  EQUAL  SIGN,  AND  AN
; EXPR. TBI EVALUATES THE EXPR. AND SETS THE VARIABLE TO  THAT
; VALUE. TBI WILL ALSO HANDLE 'LET' COMMAND WITHOUT THE   WORD
; 'LET'. THIS IS DONE BY 'DEFLT'.
;
;
;
REM:
 MOV BX,0 ;****REM****
 JP IFF1A ;JUMP AROUND EXPR
 ;
IFF:
 CALL EXP ;****IF****
IFF1A: CMP BX,0 ;IS THE EXPR = 0?
 JZ IFF1 ;NO, CONTINUE
        JMP     RUNSML
IFF1: CALL FNDSKP ;YES, SIKP REST OF LINE
 JC IFF2 ;YES, SIKP REST OF LINE
        JMP     RUNTSL
IFF2: JMP RSTART ;YES, SIKP REST OF LINE
 ;
INPERR:
 MOV BX,[STKINP] ;****INPERR****
 XCHG BX,SP  ;RESTORE OLD STACK POINTER
 POP BX ;AND OLD 'CURRNT'
 MOV [CURRNT],BX
 POP DX
 POP DX ;REDO INPUT
 ;
INPUT: EQU $ ;****INPUT****
IP1:
 PUSH DX ;SAVE IN CASE OF ERROR
 CALL QTSTG ;IS NEXT ITEM A STRING?
 JP IP2 ;NO
 CALL TSTV ;YES, BUT FOLLOWED BY A
 JC IP4 ;VARIABLE? NO.
 JP IP3 ;YES. INPUT VAR.
IP2:
 PUSH DX ;SAVE FOR 'PRTSTG'
 CALL TSTV ;MUST BE A VAR NOW
 JNC IP2A ;"WHAT" IT IS NOT!
        JMP     QWHAT
IP2A: MOV SI,DX
 LODB  ;GET READY FOR 'RTSTG'
 MOV CL,AL
 SUB AL,AL
 MOV DI,DX
 STOB
 POP DX
 CALL PRTSTG ;PRINT STRING AS PROMPT
 MOV AL,CL
 DEC DX
 MOV DI,DX
 STOB
IP3:
 PUSH DX
 XCHG DX,BX
 MOV BX,[CURRNT] ;ALSO SAVE 'CURRNT'
 PUSH BX
 MOV BX,IP1
 MOV [CURRNT],BX ;NEG NUMBER AS FLAG
 MOV [STKINP],SP
 PUSH DX ;OLD HL
 MOV AL,':' ;PRINT THIS TOO
 CALL GETLN ;AND GET A LINE
IP3A:
 MOV DX,BUFFER ; POINTS TO BUFFER
 CALL EXP ;EVALUATE INPUT
 NOP         ;CAN BE 'CALL ENDCHK'
 NOP   ;CAN BE 'CALL ENDCHK'
 NOP   ;CAN BE 'CALL ENDCHK'
 POP DX ;OK,GET OLD HL
 XCHG DX,BX ;OK,GET OLD HL
 MOV [BX],DX
 POP BX ;GET OLD 'CURRNT'
 MOV [CURRNT],BX
 POP DX ;AND GET OLD TEXT POINTER
IP4:
 POP AX
 MOV AH,','
 CALL IGNBLNK
 JNZ IP5
 JP IP1 ;YES, MORE ITEMS
IP5:
 CALL FINISH
 ;
DEFLT:
 MOV SI,DX
 LODB ;****DEFLT****
 CMP AL,0DH ;EMPTY LINE IS OK
 JZ LT1 ;ELSE IT IS 'LET'
 ;
LET:
 CALL SETVAL ;****LET****
 MOV AH,','
 CALL IGNBLNK
 JNZ LT1
 JP LET ;ITEM BY ITEM
LT1:
 CALL FINISH ;UNTIL FINISH
;
;
; ****EXPR****
;
; 'EXPR' EVALUATES ARITHMETICAL OR LOGICAL EXPRESSIONS.
; <EXPR>::=<EXPR2>
;    <EXPR2><REL.OP><EXPR2>
;
; WHERE <REL.OP> IS ONE OF THE OPERATORS IN TAB8 AND THE RE-
; SULT OF THESE OPERATIONS IS 1 IFF TRUE AND  0  IFF  FALSE.
;
; <EXPR2>::=(+ OR -)<EXPR3>(+ OR -<EXPR3>(....)
;
; WHERE () ARE OPTIONAL AND (....) ARE OPTIONAL REPEATS.
;
; <EXPR3>::=<EXPR4>(<* OR /><EXPR4>)(....)
; <EXPR4>::=<VARIABLE>
;   <FUNCTION>
;   (<EXPR>)
;
; <EXPR> IS RECURSIVE SO THAT VARIABLE '@' CAN HAVE AN EXPR
; AS INDEX, FUCTIONS CAN HAVE AN <EXPR> AS ARGUMENTS,   AND
; <EXPR4> CAN BE AN <EXPR> IN PARANTHESES.
;
;
EXP: CALL EXPR2
 PUSH BX
EXPR1:
 MOV BX,TAB8-1 ;LOOKUP REL.OP
 JMP EXEC ;GO DO IT
XP11:
 CALL XP18
 JC RET ;NO RETURN HL=0
 MOV BL,AL ;YES, RETURN HL=1
 RET
XP12:
 CALL XP18
 JZ RET ;FALSE, RETURN HL=0
 MOV BL,AL ;TRUE, RETURN HL=1
 RET
XP13:
 CALL XP18 ;REL.OP '>'
 JZ RET ;FALSE
 JC RET ;ALSO FALSE, HL=0
 MOV BL,AL ;TRUE, HL=1
 RET
XP14:
 CALL XP18 ;REL OP '<='
 MOV BL,AL ;SET HL=1
 JZ RET ;REL. TRUE, RETURN
 JC RET ;REL. TRUE, RETURN
 MOV BL,BH ;ELSE SET HL=0
 RET
XP15:
 CALL XP18 ;REL OP '='
 JNZ RET ;FALSE, RETURN HL=0
 MOV BL,AL ;ELSE SET HL=1
 RET
XP16:
 CALL XP18 ;REL.OP '<'
 JNC RET ;FALSE, RETURN HL=0
 MOV BL,AL ;ELSE SET HL=1
 RET
XP17:
 POP BX ;NOT REL OP
 RET ;RETURN HL=<EPTR2>
XP18:
 MOV AL,CL ;SUBROUTINE FOR ALL
 POP BX ;REL.OP'S
 POP CX ;REL.OP'S
 PUSH BX ;REVERSE TOP OF STACK
 PUSH CX ;REVERSE TOP OF STACK
 MOV CL,AL
 CALL EXPR2 ;GET 2ND EXPRESSION
 XCHG DX,BX ;VALUE IN DE NOW
 POP AX
 PUSH BX
 MOV BX,AX ;LAST 3 INSTR FOR XTHL INST!
 CALL CKHLDE ;COMPARE 1ST WITH SECOND
 POP DX
 MOV BX,0 ;SET HL=0, A=1
 MOV AL,1 ;SET HL=0, A=1
 RET
 ;
EXPR2:
 MOV AH,'-'
 CALL IGNBLNK ;NEGATIVE SIGN?
 JNZ XP21
 MOV BX,0 ;YES, FAKE '0-'
 JP XP26 ;TREAT LIKE SUBTRACT
XP21:
 MOV AH,'+' ;POSITIVE SIGN?
 CALL IGNBLNK
XP22:
 CALL EXPR3 ;1ST <EXPR3>
XP23:
 MOV AH,'+'
 CALL IGNBLNK ;ADD?
 JNZ XP25 ;NOTE OFFSET WHAS 21 BYTES IN 8080 VERSION
 PUSH BX ;YES, SAVE VALUE
 CALL EXPR3 ;GET 2ND <EXPR3>
XP24:
 XCHG DX,BX ;2ND IN DE
 POP AX ;THIS + NEXT 2 LINES FOR 8080 XTHL INST!!
 PUSH BX
 MOV BX,AX ;BX <-> [SP] NOW, [SP]->BUFFER,BX=OLD EXPR3
 ADD BX,DX
 POP DX
 JNO XP23 ;CHECK FOR OVERFLOW
XP24A: JMP QHOW ;ELSE WE HAVE OVERFLOW
XP25:
 MOV AH,'-'
 CALL IGNBLNK ;SUBTRACT?
 JNZ RET
XP26: PUSH BX ;YES, SAVE 1ST <EXPR3>
 CALL EXPR3 ;GET 2ND <EXPR3>
 CALL CHGSGN
 JP XP24
 ;
EXPR3:
 CALL EXPR4 ;GET 1ST <EXPR4>
XP31:
 MOV AH,'*'
 CALL IGNBLNK ;MULTIPLY?
 JNZ XP34
 PUSH BX ;YES, SAVE 1ST
 CALL EXPR4 ;AND GET 2ND <EXPR4>
 XCHG DX,BX ;2ND IN DE NOW
 POP AX ;SUBSITUTE FOR 8080 XTHL
 PUSH BX
 IMUL AX,DX ;AX:=AX*DX
 JO XP32 ;SEE INTEL BOOK ON OVERFLOW FLAG
 MOV BX,AX ;RESULT NOW IN BX
 JP XP35 ;LOOK FOR MORE
XP34:
 MOV AH,'/'
 CALL IGNBLNK ;DIVIDE?
 JNZ RET
 PUSH BX ;YES, SAVE 1ST <EXPR4>
 CALL EXPR4 ;AND GET SECOND ONE
 XCHG DX,BX ;PUT 2ND IN DE
 POP AX ;REPLACEMENT FOR XTHL
 PUSH BX
 MOV BX,AX
 OR DX,DX
 JNZ XP34A ;SAY "HOW?"
XP32: JMP AHOW
XP34A: CALL DIVIDE ;USE SUBROUTINE
 MOV BX,CX ;GET RESULT
 MOV CX,6 ;SIX SPACES
XP35:
 POP DX ;AND TEXT POINTER
 JP XP31 ;LOOK FOR MORE TERMS
 ;
EXPR4:
 MOV BX,TAB4-1 ;FIND FUCNTION IN TAB4
 JMP EXEC ;AND GOT DO IT
XP40:
 CALL TSTV ;NO, NOT A FUNCTION
 JC XP41 ;NOR A VARIABLE
 MOV AL,[BX] ;VARIABLE
 LAHF
 INC BX
 SAHF
 MOV BH,[BX] ;VALUE IN HL
 MOV BL,AL ;VALUE IN HL
 RET
XP41:
 CALL TSTNUM ;OR IS IT A NUMBER?
 MOV AL,CH ;# OF DIGITS
 OR AL,AL
 JNZ RET ;OK
PARN:
 MOV AH,'('
 CALL IGNBLNK ;NO DIGIT, MUST BE
 JNZ PARN1
 CALL EXP ;"(EXPR)"
PARN1: MOV AH,')'
 CALL IGNBLNK ;"(EXPR)"
 JNZ XP43 ;******WHY CHECK THIS?******
XP42:
 RET
XP43:
 JMP QWHAT ;ELSE SAY: "WHAT?"
 ;
RND:
 CALL PARN ;****RND(EXPR)****
 OR BX,BX
 JNS RND1 ;MUST BE POSITIVE
 JNZ RND1 ;AND NON-ZERO
        JMP     QHOW
RND1:
 PUSH CX
 PUSH DX
 MOV AH,2CH ;GET TIME
 INT 33 ;ASK MS-DOS
 MOV AX,327
 MOV DH,0
 MUL AX,DX ; 0<=AX<=32700
 XCHG DX,BX
 MOV BX,AX
 CALL DIVIDE ;RND(N)=MOD(M,N)+1
 POP DX
 POP CX
 INC BX
 RET
 ;
ABS:
 CALL PARN ;****ABS(EXPR)****
 CALL CHKSGN ;CHECK SIGN
 OR AX,BX
 JNS RET ;OK
 JMP QHOW ;SO SAY: "HOW?"
SIZE:
 MOV BX,[TXTUNF] ;****SIZE****
 PUSH DX ;GET THE NUMBER OF FREE
 XCHG DX,BX ;BYTES BETWEEN 'TXTUNF'
SIZEA:
 MOV BX,VARBGN ;AND 'VARBGN'
 SUB BX,DX
 POP DX
 RET
;
;
; ****OUT**** AND ****INP**** AND ****WAIT**** AND
; ****POKE**** AND ****PEEK**** AND ****USR****
;
;
; 'OUT I,J(,K,L)'
;
; OUTPUTS EXPRESSION 'J' TO PORT 'I', AND MAY BE REPEATED AS
; IN DATA 'L' TO PORT 'K' AS MANY TIMES AS NEEDED. THIS COM-
; MAND MODIFIES *, A SMALL SECTION OF CODE ABOVE ADDRESS 2K.
;
; 'INP (I)'
;
; THIS FUNCTION RETURNDS DATA READ FROM  INPUT  PORT 'I'  AS
; ITS VALUE. IT ALSO MODIFIES CODE JUST ABOVE 2K.
;
; 'WAIT I,J,K'
;
; THIS COMMAND READS THE STATUS OF PORT 'I', EXCLUSIVE  OR'S
; THE RESULT WITH 'K', IF THE RESULT IS ONE, OR IF NOT  WITH
; ZERO, AND'S WITH 'J' AND RETURNS WHEN THE RESULT IS   NON-
; ZERO. ITS MODIFIED CODE IS ALSO ABOVE 2K.
;
; 'POKE I,J(,K,L)
;
; THIS COMMAND WORKS LIKE OUT EXCEPT THAT IT PUTS  DATA  'J'
; INTO MEMORY LOCATION 'I'.
;
; 'PEEK (I)'
;
; THIS FUNCTION WORKS LIKE INP EXCEPT THAT IT PUTS DATA  'J'
; FROM MEMORY LOCATION 'I'.
;
; 'USR(I(,J))'
;
; USR CALL A MACHINE LANGUAGE SUBROUTINE AT LOCATION 'I'  IF
; THE OPTIONAL PARAMETER 'J' IS USED ITS VALUE IS PASSED  IN
; HL. THE VALUE OF THE FUNCTION SHOULD BE RETURNED IN HL.
;
;
OUTCMD:
 CALL EXP
 MOV AL,BL
 MOV [OUTIO+1],AL
 MOV AH,','
 CALL IGNBLNK
 JZ OUT1 ;FOUND MORE TO WORK ON
 JMP QWHAT
OUT1: CALL EXP
 MOV AL,BL
 CALL OUTIO
 MOV AH,','
 CALL IGNBLNK
 JNZ OUTCMD1
 JP OUTCMD
OUTCMD1:CALL FINISH
WAITCM:
 CALL EXP
 MOV AL,BL
 MOV [WAITIO+1],AL
 MOV AH,','
 CALL IGNBLNK
 JZ WT1
 JMP QWHAT
WT1: CALL EXP
 PUSH BX
 MOV AH,','
 CALL IGNBLNK
 JNZ WAIT1
 CALL EXP
 MOV AL,BL
 POP BX
 MOV BL,AL
 JP WAIT2
WAIT1: MOV BH,0
WAIT2: JMP WAITIO
INP:
 CALL PARN
 MOV AL,BL
 MOV [INPIO+1],AL
 MOV BX,0
 JMP INPIO
 JP QWT
POKE:
 CALL EXP
 PUSH BX
 MOV AH,','
 CALL IGNBLNK
 JZ POK1
 JMP QWHAT
POK1: CALL EXP
 MOV AL,BL
 POP BX
 MOV [BX],AL
 MOV AH,','
 CALL IGNBLNK
 JNZ POK2
 JP POKE
POK2: CALL FINISH
PEEK:
 CALL PARN
 MOV BL,[BX]
 MOV BH,0
 RET
 JMP QWHAT
USR:
 PUSH CX
 MOV AH,'('
 CALL IGNBLNK
 JNZ QWT
 CALL EXP ;EXPR
 MOV AH,')'
 CALL IGNBLNK ;EXPR
 JNZ PASPRM
 PUSH DX
 MOV DX,USRET
 PUSH DX
 PUSH BX
 RET ;CALL USR ROUTINE
PASPRM:
 MOV AH,','
 CALL IGNBLNK
 JNZ USRET1
 PUSH BX
 CALL EXP
 MOV AH,')'
 CALL IGNBLNK
 JNZ USRET1
 POP CX
 PUSH DX
 MOV DX,USRET
 PUSH DX
 PUSH CX
 RET ;CALL USR ROUTINE
USRET:
 POP DX
USRET1: POP CX
 RET
QWT: JMP QWHAT
;
;
; ****DIVIDE**** AND ****CHKSGN****
; ****CHKSGN**** AND ****CKHLDE****
;
;
; 'DIVIDE DIVIDES BX BY DX, RESULT IN CX, REMAINDER IN BX
;
; 'CHKSGN' CHECKS SIGN OF BX. IFF +, NO CHANGE. IFF -, CHANGE
; SIGN AND FLIP SIGN OF C
;
; 'CHGSGN' CHANGES SIGN OF BX AND CL UNCONDITIONALLY.
;
; 'CKHLDE' CHECK SIGN OF BX AND DX. IFF DIFFERENT, BX AND DX
; ARE INTERCHANGED. IFF SAME SIGN, NOT INTERCHANGED.   EITHER
; CASE, BX AND DX ARE THEN COMPARED TO SET THE FLAGS.
;
;
DIVIDE:
 PUSH DX ;PRESERVE DX ACCROSS CALL
 PUSH DX
 XOR DX,DX
 POP CX
 MOV AX,BX
 IDIV AX,CX
 MOV CX,AX ;QUOTIENT
 MOV BX,DX ;REMAINDER
 POP DX ;DX RESTORED
 RET
 ;
CHKSGN:
 OR BX,BX ;SET FLAGS TO CHECK SIGN
 JNS RET ;IFF -, CHANGE SIGN
 ;
CHGSGN:
 NOT BX ;****CHGSGN****
 INC BX
 XOR CH,128
 RET
 ;
CKHLDE:
 MOV AL,BH
 XOR AL,DH ;SAME SIGN?
 JNS CK1 ;YES, COMPARE
 XCHG DX,BX
CK1:
 CMP BX,DX
 RET
;
;
; ****SETVAL**** AND ****FIN**** AND ****ENDCHK****
; ****ERROR**** AND FRIENDS
;
;
; 'SETVAL' EXPECTS A VARIABLE, FOLLOWED BY AN EQUAL SIGN AND
; THEN AN EXPR. IT EVALUATES THE EXPR AND SETS THE  VARIABLE
; TO THAT VALUE.
;
; 'FIN' CHECKS THE END OF A COMMAND. IFF IT ENDED WITH ";" ,
; EXECUTION CONTINUES. IFF IT ENDED WITH A CR, IT FINDS  THE
; NEXT LINE AND CONTINUES FROM THERE.
;
; 'ENDCHK' CHECKS IFF A COMMAND IS ENDED WITH A CR, THIS  IS
; REQUIRED IN CERTAIN COMMANDS. (GOTO, RETURN, AND STOP,ETC)
;
; 'ERROR' PRINTS THE STRING POINTED BY DX (AND ENDS  WITH  A
; CR). IT THEN PRINTS THE LINE POINTED BY 'CURRNT' WITH A ?.
; INSERTED AT WHERE THE OLD TEXT POINTER (SHOULD  BE  ON TOP
; OF THE STACK) POINTS TO. EXECUTION OF TB IS  STOPPED   AND
; TBI IS RESTARTED. HOWEVER, IFF 'CURRNT' -> ZERO (INDICAT -
; ING A DIRECT COMMAND), THE DIRECT COMMAND IS NOT PRINTED ,
; AND IFF 'CURRNT' -> NEGATIVE # (INDICATING 'INPUT' COMMAND
; THE INPUT LINE IS NOT PRINTED AND EXECUTION IS NOT TERMIN-
; ATED BUR CONTINUED AT 'INPERR').
;
; RELATED TO 'ERROR' ARE THE FOLLOWING:
;
; 'QWHAT' SAVES TEXT POINTER IN STACK AND GETS MESSAGE
;  "WHAT?"
; 'AWHAT' JUST GETS MESSAGE "WHAT?" AND JUMPS TO ERROR
;
; 'QSORRY' AND 'ASORRY' DO THE SAME KIND OF THING.
;
; 'QHOW' AND 'AHOW' IN THE ZERO PAGE SECTION ALSO   DO
;  THIS.
;
;
SETVAL:
 CALL TSTV ;SEE IT IT'S A VARIABLE
 JC QWHAT ;"WHAT" NO VARIABLE
 PUSH BX ;SAVE ADDR OF VARIABLE
 MOV AH,'='
 CALL IGNBLNK
 JNZ SV1
 CALL EXP
 MOV CX,BX ;VALUE IN CX NOW
 POP BX ;GET ADDR
 MOV [BX],CL ;SAVE VALUE
 INC BX
 MOV [BX],CH ;SAVE VALUE
 RET
SV1:
 JMP QWHAT ;NO '=' SIGN
 ;
FIN:
 MOV AH,';'
 CALL IGNBLNK
 JNZ FI1
 POP AX
 JMP RUNSML
FI1:
 MOV AH,0DH
 CALL IGNBLNK
 JNZ RET
 POP AX
 JMP RUNNXL ;RUN NEXT LINE
FI2:
 RET ;ELSE RETURN TO CALLER
 ;
ENDCHK:
 MOV AH,0DH ;END WITH CR?
 CALL IGNBLNK
 JZ RET ;OK, ELSE SAY "WHAT?"
 ;
QWHAT:
 PUSH DX ;****QWHAT****
AWHAT:
 MOV DX,WHAT ;****AWHAT****
ERROR:
 SUB AL,AL ;****ERROR****
 CALL PRTSTG ;PRINT 'WHAT?','HOW?'
 POP DX
 MOV SI,DX
 LODB
 PUSH AX ;SAVE THE CHARACTER
 SUB AL,AL ;AND PUT A ZERO THERE
 MOV DI,DX
 STOB
 MOV BX,[CURRNT] ;GET CURRENT LINE #
 CMP W,[CURRNT],0 ;DIRECT COMMAND?
 JNZ ERR1 ;IFF ZERO, JUST RESTART
 JP ERR2 ;SAVE A BYTE
ERR1: MOV AL,[BX] ;IFF NEGATIVE,
 OR AL,AL
 JNS ERR1A
 JMP INPERR ;REDO INPUT
ERR1A: CALL PRTLN ;ELSE PRINT THE LINE
 DEC DX
 POP AX
 MOV DI,DX
 STOB ;RESTORE THE CHAR
 MOV AL,63 ;PRINT A '?'
 CALL CHROUT
 SUB AL,AL ;AND THE REST OF THE
 CALL PRTSTG ;LINE
ERR2: JMP RSTART
QSORRY:
 PUSH DX ;****QSORRY****
ASORRY:
 MOV DX,SORRY ;****ASORRY****
 JP ERROR
;
;
; ****GETLN**** AND ****FNDLN****
;
;
; 'GETLN' READS AN INPUT LINE INTO 'BUFFER'. IT FIRST PROMPTS
; THE CHARACTER IN A (GIVEN BY THE CALLER), THEN IT FILLS THE
; BUFFER AND ECHOS IT. IT USES BDOS PRIMITIVES TO  ACCOMPLISH
; THIS. ONCE A FULL LINE IS READ IN, 'GETLN' RETURNS.
;
; 'FNDLN' FINDS A LINE WITH A GIVEN LINE #(IN BX) IN THE TEXT
; SAVE AREA. DX IS USED AS THE TEXT POINTER. IFF THE LINE  IS
; FOUND, DX WILL POINT TO THE BEGINNING OF THAT LINE IFF THAT
; LINE (I.E. THE LOW BYTE OF THE LINE #), AND FLAGS ARE NC&Z.
; IFF THAT LINE IS NOT THERE AND A LINE WITH A HIGHER LINE  #
; IS FOUND, DX POINTS TO THERE AND FLAGS ARE NC&NZ.  IFF   WE
; REACHED THE END OF TEXT SAVE AREA AND CANNOT FIND THE LINE,
; FLAGS ARE C&NZ.
; 'FNDLN' WILL INITIALIZE DX TO THE  BEGINNING  OF  THE  TEXT
; SAVE AREA TO START THE SEARCH. SOME OTHER ENTRIES  OF  THIS
; ROUTINE WILL NOT INITIALIZE DX AND DO THE SEARCH.
;
; 'FNDLNP' WILL START WITH DX AND SEARCH FOR THE LINE #.
;
; 'FNDNXT' WILL BUMP DX BY  2, FIND A 0DH AND THEN START  THE
; SEARCH.
; 'FNDSKP' USES DX TO FIND A CR, AND THEN STARTS THE SEARCH.
;
;
;
GETLN:
 CALL CHROUT ;****GETLN****
GL1:
 MOV DX,BUFFER-2
 PUSH DX
 MOV AH,BCONIN ;BUFFERED CONSOLE INPUT
 INT 33 ;CALL MS-DOS
 POP DX
 ADD DL,[BUFFER-1]
 INC DX
 INC DX
 INC DX
 MOV DI,DX ;FOR CONSISTANCY
 PUSH DX
 CALL CRLF ;NEED CRLF
 POP DX
 RET ;WE'VE GOT A LINE
;
; AT ENTRY BX -> LINE # TO BE FOUND
;
FNDLN:
 OR BX,BX ;CHECK SIGN OF BX
 JNS FND1 ;IT CAN'T BE -
 JMP QHOW ;ERROR
FND1: MOV DX,TXTBGN
 ;
FNDLNP:
FL1:
 PUSH BX ;SAVE LINE #
 MOV BX,[TXTUNF] ;CHECK IFF WE PASSED END
 DEC BX
 CMP BX,DX ;SUBSTITUTE FOR CALL 4
 POP BX ;GET LINE # BACK
 JC RET ;C, NZ PASSED END
 MOV SI,DX
 LODW
 CMP AX,BX
 JC FL2
 RET ;NC,Z:FOUND;NC,NZ:NOT FOUND
 ;
FNDNXT:                 ;****FNDNXT****
 INC DX
FL2:
 INC DX
 ;
FNDSKP:
 MOV SI,DX
 LODB ;****FNDSKP****
 CMP AL,0DH ;TRY TO FIND CR
 JNZ FL2 ;KEEP LOOKING
 INC DX
 JP FL1 ;CHECK IFF END OF TEXT
;
;
; **** PRTSTG **** QTSTG **** PRTNUM **** PRTLN ****
;
;
; 'PRTSTG PRINTS A STRING POINTED TO BY DX. IT STOPS PRINTING
; AND RETURNS TO CALLER WHEN EITHER A 0DH IS PRINTED OR  WHEN
; THE NEXT BYTE IS THE SAMES AS WHAT WAS IN A  ( GIVEN BY THE
; CALLER). OLD AL IS STORED IN CH, OLD CH IS LOST.
;
; 'QTSTG' LOOKS FOR A BACK-SLASH,  SINGLE QUOTE,   OR  DOUBLE
; QUOTE. IFF NONE OF THESE, RETURN TO CALLER. IF BACK SLASH \
; OUTPUT A ODH WITHOUT A LF. IFF SINGLE OR DOUBLE QUOTE,PRINT
; THE STRING IN THE QUOTE AND DEMANDS A MATCHING UNQUOTE. AF-
; TER THE PRINTING THE NEXT 3 BYTES OF THE CALLER  IS SKIPPED
; OVER (USUALLY A JMP INSTRUCTION).
;
; 'PRTNUM' PRINTS THE NUMBER IN HL. LEADING BLANKS  ARE ADDED
; IFF NEEDED TO PAD THE NUMBER OF SPACES TO THE NUMBER IN  C.
; NOWEVER, IFF THE NUMBER OF DIGITS IS LARGER THAN THE NUMBER
; IN C, ALL DIGITS ARE PRINTED ANYWAY. NEGATIVE SIGN IS  ALSO
; PRINTED AND COUNTED IN, POSITIVE SIGN IS NOT.
;
; 'PRTLN' PRINTS A SAVED TEXT LINE WITH LINE # AND ALL.
;
;
;
PRTSTG:
 MOV CH,AL ;****PRTSTG****
PS1:
 MOV SI,DX
 LODB ;GET A CHAR
 LAHF            ;PRESERVE FLAGS
 INC DX
 SAHF         ;RESTORE FLAGS
 CMP AL,CH ;SAME AS OLD A?
 JNZ PS2 ;YES, RETURN
        RET
PS2: CALL CHROUT ;ELSE, PRINT IT
 CMP AL,0DH ;WAS IT A CR?
 JNZ PS1 ;NO, NEXT
 RET
 ;
QTSTG:
 MOV AH,'"'
 CALL IGNBLNK
 JNZ QT3
 MOV AL,34 ;IT IS A '"'
QT1:
 CALL PRTSTG ;PRINT UNTIL ANOTHER
 CMP AL,0DH ;WAS LAST ONE A CR?
 POP BX ;RETURN ADDRESS
 JNZ QT2 ;WAS CR, RUN NEXT LINE
        JMP     RUNNXL
QT2:
 INC BX ;SKIPS TWO BYTES ON RETURN!!!!
 INC BX
 JMP BX ;JUMP TO ADDRESS IN BX
QT3:
 MOV AH,39 ;IS IT A SINGLE QUOTE (')?
 CALL IGNBLNK
 JNZ QT4
 MOV AL,39 ;YES, DO SAME
 JP QT1 ;AS IN ' " '
QT4:
 MOV AH,'\'
 CALL IGNBLNK ;IS IT BACK-SLASH?('\')
 JNZ QT5
 MOV AL,141 ;YES, 0DH WITHOUT LF!
 CALL CHROUT ;DO IT TWICE
 CALL CHROUT ;TO GIVE TTY ENOUGH TIME
 POP BX ;RETURN ADDRESS
 JP QT2
QT5:
 RET  ;NONE OF THE ABOVE
 ;
; ON ENTRY BX = BINARY #,CL = # SPACES
;
PRTNUM:
 PUSH DX ;****PRTNUM****
 MOV DX,10 ;DECIMAL
 PUSH DX ;SAVE AS A FLAG
 MOV CH,DH ;CH=SIGN
 DEC CL ;CL=SPACES
 CALL CHKSGN ;CHECK SIGN
 JNS PN1 ;NO SIGN
 MOV CH,45 ;CH=SIGN
 DEC CL ;'-' TAKES SPACE
PN1:
 PUSH CX ;SAVE SIGN % SPACE
PN2:
 CALL DIVIDE ;DIVIDE BX BY 10 (IN DX)
 OR CX,CX ;CX HAS QUOTIENT
 JZ PN3 ;YES, WE GOT ALL
 POP AX ;GET SIGN AND SPACE COUNT
 PUSH BX ;SAVE REMAINDER
 DEC AL ;DEC SPACE COUNT
 PUSH AX ;SAVE NEW SIGN AND SPACE COUNT
 MOV BX,CX ;MOVE RESULT TO BX
 JP PN2 ;AND DIVIDE BY 10
PN3:
 POP CX ;WE GOT ALL DIGITS IN
PN4:
 DEC CL ;THE STACK
 MOV AL,CL ;LOOK AT SPACE COUNT
 OR AL,AL
 JS PN5 ;NO LEADING BLANKS
 MOV AL,32 ;LEADING BLANKS
 CALL CHROUT
 JP PN4
PN5:
 MOV AL,CH ;PRINT SIGN
 CALL CHROUT ;MAYBE, OR NULL
 MOV DL,BL ;LAST REMAINDER IN E
PN6:
 MOV AL,DL ;CHECK DIGIT IN E
 CMP AL,10 ;10 IS FLAG FOR NO MORE
 POP DX
 JZ RET ;IFF SO, RETURN
 ADD AL,48 ;ELSE CONVERT TO ASCII
 CALL CHROUT ;AND PRINT THE DIGIT
 JP PN6 ;GO BACK FOR MORE
 ;
PRTLN:
 MOV SI,DX
 LODW
 MOV BX,AX
 INC DX
 INC DX ;MOVE POINTER
PRTLN1: MOV CL,5 ;PRINT 5 DIGIT LINE #
 CALL PRTNUM
 MOV AL,32 ;FOLLOWED BY A BLANK
 CALL CHROUT
 SUB AL,AL ;AND THEN THE TEXT
 CALL PRTSTG
 RET
;
;
;
; **** MVUP **** MVDOWN **** POPA **** PUSHA ****
;
; 'MVUP' MOVES A BLOCK UP FROM WHERE DX -> WHERE CX -> UNTIL
; DX = BX
;
; 'MVDOWN' MOVES A BLOCK DOWN FROM WHERE DX -> TO WHERE BX->
; UNTIL DX = CX.
;
; 'POPA' RESTORES THE 'FOR' LOOP VAR SAVE AREA FROM THE STACK.
;
; 'PUSHA' STACKS THE 'FOR' LOOP VARIABLE SAVE AREA IN THE STACK
;
;
MVUP:
 CMP DX,BX ;***MVUP***
 JZ RET ;DE = HL, RETURN
 MOV SI,DX
 LODB ;GET ONE BYTE
 MOV DI,CX
 STOB ;MOVE IT
 INC DX
 INC CX
 JP MVUP ;UNTIL DONE
 ;
MVDOWN:
 CMP DX,CX
 JZ RET ;YES, RETURN
MD1:
 LAHF
 DEC DX
 DEC BX
 MOV SI,DX
 LODB ;BOTH POINTERS AND
 MOV [BX],AL ;THEN DO IT
 JP MVDOWN ;LOOP BACK
 ;
POPA:
 POP CX ;CX = RETURN ADDR
 POP BX ;RESTORE LOPVAR, BUT
 MOV [LOPVAR],BX ;=0 MEANS NO MORE
 OR BX,BX
 JZ PP1 ;YES, GO RETURN
 POP BX ;NO, RESTORE OTHERS
 MOV [LOPINC],BX
 POP BX
 MOV [LOPLMT],BX
 POP BX
 MOV [LOPLN],BX
 POP BX
 MOV [LOPPT],BX
PP1:
 PUSH CX ;CX = RETURN ADDR
 RET
 ;
PUSHA:
 MOV BX,STKLMT ;****PUSHA****
 CALL CHGSGN
 POP CX ;CX=RET ADDR
 ADD BX,SP
 JC PUSHB ;YES, SORRY FOR THAT.
        JMP     QSORRY
PUSHB: MOV BX,[LOPVAR] ;ELSE SAVE LOOP VARS
 OR BX,BX ;THAT WILL BE ALL
 JZ PU1
 MOV BX,[LOPPT] ;ELSE, MORE TO SAVE
 PUSH BX
 MOV BX,[LOPLN] ;ELSE, MORE TO SAVE
 PUSH BX
 MOV BX,[LOPLMT]
 PUSH BX
 MOV BX,[LOPINC]
 PUSH BX
 MOV BX,[LOPVAR]
PU1:
 PUSH BX
 PUSH CX ;CX = RETURN ADDR
 RET
 ;
 ;
 ; **** OUTC **** CHKIO ****
 ;
 ;
 ; THESE ARE THE ONLY I/O ROUTINES IN TBI.
 ;
 ;
 ; 'CHKIO' CHECKS THE INPUT, IFF NO INPUT, IT WILL RETURN TO  THE
 ; CALLER WITH THE Z FLAG SET. IFF THERE IS INPUT, THE Z FLAG  IS
 ; CLEARED AND THE INPUT BYRE IS IN A. HOWEVER, IFF THE INPUT  IS
 ; A CONTROL-O, THE 'OCSW' IS COMPLIMENTED, AND THE Z FLAG IS RE-
 ; TURNED. IFF A CONTROL-C IS READ, 'CHKIO' WILL RESTART TBI  AND
 ; DOES NOT RETURN TO THE CALLER.
 ;
CRLF: MOV AL,0DH ;****CRLF****
CHROUT:
 CMP [OCSW],0
 JZ COUT1 ;SEE IF OUTPUT REDIRECTED
 PUSH CX ;SAVE CX ON STACK
 PUSH DX ;AND DX
 PUSH BX ;AND BX TOO
 MOV [OUTCAR],AL ;SAVE CHATACTER
 MOV DL,AL ;PUT CHAR IN E FOR CP/M
 MOV AH,CONOUT ;CONSOLE OUTPUT
 INT 33 ;CALL MS-DOS AND OUTPUT CHAR
 MOV AL,[OUTCAR] ;GET CHAR. BACK
 CMP AL,0DH ;WAS IT A 'CR'?
 JNZ DONE ;NO,DONE
 MOV DL,0AH ;GET LINEFEED
 MOV AH,CONOUT ;CONSOLE OUTPUT AGAIN
 INT 33 ;CALL MS-DOS
DONE:
 MOV AL,[OUTCAR] ;GET CHAR BACK
IDONE:
 POP BX ;GET H BACK
 POP DX ;AND D
 POP CX ;THEN H
 RET ;DONE AT LAST
COUT1:
 CMP B,AL,0 ;IS IT NULL?
 JZ RET ;SKIP IT
 STOB  ;STORE AL (CHAR) IN BUFFER
 INC [BUFFER-1] ;INCREMENT COUNTER
 RET  ;DONE
CHKIO:
 PUSH CX ;SAVE B ON STACK
 PUSH DX ;AND D
 PUSH BX ;THEN H
 MOV AH,CONST ;GET CONSOLE STATUS WORD
 INT 33 ;CALL MS-DOS
 OR AL,AL ;SET FLAGS
 JNZ CI1 ;IF READY, GET CHAR
 JP IDONE ;RESTORE AND RETURN
CI1:
 MOV AH,1 ;CALL THE BDOS
 INT 33 ;CALL MS-DOS
CI2:
 CMP AL,18H ;IS TI CONTROL-X?
 JNZ IDONE ;RETURN AND RESTORE IF NOT
 JMP RSTART ;YES, RESTART TBI
LSTROM: EQU $ ;ALL ABOVE CAN BE ROM
OUTIO:
 OUTB 0FFH
 RET
WAITIO:
 INB 0FFH
 XOR AL,BH
 AND AL,BL
 JZ WAITIO
 CALL FINISH
INPIO:
 INB 0FFH
 MOV BL,AL
 RET
 ;
;
; IGNBLNK
;
; DEBLANKS WHERE DX->
; IF (DX)=AH THEN DX:=DX+1
;
IGNBLNK:MOV SI,DX
IGN1:   LODB            ;GET CHAR IN AL
 CMP AL,32 ;IGNORE BLANKS
 JNZ IGN2 ;IN TEXT (WHERE DX ->)
        INC     DX
        JP      IGN1
IGN2: CMP AL,AH ;IS SEARCH CHARACTER FOUND AT (DX)?
 JNZ RET ;NO, RETURN, POINTER (DX) STAYS
 LAHF  ;SAVE RESULTS OF COMPARISON
 INC DX ;INC POINTER IF CHARACTER MATCHES
 SAHF  ;RETURN RESULT OF COMPARISON TO FLAGS
 RET
 ;
FINISH: POP AX
 CALL FIN ;CHECK END OF COMMAND
 JMP QWHAT ;PRINT "WHAT?" IFF WRONG
 ;
OUTCAR:
 DB 0 ;OUTPUT CHAR STORAGE
OCSW:
 DB 0FFH ;OUTPUT SWITCH
CURRNT:
 DW 0 ;POINTS TO CURRENT LINE
STKGOS:
 DW 0 ;SAVES SP IN 'GOSUB'
VARNXT:
 DW 0 ;TEMP STORAGE
STKINP:
 DW 0 ;SAVES SP IN 'INPUT'
LOPVAR:
 DW 0 ;'FOR' LOOP SAVE AREA
LOPINC:
 DW 0 ;INCREMENT
LOPLMT:
 DW 0 ;LIMIT
LOPLN:
 DW 0 ;LINE NUMBER
LOPPT:
 DW 0 ;TEST POINTER
RANPNT:
 DW 0 ;RANDOM NUMBER POINTER
TXTUNF:
 DW TXTBGN ;-> UNFILLED TEXT AREA
TXTBGN: DS 1
MSG1: DB '8086 TINY BASIC V1.1 27 JUNE 82',0DH
 ORG 2000H ;MISC STORAGE, INCLUDING STACK
TXTEND: EQU $ ;TEST AREA SAVE AREA ENDS
VARBGN:
 DS 54 ;VARIABLE @(0)
 DB 80 ;MAX CHARS IN BUFFER
 DB 0 ;CHAR COUNT
BUFFER:
 DS 80 ;BUFFER MUST BE AFTER TEXT AREA
BUFEND: EQU $
 DS 400 ;EXTRA BYTES FOR STACK
STKLMT: DS 100 ;TOP LIMIT FOR STACK
STACK: EQU $ ;STACK STARTS HERE
 END


