#!/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"
}
# 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>" string_content "</string>"
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 " <if>"
} else if (word == "else") {
processed = processed " <else>"
} else if (word == "then") {
processed = processed " <then>"
} 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 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
}
second = pop()
first = pop()
push(first - second)
}
# Function for multiplication
function multiply() {
if (top < 1) {
print "Error: Not enough values on the stack"
return
}
push(pop() * pop())
}
# 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)
}
# 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
}
# 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
}
# 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
}
function print_top() {
if (top < 0) {
print "Error: Stack is empty"
return
}
print stack[top]
}
# Function to list all available words
function list_words() {
print "Available words:"
for (word in words) {
print word
}
}
# Add these new functions after the existing stack manipulation functions:
function rot() {
if (top < 2) {
print "Error: Not enough values on stack"
return
}
third = pop()
second = pop()
first = pop()
push(second)
push(third)
push(first)
}
function drop() {
if (top < 0) {
print "Error: Stack underflow"
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
}
function tuck() {
if (top < 1) {
print "Error: Not enough values on stack"
return
}
temp = pop()
second = pop()
push(temp)
push(second)
push(temp)
}
function roll() {
if (top < 0) {
print "Error: Stack underflow"
return
}
n = int(pop())
if (top < n) {
print "Error: Stack underflow"
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
}
n = int(pop())
if (top < n) {
print "Error: Stack underflow"
return
}
if (n < 0) return
push(stack[top - n])
}
function negate() {
if (top < 0) {
print "Error: Stack underflow"
return
}
push(-pop())
}
function abs() {
if (top < 0) {
print "Error: Stack underflow"
return
}
n = pop()
push(n < 0 ? -n : n)
}
function max() {
if (top < 1) {
print "Error: Not enough values on stack"
return
}
b = pop()
a = pop()
push(a > b ? a : b)
}
function min() {
if (top < 1) {
print "Error: Not enough values on stack"
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]
} 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
push(name) # Push variable name (address) onto stack
}
# Function to store a value in a variable
function store() {
if (top < 1) {
print "Error: Not enough values on stack"
return
}
addr = pop()
if (!(addr in variables)) {
print "Error: Invalid variable '" addr "'"
return
}
variables[addr] = pop()
}
# Function to fetch a value from a variable
function fetch() {
if (top < 0) {
print "Error: Stack underflow"
return
}
addr = pop()
if (!(addr in variables)) {
print "Error: Invalid 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]
}
# 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
# 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) {
push($i)
} else if ($i in words) {
# Only check for compile-only words when not in a definition
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
}
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 "'"
}
}
}
}
# Main loop to read commands (for interactive mode)
{
process_line()
}
# Add function to handle dot-quote
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)
}