TinyBasicLike core module
(Last modified 29 October 2023)

My TinyBasicLike (TBL) project uses two main C files.  This page describes the core module, which is a full target-independent interpreter built from a rewrite of BleuLlama's rewrite of an earlier rewrite of Palo Alto Tiny Basic.  :-)

The core module described below contains the language interpreter, recursive-descent parser, support for generic Tiny Basic commands (such as LIST, NEW, and RUN), support for programming keywords (such as IF, GOTO, and FOR/NEXT) and support for 26 variables (A-Z).  Any aspect of the final program that depends on the target, such as amount of RAM, presence of a flash file system, clock speed, available ports, or console I/O, must be provided by a separate target-dependent module.  This isolation of the core functions makes porting a version of TBL to a new board pretty straightforward.

For general information on my TBL project, see my TinyBasicLike web page.

Core design

Following reset, the core module starts by calling two target-supplied routines, t_ColdBoot() and t_WarmBoot().  These routines allow the target module to set up the processor and perform any port or system initialization needed.  The core module then enters a mammoth routine called loop(), which does all the work of TBL.

The loop() routine repeatedly prompts the user for input, which is gathered via calls to a target-supplied routine called t_GetChar().  After a line of text has been collected, loop() steps through the text buffer, finding tokens by searching a long, space-delimited string of known keywords, then using a large switch statement to route control via goto statements to appropriate code within the loop() routine.

For example, the loop exec might scan the text buffer and locate the keyword LIST.  The exec translates this into the value KW_LIST and invokes the large switch statement at the exec's core.  This in turn causes a goto to a block of code dedicated to processing the LIST token; this code further parses the text buffer to collect any arguments, then executes the requested LIST command.  Once the LIST command has finished, control passes back to the top of the exec loop, which continues processing the text buffer until the buffer is exhausted and the user is prompted for more input.

The core includes a recursive-descent parser that analyzes the text buffer and performs any required mathematical or logical operations, yielding a single value to be used by subsequent routines.  The parser supports basic operators, such as addition, logical-OR, and shifting.  It also supports a set of functions, such as RND() and ADDR().  These functions are defined in a table of function keywords.  Adding a function to this table requires adding code to the parser specific to that new function.  As an example, the ADDR() function returns the address of a variable passed as an argument in the function call.  So:

    10  a = addr(b)

causes variable A to hold the address of variable B.  The code for parsing the argument and determining the value to return exists in the parser.

Your program exists in an area of memory known as program RAM.  The core assumes the target provides a second area of RAM that acts as a temporary file storage area; the number of files and total RAM in this area is target-dependent.  You can save your working program to this RAM buffer using the SAVE keyword; this saves your program as a named file.  You can load a program previously saved to this RAM buffer using the LOAD keyword.  However, this storage is in RAM; its contents will NOT survive a power-cycle or reset!

Saving your files permanently requires that the target support some kind of flash read/write system.  If such is supported, you can copy the entire RAM file storage area to flash using the FLSAVE command.  Note that support for this type of storage is target-dependent.

The core knows nothing about target-specific ports or I/O registers.  The link between the core's exec and the target's I/O is a target-based table called ports_tab.  This long, space-delimited string must be defined by the target and contains all I/O ports that the target wants to make available.  If the exec finds a match between the text buffer and an entry in the ports_tab string, the index of that match will be passed to the target in a call to t_ReadPort() or t_WritePort(), as appropriate.  The target can use the passed index to take the appropriate action.

Note that the target ports defined in ports_tab need not be true I/O ports.  These port entries may be virtual ports, keywords that prompt action by the target rather than addresses to read/modify.  For example, my STM32F407 target has "ports" named RED_LED and BLUE_LED.  Writing a 1 to RED_LED causes the target to light the on-board red LED; there is no actual STM32F407 port named RED_LED.

General notes

TBL's variables use the target's integer size; yes, you can have a 64-bit TinyBasicLike.  If you are unsure as to a variable's size, you can enter:

    ?x -1

to see the largest integer TBL can display.

The core supports 26 named variables, A through Z.  Additionally, variables X, Y, and Z are also dimensioned as 400-element vectors (X(0) through X(399)) and as 20x20 2D arrays (X(0,0) through X(19,19)).  X, X(0), and X(0,0) are all the same address.

The core communicates with the user through a set of target routines.  These typically support a UART or other serial connection, but could instead support, for example, a USB keyboard and HDMI display.

The core recognizes ctrl-C as a universal BREAK character.  For example, you can break out of an infinite loop by entering the BREAK character at any time.

