feat(compiler): list literals, typed compound assignment, file:line diagnostics

- `[a, b, c]` list literals (E_LIST → emit_list); static_type learns
  slice-element, `new T`, list, string and literal kinds
- `x op= y` lowers through the same path as `x = x op y` (emit_bin_vals):
  fixed `*=`/`/=` use the Q16.16 64-bit paths, string `+=` concatenates,
  int→long widens; unary `-` keeps a fixed operand's type (arith_ty)
- one `unescape()` table for "strings", 'chars' and `interpolation`;
  `'\''`, `'\\'`, `'\"'` no longer read as 0; unterminated char literals
  and unexpected characters are errors instead of silently skipped
- every diagnostic is `file:line: error: msg` (g_parse_file / g_err_file,
  Node.file + Node.line set by node()); tok_desc() in expectation errors;
  duplicate `function` names and unknown `phase` names are reported in
  source terms (phase_id used to default unknown phases to Overlay)
- interpolation holes skip braces inside string literals
- hand-IR preludes move from the user `@fn_` prefix to `@lp_` so a user
  `is_ws` / `str_eq` / `path_join` no longer collides at link time
- `@ClearColor(expr)` accepts any constant expression; `Os.pid()` added
  (docs page + inventory); `str_starts()` in support/str
- main.ludic: `else if` flag ladder, char literals, stale script comments
- examples/lang/operators.ludic covers all of the above; os.ludic covers
  Os.pid; docs pages for Os.pid and the Overlay phase; ten changesets
- reseeded: selfhost/ludicc.seed.ll is the new compiler's own fixpoint

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-05 01:12:16 +03:00
parent ad548840c7
commit 647dfec334
88 changed files with 30081 additions and 29179 deletions

View file

