pub mod flow;
pub mod mapping;
pub mod sequence;
use crate::ast::Yaml;
use crate::error::{ErrorKind, Result, YamlError};
use crate::parser::flow::{FlowCursor, parse_flow_value};
use crate::parser::mapping::parse_block_mapping;
use crate::parser::sequence::{
parse_block_sequence, parse_scalar_or_quoted, split_mapping_key_val,
};
use crate::scanner::Scanner;
pub fn parse_node<'a>(scanner: &mut Scanner<'a>, min_indent: usize) -> Result<Yaml<'a>> {
let line = match scanner.peek() {
Some(l) => *l,
None => return Ok(Yaml::Scalar("")),
};
if line.is_document_separator() {
return Ok(Yaml::Scalar(""));
}
if line.indent < min_indent {
let pos = line.start_position();
return Err(YamlError::new(
ErrorKind::InvalidIndentation {
expected: min_indent,
found: line.indent,
},
pos,
));
}
let current_indent = line.indent;
if line.is_sequence_item() {
return parse_block_sequence(scanner, current_indent);
}
if line.content.starts_with('[') || line.content.starts_with('{') {
let line_info = scanner.next_line().expect("line exists");
let mut cursor = FlowCursor::new(line_info.content, line_info.start_position());
let val = parse_flow_value(&mut cursor)?;
return Ok(val);
}
if split_mapping_key_val(line.content).is_some() {
return parse_block_mapping(scanner, current_indent);
}
let line_info = scanner.next_line().expect("line exists");
Ok(parse_scalar_or_quoted(line_info.content))
}
pub fn parse<'a>(input: &'a str) -> Result<Yaml<'a>> {
let mut scanner = Scanner::new(input)?;
if scanner.is_empty() {
return Ok(Yaml::Scalar(""));
}
if let Some(first_line) = scanner.peek()
&& first_line.content == "---"
{
scanner.next_line();
}
if scanner.is_empty() {
return Ok(Yaml::Scalar(""));
}
let root_node = parse_node(&mut scanner, 0)?;
if let Some(next_line) = scanner.peek()
&& next_line.is_document_separator()
{
scanner.next_line();
}
Ok(root_node)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_mapping() {
let input = "
name: yami
version: 1.0
fast: true
";
let doc = parse(input).expect("should parse simple mapping");
assert_eq!(doc.get("name"), Some(&Yaml::Scalar("yami")));
assert_eq!(doc["version"], Yaml::Scalar("1.0"));
assert!(doc["fast"].to_bool().unwrap());
}
#[test]
fn test_parse_simple_sequence() {
let input = "
- rust
- zero-copy
- fast
";
let doc = parse(input).expect("should parse sequence");
assert_eq!(
doc,
Yaml::Sequence(vec![
Yaml::Scalar("rust"),
Yaml::Scalar("zero-copy"),
Yaml::Scalar("fast"),
])
);
}
#[test]
fn test_parse_nested_mapping_and_sequence() {
let input = "
database:
host: 127.0.0.1
ports:
- 5432
- 5433
";
let doc = parse(input).expect("should parse nested struct");
assert_eq!(doc["database"]["host"], Yaml::Scalar("127.0.0.1"));
assert_eq!(
doc["database"]["ports"],
Yaml::Sequence(vec![Yaml::Scalar("5432"), Yaml::Scalar("5433")])
);
}
#[test]
fn test_parse_sequence_of_mappings() {
let input = "
users:
- id: 1
name: Alice
- id: 2
name: Bob
";
let doc = parse(input).expect("should parse sequence of mappings");
let users = doc["users"].as_sequence().expect("users sequence");
assert_eq!(users.len(), 2);
assert_eq!(users[0]["name"], Yaml::Scalar("Alice"));
assert_eq!(users[1]["name"], Yaml::Scalar("Bob"));
}
#[test]
fn test_parse_mixed_block_and_flow() {
let input = "
config:
tags: [fast, safe, zero-copy]
options: { debug: false, level: 3 }
";
let doc = parse(input).expect("should parse mixed structures");
assert_eq!(
doc["config"]["tags"],
Yaml::Sequence(vec![
Yaml::Scalar("fast"),
Yaml::Scalar("safe"),
Yaml::Scalar("zero-copy"),
])
);
assert!(!doc["config"]["options"]["debug"].to_bool().unwrap());
assert_eq!(doc["config"]["options"]["level"].to_i64().unwrap(), 3);
}
#[test]
fn test_parse_duplicate_key_error() {
let input = "
key: first
key: duplicate
";
let err = parse(input).expect_err("should reject duplicate key");
assert!(matches!(err.kind, ErrorKind::DuplicateKey(_)));
}
#[test]
fn test_parse_empty_document_and_comments() {
let input = "# Only comments\n# Second comment\n";
let doc = parse(input).expect("should parse empty comment document");
assert_eq!(doc, Yaml::Scalar(""));
}
}