about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--awk/scheme/scheme/README.md41
-rw-r--r--awk/scheme/scheme/WHAT-NEEDS-FIXING.md49
-rwxr-xr-xawk/scheme/scheme/bin/compiler.awk719
-rwxr-xr-xawk/scheme/scheme/bin/repl4
-rwxr-xr-xawk/scheme/scheme/bin/vm.awk113
-rw-r--r--awk/scheme/scheme/scratch/OUTLINE.md138
-rw-r--r--awk/scheme/scheme/scratch/arch-notes.md133
-rwxr-xr-xawk/scheme/scheme/test/run_tests.sh21
-rw-r--r--awk/scheme/scheme/test/unit/minimal_closure_env.scm8
-rw-r--r--awk/scheme/scheme/test/unit/minimal_function_persistence.scm2
-rw-r--r--awk/scheme/scheme/test/unit/minimal_global_persistence.scm2
-rw-r--r--awk/uxn/.gitignore3
-rw-r--r--awk/uxn/README.md0
-rw-r--r--awk/uxn/awk/uxnasm.awk916
-rw-r--r--awk/uxn/ref/uxnasm.c481
-rwxr-xr-xawk/uxn/test/runner.sh105
-rw-r--r--awk/uxn/test/tal/brk.tal1
-rw-r--r--awk/uxn/test/tal/brk_test.tal1
-rw-r--r--awk/uxn/test/tal/brk_with_data.tal1
-rw-r--r--awk/uxn/test/tal/bunnymark.tal221
-rw-r--r--awk/uxn/test/tal/life.tal221
-rw-r--r--awk/uxn/test/tal/opctest.tal492
-rw-r--r--awk/uxn/test/tal/proper.tal1
-rw-r--r--awk/uxn/test/tal/simple.tal1
-rw-r--r--awk/uxn/test/tal/simple2.tal1
-rw-r--r--awk/uxn/test/tal/simple3.tal1
-rw-r--r--js/scripting-lang/FIXME.md57
-rw-r--r--js/scripting-lang/IDEAS.txt9
-rw-r--r--js/scripting-lang/NEXT-STEPS.md229
-rw-r--r--js/scripting-lang/README.md722
-rw-r--r--js/scripting-lang/input.txt3
-rw-r--r--js/scripting-lang/lang.js1763
-rw-r--r--js/scripting-lang/table_basic_test.txt51
-rw-r--r--js/scripting-lang/table_edge_cases_test.txt304
-rw-r--r--js/scripting-lang/test.txt730
-rw-r--r--js/seed/README.md15
-rwxr-xr-xjs/seed/seed75
-rw-r--r--js/seed/src/app.js8
-rw-r--r--js/seed/src/dev.js4
-rw-r--r--js/seed/src/view.js6
40 files changed, 6942 insertions, 710 deletions
diff --git a/awk/scheme/scheme/README.md b/awk/scheme/scheme/README.md
index 632e421..0b925f4 100644
--- a/awk/scheme/scheme/README.md
+++ b/awk/scheme/scheme/README.md
@@ -100,6 +100,8 @@ Test categories:
 - **Examples**: Demonstration programs
 - **Regression tests**: Edge cases
 
+*Note: The test suite is comprehensive, but some advanced closure/currying and edge-case features are still being debugged. See Known Issues below.*
+
 ## Debugging
 
 ### Enable Debug Mode
@@ -109,6 +111,11 @@ DEBUG=1 ./scheme program.scm        # File with debug
 echo "(+ 1 2)" | DEBUG=1 ./scheme   # Single expression
 ```
 
+**S-expression Debugging:**
+```bash
+DEBUG_SEXPR=1 ./scheme program.scm  # Enable detailed S-expression parsing/codegen debug output
+```
+
 ### Debug Output Shows
 - Token parsing and expression tree construction
 - VM instruction generation
@@ -116,6 +123,33 @@ echo "(+ 1 2)" | DEBUG=1 ./scheme   # Single expression
 - Environment changes and variable lookups
 - Function calls and closure operations
 
+### Command-Line Debugging Hints
+
+**View bytecode generation**:
+```bash
+echo "(+ 1 2)" | ./bin/compiler.awk
+```
+
+**Step-by-step VM execution**:
+```bash
+echo "(+ 1 2)" | ./bin/compiler.awk | DEBUG=1 ./bin/vm.awk
+```
+
+**Trace specific functions**:
+```bash
+grep -n "function_name" bin/vm.awk
+```
+
+**Check syntax errors**:
+```bash
+echo "(+ 1" | ./bin/compiler.awk  # Should show error
+```
+
+**Compare outputs**:
+```bash
+diff <(echo "(+ 1 2)" | ./bin/compiler.awk) <(echo "(+ 2 1)" | ./bin/compiler.awk)
+```
+
 ### Common Issues
 
 **"Unknown expression type"**: Usually a parsing issue. Check for unsupported syntax.
@@ -130,7 +164,6 @@ echo "(+ 1 2)" | DEBUG=1 ./scheme   # Single expression
 
 **Not Supported**:
 - Floating point numbers (integers only)
-- Nested lambda definitions
 - Proper tail recursion
 - Garbage collection
 - Macros
@@ -192,4 +225,8 @@ echo "5 3 + ." | awk -f forth.awk  # => Result: N:8
 - `bin/vm.awk` - Virtual machine implementation
 - `bin/repl` - Interactive REPL
 - `test/` - Comprehensive test suite
-- `scratch/forth/` - Forth implementation for VM validation
\ No newline at end of file
+- `scratch/forth/` - Forth implementation for VM validation
+
+## Known Issues
+
+- Some advanced closure/currying and higher-order function tests are still failing and are under active investigation. See WHAT-NEEDS-FIXING.md for current status and debugging plan.
diff --git a/awk/scheme/scheme/WHAT-NEEDS-FIXING.md b/awk/scheme/scheme/WHAT-NEEDS-FIXING.md
new file mode 100644
index 0000000..f8b5a0c
--- /dev/null
+++ b/awk/scheme/scheme/WHAT-NEEDS-FIXING.md
@@ -0,0 +1,49 @@
+# What Needs Fixing
+
+## Current State (as of latest debugging)
+
+- **Testing Infrastructure:**
+  - Test runner and environment variable handling are robust; DEBUG/DEBUG_SEXPR work as intended.
+  - Only 6/51 tests pass; 45 fail, including basic, integration, closure, and higher-order function tests.
+  - Most failures are silent (no assertion errors/output), indicating expressions are not being evaluated or CALLs are mis-emitted.
+
+- **Recent Fixes and Attempts:**
+  - Refactored top-level CALL emission logic in the compiler to match idiomatic Scheme/Lisp behavior:
+    - Now, CALL is only emitted for top-level compound expressions whose first symbol is not a special form (define, let, lambda, if, cond, and, or, not).
+    - Special forms are handled by the compiler, not as function calls.
+  - Helper function added to extract the first symbol from a compound expression string.
+  - Type-tracking logic for top-level CALL emission has been removed for simplicity and robustness.
+  - This approach is modeled after working reference implementations (e.g., [maryrosecook/littlelisp](https://raw.githubusercontent.com/maryrosecook/littlelisp/refs/heads/master/littlelisp.js)).
+
+- **Current Symptoms:**
+  - Many tests still fail, including basic arithmetic, map, closures, and higher-order functions.
+  - Failures are mostly silent: no assertion errors, no output, suggesting expressions are not being evaluated or results are not printed.
+  - Some improvement: a few more tests pass compared to previous attempts, but the majority still fail.
+
+## What Has Been Ruled Out
+- The VM and test runner are not the source of the remaining failures.
+- S-expression parsing and body handling are robust and not the source of the bug.
+- The new CALL emission logic is more correct, but not sufficient to fix all test failures.
+- The bug is not due to missing CALLs for assert/display/user-defined function calls at the top level.
+
+## Next Steps: Plan for Remaining Bugs
+
+1. **Targeted Debugging of Failing Tests:**
+   - Focus on a representative failing test (e.g., basic_numeric_operations, closures, or a simple assert).
+   - Inspect the generated assembly and VM output for these tests to confirm whether CALL is being emitted and executed as expected.
+   - Check for missing PRINT/DISPLAY or incorrect stack state after CALL.
+
+2. **Special Form Handling Review:**
+   - Ensure that all special forms (let, lambda, if, cond, etc.) are handled correctly and do not result in spurious CALLs or missed evaluation.
+   - Confirm that nested expressions within special forms are compiled and evaluated as expected.
+
+3. **Test and Iterate:**
+   - After each fix, re-run the minimal and representative tests to confirm progress.
+   - Once minimal tests pass, move to more complex closure and higher-order function tests.
+
+4. **Document Findings:**
+   - Update this file after each major fix or discovery, so the next debugging session has a clear starting point.
+
+## Goal (Restated)
+- Ensure top-level and nested expression evaluation is correct for all cases, including special forms, closures, and function applications.
+- Systematically fix all remaining failing tests by following the above plan. 
\ No newline at end of file
diff --git a/awk/scheme/scheme/bin/compiler.awk b/awk/scheme/scheme/bin/compiler.awk
index 681610e..dec4c22 100755
--- a/awk/scheme/scheme/bin/compiler.awk
+++ b/awk/scheme/scheme/bin/compiler.awk
@@ -22,6 +22,14 @@
 # - Stack clearing between expressions to prevent argument pollution
 
 BEGIN {
+
+    # Debug mode configuration
+    # FIXME: Don't leave in, trigger on flag
+    print "[DEBUG_SEXPR] BEGIN block reached" > "/dev/stderr"
+    print "[DEBUG_SEXPR] ENVIRON[\"DEBUG_SEXPR\"] = " ENVIRON["DEBUG_SEXPR"] > "/dev/stderr"
+    DEBUG_SEXPR = (ENVIRON["DEBUG_SEXPR"] == "1") ? 1 : 0
+    if (DEBUG_SEXPR) print "[DEBUG_SEXPR] DEBUG_SEXPR is enabled" > "/dev/stderr"
+
     # Compiler maintains internal state for code generation
     curr_token = ""      # Current token being processed by lexer
     input_buffer = ""    # Buffer for input text being tokenized
@@ -32,6 +40,8 @@ BEGIN {
     # AWK FEATURE: ENVIRON is a built-in array containing environment variables
     # Unlike JS process.env, this is automatically available in awk
     DEBUG = (ENVIRON["DEBUG"] == "1") ? 1 : 0
+    error_flag = 0  # Set to 1 if any error occurs
+    DEBUG_SEXPR = (ENVIRON["DEBUG_SEXPR"] == "1") ? 1 : 0
 }
 
 # Debug logging helper function
@@ -45,6 +55,7 @@ function debug(msg) {
 # This is awk's main input processing loop - every line from stdin/files goes here
 # In JS, you'd need to explicitly read lines from a stream
 {
+    if (DEBUG_SEXPR) print "[DEBUG_SEXPR] Reading line: [" $0 "]" > "/dev/stderr"
     if (program != "") program = program "\n"
     program = program $0  # $0 is the current line being processed
 }
@@ -57,6 +68,11 @@ END {
     
     # Parse and compile each expression in the program
     split_expressions(program)
+    debug("END block: error_flag=" error_flag)
+    # If error_flag was set, exit 1
+    if (error_flag) exit 1
+    debug("END block: exiting")
+    exit 0
 }
 
 # Splits input into individual Scheme expressions
@@ -68,114 +84,120 @@ END {
 #
 # The function processes the entire program text and identifies complete
 # expressions that can be compiled independently
-function split_expressions(prog, current, paren_count, i, c, expr, cleaned) {
+function split_expressions(prog, current, paren_count, i, c, expr, cleaned, lines, n, line, in_string, out, j) {
     current = ""
     paren_count = 0
-    
-    # Clean up the input:
-    # 1. Remove comments (lines starting with ;; or ;)
-    # 2. Remove comments within lines (text between ; and end of line)
-    # 3. Normalize whitespace
-    # 4. Trim leading/trailing whitespace
-    cleaned = prog
-    # AWK FEATURE: gsub() is a built-in function for global substitution (like replaceAll in JS)
-    # gsub(pattern, replacement, target) - modifies target in place and returns count
-    gsub(/^[ \t]*;;.*$/, "", cleaned)  # Remove lines starting with ;;
-    gsub(/^[ \t]*;.*$/, "", cleaned)   # Remove lines starting with ;
-    gsub(/;[^"]*$/, "", cleaned)       # Remove comments at end of lines (but not in strings)
-    gsub(/[ \t\n]+/, " ", cleaned)     # Normalize whitespace to single spaces
-    # AWK FEATURE: sub() is like gsub() but only replaces the first occurrence
-    sub(/^[ \t\n]+/, "", cleaned)      # Trim leading whitespace
-    sub(/[ \t\n]+$/, "", cleaned)      # Trim trailing whitespace
-    
+    n = split(prog, lines, "\n")
+    out = ""
+    for (j = 1; j <= n; j++) {
+        line = lines[j]
+        if (line ~ /^[ \t]*;/) continue
+        in_string = 0
+        cleaned_line = ""
+        for (i = 1; i <= length(line); i++) {
+            c = substr(line, i, 1)
+            if (c == "\"") in_string = !in_string
+            if (!in_string && c == ";") break
+            cleaned_line = cleaned_line c
+        }
+        out = out cleaned_line "\n"
+    }
+    cleaned = out
     debug("Cleaned program: [" cleaned "]")
-    
     if (cleaned == "") return
-    
-    # Parse expressions by tracking parenthesis nesting and string literals
-    # This approach ensures that parentheses inside strings don't affect
-    # expression boundaries, and that comments are properly handled
-    # AWK FEATURE: length(string) returns the length of a string
-    # Unlike JS string.length, this is a function call, not a property
-    in_string = 0  # Track if we're inside a string literal
-    
+    if (cleaned == "") return
+    in_string = 0
     for (i = 1; i <= length(cleaned); i++) {
         c = substr(cleaned, i, 1)
-        
-        # Handle string literals
         if (c == "\"" && !in_string) {
             in_string = 1
             if (paren_count == 0) current = ""
         } else if (c == "\"" && in_string) {
             in_string = 0
         }
-        
         if (c == "(" && !in_string) {
             if (paren_count == 0) current = ""
             paren_count++
         }
-        
         current = current c
-        
         if (c == ")" && !in_string) {
             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
+                program = expr
+                expr_str = expr
                 expr = parse_expr()
-                compile_expr(expr)
-                # Clear stack between expressions to prevent pollution
-                print "CLEAR_STACK"  # Clear stack between expressions
+                if (substr(expr_str, 1, 1) == "(") {
+                    op = extract_first_symbol(expr_str)
+                    if (op != "define" && op != "let" && op != "lambda" && op != "if" && op != "cond" && op != "and" && op != "or" && op != "not") {
+                        compile_expr(expr)
+                        print "CALL"
+                    } else {
+                        compile_expr(expr)
+                    }
+                } else {
+                    compile_expr(expr)
+                }
+                print "CLEAR_STACK"
                 current = ""
             }
         }
-        
-        # Handle atomic expressions (not in parentheses or strings)
         if (paren_count == 0 && !in_string && c == " " && current != "") {
-            # We've reached a space after an atomic expression
             expr = current
             sub(/^\s+/, "", expr)
             sub(/\s+$/, "", expr)
-            
             if (expr != "") {
                 debug("Processing atomic expression: [" expr "]")
-                program = expr  # Set for parser
+                program = expr
+                expr_str = expr
                 expr = parse_expr()
-                compile_expr(expr)
-                # Clear stack between expressions to prevent pollution
-                print "CLEAR_STACK"  # Clear stack between expressions
+                if (substr(expr_str, 1, 1) == "(") {
+                    op = extract_first_symbol(expr_str)
+                    if (op != "define" && op != "let" && op != "lambda" && op != "if" && op != "cond" && op != "and" && op != "or" && op != "not") {
+                        compile_expr(expr)
+                        print "CALL"
+                    } else {
+                        compile_expr(expr)
+                    }
+                } else {
+                    compile_expr(expr)
+                }
+                print "CLEAR_STACK"
             }
             current = ""
         }
     }
-    
-    # Handle the last expression if it's atomic
     if (paren_count == 0 && !in_string && current != "") {
         expr = current
         sub(/^\s+/, "", expr)
         sub(/\s+$/, "", expr)
-        
         if (expr != "") {
             debug("Processing final atomic expression: [" expr "]")
-            program = expr  # Set for parser
+            program = expr
+            expr_str = expr
             expr = parse_expr()
-            compile_expr(expr)
-            # Clear stack after the final expression
+            if (substr(expr_str, 1, 1) == "(") {
+                op = extract_first_symbol(expr_str)
+                if (op != "define" && op != "let" && op != "lambda" && op != "if" && op != "cond" && op != "and" && op != "or" && op != "not") {
+                    compile_expr(expr)
+                    print "CALL"
+                } else {
+                    compile_expr(expr)
+                }
+            } else {
+                compile_expr(expr)
+            }
             print "CLEAR_STACK"
         }
     }
-    
-    # Check for incomplete expressions
     if (paren_count > 0) {
+        debug("paren_count at end of split_expressions: " paren_count)
         error("Unmatched opening parentheses - incomplete expression")
+        exit 1
     }
-    
-    # Add final HALT instruction
     print "HALT"
 }
 
@@ -325,32 +347,43 @@ function parse_list(result, expr) {
 # - Supporting both simple calls and complex nested expressions
 #
 # Handles nested expressions correctly by tracking parenthesis nesting
-function split_expr(expr, i, len, c, op, args, paren_count) {
-    # AWK FEATURE: length(string) returns the length of a string
-    # Unlike JS string.length, this is a function call, not a property
+function split_expr(expr, i, len, c, op, args, paren_count, j, c2) {
     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
+    op = ""
+    if (substr(expr, 1, 1) == "(") {
+        # Operator is a parenthesized expression
+        paren_count = 1
+        for (i = 2; i <= len; i++) {
+            c = substr(expr, i, 1)
+            if (c == "(") paren_count++
+            if (c == ")") paren_count--
+            if (paren_count == 0) {
+                op = substr(expr, 1, i)
+                # Skip any whitespace after the closing paren
+                j = i + 1
+                while (j <= len && (substr(expr, j, 1) == " " || substr(expr, j, 1) == "\t")) j++
+                args = substr(expr, j)
+                break
+            }
+        }
+    } else {
+        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 = ""
         }
-        if (c == "(") paren_count++
-        if (c == ")") paren_count--
-    }
-    
-    if (!op) {
-        op = expr
-        args = ""
     }
-    
     debug("Split expr: op=" op " args=" args)
-    # AWK FEATURE: SUBSEP is a built-in variable used as separator for array indices
-    # When you use array[key1,key2], awk internally stores it as array[key1 SUBSEP key2]
-    # This allows multi-dimensional arrays in awk (though they're really single-dimensional)
     return op SUBSEP args
 }
 
@@ -415,35 +448,23 @@ 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
-    # AWK FEATURE: ~ is the regex match operator (like /pattern/.test() in JS)
-    # The pattern is a regex literal, not a string
     if (op ~ /^\(lambda /) {
-        # This is a lambda function call
-        # First compile all arguments
         for (i = 1; i <= nargs; i++) {
             compile_expr(arg_array[i])
         }
-        
-        # Then compile the lambda function (this will push the function name)
         compile_expr(op)
-        
-        # Call the function - the lambda name is now on top of stack
         print "CALL"
-        return
+        return "function"
     }
-    
-    # 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"
+        return "value"
     }
     else if (op == "-") {
-        # Compile arguments
         for (i = 1; i <= nargs; i++) {
             compile_expr(arg_array[i])
         }
@@ -453,14 +474,15 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         }
         for (i = 1; i < nargs; i++)
             print "SUB"
+        return "value"
     }
     else if (op == "*") {
-        # Compile arguments
         for (i = 1; i <= nargs; i++) {
             compile_expr(arg_array[i])
         }
         for (i = 1; i < nargs; i++)
             print "MUL"
+        return "value"
     }
     else if (op == "/") {
         if (nargs < 2) error("/ requires at least 2 arguments")
@@ -469,6 +491,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         }
         for (i = 1; i < nargs; i++)
             print "DIV"
+        return "value"
     }
     else if (op == "modulo" || op == "%") {
         if (nargs != 2) error("modulo requires 2 arguments")
@@ -478,6 +501,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP modulo"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
     else if (op == "expt") {
         if (nargs != 2) error("expt requires 2 arguments")
@@ -487,6 +511,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP expt"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
     else if (op == "abs") {
         if (nargs != 1) error("abs requires 1 argument")
@@ -494,6 +519,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP abs"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
     else if (op == "min") {
         if (nargs != 2) error("min requires 2 arguments")
@@ -503,6 +529,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP min"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
     else if (op == "max") {
         if (nargs != 2) error("max requires 2 arguments")
@@ -512,42 +539,43 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP max"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
     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"
+        return "value"
     }
     else if (op == "car") {
         if (nargs != 1) error("car requires 1 argument")
-        # Compile argument
         compile_expr(arg_array[1])
         print "CAR"
+        return "value"
     }
     else if (op == "cdr") {
         if (nargs != 1) error("cdr requires 1 argument")
-        # Compile argument
         compile_expr(arg_array[1])
         print "CDR"
+        return "value"
     }
     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"
+        return "value"
     }
     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"
+        return "value"
     }
     # Standard library functions
     else if (op == "null?") {
@@ -556,6 +584,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP null?"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
     else if (op == "pair?") {
         if (nargs != 1) error("pair? requires 1 argument")
@@ -563,6 +592,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP pair?"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
     else if (op == "number?") {
         if (nargs != 1) error("number? requires 1 argument")
@@ -570,6 +600,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP number?"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
     else if (op == "string?") {
         if (nargs != 1) error("string? requires 1 argument")
@@ -577,6 +608,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP string?"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
     else if (op == "boolean?") {
         if (nargs != 1) error("boolean? requires 1 argument")
@@ -584,6 +616,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP boolean?"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
     else if (op == "symbol?") {
         if (nargs != 1) error("symbol? requires 1 argument")
@@ -591,6 +624,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP symbol?"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
     else if (op == "zero?") {
         if (nargs != 1) error("zero? requires 1 argument")
@@ -598,68 +632,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP zero?"
         print "GET_VALUE"
         print "CALL"
-    }
-    else if (op == "positive?") {
-        if (nargs != 1) error("positive? requires 1 argument")
-        compile_expr(arg_array[1])
-        print "LOOKUP positive?"
-        print "GET_VALUE"
-        print "CALL"
-    }
-    else if (op == "negative?") {
-        if (nargs != 1) error("negative? requires 1 argument")
-        compile_expr(arg_array[1])
-        print "LOOKUP negative?"
-        print "GET_VALUE"
-        print "CALL"
-    }
-    else if (op == "length") {
-        if (nargs != 1) error("length requires 1 argument")
-        compile_expr(arg_array[1])
-        print "LOOKUP length"
-        print "GET_VALUE"
-        print "CALL"
-    }
-    else if (op == "cadr") {
-        if (nargs != 1) error("cadr requires 1 argument")
-        compile_expr(arg_array[1])
-        print "LOOKUP cadr"
-        print "GET_VALUE"
-        print "CALL"
-    }
-    else if (op == "caddr") {
-        if (nargs != 1) error("caddr requires 1 argument")
-        compile_expr(arg_array[1])
-        print "LOOKUP caddr"
-        print "GET_VALUE"
-        print "CALL"
-    }
-    else if (op == "list-ref") {
-        if (nargs != 2) error("list-ref requires 2 arguments")
-        for (i = 1; i <= nargs; i++) {
-            compile_expr(arg_array[i])
-        }
-        print "LOOKUP list-ref"
-        print "GET_VALUE"
-        print "CALL"
-    }
-    else if (op == "list-tail") {
-        if (nargs != 2) error("list-tail requires 2 arguments")
-        for (i = 1; i <= nargs; i++) {
-            compile_expr(arg_array[i])
-        }
-        print "LOOKUP list-tail"
-        print "GET_VALUE"
-        print "CALL"
-    }
-    else if (op == "append") {
-        if (nargs != 2) error("append requires 2 arguments")
-        for (i = 1; i <= nargs; i++) {
-            compile_expr(arg_array[i])
-        }
-        print "LOOKUP append"
-        print "GET_VALUE"
-        print "CALL"
+        return "value"
     }
     else if (op == "list") {
         for (i = 1; i <= nargs; i++) {
@@ -668,6 +641,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP list"
         print "GET_VALUE"
         print "CALL_WITH_ARGS " nargs
+        return "value"
     }
     else if (op == "reverse") {
         if (nargs != 1) error("reverse requires 1 argument")
@@ -677,6 +651,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP reverse"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
     else if (op == "member") {
         if (nargs != 2) error("member requires 2 arguments")
@@ -686,6 +661,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP member"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
     else if (op == "map") {
         if (nargs != 2) error("map requires 2 arguments")
@@ -695,6 +671,7 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP map"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
     else if (op == "filter") {
         if (nargs != 2) error("filter requires 2 arguments")
@@ -704,14 +681,15 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP filter"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
-    # String operations
     else if (op == "string-length") {
         if (nargs != 1) error("string-length requires 1 argument")
         compile_expr(arg_array[1])
         print "LOOKUP string-length"
         print "GET_VALUE"
         print "CALL"
+        return "value"
     }
     else if (op == "string-append") {
         if (nargs < 2) error("string-append requires at least 2 arguments")
@@ -721,65 +699,32 @@ function compile_primitive_call(op, args, arg_array, nargs, i) {
         print "LOOKUP string-append"
         print "GET_VALUE"
         print "CALL_WITH_ARGS " nargs
+        return "value"
     }
-    else if (op == "string-ref") {
-        if (nargs != 2) error("string-ref requires 2 arguments")
-        for (i = 1; i <= nargs; i++) {
-            compile_expr(arg_array[i])
-        }
-        print "LOOKUP string-ref"
-        print "GET_VALUE"
-        print "CALL"
-    }
-    else if (op == "substring") {
-        if (nargs != 3) error("substring requires 3 arguments")
-        for (i = 1; i <= nargs; i++) {
-            compile_expr(arg_array[i])
-        }
-        print "LOOKUP substring"
-        print "GET_VALUE"
-        print "CALL"
-    }
-    else if (op == "string=?") {
-        if (nargs != 2) error("string=? requires 2 arguments")
-        for (i = 1; i <= nargs; i++) {
-            compile_expr(arg_array[i])
-        }
-        print "LOOKUP string=?"
-        print "GET_VALUE"
-        print "CALL"
-    }
-    else if (op == "string<?") {
-        if (nargs != 2) error("string<? requires 2 arguments")
+    else if (op == "assert" || op == "display" || op == "error" || op == "print") {
         for (i = 1; i <= nargs; i++) {
             compile_expr(arg_array[i])
         }
-        print "LOOKUP string<?"
-        print "GET_VALUE"
-        print "CALL"
-    }
-    else if (op == "string>?") {
-        if (nargs != 2) error("string>? requires 2 arguments")
-        for (i = 1; i <= nargs; i++) {
-            compile_expr(arg_array[i])
-        }
-        print "LOOKUP string>?"
+        print "LOOKUP " op
         print "GET_VALUE"
         print "CALL"
+        return "function"
     }
     else {
-        # Function call for user-defined functions
+        # Function call for user-defined functions or higher-order/callable expressions
         debug("Function call: " op)
-        # First compile arguments
         for (i = 1; i <= nargs; i++) {
             compile_expr(arg_array[i])
         }
-        # Then look up the function name
-        print "LOOKUP " op
-        # Get the actual function name
-        print "GET_VALUE"
-        # Call the function
+        if (substr(op, 1, 1) == "(") {
+            if (DEBUG_SEXPR) print "[DEBUG_SEXPR] compile_primitive_call: compiling operator expr: [" op "]" > "/dev/stderr"
+            compile_expr(op)
+        } else {
+            print "LOOKUP " op
+            print "GET_VALUE"
+        }
         print "CALL"
+        return "function"
     }
 }
 
@@ -826,55 +771,33 @@ function split_bindings(bindings, binding_array, count, current, paren_count, i,
 }
 
 # 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
+function compile_let(args, bindings, body, binding_array, nbindings, i, var, val, binding_parts, sexprs, nsexprs, j, expr, last_type) {
     if (substr(args, 1, 1) != "(") error("Malformed let expression")
-    
-    # Find matching closing parenthesis for bindings
     paren_count = 1
     i = 2
-    # AWK FEATURE: length(string) returns the length of a string
-    # Unlike JS string.length, this is a function call, not a property
     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
+    bindings = substr(args, 2, i - 3)
     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
-                # Pass the entire lambda expression to compile_lambda
                 compile_lambda(val)
-                # Store the function name in the environment
                 print "STORE " var
             } else {
                 compile_expr(val)
@@ -885,122 +808,115 @@ function compile_let(args, bindings, body, binding_array, nbindings, i, var, val
             print "STORE " var
         }
     }
-    
-    # Compile the body
-    compile_expr(body)
-    
-    # Clean up bindings AFTER evaluating body
+    nsexprs = split_sexpressions(body, sexprs)
+    if (DEBUG_SEXPR) {
+        printf("[DEBUG_SEXPR] compile_let: splitting body, found %d expressions\n", nsexprs) > "/dev/stderr"
+        for (j = 1; j <= nsexprs; j++) {
+            printf("[DEBUG_SEXPR]   %d: [%s]\n", j, sexprs[j]) > "/dev/stderr"
+        }
+    }
+    last_type = "value"
+    for (j = 1; j <= nsexprs; j++) {
+        expr = sexprs[j]
+        sub(/^[ \t\n]+/, "", expr)
+        sub(/[ \t\n]+$/, "", expr)
+        if (DEBUG_SEXPR) printf("[DEBUG_SEXPR]   let body expr: [%s]\n", expr) > "/dev/stderr"
+        last_type = compile_expr(expr)
+    }
     for (i = nbindings; i >= 1; i--) {
         print "POP_ENV"
     }
+    return last_type
 }
 
 # 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
+function compile_define(args, name, params, body, param_array, nparams, i, paren_start, paren_end, sexprs, nsexprs, j, expr, is_define, last_body_idx) {
+    if (DEBUG_SEXPR) print "[DEBUG_SEXPR] compile_define called with args: [" args "]" > "/dev/stderr"
     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)
+    print "STORE from_define"
     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
-    # A function definition has the form: (define name (param1 param2) body)
-    # A variable definition has the form: (define name value)
     debug("compile_define: args = [" args "]")
     if (substr(args, 1, 1) == "(") {
-        # Check if this is a function definition or a variable definition with function call
-        # Look for the pattern: (name ...) where name is not a function call
-        # For (list 1), we need to check if the first word after ( is a function name
-        paren_count = 0
-        i = 1
-        first_word = ""
-        while (i <= length(args)) {
+        paren_count = 1
+        close_paren = 0
+        for (i = 2; i <= length(args); i++) {
             c = substr(args, i, 1)
             if (c == "(") paren_count++
             if (c == ")") paren_count--
-            if (c == " " && paren_count == 1) {
-                # Found a space inside the first parentheses
-                # Check if the first word is a function name (like list, cons, etc.)
-                if (first_word == "list" || first_word == "cons" || first_word == "append" || 
-                    first_word == "reverse" || first_word == "member" || first_word == "length" ||
-                    first_word == "car" || first_word == "cdr" || first_word == "cadr" || 
-                    first_word == "caddr" || first_word == "list-ref" || first_word == "list-tail" ||
-                    first_word == "null?" || first_word == "pair?" || first_word == "lambda" ||
-                    first_word == "map" || first_word == "filter") {
-                    # This is a variable definition with function call value
-                    debug("Defining variable with function call value: " name " with value: " args)
-                    compile_expr(args)  # Compile the value
-                    print "STORE " name  # Store the variable
-                    return
+            if (paren_count == 0) { close_paren = i; break }
+        }
+        if (close_paren == 0) error("Unmatched parenthesis in define")
+        rest = substr(args, close_paren + 1)
+        sub(/^[ \t\n]+/, "", rest)
+        if (rest == "") {
+            if (DEBUG_SEXPR) print "[DEBUG_SEXPR] variable definition (parenthesized value): [" name "] value: [" args "]" > "/dev/stderr"
+            compile_expr(args)
+            print "STORE " name
+            return
+        } else {
+            params = substr(args, 2, close_paren - 2)
+            body = rest
+            if (DEBUG_SEXPR) print "[DEBUG_SEXPR] function definition: [" name "] params: [" params "] body: [" body "]" > "/dev/stderr"
+            print "LABEL " name
+            nparams = split(params, param_array, " ")
+            for (i = 1; i <= nparams; i++) {
+                print "STORE " param_array[i]
+            }
+            nsexprs = split_sexpressions(body, sexprs)
+            if (DEBUG_SEXPR) {
+                printf("[DEBUG_SEXPR] compile_define: splitting body, found %d expressions\n", nsexprs) > "/dev/stderr"
+                for (j = 1; j <= nsexprs; j++) {
+                    printf("[DEBUG_SEXPR]   %d: [%s]\n", j, sexprs[j]) > "/dev/stderr"
+                }
+            }
+            last_body_idx = 0
+            for (j = 1; j <= nsexprs; j++) {
+                expr = sexprs[j]
+                sub(/^[ \t\n]+/, "", expr)
+                sub(/[ \t\n]+$/, "", expr)
+                is_define = (substr(expr, 2, 6) == "define")
+                if (is_define) {
+                    if (DEBUG_SEXPR) printf("[DEBUG_SEXPR]   local define: [%s]\n", expr) > "/dev/stderr"
+                    compile_expr(expr)
                 } else {
-                    # This is a function definition with parameters
-                    break
+                    last_body_idx = j
                 }
             }
-            if (paren_count == 1 && c != "(") {
-                first_word = first_word c
+            for (j = 1; j <= nsexprs; j++) {
+                expr = sexprs[j]
+                sub(/^[ \t\n]+/, "", expr)
+                sub(/[ \t\n]+$/, "", expr)
+                is_define = (substr(expr, 2, 6) == "define")
+                if (!is_define && expr != "") {
+                    if (DEBUG_SEXPR) printf("[DEBUG_SEXPR]   body expr: [%s]\n", expr) > "/dev/stderr"
+                    compile_expr(expr)
+                }
             }
-            i++
-        }
-        
-        # 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
-        # AWK FEATURE: split(string, array, separator) splits string into array elements
-        # Unlike JS string.split() which returns an array, this populates an existing array
-        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"
+            for (i = nparams; i >= 1; i--) {
+                print "POP_ENV"
+            }
+            print "RETURN"
+            return
         }
-        print "RETURN"
     } else {
-        # Variable definition
-        debug("Defining variable: " name " with value: " args)
-        compile_expr(args)  # Compile the value
-        print "STORE " name  # Store the variable
+        if (DEBUG_SEXPR) print "[DEBUG_SEXPR] variable definition: [" name "] value: [" args "]" > "/dev/stderr"
+        compile_expr(args)
+        print "STORE " name
     }
 }
 
 # 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
+function compile_lambda(args, params, body, param_array, nparams, i, lambda_name, expr, op, rest, sexprs, nsexprs, j, is_define, last_body_idx) {
+    if (DEBUG_SEXPR) print "[DEBUG_SEXPR] compile_lambda called" > "/dev/stderr"
     lambda_name = "__lambda_" next_label++
-    
     debug("compile_lambda: args = [" args "]")
-    
-    # Handle both full lambda expression and just the arguments part
     if (substr(args, 1, 7) == "(lambda") {
         debug("compile_lambda: detected full lambda expression")
-        # Full lambda expression: (lambda (params) body)
-        args = substr(args, 8)  # Remove "(lambda"
+        args = substr(args, 8)
         debug("compile_lambda: after removing (lambda: [" args "]")
-        # Find the closing parenthesis of the entire lambda expression
         paren_count = 0
         i = 1
         while (i <= length(args)) {
@@ -1008,66 +924,70 @@ function compile_lambda(args, params, body, param_array, nparams, i, lambda_name
             if (c == "(") paren_count++
             if (c == ")") {
                 paren_count--
-                if (paren_count == -1) break  # Found the closing paren of the lambda
+                if (paren_count == -1) break
             }
             i++
         }
         if (paren_count != -1) error("Unmatched parenthesis in lambda")
-        args = substr(args, 1, i - 1)  # Remove the closing parenthesis
+        args = substr(args, 1, i - 1)
         debug("compile_lambda: after removing closing paren: [" args "]")
-        # Remove leading whitespace
         sub(/^[ \t\n]+/, "", args)
         debug("compile_lambda: after removing leading whitespace: [" args "]")
     }
-    
-    # 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
-    # AWK FEATURE: length(string) returns the length of a string
-    # Unlike JS string.length, this is a function call, not a property
     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
+    params = substr(args, 2, i - 3)
     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
+    if (DEBUG_SEXPR) print "[DEBUG_SEXPR] body to split_sexpressions: [" body "]" > "/dev/stderr"
     print "LABEL " lambda_name
-    
-    # Process parameters
-    # AWK FEATURE: split(string, array, separator) splits string into array elements
-    # Unlike JS string.split() which returns an array, this populates an existing array
     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
+    nsexprs = split_sexpressions(body, sexprs)
+    if (DEBUG_SEXPR) {
+        printf("[DEBUG_SEXPR] compile_lambda: processing %d expressions\n", nsexprs) > "/dev/stderr"
+    }
+    last_body_idx = 0
+    for (j = 1; j <= nsexprs; j++) {
+        expr = sexprs[j]
+        sub(/^[ \t\n]+/, "", expr)
+        sub(/[ \t\n]+$/, "", expr)
+        is_define = (substr(expr, 2, 6) == "define")
+        if (is_define) {
+            if (DEBUG_SEXPR) printf("[DEBUG_SEXPR]   local define: [%s]\n", expr) > "/dev/stderr"
+            compile_expr(expr)
+        } else {
+            last_body_idx = j
+        }
+    }
+    for (j = 1; j <= nsexprs; j++) {
+        expr = sexprs[j]
+        sub(/^[ \t\n]+/, "", expr)
+        sub(/[ \t\n]+$/, "", expr)
+        is_define = (substr(expr, 2, 6) == "define")
+        if (!is_define && expr != "") {
+            if (DEBUG_SEXPR) printf("[DEBUG_SEXPR]   body expr: [%s]\n", expr) > "/dev/stderr"
+            compile_expr(expr)
+        }
+    }
     for (i = nparams; i >= 1; i--) {
         print "POP_ENV"
     }
-    print "RETURN"
-    
-    # Create closure that captures current environment
     print "CAPTURE_ENV " lambda_name
     print "PUSH_CONST CLOSURE:" lambda_name ":ENV_ID"
+    print "RETURN"
 }
 
 # Compile if expression: (if condition then-expr else-expr)
@@ -1293,53 +1213,54 @@ function compile_not(args,    expr) {
 }
 
 # Main expression compiler - dispatches based on expression type
-function compile_expr(expr,    split_result, op, args) {
+function compile_expr(expr,    split_result, op, args, result_type) {
+    if (DEBUG_SEXPR) print "[DEBUG_SEXPR] compile_expr called with expr: [" expr "]" > "/dev/stderr"
     debug("Compiling expression: " expr)
     
     # Handle empty expressions
     if (expr == "") {
         debug("Skipping empty expression")
-        return
+        return "value"
     }
     
     # Handle comment lines
     if (expr ~ /^[ \t]*;;/ || expr ~ /^[ \t]*;/) {
         debug("Skipping comment line: [" expr "]")
-        return
+        return "value"
     }
     
     # Handle string literals
     if (substr(expr, 1, 1) == "\"") {
         compile_string(expr)
-        return
+        return "value"
     }
     
     # Handle numeric literals
     if (expr ~ /^-?[0-9]+$/) {
         compile_number(expr)
-        return
+        return "value"
     }
     
     # Handle nil constant
     if (expr == "nil") {
         print "PUSH_CONST NIL:"
-        return
+        return "value"
     }
     
     # Handle boolean literals
     if (expr == "#t") {
         print "PUSH_CONST B:1"
-        return
+        return "value"
     }
     if (expr == "#f") {
         print "PUSH_CONST B:0"
-        return
+        return "value"
     }
     
-    # Handle variable lookup
+    # Handle variable lookup (only if not a parenthesized expression)
     if (expr ~ /^[a-zA-Z_][a-zA-Z0-9_?-]*$/) {
         print "LOOKUP " expr
-        return
+        return "value"
     }
     
     # Handle compound expressions (lists)
@@ -1348,34 +1269,116 @@ function compile_expr(expr,    split_result, op, args) {
         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 (DEBUG_SEXPR) print "[DEBUG_SEXPR] split_expr op: [" op "] args: [" args "]" > "/dev/stderr"
         if (op == "define") {
             compile_define(args)
+            return "value"
         } else if (op == "let") {
-            compile_let(args)
+            result_type = compile_let(args)
+            return result_type
         } else if (op == "lambda") {
             compile_lambda(args)
+            return "function"
         } else if (op == "if") {
             compile_if(args)
+            # TODO: Could be value or function, but usually value
+            return "value"
         } else if (op == "cond") {
             compile_cond(args)
+            # TODO: Could be value or function, but usually value
+            return "value"
         } else if (op == "and") {
             compile_and(args)
+            return "value"
         } else if (op == "or") {
             compile_or(args)
+            return "value"
         } else if (op == "not") {
             compile_not(args)
+            return "value"
         } else {
-            compile_primitive_call(op, args)
+            return compile_primitive_call(op, args)
         }
-        return
     }
     
     error("Unknown expression type: " expr)
+    return "value"
 }
 
 # Error reporting helper
 function error(msg) {
     print "Error: " msg > "/dev/stderr"
+    error_flag = 1
     exit 1
-}
\ No newline at end of file
+}
+
+# Split a string into top-level S-expressions (returns count, fills sexpr_array)
+function split_sexpressions(str, sexpr_array, i, c, in_string, paren_count, current, n, in_comment) {
+    if (DEBUG_SEXPR) print "[DEBUG_SEXPR] split_sexpressions called" > "/dev/stderr"
+    in_string = 0
+    paren_count = 0
+    current = ""
+    n = 0
+    in_comment = 0
+    i = 1
+    while (i <= length(str)) {
+        c = substr(str, i, 1)
+        # Skip whitespace
+        if (!in_string && (c == " " || c == "\t" || c == "\n")) { i++; continue; }
+        # Skip comments (start with ; to end of line)
+        if (!in_string && c == ";") {
+            while (i <= length(str) && substr(str, i, 1) != "\n") i++;
+            i++; continue;
+        }
+        # Parse S-expression
+        current = ""
+        if (c == "(") {
+            paren_count = 0
+            while (i <= length(str)) {
+                c = substr(str, i, 1)
+                current = current c
+                if (c == "\"") in_string = !in_string
+                if (!in_string && c == "(") paren_count++
+                if (!in_string && c == ")") paren_count--
+                i++
+                if (paren_count == 0 && !in_string) break
+            }
+            sexpr_array[++n] = current
+        } else {
+            # Parse an atom
+            while (i <= length(str)) {
+                c = substr(str, i, 1)
+                if (!in_string && (c == " " || c == "\t" || c == "\n" || c == "(" || c == ")" || c == ";")) break;
+                if (c == "\"") in_string = !in_string
+                current = current c
+                i++
+            }
+            if (current != "") sexpr_array[++n] = current
+        }
+    }
+    if (DEBUG_SEXPR) {
+        printf("[DEBUG_SEXPR] split_sexpressions: found %d expressions\n", n) > "/dev/stderr"
+        for (i = 1; i <= n; i++) {
+            printf("[DEBUG_SEXPR]   %d: [%s]\n", i, sexpr_array[i]) > "/dev/stderr"
+        }
+        print "[DEBUG_SEXPR] split_sexpressions returning" > "/dev/stderr"
+    }
+    return n
+}
+
+# Helper: Extract first symbol from a compound expression string
+function extract_first_symbol(expr_str, op) {
+    # Assumes expr_str starts with '('
+    op = ""
+    i = 2
+    # Skip whitespace after '('
+    while (i <= length(expr_str) && (substr(expr_str, i, 1) == " " || substr(expr_str, i, 1) == "\t")) i++
+    # Read until next whitespace or ')'
+    while (i <= length(expr_str)) {
+        c = substr(expr_str, i, 1)
+        if (c == " " || c == "\t" || c == ")") break
+        op = op c
+        i++
+    }
+    return op
+}
diff --git a/awk/scheme/scheme/bin/repl b/awk/scheme/scheme/bin/repl
index 1c290d1..0f1a049 100755
--- a/awk/scheme/scheme/bin/repl
+++ b/awk/scheme/scheme/bin/repl
@@ -116,6 +116,10 @@ if [ "$#" -gt 0 ]; then
     debug "Reading file: $1"
     file_content=$(cat "$1")
     debug "File content: $file_content"
+    # TODO: Workaround for curried/closure tests: just print the result of the last expression.
+    # This avoids emitting an extra CALL for the final value if it is not a function.
+    # A more robust solution would be to have the compiler analyze the top-level expression and only emit CALLs for function results,
+    # or to have the VM detect and print non-function results at the top level.
     evaluate_expression "$file_content"
     exit_code=$?
     cleanup "keep_state"  # Keep state after file execution
diff --git a/awk/scheme/scheme/bin/vm.awk b/awk/scheme/scheme/bin/vm.awk
index 7e68e92..33a52a2 100755
--- a/awk/scheme/scheme/bin/vm.awk
+++ b/awk/scheme/scheme/bin/vm.awk
@@ -297,20 +297,16 @@ function getClosureEnvId(closure_val) {
 
 # Environment capture for closures
 function captureEnvironment(env_id, i) {
-    debug("Capturing environment with ID: " env_id)
+    if (DEBUG) print "[DEBUG_CLOSURE] Capturing environment with ID: " env_id > "/dev/stderr"
     closure_env_sizes[env_id] = env_size
-
-    # Copy current environment to closure environment
     for (i = 0; i < env_size; i++) {
-        # Only capture non-global variables
         if (env_name[i] !~ /^__global_/) {
             closure_env_names[env_id, i] = env_name[i]
             closure_env_vals[env_id, i] = env_val[i]
-            debug("Captured: " env_name[i] " = " env_val[i])
+            if (DEBUG) print "[DEBUG_CLOSURE]   Captured: " env_name[i] " = " env_val[i] > "/dev/stderr"
         }
     }
-
-    debug("Captured environment size: " closure_env_sizes[env_id])
+    if (DEBUG) print "[DEBUG_CLOSURE] Captured environment size: " closure_env_sizes[env_id] > "/dev/stderr"
 }
 
 # VM instruction to capture environment
@@ -334,14 +330,14 @@ function vm_capture_env(func_name) {
 
 # Environment restoration for closures
 function pushClosureEnvironment(env_id, i) {
-    debug("Pushing closure environment: " env_id)
+    if (DEBUG) print "[DEBUG_CLOSURE] Pushing closure environment: " env_id > "/dev/stderr"
     if (env_id in closure_env_sizes) {
         for (i = 0; i < closure_env_sizes[env_id]; i++) {
             if ((env_id, i) in closure_env_names) {
                 env_name[env_size] = closure_env_names[env_id, i]
                 env_val[env_size] = closure_env_vals[env_id, i]
                 env_size++
-                debug("Restored: " closure_env_names[env_id, i] " = " closure_env_vals[env_id, i])
+                if (DEBUG) print "[DEBUG_CLOSURE]   Restored: " closure_env_names[env_id, i] " = " closure_env_vals[env_id, i] > "/dev/stderr"
             }
         }
     }
@@ -931,7 +927,7 @@ function vm_define_function(name, start_pc) {
 }
 
 # Function call implementation
-function vm_call_function(code_lines, j, saved_pc, saved_env_size, arg, param_name) {
+function vm_call_function(code_lines, j, saved_pc, saved_env_size, arg, param_name, param_names, nparams, k) {
     # Get function name from stack
     func_name = pop()
     debug("Calling function: " func_name)
@@ -1152,62 +1148,73 @@ function vm_call_function(code_lines, j, saved_pc, saved_env_size, arg, param_na
     saved_pc = pc
     saved_env_size = env_size
 
-    # AWK FEATURE: split(string, array, separator) splits string into array elements
-    # Unlike JS string.split() which returns an array, this populates an existing array
     split(FUNCTIONS[func_name], code_lines, "\n")
 
-    # 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)
-        debug("FUNCTION_PARAM: " param_name)
-
-        # Get argument from stack
-        arg = pop()
-        debug("Function argument: " arg)
-        debug("FUNCTION_ARG: " 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++
-        debug("FUNCTION_ENV_STORE: " param_name " = " arg " at index " (env_size-1))
-
-        # Execute function code directly, skipping STORE and POP_ENV instructions
-        for (j = 2; j <= length(code_lines); j++) {
+    # --- Multi-parameter function support ---
+    # Collect all leading STORE instructions as parameter names
+    nparams = 0
+    for (j = 1; j <= length(code_lines); j++) {
+        if (code_lines[j] ~ /^STORE /) {
+            nparams++
+            param_names[nparams] = substr(code_lines[j], 7)
+        } else {
+            break
+        }
+    }
+    if (nparams > 0) {
+        debug("Function parameters: " nparams)
+        # Pop arguments in reverse order so first argument is bound to first param
+        for (k = nparams; k >= 1; k--) {
+            arg = pop()
+            param_name = param_names[k]
+            debug("Binding argument to parameter: " param_name " = " arg)
+            env_name[env_size] = param_name
+            env_val[env_size] = arg
+            env_size++
+            debug("FUNCTION_ENV_STORE: " param_name " = " arg " at index " (env_size-1))
+        }
+        # Execute function code, skipping STORE and POP_ENV instructions
+        for (j = nparams + 1; j <= length(code_lines); j++) {
             if (code_lines[j] != "" && code_lines[j] != "POP_ENV") {
                 debug("Executing function instruction: " code_lines[j])
                 execute(code_lines[j])
             }
         }
-
-        # Clean up parameter
-        vm_pop_env()
-
-        # Return to caller
+        # Clean up parameters
+        for (k = 1; k <= nparams; k++) {
+            vm_pop_env()
+        }
         debug("Function completed, returning to PC: " saved_pc)
-        pc = saved_pc
-        return
-    } else {
-        # This is a built-in function or non-parameterized function
-        debug("Calling non-parameterized function: " func_name)
-
-        # Execute all function code directly
-        for (j in code_lines) {
-            if (code_lines[j] != "") {
-                debug("Executing function instruction: " code_lines[j])
-                execute(code_lines[j])
+        if (DEBUG) {
+            if (stack_ptr > 0) {
+                debug("[DEBUG_VM] Value on stack after CALL: " stack[stack_ptr-1])
+            } else {
+                debug("[DEBUG_VM] Stack is empty after CALL")
             }
         }
-
-        # Return to caller
-        debug("Function completed, returning to PC: " saved_pc)
         pc = saved_pc
         return
     }
+    # --- End multi-parameter support ---
+
+    # This is a built-in function or non-parameterized function
+    debug("Calling non-parameterized function: " func_name)
+    for (j in code_lines) {
+        if (code_lines[j] != "") {
+            debug("Executing function instruction: " code_lines[j])
+            execute(code_lines[j])
+        }
+    }
+    debug("Function completed, returning to PC: " saved_pc)
+    if (DEBUG) {
+        if (stack_ptr > 0) {
+            debug("[DEBUG_VM] Value on stack after CALL: " stack[stack_ptr-1])
+        } else {
+            debug("[DEBUG_VM] Stack is empty after CALL")
+        }
+    }
+    pc = saved_pc
+    return
 }
 
 # Function call with argument count implementation
diff --git a/awk/scheme/scheme/scratch/OUTLINE.md b/awk/scheme/scheme/scratch/OUTLINE.md
index 9969866..073afef 100644
--- a/awk/scheme/scheme/scratch/OUTLINE.md
+++ b/awk/scheme/scheme/scratch/OUTLINE.md
@@ -3,7 +3,7 @@
 ## Introduction
 - Hook: "What do you get when you cross a functional programming language with a text processing tool from the 1970s?"
 - Brief context: My journey from JS/functional programming to AWK/compilers/VMs
-- Thesis: Building a Scheme VM in AWK taught me more about language design than I expected
+- Thesis: Building a Scheme VM in AWK taught me more about language design than I expected, and it evolved into a surprisingly capable system
 
 ## The Setup: Why AWK? (And Why Not?)
 - The initial reaction: "AWK? Really?"
@@ -17,18 +17,40 @@
   - Takes Scheme expressions, outputs VM bytecode
   - Recursive descent parsing (because AWK doesn't have fancy parser generators)
   - The joy of implementing `car` and `cdr` in a language that doesn't have lists
+  - Error detection for incomplete expressions (because debugging is hard enough)
 
 - **Layer 2: The Virtual Machine** (`bin/vm.awk`)
   - Stack-based execution (because registers are hard)
   - Type system with tagged values (`N:5`, `B:1`, `P:42`)
   - Environment management for lexical scoping
   - The weirdness of implementing closures in AWK
+  - Higher-order functions with nested call support (because functional programming is fun)
 
 - **Layer 3: The REPL** (`bin/repl`)
   - Interactive development (because who doesn't love a good REPL?)
   - State persistence between sessions
   - The challenge of making AWK feel interactive
 
+## The Evolution: From Simple Interpreter to Language-Agnostic VM
+
+### The Early Days: Basic Scheme
+- Started with simple arithmetic and basic functions
+- The joy of getting `(+ 1 2)` to work
+- Why stack-based execution made sense in AWK
+- The challenge of implementing lists with cons cells
+
+### The Middle Phase: Functions and Closures
+- Adding user-defined functions (because Scheme needs functions)
+- Implementing closures (because lexical scoping is important)
+- The complexity of environment management in AWK
+- Why capturing environments was harder than expected
+
+### The Current State: Higher-Order Functions and Multi-Language Support
+- Adding `map` and `filter` with full function support
+- The nested function call system (because higher-order functions need to call other functions)
+- The Forth compiler experiment (because why not?)
+- Proving the VM is truly language-agnostic
+
 ## The Weirdness: AWK Quirks That Made Me Question My Life Choices
 
 ### 1-Indexed Everything
@@ -68,6 +90,7 @@
 - The beauty of recursive functions in AWK
 - How we handle function calls, special forms, literals
 - The joy of implementing `if` and `cond` without proper control flow
+- Error detection for incomplete expressions (because users make mistakes)
 
 ### Code Generation: From AST to Bytecode
 - The instruction set: `PUSH_CONST`, `ADD`, `CALL`, `HALT`
@@ -101,19 +124,11 @@
 - The challenge of parameter passing
 - Why `CALL` is more complex than it seems
 
-## The REPL: Making AWK Interactive
-
-### State Persistence: Between Sessions
-- Saving function definitions to files
-- Loading state on startup
-- The simplicity of text-based persistence
-- Why we don't need a database
-
-### Interactive Development: The AWK Way
-- Reading input line by line
-- Compiling and executing each expression
-- The challenge of maintaining state
-- Why the REPL feels surprisingly natural
+### Higher-Order Functions: The Real Magic
+- How `map` and `filter` work with any function type
+- The nested function call system
+- Why calling functions from within functions is tricky
+- The joy of data transformation pipelines
 
 ## The Forth Experiment: Proving Language Agnosticism
 
@@ -134,6 +149,39 @@
 - Why this matters for language design
 - The power of a well-designed instruction set
 
+## The Testing Journey: From Chaos to Order
+
+### The Early Days: Manual Testing
+- Running expressions by hand
+- The pain of debugging without proper tests
+- Why testing is crucial even for small projects
+
+### The Evolution: Comprehensive Test Suite
+- 48 unit tests covering all features
+- Integration tests for complex scenarios
+- Regression tests to prevent bugs
+- Examples that demonstrate usage
+
+### The Test Runner: Making Testing Easy
+- Automated test execution
+- Clear pass/fail reporting
+- Error detection and reporting
+- Why good tooling matters
+
+## The REPL: Making AWK Interactive
+
+### State Persistence: Between Sessions
+- Saving function definitions to files
+- Loading state on startup
+- The simplicity of text-based persistence
+- Why we don't need a database
+
+### Interactive Development: The AWK Way
+- Reading input line by line
+- Compiling and executing each expression
+- The challenge of maintaining state
+- Why the REPL feels surprisingly natural
+
 ## Lessons Learned: What AWK Taught Me About Language Design
 
 ### Simplicity is Powerful
@@ -156,6 +204,16 @@
 - Closures add significant complexity
 - Why proper scoping is crucial for functional languages
 
+### Higher-Order Functions are Worth It
+- The power of function composition
+- How data transformation becomes elegant
+- Why functional programming patterns matter
+
+### Language Agnosticism is Achievable
+- A well-designed VM can support multiple languages
+- The benefits of shared runtime and standard library
+- Why this approach reduces implementation complexity
+
 ## The Weirdest Parts: AWK Quirks That Made Me Laugh/Cry
 
 ### The 1-Indexed String Functions
@@ -185,12 +243,15 @@
 - The REPL is responsive
 - State persistence works reliably
 - The type system prevents many bugs
+- Higher-order functions work smoothly
+- The test suite catches regressions
 
 ### What's Slow/Weird
 - Complex expressions can be slow
 - Memory usage with large programs
 - The 1-indexed string operations
 - Limited data structures beyond arrays
+- No tail call optimization
 
 ### The AWK Tax
 - Everything is string-based under the hood
@@ -198,6 +259,30 @@
 - Limited control flow constructs
 - The constant string manipulation overhead
 
+## The Current State: A Surprisingly Capable System
+
+### What We Have
+- Full Scheme interpreter with higher-order functions
+- Language-agnostic VM with Forth support
+- Comprehensive test suite (48 tests)
+- Interactive REPL with state persistence
+- Error detection and reporting
+- Standard library with 50+ functions
+
+### What Makes It Special
+- Built entirely in AWK (no external dependencies)
+- Supports complex functional programming patterns
+- Language-agnostic VM design
+- Comprehensive testing and documentation
+- Clean, maintainable architecture
+
+### The Numbers
+- ~3,900 lines of AWK code
+- 48 unit tests + integration/regression tests
+- 50+ standard library functions
+- 2 target languages (Scheme, Forth)
+- 0 external dependencies
+
 ## Conclusion: Was It Worth It?
 
 ### The Unexpected Benefits
@@ -205,6 +290,7 @@
 - Appreciation for language design decisions
 - The joy of building something from scratch
 - Why constraints breed creativity
+- The power of comprehensive testing
 
 ### The AWK Revelation
 - AWK is more powerful than it looks
@@ -223,6 +309,7 @@
 - The value of building things in unexpected languages
 - Why understanding the fundamentals matters
 - The joy of programming for its own sake
+- How constraints can lead to better design
 
 ## Technical Appendix: Key Code Snippets
 
@@ -247,6 +334,17 @@ function push(val) {
 }
 ```
 
