;**************************************************************
;* 
;*                TINY BASIC FOR INTEL 8080
;*                      VERSION 1.0
;*                    BY LI-CHEN WANG
;*                     10 JUNE, 1976 
;*                       @COPYLEFT 
;*                  ALL WRONGS RESERVED
;* 
;**************************************************************
;* 
;*  ;*** ZERO PAGE SUBROUTINES ***
;* 
;*  THE 8080 INSTRUCTION SET LETS YOU HAVE 8 ROUTINES IN LOW 
;*  MEMORY THAT MAY BE CALLED BY RST N, N BEING 0 THROUGH 7. 
;*  THIS IS A ONE BYTE INSTRUCTION AND HAS THE SAME POWER AS 
;*  THE THREE BYTE INSTRUCTION CALL LLHH.  TINY BASIC WILL 
;*  USE RST 0 AS START AND RST 1 THROUGH RST 7 FOR 
;*  THE SEVEN MOST FREQUENTLY USED SUBROUTINES.
;*  TWO OTHER SUBROUTINES (CRLF AND TSTNUM) ARE ALSO IN THIS 
;*  SECTION.  THEY CAN BE REACHED ONLY BY 3-BYTE CALLS.
;*  IN ORDER TO CONFIGURE THE SYSTEM FOR USE WITH CPM I HAVE
;*  MOVED SOME OF THE ROUTINES AROUND.  START WILL NOW BE AT
;*  LOCATION 100H AND THIS SECTION WILL END AT LOCATION 3FH
;*  WITH A JUMP TO 108H.
;* 
;       ORG  8H
;       XTHL           ;*** TSTC OR RST 1 *** 
;       RST  5         ;IGNORE BLANKS AND 
;       CMP  M         ;TEST CHARACTER
;       JMP  TC1       ;REST OF THIS IS AT TC1
;* 
;CRLF   MVI  A,0DH     ;*** CRLF ***
;* 
;       PUSH PSW       ;*** OUTC OR RST 2 *** 
;       LDA  OCSW      ;PRINT CHARACTER ONLY
;       ORA  A         ;IFF OCSW SWITCH IS ON
;       JMP  OC2       ;REST OF THIS IS AT OC2
;* 
;       CALL EXPR2     ;*** EXPR OR RST 3 *** 
;       PUSH H         ;EVALUATE AN EXPRESION 
;       JMP  EXPR1     ;REST OF IT IS AT EXPR1
;       DB   'W' 
;* 
;       MOV  A,H       ;*** COMP OR RST 4 *** 
;       CMP  D         ;COMPARE HL WITH DE
;       RNZ            ;RETURN CORRECT C AND
;       MOV  A,L       ;Z FLAGS 
;       CMP  E         ;BUT OLD A IS LOST 
;       RET
;       DB   'AN'
;* 
;SS1    LDAX D         ;*** IGNBLK/RST 5 ***
;       CPI  40Q       ;IGNORE BLANKS 
;       RNZ            ;IN TEXT (WHERE DE->)
;       INX  D         ;AND RETURN THE FIRST
;       JMP  SS1       ;NON-BLANK CHAR. IN A
;* 
;       POP  PSW       ;*** FINISH/RST 6 ***
;       CALL FIN       ;CHECK END OF COMMAND
;       JMP  QWHAT     ;PRINT "WHAT?" IFF WRONG
;       DB   'G' 
;* 
;       RST  5         ;*** TSTV OR RST 7 *** 
;       SUI  100Q      ;TEST VARIABLES
;       RC             ;C:NOT A VARIABLE
;       JMP  TSTV1     ;JUMP AROUND RESERVED AREA
       ORG  100H      ;OF CPM.
START  JMP  NINIT      ;GO TO INITIALIZATION ROUTINE.	JIF
TSTV1  JNZ  TV1       ;NOT "@" ARRAY 
       INX  D         ;IT IS THE "@" ARRAY 
       CALL PARN      ;@ SHOULD BE FOLLOWED
       DAD  H         ;BY (EXPR) AS ITS INDEX
       JC   QHOW      ;IS INDEX TOO BIG? 
       PUSH D         ;WILL IT OVERWRITE 
       XCHG           ;TEXT? 
       CALL SIZE      ;FIND SIZE OF FREE 
       RST  4         ;AND CHECK THAT
       JC   ASORRY    ;IFF SO, SAY "SORRY"
SS1A   LXI  H,VARBGN  ;IFF NOT, GET ADDRESS 
       CALL SUBDE     ;OF @(EXPR) AND PUT IT 
       POP  D         ;IN HL 
       RET            ;C FLAG IS CLEARED 
TV1    CPI  33Q       ;NOT @, IS IT A TO Z?
       CMC            ;IFF NOT RETURN C FLAG
       RC 
       INX  D         ;IFF A THROUGH Z
TV1A   LXI  H,VARBGN  ;COMPUTE ADDRESS OF
       RLC            ;THAT VARIABLE 
       ADD  L         ;AND RETURN IT IN HL 
       MOV  L,A       ;WITH C FLAG CLEARED 
       MVI  A,0 
       ADC  H 
       MOV  H,A 
       RET
;* 
;*                 TSTC   XCH  HL,(SP)   ;*** TSTC OR RST 1 *** 
;*                        IGNBLK         THIS IS AT LOC. 8 
;*                        CMP  M         AND THEN JMP HERE 
TC1    INX  H         ;COMPARE THE BYTE THAT 
       JZ   TC2       ;FOLLOWS THE RST INST. 
       PUSH B         ;WITH THE TEXT (DE->)
       MOV  C,M       ;IFF NOT =, ADD THE 2ND 
       MVI  B,0       ;BYTE THAT FOLLOWS THE 
       DAD  B         ;RST TO THE OLD PC 
       POP  B         ;I.E., DO A RELATIVE 
       DCX  D         ;JUMP IFF NOT = 
TC2    INX  D         ;IFF =, SKIP THOSE BYTES
       INX  H         ;AND CONTINUE
       XTHL 
       RET
;* 
TSTNUM LXI  H,0       ;*** TSTNUM ***
       MOV  B,H       ;TEST IFF THE TEXT IS 
       RST  5         ;A NUMBER
TN1    CPI  60Q       ;IFF NOT, RETURN 0 IN 
       RC             ;B AND HL
       CPI  72Q       ;IFF NUMBERS, CONVERT 
       RNC            ;TO BINARY IN HL AND 
       MVI  A,360Q    ;SET A TO # OF DIGITS
       ANA  H         ;IFF H>255, THERE IS NO 
       JNZ  QHOW      ;ROOM FOR NEXT DIGIT 
       INR  B         ;B COUNTS # OF DIGITS
       PUSH B 
       MOV  B,H       ;HL=10;*HL+(NEW DIGIT)
       MOV  C,L 
       DAD  H         ;WHERE 10;* IS DONE BY
       DAD  H         ;SHIFT AND ADD 
       DAD  B 
       DAD  H 
       LDAX D         ;AND (DIGIT) IS FROM 
       INX  D         ;STRIPPING THE ASCII 
       ANI  17Q       ;CODE
       ADD  L 
       MOV  L,A 
       MVI  A,0 
       ADC  H 
       MOV  H,A 
       POP  B 
       LDAX D         ;DO THIS DIGIT AFTER 
       JP   TN1       ;DIGIT. S SAYS OVERFLOW
QHOW   PUSH D         ;*** ERROR: "HOW?" *** 
AHOW   LXI  D,HOW 
       JMP  ERROR 
HOW    DB   'HOW?',0DH 
OK     DB   'OK',0DH 
WHAT   DB   'WHAT?',0DH 
SORRY  DB   'SORRY',0DH 
;* 
;**************************************************************
;* 
;* *** MAIN ***
;* 
;* THIS IS THE MAIN LOOP THAT COLLECTS THE TINY BASIC PROGRAM
;* AND STORES IT IN THE MEMORY.
;* 
;* AT START, IT PRINTS OUT "(CR)OK(CR)", AND INITIALIZES THE 
;* STACK AND SOME OTHER INTERNAL VARIABLES.  THEN IT PROMPTS 
;* ">" AND READS A LINE.  IFF THE LINE STARTS WITH A NON-ZERO 
;* NUMBER, THIS NUMBER IS THE LINE NUMBER.  THE LINE NUMBER
;* (IN 16 BIT BINARY) AND THE REST OF THE LINE (INCLUDING CR)
;* IS STORED IN THE MEMORY.  IFF A LINE WITH THE SAME LINE
;* NUMBER IS ALREDY THERE, IT IS REPLACED BY THE NEW ONE.  IF
;* THE REST OF THE LINE CONSISTS OF A 0DHONLY, IT IS NOT STORED
;* AND ANY EXISTING LINE WITH THE SAME LINE NUMBER IS DELETED. 
;* 
;* AFTER A LINE ISs INSERTED, REPLACED, OR DELETED, THE PROGRAM 
;* LOOPS BACK AND ASK FOR ANOTHER LINE.  THIS LOOP WILL BE 
;* TERMINATED WHEN IT READS A LINE WITH ZERO OR NO LINE
;* NUMBER; AND CONTROL IS TRANSFERED TO "DIRCT".
;* 
;* TINY BASIC PROGRAM SAVE AREA STARTS AT THE MEMORY LOCATION
;* LABELED "TXTBGN" AND ENDED AT "TXTEND".  WE ALWAYS FILL THIS
;* AREA STARTING AT "TXTBGN", THE UNFILLED PORTION IS POINTED
;* BY THE CONTENT OF A MEMORY LOCATION LABELED "TXTUNF". 
;* 
;* THE MEMORY LOCATION "CURRNT" POINTS TO THE LINE NUMBER
;* THAT IS CURRENTLY BEING INTERPRETED.  WHILE WE ARE IN 
;* THIS LOOP OR WHILE WE ARE INTERPRETING A DIRECT COMMAND 
;* (SEE NEXT SECTION), "CURRNT" SHOULD POINT TO A 0. 
;* 
RSTART LXI  SP,STACK  ;SET STACK POINTER
ST1    CALL CRLF      ;AND JUMP TO HERE
       LXI  D,OK      ;DE->STRING
       SUB  A         ;A=0 
       CALL PRTSTG    ;PRINT STRING UNTIL 0DH
       LXI  H,ST2+1   ;LITERAL 0 
       SHLD CURRNT    ;CURRNT->LINE # = 0
ST2    LXI  H,0 
       SHLD LOPVAR
       SHLD STKGOS
ST3    MVI  A,76Q     ;PROMPT '>' AND
       CALL GETLN     ;READ A LINE 
       PUSH D         ;DE->END OF LINE 
