1use crate::Dynamic;
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!(s, "true" | "false" | "null" | "Null" | "NULL" | "~" | "yes" | "no" | "on" | "off" | "True" | "False" | "TRUE" | "FALSE" | "YES" | "NO" | "ON" | "OFF") {
60 return true;
61 }
62 if looks_like_number(s) {
64 return true;
65 }
66 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 if matches!(s, "+" | "-" | "<<" | ">>" | "<=>" | "===" | "=~" | "!~" | "**" | "&&" | "||") {
80 return true;
81 }
82 if s.starts_with('|') || s.starts_with('>') {
87 return true;
88 }
89 if s.starts_with('+') || s.starts_with('-') {
93 return true;
94 }
95 if matches!(bytes.last(), Some(b' ' | b'\t')) {
99 return true;
100 }
101 false
102}
103
104fn looks_like_number(s: &str) -> bool {
105 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
112fn 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
135fn 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 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 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
248fn is_yaml_block(value: &Dynamic) -> bool {
252 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
261fn 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 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 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 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 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 buf.push_str(&pad);
334 buf.push_str("[]");
335 }
336}
337
338fn 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 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
373fn 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
385fn 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
397fn 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
416fn 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 < buf.len() && buf[p] == b' ' {
426 count += 1;
427 p += 1;
428 }
429 count
430}
431
432fn at_line_end(buf: &[u8], pos: usize) -> bool {
434 pos >= buf.len() || buf[pos] == b'\n'
435}
436
437fn 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
449fn 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
459fn 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; 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 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 break;
518 }
519 b',' => {
520 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 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 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
543fn scalar_to_dynamic(raw: &str) -> Dynamic {
545 if matches!(raw, "" | "null" | "Null" | "NULL" | "~") {
547 return Dynamic::Null;
548 }
549 match raw {
552 "true" | "True" | "TRUE" => return Dynamic::Bool(true),
553 "false" | "False" | "FALSE" => return Dynamic::Bool(false),
554 _ => {}
555 }
556 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 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 s.replace("''", "'")
612}
613
614impl 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
622fn 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 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 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 if let Some((key, colon)) = try_read_key(buf, pos) {
648 let value_pos = skip_white(buf, colon + 1)?;
650 return parse_block_map(buf, value_pos, indent, key);
651 }
652 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
661fn try_read_key(buf: &[u8], pos: usize) -> Option<(SmolStr, usize)> {
664 let start = pos;
665 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 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
710fn 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 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 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 let after_value = if pos < buf.len() && buf[pos] == b'\n' {
756 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 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 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 let (value, consumed) = parse_node(buf, p, next_indent, true)?;
781 map.insert(current_key.clone(), value);
782 pos = consumed;
783 }
784 } else {
785 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 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
805fn 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 let after_dash = pos + 1;
832 let after_dash = skip_white(buf, after_dash)?;
833 if !at_line_end(buf, after_dash) {
835 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 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 let (value, consumed) = parse_node(buf, after_colon, parent_indent, false)?;
856 let mut map = IndexMap::new();
857 map.insert(key.clone(), value);
858 let mut p = consumed;
860 if p < buf.len() && buf[p] != b'\n' {
861 p = skip_white(buf, p)?;
863 }
864 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 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 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 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
927fn 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
957fn 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
982impl Dynamic {
985 pub fn to_yaml_string(&self) -> String {
987 let mut buf = String::new();
988 self.to_yaml(&mut buf);
989 buf
990 }
991
992 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 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 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 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 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 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 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}