Skip to main content

dynamic/
yaml.rs

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