ludic.base's Queue<T> carries a QueueTag (its name and pending count): queue_new(name), q_push(q, v), q_drain(q), q_clear(q) take no BaseState, and core_undrained(tags) names the given queues still holding facts. A reducer on a package's state can now call that package's verbs (wallet_earn(wallet_st, n)). ludic migrate state --prune (ludicc --migrate-prune) takes out each state parameter a function no longer uses, nor anything it calls, and the argument that fills it - including an argument for a parameter the callee has dropped, which is taken out before the call is checked, so a generic's T is told by the argument that says it. A reducer keeps its state. --dry-run now counts the edits it would make. Every package was moved with it: 608 base_st parameters and their arguments, 1889 edits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
178 lines
5.7 KiB
Text
178 lines
5.7 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) }
|
|
# the emitter's own refusal, here too, so a check-only build finds it (a bind to a function
|
|
# that is gone)
|
|
if find_fn(e.s) == null { ck_err("fnref", e, `fn {e.s}: no function called {e.s}`) }
|
|
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 {
|
|
mg_local_use(e, ck_tys[li]) # 0.S2 --migrate-prune: a state used here
|
|
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 "?"
|
|
}
|