diff options
author | elioat <{ID}+{username}@users.noreply.github.com> | 2025-01-02 09:19:14 -0500 |
---|---|---|
committer | elioat <{ID}+{username}@users.noreply.github.com> | 2025-01-02 09:19:14 -0500 |
commit | fbb6bb910bd7d8bddb52c06cfe72b97d44bb2813 (patch) | |
tree | 24c566bad1c6a0c4c6b095fce1e76cb8a9720f15 | |
parent | 2c154a34f478285680efeee7c15ba02116ee882d (diff) | |
download | tour-fbb6bb910bd7d8bddb52c06cfe72b97d44bb2813.tar.gz |
*
-rwxr-xr-x | awk/forth/f.awk | 504 | ||||
-rw-r--r-- | awk/forth/test.forth | 58 |
2 files changed, 173 insertions, 389 deletions
diff --git a/awk/forth/f.awk b/awk/forth/f.awk index dd8baf4..23bc9f0 100755 --- a/awk/forth/f.awk +++ b/awk/forth/f.awk @@ -110,6 +110,37 @@ BEGIN { # 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" +} + +# 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 + } + return 1 +} + +function check_underflow() { + return check_stack(1, "Error: Stack underflow") } # Function to define a new word @@ -188,84 +219,61 @@ function pop() { return stack[top--] } -function add() { - if (top < 1) { - print "Error: Not enough values on the stack" - return - } - push(pop() + pop()) -} - -# Function for subtraction -function subtract() { - if (top < 1) { - print "Error: Not enough values on the stack" - return - } +function binary_op(operation) { + if (!check_stack(2)) return second = pop() first = pop() - push(first - second) -} - -# Function for multiplication -function multiply() { - if (top < 1) { - print "Error: Not enough values on the stack" - return + 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) } - push(pop() * pop()) + 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) } -# Function for division -function divide() { - if (top < 1) { - print "Error: Not enough values on the stack" - return - } - second = pop() - if (second == 0) { - print "Error: Division by zero" - return - } - first = pop() - push(first / second) -} +# 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 (top < 0) { - print "Error: Stack is empty" - return - } - push(stack[top]) # Push the top value again + if (!check_stack(1)) return + push(stack[top]) } # Function to copy the second value to the top function over() { - if (top < 1) { - print "Error: Not enough values on the stack" - return - } - push(stack[top - 1]) # Push the second value + if (!check_stack(2)) return + push(stack[top - 1]) } # Function to swap the top two values function swap() { - if (top < 1) { - print "Error: Not enough values on the stack" - return - } - temp = pop() # Pop the top value - second = pop() # Pop the second value - push(temp) # Push the first value back - push(second) # Push the second value back + if (!check_stack(2)) return + temp = pop() + second = pop() + push(temp) + push(second) } function print_top() { - if (top < 0) { - print "Error: Stack is empty" - return - } + if (!check_stack(1)) return print stack[top] } @@ -277,13 +285,8 @@ function list_words() { } } -# Add these new functions after the existing stack manipulation functions: - function rot() { - if (top < 2) { - print "Error: Not enough values on stack" - return - } + if (!check_stack(3)) return third = pop() second = pop() first = pop() @@ -293,29 +296,20 @@ function rot() { } function drop() { - if (top < 0) { - print "Error: Stack underflow" - return - } + if (!check_stack(1)) return top-- } function nip() { - if (top < 1) { - print "Error: Not enough values on stack" - return - } - temp = stack[top] # Save top value - drop() # Remove top value - drop() # Remove second value - push(temp) # Put original top value back + if (!check_stack(2)) return + temp = stack[top] + drop() + drop() + push(temp) } function tuck() { - if (top < 1) { - print "Error: Not enough values on stack" - return - } + if (!check_stack(2)) return temp = pop() second = pop() push(temp) @@ -324,104 +318,51 @@ function tuck() { } function roll() { - if (top < 0) { - print "Error: Stack underflow" - return - } + if (!check_stack(1)) return n = int(pop()) - if (top < n) { - print "Error: Stack underflow" - return - } + if (!check_stack(n)) return if (n <= 0) return - # Store the item to be moved temp = stack[top - n + 1] - - # Shift the other items down for (i = top - n + 1; i < top; i++) { stack[i] = stack[i + 1] } - - # Put the rolled item on top stack[top] = temp } function pick() { - if (top < 0) { - print "Error: Stack underflow" - return - } + if (!check_stack(1)) return n = int(pop()) - if (top < n) { - print "Error: Stack underflow" - return - } + if (!check_stack(n)) return if (n < 0) return - push(stack[top - n]) } function negate() { - if (top < 0) { - print "Error: Stack underflow" - return - } + if (!check_stack(1)) return push(-pop()) } function abs() { - if (top < 0) { - print "Error: Stack underflow" - return - } + if (!check_stack(1)) return n = pop() push(n < 0 ? -n : n) } function max() { - if (top < 1) { - print "Error: Not enough values on stack" - return - } + if (!check_stack(2)) return b = pop() a = pop() push(a > b ? a : b) } function min() { - if (top < 1) { - print "Error: Not enough values on stack" - return - } + if (!check_stack(2)) return b = pop() a = pop() push(a < b ? a : b) } -function mod() { - if (top < 1) { - print "Error: Not enough values on stack" - return - } - b = pop() - if (b == 0) { - print "Error: Division by zero" - push(b) - return - } - a = pop() - push(a % b) -} - -function equals() { - if (top < 1) { - print "Error: Not enough values on stack" - return - } - push((pop() == pop()) ? 1 : 0) -} - function see(word) { if (word in desc) { print desc[word] @@ -441,34 +382,30 @@ function create_variable(name) { return } variables[name] = 0 # Initialize variable to 0 - push(name) # Push variable name (address) onto stack + # No need to push the variable name onto the stack } # Function to store a value in a variable -function store() { - if (top < 1) { - print "Error: Not enough values on stack" - return - } - addr = pop() +function check_variable(addr, error_msg) { if (!(addr in variables)) { - print "Error: Invalid variable '" addr "'" - return + print error_msg ? error_msg : "Error: Invalid variable '" addr "'" + return 0 } + return 1 +} + +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 (top < 0) { - print "Error: Stack underflow" - return - } + if (!check_stack(1)) return addr = pop() - if (!(addr in variables)) { - print "Error: Invalid variable '" addr "'" - return - } + if (!check_variable(addr)) return push(variables[addr]) } @@ -543,6 +480,37 @@ function handle_else() { cond_stack[cond_top] = !cond_stack[cond_top] } +# 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 + } + return 0 +} + # Move the main command processing into a function function process_line() { # Process input only if it's not empty @@ -553,6 +521,18 @@ function process_line() { 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 + } + # Check for word definitions if ($1 == ":") { word_name = $2 @@ -585,190 +565,16 @@ function process_line() { for (i = 1; i <= NF; i++) { if ($i ~ /^-?[0-9]+$/) { push($i) - } else if ($i in variables) { - push($i) - } else if ($i in words) { - # Only check for compile-only words when not in a definition + } 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 the word is defined, execute its definition - if (words[$i] == "+") add() - else if (words[$i] == "-") subtract() - else if (words[$i] == "*") multiply() - else if (words[$i] == "/") divide() - else if (words[$i] == "dup") dup() - else if (words[$i] == "over") over() - else if (words[$i] == "swap") swap() - else if (words[$i] == ".") print_top() - else if (words[$i] == "bye") exit - else if (words[$i] == "words") list_words() - else if (words[$i] == "rot") rot() - else if (words[$i] == "drop") drop() - else if (words[$i] == "nip") nip() - else if (words[$i] == "tuck") tuck() - else if (words[$i] == "roll") roll() - else if (words[$i] == "pick") pick() - else if (words[$i] == "negate") negate() - else if (words[$i] == "abs") abs() - else if (words[$i] == "max") max() - else if (words[$i] == "min") min() - else if (words[$i] == "mod") mod() - else if (words[$i] == "=") equals() - else if (words[$i] == "see") { - if (i + 1 <= NF) { - see($(i + 1)) - i++ # Skip the word name - } else { - print "Error: see requires a word name" - } - } - else if (words[$i] == "variable") { - if (i + 1 <= NF) { - create_variable($(i + 1)) - i++ # Skip the variable name - } else { - print "Error: variable requires a name" - } - } - else if (words[$i] == "!") store() - else if (words[$i] == "@") fetch() - else if (words[$i] == "?") print_var() - else if (words[$i] == "test") { - if (i + 1 <= NF) { - run_test($(i + 1)) - i++ # Skip the expected value - } else { - print "Error: test requires an expected value" - } - } - else if (words[$i] == "testing") { - print_test_description() - return + if (!execute_word($i)) { + print "Error: Unknown command '" $i "'" } - else if (words[$i] == "if") { - handle_if() - } - else if (words[$i] == "then") { - handle_then() - } - else if (words[$i] == "else") { - handle_else() - } - else if (words[$i] == "<") less_than() - else if (words[$i] == ">") greater_than() - else { - # Execute user-defined word - split(words[$i], cmd, " ") - for (j = 1; j <= length(cmd); j++) { - command = cmd[j] - if (command ~ /^-?[0-9]+$/) { # Modified for negative numbers - push(command) - } else if (command in variables) { # Check for variables - push(command) - } else if (command in words) { - # Recursive execution of defined words - if (words[command] == "+") add() - else if (words[command] == "-") subtract() - else if (words[command] == "*") multiply() - else if (words[command] == "/") divide() - else if (words[command] == "dup") dup() - else if (words[command] == "over") over() - else if (words[command] == "swap") swap() - else if (words[command] == ".") print_top() - else if (words[command] == "bye") exit - else if (words[command] == "words") list_words() - else if (words[command] == "!") store() - else if (words[command] == "@") fetch() - else if (words[command] == "?") print_var() - else if (words[command] == "see") { - if (i + 1 <= NF) { - see($(i + 1)) - i++ # Skip the word name - } else { - print "Error: see requires a word name" - } - } - else if (command == "<if>") { - handle_if() - if (!cond_stack[cond_top]) { - drop() # Now consume the condition value - # Skip until matching then/else - skip_level = 1 - while (skip_level > 0 && j < length(cmd)) { - j++ - if (j >= length(cmd)) break - if (cmd[j] == "<if>") skip_level++ - else if (cmd[j] == "<then>") { - skip_level-- - if (skip_level == 0) break - } - else if (cmd[j] == "<else>" && skip_level == 1) { - skip_level = 0 - break - } - } - } else { - drop() # Consume the condition value only after checking it - } - } - else if (command == "<else>") { - handle_else() - if (cond_stack[cond_top]) { - # Skip until matching then - skip_level = 1 - while (skip_level > 0 && j < length(cmd)) { - j++ - if (j >= length(cmd)) break - if (cmd[j] == "<if>") skip_level++ - else if (cmd[j] == "<then>") { - skip_level-- - if (skip_level == 0) break - } - } - } - } - else if (command == "<then>") { - handle_then() - } - else if (command == "<string>") { - j++ - str = "" - while (j < length(cmd) && cmd[j] != "</string>") { - str = str (str == "" ? "" : " ") cmd[j] - j++ - } - if (cond_stack[cond_top] != 0) { # Only print if inside a true condition - print_string(str) - } - j++ # Skip past </string> - } - else if (words[command] == "<") less_than() - else if (words[command] == ">") greater_than() - } - } - } - } else if ($i == "+") { - add() - } else if ($i == "-") { - subtract() - } else if ($i == "*") { - multiply() - } else if ($i == "/") { - divide() - } else if ($i == "dup") { - dup() - } else if ($i == "over") { - over() - } else if ($i == "swap") { - swap() - } else if ($i == ".") { - print_top() - } else if ($i == "bye") { - exit - } else if ($i == "words") { - list_words() } else if ($i != "") { # Ignore empty tokens print "Error: Unknown command '" $i "'" } @@ -785,25 +591,3 @@ function process_line() { function print_string(str) { printf "%s", str } - -# Add these new functions for comparisons: - -function less_than() { - if (top < 1) { - print "Error: Not enough values on stack" - return - } - second = pop() - first = pop() - push(first < second ? 1 : 0) -} - -function greater_than() { - if (top < 1) { - print "Error: Not enough values on stack" - return - } - second = pop() - first = pop() - push(first > second ? 1 : 0) -} diff --git a/awk/forth/test.forth b/awk/forth/test.forth index 2d4197b..d746e66 100644 --- a/awk/forth/test.forth +++ b/awk/forth/test.forth @@ -1,84 +1,84 @@ ( Basic Forth test suite ) -testing Basic stack operations +testing "Basic stack operations" 5 dup . . test 5 test 5 -testing Addition +testing "Addition" 3 4 + test 7 -testing Subtraction +testing "Subtraction" 10 3 - test 7 -testing Multiplication +testing "Multiplication" 6 7 * test 42 -testing Division +testing "Division" 20 4 / test 5 -testing Stack manipulation - rot +testing "Stack manipulation - rot" 1 2 3 rot test 1 -testing Stack manipulation - drop +testing "Stack manipulation - drop" 1 2 3 drop test 2 -testing Stack manipulation - nip +testing "Stack manipulation - nip" 1 2 3 nip test 3 -testing Stack manipulation - tuck +testing "Stack manipulation - tuck" 1 2 tuck test 2 -testing Stack manipulation - over +testing "Stack manipulation - over" 1 2 over test 1 -testing Variables +testing "Variables" variable x 5 x ! x @ test 5 10 x ! x ? ( should print 10 ) -testing Negate +testing "Negate" 5 negate test -5 -testing Absolute value +testing "Absolute value" -7 abs test 7 -testing Maximum +testing "Maximum" 3 8 max test 8 -testing Minimum +testing "Minimum" 3 8 min test 3 -testing Modulo +testing "Modulo" 17 5 mod test 2 -testing Equality +testing "Equality" 5 5 = test 1 5 6 = test 0 -testing Word definition +testing "Word definition" : square dup * ; 5 square test 25 -testing Complex word definition +testing "Complex word definition" variable counter : increment-counter counter @ 1 + counter ! ; 5 counter ! increment-counter counter @ test 6 -testing Basic conditional - if/then +testing "Basic conditional - if/then" : test-if-1 ( n -- n ) dup 5 > if ." Greater than 5" then ; 6 test-if-1 test 6 ( should print "Greater than 5" and leave 6 ) 4 test-if-1 test 4 ( should print nothing and leave 4 ) -testing Basic conditional - if/else/then +testing "Basic conditional - if/else/then" : test-if-2 ( n -- n ) dup 5 > if ." Greater" else ." Less=" then ; 6 test-if-2 test 6 ( should print "Greater" and leave 6 ) 4 test-if-2 test 4 ( should print "Less=" and leave 4 ) 5 test-if-2 test 5 ( should print "Less=" and leave 5 ) -testing Nested conditionals +testing "Nested conditionals" : test-if-3 ( n -- n ) dup 10 > if dup 20 > if @@ -90,7 +90,7 @@ testing Nested conditionals 15 test-if-3 test 15 ( should print ">10 " and leave 15 ) 5 test-if-3 test 5 ( should print nothing and leave 5 ) -testing Conditional with stack operations +testing "Conditional with stack operations" : test-if-4 ( n -- n n ) dup 5 > if dup @@ -98,7 +98,7 @@ testing Conditional with stack operations 6 test-if-4 swap test 6 test 6 ( should leave 6 6 ) 4 test-if-4 test 4 ( should leave just 4 ) -testing Complex nested conditionals +testing "Complex nested conditionals" : test-if-5 ( n -- n ) dup 0 < if ." negative " @@ -118,12 +118,12 @@ testing Complex nested conditionals 75 test-if-5 test 75 ( should print "medium " ) 25 test-if-5 test 25 ( should print "small " ) -testing Conditionals in word definitions +testing "Conditionals in word definitions" : abs-test ( n -- |n| ) dup 0 < if negate then ; -5 abs-test test 5 5 abs-test test 5 -testing Complex conditional word +testing "Complex conditional word" : max-test ( n1 n2 -- max ) 2dup > if drop @@ -134,10 +134,10 @@ testing Complex conditional word 3 5 max-test test 5 ( Try to use if outside of a definition - should error ) -testing Compile-only words +testing "Compile-only words" 5 4 > if 42 then ( should print error about compile-only word ) -testing Comparison operators +testing "Comparison operators" 5 3 > test 1 ( 5 > 3 is true ) 3 5 > test 0 ( 3 > 5 is false ) 3 5 < test 1 ( 3 < 5 is true ) @@ -145,7 +145,7 @@ testing Comparison operators 5 5 < test 0 ( 5 < 5 is false ) 5 5 > test 0 ( 5 > 5 is false ) -testing Comparison in conditionals +testing "Comparison in conditionals" : test-compare ( n -- ) dup 5 > if ." Greater than 5" else dup 5 < if ." Less than 5" else |