Written in C from first principles.
Stack-based execution. Lexical scoping.
First-class closures. Tri-color GC.
No runtime. No dependencies. No nonsense.
Pratt parser emits bytecode directly during parsing. No AST constructed. Compilation is a single linear pass over the token stream — keeps it fast and memory-lean.
All operations push and pop from a contiguous value stack. Call frames hold a pointer into this stack as their local variable window. Up to 64 nested frames.
Captured variables live on the stack while the enclosing function is active. OP_CLOSE_UPVALUE promotes them to the heap when the scope exits.
Gray worklist drives collection. bytesAllocated tracked against a dynamic nextGC threshold. Heap grows automatically between collection cycles.
All strings deduplicated in a global hash table backed by vm.strings. Equality is O(1) pointer comparison regardless of string length. GC-aware.
Expose any C function to A# with defineNative(). Three lines of code. No FFI overhead, no wrappers. The built-ins are all defined this way.
Converts raw source text into a flat token stream. Handles identifiers, string literals, numbers, keywords, and all punctuation in a single pass.
Pratt parser consumes tokens and emits bytecode directly into a chunk. Resolves local variables and captures upvalues inline, no AST required.
Stores the instruction byte array, a constant pool, and per-instruction line numbers for runtime error reporting and debug output.
Stack-based interpreter with tight dispatch loop. Manages value stack, 64-frame call stack, open upvalue list, and triggers garbage collection.
All heap allocation routed through a single wrapper. Runs mark-and-sweep. Maintains gray worklist and reallocation strategy.
Heap-allocated runtime objects: strings, functions, closures, upvalues. All carry a GC header and participate in the mark graph.
Open-addressing table with 75% load factor. Backs global variable lookup and the string intern pool simultaneously.
// Closures capture variables from // enclosing scope via upvalues. fun makeCounter() { var count = 0; fun increment() { count = count + 1; return count; } return increment; } var counter = makeCounter(); print counter(); print counter(); print counter();
1 2 3 // OP_CLOSE_UPVALUE promotes // `count` to heap when // makeCounter() returns.
// Recursive fibonacci fun fib(n) { if (n < 2) return n; return fib(n-2) + fib(n-1); } print fib(10); print fib(20); // Iterative — O(1) space var a = 0; var b = 1; for (var i = 0; i < 10; i = i+1) { print a; var t = a+b; a=b; b=t; }
55
6765
0 1 1 2 3
5 8 13 21 34
var a = "outer"; var b = "global"; { var a = 42; var c = 100; print a + c; // local shadows outer print b; // still visible } print a; // locals discarded // print c; → runtime error
142 global outer // OP_GET_LOCAL reads by // stack slot index. Scope // exit emits OP_POP for // each local variable.
print "Calculate Hypotenuse"; var a = input("Side A: "); var b = input("Side B: "); // input() returns strings. // Coerced to numbers in arithmetic. var h = sqrt((a*a) + (b*b)); print "Hypotenuse: " + h;
Calculate Hypotenuse Side A: 3 Side B: 4 Hypotenuse: 5 // input() reads via // readline with prompt // display.
// while loop var i = 0; while (i < 5) { print i; i = i + 1; } // C-style for loop for (var n=1; n<=5; n=n+1) { print n * n; } // if / else var age = 20; if (age >= 18) { print "adult"; } else { print "minor"; }
0 1 2 3 4 1 4 9 16 25 adult // for desugars to // while + OP_LOOP // (backward jump).
/* vm.c — add a native */ static Value absNative( int argCount, Value* args ) { double n = AS_NUMBER(args[0]); return NUMBER_VAL(n < 0 ? -n : n); } // Register in initVM() defineNative("abs", absNative);
// now callable in A# print abs(-42); 42 print abs(-3.14); 3.14 // Three lines to expose // any C function as a // first-class built-in.
Returns current time in seconds since the Unix epoch. Wraps C's time(). Use for benchmarking elapsed time between two calls.
Returns the square root of a non-negative number. Wraps C's sqrt() from math.h. Negative input produces a runtime error.
Returns the largest integer less than or equal to n. Handles negatives correctly: floor(-2.3) is -3, not -2.
Displays prompt and reads a line from stdin via readline. Returns a string. Automatically coerced to number in arithmetic context.
| opcode | category | description |
|---|---|---|
| OP_CONSTANT | stack | Push a constant from the chunk's constant pool onto the value stack |
| OP_NIL / OP_TRUE / OP_FALSE | stack | Push literal values directly without a constant pool lookup |
| OP_POP | stack | Discard the top value; emitted after expression statements |
| OP_GET_LOCAL / OP_SET_LOCAL | variable | Read or write a local variable by its stack slot index |
| OP_GET_GLOBAL / OP_SET_GLOBAL | variable | Global variable access via hash table lookup by interned name |
| OP_DEFINE_GLOBAL | variable | Bind a new name in the global hash table on first declaration |
| OP_GET_UPVALUE / OP_SET_UPVALUE | closure | Read or write a captured upvalue inside the current closure |
| OP_CLOSE_UPVALUE | closure | Promote an open upvalue from stack slot to heap allocation on scope exit |
| OP_CLOSURE | closure | Wrap a function object in a closure and capture its upvalue references |
| OP_JUMP | flow | Unconditional forward jump by a 16-bit signed offset |
| OP_JUMP_IF_FALSE | flow | Conditional forward jump; leaves condition on the stack (peek, not pop) |
| OP_LOOP | flow | Unconditional backward jump; the primitive behind while and for loops |
| OP_CALL | flow | Invoke a closure, function, or native with a given argument count |
| OP_RETURN | flow | Return from current frame; closes any remaining open upvalues |
| OP_ADD / OP_SUBTRACT / OP_MULTIPLY / OP_DIVIDE | arithmetic | Pop two operands, compute, push result. OP_ADD also concatenates strings |
| OP_EQUAL / OP_GREATER / OP_LESS | arithmetic | Comparison operators; interned strings compare by pointer in O(1) |
| OP_NOT / OP_NEGATE | arithmetic | Unary logical and numeric negation on top-of-stack value |
| OP_PRINT | i/o | Pop and print the top-of-stack value followed by a newline to stdout |
// Uncomment in common.h #define DEBUG_TRACE_EXECUTION // prints stack + instruction // before each opcode dispatch #define DEBUG_PRINT_CODE // disassembles every compiled // chunk after compilation
A-Sharp/ ├── main.c entry point, REPL ├── scanner.{c,h} tokenizer ├── compiler.{c,h} Pratt parser + codegen ├── vm.{c,h} bytecode interpreter ├── chunk.{c,h} instruction storage ├── value.{c,h} runtime values ├── object.{c,h} heap objects ├── memory.{c,h} allocator + GC ├── table.{c,h} hash table ├── debug.{c,h} disassembler ├── Makefile └── test.as sample programs