14 KiB
Luxferre's Truly Tiny BASIC (LTTB) implementation notes
This document serves as the definite reference for everyone intending to create a new and fully compatible LTTB language interpreter implementation. It describes all the algorithms and data structures necessary to recreate a fully functional LTTB interpreter from scratch.
Data structures required for LTTB implementations
- Integer numbers (signed)
- Strings
- Lists (arrays that can store numbers and strings)
- Associative arrays (key-value objects)
- Stacks (or any other linear structure that can emulate them, including lists)
Globals
An LTTB interpreter requires the following globals:
- Program working memory
PROGMEM(associative array) - Variable storage area
VARS(associative array) - Subroutine call stack
CALLSTACK - Instruction pointer
IP(integer)
Initial values:
PROGMEM= emptyVARS["A"]..VARS["Z"] = 0CALLSTACK= emptyIP= 0
Overall interpreter session flow
- Print the welcome banner.
- If the implementation is CLI-based and there is a file name passed into the command-line argument, run the
LOADroutine on this file. - Output the prompt
>and expect user input on the same line. - Read the user input, trim leading/trailing whitespace and uppercase it. Save it as
CMD. - If
CMDequals exactly toBYE, print the "Bye!" message and exit the interpreter. - If
CMDis exactly one of theseRUN LIST NEW CLEAR LOAD SAVE, execute the corresponding interactive routine and go to step 3. ForNEWandCLEAR, the interactive routine must be the same. - Execute program line input routine
PROGINPUTwithCMDas a parameter and then go to step 3.
Program line input routine (PROGINPUT)
Accepts the statement string STMT as the input line argument.
- Trim leading/trailing whitespace from
STMT. - Return from the routine if the
STMTis an empty string. - Initialize line number variable
LNOas 0. - Iterate through characters of
STMT. If the current characterCis a digit, setLNO = LNO * 10 + C. Repeat this step as long asCis a digit or a whitespace character. - Starting from the first non-digit and non-whitespace character, save the rest of the
STMTstring as the statement bodyBODY. - If
LNO > 0, setPROGMEM[LNO] = BODY. Otherwise, run statement execution routinePROGEXECwithBODYas a parameter.
Program statement execution routine (PROGEXEC)
Accepts the statement string STMT as the input line argument.
- Read the keyword identifier
KWfrom the first two non-whitespace characters inSTMT. - If the second character in
KWis equal to=, go to step 3, otherwise go to step 5. - Set
KWto the valueLE. - Prepend the value
LE(two charactersLEand then a whitespace) to the beginning ofSTMT. - If
KWis exactly one of theseEN GO IF IN LE PR RE, go to the next step, otherwise return. - Find the position of the first whitespace in
STMT, then copy everything from this position to the end ofSTMTintoBODY. - Trim leading/trailing whitespace from
BODY. - If
KWis equal toGOand the third non-whitespace character ofSTMTis equal toS, execute core language routine namedGOSwithBODYas a parameter and return. - Execute core language routine with the name equal to the value of
KWwithBODYas a parameter and return.
Number normalization function (NORM)
Accepts any number-like value V as the argument.
- If
Vis an empty string, return 0 immediately. - Convert
Vinto an integer (by truncating the decimal part if necessary). - If
Vis inside the allowed range (-32768 to 32767), returnV. - Set
V = V % 65536. - If
Vexceeds 32767, setV = V - 65536. - Return
V.
Evaluation helper function (EVALHELPER)
Simple two-value evaluator. Accepts two stacks, value stack VALSTACK and operator stack OPSTACK.
- Pop operation
OPfromOPSTACK. - Pop operand
V2fromVALSTACK. - Pop operand
V1fromVALSTACK. - If
OPis equal to$, setRESto a random value between 0 and(V2 + 32768) % 32768. - If
OPis equal to+, setRES = V1 + V2. - If
OPis equal to-, setRES = V1 - V2. - If
OPis equal to*, setRES = V1 * V2. - If
OPis equal to/andV2is not 0, setRES = V1 / V2. - Push the value of
NORM(RES)toVALSTACK. - Return
VALSTACKandOPSTACK.
Expression evaluation function (EXPREVAL)
Mathematical expression and intrinsic function application engine. Accepts a single EXPR string as a parameter.
- Trim leading/trailing whitespace from
EXPR. - If
EXPRis empty, return 0. - Initialize
VALSTACKandOPSTACKas empty stacks. - Initialize
VALBUFandPREVCas empty strings. - Set index
I = 0. - [Main Loop] Fetch character
C = EXPR[I]. If out of bounds, go to step 16. - If
Cis whitespace, incrementIand go to step 6. - If
Cis a digit:- Append
CtoVALBUF. - Set
PREVC = C, incrementI, go to step 6.
- Append
- If
VALBUFis not empty, pushNORM(VALBUF)toVALSTACKand clearVALBUF. - If EXPR[I onwards] starts with
RND:- Push 0 to
VALSTACK. - Set
Cto$. - Advance
Iby 3, go to step 14 (process$as operator).
- Push 0 to
- If
Cis a variableA-Z:- Push
NORM(VARS[C])toVALSTACK. - Set
PREVC = C, incrementI, go to step 6.
- Push
- If
Cis(:- Push
(toOPSTACK. - Set
PREVC = C, incrementI, go to step 6.
- Push
- If
Cis):- While
OPSTACKtop is not(: ExecuteEVALHELPER(VALSTACK, OPSTACK). - Pop
(fromOPSTACK. - Set
PREVC = C, incrementI, go to step 6.
- While
- If
Cis an operator (+ - * / $):- If C is
+or-AND (PREVCis empty OR belongs to( + - * / $):- Push 0 to
VALSTACK.
- Push 0 to
- While
OPSTACKis not empty ANDPRECMAP[OPSTACK top] >= PRECMAP[C]:- Execute
EVALHELPER(VALSTACK, OPSTACK).
- Execute
- Push
CtoOPSTACK. - Set
PREVC = C, incrementI, go to step 6.
- If C is
- [Finalization] If
VALBUFis not empty, pushNORM(VALBUF)toVALSTACK. - While
OPSTACKis not empty: ExecuteEVALHELPER(VALSTACK, OPSTACK). - Return pop from
VALSTACK.
Core language routines
EN
Program end routine.
- Set
IP = -1. - Clear
CALLSTACK.
GO
Direct jump routine. Accepts EXPR string as a parameter.
Set IP = EXPREVAL(EXPR) - 1.
GOS
Subroutine caller. Accepts EXPR string as a parameter.
- Push
IPtoCALLSTACK. - Set
IP = EXPREVAL(EXPR) - 1.
RE
Return from a subroutine.
Pop the value from CALLSTACK and set IP to it.
LE
Assign a value to the variable. Accepts STMT string as a parameter.
- Treat the first Latin letter in
STMTas the variable nameVARNAME. - Treat everything after the first
=character inSTMTas the expressionEXPR. - Set
VARS[VARNAME] = EXPREVAL(EXPR).
IF
Conditionally execute the statement that follows the expressions with the relative operator. Accepts STMT string as a parameter.
- Find the first position of either of these characters
> = <insideSTMT. Record the position into two integer variables,ROPOSandROEND, and the first found character itself into the string variableRELOP. - If the character at
ROPOS + 1position insideSTMTalso is either of> = <, then append this character toRELOPand incrementROENDby 1. - Set the string
EXPR1to everything from the start ofSTMTto the character at positionROPOS - 1inclusively. - Set the string
RESTto everything from the positionROEND + 1to the end ofSTMTinclusively. - Initialize the integer position variable
THENPOSto -1. - Pick the next keyword
KWfrom this list in this particular order:THEN IF LET = RETURN GOSUB INPUT PRINT GOTO END. Go to step 11 when the list is exhausted. - Set
THENPOSto the first position of the currently iterated keywordKWinsideREST. If not found, go to step 6. - If
KWis equal exactly to=, decrementTHENPOSby 1. - Copy the value of
THENPOSto another integer variable,RESTPOS. - If
KWis equal exactly toTHEN, incrementRESTPOSby 4. - Set the string
EXPR2to everything from the start ofRESTtoTHENPOS - 1inclusively. - Set
RESTto everything from the positionRESTPOSto the end of theRESTstring inclusively. - Perform comparison operation denoted by
RELOPonEXPREVAL(EXPR1)andEXPREVAL(EXPR2). If the result of comparison is true, executePROGINPUT(REST).
IN
Value input routine. Accepts STMT string as a parameter.
- Remove all whitespace inside
STMT. - Split the value of
STMTby a comma,into a listVARLIST. - Output the prompt
?and expect user input on the same line. - Read the user input into a buffer string, remove all whitespace inside it and uppercase it. Save it as
INPUTBUF. - Split the value of
INPUTBUFby a comma,into a listINPUTLIST. - Iterate over
VARLIST. For each 1-character variableVARinsideVARLIST, take the corresponding expressionVAREXPRfromINPUTLIST(under the same index) and setVARS[VAR] = EXPREVAL(VAREXPR).
PR
Value and string printing routine. Accepts STMT string as a parameter.
- Initialize an empty list
PRINTLISTand an empty string bufferITEM. - Set the
QUOTINGflag to 0. - Fetch the next character
CfromSTMT. Go to step 10 if there are no more characters. - If
Cis a double quote", then flip theQUOTINGflag (setQUOTING = 1 - QUOTING), append the value ofCtoITEMand go to step 3. Otherwise, go to step 6. - If the
QUOTINGflag is set, append the value ofCtoITEMand go to step 3. - If
Cis either a comma (,) or a semicolon (;), go to step 7, otherwise go to step 9. - If the current
ITEMvalue is not empty, append it to thePRINTLISTand clear theITEMvariable. - Append the value of
CtoPRINTLISTand go to step 3. - If
Cis not a whitespace, append its value toITEMand go to step 3. - If the current
ITEMvalue is not empty, append it to thePRINTLISTand clear theITEMvariable. - Fetch the next item string
ITEMfromPRINTLIST. Go to step 15 if there are no more items. - If
ITEMbegins with a double quote", trim all double quotes from the beginning and end and output its value verbatim (without a newline). Go to step 11. - If
ITEMis equal exactly to a comma,, then output a TAB character (\t, ASCII 9) and go to step 11. - If
ITEMis not equal to a semicolon;, then output the value ofEXPREVAL(ITEM)verbatim, without a newline. Go to step 11. - If the last
ITEMvalue is not a comma,or a semicolon;, output a newline.
Interactive routines
RUN
Run the program currently loaded into the working memory.
- Set
IP = 1. - If
PROGMEM[IP]exists, is not empty and does not start with the stringREM, executePROGEXEC(PROGMEM[IP]). - Increment
IPby 1. - If
IP > 0andIP < 32768, go to step 2.
LIST
Output program listing.
Two algorithms are provided: for the platforms that can extract associative array keys and for those that cannot.
Algorithm 1:
- Extract key list
LNOSfromPROGMEMassociative array. Sort the list in the integer numeric order. - Fetch the next key
LNOfromLNOS. Upon exhaustion, return from the routine. - If
PROGMEM[LNO]is not empty, output the value ofLNO, TAB character (ASCII 9) andPROGMEM[LNO]with a newline at the end. Go to step 2.
Algorithm 2:
- Set
I = 1. - If
PROGMEM[I]exists and is not empty, output the value ofI, TAB character (ASCII 9) andPROGMEM[I]with a newline at the end. - Increment
Iby 1. - If
I > 0andI < 32768, go to step 2.
NEW / CLEAR
Memory clear routine.
Restore all globals to their initial values:
PROGMEM= emptyVARS["A"]..VARS["Z"] = 0CALLSTACK= emptyIP= 0
LOAD
Program loading routine from external storage media. The provided algorithm works with disk file I/O as the most popular form of interaction.
- Prompt the user for a file name/path
FNAME. - If the file does not exist, notify the user about this error and return.
- Execute the
CLEARinteractive routine. - Open a file handle
FHfor reading from the file under the pathFNAME. - Read the next program line
PROGLINEfrom the file handleFH. Go to step 9 upon end-of-file condition. - Trim the leading and trailing whitespace from
PROGLINE. - If
PROGLINEis not empty, executePROGINPUT(PROGLINE). - Go to step 5.
- Close the handle
FHand notify the user about successful program loading.
SAVE
Program saving routine into external storage media. The provided algorithm works with disk file I/O as the most popular form of interaction.
- Prompt the user for a file name/path
FNAME. - If the file name/path isn't writable, notify the user about this error and return.
- Open a file handle
FHfor writing into the file under the pathFNAME. - Perform the algorithm described for the
LISTroutine, but using the specified file handle instead of standard output and space character (ASCII 32) instead of TAB character (ASCII 9). - Close the handle
FHand notify the user about successful program saving.
Closing notes
The set of algorithms in this document should be enough to create a complete and functional LTTB interpreter in any modern programming language. If you have any corrections or optimizations regarding the algorithms, feel free to create an issue in this repository.
In order to verify implementation correctness, you can use the code inside test.bas shipped in this repository.