diff options
Diffstat (limited to 'awk')
29 files changed, 2520 insertions, 5 deletions
diff --git a/awk/forth/f.awk b/awk/forth/f.awk index 73fea1e..16de171 100755 --- a/awk/forth/f.awk +++ b/awk/forth/f.awk @@ -59,7 +59,7 @@ function repl() { function interpret(line) { gsub(/\(.*\)/, "", line) # Remove everything from ( to ) - gsub(/\\.*$/, "", line) # Remove backslash comments + gsub(/\\.*$/, "", line) # Remove backslash comments, too n = split(line, words, /[ \t]+/) @@ -67,6 +67,8 @@ function interpret(line) { word = words[i] if (word == "") continue + # print "Processing word: " word + if (word == ":") { compiling = 1 i++ @@ -86,6 +88,12 @@ function interpret(line) { continue } + # Execute the word and skip further processing if it's .s + if (word == ".s") { + execute_word(word) + break # Exit the loop after executing .s + } + execute_word(word) } } @@ -110,7 +118,10 @@ function execute_word(word) { else if (word == "*") math_mul() else if (word == "/") math_div() else if (word == ".") stack_print() - else if (word == ".s") stack_show() + else if (word == ".s") { + # print "Executing .s command" + stack_show() + } else if (word == "dup") stack_dup() else if (word == "drop") stack_drop() else if (word == "swap") stack_swap() @@ -121,6 +132,28 @@ function execute_word(word) { else if (word == ">") compare_gt() else if (word == "bye") exit_program() else if (word == "words") list_words() + else if (word == "if") { + # Handle the if statement + if_condition = pop() + if (if_condition == 0) { + # Skip to the next part until we find 'then' or 'else' + skip_if = 1 + } + } + else if (word == "else") { + # Handle the else statement + if (skip_if) { + skip_if = 0 # Reset the skip flag + } else { + # Skip to the next part until we find 'then' + skip_else = 1 + } + } + else if (word == "then") { + # End of the conditional + skip_if = 0 + skip_else = 0 + } } } else { print "Error: Unknown word '" word "'" @@ -197,6 +230,11 @@ function stack_show() { printf "%s ", stack[i] } print "" + # print "Stack state after .s: " + # for (i = 0; i < stack_ptr; i++) { + # print stack[i] + # } + # print "" } function stack_dup() { diff --git a/awk/forth/test.forth b/awk/forth/test.forth index 1beedaf..daa6943 100644 --- a/awk/forth/test.forth +++ b/awk/forth/test.forth @@ -17,9 +17,9 @@ rot .s \ Should show 1 2 3 5 3 < . \ Should print 0 (false) 3 5 > . \ Should print 0 (false) -\ Test conditionals -10 20 if .s then \ Should print 1 2 (since the condition is true) -10 5 if .s else 1 then \ Should print 1 (since the condition is false) +\ Test conditionals within user-defined words +: test_if 10 20 if .s then ; \ Should print 1 2 (since the condition is true) +: test_else 10 5 if .s else 1 then ; \ Should print 1 (since the condition is false) \ Test user-defined words : square dup * ; \ Define a word to square a number diff --git a/awk/scheme/scheme/README.md b/awk/scheme/scheme/README.md new file mode 100644 index 0000000..7711442 --- /dev/null +++ b/awk/scheme/scheme/README.md @@ -0,0 +1,186 @@ +# Awk-Scheme + +## Overview +A Scheme interpreter implemented in AWK, featuring: +- A compiler that translates Scheme expressions to stack-based VM instructions +- A virtual machine that executes the compiled code +- Support for basic arithmetic, list operations, functions, and variable bindings +- Persistent global state between REPL sessions + +## Architecture + +### Components +1. **Compiler** (`bin/compiler.awk`): + - Lexical analyzer for tokenizing input + - Recursive descent parser for Scheme expressions + - Code generator that produces stack-based VM instructions + - Handles nested expressions and proper scoping + +2. **Virtual Machine** (`bin/vm.awk`): + - Stack-based execution model + - Typed value system with runtime type checking + - Environment-based variable binding + - Function call/return mechanism + - Heap memory for cons cells + - State persistence for globals and functions + +3. **REPL** (`bin/repl`): + - Interactive command line interface + - Multi-line input support + - State persistence between sessions + - Debug mode support + +### Data Types +All values are tagged with their type: +- `N:value` - Numbers (integers) +- `B:value` - Booleans (0/1) +- `P:index` - Pairs (cons cells) +- `F:name` - Functions +- `NIL:` - Empty list +- `S:name` - Symbols + +## Usage + +### Running the REPL +```bash +# Start interactive REPL +./scheme + +# Run a Scheme file +./scheme myprogram.scm + +# Enable debug output +DEBUG=1 ./scheme +``` + +### Basic Operations + +1. **Arithmetic**: +```scheme +scheme> (+ 1 2 3) +N:6 +scheme> (* 4 (- 10 5)) +N:20 +``` + +2. **Comparisons**: +```scheme +scheme> (< 1 2) +B:1 +scheme> (= 42 42) +B:1 +``` + +3. **Lists**: +```scheme +scheme> (cons 1 (cons 2 nil)) +P:1 +scheme> (car (cons 1 2)) +N:1 +``` + +4. **Variables**: +```scheme +scheme> (define x 42) +N:42 +scheme> x +N:42 +``` + +5. **Functions**: +```scheme +scheme> (define add (x y) (+ x y)) +scheme> (add 2 3) +N:5 +``` + +6. **Let Expressions**: +```scheme +scheme> (let ((x 10) (y 20)) (+ x y)) +N:30 +``` + +## Implementation Details + +### Compiler Pipeline +1. **Lexical Analysis**: + - Tokenizes input into numbers, symbols, and parentheses + - Handles whitespace and basic comments + +2. **Parsing**: + - Builds expression tree from tokens + - Validates syntax and expression structure + +3. **Code Generation**: + - Translates expressions to VM instructions + - Manages scope and variable bindings + - Handles function definitions + +### Virtual Machine +1. **Stack Operations**: + - PUSH_CONST: Push constant value + - POP: Remove top value + - STORE: Store variable binding + - LOOKUP: Retrieve variable value + +2. **Function Calls**: + - CALL: Invoke function + - RETURN: Return from function + - Environment frame management + +3. **Memory Management**: + - Stack-based evaluation + - Simple heap for cons cells + - Basic reference counting (not fully implemented) + +### State Persistence +- Global variables and functions persist between sessions +- State stored in `/tmp/scheme_vm.state` and `/tmp/scheme_vm.env` +- Automatic state loading/saving + +## Extending the System + +### Adding New Special Forms +1. Add parsing in `compile_expr()` +2. Implement code generation function +3. Add corresponding VM instructions if needed + +### Adding New Data Types +1. Define new type tag in VM +2. Add type checking predicates +3. Implement value construction/access +4. Update relevant operations + +### Adding VM Instructions +1. Add instruction handling in `execute()` +2. Implement instruction logic +3. Update compiler to generate new instruction + +### Debugging +- Enable debug output: `DEBUG=1 ./scheme` +- Debug messages show: + - Lexical analysis + - Parsing steps + - Code generation + - VM execution + - Stack operations + - Environment changes + +## Limitations +Current implementation does not support: +- Floating point numbers +- Strings +- Proper tail recursion +- Garbage collection +- Error recovery in REPL +- Full numeric tower +- Macros +- Standard library + +## Future Enhancements +1. Proper tail call optimization +2. Garbage collection +3. Error recovery +4. More data types +5. Standard library +6. Better debugging tools \ No newline at end of file diff --git a/awk/scheme/scheme/TODO.txt b/awk/scheme/scheme/TODO.txt new file mode 100644 index 0000000..31723a4 --- /dev/null +++ b/awk/scheme/scheme/TODO.txt @@ -0,0 +1,69 @@ +Scheme Interpreter TODO +===================== + +Current State: +------------- +- Basic arithmetic operations (+,-,*,/) are working +- Let expressions with simple bindings are working +- Function definitions (define) and lambda expressions are partially implemented +- Stack-based virtual machine with environment for variable bindings + +Recent Changes: +-------------- +1. Fixed function table array naming conflicts (func_name -> func_def_names) +2. Modified vm_get_value to handle function names correctly +3. Attempted to fix argument handling in function calls +4. Modified lambda compilation to avoid pushing function name twice + +Current Issues: +-------------- +1. Function Definitions (define): + - Function calls are failing with "ADD requires numeric operands" + - Arguments may not be properly passed to functions + - Function environment may not be properly set up + +2. Lambda Expressions: + - Direct lambda calls are failing + - Lambda bindings in let expressions are failing + - Function environment for lambda parameters may not be correct + +Next Steps: +----------- +1. Debug Function Calls: + - Add detailed debug logging in vm_call_function + - Verify argument handling and environment setup + - Check if function code is being properly stored and retrieved + +2. Fix Function Environment: + - Review how parameters are stored in the environment + - Ensure environment is properly restored after function calls + - Verify parameter values are correctly passed to functions + +3. Improve Lambda Support: + - Review lambda compilation process + - Ensure lambda functions are properly stored and called + - Fix environment handling for lambda parameters + +4. Testing Strategy: + - Create test cases for each type of function call + - Add debug output to track stack and environment state + - Verify each step of function compilation and execution + +5. Code Cleanup: + - Review and document function handling code + - Ensure consistent naming and error handling + - Add comments explaining complex operations + +Technical Notes: +--------------- +- Function calls use a combination of LOOKUP, GET_VALUE, and CALL instructions +- Environment stack handles variable bindings and function parameters +- Function code is stored in FUNCTIONS table with unique names +- Lambda functions use __lambda_N naming scheme + +Debugging Tips: +-------------- +1. Enable DEBUG=1 to see detailed execution logs +2. Check stack contents before and after each operation +3. Verify environment state during function calls +4. Monitor function code storage and retrieval \ No newline at end of file diff --git a/awk/scheme/scheme/bin/compiler.awk b/awk/scheme/scheme/bin/compiler.awk new file mode 100755 index 0000000..debaa2c --- /dev/null +++ b/awk/scheme/scheme/bin/compiler.awk @@ -0,0 +1,594 @@ +#!/usr/bin/awk -f + +# This is a Scheme-to-Assembly compiler implemented in AWK +# It takes Scheme expressions as input and outputs assembly instructions +# for a custom stack-based virtual machine + +BEGIN { + # Compiler maintains internal state for code generation + curr_token = "" # Current token being processed by lexer + input_buffer = "" # Buffer for input text being tokenized + next_label = 0 # Counter for generating unique labels + program = "" # Accumulates the full program text + + # Debug mode disabled by default, can be enabled via DEBUG=1 environment variable + DEBUG = (ENVIRON["DEBUG"] == "1") ? 1 : 0 +} + +# Debug logging helper function +function debug(msg) { + if (DEBUG) printf("[DEBUG] %s\n", msg) > "/dev/stderr" +} + +# Each line of input is accumulated into the program string +# This allows handling multi-line expressions +{ + if (program != "") program = program "\n" + program = program $0 +} + +# Main compiler entry point - called after all input is read +END { + debug("Raw program:\n" program) + if (program == "") exit + + # Parse and compile each expression in the program + split_expressions(program) +} + +# Splits input into individual Scheme expressions +# Handles nested parentheses and comments +function split_expressions(prog, current, paren_count, i, c, expr, cleaned) { + current = "" + paren_count = 0 + + # Clean up the input: + # 1. Remove comments (text between ; and next opening paren) + # 2. Normalize whitespace + # 3. Trim leading/trailing whitespace + cleaned = prog + gsub(/;[^(]*\(/, "(", cleaned) # Remove comments before expressions + gsub(/\)[^)]*;/, ")", cleaned) # Remove comments after expressions + gsub(/[ \t\n]+/, " ", cleaned) # Normalize whitespace to single spaces + sub(/^[ \t\n]+/, "", cleaned) # Trim leading whitespace + sub(/[ \t\n]+$/, "", cleaned) # Trim trailing whitespace + + debug("Cleaned program: [" cleaned "]") + + if (cleaned == "") return + + # Parse expressions by tracking parenthesis nesting + for (i = 1; i <= length(cleaned); i++) { + c = substr(cleaned, i, 1) + + if (c == "(") { + if (paren_count == 0) current = "" + paren_count++ + } + + current = current c + + if (c == ")") { + paren_count-- + if (paren_count == 0) { + # Complete expression found - compile it + expr = current + sub(/^\s+/, "", expr) + sub(/\s+$/, "", expr) + + debug("Processing expression: [" expr "]") + program = expr # Set for parser + expr = parse_expr() + compile_expr(expr) + current = "" + } + } + } + + # Add final HALT instruction + print "HALT" +} + +# Lexer helper functions for character classification +function is_digit(c) { return c >= "0" && c <= "9" } +function is_whitespace(c) { return c == " " || c == "\t" || c == "\n" } + +# Lexical analyzer - converts input into tokens +# Handles numbers, symbols, and parentheses +function next_token() { + # Initialize input buffer on first call + if (input_buffer == "") input_buffer = program + + # Skip whitespace between tokens + while (length(input_buffer) > 0 && is_whitespace(substr(input_buffer, 1, 1))) + input_buffer = substr(input_buffer, 2) + + if (length(input_buffer) == 0) return "EOF" + + # Handle parentheses as single-character tokens + c = substr(input_buffer, 1, 1) + if (c == "(" || c == ")") { + input_buffer = substr(input_buffer, 2) + return c + } + + # Handle numbers (including negative numbers) + if (is_digit(c) || c == "-" && length(input_buffer) > 1 && is_digit(substr(input_buffer, 2, 1))) { + num = "" + while (length(input_buffer) > 0) { + c = substr(input_buffer, 1, 1) + if (!is_digit(c) && c != "-") break + num = num c + input_buffer = substr(input_buffer, 2) + } + return num + } + + # Handle symbols (identifiers and operators) + sym = "" + while (length(input_buffer) > 0) { + c = substr(input_buffer, 1, 1) + if (is_whitespace(c) || c == "(" || c == ")") break + sym = sym c + input_buffer = substr(input_buffer, 2) + } + return sym +} + +# Recursive descent parser for Scheme expressions +# Returns parsed expression as a string +function parse_expr(token, result) { + token = next_token() + if (token == "EOF") return "" + + if (token == "(") { + result = parse_list() + debug("Parsed list: " result) + return result + } + + debug("Parsed token: " token) + return token +} + +# Parses a list expression (anything in parentheses) +function parse_list(result, expr) { + result = "" + + while (1) { + expr = parse_expr() + if (expr == "" || expr == ")") break + + if (result != "") result = result " " + result = result expr + } + + if (expr == "") error("Unexpected end of input in list") + return "(" result ")" +} + +# Splits an expression into operator and arguments +# Handles nested expressions correctly +function split_expr(expr, i, len, c, op, args, paren_count) { + len = length(expr) + paren_count = 0 + + for (i = 1; i <= len; i++) { + c = substr(expr, i, 1) + if (c == " " && paren_count == 0) { + op = substr(expr, 1, i - 1) + args = substr(expr, i + 1) + break + } + if (c == "(") paren_count++ + if (c == ")") paren_count-- + } + + if (!op) { + op = expr + args = "" + } + + debug("Split expr: op=" op " args=" args) + return op SUBSEP args +} + +# Splits argument list handling nested parentheses +function split_args(args, arg_array, len, i, c, current, paren_count, arg_count) { + len = length(args) + current = "" + paren_count = 0 + arg_count = 0 + + for (i = 1; i <= len; i++) { + c = substr(args, i, 1) + + if (c == "(") paren_count++ + if (c == ")") paren_count-- + + if (c == " " && paren_count == 0 && current != "") { + arg_array[++arg_count] = current + current = "" + } else if (c != " " || paren_count > 0) { + current = current c + } + } + + if (current != "") { + arg_array[++arg_count] = current + } + + return arg_count +} + +# Code generation for numeric literals +function compile_number(num) { + debug("Compiling number: " num) + print "PUSH_CONST N:" num +} + +# Code generation for primitive operations (+, -, *, cons, etc) +function compile_primitive_call(op, args, arg_array, nargs, i) { + debug("Primitive call: op=" op " args=" args) + nargs = split_args(args, arg_array) + + # Check if this is a lambda function call + if (substr(op, 1, 1) == "(") { + # This is a lambda function call + # First compile the lambda function + compile_expr(op) + + # Then compile all arguments + for (i = 1; i <= nargs; i++) { + compile_expr(arg_array[i]) + } + + # Call the function + print "CALL __lambda_" (next_label - 1) + return + } + + # Then emit appropriate operation + if (op == "+") { + # Compile arguments + for (i = 1; i <= nargs; i++) { + compile_expr(arg_array[i]) + } + for (i = 1; i < nargs; i++) + print "ADD" + } + else if (op == "-") { + # Compile arguments + for (i = 1; i <= nargs; i++) { + compile_expr(arg_array[i]) + } + if (nargs == 1) { + print "PUSH_CONST N:0" + print "SWAP" + } + for (i = 1; i < nargs; i++) + print "SUB" + } + else if (op == "*") { + # Compile arguments + for (i = 1; i <= nargs; i++) { + compile_expr(arg_array[i]) + } + for (i = 1; i < nargs; i++) + print "MUL" + } + else if (op == "cons") { + if (nargs != 2) error("cons requires 2 arguments") + # Compile arguments + for (i = 1; i <= nargs; i++) { + compile_expr(arg_array[i]) + } + print "CONS" + } + else if (op == "car") { + if (nargs != 1) error("car requires 1 argument") + # Compile argument + compile_expr(arg_array[1]) + print "CAR" + } + else if (op == "cdr") { + if (nargs != 1) error("cdr requires 1 argument") + # Compile argument + compile_expr(arg_array[1]) + print "CDR" + } + else if (op == "<") { + if (nargs != 2) error("< requires 2 arguments") + # Compile arguments + for (i = 1; i <= nargs; i++) { + compile_expr(arg_array[i]) + } + print "LT" + } + else if (op == "=") { + if (nargs != 2) error("= requires 2 arguments") + # Compile arguments + for (i = 1; i <= nargs; i++) { + compile_expr(arg_array[i]) + } + print "EQ" + } + else { + # Function call for user-defined functions + debug("Function call: " op) + # Look up the function name + print "LOOKUP " op + # Get the actual function name + print "GET_VALUE" + # Then compile arguments + for (i = 1; i <= nargs; i++) { + compile_expr(arg_array[i]) + } + # Call the function + print "CALL" + } +} + +# Splits let bindings into individual variable/value pairs +function split_bindings(bindings, binding_array, count, current, paren_count, i, c, in_lambda) { + count = 0 + current = "" + paren_count = 0 + in_lambda = 0 + + for (i = 1; i <= length(bindings); i++) { + c = substr(bindings, i, 1) + + # Track nested parentheses + if (c == "(") { + paren_count++ + if (paren_count == 1 && !in_lambda) { + current = "" # Start new binding + continue + } + } + if (c == ")") { + paren_count-- + if (paren_count == 0 && !in_lambda) { + # End of binding + binding_array[++count] = current + current = "" + continue + } + } + + # Track if we're inside a lambda expression + if (substr(bindings, i, 7) == "lambda ") { + in_lambda = 1 + } + + # Only add character if we're inside a binding + if (paren_count > 0) { + current = current c + } + } + + return count +} + +# Compiles let expressions (local variable bindings) +function compile_let(args, bindings, body, binding_array, nbindings, i, var, val, binding_parts) { + # Split into bindings and body + if (substr(args, 1, 1) != "(") error("Malformed let expression") + + # Find matching closing parenthesis for bindings + paren_count = 1 + i = 2 + while (paren_count > 0 && i <= length(args)) { + if (substr(args, i, 1) == "(") paren_count++ + if (substr(args, i, 1) == ")") paren_count-- + i++ + } + if (paren_count > 0) error("Unmatched parenthesis in let bindings") + + bindings = substr(args, 2, i - 3) # Remove outer parentheses + body = substr(args, i) + + # Trim whitespace from body + sub(/^[ \t\n]+/, "", body) + sub(/[ \t\n]+$/, "", body) + + debug("Let bindings: " bindings) + debug("Let body: " body) + + # Compile each binding + nbindings = split_bindings(bindings, binding_array) + for (i = 1; i <= nbindings; i++) { + debug("Processing binding: " binding_array[i]) + + # Find the variable name (everything up to the first space) + var = binding_array[i] + sub(/ .*$/, "", var) + + # Find the value (everything after the first space) + val = binding_array[i] + sub(/^[^ ]+ /, "", val) + + debug("Binding var: " var " val: " val) + + # Compile the value + if (substr(val, 1, 1) == "(") { + # Handle lambda or other compound expressions + if (substr(val, 2, 6) == "lambda") { + # This is a lambda expression + # First compile the lambda + compile_lambda(substr(val, 8)) # Skip "(lambda " + # Store the function name in the environment + print "STORE " var + } else { + compile_expr(val) + print "STORE " var + } + } else { + compile_expr(val) + print "STORE " var + } + } + + # Compile the body + compile_expr(body) + + # Clean up bindings AFTER evaluating body + for (i = nbindings; i >= 1; i--) { + print "POP_ENV" + } +} + +# Compiles define expressions (function/variable definitions) +function compile_define(args, name, params, body, param_array, nparams, i, paren_start, paren_end) { + # Set flag for global definition + print "PUSH_CONST B:1" + print "STORE from_define" # Must match exactly what vm_store checks for + + # Find the function name (everything up to the first space) + i = index(args, " ") + if (i == 0) error("Malformed define expression") + name = substr(args, 1, i - 1) + args = substr(args, i + 1) + + # Check if it's a function or variable definition + if (substr(args, 1, 1) == "(") { + # It's a function definition + paren_count = 1 + i = 2 + while (paren_count > 0 && i <= length(args)) { + if (substr(args, i, 1) == "(") paren_count++ + if (substr(args, i, 1) == ")") paren_count-- + i++ + } + if (paren_count > 0) error("Unmatched parenthesis in parameter list") + + params = substr(args, 2, i - 3) # Remove parentheses + body = substr(args, i + 1) + + # Create function label + print "LABEL " name + + # Process parameters + nparams = split(params, param_array, " ") + for (i = 1; i <= nparams; i++) { + print "STORE " param_array[i] + } + + # Compile function body + compile_expr(body) + + # Clean up parameters and return + for (i = nparams; i >= 1; i--) { + print "POP_ENV" + } + print "RETURN" + } else { + # Variable definition + debug("Defining variable: " name " with value: " args) + compile_expr(args) # Compile the value + print "STORE " name # Store the variable + } +} + +# Compiles lambda expressions (anonymous functions) +function compile_lambda(args, params, body, param_array, nparams, i, lambda_name) { + # Generate a unique name for the lambda function + lambda_name = "__lambda_" next_label++ + + # Split into parameters and body + if (substr(args, 1, 1) != "(") error("Malformed lambda expression") + + # Find matching closing parenthesis for parameters + paren_count = 1 + i = 2 + while (paren_count > 0 && i <= length(args)) { + if (substr(args, i, 1) == "(") paren_count++ + if (substr(args, i, 1) == ")") paren_count-- + i++ + } + if (paren_count > 0) error("Unmatched parenthesis in lambda parameters") + + params = substr(args, 2, i - 3) # Remove parentheses + body = substr(args, i) + + # Trim whitespace from body + sub(/^[ \t\n]+/, "", body) + sub(/[ \t\n]+$/, "", body) + + debug("Lambda parameters: " params) + debug("Lambda body: " body) + + # Create function label + print "LABEL " lambda_name + + # Process parameters + nparams = split(params, param_array, " ") + for (i = 1; i <= nparams; i++) { + print "STORE " param_array[i] + } + + # Compile function body + compile_expr(body) + + # Clean up parameters and return + for (i = nparams; i >= 1; i--) { + print "POP_ENV" + } + print "RETURN" + + # Only push the function name if we're not in a direct call + if (!(args ~ /^\([^)]*\)[^(]*$/)) { + print "PUSH_CONST S:" lambda_name + } +} + +# Main expression compiler - dispatches based on expression type +function compile_expr(expr, split_result, op, args) { + debug("Compiling expression: " expr) + + # Handle numeric literals + if (expr ~ /^-?[0-9]+$/) { + compile_number(expr) + return + } + + # Handle nil constant + if (expr == "nil") { + print "PUSH_CONST NIL:" + return + } + + # Handle variable lookup + if (expr ~ /^[a-zA-Z_][a-zA-Z0-9_]*$/) { + print "LOOKUP " expr + return + } + + # Handle compound expressions (lists) + if (substr(expr, 1, 1) == "(") { + expr = substr(expr, 2, length(expr) - 2) + split_result = split_expr(expr) + op = substr(split_result, 1, index(split_result, SUBSEP) - 1) + args = substr(split_result, index(split_result, SUBSEP) + 1) + + if (op == "define") { + compile_define(args) + } else if (op == "let") { + compile_let(args) + } else if (op == "lambda") { + compile_lambda(args) + } else { + compile_primitive_call(op, args) + } + return + } + + error("Unknown expression type: " expr) +} + +# Error reporting helper +function error(msg) { + print "Error: " msg > "/dev/stderr" + exit 1 +} \ No newline at end of file diff --git a/awk/scheme/scheme/bin/repl b/awk/scheme/scheme/bin/repl new file mode 100755 index 0000000..14a10cf --- /dev/null +++ b/awk/scheme/scheme/bin/repl @@ -0,0 +1,147 @@ +#!/bin/bash + +# Enable debug tracing +DEBUG=0 + +debug() { + if [ "$DEBUG" = "1" ]; then + echo "[DEBUG] $*" >&2 + fi +} + +# Find the directory containing this script and the components +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +COMPILER="$DIR/compiler.awk" +VM="$DIR/vm.awk" + +debug "Using compiler: $COMPILER" +debug "Using VM: $VM" + +# Verify components exist and are executable +for component in "$COMPILER" "$VM"; do + if [ ! -f "$component" ]; then + echo "Error: Cannot find $component" >&2 + echo "Please ensure all components are present" >&2 + exit 1 + fi + chmod +x "$component" +done + +# Set up temporary files and state +TMPDIR=$(mktemp -d) +debug "Created temp dir: $TMPDIR" +STATE_FILE="/tmp/scheme_vm.state" + +cleanup() { + debug "Cleaning up temp dir: $TMPDIR" + rm -rf "$TMPDIR" + if [ "$1" != "keep_state" ]; then + rm -f "$STATE_FILE" + rm -f "/tmp/scheme_vm.env" + fi +} +trap "cleanup" EXIT + +# Set up temporary files +INPUT_FILE="$TMPDIR/input.scm" +ASM_FILE="$TMPDIR/output.asm" +DEBUG_FILE="$TMPDIR/debug.out" + +# Initialize/clear state files at REPL start +if [ "$#" -eq 0 ]; then # Only for interactive mode + : > "/tmp/scheme_vm.state" + : > "/tmp/scheme_vm.env" +fi + +# Function to handle evaluation +evaluate_expression() { + local input="$1" + local result + + # Skip empty lines + if [ -z "$input" ]; then + return 0 + fi + + debug "Evaluating expression: $input" + echo "$input" > "$INPUT_FILE" + debug "Input file contents:" + cat "$INPUT_FILE" >&2 + + # Show compilation output even if it fails + debug "Running compiler..." + if awk -f "$COMPILER" "$INPUT_FILE" > "$ASM_FILE" 2> "$DEBUG_FILE"; then + debug "Compilation successful. Debug output:" + cat "$DEBUG_FILE" >&2 + debug "Generated assembly:" + cat "$ASM_FILE" >&2 + + debug "Running VM..." + # Use persistent VM state + result=$(awk -v PERSIST=1 -f "$VM" "$ASM_FILE" 2>&1) + debug "VM output: $result" + if [ -n "$result" ]; then + echo "$result" + fi + return 0 + else + echo "Compilation error" >&2 + debug "Compiler output:" + cat "$DEBUG_FILE" >&2 + return 1 + fi +} + +# Check if a file argument is provided +if [ "$#" -gt 0 ]; then + if [ ! -f "$1" ]; then + echo "Error: File not found: $1" >&2 + exit 1 + fi + debug "Reading file: $1" + file_content=$(cat "$1" | tr '\n' ' ') + debug "File content: $file_content" + evaluate_expression "$file_content" + cleanup "keep_state" # Keep state after file execution + exit 0 +fi + +# REPL state +paren_count=0 +current_input="" + +# Print welcome message +echo "Scheme REPL (Press Ctrl+D to exit)" +echo + +# Main REPL loop +while true; do + if [ $paren_count -eq 0 ]; then + printf "scheme> " + else + printf "... " + fi + + read -r line || exit 0 + + # Skip empty lines + if [ -z "$line" ]; then + continue + fi + + # Count parentheses + open_parens=$(echo "$line" | tr -cd '(' | wc -c) + close_parens=$(echo "$line" | tr -cd ')' | wc -c) + paren_count=$((paren_count + open_parens - close_parens)) + + if [ -n "$current_input" ]; then + current_input="$current_input $line" + else + current_input="$line" + fi + + if [ $paren_count -eq 0 ]; then + evaluate_expression "$current_input" + current_input="" + fi +done \ No newline at end of file diff --git a/awk/scheme/scheme/bin/vm.awk b/awk/scheme/scheme/bin/vm.awk new file mode 100755 index 0000000..4e7d2c7 --- /dev/null +++ b/awk/scheme/scheme/bin/vm.awk @@ -0,0 +1,707 @@ +#!/usr/bin/awk -f + +# This is a stack-based virtual machine for executing compiled Scheme code +# It implements a simple instruction set with support for: +# - Basic arithmetic operations +# - Function calls and returns +# - Variable bindings and lookups +# - Cons cells and list operations + +BEGIN { + # Type system tags for runtime type checking + T_NUMBER = "N" # Numbers (integers) + T_BOOLEAN = "B" # Booleans (0/1) + T_SYMBOL = "S" # Symbols (identifiers) + T_PAIR = "P" # Cons cells (pairs) + T_FUNCTION = "F" # Function references + T_NIL = "NIL" # Empty list marker + + # Virtual machine registers + stack_ptr = 0 # Points to top of evaluation stack + heap_ptr = 0 # Points to next free heap location + pc = 0 # Program counter for instruction fetch + + # Debug mode disabled by default, can be enabled via DEBUG=1 environment variable + DEBUG = (ENVIRON["DEBUG"] == "1") ? 1 : 0 + + # Environment for variable bindings + env_size = 0 # Current size of environment stack + + # Function table for storing defined functions + delete func_def_names # Function names + delete func_def_pc # Entry points + delete func_def_code # Function bodies + func_def_size = 0 # Number of defined functions + + # Call stack for function returns + call_stack_ptr = 0 + + # State persistence configuration + STATE_FILE = "/tmp/scheme_vm.state" + if (PERSIST) { + debug("Loading state from: " STATE_FILE) + if ((getline line < STATE_FILE) >= 0) { # Check if file exists and is readable + do { + if (line ~ /^FUNC /) { + # Parse and load function definition + sub(/^FUNC /, "", line) + name = line + sub(/ .*$/, "", name) + code = line + sub(/^[^ ]+ /, "", code) + + debug("Loaded function: " name) + debug("Code: " code) + + # Store function in function table + func_def_names[func_def_size] = name + func_def_code[func_def_size] = code + func_def_size++ + } + } while ((getline line < STATE_FILE) > 0) + close(STATE_FILE) + } + } + + # Function environment storage + delete func_env_names # Variable names in function scope + delete func_env_vals # Variable values in function scope + delete func_env_sizes # Size of each function's environment + + # Global function registry + delete FUNCTIONS # Maps function names to implementations + + # Environment persistence configuration + ENV_STATE_FILE = "/tmp/scheme_vm.env" + if (PERSIST) { + debug("Loading environment state from: " ENV_STATE_FILE) + if ((getline line < ENV_STATE_FILE) >= 0) { + do { + if (line ~ /^ENV /) { + # Parse and load environment binding + sub(/^ENV /, "", line) + name = line + sub(/ .*$/, "", name) + val = line + sub(/^[^ ]+ /, "", val) + + debug("Loaded env var: " name " = " val) + + # Store in environment + env_name[env_size] = name + env_val[env_size] = val + env_size++ + } + } while ((getline line < ENV_STATE_FILE) > 0) + close(ENV_STATE_FILE) + } + } + + # Register built-in functions + FUNCTIONS["+"] = "add" + FUNCTIONS["-"] = "subtract" + FUNCTIONS["*"] = "multiply" + FUNCTIONS["/"] = "divide" + FUNCTIONS["="] = "equals" + FUNCTIONS["<"] = "less_than" + FUNCTIONS[">"] = "greater_than" + FUNCTIONS["add1"] = "add_one" + + # Track if VM halted normally (vs error) + normal_exit = 0 +} + +# Debug output helper +function debug(msg) { + if (DEBUG) printf("[DEBUG] %s\n", msg) > "/dev/stderr" +} + +# Value constructors and accessors +# Values are stored as type:value pairs for runtime type checking +function makeValue(type, val) { + return type ":" val +} + +function getType(val) { + type = substr(val, 1, index(val, ":") - 1) + debug("Get type: " type " from " val) + return type +} + +function getValue(val) { + value = substr(val, index(val, ":") + 1) + debug("Get value: " value " from " val) + return value +} + +# Type checking predicates +function isNumber(val) { return getType(val) == T_NUMBER } +function isBoolean(val) { return getType(val) == T_BOOLEAN } +function isSymbol(val) { return getType(val) == T_SYMBOL } +function isPair(val) { return getType(val) == T_PAIR } +function isFunction(val) { return getType(val) == T_FUNCTION } +function isNil(val) { return getType(val) == T_NIL } + +# Stack operations +function push(val) { + stack[++stack_ptr] = val + debug("Push: " val " (SP: " stack_ptr ")") +} + +function pop() { + if (stack_ptr < 1) error("Stack underflow") + val = stack[stack_ptr--] + debug("Pop: " val " (SP: " stack_ptr ")") + return val +} + +function peek() { + if (stack_ptr < 1) error("Stack empty") + debug("Peek: " stack[stack_ptr]) + return stack[stack_ptr] +} + +# Heap operations for cons cells +function allocate(val) { + heap[++heap_ptr] = val + refs[heap_ptr] = 1 # Reference counting (not fully implemented) + debug("Allocate: " val " at " heap_ptr) + return heap_ptr +} + +function getHeap(idx) { + if (!(idx in heap)) { + error("Invalid heap access: " idx) + return "" + } + return heap[idx] +} + +# Error handling +function error(msg) { + print "Error at PC " pc ": " msg > "/dev/stderr" + exit 1 +} + +# Arithmetic instruction implementations +function vm_add() { + if (stack_ptr < 2) error("ADD requires two operands") + val2 = pop() + val1 = pop() + if (!isNumber(val1) || !isNumber(val2)) + error("ADD requires numeric operands") + result = getValue(val1) + getValue(val2) + push(makeValue(T_NUMBER, result)) +} + +function vm_subtract() { + if (stack_ptr < 2) error("SUB requires two operands") + val2 = pop() + val1 = pop() + if (!isNumber(val1) || !isNumber(val2)) + error("SUB requires numeric operands") + result = getValue(val1) - getValue(val2) + push(makeValue(T_NUMBER, result)) +} + +function vm_multiply() { + if (stack_ptr < 2) error("MUL requires two operands") + val2 = pop() + val1 = pop() + if (!isNumber(val1) || !isNumber(val2)) + error("MUL requires numeric operands") + result = getValue(val1) * getValue(val2) + push(makeValue(T_NUMBER, result)) +} + +function vm_divide() { + if (stack_ptr < 2) error("DIV requires two operands") + val2 = pop() + val1 = pop() + if (!isNumber(val1) || !isNumber(val2)) + error("DIV requires numeric operands") + if (getValue(val2) == 0) + error("Division by zero") + result = getValue(val1) / getValue(val2) + push(makeValue(T_NUMBER, result)) +} + +# List operation implementations +function vm_cons() { + if (stack_ptr < 2) error("CONS requires two operands") + val2 = pop() + val1 = pop() + pair_val = val1 "," val2 + pair_idx = allocate(pair_val) + push(makeValue(T_PAIR, pair_idx)) +} + +function vm_car() { + if (stack_ptr < 1) error("CAR requires one operand") + val = pop() + if (!isPair(val)) error("CAR requires pair operand") + pair_idx = getValue(val) + pair = getHeap(pair_idx) + car_val = substr(pair, 1, index(pair, ",") - 1) + push(car_val) +} + +function vm_cdr() { + if (stack_ptr < 1) error("CDR requires one operand") + val = pop() + if (!isPair(val)) error("CDR requires pair operand") + pair_idx = getValue(val) + pair = getHeap(pair_idx) + cdr_val = substr(pair, index(pair, ",") + 1) + push(cdr_val) +} + +# Comparison operations +function vm_equal() { + if (stack_ptr < 2) error("EQ requires two operands") + val2 = pop() + val1 = pop() + result = (val1 == val2) ? "1" : "0" + debug("Equal comparison: " val1 " == " val2 " -> " result) + push(makeValue(T_BOOLEAN, result)) +} + +function vm_less_than() { + if (stack_ptr < 2) error("LT requires two operands") + val2 = pop() + val1 = pop() + if (!isNumber(val1) || !isNumber(val2)) + error("LT requires numeric operands") + result = (getValue(val1) < getValue(val2)) ? "1" : "0" + debug("Less than comparison: " val1 " < " val2 " -> " result) + push(makeValue(T_BOOLEAN, result)) +} + +# Main instruction execution loop +function execute(instr) { + split(instr, parts, " ") + op = parts[1] + debug("Execute: " instr) + + # Dispatch based on instruction opcode + if (op == "PUSH_CONST") { + push(parts[2]) + } + else if (op == "POP") { + pop() + } + else if (op == "DUP") { + val = peek() + push(val) + } + else if (op == "SWAP") { + if (stack_ptr < 2) error("SWAP requires two operands") + val2 = pop() + val1 = pop() + push(val2) + push(val1) + } + else if (op == "ADD") { + vm_add() + } + else if (op == "SUB") { + vm_subtract() + } + else if (op == "MUL") { + vm_multiply() + } + else if (op == "DIV") { + vm_divide() + } + else if (op == "CONS") { + vm_cons() + } + else if (op == "CAR") { + vm_car() + } + else if (op == "CDR") { + vm_cdr() + } + else if (op == "EQ") { + vm_equal() + } + else if (op == "LT") { + vm_less_than() + } + else if (op == "PRINT") { + if (stack_ptr < 1) error("PRINT requires one operand") + print peek() + } + else if (op == "HALT") { + normal_exit = 1 + if (stack_ptr > 0) { + result = peek() + } + if (PERSIST) { + save_state() + } + if (result) { + print result + } + exit(0) + } + else if (op == "STORE") { + vm_store(parts[2]) + } + else if (op == "POP_ENV") { + vm_pop_env() + } + else if (op == "LOOKUP") { + vm_lookup(parts[2]) + } + else if (op == "LABEL") { + vm_define_function(parts[2], pc) + } + else if (op == "CALL") { + vm_call_function(parts[2]) + } + else if (op == "RETURN") { + vm_return() + } + else if (op == "GET_VALUE") { + vm_get_value() + } + else { + error("Unknown instruction: " op) + } +} + +# Load program instructions +{ + program[NR-1] = $0 +} + +# Main execution loop +END { + while (pc < length(program)) { + execute(program[pc++]) + } + + # Save state if we didn't halt normally + if (!normal_exit && PERSIST) { + save_state() + } +} + +# Variable binding implementation +function vm_store(name) { + debug("Storing " peek() " as " name " at env_size: " env_size) + + # Handle global definitions specially + if (lookup_no_error("from_define")) { + name = "__global_" name + # Clear the define flag + for (i = env_size - 1; i >= 0; i--) { + if (env_name[i] == "from_define") { + env_size-- + break + } + } + + # Remove any previous definition of this global + for (i = env_size - 1; i >= 0; i--) { + if (env_name[i] == name) { + # Shift everything down + for (j = i; j < env_size - 1; j++) { + env_name[j] = env_name[j + 1] + env_val[j] = env_val[j + 1] + } + env_size-- + break + } + } + } + + # Handle lambda functions + val = peek() + if (isSymbol(val)) { + func_name = getValue(val) + if (func_name ~ /^__lambda_/) { + # Store the function code under the new name + FUNCTIONS[name] = FUNCTIONS[func_name] + # Store the new name in the environment + env_name[env_size] = name + env_val[env_size] = makeValue(T_SYMBOL, name) + env_size++ + return + } + } + + # Add to environment + env_name[env_size] = name + env_val[env_size] = peek() + env_size++ + + debug("Environment after store:") + dump_env() +} + +# Remove top binding from environment +function vm_pop_env() { + if (env_size <= 0) error("Environment underflow") + debug("Popping environment at size: " env_size) + + # Don't pop globals + if (env_name[env_size-1] ~ /^__global_/) { + debug("Keeping global definition: " env_name[env_size-1]) + return + } + + debug("Removing: " env_name[env_size-1] " = " env_val[env_size-1]) + env_size-- +} + +# Variable lookup implementation +function vm_lookup(name, i, global_name, val) { + debug("Looking up " name " in environment of size: " env_size) + dump_env() + + # Check if it's a function (built-in or user-defined) + if (name in FUNCTIONS) { + debug("Found function: " name) + push(makeValue(T_SYMBOL, name)) + return + } + + # Try global name first, then local + global_name = "__global_" name + for (i = env_size - 1; i >= 0; i--) { + if (env_name[i] == global_name || env_name[i] == name) { + debug("Found " name " = " env_val[i] " at position " i) + push(env_val[i]) + return + } + } + error("Undefined variable: " name) +} + +# Function definition implementation +function vm_define_function(name, start_pc) { + debug("Defining function: " name " at " start_pc) + + # Build function code + code = "" + i = start_pc + while (i < length(program) && program[i] != "RETURN") { + if (code != "") code = code "\n" + code = code program[i] + i++ + } + code = code "\nRETURN" + + # Store function + debug("Storing function: " name " = " code) + FUNCTIONS[name] = code + + pc = i + 1 +} + +# Function call implementation +function vm_call_function(func_name, code_lines, j, saved_pc, saved_env_size, arg, param_name) { + debug("Calling function: " func_name) + + # If name is a symbol, get its value + if (isSymbol(func_name)) { + func_name = getValue(func_name) + } + + # Handle anonymous functions + if (func_name ~ /^__lambda_/) { + if (!(func_name in FUNCTIONS)) { + error("Undefined lambda function: " func_name) + } + } else if (!(func_name in FUNCTIONS)) { + error("Undefined function: " func_name) + } + + saved_pc = pc + saved_env_size = env_size + + # Split function code into lines + split(FUNCTIONS[func_name], code_lines, "\n") + + # Add function code to program at current position + for (j in code_lines) { + program[pc + j - 1] = code_lines[j] + } + + # Check if this is a parameterized function + if (code_lines[1] ~ /^STORE /) { + # This is a parameterized function (lambda) + # Get parameter name from STORE instruction + param_name = substr(code_lines[1], 7) + debug("Found parameter name: " param_name) + + # Get argument from stack + arg = pop() + debug("Function argument: " arg) + + # Create new environment frame + debug("Creating new environment frame at size: " env_size) + env_name[env_size] = param_name + env_val[env_size] = arg + env_size++ + } else { + # This is a built-in function or non-parameterized function + debug("Calling non-parameterized function: " func_name) + } + + # Save return info and jump to function + call_stack[++call_stack_ptr] = saved_pc + env_stack[call_stack_ptr] = saved_env_size + + debug("Function found, jumping to PC: " pc " with env_size: " saved_env_size) + dump_env() +} + +# Function return implementation +function vm_return() { + if (call_stack_ptr > 0) { + # Save return value + ret_val = pop() + + # Restore environment + while (env_size > env_stack[call_stack_ptr]) { + debug("Popping environment at size: " env_size) + vm_pop_env() + } + + # Restore program counter + pc = call_stack[call_stack_ptr--] + + # Push return value + push(ret_val) + + debug("Returned with value: " ret_val " and env_size: " env_size) + } +} + +# Debug helper to dump environment contents +function dump_env( i) { + debug("Environment dump:") + for (i = 0; i < env_size; i++) { + debug(sprintf(" %d: %s = %s", i, env_name[i], env_val[i])) + } +} + +# Helper for checking variable existence without error +function lookup_no_error(name, i) { + for (i = env_size - 1; i >= 0; i--) { + if (env_name[i] == name) { + return 1 + } + } + return 0 +} + +# State persistence implementation +function save_state() { + debug("Saving state to: " STATE_FILE) + for (i = 0; i < func_def_size; i++) { + debug("Saving function: " func_def_names[i]) + print "FUNC " func_def_names[i] " " func_def_code[i] > STATE_FILE + } + close(STATE_FILE) + + # Save environment state + debug("Saving environment state to: " ENV_STATE_FILE) + for (i = 0; i < env_size; i++) { + if (env_name[i] ~ /^__global_/) { # Only save globals + debug("Saving env var: " env_name[i] " = " env_val[i]) + print "ENV " env_name[i] " " env_val[i] > ENV_STATE_FILE + } + } + close(ENV_STATE_FILE) +} + +# Built-in function implementations +function equals() { + if (stack_ptr < 2) error("= requires two operands") + val2 = pop() + val1 = pop() + if (!isNumber(val1) || !isNumber(val2)) error("= requires numeric operands") + result = (getValue(val1) == getValue(val2)) ? 1 : 0 + push(makeValue(T_BOOLEAN, result)) +} + +function less_than() { + if (stack_ptr < 2) error("< requires two operands") + val2 = pop() + val1 = pop() + if (!isNumber(val1) || !isNumber(val2)) error("< requires numeric operands") + result = (getValue(val1) < getValue(val2)) ? 1 : 0 + push(makeValue(T_BOOLEAN, result)) +} + +function greater_than() { + if (stack_ptr < 2) error("> requires two operands") + val2 = pop() + val1 = pop() + if (!isNumber(val1) || !isNumber(val2)) error("> requires numeric operands") + result = (getValue(val1) > getValue(val2)) ? 1 : 0 + push(makeValue(T_BOOLEAN, result)) +} + +function add() { + if (stack_ptr < 2) error("+ requires two operands") + val2 = pop() + val1 = pop() + if (!isNumber(val1) || !isNumber(val2)) error("+ requires numeric operands") + result = getValue(val1) + getValue(val2) + push(makeValue(T_NUMBER, result)) +} + +function subtract() { + if (stack_ptr < 2) error("- requires two operands") + val2 = pop() + val1 = pop() + if (!isNumber(val1) || !isNumber(val2)) error("- requires numeric operands") + result = getValue(val1) - getValue(val2) + push(makeValue(T_NUMBER, result)) +} + +function multiply() { + if (stack_ptr < 2) error("* requires two operands") + val2 = pop() + val1 = pop() + if (!isNumber(val1) || !isNumber(val2)) error("* requires numeric operands") + result = getValue(val1) * getValue(val2) + push(makeValue(T_NUMBER, result)) +} + +function divide() { + if (stack_ptr < 2) error("/ requires two operands") + val2 = pop() + val1 = pop() + if (!isNumber(val1) || !isNumber(val2)) error("/ requires numeric operands") + if (getValue(val2) == 0) error("Division by zero") + result = getValue(val1) / getValue(val2) + push(makeValue(T_NUMBER, result)) +} + +function add_one() { + if (stack_ptr < 1) error("add1 requires one operand") + val = pop() + if (!isNumber(val)) error("add1 requires numeric operand") + result = getValue(val) + 1 + push(makeValue(T_NUMBER, result)) +} + +# Get value from top of stack +function vm_get_value() { + val = peek() + if (isSymbol(val)) { + name = getValue(val) + # If it's a function name, just push the name directly + if (name in FUNCTIONS) { + push(name) + } else { + push(makeValue(T_SYMBOL, name)) + } + } +} \ No newline at end of file diff --git a/awk/scheme/scheme/diagram.md b/awk/scheme/scheme/diagram.md new file mode 100644 index 0000000..4a719b4 --- /dev/null +++ b/awk/scheme/scheme/diagram.md @@ -0,0 +1,49 @@ +# Awk-Scheme Architecture +## Component Interaction Diagram + +``` ++----------------+ Scheme Code +----------------+ Assembly +----------------+ +| | -----------------> | | -------------> | | +| REPL | "(+ 1 2)" | Compiler | "PUSH_CONST | VM | +| (bin/repl) | | compiler.awk | N:1 | vm.awk | +| | | | PUSH_CONST | | +| - Multi-line | | - Lexer | N:2 | - Stack-based | +| - Debug mode | | - Parser | ADD | - Type system | +| - File input | | - Code gen | HALT" | - Environment | +| | <----------------- | | <------------- | | +| | Output: "N:3" | | Result | | ++----------------+ +----------------+ +----------------+ + ^ | + | v + | +----------------+ + | | Persistence | + | | /tmp files: | + +--------------------------------------------------------------+ - vm.state | + | - vm.env | + +----------------+ + +Debug Flow (when DEBUG=1): ++----------------+ Debug Info +----------------+ Debug Info +----------------+ +| REPL | -----------------> | Compiler | -------------> | VM | +| | [Input received] | | [Tokens found] | | +| [Debug output] | | [Parsing tree] | [Stack ops] | [Stack state] | +| stderr | <----------------- | [Gen code] | <------------- | [Environment] | ++----------------+ +----------------+ +----------------+ + +Execution Flow Example: +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ Input: │ │ Lexer/ │ │ Code Gen │ │ VM │ +│ (+ 1 2) │ ------> │ Parser: │ ------> │ PUSH_CONST │ ------> │ Stack: │ +│ │ │ (+ │ │ N:1 │ │ [N:1] │ +│ │ │ 1 │ │ PUSH_CONST │ │ [N:1,N:2] │ +│ │ │ 2) │ │ N:2 │ │ [N:3] │ +│ │ │ │ │ ADD │ │ │ +└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ + +State Management: +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ Global │ │ Environment │ │ Function │ +│ Variables │ ------> │ Stack │ ------> │ Calls │ +│ (persist) │ │ (frames) │ │ (stack) │ +└─────────────┘ └─────────────┘ └─────────────┘ +``` \ No newline at end of file diff --git a/awk/scheme/scheme/examples/cons.test.scm b/awk/scheme/scheme/examples/cons.test.scm new file mode 100644 index 0000000..d1e3847 --- /dev/null +++ b/awk/scheme/scheme/examples/cons.test.scm @@ -0,0 +1,3 @@ +(cons (+ 1 2) + (cons (* 3 4) + nil)) diff --git a/awk/scheme/scheme/examples/define.test.scm b/awk/scheme/scheme/examples/define.test.scm new file mode 100644 index 0000000..ec66b04 --- /dev/null +++ b/awk/scheme/scheme/examples/define.test.scm @@ -0,0 +1,2 @@ +(define add2 (x) (+ x 2)) +(add2 40) \ No newline at end of file diff --git a/awk/scheme/scheme/examples/lambda.test.scm b/awk/scheme/scheme/examples/lambda.test.scm new file mode 100644 index 0000000..1f2bb09 --- /dev/null +++ b/awk/scheme/scheme/examples/lambda.test.scm @@ -0,0 +1,12 @@ +; Test lambda function support +((lambda (x) (+ x 1)) 41) ; Should return 42 + +; Test lambda with multiple parameters +((lambda (x y) (+ x y)) 20 22) ; Should return 42 + +; Test lambda in let expression +(let ((add1 (lambda (x) (+ x 1)))) + (add1 41)) ; Should return 42 + +; Test nested lambda +((lambda (x) ((lambda (y) (+ x y)) 1)) 41) ; Should return 42 \ No newline at end of file diff --git a/awk/scheme/scheme/examples/let-and-define.test.scm b/awk/scheme/scheme/examples/let-and-define.test.scm new file mode 100644 index 0000000..fade30b --- /dev/null +++ b/awk/scheme/scheme/examples/let-and-define.test.scm @@ -0,0 +1,9 @@ +; Let expression example +(let ((x 5) (y 3)) + (+ x y)) + +; Function definition example +(define add2 (x) + (+ x 2)) + +(add2 40) ; Returns 42 \ No newline at end of file diff --git a/awk/scheme/scheme/examples/let.test.scm b/awk/scheme/scheme/examples/let.test.scm new file mode 100644 index 0000000..2cdc3b8 --- /dev/null +++ b/awk/scheme/scheme/examples/let.test.scm @@ -0,0 +1,2 @@ +(let ((x 5)) + (+ x 2)) diff --git a/awk/scheme/scheme/scheme b/awk/scheme/scheme/scheme new file mode 100755 index 0000000..cec35d1 --- /dev/null +++ b/awk/scheme/scheme/scheme @@ -0,0 +1,3 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +exec "$DIR/bin/repl" "$@" diff --git a/awk/scheme/scheme/scratch/complex_test.scm.asm b/awk/scheme/scheme/scratch/complex_test.scm.asm new file mode 100644 index 0000000..67870c3 --- /dev/null +++ b/awk/scheme/scheme/scratch/complex_test.scm.asm @@ -0,0 +1,44 @@ +# Test proper list construction (3 2 1) +# Building the list in proper order: car points to value, cdr points to next pair + +# Start with empty list +PUSH_CONST NIL: # [nil] +PRINT # Print nil + +# Build (1 . nil) +PUSH_CONST NIL: # [nil] +PUSH_CONST N:1 # [nil 1] +SWAP # [1 nil] +CONS # [(1 . nil)] +DUP +PRINT # Print (1 . nil) + +# Build (2 . (1 . nil)) +PUSH_CONST N:2 # [(1.nil) 2] +SWAP # [2 (1.nil)] +CONS # [(2 . (1.nil))] +DUP +PRINT # Print (2 . (1.nil)) + +# Build (3 . (2 . (1 . nil))) +PUSH_CONST N:3 # [(2.(1.nil)) 3] +SWAP # [3 (2.(1.nil))] +CONS # [(3 . (2.(1.nil)))] +DUP +PRINT # Print full structure + +# Test CAR/CDR operations +DUP # Keep a copy of the list for later +DUP # Another copy for CAR +CAR # Get first element (3) +PRINT # Should print 3 + +SWAP # Bring back our spare list copy +CDR # Get rest of list ((2 . (1 . nil))) +DUP +PRINT # Print rest of list + +CAR # Get first of rest (2) +PRINT # Should print 2 + +HALT \ No newline at end of file diff --git a/awk/scheme/scheme/scratch/run.sh b/awk/scheme/scheme/scratch/run.sh new file mode 100755 index 0000000..0afdb41 --- /dev/null +++ b/awk/scheme/scheme/scratch/run.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# Compile Scheme to VM instructions +awk -f compiler.awk test.scm > test.asm +# Run VM instructions +awk -f vm.awk test.asm \ No newline at end of file diff --git a/awk/scheme/scheme/scratch/test.asm b/awk/scheme/scheme/scratch/test.asm new file mode 100644 index 0000000..8e7d8df --- /dev/null +++ b/awk/scheme/scheme/scratch/test.asm @@ -0,0 +1,16 @@ +PUSH_CONST N:1 +PUSH_CONST N:2 +ADD +PUSH_CONST N:3 +PUSH_CONST N:4 +MUL +PUSH_CONST N:10 +PUSH_CONST N:2 +PUSH_CONST N:3 +ADD +SUB +PUSH_CONST NIL: +CONS +CONS +CONS +HALT diff --git a/awk/scheme/scheme/scratch/test.scm b/awk/scheme/scheme/scratch/test.scm new file mode 100644 index 0000000..a01b174 --- /dev/null +++ b/awk/scheme/scheme/scratch/test.scm @@ -0,0 +1,8 @@ +;; Build a list of calculated values +(cons (+ 1 2) ; First element: 1 + 2 = 3 + (cons (* 3 4) ; Second element: 3 * 4 = 12 + (cons (- 10 + (+ 2 3)) ; Third element: 10 - (2 + 3) = 5 + nil))) ; End of list + +;; This should create a list: (3 12 5) \ No newline at end of file diff --git a/awk/scheme/scheme/scratch/test.scm.asm b/awk/scheme/scheme/scratch/test.scm.asm new file mode 100644 index 0000000..526e2b1 --- /dev/null +++ b/awk/scheme/scheme/scratch/test.scm.asm @@ -0,0 +1,7 @@ +PUSH_CONST N:5 +PUSH_CONST N:3 +ADD +PUSH_CONST N:2 +MUL +PRINT # should output N:16 +HALT \ No newline at end of file diff --git a/awk/vm/README.md b/awk/vm/README.md new file mode 100644 index 0000000..83a35fd --- /dev/null +++ b/awk/vm/README.md @@ -0,0 +1,91 @@ +# Stack-Based Virtual Machine in AWK + +A simple stack-based virtual machine implementation in AWK, inspired by Forth. The VM provides basic stack manipulation, arithmetic operations, register access, and memory operations. + +## Architecture + +The VM consists of: +- A data stack (100 elements) +- Main memory (1000 cells) +- Three registers: + - A: General purpose register + - B: Often used as memory pointer + - P: Program pointer, used for sequential memory operations + +## Instruction Set + +### Stack Operations +- `DROP` - Remove top item from stack +- `DUP` - Duplicate top stack item +- `OVER` - Copy second item to top of stack +- `SWAP` - Exchange top two stack items +- Numbers are automatically pushed onto the stack + +### Arithmetic Operations +- `+` - Add top two stack items (a b -- a+b) +- `*` - Multiply top two stack items (a b -- a*b) +- `AND` - Bitwise AND of top two items +- `XOR` - Bitwise XOR of top two items +- `NOT` - Bitwise NOT of top item +- `2*` - Multiply top item by 2 (shift left) +- `2/` - Divide top item by 2 (shift right) + +### Register Operations +- `A` - Push value of A register onto stack +- `A!` - Store top stack value into A register +- `B!` - Store top stack value into B register + +### Memory Operations +- `@` - Fetch from memory address on stack (addr -- value) +- `!` - Store to memory address on stack (value addr --) +- `@+` - Fetch from memory at P, then increment P +- `!+` - Store to memory at P, then increment P +- `@B` - Fetch from memory address in B register +- `!B` - Store to memory address in B register +- `@P` - Fetch from memory address in P register +- `!P` - Store to memory address in P register + +### Debug & Control +- `.` - NO-OP (does nothing) +- `BYE` - Exit program +- `SHOW` - Display current machine state (stack, registers, memory) + +## Memory Model + +- Memory is zero-based +- Each cell can hold a numeric value +- Memory is accessed either directly (using @ and !) or through registers +- P register is typically used for sequential memory operations +- B register is typically used as a memory pointer + +## Example Programs + +### Store and retrieve a value +``` +5 DUP 0 ! # Store 5 at address 0 +3 DUP 1 ! # Store 3 at address 1 +0 @ 1 @ + # Load both values and add them +2 ! # Store result at address 2 +``` + +### Using registers +``` +42 A! # Store 42 in A register +A # Push A's value onto stack +100 B! # Set B to address 100 +42 !B # Store 42 at address 100 +@B # Read from address 100 +``` + +## Usage + +```bash +# Run a program directly +echo "5 3 + SHOW" | awk -f vm.awk + +# Compile and run a program +./compiler.py program.coffee | awk -f vm.awk + +# Run test suite +./vm_tests.sh +``` \ No newline at end of file diff --git a/awk/vm/compiler.py b/awk/vm/compiler.py new file mode 100755 index 0000000..a406779 --- /dev/null +++ b/awk/vm/compiler.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 + +""" +A simple compiler that translates CoffeeScript-like syntax to VM instructions. +Example input: + + # Simple arithmetic + x = 5 + y = 3 + z = x + y + + # Using memory + array = [] + array[0] = 42 + array[1] = array[0] * 2 + +Will compile to VM instructions like: + + 5 A! # store 5 in A register + 3 B! # store 3 in B register + A @B + # add them +""" + +import sys +import re + +class Compiler: + def __init__(self): + self.variables = {} # Maps variable names to memory locations + self.next_memory = 0 # Next available memory location + + def allocate_variable(self, name): + """Allocate memory location for a variable""" + if name not in self.variables: + self.variables[name] = self.next_memory + self.next_memory += 1 + return self.variables[name] + + def compile_assignment(self, line): + """Compile assignment statements like 'x = 5' or 'x = y + z'""" + # Remove any comments from the line + line = line.split('#')[0].strip() + + match = re.match(r'(\w+)\s*=\s*(.+)', line) + if not match: + return None + + var_name = match.group(1) + expression = match.group(2) + + print(f"# Compiling assignment: {var_name} = {expression}", file=sys.stderr) + + # First get the memory location + mem_loc = self.allocate_variable(var_name) + + # Then compile the expression + expr_code = self.compile_expression(expression) + if not expr_code: + print(f"# Error: Failed to compile expression: {expression}", file=sys.stderr) + return None + + # Generate code that: + # 1. Evaluates the expression + # 2. Duplicates the result (for storing and leaving on stack) + # 3. Stores at memory location + vm_code = [] + vm_code.extend(expr_code) # Evaluate expression + vm_code.append("DUP") # Make a copy + vm_code.append(str(mem_loc)) # Push memory location + vm_code.append("@") # Read current value (for debugging) + vm_code.append("DROP") # Drop the old value + vm_code.append("!") # Store new value + + return vm_code + + def compile_expression(self, expr): + """Compile expressions like '5', 'x + y', etc.""" + vm_code = [] + + # Remove any comments from the expression + expr = expr.split('#')[0].strip() + + # Handle simple number + if expr.isdigit(): + vm_code.append(expr) + return vm_code + + # Handle variable reference + if expr in self.variables: + vm_code.append(str(self.variables[expr])) + vm_code.append("@") + return vm_code + + # Handle binary operations + ops = { + '+': '+', + '*': '*', + '-': 'NOT +', + } + + # Try each operator + for op in ops: + if op in expr: + parts = expr.split(op, 1) + if len(parts) == 2: + left = parts[0].strip() + right = parts[1].strip() + + print(f"# Debug: left={left}, right={right}", file=sys.stderr) + + # Generate code for left operand + left_code = self.compile_expression(left) + if not left_code: + continue + vm_code.extend(left_code) + + # Generate code for right operand + right_code = self.compile_expression(right) + if not right_code: + continue + vm_code.extend(right_code) + + # Add the operation + vm_code.append(ops[op]) + return vm_code + + return vm_code + + def compile(self, source): + """Compile source code to VM instructions""" + output = [] + debug_output = [] + + for line in source.split('\n'): + line = line.strip() + if not line or line.startswith('#'): + continue + + if line == "SHOW": + output.append("SHOW") + continue + + if '=' in line: + vm_code = self.compile_assignment(line) + if vm_code: + output.extend(vm_code) + debug_output.append(f"{' '.join(vm_code)} # {line}") + if not line.startswith('result ='): # If not final result + output.append("DROP") # Drop the duplicate we left on stack + continue + + print("# Generated VM code:", file=sys.stderr) + for line in debug_output: + print(f"# {line}", file=sys.stderr) + + # Add final SHOW to see the result + output.append("SHOW") + return ' '.join(output) + +def main(): + if len(sys.argv) > 1: + with open(sys.argv[1]) as f: + source = f.read() + else: + source = sys.stdin.read() + + compiler = Compiler() + vm_code = compiler.compile(source) + print(vm_code) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/awk/vm/debug.coffee b/awk/vm/debug.coffee new file mode 100644 index 0000000..0663cec --- /dev/null +++ b/awk/vm/debug.coffee @@ -0,0 +1,6 @@ +x = 5 +SHOW +y = 3 +SHOW +z = x + y +SHOW \ No newline at end of file diff --git a/awk/vm/simple.coffee b/awk/vm/simple.coffee new file mode 100644 index 0000000..0a596a7 --- /dev/null +++ b/awk/vm/simple.coffee @@ -0,0 +1,4 @@ +x = 5 +y = 3 +z = x + y +result = z * 2 \ No newline at end of file diff --git a/awk/vm/simple_test.coffee b/awk/vm/simple_test.coffee new file mode 100644 index 0000000..8fec5ba --- /dev/null +++ b/awk/vm/simple_test.coffee @@ -0,0 +1,8 @@ +x = 5 +SHOW +y = 3 +SHOW +z = x + y # Should be 8 +SHOW +result = z * 2 # Should be 16 +SHOW \ No newline at end of file diff --git a/awk/vm/stack_test.coffee b/awk/vm/stack_test.coffee new file mode 100644 index 0000000..ab29e63 --- /dev/null +++ b/awk/vm/stack_test.coffee @@ -0,0 +1,15 @@ +# First store 5 in memory location 0 +x = 5 +SHOW + +# Then store 3 in memory location 1 +y = 3 +SHOW + +# Add them: load 5, load 3, add them +z = x + y +SHOW + +# Double z: load 8, multiply by 2 +result = z * 2 +SHOW \ No newline at end of file diff --git a/awk/vm/test.coffee b/awk/vm/test.coffee new file mode 100644 index 0000000..aecda14 --- /dev/null +++ b/awk/vm/test.coffee @@ -0,0 +1,7 @@ +# Calculate sum +x = 5 +y = 3 +z = x + y + +# Double it +result = z * 2 diff --git a/awk/vm/test_steps.coffee b/awk/vm/test_steps.coffee new file mode 100644 index 0000000..f1d0415 --- /dev/null +++ b/awk/vm/test_steps.coffee @@ -0,0 +1,15 @@ +# Step 1: Initialize x +x = 5 +SHOW + +# Step 2: Initialize y +y = 3 +SHOW + +# Step 3: Add x and y +z = x + y +SHOW + +# Step 4: Double z +result = z * 2 +SHOW \ No newline at end of file diff --git a/awk/vm/vm.awk b/awk/vm/vm.awk new file mode 100755 index 0000000..67da3e7 --- /dev/null +++ b/awk/vm/vm.awk @@ -0,0 +1,254 @@ +#!/usr/bin/awk -f + + +# Stack: DROP, DUP, OVER, PUSH, POP +# Math: + AND XOR NOT 2* 2/ multiply-step +# Call: JUMP CALL RETURN IF -IF +# Loop: NEXT UNEXT +# Register: A A! B! +# Memory: @ ! @+ !+ @B !B @P !P +# NO-OP: . + + +BEGIN { + # Initialize VM state + stack_pointer = 0 # Points to next free position + pc = 0 # Program counter + MAX_STACK = 100 # Maximum stack size + MAX_MEM = 1000 # Memory size + + # Initialize registers + A = 0 # A register + B = 0 # B register + P = 0 # P register (auxiliary pointer) + + # Stack operations + split("", stack) # Initialize stack array + split("", memory) # Initialize memory array +} + +# Stack operations +function push(value) { + if (stack_pointer >= MAX_STACK) { + print "Stack overflow!" > "/dev/stderr" + exit 1 + } + printf "# DEBUG: Pushing %d onto stack\n", value > "/dev/stderr" + stack[stack_pointer++] = value +} + +function pop() { + if (stack_pointer <= 0) { + print "Stack underflow!" > "/dev/stderr" + exit 1 + } + value = stack[--stack_pointer] + printf "# DEBUG: Popping %d from stack\n", value > "/dev/stderr" + return value +} + +# Basic stack manipulation +function op_drop() { + pop() +} + +function op_dup() { + if (stack_pointer <= 0) { + print "Stack underflow on DUP!" > "/dev/stderr" + exit 1 + } + push(stack[stack_pointer - 1]) +} + +function op_over() { + if (stack_pointer <= 1) { + print "Stack underflow on OVER!" > "/dev/stderr" + exit 1 + } + push(stack[stack_pointer - 2]) +} + +# Basic arithmetic operations +function op_add() { + b = pop() + a = pop() + result = a + b + printf "# DEBUG: Adding %d + %d = %d\n", a, b, result > "/dev/stderr" + push(result) +} + +function op_and() { + b = pop() + a = pop() + # For now, we'll just multiply as a placeholder + # In a real implementation, we'd need to implement proper bitwise AND + push(a * b) +} + +function op_xor() { + b = pop() + a = pop() + # For now, we'll just add as a placeholder + # In a real implementation, we'd need to implement proper bitwise XOR + push(a + b) +} + +function op_not() { + a = pop() + # For now, we'll just negate as a placeholder + # In a real implementation, we'd need to implement proper bitwise NOT + push(-a - 1) +} + +function op_2times() { + a = pop() + push(a * 2) +} + +function op_2div() { + a = pop() + push(int(a / 2)) +} + +function op_multiply() { + b = pop() + a = pop() + result = a * b + printf "# DEBUG: Multiplying %d * %d = %d\n", a, b, result > "/dev/stderr" + push(result) +} + +# Register operations +function op_a() { + push(A) +} + +function op_astore() { + A = pop() +} + +function op_bstore() { + B = pop() +} + +# Memory operations +function op_fetch() { + addr = pop() + value = memory[addr] + printf "# DEBUG: Fetching %d from memory[%d]\n", value, addr > "/dev/stderr" + push(value) +} + +function op_store() { + addr = pop() # First pop the address + value = pop() # Then pop the value + printf "# DEBUG: Storing %d at memory[%d]\n", value, addr > "/dev/stderr" + memory[addr] = value +} + +function op_fetchplus() { + push(memory[P++]) +} + +function op_storeplus() { + memory[P++] = pop() +} + +function op_fetchb() { + push(memory[B]) +} + +function op_storeb() { + memory[B] = pop() +} + +function op_fetchp() { + push(memory[P]) +} + +function op_storep() { + memory[P] = pop() +} + +function print_stack() { + printf "Stack: " + for (i = 0; i < stack_pointer; i++) { + printf "%d ", stack[i] + } + printf "\n" +} + +function print_state() { + print_stack() + printf "Registers: A=%d B=%d P=%d\n", A, B, P + printf "Memory[P]=%d Memory[B]=%d\n", memory[P], memory[B] + printf "Memory: " + for (i = 0; i < 4; i++) { # Show first 4 memory locations + printf "[%d]=%d ", i, memory[i] + } + printf "\n-------------------\n" +} + +function op_swap() { + if (stack_pointer < 2) { + print "Stack underflow on SWAP!" > "/dev/stderr" + exit 1 + } + # Swap the top two elements on the stack + temp = stack[stack_pointer - 1] + stack[stack_pointer - 1] = stack[stack_pointer - 2] + stack[stack_pointer - 2] = temp + printf "# DEBUG: Swapping top two stack elements\n" > "/dev/stderr" +} + +function execute_instruction(inst) { + if (inst ~ /^[0-9]+$/) { + # Numbers are pushed onto the stack + push(int(inst)) + return + } + + if (inst == "BYE") { exit 0 } # not really in the minimal spec as set out by Chuck Moore, but useful for a graceful exit. + if (inst == "DROP") { op_drop(); return } + if (inst == "DUP") { op_dup(); return } + if (inst == "OVER") { op_over(); return } # copy second item to top of stack + if (inst == "SWAP") { op_swap(); return } # swap top two stack items + if (inst == "+") { op_add(); return } + if (inst == "AND") { op_and(); return } + if (inst == "XOR") { op_xor(); return } + if (inst == "NOT") { op_not(); return } + if (inst == "2*") { op_2times(); return } # multiply-step + if (inst == "2/") { op_2div(); return } # divide-step + if (inst == "*") { op_multiply(); return } + if (inst == "A") { op_a(); return } # push A register + if (inst == "A!") { op_astore(); return } # store A register + if (inst == "B!") { op_bstore(); return } # store B register + if (inst == "@") { op_fetch(); return } # fetch from memory + if (inst == "!") { op_store(); return } # store to memory + if (inst == "@+") { op_fetchplus(); return } # fetch from memory at P+ + if (inst == "!+") { op_storeplus(); return } # store to memory at P+ + if (inst == "@B") { op_fetchb(); return } # fetch from memory at B + if (inst == "!B") { op_storeb(); return } # store to memory at B + if (inst == "@P") { op_fetchp(); return } # fetch from memory at P + if (inst == "!P") { op_storep(); return } # store to memory at P + if (inst == ".") { return } # NO-OP + if (inst == "SHOW") { print_state(); return } # show state info + + print "Unknown instruction: " inst > "/dev/stderr" + exit 1 +} + +# Main execution loop +{ + # Remove comments (everything after #) + sub(/#.*$/, "") + + # Skip empty lines after comment removal + if (NF == 0) next + + # Split the input line into words + n = split($0, words) + for (i = 1; i <= n; i++) { + execute_instruction(words[i]) + } +} diff --git a/awk/vm/vm_tests.sh b/awk/vm/vm_tests.sh new file mode 100755 index 0000000..6244c51 --- /dev/null +++ b/awk/vm/vm_tests.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +echo "Running VM tests..." +echo + +echo "Test 1: Basic register A operations" +echo "42 A! A A # Store 42 in A, then read A twice" | awk -f vm.awk +echo + +echo "Test 2: Register A and B with memory operations" +echo "100 A! 200 B! 42 @B # Store 100 in A, 200 in B, read from B's address" | awk -f vm.awk +echo + +echo "Test 3: Sequential memory operations using P register" +echo "0 !P 1 !+ 2 !+ 3 !+ 4 !+ 5 !+ # Store 1-5 sequentially using P" | awk -f vm.awk +echo "0 !P @+ @+ @+ @+ @+ # Read back values using P" | awk -f vm.awk +echo + +echo "Test 4: Complex register manipulation" +echo "42 A! A DUP B! @B # Store 42 in A, copy to B, read via B" | awk -f vm.awk +echo + +echo "Test 5: Register arithmetic" +echo "5 A! 3 B! A @B + # Store 5 in A, 3 in B, add A + mem[B]" | awk -f vm.awk +echo + +echo "Test 6: Memory pointer operations" +echo "42 0 ! 1 !P @P # Store 42 at addr 0, point P to 1, read P" | awk -f vm.awk +echo + +echo "Test 7: Register and memory interaction" +echo "10 A! 20 B! A @B ! # Store A's value at B's address" | awk -f vm.awk +echo "@B # Read from memory at B's address" | awk -f vm.awk +echo + +echo "Test 8: Demonstrating B! vs !B" +echo "100 B! # Set B register to 100" | awk -f vm.awk +echo "42 !B # Store 42 at memory location 100" | awk -f vm.awk +echo "@B # Read from memory location 100" | awk -f vm.awk +echo + +echo "Tests completed." \ No newline at end of file |