This is a feature update to Carl's port of Tiny Basic. There is also a version for Liberty Basic which I posted in the Liberty Basic wikispace.

The main purpose of this release was to add enough features to Tiny Basic to run the Sieve benchmark. Here are the new features:

  1. Added seconds and ms functions for benchmarking purposes. In Run Basic, however, if you use the seconds function, it will still display milliseconds. Try "print seconds".
  2. Added one array to the interpreter environment. In addition to the numeric variables "a" through "z", there is also the numeric array "a(1)" through "a(7001). There is an existing bug in the range checking which will be fixed later. Range checking works for assignment, but not for printing.
  3. For LB4 only, LOAD, SAVE and AutoRun are fixed. I also added KILL and DIR commands for file management. For the Run Basic release, this code is just commented out.

I will post results of the Sieve benchmark test in the Run Basic forum and on my blog. Needless to say that Tiny Basic is a lot slower than Run Basic or Liberty Basic. Regardless, it has a lot of potential as an embedded interpreter in a Run Basic or Liberty Basic application. Best of all, it's fun to play with!

Enjoy!

David den Haring.
Simple Computing Blog

    'TinyBasic.bas (for Run Basic)
 
    ' tinyBasic v1.1 - A very small and simple BASIC interpreter written in
    ' Run BASIC.  WARNING:  For uber geeks only.  :-p
 
    '   Copyleft 2005 by Laurent DUVEAU
    '   http://www.aldweb.com/
    ' an iziBasic sample program
 
    ' Ported to Run BASIC by Carl Gundel and Scott McLaughlin
    '   http://www.libertybasic.com
    '   http://www.runbasic.com
 
    ' Version 1.1 (Will run on Liberty BASIC 4.03 and Run BASIC build 2.27)
    ' by David den Haring (8/15/2007)
    ' The purpose of this release is to add enough features to Tiny Basic in order to run the Sieve benchmark.
    ' Added a fixed array accessible from the interpreter (i.e. a() )
    ' Added the functions "seconds" and "ms" or "milliseconds" for benchmarking purposes
    ' (LB4) Fixed LOAD and SAVE commands because it's tiring to have to type in the Sieve program by hand all the time. I'm having flashbacks to my VIC-20 before the Datasette (i.e. cassette tape storage)
    ' (LB4) Added DIR and KILL commands for file management. DIR has no parameters. KILL works like LOAD and SAVE.
    ' (LB4) Fixed AutoRun feature. If file tinyBas0 exists, it will be loaded and run automatically when the interpreter starts.
    ' A          temp
    ' B          temp
    ' C          character index in line
    ' E          line number for error msg
    ' I          temp (loops)
    ' L          number of lines. Index into A$ array
    ' N          number
    ' S          expression stack index
    ' T          temp
    ' V          variable index
 
    ' A$         temp
    ' B$         temp
    ' C$         character
    ' D$         single statement
    ' E$         error message
    ' G$         string code (")
    ' H$         HALT code (Line Feed)
    ' I$-R$      Help
    ' Z$=A$(26)  statement input
 
    DIM A$(125) ' [27-125] = 99 program lines
    DIM A(82)   ' [27-52] = 26 variables
                ' [53-82] = 30 items math stack
 
    ArraySize = 7001 'Size of user array
    DIM Array(ArraySize) 'Fixed-sized array available to user program
    DIM info$(10, 10)
 
    A$(9)="BYE, CLEAR, CLS, END"
    A$(10)="HELP, MEM, NEW, RUN, DIR"
    A$(11)="GOTO | LOAD | SAVE | KILL <exp>"
    A$(12)="IF <exp> THEN <statement>"
    A$(13)="INPUT <var>"
    A$(14)="[LET] <var>=<exp>"
    A$(15)="LIST [<exp>|PAUSE]"
    A$(16)="PRINT <exp|str>[,<exp|str>][;]"
    A$(17)="REM <any>"
    A$(18)="Numeric variables available from 'a' to 'z'"
    A$(19)="One array variable available -- a(1) to a("; STR$(ArraySize); ")"
    A$(20)="No file access for Run Basic yet!"
    A$(21)="Functions available: seconds, milliseconds or ms"
    TRUE = 1
    FALSE = 0
    arrayFlag = FALSE 'this flag indicates whether a variable is an array reference or not
    simpleFlag = FALSE 'this flag indicates a simple variable on the left side of an expression
 
    'BEGIN
    G$=CHR$(34) : H$=CHR$(10) 'double quote and linefeed, respectively
 
    'if the file "tinyBas0" exists, load and run it automatically
    'B = fileExists(DefaultDir$, "tinyBas0")
    'IF B = TRUE THEN
    '    Z$="LOAD 0: RUN"
    '    GOTO [AutoRun]
    'END IF
 
    PRINT "Tiny BASIC v1.1 for Run BASIC and Liberty BASIC"
    PRINT "Type HELP for commands."
