mod block;
mod flow;
mod scalars;
use scalars::is_plain_scalar_kind;
#[cfg(test)]
pub(crate) use flow::has_implicit_mapping_pattern;
use crate::{
error_recovery::{ErrorBuilder, ErrorRecoveryContext, ParseContext, RecoveryStrategy},
lex::{lex, SyntaxKind},
yaml::ParsedYaml,
ParseErrorKind, PositionedParseError,
};
use rowan::GreenNodeBuilder;
pub(super) const MAX_FLOW_DEPTH: usize = 256;
pub(super) struct Parser {
pub(super) tokens: Vec<(SyntaxKind, String)>,
pub(super) current_token_index: usize,
pub(super) builder: GreenNodeBuilder<'static>,
pub(super) errors: Vec<String>,
pub(super) positioned_errors: Vec<PositionedParseError>,
pub(super) in_flow_context: bool,
pub(super) error_context: ErrorRecoveryContext,
pub(super) in_value_context: bool,
pub(super) current_line_indent: usize,
pub(super) flow_depth: usize,
pub(super) nesting_depth: usize,
}
impl Parser {
fn new(text: &str) -> Self {
let lexed = lex(text);
let mut tokens = Vec::new();
for (kind, token_text) in lexed {
tokens.push((kind, token_text.to_string()));
}
let token_count = tokens.len();
tokens.reverse();
Self {
tokens,
current_token_index: token_count,
builder: GreenNodeBuilder::new(),
errors: Vec::new(),
positioned_errors: Vec::new(),
in_flow_context: false,
error_context: ErrorRecoveryContext::new(text.to_string()),
in_value_context: false,
current_line_indent: 0,
flow_depth: 0,
nesting_depth: 0,
}
}
fn parse(mut self) -> ParsedYaml {
self.builder.start_node(SyntaxKind::ROOT.into());
if self.current() == Some(SyntaxKind::BOM) {
self.bump(); }
self.skip_ws_and_newlines();
while self.current() == Some(SyntaxKind::DIRECTIVE) {
self.parse_directive();
self.skip_ws_and_newlines();
}
if self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
self.parse_document();
self.skip_ws_and_newlines();
while self.current() == Some(SyntaxKind::DOC_START)
|| self.current() == Some(SyntaxKind::DIRECTIVE)
{
while self.current() == Some(SyntaxKind::DIRECTIVE) {
self.parse_directive();
self.skip_ws_and_newlines();
}
if self.current() == Some(SyntaxKind::DOC_START)
|| (self.current().is_some() && self.current() != Some(SyntaxKind::EOF))
{
self.parse_document();
self.skip_ws_and_newlines();
} else {
break;
}
}
}
while self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
self.builder.start_node(SyntaxKind::ERROR.into());
while self.current().is_some()
&& self.current() != Some(SyntaxKind::EOF)
&& self.current() != Some(SyntaxKind::DOC_START)
&& self.current() != Some(SyntaxKind::DIRECTIVE)
{
self.bump();
}
self.builder.finish_node();
if self.current() == Some(SyntaxKind::DOC_START)
|| self.current() == Some(SyntaxKind::DIRECTIVE)
{
while self.current() == Some(SyntaxKind::DIRECTIVE) {
self.parse_directive();
self.skip_ws_and_newlines();
}
if self.current().is_some() && self.current() != Some(SyntaxKind::EOF) {
self.parse_document();
self.skip_ws_and_newlines();
}
}
}
self.builder.finish_node();
ParsedYaml {
green_node: self.builder.finish(),
errors: self.errors,
positioned_errors: self.positioned_errors,
}
}
fn parse_document(&mut self) {
self.builder.start_node(SyntaxKind::DOCUMENT.into());
if self.current() == Some(SyntaxKind::DOC_START) {
self.bump();
self.skip_ws_and_newlines();
}
if self.current().is_some()
&& self.current() != Some(SyntaxKind::DOC_END)
&& self.current() != Some(SyntaxKind::DOC_START)
{
self.parse_value();
}
if self.current() == Some(SyntaxKind::DOC_END) {
self.bump();
self.skip_whitespace();
if self.current().is_some()
&& self.current() != Some(SyntaxKind::NEWLINE)
&& self.current() != Some(SyntaxKind::EOF)
&& self.current() != Some(SyntaxKind::DOC_START)
&& self.current() != Some(SyntaxKind::DIRECTIVE)
{
self.builder.start_node(SyntaxKind::ERROR.into());
while self.current().is_some()
&& self.current() != Some(SyntaxKind::NEWLINE)
&& self.current() != Some(SyntaxKind::EOF)
&& self.current() != Some(SyntaxKind::DOC_START)
&& self.current() != Some(SyntaxKind::DIRECTIVE)
{
self.bump();
}
self.builder.finish_node();
}
}
self.builder.finish_node();
}
pub(super) fn parse_value(&mut self) {
self.parse_value_with_base_indent(0);
}
pub(super) fn parse_value_with_base_indent(&mut self, base_indent: usize) {
if self.nesting_depth >= MAX_FLOW_DEPTH {
self.add_error(
format!("Collection nested too deeply (limit {MAX_FLOW_DEPTH})"),
ParseErrorKind::Other,
);
if self.current().is_some() {
self.bump();
}
return;
}
self.nesting_depth += 1;
match self.current() {
Some(SyntaxKind::COMMENT) => {
self.bump(); self.skip_ws_and_newlines(); self.parse_value_with_base_indent(base_indent);
}
Some(SyntaxKind::DASH) if !self.in_flow_context => {
self.parse_sequence_with_base_indent(base_indent)
}
Some(SyntaxKind::ANCHOR) => {
self.bump(); self.skip_whitespace();
self.parse_value_with_base_indent(base_indent);
}
Some(SyntaxKind::REFERENCE) => self.parse_alias(),
Some(SyntaxKind::TAG) => {
if !self.in_flow_context && !self.in_value_context && self.is_mapping_key() {
self.parse_mapping_with_base_indent(base_indent);
} else {
self.parse_tagged_value();
}
}
Some(SyntaxKind::MERGE_KEY) => {
self.parse_mapping_with_base_indent(base_indent);
}
Some(SyntaxKind::QUESTION) => {
self.parse_explicit_key_mapping();
}
Some(SyntaxKind::PIPE) => self.parse_literal_block_scalar(),
Some(SyntaxKind::GREATER) => self.parse_folded_block_scalar(),
Some(kind) if is_plain_scalar_kind(kind) => {
if !self.in_flow_context && !self.in_value_context && self.is_mapping_key() {
self.parse_mapping_with_base_indent(base_indent);
} else {
self.parse_scalar();
}
}
Some(SyntaxKind::LEFT_BRACKET) => {
if !self.in_flow_context && !self.in_value_context && self.is_complex_mapping_key()
{
self.parse_complex_key_mapping();
} else {
self.parse_flow_sequence();
}
}
Some(SyntaxKind::LEFT_BRACE) => {
if !self.in_flow_context && !self.in_value_context && self.is_complex_mapping_key()
{
self.parse_complex_key_mapping();
} else {
self.parse_flow_mapping();
}
}
Some(SyntaxKind::INDENT) => {
self.bump(); self.parse_value(); }
Some(SyntaxKind::NEWLINE) => {
self.bump(); if self.current() == Some(SyntaxKind::INDENT) {
let indent_level = self.tokens.last().map_or(0, |(_, text)| text.len());
self.bump(); self.parse_value_with_base_indent(indent_level);
} else {
self.builder.start_node(SyntaxKind::SCALAR.into());
self.builder.token(SyntaxKind::NULL.into(), "");
self.builder.finish_node();
}
}
_ => self.parse_scalar(),
}
self.nesting_depth -= 1;
}
}
impl Parser {
fn parse_directive(&mut self) {
self.builder.start_node(SyntaxKind::DIRECTIVE.into());
if self.current() == Some(SyntaxKind::DIRECTIVE) {
self.bump(); } else {
self.add_error("Expected directive".to_string(), ParseErrorKind::Other);
}
self.builder.finish_node();
}
pub(super) fn skip_whitespace(&mut self) {
self.skip_tokens(&[SyntaxKind::WHITESPACE]);
}
pub(super) fn skip_tokens(&mut self, kinds: &[SyntaxKind]) {
while let Some(current) = self.current() {
if kinds.contains(¤t) {
self.bump();
} else {
break;
}
}
}
pub(super) fn is_at_dedented_position(&self, base_indent: usize) -> bool {
if base_indent == 0 {
self.current_line_indent > 0
} else {
self.current_line_indent < base_indent
}
}
pub(super) fn skip_whitespace_only_with_dedent_check(&mut self, base_indent: usize) -> bool {
while self.current().is_some() {
match self.current() {
Some(SyntaxKind::WHITESPACE) => {
self.bump();
}
Some(SyntaxKind::NEWLINE) => {
self.bump();
match self.current() {
Some(SyntaxKind::INDENT) => {
if let Some((_, text)) = self.tokens.last() {
if text.len() < base_indent {
return true;
}
if base_indent == 0 && !text.is_empty() {
return true;
}
}
self.bump(); }
Some(SyntaxKind::COMMENT) => {
if base_indent > 0 {
return true;
}
return false;
}
Some(SyntaxKind::WHITESPACE | SyntaxKind::NEWLINE) => {
}
None => {
return false;
}
_ => {
if base_indent > 0 {
return true; }
return false;
}
}
}
Some(SyntaxKind::INDENT) => {
if let Some((_, text)) = self.tokens.last() {
if text.len() < base_indent {
return true; }
}
self.bump();
}
_ => {
return false;
}
}
}
false
}
pub(super) fn skip_ws_and_newlines(&mut self) {
self.skip_tokens(&[
SyntaxKind::WHITESPACE,
SyntaxKind::NEWLINE,
SyntaxKind::INDENT,
SyntaxKind::COMMENT,
]);
}
pub(super) fn bump(&mut self) {
if let Some((kind, text)) = self.tokens.pop() {
match kind {
SyntaxKind::INDENT => {
self.current_line_indent = text.len();
}
SyntaxKind::NEWLINE => {
self.current_line_indent = 0;
}
SyntaxKind::DASH => {
self.current_line_indent += text.len();
}
_ => {}
}
self.builder.token(kind.into(), &text);
if self.current_token_index > 0 {
self.current_token_index -= 1;
}
self.error_context.advance(text.len());
}
}
pub(super) fn current(&self) -> Option<SyntaxKind> {
self.tokens.last().map(|(kind, _)| *kind)
}
pub(super) fn current_text(&self) -> Option<&str> {
self.tokens.last().map(|(_, text)| text.as_str())
}
pub(super) fn upcoming_tokens(&self) -> impl Iterator<Item = SyntaxKind> + '_ {
let len = self.tokens.len();
(0..len.saturating_sub(1))
.rev()
.map(move |i| self.tokens[i].0)
}
pub(super) fn add_error(&mut self, message: String, kind: ParseErrorKind) {
let token_len = self.current_text().map_or(1, |s| s.len());
let positioned_error = self.error_context.create_error(message, token_len, kind);
self.errors.push(positioned_error.message.clone());
self.positioned_errors.push(positioned_error);
}
pub(super) fn add_error_and_recover(
&mut self,
message: String,
expected: SyntaxKind,
kind: ParseErrorKind,
) {
self.add_error(message, kind);
let found = self.current();
let strategy = self.error_context.suggest_recovery(expected, found);
match strategy {
RecoveryStrategy::SkipToken => {
if self.current().is_some() {
self.bump();
}
}
RecoveryStrategy::SkipToEndOfLine => {
while self.current().is_some() && self.current() != Some(SyntaxKind::NEWLINE) {
self.bump();
}
}
RecoveryStrategy::InsertToken(kind) => {
self.builder.token(kind.into(), "");
}
RecoveryStrategy::SyncToSafePoint => {
let sync_point = self
.error_context
.find_sync_point(&self.tokens, self.tokens.len() - self.current_token_index);
let tokens_to_skip = sync_point - (self.tokens.len() - self.current_token_index);
for _ in 0..tokens_to_skip {
if self.current().is_some() {
self.bump();
}
}
}
}
}
pub(super) fn create_detailed_error(
&self,
base_message: &str,
expected: &str,
found: Option<&str>,
) -> String {
let mut builder = ErrorBuilder::new(base_message);
builder = builder.expected(expected);
if let Some(found_str) = found {
builder = builder.found(found_str);
} else if let Some(token) = self.current_text() {
builder = builder.found(format!("'{token}'"));
} else {
builder = builder.found("end of input");
}
let context = match self.error_context.current_context() {
ParseContext::Mapping => "in mapping",
ParseContext::Sequence => "in sequence",
ParseContext::FlowMapping => "in flow mapping",
ParseContext::FlowSequence => "in flow sequence",
ParseContext::BlockScalar => "in block scalar",
ParseContext::QuotedString => "in quoted string",
_ => "at document level",
};
builder = builder.context(context);
let suggestion = self.get_error_suggestion(base_message, expected, found);
if let Some(suggestion_text) = suggestion {
builder = builder.suggestion(suggestion_text);
}
builder.build()
}
fn get_error_suggestion(
&self,
base_message: &str,
expected: &str,
found: Option<&str>,
) -> Option<String> {
if base_message.contains("Unterminated quoted string") {
return Some(
"Add closing quote or check for unescaped quotes within the string".to_string(),
);
}
if base_message.contains("Missing colon") || expected.contains("':'") {
return Some("Add ':' after the key, or check for proper indentation".to_string());
}
if base_message.contains("Unclosed flow sequence") {
return Some(
"Add ']' to close the array, or check for missing commas between elements"
.to_string(),
);
}
if base_message.contains("Unclosed flow mapping") {
return Some(
"Add '}' to close the object, or check for missing commas between key-value pairs"
.to_string(),
);
}
if let Some(found_text) = found {
if found_text.contains('\n') {
return Some(
"Unexpected newline - check indentation and YAML structure".to_string(),
);
}
if found_text.contains('\t') {
return Some(
"Tabs are not allowed in YAML - use spaces for indentation".to_string(),
);
}
}
None
}
}
pub(crate) fn parse(text: &str) -> ParsedYaml {
let parser = Parser::new(text);
parser.parse()
}