Skip to main content

ratex_parser/
macro_expander.rs

1use std::collections::HashMap;
2
3use ratex_lexer::token::{SourceLocation, Token};
4use ratex_lexer::Lexer;
5
6use crate::error::{ParseError, ParseResult};
7use crate::functions::FUNCTIONS;
8use crate::parse_node::Mode;
9
10/// Commands that act like macros but aren't defined as a macro, function, or symbol.
11/// Used in `is_defined`.
12pub static IMPLICIT_COMMANDS: &[&str] = &["^", "_", "\\limits", "\\nolimits"];
13
14/// Handler type for function-based macros (e.g. \TextOrMath, \@ifstar).
15/// Takes the MacroExpander mutably and returns tokens to push onto the stack.
16pub type FnMacroHandler = fn(&mut MacroExpander) -> ParseResult<Vec<Token>>;
17
18/// A macro definition: string template, token list, or function.
19#[derive(Clone)]
20pub enum MacroDefinition {
21    /// Simple string expansion (e.g., `\def\foo{bar}` → "bar")
22    Text(String),
23    /// Pre-tokenized expansion with argument count
24    Tokens {
25        tokens: Vec<Token>,
26        num_args: usize,
27    },
28    /// Function-based macro (consumes tokens directly, returns expansion)
29    Function(FnMacroHandler),
30}
31
32impl std::fmt::Debug for MacroDefinition {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match self {
35            Self::Text(s) => write!(f, "Text({:?})", s),
36            Self::Tokens { tokens, num_args } => {
37                write!(f, "Tokens {{ {} tokens, {} args }}", tokens.len(), num_args)
38            }
39            Self::Function(_) => write!(f, "Function(...)"),
40        }
41    }
42}
43
44/// Result of expanding a macro once.
45struct MacroExpansion {
46    tokens: Vec<Token>,
47    num_args: usize,
48    unexpandable: bool,
49}
50
51/// The MacroExpander (or "gullet") manages macro expansion.
52///
53/// It sits between the Lexer (mouth) and the Parser (stomach).
54/// Tokens are read from the lexer, pushed onto an internal stack,
55/// and macros are expanded until only non-expandable tokens remain.
56///
57/// Modeled after KaTeX's MacroExpander.ts.
58pub struct MacroExpander<'a> {
59    pub lexer: Lexer<'a>,
60    pub mode: Mode,
61    stack: Vec<Token>,
62    macros: MacroNamespace,
63    expansion_count: usize,
64    max_expand: usize,
65}
66
67/// Scoped macro namespace supporting group nesting.
68struct MacroNamespace {
69    current: HashMap<String, MacroDefinition>,
70    group_stack: Vec<HashMap<String, Option<MacroDefinition>>>,
71}
72
73impl MacroNamespace {
74    fn new() -> Self {
75        Self {
76            current: HashMap::new(),
77            group_stack: Vec::new(),
78        }
79    }
80
81    fn get(&self, name: &str) -> Option<&MacroDefinition> {
82        self.current.get(name)
83    }
84
85    fn set(&mut self, name: String, def: MacroDefinition) {
86        if let Some(undo) = self.group_stack.last_mut() {
87            undo.entry(name.clone()).or_insert_with(|| self.current.get(&name).cloned());
88        }
89        self.current.insert(name, def);
90    }
91
92    fn set_global(&mut self, name: String, def: MacroDefinition) {
93        self.current.insert(name, def);
94    }
95
96    fn has(&self, name: &str) -> bool {
97        self.current.contains_key(name)
98    }
99
100    fn begin_group(&mut self) {
101        self.group_stack.push(HashMap::new());
102    }
103
104    fn end_group(&mut self) {
105        if let Some(undo) = self.group_stack.pop() {
106            for (name, old_val) in undo {
107                match old_val {
108                    Some(def) => { self.current.insert(name, def); }
109                    None => { self.current.remove(&name); }
110                }
111            }
112        }
113    }
114
115    fn end_groups(&mut self) {
116        while !self.group_stack.is_empty() {
117            self.end_group();
118        }
119    }
120}
121
122/// Tokenize a macro expansion string into stack order (same as [`MacroDefinition::Text`] bodies).
123fn lex_string_to_stack_tokens(text: &str) -> Vec<Token> {
124    let mut body_lexer = Lexer::new(text);
125    let mut tokens = Vec::new();
126    loop {
127        let tok = body_lexer.lex();
128        if tok.is_eof() {
129            break;
130        }
131        tokens.push(tok);
132    }
133    tokens.reverse();
134    tokens
135}
136
137impl<'a> MacroExpander<'a> {
138    pub fn new(input: &'a str, mode: Mode) -> Self {
139        let mut me = Self {
140            lexer: Lexer::new(input),
141            mode,
142            stack: Vec::new(),
143            macros: MacroNamespace::new(),
144            expansion_count: 0,
145            max_expand: 1000,
146        };
147        me.load_builtins();
148        me
149    }
150
151    fn load_builtins(&mut self) {
152        let builtins: &[(&str, &str)] = &[
153            // ── Grouping ──
154            ("\\bgroup", "{"),
155            ("\\egroup", "}"),
156
157            // ── Symbols from latex.ltx ──
158            ("\\lq", "`"),
159            ("\\rq", "'"),
160            // \lbrack and \rbrack are in the symbol table directly
161            ("\\aa", "\\r a"),
162            ("\\AA", "\\r A"),
163
164            // ── Active characters ──
165            ("~", "\\nobreakspace"),
166
167            // ── Phantoms ──
168            ("\\hphantom", "\\smash{\\phantom{#1}}"),
169
170            // ── Negated symbols ──
171            ("\\not", "\\html@mathml{\\mathrel{\\mathrlap\\@not}\\nobreak}{\\char\"338}"),
172            ("\\neq", "\\html@mathml{\\mathrel{\\not=}}{\\mathrel{\\char`≠}}"),
173            ("\\ne", "\\neq"),
174            ("\u{2260}", "\\neq"),
175            ("\\notin", "\\html@mathml{\\mathrel{{\\in}\\mathllap{/\\mskip1mu}}}{\\mathrel{\\char`∉}}"),
176            ("\u{2209}", "\\notin"),
177            ("\\notni", "\\html@mathml{\\not\\ni}{\\mathrel{\\char`\u{220C}}}"),
178            ("\u{220C}", "\\notni"),
179            // \le and \ge are in the symbol table directly, not macros
180
181            // ── amsmath iff/implies ──
182            ("\\iff", "\\DOTSB\\;\\Longleftrightarrow\\;"),
183            ("\\implies", "\\DOTSB\\;\\Longrightarrow\\;"),
184            ("\\impliedby", "\\DOTSB\\;\\Longleftarrow\\;"),
185
186            // ── Italic Greek capitals ──
187            ("\\varGamma", "\\mathit{\\Gamma}"),
188            ("\\varDelta", "\\mathit{\\Delta}"),
189            ("\\varTheta", "\\mathit{\\Theta}"),
190            ("\\varLambda", "\\mathit{\\Lambda}"),
191            ("\\varXi", "\\mathit{\\Xi}"),
192            ("\\varPi", "\\mathit{\\Pi}"),
193            ("\\varSigma", "\\mathit{\\Sigma}"),
194            ("\\varUpsilon", "\\mathit{\\Upsilon}"),
195            ("\\varPhi", "\\mathit{\\Phi}"),
196            ("\\varPsi", "\\mathit{\\Psi}"),
197            ("\\varOmega", "\\mathit{\\Omega}"),
198
199            // ── Spacing (mode-aware via \TextOrMath) ──
200            ("\\,", "\\TextOrMath{\\kern{.1667em}}{\\mskip{3mu}}"),
201            ("\\thinspace", "\\,"),
202            ("\\>", "\\mskip{4mu}"),
203            ("\\:", "\\TextOrMath{\\kern{.2222em}}{\\mskip{4mu}}"),
204            ("\\medspace", "\\:"),
205            ("\\;", "\\TextOrMath{\\kern{.2777em}}{\\mskip{5mu}}"),
206            ("\\thickspace", "\\;"),
207            ("\\!", "\\TextOrMath{\\kern{-.1667em}}{\\mskip{-3mu}}"),
208            ("\\negthinspace", "\\!"),
209            ("\\negmedspace", "\\TextOrMath{\\kern{-.2222em}}{\\mskip{-4mu}}"),
210            ("\\negthickspace", "\\TextOrMath{\\kern{-.2777em}}{\\mskip{-5mu}}"),
211            ("\\enspace", "\\kern.5em "),
212            ("\\enskip", "\\hskip.5em\\relax"),
213            ("\\quad", "\\hskip1em\\relax"),
214            ("\\qquad", "\\hskip2em\\relax"),
215
216            // ── Newline ──
217            ("\\newline", "\\\\\\relax"),
218
219            // ── hspace ──
220            ("\\@hspace", "\\hskip #1\\relax"),
221            ("\\@hspacer", "\\rule{0pt}{0pt}\\hskip #1\\relax"),
222
223            // ── llap / rlap / clap ──
224            ("\\llap", "\\mathllap{\\textrm{#1}}"),
225            ("\\rlap", "\\mathrlap{\\textrm{#1}}"),
226            ("\\clap", "\\mathclap{\\textrm{#1}}"),
227
228            // ── Logos ──
229            ("\\TeX", "\\textrm{\\html@mathml{T\\kern-.1667em\\raisebox{-.5ex}{E}\\kern-.125emX}{TeX}}"),
230            ("\\LaTeX", "\\textrm{\\html@mathml{L\\kern-.36em\\raisebox{0.21em}{\\scriptstyle A}\\kern-.15em\\TeX}{LaTeX}}"),
231            ("\\KaTeX", "\\textrm{\\html@mathml{K\\kern-.17em\\raisebox{0.21em}{\\scriptstyle A}\\kern-.15em\\TeX}{KaTeX}}"),
232
233            // ── imath / jmath ──
234            ("\\imath", "\\html@mathml{\\@imath}{\u{0131}}"),
235            ("\\jmath", "\\html@mathml{\\@jmath}{\u{0237}}"),
236
237            // ── minuso ──
238            ("\\minuso", "\\mathbin{\\html@mathml{{\\mathrlap{\\mathchoice{\\kern{0.145em}}{\\kern{0.145em}}{\\kern{0.1015em}}{\\kern{0.0725em}}\\circ}{-}}}{\\char`\u{29B5}}}"),
239            ("\\clap", "\\mathclap{\\textrm{#1}}"),
240
241            // ── mathstrut / underbar ──
242            ("\\mathstrut", "\\vphantom{(}"),
243            ("\\underbar", "\\underline{\\text{#1}}"),
244
245            // ── Bbbk ──
246            ("\\Bbbk", "\\Bbb{k}"),
247
248            // ── substack ──
249            ("\\substack", "\\begin{subarray}{c}#1\\end{subarray}"),
250
251            // ── boxed ──
252            ("\\boxed", "\\fbox{$\\displaystyle{#1}$}"),
253
254            // ── colon ──
255            ("\\colon", "\\nobreak\\mskip2mu\\mathpunct{}\\mathchoice{\\mkern-3mu}{\\mkern-3mu}{}{}{:}\\mskip6mu\\relax"),
256
257            // ── dots (string-based) ──
258            ("\\dots", "\\cdots"),
259            ("\\cdots", "\\@cdots"),
260            ("\\dotsb", "\\cdots"),
261            ("\\dotsm", "\\cdots"),
262            ("\\dotsi", "\\!\\cdots"),
263            ("\\dotsx", "\\ldots\\,"),
264            ("\\dotsc", "\\ldots"),  // comma list: x,\dotsc,y
265            ("\\dotso", "\\ldots"),  // other
266            ("\\DOTSI", "\\relax"),
267            ("\\DOTSB", "\\relax"),
268            ("\\DOTSX", "\\relax"),
269
270            // ── negated relations / corners (→ symbol table \@xxx) ──
271            ("\\gvertneqq", "\\@gvertneqq"),
272            ("\\lvertneqq", "\\@lvertneqq"),
273            ("\\ngeqq", "\\@ngeqq"),
274            ("\\ngeqslant", "\\@ngeqslant"),
275            ("\\nleqq", "\\@nleqq"),
276            ("\\nleqslant", "\\@nleqslant"),
277            ("\\nshortmid", "\\@nshortmid"),
278            ("\\nshortparallel", "\\@nshortparallel"),
279            ("\\nsubseteqq", "\\@nsubseteqq"),
280            ("\\nsupseteqq", "\\@nsupseteqq"),
281            ("\\ulcorner", "\\@ulcorner"),
282            ("\\urcorner", "\\@urcorner"),
283            ("\\llcorner", "\\@llcorner"),
284            ("\\lrcorner", "\\@lrcorner"),
285            ("\\varsubsetneq", "\\@varsubsetneq"),
286            ("\\varsubsetneqq", "\\@varsubsetneqq"),
287            ("\\varsupsetneq", "\\@varsupsetneq"),
288            ("\\varsupsetneqq", "\\@varsupsetneqq"),
289
290            // ── delimiters / text (compose from existing) ──
291            // Match KaTeX `macros.ts` html@mathml first branch (STIX-style white tortoise brackets).
292            ("\\lBrace", "\\mathopen{\\{\\mkern-3.2mu[}"),
293            ("\\rBrace", "\\mathclose{]\\mkern-3.2mu\\}}"),
294            ("\\llbracket", "\\mathopen{[\\mkern-3.2mu[}"),
295            ("\\rrbracket", "\\mathclose{]\\mkern-3.2mu]}"),
296            ("\\copyright", "\\textcircled{c}"),
297            ("\\textregistered", "\\textcircled{\\scriptsize R}"),
298
299            // ── dddot / ddddot ──
300            ("\\dddot", "{\\overset{\\raisebox{-0.1ex}{\\normalsize ...}}{#1}}"),
301            ("\\ddddot", "{\\overset{\\raisebox{-0.1ex}{\\normalsize ....}}{#1}}"),
302
303            // ── vdots ──
304            ("\\vdots", "{\\varvdots\\rule{0pt}{15pt}}"),
305            ("\u{22ee}", "\\vdots"),
306
307            // ── bmod / pod / pmod / mod ──
308            ("\\bmod", "\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}\\mathbin{\\rm mod}\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}"),
309            ("\\pod", "\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern8mu}{\\mkern8mu}{\\mkern8mu}(#1)"),
310            ("\\pmod", "\\pod{{\\rm mod}\\mkern6mu#1}"),
311            ("\\mod", "\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern12mu}{\\mkern12mu}{\\mkern12mu}{\\rm mod}\\,\\,#1"),
312
313            // ── limsup / liminf / etc ──
314            ("\\limsup", "\\DOTSB\\operatorname*{lim\\,sup}"),
315            ("\\liminf", "\\DOTSB\\operatorname*{lim\\,inf}"),
316            ("\\injlim", "\\DOTSB\\operatorname*{inj\\,lim}"),
317            ("\\projlim", "\\DOTSB\\operatorname*{proj\\,lim}"),
318            ("\\varlimsup", "\\DOTSB\\operatorname*{\\overline{lim}}"),
319            ("\\varliminf", "\\DOTSB\\operatorname*{\\underline{lim}}"),
320            ("\\varinjlim", "\\DOTSB\\operatorname*{\\underrightarrow{lim}}"),
321            ("\\varprojlim", "\\DOTSB\\operatorname*{\\underleftarrow{lim}}"),
322
323            // ── statmath ──
324            ("\\argmin", "\\DOTSB\\operatorname*{arg\\,min}"),
325            ("\\argmax", "\\DOTSB\\operatorname*{arg\\,max}"),
326            ("\\plim", "\\DOTSB\\mathop{\\operatorname{plim}}\\limits"),
327
328            // ── mathtools colon variants ──
329            ("\\ordinarycolon", ":"),
330            ("\\vcentcolon", "\\mathrel{\\mathop\\ordinarycolon}"),
331            ("\\dblcolon", "\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-.9mu}\\vcentcolon}}{\\mathop{\\char\"2237}}"),
332            ("\\coloneqq", "\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char\"2254}}"),
333            ("\\Coloneqq", "\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char\"2237\\char\"3d}}"),
334            ("\\coloneq", "\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char\"3a\\char\"2212}}"),
335            ("\\Coloneq", "\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char\"2237\\char\"2212}}"),
336            ("\\eqqcolon", "\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char\"2255}}"),
337            ("\\Eqqcolon", "\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char\"3d\\char\"2237}}"),
338            ("\\eqcolon", "\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char\"2239}}"),
339            ("\\Eqcolon", "\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char\"2212\\char\"2237}}"),
340            ("\\colonapprox", "\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char\"3a\\char\"2248}}"),
341            ("\\Colonapprox", "\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char\"2237\\char\"2248}}"),
342            ("\\colonsim", "\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char\"3a\\char\"223c}}"),
343            ("\\Colonsim", "\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char\"2237\\char\"223c}}"),
344
345            // ── colonequals alternate names ──
346            ("\\ratio", "\\vcentcolon"),
347            ("\\coloncolon", "\\dblcolon"),
348            ("\\colonequals", "\\coloneqq"),
349            ("\\coloncolonequals", "\\Coloneqq"),
350            ("\\equalscolon", "\\eqqcolon"),
351            ("\\equalscoloncolon", "\\Eqqcolon"),
352            ("\\colonminus", "\\coloneq"),
353            ("\\coloncolonminus", "\\Coloneq"),
354            ("\\minuscolon", "\\eqcolon"),
355            ("\\minuscoloncolon", "\\Eqcolon"),
356            ("\\coloncolonapprox", "\\Colonapprox"),
357            ("\\coloncolonsim", "\\Colonsim"),
358            ("\\simcolon", "\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\vcentcolon}"),
359            ("\\simcoloncolon", "\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\dblcolon}"),
360            ("\\approxcolon", "\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\vcentcolon}"),
361            ("\\approxcoloncolon", "\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\dblcolon}"),
362
363            // ── braket (string-based) ──
364            ("\\bra", "\\mathinner{\\langle{#1}|}"),
365            ("\\ket", "\\mathinner{|{#1}\\rangle}"),
366            ("\\braket", "\\mathinner{\\langle{#1}\\rangle}"),
367            ("\\Braket", "\\bra@ket{\\left\\langle}{\\;\\middle\\vert\\;}{\\;\\middle\\Vert\\;}{\\right\\rangle}"),
368            ("\\Bra", "\\left\\langle#1\\right|"),
369            ("\\Ket", "\\left|#1\\right\\rangle"),
370
371            // ── texvc (MediaWiki) ──
372            ("\\darr", "\\downarrow"),
373            ("\\dArr", "\\Downarrow"),
374            ("\\Darr", "\\Downarrow"),
375            ("\\lang", "\\langle"),
376            ("\\rang", "\\rangle"),
377            ("\\uarr", "\\uparrow"),
378            ("\\uArr", "\\Uparrow"),
379            ("\\Uarr", "\\Uparrow"),
380            ("\\N", "\\mathbb{N}"),
381            ("\\R", "\\mathbb{R}"),
382            ("\\Z", "\\mathbb{Z}"),
383            ("\\alef", "\\aleph"),
384            ("\\alefsym", "\\aleph"),
385            ("\\Alpha", "\\mathrm{A}"),
386            ("\\Beta", "\\mathrm{B}"),
387            ("\\bull", "\\bullet"),
388            ("\\Chi", "\\mathrm{X}"),
389            ("\\clubs", "\\clubsuit"),
390            ("\\cnums", "\\mathbb{C}"),
391            ("\\Complex", "\\mathbb{C}"),
392            ("\\Dagger", "\\ddagger"),
393            ("\\diamonds", "\\diamondsuit"),
394            ("\\empty", "\\emptyset"),
395            ("\\Epsilon", "\\mathrm{E}"),
396            ("\\Eta", "\\mathrm{H}"),
397            ("\\exist", "\\exists"),
398            ("\\harr", "\\leftrightarrow"),
399            ("\\hArr", "\\Leftrightarrow"),
400            ("\\Harr", "\\Leftrightarrow"),
401            ("\\hearts", "\\heartsuit"),
402            ("\\image", "\\Im"),
403            ("\\infin", "\\infty"),
404            ("\\Iota", "\\mathrm{I}"),
405            ("\\isin", "\\in"),
406            ("\\Kappa", "\\mathrm{K}"),
407            ("\\larr", "\\leftarrow"),
408            ("\\lArr", "\\Leftarrow"),
409            ("\\Larr", "\\Leftarrow"),
410            ("\\lrarr", "\\leftrightarrow"),
411            ("\\lrArr", "\\Leftrightarrow"),
412            ("\\Lrarr", "\\Leftrightarrow"),
413            ("\\Mu", "\\mathrm{M}"),
414            ("\\natnums", "\\mathbb{N}"),
415            ("\\Nu", "\\mathrm{N}"),
416            ("\\Omicron", "\\mathrm{O}"),
417            ("\\plusmn", "\\pm"),
418            ("\\rarr", "\\rightarrow"),
419            ("\\rArr", "\\Rightarrow"),
420            ("\\Rarr", "\\Rightarrow"),
421            ("\\real", "\\Re"),
422            ("\\reals", "\\mathbb{R}"),
423            ("\\Reals", "\\mathbb{R}"),
424            ("\\Rho", "\\mathrm{P}"),
425            ("\\sdot", "\\cdot"),
426            ("\\sect", "\\S"),
427            ("\\spades", "\\spadesuit"),
428            ("\\sub", "\\subset"),
429            ("\\sube", "\\subseteq"),
430            ("\\supe", "\\supseteq"),
431            ("\\Tau", "\\mathrm{T}"),
432            ("\\thetasym", "\\vartheta"),
433            ("\\weierp", "\\wp"),
434            ("\\Zeta", "\\mathrm{Z}"),
435
436            // ── Khan Academy color aliases ──
437            ("\\blue", "\\textcolor{##6495ed}{#1}"),
438            ("\\orange", "\\textcolor{##ffa500}{#1}"),
439            ("\\pink", "\\textcolor{##ff00af}{#1}"),
440            ("\\red", "\\textcolor{##df0030}{#1}"),
441            ("\\green", "\\textcolor{##28ae7b}{#1}"),
442            ("\\gray", "\\textcolor{gray}{#1}"),
443            ("\\purple", "\\textcolor{##9d38bd}{#1}"),
444
445            // ── Unicode script letters ──
446            ("\u{212C}", "\\mathscr{B}"),
447            ("\u{2130}", "\\mathscr{E}"),
448            ("\u{2131}", "\\mathscr{F}"),
449            ("\u{210B}", "\\mathscr{H}"),
450            ("\u{2110}", "\\mathscr{I}"),
451            ("\u{2112}", "\\mathscr{L}"),
452            ("\u{2133}", "\\mathscr{M}"),
453            ("\u{211B}", "\\mathscr{R}"),
454            ("\u{212D}", "\\mathfrak{C}"),
455            ("\u{210C}", "\\mathfrak{H}"),
456            ("\u{2128}", "\\mathfrak{Z}"),
457
458            // ── notni ──
459            ("\\notni", "\\html@mathml{\\not\\ni}{\\mathrel{\\char`\u{220C}}}"),
460
461            // ── actuarialangle ──
462            ("\\angln", "{\\angl n}"),
463
464            // ── set/Set (braket notation, simplified) ──
465            ("\\set", "\\bra@set{\\{\\,}{\\mid}{}{\\,\\}}"),
466            ("\\Set", "\\bra@set{\\left\\{\\:}{\\;\\middle\\vert\\;}{\\;\\middle\\Vert\\;}{\\:\\right\\}}"),
467
468            // ── tag ──
469            ("\\tag", "\\@ifstar\\tag@literal\\tag@paren"),
470            ("\\tag@paren", "\\tag@literal{({#1})}"),
471            ("\\tag@literal", "\\gdef\\df@tag{\\text{#1}}"),
472            // ── equation numbering (display math; no-op in parser) ──
473            ("\\nonumber", "\\relax"),
474            ("\\notag", "\\relax"),
475
476            // ── KaTeX mhchem (\\tripledash for \\bond ~ forms) ──
477            (
478                "\\tripledash",
479                "{\\vphantom{-}\\raisebox{2.56mu}{$\\mkern2mu\\tiny\\text{-}\\mkern1mu\\text{-}\\mkern1mu\\text{-}\\mkern2mu$}}",
480            ),
481        ];
482
483        for &(name, expansion) in builtins {
484            self.macros.set(
485                name.to_string(),
486                MacroDefinition::Text(expansion.to_string()),
487            );
488        }
489
490        self.load_function_macros();
491    }
492
493    fn load_function_macros(&mut self) {
494        // \noexpand: mark the next token as non-expandable (only if expandable)
495        self.macros.set(
496            "\\noexpand".to_string(),
497            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
498                let mut tok = me.pop_token();
499                if me.is_expandable(&tok.text) {
500                    tok.noexpand = true;
501                    tok.treat_as_relax = true;
502                }
503                Ok(vec![tok])
504            }),
505        );
506
507        // \@firstoftwo{A}{B} → A
508        // NOTE: consume_args returns tokens in stack order (reversed).
509        // We return them as-is since expand_once does stack.extend(tokens).
510        self.macros.set(
511            "\\@firstoftwo".to_string(),
512            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
513                let args = me.consume_args(2)?;
514                Ok(args.into_iter().next().unwrap())
515            }),
516        );
517
518        // \@secondoftwo{A}{B} → B
519        self.macros.set(
520            "\\@secondoftwo".to_string(),
521            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
522                let args = me.consume_args(2)?;
523                Ok(args.into_iter().nth(1).unwrap())
524            }),
525        );
526
527        // \@ifnextchar{C}{T}{F}: peek; if next non-space == C then T else F
528        self.macros.set(
529            "\\@ifnextchar".to_string(),
530            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
531                let args = me.consume_args(3)?;
532                me.consume_spaces();
533                let next = me.future().text.clone();
534                let char_arg = &args[0];
535                // char_arg is reversed; the "first" char in original order is the last element
536                let char_text = char_arg.first().map_or("", |t| t.text.as_str());
537                if next == char_text {
538                    Ok(args[1].clone())
539                } else {
540                    Ok(args[2].clone())
541                }
542            }),
543        );
544
545        // \@ifstar{with-star}{without-star}: if next is * → consume * and use first arg
546        self.macros.set(
547            "\\@ifstar".to_string(),
548            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
549                let args = me.consume_args(2)?;
550                let next = me.future().text.clone();
551                if next == "*" {
552                    me.pop_token();
553                    Ok(args[0].clone())
554                } else {
555                    Ok(args[1].clone())
556                }
557            }),
558        );
559
560        // \TextOrMath{text-branch}{math-branch}: choose based on mode
561        self.macros.set(
562            "\\TextOrMath".to_string(),
563            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
564                let args = me.consume_args(2)?;
565                if me.mode == Mode::Text {
566                    Ok(args[0].clone())
567                } else {
568                    Ok(args[1].clone())
569                }
570            }),
571        );
572
573        // \html@mathml is registered as a function in htmlmathml.rs
574
575        // \newcommand{\name}[nargs]{body}
576        self.macros.set(
577            "\\newcommand".to_string(),
578            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
579                handle_newcommand(me, false, true)
580            }),
581        );
582
583        // \renewcommand{\name}[nargs]{body}
584        self.macros.set(
585            "\\renewcommand".to_string(),
586            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
587                handle_newcommand(me, true, false)
588            }),
589        );
590
591        // \providecommand{\name}[nargs]{body}
592        self.macros.set(
593            "\\providecommand".to_string(),
594            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
595                handle_newcommand(me, true, true)
596            }),
597        );
598
599        // \char: parse decimal/octal/hex/backtick number → \@char{N}
600        self.macros.set(
601            "\\char".to_string(),
602            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
603                let mut tok = me.pop_token();
604                let mut number: i64;
605                let base: Option<u32>;
606
607                if tok.text == "'" {
608                    base = Some(8);
609                    tok = me.pop_token();
610                } else if tok.text == "\"" {
611                    base = Some(16);
612                    tok = me.pop_token();
613                } else if tok.text == "`" {
614                    tok = me.pop_token();
615                    if tok.text.starts_with('\\') {
616                        number = tok.text.chars().nth(1).map_or(0, |c| c as i64);
617                    } else {
618                        number = tok.text.chars().next().map_or(0, |c| c as i64);
619                    }
620                    // Build \@char{N} tokens in reverse (stack order)
621                    let s = number.to_string();
622                    let loc = tok.loc.clone();
623                    let mut result = vec![Token::new("}", loc.start, loc.end)];
624                    for ch in s.chars().rev() {
625                        result.push(Token::new(ch.to_string(), loc.start, loc.end));
626                    }
627                    result.push(Token::new("{", loc.start, loc.end));
628                    result.push(Token::new("\\@char", loc.start, loc.end));
629                    return Ok(result);
630                } else {
631                    base = Some(10);
632                }
633
634                if let Some(b) = base {
635                    number = i64::from_str_radix(&tok.text, b).unwrap_or(0);
636                    loop {
637                        let next = me.future().text.clone();
638                        if let Ok(d) = i64::from_str_radix(&next, b) {
639                            me.pop_token();
640                            number = number * (b as i64) + d;
641                        } else {
642                            break;
643                        }
644                    }
645                } else {
646                    number = 0;
647                }
648
649                let s = number.to_string();
650                let loc = tok.loc.clone();
651                let mut result = vec![Token::new("}", loc.start, loc.end)];
652                for ch in s.chars().rev() {
653                    result.push(Token::new(ch.to_string(), loc.start, loc.end));
654                }
655                result.push(Token::new("{", loc.start, loc.end));
656                result.push(Token::new("\\@char", loc.start, loc.end));
657                Ok(result)
658            }),
659        );
660
661        // \operatorname: \@ifstar\operatornamewithlimits\operatorname@
662        self.macros.set(
663            "\\operatorname".to_string(),
664            MacroDefinition::Text(
665                "\\@ifstar\\operatornamewithlimits\\operatorname@".to_string(),
666            ),
667        );
668
669        // \message{...}: consume argument and discard (no-op)
670        self.macros.set(
671            "\\message".to_string(),
672            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
673                let _args = me.consume_args(1)?;
674                Ok(vec![])
675            }),
676        );
677
678        // \errmessage{...}: consume argument and discard (no-op)
679        self.macros.set(
680            "\\errmessage".to_string(),
681            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
682                let _args = me.consume_args(1)?;
683                Ok(vec![])
684            }),
685        );
686
687        // KaTeX HTML extensions: no-op (only render content, no HTML attributes).
688        // Not standard LaTeX; for compatibility we parse and expand to second argument only.
689        for name in &["\\htmlClass", "\\htmlData", "\\htmlId", "\\htmlStyle"] {
690            let name = (*name).to_string();
691            self.macros.set(
692                name.clone(),
693                MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
694                    let args = me.consume_args(2)?;
695                    let content = args[1].iter().cloned().rev().collect::<Vec<_>>();
696                    Ok(content)
697                }),
698            );
699        }
700
701        // \bra@ket: like \bra@set but replaces ALL | at depth 0 (for \Braket)
702        self.macros.set(
703            "\\bra@ket".to_string(),
704            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
705                let args = me.consume_args(4)?;
706                let left = args[0].clone();
707                let middle = args[1].clone();
708                let middle_double = args[2].clone();
709                let right = args[3].clone();
710
711                let content = me.consume_args(1)?;
712                let content = content.into_iter().next().unwrap();
713
714                // Convert stack-order (reversed) to logical order, replace all | at depth 0,
715                // then reverse back to stack order.
716                let logical: Vec<Token> = content.into_iter().rev().collect();
717                let mut new_logical: Vec<Token> = Vec::new();
718                let mut depth: i32 = 0;
719                let mut i = 0;
720                while i < logical.len() {
721                    let t = &logical[i];
722                    if t.text == "{" {
723                        depth += 1;
724                        new_logical.push(t.clone());
725                    } else if t.text == "}" {
726                        depth -= 1;
727                        new_logical.push(t.clone());
728                    } else if depth == 0 && t.text == "|" {
729                        // Check for || (double pipe) → middleDouble
730                        if !middle_double.is_empty()
731                            && i + 1 < logical.len()
732                            && logical[i + 1].text == "|"
733                        {
734                            // middle_double is in stack/reversed order; reverse to logical order
735                            new_logical.extend(middle_double.iter().rev().cloned());
736                            i += 2;
737                            continue;
738                        }
739                        // middle is in stack/reversed order; reverse to logical order
740                        new_logical.extend(middle.iter().rev().cloned());
741                    } else {
742                        new_logical.push(t.clone());
743                    }
744                    i += 1;
745                }
746
747                // Reverse back to stack order
748                let content_rev: Vec<Token> = new_logical.into_iter().rev().collect();
749
750                // Build: right + content + left (reversed for stack)
751                let mut to_expand = Vec::new();
752                to_expand.extend(right);
753                to_expand.extend(content_rev);
754                to_expand.extend(left);
755
756                me.begin_group();
757                let expanded = me.expand_tokens(to_expand)?;
758                me.end_group();
759
760                Ok(expanded)
761            }),
762        );
763
764        // \bra@set: braket set notation helper
765        // Only replaces the FIRST | with middle tokens (one-shot), matching KaTeX
766        self.macros.set(
767            "\\bra@set".to_string(),
768            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
769                let args = me.consume_args(4)?;
770                let left = args[0].clone();
771                let middle = args[1].clone();
772                let middle_double = args[2].clone();
773                let right = args[3].clone();
774
775                let content = me.consume_args(1)?;
776                let mut content = content.into_iter().next().unwrap();
777
778                // Scan content and replace only the first | at depth 0
779                // Content tokens are in reversed order (stack), so iterate from end
780                let mut depth: i32 = 0;
781                let mut _first_pipe_idx: Option<usize> = None;
782                // Tokens are reversed (last token first in vec), scan in logical order
783                for i in (0..content.len()).rev() {
784                    let t = &content[i];
785                    if t.text == "{" { depth += 1; }
786                    else if t.text == "}" { depth -= 1; }
787                    else if depth == 0 && t.text == "|" {
788                        // Check for || (double pipe) → middleDouble
789                        if !middle_double.is_empty() && i > 0 && content[i - 1].text == "|" {
790                            _first_pipe_idx = Some(i);
791                            // Replace || with middleDouble
792                            content.remove(i);
793                            content.remove(i - 1);
794                            let insert_at = if i >= 2 { i - 1 } else { 0 };
795                            for (j, tok) in middle_double.iter().enumerate() {
796                                content.insert(insert_at + j, tok.clone());
797                            }
798                            break;
799                        }
800                        _first_pipe_idx = Some(i);
801                        content.remove(i);
802                        for (j, tok) in middle.iter().enumerate() {
803                            content.insert(i + j, tok.clone());
804                        }
805                        break;
806                    }
807                }
808
809                // Build: right + content + left (reversed for stack)
810                let mut to_expand = Vec::new();
811                to_expand.extend(right);
812                to_expand.extend(content);
813                to_expand.extend(left);
814
815                me.begin_group();
816                let expanded = me.expand_tokens(to_expand)?;
817                me.end_group();
818
819                Ok(expanded)
820            }),
821        );
822
823        // \\ce / \\pu: KaTeX mhchem 3.3.0 (Rust port in `crate::mhchem`)
824        self.macros.set(
825            "\\ce".to_string(),
826            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
827                let args = me.consume_args(1)?;
828                let s = crate::mhchem::mhchem_arg_tokens_to_string(&args[0]);
829                let tex = crate::mhchem::chem_parse_str(&s, "ce")
830                    .map_err(|e| ParseError::msg(format!("\\ce: {e}")))?;
831                Ok(lex_string_to_stack_tokens(&tex))
832            }),
833        );
834        self.macros.set(
835            "\\pu".to_string(),
836            MacroDefinition::Function(|me: &mut MacroExpander| -> ParseResult<Vec<Token>> {
837                let args = me.consume_args(1)?;
838                let s = crate::mhchem::mhchem_arg_tokens_to_string(&args[0]);
839                let tex = crate::mhchem::chem_parse_str(&s, "pu")
840                    .map_err(|e| ParseError::msg(format!("\\pu: {e}")))?;
841                Ok(lex_string_to_stack_tokens(&tex))
842            }),
843        );
844    }
845
846    pub fn set_macro(&mut self, name: String, def: MacroDefinition) {
847        self.macros.set(name, def);
848    }
849
850    pub fn set_macro_global(&mut self, name: String, def: MacroDefinition) {
851        self.macros.set_global(name, def);
852    }
853
854    pub fn set_text_macro(&mut self, name: &str, text: &str) {
855        self.macros.set(
856            name.to_string(),
857            MacroDefinition::Text(text.to_string()),
858        );
859    }
860
861    pub fn get_macro(&self, name: &str) -> Option<&MacroDefinition> {
862        self.macros.get(name)
863    }
864
865    /// Expand a list of tokens fully (for \edef/\xdef).
866    pub fn expand_tokens(&mut self, tokens: Vec<Token>) -> ParseResult<Vec<Token>> {
867        let saved_stack = std::mem::take(&mut self.stack);
868        self.stack = tokens;
869
870        let mut result = Vec::new();
871        loop {
872            if self.stack.is_empty() {
873                break;
874            }
875            let expanded = self.expand_once(false)?;
876            if !expanded {
877                if let Some(tok) = self.stack.pop() {
878                    if tok.is_eof() {
879                        break;
880                    }
881                    result.push(tok);
882                }
883            }
884        }
885
886        self.stack = saved_stack;
887        result.reverse();
888        Ok(result)
889    }
890
891    pub fn switch_mode(&mut self, new_mode: Mode) {
892        self.mode = new_mode;
893    }
894
895    pub fn begin_group(&mut self) {
896        self.macros.begin_group();
897    }
898
899    pub fn end_group(&mut self) {
900        self.macros.end_group();
901    }
902
903    pub fn end_groups(&mut self) {
904        self.macros.end_groups();
905    }
906
907    /// Returns the topmost token on the stack, without expanding it.
908    pub fn future(&mut self) -> &Token {
909        if self.stack.is_empty() {
910            let tok = self.lexer.lex();
911            self.stack.push(tok);
912        }
913        self.stack.last().unwrap()
914    }
915
916    /// Remove and return the next unexpanded token.
917    pub fn pop_token(&mut self) -> Token {
918        self.future();
919        self.stack.pop().unwrap()
920    }
921
922    /// Modify the top token's text on the stack (for \global prefix handling).
923    pub fn set_top_text(&mut self, text: String) {
924        self.future();
925        if let Some(tok) = self.stack.last_mut() {
926            tok.text = text;
927        }
928    }
929
930    /// Push a token onto the stack.
931    pub fn push_token(&mut self, token: Token) {
932        self.stack.push(token);
933    }
934
935    /// Push multiple tokens onto the stack.
936    pub fn push_tokens(&mut self, tokens: Vec<Token>) {
937        self.stack.extend(tokens);
938    }
939
940    /// Consume all following space tokens, without expansion.
941    pub fn consume_spaces(&mut self) {
942        loop {
943            let is_space = self.future().text == " ";
944            if is_space {
945                self.stack.pop();
946            } else {
947                break;
948            }
949        }
950    }
951
952    /// Expand the next token once if possible.
953    /// Returns Ok(true) if expanded, Ok(false) if not expandable.
954    fn expand_once(&mut self, expandable_only: bool) -> ParseResult<bool> {
955        let top_token = self.pop_token();
956        let name = &top_token.text;
957
958        if top_token.noexpand {
959            self.push_token(top_token);
960            return Ok(false);
961        }
962
963        // Check for function-based macro first — always expandable
964        if let Some(MacroDefinition::Function(handler)) = self.macros.get(name).cloned() {
965            self.count_expansion(1)?;
966            let tokens = handler(self)?;
967            self.stack.extend(tokens);
968            return Ok(true);
969        }
970
971        let expansion = self.get_expansion(name);
972        match expansion {
973            None => {
974                if expandable_only && name.starts_with('\\') && !self.is_defined(name) {
975                    return Err(ParseError::new(
976                        format!("Undefined control sequence: {}", name),
977                        Some(&top_token),
978                    ));
979                }
980                self.push_token(top_token);
981                Ok(false)
982            }
983            Some(exp) if expandable_only && exp.unexpandable => {
984                self.push_token(top_token);
985                Ok(false)
986            }
987            Some(exp) => {
988                self.count_expansion(1)?;
989                let mut tokens = exp.tokens;
990                if exp.num_args > 0 {
991                    let args = self.consume_args(exp.num_args)?;
992                    tokens = self.substitute_args(tokens, &args);
993                }
994                self.stack.extend(tokens);
995                Ok(true)
996            }
997        }
998    }
999
1000    fn substitute_args(&self, mut tokens: Vec<Token>, args: &[Vec<Token>]) -> Vec<Token> {
1001        let mut i = tokens.len();
1002        while i > 0 {
1003            i -= 1;
1004            if tokens[i].text == "#" && i > 0 {
1005                let next = &tokens[i - 1];
1006                if next.text == "#" {
1007                    tokens.remove(i);
1008                    i -= 1;
1009                } else if let Ok(n) = next.text.parse::<usize>() {
1010                    if n >= 1 && n <= args.len() {
1011                        tokens.remove(i);
1012                        tokens.remove(i - 1);
1013                        let arg_tokens = &args[n - 1];
1014                        for (j, t) in arg_tokens.iter().enumerate() {
1015                            tokens.insert(i - 1 + j, t.clone());
1016                        }
1017                        i = i.saturating_sub(1);
1018                    }
1019                }
1020            }
1021        }
1022        tokens
1023    }
1024
1025    fn get_expansion(&self, name: &str) -> Option<MacroExpansion> {
1026        let def = self.macros.get(name)?;
1027
1028        if name.len() == 1 {
1029            let ch = name.chars().next().unwrap();
1030            let catcode = self.lexer_catcode(ch);
1031            if catcode != 0 && catcode != 13 {
1032                return None;
1033            }
1034        }
1035
1036        match def {
1037            MacroDefinition::Text(text) => {
1038                let mut num_args = 0;
1039                let stripped = text.replace("##", "");
1040                while stripped.contains(&format!("#{}", num_args + 1)) {
1041                    num_args += 1;
1042                }
1043                let mut body_lexer = Lexer::new(text);
1044                let mut tokens = Vec::new();
1045                loop {
1046                    let tok = body_lexer.lex();
1047                    if tok.is_eof() {
1048                        break;
1049                    }
1050                    tokens.push(tok);
1051                }
1052                tokens.reverse();
1053                Some(MacroExpansion {
1054                    tokens,
1055                    num_args,
1056                    unexpandable: false,
1057                })
1058            }
1059            MacroDefinition::Tokens { tokens, num_args } => Some(MacroExpansion {
1060                tokens: tokens.clone(),
1061                num_args: *num_args,
1062                unexpandable: false,
1063            }),
1064            MacroDefinition::Function(_) => {
1065                // Signal that this is a function macro; handled in expand_once
1066                Some(MacroExpansion {
1067                    tokens: vec![],
1068                    num_args: 0,
1069                    unexpandable: false,
1070                })
1071            }
1072        }
1073    }
1074
1075    fn lexer_catcode(&self, ch: char) -> u8 {
1076        self.lexer.get_catcode(ch)
1077    }
1078
1079    fn count_expansion(&mut self, amount: usize) -> ParseResult<()> {
1080        self.expansion_count += amount;
1081        if self.expansion_count > self.max_expand {
1082            Err(ParseError::msg(
1083                "Too many expansions: infinite loop or need to increase maxExpand setting",
1084            ))
1085        } else {
1086            Ok(())
1087        }
1088    }
1089
1090    /// Recursively expand the next token until a non-expandable token is found.
1091    pub fn expand_next_token(&mut self) -> ParseResult<Token> {
1092        loop {
1093            let expanded = self.expand_once(false)?;
1094            if !expanded {
1095                let mut token = self.stack.pop().unwrap();
1096                if token.treat_as_relax {
1097                    token.text = "\\relax".to_string();
1098                }
1099                return Ok(token);
1100            }
1101        }
1102    }
1103
1104    /// Consume a single argument from the token stream.
1105    pub fn consume_arg(&mut self, delims: Option<&[&str]>) -> ParseResult<ConsumedArg> {
1106        let is_delimited = delims.is_some_and(|d| !d.is_empty());
1107        if !is_delimited {
1108            self.consume_spaces();
1109        }
1110
1111        let start = self.future().clone();
1112        let mut tokens = Vec::new();
1113        let mut depth: i32 = 0;
1114        let mut end_tok;
1115
1116        loop {
1117            let tok = self.pop_token();
1118            end_tok = tok.clone();
1119            tokens.push(tok.clone());
1120
1121            if tok.text == "{" {
1122                depth += 1;
1123            } else if tok.text == "}" {
1124                depth -= 1;
1125                if depth == -1 {
1126                    return Err(ParseError::new("Extra }", Some(&tok)));
1127                }
1128            } else if tok.is_eof() {
1129                return Err(ParseError::new(
1130                    "Unexpected end of input in a macro argument",
1131                    Some(&tok),
1132                ));
1133            }
1134
1135            if depth == 0 && !is_delimited {
1136                break;
1137            }
1138
1139            if let Some(delims) = delims {
1140                if is_delimited && depth == 0 {
1141                    if let Some(last) = delims.last() {
1142                        if tok.text == *last {
1143                            tokens.pop();
1144                            break;
1145                        }
1146                    }
1147                }
1148            }
1149        }
1150
1151        if start.text == "{" && tokens.last().is_some_and(|t| t.text == "}") {
1152            tokens.pop();
1153            tokens.remove(0);
1154        }
1155
1156        tokens.reverse();
1157
1158        Ok(ConsumedArg {
1159            tokens,
1160            start,
1161            end: end_tok,
1162        })
1163    }
1164
1165    /// Consume N arguments.
1166    fn consume_args(&mut self, num_args: usize) -> ParseResult<Vec<Vec<Token>>> {
1167        let mut args = Vec::with_capacity(num_args);
1168        for _ in 0..num_args {
1169            let arg = self.consume_arg(None)?;
1170            args.push(arg.tokens);
1171        }
1172        Ok(args)
1173    }
1174
1175    /// Scan a function argument (optional or mandatory).
1176    /// Pushes an EOF token to mark the end, then pushes the argument tokens.
1177    pub fn scan_argument(&mut self, is_optional: bool) -> ParseResult<Option<Token>> {
1178        if is_optional {
1179            self.consume_spaces();
1180            if self.future().text != "[" {
1181                return Ok(None);
1182            }
1183            let start = self.pop_token();
1184            let arg = self.consume_arg(Some(&["]"]))?;
1185            let end = &arg.end;
1186            let end_loc = end.loc.clone();
1187
1188            self.push_token(Token::new("EOF", end_loc.start, end_loc.end));
1189            self.push_tokens(arg.tokens);
1190
1191            let result = Token {
1192                text: String::new(),
1193                loc: SourceLocation::range(&start.loc, &end_loc),
1194                noexpand: false,
1195                treat_as_relax: false,
1196            };
1197            Ok(Some(result))
1198        } else {
1199            let arg = self.consume_arg(None)?;
1200            let end_loc = arg.end.loc.clone();
1201
1202            self.push_token(Token::new("EOF", end_loc.start, end_loc.end));
1203            self.push_tokens(arg.tokens);
1204
1205            let result = Token {
1206                text: String::new(),
1207                loc: SourceLocation::range(&arg.start.loc, &end_loc),
1208                noexpand: false,
1209                treat_as_relax: false,
1210            };
1211            Ok(Some(result))
1212        }
1213    }
1214
1215    /// Check if a command name is currently defined.
1216    pub fn is_defined(&self, name: &str) -> bool {
1217        self.macros.has(name)
1218            || FUNCTIONS.contains_key(name)
1219            || is_known_symbol(name)
1220            || IMPLICIT_COMMANDS.contains(&name)
1221    }
1222
1223    /// Check if a command is expandable.
1224    pub fn is_expandable(&self, name: &str) -> bool {
1225        if let Some(_def) = self.macros.get(name) {
1226            return true;
1227        }
1228        if let Some(func) = FUNCTIONS.get(name) {
1229            return !func.primitive;
1230        }
1231        false
1232    }
1233}
1234
1235pub struct ConsumedArg {
1236    pub tokens: Vec<Token>,
1237    pub start: Token,
1238    pub end: Token,
1239}
1240
1241fn handle_newcommand(
1242    me: &mut MacroExpander,
1243    exists_ok: bool,
1244    nonexists_ok: bool,
1245) -> ParseResult<Vec<Token>> {
1246    let name_arg = me.consume_arg(None)?;
1247    // name_arg.tokens is reversed (stack order); last element = first token in original
1248    let name = name_arg.tokens.last().map_or_else(String::new, |t| t.text.clone());
1249
1250    let exists = me.is_defined(&name);
1251    if exists && !exists_ok {
1252        return Err(ParseError::msg(format!(
1253            "\\newcommand{{{}}} attempting to redefine {}; use \\renewcommand",
1254            name, name
1255        )));
1256    }
1257    if !exists && !nonexists_ok {
1258        return Err(ParseError::msg(format!(
1259            "\\renewcommand{{{}}} when command {} does not yet exist; use \\newcommand",
1260            name, name
1261        )));
1262    }
1263
1264    me.consume_spaces();
1265    let mut num_args = 0usize;
1266    if me.future().text == "[" {
1267        me.pop_token();
1268        let narg_tok = me.pop_token();
1269        num_args = narg_tok.text.parse().unwrap_or(0);
1270        let close = me.pop_token();
1271        if close.text != "]" {
1272            return Err(ParseError::msg("Expected ] in \\newcommand"));
1273        }
1274    }
1275
1276    let body_arg = me.consume_arg(None)?;
1277    let tokens = body_arg.tokens;
1278
1279    me.set_macro(name, MacroDefinition::Tokens { tokens, num_args });
1280    Ok(vec![])
1281}
1282
1283fn is_known_symbol(name: &str) -> bool {
1284    use ratex_font::symbols;
1285    symbols::get_symbol(name, symbols::Mode::Math).is_some()
1286        || symbols::get_symbol(name, symbols::Mode::Text).is_some()
1287}