about summary refs log blame commit diff stats
path: root/awk/forth/f.awk
blob: dd8baf4db8fc6ece97deef5c7118e7e8016e9073 (plain) (tree)
1
2
3
4
5
6
7
8
9
10



                          


                                                        


                                        
                                                





















                              



                          




















                                                                                 



                                                                         


















                                                                       











                                                                     










                                                                   







                                                                         



                                        


















                                                                

































                                                                                                                 





                                          







































































































                                                     











































































































































































































































                                                                          







                                                   

                                              
















                                     
                                                

 



                                                  
                                                  
                                                           


                                                                  




                                    

                                                   
                                       


                                                              
                                                                             




                                                     
             
                                  
                                                             





                                                    




                                   



                                         
                                     




                                                                            





















































                                                                      










                                                         































                                                                                      


                                                            
                                                                             



                                                                               


                                                                          
                                                        
                                                                      
                                         
                                                                                         
                                                          


                                                 

                                                                                                








                                                                               
                                                                   
                                                                          



                                                                      












                                                                                  


                                                                                                         



                                                                          

























                                                         


         




                                                   


























                                                 
#!/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)
}