feat(stdlib): namespaced standard library (issue #2)
Implement the bulk of the namespaced-stdlib proposal (workshopsoft/ludic#2): 156 namespace methods across Math, Text, List, Ease, Collide, World, Net, Sys, Save, Mem, extended Screen, Color functions, extended Random, and Time. All deterministic fixed-point; self-hosting (C-free bootstrap fixpoint holds). Compiler (selfhost/): - Math.*: sqrt/sin/cos/tan/atan2/asin/acos (fixed-point runtime prelude — bit-by-bit isqrt, 256-entry interpolated sine table, Ross atan2), plus hypot/dist/dist2/deg_to_rad/rad_to_deg/posmod/wrap/ping_pong/snapped/ move_toward/smoothstep/lerp/remap/sign/floor/ceil/round. - Text.* (complete): upper/lower/trim/repeat/pad, split/join/replace, and the libc-backed queries. - List.* (complete): insert/remove_at/remove/sort plus the earlier ops. - Ease.* (in/out/in_out/back/bounce) and Collide.* (rects/point_rect/ circles/rect_circle). - Phase 3: World/Net/Sys/Save namespaced over the bare builtins (byte- identical IR) and Mem.* (bytes/words/copy/fill/peek/poke). - Screen.* extended (line/circle/fill_circle/triangle/fill_triangle via new runtime primitives; sprite/sprite_scaled aliases), Color.* functions, Random.* (value/int/sign), Time.* (frame/delta/elapsed/now — new game-loop frame counter). - Fix a lexer bug: fixed-point literals with >4 fractional digits overflowed. Docs & tooling: - 129 new per-symbol doc pages; gen.py made data-driven (namespaces discovered from the docs, no hardcoded list); new check-impl.py enforces that every implemented namespace method / keyword / type / phase has a doc page, wired into `x test-tools`. Document the previously-undocumented keywords (break/continue/where/entry/new/public + and/or/not tokens). - LSP: namespaced signature help (ns_method_sig) covering every namespace. Tests: 12 new self-host/regression tests + a golden render for the drawing primitives. All suites green (selfhost 21, regression 45, tools 29). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ff15c4e01d
commit
a38195128f
235 changed files with 24676 additions and 7762 deletions
294
selfhost/emit_list.ludic
Normal file
294
selfhost/emit_list.ludic
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
# emit_list.ludic — the List.* namespace over []T slices. A slice is the
|
||||
# { data, len, cap } %LSlice header (see emit_new.ludic); every op here reads or
|
||||
# mutates that header in place, so all holders of the slice observe the change.
|
||||
# Element comparison (contains/index_of) is by value for scalars and by identity
|
||||
# for reference elements (structs/strings), matching how `==` behaves elsewhere.
|
||||
|
||||
# address of element `idx` (an i32 code) in slice header `h`, element LLVM type `elt`
|
||||
fn list_elem_addr(h: ptr, elt: ptr, idx: ptr) -> ptr {
|
||||
let dp = slice_field(h, 0)
|
||||
let data = emit_bind(`load ptr, ptr {dp}`)
|
||||
return emit_bind(`getelementptr inbounds {elt}, ptr {data}, i32 {idx}`)
|
||||
}
|
||||
|
||||
fn is_list_ns(meth: ptr) -> bool {
|
||||
if (meth == "len") or (meth == "push") or (meth == "clear") { return true }
|
||||
if (meth == "first") or (meth == "last") or (meth == "pop") or (meth == "swap") { return true }
|
||||
if (meth == "contains") or (meth == "index_of") or (meth == "reverse") { return true }
|
||||
if (meth == "insert") or (meth == "remove_at") or (meth == "remove") or (meth == "sort") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
# grow the slice's backing buffer if it is full, exactly as push does (double,
|
||||
# or 8 from empty). Leaves length untouched; only capacity/data may change.
|
||||
fn list_grow_if_full(h: ptr, elt: ptr) -> void {
|
||||
let lp = slice_field(h, 1); let cp = slice_field(h, 2); let dp = slice_field(h, 0)
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let c = emit_bind(`load i32, ptr {cp}`)
|
||||
let full = emit_bind(`icmp sge i32 {l}, {c}`)
|
||||
let grow = lbl("ig"); let done = lbl("igd")
|
||||
emit(" br i1 "); emit(full); emit(", label %"); emit(grow); emit(", label %"); emit(done); emit("\n")
|
||||
emit(grow); emit(":\n")
|
||||
let dbl = emit_bind(`mul i32 {c}, 2`)
|
||||
let isz = emit_bind(`icmp eq i32 {c}, 0`)
|
||||
let nc = emit_bind(`select i1 {isz}, i32 8, i32 {dbl}`)
|
||||
let esz = emit_sizeof(elt)
|
||||
let ncw = emit_bind(`zext i32 {nc} to i64`)
|
||||
let byts = emit_bind(`mul i64 {ncw}, {esz}`)
|
||||
let old = emit_bind(`load ptr, ptr {dp}`)
|
||||
let nd = emit_bind(`call ptr @realloc(ptr {old}, i64 {byts})`)
|
||||
emit(" store ptr "); emit(nd); emit(", ptr "); emit(dp); emit("\n")
|
||||
emit(" store i32 "); emit(nc); emit(", ptr "); emit(cp); emit("\n")
|
||||
emit(" br label %"); emit(done); emit("\n")
|
||||
emit(done); emit(":\n")
|
||||
}
|
||||
|
||||
fn emit_list_ns(meth: ptr, e: Node) -> Val {
|
||||
if (meth == "len") { return emit_len(e) } # same header length as len(s)
|
||||
if (meth == "push") { return emit_push(e) } # same as push(s, v)
|
||||
let s = emit_expr(e.kids[0])
|
||||
if not is_slice_ty(s.ty) { perr(`List.{meth} needs a slice`) }
|
||||
let el = slice_elem(s.ty)
|
||||
let elt = llty(el)
|
||||
let h = s.code
|
||||
let lp = slice_field(h, 1)
|
||||
|
||||
if (meth == "clear") { # drop to length 0 (keeps capacity)
|
||||
emit(" store i32 0, ptr "); emit(lp); emit("\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "first") { # element 0 (assumes non-empty)
|
||||
let a = list_elem_addr(h, elt, "0")
|
||||
return val(emit_bind(`load {elt}, ptr {a}`), el)
|
||||
}
|
||||
if (meth == "last") { # element len-1 (assumes non-empty)
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let l1 = emit_bind(`sub i32 {l}, 1`)
|
||||
let a = list_elem_addr(h, elt, l1)
|
||||
return val(emit_bind(`load {elt}, ptr {a}`), el)
|
||||
}
|
||||
if (meth == "pop") { # remove & return the last element
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let l1 = emit_bind(`sub i32 {l}, 1`)
|
||||
let a = list_elem_addr(h, elt, l1)
|
||||
let v = emit_bind(`load {elt}, ptr {a}`)
|
||||
emit(" store i32 "); emit(l1); emit(", ptr "); emit(lp); emit("\n")
|
||||
return val(v, el)
|
||||
}
|
||||
if (meth == "swap") { # exchange elements i and j
|
||||
let i = emit_expr(e.kids[1]); let j = emit_expr(e.kids[2])
|
||||
let ai = list_elem_addr(h, elt, i.code)
|
||||
let aj = list_elem_addr(h, elt, j.code)
|
||||
let vi = emit_bind(`load {elt}, ptr {ai}`)
|
||||
let vj = emit_bind(`load {elt}, ptr {aj}`)
|
||||
emit(" store "); emit(elt); emit(" "); emit(vj); emit(", ptr "); emit(ai); emit("\n")
|
||||
emit(" store "); emit(elt); emit(" "); emit(vi); emit(", ptr "); emit(aj); emit("\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "contains") or (meth == "index_of") { # linear scan; index_of -> -1 if absent
|
||||
let needle = emit_expr(e.kids[1])
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let res = emit_alloca("i32")
|
||||
store_at("i32", "-1", res)
|
||||
let ix = emit_alloca("i32")
|
||||
store_at("i32", "0", ix)
|
||||
let cl = lbl("lc_cond"); let bl = lbl("lc_body"); let hit = lbl("lc_hit")
|
||||
let nx = lbl("lc_next"); let en = lbl("lc_end")
|
||||
emit(" br label %"); emit(cl); emit("\n")
|
||||
emit(cl); emit(":\n")
|
||||
let iv = emit_bind(`load i32, ptr {ix}`)
|
||||
let more = emit_bind(`icmp slt i32 {iv}, {l}`)
|
||||
emit(" br i1 "); emit(more); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
||||
emit(bl); emit(":\n")
|
||||
let a = list_elem_addr(h, elt, iv)
|
||||
let ev = emit_bind(`load {elt}, ptr {a}`)
|
||||
let eq = emit_bind(`icmp eq {elt} {ev}, {needle.code}`)
|
||||
emit(" br i1 "); emit(eq); emit(", label %"); emit(hit); emit(", label %"); emit(nx); emit("\n")
|
||||
emit(hit); emit(":\n")
|
||||
store_at("i32", iv, res)
|
||||
emit(" br label %"); emit(en); emit("\n")
|
||||
emit(nx); emit(":\n")
|
||||
let iv1 = emit_bind(`add i32 {iv}, 1`)
|
||||
store_at("i32", iv1, ix)
|
||||
emit(" br label %"); emit(cl); emit("\n")
|
||||
emit(en); emit(":\n")
|
||||
let found = emit_bind(`load i32, ptr {res}`)
|
||||
if (meth == "index_of") { return val(found, "int") }
|
||||
let hasit = emit_bind(`icmp sge i32 {found}, 0`) # contains -> found >= 0
|
||||
return val(emit_bind(`zext i1 {hasit} to i32`), "bool")
|
||||
}
|
||||
if (meth == "insert") { # insert v at index i, shifting the rest up
|
||||
let idx = emit_expr(e.kids[1]); let vv = emit_expr(e.kids[2])
|
||||
list_grow_if_full(h, elt)
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let jp = emit_alloca("i32")
|
||||
store_at("i32", l, jp)
|
||||
let cl = lbl("li_c"); let bl = lbl("li_b"); let en = lbl("li_e")
|
||||
emit(" br label %"); emit(cl); emit("\n"); emit(cl); emit(":\n")
|
||||
let j = emit_bind(`load i32, ptr {jp}`)
|
||||
let go = emit_bind(`icmp sgt i32 {j}, {idx.code}`)
|
||||
emit(" br i1 "); emit(go); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
||||
emit(bl); emit(":\n")
|
||||
let jm1 = emit_bind(`sub i32 {j}, 1`)
|
||||
let src = list_elem_addr(h, elt, jm1)
|
||||
let sv = emit_bind(`load {elt}, ptr {src}`)
|
||||
let dst = list_elem_addr(h, elt, j)
|
||||
emit(" store "); emit(elt); emit(" "); emit(sv); emit(", ptr "); emit(dst); emit("\n")
|
||||
store_at("i32", jm1, jp)
|
||||
emit(" br label %"); emit(cl); emit("\n")
|
||||
emit(en); emit(":\n")
|
||||
let at = list_elem_addr(h, elt, idx.code)
|
||||
emit(" store "); emit(elt); emit(" "); emit(vv.code); emit(", ptr "); emit(at); emit("\n")
|
||||
let l1 = emit_bind(`add i32 {l}, 1`)
|
||||
emit(" store i32 "); emit(l1); emit(", ptr "); emit(lp); emit("\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "remove_at") { # remove index i, shifting the rest down
|
||||
let idx = emit_expr(e.kids[1])
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let lm1 = emit_bind(`sub i32 {l}, 1`)
|
||||
let jp = emit_alloca("i32")
|
||||
store_at("i32", idx.code, jp)
|
||||
let cl = lbl("lr_c"); let bl = lbl("lr_b"); let en = lbl("lr_e")
|
||||
emit(" br label %"); emit(cl); emit("\n"); emit(cl); emit(":\n")
|
||||
let j = emit_bind(`load i32, ptr {jp}`)
|
||||
let go = emit_bind(`icmp slt i32 {j}, {lm1}`)
|
||||
emit(" br i1 "); emit(go); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
||||
emit(bl); emit(":\n")
|
||||
let jp1 = emit_bind(`add i32 {j}, 1`)
|
||||
let src = list_elem_addr(h, elt, jp1)
|
||||
let sv = emit_bind(`load {elt}, ptr {src}`)
|
||||
let dst = list_elem_addr(h, elt, j)
|
||||
emit(" store "); emit(elt); emit(" "); emit(sv); emit(", ptr "); emit(dst); emit("\n")
|
||||
store_at("i32", jp1, jp)
|
||||
emit(" br label %"); emit(cl); emit("\n")
|
||||
emit(en); emit(":\n")
|
||||
emit(" store i32 "); emit(lm1); emit(", ptr "); emit(lp); emit("\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "remove") { # remove the first element equal to v
|
||||
let needle = emit_expr(e.kids[1])
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
# scan for the index of v (-1 if absent)
|
||||
let fp = emit_alloca("i32")
|
||||
store_at("i32", "-1", fp)
|
||||
let ip = emit_alloca("i32")
|
||||
store_at("i32", "0", ip)
|
||||
let sc = lbl("lv_c"); let sb = lbl("lv_b"); let sh = lbl("lv_h"); let sn = lbl("lv_n"); let se = lbl("lv_e")
|
||||
emit(" br label %"); emit(sc); emit("\n"); emit(sc); emit(":\n")
|
||||
let iv = emit_bind(`load i32, ptr {ip}`)
|
||||
let more = emit_bind(`icmp slt i32 {iv}, {l}`)
|
||||
let seen = emit_bind(`load i32, ptr {fp}`)
|
||||
let notyet = emit_bind(`icmp slt i32 {seen}, 0`)
|
||||
let cont = emit_bind(`and i1 {more}, {notyet}`)
|
||||
emit(" br i1 "); emit(cont); emit(", label %"); emit(sb); emit(", label %"); emit(se); emit("\n")
|
||||
emit(sb); emit(":\n")
|
||||
let a = list_elem_addr(h, elt, iv)
|
||||
let ev = emit_bind(`load {elt}, ptr {a}`)
|
||||
let eq = emit_bind(`icmp eq {elt} {ev}, {needle.code}`)
|
||||
emit(" br i1 "); emit(eq); emit(", label %"); emit(sh); emit(", label %"); emit(sn); emit("\n")
|
||||
emit(sh); emit(":\n"); store_at("i32", iv, fp); emit(" br label %"); emit(sc); emit("\n")
|
||||
emit(sn); emit(":\n")
|
||||
let iv1 = emit_bind(`add i32 {iv}, 1`)
|
||||
store_at("i32", iv1, ip)
|
||||
emit(" br label %"); emit(sc); emit("\n")
|
||||
emit(se); emit(":\n")
|
||||
# if found, shift the tail down and shrink
|
||||
let found = emit_bind(`load i32, ptr {fp}`)
|
||||
let has = emit_bind(`icmp sge i32 {found}, 0`)
|
||||
let doit = lbl("lv_do"); let end = lbl("lv_end")
|
||||
emit(" br i1 "); emit(has); emit(", label %"); emit(doit); emit(", label %"); emit(end); emit("\n")
|
||||
emit(doit); emit(":\n")
|
||||
let lm1 = emit_bind(`sub i32 {l}, 1`)
|
||||
let jp = emit_alloca("i32")
|
||||
store_at("i32", found, jp)
|
||||
let dc = lbl("lv_dc"); let db = lbl("lv_db"); let de = lbl("lv_de")
|
||||
emit(" br label %"); emit(dc); emit("\n"); emit(dc); emit(":\n")
|
||||
let j = emit_bind(`load i32, ptr {jp}`)
|
||||
let go = emit_bind(`icmp slt i32 {j}, {lm1}`)
|
||||
emit(" br i1 "); emit(go); emit(", label %"); emit(db); emit(", label %"); emit(de); emit("\n")
|
||||
emit(db); emit(":\n")
|
||||
let jp1 = emit_bind(`add i32 {j}, 1`)
|
||||
let src = list_elem_addr(h, elt, jp1)
|
||||
let sv = emit_bind(`load {elt}, ptr {src}`)
|
||||
let dst = list_elem_addr(h, elt, j)
|
||||
emit(" store "); emit(elt); emit(" "); emit(sv); emit(", ptr "); emit(dst); emit("\n")
|
||||
store_at("i32", jp1, jp)
|
||||
emit(" br label %"); emit(dc); emit("\n")
|
||||
emit(de); emit(":\n")
|
||||
emit(" store i32 "); emit(lm1); emit(", ptr "); emit(lp); emit("\n")
|
||||
emit(" br label %"); emit(end); emit("\n")
|
||||
emit(end); emit(":\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "sort") { # ascending insertion sort, in place
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let ip = emit_alloca("i32")
|
||||
store_at("i32", "1", ip)
|
||||
let oc = lbl("ls_oc"); let ob = lbl("ls_ob"); let oe = lbl("ls_oe")
|
||||
emit(" br label %"); emit(oc); emit("\n"); emit(oc); emit(":\n")
|
||||
let i = emit_bind(`load i32, ptr {ip}`)
|
||||
let omore = emit_bind(`icmp slt i32 {i}, {l}`)
|
||||
emit(" br i1 "); emit(omore); emit(", label %"); emit(ob); emit(", label %"); emit(oe); emit("\n")
|
||||
emit(ob); emit(":\n")
|
||||
let ai = list_elem_addr(h, elt, i)
|
||||
let key = emit_bind(`load {elt}, ptr {ai}`)
|
||||
let jp = emit_alloca("i32")
|
||||
let im1 = emit_bind(`sub i32 {i}, 1`)
|
||||
store_at("i32", im1, jp)
|
||||
let ic = lbl("ls_ic"); let ib = lbl("ls_ib"); let ie = lbl("ls_ie")
|
||||
emit(" br label %"); emit(ic); emit("\n"); emit(ic); emit(":\n")
|
||||
let j = emit_bind(`load i32, ptr {jp}`)
|
||||
let jok = emit_bind(`icmp sge i32 {j}, 0`)
|
||||
let aj = list_elem_addr(h, elt, j)
|
||||
let ev = emit_bind(`load {elt}, ptr {aj}`)
|
||||
let gt = emit_bind(`icmp sgt {elt} {ev}, {key}`)
|
||||
let shift = emit_bind(`and i1 {jok}, {gt}`)
|
||||
emit(" br i1 "); emit(shift); emit(", label %"); emit(ib); emit(", label %"); emit(ie); emit("\n")
|
||||
emit(ib); emit(":\n")
|
||||
let jp1 = emit_bind(`add i32 {j}, 1`)
|
||||
let dst = list_elem_addr(h, elt, jp1)
|
||||
emit(" store "); emit(elt); emit(" "); emit(ev); emit(", ptr "); emit(dst); emit("\n")
|
||||
let jm1 = emit_bind(`sub i32 {j}, 1`)
|
||||
store_at("i32", jm1, jp)
|
||||
emit(" br label %"); emit(ic); emit("\n")
|
||||
emit(ie); emit(":\n")
|
||||
let j2 = emit_bind(`load i32, ptr {jp}`)
|
||||
let slot = emit_bind(`add i32 {j2}, 1`)
|
||||
let sa = list_elem_addr(h, elt, slot)
|
||||
emit(" store "); emit(elt); emit(" "); emit(key); emit(", ptr "); emit(sa); emit("\n")
|
||||
let i1 = emit_bind(`add i32 {i}, 1`)
|
||||
store_at("i32", i1, ip)
|
||||
emit(" br label %"); emit(oc); emit("\n")
|
||||
emit(oe); emit(":\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
# reverse: swap ends inward until the cursors meet
|
||||
let l = emit_bind(`load i32, ptr {lp}`)
|
||||
let loi = emit_alloca("i32")
|
||||
store_at("i32", "0", loi)
|
||||
let hii = emit_alloca("i32")
|
||||
let lm1 = emit_bind(`sub i32 {l}, 1`)
|
||||
store_at("i32", lm1, hii)
|
||||
let cl = lbl("lr_cond"); let bl = lbl("lr_body"); let en = lbl("lr_end")
|
||||
emit(" br label %"); emit(cl); emit("\n")
|
||||
emit(cl); emit(":\n")
|
||||
let lo = emit_bind(`load i32, ptr {loi}`)
|
||||
let hi = emit_bind(`load i32, ptr {hii}`)
|
||||
let go = emit_bind(`icmp slt i32 {lo}, {hi}`)
|
||||
emit(" br i1 "); emit(go); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
||||
emit(bl); emit(":\n")
|
||||
let alo = list_elem_addr(h, elt, lo)
|
||||
let ahi = list_elem_addr(h, elt, hi)
|
||||
let vlo = emit_bind(`load {elt}, ptr {alo}`)
|
||||
let vhi = emit_bind(`load {elt}, ptr {ahi}`)
|
||||
emit(" store "); emit(elt); emit(" "); emit(vhi); emit(", ptr "); emit(alo); emit("\n")
|
||||
emit(" store "); emit(elt); emit(" "); emit(vlo); emit(", ptr "); emit(ahi); emit("\n")
|
||||
let lo1 = emit_bind(`add i32 {lo}, 1`)
|
||||
store_at("i32", lo1, loi)
|
||||
let hi1 = emit_bind(`sub i32 {hi}, 1`)
|
||||
store_at("i32", hi1, hii)
|
||||
emit(" br label %"); emit(cl); emit("\n")
|
||||
emit(en); emit(":\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue