A prebuilt tool, bin/ludic-ui-preview (built by ludic-dev build, shipped beside ludic-lsp, run as `ludic ui-preview [--font DIR]`): ludic.ui with a backend that records every draw call as a line, and mock component classes the studio describes (load / model / calls). frame t runs ui_show at interface time t; text is measured on the CPU with the game's font.json metrics; locale goes through ludic.i18n; tree / box / rules inspect the frame. No game code, no GPU, no network. The wire protocol is frozen as tools/ui-preview/protocol-v1.md; smoke.txt is a transcript to run. ludic.ui gains, all additive: UiClass.make_of, UiBackend.said / emitted, UiRule.text / at (with comment line breaks kept so rule lines count true, and a class's styles read under its .lss path), and ui_root, ui_find, ui_rules_of, ui_rule_value, ui_building, ui_class, ui_class_load, ui_file_forget, ui_errors_clear; ui_nine_cuts_into exported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
67 lines
2 KiB
Text
67 lines
2 KiB
Text
# json.ludic - a Value written into the reply as one line of JSON (strings quoted as pv_q quotes every
|
|
# string, numbers as pv_num writes them), and a Value copied deep
|
|
function pv_json(o: PvOut, v: Val) -> void {
|
|
pv_byte(o, 32)
|
|
pv_json_in(o, v)
|
|
}
|
|
function pv_json_in(o: PvOut, v: Val) -> void {
|
|
let k = Value.kind(v)
|
|
if k == 1 {
|
|
pv_digits(o, Value.as_int(v))
|
|
} else if k == 2 or k == 7 {
|
|
let at = o.n
|
|
pv_num(o, Value.as_float(v))
|
|
pv_unspace(o, at)
|
|
} else if k == 3 {
|
|
if Value.as_int(v) != 0 { pv_s(o, "true") } else { pv_s(o, "false") }
|
|
} else if k == 4 {
|
|
let at = o.n
|
|
pv_q(o, Value.as_str(v))
|
|
pv_unspace(o, at)
|
|
} else if k == 5 {
|
|
pv_byte(o, 91)
|
|
for i in 0 .. Value.count(v) {
|
|
if i > 0 { pv_byte(o, 44) }
|
|
pv_json_in(o, Value.at(v, i))
|
|
}
|
|
pv_byte(o, 93)
|
|
} else if k == 6 {
|
|
pv_byte(o, 123)
|
|
for i in 0 .. Value.count(v) {
|
|
if i > 0 { pv_byte(o, 44) }
|
|
let at = o.n
|
|
pv_q(o, Value.key_at(v, i))
|
|
pv_unspace(o, at)
|
|
pv_byte(o, 58)
|
|
pv_json_in(o, Value.at(v, i))
|
|
}
|
|
pv_byte(o, 125)
|
|
} else { pv_s(o, "null") }
|
|
}
|
|
# the space pv_num and pv_q put before what they write (at `at`), taken back inside a JSON value
|
|
function pv_unspace(o: PvOut, at: int) -> void {
|
|
unsafe {
|
|
for i in at .. o.n - 1 { o.p[i] = o.p[i + 1] }
|
|
}
|
|
o.n -= 1
|
|
}
|
|
# a Value and everything under it made again, so nothing is shared with the original
|
|
@alloc_ok("an action's set: a press, never a build")
|
|
function pv_copy(v: Val) -> Val {
|
|
let k = Value.kind(v)
|
|
if k == 1 { return Value.int(Value.as_int(v)) }
|
|
if k == 2 or k == 7 { return Value.float(Value.as_float(v)) }
|
|
if k == 3 { return Value.bool(Value.as_int(v)) }
|
|
if k == 4 { return Value.str(Value.as_str(v)) }
|
|
if k == 5 {
|
|
let l = Value.list()
|
|
for i in 0 .. Value.count(v) { Value.add(l, pv_copy(Value.at(v, i))) }
|
|
return l
|
|
}
|
|
if k == 6 {
|
|
let o = Value.object()
|
|
for i in 0 .. Value.count(v) { Value.put(o, Value.key_at(v, i), pv_copy(Value.at(v, i))) }
|
|
return o
|
|
}
|
|
return Value.null()
|
|
}
|