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:
Orkun ÇAKILKAYA 2026-08-27 23:52:59 +03:00
parent 573813609c
commit e1d7797e29
38 changed files with 2909 additions and 2778 deletions

View file

@ -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)) }

View file

@ -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
}

View file

@ -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() }

View file

@ -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))

View file

@ -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

View file

@ -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()

View file

@ -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))

View file

@ -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" }

View file

@ -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)

View file

@ -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")

View file

@ -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")

View file

@ -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]

View file

@ -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 {

View file

@ -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")
}
}

View file

@ -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 }

View file

@ -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) }

View file

@ -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
}

View file

@ -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

View file

@ -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")))

View file

@ -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()
}

View file

@ -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 }

View file

@ -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

View file

@ -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