/// JSON 序列化:Agent 协议的文本面(读取文档、接收结构化错误,docs/05/07)。
/// v1 只做编码;JSON → Patch 的解码在 M1 桥接层(MoonBit JS 后端)落地。
///|
/// JSON 字符串转义(含引号、反斜杠与全部 C0 控制符——\b \f 等按 RFC 8259
/// 统一输出 \u00xx,裸控制字符合法 JSON 不允许;playground 协议层复用)。
pub fn json_escape(s : String) -> String {
let sb = StringBuilder::new()
for c in s.iter() {
match c {
'"' => sb.write_string("\\\"")
'\\' => sb.write_string("\\\\")
'\n' => sb.write_string("\\n")
'\r' => sb.write_string("\\r")
'\t' => sb.write_string("\\t")
_ => {
let code = c.to_int()
if code < 0x20 {
write_c0_u_escape(sb, code)
} else {
sb.write_char(c)
}
}
}
}
sb.to_string()
}
///|
/// C0 控制符(0x00–0x1F)→ \u00xx(小写十六进制)。json_escape 与
/// export.mbt 的 js_string_escape 共用的机械编码尾段——两处转义策略
/// 各自独立(JSON 协议语义 / JS 字符串语义),仅这一段逐字相同。
fn write_c0_u_escape(sb : StringBuilder, code : Int) -> Unit {
let hi = (code >> 4) & 15
let lo = code & 15
sb.write_string("\\u00")
sb.write_char(Char::from_int(if hi < 10 { hi + 48 } else { hi + 87 }))
sb.write_char(Char::from_int(if lo < 10 { lo + 48 } else { lo + 87 }))
}
///|
/// 文档的规范代码表示:嵌套 children 的 JSON 树。
/// Agent 读取这份 JSON 来理解当前设计,然后提交补丁。
pub fn Document::to_json(self : Document) -> String {
let sb = StringBuilder::new()
sb.write_string(
"{\"revision\":\{self.revision},\"root_id\":\"\{json_escape(self.root_id)}\",\"nodes\":",
)
write_node_json(sb, self, self.root_id)
sb.write_string("}")
sb.to_string()
}
///|
fn write_node_json(sb : StringBuilder, doc : Document, id : String) -> Unit {
match doc.nodes.get(id) {
None => sb.write_string("null")
Some(n) => {
let kind : String = match n.kind {
Frame => "frame"
Group => "group"
Rect => "rect"
Text => "text"
Image => "image"
}
sb.write_string(
"{\"id\":\"\{json_escape(n.id)}\",\"kind\":\"\{kind}\",\"name\":\"\{json_escape(n.name)}\"," +
"\"component\":\"\{json_escape(n.component)}\"," +
"\"width\":\{size_spec_json(n.width)},\"height\":\{size_spec_json(n.height)}," +
"\"x\":\{pos_spec_json(n.x)},\"y\":\{pos_spec_json(n.y)},\"text\":\"\{json_escape(n.text)}\"," +
"\"constraint\":\"\{n.constraint}\"," +
"\"style\":{\"fill\":\"\{n.style.fill}\",\"stroke\":\"\{n.style.stroke}\",\"stroke_width\":\{n.style.stroke_width},\"corner_radius\":\{n.style.corner_radius},\"opacity\":\{n.style.opacity},\"font_size\":\{n.style.font_size},\"text_color\":\"\{n.style.text_color}\",\"font_weight\":\"\{n.style.font_weight}\",\"shadow\":\"\{n.style.shadow}\",\"rotate\":\{n.style.rotate},\"blur\":\{n.style.blur},\"blend\":\"\{n.style.blend}\",\"line_height\":\{n.style.line_height},\"tracking\":\{n.style.tracking},\"flip\":\"\{n.style.flip}\",\"text_align\":\"\{n.style.text_align}\",\"font_style\":\"\{n.style.font_style}\",\"stroke_dash\":\"\{n.style.stroke_dash}\",\"visible\":\{n.style.visible}},\"children\":[",
)
for i in 0.. 0 {
sb.write_string(",")
}
write_node_json(sb, doc, n.children[i])
}
sb.write_string("]}")
}
}
}
///|
fn size_spec_json(s : SizeSpec) -> String {
match s {
Fixed(v) => "{\"kind\":\"fixed\",\"value\":\{v}}"
Fill => "{\"kind\":\"fill\"}"
Hug => "{\"kind\":\"hug\"}"
}
}
///|
fn pos_spec_json(s : PosSpec) -> String {
match s {
Fixed(v) => "{\"kind\":\"fixed\",\"value\":\{v}}"
AlignStart(v) => "{\"kind\":\"align_start\",\"value\":\{v}}"
AlignEnd(v) => "{\"kind\":\"align_end\",\"value\":\{v}}"
CenterInParent => "{\"kind\":\"center\"}"
}
}
///|
pub fn Violation::to_json(self : Violation) -> String {
"{\"predicate\":\"\{self.predicate.name()}\",\"node_id\":\"\{json_escape(self.node_id)}\",\"detail\":\"\{json_escape(self.detail)}\"}"
}
///|
pub fn PatchError::to_json(self : PatchError) -> String {
match self {
StaleBase(cur) =>
"{\"error\":\"stale_base\",\"current_revision\":\{cur},\"hint\":\"文档已前进,请重新读取后基于最新 revision 提交\"}"
UnknownNode(id) =>
"{\"error\":\"unknown_node\",\"node_id\":\"\{json_escape(id)}\"}"
UnknownParent(id) =>
"{\"error\":\"unknown_parent\",\"node_id\":\"\{json_escape(id)}\"}"
DuplicateId(id) =>
"{\"error\":\"duplicate_id\",\"node_id\":\"\{json_escape(id)}\"}"
HierarchyError(msg) =>
"{\"error\":\"hierarchy_error\",\"detail\":\"\{json_escape(msg)}\"}"
StructuralBlock(vs) => block_json("structural_block", vs)
HardBlock(vs) => block_json("hard_block", vs)
}
}
///|
fn block_json(kind : String, vs : Array[Violation]) -> String {
let sb = StringBuilder::new()
sb.write_string("{\"error\":\"\{kind}\",\"violations\":[")
for i in 0.. 0 {
sb.write_string(",")
}
sb.write_string(vs[i].to_json())
}
sb.write_string("]}")
sb.to_string()
}
///|
/// 字符串数组 → JSON 数组文本(逐项 json_escape;空数组恒输出 `[]`——
/// issue #10 纪律:envelope 集合键不允许裸拼接)。信封构造的统一出口。
pub fn json_string_array(items : Array[String]) -> String {
let parts : Array[String] = []
for it in items.iter() {
parts.push("\"" + json_escape(it) + "\"")
}
"[" + parts.join(",") + "]"
}