#!/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[">"] = ">"
# 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[">"] = ">"
# 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
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 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 = ""
for (i = 1; i <= length(def_words); i++) {
word = def_words[i]
if (word == ".\"") {
# Start collecting string
in_string = 1
string_content = ""
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_content ""
in_string = 0
} else {
# Add to string content
string_content = string_content (string_content == "" ? "" : " ") word
}
continue
}
# Handle non-string words
if (word == "if") {
processed = processed " "
} else if (word == "else") {
processed = processed " "
} else if (word == "then") {
processed = processed " "
} else {
processed = processed " " word
}
}
return processed
}
# Stack to hold values
function push(value) {
stack[++top] = value
}
function pop() {
if (top < 0) {
print "Error: Stack underflow"
return 0
}
return stack[top--]
}
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 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 see(word) {
if (word in desc) {
print desc[word]
} else if (word in words) {
print ": " word " " words[word] " ;"
} else {
print "Word '" word "' not found"
}
}
# Add these new functions for variable handling:
# Function to create a new variable
function create_variable(name) {
if (name == "") {
print "Error: Variable name required"
return
}
variables[name] = 0 # Initialize variable to 0
# No need to push the variable name onto the stack
}
# 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
}
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 (!check_stack(1)) return
addr = pop()
if (!check_variable(addr)) return
push(variables[addr])
}
# Function to print a variable's value
function print_var() {
if (top < 0) {
print "Error: Stack underflow"
return
}
addr = pop()
if (!(addr in variables)) {
print "Error: Invalid variable '" addr "'"
return
}
print variables[addr]
}
# Add these test-related functions:
function run_test(expected) {
if (top < 0) {
print "❌ FAIL: Stack is empty"
return
}
actual = pop()
if (actual == expected) {
print "✓ PASS"
} else {
print "❌ FAIL: Expected", expected, "but got", actual
}
}
function print_test_description() {
if (NF < 2) {
print "Error: testing requires a description"
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
}
# Add these new functions for conditional handling:
# Function to handle if statement
function handle_if() {
if (top < 0) {
print "Error: Stack underflow"
return
}
# Save condition without consuming it
cond_stack[++cond_top] = (stack[top] != 0)
}
# Function to handle then statement
function handle_then() {
if (cond_top < 0) {
print "Error: Unmatched then"
return
}
cond_top-- # Pop condition
}
# Function to handle else statement
function handle_else() {
if (cond_top < 0) {
print "Error: Unmatched else"
return
}
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
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
}
# 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
}
# 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 "'"
}
}
}
}
# Main loop to read commands (for interactive mode)
{
process_line()
}
# Add function to handle dot-quote
function print_string(str) {
printf "%s", str
}