The character ':' serves as a mid-line end-of-line character (MIDEOL).  For example:

        10 A = B + 3 : PRINT A, B

The core accepts string constants for use in PRINT statements, but does not support string variables or string operators.

The core accepts both upper- and lowercase characters on entry, but most characters will be converted to uppercase for internal storage and will appear as uppercase when you list out your program.  Exceptions to this conversion are string constants and text following a comment keyword.  For example, consider:

        10 print "Hello, world!"   :  \ This is a comment

If you LIST this line, it will appear as:

        10 PRINT "Hello, world!" :  \ This is a comment

True to its TinyBasic ancestors, the core has three generic error messages.  "What?" appears for syntax or some illegal operations.  "How?" appears for errors involving line numbers or bad IF or FOR constructs.  "Sorry." appears if you request an unsupported operation, such as trying to REBOOT from within a program.  Note that the original authors of the foundational code probably had strict rules for which of these error messages appeared under what circumstances.  I have tried to stay consistent with their design, but probably messed up a few times in selecting the proper response.  If your program throws one of these errors when running, the core will tell you the offending line number.

If the target supports storing files in flash, the core can execute a program on reset (called autorun).  The program to autorun must be named "autorun.bas" and must reside in the flash file system on reset.


Keywords

Here is a list of all keywords supported by the TBL core.  Most keywords can be entered directly at the command prompt, such as PRINT or LIST.  Other keywords are only legal within a program, such as FOR or NEXT.

Keyword
Function
Examples
Notes
LIST
List one or more lines of the current program
LIST
LIST 10 30

LOAD
Load a program from the target's RAM holding buffer, if supported.  Overwrites current program.
LOAD  "timer.bas"
Filename must be enclosed in double-quotes.  Warns about overwriting current program, if necessary.
MERGE
Loads a program from the target's RAM holding buffer, if supported.  Merges the loaded program with the current program.
MERGE  "lib1.bas"
Filename must be enclosed in double-quotes.  Overwrites any lines that have the same number.
NEW
Erases current program.
NEW
Warns about erasing current program, if necessary.
RUN
Executes current program.
RUN

SAVE
Writes one or more lines of current program to a RAM holding buffer on the target.
SAVE  "timer.bas"
SAVE  10 400 "lib1.bas"
Filename must be enclosed in double-quotes.
Does NOT save to target flash!  See FLSAVE.
FLSAVE
Writes target's RAM holding buffer to target's flash system, if supported.
FLSAVE
Writes entire RAM holding buffer, including all previous SAVEs, to target flash, if supported.
FOR
Begins FOR-NEXT loop.  Selects loop iterator and defines end limit.  Supports STEP for controlling change to iterator at loop's NEXT.
FOR N=1 TO 10
FOR N = 1 TO 20 STEP 2
Iterator increments by 1 as default.  Use STEP to select different iterator (can be negative).
NEXT
Ends FOR-NEXT loop.  Updates loop iterator, exits loop if iterator exceeds end limit.
50 NEXT N

LET
Assigns value to a variable.
30 LET A = 4
Obsolete but supported; can be omitted:
A = 4
IF
Tests a value or condition.  If condition is TRUE, executes remainder of line.
30 IF A > 5  K = 1
30 IF A > 5  GOTO 200


GOTO
Transfers to line number following GOTO.  Supports variables and calculations for line numbers.
70 GOTO  250
70 GOTO  A+30
Can be entered at the command prompt to start program execution at a specific line.
GOSUB
Calls a subroutine at the line number following GOSUB.  Supports variables and calculations for line numbers.
90 GOSUB  250
90 GOSUB  A+30

Does not support mid-EOL (:) characters in GOSUB statement.
RETURN
Returns control to the line following the line containing the original GOSUB.
200 RETURN

REM
Marks remainder of line as a comment.
100 REM  This is a test.
Must be first token in a statement or following ':' MIDEOL.  Preserves case of comment text.
\
One-char REM operator.  Marks remainder of line as a comment.
100 \  This is a test.
Must be first token in a statement or following ':' MIDEOL.  Preserves case of comment text.
INPUT
Accepts an integer value from the console.
30 INPUT  A
Prompts the user with the variable to be modified and a question mark.

A ?

Blocks until user enters a value.  Blank lines are ignored and the user is prompted again.

