ReefVM/GUIDE.md
2025-10-05 22:40:22 -07:00

7.0 KiB

Reef Compiler Guide

Quick reference for compiling to Reef bytecode.

Bytecode Syntax

Instructions

OPCODE operand     ; comment

Labels

.label_name:       ; Define label
JUMP .label_name   ; Reference label

Constants

  • Numbers: PUSH 42, PUSH 3.14
  • Strings: PUSH "hello" or PUSH 'world'
  • Booleans: PUSH true, PUSH false
  • Null: PUSH null

Variables

  • Load: LOAD varname
  • Store: STORE varname

Functions

MAKE_FUNCTION (x y) .body       ; Basic
MAKE_FUNCTION (x=10 y=20) .body ; Defaults
MAKE_FUNCTION (x ...rest) .body ; Variadic
MAKE_FUNCTION (x @named) .body  ; Named args
MAKE_FUNCTION (x ...rest @named) .body ; Both

Function Calls

Stack order (bottom to top):

LOAD fn
PUSH arg1           ; Positional args
PUSH arg2
PUSH "name"         ; Named arg key
PUSH "value"        ; Named arg value
PUSH 2              ; Positional count
PUSH 1              ; Named count
CALL

Opcodes

Stack

  • PUSH <const> - Push constant
  • POP - Remove top
  • DUP - Duplicate top

Variables

  • LOAD <name> - Push variable value
  • STORE <name> - Pop and store in variable

Arithmetic

  • ADD, SUB, MUL, DIV, MOD - Binary ops (pop 2, push result)

Comparison

  • EQ, NEQ, LT, GT, LTE, GTE - Pop 2, push boolean

Logic

  • NOT - Pop 1, push !value

Control Flow

  • JUMP .label - Unconditional jump
  • JUMP_IF_FALSE .label - Jump if top is false or null (pops value)
  • JUMP_IF_TRUE .label - Jump if top is truthy (pops value)
  • HALT - Stop execution of the program

Functions

  • MAKE_FUNCTION (params) .body - Create function, push to stack
  • CALL - Call function (see calling convention above)
  • TAIL_CALL - Tail-recursive call (no stack growth)
  • RETURN - Return from function (pops return value)
  • BREAK - Exit iterator/loop (unwinds to break target)

Arrays

  • MAKE_ARRAY #N - Pop N items, push array
  • ARRAY_GET - Pop index and array, push element
  • ARRAY_SET - Pop value, index, array; mutate array
  • ARRAY_PUSH - Pop value and array, append to array
  • ARRAY_LEN - Pop array, push length

Dicts

  • MAKE_DICT #N - Pop N key-value pairs, push dict
  • DICT_GET - Pop key and dict, push value (or null)
  • DICT_SET - Pop value, key, dict; mutate dict
  • DICT_HAS - Pop key and dict, push boolean

Exceptions

  • PUSH_TRY .catch - Register exception handler
  • PUSH_FINALLY .finally - Add finally to current handler
  • POP_TRY - Remove handler (try succeeded)
  • THROW - Throw exception (pops error value)

Native

  • CALL_NATIVE <name> - Call registered TypeScript function (consumes entire stack as args)

Compiler Patterns

If-Else

<condition>
JUMP_IF_FALSE .else
  <then-block>
  JUMP .end
.else:
  <else-block>
.end:

While Loop

.loop:
  <condition>
  JUMP_IF_FALSE .end
  <body>
  JUMP .loop
.end:

For Loop

<init>
.loop:
  <condition>
  JUMP_IF_FALSE .end
  <body>
  <increment>
  JUMP .loop
.end:

Continue

No CONTINUE opcode. Use backward jump to loop start:

.loop:
  <condition>
  JUMP_IF_FALSE .end
  <early-check>
  JUMP_IF_TRUE .loop    ; continue
  <body>
  JUMP .loop
.end:

Break in Loop

Mark iterator function as break target, use BREAK opcode:

MAKE_FUNCTION () .each_body
STORE each
LOAD collection
LOAD each
<call-iterator-with-break-semantics>
HALT

