┌─────────────────────────────────────────────┐
│ Layer 3: BASIC Interpreter │
│ Tokenizer, parser, statement dispatch, │
│ execution of tokenized lines │
├─────────────────────────────────────────────┤
│ Layer 2: BASIC Runtime │
│ Terminal I/O, line storage, expression │
│ helpers, GOSUB/FOR stacks, PEEK/POKE, │
│ tape abstraction, variable table │
├─────────────────────────────────────────────┤
│ Layer 1: P-Code VM │
│ Stack machine, eval+call stacks, globals, │
│ heap, traps, sys calls (PUTC/GETC/LED), │
│ byte/word memory access │
├─────────────────────────────────────────────┤
│ Layer 0: COR24 Hardware / Emulator │
│ 24-bit RISC ISA, SRAM, UART, LED/switch, │
│ MMIO at 0xFF0000+ │
└─────────────────────────────────────────────┘
BASIC is an interpreter running on the VM, not compiled to p-code. The VM executes the BASIC interpreter's code (written in Pascal, compiled to p-code by p24p); the interpreter in turn parses and executes BASIC programs stored in its own memory area.
The BASIC interpreter is a Pascal program. The toolchain:
basic.pas → p24p (Pascal→p-code) → basic.spc → pa24r → basic.p24 → pvm.s
This dogfoods the Pascal compiler. If p24p lacks a needed feature (e.g., arrays, procedures), BASIC work pauses until p24p is updated.
The BASIC interpreter is a p-code program that:
- Uses
sys GETC/sys PUTCfor terminal I/O - Uses
loadb/storebfor byte-level token/string manipulation - Uses
load/storefor word-level data (variables, line pointers) - Uses
call/retfor internal interpreter subroutines - Uses globals for interpreter state (or a state struct in the heap)
- Uses the VM eval stack for expression evaluation
- Uses
trapfor fatal errors
The interpreter's own GOSUB stack and FOR stack are not the VM call/eval stacks — they are BASIC-level data structures stored in the interpreter's memory area.
Within the VM's address space, the BASIC interpreter allocates its working memory from the globals/heap area:
┌──────────────────────────────────┐ High addresses
│ Input Buffer (256 bytes) │ Raw terminal input line
├──────────────────────────────────┤
│ Token Buffer (256 bytes) │ Tokenized form of current line
├──────────────────────────────────┤
│ String Pool (512 bytes) │ String literals from tokenized
│ │ program (referenced by tokens)
├──────────────────────────────────┤
│ GOSUB Stack (64 entries) │ Return line pointers
│ (64 × 3 = 192 bytes) │
├──────────────────────────────────┤
│ FOR Stack (16 entries) │ var, limit, step, restart ptr
│ (16 × 12 = 192 bytes) │
├──────────────────────────────────┤
│ Variable Table │ 26 words (A-Z) or 286 (A-Z,
│ (78-858 bytes) │ A0-Z9)
├──────────────────────────────────┤
│ Program Area (~8-16 KB) │ Packed sorted tokenized lines
│ │ [line#][len][tokens...] ...
├──────────────────────────────────┤
│ Interpreter State │ Pointers, flags, counters
│ (~30 words) │
└──────────────────────────────────┘ Low addresses (globals base)
Stored as a packed sorted buffer of tokenized lines:
┌───────┬─────┬──────────────────────┐
│ line# │ len │ tokenized content ... │
│ (word)│(byte)│ │
└───────┴─────┴──────────────────────┘
- Lines sorted by line number
- Insertion/deletion requires shifting subsequent lines
- Linear search for line lookup (index table optional later)
program_startandprogram_endpointers track bounds
- 26 integer words for A-Z (minimal)
- Optionally 286 words for A-Z plus A0-Z9 (extended)
- Direct index: variable letter maps to offset
- All variables are signed 24-bit integers
- Initialized to 0 on
NEWorRUN
- Fixed-size stack of return-line pointers
- Push on
GOSUB, pop onRETURN - Stack pointer tracks depth
- Overflow:
OUT OF MEMORYerror
Each entry stores:
- Variable index (which loop variable)
- Limit value
- Step value
- Restart pointer (line pointer to loop body)
Push on FOR, pop on NEXT when limit reached.
Nested FORs stack naturally. NEXT matches by variable name.
Tokens are single bytes, with multi-byte payloads for literals and strings:
| Token Type | Encoding | Description |
|---|---|---|
| Keyword | 0x80-0xAF |
PRINT, IF, GOTO, INKEY, etc. |
| Delimiter | 0xB0-0xB3 |
(, ), comma, semicolon |
| Operator | 0xB4-0xBD |
+, -, *, /, =, <>, etc. |
| Variable | 0xC0-0xD9 |
A=0xC0, B=0xC1, ... Z=0xD9 |
| Integer literal | 0xE0 + 3 bytes |
24-bit value follows |
| String literal | 0xE1 + len + bytes |
Length-prefixed string |
| End of line | 0x00 |
Terminator |
Keywords, operators, and delimiters are single-byte tokens. The keyword range intentionally reserves 48 slots so new builtins can be added without colliding with delimiter, operator, or variable tokens.
loop:
print "READY"
read_line(input_buffer)
if line starts with digit:
tokenize(input_buffer, token_buffer)
store_line(token_buffer) ; insert/replace/delete
else:
tokenize(input_buffer, token_buffer)
execute_immediate(token_buffer) ; run now
goto loop
- Immediate: tokenize and execute the single line directly
- Stored (RUN): start at lowest line number, advance
current_line_ptrthrough program area, executing each line
RUN:
clear variables
clear GOSUB stack
clear FOR stack
current_line_ptr = program_start
while current_line_ptr < program_end:
next_line_ptr = advance(current_line_ptr)
execute_line(current_line_ptr)
if goto/gosub changed current_line_ptr:
continue
current_line_ptr = next_line_ptr
Each line's first token determines the handler:
switch first_token:
TOK_LET -> stmt_let()
TOK_PRINT -> stmt_print()
TOK_INPUT -> stmt_input()
TOK_IF -> stmt_if()
TOK_GOTO -> stmt_goto()
TOK_GOSUB -> stmt_gosub()
TOK_RETURN -> stmt_return()
TOK_FOR -> stmt_for()
TOK_NEXT -> stmt_next()
TOK_STOP -> stmt_stop()
TOK_END -> stmt_end()
TOK_REM -> (skip)
TOK_VAR -> stmt_implicit_let() ; A = expr
default -> SYNTAX ERROR
| BASIC Operation | VM Mechanism |
|---|---|
| PRINT character | sys 1 (PUTC) |
| INPUT character | sys 2 (GETC) |
| INKEY poll | sys 9 (INKEY) |
| LED control | sys 3 (LED) |
| BASIC Operation | VM Mechanism |
|---|---|
| PEEK(addr) | push addr + loadb |
| POKE addr,val | push val + push addr + storeb |
| Variable read | loadg or indexed load |
| Token scanning | loadb through token buffer |
| Error Condition | VM Trap |
|---|---|
| Division by zero | VM trap 1 (DIV_ZERO) — caught by interpreter |
| Out of memory | Interpreter checks bounds, reports error |
| Bad address | Interpreter validates, or VM trap 5 |
These language-neutral primitives would benefit BASIC and future interpreters. Status based on current VM (sw-cor24-pcode):
| Primitive | Status | Notes |
|---|---|---|
loadb / storeb |
Present | Byte access exists |
load / store |
Present | Word access exists |
trap |
Present | Trap with code exists |
sys (PUTC/GETC) |
Present | Console I/O exists |
sys (ALLOC/FREE) |
Present | Heap allocation exists |
| MEMCPY (0x70) | Present | Block copy with memmove semantics |
| MEMSET (0x71) | Present | Block fill |
| MEMCMP (0x72) | Present | Lexicographic byte comparison |
| JMP_IND (0x73) | Present | Indirect jump for dispatch tables |
| CALL_IND | Missing | Indirect call — defer unless needed |
| FIND_BYTE | Missing | Byte scan — defer unless needed |
MEMCPY, MEMSET, MEMCMP, and JMP_IND were added to the VM as part of the interpreter-assisting primitives feature request.
basic_repl REPL loop, prompt, line classification
basic_lex Tokenizer: source text -> token stream
basic_tokens Token definitions, keyword table, detokenizer
basic_store Program storage: insert, delete, find, list
basic_expr Expression parser (Pratt/precedence climbing)
basic_stmt Statement handlers (LET, PRINT, IF, GOTO, etc.)
basic_exec Execution engine: RUN, line dispatch, flow control
basic_runtime Runtime state, variable table, stacks, init/reset
basic_errors Error codes, error messages, error reporting
basic_io Terminal I/O and tape abstraction layer
basic_repl
├── basic_lex
├── basic_store
├── basic_exec
│ ├── basic_stmt
│ │ ├── basic_expr
│ │ └── basic_runtime
│ └── basic_runtime
├── basic_io
└── basic_errors
basic_tokens (shared definitions, used by lex, store, expr, stmt)
sw-cor24-basic (this project)
│
│ runs on
▼
sw-cor24-pcode/vm (p-code VM)
│
│ executes on
▼
sw-cor24-emulator (COR24 emulator / hardware)
sw-cor24-pascal (Pascal compiler) ──► also targets p-code VM
sw-cor24-pcode/assembler (pa24r) ──► assembles .spc to .p24
sw-cor24-pcode/linker (pl24r) ──► links .spc modules