diff options
Diffstat (limited to 'awk/forth/f.awk')
-rwxr-xr-x | awk/forth/f.awk | 774 |
1 files changed, 275 insertions, 499 deletions
diff --git a/awk/forth/f.awk b/awk/forth/f.awk index 23bc9f0..16de171 100755 --- a/awk/forth/f.awk +++ b/awk/forth/f.awk @@ -1,593 +1,369 @@ #!/usr/bin/awk -f -# Forth interpreter in AWK +# I wanted to implement something non-trivial using awk. +# If I was clever I wouldn’t implement forth directly in awk, +# instead I’d implement a simple virtual machine using awk, +# and then implement the forth using the virtual machine’s byte code +# ...but there is only so much brain power I can exert on such a silly project. BEGIN { - print "Welcome to the AWK Forth Interpreter!" - print "Type your commands below. Use 'bye' to quit." - # Initialize variables - top = -1 # Initialize stack pointer + # Initialize stacks and dictionaries + stack_ptr = 0 + dict_size = 0 - # Initialize the dictionary with basic words - words["+"] = "+" - words["-"] = "-" - words["*"] = "*" - words["/"] = "/" - words["dup"] = "dup" - words["over"] = "over" - words["swap"] = "swap" - words["."] = "." - words["bye"] = "bye" - words["rot"] = "rot" - words["drop"] = "drop" - words["nip"] = "nip" - words["tuck"] = "tuck" - words["roll"] = "roll" - words["pick"] = "pick" - words["negate"] = "negate" - words["abs"] = "abs" - words["max"] = "max" - words["min"] = "min" - words["mod"] = "mod" - words["="] = "=" - words["see"] = "see" - words["if"] = "if" - words["then"] = "then" - words["else"] = "else" - words[">"] = ">" - - # Add descriptions for words - desc["+"] = "( n1 n2 -- sum ) Add top two numbers" - desc["-"] = "( n1 n2 -- diff ) Subtract top number from second" - desc["*"] = "( n1 n2 -- prod ) Multiply top two numbers" - desc["/"] = "( n1 n2 -- quot ) Divide second by top" - desc["dup"] = "( n -- n n ) Duplicate top of stack" - desc["over"] = "( n1 n2 -- n1 n2 n1 ) Copy second item to top" - desc["swap"] = "( n1 n2 -- n2 n1 ) Swap top two items" - desc["rot"] = "( n1 n2 n3 -- n2 n3 n1 ) Rotate top three items" - desc["drop"] = "( n -- ) Discard top item" - desc["nip"] = "( n1 n2 -- n2 ) Remove second item" - desc["tuck"] = "( n1 n2 -- n2 n1 n2 ) Copy top item below second" - desc["roll"] = "( nk ... n1 n0 k -- nk-1 ... n1 n0 nk ) Move kth item to top" - desc["pick"] = "( nk ... n1 n0 k -- nk ... n1 n0 nk ) Copy kth item to top" - desc["negate"] = "( n -- -n ) Negate number" - desc["abs"] = "( n -- |n| ) Absolute value" - desc["max"] = "( n1 n2 -- max ) Maximum of top two numbers" - desc["min"] = "( n1 n2 -- min ) Minimum of top two numbers" - desc["mod"] = "( n1 n2 -- rem ) Remainder of n1/n2" - desc["="] = "( n1 n2 -- flag ) Test if equal, leaves 1 if true, 0 if false" - desc["if"] = "( flag -- ) Begin conditional execution" - desc["then"] = "( -- ) End conditional execution" - desc["else"] = "( -- ) Execute if previous condition was false" - desc[">"] = "( n1 n2 -- flag ) Returns true if n1 is greater than n2" - - # Add variable-related words to dictionary - words["variable"] = "variable" - words["!"] = "!" - words["@"] = "@" - words["?"] = "?" - - # Add descriptions for the new words - desc["variable"] = "( -- addr ) Create a variable" - desc["!"] = "( n addr -- ) Store n at addr" - desc["@"] = "( addr -- n ) Fetch contents of addr" - desc["?"] = "( addr -- ) Print contents of addr" - - # Initialize test-related words - words["test"] = "test" - words["testing"] = "testing" - desc["test"] = "( n1 n2 -- ) Compare values and report test result" - desc["testing"] = "( -- ) Print test description" - - # Initialize condition stack - cond_top = -1 - - # Mark these as compile-only words - compile_only["if"] = 1 - compile_only["then"] = 1 - compile_only["else"] = 1 - - # Add dot-quote to dictionary - words[".\""] = ".\"" - desc[".\""] = "( -- ) Print following string up to closing quote" - - # Handle file input - if (ARGC > 1) { - print "Loading file:", ARGV[1] - while ((getline < ARGV[1]) > 0) { - if ($0 ~ /^[[:space:]]*$/) continue # Skip empty lines - print "> " $0 - process_line() - } - close(ARGV[1]) - exit - } - - # Add to dictionary initialization - words["<"] = "<" - words[">"] = ">" + # Built-in words, and some documentation (I could use stack comments, + # but I find those sort of unintuitive) + dict["+"] = "+ : Adds the top two numbers on the stack." + dict["-"] = "- : Subtracts the top number from the second top number on the stack." + dict["*"] = "* : Multiplies the top two numbers on the stack." + dict["/"] = "/ : Divides the second top number by the top number on the stack." + dict["."] = ". : Prints the top of the stack." + dict[".s"] = ".s : Shows all values on the stack." + dict["dup"] = "dup : Duplicates the top value on the stack." + dict["drop"] = "drop : Removes the top value from the stack." + dict["swap"] = "swap : Swaps the top two values on the stack." + dict["over"] = "over : Copies the second top value to the top of the stack." + dict["rot"] = "rot : Rotates the top three values on the stack." + dict["="] = "= : Compares the top two values for equality." + dict["<"] = "< : Checks if the second top value is less than the top value." + dict[">"] = "> : Checks if the second top value is greater than the top value." + dict["bye"] = "bye : Exits the interpreter." + dict["words"] = "words : Lists all available words and their documentation." - # Add descriptions - desc["<"] = "( n1 n2 -- flag ) Returns true if n1 is less than n2" - desc[">"] = "( n1 n2 -- flag ) Returns true if n1 is greater than n2" - - # Add handlers - handlers["+" ] = "add" - handlers["-" ] = "subtract" - handlers["*" ] = "multiply" - handlers["/" ] = "divide" - handlers["dup"] = "dup" - handlers["over"] = "over" - handlers["swap"] = "swap" - handlers["."] = "print_top" - handlers["<"] = "less_than" - handlers[">"] = "greater_than" - - # Add variable handler - handlers["variable"] = "create_variable" - handlers["!"] = "store" - handlers["@"] = "fetch" - handlers["?"] = "print_var" + # State flags + compiling = 0 + current_def = "" + def_name = "" + + # If an input file isn't specified, enter REPL mode + if (ARGC == 1) { + repl() + } } -# Add these helper functions near the top -function check_stack(min_items, error_msg) { - if (top < min_items - 1) { - print error_msg ? error_msg : "Error: Not enough values on stack" - return 0 +# Handle file input +{ + if (FILENAME ~ /\.forth$/) { + interpret($0) } - return 1 } -function check_underflow() { - return check_stack(1, "Error: Stack underflow") +function repl() { + print "f.awk! A forth interpreter.\nUse 'bye' to exit.\nUse 'words' to list all available words.\n" + while (1) { + printf "f> " + if (getline input < "/dev/tty" <= 0) break + interpret(input) + } } -# Function to define a new word -function define_word(name, definition) { - # Store the raw definition - raw_definitions[name] = definition - print "DEBUG: Raw definition for " name ": " definition - - # Process the definition to handle conditionals - processed = process_definition(definition) - print "DEBUG: Processed definition for " name ": " processed - words[name] = processed -} +function interpret(line) { + gsub(/\(.*\)/, "", line) # Remove everything from ( to ) + gsub(/\\.*$/, "", line) # Remove backslash comments, too -function process_definition(definition) { - # Normalize whitespace in definition - gsub(/^[[:space:]]+/, "", definition) - gsub(/[[:space:]]+$/, "", definition) - gsub(/[[:space:]]+/, " ", definition) - - # Split definition into words - split(definition, def_words, " ") - processed = "" - in_string = 0 - string_content = "" + n = split(line, words, /[ \t]+/) - for (i = 1; i <= length(def_words); i++) { - word = def_words[i] + for (i = 1; i <= n; i++) { + word = words[i] + if (word == "") continue + + # print "Processing word: " word - if (word == ".\"") { - # Start collecting string - in_string = 1 - string_content = "" + if (word == ":") { + compiling = 1 + i++ + def_name = words[i] + current_def = "" continue } - if (in_string) { - # Check if this word contains the closing quote - if (word ~ /"$/) { - # End of string found - string_content = string_content (string_content == "" ? "" : " ") substr(word, 1, length(word)-1) - processed = processed " <string>" string_content "</string>" - in_string = 0 - } else { - # Add to string content - string_content = string_content (string_content == "" ? "" : " ") word + if (compiling) { + if (word == ";") { + # Store user-defined word with its name and definition + dict[def_name] = "word " current_def + compiling = 0 + continue } + current_def = current_def " " word continue } - # Handle non-string words - if (word == "if") { - processed = processed " <if>" - } else if (word == "else") { - processed = processed " <else>" - } else if (word == "then") { - processed = processed " <then>" + # Execute the word and skip further processing if it's .s + if (word == ".s") { + execute_word(word) + break # Exit the loop after executing .s + } + + execute_word(word) + } +} + +function execute_word(word) { + if (word ~ /^-?[0-9]+$/) { + push(word + 0) + } else if (word in dict) { + if (dict[word] ~ /^word /) { + # User-defined word + sequence = substr(dict[word], 6) + split(sequence, subwords, " ") + for (sw in subwords) { + if (subwords[sw] != "") { + execute_word(subwords[sw]) + } + } } else { - processed = processed " " word + # Built-in words + if (word == "+") math_add() + else if (word == "-") math_sub() + else if (word == "*") math_mul() + else if (word == "/") math_div() + else if (word == ".") stack_print() + else if (word == ".s") { + # print "Executing .s command" + stack_show() + } + else if (word == "dup") stack_dup() + else if (word == "drop") stack_drop() + else if (word == "swap") stack_swap() + else if (word == "over") stack_over() + else if (word == "rot") stack_rot() + else if (word == "=") compare_eq() + else if (word == "<") compare_lt() + else if (word == ">") compare_gt() + else if (word == "bye") exit_program() + else if (word == "words") list_words() + else if (word == "if") { + # Handle the if statement + if_condition = pop() + if (if_condition == 0) { + # Skip to the next part until we find 'then' or 'else' + skip_if = 1 + } + } + else if (word == "else") { + # Handle the else statement + if (skip_if) { + skip_if = 0 # Reset the skip flag + } else { + # Skip to the next part until we find 'then' + skip_else = 1 + } + } + else if (word == "then") { + # End of the conditional + skip_if = 0 + skip_else = 0 + } } + } else { + print "Error: Unknown word '" word "'" } - - return processed } -# Stack to hold values -function push(value) { - stack[++top] = value +function push(val) { + stack[stack_ptr++] = val } function pop() { - if (top < 0) { + if (stack_ptr <= 0) { print "Error: Stack underflow" return 0 } - return stack[top--] + return stack[--stack_ptr] } -function binary_op(operation) { - if (!check_stack(2)) return - second = pop() - first = pop() - if (operation == "+") push(first + second) - else if (operation == "-") push(first - second) - else if (operation == "*") push(first * second) - else if (operation == "/") { - if (second == 0) { - print "Error: Division by zero" - push(first) - push(second) - return - } - push(first / second) - } - else if (operation == "mod") push(first % second) - else if (operation == "=") push(first == second ? 1 : 0) - else if (operation == "<") push(first < second ? 1 : 0) - else if (operation == ">") push(first > second ? 1 : 0) -} - -# Then simplify the operation functions: -function add() { binary_op("+") } -function subtract() { binary_op("-") } -function multiply() { binary_op("*") } -function divide() { binary_op("/") } -function mod() { binary_op("mod") } -function equals() { binary_op("=") } -function less_than() { binary_op("<") } -function greater_than() { binary_op(">") } - -# Function to duplicate the top value -function dup() { - if (!check_stack(1)) return - push(stack[top]) -} - -# Function to copy the second value to the top -function over() { - if (!check_stack(2)) return - push(stack[top - 1]) -} - -# Function to swap the top two values -function swap() { - if (!check_stack(2)) return - temp = pop() - second = pop() - push(temp) - push(second) -} - -function print_top() { - if (!check_stack(1)) return - print stack[top] -} - -# Function to list all available words -function list_words() { - print "Available words:" - for (word in words) { - print word +function math_add() { + if (stack_ptr < 2) { + print "Error: Stack underflow" + return } + b = pop() + a = pop() + push(a + b) } -function rot() { - if (!check_stack(3)) return - third = pop() - second = pop() - first = pop() - push(second) - push(third) - push(first) -} - -function drop() { - if (!check_stack(1)) return - top-- -} - -function nip() { - if (!check_stack(2)) return - temp = stack[top] - drop() - drop() - push(temp) -} - -function tuck() { - if (!check_stack(2)) return - temp = pop() - second = pop() - push(temp) - push(second) - push(temp) -} - -function roll() { - if (!check_stack(1)) return - n = int(pop()) - if (!check_stack(n)) return - if (n <= 0) return - - temp = stack[top - n + 1] - for (i = top - n + 1; i < top; i++) { - stack[i] = stack[i + 1] +function math_sub() { + if (stack_ptr < 2) { + print "Error: Stack underflow" + return } - stack[top] = temp -} - -function pick() { - if (!check_stack(1)) return - n = int(pop()) - if (!check_stack(n)) return - if (n < 0) return - push(stack[top - n]) -} - -function negate() { - if (!check_stack(1)) return - push(-pop()) -} - -function abs() { - if (!check_stack(1)) return - n = pop() - push(n < 0 ? -n : n) -} - -function max() { - if (!check_stack(2)) return b = pop() a = pop() - push(a > b ? a : b) + push(a - b) } -function min() { - if (!check_stack(2)) return +function math_mul() { + if (stack_ptr < 2) { + print "Error: Stack underflow" + return + } b = pop() a = pop() - push(a < b ? a : b) + push(a * b) } -function see(word) { - if (word in desc) { - print desc[word] - } else if (word in words) { - print ": " word " " words[word] " ;" - } else { - print "Word '" word "' not found" +function math_div() { + if (stack_ptr < 2) { + print "Error: Stack underflow" + return } -} - -# Add these new functions for variable handling: - -# Function to create a new variable -function create_variable(name) { - if (name == "") { - print "Error: Variable name required" + b = pop() + if (b == 0) { + print "Error: Division by zero" return } - variables[name] = 0 # Initialize variable to 0 - # No need to push the variable name onto the stack + a = pop() + push(int(a / b)) } -# Function to store a value in a variable -function check_variable(addr, error_msg) { - if (!(addr in variables)) { - print error_msg ? error_msg : "Error: Invalid variable '" addr "'" - return 0 +function stack_print() { + if (stack_ptr < 1) { + print "Error: Stack underflow" + return } - return 1 + print pop() } -function store() { - if (!check_stack(2)) return - addr = pop() - if (!check_variable(addr)) return - variables[addr] = pop() -} - -# Function to fetch a value from a variable -function fetch() { - if (!check_stack(1)) return - addr = pop() - if (!check_variable(addr)) return - push(variables[addr]) +function stack_show() { + print "<", stack_ptr, "> " + for (i = 0; i < stack_ptr; i++) { + printf "%s ", stack[i] + } + print "" + # print "Stack state after .s: " + # for (i = 0; i < stack_ptr; i++) { + # print stack[i] + # } + # print "" } -# Function to print a variable's value -function print_var() { - if (top < 0) { +function stack_dup() { + if (stack_ptr < 1) { print "Error: Stack underflow" return } - addr = pop() - if (!(addr in variables)) { - print "Error: Invalid variable '" addr "'" + val = stack[stack_ptr - 1] + push(val) +} + +function stack_drop() { + if (stack_ptr < 1) { + print "Error: Stack underflow" return } - print variables[addr] + pop() } -# Add these test-related functions: -function run_test(expected) { - if (top < 0) { - print "❌ FAIL: Stack is empty" +function stack_swap() { + if (stack_ptr < 2) { + print "Error: Stack underflow" return } - actual = pop() - if (actual == expected) { - print "✓ PASS" - } else { - print "❌ FAIL: Expected", expected, "but got", actual - } + b = pop() + a = pop() + push(b) + push(a) } -function print_test_description() { - if (NF < 2) { - print "Error: testing requires a description" +function stack_over() { + if (stack_ptr < 2) { + print "Error: Stack underflow" return } - # Collect all words after 'testing' as the description - test_desc = "" - for (i = 2; i <= NF; i++) { - test_desc = test_desc " " $i - } - print "\nTesting:" test_desc + b = pop() + a = pop() + push(a) + push(b) + push(a) } -# Add these new functions for conditional handling: - -# Function to handle if statement -function handle_if() { - if (top < 0) { +function stack_rot() { + if (stack_ptr < 3) { print "Error: Stack underflow" return } - # Save condition without consuming it - cond_stack[++cond_top] = (stack[top] != 0) + c = pop() + b = pop() + a = pop() + push(b) + push(c) + push(a) } -# Function to handle then statement -function handle_then() { - if (cond_top < 0) { - print "Error: Unmatched then" +function compare_eq() { + if (stack_ptr < 2) { + print "Error: Stack underflow" return } - cond_top-- # Pop condition + b = pop() + a = pop() + push(a == b ? -1 : 0) } -# Function to handle else statement -function handle_else() { - if (cond_top < 0) { - print "Error: Unmatched else" +function compare_lt() { + if (stack_ptr < 2) { + print "Error: Stack underflow" return } - cond_stack[cond_top] = !cond_stack[cond_top] + b = pop() + a = pop() + push(a < b ? -1 : 0) } -# Add this function -function execute_word(word) { - if (word in handlers) { - if (handlers[word] == "add") add() - else if (handlers[word] == "subtract") subtract() - else if (handlers[word] == "multiply") multiply() - else if (handlers[word] == "divide") divide() - else if (handlers[word] == "dup") dup() - else if (handlers[word] == "over") over() - else if (handlers[word] == "swap") swap() - else if (handlers[word] == "print_top") print_top() - else if (handlers[word] == "create_variable") { - # Special handling for variable creation - if (i + 1 <= NF) { - create_variable($(++i)) - } else { - print "Error: variable requires a name" - } - } - else if (handlers[word] == "store") store() - else if (handlers[word] == "fetch") fetch() - else if (handlers[word] == "print_var") print_var() - else { - print "Error: Handler '" handlers[word] "' not implemented" - return 0 - } - return 1 +function compare_gt() { + if (stack_ptr < 2) { + print "Error: Stack underflow" + return } - return 0 + b = pop() + a = pop() + push(a > b ? -1 : 0) } -# Move the main command processing into a function -function process_line() { - # Process input only if it's not empty - if (NF > 0) { - # Remove comments and normalize whitespace - gsub(/\(.*\)/, "") # Remove everything from ( to ) - gsub(/^[[:space:]]+/, "") # Remove leading whitespace - gsub(/[[:space:]]+$/, "") # Remove trailing whitespace - gsub(/[[:space:]]+/, " ") # Normalize internal whitespace - - # Special handling for testing command - if ($1 == "testing") { - # Collect the description string - if ($2 ~ /^".*"$/) { # Check if the second token is a quoted string - test_desc = substr($2, 2, length($2) - 2) # Remove quotes - print "\nTesting: " test_desc - } else { - print "Error: testing requires a description in quotes" - } - return - } +function exit_program() { + print "Exiting program." + exit 0 +} - # Check for word definitions - if ($1 == ":") { - word_name = $2 - definition = "" - in_definition = 1 - # Remove : and word name from the input - for (i = 3; i <= NF; i++) { - definition = definition " " $i - } - # If we don't find a semicolon, keep reading lines - while (definition !~ /;$/) { # Changed to match semicolon at end - if ((getline) <= 0) break - gsub(/\(.*\)/, "") # Remove comments - gsub(/^[[:space:]]+/, "") - gsub(/[[:space:]]+$/, "") - definition = definition " " $0 - } - # Remove the semicolon - sub(/[[:space:]]*;[[:space:]]*$/, "", definition) - - if (definition != "") { - define_word(word_name, definition) - print "Defined new word: " word_name - } - in_definition = 0 - return +function list_words() { + print "Available words:" + + # Separate arrays to hold built-in and user-defined words + split("", built_in_words) + split("", user_defined_words) + + for (w in dict) { + split(dict[w], parts, ": ") + if (parts[1] ~ /^word /) { + user_defined_words[w] = parts[2] + } else { + built_in_words[w] = parts[2] } - - # Process remaining input - for (i = 1; i <= NF; i++) { - if ($i ~ /^-?[0-9]+$/) { - push($i) - } else if ($i in variables) { # Check if it's a variable - push(variables[$i]) # Push the value of the variable onto the stack - } else if ($i in words) { # Check if it's a defined word - if (!in_definition && compile_only[$i]) { - print "Error: '" $i "' is compile-only" - return - } - if (!execute_word($i)) { - print "Error: Unknown command '" $i "'" - } - } else if ($i != "") { # Ignore empty tokens - print "Error: Unknown command '" $i "'" + } + + # Sort built-in words manually because I'm picky + n = 0 + for (w in built_in_words) { + sorted_words[n++] = w + } + + for (i = 0; i < n; i++) { + for (j = i + 1; j < n; j++) { + if (sorted_words[i] > sorted_words[j]) { + temp = sorted_words[i] + sorted_words[i] = sorted_words[j] + sorted_words[j] = temp } } } -} - -# Main loop to read commands (for interactive mode) -{ - process_line() -} - -# Add function to handle dot-quote -function print_string(str) { - printf "%s", str -} + + # First print the built-in words + for (i = 0; i < n; i++) { + print sorted_words[i] ": " built_in_words[sorted_words[i]] + } + + # Then print the user-defined words + for (w in user_defined_words) { + print w ": " user_defined_words[w] " ( User-defined )" + } +} \ No newline at end of file |