ST3A   LXI  D,BUFFER  ;DE->BEGINNING OF LINE 
       CALL TSTNUM    ;TESt IFF IT IS A NUMBER
       RST  5 
       MOV  A,H       ;HL=VALUE OF THE # OR
       ORA  L         ;0 IFF NO # WAS FOUND 
       POP  B         ;BC->END OF LINE 
       JZ   DIRECT
       DCX  D         ;BACKUP DE AND SAVE
       MOV  A,H       ;VALUE OF LINE # THERE 
       STAX D 
       DCX  D 
       MOV  A,L 
       STAX D 
       PUSH B         ;BC,DE->BEGIN, END 
       PUSH D 
       MOV  A,C 
       SUB  E 
       PUSH PSW       ;A=# OF BYTES IN LINE
       CALL FNDLN     ;FIND THIS LINE IN SAVE
       PUSH D         ;AREA, DE->SAVE AREA 
       JNZ  ST4       ;NZ:NOT FOUND, INSERT
       PUSH D         ;Z:FOUND, DELETE IT
       CALL FNDNXT    ;FIND NEXT LINE
;*                                       DE->NEXT LINE 
       POP  B         ;BC->LINE TO BE DELETED
       LHLD TXTUNF    ;HL->UNFILLED SAVE AREA
       CALL MVUP      ;MOVE UP TO DELETE 
       MOV  H,B       ;TXTUNF->UNFILLED AREA 
       MOV  L,C 
       SHLD TXTUNF    ;UPDATE
ST4    POP  B         ;GET READY TO INSERT 
       LHLD TXTUNF    ;BUT FIRT CHECK IF
       POP  PSW       ;THE LENGTH OF NEW LINE
       PUSH H         ;IS 3 (LINE # AND CR)
       CPI  3         ;THEN DO NOT INSERT
       JZ   RSTART    ;MUST CLEAR THE STACK
       ADD  L         ;COMPUTE NEW TXTUNF
       MOV  L,A 
       MVI  A,0 
       ADC  H 
       MOV  H,A       ;HL->NEW UNFILLED AREA 
ST4A   LXI  D,TXTEND  ;CHECK TO SEE IF THERE 
       RST  4         ;IS ENOUGH SPACE 
       JNC  QSORRY    ;SORRY, NO ROOM FOR IT 
       SHLD TXTUNF    ;OK, UPDATE TXTUNF 
       POP  D         ;DE->OLD UNFILLED AREA 
       CALL MVDOWN
       POP  D         ;DE->BEGIN, HL->END
       POP  H 
       CALL MVUP      ;MOVE NEW LINE TO SAVE 
       JMP  ST3       ;AREA
;* 
;**************************************************************
;* 
;* *** TABLES *** DIRECT *** & EXEC ***
;* 
;* THIS SECTION OF THE CODE TESTS A STRING AGAINST A TABLE.
;* WHEN A MATCH IS FOUND, CONTROL IS TRANSFERED TO THE SECTION 
;* OF CODE ACCORDING TO THE TABLE. 
;* 
;* AT 'EXEC', DE SHOULD POINT TO THE STRING AD HL SHOULD POINT
;* TO THE TABLE-1.  AT 'DIRECT', DE SHOULD POINT TO THE STRING,
;* HL 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., 'P.', '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 0 AND 
;* A JUMP ADDRESS STORED HI-LOW WITH BIT 7 OF THE HIGH 
;* BYTE SET TO 1.
;* 
;* END OF TABLE IS AN ITEM WITH A JUMP ADDRESS ONLY.  IFF THE 
;* STRING DOES NOT MATCH ANY OF THE OTHER ITEMS, IT WILL 
;* MATCH THIS NULL ITEM AS DEFAULT.
;* 
TAB1   EQU  $         ;DIRECT COMMANDS 
       DB   'LIST'
       DB   LIST SHR 8 + 128,LIST AND 0FFH
       DB   'RUN'
       DB   RUN SHR 8 + 128,RUN AND 255
       DB   'NEW'
       DB   NEW SHR 8 + 128,NEW AND 255
       DB   'LOAD'
       DB   DLOAD SHR 8 + 128,DLOAD AND 255
       DB   'SAVE'
       DB   DSAVE SHR 8 + 128,DSAVE AND 255
       DB   'BYE',80H,0H   ;GO BACK TO CPM
TAB2   EQU  $         ;DIRECT/TATEMENT
       DB   'NEXT'
       DB   NEXT SHR 8 + 128,NEXT AND 255
       DB   'LET'
       DB   LET SHR 8 + 128,LET AND 255
       DB   'OUT'
       DB   OUTCMD SHR 8 + 128,OUTCMD AND 255 
       DB   'POKE'
       DB   POKE SHR 8 + 128,POKE AND 255
       DB   'WAIT'
       DB   WAITCM SHR 8 + 128,WAITCM AND 255
       DB   'IF'
       DB   IFF SHR 8 + 128,IFF AND 255
       DB   'GOTO'
       DB   GOTO SHR 8 + 128,GOTO AND 255
       DB   'GOSUB'
       DB   GOSUB SHR 8 + 128,GOSUB AND 255
       DB   'RETURN'
       DB   RETURN SHR 8 + 128,RETURN AND 255
       DB   'REM'
       DB   REM SHR 8 + 128,REM AND 255
       DB   'FOR'
       DB   FOR SHR 8 + 128,FOR AND 255
       DB   'INPUT'
       DB   INPUT SHR 8 + 128,INPUT AND 255
       DB   'PRINT'
       DB   PRINT SHR 8 + 128,PRINT AND 255
       DB   'STOP'
       DB   STOP SHR 8 + 128,STOP AND 255
       DB   DEFLT SHR 8 + 128,DEFLT AND 255
       DB   'YOU CAN ADD MORE' ;COMMANDS BUT
            ;REMEMBER TO MOVE DEFAULT DOWN.
TAB4   EQU  $         ;FUNCTIONS 
       DB   'RND'
       DB   RND SHR 8 + 128,RND AND 255
       DB   'INP'
       DB   INP SHR 8 + 128,INP AND 255
       DB   'PEEK'
       DB   PEEK SHR 8 + 128,PEEK AND 255
       DB   'USR'
       DB   USR SHR 8 + 128,USR AND 255
       DB   'ABS'
       DB   ABS SHR 8 + 128,ABS AND 255
       DB   'SIZE'
       DB   SIZE SHR 8 + 128,SIZE AND 255
       DB   XP40 SHR 8 + 128,XP40 AND 255
       DB   'YOU CAN ADD MORE' ;FUNCTIONS BUT REMEMBER
                      ;TO MOVE XP40 DOWN
TAB5   EQU  $         ;"TO" IN "FOR" 
       DB   'TO'
       DB   FR1 SHR 8 + 128,FR1 AND 255
       DB   QWHAT SHR 8 + 128,QWHAT AND 255
TAB6   EQU  $         ;"STEP" IN "FOR" 
       DB   'STEP'
       DB   FR2 SHR 8 + 128,FR2 AND 255
       DB   FR3 SHR 8 + 128,FR3 AND 255
TAB8   EQU  $         ;RELATION OPERATORS
       DB   '>='
       DB   XP11 SHR 8 + 128,XP11 AND 255
       DB   '#'
       DB   XP12 SHR 8 + 128,XP12 AND 255
       DB   '>'
       DB   XP13 SHR 8 + 128,XP13 AND 255
       DB   '='
       DB   XP15 SHR 8 + 128,XP15 AND 255
       DB   '<='
       DB   XP14 SHR 8 + 128,XP14 AND 255
       DB   '<'
       DB   XP16 SHR 8 + 128,XP16 AND 255
       DB   XP17 SHR 8 + 128,XP17 AND 255
;* 
DIRECT LXI  H,TAB1-1  ;*** DIRECT ***
;* 
EXEC   EQU  $         ;*** EXEC ***
EX0    RST  5         ;IGNORE LEADING BLANKS 
       PUSH D         ;SAVE POINTER
EX1    LDAX D         ;IFF FOUND '.' IN STRING
       INX  D         ;BEFORE ANY MISMATCH 
       CPI  56Q       ;WE DECLARE A MATCH
       JZ   EX3 
       INX  H         ;HL->TABLE 
       CMP  M         ;IFF MATCH, TEST NEXT 
       JZ   EX1 
       MVI  A,177Q    ;ELSE, SEE IFF BIT 7
       DCX  D         ;OF TABLEIS SET, WHICH
       CMP  M         ;IS THE JUMP ADDR. (HI)
       JC   EX5       ;C:YES, MATCHED
EX2    INX  H         ;NC:NO, FIND JUMP ADDR.
       CMP  M 
       JNC  EX2 
       INX  H         ;BUMP TO NEXT TAB. ITEM
       POP  D         ;RESTORE STRING POINTER
       JMP  EX0       ;TEST AGAINST NEXT ITEM
EX3    MVI  A,177Q    ;PARTIAL MATCH, FIND 
EX4    INX  H         ;JUMP ADDR., WHICH IS
       CMP  M         ;FLAGGED BY BIT 7
       JNC  EX4 
EX5    MOV  A,M       ;LOAD HL WITH THE JUMP 
       INX  H         ;ADDRESS FROM THE TABLE
       MOV  L,M 
       ANI  177Q      ;MASK OFF BIT 7
       MOV  H,A 
       POP  PSW       ;CLEAN UP THE GABAGE 
       PCHL           ;AND WE GO DO IT 
;* 
;**************************************************************
;* 
;* WHAT FOLLOWS IS THE CODE TO EXECUTE DIRECT AND STATEMENT
;* COMMANDS.  CONTROL IS TRANSFERED TO THESE POINTS VIA THE
;* COMMAND TABLE LOOKUP CODE OF 'DIRECT' AND 'EXEC' IN LAST
;* SECTION.  AFTER THE COMMAND IS EXECUTED, CONTROL IS 
;* TANSFERED TO OTHER SECTIONS AS FOLLOWS:
;* 
;* FOR 'LIST', 'NEW', AND '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 STORED LINE, STORE ITS ADDRESS (IN
;* 'CURRNT'), AND START EXECUTE IT.  NOTE THAT ONLY THOSE
;* COMMANDS IN TAB2 ARE LEGAL FOR STORED PROGRAM.
;* 
;* THERE ARE 3 MORE ENTRIES IN 'RUN':
;* 'RUNNXL' FINDS NEXT LINE, STORES ITS ADDR. AND EXECUTES IT. 
;* 'RUNTSL' STORES THE ADDRESS OF THIS LINE AND EXECUTES IT. 
;* 'RUNSML' CONTINUES THE EXECUTION ON SAME LINE.
;* 
;* 'GOTO EXPR(CR)' EVALUATES THE EXPRESSION, FIND THE TARGET 
;* LINE, AND JUMP TO 'RUNTSL' TO DO IT.
;* 'DLOAD' LOADS A NAMED PROGRAM FROM DISK.
;* 'DSAVE' SAVES A NAMED PROGRAM ON DISK.
;* 'FCBSET' SETS UP THE FILE CONTROL BLOCK FOR SUBSEQUENT DISK I/O.
;* 
NEW    CALL ENDCHK    ;*** NEW(CR) *** 
       LXI  H,TXTBGN
       SHLD TXTUNF