@ -6,7 +6,7 @@ function emit_block(b: Node) -> void {
while i < len(b.kids) {
if g_term { return }
emit_stmt(b.kids[i])
i = i + 1
i += 1
}
}
@ -15,6 +15,11 @@ function store_at(lt: pointer, v: pointer, addr: pointer) -> void {
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
}
# the binary operator behind a compound assignment token: "+=" -> "+"
function compound_op(tok: pointer) -> pointer {
return tok[0..1]
}
function emit_assign(st: Node) -> void {
# resolve the target's address and type
let t = st.a
@ -39,17 +44,16 @@ function emit_assign(st: Node) -> void {
else { perr("bad assignment target") } }
}
let lt = llty(ty)
let rv = emit_expr(st.b)
var rv = emit_expr(st.b)
if not (st.s == "=") {
# `x op= y` is `x = x op y`: reload the target and lower through the same
# path as a binary expression, so fixed `*=`, string `+=` and long promotion
# all behave exactly like their spelled-out forms.
let cur = val(emit_bind(`load {lt}, ptr {addr}`), ty)
rv = emit_bin_vals(compound_op(st.s), cur, rv, false)
}
var v = coerce_code(rv, ty)
if (lt == "i8") { v = emit_bind(`trunc i32 {v} to i8`) } # narrow to a byte for p[i] = v
if not (st.s == "=") {
let cur = emit_bind(`load {lt}, ptr {addr}`)
var opc = "add"
if (st.s == ("-=")) { opc = "sub" }
if (st.s == ("*=")) { opc = "mul" }
if (st.s == ("/=")) { opc = "sdiv" }
v = emit_bind(`{opc} {lt} {cur}, {v}`)
}
store_at(lt, v, addr)
}
@ -123,7 +127,7 @@ function emit_return(st: Node) -> void {
function arm_is_default(arm: Node) -> bool {
var p = 0
while p < len(arm.kids) { if arm.kids[p].kind == E_ID and (arm.kids[p].s == "_") { return true }; p = p + 1 }
while p < len(arm.kids) { if arm.kids[p].kind == E_ID and (arm.kids[p].s == "_") { return true }; p += 1 }
return false
}
@ -147,7 +151,7 @@ function emit_bind_payload(en: Node, pat: Node, sv: Val) -> void {
let lv = emit_bind(`load {lt}, ptr {p}`)
let slot = emit_alloca(lt); store_at(lt, lv, slot)
loc_push(pat.kids[k].s, slot, pty)
k = k + 1
k += 1
}
}
@ -178,7 +182,7 @@ function emit_match_tagged(st: Node, sv: Val, en: Node) -> void {
covered = covered | (1 << ord)
let c = emit_bind(`icmp eq i32 {tag}, {itoa(ord)}`)
if first { acc = c; first = false } else { acc = emit_bind(`or i1 {acc}, {c}`) }
p = p + 1
p += 1
}
let bodyl = lbl("mbody"); let nextl = lbl("marm")
emit(" br i1 "); emit(acc); emit(", label %"); emit(bodyl); emit(", label %"); emit(nextl); emit("\n")
@ -190,7 +194,7 @@ function emit_match_tagged(st: Node, sv: Val, en: Node) -> void {
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
emit(nextl); emit(":\n"); g_term = false
}
i = i + 1
i += 1
}
if (deflt != null) { emit_block(deflt.a) }
else {
@ -199,7 +203,7 @@ function emit_match_tagged(st: Node, sv: Val, en: Node) -> void {
var m = 0
while m < len(en.kids) {
if (covered & (1 << m)) == 0 { perr(`match on {en.s} is not exhaustive: variant {en.kids[m].s} is unhandled (add it or a _ arm)`) }
m = m + 1
m += 1
}
}
}
@ -226,7 +230,7 @@ function emit_match(st: Node) -> void {
let c = emit_bind(`icmp eq i32 {sv.code}, {pv.code}`)
if first { acc = c; first = false }
else { acc = emit_bind(`or i1 {acc}, {c}`) }
p = p + 1
p += 1
}
let bodyl = lbl("mbody"); let nextl = lbl("marm")
emit(" br i1 "); emit(acc); emit(", label %"); emit(bodyl); emit(", label %"); emit(nextl); emit("\n")
@ -235,7 +239,7 @@ function emit_match(st: Node) -> void {
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
emit(nextl); emit(":\n"); g_term = false
}
i = i + 1
i += 1
}
if (deflt != null) { emit_block(deflt.a) }
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
@ -256,14 +260,14 @@ function emit_emit(st: Node) -> Val {
let fd = ev.kids[f]
var av: Node = null # the caller's value for this field, if given
var j = 0
while j < len(st.a.kids) { if (st.a.kids[j].s == fd.s) { av = st.a.kids[j].a }; j = j + 1 }
while j < len(st.a.kids) { if (st.a.kids[j].s == fd.s) { av = st.a.kids[j].a }; j += 1 }
let lt = llty(fd.ty)
var code = "0"
if (lt == "ptr") { code = "null" }
if (av != null) { let v = emit_expr(av); code = v.code }
else { if (fd.a != null) { let dv = emit_expr(fd.a); code = dv.code } } # declared default
push(fcodes, code); push(ftys, lt)
f = f + 1
f += 1
}
# N4: a remote event (@ToServer/@ToClients) serializes its payload as
# [i32 event_id][packed fields] and net_send in its direction — the far side's
@ -276,8 +280,8 @@ function emit_emit(st: Node) -> Val {
while k < len(ev.kids) {
let dp = nreg(); emit(" "); emit(dp); emit(" = getelementptr inbounds i8, ptr @L_sendbuf, i32 "); emit(itoa(off)); emit("\n")
emit(" store "); emit(ftys[k]); emit(" "); emit(fcodes[k]); emit(", ptr "); emit(dp); emit("\n")
off = off + net_field_ibytes(ev.kids[k].ty)
k = k + 1
off += net_field_ibytes(ev.kids[k].ty)
k += 1
}
var peer = "0"
if (ev.ty == "toclients") { peer = "-1" } # broadcast (loopback ignores the peer id)
@ -292,7 +296,7 @@ function emit_emit(st: Node) -> Val {
while g < len(fcodes) {
if g > 0 { buf_puts(args, ", ") }
buf_puts(args, ftys[g]); buf_puts(args, " "); buf_puts(args, fcodes[g])
g = g + 1
g += 1
}
if ev.ival == 1 {
let r = nreg()
@ -304,6 +308,7 @@ function emit_emit(st: Node) -> Val {
}
function emit_stmt(st: Node) -> void {
g_err_file = st.file; g_err_line = st.line # so a lowering error names its statement
emit_cov_hit(st.line) # --coverage: bump this line's hit counter (no-op otherwise)
if st.kind == S_LET {
var ty = st.ty
@ -347,6 +352,6 @@ function emit_stmt(st: Node) -> void {
function loop_push(cont: pointer, brk: pointer) -> void {
if nloop < len(cnt_lbl) { cnt_lbl[nloop] = cont; brk_lbl[nloop] = brk }
else { push(cnt_lbl, cont); push(brk_lbl, brk) }
nloop = nloop + 1
nloop += 1
}
function loop_pop() -> void { nloop = nloop - 1 }
function loop_pop() -> void { nloop -= 1 }