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');
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 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 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
353fn 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
365fn 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
377fn 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
396fn 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 < buf.len() && buf[p] == b' ' {
406 count += 1;
407 p += 1;
408 }
409 count
410}
411
412fn at_line_end(buf: &[u8], pos: usize) -> bool {
414 pos >= buf.len() || buf[pos] == b'\n'
415}
416
417fn 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
429fn 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
439fn 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; 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 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 break;
498 }
499 b',' => {
500 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 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 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
523fn scalar_to_dynamic(raw: &str) -> Dynamic {
525 if matches!(raw, "" | "null" | "Null" | "NULL" | "~") {
527 return Dynamic::Null;
528 }
529 match raw {
532 "true" | "True" | "TRUE" => return Dynamic::Bool(true),
533 "false" | "False" | "FALSE" => return Dynamic::Bool(false),
534 _ => {}
535 }
536 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 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 s.replace("''", "'")
592}
593
594impl 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
602fn 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 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 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 if let Some((key, colon)) = try_read_key(buf, pos) {
628 let value_pos = skip_white(buf, colon + 1)?;
630 return parse_block_map(buf, value_pos, indent, key);
631 }
632 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
641fn try_read_key(buf: &[u8], pos: usize) -> Option<(SmolStr, usize)> {
644 let start = pos;
645 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 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
690fn 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 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 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 let after_value = if pos < buf.len() && buf[pos] == b'\n' {
736 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 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 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 let (value, consumed) = parse_node(buf, p, next_indent, true)?;
761 map.insert(current_key.clone(), value);
762 pos = consumed;
763 }
764 } else {
765 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 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
785fn 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 let after_dash = pos + 1;
812 let after_dash = skip_white(buf, after_dash)?;
813 if !at_line_end(buf, after_dash) {
815 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 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 let (value, consumed) = parse_node(buf, after_colon, parent_indent, false)?;
836 let mut map = IndexMap::new();
837 map.insert(key.clone(), value);
838 let mut p = consumed;
840 if p < buf.len() && buf[p] != b'\n' {
841 p = skip_white(buf, p)?;
843 }
844 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 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 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 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
906fn 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
936fn 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
961impl Dynamic {
964 pub fn to_yaml_string(&self) -> String {
966 let mut buf = String::new();
967 self.to_yaml(&mut buf);
968 buf
969 }
970
971 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 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#[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 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 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 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 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 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 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}