.each_body:
  <condition>
  JUMP_IF_TRUE .done
  <body>
  BREAK                  ; exits to caller
.done:
  RETURN

Short-Circuit AND

<left>
DUP
JUMP_IF_FALSE .end      ; Short-circuit if false
POP
<right>
.end:                    ; Result on stack

Short-Circuit OR

<left>
DUP
JUMP_IF_TRUE .end       ; Short-circuit if true
POP
<right>
.end:                    ; Result on stack

Try-Catch

PUSH_TRY .catch
  <try-block>
  POP_TRY
  JUMP .end
.catch:
  STORE err
  <catch-block>
.end:

Try-Catch-Finally

PUSH_TRY .catch
PUSH_FINALLY .finally
  <try-block>
  POP_TRY
  JUMP .finally         ; Compiler must generate this
.catch:
  STORE err
  <catch-block>
  JUMP .finally         ; And this
.finally:
  <finally-block>       ; Executes in both paths
.end:

Important: VM only auto-jumps to finally on THROW. For successful try/catch, compiler must explicitly JUMP to finally.

Closures

Functions automatically capture current scope:

PUSH 0
STORE counter
MAKE_FUNCTION () .increment
RETURN

.increment:
  LOAD counter          ; Captured variable
  PUSH 1
  ADD
  STORE counter
  LOAD counter
  RETURN

Tail Recursion

Use TAIL_CALL instead of CALL for last call:

MAKE_FUNCTION (n acc) .factorial
STORE factorial
<...>

.factorial:
  LOAD n
  PUSH 0
  LTE
  JUMP_IF_FALSE .recurse
  LOAD acc
  RETURN
.recurse:
  LOAD factorial
  LOAD n
  PUSH 1
  SUB
  LOAD n
  LOAD acc
  MUL
  PUSH 2
  PUSH 0
  TAIL_CALL             ; Reuses stack frame

Key Concepts

Truthiness

Only null and false are falsy. Everything else (including 0, "", empty arrays/dicts) is truthy.

Type Coercion

toNumber:

  • number → identity
  • string → parseFloat (or 0 if invalid)
  • boolean → 1 (true) or 0 (false)
  • null → 0
  • Others → 0

toString:

  • string → identity
  • number → string representation
  • boolean → "true" or "false"
  • null → "null"
  • function → ""
  • array → "[item, item]"
  • dict → "{key: value, ...}"

Arithmetic ops (ADD, SUB, MUL, DIV, MOD) coerce both operands to numbers.

Comparison ops (LT, GT, LTE, GTE) coerce both operands to numbers.

Equality ops (EQ, NEQ) use type-aware comparison with deep equality for arrays/dicts.

Note: There is no string concatenation operator. ADD only works with numbers.

Scope

  • Variables resolved through parent scope chain
  • STORE updates existing variable or creates in current scope
  • Functions capture scope at definition time

Break Semantics

  • CALL marks current frame as break target
  • BREAK unwinds call stack to that target
  • Used for Ruby-style iterator pattern

Parameter Binding Priority

For function calls, parameters bound in order:

  1. Positional argument (if provided)
  2. Named argument (if provided and matches param name)
  3. Default value (if defined)
  4. Null

Exception Handlers

  • PUSH_TRY uses absolute addresses for catch blocks
  • Nested try blocks form a stack
  • THROW unwinds to most recent handler and jumps to finally (if present) or catch
  • VM does NOT automatically jump to finally on success - compiler must generate JUMPs
  • Finally execution in all cases is compiler's responsibility, not VM's

Calling Convention

All calls push arguments in order:

  1. Function
  2. Positional args (in order)
  3. Named args (key1, val1, key2, val2, ...)
  4. Positional count (as number)
  5. Named count (as number)
  6. CALL or TAIL_CALL

CALL_NATIVE Behavior

Unlike CALL, CALL_NATIVE consumes the entire stack as arguments and clears the stack. The native function receives all values that were on the stack at the time of the call.

Empty Stack

  • RETURN with empty stack returns null
  • HALT with empty stack returns null