Precede hex value with 0x:  A ? 0x30
Enter an ASCII character by enclosing it in single-quotes:  A ? 'v'
PRINTX
Print integer as a hex value.
PRINTX  A
In example, if A is 255, prints FF.
?X
Print integer as a hex value.
?X  A
In example, if A is 255, prints FF.
PRINTA
Print integer as an ASCII character.
PRINTA  48
Example prints a '0'.
?A
Print integer as an ASCII character. ?A  48 Example prints a '0'.
PRINT
Print integers, variables, or fixed strings.  Supports fields (comma) and concatenated text (semicolon).
PRINT  A, B
PRINT  "A=";A
PRINT  A+2*B;
First example prints values of A and B separated by spaces, followed by CR/LF.

If A = 47 in second example, prints:  A=47, followed by CR/LF.

If A = 47 and B = 3 in third example, prints:  53  and CR/LF is suppressed.
?
Print integers, variables, or fixed strings.  Supports fields (comma) and concatenated text (semicolon). ?  A, B
?  "A=";A
?  A+2*B;
First example prints values of A and B separated by spaces, followed by CR/LF.

If A = 47 in second example, prints:  A=47, followed by CR/LF.

If A = 47 and B = 3 in third example, prints:  53  and CR/LF is suppressed.
POKE
Write a value to a memory location.
-----
In original TinyBasic but not implemented in this version.
STOP
Halts program execution and returns to command prompt.
100 STOP
Synonym for END.
END
Halts program execution and returns to command prompt. 100 END
Synonym for STOP.
BYE
Exits the TBL program.
BYE
User will be prompted to save any changes to current program before exiting TBL.  BYE will not exit TBL unless changes are saved or deleted.

Exiting TBL from within a program is not supported; BYE must be entered at the command prompt.

Note that on most embedded systems, there is nowhere to go after exiting main(); control usually loops in place waiting for a reset.  Actual behavior of BYE is target-dependent.
REBOOT
Exits the TBL program, then forces a hardware reset of the system, if supported by the target.
REBOOT
User will be prompted to save any changes to current program before rebooting.  REBOOT will not reset target unless changes are saved or deleted.

Rebooting from within a program is not supported; REBOOT must be entered at the command prompt.

Target must provide hardware reset.
FILES
Lists all files currently in RAM file buffer.
FILES
Lists only files in the RAM buffer.  Does not list files in the target's flash memory, even if target supports flash file system.
DELETE
Deletes a file from the RAM file buffer.
DELETE "file.bas"
Deletes a file from the RAM buffer.  Does not delete the matching file from the target's flash memory.

If the target supports a flash file system, first DELETE the file from the RAM buffer, then use FLSAVE to write the updated RAM buffer to the flash file system.
MEM
Display amount of memory available for program storage.
MEM
Memory available is target-dependent.
AWRITE
Read an analog input.
-----
In original TinyBasic but not implemented in this version.
DWRITE
Write a digital output.
-----
In original TinyBasic but not implemented in this version.
RSEED
Sets seed for random number generator (RNG).
RSEED 99
Using a value of 0 will reset the RNG seed to its initial (power-on) value.
CHAIN

-----
In original TinyBasic but not implemented in this version.
WORDS
Shows a list of all keywords, logical operators, mathematical operators, comparison operators, and all target-defined addresses and ports.
WORDS

TIMERRATE
Sets the tic rate for all down-counting timers (DCTs) value is tic rate in microseconds.
TIMERRATE  1000
Variables in the DCT list will automatically decrement their value by one on each DCT tic until the value reaches 0.

By default, target should have already set the DCT tic rate to 1 millisecond (value of 1000).
ADDTIMER
Adds a variable to the list of DCTs
ADDTIMER  T
Number of supported DCTs is target-dependent.  Adding a variable that is already in the list is ignored.
DELTIMER
Removes a variable from the list of DCTs.
DELTIMER  T
Deleting a variable that is not in the list is ignored.
TONE
Generates a tone on the target-defined output pin, if supported.  First argument is frequency in Hz.  Second argument is duration in msecs.
TONE  440, 500
This command starts a tone.  Use TONE  0 to shut off the tone.

Example sounds a 440 Hz tone for 500 msecs.
LOCK?
Reports state of editing lock on current program.
LOCK?

LOCK
Prevents editing all or a section of the current program.  Lock range is specified as line numbers.
LOCK
LOCK 100,200
LOCK 100,
LOCK ,999
First example locks entire program.
Second example locks all lines from 100 to 200, inclusive.
Third example locks all lines from 100 to last legal line number (65534).
Fourth example locks all lines from 1 to 999, inclusive.
UNLOCK
Removes editing lock of current program.
UNLOCK