[Ready] 'display any errors and show ready prompt
    simpleFlag = FALSE
    arrayFlag = FALSE
    IF E$<>"" THEN 'print the error
        IF E>0 THEN
            E$="#Err in "+STR$(E)+": "+E$
        ELSE
            E$="#Err: "+E$
        END IF
        PRINT E$ : E$=""
    END IF
    PRINT "Ready"
[Input] 'get user input
    LINE INPUT Z$
[AutoRun] 'parse the user input
    A$(26)=Z$
    L=26 : C=1 : GOSUB [GetNumber] : E=N
    IF N=0 THEN 'no line number
        IF C$="" THEN [Ready] 'user just hit the return key
        GOTO [NextStatement] 'get the next token
    ELSE 'line number
        GOSUB [EnterLine] 'enter line of code into program
        IF E$<>"" THEN [Ready] 'branch and display error
        GOTO [Input] 'get user input
    END IF
[Exec] 'get the next number
    GOSUB [GetNumber] : E=N
[NextStatement]
    'This commented-out code is for stopping the execution of a running program
    'A$=INKEY$
    'IF A$=H$ THEN
    ' A$="Break in "+STR$(E,0)
    ' PRINT A$ : GOTO [Ready]
    'END IF
    GOSUB [GetLabel]
    IF E$<>"" THEN [Ready] 'branch and display error
    SELECT CASE D$
        CASE "if"
        GOSUB [GetExpression]
        IF E$<>"" THEN [Ready]
        IF N<1 THEN
            B$=A$(L) : C=LEN(B$)+1
            GOTO [FinishStatement]
        END IF
        GOSUB [GetLabel]
        IF E$<>"" THEN [Ready]
        IF D$<>"then" THEN
            E$="'THEN' expected"
            GOTO [Ready]
        END IF
        GOTO [NextStatement]
        CASE "rem"
        B$=A$(L) : C=LEN(B$)+1
        GOTO [FinishStatement]
        CASE "input"
        GOSUB [GetVar]
        IF E$<>"" THEN [Ready]
        INPUT N
        IF arrayFlag THEN
            Array(V)=N 'set the value of the array
        ELSE
            A(V)=N 'set the value of simple variable
        END IF
        GOTO [FinishStatement]
        CASE "print"
[Print]
        GOSUB [SkipSpace]
        GOSUB [GetChar]
        IF C$=G$ THEN 'print quoted string
            B$=""
[NextChar]
            C = C + 1 : C$=MID$(A$,C,1)
            IF C$="" THEN
                E$="Unterminated string"
                GOTO [Ready]
            ELSE
                IF C$<>G$ THEN
                    B$=B$+C$
                    GOTO [NextChar]
                END IF
            END IF
            C = C + 1 : C$=MID$(A$,C,1)
            IF C$=G$ THEN
                B$=B$+C$
                GOTO [NextChar]
            END IF
            PRINT B$;
        ELSE 'print expression
            GOSUB [GetExpression]
            IF E$<>"" THEN [Ready]
            B=0
            IF B=N THEN 'if variable is 0, then print a trailing "*"
                PRINT N;"*";
            ELSE
                PRINT N;
            END IF
        END IF
        GOSUB [SkipSpace]
        GOSUB [GetChar]
        IF C$="," THEN C = C + 1 : GOTO [Print]
        GOSUB [SkipSpace]
        GOSUB [GetChar]
        IF C$<>";" THEN
            PRINT
        ELSE
            C = C + 1
        END IF
        GOTO [FinishStatement]
        CASE "clear"
        FOR I=27 TO 52 : A(I)=0 : NEXT I 'clear variables a -- z
        FOR I = 0 TO ArraySize : Array(I) = 0: NEXT I 'clear the array -- can't REDIM becuase Run Basic doesn't support it yet
        GOTO [FinishStatement]
        CASE "run"
        FOR I=27 TO 52 : A(I)=0 : NEXT I
        L=27 : C=1
        GOTO [FinishStatement2]
        CASE "goto"
        GOSUB [GetExpression]
        IF E$<>"" THEN [Ready]
        IF E>=N THEN L=27
        C=1 : T=N
