use std::borrow::Cow;
use crate::{
Parser, Result, Value,
patterns::resolve_scalar,
value::apply_tag,
};
impl<'a> Parser<'a> {
pub(super) fn parse_flow_seq(&mut self) -> Result<Value<'a>> {
self.advance();
let mut items = Vec::new();
loop {
self.skip_flow_whitespace();
match self.peek() {
Some(b']') => {
self.advance();
break;
}
None => return Err(self.err("unterminated flow sequence")),
_ => {}
}
items.push(self.parse_flow_node()?);
self.skip_flow_whitespace();
match self.peek() {
Some(b',') => {
self.advance();
}
Some(b']') => {
self.advance();
break;
}
None => return Err(self.err("unterminated flow sequence")),
Some(_) => return Err(self.err("expected ',' or ']' in flow sequence")),
}
}
Ok(Value::Seq(items))
}
pub(super) fn parse_flow_map(&mut self) -> Result<Value<'a>> {
self.advance(); let mut pairs = Vec::new();
loop {
self.skip_flow_whitespace();
match self.peek() {
Some(b'}') => {
self.advance();
break;
}
None => return Err(self.err("unterminated flow map")),
_ => {}
}
let key = self.parse_flow_node()?;
self.skip_flow_whitespace();
let value = if self.peek() == Some(b':') {
self.advance();
self.skip_flow_whitespace();
match self.peek() {
Some(b',' | b'}') | None => Value::Null,
_ => self.parse_flow_node()?,
}
} else {
Value::Null };
pairs.push((key, value));
self.skip_flow_whitespace();
match self.peek() {
Some(b',') => self.advance(),
Some(b'}') => {
self.advance();
break;
}
None => return Err(self.err("unterminated flow map")),
Some(_) => return Err(self.err("expected ',' or '}' in flow map")),
}
}
Ok(Value::Map(pairs))
}
fn parse_flow_node(&mut self) -> Result<Value<'a>> {
let mut tag: Option<Cow<'a, str>> = None;
let mut anchor: Option<&'a str> = None;
loop {
let mut consumed = false;
if tag.is_none()
&& let Some(t) = self.try_consume_tag()? {
tag = Some(t);
consumed = true;
}
if anchor.is_none()
&& let Some(a) = self.try_consume_anchor()?
{
anchor = Some(a);
consumed = true;
}
if !consumed {
break;
}
self.skip_flow_whitespace();
}
let value = match self.peek() {
Some(b'[') => self.parse_flow_seq()?,
Some(b'{') => self.parse_flow_map()?,
Some(b'"') => Value::String(self.parse_double_quoted(self.col.saturating_sub(1))?),
Some(b'\'') => Value::String(self.parse_single_quoted(self.col.saturating_sub(1))?),
Some(b'*') => self.parse_alias()?,
Some(b',' | b']' | b'}') | None => Value::Null,
_ => {
let s = self.parse_plain_scalar(true);
resolve_scalar(s)
}
};
let value = match tag {
Some(t) => apply_tag(t, value),
None => value,
};
if let Some(name) = anchor {
self.anchors.insert(name, value.clone());
}
Ok(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(src: &str) -> Value<'_> {
Parser::new(src).parse_node(0).unwrap()
}
fn as_seq(v: Value<'_>) -> Vec<Value<'_>> {
match v {
Value::Seq(items) => items,
other => panic!("expected Seq, got {other:?}"),
}
}
fn as_map(v: Value<'_>) -> Vec<(Value<'_>, Value<'_>)> {
match v {
Value::Map(pairs) => pairs,
other => panic!("expected Map, got {other:?}"),
}
}
#[test]
fn flow_seq_empty() {
assert!(as_seq(parse("[]\n")).is_empty());
}
#[test]
fn flow_seq_simple_strings() {
let items = as_seq(parse("[a, b, c]\n"));
let strs: Vec<&str> = items.iter().map(|v| match v {
Value::String(s) => s.as_ref(),
_ => panic!(),
}).collect();
assert_eq!(strs, vec!["a", "b", "c"]);
}
#[test]
fn flow_seq_ints() {
let items = as_seq(parse("[1, 2, 3]\n"));
assert!(matches!(&items[0], Value::UInt(1)));
assert!(matches!(&items[1], Value::UInt(2)));
assert!(matches!(&items[2], Value::UInt(3)));
}
#[test]
fn flow_seq_trailing_comma() {
assert_eq!(as_seq(parse("[a, b,]\n")).len(), 2);
}
#[test]
fn flow_seq_quoted_items() {
let items = as_seq(parse("[\"a b\", 'c d']\n"));
assert!(matches!(&items[0], Value::String(s) if s == "a b"));
assert!(matches!(&items[1], Value::String(s) if s == "c d"));
}
#[test]
fn flow_seq_nested() {
let items = as_seq(parse("[[1, 2], [3]]\n"));
let inner0 = match &items[0] {
Value::Seq(s) => s,
_ => panic!(),
};
assert_eq!(inner0.len(), 2);
let inner1 = match &items[1] {
Value::Seq(s) => s,
_ => panic!(),
};
assert_eq!(inner1.len(), 1);
}
#[test]
fn flow_seq_multiline() {
let items = as_seq(parse("[\n a,\n b,\n]\n"));
assert_eq!(items.len(), 2);
}
#[test]
fn flow_seq_unterminated_errors() {
assert!(Parser::new("[a, b\n").parse_node(0).is_err());
}
#[test]
fn flow_seq_with_comment() {
let items = as_seq(parse("[a, # ignored\n b]\n"));
assert_eq!(items.len(), 2);
assert!(matches!(&items[0], Value::String(s) if s == "a"));
assert!(matches!(&items[1], Value::String(s) if s == "b"));
}
#[test]
fn flow_seq_with_alias() {
let pairs = as_map(parse("defs: &x 7\nitems: [*x, *x]\n"));
let items = match &pairs[1].1 {
Value::Seq(s) => s,
_ => panic!(),
};
assert!(matches!(&items[0], Value::UInt(7)));
assert!(matches!(&items[1], Value::UInt(7)));
}
#[test]
fn flow_map_empty() {
assert!(as_map(parse("{}\n")).is_empty());
}
#[test]
fn flow_map_simple() {
let pairs = as_map(parse("{a: 1, b: 2}\n"));
assert!(matches!(&pairs[0].0, Value::String(s) if s == "a"));
assert!(matches!(&pairs[0].1, Value::UInt(1)));
assert!(matches!(&pairs[1].0, Value::String(s) if s == "b"));
assert!(matches!(&pairs[1].1, Value::UInt(2)));
}
#[test]
fn flow_map_quoted_keys() {
let pairs = as_map(parse("{\"k 1\": 1}\n"));
assert!(matches!(&pairs[0].0, Value::String(s) if s == "k 1"));
assert!(matches!(&pairs[0].1, Value::UInt(1)));
}
#[test]
fn flow_map_json_style_quoted_key() {
let pairs = as_map(parse("{\"a\":1}\n"));
assert!(matches!(&pairs[0].0, Value::String(s) if s == "a"));
assert!(matches!(&pairs[0].1, Value::UInt(1)));
}
#[test]
fn flow_map_colon_no_space() {
let pairs = as_map(parse("{a:1, b:2}\n"));
assert!(matches!(&pairs[0].0, Value::String(s) if s == "a"));
assert!(matches!(&pairs[0].1, Value::UInt(1)));
assert!(matches!(&pairs[1].0, Value::String(s) if s == "b"));
assert!(matches!(&pairs[1].1, Value::UInt(2)));
}
#[test]
fn flow_map_implicit_null_value() {
let pairs = as_map(parse("{a:, b: 2}\n"));
assert!(matches!(&pairs[0].1, Value::Null));
assert!(matches!(&pairs[1].1, Value::UInt(2)));
}
#[test]
fn flow_pair_shorthand() {
let pairs = as_map(parse("{a, b}\n"));
assert_eq!(pairs.len(), 2);
assert!(matches!(&pairs[0].1, Value::Null));
assert!(matches!(&pairs[1].1, Value::Null));
}
#[test]
fn flow_map_multiline() {
let pairs = as_map(parse("{\n a: 1,\n b: 2,\n}\n"));
assert_eq!(pairs.len(), 2);
}
#[test]
fn flow_map_with_comment() {
let pairs = as_map(parse("{a: 1, # mid-map\n b: 2}\n"));
assert_eq!(pairs.len(), 2);
}
#[test]
fn flow_map_unterminated_errors() {
assert!(Parser::new("{a: 1\n").parse_node(0).is_err());
}
#[test]
fn flow_map_nested_in_seq() {
let items = as_seq(parse("[{a: 1}, {b: 2}]\n"));
assert_eq!(items.len(), 2);
let first = match &items[0] {
Value::Map(p) => p,
_ => panic!(),
};
assert_eq!(first.len(), 1);
}
#[test]
fn flow_seq_as_block_map_value() {
let pairs = as_map(parse("items: [a, b]\nname: x\n"));
assert_eq!(pairs.len(), 2);
let items = match &pairs[0].1 {
Value::Seq(s) => s,
_ => panic!(),
};
assert_eq!(items.len(), 2);
assert!(matches!(&pairs[1].1, Value::String(s) if s == "x"));
}
}