+### Higher-Order Functions
+```awk
+function stdlib_map() {
+    # Get function and list from stack
+    func = pop()
+    list = pop()
+    # Call function for each element
+    result = execute_nested_function_call(func, element)
+}
+```
+
 ### The Forth Compiler
 ```awk
 if (token ~ /^-?[0-9]+$/) {
@@ -256,9 +354,19 @@ if (token ~ /^-?[0-9]+$/) {
 }
 ```
 
+### Error Detection
+```awk
+if (paren_count > 0) {
+    error("Unmatched opening parentheses - incomplete expression")
+}
+```
+
 ## Resources and Further Reading
 - AWK documentation and tutorials
 - Scheme language specification
 - Virtual machine design principles
 - Stack-based execution models
-- Language implementation techniques 
\ No newline at end of file
+- Language implementation techniques
+- Functional programming concepts
+- Higher-order functions and closures
+- Language-agnostic VM design 
\ No newline at end of file
diff --git a/awk/scheme/scheme/scratch/arch-notes.md b/awk/scheme/scheme/scratch/arch-notes.md
index 8644da8..bb2e240 100644
--- a/awk/scheme/scheme/scratch/arch-notes.md
+++ b/awk/scheme/scheme/scratch/arch-notes.md
@@ -1,7 +1,7 @@
 # Awk-Scheme: Architectural Notes
 
 ## Overview
-A little Scheme interpreter implemented in AWK, composed of a compiler and a stack-based virtual machine. The architecture seeks to be modular, with clear separation of concerns between parsing/compilation and execution.
+A little Scheme interpreter implemented in AWK, composed of a compiler and a stack-based virtual machine. The architecture seeks to be modular, with clear separation of concerns between parsing/compilation and execution. The system has evolved to support higher-order functions, nested function calls, and language-agnostic VM design with Forth as a second target language.
 
 ---
 
@@ -24,7 +24,7 @@ A little Scheme interpreter implemented in AWK, composed of a compiler and a sta
 ### 2.2. Parsing (Recursive Descent)
 - **Pattern**: *Recursive Descent Parser*
 - **Why**: Simple, direct mapping from grammar to code; easy to debug and extend for a small language.
-- **How**: The parser builds an expression tree from tokens, handling nested expressions, string literals, and validating syntax with proper parenthesis matching.
+- **How**: The parser builds an expression tree from tokens, handling nested expressions, string literals, and validating syntax with proper parenthesis matching and error detection for incomplete expressions.
 
 ### 2.3. Code Generation
 - **Pattern**: *Visitor/Dispatcher* (for expression types)
@@ -41,6 +41,11 @@ A little Scheme interpreter implemented in AWK, composed of a compiler and a sta
 - **Why**: Enables lexical scoping and first-class functions by capturing the environment at lambda creation.
 - **How**: The compiler emits `CAPTURE_ENV` instructions to capture the current environment and create closure objects with unique environment IDs.
 
+### 2.6. Error Detection
+- **Pattern**: *Fail-Fast Error Detection*
+- **Why**: Early detection of syntax errors prevents runtime issues and improves debugging.
+- **How**: The compiler validates parenthesis matching and detects incomplete expressions during parsing, providing clear error messages.
+
 ---
 
 ## 3. Virtual Machine (`bin/vm.awk`)
@@ -75,17 +80,27 @@ A little Scheme interpreter implemented in AWK, composed of a compiler and a sta
 - **Why**: Enables functions like `string-append` to accept any number of arguments, improving usability.
 - **How**: Functions like `string_append()` process all arguments on the stack using a loop, maintaining proper argument order and type checking.
 
-### 3.7. Output System
+### 3.7. Higher-Order Functions
+- **Pattern**: *Function as Data* (first-class functions)
+- **Why**: Enables functional programming patterns like `map` and `filter` with user-defined functions, lambdas, and closures.
+- **How**: The VM implements `map` and `filter` functions that can accept any function type (built-in, user-defined, lambda, closure) through a nested function call mechanism.
+
+### 3.8. Nested Function Call System
+- **Pattern**: *Context Preservation* (for nested execution)
+- **Why**: Enables higher-order functions to call other functions while preserving the calling context.
+- **How**: The VM maintains a call stack that saves and restores execution context, allowing functions to be called from within other functions without losing state.
+
+### 3.9. Output System
 - **Pattern**: *Visitor Pattern* (for value display)
 - **Why**: Different value types require different display formats, and the display logic should be extensible.
 - **How**: The `display_value()` function recursively visits different value types, converting them to readable string representations with proper list formatting.
 
-### 3.8. Heap and Memory Management
+### 3.10. Heap and Memory Management
 - **Pattern**: *Manual Heap with Reference Counting (partial)*
 - **Why**: Enables cons cell allocation and basic memory management for list operations.
 - **How**: The VM allocates cons cells on a heap array, with a placeholder for reference counting (not fully implemented).
 
-### 3.9. State Persistence
+### 3.11. State Persistence
 - **Pattern**: *State Serialization*
 - **Why**: Allows global state and functions to persist across REPL sessions for continuity.
 - **How**: The VM serializes global variables and function definitions to files, loading them on startup with proper state restoration.
@@ -109,45 +124,94 @@ A little Scheme interpreter implemented in AWK, composed of a compiler and a sta
 - **Why**: String operations like concatenation need to handle variable numbers of arguments efficiently.
 - **How**: Functions like `string_append()` build the result incrementally by processing all arguments on the stack in order.
 
+### 4.4. Higher-Order Functions
+- **Pattern**: *Functional Programming Patterns*
+- **Why**: Enables data transformation and filtering with user-defined functions.
+- **How**: `map` and `filter` functions accept any function type and execute them in a nested context, supporting complex data processing pipelines.
+
+---
+
+## 5. Language-Agnostic VM Design
+
+### 5.1. Forth Compiler
+- **Pattern**: *Multi-Language Frontend*
+- **Why**: Demonstrates the VM's language-agnostic nature and validates the instruction set design.
+- **How**: A separate Forth compiler (`scratch/forth/forth.awk`) generates the same VM bytecode, proving that the VM can execute code from different source languages.
+
+### 5.2. Shared Instruction Set
+- **Pattern**: *Universal Bytecode*
+- **Why**: Enables multiple languages to target the same VM, reducing implementation complexity.
+- **How**: Both Scheme and Forth compilers generate identical VM instructions, allowing seamless execution of mixed-language programs.
+
+### 5.3. Standard Library Integration
+- **Pattern**: *Shared Runtime*
+- **Why**: Provides consistent functionality across different source languages.
+- **How**: The VM's standard library functions are available to all languages targeting the VM, ensuring consistent behavior.
+
 ---
 
-## 5. Extensibility and Maintainability
+## 6. Testing Architecture
 
-### 5.1. Separation of Concerns
+### 6.1. Comprehensive Test Suite
+- **Pattern**: *Layered Testing Strategy*
+- **Why**: Ensures correctness across different levels of complexity and use cases.
+- **How**: Tests are organized into unit tests (48 tests), integration tests, regression tests, and examples, covering basic operations to complex higher-order function scenarios.
+
+### 6.2. Test Organization
+- **Pattern**: *Test Categories*
+- **Why**: Different types of tests serve different purposes in validation.
+- **How**: Unit tests focus on individual features, integration tests validate complex interactions, regression tests prevent regressions, and examples demonstrate usage patterns.
+
+### 6.3. Error Detection Testing
+- **Pattern**: *Negative Testing*
+- **Why**: Ensures the system properly handles and reports errors.
+- **How**: Tests include syntax error detection, type checking, and edge cases to validate error handling behavior.
+
+---
+
+## 7. Extensibility and Maintainability
+
+### 7.1. Separation of Concerns
 - **Pattern**: *Layered Architecture*
 - **Why**: Compiler and VM are independent, making it easy to extend the language or change the execution model.
 - **How**: Clear interfaces between compilation and execution phases, with well-defined instruction formats.
 
-### 5.2. Function Extension
+### 7.2. Function Extension
 - **Pattern**: *Open/Closed Principle* (open for extension, closed for modification)
 - **Why**: Adding new functions should not require changes to existing code.
 - **How**: New functions are added by registering them in the `FUNCTIONS` table and implementing the corresponding VM function.
 
-### 5.3. Error Handling
+### 7.3. Error Handling
 - **Pattern**: *Fail-Fast* (for debugging)
 - **Why**: Early error detection helps identify issues quickly during development.
 - **How**: Functions validate arguments and types at runtime, throwing descriptive error messages.
 
+### 7.4. Language Extension
+- **Pattern**: *Plugin Architecture* (for new languages)
+- **Why**: Enables new languages to target the VM without modifying existing code.
+- **How**: New language compilers can be added as separate modules that generate the same VM bytecode.
+
 ---
 
-## 6. Notable Limitations and Future Enhancements
+## 8. Notable Limitations and Future Enhancements
 
-### 6.1. Current Limitations
-- **Nested Lambda Support**: Limited support for lambdas defined inside other lambdas
+### 8.1. Current Limitations
 - **Tail Recursion**: No proper tail call optimization
 - **Garbage Collection**: Reference counting is stubbed but not enforced
 - **Error Recovery**: Minimal error recovery in REPL
 - **Type System**: Basic runtime type checking only
+- **Performance**: Limited optimization for complex expressions
 
-### 6.2. Architectural Patterns for Future Enhancements
+### 8.2. Architectural Patterns for Future Enhancements
 - **Continuation-Passing Style**: For proper tail recursion
 - **Generational Garbage Collection**: For memory management
 - **Exception Handling**: For better error recovery
 - **Type Inference**: For compile-time type checking
+- **JIT Compilation**: For performance optimization
 
 ---
 
-## 7. Summary Table: Patterns Used
+## 9. Summary Table: Patterns Used
 
 | Area                | Pattern(s) Used                  | Why?                                 |
 |---------------------|----------------------------------|--------------------------------------|
@@ -161,44 +225,57 @@ A little Scheme interpreter implemented in AWK, composed of a compiler and a sta
 | State Persistence   | State Serialization              | REPL continuity                      |
 | Memory Management   | Manual Heap, Ref Counting (stub) | List support, future GC              |
 | Error Handling      | Fail-Fast                        | Debugging, development               |
+| Higher-Order Funcs  | Function as Data, Context Pres.  | Functional programming support       |
+| Multi-Language      | Universal Bytecode, Shared Runtime| Language agnosticism                 |
+| Testing             | Layered Testing, Test Categories | Comprehensive validation             |
 
 ---
 
-## 8. Architectural Choices: Rationale
+## 10. Architectural Choices: Rationale
 
-### 8.1. Language Choice
+### 10.1. Language Choice
 - **AWK as Implementation Language**: Chosen for portability and as a challenge; influences the use of arrays and string-based data structures.
 - **Why**: AWK's built-in associative arrays and string manipulation capabilities map well to the VM's needs.
 
-### 8.2. VM Design
+### 10.2. VM Design
 - **Stack Machine**: Maps well to AWK's capabilities and keeps the VM simple.
 - **Why**: Stack operations are straightforward to implement in AWK and provide clear semantics.
 
-### 8.3. Modularity
+### 10.3. Modularity
 - **Separation of Compiler/VM**: Enables clear boundaries and easier debugging.
 - **Why**: Independent development and testing of compilation and execution phases.
 
-### 8.4. Type System
+### 10.4. Type System
 - **Explicit Typing**: Reduces runtime errors and clarifies value handling.
 - **Why**: Tagged values provide runtime safety and clear debugging information.
 
+### 10.5. Language Agnosticism
+- **Universal VM Target**: Enables multiple languages to share the same runtime.
+- **Why**: Reduces implementation complexity and enables language experimentation.
+
 ---
 
-## 9. Flow Diagram (Textual)
+## 11. Flow Diagram (Textual)
 
 ```
-User Input (Scheme) → [Compiler] → VM Instructions → [VM] → Result/State
-                                                      ↓
-                                              [Display/Output]
+User Input (Scheme/Forth) → [Compiler] → VM Instructions → [VM] → Result/State
+                                                              ↓
+                                                      [Display/Output]
 ```
 
-## 10. Key Architectural Insights
+## 12. Key Architectural Insights
 
-### 10.1. Pattern Composition
+### 12.1. Pattern Composition
 The system demonstrates how classic architectural patterns can be composed to create a functional interpreter. The combination of Registry, Visitor, and Strategy patterns enables extensibility while maintaining simplicity.
 
-### 10.2. AWK-Specific Adaptations
+### 12.2. AWK-Specific Adaptations
 The patterns are adapted to AWK's strengths: associative arrays for registries, string manipulation for value representation, and procedural programming for the VM loop.
 
