blob: 52df3a29f4ed92aa472f53ca1bcafe02675fb23b (
plain) (
tree)
|
|
#!/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"
# 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"
# 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"
# 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
}
}
# Function to define a new word
function define_word(name, definition) {
words[name] = definition
}
# 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
}
# Move the main command processing into a function
function process_line() {
# Process input only if it's not empty
if (NF > 0) {
# Remove comments
gsub(/\(.*\)/, "") # Remove everything from ( to )
# Check for word definitions
if ($1 == ":") {
word_name = $2
definition = ""
for (i = 3; i <= NF; i++) {
if ($i == ";") {
define_word(word_name, definition)
print "Defined new word: " word_name
break
} else {
definition = definition " " $i
}
}
return
}
# Process remaining input
for (i = 1; i <= NF; i++) {
if ($i ~ /^-?[0-9]+$/) { # Modified to handle negative numbers
push($i) # Push numbers onto the stack
} else if ($i in variables) { # Check for variables BEFORE words
push($i) # Push variable name onto stack
} else if ($i in words) {
# 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 {
# 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 ($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()
}
|