Skip to main content

dynamic/
yaml.rs

1use crate::{Dynamic, ZOnce};
2
3use anyhow::{Result, anyhow};
4use indexmap::IndexMap;
5use parking_lot::RwLock;
6use smol_str::SmolStr;
7use std::sync::Arc;
8
9/// 序列化方向:把一个 `Dynamic` 写成 YAML 文本。
10///
11/// 输出风格刻意保持"LLM 友好":
12/// - 默认 block style,序列用 `- item`,映射用 `key: value`。
13/// - 字符串尽量裸出;只有会改变语义或被误读时才加双引号。
14/// - 多行字符串用 `|` literal block scalar,保留换行,避免 `\n` 转义噪声。
15/// - 不输出 anchor / alias / 显式 `!!str` 标签,这些东西 LLM 经常搞错。
16pub trait ToYaml {
17    fn to_yaml(&self, buf: &mut String);
18}
19
20/// 反序列化方向:从 YAML 文本里解析出一个 `Dynamic`。
21///
22/// 只覆盖 LLM 常用 YAML 子集:标量(整数/浮点/字符串/bool/null)、block 风格
23/// mapping / sequence、有限的 flow 风格(嵌套用得不多)、注释。anchors / 复杂 tag
24/// / 折叠 scalar(`>`)/ 集合类型提示(`!!set` 等)不在支持范围内,遇到直接报错。
25pub trait FromYaml: Sized {
26    fn from_yaml(buf: &[u8]) -> Result<(Self, usize)>;
27}
28
29// ---- 序列化 -----------------------------------------------------------
30
31/// 判断字符串是否"必须"加引号,避免和 YAML 标量字面量冲突。
32///
33/// 规则:
34/// - 空串;
35/// - 首字符是 reserved indicator (`!`, `&`, `*`, `?`, `|`, `>`, `'`, `"`, `%`,
36///   `@`, `\``, `#`, `,`, `[`, `]`, `{`, `}`, `:`, `-`);
37/// - 包含 `: ` / ` #`(在普通位置会触发 mapping / 注释);
38/// - 看起来像数字、bool、null、~ 等保留字;
39/// - 包含控制字符;
40/// - 是单独的 `---` / `...` 这种文档分隔符。
41///
42/// LLM 经常输出类似 `"true"` 或 `"123"` 这样的"看起来是数字但其实是字符串"的
43/// 值,因此需要在"字符串内容本身合法但和标量字面量撞"时也强制加引号。
44fn yaml_string_needs_quoting(s: &str) -> bool {
45    if s.is_empty() {
46        return true;
47    }
48    // 文档分隔符不能裸出
49    if s == "---" || s == "..." {
50        return true;
51    }
52    let bytes = s.as_bytes();
53    // 首字符限定
54    const RESERVED_FIRST: &[u8] = b"!\"&'*|>%@`,[]{}#?:";
55    if RESERVED_FIRST.contains(&bytes[0]) {
56        return true;
57    }
58    // 数字 / null / bool 字面量全比较一遍,大小写敏感 (YAML 1.2)。
59    if matches!(
60        s,
61        "true"
62            | "false"
63            | "null"
64            | "Null"
65            | "NULL"
66            | "~"
67            | "yes"
68            | "no"
69            | "on"
70            | "off"
71            | "True"
72            | "False"
73            | "TRUE"
74            | "FALSE"
75            | "YES"
76            | "NO"
77            | "ON"
78            | "OFF"
79    ) {
80        return true;
81    }
82    // 像数字的字符串也需要引号,避免下游解析误读。
83    if looks_like_number(s) {
84        return true;
85    }
86    // 走一遍内容,处理 ": "、首尾空格、tab、控制字符、"#" 前有空格
87    for (idx, b) in bytes.iter().enumerate() {
88        match *b {
89            b'\n' | b'\t' => return true,
90            0..=0x1f => return true,
91            b':' if idx + 1 < bytes.len() && (bytes[idx + 1] == b' ' || bytes[idx + 1] == b'\t') => return true,
92            b'#' if idx > 0 && (bytes[idx - 1] == b' ' || bytes[idx - 1] == b'\t') => return true,
93            _ => {}
94        }
95    }
96    // 首字符是 `-` 或 `?` 时为了避免被解析成 sequence / complex key,也加引号。
97    // (已在 RESERVED_FIRST 包含)
98    // 末尾空格 / tab 需要引号,否则会被 trim 掉。
99    if matches!(bytes.last(), Some(b' ' | b'\t')) {
100        return true;
101    }
102    false
103}
104
105fn looks_like_number(s: &str) -> bool {
106    // 包含 `.` / `e` / `E` 当作浮点尝试;否则当整数。允许前导 `+` / `-`。
107    if s.eq_ignore_ascii_case("inf") || s.eq_ignore_ascii_case("nan") || s.eq_ignore_ascii_case(".inf") || s.eq_ignore_ascii_case(".nan") {
108        return true;
109    }
110    if s.contains('.') || s.contains('e') || s.contains('E') {
111        return s.parse::<f64>().is_ok()
112    } else {
113        return s.parse::<i64>().is_ok()
114    }
115}
116
117/// 把字符串内容里必须转义的字符按 YAML 双引号 scalar 的规则写出。
118fn yaml_quote_string(s: &str, buf: &mut String) {
119    buf.push('"');
120    for ch in s.chars() {
121        match ch {
122            '\\' => buf.push_str("\\\\"),
123            '"' => buf.push_str("\\\""),
124            '\n' => buf.push_str("\\n"),
125            '\r' => buf.push_str("\\r"),
126            '\t' => buf.push_str("\\t"),
127            '\0' => buf.push_str("\\0"),
128            '\x08' => buf.push_str("\\b"),
129            '\x0c' => buf.push_str("\\f"),
130            c if (c as u32) < 0x20 => {
131                use std::fmt::Write;
132                let _ = write!(buf, "\\u{:04x}", c as u32);
133            }
134            c => buf.push(c),
135        }
136    }
137    buf.push('"');
138}
139
140/// 多行字符串用 `|` literal block scalar 输出。
141/// `|` 之后的 chomping indicator 用 `+` 表示保留末尾所有换行;LLM 解析 YAML 时
142/// 这个最不会出错,代价是多一两行。块缩进以首个非空行的缩进为基准。
143fn yaml_block_string(s: &str, indent: usize, buf: &mut String) {
144    let pad = " ".repeat(indent);
145    buf.push_str("|+\n");
146    let mut count = 0usize;
147    for ch in s.chars() {
148        buf.push_str(&pad);
149        if ch == '\n' {
150            buf.push('\n');
151            count = 0;
152        } else {
153            buf.push(ch);
154            count += 1;
155        }
156    }
157    if count > 0 {
158        buf.push('\n');
159    }
160}
161
162/// 把一个字符串写出。优先裸出;必要时双引号;含换行时用 `|` block。
163fn yaml_write_string(s: &str, indent: usize, buf: &mut String) {
164    if s.contains('\n') {
165        yaml_block_string(s, indent, buf);
166        return;
167    }
168    if yaml_string_needs_quoting(s) {
169        yaml_quote_string(s, buf);
170    } else {
171        buf.push_str(s);
172    }
173}
174
175fn yaml_write_key(key: &str, buf: &mut String) {
176    // key 不强制缩进风格;统一处理成"裸出优先,不行就加引号"。
177    if yaml_string_needs_quoting(key) {
178        yaml_quote_string(key, buf);
179    } else {
180        buf.push_str(key);
181    }
182}
183
184impl ToYaml for &str {
185    fn to_yaml(&self, buf: &mut String) {
186        yaml_write_string(self, 0, buf);
187    }
188}
189
190impl ToYaml for i64 {
191    fn to_yaml(&self, buf: &mut String) {
192        buf.push_str(&self.to_string());
193    }
194}
195
196impl ToYaml for Dynamic {
197    fn to_yaml(&self, buf: &mut String) {
198        self.to_yaml_indent(0, buf);
199    }
200}
201
202impl Dynamic {
203    /// 把当前值以 YAML 写出,根节点的缩进是 `indent`(通常传 0)。
204    /// 顶层若不是 mapping / sequence,需要补一行 `---` 开头,避免直接写裸字符串
205    /// 看起来像单 key 的文档。这是 LLM 阅读时一个常见的边界 bug。
206    fn to_yaml_indent(&self, indent: usize, buf: &mut String) {
207        match self {
208            Self::Iter { .. } => {}
209            Self::Null => buf.push_str("null"),
210            Self::Bool(b) => buf.push_str(if *b { "true" } else { "false" }),
211            Self::F16(bits) => buf.push_str(&crate::f16_to_f64(*bits).to_string()),
212            Self::F32(f) => buf.push_str(&f.to_string()),
213            Self::F64(f) => buf.push_str(&f.to_string()),
214            Self::I8(i) => buf.push_str(&i.to_string()),
215            Self::I16(i) => buf.push_str(&i.to_string()),
216            Self::I32(i) => buf.push_str(&i.to_string()),
217            Self::I64(i) => buf.push_str(&i.to_string()),
218            Self::U8(i) => buf.push_str(&i.to_string()),
219            Self::U16(i) => buf.push_str(&i.to_string()),
220            Self::U32(i) => buf.push_str(&i.to_string()),
221            Self::U64(i) => buf.push_str(&i.to_string()),
222            Self::String(s) => yaml_write_string(s.as_str(), indent, buf),
223            Self::StringBuf(s) => yaml_write_string(s.as_str(), indent, buf),
224            Self::Bytes(vec) => yaml_write_seq(vec.iter().map(|b| Dynamic::U8(*b)), indent, buf),
225            Self::VecI8(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::I8(*v)), indent, buf),
226            Self::VecU16(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::U16(*v)), indent, buf),
227            Self::VecI16(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::I16(*v)), indent, buf),
228            Self::VecU32(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::U32(*v)), indent, buf),
229            Self::VecI32(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::I32(*v)), indent, buf),
230            Self::VecF32(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::F32(*v)), indent, buf),
231            Self::VecU64(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::U64(*v)), indent, buf),
232            Self::VecI64(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::I64(*v)), indent, buf),
233            Self::VecF64(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::F64(*v)), indent, buf),
234            Self::List(items) => {
235                let items = items.read().clone();
236                yaml_write_seq(items.into_iter(), indent, buf);
237            }
238            Self::Map(map) => {
239                let map = map.read().clone();
240                yaml_write_map(map.into_iter(), indent, buf);
241            }
242            Self::StructView { .. } | Self::StructOwned { .. } => {
243                let keys = self.keys();
244                if keys.is_empty() {
245                    buf.push_str("{}\n");
246                    return;
247                }
248                for key in keys.iter() {
249                    buf.push_str(&" ".repeat(indent));
250                    yaml_write_key(key.as_str(), buf);
251                    buf.push(':');
252                    let value = self.get_dynamic(key).unwrap_or(Dynamic::Null);
253                    if is_yaml_block(&value) {
254                        buf.push('\n');
255                        value.to_yaml_indent(indent + 2, buf);
256                    } else {
257                        buf.push(' ');
258                        value.to_yaml_indent(0, buf);
259                        buf.push('\n');
260                    }
261                }
262            }
263            Self::Custom(value) => {
264                buf.push_str("{@custom: ");
265                yaml_write_string(value.custom_type_name(), indent, buf);
266                buf.push_str("}\n");
267            }
268        }
269    }
270}
271
272/// 序列和映射的 child 是否需要展开到下一行(block style),还是能塞进当前行
273/// (flow scalar)。标量、null、bool、纯数字字符串这些可以直接 inline;mapping /
274/// sequence 必须换行展开。
275fn is_yaml_block(value: &Dynamic) -> bool {
276    matches!(value, Dynamic::Map(_) | Dynamic::List(_) | Dynamic::Bytes(_) | Dynamic::StructView { .. } | Dynamic::StructOwned { .. })
277        || matches!(value, Dynamic::VecI8(_) | Dynamic::VecU16(_) | Dynamic::VecI16(_) | Dynamic::VecU32(_) | Dynamic::VecI32(_) | Dynamic::VecF32(_) | Dynamic::VecU64(_) | Dynamic::VecI64(_) | Dynamic::VecF64(_))
278}
279
280fn yaml_write_seq<I: Iterator<Item = Dynamic>>(items: I, indent: usize, buf: &mut String) {
281    let pad = " ".repeat(indent);
282    let mut first = true;
283    for item in items {
284        // 第一个 item 用 pad (无 newline);后续 item 用 "\n + pad" 分隔。
285        buf.push_str(if first { &pad } else { "\n" });
286        if !first {
287            buf.push_str(&pad);
288        }
289        first = false;
290        buf.push_str("- ");
291        // 序列里的 Map 项用紧凑形式:首条 `key: value` 跟 `- ` 同行,
292        // 后续 key 缩进对齐。这是 LLM 最容易看懂的列表项写法。
293        if let Some(pairs) = map_first_pair_inline(&item) {
294            yaml_write_key(pairs.0.as_str(), buf);
295            buf.push(':');
296            if is_yaml_block(&pairs.1) {
297                buf.push('\n');
298                pairs.1.to_yaml_indent(indent + 4, buf);
299            } else {
300                buf.push(' ');
301                pairs.1.to_yaml_indent(0, buf);
302            }
303            if let Some(rest) = map_remaining_pairs(&item) {
304                // 第一条 sibling key 必须换到新行;yaml_write_map 自己
305                // 负责 pad 和后续换行,不要再手动 push pad(否则会跟 map
306                // 内部的首条 pad 叠加成双倍缩进)。
307                buf.push('\n');
308                yaml_write_map(rest, indent + 2, buf);
309            }
310        } else if is_yaml_block(&item) {
311            let one_more = indent + 2;
312            buf.push('\n');
313            item.to_yaml_indent(one_more, buf);
314        } else {
315            item.to_yaml_indent(0, buf);
316        }
317    }
318    if first {
319        // 空序列:用 flow 形式 `[]`,避免下游解析器拿到空 buf。
320        buf.push_str("[]");
321    }
322}
323
324fn yaml_write_map<I: Iterator<Item = (SmolStr, Dynamic)>>(entries: I, indent: usize, buf: &mut String) {
325    let pad = " ".repeat(indent);
326    let mut first = true;
327    for (key, value) in entries {
328        if first {
329            // 嵌套 map(indent>0)的第一条 entry 需要缩进;
330            // 根 map(indent==0)由调用方负责前导换行,不加 pad。
331            if indent > 0 {
332                buf.push_str(&pad);
333            }
334        } else {
335            buf.push('\n');
336            buf.push_str(&pad);
337        }
338        first = false;
339        yaml_write_key(key.as_str(), buf);
340        buf.push(':');
341        if is_yaml_block(&value) {
342            buf.push('\n');
343            value.to_yaml_indent(indent + 2, buf);
344        } else {
345            buf.push(' ');
346            value.to_yaml_indent(0, buf);
347        }
348    }
349    if indent == 0 {
350        buf.push('\n');
351    }
352}
353
354/// 如果 `value` 是一个至少有 1 个 entry 的 map,返回第一个 (key, value) 对;
355/// 否则返回 None。给序列里的 map 紧凑写法用。
356fn map_first_pair_inline(value: &Dynamic) -> Option<(SmolStr, Dynamic)> {
357    if let Dynamic::Map(m) = value {
358        let m = m.read();
359        let first = m.iter().next()?;
360        Some((first.0.clone(), first.1.clone()))
361    } else {
362        None
363    }
364}
365
366/// 返回 map 剩余 (除第一对外) 的 entry 迭代器。
367fn map_remaining_pairs(value: &Dynamic) -> Option<impl Iterator<Item = (SmolStr, Dynamic)>> {
368    if let Dynamic::Map(m) = value {
369        let m = m.read().clone();
370        let mut iter = m.into_iter();
371        iter.next();
372        Some(iter)
373    } else {
374        None
375    }
376}
377
378// ---- 反序列化 ---------------------------------------------------------
379
380/// 跳过空白和注释。YAML 注释从 `#` 开始,到行尾结束。
381/// 跟 json 不同,空白是空格 / tab / 换行 + 注释。
382fn skip_white(buf: &[u8], mut pos: usize) -> Result<usize> {
383    while pos < buf.len() {
384        match buf[pos] {
385            b' ' | b'\t' | b'\r' | b'\n' => pos += 1,
386            b'#' => {
387                while pos < buf.len() && buf[pos] != b'\n' {
388                    pos += 1;
389                }
390            }
391            _ => break,
392        }
393    }
394    Ok(pos)
395}
396
397/// 返回当前行(从行首到 pos)的缩进空格数。空 buffer 返回 0。
398fn line_indent(buf: &[u8], pos: usize) -> usize {
399    let mut p = pos;
400    while p > 0 && buf[p - 1] != b'\n' {
401        p -= 1;
402    }
403    let mut count = 0usize;
404    // 数到非空字符为止;`while p < pos` 是错的,pos 本身可能就是首字符,
405    // 那时一次都不进入循环。
406    while p < buf.len() && buf[p] == b' ' {
407        count += 1;
408        p += 1;
409    }
410    count
411}
412
413/// 看 pos 处是不是 `\n` 或文件末尾。
414fn at_line_end(buf: &[u8], pos: usize) -> bool {
415    pos >= buf.len() || buf[pos] == b'\n'
416}
417
418/// 判断一行是否完全为空 (只有空白 / 注释 / 换行)。
419fn line_is_blank(buf: &[u8], mut pos: usize) -> bool {
420    while pos < buf.len() && buf[pos] != b'\n' {
421        match buf[pos] {
422            b' ' | b'\t' | b'\r' => pos += 1,
423            b'#' => return true,
424            _ => return false,
425        }
426    }
427    true
428}
429
430/// 看接下来的 token 是否以 `---` (文档开始) 开头。
431/// 这里只把 `---` 当一个"可选的前缀"吃掉,不影响实际语义。
432fn skip_doc_marker(buf: &[u8], mut pos: usize) -> usize {
433    pos = skip_white(buf, pos).unwrap_or(pos);
434    if buf.get(pos..pos + 3) == Some(b"---") && (pos + 3 == buf.len() || matches!(buf[pos + 3], b' ' | b'\t' | b'\n' | b'\r')) {
435        pos += 3;
436    }
437    skip_white(buf, pos).unwrap_or(pos)
438}
439
440/// 解析一个 YAML scalar 字面量(到行尾或遇到 `#` 注释为止)。
441/// 返回 (去掉 trailing comment 后的内容, 消耗的字节数)。
442///
443/// 这里不处理 multi-line scalar (`|` / `>`);要支持的话需要在 mapping / sequence
444/// 解析中专门检测。当前实现里,parser 一旦进入 scalar 就只读本行,后面的多行
445/// 内容会被当成"意外的额外内容"报错 —— 这是有意的取舍,LLM 输出通常不会写
446/// 折叠 scalar。
447fn read_scalar(buf: &[u8], pos: usize) -> Result<(&str, usize)> {
448    let start = pos;
449    let mut end = pos;
450    let mut in_flow_bracket = 0i32; // [ ] { } 计数,处理 inline 集合里的 scalar
451    let mut in_double_quote = false;
452    let mut in_single_quote = false;
453    while end < buf.len() {
454        let b = buf[end];
455        if in_double_quote {
456            if b == b'\\' && end + 1 < buf.len() {
457                end += 2;
458                continue;
459            }
460            if b == b'"' {
461                in_double_quote = false;
462                end += 1;
463                continue;
464            }
465            end += 1;
466            continue;
467        }
468        if in_single_quote {
469            if b == b'\'' {
470                if end + 1 < buf.len() && buf[end + 1] == b'\'' {
471                    // '' 是单引号里的转义,跳两个字符。
472                    end += 2;
473                    continue;
474                }
475                in_single_quote = false;
476                end += 1;
477                continue;
478            }
479            end += 1;
480            continue;
481        }
482        match b {
483            b'"' => {
484                in_double_quote = true;
485                end += 1;
486            }
487            b'\'' => {
488                in_single_quote = true;
489                end += 1;
490            }
491            b'[' | b'{' => {
492                in_flow_bracket += 1;
493                end += 1;
494            }
495            b']' | b'}' => {
496                // `]` / `}` 在 plain scalar 上下文里就是终止符,
497                // 不管是不是在 flow 集合里 —— 调用方会在外层恢复对它们的处理。
498                break;
499            }
500            b',' => {
501                // `,` 是 flow 集合分隔符,plain scalar 遇到直接终止。
502                // 用户如果真的需要带 `,` 的裸字符串,自己加引号。
503                break;
504            }
505            b':' if in_flow_bracket == 0 && end + 1 < buf.len() && (buf[end + 1] == b' ' || buf[end + 1] == b'\t' || buf[end + 1] == b'\n') => {
506                // mapping 的 key:value 分隔,跳出。
507                break;
508            }
509            b'#' if in_flow_bracket == 0 => break,
510            b'\n' if in_flow_bracket == 0 => break,
511            _ => end += 1,
512        }
513    }
514    // 裁掉 trailing 空白。
515    while end > start && matches!(buf[end - 1], b' ' | b'\t' | b'\r') {
516        end -= 1;
517    }
518    if end == start {
519        return Err(anyhow!("yaml scalar 为空 @{}", start));
520    }
521    std::str::from_utf8(&buf[start..end]).map(|s| (s, end - start)).map_err(|e| anyhow!("yaml scalar 含非法 UTF-8: {e}"))
522}
523
524/// 把 scalar token 解析成 Dynamic:支持整数 / 浮点 / bool / null / 普通字符串。
525fn scalar_to_dynamic(raw: &str) -> Dynamic {
526    // null / ~
527    if matches!(raw, "" | "null" | "Null" | "NULL" | "~") {
528        return Dynamic::Null;
529    }
530    // bool (YAML 1.2 只有 true/false,大写也接受。LLM 经常写 yes/no,这里
531    // 也兼容一下,但这是非标准的,使用时要谨慎)。
532    match raw {
533        "true" | "True" | "TRUE" => return Dynamic::Bool(true),
534        "false" | "False" | "FALSE" => return Dynamic::Bool(false),
535        _ => {}
536    }
537    // 整数优先,失败再试浮点。
538    if let Ok(v) = raw.parse::<i64>() {
539        return Dynamic::I64(v);
540    }
541    if let Ok(v) = raw.parse::<f64>() {
542        if v.is_finite() {
543            return Dynamic::F64(v);
544        }
545    }
546    // 字符串:处理双引号 / 单引号包裹 + 转义。
547    if raw.starts_with('"') && raw.ends_with('"') && raw.len() >= 2 {
548        return Dynamic::String(unescape_double_quoted(&raw[1..raw.len() - 1]).into());
549    }
550    if raw.starts_with('\'') && raw.ends_with('\'') && raw.len() >= 2 {
551        return Dynamic::String(unescape_single_quoted(&raw[1..raw.len() - 1]).into());
552    }
553    Dynamic::String(raw.into())
554}
555
556fn unescape_double_quoted(s: &str) -> String {
557    let mut out = String::with_capacity(s.len());
558    let mut chars = s.chars();
559    while let Some(c) = chars.next() {
560        if c == '\\' {
561            match chars.next() {
562                Some('n') => out.push('\n'),
563                Some('t') => out.push('\t'),
564                Some('r') => out.push('\r'),
565                Some('\\') => out.push('\\'),
566                Some('"') => out.push('"'),
567                Some('0') => out.push('\0'),
568                Some('/') => out.push('/'),
569                Some('u') => {
570                    let hex: String = chars.by_ref().take(4).collect();
571                    if let Ok(code) = u32::from_str_radix(&hex, 16) {
572                        if let Some(ch) = char::from_u32(code) {
573                            out.push(ch);
574                        }
575                    }
576                }
577                Some(other) => {
578                    out.push('\\');
579                    out.push(other);
580                }
581                None => out.push('\\'),
582            }
583        } else {
584            out.push(c);
585        }
586    }
587    out
588}
589
590fn unescape_single_quoted(s: &str) -> String {
591    // 单引号 scalar 里,所有内容都是字面量,只有 '' 代表单个 '。
592    s.replace("''", "'")
593}
594
595/// `from_yaml` 入口。跟 `from_json` 一样返回 (value, consumed)。
596impl FromYaml for Dynamic {
597    fn from_yaml(buf: &[u8]) -> Result<(Self, usize)> {
598        let pos = skip_doc_marker(buf, 0);
599        parse_node(buf, pos, 0, false)
600    }
601}
602
603/// 解析一个 YAML node。`min_indent` 是当前 block 的最小缩进,`in_block` 表示
604/// 我们正在某个 block 集合(mapping / sequence)的内部 —— 这种情况下一旦遇到
605/// 缩进小于 `min_indent` 的行,就应该结束。
606fn parse_node(buf: &[u8], pos: usize, min_indent: usize, in_block: bool) -> Result<(Dynamic, usize)> {
607    let pos = skip_white(buf, pos)?;
608    if pos >= buf.len() {
609        return Err(anyhow!("yaml 文档提前结束"));
610    }
611    // 先看是否是流集合 `{ ... }` / `[ ... ]`。
612    if buf[pos] == b'{' {
613        return parse_flow_map(buf, pos);
614    }
615    if buf[pos] == b'[' {
616        return parse_flow_seq(buf, pos);
617    }
618    // 否则是 block 风格。看当前行缩进以及接下来的 token 形态。
619    let indent = line_indent(buf, pos);
620    if indent < min_indent {
621        return Err(anyhow!("yaml 缩进不足: {} < {}", indent, min_indent));
622    }
623    if buf[pos] == b'-' && (pos + 1 == buf.len() || matches!(buf[pos + 1], b' ' | b'\t' | b'\n')) {
624        return parse_block_seq(buf, pos, indent);
625    }
626    // mapping 第一个 key:value 可能跟 caller 同一行(在 indent 等于 caller
627    // 缩进的情况下),所以不强制从行首开始。
628    if let Some((key, colon)) = try_read_key(buf, pos) {
629        // 把 pos 推到 `:` 之后;parse_block_map 不再重新扫描 key。
630        let value_pos = skip_white(buf, colon + 1)?;
631        return parse_block_map(buf, value_pos, indent, key);
632    }
633    // 走到这里说明是孤立的 scalar —— 只有当 caller 不要求 block 形态时才能
634    // 接受,否则应当报错(避免 mapping 里 `- foo` 被误读成 `foo`)。
635    if in_block {
636        return Err(anyhow!("yaml 期望 block 结构但遇到孤立标量 @{}", pos));
637    }
638    let (raw, consumed) = read_scalar(buf, pos)?;
639    Ok((scalar_to_dynamic(raw), pos + consumed))
640}
641
642/// 在 pos 处尝试读取一个 `key:` 形式的 mapping key。
643/// 成功时返回 (key, ':' 位置)。不修改 pos。
644fn try_read_key(buf: &[u8], pos: usize) -> Option<(SmolStr, usize)> {
645    let start = pos;
646    // 单 / 双引号包裹的 key 直接到对应引号收尾。
647    if buf[pos] == b'"' || buf[pos] == b'\'' {
648        let quote = buf[pos];
649        let mut p = pos + 1;
650        while p < buf.len() && buf[p] != quote {
651            if buf[p] == b'\\' && quote == b'"' && p + 1 < buf.len() {
652                p += 2;
653                continue;
654            }
655            p += 1;
656        }
657        if p >= buf.len() {
658            return None;
659        }
660        let key = &buf[start + 1..p];
661        let after = p + 1;
662        let after = skip_white(buf, after).ok()?;
663        if after >= buf.len() || buf[after] != b':' {
664            return None;
665        }
666        let key_str = std::str::from_utf8(key).ok()?;
667        return Some((SmolStr::from(if quote == b'"' { unescape_double_quoted(key_str) } else { unescape_single_quoted(key_str) }), after));
668    }
669    // 普通 key:扫到 `:` 或"明显不能作为 key 字符"为止。空白 / `,` / `}` / `]` /
670// `#` / `\n` 都属于 separator,提前终止。这样在 `{a: 1, b: 2}` 这种 inline 上下文里
671// 才不会把后面的 `, b: 2` 吞进 key。
672    let mut p = pos;
673    while p < buf.len() {
674        match buf[p] {
675            b':' if p + 1 < buf.len() && (matches!(buf[p + 1], b' ' | b'\t' | b'\n') || p + 1 == buf.len()) => break,
676            b' ' | b'\t' | b'\r' | b'\n' | b'#' | b',' | b'}' | b']' => return None,
677            _ => p += 1,
678        }
679    }
680    if p >= buf.len() || buf[p] != b':' {
681        return None;
682    }
683    let key_bytes = &buf[start..p];
684    let key = std::str::from_utf8(key_bytes).ok()?.trim();
685    if key.is_empty() {
686        return None;
687    }
688    Some((SmolStr::from(key), p))
689}
690
691/// 在 block 风格中解析一个 mapping。`parent_indent` 是 caller 期望的最浅缩进,
692/// `pos` 指向 `:` 之后的第一个字符(可能就是 value 起点,可能是换行,
693/// 也可能是注释/行尾)。`first_key` 是 caller 已经读到的 key。
694/// 返回值会一直读到缩进小于 `parent_indent` 行为止。
695fn parse_block_map(buf: &[u8], mut pos: usize, parent_indent: usize, first_key: SmolStr) -> Result<(Dynamic, usize)> {
696    let mut map: IndexMap<SmolStr, Dynamic> = IndexMap::new();
697    let mut current_key = first_key;
698    let mut expect_value = true;
699    loop {
700        pos = skip_white(buf, pos)?;
701        // 跳过空行 / 纯注释行。
702        while pos < buf.len() && line_is_blank(buf, pos) {
703            let mut p = pos;
704            while p < buf.len() && buf[p] != b'\n' {
705                p += 1;
706            }
707            if p < buf.len() {
708                p += 1;
709            }
710            pos = p;
711        }
712        if pos >= buf.len() {
713            break;
714        }
715        let indent = line_indent(buf, pos);
716        if indent < parent_indent {
717            break;
718        }
719        if expect_value {
720            // 因为 pos 已经在 `:` 之后,这里不需要再 try_read_key。
721            // 但 compact block sequence 的边界:上一层调用方可能在 pos 处
722            // 直接撞上更深的兄弟 key (例如 `- name: alice` 后面的 `    age: 30`),
723            // try_read_key 会匹配,这种情况应当让 current_key 没值并切换。
724            if indent == parent_indent {
725                if let Some((next_key, colon)) = try_read_key(buf, pos) {
726                    map.insert(current_key.clone(), Dynamic::Null);
727                    current_key = next_key;
728                    pos = colon + 1;
729                    expect_value = true;
730                    continue;
731                }
732            }
733            // 解析 value。先看 value 是跟 `:` 同行 (inline),还是已经跳到下一行。
734            // pos 在 `:` 之后;如果当前字符是 `\n`,那 value 一定在下一行;
735            // 如果不是 `\n`,要看是不是到了行尾 (即空格 / tab / 行尾)。
736            let after_value = if pos < buf.len() && buf[pos] == b'\n' {
737                // 已经在 `:` 紧跟的换行处,跳到下一行起点。
738                pos + 1
739            } else {
740                skip_white(buf, pos)?
741            };
742            let on_next_line = pos < buf.len() && buf[pos] == b'\n';
743            if on_next_line || at_line_end(buf, after_value) {
744                // 值在下一行,缩进必须更深一档。
745                let p = after_value;
746                let next_indent = if p < buf.len() { line_indent(buf, p) } else { 0 };
747                if next_indent <= parent_indent {
748                    map.insert(current_key.clone(), Dynamic::Null);
749                    expect_value = false;
750                    pos = p;
751                    continue;
752                }
753                if let Some((next_key, colon)) = try_read_key(buf, p) {
754                    // 续行 mapping:把 next_key 接到 current_key 的 mapping 里。
755                    let value_pos = skip_white(buf, colon + 1)?;
756                    let (value, consumed) = parse_block_map(buf, value_pos, next_indent - 1, next_key)?;
757                    map.insert(current_key.clone(), value);
758                    pos = consumed;
759                } else {
760                    // 真正的 block value (sequence 或 deeper mapping)。
761                    let (value, consumed) = parse_node(buf, p, next_indent, true)?;
762                    map.insert(current_key.clone(), value);
763                    pos = consumed;
764                }
765            } else {
766                // 同行 inline value。
767                let (value, consumed) = parse_node(buf, after_value, parent_indent, false)?;
768                map.insert(current_key.clone(), value);
769                pos = consumed;
770            }
771            expect_value = false;
772            continue;
773        }
774        // 期待下一个 key。
775        if let Some((next_key, colon)) = try_read_key(buf, pos) {
776            current_key = next_key;
777            pos = colon + 1;
778            expect_value = true;
779        } else {
780            break;
781        }
782    }
783    Ok((Dynamic::Map(Arc::new(RwLock::new(map))), pos))
784}
785
786/// 解析 block 风格的 sequence。`- ` 开头,每项一个。
787fn parse_block_seq(buf: &[u8], mut pos: usize, parent_indent: usize) -> Result<(Dynamic, usize)> {
788    let mut items: Vec<Dynamic> = Vec::new();
789    loop {
790        pos = skip_white(buf, pos)?;
791        while pos < buf.len() && line_is_blank(buf, pos) {
792            let mut p = pos;
793            while p < buf.len() && buf[p] != b'\n' {
794                p += 1;
795            }
796            if p < buf.len() {
797                p += 1;
798            }
799            pos = p;
800        }
801        if pos >= buf.len() {
802            break;
803        }
804        let indent = line_indent(buf, pos);
805        if indent < parent_indent {
806            break;
807        }
808        if buf[pos] != b'-' || (pos + 1 < buf.len() && !matches!(buf[pos + 1], b' ' | b'\t' | b'\n')) {
809            break;
810        }
811        // 跳过 `- ` 前缀。
812        let after_dash = pos + 1;
813        let after_dash = skip_white(buf, after_dash)?;
814        // inline scalar?
815        if !at_line_end(buf, after_dash) {
816            // `- foo` 或 `- key: value` 形式。
817            // 先看是不是 inline mapping(同行带 `key: value`)。
818            if let Some((key, colon)) = try_read_key(buf, after_dash) {
819                let after_colon = skip_white(buf, colon + 1)?;
820                if at_line_end(buf, after_colon) {
821                    // 值在下一行,缩进至少要比 `- ` 之后更深一档。
822                    let mut p = after_colon;
823                    if p < buf.len() && buf[p] == b'\n' {
824                        p += 1;
825                    }
826                    let next_indent = if p < buf.len() { line_indent(buf, p) } else { 0 };
827                    let (value, consumed) = parse_node(buf, p, next_indent.max(parent_indent + 2), true)?;
828                    let mut map = IndexMap::new();
829                    map.insert(key, value);
830                    items.push(Dynamic::Map(Arc::new(RwLock::new(map))));
831                    pos = consumed;
832                } else {
833                    // `- key: value` 同行内联值。处理完首条 entry 后,
834                    // 还要看后面是不是还有以更深缩进延续的 `key: value` 行
835                    // (YAML compact block sequence)。
836                    let (value, consumed) = parse_node(buf, after_colon, parent_indent, false)?;
837                    let mut map = IndexMap::new();
838                    map.insert(key.clone(), value);
839                    // 把已读位置推到行尾之后,准备看下一行。
840                    let mut p = consumed;
841                    if p < buf.len() && buf[p] != b'\n' {
842                        // parse_node 没吞掉换行,手动跳过空白。
843                        p = skip_white(buf, p)?;
844                    }
845                    // 跳过空行 / 注释行,看下一条 entry 是不是更深缩进的 key:。
846                    while p < buf.len() && line_is_blank(buf, p) {
847                        let mut q = p;
848                        while q < buf.len() && buf[q] != b'\n' {
849                            q += 1;
850                        }
851                        if q < buf.len() {
852                            q += 1;
853                        }
854                        p = q;
855                    }
856                    if p < buf.len() {
857                        let next_indent = line_indent(buf, p);
858                        let dash_indent = line_indent(buf, pos);
859                        // 跳过行首缩进,看看是不是 `key:` 续行。
860                        let key_start = p + next_indent;
861                        if next_indent > dash_indent && key_start < buf.len()
862                            && let Some((next_key, colon)) = try_read_key(buf, key_start)
863                        {
864                            // 用 parse_block_map 把后续 key:value 行都收进来。
865                            // parse_block_map 现在期望 pos 在 `:` 之后。
866                            let value_pos = skip_white(buf, colon + 1)?;
867                            let (rest, consumed2) = parse_block_map(buf, value_pos, next_indent - 1, next_key)?;
868                            if let Dynamic::Map(extra) = rest {
869                                for (k, v) in extra.read().iter() {
870                                    map.insert(k.clone(), v.clone());
871                                }
872                            }
873                            p = consumed2;
874                        }
875                    }
876                    items.push(Dynamic::Map(Arc::new(RwLock::new(map))));
877                    pos = p;
878                }
879            } else {
880                let (value, consumed) = parse_node(buf, after_dash, parent_indent, false)?;
881                items.push(value);
882                pos = consumed;
883            }
884            continue;
885        }
886        // 值在下一行,缩进必须比 `- ` 之后更深。
887        let mut p = after_dash;
888        if p < buf.len() && buf[p] == b'\n' {
889            p += 1;
890        }
891        if p >= buf.len() {
892            items.push(Dynamic::Null);
893            break;
894        }
895        let next_indent = line_indent(buf, p);
896        if next_indent <= parent_indent {
897            items.push(Dynamic::Null);
898            break;
899        }
900        let (value, consumed) = parse_node(buf, p, next_indent, true)?;
901        items.push(value);
902        pos = consumed;
903    }
904    Ok((Dynamic::List(Arc::new(RwLock::new(items))), pos))
905}
906
907/// 解析 `{ key: value, ... }` 这种 inline mapping。
908fn parse_flow_map(buf: &[u8], pos: usize) -> Result<(Dynamic, usize)> {
909    let mut p = pos + 1;
910    let mut map: IndexMap<SmolStr, Dynamic> = IndexMap::new();
911    p = skip_white(buf, p)?;
912    if p < buf.len() && buf[p] == b'}' {
913        return Ok((Dynamic::Map(Arc::new(RwLock::new(map))), p + 1));
914    }
915    loop {
916        p = skip_white(buf, p)?;
917        let (key, after) = match try_read_key(buf, p) {
918            Some(pair) => pair,
919            None => return Err(anyhow!("yaml flow mapping 缺少 key @{}", p)),
920        };
921        p = skip_white(buf, after + 1)?;
922        let (value, consumed) = parse_node(buf, p, 0, false)?;
923        map.insert(key, value);
924        p = consumed;
925        p = skip_white(buf, p)?;
926        if p < buf.len() && buf[p] == b',' {
927            p += 1;
928            continue;
929        }
930        if p < buf.len() && buf[p] == b'}' {
931            return Ok((Dynamic::Map(Arc::new(RwLock::new(map))), p + 1));
932        }
933        return Err(anyhow!("yaml flow mapping 缺少 ',' 或 '}}' @{}", p));
934    }
935}
936
937/// 解析 `[ a, b, c ]` 这种 inline sequence。
938fn parse_flow_seq(buf: &[u8], pos: usize) -> Result<(Dynamic, usize)> {
939    let mut p = pos + 1;
940    let mut items: Vec<Dynamic> = Vec::new();
941    p = skip_white(buf, p)?;
942    if p < buf.len() && buf[p] == b']' {
943        return Ok((Dynamic::List(Arc::new(RwLock::new(items))), p + 1));
944    }
945    loop {
946        p = skip_white(buf, p)?;
947        let (value, consumed) = parse_node(buf, p, 0, false)?;
948        items.push(value);
949        p = consumed;
950        p = skip_white(buf, p)?;
951        if p < buf.len() && buf[p] == b',' {
952            p += 1;
953            continue;
954        }
955        if p < buf.len() && buf[p] == b']' {
956            return Ok((Dynamic::List(Arc::new(RwLock::new(items))), p + 1));
957        }
958        return Err(anyhow!("yaml flow sequence 缺少 ',' 或 ']' @{}", p));
959    }
960}
961
962// ---- Dynamic 上的便捷方法 ------------------------------------------
963
964impl Dynamic {
965    /// 顶层便捷入口:把 `Dynamic` 序列化成 YAML 字符串。
966    pub fn to_yaml_string(&self) -> String {
967        let mut buf = String::new();
968        self.to_yaml(&mut buf);
969        buf
970    }
971
972    /// 顶层便捷入口:从 YAML 文本解析 `Dynamic`。
973    pub fn from_yaml_buf(buf: &[u8]) -> Result<Self> {
974        let (value, _) = <Self as FromYaml>::from_yaml(buf)?;
975        Ok(value)
976    }
977}
978
979impl Dynamic {
980    /// 给 `to_yaml` / 测试用的小工具:在已有 map 里塞一个 entry 并返回 map。
981    fn map_with_entry(key: SmolStr, value: Dynamic) -> Self {
982        let mut map = IndexMap::new();
983        map.insert(key, value);
984        Dynamic::Map(Arc::new(RwLock::new(map)))
985    }
986}
987
988// ---- 测试 --------------------------------------------------------------
989
990#[cfg(test)]
991mod tests {
992    use super::*;
993
994    fn round_trip(input: &str) -> String {
995        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse yaml");
996        let mut buf = String::new();
997        value.to_yaml(&mut buf);
998        buf
999    }
1000
1001    #[test]
1002    fn simple_scalar_round_trip() {
1003        assert_eq!(round_trip("42"), "42");
1004        assert_eq!(round_trip("-7"), "-7");
1005        assert_eq!(round_trip("3.14"), "3.14");
1006        assert_eq!(round_trip("true"), "true");
1007        assert_eq!(round_trip("false"), "false");
1008        assert_eq!(round_trip("null"), "null");
1009        assert_eq!(round_trip("~"), "null");
1010    }
1011
1012    #[test]
1013    fn string_quoting_rules() {
1014        // 像数字的字符串必须加引号。
1015        let value = Dynamic::String("123".into());
1016        let mut buf = String::new();
1017        value.to_yaml(&mut buf);
1018        assert_eq!(buf, "\"123\"");
1019
1020        // 像 bool 的字符串也要加引号。
1021        let value = Dynamic::String("yes".into());
1022        let mut buf = String::new();
1023        value.to_yaml(&mut buf);
1024        assert_eq!(buf, "\"yes\"");
1025
1026        // 普通字符串裸出。
1027        let value = Dynamic::String("hello world".into());
1028        let mut buf = String::new();
1029        value.to_yaml(&mut buf);
1030        assert_eq!(buf, "hello world");
1031
1032        // 含 `:` 的字符串必须引号。
1033        let value = Dynamic::String("a: b".into());
1034        let mut buf = String::new();
1035        value.to_yaml(&mut buf);
1036        assert_eq!(buf, "\"a: b\"");
1037
1038        // 含 `#` 的字符串要引号。
1039        let value = Dynamic::String("color #ff00ff".into());
1040        let mut buf = String::new();
1041        value.to_yaml(&mut buf);
1042        assert_eq!(buf, "\"color #ff00ff\"");
1043    }
1044
1045    #[test]
1046    fn multiline_string_uses_block_scalar() {
1047        let value = Dynamic::String("line1\nline2\nline3".into());
1048        let mut buf = String::new();
1049        value.to_yaml(&mut buf);
1050        assert!(buf.starts_with("|+\n"), "should use block scalar: {buf}");
1051        assert!(buf.contains("line1\n"));
1052        assert!(buf.contains("line2\n"));
1053        assert!(buf.contains("line3\n"));
1054    }
1055
1056    #[test]
1057    fn mapping_block_style() {
1058        let mut map = IndexMap::new();
1059        map.insert(SmolStr::from("name"), Dynamic::String("zust".into()));
1060        map.insert(SmolStr::from("version"), Dynamic::I64(1));
1061        map.insert(SmolStr::from("active"), Dynamic::Bool(true));
1062        let value = Dynamic::Map(Arc::new(RwLock::new(map)));
1063        let mut buf = String::new();
1064        value.to_yaml(&mut buf);
1065        assert!(buf.contains("name: zust"), "{buf}");
1066        assert!(buf.contains("version: 1"), "{buf}");
1067        assert!(buf.contains("active: true"), "{buf}");
1068    }
1069
1070    #[test]
1071    fn nested_mapping_and_list() {
1072        let mut inner = IndexMap::new();
1073        inner.insert(SmolStr::from("a"), Dynamic::I64(1));
1074        inner.insert(SmolStr::from("b"), Dynamic::I64(2));
1075        let mut map = IndexMap::new();
1076        map.insert(SmolStr::from("items"), Dynamic::list(vec![Dynamic::Map(Arc::new(RwLock::new(inner)))]));
1077        let value = Dynamic::Map(Arc::new(RwLock::new(map)));
1078        let yaml = value.to_yaml_string();
1079        assert!(yaml.contains("items:\n  - a: 1"), "{yaml}");
1080    }
1081
1082    #[test]
1083    fn block_sequence_round_trip() {
1084        let input = "- 1\n- 2\n- 3\n";
1085        let (value, consumed) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1086        assert!(matches!(value, Dynamic::List(_)));
1087        assert_eq!(consumed, input.len());
1088    }
1089
1090    #[test]
1091    fn block_mapping_round_trip() {
1092        let input = "name: zust\nversion: 1\n";
1093        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1094        let map = value.get_dynamic("name").unwrap();
1095        assert_eq!(map.as_str(), "zust");
1096        assert_eq!(value.get_dynamic("version").and_then(|v| v.as_int()), Some(1));
1097    }
1098
1099    #[test]
1100    fn nested_block_structures() {
1101        let input = "users:\n  - name: alice\n    age: 30\n  - name: bob\n    age: 25\n";
1102        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1103        let users = value.get_dynamic("users").unwrap();
1104        assert!(users.is_list());
1105        let first = users.get_idx(0).unwrap();
1106        assert_eq!(first.get_dynamic("name").unwrap().as_str(), "alice");
1107        assert_eq!(first.get_dynamic("age").unwrap().as_int(), Some(30));
1108    }
1109
1110    #[test]
1111    fn inline_mapping_and_sequence() {
1112        let input = "{a: 1, b: [2, 3]}";
1113        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1114        assert_eq!(value.get_dynamic("a").unwrap().as_int(), Some(1));
1115        let b = value.get_dynamic("b").unwrap();
1116        assert!(b.is_list());
1117    }
1118
1119    #[test]
1120    fn comments_and_blank_lines_are_ignored() {
1121        let input = "# header comment\nname: zust  # trailing comment\n\nversion: 1\n";
1122        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1123        assert_eq!(value.get_dynamic("name").unwrap().as_str(), "zust");
1124        assert_eq!(value.get_dynamic("version").unwrap().as_int(), Some(1));
1125    }
1126
1127    #[test]
1128    fn double_quoted_string_with_escapes() {
1129        let input = "msg: \"hello\\nworld\"\n";
1130        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1131        assert_eq!(value.get_dynamic("msg").unwrap().as_str(), "hello\nworld");
1132    }
1133
1134    #[test]
1135    fn single_quoted_string_literal() {
1136        let input = "msg: 'a: b'\n";
1137        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1138        assert_eq!(value.get_dynamic("msg").unwrap().as_str(), "a: b");
1139    }
1140
1141    #[test]
1142    fn quoted_round_trip_preserves_string_like_number() {
1143        // 字符串 "123" 必须 round-trip 仍然是字符串,而不是整数。
1144        let mut map = IndexMap::new();
1145        map.insert(SmolStr::from("id"), Dynamic::String("123".into()));
1146        let value = Dynamic::Map(Arc::new(RwLock::new(map)));
1147        let yaml = value.to_yaml_string();
1148        assert!(yaml.contains("id: \"123\""), "{yaml}");
1149        let (parsed, _) = <Dynamic as FromYaml>::from_yaml(yaml.as_bytes()).expect("parse");
1150        let id = parsed.get_dynamic("id").unwrap();
1151        assert_eq!(id.as_str(), "123");
1152        assert!(!id.is_int(), "id should stay string");
1153    }
1154
1155    #[test]
1156    fn empty_collection_serialization() {
1157        let value = Dynamic::List(Arc::new(RwLock::new(Vec::new())));
1158        let mut buf = String::new();
1159        value.to_yaml(&mut buf);
1160        assert_eq!(buf, "[]");
1161    }
1162}