use std::ops::Range;
use logos::Logos;
use winnow::error::{ErrMode, ParserError};
use winnow::prelude::*;
use winnow::stream::{Stream, TokenSlice};
use winnow::token::any;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Nesting {
Opens,
Closes,
Neither,
}
pub trait Lexeme: Copy + Eq + std::fmt::Debug {
fn describe(self) -> &'static str;
fn nesting(self) -> Nesting;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Token<'s, K> {
pub kind: K,
pub text: &'s str,
pub span: Range<usize>,
}
impl<K: PartialEq> PartialEq<K> for Token<'_, K> {
fn eq(&self, other: &K) -> bool {
self.kind == *other
}
}
pub type Tokens<'i, K> = TokenSlice<'i, Token<'i, K>>;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("unexpected character {found:?} at byte {offset}")]
pub struct LexError {
pub offset: usize,
pub found: char,
}
pub fn lex<'s, K>(input: &'s str) -> Result<Vec<Token<'s, K>>, LexError>
where
K: Logos<'s, Source = str, Error = ()>,
K::Extras: Default,
{
let mut lexer = K::lexer(input);
let mut tokens = Vec::new();
while let Some(kind) = lexer.next() {
let span = lexer.span();
let kind = kind.map_err(|()| LexError {
offset: span.start,
found: lexer.slice().chars().next().unwrap_or('\u{0}'),
})?;
tokens.push(Token {
kind,
text: lexer.slice(),
span,
});
}
Ok(tokens)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Failure {
pub expected: Option<&'static str>,
}
impl<I: Stream> ParserError<I> for Failure {
type Inner = Self;
fn from_input(_: &I) -> Self {
Self::default()
}
fn into_inner(self) -> Result<Self::Inner, Self> {
Ok(self)
}
}
pub type PResult<T> = ModalResult<T, Failure>;
pub trait Expecting<'i, K: Lexeme + 'i, O>:
Parser<Tokens<'i, K>, O, ErrMode<Failure>> + Sized
{
fn expecting(self, what: &'static str) -> impl Parser<Tokens<'i, K>, O, ErrMode<Failure>> {
let mut parser = self;
move |i: &mut Tokens<'i, K>| {
parser.parse_next(i).map_err(|error| match error {
ErrMode::Backtrack(_) => ErrMode::Backtrack(Failure {
expected: Some(what),
}),
other => other,
})
}
}
}
impl<'i, K: Lexeme + 'i, O, P: Parser<Tokens<'i, K>, O, ErrMode<Failure>>> Expecting<'i, K, O>
for P
{
}
pub fn backtrack<T>() -> PResult<T> {
Err(ErrMode::Backtrack(Failure::default()))
}
pub fn refuse<'i, K: Lexeme, T>(
i: &mut Tokens<'i, K>,
start: &<Tokens<'i, K> as Stream>::Checkpoint,
what: &'static str,
) -> PResult<T> {
i.reset(start);
Err(ErrMode::Cut(Failure {
expected: Some(what),
}))
}
pub fn kind<'i, K: Lexeme + 'i>(
wanted: K,
) -> impl Parser<Tokens<'i, K>, &'i Token<'i, K>, ErrMode<Failure>> {
any.verify(move |t: &Token<'i, K>| t.kind == wanted)
.expecting(wanted.describe())
}
pub fn one_of_kinds<'i, K: Lexeme + 'i, const N: usize>(
wanted: [K; N],
) -> impl Parser<Tokens<'i, K>, &'i Token<'i, K>, ErrMode<Failure>> {
any.verify(move |t: &Token<'i, K>| wanted.contains(&t.kind))
}
pub const NESTING_LIMIT: usize = 64;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ParseError {
#[error(transparent)]
Lex(#[from] LexError),
#[error("the expression nests {depth} deep at byte {offset}; the limit is {limit}")]
TooDeep {
offset: usize,
depth: usize,
limit: usize,
},
#[error("expected {expected} at byte {offset}, found {found}")]
Syntax {
offset: usize,
expected: String,
found: String,
},
}
impl ParseError {
#[must_use]
pub const fn offset(&self) -> usize {
match self {
Self::Lex(error) => error.offset,
Self::TooDeep { offset, .. } | Self::Syntax { offset, .. } => *offset,
}
}
}
fn too_deep<K: Lexeme>(tokens: &[Token<'_, K>]) -> Option<(usize, usize)> {
let mut depth = 0_usize;
for token in tokens {
match token.kind.nesting() {
Nesting::Opens => {
depth = depth.saturating_add(1);
if depth > NESTING_LIMIT {
return Some((token.span.start, depth));
}
}
Nesting::Closes => depth = depth.saturating_sub(1),
Nesting::Neither => {}
}
}
None
}
pub fn parse<'s, K, O>(
input: &'s str,
what: &'static str,
mut whole: impl for<'i> FnMut(&mut Tokens<'i, K>) -> PResult<O>,
) -> Result<O, ParseError>
where
K: Lexeme + Logos<'s, Source = str, Error = ()>,
K::Extras: Default,
{
let tokens = lex::<K>(input)?;
if let Some((offset, depth)) = too_deep(&tokens) {
return Err(ParseError::TooDeep {
offset,
depth,
limit: NESTING_LIMIT,
});
}
whole.parse(TokenSlice::new(&tokens)).map_err(|error| {
let index = error.offset();
let (offset, found) = tokens.get(index).map_or_else(
|| (input.len(), String::from("the end of the expression")),
|token| (token.span.start, format!("{:?}", token.text)),
);
ParseError::Syntax {
offset,
expected: String::from(error.inner().expected.unwrap_or(what)),
found,
}
})
}