1use std::collections::HashMap;
8
9use crate::token;
10use crate::token::trace;
11use crate::vm;
12use texcraft_stdext::algorithms::spellcheck::{self, WordDiff};
13
14pub mod display;
15
16#[derive(Debug)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub struct TracedTexError {
25 #[cfg_attr(
26 feature = "serde",
27 serde(
28 serialize_with = "serialize_error",
29 deserialize_with = "deserialize_error"
30 )
31 )]
32 pub error: Box<dyn TexError>,
33 pub stack_trace: Vec<StackTraceElement>,
34 pub token_traces: HashMap<token::Token, trace::SourceCodeTrace>,
35 pub end_of_input_trace: Option<trace::SourceCodeTrace>,
36}
37
38#[cfg(feature = "serde")]
39#[allow(clippy::borrowed_box)] fn serialize_error<S>(value: &Box<dyn TexError>, serializer: S) -> Result<S::Ok, S::Error>
41where
42 S: serde::Serializer,
43{
44 use serde::Serialize;
45 let serializable_error = SerializableError {
46 kind: value.kind().clone(),
47 title: value.title(),
48 notes: value.notes(),
49 source_annotation: value.source_annotation(),
50 };
51 serializable_error.serialize(serializer)
52}
53
54#[cfg(feature = "serde")]
55fn deserialize_error<'de, D>(deserializer: D) -> Result<Box<dyn TexError>, D::Error>
56where
57 D: serde::Deserializer<'de>,
58{
59 use serde::Deserialize;
60 let serializable_error = SerializableError::deserialize(deserializer)?;
61 Ok(Box::new(serializable_error))
62}
63
64#[derive(Clone, Debug)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
66struct SerializableError {
67 kind: Kind,
68 title: String,
69 notes: Vec<display::Note>,
70 source_annotation: String,
71}
72
73impl TexError for SerializableError {
74 fn kind(&self) -> Kind {
75 self.kind.clone()
76 }
77 fn title(&self) -> String {
78 self.title.clone()
79 }
80 fn notes(&self) -> Vec<display::Note> {
81 self.notes.clone()
82 }
83 fn source_annotation(&self) -> String {
84 self.source_annotation.clone()
85 }
86}
87
88impl TracedTexError {
89 pub(crate) fn new(
90 error: Box<dyn TexError>,
91 tracer: &trace::Tracer,
92 cs_name_interner: &token::CsNameInterner,
93 stack_trace: Vec<StackTraceElement>,
94 ) -> Self {
95 let (end_of_input_trace, mut tokens) = match error.kind() {
96 Kind::Token(token) => (None, vec![token]),
97 Kind::EndOfInput => (Some(tracer.trace_end_of_input()), vec![]),
98 Kind::FailedPrecondition => (None, vec![]),
99 };
100 for note in error.notes() {
101 if let display::Note::SourceCodeTrace(_, token) = note {
102 tokens.push(token);
103 }
104 }
105 let token_traces: HashMap<token::Token, trace::SourceCodeTrace> = tokens
106 .into_iter()
107 .map(|token| (token, tracer.trace(token, cs_name_interner)))
108 .collect();
109 TracedTexError {
110 error,
111 stack_trace,
112 token_traces,
113 end_of_input_trace,
114 }
115 }
116}
117
118pub trait EndOfInputError: std::fmt::Debug + 'static {
120 fn doing(&self) -> String;
121 fn notes(&self) -> Vec<display::Note> {
122 vec![]
123 }
124}
125
126#[allow(non_snake_case)]
136pub fn TODO() -> impl TexError + EndOfInputError {
137 TodoError {}
138}
139
140#[derive(Debug)]
141struct TodoError {}
142
143impl EndOfInputError for TodoError {
144 fn doing(&self) -> String {
145 "? (TODO: add a specific end of input error for this case.)".into()
146 }
147 fn notes(&self) -> Vec<display::Note> {
148 vec![
149 "the Rust source code uses `texlang::error::TODO()` for this error case".into(),
150 "a more specific end of input error needs to be added".into(),
151 ]
152 }
153}
154
155impl TexError for TodoError {
156 fn kind(&self) -> Kind {
157 Kind::FailedPrecondition
158 }
159 fn title(&self) -> String {
160 "? (TODO: add a specific error for this case.)".into()
161 }
162 fn notes(&self) -> Vec<display::Note> {
163 vec![
164 "the Rust source code uses `texlang::error::TODO()` for this error case".into(),
165 "a more specific error needs to be added".into(),
166 ]
167 }
168}
169
170#[derive(Debug)]
171pub(crate) struct EofError {
172 doing: String,
173 notes: Vec<display::Note>,
174}
175
176impl EofError {
177 pub(crate) fn new<E: EndOfInputError>(err: E) -> Self {
178 Self {
179 doing: err.doing(),
180 notes: err.notes(),
181 }
182 }
183}
184
185impl TexError for EofError {
186 fn kind(&self) -> Kind {
187 Kind::EndOfInput
188 }
189
190 fn title(&self) -> String {
191 format!("Unexpected end of input while {}", self.doing)
192 }
193 fn notes(&self) -> Vec<display::Note> {
194 self.notes.clone()
195 }
196}
197
198#[derive(Clone, Debug)]
200#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
201pub struct StackTraceElement {
202 pub context: OperationKind,
203 pub token: token::Token,
204 pub trace: trace::SourceCodeTrace,
205}
206
207impl std::fmt::Display for TracedTexError {
208 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209 display::format_error(f, self)
210 }
211}
212
213#[derive(Clone, Debug, PartialEq, Eq)]
215#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
216pub enum Kind {
217 Token(token::Token),
221 EndOfInput,
225 FailedPrecondition,
230}
231
232pub trait TexError: std::fmt::Debug + 'static {
234 fn kind(&self) -> Kind;
235
236 fn title(&self) -> String;
237
238 fn notes(&self) -> Vec<display::Note> {
239 vec![]
240 }
241
242 fn source_annotation(&self) -> String {
243 TexError::default_source_annotation(self)
244 }
245
246 fn default_source_annotation(&self) -> String {
247 match TexError::kind(self) {
248 Kind::Token(t) => match (t.char(), t.cat_code()) {
249 (Some(c), Some(code)) => {
250 format!["character token with value {c} and category code {code}",]
251 }
252 _ => "control sequence".to_string(),
253 },
254 Kind::EndOfInput => "input ended here".into(),
255 Kind::FailedPrecondition => "error occurred while running this command".into(),
256 }
257 }
258
259 fn source_code_trace_override(&self) -> Option<&trace::SourceCodeTrace> {
260 None
261 }
262 }
265
266#[derive(Copy, Clone, Debug)]
267#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
268pub enum OperationKind {
269 Expansion,
270 Execution,
271 VariableIndex,
272 VariableAssignment,
273}
274
275impl OperationKind {
276 fn action(&self) -> &'static str {
277 match self {
278 OperationKind::Expansion => "expanding this command",
279 OperationKind::Execution => "executing this command",
280 OperationKind::VariableIndex => "determining the index of this variable",
281 OperationKind::VariableAssignment => "determining the value to assign to this variable",
282 }
283 }
284}
285
286#[derive(Debug)]
287pub struct SimpleTokenError {
288 pub token: token::Token,
289 pub title: String,
290}
291
292impl SimpleTokenError {
293 pub fn new<T: AsRef<str>>(token: token::Token, title: T) -> SimpleTokenError {
295 SimpleTokenError {
296 token,
297 title: title.as_ref().into(),
298 }
299 }
300}
301
302impl TexError for SimpleTokenError {
303 fn kind(&self) -> Kind {
304 Kind::Token(self.token)
305 }
306
307 fn title(&self) -> String {
308 self.title.clone()
309 }
310}
311
312#[derive(Debug)]
313pub struct SimpleFailedPreconditionError {
314 pub title: String,
315 pub text_notes: Vec<String>,
316}
317
318impl SimpleFailedPreconditionError {
319 pub fn new<T: AsRef<str>>(title: T) -> Self {
321 Self {
322 title: title.as_ref().into(),
323 text_notes: vec![],
324 }
325 }
326
327 pub fn with_note<T: Into<String>>(mut self, note: T) -> Self {
328 self.text_notes.push(note.into());
329 self
330 }
331}
332
333impl TexError for SimpleFailedPreconditionError {
334 fn kind(&self) -> Kind {
335 Kind::FailedPrecondition
336 }
337
338 fn title(&self) -> String {
339 self.title.clone()
340 }
341
342 fn notes(&self) -> Vec<display::Note> {
343 let mut notes = vec![];
344 for text_note in &self.text_notes {
345 notes.push(text_note.into())
346 }
347 notes
348 }
349}
350
351#[derive(Debug)]
356pub struct UndefinedCommandError {
357 pub token: token::Token,
359 pub close_names: Vec<WordDiff>,
361}
362
363impl UndefinedCommandError {
364 pub fn new<S>(vm: &vm::VM<S>, token: token::Token) -> UndefinedCommandError {
366 let name = match &token.value() {
367 token::Value::CommandRef(command_ref) => command_ref.to_string(vm.cs_name_interner()),
368 _ => panic!("undefined command error does not work for non-command-ref tokens"),
369 };
370 let mut all_names = Vec::<&str>::new();
371 for (cs_name, _) in vm.get_commands_as_map_slow().into_iter() {
372 all_names.push(cs_name);
373 }
374 let close_names = spellcheck::find_close_words(&all_names, &name);
375
376 UndefinedCommandError { token, close_names }
377 }
378}
379
380impl TexError for UndefinedCommandError {
381 fn kind(&self) -> Kind {
382 Kind::Token(self.token)
383 }
384
385 fn title(&self) -> String {
386 "undefined control sequence".into()
387 }
388
389 fn notes(&self) -> Vec<display::Note> {
390 let mut notes: Vec<display::Note> = Default::default();
391 use texcraft_stdext::color::Colorize;
392 if let Some(close_name) = self.close_names.first() {
393 notes.push(format!["did you mean \\{}?\n", close_name.right().bold(),].into());
394 }
395 notes
396 }
397}