use std::{error::Error, fmt::Display};
use thiserror::Error;
use crate::event::Grouping;
use super::SpanStack;
#[derive(Debug)]
pub struct ParserError {
inner: Box<Inner>,
}
#[derive(Debug)]
struct Inner {
error: ErrorKind,
context: Box<str>,
}
impl ParserError {
pub(super) fn new(error: ErrorKind, place: *const u8, span_stack: &mut SpanStack) -> Self {
const CONTEXT_SIZE: usize = 12;
const CONTEXT_PREFIX: &str = "context: ";
const EXPANSION_PREFIX: &str = "which was expanded from: ";
let index = span_stack.reach_original_call_site(place);
let mut context = String::from(CONTEXT_PREFIX);
let first_string = span_stack
.expansions
.last()
.map(|exp| exp.full_expansion)
.unwrap_or(span_stack.input);
let (mut lower_bound, mut upper_bound) = (
floor_char_boundary(first_string, index.saturating_sub(CONTEXT_SIZE)),
floor_char_boundary(first_string, index + CONTEXT_SIZE),
);
span_stack
.expansions
.iter()
.rev()
.enumerate()
.for_each(|(index, expansion)| {
let context_str = &expansion.full_expansion[lower_bound..upper_bound];
context.push_str(context_str);
context.push('\n');
context.push_str(EXPANSION_PREFIX);
let next_string = (span_stack.expansions.len() - 1)
.checked_sub(index + 1)
.map(|index| span_stack.expansions[index].full_expansion)
.unwrap_or(span_stack.input);
lower_bound = floor_char_boundary(
next_string,
expansion
.call_site_in_origin
.start
.saturating_sub(CONTEXT_SIZE),
);
upper_bound = floor_char_boundary(
next_string,
expansion.call_site_in_origin.end + CONTEXT_SIZE,
);
});
context.push_str(&span_stack.input[lower_bound..upper_bound]);
context.shrink_to_fit();
Self {
inner: Box::new(Inner {
error,
context: context.into_boxed_str(),
}),
}
}
}
impl Error for ParserError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.inner.error)
}
}
impl Display for ParserError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("parsing error: ")?;
self.inner.error.fmt(f)?;
f.write_str("\n")?;
f.write_str(&self.inner.context)?;
Ok(())
}
}
pub(crate) type InnerResult<T> = std::result::Result<T, ErrorKind>;
#[derive(Debug, Error)]
pub(crate) enum ErrorKind {
#[error("unbalanced group found, expected {:?}", .0)]
UnbalancedGroup(Option<Grouping>),
#[error("unkown mathematical environment found")]
Environment,
#[error(
"unexpected math `$` (math shift) character - this character cannot be used inside math mode"
)]
MathShift,
#[error(
"unexpected hash sign `#` character - this character can only be used in macro definitions"
)]
HashSign,
#[error("unexpected end of input")]
EndOfInput,
#[error("expected a dimension or glue argument")]
DimensionArgument,
#[error("expected a dimensional unit")]
DimensionUnit,
#[error("expected mathematical units (mu) in dimension specification")]
MathUnit,
#[error("expected a delimiter token")]
Delimiter,
#[error("expected a control sequence")]
ControlSequence,
#[error("expected a number")]
Number,
#[error("expected a character representing a number after '`'. found a non ascii character")]
CharacterNumber,
#[error("expected an argument")]
Argument,
#[error("expected an argument delimited by `{{}}`")]
GroupArgument,
#[error("trying to add a subscript twice to the same element")]
DoubleSubscript,
#[error("trying to add a superscript twice to the same element")]
DoubleSuperscript,
#[error("unknown primitive command found")]
UnknownPrimitive,
#[error("control sequence found as argument to a command that does not support them")]
ControlSequenceAsArgument,
#[error("subscript and/or superscript found as argument to a command")]
ScriptAsArgument,
#[error("empty control sequence")]
EmptyControlSequence,
#[error("unkown color. colors must either be predefined or in the form `#RRGGBB`")]
UnknownColor,
#[error("expected a number in the range 0..=255 for it to be translated into a character")]
InvalidCharNumber,
#[error("cannot use the `\\relax` command in this context")]
Relax,
#[error("macro definition of parameters contains '{{' or '}}'")]
BracesInParamText,
#[error("macro definition of parameters contains a (`%`) comment")]
CommentInParamText,
#[error("macro definition found parameter #{0} but expected #{1}")]
IncorrectMacroParams(u8, u8),
#[error(
"macro definition found parameter #{0} but expected a parameter in the range [#1, #{1}]"
)]
IncorrectReplacementParams(u8, u8),
#[error("macro definition contains too many parameters, the maximum is 9")]
TooManyParams,
#[error("macro definition contains a standalone '#'")]
StandaloneHashSign,
#[error("macro use does not match its definition, expected it to begin with a prefix string as specified in the definition")]
IncorrectMacroPrefix,
#[error("macro already defined")]
MacroAlreadyDefined,
#[error("macro not defined")]
MacroNotDefined,
}
fn floor_char_boundary(str: &str, index: usize) -> usize {
if index >= str.len() {
str.len()
} else {
let lower_bound = index.saturating_sub(3);
let new_index = str.as_bytes()[lower_bound..=index].iter().rposition(|b| {
(*b as i8) >= -0x40
});
unsafe { lower_bound + new_index.unwrap_unchecked() }
}
}