ludic/selfhost/check/check_expr.ludic
Orkuncakilkaya ce80e64b24 feat(lang): 0.S - components take states in their header; migrate writes them, never inside a name, never outside the programs given, never into a package's state
- component Name (a: mut A, b: B) { ... }: every getter, default, function and event takes the
  header's states before the instance; a member's call to another passes them on; the glue is
  supplied them; the template never sees them; a read-only one is read-only in every member
- ludic migrate state: a component's members' needs go into its header (added to an existing one,
  mut added where now changed); a field read or a member call inside a component is the compiler's,
  so nothing is written inside a name and no ', )' is left; an entry point that declares states
  already gets the rest after them
- a program's module named like a package gets <Name>AppState; a program's own file its own state;
  a friend module's files go by directory; a package's settable var stays state
- it writes only under the programs and directories given (and runtime/ with --runtime), and
  refuses the whole run naming any other file that would have to change
- a name a package already moved into its state is rewritten through it; a read of the runtime's
  var through the runtime function that answers it (gl_w: gl_width())
- a state's instance supplied by the runtime is not a uses reference
- tests: state/component, rejected/state_component_ro, rendering/ui_render3d, migrate component
  and foreign cases

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:11:15 +03:00

172 lines
5.4 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 or n.kind == S_UNSAFE { ck_stmt(n); return }
ck_expr(n)
}
function ck_expr(e: Node) -> pointer {
if e == null { return "?" }
let ex = ck_expect
ck_expect = null
let k = e.kind
if k == E_INT {
if e.s != null { return "long" } # a literal past 2^31 - 1
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)
ck_vis(f, e.s, e)
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 {
ck_call_expect = ex
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 {
mg_ref(e, g)
ck_vis(g, e.s, e)
return g.ty
}
let fv = mg_field_var(e.s) # 0.S2: a var a package moved into its state
if fv != null {
mg_ref(e, fv)
return fv.ty
}
return "?"
}
# --check: the module rules (uses, export, ports - emit_vis.ludic) asked here, where every reference
# is resolved, since a check-only build stops before the emitter that asks them otherwise
var g_check_only: bool = false
var ck_vis_file: pointer = null # a global's initializer is its declaration's file's code (as emitted)
var ck_vis_skip: bool = false # a port's bind, checked where it was written (ports.ludic)
function ck_vis(d: Node, name: pointer, at: Node) -> void {
if not g_check_only or ck_vis_skip or d == null or at == null or at.file == null { return }
g_err_file = at.file
if ck_vis_file != null { g_err_file = ck_vis_file }
g_err_line = at.line
vis_check(d, name)
}
function ck_new(e: Node) -> pointer {
let r = ck_record(e.s)
ck_vis(r, e.s, e)
if r == null and g_check_only and port_find(e.s) >= 0 {
ck_err("port", e, `{e.s} is a port: it is filled once with 'bind {e.s} {{ ... }}' where the program is put together, not made with new`)
}
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}`) }
if ft == null { ck_err("field", fi.a, `{gen_show(e.s)} has no field {fi.s}`) }
}
i += 1
}
}
return e.s
}
function ck_list(e: Node) -> pointer {
var t: pointer = "?"
var i = 0
let saved = ck_vis_file
while i < len(e.kids) {
# a registry's entries are their defs' files' code (emit_list_elem)
if saved != null and e.kids[i].file != null { ck_vis_file = e.kids[i].file }
let et = ck_expr(e.kids[i])
ck_vis_file = saved
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 ck_is_raw(bt) { ck_raw(e, `indexing {ck_a(bt)}`) }
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 "?"
}