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 mod 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.
- Set the integer result
RESto 0. - 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 any leading/trailing whitespace from
EXPR. - If
EXPRis empty, return 0 as a result. - If
EXPRonly consists of digits, returnNORM(EXPR)as a result. - Substitute all occurrences of the string
RNDwith the string0$insideEXPR. - [Phase 1 start] Initialize a
PRE_EXPR(prepared expression) string buffer with three opening parens(((. - Initialize a previous character holder
PREVCwith an empty string. - Fetch the next character
CinEXPR. Go to step 15 if there are no more characters. - If
Cis a digit or$character, append it toPRE_EXPRand go to step 14. - If
Cis a variable nameA-Z, append the valueVARS[C]toPRE_EXPRand go to step 14. - If
Cis an opening parenthesis(, append(((toPRE_EXPRand go to step 14. - If
Cis a closing parenthesis), append)))toPRE_EXPRand go to step 14. - If
Cis either*or/, append), then the value ofC, then(toPRE_EXPRand go to step 14. - If
Cis either+or-, check for thePREVCvalue. IfPREVCis either an empty string or belongs to the( + - * /set, then append0and the value ofCtoPRE_EXPR, otherwise append)), then the value ofC, then((toPRE_EXPR. - Save the current value of
CintoPREVCand go to step 7. - Append three closing parens
)))to the end ofPRE_EXPR. - [Phase 2 start] Initialize a value buffer
VALBUFas an empty string, and two empty stacks:VALSTACKandOPSTACK. - Initialize precedence map
PRECMAPwith string keys and integer values. The map can be represented in JSON as follows:{"+": 1, "-": 1, "*": 2, "/": 2, "$": 3}. - Fetch the next non-whitespace character
CinPRE_EXPR. Go to step 33 if there are no more characters. - If
Cis a digit, append it toVALBUFand go to step 18. - If
VALBUFis not empty, push its value ontoVALSTACKand clear theVALBUFbuffer. - If
Cis an opening parenthesis(, push it ontoOPSTACKand go to step 18. - If
Cis a closing parenthesis), go to step 23, otherwise go to step 25. - While
OPSTACKis not empty and the top ofOPSTACKis not(, executeEVALHELPER(VALSTACK,OPSTACK). - If the top of
OPSTACKis(, pop it from there. Go to step 18. - If
Cis one of these+ - * / $, go to step 26, otherwise go to step 18. - Set the current precedence value
PREC = PRECMAP[C]. - If
OPSTACKis empty, go to step 32. - Get the top operation on the
OPSTACKwithout popping it. Assign it toTOP_OP. - If
TOP_OPis(, go to step 32. - If
PRECMAP[TOP_OP] >= PREC, executeEVALHELPER(VALSTACK,OPSTACK), otherwise go to step 32. - Go to step 27.
- Push
CintoOPSTACKand go to step 18. - While
OPSTACKis not empty, executeEVALHELPER(VALSTACK,OPSTACK). - Pop the value
RESfromVALSTACKand returnNORM(RES)as the expression result.
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 LET = RETURN GOSUB INPUT PRINT GOTO END IF. 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 and is not empty, executePROGEXEC(PROGMEM[IP]). - Increment
IPby 1. - If
IP > 0andIP < 32768, go to step 2.
LIST
Output program listing.
- 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. - Set
I = 1. - If
PROGMEM[I]exists and is not empty, write the value ofI, space character (ASCII 32) andPROGMEM[I]with a newline at the end to the file handleFH. - Increment
Iby 1. - If
I > 0andI < 32768, go to step 5. - 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.