ludic/selfhost/check/check_expr.ludic
Orkuncakilkaya 57b66bdf47 feat(lang): L4 type checker between parse and emit
selfhost/check/ walks every function, the entry, tests, globals' initializers and
@On listeners with real scopes, and refuses mixed number kinds, text and numbers,
two record types, mismatched slices and fn types, wrong argument counts, wrong
returns and wrong push elements - every mix-up at once, each at its line.
LUDIC_CHECK_REPORT=1 lists them by category. pointer stays untyped (L7's).

What it found is fixed: render3d's HDR scan calling the float-bits extern f_lt
with floats; ludic.shooter's right-stick aim overflowing past half a push;
prof.ludic storing longs in []int; extern arguments now coerced to their
parameters. Text-returning runtime functions say string; Assets.ready says bool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 01:34:49 +03:00

132 lines
3.8 KiB
Text

# check_expr.ludic — L4: the type of an expression, and every mix-up inside it. Each form works
# out its operands' types first, so an error deep in an argument is found as well as one at the top.
function ck_field(r: Node, name: pointer) -> pointer {
var i = 0
while i < len(r.kids) {
if (r.kids[i].s == name) { return r.kids[i].ty }
i += 1
}
return null
}
# anything the checker does not type is still walked for the errors inside it
function ck_walk(e: Node) -> void {
if e == null { return }
ck_any(e.a)
ck_any(e.b)
ck_any(e.c)
var i = 0
while i < len(e.kids) {
ck_any(e.kids[i])
i += 1
}
}
function ck_any(n: Node) -> void {
if n == null { return }
if n.kind == N_BLOCK { ck_block(n); return }
if n.kind == E_FINIT { ck_expr(n.a); return }
if (n.kind >= S_LET and n.kind <= S_BECOME) or n.kind == S_EMIT { ck_stmt(n); return }
ck_expr(n)
}
function ck_expr(e: Node) -> pointer {
if e == null { return "?" }
let k = e.kind
if k == E_INT { return "int" }
if k == E_FLOAT {
if is_float_file(e.file) { return "float" }
return "fixed"
}
if k == E_STR { return "string" }
if k == E_SLICE { ck_walk(e); return "string" }
if k == E_BOOL { return "bool" }
if k == E_NULL { return "null" }
if k == E_ID { return ck_id(e) }
if k == E_FNREF {
let f = ck_fn(e.s)
if f != null { return fn_sig_of(f) }
return "?"
}
if k == E_NEW { return ck_new(e) }
if k == E_LIST { return ck_list(e) }
if k == E_BIN { return ck_bin(e) }
if k == E_UN { return ck_un(e) }
if k == E_MEMBER { return ck_member(e) }
if k == E_INDEX { return ck_index_of(e) }
if k == E_CALL { return ck_call(e) }
if k == S_EMIT { ck_emit(e); return "?" }
ck_walk(e)
return "?"
}
function ck_id(e: Node) -> pointer {
let li = ck_local(e.s)
if li >= 0 { return ck_tys[li] }
let g = ck_global(e.s)
if g != null { return g.ty }
return "?"
}
function ck_new(e: Node) -> pointer {
let r = ck_record(e.s)
if e.a != null {
var i = 0
while i < len(e.a.kids) {
let fi = e.a.kids[i]
let vt = ck_expr(fi.a)
if r != null and fi.kind == E_FINIT {
let ft = ck_field(r, fi.s)
if ft != null { ck_give(ft, vt, fi.a, `field {fi.s} of {e.s}`) }
}
i += 1
}
}
return e.s
}
function ck_list(e: Node) -> pointer {
var t: pointer = "?"
var i = 0
while i < len(e.kids) {
let et = ck_expr(e.kids[i])
if ck_unknown(t) { t = et }
else { ck_give(t, et, e.kids[i], `an element of a []{t}`) }
i += 1
}
if ck_unknown(t) or (t == "null") { return "?" }
return "[]" + t
}
function ck_un(e: Node) -> pointer {
let t = ck_expr(e.a)
if (e.s == "not") or (e.s == "!") {
ck_cond(e.a, t)
return "bool"
}
if (e.s == "-") and (t == "bool") { ck_err("bool", e, "- on a bool") }
return t
}
function ck_member(e: Node) -> pointer {
let b = e.a
if b.kind == E_ID and ck_local(b.s) < 0 and ck_global(b.s) == null {
# a namespace, an enum or a component name: Math.PI, Color.Red, Thing.of
let en = ck_enum(b.s)
if en != null and not enum_is_tagged(en) { return b.s }
return "?"
}
let bt = ck_expr(b)
if ck_unknown(bt) { return "?" }
let r = ck_record(bt)
if r != null {
let ft = ck_field(r, e.s)
if ft != null { return ft }
}
return "?"
}
function ck_index_of(e: Node) -> pointer {
let bt = ck_expr(e.a)
let it = ck_expr(e.b)
if not ck_unknown(it) and not ck_is_int(it) { ck_err("index", e.b, `an index wants an int and this is {ck_a(it)}`) }
if ck_unknown(bt) { return "?" }
if is_slice_ty(bt) { return slice_elem(bt) }
if (bt == "string") or (bt == "words") { return "int" }
if (bt == "floats") { return "float" }
if (bt == "doubles") { return "double" }
if (bt == "fixeds") { return "fixed" }
if (bt == "pointers") { return "pointer" }
return "?"
}