[NextGoto]
        IF L=126 THEN
            E$="Line not found"
            GOTO [Ready]
        END IF
        GOSUB [GetNumber]
        IF N=T THEN E=N : GOTO [NextStatement]
        L = L + 1 : C=1
        GOTO [NextGoto]
        CASE "new"
        FOR I=27 TO 125 : A$(I)="" : NEXT I 'clear the program
        FOR I=27 TO 52 : A(I)=0 : NEXT I 'clear variables a -- z
        FOR I = 0 TO ArraySize : Array(I) = 0: NEXT I 'clear the array -- can't REDIM becuase Run Basic doesn't support it yet
        IF E=0 THEN [FinishStatement]
        GOTO [Ready]
        CASE "cls"
        CLS : GOTO [FinishStatement]
        CASE "help"
        FOR I=9 TO 21
            B$=A$(I) : PRINT B$
        NEXT I
        GOTO [FinishStatement]
        CASE "mem"
        B=126
        FOR I=27 TO 125
            diffI = 152 - I  'Cheating here
            B$=A$(diffI) : IF B$="" THEN B=diffI
        NEXT I
        B=126-B : PRINT B;"*";
        PRINT " lines free"
        GOTO [FinishStatement]
        CASE "end"
        GOTO [Ready]
        CASE "bye"
        GOTO [ExitTinyBAS]
        CASE "list"
        GOSUB [GetNumber] : T=N : A=L : I=C
        IF T=0 THEN
            GOSUB [GetLabel]
            IF E$="" AND D$="pause" THEN I=C
            E$=""
        END IF
        FOR L=27 TO 125
            C=1 : GOSUB [GetNumber]
            B=(T=0) OR (N=T)
            IF B=TRUE THEN
                IF A$<>"" THEN
                    PRINT A$
                    IF D$="pause" THEN
                        B = (L-26) mod 10
                        IF B=0 THEN PRINT "Pause..."; : INPUT AAA$
                    END IF
                END IF
            END IF
        NEXT L
        L=A : C=I
        GOTO [FinishStatement]
        CASE "save" 'save a program file
        PRINT "Liberty Basic 4 only"
        GOTO [Ready]
        'GOSUB [GetExpression]
        'IF E$<>"" THEN [Ready]
        'A$="tinyBas"+STR$(N) : A=FALSE
        'OPEN A$ FOR OUTPUT AS #1
        'FOR I=27 TO 125
        '    B$=A$(I)
        '    IF B$<>"" THEN PRINT #1,B$ : A=TRUE
        'NEXT I
        'CLOSE #1
        'IF A=FALSE THEN KILL A$
        'GOTO [FinishStatement]
        CASE "load" 'load a program file, but does not account for a program on disk that's too large!
        PRINT "Liberty Basic 4 only"
        GOTO [Ready]
        'GOSUB [GetExpression]
        'IF E$<>"" THEN GOTO [Ready]
        'A$="tinyBas"+STR$(N)
        'B=fileExists(DefaultDir$, A$)
        'IF B=FALSE THEN
        '    E$="File "+A$+" not found"
        '    GOTO [Ready]
        'END IF
        'OPEN A$ FOR INPUT AS #1
        'B=FALSE : I=27
        'B=EOF(#1)
        'WHILE B <> (-1)
        '    LINE INPUT #1,B$ : A$(I)=B$ : I=I+1
        '    B=EOF(#1)
        'WEND
        'CLOSE #1
        'WHILE I<=125
        '    A$(I)="" : I=I+1
        'WEND
        'IF E=0 THEN [FinishStatement]
        'GOTO [Ready]
        CASE "kill" 'delete a program file
        PRINT "Liberty Basic 4 only"
        GOTO [Ready]
        'GOSUB [GetExpression]
        'IF E$<>"" THEN [Ready]
        'A$="tinyBas"+STR$(N)
        'Kill A$
        'GOTO [FinishStatement]
        CASE "dir" 'list all program files
        PRINT "Liberty Basic 4 only"
        GOTO [Ready]
        'FILES DefaultDir$, "tinyBas*", info$()
        'IF VAL(info$(0,0)) > 0 THEN 'list the files
        '    FOR I = 1 to VAL(info$(0,0))
        '        print info$(I, 0), info$(I, 2) 'print filename and date/times stamp
        '    NEXT I
        'ELSE
        '    PRINT "No files!"
        'END IF
        'GOTO [Ready]
        CASE "let"
        GOSUB [GetLabel]
        IF E$<>"" THEN [Ready]
    END SELECT
    'it must be an expression
    GOSUB [ReturnVar]
    IF E$<>"" THEN [Ready]
    GOSUB [SkipSpace]
    GOSUB [GetChar]
    IF C$<>"=" THEN
        E$="'=' expected"
        GOTO [Ready]
    END IF
    C = C + 1 : T=V
    IF NOT(arrayFlag) THEN simpleFlag = TRUE 'left side is a simple variable
    GOSUB [GetExpression] 'get the right side which could be an expression
    IF E$<>"" THEN [Ready]
    IF simpleFlag THEN
        A(T)=N 'set the value of simple variable
    ELSE
        Array(T)=N 'set the value of the array
    END IF
    arrayFlag = FALSE : simpleFlag = FALSE
