! ================================================================
! SUPER TINY BASIC INTERPRETER VERSION 1.0
! ================================================================
! A complete BASIC interpreter implementation built with Hobby BASIC
! Optimized for Windows 11 Console
! See LICENCE.TXT for Hobby BASIC and Super Tiny BASIC terms of use
!
! About the name — why "Super Tiny BASIC"
! Tiny BASIC historically referred to extremely small BASIC interpreters
! that could fit in ~4 KB of memory. Super Tiny BASIC keeps the nostalgic
! spirit (single-letter variables, single-line IF, simple loops and I/O)
! while providing a more practical, feature-friendly environment implemented
! in Hobby BASIC. It's not the 4 KB minimalist implementation — it's a
! playful, lightweight, and hackable interpreter inspired by that tradition.
!
! Full source code and example programs included in the EXAMPLES\TINY folder.
!
! Features:
! - BASIC commands: LET, PRINT, INPUT, IF-THEN-ELSE, FOR-NEXT, WHILE-WEND,
! GOSUB-RETURN, REM, CLS, WAIT, END, LOCATE, COLOR, INKEY
! - Numeric variables: Single letters A-Z only (e.g., A, B, X)
! - Expression parser: Arithmetic (+, -, *, /, %), comparison (=, <>, <, >, <=, >=),
! and logical operators (AND, OR) with proper precedence
! - Built-in functions:
! * RND() - Returns random integer
! * CHR(n) - Returns character with specified ASCII code (in PRINT statements)
! - Comprehensive error handling with detailed error messages
! - File operations: LOAD and SAVE programs to disk
! - Console control: LOCATE (set cursor position), COLOR (set colors), CLS (clear screen),
! WAIT (delay)
! - INKEY command with two modes:
! * Standalone: INKEY (stores key info in variables I, J, K)
! * Assignment: A=INKEY (stores key value in specified variable)
! - Multi-statement lines using colon (:) separator
! - String literals supported in PRINT and INPUT statements (e.g., PRINT "HELLO")
! - Automatic uppercase conversion for keywords and variables outside of string literals
! - Program editing: Line number-based entry and deletion
!
! Example Programs Included:
! STRESS.BAS - Comprehensive language test
! ADVANCED.BAS - Complex expressions test
! MEGATEST.BAS - Multi-category test suite
! MANUAL.BAS - Language examples guide
! 100LINES.BAS - Compact code test
! GUESSNUM.BAS - Number guessing game
! COINFLIP.BAS - Coin toss game
! CODEBREAK.BAS - Code breaking puzzle
! HORSERACE.BAS - Horse racing game
! FOREST.BAS - Text adventure game
!
! This interpreter demonstrates the power of Hobby BASIC for
! creating complex console applications while maintaining the
! simplicity and readability of classic BASIC syntax.
! ================================================================
! Set current directory to TINY examples folder
path$ = RIGHT(PATH(2), -3) + "\EXAMPLES\"
FILE(path$, 4)
! Initialize console environment
view 11
screen 80, 40, 9999
title "Super Tiny BASIC Ιnterpreter Version 1.0"
cursor 1
color 0, 7
cls
! Predefined character sets for fast INSET() validation
! INSET() is faster than ASCII range checks
DIGITS$ = "0123456789"
LETTERS$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
! Program storage arrays
MAX_LINES = 10000
dim prog$[MAX_LINES] ! Array for program lines
dim prog_num[MAX_LINES] ! Array for line numbers
dim vars[26] ! Variables A-Z
! FOR-NEXT loop management arrays
dim loop_var$[10] ! Stack for loop variable names
dim loop_end[10] ! Stack for loop end values
dim loop_step[10] ! Stack for loop step values
dim loop_pc[10] ! Stack for program counters
loop_count = 0 ! Active loop counter
! GOSUB-RETURN stack management
dim gosub_stack[20] ! Return address stack
gosub_sp = 0 ! Stack pointer
! WHILE-WEND loop management
dim while_pc[20] ! WHILE statement positions
dim while_conditions$[20]! WHILE conditions
while_count = 0 ! Active WHILE loop counter
! Display program header and instructions
print "=== Super Tiny BASIC Interpreter ==="
print "Optimized for Windows 11 Console"
print "Built with Hobby BASIC"
print
print "-------------------------------------------------"
print "Commands available:"
print " LOAD <file.bas> - Load a program (e.g., LOAD FOREST.BAS)"
print " SAVE <file.bas> - Save current program to file"
print " RUN - Run the loaded program"
print " LIST - Show program listing"
print " NEW - Clear program from memory"
print " CLS - Clear the screen"
print ""
print "Enter program lines directly to build your code."
print "-------------------------------------------------"
print
! ================================================================
! MAIN INPUT LOOP
! Handles user commands and program line entry
! ================================================================
input_loop#
line$ = ""
input "> ", line$
! Convert to uppercase outside of strings
line$ = TRIM(0, ucase_outside_strings(line$))
! Handle empty input
if LEN(line$) = 0 then goto input_loop
! Process interpreter commands
if LEFT(line$, 4) = "LOAD"
gosub load_command
goto input_loop
elseif LEFT(line$, 4) = "SAVE"
gosub save_command
goto input_loop
elseif line$ = "CLS"
color 0, 7 : cls
goto input_loop
elseif line$ = "LIST"
execute_flag = 0
goto program_listing
elseif line$ = "RUN"
execute_flag = 1
goto program_listing
elseif line$ = "NEW"
line_count = 0
print "Program cleared."
goto input_loop
endif
! Handle line deletion (empty line after line number)
space_pos = FIND(line$, " ")
if space_pos = 0
line_num= VAL(TRIM(0, line$))
if line_num < 1 or line_num > MAX_LINES
print "Syntax Error: Line must start with a number (1-", MAX_LINES, ")"
goto input_loop
endif
! Find and delete the specified line
existing_line_index = -1
for i = 0 to line_count - 1
if prog_num[i] = line_num
existing_line_index = i
break
endif
next
if existing_line_index >= 0
! Shift array elements to remove the line
for i = existing_line_index to line_count - 2
prog_num[i] = prog_num[i + 1]
prog$[i] = prog$[i + 1]
next
line_count--
print "Line ", line_num, " deleted"
else
print "Line ", line_num, " not found"
endif
goto input_loop
endif
! Extract line number from input
line_num$ = TRIM(0, LEFT(line$, space_pos - 1))
line_num = VAL(line_num$)
if line_num < 1 or line_num > MAX_LINES
print "Syntax Error: Invalid line number '", TRIM(0, line_num$), "'"
goto input_loop
endif
! Check if line number already exists
existing_line_index = -1
for i = 0 to line_count - 1
if prog_num[i] = line_num
existing_line_index = i
break
endif
next
! Replace existing line or add new line
if existing_line_index >= 0
prog$[existing_line_index] = line$
print "Line ", line_num, " replaced"
else
! Check if program storage is full
if line_count >= MAX_LINES
print "Program storage full."
goto program_listing
endif
! Add new line to program
prog_num[line_count] = line_num
prog$[line_count] = line$
line_count++
endif
goto input_loop
! ================================================================
! LOAD COMMAND SUBROUTINE
! Loads a BASIC program from file
! ================================================================
load_command#
path$ = TRIM(0, MID(line$, 5, LEN(line$) - 4))
! Load file contents
load path$, source$
if V0 = -1
print "Load error: File not found or cannot be read"
else
! Clear current program
line_count = 0
! Split file content into lines
dim lines$[MAX_LINES].ZERO
num_lines = split(source$, CHR(10), lines$[])
! Process each line
for i = 0 to num_lines - 1
current_line$ = TRIM(0, ucase_outside_strings(lines$[i]))
if LEN(current_line$) > 0
space_pos = FIND(current_line$, " ")
if space_pos > 0
line_num = VAL(TRIM(0, LEFT(current_line$, space_pos - 1)))
! Validate line number and add to program (allow 1..MAX_LINES)
if line_num >= 1 and line_num <= MAX_LINES
prog_num[line_count] = line_num
prog$[line_count] = current_line$
line_count++
endif
endif
endif
next
print "Program loaded: ", path$, " (", num_lines, " lines)"
print "Type LIST to view or RUN to execute"
endif
return
! ================================================================
! SAVE COMMAND SUBROUTINE
! Saves the current program to a file
! ================================================================
save_command#
save_part$ = TRIM(1, MID(line$, 5, LEN(line$) - 4))
path$ = TRIM(0, save_part$)
! Build program source from lines
source$ = ""
for i = 0 to line_count - 1
source$ = source$ + prog$[i] + EOL$
next
! Save to file
save path$, source$
if V0 = -1
print "Save error: Cannot write to file"
else
print "Program saved: ", path$, " (", line_count, " lines)"
endif
return
! ================================================================
! PROGRAM LISTING AND EXECUTION
! Handles both program display and execution
! ================================================================
program_listing#
! Sort program lines by line number (bubble sort)
for i = 0 to line_count - 2
for j = i + 1 to line_count - 1
if prog_num[i] > prog_num[j]
swap prog_num[i], prog_num[j]
swap prog$[i], prog$[j]
endif
next
next
! Display program listing if not in execute mode
if not execute_flag
print
print "--- Program Listing ---"
for i = 0 to line_count - 1
print prog$[i]
if i % 10 = 0 and KEY(27) then break
wait 1
next
print "--- End of Listing ---"
print
goto input_loop
endif
! Execute program
print
print "--- Running Program ---"
! Create line number to index mapping for faster GOTO/GOSUB
dim line_map[MAX_LINES].FILL -1
for i = 0 to line_count - 1
if prog_num[i] >= 1 and prog_num[i] <= MAX_LINES
line_map[prog_num[i]] = i
endif
next
! Main program execution loop
pc = 0
dim cmd_list$[100]
fatal_error = 0
while pc < line_count and not fatal_error
current_line$ = prog$[pc]
! Search for space character
space_pos = 0
for i = 1 to LEN(current_line$)
if MID(current_line$, i, 1) = " "
space_pos = i
break
endif
next
if space_pos = 0
! Check if line contains only a valid line number
line_num = VAL(current_line$)
if line_num >= 0
pc++ ! Skip to next line if only line number
else
print "Syntax Error in line: ", current_line$
pc++
endif
else
! Extract line number and commands
line_num = VAL(LEFT(current_line$, space_pos))
commands$ = TRIM(1, MID(current_line$, space_pos + 1, LEN(current_line$) - space_pos))
! Split multiple commands on same line (separated by colons)
cmd_list$[].ZERO
num_cmds = split_commands(commands$, ":", byref cmd_list$[])
jump_occurred = 0
! Execute each command in sequence
for i = 0 to num_cmds - 1
if jump_occurred then break
command$ = TRIM(0, cmd_list$[i])
if LEN(command$) > 0
jump_occurred = execute_command(command$, byref pc, line_map[])
if fatal_error then break
endif
next
! Move to next line if no jump occurred
if not jump_occurred
pc++
endif
endif
endw
! ================================================================
! PROGRAM END HANDLING
! Set text color and print program termination message
! Wait for key press
! Restore current directory to the program's original folder
! ================================================================
program_end#
color 0, 7
print "--- Program Finished ---"
inkey
end
! ================================================================
! UCASE OUTSIDE STRINGS FUNCTION
! Converts characters to uppercase and replaces tabs with spaces,
! but preserves case and tabs inside strings
! ================================================================
sub ucase_outside_strings(s$)
local result$ = s$
local i, in_string = 0
for i = 0 to LEN(s$)-1
if result$(i) = CHR(34) ! Check for quote
in_string = 1 - in_string ! Toggle string mode
elseif not in_string
result$(i) = UCASE(result$(i)) ! Convert to uppercase
if result$(i) = CHR(9) then result$(i) = CHR(32) ! Replace tab with spaces
endif
next
rets result$
ends
! ================================================================
! DISPLAY_ERROR SUBROUTINE
! Displays error message with line number
! ================================================================
sub display_error(msg$)
color 0, 7
print : print "[", line_num, "] ", msg$
fatal_error = 1
rets
ends
! ================================================================
! SPLIT COMMANDS FUNCTION
! Splits commands separated by colons, respecting strings and comments,
! and keeps entire IF ... THEN ... ELSE ... as a single command.
! ================================================================
sub split_commands(commands$, separator$, result$[])
local in_string = 0 ! Track if we're inside a string
local in_rem = 0 ! Track if we're inside a REM comment
local current_cmd$ = "" ! Current command being built
local count = 0 ! Number of commands found
local i, char$, look$
for i = 0 to LEN(commands$)-1
char$ = commands$(i)
! If we're inside a REM comment, add all characters
if in_rem
current_cmd$ = current_cmd$ + char$
! Handle string literals
elseif char$ = CHR(34)
in_string = 1 - in_string
current_cmd$ = current_cmd$ + char$
! Check for REM comments outside strings
elseif not in_string and not in_rem and MID(commands$, i, 3) = "REM"
in_rem = 1
current_cmd$ = current_cmd$ + char$
! Handle command separators outside strings and comments
elseif char$ = separator$ and not in_string and not in_rem
! If the command built so far starts with IF, do NOT split here.
! This keeps the whole IF ... THEN ... ELSE ... (even with colons) as one command.
look$ = TRIM(0, current_cmd$)
if LEN(look$) >= 2 and LEFT(look$, 2) = "IF"
current_cmd$ = current_cmd$ + char$ ! treat ':' as normal char inside IF
else
result$[count] = TRIM(0, current_cmd$)
count++
current_cmd$ = ""
endif
! Add regular characters
else
current_cmd$ = current_cmd$ + char$
endif
next
! Check for unclosed string at the end of parsing
if in_string and not in_rem
display_error("Syntax Error: Unclosed string in command")
rets
endif
! Add the last command if it exists
if LEN(TRIM(0, current_cmd$)) > 0
result$[count] = TRIM(0, current_cmd$)
count++
endif
rets count
ends
! ================================================================
! EXECUTE COMMAND FUNCTION
! Main command dispatcher - handles all BASIC commands
! ================================================================
sub execute_command(cmd$, pc, line_map[])
local result, jump_occurred
local var_index, expr$
result = 0
jump_occurred = 0
cmd$ = TRIM(0, cmd$)
! ============================================================
! LET COMMAND - Variable assignment
! ============================================================
if LEFT(cmd$, 3) = "LET"
local let_part$, eq_pos, var_name$
! Extract the part after LET
let_part$ = TRIM(1, MID(cmd$, 4, LEN(cmd$) - 3))
! Find the equals sign
eq_pos = FIND(let_part$, "=")
if eq_pos = 0
display_error("Syntax Error: Missing '=' in LET")
rets
else
! Extract variable name and expression
var_name$ = TRIM(0, LEFT(let_part$, eq_pos - 1))
expr$ = TRIM(1, MID(let_part$, eq_pos + 1, LEN(let_part$) - eq_pos))
! Check for INKEY assignment
if expr$ = "INKEY"
inkey
var_index = ASC(var_name$) - 65
vars[var_index] = V0
else
! Validate variable name (A-Z)
if LEN(var_name$) = 1 and INSET(var_name$, LETTERS$)
var_index = ASC(var_name$) - 65
vars[var_index] = eval_expr(expr$)
else
display_error("Error: Invalid variable name '" + var_name$ + "'")
rets
endif
endif
endif
! ============================================================
! PRINT COMMAND - Output to screen
! ============================================================
elseif LEFT(cmd$, 5) = "PRINT"
local print_part$
print_part$ = TRIM(1, MID(cmd$, 6, LEN(cmd$) - 5))
process_print(print_part$)
! ============================================================
! GOTO COMMAND - Unconditional jump
! Accepts line number expressions (e.g., GOTO X, GOTO X+10)
! ============================================================
elseif LEFT(cmd$, 4) = "GOTO"
local goto_part$, target_line
! Extract and evaluate target line expression
goto_part$ = TRIM(1, MID(cmd$, 5, LEN(cmd$) - 4))
target_line = eval_expr(goto_part$)
! Validate target line exists
if target_line >= 1 and target_line <= MAX_LINES
if line_map[target_line] < 0 then goto invalid_goto_target
pc = line_map[target_line]
jump_occurred = 1
else
invalid_goto_target#
display_error("Error: Line " + STR(target_line) + " not found")
rets
endif
! ============================================================
! IF COMMAND - Conditional execution (robust THEN/ELSE matching)
! ============================================================
elseif LEFT(cmd$, 2) = "IF"
local if_part$, then_pos, else_pos, condition$, then_command$, else_command$
local i, p, in_string, in_rem, nested_if, ch$, look$, prev_ch$
if_part$ = TRIM(1, MID(cmd$, 3, LEN(cmd$) - 2))
! Find THEN position safely (respect strings and REM)
then_pos = 0
in_string = 0
in_rem = 0
for i = 1 to LEN(if_part$)
ch$ = MID(if_part$, i, 1)
! Toggle string state when quote encountered (unless inside REM)
if ch$ = CHR(34) and not in_rem
in_string = 1 - in_string
endif
! Start REM if we see REM outside strings
if not in_string and not in_rem and MID(if_part$, i, 3) = "REM"
in_rem = 1
endif
! Detect THEN outside strings/comments with word-boundary check
if not in_string and not in_rem and then_pos = 0
look$ = MID(if_part$, i, 4)
if look$ = "THEN"
! ensure previous char is not a letter (word boundary)
if i = 1
then_pos = i
else
prev_ch$ = MID(if_part$, i-1, 1)
if not INSET(prev_ch$, LETTERS$)
then_pos = i
endif
endif
endif
endif
if then_pos > 0 then break
next
if then_pos = 0
display_error("Syntax Error: Missing THEN in IF")
rets
endif
! Extract condition (text before THEN)
condition$ = TRIM(0, LEFT(if_part$, then_pos - 1))
! Find the matching ELSE for this IF (respect nested IFs)
else_pos = 0
nested_if = 0
in_string = 0
in_rem = 0
p = then_pos + 4
while p <= LEN(if_part$)
ch$ = MID(if_part$, p, 1)
! Toggle string state
if ch$ = CHR(34) and not in_rem
in_string = 1 - in_string
endif
! Start REM if encountered
if not in_string and not in_rem and MID(if_part$, p, 3) = "REM"
in_rem = 1
endif
if not in_string and not in_rem
! Look for IF to increment nesting
look$ = MID(if_part$, p, 2)
if look$ = "IF"
! ensure previous char is not letter
ch$ = MID(if_part$, p-1, 1)
if p = 1 or not INSET(ch$, LETTERS$)
nested_if++
p++ ! advance a bit to avoid infinite loop
pass
endif
endif
! Look for ELSE
look$ = MID(if_part$, p, 4)
if look$ = "ELSE"
! ensure previous char is not letter
ch$ = MID(if_part$, p-1, 1)
if p = 1 or not INSET(ch$, LETTERS$)
if nested_if = 0
else_pos = p
break
else
nested_if--
p += 3
pass
endif
endif
endif
endif
p++
endw
! Extract then/else command strings
if else_pos > 0
then_command$ = TRIM(1, MID(if_part$, then_pos + 4, else_pos - then_pos - 4))
else_command$ = TRIM(1, MID(if_part$, else_pos + 4, LEN(if_part$) - else_pos - 3))
else
then_command$ = TRIM(1, MID(if_part$, then_pos + 4, LEN(if_part$) - then_pos - 3))
else_command$ = ""
endif
! Evaluate condition
cond_result = eval_expr(condition$)
! Execute appropriate branch: split branch on ':' and execute sub-commands
if cond_result
if LEN(then_command$) > 0
local then_list$[64].ZERO
num_then = split_commands(then_command$, ":", byref then_list$[])
for t = 0 to num_then - 1
if jump_occurred then break
subcmd$ = TRIM(0, then_list$[t])
if LEN(subcmd$) > 0
jump_occurred = execute_command(subcmd$, byref pc, line_map[])
if fatal_error then break
endif
next
endif
elseif else_command$ <> ""
local else_list$[64].ZERO
num_else = split_commands(else_command$, ":", byref else_list$[])
for e = 0 to num_else - 1
if jump_occurred then break
subcmd$ = TRIM(0, else_list$[e])
if LEN(subcmd$) > 0
jump_occurred = execute_command(subcmd$, byref pc, line_map[])
if fatal_error then break
endif
next
endif
! ============================================================
! FOR COMMAND - Loop initialization
! ============================================================
elseif LEFT(cmd$, 3) = "FOR"
local for_part$, eq_pos, var_name$, rest$, to_pos, start_expr$, rest2$, step_pos, end_expr$, step_expr$
local start_val, end_val, step_val
for_part$ = TRIM(1, MID(cmd$, 4, LEN(cmd$) - 3))
eq_pos = FIND(for_part$, "=")
if eq_pos = 0
display_error("Syntax Error: Missing '=' in FOR")
rets
endif
! Extract and validate variable name
var_name$ = TRIM(0, LEFT(for_part$, eq_pos - 1))
if LEN(var_name$) <> 1 or not INSET(var_name$, LETTERS$)
display_error("Error: Invalid variable name in FOR")
rets
endif
! Parse loop parameters
rest$ = TRIM(1, MID(for_part$, eq_pos + 1, LEN(for_part$) - eq_pos))
to_pos = FIND(rest$, "TO")
if to_pos = 0
display_error("Syntax Error: Missing 'TO' in FOR")
rets
endif
start_expr$ = TRIM(0, LEFT(rest$, to_pos - 1))
rest2$ = TRIM(1, MID(rest$, to_pos + 2, LEN(rest$) - to_pos - 1))
! Check for optional STEP clause
step_pos = FIND(rest2$, "STEP")
if step_pos > 0
end_expr$ = TRIM(0, LEFT(rest2$, step_pos - 1))
step_expr$ = TRIM(1, MID(rest2$, step_pos + 4, LEN(rest2$) - step_pos - 3))
else
end_expr$ = rest2$
step_expr$ = "1"
endif
! Evaluate expressions
start_val = eval_expr(start_expr$)
end_val = eval_expr(end_expr$)
step_val = eval_expr(step_expr$)
! Validate step value
if step_val = 0
display_error("Error: STEP value cannot be zero")
rets
endif
! Initialize loop variable
var_index = ASC(var_name$) - 65
vars[var_index] = start_val
! Validate loop body exists
if pc + 1 >= line_count
display_error("Error: No loop body after FOR")
rets
endif
! Check for maximum loop nesting
if loop_count >= 10
display_error("Error: Too many nested loops")
rets
endif
! Store loop information
loop_var$[loop_count] = var_name$
loop_end[loop_count] = end_val
loop_step[loop_count] = step_val
loop_pc[loop_count] = pc + 1
loop_count++
rets 0
! ============================================================
! NEXT COMMAND - Loop termination
! ============================================================
elseif LEFT(cmd$, 4) = "NEXT"
local next_part$, var_name$, step_val, current_val, end_val
next_part$ = TRIM(1, MID(cmd$, 5, LEN(cmd$) - 4))
! Handle explicit or implicit variable name
if LEN(next_part$) > 0
var_name$ = TRIM(0, next_part$)
! Validate loop stack
if loop_count = 0
display_error("Error: NEXT without FOR")
rets
endif
! Validate variable matches current loop
if var_name$ <> loop_var$[loop_count-1]
display_error("Error: NEXT variable doesn't match FOR")
rets
endif
else
if loop_count = 0
display_error("Error: NEXT without FOR")
rets
endif
var_name$ = loop_var$[loop_count-1]
endif
! Update loop variable
var_index = ASC(var_name$) - 65
vars[var_index] += loop_step[loop_count-1]
! Check loop condition
step_val = loop_step[loop_count-1]
current_val = vars[var_index]
end_val = loop_end[loop_count-1]
! Continue or terminate loop based on step direction
if step_val > 0
if current_val <= end_val
pc = loop_pc[loop_count-1]
rets 1
endif
else
if current_val >= end_val
pc = loop_pc[loop_count-1]
rets 1
endif
endif
! Loop finished - remove from stack
loop_count--
rets 0
! ============================================================
! WHILE COMMAND - Conditional loop start
! ============================================================
elseif LEFT(cmd$, 5) = "WHILE"
local while_condition$, condition_result, wend_found, temp_pc, depth, current_line$, space_pos, command$
while_condition$ = TRIM(1, MID(cmd$, 6, LEN(cmd$) - 5))
condition_result = eval_expr(while_condition$)
if condition_result
! Condition true - store loop information
if while_count >= 20
display_error("Error: Too many nested WHILE loops")
rets
else
while_pc[while_count] = pc
while_count++
endif
else
! Condition false - skip to matching WEND
wend_found = 0
temp_pc = pc + 1
depth = 1
! Search for matching WEND
while temp_pc < line_count and not wend_found
current_line$ = prog$[temp_pc]
space_pos = FIND(current_line$, " ")
if space_pos > 0
command$ = TRIM(1, MID(current_line$, space_pos + 1, LEN(current_line$) - space_pos))
! Handle nested WHILE loops
if LEFT(command$, 5) = "WHILE"
depth++
elseif command$ = "WEND"
depth--
! Found matching WEND
if depth = 0
wend_found = 1
pc = temp_pc + 1
endif
endif
endif
temp_pc++
endw
if not wend_found
display_error("Error: WHILE without WEND")
rets
endif
rets 1
endif
! ============================================================
! WEND COMMAND - WHILE loop end
! ============================================================
elseif cmd$ = "WEND"
if while_count <= 0
display_error("Error: WEND without WHILE")
rets
else
! Return to WHILE statement
pc = while_pc[while_count - 1]
while_count--
endif
rets 1
! ============================================================
! GOSUB COMMAND - Subroutine call
! Accepts a line number expression (e.g., GOSUB X, GOSUB X*2)
! ============================================================
elseif LEFT(cmd$, 5) = "GOSUB"
local gosub_part$, target_line
! Extract and evaluate target line expression
gosub_part$ = TRIM(1, MID(cmd$, 6, LEN(cmd$) - 5))
target_line = eval_expr(gosub_part$)
! Validate target line exists
if target_line >= 1 and target_line <= MAX_LINES
if line_map[target_line] < 0 then goto invalid_gosub_target
! Check stack overflow
if gosub_sp >= 20
display_error("Error: GOSUB stack overflow")
rets
endif
! Push return address and jump to subroutine
gosub_stack[gosub_sp] = pc + 1
gosub_sp++
pc = line_map[target_line]
rets 1
else
invalid_gosub_target#
display_error("Error: Line " + STR(target_line) + " not found")
rets
endif
! ============================================================
! RETURN COMMAND - Subroutine return
! ============================================================
elseif cmd$ = "RETURN"
if gosub_sp <= 0
display_error("Error: RETURN without GOSUB")
rets
else
! Pop return address from stack
gosub_sp--
pc = gosub_stack[gosub_sp]
rets 1
endif
! ============================================================
! LOCATE COMMAND - Set cursor position
! ============================================================
elseif LEFT(cmd$, 6) = "LOCATE"
local locate_part$, comma_pos, x_str$, y_str$
locate_part$ = TRIM(1, MID(cmd$, 7, LEN(cmd$) - 6))
comma_pos = FIND(locate_part$, ",")
if comma_pos > 0
! Extract and validate coordinates
x_str$ = TRIM(0, LEFT(locate_part$, comma_pos - 1))
y_str$ = TRIM(0, MID(locate_part$, comma_pos + 1, LEN(locate_part$) - comma_pos))
locate eval_expr(x_str$), eval_expr(y_str$)
else
display_error("Syntax Error: LOCATE requires two parameters (X,Y)")
rets
endif
! ============================================================
! COLOR COMMAND - Set text and background colors
! ============================================================
elseif LEFT(cmd$, 5) = "COLOR"
local color_part$, comma_pos, bg_str$, fg_str$
color_part$ = TRIM(1, MID(cmd$, 6, LEN(cmd$) - 5))
comma_pos = FIND(color_part$, ",")
if comma_pos > 0
! Extract and validate color values
bg_str$ = TRIM(0, LEFT(color_part$, comma_pos - 1))
fg_str$ = TRIM(0, MID(color_part$, comma_pos + 1, LEN(color_part$) - comma_pos))
color eval_expr(bg_str$), eval_expr(fg_str$)
else
display_error("Syntax Error: COLOR requires two parameters (background,foreground)")
rets
endif
! ============================================================
! INKEY COMMAND - Keyboard input with variable assignment
! ============================================================
elseif RIGHT(cmd$, 5) = "INKEY" and FIND(cmd$, "=") > 0
local eq_pos, var_name$
eq_pos = FIND(cmd$, "=")
var_name$ = TRIM(0, LEFT(cmd$, eq_pos - 1))
! Validate variable name
if LEN(var_name$) = 1 and INSET(var_name$, LETTERS$)
inkey
var_index = ASC(var_name$) - 65
vars[var_index] = V0
else
display_error("Error: Invalid variable name for INKEY")
rets
endif
! ============================================================
! INKEY COMMAND (standalone) - Keyboard input
! ============================================================
elseif cmd$ = "INKEY"
inkey
! Store key info in variables I, J, K
vars[8] = V0
vars[9] = V1
vars[10] = V2
! ============================================================
! REM COMMAND - Comment (no operation)
! ============================================================
elseif LEFT(cmd$, 3) = "REM"
rets
! ============================================================
! INPUT COMMAND - User input
! ============================================================
elseif LEFT(cmd$, 5) = "INPUT"
local input_part$
input_part$ = TRIM(1, MID(cmd$, 6, LEN(cmd$) - 5))
process_input(input_part$)
! ============================================================
! CLS COMMAND - Clear screen
! ============================================================
elseif cmd$ = "CLS"
color 0, 7
cls
! ============================================================
! WAIT COMMAND - Delay execution
! ============================================================
elseif LEFT(cmd$, 4) = "WAIT"
local wait_part$
wait_part$ = TRIM(1, MID(cmd$, 5, LEN(cmd$) - 4))
wait VAL(wait_part$)
! ============================================================
! END COMMAND - Program termination
! ============================================================
elseif cmd$ = "END"
print
print "Program ended"
goto program_end
! ============================================================
! UNKNOWN COMMAND - Error handling
! ============================================================
else
display_error("Unknown command: " + cmd$)
goto program_end
endif
rets jump_occurred
ends
! ================================================================
! PROCESS PRINT FUNCTION
! Handles PRINT command formatting and output
! ================================================================
sub process_print(print_args$)
local dim args$[30] ! Array for print arguments
local dim sep_type[30] ! Array for separator types
local num_args = 0 ! Number of arguments
local in_string = 0 ! String mode flag
local current_arg$ = "" ! Current argument being built
local i, char$, temp$, pos, chr_result, expr$, rem_pos, depth
! Remove REM comments from print arguments, but ignore REM inside strings
rem_pos = 0
in_string = 0
for i = 0 to LEN(print_args$)-1
! Toggle string mode on quotes
if print_args$(i) = CHR(34)
in_string = 1 - in_string
endif
! If we find REM outside a string, record position and stop
if not in_string and i + 2 <= LEN(print_args$) and MID(print_args$, i, 3) = "REM"
rem_pos = i
break
endif
next
if rem_pos > 0
print_args$ = LEFT(print_args$, rem_pos - 1)
endif
print_args$ = TRIM(0, print_args$)
! Handle empty PRINT command
if LEN(print_args$) = 0 or print_args$ = "PRINT"
print
rets
endif
! Parse print arguments
i = 1
while i <= LEN(print_args$)
char$ = MID(print_args$, i, 1)
! Handle string literals
if in_string
current_arg$ = current_arg$ + char$
if char$ = CHR(34) ! Quote character
in_string = 0
endif
i++
else
! Start of string literal
if char$ = CHR(34) ! Quote character
current_arg$ = current_arg$ + char$
in_string = 1
i++
! Handle CHR() function calls
elseif MID(print_args$, i, 4) = "CHR("
! Optimized CHR() processing with character caching
pos = i + 4
depth = 1
expr$ = ""
while pos <= LEN(print_args$) and depth > 0
char$ = MID(print_args$, pos, 1)
if char$ = "("
depth++
elseif char$ = ")"
depth--
endif
if depth > 0
expr$ = expr$ + char$
endif
pos++
endw
if depth <> 0
display_error("Syntax Error: Unclosed CHR()")
rets
endif
expr$ = TRIM(0, expr$)
chr_result = eval_expr(expr$)
current_arg$ = CHR(34) + CHR(chr_result) + CHR(34)
i = pos - 1 ! Skip past CHR()
! Handle separators
elseif INSET(char$, ",;")
args$[num_args] = TRIM(0, current_arg$)
! Comma = no space, Semicolon = tab
if char$ = ","
sep_type[num_args] = 1
else
sep_type[num_args] = 2
endif
num_args++
current_arg$ = ""
i++
! Regular characters
else
current_arg$ = current_arg$ + char$
i++
endif
endif
endw
! Check for unclosed string at the end of parsing
if in_string
display_error("Syntax Error: Unclosed string in PRINT statement")
rets
endif
! Add the last argument
if current_arg$ <> ""
args$[num_args] = TRIM(0, current_arg$)
sep_type[num_args] = 0
num_args++
endif
! Process and print all arguments
for i = 0 to num_args - 1
arg$ = args$[i]
! Handle string literals (including CHR() results which are now wrapped in quotes)
if arg$(0) = CHR(34)
temp$ = MID(arg$, 2, LEN(arg$)-1)
pos = FIND(temp$, CHR(34))
if pos = 0
display_error("Syntax Error: Unclosed string")
rets
else
string_to_print$ = LEFT(temp$, pos-1)
print string_to_print$,
endif
else
! Regular expression
result = eval_expr(arg$)
print result,
endif
! Handle separators between arguments
if i < num_args - 1
if sep_type[i] = 1 ! Comma - no space
pass
elseif sep_type[i] = 2 ! Semicolon - tab
print " ",
endif
endif
next
! Handle line ending
last_char$ = RIGHT(print_args$, 1)
if not INSET(last_char$, ",;")
print
endif
ends
! ================================================================
! PROCESS INPUT FUNCTION
! Handles INPUT command with optional prompt
! ================================================================
sub process_input(input_part$)
local vars_list$[10] ! List of variables to input
local values[10] ! Numeric array for values
local i, pos, num_vars, var_index
local temp$, rest$, input_vars$, current_var$, char$, single_value$, message$ = ""
! Check for prompt message
if input_part$(0) = CHR(34)
temp$ = MID(input_part$, 2, LEN(input_part$) - 1)
pos = FIND(temp$, CHR(34))
if pos = 0
display_error("Syntax Error: Unclosed string in INPUT")
rets
else
! Extract prompt message
message$ = LEFT(temp$, pos-1)
rest$ = TRIM(1, MID(temp$, pos+1, LEN(temp$) - pos))
! Remove comma after prompt if present
if rest$(0) = "," then rest$ = TRIM(1, MID(rest$, 2, LEN(rest$) - 1))
input_vars$ = rest$
endif
else
input_vars$ = input_part$
endif
! Parse variable list
vars_list$[].ZERO
num_vars = 0
current_var$ = ""
for i = 0 to LEN(input_vars$)-1
char$ = input_vars$(i)
! Comma separates variables
if char$ = ","
if LEN(current_var$) > 0
vars_list$[num_vars] = TRIM(0, current_var$)
num_vars++
current_var$ = ""
endif
else
current_var$ = current_var$ + char$
endif
next
! Add last variable
if LEN(current_var$) > 0
vars_list$[num_vars] = TRIM(0, current_var$)
num_vars++
endif
! Validate variables
if num_vars = 0
display_error("Syntax Error: No variables in INPUT")
rets
else
! Display prompt if provided
if message$ <> "" then print message$,
! Single variable input
if num_vars = 1
input single_value$
! Validate and store variable
var_name$ = TRIM(0, vars_list$[0])
if LEN(var_name$) = 1 and INSET(var_name$, LETTERS$)
var_index = ASC(var_name$) - 65
vars[var_index] = VAL(single_value$)
else
display_error("Error: Invalid variable name '" + var_name$ + "'")
rets
endif
! Multiple variable input
else
input input_values$
values[].ZERO
num_values = 0
current_val$ = ""
! Parse input values
for i = 1 to LEN(input_values$)
char$ = MID(input_values$, i, 1)
if char$ = ","
if LEN(current_val$) > 0
values[num_values] = VAL(TRIM(0, current_val$))
num_values++
current_val$ = ""
endif
else
current_val$ = current_val$ + char$
endif
next
! Add last value
if LEN(current_val$) > 0
values[num_values] = VAL(TRIM(0, current_val$))
num_values++
endif
! Validate value count matches variable count
if num_values <> num_vars
display_error("Error: Incorrect number of values. Expected " + STR(num_vars) + " but got " + STR(num_values))
rets
else
! Store values in variables
for i = 0 to num_vars - 1
var_name$ = TRIM(0, vars_list$[i])
if LEN(var_name$) = 1 and INSET(var_name$, LETTERS$)
var_index = ASC(var_name$) - 65
vars[var_index] = values[i]
else
display_error("Error: Invalid variable name '" + var_name$ + "'")
rets
endif
next
endif
endif
endif
ends
! ================================================================
! EXPRESSION EVALUATION FUNCTIONS
! Recursive descent parser for mathematical expressions
! ================================================================
! Main expression evaluator
sub eval_expr(expr$)
local result
expr$ = TRIM(0, expr$)
result = 0
or_expr(byref result, byref expr$)
rets result
ends
! OR expression handling
sub or_expr(result, expr$)
local temp
and_expr(byref result, byref expr$)
while LEN(expr$) > 0
expr$ = TRIM(0, expr$)
if LEFT(expr$, 2) = "OR"
expr$ = MID(expr$, 3, LEN(expr$) - 2)
temp = 0
and_expr(byref temp, byref expr$)
result = result or temp
else
break
endif
endw
ends
! AND expression handling
sub and_expr(result, expr$)
local temp
comp_expr(byref result, byref expr$)
while LEN(expr$) > 0
expr$ = TRIM(0, expr$)
if LEFT(expr$, 3) = "AND"
expr$ = MID(expr$, 4, LEN(expr$) - 3)
temp = 0
comp_expr(byref temp, byref expr$)
result = result and temp
else
break
endif
endw
ends
! Comparison operators (=, <>, <, >, <=, >=)
sub comp_expr(result, expr$)
local temp, op_found$, op_pos, op_len
add_sub(byref result, byref expr$)
while LEN(expr$) > 0
expr$ = TRIM(0, expr$)
op_found$ = ""
op_pos = 0
! Identify comparison operator
if LEFT(expr$, 2) = "<>"
op_found$ = "<>"
op_len = 2
elseif LEFT(expr$, 2) = "<="
op_found$ = "<="
op_len = 2
elseif LEFT(expr$, 2) = ">="
op_found$ = ">="
op_len = 2
elseif expr$(0) = "<"
op_found$ = "<"
op_len = 1
elseif expr$(0) = ">"
op_found$ = ">"
op_len = 1
elseif expr$(0) = "="
op_found$ = "="
op_len = 1
endif
! Apply comparison if operator found
if op_found$ <> ""
expr$ = MID(expr$, op_len + 1, LEN(expr$) - op_len)
temp = 0
add_sub(byref temp, byref expr$)
if op_found$ = "<"
result = result < temp
elseif op_found$ = ">"
result = result > temp
elseif op_found$ = "="
result = result = temp
elseif op_found$ = "<>"
result = result <> temp
elseif op_found$ = "<="
result = result <= temp
elseif op_found$ = ">="
result = result >= temp
endif
else
break
endif
endw
ends
! Addition and subtraction
sub add_sub(result, expr$)
local temp
term(byref result, byref expr$)
while LEN(expr$) > 0
expr$ = TRIM(0, expr$)
if expr$(0) = "+"
expr$ = MID(expr$, 2, LEN(expr$) - 1)
temp = 0
term(byref temp, byref expr$)
result = result + temp
elseif expr$(0) = "-"
expr$ = MID(expr$, 2, LEN(expr$) - 1)
temp = 0
term(byref temp, byref expr$)
result = result - temp
else
break
endif
endw
ends
! Multiplication, division, and modulo
sub term(result, expr$)
local temp
primary(byref result, byref expr$)
while LEN(expr$) > 0
expr$ = TRIM(0, expr$)
if expr$(0) = "*"
expr$ = MID(expr$, 2, LEN(expr$) - 1)
temp = 0
primary(byref temp, byref expr$)
result = result * temp
elseif expr$(0) = "/"
expr$ = MID(expr$, 2, LEN(expr$) - 1)
temp = 0
primary(byref temp, byref expr$)
if temp <> 0
result = result / temp
else
print "Division by zero!"
fatal_error = 1
result = 0
rets
endif
elseif expr$(0) = "%"
expr$ = MID(expr$, 2, LEN(expr$) - 1)
temp = 0
primary(byref temp, byref expr$)
if temp <> 0
result = result % temp
else
print "Modulo by zero!"
fatal_error = 1
result = 0
rets
endif
else
break
endif
endw
ends
! Primary expressions (numbers, variables, parentheses)
sub primary(result, expr$)
local char$, num_str$, i, char_code, sub_expr$, temp_result, depth, pos
expr$ = TRIM(0, expr$)
! Handle parentheses
if expr$(0) = "("
depth = 1
pos = 2
! Find matching closing parenthesis
while pos <= LEN(expr$) and depth > 0
char$ = MID(expr$, pos, 1)
if char$ = "("
depth++
elseif char$ = ")"
depth--
endif
pos++
endw
if depth <> 0
display_error("Syntax Error: Unmatched parentheses")
result = 0
expr$ = ""
rets
endif
! Evaluate expression inside parentheses
sub_expr$ = MID(expr$, 2, pos - 3)
temp_result = eval_expr(sub_expr$)
result = temp_result
expr$ = MID(expr$, pos, LEN(expr$) - pos + 1)
rets
endif
! Check for RND() function call first
if LEFT(expr$, 4) = "RND("
! Find the closing parenthesis for RND - must be immediately after
if LEN(expr$) < 5 or expr$(4) <> ')'
display_error("Syntax Error: RND takes no arguments")
result = 0
expr$ = ""
rets
endif
result = RND() ! Call Hobby BASIC's built-in RND function
expr$ = MID(expr$, 6, LEN(expr$) - 5) ! Skip "RND()"
rets
endif
! Extract numbers
num_str$ = ""
i = 1
while i <= LEN(expr$)
char$ = MID(expr$, i, 1)
if not INSET(char$, DIGITS$) then break
num_str$ = num_str$ + char$
i++
endw
if LEN(num_str$) > 0
result = VAL(num_str$)
expr$ = MID(expr$, i, LEN(expr$) - i + 1)
rets
endif
! Extract variables
if LEN(expr$) > 0
char_code = ASC(expr$)
if INSET(CHR(char_code), LETTERS$)
result = vars[char_code - 65]
expr$ = MID(expr$, 2, LEN(expr$) - 1)
rets
endif
endif
! Default return if no valid expression found
result = 0
ends