Skip to main content

tex_engine/engine/gullet/
methods.rs

1/*! Default implementations for [`Gullet`] methods.
2*/
3use crate::commands::primitives::PRIMITIVES;
4use crate::commands::{
5    ActiveConditional, CharOrPrimitive, PrimitiveCommand, ResolvedToken, TeXCommand,
6};
7use crate::engine::gullet::Gullet;
8use crate::engine::mouth::Mouth;
9use crate::engine::state::State;
10use crate::engine::EngineAux;
11use crate::engine::{EngineReferences, EngineTypes};
12use crate::tex::catcodes::CommandCode;
13use crate::tex::characters::Character;
14use crate::tex::numerics::TeXDimen;
15use crate::tex::numerics::{
16    MuDim, MuSkip, MuStretchShrink, NumSet, Skip, StretchShrink, STRETCH_SHRINK_UNITS,
17};
18use crate::tex::tokens::control_sequences::{CSHandler, ResolvedCSName};
19use crate::tex::tokens::token_lists::TokenList;
20use crate::tex::tokens::{StandardToken, Token};
21use crate::utils::errors::{TeXError, TeXResult};
22use either::Either;
23
24/// processes the parameter signature `params` of a [`Macro`](crate::commands::Macro) by reading the relevant arguments;
25/// storing them in `args`
26pub fn read_arguments<ET: EngineTypes>(
27    engine: &mut EngineReferences<ET>,
28    args: &mut [Vec<ET::Token>; 9],
29    params: TokenList<ET::Token>,
30    long: bool,
31    token: &ET::Token,
32) -> TeXResult<(), ET> {
33    let mut i = 1usize;
34    let inner = &params.0;
35    let mut next = &inner[0];
36    loop {
37        match next.is_argument_marker() {
38            Some(a) => match inner.get(i) {
39                Some(n) if n.is_argument_marker().is_some() => {
40                    next = n;
41                    i += 1;
42                    read_argument(engine, &mut args[a as usize], long, token)?
43                }
44                None => {
45                    return read_argument(engine, &mut args[a as usize], long, token);
46                }
47                Some(o) => {
48                    let mut delim = vec![o.clone()];
49                    i += 1;
50                    while let Some(n) = inner.get(i) {
51                        if n.is_argument_marker().is_some() {
52                            break;
53                        }
54                        delim.push(n.clone());
55                        i += 1;
56                    }
57                    read_delimited_argument(engine, &mut args[a as usize], &delim, long, token)?;
58                    match inner.get(i) {
59                        Some(n) => {
60                            next = n;
61                            i += 1
62                        }
63                        _ => return Ok(()),
64                    }
65                }
66            },
67            _ => match engine.mouth.get_next(engine.aux, engine.state)? {
68                Some(o) if o == *next => match inner.get(i) {
69                    Some(n) => {
70                        next = n;
71                        i += 1
72                    }
73                    _ => return Ok(()),
74                },
75                Some(o) => TeXError::wrong_definition(
76                    engine.aux,
77                    engine.state,
78                    engine.mouth,
79                    &o,
80                    next,
81                    token,
82                )?,
83                None => {
84                    TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, token)?
85                }
86            },
87        }
88    }
89}
90
91fn read_delimited_argument<ET: EngineTypes>(
92    engine: &mut EngineReferences<ET>,
93    arg: &mut Vec<ET::Token>,
94    delim: &[ET::Token],
95    long: bool,
96    token: &ET::Token,
97) -> TeXResult<(), ET> {
98    let par = engine.aux.memory.cs_interner().par();
99    let last = delim.last().unwrap();
100    let ends_with_bgroup = last.command_code() == CommandCode::BeginGroup;
101    let mut remove_braces: Option<Option<usize>> = None;
102    loop {
103        let t = engine.need_next(false, token)?;
104        match t.command_code() {
105            CommandCode::Primitive if t.is_primitive() == Some(PRIMITIVES.noexpand) => continue,
106            CommandCode::BeginGroup if !ends_with_bgroup => {
107                if arg.is_empty() {
108                    remove_braces = Some(None)
109                }
110                arg.push(t);
111                let r = if long {
112                    engine.read_until_endgroup(token, |_, _, t| {
113                        arg.push(t);
114                        Ok(())
115                    })?
116                } else {
117                    engine.read_until_endgroup(token, |a, s, t| {
118                        if t.is_cs(&par) {
119                            Err(TeXError::ParagraphEnded(
120                                t.display(
121                                    a.memory.cs_interner(),
122                                    s.get_catcode_scheme(),
123                                    s.get_escape_char(),
124                                )
125                                .to_string(),
126                            ))
127                        } else {
128                            arg.push(t);
129                            Ok(())
130                        }
131                    })?
132                };
133                arg.push(r);
134                if let Some(ref mut n @ None) = remove_braces {
135                    *n = Some(arg.len())
136                }
137                continue;
138            }
139            CommandCode::Escape if !long && t.is_cs(&par) => {
140                TeXError::paragraph_ended(engine.aux, engine.state, engine.mouth, token)?
141            }
142            _ => (),
143        }
144        if t == *last {
145            arg.push(t);
146            if arg.ends_with(delim) {
147                arg.truncate(arg.len() - delim.len());
148                if let Some(Some(n)) = remove_braces {
149                    if arg.len() == n {
150                        arg.pop();
151                        arg.drain(..1).next();
152                    }
153                }
154                if ends_with_bgroup {
155                    if let Some(a) = engine.gullet.get_align_data() {
156                        a.ingroups -= 1
157                    }
158                }
159                return Ok(());
160            }
161        } else {
162            arg.push(t);
163        }
164    }
165}
166
167fn read_argument<ET: EngineTypes>(
168    engine: &mut EngineReferences<ET>,
169    arg: &mut Vec<ET::Token>,
170    long: bool,
171    token: &ET::Token,
172) -> TeXResult<(), ET> {
173    let eof = |a: &_, s: &_, m: &mut _| TeXError::file_end_while_use(a, s, m, token);
174    loop {
175        let t = match engine.mouth.get_next(engine.aux, engine.state)? {
176            Some(t) => t,
177            None => {
178                TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, token)?;
179                continue;
180            }
181        };
182        match t.command_code() {
183            CommandCode::Primitive if t.is_primitive() == Some(PRIMITIVES.noexpand) => continue,
184            CommandCode::Space => continue,
185            CommandCode::BeginGroup => {
186                if long {
187                    engine.mouth.read_until_endgroup(
188                        engine.aux,
189                        engine.state,
190                        |_, t| {
191                            arg.push(t);
192                            Ok(())
193                        },
194                        eof,
195                    )?;
196                } else {
197                    let par = engine.aux.memory.cs_interner().par();
198                    engine.mouth.read_until_endgroup(
199                        engine.aux,
200                        engine.state,
201                        |a, t| {
202                            if t.is_cs(&par) {
203                                Err(TeXError::ParagraphEnded(
204                                    t.display(
205                                        a.memory.cs_interner(),
206                                        engine.state.get_catcode_scheme(),
207                                        engine.state.get_escape_char(),
208                                    )
209                                    .to_string(),
210                                ))
211                            } else {
212                                arg.push(t);
213                                Ok(())
214                            }
215                        },
216                        eof,
217                    )?;
218                }
219                return Ok(());
220            }
221            _ => (),
222        }
223        arg.push(t);
224        return Ok(());
225    }
226}
227
228/// Default implementation for [`Gullet::expand_until_endgroup`].
229pub fn expand_until_endgroup<
230    ET: EngineTypes,
231    Fn: FnMut(&mut EngineAux<ET>, &ET::State, ET::Token) -> TeXResult<(), ET>,
232>(
233    engine: &mut EngineReferences<ET>,
234    expand_protected: bool,
235    edef_like: bool,
236    token: &ET::Token,
237    mut cont: Fn,
238) -> TeXResult<(), ET> {
239    let mut ingroups = 0;
240    loop {
241        let t = engine.need_next(false, token)?;
242        match t.command_code() {
243            CommandCode::EndGroup => {
244                if ingroups == 0 {
245                    return Ok(());
246                }
247                ingroups -= 1;
248                cont(engine.aux, engine.state, t)?;
249                continue;
250            }
251            CommandCode::BeginGroup => {
252                ingroups += 1;
253                cont(engine.aux, engine.state, t)?;
254                continue;
255            }
256            CommandCode::Primitive if t.is_primitive() == Some(PRIMITIVES.noexpand) => {
257                let next = engine.need_next(false, token)?;
258                cont(engine.aux, engine.state, next)?;
259                continue;
260            }
261            CommandCode::Escape | CommandCode::Active => match engine.resolve(&t) {
262                ResolvedToken::Tk { .. } => cont(engine.aux, engine.state, t)?,
263                ResolvedToken::Cmd(Some(TeXCommand::Macro(m)))
264                    if m.protected && !expand_protected =>
265                {
266                    cont(engine.aux, engine.state, t)?
267                }
268                ResolvedToken::Cmd(Some(TeXCommand::Macro(m))) => {
269                    ET::Gullet::do_macro(engine, m.clone(), t)?
270                }
271                ResolvedToken::Cmd(Some(TeXCommand::Primitive {
272                    name,
273                    cmd: PrimitiveCommand::Conditional(cond),
274                })) => ET::Gullet::do_conditional(engine, *name, t, *cond, false)?,
275                ResolvedToken::Cmd(Some(TeXCommand::Primitive { name, .. }))
276                    if *name == PRIMITIVES.unexpanded =>
277                {
278                    engine.expand_until_bgroup(false, &t)?;
279                    engine.read_until_endgroup(token, |a, s, t| {
280                        if edef_like && t.command_code() == CommandCode::Parameter {
281                            cont(a, s, t.clone())?;
282                        }
283                        cont(a, s, t)
284                    })?;
285                }
286                ResolvedToken::Cmd(Some(TeXCommand::Primitive { name, .. }))
287                    if *name == PRIMITIVES.the =>
288                {
289                    engine.do_the(|a, s, _, t| {
290                        if t.command_code() == CommandCode::Parameter && edef_like {
291                            cont(a, s, t.clone())?;
292                        }
293                        cont(a, s, t)
294                    })?
295                }
296                ResolvedToken::Cmd(Some(TeXCommand::Primitive {
297                    name,
298                    cmd: PrimitiveCommand::SimpleExpandable(e),
299                })) => ET::Gullet::do_simple_expandable(engine, *name, t, *e)?,
300                ResolvedToken::Cmd(Some(TeXCommand::Primitive {
301                    name,
302                    cmd: PrimitiveCommand::Expandable(e),
303                })) => ET::Gullet::do_expandable(engine, *name, t, *e)?,
304                ResolvedToken::Cmd(_) => cont(engine.aux, engine.state, t)?,
305            },
306            _ => cont(engine.aux, engine.state, t)?,
307        }
308    }
309}
310
311pub(crate) fn case_loop<ET: EngineTypes>(
312    engine: &mut EngineReferences<ET>,
313    idx: usize,
314) -> TeXResult<(), ET> {
315    let (mut incond, cond) = {
316        let conds = engine.gullet.get_conditionals();
317        let ic = conds.len() - idx;
318        for _ in 0..ic {
319            conds.pop();
320        }
321        (
322            ic,
323            match conds.pop() {
324                Some(ActiveConditional::Case(c)) => c,
325                _ => unreachable!(),
326            },
327        )
328    };
329    let mut curr_int = <ET::Num as NumSet>::Int::default();
330    let one = <ET::Num as NumSet>::Int::from(1);
331    let mut conds = std::mem::take(engine.gullet.get_conditionals());
332    engine.iterate(
333        |_, state, t| {
334            if !t.is_cs_or_active() {
335                return Ok(None);
336            }
337            match ET::Gullet::char_or_primitive(state, &t) {
338                Some(CharOrPrimitive::Primitive(name))
339                    if name == PRIMITIVES.r#else && incond == 0 =>
340                {
341                    conds.push(ActiveConditional::Else(PRIMITIVES.ifcase));
342                    Ok(Some(()))
343                }
344                Some(CharOrPrimitive::Primitive(name)) if name == PRIMITIVES.fi => {
345                    if incond == 0 {
346                        Ok(Some(()))
347                    } else {
348                        incond -= 1;
349                        Ok(None)
350                    }
351                }
352                Some(CharOrPrimitive::Primitive(name)) if name == PRIMITIVES.or && incond == 0 => {
353                    curr_int = curr_int + one;
354                    if cond == curr_int {
355                        conds.push(ActiveConditional::Case(cond));
356                        Ok(Some(()))
357                    } else {
358                        Ok(None)
359                    }
360                }
361                Some(CharOrPrimitive::Primitive(name)) => {
362                    if let Some(TeXCommand::Primitive {
363                        cmd: PrimitiveCommand::Conditional(_),
364                        ..
365                    }) = state.primitives().get_id(name)
366                    {
367                        incond += 1
368                    }
369                    Ok(None)
370                }
371                _ => Ok(None),
372            }
373        },
374        |a, s, m| TeXError::incomplete_conditional(a, s, m, PRIMITIVES.ifcase),
375    )?;
376    *engine.gullet.get_conditionals() = conds;
377    Ok(())
378}
379
380/// What to do on a false conditional - skips [`Token`]s until the next `\else` (if `allowelse` is true) or `\fi`
381/// If `skipelse` is false, precisely one `\else` is skipped as well (this happens in `\ifcase` when the
382/// appropriate case is already done, so the corresponding `\else` should be skipped).
383pub fn false_loop<ET: EngineTypes>(
384    engine: &mut EngineReferences<ET>,
385    idx: usize,
386    allowelse: bool,
387    mut skipelse: bool,
388) -> TeXResult<(), ET> {
389    let (mut incond, cond) = {
390        let conds = engine.gullet.get_conditionals();
391        let ic = conds.len() - idx;
392        for _ in 0..ic {
393            conds.pop();
394        }
395        (ic, conds.pop().unwrap().name())
396    };
397    let mut conds = std::mem::take(engine.gullet.get_conditionals());
398    engine.iterate(
399        |_, state, t| {
400            if !t.is_cs_or_active() {
401                return Ok(None);
402            }
403            match ET::Gullet::char_or_primitive(state, &t) {
404                Some(CharOrPrimitive::Primitive(name))
405                    if name == PRIMITIVES.r#else && incond == 0 =>
406                {
407                    if allowelse {
408                        conds.push(ActiveConditional::Else(cond));
409                        Ok(Some(()))
410                    } else if skipelse {
411                        skipelse = false;
412                        Ok(None)
413                    } else {
414                        Ok(None) // Apparently spurious \elses are just skipped??
415                    }
416                }
417                Some(CharOrPrimitive::Primitive(name)) if name == PRIMITIVES.fi => {
418                    if incond == 0 {
419                        Ok(Some(()))
420                    } else {
421                        incond -= 1;
422                        Ok(None)
423                    }
424                }
425                Some(CharOrPrimitive::Primitive(name)) => {
426                    if let Some(TeXCommand::Primitive {
427                        cmd: PrimitiveCommand::Conditional(_),
428                        ..
429                    }) = state.primitives().get_id(name)
430                    {
431                        incond += 1
432                    }
433                    Ok(None)
434                }
435                _ => Ok(None),
436            }
437        },
438        |a, s, m| TeXError::incomplete_conditional(a, s, m, cond),
439    )?;
440    *engine.gullet.get_conditionals() = conds;
441    Ok(())
442}
443
444/// Default implementation for [`Gullet::read_string`].
445pub fn read_string<ET: EngineTypes>(
446    engine: &mut EngineReferences<ET>,
447    skip_eq: bool,
448    ret: &mut String,
449    in_token: &ET::Token,
450) -> TeXResult<(), ET> {
451    use std::fmt::Write;
452    let mut quoted = false;
453    let mut braced = false;
454    let mut had_eq = !skip_eq;
455    let mut was_quoted = false;
456    let mut is_empty = true;
457    crate::expand_loop!(engine,token,
458        ResolvedToken::Tk {char,code,..} => match code {
459            CommandCode::Space if is_empty && !was_quoted && !braced => (),
460            CommandCode::BeginGroup if is_empty && !was_quoted && !quoted && !braced => braced = true,
461            CommandCode::Space if quoted || braced => ret.push(' '),
462            CommandCode::Space => return Ok(()),
463            CommandCode::Other if !had_eq && is_empty && matches!(char.try_into(),Ok(b'=')) => {
464                had_eq = true;
465            }
466            CommandCode::EndGroup if braced => return Ok(()),
467            _ => {
468                if matches!(char.try_into(),Ok(b'\"')) {
469                    was_quoted = true;
470                    is_empty = false;
471                    quoted = !quoted;
472                } else {
473                    is_empty = false;
474                    char.display_fmt(ret);
475                }
476            }
477        }
478        ResolvedToken::Cmd(Some(TeXCommand::CharDef(c))) => {
479            c.display_fmt(ret);
480            is_empty = false;
481        }
482        ResolvedToken::Cmd(Some(TeXCommand::Char{char,..})) => {
483            is_empty = false;
484            char.display_fmt(ret)
485        }
486        ResolvedToken::Cmd(Some(TeXCommand::Primitive{cmd:PrimitiveCommand::Relax,..})) if !quoted && !braced => return Ok(()),
487        ResolvedToken::Cmd(_) if !quoted && !braced => {
488            engine.mouth.requeue(token);
489            return Ok(())
490        }
491        _ => {
492            is_empty = false;
493            write!(ret,"{}",token.display(engine.aux.memory.cs_interner(),engine.state.get_catcode_scheme(),engine.state.get_escape_char()))?;
494        }
495    );
496    TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, in_token)
497}
498
499/// Default implementation for [`Gullet::read_maybe_braced_string`].
500pub fn read_maybe_braced_string<ET: EngineTypes>(
501    engine: &mut EngineReferences<ET>,
502    skip_eq: bool,
503    ret: &mut String,
504    in_token: &ET::Token,
505) -> TeXResult<(), ET> {
506    use std::fmt::Write;
507    let mut quoted = false;
508    let mut had_eq = !skip_eq;
509    let mut was_quoted = false;
510    let mut is_empty = true;
511    let mut braced = false;
512    crate::expand_loop!(engine,token,
513        ResolvedToken::Tk {char,code,..} => match code {
514            CommandCode::Space if is_empty && !was_quoted && !braced => (),
515            CommandCode::Space if quoted || braced => ret.push(' '),
516            CommandCode::Space => return Ok(()),
517            CommandCode::BeginGroup if !braced && is_empty => {braced = true;is_empty = false}
518            CommandCode::EndGroup if braced => return Ok(()),
519            CommandCode::Other if !had_eq && is_empty && matches!(char.try_into(),Ok(b'=')) => {
520                had_eq = true;
521            }
522            _ => {
523                if matches!(char.try_into(),Ok(b'\"')) {
524                    if quoted {return Ok(())}
525                    was_quoted = true;
526                    is_empty = false;
527                    quoted = !quoted;
528                } else {
529                    is_empty = false;
530                    char.display_fmt(ret);
531                }
532            }
533        }
534        ResolvedToken::Cmd(Some(TeXCommand::CharDef(c))) => {
535            c.display_fmt(ret);
536            is_empty = false;
537        }
538        ResolvedToken::Cmd(Some(TeXCommand::Char{char,..})) => {
539            is_empty = false;
540            char.display_fmt(ret)
541        }
542        ResolvedToken::Cmd(Some(TeXCommand::Primitive{cmd:PrimitiveCommand::Relax,..})) if !quoted && !braced => return Ok(()),
543        ResolvedToken::Cmd(_) if !quoted => {
544            engine.mouth.requeue(token);
545            return Ok(())
546        }
547        _ => {
548            is_empty = false;
549            write!(ret,"{}",token.display(engine.aux.memory.cs_interner(),engine.state.get_catcode_scheme(),engine.state.get_escape_char()))?;
550        }
551    );
552    TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, in_token)
553}
554
555fn is_ascii_digit(u: u8) -> bool {
556    (48..=57).contains(&u)
557}
558fn is_ascii_oct_digit(u: u8) -> bool {
559    (48..=55).contains(&u)
560}
561fn is_ascii_hex_digit(u: u8) -> bool {
562    is_ascii_digit(u) || (65..=70).contains(&u) || (97..=102).contains(&u)
563}
564
565pub struct NumContinuation<ET: EngineTypes> {
566    pub is_negative: bool,
567    pub next: Either<u8, (TeXCommand<ET>, ET::Token)>,
568}
569
570/// Takes care of the boilerplate for reading a number/dimension/skip.
571/// Expands [`Token`]s until an unexpandable [`Token`] is encountered; skips `=` if `skip_eq` is true,
572/// returns `true` in the first component if an odd number of `-`-characters is encountered before the first digit,
573/// returns in the second component either
574/// the ASCII value of the fist simple [`Token`] encountered, or the relevant [`TeXCommand`]
575/// in the case of a control sequence. Spaces are skipped.
576pub fn read_numeric<ET: EngineTypes>(
577    engine: &mut EngineReferences<ET>,
578    skip_eq: bool,
579    in_token: &ET::Token,
580) -> TeXResult<NumContinuation<ET>, ET> {
581    let mut is_negative = false;
582    let mut had_eq = !skip_eq;
583    crate::expand_loop!(token => if token.is_primitive() == Some(PRIMITIVES.noexpand) {continue};engine,
584        ResolvedToken::Tk {char,code,..} => match (char.try_into(),code) {
585            (_,CommandCode::Space) => (),
586            (Ok(b'='),CommandCode::Other) if !had_eq => {
587                had_eq = true;
588            }
589            (Ok(b'-'),CommandCode::Other) => {
590                is_negative = !is_negative;
591            }
592            (Ok(b'+'),CommandCode::Other) => (),
593            (Ok(b),CommandCode::Other) => return Ok(NumContinuation{is_negative,next:either::Left(b)}),
594            _ => return Err(TeXError::General(format!(
595                "Unexpected token when reading numeric:{}\nTODO: Better error message",
596                token.display(engine.aux.memory.cs_interner(),engine.state.get_catcode_scheme(),engine.state.get_escape_char())
597            )))
598        }
599        ResolvedToken::Cmd(Some(TeXCommand::Char {char,code})) => match ((*char).try_into(),code) {
600            (_,CommandCode::Space) => (),
601            (Ok(b'='),CommandCode::Other) if !had_eq => {
602                had_eq = true;
603            }
604            (Ok(b'-'),CommandCode::Other) => {
605                is_negative = !is_negative;
606            }
607            (Ok(b'+'),CommandCode::Other) => (),
608            (Ok(b),CommandCode::Other) => return Ok(NumContinuation{is_negative,next:either::Left(b)}),
609            _ => return Err(TeXError::General(format!(
610                "Unexpected token when reading numeric:{}\nTODO: Better error message",
611                token.display(engine.aux.memory.cs_interner(),engine.state.get_catcode_scheme(),engine.state.get_escape_char())
612            )))
613        }
614        ResolvedToken::Cmd(None) =>
615            TeXError::undefined(engine.aux,engine.state,engine.mouth,&token)?,
616        ResolvedToken::Cmd(Some(cmd)) => return Ok(NumContinuation{is_negative,next:either::Right((cmd.clone(),token))})
617    );
618    TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, &in_token)?;
619    Ok(NumContinuation {
620        is_negative,
621        next: either::Left(b'0'),
622    })
623}
624
625/// default implementation for [`Gullet::read_int`]
626pub fn read_int<ET: EngineTypes>(
627    engine: &mut EngineReferences<ET>,
628    skip_eq: bool,
629    in_token: &ET::Token,
630) -> TeXResult<ET::Int, ET> {
631    let NumContinuation { is_negative, next } = read_numeric(engine, skip_eq, in_token)?;
632    match next {
633        Either::Left(b) => read_int_byte(engine, is_negative, b),
634        Either::Right((cmd, token)) => read_int_command(engine, is_negative, cmd, token),
635    }
636}
637
638/// reads an integer literal starting with `b`.
639pub fn read_int_byte<ET: EngineTypes>(
640    engine: &mut EngineReferences<ET>,
641    is_negative: bool,
642    b: u8,
643) -> TeXResult<ET::Int, ET> {
644    match b {
645        b'\"' => read_hex_int(engine, is_negative),
646        b'\'' => read_oct_int(engine, is_negative),
647        b'`' => read_int_char(engine, is_negative),
648        b if is_ascii_digit(b) => read_dec_int(engine, is_negative, b),
649        _ => {
650            TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
651            Ok(ET::Int::default())
652        }
653    }
654}
655
656/// reads a character literal triggered by a backtick, when a number is expected
657fn read_int_char<ET: EngineTypes>(
658    engine: &mut EngineReferences<ET>,
659    is_negative: bool,
660) -> TeXResult<ET::Int, ET> {
661    let next = match engine.mouth.get_next(engine.aux, engine.state)? {
662        Some(t) => t,
663        None => {
664            TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
665            return Ok(ET::Int::default());
666        }
667    };
668    let ret = match next.to_enum() {
669        StandardToken::Character(c, _) => {
670            let i: u64 = c.into();
671            match <ET::Num as NumSet>::Int::try_from(i as i64) {
672                Ok(v) => {
673                    if is_negative {
674                        -v
675                    } else {
676                        v
677                    }
678                }
679                _ => {
680                    TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
681                    return Ok(ET::Int::default());
682                }
683            }
684        }
685        StandardToken::ControlSequence(cs) => {
686            let resolved = engine.aux.memory.cs_interner().resolve(&cs);
687            if resolved.len() == 1 {
688                let c: ET::Char = resolved.iter().next().unwrap();
689                if is_negative {
690                    -<ET::Num as NumSet>::Int::from(c.into() as i32)
691                } else {
692                    <ET::Num as NumSet>::Int::from(c.into() as i32)
693                }
694            } else {
695                TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
696                return Ok(ET::Int::default());
697            }
698        }
699        _ => {
700            TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
701            return Ok(ET::Int::default());
702        }
703    };
704    crate::expand_loop!(engine,token,
705        ResolvedToken::Tk{code,..} => {
706            if code != CommandCode::Space {
707                engine.requeue(token)?;
708            }
709            break
710        }
711        ResolvedToken::Cmd(Some(TeXCommand::Char {code:CommandCode::Space,..})) => {
712            break
713        }
714        ResolvedToken::Cmd(_) => {
715            engine.requeue(token)?;
716            break
717        }
718    );
719    Ok(ret)
720}
721
722/// reads an integer from some [`TeXCommand`] that should correspond to an integer value (e.g. `\count` or `\lastpenalty`).
723pub fn read_int_command<ET: EngineTypes>(
724    engine: &mut EngineReferences<ET>,
725    is_negative: bool,
726    cmd: TeXCommand<ET>,
727    token: ET::Token,
728) -> TeXResult<ET::Int, ET> {
729    match cmd {
730        TeXCommand::Primitive {
731            cmd: PrimitiveCommand::Int { read, .. },
732            ..
733        } => {
734            if is_negative {
735                Ok(-read(engine, token)?)
736            } else {
737                read(engine, token)
738            }
739        }
740        TeXCommand::Primitive {
741            name,
742            cmd: PrimitiveCommand::PrimitiveInt,
743        } => Ok(if is_negative {
744            -engine.state.get_primitive_int(name)
745        } else {
746            engine.state.get_primitive_int(name)
747        }),
748        TeXCommand::IntRegister(u) => {
749            let i = engine.state.get_int_register(u);
750            Ok(if is_negative { -i } else { i })
751        }
752        TeXCommand::CharDef(c) => {
753            let val: u64 = c.into();
754            match <ET::Num as NumSet>::Int::try_from(val as i64) {
755                Ok(val) => Ok(if is_negative { -val } else { val }),
756                _ => {
757                    TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
758                    Ok(ET::Int::default())
759                }
760            }
761        }
762        TeXCommand::MathChar(u) => match <ET::Num as NumSet>::Int::try_from(u as i64) {
763            Ok(val) => Ok(if is_negative { -val } else { val }),
764            _ => {
765                TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
766                Ok(ET::Int::default())
767            }
768        },
769        TeXCommand::DimRegister(u) => {
770            let base = ET::Num::dim_to_int(engine.state.get_dim_register(u));
771            Ok(if is_negative { -base } else { base })
772        }
773        TeXCommand::Primitive {
774            name,
775            cmd: PrimitiveCommand::PrimitiveDim,
776        } => {
777            let base = ET::Num::dim_to_int(engine.state.get_primitive_dim(name));
778            Ok(if is_negative { -base } else { base })
779        }
780        TeXCommand::Primitive {
781            cmd: PrimitiveCommand::Dim { read, .. },
782            ..
783        } => {
784            let base = ET::Num::dim_to_int((read)(engine, token)?);
785            Ok(if is_negative { -base } else { base })
786        }
787        TeXCommand::SkipRegister(u) => {
788            let base = ET::Num::dim_to_int(engine.state.get_skip_register(u).base);
789            Ok(if is_negative { -base } else { base })
790        }
791        TeXCommand::Primitive {
792            name,
793            cmd: PrimitiveCommand::PrimitiveSkip,
794        } => {
795            let base = ET::Num::dim_to_int(engine.state.get_primitive_skip(name).base);
796            Ok(if is_negative { -base } else { base })
797        }
798        TeXCommand::Primitive {
799            cmd: PrimitiveCommand::Skip { read, .. },
800            ..
801        } => {
802            let base = ET::Num::dim_to_int((read)(engine, token)?.base);
803            Ok(if is_negative { -base } else { base })
804        }
805        _ => {
806            TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
807            Ok(ET::Int::default())
808        }
809    }
810}
811
812fn read_dec_int<ET: EngineTypes>(
813    engine: &mut EngineReferences<ET>,
814    is_negative: bool,
815    first: u8,
816) -> TeXResult<ET::Int, ET> {
817    let mut ret = (first - b'0') as i32;
818    crate::expand_loop!(engine,token,
819        ResolvedToken::Tk{char,code} => match (char.try_into(),code) {
820            (Ok(b),CommandCode::Other) if is_ascii_digit(b) => {
821                ret = 10*ret + ((b - b'0') as i32);
822            }
823            (_,CommandCode::Space) => return Ok(if is_negative {- ET::Int::from(ret)} else {ET::Int::from(ret)}),
824            _  => {
825                engine.requeue(token)?;
826                return Ok(if is_negative {- ET::Int::from(ret)} else {ET::Int::from(ret)})
827            }
828        }
829        ResolvedToken::Cmd(Some(TeXCommand::Char {code:CommandCode::Space,..})) => {
830            return Ok(if is_negative {- ET::Int::from(ret)} else {ET::Int::from(ret)})
831        }
832        ResolvedToken::Cmd(_) => {
833            engine.requeue(token)?;
834            return Ok(if is_negative {- ET::Int::from(ret)} else {ET::Int::from(ret)})
835        }
836    );
837    Ok(if is_negative {
838        -ET::Int::from(ret)
839    } else {
840        ET::Int::from(ret)
841    })
842}
843
844fn hex_to_num(b: u8) -> i64 {
845    match b {
846        b'0'..=b'9' => (b - b'0') as i64,
847        b'A'..=b'F' => (10 + b - b'A') as i64,
848        b'a'..=b'f' => (10 + b - b'a') as i64,
849        _ => unreachable!(),
850    }
851}
852
853fn read_hex_int<ET: EngineTypes>(
854    engine: &mut EngineReferences<ET>,
855    is_negative: bool,
856) -> TeXResult<ET::Int, ET> {
857    let mut ret = 0i64;
858    let mut empty = true;
859    crate::expand_loop!(engine,token,
860        ResolvedToken::Tk{char,code} => match (char.try_into(),code) {
861            (Ok(b),CommandCode::Other|CommandCode::Letter) if is_ascii_hex_digit(b) => {
862                ret = 16*ret + hex_to_num(b);
863                empty = false;
864            }
865            (_,CommandCode::Space) => {
866                let ret = if is_negative {-ret} else {ret};
867                match ret.try_into() {
868                    Ok(v) => return Ok(v),
869                    _ => return Err(TeXError::General(format!("Integer out of range: {}\nTODO: Better error message",ret)))
870                }
871            }
872            _ if !empty => {
873                engine.requeue(token)?;
874                let ret = if is_negative {-ret} else {ret};
875                match ret.try_into() {
876                    Ok(v) => return Ok(v),
877                    _ => return Err(TeXError::General(format!("Integer out of range: {}\nTODO: Better error message",ret)))
878                }
879            }
880            _ => {
881                engine.requeue(token)?;
882                TeXError::missing_number(engine.aux,engine.state,engine.mouth)?;
883                return Ok(ET::Int::default())
884            }
885        }
886        ResolvedToken::Cmd(Some(TeXCommand::Char {code:CommandCode::Space,..})) => {
887            let ret = if is_negative {-ret} else {ret};
888            match ret.try_into() {
889                Ok(v) => return Ok(v),
890                _ => return Err(TeXError::General(format!("Integer out of range: {}\nTODO: Better error message",ret)))
891            }
892        }
893        ResolvedToken::Cmd(_) if !empty => {
894            engine.mouth.requeue(token);
895            let ret = if is_negative {-ret} else {ret};
896            match ret.try_into() {
897                Ok(v) => return Ok(v),
898                _ => return Err(TeXError::General(format!("Integer out of range: {}\nTODO: Better error message",ret)))
899            }
900        }
901        _ => {
902            engine.requeue(token)?;
903            TeXError::missing_number(engine.aux,engine.state,engine.mouth)?;
904            return Ok(ET::Int::default())
905        }
906    );
907    if empty {
908        TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
909        Ok(ET::Int::default())
910    } else {
911        let ret = if is_negative { -ret } else { ret };
912        match ret.try_into() {
913            Ok(v) => Ok(v),
914            _ => Err(TeXError::General(format!(
915                "Integer out of range: {}\nTODO: Better error message",
916                ret
917            ))),
918        }
919    }
920}
921
922fn read_oct_int<ET: EngineTypes>(
923    engine: &mut EngineReferences<ET>,
924    is_negative: bool,
925) -> TeXResult<ET::Int, ET> {
926    let mut ret = 0i32;
927    let mut empty = true;
928    crate::expand_loop!(engine,token,
929        ResolvedToken::Tk{char,code} => match (char.try_into(),code) {
930            (Ok(b),CommandCode::Other) if is_ascii_oct_digit(b) => {
931                ret = 8*ret + ((b - b'0') as i32);
932                empty = false
933            }
934            (_,CommandCode::Space) if !empty => return Ok(if is_negative {- ET::Int::from(ret)} else {ET::Int::from(ret)}),
935            _  if !empty => {
936                engine.requeue(token)?;
937                return Ok(if is_negative {- ET::Int::from(ret)} else {ET::Int::from(ret)})
938            }
939            _ => {
940                engine.requeue(token)?;
941                TeXError::missing_number(engine.aux,engine.state,engine.mouth)?;
942                return Ok(ET::Int::default())
943            }
944        }
945        ResolvedToken::Cmd(Some(TeXCommand::Char {code:CommandCode::Space,..})) if !empty => {
946            return Ok(if is_negative {- ET::Int::from(ret)} else {ET::Int::from(ret)})
947        }
948        ResolvedToken::Cmd(_) if !empty => {
949            engine.mouth.requeue(token);
950            return Ok(if is_negative {- ET::Int::from(ret)} else {ET::Int::from(ret)})
951        }
952        _ => {
953            engine.requeue(token)?;
954            TeXError::missing_number(engine.aux,engine.state,engine.mouth)?;
955            return Ok(ET::Int::default())
956        }
957    );
958    if empty {
959        TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
960        Ok(ET::Int::default())
961    } else {
962        Ok(if is_negative {
963            -ET::Int::from(ret)
964        } else {
965            ET::Int::from(ret)
966        })
967    }
968}
969
970/// Default implementation for [`Gullet::read_dim`].
971pub fn read_dim<ET: EngineTypes>(
972    engine: &mut EngineReferences<ET>,
973    skip_eq: bool,
974    in_token: &ET::Token,
975) -> TeXResult<ET::Dim, ET> {
976    let NumContinuation { is_negative, next } = read_numeric(engine, skip_eq, in_token)?;
977    match next {
978        Either::Left(b) => read_dim_byte(engine, is_negative, b),
979        Either::Right((cmd, token)) => read_dim_command(engine, is_negative, cmd, token),
980    }
981}
982
983/// reads a dimension literal starting with `b`.
984pub fn read_dim_byte<ET: EngineTypes>(
985    engine: &mut EngineReferences<ET>,
986    is_negative: bool,
987    b: u8,
988) -> TeXResult<ET::Dim, ET> {
989    if b == b',' || b == b'.' {
990        read_dim_float(engine, is_negative, b'.')
991    } else if b == b'`' {
992        let i = read_int_char(engine, is_negative)?.into();
993        read_unit_or_dim(engine, i as f64)
994    } else if is_ascii_digit(b) {
995        read_dim_float(engine, is_negative, b)
996    } else {
997        TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
998        read_unit_or_dim(engine, 0f64)
999    }
1000}
1001
1002/// reads a dimension from some [`TeXCommand`] that should correspond to a dimension value (e.g. `\dimen` or `\hsize`).
1003/// If the command is a skip, the base value is taken. If the command is an integer,
1004/// it looks for an appropriate unit.
1005pub fn read_dim_command<ET: EngineTypes>(
1006    engine: &mut EngineReferences<ET>,
1007    is_negative: bool,
1008    cmd: TeXCommand<ET>,
1009    token: ET::Token,
1010) -> TeXResult<ET::Dim, ET> {
1011    match cmd {
1012        TeXCommand::IntRegister(u) => {
1013            let i = engine.state.get_int_register(u);
1014            let i = if is_negative { -i } else { i };
1015            let i = i.into() as f64;
1016            read_unit_or_dim(engine, i)
1017        }
1018        TeXCommand::Primitive {
1019            name,
1020            cmd: PrimitiveCommand::PrimitiveInt,
1021        } => {
1022            let i = engine.state.get_primitive_int(name);
1023            let i = if is_negative { -i } else { i };
1024            let i = i.into() as f64;
1025            read_unit_or_dim(engine, i)
1026        }
1027        TeXCommand::Primitive {
1028            cmd: PrimitiveCommand::Int { read, .. },
1029            ..
1030        } => {
1031            let i = if is_negative {
1032                -read(engine, token)?
1033            } else {
1034                read(engine, token)?
1035            };
1036            let f = i.into() as f64;
1037            read_unit_or_dim(engine, f)
1038        }
1039        TeXCommand::CharDef(c) => {
1040            let val = if is_negative {
1041                -(c.into() as f64)
1042            } else {
1043                c.into() as f64
1044            };
1045            read_unit_or_dim(engine, val)
1046        }
1047        TeXCommand::MathChar(c) => {
1048            let val = if is_negative { -(c as f64) } else { c as f64 };
1049            read_unit_or_dim(engine, val)
1050        }
1051        TeXCommand::DimRegister(u) => {
1052            if is_negative {
1053                Ok(-engine.state.get_dim_register(u))
1054            } else {
1055                Ok(engine.state.get_dim_register(u))
1056            }
1057        }
1058        TeXCommand::Primitive {
1059            name,
1060            cmd: PrimitiveCommand::PrimitiveDim,
1061        } => {
1062            let val = engine.state.get_primitive_dim(name);
1063            if is_negative {
1064                Ok(-val)
1065            } else {
1066                Ok(val)
1067            }
1068        }
1069        TeXCommand::Primitive {
1070            cmd: PrimitiveCommand::Dim { read, .. },
1071            ..
1072        } => {
1073            let val = read(engine, token)?;
1074            if is_negative {
1075                Ok(-val)
1076            } else {
1077                Ok(val)
1078            }
1079        }
1080        TeXCommand::SkipRegister(u) => {
1081            let val = engine.state.get_skip_register(u).base;
1082            if is_negative {
1083                Ok(-val)
1084            } else {
1085                Ok(val)
1086            }
1087        }
1088        TeXCommand::Primitive {
1089            name,
1090            cmd: PrimitiveCommand::PrimitiveSkip,
1091        } => {
1092            let val = engine.state.get_primitive_skip(name).base;
1093            if is_negative {
1094                Ok(-val)
1095            } else {
1096                Ok(val)
1097            }
1098        }
1099        TeXCommand::Primitive {
1100            cmd: PrimitiveCommand::Skip { read, .. },
1101            ..
1102        } => {
1103            let val = read(engine, token)?.base;
1104            if is_negative {
1105                Ok(-val)
1106            } else {
1107                Ok(val)
1108            }
1109        }
1110        _ => {
1111            TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
1112            read_unit_or_dim(engine, 0f64)
1113        }
1114    }
1115}
1116
1117fn read_dim_float<ET: EngineTypes>(
1118    engine: &mut EngineReferences<ET>,
1119    is_negative: bool,
1120    first: u8,
1121) -> TeXResult<ET::Dim, ET> {
1122    let mut ret = 0f64;
1123    let mut in_decimal = first == b'.';
1124    let mut fac = 10f64;
1125    if !in_decimal {
1126        ret = (first - b'0') as f64;
1127    }
1128    crate::expand_loop!(engine,token,
1129        ResolvedToken::Tk {char,code} => match (char.try_into(),code) {
1130            (Ok(b),CommandCode::Other) if is_ascii_digit(b) => {
1131                if in_decimal {
1132                    ret += (b - b'0') as f64 / fac;
1133                    fac *= 10.0;
1134                } else {
1135                    ret = 10.0*ret + ((b - b'0') as f64);
1136                }
1137            }
1138            (Ok(b','|b'.'),CommandCode::Other) => {
1139                if in_decimal {
1140                    TeXError::missing_number(engine.aux,engine.state,engine.mouth)?;
1141                    return read_unit_or_dim(engine,0f64)
1142                }
1143                in_decimal = true;
1144            }
1145            (_,CommandCode::Space) => {
1146                let f = if is_negative {-ret} else {ret};
1147                return read_unit_or_dim(engine,f)
1148            }
1149            (Ok(b),CommandCode::Other | CommandCode::Letter) => {
1150                let f = if is_negative {-ret} else {ret};
1151                return read_dim_unit(engine,f,Some((b,token)))
1152            }
1153            _ => {
1154                engine.requeue(token)?;
1155                TeXError::missing_number(engine.aux,engine.state,engine.mouth)?;
1156                return read_unit_or_dim(engine,0f64)
1157            }
1158        }
1159        ResolvedToken::Cmd(Some(c)) => {
1160            let f = if is_negative {-ret} else {ret};
1161            return read_unit_cmd(engine,f,c.clone(),token)
1162        }
1163        _ => {
1164            engine.requeue(token)?;
1165            TeXError::missing_number(engine.aux,engine.state,engine.mouth)?;
1166            return read_unit_or_dim(engine,0f64)
1167        }
1168    );
1169    TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
1170    read_unit_or_dim(engine, 0f64)
1171}
1172
1173fn read_unit_or_dim<ET: EngineTypes>(
1174    engine: &mut EngineReferences<ET>,
1175    float: f64,
1176) -> TeXResult<ET::Dim, ET> {
1177    crate::expand_loop!(engine,token,
1178        ResolvedToken::Tk {char,code} => match (char.try_into(),code) {
1179            (Ok(b),CommandCode::Other | CommandCode::Letter) => {
1180                return read_dim_unit(engine,float,Some((b,token)))
1181            }
1182            (_,CommandCode::Space) => (),
1183            _ => {
1184                engine.requeue(token)?;
1185                TeXError::missing_unit(engine.aux,engine.state,engine.mouth)?;
1186                return Ok(ET::Dim::from_float(engine,float,b"pt"))
1187            }
1188        }
1189        ResolvedToken::Cmd(Some(TeXCommand::Char {code:CommandCode::Space,..})) => (),
1190        ResolvedToken::Cmd(Some(TeXCommand::Char{char,code:CommandCode::Other | CommandCode::Letter})) => match (*char).try_into() {
1191            Ok(b) => return read_dim_unit(engine,float,Some((b,token))),
1192            _ => {
1193                engine.requeue(token)?;
1194                TeXError::missing_unit(engine.aux,engine.state,engine.mouth)?;
1195                return Ok(ET::Dim::from_float(engine,float,b"pt"))
1196            }
1197        }
1198        ResolvedToken::Cmd(Some(cmd)) => return read_unit_cmd(engine,float,cmd.clone(),token),
1199        ResolvedToken::Cmd(None) =>
1200            TeXError::undefined(engine.aux,engine.state,engine.mouth,&token)?,
1201    );
1202    TeXError::missing_unit(engine.aux, engine.state, engine.mouth)?;
1203    Ok(ET::Dim::from_float(engine, float, b"pt"))
1204}
1205
1206fn read_unit_cmd<ET: EngineTypes>(
1207    engine: &mut EngineReferences<ET>,
1208    float: f64,
1209    cmd: TeXCommand<ET>,
1210    token: ET::Token,
1211) -> TeXResult<ET::Dim, ET> {
1212    match cmd {
1213        TeXCommand::IntRegister(u) => {
1214            let base = ET::Dim::from_sp(engine.state.get_int_register(u).into() as i32);
1215            Ok(base.scale_float(float))
1216        }
1217        TeXCommand::Primitive {
1218            name,
1219            cmd: PrimitiveCommand::PrimitiveInt,
1220        } => {
1221            let base = ET::Dim::from_sp(engine.state.get_primitive_int(name).into() as i32);
1222            Ok(base.scale_float(float))
1223        }
1224        TeXCommand::Primitive {
1225            cmd: PrimitiveCommand::Int { read, .. },
1226            ..
1227        } => {
1228            let base = ET::Dim::from_sp(read(engine, token)?.into() as i32);
1229            Ok(base.scale_float(float))
1230        }
1231        TeXCommand::DimRegister(u) => {
1232            let base = engine.state.get_dim_register(u);
1233            Ok(base.scale_float(float))
1234        }
1235        TeXCommand::Primitive {
1236            name,
1237            cmd: PrimitiveCommand::PrimitiveDim,
1238        } => {
1239            let base = engine.state.get_primitive_dim(name);
1240            Ok(base.scale_float(float))
1241        }
1242        TeXCommand::Primitive {
1243            cmd: PrimitiveCommand::Dim { read, .. },
1244            ..
1245        } => {
1246            let base = read(engine, token)?;
1247            Ok(base.scale_float(float))
1248        }
1249        TeXCommand::SkipRegister(u) => {
1250            let base = engine.state.get_skip_register(u).base;
1251            Ok(base.scale_float(float))
1252        }
1253        TeXCommand::Primitive {
1254            name,
1255            cmd: PrimitiveCommand::PrimitiveSkip,
1256        } => {
1257            let base = engine.state.get_primitive_skip(name).base;
1258            Ok(base.scale_float(float))
1259        }
1260        TeXCommand::Primitive {
1261            cmd: PrimitiveCommand::Skip { read, .. },
1262            ..
1263        } => {
1264            let base = read(engine, token)?.base;
1265            Ok(base.scale_float(float))
1266        }
1267        _ => {
1268            engine.requeue(token)?;
1269            TeXError::missing_unit(engine.aux, engine.state, engine.mouth)?;
1270            Ok(ET::Dim::from_float(engine, float, b"pt"))
1271        }
1272    }
1273}
1274
1275fn read_dim_unit<ET: EngineTypes>(
1276    engine: &mut EngineReferences<ET>,
1277    mut float: f64,
1278    mut first: Option<(u8, ET::Token)>,
1279) -> TeXResult<ET::Dim, ET> {
1280    let is_true = match &first {
1281        Some((b't' | b'T', _)) => read_keyword(engine, b"true", std::mem::take(&mut first))?,
1282        Some(_) => false,
1283        None => read_keyword(engine, b"true", None)?,
1284    };
1285    if is_true {
1286        let mag = engine.state.get_primitive_int(PRIMITIVES.mag);
1287        float *= Into::<i64>::into(mag) as f64 / 1000.0;
1288    }
1289    let units = ET::Dim::UNITS;
1290    match read_keywords(engine, units, first)? {
1291        Some(d) => Ok(<ET::Num as NumSet>::Dim::from_float(engine, float, d)),
1292        _ => {
1293            TeXError::missing_unit(engine.aux, engine.state, engine.mouth)?;
1294            Ok(ET::Dim::from_float(engine, float, b"pt"))
1295        }
1296    }
1297}
1298
1299/// Default implementation for [`Gullet::read_skip`].
1300pub fn read_skip<ET: EngineTypes>(
1301    engine: &mut EngineReferences<ET>,
1302    skip_eq: bool,
1303    in_token: &ET::Token,
1304) -> TeXResult<Skip<ET::Dim>, ET> {
1305    let NumContinuation { is_negative, next } = read_numeric(engine, skip_eq, in_token)?;
1306    match next {
1307        Either::Left(b) => read_skip_byte(engine, is_negative, b),
1308        Either::Right((cmd, token)) => read_skip_command(engine, is_negative, cmd, token),
1309    }
1310}
1311
1312/// reads a skip literal starting with `b`.
1313pub fn read_skip_byte<ET: EngineTypes>(
1314    engine: &mut EngineReferences<ET>,
1315    is_negative: bool,
1316    b: u8,
1317) -> TeXResult<Skip<ET::Dim>, ET> {
1318    if b == b',' || b == b'.' {
1319        read_skip_dim(engine, is_negative, b'.')
1320    } else if is_ascii_digit(b) {
1321        read_skip_dim(engine, is_negative, b)
1322    } else {
1323        TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
1324        read_skip_dim(engine, false, b)
1325    }
1326}
1327
1328/// reads a skip from some [`TeXCommand`] that should correspond to a skip value (e.g. `\skip` or `\lastskip`).
1329/// If the command only yields an integer or dimension, the remaining components of the skip are read.
1330pub fn read_skip_command<ET: EngineTypes>(
1331    engine: &mut EngineReferences<ET>,
1332    is_negative: bool,
1333    cmd: TeXCommand<ET>,
1334    token: ET::Token,
1335) -> TeXResult<Skip<ET::Dim>, ET> {
1336    match cmd {
1337        TeXCommand::IntRegister(u) => {
1338            let base = engine.state.get_int_register(u);
1339            let base = if is_negative { -base } else { base };
1340            let base = read_unit_or_dim(engine, base.into() as f64)?;
1341            read_skip_ii(engine, base)
1342        }
1343        TeXCommand::Primitive {
1344            name,
1345            cmd: PrimitiveCommand::PrimitiveInt,
1346        } => {
1347            let base = engine.state.get_primitive_int(name);
1348            let base = if is_negative { -base } else { base };
1349            let base = read_unit_or_dim(engine, base.into() as f64)?;
1350            read_skip_ii(engine, base)
1351        }
1352        TeXCommand::Primitive {
1353            cmd: PrimitiveCommand::Int { read, .. },
1354            ..
1355        } => {
1356            let base = read(engine, token)?;
1357            let base = if is_negative { -base } else { base };
1358            let base = read_unit_or_dim(engine, base.into() as f64)?;
1359            read_skip_ii(engine, base)
1360        }
1361        TeXCommand::CharDef(c) => {
1362            let val: u64 = c.into();
1363            let base = read_unit_or_dim(engine, val as f64)?;
1364            read_skip_ii(engine, base)
1365        }
1366        TeXCommand::MathChar(u) => {
1367            let base = read_unit_or_dim(engine, u as f64)?;
1368            read_skip_ii(engine, base)
1369        }
1370        TeXCommand::DimRegister(u) => {
1371            let base = engine.state.get_dim_register(u);
1372            let base = if is_negative { -base } else { base };
1373            read_skip_ii(engine, base)
1374        }
1375        TeXCommand::Primitive {
1376            name,
1377            cmd: PrimitiveCommand::PrimitiveDim,
1378        } => {
1379            let base = engine.state.get_primitive_dim(name);
1380            let base = if is_negative { -base } else { base };
1381            read_skip_ii(engine, base)
1382        }
1383        TeXCommand::Primitive {
1384            cmd: PrimitiveCommand::Dim { read, .. },
1385            ..
1386        } => {
1387            let base = read(engine, token)?;
1388            let base = if is_negative { -base } else { base };
1389            read_skip_ii(engine, base)
1390        }
1391        TeXCommand::SkipRegister(u) => {
1392            let base = engine.state.get_skip_register(u);
1393            Ok(if is_negative { -base } else { base })
1394        }
1395        TeXCommand::Primitive {
1396            name,
1397            cmd: PrimitiveCommand::PrimitiveSkip,
1398        } => {
1399            let val = engine.state.get_primitive_skip(name);
1400            Ok(if is_negative { -val } else { val })
1401        }
1402        TeXCommand::Primitive {
1403            cmd: PrimitiveCommand::Skip { read, .. },
1404            ..
1405        } => {
1406            let val = read(engine, token)?;
1407            Ok(if is_negative { -val } else { val })
1408        }
1409        _ => {
1410            engine.requeue(token)?;
1411            TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
1412            let base = read_unit_or_dim(engine, 0f64)?;
1413            read_skip_ii(engine, base)
1414        }
1415    }
1416}
1417
1418const PLUS: &[u8] = b"plus";
1419const MINUS: &[u8] = b"minus";
1420
1421fn read_skip_dim<ET: EngineTypes>(
1422    engine: &mut EngineReferences<ET>,
1423    is_negative: bool,
1424    first: u8,
1425) -> TeXResult<Skip<ET::Dim>, ET> {
1426    let base = read_dim_float(engine, is_negative, first)?;
1427    read_skip_ii(engine, base)
1428}
1429
1430fn read_skip_ii<ET: EngineTypes>(
1431    engine: &mut EngineReferences<ET>,
1432    base: <ET::Num as NumSet>::Dim,
1433) -> TeXResult<Skip<ET::Dim>, ET> {
1434    match read_keywords(engine, &[PLUS, MINUS], None)? {
1435        Some(b) if b == PLUS => {
1436            let stretch = read_stretch(engine)?;
1437            if read_keyword(engine, MINUS, None)? {
1438                Ok(Skip::new(base, Some(stretch), Some(read_stretch(engine)?)))
1439            } else {
1440                Ok(Skip::new(base, Some(stretch), None))
1441            }
1442        }
1443        Some(b) if b == MINUS => {
1444            let shrink = read_stretch(engine)?;
1445            if read_keyword(engine, PLUS, None)? {
1446                Ok(Skip::new(base, Some(read_stretch(engine)?), Some(shrink)))
1447            } else {
1448                Ok(Skip::new(base, None, Some(shrink)))
1449            }
1450        }
1451        _ => Ok(Skip::new(base, None, None)),
1452    }
1453}
1454
1455fn read_stretch<ET: EngineTypes>(
1456    engine: &mut EngineReferences<ET>,
1457) -> TeXResult<StretchShrink<ET::Dim>, ET> {
1458    let mut is_negative = false;
1459    crate::expand_loop!(engine,token,
1460        ResolvedToken::Tk {char,code } => match (char.try_into(),code) {
1461            (_,CommandCode::Space) => (),
1462            (Ok(b'-'),CommandCode::Other) => {
1463                is_negative = !is_negative;
1464            }
1465            (Ok(b'+'),CommandCode::Other) => (),
1466            (Ok(b),CommandCode::Other) if is_ascii_digit(b) => return read_stretch_float(engine,is_negative,b),
1467            (Ok(b','|b'.'),CommandCode::Other) => return read_stretch_float(engine,is_negative,b'.'),
1468            _ => {
1469                engine.requeue(token)?;
1470                TeXError::missing_number(engine.aux,engine.state,engine.mouth)?;
1471                return read_stretch_unit(engine,0f64,None)
1472            }
1473        }
1474        ResolvedToken::Cmd(cmd) => match cmd {
1475            Some(TeXCommand::DimRegister(u)) => {
1476                let base = engine.state.get_dim_register(*u);
1477                return Ok(StretchShrink::Dim(if is_negative {-base} else {base}))
1478            }
1479            Some(TeXCommand::Primitive{name,cmd:PrimitiveCommand::PrimitiveDim}) => {
1480                let base = engine.state.get_primitive_dim(*name);
1481                return Ok(StretchShrink::Dim(if is_negative {-base} else {base}))
1482            }
1483            Some(TeXCommand::Primitive{cmd:PrimitiveCommand::Dim{read,..},..}) => {
1484                let base = read(engine,token)?;
1485                return Ok(StretchShrink::Dim(if is_negative {-base} else {base}))
1486            }
1487            Some(TeXCommand::IntRegister(u)) => {
1488                let base = engine.state.get_int_register(*u).into() as f64;
1489                return read_stretch_unit(engine,if is_negative {-base} else {base},None)
1490            }
1491            Some(TeXCommand::Primitive{name,cmd:PrimitiveCommand::PrimitiveInt}) => {
1492                let base = engine.state.get_primitive_int(*name).into() as f64;
1493                return read_stretch_unit(engine,if is_negative {-base} else {base},None)
1494            }
1495            Some(TeXCommand::Primitive{cmd:PrimitiveCommand::Int{read,..},..}) => {
1496                let base = read(engine,token)?.into() as f64;
1497                return read_stretch_unit(engine,if is_negative {-base} else {base},None)
1498            }
1499            Some(TeXCommand::CharDef(c)) => {
1500                let base = Into::<u64>::into(*c) as f64;
1501                return read_stretch_unit(engine,if is_negative {-base} else {base},None)
1502            }
1503            _ => {
1504                engine.requeue(token)?;
1505                TeXError::missing_number(engine.aux,engine.state,engine.mouth)?;
1506                return read_stretch_unit(engine,0f64,None)
1507            }
1508        }
1509    );
1510    TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
1511    read_stretch_unit(engine, 0f64, None)
1512}
1513fn read_stretch_float<ET: EngineTypes>(
1514    engine: &mut EngineReferences<ET>,
1515    is_negative: bool,
1516    first: u8,
1517) -> TeXResult<StretchShrink<ET::Dim>, ET> {
1518    let mut ret = 0f64;
1519    let mut in_decimal = first == b'.';
1520    let mut scale = 10f64;
1521    if !in_decimal {
1522        ret = (first - b'0') as f64;
1523    }
1524    crate::expand_loop!(engine,token,
1525        ResolvedToken::Tk {char,code} => match (char.try_into(),code) {
1526            (Ok(b),CommandCode::Other) if is_ascii_digit(b) => {
1527                if in_decimal {
1528                    ret += (b - b'0') as f64 / scale;
1529                    scale *= 10.0;
1530                } else {
1531                    ret = 10.0*ret + ((b - b'0') as f64);
1532                }
1533            }
1534            (Ok(b','|b'.'),CommandCode::Other) => {
1535                if in_decimal {
1536                    engine.requeue(token)?;
1537                    TeXError::missing_unit(engine.aux,engine.state,engine.mouth)?;
1538                    return Ok(StretchShrink::Dim(ET::Dim::from_float(engine,ret,b"pt")))
1539                }
1540                in_decimal = true;
1541            }
1542            (_,CommandCode::Space) => {
1543                let f = if is_negative {-ret} else {ret};
1544                return read_stretch_unit(engine,f,None)
1545            }
1546            (Ok(b),CommandCode::Other | CommandCode::Letter) => {
1547                let f = if is_negative {-ret} else {ret};
1548                return read_stretch_unit(engine,f,Some((b,token)))
1549            }
1550            _ => {
1551                engine.requeue(token)?;
1552                TeXError::missing_unit(engine.aux,engine.state,engine.mouth)?;
1553                return Ok(StretchShrink::Dim(ET::Dim::from_float(engine,ret,b"pt")))
1554            }
1555        }
1556        ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,cmd:PrimitiveCommand::PrimitiveDim})) => {
1557            let base = engine.state.get_primitive_dim(*name);
1558            let scale = if is_negative {-ret} else {ret};
1559            return Ok(StretchShrink::Dim(base.scale_float(scale)))
1560        }
1561        ResolvedToken::Cmd(Some(TeXCommand::Primitive{cmd:PrimitiveCommand::Dim{read,..},..})) => {
1562            let base = read(engine,token)?;
1563            let scale = if is_negative {-ret} else {ret};
1564            return Ok(StretchShrink::Dim(base.scale_float(scale)))
1565        }
1566        ResolvedToken::Cmd(Some(TeXCommand::DimRegister(u))) => {
1567            let base = engine.state.get_dim_register(*u);
1568            let scale = if is_negative {-ret} else {ret};
1569            return Ok(StretchShrink::Dim(base.scale_float(scale)))
1570        }
1571        ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,cmd:PrimitiveCommand::PrimitiveSkip})) => {
1572            let base = engine.state.get_primitive_skip(*name).base;
1573            let scale = if is_negative {-ret} else {ret};
1574            return Ok(StretchShrink::Dim(base.scale_float(scale)))
1575        }
1576        ResolvedToken::Cmd(Some(TeXCommand::SkipRegister(u))) => {
1577            let base = engine.state.get_skip_register(*u).base;
1578            let scale = if is_negative {-ret} else {ret};
1579            return Ok(StretchShrink::Dim(base.scale_float(scale)))
1580        }
1581        _ => {
1582            engine.requeue(token)?;
1583            TeXError::missing_unit(engine.aux,engine.state,engine.mouth)?;
1584            return Ok(StretchShrink::Dim(ET::Dim::from_float(engine,ret,b"pt")))
1585        }
1586    );
1587    TeXError::missing_unit(engine.aux, engine.state, engine.mouth)?;
1588    Ok(StretchShrink::Dim(ET::Dim::from_float(engine, ret, b"pt")))
1589}
1590fn read_stretch_unit<ET: EngineTypes>(
1591    engine: &mut EngineReferences<ET>,
1592    mut float: f64,
1593    mut first: Option<(u8, ET::Token)>,
1594) -> TeXResult<StretchShrink<ET::Dim>, ET> {
1595    let is_true = match &first {
1596        Some((b't' | b'T', _)) => read_keyword(engine, b"true", std::mem::take(&mut first))?,
1597        Some(_) => false,
1598        None => read_keyword(engine, b"true", None)?,
1599    };
1600    if is_true {
1601        let mag = engine.state.get_primitive_int(PRIMITIVES.mag);
1602        float *= Into::<i64>::into(mag) as f64 / 1000.0;
1603    }
1604    match read_keywords(engine, STRETCH_SHRINK_UNITS, first)? {
1605        Some(d) => Ok(StretchShrink::from_float(engine, float, d)),
1606        _ => {
1607            let ret = read_unit_or_dim(engine, float)?;
1608            Ok(StretchShrink::Dim(ret))
1609        }
1610    }
1611}
1612
1613/// Default implementation for [`Gullet::read_muskip`].
1614pub fn read_muskip<ET: EngineTypes>(
1615    engine: &mut EngineReferences<ET>,
1616    skip_eq: bool,
1617    in_token: &ET::Token,
1618) -> TeXResult<MuSkip<ET::MuDim>, ET> {
1619    let NumContinuation { is_negative, next } = read_numeric(engine, skip_eq, in_token)?;
1620    match next {
1621        Either::Left(b) => read_muskip_byte(engine, is_negative, b, |d, e| read_muskip_ii(e, d)),
1622        Either::Right((cmd, token)) => read_muskip_command(
1623            engine,
1624            is_negative,
1625            cmd,
1626            token,
1627            |d, e| read_muskip_ii(e, d),
1628            Ok,
1629        ),
1630    }
1631}
1632
1633/// Default implementation for [`Gullet::read_mudim`].
1634pub fn read_mudim<ET: EngineTypes>(
1635    engine: &mut EngineReferences<ET>,
1636    skip_eq: bool,
1637    in_token: &ET::Token,
1638) -> TeXResult<ET::MuDim, ET> {
1639    let NumContinuation { is_negative, next } = read_numeric(engine, skip_eq, in_token)?;
1640    match next {
1641        Either::Left(b) => read_muskip_byte(engine, is_negative, b, |d, _| Ok(d)),
1642        Either::Right((cmd, token)) => read_muskip_command(
1643            engine,
1644            is_negative,
1645            cmd,
1646            token,
1647            |d, _| Ok(d),
1648            |s| Ok(s.base),
1649        ),
1650    }
1651}
1652
1653/// reads a muskip or mudim literal starting with `b`.
1654pub fn read_muskip_byte<R, ET: EngineTypes>(
1655    engine: &mut EngineReferences<ET>,
1656    is_negative: bool,
1657    b: u8,
1658    kern: fn(ET::MuDim, &mut EngineReferences<ET>) -> TeXResult<R, ET>,
1659) -> TeXResult<R, ET> {
1660    if b == b',' || b == b'.' {
1661        read_muskip_dim(engine, is_negative, b'.', kern)
1662    } else if is_ascii_digit(b) {
1663        read_muskip_dim(engine, is_negative, b, kern)
1664    } else {
1665        TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
1666        read_muskip_dim(engine, false, b, kern)
1667    }
1668}
1669
1670/// reads a muskip or mudim value from some [`TeXCommand`] that should correspond to a muskip value (e.g. `\muskip` or `\lastskip`).
1671pub fn read_muskip_command<R, ET: EngineTypes>(
1672    engine: &mut EngineReferences<ET>,
1673    is_negative: bool,
1674    cmd: TeXCommand<ET>,
1675    token: ET::Token,
1676    kern: fn(ET::MuDim, &mut EngineReferences<ET>) -> TeXResult<R, ET>,
1677    skip: fn(MuSkip<ET::MuDim>) -> TeXResult<R, ET>,
1678) -> TeXResult<R, ET> {
1679    match cmd {
1680        TeXCommand::MuSkipRegister(u) => {
1681            let base = engine.state.get_muskip_register(u);
1682            let base = if is_negative { -base } else { base };
1683            skip(base)
1684        }
1685        TeXCommand::Primitive {
1686            name,
1687            cmd: PrimitiveCommand::PrimitiveMuSkip,
1688        } => {
1689            let base = engine.state.get_primitive_muskip(name);
1690            let base = if is_negative { -base } else { base };
1691            skip(base)
1692        }
1693        TeXCommand::Primitive {
1694            cmd: PrimitiveCommand::MuSkip { read, .. },
1695            ..
1696        } => {
1697            let base = read(engine, token)?;
1698            let base = if is_negative { -base } else { base };
1699            skip(base)
1700        }
1701        TeXCommand::CharDef(c) => {
1702            let base = c.into() as i64;
1703            let base = (if is_negative { -base } else { base }) as f64;
1704            let base = read_mudim_unit(engine, base, None)?;
1705            kern(base, engine)
1706        }
1707        TeXCommand::IntRegister(u) => {
1708            let base = engine.state.get_int_register(u);
1709            let base = (if is_negative { -base } else { base }).into() as f64;
1710            let base = read_mudim_unit(engine, base, None)?;
1711            kern(base, engine)
1712        }
1713        _ => {
1714            engine.requeue(token)?;
1715            TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
1716            let base = read_mudim_unit(engine, 0f64, None)?;
1717            kern(base, engine)
1718        }
1719    }
1720}
1721
1722fn read_muskip_dim<R, ET: EngineTypes>(
1723    engine: &mut EngineReferences<ET>,
1724    is_negative: bool,
1725    first: u8,
1726    kern: fn(ET::MuDim, &mut EngineReferences<ET>) -> TeXResult<R, ET>,
1727) -> TeXResult<R, ET> {
1728    let base = read_mudim_float(engine, is_negative, first)?;
1729    kern(base, engine)
1730}
1731
1732fn read_mudim_float<ET: EngineTypes>(
1733    engine: &mut EngineReferences<ET>,
1734    is_negative: bool,
1735    first: u8,
1736) -> TeXResult<ET::MuDim, ET> {
1737    let mut ret = 0f64;
1738    let mut in_decimal = first == b'.';
1739    let mut fac = 10f64;
1740    if !in_decimal {
1741        ret = (first - b'0') as f64;
1742    }
1743    crate::expand_loop!(engine,token,
1744        ResolvedToken::Tk {char,code} => match (char.try_into(),code) {
1745            (Ok(b),CommandCode::Other) if is_ascii_digit(b) => {
1746                if in_decimal {
1747                    ret += (b - b'0') as f64 / fac;
1748                    fac *= 10.0;
1749                } else {
1750                    ret = 10.0*ret + ((b - b'0') as f64);
1751                }
1752            }
1753            (Ok(b','|b'.'),CommandCode::Other) => {
1754                if in_decimal {
1755                    engine.requeue(token)?;
1756                    TeXError::missing_unit(engine.aux,engine.state,engine.mouth)?;
1757                    return Ok(ET::MuDim::from_float(engine,ret,b"mu"))
1758                }
1759                in_decimal = true;
1760            }
1761            (_,CommandCode::Space) => {
1762                let f = if is_negative {-ret} else {ret};
1763                return read_mudim_unit(engine,f,None)
1764            }
1765            (Ok(b),CommandCode::Other | CommandCode::Letter) => {
1766                let f = if is_negative {-ret} else {ret};
1767                return read_mudim_unit(engine,f,Some((b,token)))
1768            }
1769            _ => {
1770                engine.requeue(token)?;
1771                TeXError::missing_unit(engine.aux,engine.state,engine.mouth)?;
1772                return Ok(ET::MuDim::from_float(engine,ret,b"mu"))
1773            }
1774        }
1775        _ => {
1776            engine.requeue(token)?;
1777            TeXError::missing_unit(engine.aux,engine.state,engine.mouth)?;
1778            return Ok(ET::MuDim::from_float(engine,ret,b"mu"))
1779        }
1780    );
1781    TeXError::missing_unit(engine.aux, engine.state, engine.mouth)?;
1782    Ok(ET::MuDim::from_float(engine, ret, b"mu"))
1783}
1784
1785fn read_mudim_unit<ET: EngineTypes>(
1786    engine: &mut EngineReferences<ET>,
1787    float: f64,
1788    first: Option<(u8, ET::Token)>,
1789) -> TeXResult<ET::MuDim, ET> {
1790    let units = ET::MuDim::UNITS;
1791    match read_keywords(engine, units, first)? {
1792        Some(d) => Ok(ET::MuDim::from_float(engine, float, d)),
1793        _ => {
1794            TeXError::missing_unit(engine.aux, engine.state, engine.mouth)?;
1795            Ok(ET::MuDim::from_float(engine, float, b"mu"))
1796        }
1797    }
1798}
1799
1800pub(crate) fn read_muskip_ii<ET: EngineTypes>(
1801    engine: &mut EngineReferences<ET>,
1802    base: ET::MuDim,
1803) -> TeXResult<MuSkip<ET::MuDim>, ET> {
1804    match read_keywords(engine, &[PLUS, MINUS], None)? {
1805        Some(b) if b == PLUS => {
1806            let stretch = read_mustretch(engine)?;
1807            Ok(if read_keyword(engine, MINUS, None)? {
1808                MuSkip::new(base, Some(stretch), Some(read_mustretch(engine)?))
1809            } else {
1810                MuSkip::new(base, Some(stretch), None)
1811            })
1812        }
1813        Some(b) if b == MINUS => {
1814            let shrink = read_mustretch(engine)?;
1815            Ok(if read_keyword(engine, PLUS, None)? {
1816                MuSkip::new(base, Some(read_mustretch(engine)?), Some(shrink))
1817            } else {
1818                MuSkip::new(base, None, Some(shrink))
1819            })
1820        }
1821        _ => Ok(MuSkip::new(base, None, None)),
1822    }
1823}
1824
1825fn read_mustretch<ET: EngineTypes>(
1826    engine: &mut EngineReferences<ET>,
1827) -> TeXResult<MuStretchShrink<ET::MuDim>, ET> {
1828    let mut is_negative = false;
1829    crate::expand_loop!(engine,token,
1830        ResolvedToken::Tk {char,code} => match (char.try_into(),code) {
1831            (_,CommandCode::Space) => (),
1832            (Ok(b'-'),CommandCode::Other) => {
1833                is_negative = !is_negative;
1834            }
1835            (Ok(b'+'),CommandCode::Other) => (),
1836            (Ok(b),CommandCode::Other) if is_ascii_digit(b) => return read_mustretch_float(engine,is_negative,b),
1837            (Ok(b','|b'.'),CommandCode::Other) => return read_mustretch_float(engine,is_negative,b'.'),
1838            _ => {
1839                engine.requeue(token)?;
1840                TeXError::missing_number(engine.aux,engine.state,engine.mouth)?;
1841                return read_mustretch_unit(engine,0f64,None)
1842            }
1843        }
1844        _ => {
1845            engine.requeue(token)?;
1846            TeXError::missing_number(engine.aux,engine.state,engine.mouth)?;
1847            return read_mustretch_unit(engine,0f64,None)
1848        }
1849    );
1850    TeXError::missing_number(engine.aux, engine.state, engine.mouth)?;
1851    Ok(MuStretchShrink::Mu(ET::MuDim::default()))
1852}
1853fn read_mustretch_float<ET: EngineTypes>(
1854    engine: &mut EngineReferences<ET>,
1855    is_negative: bool,
1856    first: u8,
1857) -> TeXResult<MuStretchShrink<ET::MuDim>, ET> {
1858    let mut ret = 0f64;
1859    let mut in_decimal = first == b'.';
1860    let mut fac = 10f64;
1861    if !in_decimal {
1862        ret = (first - b'0') as f64;
1863    }
1864    crate::expand_loop!(engine,token,
1865        ResolvedToken::Tk {char,code} => match (char.try_into(),code) {
1866            (Ok(b),CommandCode::Other) if is_ascii_digit(b) => {
1867                if in_decimal {
1868                    ret += (b - b'0') as f64 / fac;
1869                    fac *= 10.0;
1870                } else {
1871                    ret = 10.0*ret + ((b - b'0') as f64);
1872                }
1873            }
1874            (Ok(b','|b'.'),CommandCode::Other) => {
1875                if in_decimal {
1876                    engine.requeue(token)?;
1877                    TeXError::missing_unit(engine.aux,engine.state,engine.mouth)?;
1878                    return Ok(MuStretchShrink::Mu(ET::MuDim::from_float(engine,ret,b"mu")))
1879                }
1880                in_decimal = true;
1881            }
1882            (_,CommandCode::Space) => {
1883                let f = if is_negative {-ret} else {ret};
1884                return read_mustretch_unit(engine,f,None)
1885            }
1886            (Ok(b),CommandCode::Other | CommandCode::Letter) => {
1887                let f = if is_negative {-ret} else {ret};
1888                return read_mustretch_unit(engine,f,Some((b,token)))
1889            }
1890            _ => {
1891                engine.requeue(token)?;
1892                TeXError::missing_unit(engine.aux,engine.state,engine.mouth)?;
1893                return Ok(MuStretchShrink::Mu(ET::MuDim::from_float(engine,ret,b"mu")))
1894            }
1895        }
1896        _ => {
1897            engine.requeue(token)?;
1898            TeXError::missing_unit(engine.aux,engine.state,engine.mouth)?;
1899            return Ok(MuStretchShrink::Mu(ET::MuDim::from_float(engine,ret,b"mu")))
1900        }
1901    );
1902    TeXError::missing_unit(engine.aux, engine.state, engine.mouth)?;
1903    Ok(MuStretchShrink::Mu(ET::MuDim::from_float(
1904        engine, ret, b"mu",
1905    )))
1906}
1907fn read_mustretch_unit<ET: EngineTypes>(
1908    engine: &mut EngineReferences<ET>,
1909    float: f64,
1910    first: Option<(u8, ET::Token)>,
1911) -> TeXResult<MuStretchShrink<ET::MuDim>, ET> {
1912    match read_keywords(engine, STRETCH_SHRINK_UNITS, first)? {
1913        Some(d) => Ok(MuStretchShrink::from_float(engine, float, d)),
1914        _ => match read_keywords(engine, ET::MuDim::UNITS, None)? {
1915            Some(d) => Ok(MuStretchShrink::from_float(engine, float, d)),
1916            _ => {
1917                TeXError::missing_unit(engine.aux, engine.state, engine.mouth)?;
1918                Ok(MuStretchShrink::Mu(ET::MuDim::from_float(
1919                    engine, float, b"mu",
1920                )))
1921            }
1922        },
1923    }
1924}
1925
1926/// Default implementation for [`Gullet::read_keyword`].
1927pub fn read_keyword<ET: EngineTypes>(
1928    engine: &mut EngineReferences<ET>,
1929    kw: &[u8],
1930    first: Option<(u8, ET::Token)>,
1931) -> TeXResult<bool, ET> {
1932    let mut ret = arrayvec::ArrayVec::<_, 20>::new(); //engine.aux.memory.get_bytes();
1933    let mut read = arrayvec::ArrayVec::<_, 20>::new(); //engine.aux.memory.get_token_vec();
1934    if let Some((b, t)) = first {
1935        ret.push(b.to_ascii_lowercase());
1936        read.push(t);
1937    }
1938    crate::expand_loop!(engine,token,
1939        ResolvedToken::Tk {char,code} => match (char.try_into(),code) {
1940            (_,CommandCode::Space) if ret.is_empty() => (),
1941            (Ok(b),_) => {
1942                ret.push(b.to_ascii_lowercase());
1943                read.push(token);
1944                if !kw.starts_with(&ret) {
1945                    for t in read.into_iter().rev() {engine.requeue(t)?}
1946                    return Ok(false)
1947                }
1948                if kw.len() == ret.len() {
1949                    return Ok(true)
1950                }
1951            }
1952            _ => {
1953                read.push(token);
1954                for t in read.into_iter().rev() {engine.requeue(t)?}
1955                return Ok(false)
1956            }
1957        }
1958        ResolvedToken::Cmd(_) => {
1959            read.push(token);
1960            for t in read.into_iter().rev() {engine.requeue(t)?}
1961            return Ok(false)
1962        }
1963    );
1964    if kw.len() != ret.len() || !kw.starts_with(&ret) {
1965        for t in read.into_iter().rev() {
1966            engine.requeue(t)?
1967        }
1968        Ok(false)
1969    } else {
1970        Ok(true)
1971    }
1972}
1973
1974/// Default implementation for [`Gullet::read_keywords`].
1975pub fn read_keywords<'a, ET: EngineTypes>(
1976    engine: &mut EngineReferences<ET>,
1977    kws: &[&'a [u8]],
1978    first: Option<(u8, ET::Token)>,
1979) -> TeXResult<Option<&'a [u8]>, ET> {
1980    let mut ret = arrayvec::ArrayVec::<_, 20>::new(); //engine.aux.memory.get_bytes();
1981    let mut read = arrayvec::ArrayVec::<_, 20>::new(); //engine.aux.memory.get_token_vec();
1982    if let Some((b, t)) = first {
1983        ret.push(b.to_ascii_lowercase());
1984        read.push(t);
1985    }
1986    crate::expand_loop!(engine,token,
1987        ResolvedToken::Tk {char,code} => match (char.try_into(),code) {
1988            (_,CommandCode::Space) if ret.is_empty() => (),
1989            (Ok(b),_) => {
1990                ret.push(b.to_ascii_lowercase());
1991                let curr = kws.iter().filter(|k| k.to_ascii_lowercase().starts_with(&ret)).collect::<Vec<_>>();
1992                if curr.is_empty() {
1993                    ret.pop();
1994                    match kws.iter().find(|e| e.eq_ignore_ascii_case(ret.as_slice())) {
1995                        Some(w) => {
1996                            engine.requeue(token)?;
1997                            return Ok(Some(w))
1998                        }
1999                        None => {
2000                            engine.requeue(token)?;
2001                            for t in read.into_iter().rev() {engine.requeue(t)?}
2002                            return Ok(None)
2003                        }
2004                    }
2005                }
2006                read.push(token);
2007                if curr.len() == 1 && curr[0].len() == ret.len() {
2008                    return Ok(Some(curr[0]))
2009                }
2010            }
2011            _ => {
2012                let curr = kws.iter().filter(|k| k.to_ascii_lowercase().starts_with(&ret)).collect::<Vec<_>>();
2013                match curr.iter().find(|b| b.eq_ignore_ascii_case(ret.as_slice())) {
2014                    Some(b) => {
2015                        engine.requeue(token)?;
2016                        return Ok(Some(**b))
2017                    }
2018                    _ => {
2019                        engine.requeue(token)?;
2020                        for t in read.into_iter().rev() {engine.requeue(t)?}
2021                        return Ok(None)
2022                    }
2023                }
2024            }
2025        }
2026        ResolvedToken::Cmd(_) => {
2027            let curr = kws.iter().filter(|k| k.to_ascii_lowercase().starts_with(&ret)).collect::<Vec<_>>();
2028            match curr.iter().find(|b| ***b == ret.as_slice()) {
2029                Some(b) => {
2030                    engine.mouth.requeue(token);
2031                    return Ok(Some(**b))
2032                }
2033                _ => {
2034                    engine.mouth.requeue(token);
2035                    for t in read.into_iter().rev() {engine.requeue(t)?}
2036                    return Ok(None)
2037                }
2038            }
2039        }
2040    );
2041    match kws.iter().find(|b| b.eq_ignore_ascii_case(ret.as_slice())) {
2042        Some(b) => Ok(Some(*b)),
2043        _ => {
2044            for t in read.into_iter().rev() {
2045                engine.requeue(t)?
2046            }
2047            Ok(None)
2048        }
2049    }
2050}
2051
2052/// Default implementation for [`Gullet::read_chars`].
2053pub fn read_chars<ET: EngineTypes>(
2054    engine: &mut EngineReferences<ET>,
2055    kws: &[u8],
2056) -> TeXResult<Either<u8, Option<ET::Token>>, ET> {
2057    crate::expand_loop!(engine,token,
2058        ResolvedToken::Tk {char,code} => match (char.try_into(),code) {
2059            (_,CommandCode::Space) => (),
2060            (Ok(b),_) if kws.contains(&b) => {
2061                return Ok(Either::Left(b))
2062            }
2063            _ => {
2064                return Ok(Either::Right(Some(token)))
2065            }
2066        }
2067        ResolvedToken::Cmd(_) => {
2068            return Ok(Either::Right(Some(token)))
2069        }
2070    );
2071    Ok(Either::Right(None))
2072}
2073
2074/// Either a [`CSName`](crate::tex::tokens::control_sequences::CSName) or an active character
2075pub enum CSOrActiveChar<T: Token> {
2076    Active(T::Char),
2077    Name(T::CS),
2078}
2079impl<ET: EngineTypes> EngineReferences<'_, ET> {
2080    /// Reads a control sequence or active character from the [`Mouth`] and returns it as a
2081    /// [`CSOrActiveChar`].
2082    pub fn read_control_sequence(
2083        &mut self,
2084        in_token: &ET::Token,
2085    ) -> TeXResult<CSOrActiveChar<ET::Token>, ET> {
2086        loop {
2087            let token = self.need_next(false, in_token)?;
2088            match self.resolve(&token) {
2089                ResolvedToken::Cmd(Some(TeXCommand::Primitive {
2090                    name,
2091                    cmd: PrimitiveCommand::Expandable(f),
2092                })) => ET::Gullet::do_expandable(self, *name, token, *f)?,
2093                ResolvedToken::Cmd(Some(TeXCommand::Primitive {
2094                    name,
2095                    cmd: PrimitiveCommand::SimpleExpandable(f),
2096                })) => ET::Gullet::do_simple_expandable(self, *name, token, *f)?,
2097                ResolvedToken::Cmd(Some(TeXCommand::Primitive {
2098                    name,
2099                    cmd: PrimitiveCommand::Conditional(f),
2100                })) => ET::Gullet::do_conditional(self, *name, token, *f, false)?,
2101                ResolvedToken::Cmd(_) => {
2102                    let ret = match token.to_enum() {
2103                        StandardToken::Character(c, CommandCode::Active) => {
2104                            CSOrActiveChar::Active(c)
2105                        }
2106                        StandardToken::ControlSequence(cs) => CSOrActiveChar::Name(cs),
2107                        _ => unreachable!(),
2108                    };
2109                    self.set_command(
2110                        &ret,
2111                        Some(TeXCommand::Primitive {
2112                            name: PRIMITIVES.relax,
2113                            cmd: PrimitiveCommand::Relax,
2114                        }),
2115                        false,
2116                    );
2117                    return Ok(ret);
2118                }
2119                ResolvedToken::Tk {
2120                    code: CommandCode::Space,
2121                    ..
2122                } => (),
2123                _ => {
2124                    return Err(TeXError::General(
2125                        "Control sequence expected\n TODO: Better error message".to_string(),
2126                    ))
2127                }
2128            }
2129        }
2130    }
2131}