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                // 紧接 `- key: value` 之后还有 sibling key,需要换行 + 缩进对齐。
305                buf.push('\n');
306                buf.push_str(&" ".repeat(indent + 4));
307                yaml_write_map(rest, indent + 4, buf);
308            }
309        } else if is_yaml_block(&item) {
310            let one_more = indent + 2;
311            buf.push('\n');
312            item.to_yaml_indent(one_more, buf);
313        } else {
314            item.to_yaml_indent(0, buf);
315        }
316    }
317    if first {
318        // 空序列:用 flow 形式 `[]`,避免下游解析器拿到空 buf。
319        buf.push_str("[]");
320    }
321}
322
323fn yaml_write_map<I: Iterator<Item = (SmolStr, Dynamic)>>(entries: I, indent: usize, buf: &mut String) {
324    let pad = " ".repeat(indent);
325    let mut first = true;
326    for (key, value) in entries {
327        if first {
328            // 嵌套 map(indent>0)的第一条 entry 需要缩进;
329            // 根 map(indent==0)由调用方负责前导换行,不加 pad。
330            if indent > 0 {
331                buf.push_str(&pad);
332            }
333        } else {
334            buf.push('\n');
335            buf.push_str(&pad);
336        }
337        first = false;
338        yaml_write_key(key.as_str(), buf);
339        buf.push(':');
340        if is_yaml_block(&value) {
341            buf.push('\n');
342            value.to_yaml_indent(indent + 2, buf);
343        } else {
344            buf.push(' ');
345            value.to_yaml_indent(0, buf);
346        }
347    }
348    if indent == 0 {
349        buf.push('\n');
350    }
351}
352
353/// 如果 `value` 是一个至少有 1 个 entry 的 map,返回第一个 (key, value) 对;
354/// 否则返回 None。给序列里的 map 紧凑写法用。
355fn map_first_pair_inline(value: &Dynamic) -> Option<(SmolStr, Dynamic)> {
356    if let Dynamic::Map(m) = value {
357        let m = m.read();
358        let first = m.iter().next()?;
359        Some((first.0.clone(), first.1.clone()))
360    } else {
361        None
362    }
363}
364
365/// 返回 map 剩余 (除第一对外) 的 entry 迭代器。
366fn map_remaining_pairs(value: &Dynamic) -> Option<impl Iterator<Item = (SmolStr, Dynamic)>> {
367    if let Dynamic::Map(m) = value {
368        let m = m.read().clone();
369        let mut iter = m.into_iter();
370        iter.next();
371        Some(iter)
372    } else {
373        None
374    }
375}
376
377// ---- 反序列化 ---------------------------------------------------------
378
379/// 跳过空白和注释。YAML 注释从 `#` 开始,到行尾结束。
380/// 跟 json 不同,空白是空格 / tab / 换行 + 注释。
381fn skip_white(buf: &[u8], mut pos: usize) -> Result<usize> {
382    while pos < buf.len() {
383        match buf[pos] {
384            b' ' | b'\t' | b'\r' | b'\n' => pos += 1,
385            b'#' => {
386                while pos < buf.len() && buf[pos] != b'\n' {
387                    pos += 1;
388                }
389            }
390            _ => break,
391        }
392    }
393    Ok(pos)
394}
395
396/// 返回当前行(从行首到 pos)的缩进空格数。空 buffer 返回 0。
397fn line_indent(buf: &[u8], pos: usize) -> usize {
398    let mut p = pos;
399    while p > 0 && buf[p - 1] != b'\n' {
400        p -= 1;
401    }
402    let mut count = 0usize;
403    // 数到非空字符为止;`while p < pos` 是错的,pos 本身可能就是首字符,
404    // 那时一次都不进入循环。
405    while p < buf.len() && buf[p] == b' ' {
406        count += 1;
407        p += 1;
408    }
409    count
410}
411
412/// 看 pos 处是不是 `\n` 或文件末尾。
413fn at_line_end(buf: &[u8], pos: usize) -> bool {
414    pos >= buf.len() || buf[pos] == b'\n'
415}
416
417/// 判断一行是否完全为空 (只有空白 / 注释 / 换行)。
418fn line_is_blank(buf: &[u8], mut pos: usize) -> bool {
419    while pos < buf.len() && buf[pos] != b'\n' {
420        match buf[pos] {
421            b' ' | b'\t' | b'\r' => pos += 1,
422            b'#' => return true,
423            _ => return false,
424        }
425    }
426    true
427}
428
429/// 看接下来的 token 是否以 `---` (文档开始) 开头。
430/// 这里只把 `---` 当一个"可选的前缀"吃掉,不影响实际语义。
431fn skip_doc_marker(buf: &[u8], mut pos: usize) -> usize {
432    pos = skip_white(buf, pos).unwrap_or(pos);
433    if buf.get(pos..pos + 3) == Some(b"---") && (pos + 3 == buf.len() || matches!(buf[pos + 3], b' ' | b'\t' | b'\n' | b'\r')) {
434        pos += 3;
435    }
436    skip_white(buf, pos).unwrap_or(pos)
437}
438
439/// 解析一个 YAML scalar 字面量(到行尾或遇到 `#` 注释为止)。
440/// 返回 (去掉 trailing comment 后的内容, 消耗的字节数)。
441///
442/// 这里不处理 multi-line scalar (`|` / `>`);要支持的话需要在 mapping / sequence
443/// 解析中专门检测。当前实现里,parser 一旦进入 scalar 就只读本行,后面的多行
444/// 内容会被当成"意外的额外内容"报错 —— 这是有意的取舍,LLM 输出通常不会写
445/// 折叠 scalar。
446fn read_scalar(buf: &[u8], pos: usize) -> Result<(&str, usize)> {
447    let start = pos;
448    let mut end = pos;
449    let mut in_flow_bracket = 0i32; // [ ] { } 计数,处理 inline 集合里的 scalar
450    let mut in_double_quote = false;
451    let mut in_single_quote = false;
452    while end < buf.len() {
453        let b = buf[end];
454        if in_double_quote {
455            if b == b'\\' && end + 1 < buf.len() {
456                end += 2;
457                continue;
458            }
459            if b == b'"' {
460                in_double_quote = false;
461                end += 1;
462                continue;
463            }
464            end += 1;
465            continue;
466        }
467        if in_single_quote {
468            if b == b'\'' {
469                if end + 1 < buf.len() && buf[end + 1] == b'\'' {
470                    // '' 是单引号里的转义,跳两个字符。
471                    end += 2;
472                    continue;
473                }
474                in_single_quote = false;
475                end += 1;
476                continue;
477            }
478            end += 1;
479            continue;
480        }
481        match b {
482            b'"' => {
483                in_double_quote = true;
484                end += 1;
485            }
486            b'\'' => {
487                in_single_quote = true;
488                end += 1;
489            }
490            b'[' | b'{' => {
491                in_flow_bracket += 1;
492                end += 1;
493            }
494            b']' | b'}' => {
495                // `]` / `}` 在 plain scalar 上下文里就是终止符,
496                // 不管是不是在 flow 集合里 —— 调用方会在外层恢复对它们的处理。
497                break;
498            }
499            b',' => {
500                // `,` 是 flow 集合分隔符,plain scalar 遇到直接终止。
501                // 用户如果真的需要带 `,` 的裸字符串,自己加引号。
502                break;
503            }
504            b':' if in_flow_bracket == 0 && end + 1 < buf.len() && (buf[end + 1] == b' ' || buf[end + 1] == b'\t' || buf[end + 1] == b'\n') => {
505                // mapping 的 key:value 分隔,跳出。
506                break;
507            }
508            b'#' if in_flow_bracket == 0 => break,
509            b'\n' if in_flow_bracket == 0 => break,
510            _ => end += 1,
511        }
512    }
513    // 裁掉 trailing 空白。
514    while end > start && matches!(buf[end - 1], b' ' | b'\t' | b'\r') {
515        end -= 1;
516    }
517    if end == start {
518        return Err(anyhow!("yaml scalar 为空 @{}", start));
519    }
520    std::str::from_utf8(&buf[start..end]).map(|s| (s, end - start)).map_err(|e| anyhow!("yaml scalar 含非法 UTF-8: {e}"))
521}
522
523/// 把 scalar token 解析成 Dynamic:支持整数 / 浮点 / bool / null / 普通字符串。
524fn scalar_to_dynamic(raw: &str) -> Dynamic {
525    // null / ~
526    if matches!(raw, "" | "null" | "Null" | "NULL" | "~") {
527        return Dynamic::Null;
528    }
529    // bool (YAML 1.2 只有 true/false,大写也接受。LLM 经常写 yes/no,这里
530    // 也兼容一下,但这是非标准的,使用时要谨慎)。
531    match raw {
532        "true" | "True" | "TRUE" => return Dynamic::Bool(true),
533        "false" | "False" | "FALSE" => return Dynamic::Bool(false),
534        _ => {}
535    }
536    // 整数优先,失败再试浮点。
537    if let Ok(v) = raw.parse::<i64>() {
538        return Dynamic::I64(v);
539    }
540    if let Ok(v) = raw.parse::<f64>() {
541        if v.is_finite() {
542            return Dynamic::F64(v);
543        }
544    }
545    // 字符串:处理双引号 / 单引号包裹 + 转义。
546    if raw.starts_with('"') && raw.ends_with('"') && raw.len() >= 2 {
547        return Dynamic::String(unescape_double_quoted(&raw[1..raw.len() - 1]).into());
548    }
549    if raw.starts_with('\'') && raw.ends_with('\'') && raw.len() >= 2 {
550        return Dynamic::String(unescape_single_quoted(&raw[1..raw.len() - 1]).into());
551    }
552    Dynamic::String(raw.into())
553}
554
555fn unescape_double_quoted(s: &str) -> String {
556    let mut out = String::with_capacity(s.len());
557    let mut chars = s.chars();
558    while let Some(c) = chars.next() {
559        if c == '\\' {
560            match chars.next() {
561                Some('n') => out.push('\n'),
562                Some('t') => out.push('\t'),
563                Some('r') => out.push('\r'),
564                Some('\\') => out.push('\\'),
565                Some('"') => out.push('"'),
566                Some('0') => out.push('\0'),
567                Some('/') => out.push('/'),
568                Some('u') => {
569                    let hex: String = chars.by_ref().take(4).collect();
570                    if let Ok(code) = u32::from_str_radix(&hex, 16) {
571                        if let Some(ch) = char::from_u32(code) {
572                            out.push(ch);
573                        }
574                    }
575                }
576                Some(other) => {
577                    out.push('\\');
578                    out.push(other);
579                }
580                None => out.push('\\'),
581            }
582        } else {
583            out.push(c);
584        }
585    }
586    out
587}
588
589fn unescape_single_quoted(s: &str) -> String {
590    // 单引号 scalar 里,所有内容都是字面量,只有 '' 代表单个 '。
591    s.replace("''", "'")
592}
593
594/// `from_yaml` 入口。跟 `from_json` 一样返回 (value, consumed)。
595impl FromYaml for Dynamic {
596    fn from_yaml(buf: &[u8]) -> Result<(Self, usize)> {
597        let pos = skip_doc_marker(buf, 0);
598        parse_node(buf, pos, 0, false)
599    }
600}
601
602/// 解析一个 YAML node。`min_indent` 是当前 block 的最小缩进,`in_block` 表示
603/// 我们正在某个 block 集合(mapping / sequence)的内部 —— 这种情况下一旦遇到
604/// 缩进小于 `min_indent` 的行,就应该结束。
605fn parse_node(buf: &[u8], pos: usize, min_indent: usize, in_block: bool) -> Result<(Dynamic, usize)> {
606    let pos = skip_white(buf, pos)?;
607    if pos >= buf.len() {
608        return Err(anyhow!("yaml 文档提前结束"));
609    }
610    // 先看是否是流集合 `{ ... }` / `[ ... ]`。
611    if buf[pos] == b'{' {
612        return parse_flow_map(buf, pos);
613    }
614    if buf[pos] == b'[' {
615        return parse_flow_seq(buf, pos);
616    }
617    // 否则是 block 风格。看当前行缩进以及接下来的 token 形态。
618    let indent = line_indent(buf, pos);
619    if indent < min_indent {
620        return Err(anyhow!("yaml 缩进不足: {} < {}", indent, min_indent));
621    }
622    if buf[pos] == b'-' && (pos + 1 == buf.len() || matches!(buf[pos + 1], b' ' | b'\t' | b'\n')) {
623        return parse_block_seq(buf, pos, indent);
624    }
625    // mapping 第一个 key:value 可能跟 caller 同一行(在 indent 等于 caller
626    // 缩进的情况下),所以不强制从行首开始。
627    if let Some((key, colon)) = try_read_key(buf, pos) {
628        // 把 pos 推到 `:` 之后;parse_block_map 不再重新扫描 key。
629        let value_pos = skip_white(buf, colon + 1)?;
630        return parse_block_map(buf, value_pos, indent, key);
631    }
632    // 走到这里说明是孤立的 scalar —— 只有当 caller 不要求 block 形态时才能
633    // 接受,否则应当报错(避免 mapping 里 `- foo` 被误读成 `foo`)。
634    if in_block {
635        return Err(anyhow!("yaml 期望 block 结构但遇到孤立标量 @{}", pos));
636    }
637    let (raw, consumed) = read_scalar(buf, pos)?;
638    Ok((scalar_to_dynamic(raw), pos + consumed))
639}
640
641/// 在 pos 处尝试读取一个 `key:` 形式的 mapping key。
642/// 成功时返回 (key, ':' 位置)。不修改 pos。
643fn try_read_key(buf: &[u8], pos: usize) -> Option<(SmolStr, usize)> {
644    let start = pos;
645    // 单 / 双引号包裹的 key 直接到对应引号收尾。
646    if buf[pos] == b'"' || buf[pos] == b'\'' {
647        let quote = buf[pos];
648        let mut p = pos + 1;
649        while p < buf.len() && buf[p] != quote {
650            if buf[p] == b'\\' && quote == b'"' && p + 1 < buf.len() {
651                p += 2;
652                continue;
653            }
654            p += 1;
655        }
656        if p >= buf.len() {
657            return None;
658        }
659        let key = &buf[start + 1..p];
660        let after = p + 1;
661        let after = skip_white(buf, after).ok()?;
662        if after >= buf.len() || buf[after] != b':' {
663            return None;
664        }
665        let key_str = std::str::from_utf8(key).ok()?;
666        return Some((SmolStr::from(if quote == b'"' { unescape_double_quoted(key_str) } else { unescape_single_quoted(key_str) }), after));
667    }
668    // 普通 key:扫到 `:` 或"明显不能作为 key 字符"为止。空白 / `,` / `}` / `]` /
669// `#` / `\n` 都属于 separator,提前终止。这样在 `{a: 1, b: 2}` 这种 inline 上下文里
670// 才不会把后面的 `, b: 2` 吞进 key。
671    let mut p = pos;
672    while p < buf.len() {
673        match buf[p] {
674            b':' if p + 1 < buf.len() && (matches!(buf[p + 1], b' ' | b'\t' | b'\n') || p + 1 == buf.len()) => break,
675            b' ' | b'\t' | b'\r' | b'\n' | b'#' | b',' | b'}' | b']' => return None,
676            _ => p += 1,
677        }
678    }
679    if p >= buf.len() || buf[p] != b':' {
680        return None;
681    }
682    let key_bytes = &buf[start..p];
683    let key = std::str::from_utf8(key_bytes).ok()?.trim();
684    if key.is_empty() {
685        return None;
686    }
687    Some((SmolStr::from(key), p))
688}
689
690/// 在 block 风格中解析一个 mapping。`parent_indent` 是 caller 期望的最浅缩进,
691/// `pos` 指向 `:` 之后的第一个字符(可能就是 value 起点,可能是换行,
692/// 也可能是注释/行尾)。`first_key` 是 caller 已经读到的 key。
693/// 返回值会一直读到缩进小于 `parent_indent` 行为止。
694fn parse_block_map(buf: &[u8], mut pos: usize, parent_indent: usize, first_key: SmolStr) -> Result<(Dynamic, usize)> {
695    let mut map: IndexMap<SmolStr, Dynamic> = IndexMap::new();
696    let mut current_key = first_key;
697    let mut expect_value = true;
698    loop {
699        pos = skip_white(buf, pos)?;
700        // 跳过空行 / 纯注释行。
701        while pos < buf.len() && line_is_blank(buf, pos) {
702            let mut p = pos;
703            while p < buf.len() && buf[p] != b'\n' {
704                p += 1;
705            }
706            if p < buf.len() {
707                p += 1;
708            }
709            pos = p;
710        }
711        if pos >= buf.len() {
712            break;
713        }
714        let indent = line_indent(buf, pos);
715        if indent < parent_indent {
716            break;
717        }
718        if expect_value {
719            // 因为 pos 已经在 `:` 之后,这里不需要再 try_read_key。
720            // 但 compact block sequence 的边界:上一层调用方可能在 pos 处
721            // 直接撞上更深的兄弟 key (例如 `- name: alice` 后面的 `    age: 30`),
722            // try_read_key 会匹配,这种情况应当让 current_key 没值并切换。
723            if indent == parent_indent {
724                if let Some((next_key, colon)) = try_read_key(buf, pos) {
725                    map.insert(current_key.clone(), Dynamic::Null);
726                    current_key = next_key;
727                    pos = colon + 1;
728                    expect_value = true;
729                    continue;
730                }
731            }
732            // 解析 value。先看 value 是跟 `:` 同行 (inline),还是已经跳到下一行。
733            // pos 在 `:` 之后;如果当前字符是 `\n`,那 value 一定在下一行;
734            // 如果不是 `\n`,要看是不是到了行尾 (即空格 / tab / 行尾)。
735            let after_value = if pos < buf.len() && buf[pos] == b'\n' {
736                // 已经在 `:` 紧跟的换行处,跳到下一行起点。
737                pos + 1
738            } else {
739                skip_white(buf, pos)?
740            };
741            let on_next_line = pos < buf.len() && buf[pos] == b'\n';
742            if on_next_line || at_line_end(buf, after_value) {
743                // 值在下一行,缩进必须更深一档。
744                let p = after_value;
745                let next_indent = if p < buf.len() { line_indent(buf, p) } else { 0 };
746                if next_indent <= parent_indent {
747                    map.insert(current_key.clone(), Dynamic::Null);
748                    expect_value = false;
749                    pos = p;
750                    continue;
751                }
752                if let Some((next_key, colon)) = try_read_key(buf, p) {
753                    // 续行 mapping:把 next_key 接到 current_key 的 mapping 里。
754                    let value_pos = skip_white(buf, colon + 1)?;
755                    let (value, consumed) = parse_block_map(buf, value_pos, next_indent - 1, next_key)?;
756                    map.insert(current_key.clone(), value);
757                    pos = consumed;
758                } else {
759                    // 真正的 block value (sequence 或 deeper mapping)。
760                    let (value, consumed) = parse_node(buf, p, next_indent, true)?;
761                    map.insert(current_key.clone(), value);
762                    pos = consumed;
763                }
764            } else {
765                // 同行 inline value。
766                let (value, consumed) = parse_node(buf, after_value, parent_indent, false)?;
767                map.insert(current_key.clone(), value);
768                pos = consumed;
769            }
770            expect_value = false;
771            continue;
772        }
773        // 期待下一个 key。
774        if let Some((next_key, colon)) = try_read_key(buf, pos) {
775            current_key = next_key;
776            pos = colon + 1;
777            expect_value = true;
778        } else {
779            break;
780        }
781    }
782    Ok((Dynamic::Map(Arc::new(RwLock::new(map))), pos))
783}
784
785/// 解析 block 风格的 sequence。`- ` 开头,每项一个。
786fn parse_block_seq(buf: &[u8], mut pos: usize, parent_indent: usize) -> Result<(Dynamic, usize)> {
787    let mut items: Vec<Dynamic> = Vec::new();
788    loop {
789        pos = skip_white(buf, pos)?;
790        while pos < buf.len() && line_is_blank(buf, pos) {
791            let mut p = pos;
792            while p < buf.len() && buf[p] != b'\n' {
793                p += 1;
794            }
795            if p < buf.len() {
796                p += 1;
797            }
798            pos = p;
799        }
800        if pos >= buf.len() {
801            break;
802        }
803        let indent = line_indent(buf, pos);
804        if indent < parent_indent {
805            break;
806        }
807        if buf[pos] != b'-' || (pos + 1 < buf.len() && !matches!(buf[pos + 1], b' ' | b'\t' | b'\n')) {
808            break;
809        }
810        // 跳过 `- ` 前缀。
811        let after_dash = pos + 1;
812        let after_dash = skip_white(buf, after_dash)?;
813        // inline scalar?
814        if !at_line_end(buf, after_dash) {
815            // `- foo` 或 `- key: value` 形式。
816            // 先看是不是 inline mapping(同行带 `key: value`)。
817            if let Some((key, colon)) = try_read_key(buf, after_dash) {
818                let after_colon = skip_white(buf, colon + 1)?;
819                if at_line_end(buf, after_colon) {
820                    // 值在下一行,缩进至少要比 `- ` 之后更深一档。
821                    let mut p = after_colon;
822                    if p < buf.len() && buf[p] == b'\n' {
823                        p += 1;
824                    }
825                    let next_indent = if p < buf.len() { line_indent(buf, p) } else { 0 };
826                    let (value, consumed) = parse_node(buf, p, next_indent.max(parent_indent + 2), true)?;
827                    let mut map = IndexMap::new();
828                    map.insert(key, value);
829                    items.push(Dynamic::Map(Arc::new(RwLock::new(map))));
830                    pos = consumed;
831                } else {
832                    // `- key: value` 同行内联值。处理完首条 entry 后,
833                    // 还要看后面是不是还有以更深缩进延续的 `key: value` 行
834                    // (YAML compact block sequence)。
835                    let (value, consumed) = parse_node(buf, after_colon, parent_indent, false)?;
836                    let mut map = IndexMap::new();
837                    map.insert(key.clone(), value);
838                    // 把已读位置推到行尾之后,准备看下一行。
839                    let mut p = consumed;
840                    if p < buf.len() && buf[p] != b'\n' {
841                        // parse_node 没吞掉换行,手动跳过空白。
842                        p = skip_white(buf, p)?;
843                    }
844                    // 跳过空行 / 注释行,看下一条 entry 是不是更深缩进的 key:。
845                    while p < buf.len() && line_is_blank(buf, p) {
846                        let mut q = p;
847                        while q < buf.len() && buf[q] != b'\n' {
848                            q += 1;
849                        }
850                        if q < buf.len() {
851                            q += 1;
852                        }
853                        p = q;
854                    }
855                    if p < buf.len() {
856                        let next_indent = line_indent(buf, p);
857                        let dash_indent = line_indent(buf, pos);
858                        // 跳过行首缩进,看看是不是 `key:` 续行。
859                        let key_start = p + next_indent;
860                        if next_indent > dash_indent && key_start < buf.len()
861                            && let Some((next_key, colon)) = try_read_key(buf, key_start)
862                        {
863                            // 用 parse_block_map 把后续 key:value 行都收进来。
864                            // parse_block_map 现在期望 pos 在 `:` 之后。
865                            let value_pos = skip_white(buf, colon + 1)?;
866                            let (rest, consumed2) = parse_block_map(buf, value_pos, next_indent - 1, next_key)?;
867                            if let Dynamic::Map(extra) = rest {
868                                for (k, v) in extra.read().iter() {
869                                    map.insert(k.clone(), v.clone());
870                                }
871                            }
872                            p = consumed2;
873                        }
874                    }
875                    items.push(Dynamic::Map(Arc::new(RwLock::new(map))));
876                    pos = p;
877                }
878            } else {
879                let (value, consumed) = parse_node(buf, after_dash, parent_indent, false)?;
880                items.push(value);
881                pos = consumed;
882            }
883            continue;
884        }
885        // 值在下一行,缩进必须比 `- ` 之后更深。
886        let mut p = after_dash;
887        if p < buf.len() && buf[p] == b'\n' {
888            p += 1;
889        }
890        if p >= buf.len() {
891            items.push(Dynamic::Null);
892            break;
893        }
894        let next_indent = line_indent(buf, p);
895        if next_indent <= parent_indent {
896            items.push(Dynamic::Null);
897            break;
898        }
899        let (value, consumed) = parse_node(buf, p, next_indent, true)?;
900        items.push(value);
901        pos = consumed;
902    }
903    Ok((Dynamic::List(Arc::new(RwLock::new(items))), pos))
904}
905
906/// 解析 `{ key: value, ... }` 这种 inline mapping。
907fn parse_flow_map(buf: &[u8], pos: usize) -> Result<(Dynamic, usize)> {
908    let mut p = pos + 1;
909    let mut map: IndexMap<SmolStr, Dynamic> = IndexMap::new();
910    p = skip_white(buf, p)?;
911    if p < buf.len() && buf[p] == b'}' {
912        return Ok((Dynamic::Map(Arc::new(RwLock::new(map))), p + 1));
913    }
914    loop {
915        p = skip_white(buf, p)?;
916        let (key, after) = match try_read_key(buf, p) {
917            Some(pair) => pair,
918            None => return Err(anyhow!("yaml flow mapping 缺少 key @{}", p)),
919        };
920        p = skip_white(buf, after + 1)?;
921        let (value, consumed) = parse_node(buf, p, 0, false)?;
922        map.insert(key, value);
923        p = consumed;
924        p = skip_white(buf, p)?;
925        if p < buf.len() && buf[p] == b',' {
926            p += 1;
927            continue;
928        }
929        if p < buf.len() && buf[p] == b'}' {
930            return Ok((Dynamic::Map(Arc::new(RwLock::new(map))), p + 1));
931        }
932        return Err(anyhow!("yaml flow mapping 缺少 ',' 或 '}}' @{}", p));
933    }
934}
935
936/// 解析 `[ a, b, c ]` 这种 inline sequence。
937fn parse_flow_seq(buf: &[u8], pos: usize) -> Result<(Dynamic, usize)> {
938    let mut p = pos + 1;
939    let mut items: Vec<Dynamic> = Vec::new();
940    p = skip_white(buf, p)?;
941    if p < buf.len() && buf[p] == b']' {
942        return Ok((Dynamic::List(Arc::new(RwLock::new(items))), p + 1));
943    }
944    loop {
945        p = skip_white(buf, p)?;
946        let (value, consumed) = parse_node(buf, p, 0, false)?;
947        items.push(value);
948        p = consumed;
949        p = skip_white(buf, p)?;
950        if p < buf.len() && buf[p] == b',' {
951            p += 1;
952            continue;
953        }
954        if p < buf.len() && buf[p] == b']' {
955            return Ok((Dynamic::List(Arc::new(RwLock::new(items))), p + 1));
956        }
957        return Err(anyhow!("yaml flow sequence 缺少 ',' 或 ']' @{}", p));
958    }
959}
960
961// ---- Dynamic 上的便捷方法 ------------------------------------------
962
963impl Dynamic {
964    /// 顶层便捷入口:把 `Dynamic` 序列化成 YAML 字符串。
965    pub fn to_yaml_string(&self) -> String {
966        let mut buf = String::new();
967        self.to_yaml(&mut buf);
968        buf
969    }
970
971    /// 顶层便捷入口:从 YAML 文本解析 `Dynamic`。
972    pub fn from_yaml_buf(buf: &[u8]) -> Result<Self> {
973        let (value, _) = <Self as FromYaml>::from_yaml(buf)?;
974        Ok(value)
975    }
976}
977
978impl Dynamic {
979    /// 给 `to_yaml` / 测试用的小工具:在已有 map 里塞一个 entry 并返回 map。
980    fn map_with_entry(key: SmolStr, value: Dynamic) -> Self {
981        let mut map = IndexMap::new();
982        map.insert(key, value);
983        Dynamic::Map(Arc::new(RwLock::new(map)))
984    }
985}
986
987// ---- 测试 --------------------------------------------------------------
988
989#[cfg(test)]
990mod tests {
991    use super::*;
992
993    fn round_trip(input: &str) -> String {
994        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse yaml");
995        let mut buf = String::new();
996        value.to_yaml(&mut buf);
997        buf
998    }
999
1000    #[test]
1001    fn simple_scalar_round_trip() {
1002        assert_eq!(round_trip("42"), "42");
1003        assert_eq!(round_trip("-7"), "-7");
1004        assert_eq!(round_trip("3.14"), "3.14");
1005        assert_eq!(round_trip("true"), "true");
1006        assert_eq!(round_trip("false"), "false");
1007        assert_eq!(round_trip("null"), "null");
1008        assert_eq!(round_trip("~"), "null");
1009    }
1010
1011    #[test]
1012    fn string_quoting_rules() {
1013        // 像数字的字符串必须加引号。
1014        let value = Dynamic::String("123".into());
1015        let mut buf = String::new();
1016        value.to_yaml(&mut buf);
1017        assert_eq!(buf, "\"123\"");
1018
1019        // 像 bool 的字符串也要加引号。
1020        let value = Dynamic::String("yes".into());
1021        let mut buf = String::new();
1022        value.to_yaml(&mut buf);
1023        assert_eq!(buf, "\"yes\"");
1024
1025        // 普通字符串裸出。
1026        let value = Dynamic::String("hello world".into());
1027        let mut buf = String::new();
1028        value.to_yaml(&mut buf);
1029        assert_eq!(buf, "hello world");
1030
1031        // 含 `:` 的字符串必须引号。
1032        let value = Dynamic::String("a: b".into());
1033        let mut buf = String::new();
1034        value.to_yaml(&mut buf);
1035        assert_eq!(buf, "\"a: b\"");
1036
1037        // 含 `#` 的字符串要引号。
1038        let value = Dynamic::String("color #ff00ff".into());
1039        let mut buf = String::new();
1040        value.to_yaml(&mut buf);
1041        assert_eq!(buf, "\"color #ff00ff\"");
1042    }
1043
1044    #[test]
1045    fn multiline_string_uses_block_scalar() {
1046        let value = Dynamic::String("line1\nline2\nline3".into());
1047        let mut buf = String::new();
1048        value.to_yaml(&mut buf);
1049        assert!(buf.starts_with("|+\n"), "should use block scalar: {buf}");
1050        assert!(buf.contains("line1\n"));
1051        assert!(buf.contains("line2\n"));
1052        assert!(buf.contains("line3\n"));
1053    }
1054
1055    #[test]
1056    fn mapping_block_style() {
1057        let mut map = IndexMap::new();
1058        map.insert(SmolStr::from("name"), Dynamic::String("zust".into()));
1059        map.insert(SmolStr::from("version"), Dynamic::I64(1));
1060        map.insert(SmolStr::from("active"), Dynamic::Bool(true));
1061        let value = Dynamic::Map(Arc::new(RwLock::new(map)));
1062        let mut buf = String::new();
1063        value.to_yaml(&mut buf);
1064        assert!(buf.contains("name: zust"), "{buf}");
1065        assert!(buf.contains("version: 1"), "{buf}");
1066        assert!(buf.contains("active: true"), "{buf}");
1067    }
1068
1069    #[test]
1070    fn nested_mapping_and_list() {
1071        let mut inner = IndexMap::new();
1072        inner.insert(SmolStr::from("a"), Dynamic::I64(1));
1073        inner.insert(SmolStr::from("b"), Dynamic::I64(2));
1074        let mut map = IndexMap::new();
1075        map.insert(SmolStr::from("items"), Dynamic::list(vec![Dynamic::Map(Arc::new(RwLock::new(inner)))]));
1076        let value = Dynamic::Map(Arc::new(RwLock::new(map)));
1077        let yaml = value.to_yaml_string();
1078        assert!(yaml.contains("items:\n  - a: 1"), "{yaml}");
1079    }
1080
1081    #[test]
1082    fn block_sequence_round_trip() {
1083        let input = "- 1\n- 2\n- 3\n";
1084        let (value, consumed) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1085        assert!(matches!(value, Dynamic::List(_)));
1086        assert_eq!(consumed, input.len());
1087    }
1088
1089    #[test]
1090    fn block_mapping_round_trip() {
1091        let input = "name: zust\nversion: 1\n";
1092        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1093        let map = value.get_dynamic("name").unwrap();
1094        assert_eq!(map.as_str(), "zust");
1095        assert_eq!(value.get_dynamic("version").and_then(|v| v.as_int()), Some(1));
1096    }
1097
1098    #[test]
1099    fn nested_block_structures() {
1100        let input = "users:\n  - name: alice\n    age: 30\n  - name: bob\n    age: 25\n";
1101        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1102        let users = value.get_dynamic("users").unwrap();
1103        assert!(users.is_list());
1104        let first = users.get_idx(0).unwrap();
1105        assert_eq!(first.get_dynamic("name").unwrap().as_str(), "alice");
1106        assert_eq!(first.get_dynamic("age").unwrap().as_int(), Some(30));
1107    }
1108
1109    #[test]
1110    fn inline_mapping_and_sequence() {
1111        let input = "{a: 1, b: [2, 3]}";
1112        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1113        assert_eq!(value.get_dynamic("a").unwrap().as_int(), Some(1));
1114        let b = value.get_dynamic("b").unwrap();
1115        assert!(b.is_list());
1116    }
1117
1118    #[test]
1119    fn comments_and_blank_lines_are_ignored() {
1120        let input = "# header comment\nname: zust  # trailing comment\n\nversion: 1\n";
1121        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1122        assert_eq!(value.get_dynamic("name").unwrap().as_str(), "zust");
1123        assert_eq!(value.get_dynamic("version").unwrap().as_int(), Some(1));
1124    }
1125
1126    #[test]
1127    fn double_quoted_string_with_escapes() {
1128        let input = "msg: \"hello\\nworld\"\n";
1129        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1130        assert_eq!(value.get_dynamic("msg").unwrap().as_str(), "hello\nworld");
1131    }
1132
1133    #[test]
1134    fn single_quoted_string_literal() {
1135        let input = "msg: 'a: b'\n";
1136        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1137        assert_eq!(value.get_dynamic("msg").unwrap().as_str(), "a: b");
1138    }
1139
1140    #[test]
1141    fn quoted_round_trip_preserves_string_like_number() {
1142        // 字符串 "123" 必须 round-trip 仍然是字符串,而不是整数。
1143        let mut map = IndexMap::new();
1144        map.insert(SmolStr::from("id"), Dynamic::String("123".into()));
1145        let value = Dynamic::Map(Arc::new(RwLock::new(map)));
1146        let yaml = value.to_yaml_string();
1147        assert!(yaml.contains("id: \"123\""), "{yaml}");
1148        let (parsed, _) = <Dynamic as FromYaml>::from_yaml(yaml.as_bytes()).expect("parse");
1149        let id = parsed.get_dynamic("id").unwrap();
1150        assert_eq!(id.as_str(), "123");
1151        assert!(!id.is_int(), "id should stay string");
1152    }
1153
1154    #[test]
1155    fn empty_collection_serialization() {
1156        let value = Dynamic::List(Arc::new(RwLock::new(Vec::new())));
1157        let mut buf = String::new();
1158        value.to_yaml(&mut buf);
1159        assert_eq!(buf, "[]");
1160    }
1161}