;* 
STOP   CALL ENDCHK    ;*** STOP(CR) ***
       JMP RSTART
;* 
RUN    CALL ENDCHK    ;*** RUN(CR) *** 
       LXI  D,TXTBGN  ;FIRST SAVED LINE
;* 
RUNNXL LXI  H,0       ;*** RUNNXL ***
       CALL FNDLNP    ;FIND WHATEVER LINE #
       JC   RSTART    ;C:PASSED TXTUNF, QUIT 
;* 
RUNTSL XCHG           ;*** RUNTSL ***
       SHLD CURRNT    ;SET 'CURRNT'->LINE #
       XCHG 
       INX  D         ;BUMP PASS LINE #
       INX  D 
;* 
RUNSML CALL CHKIO     ;*** RUNSML ***
       LXI  H,TAB2-1  ;FIND COMMAND IN TAB2
       JMP  EXEC      ;AND EXECUTE IT
;* 
GOTO   RST  3         ;*** GOTO EXPR *** 
       PUSH D         ;SAVE FOR ERROR ROUTINE
       CALL ENDCHK    ;MUST FIND A 0DH
       CALL FNDLN     ;FIND THE TARGET LINE
       JNZ  AHOW      ;NO SUCH LINE #
       POP  PSW       ;CLEAR THE "PUSH DE" 
       JMP  RUNTSL    ;GO DO IT
CPM    EQU  5         ;DISK PARAMETERS
FCB    EQU  5CH
SETDMA EQU  26
OPEN   EQU  15
READD  EQU  20
WRITED EQU  21
CLOSE  EQU  16
MAKE   EQU  22
DELETE EQU  19
;*
DLOAD  RST  5         ;IGNORE BLANKS
       PUSH H         ;SAVE H
       CALL FCBSET    ;SET UP FILE CONTROL BLOCK
       PUSH D         ;SAVE THE REST
       PUSH B         
       LXI  D,FCB     ;GET FCB ADDRESS
       MVI  C,OPEN    ;PREPARE TO OPEN FILE
       CALL CPM       ;OPEN IT
       CPI  0FFH      ;IS IT THERE?
       JZ   QHOW      ;NO, SEND ERROR
       XRA  A         ;CLEAR A
       STA  FCB+32    ;START AT RECORD 0
       LXI  D,TXTUNF  ;GET BEGINNING
LOAD   PUSH D         ;SAVE DMA ADDRESS
       MVI  C,SETDMA  ;
       CALL CPM       ;SET DMA ADDRESS
       MVI  C,READD   ;
       LXI  D,FCB
       CALL CPM       ;READ SECTOR
       CPI  1         ;DONE?
       JC   RDMORE    ;NO, READ MORE
       JNZ  QHOW      ;BAD READ
       MVI  C,CLOSE
       LXI  D,FCB 
       CALL CPM       ;CLOSE FILE
       POP  D         ;THROW AWAY DMA ADD.
       POP  B         ;GET OLD REGISTERS BACK
       POP  D
       POP  H
       RST  6         ;FINISH
RDMORE POP  D         ;GET DMA ADDRESS
       LXI  H,80H     ;GET 128
       DAD  D         ;ADD 128 TO DMA ADD.
       XCHG           ;PUT IT BACK IN D
       JMP  LOAD      ;AND READ SOME MORE
;*
DSAVE  RST  5         ;IGNORE BLANKS
       PUSH H         ;SAVE H
       CALL FCBSET    ;SETUP FCB
       PUSH D
       PUSH B         ;SAVE OTHERS
       LXI  D,FCB
       MVI  C,DELETE
       CALL CPM       ;ERASE FILE IF IT EXISTS
       LXI  D,FCB  
       MVI  C,MAKE
       CALL CPM       ;MAKE A NEW ONE
       CPI  0FFH      ;IS THERE SPACE?
       JZ   QHOW      ;NO, ERROR
       XRA  A         ;CLEAR A
       STA  FCB+32    ;START AT RECORD 0
       LXI  D,TXTUNF  ;GET BEGINNING
SAVE   PUSH D         ;SAVE DMA ADDRESS
       MVI  C,SETDMA  ;
       CALL CPM       ;SET DMA ADDRESS
       MVI  C,WRITED
       LXI  D,FCB 
       CALL CPM       ;WRITE SECTOR
       ORA  A         ;SET FLAGS
       JNZ  QHOW      ;IF NOT ZERO, ERROR
       POP  D         ;GET DMA ADD. BACK
       LDA  TXTUNF+1  ;AND MSB OF LAST ADD.
       CMP  D         ;IS D SMALLER?
       JC   SAVDON    ;YES, DONE
       JNZ  WRITMOR   ;DONT TEST E IF NOT EQUAL
       LDA  TXTUNF    ;IS E SMALLER?
       CMP  E
       JC   SAVDON    ;YES, DONE
WRITMOR LXI  H,80H 
       DAD  D         ;ADD 128 TO DMA ADD.
       XCHG           ;GET IT BACK IN D
       JMP  SAVE      ;WRITE SOME MORE
SAVDON MVI  C,CLOSE
       LXI  D,FCB 
       CALL CPM       ;CLOSE FILE
       POP  B         ;GET REGISTERS BACK
       POP  D
       POP  H
       RST  6         ;FINISH
;*
FCBSET LXI  H,FCB     ;GET FILE CONTROL BLOCK ADDRESS
       MVI  M,0       ;CLEAR ENTRY TYPE
FNCLR  INX  H         ;NEXT LOCATION
       MVI  M,' '     ;CLEAR TO SPACE
       MVI  A,FCB+8 AND 255
       CMP  L         ;DONE?
       JNZ  FNCLR     ;NO, DO IT AGAIN
       INX  H         ;NEXT
       MVI  M,'T'     ;SET FILE TYPE TO 'TBI'
       INX  H
       MVI  M,'B'
       INX  H
       MVI  M,'I'
EXRC   INX  H         ;CLEAR REST OF FCB
       MVI  M,0
       MVI  A,FCB+15 AND 255
       CMP  L         ;DONE?
       JNZ  EXRC      ;NO, CONTINUE
       LXI  H,FCB+1   ;GET FILENAME START
FN     LDAX D         ;GET CHARACTER
       CPI  0DH       ;IS IT A 'CR'
       RZ             ;YES, DONE
       CPI  '!'       ;LEGAL CHARACTER?
       JC   QWHAT     ;NO, SEND ERROR
       CPI  '['       ;AGAIN
       JNC  QWHAT     ;DITTO
       MOV  M,A        ;SAVE IT IN FCB
       INX  H         ;NEXT
       INX  D
       MVI  A,FCB+9 AND 255
       CMP  L         ;LAST?
       JNZ  FN        ;NO, CONTINUE
       RET            ;TRUNCATE AT 8 CHARACTERS
;* 
;************************************************************* 
;* 
;* *** LIST *** & PRINT ***
;* 
;* LIST HAS TWO FORMS: 
;* 'LIST(CR)' LISTS ALL SAVED LINES
;* 'LIST #(CR)' START LIST AT THIS LINE #
;* YOU CAN STOP THE LISTING BY CONTROL C KEY 
;* 
;* PRINT COMMAND IS 'PRINT ....;' OR 'PRINT ....(CR)'
;* WHERE '....' IS A LIST OF EXPRESIONS, FORMATS, BACK-
;* ARROWS, AND STRINGS.  THESE ITEMS ARE SEPERATED BY COMMAS.
;* 
;* A FORMAT IS A POUND SIGN FOLLOWED BY A NUMBER.  IT CONTROLSs 
;* THE NUMBER OF SPACES THE VALUE OF A EXPRESION IS GOING TO 
;* BE PRINTED.  IT STAYS EFFECTIVE FOR THE REST OF THE PRINT 
;* COMMAND UNLESS CHANGED BY ANOTHER FORMAT.  IFF NO FORMAT IS
;* SPECIFIED, 6 POSITIONS WILL BE USED.
;* 
;* A STRING IS QUOTED IN A PAIR OF SINGLE QUOTES OR A PAIR OF
;* DOUBLE QUOTES.
;* 
;* A BACK-ARROW MEANS GENERATE A (CR) WITHOUT (LF) 
;* 
;* A (CRLF) IS GENERATED AFTER THE ENTIRE LIST HAS BEEN
;* PRINTED OR IFF THE LIST IS A NULL LIST.  HOWEVER IFF THE LIST 
;* ENDED WITH A COMMA, NO (CRL) 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    JC   RSTART    ;C:PASSED TXTUNF 
       CALL PRTLN     ;PRINT THE LINE
       CALL CHKIO     ;STOP IFF HIT CONTROL-C 
       CALL FNDLNP    ;FIND NEXT LINE
       JMP  LS1       ;AND LOOP BACK 
;* 
PRINT  MVI  C,6       ;C = # OF SPACES 
       RST  1         ;IFF NULL LIST & ";"
       DB   73Q 
       DB   6Q 
       CALL CRLF      ;GIVE CR-LF AND
       JMP  RUNSML    ;CONTINUE SAME LINE
PR2    RST  1         ;IFF NULL LIST (CR) 
       DB   0DH
       DB   6Q
       CALL CRLF      ;ALSO GIVE CR-LF AND 
       JMP  RUNNXL    ;GO TO NEXT LINE 
PR0    RST  1         ;ELSE IS IT FORMAT?
       DB   '#' 
       DB   5Q
       RST  3         ;YES, EVALUATE EXPR. 
       MOV  C,L       ;AND SAVE IT IN C
       JMP  PR3       ;LOOK FOR MORE TO PRINT
PR1    CALL QTSTG     ;OR IS IT A STRING?
       JMP  PR8       ;IFF NOT, MUST BE EXPR. 
PR3    RST  1         ;IFF ",", GO FIND NEXT
       DB   ',' 
       DB   6Q
       CALL FIN       ;IN THE LIST.
       JMP  PR0       ;LIST CONTINUES
PR6    CALL CRLF      ;LIST ENDS 
       RST  6 
PR8    RST  3         ;EVALUATE THE EXPR 
       PUSH B 
       CALL PRTNUM    ;PRINT THE VALUE 
       POP  B 
       JMP  PR3       ;MORE TO PRINT?
