REM file: Whatis - Public Domain DOS Utility
REM Version 1.0a created 01/15/1996
REM Version 1.1a created 11/26/1998
REM Version 1.2a created 11/29/1999
REM Version 1.3a created 02/02/2000
REM Version 1.4a created 02/13/2000
REM Version 1.5a created 03/06/2000
REM Version 1.6a created 03/16/2000
REM Version 1.7a created 09/07/2000
REM Version 1.7b created 10/11/2000
REM Version 1.7c created 10/27/2000
REM Version 1.8a created 11/10/2000
REM Version 1.8b created 12/18/2000
REM Version 1.9a created 02/28/2001
REM Version 2.0a created 04/15/2001

DEFINT A-Z
REM $DYNAMIC

' constant declarations
CONST FalseD = 0#
CONST TrueD = -1#
CONST False = 0
CONST True = -1
CONST Nul = ""

CONST Max.Arrays = 128 ' can be changed
CONST Max.FNs = 26 ' can be changed

' define color values
CONST Black = 0
CONST Plain = 7
CONST White = 15
CONST Yellow = 14

' get include files
REM $INCLUDE: 'bpb.bi'
REM $INCLUDE: 'dta.bi'
REM $INCLUDE: 'qbx.bi'
REM $INCLUDE: 'fat32.bi'

' declare registers
COMMON SHARED InregsX AS RegtypeX, OutregsX AS RegtypeX

' declare program dta
COMMON SHARED BASIC.DTA.SEG AS INTEGER, BASIC.DTA.OFF AS INTEGER

' declare some structures
COMMON SHARED BPBfile AS BPBtype, FAT32struc AS FAT32Type

' global variables
COMMON SHARED Assign AS INTEGER
COMMON SHARED ASCIZ AS STRING * 260
COMMON SHARED Data.Error AS INTEGER
COMMON SHARED Last.Token AS INTEGER
COMMON SHARED Out2 AS STRING
COMMON SHARED Out3 AS STRING
COMMON SHARED Out4 AS STRING
COMMON SHARED Strng AS STRING
COMMON SHARED Token AS INTEGER
COMMON SHARED Token.Index AS INTEGER
COMMON SHARED Token.List AS STRING
COMMON SHARED White.Space AS STRING

' data areas
COMMON SHARED Arrays() AS DOUBLE ' variable arrays A() to Z()
COMMON SHARED Definitions() AS STRING ' DEF FN functions
COMMON SHARED Strngs() AS STRING ' string variables A$ to Z$
COMMON SHARED Variables() AS DOUBLE ' variables A to Z

