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
9pub trait ToYaml {
17 fn to_yaml(&self, buf: &mut String);
18}
19
20pub trait FromYaml: Sized {
26 fn from_yaml(buf: &[u8]) -> Result<(Self, usize)>;
27}
28
29fn yaml_string_needs_quoting(s: &str) -> bool {
45 if s.is_empty() {
46 return true;
47 }
48 if s == "---" || s == "..." {
50 return true;
51 }
52 let bytes = s.as_bytes();
53 const RESERVED_FIRST: &[u8] = b"!\"&'*|>%@`,[]{}#?:";
55 if RESERVED_FIRST.contains(&bytes[0]) {
56 return true;
57 }
58 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 if looks_like_number(s) {
84 return true;
85 }
86 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 if matches!(bytes.last(), Some(b' ' | b'\t')) {
100 return true;
101 }
102 false
103}
104
105fn looks_like_number(s: &str) -> bool {
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") {
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
117fn 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
140fn 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
162fn 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 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 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
272fn 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 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 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 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 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 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
354fn 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
366fn 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
378fn 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
397fn 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 < buf.len() && buf[p] == b' ' {
407 count += 1;
408 p += 1;
409 }
410 count
411}
412
413fn at_line_end(buf: &[u8], pos: usize) -> bool {
415 pos >= buf.len() || buf[pos] == b'\n'
416}
417
418fn 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
430fn 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
440fn 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; 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 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 break;
499 }
500 b',' => {
501 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 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 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
524fn scalar_to_dynamic(raw: &str) -> Dynamic {
526 if matches!(raw, "" | "null" | "Null" | "NULL" | "~") {
528 return Dynamic::Null;
529 }
530 match raw {
533 "true" | "True" | "TRUE" => return Dynamic::Bool(true),
534 "false" | "False" | "FALSE" => return Dynamic::Bool(false),
535 _ => {}
536 }
537 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 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 s.replace("''", "'")
593}
594
595impl 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
603fn 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 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 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 if let Some((key, colon)) = try_read_key(buf, pos) {
629 let value_pos = skip_white(buf, colon + 1)?;
631 return parse_block_map(buf, value_pos, indent, key);
632 }
633 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
642fn try_read_key(buf: &[u8], pos: usize) -> Option<(SmolStr, usize)> {
645 let start = pos;
646 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 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
691fn 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 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 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 let after_value = if pos < buf.len() && buf[pos] == b'\n' {
737 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 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 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 let (value, consumed) = parse_node(buf, p, next_indent, true)?;
762 map.insert(current_key.clone(), value);
763 pos = consumed;
764 }
765 } else {
766 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 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
786fn 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 let after_dash = pos + 1;
813 let after_dash = skip_white(buf, after_dash)?;
814 if !at_line_end(buf, after_dash) {
816 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 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 let (value, consumed) = parse_node(buf, after_colon, parent_indent, false)?;
837 let mut map = IndexMap::new();
838 map.insert(key.clone(), value);
839 let mut p = consumed;
841 if p < buf.len() && buf[p] != b'\n' {
842 p = skip_white(buf, p)?;
844 }
845 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 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 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 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
907fn 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
937fn 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
962impl Dynamic {
965 pub fn to_yaml_string(&self) -> String {
967 let mut buf = String::new();
968 self.to_yaml(&mut buf);
969 buf
970 }
971
972 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 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#[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 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 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 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 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 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 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}