-### 10.3. Extensibility Through Registration
-The function registration system demonstrates how a simple registry pattern can enable significant extensibility without complex inheritance hierarchies or plugin systems.
\ No newline at end of file
+### 12.3. Extensibility Through Registration
+The function registration system demonstrates how a simple registry pattern can enable significant extensibility without complex inheritance hierarchies or plugin systems.
+
+### 12.4. Language Agnosticism
+The VM's language-agnostic design demonstrates how a well-designed instruction set can support multiple source languages, enabling experimentation and reducing implementation complexity.
+
+### 12.5. Higher-Order Function Support
+The nested function call system shows how complex functional programming features can be implemented in a simple stack-based VM, enabling powerful data transformation capabilities.
\ No newline at end of file
diff --git a/awk/scheme/scheme/test/run_tests.sh b/awk/scheme/scheme/test/run_tests.sh
index aac834d..b32d57b 100755
--- a/awk/scheme/scheme/test/run_tests.sh
+++ b/awk/scheme/scheme/test/run_tests.sh
@@ -49,6 +49,7 @@ print_status() {
 run_test() {
     local test_file=$1
     local test_name=$(basename "$test_file" .scm)
+    local rel_test_file=${test_file#$PROJECT_ROOT/}
     
     TOTAL_TESTS=$((TOTAL_TESTS + 1))
     
@@ -67,13 +68,23 @@ run_test() {
     
     if [ "$DEBUG" = "1" ]; then
         # Run with debug output
-        output=$(DEBUG=1 "$SCHEME_BIN" "$test_file" 2>&1)
+        output=$(env DEBUG=1 timeout 15s stdbuf -oL "$SCHEME_BIN" "$test_file" 2>&1)
         exit_code=$?
     else
         # Run normally
-        output=$("$SCHEME_BIN" "$test_file" 2>&1)
+        output=$(timeout 15s stdbuf -oL "$SCHEME_BIN" "$test_file" 2>&1)
         exit_code=$?
     fi
+
+    # Check for timeout (exit code 124)
+    if [ $exit_code -eq 124 ]; then
+        print_status "FAIL" "$test_name (timeout)"
+        FAILED_TESTS=$((FAILED_TESTS + 1))
+        FAILED_TEST_NAMES+=("$rel_test_file")
+        echo "Error: Test timed out after 15 seconds."
+        echo
+        return 1
+    fi
     
     # Check if test passed (exit code 0)
     if [ $exit_code -eq 0 ]; then
@@ -88,7 +99,7 @@ run_test() {
     else
         print_status "FAIL" "$test_name (exit code: $exit_code)"
         FAILED_TESTS=$((FAILED_TESTS + 1))
-        FAILED_TEST_NAMES+=("$test_name")
+        FAILED_TEST_NAMES+=("$rel_test_file")
         
         echo "Error output:"
         echo "$output" | sed 's/^/  /'
@@ -140,8 +151,8 @@ print_summary() {
         echo -e "${RED}Some tests failed!${NC}"
         echo
         echo "Failed tests:"
-        for test_name in "${FAILED_TEST_NAMES[@]}"; do
-            echo -e "  ${RED}✗${NC} $test_name"
+        for test_path in "${FAILED_TEST_NAMES[@]}"; do
+            echo -e "  ${RED}✗${NC} $test_path"
         done
         exit 1
     fi
diff --git a/awk/scheme/scheme/test/unit/minimal_closure_env.scm b/awk/scheme/scheme/test/unit/minimal_closure_env.scm
new file mode 100644
index 0000000..a7de816
--- /dev/null
+++ b/awk/scheme/scheme/test/unit/minimal_closure_env.scm
@@ -0,0 +1,8 @@
+(define make-outer
+  (lambda (x)
+    (lambda (y)
+      (let ((x (+ x y)))
+        (lambda (z) (+ x y z))))))
+
+(define closure1 ((make-outer 10) 5))
+(closure1 2) 
\ No newline at end of file
diff --git a/awk/scheme/scheme/test/unit/minimal_function_persistence.scm b/awk/scheme/scheme/test/unit/minimal_function_persistence.scm
new file mode 100644
index 0000000..c6825e4
--- /dev/null
+++ b/awk/scheme/scheme/test/unit/minimal_function_persistence.scm
@@ -0,0 +1,2 @@
+(define add2 (lambda (x) (+ x 2)))
+(add2 40) 
\ No newline at end of file
diff --git a/awk/scheme/scheme/test/unit/minimal_global_persistence.scm b/awk/scheme/scheme/test/unit/minimal_global_persistence.scm
new file mode 100644
index 0000000..7b75b28
--- /dev/null
+++ b/awk/scheme/scheme/test/unit/minimal_global_persistence.scm
@@ -0,0 +1,2 @@
+(define foo 42)
+foo 
\ No newline at end of file
diff --git a/awk/uxn/.gitignore b/awk/uxn/.gitignore
new file mode 100644
index 0000000..f71ddea
--- /dev/null
+++ b/awk/uxn/.gitignore
@@ -0,0 +1,3 @@
+**/out/
+**/uxnasm
+
diff --git a/awk/uxn/README.md b/awk/uxn/README.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/awk/uxn/README.md
diff --git a/awk/uxn/awk/uxnasm.awk b/awk/uxn/awk/uxnasm.awk
new file mode 100644
index 0000000..cfcdd00
--- /dev/null
+++ b/awk/uxn/awk/uxnasm.awk
@@ -0,0 +1,916 @@
+#!/usr/bin/awk -f
+
+# Uxntal Assembler in AWK - Two-Pass Implementation
+
+BEGIN {
+    # Constants
+    PAGE = 256
+    MAX_LABELS = 1024
+    MAX_REFS = 4096
+    
+    # Global state
+    ptr = PAGE
+    data_length = PAGE
+    
+    # Label and reference tracking
+    labels_len = 0
+    refs_len = 0
+    macros_len = 0
+    
+    # Device tracking
+    devices_len = 0
+    last_padding = ""
+    last_size = 0
+    current_device = ""
+    
+    # Lambda tracking
+    lambda_len = 0
+    lambda_ptr = 0
+    
+    # Opcode table
+    split("LIT INC POP NIP SWP ROT DUP OVR EQU NEQ GTH LTH JMP JCN JSR STH LDZ STZ LDR STR LDA STA DEI DEO ADD SUB MUL DIV AND ORA EOR SFT", ops)
+    
+    # Utility strings
+    hexad = "0123456789abcdef"
+    
+    # Check arguments
+    if (ARGC < 3) {
+        printf "usage: uxnasm.awk [-v] input.tal output.rom\n" > "/dev/stderr"
+        exit 1
+    }
+    
+    if (ARGC == 3 && substr(ARGV[1], 1, 2) == "-v") {
+        printf "Uxnasm.awk\n"
+        exit 0
+    }
+    
+    if (ARGC != 3) {
+        printf "usage: uxnasm.awk [-v] input.tal output.rom\n" > "/dev/stderr"
+        exit 1
+    }
+    
+    input_file = ARGV[1]
+    output_file = ARGV[2]
+    
+    # Remove output file from ARGV so AWK doesn't try to read it
+    ARGV[2] = ""
+    
+    # Two-pass assembly
+    if (!pass1_collect_symbols(input_file)) {
+        exit 1
+    }
+    
+    if (!pass2_generate_code(input_file)) {
+        exit 1
+    }
+    
+    if (!build_output(output_file)) {
+        exit 1
+    }
+    
+    printf "Assembled %s in %d bytes(%.2f%% used), %d labels, %d refs.\n",
+           output_file, data_length - PAGE, (data_length - PAGE) / 652.80, labels_len, refs_len
+}
+
+# Utility functions
+function remove_comments(line,    result, i, c, depth) {
+    # Remove comments from a line
+    # Comments are delimited by ( and ) and can be nested
+    result = ""
+    depth = 0
+    
+    for (i = 1; i <= length(line); i++) {
+        c = substr(line, i, 1)
+        if (c == "(") {
+            depth++
+        } else if (c == ")") {
+            depth--
+        } else if (depth == 0) {
+            result = result c
+        }
+    }
+    
+    # Trim whitespace
+    gsub(/^[ \t]+|[ \t]+$/, "", result)
+    return result
+}
+
+function shex(s,    d, n, c, i) {
+    n = 0
+    for (i = 1; i <= length(s); i++) {
+        c = substr(s, i, 1)
+        d = index(hexad, c) - 1
+        if (d < 0) return -1
+        n = n * 16 + d
+    }
+    return n
+}
+
+function ishex(x) {
+    return shex(x) >= 0
+}
+
+function findopcode(s,    i, m, base, c) {
+    # Special case for BRK
+    if (s == "BRK") return 0
+    
+    for (i = 1; i <= 32; i++) {
+        if (substr(ops[i], 1, 3) == substr(s, 1, 3)) {
+            base = i - 1
+            if (i == 1) base = base + 128  # LIT special case
+            
+            m = 4
+            while (m <= length(s)) {
+                c = substr(s, m, 1)
+                if (c == "2")
+                    base = base + 32
+                else if (c == "r")
+                    base = base + 64
+                else if (c == "k")
+                    base = base + 128
+                else
+                    return -1
+                m++
+            }
+            return base
+        }
+    }
+    return -1
+}
+
+function isopc(x) {
+    return findopcode(x) >= 0 || x == "BRK"
+}
+
+function makelabel(name, setscope) {
+    if (labels_len >= MAX_LABELS) {
+        printf "Labels limit exceeded\n" > "/dev/stderr"
+        return 0
+    }
+    
+    labels_len++
+    labels[labels_len, "name"] = name
+    labels[labels_len, "addr"] = ptr
+    labels[labels_len, "refs"] = 0
+    
+    printf "DEBUG: Created label '%s' at addr %d\n", name, ptr > "/dev/stderr"
+    
+    return 1
+}
+
+function findlabel(name,    i) {
+    for (i = 1; i <= labels_len; i++) {
+        if (labels[i, "name"] == name) {
+            return i
+        }
+    }
+    return 0
+}
+
+function makeref(label, rune, addr) {
+    if (refs_len >= MAX_REFS) {
+        printf "References limit exceeded\n" > "/dev/stderr"
+        return 0
+    }
+    
+    refs_len++
+    refs[refs_len, "name"] = label
+    refs[refs_len, "rune"] = rune
+    refs[refs_len, "addr"] = addr
+    
+    return 1
+}
+
+function makedevice(name, base_addr) {
+    if (devices_len >= MAX_LABELS) {
+        printf "Devices limit exceeded\n" > "/dev/stderr"
+        return 0
+    }
+    
+    devices_len++
+    devices[devices_len, "name"] = name
+    devices[devices_len, "base"] = base_addr
+    devices[devices_len, "fields_len"] = 0
+    
+    return 1
+}
+
+function adddevicefield(device_name, field_name, size) {
+    # Find device
+    for (i = 1; i <= devices_len; i++) {
+        if (devices[i, "name"] == device_name) {
+            devices[i, "fields_len"]++
+            devices[i, "fields", devices[i, "fields_len"], "name"] = field_name
+            devices[i, "fields", devices[i, "fields_len"], "size"] = size
+            return 1
+        }
+    }
+    return 0
+}
+
+function finddevicefield(device_name, field_name,    i, j) {
+    for (i = 1; i <= devices_len; i++) {
+        if (devices[i, "name"] == device_name) {
+            addr = devices[i, "base"]
+            for (j = 1; j <= devices[i, "fields_len"]; j++) {
+                if (devices[i, "fields", j, "name"] == field_name) {
+                    return addr
+                }
+                addr += devices[i, "fields", j, "size"]
+            }
+        }
+    }
+    return -1
+}
+
+# --- PASS 1: Symbol/Label Collection ---
+function pass1_collect_symbols(filename) {
+    ptr = PAGE
+    data_length = PAGE
+    
+    while ((getline < filename) > 0) {
+        pass1_process_line($0)
+    }
+    close(filename)
+    
+    return 1
+}
+
+function pass1_process_line(line_text,    tokens, i, token, j) {
+    line_text = remove_comments(line_text)
+    if (line_text == "") return 1
+    
+    # Custom tokenization to handle quoted strings properly
+    tokens_len = 0
+    i = 1
+    while (i <= length(line_text)) {
+        c = substr(line_text, i, 1)
+        if (c == " " || c == "\t") {
+            i++
+            continue
+        }
+        
+        if (c == "\"") {
+            # Handle quoted string - capture everything until closing quote
+            token = "\""
+            i++
+            while (i <= length(line_text) && substr(line_text, i, 1) != "\"") {
+                token = token substr(line_text, i, 1)
+                i++
+            }
+            if (i <= length(line_text)) {
+                token = token "\""
+                i++
+            }
+            tokens[++tokens_len] = token
+        } else {
+            # Regular token - capture until whitespace
+            token = ""
+            while (i <= length(line_text) && substr(line_text, i, 1) != " " && substr(line_text, i, 1) != "\t") {
+                token = token substr(line_text, i, 1)
+                i++
+            }
+            if (token != "") {
+                tokens[++tokens_len] = token
+            }
+        }
+    }
+    
+    # Combine - tokens with / (like -Screen/pixel)
+    for (i = 1; i < tokens_len; i++) {
+        if (tokens[i] == "-" && index(tokens[i+1], "/") > 0) {
+            tokens[i] = tokens[i] tokens[i+1]
+            for (j = i + 1; j < tokens_len; j++) {
+                tokens[j] = tokens[j+1]
+            }
+            tokens_len--
+        }
+    }
+    
+    for (i = 1; i <= tokens_len; i++) {
+        token = tokens[i]
+        printf "DEBUG: pass1 processing token: '%s'\n", token > "/dev/stderr"
+        if (!pass1_parse_token(token)) {
+            printf "ERROR: Failed to parse token '%s' at line %d\n", token, line_number > "/dev/stderr"
+            return 0
+        }
+    }
+    return 1
+}
+
+function pass1_parse_token(w) {
+    
+                # Skip standalone tokens
+            if (w == ">" || w == "-") {
+                return 1
+            }
+    
+    # Handle device definitions and labels
+    if (substr(w, 1, 1) == "@") {
+        printf "DEBUG: Processing @ token: %s\n", w > "/dev/stderr"
+        # Check if this is a macro definition (labels starting with @<)
+        printf "DEBUG: Checking macro condition: substr(w, 2, 1)='%s', substr(w, length(w), 1)='%s'\n", substr(w, 2, 1), substr(w, length(w), 1) > "/dev/stderr"
+        printf "DEBUG: Condition check: '%s' == '<' && '%s' == '>' = %s\n", substr(w, 2, 1), substr(w, length(w), 1), (substr(w, 2, 1) == "<" && substr(w, length(w), 1) == ">") > "/dev/stderr"
+        if (substr(w, 2, 1) == "<" && substr(w, length(w), 1) == ">") {
+            printf "DEBUG: Found macro definition: %s\n", w > "/dev/stderr"
+            makemacro(substr(w, 3, length(w) - 3))
+            return 1
+        }
+        
+        # Check if this is a device definition (has base address)
+        if (last_padding != "" && current_device == "") {
+            makedevice(substr(w, 2), shex(last_padding))
+            current_device = substr(w, 2)
+            last_padding = ""  # Reset after device definition
+        } else {
+            makelabel(substr(w, 2), 1)
+        }
+        return 1
+    }
+    
+    # Handle device fields
+    if (substr(w, 1, 1) == "&") {
+        if (current_device != "") {
+            adddevicefield(current_device, substr(w, 2), last_size)
+        } else {
+            makelabel(w, 0)
+        }
+        return 1
+    }
+    
+    # Skip brackets and control flow
+    if (substr(w, 1, 1) == "[" || substr(w, 1, 1) == "]" || w == "{") {
+        return 1
+    }
+    
+    # Handle lambda labels
+    if (w == "}") {
+        makelabel(makelambda(lambda_len++))
+        return 1
+    }
+    
+    # Handle padding and size
+    if (substr(w, 1, 1) == "|") {
+        last_padding = substr(w, 2)
+        # Set pointer based on padding value (no writing, just positioning)
+        if (last_padding == "0000") {
+            ptr = 0
+        } else if (last_padding == "0100") {
+            ptr = PAGE
+        } else {
+            ptr = shex(last_padding)
+        }
+        return 1
+    }
+    if (substr(w, 1, 1) == "$") {
+        last_size = shex(substr(w, 2))
+        # Advance pointer by size (no writing, just positioning)
+        ptr += last_size
+        return 1
+    }
+    
+    # Handle references (just collect them, don't resolve yet)
+    if (substr(w, 1, 1) == "_") {
+        makeref(substr(w, 2), substr(w, 1, 1), ptr)
+        ptr++
+        return 1
+    }
+    if (substr(w, 1, 1) == ",") {
+        makeref(substr(w, 2), substr(w, 1, 1), ptr + 1)
+        ptr += 2
+        return 1
+    }
+    if (substr(w, 1, 1) == "-") {
+        # Check if this is a device field reference
+        if (index(substr(w, 2), "/") > 0) {
+            # Device field reference - just advance pointer (will be resolved in pass2)
+            ptr++
+        } else {
+            makeref(substr(w, 2), substr(w, 1, 1), ptr)
+            ptr++
+        }
+        return 1
+    }
+    if (substr(w, 1, 1) == ".") {
+        # Check if this is a device field reference
+        if (index(substr(w, 2), "/") > 0) {
+            # Device field reference - just advance pointer
+            ptr += 2
+        } else {
+            makeref(substr(w, 2), substr(w, 1, 1), ptr + 1)
+            ptr += 2
+        }
+        return 1
+    }
+    if (substr(w, 1, 1) == "=") {
+        makeref(substr(w, 2), substr(w, 1, 1), ptr)
+        ptr += 2
+        return 1
+    }
+    if (substr(w, 1, 1) == ";") {
+        makeref(substr(w, 2), substr(w, 1, 1), ptr + 1)
+        ptr += 3
+        return 1
+    }
+    if (substr(w, 1, 1) == "?") {
+        makeref(substr(w, 2), substr(w, 1, 1), ptr + 1)
+        ptr += 3
+        return 1
+    }
+    if (substr(w, 1, 1) == "!") {
+        makeref(substr(w, 2), substr(w, 1, 1), ptr + 1)
+        ptr += 3
+        return 1
+    }
+    
+    # Handle hex literals (with # prefix or raw hex values)
+    if (substr(w, 1, 1) == "#") {
+        if (length(substr(w, 2)) > 2) {
+            ptr += 3  # LIT2 + 2 bytes
+        } else {
+            ptr += 2  # LIT + 1 byte
+        }
+        return 1
+    }
+    
+    # Handle raw hex values (like font data)
+    if (ishex(w)) {
+        if (length(w) > 2) {
+            ptr += 3  # LIT2 + 2 bytes
+        } else {
+            ptr += 2  # LIT + 1 byte
+        }
+        return 1
+    }
+    
+    # Handle opcodes
+    if (isopc(w)) {
+        ptr++
+        return 1
+    }
+    
+    # Handle strings
+    if (substr(w, 1, 1) == "\"") {
+        ptr += length(substr(w, 2))
+        return 1
+    }
+    
+    # Handle macro definitions (labels starting with @<)
+    if (substr(w, 1, 1) == "@" && substr(w, 2, 1) == "<" && substr(w, length(w), 1) == ">") {
+        makemacro(substr(w, 3, length(w) - 3))
+        return 1
+    }
+    
+    # Handle macro calls (tokens starting with <)
+    if (substr(w, 1, 1) == "<" && substr(w, length(w), 1) == ">") {
+        # Just advance pointer in pass1, will be expanded in pass2
+        ptr += 1  # Placeholder - actual size depends on macro content
+        return 1
+    }
+    
+    # Handle unknown tokens as label references (fallback)
+    makeref(w, " ", ptr + 1)
+    ptr += 3  # LIT2 + 2 bytes
+    return 1
+}
+
+# --- PASS 2: Code Generation ---
+function pass2_generate_code(filename) {
+    ptr = PAGE
+    data_length = PAGE
+    
+    while ((getline < filename) > 0) {
+        pass2_process_line($0)
+    }
+    close(filename)
+    
+    return 1
+}
+
+function pass2_process_line(line_text,    tokens, i, token, j) {
+    line_text = remove_comments(line_text)
+    if (line_text == "") return 1
+    
+    # Custom tokenization to handle quoted strings properly
+    tokens_len = 0
+    i = 1
+    while (i <= length(line_text)) {
+        c = substr(line_text, i, 1)
+        if (c == " " || c == "\t") {
+            i++
+            continue
+        }
+        
+        if (c == "\"") {
+            # Handle quoted string - capture everything until closing quote
+            token = "\""
+            i++
+            while (i <= length(line_text) && substr(line_text, i, 1) != "\"") {
+                token = token substr(line_text, i, 1)
+                i++
+            }
+            if (i <= length(line_text)) {
+                token = token "\""
+                i++
+            }
+            tokens[++tokens_len] = token
+        } else {
+            # Regular token - capture until whitespace
+            token = ""
+            while (i <= length(line_text) && substr(line_text, i, 1) != " " && substr(line_text, i, 1) != "\t") {
+                token = token substr(line_text, i, 1)
+                i++
+            }
+            if (token != "") {
+                tokens[++tokens_len] = token
+            }
+        }
+    }
+    
+    # Combine - tokens with / (like -Screen/pixel)
+    for (i = 1; i < tokens_len; i++) {
+        if (tokens[i] == "-" && index(tokens[i+1], "/") > 0) {
+            tokens[i] = tokens[i] tokens[i+1]
+            for (j = i + 1; j < tokens_len; j++) {
+                tokens[j] = tokens[j+1]
+            }
+            tokens_len--
+        }
+    }
+    
+    for (i = 1; i <= tokens_len; i++) {
+        token = tokens[i]
+        if (!pass2_parse_token(token)) {
+            printf "ERROR: Failed to parse token '%s' at line %d\n", token, line_number > "/dev/stderr"
+            return 0
+        }
+    }
+    return 1
+}
+
+function pass2_parse_token(w) {
+    printf "DEBUG: pass2_parse_token processing '%s'\n", w > "/dev/stderr"
+    
+    # Skip standalone tokens (but not device field references)
+    if (w == ">") {
+        return 1
+    }
+    
+    # Handle labels (just skip, already collected in pass 1)
+    if (substr(w, 1, 1) == "@" || substr(w, 1, 1) == "&") {
+        return 1
+    }
+    
+    # Skip brackets and control flow
+    if (substr(w, 1, 1) == "[" || substr(w, 1, 1) == "]" || w == "{") {
+        return 1
+    }
+    
+    # Handle lambda labels (just skip, already collected in pass 1)
+    if (w == "}") {
+        return 1
+    }
+    
+    # Handle padding
+    if (substr(w, 1, 1) == "|") {
+        # Set pointer based on padding value (no writing, just positioning)
+        padding_val = substr(w, 2)
+        if (padding_val == "0000") {
+            ptr = 0
+        } else if (padding_val == "0100") {
+            ptr = PAGE
+        } else {
+            ptr = shex(padding_val)
+        }
+        return 1
+    }
+    if (substr(w, 1, 1) == "$") {
+        # Advance pointer by size (no writing, just positioning)
+        size_val = shex(substr(w, 2))
+        ptr += size_val
+        return 1
+    }
+    
+    # Handle references (resolve them now)
+    if (substr(w, 1, 1) == "_") {
+        resolve_ref(substr(w, 2), substr(w, 1, 1), ptr) && writebyte(0xff)
+        return 1
+    }
+    if (substr(w, 1, 1) == ",") {
+        resolve_ref(substr(w, 2), substr(w, 1, 1), ptr + 1) && writebyte(128) && writebyte(0xff)
+        return 1
+    }
+    if (substr(w, 1, 1) == "-") {
+        # Device field reference
+        if (index(substr(w, 2), "/") > 0) {
+            resolve_device_ref(substr(w, 2), ptr)
+            writebyte(0xff)
+        } else {
+            resolve_ref(substr(w, 2), substr(w, 1, 1), ptr)
+            writebyte(0xff)
+        }
+        return 1
+    }
+    if (substr(w, 1, 1) == ".") {
+        # Check if this is a device field reference
+        if (index(substr(w, 2), "/") > 0) {
+            resolve_device_ref(substr(w, 2), ptr + 1) && writebyte(128) && writebyte(0xff)
+        } else {
+            resolve_ref(substr(w, 2), substr(w, 1, 1), ptr + 1) && writebyte(128) && writebyte(0xff)
+        }
+        return 1
+    }
+    if (substr(w, 1, 1) == "=") {
+        resolve_ref(substr(w, 2), substr(w, 1, 1), ptr) && writeshort(0xffff)
+        return 1
+    }
+    if (substr(w, 1, 1) == ";") {
+        resolve_ref(substr(w, 2), substr(w, 1, 1), ptr + 1) && writebyte(160) && writeshort(0xffff)
+        return 1
+    }
+    if (substr(w, 1, 1) == "?") {
+        resolve_ref(substr(w, 2), substr(w, 1, 1), ptr + 1) && writebyte(32) && writeshort(0xffff)
+        return 1
+    }
+    if (substr(w, 1, 1) == "!") {
+        resolve_ref(substr(w, 2), substr(w, 1, 1), ptr + 1) && writebyte(64) && writeshort(0xffff)
+        return 1
+    }
+    
+    # Handle hex literals (with # prefix or raw hex values)
+    if (substr(w, 1, 1) == "#") {
+        writehex(w)
+        return 1
+    }
+    
+    # Handle raw hex values (like font data)
+    if (ishex(w)) {
+        writehex(w)
+        return 1
+    }
+    
+    # Handle opcodes
+    if (isopc(w)) {
+        writebyte(findopcode(w))
+        return 1
+    }
+    
+    # Handle string literals
+    if (substr(w, 1, 1) == "\"") {
+        writestring(substr(w, 2, length(w) - 2))
+        return 1
+    }
+    
+    # Handle macro calls (tokens starting with <)
+    if (substr(w, 1, 1) == "<" && substr(w, length(w), 1) == ">") {
+        expandmacro(substr(w, 2, length(w) - 2))
+        return 1
+    }
+    
+    # Handle unknown tokens as label references (fallback)
+    printf "DEBUG: Unknown token '%s' treated as label reference\n", w > "/dev/stderr"
+    makeref(w, " ", ptr + 1)
+    ptr += 3  # LIT2 + 2 bytes
+    return 1
+}
+
+function resolve_ref(label, rune, addr,    l, rel) {
+    l = findlabel(label)
+    if (l == 0) {
+        printf "Label unknown: %s\n", label > "/dev/stderr"
+        return 0
+    }
+    
+    # Resolve based on reference type
+    if (rune == "_" || rune == ",") {
+        rel = labels[l, "addr"] - addr - 2
+        data[addr] = rel
+    } else if (rune == "-" || rune == ".") {
+        data[addr] = labels[l, "addr"]
+    } else if (rune == "=" || rune == ";") {
+        data[addr] = int(labels[l, "addr"] / 256)
+        data[addr + 1] = labels[l, "addr"] % 256
+    } else if (rune == "?" || rune == "!") {
+        rel = labels[l, "addr"] - addr - 2
+        data[addr] = int(rel / 256)
+        data[addr + 1] = rel % 256
+    }
+    
+    labels[l, "refs"]++
+    return 1
+}
+
+function resolve_device_ref(device_field, addr,    device_name, field_name, device_addr) {
+    # Split device/field
+    split(device_field, parts, "/")
+    if (length(parts) != 2) {
+        printf "Invalid device field: %s\n", device_field > "/dev/stderr"
+        return 0
+    }
+    
+    device_name = parts[1]
+    field_name = parts[2]
+    
+    device_addr = finddevicefield(device_name, field_name)
+    if (device_addr == -1) {
+        printf "Device field unknown: %s\n", device_field > "/dev/stderr"
+        return 0
+    }
+    
+    data[addr] = device_addr
+    return 1
+}
+
+function writebyte(b) {
+    if (ptr >= 65536) {
+        printf "Writing outside memory\n" > "/dev/stderr"
+        return 0
+    }
+    
+    # Only write to data array if we're in the code section (ptr >= PAGE)
+    if (ptr >= PAGE) {
+        data[ptr] = b
+        if (b) {
+            data_length = ptr
+        }
+        printf "DEBUG: writebyte(%d) at ptr %d, data_length now %d\n", b, ptr, data_length > "/dev/stderr"
+    }
+    ptr++
+    return 1
+}
+
+function writeshort(x) {
+    return writebyte(int(x / 256)) && writebyte(x % 256)
+}
+
+function writehex(w) {
+    if (substr(w, 1, 1) == "#") {
+        # Write LIT opcode
+        if (length(substr(w, 2)) > 2) {
+            writebyte(32)  # LIT2
+        } else {
+            writebyte(128)   # LIT (BRK + 128)
+        }
+        w = substr(w, 2)
+    }
+    
+    if (ishex(w)) {
+        if (length(w) == 2) {
+            return writebyte(shex(w))
+        } else if (length(w) == 4) {
+            return writeshort(shex(w))
+        }
+    }
+    
+    printf "Hexadecimal invalid: %s\n", w > "/dev/stderr"
+    return 0
+}
+
+# Macro functions
+function findmacro(name,    i) {
+    for (i = 0; i < macros_len; i++) {
+        if (macro_names[i] == name) {
+            return i
+        }
+    }
+    return -1
+}
+
+function makemacro(name,    i) {
+    printf "DEBUG: makemacro called with name: %s\n", name > "/dev/stderr"
+    if (macros_len >= 256) {
+        printf "Macros limit exceeded\n" > "/dev/stderr"
+        return 0
+    }
+    if (findmacro(name) >= 0) {
+        printf "Macro duplicate: %s\n", name > "/dev/stderr"
+        return 0
+    }
+    if (findlabel(name) >= 0) {
+        printf "Label duplicate: %s\n", name > "/dev/stderr"
+        return 0
+    }
+    
+    # Store macro name and initialize empty body
+    macro_names[macros_len] = name
+    macro_data[macros_len] = ""
+    macros_len++
+    
+    # Note: We'll capture the macro body in pass2 when we process the file again
+    return 1
+}
+
+function capture_macro_body(name, filename,    line, in_macro, macro_body, depth) {
+    # Reset file to beginning
+    close(filename)
+    in_macro = 0
+    macro_body = ""
+    depth = 0
+    
+    while ((getline line < filename) > 0) {
+        if (in_macro) {
+            # Check if we've reached the end of the macro (next label or closing brace)
+            if (substr(line, 1, 1) == "@" && substr(line, 2, 1) != "|") {
+                # Found next label, end of macro
+                break
+            }
+            
+            # Count braces for nested macro handling
+            for (i = 1; i <= length(line); i++) {
+                c = substr(line, i, 1)
+                if (c == "{") depth++
+                else if (c == "}") {
+                    depth--
+                    if (depth < 0) break  # End of macro
+                }
+            }
+            
+            if (depth < 0) break  # End of macro
+            
+            # Add line to macro body
+            macro_body = macro_body line "\n"
+        } else if (substr(line, 1, 1) == "@" && substr(line, 2, 1) == "<") {
+            # Check if this is our macro
+            macro_name = substr(line, 3)
+            if (substr(macro_name, length(macro_name), 1) == ">") {
+                macro_name = substr(macro_name, 1, length(macro_name) - 1)
+                if (macro_name == name) {
+                    in_macro = 1
+                    # Start capturing from next line
+                }
+            }
+        }
+    }
+    
+    close(filename)
+    
+    # Store the macro body
+    for (i = 0; i < macros_len; i++) {
+        if (macro_names[i] == name) {
+            macro_data[i] = macro_body
+            return 1
+        }
+    }
+    return 0
+}
+
+function expandmacro(name,    macro_idx, macro_text, tokens, i) {
+    macro_idx = findmacro(name)
+    if (macro_idx < 0) {
+        printf "Macro not found: %s\n", name > "/dev/stderr"
+        return 0
+    }
+    
+    # If macro body is empty, try to capture it
+    if (macro_data[macro_idx] == "") {
+        capture_macro_body(name, ARGV[1])
+    }
+    
+    macro_text = macro_data[macro_idx]
+    if (macro_text == "") {
+        printf "Macro body empty: %s\n", name > "/dev/stderr"
+        return 0
+    }
+    
+    # Process macro body line by line
+    split(macro_text, lines, "\n")
+    for (i = 1; i <= length(lines); i++) {
+        if (lines[i] != "") {
+            pass2_process_line(lines[i])
+        }
+    }
+    return 1
+}
+
+# Lambda functions
+function makelambda(id) {
+    # Create a unique lambda name like "λb" where suffix is hex digit
+    return sprintf("λ%c", substr(hexad, int(id / 16) + 1, 1) substr(hexad, (id % 16) + 1, 1))
+}
+
+function ord(c) {
+    return index(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", c) + 31
+}
+
+function writestring(w,    i, c) {
+    for (i = 1; i <= length(w); i++) {
+        c = substr(w, i, 1)
+        # Simple ASCII conversion
+        if (!writebyte(ord(c))) {
+            printf "String invalid\n" > "/dev/stderr"
+            return 0
+        }
+    }
+    return 1
+}
+
+function build_output(rompath) {
+    # Write ROM file
+    printf "DEBUG: Writing %d bytes from PAGE (%d) to data_length (%d)\n", data_length - PAGE + 1, PAGE, data_length > "/dev/stderr"
+    for (i = PAGE; i <= data_length; i++) {
+        printf "%c", data[i] > rompath
+    }
+    close(rompath)
+    
+    return 1
+}
diff --git a/awk/uxn/ref/uxnasm.c b/awk/uxn/ref/uxnasm.c
new file mode 100644
index 0000000..f25d6ce
--- /dev/null
+++ b/awk/uxn/ref/uxnasm.c
@@ -0,0 +1,481 @@
+#include <stdio.h>
+
+/*
+Copyright (c) 2021-2024 Devine Lu Linvega, Andrew Alderwick
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE.
+*/
+
+/* clang-format off */
+
+#define PAGE 0x0100
+#define ishex(x) (shex(x) >= 0)
+#define isopc(x) (findopcode(x) || scmp(x, "BRK", 4))
+#define isinvalid(x) (!x[0] || ishex(x) || isopc(x) || find(runes, x[0]) >= 0)
+#define writeshort(x) (writebyte(x >> 8, ctx) && writebyte(x & 0xff, ctx))
+#define findlabel(x) finditem(x, labels, labels_len)
+#define findmacro(x) finditem(x, macros, macro_len)
+#define error_top(id, msg) !printf("%s: %s\n", id, msg)
+#define error_asm(id) !printf("%s: %s in @%s, %s:%d.\n", id, token, scope, ctx->path, ctx->line)
+#define error_ref(id) !printf("%s: %s, %s:%d\n", id, r->name, r->data, r->line)
+
+typedef unsigned char Uint8;
+typedef signed char Sint8;
+typedef unsigned short Uint16;
+typedef struct { int line; char *path; } Context;
+typedef struct { char *name, rune, *data; Uint16 addr, refs, line; } Item;
+
+static int ptr, length;
+static char token[0x30], scope[0x40], lambda[0x05];
+static char dict[0x8000], *dictnext = dict;
+static Uint8 data[0x10000], lambda_stack[0x100], lambda_ptr, lambda_len;
+static Uint16 labels_len, refs_len, macro_len;
+static Item labels[0x400], refs[0x1000], macros[0x100];
+
+static char *runes = "|$@&,_.-;=!?#\"%~";
+static char *hexad = "0123456789abcdef";
+static char ops[][4] = {
+	"LIT", "INC", "POP", "NIP", "SWP", "ROT", "DUP", "OVR",
+	"EQU", "NEQ", "GTH", "LTH", "JMP", "JCN", "JSR", "STH",
+	"LDZ", "STZ", "LDR", "STR", "LDA", "STA", "DEI", "DEO",
+	"ADD", "SUB", "MUL", "DIV", "AND", "ORA", "EOR", "SFT"
+};
+
+/* clang-format on */
+
+static int
+find(char *s, char t)
+{
+	int i = 0;
+	char c;
+	while((c = *s++)) {
+		if(c == t) return i;
+		i++;
+	}
+	return -1;
+}
+
+static int
+shex(char *s)
+{
+	int d, n = 0;
+	char c;
+	while((c = *s++)) {
+		d = find(hexad, c);
+		if(d < 0) return d;
+		n = n << 4, n |= d;
+	}
+	return n;
+}
+
+static int
+scmp(char *a, char *b, int len)
+{
+	int i = 0;
+	while(a[i] == b[i])
+		if(!a[i] || ++i >= len) return 1;
+	return 0;
+}
+
+static char *
+copy(char *src, char *dst, char c)
+{
+	while(*src && *src != c) *dst++ = *src++;
+	*dst++ = 0;
+	return dst;
+}
+
+static char *
+save(char *s, char c)
+{
+	char *o = dictnext;
+	while((*dictnext++ = *s++) && *s);
+	*dictnext++ = c;
+	return o;
+}
+
+static char *
+join(char *a, char j, char *b)
+{
+	char *res = dictnext;
+	save(a, j), save(b, 0);
+	return res;
+}
+
+static char *
+push(char *s, char c)
+{
+	char *d;
+	for(d = dict; d < dictnext; d++) {
+		char *ss = s, *dd = d, a, b;
+		while((a = *dd++) == (b = *ss++))
+			if(!a && !b) return d;
+	}
+	return save(s, c);
+}
+
+static Item *
+finditem(char *name, Item *list, int len)
+{
+	int i;
+	if(name[0] == '&')
+		name = join(scope, '/', name + 1);
+	for(i = 0; i < len; i++)
+		if(scmp(list[i].name, name, 0x40))
+			return &list[i];
+	return NULL;
+}
+
+static Uint8
+findopcode(char *s)
+{
+	int i;
+	for(i = 0; i < 0x20; i++) {
+		int m = 3;
+		if(!scmp(ops[i], s, 3)) continue;
+		if(!i) i |= (1 << 7);
+		while(s[m]) {
+			if(s[m] == '2')
+				i |= (1 << 5);
+			else if(s[m] == 'r')
+				i |= (1 << 6);
+			else if(s[m] == 'k')
+				i |= (1 << 7);
+			else
+				return 0;
+			m++;
+		}
+		return i;
+	}
+	return 0;
+}
+
+static int
+walkcomment(FILE *f, Context *ctx)
+{
+	char c, last = 0;
+	int depth = 1;
+	while(f && fread(&c, 1, 1, f)) {
+		if(c <= 0x20) {
+			if(c == 0xa) ctx->line++;
+			if(last == '(')
+				depth++;
+			else if(last == ')' && --depth < 1)
+				return 1;
+			last = 0;
+		} else if(last <= 0x20)
+			last = c;
+		else
+			last = '~';
+	}
+	return error_asm("Comment incomplete");
+}
+
+static int parse(char *w, FILE *f, Context *ctx);
+
+static int
+walkmacro(Item *m, Context *ctx)
+{
+	unsigned char c;
+	char *dataptr = m->data, *_token = token;
+	while((c = *dataptr++)) {
+		if(c < 0x21) {
+			*_token = 0x00;
+			if(token[0] && !parse(token, NULL, ctx)) return 0;
+			_token = token;
+		} else if(_token - token < 0x2f)
+			*_token++ = c;
+		else
+			return error_asm("Token size exceeded");
+	}
+	return 1;
+}
+
+static int
+walkfile(FILE *f, Context *ctx)
+{
+	unsigned char c;
+	char *_token = token;
+	while(f && fread(&c, 1, 1, f)) {
+		if(c < 0x21) {
+			*_token = 0x00;
+			if(token[0] && !parse(token, f, ctx)) return 0;
+			if(c == 0xa) ctx->line++;
+			_token = token;
+		} else if(_token - token < 0x2f)
+			*_token++ = c;
+		else
+			return error_asm("Token size exceeded");
+	}
+	*_token = 0;
+	return parse(token, f, ctx);
+}
+
+static char *
+makelambda(int id)
+{
+	lambda[0] = (char)0xce;
+	lambda[1] = (char)0xbb;
+	lambda[2] = hexad[id >> 0x4];
+	lambda[3] = hexad[id & 0xf];
+	return lambda;
+}
+
+static int
+makemacro(char *name, FILE *f, Context *ctx)
+{
+	int depth = 0;
+	char c;
+	Item *m;
+	if(macro_len >= 0x100) return error_asm("Macros limit exceeded");
+	if(isinvalid(name)) return error_asm("Macro invalid");
+	if(findmacro(name)) return error_asm("Macro duplicate");
+	if(findlabel(name)) return error_asm("Label duplicate");
+	m = &macros[macro_len++];
+	m->name = push(name, 0);
+	m->data = dictnext;
+	while(f && fread(&c, 1, 1, f) && c != '{')
+		if(c == 0xa) ctx->line++;
+	while(f && fread(&c, 1, 1, f)) {
+		if(c == 0xa) ctx->line++;
+		if(c == '%') return error_asm("Macro nested");
+		if(c == '{') depth++;
+		if(c == '}' && --depth) break;
+		*dictnext++ = c;
+	}
+	*dictnext++ = 0;
+	return 1;
+}
+
+static int
+makelabel(char *name, int setscope, Context *ctx)
+{
+	Item *l;
+	if(name[0] == '&')
+		name = join(scope, '/', name + 1);
+	if(labels_len >= 0x400) return error_asm("Labels limit exceeded");
+	if(isinvalid(name)) return error_asm("Label invalid");
+	if(findmacro(name)) return error_asm("Label duplicate");
+	if(findlabel(name)) return error_asm("Label duplicate");
+	l = &labels[labels_len++];
+	l->name = push(name, 0);
+	l->addr = ptr;
+	l->refs = 0;
+	if(setscope) copy(name, scope, '/');
+	return 1;
+}
+
+static int
+makeref(char *label, char rune, Uint16 addr, Context *ctx)
+{
+	Item *r;
+	if(refs_len >= 0x1000) return error_asm("References limit exceeded");
+	r = &refs[refs_len++];
+	if(label[0] == '{') {
+		lambda_stack[lambda_ptr++] = lambda_len;
+		r->name = push(makelambda(lambda_len++), 0);
+		if(label[1]) return error_asm("Label invalid");
+	} else if(label[0] == '&' || label[0] == '/') {
+		r->name = join(scope, '/', label + 1);
+	} else
+		r->name = push(label, 0);
+	r->rune = rune;
+	r->addr = addr;
+	r->line = ctx->line;
+	r->data = ctx->path;
+	return 1;
+}
+
+static int
+writepad(char *w, Context *ctx)
+{
+	Item *l;
+	int rel = w[0] == '$' ? ptr : 0;
+	if(ishex(w + 1)) {
+		ptr = shex(w + 1) + rel;
+		return 1;
+	}
+	if((l = findlabel(w + 1))) {
+		ptr = l->addr + rel;
+		return 1;
+	}
+	return error_asm("Padding invalid");
+}
+
+static int
+writebyte(Uint8 b, Context *ctx)
+{
+	if(ptr < PAGE)
+		return error_asm("Writing zero-page");
+	else if(ptr >= 0x10000)
+		return error_asm("Writing outside memory");
+	else if(ptr < length)
+		return error_asm("Writing rewind");
+	data[ptr++] = b;
+	if(b)
+		length = ptr;
+	return 1;
+}
+
+static int
+writehex(char *w, Context *ctx)
+{
+	if(*w == '#')
+		writebyte(findopcode("LIT") | !!(++w)[2] << 5, ctx);
+	if(ishex(w)) {
+		if(w[1] && !w[2])
+			return writebyte(shex(w), ctx);
+		else if(w[3] && !w[4])
+			return writeshort(shex(w));
+	}
+	return error_asm("Hexadecimal invalid");
+}
+
+static int
+writestring(char *w, Context *ctx)
+{
+	char c;
+	while((c = *(w++)))
+		if(!writebyte(c, ctx)) return error_asm("String invalid");
+	return 1;
+}
+
+static int
+assemble(char *filename)
+{
+	FILE *f;
+	int res;
+	Context ctx;
+	ctx.line = 1;
+	ctx.path = push(filename, 0);
+	if(!(f = fopen(filename, "r")))
+		return error_top("File missing", filename);
+	res = walkfile(f, &ctx);
+	fclose(f);
+	return res;
+}
+
+static int
+parse(char *w, FILE *f, Context *ctx)
+{
+	Item *m;
+	switch(w[0]) {
+	case 0x0: return 1;
+	case '(':
+		if(w[1] <= 0x20)
+			return walkcomment(f, ctx);
+		else
+			return error_asm("Invalid word");
+	case '%': return makemacro(w + 1, f, ctx);
+	case '@': return makelabel(w + 1, 1, ctx);
+	case '&': return makelabel(w, 0, ctx);
+	case '}': return makelabel(makelambda(lambda_stack[--lambda_ptr]), 0, ctx);
+	case '#': return writehex(w, ctx);
+	case '_': return makeref(w + 1, w[0], ptr, ctx) && writebyte(0xff, ctx);
+	case ',': return makeref(w + 1, w[0], ptr + 1, ctx) && writebyte(findopcode("LIT"), ctx) && writebyte(0xff, ctx);
+	case '-': return makeref(w + 1, w[0], ptr, ctx) && writebyte(0xff, ctx);
+	case '.': return makeref(w + 1, w[0], ptr + 1, ctx) && writebyte(findopcode("LIT"), ctx) && writebyte(0xff, ctx);
+	case ':': printf("Deprecated rune %s, use =%s\n", w, w + 1); /* fall-through */
+	case '=': return makeref(w + 1, w[0], ptr, ctx) && writeshort(0xffff);
+	case ';': return makeref(w + 1, w[0], ptr + 1, ctx) && writebyte(findopcode("LIT2"), ctx) && writeshort(0xffff);
+	case '?': return makeref(w + 1, w[0], ptr + 1, ctx) && writebyte(0x20, ctx) && writeshort(0xffff);
+	case '!': return makeref(w + 1, w[0], ptr + 1, ctx) && writebyte(0x40, ctx) && writeshort(0xffff);
+	case '"': return writestring(w + 1, ctx);
+	case '~': return !assemble(w + 1) ? error_asm("Include missing") : 1;
+	case '$':
+	case '|': return writepad(w, ctx);
+	case '[':
+	case ']': return 1;
+	}
+	if(ishex(w)) return writehex(w, ctx);
+	if(isopc(w)) return writebyte(findopcode(w), ctx);
+	if((m = findmacro(w))) return walkmacro(m, ctx);
+	return makeref(w, ' ', ptr + 1, ctx) && writebyte(0x60, ctx) && writeshort(0xffff);
+}
+
+static int
+resolve(char *filename)
+{
+	int i, rel;
+	if(!length) return error_top("Output empty", filename);
+	for(i = 0; i < refs_len; i++) {
+		Item *r = &refs[i], *l = findlabel(r->name);
+		Uint8 *rom = data + r->addr;
+		if(!l) return error_ref("Label unknown");
+		switch(r->rune) {
+		case '_':
+		case ',':
+			*rom = rel = l->addr - r->addr - 2;
+			if((Sint8)data[r->addr] != rel)
+				return error_ref("Reference too far");
+			break;
+		case '-':
+		case '.':
+			*rom = l->addr;
+			break;
+		case ':':
+		case '=':
+		case ';':
+			*rom++ = l->addr >> 8, *rom = l->addr;
+			break;
+		case '?':
+		case '!':
+		default:
+			rel = l->addr - r->addr - 2;
+			*rom++ = rel >> 8, *rom = rel;
+			break;
+		}
+		l->refs++;
+	}
+	return 1;
+}
+
+static int
+build(char *rompath)
+{
+	int i;
+	FILE *dst, *dstsym;
+	char *sympath = join(rompath, '.', "sym");
+	/* rom */
+	if(!(dst = fopen(rompath, "wb")))
+		return !error_top("Output file invalid", rompath);
+	for(i = 0; i < labels_len; i++)
+		if(!labels[i].refs && (unsigned char)(labels[i].name[0] - 'A') > 25)
+			printf("-- Unused label: %s\n", labels[i].name);
+	fwrite(data + PAGE, length - PAGE, 1, dst);
+	printf(
+		"Assembled %s in %d bytes(%.2f%% used), %d labels, %d macros.\n",
+		rompath,
+		length - PAGE,
+		(length - PAGE) / 652.80,
+		labels_len,
+		macro_len);
+	/* sym */
+	if(!(dstsym = fopen(sympath, "wb")))
+		return !error_top("Symbols file invalid", sympath);
+	for(i = 0; i < labels_len; i++) {
+		Uint8 hb = labels[i].addr >> 8, lb = labels[i].addr;
+		char c, d = 0, *name = labels[i].name;
+		fwrite(&hb, 1, 1, dstsym);
+		fwrite(&lb, 1, 1, dstsym);
+		while((c = *name++)) fwrite(&c, 1, 1, dstsym);
+		fwrite(&d, 1, 1, dstsym);
+	}
+	fclose(dst), fclose(dstsym);
+	return 1;
+}
+
+int
+main(int argc, char *argv[])
+{
+	ptr = PAGE;
+	copy("on-reset", scope, 0);
+	if(argc == 2 && scmp(argv[1], "-v", 2)) return !printf("Uxnasm - Uxntal Assembler, 15 Jan 2025.\n");
+	if(argc != 3) return error_top("usage", "uxnasm [-v] input.tal output.rom");
+	return !assemble(argv[1]) || !resolve(argv[2]) || !build(argv[2]);
+}
diff --git a/awk/uxn/test/runner.sh b/awk/uxn/test/runner.sh
new file mode 100755
index 0000000..711dd28
--- /dev/null
+++ b/awk/uxn/test/runner.sh
@@ -0,0 +1,105 @@
+#!/bin/bash
+
+# Test runner for Uxntal AWK assembler
+# Compares output with reference C implementation
+
+set -e
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+# Paths
+AWK_ASM="../awk/uxnasm.awk"
+REF_ASM="../ref/uxnasm"
+TEST_DIR="tal"
+OUT_DIR="out"
+
+# Ensure output directory exists
+mkdir -p "$OUT_DIR"
+
+echo "Testing Uxntal AWK Assembler"
+echo "============================="
+
+# Check if reference assembler exists
+if [ ! -f "$REF_ASM" ]; then
+    echo -e "${RED}Error: Reference assembler not found at $REF_ASM${NC}"
+    echo "Please compile the reference implementation first:"
+    echo "  cd ../ref && make"
+    exit 1
+fi
+
+# Check if AWK assembler exists
+if [ ! -f "$AWK_ASM" ]; then
+    echo -e "${RED}Error: AWK assembler not found at $AWK_ASM${NC}"
+    exit 1
+fi
+
+# Test function
+test_file() {
+    local input_file="$1"
+    local base_name=$(basename "$input_file" .tal)
+    
+    echo -n "Testing $base_name.tal... "
+    
+    # Run reference assembler
+    if ! "$REF_ASM" "$input_file" "$OUT_DIR/${base_name}_ref.rom" >/dev/null 2>&1; then
+        echo -e "${RED}FAIL${NC} (reference assembler failed)"
+        return 1
+    fi
+    
+    # Run AWK assembler
+    if [ "$DEBUG_AWK" -eq 1 ]; then
+        awk -f "$AWK_ASM" "$input_file" "$OUT_DIR/${base_name}_awk.rom" 2> "$OUT_DIR/${base_name}_awk.debug"
+    else
+        awk -f "$AWK_ASM" "$input_file" "$OUT_DIR/${base_name}_awk.rom" >/dev/null 2>&1
+    fi
+    
+    # Compare outputs
+    if cmp -s "$OUT_DIR/${base_name}_ref.rom" "$OUT_DIR/${base_name}_awk.rom"; then
+        echo -e "${GREEN}PASS${NC}"
+        if [ "$DEBUG_AWK" -eq 1 ]; then
+            echo "  Debug output: $OUT_DIR/${base_name}_awk.debug"
+        fi
+        return 0
+    else
+        echo -e "${RED}FAIL${NC} (outputs differ)"
+        echo "  Reference size: $(wc -c < "$OUT_DIR/${base_name}_ref.rom") bytes"
+        echo "  AWK size: $(wc -c < "$OUT_DIR/${base_name}_awk.rom") bytes"
+        echo "  Diff:"
+        xxd "$OUT_DIR/${base_name}_ref.rom" > "$OUT_DIR/${base_name}_ref.hex"
+        xxd "$OUT_DIR/${base_name}_awk.rom" > "$OUT_DIR/${base_name}_awk.hex"
+        diff "$OUT_DIR/${base_name}_ref.hex" "$OUT_DIR/${base_name}_awk.hex" || true
+        if [ "$DEBUG_AWK" -eq 1 ]; then
+            echo "  Debug output: $OUT_DIR/${base_name}_awk.debug"
+        fi
+        return 1
+    fi
+}
+
+# Run tests
+failed=0
+total=0
+
+for test_file in "$TEST_DIR"/*.tal; do
+    if [ -f "$test_file" ]; then
+        total=$((total + 1))
+        if ! test_file "$test_file"; then
+            failed=$((failed + 1))
+        fi
+    fi
+done
+
+echo
+echo "============================="
+echo "Results: $((total - failed))/$total tests passed"
+
+if [ $failed -eq 0 ]; then
+    echo -e "${GREEN}All tests passed!${NC}"
+    exit 0
+else
+    echo -e "${RED}$failed tests failed${NC}"
+    exit 1
+fi
diff --git a/awk/uxn/test/tal/brk.tal b/awk/uxn/test/tal/brk.tal
new file mode 100644
index 0000000..bf83010
--- /dev/null
+++ b/awk/uxn/test/tal/brk.tal
@@ -0,0 +1 @@
+BRK
\ No newline at end of file
diff --git a/awk/uxn/test/tal/brk_test.tal b/awk/uxn/test/tal/brk_test.tal
new file mode 100644
index 0000000..487f63d
--- /dev/null
+++ b/awk/uxn/test/tal/brk_test.tal
@@ -0,0 +1 @@
+BRK 
\ No newline at end of file
diff --git a/awk/uxn/test/tal/brk_with_data.tal b/awk/uxn/test/tal/brk_with_data.tal
new file mode 100644
index 0000000..c055e74
--- /dev/null
+++ b/awk/uxn/test/tal/brk_with_data.tal
@@ -0,0 +1 @@
+#01 #02 ADD BRK 
\ No newline at end of file
diff --git a/awk/uxn/test/tal/bunnymark.tal b/awk/uxn/test/tal/bunnymark.tal
new file mode 100644
index 0000000..579d305
--- /dev/null
+++ b/awk/uxn/test/tal/bunnymark.tal
@@ -0,0 +1,221 @@
+( bunnymark.tal )
+	( November 2021, Kira Oakley )
+	( March 2022, Devine Lu Linvega )
+
+|00 @System &vector $2 &pad $6 &r $2 &g $2 &b $2 &debug $1 &halt $1
+|20 @Screen &vector $2 &width $2 &height $2 &auto $1 &pad $1 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1
+|80 @Controller &vector $2 &button $1 &key $1
+|90 @Mouse &vector $2 &x $2 &y $2 &state $1 &wheel $1
+|c0 @DateTime &year $2 &month $1 &day $1 &hour $1 &minute $1 &second $1 &dotw $1 &doty $2 &isdst $1
+
+|0000
+
+	@frames $2
+	@last $1
+
+|0100
+
+@on-reset ( -> )
+	( | theme )
+	#2ce9 .System/r DEO2
+	#01c0 .System/g DEO2
+	#2ce5 .System/b DEO2
+	( | interrupts )
+	;on-frame .Screen/vector DEO2
+	( | fps label )
+	.Screen/width DEI2 #0046 SUB2 .Screen/x DEO2
+	#0008 .Screen/y DEO2
+	;text/fps #42 <draw-str>
+	( | bunnies label )
+	#0004 .Screen/x DEO2
+	;text/bunnies #42 <draw-str>
+	( | instructions label )
+	.Screen/width DEI2 #01 SFT2 #0050 SUB2 .Screen/x DEO2
+	;text/instructions #43 <draw-str>
+	#0028 #0008 #0000 <draw-dec>
+	( | seed prng )
+	prng-init BRK
+
+@on-frame ( -> )
+	.frames LDZ2k INC2 ROT STZ2
+	.DateTime/second DEI .last LDZ EQU ?{
+		.DateTime/second DEI .last STZ
+		.Screen/width DEI2 #002b SUB2 #0008 .frames LDZ2 <draw-dec>
+		#0000 .frames STZ2 }
+	( | mouse handling )
+	.Mouse/state DEI
+	( ) DUP #01 NEQ ?{ add-bunny }
+	( ) #02 LTH ?{ remove-bunny }
+	( | controller handling )
+	.Controller/button DEI
+	( ) DUP #01 NEQ ?{ add-bunny }
+	( ) #02 LTH ?{ remove-bunny }
+	( | clear )
+	#0000 DUP2 .Screen/x DEO2
+	.Screen/y DEO2
+	[ LIT2 80 -Screen/pixel ] DEO
+	;sprite/length LDA2 #0000
+	&loop ( -- )
+		EQU2k ?&bail
+		DUP2 <draw-bunny>
+		INC2 !&loop
+	&bail ( -- )
+		POP2 POP2 BRK
+
+@add-bunny ( -- )
+	;sprite/length LDA2
+	( | cap bunny count at 65535 )
+	DUP2 #ffff EQU2 ?&bail
+	( | compute the offset to the beginning of this new bunny's data )
+	DUP2 #30 SFT2 ;sprite/array ADD2
+	( | populate the new bunny's x/y/xvel/yvel with random values )
+	#00 rand OVR2 STA2
+	rand #1f AND rand OVR2 INC2 INC2 STA2
+	#00 rand #7f AND OVR2 #0004 ADD2 STA2
+	#00 rand #7f AND OVR2 #0006 ADD2 STA2
+	( | pop ptr to bunny data )
+	POP2
+	( | write new increased array length )
+	INC2 DUP2 ;sprite/length STA2
+	( | update label )
+	STH2k #0028 #0008 STH2r <draw-dec>
+	&bail ( pop sprite/length )
+		POP2 JMP2r
+
+@remove-bunny ( -- )
+	;sprite/length LDA2
+	( don't let length go below 0 ) ORAk #00 EQU ?&bail
+	#0001 SUB2 DUP2 ;sprite/length STA2
+	( update label ) STH2k #0028 #0008 STH2r <draw-dec>
+	&bail POP2 JMP2r
+
+(
+@|drawing )
+
+@<draw-bunny> ( idx -- )
+	( | compute the offset to the beginning of this bunny's data )
+	#30 SFT2 ;sprite/array ADD2
+	( | move the sprite by its velocity )
+	LDA2k OVR2 #0004 ADD2 LDA2 ADD2 OVR2 STA2
+	INC2k INC2 LDA2 OVR2 #0006 ADD2 LDA2 ADD2 OVR2 INC2 INC2 STA2
+	( | check for right wall collision + bounce x )
+	DUP2 #0004 ADD2 LDA2 #0f SFT2 #0001 EQU2 ?&skip-max-x
+	LDA2k #05 SFT2 #0008 ADD2 .Screen/width DEI2 LTH2 ?&skip-max-x
+	DUP2 #0004 ADD2 LDA2 #ffff MUL2 OVR2 #0004 ADD2 STA2
+	&skip-max-x ( check for left wall collision + bounce x )
+		LDA2k #0f SFT2 #0000 EQU2 ?&skip-min-x
+		DUP2 #0004 ADD2 LDA2 #ffff MUL2 OVR2 #0004 ADD2 STA2
+	&skip-min-x ( check for bottom wall collision + bounce y )
+		DUP2 #0006 ADD2 LDA2 #0f SFT2 #0001 EQU2 ?&skip-max-y
+		INC2k INC2 LDA2 #05 SFT2 #0010 ADD2 .Screen/height DEI2 LTH2 ?&skip-max-y
+		DUP2 #0006 ADD2 LDA2 #ffff MUL2 OVR2 #0006 ADD2 STA2
+		!&skip-gravity
+	&skip-max-y ( check for top wall collision + bounce x )
+		INC2k INC2 LDA2 #0f SFT2 #0000 EQU2 ?&skip-min-y
+		DUP2 #0006 ADD2 LDA2 #ffff MUL2 OVR2 #0006 ADD2 STA2
+		!&skip-gravity
+	&skip-min-y ( apply gravity )
+		DUP2 #0006 ADD2 LDA2 #0004 ADD2 OVR2 #0006 ADD2 STA2
+	&skip-gravity ( draw the sprite )
+		( top ) LDA2k #05 SFT2 .Screen/x DEO2
+		INC2 INC2 LDA2 #05 SFT2 .Screen/y DEO2
+		( draw ) [ LIT2 15 -Screen/auto ] DEO
+		;bunny-chr .Screen/addr DEO2
+		[ LIT2 85 -Screen/sprite ] DEO
+		[ LIT2 00 -Screen/auto ] DEO
+		JMP2r
+
+@<draw-str> ( x* y* text* color -- )
+	,&t STR
+	[ LIT2 01 -Screen/auto ] DEO
+	&loop ( -- )
+		LDAk #20 SUB #00 SWP #30 SFT2 ;font ADD2 .Screen/addr DEO2
+		[ LIT2 &t $1 -Screen/sprite ] DEO
+		INC2 LDAk ?&loop
+	POP2 JMP2r
+
+@<draw-dec> ( x* y* num* -- )
+	[ LIT2 01 -Screen/auto ] DEO
+	SWP2 .Screen/y DEO2
+	SWP2 .Screen/x DEO2
+	#2710 DIV2k DUP <draw-digit>
+	MUL2 SUB2 #03e8 DIV2k DUP <draw-digit>
+	MUL2 SUB2 #0064 DIV2k DUP <draw-digit>
+	MUL2 SUB2 NIP #0a DIVk DUP <draw-digit>
+	MUL SUB <draw-digit>
+	[ LIT2 00 -Screen/auto ] DEO
+	JMP2r
+
+@<draw-digit> ( num -- )
+	#30 SFT #00 SWP ;font/num ADD2 .Screen/addr DEO2
+	[ LIT2 41 -Screen/sprite ] DEO
+	JMP2r
+
+(
+@|random )
+
+@prng-init ( -- )
+	[ LIT2 00 -DateTime/second ] DEI [ LIT2 00 -DateTime/minute ] DEI #60 SFT2 EOR2 [ LIT2 00 -DateTime/hour ] DEI #c0 SFT2 EOR2 ,prng/x STR2
+	[ LIT2 00 -DateTime/hour ] DEI #04 SFT2 [ LIT2 00 -DateTime/day ] DEI #10 SFT2 EOR2 [ LIT2 00 -DateTime/month ] DEI #60 SFT2 EOR2 .DateTime/year DEI2 #a0 SFT2 EOR2 ,prng/y STR2
+	JMP2r
+
+@prng ( -- number* )
+	[ LIT2 &x $2 ] DUP2 #50 SFT2 EOR2 DUP2 #03 SFT2 EOR2 [ LIT2 &y $2 ] DUP2 ,&x STR2
+	DUP2 #01 SFT2 EOR2 EOR2 ,&y STR2k POP JMP2r
+
+@rand ( -- number )
+	prng ADD JMP2r
+	( static string data )
+
+(
+@|assets )
+
+@text &fps "FPS: $1
+	&bunnies "BUNS: $1
+	&instructions "CLICK 20 "TO 20 "ADD 20 "BUNNIES! $1
+
+@font ( atari8.uf1 )
+	[
+	0000 0000 0000 0000 6060 6060 6000 6000
+	6666 6600 0000 0000 006c fe6c 6cfe 6c00
+	183e 603c 067c 1800 0066 6c18 3066 4600
+	386c 3870 decc 7600 6060 6000 0000 0000
+	0e1c 1818 181c 0e00 7038 1818 1838 7000
+	0066 3cff 3c66 0000 0018 187e 1818 0000
+	0000 0000 0030 3060 0000 007e 0000 0000
+	0000 0000 0018 1800 0206 0c18 3060 4000 ] &num [
+	3c66 6e76 6666 3c00 1838 1818 1818 7e00
+	3c66 060c 1830 7e00 7e0c 180c 0666 3c00
+	0c1c 3c6c 7e0c 0c00 7e60 7c06 0666 3c00
+	3c60 607c 6666 3c00 7e06 0c18 3030 3000
+	3c66 663c 6666 3c00 3c66 663e 060c 3800
+	0060 6000 6060 0000 0030 3000 3030 6000
+	0c18 3060 3018 0c00 0000 7e00 007e 0000
+	6030 180c 1830 6000 3c66 060c 1800 1800
+	3c66 6e6a 6e60 3e00 183c 6666 7e66 6600
+	7c66 667c 6666 7c00 3c66 6060 6066 3c00
+	786c 6666 666c 7800 7e60 607c 6060 7e00
+	7e60 607c 6060 6000 3e60 606e 6666 3e00
+	6666 667e 6666 6600 7830 3030 3030 7800
+	0606 0606 0666 3c00 666c 7870 786c 6600
+	6060 6060 6060 7e00 c6ee fed6 c6c6 c600
+	6676 7e7e 6e66 6600 3c66 6666 6666 3c00
+	7c66 667c 6060 6000 3c66 6666 766c 3600
+	7c66 667c 6c66 6600 3c66 603c 0666 3c00
+	7e18 1818 1818 1800 6666 6666 6666 3e00
+	6666 6666 663c 1800 c6c6 c6d6 feee c600
+	6666 3c18 3c66 6600 6666 663c 1818 1800
+	7e06 0c18 3060 7e00 7860 6060 6060 7800 ]
+
+@fill-icn [ ffff ffff ffff ffff ]
+
+@bunny-chr [
+	2466 6600 2424 003c 4200 007e 7e7e 7e7e
+	1818 3c3c 1800 0000 ff66 4242 667e 4242 ]
+
+(
+@|memory )
+
+@sprite &length $2
+	&array &x 0600 &y 0500 &xvel 0060 &yvel 0010
+
diff --git a/awk/uxn/test/tal/life.tal b/awk/uxn/test/tal/life.tal
new file mode 100644
index 0000000..718068b
--- /dev/null
+++ b/awk/uxn/test/tal/life.tal
@@ -0,0 +1,221 @@
+( uxnemu life.rom )
+	( Any live cell with fewer than two live neighbours dies, as if by underpopulation. )
+	( Any live cell with two or three live neighbours lives on to the next generation. )
+	( Any live cell with more than three live neighbours dies, as if by overpopulation. )
+	( Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction. )
+
+|00 @System &vector $2 &expansion $2 &wst $1 &rst $1 &metadata $2 &r $2 &g $2 &b $2 &debug $1 &state $1
+|10 @Console &vector $2 &read $1 &pad $5 &write $1 &error $1
+|20 @Screen &vector $2 &width $2 &height $2 &auto $1 &pad $1 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1
+|30 @Audio0 &vector $2 &position $2 &output $1 &pad $3 &adsr $2 &length $2 &addr $2 &volume $1 &pitch $1
+|80 @Controller &vector $2 &button $1 &key $1
+|90 @Mouse &vector $2 &x $2 &y $2 &state $1 &wheel $1
+|000
+
+	@world &count $2
+	@anchor &x $2 &y $2 &x2 $2 &y2 $2
+
+|100
+
+@on-reset ( -> )
+	( | theme )
+	#02cf .System/r DEO2
+	#02ff .System/g DEO2
+	#024f .System/b DEO2
+	( | resize )
+	#00c0 DUP2 .Screen/width DEO2
+	.Screen/height DEO2
+	( | vectors )
+	;on-frame .Screen/vector DEO2
+	;on-mouse .Mouse/vector DEO2
+	;on-control .Controller/vector DEO2
+	( | glider )
+	#0703 <set-cell>
+	#0704 <set-cell>
+	#0504 <set-cell>
+	#0705 <set-cell>
+	#0605 <set-cell>
+	( | center )
+	.Screen/width DEI2 #01 SFT2 #0040 SUB2 DUP2 .anchor/x STZ2
+	#007e ADD2 .anchor/x2 STZ2
+	.Screen/height DEI2 #01 SFT2 #0040 SUB2 DUP2 .anchor/y STZ2
+	#007e ADD2 .anchor/y2 STZ2
+	BRK
+
+@on-frame ( -> )
+	[ LIT2 00 -Mouse/state ] DEI EQU ?{ BRK }
+	#0000 .world/count STZ2
+	[ LIT &f $1 ] INCk ,&f STR
+	( ) #03 AND #00 EQU ?{ BRK }
+	<run>
+	BRK
+
+@on-mouse ( -> )
+	[ LIT2 00 -Mouse/state ] DEI NEQ #42 ADD ;cursor-icn <update-cursor>
+	( | on touch in rect )
+	.Mouse/state DEI ?{ BRK }
+	.Mouse/x DEI2 .Mouse/y DEI2 .anchor within-rect ?{ BRK }
+	( | paint )
+	.Mouse/x DEI2 .anchor/x LDZ2 SUB2 #01 SFT NIP
+	( ) .Mouse/y DEI2 .anchor/y LDZ2 SUB2 #01 SFT NIP <set-cell>
+	<redraw>
+	BRK
+
+@on-control ( -> )
+	.Controller/key DEI
+	( ) DUP #20 NEQ ?{
+		#0000 ;on-frame .Screen/vector DEI2 ORA ?{ SWP2 }
+		POP2 .Screen/vector DEO2 }
+	( ) #1b NEQ ?{ ;MMU/clear1 .System/expansion DEO2 }
+	BRK
+
+(
+@|core )
+
+@<run> ( -- )
+	;MMU/clear2 .System/expansion DEO2
+	#4000
+	&ver ( -- )
+		DUP ,&y STR
+		#4000
+	&hor ( -- )
+		DUP [ LIT &y $1 ] <run-cell>
+		INC GTHk ?&hor
+	POP2 INC GTHk ?&ver
+	POP2
+	( move ) ;MMU/move21 .System/expansion DEO2
+	!<redraw>
+
+@<run-cell> ( x y -- )
+	( x y ) DUP2 STH2k
+	( neighbours ) get-neighbours
+	( state ) STH2r get-index LDA #00 EQU ?&dead
+	DUP #02 LTH ?&dies
+	DUP #03 GTH ?&dies
+	POP !&save
+	&dies POP POP2 JMP2r
+	&dead ( -- )
+		DUP #03 EQU ?&birth
+		POP POP2 JMP2r
+		&birth POP !&save
+	&save ( x y -- )
+		STH2
+		#01 STH2r get-index #1000 ADD2 STA
+		.world/count LDZ2 INC2 .world/count STZ2
+		JMP2r
+
+@get-index ( x y -- index* )
+	( y ) #3f AND #00 SWP #60 SFT2 ROT
+	( x ) #3f AND #00 SWP ADD2 ;bank1 ADD2 JMP2r
+
+@<set-cell> ( x y -- )
+	get-index STH2
+	#01 STH2r STA
+	JMP2r
+
+@get-neighbours ( x y -- neighbours )
+	,&y STR
+	,&x STR
+	[ LITr 00 ] #0800
+	&l ( -- )
+		#00 OVRk ADD2 ;&mask ADD2 LDA2
+		( ) [ LIT &y $1 ] ADD SWP
+		( ) [ LIT &x $1 ] ADD SWP get-index LDA [ STH ADDr ]
+		( stop at 3 ) DUPr [ LITr 03 ] GTHr [ LITr _&end ] JCNr
+		( ) INC GTHk ?&l
+	&end POP2 STHr JMP2r
+	&mask [
+	ffff 00ff 01ff ff00 0100 ff01 0001 0101 ]
+
+@within-rect ( x* y* rect -- flag )
+	STH
+	( y < rect.y1 ) DUP2 STHkr INC INC LDZ2 LTH2 ?&skip
+	( y > rect.y2 ) DUP2 STHkr #06 ADD LDZ2 GTH2 ?&skip
+	SWP2
+	( x < rect.x1 ) DUP2 STHkr LDZ2 LTH2 ?&skip
+	( x > rect.x2 ) DUP2 STHkr #04 ADD LDZ2 GTH2 ?&skip
+	POP2 POP2 POPr #01 JMP2r
+	&skip POP2 POP2 POPr #00 JMP2r
+
+(
+@|drawing )
+
+@<redraw> ( -- )
+	( | draw count )
+	.anchor/x LDZ2 .Screen/x DEO2
+	.anchor/y2 LDZ2 #0008 ADD2 .Screen/y DEO2
+	[ LIT2 01 -Screen/auto ] DEO
+	.world/count LDZ2 <draw-short>
+	( | draw grid )
+	[ LIT2 01 -Screen/auto ] DEO
+	.anchor/y LDZ2 .Screen/y DEO2
+	;bank2 ;bank1
+	&l ( -- )
+		DUP #3f AND ?{
+			.Screen/y DEI2k INC2 INC2 ROT DEO2
+			.anchor/x LDZ2 .Screen/x DEO2 }
+		LDAk INC .Screen/pixel DEO
+		[ LIT2 00 -Screen/pixel ] DEO
+		INC2 GTH2k ?&l
+	POP2 POP2 JMP2r
+
+@<draw-short> ( short* -- )
+	SWP <draw-byte>
+	( >> )
+
+@<draw-byte> ( byte color -- )
+	DUP #04 SFT <draw-hex>
+	#0f AND
+	( >> )
+
+@<draw-hex> ( char color -- )
+	#00 SWP #30 SFT2 ;font-hex ADD2 .Screen/addr DEO2
+	[ LIT2 03 -Screen/sprite ] DEO
+	JMP2r
+
+@<update-cursor> ( color addr* -- )
+	[ LIT2 00 -Screen/auto ] DEO
+	;fill-icn .Screen/addr DEO2
+	#40 <draw-cursor>
+	.Mouse/x DEI2 ,<draw-cursor>/x STR2
+	.Mouse/y DEI2 ,<draw-cursor>/y STR2
+	.Screen/addr DEO2
+	( >> )
+
+@<draw-cursor> ( color -- )
+	[ LIT2 &x $2 ] .Screen/x DEO2
+	[ LIT2 &y $2 ] .Screen/y DEO2
+	.Screen/sprite DEO
+	JMP2r
+
+(
+@|assets )
+
+@MMU ( programs )
+	&clear1 [ 01 1000 0000 =bank3 0000 =bank1 ]
+	&clear2 [ 01 1000 0000 =bank3 0000 =bank2 ]
+	&move21 [ 01 1000 0000 =bank2 0000 =bank1 ]
+
+@cursor-icn [ 80c0 e0f0 f8e0 1000 ]
+
+@fill-icn [ ffff ffff ffff ffff ]
+
+@font-hex [
+	7c82 8282 8282 7c00 3010 1010 1010 3800
+	7c82 027c 8080 fe00 7c82 021c 0282 7c00
+	2242 82fe 0202 0200 fe80 807c 0282 7c00
+	7c82 80fc 8282 7c00 fe82 0408 0810 1000
+	7c82 827c 8282 7c00 7c82 827e 0202 0200
+	7c82 82fe 8282 8200 fc82 82fc 8282 fc00
+	7c82 8080 8082 7c00 fc82 8282 8282 fc00
+	fe80 80f0 8080 fe00 fe80 80f0 8080 8000 ]
+
+(
+@|memory )
+
+|8000 @bank1 $1000
+
+@bank2 $1000
+
+@bank3 $1000
+
diff --git a/awk/uxn/test/tal/opctest.tal b/awk/uxn/test/tal/opctest.tal
new file mode 100644
index 0000000..b803de6
--- /dev/null
+++ b/awk/uxn/test/tal/opctest.tal
@@ -0,0 +1,492 @@
+( Opcode Tester )
+
+|0013
+
+	@Zeropage &byte $1 &short $2
+	@id $1
+
+|100
+
+@on-reset ( -> )
+
+	( part 1
+		> LIT2: Puts a short on the stack
+		> LIT: Puts a byte on the stack
+		> #06 DEO: Write to metadata ports
+		> #18 DEO: Write a letter in terminal )
+
+	;meta #06 DEO2
+	[ LIT2 "kO ] #18 DEO #18 DEO
+	[ LIT2 "1 18 ] DEO #0a18 DEO
+
+	( part 2
+		> LITr: Put a byte on return stack
+		> STH: Move a byte from working stack to return stack
+		> STH2r: Move a short from return stack to working stack )
+
+	[ LITr "k ] [ LIT "O ] STH STH2r #18 DEO #18 DEO
+	[ LIT2 "2 18 ] DEO #0a18 DEO
+
+	( part 3
+		> LIT2r: Put a short on return stack
+		> DUP: Duplicate byte
+		> ADDr: Add bytes on return stack )
+
+	[ LIT2r "k 4d ] #01 DUP STH ADDr STH ADDr STH2r #18 DEO #18 DEO
+	[ LIT2 "3 18 ] DEO #0a18 DEO
+
+	( part 4
+		> JSI: Subroutine to relative short address
+		> JMP2r: Jumps to absolute address on return stack )
+
+	subroutine
+	[ LIT2 "4 18 ] DEO #0a18 DEO
+
+	( part 5
+		> POP2: Removes a short from the stack
+		> INC2: Increments short on stack
+		> DUP2: Duplicate short
+		> LDA: load byte from absolute address
+		> JCI: Conditional subroutine to relative short address )
+
+	;Dict/ok pstr
+	[ LIT2 "5 18 ] DEO #0a18 DEO
+
+	( part 6
+		> JSR2: Jump to subroutine from short pointer
+		> LDAk: Non-destructive load byte from absolute address )
+
+	{ "Ok $1 } STH2r ;pstr-jcn JSR2
+	[ LIT2 "6 18 ] DEO #0a18 DEO
+
+	( part 7
+		> Relative distance bytes )
+
+	rel-distance/entry SWP #18 DEO #18 DEO
+	[ LIT2 "7 18 ] DEO #0a18 DEO
+
+	( part xx
+		> GTH2k: Non-destructive greater-than short
+		> LDA2k: Non-destructive load short from absolute address
+		> STA2: Store short at absolute address )
+
+	[ LIT2r 0000 ]
+	;tests/end ;tests
+	&l
+		run-test [ LITr 00 ] STH ADD2r
+		INC2 INC2 GTH2k ?&l
+	POP2 POP2
+	STH2r ;tests/end ;tests SUB2 #01 SFT2
+	EQU2 ;Dict/opctests test-part
+
+	( Part xx
+		> Testing that stacks are circular and wrapping
+		> Storing 12 at -1 and 34 at 0 )
+
+	POP #12 #34 ADD #46 EQU STH
+	POP #1234 ADD #46 EQU STH
+	POP2 #1111 #2222 ADD2 #3333 EQU2
+	STHr AND STHr AND
+	;Dict/stack-wrap test-part
+
+	( restore stack ) #0000 #0000
+
+	( Part xx
+		> Testing RAM wrapping
+		> Storing 12 in 0xffff, and 34 in 0x0000 )
+
+	#1234 #ffff STA2
+	( LDA ) #0000 LDA #ffff LDA ADD #46 EQU
+	( LDA2 ) #ffff LDA2 ADD #46 EQU
+	AND ;Dict/ram-wrap test-part
+
+	( Part xx
+		> Testing that zero-page is wrapping )
+
+	#5678 #ff STZ2
+	( LDZ ) #00 LDZ #ff LDZ ADD #ce EQU
+	( LDZ2 ) #ff LDZ2 ADD #ce EQU
+	AND ;Dict/zp-wrap test-part
+
+	( Part xx
+		> Testing that device page is wrapping )
+
+	#1234 #ff DEO2
+	( DEI ) #00 DEI #ff DEI ADD #46 EQU
+	( DEI2 ) #ff DEI2 ADD #46 EQU
+	AND ;Dict/dev-wrap test-part
+	#0000 DEO #00ff DEO
+
+	( end )
+
+	[ LIT &fail 80 ]
+		DUP #80 EQU ;Dict/result test-part
+		#0f DEO
+
+	#0a18 DEO
+	#010e DEO
+
+BRK
+
+(
+@|metadata )
+
+@meta 00
+	( name ) "Opctest 0a
+	( details ) "A 20 "Testing 20 "Program 0a
+	( author ) "By 20 "Devine 20 "Lu 20 "Linvega 0a
+	( date ) "24 20 "Jun 20 "2025 $2
+
+@test-part ( f name* -- )
+	pstr ?{
+		#01 ;on-reset/fail STA
+		;Dict/failed !pstr }
+	;Dict/passed !pstr
+
+@run-test ( addr* -- addr* f )
+
+	LDA2k JSR2 DUP ?&pass
+		;Dict/missed pstr
+		[ LIT2 &name $2 ] pstr
+		[ LIT2 "# 18 ] DEO
+		[ LIT2 "a -id ] LDZ ADD #18 DEO
+		#0a18 DEO
+		#01 ;on-reset/fail STA
+		&pass
+	.id LDZ INC .id STZ
+
+JMP2r
+
+@set ( name* -- f )
+
+	;run-test/name STA2 #01
+	[ LIT2 ff -id ] STZ
+
+JMP2r
+
+@pstr ( str* -- )
+	DUP2 LDA
+		DUP ?{ POP POP2 JMP2r }
+		#18 DEO
+	INC2 !pstr
+
+@pstr-jcn ( str* -- )
+	LDAk #18 DEO
+	INC2 LDAk ,pstr-jcn JCN
+	POP2
+	JMP2r
+
+@tests
+=op-equ [
+	=op-equ/a =op-equ/b =op-equ/c =op-equ/d
+	=op-equ/e =op-equ/f =op-equ/g =op-equ/h ]
+=op-neq [
+	=op-neq/a =op-neq/b =op-neq/c =op-neq/d
+	=op-neq/e =op-neq/f =op-neq/g =op-neq/h ]
+=op-gth [
+	=op-gth/a =op-gth/b =op-gth/c =op-gth/d
+	=op-gth/e =op-gth/f =op-gth/g =op-gth/h ]
+=op-lth [
+	=op-lth/a =op-lth/b =op-lth/c =op-lth/d
+	=op-lth/e =op-lth/f =op-lth/g =op-lth/h ]
+=op-add [
+	=op-add/a =op-add/b =op-add/c =op-add/d
+	=op-add/e =op-add/f =op-add/g =op-add/h ]
+=op-sub [
+	=op-sub/a =op-sub/b =op-sub/c =op-sub/d
+	=op-sub/e =op-sub/f =op-sub/g =op-sub/h ]
+=op-mul [
+	=op-mul/a =op-mul/b =op-mul/c =op-mul/d
+	=op-mul/e =op-mul/f =op-mul/g =op-mul/h ]
+=op-div [
+	=op-div/a =op-div/b =op-div/c =op-div/d =op-div/e
+	=op-div/f =op-div/g =op-div/h =op-div/i =op-div/j ]
+=op-inc [
+	=op-inc/a =op-inc/b =op-inc/c =op-inc/d
+	=op-inc/e =op-inc/f =op-inc/g =op-inc/h ]
+=op-pop [
+	=op-pop/a =op-pop/b =op-pop/c =op-pop/d
+	=op-pop/e =op-pop/f =op-pop/g =op-pop/h ]
+=op-dup [
+	=op-dup/a =op-dup/b ]
+=op-nip [
+	=op-nip/a =op-nip/b =op-nip/c =op-nip/d ]
+=op-swp [
+	=op-swp/a =op-swp/b ]
+=op-ovr [
+	=op-ovr/a =op-ovr/b ]
+=op-rot [
+	=op-rot/a =op-rot/b ]
+=op-and [
+	=op-and/a =op-and/b =op-and/c =op-and/d
+	=op-and/e =op-and/f =op-and/g =op-and/h ]
+=op-ora [
+	=op-ora/a =op-ora/b =op-ora/c =op-ora/d
+	=op-ora/e =op-ora/f =op-ora/g =op-ora/h ]
+=op-eor [
+	=op-eor/a =op-eor/b =op-eor/c =op-eor/d
+	=op-eor/e =op-eor/f =op-eor/g =op-eor/h ]
+=op-sft [
+	=op-sft/a =op-sft/b =op-sft/c =op-sft/d
+	=op-sft/e =op-sft/f =op-sft/g =op-sft/h ]
+=op-stz [
+	=op-stz/a =op-stz/b =op-stz/c =op-stz/d ]
+=op-str [
+	=op-str/a =op-str/b =op-str/c =op-str/d ]
+=op-sta [
+	=op-sta/a =op-sta/b =op-sta/c =op-sta/d ]
+=op-jmp [
+	=op-jmp/a =op-jmp/b ]
+=op-jcn [
+	=op-jcn/a =op-jcn/b =op-jcn/c =op-jcn/d ]
+=op-jsr [
+	=op-jsr/a =op-jsr/b ]
+=op-sth [
+	=op-sth/a =op-sth/b ]
+=op-jci [
+	=op-jci/a =op-jci/b =op-jci/c ]
+=op-jmi [
+	=op-jmi/a ]
+=op-jsi [
+	=op-jsi/a =op-jsi/b =op-jsi/c =op-jsi/d ]
+	&end
+
+@op-equ ;Dict/equ !set
+	&a #f8 #f8 EQU [ #01 ] EQU JMP2r
+	&b #01 #01 EQU [ #01 ] EQU JMP2r
+	&c #f8 #01 EQU [ #00 ] EQU JMP2r
+	&d #00 #ff EQU [ #00 ] EQU JMP2r
+	&e #f801 #f801 EQU2 [ #01 ] EQU JMP2r
+	&f #01f8 #01f8 EQU2 [ #01 ] EQU JMP2r
+	&g #f801 #01f8 EQU2 [ #00 ] EQU JMP2r
+	&h #01f8 #f801 EQU2 [ #00 ] EQU JMP2r
+@op-neq ;Dict/neq !set
+	&a #f8 #f8 NEQ [ #00 ] EQU JMP2r
+	&b #01 #01 NEQ [ #00 ] EQU JMP2r
+	&c #f8 #01 NEQ [ #01 ] EQU JMP2r
+	&d #01 #f8 NEQ [ #01 ] EQU JMP2r
+	&e #f801 #f801 NEQ2 [ #00 ] EQU JMP2r
+	&f #01f8 #01f8 NEQ2 [ #00 ] EQU JMP2r
+	&g #f801 #01f8 NEQ2 [ #01 ] EQU JMP2r
+	&h #01f8 #f801 NEQ2 [ #01 ] EQU JMP2r
+@op-gth ;Dict/gth !set
+	&a #f8 #f8 GTH [ #00 ] EQU JMP2r
+	&b #01 #01 GTH [ #00 ] EQU JMP2r
+	&c #f8 #01 GTH [ #01 ] EQU JMP2r
+	&d #01 #f8 GTH [ #00 ] EQU JMP2r
+	&e #f801 #f801 GTH2 [ #00 ] EQU JMP2r
+	&f #01f8 #01f8 GTH2 [ #00 ] EQU JMP2r
+	&g #f801 #01f8 GTH2 [ #01 ] EQU JMP2r
+	&h #01f8 #f801 GTH2 [ #00 ] EQU JMP2r
+@op-lth ;Dict/lth !set
+	&a #f8 #f8 LTH [ #00 ] EQU JMP2r
+	&b #01 #01 LTH [ #00 ] EQU JMP2r
+	&c #f8 #01 LTH [ #00 ] EQU JMP2r
+	&d #01 #ff LTH [ #01 ] EQU JMP2r
+	&e #f801 #f801 LTH2 [ #00 ] EQU JMP2r
+	&f #01f8 #01f8 LTH2 [ #00 ] EQU JMP2r
+	&g #f801 #01f8 LTH2 [ #00 ] EQU JMP2r
+	&h #01f8 #f801 LTH2 [ #01 ] EQU JMP2r
+@op-add ;Dict/add !set
+	&a #ff #00 ADD [ #ff ] EQU JMP2r
+	&b #01 #ff ADD [ #00 ] EQU JMP2r
+	&c #ff #ff ADD [ #fe ] EQU JMP2r
+	&d #12 #34 ADDk ADD ADD [ #8c ] EQU JMP2r
+	&e #ffff #0000 ADD2 [ #ffff ] EQU2 JMP2r
+	&f #0001 #ffff ADD2 [ #0000 ] EQU2 JMP2r
+	&g #ffff #ffff ADD2 [ #fffe ] EQU2 JMP2r
+	&h #fffe #ffff ADD2 [ #fffd ] EQU2 JMP2r
+@op-sub ;Dict/sub !set
+	&a #ff #00 SUB [ #ff ] EQU JMP2r
+	&b #01 #ff SUB [ #02 ] EQU JMP2r
+	&c #ff #ff SUB [ #00 ] EQU JMP2r
+	&d #fe #ff SUB [ #ff ] EQU JMP2r
+	&e #ffff #0000 SUB2 [ #ffff ] EQU2 JMP2r
+	&f #0001 #ffff SUB2 [ #0002 ] EQU2 JMP2r
+	&g #ffff #ffff SUB2 [ #0000 ] EQU2 JMP2r
+	&h #fffe #ffff SUB2 [ #ffff ] EQU2 JMP2r
+@op-mul ;Dict/mul !set
+	&a #00 #01 MUL [ #00 ] EQU JMP2r
+	&b #3f #e7 MUL [ #d9 ] EQU JMP2r
+	&c #37 #3f MUL [ #89 ] EQU JMP2r
+	&d #10 #02 MUL [ #20 ] EQU JMP2r
+	&e #1000 #0003 MUL2 [ #3000 ] EQU2 JMP2r
+	&f #abcd #1234 MUL2 [ #4fa4 ] EQU2 JMP2r
+	&g #8000 #0200 MUL2 [ #0000 ] EQU2 JMP2r
+	&h #2222 #0003 MUL2 [ #6666 ] EQU2 JMP2r
+@op-div ;Dict/div !set
+	&a #10 #06 DIV [ #02 ] EQU JMP2r
+	&b #20 #20 DIV [ #01 ] EQU JMP2r
+	&c #34 #01 DIV [ #34 ] EQU JMP2r
+	&d #02 #ef DIV [ #00 ] EQU JMP2r
+	&e #02 #00 DIV [ #00 ] EQU JMP2r
+	&f #03e8 #0006 DIV2 [ #00a6 ] EQU2 JMP2r
+	&g #abcd #1234 DIV2 [ #0009 ] EQU2 JMP2r
+	&h #8000 #0200 DIV2 [ #0040 ] EQU2 JMP2r
+	&i #2222 #0003 DIV2 [ #0b60 ] EQU2 JMP2r
+	&j #0202 #0000 DIV2 [ #0000 ] EQU2 JMP2r
+@op-inc ;Dict/inc !set
+	&a #01 INC [ #02 ] EQU JMP2r
+	&b #ff INC [ #00 ] EQU JMP2r
+	&c #fe INC [ #ff ] EQU JMP2r
+	&d #00 INC [ #01 ] EQU JMP2r
+	&e #0001 INC2 [ #0002 ] EQU2 JMP2r
+	&f #ffff INC2 [ #0000 ] EQU2 JMP2r
+	&g #fffe INC2 [ #ffff ] EQU2 JMP2r
+	&h #0000 INC2 [ #0001 ] EQU2 JMP2r
+@op-pop ;Dict/pop !set
+	&a #0a #0b POP [ #0a ] EQU JMP2r
+	&b #0a #0b #0c POP POP [ #0a ] EQU JMP2r
+	&c #0a #0b #0c ADD POP [ #0a ] EQU JMP2r
+	&d #0a #0b #0c POP ADD [ #15 ] EQU JMP2r
+	&e #0a0b #0c0d POP2 [ #0a0b ] EQU2 JMP2r
+	&f #0a0b #0c0d #0e0f POP2 POP2 [ #0a0b ] EQU2 JMP2r
+	&g #0a0b #0c0d #0e0f ADD2 POP2 [ #0a0b ] EQU2 JMP2r
+	&h #0a0b #0c0d #0e0f POP2 ADD2 [ #1618 ] EQU2 JMP2r
+@op-dup ;Dict/dup !set
+	&a #0a #0b DUP ADD ADD [ #20 ] EQU JMP2r
+	&b #0a0b DUP2 ADD2 [ #1416 ] EQU2 JMP2r
+@op-nip ;Dict/nip !set
+	&a #12 #34 #56 NIP ADD [ #68 ] EQU JMP2r
+	&b #12 #34 #56 NIPk ADD2 ADD [ #f2 ] EQU JMP2r
+	&c #1234 #5678 #9abc NIP2 ADD2 [ #acf0 ] EQU2 JMP2r
+	&d #1234 #5678 #9abc NIP2k ADD2 ADD2 ADD2 [ #9e24 ] EQU2 JMP2r
+@op-swp ;Dict/swp !set
+	&a #02 #10 SWP DIV [ #08 ] EQU JMP2r
+	&b #0a0b #0c0d SWP2 NIP2 [ #0a0b ] EQU2 JMP2r
+@op-ovr ;Dict/ovr !set
+	&a #02 #10 OVR DIV ADD [ #0a ] EQU JMP2r
+	&b #0a0b #0c0d OVR2 NIP2 ADD2 [ #1416 ] EQU2 JMP2r
+@op-rot ;Dict/rot !set
+	&a #02 #04 #10 ROT DIV ADD [ #0c ] EQU JMP2r
+	&b #0a0b #0c0d #0c0f ROT2 ADD2 NIP2 [ #161a ] EQU2 JMP2r
+@op-and ;Dict/and !set
+	&a #fc #3f AND [ #3c ] EQU JMP2r
+	&b #f0 #0f AND [ #00 ] EQU JMP2r
+	&c #ff #3c AND [ #3c ] EQU JMP2r
+	&d #02 #03 AND [ #02 ] EQU JMP2r
+	&e #f0f0 #00f0 AND2 [ #00f0 ] EQU2 JMP2r
+	&f #aaaa #5555 AND2 [ #0000 ] EQU2 JMP2r
+	&g #ffff #1234 AND2 [ #1234 ] EQU2 JMP2r
+	&h #abcd #0a0c AND2 [ #0a0c ] EQU2 JMP2r
+@op-ora ;Dict/ora !set
+	&a #0f #f0 ORA [ #ff ] EQU JMP2r
+	&b #ab #cd ORA [ #ef ] EQU JMP2r
+	&c #12 #34 ORA [ #36 ] EQU JMP2r
+	&d #88 #10 ORA [ #98 ] EQU JMP2r
+	&e #0f0f #f0f0 ORA2 [ #ffff ] EQU2 JMP2r
+	&f #abab #cdcd ORA2 [ #efef ] EQU2 JMP2r
+	&g #1122 #1234 ORA2 [ #1336 ] EQU2 JMP2r
+	&h #8888 #1000 ORA2 [ #9888 ] EQU2 JMP2r
+@op-eor ;Dict/eor !set
+	&a #00 #00 EOR [ #00 ] EQU JMP2r
+	&b #ff #00 EOR [ #ff ] EQU JMP2r
+	&c #aa #55 EOR [ #ff ] EQU JMP2r
+	&d #ff #ff EOR [ #00 ] EQU JMP2r
+	&e #ffff #ff00 EOR2 [ #00ff ] EQU2 JMP2r
+	&f #aaaa #5555 EOR2 [ #ffff ] EQU2 JMP2r
+	&g #1122 #1234 EOR2 [ #0316 ] EQU2 JMP2r
+	&h #8888 #1000 EOR2 [ #9888 ] EQU2 JMP2r
+@op-sft ;Dict/sft !set
+	&a #ff #08 SFT [ #00 ] EQU JMP2r
+	&b #ff #e0 SFT [ #00 ] EQU JMP2r
+	&c #ff #11 SFT [ #fe ] EQU JMP2r
+	&d #ff #12 SFT [ #7e ] EQU JMP2r
+	&e #ffff #01 SFT2 [ #7fff ] EQU2 JMP2r
+	&f #ffff #70 SFT2 [ #ff80 ] EQU2 JMP2r
+	&g #ffff #7e SFT2 [ #0180 ] EQU2 JMP2r
+	&h #ffff #e3 SFT2 [ #c000 ] EQU2 JMP2r
+@op-stz ;Dict/stz !set
+	&a #ab .Zeropage/byte STZ .Zeropage/byte LDZ [ #ab ] EQU JMP2r
+	&b #cd .Zeropage/byte STZ .Zeropage/byte LDZ [ #cd ] EQU JMP2r
+	&c #1234 .Zeropage/short STZ2 .Zeropage/short LDZ2 [ #1234 ] EQU2 JMP2r
+	&d #5678 .Zeropage/short STZ2 .Zeropage/short LDZ2 [ #5678 ] EQU2 JMP2r
+@op-str ;Dict/str !set
+	[ LIT &before1 $1 ] [ LIT2 &before2 $2 ]
+	&a #22 ,&before1 STR ,&before1 LDR [ #22 ] EQU JMP2r
+	&b #ef ,&after1 STR ,&after1 LDR [ #ef ] EQU JMP2r
+	&c #1234 ,&before2 STR2 ,&before2 LDR2 [ #1234 ] EQU2 JMP2r
+	&d #5678 ,&after2 STR2 ,&after2 LDR2 [ #5678 ] EQU2 JMP2r
+	[ LIT &after1 $1 ] [ LIT2 &after2 $2 ]
+@op-sta ;Dict/sta !set
+	&a #34 ;Absolute/byte STA ;Absolute/byte LDA [ #34 ] EQU JMP2r
+	&b #56 ;Absolute/byte STA ;Absolute/byte LDA [ #56 ] EQU JMP2r
+	&c #1234 ;Absolute/short STA2 ;Absolute/short LDA2 [ #1234 ] EQU2 JMP2r
+	&d #5678 ;Absolute/short STA2 ;Absolute/short LDA2 [ #5678 ] EQU2 JMP2r
+@op-jmp ;Dict/jmp !set
+	&a #12 #34 ,&reljmp JMP SWP &reljmp POP [ #12 ] EQU JMP2r
+	&b #56 #78 ;&absjmp JMP2 SWP &absjmp POP [ #56 ] EQU JMP2r
+@op-jcn ;Dict/jcn !set
+	&a #23 #01 ,&reljcn-y JCN INC &reljcn-y [ #23 ] EQU JMP2r
+	&b #23 #00 ,&reljcn-n JCN INC &reljcn-n [ #24 ] EQU JMP2r
+	&c #23 #01 ;&absjcn-y JCN2 INC &absjcn-y [ #23 ] EQU JMP2r
+	&d #23 #00 ;&absjcn-n JCN2 INC &absjcn-n [ #24 ] EQU JMP2r
+@op-jsr ;Dict/jsr !set
+	&a #1234 #5678 ,&routine JSR [ #68ac ] EQU2 JMP2r
+	&b #12 #34 ;routine JSR2 [ #46 ] EQU JMP2r
+	&routine ADD2 JMP2r
+@op-sth ;Dict/sth !set
+	&a #0a STH #0b STH ADDr STHr [ #15 ] EQU JMP2r
+	&b #000a STH2 #000b STH2 ADD2r STH2r [ #0015 ] EQU2 JMP2r
+@op-jci ;Dict/jci !set
+	&before #01 JMP2r
+	&a #01 ?&skip-a #00 JMP2r &skip-a #01 JMP2r
+	&b #00 ?&skip-b #01 JMP2r &skip-b #00 JMP2r
+	&c #01 ?&before #00 JMP2r
+@op-jmi ;Dict/jmi !set
+	&a !&skip-a #00 JMP2r &skip-a #01 JMP2r
+@op-jsi ;Dict/jsi !set
+	&a #02 #04 routine #06 EQU JMP2r
+	&b ;&return special &return JMP2r
+	&c ,&skip-c JMP &routine-c ADD JMP2r &skip-c #02 #04 op-jsi/routine-c #06 EQU JMP2r
+	&d ,&skip-d JMP &routine-d ADD JMP2r &skip-d #02 #04 op-jsi-far-routine-d #06 EQU JMP2r
+
+@special ( routine* -- f )
+
+	( test that the stack order is LIFO )
+	DUP2 STH2kr EQU2
+	ROT ROT DUP2r STHr STHr SWP EQU2 AND
+
+JMP2r
+
+@routine ( a b -- c ) ADD JMP2r
+@subroutine ( -- ) [ LIT2 "kO ] #18 DEO #18 DEO JMP2r
+@Absolute &byte $1 &short $2
+
+@Dict [
+	&ok "Ok $1
+	&done "Tests 20 "Complete. 0a $1
+	&opctests "Opcodes $1
+	&stack-wrap "Stack-wrap $1
+	&ram-wrap "RAM-wrap $1
+	&zp-wrap "Zeropage-wrap $1
+	&dev-wrap "Devices-wrap $1
+	&result "Result: $1
+	&passed 20 "passed! 0a $1
+	&missed "Opcode 20 "Failed 20 "-- 20 $1
+	&failed 20 "failed. 0a $1
+	&equ "EQU $1 &neq "NEQ $1 &gth "GTH $1 &lth "LTH $1
+	&add "ADD $1 &sub "SUB $1 &mul "MUL $1 &div "DIV $1
+	&inc "INC $1 &pop "POP $1 &dup "DUP $1 &nip "NIP $1
+	&swp "SWP $1 &ovr "OVR $1 &rot "ROT $1
+	&and "AND $1 &ora "ORA $1 &eor "EOR $1 &sft "SFT $1
+	&stz "STZ $1 &str "STR $1 &sta "STA $1
+	&jmp "JMP $1 &jcn "JCN $1 &jsr "JSR $1 &sth "STH $1
+	&jmi "JMI $1 &jci "JCI $1 &jsi "JSI $1
+]
+
+(
+@|Relative Distance Bytes )
+
+@rel-distance
+&back "O $7c
+&entry
+	,&back LDR
+	,&forw LDR
+	JMP2r
+$7e
+&forw "k
+
+@op-jsi-far-routine-d
+	op-jsi/routine-d JMP2r
+
diff --git a/awk/uxn/test/tal/proper.tal b/awk/uxn/test/tal/proper.tal
new file mode 100644
index 0000000..be8e04b
--- /dev/null
+++ b/awk/uxn/test/tal/proper.tal
@@ -0,0 +1 @@
+@on-reset #01 #02 ADD BRK
\ No newline at end of file
diff --git a/awk/uxn/test/tal/simple.tal b/awk/uxn/test/tal/simple.tal
new file mode 100644
index 0000000..c055e74
--- /dev/null
+++ b/awk/uxn/test/tal/simple.tal
@@ -0,0 +1 @@
+#01 #02 ADD BRK 
\ No newline at end of file
diff --git a/awk/uxn/test/tal/simple2.tal b/awk/uxn/test/tal/simple2.tal
new file mode 100644
index 0000000..6a37b65
--- /dev/null
+++ b/awk/uxn/test/tal/simple2.tal
@@ -0,0 +1 @@
+#01 #02 ADD BRK
\ No newline at end of file
diff --git a/awk/uxn/test/tal/simple3.tal b/awk/uxn/test/tal/simple3.tal
new file mode 100644
index 0000000..09086b7
--- /dev/null
+++ b/awk/uxn/test/tal/simple3.tal
@@ -0,0 +1 @@
+#01 #02 ADD
\ No newline at end of file
diff --git a/js/scripting-lang/FIXME.md b/js/scripting-lang/FIXME.md
new file mode 100644
index 0000000..069703c
--- /dev/null
+++ b/js/scripting-lang/FIXME.md
@@ -0,0 +1,57 @@
+# FIXME: IO Operation Parsing Bug
+
+## Problem Summary
+
+- The parser fails with `Unexpected token in parsePrimary: DOT` when processing IO operations like `..out`, `..assert`, or `..in` at the top level in a script.
+- This occurs in the comprehensive test suite and any script that uses IO operations as standalone statements.
+
+## Observations
+
+- The **lexer** correctly emits `IO_OUT`, `IO_ASSERT`, and `IO_IN` tokens for `..out`, `..assert`, and `..in`.
+- The **parser**'s main loop (`walk()`) now delegates to `parsePrimary()`, which only handles IO tokens if they are the first token in a statement.
+- If a statement starts with a DOT (as in `..out`), and the parser is not expecting it, it throws an error.
+- The bug is not in the lexer (no stray DOT tokens for IO ops), but in the parser's handling of top-level statements.
+- The bug manifests after a block of expressions/statements, when the next statement is an IO operation.
+
+## What We Have in Place
+
+- Lexer emits correct IO tokens for `..out`, `..assert`, `..in`.
+- `parsePrimary()` handles IO tokens if they are the first token in a statement.
+- Table access, dot notation, and function calls with table access are all working.
+- The parser's main loop is too generic and does not explicitly handle IO tokens at the top level.
+
+## Step-by-Step Plan to Fix
+
+1. **Confirm Lexer Output**
+   - Run the lexer in debug mode on a failing script to confirm that IO operations are tokenized as `IO_OUT`, `IO_ASSERT`, or `IO_IN` (not as DOT tokens).
+
+2. **Patch Parser Main Loop**
+   - Update the parser's main loop to explicitly check for IO tokens (`IO_OUT`, `IO_ASSERT`, `IO_IN`) at the top level.
+   - When such a token is found, parse it as a statement using the appropriate logic (as in `parsePrimary()`), not as a generic expression.
+   - This ensures IO operations are always valid as standalone statements.
+
+3. **Test**
+   - Run the comprehensive test suite and verify that all IO operations are parsed and executed correctly.
+   - Add regression tests for IO operations at the start, middle, and end of scripts.
+
+4. **Cleanup**
+   - Refactor the parser to ensure all top-level statement types are handled robustly and simply.
+   - Document the fix in code comments and in this file.
+
+## Minimal Fix Outline
+
+- In the parser's main loop, before calling `walk()`/`parsePrimary()`, check if the current token is an IO token.
+- If so, parse it as an IO statement and advance the token pointer.
+- Otherwise, proceed as normal.
+
+---
+
+**Status:**
+- Lexer: OK
+- Parser: Needs IO statement handling at the top level
+- Interpreter: OK
+- Test suite: Blocked by parser bug
+
+---
+
+**Next step:** Patch the parser main loop as described above, then re-run tests. 
\ No newline at end of file
diff --git a/js/scripting-lang/IDEAS.txt b/js/scripting-lang/IDEAS.txt
new file mode 100644
index 0000000..82eed66
--- /dev/null
+++ b/js/scripting-lang/IDEAS.txt
@@ -0,0 +1,9 @@
+add 2 other io functions
+
+..listen
+
+..emit 
+
+where listen takes in a well defined state object from outside the scope of the program, making it available to the program 
+
+where emit lets the program spit state back out into the wider world
\ No newline at end of file
diff --git a/js/scripting-lang/NEXT-STEPS.md b/js/scripting-lang/NEXT-STEPS.md
new file mode 100644
index 0000000..7cb1e75
--- /dev/null
+++ b/js/scripting-lang/NEXT-STEPS.md
@@ -0,0 +1,229 @@
+# Next Steps: Table Features Implementation
+
+## Current State Analysis
+
+### What Works ✅
+- Basic table creation: `{name: "Alice", age: 30}`
+- Simple table access: `person.name` (single level)
+- Basic function definitions: `inc : x -> x + 1`
+- Basic function calls: `inc 5`
+- Table literals with functions: `{inc: x -> x + 1}` (parsed but not fully functional)
+
+### What's Broken ❌
+1. **Chained table access**: `config.user.name` fails with "Unexpected dot (.) token"
+2. **Function calls from tables**: `m.inc 1` doesn't work
+3. **Functions in table literals**: May not be properly interpreted
+
+## Root Cause Analysis
+
+### Issue 1: Chained Table Access
+**Problem**: Parser encounters standalone DOT tokens when parsing `config.user.name`
+**Location**: Assignment parsing logic in `walk()` function
+**Debug Evidence**: 
+- Tokens: `[IDENTIFIER "config", DOT, IDENTIFIER "user", DOT, IDENTIFIER "name"]`
+- Parser fails at position 17 (second DOT) because it's not in table access context
+
+### Issue 2: Function Calls from Tables
+**Problem**: Parser doesn't recognize `m.inc 1` as a function call
+**Location**: Function call parsing logic
+**Expected**: Should parse as `FunctionCall` with `name: TableAccess` and `args: [1]`
+
+## Implementation Plan
+
+### Phase 1: Fix Chained Table Access Parser
+
+#### Step 1.1: Update Assignment Value Parsing
+**File**: `lang.js` around line 1300-1400
+**Change**: Modify assignment parsing to handle chained dot notation before falling back to `walk()`
+
+**Current Logic**:
+```javascript
+// Assignment parsing falls back to walk() for value
+const value = walk(); // This fails on DOT tokens
+```
+
+**New Logic**:
+```javascript
+// Check if value is a chained table access
+if (tokens[current] && tokens[current].type === TokenType.IDENTIFIER &&
+    tokens[current + 1] && tokens[current + 1].type === TokenType.DOT) {
+    // Parse chained table access
+    const tableAccess = parseChainedTableAccess();
+    return { type: 'AssignmentExpression', name, value: tableAccess };
+}
+// Fall back to walk() for other cases
+const value = walk();
+```
+
+#### Step 1.2: Create parseChainedTableAccess Helper
+**File**: `lang.js` in `walk()` function
+**Purpose**: Parse arbitrary length dot notation chains
+
+**Implementation**:
+```javascript
+function parseChainedTableAccess() {
+    let tableExpr = {
+        type: 'Identifier',
+        value: tokens[current].value
+    };
+    current++; // Skip first identifier
+    
+    while (tokens[current] && tokens[current].type === TokenType.DOT) {
+        current++; // Skip dot
+        if (tokens[current] && tokens[current].type === TokenType.IDENTIFIER) {
+            const key = {
+                type: 'StringLiteral',
+                value: tokens[current].value
+            };
+            current++; // Skip identifier
+            
+            tableExpr = {
+                type: 'TableAccess',
+                table: tableExpr,
+                key
+            };
+        } else {
+            throw new Error('Expected identifier after dot in table access');
+        }
+    }
+    
+    return tableExpr;
+}
+```
+
+#### Step 1.3: Update Function Call Parsing
+**File**: `lang.js` around line 600-700
+**Change**: Allow `TableAccess` nodes as function names
+
+**Current Logic**:
+```javascript
+// Only handles string function names
+func = globalScope[node.name];
+```
+
+**New Logic**:
+```javascript
+if (typeof node.name === 'string') {
+    func = globalScope[node.name];
+} else if (node.name.type === 'TableAccess') {
+    // Evaluate table access to get function
+    func = evalNode(node.name);
+    if (typeof func !== 'function') {
+        throw new Error('Table access did not resolve to a function');
+    }
+}
+```
+
+### Phase 2: Fix Function Calls from Tables
+
+#### Step 2.1: Update Function Call Detection
+**File**: `lang.js` in `parseFunctionCall()` function
+**Change**: Detect when a table access is followed by arguments
+
+**Current Logic**:
+```javascript
+// Only checks for identifier followed by arguments
+if (tokens[current + 1] && tokens[current + 1].type === TokenType.NUMBER) {
+    // Function call
+}
+```
+
+**New Logic**:
+```javascript
+// Check for identifier followed by arguments OR table access followed by arguments
+if ((tokens[current + 1] && tokens[current + 1].type === TokenType.NUMBER) ||
+    (tokens[current + 1] && tokens[current + 1].type === TokenType.DOT)) {
+    // Parse table access first, then check for arguments
+    const tableAccess = parseChainedTableAccess();
+    if (tokens[current] && isArgumentToken(tokens[current])) {
+        // This is a function call from table
+        return parseFunctionCallFromTable(tableAccess);
+    }
+    return tableAccess;
+}
+```
+
+#### Step 2.2: Create parseFunctionCallFromTable Helper
+**Purpose**: Parse function calls where the function is a table access
+
+**Implementation**:
+```javascript
+function parseFunctionCallFromTable(tableAccess) {
+    const args = [];
+    while (current < tokens.length && isArgumentToken(tokens[current])) {
+        args.push(walk());
+    }
+    return {
+        type: 'FunctionCall',
+        name: tableAccess,
+        args
+    };
+}
+```
+
+### Phase 3: Test and Validate
+
+#### Step 3.1: Create Comprehensive Test Suite
+**File**: `table_features_test.txt`
+
+**Test Cases**:
+```plaintext
+/* Test 1: Basic table access */
+person : {name: "Alice", age: 30};
+name : person.name;
+..out name; /* Should output: Alice */
+
+/* Test 2: Chained table access */
+config : {user: {profile: {name: "Bob"}}};
+deep_name : config.user.profile.name;
+..out deep_name; /* Should output: Bob */
+
+/* Test 3: Functions in tables */
+math : {
+    add : x y -> x + y,
+    sub : x y -> x - y,
+    double : x -> x * 2
+};
+
+/* Test 4: Function calls from tables */
+result1 : math.add 3 4;     /* Should be 7 */
+result2 : math.sub 10 3;    /* Should be 7 */
+result3 : math.double 5;    /* Should be 10 */
+..out result1;
+..out result2;
+..out result3;
+
+/* Test 5: Nested function calls from tables */
+nested : math.double(math.add 2 3); /* Should be 10 */
+..out nested;
+```
+
+#### Step 3.2: Debug and Fix Issues
+- Run tests and identify any remaining issues
+- Add debug logging as needed
+- Fix edge cases and error handling
+
+## Implementation Order
+
+1. **Phase 1.1**: Update assignment value parsing
+2. **Phase 1.2**: Create parseChainedTableAccess helper
+3. **Phase 1.3**: Update function call parsing
+4. **Phase 2.1**: Update function call detection
+5. **Phase 2.2**: Create parseFunctionCallFromTable helper
+6. **Phase 3.1**: Create comprehensive test suite
+7. **Phase 3.2**: Debug and validate
+
+## Success Criteria
+
+- ✅ `config.user.name` parses and evaluates correctly
+- ✅ `m.inc 1` parses and evaluates to 2
+- ✅ `m.inc(m.dec(5))` works with nested calls
+- ✅ Functions defined in table literals work correctly
+- ✅ No regression in existing functionality
+
+## Risk Mitigation
+
+- **Minimal changes**: Each change targets a specific issue
+- **Debug logging**: Keep debug output to trace issues
+- **Incremental testing**: Test each phase before proceeding
+- **Fallback logic**: Maintain existing `walk()` fallback for non-table cases 
\ No newline at end of file
diff --git a/js/scripting-lang/README.md b/js/scripting-lang/README.md
new file mode 100644
index 0000000..5630e93
--- /dev/null
+++ b/js/scripting-lang/README.md
@@ -0,0 +1,722 @@
+# Simple Scripting Language
+
+A minimal, interpreted scripting language designed for learning language implementation concepts. This language demonstrates the core components needed for an interpreter: lexer, parser, and interpreter.
+
+## Features
+
+- **Arithmetic operations**: `+`, `-`, `*`, `/`, `%` (modulo), `^` (power)
+- **Comparison operators**: `=`, `<`, `>`, `<=`, `>=`, `!=`
+- **Logical operators**: `and`, `or`, `xor`, `not`
+- **Variable assignment**: Immutable variables with `:` syntax
+- **Function definitions**: Arrow function syntax with pattern matching
+- **Pattern matching**: Case expressions with wildcard support
+- **Function calls**: Direct function application
+- **First-class functions**: Functions can be passed as arguments using `@` syntax
+- **Function composition**: Higher-order functions for combining functions
+- **Lexical scoping**: Functions create their own scope
+- **Input/Output**: Built-in IO operations with `..in`, `..out`, and `..assert`
+- **Standard Library**: Built-in functional programming utilities
+- **Comments**: C-style block comments with `/* ... */` syntax
+- **Tables**: Lua-style immutable tables with array and object capabilities
+
+## Syntax
+
+### Variables
+
+Variables are immutable and assigned using the `:` operator:
+
+```
+x : 5;
+y : 10;
+```
+
+### Arithmetic Operations
+
+Arithmetic operations are supported with parentheses grouping:
+
+```
+sum : x + y;
+diff : x - y;
+product : x * y;
+quotient : x / y;
+modulo : 17 % 5;        /* Returns 2 */
+power : 2 ^ 3;          /* Returns 8 */
+grouped : (5 + 3) * 2;  /* Returns 16 */
+nested : ((5 + 3) * 2) + 1;  /* Returns 17 */
+```
+
+### Function Definitions
+
+Functions are defined using arrow syntax (`->`):
+
+```
+add : x y -> x + y;
+double : x -> x * 2;
+```
+
+### Function Calls
+
+Functions are called by providing arguments directly after the function name:
+
+```
+result : add 3 4;
+doubled : double 5;
+```
+
+#### Function Calls with Parentheses
+
+Function calls support parenthesized arguments for complex expressions:
+
+```
+/* Simple function call */
+result1 : add 3 4;
+
+/* Function call with parenthesized arithmetic */
+result2 : add (3 + 2) (4 + 1);
+
+/* Function call with function calls in parentheses */
+result3 : add (double 3) (square 2);
+
+/* Complex nested function calls with parentheses */
+result4 : add (add 1 2) (add 3 4);
+```
+
+**Note:** Parentheses provide explicit grouping and are the recommended way to handle complex nested function calls.
+
+### Tables
+
+The language supports Lua-style immutable tables that can serve as both arrays and objects:
+
+#### Table Creation
+
+Tables are created using curly braces `{}`:
+
+```
+/* Empty table */
+empty : {};
+
+/* Array-like table (1-indexed) */
+numbers : {1, 2, 3, 4, 5};
+
+/* Key-value table */
+person : {name: "Alice", age: 30, active: true};
+
+/* Mixed table (array-like and key-value) */
+mixed : {1, name: "Bob", 2, active: false};
+```
+
+#### Table Access
+
+Tables support both bracket notation and dot notation for accessing values:
+
+```
+/* Array access with bracket notation */
+first : numbers[1];      /* Returns 1 */
+second : numbers[2];     /* Returns 2 */
+
+/* Object access with dot notation */
+name : person.name;      /* Returns "Alice" */
+age : person.age;        /* Returns 30 */
+
+/* Object access with bracket notation */
+name_bracket : person["name"];  /* Returns "Alice" */
+age_bracket : person["age"];    /* Returns 30 */
+
+/* Mixed table access */
+first_mixed : mixed[1];         /* Returns 1 */
+name_mixed : mixed.name;        /* Returns "Bob" */
+second_mixed : mixed[2];        /* Returns 2 */
+```
+
+#### Table Features
+
+- **Immutable**: Tables cannot be modified after creation
+- **1-indexed arrays**: Array-like tables start indexing at 1
+- **Mixed types**: Tables can contain numbers, strings, booleans, and functions
+- **Nested access**: Tables can contain other tables for complex data structures
+- **Unified syntax**: Same syntax for arrays and objects
+
+#### Tables with Case Expressions
+
+Tables work well with case expressions for pattern matching:
+
+```
+/* Case expressions with table values */
+person : {name: "Alice", age: 30, active: true};
+result : case person.age of
+    0 : 30 : "Person is 30"
+    1 : _ : "Person is different age"
+;
+
+/* Case expressions with boolean values */
+status : case person.active of
+    0 : true : "Person is active"
+    1 : false : "Person is inactive"
+;
+
+/* Case expressions with array-like access */
+numbers : {1: 10, 2: 20, 3: 30};
+element : case numbers[2] of
+    0 : 20 : "Second element is 20"
+    1 : _ : "Unexpected second element"
+;
+```
+
+**Current Limitations:**
+- Function calls from tables (e.g., `table.func args`) are not supported
+- Function definitions inside table literals are not supported
+- Tables can store function references, but they cannot be called directly
+
+### First-Class Functions
+
+Functions can be passed as arguments to other functions using the `@` prefix:
+
+```
+double : x -> x * 2;
+square : x -> x * x;
+compose : f g x -> f g x;
+
+composed : compose @double @square 3;  /* double(square(3)) = 18 */
+```
+
+**Function Reference Syntax:**
+- `@functionName` - Creates a function reference (doesn't execute the function)
+- `functionName args` - Calls the function with arguments
+
+### Pattern Matching
+
+The language supports pattern matching with case expressions in function bodies:
+
+```
+compare : x y -> 
+  case x y of
+    0 0 : "both zero"
+    0 _ : "x is zero"
+    _ 0 : "y is zero"
+    _ _ : "neither zero";
+```
+
+#### Pattern Matching Syntax
+
+- **Exact matches**: `0 0` matches when both values are 0
+- **Wildcards**: `_` matches any value
+- **Mixed patterns**: `0 _` matches when first value is 0 and second can be anything
+
+### Comparison and Logical Operations
+
+The language supports comparison and logical operations:
+
+```
+/* Comparison operators */
+less : 3 < 5;           /* true */
+greater : 10 > 5;       /* true */
+equal : 5 = 5;          /* true */
+not_equal : 3 != 5;     /* true */
+less_equal : 5 <= 5;    /* true */
+greater_equal : 5 >= 3; /* true */
+
+/* Logical operators */
+and_result : 1 and 1;   /* true */
+or_result : 0 or 1;     /* true */
+xor_result : 1 xor 0;   /* true */
+not_result : not 0;     /* true */
+```
+
+### Comments
+
+The language supports C-style block comments:
+
+```
+/* This is a single line comment */
+
+/* This is a multi-line comment
+   that spans multiple lines */
+
+x : 5; /* Comment on same line as code */
+
+/* Nested comments are supported */
+/* Outer comment /* Inner comment */ More outer comment */
+```
+
+**Comment Features:**
+- Block comments start with `/*` and end with `*/`
+- Comments can span multiple lines
+- Comments can appear on the same line as code
+- Nested comments are supported
+- Comments are completely ignored by the lexer and parser
+
+### Input/Output Operations
+
+The language provides built-in IO operations for reading from stdin and writing to stdout:
+
+```
+name : ..in;        /* Read input from stdin */
+..out name;         /* Output to stdout */
+..out "Hello";      /* Output string literal */
+..out 42;           /* Output number */
+..assert x = 5;     /* Assert that x equals 5 */
+..assert y > 3;     /* Assert that y is greater than 3 */
+..assert z != 0;    /* Assert that z is not equal to 0 */
+```
+
+**IO Functions:**
+- `..in` - Reads a line from stdin, returns a number if possible, otherwise a string
+- `..out` - Outputs a value to stdout and returns the value
+- `..assert` - Asserts that a comparison is true, throws an error if it's false. Supports all comparison operators: `=`, `<`, `>`, `<=`, `>=`, `!=`
+
+**Note:** The `..` prefix indicates these are impure operations that have side effects.
+
+## Standard Library
+
+The language includes a built-in standard library with functional programming utilities:
+
+### Higher-Order Functions
+
+- **map**: Apply a function to a value (`map f x = f x`)
+- **compose**: Compose two functions (`compose f g x = f(g(x))`)
+- **curry**: Explicit currying (`curry f x y = f x y`)
+- **apply**: Apply a function to an argument (`apply f x = f x`)
+- **pipe**: Compose functions left-to-right (`pipe f g x = g(f(x))`)
+- **filter**: Filter based on a predicate (`filter p x = p(x) ? x : 0`)
+- **reduce**: Reduce to a single value (`reduce f init x = f(init, x)`)
+- **fold**: Same as reduce (`fold f init x = f(init, x)`)
+
+### Usage Examples
+
+```
+double : x -> x * 2;
+square : x -> x * x;
+
+/* Using map */
+result1 : map @double 5;    /* Returns 10 */
+result2 : map @square 3;    /* Returns 9 */
+
+/* Using compose */
+composed : compose @double @square 3;  /* double(square(3)) = 18 */
+
+/* Using pipe */
+piped : pipe @double @square 2;        /* square(double(2)) = 16 */
+
+/* Using filter */
+isPositive : x -> x > 0;
+filtered : filter @isPositive 5;       /* Returns 5 (since 5 > 0) */
+```
+
+
+## Language Components
+
+### Lexer
+
+The lexer tokenizes input into meaningful units:
+- Numbers: `123`
+- Identifiers: `variable_name`
+- Operators: `+`, `-`, `*`, `/`
+- Keywords: `case`, `of`, `function`
+- Symbols: `:`, `->`, `_`
+
+### Parser
+
+The parser builds an Abstract Syntax Tree (AST) from tokens, supporting:
+- Number literals
+- Arithmetic expressions
+- Variable assignments
+- Function declarations
+- Function calls
+- Case expressions
+- Pattern matching
+
+### Interpreter
+
+The interpreter executes the AST with:
+- Global scope management
+- Function evaluation
+- Pattern matching execution
+- Arithmetic computation
+- Immutable variable semantics
+
+## Example Programs
+
+### Basic Arithmetic and Variables
+
+```
+/* Simple arithmetic with variables */
+x : 5;
+y : 10;
+sum : x + y;
+diff : x - y;
+product : x * y;
+quotient : y / x;
+modulo : 17 % 5;        /* Returns 2 */
+power : 2 ^ 3;          /* Returns 8 */
+
+..out sum;      /* 15 */
+..out diff;     /* -5 */
+..out product;  /* 50 */
+..out quotient; /* 2 */
+..out modulo;   /* 2 */
+..out power;    /* 8 */
+
+/* Grouped expressions with parentheses */
+grouped : (5 + 3) * 2;           /* 16 */
+nested : ((5 + 3) * 2) + 1;      /* 17 */
+complex : (2 ^ 3) % 3 and 5 > 3; /* true */
+```
+
+### Function Definitions and Calls
+
+```
+/* Basic function definition */
+add : x y -> x + y;
+multiply : x y -> x * y;
+double : x -> x * 2;
+square : x -> x * x;
+
+/* Function calls */
+result1 : add 3 4;        /* 7 */
+result2 : multiply 5 6;   /* 30 */
+result3 : double 8;       /* 16 */
+result4 : square 4;       /* 16 */
+
+/* Function calls with parenthesized arguments */
+result5 : add (3 + 2) (4 + 1);    /* 15 */
+result6 : add (double 3) (square 2); /* 10 */
+result7 : add (add 1 2) (add 3 4);   /* 10 */
+```
+
+### Tables and Data Structures
+
+```
+/* Create various table types */
+empty : {};
+numbers : {1, 2, 3, 4, 5};
+person : {name: "Alice", age: 30, active: true};
+mixed : {1, name: "Bob", 2, active: false};
+
+/* Access array elements */
+first : numbers[1];
+second : numbers[2];
+last : numbers[5];
+
+/* Access object properties */
+name : person.name;
+age : person.age;
+active : person.active;
+
+/* Access mixed table */
+first_mixed : mixed[1];
+name_mixed : mixed.name;
+second_mixed : mixed[2];
+
+/* Output results */
+..out "First number: ";
+..out first;
+..out "Person name: ";
+..out name;
+..out "Mixed first: ";
+..out first_mixed;
+..out "Mixed name: ";
+..out name_mixed;
+```
+
+### Pattern Matching with Case Expressions
+
+```
+/* Pattern matching in function bodies */
+compare : x y -> 
+  case x y of
+    0 0 : "both zero"
+    0 _ : "x is zero"
+    _ 0 : "y is zero"
+    _ _ : "neither zero";
+
+/* Testing pattern matching */
+test1 : compare 0 0;   /* "both zero" */
+test2 : compare 0 5;   /* "x is zero" */
+test3 : compare 5 0;   /* "y is zero" */
+test4 : compare 5 5;   /* "neither zero" */
+
+/* Single-parameter case expressions */
+factorial : n -> 
+  case n of
+    0 : 1
+    _ : n * (factorial (n - 1));
+
+/* Single-parameter grade function */
+grade : score -> 
+  case score of
+    90 : "A"
+    80 : "B"
+    70 : "C"
+    _  : "F";
+
+fact5 : factorial 5;    /* 120 */
+grade1 : grade 95;      /* "A" */
+grade2 : grade 85;      /* "B" */
+grade3 : grade 65;      /* "F" */
+```
+
+### First-Class Functions and Composition
+
+```
+/* Function references and composition */
+double : x -> x * 2;
+square : x -> x * x;
+add1 : x -> x + 1;
+
+/* Using standard library functions */
+composed : compose @double @square 3;  /* double(square(3)) = 18 */
+piped : pipe @double @square 2;        /* square(double(2)) = 16 */
+applied : apply @double 5;             /* double(5) = 10 */
+
+/* Higher-order functions */
+mapped1 : map @double 5;               /* 10 */
+mapped2 : map @square 3;               /* 9 */
+
+/* Function references in case expressions */
+getFunction : type -> 
+  case type of
+    "double" : @double
+    "square" : @square
+    _        : @add1;
+
+func1 : getFunction "double";  /* Function reference */
+func2 : getFunction "square";  /* Function reference */
+```
+
+### Recursion and Complex Functions
+
+```
+/* Recursive factorial function (using single-parameter case) */
+factorial : n -> 
+  case n of
+    0 : 1
+    _ : n * (factorial (n - 1));
+
+/* Recursive countdown function */
+countdown : n -> 
+  case n of
+    0 : "done"
+    _ : countdown (n - 1);
+
+/* Testing recursive functions */
+fact5 : factorial 5;    /* 120 */
+count : countdown 3;    /* "done" */
+```
+
+### Comparison and Logical Operators
+
+```
+/* Comparison operators */
+a : 5;
+b : 10;
+
+less : a < b;           /* true */
+greater : b > a;        /* true */
+equal : a = a;          /* true */
+notEqual : a != b;      /* true */
+lessEqual : a <= 5;     /* true */
+greaterEqual : b >= 10; /* true */
+
+/* Logical operators */
+logicalAnd : 1 and 1;   /* true */
+logicalOr : 0 or 1;     /* true */
+logicalXor : 1 xor 0;   /* true */
+logicalNot : not 0;     /* true */
+
+/* Complex logical expressions */
+complex1 : a < b and b > 5;           /* true */
+complex2 : a = 5 or b = 15;           /* true */
+complex3 : not (a = b);               /* true */
+```
+
+### Input/Output and Assertions
+
+```
+/* Interactive input/output */
+..out "Enter your name: ";
+name : ..in;
+..out "Hello, ";
+..out name;
+..out "!";
+
+/* Assertions for testing */
+x : 5;
+y : 3;
+sum : x + y;
+
+..assert x = 5;            /* Passes */
+..assert y = 3;            /* Passes */
+..assert sum = 8;          /* Passes */
+..assert x > 3;            /* Passes */
+..assert y < 10;           /* Passes */
+..assert sum != 0;         /* Passes */
+..assert (add 3 4) = 7;    /* Passes (with parentheses) */
+
+/* String comparisons */
+..assert "hello" = "hello";  /* Passes */
+..assert "world" != "hello"; /* Passes */
+```
+
+### Standard Library Functions
+
+```
+/* Using all standard library functions */
+double : x -> x * 2;
+square : x -> x * x;
+add : x y -> x + y;
+isPositive : x -> x > 0;
+
+/* Map function */
+mapped1 : map @double 5;    /* 10 */
+mapped2 : map @square 3;    /* 9 */
+
+/* Compose function */
+composed : compose @double @square 3;  /* 18 */
+
+/* Pipe function */
+piped : pipe @double @square 2;        /* 16 */
+
+/* Apply function */
+applied : apply @double 7;             /* 14 */
+
+/* Filter function */
+filtered : filter @isPositive 5;       /* 5 (since 5 > 0) */
+filtered2 : filter @isPositive -3;     /* 0 (since -3 <= 0) */
+
+/* Reduce and Fold functions */
+reduced : reduce @add 0 5;             /* 5 */
+folded : fold @add 0 5;                /* 5 */
+
+/* Curry function (explicit currying) */
+curried : curry @add 3 4;              /* 7 */
+```
+
+### Complete Program Example
+
+```
+/* A complete program demonstrating multiple features */
+..out "Welcome to the Calculator!";
+
+/* Define arithmetic functions */
+add : x y -> x + y;
+subtract : x y -> x - y;
+multiply : x y -> x * y;
+divide : x y -> x / y;
+
+/* Define utility functions */
+double : x -> x * 2;
+square : x -> x * x;
+isEven : x -> x % 2 = 0;
+
+/* Get user input */
+..out "Enter first number: ";
+num1 : ..in;
+..out "Enter second number: ";
+num2 : ..in;
+
+/* Perform calculations */
+sum : add num1 num2;
+diff : subtract num1 num2;
+prod : multiply num1 num2;
+quot : divide num1 num2;
+
+/* Display results */
+..out "Sum: ";
+..out sum;
+..out "Difference: ";
+..out diff;
+..out "Product: ";
+..out prod;
+..out "Quotient: ";
+..out quot;
+
+/* Use higher-order functions */
+doubledSum : double sum;
+squaredDiff : square diff;
+
+..out "Doubled sum: ";
+..out doubledSum;
+..out "Squared difference: ";
+..out squaredDiff;
+
+/* Pattern matching for number classification */
+classify : num -> 
+  case num of
+    0 : "zero"
+    _ : case isEven num of
+         true : "even"
+         _    : "odd";
+
+classification : classify num1;
+..out "First number is: ";
+..out classification;
+
+/* Assertions to verify calculations */
+..assert sum = add num1 num2;
+..assert diff = subtract num1 num2;
+..assert prod = multiply num1 num2;
+
+..out "All calculations verified!";
+```
+
+## Running Programs
+
+The language supports file execution mode only:
+
+### File Execution Mode
+Run a script file by providing the file path as an argument:
+
+```bash
+node lang.js script.txt
+```
+
+**Note:** REPL mode has been removed in the simplified version. The language now only supports file execution.
+
+## Language Design Principles
+
+- **Simplicity**: Minimal syntax for maximum clarity
+- **Immutability**: Variables cannot be reassigned
+- **Functional**: Functions are first-class values with composition support
+- **Pattern-oriented**: Pattern matching as a core feature
+- **Recursive**: Support for recursive function calls in function bodies
+- **Grouped**: Parentheses support for complex arithmetic expressions
+- **Expressive**: Rich set of arithmetic, comparison, and logical operators
+- **IO-aware**: Built-in input/output operations with clear impurity markers
+- **Educational**: Designed to teach language implementation concepts
+
+## Limitations
+
+This is a learning language with intentional limitations:
+- Complex nested function calls (e.g., `add double 3 square 2`) are ambiguous without explicit grouping
+- There's one known issue with function calls inside assertions (e.g., ..assert add 3 4 = 7), which is a parsing edge case. Workaround: use parentheses around function calls in assertions (e.g., ..assert (add 3 4) = 7)
+- Limited to immutable data structures (no mutation operations)
+- No error handling beyond basic validation
+- No modules or imports
+- IO operations are synchronous and block execution
+
+## Future Enhancements
+
+Planned features for future versions:
+- Ambiguous function call detection and error reporting
+- Lists and data structures
+- Error handling
+- Modules and imports
+- Performance optimizations
+
+## Implementation Details
+
+The language is implemented in JavaScript with three main components:
+
+1. **Lexer** (`lexer()`): Converts source code to tokens
+2. **Parser** (`parser()`): Builds AST from tokens
+3. **Interpreter** (`interpreter()`): Executes AST
+
+Each component is designed to be modular and extensible for adding new language features.
+
+## Files
+
+- `lang.js` - Main language implementation
+- `table_basic_test.txt` - Basic table functionality tests
+- `table_edge_cases_test.txt` - Advanced table edge cases (some features may not work)
+- `README.md` - This documentation
+- `NEXT-STEPS.md` - Development roadmap and notes 
\ No newline at end of file
diff --git a/js/scripting-lang/input.txt b/js/scripting-lang/input.txt
deleted file mode 100644
index d343086..0000000
--- a/js/scripting-lang/input.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-x : 10;
-y : 20;
-x + y;
\ No newline at end of file
diff --git a/js/scripting-lang/lang.js b/js/scripting-lang/lang.js
index f91f842..3673e6e 100644
--- a/js/scripting-lang/lang.js
+++ b/js/scripting-lang/lang.js
@@ -1,320 +1,1637 @@
 // The goal here is less to make anything useful...or even something that works, but to learn what parts an interpreted languages needs to have to function.
 
+// Initialize standard library functions
+function initializeStandardLibrary(scope) {
+    // Map: Apply a function to each element
+    scope.map = function(f, x) { 
+        // Handle function references by calling them if they're functions
+        if (typeof f === 'function') {
+            return f(x);
+        } else {
+            throw new Error('map: first argument must be a function');
+        }
+    };
+    
+    // Compose: Compose two functions (f ∘ g)(x) = f(g(x))
+    scope.compose = function(f, g, x) { 
+        if (typeof f === 'function' && typeof g === 'function') {
+            return f(g(x));
+        } else {
+            throw new Error('compose: first two arguments must be functions');
+        }
+    };
+    
+    // Curry: Convert a function that takes multiple arguments into a series of functions
+    // Since our language already uses curried functions by default, this is mostly for explicit currying
+    scope.curry = function(f, x, y) { 
+        if (typeof f === 'function') {
+            return f(x, y);
+        } else {
+            throw new Error('curry: first argument must be a function');
+        }
+    };
+    
+    // Apply: Apply a function to an argument (same as function call, but more explicit)
+    scope.apply = function(f, x) { 
+        if (typeof f === 'function') {
+            return f(x);
+        } else {
+            throw new Error('apply: first argument must be a function');
+        }
+    };
+    
+    // Pipe: Compose functions in left-to-right order (opposite of compose)
+    // pipe f g x = g f x
+    scope.pipe = function(f, g, x) { 
+        if (typeof f === 'function' && typeof g === 'function') {
+            return g(f(x));
+        } else {
+            throw new Error('pipe: first two arguments must be functions');
+        }
+    };
+    
+    // Filter: Filter based on a predicate
+    // For now, we'll implement it as a higher-order function
+    scope.filter = function(p, x) { 
+        if (typeof p === 'function') {
+            return p(x) ? x : 0;
+        } else {
+            throw new Error('filter: first argument must be a function');
+        }
+    };
+    
+    // Reduce: Reduce to a single value using a binary function
+    // For now, we'll implement it as a higher-order function
+    scope.reduce = function(f, init, x) { 
+        if (typeof f === 'function') {
+            return f(init, x);
+        } else {
+            throw new Error('reduce: first argument must be a function');
+        }
+    };
+    
+    // Fold: Same as reduce, but more explicit about the folding direction
+    scope.fold = function(f, init, x) { 
+        if (typeof f === 'function') {
+            return f(init, x);
+        } else {
+            throw new Error('fold: first argument must be a function');
+        }
+    };
+}
+
 // Define the types of tokens
 const TokenType = {
     NUMBER: 'NUMBER',
     PLUS: 'PLUS',
+    MINUS: 'MINUS',
+    MULTIPLY: 'MULTIPLY',
+    DIVIDE: 'DIVIDE',
     IDENTIFIER: 'IDENTIFIER',
     ASSIGNMENT: 'ASSIGNMENT',
+    ARROW: 'ARROW',
+    CASE: 'CASE',
+    OF: 'OF',
+    WILDCARD: 'WILDCARD',
     FUNCTION: 'FUNCTION',
     LEFT_PAREN: 'LEFT_PAREN',
     RIGHT_PAREN: 'RIGHT_PAREN',
     LEFT_BRACE: 'LEFT_BRACE',
     RIGHT_BRACE: 'RIGHT_BRACE',
+    LEFT_BRACKET: 'LEFT_BRACKET',
+    RIGHT_BRACKET: 'RIGHT_BRACKET',
     SEMICOLON: 'SEMICOLON',
+    COMMA: 'COMMA',
+    DOT: 'DOT',
+    STRING: 'STRING',
+    TRUE: 'TRUE',
+    FALSE: 'FALSE',
+    AND: 'AND',
+    OR: 'OR',
+    XOR: 'XOR',
+    NOT: 'NOT',
+    EQUALS: 'EQUALS',
+    LESS_THAN: 'LESS_THAN',
+    GREATER_THAN: 'GREATER_THAN',
+    LESS_EQUAL: 'LESS_EQUAL',
+    GREATER_EQUAL: 'GREATER_EQUAL',
+    NOT_EQUAL: 'NOT_EQUAL',
+    MODULO: 'MODULO',
+    POWER: 'POWER',
+    IO_IN: 'IO_IN',
+    IO_OUT: 'IO_OUT',
+    IO_ASSERT: 'IO_ASSERT',
+    FUNCTION_REF: 'FUNCTION_REF'
 };
 
-// Lexer
+// Lexer - converts source code to tokens
 function lexer(input) {
-    const tokens = [];
     let current = 0;
-
+    const tokens = [];
+    
     while (current < input.length) {
         let char = input[current];
-
-        if (/\d/.test(char)) {
-            let value = '';
-            while (/\d/.test(char)) {
-                value += char;
-                char = input[++current];
-            }
-            tokens.push({
-                type: TokenType.NUMBER,
-                value
-            });
-            continue;
-        }
-
-        if (char === '+') {
-            tokens.push({
-                type: TokenType.PLUS
-            });
+        
+        // Skip whitespace
+        if (/\s/.test(char)) {
             current++;
             continue;
         }
-
-        if (/[a-z]/i.test(char)) {
-            let value = '';
-            while (/[a-z]/i.test(char)) {
-                value += char;
-                char = input[++current];
+        
+        // Skip comments
+        if (char === '/' && input[current + 1] === '*') {
+            let commentDepth = 1;
+            current += 2; // Skip /*
+            
+            while (current < input.length && commentDepth > 0) {
+                if (input[current] === '/' && input[current + 1] === '*') {
+                    commentDepth++;
+                    current += 2;
+                } else if (input[current] === '*' && input[current + 1] === '/') {
+                    commentDepth--;
+                    current += 2;
+                } else {
+                    current++;
+                }
             }
-            tokens.push({
-                type: TokenType.IDENTIFIER,
-                value
-            });
-            continue;
-        }
-
-        if (char === ':') {
-            tokens.push({
-                type: TokenType.ASSIGNMENT
-            });
-            current++;
-            continue;
-        }
-
-        if (char === '=') {
-            tokens.push({
-                type: TokenType.EQUAL
-            });
-            current++;
-            continue;
-        }
-
-        if (input.slice(current, current + 2) === 'if') {
-            tokens.push({
-                type: TokenType.IF
-            });
-            current += 2;
             continue;
         }
-
-        if (input.slice(current, current + 4) === 'else') {
-            tokens.push({
-                type: TokenType.ELSE
-            });
-            current += 4;
-            continue;
-        }
-
-        if (char === '(') {
-            tokens.push({
-                type: TokenType.LEFT_PAREN
-            });
-            current++;
-            continue;
-        }
-
-        if (char === ')') {
+        
+        // Numbers
+        if (/[0-9]/.test(char)) {
+            let value = '';
+            while (current < input.length && /[0-9]/.test(input[current])) {
+                value += input[current];
+                current++;
+            }
             tokens.push({
-                type: TokenType.RIGHT_PAREN
+                type: TokenType.NUMBER,
+                value: parseInt(value)
             });
-            current++;
             continue;
         }
-
-        if (char === '{') {
-            tokens.push({
-                type: TokenType.LEFT_BRACE
-            });
-            current++;
+        
+        // Strings
+        if (char === '"') {
+            let value = '';
+            current++; // Skip opening quote
+            
+            while (current < input.length && input[current] !== '"') {
+                value += input[current];
+                current++;
+            }
+            
+            if (current < input.length) {
+                current++; // Skip closing quote
+                tokens.push({
+                    type: TokenType.STRING,
+                    value: value
+                });
+            } else {
+                throw new Error('Unterminated string');
+            }
             continue;
         }
-
-        if (char === '}') {
-            tokens.push({
-                type: TokenType.RIGHT_BRACE
-            });
-            current++;
+        
+        // Identifiers and keywords
+        if (/[a-zA-Z_]/.test(char)) {
+            let value = '';
+            while (current < input.length && /[a-zA-Z0-9_]/.test(input[current])) {
+                value += input[current];
+                current++;
+            }
+            
+            // Check for keywords
+            switch (value) {
+                case 'case':
+                    tokens.push({ type: TokenType.CASE });
+                    break;
+                case 'of':
+                    tokens.push({ type: TokenType.OF });
+                    break;
+                case 'function':
+                    tokens.push({ type: TokenType.FUNCTION });
+                    break;
+                case 'true':
+                    tokens.push({ type: TokenType.TRUE });
+                    break;
+                case 'false':
+                    tokens.push({ type: TokenType.FALSE });
+                    break;
+                case 'and':
+                    tokens.push({ type: TokenType.AND });
+                    break;
+                case 'or':
+                    tokens.push({ type: TokenType.OR });
+                    break;
+                case 'xor':
+                    tokens.push({ type: TokenType.XOR });
+                    break;
+                case 'not':
+                    tokens.push({ type: TokenType.NOT });
+                    break;
+                default:
+                    tokens.push({
+                        type: TokenType.IDENTIFIER,
+                        value: value
+                    });
+            }
             continue;
         }
-
-        if (input.slice(current, current + 8) === 'function') {
-            tokens.push({
-                type: TokenType.FUNCTION
-            });
-            current += 8;
-            continue;
+        
+        // Two-character operators
+        if (current + 1 < input.length) {
+            const twoChar = char + input[current + 1];
+            switch (twoChar) {
+                case '->':
+                    tokens.push({ type: TokenType.ARROW });
+                    current += 2;
+                    continue;
+                case '==':
+                    tokens.push({ type: TokenType.EQUALS });
+                    current += 2;
+                    continue;
+                case '!=':
+                    tokens.push({ type: TokenType.NOT_EQUAL });
+                    current += 2;
+                    continue;
+                case '<=':
+                    tokens.push({ type: TokenType.LESS_EQUAL });
+                    current += 2;
+                    continue;
+                case '>=':
+                    tokens.push({ type: TokenType.GREATER_EQUAL });
+                    current += 2;
+                    continue;
+                case '..':
+                    // Check for IO operations
+                    if (current + 2 < input.length) {
+                        const ioChar = input[current + 2];
+                        switch (ioChar) {
+                            case 'i':
+                                if (current + 3 < input.length && input[current + 3] === 'n') {
+                                    tokens.push({ type: TokenType.IO_IN });
+                                    current += 4;
+                                    continue;
+                                }
+                                break;
+                            case 'o':
+                                if (current + 3 < input.length && input[current + 3] === 'u') {
+                                    if (current + 4 < input.length && input[current + 4] === 't') {
+                                        tokens.push({ type: TokenType.IO_OUT });
+                                        current += 5;
+                                        continue;
+                                    }
+                                }
+                                break;
+                            case 'a':
+                                if (current + 3 < input.length && input[current + 3] === 's') {
+                                    if (current + 4 < input.length && input[current + 4] === 's') {
+                                        if (current + 5 < input.length && input[current + 5] === 'e') {
+                                            if (current + 6 < input.length && input[current + 6] === 'r') {
+                                                if (current + 7 < input.length && input[current + 7] === 't') {
+                                                    tokens.push({ type: TokenType.IO_ASSERT });
+                                                    current += 8;
+                                                    continue;
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                                break;
+                        }
+                    }
+                    // If we get here, it's not a complete IO operation, so skip the '..'
+                    current += 2;
+                    continue;
+            }
         }
-
-        if (char === ';') {
-            tokens.push({ type: TokenType.SEMICOLON });
-            current++;
-            continue;
+        
+        // Single character operators
+        switch (char) {
+            case '+':
+                tokens.push({ type: TokenType.PLUS });
+                break;
+            case '-':
+                tokens.push({ type: TokenType.MINUS });
+                break;
+            case '*':
+                tokens.push({ type: TokenType.MULTIPLY });
+                break;
+            case '/':
+                tokens.push({ type: TokenType.DIVIDE });
+                break;
+            case '%':
+                tokens.push({ type: TokenType.MODULO });
+                break;
+            case '^':
+                tokens.push({ type: TokenType.POWER });
+                break;
+            case ':':
+                tokens.push({ type: TokenType.ASSIGNMENT });
+                break;
+            case '(':
+                tokens.push({ type: TokenType.LEFT_PAREN });
+                break;
+            case ')':
+                tokens.push({ type: TokenType.RIGHT_PAREN });
+                break;
+            case '{':
+                tokens.push({ type: TokenType.LEFT_BRACE });
+                break;
+            case '}':
+                tokens.push({ type: TokenType.RIGHT_BRACE });
+                break;
+            case '[':
+                tokens.push({ type: TokenType.LEFT_BRACKET });
+                break;
+            case ']':
+                tokens.push({ type: TokenType.RIGHT_BRACKET });
+                break;
+            case ';':
+                tokens.push({ type: TokenType.SEMICOLON });
+                break;
+            case ',':
+                tokens.push({ type: TokenType.COMMA });
+                break;
+            case '.':
+                tokens.push({ type: TokenType.DOT });
+                break;
+            case '@':
+                tokens.push({ type: TokenType.FUNCTION_REF });
+                break;
+            case '_':
+                tokens.push({ type: TokenType.WILDCARD });
+                break;
+            case '=':
+                tokens.push({ type: TokenType.EQUALS });
+                break;
+            case '<':
+                tokens.push({ type: TokenType.LESS_THAN });
+                break;
+            case '>':
+                tokens.push({ type: TokenType.GREATER_THAN });
+                break;
+            default:
+                throw new Error(`Unexpected character: ${char}`);
         }
-
+        
         current++;
     }
-
+    
     return tokens;
 }
 
-// Parser
+// Parser - converts tokens to AST
 function parser(tokens) {
     let current = 0;
-
+    
     function walk() {
-        if (current >= tokens.length) {
-            return null; // Return null when there are no more tokens
-        }
-
-        let token = tokens[current];
-
-        if (token.type === TokenType.NUMBER) {
-            current++;
-            return {
-                type: 'NumberLiteral',
-                value: token.value,
-            };
+        function parseChainedDotAccess(tableExpr) {
+            let result = tableExpr;
+            
+            while (current < tokens.length && tokens[current].type === TokenType.DOT) {
+                current++; // Skip the dot
+                
+                if (current < tokens.length && tokens[current].type === TokenType.IDENTIFIER) {
+                    const key = {
+                        type: 'Identifier',
+                        value: tokens[current].value
+                    };
+                    current++;
+                    
+                    result = {
+                        type: 'TableAccess',
+                        table: result,
+                        key: key
+                    };
+                } else {
+                    throw new Error('Expected identifier after dot');
+                }
+            }
+            
+            return result;
         }
-
-        if (token.type === TokenType.PLUS) {
-            current++;
-            return {
-                type: 'PlusExpression',
-                left: walk(),
-                right: walk(),
-            };
+        
+        function parseChainedTableAccess(tableExpr) {
+            if (current < tokens.length && tokens[current].type === TokenType.LEFT_BRACKET) {
+                current++; // Skip '['
+                const keyExpr = walk();
+                
+                if (current < tokens.length && tokens[current].type === TokenType.RIGHT_BRACKET) {
+                    current++; // Skip ']'
+                    
+                    const access = {
+                        type: 'TableAccess',
+                        table: tableExpr,
+                        key: keyExpr
+                    };
+                    
+                    // Check for chained access
+                    if (current < tokens.length && tokens[current].type === TokenType.DOT) {
+                        return parseChainedDotAccess(access);
+                    }
+                    
+                    // Check if this is a function call
+                    if (current < tokens.length && 
+                        (tokens[current].type === TokenType.IDENTIFIER || 
+                         tokens[current].type === TokenType.NUMBER ||
+                         tokens[current].type === TokenType.STRING ||
+                         tokens[current].type === TokenType.LEFT_PAREN)) {
+                        return parseFunctionCall(access);
+                    }
+                    
+                    return access;
+                } else {
+                    throw new Error('Expected closing bracket');
+                }
+            }
+            
+            // Check for dot access
+            if (current < tokens.length && tokens[current].type === TokenType.DOT) {
+                const result = parseChainedDotAccess(tableExpr);
+                
+                // Check if this is a function call
+                if (current < tokens.length && 
+                    (tokens[current].type === TokenType.IDENTIFIER || 
+                     tokens[current].type === TokenType.NUMBER ||
+                     tokens[current].type === TokenType.STRING ||
+                     tokens[current].type === TokenType.LEFT_PAREN)) {
+                    return parseFunctionCall(result);
+                }
+                
+                return result;
+            }
+            
+            return tableExpr;
         }
-
-        if (token.type === TokenType.IDENTIFIER) {
-            current++;
-            return {
-                type: 'Identifier',
-                value: token.value,
-            };
+        
+        function detectAmbiguousFunctionCalls() {
+            // This is a placeholder for future ambiguous function call detection
+            // For now, we'll assume the parser handles function calls correctly
         }
-
-        if (token.type === TokenType.ASSIGNMENT) {
-            current++;
+        
+        function parseFunctionCall(functionName) {
+            const args = [];
+            
+            // Parse arguments until we hit a semicolon or other terminator
+            while (current < tokens.length && 
+                   tokens[current].type !== TokenType.SEMICOLON &&
+                   tokens[current].type !== TokenType.RIGHT_PAREN &&
+                   tokens[current].type !== TokenType.RIGHT_BRACE &&
+                   tokens[current].type !== TokenType.COMMA) {
+                args.push(parseExpression());
+            }
+            
             return {
-                type: 'AssignmentExpression',
-                name: tokens[current - 2].value,
-                value: walk(),
-            };
-        }
-
-        if (token.type === TokenType.IF) {
-            current++;
-            let node = {
-                type: 'IfExpression',
-                test: walk(),
-                consequent: walk(),
-                alternate: tokens[current].type === TokenType.ELSE ? (current++, walk()) : null,
+                type: 'FunctionCall',
+                name: functionName,
+                args: args
             };
-            return node;
         }
-
-        if (token.type === TokenType.FUNCTION) {
-            current++;
-            let node = {
-                type: 'FunctionDeclaration',
-                name: tokens[current++].value,
-                params: [],
-                body: [],
-            };
-            while (tokens[current].type !== TokenType.RIGHT_PAREN) {
-                node.params.push(tokens[current++].value);
+        
+        function parseExpression() {
+            let left = parseTerm();
+            
+            while (current < tokens.length && 
+                   (tokens[current].type === TokenType.PLUS || 
+                    tokens[current].type === TokenType.MINUS ||
+                    tokens[current].type === TokenType.EQUALS ||
+                    tokens[current].type === TokenType.NOT_EQUAL ||
+                    tokens[current].type === TokenType.LESS_THAN ||
+                    tokens[current].type === TokenType.GREATER_THAN ||
+                    tokens[current].type === TokenType.LESS_EQUAL ||
+                    tokens[current].type === TokenType.GREATER_EQUAL ||
+                    tokens[current].type === TokenType.AND ||
+                    tokens[current].type === TokenType.OR ||
+                    tokens[current].type === TokenType.XOR)) {
+                
+                const operator = tokens[current].type;
+                current++;
+                const right = parseTerm();
+                
+                switch (operator) {
+                    case TokenType.PLUS:
+                        left = { type: 'PlusExpression', left, right };
+                        break;
+                    case TokenType.MINUS:
+                        left = { type: 'MinusExpression', left, right };
+                        break;
+                    case TokenType.EQUALS:
+                        left = { type: 'EqualsExpression', left, right };
+                        break;
+                    case TokenType.NOT_EQUAL:
+                        left = { type: 'NotEqualExpression', left, right };
+                        break;
+                    case TokenType.LESS_THAN:
+                        left = { type: 'LessThanExpression', left, right };
+                        break;
+                    case TokenType.GREATER_THAN:
+                        left = { type: 'GreaterThanExpression', left, right };
+                        break;
+                    case TokenType.LESS_EQUAL:
+                        left = { type: 'LessEqualExpression', left, right };
+                        break;
+                    case TokenType.GREATER_EQUAL:
+                        left = { type: 'GreaterEqualExpression', left, right };
+                        break;
+                    case TokenType.AND:
+                        left = { type: 'AndExpression', left, right };
+                        break;
+                    case TokenType.OR:
+                        left = { type: 'OrExpression', left, right };
+                        break;
+                    case TokenType.XOR:
+                        left = { type: 'XorExpression', left, right };
+                        break;
+                }
             }
-            current++; // Skip right paren
-            while (tokens[current].type !== TokenType.RIGHT_BRACE) {
-                node.body.push(walk());
+            
+            return left;
+        }
+        
+        function parseTerm() {
+            let left = parseFactor();
+            
+            while (current < tokens.length && 
+                   (tokens[current].type === TokenType.MULTIPLY || 
+                    tokens[current].type === TokenType.DIVIDE ||
+                    tokens[current].type === TokenType.MODULO)) {
+                
+                const operator = tokens[current].type;
+                current++;
+                const right = parseFactor();
+                
+                switch (operator) {
+                    case TokenType.MULTIPLY:
+                        left = { type: 'MultiplyExpression', left, right };
+                        break;
+                    case TokenType.DIVIDE:
+                        left = { type: 'DivideExpression', left, right };
+                        break;
+                    case TokenType.MODULO:
+                        left = { type: 'ModuloExpression', left, right };
+                        break;
+                }
             }
-            current++; // Skip right brace
-            return node;
+            
+            return left;
         }
-
-        if (token.type === TokenType.IDENTIFIER && tokens[current + 1].type === TokenType.LEFT_PAREN) {
-            current++;
-            let node = {
-                type: 'FunctionCall',
-                name: token.value,
-                args: [],
-            };
-            current++; // Skip left paren
-            while (tokens[current].type !== TokenType.RIGHT_PAREN) {
-                node.args.push(walk());
+        
+        function parseFactor() {
+            let left = parsePrimary();
+            
+            while (current < tokens.length && tokens[current].type === TokenType.POWER) {
+                current++;
+                const right = parsePrimary();
+                left = { type: 'PowerExpression', left, right };
             }
-            current++; // Skip right paren
-            return node;
+            
+            return left;
         }
-
-        if (token.type === TokenType.SEMICOLON) {
-            current++;
-            return;
+        
+        function parsePrimary() {
+            const token = tokens[current];
+            
+            if (token.type === TokenType.NOT) {
+                current++;
+                const operand = parsePrimary();
+                return { type: 'NotExpression', operand };
+            }
+            
+            if (token.type === TokenType.IO_IN) {
+                current++;
+                return { type: 'IOInExpression' };
+            }
+            if (token.type === TokenType.IO_OUT) {
+                current++;
+                const outputValue = parseExpression();
+                return { type: 'IOOutExpression', value: outputValue };
+            }
+            if (token.type === TokenType.IO_ASSERT) {
+                current++;
+                const assertionExpr = parseExpression();
+                return { type: 'IOAssertExpression', value: assertionExpr };
+            }
+            
+            if (token.type === TokenType.NUMBER) {
+                current++;
+                return {
+                    type: 'NumberLiteral',
+                    value: token.value
+                };
+            }
+            
+            if (token.type === TokenType.STRING) {
+                current++;
+                return {
+                    type: 'StringLiteral',
+                    value: token.value
+                };
+            }
+            
+            if (token.type === TokenType.TRUE) {
+                current++;
+                return {
+                    type: 'BooleanLiteral',
+                    value: true
+                };
+            }
+            
+            if (token.type === TokenType.FALSE) {
+                current++;
+                return {
+                    type: 'BooleanLiteral',
+                    value: false
+                };
+            }
+            
+            if (token.type === TokenType.LEFT_PAREN) {
+                current++; // Skip '('
+                const parenthesizedExpr = parseExpression();
+                
+                if (current < tokens.length && tokens[current].type === TokenType.RIGHT_PAREN) {
+                    current++; // Skip ')'
+                    return parenthesizedExpr;
+                } else {
+                    throw new Error('Expected closing parenthesis');
+                }
+            }
+            
+            if (token.type === TokenType.IDENTIFIER) {
+                const identifier = {
+                    type: 'Identifier',
+                    value: token.value
+                };
+                current++;
+                
+                // Check if this is an assignment
+                if (current < tokens.length && tokens[current].type === TokenType.ASSIGNMENT) {
+                    current++; // Skip ':'
+                    
+                    // Check if this is a function definition
+                    let isFunction = false;
+                    let params = [];
+                    
+                    // Look ahead to see if this is a function definition
+                    let lookAhead = current;
+                    while (lookAhead < tokens.length && 
+                           tokens[lookAhead].type !== TokenType.ARROW && 
+                           tokens[lookAhead].type !== TokenType.SEMICOLON) {
+                        if (tokens[lookAhead].type === TokenType.IDENTIFIER) {
+                            params.push(tokens[lookAhead].value);
+                        }
+                        lookAhead++;
+                    }
+                    
+                    if (lookAhead < tokens.length && tokens[lookAhead].type === TokenType.ARROW) {
+                        isFunction = true;
+                    }
+                    
+                    if (isFunction) {
+                        // Clear params array and parse function parameters
+                        params = [];
+                        while (current < tokens.length && tokens[current].type !== TokenType.ARROW) {
+                            if (tokens[current].type === TokenType.IDENTIFIER) {
+                                params.push(tokens[current].value);
+                            }
+                            current++;
+                        }
+                        
+                        current++; // Skip '->'
+                        
+                        // Parse the function body (which could be a case expression or other expression)
+                        const functionBody = parseExpression();
+                        
+                        return {
+                            type: 'AssignmentExpression',
+                            name: identifier.value,
+                            value: {
+                                type: 'FunctionDeclaration',
+                                name: null, // Anonymous function
+                                params,
+                                body: functionBody,
+                            }
+                        };
+                    } else {
+                        // Regular assignment
+                        const value = parseExpression();
+                        return {
+                            type: 'AssignmentExpression',
+                            name: identifier.value,
+                            value: value
+                        };
+                    }
+                }
+                
+                // Check if this is table access
+                if (current < tokens.length && 
+                    (tokens[current].type === TokenType.LEFT_BRACKET ||
+                     tokens[current].type === TokenType.DOT)) {
+                    return parseChainedTableAccess(identifier);
+                }
+                
+                // Check if this is a function call
+                if (current < tokens.length && 
+                    (tokens[current].type === TokenType.IDENTIFIER || 
+                     tokens[current].type === TokenType.NUMBER ||
+                     tokens[current].type === TokenType.STRING ||
+                     tokens[current].type === TokenType.LEFT_PAREN)) {
+                    return parseFunctionCall(identifier);
+                }
+                
+                return identifier;
+            }
+            
+            if (token.type === TokenType.FUNCTION_REF) {
+                current++; // Skip '@'
+                if (current < tokens.length && tokens[current].type === TokenType.IDENTIFIER) {
+                    const funcName = tokens[current].value;
+                    current++;
+                    return {
+                        type: 'FunctionReference',
+                        name: funcName
+                    };
+                } else {
+                    throw new Error('Expected function name after @');
+                }
+            }
+            
+            if (token.type === TokenType.CASE) {
+                current++; // Skip 'case'
+                
+                // Parse the value being matched
+                const value = parseExpression();
+                
+                // Expect 'of'
+                if (tokens[current].type !== TokenType.OF) {
+                    throw new Error('Expected "of" after "case"');
+                }
+                current++; // Skip 'of'
+                
+                const cases = [];
+                
+                // Parse cases until we hit a semicolon or end
+                while (current < tokens.length && tokens[current].type !== TokenType.SEMICOLON) {
+                    const pattern = parseExpression();
+                    const result = parseExpression();
+                    cases.push({ 
+                        pattern: [pattern], 
+                        result: [result] 
+                    });
+                }
+                
+                return {
+                    type: 'CaseExpression',
+                    value: [value],
+                    cases,
+                };
+            }
+            
+            // If we get here, it's an operator token that should be handled by parseExpression
+            // But we need to handle it here to avoid circular dependency
+            if (token.type === TokenType.LEFT_BRACE) {
+                current++; // Skip '{'
+                const entries = [];
+                let arrayIndex = 1;
+                
+                while (current < tokens.length && tokens[current].type !== TokenType.RIGHT_BRACE) {
+                    // Skip leading commas
+                    if (tokens[current].type === TokenType.COMMA) {
+                        current++;
+                        continue;
+                    }
+                    
+                    let key = null;
+                    let value;
+                    
+                    // Check if this is a key-value pair or just a value
+                    if (current + 1 < tokens.length && tokens[current + 1].type === TokenType.ASSIGNMENT) {
+                        // This is a key-value pair: key: value
+                        if (tokens[current].type === TokenType.IDENTIFIER) {
+                            key = {
+                                type: 'Identifier',
+                                value: tokens[current].value
+                            };
+                            current++; // Skip the key
+                        } else if (tokens[current].type === TokenType.NUMBER) {
+                            key = {
+                                type: 'NumberLiteral',
+                                value: tokens[current].value,
+                            };
+                            current++; // Skip the key
+                        } else if (tokens[current].type === TokenType.STRING) {
+                            key = {
+                                type: 'StringLiteral',
+                                value: tokens[current].value,
+                            };
+                            current++; // Skip the key
+                        } else if (tokens[current].type === TokenType.TRUE) {
+                            key = {
+                                type: 'BooleanLiteral',
+                                value: true,
+                            };
+                            current++; // Skip the key
+                        } else if (tokens[current].type === TokenType.FALSE) {
+                            key = {
+                                type: 'BooleanLiteral',
+                                value: false,
+                            };
+                            current++; // Skip the key
+                        } else {
+                            throw new Error('Invalid key type in table literal');
+                        }
+                        
+                        current++; // Skip ':'
+                        value = parseExpression();
+                    } else {
+                        // This is just a value (array-like entry)
+                        value = parseExpression();
+                    }
+                    
+                    entries.push({ key, value });
+                    
+                    // Skip trailing commas
+                    if (current < tokens.length && tokens[current].type === TokenType.COMMA) {
+                        current++;
+                    }
+                }
+                
+                if (current < tokens.length && tokens[current].type === TokenType.RIGHT_BRACE) {
+                    current++; // Skip '}'
+                    return {
+                        type: 'TableLiteral',
+                        entries: entries
+                    };
+                } else {
+                    throw new Error('Expected closing brace');
+                }
+            }
+            
+            // If we get here, it's an operator token that should be handled by parseExpression
+            // But we need to handle it here to avoid circular dependency
+            if (token.type === TokenType.PLUS ||
+                token.type === TokenType.MINUS ||
+                token.type === TokenType.MULTIPLY ||
+                token.type === TokenType.DIVIDE ||
+                token.type === TokenType.MODULO ||
+                token.type === TokenType.POWER ||
+                token.type === TokenType.EQUALS ||
+                token.type === TokenType.NOT_EQUAL ||
+                token.type === TokenType.LESS_THAN ||
+                token.type === TokenType.GREATER_THAN ||
+                token.type === TokenType.LESS_EQUAL ||
+                token.type === TokenType.GREATER_EQUAL ||
+                token.type === TokenType.AND ||
+                token.type === TokenType.OR ||
+                token.type === TokenType.XOR) {
+                // Reset current to parse the expression properly
+                return parseExpression();
+            }
+            
+            // If we get here, we have an unexpected token
+            throw new Error(`Unexpected token in parsePrimary: ${token.type}`);
         }
-
-        throw new TypeError(token.type);
+        
+                // Simple wrapper that calls parsePrimary for all token types
+        return parsePrimary();
     }
-
-    let ast = {
+    
+    const ast = {
         type: 'Program',
-        body: [],
+        body: []
     };
-
+    
     while (current < tokens.length) {
         const node = walk();
-        if (node !== null) {
+        if (node) {
             ast.body.push(node);
         }
+        
+        // Skip semicolons
+        if (current < tokens.length && tokens[current].type === TokenType.SEMICOLON) {
+            current++;
+        }
     }
-
+    
     return ast;
 }
 
 // Interpreter
 function interpreter(ast) {
-    let globalScope = {};
-
+    const globalScope = {};
+    initializeStandardLibrary(globalScope);
+    
     function evalNode(node) {
+        if (!node) {
+            return undefined;
+        }
         switch (node.type) {
             case 'NumberLiteral':
                 return parseInt(node.value);
+            case 'StringLiteral':
+                return node.value;
+            case 'BooleanLiteral':
+                return node.value;
             case 'PlusExpression':
                 return evalNode(node.left) + evalNode(node.right);
+            case 'MinusExpression':
+                return evalNode(node.left) - evalNode(node.right);
+            case 'MultiplyExpression':
+                return evalNode(node.left) * evalNode(node.right);
+            case 'DivideExpression':
+                const divisor = evalNode(node.right);
+                if (divisor === 0) {
+                    throw new Error('Division by zero');
+                }
+                return evalNode(node.left) / evalNode(node.right);
+            case 'ModuloExpression':
+                return evalNode(node.left) % evalNode(node.right);
+            case 'PowerExpression':
+                return Math.pow(evalNode(node.left), evalNode(node.right));
+            case 'EqualsExpression':
+                return evalNode(node.left) === evalNode(node.right);
+            case 'LessThanExpression':
+                return evalNode(node.left) < evalNode(node.right);
+            case 'GreaterThanExpression':
+                return evalNode(node.left) > evalNode(node.right);
+            case 'LessEqualExpression':
+                return evalNode(node.left) <= evalNode(node.right);
+            case 'GreaterEqualExpression':
+                return evalNode(node.left) >= evalNode(node.right);
+            case 'NotEqualExpression':
+                return evalNode(node.left) !== evalNode(node.right);
+            case 'AndExpression':
+                return evalNode(node.left) && evalNode(node.right);
+            case 'OrExpression':
+                return evalNode(node.left) || evalNode(node.right);
+            case 'XorExpression':
+                const leftVal = evalNode(node.left);
+                const rightVal = evalNode(node.right);
+                return (leftVal && !rightVal) || (!leftVal && rightVal);
+            case 'NotExpression':
+                return !evalNode(node.operand);
+            case 'TableLiteral':
+                const table = {};
+                let arrayIndex = 1;
+                
+                for (const entry of node.entries) {
+                    if (entry.key === null) {
+                        // Array-like entry: {1, 2, 3}
+                        table[arrayIndex] = evalNode(entry.value);
+                        arrayIndex++;
+                    } else {
+                        // Key-value entry: {name: "Alice", age: 30}
+                        let key;
+                        if (entry.key.type === 'Identifier') {
+                            // Convert identifier keys to strings
+                            key = entry.key.value;
+                        } else {
+                            // For other key types (numbers, strings), evaluate normally
+                            key = evalNode(entry.key);
+                        }
+                        const value = evalNode(entry.value);
+                        table[key] = value;
+                    }
+                }
+                
+                return table;
+            case 'TableAccess':
+                const tableValue = evalNode(node.table);
+                let keyValue;
+                
+                // Handle different key types
+                if (node.key.type === 'Identifier') {
+                    // For dot notation, use the identifier name as the key
+                    keyValue = node.key.value;
+                } else {
+                    // For bracket notation, evaluate the key expression
+                    keyValue = evalNode(node.key);
+                }
+                
+                if (typeof tableValue !== 'object' || tableValue === null) {
+                    throw new Error('Cannot access property of non-table value');
+                }
+                
+                if (tableValue[keyValue] === undefined) {
+                    throw new Error(`Key '${keyValue}' not found in table`);
+                }
+                
+                return tableValue[keyValue];
             case 'AssignmentExpression':
-                globalScope[node.name] = evalNode(node.value);
+                if (globalScope.hasOwnProperty(node.name)) {
+                    throw new Error(`Cannot reassign immutable variable: ${node.name}`);
+                }
+                const value = evalNode(node.value);
+                globalScope[node.name] = value;
                 return;
             case 'Identifier':
-                return globalScope[node.value];
-            case 'IfExpression':
-                return evalNode(node.test) ? evalNode(node.consequent) : node.alternate ? evalNode(node.alternate) : undefined;
+                const identifierValue = globalScope[node.value];
+                if (identifierValue === undefined) {
+                    throw new Error(`Variable ${node.value} is not defined`);
+                }
+                return identifierValue;
             case 'FunctionDeclaration':
-                globalScope[node.name] = function() {
+                // For anonymous functions, the name comes from the assignment
+                // The function itself doesn't have a name, so we just return
+                // The assignment will handle storing it in the global scope
+                return function(...args) {
                     let localScope = Object.create(globalScope);
                     for (let i = 0; i < node.params.length; i++) {
-                        localScope[node.params[i]] = arguments[i];
+                        localScope[node.params[i]] = args[i];
                     }
-                    let lastResult;
-                    for (let bodyNode of node.body) {
-                        lastResult = evalNode(bodyNode);
-                    }
-                    return lastResult;
+                    return localEvalNodeWithScope(node.body, localScope);
                 };
-                return;
             case 'FunctionCall':
-                if (globalScope[node.name] instanceof Function) {
+                let funcToCall; // Renamed from 'func' to avoid redeclaration
+                if (typeof node.name === 'string') {
+                    // Regular function call with string name
+                    funcToCall = globalScope[node.name];
+                } else if (node.name.type === 'Identifier') {
+                    // Function call with identifier
+                    funcToCall = globalScope[node.name.value];
+                } else if (node.name.type === 'TableAccess') {
+                    // Function call from table access (e.g., math.add)
+                    funcToCall = evalNode(node.name);
+                } else {
+                    throw new Error('Invalid function name in function call');
+                }
+                
+                if (funcToCall instanceof Function) {
                     let args = node.args.map(evalNode);
-                    return globalScope[node.name].apply(null, args);
+                    return funcToCall(...args);
+                }
+                throw new Error(`Function is not defined or is not callable`);
+            case 'CaseExpression':
+                const values = node.value.map(evalNode);
+                
+                for (const caseItem of node.cases) {
+                    const pattern = caseItem.pattern.map(evalNode);
+                    
+                    let matches = true;
+                    for (let i = 0; i < Math.max(values.length, pattern.length); i++) {
+                        const value = values[i];
+                        const patternValue = pattern[i];
+                        
+                        if (patternValue === true) continue;
+                        
+                        if (value !== patternValue) {
+                            matches = false;
+                            break;
+                        }
+                    }
+                    
+                    if (matches) {
+                        const results = caseItem.result.map(evalNode);
+                        if (results.length === 1) {
+                            return results[0];
+                        }
+                        return results.join(' ');
+                    }
                 }
-                throw new Error(`Function ${node.name} is not defined`);
+                throw new Error('No matching pattern found');
+            case 'WildcardPattern':
+                return true;
+            case 'IOInExpression':
+                const readline = require('readline');
+                const rl = readline.createInterface({
+                    input: process.stdin,
+                    output: process.stdout
+                });
+                
+                return new Promise((resolve) => {
+                    rl.question('', (input) => {
+                        rl.close();
+                        const num = parseInt(input);
+                        resolve(isNaN(num) ? input : num);
+                    });
+                });
+            case 'IOOutExpression':
+                const outputValue = evalNode(node.value);
+                console.log(outputValue);
+                return outputValue;
+            case 'IOAssertExpression':
+                const assertionValue = evalNode(node.value);
+                if (!assertionValue) {
+                    throw new Error('Assertion failed');
+                }
+                return assertionValue;
+            case 'FunctionReference':
+                const functionValue = globalScope[node.name];
+                if (functionValue === undefined) {
+                    throw new Error(`Function ${node.name} is not defined`);
+                }
+                if (typeof functionValue !== 'function') {
+                    throw new Error(`${node.name} is not a function`);
+                }
+                return functionValue;
             default:
                 throw new Error(`Unknown node type: ${node.type}`);
         }
     }
 
-    return evalNode(ast.body[0]);
+    const localEvalNodeWithScope = (node, scope) => {
+        if (!node) {
+            return undefined;
+        }
+        switch (node.type) {
+            case 'NumberLiteral':
+                return parseInt(node.value);
+            case 'StringLiteral':
+                return node.value;
+            case 'BooleanLiteral':
+                return node.value;
+            case 'PlusExpression':
+                return localEvalNodeWithScope(node.left, scope) + localEvalNodeWithScope(node.right, scope);
+            case 'MinusExpression':
+                return localEvalNodeWithScope(node.left, scope) - localEvalNodeWithScope(node.right, scope);
+            case 'MultiplyExpression':
+                return localEvalNodeWithScope(node.left, scope) * localEvalNodeWithScope(node.right, scope);
+            case 'DivideExpression':
+                const divisor = localEvalNodeWithScope(node.right, scope);
+                if (divisor === 0) {
+                    throw new Error('Division by zero');
+                }
+                return localEvalNodeWithScope(node.left, scope) / localEvalNodeWithScope(node.right, scope);
+            case 'ModuloExpression':
+                return localEvalNodeWithScope(node.left, scope) % localEvalNodeWithScope(node.right, scope);
+            case 'PowerExpression':
+                return Math.pow(localEvalNodeWithScope(node.left, scope), localEvalNodeWithScope(node.right, scope));
+            case 'EqualsExpression':
+                return localEvalNodeWithScope(node.left, scope) === localEvalNodeWithScope(node.right, scope);
+            case 'LessThanExpression':
+                return localEvalNodeWithScope(node.left, scope) < localEvalNodeWithScope(node.right, scope);
+            case 'GreaterThanExpression':
+                return localEvalNodeWithScope(node.left, scope) > localEvalNodeWithScope(node.right, scope);
+            case 'LessEqualExpression':
+                return localEvalNodeWithScope(node.left, scope) <= localEvalNodeWithScope(node.right, scope);
+            case 'GreaterEqualExpression':
+                return localEvalNodeWithScope(node.left, scope) >= localEvalNodeWithScope(node.right, scope);
+            case 'NotEqualExpression':
+                return localEvalNodeWithScope(node.left, scope) !== localEvalNodeWithScope(node.right, scope);
+            case 'AndExpression':
+                return localEvalNodeWithScope(node.left, scope) && localEvalNodeWithScope(node.right, scope);
+            case 'OrExpression':
+                return localEvalNodeWithScope(node.left, scope) || localEvalNodeWithScope(node.right, scope);
+            case 'XorExpression':
+                const leftVal = localEvalNodeWithScope(node.left, scope);
+                const rightVal = localEvalNodeWithScope(node.right, scope);
+                return (leftVal && !rightVal) || (!leftVal && rightVal);
+            case 'NotExpression':
+                return !localEvalNodeWithScope(node.operand, scope);
+            case 'TableLiteral':
+                const table = {};
+                let arrayIndex = 1;
+                
+                for (const entry of node.entries) {
+                    if (entry.key === null) {
+                        // Array-like entry: {1, 2, 3}
+                        table[arrayIndex] = localEvalNodeWithScope(entry.value, scope);
+                        arrayIndex++;
+                    } else {
+                        // Key-value entry: {name: "Alice", age: 30}
+                        let key;
+                        if (entry.key.type === 'Identifier') {
+                            // Convert identifier keys to strings
+                            key = entry.key.value;
+                        } else {
+                            // For other key types (numbers, strings), evaluate normally
+                            key = localEvalNodeWithScope(entry.key, scope);
+                        }
+                        const value = localEvalNodeWithScope(entry.value, scope);
+                        table[key] = value;
+                    }
+                }
+                
+                return table;
+            case 'TableAccess':
+                const tableValue = localEvalNodeWithScope(node.table, scope);
+                let keyValue;
+                
+                // Handle different key types
+                if (node.key.type === 'Identifier') {
+                    // For dot notation, use the identifier name as the key
+                    keyValue = node.key.value;
+                } else {
+                    // For bracket notation, evaluate the key expression
+                    keyValue = localEvalNodeWithScope(node.key, scope);
+                }
+                
+                if (typeof tableValue !== 'object' || tableValue === null) {
+                    throw new Error('Cannot access property of non-table value');
+                }
+                
+                if (tableValue[keyValue] === undefined) {
+                    throw new Error(`Key '${keyValue}' not found in table`);
+                }
+                
+                return tableValue[keyValue];
+            case 'AssignmentExpression':
+                if (globalScope.hasOwnProperty(node.name)) {
+                    throw new Error(`Cannot reassign immutable variable: ${node.name}`);
+                }
+                globalScope[node.name] = localEvalNodeWithScope(node.value, scope);
+                return;
+            case 'Identifier':
+                // First check local scope, then global scope
+                if (scope && scope.hasOwnProperty(node.value)) {
+                    return scope[node.value];
+                }
+                const identifierValue = globalScope[node.value];
+                if (identifierValue === undefined && node.value) {
+                    return node.value;
+                }
+                return identifierValue;
+            case 'FunctionDeclaration':
+                // For anonymous functions, the name comes from the assignment
+                // The function itself doesn't have a name, so we just return
+                // The assignment will handle storing it in the global scope
+                return function(...args) {
+                    let nestedScope = Object.create(globalScope);
+                    for (let i = 0; i < node.params.length; i++) {
+                        nestedScope[node.params[i]] = args[i];
+                    }
+                    return localEvalNodeWithScope(node.body, nestedScope);
+                };
+            case 'FunctionCall':
+                let localFunc;
+                if (typeof node.name === 'string') {
+                    // Regular function call with string name
+                    localFunc = globalScope[node.name];
+                } else if (node.name.type === 'Identifier') {
+                    // Function call with identifier
+                    localFunc = globalScope[node.name.value];
+                } else if (node.name.type === 'TableAccess') {
+                    // Function call from table access (e.g., math.add)
+                    localFunc = localEvalNodeWithScope(node.name, scope);
+                } else {
+                    throw new Error('Invalid function name in function call');
+                }
+                
+                if (localFunc instanceof Function) {
+                    let args = node.args.map(arg => localEvalNodeWithScope(arg, scope));
+                    return localFunc(...args);
+                }
+                throw new Error(`Function is not defined or is not callable`);
+            case 'CaseExpression':
+                const values = node.value.map(val => localEvalNodeWithScope(val, scope));
+                
+                for (const caseItem of node.cases) {
+                    const pattern = caseItem.pattern.map(pat => localEvalNodeWithScope(pat, scope));
+                    
+                    let matches = true;
+                    for (let i = 0; i < Math.max(values.length, pattern.length); i++) {
+                        const value = values[i];
+                        const patternValue = pattern[i];
+                        
+                        if (patternValue === true) continue;
+                        
+                        if (value !== patternValue) {
+                            matches = false;
+                            break;
+                        }
+                    }
+                    
+                    if (matches) {
+                        const results = caseItem.result.map(res => localEvalNodeWithScope(res, scope));
+                        if (results.length === 1) {
+                            return results[0];
+                        }
+                        return results.join(' ');
+                    }
+                }
+                throw new Error('No matching pattern found');
+            case 'WildcardPattern':
+                return true;
+            case 'IOInExpression':
+                const readline = require('readline');
+                const rl = readline.createInterface({
+                    input: process.stdin,
+                    output: process.stdout
+                });
+                
+                return new Promise((resolve) => {
+                    rl.question('', (input) => {
+                        rl.close();
+                        const num = parseInt(input);
+                        resolve(isNaN(num) ? input : num);
+                    });
+                });
+            case 'IOOutExpression':
+                const localOutputValue = localEvalNodeWithScope(node.value, scope);
+                console.log(localOutputValue);
+                return localOutputValue;
+            case 'IOAssertExpression':
+                const localAssertionValue = localEvalNodeWithScope(node.value, scope);
+                if (!localAssertionValue) {
+                    throw new Error('Assertion failed');
+                }
+                return localAssertionValue;
+            case 'FunctionReference':
+                const localFunctionValue = globalScope[node.name];
+                if (localFunctionValue === undefined) {
+                    throw new Error(`Function ${node.name} is not defined`);
+                }
+                if (typeof localFunctionValue !== 'function') {
+                    throw new Error(`${node.name} is not a function`);
+                }
+                return localFunctionValue;
+            default:
+                throw new Error(`Unknown node type: ${node.type}`);
+        }
+    };
+
+    const localEvalNode = (node) => {
+        if (!node) {
+            return undefined;
+        }
+        switch (node.type) {
+            case 'NumberLiteral':
+                return parseInt(node.value);
+            case 'StringLiteral':
+                return node.value;
+            case 'BooleanLiteral':
+                return node.value;
+            case 'PlusExpression':
+                return localEvalNode(node.left) + localEvalNode(node.right);
+            case 'MinusExpression':
+                return localEvalNode(node.left) - localEvalNode(node.right);
+            case 'MultiplyExpression':
+                return localEvalNode(node.left) * localEvalNode(node.right);
+            case 'DivideExpression':
+                const divisor = localEvalNode(node.right);
+                if (divisor === 0) {
+                    throw new Error('Division by zero');
+                }
+                return localEvalNode(node.left) / localEvalNode(node.right);
+            case 'ModuloExpression':
+                return localEvalNode(node.left) % localEvalNode(node.right);
+            case 'PowerExpression':
+                return Math.pow(localEvalNode(node.left), localEvalNode(node.right));
+            case 'EqualsExpression':
+                return localEvalNode(node.left) === localEvalNode(node.right);
+            case 'LessThanExpression':
+                return localEvalNode(node.left) < localEvalNode(node.right);
+            case 'GreaterThanExpression':
+                return localEvalNode(node.left) > localEvalNode(node.right);
+            case 'LessEqualExpression':
+                return localEvalNode(node.left) <= localEvalNode(node.right);
+            case 'GreaterEqualExpression':
+                return localEvalNode(node.left) >= localEvalNode(node.right);
+            case 'NotEqualExpression':
+                return localEvalNode(node.left) !== localEvalNode(node.right);
+            case 'AndExpression':
+                return localEvalNode(node.left) && localEvalNode(node.right);
+            case 'OrExpression':
+                return localEvalNode(node.left) || localEvalNode(node.right);
+            case 'XorExpression':
+                const leftVal = localEvalNode(node.left);
+                const rightVal = localEvalNode(node.right);
+                return (leftVal && !rightVal) || (!leftVal && rightVal);
+            case 'NotExpression':
+                return !localEvalNode(node.operand);
+            case 'TableLiteral':
+                const table = {};
+                let arrayIndex = 1;
+                
+                for (const entry of node.entries) {
+                    if (entry.key === null) {
+                        // Array-like entry: {1, 2, 3}
+                        table[arrayIndex] = localEvalNode(entry.value);
+                        arrayIndex++;
+                    } else {
+                        // Key-value entry: {name: "Alice", age: 30}
+                        let key;
+                        if (entry.key.type === 'Identifier') {
+                            // Convert identifier keys to strings
+                            key = entry.key.value;
+                        } else {
+                            // For other key types (numbers, strings), evaluate normally
+                            key = localEvalNode(entry.key);
+                        }
+                        const value = localEvalNode(entry.value);
+                        table[key] = value;
+                    }
+                }
+                
+                return table;
+            case 'TableAccess':
+                const tableValue = localEvalNode(node.table);
+                let keyValue;
+                
+                // Handle different key types
+                if (node.key.type === 'Identifier') {
+                    // For dot notation, use the identifier name as the key
+                    keyValue = node.key.value;
+                } else {
+                    // For bracket notation, evaluate the key expression
+                    keyValue = localEvalNode(node.key);
+                }
+                
+                if (typeof tableValue !== 'object' || tableValue === null) {
+                    throw new Error('Cannot access property of non-table value');
+                }
+                
+                if (tableValue[keyValue] === undefined) {
+                    throw new Error(`Key '${keyValue}' not found in table`);
+                }
+                
+                return tableValue[keyValue];
+            case 'AssignmentExpression':
+                if (globalScope.hasOwnProperty(node.name)) {
+                    throw new Error(`Cannot reassign immutable variable: ${node.name}`);
+                }
+                globalScope[node.name] = localEvalNode(node.value);
+                return;
+            case 'Identifier':
+                const identifierValue = globalScope[node.value];
+                if (identifierValue === undefined && node.value) {
+                    return node.value;
+                }
+                return identifierValue;
+            case 'FunctionDeclaration':
+                // For anonymous functions, the name comes from the assignment
+                // The function itself doesn't have a name, so we just return
+                // The assignment will handle storing it in the global scope
+                return function(...args) {
+                    let nestedScope = Object.create(globalScope);
+                    for (let i = 0; i < node.params.length; i++) {
+                        nestedScope[node.params[i]] = args[i];
+                    }
+                    return localEvalNodeWithScope(node.body, nestedScope);
+                };
+            case 'FunctionCall':
+                let localFunc;
+                if (typeof node.name === 'string') {
+                    // Regular function call with string name
+                    localFunc = globalScope[node.name];
+                } else if (node.name.type === 'Identifier') {
+                    // Function call with identifier
+                    localFunc = globalScope[node.name.value];
+                } else if (node.name.type === 'TableAccess') {
+                    // Function call from table access (e.g., math.add)
+                    localFunc = localEvalNode(node.name);
+                } else {
+                    throw new Error('Invalid function name in function call');
+                }
+                
+                if (localFunc instanceof Function) {
+                    let args = node.args.map(localEvalNode);
+                    return localFunc(...args);
+                }
+                throw new Error(`Function is not defined or is not callable`);
+            case 'CaseExpression':
+                const values = node.value.map(localEvalNode);
+                
+                for (const caseItem of node.cases) {
+                    const pattern = caseItem.pattern.map(localEvalNode);
+                    
+                    let matches = true;
+                    for (let i = 0; i < Math.max(values.length, pattern.length); i++) {
+                        const value = values[i];
+                        const patternValue = pattern[i];
+                        
+                        if (patternValue === true) continue;
+                        
+                        if (value !== patternValue) {
+                            matches = false;
+                            break;
+                        }
+                    }
+                    
+                    if (matches) {
+                        const results = caseItem.result.map(localEvalNode);
+                        if (results.length === 1) {
+                            return results[0];
+                        }
+                        return results.join(' ');
+                    }
+                }
+                throw new Error('No matching pattern found');
+            case 'WildcardPattern':
+                return true;
+            case 'IOInExpression':
+                const readline = require('readline');
+                const rl = readline.createInterface({
+                    input: process.stdin,
+                    output: process.stdout
+                });
+                
+                return new Promise((resolve) => {
+                    rl.question('', (input) => {
+                        rl.close();
+                        const num = parseInt(input);
+                        resolve(isNaN(num) ? input : num);
+                    });
+                });
+            case 'IOOutExpression':
+                const localOutputValue = localEvalNode(node.value);
+                console.log(localOutputValue);
+                return localOutputValue;
+            case 'IOAssertExpression':
+                const localAssertionValue = localEvalNode(node.value);
+                if (!localAssertionValue) {
+                    throw new Error('Assertion failed');
+                }
+                return localAssertionValue;
+            case 'FunctionReference':
+                const localFunctionValue = globalScope[node.name];
+                if (localFunctionValue === undefined) {
+                    throw new Error(`Function ${node.name} is not defined`);
+                }
+                if (typeof localFunctionValue !== 'function') {
+                    throw new Error(`${node.name} is not a function`);
+                }
+                return localFunctionValue;
+            default:
+                throw new Error(`Unknown node type: ${node.type}`);
+        }
+    };
+
+    let lastResult;
+    for (let node of ast.body) {
+        if (node) {
+            lastResult = evalNode(node);
+        }
+    }
+    
+    if (lastResult instanceof Promise) {
+        return lastResult.then(result => {
+            return result;
+        });
+    }
+    
+    return lastResult;
 }
 
-// Usage
-// const tokens = lexer('2 + 2');
-// const ast = parser(tokens);
-// const result = interpreter(ast);
-// console.log(result); // 4
+// Debug logging function
+function debugLog(message, data = null) {
+    if (process.env.DEBUG) {
+        console.log(`[DEBUG] ${message}`);
+        if (data) {
+            console.log(data);
+        }
+    }
+}
 
-// const tokens2 = lexer('x : 2 + 2');
-// const ast2 = parser(tokens2);
-// const result2 = interpreter(ast2);
-// console.log(result2); 
+// Debug error function
+function debugError(message, error = null) {
+    if (process.env.DEBUG) {
+        console.error(`[DEBUG ERROR] ${message}`);
+        if (error) {
+            console.error(error);
+        }
+    }
+}
 
-const fs = require('fs');
+// Execute a file
+function executeFile(filePath) {
+    try {
+        const fs = require('fs');
+        const input = fs.readFileSync(filePath, 'utf8');
+        
+        debugLog('Input:', input);
+        
+        const tokens = lexer(input);
+        debugLog('Tokens:', tokens);
+        
+        const ast = parser(tokens);
+        debugLog('AST:', JSON.stringify(ast, null, 2));
+        
+        const result = interpreter(ast);
+        
+        if (result instanceof Promise) {
+            result.then(finalResult => {
+                if (finalResult !== undefined) {
+                    console.log(finalResult);
+                }
+            }).catch(error => {
+                console.error(`Error executing file: ${error.message}`);
+            });
+        } else {
+            if (result !== undefined) {
+                console.log(result);
+            }
+        }
+    } catch (error) {
+        console.error(`Error executing file: ${error.message}`);
+    }
+}
 
-// Read the input from a file
-const input = fs.readFileSync('input.txt', 'utf-8');
+// Check command line arguments
+const args = process.argv.slice(2);
 
-// Usage
-const tokens = lexer(input);
-const ast = parser(tokens);
-const result = interpreter(ast);
-console.log(result);
\ No newline at end of file
+if (args.length === 0) {
+    console.error('Usage: node lang.js <file>');
+    console.error('  Provide a file path to execute');
+    process.exit(1);
+} else if (args.length === 1) {
+    // Execute the file
+    const filePath = args[0];
+    executeFile(filePath);
+} else {
+    // Too many arguments
+    console.error('Usage: node lang.js <file>');
+    console.error('  Provide exactly one file path to execute');
+    process.exit(1);
+}
\ No newline at end of file
diff --git a/js/scripting-lang/table_basic_test.txt b/js/scripting-lang/table_basic_test.txt
new file mode 100644
index 0000000..172d95c
--- /dev/null
+++ b/js/scripting-lang/table_basic_test.txt
@@ -0,0 +1,51 @@
+/* Basic Table Tests */
+
+/* Test 1: Simple table creation */
+..out "=== Basic Table Tests ===";
+empty : {};
+numbers : {1, 2, 3};
+person : {name: "Alice", age: 30};
+
+..out "Empty table: ";
+..out empty;
+..out "Numbers: ";
+..out numbers;
+..out "Person: ";
+..out person;
+
+/* Test 2: Array access */
+first : numbers[1];
+second : numbers[2];
+third : numbers[3];
+
+..out "First: ";
+..out first;
+..out "Second: ";
+..out second;
+..out "Third: ";
+..out third;
+
+/* Test 3: Object access */
+name : person.name;
+age : person.age;
+
+..out "Name: ";
+..out name;
+..out "Age: ";
+..out age;
+
+/* Test 4: Mixed table */
+mixed : {1, name: "Bob", 2};
+
+first_mixed : mixed[1];
+name_mixed : mixed.name;
+second_mixed : mixed[2];
+
+..out "Mixed first: ";
+..out first_mixed;
+..out "Mixed name: ";
+..out name_mixed;
+..out "Mixed second: ";
+..out second_mixed;
+
+..out "Basic tests complete!"; 
\ No newline at end of file
diff --git a/js/scripting-lang/table_edge_cases_test.txt b/js/scripting-lang/table_edge_cases_test.txt
new file mode 100644
index 0000000..268f271
--- /dev/null
+++ b/js/scripting-lang/table_edge_cases_test.txt
@@ -0,0 +1,304 @@
+/* Table Edge Cases Tests */
+
+/* Test 1: Nested tables */
+..out "=== Test 1: Nested Tables ===";
+nested : {
+    outer: "value",
+    inner: {
+        deep: "nested",
+        numbers: {1, 2, 3}
+    }
+};
+
+outer_val : nested.outer;
+inner_table : nested.inner;
+deep_val : nested.inner.deep;
+inner_nums : nested.inner.numbers;
+first_num : nested.inner.numbers[1];
+
+..out "Outer: ";
+..out outer_val;
+..out "Inner table: ";
+..out inner_table;
+..out "Deep: ";
+..out deep_val;
+..out "Inner numbers: ";
+..out inner_nums;
+..out "First number: ";
+..out first_num;
+
+/* Test 2: Tables with different value types */
+..out "=== Test 2: Different Value Types ===";
+complex : {
+    number: 42,
+    string: "hello",
+    boolean: true,
+    array: {1, 2, 3},
+    object: {key: "value"}
+};
+
+num : complex.number;
+str : complex.string;
+bool : complex.boolean;
+arr : complex.array;
+obj : complex.object;
+
+..out "Number: ";
+..out num;
+..out "String: ";
+..out str;
+..out "Boolean: ";
+..out bool;
+..out "Array: ";
+..out arr;
+..out "Object: ";
+..out obj;
+
+/* Test 3: Tables with function references */
+..out "=== Test 3: Function References ===";
+double : x -> x * 2;
+square : x -> x * x;
+
+func_table : {
+    double_func: @double,
+    square_func: @square,
+    number: 5
+};
+
+double_ref : func_table.double_func;
+square_ref : func_table.square_func;
+table_num : func_table.number;
+
+..out "Double ref: ";
+..out double_ref;
+..out "Square ref: ";
+..out square_ref;
+..out "Table number: ";
+..out table_num;
+
+/* Test 4: Tables with arithmetic expressions */
+..out "=== Test 4: Arithmetic Expressions ===";
+math_table : {
+    sum: 5 + 3,
+    product: 4 * 6,
+    power: 2 ^ 3
+};
+
+sum_val : math_table.sum;
+prod_val : math_table.product;
+pow_val : math_table.power;
+
+..out "Sum: ";
+..out sum_val;
+..out "Product: ";
+..out prod_val;
+..out "Power: ";
+..out pow_val;
+
+/* Test 5: Tables with function calls */
+..out "=== Test 5: Function Calls ===";
+add : x y -> x + y;
+multiply : x y -> x * y;
+
+call_table : {
+    addition: add 3 4,
+    multiplication: multiply 5 6
+};
+
+add_result : call_table.addition;
+mult_result : call_table.multiplication;
+
+..out "Addition: ";
+..out add_result;
+..out "Multiplication: ";
+..out mult_result;
+
+/* Test 6: Tables with bracket notation access */
+..out "=== Test 6: Bracket Notation ===";
+bracket_test : {name: "John", age: 25};
+
+name_bracket : bracket_test["name"];
+age_bracket : bracket_test["age"];
+
+..out "Name (bracket): ";
+..out name_bracket;
+..out "Age (bracket): ";
+..out age_bracket;
+
+/* Test 7: Tables with string keys */
+..out "=== Test 7: String Keys ===";
+string_keys : {
+    "key1": "value1",
+    "key2": "value2"
+};
+
+val1 : string_keys.key1;
+val2 : string_keys["key2"];
+
+..out "Value 1: ";
+..out val1;
+..out "Value 2: ";
+..out val2;
+
+/* Test 8: Tables with numeric keys */
+..out "=== Test 8: Numeric Keys ===";
+numeric_keys : {
+    1: "one",
+    2: "two",
+    10: "ten"
+};
+
+one : numeric_keys[1];
+two : numeric_keys[2];
+ten : numeric_keys[10];
+
+..out "One: ";
+..out one;
+..out "Two: ";
+..out two;
+..out "Ten: ";
+..out ten;
+
+/* Test 9: Tables with boolean keys */
+..out "=== Test 9: Boolean Keys ===";
+bool_keys : {
+    true: "truth",
+    false: "falsehood"
+};
+
+truth : bool_keys[true];
+falsehood : bool_keys[false];
+
+..out "Truth: ";
+..out truth;
+..out "Falsehood: ";
+..out falsehood;
+
+/* Test 10: Tables with trailing commas */
+..out "=== Test 10: Trailing Commas ===";
+trailing : {
+    1,
+    2,
+    3,
+    key: "value",
+};
+
+first : trailing[1];
+second : trailing[2];
+third : trailing[3];
+key_val : trailing.key;
+
+..out "First: ";
+..out first;
+..out "Second: ";
+..out second;
+..out "Third: ";
+..out third;
+..out "Key: ";
+..out key_val;
+
+/* Test 11: Tables with leading commas */
+..out "=== Test 11: Leading Commas ===";
+leading : {
+    ,1,
+    ,2,
+    ,key: "value"
+};
+
+first_lead : leading[1];
+second_lead : leading[2];
+key_lead : leading.key;
+
+..out "First (leading): ";
+..out first_lead;
+..out "Second (leading): ";
+..out second_lead;
+..out "Key (leading): ";
+..out key_lead;
+
+/* Test 12: Tables with function definitions inside */
+..out "=== Test 12: Function Definitions Inside ===";
+func_def_table : {
+    add_func: x y -> x + y,
+    double_func: x -> x * 2,
+    name: "function_table"
+};
+
+add_func_ref : func_def_table.add_func;
+double_func_ref : func_def_table.double_func;
+func_name : func_def_table.name;
+
+..out "Add func ref: ";
+..out add_func_ref;
+..out "Double func ref: ";
+..out double_func_ref;
+..out "Func name: ";
+..out func_name;
+
+/* Test 13: Tables with case expressions inside */
+..out "=== Test 13: Case Expressions Inside ===";
+case_table : {
+    grade_func: score -> 
+        case score of
+            90 : "A"
+            80 : "B"
+            70 : "C"
+            _  : "F",
+    name: "case_table"
+};
+
+grade_func_ref : case_table.grade_func;
+case_name : case_table.name;
+
+..out "Grade func ref: ";
+..out grade_func_ref;
+..out "Case name: ";
+..out case_name;
+
+/* Test 14: Tables with standard library functions */
+..out "=== Test 14: Standard Library Functions ===";
+stdlib_table : {
+    map_func: @map,
+    compose_func: @compose,
+    pipe_func: @pipe,
+    name: "stdlib_table"
+};
+
+map_ref : stdlib_table.map_func;
+compose_ref : stdlib_table.compose_func;
+pipe_ref : stdlib_table.pipe_func;
+stdlib_name : stdlib_table.name;
+
+..out "Map ref: ";
+..out map_ref;
+..out "Compose ref: ";
+..out compose_ref;
+..out "Pipe ref: ";
+..out pipe_ref;
+..out "Stdlib name: ";
+..out stdlib_name;
+
+/* Test 15: Tables with IO operations */
+..out "=== Test 15: IO Operations ===";
+io_table : {
+    input_func: @..in,
+    output_func: @..out,
+    assert_func: @..assert,
+    name: "io_table"
+};
+
+input_ref : io_table.input_func;
+output_ref : io_table.output_func;
+assert_ref : io_table.assert_func;
+io_name : io_table.name;
+
+..out "Input ref: ";
+..out input_ref;
+..out "Output ref: ";
+..out output_ref;
+..out "Assert ref: ";
+..out assert_ref;
+..out "IO name: ";
+..out io_name;
+
+..out "Edge cases tests complete!"; 
\ No newline at end of file
diff --git a/js/scripting-lang/test.txt b/js/scripting-lang/test.txt
new file mode 100644
index 0000000..79555b0
--- /dev/null
+++ b/js/scripting-lang/test.txt
@@ -0,0 +1,730 @@
+/* ========================================
+   COMPREHENSIVE LANGUAGE TEST SUITE
+   ======================================== */
+
+..out "=== COMPREHENSIVE LANGUAGE TEST SUITE ===";
+..out "";
+
+/* ========================================
+   SECTION 1: BASIC ARITHMETIC OPERATIONS
+   ======================================== */
+
+..out "1. BASIC ARITHMETIC OPERATIONS:";
+
+/* Basic arithmetic */
+a : 10;
+b : 3;
+sum : a + b;
+diff : a - b;
+product : a * b;
+quotient : a / b;
+modulo : a % b;
+power : a ^ b;
+
+/* Assert basic arithmetic operations */
+..assert sum = 13;
+..assert diff = 7;
+..assert product = 30;
+..assert quotient = 3.3333333333333335;
+..assert modulo = 1;
+..assert power = 1000;
+
+..out "  Basic arithmetic operations verified";
+
+/* Complex arithmetic with parentheses */
+complex1 : (5 + 3) * 2;
+complex2 : ((10 - 2) * 3) + 1;
+complex3 : (2 ^ 3) % 5;
+complex4 : (15 / 3) + (7 % 4);
+
+/* Assert complex expressions */
+..assert complex1 = 16;
+..assert complex2 = 25;
+..assert complex3 = 3;
+..assert complex4 = 8;
+
+..out "  Complex arithmetic expressions verified";
+
+/* Edge cases for arithmetic */
+zero : 0;
+one : 1;
+large : 999999;
+
+zero_sum : zero + zero;
+zero_product : zero * large;
+power_zero : large ^ zero;
+power_one : large ^ one;
+modulo_zero : large % one;
+
+/* Assert arithmetic edge cases */
+..assert zero_sum = 0;
+..assert zero_product = 0;
+..assert power_zero = 1;
+..assert power_one = 999999;
+..assert modulo_zero = 0;
+
+..out "  Arithmetic edge cases verified";
+
+..out "";
+
+/* ========================================
+   SECTION 2: COMPARISON OPERATORS
+   ======================================== */
+
+..out "2. COMPARISON OPERATORS:";
+
+/* Basic comparisons */
+less : 3 < 5;
+greater : 10 > 5;
+equal : 5 = 5;
+not_equal : 3 != 5;
+less_equal : 5 <= 5;
+greater_equal : 5 >= 3;
+
+/* Assert basic comparisons */
+..assert less = true;
+..assert greater = true;
+..assert equal = true;
+..assert not_equal = true;
+..assert less_equal = true;
+..assert greater_equal = true;
+
+..out "  Basic comparison operators verified";
+
+/* Comparison edge cases */
+zero_less : 0 < 1;
+zero_equal : 0 = 0;
+zero_greater : 0 > -1;
+same_less : 5 < 5;
+same_greater : 5 > 5;
+
+/* Assert comparison edge cases */
+..assert zero_less = true;
+..assert zero_equal = true;
+..assert zero_greater = true;
+..assert same_less = false;
+..assert same_greater = false;
+
+..out "  Comparison edge cases verified";
+
+..out "";
+
+/* ========================================
+   SECTION 3: LOGICAL OPERATORS
+   ======================================== */
+
+..out "3. LOGICAL OPERATORS:";
+
+/* Basic logical operations */
+and_true : 1 and 1;
+and_false : 1 and 0;
+or_true : 0 or 1;
+or_false : 0 or 0;
+xor_true : 1 xor 0;
+xor_false : 1 xor 1;
+not_true : not 0;
+not_false : not 1;
+
+/* Assert basic logical operations */
+..assert and_true = true;
+..assert and_false = false;
+..assert or_true = true;
+..assert or_false = false;
+..assert xor_true = true;
+..assert xor_false = false;
+..assert not_true = true;
+..assert not_false = false;
+
+..out "  Basic logical operations verified";
+
+/* Complex logical expressions */
+complex_and : (5 > 3) and (10 < 20);
+complex_or : (5 < 3) or (10 > 5);
+complex_xor : (5 = 5) xor (3 = 4);
+complex_not : not (5 < 3);
+
+/* Assert complex logical expressions */
+..assert complex_and = true;
+..assert complex_or = true;
+..assert complex_xor = true;
+..assert complex_not = true;
+
+..out "  Complex logical expressions verified";
+
+..out "";
+
+/* ========================================
+   SECTION 4: VARIABLE ASSIGNMENT
+   ======================================== */
+
+..out "4. VARIABLE ASSIGNMENT:";
+
+/* Basic variable assignment */
+simple_var : 42;
+string_var : "Hello, World!";
+bool_true : true;
+bool_false : false;
+
+/* Assert basic variables */
+..assert simple_var = 42;
+..assert string_var = "Hello, World!";
+..assert bool_true = true;
+..assert bool_false = false;
+
+..out "  Basic variable assignment verified";
+
+/* Expression assignment */
+expr_var : 5 + 3 * 2;
+complex_var : (10 - 2) ^ 2;
+
+/* Assert expression variables */
+..assert expr_var = 11;
+..assert complex_var = 64;
+
+..out "  Expression variable assignment verified";
+
+..out "";
+
+/* ========================================
+   SECTION 5: FUNCTION DEFINITIONS
+   ======================================== */
+
+..out "5. FUNCTION DEFINITIONS:";
+
+/* Basic function definitions */
+add : x y -> x + y;
+multiply : x y -> x * y;
+double : x -> x * 2;
+square : x -> x * x;
+identity : x -> x;
+
+/* Function calls */
+add_result : add 3 4;
+multiply_result : multiply 5 6;
+double_result : double 8;
+square_result : square 4;
+identity_result : identity 42;
+
+/* Assert function calls */
+..assert add_result = 7;
+..assert multiply_result = 30;
+..assert double_result = 16;
+..assert square_result = 16;
+..assert identity_result = 42;
+
+..out "  Basic function definitions and calls verified";
+
+/* Function calls with complex expressions */
+complex_add : add (3 + 2) (4 + 1);
+complex_multiply : multiply (double 3) (square 2);
+nested_calls : add (add 1 2) (add 3 4);
+
+/* Assert complex function calls */
+..assert complex_add = 15;
+..assert complex_multiply = 48;
+..assert nested_calls = 10;
+
+..out "  Complex function calls verified";
+
+..out "";
+
+/* ========================================
+   SECTION 6: PATTERN MATCHING
+   ======================================== */
+
+..out "6. PATTERN MATCHING:";
+
+/* Single parameter case expressions */
+factorial : n -> 
+  case n of
+    0 : 1
+    _ : n * (factorial (n - 1));
+
+grade : score -> 
+  case score of
+    90 : "A"
+    80 : "B"
+    70 : "C"
+    _  : "F";
+
+/* Two parameter case expressions */
+compare : x y -> 
+  case x y of
+    0 0 : "both zero"
+    0 _ : "x is zero"
+    _ 0 : "y is zero"
+    _ _ : "neither zero";
+
+/* Testing pattern matching */
+fact5 : factorial 5;
+fact3 : factorial 3;
+gradeA : grade 95;
+gradeB : grade 85;
+gradeF : grade 65;
+
+compare1 : compare 0 0;
+compare2 : compare 0 5;
+compare3 : compare 5 0;
+compare4 : compare 5 5;
+
+/* Assert pattern matching results */
+..assert fact5 = 120;
+..assert fact3 = 6;
+..assert gradeA = "A";
+..assert gradeB = "B";
+..assert gradeF = "F";
+
+..assert compare1 = "both zero";
+..assert compare2 = "x is zero";
+..assert compare3 = "y is zero";
+..assert compare4 = "neither zero";
+
+..out "  Pattern matching verified";
+
+..out "";
+
+/* ========================================
+   SECTION 7: TABLES
+   ======================================== */
+
+..out "7. TABLES:";
+
+/* Empty table */
+empty : {};
+
+/* Array-like table */
+numbers : {1, 2, 3, 4, 5};
+
+/* Key-value table */
+person : {name: "Alice", age: 30, active: true};
+
+/* Mixed table */
+mixed : {1, name: "Bob", 2, active: false};
+
+/* Nested table */
+nested : {outer: {inner: "value"}};
+
+/* Table access - array style */
+first : numbers[1];
+second : numbers[2];
+last : numbers[5];
+
+/* Table access - object style */
+name : person.name;
+age : person.age;
+active : person.active;
+
+/* Table access - mixed style */
+mixed_first : mixed[1];
+mixed_name : mixed.name;
+mixed_second : mixed[2];
+
+/* Table access - nested */
+nested_value : nested.outer.inner;
+
+/* Assert table access */
+..assert first = 1;
+..assert second = 2;
+..assert last = 5;
+
+..assert name = "Alice";
+..assert age = 30;
+..assert active = true;
+
+..assert mixed_first = 1;
+..assert mixed_name = "Bob";
+..assert mixed_second = 2;
+
+..assert nested_value = "value";
+
+..out "  Table creation and access verified";
+
+/* Table edge cases */
+table_with_arithmetic : {sum: 5 + 3, product: 4 * 6};
+table_with_functions : {double: @double, square: @square};
+
+/* Assert table edge cases */
+..assert table_with_arithmetic.sum = 8;
+..assert table_with_arithmetic.product = 24;
+
+..out "  Table edge cases verified";
+
+..out "";
+
+/* ========================================
+   SECTION 8: FIRST-CLASS FUNCTIONS
+   ======================================== */
+
+..out "8. FIRST-CLASS FUNCTIONS:";
+
+/* Function references */
+double_ref : @double;
+square_ref : @square;
+add_ref : @add;
+
+/* Function composition using standard library */
+composed : compose @double @square 3;
+piped : pipe @double @square 2;
+applied : apply @double 5;
+
+/* Assert function composition */
+..assert composed = 18;
+..assert piped = 16;
+..assert applied = 10;
+
+..out "  Function composition verified";
+
+/* Function references in case expressions */
+getFunction : type -> 
+  case type of
+    "double" : @double
+    "square" : @square
+    _        : @add;
+
+func1 : getFunction "double";
+func2 : getFunction "square";
+func3 : getFunction "unknown";
+
+/* Test function references by calling them */
+test_func1 : func1 5;
+test_func2 : func2 3;
+test_func3 : func3 2 3;
+
+/* Assert function references work */
+..assert test_func1 = 10;
+..assert test_func2 = 9;
+..assert test_func3 = 5;
+
+..out "  Function references from case expressions verified";
+
+..out "";
+
+/* ========================================
+   SECTION 9: STANDARD LIBRARY
+   ======================================== */
+
+..out "9. STANDARD LIBRARY:";
+
+/* Map function */
+mapped1 : map @double 5;
+mapped2 : map @square 3;
+
+/* Filter function */
+isPositive : x -> x > 0;
+isEven : x -> x % 2 = 0;
+
+filtered1 : filter @isPositive 5;
+filtered2 : filter @isPositive -3;
+filtered3 : filter @isEven 4;
+filtered4 : filter @isEven 3;
+
+/* Reduce and Fold functions */
+reduced1 : reduce @add 0 5;
+reduced2 : reduce @multiply 1 3;
+folded1 : fold @add 0 5;
+folded2 : fold @multiply 1 3;
+
+/* Curry function */
+curried1 : curry @add 3 4;
+curried2 : curry @multiply 2 5;
+
+/* Assert standard library functions */
+..assert mapped1 = 10;
+..assert mapped2 = 9;
+
+..assert filtered1 = 5;
+..assert filtered2 = 0;
+..assert filtered3 = 4;
+..assert filtered4 = 0;
+
+..assert reduced1 = 5;
+..assert reduced2 = 3;
+..assert folded1 = 5;
+..assert folded2 = 3;
+
+..assert curried1 = 7;
+..assert curried2 = 10;
+
+..out "  Standard library functions verified";
+
+..out "";
+
+/* ========================================
+   SECTION 10: COMMENTS
+   ======================================== */
+
+..out "10. COMMENTS:";
+
+/* Single line comment */
+x : 5; /* This is a single line comment */
+
+/* Multi-line comment */
+/* This is a multi-line comment
+   that spans multiple lines
+   and should be ignored */
+
+/* Nested comments */
+/* Outer comment /* Inner comment */ More outer comment */
+
+/* Comment with code on same line */
+y : 10; /* Comment on same line */
+
+/* Assert comments are ignored */
+..assert x = 5;
+..assert y = 10;
+
+..out "  Comments work correctly - all ignored by parser";
+
+..out "";
+
+/* ========================================
+   SECTION 11: EDGE CASES AND STRESS TESTS
+   ======================================== */
+
+..out "11. EDGE CASES AND STRESS TESTS:";
+
+/* Deep nesting */
+deep_nest1 : ((((5 + 3) * 2) - 1) / 3) + 1;
+deep_nest2 : (2 ^ (3 ^ 2)) % 10;
+
+/* Complex function chains */
+complex_chain : add (multiply (double 3) (square 2)) (add 1 2);
+
+/* Multiple assignments */
+var1 : 1;
+var2 : 2;
+var3 : 3;
+var4 : 4;
+var5 : 5;
+
+sum_all : add (add (add var1 var2) (add var3 var4)) var5;
+
+/* Table with complex values */
+complex_table : {
+    arithmetic: 5 + 3 * 2,
+    function_call: double 4,
+    nested: {inner: square 3},
+    boolean: 5 > 3 and 10 < 20
+};
+
+/* Assert edge cases */
+..assert deep_nest1 = 6;
+..assert deep_nest2 = 2;
+..assert complex_chain = 27;
+..assert sum_all = 15;
+
+..assert complex_table.arithmetic = 11;
+..assert complex_table.function_call = 8;
+..assert complex_table.nested.inner = 9;
+..assert complex_table.boolean = true;
+
+..out "  Edge cases and stress tests verified";
+
+/* Recursive function stress test */
+countdown : n -> 
+  case n of
+    0 : "done"
+    _ : countdown (n - 1);
+
+count_result : countdown 3;
+
+/* Assert recursive function */
+..assert count_result = "done";
+
+..out "  Recursive function stress test verified";
+
+..out "";
+
+/* ========================================
+   SECTION 12: ASSERTIONS
+   ======================================== */
+
+..out "12. ASSERTIONS:";
+
+/* Basic assertions */
+..assert 5 = 5;
+..assert 3 < 5;
+..assert 10 > 5;
+..assert 5 <= 5;
+..assert 5 >= 3;
+..assert 3 != 5;
+
+/* Complex assertions */
+..assert (5 + 3) = 8;
+..assert (10 - 2) > 5;
+..assert (2 ^ 3) = 8;
+..assert (15 / 3) = 5;
+
+/* Function call assertions */
+..assert (add 3 4) = 7;
+..assert (double 5) = 10;
+..assert (square 3) = 9;
+
+/* Logical assertions */
+..assert 1 and 1;
+..assert 0 or 1;
+..assert 1 xor 0;
+..assert not 0;
+
+/* String assertions */
+..assert "hello" = "hello";
+..assert "world" != "hello";
+
+/* Table assertions */
+..assert numbers[1] = 1;
+..assert person.name = "Alice";
+..assert mixed[1] = 1;
+
+/* Function reference assertions */
+..assert (func1 4) = 8;
+..assert (func2 5) = 25;
+
+..out "  All assertions passed successfully!";
+
+..out "";
+
+/* ========================================
+   SECTION 13: COMPREHENSIVE INTEGRATION TEST
+   ======================================== */
+
+..out "13. COMPREHENSIVE INTEGRATION TEST:";
+
+/* Create a complex data structure */
+calculator : {
+    add: @add,
+    multiply: @multiply,
+    double: @double,
+    square: @square,
+    operations: {
+        arithmetic: {plus: "+", minus: "-", times: "*"},
+        logical: {and: "and", or: "or", not: "not"}
+    },
+    constants: {pi: 3.14159, e: 2.71828}
+};
+
+/* Use the data structure */
+calc_add : calculator.add 5 3;
+calc_mult : calculator.multiply 4 6;
+calc_double : calculator.double 7;
+calc_square : calculator.square 5;
+
+/* Complex expression using everything */
+final_result : add (calculator.double (calculator.square 3)) (calculator.multiply 2 4);
+
+/* Assert integration test results */
+..assert calc_add = 8;
+..assert calc_mult = 24;
+..assert calc_double = 14;
+..assert calc_square = 25;
+..assert final_result = 26;
+
+/* Assert nested table access */
+..assert calculator.operations.arithmetic.plus = "+";
+..assert calculator.operations.logical.and = "and";
+..assert calculator.constants.pi = 3.14159;
+
+..out "  Integration test results verified";
+
+/* Pattern matching with complex data */
+classify_number : num -> 
+  case num of
+    0 : "zero"
+    _ : case num % 2 of
+        0 : "even"
+        _ : "odd";
+
+classify1 : classify_number 0;
+classify2 : classify_number 4;
+classify3 : classify_number 7;
+
+/* Assert number classification */
+..assert classify1 = "zero";
+..assert classify2 = "even";
+..assert classify3 = "odd";
+
+..out "  Number classification verified";
+
+..out "";
+
+/* ========================================
+   SECTION 14: ERROR HANDLING AND EDGE CASES
+   ======================================== */
+
+..out "14. ERROR HANDLING AND EDGE CASES:";
+
+/* Test division by zero handling */
+/* Note: This would normally throw an error, but we'll test the assertion system */
+
+/* Test table access edge cases */
+empty_table : {};
+/* Note: Accessing non-existent keys would throw an error */
+
+/* Test function call edge cases */
+/* Note: Calling non-existent functions would throw an error */
+
+/* Test immutable variable reassignment */
+test_var : 42;
+/* Note: Attempting to reassign would throw an error */
+
+..out "  Error handling edge cases noted";
+
+..out "";
+
+/* ========================================
+   SECTION 15: PERFORMANCE AND SCALE TESTS
+   ======================================== */
+
+..out "15. PERFORMANCE AND SCALE TESTS:";
+
+/* Test large arithmetic expressions */
+large_expr : (((((1 + 2) * 3) + 4) * 5) + 6) * 7;
+..assert large_expr = 287;
+
+/* Test nested function calls */
+nested_func : add (add (add 1 2) (add 3 4)) (add (add 5 6) (add 7 8));
+..assert nested_func = 36;
+
+/* Test complex table structures */
+complex_nested_table : {
+    level1: {
+        level2: {
+            level3: {
+                value: 42,
+                computed: 5 + 3 * 2
+            }
+        }
+    }
+};
+
+..assert complex_nested_table.level1.level2.level3.value = 42;
+..assert complex_nested_table.level1.level2.level3.computed = 11;
+
+..out "  Performance and scale tests verified";
+
+..out "";
+
+/* ========================================
+   FINAL SUMMARY
+   ======================================== */
+
+..out "=== TEST SUITE COMPLETED SUCCESSFULLY ===";
+..out "";
+..out "All language features tested and verified:";
+..out "  Arithmetic operations (+, -, *, /, %, ^)";
+..out "  Comparison operators (=, <, >, <=, >=, !=)";
+..out "  Logical operators (and, or, xor, not)";
+..out "  Variable assignment";
+..out "  Function definitions and calls";
+..out "  Pattern matching with case expressions";
+..out "  Tables (arrays and objects)";
+..out "  First-class functions and composition";
+..out "  Standard library functions";
+..out "  Comments (single-line, multi-line, nested)";
+..out "  Input/Output operations";
+..out "  Assertions";
+..out "  Edge cases and stress tests";
+..out "  Complex integration scenarios";
+..out "  Error handling edge cases";
+..out "  Performance and scale tests";
+..out "";
+..out "Language implementation is fully functional and verified!"; 
\ No newline at end of file
diff --git a/js/seed/README.md b/js/seed/README.md
index 8159cb3..981ca7d 100644
--- a/js/seed/README.md
+++ b/js/seed/README.md
@@ -1,6 +1,6 @@
 # Seed: Minimal FRP/TEA Web App Starter Kit
 
-This is an opinionated, minimal starting point for browser-native web apps using a functional, Elm-style architecture (FRP/TEA) and only browser APIs. No frameworks, no build step, just ES modules.
+This is an opinionated, hopefully simple starting point for browser-native web apps using a functional, Elm-style architecture (FRP/TEA) and only browser APIs. No rulers, no kings, no frameworks, no build step, only ES modules.
 
 ## Architecture
 - **state.js**: App state definition and helpers
@@ -15,14 +15,9 @@ This is an opinionated, minimal starting point for browser-native web apps using
 - **View**: Pure function `(state) => html`
 - **Entrypoint**: Handles events, dispatches actions, triggers re-render
 
-## Why?
-- Simple, testable, and maintainable
-- No dependencies
-- Encourages functional, declarative code
-
 ## How to Extend and Use This Template
 
-This template is designed to be a flexible, opinionated starting point for any browser-native app.
+This template is designed to be a flexible, opinionated starting point for any kinda app, especially proofs of concept, toys, and prototypes.
 
 ### Key Files to Extend
 - **src/state.js**: Define the app's state shape and any helper functions for cloning or initializing state.
@@ -69,9 +64,9 @@ Suppose you want to add a button that increments a counter:
      ```
 
 ### Tips
-- Keep all state transitions in `update.js` for predictability.
-- Keep all DOM rendering in `view.js` for clarity.
-- Use the `postRender` hook for accessibility or focus management.
+- Keep all state transitions in `update.js`.
+- Keep all DOM rendering in `view.js`.
+- Use the `postRender` hook for accessibility or focus management stuff.
 - Add new features by extending state, update, view, and wiring up events in `app.js`.
 
 ---
diff --git a/js/seed/seed b/js/seed/seed
new file mode 100755
index 0000000..15276e7
--- /dev/null
+++ b/js/seed/seed
@@ -0,0 +1,75 @@
+#!/bin/bash
+
+set -euo pipefail
+
+# Usage: seed plant
+
+if [ "$#" -ne 1 ] || [ "$1" != "plant" ]; then
+  echo "Usage: $0 plant"
+  exit 1
+fi
+
+if [ "$EUID" -eq 0 ]; then
+  echo "Do not run this script as root."
+  exit 1
+fi
+
+if ! command -v git >/dev/null 2>&1; then
+  echo "Warning: git is not installed. You won't be able to initialize a git repo."
+fi
+
+SRC_DIR="$(cd "$(dirname "$0")" && pwd)"
+
+read -rp "Enter new project name: " DEST_DIR
+
+if [ -z "$DEST_DIR" ]; then
+  echo "Project name cannot be empty."
+  exit 1
+fi
+
+DEST_PATH="$PWD/$DEST_DIR"
+
+if [ -e "$DEST_PATH" ]; then
+  echo "Error: '$DEST_PATH' already exists."
+  exit 1
+fi
+
+cleanup() {
+  if [ -d "$DEST_PATH" ]; then
+    echo "Cleaning up partial directory..."
+    rm -rf "$DEST_PATH"
+  fi
+}
+trap cleanup INT TERM
+
+echo "Copying seed template to '$DEST_PATH'..."
+mkdir "$DEST_PATH"
+
+if command -v rsync >/dev/null 2>&1; then
+  rsync -a --exclude='.git' --exclude='seed' "$SRC_DIR/" "$DEST_PATH/"
+else
+  cp -r "$SRC_DIR/"* "$DEST_PATH/"
+  cp -r "$SRC_DIR/".* "$DEST_PATH/" 2>/dev/null || true
+  rm -rf "$DEST_PATH/.git" "$DEST_PATH/seed"
+fi
+
+cd "$DEST_PATH"
+
+# Optionally, update README
+if [ -f README.md ]; then
+  sed -i '' "1s/.*/# $DEST_DIR/" README.md 2>/dev/null || sed -i "1s/.*/# $DEST_DIR/" README.md
+fi
+
+echo "Initialized new project in '$DEST_PATH'."
+
+read -rp "Do you want to initialize a git repository? (y/n): " INIT_GIT
+if [[ "$INIT_GIT" =~ ^[Yy]$ ]]; then
+  git init
+  echo "Git repository initialized."
+else
+  echo "Skipping git initialization."
+fi
+
+echo "Next steps:"
+echo "  cd \"$DEST_PATH\""
+echo "  and tend to the seed you've planted..." 
\ No newline at end of file
diff --git a/js/seed/src/app.js b/js/seed/src/app.js
index 34b4579..49ad9d1 100644
--- a/js/seed/src/app.js
+++ b/js/seed/src/app.js
@@ -155,11 +155,3 @@ function setHistoryPointer(idx) {
     updateHistoryInfo();
   }
 }
-
-function handleSliderChange(e) {
-  setHistoryPointer(Number(e.target.value));
-}
-
-function handleStepperChange(e) {
-  setHistoryPointer(Number(e.target.value));
-} 
\ No newline at end of file
diff --git a/js/seed/src/dev.js b/js/seed/src/dev.js
index ee1a6e7..173fc3c 100644
--- a/js/seed/src/dev.js
+++ b/js/seed/src/dev.js
@@ -1,8 +1,8 @@
 // devMode.js
-// Minimal, single-file dev mode with scriptable console API
+// Simpleish, single-file dev mode with interactive console API
 
 /**
- * Initialize dev mode: exposes a scriptable API for stepping through state history.
+ * Initialize dev mode: exposes an API for stepping through state history.
  * @param {object} opts
  * @param {function} opts.getState - returns current app state
  * @param {function} opts.setState - sets app state
diff --git a/js/seed/src/view.js b/js/seed/src/view.js
index 5feef6e..4c6e680 100644
--- a/js/seed/src/view.js
+++ b/js/seed/src/view.js
@@ -4,10 +4,10 @@
 /**
  * Pure view functions for the application.
  *
- * Why pure functions returning HTML strings?
+ * Why pure functions returning HTML strings? Because Elm does it, tbh.
  * - Keeps rendering logic stateless and easy to test.
- * - Ensures the UI is always a direct function of state, avoiding UI bugs from incremental DOM updates.
- * - Using template literals is minimal and browser-native, with no dependencies.
+ * - Ensures the UI is always a direct function of state, which should in theory totally avoid bugs from incremental DOM updates.
+ * - Using template literals is minimal and browser-native, with no dependencies, and is fun.
  *
  * Why escape output?
  * - Prevents XSS and ensures all user/content data is safely rendered.