use std::cell::Cell;
use crate::parser::cursor;
use crate::parser::lexer::Token;
const PARSER_STEP_LIMIT: u32 = 15_000_000;
pub(crate) struct ParserCtx<'a> {
tokens: &'a [Token],
steps: Cell<u32>,
high_water: Cell<usize>,
}
impl<'a> ParserCtx<'a> {
pub(crate) fn new(tokens: &'a [Token]) -> Self {
Self {
tokens,
steps: Cell::new(0),
high_water: Cell::new(0),
}
}
#[inline]
fn step(&self, i: usize) {
if i > self.high_water.get() {
self.high_water.set(i);
self.steps.set(0);
}
let steps = self.steps.get();
assert!(
steps < PARSER_STEP_LIMIT,
"parser exceeded {PARSER_STEP_LIMIT} peeks without advancing past token {} \
— non-advancing loop",
self.high_water.get()
);
self.steps.set(steps + 1);
}
pub(crate) fn token(&self, i: usize) -> Option<&'a Token> {
self.step(i);
self.tokens.get(i)
}
pub(crate) fn tokens(&self) -> &'a [Token] {
self.tokens
}
pub(crate) fn skip_ws(&self, i: usize) -> usize {
self.step(i);
cursor::skip_ws(self.tokens, i)
}
pub(crate) fn skip_ws_and_block_comments(&self, i: usize) -> usize {
self.step(i);
cursor::skip_ws_and_block_comments(self.tokens, i)
}
pub(crate) fn skip_ws_and_newlines(&self, i: usize) -> usize {
self.step(i);
cursor::skip_ws_and_newlines(self.tokens, i)
}
pub(crate) fn skip_trivia(&self, i: usize) -> usize {
self.step(i);
cursor::skip_trivia(self.tokens, i)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "non-advancing loop")]
fn step_guard_trips_when_wedged() {
let ctx = ParserCtx::new(&[]);
ctx.high_water.set(usize::MAX);
ctx.steps.set(PARSER_STEP_LIMIT - 1);
ctx.step(0); ctx.step(0); }
#[test]
fn step_budget_resets_on_progress() {
let ctx = ParserCtx::new(&[]);
ctx.steps.set(PARSER_STEP_LIMIT - 1);
ctx.step(1);
assert_eq!(
ctx.steps.get(),
1,
"advancing the frontier resets the budget"
);
assert_eq!(ctx.high_water.get(), 1);
}
}