use crate::{
Error, Parser,
parser::{line_end, space, tab, whitespace},
};
impl<'a> Parser<'a> {
pub(super) fn err(&self, msg: impl Into<String>) -> Error {
Error {
msg: msg.into(),
line: Some(self.line),
col: Some(self.col),
}
}
pub(super) fn peek(&self) -> Option<u8> {
self.src.as_bytes().get(self.pos).copied()
}
pub(super) fn peek_at(&self, pos: usize) -> Option<u8> {
self.src.as_bytes().get(pos).copied()
}
pub(super) fn advance(&mut self) {
if let Some(b) = self.peek() {
self.pos += 1;
if line_end(b) {
self.line += 1;
self.col = 1;
} else {
self.col += 1;
}
}
}
pub(super) fn at_eof(&self) -> bool {
self.peek().is_none()
}
pub(super) fn skip_spaces(&mut self) {
while let Some(b) = self.peek() {
if whitespace(b) {
self.advance()
} else {
break;
}
}
}
pub(super) fn at_line_end(&self) -> bool {
self.peek().is_none_or(line_end)
}
pub(super) fn consume_newline(&mut self) {
if self.peek() == Some(b'\r') {
self.advance();
}
if self.peek() == Some(b'\n') {
self.advance();
}
}
pub(super) fn skip_blank_and_comment_lines(&mut self) {
loop {
if self.at_eof() {
return;
}
if self.current_line_is_blank() || self.current_line_is_comment() {
self.skip_to_next_line();
} else {
return;
}
}
}
pub(super) fn current_line_is_blank(&self) -> bool {
let mut i = self.pos;
while let Some(b) = self.src.as_bytes().get(i).copied() {
if line_end(b) {
return true;
}
if !whitespace(b) {
return false;
}
i += 1;
}
true
}
pub(super) fn current_line_is_comment(&self) -> bool {
let mut i = self.pos;
while let Some(b) = self.src.as_bytes().get(i).copied() {
if !whitespace(b) {
return matches!(b, b'#');
}
i += 1;
}
false
}
pub(super) fn skip_to_next_line(&mut self) {
while let Some(b) = self.peek() {
if line_end(b) {
break;
}
self.advance();
}
self.consume_newline();
}
pub(super) fn current_indent(&self) -> crate::Result<usize> {
let mut i = self.pos;
while let Some(b) = self.peek_at(i) {
match b {
b if tab(b) => {
return Err(Error {
msg: "parsing failed: illegal indent whitespace using \\t".into(),
line: Some(self.line),
col: Some(self.col),
});
}
b if space(b) => {
i += 1;
}
_ => break,
};
}
Ok(i - self.pos)
}
pub(super) fn consume_line_fold(&mut self, out: &mut String, parent_indent: usize) {
self.consume_one_line_break();
let mut empty_lines = 0;
let mut next_more_indented = false;
loop {
let mut stripped = 0;
while stripped < parent_indent && matches!(self.peek(), Some(b' ' | b'\t')) {
self.advance();
stripped += 1;
}
match self.peek() {
Some(b'\n' | b'\r') => {
empty_lines += 1;
self.consume_one_line_break();
}
Some(b' ' | b'\t') => {
next_more_indented = true;
break;
}
_ => break,
}
}
if empty_lines == 0 {
if next_more_indented {
out.push('\n');
} else {
out.push(' ');
}
} else {
for _ in 0..empty_lines {
out.push('\n');
}
}
}
pub(super) fn consume_one_line_break(&mut self) {
match self.peek() {
Some(b'\r') => {
self.advance();
if self.peek() == Some(b'\n') {
self.advance();
}
}
Some(b'\n') => self.advance(),
_ => {}
}
}
pub(super) fn at_doc_marker(&self, marker: &[u8]) -> bool {
if self.col != 1 {
return false;
}
let bytes = self.src.as_bytes();
if self.pos + marker.len() > bytes.len() {
return false;
}
if &bytes[self.pos..self.pos + marker.len()] != marker {
return false;
}
matches!(
bytes.get(self.pos + marker.len()),
None | Some(b' ' | b'\t' | b'\n' | b'\r')
)
}
pub(super) fn skip_flow_whitespace(&mut self) {
while let Some(b) = self.peek() {
match b {
b' '|b'\t'|b'\n'|b'\r' => self.advance(),
b'#' => {
while let Some(b) = self.peek() {
if matches!(b, b'\n' | b'\r') {
break;
}
self.advance();
}
}
_ => break,
}
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn advance_tracks_line_col() {
let mut parser = super::Parser::new("ab\ncd");
parser.advance();
parser.advance();
parser.advance();
parser.advance();
assert_eq!((parser.line, parser.col), (2, 2))
}
#[test]
fn skip_spaces_stops_at_newline() {
let mut parser = super::Parser::new(" \nx");
parser.skip_spaces();
assert!(parser.at_line_end());
assert_eq!(parser.peek(), Some(b'\n'));
}
#[test]
fn skip_blank_skips_comments_and_blanks() {
let mut parser = super::Parser::new("\n# x\n\n y");
parser.skip_blank_and_comment_lines();
assert_eq!(parser.peek(), Some(b' '));
parser.advance();
assert_eq!(parser.peek(), Some(b' '));
parser.advance();
assert_eq!(parser.peek(), Some(b'y'));
}
#[test]
fn current_indent_counts_leading_spaces() -> crate::Result<()> {
let parser = super::Parser::new(" foo");
let indent = parser.current_indent()?;
assert_eq!(indent, 4);
Ok(())
}
#[test]
fn tab_in_indent_errors() {
let parser = super::Parser::new("\tfoo");
let err = parser.current_indent().unwrap_err();
assert_eq!(err.line, Some(1));
assert_eq!(err.col, Some(1));
}
}