use granit_parser::{
input::{is_breakz, BorrowedInput, Input},
Event, Parser, ScalarStyle, ScanError, StructureStyle,
};
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");
}
fn scalars_of(events: &[Event<'_>]) -> Vec<(String, ScalarStyle)> {
events
.iter()
.filter_map(|event| {
if let Event::Scalar(value, style, ..) = event {
Some((value.to_string(), *style))
} else {
None
}
})
.collect()
}
const WINDOW: usize = 8;
struct WindowedInput {
chars: Vec<char>,
pos: usize,
window: usize,
}
impl WindowedInput {
fn new(source: &str) -> Self {
Self {
chars: source.chars().collect(),
pos: 0,
window: 0,
}
}
fn consume_one(&mut self) {
if self.pos < self.chars.len() {
self.pos += 1;
}
self.window = self.window.saturating_sub(1);
}
}
impl Input for WindowedInput {
fn lookahead(&mut self, count: usize) {
self.window = self.window.max(count.min(WINDOW));
}
fn buflen(&self) -> usize {
self.window
}
fn bufmaxlen(&self) -> usize {
WINDOW
}
fn raw_read_ch(&mut self) -> char {
let c = self.chars.get(self.pos).copied().unwrap_or('\0');
self.consume_one();
c
}
fn raw_read_non_breakz_ch(&mut self) -> Option<char> {
let c = self.chars.get(self.pos).copied()?;
if is_breakz(c) {
None
} else {
self.consume_one();
Some(c)
}
}
fn skip(&mut self) {
self.consume_one();
}
fn skip_n(&mut self, count: usize) {
for _ in 0..count {
self.consume_one();
}
}
fn peek(&self) -> char {
self.chars.get(self.pos).copied().unwrap_or('\0')
}
fn peek_nth(&self, n: usize) -> char {
self.chars.get(self.pos + n).copied().unwrap_or('\0')
}
}
impl BorrowedInput<'static> for WindowedInput {
fn slice_borrowed(&self, _start: usize, _end: usize) -> Option<&'static str> {
None
}
}
struct SliceableStreamInput {
source: String,
pos: usize,
window: usize,
provide_slices: bool,
}
impl SliceableStreamInput {
fn new(source: &str, provide_slices: bool) -> Self {
Self {
source: source.to_owned(),
pos: 0,
window: 0,
provide_slices,
}
}
fn rest(&self) -> &str {
&self.source[self.pos..]
}
}
impl Input for SliceableStreamInput {
fn lookahead(&mut self, count: usize) {
self.window = self.window.max(count);
}
fn buflen(&self) -> usize {
self.window
}
fn bufmaxlen(&self) -> usize {
128
}
fn raw_read_ch(&mut self) -> char {
match self.rest().chars().next() {
Some(c) => {
self.pos += c.len_utf8();
c
}
None => '\0',
}
}
fn raw_read_non_breakz_ch(&mut self) -> Option<char> {
let c = self.rest().chars().next()?;
if is_breakz(c) {
None
} else {
self.pos += c.len_utf8();
Some(c)
}
}
fn skip(&mut self) {
if let Some(c) = self.rest().chars().next() {
self.pos += c.len_utf8();
}
}
fn skip_n(&mut self, count: usize) {
for _ in 0..count {
self.skip();
}
}
fn peek(&self) -> char {
self.rest().chars().next().unwrap_or('\0')
}
fn peek_nth(&self, n: usize) -> char {
self.rest().chars().nth(n).unwrap_or('\0')
}
fn byte_offset(&self) -> Option<usize> {
Some(self.pos)
}
fn slice_bytes(&self, start: usize, end: usize) -> Option<&str> {
if self.provide_slices {
self.source.get(start..end)
} else {
None
}
}
}
impl BorrowedInput<'static> for SliceableStreamInput {
fn slice_borrowed(&self, _start: usize, _end: usize) -> Option<&'static str> {
None
}
}
#[test]
fn block_scalar_line_longer_than_lookahead_window_is_read_raw() {
let events: Result<Vec<Event<'static>>, ScanError> = Parser::new(WindowedInput::new(
"|\n abcdefghijklmnopqrstuvwxyz 0123456789\n",
))
.map(|event| event.map(|(event, _)| event))
.collect();
let events = events.expect("valid literal scalar must parse");
assert_eq!(
scalars_of(&events),
vec![(
"abcdefghijklmnopqrstuvwxyz 0123456789\n".to_owned(),
ScalarStyle::Literal
)]
);
}
#[test]
fn folded_scalar_long_lines_with_windowed_input_fold_to_spaces() {
let events: Result<Vec<Event<'static>>, ScanError> = Parser::new(WindowedInput::new(
">\n the quick brown fox jumps over\n the lazy dog and runs away\n",
))
.map(|event| event.map(|(event, _)| event))
.collect();
let events = events.expect("valid folded scalar must parse");
assert_eq!(
scalars_of(&events),
vec![(
"the quick brown fox jumps over the lazy dog and runs away\n".to_owned(),
ScalarStyle::Folded
)]
);
}
#[test]
fn deeply_indented_block_scalar_with_buffered_input() {
let indent = " ".repeat(16);
let yaml = format!("k:\n{indent}|\n{indent}aaaa\n\n{indent}bbbb\n");
let events: Result<Vec<Event<'static>>, ScanError> =
Parser::new_from_iter(yaml.chars().collect::<Vec<char>>().into_iter())
.map(|event| event.map(|(event, _)| event))
.collect();
let events = events.expect("valid literal scalar must parse");
assert_eq!(
scalars_of(&events),
vec![
("k".to_owned(), ScalarStyle::Plain),
("aaaa\n\nbbbb\n".to_owned(), ScalarStyle::Literal),
]
);
}
#[test]
fn deeply_indented_block_scalar_with_windowed_input() {
let indent = " ".repeat(10);
let yaml = format!("k:\n{indent}|\n{indent}aaaa\n\n{indent}bbbb\n");
let events: Result<Vec<Event<'static>>, ScanError> = Parser::new(WindowedInput::new(&yaml))
.map(|event| event.map(|(event, _)| event))
.collect();
let events = events.expect("valid literal scalar must parse");
assert_eq!(
scalars_of(&events),
vec![
("k".to_owned(), ScalarStyle::Plain),
("aaaa\n\nbbbb\n".to_owned(), ScalarStyle::Literal),
]
);
}
#[test]
fn double_quoted_trailing_blank_before_break_folds_to_space() {
let events = parse_events("\"a \nb\"\n").unwrap();
assert_eq!(
scalars_of(&events),
vec![("a b".to_owned(), ScalarStyle::DoubleQuoted)]
);
}
#[test]
fn single_quoted_trailing_blanks_before_break_fold_to_space() {
let events = parse_events("'a \n b'\n").unwrap();
assert_eq!(
scalars_of(&events),
vec![("a b".to_owned(), ScalarStyle::SingleQuoted)]
);
}
#[test]
fn quoted_scalar_from_offsets_without_borrowing_is_copied() {
let events: Result<Vec<Event<'static>>, ScanError> =
Parser::new(SliceableStreamInput::new("\"hello world\"\n", true))
.map(|event| event.map(|(event, _)| event))
.collect();
let events = events.expect("valid double quoted scalar must parse");
assert_eq!(
scalars_of(&events),
vec![("hello world".to_owned(), ScalarStyle::DoubleQuoted)]
);
}
#[test]
fn quoted_scalar_from_offsets_without_slices_is_internal_error() {
let mut parser = Parser::new(SliceableStreamInput::new("\"hello world\"\n", false));
let error = parser.find_map(Result::err).expect("expected parser error");
assert_eq!(
error.info(),
"internal error: input advertised offsets but did not provide a slice"
);
}
#[test]
fn explicit_key_after_flow_sequence_end_is_rejected() {
assert_eq!(
first_error_info("[a] ? b\n"),
"mapping keys are not allowed in this context"
);
}
#[test]
fn flow_entry_after_required_simple_key_is_rejected() {
assert_eq!(
first_error_info("x: y\n[a], b\n"),
"simple key expected ':'"
);
}
#[test]
fn empty_flow_sequence_key_in_existing_block_mapping() {
let events = parse_events("x: y\n[]: b\n").unwrap();
assert_eq!(
events,
vec![
Event::StreamStart,
Event::DocumentStart(false, None),
Event::MappingStart(StructureStyle::Block, 0, None),
Event::Scalar("x".into(), ScalarStyle::Plain, 0, None),
Event::Scalar("y".into(), ScalarStyle::Plain, 0, None),
Event::SequenceStart(StructureStyle::Flow, 0, None),
Event::SequenceEnd,
Event::Scalar("b".into(), ScalarStyle::Plain, 0, None),
Event::MappingEnd,
Event::DocumentEnd,
Event::StreamEnd,
]
);
}
#[test]
fn sequence_indented_one_column_under_mapping_key() {
let events = parse_events("a:\n - b\n").unwrap();
assert_eq!(
events,
vec![
Event::StreamStart,
Event::DocumentStart(false, None),
Event::MappingStart(StructureStyle::Block, 0, None),
Event::Scalar("a".into(), ScalarStyle::Plain, 0, None),
Event::SequenceStart(StructureStyle::Block, 0, None),
Event::Scalar("b".into(), ScalarStyle::Plain, 0, None),
Event::SequenceEnd,
Event::MappingEnd,
Event::DocumentEnd,
Event::StreamEnd,
]
);
}