ludic/selfhost/emit_list.ludic
Orkuncakilkaya 4c48077d68 refactor(lang): rename the fn keyword to function
Expand the function-declaration keyword to the full word across the whole
language and toolchain:
  fn name(...) -> T { ... }   ->   function name(...) -> T { ... }

Done as a self-hosting migration: teach the parser both spellings, reseed,
rewrite every .ludic definition to `function`, then drop `fn`. The compiler
now rejects `fn`. Touches the parser, all selfhost/tools/runtime/example/test
sources, the grammars (TextMate shared+vscode, ludic_syntax.h, JetBrains
LudicTokens.kt), the LSP and formatter, the Python doc/vocab tools
(check-impl, check-docs, validate, palette, test-lsp), and the docs
(fences, prose, kw-fn -> kw-function).

Reseeded; C-free bootstrap fixpoint holds. All suites green (45 regression,
24 self-host, 29 tool); the docs site generates and check.py passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 01:43:22 +03:00

294 lines
14 KiB
Text

# 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`
function 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}`)
}
function 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.
function 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")
}
function 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")
}