# state.ludic — 0.S: no function writes a global. A module's changing data is a `state` record: # # state Hiker { hips: int = -1, spine: int = -1 } # # The program holds exactly one instance of each, which no code can name - it is the hidden global # `state$Hiker` (a name the lexer cannot produce). It reaches code only as a parameter, `h: mut # Hiker` to change it or `h: Hiker` to read it, and the runtime supplies it at the entry points: # - a body that declares it: `entry (h: mut Hiker)`, `handler Draw(h: Hiker) phase Render`, # `@On(E) handler Heard(h: mut Hiker)`, `test "name" (h: mut Hiker)` - each is a `let` at the # body's top bound to the instance; # - a function value: `fn f` of a function whose leading parameters are states is that function # with them supplied (emit_fnval.ludic writes the thunk), so a system, a port's bind and any # callback a package calls are entry points without saying so; # - a call the compiler writes: a namespace method's target, a runtime built-in. # A module-level `var` is refused (a `state` holds it), unless the build says --globals (the # toolchain's own programs, which are not part of this); a module-level `let` is deeply immutable. var g_state_names: []pointer = new []pointer var g_allow_globals: bool = false var g_gen_nodes: bool = false # the finishing passes are writing code: its nodes are generated # a call the compiler wrote itself (a view's model, a component's glue): its callee's states are # supplied - the runtime's side of an entry point function state_inject_generated(e: Node, f: Node) -> void { if e != null and e.pos < 0 { state_inject(e, f) } } # `(h: mut Hiker)` copied onto the front of a function's parameters (a view's functions) function state_params_onto(f: Node, ps: []Node) -> void { if len(ps) == 0 { return } let kids = new []Node var i = 0 while i < len(ps) { let p = node(N_PARAM) p.s = ps[i].s p.ty = ps[i].ty p.uns = ps[i].uns p.file = ps[i].file p.line = ps[i].line push(kids, p) i += 1 } i = 0 while i < len(f.kids) { push(kids, f.kids[i]) i += 1 } f.kids = kids } function state_global(name: pointer) -> pointer { return `state${name}` } function is_state_ty(t: pointer) -> bool { if t == null { return false } var i = 0 while i < len(g_state_names) { if (g_state_names[i] == t) { return true } i += 1 } return false } # state NAME { fields } - a record, and its one instance function parse_state() -> void { let c = parse_component() c.uns = 2 push(prog, c) push(g_state_names, c.s) let v = node(N_VAR) v.s = state_global(c.s) v.ty = c.s v.line = c.line v.uns = 1 let nw = node(E_NEW) nw.s = c.s nw.file = c.file nw.line = c.line v.a = nw push(prog, v) } # `name: mut T` in a parameter list: the `mut`, when it is there function parse_param_mut(p: Node) -> void { if is_id("mut") and toks[pi + 1].kind == TK_ID { pi += 1 p.uns = 1 } } # `(h: mut Hiker, ...)` before an entry point's body: the states the runtime supplies, as the # `let`s that open the body function parse_entry_params() -> []Node { let out = new []Node if not is_op("(") { return out } pi += 1 while not is_op(")") { skipnl() let p = node(N_PARAM) p.s = eat_id() eat_op(":") parse_param_mut(p) p.ty = ptype() push(out, p) skipnl() if is_op(",") { pi += 1 } } eat_op(")") return out } # a call the compiler writes (a namespace method's target, a runtime built-in) to a function whose # leading parameters are states: the instances, unless the call already gives every parameter function state_inject(e: Node, f: Node) -> void { if f == null or e == null { return } var k = 0 var total = 0 var i = 0 while i < len(f.kids) { if f.kids[i].kind == N_PARAM { if total == k and is_state_ty(f.kids[i].ty) { k += 1 } total += 1 } i += 1 } if k == 0 or len(e.kids) + k > total { return } let args = new []Node i = 0 while i < k { let id = node(E_ID) id.s = state_global(f.kids[i].ty) id.file = e.file id.line = e.line push(args, id) i += 1 } i = 0 while i < len(e.kids) { push(args, e.kids[i]) i += 1 } e.kids = args } # a retained `ui` block is an entry point: `font: MenuState.title_font` reads the instance function ui_state_refs(n: Node) -> void { if n == null { return } if n.kind == E_MEMBER and n.a != null and n.a.kind == E_ID and is_state_ty(n.a.s) { n.a.s = state_global(n.a.s) } ui_state_refs(n.a) ui_state_refs(n.b) ui_state_refs(n.c) var i = 0 while i < len(n.kids) { ui_state_refs(n.kids[i]) i += 1 } } function ui_blocks_states() -> void { var i = 0 while i < len(prog) { if prog[i].kind == N_UI and prog[i].ival == 1 { ui_state_refs(prog[i].a) } i += 1 } } function entry_bind(body: Node, ps: []Node) -> void { if len(ps) == 0 or body == null { return } let lets = new []Node var i = 0 while i < len(ps) { let p = ps[i] let l = node(S_LET) l.s = p.s l.ty = p.ty l.file = p.file l.line = p.line l.ival = 0 l.uns = p.uns # 1: a `mut` state; 0: read-only l.tps = "state" # an entry point's state: checked to be one let id = node(E_ID) id.s = state_global(p.ty) id.file = p.file id.line = p.line l.a = id push(lets, l) i += 1 } var k = 0 while k < len(body.kids) { push(lets, body.kids[k]) k += 1 } body.kids = lets }