Skip to main content

tex_engine/commands/
methods.rs

1/*! Utility methods for [`TeXCommand`]s.
2*/
3
4use crate::commands::primitives::{PrimitiveIdentifier, PRIMITIVES};
5use crate::commands::{
6    CharOrPrimitive, Macro, MacroSignature, PrimitiveCommand, ResolvedToken, TeXCommand,
7};
8use crate::engine::filesystem::FileSystem;
9use crate::engine::fontsystem::{Font, FontSystem};
10use crate::engine::gullet::hvalign::{AlignColumn, AlignData};
11use crate::engine::gullet::Gullet;
12use crate::engine::mouth::Mouth;
13use crate::engine::state::{GroupType, State};
14use crate::engine::stomach::TeXMode;
15use crate::engine::stomach::{Stomach, StomachData};
16use crate::engine::{EngineAux, EngineReferences, EngineTypes};
17use crate::expand_loop;
18use crate::tex::catcodes::CommandCode;
19use crate::tex::nodes::boxes::{BoxType, HBoxInfo, TeXBox, ToOrSpread, VBoxInfo};
20use crate::tex::nodes::horizontal::{HNode, HorizontalNodeListType};
21use crate::tex::nodes::math::{
22    Delimiter, MathAtom, MathClass, MathKernel, MathNode, MathNucleus, UnresolvedMathFontStyle,
23};
24use crate::tex::nodes::vertical::{VNode, VerticalNodeListType};
25use crate::tex::nodes::NodeTrait;
26use crate::tex::nodes::{
27    BoxTarget, LeaderBody, LeaderSkip, LeaderType, Leaders, ListTarget, NodeList,
28};
29use crate::tex::numerics::Skip;
30use crate::tex::tokens::control_sequences::CSHandler;
31use crate::tex::tokens::token_lists::TokenList;
32use crate::tex::tokens::Token;
33use crate::utils::errors::{TeXError, TeXResult};
34use crate::utils::HMap;
35
36pub(crate) struct MacroParser<T: Token> {
37    arity: u8,
38    params: shared_vector::Vector<T>,
39    inparam: bool,
40    ends_with_brace: Option<T>,
41    exp: shared_vector::Vector<T>,
42}
43impl<T: Token> MacroParser<T> {
44    pub(crate) fn new() -> Self {
45        Self {
46            arity: 0,
47            params: shared_vector::Vector::new(),
48            inparam: false,
49            ends_with_brace: None,
50            exp: shared_vector::Vector::new(),
51        }
52    }
53    pub(crate) fn do_signature_token<ET: EngineTypes<Token = T>>(
54        &mut self,
55        t: T,
56    ) -> TeXResult<Option<()>, ET> {
57        match t.command_code() {
58            CommandCode::BeginGroup => {
59                if self.inparam {
60                    self.inparam = false;
61                    self.params.push(t.clone());
62                    self.ends_with_brace = Some(t);
63                }
64                return Ok(Some(()));
65            }
66            CommandCode::Parameter if self.inparam => {
67                self.inparam = false;
68                self.params.push(t);
69            }
70            CommandCode::Parameter => {
71                self.inparam = true;
72            }
73            _ if self.inparam => {
74                self.inparam = false;
75                match t.char_value() {
76                    Some(c) => match c.try_into() {
77                        Ok(u) if u > 48 && u == 49 + self.arity => {
78                            self.params.push(T::argument_marker(self.arity))
79                        }
80                        _ => {
81                            return Err(TeXError::General(
82                                "Invalid argument number\nTODO: Better error message".to_string(),
83                            ))
84                        }
85                    },
86                    None => {
87                        return Err(TeXError::General(
88                            "Missing argument number\nTODO: Better error message".to_string(),
89                        ))
90                    }
91                }
92                self.arity += 1;
93            }
94            _ => self.params.push(t),
95        }
96        Ok(None)
97    }
98
99    pub(crate) fn do_expansion_token<ET: EngineTypes>(&mut self, t: T) -> TeXResult<(), ET> {
100        match t.command_code() {
101            CommandCode::Parameter if self.inparam => {
102                self.inparam = false;
103                self.exp.push(t);
104            }
105            CommandCode::Parameter => self.inparam = true,
106            _ if self.inparam => {
107                self.inparam = false;
108                match t.char_value() {
109                    Some(c) => match c.try_into() {
110                        Ok(u) if u > 48 && u - 49 < self.arity => {
111                            self.exp.push(T::argument_marker(u - 49))
112                        }
113                        _ => {
114                            return Err(TeXError::General(
115                                "Invalid argument number\nTODO: Better error message".to_string(),
116                            ))
117                        }
118                    },
119                    None => {
120                        return Err(TeXError::General(
121                            "Missing argument number\nTODO: Better error message".to_string(),
122                        ))
123                    }
124                }
125            }
126            _ => self.exp.push(t),
127        }
128        Ok(())
129    }
130
131    pub(crate) fn close(mut self, long: bool, outer: bool, protected: bool) -> Macro<T> {
132        if let Some(t) = self.ends_with_brace {
133            self.exp.push(t);
134        }
135        Macro {
136            long,
137            outer,
138            protected,
139            expansion: self.exp.into(),
140            signature: MacroSignature {
141                arity: self.arity,
142                params: self.params.into(),
143            },
144        }
145    }
146}
147
148pub(in crate::commands) fn modify_int_register<
149    ET: EngineTypes,
150    O: FnOnce(ET::Int, &mut EngineReferences<ET>) -> TeXResult<ET::Int, ET>,
151>(
152    engine: &mut EngineReferences<ET>,
153    idx: usize,
154    globally: bool,
155    op: O,
156) -> TeXResult<(), ET> {
157    engine.read_keyword(b"by")?;
158    let old = engine.state.get_int_register(idx);
159    let new = op(old, engine)?;
160    engine
161        .state
162        .set_int_register(engine.aux, idx, new, globally);
163    Ok(())
164}
165
166pub(in crate::commands) fn modify_primitive_int<
167    ET: EngineTypes,
168    O: FnOnce(ET::Int, &mut EngineReferences<ET>) -> TeXResult<ET::Int, ET>,
169>(
170    engine: &mut EngineReferences<ET>,
171    name: PrimitiveIdentifier,
172    globally: bool,
173    op: O,
174) -> TeXResult<(), ET> {
175    engine.read_keyword(b"by")?;
176    let old = engine.state.get_primitive_int(name);
177    let new = op(old, engine)?;
178    engine
179        .state
180        .set_primitive_int(engine.aux, name, new, globally);
181    Ok(())
182}
183
184pub(in crate::commands) fn modify_dim_register<
185    ET: EngineTypes,
186    O: FnOnce(ET::Dim, &mut EngineReferences<ET>) -> TeXResult<ET::Dim, ET>,
187>(
188    engine: &mut EngineReferences<ET>,
189    idx: usize,
190    globally: bool,
191    op: O,
192) -> TeXResult<(), ET> {
193    engine.read_keyword(b"by")?;
194    let old = engine.state.get_dim_register(idx);
195    let new = op(old, engine)?;
196    engine
197        .state
198        .set_dim_register(engine.aux, idx, new, globally);
199    Ok(())
200}
201
202pub(in crate::commands) fn modify_primitive_dim<
203    ET: EngineTypes,
204    O: FnOnce(ET::Dim, &mut EngineReferences<ET>) -> TeXResult<ET::Dim, ET>,
205>(
206    engine: &mut EngineReferences<ET>,
207    name: PrimitiveIdentifier,
208    globally: bool,
209    op: O,
210) -> TeXResult<(), ET> {
211    engine.read_keyword(b"by")?;
212    let old = engine.state.get_primitive_dim(name);
213    let new = op(old, engine)?;
214    engine
215        .state
216        .set_primitive_dim(engine.aux, name, new, globally);
217    Ok(())
218}
219
220pub(in crate::commands) fn modify_skip_register<
221    ET: EngineTypes,
222    O: FnOnce(Skip<ET::Dim>, &mut EngineReferences<ET>) -> TeXResult<Skip<ET::Dim>, ET>,
223>(
224    engine: &mut EngineReferences<ET>,
225    idx: usize,
226    globally: bool,
227    op: O,
228) -> TeXResult<(), ET> {
229    engine.read_keyword(b"by")?;
230    let old = engine.state.get_skip_register(idx);
231    let new = op(old, engine)?;
232    engine
233        .state
234        .set_skip_register(engine.aux, idx, new, globally);
235    Ok(())
236}
237
238pub(in crate::commands) fn modify_primitive_skip<
239    ET: EngineTypes,
240    O: FnOnce(Skip<ET::Dim>, &mut EngineReferences<ET>) -> TeXResult<Skip<ET::Dim>, ET>,
241>(
242    engine: &mut EngineReferences<ET>,
243    name: PrimitiveIdentifier,
244    globally: bool,
245    op: O,
246) -> TeXResult<(), ET> {
247    engine.read_keyword(b"by")?;
248    let old = engine.state.get_primitive_skip(name);
249    let new = op(old, engine)?;
250    engine
251        .state
252        .set_primitive_skip(engine.aux, name, new, globally);
253    Ok(())
254}
255
256pub(in crate::commands) fn do_box_start<ET: EngineTypes>(
257    engine: &mut EngineReferences<ET>,
258    tp: GroupType,
259    every: PrimitiveIdentifier,
260    tk: &ET::Token,
261) -> TeXResult<ToOrSpread<ET::Dim>, ET> {
262    let scaled = match engine.read_keywords(&[b"to", b"spread"])? {
263        Some(b"to") => {
264            let to = engine.read_dim(false, tk)?;
265            ToOrSpread::To(to)
266        }
267        Some(b"spread") => {
268            let spread = engine.read_dim(false, tk)?;
269            ToOrSpread::Spread(spread)
270        }
271        _ => ToOrSpread::None,
272    };
273    crate::expand_loop!(engine,token,
274        ResolvedToken::Tk {code:CommandCode::Space,..} => (),
275        ResolvedToken::Cmd(Some(TeXCommand::Primitive{cmd:PrimitiveCommand::Relax,..})) => (),
276        ResolvedToken::Tk {code:CommandCode::BeginGroup,..} |
277        ResolvedToken::Cmd(Some(TeXCommand::Char{code:CommandCode::BeginGroup,..})) => {
278            engine.state.push(engine.aux,tp,engine.mouth.line_number());
279            engine.push_every(every);
280            return Ok(scaled)
281        }
282        _ => return Err(TeXError::General("Begin group expected in box start\nTODO: Better error message".to_string()))
283    );
284    Err(TeXError::General(
285        "File ended unexpectedly\nTODO: Better error message".to_string(),
286    ))
287}
288
289pub(in crate::commands) fn get_if_token<ET: EngineTypes>(
290    engine: &mut EngineReferences<ET>,
291    in_token: &ET::Token,
292) -> TeXResult<(Option<ET::Char>, CommandCode), ET> {
293    let mut exp = true;
294    loop {
295        let token = engine.need_next(!exp, in_token)?;
296        if token.is_primitive() == Some(PRIMITIVES.noexpand) {
297            exp = false;
298            continue;
299        }
300        if !exp {
301            if let crate::tex::tokens::StandardToken::Character(c, CommandCode::Active) =
302                token.to_enum()
303            {
304                return Ok((Some(c), CommandCode::Active));
305            }
306        }
307        match engine.resolve(&token) {
308            ResolvedToken::Tk { char, code } => return Ok((Some(char), code)),
309            ResolvedToken::Cmd(cmd) => match cmd {
310                Some(TeXCommand::Macro(m)) if exp => {
311                    ET::Gullet::do_macro(engine, m.clone(), token)?;
312                }
313                Some(TeXCommand::Primitive { name, .. }) if exp && *name == PRIMITIVES.noexpand => {
314                    exp = false;
315                }
316                Some(TeXCommand::Primitive {
317                    name,
318                    cmd: PrimitiveCommand::Conditional(cond),
319                }) if exp => ET::Gullet::do_conditional(engine, *name, token, *cond, false)?,
320                Some(TeXCommand::Primitive {
321                    name,
322                    cmd: PrimitiveCommand::Expandable(e),
323                }) if exp => ET::Gullet::do_expandable(engine, *name, token, *e)?,
324                Some(TeXCommand::Primitive {
325                    name,
326                    cmd: PrimitiveCommand::SimpleExpandable(e),
327                }) if exp => ET::Gullet::do_simple_expandable(engine, *name, token, *e)?,
328                Some(TeXCommand::Char { char, code }, ..) => return Ok((Some(*char), *code)),
329                _ => return Ok((None, CommandCode::Escape)),
330            },
331        }
332    }
333}
334
335pub(in crate::commands) enum IfxCmd<ET: EngineTypes> {
336    Char(ET::Char, CommandCode),
337    Undefined,
338    Primitive(PrimitiveIdentifier),
339    Noexpand(ET::Token),
340    Chardef(ET::Char),
341    Font(<ET::FontSystem as FontSystem>::Font),
342    MathChar(u32),
343    Macro(Macro<ET::Token>),
344    IntRegister(usize),
345    DimRegister(usize),
346    SkipRegister(usize),
347    MuSkipRegister(usize),
348    ToksRegister(usize),
349}
350impl<ET: EngineTypes> IfxCmd<ET> {
351    pub(in crate::commands) fn read(
352        engine: &mut EngineReferences<ET>,
353        in_token: &ET::Token,
354    ) -> TeXResult<Self, ET> {
355        let next = engine.need_next(true, in_token)?;
356        Ok(if next.is_primitive() == Some(PRIMITIVES.noexpand) {
357            IfxCmd::Noexpand(engine.need_next(true, &next)?)
358        } else {
359            Self::resolve(engine.resolve(&next))
360        })
361    }
362
363    fn resolve(r: ResolvedToken<ET>) -> Self {
364        match r {
365            ResolvedToken::Tk { char, code, .. } => Self::Char(char, code),
366            ResolvedToken::Cmd(cmd) => match cmd {
367                Some(TeXCommand::Char { char, code }) => Self::Char(*char, *code),
368                None => Self::Undefined,
369                Some(TeXCommand::Macro(m)) => Self::Macro(m.clone()),
370                Some(TeXCommand::CharDef(c)) => Self::Chardef(*c),
371                Some(TeXCommand::Font(f)) => Self::Font(f.clone()),
372                Some(TeXCommand::MathChar(u)) => Self::MathChar(*u),
373                Some(TeXCommand::IntRegister(u)) => Self::IntRegister(*u),
374                Some(TeXCommand::DimRegister(u)) => Self::DimRegister(*u),
375                Some(TeXCommand::SkipRegister(u)) => Self::SkipRegister(*u),
376                Some(TeXCommand::MuSkipRegister(u)) => Self::MuSkipRegister(*u),
377                Some(TeXCommand::ToksRegister(u)) => Self::ToksRegister(*u),
378                Some(TeXCommand::Primitive { name, .. }) => Self::Primitive(*name),
379            },
380        }
381    }
382}
383
384impl<ET: EngineTypes> PartialEq for IfxCmd<ET> {
385    fn eq(&self, other: &Self) -> bool {
386        match (self, other) {
387            (Self::Char(_, CommandCode::Space), Self::Char(_, CommandCode::Space)) => true,
388            (Self::Char(c1, cc1), Self::Char(c2, cc2)) => c1 == c2 && cc1 == cc2,
389            (Self::Undefined, Self::Undefined) => true,
390            (Self::Primitive(id), Self::Primitive(id2)) => id == id2,
391            (Self::Noexpand(t1), Self::Noexpand(t2)) => t1 == t2,
392            (Self::Chardef(c), Self::Chardef(c2)) => c == c2,
393            (Self::Font(f1), Self::Font(f2)) => f1.name() == f2.name(),
394            (Self::MathChar(u1), Self::MathChar(u2)) => u1 == u2,
395            (Self::IntRegister(u1), Self::IntRegister(u2)) => u1 == u2,
396            (Self::DimRegister(u1), Self::DimRegister(u2)) => u1 == u2,
397            (Self::SkipRegister(u1), Self::SkipRegister(u2)) => u1 == u2,
398            (Self::MuSkipRegister(u1), Self::MuSkipRegister(u2)) => u1 == u2,
399            (Self::ToksRegister(u1), Self::ToksRegister(u2)) => u1 == u2,
400            (Self::Macro(m1), Self::Macro(m2)) => {
401                m1.long == m2.long
402                    && m1.outer == m2.outer
403                    && m1.protected == m2.protected
404                    && m1.signature.params == m2.signature.params
405                    && m1.expansion == m2.expansion
406            }
407            _ => false,
408        }
409    }
410}
411
412pub(crate) fn do_marks<ET: EngineTypes>(
413    engine: &mut EngineReferences<ET>,
414    idx: usize,
415    in_token: &ET::Token,
416) -> TeXResult<(), ET> {
417    let mut v = shared_vector::Vector::new();
418    engine.expand_until_bgroup(false, in_token)?;
419    engine.expand_until_endgroup(true, true, in_token, |_, _, t| {
420        v.push(t);
421        Ok(())
422    })?;
423    let data = engine.stomach.data_mut();
424    for list in data.open_lists.iter_mut().rev() {
425        match list {
426            NodeList::Horizontal {
427                children,
428                tp: HorizontalNodeListType::Paragraph(..),
429            } => {
430                children.push(HNode::Mark(idx, v.into()));
431                return Ok(());
432            }
433            NodeList::Vertical { children, .. } => {
434                children.push(VNode::Mark(idx, v.into()));
435                return Ok(());
436            }
437            NodeList::Math { children, .. } => {
438                children.push(MathNode::Mark(idx, v.into()));
439                return Ok(());
440            }
441            _ => (),
442        }
443    }
444    data.page.push(VNode::Mark(idx, v.into()));
445    Ok(())
446}
447
448pub(crate) fn get_marks<ET: EngineTypes>(
449    engine: &mut EngineReferences<ET>,
450    exp: &mut Vec<ET::Token>,
451    f: fn(&mut StomachData<ET>) -> &mut HMap<usize, TokenList<ET::Token>>,
452    idx: usize,
453) {
454    if let Some(v) = f(engine.stomach.data_mut()).get(&idx) {
455        exp.extend(v.0.iter().cloned())
456    }
457}
458
459pub(crate) fn do_align<ET: EngineTypes>(
460    engine: &mut EngineReferences<ET>,
461    inner: BoxType,
462    between: BoxType,
463    _to: Option<ET::Dim>,
464    tk: &ET::Token,
465) -> TeXResult<(), ET> {
466    // TODO use to
467    engine.gullet.push_align(AlignData::dummy());
468    engine.expand_until_bgroup(true, tk)?;
469    let data = read_align_preamble(engine, inner, between, tk)?;
470    *engine.gullet.get_align_data().unwrap() = data;
471    ET::Stomach::open_align(engine, inner, between);
472    start_align_row(engine, inner)
473}
474
475fn read_align_preamble<ET: EngineTypes>(
476    engine: &mut EngineReferences<ET>,
477    inner_mode: BoxType,
478    between_mode: BoxType,
479    in_token: &ET::Token,
480) -> TeXResult<AlignData<ET::Token, ET::Dim>, ET> {
481    struct AlignmentDataBuilder<ET: EngineTypes> {
482        columns: Vec<AlignColumn<ET::Token, ET::Dim>>,
483        recindex: Option<usize>,
484        current_u: Vec<ET::Token>,
485        current_v: Vec<ET::Token>,
486        ingroups: u8,
487        in_v: bool,
488        tabskip: Skip<ET::Dim>,
489        inner_mode: BoxType,
490        between_mode: BoxType,
491    }
492    impl<ET: EngineTypes> AlignmentDataBuilder<ET> {
493        fn push(&mut self, tk: ET::Token) {
494            if self.in_v {
495                self.current_v.push(tk)
496            } else {
497                self.current_u.push(tk)
498            }
499        }
500        fn build(mut self, token: ET::Token) -> AlignData<ET::Token, ET::Dim> {
501            self.columns.push(AlignColumn::new(
502                self.current_u,
503                self.current_v,
504                self.tabskip,
505                self.ingroups,
506            ));
507            AlignData {
508                token,
509                columns: self.columns.into(),
510                ingroups: 0,
511                currindex: 0,
512                repeat_index: self.recindex,
513                omit: false,
514                span: false,
515                inner_mode: self.inner_mode,
516                outer_mode: self.between_mode,
517            }
518        }
519    }
520
521    let tabskip = engine.state.get_primitive_skip(PRIMITIVES.tabskip);
522    let mut cols = AlignmentDataBuilder::<ET> {
523        columns: Vec::new(),
524        recindex: None,
525        ingroups: 0,
526        current_u: Vec::new(),
527        current_v: Vec::new(),
528        in_v: false,
529        tabskip,
530        inner_mode,
531        between_mode,
532    };
533    let mut ingroups = 0;
534
535    while let Some(next) = engine.mouth.get_next(engine.aux, engine.state)? {
536        if next.is_primitive() == Some(PRIMITIVES.noexpand) {
537            let Ok(Some(n)) = engine.mouth.get_next(engine.aux, engine.state) else {
538                unreachable!()
539            };
540            cols.push(n);
541            continue;
542        }
543        match next.command_code() {
544            CommandCode::BeginGroup => {
545                ingroups += 1;
546                cols.push(next);
547                continue;
548            }
549            CommandCode::EndGroup => {
550                ingroups -= 1;
551                cols.push(next);
552                continue;
553            }
554            _ => (),
555        }
556        match ET::Gullet::char_or_primitive(engine.state, &next) {
557            Some(CharOrPrimitive::Char(_, Some(CommandCode::Parameter))) => {
558                if cols.in_v {
559                    return Err(TeXError::General(
560                        "Unexpected # in alignment\nTODO: Better error message".to_string(),
561                    ));
562                }
563                cols.in_v = true;
564                cols.ingroups = ingroups;
565            }
566            Some(CharOrPrimitive::Char(_, Some(CommandCode::AlignmentTab))) => {
567                if ingroups != 0 {
568                    return Err(TeXError::General(
569                        "Unbalanced number of braces in alignment\nTODO: Better error message"
570                            .to_string(),
571                    ));
572                }
573                if !cols.in_v && cols.current_u.is_empty() {
574                    cols.recindex = Some(cols.columns.len());
575                } else {
576                    let (u, v, g) = (
577                        std::mem::take(&mut cols.current_u),
578                        std::mem::take(&mut cols.current_v),
579                        std::mem::take(&mut cols.ingroups),
580                    );
581                    cols.columns.push(AlignColumn::new(u, v, cols.tabskip, g));
582                    cols.tabskip = tabskip;
583                    cols.in_v = false;
584                }
585            }
586            Some(CharOrPrimitive::Primitive(name)) if name == PRIMITIVES.tabskip => {
587                cols.tabskip = engine.read_skip(true, in_token)?
588            }
589            Some(CharOrPrimitive::Primitive(name))
590                if name == PRIMITIVES.cr || name == PRIMITIVES.crcr =>
591            {
592                if ingroups != 0 {
593                    return Err(TeXError::General(
594                        "Unbalanced number of braces in alignment\nTODO: Better error message"
595                            .to_string(),
596                    ));
597                }
598                engine.push_every(PRIMITIVES.everycr);
599                return Ok(cols.build(in_token.clone()));
600            }
601            Some(CharOrPrimitive::Primitive(name)) if name == PRIMITIVES.span => {
602                let t = match engine.mouth.get_next(engine.aux, engine.state)? {
603                    Some(t) => t,
604                    _ => {
605                        TeXError::file_end_while_use(
606                            engine.aux,
607                            engine.state,
608                            engine.mouth,
609                            in_token,
610                        )?;
611                        continue;
612                    }
613                };
614                engine.expand(t)?;
615            }
616            _ => cols.push(next),
617        }
618    }
619    TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, in_token)?;
620    Ok(cols.build(in_token.clone()))
621}
622
623pub(in crate::commands) fn start_align_row<ET: EngineTypes>(
624    engine: &mut EngineReferences<ET>,
625    mode: BoxType,
626) -> TeXResult<(), ET> {
627    if let Some(d) = engine.gullet.get_align_data() {
628        d.currindex = 0
629    } else {
630        unreachable!()
631    }
632    // avoid the gullet here as to not throw an error on '}'!
633    while let Some(token) = engine.mouth.get_next(engine.aux, engine.state)? {
634        crate::expand!(engine,token;
635            ResolvedToken::Tk{code:CommandCode::EndGroup,..} |
636            ResolvedToken::Cmd(Some(TeXCommand::Char {code:CommandCode::EndGroup,..})) => {
637                engine.gullet.pop_align();
638                ET::Stomach::close_align(engine)?;
639                return Ok(())
640            }
641            ResolvedToken::Tk{code:CommandCode::Space,..} => (),
642            ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,..})) if *name == PRIMITIVES.crcr => (),
643            ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,..})) if *name == PRIMITIVES.noalign => {
644                engine.expand_until_bgroup(true,&token)?;
645                engine.state.push(engine.aux,GroupType::Noalign,engine.mouth.line_number());
646                engine.stomach.data_mut().open_lists.push(
647                    match mode {
648                        BoxType::Vertical => NodeList::Horizontal {
649                            children:Vec::new(),
650                            tp:HorizontalNodeListType::Box(HBoxInfo::HAlignRow,engine.mouth.start_ref(),BoxTarget::new(
651                                move |engine,l| {
652                                    if let TeXBox::H {children,info:HBoxInfo::HAlignRow,..} = l  {
653                                        for c in children.into_vec() {
654                                            ET::Stomach::add_node_h(engine,c);
655                                        }
656                                    } else {unreachable!()}
657                                    start_align_row(engine,mode)
658                                }
659                            ))
660                        },
661                        _ => NodeList::Vertical {
662                            children:Vec::new(),
663                            tp:VerticalNodeListType::Box(VBoxInfo::VAlignColumn,engine.mouth.start_ref(),BoxTarget::new(
664                                move |engine,l| {
665                                    if let TeXBox::V {children,info:VBoxInfo::VAlignColumn,..} = l  {
666                                        for c in children.into_vec() {
667                                            ET::Stomach::add_node_v(engine,c)?;
668                                        }
669                                    } else {unreachable!()}
670                                    start_align_row(engine,mode)
671                                }
672                            ))
673                        }
674                    }
675                );
676                return Ok(())
677            }
678            ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,..})) if *name == PRIMITIVES.omit => {
679                engine.stomach.data_mut().open_lists.push(
680                    match mode {
681                        BoxType::Vertical => NodeList::Vertical {
682                            children:Vec::new(),
683                            tp:VerticalNodeListType::VAlignColumn(engine.mouth.start_ref())
684                        },
685                        _ => NodeList::Horizontal {
686                            children:Vec::new(),
687                            tp:HorizontalNodeListType::HAlignRow(engine.mouth.start_ref())
688                        }
689                    }
690                );
691                engine.gullet.get_align_data().unwrap().omit = true;
692                open_align_cell(engine,mode);
693                return Ok(())
694            }
695            _ => {
696                engine.stomach.data_mut().open_lists.push(
697                    match mode {
698                        BoxType::Vertical => NodeList::Vertical {
699                            children:Vec::new(),
700                            tp:VerticalNodeListType::VAlignColumn(engine.mouth.start_ref())
701                        },
702                        _ => NodeList::Horizontal {
703                            children:Vec::new(),
704                            tp:HorizontalNodeListType::HAlignRow(engine.mouth.start_ref())
705                        }
706                    }
707                );
708                engine.mouth.requeue(token);
709                open_align_cell(engine,mode);
710                return Ok(())
711            }
712        );
713    }
714    let Some(ad) = engine.gullet.get_align_data() else {
715        return Err(TeXError::General("Not in align".to_string()));
716    };
717    TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, &ad.token)
718}
719
720pub(in crate::commands) fn open_align_cell<ET: EngineTypes>(
721    engine: &mut EngineReferences<ET>,
722    mode: BoxType,
723) {
724    match engine.gullet.get_align_data() {
725        None => unreachable!(),
726        Some(data) => {
727            if !data.omit {
728                engine
729                    .mouth
730                    .push_slice_rev(&data.columns[data.currindex].left);
731            }
732            if data.span {
733                data.span = false
734            } else {
735                engine
736                    .state
737                    .push(engine.aux, GroupType::Align, engine.mouth.line_number());
738                engine.stomach.data_mut().open_lists.push(match mode {
739                    BoxType::Vertical => NodeList::Vertical {
740                        children: vec![],
741                        tp: VerticalNodeListType::VAlignCell(engine.mouth.start_ref(), 0),
742                    },
743                    _ => NodeList::Horizontal {
744                        children: vec![],
745                        tp: HorizontalNodeListType::HAlignCell(engine.mouth.start_ref(), 0),
746                    },
747                })
748            }
749        }
750    }
751}
752
753pub(in crate::commands) fn pop_align_cell<ET: EngineTypes>(
754    state: &mut ET::State,
755    aux: &mut EngineAux<ET>,
756    stomach: &mut ET::Stomach,
757    mouth: &mut ET::Mouth,
758    inner_mode: BoxType,
759) {
760    match inner_mode {
761        BoxType::Vertical => pop_align_cell_v(state, aux, stomach, mouth),
762        _ => pop_align_cell_h(state, aux, stomach, mouth),
763    }
764}
765
766fn pop_align_cell_v<ET: EngineTypes>(
767    state: &mut ET::State,
768    aux: &mut EngineAux<ET>,
769    stomach: &mut ET::Stomach,
770    mouth: &mut ET::Mouth,
771) {
772    let (children, start, spans) = match stomach.data_mut().open_lists.pop() {
773        Some(NodeList::Vertical {
774            children,
775            tp: VerticalNodeListType::VAlignCell(start, i),
776        }) => (children, start, i),
777        _ => unreachable!(),
778    };
779    state.pop(aux, mouth);
780    let bx = TeXBox::V {
781        children: children.into(),
782        start,
783        info: VBoxInfo::VAlignCell { to: None, spans },
784        end: mouth.current_sourceref(),
785    };
786    match stomach.data_mut().open_lists.last_mut() {
787        Some(NodeList::Vertical {
788            children,
789            tp: VerticalNodeListType::VAlignColumn(..),
790        }) => children.push(VNode::Box(bx)),
791        _ => unreachable!(),
792    }
793}
794fn pop_align_cell_h<ET: EngineTypes>(
795    state: &mut ET::State,
796    aux: &mut EngineAux<ET>,
797    stomach: &mut ET::Stomach,
798    mouth: &mut ET::Mouth,
799) {
800    let (children, start, spans) = match stomach.data_mut().open_lists.pop() {
801        Some(NodeList::Horizontal {
802            children,
803            tp: HorizontalNodeListType::HAlignCell(start, i),
804        }) => (children, start, i),
805        _ => unreachable!(),
806    };
807    state.pop(aux, mouth);
808    let bx = TeXBox::H {
809        children: children.into(),
810        start,
811        info: HBoxInfo::new_cell(spans),
812        end: mouth.current_sourceref(),
813        preskip: None,
814    };
815    match stomach.data_mut().open_lists.last_mut() {
816        Some(NodeList::Horizontal {
817            children,
818            tp: HorizontalNodeListType::HAlignRow(..),
819        }) => children.push(HNode::Box(bx)),
820        _ => unreachable!(),
821    }
822}
823
824pub(in crate::commands) fn pop_align_row<ET: EngineTypes>(
825    stomach: &mut ET::Stomach,
826    mouth: &mut ET::Mouth,
827    inner_mode: BoxType,
828) {
829    match inner_mode {
830        BoxType::Vertical => pop_align_row_v::<ET>(stomach, mouth),
831        _ => pop_align_row_h::<ET>(stomach, mouth),
832    }
833}
834
835fn pop_align_row_v<ET: EngineTypes>(stomach: &mut ET::Stomach, mouth: &mut ET::Mouth) {
836    let (children, start) = match stomach.data_mut().open_lists.pop() {
837        Some(NodeList::Vertical {
838            children,
839            tp: VerticalNodeListType::VAlignColumn(start),
840        }) => (children, start),
841        _ => unreachable!(),
842    };
843    let bx = TeXBox::V {
844        children: children.into(),
845        start,
846        info: VBoxInfo::VAlignColumn,
847        end: mouth.current_sourceref(),
848    };
849    match stomach.data_mut().open_lists.last_mut() {
850        Some(NodeList::Horizontal {
851            children,
852            tp: HorizontalNodeListType::VAlign,
853        }) => children.push(HNode::Box(bx)),
854        _ => unreachable!(),
855    }
856}
857
858fn pop_align_row_h<ET: EngineTypes>(stomach: &mut ET::Stomach, mouth: &mut ET::Mouth) {
859    let (children, start) = match stomach.data_mut().open_lists.pop() {
860        Some(NodeList::Horizontal {
861            children,
862            tp: HorizontalNodeListType::HAlignRow(start),
863        }) => (children, start),
864        _ => unreachable!(),
865    };
866    let bx = TeXBox::H {
867        children: children.into(),
868        start,
869        info: HBoxInfo::HAlignRow,
870        end: mouth.current_sourceref(),
871        preskip: None,
872    };
873    match stomach.data_mut().open_lists.last_mut() {
874        Some(NodeList::Vertical {
875            children,
876            tp: VerticalNodeListType::HAlign,
877        }) => children.push(VNode::Box(bx)),
878        _ => unreachable!(),
879    }
880}
881
882pub(crate) const END_TEMPLATE: &str = "!\"$%&/(endtemplate)\\&%$\"!";
883pub(crate) const END_TEMPLATE_ROW: &str = "!\"$%&/(endtemplate_row)\\&%$\"!";
884
885pub(crate) fn un_x<ET: EngineTypes>(
886    engine: &mut EngineReferences<ET>,
887    v: fn(&VNode<ET>) -> bool,
888    h: fn(&HNode<ET>) -> bool,
889    m: fn(&MathNode<ET, UnresolvedMathFontStyle<ET>>) -> bool,
890) {
891    let data = engine.stomach.data_mut();
892    match data.open_lists.last_mut() {
893        None => (), //todo!("throw error: Not allowed in vertical"), <- not entirely true; if there's contributed stuff not yet migrated to the current page, it's allowed
894        Some(NodeList::Vertical { children, .. }) => {
895            for (i, n) in children.iter().enumerate().rev() {
896                if n.opaque() {
897                    continue;
898                }
899                if v(n) {
900                    children.remove(i);
901                    return;
902                }
903                break;
904            }
905        }
906        Some(NodeList::Horizontal { children, .. }) => {
907            for (i, n) in children.iter().enumerate().rev() {
908                if n.opaque() {
909                    continue;
910                }
911                if h(n) {
912                    children.remove(i);
913                    return;
914                }
915                break;
916            }
917        }
918        Some(NodeList::Math { children, .. }) => {
919            for (i, n) in children.list().iter().enumerate().rev() {
920                if n.opaque() {
921                    continue;
922                }
923                if m(n) {
924                    children.list_mut().remove(i);
925                    return;
926                }
927                break;
928            }
929        }
930    }
931}
932
933pub(crate) fn last_x<R, ET: EngineTypes>(
934    engine: &mut EngineReferences<ET>,
935    v: fn(&VNode<ET>) -> Option<R>,
936    h: fn(&HNode<ET>) -> Option<R>,
937    m: fn(&MathNode<ET, UnresolvedMathFontStyle<ET>>) -> Option<R>,
938) -> Option<R> {
939    let data = engine.stomach.data_mut();
940    match data.open_lists.last_mut() {
941        None => {
942            for n in data.page.iter().rev() {
943                if n.opaque() {
944                    continue;
945                }
946                return v(n);
947            }
948        }
949        Some(NodeList::Vertical { children, .. }) => {
950            for n in children.iter().rev() {
951                if n.opaque() {
952                    continue;
953                }
954                return v(n);
955            }
956        }
957        Some(NodeList::Horizontal { children, .. }) => {
958            for n in children.iter().rev() {
959                if n.opaque() {
960                    continue;
961                }
962                return h(n);
963            }
964        }
965        Some(NodeList::Math { children, .. }) => {
966            for n in children.list_mut().iter().rev() {
967                if n.opaque() {
968                    continue;
969                }
970                return m(n);
971            }
972        }
973    }
974    None
975}
976
977pub(crate) fn do_leaders<ET: EngineTypes>(
978    engine: &mut EngineReferences<ET>,
979    tp: LeaderType,
980    tk: &ET::Token,
981) -> TeXResult<(), ET> {
982    match engine.read_keywords(&[b"width", b"height", b"depth"])? {
983        Some(_) => {
984            return Err(TeXError::General(
985                "Not yet implemented: leaders with width/height/depth".to_string(),
986            ))
987        }
988        _ => crate::expand_loop!(engine,token,
989            ResolvedToken::Cmd(Some(TeXCommand::Primitive {cmd:PrimitiveCommand::Box(read),..})) => {
990                match read(engine,token)? {
991                    either::Left(None) => return Err(TeXError::General("Box expected for leaders\nTODO: Better error message".to_string())),
992                    either::Left(Some(bx)) => return leaders_skip(engine,LeaderBody::Box(bx),tp,tk),
993                    either::Right(bi) => {
994                        let tk = tk.clone();
995                        let target = BoxTarget::<ET>::new(move |e,b| leaders_skip(e,LeaderBody::Box(b),tp,&tk));
996                        let mut ls = bi.open_list(engine.mouth.start_ref());
997                        match ls {
998                            NodeList::Horizontal {tp:HorizontalNodeListType::Box(_,_,ref mut t),..} => *t = target,
999                            NodeList::Vertical {tp:VerticalNodeListType::Box(_,_,ref mut t),..} => *t = target,
1000                            _ => unreachable!()
1001                        }
1002                        engine.stomach.data_mut().open_lists.push(ls);
1003                        return Ok(())
1004                    }
1005                }
1006            }
1007            ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,..})) if *name == PRIMITIVES.hrule || *name == PRIMITIVES.vrule => {
1008                let mut width = None;
1009                let mut height = None;
1010                let mut depth = None;
1011                loop {
1012                    match engine.read_keywords(&[b"width",b"height",b"depth"])? {
1013                        Some(b"width") => {
1014                            width = Some(engine.read_dim(false,tk)?);
1015                        }
1016                        Some(b"height") => {
1017                            height = Some(engine.read_dim(false,tk)?);
1018                        }
1019                        Some(b"depth") => {
1020                            depth = Some(engine.read_dim(false,tk)?);
1021                        }
1022                        _ => break
1023                    }
1024                }
1025                return leaders_skip(engine,LeaderBody::Rule {width,height,depth},tp,tk)
1026            }
1027            _ => return Err(TeXError::General("Box expected for leaders\nTODO: Better error message".to_string()))
1028        ),
1029    }
1030    Err(TeXError::General(
1031        "File ended unexpectedly\nTODO: Better error message".to_string(),
1032    ))
1033}
1034
1035fn leaders_skip<ET: EngineTypes>(
1036    engine: &mut EngineReferences<ET>,
1037    body: LeaderBody<ET>,
1038    tp: LeaderType,
1039    tk: &ET::Token,
1040) -> TeXResult<(), ET> {
1041    crate::expand_loop!(engine,token,
1042        ResolvedToken::Cmd(Some(TeXCommand::Primitive{name,..})) => {
1043            let skip = match *name {
1044                n if n == PRIMITIVES.vskip => LeaderSkip::VSkip(engine.read_skip(false,tk)?),
1045                n if n == PRIMITIVES.hskip => LeaderSkip::HSkip(engine.read_skip(false,tk)?),
1046                n if n == PRIMITIVES.vfil => LeaderSkip::VFil,
1047                n if n == PRIMITIVES.hfil => LeaderSkip::HFil,
1048                n if n == PRIMITIVES.vfill => LeaderSkip::VFill,
1049                n if n == PRIMITIVES.hfill => LeaderSkip::HFill,
1050                _ => return Err(TeXError::General("Glue expected for leaders\nTODO: Better error message".to_string()))
1051            };
1052            let leaders = Leaders {skip,body,tp};
1053            crate::add_node!(ET::Stomach;engine,VNode::Leaders(leaders),HNode::Leaders(leaders),MathNode::Leaders(leaders));
1054            return Ok(())
1055        }
1056        _ => return Err(TeXError::General("Glue expected for leaders\nTODO: Better error message".to_string()))
1057    );
1058    Err(TeXError::General(
1059        "File ended unexpectedly\nTODO: Better error message".to_string(),
1060    ))
1061}
1062
1063pub(crate) fn do_math_class<ET: EngineTypes>(
1064    engine: &mut EngineReferences<ET>,
1065    cls: Option<MathClass>,
1066    in_token: &ET::Token,
1067) -> TeXResult<(), ET> {
1068    engine.read_char_or_math_group(
1069        in_token,
1070        |(), engine, mc| {
1071            let mut atom = mc.to_atom();
1072            if let Some(cls) = cls {
1073                let MathNucleus::Simple { cls: ocls, .. } = &mut atom.nucleus else {
1074                    unreachable!()
1075                };
1076                *ocls = cls;
1077            }
1078            ET::Stomach::add_node_m(engine, MathNode::Atom(atom));
1079            Ok(())
1080        },
1081        move |()| {
1082            ListTarget::<ET, _>::new(move |engine, mut children, start| {
1083                if children.len() == 1 {
1084                    let mut child = children.pop().unwrap_or_else(|| unreachable!());
1085                    if let Some(cls) = cls {
1086                        if let MathNode::Atom(MathAtom {
1087                            sub: None,
1088                            sup: None,
1089                            nucleus: MathNucleus::Simple { cls: ocls, .. },
1090                        }) = &mut child
1091                        {
1092                            *ocls = cls;
1093                        }
1094                    }
1095                    ET::Stomach::add_node_m(engine, child);
1096                    return Ok(());
1097                }
1098
1099                let node = MathNode::Atom(MathAtom {
1100                    sub: None,
1101                    sup: None,
1102                    nucleus: match cls {
1103                        None => MathNucleus::Inner(MathKernel::List {
1104                            start,
1105                            children: children.into(),
1106                            end: engine.mouth.current_sourceref(),
1107                        }),
1108                        Some(cls) => MathNucleus::Simple {
1109                            kernel: MathKernel::List {
1110                                start,
1111                                children: children.into(),
1112                                end: engine.mouth.current_sourceref(),
1113                            },
1114                            cls,
1115                            limits: None,
1116                        },
1117                    },
1118                });
1119                ET::Stomach::add_node_m(engine, node);
1120                Ok(())
1121            })
1122        },
1123        (),
1124    )
1125}
1126
1127impl<ET: EngineTypes> EngineReferences<'_, ET> {
1128    /// expands the [`Token`] if it is expandable, otherwise requeues it
1129    pub fn expand(&mut self, token: ET::Token) -> TeXResult<(), ET> {
1130        match self.resolve(&token) {
1131            ResolvedToken::Cmd(Some(cmd)) => match cmd {
1132                TeXCommand::Macro(m) => ET::Gullet::do_macro(self, m.clone(), token),
1133                TeXCommand::Primitive {
1134                    name,
1135                    cmd: PrimitiveCommand::Conditional(cond),
1136                } => ET::Gullet::do_conditional(self, *name, token, *cond, false),
1137                TeXCommand::Primitive {
1138                    name,
1139                    cmd: PrimitiveCommand::Expandable(expand),
1140                } => ET::Gullet::do_expandable(self, *name, token, *expand),
1141                TeXCommand::Primitive {
1142                    name,
1143                    cmd: PrimitiveCommand::SimpleExpandable(exp),
1144                } => ET::Gullet::do_simple_expandable(self, *name, token, *exp),
1145                _ => self.requeue(token),
1146            },
1147            _ => self.requeue(token),
1148        }
1149    }
1150
1151    /// reads an integer from the input stream and makes sure it's in the range of
1152    /// a state register
1153    pub fn read_register_index(
1154        &mut self,
1155        skip_eq: bool,
1156        in_token: &ET::Token,
1157    ) -> TeXResult<usize, ET> {
1158        let idx = self.read_int(skip_eq, in_token)?;
1159        match ET::State::register_index(idx) {
1160            Some(idx) => Ok(idx),
1161            None => Err(TeXError::General(
1162                "Invalid register index\nTODO: Better error message".to_string(),
1163            )),
1164        }
1165    }
1166    /// reads an integer and makes sure it's in the range of a math font index (0-15)
1167    pub fn mathfont_index(&mut self, skip_eq: bool, in_token: &ET::Token) -> TeXResult<u8, ET> {
1168        let idx = self.read_int(skip_eq, in_token)?.into();
1169        if !(0..=15).contains(&idx) {
1170            return Err(TeXError::General(
1171                "Invalid math font index\nTODO: Better error message".to_string(),
1172            ));
1173        }
1174        Ok(idx as u8)
1175    }
1176    /// expects a [`BeginGroup`](CommandCode::BeginGroup) token, reads until the
1177    /// matching [`EndGroup`](CommandCode::EndGroup) token and discards everything
1178    /// in between.
1179    pub fn skip_argument(&mut self, token: &ET::Token) -> TeXResult<(), ET> {
1180        let t = self.need_next(false, token)?;
1181        if t.command_code() != CommandCode::BeginGroup {
1182            TeXError::missing_begingroup(self.aux, self.state, self.mouth)?;
1183        }
1184        self.read_until_endgroup(token, |_, _, _| Ok(()))?;
1185        Ok(())
1186    }
1187
1188    /// reads the name of a control sequence until `\endcsname` and returns the
1189    /// corresponding [`CSName`](crate::tex::tokens::control_sequences::CSName) (i.e. what `\csname` and
1190    /// `\ifcsname` do)
1191    pub fn read_csname(&mut self) -> TeXResult<ET::CSName, ET> {
1192        *self.gullet.csnames() += 1;
1193        let mut namev = vec![];
1194        crate::expand_loop!(self,token,
1195            ResolvedToken::Tk {char,..} => namev.push(char),
1196            ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,..})) if *name == PRIMITIVES.endcsname => {
1197                *self.gullet.csnames() -= 1;
1198                let id = self.aux.memory.cs_interner_mut().cs_from_chars(&namev);
1199                //engine.aux.memory.return_string(name);
1200                return Ok(id)
1201            }
1202            ResolvedToken::Cmd(_) => return Err(TeXError::General("Unexpandable command in \\csname\nTODO: Better error message".to_string()))
1203        );
1204        Err(TeXError::General(
1205            "File ended while reading \\csname\nTODO: Better error message".to_string(),
1206        ))
1207    }
1208    /// reads a number from the input stream and makes sure it's in the range of
1209    /// a file input/output stream index (0-255)
1210    pub fn read_file_index(&mut self, in_token: &ET::Token) -> TeXResult<u8, ET> {
1211        let idx = self.read_int(false, in_token)?.into();
1212        if !(0..=255).contains(&idx) {
1213            return Err(TeXError::General(
1214                "Invalid file stream index\nTODO: Better error message".to_string(),
1215            ));
1216        }
1217        Ok(idx as u8)
1218    }
1219
1220    /// reads a number from the input stream, making sure it's in the range of
1221    /// a file input/output stream index (0-255), and subsequently reads a filename
1222    /// - i.e. the first two steps of `\openin` or `\openout`
1223    pub fn read_filename_and_index(
1224        &mut self,
1225        prefix: &str,
1226        in_token: &ET::Token,
1227    ) -> TeXResult<(u8, ET::File), ET> {
1228        let idx = self.read_file_index(in_token)?;
1229        let mut filename = self.aux.memory.get_string();
1230        self.read_string(true, &mut filename, in_token)?;
1231        filename.insert_str(0, prefix);
1232        let file = self.filesystem.get(&filename);
1233        self.aux.memory.return_string(filename);
1234        Ok((idx, file))
1235    }
1236
1237    /// `\the`, but using a continuation function; this is used for both [`the`](super::tex::the)
1238    /// as well as in [`expand_until_endgroup`](Self::expand_until_endgroup)
1239    /// to speed things up
1240    pub fn do_the<
1241        F: FnMut(&mut EngineAux<ET>, &ET::State, &mut ET::Gullet, ET::Token) -> TeXResult<(), ET>,
1242    >(
1243        &mut self,
1244        mut cont: F,
1245    ) -> TeXResult<(), ET> {
1246        expand_loop!(self,token,
1247            ResolvedToken::Cmd(Some(c)) => return c.clone().the(self,token,|a,s,g,t|cont(a,s,g,t).expect("the continuation function should not throw errors on Other characters")),
1248            _ => return Err(TeXError::General("command expected after \\the\nTODO: Better error message".to_string()))
1249        );
1250        Err(TeXError::General(
1251            "File ended while reading command for \\the\nTODO: Better error message".to_string(),
1252        ))
1253    }
1254
1255    /// reads a [`Delimiter`] from the input stream;
1256    /// e.g. from `\delimiter` or the `\delcode` of the next character
1257    pub fn read_opt_delimiter(
1258        &mut self,
1259        in_token: &ET::Token,
1260    ) -> TeXResult<Option<Delimiter<ET>>, ET> {
1261        crate::expand_loop!(self,token,
1262            ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,..}))  if *name == PRIMITIVES.delimiter => {
1263                let num = self.read_int(false,in_token)?;
1264                return Ok(Some(match Delimiter::from_int(num,self.state) {
1265                    either::Left(d) => d,
1266                    either::Right((d,i)) => {
1267                        self.general_error(format!("Bad delimiter code ({})",i))?;
1268                        d
1269                    }
1270                }))
1271            }
1272            ResolvedToken::Tk{char,code:CommandCode::Letter|CommandCode::Other,..} => {
1273                let num = self.state.get_delcode(char);
1274                if num == ET::Int::default() {return Ok(None)}
1275                return Ok(Some(match Delimiter::from_int(num,self.state) {
1276                    either::Left(d) => d,
1277                    either::Right((d,i)) => {
1278                        self.general_error(format!("Bad delimiter code ({})",i))?;
1279                        d
1280                    }
1281                }))
1282            }
1283            _ => return Err(TeXError::General("Unexpected token for delimiter\nTODO: Better error message".to_string()))
1284        );
1285        Err(TeXError::General(
1286            "File ended while expecting delimiter\nTODO: Better error message".to_string(),
1287        ))
1288    }
1289}