# eval.ludic - an expression's value, here function ui_eval(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val { if x.op == X_LIT { return x.v } if x.op == X_NAME { return env_get(ui_st, e, x.s) } if x.op == X_MEMBER { let o = ui_eval(ui_st, x.a, e) if Value.kind(o) == 6 and Value.has(o, x.s) != 0 { return Value.get(o, x.s) } if Value.kind(o) == 5 and x.s == "length" { return uv_int(ui_st, Value.count(o)) } @alloc_ok("only when a template is wrong: a broken screen, never a working one") ui_err(ui_st, `.{x.s}: there is no such field`) return Value.null() } if x.op == X_INDEX { return Value.at(ui_eval(ui_st, x.a, e), Value.as_int(ui_eval(ui_st, x.b, e))) } if x.op == X_CALL { return ev_call(ui_st, x, e) } if x.op == X_NOT { return vl_bool(ui_st, not vl_truthy(ui_eval(ui_st, x.a, e))) } if x.op == X_NEG { return vl_arith(ui_st, "-", mm_ival(ui_st, 0), ui_eval(ui_st, x.a, e)) } if x.op == X_AND { if not vl_truthy(ui_eval(ui_st, x.a, e)) { return vl_bool(ui_st, false) } return vl_bool(ui_st, vl_truthy(ui_eval(ui_st, x.b, e))) } if x.op == X_OR { if vl_truthy(ui_eval(ui_st, x.a, e)) { return vl_bool(ui_st, true) } return vl_bool(ui_st, vl_truthy(ui_eval(ui_st, x.b, e))) } if x.op == X_COND { if vl_truthy(ui_eval(ui_st, x.a, e)) { return ui_eval(ui_st, x.b, e) } return ui_eval(ui_st, x.c, e) } if x.op == X_TEXT { return ev_text(ui_st, x, e) } return vl_binary(ui_st, x.s, ui_eval(ui_st, x.a, e), ui_eval(ui_st, x.b, e)) } # a call: len(), range() and t() are the template's own, anything else is the view's function ev_call(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> Val { let depth = ui_st.ev_depth @alloc_ok("once per depth of nesting, then kept") while len(ui_st.ev_args) <= depth { push(ui_st.ev_args, Value.list()) } let args = ui_st.ev_args[depth] Value.clear(args) ui_st.ev_depth = depth + 1 for i in 0 .. len(x.args) { Value.add(args, ui_eval(ui_st, x.args[i], e)) } let r = ev_call_with(ui_st, x, e, args) ui_st.ev_depth = depth return r } # the call itself, its arguments evaluated; a callee reads them and keeps none function ev_call_with(ui_st: mut UiState, x: UiExpr, e: UiEnv, args: Val) -> Val { if x.s == "len" and len(x.args) == 1 { return uv_int(ui_st, Value.count(Value.at(args, 0))) } if x.s == "t" and len(x.args) >= 1 { return ev_t(ui_st, args) } # tkey.ludic: a key, and its holes if x.s == "range" and len(x.args) == 1 { let out = uv_list(ui_st) for i in 0 .. Value.as_int(Value.at(args, 0)) { Value.add(out, uv_int(ui_st, i)) } return out } var at = e while at != null { if at.inst != null { let r = at.inst.cls.call(at.inst.ptr, x.s, args, ev_into(ui_st)) if Value.kind(r) != 99 { return r } } at = at.parent } if ui_st.ui_view == null or ui_st.ui_view.call == null { @alloc_ok("only when a template is wrong: a broken screen, never a working one") ui_err(ui_st, `{x.s}(): there is no function or event of that name here`) return Value.null() } return ui_st.ui_view.call(x.s, args) } # where a call writes its answer: this build's pool, or while an action runs (the pool is off) the next # of EV_RING kept slots - an answer is read long before 64 more calls come round to it function ev_into(ui_st: mut UiState) -> Val { let v = uv_hold(ui_st) if v != null { return v } let ring = ui_st.ev_ring if len(ring) < EV_RING { ui_kept(ring, Value.null()) } let at = ui_st.ev_ring_at % len(ring) ui_st.ev_ring_at = at + 1 return ring[at] } const EV_RING: int = 64 function ev_ring_new() -> []Val { let l = new []Val for i in 0 .. EV_RING { push(l, Value.null()) } return l } function ui_eval_text(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> string { return vl_text(ui_eval(ui_st, x, e)) } function ui_eval_bool(ui_st: mut UiState, x: UiExpr, e: UiEnv) -> bool { return vl_truthy(ui_eval(ui_st, x, e)) }