diff options
Diffstat (limited to 'awk/forth')
-rwxr-xr-x | awk/forth/f.awk | 369 | ||||
-rwxr-xr-x | awk/forth/old/f.awk | 344 | ||||
-rw-r--r-- | awk/forth/old/test.forth | 44 | ||||
-rw-r--r-- | awk/forth/test.forth | 34 |
4 files changed, 791 insertions, 0 deletions
diff --git a/awk/forth/f.awk b/awk/forth/f.awk new file mode 100755 index 0000000..16de171 --- /dev/null +++ b/awk/forth/f.awk @@ -0,0 +1,369 @@ +#!/usr/bin/awk -f + +# 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 { + # Initialize stacks and dictionaries + stack_ptr = 0 + dict_size = 0 + + # 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." + + # State flags + compiling = 0 + current_def = "" + def_name = "" + + # If an input file isn't specified, enter REPL mode + if (ARGC == 1) { + repl() + } +} + +# Handle file input +{ + if (FILENAME ~ /\.forth$/) { + interpret($0) + } +} + +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 interpret(line) { + gsub(/\(.*\)/, "", line) # Remove everything from ( to ) + gsub(/\\.*$/, "", line) # Remove backslash comments, too + + n = split(line, words, /[ \t]+/) + + for (i = 1; i <= n; i++) { + word = words[i] + if (word == "") continue + + # print "Processing word: " word + + if (word == ":") { + compiling = 1 + i++ + def_name = words[i] + current_def = "" + continue + } + + 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 + } + + # 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 { + # 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 "'" + } +} + +function push(val) { + stack[stack_ptr++] = val +} + +function pop() { + if (stack_ptr <= 0) { + print "Error: Stack underflow" + return 0 + } + return stack[--stack_ptr] +} + +function math_add() { + if (stack_ptr < 2) { + print "Error: Stack underflow" + return + } + b = pop() + a = pop() + push(a + b) +} + +function math_sub() { + if (stack_ptr < 2) { + print "Error: Stack underflow" + return + } + b = pop() + a = pop() + push(a - b) +} + +function math_mul() { + if (stack_ptr < 2) { + print "Error: Stack underflow" + return + } + b = pop() + a = pop() + push(a * b) +} + +function math_div() { + if (stack_ptr < 2) { + print "Error: Stack underflow" + return + } + b = pop() + if (b == 0) { + print "Error: Division by zero" + return + } + a = pop() + push(int(a / b)) +} + +function stack_print() { + if (stack_ptr < 1) { + print "Error: Stack underflow" + return + } + print pop() +} + +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 stack_dup() { + if (stack_ptr < 1) { + print "Error: Stack underflow" + return + } + val = stack[stack_ptr - 1] + push(val) +} + +function stack_drop() { + if (stack_ptr < 1) { + print "Error: Stack underflow" + return + } + pop() +} + +function stack_swap() { + if (stack_ptr < 2) { + print "Error: Stack underflow" + return + } + b = pop() + a = pop() + push(b) + push(a) +} + +function stack_over() { + if (stack_ptr < 2) { + print "Error: Stack underflow" + return + } + b = pop() + a = pop() + push(a) + push(b) + push(a) +} + +function stack_rot() { + if (stack_ptr < 3) { + print "Error: Stack underflow" + return + } + c = pop() + b = pop() + a = pop() + push(b) + push(c) + push(a) +} + +function compare_eq() { + if (stack_ptr < 2) { + print "Error: Stack underflow" + return + } + b = pop() + a = pop() + push(a == b ? -1 : 0) +} + +function compare_lt() { + if (stack_ptr < 2) { + print "Error: Stack underflow" + return + } + b = pop() + a = pop() + push(a < b ? -1 : 0) +} + +function compare_gt() { + if (stack_ptr < 2) { + print "Error: Stack underflow" + return + } + b = pop() + a = pop() + push(a > b ? -1 : 0) +} + +function exit_program() { + print "Exiting program." + exit 0 +} + +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] + } + } + + # 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 + } + } + } + + # 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 diff --git a/awk/forth/old/f.awk b/awk/forth/old/f.awk new file mode 100755 index 0000000..eed9774 --- /dev/null +++ b/awk/forth/old/f.awk @@ -0,0 +1,344 @@ +#!/usr/bin/awk -f + +# Forth interpreter in AWK + +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 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[">"] = ">" + words["<"] = "<" + + # Add handlers for all words + 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" + handlers["rot"] = "rot" + handlers["drop"] = "drop" + handlers["nip"] = "nip" + handlers["tuck"] = "tuck" + handlers["roll"] = "roll" + handlers["pick"] = "pick" + handlers["negate"] = "negate" + handlers["abs"] = "abs" + handlers["max"] = "max" + handlers["min"] = "min" + handlers["mod"] = "mod" + handlers["="] = "equals" + handlers["if"] = "handle_if" + handlers["then"] = "handle_then" + handlers["else"] = "handle_else" + handlers["bye"] = "bye" + handlers["see"] = "see" + + # 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" + desc["<"] = "( n1 n2 -- flag ) Returns true if n1 is less than n2" + desc["bye"] = "( -- ) Exit the interpreter" + desc["see"] = "( -- ) Show definition of a word" + + # Initialize condition stack + cond_top = -1 + + # Mark these as compile-only words + compile_only["if"] = 1 + compile_only["then"] = 1 + compile_only["else"] = 1 +} + +# Stack operations +function push(value) { + stack[++top] = value +} + +function pop() { + if (top < 0) { + print "Error: Stack underflow" + return 0 + } + return stack[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 + } + return 1 +} + +# Binary operations +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) +} + +# Handler 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 dup() { + if (!check_stack(1)) return + push(stack[top]) +} + +function over() { + if (!check_stack(2)) return + push(stack[top - 1]) +} + +function swap() { + if (!check_stack(2)) return + temp = pop() + second = pop() + push(temp) + push(second) +} + +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] + } + 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) +} + +function min() { + if (!check_stack(2)) return + b = pop() + a = pop() + push(a < b ? a : b) +} + +function print_top() { + if (!check_stack(1)) return + print stack[top] + drop() +} + +function bye() { + exit +} + +function see(word) { + if (!(word in words)) { + print "Error: Word '" word "' not found" + return + } + if (word in desc) { + print desc[word] + } + if (word in raw_definitions) { + print ": " word " " raw_definitions[word] " ;" + } +} + +# Main processing function +function execute_word(word) { + if (word in handlers) { + handler = handlers[word] + if (handler == "bye") exit + else if (handler == "see") { + if (i + 1 <= NF) see($(++i)) + else print "Error: see requires a word name" + } + else if (handler == "add") add() + else if (handler == "subtract") subtract() + else if (handler == "multiply") multiply() + else if (handler == "divide") divide() + else if (handler == "dup") dup() + else if (handler == "over") over() + else if (handler == "swap") swap() + else if (handler == "print_top") print_top() + else if (handler == "less_than") less_than() + else if (handler == "greater_than") greater_than() + else if (handler == "rot") rot() + else if (handler == "drop") drop() + else if (handler == "nip") nip() + else if (handler == "tuck") tuck() + else if (handler == "roll") roll() + else if (handler == "pick") pick() + else if (handler == "negate") negate() + else if (handler == "abs") abs() + else if (handler == "max") max() + else if (handler == "min") min() + else if (handler == "mod") mod() + else if (handler == "equals") equals() + else if (handler == "handle_if") handle_if() + else if (handler == "handle_then") handle_then() + else if (handler == "handle_else") handle_else() + else { + print "Error: Handler '" handler "' not implemented" + return 0 + } + return 1 + } + return 0 +} + +# Process each line of input +{ + if (NF > 0) { + # Remove comments and normalize whitespace + gsub(/\(.*\)/, "") + gsub(/^[[:space:]]+/, "") + gsub(/[[:space:]]+$/, "") + gsub(/[[:space:]]+/, " ") + + # Process each token + for (i = 1; i <= NF; i++) { + if ($i ~ /^-?[0-9]+$/) { + push($i) + } else if ($i in words) { + if (!execute_word($i)) { + print "Error: Failed to execute word '" $i "'" + } + } else { + print "Error: Unknown word '" $i "'" + } + } + } +} \ No newline at end of file diff --git a/awk/forth/old/test.forth b/awk/forth/old/test.forth new file mode 100644 index 0000000..a1f4f50 --- /dev/null +++ b/awk/forth/old/test.forth @@ -0,0 +1,44 @@ +( Basic arithmetic operations ) +2 3 + . ( expect: 5 ) +10 3 - . ( expect: 7 ) +4 5 * . ( expect: 20 ) +20 4 / . ( expect: 5 ) +7 3 mod . ( expect: 1 ) + +( Stack manipulation operations ) +5 dup . . ( expect: 5 5 ) +1 2 swap . . ( expect: 2 1 ) +1 2 over . . . ( expect: 1 2 1 ) +1 2 3 rot . . . ( expect: 2 3 1 ) +1 2 3 4 2 roll . . . . ( expect: 1 3 4 2 ) +5 drop +1 2 nip . ( expect: 2 ) +1 2 tuck . . . ( expect: 2 1 2 ) + +( Comparison operations ) +5 3 > . ( expect: 1 ) +3 5 < . ( expect: 1 ) +4 4 = . ( expect: 1 ) +5 3 < . ( expect: 0 ) +3 5 > . ( expect: 0 ) +4 5 = . ( expect: 0 ) + +( Math operations ) +5 negate . ( expect: -5 ) +-7 abs . ( expect: 7 ) +5 2 max . ( expect: 5 ) +5 2 min . ( expect: 2 ) + +( Complex stack manipulations ) +1 2 3 4 5 \ Put 5 numbers on stack +3 pick . ( expect: 2 ) +2 roll . ( expect: 4 ) +. . . . ( expect: 5 3 1 ) + +( Error handling tests ) +drop drop drop drop drop \ Clear stack +drop ( expect: Error: Stack underflow ) +. ( expect: Error: Stack underflow ) +5 0 / ( expect: Error: Division by zero ) + +bye \ No newline at end of file diff --git a/awk/forth/test.forth b/awk/forth/test.forth new file mode 100644 index 0000000..daa6943 --- /dev/null +++ b/awk/forth/test.forth @@ -0,0 +1,34 @@ +\ Test arithmetic operations +10 5 + . \ Should print 15 +10 5 - . \ Should print 5 +10 5 * . \ Should print 50 +10 5 / . \ Should print 2 + +\ Test stack manipulation +1 2 3 .s \ Should show 3 values: 1 2 3 +dup . \ Should print 3 again +drop . \ Should print 2 +swap .s \ Should show 2 1 +over .s \ Should show 2 1 2 +rot .s \ Should show 1 2 3 + +\ Test comparisons +5 5 = . \ Should print -1 (true) +5 3 < . \ Should print 0 (false) +3 5 > . \ Should print 0 (false) + +\ Test conditionals within user-defined words +: test_if 10 20 if .s then ; \ Should print 1 2 (since the condition is true) +: test_else 10 5 if .s else 1 then ; \ Should print 1 (since the condition is false) + +\ Test user-defined words +: square dup * ; \ Define a word to square a number +4 square . \ Should print 16 + +: add_three 1 2 + + ; \ Define a word to add three numbers +1 2 add_three . \ Should print 6 + +\ List all words +words \ Should list all available words + +bye \ Exit the interpreter \ No newline at end of file |