about summary refs log tree commit diff stats
path: root/awk/vm
diff options
context:
space:
mode:
Diffstat (limited to 'awk/vm')
-rw-r--r--awk/vm/README.md91
-rwxr-xr-xawk/vm/compiler.py172
-rw-r--r--awk/vm/debug.coffee6
-rw-r--r--awk/vm/simple.coffee4
-rw-r--r--awk/vm/simple_test.coffee8
-rw-r--r--awk/vm/stack_test.coffee15
-rw-r--r--awk/vm/test.coffee7
-rw-r--r--awk/vm/test_steps.coffee15
-rwxr-xr-xawk/vm/vm.awk254
-rwxr-xr-xawk/vm/vm_tests.sh42
10 files changed, 614 insertions, 0 deletions
diff --git a/awk/vm/README.md b/awk/vm/README.md
new file mode 100644
index 0000000..83a35fd
--- /dev/null
+++ b/awk/vm/README.md
@@ -0,0 +1,91 @@
+# Stack-Based Virtual Machine in AWK
+
+A simple stack-based virtual machine implementation in AWK, inspired by Forth. The VM provides basic stack manipulation, arithmetic operations, register access, and memory operations.
+
+## Architecture
+
+The VM consists of:
+- A data stack (100 elements)
+- Main memory (1000 cells)
+- Three registers:
+  - A: General purpose register
+  - B: Often used as memory pointer
+  - P: Program pointer, used for sequential memory operations
+
+## Instruction Set
+
+### Stack Operations
+- `DROP` - Remove top item from stack
+- `DUP` - Duplicate top stack item
+- `OVER` - Copy second item to top of stack
+- `SWAP` - Exchange top two stack items
+- Numbers are automatically pushed onto the stack
+
+### Arithmetic Operations
+- `+` - Add top two stack items (a b -- a+b)
+- `*` - Multiply top two stack items (a b -- a*b)
+- `AND` - Bitwise AND of top two items
+- `XOR` - Bitwise XOR of top two items
+- `NOT` - Bitwise NOT of top item
+- `2*` - Multiply top item by 2 (shift left)
+- `2/` - Divide top item by 2 (shift right)
+
+### Register Operations
+- `A` - Push value of A register onto stack
+- `A!` - Store top stack value into A register
+- `B!` - Store top stack value into B register
+
+### Memory Operations
+- `@` - Fetch from memory address on stack (addr -- value)
+- `!` - Store to memory address on stack (value addr --)
+- `@+` - Fetch from memory at P, then increment P
+- `!+` - Store to memory at P, then increment P
+- `@B` - Fetch from memory address in B register
+- `!B` - Store to memory address in B register
+- `@P` - Fetch from memory address in P register
+- `!P` - Store to memory address in P register
+
+### Debug & Control
+- `.` - NO-OP (does nothing)
+- `BYE` - Exit program
+- `SHOW` - Display current machine state (stack, registers, memory)
+
+## Memory Model
+
+- Memory is zero-based
+- Each cell can hold a numeric value
+- Memory is accessed either directly (using @ and !) or through registers
+- P register is typically used for sequential memory operations
+- B register is typically used as a memory pointer
+
+## Example Programs
+
+### Store and retrieve a value
+```
+5 DUP 0 ! # Store 5 at address 0
+3 DUP 1 ! # Store 3 at address 1
+0 @ 1 @ + # Load both values and add them
+2 ! # Store result at address 2
+```
+
+### Using registers
+```
+42 A! # Store 42 in A register
+A # Push A's value onto stack
+100 B! # Set B to address 100
+42 !B # Store 42 at address 100
+@B # Read from address 100
+```
+
+## Usage
+
+```bash
+# Run a program directly
+echo "5 3 + SHOW" | awk -f vm.awk
+
+# Compile and run a program
+./compiler.py program.coffee | awk -f vm.awk
+
+# Run test suite
+./vm_tests.sh
+```
\ No newline at end of file
diff --git a/awk/vm/compiler.py b/awk/vm/compiler.py
new file mode 100755
index 0000000..a406779
--- /dev/null
+++ b/awk/vm/compiler.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python3
+
+"""
+A simple compiler that translates CoffeeScript-like syntax to VM instructions.
+Example input:
+    
+    # Simple arithmetic
+    x = 5
+    y = 3
+    z = x + y
+    
+    # Using memory
+    array = []
+    array[0] = 42
+    array[1] = array[0] * 2
+    
+Will compile to VM instructions like:
+    
+    5 A!              # store 5 in A register
+    3 B!              # store 3 in B register
+    A @B +            # add them
+"""
+
+import sys
+import re
+
+class Compiler:
+    def __init__(self):
+        self.variables = {}  # Maps variable names to memory locations
+        self.next_memory = 0  # Next available memory location
+        
+    def allocate_variable(self, name):
+        """Allocate memory location for a variable"""
+        if name not in self.variables:
+            self.variables[name] = self.next_memory
+            self.next_memory += 1
+        return self.variables[name]
+    
+    def compile_assignment(self, line):
+        """Compile assignment statements like 'x = 5' or 'x = y + z'"""
+        # Remove any comments from the line
+        line = line.split('#')[0].strip()
+        
+        match = re.match(r'(\w+)\s*=\s*(.+)', line)
+        if not match:
+            return None
+        
+        var_name = match.group(1)
+        expression = match.group(2)
+        
+        print(f"# Compiling assignment: {var_name} = {expression}", file=sys.stderr)
+        
+        # First get the memory location
+        mem_loc = self.allocate_variable(var_name)
+        
+        # Then compile the expression
+        expr_code = self.compile_expression(expression)
+        if not expr_code:
+            print(f"# Error: Failed to compile expression: {expression}", file=sys.stderr)
+            return None
+        
+        # Generate code that:
+        # 1. Evaluates the expression
+        # 2. Duplicates the result (for storing and leaving on stack)
+        # 3. Stores at memory location
+        vm_code = []
+        vm_code.extend(expr_code)     # Evaluate expression
+        vm_code.append("DUP")         # Make a copy
+        vm_code.append(str(mem_loc))  # Push memory location
+        vm_code.append("@")           # Read current value (for debugging)
+        vm_code.append("DROP")        # Drop the old value
+        vm_code.append("!")           # Store new value
+        
+        return vm_code
+    
+    def compile_expression(self, expr):
+        """Compile expressions like '5', 'x + y', etc."""
+        vm_code = []
+        
+        # Remove any comments from the expression
+        expr = expr.split('#')[0].strip()
+        
+        # Handle simple number
+        if expr.isdigit():
+            vm_code.append(expr)
+            return vm_code
+            
+        # Handle variable reference
+        if expr in self.variables:
+            vm_code.append(str(self.variables[expr]))
+            vm_code.append("@")
+            return vm_code
+            
+        # Handle binary operations
+        ops = {
+            '+': '+',
+            '*': '*',
+            '-': 'NOT +',
+        }
+        
+        # Try each operator
+        for op in ops:
+            if op in expr:
+                parts = expr.split(op, 1)
+                if len(parts) == 2:
+                    left = parts[0].strip()
+                    right = parts[1].strip()
+                    
+                    print(f"# Debug: left={left}, right={right}", file=sys.stderr)
+                    
+                    # Generate code for left operand
+                    left_code = self.compile_expression(left)
+                    if not left_code:
+                        continue
+                    vm_code.extend(left_code)
+                    
+                    # Generate code for right operand
+                    right_code = self.compile_expression(right)
+                    if not right_code:
+                        continue
+                    vm_code.extend(right_code)
+                    
+                    # Add the operation
+                    vm_code.append(ops[op])
+                    return vm_code
+        
+        return vm_code
+
+    def compile(self, source):
+        """Compile source code to VM instructions"""
+        output = []
+        debug_output = []
+        
+        for line in source.split('\n'):
+            line = line.strip()
+            if not line or line.startswith('#'):
+                continue
+                
+            if line == "SHOW":
+                output.append("SHOW")
+                continue
+                
+            if '=' in line:
+                vm_code = self.compile_assignment(line)
+                if vm_code:
+                    output.extend(vm_code)
+                    debug_output.append(f"{' '.join(vm_code)}  # {line}")
+                    if not line.startswith('result ='):  # If not final result
+                        output.append("DROP")  # Drop the duplicate we left on stack
+                    continue
+        
+        print("# Generated VM code:", file=sys.stderr)
+        for line in debug_output:
+            print(f"# {line}", file=sys.stderr)
+            
+        # Add final SHOW to see the result
+        output.append("SHOW")
+        return ' '.join(output)
+
+def main():
+    if len(sys.argv) > 1:
+        with open(sys.argv[1]) as f:
+            source = f.read()
+    else:
+        source = sys.stdin.read()
+    
+    compiler = Compiler()
+    vm_code = compiler.compile(source)
+    print(vm_code)
+
+if __name__ == '__main__':
+    main() 
\ No newline at end of file
diff --git a/awk/vm/debug.coffee b/awk/vm/debug.coffee
new file mode 100644
index 0000000..0663cec
--- /dev/null
+++ b/awk/vm/debug.coffee
@@ -0,0 +1,6 @@
+x = 5
+SHOW
+y = 3
+SHOW
+z = x + y
+SHOW 
\ No newline at end of file
diff --git a/awk/vm/simple.coffee b/awk/vm/simple.coffee
new file mode 100644
index 0000000..0a596a7
--- /dev/null
+++ b/awk/vm/simple.coffee
@@ -0,0 +1,4 @@
+x = 5
+y = 3
+z = x + y
+result = z * 2 
\ No newline at end of file
diff --git a/awk/vm/simple_test.coffee b/awk/vm/simple_test.coffee
new file mode 100644
index 0000000..8fec5ba
--- /dev/null
+++ b/awk/vm/simple_test.coffee
@@ -0,0 +1,8 @@
+x = 5
+SHOW
+y = 3
+SHOW
+z = x + y  # Should be 8
+SHOW
+result = z * 2  # Should be 16
+SHOW 
\ No newline at end of file
diff --git a/awk/vm/stack_test.coffee b/awk/vm/stack_test.coffee
new file mode 100644
index 0000000..ab29e63
--- /dev/null
+++ b/awk/vm/stack_test.coffee
@@ -0,0 +1,15 @@
+# First store 5 in memory location 0
+x = 5
+SHOW
+
+# Then store 3 in memory location 1
+y = 3
+SHOW
+
+# Add them: load 5, load 3, add them
+z = x + y
+SHOW
+
+# Double z: load 8, multiply by 2
+result = z * 2
+SHOW 
\ No newline at end of file
diff --git a/awk/vm/test.coffee b/awk/vm/test.coffee
new file mode 100644
index 0000000..aecda14
--- /dev/null
+++ b/awk/vm/test.coffee
@@ -0,0 +1,7 @@
+# Calculate sum
+x = 5
+y = 3
+z = x + y
+
+# Double it
+result = z * 2
diff --git a/awk/vm/test_steps.coffee b/awk/vm/test_steps.coffee
new file mode 100644
index 0000000..f1d0415
--- /dev/null
+++ b/awk/vm/test_steps.coffee
@@ -0,0 +1,15 @@
+# Step 1: Initialize x
+x = 5
+SHOW
+
+# Step 2: Initialize y
+y = 3
+SHOW
+
+# Step 3: Add x and y
+z = x + y
+SHOW
+
+# Step 4: Double z
+result = z * 2
+SHOW 
\ No newline at end of file
diff --git a/awk/vm/vm.awk b/awk/vm/vm.awk
new file mode 100755
index 0000000..67da3e7
--- /dev/null
+++ b/awk/vm/vm.awk
@@ -0,0 +1,254 @@
+#!/usr/bin/awk -f
+
+
+# Stack: DROP, DUP, OVER, PUSH, POP
+# Math:	+ AND XOR NOT 2* 2/ multiply-step
+# Call:	JUMP CALL RETURN IF -IF
+# Loop:	NEXT UNEXT
+# Register:	A A! B!
+# Memory: @ ! @+ !+ @B !B @P !P
+# NO-OP: .
+
+
+BEGIN {
+    # Initialize VM state
+    stack_pointer = 0    # Points to next free position
+    pc = 0               # Program counter
+    MAX_STACK = 100      # Maximum stack size
+    MAX_MEM = 1000       # Memory size
+    
+    # Initialize registers
+    A = 0              # A register
+    B = 0              # B register
+    P = 0              # P register (auxiliary pointer)
+    
+    # Stack operations
+    split("", stack)   # Initialize stack array
+    split("", memory)  # Initialize memory array
+}
+
+# Stack operations
+function push(value) {
+    if (stack_pointer >= MAX_STACK) {
+        print "Stack overflow!" > "/dev/stderr"
+        exit 1
+    }
+    printf "# DEBUG: Pushing %d onto stack\n", value > "/dev/stderr"
+    stack[stack_pointer++] = value
+}
+
+function pop() {
+    if (stack_pointer <= 0) {
+        print "Stack underflow!" > "/dev/stderr"
+        exit 1
+    }
+    value = stack[--stack_pointer]
+    printf "# DEBUG: Popping %d from stack\n", value > "/dev/stderr"
+    return value
+}
+
+# Basic stack manipulation
+function op_drop() {
+    pop()
+}
+
+function op_dup() {
+    if (stack_pointer <= 0) {
+        print "Stack underflow on DUP!" > "/dev/stderr"
+        exit 1
+    }
+    push(stack[stack_pointer - 1])
+}
+
+function op_over() {
+    if (stack_pointer <= 1) {
+        print "Stack underflow on OVER!" > "/dev/stderr"
+        exit 1
+    }
+    push(stack[stack_pointer - 2])
+}
+
+# Basic arithmetic operations
+function op_add() {
+    b = pop()
+    a = pop()
+    result = a + b
+    printf "# DEBUG: Adding %d + %d = %d\n", a, b, result > "/dev/stderr"
+    push(result)
+}
+
+function op_and() {
+    b = pop()
+    a = pop()
+    # For now, we'll just multiply as a placeholder
+    # In a real implementation, we'd need to implement proper bitwise AND
+    push(a * b)
+}
+
+function op_xor() {
+    b = pop()
+    a = pop()
+    # For now, we'll just add as a placeholder
+    # In a real implementation, we'd need to implement proper bitwise XOR
+    push(a + b)
+}
+
+function op_not() {
+    a = pop()
+    # For now, we'll just negate as a placeholder
+    # In a real implementation, we'd need to implement proper bitwise NOT
+    push(-a - 1)
+}
+
+function op_2times() {
+    a = pop()
+    push(a * 2)
+}
+
+function op_2div() {
+    a = pop()
+    push(int(a / 2))
+}
+
+function op_multiply() {
+    b = pop()
+    a = pop()
+    result = a * b
+    printf "# DEBUG: Multiplying %d * %d = %d\n", a, b, result > "/dev/stderr"
+    push(result)
+}
+
+# Register operations
+function op_a() {
+    push(A)
+}
+
+function op_astore() {
+    A = pop()
+}
+
+function op_bstore() {
+    B = pop()
+}
+
+# Memory operations
+function op_fetch() {
+    addr = pop()
+    value = memory[addr]
+    printf "# DEBUG: Fetching %d from memory[%d]\n", value, addr > "/dev/stderr"
+    push(value)
+}
+
+function op_store() {
+    addr = pop()  # First pop the address
+    value = pop() # Then pop the value
+    printf "# DEBUG: Storing %d at memory[%d]\n", value, addr > "/dev/stderr"
+    memory[addr] = value
+}
+
+function op_fetchplus() {
+    push(memory[P++])
+}
+
+function op_storeplus() {
+    memory[P++] = pop()
+}
+
+function op_fetchb() {
+    push(memory[B])
+}
+
+function op_storeb() {
+    memory[B] = pop()
+}
+
+function op_fetchp() {
+    push(memory[P])
+}
+
+function op_storep() {
+    memory[P] = pop()
+}
+
+function print_stack() {
+    printf "Stack: "
+    for (i = 0; i < stack_pointer; i++) {
+        printf "%d ", stack[i]
+    }
+    printf "\n"
+}
+
+function print_state() {
+    print_stack()
+    printf "Registers: A=%d B=%d P=%d\n", A, B, P
+    printf "Memory[P]=%d Memory[B]=%d\n", memory[P], memory[B]
+    printf "Memory: "
+    for (i = 0; i < 4; i++) {  # Show first 4 memory locations
+        printf "[%d]=%d ", i, memory[i]
+    }
+    printf "\n-------------------\n"
+}
+
+function op_swap() {
+    if (stack_pointer < 2) {
+        print "Stack underflow on SWAP!" > "/dev/stderr"
+        exit 1
+    }
+    # Swap the top two elements on the stack
+    temp = stack[stack_pointer - 1]
+    stack[stack_pointer - 1] = stack[stack_pointer - 2]
+    stack[stack_pointer - 2] = temp
+    printf "# DEBUG: Swapping top two stack elements\n" > "/dev/stderr"
+}
+
+function execute_instruction(inst) {
+    if (inst ~ /^[0-9]+$/) {
+        # Numbers are pushed onto the stack
+        push(int(inst))
+        return
+    }
+    
+    if (inst == "BYE")      { exit 0 } # not really in the minimal spec as set out by Chuck Moore, but useful for a graceful exit.
+    if (inst == "DROP")     { op_drop(); return }
+    if (inst == "DUP")      { op_dup(); return }
+    if (inst == "OVER")     { op_over(); return } # copy second item to top of stack
+    if (inst == "SWAP")     { op_swap(); return } # swap top two stack items
+    if (inst == "+")        { op_add(); return }
+    if (inst == "AND")      { op_and(); return }
+    if (inst == "XOR")      { op_xor(); return }
+    if (inst == "NOT")      { op_not(); return }
+    if (inst == "2*")       { op_2times(); return } # multiply-step
+    if (inst == "2/")       { op_2div(); return } # divide-step
+    if (inst == "*")        { op_multiply(); return }
+    if (inst == "A")        { op_a(); return } # push A register
+    if (inst == "A!")       { op_astore(); return } # store A register
+    if (inst == "B!")       { op_bstore(); return } # store B register
+    if (inst == "@")        { op_fetch(); return } # fetch from memory
+    if (inst == "!")        { op_store(); return } # store to memory
+    if (inst == "@+")       { op_fetchplus(); return } # fetch from memory at P+
+    if (inst == "!+")       { op_storeplus(); return } # store to memory at P+
+    if (inst == "@B")       { op_fetchb(); return } # fetch from memory at B
+    if (inst == "!B")       { op_storeb(); return } # store to memory at B
+    if (inst == "@P")       { op_fetchp(); return } # fetch from memory at P
+    if (inst == "!P")       { op_storep(); return } # store to memory at P
+    if (inst == ".")        { return }  # NO-OP
+    if (inst == "SHOW")     { print_state(); return } # show state info
+    
+    print "Unknown instruction: " inst > "/dev/stderr"
+    exit 1
+}
+
+# Main execution loop
+{
+    # Remove comments (everything after #)
+    sub(/#.*$/, "")
+    
+    # Skip empty lines after comment removal
+    if (NF == 0) next
+    
+    # Split the input line into words
+    n = split($0, words)
+    for (i = 1; i <= n; i++) {
+        execute_instruction(words[i])
+    }
+}
diff --git a/awk/vm/vm_tests.sh b/awk/vm/vm_tests.sh
new file mode 100755
index 0000000..6244c51
--- /dev/null
+++ b/awk/vm/vm_tests.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+echo "Running VM tests..."
+echo
+
+echo "Test 1: Basic register A operations"
+echo "42 A! A A     # Store 42 in A, then read A twice" | awk -f vm.awk
+echo
+
+echo "Test 2: Register A and B with memory operations"
+echo "100 A! 200 B! 42 @B     # Store 100 in A, 200 in B, read from B's address" | awk -f vm.awk
+echo
+
+echo "Test 3: Sequential memory operations using P register"
+echo "0 !P 1 !+ 2 !+ 3 !+ 4 !+ 5 !+     # Store 1-5 sequentially using P" | awk -f vm.awk
+echo "0 !P @+ @+ @+ @+ @+     # Read back values using P" | awk -f vm.awk
+echo
+
+echo "Test 4: Complex register manipulation"
+echo "42 A! A DUP B! @B     # Store 42 in A, copy to B, read via B" | awk -f vm.awk
+echo
+
+echo "Test 5: Register arithmetic"
+echo "5 A! 3 B! A @B +     # Store 5 in A, 3 in B, add A + mem[B]" | awk -f vm.awk
+echo
+
+echo "Test 6: Memory pointer operations"
+echo "42 0 ! 1 !P @P     # Store 42 at addr 0, point P to 1, read P" | awk -f vm.awk
+echo
+
+echo "Test 7: Register and memory interaction"
+echo "10 A! 20 B! A @B !     # Store A's value at B's address" | awk -f vm.awk
+echo "@B     # Read from memory at B's address" | awk -f vm.awk
+echo
+
+echo "Test 8: Demonstrating B! vs !B"
+echo "100 B!     # Set B register to 100" | awk -f vm.awk
+echo "42 !B      # Store 42 at memory location 100" | awk -f vm.awk
+echo "@B         # Read from memory location 100" | awk -f vm.awk
+echo
+
+echo "Tests completed." 
\ No newline at end of file