active development — core runtime stable
A#
Bytecode Virtual Machine & Language

Written in C from first principles.
Stack-based execution. Lexical scoping.
First-class closures. Tri-color GC.
No runtime. No dependencies. No nonsense.

$sudo apt install asharp// recommended
$asharp script.as// run a file
$asharp// launch REPL
asharp  —  interactive session
$ asharp
A# bytecode VM  |  readline enabled
A# fun makeCounter() {
   var count = 0;
   fun increment() {
      count = count + 1;
      return count;
   }
   return increment;
}
A# var c = makeCounter();
A# print c(); 1
A# print c(); 2
A# print c(); 3
A# fun fib(n) { if (n < 2) return n; return fib(n-2)+fib(n-1); }
A# print fib(10); 55
A# print sqrt(floor(64.9)); 8
A#
FEATURES
written in C  |  no external deps
// 01
single_pass compiler

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.

// 02
stack_based VM

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.

// 03
closures + upvalues

Captured variables live on the stack while the enclosing function is active. OP_CLOSE_UPVALUE promotes them to the heap when the scope exits.

// 04
tri_color mark-and-sweep GC

Gray worklist drives collection. bytesAllocated tracked against a dynamic nextGC threshold. Heap grows automatically between collection cycles.

// 05
string interning

All strings deduplicated in a global hash table backed by vm.strings. Equality is O(1) pointer comparison regardless of string length. GC-aware.

// 06
native function API

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.

ARCHITECTURE
compilation pipeline
// step 01
Scanner
scanner.c  scanner.h

Converts raw source text into a flat token stream. Handles identifiers, string literals, numbers, keywords, and all punctuation in a single pass.

// step 02
Compiler
compiler.c  compiler.h

Pratt parser consumes tokens and emits bytecode directly into a chunk. Resolves local variables and captures upvalues inline, no AST required.

// step 03
Chunk
chunk.c  chunk.h

Stores the instruction byte array, a constant pool, and per-instruction line numbers for runtime error reporting and debug output.

// step 04
VM
vm.c  vm.h

Stack-based interpreter with tight dispatch loop. Manages value stack, 64-frame call stack, open upvalue list, and triggers garbage collection.

// support
Memory
memory.c  memory.h

All heap allocation routed through a single wrapper. Runs mark-and-sweep. Maintains gray worklist and reallocation strategy.

// support
Objects
object.c  object.h

Heap-allocated runtime objects: strings, functions, closures, upvalues. All carry a GC header and participate in the mark graph.

// support
Hash Table
table.c  table.h

Open-addressing table with 75% load factor. Backs global variable lookup and the string intern pool simultaneously.

EXAMPLES
// 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();
// OUTPUT
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;
}
// OUTPUT
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
// OUTPUT
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;
// OUTPUT
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"; }
// OUTPUT
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);
// OUTPUT
// 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.
NATIVE FUNCTIONS
built-in reference
clock() → number

Returns current time in seconds since the Unix epoch. Wraps C's time(). Use for benchmarking elapsed time between two calls.

var t = clock(); // 1738612345.678
sqrt(n) → number

Returns the square root of a non-negative number. Wraps C's sqrt() from math.h. Negative input produces a runtime error.

sqrt(64); // 8
floor(n) → number

Returns the largest integer less than or equal to n. Handles negatives correctly: floor(-2.3) is -3, not -2.

floor(-2.3); // -3
input(prompt) → string

Displays prompt and reads a line from stdin via readline. Returns a string. Automatically coerced to number in arithmetic context.

input("Name: "); // string
BYTECODE REFERENCE
instruction set  |  16-bit jump offsets
opcodecategorydescription
OP_CONSTANTstackPush a constant from the chunk's constant pool onto the value stack
OP_NIL / OP_TRUE / OP_FALSEstackPush literal values directly without a constant pool lookup
OP_POPstackDiscard the top value; emitted after expression statements
OP_GET_LOCAL / OP_SET_LOCALvariableRead or write a local variable by its stack slot index
OP_GET_GLOBAL / OP_SET_GLOBALvariableGlobal variable access via hash table lookup by interned name
OP_DEFINE_GLOBALvariableBind a new name in the global hash table on first declaration
OP_GET_UPVALUE / OP_SET_UPVALUEclosureRead or write a captured upvalue inside the current closure
OP_CLOSE_UPVALUEclosurePromote an open upvalue from stack slot to heap allocation on scope exit
OP_CLOSUREclosureWrap a function object in a closure and capture its upvalue references
OP_JUMPflowUnconditional forward jump by a 16-bit signed offset
OP_JUMP_IF_FALSEflowConditional forward jump; leaves condition on the stack (peek, not pop)
OP_LOOPflowUnconditional backward jump; the primitive behind while and for loops
OP_CALLflowInvoke a closure, function, or native with a given argument count
OP_RETURNflowReturn from current frame; closes any remaining open upvalues
OP_ADD / OP_SUBTRACT / OP_MULTIPLY / OP_DIVIDEarithmeticPop two operands, compute, push result. OP_ADD also concatenates strings
OP_EQUAL / OP_GREATER / OP_LESSarithmeticComparison operators; interned strings compare by pointer in O(1)
OP_NOT / OP_NEGATEarithmeticUnary logical and numeric negation on top-of-stack value
OP_PRINTi/oPop and print the top-of-stack value followed by a newline to stdout
INSTALL
debian / ubuntu / wsl / macos
APT  / Debian · Ubuntu · WSL  (recommended)
$echo "deb [trusted=yes] https://aadesh006.github.io/A-Sharp/ ./" | sudo tee /etc/apt/sources.list.d/asharp.list
adds the package repository
$sudo apt update
$sudo apt install asharp
installs asharp binary to /usr/bin
$asharp  script.as
or just `asharp` to open the REPL
SOURCE  / any Linux
$sudo apt install build-essential libreadline-dev
$git clone https://github.com/aadesh006/A-Sharp.git && cd A-Sharp
$make
outputs ./asharp in the project root
MACOS  / Homebrew
$brew install readline
$export CPPFLAGS="-I/opt/homebrew/opt/readline/include"
$export LDFLAGS="-L/opt/homebrew/opt/readline/lib"
$git clone https://github.com/aadesh006/A-Sharp.git && cd A-Sharp && make
DEBUG BUILD  / common.h flags
// 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
$make CFLAGS="-g -O0"
build with debug symbols for gdb / lldb
$./asharp test.as
run the bundled sample programs
PROJECT STRUCTURE
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