use crate::io::traits::ISource;
use crate::parser::ParseResult;
use crate::parser::directives::DirectiveContext;
use crate::utils::{is_comment_start, is_horizontal_space, is_line_terminator};
use super::core::{parse_error_token, to_yaml_error};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum DocMarkerKind {
Start,
End,
}
#[inline]
pub(crate) fn classify_doc_marker(
ts: &crate::parser::token_stream::TokenStream,
) -> Option<DocMarkerKind> {
use crate::parser::lexer::Token;
match ts.current() {
Some(Token::DocumentStart) => Some(DocMarkerKind::Start),
Some(Token::DocumentEnd) => Some(DocMarkerKind::End),
_ => None,
}
}
#[inline]
pub(crate) fn peek_tag_after_doc_start(source: &mut dyn ISource) -> Option<String> {
let st_tag = source.save_state();
let mut tag_raw = String::new();
while let Some(ch) = source.current() {
if is_horizontal_space(ch) || is_line_terminator(ch) || is_comment_start(ch) {
break;
}
tag_raw.push(ch);
source.next();
}
source.restore_state(st_tag);
if tag_raw.is_empty() {
None
} else {
Some(tag_raw)
}
}
pub(crate) fn parse_document_markers(
source: &mut dyn ISource,
directives: &DirectiveContext,
) -> ParseResult<()> {
let has_document_marker = {
let st = source.save_state();
let ts = crate::parser::token_stream::TokenStream::new(source, directives, false)
.map_err(to_yaml_error)?;
let res = matches!(classify_doc_marker(&ts), Some(DocMarkerKind::Start));
source.restore_state(st);
res
};
if has_document_marker {
source.next();
source.next();
source.next();
while let Some(c) = source.current() {
if is_horizontal_space(c) {
source.next();
} else {
break;
}
}
if matches!(source.current(), Some(crate::constants::CHAR_EXCLAMATION)) {
if let Some(tag_raw) = peek_tag_after_doc_start(source) {
if let Err(e) = directives.validate_tag_handle_usage(&tag_raw) {
let ts_err =
crate::parser::token_stream::TokenStream::new(source, directives, false)
.map_err(to_yaml_error)?;
return Err(parse_error_token(&ts_err, &e.to_string()));
}
}
}
if matches!(source.current(), Some('&')) {
let st_anchor = source.save_state();
source.next(); while let Some(ch) = source.current() {
if is_horizontal_space(ch)
|| ch == '&'
|| ch.is_alphanumeric()
|| ch == '_'
|| ch == '-'
{
source.next();
} else {
break;
}
}
while let Some(ch) = source.current() {
if is_horizontal_space(ch) {
source.next();
} else {
break;
}
}
let mut has_mapping_colon = false;
while let Some(ch) = source.current() {
if is_line_terminator(ch) {
break;
}
if ch == ':' {
source.next();
if let Some(next_ch) = source.current() {
if is_horizontal_space(next_ch) || is_line_terminator(next_ch) {
has_mapping_colon = true;
}
} else {
has_mapping_colon = true;
}
break;
}
source.next();
}
source.restore_state(st_anchor);
if has_mapping_colon {
let ts_err =
crate::parser::token_stream::TokenStream::new(source, directives, false)
.map_err(to_yaml_error)?;
return Err(parse_error_token(
&ts_err,
"Mapping keys are not allowed on the same line as document start marker (---).",
));
}
}
let st = source.save_state();
let early_error = {
let mut err = None;
if let Ok(mut ts) =
crate::parser::token_stream::TokenStream::new(source, directives, false)
{
loop {
match ts.current() {
Some(crate::parser::lexer::Token::Indent(_)) => {
ts.next().ok();
}
_ => break,
}
}
match ts.current() {
Some(crate::parser::lexer::Token::Newline)
| Some(crate::parser::lexer::Token::Eof) => {}
Some(crate::parser::lexer::Token::Comment(_)) => {}
Some(crate::parser::lexer::Token::Tag(tag_str)) => {
if let Err(e) = directives.validate_tag_handle_usage(&tag_str) {
err = Some(parse_error_token(&ts, &e.to_string()));
}
}
Some(crate::parser::lexer::Token::Anchor(_)) => {
}
Some(crate::parser::lexer::Token::Plain(_)) => {
if let Some(crate::parser::lexer::Token::Colon) = ts.peek().ok().flatten() {
err = Some(parse_error_token(
&ts,
"Mapping keys are not allowed on the same line as document start marker (---).",
));
}
}
Some(crate::parser::lexer::Token::Colon) => {
err = Some(parse_error_token(
&ts,
"Mapping keys are not allowed on the same line as document start marker (---).",
));
}
_ => {}
}
}
err
};
source.restore_state(st);
if let Some(e) = early_error {
return Err(e);
}
if source.current().map_or(false, is_line_terminator) {
source.next();
}
}
Ok(())
}
pub(crate) fn parse_document_end_marker(
source: &mut dyn ISource,
directives: &DirectiveContext,
) -> ParseResult<bool> {
let mut consumed_end = false;
crate::utils::skip_whitespace_and_comments(source);
let has_document_end = {
let st = source.save_state();
let mut dot_count = 0;
while let Some('.') = source.current() {
dot_count += 1;
if dot_count == 3 {
break;
}
source.next();
}
let sep_ok = match source.current() {
Some(c) if is_horizontal_space(c) || is_line_terminator(c) || is_comment_start(c) => {
true
}
None => true,
_ => false,
};
let found = dot_count == 3 && sep_ok;
source.restore_state(st);
found
};
if has_document_end {
if source.current() == Some('.') {
source.next();
}
if source.current() == Some('.') {
source.next();
}
if source.current() == Some('.') {
source.next();
}
consumed_end = true;
loop {
match source.current() {
Some(c) if is_horizontal_space(c) => {
source.next();
}
Some(c) if is_comment_start(c) => {
while let Some(c2) = source.current() {
if is_line_terminator(c2) {
break;
}
source.next();
}
}
Some(c) if is_line_terminator(c) => break,
None => break,
Some(_) => {
let ts =
crate::parser::token_stream::TokenStream::new(source, directives, false)
.map_err(to_yaml_error)?;
return Err(parse_error_token(
&ts,
"Invalid content after document end marker (...)",
));
}
}
}
if source.current() == Some('\r') {
source.next();
if source.current() == Some('\n') {
source.next();
}
} else if source.current().map_or(false, is_line_terminator) {
source.next();
}
}
Ok(consumed_end)
}