# check_stmt.ludic — L4: statements, and the pass itself. check_program runs between the parse and # the emitter over everything the program wrote (not the runtime spliced in after it): every # function, the entry, the tests, the globals' initializers and every @On listener with its # event's fields in scope. ECS handler, query, spawn and machine bodies bind names the checker # does not model, so they are left to the emitter. function ck_block(b: Node) -> void { if b == null { return } if b.kind != N_BLOCK { ck_stmt(b) return } let m = ck_mark() var i = 0 while i < len(b.kids) { ck_stmt(b.kids[i]) i += 1 } ck_pop(m) } function ck_target(t: Node) -> pointer { if t.kind == E_ID { return ck_id(t) } return ck_expr(t) } function ck_let(s: Node) -> void { var t: pointer = "?" ck_expect = s.ty if s.a != null { t = ck_expr(s.a) } if s.ty != null { if s.a != null { ck_give(s.ty, t, s.a, s.s) } ck_bind(s.s, s.ty) if s.tps != null and (s.tps == "state") { # an entry point's state (state.ludic) if not is_state_ty(s.ty) { ck_err("state", s, `an entry point takes states, and {s.s}: {s.ty} is not one`) } ck_set_ro(s.uns == 0) } else { ck_let_from(s.a, s.ty) } return } ck_bind(s.s, t) # a local bound to a read-only state, or to a reference out of one, is read-only too if s.a != null and s.a.kind == E_ID { let li = ck_local(s.a.s) if li >= 0 and li < ck_top - 1 and ck_is_ro(li) { ck_ro_like(li) } } ck_let_from(s.a, t) } function ck_assign(s: Node) -> void { # a local rebound is not a write through it if not (s.a.kind == E_ID and ck_local(s.a.s) >= 0) { ck_write_check(s.a, "this assignment") } let lt = ck_target(s.a) if (s.s == "=") { ck_expect = lt } let rt = ck_expr(s.b) var what: pointer = "this assignment" if s.a.kind == E_ID { what = s.a.s } if s.a.kind == E_MEMBER { what = `field {s.a.s}` } if (s.s == "=") { ck_give(lt, rt, s.b, what) return } let op = s.s[0..1] let res = ck_binop(s, op, lt, rt, s.a, s.b) ck_give(lt, res, s.b, what) } function ck_match(s: Node) -> void { ck_expr(s.a) var i = 0 while i < len(s.kids) { let arm = s.kids[i] let m = ck_mark() var p = 0 while p < len(arm.kids) { let pat = arm.kids[p] # `Door(n) =>` binds its payload's names if pat.kind == E_CALL { var q = 0 while q < len(pat.kids) { if pat.kids[q].kind == E_ID { ck_bind(pat.kids[q].s, "?") } q += 1 } } p += 1 } ck_block(arm.a) ck_pop(m) i += 1 } } function ck_stmt(s: Node) -> void { if s == null { return } let k = s.kind if k == N_BLOCK { ck_block(s); return } if k == S_LET { ck_let(s); return } if k == S_ASSIGN { ck_assign(s); return } if k == S_IF { ck_cond(s.a, ck_expr(s.a)) ck_block(s.b) ck_block(s.c) return } if k == S_WHILE { ck_cond(s.a, ck_expr(s.a)) ck_block(s.b) return } if k == S_FOR { ck_expr(s.a) ck_expr(s.b) let m = ck_mark() ck_bind(s.s, "int") ck_block(s.c) ck_pop(m) return } if k == S_RETURN { if s.a == null { return } ck_expect = ck_ret let t = ck_expr(s.a) if (ck_ret != "void") { ck_give(ck_ret, t, s.a, "the result") } return } if k == S_EXPR { ck_expr(s.a); return } if k == S_MATCH { ck_match(s); return } if k == S_EMIT { ck_emit(s); return } if k == S_UNSAFE { ck_unsafe_here(s) ck_unsafe += 1 ck_block(s.a) ck_unsafe -= 1 return } # a machine's store: its states' `become` writes it if k == S_MACHINE { if s.a != null and not (s.a.kind == E_INT) { ck_write_check(s.a, "a machine's become") } ck_walk(s) return } # 0.S: a query's body, a spawn's fields, a machine's states - read for what they touch if k == S_QUERY { let m = ck_mark() var t = 0 var i = 0 while i < len(s.kids) { var ty: pointer = "?" if s.c != null { while t < len(s.c.kids) and s.c.kids[t].ival == 1 { t += 1 } if t < len(s.c.kids) { ty = s.c.kids[t].s } t += 1 } ck_bind(s.kids[i].s, ty) i += 1 } ck_any(s.b) ck_block(s.a) ck_pop(m) return } ck_walk(s) } # a name given to two parameters: the second would shadow the first, and nothing could reach it function ck_param_twice(d: Node, i: int) -> void { var j = 0 while j < i { if d.kids[j].kind == N_PARAM and (d.kids[j].s == d.kids[i].s) { ck_err("arity", d.kids[i], `{d.s} names two parameters {d.kids[i].s}`) return } j += 1 } } function ck_fn_body(d: Node) -> void { mg_enter(d, 0) let m = ck_mark() var i = 0 while i < len(d.kids) { if d.kids[i].kind == N_PARAM { ck_param_twice(d, i) ck_bind_param(d.kids[i]) } i += 1 } ck_ret = d.ty if ck_ret == null { ck_ret = "void" } if d.uns == 1 { ck_unsafe_here(d) ck_unsafe += 1 } ck_block(d.a) if d.uns == 1 { ck_unsafe -= 1 } ck_ret = "void" ck_pop(m) mg_leave() } function ck_listener(l: Node) -> void { mg_enter(l, 4) let ev = find_event(l.s) ck_vis(ev, l.s, l) let m = ck_mark() if ev != null { var i = 0 while i < len(ev.kids) { ck_bind(ev.kids[i].s, ev.kids[i].ty) i += 1 } } ck_ret = "void" ck_block(l.a) ck_pop(m) mg_leave() } # a program's function named like one of the runtime's takes every call the runtime makes to its # own - the runtime's ui_measure started answering to a package's - so the name is refused; one # named like a compiler built-in never ran at all (every call to `run` was C's system()), so too function ck_runtime_shadows() -> void { var u = 0 while u < g_prog_user_end { let d = prog[u] if d.kind == N_FN and (is_intrinsic(d.s) or is_intrinsic2(d.s)) { ck_err("shadow", d, `{d.s} is a built-in of the compiler, and a call to it would never reach this function; choose another name`) } u += 1 } var r = g_prog_user_end while r < len(prog) { let d = prog[r] if d.kind == N_FN { let u = ck_fn(d.s) if u != null and u != d { ck_err("shadow", u, `{d.s} is also a function of the engine runtime ({d.file}); choose another name`) } } # a type the runtime declares - `property PadButton` beside its `enum PadButton` compiled, # and the two layouts met in the IR if ck_type_kind(d) != "" { var k = 0 while k < g_prog_user_end and k < len(prog) { let o = prog[k] if ck_type_kind(o) != "" and (o.s == d.s) { ck_err("shadow", o, `{d.s} is the runtime's {ck_type_kind(d)} ({d.file}); choose another name for this {ck_type_kind(o)}`) } k += 1 } } r += 1 } } function ck_type_kind(d: Node) -> pointer { if d.kind == N_ENUM { return "enum" } if d.kind == N_COMP and d.uns == 2 { return "state" } if d.kind == N_COMP { return "property" } if d.kind == N_STRUCT { return "record" } if d.kind == N_EVENT { return "event" } return "" } function ck_hooks(hs: []Node) -> void { var i = 0 while i < len(hs) { mg_enter(hs[i], 4) ck_block(hs[i].a) mg_leave() i += 1 } } function ck_scene_block(b: Node) -> void { if b == null { return } mg_enter(b, 5) ck_block(b) mg_leave() } # a retained ui widget (N_UI, whose number is also S_IF's): its props and its children function ck_widget(w: Node) -> void { if w == null { return } var i = 0 while w.b != null and i < len(w.b.kids) { ck_expr(w.b.kids[i].a) i += 1 } i = 0 while i < len(w.kids) { ck_widget(w.kids[i]) i += 1 } } # the pass also makes the generics real (check_gen.ludic), so it always runs function check_program() -> void { gen_collect() ck_index() ck_runtime_shadows() var i = 0 while i < len(prog) { let d = prog[i] if d.kind == N_FN { ck_fn_body(d) } if d.kind == N_MAIN or d.kind == N_TEST { mg_enter(d, 1) ck_block(d.a) mg_leave() } if d.kind == N_UI and d.ival == 1 and g_migrate { # 0.S2: a ui block's props, for what they read g_mg_cur = -3 ck_widget(d.a) g_mg_cur = -1 } if d.kind == N_SYS and g_migrate { # a handler's body: only the migration reads it here mg_enter(d, 2) ck_block(d.a) mg_leave() } if (d.kind == N_VAR or d.kind == N_CONST) and d.a != null { ck_vis_file = d.file ck_vis_skip = d.kind == N_VAR and is_port_var(d) ck_give(d.ty, ck_expr(d.a), d.a, d.s) ck_vis_file = null ck_vis_skip = false } i += 1 } var j = 0 while j < len(g_onlisten) { ck_listener(g_onlisten[j]) j += 1 } if g_migrate { # 0.S2: the hooks' bodies and the scenes', for what they touch ck_hooks(g_onspawn) ck_hooks(g_ondespawn) ck_hooks(g_onattach) ck_hooks(g_ondetach) ck_hooks(g_onenable) ck_hooks(g_ondisable) j = 0 while j < len(g_scenes) { ck_scene_block(g_scenes[j].a) ck_scene_block(g_scenes[j].b) j += 1 } } # a test block is checked like `entry`: that is also what makes its generic calls real j = 0 while j < len(g_tests) { mg_enter(g_tests[j], 3) ck_block(g_tests[j].a) mg_leave() j += 1 } gen_finish() if ck_errors > 0 and not ck_reporting() and not g_migrate { let m = `{itoa(ck_errors)} type error(s)\n` file_write(file_stderr(), m, len(m)) exit(1) } }