OUTCHAR
Sends a value to the console as an ASCII character.
OUTCHAR 48
Example sends the character '0' to the console.
BEEP
If target supports tone generation, sounds a predefined beep on the target's speaker.
BEEP

TEST
Invokes a target-dependent test routine.
TEST
Functionality depends on target code.  This is usually used to trigger some test code during development.


Functions

Here is a list of all functions supported by the TBL core.


Name
Function
Example
Notes
ABS
Returns absolute value of a variable or expression.
? ABS(10)
? ABS(B)
? ABS(-7)
First example prints 10.
Second example prints the absolute value of variable B.
Third example prints 7.
ADDR
Returns the address of a variable.
? ADDR(B)
? ADDR(X(10))
? ADDR(Z(5,2))
First example prints the address of the variable B.
Second example prints address of element 10 (from 0) of array X.
Third example prints the address of element (5,2) (from 0,0) of array Z.
AREAD
Returns value from analog input.
-----
In original TinyBasic but not implemented in this version.
DREAD
Returns state of a digital input.
-----
In original TinyBasic but not implemented in this version.
PEEK
Returns contents of a memory address.
-----
In original TinyBasic but not implemented in this version.
RND
Returns a random number in range of 0 to (argument-1).
? RND(10)
Prints a random number from 0 to 9.

Refer to RSEED keyword for modifying the RNG seed.
TONE?
Returns state of tone generator, if supported.
? TONE?()
Returns 1 if tone is active or 0 if tone is not active.


Numeric operators

Here is a list of all numeric operators supported by the TBL core.

Operator
Function
Example
Notes
=
Assignment operator; stores a value into a named variable.  Supports storing a sequence of values into a sequence of array elements.
10 A = 5
10 X(5) = B
10 X(0) = 1,2,3,4
10 X(5) = 1,,4
First example stores 5 in A.
Second example stores the value of B in X(5).
Third example stores the value 1 in X(0), 2 in X(1), 3 in X(2), and 4 in X(3).
Fourth example stores the value 1 in X(5) and the value 4 in X(8); the contents of X(6) and X(7) are not changed.
+
Addition
10 A = 7 + B

-
Negation
10 A = -B
Unary negation
-
Subtraction
10 A = B - 22

*
Multiplcation
10 A = B * K

/
Division
10 A = 8 / R

%
Modulus
10 A = B % 4
Modulus of 0 is illegal.
MOD
Modulus
10 A = B MOD 4
Modulus of 0 is illegal.



Relational operators

Here is a list of all relational operators supported by the TBL core.

Operator
Function
Example
Notes
>=
Greater than or equal
10 IF A >= 0 GOTO 20

<>
Not equal
10 IF A <> B GOTO 20

!=
Not equal
10 IF A != B GOTO 20

<=
Less than or equal
10 IF A <= B GOTO 20

>
Greater than
10 IF A > B GOTO 20

=
Equal
10 IF A = B GOTO 20

<
Less than
10 IF A < B GOTO 20


Logical operators

Here is a list of all logical operators supported by the TBL core.

Operator
Function
Example
Notes
AND
Returns logical-AND of two values
? 4 AND 0x55
Prints 0
NOT
Returns the logical negation of a value
? NOT B
Prints 1 (B is 0 by default; TRUE is returned as 1)
OR
Returns the logical-OR of two values
? 4 OR 0x55
Returns 85
XOR
Returns the logical exclusive-OR of two values
? 4 XOR 0x55
Returns 81


Shift operators

Here is a list of all bit-wise shift operators supported by the TBL core.

Operator
Function
Example
Notes
<<
Left bit-wise shift
?x 0x10 << 2
Prints 40
>>
Right bit-wise shift
?x 0x10 >> 2
Prints 4




That's a wrap

This has been the most fun I've had in embedded development in quite a while.  It has been a pleasure to work with such well-designed code, and I appreciate the effort that the previous contributors put into their work.  I also appreciate their making this code available for people like me to play with.

I have left their licensing and acknowledgements intact in my TinyBasicLike.c source file, while adding my own list of contributions.  If you use or modify any code in either of my C source files, please retain all original licensing and acknowledgements.

Note that this code has been lightly tested and I guarantee you will find bugs.  You would be daft to rely on this code for anything serious.  You've been warned; don't blame me if things go belly-up using this code or any code derived from it.  I'm releasing it so you can tinker and have fun, just like I did.

Here's the zip file.

Please drop me an email with any comments or suggestions.


Home