tex_engine/utils/
errors.rs

1/*! Recovery from errors.
2
3  An instance of [`ErrorHandler`] provides methods that get called when errors occur during compilation.
4  The signatures of these methods reflect where they are called and what needs to be returned in order
5  to recover from the error.
6*/
7#![allow(clippy::result_unit_err)]
8
9use crate::commands::primitives::PrimitiveIdentifier;
10use crate::engine::state::State;
11use crate::engine::utils::memory::MemoryManager;
12use crate::engine::{EngineAux, EngineReferences, EngineTypes};
13use crate::prelude::{Mouth, TeXMode};
14use crate::tex::characters::{Character, StringLineSource};
15use crate::tex::tokens::control_sequences::CSHandler;
16use crate::tex::tokens::StandardToken;
17use crate::tex::tokens::Token;
18use std::fmt::Debug;
19use std::marker::PhantomData;
20use thiserror::Error;
21
22#[derive(Debug, Error)]
23pub enum TeXError<ET: EngineTypes> {
24    #[error(transparent)]
25    InvalidCharacter(#[from] InvalidCharacter<ET::Char>),
26    #[error("Use of {0} doesn't match its definition")]
27    WrongDefinition(String),
28    #[error("! File ended while scanning use of `{0}`")]
29    FileEndedWhileScanningUseOf(String),
30    #[error("Undefined {0}")]
31    Undefined(String),
32    #[error("! Too many }}'s")]
33    TooManyCloseBraces,
34    #[error("Emergency stop.")]
35    EmergencyStop,
36    #[error("! Missing {{ inserted")]
37    MissingBegingroup,
38    #[error("! Missing }} inserted")]
39    MissingEndgroup,
40    #[error("! Missing $ inserted")]
41    MissingDollar,
42    #[error("Missing number, treated as zero.")]
43    MissingNumber,
44    #[error("Illegal unit of measure (pt inserted)")]
45    MissingUnit,
46    #[error("Runaway argument? Paragraph ended before {0} was complete.")]
47    ParagraphEnded(String),
48    #[error("! Missing {} inserted",.0[0])]
49    MissingKeyword(&'static [&'static str]),
50    #[error("Incomplete {}; all text was ignored after line {line_no}",.name.display::<u8>(Some(b'\\')))]
51    IncompleteConditional {
52        name: PrimitiveIdentifier,
53        line_no: usize,
54    },
55    #[error("You can't use {} in {mode} mode",.name.display::<u8>(Some(b'\\')))]
56    NotAllowedInMode {
57        name: PrimitiveIdentifier,
58        mode: TeXMode,
59    },
60    #[error("Errror: {0}")]
61    General(String),
62    #[error(transparent)]
63    Fmt(#[from] std::fmt::Error),
64    /*
65    FileEndWhileScanningTextOf(ET::Token),
66    MissingEndgroup,
67    MissingArgument(ET::Token),
68    MissingNumber,
69    MissingKeyword(&'static[&'static str]),
70    EndInsideGroup,
71     */
72}
73impl<ET: EngineTypes> From<String> for TeXError<ET> {
74    #[inline]
75    fn from(value: String) -> Self {
76        Self::General(value)
77    }
78}
79
80macro_rules! throw {
81    ($aux:expr,$state:expr,$mouth:expr,$f:ident($($arg:expr),*) => $err:expr) => {{
82        let eh = &$aux.error_handler;
83        let ret = eh.$f(&$aux.outputs,&$aux.memory,$state$(,$arg)*);
84        match ret {
85            Ok(Some(src)) => {
86                $mouth.push_string(src);
87                Ok(())
88            },
89            Ok(None) => Ok(()),
90            _ => Err($err)
91        }
92    }};
93}
94impl<ET: EngineTypes> TeXError<ET> {
95    /// #### Errors
96    /// because that's what it's supposed to do
97    pub fn incomplete_conditional<M: Mouth<ET>>(
98        aux: &EngineAux<ET>,
99        state: &ET::State,
100        mouth: &mut M,
101        name: PrimitiveIdentifier,
102    ) -> TeXResult<(), ET> {
103        let line_no = mouth.line_number();
104        throw!(aux,state,mouth,incomplete_conditional(name,line_no) => Self::IncompleteConditional{name,line_no})
105    }
106    /// #### Errors
107    /// because that's what it's supposed to do
108    pub fn wrong_definition<M: Mouth<ET>>(
109        aux: &EngineAux<ET>,
110        state: &ET::State,
111        mouth: &mut M,
112        found: &ET::Token,
113        expected: &ET::Token,
114        in_macro: &ET::Token,
115    ) -> TeXResult<(), ET> {
116        throw!(aux,state,mouth,wrong_definition(found,expected,in_macro) =>
117            Self::WrongDefinition(Token::display(in_macro,aux.memory.cs_interner(),state.get_catcode_scheme(),state.get_escape_char()).to_string())
118        )
119    }
120    /// #### Errors
121    /// because that's what it's supposed to do
122    pub fn file_end_while_use<M: Mouth<ET>>(
123        aux: &EngineAux<ET>,
124        state: &ET::State,
125        mouth: &mut M,
126        in_macro: &ET::Token,
127    ) -> TeXResult<(), ET> {
128        throw!(aux,state,mouth,file_end_while_use(in_macro) => Self::FileEndedWhileScanningUseOf(Token::display(in_macro,aux.memory.cs_interner(),state.get_catcode_scheme(),state.get_escape_char()).to_string()))
129    }
130    /// #### Errors
131    /// because that's what it's supposed to do
132    pub fn undefined<M: Mouth<ET>>(
133        aux: &EngineAux<ET>,
134        state: &ET::State,
135        mouth: &mut M,
136        token: &ET::Token,
137    ) -> TeXResult<(), ET> {
138        throw!(aux,state,mouth,undefined(token) => Self::Undefined(
139            match token.to_enum() {
140                StandardToken::ControlSequence(cs) =>
141                    format!("control sequence {}{}",ET::Char::display_opt(state.get_escape_char()),aux.memory.cs_interner().resolve(&cs)),
142                StandardToken::Character(c,_) =>
143                    format!("active character {}",c.display()),
144                StandardToken::Primitive(_) => unreachable!()
145            }
146        ))
147    }
148    /// #### Errors
149    /// because that's what it's supposed to do
150    pub fn not_allowed_in_mode<M: Mouth<ET>>(
151        aux: &EngineAux<ET>,
152        state: &ET::State,
153        mouth: &mut M,
154        name: PrimitiveIdentifier,
155        mode: TeXMode,
156    ) -> TeXResult<(), ET> {
157        throw!(aux,state,mouth,not_allowed_in_mode(name,mode) => Self::NotAllowedInMode{name,mode})
158    }
159    /// #### Errors
160    /// because that's what it's supposed to do
161    pub fn missing_begingroup<M: Mouth<ET>>(
162        aux: &EngineAux<ET>,
163        state: &ET::State,
164        mouth: &mut M,
165    ) -> TeXResult<(), ET> {
166        throw!(aux,state,mouth,missing_begingroup() => Self::MissingBegingroup)
167    }
168    /// #### Errors
169    /// because that's what it's supposed to do
170    pub fn missing_endgroup<M: Mouth<ET>>(
171        aux: &EngineAux<ET>,
172        state: &ET::State,
173        mouth: &mut M,
174    ) -> TeXResult<(), ET> {
175        throw!(aux,state,mouth,missing_endgroup() => Self::MissingEndgroup)
176    }
177    /// #### Errors
178    /// because that's what it's supposed to do
179    pub fn missing_number<M: Mouth<ET>>(
180        aux: &EngineAux<ET>,
181        state: &ET::State,
182        mouth: &mut M,
183    ) -> TeXResult<(), ET> {
184        throw!(aux,state,mouth,missing_number() => Self::MissingNumber)
185    }
186    /// #### Errors
187    /// because that's what it's supposed to do
188    pub fn missing_unit<M: Mouth<ET>>(
189        aux: &EngineAux<ET>,
190        state: &ET::State,
191        mouth: &mut M,
192    ) -> TeXResult<(), ET> {
193        throw!(aux,state,mouth,missing_number() => Self::MissingUnit)
194    }
195    /// #### Errors
196    /// because that's what it's supposed to do
197    pub fn missing_keyword<M: Mouth<ET>>(
198        aux: &EngineAux<ET>,
199        state: &ET::State,
200        mouth: &mut M,
201        kws: &'static [&'static str],
202    ) -> TeXResult<(), ET> {
203        throw!(aux,state,mouth,missing_keyword(kws) => Self::MissingKeyword(kws))
204    }
205    /// #### Errors
206    /// because that's what it's supposed to do
207    pub fn missing_dollar_inserted<M: Mouth<ET>>(
208        aux: &EngineAux<ET>,
209        state: &ET::State,
210        mouth: &mut M,
211    ) -> TeXResult<(), ET> {
212        throw!(aux,state,mouth,missing_dollar() => Self::MissingDollar)
213    }
214    /// #### Errors
215    /// because that's what it's supposed to do
216    pub fn paragraph_ended<M: Mouth<ET>>(
217        aux: &EngineAux<ET>,
218        state: &ET::State,
219        mouth: &mut M,
220        t: &ET::Token,
221    ) -> TeXResult<(), ET> {
222        throw!(aux,state,mouth,paragraph_ended(t) => Self::ParagraphEnded(
223            match t.to_enum() {
224                StandardToken::ControlSequence(cs) =>
225                    format!("control sequence {}{}",ET::Char::display_opt(state.get_escape_char()),aux.memory.cs_interner().resolve(&cs)),
226                StandardToken::Character(c,_) =>
227                    format!("active character {}",c.display()),
228                StandardToken::Primitive(_) => unreachable!()
229            }
230        ))
231    }
232}
233pub type TeXResult<A, ET> = Result<A, TeXError<ET>>;
234
235pub trait IntoErr<ET: EngineTypes, Err> {
236    fn into_err(self, aux: &EngineAux<ET>, state: &ET::State) -> Err;
237}
238impl<ET: EngineTypes, Err, A> IntoErr<ET, Err> for A
239where
240    Err: From<A>,
241{
242    fn into_err(self, _aux: &EngineAux<ET>, _state: &ET::State) -> Err {
243        self.into()
244    }
245}
246
247pub trait RecoverableError<ET: EngineTypes>: Sized {
248    /// #### Errors
249    /// because that's what it's supposed to do
250    fn recover<M: Mouth<ET>, Err>(
251        self,
252        aux: &EngineAux<ET>,
253        state: &ET::State,
254        mouth: &mut M,
255    ) -> Result<(), Err>
256    where
257        Self: IntoErr<ET, Err>;
258
259    /// #### Errors
260    /// because that's what it's supposed to do
261    #[inline]
262    fn throw<M: Mouth<ET>>(
263        self,
264        aux: &EngineAux<ET>,
265        state: &ET::State,
266        mouth: &mut M,
267    ) -> Result<(), TeXError<ET>>
268    where
269        Self: IntoErr<ET, TeXError<ET>>,
270    {
271        self.recover(aux, state, mouth)
272            .map_err(|e| e.into_err(aux, state))
273    }
274}
275macro_rules! split {
276    ($self:ident,$aux:expr,$state:ident,$mouth:ident,$f:ident($($arg:expr),*)) => {{
277        throw!($aux,$state,$mouth,$f($($arg),*) => $self.into_err($aux,$state))
278    }};
279}
280
281#[derive(Debug, Error)]
282#[error("! Too many }}'s")]
283pub enum GulletError<C: Character> {
284    #[error("! Text line contains an invalid character.\n{}",.0.display())]
285    InvalidChar(C),
286    TooManyCloseBraces,
287}
288impl<C: Character> From<InvalidCharacter<C>> for GulletError<C> {
289    fn from(InvalidCharacter(c): InvalidCharacter<C>) -> Self {
290        Self::InvalidChar(c)
291    }
292}
293impl<C: Character> From<TooManyCloseBraces> for GulletError<C> {
294    fn from(_: TooManyCloseBraces) -> Self {
295        Self::TooManyCloseBraces
296    }
297}
298impl<ET: EngineTypes> From<GulletError<ET::Char>> for TeXError<ET> {
299    fn from(e: GulletError<ET::Char>) -> Self {
300        match e {
301            GulletError::InvalidChar(c) => Self::InvalidCharacter(InvalidCharacter(c)),
302            GulletError::TooManyCloseBraces => Self::TooManyCloseBraces,
303        }
304    }
305}
306
307/// An error indicating that an invalid [`Character`] was encountered
308#[derive(Debug, Error)]
309#[error("! Text line contains an invalid character.\n{}",.0.display())]
310pub struct InvalidCharacter<C: Character>(pub C);
311impl<ET: EngineTypes> RecoverableError<ET> for InvalidCharacter<ET::Char> {
312    fn recover<M: Mouth<ET>, Err>(
313        self,
314        aux: &EngineAux<ET>,
315        state: &ET::State,
316        mouth: &mut M,
317    ) -> Result<(), Err>
318    where
319        Self: IntoErr<ET, Err>,
320    {
321        split!(self, aux, state, mouth, invalid_character(self.0))
322    }
323}
324
325#[derive(Debug, Error)]
326#[error("! Too many }}'s")]
327pub struct TooManyCloseBraces;
328impl<ET: EngineTypes> RecoverableError<ET> for TooManyCloseBraces {
329    fn recover<M: Mouth<ET>, Err>(
330        self,
331        aux: &EngineAux<ET>,
332        state: &ET::State,
333        mouth: &mut M,
334    ) -> Result<(), Err>
335    where
336        Self: IntoErr<ET, Err>,
337    {
338        split!(self, aux, state, mouth, too_many_closebraces())
339    }
340}
341impl<ET: EngineTypes> From<TooManyCloseBraces> for TeXError<ET> {
342    fn from(_: TooManyCloseBraces) -> Self {
343        Self::TooManyCloseBraces
344    }
345}
346
347/// Trait for error recovery, to be implemented for an engine.
348pub trait ErrorHandler<ET: EngineTypes> {
349    /// "Text line contains an invalid character".
350    /// #### Errors
351    /// because that's what it's supposed to do
352    #[inline]
353    fn invalid_character(
354        &self,
355        _out: &ET::Outputs,
356        _memory: &MemoryManager<ET::Token>,
357        _state: &ET::State,
358        _c: ET::Char,
359    ) -> Result<Option<StringLineSource<ET::Char>>, ()> {
360        Err(())
361    }
362    /// "Use of `\foo` doesn't match its definition".
363    /// #### Errors
364    /// because that's what it's supposed to do
365    #[inline]
366    fn wrong_definition(
367        &self,
368        _out: &ET::Outputs,
369        _memory: &MemoryManager<ET::Token>,
370        _state: &ET::State,
371        _found: &ET::Token,
372        _expected: &ET::Token,
373        _in_macro: &ET::Token,
374    ) -> Result<Option<StringLineSource<ET::Char>>, ()> {
375        Err(())
376    }
377    /// "File ended while scanning use of `\foo`".
378    /// #### Errors
379    /// because that's what it's supposed to do
380    #[inline]
381    fn file_end_while_use(
382        &self,
383        _out: &ET::Outputs,
384        _memory: &MemoryManager<ET::Token>,
385        _state: &ET::State,
386        _in_macro: &ET::Token,
387    ) -> Result<Option<StringLineSource<ET::Char>>, ()> {
388        Err(())
389    }
390    /// "Too many }'s".
391    /// #### Errors
392    /// because that's what it's supposed to do
393    #[inline]
394    fn too_many_closebraces(
395        &self,
396        _out: &ET::Outputs,
397        _memory: &MemoryManager<ET::Token>,
398        _state: &ET::State,
399    ) -> Result<Option<StringLineSource<ET::Char>>, ()> {
400        Err(())
401    }
402    /// "Missing { inserted".
403    /// #### Errors
404    /// because that's what it's supposed to do
405    #[inline]
406    fn missing_begingroup(
407        &self,
408        _out: &ET::Outputs,
409        _memory: &MemoryManager<ET::Token>,
410        _state: &ET::State,
411    ) -> Result<Option<StringLineSource<ET::Char>>, ()> {
412        Err(())
413    }
414    /// "Missing $ inserted".
415    /// #### Errors
416    /// because that's what it's supposed to do
417    #[inline]
418    fn missing_dollar(
419        &self,
420        _out: &ET::Outputs,
421        _memory: &MemoryManager<ET::Token>,
422        _state: &ET::State,
423    ) -> Result<Option<StringLineSource<ET::Char>>, ()> {
424        Err(())
425    }
426    /// "Missing } inserted".
427    /// #### Errors
428    /// because that's what it's supposed to do
429    #[inline]
430    fn missing_endgroup(
431        &self,
432        _out: &ET::Outputs,
433        _memory: &MemoryManager<ET::Token>,
434        _state: &ET::State,
435    ) -> Result<Option<StringLineSource<ET::Char>>, ()> {
436        Err(())
437    }
438
439    /// "Incomplete \if...; all text was ignored after line `n`".
440    /// #### Errors
441    /// because that's what it's supposed to do
442    #[inline]
443    fn incomplete_conditional(
444        &self,
445        _out: &ET::Outputs,
446        _memory: &MemoryManager<ET::Token>,
447        _state: &ET::State,
448        _name: PrimitiveIdentifier,
449        _line_no: usize,
450    ) -> Result<Option<StringLineSource<ET::Char>>, ()> {
451        Err(())
452    }
453
454    /// "Undefined `[`control sequence`|`active character`]`"
455    /// #### Errors
456    /// because that's what it's supposed to do
457    #[inline]
458    fn undefined(
459        &self,
460        _out: &ET::Outputs,
461        _memory: &MemoryManager<ET::Token>,
462        _state: &ET::State,
463        _token: &ET::Token,
464    ) -> Result<Option<StringLineSource<ET::Char>>, ()> {
465        Err(())
466    }
467
468    /// "Runaway argument? Paragraph ended before `\foo` was complete."
469    /// #### Errors
470    /// because that's what it's supposed to do
471    #[inline]
472    fn paragraph_ended(
473        &self,
474        _out: &ET::Outputs,
475        _memory: &MemoryManager<ET::Token>,
476        _state: &ET::State,
477        _token: &ET::Token,
478    ) -> Result<Option<StringLineSource<ET::Char>>, ()> {
479        Err(())
480    }
481
482    /// "You can't use `\foo` in `M` mode."
483    /// #### Errors
484    /// because that's what it's supposed to do
485    #[inline]
486    fn not_allowed_in_mode(
487        &self,
488        _out: &ET::Outputs,
489        _memory: &MemoryManager<ET::Token>,
490        _state: &ET::State,
491        _name: PrimitiveIdentifier,
492        _mode: TeXMode,
493    ) -> Result<Option<StringLineSource<ET::Char>>, ()> {
494        Err(())
495    }
496    /// "Missing `x` inserted"
497    /// #### Errors
498    /// because that's what it's supposed to do
499    #[inline]
500    fn missing_keyword(
501        &self,
502        _out: &ET::Outputs,
503        _memory: &MemoryManager<ET::Token>,
504        _state: &ET::State,
505        _kws: &'static [&'static str],
506    ) -> Result<Option<StringLineSource<ET::Char>>, ()> {
507        Err(())
508    }
509
510    /// "Missing number, treated as zero."
511    /// #### Errors
512    /// because that's what it's supposed to do
513    #[inline]
514    fn missing_number(
515        &self,
516        _out: &ET::Outputs,
517        _memory: &MemoryManager<ET::Token>,
518        _state: &ET::State,
519    ) -> Result<Option<StringLineSource<ET::Char>>, ()> {
520        Err(())
521    }
522
523    /// Any other possibly recoverable error.
524    /// #### Errors
525    /// because that's what it's supposed to do
526    #[inline]
527    fn other(
528        &self,
529        _out: &ET::Outputs,
530        _memory: &MemoryManager<ET::Token>,
531        _state: &ET::State,
532        _msg: &str,
533    ) -> Result<Option<StringLineSource<ET::Char>>, ()> {
534        Err(())
535    }
536
537    /*
538    /// "Runaway argument? Paragraph ended before `\foo` was complete."
539    fn no_par<T:Token,St:AsRef<str>,S:TextLineSource<T::Char>>(&self, _tokenizer:&mut InputTokenizer<T::Char,S>, _name:St, _start:(usize, usize)) -> T {
540        //let line = &tokenizer.string.line(start.0)[start.1..];
541        //throw!("Runaway argument?\n{}\n! Paragraph ended before \\{} was complete.",InputLinePresenter(line),name.as_ref());
542    }
543    /// "Runaway argument? File ended while scanning use of `\foo`."
544    fn file_end<T:Token,St:AsRef<str>,S:TextLineSource<T::Char>>(&self, _tokenizer:&mut InputTokenizer<T::Char,S>, _name:St, _start:(usize, usize)) -> T {
545        //let line = &tokenizer.string.line(start.0)[start.1..];
546        //throw!("Runaway argument?\n{}\n! File ended while scanning use of \\{}.",InputLinePresenter(line),name.as_ref());
547    }
548
549     */
550    fn new() -> Self;
551}
552
553/// Default [`ErrorHandler`] that just throws a [`TeXError`].
554pub struct ErrorThrower<ET: EngineTypes>(PhantomData<ET>);
555impl<ET: EngineTypes> ErrorHandler<ET> for ErrorThrower<ET> {
556    fn new() -> Self {
557        Self(PhantomData)
558    }
559}
560
561impl<ET: EngineTypes> EngineReferences<'_, ET> {
562    /// #### Errors
563    /// because that's what it's supposed to do
564    #[inline]
565    pub fn general_error(&mut self, msg: String) -> TeXResult<(), ET> {
566        throw!(self.aux,self.state,self.mouth,other(&msg) => TeXError::General(msg))
567    }
568}