use granit_parser::{
input::SkipTabs, BufferedInput, Event, Input, Parser, Placement, ScanError, StrInput,
TryEventReceiver, TryLoadError,
};
fn parse_events(input: &str) -> Result<Vec<Event<'_>>, ScanError> {
Parser::new_from_str(input)
.map(|event| event.map(|(event, _)| event))
.collect()
}
fn first_error_info(input: &str) -> String {
for event in Parser::new_from_str(input) {
if let Err(error) = event {
return error.info().to_owned();
}
}
panic!("expected parser error");
}
struct RejectStreamEnd;
impl<'input> TryEventReceiver<'input> for RejectStreamEnd {
type Error = &'static str;
fn on_event(&mut self, ev: Event<'input>) -> Result<(), Self::Error> {
if matches!(ev, Event::StreamEnd) {
Err("stream end rejected")
} else {
Ok(())
}
}
}
struct AcceptAll;
impl<'input> TryEventReceiver<'input> for AcceptAll {
type Error = &'static str;
fn on_event(&mut self, _ev: Event<'input>) -> Result<(), Self::Error> {
Ok(())
}
}
#[test]
fn buffered_default_skip_ws_to_eol_consumes_blanks_and_comment() {
let mut input = BufferedInput::new(" \t # note\nx".chars());
let (consumed, result) = input.skip_ws_to_eol(SkipTabs::Yes);
assert_eq!(consumed, 10);
let skipped = result.expect("whitespace with a comment must be accepted");
assert!(skipped.found_tabs());
assert!(skipped.has_valid_yaml_ws());
assert_eq!(input.look_ch(), '\n');
}
#[test]
fn buffered_default_skip_ws_to_eol_rejects_comment_without_whitespace() {
let mut input = BufferedInput::new("#no-space".chars());
let (consumed, result) = input.skip_ws_to_eol(SkipTabs::Yes);
assert_eq!(consumed, 0);
let Err(message) = result else {
panic!("expected an error for a comment without leading whitespace");
};
assert_eq!(
message,
"comments must be separated from other tokens by whitespace"
);
assert_eq!(input.look_ch(), '#');
}
#[test]
fn buffered_default_skip_ws_to_eol_stops_at_tab_when_tabs_disallowed() {
let mut input = BufferedInput::new("\tx".chars());
let (consumed, result) = input.skip_ws_to_eol(SkipTabs::No);
assert_eq!(consumed, 0);
let skipped = result.expect("stopping at a tab is not an error");
assert!(!skipped.found_tabs());
assert!(!skipped.has_valid_yaml_ws());
assert_eq!(input.look_ch(), '\t');
}
#[test]
fn buffered_raw_read_ch_pads_after_source_is_exhausted() {
let mut input = BufferedInput::new("a".chars());
assert_eq!(input.raw_read_ch(), 'a');
assert_eq!(input.raw_read_ch(), '\0');
assert_eq!(input.raw_read_ch(), '\0');
}
#[test]
fn buffered_raw_read_non_breakz_ch_uses_buffered_front() {
let mut input = BufferedInput::new("a\nb".chars());
input.lookahead(2);
assert_eq!(input.raw_read_non_breakz_ch(), Some('a'));
assert_eq!(input.raw_read_non_breakz_ch(), None);
assert_eq!(input.peek(), '\n');
}
#[test]
fn str_input_skip_ws_to_eol_blanks_stops_before_tab_when_tabs_disallowed() {
let mut input = StrInput::new(" \tfoo");
let (consumed, skipped) = input.skip_ws_to_eol_blanks(SkipTabs::No);
assert_eq!(consumed, 2);
assert!(!skipped.found_tabs());
assert!(skipped.has_valid_yaml_ws());
assert_eq!(input.look_ch(), '\t');
}
#[test]
fn stray_flow_entry_in_block_sequence_reports_block_sequence_error() {
assert_eq!(
first_error_info("- ,\n"),
"unexpected EOF while parsing a block sequence"
);
}
#[test]
fn stray_flow_entry_in_block_mapping_value_reports_block_mapping_error() {
assert_eq!(
first_error_info("a: ,\n"),
"unexpected EOF while parsing a block mapping"
);
}
#[test]
fn comment_after_value_in_flow_sequence_explicit_pair_is_emitted() {
let events = parse_events("[? a : # note\n b]\n").unwrap();
let comment_pos = events
.iter()
.position(|event| matches!(event, Event::Comment(text, _) if text == " note"))
.expect("expected the inline comment event");
assert!(matches!(
events[comment_pos - 2],
Event::MappingStart(granit_parser::StructureStyle::Flow, 0, None)
));
assert!(matches!(
events[comment_pos - 1],
Event::Scalar(ref value, ..) if value == "a"
));
assert!(matches!(
events[comment_pos + 1],
Event::Scalar(ref value, ..) if value == "b"
));
assert!(matches!(
events[comment_pos],
Event::Comment(_, Placement::Right)
));
}
#[test]
fn try_load_returns_error_buffered_by_peek() {
let mut parser = Parser::new_from_str("a: *missing\n");
let buffered_error = loop {
match parser.peek() {
Some(Ok(_)) => {
parser.next_event().unwrap().unwrap();
}
Some(Err(error)) => break error,
None => panic!("expected an unknown alias error"),
}
};
assert_eq!(
buffered_error.info(),
"while parsing node, found unknown anchor"
);
let mut receiver = AcceptAll;
let err = parser.try_load(&mut receiver, true).unwrap_err();
assert_eq!(err, TryLoadError::Scan(buffered_error));
assert!(parser.next_event().is_none());
}
#[test]
fn next_event_after_receiver_error_on_stream_end_returns_stream_end() {
let mut parser = Parser::new_from_str("foo\n");
let mut receiver = RejectStreamEnd;
let err = parser.try_load(&mut receiver, true).unwrap_err();
assert_eq!(err, TryLoadError::Receiver("stream end rejected"));
let (event, _) = parser.next_event().expect("stream end event").unwrap();
assert_eq!(event, Event::StreamEnd);
assert!(parser.next_event().is_none());
}
#[test]
fn next_event_after_receiver_error_on_stream_end_with_comments_reports_eof() {
let mut parser = Parser::new_from_str("# hello\nfoo\n");
let mut receiver = RejectStreamEnd;
let err = parser.try_load(&mut receiver, true).unwrap_err();
assert_eq!(err, TryLoadError::Receiver("stream end rejected"));
let error = parser
.next_event()
.expect("expected an EOF error")
.unwrap_err();
assert_eq!(error.info(), "unexpected eof");
assert!(parser.next_event().is_none());
}