Phase 6f: let = immutable, var = mutable (binding-only)
Bindings now signal mutability the way Rust/Swift do, instead of `let` meaning
"local" and `var` meaning "module-level":
- `let x = e` -> immutable binding; a later `x = …` is a compile error
(`cannot assign to immutable 'x' … use var`).
- `var x = e` -> mutable binding, at local OR module scope (position decides
scope; the keyword decides mutability).
- `const` -> unchanged (compile-time).
Immutability is of the *binding*, not the object: `let n = new Node; n.kind = 1`
is fine (mutation through the reference); only rebinding `n` is rejected. The
check lives in emit_assign — a direct `name =` whose target is a `let` local
(loc_mut == 0) errors; field/index targets and `var`/param/loop bindings are
unaffected.
Delivered as three reseeds so the self-hosting compiler never had to compile
source its own rules would reject:
A) add `var` as a local statement + per-local mutability tracking (loc_mut),
no enforcement;
B) migrate every reassigned `let` -> `var` across the compiler, runtime and
examples (337 declarations), driven by a per-function, string/comment-aware
scan (binding targets only, never `x.f =` / `x[i] =`);
C) turn on the check. bootstrap-cfree (compiler vs its own source) and every
golden build (which splices the runtime) then proved zero reassigned `let`
was missed anywhere.
Also folded in: removed leftover debug instrumentation in block() (a `cur=` /
print_int(777…) trace on the separator-error path) and fixed parse.ludic's stale
header comment (no more `struct`). Reseeded (21711 lines); C-free fixpoint holds;
goldens identical; 17/17; vocab + doc-fences clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
573813609c
commit
e1d7797e29
38 changed files with 2909 additions and 2778 deletions
|
|
@ -22,7 +22,7 @@ fn buf_putc(b: Buf, c: int) -> void {
|
|||
b.len = b.len + 1
|
||||
}
|
||||
fn buf_puts(b: Buf, s: ptr) -> void {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while peek8(s, i) != 0 { buf_putc(b, peek8(s, i)); i = i + 1 }
|
||||
}
|
||||
fn buf_puti(b: Buf, n: int) -> void { buf_puts(b, itoa(n)) }
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ var ll_str: int = 0 # string-constant counter
|
|||
var loc_name: []ptr
|
||||
var loc_reg: []ptr
|
||||
var loc_ty: []ptr
|
||||
var loc_mut: []int # 1 = mutable (var / param / loop-var), 0 = immutable (let)
|
||||
var nloc: int = 0
|
||||
|
||||
# loop targets for break/continue (innermost last)
|
||||
|
|
@ -45,9 +46,9 @@ fn emit_alloca(llt: ptr) -> ptr {
|
|||
fn sconcat(a: ptr, b: ptr) -> ptr {
|
||||
let la = slen(a); let lb = slen(b)
|
||||
let out = mem_alloc(la + lb + 1)
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < la { poke8(out, i, peek8(a, i)); i = i + 1 }
|
||||
let j = 0
|
||||
var j = 0
|
||||
while j < lb { poke8(out, la + j, peek8(b, j)); j = j + 1 }
|
||||
poke8(out, la + lb, 0)
|
||||
return out
|
||||
|
|
@ -67,12 +68,12 @@ fn is_slice_ty(t: ptr) -> bool { return peek8(t, 0) == 91 and peek8(t, 1) == 93
|
|||
fn slice_elem(t: ptr) -> ptr { return substr(t, 2, slen(t) - 2) }
|
||||
|
||||
fn find_arch(name: ptr) -> Node {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) { let d = prog[i]; if d.kind == N_ARCH and streq(d.s, name) { return d }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
fn find_comp(name: ptr) -> Node {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) { let d = prog[i]; if d.kind == N_COMP and streq(d.s, name) { return d }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
|
|
@ -82,19 +83,19 @@ fn layout_node(name: ptr) -> Node { return find_comp(name) }
|
|||
fn layout_ty(name: ptr) -> ptr { return sconcat("%Cmp_", name) }
|
||||
|
||||
fn field_index(s: Node, fname: ptr) -> int {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(s.kids) { if streq(s.kids[i].s, fname) { return i }; i = i + 1 }
|
||||
return 0 - 1
|
||||
}
|
||||
fn field_type(s: Node, fname: ptr) -> ptr {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(s.kids) { if streq(s.kids[i].s, fname) { return s.kids[i].ty }; i = i + 1 }
|
||||
return "int"
|
||||
}
|
||||
|
||||
# find a global var/const by name
|
||||
fn find_global(name: ptr) -> Node {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_VAR and streq(d.s, name) { return d }
|
||||
|
|
@ -107,11 +108,11 @@ fn find_global(name: ptr) -> Node {
|
|||
# `Enum.Variant` -> the variant's ordinal (its index), or -1 if `ename` names no
|
||||
# enum with that variant. Enum names live in `prog` like any other declaration.
|
||||
fn enum_ordinal(ename: ptr, vname: ptr) -> int {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_ENUM and streq(d.s, ename) {
|
||||
let j = 0
|
||||
var j = 0
|
||||
while j < len(d.kids) { if streq(d.kids[j].s, vname) { return j }; j = j + 1 }
|
||||
}
|
||||
i = i + 1
|
||||
|
|
@ -119,7 +120,7 @@ fn enum_ordinal(ename: ptr, vname: ptr) -> int {
|
|||
return 0 - 1
|
||||
}
|
||||
fn find_fn(name: ptr) -> Node {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) { let d = prog[i]; if d.kind == N_FN and streq(d.s, name) { return d }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
|
|
@ -136,7 +137,7 @@ fn register_computed(prop: ptr, field: ptr, ty: ptr, e: Node) -> void {
|
|||
fn computed_expr(prop: ptr, field: ptr) -> Node {
|
||||
if ptr_is_null(prop) { return ptr_null() }
|
||||
let key = sconcat(prop, sconcat(".", field))
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(g_computed) { if streq(g_computed[i].s, key) { return g_computed[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
|
|
@ -155,7 +156,7 @@ fn register_onspawn(model: ptr, body: Node) -> void {
|
|||
let n = node(N_BLOCK); n.s = model; n.a = body; push(g_onspawn, n)
|
||||
}
|
||||
fn onspawn_body(model: ptr) -> Node {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(g_onspawn) { if streq(g_onspawn[i].s, model) { return g_onspawn[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
|
|
@ -170,7 +171,7 @@ fn register_ondespawn(model: ptr, body: Node) -> void {
|
|||
let n = node(N_BLOCK); n.s = model; n.a = body; push(g_ondespawn, n)
|
||||
}
|
||||
fn ondespawn_body(model: ptr) -> Node {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(g_ondespawn) { if streq(g_ondespawn[i].s, model) { return g_ondespawn[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
|
|
@ -178,7 +179,7 @@ fn register_onattach(prop: ptr, body: Node) -> void {
|
|||
let n = node(N_BLOCK); n.s = prop; n.a = body; push(g_onattach, n)
|
||||
}
|
||||
fn onattach_body(prop: ptr) -> Node {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(g_onattach) { if streq(g_onattach[i].s, prop) { return g_onattach[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
|
|
@ -190,12 +191,12 @@ var g_ondisable: []Node
|
|||
fn register_onenable(prop: ptr, body: Node) -> void { let n = node(N_BLOCK); n.s = prop; n.a = body; push(g_onenable, n) }
|
||||
fn register_ondisable(prop: ptr, body: Node) -> void { let n = node(N_BLOCK); n.s = prop; n.a = body; push(g_ondisable, n) }
|
||||
fn onenable_body(prop: ptr) -> Node {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(g_onenable) { if streq(g_onenable[i].s, prop) { return g_onenable[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
fn ondisable_body(prop: ptr) -> Node {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(g_ondisable) { if streq(g_ondisable[i].s, prop) { return g_ondisable[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
|
|
@ -205,13 +206,17 @@ fn is_model(name: ptr) -> bool { return find_arch_id(name) > 0 }
|
|||
|
||||
# local variable environment
|
||||
fn loc_reset() -> void { nloc = 0 }
|
||||
# push a local. Defaults to mutable (params, loop and query bindings are all
|
||||
# reassignable/rebindable); a `let` binding marks its slot immutable afterward
|
||||
# via loc_set_mut, so a later `name = …` can be rejected.
|
||||
fn loc_push(name: ptr, r: ptr, ty: ptr) -> void {
|
||||
if nloc < len(loc_name) { loc_name[nloc] = name; loc_reg[nloc] = r; loc_ty[nloc] = ty }
|
||||
else { push(loc_name, name); push(loc_reg, r); push(loc_ty, ty) }
|
||||
if nloc < len(loc_name) { loc_name[nloc] = name; loc_reg[nloc] = r; loc_ty[nloc] = ty; loc_mut[nloc] = 1 }
|
||||
else { push(loc_name, name); push(loc_reg, r); push(loc_ty, ty); push(loc_mut, 1) }
|
||||
nloc = nloc + 1
|
||||
}
|
||||
fn loc_set_mut(m: int) -> void { if nloc > 0 { loc_mut[nloc - 1] = m } }
|
||||
fn loc_find(name: ptr) -> int {
|
||||
let i = nloc - 1
|
||||
var i = nloc - 1
|
||||
while i >= 0 { if streq(loc_name[i], name) { return i }; i = i - 1 }
|
||||
return 0 - 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# ahead of it (see emit_alloca). Returns go through a %retval slot + `ret` label.
|
||||
|
||||
fn emit_params_sig(d: Node) -> void {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(d.kids) {
|
||||
if i > 0 { emit(", ") }
|
||||
emit(llty(d.kids[i].ty)); emit(" %arg_"); emit(d.kids[i].s)
|
||||
|
|
@ -21,7 +21,7 @@ fn emit_fn(d: Node) -> void {
|
|||
let rl = llty(ret_ty)
|
||||
if not streq(rl, "void") { buf_puts(falloc, " %retval = alloca "); buf_puts(falloc, rl); buf_puts(falloc, "\n") }
|
||||
# params: store each incoming argument into a stack slot
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(d.kids) {
|
||||
let p = d.kids[i]
|
||||
let slot = emit_alloca(llty(p.ty))
|
||||
|
|
@ -67,13 +67,13 @@ fn emit_main(d: Node) -> void {
|
|||
fn emit_program() -> void {
|
||||
head = buf_new()
|
||||
code = buf_new()
|
||||
loc_name = new []ptr; loc_reg = new []ptr; loc_ty = new []ptr
|
||||
loc_name = new []ptr; loc_reg = new []ptr; loc_ty = new []ptr; loc_mut = new []int
|
||||
brk_lbl = new []ptr; cnt_lbl = new []ptr
|
||||
self_stk = new []ptr
|
||||
mach_stk = new []Node
|
||||
emit_header()
|
||||
if has_ecs() { emit_ecs_storage() }
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_FN { emit_fn(prog[i]) }; i = i + 1 }
|
||||
if has_ecs() { emit_ecs_allocator(); emit_snapshot() }
|
||||
if has_ui() { emit_ui_build() }
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@
|
|||
const MAX_ENT: int = 1024
|
||||
|
||||
fn has_systems() -> bool {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_SYS { return true }; i = i + 1 }
|
||||
return false
|
||||
}
|
||||
fn has_models() -> bool {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_ARCH { return true }; i = i + 1 }
|
||||
return false
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ fn emit_ecs_storage() -> void {
|
|||
emith(sconcat("@L_kind = internal global [", sconcat(me, " x i32] zeroinitializer\n")))
|
||||
emith(sconcat("@L_freelist = internal global [", sconcat(me, " x i32] zeroinitializer\n")))
|
||||
emith("@L_freen = internal global i32 0\n")
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
let c = prog[i]
|
||||
# per-entity storage for a property (its %Cmp_ layout is emitted in the
|
||||
|
|
@ -52,7 +52,7 @@ fn emit_ecs_storage() -> void {
|
|||
fn emit_ecs_allocator() -> void {
|
||||
let me = itoa(MAX_ENT)
|
||||
emit("define void @L_reset(i32 %e) {\nentry:\n")
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_COMP {
|
||||
let hn = sconcat("%h", itoa(i))
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ fn emit_bin(e: Node) -> Val {
|
|||
let b = emit_expr(e.b)
|
||||
let fx = streq(a.ty, "fixed") or streq(b.ty, "fixed")
|
||||
if is_cmp(e.s) {
|
||||
let ac = a.code; let bc = b.code
|
||||
var ac = a.code; var bc = b.code
|
||||
if fx { ac = to_fixed(a); bc = to_fixed(b) }
|
||||
let c = emit_bind(sconcat("icmp ", sconcat(cmp_code(e.s), sconcat(" i32 ", sconcat(ac, sconcat(", ", bc))))))
|
||||
return val(emit_bind(sconcat("zext i1 ", sconcat(c, " to i32"))), "bool")
|
||||
|
|
@ -100,8 +100,8 @@ fn emit_call(e: Node) -> Val {
|
|||
if is_intrinsic(name) { return emit_intrinsic(name, e) }
|
||||
if is_intrinsic2(name) { return emit_intrinsic2(name, e) }
|
||||
if is_math_builtin(name) { return emit_math_builtin(name, e) }
|
||||
let fn2 = find_fn(name)
|
||||
let cname = name
|
||||
var fn2 = find_fn(name)
|
||||
var cname = name
|
||||
if ptr_is_null(fn2) {
|
||||
# a builtin like clear()/reg() is satisfied by its rt_ function
|
||||
let rtname = sconcat("rt_", name)
|
||||
|
|
@ -112,11 +112,11 @@ fn emit_call(e: Node) -> Val {
|
|||
# evaluate args first (their IR is emitted before the call instruction)
|
||||
let args = new []ptr
|
||||
let atys = new []ptr
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(e.kids) { let v = emit_expr(e.kids[i]); push(args, v.code); push(atys, v.ty); i = i + 1 }
|
||||
let rl = llty(fn2.ty)
|
||||
emit(" ")
|
||||
let rreg = "0"
|
||||
var rreg = "0"
|
||||
if not streq(rl, "void") { rreg = nreg(); emit(rreg); emit(" = ") }
|
||||
emit("call "); emit(rl); emit(" @fn_"); emit(cname); emit("(")
|
||||
i = 0
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ fn emit_system_fn(sys: Node) -> void {
|
|||
}
|
||||
|
||||
fn emit_calls_for_phase(phase: ptr) -> void {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_SYS and streq(d.ty, phase) { # skip a disabled handler
|
||||
|
|
@ -39,7 +39,7 @@ fn emit_calls_for_phase(phase: ptr) -> void {
|
|||
# @OnDespawn(Model) hooks compile to `@on_despawn_<Model>(entity)` functions that
|
||||
# bind the model's properties and run the body — dispatched by kind at `despawn`.
|
||||
fn emit_despawn_hooks() -> void {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(g_ondespawn) {
|
||||
let hk = g_ondespawn[i]
|
||||
let model = find_arch(hk.s)
|
||||
|
|
@ -64,7 +64,7 @@ fn emit_despawn_hooks() -> void {
|
|||
|
||||
fn emit_game_main() -> void {
|
||||
# every system becomes a function first
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_SYS { emit_system_fn(prog[i]) }; i = i + 1 }
|
||||
emit_despawn_hooks()
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ fn emit_str_const(s: ptr) -> ptr {
|
|||
let n = slen(s)
|
||||
emith(name); emith(" = private unnamed_addr constant [")
|
||||
emith(itoa(n + 1)); emith(" x i8] c\"")
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < n {
|
||||
let c = peek8(s, i)
|
||||
if c == 34 or c == 92 or c < 32 or c > 126 {
|
||||
|
|
@ -79,13 +79,13 @@ fn emit_header() -> void {
|
|||
# property layouts — a %Cmp_ record of named fields, emitted here so `new`
|
||||
# works whether or not the program runs the ECS. The per-entity @S_/@H_ arrays
|
||||
# are separate (emit_ecs_storage), emitted only for a program that runs the ECS.
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_COMP {
|
||||
emith(layout_ty(d.s)); emith(" = type { ")
|
||||
if len(d.kids) == 0 { emith("i32") }
|
||||
let f = 0
|
||||
var f = 0
|
||||
while f < len(d.kids) {
|
||||
if f > 0 { emith(", ") }
|
||||
emith(llty(d.kids[f].ty))
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
|
|||
if streq(name, "file_read") or streq(name, "file_write") {
|
||||
let f = arg_code(e, 0); let b = arg_code(e, 1); let n = arg_code(e, 2)
|
||||
let w = emit_bind(sconcat("zext i32 ", sconcat(n, " to i64")))
|
||||
let fn2 = "@fread"
|
||||
var fn2 = "@fread"
|
||||
if streq(name, "file_write") { fn2 = "@fwrite" }
|
||||
let r = emit_bind(sconcat("call i64 ", sconcat(fn2, sconcat("(ptr ", sconcat(b, sconcat(", i64 1, i64 ", sconcat(w, sconcat(", ptr ", sconcat(f, ")")))))))))
|
||||
return val(emit_bind(sconcat("trunc i64 ", sconcat(r, " to i32"))), "int")
|
||||
|
|
@ -117,7 +117,7 @@ fn emit_intrinsic(name: ptr, e: Node) -> Val {
|
|||
if streq(name, "bnot") { let a = arg_code(e, 0); return val(emit_bind(sconcat("xor i32 ", sconcat(a, ", -1"))), "int") }
|
||||
if streq(name,"shl") or streq(name,"shr") or streq(name,"band") or streq(name,"bor") or streq(name,"bxor") {
|
||||
let a = arg_code(e, 0); let b = arg_code(e, 1)
|
||||
let opc = "shl"
|
||||
var opc = "shl"
|
||||
if streq(name,"shr") { opc = "lshr" }
|
||||
if streq(name,"band") { opc = "and" }
|
||||
if streq(name,"bor") { opc = "or" }
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ fn emit_machine(st: Node) -> void {
|
|||
if nmach < len(mach_stk) { mach_stk[nmach] = st } else { push(mach_stk, st) }
|
||||
nmach = nmach + 1
|
||||
let endl = lbl("smend")
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(st.kids) {
|
||||
let state = st.kids[i]
|
||||
let v = emit_expr(state.b)
|
||||
|
|
@ -29,8 +29,8 @@ fn emit_machine(st: Node) -> void {
|
|||
fn emit_become(st: Node) -> void {
|
||||
if nmach == 0 { perr("'become' outside a machine") }
|
||||
let m = mach_stk[nmach - 1]
|
||||
let target: Node = ptr_null()
|
||||
let i = 0
|
||||
var target: Node = ptr_null()
|
||||
var i = 0
|
||||
while i < len(m.kids) { if streq(m.kids[i].s, st.s) { target = m.kids[i] }; i = i + 1 }
|
||||
if ptr_is_null(target) { perr(sconcat("become: no state ", st.s)) }
|
||||
let regv = emit_expr(m.a)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ fn emit_math_builtin(name: ptr, e: Node) -> Val {
|
|||
}
|
||||
if streq(name, "min") or streq(name, "max") {
|
||||
let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1])
|
||||
let op = "slt"
|
||||
var op = "slt"
|
||||
if streq(name, "max") { op = "sgt" }
|
||||
let c = emit_bind(sconcat("icmp ", sconcat(op, sconcat(" i32 ", sconcat(a.code, sconcat(", ", b.code))))))
|
||||
return val(emit_bind(sconcat("select i1 ", sconcat(c, sconcat(", i32 ", sconcat(a.code, sconcat(", i32 ", b.code)))))), "int")
|
||||
|
|
|
|||
|
|
@ -13,14 +13,14 @@ fn emit_new_struct(name: ptr) -> Val {
|
|||
let lty = layout_ty(name)
|
||||
let sz = emit_sizeof(lty)
|
||||
let obj = emit_bind(sconcat("call ptr @malloc(i64 ", sconcat(sz, ")")))
|
||||
let f = 0
|
||||
var f = 0
|
||||
while f < len(s.kids) {
|
||||
let fd = s.kids[f]
|
||||
let addr = nreg()
|
||||
emit(" "); emit(addr); emit(" = getelementptr inbounds "); emit(lty)
|
||||
emit(", ptr "); emit(obj); emit(", i32 0, i32 "); emit(itoa(f)); emit("\n")
|
||||
let lt = llty(fd.ty)
|
||||
let v = "0"
|
||||
var v = "0"
|
||||
if streq(lt, "ptr") { v = "null" }
|
||||
if not ptr_is_null(fd.a) { let dv = emit_expr(fd.a); v = dv.code }
|
||||
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# the body once per match. Mirrors ll_query in compiler/back/ir_ecs.c.
|
||||
|
||||
fn find_arch_id(name: ptr) -> int {
|
||||
let i = 0; let n = 1
|
||||
var i = 0; var n = 1
|
||||
while i < len(prog) { if prog[i].kind == N_ARCH { if streq(prog[i].s, name) { return n }; n = n + 1 }; i = i + 1 }
|
||||
return 0
|
||||
}
|
||||
|
|
@ -34,11 +34,11 @@ fn emit_query(st: Node) -> void {
|
|||
|
||||
let terms = st.c
|
||||
# component / archetype filters
|
||||
let t = 0
|
||||
var t = 0
|
||||
while t < len(terms.kids) {
|
||||
let tm = terms.kids[t]
|
||||
let ak = find_arch_id(tm.s)
|
||||
let ok = "0"
|
||||
var ok = "0"
|
||||
if ak > 0 {
|
||||
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(i1); emit("\n")
|
||||
let kv = emit_bind(sconcat("load i32, ptr ", kp))
|
||||
|
|
@ -59,7 +59,7 @@ fn emit_query(st: Node) -> void {
|
|||
|
||||
# bind the requested components to the loop variables, in order
|
||||
let save = nloc
|
||||
let vi = 0
|
||||
var vi = 0
|
||||
t = 0
|
||||
while t < len(terms.kids) {
|
||||
let tm = terms.kids[t]
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ fn emit_snapshot_blocks(fn2: ptr) -> void {
|
|||
emit_io(fn2, "@L_alive", "%nalive")
|
||||
emit_io(fn2, "@L_freelist", "%nalive")
|
||||
emit_io(fn2, "@L_kind", "%nalive")
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_VAR { emit_io(fn2, sconcat("@g_", prog[i].s), "4") }; i = i + 1 }
|
||||
let ci = 0
|
||||
var ci = 0
|
||||
i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_COMP {
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ fn emit_init_component(e: ptr, comp: ptr, rec: Node) -> void {
|
|||
let slot = nreg()
|
||||
emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(comp); emit("], ptr @S_"); emit(comp); emit(", i32 0, i32 "); emit(e); emit("\n")
|
||||
# defaults
|
||||
let f = 0
|
||||
var f = 0
|
||||
while f < len(c.kids) {
|
||||
let fd = c.kids[f]
|
||||
let addr = nreg()
|
||||
emit(" "); emit(addr); emit(" = getelementptr inbounds %Cmp_"); emit(comp); emit(", ptr "); emit(slot); emit(", i32 0, i32 "); emit(itoa(f)); emit("\n")
|
||||
let lt = llty(fd.ty)
|
||||
let v = "0"
|
||||
var v = "0"
|
||||
if streq(lt, "ptr") { v = "null" }
|
||||
if not ptr_is_null(fd.a) { let dv = emit_expr(fd.a); v = dv.code }
|
||||
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
|
||||
|
|
@ -24,7 +24,7 @@ fn emit_init_component(e: ptr, comp: ptr, rec: Node) -> void {
|
|||
}
|
||||
# per-spawn overrides
|
||||
if not ptr_is_null(rec) {
|
||||
let j = 0
|
||||
var j = 0
|
||||
while j < len(rec.kids) {
|
||||
let fi = rec.kids[j]
|
||||
let fidx = field_index(c, fi.s)
|
||||
|
|
@ -53,7 +53,7 @@ fn emit_init_component(e: ptr, comp: ptr, rec: Node) -> void {
|
|||
# @OnSpawn hook body can address them by name (like a query binding for one entity).
|
||||
fn emit_bind_props(model: Node, e: ptr) -> void {
|
||||
let me = itoa(MAX_ENT)
|
||||
let c = 0
|
||||
var c = 0
|
||||
while c < len(model.kids) {
|
||||
let pname = model.kids[c].s
|
||||
let slot = nreg()
|
||||
|
|
@ -73,11 +73,11 @@ fn emit_spawn(st: Node) -> void {
|
|||
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(e); emit("\n")
|
||||
emit(" store i32 "); emit(itoa(ak)); emit(", ptr "); emit(kp); emit("\n")
|
||||
let arch = find_arch(st.s)
|
||||
let c = 0
|
||||
var c = 0
|
||||
while c < len(arch.kids) {
|
||||
let cn = arch.kids[c].s
|
||||
let rec = ptr_null()
|
||||
let i = 0
|
||||
var rec = ptr_null()
|
||||
var i = 0
|
||||
while i < len(st.kids) { if streq(st.kids[i].s, cn) { rec = st.kids[i].a }; i = i + 1 }
|
||||
emit_init_component(e, cn, rec)
|
||||
c = c + 1
|
||||
|
|
@ -90,7 +90,7 @@ fn emit_spawn(st: Node) -> void {
|
|||
nloc = save
|
||||
}
|
||||
} else {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(st.kids) { emit_init_component(e, st.kids[i].s, st.kids[i].a); i = i + 1 }
|
||||
}
|
||||
}
|
||||
|
|
@ -102,7 +102,7 @@ fn emit_despawn(st: Node) -> void {
|
|||
let me = itoa(MAX_ENT)
|
||||
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(v.code); emit("\n")
|
||||
let kind = emit_bind(sconcat("load i32, ptr ", kp))
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(g_ondespawn) {
|
||||
let mname = g_ondespawn[i].s
|
||||
let c = emit_bind(sconcat("icmp eq i32 ", sconcat(kind, sconcat(", ", itoa(find_arch_id(mname))))))
|
||||
|
|
@ -120,13 +120,13 @@ fn emit_despawn(st: Node) -> void {
|
|||
# data persists, so re-enabling restores it, and queries already skip a cleared
|
||||
# flag). A bare `<Model>` / `<Handler>` flips a global enabled flag.
|
||||
fn emit_toggle(st: Node) -> void {
|
||||
let val = "0"; if st.ival == 1 { val = "1" }
|
||||
var val = "0"; if st.ival == 1 { val = "1" }
|
||||
if not ptr_is_null(st.a) {
|
||||
let ev = emit_expr(st.a)
|
||||
let me = itoa(MAX_ENT)
|
||||
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n")
|
||||
emit(" store i8 "); emit(val); emit(", ptr "); emit(hp); emit("\n")
|
||||
let hb: Node = ptr_null()
|
||||
var hb: Node = ptr_null()
|
||||
if st.ival == 1 { hb = onenable_body(st.s) } else { hb = ondisable_body(st.s) }
|
||||
if not ptr_is_null(hb) {
|
||||
let save = nloc
|
||||
|
|
@ -138,7 +138,7 @@ fn emit_toggle(st: Node) -> void {
|
|||
nloc = save
|
||||
}
|
||||
} else {
|
||||
let g = "@HE_"; if is_model(st.s) { g = "@ME_" } # model vs handler
|
||||
var g = "@HE_"; if is_model(st.s) { g = "@ME_" } # model vs handler
|
||||
emit(" store i32 "); emit(val); emit(", ptr "); emit(g); emit(st.s); emit("\n")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# block is skipped until a new basic block opens.
|
||||
|
||||
fn emit_block(b: Node) -> void {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(b.kids) {
|
||||
if g_term { return }
|
||||
emit_stmt(b.kids[i])
|
||||
|
|
@ -18,11 +18,16 @@ fn store_at(lt: ptr, v: ptr, addr: ptr) -> void {
|
|||
fn emit_assign(st: Node) -> void {
|
||||
# resolve the target's address and type
|
||||
let t = st.a
|
||||
let addr = "0"
|
||||
let ty = "int"
|
||||
var addr = "0"
|
||||
var ty = "int"
|
||||
if t.kind == E_ID {
|
||||
let li = loc_find(t.s)
|
||||
if li >= 0 { addr = loc_reg[li]; ty = loc_ty[li] }
|
||||
if li >= 0 {
|
||||
# a `let` binding is immutable: rebinding it is an error (mutating THROUGH
|
||||
# it — `x.field = …`, `x[i] = …` — is fine, and lands in the branches below).
|
||||
if loc_mut[li] == 0 { perr(sconcat("cannot assign to immutable '", sconcat(t.s, sconcat("' (declared with let; use var) — line ", itoa(st.line))))) }
|
||||
addr = loc_reg[li]; ty = loc_ty[li]
|
||||
}
|
||||
else {
|
||||
let g = find_global(t.s)
|
||||
if ptr_is_null(g) { perr(sconcat("assign to unknown ", t.s)) }
|
||||
|
|
@ -35,10 +40,10 @@ fn emit_assign(st: Node) -> void {
|
|||
}
|
||||
let lt = llty(ty)
|
||||
let rv = emit_expr(st.b)
|
||||
let v = rv.code
|
||||
var v = rv.code
|
||||
if not streq(st.s, "=") {
|
||||
let cur = emit_bind(sconcat("load ", sconcat(lt, sconcat(", ptr ", addr))))
|
||||
let opc = "add"
|
||||
var opc = "add"
|
||||
if streq(st.s, "-=") { opc = "sub" }
|
||||
if streq(st.s, "*=") { opc = "mul" }
|
||||
if streq(st.s, "/=") { opc = "sdiv" }
|
||||
|
|
@ -116,7 +121,7 @@ fn emit_return(st: Node) -> void {
|
|||
}
|
||||
|
||||
fn arm_is_default(arm: Node) -> bool {
|
||||
let p = 0
|
||||
var p = 0
|
||||
while p < len(arm.kids) { if arm.kids[p].kind == E_ID and streq(arm.kids[p].s, "_") { return true }; p = p + 1 }
|
||||
return false
|
||||
}
|
||||
|
|
@ -124,15 +129,15 @@ fn arm_is_default(arm: Node) -> bool {
|
|||
fn emit_match(st: Node) -> void {
|
||||
let sv = emit_expr(st.a)
|
||||
let endl = lbl("mend")
|
||||
let deflt: Node = ptr_null()
|
||||
let i = 0
|
||||
var deflt: Node = ptr_null()
|
||||
var i = 0
|
||||
while i < len(st.kids) {
|
||||
let arm = st.kids[i]
|
||||
if arm_is_default(arm) { deflt = arm }
|
||||
else {
|
||||
let acc = "0"
|
||||
let first = true
|
||||
let p = 0
|
||||
var acc = "0"
|
||||
var first = true
|
||||
var p = 0
|
||||
while p < len(arm.kids) {
|
||||
let pv = emit_expr(arm.kids[p])
|
||||
let c = emit_bind(sconcat("icmp eq i32 ", sconcat(sv.code, sconcat(", ", pv.code))))
|
||||
|
|
@ -156,16 +161,17 @@ fn emit_match(st: Node) -> void {
|
|||
|
||||
fn emit_stmt(st: Node) -> void {
|
||||
if st.kind == S_LET {
|
||||
let ty = st.ty
|
||||
var ty = st.ty
|
||||
if ptr_is_null(ty) { let v0 = emit_expr(st.a); ty = v0.ty
|
||||
let slot = emit_alloca(llty(ty)); store_at(llty(ty), v0.code, slot); loc_push(st.s, slot, ty); return }
|
||||
let slot = emit_alloca(llty(ty)); store_at(llty(ty), v0.code, slot); loc_push(st.s, slot, ty); loc_set_mut(st.ival); return }
|
||||
let slot = emit_alloca(llty(ty))
|
||||
if ptr_is_null(st.a) {
|
||||
let z = "0"
|
||||
var z = "0"
|
||||
if streq(llty(ty), "ptr") { z = "null" }
|
||||
store_at(llty(ty), z, slot)
|
||||
} else { let v = emit_expr(st.a); store_at(llty(ty), v.code, slot) }
|
||||
loc_push(st.s, slot, ty)
|
||||
loc_set_mut(st.ival)
|
||||
return
|
||||
}
|
||||
if st.kind == S_ASSIGN { emit_assign(st); return }
|
||||
|
|
|
|||
|
|
@ -15,26 +15,26 @@ fn ui_wtype(w: Node) -> int {
|
|||
return 0
|
||||
}
|
||||
fn ui_prop(w: Node, key: ptr) -> Node {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(w.b.kids) { if streq(w.b.kids[i].s, key) { return w.b.kids[i].a }; i = i + 1 }
|
||||
return ptr_null()
|
||||
}
|
||||
fn ui_flatten(w: Node, parent: int) -> void {
|
||||
let idx = len(uiw)
|
||||
push(uiw, w); push(uiw_parent, parent)
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(w.kids) { ui_flatten(w.kids[i], idx); i = i + 1 }
|
||||
}
|
||||
fn ui_flatten_all() -> void {
|
||||
uiw = new []Node; uiw_parent = new []int; ui_roots = new []int
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_UI and prog[i].ival == 1 { push(ui_roots, len(uiw)); ui_flatten(prog[i].a, 0 - 1) }
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
fn has_ui() -> bool {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(prog) { if prog[i].kind == N_UI and prog[i].ival == 1 { return true }; i = i + 1 }
|
||||
return false
|
||||
}
|
||||
|
|
@ -42,8 +42,8 @@ fn has_ui() -> bool {
|
|||
# index of a UI_<name>: a ui block's root, or a widget's id=
|
||||
fn ui_index_of(nm: ptr) -> int {
|
||||
let s = substr(nm, 3, slen(nm) - 3) # strip "UI_"
|
||||
let u = 0; let bi = 0
|
||||
let i = 0
|
||||
let u = 0; var bi = 0
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
if prog[i].kind == N_UI and prog[i].ival == 1 { if streq(prog[i].s, s) { return ui_roots[bi] }; bi = bi + 1 }
|
||||
i = i + 1
|
||||
|
|
@ -79,14 +79,14 @@ fn emit_ui_build() -> void {
|
|||
let saved = code
|
||||
code = fbody
|
||||
emit(" call void @fn_rt_ui_reset(i32 "); emit(itoa(len(uiw))); emit(")\n")
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(uiw) {
|
||||
let w = uiw[i]
|
||||
let t = ui_wtype(w)
|
||||
ll_ui_set(i, 0, itoa(t))
|
||||
ll_ui_set(i, 1, itoa(uiw_parent[i]))
|
||||
if t == 4 { ll_ui_set(i, 17, "1") }
|
||||
let p = 0
|
||||
var p = 0
|
||||
while p < len(w.b.kids) {
|
||||
let pr = w.b.kids[p]
|
||||
let k = pr.s
|
||||
|
|
@ -101,7 +101,7 @@ fn emit_ui_build() -> void {
|
|||
if streq(k, "skin") { ll_ui_set(i, 20, r) } else { ll_ui_set(i, 19, r) }
|
||||
} else { if streq(k, "align") {
|
||||
if v.kind == E_ID {
|
||||
let a = 0
|
||||
var a = 0
|
||||
if streq(v.s, "center") { a = 1 }; if streq(v.s, "end") { a = 2 }
|
||||
ll_ui_set(i, 12, itoa(a))
|
||||
} else { let vv = emit_expr(v); ll_ui_set(i, 12, vv.code) }
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ fn read_file(path: str) -> ptr {
|
|||
|
||||
# length of a NUL-terminated buffer
|
||||
fn cstr_len(s: ptr) -> int {
|
||||
let n = 0
|
||||
var n = 0
|
||||
while peek8(s, n) != 0 { n = n + 1 }
|
||||
return n
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ fn is_op1(c: int) -> bool {
|
|||
|
||||
fn lex(src: ptr) -> void {
|
||||
toks = new []Tok
|
||||
let i = 0
|
||||
let line = 1
|
||||
var i = 0
|
||||
var line = 1
|
||||
let n = slen(src)
|
||||
while i < n {
|
||||
let c = peek8(src, i)
|
||||
|
|
@ -52,11 +52,11 @@ fn lex(src: ptr) -> void {
|
|||
i = i + 1
|
||||
let start = i
|
||||
let out = mem_alloc(n)
|
||||
let j = 0
|
||||
var j = 0
|
||||
while i < n and peek8(src, i) != 34 {
|
||||
if peek8(src, i) == 92 { # backslash escape
|
||||
let e = peek8(src, i + 1)
|
||||
let r = e
|
||||
var r = e
|
||||
if e == 110 { r = 10 }
|
||||
if e == 116 { r = 9 }
|
||||
if e == 48 { r = 0 }
|
||||
|
|
@ -70,7 +70,7 @@ fn lex(src: ptr) -> void {
|
|||
}
|
||||
if c == 39 { # 'c' char literal -> int
|
||||
i = i + 1
|
||||
let v = 0
|
||||
var v = 0
|
||||
if peek8(src, i) == 92 {
|
||||
let e = peek8(src, i + 1)
|
||||
if e == 110 { v = 10 }
|
||||
|
|
@ -84,11 +84,11 @@ fn lex(src: ptr) -> void {
|
|||
}
|
||||
if char_is_digit(c) {
|
||||
if c == 48 and peek8(src, i + 1) == 120 { # 0x hex
|
||||
let v = 0
|
||||
var v = 0
|
||||
i = i + 2
|
||||
while i < n {
|
||||
let h = peek8(src, i)
|
||||
let d = 0
|
||||
var d = 0
|
||||
if char_is_digit(h) { d = h - 48 }
|
||||
else { if h >= 97 and h <= 102 { d = h - 87 }
|
||||
else { if h >= 65 and h <= 70 { d = h - 55 } else { break } } }
|
||||
|
|
@ -98,12 +98,12 @@ fn lex(src: ptr) -> void {
|
|||
tok_push(TK_INT, ptr_null(), v, line)
|
||||
continue
|
||||
}
|
||||
let v = 0
|
||||
var v = 0
|
||||
while i < n and char_is_digit(peek8(src, i)) { v = v * 10 + (peek8(src, i) - 48); i = i + 1 }
|
||||
# a fractional part makes it a Q16.16 fixed literal
|
||||
if i < n and peek8(src, i) == 46 and char_is_digit(peek8(src, i + 1)) {
|
||||
i = i + 1
|
||||
let fnum = 0; let fden = 1
|
||||
var fnum = 0; var fden = 1
|
||||
while i < n and char_is_digit(peek8(src, i)) { fnum = fnum * 10 + (peek8(src, i) - 48); fden = fden * 10; i = i + 1 }
|
||||
let bits = shl(v, 16) + shl(fnum, 16) / fden
|
||||
tok_push(TK_FLOAT, ptr_null(), bits, line)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -16,8 +16,8 @@
|
|||
|
||||
# basename: the part of a path after the last '/'.
|
||||
fn base_name(path: ptr) -> ptr {
|
||||
let last = 0 - 1
|
||||
let i = 0
|
||||
var last = 0 - 1
|
||||
var i = 0
|
||||
while peek8(path, i) != 0 { if peek8(path, i) == 47 { last = i }; i = i + 1 }
|
||||
return substr(path, last + 1, i - (last + 1))
|
||||
}
|
||||
|
|
@ -52,18 +52,18 @@ fn die(msg: ptr) -> void {
|
|||
}
|
||||
|
||||
entry {
|
||||
let path = ptr_null()
|
||||
let out = ptr_null()
|
||||
let want = 0 # 0 = auto, 1 = windowed, 2 = headless
|
||||
let emit_ir = false # --emit-llvm: stop after writing IR
|
||||
let fmt = false # --fmt: lex + parse only, then exit (the doc-check gate)
|
||||
let save = false # --save-temps: keep the intermediate .ll
|
||||
let run = false # compile then execute the result
|
||||
var path = ptr_null()
|
||||
var out = ptr_null()
|
||||
var want = 0 # 0 = auto, 1 = windowed, 2 = headless
|
||||
var emit_ir = false # --emit-llvm: stop after writing IR
|
||||
var fmt = false # --fmt: lex + parse only, then exit (the doc-check gate)
|
||||
var save = false # --save-temps: keep the intermediate .ll
|
||||
var run = false # compile then execute the result
|
||||
|
||||
# multi-call: invoked as `ludic` -> run mode by default
|
||||
if streq(base_name(os_arg(0)), "ludic") { run = true }
|
||||
|
||||
let ai = 1
|
||||
var ai = 1
|
||||
while ai < os_argc() {
|
||||
let a = os_arg(ai)
|
||||
if streq(a, "--windowed") { want = 1 }
|
||||
|
|
@ -134,7 +134,7 @@ entry {
|
|||
# platform layer and the Cocoa framework.
|
||||
# -Wno-override-module: our IR carries an explicit target triple, which clang
|
||||
# would otherwise warn about on every build.
|
||||
let cmd = sconcat(cc, sconcat(" -O2 -Wno-override-module ", ll))
|
||||
var cmd = sconcat(cc, sconcat(" -O2 -Wno-override-module ", ll))
|
||||
if g_windowed {
|
||||
let cocoa = path_join(home, "runtime/native/cocoa.ll")
|
||||
cmd = sconcat(cmd, sconcat(" ", sconcat(cocoa, " -framework Cocoa -Wl,-rpath,@loader_path")))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# parse.ludic — recursive-descent parser: the token slice -> an AST.
|
||||
# Reduced grammar: structs, vars, consts, fns, main; no ECS/scene/ui/match.
|
||||
# Mirrors compiler/front/parse.c. Uses the global `toks` and a cursor `pi`.
|
||||
# Core grammar: properties, vars/lets, consts, fns, entry; the ECS/ui/match
|
||||
# constructs live in parse_game.ludic. Uses the global `toks` and a cursor `pi`.
|
||||
|
||||
var pi: int = 0
|
||||
var prog: []Node # the top-level declarations
|
||||
|
|
@ -37,7 +37,7 @@ fn ptype() -> ptr {
|
|||
let el = ptype()
|
||||
let out = mem_alloc(slen(el) + 3)
|
||||
poke8(out, 0, 91); poke8(out, 1, 93) # "[]"
|
||||
let i = 0
|
||||
var i = 0
|
||||
while peek8(el, i) != 0 { poke8(out, 2 + i, peek8(el, i)); i = i + 1 }
|
||||
poke8(out, 2 + i, 0)
|
||||
return out
|
||||
|
|
@ -71,7 +71,7 @@ fn p_primary() -> Node {
|
|||
}
|
||||
|
||||
fn p_postfix() -> Node {
|
||||
let e = p_primary()
|
||||
var e = p_primary()
|
||||
while true {
|
||||
if is_op(".") { pi = pi + 1; let m = node(E_MEMBER); m.a = e; m.s = eat_id(); e = m }
|
||||
else { if is_op("[") { pi = pi + 1; let ix = node(E_INDEX); ix.a = e; ix.b = expr(); eat_op("]"); e = ix }
|
||||
|
|
@ -89,29 +89,29 @@ fn p_unary() -> Node {
|
|||
fn mkbin(op: ptr, l: Node, r: Node) -> Node { let b = node(E_BIN); b.s = op; b.a = l; b.b = r; return b }
|
||||
|
||||
fn p_mul() -> Node {
|
||||
let l = p_unary()
|
||||
var l = p_unary()
|
||||
while is_op("*") or is_op("/") or is_op("%") { let op = toks[pi].text; pi = pi + 1; l = mkbin(op, l, p_unary()) }
|
||||
return l
|
||||
}
|
||||
fn p_add() -> Node {
|
||||
let l = p_mul()
|
||||
var l = p_mul()
|
||||
while is_op("+") or is_op("-") { let op = toks[pi].text; pi = pi + 1; l = mkbin(op, l, p_mul()) }
|
||||
return l
|
||||
}
|
||||
fn p_cmp() -> Node {
|
||||
let l = p_add()
|
||||
var l = p_add()
|
||||
while is_op("<") or is_op("<=") or is_op(">") or is_op(">=") or is_op("==") or is_op("!=") {
|
||||
let op = toks[pi].text; pi = pi + 1; l = mkbin(op, l, p_add())
|
||||
}
|
||||
return l
|
||||
}
|
||||
fn p_and() -> Node {
|
||||
let l = p_cmp()
|
||||
var l = p_cmp()
|
||||
while is_id("and") { pi = pi + 1; l = mkbin("and", l, p_cmp()) }
|
||||
return l
|
||||
}
|
||||
fn p_or() -> Node {
|
||||
let l = p_and()
|
||||
var l = p_and()
|
||||
while is_id("or") { pi = pi + 1; l = mkbin("or", l, p_and()) }
|
||||
return l
|
||||
}
|
||||
|
|
@ -136,14 +136,9 @@ fn block() -> Node {
|
|||
# Rule B: statements are separated by a newline or ';' (both lex to TK_NL).
|
||||
# After a statement the next token must be that separator or the block's end —
|
||||
# two statements may not sit adjacent with only spaces between them.
|
||||
let sep = toks[pi].kind == TK_NL
|
||||
var sep = toks[pi].kind == TK_NL
|
||||
if is_op("}") { sep = true }
|
||||
if not sep {
|
||||
let ez = file_stderr(); file_write(ez, "cur=", 4)
|
||||
if not ptr_is_null(toks[pi].text) { file_write(ez, toks[pi].text, slen(toks[pi].text)) }
|
||||
file_write(ez, "\n", 1); print_int(7770000 + toks[pi].line)
|
||||
perr("expected newline or ';' between statements")
|
||||
}
|
||||
if not sep { perr("expected newline or ';' between statements") }
|
||||
}
|
||||
eat_op("}")
|
||||
return b
|
||||
|
|
@ -152,8 +147,9 @@ fn block() -> Node {
|
|||
fn stmt() -> Node {
|
||||
let t = toks[pi]
|
||||
if t.kind == TK_ID {
|
||||
if streq(t.text, "let") {
|
||||
pi = pi + 1; let n = node(S_LET); n.s = eat_id()
|
||||
if streq(t.text, "let") or streq(t.text, "var") {
|
||||
var mut = 0; if streq(t.text, "var") { mut = 1 } # let = immutable, var = mutable
|
||||
pi = pi + 1; let n = node(S_LET); n.ival = mut; n.line = toks[pi].line; n.s = eat_id()
|
||||
if is_op(":") { pi = pi + 1; n.ty = ptype() }
|
||||
if is_op("=") { pi = pi + 1; n.a = expr() }
|
||||
return n
|
||||
|
|
@ -183,7 +179,7 @@ fn stmt() -> Node {
|
|||
if streq(t.text, "spawn") { return parse_spawn() }
|
||||
if streq(t.text, "machine") {
|
||||
pi = pi + 1; let n = node(S_MACHINE); n.a = expr(); skipnl(); eat_op("{")
|
||||
let sidx = 0 # states auto-number by declaration order
|
||||
var sidx = 0 # states auto-number by declaration order
|
||||
while true { skipnl(); if is_op("}") { break }
|
||||
let stkw = eat_id() # 'state'
|
||||
let s = node(S_STATE); s.s = eat_id()
|
||||
|
|
@ -196,7 +192,7 @@ fn stmt() -> Node {
|
|||
if streq(t.text, "become") { pi = pi + 1; let n = node(S_BECOME); n.s = eat_id(); return n }
|
||||
if streq(t.text, "despawn") { pi = pi + 1; let n = node(S_DESPAWN); n.a = expr(); return n }
|
||||
if streq(t.text, "enable") or streq(t.text, "disable") {
|
||||
let en = 0; if streq(t.text, "enable") { en = 1 }
|
||||
var en = 0; if streq(t.text, "enable") { en = 1 }
|
||||
pi = pi + 1; let n = node(S_TOGGLE); n.ival = en; n.s = eat_id() # `enable P on e` / `disable Model` / `disable Handler`
|
||||
if is_id("on") { pi = pi + 1; n.a = expr() } # property on an entity
|
||||
return n
|
||||
|
|
@ -218,7 +214,7 @@ fn stmt() -> Node {
|
|||
}
|
||||
let e = expr()
|
||||
if is_op("=") or is_op("+=") or is_op("-=") or is_op("*=") or is_op("/=") {
|
||||
let n = node(S_ASSIGN); n.s = toks[pi].text; pi = pi + 1; n.a = e; n.b = expr(); return n
|
||||
let n = node(S_ASSIGN); n.line = toks[pi].line; n.s = toks[pi].text; pi = pi + 1; n.a = e; n.b = expr(); return n
|
||||
}
|
||||
let n = node(S_EXPR); n.a = e; return n
|
||||
}
|
||||
|
|
@ -249,8 +245,8 @@ fn parse_main() -> Node { pi = pi + 1; let n = node(N_MAIN); n.a = block(); retu
|
|||
|
||||
# directory part of a path, including the trailing '/', or "" if none
|
||||
fn dir_of(path: ptr) -> ptr {
|
||||
let last = 0 - 1
|
||||
let i = 0
|
||||
var last = 0 - 1
|
||||
var i = 0
|
||||
while peek8(path, i) != 0 { if peek8(path, i) == 47 { last = i }; i = i + 1 }
|
||||
if last < 0 { return "" }
|
||||
return substr(path, 0, last + 1)
|
||||
|
|
@ -264,7 +260,7 @@ var loaded_paths: []ptr
|
|||
var cur_dir: ptr
|
||||
|
||||
fn already_loaded(full: ptr) -> bool {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < len(loaded_paths) { if streq(loaded_paths[i], full) { return true }; i = i + 1 }
|
||||
return false
|
||||
}
|
||||
|
|
@ -273,14 +269,14 @@ fn already_loaded(full: ptr) -> bool {
|
|||
# Modifiers are `@annotations` in front of the declaration: `@export`, `@edge`,
|
||||
# `@pure`, `@deterministic`, … — one channel, not a zoo of prefix keywords.
|
||||
fn parse_one_decl() -> void {
|
||||
let is_export = false
|
||||
let qspec: Node = ptr_null()
|
||||
let onspawn_model: ptr = ptr_null()
|
||||
let ondespawn_model: ptr = ptr_null()
|
||||
let onattach_prop: ptr = ptr_null()
|
||||
let onenable_prop: ptr = ptr_null()
|
||||
let ondisable_prop: ptr = ptr_null()
|
||||
let hook_phase: ptr = ptr_null() # @OnStart / @OnQuit override the phase
|
||||
var is_export = false
|
||||
var qspec: Node = ptr_null()
|
||||
var onspawn_model: ptr = ptr_null()
|
||||
var ondespawn_model: ptr = ptr_null()
|
||||
var onattach_prop: ptr = ptr_null()
|
||||
var onenable_prop: ptr = ptr_null()
|
||||
var ondisable_prop: ptr = ptr_null()
|
||||
var hook_phase: ptr = ptr_null() # @OnStart / @OnQuit override the phase
|
||||
while is_op("@") {
|
||||
pi = pi + 1; let a = eat_id() # collect a leading @annotation
|
||||
if streq(a, "export") { is_export = true }
|
||||
|
|
@ -292,7 +288,7 @@ fn parse_one_decl() -> void {
|
|||
else { if streq(a, "OnDisable") { eat_op("("); ondisable_prop = eat_id(); eat_op(")") }
|
||||
else { if streq(a, "OnStart") { hook_phase = "Start" } # boot
|
||||
else { if streq(a, "OnQuit") { hook_phase = "OnQuit" } # shutdown
|
||||
else { if is_op("(") { let d = 0 # any other @anno(args) — parsed and skipped
|
||||
else { if is_op("(") { var d = 0 # any other @anno(args) — parsed and skipped
|
||||
while true { if is_op("(") { d = d + 1 }; if is_op(")") { d = d - 1 }; pi = pi + 1; if d == 0 { break } } } } } } } } } } } }
|
||||
skipnl()
|
||||
}
|
||||
|
|
@ -371,7 +367,7 @@ fn parse_program() -> void {
|
|||
# @annotations on the program itself (e.g. @Handles(Movement)) — parsed, skipped
|
||||
while is_op("@") {
|
||||
pi = pi + 1; let a = eat_id()
|
||||
if is_op("(") { let d = 0
|
||||
if is_op("(") { var d = 0
|
||||
while true { if is_op("(") { d = d + 1 }; if is_op(")") { d = d - 1 }; pi = pi + 1; if d == 0 { break } } }
|
||||
skipnl()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
fn parse_component() -> Node {
|
||||
pi = pi + 1; let n = node(N_COMP); n.s = eat_id(); skipnl(); eat_op("{")
|
||||
while true { skipnl(); if is_op("}") { break }
|
||||
let is_computed = false
|
||||
var is_computed = false
|
||||
if is_op("@") { pi = pi + 1; let ann = eat_id(); if streq(ann, "Computed") { is_computed = true }; skipnl() }
|
||||
let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
|
||||
if is_op("=") { pi = pi + 1; f.a = expr() }
|
||||
|
|
@ -23,7 +23,7 @@ fn parse_system() -> Node {
|
|||
# in a signature clause.
|
||||
while true {
|
||||
skipnl() # clauses may span several lines
|
||||
if is_op("@") { pi = pi + 1; let a = eat_id(); if is_op("(") { let d = 0 # @anno, one per turn so a
|
||||
if is_op("@") { pi = pi + 1; let a = eat_id(); if is_op("(") { var d = 0 # @anno, one per turn so a
|
||||
while true { if is_op("(") { d = d + 1 }; if is_op(")") { d = d - 1 }; pi = pi + 1; if d == 0 { break } } }
|
||||
continue } # newline-separated @anno re-skips at the loop top
|
||||
if is_id("phase") { pi = pi + 1; n.ty = eat_id(); continue }
|
||||
|
|
@ -99,7 +99,7 @@ fn parse_queries_anno() -> Node {
|
|||
eat_op("(")
|
||||
let qn = node(S_QUERY)
|
||||
let terms = node(N_BLOCK)
|
||||
let wh: Node = ptr_null()
|
||||
var wh: Node = ptr_null()
|
||||
while not is_op(")") {
|
||||
skipnl()
|
||||
if is_op(")") { break }
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
# byte buffers reached with peek8/poke8.
|
||||
|
||||
fn streq(a: ptr, b: ptr) -> bool {
|
||||
let i = 0
|
||||
var i = 0
|
||||
while true {
|
||||
let ca = peek8(a, i)
|
||||
let cb = peek8(b, i)
|
||||
|
|
@ -15,7 +15,7 @@ fn streq(a: ptr, b: ptr) -> bool {
|
|||
}
|
||||
|
||||
fn slen(s: ptr) -> int {
|
||||
let n = 0
|
||||
var n = 0
|
||||
while peek8(s, n) != 0 { n = n + 1 }
|
||||
return n
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ fn slen(s: ptr) -> int {
|
|||
# a fresh NUL-terminated copy of src[start .. start+n]
|
||||
fn substr(src: ptr, start: int, n: int) -> ptr {
|
||||
let b = mem_alloc(n + 1)
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < n { poke8(b, i, peek8(src, start + i)); i = i + 1 }
|
||||
poke8(b, n, 0)
|
||||
return b
|
||||
|
|
@ -40,18 +40,18 @@ fn char_is_alnum(c: int) -> bool { return char_is_alpha(c) or char_is_digit(c) }
|
|||
# integer -> fresh decimal string
|
||||
fn itoa(v: int) -> ptr {
|
||||
if v == 0 { let z = mem_alloc(2); poke8(z, 0, 48); poke8(z, 1, 0); return z }
|
||||
let neg = false
|
||||
let x = v
|
||||
var neg = false
|
||||
var x = v
|
||||
if x < 0 { neg = true; x = 0 - x }
|
||||
let tmp = mem_alloc(16)
|
||||
let n = 0
|
||||
var n = 0
|
||||
while x > 0 { poke8(tmp, n, 48 + x % 10); x = x / 10; n = n + 1 }
|
||||
let total = n
|
||||
var total = n
|
||||
if neg { total = total + 1 }
|
||||
let out = mem_alloc(total + 1)
|
||||
let k = 0
|
||||
var k = 0
|
||||
if neg { poke8(out, 0, 45); k = 1 }
|
||||
let i = 0
|
||||
var i = 0
|
||||
while i < n { poke8(out, k + i, peek8(tmp, n - 1 - i)); i = i + 1 }
|
||||
poke8(out, total, 0)
|
||||
return out
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ program T {
|
|||
fn fib(n: int) -> int { if n < 2 { return n }; return fib(n-1) + fib(n-2) }
|
||||
entry {
|
||||
print_int(fib(10)) # 55
|
||||
let s = 0
|
||||
var s = 0
|
||||
for i in 0 .. 10 { if i == 5 { break }; if i % 2 == 0 { continue }; s = s + i }
|
||||
print_int(s) # 1+3 = 4
|
||||
let i = 0; let t = 0
|
||||
var i = 0; var t = 0
|
||||
while true { i = i + 1; if i > 5 { break }; t = t + i }
|
||||
print_int(t) # 15
|
||||
if false and (1/0 == 0) { print_int(999) } else { print_int(1) } # short-circuit: no div by zero
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue