ludic/tools/ui-preview/json.ludic
Orkuncakilkaya 49b5e80af5 ludic ui-preview: ludic.ui components previewed for a studio over stdio (R5)
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>
2026-09-29 19:53:26 +03:00

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()
}