use crate::io::traits::ISource;
use crate::parser::ParseResult;
use crate::parser::directives::DirectiveContext;
use crate::parser::token_stream::TokenStream;
use crate::parser::utils::context::ParsingContext;
use crate::utils::{is_comment_start, is_horizontal_space, is_line_terminator};
use super::core::{parse_error_token, to_yaml_error};
pub(crate) fn validate_indentation_and_whitespace(
source: &mut dyn ISource,
directives: &DirectiveContext,
ctx: &ParsingContext,
) -> ParseResult<()> {
let state = source.save_state();
let stream = crate::parser::token_stream::TokenStream::new(source, directives, false)
.map_err(to_yaml_error)?;
let result = crate::parser::utils::indentation::validate_indentation_tokens(&stream, ctx);
source.restore_state(state);
result
}
#[allow(dead_code)]
pub(crate) fn validate_no_tab_indentation_tokens(
stream: &TokenStream,
ctx: &ParsingContext,
) -> ParseResult<()> {
crate::parser::utils::indentation::validate_indentation_tokens(stream, ctx)
}
pub(crate) fn validate_trailing_content_after_document_end(
stream: &mut TokenStream,
) -> ParseResult<()> {
loop {
match stream.source_mut().current() {
Some(c) if is_horizontal_space(c) => {
stream.source_mut().next();
}
Some(c) if is_comment_start(c) => {
while let Some(c2) = stream.source_mut().current() {
if is_line_terminator(c2) {
break;
}
stream.source_mut().next();
}
break;
}
Some(c) if is_line_terminator(c) => break,
None => break,
Some(c) => {
return Err(parse_error_token(
stream,
&format!("Invalid content '{}' after document end marker (...)", c),
));
}
}
}
Ok(())
}
#[allow(dead_code)]
pub(crate) fn validate_comment_spacing_token(stream: &TokenStream) -> ParseResult<()> {
use crate::parser::lexer::Token;
if let Some(Token::Comment(_)) = stream.current() {
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::io::sources::buffer::Buffer;
use crate::parser::utils::context::{CollectionType, ParsingContext};
#[test]
fn test_validate_indentation_block_context_with_tab() {
let mut source = Buffer::new(b"\t content");
let mut ctx = ParsingContext::new(0);
ctx.mark_newline_consumed();
let directives = crate::parser::directives::DirectiveContext::new();
let ts_result = TokenStream::new(&mut source, &directives, false);
assert!(
ts_result.is_err(),
"TokenStream should error on tabs in block context"
);
if let Err(ref err) = ts_result {
let err_str = err.to_string();
assert!(
err_str.contains("Tabs") && err_str.contains("not allowed"),
"Error: {}",
err_str
);
return;
}
}
#[test]
fn test_validate_indentation_block_context_no_tab() {
let mut source = Buffer::new(b" content");
let mut ctx = ParsingContext::new(0);
ctx.mark_newline_consumed();
let directives = crate::parser::directives::DirectiveContext::new();
let result = crate::parser::utils::indentation::validate_indentation_tokens(
&TokenStream::new(&mut source, &directives, false)
.expect("TokenStream creation failed"),
&ctx,
);
assert!(result.is_ok());
}
#[test]
fn test_validate_indentation_flow_context_with_tab() {
let mut source = Buffer::new(b"\t content");
let ctx = ParsingContext::new(0).child_flow_context(CollectionType::FlowSequence);
let directives = crate::parser::directives::DirectiveContext::new();
let ts_result = TokenStream::new(&mut source, &directives, true);
assert!(
ts_result.is_ok(),
"TokenStream should allow tabs in flow context"
);
if ts_result.is_err() {
panic!("TokenStream should allow tabs in flow context");
}
let stream = ts_result.unwrap();
let result = crate::parser::utils::indentation::validate_indentation_tokens(&stream, &ctx);
assert!(result.is_ok(), "Tabs should be allowed in flow context");
}
#[test]
fn test_validate_indentation_not_after_newline() {
let mut source = Buffer::new(b"\tcontent");
let ctx = ParsingContext::new(0);
let directives = crate::parser::directives::DirectiveContext::new();
let ts_result = TokenStream::new(&mut source, &directives, true);
assert!(
ts_result.is_ok(),
"TokenStream should allow tabs not at indentation position"
);
if ts_result.is_err() {
panic!("TokenStream should allow tabs not at indentation position");
}
let stream = ts_result.unwrap();
let result = crate::parser::utils::indentation::validate_indentation_tokens(&stream, &ctx);
assert!(result.is_ok(), "Tabs OK when not at indentation position");
}
#[test]
fn test_validate_indentation_blank_line() {
let mut source = Buffer::new(b"\t\n");
let mut ctx = ParsingContext::new(0);
ctx.mark_newline_consumed();
let directives = crate::parser::directives::DirectiveContext::new();
let ts_result = TokenStream::new(&mut source, &directives, false);
assert!(
ts_result.is_ok(),
"TokenStream should allow tab-only lines (blank lines) in block context"
);
}
#[test]
fn test_validate_indentation_actual_blank_line() {
let mut source = Buffer::new(b"\n");
let mut ctx = ParsingContext::new(0);
ctx.mark_newline_consumed();
let directives = crate::parser::directives::DirectiveContext::new();
let result = crate::parser::utils::indentation::validate_indentation_tokens(
&TokenStream::new(&mut source, &directives, false)
.expect("TokenStream creation failed"),
&ctx,
);
assert!(result.is_ok(), "Blank lines without tabs should be OK");
}
#[test]
fn test_validate_indentation_spaces_then_tab() {
let mut source = Buffer::new(b" \tcontent");
let mut ctx = ParsingContext::new(0);
ctx.mark_newline_consumed();
let directives = crate::parser::directives::DirectiveContext::new();
let ts_result = TokenStream::new(&mut source, &directives, false);
assert!(
ts_result.is_ok(),
"TokenStream should allow tabs after spaces for block scalar content"
);
}
#[test]
fn test_validate_no_tab_indentation_backward_compat() {
let mut source = Buffer::new(b"\tcontent");
let directives = crate::parser::directives::DirectiveContext::new();
let ts_result = TokenStream::new(&mut source, &directives, false);
assert!(
ts_result.is_err(),
"TokenStream should error on tabs in block context"
);
if let Err(ref err) = ts_result {
let err_str = err.to_string();
assert!(
err_str.contains("Tabs") && err_str.contains("not allowed"),
"Error: {}",
err_str
);
return;
}
}
#[test]
fn test_skip_whitespace_with_context_block() {
let mut source = Buffer::new(b" content");
let mut ctx = ParsingContext::new(0);
ctx.mark_newline_consumed();
let directives = crate::parser::directives::DirectiveContext::new();
{
let mut stream = TokenStream::new(&mut source, &directives, false)
.expect("TokenStream creation failed");
let result = stream.skip_whitespace_and_comments();
assert!(
result.is_ok(),
"Whitespace skipping in block context should succeed"
);
use crate::parser::lexer::Token;
match stream.current() {
Some(Token::Plain(s)) => {
assert!(
s.starts_with('c'),
"TokenStream should be at plain scalar starting with 'c'"
);
}
other => panic!(
"TokenStream not at expected plain scalar after whitespace: {:?}",
other
),
}
}
}
#[test]
fn test_skip_whitespace_with_context_tab_error() {
let mut source = Buffer::new(b"\tcontent");
let mut ctx = ParsingContext::new(0);
ctx.mark_newline_consumed();
let directives = crate::parser::directives::DirectiveContext::new();
let ts_result = TokenStream::new(&mut source, &directives, false);
assert!(
ts_result.is_err(),
"TokenStream should error on tabs in block context"
);
if let Err(ref err) = ts_result {
let err_str = err.to_string();
assert!(
err_str.contains("Tabs") && err_str.contains("not allowed"),
"Error: {}",
err_str
);
return;
}
}
#[test]
fn test_skip_whitespace_with_context_flow() {
let mut source = Buffer::new(b"\tcontent");
let directives = crate::parser::directives::DirectiveContext::new();
let ts_result = TokenStream::new(&mut source, &directives, true);
assert!(
ts_result.is_ok(),
"TokenStream should allow tabs in flow context"
);
if ts_result.is_err() {
panic!("TokenStream should allow tabs in flow context");
}
let mut stream = ts_result.unwrap();
let result = stream.skip_whitespace_and_comments();
assert!(result.is_ok(), "Tabs should be allowed in flow context");
}
}