Skip to main content

tex_engine/commands/
etex.rs

1use super::primitives::*;
2use crate::commands::{
3    CharOrPrimitive, CommandScope, Macro, MacroSignature, PrimitiveCommand, ResolvedToken,
4    TeXCommand,
5};
6use crate::engine::filesystem::FileSystem;
7use crate::engine::fontsystem::Font;
8use crate::engine::gullet::methods::NumContinuation;
9use crate::engine::gullet::Gullet;
10use crate::engine::mouth::Mouth;
11use crate::engine::state::{GroupType, State};
12use crate::engine::stomach::Stomach;
13use crate::engine::{EngineAux, EngineReferences, EngineTypes, TeXEngine};
14use crate::tex::catcodes::{CategoryCode, CommandCode};
15use crate::tex::characters::Character;
16use crate::tex::characters::CharacterMap;
17use crate::tex::nodes::math::MathAtom;
18use crate::tex::nodes::math::{MathNode, MathNucleus};
19use crate::tex::nodes::{NodeList, NodeTrait};
20use crate::tex::numerics::{MuSkip, NumSet, Numeric, Skip, StretchShrink};
21use crate::tex::tokens::control_sequences::{CSHandler, ResolvedCSName};
22use crate::tex::tokens::token_lists::{CharWrite, Otherize};
23use crate::tex::tokens::{StandardToken, Token};
24use crate::utils::errors::{TeXError, TeXResult};
25use either::Either;
26
27#[allow(non_snake_case)]
28pub fn eTeXversion<ET: EngineTypes>(
29    _engine: &mut EngineReferences<ET>,
30    _tk: ET::Token,
31) -> TeXResult<ET::Int, ET> {
32    Ok(2.into())
33}
34#[allow(non_snake_case)]
35pub fn eTeXrevision<ET: EngineTypes>(
36    _engine: &mut EngineReferences<ET>,
37    exp: &mut Vec<ET::Token>,
38    _tk: ET::Token,
39) -> TeXResult<(), ET> {
40    exp.push(ET::Token::from_char_cat(b'.'.into(), CommandCode::Other));
41    exp.push(ET::Token::from_char_cat(b'6'.into(), CommandCode::Other));
42    Ok(())
43}
44
45fn expr_inner<ET: EngineTypes, R: Numeric<<ET::Num as NumSet>::Int>>(
46    engine: &mut EngineReferences<ET>,
47    byte: fn(&mut EngineReferences<ET>, bool, u8) -> TeXResult<R, ET>,
48    cmd: fn(&mut EngineReferences<ET>, bool, TeXCommand<ET>, ET::Token) -> TeXResult<R, ET>,
49    tk: &ET::Token,
50) -> TeXResult<R, ET> {
51    let NumContinuation { is_negative, next } =
52        crate::engine::gullet::methods::read_numeric(engine, false, tk)?;
53    match next {
54        Either::Left(b) => {
55            if b == b'(' {
56                let (int, ret) = expr_loop(engine, byte, cmd, tk)?;
57                match ret {
58                    Some(ret) => match ret.to_enum() {
59                        StandardToken::Character(char, CommandCode::Other)
60                            if matches!(char.try_into(), Ok(b')')) =>
61                        {
62                            if is_negative {
63                                Ok(-int)
64                            } else {
65                                Ok(int)
66                            }
67                        }
68                        _ => Err(TeXError::General(
69                            "Closing Parenthesis expected\nTODO: Better error message".to_string(),
70                        )),
71                    },
72                    _ => Err(TeXError::General(
73                        "Closing Parenthesis expected\nTODO: Better error message".to_string(),
74                    )),
75                }
76            } else {
77                byte(engine, is_negative, b)
78            }
79        }
80        Either::Right((c, token)) => cmd(engine, is_negative, c, token),
81    }
82}
83
84struct Summand<ET: EngineTypes, R: Numeric<ET::Int>> {
85    base: R,
86    times: Vec<ET::Int>,
87    div: Vec<ET::Int>,
88}
89impl<ET: EngineTypes, R: Numeric<ET::Int>> Summand<ET, R> {
90    fn new(r: R) -> Self {
91        Self {
92            base: r,
93            times: vec![],
94            div: vec![],
95        }
96    }
97    fn resolve(self) -> R {
98        let times = if self.times.is_empty() {
99            ET::Int::from(1)
100        } else {
101            self.times.into_iter().reduce(|a, b| a * b).unwrap()
102        };
103        let div = if self.div.is_empty() {
104            ET::Int::from(1)
105        } else {
106            self.div.into_iter().reduce(|a, b| a * b).unwrap()
107        };
108        self.base.scale(times, div)
109    }
110    fn mult(&mut self, i: ET::Int) {
111        self.times.push(i)
112    }
113    fn div(&mut self, i: ET::Int) {
114        self.div.push(i)
115    }
116}
117impl<ET: EngineTypes, R: Numeric<ET::Int>> std::fmt::Display for Summand<ET, R> {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        write!(f, "({}", self.base)?;
120        for x in self.times.iter() {
121            write!(f, " *{}", x)?;
122        }
123        for x in self.div.iter() {
124            write!(f, " /{}", x)?;
125        }
126        write!(f, ")")
127    }
128}
129
130fn expr_loop<ET: EngineTypes, R: Numeric<<ET::Num as NumSet>::Int>>(
131    engine: &mut EngineReferences<ET>,
132    byte: fn(&mut EngineReferences<ET>, bool, u8) -> TeXResult<R, ET>,
133    cmd: fn(&mut EngineReferences<ET>, bool, TeXCommand<ET>, ET::Token) -> TeXResult<R, ET>,
134    tk: &ET::Token,
135) -> TeXResult<(R, Option<ET::Token>), ET> {
136    let mut prev: Option<R> = None;
137    let mut curr = Summand::<ET, R>::new(expr_inner(engine, byte, cmd, tk)?);
138    loop {
139        match engine.read_chars(b"+-*/")? {
140            Either::Right(r) => {
141                match prev {
142                    Some(p) => return Ok((p + curr.resolve(), r)),
143                    _ => return Ok((curr.resolve(), r)),
144                }
145                //return (Summand::reduce(adds),r)
146            }
147            Either::Left(b'+') => {
148                let old =
149                    std::mem::replace(&mut curr, Summand::new(expr_inner(engine, byte, cmd, tk)?))
150                        .resolve();
151                prev = match prev {
152                    Some(s) => Some(s + old),
153                    None => Some(old),
154                }
155            }
156            Either::Left(b'-') => {
157                let old =
158                    std::mem::replace(&mut curr, Summand::new(-expr_inner(engine, byte, cmd, tk)?))
159                        .resolve();
160                prev = match prev {
161                    Some(s) => Some(s + old),
162                    None => Some(old),
163                }
164            }
165            Either::Left(b'*') => curr.mult(expr_inner(
166                engine,
167                crate::engine::gullet::methods::read_int_byte,
168                crate::engine::gullet::methods::read_int_command,
169                tk,
170            )?),
171            Either::Left(b'/') => curr.div(expr_inner(
172                engine,
173                crate::engine::gullet::methods::read_int_byte,
174                crate::engine::gullet::methods::read_int_command,
175                tk,
176            )?),
177            Either::Left(_) => unreachable!(),
178        }
179    }
180}
181
182pub fn currentgrouplevel<ET: EngineTypes>(
183    engine: &mut EngineReferences<ET>,
184    _tk: ET::Token,
185) -> TeXResult<ET::Int, ET> {
186    Ok((engine.state.get_group_level() as i32).into())
187}
188/*\currentgrouptype returns a number representing the type of the innermost
189group:
1900: bottom level (no group)
1911: simple group
1922: hbox group
193// 3: adjusted hbox group
1944: vbox group
195// 5: vtop group
196// 6: align group
197// 7: no align group
198// 8: output group
199// 9: math group
200// 10: disc group
201// 11: insert group
202// 12: vcenter group
203// 13: math choice group
20414: semi simple group
20515: math shift group
20616: math left group
207 */
208pub fn currentgrouptype<ET: EngineTypes>(
209    engine: &mut EngineReferences<ET>,
210    _tk: ET::Token,
211) -> TeXResult<ET::Int, ET> {
212    Ok(match engine.state.get_group_type() {
213        None => ET::Int::default(),
214        Some(gt) => ET::Int::from(gt.to_byte() as i32),
215    })
216}
217
218pub fn detokenize<ET: EngineTypes>(
219    engine: &mut EngineReferences<ET>,
220    exp: &mut Vec<ET::Token>,
221    tk: ET::Token,
222) -> TeXResult<(), ET> {
223    engine.expand_until_bgroup(false, &tk)?;
224    let mut f = |t| exp.push(t);
225    let escapechar = engine.state.get_escape_char();
226    let g = |a: &mut EngineAux<ET>, st: &<ET as EngineTypes>::State, t: ET::Token, f: &mut _| {
227        let mut tokenizer = Otherize::new(f);
228        match t.to_enum() {
229            StandardToken::Character(c, _) => tokenizer.push_char(c),
230            StandardToken::ControlSequence(cs) => tokenizer.push_cs(
231                cs,
232                a.memory.cs_interner(),
233                st.get_catcode_scheme(),
234                escapechar,
235            ),
236            StandardToken::Primitive(id) => {
237                tokenizer.push_cs(
238                    a.memory.cs_interner_mut().cs_from_str("pdfprimitive"),
239                    a.memory.cs_interner(),
240                    st.get_catcode_scheme(),
241                    escapechar,
242                );
243                let name = a
244                    .memory
245                    .cs_interner_mut()
246                    .cs_from_str(&id.display::<ET::Char>(None).to_string());
247                tokenizer.push_cs(
248                    name,
249                    a.memory.cs_interner(),
250                    st.get_catcode_scheme(),
251                    escapechar,
252                );
253            }
254        }
255    };
256    engine.read_until_endgroup(&tk, |a, st, t| {
257        match t.command_code() {
258            CommandCode::Space => f(t),
259            CommandCode::Parameter => {
260                g(a, st, t.clone(), &mut f);
261                g(a, st, t, &mut f)
262            }
263            _ => g(a, st, t, &mut f),
264        }
265        Ok(())
266    })?;
267    Ok(())
268}
269
270pub fn expanded<ET: EngineTypes>(
271    engine: &mut EngineReferences<ET>,
272    exp: &mut Vec<ET::Token>,
273    tk: ET::Token,
274) -> TeXResult<(), ET> {
275    if engine.need_next(false, &tk)?.command_code() == CommandCode::BeginGroup {
276    } else {
277        TeXError::missing_begingroup(engine.aux, engine.state, engine.mouth)?;
278    }
279    ET::Gullet::expand_until_endgroup(engine, false, false, &tk, |_, _, t| {
280        exp.push(t);
281        Ok(())
282    })
283}
284
285pub fn fontchardp<ET: EngineTypes>(
286    engine: &mut EngineReferences<ET>,
287    tk: ET::Token,
288) -> TeXResult<ET::Dim, ET> {
289    let fnt = engine.read_font(false, &tk)?;
290    let char = engine.read_charcode(false, &tk)?;
291    Ok(fnt.get_dp(char))
292}
293pub fn fontcharht<ET: EngineTypes>(
294    engine: &mut EngineReferences<ET>,
295    tk: ET::Token,
296) -> TeXResult<ET::Dim, ET> {
297    let fnt = engine.read_font(false, &tk)?;
298    let char = engine.read_charcode(false, &tk)?;
299    Ok(fnt.get_ht(char))
300}
301pub fn fontcharwd<ET: EngineTypes>(
302    engine: &mut EngineReferences<ET>,
303    tk: ET::Token,
304) -> TeXResult<ET::Dim, ET> {
305    let fnt = engine.read_font(false, &tk)?;
306    let char = engine.read_charcode(false, &tk)?;
307    Ok(fnt.get_wd(char))
308}
309pub fn fontcharic<ET: EngineTypes>(
310    engine: &mut EngineReferences<ET>,
311    tk: ET::Token,
312) -> TeXResult<ET::Dim, ET> {
313    let fnt = engine.read_font(false, &tk)?;
314    let char = engine.read_charcode(false, &tk)?;
315    Ok(fnt.get_ic(char))
316}
317
318pub fn ifcsname<ET: EngineTypes>(
319    engine: &mut EngineReferences<ET>,
320    _tk: ET::Token,
321) -> TeXResult<bool, ET> {
322    let name = engine.read_csname()?;
323    Ok(engine.state.get_command(&name).is_some())
324}
325
326pub fn ifdefined<ET: EngineTypes>(
327    engine: &mut EngineReferences<ET>,
328    tk: ET::Token,
329) -> TeXResult<bool, ET> {
330    Ok(match engine.need_next(false, &tk)?.to_enum() {
331        StandardToken::Character(c, CommandCode::Active) => {
332            engine.state.get_ac_command(c).is_some()
333        }
334        StandardToken::ControlSequence(name) => engine.state.get_command(&name).is_some(),
335        _ => true,
336    })
337}
338
339pub fn iffontchar<ET: EngineTypes>(
340    engine: &mut EngineReferences<ET>,
341    tk: ET::Token,
342) -> TeXResult<bool, ET> {
343    let font = engine.read_font(false, &tk)?;
344    let char = engine.read_charcode(false, &tk)?;
345    Ok(font.has_char(char))
346}
347
348pub fn numexpr<ET: EngineTypes>(
349    engine: &mut EngineReferences<ET>,
350    tk: ET::Token,
351) -> TeXResult<ET::Int, ET> {
352    let (i, r) = expr_loop(
353        engine,
354        crate::engine::gullet::methods::read_int_byte,
355        crate::engine::gullet::methods::read_int_command,
356        &tk,
357    )?;
358    if let Some(r) = r {
359        if !r.is_cs_or_active() {
360            engine.requeue(r)?
361        } else {
362            match ET::Gullet::char_or_primitive(engine.state, &r) {
363                Some(CharOrPrimitive::Primitive(name)) if name == PRIMITIVES.relax => (),
364                _ => engine.requeue(r)?,
365            }
366        }
367    }
368    Ok(i)
369}
370
371pub fn dimexpr<ET: EngineTypes>(
372    engine: &mut EngineReferences<ET>,
373    tk: ET::Token,
374) -> TeXResult<ET::Dim, ET> {
375    let (i, r) = expr_loop(
376        engine,
377        crate::engine::gullet::methods::read_dim_byte,
378        crate::engine::gullet::methods::read_dim_command,
379        &tk,
380    )?;
381    if let Some(r) = r {
382        if !r.is_cs_or_active() {
383            engine.requeue(r)?
384        } else {
385            match ET::Gullet::char_or_primitive(engine.state, &r) {
386                Some(CharOrPrimitive::Primitive(name)) if name == PRIMITIVES.relax => (),
387                _ => engine.requeue(r)?,
388            }
389        }
390    }
391    Ok(i)
392}
393
394pub fn glueexpr<ET: EngineTypes>(
395    engine: &mut EngineReferences<ET>,
396    tk: ET::Token,
397) -> TeXResult<Skip<ET::Dim>, ET> {
398    let (i, r) = expr_loop(
399        engine,
400        crate::engine::gullet::methods::read_skip_byte,
401        crate::engine::gullet::methods::read_skip_command,
402        &tk,
403    )?;
404    if let Some(r) = r {
405        if !r.is_cs_or_active() {
406            engine.requeue(r)?
407        } else {
408            match ET::Gullet::char_or_primitive(engine.state, &r) {
409                Some(CharOrPrimitive::Primitive(name)) if name == PRIMITIVES.relax => (),
410                _ => engine.requeue(r)?,
411            }
412        }
413    }
414    Ok(i)
415}
416
417pub fn muexpr<ET: EngineTypes>(
418    engine: &mut EngineReferences<ET>,
419    tk: ET::Token,
420) -> TeXResult<MuSkip<ET::MuDim>, ET> {
421    fn muskip_byte<ET: EngineTypes>(
422        engine: &mut EngineReferences<ET>,
423        is_negative: bool,
424        b: u8,
425    ) -> TeXResult<MuSkip<ET::MuDim>, ET> {
426        crate::engine::gullet::methods::read_muskip_byte(engine, is_negative, b, |d, e| {
427            crate::engine::gullet::methods::read_muskip_ii(e, d)
428        })
429    }
430    fn muskip_cmd<ET: EngineTypes>(
431        engine: &mut EngineReferences<ET>,
432        is_negative: bool,
433        cmd: TeXCommand<ET>,
434        tk: ET::Token,
435    ) -> TeXResult<MuSkip<ET::MuDim>, ET> {
436        crate::engine::gullet::methods::read_muskip_command(
437            engine,
438            is_negative,
439            cmd,
440            tk,
441            |d, e| crate::engine::gullet::methods::read_muskip_ii(e, d),
442            Ok,
443        )
444    }
445    let (i, r) = expr_loop(engine, muskip_byte, muskip_cmd, &tk)?;
446    if let Some(r) = r {
447        if !r.is_cs_or_active() {
448            engine.requeue(r)?
449        } else {
450            match ET::Gullet::char_or_primitive(engine.state, &r) {
451                Some(CharOrPrimitive::Primitive(name)) if name == PRIMITIVES.relax => (),
452                _ => engine.requeue(r)?,
453            }
454        }
455    }
456    Ok(i)
457}
458
459pub fn lastnodetype<ET: EngineTypes>(
460    engine: &mut EngineReferences<ET>,
461    _tk: ET::Token,
462) -> TeXResult<ET::Int, ET> {
463    let data = engine.stomach.data_mut();
464    Ok(match data.open_lists.last() {
465        None => match data.page.last() {
466            None => (-1).into(),
467            Some(n) => (n.nodetype().to_u8() as i32).into(),
468        },
469        Some(NodeList::Vertical { children, .. }) => match children.last() {
470            None => (-1).into(),
471            Some(n) => (n.nodetype().to_u8() as i32).into(),
472        },
473        Some(NodeList::Horizontal { children, .. }) => match children.last() {
474            None => (-1).into(),
475            Some(n) => (n.nodetype().to_u8() as i32).into(),
476        },
477        Some(NodeList::Math { children, .. }) => match children.list().last() {
478            None => (-1).into(),
479            Some(n) => (n.nodetype().to_u8() as i32).into(),
480        },
481    })
482}
483
484pub fn protected<ET: EngineTypes>(
485    engine: &mut EngineReferences<ET>,
486    tk: ET::Token,
487    outer: bool,
488    long: bool,
489    _protected: bool,
490    globally: bool,
491) -> TeXResult<(), ET> {
492    crate::expand_loop!(engine,token,
493        ResolvedToken::Cmd(Some(TeXCommand::Primitive{name,..})) => match *name {
494            n if n == PRIMITIVES.outer => return super::tex::outer(engine,token,outer,long,true,globally),
495            n if n == PRIMITIVES.long => return super::tex::long(engine,token,outer,long,true,globally),
496            n if n == PRIMITIVES.protected => return self::protected(engine,token,outer,long,true,globally),
497            n if n == PRIMITIVES.global => return super::tex::global(engine,token,outer,long,true,globally),
498            n if n == PRIMITIVES.def => return super::tex::def(engine,token,outer,long,true,globally),
499            n if n == PRIMITIVES.edef => return super::tex::edef(engine,token,outer,long,true,globally),
500            n if n == PRIMITIVES.xdef => return super::tex::xdef(engine,token,outer,long,true,globally),
501            n if n == PRIMITIVES.gdef => return super::tex::gdef(engine,token,outer,long,true,globally),
502            n if n == PRIMITIVES.relax => (),
503            r => {
504                let s = r.display(engine.state.get_escape_char()).to_string();
505                engine.requeue(token)?;
506                return Err(TeXError::General(format!("You can't use a prefix with '{}'",s)))
507            }
508        }
509        _ => {
510            let s = token.display(engine.aux.memory.cs_interner(),engine.state.get_catcode_scheme(),engine.state.get_escape_char()).to_string();
511            engine.requeue(token)?;
512            return Err(TeXError::General(format!("You can't use a prefix with '{}'",s)))
513        }
514    );
515    TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, &tk)
516}
517
518pub fn readline<ET: EngineTypes>(
519    engine: &mut EngineReferences<ET>,
520    tk: ET::Token,
521    globally: bool,
522) -> TeXResult<(), ET> {
523    let idx = engine.read_file_index(&tk)?;
524    if !engine.read_keyword("to".as_bytes())? {
525        TeXError::missing_keyword(engine.aux, engine.state, engine.mouth, &["to"])?
526    }
527    let cs = engine.read_control_sequence(&tk)?;
528    let mut ret = shared_vector::Vector::new();
529    engine
530        .filesystem
531        .readline(idx, engine.state, |t| ret.push(t))?;
532    let m = Macro {
533        long: false,
534        outer: false,
535        protected: false,
536        expansion: ret.into(),
537        signature: MacroSignature {
538            arity: 0,
539            params: engine.aux.memory.empty_list(),
540        },
541    };
542    engine.set_command(&cs, Some(TeXCommand::Macro(m)), globally);
543    Ok(())
544}
545
546pub fn scantokens<ET: EngineTypes>(
547    engine: &mut EngineReferences<ET>,
548    tk: ET::Token,
549) -> TeXResult<(), ET> {
550    engine.expand_until_bgroup(false, &tk)?;
551    let mut ret: Vec<Box<[ET::Char]>> = vec![];
552    let mut curr = vec![];
553    let mut f = |c: ET::Char| {
554        if matches!(c.try_into(), Ok(b'\n')) {
555            if curr.last().copied() == Some(b'\r'.into()) {
556                curr.pop();
557            }
558            ret.push(std::mem::take(&mut curr).into());
559        } else {
560            curr.push(c)
561        }
562    };
563    let escapechar = engine.state.get_escape_char();
564    engine.read_until_endgroup(&tk, |a, state, t| {
565        match t.to_enum() {
566            //StandardToken::Character(c,CommandCode::Parameter) => {f(c);f(c)},
567            StandardToken::Character(c, _) => f(c),
568            StandardToken::ControlSequence(cs) => {
569                if let Some(esc) = escapechar {
570                    f(esc);
571                }
572                let res = a.memory.cs_interner().resolve(&cs);
573                for c in res.iter() {
574                    f(c)
575                }
576                if res.len() == 1 {
577                    let c = res.iter().next().unwrap();
578                    if state.get_catcode_scheme().get(c) == &CategoryCode::Letter {
579                        f(b' '.into())
580                    }
581                } else {
582                    f(b' '.into())
583                }
584            }
585            StandardToken::Primitive(id) => {
586                if let Some(esc) = escapechar {
587                    f(esc);
588                }
589                for c in ET::Char::string_to_iter("pdfprimitive") {
590                    f(c)
591                }
592                f(b' '.into());
593                let s = id.display::<ET::Char>(None).to_string();
594                let res = ET::Char::string_to_iter(&s);
595                for c in res {
596                    f(c)
597                }
598                let mut res = ET::Char::string_to_iter(&s);
599                if res.len() == 1 {
600                    let c = res.next().unwrap();
601                    if state.get_catcode_scheme().get(c) == &CategoryCode::Letter {
602                        f(b' '.into())
603                    }
604                } else {
605                    f(b' '.into())
606                }
607            }
608        }
609        Ok(())
610    })?;
611    if !curr.is_empty() {
612        ret.push(curr.into())
613    }
614    engine.mouth.push_string(ret.into());
615    Ok(())
616}
617
618pub fn unexpanded<ET: EngineTypes>(
619    engine: &mut EngineReferences<ET>,
620    exp: &mut Vec<ET::Token>,
621    tk: ET::Token,
622) -> TeXResult<(), ET> {
623    engine.expand_until_bgroup(false, &tk)?;
624    engine.read_until_endgroup(&tk, |_, _, t| {
625        exp.push(t);
626        Ok(())
627    })?;
628    Ok(())
629}
630
631pub fn unless<ET: EngineTypes>(
632    engine: &mut EngineReferences<ET>,
633    tk: ET::Token,
634) -> TeXResult<(), ET> {
635    let t = engine.need_next(false, &tk)?;
636    match engine.resolve(&t) {
637        ResolvedToken::Cmd(Some(TeXCommand::Primitive {
638            name,
639            cmd: PrimitiveCommand::Conditional(cnd),
640        })) => ET::Gullet::do_conditional(engine, *name, t, *cnd, true),
641        _ => {
642            let s = t
643                .display(
644                    engine.aux.memory.cs_interner(),
645                    engine.state.get_catcode_scheme(),
646                    engine.state.get_escape_char(),
647                )
648                .to_string();
649            engine.general_error(format!("You can't use `\\unless` before {}", s))
650        }
651    }
652}
653
654pub fn middle<ET: EngineTypes>(
655    engine: &mut EngineReferences<ET>,
656    tk: ET::Token,
657) -> TeXResult<(), ET> {
658    match engine.state.get_group_type() {
659        Some(GroupType::LeftRight) => (),
660        _ => {
661            return engine.general_error(
662                "You can't use `\\middle` outside of a `\\left`-`\\right` pair".to_string(),
663            )
664        }
665    }
666    let del = match engine.read_opt_delimiter(&tk)? {
667        None => return engine.general_error("Delimiter expected after `\\middle`".to_string()),
668        Some(c) => c,
669    };
670    ET::Stomach::add_node_m(
671        engine,
672        MathNode::Atom(MathAtom {
673            sub: None,
674            sup: None,
675            nucleus: MathNucleus::Middle(del.small.char, del.small.style),
676        }),
677    );
678    Ok(())
679}
680
681pub fn marks<ET: EngineTypes>(
682    engine: &mut EngineReferences<ET>,
683    tk: ET::Token,
684) -> TeXResult<(), ET> {
685    let i = engine.read_int(false, &tk)?.into();
686    if i < 0 {
687        return engine.general_error(format!("Illegal \\marks register: {i}"));
688    }
689    super::methods::do_marks(engine, i as usize, &tk)
690}
691
692pub fn topmarks<ET: EngineTypes>(
693    engine: &mut EngineReferences<ET>,
694    exp: &mut Vec<ET::Token>,
695    tk: ET::Token,
696) -> TeXResult<(), ET> {
697    let i = engine.read_int(false, &tk)?.into();
698    if i < 0 {
699        return engine.general_error(format!("Illegal \\topmarks register: {i}"));
700    }
701    super::methods::get_marks(engine, exp, |d| &mut d.topmarks, i as usize);
702    Ok(())
703}
704pub fn firstmarks<ET: EngineTypes>(
705    engine: &mut EngineReferences<ET>,
706    exp: &mut Vec<ET::Token>,
707    tk: ET::Token,
708) -> TeXResult<(), ET> {
709    let i = engine.read_int(false, &tk)?.into();
710    if i < 0 {
711        return engine.general_error(format!("Illegal \\firstmarks register: {i}"));
712    }
713    super::methods::get_marks(engine, exp, |d| &mut d.firstmarks, i as usize);
714    Ok(())
715}
716pub fn botmarks<ET: EngineTypes>(
717    engine: &mut EngineReferences<ET>,
718    exp: &mut Vec<ET::Token>,
719    tk: ET::Token,
720) -> TeXResult<(), ET> {
721    let i = engine.read_int(false, &tk)?.into();
722    if i < 0 {
723        return engine.general_error(format!("Illegal \\botmarks register: {i}"));
724    }
725    super::methods::get_marks(engine, exp, |d| &mut d.botmarks, i as usize);
726    Ok(())
727}
728pub fn splitfirstmarks<ET: EngineTypes>(
729    engine: &mut EngineReferences<ET>,
730    exp: &mut Vec<ET::Token>,
731    tk: ET::Token,
732) -> TeXResult<(), ET> {
733    let i = engine.read_int(false, &tk)?.into();
734    if i < 0 {
735        return engine.general_error(format!("Illegal \\splitfirstmarks register: {i}"));
736    }
737    super::methods::get_marks(engine, exp, |d| &mut d.splitfirstmarks, i as usize);
738    Ok(())
739}
740pub fn splitbotmarks<ET: EngineTypes>(
741    engine: &mut EngineReferences<ET>,
742    exp: &mut Vec<ET::Token>,
743    tk: ET::Token,
744) -> TeXResult<(), ET> {
745    let i = engine.read_int(false, &tk)?.into();
746    if i < 0 {
747        return engine.general_error(format!("Illegal \\splitbotmarks register: {i}"));
748    }
749    super::methods::get_marks(engine, exp, |d| &mut d.splitbotmarks, i as usize);
750    Ok(())
751}
752
753pub fn glueshrinkorder<ET: EngineTypes>(
754    engine: &mut EngineReferences<ET>,
755    tk: ET::Token,
756) -> TeXResult<ET::Int, ET> {
757    let dim = engine.read_skip(false, &tk)?;
758    Ok(match dim.shrink {
759        None | Some(StretchShrink::Dim(_)) => ET::Int::from(0),
760        Some(StretchShrink::Fil(_)) => ET::Int::from(1),
761        Some(StretchShrink::Fill(_)) => ET::Int::from(2),
762        Some(StretchShrink::Filll(_)) => ET::Int::from(2),
763    })
764}
765pub fn gluestretchorder<ET: EngineTypes>(
766    engine: &mut EngineReferences<ET>,
767    tk: ET::Token,
768) -> TeXResult<ET::Int, ET> {
769    let dim = engine.read_skip(false, &tk)?;
770    Ok(match dim.stretch {
771        None | Some(StretchShrink::Dim(_)) => ET::Int::from(0),
772        Some(StretchShrink::Fil(_)) => ET::Int::from(1),
773        Some(StretchShrink::Fill(_)) => ET::Int::from(2),
774        Some(StretchShrink::Filll(_)) => ET::Int::from(2),
775    })
776}
777pub fn glueshrink<ET: EngineTypes>(
778    engine: &mut EngineReferences<ET>,
779    tk: ET::Token,
780) -> TeXResult<ET::Dim, ET> {
781    use crate::tex::numerics::TeXDimen;
782    let dim = engine.read_skip(false, &tk)?;
783    Ok(match dim.shrink {
784        None => ET::Dim::default(),
785        Some(StretchShrink::Dim(d)) => d,
786        Some(StretchShrink::Fil(i)) => ET::Dim::from_sp(i),
787        Some(StretchShrink::Fill(i)) => ET::Dim::from_sp(i),
788        Some(StretchShrink::Filll(i)) => ET::Dim::from_sp(i),
789    })
790}
791pub fn gluestretch<ET: EngineTypes>(
792    engine: &mut EngineReferences<ET>,
793    tk: ET::Token,
794) -> TeXResult<ET::Dim, ET> {
795    use crate::tex::numerics::TeXDimen;
796    let dim = engine.read_skip(false, &tk)?;
797    Ok(match dim.stretch {
798        None => ET::Dim::default(),
799        Some(StretchShrink::Dim(d)) => d,
800        Some(StretchShrink::Fil(i)) => ET::Dim::from_sp(i),
801        Some(StretchShrink::Fill(i)) => ET::Dim::from_sp(i),
802        Some(StretchShrink::Filll(i)) => ET::Dim::from_sp(i),
803    })
804}
805
806const PRIMITIVE_INTS: &[&str] = &[
807    "savinghyphcodes",
808    "tracingassigns",
809    "tracinggroups",
810    "tracingifs",
811    "tracingnesting",
812    "tracingscantokens",
813    "savingvdiscards",
814    "predisplaydirection",
815    "interactionmode",
816];
817
818const PRIMITIVE_TOKS: &[&str] = &["everyeof"];
819
820pub fn register_etex_primitives<E: TeXEngine>(engine: &mut E) {
821    register_primitive_int(engine, PRIMITIVE_INTS);
822    register_primitive_toks(engine, PRIMITIVE_TOKS);
823
824    register_int(engine, "numexpr", numexpr, None);
825    register_dim(engine, "dimexpr", dimexpr, None);
826    register_skip(engine, "glueexpr", glueexpr, None);
827    register_muskip(engine, "muexpr", muexpr, None);
828
829    register_int(engine, "currentgrouplevel", currentgrouplevel, None);
830    register_int(engine, "currentgrouptype", currentgrouptype, None);
831    register_int(engine, "lastnodetype", lastnodetype, None);
832    register_int(engine, "eTeXversion", eTeXversion, None);
833    register_int(engine, "glueshrinkorder", glueshrinkorder, None);
834    register_int(engine, "gluestretchorder", gluestretchorder, None);
835
836    register_dim(engine, "fontchardp", fontchardp, None);
837    register_dim(engine, "fontcharht", fontcharht, None);
838    register_dim(engine, "fontcharwd", fontcharwd, None);
839    register_dim(engine, "fontcharic", fontcharic, None);
840    register_dim(engine, "glueshrink", glueshrink, None);
841    register_dim(engine, "gluestretch", gluestretch, None);
842
843    register_assignment(engine, "protected", |e, cmd, g| {
844        protected(e, cmd, false, false, false, g)
845    });
846    register_assignment(engine, "readline", readline);
847
848    register_conditional(engine, "ifcsname", ifcsname);
849    register_conditional(engine, "ifdefined", ifdefined);
850    register_conditional(engine, "iffontchar", iffontchar);
851
852    register_expandable(engine, "detokenize", detokenize);
853    register_expandable(engine, "expanded", expanded);
854    register_expandable(engine, "unexpanded", unexpanded);
855    register_expandable(engine, "eTeXrevision", eTeXrevision);
856
857    register_unexpandable(engine, "marks", CommandScope::Any, marks);
858    register_unexpandable(engine, "middle", CommandScope::MathOnly, middle);
859
860    register_simple_expandable(engine, "unless", unless);
861    register_simple_expandable(engine, "scantokens", scantokens);
862
863    register_expandable(engine, "topmarks", topmarks);
864    register_expandable(engine, "firstmarks", firstmarks);
865    register_expandable(engine, "botmarks", botmarks);
866    register_expandable(engine, "splitfirstmarks", splitfirstmarks);
867    register_expandable(engine, "splitbotmarks", splitbotmarks);
868
869    cmtodo!(engine, beginL);
870    cmtodo!(engine, beginR);
871    cmtodo!(engine, clubpenalties);
872    cmtodo!(engine, currentifbranch);
873    cmtodo!(engine, currentiflevel);
874    cmtodo!(engine, currentiftype);
875    cmtodo!(engine, displaywidowpenalties);
876    cmtodo!(engine, endL);
877    cmtodo!(engine, endR);
878    cmtodo!(engine, gluetomu);
879    cmtodo!(engine, interlinepenalties);
880    cmtodo!(engine, lastlinefit);
881    cmtodo!(engine, mutoglue);
882    cmtodo!(engine, pagediscards);
883    cmtodo!(engine, parshapedimen);
884    cmtodo!(engine, parshapeindent);
885    cmtodo!(engine, parshapelength);
886    cmtodo!(engine, showgroups);
887    cmtodo!(engine, showifs);
888    cmtodo!(engine, showtokens);
889    cmtodo!(engine, splitdiscards);
890    cmtodo!(engine, TeXXeTstate);
891    cmtodo!(engine, widowpenalties);
892    /*
893       register_int!(currentgrouplevel,engine,(e,c) => currentgrouplevel::<ET>(e,&c));
894       register_expandable!(detokenize,engine,(e,c,f) =>detokenize::<ET>(e,&c,f));
895       register_dim!(dimexpr,engine,(e,c) => dimexpr::<ET>(e,&c));
896       register_expandable!(eTeXrevision,engine,(e,c,f) => eTeXrevision::<ET>(e,&c,f));
897       register_int!(eTeXversion,engine,(e,c) => eTeXversion::<ET>(&c));
898       register_tok_assign!(everyeof,engine);
899       register_expandable!(expanded,engine,(e,c,f) => expanded::<ET>(e,&c,f));
900       register_dim!(fontchardp,engine,(e,c) => fontchardp::<ET>(e,&c));
901       register_dim!(fontcharht,engine,(e,c) => fontcharht::<ET>(e,&c));
902       register_dim!(fontcharic,engine,(e,c) => fontcharic::<ET>(e,&c));
903       register_dim!(fontcharwd,engine,(e,c) => fontcharwd::<ET>(e,&c));
904       register_skip!(glueexpr,engine,(e,c) => glueexpr::<ET>(e,&c));
905       register_conditional!(ifcsname,engine,(eu,cmd) =>ifcsname::<ET>(eu,&cmd));
906       register_conditional!(ifdefined,engine,(eu,cmd) =>ifdefined::<ET>(eu,&cmd));
907       register_conditional!(iffontchar,engine,(e,cmd) =>iffontchar::<ET>(e,&cmd));
908       register_int!(lastnodetype,engine,(e,c) => lastnodetype::<ET>(e,&c));
909       register_unexpandable!(marks,engine,None,(e,cmd) =>marks::<ET>(e,&cmd));
910       register_expandable!(topmarks,engine,(e,c,f) =>topmarks::<ET>(e,&c,f));
911       register_expandable!(firstmarks,engine,(e,c,f) =>firstmarks::<ET>(e,&c,f));
912       register_expandable!(botmarks,engine,(e,c,f) =>botmarks::<ET>(e,&c,f));
913       register_expandable!(splitfirstmarks,engine,(e,c,f) =>splitfirstmarks::<ET>(e,&c,f));
914       register_expandable!(splitbotmarks,engine,(e,c,f) =>splitbotmarks::<ET>(e,&c,f));
915       register_muskip!(muexpr,engine,(e,c) => muexpr::<ET>(e,&c));
916       register_int!(numexpr,engine,(e,c) => numexpr::<ET>(e,&c));
917       register_assign!(readline,engine,(eu,cmd,global) =>readline::<ET>(eu,&cmd,global));
918       register_expandable_notk!(scantokens,engine,(e,c) =>scantokens::<ET>(e,&c));
919       register_assign!(protected,engine,(eu,cmd,g) =>protected::<ET>(eu,&cmd,g,false,false,false));
920       register_expandable!(unexpanded,engine,(e,c,f) => unexpanded::<ET>(e,&c,f));
921       register_expandable_notk!(unless,engine,(eu,cmd) =>unless::<ET>(eu,&cmd));
922
923
924    */
925}