rename crates/ to engrams/; add el-compiler el package with bootstrap artifact

- crates/ → engrams/ (Rust engrams live here)
- el-compiler/ added: el self-hosting compiler as an el package
  - src/{compiler,lexer,parser,codegen}.el
  - bootstrap/el-compiler.elc (114KB, Rust-compiled seed)
- el.toml Cargo.toml workspace paths updated
- neuron-rs cross-repo path deps fixed (were pointing to products/ instead of foundation/)
This commit is contained in:
Will Anderson
2026-04-29 03:27:32 -05:00
parent 19ed2721ee
commit a42429012e
120 changed files with 3836 additions and 64 deletions
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "el-compiler"
version = "0.1.0"
description = "el self-hosting compiler — lexer, parser, codegen"
edition = "2026"
[build]
entry = "src/compiler.el"
+719
View File
@@ -0,0 +1,719 @@
// codegen.el el self-hosting bytecode code generator
//
// Input: list of AST statement maps (from parser.el)
// Output: JSON string encoding an array of bytecode instructions
//
// Bytecode JSON format matches the Rust serde output exactly.
// See the el-compiler/src/bytecode.rs for the canonical enum.
//
// Entry point: fn codegen(stmts: [Map<String, Any>], source: String) -> String
// JSON helpers
// Escape a string for JSON embedding.
fn json_escape(s: String) -> String {
let chars: [String] = native_string_chars(s)
let total: Int = native_list_len(chars)
let out = ""
let i = 0
while i < total {
let ch: String = native_list_get(chars, i)
if ch == "\"" {
let out = out + "\\\""
} else {
if ch == "\\" {
let out = out + "\\\\"
} else {
if ch == "\n" {
let out = out + "\\n"
} else {
if ch == "\r" {
let out = out + "\\r"
} else {
if ch == "\t" {
let out = out + "\\t"
} else {
let out = out + ch
}
}
}
}
}
let i = i + 1
}
out
}
fn json_str(s: String) -> String {
"\"" + json_escape(s) + "\""
}
fn json_int(n: Int) -> String {
native_int_to_str(n)
}
fn json_bool(b: Bool) -> String {
if b { return "true" }
"false"
}
// Codegen state
//
// el has no mutable globals. We thread a "ctx" map through all codegen
// functions. The context holds:
// "instrs" -> [String] JSON strings for each emitted instruction
// "patches" -> [Map<String,Any>] pending forward-jump patches
// each patch: { "idx": Int, "kind": String }
// (kind is "JumpIfNot" / "Jump" we store the instruction index and
// patch it at the end of the construct)
fn ctx_new() -> Map<String, Any> {
{ "instrs": native_list_empty(), "patches": native_list_empty() }
}
fn ctx_emit(ctx: Map<String, Any>, instr_json: String) -> Map<String, Any> {
let instrs = ctx["instrs"]
let instrs = native_list_append(instrs, instr_json)
{ "instrs": instrs, "patches": ctx["patches"] }
}
fn ctx_len(ctx: Map<String, Any>) -> Int {
let instrs = ctx["instrs"]
native_list_len(instrs)
}
// Patch a previously-emitted placeholder instruction at index idx.
// For Jump/JumpIf/JumpIfNot, we need to replace the entry in instrs.
// We rebuild the list with the patched value at position idx.
fn ctx_patch(ctx: Map<String, Any>, idx: Int, dest: Int) -> Map<String, Any> {
// offset = dest - (idx + 1)
let offset = dest - (idx + 1)
let instrs = ctx["instrs"]
let total = native_list_len(instrs)
let new_instrs: [String] = native_list_empty()
let i = 0
while i < total {
let instr: String = native_list_get(instrs, i)
if i == idx {
// Replace: determine kind from original instruction string
if native_string_contains(instr, "JumpIfNot") {
let new_instrs = native_list_append(new_instrs, "{\"JumpIfNot\":" + json_int(offset) + "}")
} else {
if native_string_contains(instr, "JumpIf") {
let new_instrs = native_list_append(new_instrs, "{\"JumpIf\":" + json_int(offset) + "}")
} else {
let new_instrs = native_list_append(new_instrs, "{\"Jump\":" + json_int(offset) + "}")
}
}
} else {
let new_instrs = native_list_append(new_instrs, instr)
}
let i = i + 1
}
{ "instrs": new_instrs, "patches": ctx["patches"] }
}
// Instruction emitters
fn emit_halt(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"Halt\"")
}
fn emit_nop(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"Nop\"")
}
fn emit_pop(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"Pop\"")
}
fn emit_return(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"Return\"")
}
fn emit_add(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"Add\"")
}
fn emit_sub(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"Sub\"")
}
fn emit_mul(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"Mul\"")
}
fn emit_div(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"Div\"")
}
fn emit_eq(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"Eq\"")
}
fn emit_not_eq(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"NotEq\"")
}
fn emit_lt(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"Lt\"")
}
fn emit_gt(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"Gt\"")
}
fn emit_lt_eq(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"LtEq\"")
}
fn emit_gt_eq(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"GtEq\"")
}
fn emit_and(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"And\"")
}
fn emit_or(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"Or\"")
}
fn emit_not(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"Not\"")
}
fn emit_get_index(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "\"GetIndex\"")
}
fn emit_push_nil(ctx: Map<String, Any>) -> Map<String, Any> {
ctx_emit(ctx, "{\"Push\":\"Nil\"}")
}
fn emit_push_int(ctx: Map<String, Any>, n: Int) -> Map<String, Any> {
ctx_emit(ctx, "{\"Push\":{\"Int\":" + json_int(n) + "}}")
}
fn emit_push_bool(ctx: Map<String, Any>, b: Bool) -> Map<String, Any> {
ctx_emit(ctx, "{\"Push\":{\"Bool\":" + json_bool(b) + "}}")
}
fn emit_push_str(ctx: Map<String, Any>, s: String) -> Map<String, Any> {
ctx_emit(ctx, "{\"Push\":{\"Str\":" + json_str(s) + "}}")
}
fn emit_load(ctx: Map<String, Any>, name: String) -> Map<String, Any> {
ctx_emit(ctx, "{\"LoadLocal\":" + json_str(name) + "}")
}
fn emit_store(ctx: Map<String, Any>, name: String) -> Map<String, Any> {
ctx_emit(ctx, "{\"StoreLocal\":" + json_str(name) + "}")
}
fn emit_call(ctx: Map<String, Any>, name: String, arity: Int) -> Map<String, Any> {
ctx_emit(ctx, "{\"Call\":{\"name\":" + json_str(name) + ",\"arity\":" + json_int(arity) + "}}")
}
fn emit_get_field(ctx: Map<String, Any>, field: String) -> Map<String, Any> {
ctx_emit(ctx, "{\"GetField\":" + json_str(field) + "}")
}
fn emit_build_map(ctx: Map<String, Any>, n: Int) -> Map<String, Any> {
ctx_emit(ctx, "{\"BuildMap\":" + json_int(n) + "}")
}
fn emit_build_list(ctx: Map<String, Any>, n: Int) -> Map<String, Any> {
ctx_emit(ctx, "{\"BuildList\":" + json_int(n) + "}")
}
// Emit a placeholder Jump and return its index (for later patching)
fn emit_jump_placeholder(ctx: Map<String, Any>) -> Map<String, Any> {
let idx = ctx_len(ctx)
let ctx = ctx_emit(ctx, "{\"Jump\":0}")
{ "ctx": ctx, "idx": idx }
}
fn emit_jump_if_not_placeholder(ctx: Map<String, Any>) -> Map<String, Any> {
let idx = ctx_len(ctx)
let ctx = ctx_emit(ctx, "{\"JumpIfNot\":0}")
{ "ctx": ctx, "idx": idx }
}
fn emit_jump_to(ctx: Map<String, Any>, dest: Int) -> Map<String, Any> {
let here = ctx_len(ctx)
let offset = dest - (here + 1)
ctx_emit(ctx, "{\"Jump\":" + json_int(offset) + "}")
}
// Expression codegen
fn cg_expr(ctx: Map<String, Any>, expr: Map<String, Any>) -> Map<String, Any> {
let kind: String = expr["expr"]
if kind == "Int" {
let v: String = expr["value"]
let n: Int = native_str_to_int(v)
return emit_push_int(ctx, n)
}
if kind == "Float" {
let v: String = expr["value"]
// Push as string for now VM handles float parsing from Str
return emit_push_str(ctx, v)
}
if kind == "Str" {
let v: String = expr["value"]
return emit_push_str(ctx, v)
}
if kind == "Bool" {
let v: String = expr["value"]
if v == "true" {
return emit_push_bool(ctx, true)
}
return emit_push_bool(ctx, false)
}
if kind == "Nil" {
return emit_push_nil(ctx)
}
if kind == "Ident" {
let name: String = expr["name"]
return emit_load(ctx, name)
}
if kind == "Not" {
let inner = expr["inner"]
let ctx = cg_expr(ctx, inner)
return emit_not(ctx)
}
if kind == "Neg" {
// unary minus: push 0, push inner, sub
let ctx = emit_push_int(ctx, 0)
let inner = expr["inner"]
let ctx = cg_expr(ctx, inner)
return emit_sub(ctx)
}
if kind == "BinOp" {
let op: String = expr["op"]
let left = expr["left"]
let right = expr["right"]
let ctx = cg_expr(ctx, left)
let ctx = cg_expr(ctx, right)
if op == "Plus" { return emit_add(ctx) }
if op == "Minus" { return emit_sub(ctx) }
if op == "Star" { return emit_mul(ctx) }
if op == "Slash" { return emit_div(ctx) }
if op == "EqEq" { return emit_eq(ctx) }
if op == "NotEq" { return emit_not_eq(ctx) }
if op == "Lt" { return emit_lt(ctx) }
if op == "Gt" { return emit_gt(ctx) }
if op == "LtEq" { return emit_lt_eq(ctx) }
if op == "GtEq" { return emit_gt_eq(ctx) }
if op == "And" { return emit_and(ctx) }
if op == "Or" { return emit_or(ctx) }
return ctx
}
if kind == "Call" {
let func = expr["func"]
let args = expr["args"]
let arity: Int = native_list_len(args)
// push args left-to-right
let i = 0
while i < arity {
let arg = native_list_get(args, i)
let ctx = cg_expr(ctx, arg)
let i = i + 1
}
// get function name from func expr
let func_kind: String = func["expr"]
if func_kind == "Ident" {
let fn_name: String = func["name"]
return emit_call(ctx, fn_name, arity)
}
if func_kind == "Field" {
let obj = func["object"]
let field: String = func["field"]
let ctx = cg_expr(ctx, obj)
return emit_call(ctx, field, arity + 1)
}
return emit_call(ctx, "__dynamic__", arity)
}
if kind == "Field" {
let obj = expr["object"]
let field: String = expr["field"]
let ctx = cg_expr(ctx, obj)
return emit_get_field(ctx, field)
}
if kind == "Index" {
let obj = expr["object"]
let idx = expr["index"]
let ctx = cg_expr(ctx, obj)
let ctx = cg_expr(ctx, idx)
return emit_get_index(ctx)
}
if kind == "Array" {
let elems = expr["elems"]
let n: Int = native_list_len(elems)
let i = 0
while i < n {
let elem = native_list_get(elems, i)
let ctx = cg_expr(ctx, elem)
let i = i + 1
}
return emit_build_list(ctx, n)
}
if kind == "Map" {
let pairs = expr["pairs"]
let n: Int = native_list_len(pairs)
let i = 0
while i < n {
let pair = native_list_get(pairs, i)
let key: String = pair["key"]
let val = pair["value"]
let ctx = emit_push_str(ctx, key)
let ctx = cg_expr(ctx, val)
let i = i + 1
}
return emit_build_map(ctx, n)
}
if kind == "If" {
let cond = expr["cond"]
let then_stmts = expr["then"]
let else_stmts = expr["else"]
let has_else: Bool = expr["has_else"]
// cond
let ctx = cg_expr(ctx, cond)
// JumpIfNot placeholder
let r = emit_jump_if_not_placeholder(ctx)
let ctx = r["ctx"]
let jump_false_idx: Int = r["idx"]
// then body
let ctx = cg_stmts(ctx, then_stmts)
if has_else {
// jump over else
let r2 = emit_jump_placeholder(ctx)
let ctx = r2["ctx"]
let jump_end_idx: Int = r2["idx"]
// patch jump_false to here
let else_start = ctx_len(ctx)
let ctx = ctx_patch(ctx, jump_false_idx, else_start)
// else body
let ctx = cg_stmts(ctx, else_stmts)
// patch jump_end to here
let after_else = ctx_len(ctx)
let ctx = ctx_patch(ctx, jump_end_idx, after_else)
return ctx
} else {
let after_then = ctx_len(ctx)
let ctx = ctx_patch(ctx, jump_false_idx, after_then)
return ctx
}
}
if kind == "Match" {
let subject = expr["subject"]
let arms = expr["arms"]
let n_arms: Int = native_list_len(arms)
let ctx = cg_expr(ctx, subject)
// store subject in temp var
let ctx = emit_store(ctx, "__match_subj__")
let end_jump_idxs: [Int] = native_list_empty()
let i = 0
while i < n_arms {
let arm = native_list_get(arms, i)
let pattern = arm["pattern"]
let body = arm["body"]
let pat_kind: String = pattern["pattern"]
if pat_kind == "Wildcard" {
// always matches just emit body
let ctx = cg_expr(ctx, body)
let r = emit_jump_placeholder(ctx)
let ctx = r["ctx"]
let jidx: Int = r["idx"]
let end_jump_idxs = native_list_append(end_jump_idxs, jidx)
} else {
if pat_kind == "Binding" {
let bind_name: String = pattern["name"]
let ctx = emit_load(ctx, "__match_subj__")
let ctx = emit_store(ctx, bind_name)
let ctx = cg_expr(ctx, body)
let r = emit_jump_placeholder(ctx)
let ctx = r["ctx"]
let jidx: Int = r["idx"]
let end_jump_idxs = native_list_append(end_jump_idxs, jidx)
} else {
// literal pattern: compare subject to literal
let ctx = emit_load(ctx, "__match_subj__")
if pat_kind == "LitInt" {
let v: String = pattern["value"]
let n: Int = native_str_to_int(v)
let ctx = emit_push_int(ctx, n)
} else {
if pat_kind == "LitStr" {
let v: String = pattern["value"]
let ctx = emit_push_str(ctx, v)
} else {
if pat_kind == "LitBool" {
let v: String = pattern["value"]
if v == "true" {
let ctx = emit_push_bool(ctx, true)
} else {
let ctx = emit_push_bool(ctx, false)
}
} else {
let ctx = emit_push_nil(ctx)
}
}
}
let ctx = emit_eq(ctx)
let r = emit_jump_if_not_placeholder(ctx)
let ctx = r["ctx"]
let no_match_idx: Int = r["idx"]
let ctx = cg_expr(ctx, body)
let r2 = emit_jump_placeholder(ctx)
let ctx = r2["ctx"]
let jidx: Int = r2["idx"]
let end_jump_idxs = native_list_append(end_jump_idxs, jidx)
let next_arm = ctx_len(ctx)
let ctx = ctx_patch(ctx, no_match_idx, next_arm)
}
}
let i = i + 1
}
// default: push nil
let ctx = emit_push_nil(ctx)
let end_pos = ctx_len(ctx)
// patch all end jumps
let n_end: Int = native_list_len(end_jump_idxs)
let j = 0
while j < n_end {
let jidx: Int = native_list_get(end_jump_idxs, j)
let ctx = ctx_patch(ctx, jidx, end_pos)
let j = j + 1
}
return ctx
}
if kind == "For" {
// for item in list { body }
// Implementation:
// __for_list__ = list
// __for_len__ = len(list)
// __for_i__ = 0
// loop_start:
// if __for_i__ >= __for_len__ goto done
// item = __for_list__[__for_i__]
// body
// __for_i__ = __for_i__ + 1
// goto loop_start
// done:
let item: String = expr["item"]
let list_expr = expr["list"]
let body = expr["body"]
// emit list, store it
let ctx = cg_expr(ctx, list_expr)
let ctx = emit_store(ctx, "__for_list__")
// compute length
let ctx = emit_load(ctx, "__for_list__")
let ctx = emit_call(ctx, "native_list_len", 1)
let ctx = emit_store(ctx, "__for_len__")
// init counter
let ctx = emit_push_int(ctx, 0)
let ctx = emit_store(ctx, "__for_i__")
// loop start
let loop_start = ctx_len(ctx)
// condition: __for_i__ < __for_len__
let ctx = emit_load(ctx, "__for_i__")
let ctx = emit_load(ctx, "__for_len__")
let ctx = emit_lt(ctx)
let r = emit_jump_if_not_placeholder(ctx)
let ctx = r["ctx"]
let to_done_idx: Int = r["idx"]
// get current element
let ctx = emit_load(ctx, "__for_list__")
let ctx = emit_load(ctx, "__for_i__")
let ctx = emit_get_index(ctx)
let ctx = emit_store(ctx, item)
// body
let ctx = cg_stmts(ctx, body)
// increment counter
let ctx = emit_load(ctx, "__for_i__")
let ctx = emit_push_int(ctx, 1)
let ctx = emit_add(ctx)
let ctx = emit_store(ctx, "__for_i__")
// jump back
let ctx = emit_jump_to(ctx, loop_start)
// patch done
let done_pos = ctx_len(ctx)
let ctx = ctx_patch(ctx, to_done_idx, done_pos)
return ctx
}
if kind == "Try" {
// Just emit the inner expression error propagation handled at runtime
let inner = expr["inner"]
return cg_expr(ctx, inner)
}
// Fallback
emit_push_nil(ctx)
}
// Statement codegen
fn cg_stmt(ctx: Map<String, Any>, stmt: Map<String, Any>) -> Map<String, Any> {
let kind: String = stmt["stmt"]
if kind == "Let" {
let name: String = stmt["name"]
let val = stmt["value"]
let ctx = cg_expr(ctx, val)
return emit_store(ctx, name)
}
if kind == "Return" {
let val = stmt["value"]
let ctx = cg_expr(ctx, val)
return emit_return(ctx)
}
if kind == "FnDef" {
let fn_name: String = stmt["name"]
let params = stmt["params"]
let body = stmt["body"]
let n_params: Int = native_list_len(params)
// emit a Jump to skip over the function body
let r = emit_jump_placeholder(ctx)
let ctx = r["ctx"]
let skip_jump_idx: Int = r["idx"]
// function body entry: store params in reverse order (caller pushes LR, so pop RL)
let pi = n_params - 1
while pi >= 0 {
let param = native_list_get(params, pi)
let pname: String = param["name"]
let ctx = emit_store(ctx, pname)
let pi = pi - 1
}
// emit body statements
let ctx = cg_stmts(ctx, body)
// implicit nil return
let ctx = emit_push_nil(ctx)
let ctx = emit_return(ctx)
// patch skip jump
let after_fn = ctx_len(ctx)
let ctx = ctx_patch(ctx, skip_jump_idx, after_fn)
// register function entry point
let entry_ip = skip_jump_idx + 1
let ctx = emit_push_int(ctx, entry_ip)
let ctx = emit_store(ctx, "__fn_" + fn_name)
return ctx
}
if kind == "While" {
let cond = stmt["cond"]
let body = stmt["body"]
let loop_start = ctx_len(ctx)
let ctx = cg_expr(ctx, cond)
let r = emit_jump_if_not_placeholder(ctx)
let ctx = r["ctx"]
let to_done_idx: Int = r["idx"]
let ctx = cg_stmts(ctx, body)
let ctx = emit_jump_to(ctx, loop_start)
let done_pos = ctx_len(ctx)
let ctx = ctx_patch(ctx, to_done_idx, done_pos)
return ctx
}
if kind == "For" {
// Desugar for-loop as expression codegen
let item: String = stmt["item"]
let list_expr = stmt["list"]
let body = stmt["body"]
let for_expr = { "expr": "For", "item": item, "list": list_expr, "body": body }
return cg_expr(ctx, for_expr)
}
if kind == "Expr" {
let val = stmt["value"]
let val_kind: String = val["expr"]
let ctx = cg_expr(ctx, val)
// Discard result unless it's a control-flow expression
if val_kind == "If" {
return ctx
}
if val_kind == "For" {
return ctx
}
if val_kind == "Match" {
return ctx
}
return emit_pop(ctx)
}
if kind == "TypeDef" {
// compile-time only; no runtime code
return ctx
}
if kind == "EnumDef" {
// compile-time only; no runtime code
return ctx
}
if kind == "Import" {
// handled at a higher level; skip
return ctx
}
ctx
}
fn cg_stmts(ctx: Map<String, Any>, stmts: [Map<String, Any>]) -> Map<String, Any> {
let n: Int = native_list_len(stmts)
let i = 0
while i < n {
let stmt = native_list_get(stmts, i)
let ctx = cg_stmt(ctx, stmt)
let i = i + 1
}
ctx
}
// JSON serialisation
fn instrs_to_json(instrs: [String]) -> String {
let n: Int = native_list_len(instrs)
let out = "["
let i = 0
while i < n {
let instr: String = native_list_get(instrs, i)
if i > 0 {
let out = out + ","
}
let out = out + instr
let i = i + 1
}
out + "]"
}
// Entry point
fn codegen(stmts: [Map<String, Any>], source: String) -> String {
let ctx = ctx_new()
let ctx = cg_stmts(ctx, stmts)
let ctx = emit_halt(ctx)
let instrs = ctx["instrs"]
instrs_to_json(instrs)
}
+19
View File
@@ -0,0 +1,19 @@
// compiler.el el self-hosting compiler pipeline
//
// Wires lexer -> parser -> codegen into a single compile() function.
// This is the bootstrap entry point: compiled once by the Rust el-compiler,
// then self-hosted from that point forward.
//
// The returned JSON is an array of bytecode instruction objects matching
// the serde serialisation format of el-compiler's Bytecode enum.
import "lexer.el"
import "parser.el"
import "codegen.el"
// compile full pipeline: source string -> JSON bytecode string
fn compile(source: String) -> String {
let tokens: [Map<String, Any>] = lex(source)
let stmts: [Map<String, Any>] = parse(tokens)
codegen(stmts, source)
}
+517
View File
@@ -0,0 +1,517 @@
// lexer.el el self-hosting lexer
//
// Tokenises an el source string into a list of token maps.
// Each token is a Map<String, Any> with keys:
// "kind" -> String (e.g. "Int", "Ident", "Plus")
// "value" -> String (the raw text of the token)
//
// Entry point: fn lex(source: String) -> [Map<String, Any>]
// Character helpers
fn is_digit(ch: String) -> Bool {
if ch == "0" { return true }
if ch == "1" { return true }
if ch == "2" { return true }
if ch == "3" { return true }
if ch == "4" { return true }
if ch == "5" { return true }
if ch == "6" { return true }
if ch == "7" { return true }
if ch == "8" { return true }
if ch == "9" { return true }
false
}
fn is_alpha(ch: String) -> Bool {
if ch == "a" { return true }
if ch == "b" { return true }
if ch == "c" { return true }
if ch == "d" { return true }
if ch == "e" { return true }
if ch == "f" { return true }
if ch == "g" { return true }
if ch == "h" { return true }
if ch == "i" { return true }
if ch == "j" { return true }
if ch == "k" { return true }
if ch == "l" { return true }
if ch == "m" { return true }
if ch == "n" { return true }
if ch == "o" { return true }
if ch == "p" { return true }
if ch == "q" { return true }
if ch == "r" { return true }
if ch == "s" { return true }
if ch == "t" { return true }
if ch == "u" { return true }
if ch == "v" { return true }
if ch == "w" { return true }
if ch == "x" { return true }
if ch == "y" { return true }
if ch == "z" { return true }
if ch == "A" { return true }
if ch == "B" { return true }
if ch == "C" { return true }
if ch == "D" { return true }
if ch == "E" { return true }
if ch == "F" { return true }
if ch == "G" { return true }
if ch == "H" { return true }
if ch == "I" { return true }
if ch == "J" { return true }
if ch == "K" { return true }
if ch == "L" { return true }
if ch == "M" { return true }
if ch == "N" { return true }
if ch == "O" { return true }
if ch == "P" { return true }
if ch == "Q" { return true }
if ch == "R" { return true }
if ch == "S" { return true }
if ch == "T" { return true }
if ch == "U" { return true }
if ch == "V" { return true }
if ch == "W" { return true }
if ch == "X" { return true }
if ch == "Y" { return true }
if ch == "Z" { return true }
false
}
fn is_alnum_or_underscore(ch: String) -> Bool {
if is_digit(ch) { return true }
if is_alpha(ch) { return true }
if ch == "_" { return true }
false
}
fn is_whitespace(ch: String) -> Bool {
if ch == " " { return true }
if ch == "\t" { return true }
if ch == "\n" { return true }
if ch == "\r" { return true }
false
}
fn make_tok(kind: String, value: String) -> Map<String, Any> {
{ "kind": kind, "value": value }
}
// Keyword lookup
fn keyword_kind(word: String) -> String {
if word == "let" { return "Let" }
if word == "fn" { return "Fn" }
if word == "type" { return "Type" }
if word == "enum" { return "Enum" }
if word == "match" { return "Match" }
if word == "return" { return "Return" }
if word == "if" { return "If" }
if word == "else" { return "Else" }
if word == "for" { return "For" }
if word == "in" { return "In" }
if word == "while" { return "While" }
if word == "import" { return "Import" }
if word == "from" { return "From" }
if word == "as" { return "As" }
if word == "with" { return "With" }
if word == "sealed" { return "Sealed" }
if word == "activate" { return "Activate" }
if word == "where" { return "Where" }
if word == "test" { return "Test" }
if word == "seed" { return "Seed" }
if word == "assert" { return "Assert" }
if word == "protocol" { return "Protocol" }
if word == "impl" { return "Impl" }
if word == "retry" { return "Retry" }
if word == "times" { return "Times" }
if word == "fallback" { return "Fallback" }
if word == "reason" { return "Reason" }
if word == "parallel" { return "Parallel" }
if word == "trace" { return "Trace" }
if word == "requires" { return "Requires" }
if word == "deploy" { return "Deploy" }
if word == "to" { return "To" }
if word == "via" { return "Via" }
if word == "target" { return "Target" }
if word == "true" { return "Bool" }
if word == "false" { return "Bool" }
""
}
// Scan helpers
// scan_digits advance i while chars[i] is a digit, return { "text": ..., "pos": i }
fn scan_digits(chars: [String], start: Int, total: Int) -> Map<String, Any> {
let i = start
let text = ""
let running = true
while running {
if i >= total {
let running = false
} else {
let ch = native_list_get(chars, i)
if is_digit(ch) {
let text = text + ch
let i = i + 1
} else {
let running = false
}
}
}
{ "text": text, "pos": i }
}
// scan_ident advance i while chars[i] is alphanumeric or underscore
fn scan_ident(chars: [String], start: Int, total: Int) -> Map<String, Any> {
let i = start
let text = ""
let running = true
while running {
if i >= total {
let running = false
} else {
let ch = native_list_get(chars, i)
if is_alnum_or_underscore(ch) {
let text = text + ch
let i = i + 1
} else {
let running = false
}
}
}
{ "text": text, "pos": i }
}
// scan_string scan a quoted string literal, handling \" escapes.
// Starts AFTER the opening quote. Returns { "text": content, "pos": i_after_close }
fn scan_string(chars: [String], start: Int, total: Int) -> Map<String, Any> {
let i = start
let text = ""
let closed = false
let running = true
while running {
if i >= total {
let running = false
} else {
let ch = native_list_get(chars, i)
if ch == "\\" {
// escape: peek next char
let next_i = i + 1
if next_i < total {
let next_ch = native_list_get(chars, next_i)
if next_ch == "\"" {
let text = text + "\""
let i = next_i + 1
} else {
if next_ch == "n" {
let text = text + "\n"
let i = next_i + 1
} else {
if next_ch == "t" {
let text = text + "\t"
let i = next_i + 1
} else {
if next_ch == "\\" {
let text = text + "\\"
let i = next_i + 1
} else {
let text = text + next_ch
let i = next_i + 1
}
}
}
}
} else {
let i = i + 1
}
} else {
if ch == "\"" {
let closed = true
let i = i + 1
let running = false
} else {
let text = text + ch
let i = i + 1
}
}
}
}
{ "text": text, "pos": i }
}
// Main lexer
fn lex(source: String) -> [Map<String, Any>] {
let chars: [String] = native_string_chars(source)
let total: Int = native_list_len(chars)
let tokens: [Map<String, Any>] = native_list_empty()
let i: Int = 0
while i < total {
let ch: String = native_list_get(chars, i)
// Skip whitespace
if is_whitespace(ch) {
let i = i + 1
} else {
// Line comments: //
if ch == "/" {
let next_i = i + 1
if next_i < total {
let next_ch: String = native_list_get(chars, next_i)
if next_ch == "/" {
// skip to end of line
let i = i + 2
let running2 = true
while running2 {
if i >= total {
let running2 = false
} else {
let lch: String = native_list_get(chars, i)
if lch == "\n" {
let running2 = false
} else {
let i = i + 1
}
}
}
} else {
let tokens = native_list_append(tokens, make_tok("Slash", "/"))
let i = i + 1
}
} else {
let tokens = native_list_append(tokens, make_tok("Slash", "/"))
let i = i + 1
}
} else {
// String literal
if ch == "\"" {
let result = scan_string(chars, i + 1, total)
let str_text: String = result["text"]
let new_pos: Int = result["pos"]
let tokens = native_list_append(tokens, make_tok("Str", str_text))
let i = new_pos
} else {
// Number literal
if is_digit(ch) {
let result = scan_digits(chars, i, total)
let num_text: String = result["text"]
let new_pos: Int = result["pos"]
// check for float (dot followed by digit)
if new_pos < total {
let dot_ch: String = native_list_get(chars, new_pos)
if dot_ch == "." {
let after_dot = new_pos + 1
if after_dot < total {
let after_dot_ch: String = native_list_get(chars, after_dot)
if is_digit(after_dot_ch) {
let frac_result = scan_digits(chars, after_dot, total)
let frac_text: String = frac_result["text"]
let frac_pos: Int = frac_result["pos"]
let tokens = native_list_append(tokens, make_tok("Float", num_text + "." + frac_text))
let i = frac_pos
} else {
let tokens = native_list_append(tokens, make_tok("Int", num_text))
let i = new_pos
}
} else {
let tokens = native_list_append(tokens, make_tok("Int", num_text))
let i = new_pos
}
} else {
let tokens = native_list_append(tokens, make_tok("Int", num_text))
let i = new_pos
}
} else {
let tokens = native_list_append(tokens, make_tok("Int", num_text))
let i = new_pos
}
} else {
// Identifier or keyword
if is_alpha(ch) || ch == "_" {
let result = scan_ident(chars, i, total)
let word: String = result["text"]
let new_pos: Int = result["pos"]
let kw = keyword_kind(word)
if kw == "" {
let tokens = native_list_append(tokens, make_tok("Ident", word))
} else {
let tokens = native_list_append(tokens, make_tok(kw, word))
}
let i = new_pos
} else {
// Multi-char and single-char operators/delimiters
let peek_i = i + 1
let peek_ch = ""
if peek_i < total {
let peek_ch = native_list_get(chars, peek_i)
}
if ch == "=" {
if peek_ch == "=" {
let tokens = native_list_append(tokens, make_tok("EqEq", "=="))
let i = i + 2
} else {
if peek_ch == ">" {
let tokens = native_list_append(tokens, make_tok("FatArrow", "=>"))
let i = i + 2
} else {
let tokens = native_list_append(tokens, make_tok("Eq", "="))
let i = i + 1
}
}
} else {
if ch == "!" {
if peek_ch == "=" {
let tokens = native_list_append(tokens, make_tok("NotEq", "!="))
let i = i + 2
} else {
let tokens = native_list_append(tokens, make_tok("Not", "!"))
let i = i + 1
}
} else {
if ch == "<" {
if peek_ch == "=" {
let tokens = native_list_append(tokens, make_tok("LtEq", "<="))
let i = i + 2
} else {
let tokens = native_list_append(tokens, make_tok("Lt", "<"))
let i = i + 1
}
} else {
if ch == ">" {
if peek_ch == "=" {
let tokens = native_list_append(tokens, make_tok("GtEq", ">="))
let i = i + 2
} else {
let tokens = native_list_append(tokens, make_tok("Gt", ">"))
let i = i + 1
}
} else {
if ch == "&" {
if peek_ch == "&" {
let tokens = native_list_append(tokens, make_tok("And", "&&"))
let i = i + 2
} else {
let i = i + 1
}
} else {
if ch == "|" {
if peek_ch == "|" {
let tokens = native_list_append(tokens, make_tok("Or", "||"))
let i = i + 2
} else {
if peek_ch == ">" {
let tokens = native_list_append(tokens, make_tok("PipeOp", "|>"))
let i = i + 2
} else {
let tokens = native_list_append(tokens, make_tok("Pipe", "|"))
let i = i + 1
}
}
} else {
if ch == "-" {
if peek_ch == ">" {
let tokens = native_list_append(tokens, make_tok("Arrow", "->"))
let i = i + 2
} else {
let tokens = native_list_append(tokens, make_tok("Minus", "-"))
let i = i + 1
}
} else {
if ch == ":" {
if peek_ch == ":" {
let tokens = native_list_append(tokens, make_tok("ColonColon", "::"))
let i = i + 2
} else {
let tokens = native_list_append(tokens, make_tok("Colon", ":"))
let i = i + 1
}
} else {
if ch == "+" {
let tokens = native_list_append(tokens, make_tok("Plus", "+"))
let i = i + 1
} else {
if ch == "*" {
let tokens = native_list_append(tokens, make_tok("Star", "*"))
let i = i + 1
} else {
if ch == "(" {
let tokens = native_list_append(tokens, make_tok("LParen", "("))
let i = i + 1
} else {
if ch == ")" {
let tokens = native_list_append(tokens, make_tok("RParen", ")"))
let i = i + 1
} else {
if ch == "{" {
let tokens = native_list_append(tokens, make_tok("LBrace", "{"))
let i = i + 1
} else {
if ch == "}" {
let tokens = native_list_append(tokens, make_tok("RBrace", "}"))
let i = i + 1
} else {
if ch == "[" {
let tokens = native_list_append(tokens, make_tok("LBracket", "["))
let i = i + 1
} else {
if ch == "]" {
let tokens = native_list_append(tokens, make_tok("RBracket", "]"))
let i = i + 1
} else {
if ch == "," {
let tokens = native_list_append(tokens, make_tok("Comma", ","))
let i = i + 1
} else {
if ch == "." {
let tokens = native_list_append(tokens, make_tok("Dot", "."))
let i = i + 1
} else {
if ch == ";" {
let tokens = native_list_append(tokens, make_tok("Semicolon", ";"))
let i = i + 1
} else {
if ch == "@" {
let tokens = native_list_append(tokens, make_tok("At", "@"))
let i = i + 1
} else {
if ch == "?" {
let tokens = native_list_append(tokens, make_tok("QuestionMark", "?"))
let i = i + 1
} else {
// unknown char skip
let i = i + 1
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
let tokens = native_list_append(tokens, make_tok("Eof", ""))
tokens
}
+759
View File
@@ -0,0 +1,759 @@
// parser.el el self-hosting recursive descent parser
//
// Consumes the token list produced by lexer.el and builds a list of AST
// statement maps. Each statement and expression is a Map<String, Any>.
//
// The cursor (integer position into the token list) is threaded through every
// parse function. Functions return { "node": <map>, "pos": <int> }.
//
// Entry point: fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>]
// Token access helpers
fn tok_at(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
native_list_get(tokens, pos)
}
fn tok_kind(tokens: [Map<String, Any>], pos: Int) -> String {
let t = tok_at(tokens, pos)
t["kind"]
}
fn tok_value(tokens: [Map<String, Any>], pos: Int) -> String {
let t = tok_at(tokens, pos)
t["value"]
}
fn expect(tokens: [Map<String, Any>], pos: Int, kind: String) -> Int {
let k = tok_kind(tokens, pos)
if k == kind {
return pos + 1
}
// On mismatch just advance; error recovery is best-effort
pos + 1
}
// Result helpers
fn make_result(node: Map<String, Any>, pos: Int) -> Map<String, Any> {
{ "node": node, "pos": pos }
}
// Type annotation parser
// Skips over a type annotation, returning the new position.
// Types can be: Ident, [Type], Map<K,V>, Type?, Type<Type,...>
fn skip_type(tokens: [Map<String, Any>], pos: Int) -> Int {
let k = tok_kind(tokens, pos)
// Array type: [Type]
if k == "LBracket" {
let p = pos + 1
let p = skip_type(tokens, p)
let p = expect(tokens, p, "RBracket")
return p
}
// Named type (possibly generic)
if k == "Ident" {
let p = pos + 1
let k2 = tok_kind(tokens, p)
if k2 == "Lt" {
// Generic params: skip until matching >
let p = p + 1
let depth = 1
let running = true
while running {
let kk = tok_kind(tokens, p)
if kk == "Eof" {
let running = false
} else {
if kk == "Lt" {
let depth = depth + 1
let p = p + 1
} else {
if kk == "Gt" {
let depth = depth - 1
let p = p + 1
if depth <= 0 {
let running = false
}
} else {
let p = p + 1
}
}
}
}
let k3 = tok_kind(tokens, p)
if k3 == "QuestionMark" {
let p = p + 1
}
return p
}
// Optional marker
if k2 == "QuestionMark" {
return p + 1
}
return p
}
pos + 1
}
// Parameter list
// Parses (name: Type, name: Type, ...) returns { "params": [...], "pos": ... }
fn parse_params(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let p = expect(tokens, pos, "LParen")
let params: [Map<String, Any>] = native_list_empty()
let running = true
while running {
let k = tok_kind(tokens, p)
if k == "RParen" {
let running = false
} else {
if k == "Eof" {
let running = false
} else {
// param name
let pname = tok_value(tokens, p)
let p = p + 1
let p = expect(tokens, p, "Colon")
let p = skip_type(tokens, p)
let param = { "name": pname }
let params = native_list_append(params, param)
let k2 = tok_kind(tokens, p)
if k2 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(tokens, p, "RParen")
{ "params": params, "pos": p }
}
// Expression parsing
fn parse_primary(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let k = tok_kind(tokens, pos)
let v = tok_value(tokens, pos)
// Integer literal
if k == "Int" {
return make_result({ "expr": "Int", "value": v }, pos + 1)
}
// Float literal
if k == "Float" {
return make_result({ "expr": "Float", "value": v }, pos + 1)
}
// String literal
if k == "Str" {
return make_result({ "expr": "Str", "value": v }, pos + 1)
}
// Bool literal
if k == "Bool" {
return make_result({ "expr": "Bool", "value": v }, pos + 1)
}
// Identifier
if k == "Ident" {
return make_result({ "expr": "Ident", "name": v }, pos + 1)
}
// Grouped expression
if k == "LParen" {
let r = parse_expr(tokens, pos + 1)
let node = r["node"]
let p = r["pos"]
let p = expect(tokens, p, "RParen")
return make_result(node, p)
}
// Array literal: [e1, e2, ...]
if k == "LBracket" {
let p = pos + 1
let elems: [Map<String, Any>] = native_list_empty()
let running = true
while running {
let k2 = tok_kind(tokens, p)
if k2 == "RBracket" {
let running = false
} else {
if k2 == "Eof" {
let running = false
} else {
let r = parse_expr(tokens, p)
let elem = r["node"]
let p = r["pos"]
let elems = native_list_append(elems, elem)
let k3 = tok_kind(tokens, p)
if k3 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(tokens, p, "RBracket")
return make_result({ "expr": "Array", "elems": elems }, p)
}
// Map literal: { "key": val, ... }
if k == "LBrace" {
let p = pos + 1
let pairs: [Map<String, Any>] = native_list_empty()
let running = true
while running {
let k2 = tok_kind(tokens, p)
if k2 == "RBrace" {
let running = false
} else {
if k2 == "Eof" {
let running = false
} else {
// key: Str token
let key = tok_value(tokens, p)
let p = p + 1
let p = expect(tokens, p, "Colon")
let r = parse_expr(tokens, p)
let val_node = r["node"]
let p = r["pos"]
let pair = { "key": key, "value": val_node }
let pairs = native_list_append(pairs, pair)
let k3 = tok_kind(tokens, p)
if k3 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(tokens, p, "RBrace")
return make_result({ "expr": "Map", "pairs": pairs }, p)
}
// if expression
if k == "If" {
let r = parse_if(tokens, pos)
return r
}
// match expression
if k == "Match" {
let r = parse_match(tokens, pos)
return r
}
// for expression (used as statement)
if k == "For" {
let r = parse_for_expr(tokens, pos)
return r
}
// Unary not
if k == "Not" {
let r = parse_primary(tokens, pos + 1)
let inner = r["node"]
let p = r["pos"]
return make_result({ "expr": "Not", "inner": inner }, p)
}
// Unary minus
if k == "Minus" {
let r = parse_primary(tokens, pos + 1)
let inner = r["node"]
let p = r["pos"]
return make_result({ "expr": "Neg", "inner": inner }, p)
}
// Fallback: skip unknown token
make_result({ "expr": "Nil" }, pos + 1)
}
fn parse_if(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let p = expect(tokens, pos, "If")
let r = parse_expr(tokens, p)
let cond = r["node"]
let p = r["pos"]
let r2 = parse_block(tokens, p)
let then_stmts = r2["stmts"]
let p = r2["pos"]
let has_else = false
let else_stmts: [Map<String, Any>] = native_list_empty()
let k2 = tok_kind(tokens, p)
if k2 == "Else" {
let p = p + 1
let k3 = tok_kind(tokens, p)
if k3 == "If" {
// else-if chain: parse as nested if
let r3 = parse_if(tokens, p)
let nested = r3["node"]
let p = r3["pos"]
let else_stmts = native_list_append(else_stmts, { "stmt": "Expr", "value": nested })
let has_else = true
} else {
let r3 = parse_block(tokens, p)
let else_stmts = r3["stmts"]
let p = r3["pos"]
let has_else = true
}
}
make_result({ "expr": "If", "cond": cond, "then": then_stmts, "else": else_stmts, "has_else": has_else }, p)
}
fn parse_match(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let p = expect(tokens, pos, "Match")
let r = parse_expr(tokens, p)
let subject = r["node"]
let p = r["pos"]
let p = expect(tokens, p, "LBrace")
let arms: [Map<String, Any>] = native_list_empty()
let running = true
while running {
let k = tok_kind(tokens, p)
if k == "RBrace" {
let running = false
} else {
if k == "Eof" {
let running = false
} else {
// parse pattern => body
let r2 = parse_pattern(tokens, p)
let pattern = r2["node"]
let p = r2["pos"]
let p = expect(tokens, p, "FatArrow")
let r3 = parse_expr(tokens, p)
let body = r3["node"]
let p = r3["pos"]
let arm = { "pattern": pattern, "body": body }
let arms = native_list_append(arms, arm)
let k2 = tok_kind(tokens, p)
if k2 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(tokens, p, "RBrace")
make_result({ "expr": "Match", "subject": subject, "arms": arms }, p)
}
fn parse_pattern(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let k = tok_kind(tokens, pos)
if k == "Ident" {
let v = tok_value(tokens, pos)
if v == "_" {
return make_result({ "pattern": "Wildcard" }, pos + 1)
}
return make_result({ "pattern": "Binding", "name": v }, pos + 1)
}
if k == "Int" {
return make_result({ "pattern": "LitInt", "value": tok_value(tokens, pos) }, pos + 1)
}
if k == "Str" {
return make_result({ "pattern": "LitStr", "value": tok_value(tokens, pos) }, pos + 1)
}
if k == "Bool" {
return make_result({ "pattern": "LitBool", "value": tok_value(tokens, pos) }, pos + 1)
}
// Wildcard _
make_result({ "pattern": "Wildcard" }, pos + 1)
}
fn parse_for_expr(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let p = expect(tokens, pos, "For")
let item_name = tok_value(tokens, p)
let p = p + 1
let p = expect(tokens, p, "In")
let r = parse_expr(tokens, p)
let list_expr = r["node"]
let p = r["pos"]
let r2 = parse_block(tokens, p)
let body = r2["stmts"]
let p = r2["pos"]
make_result({ "expr": "For", "item": item_name, "list": list_expr, "body": body }, p)
}
fn parse_block(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let p = expect(tokens, pos, "LBrace")
let stmts: [Map<String, Any>] = native_list_empty()
let running = true
while running {
let k = tok_kind(tokens, p)
if k == "RBrace" {
let running = false
} else {
if k == "Eof" {
let running = false
} else {
let r = parse_stmt(tokens, p)
let stmt = r["node"]
let p = r["pos"]
let stmts = native_list_append(stmts, stmt)
}
}
}
let p = expect(tokens, p, "RBrace")
{ "stmts": stmts, "pos": p }
}
// Postfix expressions (calls, field access, index)
fn parse_postfix(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let r = parse_primary(tokens, pos)
let node = r["node"]
let p = r["pos"]
let running = true
while running {
let k = tok_kind(tokens, p)
if k == "LParen" {
// function call
let p = p + 1
let args: [Map<String, Any>] = native_list_empty()
let run2 = true
while run2 {
let k2 = tok_kind(tokens, p)
if k2 == "RParen" {
let run2 = false
} else {
if k2 == "Eof" {
let run2 = false
} else {
let r2 = parse_expr(tokens, p)
let arg = r2["node"]
let p = r2["pos"]
let args = native_list_append(args, arg)
let k3 = tok_kind(tokens, p)
if k3 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(tokens, p, "RParen")
let node = { "expr": "Call", "func": node, "args": args }
} else {
if k == "Dot" {
let field = tok_value(tokens, p + 1)
let p = p + 2
let node = { "expr": "Field", "object": node, "field": field }
} else {
if k == "LBracket" {
let r2 = parse_expr(tokens, p + 1)
let idx = r2["node"]
let p = r2["pos"]
let p = expect(tokens, p, "RBracket")
let node = { "expr": "Index", "object": node, "index": idx }
} else {
if k == "QuestionMark" {
let p = p + 1
let node = { "expr": "Try", "inner": node }
} else {
let running = false
}
}
}
}
}
make_result(node, p)
}
// Binary expression precedence climbing
fn op_precedence(kind: String) -> Int {
if kind == "Or" { return 1 }
if kind == "And" { return 2 }
if kind == "EqEq" { return 3 }
if kind == "NotEq" { return 3 }
if kind == "Lt" { return 4 }
if kind == "Gt" { return 4 }
if kind == "LtEq" { return 4 }
if kind == "GtEq" { return 4 }
if kind == "Plus" { return 5 }
if kind == "Minus" { return 5 }
if kind == "Star" { return 6 }
if kind == "Slash" { return 6 }
0
}
fn is_binop(kind: String) -> Bool {
if kind == "Or" { return true }
if kind == "And" { return true }
if kind == "EqEq" { return true }
if kind == "NotEq" { return true }
if kind == "Lt" { return true }
if kind == "Gt" { return true }
if kind == "LtEq" { return true }
if kind == "GtEq" { return true }
if kind == "Plus" { return true }
if kind == "Minus" { return true }
if kind == "Star" { return true }
if kind == "Slash" { return true }
false
}
fn parse_binop(tokens: [Map<String, Any>], pos: Int, min_prec: Int) -> Map<String, Any> {
let r = parse_postfix(tokens, pos)
let left = r["node"]
let p = r["pos"]
let running = true
while running {
let k = tok_kind(tokens, p)
let prec = op_precedence(k)
if is_binop(k) {
if prec >= min_prec {
let op = k
let r2 = parse_binop(tokens, p + 1, prec + 1)
let right = r2["node"]
let p = r2["pos"]
let left = { "expr": "BinOp", "op": op, "left": left, "right": right }
} else {
let running = false
}
} else {
let running = false
}
}
make_result(left, p)
}
fn parse_expr(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
parse_binop(tokens, pos, 1)
}
// Statement parsing
fn parse_stmt(tokens: [Map<String, Any>], pos: Int) -> Map<String, Any> {
let k = tok_kind(tokens, pos)
// let binding
if k == "Let" {
let p = pos + 1
let name = tok_value(tokens, p)
let p = p + 1
let k2 = tok_kind(tokens, p)
// optional type annotation: name: Type
if k2 == "Colon" {
let p = p + 1
let p = skip_type(tokens, p)
}
let p = expect(tokens, p, "Eq")
let r = parse_expr(tokens, p)
let val = r["node"]
let p = r["pos"]
return make_result({ "stmt": "Let", "name": name, "value": val }, p)
}
// return statement
if k == "Return" {
let p = pos + 1
let k2 = tok_kind(tokens, p)
if k2 == "RBrace" {
return make_result({ "stmt": "Return", "value": { "expr": "Nil" } }, p)
}
if k2 == "Eof" {
return make_result({ "stmt": "Return", "value": { "expr": "Nil" } }, p)
}
let r = parse_expr(tokens, p)
let val = r["node"]
let p = r["pos"]
return make_result({ "stmt": "Return", "value": val }, p)
}
// fn definition
if k == "Fn" {
let p = pos + 1
let name = tok_value(tokens, p)
let p = p + 1
let r = parse_params(tokens, p)
let params = r["params"]
let p = r["pos"]
// return type annotation: -> Type
let k2 = tok_kind(tokens, p)
if k2 == "Arrow" {
let p = p + 1
let p = skip_type(tokens, p)
}
let r2 = parse_block(tokens, p)
let body = r2["stmts"]
let p = r2["pos"]
return make_result({ "stmt": "FnDef", "name": name, "params": params, "body": body }, p)
}
// type definition
if k == "Type" {
let p = pos + 1
let name = tok_value(tokens, p)
let p = p + 1
let p = expect(tokens, p, "LBrace")
let fields: [Map<String, Any>] = native_list_empty()
let running = true
while running {
let k2 = tok_kind(tokens, p)
if k2 == "RBrace" {
let running = false
} else {
if k2 == "Eof" {
let running = false
} else {
let fname = tok_value(tokens, p)
let p = p + 1
let p = expect(tokens, p, "Colon")
let p = skip_type(tokens, p)
let fields = native_list_append(fields, { "name": fname })
let k3 = tok_kind(tokens, p)
if k3 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(tokens, p, "RBrace")
return make_result({ "stmt": "TypeDef", "name": name, "fields": fields }, p)
}
// enum definition
if k == "Enum" {
let p = pos + 1
let name = tok_value(tokens, p)
let p = p + 1
let p = expect(tokens, p, "LBrace")
let variants: [Map<String, Any>] = native_list_empty()
let running = true
while running {
let k2 = tok_kind(tokens, p)
if k2 == "RBrace" {
let running = false
} else {
if k2 == "Eof" {
let running = false
} else {
let vname = tok_value(tokens, p)
let p = p + 1
let variants = native_list_append(variants, { "name": vname })
let k3 = tok_kind(tokens, p)
if k3 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(tokens, p, "RBrace")
return make_result({ "stmt": "EnumDef", "name": name, "variants": variants }, p)
}
// import statement
if k == "Import" {
let p = pos + 1
let path = tok_value(tokens, p)
let p = p + 1
return make_result({ "stmt": "Import", "path": path }, p)
}
// from ... import { ... }
if k == "From" {
let p = pos + 1
let module_name = tok_value(tokens, p)
let p = p + 1
// skip "import" keyword
let k2 = tok_kind(tokens, p)
if k2 == "Import" {
let p = p + 1
}
// skip { Name, ... }
let k3 = tok_kind(tokens, p)
if k3 == "LBrace" {
let p = p + 1
let running = true
while running {
let k4 = tok_kind(tokens, p)
if k4 == "RBrace" {
let running = false
} else {
if k4 == "Eof" {
let running = false
} else {
let p = p + 1
let k5 = tok_kind(tokens, p)
if k5 == "Comma" {
let p = p + 1
}
}
}
}
let p = expect(tokens, p, "RBrace")
}
return make_result({ "stmt": "Import", "path": module_name }, p)
}
// while loop
if k == "While" {
let p = pos + 1
let r = parse_expr(tokens, p)
let cond = r["node"]
let p = r["pos"]
let r2 = parse_block(tokens, p)
let body = r2["stmts"]
let p = r2["pos"]
return make_result({ "stmt": "While", "cond": cond, "body": body }, p)
}
// for loop
if k == "For" {
let p = pos + 1
let item_name = tok_value(tokens, p)
let p = p + 1
let p = expect(tokens, p, "In")
let r = parse_expr(tokens, p)
let list_expr = r["node"]
let p = r["pos"]
let r2 = parse_block(tokens, p)
let body = r2["stmts"]
let p = r2["pos"]
return make_result({ "stmt": "For", "item": item_name, "list": list_expr, "body": body }, p)
}
// @decorator skip and parse next stmt
if k == "At" {
let p = pos + 1
// skip decorator name
let p = p + 1
return parse_stmt(tokens, p)
}
// bare expression or if/match statement
let r = parse_expr(tokens, pos)
let val = r["node"]
let p = r["pos"]
make_result({ "stmt": "Expr", "value": val }, p)
}
// Top-level parse
fn parse(tokens: [Map<String, Any>]) -> [Map<String, Any>] {
let total: Int = native_list_len(tokens)
let stmts: [Map<String, Any>] = native_list_empty()
let pos: Int = 0
let running = true
while running {
if pos >= total {
let running = false
} else {
let k = tok_kind(tokens, pos)
if k == "Eof" {
let running = false
} else {
let r = parse_stmt(tokens, pos)
let stmt = r["node"]
let new_pos: Int = r["pos"]
let stmts = native_list_append(stmts, stmt)
// Guard against infinite loops if pos didn't advance, force it
if new_pos <= pos {
let pos = pos + 1
} else {
let pos = new_pos
}
}
}
}
stmts
}