use super::error::{Expectation, TagError, build_message};
pub const MAX_VALUE_DEPTH: usize = 64;
pub(crate) struct Cursor<'a> {
input: &'a str,
pos: usize,
furthest: usize,
variants: Vec<Expectation>,
silent: u32,
depth: usize,
depth_exhausted: Option<usize>,
}
impl<'a> Cursor<'a> {
pub(crate) fn new(input: &'a str) -> Self {
Self {
input,
pos: 0,
furthest: 0,
variants: Vec::new(),
silent: 0,
depth: 0,
depth_exhausted: None,
}
}
pub(crate) fn pos(&self) -> usize {
self.pos
}
pub(crate) fn reset(&mut self, pos: usize) {
self.pos = pos;
}
pub(crate) fn at_end(&self) -> bool {
self.pos >= self.input.len()
}
fn rest(&self) -> &'a str {
self.input.get(self.pos..).unwrap_or_default()
}
pub(crate) fn peek(&self) -> Option<char> {
self.rest().chars().next()
}
pub(crate) fn advance(&mut self) -> Option<char> {
let ch = self.peek()?;
self.pos += ch.len_utf8();
Some(ch)
}
fn expect(&mut self, expectation: Expectation) {
if self.silent > 0 || self.pos < self.furthest {
return;
}
if self.pos > self.furthest {
self.furthest = self.pos;
self.variants.clear();
}
self.variants.push(expectation);
}
pub(crate) fn expect_end_of_input(&mut self) {
self.expect(Expectation::EndOfInput);
}
pub(crate) fn enter_named(&mut self, name: &'static str) {
self.expect(Expectation::Named(name));
self.silent += 1;
}
pub(crate) fn leave_named(&mut self) {
self.silent = self.silent.saturating_sub(1);
}
pub(crate) fn literal(&mut self, text: &'static str) -> bool {
self.expect(Expectation::Literal(text));
if self.rest().starts_with(text) {
self.pos += text.len();
true
} else {
false
}
}
pub(crate) fn whitespace(&mut self) -> bool {
self.expect(Expectation::Named("whitespace"));
match self.peek() {
Some(' ' | '\n' | '\t') => {
self.pos += 1;
true
}
_ => false,
}
}
pub(crate) fn whitespace_star(&mut self) {
while self.whitespace() {}
}
pub(crate) fn whitespace_plus(&mut self) -> bool {
if !self.whitespace() {
return false;
}
self.whitespace_star();
true
}
pub(crate) fn identifier(&mut self) -> Option<&'a str> {
self.enter_named("identifier");
let start = self.pos;
let end = start
+ self
.rest()
.bytes()
.take_while(|b| b.is_ascii_alphanumeric() || *b == b'_' || *b == b'-')
.count();
self.leave_named();
if end == start {
return None;
}
self.pos = end;
self.input.get(start..end)
}
pub(crate) fn digit(&mut self) -> bool {
if self.rest().starts_with(|c: char| c.is_ascii_digit()) {
self.pos += 1;
true
} else {
false
}
}
pub(crate) fn slice(&self, start: usize, end: usize) -> &'a str {
self.input.get(start..end).unwrap_or_default()
}
pub(crate) fn enter_value(&mut self) -> bool {
if self.depth >= MAX_VALUE_DEPTH {
if self.depth_exhausted.is_none() {
self.depth_exhausted = Some(self.pos);
}
return false;
}
self.depth += 1;
true
}
pub(crate) fn leave_value(&mut self) {
self.depth = self.depth.saturating_sub(1);
}
pub(crate) fn into_error(self) -> TagError {
if let Some(offset) = self.depth_exhausted {
let end = self
.input
.get(offset..)
.and_then(|rest| rest.chars().next())
.map_or(offset, |ch| offset + ch.len_utf8());
return TagError::new(
format!("Value nesting exceeds the maximum depth of {MAX_VALUE_DEPTH}."),
offset,
end,
);
}
let found = self
.input
.get(self.furthest..)
.and_then(|rest| rest.chars().next());
let end = found.map_or(self.furthest, |ch| self.furthest + ch.len_utf8());
TagError::new(build_message(&self.variants, found), self.furthest, end)
}
}