;* 
;**************************************************************
;* 
;* *** GOSUB *** & RETURN ***
;* 
;* 'GOSUB EXPR;' OR 'GOSUB EXPR (CR)' IS LIKE THE 'GOTO' 
;* COMMAND, EXCEPT THAT THE CURRENT TEXT POINTER, STACK POINTER
;* ETC. ARE SAVE SO THAT EXECUTION CAN BE CONTINUED AFTER THE
;* SUBROUTINE 'RETURN'.  IN ORDER THAT 'GOSUB' CAN BE NESTED 
;* (AND EVEN RECURSIVE), THE SAVE AREA MUST BE STACKED.
;* THE STACK POINTER IS SAVED IN 'STKGOS'. THE OLD 'STKGOS' IS 
;* SAVED IN THE STACK.  IFF 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 FORr NO FURTHER 'RETURN'S.
;* 
;* 'RETURN(CR)' UNDOS EVERYHING THAT 'GOSUB' DID, AND THUS
;* RETURN THE EXCUTION TO THE COMMAND AFTER THE MOST RECENT
;* 'GOSUB'.  IFF 'STKGOS' IS ZERO, IT INDICATES THAT WE 
;* NEVER HAD A 'GOSUB' AND IS THUS AN ERROR. 
;* 
GOSUB  CALL PUSHA     ;SAVE THE CURRENT "FOR"
       RST  3         ;PARAMETERS
       PUSH D         ;AND TEXT POINTER
       CALL FNDLN     ;FIND THE TARGET LINE
       JNZ  AHOW      ;NOT THERE. SAY "HOW?" 
       LHLD CURRNT    ;FOUND IT, SAVE OLD
       PUSH H         ;'CURRNT' OLD 'STKGOS' 
       LHLD STKGOS
       PUSH H 
       LXI  H,0       ;AND LOAD NEW ONES 
       SHLD LOPVAR
       DAD  SP
       SHLD STKGOS
       JMP  RUNTSL    ;THEN RUN THAT LINE
RETURN CALL ENDCHK    ;THERE MUST BE A 0DH
       LHLD STKGOS    ;OLD STACK POINTER 
       MOV  A,H       ;0 MEANS NOT EXIST 
       ORA  L 
       JZ   QWHAT     ;SO, WE SAY: "WHAT?" 
       SPHL           ;ELSE, RESTORE IT
       POP  H 
       SHLD STKGOS    ;AND THE OLD 'STKGOS'
       POP  H 
       SHLD CURRNT    ;AND THE OLD 'CURRNT'
       POP  D         ;OLD TEXT POINTER
       CALL POPA      ;OLD "FOR" PARAMETERS
       RST  6         ;AND WE ARE BACK HOME
