Files
lttb/lttb-impl-notes.md
T
2026-06-13 19:02:47 +03:00

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 = empty
  • VARS["A"]..VARS["Z"] = 0
  • CALLSTACK = empty
  • IP = 0

Overall interpreter session flow

  1. Print the welcome banner.
  2. If the implementation is CLI-based and there is a file name passed into the command-line argument, run the LOAD routine on this file.
  3. Output the prompt > and expect user input on the same line.
  4. Read the user input, trim leading/trailing whitespace and uppercase it. Save it as CMD.
  5. If CMD equals exactly to BYE, print the "Bye!" message and exit the interpreter.
  6. If CMD is exactly one of these RUN LIST NEW CLEAR LOAD SAVE, execute the corresponding interactive routine and go to step 3. For NEW and CLEAR, the interactive routine must be the same.
  7. Execute program line input routine PROGINPUT with CMD as a parameter and then go to step 3.

Program line input routine (PROGINPUT)

Accepts the statement string STMT as the input line argument.

  1. Trim leading/trailing whitespace from STMT.
  2. Return from the routine if the STMT is an empty string.
  3. Initialize line number variable LNO as 0.
  4. Iterate through characters of STMT. If the current character C is a digit, set LNO = LNO * 10 + C. Repeat this step as long as C is a digit or a whitespace character.
  5. Starting from the first non-digit and non-whitespace character, save the rest of the STMT string as the statement body BODY.
  6. If LNO > 0, set PROGMEM[LNO] = BODY. Otherwise, run statement execution routine PROGEXEC with BODY as a parameter.

Program statement execution routine (PROGEXEC)

Accepts the statement string STMT as the input line argument.

  1. Read the keyword identifier KW from the first two non-whitespace characters in STMT.
  2. If the second character in KW is equal to =, go to step 3, otherwise go to step 5.
  3. Set KW to the value LE.
  4. Prepend the value LE (two characters LE and then a whitespace) to the beginning of STMT.
  5. If KW is exactly one of these EN GO IF IN LE PR RE, go to the next step, otherwise return.
  6. Find the position of the first whitespace in STMT, then copy everything from this position to the end of STMT into BODY.
  7. Trim leading/trailing whitespace from BODY.
  8. If KW is equal to GO and the third non-whitespace character of STMT is equal to S, execute core language routine named GOS with BODY as a parameter and return.
  9. Execute core language routine with the name equal to the value of KW with BODY as a parameter and return.

Number normalization function (NORM)

Accepts any number-like value V as the argument.

  1. If V is an empty string, return 0 immediately.
  2. Convert V into an integer (by truncating the decimal part if necessary).
  3. If V is inside the allowed range (-32768 to 32767), return V.
  4. Set V = V mod 65536.
  5. If V exceeds 32767, set V = V - 65536.
  6. Return V.

Evaluation helper function (EVALHELPER)

Simple two-value evaluator. Accepts two stacks, value stack VALSTACK and operator stack OPSTACK.

  1. Set the integer result RES to 0.
  2. Pop operation OP from OPSTACK.
  3. Pop operand V2 from VALSTACK.
  4. Pop operand V1 from VALSTACK.
  5. If OP is equal to $, set RES to a random value between 0 and (V2 + 32768) % 32768.
  6. If OP is equal to +, set RES = V1 + V2.
  7. If OP is equal to -, set RES = V1 - V2.
  8. If OP is equal to *, set RES = V1 * V2.
  9. If OP is equal to / and V2 is not 0, set RES = V1 / V2.
  10. Push the value of NORM(RES) to VALSTACK.
  11. Return VALSTACK and OPSTACK.

Expression evaluation function (EXPREVAL)