' subroutine declarations
DECLARE SUB Arith (T$, T1#, T2#)
DECLARE SUB Assignment1 (T$, T%)
DECLARE SUB Assignment2 (T$, T1%, T2%)
DECLARE SUB Enter.Equate (T%)
DECLARE SUB Equate (T#)
DECLARE SUB Get.Token ()
DECLARE SUB Get.Token2 (V%)
DECLARE SUB Get.Token3 ()
DECLARE SUB Get.Token4 ()
DECLARE SUB Parse.Alphabetic1 (T#)
DECLARE SUB Parse.Alphabetic2 (T#)
DECLARE SUB Parse.Alphabetic3 (T#)
DECLARE SUB Parse.Numeric (T#)
DECLARE SUB Parse.Quoted ()
DECLARE SUB Parse1 (T#)
DECLARE SUB Parse2 (T#)
DECLARE SUB Parse3 (T#)
DECLARE SUB Parse4 (T#)
DECLARE SUB Parse5 (T#)
DECLARE SUB Parse6 (T#)
DECLARE SUB Parse7 (T#)
DECLARE SUB Parse8 (T#)
DECLARE SUB Parse9 (T#)
DECLARE SUB Remove.Spaces ()

' function declarations
DECLARE FUNCTION ClearBreak()
DECLARE FUNCTION BreakIS()
DECLARE FUNCTION KeyIS()
DECLARE FUNCTION KeyboardChar$()
DECLARE FUNCTION KeyboardLine$()

' declare external procedures
DECLARE SUB SetInt
DECLARE SUB RestInt

' backwards compatible for bc 7.1
REM $INCLUDE: 'bc7.inc'

' install new interrupt service routine
CALL SetInt

' program variables
REDIM Arrays(0 TO 26, 0 TO Max.Arrays) AS DOUBLE
REDIM Definitions(0 TO Max.FNs) AS STRING
REDIM Strngs(0 TO 26) AS STRING
REDIM Variables(0 TO 26) AS DOUBLE

' increase stack for recursion
STACK STACK

' declare global error routine
ON ERROR GOTO Error.Routine

' initialize some variables
Token.List = " -+*/\^()[]{}<>=|&!%~?:#@`;," + CHR$(34) ' cannot be changed
White.Space = CHR$(32) + CHR$(9) ' can be changed

' store basic dta
InregsX.AX = &H2F00
CALL InterruptX(&H21, InregsX, OutregsX)
BASIC.DTA.SEG = OutregsX.ES
BASIC.DTA.OFF = OutregsX.BX

' read command line from PSP
Command.Line = False
Command.Line$ = ""
InregsX.AX = &H6200
CALL InterruptX(&H21, InregsX, OutregsX)
PSPsegment = OutregsX.BX
PSPoffset = 128
DEF SEG = PSPsegment
FOR Count = 1 TO 127
   Command.Char = PEEK(PSPoffset + Count)
   SELECT CASE Command.Char
   CASE 0, 10, 13
      EXIT FOR
   CASE ELSE
      Command.Line$ = Command.Line$ + CHR$(Command.Char)
   END SELECT
NEXT
DEF SEG
Command.Line$ = LTRIM$(Command.Line$)
Command.Line$ = RTRIM$(Command.Line$)
IF LEN(Command.Line$) THEN
   Command.Line = True
END IF

' check break flag override
IF Command.Line THEN
   Var1 = INSTR(Command.Line$, "/~")
   IF Var1 THEN
      Var2 = ClearBreak
      Command.Line$ = LEFT$(Command.Line$, Var1 - 1) + MID$(Command.Line$, Var1 + 2)
   END IF
END IF
Command.Line$ = LTRIM$(Command.Line$)
Command.Line$ = RTRIM$(Command.Line$)
IF Command.Line$ = Nul THEN
   Command.Line = False
END IF

' check control break
IF BreakIS THEN
   GOTO End.Whatis
END IF

' get standard input
Standard.Input = False
Standard.Input$ = NUL
InregsX.AX = &HB00
CALL InterruptX(&H21, InregsX, OutregsX)
DO WHILE (OutregsX.AX AND &HFF) = &HFF
   Standard.Input = True
   InregsX.AX = &H800
   CALL InterruptX(&H21, InregsX, OutregsX)
   Char$ = CHR$(OutregsX.AX AND &HFF)
   SELECT CASE ASC(Char$)
   CASE 10, 13, 26
      ' nul
   CASE ELSE
      Standard.Input$ = Standard.Input$ + Char$
   END SELECT
   InregsX.AX = &HB00
   CALL InterruptX(&H21, InregsX, OutregsX)
LOOP

' store redirected input
IF Standard.Input THEN
   Quiet.Mode = True
   Out2 = Standard.Input$
END IF

' clear break flag
IF Standard.Input = False THEN
   Var = ClearBreak
END IF

' check control break
IF BreakIS THEN
   GOTO End.Whatis
END IF

' check nul filename input
IF Standard.Input$ = NUL THEN
   CALL RestInt ' restore Control-Break
   X$ = Inkey$ ' quits here
   CALL SetInt ' reset Control-Break
   IF X$ = CHR$(0) + CHR$(0) THEN
      GOTO End.Whatis
   END IF
END IF

' read the environment
IF ENVIRON$("WHATIS") <> Nul THEN
   Quiet.Mode = True
END IF

' display banner
IF Quiet.Mode = False THEN
   COLOR White, Black
   PRINT "Whatis v2.0a: Expression parser;"
END IF

' parse command line
IF Command.Line THEN
   Data.Error = False
   Command.Line$ = LTRIM$(Command.Line$)
   IF UCASE$(LEFT$(Command.Line$, 2)) = "/X" THEN
      Add.Lines = True
      Symbol$="+"
      Command.Line$ = MID$(Command.Line$, 3)
      IF LEFT$(Command.Line$, 1) = CHR$(34) THEN
         Symbol$ = MID$(Command.Line$, 2, 1)
         Command.Line$ = MID$(Command.Line$, 4)
      END IF
   END IF
   IF UCASE$(LEFT$(Command.Line$, 3)) = "/F:" THEN
      Filename$ = MID$(Command.Line$, 4)
      OPEN Filename$ FOR INPUT AS #1
      Out2 = ""
      WHILE NOT EOF(1)
         LINE INPUT #1, Var$
         IF LEN(Var$) THEN
            Out2 = Out2 + Var$
            IF Add.Lines THEN
               Out2 = Out2 + Symbol$
            END IF
         END IF
      WEND
      Out2 = RTRIM$(Out2)
      IF Add.Lines THEN
         Out2 = LEFT$(Out2, LEN(Out2) - 1)
      END IF
   ELSE
      Out2 = Command.Line$
   END IF
   CALL Remove.Spaces
END IF

' determine input type
IF Command.Line OR Standard.Input THEN
   IF Quiet.Mode = False THEN
      PRINT Out2; " Equals:";
   END IF
   CALL Enter.Equate(True)
   Error.Resume1:
   CALL RestInt
   COLOR Plain, Black
   END
END IF

' reset error flow
Data.Error = True

' display header
IF Quiet.Mode = False THEN
   PRINT "Enter 'quit' to return to system."
END IF

Error.Resume2:

' keyboard input loop
DO
   COLOR Yellow, Black
   LOCATE , 1, 1
   PRINT ">";
   LOCATE , , 1
   ' get input line
   Out2 = KeyboardLine$
   PRINT
   ' check for break
   IF BreakIS THEN
      COLOR Plain, Black
      PRINT "*break*"
      EXIT DO
   END IF
   ' parse input line
   SELECT CASE UCASE$(Out2)
   CASE "QUIT"
      EXIT DO
   CASE ELSE
      ' calculate line
      CALL Remove.Spaces
      CALL Enter.Equate(False)
   END SELECT
LOOP

' end program
IF Quiet.Mode = False THEN
   IF BreakIS THEN
      GOTO End.Whatis
   END IF
   COLOR White, Black
   PRINT "Press <enter> to return to DOS:";
   DO
      IF KeyIS THEN
         IF BreakIS THEN
            EXIT DO
         END IF
         Input.Char$ = KeyboardChar$
         IF Input.Char$ = CHR$(13) THEN
            EXIT DO
         END IF
      END IF
   LOOP
   PRINT
END IF

' end program label
End.Whatis:

' restore key trapping
CALL RestInt

' stop program
COLOR Plain, Black
END

' main error routine
Error.Routine:
 IF Data.Error = False THEN
    PRINT
 END IF
 IF Quiet.Mode THEN
    GOTO Error.Exit1
 END IF
 Error.Mode = ERR
 SELECT CASE Error.Mode
 CASE 53
    Print "File not found."
 CASE 57
    Print "Media error."
 CASE 61
    Print "Disk full."
 CASE 70
    Print "Permission denied."
 CASE 71
    Print "Disk not ready."
 CASE 5
    PRINT "Error #001: Illegal function call."
 CASE 6
    PRINT "Error #002: Overflow."
 CASE 9
    PRINT "Error #003: Subscript out of range."
 CASE 11
    PRINT "Error #004: Division by zero."
 CASE 92
    PRINT "Error #005: Whatis: "; Strng
 CASE 130
    PRINT "Error #006: Bad numeric specifier."
 CASE 140
    PRINT "Error #007: Bad exponent specifier."
 CASE 145
    PRINT "Error #008: Bad DEFFN declaration."
 CASE ELSE
    PRINT "Error #000: Error"; Error.Mode
 END SELECT
Error.Exit1:
 IF Data.Error = False THEN
    RESUME Error.Resume1
 ELSE
    RESUME Error.Resume2
 END IF
 END

' routine accepts an operation and performs on two values, string or numeric
SUB Arith (Token.Parsed$, Temp#, Temp2#)
 IF Last.Token THEN
    SELECT CASE Token.Parsed$
    CASE "-"
       Temp# = Temp# - Temp2#
    CASE "+"
       Temp# = Temp# + Temp2#
    CASE "/"
       Temp# = Temp# / Temp2#
    CASE "\"
       Temp# = Temp# \ Temp2#
    CASE "*"
       Temp# = Temp# * Temp2#
    CASE "^"
       Temp# = Temp# ^ Temp2#
    CASE "<"
       Temp# = (Temp# < Temp2#)
    CASE ">"
       Temp# = (Temp# > Temp2#)
    CASE "="
       Temp# = (Temp# = Temp2#)
    CASE "<="
       Temp# = (Temp# <= Temp2#)
    CASE ">="
       Temp# = (Temp# >= Temp2#)
    CASE "<>"
       Temp# = (Temp# <> Temp2#)
    CASE "|"
       Temp# = Temp# OR Temp2#
    CASE "&"
       Temp# = Temp# AND Temp2#
    CASE "%"
       Temp# = Temp# MOD Temp2#
    CASE "~"
       Temp# = Temp# XOR Temp2#
    CASE "?"
       Temp# = Temp# IMP Temp2#
    CASE ":"
       Temp# = Temp# EQV Temp2#
    CASE "#"
       Temp# = NOT (Temp# OR Temp2#)
    CASE "@"
       Temp# = NOT (Temp# IMP Temp2#)
    CASE "`"
       Temp# = NOT (Temp# AND Temp2#)
    END SELECT
    EXIT SUB
 END IF
 SELECT CASE Token.Parsed$
 CASE "-"
    IF RIGHT$(Out3, LEN(Out4)) = Out4 THEN
       Out3 = LEFT$(Out3, LEN(Out3) - LEN(Out4))
    END IF
 CASE "+"
    Out3 = Out3 + Out4
 CASE "/", "\"
    IF LEN(Out3) > False AND LEN(Out4) > False THEN
       Imbedded = INSTR(Out3, Out4)
       WHILE Imbedded
	  Out3 = LEFT$(Out3, Imbedded - 1) + MID$(Out3, Imbedded + LEN(Out4))
	  Imbedded = INSTR(Out3, Out4)
       WEND
    END IF
 CASE "<"
    Last.Token = True
    Temp# = Out3 < Out4
 CASE ">"
    Last.Token = True
    Temp# = Out3 > Out4
 CASE "="
    Last.Token = True
    Temp# = Out3 = Out4
 CASE "<="
    Last.Token = True
    Temp# = Out3 <= Out4
 CASE ">="
    Last.Token = True
    Temp# = Out3 >= Out4
 CASE "<>"
    Last.Token = True
    Temp# = Out3 <> Out4
 END SELECT
END SUB

SUB Assignment1 (S$, S%)
 IF LEFT$(S$, 2) = "--" THEN
    Assign = True
    Variables(S%) = Variables(S%) - 1#
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "++" THEN
    Assign = True
    Variables(S%) = Variables(S%) + 1#
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "**" THEN
    Assign = True
    Variables(S%) = Variables(S%) ^ 2#
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "//" THEN
    Assign = True
    Variables(S%) = SQR(Variables(S%))
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "-=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = Variables(S%) - T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "+=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = Variables(S%) + T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "/=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = Variables(S%) / T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "\=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = Variables(S%) \ T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "*=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = Variables(S%) * T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "^=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = Variables(S%) ^ T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 3) = "^-=" THEN
    Out2 = MID$(S$, 4)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = Variables(S%) ^ (-T#)
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "%=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = Variables(S%) MOD T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "|=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = Variables(S%) OR T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "&=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = Variables(S%) AND T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "~=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = Variables(S%) XOR T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "?=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = Variables(S%) IMP T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = ":=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = Variables(S%) EQV T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "#=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = NOT (Variables(S%) OR T#)
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "@=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = NOT (Variables(S%) IMP T#)
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "`=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = NOT (Variables(S%) AND T#)
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 1) = "=" THEN
    Out2 = MID$(S$, 2)
    Out2 = LTRIM$(Out2)
    CALL Equate(T#)
    IF Last.Token = True THEN
       Assign = True
       Variables(S%) = T#
    END IF
    EXIT SUB
 END IF
END SUB

SUB Assignment2 (S$, S1%, S2%)
 IF LEFT$(S$, 2) = "--" THEN
    Assign = True
    Arrays(S1%, S2%) = Arrays(S1%, S2%) - 1#
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "++" THEN
    Assign = True
    Arrays(S1%, S2%) = Arrays(S1%, S2%) + 1#
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "**" THEN
    Assign = True
    Arrays(S1%, S2%) = Arrays(S1%, S2%) ^ 2#
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "//" THEN
    Assign = True
    Arrays(S1%, S2%) = SQR(Arrays(S1%, S2%))
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "-=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = Arrays(S1%, S2%) - T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "+=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = Arrays(S1%, S2%) + T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "/=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = Arrays(S1%, S2%) / T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "\=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = Arrays(S1%, S2%) \ T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "*=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = Arrays(S1%, S2%) * T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "^=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = Arrays(S1%, S2%) ^ T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 3) = "^-=" THEN
    Out2 = MID$(S$, 4)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = Arrays(S1%, S2%) ^ (-T#)
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "%=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = Arrays(S1%, S2%) MOD T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "|=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = Arrays(S1%, S2%) OR T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "&=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = Arrays(S1%, S2%) AND T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "~=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = Arrays(S1%, S2%) XOR T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "?=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = Arrays(S1%, S2%) IMP T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = ":=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = Arrays(S1%, S2%) EQV T#
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "#=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = NOT (Arrays(S1%, S2%) OR T#)
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "@=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = NOT (Arrays(S1%, S2%) IMP T#)
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 2) = "`=" THEN
    Out2 = MID$(S$, 3)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = NOT (Arrays(S1%, S2%) AND T#)
    END IF
    EXIT SUB
 END IF
 IF LEFT$(S$, 1) = "=" THEN
    Out2 = MID$(S$, 2)
    Out2 = LTRIM$(Out2)
    Token.Index = 1
    CALL Get.Token
    CALL Parse1(T#)
    IF Last.Token = True THEN
       Assign = True
       Arrays(S1%, S2%) = T#
    END IF
 END IF
END SUB

SUB Enter.Equate(VarX%)
 Out2 = LTRIM$(Out2)
 IF Out2 = Nul THEN
    EXIT SUB
 END IF
 Store.Input$ = Out2
 Assign = False
 GOSUB Assignment
 IF Assign THEN
    EXIT SUB
 END IF
 Out2 = Store.Input$
 D$ = UCASE$(Out2)
 IF LEFT$(D$, 3) = "MID" THEN
    GOSUB Assign.Mid.String
    If Assign Then
       EXIT SUB
    END IF
 END IF
 IF LEFT$(D$, 4) = "LEFT" THEN
    GOSUB Assign.Left.String
    If Assign Then
       EXIT SUB
    END IF
 END IF
 IF LEFT$(D$, 5) = "RIGHT" THEN
    GOSUB Assign.Right.String
    If Assign Then
       EXIT SUB
    END IF
 END IF
 IF LEFT$(D$, 5) = "DEFFN" THEN
    GOSUB Define.Function
    If Assign Then
       EXIT SUB
    END IF
 END IF
 Out2 = Store.Input$
 Last.Token = False
 Out3 = Nul
 Out4 = Nul
 Temp# = False
 Token.Index = 1
 CALL Get.Token
 CALL Parse1(T#)
 ' parse leftover tokens
 WHILE LEN(Strng)
    CALL Parse1(X#)
    ' toss away any unwanted tokens
    IF Last.Token = 3 THEN
       Strng = "<extra token>"
       ERROR 92
    END IF
 WEND
 IF Last.Token THEN
    IF T# < 0# THEN
       IF VarX% THEN
          PRINT " "; T#
          EXIT SUB
       END IF
    END IF
    PRINT T#
 ELSE
    IF VarX% THEN
       PRINT " "; Out3
    ELSE
       PRINT Out3
    END IF
 END IF
 EXIT SUB

Assignment:
 Assign = False
 Temp1$ = UCASE$(LEFT$(Out2, 1))
 IF Temp1$ >= "A" AND Temp1$ <= "Z" THEN
    Variable = ASC(Temp1$) - 64
    Temp1$ = MID$(Out2, 2)
    Temp1$ = LTRIM$(Temp1$)
    CALL Assignment1(Temp1$, Variable)
    IF Assign THEN
       GOSUB Assign.End
       RETURN
    END IF
    IF LEFT$(Temp1$, 1) = "$" THEN
       Temp1$ = MID$(Temp1$, 2)
       Temp1$ = LTRIM$(Temp1$)
       IF LEFT$(Temp1$, 1) = "=" THEN
	  Out2 = MID$(Temp1$, 2)
          Out2 = LTRIM$(Out2)
          Token.Index = 1
          CALL Get.Token
          CALL Parse1(Temp3#)
          IF Last.Token = False THEN
             Strngs(Variable) = Out3
             Assign = True
             GOSUB Assign.End
             RETURN
          END IF
       END IF
    END IF
    IF LEFT$(Temp1$, 1) = "(" THEN
       Out2 = MID$(Temp1$, 2)
       Out2 = LTRIM$(Out2)
       Last.Token = False
       Token.Index = 1
       CALL Get.Token
       CALL Parse1(Temp3#)
       IF Last.Token = True THEN
          Element = CINT(Temp3#)
          Temp1$ = MID$(Out2, Token.Index)
          Temp1$ = LTRIM$(Temp1$)
          CALL Assignment2(Temp1$, Variable, Element)
          IF Assign THEN
             GOSUB Assign.End
             RETURN
          END IF
       END IF
    END IF
 END IF
 RETURN

Assign.Mid.String:
 Out2 = MID$(Out2, 4)
 IF LEN(Out2) THEN
    Out2 = LTRIM$(Out2)
    Out2 = MID$(Out2, 3)
    V$ = UCASE$(LEFT$(Out2, 1))
    IF V$ >= "A" AND V$ <= "Z" THEN
       Variable = ASC(V$) - 64
       IF MID$(Out2, 2, 1) = "$" THEN
	  Out2 = MID$(Out2, 4)
	  Last.Token = False
	  Token.Index = 1
	  CALL Get.Token
	  CALL Parse1(T#)
	  Start = CINT(T#)
          IF Strng = "," THEN
             CALL Get.Token
             CALL Parse1(T#)
             Length = CINT(T#)
             Out2 = MID$(Out2, Token.Index)
             Out2 = LTRIM$(Out2)
             IF LEFT$(Out2, 1) = "=" THEN
                Out2 = MID$(Out2, 2)
                Out2 = LTRIM$(Out2)
                Token.Index = 1
                CALL Get.Token
                CALL Parse1(T#)
                IF Last.Token = False THEN
                   Assign = True
                   MID$(Strngs(Variable), Start, Length) = Out3
                   RETURN
                END IF
             END IF
	  END IF
       END IF
    END IF
 END IF
 RETURN

Assign.Left.String:
 Out2 = MID$(Out2, 5)
 IF LEN(Out2) THEN
    Out2 = LTRIM$(Out2)
    Out2 = MID$(Out2, 3)
    V$ = UCASE$(LEFT$(Out2, 1))
    IF V$ >= "A" AND V$ <= "Z" THEN
       Variable = ASC(V$) - 64
       IF MID$(Out2, 2, 1) = "$" THEN
	  Out2 = MID$(Out2, 4)
	  Last.Token = False
	  Token.Index = 1
	  CALL Get.Token
	  CALL Parse1(T#)
	  Start = CINT(T#)
	  Out2 = MID$(Out2, Token.Index)
	  Out2 = LTRIM$(Out2)
	  IF LEFT$(Out2, 1) = "=" THEN
             Assign = True
	     Out2 = MID$(Out2, 2)
	     Out2 = LTRIM$(Out2)
	     Token.Index = 1
	     CALL Get.Token
             CALL Parse1(T#)
             IF Last.Token = False THEN
                Assign = True
                MID$(Strngs(Variable), 1, Start) = Out3
                RETURN
             END IF
	  END IF
       END IF
    END IF
 END IF
 RETURN

Assign.Right.String:
 Out2 = MID$(Out2, 6)
 IF LEN(Out2) THEN
    Out2 = LTRIM$(Out2)
    Out2 = MID$(Out2, 3)
    V$ = UCASE$(LEFT$(Out2, 1))
    IF V$ >= "A" AND V$ <= "Z" THEN
       Variable = ASC(V$) - 64
       IF MID$(Out2, 2, 1) = "$" THEN
	  Out2 = MID$(Out2, 4)
	  Last.Token = False
	  Token.Index = 1
	  CALL Get.Token
	  CALL Parse1(T#)
	  Start = CINT(T#)
	  Out2 = MID$(Out2, Token.Index)
	  Out2 = LTRIM$(Out2)
	  IF LEFT$(Out2, 1) = "=" THEN
             Assign = True
	     Out2 = MID$(Out2, 2)
	     Out2 = LTRIM$(Out2)
	     Token.Index = 1
	     CALL Get.Token
	     CALL Parse1(T#)
	     Length = LEN(Strngs(Variable)) - Start + 1
             IF Last.Token = False THEN
                IF Length > False THEN
                   Assign = True
                   MID$(Strngs(Variable), Length, Start) = Out3
                   RETURN
                END IF
	     END IF
	  END IF
       END IF
    END IF
 END IF
 RETURN

Assign.End:
 IF LEN(Strng) THEN
    CALL Parse1(X#)
    Strng = "<extra token>"
    ERROR 92
 END IF
 RETURN

Define.Function:
 Out2 = MID$(Out2, 6)
 Out2 = LTRIM$(Out2)
 Token.Index = 1
 CALL Get.Token3
 CALL Get.Token
 CALL Parse1(Temp4#)
 CALL Get.Token4
 Func% = CINT(Temp4#)
 IF Func% >= 1 AND Func% <= Max.FNs THEN
    Out2 = MID$(Out2, Token.Index)
    Out2 = LTRIM$(Out2)
    IF LEFT$(Out2, 1) = "=" THEN
       Out2 = MID$(Out2, 2)
       Definitions(Func%) = Out2
       Assign = True
       RETURN
    END IF
 END IF
 ERROR 145
END SUB

 ' returns next token in string form,
 ' increments pointer to next token,
 ' determines token type.
SUB Get.Token
 Strng = Nul ' reset token
 Token = False ' reset token type
 ' locate symbols
 CALL Get.Token2(Token.Exists)
 IF Token.Exists THEN
    EXIT SUB
 END IF
 IF Token.Index > LEN(Out2) THEN
    Strng = Nul
    Token = False
    Token.Index = Token.Index + 1
    EXIT SUB
 END IF
 ' locate expression symbol
 Token.Element$ = MID$(Out2, Token.Index, 1)
 IF INSTR(Token.List, Token.Element$) THEN
    Token = 1 ' store token type
    Strng = Token.Element$ ' store token
    Token.Index = Token.Index + 1 ' increment pointer
    EXIT SUB
 END IF
 ' locate expression is number
 Token.Element$ = MID$(Out2, Token.Index, 1)
 IF Token.Element$ >= "0" AND Token.Element$ <= "9" THEN
    ' increment token until token is other than number
    DO
       IF LEN(Token.Element$) = False THEN
          EXIT DO
       END IF
       IF INSTR(Token.List, Token.Element$) THEN
          EXIT DO
       END IF
       Strng = Strng + Token.Element$
       Token.Index = Token.Index + 1
       Token.Element$ = MID$(Out2, Token.Index, 1)
    LOOP
    Token = 2 ' store token type
    EXIT SUB
 END IF
 ' locate expression is number beginning with decimal
 Token.Element$ = MID$(Out2, Token.Index, 1)
 IF Token.Element$ = "." THEN
    ' increment token until token is other than number
    DO
       IF LEN(Token.Element$) = False THEN
          EXIT DO
       END IF
       IF INSTR(Token.List, Token.Element$) THEN
          EXIT DO
       END IF
       Strng = Strng + Token.Element$
       Token.Index = Token.Index + 1
       Token.Element$ = MID$(Out2, Token.Index, 1)
    LOOP
    Token = 2 ' store token type
    EXIT SUB
 END IF
 ' locate expression is alphabetic
 Token.Element$ = UCASE$(MID$(Out2, Token.Index, 1))
 IF Token.Element$ >= "A" AND Token.Element$ <= "Z" THEN
    ' increment token until token is other than alphabetic
    DO
       IF LEN(Token.Element$) = False THEN
          EXIT DO
       END IF
       IF INSTR(Token.List, Token.Element$) THEN
          EXIT DO
       END IF
       Strng = Strng + Token.Element$
       Token.Index = Token.Index + 1
       Token.Element$ = UCASE$(MID$(Out2, Token.Index, 1))
    LOOP
    Token = 3 ' store token type
    EXIT SUB
 END IF
 Token = False
 Strng = Token.Element$
 Token.Index = Token.Index + 1
END SUB

' gets two-symbol tokens,
' increments pointer to next token.
SUB Get.Token2 (Token.Exists)
 Token.Exists = False
 Stored.Token$ = Out2
 ' locate expression symbols
 Next.Token$ = MID$(Stored.Token$, Token.Index, 2)
 SELECT CASE Next.Token$
 CASE ">=", "=>"
    Token = 1 ' store token type
    Strng = ">=" ' store token
    Token.Index = Token.Index + 2 ' increment pointer
    Token.Exists = True
 CASE "<=", "=<"
    Token = 1 ' store token type
    Strng = "<=" ' store token
    Token.Index = Token.Index + 2 ' increment pointer
    Token.Exists = True
 CASE "<>", "><"
    Token = 1 ' store token type
    Strng = "<>" ' store token
    Token.Index = Token.Index + 2 ' increment pointer
    Token.Exists = True
 END SELECT
END SUB

' verifies next token is opening parenthesis.
SUB Get.Token3
 Strng = Nul
 Token = False
 IF Token.Index > LEN(Out2) THEN
    Strng = "<missing opening parenthesis>"
    ERROR 92
 END IF
 ' locate parenthesis symbol
 Token.Element$ = MID$(Out2, Token.Index, 1)
 IF Token.Element$ = "(" THEN
    Token = 1 ' store token type
    Strng = Token.Element$ ' store token
    Token.Index = Token.Index + 1 ' increment pointer
    EXIT SUB
 END IF
 Strng = "<missing opening parenthesis>"
 ERROR 92
END SUB

' verifies last token is closing parenthesis.
SUB Get.Token4
 ' locate parenthesis symbol
 IF Strng = ")" THEN
    EXIT SUB
 END IF
 Strng = "<missing closing parenthesis>"
 ERROR 92
END SUB

' parses single letter tokens
SUB Parse.Alphabetic1 (Temp#)
 SELECT CASE UCASE$(Strng) ' test variable symbol
 CASE "A" TO "Z"
    Element = ASC(UCASE$(Strng)) - 64
    Element.Type$ = MID$(Out2, Token.Index, 1)
    SELECT CASE Element.Type$
    CASE "("
       CALL Get.Token
       CALL Get.Token
       CALL Parse1(Temp#)
       IF Strng = ")" THEN
          Temp# = Arrays(Element, CINT(Temp#))
          Last.Token = True
       ELSE
          Strng = "<missing closing token>"
          ERROR 92
       END IF
    CASE "["
       CALL Get.Token
       CALL Get.Token
       CALL Parse1(Temp#)
       IF Strng = "]" THEN
          Temp# = Arrays(Element, CINT(Temp#))
          Last.Token = True
       ELSE
          Strng = "<missing closing token>"
          ERROR 92
       END IF
    CASE "{"
       CALL Get.Token
       CALL Get.Token
       CALL Parse1(Temp#)
       IF Strng = "}" THEN
          Temp# = Arrays(Element, CINT(Temp#))
          Last.Token = True
       ELSE
          Strng = "<missing closing token>"
          ERROR 92
       END IF
    CASE ELSE
       Temp# = Variables(Element)
       Last.Token = True
    END SELECT
 CASE ELSE
    ERROR 92
 END SELECT
END SUB

' parses two-letter tokens
SUB Parse.Alphabetic2 (Temp#)
 SELECT CASE UCASE$(Strng)
 ' FN(1) to FN(26)
 CASE "FN"
    Var$ = LEFT$(Out2, Token.Index - 3)
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp4#)
    CALL Get.Token4
    Func% = CINT(Temp4#)
    IF Func% >= 1 AND Func% <= Max.FNs THEN
       Var2$ = MID$(Out2, Token.Index)
       Out2 = Definitions(Func%)
       Out2 = LTRIM$(Out2)
       IF Out2 = Nul THEN
          Strng = "<bad FN token>"
          ERROR 92
       END IF
       Token.Index = 1
       CALL Get.Token
       CALL Parse1(Temp#)
       Out2 = Var$ + Var2$
       Token.Index = LEN(Var$) + 1
       EXIT SUB
    END IF
    Strng = "<bad FN range>"
    ERROR 92
 ' functions w/o parameters
 CASE "PI" ' calculate PI
    Temp# = ATN(1#) * 4#
    Last.Token = True
 CASE "EX" ' calculate E
    Temp# = EXP(1#)
    Last.Token = True
 ' special case functions
 CASE "OR"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Number# = Temp#
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = Number# OR Temp#
    CALL Get.Token4
    Last.Token = True
 CASE ELSE
    IF MID$(Strng, 2, 1) = "$" THEN
       SELECT CASE UCASE$(LEFT$(Strng, 1))
       CASE "A" TO "Z"
	  Element = ASC(UCASE$(LEFT$(Strng, 1))) - 64
	  Out3 = Strngs(Element)
	  Last.Token = False
       END SELECT
    ELSE
       ERROR 92
    END IF
 END SELECT
END SUB

' parses three and greater letter tokens
SUB Parse.Alphabetic3 (Temp#)
 DIM DTAfile AS DTAtype
 SELECT CASE UCASE$(Strng)
 ' constant functions
 CASE "AUTHOR$"
    Out3 = "Erik Jon Oredson AS. CSci"
    Last.Token = False
 CASE "EMAIL$"
    Out3 = "eoredson@yahoo.com"
    Last.Token = False
 CASE "URL$"
    Out3 = "www.simtel.net www.filegate.net"
    Last.Token = False
 CASE "VERSION$"
    Out3 = "2.0a"
    Last.Token = False
 CASE "RELEASE$"
    Out3 = "1.0"
    Last.Token = False
 CASE "PROGRAM$"
    Out3 = "whatis"
    Last.Token = False
 CASE "PUBLISH$"
    Out3 = "04/15/2001"
    Last.Token = False
 CASE "COOKIE$"
    Out3 = "Thank you for all the cookies!"
    Last.Token = False
 ' functions w/o parameters
 CASE "FALSE"
    Temp# = FalseD
    Last.Token = True
 CASE "TRUE"
    Temp# = TrueD
    Last.Token = True
 CASE "TIMER"
    Temp# = TIMER
    Last.Token = True
 CASE "RND"
    Temp# = RND
    Last.Token = True
 ' boolean functions
 CASE "AND"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Number# = Temp#
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = Number# AND Temp#
    CALL Get.Token4
    Last.Token = True
 CASE "MOD"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Number# = Temp#
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = Number# MOD Temp#
    CALL Get.Token4
    Last.Token = True
 CASE "NOR"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Number# = Temp#
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = NOT (Number# OR Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "NON"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Number# = Temp#
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = NOT (Number# IMP Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "XAN"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Number# = Temp#
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = NOT (Number# AND Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "XOR"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Number# = Temp#
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = Number# XOR Temp#
    CALL Get.Token4
    Last.Token = True
 CASE "IMP"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Number# = Temp#
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = Number# IMP Temp#
    CALL Get.Token4
    Last.Token = True
 CASE "EQV"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Number# = Temp#
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = Number# EQV Temp#
    CALL Get.Token4
    Last.Token = True
 CASE "NOT"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = NOT Temp#
    CALL Get.Token4
    Last.Token = True
 ' date\time functions
 CASE "CLOCK"
    Temp# = Timer
    Last.Token = True
 CASE "DATE$"
    Out3 = DATE$
    Last.Token = False
 CASE "TIME$"
    Out3 = TIME$
    Last.Token = False
 ' conversion/calculation functions
 CASE "BIN$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Out3 = Nul
    IF Temp# >= FalseD THEN
       Digits = False
       DO
	  IF 2 ^ (Digits + 1) > Temp# THEN
	     EXIT DO
	  END IF
	  Digits = Digits + 1
       LOOP
       FOR Power = Digits TO 0 STEP -1
	  IF Temp# - 2 ^ Power >= 0 THEN
	     Temp# = Temp# - 2 ^ Power
	     Out3 = Out3 + "1"
	  ELSE
	     Out3 = Out3 + "0"
	  END IF
       NEXT
    END IF
    CALL Get.Token4
    Last.Token = False
 CASE "CHR$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Out3 = CHR$(CINT(Temp#))
    CALL Get.Token4
    Last.Token = False
 CASE "HEX$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Out3 = HEX$(Temp#)
    CALL Get.Token4
    Last.Token = False
 CASE "OCT$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Out3 = OCT$(Temp#)
    CALL Get.Token4
    Last.Token = False
 CASE "STR$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Out3 = STR$(Temp#)
    CALL Get.Token4
    Last.Token = False
 CASE "LCASE$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Out3 = LCASE$(Out3)
    CALL Get.Token4
    Last.Token = False
 CASE "UCASE$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Out3 = UCASE$(Out3)
    CALL Get.Token4
    Last.Token = False
 CASE "RTRIM$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Out3 = RTRIM$(Out3)
    CALL Get.Token4
    Last.Token = False
 CASE "LTRIM$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Out3 = LTRIM$(Out3)
    CALL Get.Token4
    Last.Token = False
 CASE "TRIM$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Out3 = LTRIM$(RTRIM$(Out3))
    CALL Get.Token4
    Last.Token = False
 CASE "SPACE$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Out3 = STRING$(Temp#,32)
    CALL Get.Token4
    Last.Token = False
 CASE "MID$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp4# = Temp#
    IF Strng = "," THEN
       CALL Get.Token
       CALL Parse1(Temp#)
       Length# = Temp#
       Out3 = MID$(Out3, Temp4#, Length#)
    ELSE
       Out3 = MID$(Out3, Temp4#)
    END IF
    CALL Get.Token4
    Last.Token = False
 CASE "REMID$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    CALL Get.Token
    CALL Parse1(Temp#)
    Start# = Temp#
    CALL Get.Token
    CALL Parse1(Temp#)
    Length# = Temp#
    Out3 = LEFT$(Out3, Start# - 1) + MID$(Out3, Start# + Length#)
    CALL Get.Token4
    Last.Token = False
 CASE "DEMID$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    String1$ = Out3
    CALL Get.Token
    CALL Parse1(Temp#)
    IF LEN(String1$) AND LEN(Out3) THEN
       Imbedded = INSTR(String1$, Out3)
       WHILE Imbedded
          String1$ = LEFT$(String1$, Imbedded - 1) + MID$(String1$, Imbedded + LEN(Out3))
	  Imbedded = INSTR(String1$, Out3)
       WEND
    END IF
    Out3 = String1$
    CALL Get.Token4
    Last.Token = False
 CASE "REPLACE$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    String1$ = Out3
    CALL Get.Token
    CALL Parse1(Temp#)
    String2$ = Out3
    CALL Get.Token
    CALL Parse1(Temp#)
    String3$ = Out3
    IF String1$ = Nul OR String2$ = Nul OR String2$ = String3$ THEN
       Out3 = String1$
       EXIT SUB
    END IF
    Temp1 = 1
    DO
       IF String1$ = Nul THEN
	  EXIT DO
       END IF
       Var1 = INSTR(Temp1, String1$, String2$)
       IF Var1 = False THEN
	  EXIT DO
       END IF
       String1$ = LEFT$(String1$, Var1 - 1) + String3$ + MID$(String1$, Var1 + LEN(String2$))
       Temp1 = Temp1 + LEN(String3$)
    LOOP
    Out3 = String1$
    CALL Get.Token4
    Last.Token = False
 CASE "LEFT$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    CALL Get.Token
    CALL Parse1(Temp#)
    Out3 = LEFT$(Out3, Temp#)
    CALL Get.Token4
    Last.Token = False
 CASE "RIGHT$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    CALL Get.Token
    CALL Parse1(Temp#)
    Out3 = RIGHT$(Out3, Temp#)
    CALL Get.Token4
    Last.Token = False
 CASE "STRING$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Number# = Temp#
    CALL Get.Token
    CALL Parse1(Temp#)
    IF Last.Token THEN
       Out3 = STRING$(Number#, Temp#)
    ELSE
       Out3 = STRING$(Number#, Out3)
    END IF
    CALL Get.Token4
    Last.Token = False
 CASE "ABS"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = ABS(Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "ASC"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = ASC(Out3)
    CALL Get.Token4
    Last.Token = True
 CASE "ATN"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = ATN(Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "COS"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = COS(Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "COT"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = COS(Temp#) / SIN(Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "CSC"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = 1# / SIN(Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "EXP"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = EXP(Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "FBN" ' calculate xth fibonachi value
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Fibo.A# = 1
    Fibo.B# = 1
    IF Temp# >= 1 THEN
       FOR Fibo.Number# = 1 TO Temp# - 1
	   Fibo.Temp# = Fibo.B#
	   Fibo.B# = Fibo.A# + Fibo.B#
	   Fibo.A# = Fibo.Temp#
       NEXT
    END IF
    Temp# = Fibo.B#
    CALL Get.Token4
    Last.Token = True
 CASE "FCT"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Factorial# = 1
    Factorial.Count# = INT(Temp#)
    IF Factorial.Count# >= 1 THEN
       FOR Factorial.Count# = 1 TO Temp#
	  Factorial# = Factorial# * Factorial.Count#
       NEXT
    END IF
    Temp# = Factorial#
    CALL Get.Token4
    Last.Token = True
 CASE "FIX"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = FIX(Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "INSTR"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    IF Last.Token THEN
       Start# = Temp#
       CALL Get.Token
       CALL Parse1(Temp#)
       Stored.String$ = Out3
       CALL Get.Token
       CALL Parse1(Temp#)
    ELSE
       Start# = 1#
       Stored.String$ = Out3
       CALL Get.Token
       CALL Parse1(Temp#)
    END IF
    Temp# = INSTR(Start#, Stored.String$, Out3)
    CALL Get.Token4
    Last.Token = True
 CASE "FRE"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    IF Last.Token THEN
       Value# = Temp#
       Temp# = FRE(Value#)
    ELSE
       Temp# = FRE(Out3)
    END IF
    CALL Get.Token4
    Last.Token = True
 CASE "INT"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = INT(Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "LEN"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = LEN(Out3)
    CALL Get.Token4
    Last.Token = True
 CASE "LOG"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = LOG(Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "PRM" ' calculate xth prime
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Prime.Number# = INT(Temp#)
    Prime# = 1
    IF Prime.Number# > FalseD THEN
       Prime.Counter# = False
       DO
	  Prime# = Prime# + 1
	  Prime.Flag = False
          FOR Loop.Count# = 2# TO INT(SQR(Prime#))
             IF Prime# / Loop.Count# = INT(Prime# / Loop.Count#) THEN
		Prime.Flag = True
		EXIT FOR
	     END IF
	  NEXT
	  IF Prime.Flag = False THEN
	     Prime.Counter# = Prime.Counter# + 1
	  END IF
	  IF Prime.Counter# = Prime.Number# THEN
	     EXIT DO
	  END IF
       LOOP
    END IF
    Temp# = Prime#
    CALL Get.Token4
    Last.Token = True
 CASE "SEC"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = 1# / COS(Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "SGN"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = SGN(Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "SIN"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = SIN(Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "SQR"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = SQR(Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "CBR"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = Temp# ^ (1#/3#)
    CALL Get.Token4
    Last.Token = True
 CASE "NTH"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Number# = Temp#
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = Number# ^ (1#/Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "PWR"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Number# = Temp#
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = Number# ^ Temp#
    CALL Get.Token4
    Last.Token = True
 CASE "TAN"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = TAN(Temp#)
    CALL Get.Token4
    Last.Token = True
 CASE "VAL"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = VAL(Out3)
    CALL Get.Token4
    Last.Token = True
 ' misc. functions
 CASE "FORMAT$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Value# = Temp#
    CALL Get.Token
    CALL Parse1(Temp#)
    Out3 = FORMAT$(Value#, Out3)
    CALL Get.Token4
    Last.Token = False
 ' disk functions
 CASE "DIR$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    IF Out3 = Nul THEN
       Out3 = DIR$
    ELSE
       Out3 = DIR$(Out3)
    END IF
    CALL Get.Token4
    Last.Token = False
 CASE "CURDIR$"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Out3 = CURDIR$(Out3)
    CALL Get.Token4
    Last.Token = False
 CASE "DRV"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    IF Last.Token THEN
       Drive.Number% = CINT(Temp#)
    ELSE
       Drive.Number% = CINT(ASC(UCASE$(Out3))-64)
    END IF
    Last.Token = True
    CALL Get.Token4
    InregsX.AX = &H3600
    InregsX.DX = Drive.Number%
    CALL InterruptX(&H21, InregsX, OutregsX)
    Temp# = FalseD
    IF OutregsX.AX = &HFFFF THEN
       EXIT SUB
    END IF
    ASCIZ = CHR$(Drive.Number% + 64) + ":\" + CHR$(0)
    InregsX.AX = &H7303
    InregsX.DS = VARSEG(ASCIZ)
    InregsX.DX = VARPTR(ASCIZ)
    InregsX.ES = VARSEG(FAT32Struc)
    InregsX.DI = VARPTR(FAT32Struc)
    InregsX.CX = LEN(FAT32Struc)
    CALL InterruptX(&H21, InregsX, OutregsX)
    IF (OutregsX.Flags AND &H1) = &H0 THEN
       IF (OutregsX.AX AND &HFF) <> 0 THEN
          Bytes# = CLNG(ASC(MID$(FAT32Struc.BytesPerSector, 1, 1)))
          Bytes# = Bytes# + CLNG(ASC(MID$(FAT32Struc.BytesPerSector, 2, 1))) * 256#
          Bytes# = Bytes# + CLNG(ASC(MID$(FAT32Struc.BytesPerSector, 3, 1))) * 65536#
          Bytes# = Bytes# + CLNG(ASC(MID$(FAT32Struc.BytesPerSector, 4, 1))) * 16777216#
          Sectors# = CLNG(ASC(MID$(FAT32Struc.FreeSectors, 1, 1)))
          Sectors# = Sectors# + CLNG(ASC(MID$(FAT32Struc.FreeSectors, 2, 1))) * 256#
          Sectors# = Sectors# + CLNG(ASC(MID$(FAT32Struc.FreeSectors, 3, 1))) * 65536#
          Sectors# = Sectors# + CLNG(ASC(MID$(FAT32Struc.FreeSectors, 4, 1))) * 16777216#
          Temp# = Bytes# * Sectors#
          EXIT SUB
       END IF
    END IF
    InregsX.AX = &H3600
    InregsX.DX = Drive.Number%
    CALL InterruptX(&H21, InregsX, OutregsX)
    IF OutregsX.AX < False THEN
       Sectors# = CDBL(OutregsX.AX + 65536)
    ELSE
       Sectors# = CDBL(OutregsX.AX)
    END IF
    IF OutregsX.BX < False THEN
       Clusters# = CDBL(OutregsX.BX + 65536)
    ELSE
       Clusters# = CDBL(OutregsX.BX)
    END IF
    IF OutregsX.CX < False THEN
       Bytes# = CDBL(OutregsX.CX + 65536)
    ELSE
       Bytes# = CDBL(OutregsX.CX)
    END IF
    Temp# = Sectors# * Clusters# * Bytes#
 CASE "TDRV"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    IF Last.Token THEN
       Drive.Number% = CINT(Temp#)
    ELSE
       Drive.Number% = CINT(ASC(UCASE$(Out3))-64)
    END IF
    Last.Token = True
    CALL Get.Token4
    InregsX.AX = &H3600
    InregsX.DX = Drive.Number%
    CALL InterruptX(&H21, InregsX, OutregsX)
    Temp# = FalseD
    IF OutregsX.AX = &HFFFF THEN
       EXIT SUB
    END IF
    ASCIZ = CHR$(Drive.Number% + 64) + ":\" + CHR$(0)
    InregsX.AX = &H7303
    InregsX.DS = VARSEG(ASCIZ)
    InregsX.DX = VARPTR(ASCIZ)
    InregsX.ES = VARSEG(FAT32Struc)
    InregsX.DI = VARPTR(FAT32Struc)
    InregsX.CX = LEN(FAT32Struc)
    CALL InterruptX(&H21, InregsX, OutregsX)
    IF (OutregsX.Flags AND &H1) = &H0 THEN
       IF (OutregsX.AX AND &HFF) <> 0 THEN
          Bytes# = CLNG(ASC(MID$(FAT32Struc.BytesPerSector, 1, 1)))
          Bytes# = Bytes# + CLNG(ASC(MID$(FAT32Struc.BytesPerSector, 2, 1))) * 256#
          Bytes# = Bytes# + CLNG(ASC(MID$(FAT32Struc.BytesPerSector, 3, 1))) * 65536#
          Bytes# = Bytes# + CLNG(ASC(MID$(FAT32Struc.BytesPerSector, 4, 1))) * 16777216#
          Sectors# = CLNG(ASC(MID$(FAT32Struc.TotalSectors, 1, 1)))
          Sectors# = Sectors# + CLNG(ASC(MID$(FAT32Struc.TotalSectors, 2, 1))) * 256#
          Sectors# = Sectors# + CLNG(ASC(MID$(FAT32Struc.TotalSectors, 3, 1))) * 65536#
          Sectors# = Sectors# + CLNG(ASC(MID$(FAT32Struc.TotalSectors, 4, 1))) * 16777216#
          Temp# = Bytes# * Sectors#
          EXIT SUB
       END IF
    END IF
    InregsX.AX = &H3600
    InregsX.DX = Drive.Number%
    CALL InterruptX(&H21, InregsX, OutregsX)
    IF OutregsX.AX < False THEN
       Sectors# = CDBL(OutregsX.AX + 65536)
    ELSE
       Sectors# = CDBL(OutregsX.AX)
    END IF
    IF OutregsX.CX < False THEN
       Clusters# = CDBL(OutregsX.CX + 65536)
    ELSE
       Clusters# = CDBL(OutregsX.CX)
    END IF
    IF OutregsX.DX < False THEN
       Bytes# = CDBL(OutregsX.DX + 65536)
    ELSE
       Bytes# = CDBL(OutregsX.DX)
    END IF
    Temp# = Sectors# * Clusters# * Bytes#
 CASE "FILESIZE"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = FalseD
    IF Last.Token = False THEN
       IF LEN(Out3) THEN
          Last.Token = True
          ASCIZ = Out3 + CHR$(0)
          ' restore directory search dta
          InregsX.AX = &H1A00
          InregsX.DS = VARSEG(DTAfile)
          InregsX.DX = VARPTR(DTAfile)
          CALL InterruptX(&H21, InregsX, OutregsX)
          ' find first filename
          InregsX.AX = &H4E00
          InregsX.CX = &H27
          InregsX.DS = VARSEG(ASCIZ)
          InregsX.DX = VARPTR(ASCIZ)
          CALL InterruptX(&H21, InregsX, OutregsX)
          ' check findfirst error
          IF (OutregsX.Flags AND &H1) = &H0 THEN
             ' store file size
             Temp# = Dfalse
             Temp# = Temp# + ASC(MID$(DTAfile.FileSize, 4, 1))
             Temp# = Temp# * &H100 + ASC(MID$(DTAfile.FileSize, 3, 1))
             Temp# = Temp# * &H100 + ASC(MID$(DTAfile.FileSize, 2, 1))
             Temp# = Temp# * &H100 + ASC(MID$(DTAfile.FileSize, 1, 1))
          END IF
          ' restore basic dta
          InregsX.AX = &H1A00
          InregsX.DS = BASIC.DTA.SEG
          InregsX.DX = BASIC.DTA.OFF
          CALL InterruptX(&H21, InregsX, OutregsX)
       END IF
    END IF
    CALL Get.Token4
 CASE "FILEATTR"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = FalseD
    IF Last.Token = False THEN
       IF LEN(Out3) THEN
	  Last.Token = True
	  ASCIZ = Out3 + CHR$(0)
	  Out3 = SPACE$(8)
	  ' restore directory search dta
	  InregsX.AX = &H1A00
          InregsX.DS = VARSEG(DTAfile)
          InregsX.DX = VARPTR(DTAfile)
	  CALL InterruptX(&H21, InregsX, OutregsX)
	  ' find first filename
	  InregsX.AX = &H4E00
	  InregsX.CX = &H37
	  InregsX.DS = VARSEG(ASCIZ)
	  InregsX.DX = VARPTR(ASCIZ)
	  CALL InterruptX(&H21, InregsX, OutregsX)
	  ' store file attribute
          Temp# = ASC(DTAfile.FileAttr)
	  Attribute = CINT(Temp#)
	  ' check findfirst error
	  IF (OutregsX.Flags AND &H1) = &H1 THEN
	     Out3 = Out3 + "E"
	  END IF
	  IF (OutregsX.Flags AND &H1) = &H0 THEN
	     IF (Attribute AND 1) = 1 THEN
		MID$(Out3, 1, 1) = "R"
	     END IF
	     IF (Attribute AND 2) = 2 THEN
		MID$(Out3, 2, 1) = "H"
	     END IF
	     IF (Attribute AND 4) = 4 THEN
		MID$(Out3, 3, 1) = "S"
	     END IF
	     IF (Attribute AND 8) = 8 THEN
		MID$(Out3, 4, 1) = "V"
	     END IF
	     IF (Attribute AND &H10) = &H10 THEN
		MID$(Out3, 5, 1) = "D"
	     END IF
	     IF (Attribute AND &H20) = &H20 THEN
		MID$(Out3, 6, 1) = "A"
	     END IF
	     IF (Attribute AND &H40) = &H40 THEN
		MID$(Out3, 7, 1) = "U"
	     END IF
	     IF (Attribute AND &H80) = &H80 THEN
		MID$(Out3, 8, 1) = "N"
	     END IF
	  END IF
	  ' restore basic dta
	  InregsX.AX = &H1A00
	  InregsX.DS = BASIC.DTA.SEG
	  InregsX.DX = BASIC.DTA.OFF
	  CALL InterruptX(&H21, InregsX, OutregsX)
       END IF
    END IF
    CALL Get.Token4
 CASE "FILEDATE"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = FalseD
    IF Last.Token = False THEN
       IF LEN(Out3) THEN
	  ASCIZ = Out3 + CHR$(0)
	  Out3 = Nul
	  ' restore directory search dta
	  InregsX.AX = &H1A00
          InregsX.DS = VARSEG(DTAfile)
          InregsX.DX = VARPTR(DTAfile)
	  CALL InterruptX(&H21, InregsX, OutregsX)
	  ' find first filename
	  InregsX.AX = &H4E00
	  InregsX.CX = &H27
	  InregsX.DS = VARSEG(ASCIZ)
	  InregsX.DX = VARPTR(ASCIZ)
	  CALL InterruptX(&H21, InregsX, OutregsX)
	  ' check findfirst error
	  IF (OutregsX.Flags AND &H1) = &H0 THEN
	     ' store file date
             Work! = ASC(MID$(DTAfile.FileDate, 2, 1))
             Work! = Work! * &H100 + ASC(MID$(DTAfile.FileDate, 1, 1))
             YearTemp! = INT(Work! / 512)
             MonthTemp! = INT((Work! AND &H1E0) / 32)
             DayTemp! = INT(Work! AND &H1F)
             YearTemp! = YearTemp! + 1980
             Out3 = RIGHT$(STR$(MonthTemp! + 100), 2) + "-" + RIGHT$(STR$(DayTemp! + 100), 2) + "-" + MID$(STR$(YearTemp!), 2)
	  END IF
	  ' restore basic dta
	  InregsX.AX = &H1A00
	  InregsX.DS = BASIC.DTA.SEG
	  InregsX.DX = BASIC.DTA.OFF
	  CALL InterruptX(&H21, InregsX, OutregsX)
       END IF
    END IF
    CALL Get.Token4
 CASE "FILETIME"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    Temp# = FalseD
    IF Last.Token = False THEN
       IF LEN(Out3) THEN
	  ASCIZ = Out3 + CHR$(0)
	  Out3 = Nul
	  ' restore directory search dta
	  InregsX.AX = &H1A00
          InregsX.DS = VARSEG(DTAfile)
          InregsX.DX = VARPTR(DTAfile)
	  CALL InterruptX(&H21, InregsX, OutregsX)
	  ' find first filename
	  InregsX.AX = &H4E00
	  InregsX.CX = &H27
	  InregsX.DS = VARSEG(ASCIZ)
	  InregsX.DX = VARPTR(ASCIZ)
	  CALL InterruptX(&H21, InregsX, OutregsX)
	  ' check findfirst error
	  IF (OutregsX.Flags AND &H1) = &H0 THEN
             Work! = ASC(MID$(DTAfile.FileTime, 2, 1))
             Work! = Work! * &H100 + ASC(MID$(DTAfile.FileTime, 1, 1))
             HourTemp! = INT(Work! / 2048)
             MinuteTemp! = INT((Work! AND &H7E0) / 32)
             SecondsTemp! = INT((Work! AND &H1F) / 2)
             Out3 = RIGHT$(STR$(HourTemp! + 100), 2) + ":" + RIGHT$(STR$(MinuteTemp! + 100), 2) + ":" + RIGHT$(STR$(SecondsTemp! + 100), 2)
	  END IF
	  ' restore basic dta
	  InregsX.AX = &H1A00
	  InregsX.DS = BASIC.DTA.SEG
	  InregsX.DX = BASIC.DTA.OFF
	  CALL InterruptX(&H21, InregsX, OutregsX)
       END IF
    END IF
    CALL Get.Token4
 CASE "VLABEL"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    IF Last.Token THEN
       Drive.Number% = CINT(Temp#)
    ELSE
       Drive.Number% = CINT(ASC(UCASE$(Out3)) - 64)
    END IF
    Last.Token = False
    ASCIZ = CHR$(Drive.Number% + 64) + ":\*.*" + CHR$(0)
    Out3 = Nul
    ' restore directory search dta
    InregsX.AX = &H1A00
    InregsX.DS = VARSEG(DTAfile)
    InregsX.DX = VARPTR(DTAfile)
    CALL InterruptX(&H21, InregsX, OutregsX)
    ' find volume label
    InregsX.AX = &H4E00
    InregsX.CX = &H8
    InregsX.DS = VARSEG(ASCIZ)
    InregsX.DX = VARPTR(ASCIZ)
    CALL InterruptX(&H21, InregsX, OutregsX)
    ' check findfirst error
    IF (OutregsX.Flags AND &H1) = &H0 THEN
       Out3 = DTAfile.ASCIZfilename
    END IF
    ' restore basic dta
    InregsX.AX = &H1A00
    InregsX.DS = BASIC.DTA.SEG
    InregsX.DX = BASIC.DTA.OFF
    CALL InterruptX(&H21, InregsX, OutregsX)
    CALL Get.Token4
 CASE "VSERIAL"
    CALL Get.Token3
    CALL Get.Token
    CALL Parse1(Temp#)
    IF Last.Token THEN
       Drive.Number% = CINT(Temp#)
    ELSE
       Drive.Number% = CINT(ASC(UCASE$(Out3)) - 64)
    END IF
    Last.Token = False
    Out3 = Nul
    ' get volume info
    InregsX.AX = &H6900
    InregsX.BX = Drive.Number%
    InregsX.DS = VARSEG(BPBfile)
    InregsX.DX = VARPTR(BPBfile)
    CALL InterruptX(&H21, InregsX, OutregsX)
    ' check flag error
    IF (OutregsX.Flags AND &H1) = &H0 THEN
       ' store volume serial number
       FOR Serial.Digit = 4 TO 1 STEP -1
	  IF Serial.Digit = 2 THEN
	     Out3 = Out3 + "-"
	  END IF
          Serial.Value = ASC(MID$(BPBfile.Serial, Serial.Digit, 1))
          Out3 = Out3 + RIGHT$(HEX$(Serial.Value + &H100), 2)
       NEXT
    END IF
    CALL Get.Token4
 CASE ELSE
    ERROR 92
 END SELECT
END SUB

' process a number
SUB Parse.Numeric (Temp#)
 SELECT CASE RIGHT$(UCASE$(Strng), 1) ' compare character after number
 CASE "P" ' prime number suffix
    FOR Digit = 1 TO LEN(Strng) - 1
       OutX$ = UCASE$(MID$(Strng, Digit, 1))
       SELECT CASE OutX$
       CASE "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
          ' nul
       CASE ELSE
          ERROR 130
       END SELECT
    NEXT
    Temp# = CDBL(VAL(LEFT$(Strng, LEN(Strng) - 1)))
    Prime.Number# = INT(Temp#)
    Prime# = 1
    IF Prime.Number# > False THEN
       Prime.Counter# = False
       DO
	  Prime# = Prime# + 1
	  Prime.Flag = False
          FOR Factor# = 2 TO INT(SQR(Prime#))
             IF Prime# / Factor# = INT(Prime# / Factor#) THEN
		Prime.Flag = True
		EXIT FOR
	     END IF
	  NEXT
	  IF Prime.Flag = False THEN
	     Prime.Counter# = Prime.Counter# + 1
	  END IF
	  IF Prime.Counter# = Prime.Number# THEN
	     EXIT DO
	  END IF
       LOOP
    END IF
    Temp# = Prime#
 CASE "F" ' factorial
    Temp2 = False
    Temp3 = LEN(Strng)
    DO
       IF UCASE$(MID$(Strng, Temp3, 1)) <> "F" THEN
          EXIT DO
       END IF
       Temp2 = Temp2 + 1
       Temp3 = Temp3 - 1
    LOOP
    FOR Decimal.Digit = 1 TO Temp3
       OutX$ = UCASE$(MID$(Strng, Decimal.Digit, 1))
       SELECT CASE OutX$
       CASE "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
          ' nul
       CASE ELSE
          ERROR 130
       END SELECT
    NEXT
    Temp4# = VAL(LEFT$(Strng, Temp3))
    FOR Number = 1 TO Temp2
       Temp# = 1#
       FOR Factorial# = 2# TO Temp4#
          Temp# = Temp# * Factorial#
       NEXT
       Temp4# = Temp#
    NEXT
 CASE "H" ' hexidecimal suffix
    Temp# = False
    Hex.Power = False
    FOR Hex.Digit = LEN(Strng) - 1 TO 1 STEP -1
       OutX$ = UCASE$(MID$(Strng, Hex.Digit, 1))
       SELECT CASE OutX$
       CASE "0" TO "9", "A" TO "F"
          Hex.Value = VAL("&H" + OutX$)
          Temp# = Temp# + Hex.Value * 16 ^ Hex.Power
          Hex.Power = Hex.Power + 1
       CASE ELSE
          ERROR 130
       END SELECT
    NEXT
 CASE "O" ' octal suffix
    Temp# = False
    Octal.Power = False
    FOR Octal.Digit = LEN(Strng) - 1 TO 1 STEP -1
       OutX$ = MID$(Strng, Octal.Digit, 1)
       IF OutX$ >= "0" AND OutX$ <= "7" THEN
          Octal.Value = VAL(OutX$)
          Temp# = Temp# + Octal.Value * 8 ^ Octal.Power
          Octal.Power = Octal.Power + 1
       ELSE
          ERROR 130
       END IF
    NEXT
 CASE "B" ' binary suffix
    Temp# = False
    Binary.Power = False
    FOR Binary.Digit = LEN(Strng) - 1 TO 1 STEP -1
       OutX$ = MID$(Strng, Binary.Digit, 1)
       SELECT CASE OutX$
       CASE "0"
          ' nul
       CASE "1"
	  Temp# = Temp# + 2 ^ Binary.Power
       CASE ELSE
          ERROR 130
       END SELECT
       Binary.Power = Binary.Power + 1
    NEXT
 CASE "0" TO "9", "D", "E" ' decimal/exponent suffix
    Decimal = False
    Exponent = False
    Number$ = Strng
 Start.Token:
    FOR Decimal.Digit = 1 TO LEN(Number$)
       OutX$ = UCASE$(MID$(Number$, Decimal.Digit, 1))
       SELECT CASE OutX$
       CASE "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "-", "+"
          ' nul
       CASE "D", "E"
          IF Exponent THEN
             ERROR 140
          END IF
          Exponent = True
          Exponent$ = UCASE$(RIGHT$(Number$, 1))
          IF Exponent$ = "D" OR Exponent$ = "E" THEN
             ' concatenate pre-parsed tokens into exponent
             Unary$ = MID$(Out2, Token.Index, 1)
             IF Unary$ = "+" OR Unary$ = "-" THEN
                Var$ = LEFT$(Out2, Token.Index - 1)
                CALL Get.Token
                Out2 = Var$ + Strng + MID$(Out2, Token.Index)
                CALL Get.Token
                IF Strng = Nul THEN
                   ERROR 140
                END IF
                Number$ = Number$ + Unary$ + Strng
                IF Strng = "-" OR Strng = "+" THEN
                   ERROR 140
                END IF
                IF INSTR(Strng, ".") THEN
                   ERROR 140
                END IF
                DO
                   IF LEFT$(Number$, 1) = "-" THEN
                      Number$ = MID$(Number$, 2)
                   ELSE
                      IF LEFT$(Number$, 1) = "+" THEN
                         Number$ = MID$(Number$, 2)
                      ELSE
                         EXIT DO
                      END IF
                   END IF
                LOOP
                Decimal = False
                Exponent = False
                GOTO Start.Token
             ELSE
                ERROR 140
             END IF
          END IF
       CASE "."
          IF Exponent THEN
             ERROR 140
          END IF
          IF Decimal THEN
             ERROR 140
          END IF
          Decimal = True
       CASE ELSE
          ERROR 130
       END SELECT
    NEXT
    Temp# = CDBL(VAL(Number$))
 CASE ELSE
    ERROR 130
 END SELECT
 CALL Get.Token
 Last.Token = True
END SUB

' parse string in quotes
SUB Parse.Quoted
 Out3 = Nul
 DO UNTIL MID$(Out2, Token.Index, 1) = CHR$(34)
    Out3 = Out3 + MID$(Out2, Token.Index, 1)
    Token.Index = Token.Index + 1
    IF Token.Index > LEN(Out2) THEN
       ERROR 92
       EXIT DO
    END IF
 LOOP
 CALL Get.Token
 CALL Get.Token
 Last.Token = False
END SUB

REM entry to expression parser, each following parser is higher precedence,
REM this routine called by higher precedence routines recursively.

REM precedence of operators:
REM   !, +, -  Not, Unary plus, Unary minus
REM   ^        Power
REM   *, /     Multiplication, Division
REM   \        Integer Division
REM   %        Modulo
REM   +, -     Addition, Subtraction
REM   =, <, >, <>, >=, <=   Relational
REM   |, &, ~, ?, :, #, @, `  Logical (or, and, xor, imp, eqv, nor, non, xan)

SUB Equate (Temp#)
 Last.Token = False
 Out3 = Nul ' reset string storage
 Out4 = Nul ' reset string concatenate storage
 Temp# = False ' reset result
 Token.Index = 1 ' reset pointer to expression token
 CALL Get.Token ' read next token
 CALL Parse1(Temp#) ' entry to parse the expression
 ' parse leftover tokens
 WHILE LEN(Strng)
    CALL Parse1(X#)
    ' toss away any unwanted tokens
    IF Last.Token = 3 THEN
       Strng = "<extra token>"
       ERROR 92
    END IF
 WEND
END SUB

 ' logical parser
SUB Parse1 (Temp#)
 CALL Parse2(Temp#) ' get next operator precedence
 Token.Parsed$ = Strng ' store token
 ' process token
 DO
    SELECT CASE Token.Parsed$
    CASE "|", "&", "~", "?", ":", "#", "@", "`"
       ' Nul
    CASE ELSE
       EXIT DO
    END SELECT
    Token.Stored$ = Out3 ' store current token
    CALL Get.Token ' read next token
    CALL Parse2(Temp2#) ' get next operator
    Out4 = Out3 ' reset current string
    Out3 = Token.Stored$ ' restore previous string
    CALL Arith(Token.Parsed$, Temp#, Temp2#) ' calculate expression
    Token.Parsed$ = Strng ' store next token
 LOOP
END SUB

 ' relational parser
SUB Parse2 (Temp#)
 CALL Parse3(Temp#) ' get next operator precedence
 Token.Parsed$ = Strng ' store token
 ' process token
 DO
    SELECT CASE Token.Parsed$
    CASE "<", ">", "=", ">=", "<=", "<>"
       ' Nul
    CASE ELSE
       EXIT DO
    END SELECT
    Token.Stored$ = Out3 ' store current token
    CALL Get.Token ' read next token
    CALL Parse3(Temp2#) ' get next operator
    Out4 = Out3 ' reset current string
    Out3 = Token.Stored$ ' restore previous string
    CALL Arith(Token.Parsed$, Temp#, Temp2#) ' calculate expression
    Token.Parsed$ = Strng ' store next token
 LOOP
END SUB

 ' addition/subtraction parser
SUB Parse3 (Temp#)
 CALL Parse4(Temp#) ' get next operator precedence
 Token.Parsed$ = Strng ' store token
 ' process token
 DO
    SELECT CASE Token.Parsed$
    CASE "+", "-"
       ' Nul
    CASE ELSE
       EXIT DO
    END SELECT
    Token.Stored$ = Out3 ' store current token
    CALL Get.Token ' read next token
    CALL Parse4(Temp2#) ' get next operator
    Out4 = Out3 ' reset current string
    Out3 = Token.Stored$ ' restore previous string
    CALL Arith(Token.Parsed$, Temp#, Temp2#) ' calculate expression
    Token.Parsed$ = Strng ' store next token
 LOOP
END SUB

 ' modulo parser
SUB Parse4 (Temp#)
 CALL Parse5(Temp#) ' get next operator precedence
 Token.Parsed$ = Strng ' store token
 ' process token
 DO
    SELECT CASE Token.Parsed$
    CASE "%"
       ' Nul
    CASE ELSE
       EXIT DO
    END SELECT
    Token.Stored$ = Out3 ' store current token
    CALL Get.Token ' read next token
    CALL Parse5(Temp2#) ' get next operator
    Out4 = Out3 ' reset current string
    Out3 = Token.Stored$ ' restore previous string
    CALL Arith(Token.Parsed$, Temp#, Temp2#) ' calculate expression
    Token.Parsed$ = Strng ' store next token
 LOOP
END SUB

 ' integer division parser
SUB Parse5 (Temp#)
 CALL Parse6(Temp#) ' get next operator precedence
 Token.Parsed$ = Strng ' store token
 ' process token
 DO
    SELECT CASE Token.Parsed$
    CASE "\"
       ' Nul
    CASE ELSE
       EXIT DO
    END SELECT
    Token.Stored$ = Out3 ' store current token
    CALL Get.Token ' read next token
    CALL Parse6(Temp2#) ' get next operator
    Out4 = Out3 ' reset current string
    Out3 = Token.Stored$ ' restore previous string
    CALL Arith(Token.Parsed$, Temp#, Temp2#) ' calculate expression
    Token.Parsed$ = Strng ' store next token
 LOOP
END SUB

 ' multiplication/division parser
SUB Parse6 (Temp#)
 CALL Parse7(Temp#) ' get next operator precedence
 Token.Parsed$ = Strng ' store token
 ' process token
 DO
    SELECT CASE Token.Parsed$
    CASE "*", "/"
       ' Nul
    CASE ELSE
       EXIT DO
    END SELECT
    Token.Stored$ = Out3 ' store current token
    CALL Get.Token ' read next token
    CALL Parse7(Temp2#) ' get next operator
    Out4 = Out3 ' reset current string
    Out3 = Token.Stored$ ' restore previous string
    CALL Arith(Token.Parsed$, Temp#, Temp2#) ' calculate expression
    Token.Parsed$ = Strng ' store next token
 LOOP
END SUB

 ' power parser
SUB Parse7 (Temp#)
 CALL Parse8(Temp#) ' get next operator precedence
 Token.Parsed$ = Strng ' store token
 ' process token
 DO
    SELECT CASE Token.Parsed$
    CASE "^"
       ' Nul
    CASE ELSE
       EXIT DO
    END SELECT
    Token.Stored$ = Out3 ' store current token
    CALL Get.Token ' read next token
    CALL Parse8(Temp2#) ' get next operator
    Out4 = Out3 ' reset current string
    Out3 = Token.Stored$ ' restore previous string
    CALL Arith(Token.Parsed$, Temp#, Temp2#) ' calculate expression
    Token.Parsed$ = Strng ' store next token
 LOOP
END SUB

 ' not/unary plus/unary negative parser
SUB Parse8 (Temp#)
 Token.Negate$ = Nul ' reset not/unary tokens
 Token.Parsed$ = Strng ' store token
 ' process token
 DO
    SELECT CASE Token.Parsed$
    CASE "!", "-", "+"
       ' Nul
    CASE ELSE
       EXIT DO
    END SELECT
    CALL Get.Token ' read next token
    ' store combined not/unary tokens
    Token.Negate$ = Token.Negate$ + Token.Parsed$
    Token.Parsed$ = Strng
 LOOP
 CALL Parse9(Temp#) ' get next operator precedence
 ' process the combined operators in reverse
 FOR Token.Type = LEN(Token.Negate$) TO 1 STEP -1
    SELECT CASE MID$(Token.Negate$, Token.Type, 1)
    CASE "+"
       ' nul calculation for unary plus
    CASE "-"
       Temp# = -Temp# ' perform negate
    CASE "!" ' not
       Temp# = NOT Temp# ' perform not calculation
    END SELECT
 NEXT
END SUB

 ' string/numeric expression parser,
 ' routine returns calculated expression string/value,
 ' calls Parse1 recursively for values inside parenthesis/functions.
SUB Parse9 (Temp#)
 SELECT CASE Token ' determine token type
 CASE 0 ' unknown token
    IF Last.Token = 3 THEN
       Strng = "<extra token>"
       ERROR 92
    END IF
    IF Strng = Nul THEN
       Strng = "<missing token>"
    END IF
    ERROR 92
    Last.Token = False
 CASE 1 ' token is symbol
    Token.Parsed$ = Strng
    SELECT CASE Token.Parsed$ ' determine token
    CASE ",", ";" ' separaters
       Last.Token = 3
    CASE CHR$(34) ' parse string
       CALL Parse.Quoted
       Quotes = True
    CASE "(" ' calculate opening parenthesis
       CALL Get.Token ' read next token value inside parenthesis
       IF Strng = ")" THEN
          Strng = "<empty closing token>"
          ERROR 92
       END IF
       IF Strng <> ")" THEN
          DO ' calculate value
             CALL Parse1(Temp#) ' call parse entry
          LOOP UNTIL Strng = ")" OR Token = False ' check closing parenthesis
       END IF
       IF Token = False THEN
          Strng = "<missing closing token>"
          ERROR 92
       END IF
       CALL Get.Token ' read next token after parenthesis
    CASE "["
       CALL Get.Token
       IF Strng = "]" THEN
          Strng = "<empty closing token>"
          ERROR 92
       END IF
       IF Strng <> "]" THEN
          DO
             CALL Parse1(Temp#)
          LOOP UNTIL Strng = "]" OR Token = False
       END IF
       IF Token = False THEN
          Strng = "<missing closing token>"
          ERROR 92
       END IF
       CALL Get.Token
    CASE "{"
       CALL Get.Token
       IF Strng = "}" THEN
          Strng = "<empty closing token>"
          ERROR 92
       END IF
       IF Strng <> "}" THEN
          DO
             CALL Parse1(Temp#)
          LOOP UNTIL Strng = "}" OR Token = False
       END IF
       IF Token = False THEN
          Strng = "<missing closing token>"
          ERROR 92
       END IF
       CALL Get.Token
    CASE ")", "]", "}" ' check token is closing parenthesis
       Strng = "<extra closing token>"
       ERROR 92
    END SELECT
 CASE 2 ' token is numeric value
    CALL Parse.Numeric(Temp#)
 CASE 3 ' token type is alphabetic
    SELECT CASE LEN(Strng) ' check variable type
    CASE 1
       CALL Parse.Alphabetic1(Temp#)
    CASE 2
       CALL Parse.Alphabetic2(Temp#)
    CASE ELSE
       CALL Parse.Alphabetic3(Temp#)
    END SELECT
    CALL Get.Token
 END SELECT
END SUB

' remove all white spaces, skip white spaces in quotes.
SUB Remove.Spaces
 Var$ = Out2
 Temp = False
 DO
    Temp = Temp + 1
    IF Temp > LEN(Var$) THEN
       EXIT DO
    END IF
    IF MID$(Var$, Temp, 1) = CHR$(34) THEN
       DO
          Temp = Temp + 1
          IF Temp > LEN(Var$) THEN
             EXIT DO
          END IF
          IF MID$(Var$, Temp, 1) = CHR$(34) THEN
             EXIT DO
          END IF
       LOOP
    END IF
    FOR Blanks = 1 TO LEN(White.Space)
       IF MID$(Var$, Temp, 1) = MID$(White.Space, Blanks, 1) THEN
          Var$ = LEFT$(Var$, Temp - 1) + MID$(Var$, Temp + 1)
          Temp = Temp - 1
          EXIT FOR
       END IF
    NEXT
 LOOP
 Out2 = Var$
END SUB

' uses direct keyboard input to get an input line.
FUNCTION KeyboardLine$
 Var$ = ""
 DO
    IF KeyIS THEN ' check keyboard
       ' check control-break key
       IF BreakIS THEN
          EXIT DO
       END IF
       Var2$ = KeyboardChar$ ' get character
       SELECT CASE Var2$
       CASE CHR$(0) ' extended character
          ' nul
       CASE CHR$(13) ' return
          EXIT DO
       CASE CHR$(8) ' backspace
          IF LEN(Var$) > 0 THEN
             Var$ = LEFT$(Var$, LEN(Var$) - 1)
             IF POS(0) > 1 THEN ' check cursor location
                LOCATE CSRLIN, POS(0) - 1, 0
                PRINT " ";
                LOCATE CSRLIN, POS(0) - 1, 1
             ELSE ' line-wrap
                LOCATE CSRLIN - 1, 80, 0
                PRINT " ";
                LOCATE CSRLIN - 1, 80, 1
             END IF
          END IF
       CASE ELSE ' store input
          PRINT Var2$;
          LOCATE , , 1
          Var$ = Var$ + Var2$
       END SELECT
    END IF
 LOOP

 ' return input line
 KeyboardLine$ = Var$
END FUNCTION

' reads character directly from keyboard.
FUNCTION KeyboardChar$
 ' removes keystroke from buffer
 InregsX.AX = &H0000
 CALL InterruptX(&H16, InregsX, OutregsX)
 ' store ascii character, ignoring extended keys
 Key1 = (OutregsX.AX AND &HFF)
 ' return character
 KeyboardChar$ = CHR$(Key1)
END FUNCTION

' checks keyboard buffer
FUNCTION KeyIS
 ' does not remove keystroke from buffer
 InregsX.AX = &H0100
 CALL InterruptX(&H16, InregsX, OutregsX)
 ' check zero flag
 IF (OutregsX.Flags AND &H40) = &H40 THEN
    KeyIS = False
 ELSE
    KeyIS = True
 END IF
END FUNCTION

' clears Control-Break flag
FUNCTION ClearBreak
 DEF SEG = &H40
 POKE &H71, &H0
 DEF SEG
 ClearBreak = True
END FUNCTION

' checks Control-Break flag
FUNCTION BreakIS
 ' store break flag between calls
 STATIC Var AS INTEGER
 IF KeyIS THEN
    ' control-break returns 0, 0.
    IF OutregsX.AX = 0 THEN
       Var = True
    END IF
 END IF
 IF Var THEN
    Continuous.Display = True
 END IF
 ' return break flag
 BreakIS = Var
END FUNCTION
