Compile to Bytecode

hard · compilers, bytecode, lowering

Compile to Bytecode

Lower a Program AST into stack-based bytecode.

This is the compiler step that produces a separate executable artifact. The VM will consume this bytecode later.

Function signature

func Compile(prog *Program) Bytecode

Bytecode types

Use the provided types:

  • Bytecode with Main []Instr and Functions []Function
  • Function with Name, Params, and Code []Instr
  • Instr with Op, Int, and Str

Int is used for numeric literals, jump targets, and arity. Str is used for names and string literals.

Instruction rules

Literals / variables

  • number: PUSH_NUM <int>
  • string: PUSH_STR <string>
  • bool: PUSH_BOOL <0|1>
  • nil: PUSH_NIL
  • identifier: LOAD <name>

Unary / binary

  • unary -: NEG
  • unary !: NOT
  • + - * / == != < <= > >= map to their matching opcodes

Statements

  • let / var: evaluate init (or push nil), then DEFINE_VAR <name>
  • const: evaluate init, then DEFINE_CONST <name>
  • assignment: evaluate expr, then STORE <name>
  • expression statement: evaluate expr, then POP
  • block: ENTER_SCOPE, compile statements, EXIT_SCOPE
  • return: evaluate expr (or push nil), then RETURN

Control flow

  • if:
    • compile condition
    • JUMP_IF_FALSE <else>
    • compile then-block
    • JUMP <end>
    • compile else-block (if any)
  • while:
    • loop start label
    • compile condition
    • JUMP_IF_FALSE <end>
    • compile body
    • JUMP <start>

Jumps use absolute instruction indexes in the current code slice.

Functions

  • Each function declaration becomes a Function in Bytecode.Functions.
  • Function bodies are compiled like statement lists (no extra scope wrapper).
  • If the last instruction is not RETURN, append PUSH_NIL + RETURN.
  • Top-level statements compile into Bytecode.Main.

Notes

  • Assume the AST is already valid.
  • Keep the compiler readable and straightforward.
Run tests to see results
No issues detected