Mathematical expression and intrinsic function application engine. Accepts a single EXPR string as a parameter.

  1. Trim any leading/trailing whitespace from EXPR.
  2. If EXPR is empty, return 0 as a result.
  3. If EXPR only consists of digits, return NORM(EXPR) as a result.
  4. Substitute all occurrences of the string RND with the string 0$ inside EXPR.
  5. [Phase 1 start] Initialize a PRE_EXPR (prepared expression) string buffer with three opening parens (((.
  6. Initialize a previous character holder PREVC with an empty string.
  7. Fetch the next character C in EXPR. Go to step 15 if there are no more characters.
  8. If C is a digit or $ character, append it to PRE_EXPR and go to step 14.
  9. If C is a variable name A-Z, append the value VARS[C] to PRE_EXPR and go to step 14.
  10. If C is an opening parenthesis (, append ((( to PRE_EXPR and go to step 14.
  11. If C is a closing parenthesis ), append ))) to PRE_EXPR and go to step 14.
  12. If C is either * or /, append ), then the value of C, then ( to PRE_EXPR and go to step 14.
  13. If C is either + or -, check for the PREVC value. If PREVC is either an empty string or belongs to the ( + - * / set, then append 0 and the value of C to PRE_EXPR, otherwise append )), then the value of C, then (( to PRE_EXPR.
  14. Save the current value of C into PREVC and go to step 7.
  15. Append three closing parens ))) to the end of PRE_EXPR.
  16. [Phase 2 start] Initialize a value buffer VALBUF as an empty string, and two empty stacks: VALSTACK and OPSTACK.
  17. Initialize precedence map PRECMAP with string keys and integer values. The map can be represented in JSON as follows: {"+": 1, "-": 1, "*": 2, "/": 2, "$": 3}.
  18. Fetch the next non-whitespace character C in PRE_EXPR. Go to step 33 if there are no more characters.
  19. If C is a digit, append it to VALBUF and go to step 18.
  20. If VALBUF is not empty, push its value onto VALSTACK and clear the VALBUF buffer.
  21. If C is an opening parenthesis (, push it onto OPSTACK and go to step 18.
  22. If C is a closing parenthesis ), go to step 23, otherwise go to step 25.
  23. While OPSTACK is not empty and the top of OPSTACK is not (, execute EVALHELPER(VALSTACK,OPSTACK).
  24. If the top of OPSTACK is (, pop it from there. Go to step 18.
  25. If C is one of these + - * / $, go to step 26, otherwise go to step 18.
  26. Set the current precedence value PREC = PRECMAP[C].
  27. If OPSTACK is empty, go to step 32.
  28. Get the top operation on the OPSTACK without popping it. Assign it to TOP_OP.
  29. If TOP_OP is (, go to step 32.
  30. If PRECMAP[TOP_OP] >= PREC, execute EVALHELPER(VALSTACK,OPSTACK), otherwise go to step 32.
  31. Go to step 27.
  32. Push C into OPSTACK and go to step 18.
  33. While OPSTACK is not empty, execute EVALHELPER(VALSTACK,OPSTACK).
  34. Pop the value RES from VALSTACK and return NORM(RES) as the expression result.

Core language routines

EN

Program end routine.

  1. Set IP = -1.
  2. 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.

  1. Push IP to CALLSTACK.
  2. 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.

  1. Treat the first Latin letter in STMT as the variable name VARNAME.
  2. Treat everything after the first = character in STMT as the expression EXPR.
  3. Set VARS[VARNAME] = EXPREVAL(EXPR).

IF

Conditionally execute the statement that follows the expressions with the relative operator. Accepts STMT string as a parameter.

  1. Find the first position of either of these characters > = < inside STMT. Record the position into two integer variables, ROPOS and ROEND, and the first found character itself into the string variable RELOP.
  2. If the character at ROPOS + 1 position inside STMT also is either of > = <, then append this character to RELOP and increment ROEND by 1.
  3. Set the string EXPR1 to everything from the start of STMT to the character at position ROPOS - 1 inclusively.
  4. Set the string REST to everything from the position ROEND + 1 to the end of STMT inclusively.
  5. Initialize the integer position variable THENPOS to -1.
  6. Pick the next keyword KW from this list in this particular order: THEN LET = RETURN GOSUB INPUT PRINT GOTO END IF. Go to step 11 when the list is exhausted.
  7. Set THENPOS to the first position of the currently iterated keyword KW inside REST. If not found, go to step 6.
  8. If KW is equal exactly to =, decrement THENPOS by 1.
  9. Copy the value of THENPOS to another integer variable, RESTPOS.
  10. If KW is equal exactly to THEN, increment RESTPOS by 4.
  11. Set the string EXPR2 to everything from the start of REST to THENPOS - 1 inclusively.
  12. Set REST to everything from the position RESTPOS to the end of the REST string inclusively.
  13. Perform comparison operation denoted by RELOP on EXPREVAL(EXPR1) and EXPREVAL(EXPR2). If the result of comparison is true, execute PROGINPUT(REST).

IN

Value input routine. Accepts STMT string as a parameter.

  1. Remove all whitespace inside STMT.
  2. Split the value of STMT by a comma , into a list VARLIST.
  3. Output the prompt ? and expect user input on the same line.
  4. Read the user input into a buffer string, remove all whitespace inside it and uppercase it. Save it as INPUTBUF.
  5. Split the value of INPUTBUF by a comma , into a list INPUTLIST.
  6. Iterate over VARLIST. For each 1-character variable VAR inside VARLIST, take the corresponding expression VAREXPR from INPUTLIST (under the same index) and set VARS[VAR] = EXPREVAL(VAREXPR).

PR

Value and string printing routine. Accepts STMT string as a parameter.

  1. Initialize an empty list PRINTLIST and an empty string buffer ITEM.
  2. Set the QUOTING flag to 0.
  3. Fetch the next character C from STMT. Go to step 10 if there are no more characters.
  4. If C is a double quote ", then flip the QUOTING flag (set QUOTING = 1 - QUOTING), append the value of C to ITEM and go to step 3. Otherwise, go to step 6.
  5. If the QUOTING flag is set, append the value of C to ITEM and go to step 3.
  6. If C is either a comma (,) or a semicolon (;), go to step 7, otherwise go to step 9.
  7. If the current ITEM value is not empty, append it to the PRINTLIST and clear the ITEM variable.
  8. Append the value of C to PRINTLIST and go to step 3.
  9. If C is not a whitespace, append its value to ITEM and go to step 3.
  10. If the current ITEM value is not empty, append it to the PRINTLIST and clear the ITEM variable.
  11. Fetch the next item string ITEM from PRINTLIST. Go to step 15 if there are no more items.
  12. If ITEM begins 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.
  13. If ITEM is equal exactly to a comma ,, then output a TAB character (\t, ASCII 9) and go to step 11.
  14. If ITEM is not equal to a semicolon ;, then output the value of EXPREVAL(ITEM) verbatim, without a newline. Go to step 11.
  15. If the last ITEM value is not a comma , or a semicolon ;, output a newline.

Interactive routines

RUN

Run the program currently loaded into the working memory.

  1. Set IP = 1.
  2. If PROGMEM[IP] exists and is not empty, execute PROGEXEC(PROGMEM[IP]).
  3. Increment IP by 1.
  4. If IP > 0 and IP < 32768, go to step 2.

LIST

Output program listing.

  1. Set I = 1.
  2. If PROGMEM[I] exists and is not empty, output the value of I, TAB character (ASCII 9) and PROGMEM[I] with a newline at the end.
  3. Increment I by 1.
  4. If I > 0 and I < 32768, go to step 2.

NEW / CLEAR

Memory clear routine.

Restore all globals to their initial values:

  • PROGMEM = empty
  • VARS["A"]..VARS["Z"] = 0
  • CALLSTACK = empty
  • IP = 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.

  1. Prompt the user for a file name/path FNAME.
  2. If the file does not exist, notify the user about this error and return.
  3. Execute the CLEAR interactive routine.
  4. Open a file handle FH for reading from the file under the path FNAME.
  5. Read the next program line PROGLINE from the file handle FH. Go to step 9 upon end-of-file condition.
  6. Trim the leading and trailing whitespace from PROGLINE.
  7. If PROGLINE is not empty, execute PROGINPUT(PROGLINE).
  8. Go to step 5.
  9. Close the handle FH and 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.

  1. Prompt the user for a file name/path FNAME.
  2. If the file name/path isn't writable, notify the user about this error and return.
  3. Open a file handle FH for writing into the file under the path FNAME.
  4. Set I = 1.
  5. If PROGMEM[I] exists and is not empty, write the value of I, space character (ASCII 32) and PROGMEM[I] with a newline at the end to the file handle FH.
  6. Increment I by 1.
  7. If I > 0 and I < 32768, go to step 5.
  8. Close the handle FH and 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.