use crate::{Dynamic, ZOnce};
use anyhow::{Result, anyhow};
use indexmap::IndexMap;
use parking_lot::RwLock;
use smol_str::SmolStr;
use std::sync::Arc;
pub trait ToYaml {
fn to_yaml(&self, buf: &mut String);
}
pub trait FromYaml: Sized {
fn from_yaml(buf: &[u8]) -> Result<(Self, usize)>;
}
fn yaml_string_needs_quoting(s: &str) -> bool {
if s.is_empty() {
return true;
}
if s == "---" || s == "..." {
return true;
}
let bytes = s.as_bytes();
const RESERVED_FIRST: &[u8] = b"!\"&'*|>%@`,[]{}#?:";
if RESERVED_FIRST.contains(&bytes[0]) {
return true;
}
if matches!(
s,
"true"
| "false"
| "null"
| "Null"
| "NULL"
| "~"
| "yes"
| "no"
| "on"
| "off"
| "True"
| "False"
| "TRUE"
| "FALSE"
| "YES"
| "NO"
| "ON"
| "OFF"
) {
return true;
}
if looks_like_number(s) {
return true;
}
for (idx, b) in bytes.iter().enumerate() {
match *b {
b'\n' | b'\t' => return true,
0..=0x1f => return true,
b':' if idx + 1 < bytes.len() && (bytes[idx + 1] == b' ' || bytes[idx + 1] == b'\t') => return true,
b'#' if idx > 0 && (bytes[idx - 1] == b' ' || bytes[idx - 1] == b'\t') => return true,
_ => {}
}
}
if matches!(bytes.last(), Some(b' ' | b'\t')) {
return true;
}
false
}
fn looks_like_number(s: &str) -> bool {
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") {
return true;
}
if s.contains('.') || s.contains('e') || s.contains('E') {
return s.parse::<f64>().is_ok()
} else {
return s.parse::<i64>().is_ok()
}
}
fn yaml_quote_string(s: &str, buf: &mut String) {
buf.push('"');
for ch in s.chars() {
match ch {
'\\' => buf.push_str("\\\\"),
'"' => buf.push_str("\\\""),
'\n' => buf.push_str("\\n"),
'\r' => buf.push_str("\\r"),
'\t' => buf.push_str("\\t"),
'\0' => buf.push_str("\\0"),
'\x08' => buf.push_str("\\b"),
'\x0c' => buf.push_str("\\f"),
c if (c as u32) < 0x20 => {
use std::fmt::Write;
let _ = write!(buf, "\\u{:04x}", c as u32);
}
c => buf.push(c),
}
}
buf.push('"');
}
fn yaml_block_string(s: &str, indent: usize, buf: &mut String) {
let pad = " ".repeat(indent);
buf.push_str("|+\n");
let mut count = 0usize;
for ch in s.chars() {
buf.push_str(&pad);
if ch == '\n' {
buf.push('\n');
count = 0;
} else {
buf.push(ch);
count += 1;
}
}
if count > 0 {
buf.push('\n');
}
}
fn yaml_write_string(s: &str, indent: usize, buf: &mut String) {
if s.contains('\n') {
yaml_block_string(s, indent, buf);
return;
}
if yaml_string_needs_quoting(s) {
yaml_quote_string(s, buf);
} else {
buf.push_str(s);
}
}
fn yaml_write_key(key: &str, buf: &mut String) {
if yaml_string_needs_quoting(key) {
yaml_quote_string(key, buf);
} else {
buf.push_str(key);
}
}
impl ToYaml for &str {
fn to_yaml(&self, buf: &mut String) {
yaml_write_string(self, 0, buf);
}
}
impl ToYaml for i64 {
fn to_yaml(&self, buf: &mut String) {
buf.push_str(&self.to_string());
}
}
impl ToYaml for Dynamic {
fn to_yaml(&self, buf: &mut String) {
self.to_yaml_indent(0, buf);
}
}
impl Dynamic {
fn to_yaml_indent(&self, indent: usize, buf: &mut String) {
match self {
Self::Iter { .. } => {}
Self::Null => buf.push_str("null"),
Self::Bool(b) => buf.push_str(if *b { "true" } else { "false" }),
Self::F16(bits) => buf.push_str(&crate::f16_to_f64(*bits).to_string()),
Self::F32(f) => buf.push_str(&f.to_string()),
Self::F64(f) => buf.push_str(&f.to_string()),
Self::I8(i) => buf.push_str(&i.to_string()),
Self::I16(i) => buf.push_str(&i.to_string()),
Self::I32(i) => buf.push_str(&i.to_string()),
Self::I64(i) => buf.push_str(&i.to_string()),
Self::U8(i) => buf.push_str(&i.to_string()),
Self::U16(i) => buf.push_str(&i.to_string()),
Self::U32(i) => buf.push_str(&i.to_string()),
Self::U64(i) => buf.push_str(&i.to_string()),
Self::String(s) => yaml_write_string(s.as_str(), indent, buf),
Self::StringBuf(s) => yaml_write_string(s.as_str(), indent, buf),
Self::Bytes(vec) => yaml_write_seq(vec.iter().map(|b| Dynamic::U8(*b)), indent, buf),
Self::VecI8(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::I8(*v)), indent, buf),
Self::VecU16(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::U16(*v)), indent, buf),
Self::VecI16(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::I16(*v)), indent, buf),
Self::VecU32(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::U32(*v)), indent, buf),
Self::VecI32(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::I32(*v)), indent, buf),
Self::VecF32(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::F32(*v)), indent, buf),
Self::VecU64(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::U64(*v)), indent, buf),
Self::VecI64(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::I64(*v)), indent, buf),
Self::VecF64(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::F64(*v)), indent, buf),
Self::List(items) => {
let items = items.read().clone();
yaml_write_seq(items.into_iter(), indent, buf);
}
Self::Map(map) => {
let map = map.read().clone();
yaml_write_map(map.into_iter(), indent, buf);
}
Self::StructView { .. } | Self::StructOwned { .. } => {
let keys = self.keys();
if keys.is_empty() {
buf.push_str("{}\n");
return;
}
for key in keys.iter() {
buf.push_str(&" ".repeat(indent));
yaml_write_key(key.as_str(), buf);
buf.push(':');
let value = self.get_dynamic(key).unwrap_or(Dynamic::Null);
if is_yaml_block(&value) {
buf.push('\n');
value.to_yaml_indent(indent + 2, buf);
} else {
buf.push(' ');
value.to_yaml_indent(0, buf);
buf.push('\n');
}
}
}
Self::Custom(value) => {
buf.push_str("{@custom: ");
yaml_write_string(value.custom_type_name(), indent, buf);
buf.push_str("}\n");
}
}
}
}
fn is_yaml_block(value: &Dynamic) -> bool {
matches!(value, Dynamic::Map(_) | Dynamic::List(_) | Dynamic::Bytes(_) | Dynamic::StructView { .. } | Dynamic::StructOwned { .. })
|| matches!(value, Dynamic::VecI8(_) | Dynamic::VecU16(_) | Dynamic::VecI16(_) | Dynamic::VecU32(_) | Dynamic::VecI32(_) | Dynamic::VecF32(_) | Dynamic::VecU64(_) | Dynamic::VecI64(_) | Dynamic::VecF64(_))
}
fn yaml_write_seq<I: Iterator<Item = Dynamic>>(items: I, indent: usize, buf: &mut String) {
let pad = " ".repeat(indent);
let mut first = true;
for item in items {
buf.push_str(if first { &pad } else { "\n" });
if !first {
buf.push_str(&pad);
}
first = false;
buf.push_str("- ");
if let Some(pairs) = map_first_pair_inline(&item) {
yaml_write_key(pairs.0.as_str(), buf);
buf.push(':');
if is_yaml_block(&pairs.1) {
buf.push('\n');
pairs.1.to_yaml_indent(indent + 4, buf);
} else {
buf.push(' ');
pairs.1.to_yaml_indent(0, buf);
}
if let Some(rest) = map_remaining_pairs(&item) {
buf.push('\n');
buf.push_str(&" ".repeat(indent + 4));
yaml_write_map(rest, indent + 4, buf);
}
} else if is_yaml_block(&item) {
let one_more = indent + 2;
buf.push('\n');
item.to_yaml_indent(one_more, buf);
} else {
item.to_yaml_indent(0, buf);
}
}
if first {
buf.push_str("[]");
}
}
fn yaml_write_map<I: Iterator<Item = (SmolStr, Dynamic)>>(entries: I, indent: usize, buf: &mut String) {
let pad = " ".repeat(indent);
let mut first = true;
for (key, value) in entries {
if first {
if indent > 0 {
buf.push_str(&pad);
}
} else {
buf.push('\n');
buf.push_str(&pad);
}
first = false;
yaml_write_key(key.as_str(), buf);
buf.push(':');
if is_yaml_block(&value) {
buf.push('\n');
value.to_yaml_indent(indent + 2, buf);
} else {
buf.push(' ');
value.to_yaml_indent(0, buf);
}
}
if indent == 0 {
buf.push('\n');
}
}
fn map_first_pair_inline(value: &Dynamic) -> Option<(SmolStr, Dynamic)> {
if let Dynamic::Map(m) = value {
let m = m.read();
let first = m.iter().next()?;
Some((first.0.clone(), first.1.clone()))
} else {
None
}
}
fn map_remaining_pairs(value: &Dynamic) -> Option<impl Iterator<Item = (SmolStr, Dynamic)>> {
if let Dynamic::Map(m) = value {
let m = m.read().clone();
let mut iter = m.into_iter();
iter.next();
Some(iter)
} else {
None
}
}
fn skip_white(buf: &[u8], mut pos: usize) -> Result<usize> {
while pos < buf.len() {
match buf[pos] {
b' ' | b'\t' | b'\r' | b'\n' => pos += 1,
b'#' => {
while pos < buf.len() && buf[pos] != b'\n' {
pos += 1;
}
}
_ => break,
}
}
Ok(pos)
}
fn line_indent(buf: &[u8], pos: usize) -> usize {
let mut p = pos;
while p > 0 && buf[p - 1] != b'\n' {
p -= 1;
}
let mut count = 0usize;
while p < buf.len() && buf[p] == b' ' {
count += 1;
p += 1;
}
count
}
fn at_line_end(buf: &[u8], pos: usize) -> bool {
pos >= buf.len() || buf[pos] == b'\n'
}
fn line_is_blank(buf: &[u8], mut pos: usize) -> bool {
while pos < buf.len() && buf[pos] != b'\n' {
match buf[pos] {
b' ' | b'\t' | b'\r' => pos += 1,
b'#' => return true,
_ => return false,
}
}
true
}
fn skip_doc_marker(buf: &[u8], mut pos: usize) -> usize {
pos = skip_white(buf, pos).unwrap_or(pos);
if buf.get(pos..pos + 3) == Some(b"---") && (pos + 3 == buf.len() || matches!(buf[pos + 3], b' ' | b'\t' | b'\n' | b'\r')) {
pos += 3;
}
skip_white(buf, pos).unwrap_or(pos)
}
fn read_scalar(buf: &[u8], pos: usize) -> Result<(&str, usize)> {
let start = pos;
let mut end = pos;
let mut in_flow_bracket = 0i32; let mut in_double_quote = false;
let mut in_single_quote = false;
while end < buf.len() {
let b = buf[end];
if in_double_quote {
if b == b'\\' && end + 1 < buf.len() {
end += 2;
continue;
}
if b == b'"' {
in_double_quote = false;
end += 1;
continue;
}
end += 1;
continue;
}
if in_single_quote {
if b == b'\'' {
if end + 1 < buf.len() && buf[end + 1] == b'\'' {
end += 2;
continue;
}
in_single_quote = false;
end += 1;
continue;
}
end += 1;
continue;
}
match b {
b'"' => {
in_double_quote = true;
end += 1;
}
b'\'' => {
in_single_quote = true;
end += 1;
}
b'[' | b'{' => {
in_flow_bracket += 1;
end += 1;
}
b']' | b'}' => {
break;
}
b',' => {
break;
}
b':' if in_flow_bracket == 0 && end + 1 < buf.len() && (buf[end + 1] == b' ' || buf[end + 1] == b'\t' || buf[end + 1] == b'\n') => {
break;
}
b'#' if in_flow_bracket == 0 => break,
b'\n' if in_flow_bracket == 0 => break,
_ => end += 1,
}
}
while end > start && matches!(buf[end - 1], b' ' | b'\t' | b'\r') {
end -= 1;
}
if end == start {
return Err(anyhow!("yaml scalar 为空 @{}", start));
}
std::str::from_utf8(&buf[start..end]).map(|s| (s, end - start)).map_err(|e| anyhow!("yaml scalar 含非法 UTF-8: {e}"))
}
fn scalar_to_dynamic(raw: &str) -> Dynamic {
if matches!(raw, "" | "null" | "Null" | "NULL" | "~") {
return Dynamic::Null;
}
match raw {
"true" | "True" | "TRUE" => return Dynamic::Bool(true),
"false" | "False" | "FALSE" => return Dynamic::Bool(false),
_ => {}
}
if let Ok(v) = raw.parse::<i64>() {
return Dynamic::I64(v);
}
if let Ok(v) = raw.parse::<f64>() {
if v.is_finite() {
return Dynamic::F64(v);
}
}
if raw.starts_with('"') && raw.ends_with('"') && raw.len() >= 2 {
return Dynamic::String(unescape_double_quoted(&raw[1..raw.len() - 1]).into());
}
if raw.starts_with('\'') && raw.ends_with('\'') && raw.len() >= 2 {
return Dynamic::String(unescape_single_quoted(&raw[1..raw.len() - 1]).into());
}
Dynamic::String(raw.into())
}
fn unescape_double_quoted(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut chars = s.chars();
while let Some(c) = chars.next() {
if c == '\\' {
match chars.next() {
Some('n') => out.push('\n'),
Some('t') => out.push('\t'),
Some('r') => out.push('\r'),
Some('\\') => out.push('\\'),
Some('"') => out.push('"'),
Some('0') => out.push('\0'),
Some('/') => out.push('/'),
Some('u') => {
let hex: String = chars.by_ref().take(4).collect();
if let Ok(code) = u32::from_str_radix(&hex, 16) {
if let Some(ch) = char::from_u32(code) {
out.push(ch);
}
}
}
Some(other) => {
out.push('\\');
out.push(other);
}
None => out.push('\\'),
}
} else {
out.push(c);
}
}
out
}
fn unescape_single_quoted(s: &str) -> String {
s.replace("''", "'")
}
impl FromYaml for Dynamic {
fn from_yaml(buf: &[u8]) -> Result<(Self, usize)> {
let pos = skip_doc_marker(buf, 0);
parse_node(buf, pos, 0, false)
}
}
fn parse_node(buf: &[u8], pos: usize, min_indent: usize, in_block: bool) -> Result<(Dynamic, usize)> {
let pos = skip_white(buf, pos)?;
if pos >= buf.len() {
return Err(anyhow!("yaml 文档提前结束"));
}
if buf[pos] == b'{' {
return parse_flow_map(buf, pos);
}
if buf[pos] == b'[' {
return parse_flow_seq(buf, pos);
}
let indent = line_indent(buf, pos);
if indent < min_indent {
return Err(anyhow!("yaml 缩进不足: {} < {}", indent, min_indent));
}
if buf[pos] == b'-' && (pos + 1 == buf.len() || matches!(buf[pos + 1], b' ' | b'\t' | b'\n')) {
return parse_block_seq(buf, pos, indent);
}
if let Some((key, colon)) = try_read_key(buf, pos) {
let value_pos = skip_white(buf, colon + 1)?;
return parse_block_map(buf, value_pos, indent, key);
}
if in_block {
return Err(anyhow!("yaml 期望 block 结构但遇到孤立标量 @{}", pos));
}
let (raw, consumed) = read_scalar(buf, pos)?;
Ok((scalar_to_dynamic(raw), pos + consumed))
}
fn try_read_key(buf: &[u8], pos: usize) -> Option<(SmolStr, usize)> {
let start = pos;
if buf[pos] == b'"' || buf[pos] == b'\'' {
let quote = buf[pos];
let mut p = pos + 1;
while p < buf.len() && buf[p] != quote {
if buf[p] == b'\\' && quote == b'"' && p + 1 < buf.len() {
p += 2;
continue;
}
p += 1;
}
if p >= buf.len() {
return None;
}
let key = &buf[start + 1..p];
let after = p + 1;
let after = skip_white(buf, after).ok()?;
if after >= buf.len() || buf[after] != b':' {
return None;
}
let key_str = std::str::from_utf8(key).ok()?;
return Some((SmolStr::from(if quote == b'"' { unescape_double_quoted(key_str) } else { unescape_single_quoted(key_str) }), after));
}
let mut p = pos;
while p < buf.len() {
match buf[p] {
b':' if p + 1 < buf.len() && (matches!(buf[p + 1], b' ' | b'\t' | b'\n') || p + 1 == buf.len()) => break,
b' ' | b'\t' | b'\r' | b'\n' | b'#' | b',' | b'}' | b']' => return None,
_ => p += 1,
}
}
if p >= buf.len() || buf[p] != b':' {
return None;
}
let key_bytes = &buf[start..p];
let key = std::str::from_utf8(key_bytes).ok()?.trim();
if key.is_empty() {
return None;
}
Some((SmolStr::from(key), p))
}
fn parse_block_map(buf: &[u8], mut pos: usize, parent_indent: usize, first_key: SmolStr) -> Result<(Dynamic, usize)> {
let mut map: IndexMap<SmolStr, Dynamic> = IndexMap::new();
let mut current_key = first_key;
let mut expect_value = true;
loop {
pos = skip_white(buf, pos)?;
while pos < buf.len() && line_is_blank(buf, pos) {
let mut p = pos;
while p < buf.len() && buf[p] != b'\n' {
p += 1;
}
if p < buf.len() {
p += 1;
}
pos = p;
}
if pos >= buf.len() {
break;
}
let indent = line_indent(buf, pos);
if indent < parent_indent {
break;
}
if expect_value {
if indent == parent_indent {
if let Some((next_key, colon)) = try_read_key(buf, pos) {
map.insert(current_key.clone(), Dynamic::Null);
current_key = next_key;
pos = colon + 1;
expect_value = true;
continue;
}
}
let after_value = if pos < buf.len() && buf[pos] == b'\n' {
pos + 1
} else {
skip_white(buf, pos)?
};
let on_next_line = pos < buf.len() && buf[pos] == b'\n';
if on_next_line || at_line_end(buf, after_value) {
let p = after_value;
let next_indent = if p < buf.len() { line_indent(buf, p) } else { 0 };
if next_indent <= parent_indent {
map.insert(current_key.clone(), Dynamic::Null);
expect_value = false;
pos = p;
continue;
}
if let Some((next_key, colon)) = try_read_key(buf, p) {
let value_pos = skip_white(buf, colon + 1)?;
let (value, consumed) = parse_block_map(buf, value_pos, next_indent - 1, next_key)?;
map.insert(current_key.clone(), value);
pos = consumed;
} else {
let (value, consumed) = parse_node(buf, p, next_indent, true)?;
map.insert(current_key.clone(), value);
pos = consumed;
}
} else {
let (value, consumed) = parse_node(buf, after_value, parent_indent, false)?;
map.insert(current_key.clone(), value);
pos = consumed;
}
expect_value = false;
continue;
}
if let Some((next_key, colon)) = try_read_key(buf, pos) {
current_key = next_key;
pos = colon + 1;
expect_value = true;
} else {
break;
}
}
Ok((Dynamic::Map(Arc::new(RwLock::new(map))), pos))
}
fn parse_block_seq(buf: &[u8], mut pos: usize, parent_indent: usize) -> Result<(Dynamic, usize)> {
let mut items: Vec<Dynamic> = Vec::new();
loop {
pos = skip_white(buf, pos)?;
while pos < buf.len() && line_is_blank(buf, pos) {
let mut p = pos;
while p < buf.len() && buf[p] != b'\n' {
p += 1;
}
if p < buf.len() {
p += 1;
}
pos = p;
}
if pos >= buf.len() {
break;
}
let indent = line_indent(buf, pos);
if indent < parent_indent {
break;
}
if buf[pos] != b'-' || (pos + 1 < buf.len() && !matches!(buf[pos + 1], b' ' | b'\t' | b'\n')) {
break;
}
let after_dash = pos + 1;
let after_dash = skip_white(buf, after_dash)?;
if !at_line_end(buf, after_dash) {
if let Some((key, colon)) = try_read_key(buf, after_dash) {
let after_colon = skip_white(buf, colon + 1)?;
if at_line_end(buf, after_colon) {
let mut p = after_colon;
if p < buf.len() && buf[p] == b'\n' {
p += 1;
}
let next_indent = if p < buf.len() { line_indent(buf, p) } else { 0 };
let (value, consumed) = parse_node(buf, p, next_indent.max(parent_indent + 2), true)?;
let mut map = IndexMap::new();
map.insert(key, value);
items.push(Dynamic::Map(Arc::new(RwLock::new(map))));
pos = consumed;
} else {
let (value, consumed) = parse_node(buf, after_colon, parent_indent, false)?;
let mut map = IndexMap::new();
map.insert(key.clone(), value);
let mut p = consumed;
if p < buf.len() && buf[p] != b'\n' {
p = skip_white(buf, p)?;
}
while p < buf.len() && line_is_blank(buf, p) {
let mut q = p;
while q < buf.len() && buf[q] != b'\n' {
q += 1;
}
if q < buf.len() {
q += 1;
}
p = q;
}
if p < buf.len() {
let next_indent = line_indent(buf, p);
let dash_indent = line_indent(buf, pos);
let key_start = p + next_indent;
if next_indent > dash_indent && key_start < buf.len()
&& let Some((next_key, colon)) = try_read_key(buf, key_start)
{
let value_pos = skip_white(buf, colon + 1)?;
let (rest, consumed2) = parse_block_map(buf, value_pos, next_indent - 1, next_key)?;
if let Dynamic::Map(extra) = rest {
for (k, v) in extra.read().iter() {
map.insert(k.clone(), v.clone());
}
}
p = consumed2;
}
}
items.push(Dynamic::Map(Arc::new(RwLock::new(map))));
pos = p;
}
} else {
let (value, consumed) = parse_node(buf, after_dash, parent_indent, false)?;
items.push(value);
pos = consumed;
}
continue;
}
let mut p = after_dash;
if p < buf.len() && buf[p] == b'\n' {
p += 1;
}
if p >= buf.len() {
items.push(Dynamic::Null);
break;
}
let next_indent = line_indent(buf, p);
if next_indent <= parent_indent {
items.push(Dynamic::Null);
break;
}
let (value, consumed) = parse_node(buf, p, next_indent, true)?;
items.push(value);
pos = consumed;
}
Ok((Dynamic::List(Arc::new(RwLock::new(items))), pos))
}
fn parse_flow_map(buf: &[u8], pos: usize) -> Result<(Dynamic, usize)> {
let mut p = pos + 1;
let mut map: IndexMap<SmolStr, Dynamic> = IndexMap::new();
p = skip_white(buf, p)?;
if p < buf.len() && buf[p] == b'}' {
return Ok((Dynamic::Map(Arc::new(RwLock::new(map))), p + 1));
}
loop {
p = skip_white(buf, p)?;
let (key, after) = match try_read_key(buf, p) {
Some(pair) => pair,
None => return Err(anyhow!("yaml flow mapping 缺少 key @{}", p)),
};
p = skip_white(buf, after + 1)?;
let (value, consumed) = parse_node(buf, p, 0, false)?;
map.insert(key, value);
p = consumed;
p = skip_white(buf, p)?;
if p < buf.len() && buf[p] == b',' {
p += 1;
continue;
}
if p < buf.len() && buf[p] == b'}' {
return Ok((Dynamic::Map(Arc::new(RwLock::new(map))), p + 1));
}
return Err(anyhow!("yaml flow mapping 缺少 ',' 或 '}}' @{}", p));
}
}
fn parse_flow_seq(buf: &[u8], pos: usize) -> Result<(Dynamic, usize)> {
let mut p = pos + 1;
let mut items: Vec<Dynamic> = Vec::new();
p = skip_white(buf, p)?;
if p < buf.len() && buf[p] == b']' {
return Ok((Dynamic::List(Arc::new(RwLock::new(items))), p + 1));
}
loop {
p = skip_white(buf, p)?;
let (value, consumed) = parse_node(buf, p, 0, false)?;
items.push(value);
p = consumed;
p = skip_white(buf, p)?;
if p < buf.len() && buf[p] == b',' {
p += 1;
continue;
}
if p < buf.len() && buf[p] == b']' {
return Ok((Dynamic::List(Arc::new(RwLock::new(items))), p + 1));
}
return Err(anyhow!("yaml flow sequence 缺少 ',' 或 ']' @{}", p));
}
}
impl Dynamic {
pub fn to_yaml_string(&self) -> String {
let mut buf = String::new();
self.to_yaml(&mut buf);
buf
}
pub fn from_yaml_buf(buf: &[u8]) -> Result<Self> {
let (value, _) = <Self as FromYaml>::from_yaml(buf)?;
Ok(value)
}
}
impl Dynamic {
fn map_with_entry(key: SmolStr, value: Dynamic) -> Self {
let mut map = IndexMap::new();
map.insert(key, value);
Dynamic::Map(Arc::new(RwLock::new(map)))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn round_trip(input: &str) -> String {
let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse yaml");
let mut buf = String::new();
value.to_yaml(&mut buf);
buf
}
#[test]
fn simple_scalar_round_trip() {
assert_eq!(round_trip("42"), "42");
assert_eq!(round_trip("-7"), "-7");
assert_eq!(round_trip("3.14"), "3.14");
assert_eq!(round_trip("true"), "true");
assert_eq!(round_trip("false"), "false");
assert_eq!(round_trip("null"), "null");
assert_eq!(round_trip("~"), "null");
}
#[test]
fn string_quoting_rules() {
let value = Dynamic::String("123".into());
let mut buf = String::new();
value.to_yaml(&mut buf);
assert_eq!(buf, "\"123\"");
let value = Dynamic::String("yes".into());
let mut buf = String::new();
value.to_yaml(&mut buf);
assert_eq!(buf, "\"yes\"");
let value = Dynamic::String("hello world".into());
let mut buf = String::new();
value.to_yaml(&mut buf);
assert_eq!(buf, "hello world");
let value = Dynamic::String("a: b".into());
let mut buf = String::new();
value.to_yaml(&mut buf);
assert_eq!(buf, "\"a: b\"");
let value = Dynamic::String("color #ff00ff".into());
let mut buf = String::new();
value.to_yaml(&mut buf);
assert_eq!(buf, "\"color #ff00ff\"");
}
#[test]
fn multiline_string_uses_block_scalar() {
let value = Dynamic::String("line1\nline2\nline3".into());
let mut buf = String::new();
value.to_yaml(&mut buf);
assert!(buf.starts_with("|+\n"), "should use block scalar: {buf}");
assert!(buf.contains("line1\n"));
assert!(buf.contains("line2\n"));
assert!(buf.contains("line3\n"));
}
#[test]
fn mapping_block_style() {
let mut map = IndexMap::new();
map.insert(SmolStr::from("name"), Dynamic::String("zust".into()));
map.insert(SmolStr::from("version"), Dynamic::I64(1));
map.insert(SmolStr::from("active"), Dynamic::Bool(true));
let value = Dynamic::Map(Arc::new(RwLock::new(map)));
let mut buf = String::new();
value.to_yaml(&mut buf);
assert!(buf.contains("name: zust"), "{buf}");
assert!(buf.contains("version: 1"), "{buf}");
assert!(buf.contains("active: true"), "{buf}");
}
#[test]
fn nested_mapping_and_list() {
let mut inner = IndexMap::new();
inner.insert(SmolStr::from("a"), Dynamic::I64(1));
inner.insert(SmolStr::from("b"), Dynamic::I64(2));
let mut map = IndexMap::new();
map.insert(SmolStr::from("items"), Dynamic::list(vec![Dynamic::Map(Arc::new(RwLock::new(inner)))]));
let value = Dynamic::Map(Arc::new(RwLock::new(map)));
let yaml = value.to_yaml_string();
assert!(yaml.contains("items:\n - a: 1"), "{yaml}");
}
#[test]
fn block_sequence_round_trip() {
let input = "- 1\n- 2\n- 3\n";
let (value, consumed) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
assert!(matches!(value, Dynamic::List(_)));
assert_eq!(consumed, input.len());
}
#[test]
fn block_mapping_round_trip() {
let input = "name: zust\nversion: 1\n";
let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
let map = value.get_dynamic("name").unwrap();
assert_eq!(map.as_str(), "zust");
assert_eq!(value.get_dynamic("version").and_then(|v| v.as_int()), Some(1));
}
#[test]
fn nested_block_structures() {
let input = "users:\n - name: alice\n age: 30\n - name: bob\n age: 25\n";
let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
let users = value.get_dynamic("users").unwrap();
assert!(users.is_list());
let first = users.get_idx(0).unwrap();
assert_eq!(first.get_dynamic("name").unwrap().as_str(), "alice");
assert_eq!(first.get_dynamic("age").unwrap().as_int(), Some(30));
}
#[test]
fn inline_mapping_and_sequence() {
let input = "{a: 1, b: [2, 3]}";
let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
assert_eq!(value.get_dynamic("a").unwrap().as_int(), Some(1));
let b = value.get_dynamic("b").unwrap();
assert!(b.is_list());
}
#[test]
fn comments_and_blank_lines_are_ignored() {
let input = "# header comment\nname: zust # trailing comment\n\nversion: 1\n";
let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
assert_eq!(value.get_dynamic("name").unwrap().as_str(), "zust");
assert_eq!(value.get_dynamic("version").unwrap().as_int(), Some(1));
}
#[test]
fn double_quoted_string_with_escapes() {
let input = "msg: \"hello\\nworld\"\n";
let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
assert_eq!(value.get_dynamic("msg").unwrap().as_str(), "hello\nworld");
}
#[test]
fn single_quoted_string_literal() {
let input = "msg: 'a: b'\n";
let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
assert_eq!(value.get_dynamic("msg").unwrap().as_str(), "a: b");
}
#[test]
fn quoted_round_trip_preserves_string_like_number() {
let mut map = IndexMap::new();
map.insert(SmolStr::from("id"), Dynamic::String("123".into()));
let value = Dynamic::Map(Arc::new(RwLock::new(map)));
let yaml = value.to_yaml_string();
assert!(yaml.contains("id: \"123\""), "{yaml}");
let (parsed, _) = <Dynamic as FromYaml>::from_yaml(yaml.as_bytes()).expect("parse");
let id = parsed.get_dynamic("id").unwrap();
assert_eq!(id.as_str(), "123");
assert!(!id.is_int(), "id should stay string");
}
#[test]
fn empty_collection_serialization() {
let value = Dynamic::List(Arc::new(RwLock::new(Vec::new())));
let mut buf = String::new();
value.to_yaml(&mut buf);
assert_eq!(buf, "[]");
}
}