[FinishStatement]
    GOSUB [SkipSpace]
    GOSUB [GetChar]
    IF C$=":" THEN
        C = C + 1 : GOTO [NextStatement]
    ELSE
        IF C$<>"" THEN
            E$="End of statement expected"
            GOTO [Ready]
        END IF
    END IF
    IF L=26 THEN [Ready]
    L = L + 1 : C=1
    IF L=126 THEN
        E$="Program Overflow"
        GOTO [Ready]
    END IF
[FinishStatement2]
    B$=A$(L)
    IF B$="" THEN [Ready]
    GOTO [Exec]
[ExitTinyBAS] 'end the program
    PRINT "Thanks for using Tiny BASIC."
    END
 
[EnterLine]
    L=27 : C=1 : T=N
[NextLine]
    GOSUB [GetNumber]
    B=(N<T) AND (N<>0) AND (L<126)
    IF B=TRUE THEN
        L = L + 1 : C=1 : GOTO [NextLine]
    END IF
    IF L=126 THEN
        E$="Program Overflow"
        GOTO [EndEnterLine]
    END IF
    IF T<>N THEN
        FOR I=L to 125
            diffI = (125+L)-I
            B=diffI-1 : A$(diffI)=A$(B)
        NEXT I
    END IF
    A$(L)=Z$
    GOSUB [SkipSpace]
    IF C$="" THEN
        FOR I=L TO 124
            B=I+1 : A$(I)=A$(B)
        NEXT I
    END IF
[EndEnterLine]
    RETURN
 
[GetExpression] 'numeric expressions only
    A(53)=0 : S=53
    GOSUB [BoolExpression]
    N=A(S) : GOTO [EndExpression]
[BoolExpression]
    GOSUB [AddExpression]
    GOSUB [SkipSpace]
    GOSUB [GetChar]