;* 
;**************************************************************
;* 
;* *** FOR *** & NEXT ***
;* 
;* 'FOR' HAS TWO FORMS:
;* 'FOR VAR=EXP1 TO EXP2 STEP EXP1' AND 'FOR VAR=EXP1 TO EXP2' 
;* THE SECOND FORM MEANS THE SAME THING AS THE FIRST FORM WITH 
;* EXP1=1.  (I.E., WITH A STEP OF +1.) 
;* TBI WILL FIND THE VARIABLE VAR. AND SET ITS VALUE TO THE
;* CURRENT VALUE OF EXP1.  IT ALSO EVALUATES EXPR2 AND EXP1
;* AND SAVE ALL THESE TOGETHER WITH THE TEXT POINTERr 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 
;* BEFORE 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. 
;* IFF THAT IS THE CASE THEN THE OLD 'FOR' LOOP IS DEACTIVATED.
;* (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 DIGS IN 
;* THE STACK TO FIND THE RIGHTt ONE AND PURGES ALL THOSE THAT 
;* DID NOT MATCH.  EITHER WAY, TBI THEN ADDS THE 'STEP' TO 
;* THAT VARIABLE AND CHECK THE RESULT WITH THE LIMIT.  IFF IT 
;* IS WITHIN THE LIMIT, CONTROL LOOPS BACK TO THE COMMAND
;* FOLLOWING THE 'FOR'.  IFF OUTSIDE THE LIMIT, THE SAVE ARER 
;* IS PURGED AND EXECUTION CONTINUES.
;* 
FOR    CALL PUSHA     ;SAVE THE OLD SAVE AREA
       CALL SETVAL    ;SET THE CONTROL VAR.
       DCX  H         ;HL IS ITS ADDRESS 
       SHLD LOPVAR    ;SAVE THAT 
       LXI  H,TAB5-1  ;USE 'EXEC' TO LOOK
       JMP  EXEC      ;FOR THE WORD 'TO' 
FR1    RST  3         ;EVALUATE THE LIMIT
       SHLD LOPLMT    ;SAVE THAT 
       LXI  H,TAB6-1  ;USE 'EXEC' TO LOOK
       JMP  EXEC      ;FOR THE WORD 'STEP'
FR2    RST  3         ;FOUND IT, GET STEP
       JMP  FR4 
FR3    LXI  H,1Q      ;NOT FOUND, SET TO 1 
FR4    SHLD LOPINC    ;SAVE THAT TOO 
FR5    LHLD CURRNT    ;SAVE CURRENT LINE # 
       SHLD LOPLN 
       XCHG           ;AND TEXT POINTER
       SHLD LOPPT 
       LXI  B,12Q     ;DIG INTO STACK TO 
       LHLD LOPVAR    ;FIND 'LOPVAR' 
       XCHG 
       MOV  H,B 
       MOV  L,B       ;HL=0 NOW
       DAD  SP        ;HERE IS THE STACK 
       DB   76Q 
FR7    DAD  B         ;EACH LEVEL IS 10 DEEP 
       MOV  A,M       ;GET THAT OLD 'LOPVAR' 
       INX  H 
       ORA  M 
       JZ   FR8       ;0 SAYS NO MORE IN IT
       MOV  A,M 
       DCX  H 
       CMP  D         ;SAME AS THIS ONE? 
       JNZ  FR7 
       MOV  A,M       ;THE OTHER HALF? 
       CMP  E 
       JNZ  FR7 
       XCHG           ;YES, FOUND ONE
       LXI  H,0Q
       DAD  SP        ;TRY TO MOVE SP
       MOV  B,H 
       MOV  C,L 
       LXI  H,12Q 
       DAD  D 
       CALL MVDOWN    ;AND PURGE 10 WORDS
       SPHL           ;IN THE STACK
FR8    LHLD LOPPT     ;JOB DONE, RESTORE DE
       XCHG 
       RST  6         ;AND CONTINUE
;* 
NEXT   RST  7         ;GET ADDRESS OF VAR. 
       JC   QWHAT     ;NO VARIABLE, "WHAT?"
       SHLD VARNXT    ;YES, SAVE IT
NX0    PUSH D         ;SAVE TEXT POINTER 
       XCHG 
       LHLD LOPVAR    ;GET VAR. IN 'FOR' 
       MOV  A,H 
       ORA  L         ;0 SAYS NEVER HAD ONE
       JZ   AWHAT     ;SO WE ASK: "WHAT?"
       RST  4         ;ELSE WE CHECK THEM
       JZ   NX3       ;OK, THEY AGREE
       POP  D         ;NO, LET'S SEE 
       CALL POPA      ;PURGE CURRENT LOOP
       LHLD VARNXT    ;AND POP ONE LEVEL 
       JMP  NX0       ;GO CHECK AGAIN
NX3    MOV  E,M       ;COME HERE WHEN AGREED 
       INX  H 
       MOV  D,M       ;DE=VALUE OF VAR.
       LHLD LOPINC
       PUSH H 
       DAD  D         ;ADD ONE STEP
       XCHG 
       LHLD LOPVAR    ;PUT IT BACK 
       MOV  M,E 
       INX  H 
       MOV  M,D 
       LHLD LOPLMT    ;HL->LIMIT 
       POP  PSW       ;OLD HL
       ORA  A 
       JP   NX1       ;STEP > 0
       XCHG 
NX1    CALL CKHLDE    ;COMPARE WITH LIMIT
       POP  D         ;RESTORE TEXT POINTER
       JC   NX2       ;OUTSIDE LIMIT 
       LHLD LOPLN     ;WITHIN LIMIT, GO
       SHLD CURRNT    ;BACK TO THE SAVED 
       LHLD LOPPT     ;'CURRNT' AND TEXT 
       XCHG           ;POINTER 
       RST  6 
NX2    CALL POPA      ;PURGE THIS LOOP 
       RST  6 
;* 
;**************************************************************
;* 
;* *** REM *** IFF *** INPUT *** & LET (& DEFLT) ***
;* 
;* '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 OUTHER '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 FOLLOWS ARE IGNORED AND
;* EXECUTION CONTINUES AT THE NEXT LINE. 
;* 
;* 'IPUT' COMMAND 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 EFFECT AS
;* IN 'PRINT'.  IFF AN ITEM IS A VARIABLE, THIS VARIABLE NAME IS
;* PRINTED OUT FOLLOWED BY A COLON.  THEN TBI WAITS FOR AN 
;* EXPR. TO BE TYPED IN.  THE VARIABLE ISs THEN SET TO THE
;* VALUE OF THIS EXPR.  IFF THE VARIABLE IS PROCEDED BY A STRING
;* (AGAIN IN SINGLE OR DOUBLE QUOTES), THE STRING WILL BE
;* PRINTED FOLLOWED BY A COLON.  TBI THEN WAITS FOR INPUT EXPR.
;* AND SET 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 SET THE VARIBLE TO THAT VALUE.
;* TB WILL ALSO HANDLE 'LET' COMMAND WITHOUT THE WORD 'LET'.
;* THIS IS DONE BY 'DEFLT'.
;* 
REM    LXI  H,0Q      ;*** REM *** 
       DB   76Q 
;* 
IFF     RST  3         ;*** IFF ***
       MOV  A,H       ;IS THE EXPR.=0? 
       ORA  L 
       JNZ  RUNSML    ;NO, CONTINUE
       CALL FNDSKP    ;YES, SKIP REST OF LINE
       JNC  RUNTSL
       JMP  RSTART
;* 
INPERR LHLD STKINP    ;*** INPERR ***
       SPHL           ;RESTORE OLD SP
       POP  H         ;AND OLD 'CURRNT'
       SHLD CURRNT
       POP  D         ;AND OLD TEXT POINTER
       POP  D         ;REDO INPUT
;* 
INPUT  EQU  $         ;*** INPUT *** 
IP1    PUSH D         ;SAVE IN CASE OF ERROR 
       CALL QTSTG     ;IS NEXT ITEM A STRING?
       JMP  IP2       ;NO
       RST  7         ;YES. BUT FOLLOWED BY A
       JC   IP4       ;VARIABLE?   NO. 
       JMP  IP3       ;YES.  INPUT VARIABLE
IP2    PUSH D         ;SAVE FOR 'PRTSTG' 
       RST  7         ;MUST BE VARIABLE NOW
       JC   QWHAT     ;"WHAT?" IT IS NOT?
       LDAX D         ;GET READY FOR 'RTSTG'
       MOV  C,A 
       SUB  A 
       STAX D 
       POP  D 
       CALL PRTSTG    ;PRINT STRING AS PROMPT
       MOV  A,C       ;RESTORE TEXT
       DCX  D 
       STAX D 
IP3    PUSH D         ;SAVE IN CASE OF ERROR 
       XCHG 
       LHLD CURRNT    ;ALSO SAVE 'CURRNT'
       PUSH H 
       LXI  H,IP1     ;A NEGATIVE NUMBER 
       SHLD CURRNT    ;AS A FLAG 
       LXI  H,0Q      ;SAVE SP TOO 
       DAD  SP
       SHLD STKINP
       PUSH D         ;OLD HL
       MVI  A,72Q     ;PRINT THIS TOO
       CALL GETLN     ;AND GET A LINE
IP3A   LXI  D,BUFFER  ;POINTS TO BUFFER
       RST  3         ;EVALUATE INPUT
       NOP            ;CAN BE 'CALL ENDCHK'
       NOP
       NOP
       POP  D         ;OK, GET OLD HL
       XCHG 
       MOV  M,E       ;SAVE VALUE IN VAR.
       INX  H 
       MOV  M,D 
       POP  H         ;GET OLD 'CURRNT'
       SHLD CURRNT
       POP  D         ;AND OLD TEXT POINTER
IP4    POP  PSW       ;PURGE JUNK IN STACK 
       RST  1         ;IS NEXT CH. ','?
       DB   ',' 
       DB   3Q
       JMP  IP1       ;YES, MORE ITEMS.
IP5    RST  6 
;* 
DEFLT  LDAX D         ;*** DEFLT *** 
       CPI  0DH       ;EMPTY LINE IS OK
       JZ   LT1       ;ELSE IT IS 'LET'
;* 
LET    CALL SETVAL    ;*** LET *** 
       RST  1         ;SET VALUE TO VAR. 
       DB   ',' 
       DB   3Q
       JMP  LET       ;ITEM BY ITEM
LT1    RST  6         ;UNTIL FINISH
;* 
;**************************************************************
;* 
;* *** EXPR ***
;* 
;* 'EXPR' EVALUATES ARITHMETICAL OR LOGICAL EXPRESSIONS. 
;* <EXPR>::=<EXPR2>
;*          <EXPR2><REL.OP.><EXPR2>
;* WHERE <REL.OP.> IS ONE OF THE OPERATORSs IN TAB8 AND THE 
;* RESULT 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, FNCTIONS CAN HAVE AN <EXPR> AS ARGUMENTS, AND
;* <EXPR4> CAN BE AN <EXPR> IN PARANTHESE. 
;* 
;*                 EXPR   CALL EXPR2     THIS IS AT LOC. 18
;*                        PUSH HL        SAVE <EXPR2> VALUE
EXPR1  LXI  H,TAB8-1  ;LOOKUP REL.OP.
       JMP  EXEC      ;GO DO IT
XP11   CALL XP18      ;REL.OP.">=" 
       RC             ;NO, RETURN HL=0 
       MOV  L,A       ;YES, RETURN HL=1
       RET
XP12   CALL XP18      ;REL.OP."#"
       RZ             ;FALSE, RETURN HL=0
       MOV  L,A       ;TRUE, RETURN HL=1 
       RET
XP13   CALL XP18      ;REL.OP.">"
       RZ             ;FALSE 
       RC             ;ALSO FALSE, HL=0
       MOV  L,A       ;TRUE, HL=1
       RET
XP14   CALL XP18      ;REL.OP."<=" 
       MOV  L,A       ;SET HL=1
       RZ             ;REL. TRUE, RETURN 
       RC 
       MOV  L,H       ;ELSE SET HL=0 
       RET
XP15   CALL XP18      ;REL.OP."="
       RNZ            ;FALSE, RETRUN HL=0
       MOV  L,A       ;ELSE SET HL=1 
       RET
XP16   CALL XP18      ;REL.OP."<"
       RNC            ;FALSE, RETURN HL=0
       MOV  L,A       ;ELSE SET HL=1 
       RET
XP17   POP  H         ;NOT REL.OP. 
       RET            ;RETURN HL=<EXPR2> 
XP18   MOV  A,C       ;SUBROUTINE FOR ALL
       POP  H         ;REL.OP.'S 
       POP  B 
       PUSH H         ;REVERSE TOP OF STACK
       PUSH B 
       MOV  C,A 
       CALL EXPR2     ;GET 2ND <EXPR2> 
       XCHG           ;VALUE IN DE NOW 
       XTHL           ;1ST <EXPR2> IN HL 
       CALL CKHLDE    ;COMPARE 1ST WITH 2ND
       POP  D         ;RESTORE TEXT POINTER
       LXI  H,0Q      ;SET HL=0, A=1 
       MVI  A,1 
       RET
;* 
EXPR2  RST  1         ;NEGATIVE SIGN?
       DB   '-' 
       DB   6Q
       LXI  H,0Q      ;YES, FAKE '0-'
       JMP  XP26      ;TREAT LIKE SUBTRACT 
XP21   RST  1         ;POSITIVE SIGN?  IGNORE
       DB   '+' 
       DB   0Q
XP22   CALL EXPR3     ;1ST <EXPR3> 
XP23   RST  1         ;ADD?
       DB   '+' 
       DB   25Q 
       PUSH H         ;YES, SAVE VALUE 
       CALL EXPR3     ;GET 2ND<EXPR3> 
XP24   XCHG           ;2ND IN DE 
       XTHL           ;1ST IN HL 
       MOV  A,H       ;COMPARE SIGN
       XRA  D 
       MOV  A,D 
       DAD  D 
       POP  D         ;RESTORE TEXT POINTER
       JM   XP23      ;1ST 2ND SIGN DIFFER 
       XRA  H         ;1ST 2ND SIGN EQUAL
       JP   XP23      ;SO ISp RESULT
       JMP  QHOW      ;ELSE WE HAVE OVERFLOW 
XP25   RST  1         ;SUBTRACT? 
       DB   '-' 
       DB   203Q
XP26   PUSH H         ;YES, SAVE 1ST <EXPR3> 
       CALL EXPR3     ;GET 2ND <EXPR3> 
       CALL CHGSGN    ;NEGATE
       JMP  XP24      ;AND ADD THEM
;* 
EXPR3  CALL EXPR4     ;GET 1ST <EXPR4> 
XP31   RST  1         ;MULTIPLY? 
       DB   '*' 
       DB   54Q 
       PUSH H         ;YES, SAVE 1ST 
       CALL EXPR4     ;AND GET 2ND <EXPR4> 
       MVI  B,0Q      ;CLEAR B FOR SIGN
       CALL CHKSGN    ;CHECK SIGN
       XCHG           ;2ND IN DE NOW 
       XTHL           ;1ST IN HL 
       CALL CHKSGN    ;CHECK SIGN OF 1ST 
       MOV  A,H       ;IS HL > 255 ? 
       ORA  A 
       JZ   XP32      ;NO
       MOV  A,D       ;YES, HOW ABOUT DE 
       ORA  D 
       XCHG           ;PUT SMALLER IN HL 
       JNZ  AHOW      ;ALSO >, WILL OVERFLOW 
XP32   MOV  A,L       ;THIS IS DUMB
       LXI  H,0Q      ;CLEAR RESULT
       ORA  A         ;ADD AND COUNT 
       JZ   XP35
XP33   DAD  D 
       JC   AHOW      ;OVERFLOW
       DCR  A 
       JNZ  XP33
       JMP  XP35      ;FINISHED
XP34   RST  1         ;DIVIDE? 
       DB   '/' 
       DB   104Q
       PUSH H         ;YES, SAVE 1ST <EXPR4> 
       CALL EXPR4     ;AND GET 2ND ONE 
       MVI  B,0Q      ;CLEAR B FOR SIGN
       CALL CHKSGN    ;CHECK SIGN OF 2ND 
       XCHG           ;PUT 2ND IN DE 
       XTHL           ;GET 1ST IN HL 
       CALL CHKSGN    ;CHECK SIGN OF 1ST 
       MOV  A,D       ;DIVIDE BY 0?
       ORA  E 
       JZ   AHOW      ;SAY "HOW?"
       PUSH B         ;ELSE SAVE SIGN
       CALL DIVIDE    ;USE SUBROUTINE
       MOV  H,B       ;RESULT IN HL NOW
       MOV  L,C 
       POP  B         ;GET SIGN BACK 
XP35   POP  D         ;AND TEXT POINTER
       MOV  A,H       ;HL MUST BE +
       ORA  A 
       JM   QHOW      ;ELSE IT IS OVERFLOW 
       MOV  A,B 
       ORA  A 
       CM   CHGSGN    ;CHANGE SIGN IFF NEEDED 
       JMP  XP31      ;LOOK OR MORE TERMS 
;* 
EXPR4  LXI  H,TAB4-1  ;FIND FUNCTION IN TAB4 
       JMP  EXEC      ;AND GO DO IT
XP40   RST  7         ;NO, NOT A FUNCTION
       JC   XP41      ;NOR A VARIABLE
       MOV  A,M       ;VARIABLE
       INX  H 
       MOV  H,M       ;VALUE IN HL 
       MOV  L,A 
       RET
XP41   CALL TSTNUM    ;OR IS IT A NUMBER 
       MOV  A,B       ;# OF DIGIT
       ORA  A 
       RNZ            ;OK
PARN   RST  1         ;NO DIGIT, MUST BE 
       DB   '(' 
       DB   5Q
       RST  3         ;"(EXPR)"
       RST  1 
       DB   ')' 
       DB   1Q
XP42   RET
XP43   JMP  QWHAT     ;ELSE SAY: "WHAT?" 
;* 
RND    CALL PARN      ;*** RND(EXPR) *** 
       MOV  A,H       ;EXPR MUST BE +
       ORA  A 
       JM   QHOW
       ORA  L         ;AND NON-ZERO
       JZ   QHOW
       PUSH D         ;SAVE BOTH 
       PUSH H 
       LHLD RANPNT    ;GET MEMORY AS RANDOM
       LXI  D,LSTROM  ;NUMBER
       RST  4 
       JC   RA1       ;WRAP AROUND IFF LAST 
       LXI  H,START 
RA1    MOV  E,M 
       INX  H 
       MOV  D,M 
       SHLD RANPNT
       POP  H 
       XCHG 
       PUSH B 
       CALL DIVIDE    ;RND(N)=MOD(M,N)+1 
       POP  B 
       POP  D 
       INX  H 
       RET
;* 
ABS    CALL PARN      ;*** ABS(EXPR) *** 
       CALL CHKSGN    ;CHECK SIGN
       MOV  A,H       ;NOTE THAT -32768
       ORA  H         ;CANNOT CHANGE SIGN
       JM   QHOW      ;SO SAY: "HOW?"
       RET
SIZE   LHLD TXTUNF    ;*** SIZE ***
       PUSH D         ;GET THE NUMBER OF FREE
       XCHG           ;BYTES BETWEEN 'TXTUNF'
SIZEA  LXI  H,VARBGN  ;AND 'VARBGN'
       CALL SUBDE 
       POP  D 
       RET
;*
;*********************************************************
;*
;*   *** OUT *** INP *** WAIT *** POKE *** PEEK *** & 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 COMMAND MODIFIES ;*  THIS COMMAND MODIFIES 
;*  THIS COMMAND MODIFY'S A SMALL SECTION OF CODE LOCATED 
;*  JUST ABOVE ADDRESS 2K
;*
;*  INP (I)
;*
;*  THIS FUNCTION RETURNS DATA READ FROM INPUT PORT 'I' AS
;*  IT'S 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 THERE IS ONE, OR IF NOT WITH 0, 
;*  AND'S WITH 'J' AND RETURNS WHEN THE RESULT IS NONZERO.
;*  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 IT GETS IT'S VALUE
;*  FROM MEMORY LOCATION 'I'.
;*
;*  USR (I(,J))
;*
;*  USR CALLS A MACHINE LANGUAGE SUBROUTINE AT LOCATION 'I'
;*  IF THE OPTIONAL PARAMETER 'J' IS USED ITS VALUE IS PASSED
;*  IN H&L.  THE VALUE OF THE FUNCTION SHOULD BE RETURNED IN H&L.
;*
;************************************************************
;*
OUTCMD RST  3 
       MOV  A,L
       STA  OUTIO + 1
       RST  1
       DB   ','
       DB   2FH
       RST  3
       MOV  A,L
       CALL OUTIO
       RST  1
       DB   ','
       DB   03H
       JMP  OUTCMD 
       RST  6
WAITCM RST  3
       MOV  A,L
       STA  WAITIO + 1
       RST  1
       DB   ','
       DB   1BH
       RST  3
       PUSH H
       RST  1
       DB   ','
       DB   7H
       RST  3
       MOV  A,L
       POP  H
       MOV  H,A
       JMP  $ + 2
       MVI  H,0
       JMP  WAITIO
INP    CALL PARN
       MOV  A,L
       STA  INPIO + 1
       MVI  H,0
       JMP  INPIO
       JMP  QWHAT
POKE   RST  3
       PUSH H
       RST  1
       DB   ','
       DB   12H
       RST  3
       MOV  A,L
       POP  H
       MOV  M,A
       RST  1
       DB   ',',03H
       JMP  POKE
       RST 6
PEEK   CALL PARN
       MOV  L,M
       MVI  H,0
       RET
       JMP  QWHAT
USR    PUSH B
       RST  1
       DB   '(',28D    ;QWHAT
       RST  3          ;EXPR
       RST  1
       DB   ')',7      ;PASPARM
       PUSH D
       LXI  D,USRET
       PUSH D
       PUSH H
       RET             ;CALL USR ROUTINE
PASPRM RST  1
       DB   ',',14D
       PUSH H
       RST  3
       RST  1
       DB   ')',9
       POP  B
       PUSH D
       LXI  D,USRET
       PUSH D
       PUSH B
       RET             ;CALL USR ROUTINE
USRET  POP  D
       POP  B
       RET
       JMP  QWHAT
;*
;**************************************************************
;* 
;* *** DIVIDE *** SUBDE *** CHKSGN *** CHGSGN *** & CKHLDE *** 
;* 
;* 'DIVIDE' DIVIDES HL BY DE, RESULT IN BC, REMAINDER IN HL
;* 
;* 'SUBDE' SUBTRACTS DE FROM HL
;* 
;* 'CHKSGN' CHECKS SIGN OF HL.  IFF +, NO CHANGE.  IFF -, CHANGE 
;* SIGN AND FLIP SIGN OF B.
;* 
;* 'CHGSGN' CHNGES SIGN OF HL AND B UNCONDITIONALLY. 
;* 
;* 'CKHLE' CHECKS SIGN OF HL AND DE.  IFF DIFFERENT, HL AND DE 
;* ARE INTERCHANGED.  IFF SAME SIGN, NOT INTERCHANGED.  EITHER
;* CASE, HL DE ARE THEN COMPARED TO SET THE FLAGS. 
;* 
DIVIDE PUSH H         ;*** DIVIDE ***
       MOV  L,H       ;DIVIDE H BY DE
       MVI  H,0 
       CALL DV1 
       MOV  B,C       ;SAVE RESULT IN B
       MOV  A,L       ;(REMAINDER+L)/DE
       POP  H 
       MOV  H,A 
DV1    MVI  C,377Q    ;RESULT IN C 
DV2    INR  C         ;DUMB ROUTINE
       CALL SUBDE     ;DIVIDE BY SUBTRACT
       JNC  DV2       ;AND COUNT 
       DAD  D 
       RET
;* 
SUBDE  MOV  A,L       ;*** SUBDE *** 
       SUB  E         ;SUBTRACT DE FROM
       MOV  L,A       ;HL
       MOV  A,H 
       SBB  D 
       MOV  H,A 
       RET
;* 
CHKSGN MOV  A,H       ;*** CHKSGN ***
       ORA  A         ;CHECK SIGN OF HL
       RP             ;IFF -, CHANGE SIGN 
;* 
CHGSGN MOV  A,H       ;*** CHGSGN ***
       CMA            ;CHANGE SIGN OF HL 
       MOV  H,A 
       MOV  A,L 
       CMA
       MOV  L,A 
       INX  H 
       MOV  A,B       ;AND ALSO FLIP B 
       XRI  200Q
       MOV  B,A 
       RET
;* 
CKHLDE MOV  A,H 
       XRA  D         ;SAME SIGN?
       JP   CK1       ;YES, COMPARE
       XCHG           ;NO, XCH AND COMP
CK1    RST  4 
       RET
;* 
;**************************************************************
;* 
;* *** SETVAL *** FIN *** ENDCHK *** & ERROR (& FRIENDS) *** 
;* 
;* "SETVAL" EXPECTS A VARIABLE, FOLLOWED BY AN EQUAL SIGN AND
;* THEN AN EXPR.  IT EVALUATES THE EXPR. AND SET 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 CONTINUE FROM THERE.
;* 
;* "ENDCHK" CHECKS IFF A COMMAND IS ENDED WITH CR.  THIS IS 
;* REQUIRED IN CERTAIN COMMANDS. (GOTO, RETURN, AND STOP ETC.) 
;* 
;* "ERROR" PRINTS THE STRING POINTED BY DE (AND ENDS WITH CR). 
;* IT THEN PRINTS THE LINE POINTED BY 'CURRNT' WITH A "?"
;* INSERTED AT WHERE THE OLD TEXT POINTER (SHOULD BE ON TOP
;* O THE STACK) POINTS TO.  EXECUTION OF TB IS STOPPED
;* AND TBI IS RESTARTED.  HOWEVER, IFF 'CURRNT' -> ZERO 
;* (INDICATING 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 TERMINATED BUT CONTINUED AT 'INPERR'. 
;* 
;* RELATED TO 'ERROR' ARE THE FOLLOWING: 
;* 'QWHAT' SAVES TEXT POINTER IN STACK AND GET MESSAGE "WHAT?" 
;* 'AWHAT' JUST GET MESSAGE "WHAT?" AND JUMP TO 'ERROR'. 
;* 'QSORRY' AND 'ASORRY' DO SAME KIND OF THING.
;* 'QHOW' AND 'AHOW' IN THE ZERO PAGE SECTION ALSO DO THIS 
;* 
SETVAL RST  7         ;*** SETVAL ***
       JC   QWHAT     ;"WHAT?" NO VARIABLE 
       PUSH H         ;SAVE ADDRESS OF VAR.
       RST  1         ;PASS "=" SIGN 
       DB   '=' 
       DB   10Q 
       RST  3         ;EVALUATE EXPR.
       MOV  B,H       ;VALUE IN BC NOW 
       MOV  C,L 
       POP  H         ;GET ADDRESS 
       MOV  M,C       ;SAVE VALUE
       INX  H 
       MOV  M,B 
       RET
SV1    JMP  QWHAT     ;NO "=" SIGN 
;* 
FIN    RST  1         ;*** FIN *** 
       DB   73Q 
       DB   4Q 
       POP  PSW       ;";", PURGE RET ADDR.
       JMP  RUNSML    ;CONTINUE SAME LINE
FI1    RST  1         ;NOT ";", IS IT CR?
       DB   0DH
       DB   4Q 
       POP  PSW       ;YES, PURGE RET ADDR.
       JMP  RUNNXL    ;RUN NEXT LINE 
FI2    RET            ;ELSE RETURN TO CALLER 
;* 
ENDCHK RST  5         ;*** ENDCHK ***
       CPI  0DH       ;END WITH CR?
       RZ             ;OK, ELSE SAY: "WHAT?" 
;* 
QWHAT  PUSH D         ;*** QWHAT *** 
AWHAT  LXI  D,WHAT    ;*** AWHAT *** 
ERROR  SUB  A         ;*** ERROR *** 
       CALL PRTSTG    ;PRINT 'WHAT?', 'HOW?' 
       POP  D         ;OR 'SORRY'
       LDAX D         ;SAVE THE CHARACTER
       PUSH PSW       ;AT WHERE OLD DE ->
       SUB  A         ;AND PUT A 0 THERE 
       STAX D 
       LHLD CURRNT    ;GET CURRENT LINE #
       PUSH H 
       MOV  A,M       ;CHECK THE VALUE 
       INX  H 
       ORA  M 
       POP  D 
       JZ   RSTART    ;IFF ZERO, JUST RERSTART
       MOV  A,M       ;IFF NEGATIVE,
       ORA  A 
       JM   INPERR    ;REDO INPUT
       CALL PRTLN     ;ELSE PRINT THE LINE 
       DCX  D         ;UPTO WHERE THE 0 IS 
       POP  PSW       ;RESTORE THE CHARACTER 
       STAX D 
       MVI  A,77Q     ;PRINTt A "?" 
       RST  2 
       SUB  A         ;AND THE REST OF THE 
       CALL PRTSTG    ;LINE
       JMP  RSTART
QSORRY PUSH D         ;*** QSORRY ***
ASORRY LXI  D,SORRY   ;*** ASORRY ***
       JMP  ERROR 
;* 
;**************************************************************
;* 
;* *** GETLN *** FNDLN (& FRIENDS) *** 
;* 
;* 'GETLN' READS A INPUT LINE INTO 'BUFFER'.  IT FIRST PROMPT
;* THE CHARACTER IN A (GIVEN BY THE CALLER), THEN IT FILLS THE 
;* THE BUFFER AND ECHOS.  IT IGNORES LF'S AND NULLS, BUT STILL 
;* ECHOS THEM BACK.  RUB-OUT IS USED TO CAUSE IT TO DELETE 
;* THE LAST CHARATER (IFF THERE IS ONE), AND ALT-MOD IS USED TO 
;* CAUSE IT TO DELETE THE WHOLE LINE AND START IT ALL OVER.
;* 0DHSIGNALS THE END OF A LINE, AND CAUE 'GETLN' TO RETURN.
;* 
;* 'FNDLN' FINDS A LINE WITH A GIVEN LINE # (IN HL) IN THE 
;* TEXT SAVE AREA.  DE IS USED AS THE TEXT POINTER.  IFF THE
;* LINE IS FOUND, DE WILL POINT TO THE BEGINNING OF 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, DE POINTS TO THERE AND FLAGS ARE NC & NZ.  IFF 
;* WE REACHED THE END OF TEXT SAVE ARE AND CANNOT FIND THE 
;* LINE, FLAGS ARE C & NZ. 
;* 'FNDLN' WILL INITIALIZE DE TO THE BEGINNING OF THE TEXT SAVE
;* AREA TO START THE SEARCH.  SOME OTHER ENTRIES OF THIS 
;* ROUTINE WILL NOT INITIALIZE DE AND DO THE SEARCH. 
;* 'FNDLNP' WILL START WITH DE AND SEARCH FOR THE LINE #.
;* 'FNDNXT' WILL BUMP DE BY 2, FIND A 0DHAND THEN START SEARCH.
;* 'FNDSKP' USE DE TO FIND A CR, AND THEN STRART SEARCH. 
;* 
GETLN  RST  2         ;*** GETLN *** 
       LXI  D,BUFFER  ;PROMPT AND INIT
GL1    CALL CHKIO     ;CHECK KEYBOARD
       JZ   GL1       ;NO INPUT, WAIT
       CPI  177Q      ;DELETE LST CHARACTER?
       JZ   GL3       ;YES 
       CPI  12Q       ;IGNORE LF 
       JZ   GL1 
       ORA  A         ;IGNORE NULL 
       JZ   GL1 
       CPI  134Q      ;DELETE THE WHOLE LINE?
       JZ   GL4       ;YES 
       STAX D         ;ELSE, SAVE INPUT
       INX  D         ;AND BUMP POINTER
       CPI  15Q       ;WAS IT CR?
       JNZ  GL2       ;NO
       MVI  A,12Q     ;YES, GET LINE FEED
       RST  2         ;CALL OUTC AND LINE FEED
       RET            ;WE'VE GOT A LINE
GL2    MOV  A,E       ;MORE FREE ROOM?
       CPI  BUFEND AND 0FFH
       JNZ  GL1       ;YES, GET NEXT INPUT 
GL3    MOV  A,E       ;DELETE LAST CHARACTER 
       CPI  BUFFER AND 0FFH    ;BUT DO WE HAVE ANY? 
       JZ   GL4       ;NO, REDO WHOLE LINE 
       DCX  D         ;YES, BACKUP POINTER 
       MVI  A,'_'     ;AND ECHO A BACK-SPACE 
       RST  2 
       JMP  GL1       ;GO GET NEXT INPUT 
GL4    CALL CRLF      ;REDO ENTIRE LINE
       MVI  A,136Q    ;CR, LF AND UP-ARROW 
       JMP  GETLN 
;* 
FNDLN  MOV  A,H       ;*** FNDLN *** 
       ORA  A         ;CHECK SIGN OF HL
       JM   QHOW      ;IT CANNT BE -
       LXI  D,TXTBGN  ;INIT. TEXT POINTER
;* 
FNDLNP EQU  $         ;*** FNDLNP ***
FL1    PUSH H         ;SAVE LINE # 
       LHLD TXTUNF    ;CHECK IFF WE PASSED END
       DCX  H 
       RST  4 
       POP  H         ;GET LINE # BACK 
       RC             ;C,NZ PASSED END 
       LDAX D         ;WE DID NOT, GET BYTE 1
       SUB  L         ;IS THIS THE LINE? 
       MOV  B,A       ;COMPARE LOW ORDER 
       INX  D 
       LDAX D         ;GET BYTE 2
       SBB  H         ;COMPARE HIGH ORDER
       JC   FL2       ;NO, NOT THERE YET 
       DCX  D         ;ELSE WE EITHER FOUND
       ORA  B         ;IT, OR IT IS NOT THERE
       RET            ;NC,Z:FOUND; NC,NZ:NO
;* 
FNDNXT EQU  $         ;*** FNDNXT ***
       INX  D         ;FIND NEXT LINE
FL2    INX  D         ;JUST PASSED BYTE 1 & 2
;* 
FNDSKP LDAX D         ;*** FNDSKP ***
       CPI  0DH       ;TRY TO FIND 0DH
       JNZ  FL2       ;KEEP LOOKING
       INX  D         ;FOUND CR, SKIP OVER 
       JMP  FL1       ;CHECK IFF END OF TEXT
;* 
;*************************************************************
;* 
;* *** PRTSTG *** QTSTG *** PRTNUM *** & PRTLN *** 
;* 
;* 'PRTSTG' PRINTS A STRING POINTED BY DE.  IT STOPS PRINTING
;* AND RETURNS TO CAL̀ER WHEN EITHER A 0DHIS PRINTED OR WHEN 
;* THE NEXT BYTE IS THE SAME AS WHAT WAS IN A (GIVEN BY THE
;* CALLER).  OLD A IS STORED IN B, OLD B IS LOST.
;* 
;* 'QTSTG' LOOKS FOR A BACK-ARROW, SINGLE QUOTE, OR DOUBLE 
;* QUOTE.  IFF NONE OF THESE, RETURN TO CALLER.  IFF BACK-ARROW, 
;* OUTPUT A 0DHWITHOUT A LF.  IFF SINGLE OR DOUBLE QUOTE, PRINT 
;* THE STRING IN THE QUOTE AND DEMANDS A MATCHING UNQUOTE. 
;* AFTER THE PRINTING THE NEXT 3 BYTES OF THE CALLER IS SKIPPED
;* OVER (USUALLY A JUMP INSTRUCTION).
;* 
;* 'PRTNUM' PRINTS THE NUMBER IN HL.  LEADING BLANKS ARE ADDED 
;* IFF NEEDED TO PAD THE NUMBER OF SPACES TO THE NUMBER IN C. 
;* HOWEVER, IFF THE NUMBER OF DIGITS IS LARGER THAN THE # IN
;* C, ALL DIGITS ARE PRINTED ANYWAY.  NEGATIVE SIGN IS ALSO
;* PRINTED AND COUNTED IN, POSITIVE SIGN IS NOT. 
;* 
;* 'PRTLN' PRINSrA SAVED TEXT LINE WITH LINE # AND ALL. 
;* 
PRTSTG MOV  B,A       ;*** PRTSTG ***
PS1    LDAX D         ;GET A CHARACTERr 
       INX  D         ;BUMP POINTER
       CMP  B         ;SAME AS OLD A?
       RZ             ;YES, RETURN 
       RST  2         ;ELSE PRINT IT 
       CPI  0DH       ;WAS IT A CR?
       JNZ  PS1       ;NO, NEXT
       RET            ;YES, RETURN 
;* 
QTSTG  RST  1         ;*** QTSTG *** 
       DB   '"' 
       DB   17Q 
       MVI  A,42Q     ;IT IS A " 
QT1    CALL PRTSTG    ;PRINT UNTIL ANOTHER 
       CPI  0DH       ;WAS LAST ONE A CR?
       POP  H         ;RETURN ADDRESS
       JZ   RUNNXL    ;WAS CR, RUN NEXT LINE 
QT2    INX  H         ;SKIP 3 BYTES ON RETURN
       INX  H 
       INX  H 
       PCHL           ;RETURN
QT3    RST  1         ;IS IT A ' ? 
       DB   47Q 
       DB   5Q
       MVI  A,47Q     ;YES, DO SAME
       JMP  QT1       ;AS IN " 
QT4    RST  1         ;IS IT BACK-ARROW? 
       DB   137Q
       DB   10Q 
       MVI  A,215Q    ;YES, 0DHWITHOUT LF!!
       RST  2         ;DO IT TWICE TO GIVE 
       RST  2         ;TTY ENOUGH TIME 
       POP  H         ;RETURN ADDRESS
       JMP  QT2 
QT5    RET            ;NONE OF ABOVE 
;* 
PRTNUM PUSH D         ;*** PRTNUM ***
       LXI  D,12Q     ;DECIMAL 
       PUSH D         ;SAVE AS A FLAG
       MOV  B,D       ;B=SIGN
       DCR  C         ;C=SPACES
       CALL CHKSGN    ;CHECK SIGN
       JP   PN1       ;NO SIGN 
       MVI  B,55Q     ;B=SIGN
       DCR  C         ;'-' TAKES SPACE 
PN1    PUSH B         ;SAVE SIGN & SPACE 
PN2    CALL DIVIDE    ;DEVIDE HL BY 10 
       MOV  A,B       ;RESULT 0? 
       ORA  C 
       JZ   PN3       ;YES, WE GOT ALL 
       XTHL           ;NO, SAVE REMAINDER
       DCR  L         ;AND COUNT SPACE 
       PUSH H         ;HL IS OLD BC
       MOV  H,B       ;MOVE RESULT TO BC 
       MOV  L,C 
       JMP  PN2       ;AND DIVIDE BY 10
PN3    POP  B         ;WE GOT ALL DIGITS IN
PN4    DCR  C         ;THE STACK 
       MOV  A,C       ;LOOK AT SPACE COUNT 
       ORA  A 
       JM   PN5       ;NO LEADING BLANKS 
       MVI  A,40Q     ;LEADING BLANKS
       RST  2 
       JMP  PN4       ;MORE? 
PN5    MOV  A,B       ;PRINT SIGN
       RST  2         ;MAYBE - OR NULL 
       MOV  E,L       ;LAST REMAINDER IN E 
PN6    MOV  A,E       ;CHECK DIGIT IN E
       CPI  12Q       ;10 IS FLAG FOR NO MORE
       POP  D 
       RZ             ;IFF SO, RETURN 
       ADI  60Q		;ELSE CONVERT TO ASCII
       RST  2         ;AND PRINT THE DIGIT 
       JMP  PN6       ;GO BACK FOR MORE
;* 
PRTLN  LDAX D         ;*** PRTLN *** 
       MOV  L,A       ;LOW ORDER LINE #
       INX  D 
       LDAX D         ;HIGH ORDER
       MOV  H,A 
       INX  D 
       MVI  C,4Q      ;PRINT 4 DIGIT LINE #
       CALL PRTNUM
       MVI  A,40Q     ;FOLLOWED BY A BLANK 
       RST  2 
       SUB  A         ;AND THEN THE TEXT 
       CALL PRTSTG
       RET
;* 
;**************************************************************
;* 
;* *** MVUP *** MVDOWN *** POPA *** & PUSHA ***
;* 
;* 'MVUP' MOVES A BLOCK UP FROM HERE DE-> TO WHERE BC-> UNTIL 
;* DE = HL 
;* 
;* 'MVDOWN' MOVES A BLOCK DOWN FROM WHERE DE-> TO WHERE HL-> 
;* UNTIL DE = BC 
;* 
;* 'POPA' RESTORES THE 'FOR' LOOP VARIABLE SAVE AREA FROM THE
;* STACK 
;* 
;* 'PUSHA' STACKS THE 'FOR' LOOP VARIABLE SAVE AREA INTO THE 
;* STACK 
;* 
MVUP   RST  4         ;*** MVUP ***
       RZ             ;DE = HL, RETURN 
       LDAX D         ;GET ONE BYTE
       STAX B         ;MOVE IT 
       INX  D         ;INCREASE BOTH POINTERS
       INX  B 
       JMP  MVUP      ;UNTIL DONE
;* 
MVDOWN MOV  A,B       ;*** MVDOWN ***
       SUB  D         ;TEST IFF DE = BC 
       JNZ  MD1       ;NO, GO MOVE 
       MOV  A,C       ;MAYBE, OTHER BYTE?
       SUB  E 
       RZ             ;YES, RETURN 
MD1    DCX  D         ;ELSE MOVE A BYTE
       DCX  H         ;BUT FIRST DECREASE
       LDAX D         ;BOTH POINTERS AND 
       MOV  M,A       ;THEN DO IT
       JMP  MVDOWN    ;LOOP BACK 
;* 
POPA   POP  B         ;BC = RETURN ADDR. 
       POP  H         ;RESTORE LOPVAR, BUT 
       SHLD LOPVAR    ;=0 MEANS NO MORE
       MOV  A,H 
       ORA  L 
       JZ   PP1       ;YEP, GO RETURN
       POP  H         ;NOP, RESTORE OTHERS 
       SHLD LOPINC
       POP  H 
       SHLD LOPLMT
       POP  H 
       SHLD LOPLN 
       POP  H 
       SHLD LOPPT 
PP1    PUSH B         ;BC = RETURN ADDR. 
       RET
;* 
PUSHA  LXI  H,STKLMT  ;*** PUSHA *** 
       CALL CHGSGN
       POP  B         ;BC=RETURN ADDRESS 
       DAD  SP        ;IS STACK NEAR THE TOP?
       JNC  QSORRY    ;YES, SORRY FOR THAT.
       LHLD LOPVAR    ;ELSE SAVE LOOP VAR.S
       MOV  A,H       ;BUT IFF LOPVAR IS 0
       ORA  L         ;THAT WILL BE ALL
       JZ   PU1 
       LHLD LOPPT     ;ELSE, MORE TO SAVE
       PUSH H 
       LHLD LOPLN 
       PUSH H 
       LHLD LOPLMT
       PUSH H 
       LHLD LOPINC
       PUSH H 
       LHLD LOPVAR
PU1    PUSH H 
       PUSH B         ;BC = RETURN ADDR. 
       RET
;* 
;**************************************************************
;* 
;* *** OUTC *** & CHKIO ***
*!
;* THESE ARE THE ONLY I/O ROUTINES IN TBI. 
;* 'OUTC' IS CONTROLLED BY A SOFTWARE SWITCH 'OCSW'.  IFF OCSW=0
;* 'OUTC' WILL JUST RETURN TO THE CALLER.  IFF OCSW IS NOT 0, 
;* IT WILL OUTPUT THE BYTE IN A.  IFF THAT IS A CR, A LF IS ALSO
;* SEND OUT.  ONLY THE FLAGS MAY BE CHANGED AT RETURN, ALL REG.
;* ARE RESTORED. 
;* 
;* 'CHKIO' CHECKS THE INPUT.  IFF NO INPUT, IT WILL RETURN TO 
;* THE CALLER WITH THE Z FLAG SET.  IFF THERE IS INPUT, Z FLAG
;* IS CLEARED AND THE INPUT BYTE IS IN A.  HOWERER, IFF THE 
;* INPUT IS A CONTROL-O, THE 'OCSW' SWITCH IS COMPLIMENTED, AND
;* Z FLAG IS RETURNED.  IFF A CONTROL-C IS READ, 'CHKIO' WILL 
;* RESTART TBI AND DO NOT RETURN TO THE CALLER.
;* 
;*                 OUTC   PUSH AF        THIS IS AT LOC. 10
;*                        LD   A,OCSW    CHECK SOFTWARE SWITCH 
;*                        IOR  A 
OC2    JNZ  OC3       ;IT IS ON
       POP  PSW       ;IT IS OFF 
       RET            ;RESTORE AF AND RETURN 
OC3    POP  A         ;GET OLD A BACK
       PUSH B         ;SAVE B ON STACK
       PUSH D         ;AND D
       PUSH H         ;AND H TOO
       STA  OUTCAR    ;SAVE CHARACTER
       MOV  E,A       ;PUT CHAR. IN E FOR CPM
       MVI  C,2       ;GET CONOUT COMMAND
       CALL CPM       ;CALL CPM AND DO IT
       LDA  OUTCAR    ;GET CHAR. BACK
       CPI  0DH       ;WAS IT A 'CR'?
       JNZ  DONE      ;NO, DONE
       MVI  E,0AH     ;GET LINEFEED
       MVI  C,2       ;AND CONOUT AGAIN
       CALL CPM       ;CALL CPM
DONE   LDA  OUTCAR    ;GET CHARACTER BACK
IDONE  POP  H         ;GET H BACK
       POP  D         ;AND D
       POP  B         ;AND B TOO
       RET            ;DONE AT LAST
CHKIO  PUSH B         ;SAVE B ON STACK
       PUSH D         ;AND D
       PUSH H         ;THEN H
       MVI  C,11      ;GET CONSTAT WORD
       CALL CPM       ;CALL THE BDOS
       ORA  A         ;SET FLAGS
       JNZ  CI1       ;IF READY GET CHARACTER
       JMP  IDONE     ;RESTORE AND RETURN
CI1    MVI  C,1       ;GET CONIN WORD
       CALL CPM       ;CALL THE BDOS
       CPI  0FH       ;IS IT CONTROL-O?
       JNZ  CI2       ;NO, MORE CHECKING
       LDA  OCSW      ;CONTROL-O  FLIP OCSW
       CMA            ;ON TO OFF, OFF TO ON
       STA  OCSW      ;AND PUT IT BACK
       JMP  CHKIO     ;AND GET ANOTHER CHARACTER
CI2    CPI  3         ;IS IT CONTROL-C?
       JNZ  IDONE     ;RETURN AND RESTORE IF NOT
       JMP  RSTART    ;YES, RESTART TBI
LSTROM EQU  $         ;ALL ABOVE CAN BE ROM
OUTIO  OUT  0FFH
       RET
WAITIO IN   0FFH
       XRA  H
       ANA  L
       JZ   WAITIO
       RST  6
INPIO  IN   0FFH
       MOV  L,A
       RET
OUTCAR DB   0         ;OUTPUT CHAR. STORAGE
OCSW   DB   0FFH      ;SWITCH FOR OUTPUT
CURRNT DW   0         ;POINTS TO CURRENT LINE
STKGOS DW   0         ;SAVES SP IN 'GOSUB'
VARNXT DW   0         ;TEMPORARY 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         ;TEXT POINTER
RANPNT DW   START     ;RANDOM NUMBER POINTER
TXTUNF DW   TXTBGN    ;->UNFILLED TEXT AREA
TXTBGN DS   1         ;TEXT SAVE AREA BEGINS 
MSG1   DB   7FH,7FH,7FH,'SHERRY BROTHERS TINY BASIC VER. 3.1',0DH 
INIT   MVI  A,0FFH
       STA  OCSW      ;TURN ON OUTPUT SWITCH 
       MVI  A,0CH     ;GET FORM FEED 
       RST  2         ;SEND TO CRT 
PATLOP SUB  A         ;CLEAR ACCUMULATOR
       LXI  D,MSG1    ;GET INIT MESSAGE
       CALL PRTSTG    ;SEND IT
LSTRAM LDA  7         ;GET FBASE FOR TOP
       STA  RSTART+2
       DCR  A         ;DECREMENT FOR OTHER POINTERS
       STA  SS1A+2    ;AND FIX THEM TOO
       STA  TV1A+2
       STA  ST3A+2
       STA  ST4A+2
       STA  IP3A+2
       STA  SIZEA+2
       STA  GETLN+3
       STA  PUSHA+2
       LXI  H,ST1     ;GET NEW START JUMP
       SHLD START+1   ;AND FIX IT
       JMP  ST1
;	RESTART TABLE
	ORG	0A50H
RSTBL:
       XTHL           ;*** TSTC OR RST 1 *** 
       RST  5         ;IGNORE BLANKS AND 
       CMP  M         ;TEST CHARACTER
       JMP  TC1       ;REST OF THIS IS AT TC1
;* 
CRLF:	EQU	0EH	;EXECUTE TIME LOCATION OF THIS INSTRUCTION.
	MVI  A,0DH     ;*** CRLF ***
;* 
       PUSH PSW       ;*** OUTC OR RST 2 *** 
       LDA  OCSW      ;PRINT CHARACTER ONLY
       ORA  A         ;IFF OCSW SWITCH IS ON
       JMP  OC2       ;REST OF THIS IS AT OC2
;* 
       CALL EXPR2     ;*** EXPR OR RST 3 *** 
       PUSH H         ;EVALUATE AN EXPRESION 
       JMP  EXPR1     ;REST OF IT IS AT EXPR1
       DB   'W' 
;* 
       MOV  A,H       ;*** COMP OR RST 4 *** 
       CMP  D         ;COMPARE HL WITH DE
       RNZ            ;RETURN CORRECT C AND
       MOV  A,L       ;Z FLAGS 
       CMP  E         ;BUT OLD A IS LOST 
       RET
       DB   'AN'
;* 
SS1:	EQU	28H	;EXECUTE TIME LOCATION OF THIS INSTRUCTION.
	LDAX D         ;*** IGNBLK/RST 5 ***
       CPI  40Q       ;IGNORE BLANKS 
       RNZ            ;IN TEXT (WHERE DE->)
       INX  D         ;AND RETURN THE FIRST
       JMP  SS1       ;NON-BLANK CHAR. IN A
;* 
       POP  PSW       ;*** FINISH/RST 6 ***
       CALL FIN       ;CHECK END OF COMMAND
       JMP  QWHAT     ;PRINT "WHAT?" IFF WRONG
       DB   'G' 
;* 
       RST  5         ;*** TSTV OR RST 7 *** 
       SUI  100Q      ;TEST VARIABLES
       RC             ;C:NOT A VARIABLE
       JMP  TSTV1     ;JUMP AROUND RESERVED AREA
; ROUTINE TO COPY RESTART TABLE INTO LOW MEMORY
RST1:	EQU	8	;LOCATION FIRST REATART ROUTINE

EOT:	EQU	40H	;LAST LOC TO BE FILLED

	ORG	0AA0H
NINIT:	LXI	H,RST1		;POINT TO BEGINNING OF MODEL TABLE
	LXI	D,RSTBL
NXT:	LDAX	D
	MOV	M,A
	INX	H
	INX	D
	MVI	A,EOT
	CMP	L
	JNZ	NXT
	LXI	H,INIT
	SHLD	START+1
	JMP	START
       ORG  0F00H
TXTEND EQU  $         ;TEXT SAVE AREA ENDS 
VARBGN DS   2*27      ;VARIABLE @(0)
       DS   1         ;EXTRA BYTE FOR BUFFER
BUFFER DS   80        ;INPUT BUFFER
BUFEND EQU  $         ;BUFFER ENDS
       DS   40        ;EXTRA BYTES FOR STACK
STKLMT EQU  $         ;TOP LIMIT FOR STACK
       ORG  2000H
STACK  EQU  $         ;STACK STARTS HERE
       END
