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(&pad);
324 buf.push_str("[]");
325 }
326}
327
328fn yaml_write_map<I: Iterator<Item = (SmolStr, Dynamic)>>(entries: I, indent: usize, buf: &mut String) {
329 let pad = " ".repeat(indent);
330 let mut first = true;
331 for (key, value) in entries {
332 if first {
333 if indent > 0 {
336 buf.push_str(&pad);
337 }
338 } else {
339 buf.push('\n');
340 buf.push_str(&pad);
341 }
342 first = false;
343 yaml_write_key(key.as_str(), buf);
344 buf.push(':');
345 if is_yaml_block(&value) {
346 buf.push('\n');
347 value.to_yaml_indent(indent + 2, buf);
348 } else {
349 buf.push(' ');
350 value.to_yaml_indent(0, buf);
351 }
352 }
353 if indent == 0 {
354 buf.push('\n');
355 }
356}
357
358fn map_first_pair_inline(value: &Dynamic) -> Option<(SmolStr, Dynamic)> {
361 if let Dynamic::Map(m) = value {
362 let m = m.read();
363 let first = m.iter().next()?;
364 Some((first.0.clone(), first.1.clone()))
365 } else {
366 None
367 }
368}
369
370fn map_remaining_pairs(value: &Dynamic) -> Option<impl Iterator<Item = (SmolStr, Dynamic)>> {
372 if let Dynamic::Map(m) = value {
373 let m = m.read().clone();
374 let mut iter = m.into_iter();
375 iter.next();
376 Some(iter)
377 } else {
378 None
379 }
380}
381
382fn skip_white(buf: &[u8], mut pos: usize) -> Result<usize> {
387 while pos < buf.len() {
388 match buf[pos] {
389 b' ' | b'\t' | b'\r' | b'\n' => pos += 1,
390 b'#' => {
391 while pos < buf.len() && buf[pos] != b'\n' {
392 pos += 1;
393 }
394 }
395 _ => break,
396 }
397 }
398 Ok(pos)
399}
400
401fn line_indent(buf: &[u8], pos: usize) -> usize {
403 let mut p = pos;
404 while p > 0 && buf[p - 1] != b'\n' {
405 p -= 1;
406 }
407 let mut count = 0usize;
408 while p < buf.len() && buf[p] == b' ' {
411 count += 1;
412 p += 1;
413 }
414 count
415}
416
417fn at_line_end(buf: &[u8], pos: usize) -> bool {
419 pos >= buf.len() || buf[pos] == b'\n'
420}
421
422fn line_is_blank(buf: &[u8], mut pos: usize) -> bool {
424 while pos < buf.len() && buf[pos] != b'\n' {
425 match buf[pos] {
426 b' ' | b'\t' | b'\r' => pos += 1,
427 b'#' => return true,
428 _ => return false,
429 }
430 }
431 true
432}
433
434fn skip_doc_marker(buf: &[u8], mut pos: usize) -> usize {
437 pos = skip_white(buf, pos).unwrap_or(pos);
438 if buf.get(pos..pos + 3) == Some(b"---") && (pos + 3 == buf.len() || matches!(buf[pos + 3], b' ' | b'\t' | b'\n' | b'\r')) {
439 pos += 3;
440 }
441 skip_white(buf, pos).unwrap_or(pos)
442}
443
444fn read_scalar(buf: &[u8], pos: usize) -> Result<(&str, usize)> {
452 let start = pos;
453 let mut end = pos;
454 let mut in_flow_bracket = 0i32; let mut in_double_quote = false;
456 let mut in_single_quote = false;
457 while end < buf.len() {
458 let b = buf[end];
459 if in_double_quote {
460 if b == b'\\' && end + 1 < buf.len() {
461 end += 2;
462 continue;
463 }
464 if b == b'"' {
465 in_double_quote = false;
466 end += 1;
467 continue;
468 }
469 end += 1;
470 continue;
471 }
472 if in_single_quote {
473 if b == b'\'' {
474 if end + 1 < buf.len() && buf[end + 1] == b'\'' {
475 end += 2;
477 continue;
478 }
479 in_single_quote = false;
480 end += 1;
481 continue;
482 }
483 end += 1;
484 continue;
485 }
486 match b {
487 b'"' => {
488 in_double_quote = true;
489 end += 1;
490 }
491 b'\'' => {
492 in_single_quote = true;
493 end += 1;
494 }
495 b'[' | b'{' => {
496 in_flow_bracket += 1;
497 end += 1;
498 }
499 b']' | b'}' => {
500 break;
503 }
504 b',' => {
505 break;
508 }
509 b':' if in_flow_bracket == 0 && end + 1 < buf.len() && (buf[end + 1] == b' ' || buf[end + 1] == b'\t' || buf[end + 1] == b'\n') => {
510 break;
512 }
513 b'#' if in_flow_bracket == 0 => break,
514 b'\n' if in_flow_bracket == 0 => break,
515 _ => end += 1,
516 }
517 }
518 while end > start && matches!(buf[end - 1], b' ' | b'\t' | b'\r') {
520 end -= 1;
521 }
522 if end == start {
523 return Err(anyhow!("yaml scalar 为空 @{}", start));
524 }
525 std::str::from_utf8(&buf[start..end]).map(|s| (s, end - start)).map_err(|e| anyhow!("yaml scalar 含非法 UTF-8: {e}"))
526}
527
528fn scalar_to_dynamic(raw: &str) -> Dynamic {
530 if matches!(raw, "" | "null" | "Null" | "NULL" | "~") {
532 return Dynamic::Null;
533 }
534 match raw {
537 "true" | "True" | "TRUE" => return Dynamic::Bool(true),
538 "false" | "False" | "FALSE" => return Dynamic::Bool(false),
539 _ => {}
540 }
541 if let Ok(v) = raw.parse::<i64>() {
543 return Dynamic::I64(v);
544 }
545 if let Ok(v) = raw.parse::<f64>() {
546 if v.is_finite() {
547 return Dynamic::F64(v);
548 }
549 }
550 if raw.starts_with('"') && raw.ends_with('"') && raw.len() >= 2 {
552 return Dynamic::String(unescape_double_quoted(&raw[1..raw.len() - 1]).into());
553 }
554 if raw.starts_with('\'') && raw.ends_with('\'') && raw.len() >= 2 {
555 return Dynamic::String(unescape_single_quoted(&raw[1..raw.len() - 1]).into());
556 }
557 Dynamic::String(raw.into())
558}
559
560fn unescape_double_quoted(s: &str) -> String {
561 let mut out = String::with_capacity(s.len());
562 let mut chars = s.chars();
563 while let Some(c) = chars.next() {
564 if c == '\\' {
565 match chars.next() {
566 Some('n') => out.push('\n'),
567 Some('t') => out.push('\t'),
568 Some('r') => out.push('\r'),
569 Some('\\') => out.push('\\'),
570 Some('"') => out.push('"'),
571 Some('0') => out.push('\0'),
572 Some('/') => out.push('/'),
573 Some('u') => {
574 let hex: String = chars.by_ref().take(4).collect();
575 if let Ok(code) = u32::from_str_radix(&hex, 16) {
576 if let Some(ch) = char::from_u32(code) {
577 out.push(ch);
578 }
579 }
580 }
581 Some(other) => {
582 out.push('\\');
583 out.push(other);
584 }
585 None => out.push('\\'),
586 }
587 } else {
588 out.push(c);
589 }
590 }
591 out
592}
593
594fn unescape_single_quoted(s: &str) -> String {
595 s.replace("''", "'")
597}
598
599impl FromYaml for Dynamic {
601 fn from_yaml(buf: &[u8]) -> Result<(Self, usize)> {
602 let pos = skip_doc_marker(buf, 0);
603 parse_node(buf, pos, 0, false)
604 }
605}
606
607fn parse_node(buf: &[u8], pos: usize, min_indent: usize, in_block: bool) -> Result<(Dynamic, usize)> {
611 let pos = skip_white(buf, pos)?;
612 if pos >= buf.len() {
613 return Err(anyhow!("yaml 文档提前结束"));
614 }
615 if buf[pos] == b'{' {
617 return parse_flow_map(buf, pos);
618 }
619 if buf[pos] == b'[' {
620 return parse_flow_seq(buf, pos);
621 }
622 let indent = line_indent(buf, pos);
624 if indent < min_indent {
625 return Err(anyhow!("yaml 缩进不足: {} < {}", indent, min_indent));
626 }
627 if buf[pos] == b'-' && (pos + 1 == buf.len() || matches!(buf[pos + 1], b' ' | b'\t' | b'\n')) {
628 return parse_block_seq(buf, pos, indent);
629 }
630 if let Some((key, colon)) = try_read_key(buf, pos) {
633 let value_pos = skip_white(buf, colon + 1)?;
635 return parse_block_map(buf, value_pos, indent, key);
636 }
637 if in_block {
640 return Err(anyhow!("yaml 期望 block 结构但遇到孤立标量 @{}", pos));
641 }
642 let (raw, consumed) = read_scalar(buf, pos)?;
643 Ok((scalar_to_dynamic(raw), pos + consumed))
644}
645
646fn try_read_key(buf: &[u8], pos: usize) -> Option<(SmolStr, usize)> {
649 let start = pos;
650 if buf[pos] == b'"' || buf[pos] == b'\'' {
652 let quote = buf[pos];
653 let mut p = pos + 1;
654 while p < buf.len() && buf[p] != quote {
655 if buf[p] == b'\\' && quote == b'"' && p + 1 < buf.len() {
656 p += 2;
657 continue;
658 }
659 p += 1;
660 }
661 if p >= buf.len() {
662 return None;
663 }
664 let key = &buf[start + 1..p];
665 let after = p + 1;
666 let after = skip_white(buf, after).ok()?;
667 if after >= buf.len() || buf[after] != b':' {
668 return None;
669 }
670 let key_str = std::str::from_utf8(key).ok()?;
671 return Some((SmolStr::from(if quote == b'"' { unescape_double_quoted(key_str) } else { unescape_single_quoted(key_str) }), after));
672 }
673 let mut p = pos;
677 while p < buf.len() {
678 match buf[p] {
679 b':' if p + 1 < buf.len() && (matches!(buf[p + 1], b' ' | b'\t' | b'\n') || p + 1 == buf.len()) => break,
680 b' ' | b'\t' | b'\r' | b'\n' | b'#' | b',' | b'}' | b']' => return None,
681 _ => p += 1,
682 }
683 }
684 if p >= buf.len() || buf[p] != b':' {
685 return None;
686 }
687 let key_bytes = &buf[start..p];
688 let key = std::str::from_utf8(key_bytes).ok()?.trim();
689 if key.is_empty() {
690 return None;
691 }
692 Some((SmolStr::from(key), p))
693}
694
695fn parse_block_map(buf: &[u8], mut pos: usize, parent_indent: usize, first_key: SmolStr) -> Result<(Dynamic, usize)> {
700 let mut map: IndexMap<SmolStr, Dynamic> = IndexMap::new();
701 let mut current_key = first_key;
702 let mut expect_value = true;
703 loop {
704 pos = skip_white(buf, pos)?;
705 while pos < buf.len() && line_is_blank(buf, pos) {
707 let mut p = pos;
708 while p < buf.len() && buf[p] != b'\n' {
709 p += 1;
710 }
711 if p < buf.len() {
712 p += 1;
713 }
714 pos = p;
715 }
716 if pos >= buf.len() {
717 break;
718 }
719 let indent = line_indent(buf, pos);
720 if indent < parent_indent {
721 break;
722 }
723 if expect_value {
724 if indent == parent_indent {
729 if let Some((next_key, colon)) = try_read_key(buf, pos) {
730 map.insert(current_key.clone(), Dynamic::Null);
731 current_key = next_key;
732 pos = colon + 1;
733 expect_value = true;
734 continue;
735 }
736 }
737 let after_value = if pos < buf.len() && buf[pos] == b'\n' {
741 pos + 1
743 } else {
744 skip_white(buf, pos)?
745 };
746 let on_next_line = pos < buf.len() && buf[pos] == b'\n';
747 if on_next_line || at_line_end(buf, after_value) {
748 let p = after_value;
750 let next_indent = if p < buf.len() { line_indent(buf, p) } else { 0 };
751 if next_indent <= parent_indent {
752 map.insert(current_key.clone(), Dynamic::Null);
753 expect_value = false;
754 pos = p;
755 continue;
756 }
757 if let Some((next_key, colon)) = try_read_key(buf, p) {
758 let value_pos = skip_white(buf, colon + 1)?;
760 let (value, consumed) = parse_block_map(buf, value_pos, next_indent - 1, next_key)?;
761 map.insert(current_key.clone(), value);
762 pos = consumed;
763 } else {
764 let (value, consumed) = parse_node(buf, p, next_indent, true)?;
766 map.insert(current_key.clone(), value);
767 pos = consumed;
768 }
769 } else {
770 let (value, consumed) = parse_node(buf, after_value, parent_indent, false)?;
772 map.insert(current_key.clone(), value);
773 pos = consumed;
774 }
775 expect_value = false;
776 continue;
777 }
778 if let Some((next_key, colon)) = try_read_key(buf, pos) {
780 current_key = next_key;
781 pos = colon + 1;
782 expect_value = true;
783 } else {
784 break;
785 }
786 }
787 Ok((Dynamic::Map(Arc::new(RwLock::new(map))), pos))
788}
789
790fn parse_block_seq(buf: &[u8], mut pos: usize, parent_indent: usize) -> Result<(Dynamic, usize)> {
792 let mut items: Vec<Dynamic> = Vec::new();
793 loop {
794 pos = skip_white(buf, pos)?;
795 while pos < buf.len() && line_is_blank(buf, pos) {
796 let mut p = pos;
797 while p < buf.len() && buf[p] != b'\n' {
798 p += 1;
799 }
800 if p < buf.len() {
801 p += 1;
802 }
803 pos = p;
804 }
805 if pos >= buf.len() {
806 break;
807 }
808 let indent = line_indent(buf, pos);
809 if indent < parent_indent {
810 break;
811 }
812 if buf[pos] != b'-' || (pos + 1 < buf.len() && !matches!(buf[pos + 1], b' ' | b'\t' | b'\n')) {
813 break;
814 }
815 let after_dash = pos + 1;
817 let after_dash = skip_white(buf, after_dash)?;
818 if !at_line_end(buf, after_dash) {
820 if let Some((key, colon)) = try_read_key(buf, after_dash) {
823 let after_colon = skip_white(buf, colon + 1)?;
824 if at_line_end(buf, after_colon) {
825 let mut p = after_colon;
827 if p < buf.len() && buf[p] == b'\n' {
828 p += 1;
829 }
830 let next_indent = if p < buf.len() { line_indent(buf, p) } else { 0 };
831 let (value, consumed) = parse_node(buf, p, next_indent.max(parent_indent + 2), true)?;
832 let mut map = IndexMap::new();
833 map.insert(key, value);
834 items.push(Dynamic::Map(Arc::new(RwLock::new(map))));
835 pos = consumed;
836 } else {
837 let (value, consumed) = parse_node(buf, after_colon, parent_indent, false)?;
841 let mut map = IndexMap::new();
842 map.insert(key.clone(), value);
843 let mut p = consumed;
845 if p < buf.len() && buf[p] != b'\n' {
846 p = skip_white(buf, p)?;
848 }
849 while p < buf.len() && line_is_blank(buf, p) {
851 let mut q = p;
852 while q < buf.len() && buf[q] != b'\n' {
853 q += 1;
854 }
855 if q < buf.len() {
856 q += 1;
857 }
858 p = q;
859 }
860 if p < buf.len() {
861 let next_indent = line_indent(buf, p);
862 let dash_indent = line_indent(buf, pos);
863 let key_start = p + next_indent;
865 if next_indent > dash_indent && key_start < buf.len()
866 && let Some((next_key, colon)) = try_read_key(buf, key_start)
867 {
868 let value_pos = skip_white(buf, colon + 1)?;
871 let (rest, consumed2) = parse_block_map(buf, value_pos, next_indent - 1, next_key)?;
872 if let Dynamic::Map(extra) = rest {
873 for (k, v) in extra.read().iter() {
874 map.insert(k.clone(), v.clone());
875 }
876 }
877 p = consumed2;
878 }
879 }
880 items.push(Dynamic::Map(Arc::new(RwLock::new(map))));
881 pos = p;
882 }
883 } else {
884 let (value, consumed) = parse_node(buf, after_dash, parent_indent, false)?;
885 items.push(value);
886 pos = consumed;
887 }
888 continue;
889 }
890 let mut p = after_dash;
892 if p < buf.len() && buf[p] == b'\n' {
893 p += 1;
894 }
895 if p >= buf.len() {
896 items.push(Dynamic::Null);
897 break;
898 }
899 let next_indent = line_indent(buf, p);
900 if next_indent <= parent_indent {
901 items.push(Dynamic::Null);
902 break;
903 }
904 let (value, consumed) = parse_node(buf, p, next_indent, true)?;
905 items.push(value);
906 pos = consumed;
907 }
908 Ok((Dynamic::List(Arc::new(RwLock::new(items))), pos))
909}
910
911fn parse_flow_map(buf: &[u8], pos: usize) -> Result<(Dynamic, usize)> {
913 let mut p = pos + 1;
914 let mut map: IndexMap<SmolStr, Dynamic> = IndexMap::new();
915 p = skip_white(buf, p)?;
916 if p < buf.len() && buf[p] == b'}' {
917 return Ok((Dynamic::Map(Arc::new(RwLock::new(map))), p + 1));
918 }
919 loop {
920 p = skip_white(buf, p)?;
921 let (key, after) = match try_read_key(buf, p) {
922 Some(pair) => pair,
923 None => return Err(anyhow!("yaml flow mapping 缺少 key @{}", p)),
924 };
925 p = skip_white(buf, after + 1)?;
926 let (value, consumed) = parse_node(buf, p, 0, false)?;
927 map.insert(key, value);
928 p = consumed;
929 p = skip_white(buf, p)?;
930 if p < buf.len() && buf[p] == b',' {
931 p += 1;
932 continue;
933 }
934 if p < buf.len() && buf[p] == b'}' {
935 return Ok((Dynamic::Map(Arc::new(RwLock::new(map))), p + 1));
936 }
937 return Err(anyhow!("yaml flow mapping 缺少 ',' 或 '}}' @{}", p));
938 }
939}
940
941fn parse_flow_seq(buf: &[u8], pos: usize) -> Result<(Dynamic, usize)> {
943 let mut p = pos + 1;
944 let mut items: Vec<Dynamic> = Vec::new();
945 p = skip_white(buf, p)?;
946 if p < buf.len() && buf[p] == b']' {
947 return Ok((Dynamic::List(Arc::new(RwLock::new(items))), p + 1));
948 }
949 loop {
950 p = skip_white(buf, p)?;
951 let (value, consumed) = parse_node(buf, p, 0, false)?;
952 items.push(value);
953 p = consumed;
954 p = skip_white(buf, p)?;
955 if p < buf.len() && buf[p] == b',' {
956 p += 1;
957 continue;
958 }
959 if p < buf.len() && buf[p] == b']' {
960 return Ok((Dynamic::List(Arc::new(RwLock::new(items))), p + 1));
961 }
962 return Err(anyhow!("yaml flow sequence 缺少 ',' 或 ']' @{}", p));
963 }
964}
965
966impl Dynamic {
969 pub fn to_yaml_string(&self) -> String {
971 let mut buf = String::new();
972 self.to_yaml(&mut buf);
973 buf
974 }
975
976 pub fn from_yaml_buf(buf: &[u8]) -> Result<Self> {
978 let (value, _) = <Self as FromYaml>::from_yaml(buf)?;
979 Ok(value)
980 }
981}
982
983impl Dynamic {
984 fn map_with_entry(key: SmolStr, value: Dynamic) -> Self {
986 let mut map = IndexMap::new();
987 map.insert(key, value);
988 Dynamic::Map(Arc::new(RwLock::new(map)))
989 }
990}
991
992#[cfg(test)]
995mod tests {
996 use super::*;
997
998 fn round_trip(input: &str) -> String {
999 let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse yaml");
1000 let mut buf = String::new();
1001 value.to_yaml(&mut buf);
1002 buf
1003 }
1004
1005 #[test]
1006 fn simple_scalar_round_trip() {
1007 assert_eq!(round_trip("42"), "42");
1008 assert_eq!(round_trip("-7"), "-7");
1009 assert_eq!(round_trip("3.14"), "3.14");
1010 assert_eq!(round_trip("true"), "true");
1011 assert_eq!(round_trip("false"), "false");
1012 assert_eq!(round_trip("null"), "null");
1013 assert_eq!(round_trip("~"), "null");
1014 }
1015
1016 #[test]
1017 fn string_quoting_rules() {
1018 let value = Dynamic::String("123".into());
1020 let mut buf = String::new();
1021 value.to_yaml(&mut buf);
1022 assert_eq!(buf, "\"123\"");
1023
1024 let value = Dynamic::String("yes".into());
1026 let mut buf = String::new();
1027 value.to_yaml(&mut buf);
1028 assert_eq!(buf, "\"yes\"");
1029
1030 let value = Dynamic::String("hello world".into());
1032 let mut buf = String::new();
1033 value.to_yaml(&mut buf);
1034 assert_eq!(buf, "hello world");
1035
1036 let value = Dynamic::String("a: b".into());
1038 let mut buf = String::new();
1039 value.to_yaml(&mut buf);
1040 assert_eq!(buf, "\"a: b\"");
1041
1042 let value = Dynamic::String("color #ff00ff".into());
1044 let mut buf = String::new();
1045 value.to_yaml(&mut buf);
1046 assert_eq!(buf, "\"color #ff00ff\"");
1047 }
1048
1049 #[test]
1050 fn multiline_string_uses_block_scalar() {
1051 let value = Dynamic::String("line1\nline2\nline3".into());
1052 let mut buf = String::new();
1053 value.to_yaml(&mut buf);
1054 assert!(buf.starts_with("|+\n"), "should use block scalar: {buf}");
1055 assert!(buf.contains("line1\n"));
1056 assert!(buf.contains("line2\n"));
1057 assert!(buf.contains("line3\n"));
1058 }
1059
1060 #[test]
1061 fn mapping_block_style() {
1062 let mut map = IndexMap::new();
1063 map.insert(SmolStr::from("name"), Dynamic::String("zust".into()));
1064 map.insert(SmolStr::from("version"), Dynamic::I64(1));
1065 map.insert(SmolStr::from("active"), Dynamic::Bool(true));
1066 let value = Dynamic::Map(Arc::new(RwLock::new(map)));
1067 let mut buf = String::new();
1068 value.to_yaml(&mut buf);
1069 assert!(buf.contains("name: zust"), "{buf}");
1070 assert!(buf.contains("version: 1"), "{buf}");
1071 assert!(buf.contains("active: true"), "{buf}");
1072 }
1073
1074 #[test]
1075 fn nested_mapping_and_list() {
1076 let mut inner = IndexMap::new();
1077 inner.insert(SmolStr::from("a"), Dynamic::I64(1));
1078 inner.insert(SmolStr::from("b"), Dynamic::I64(2));
1079 let mut map = IndexMap::new();
1080 map.insert(SmolStr::from("items"), Dynamic::list(vec![Dynamic::Map(Arc::new(RwLock::new(inner)))]));
1081 let value = Dynamic::Map(Arc::new(RwLock::new(map)));
1082 let yaml = value.to_yaml_string();
1083 assert!(yaml.contains("items:\n - a: 1"), "{yaml}");
1084 }
1085
1086 #[test]
1087 fn block_sequence_round_trip() {
1088 let input = "- 1\n- 2\n- 3\n";
1089 let (value, consumed) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1090 assert!(matches!(value, Dynamic::List(_)));
1091 assert_eq!(consumed, input.len());
1092 }
1093
1094 #[test]
1095 fn block_mapping_round_trip() {
1096 let input = "name: zust\nversion: 1\n";
1097 let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1098 let map = value.get_dynamic("name").unwrap();
1099 assert_eq!(map.as_str(), "zust");
1100 assert_eq!(value.get_dynamic("version").and_then(|v| v.as_int()), Some(1));
1101 }
1102
1103 #[test]
1104 fn nested_block_structures() {
1105 let input = "users:\n - name: alice\n age: 30\n - name: bob\n age: 25\n";
1106 let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1107 let users = value.get_dynamic("users").unwrap();
1108 assert!(users.is_list());
1109 let first = users.get_idx(0).unwrap();
1110 assert_eq!(first.get_dynamic("name").unwrap().as_str(), "alice");
1111 assert_eq!(first.get_dynamic("age").unwrap().as_int(), Some(30));
1112 }
1113
1114 #[test]
1115 fn inline_mapping_and_sequence() {
1116 let input = "{a: 1, b: [2, 3]}";
1117 let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1118 assert_eq!(value.get_dynamic("a").unwrap().as_int(), Some(1));
1119 let b = value.get_dynamic("b").unwrap();
1120 assert!(b.is_list());
1121 }
1122
1123 #[test]
1124 fn comments_and_blank_lines_are_ignored() {
1125 let input = "# header comment\nname: zust # trailing comment\n\nversion: 1\n";
1126 let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1127 assert_eq!(value.get_dynamic("name").unwrap().as_str(), "zust");
1128 assert_eq!(value.get_dynamic("version").unwrap().as_int(), Some(1));
1129 }
1130
1131 #[test]
1132 fn double_quoted_string_with_escapes() {
1133 let input = "msg: \"hello\\nworld\"\n";
1134 let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1135 assert_eq!(value.get_dynamic("msg").unwrap().as_str(), "hello\nworld");
1136 }
1137
1138 #[test]
1139 fn single_quoted_string_literal() {
1140 let input = "msg: 'a: b'\n";
1141 let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1142 assert_eq!(value.get_dynamic("msg").unwrap().as_str(), "a: b");
1143 }
1144
1145 #[test]
1146 fn quoted_round_trip_preserves_string_like_number() {
1147 let mut map = IndexMap::new();
1149 map.insert(SmolStr::from("id"), Dynamic::String("123".into()));
1150 let value = Dynamic::Map(Arc::new(RwLock::new(map)));
1151 let yaml = value.to_yaml_string();
1152 assert!(yaml.contains("id: \"123\""), "{yaml}");
1153 let (parsed, _) = <Dynamic as FromYaml>::from_yaml(yaml.as_bytes()).expect("parse");
1154 let id = parsed.get_dynamic("id").unwrap();
1155 assert_eq!(id.as_str(), "123");
1156 assert!(!id.is_int(), "id should stay string");
1157 }
1158
1159 #[test]
1160 fn empty_collection_serialization() {
1161 let value = Dynamic::List(Arc::new(RwLock::new(Vec::new())));
1162 let mut buf = String::new();
1163 value.to_yaml(&mut buf);
1164 assert_eq!(buf, "[]");
1165 }
1166}