[NextBool]
    SELECT CASE C$
        CASE "="
        C = C + 1 : GOSUB [AddExpression]
        B=S-1 : A(B)=A(B)=A(S) : S = S - 1
        CASE ">"
        C = C + 1 : GOSUB [GetChar]
        IF C$="=" THEN
            C = C + 1 : GOSUB [AddExpression]
            B=S-1 : A(B)=A(B)>=A(S) : S = S - 1
        ELSE
            GOSUB [AddExpression]
            B=S-1 : A(B)=A(B)>A(S) : S = S - 1
        END IF
        CASE "<"
        C = C + 1 : GOSUB [GetChar]
        SELECT CASE C$
            CASE "="
            C = C + 1 : GOSUB [AddExpression]
            B=S-1 : A(B)=A(B)<=A(S) : S = S - 1
            CASE ">"
            C = C + 1 : GOSUB [AddExpression]
            B=S-1 : A(B)=A(B)<>A(S) : S = S - 1
            CASE ELSE
            GOSUB [AddExpression]
            B=S-1 : A(B)=A(B)<A(S) : S = S - 1
        END SELECT
    END SELECT
    GOSUB [SkipSpace]
    GOSUB [GetChar]
    B=ASC(C$) : B=(B>=60) AND (B<=62)
    IF B=TRUE THEN [NextBool]
    GOTO [EndExpression]
[AddExpression]
    GOSUB [MulExpression]
    GOSUB [SkipSpace]
    GOSUB [GetChar]
[NextAdd]
    SELECT CASE C$
        CASE "+"
        C = C + 1 : GOSUB [MulExpression]
        B=S-1 : A(B)=A(B)+A(S) : S = S - 1
        CASE "-"
        C = C + 1 : GOSUB [MulExpression]
        B=S-1 : A(B)=A(B)-A(S) : S = S - 1
    END SELECT
    GOSUB [SkipSpace]
    GOSUB [GetChar]
    B=ASC(C$) : B=(B=43) OR (B=45)
    IF B=TRUE THEN [NextAdd]
    GOTO [EndExpression]
[MulExpression]
    GOSUB [GroupExpression]
    GOSUB [SkipSpace]
    GOSUB [GetChar]
[NextMul]
    SELECT CASE C$
        CASE "*"
        C = C + 1 : GOSUB [GroupExpression]
        B=S-1 : A(B)=A(B)*A(S) : S = S - 1
        CASE "/"
        C = C + 1 : GOSUB [GroupExpression]
        B=A(S)
        IF B=0 THEN
            IF E$="" THEN E$="Division by zero"
            S = S - 1 : GOTO [EndExpression]
        ELSE
            B=S-1 : A(B)=A(B)/A(S) : S = S - 1
        END IF
        CASE " "
        C = C + 1 : GOSUB [GroupExpression]
        B=A(S)
        IF B=0 THEN
            IF E$="" THEN E$="Division by zero"
            S = S - 1 : GOTO [EndExpression]
        ELSE
            B=S-1 : A(B)=A(B) mod A(S) : S = S - 1
        END IF
    END SELECT
    GOSUB [SkipSpace]
    GOSUB [GetChar]
    B=ASC(C$)
    B=(B=42) OR (B=47)  OR (B=92)
    IF B=TRUE THEN [NextMul]
    GOTO [EndExpression]
[GroupExpression]
    GOSUB [SkipSpace]
    GOSUB [GetChar]
    SELECT CASE C$
        CASE "("
        C = C + 1 : GOSUB [BoolExpression]
        GOSUB [SkipSpace]
        GOSUB [GetChar]
        IF C$<>")" THEN
            IF E$="" THEN E$="Missing ')'"
            GOTO [EndExpression]
        END IF
        C = C + 1
        CASE ""
        IF E$="" THEN E$="Invalid Factor"
        CASE ELSE
        B=ASC(C$) : B=(B<48) OR (B>57)
        IF B=FALSE THEN
            GOSUB [GetNumber]
            S = S + 1 : A(S)=N
        ELSE
            GOSUB [GetLabel]
            IF E$<>"" THEN [EndExpression]
            B=LEN(D$)
            IF B = 1 OR (LEFT$(D$, 2) = "a(" AND RIGHT$(D$,1) = ")") THEN
                GOSUB [ReturnVar]
                S = S + 1
                IF arrayFlag THEN
                    A(S)=Array(V)
                ELSE
                    A(S)=A(V)
                END IF
            ELSE
                SELECT CASE D$
                    CASE "milliseconds", "ms"
                    S = S + 1 : A(S)=time$("ms") 'return number of milliseconds since midnight
                    CASE "seconds"
                    S = S + 1 : A(S)=time$("seconds") 'return number of seconds since midnight
                    CASE ELSE
                    IF E$="" THEN E$="Function expected"
                END SELECT
            END IF
        END IF
    END SELECT
[EndExpression]
    RETURN
 
[GetNumber] 'get the line number if it exists
    GOSUB [SkipSpace] 'skip leading spaces
    B$=""
[NextNumber]
    GOSUB [GetChar] 'get the next character
    IF C$="" THEN [GetNumberExit]
    B=ASC(C$) 'convert to ASCII character code
    B=((B<48) OR (B>57)) AND (B<>46)
    IF B=TRUE THEN [GetNumberExit] 'Abort if not a digit or decimal point
    B$=B$+C$ : C = C + 1 : GOTO [NextNumber] 'Build the number
[GetNumberExit]
    N=VAL(B$) 'convert the assembled string to a number
    RETURN
 
[GetVar]
    GOSUB [GetLabel]
    IF E$<>"" THEN [GetVarExit]
[ReturnVar]
    arrayFlag = FALSE
    IF LEFT$(D$, 2) = "a(" and RIGHT$(D$,1) = ")" THEN 'possible array variable (i.e. a() )
        GOSUB [ExtractArrayIndex]
    ELSE 'simple variable (i.e. a-z)
        B=ASC(D$) : A=LEN(D$)
        A=(A<>1) OR (B<97) OR (B>122)
        IF A=FALSE THEN
            V=B-70
        ELSE
            IF E$="" THEN E$="Variable expected"
        END IF
    END IF
[GetVarExit]
    RETURN
 
[GetLabel]
    GOSUB [SkipSpace]
    GOSUB [GetChar]
    D$="" 'single statement
    IF C$="" THEN [GetLabelError]
    B=ASC(C$) : B=((B>=97) AND (B<=122)) OR (B=40) OR (B=41) OR ((B>=48) AND (B<=57)) 'letters, parentheses and digits are OK
    IF B=FALSE THEN [GetLabelError]
[GetNextLabel]
    D$=D$+C$ : C = C + 1
    GOSUB [GetChar]
    IF C$="" THEN [GetLabelExit]
    B=ASC(C$) : B=((B>=97) AND (B<=122)) OR (B=40) OR (B=41) OR ((B>=48) AND (B<=57)) 'letters, parentheses and digits are OK
    IF B=TRUE THEN [GetNextLabel]
    GOTO [GetLabelExit]
[GetLabelError]
    IF E$="" THEN E$="Invalid label"
[GetLabelExit]
    RETURN
 
[SkipSpace] 'skip leading spaces in input
    GOSUB [GetChar]
    IF C$=" " THEN C = C + 1 : GOTO [SkipSpace] 'skip all leading spaces
    RETURN
 
[GetChar] 'get the next character and change it to lowercase
    A$=A$(L) 'efficient? This assignment is made before retrieving each character.
    C$=MID$(A$,C,1) : C$=LOWER$(C$)
    RETURN
 
[ExtractArrayIndex] 'get the array index
    'the index can be a number or a simple variable
    A$ = MID$(D$, 3, (LEN(D$) - 3))
    A = VAL(A$)
    IF A = 0 THEN 'index is 0 or it's a variable
        B = ASC(LOWER$(A$))
        IF (B >= 97) AND (B <= 122) THEN 'it's a variable
            arrayFlag = TRUE
            V = A(B-70) 'get the index value
            IF V = 0 THEN
                IF E$="" THEN E$="Array index should be between 1 and "; STR$(ArraySize)
            END IF
        ELSE
            IF E$="" THEN E$="Array index should be between 1 and "; STR$(ArraySize)
        END IF
    ELSE
        IF A > ArraySize THEN
            IF E$="" THEN E$="Array index should be between 1 and "; STR$(ArraySize)
        ELSE
            arrayFlag = TRUE
            V = A 'must use array flag to determine where to use the V index
        END IF
    END IF
    RETURN
 
'function fileExists(path$, filename$)
'  'this function is from the LB4 help file
'  'dimension the array info$() at the beginning of your program
'  files path$, filename$, info$()
'  fileExists = val(info$(0, 0))  'non zero is true
'end function