Skip to main content

tex_engine/commands/
tex.rs

1use super::primitives::*;
2use crate::commands::methods::{IfxCmd, MacroParser, END_TEMPLATE, END_TEMPLATE_ROW};
3use crate::commands::{
4    ActiveConditional, CharOrPrimitive, CommandScope, Macro, MacroSignature, PrimitiveCommand,
5    ResolvedToken, TeXCommand,
6};
7use crate::engine::filesystem::{File, FileSystem};
8use crate::engine::fontsystem::Font;
9use crate::engine::fontsystem::FontSystem;
10use crate::engine::gullet::methods::CSOrActiveChar;
11use crate::engine::gullet::Gullet;
12use crate::engine::mouth::Mouth;
13use crate::engine::state::{GroupType, State};
14use crate::engine::stomach::methods::SplitResult;
15use crate::engine::stomach::{Stomach, TeXMode};
16use crate::engine::utils::outputs::Outputs;
17use crate::engine::{EngineReferences, EngineTypes, TeXEngine};
18use crate::tex::catcodes::{CategoryCode, CommandCode};
19use crate::tex::characters::{Character, CharacterMap};
20use crate::tex::nodes::boxes::{BoxInfo, BoxType, HBoxInfo, TeXBox, ToOrSpread, VBoxInfo};
21use crate::tex::nodes::horizontal::{HNode, HorizontalNodeListType};
22use crate::tex::nodes::math::{
23    Delimiter, EqNoPosition, MathAtom, MathChar, MathClass, MathKernel, MathNode, MathNodeList,
24    MathNodeListType, MathNucleus, UnresolvedMarkers, UnresolvedMathChoice,
25    UnresolvedMathFontStyle,
26};
27use crate::tex::nodes::vertical::{VNode, VerticalNodeListType};
28use crate::tex::nodes::{BoxTarget, LeaderType, ListTarget, NodeList, NodeTrait, WhatsitFunction};
29use crate::tex::numerics::TeXDimen;
30use crate::tex::numerics::{MuSkip, NumSet, Skip};
31use crate::tex::tokens::control_sequences::{CSHandler, ResolvedCSName};
32use crate::tex::tokens::token_lists::CharWrite;
33use crate::tex::tokens::token_lists::Otherize;
34use crate::tex::tokens::{StandardToken, Token};
35use crate::utils::errors::{TeXError, TeXResult};
36use crate::{add_node, expand_loop};
37use either::Either;
38use std::cmp::Ordering;
39use std::fmt::Write;
40
41pub fn accent<ET: EngineTypes>(
42    engine: &mut EngineReferences<ET>,
43    tk: ET::Token,
44) -> TeXResult<(), ET> {
45    let accent = engine.read_charcode(false, &tk)?;
46    crate::expand_loop!(engine,token,
47        ResolvedToken::Tk{char,code:CommandCode::Other|CommandCode::Letter} => {
48            ET::Stomach::add_node_h(engine,HNode::Accent {accent,char,font:engine.state.get_current_font().clone()});
49            return Ok(())
50        }
51        ResolvedToken::Cmd(Some(TeXCommand::Char {char,code:CommandCode::Other|CommandCode::Letter})) => {
52            ET::Stomach::add_node_h(engine,HNode::Accent {accent,char:*char,font:engine.state.get_current_font().clone()});
53            return Ok(())
54        }
55        ResolvedToken::Cmd(Some(TeXCommand::CharDef(char))) => {
56            ET::Stomach::add_node_h(engine,HNode::Accent {accent,char:*char,font:engine.state.get_current_font().clone()});
57            return Ok(())
58        }
59        _ => {
60            engine.requeue(token)?;
61            let tk = <ET::Token as Token>::from_char_cat(accent,CommandCode::Other);
62            ET::Stomach::do_char(engine,tk,accent,CommandCode::Other)?;
63            return Ok(())
64        }
65    );
66    engine.general_error("Unexpected token after accent command".to_string())
67}
68
69pub fn afterassignment<ET: EngineTypes>(
70    engine: &mut EngineReferences<ET>,
71    tk: ET::Token,
72) -> TeXResult<(), ET> {
73    let next = engine.need_next(true, &tk)?;
74    *engine.stomach.afterassignment() = Some(next);
75    Ok(())
76}
77
78pub fn aftergroup<ET: EngineTypes>(
79    engine: &mut EngineReferences<ET>,
80    tk: ET::Token,
81) -> TeXResult<(), ET> {
82    let next = engine.need_next(true, &tk)?;
83    engine.state.aftergroup(next);
84    Ok(())
85}
86
87pub fn begingroup<ET: EngineTypes>(
88    engine: &mut EngineReferences<ET>,
89    _tk: ET::Token,
90) -> TeXResult<(), ET> {
91    engine.state.push(
92        engine.aux,
93        GroupType::SemiSimple,
94        engine.mouth.line_number(),
95    );
96    Ok(())
97}
98pub fn endgroup<ET: EngineTypes>(
99    engine: &mut EngineReferences<ET>,
100    _tk: ET::Token,
101) -> TeXResult<(), ET> {
102    match engine.state.get_group_type() {
103        Some(GroupType::SemiSimple) => (),
104        _ => TeXError::missing_endgroup(engine.aux, engine.state, engine.mouth)?,
105    }
106    engine.state.pop(engine.aux, engine.mouth);
107    Ok(())
108}
109
110pub fn end<ET: EngineTypes>(
111    engine: &mut EngineReferences<ET>,
112    _tk: ET::Token,
113) -> TeXResult<(), ET> {
114    ET::Stomach::flush(engine)?;
115    engine.mouth.finish();
116    Ok(())
117}
118
119pub fn discretionary<ET: EngineTypes>(
120    engine: &mut EngineReferences<ET>,
121    tk: ET::Token,
122) -> TeXResult<(), ET> {
123    engine.skip_argument(&tk)?;
124    engine.skip_argument(&tk)?;
125    engine.skip_argument(&tk)
126    // TODO
127}
128
129pub fn endinput<ET: EngineTypes>(
130    engine: &mut EngineReferences<ET>,
131    _tk: ET::Token,
132) -> TeXResult<(), ET> {
133    engine.mouth.endinput(engine.aux, engine.state)?;
134    Ok(())
135}
136
137pub fn errorstopmode<ET: EngineTypes>(
138    _engine: &mut EngineReferences<ET>,
139    _tk: ET::Token,
140) -> TeXResult<(), ET> {
141    Ok(())
142}
143
144pub fn expandafter<ET: EngineTypes>(
145    engine: &mut EngineReferences<ET>,
146    tk: ET::Token,
147) -> TeXResult<(), ET> {
148    let first = engine.need_next(false, &tk)?;
149    let second = engine.need_next(false, &tk)?;
150    engine.expand(second)?;
151    engine.requeue(first)
152}
153
154pub fn catcode_get<ET: EngineTypes>(
155    engine: &mut EngineReferences<ET>,
156    tk: ET::Token,
157) -> TeXResult<ET::Int, ET> {
158    let char = engine.read_charcode(false, &tk)?;
159    let u: u8 = (*engine.state.get_catcode_scheme().get(char)).into();
160    Ok(ET::Int::from(u as i32))
161}
162pub fn catcode_set<ET: EngineTypes>(
163    engine: &mut EngineReferences<ET>,
164    tk: ET::Token,
165    globally: bool,
166) -> TeXResult<(), ET> {
167    let char = engine.read_charcode(false, &tk)?;
168    let val: i64 = engine.read_int(true, &tk)?.into();
169    if !(0..=15).contains(&val) {
170        return engine.general_error(format!("Illegal category code {val}"));
171    }
172    let cc: CategoryCode = (val as u8).try_into().unwrap();
173    engine.state.set_catcode(engine.aux, char, cc, globally);
174    Ok(())
175}
176
177pub fn sfcode_get<ET: EngineTypes>(
178    engine: &mut EngineReferences<ET>,
179    tk: ET::Token,
180) -> TeXResult<ET::Int, ET> {
181    let char = engine.read_charcode(false, &tk)?;
182    let u: u16 = engine.state.get_sfcode(char);
183    Ok(ET::Int::from(u as i32))
184}
185pub fn sfcode_set<ET: EngineTypes>(
186    engine: &mut EngineReferences<ET>,
187    tk: ET::Token,
188    globally: bool,
189) -> TeXResult<(), ET> {
190    let char = engine.read_charcode(false, &tk)?;
191    let val: i64 = engine.read_int(true, &tk)?.into();
192    if !(0..=32767).contains(&val) {
193        return engine.general_error(format!("Illegal spacefactor code {val}"));
194    }
195    let sf = val as u16;
196    engine.state.set_sfcode(engine.aux, char, sf, globally);
197    Ok(())
198}
199
200pub fn spacefactor_get<ET: EngineTypes>(
201    engine: &mut EngineReferences<ET>,
202    _tk: ET::Token,
203) -> TeXResult<ET::Int, ET> {
204    Ok(ET::Int::from(engine.stomach.data_mut().spacefactor))
205}
206pub fn spacefactor_set<ET: EngineTypes>(
207    engine: &mut EngineReferences<ET>,
208    tk: ET::Token,
209    _globally: bool,
210) -> TeXResult<(), ET> {
211    let val = match engine.read_int(true, &tk)?.try_into() {
212        Ok(v) => v,
213        _ => return engine.general_error("Illegal spacefactor code".to_string()),
214    };
215    engine.stomach.data_mut().spacefactor = val;
216    Ok(())
217}
218
219pub fn parshape_get<ET: EngineTypes>(
220    engine: &mut EngineReferences<ET>,
221    _tk: ET::Token,
222) -> TeXResult<ET::Int, ET> {
223    Ok(ET::Int::from(engine.state.get_parshape().len() as i32))
224}
225pub fn parshape_set<ET: EngineTypes>(
226    engine: &mut EngineReferences<ET>,
227    tk: ET::Token,
228    globally: bool,
229) -> TeXResult<(), ET> {
230    let len = engine.read_int(false, &tk)?.into();
231    if len < 0 {
232        return engine.general_error(format!("Illegal parshape length {len}"));
233    }
234    let mut shape = Vec::with_capacity(len as usize);
235    for _ in 0..len {
236        let a = engine.read_dim(false, &tk)?;
237        let b = engine.read_dim(false, &tk)?;
238        shape.push((a, b))
239    }
240    engine.state.set_parshape(engine.aux, shape, globally);
241    Ok(())
242}
243
244pub fn lccode_get<ET: EngineTypes>(
245    engine: &mut EngineReferences<ET>,
246    tk: ET::Token,
247) -> TeXResult<ET::Int, ET> {
248    let char = engine.read_charcode(false, &tk)?;
249    let u = engine.state.get_lccode(char).into();
250    match ET::Int::try_from(u as i64) {
251        Ok(v) => Ok(v),
252        _ => {
253            engine.general_error(format!("Illegal lowercase code {u}"))?;
254            Ok(ET::Int::from(0))
255        }
256    }
257}
258pub fn lccode_set<ET: EngineTypes>(
259    engine: &mut EngineReferences<ET>,
260    tk: ET::Token,
261    globally: bool,
262) -> TeXResult<(), ET> {
263    let char = engine.read_charcode(false, &tk)?;
264    let val = engine.read_charcode(true, &tk)?;
265    engine.state.set_lccode(engine.aux, char, val, globally);
266    Ok(())
267}
268
269pub fn uccode_get<ET: EngineTypes>(
270    engine: &mut EngineReferences<ET>,
271    tk: ET::Token,
272) -> TeXResult<ET::Int, ET> {
273    let char = engine.read_charcode(false, &tk)?;
274    let u = engine.state.get_uccode(char).into();
275    match ET::Int::try_from(u as i64) {
276        Ok(v) => Ok(v),
277        _ => {
278            engine.general_error(format!("Illegal uppercase code {u}"))?;
279            Ok(ET::Int::from(0))
280        }
281    }
282}
283pub fn uccode_set<ET: EngineTypes>(
284    engine: &mut EngineReferences<ET>,
285    tk: ET::Token,
286    globally: bool,
287) -> TeXResult<(), ET> {
288    let char = engine.read_charcode(false, &tk)?;
289    let val = engine.read_charcode(true, &tk)?;
290    engine.state.set_uccode(engine.aux, char, val, globally);
291    Ok(())
292}
293
294pub fn mathcode_get<ET: EngineTypes>(
295    engine: &mut EngineReferences<ET>,
296    tk: ET::Token,
297) -> TeXResult<ET::Int, ET> {
298    let char = engine.read_charcode(false, &tk)?;
299    let u = engine.state.get_mathcode(char);
300    match ET::Int::try_from(u as i64) {
301        Ok(v) => Ok(v),
302        _ => {
303            engine.general_error(format!("Illegal mathcode {u}"))?;
304            Ok(ET::Int::from(0))
305        }
306    }
307}
308pub fn mathcode_set<ET: EngineTypes>(
309    engine: &mut EngineReferences<ET>,
310    tk: ET::Token,
311    globally: bool,
312) -> TeXResult<(), ET> {
313    let char = engine.read_charcode(false, &tk)?;
314    let mut val = engine.read_int(true, &tk)?.into();
315    if val < 0 || val > 32768 {
316        engine.general_error(format!("Illegal mathcode {val}"))?;
317        val = 0;
318    }
319    engine
320        .state
321        .set_mathcode(engine.aux, char, val as u32, globally);
322    Ok(())
323}
324
325pub fn delcode_get<ET: EngineTypes>(
326    engine: &mut EngineReferences<ET>,
327    tk: ET::Token,
328) -> TeXResult<ET::Int, ET> {
329    let char = engine.read_charcode(false, &tk)?;
330    Ok(engine.state.get_delcode(char))
331}
332pub fn delcode_set<ET: EngineTypes>(
333    engine: &mut EngineReferences<ET>,
334    tk: ET::Token,
335    globally: bool,
336) -> TeXResult<(), ET> {
337    let char = engine.read_charcode(false, &tk)?;
338    let val = engine.read_int(true, &tk)?;
339    engine.state.set_delcode(engine.aux, char, val, globally);
340    Ok(())
341}
342
343pub fn chardef<ET: EngineTypes>(
344    engine: &mut EngineReferences<ET>,
345    tk: ET::Token,
346    globally: bool,
347) -> TeXResult<(), ET> {
348    let name = engine.read_control_sequence(&tk)?;
349    let char = engine.read_charcode(true, &tk)?;
350    let cmd = TeXCommand::CharDef(char);
351    engine.set_command(&name, Some(cmd), globally);
352    Ok(())
353}
354
355pub fn r#char<ET: EngineTypes>(
356    engine: &mut EngineReferences<ET>,
357    tk: ET::Token,
358) -> TeXResult<(), ET> {
359    let char = engine.read_charcode(false, &tk)?;
360    match engine.stomach.data_mut().mode() {
361        TeXMode::DisplayMath | TeXMode::InlineMath => ET::Stomach::do_char_in_math(engine, char),
362        _ => {
363            let tk = <ET::Token as Token>::from_char_cat(char, CommandCode::Other);
364            ET::Stomach::do_char(engine, tk, char, CommandCode::Other)
365        }
366    }
367}
368
369pub fn csname<ET: EngineTypes>(
370    engine: &mut EngineReferences<ET>,
371    _tk: ET::Token,
372) -> TeXResult<(), ET> {
373    let name = engine.read_csname()?;
374    if engine.state.get_command(&name).is_none() {
375        engine.state.set_command(
376            engine.aux,
377            name.clone(),
378            Some(TeXCommand::Primitive {
379                name: PRIMITIVES.relax,
380                cmd: PrimitiveCommand::Relax,
381            }),
382            false,
383        )
384    }
385    engine.mouth.requeue(ET::Token::from_cs(name));
386    Ok(())
387}
388
389pub fn endcsname<ET: EngineTypes>(
390    engine: &mut EngineReferences<ET>,
391    _tk: ET::Token,
392) -> TeXResult<(), ET> {
393    engine.general_error("Unexpected `\\endcsname`".to_string())
394}
395
396pub fn count_get<ET: EngineTypes>(
397    engine: &mut EngineReferences<ET>,
398    tk: ET::Token,
399) -> TeXResult<ET::Int, ET> {
400    let idx = engine.read_register_index(false, &tk)?;
401    Ok(engine.state.get_int_register(idx))
402}
403pub fn count_set<ET: EngineTypes>(
404    engine: &mut EngineReferences<ET>,
405    tk: ET::Token,
406    globally: bool,
407) -> TeXResult<(), ET> {
408    let idx = engine.read_register_index(false, &tk)?;
409    let val = engine.read_int(true, &tk)?;
410    engine
411        .state
412        .set_int_register(engine.aux, idx, val, globally);
413    Ok(())
414}
415
416pub fn dimen_get<ET: EngineTypes>(
417    engine: &mut EngineReferences<ET>,
418    tk: ET::Token,
419) -> TeXResult<ET::Dim, ET> {
420    let idx = engine.read_register_index(false, &tk)?;
421    Ok(engine.state.get_dim_register(idx))
422}
423pub fn dimen_set<ET: EngineTypes>(
424    engine: &mut EngineReferences<ET>,
425    tk: ET::Token,
426    globally: bool,
427) -> TeXResult<(), ET> {
428    let idx = engine.read_register_index(false, &tk)?;
429    let val = engine.read_dim(true, &tk)?;
430    engine
431        .state
432        .set_dim_register(engine.aux, idx, val, globally);
433    Ok(())
434}
435
436pub fn skip_get<ET: EngineTypes>(
437    engine: &mut EngineReferences<ET>,
438    tk: ET::Token,
439) -> TeXResult<Skip<ET::Dim>, ET> {
440    let idx = engine.read_register_index(false, &tk)?;
441    Ok(engine.state.get_skip_register(idx))
442}
443pub fn skip_set<ET: EngineTypes>(
444    engine: &mut EngineReferences<ET>,
445    tk: ET::Token,
446    globally: bool,
447) -> TeXResult<(), ET> {
448    let idx = engine.read_register_index(false, &tk)?;
449    let val = engine.read_skip(true, &tk)?;
450    engine
451        .state
452        .set_skip_register(engine.aux, idx, val, globally);
453    Ok(())
454}
455
456pub fn muskip_get<ET: EngineTypes>(
457    engine: &mut EngineReferences<ET>,
458    tk: ET::Token,
459) -> TeXResult<MuSkip<ET::MuDim>, ET> {
460    let idx = engine.read_register_index(false, &tk)?;
461    Ok(engine.state.get_muskip_register(idx))
462}
463pub fn muskip_set<ET: EngineTypes>(
464    engine: &mut EngineReferences<ET>,
465    tk: ET::Token,
466    globally: bool,
467) -> TeXResult<(), ET> {
468    let idx = engine.read_register_index(false, &tk)?;
469    let val = engine.read_muskip(true, &tk)?;
470    engine
471        .state
472        .set_muskip_register(engine.aux, idx, val, globally);
473    Ok(())
474}
475
476pub fn countdef<ET: EngineTypes>(
477    engine: &mut EngineReferences<ET>,
478    tk: ET::Token,
479    globally: bool,
480) -> TeXResult<(), ET> {
481    let name = engine.read_control_sequence(&tk)?;
482    let i = engine.read_register_index(true, &tk)?;
483    let cmd = TeXCommand::IntRegister(i);
484    engine.set_command(&name, Some(cmd), globally);
485    Ok(())
486}
487
488pub fn dimendef<ET: EngineTypes>(
489    engine: &mut EngineReferences<ET>,
490    tk: ET::Token,
491    globally: bool,
492) -> TeXResult<(), ET> {
493    let name = engine.read_control_sequence(&tk)?;
494    let i = engine.read_register_index(true, &tk)?;
495    let cmd = TeXCommand::DimRegister(i);
496    engine.set_command(&name, Some(cmd), globally);
497    Ok(())
498}
499
500pub fn skipdef<ET: EngineTypes>(
501    engine: &mut EngineReferences<ET>,
502    tk: ET::Token,
503    globally: bool,
504) -> TeXResult<(), ET> {
505    let name = engine.read_control_sequence(&tk)?;
506    let i = engine.read_register_index(true, &tk)?;
507    let cmd = TeXCommand::SkipRegister(i);
508    engine.set_command(&name, Some(cmd), globally);
509    Ok(())
510}
511
512pub fn muskipdef<ET: EngineTypes>(
513    engine: &mut EngineReferences<ET>,
514    tk: ET::Token,
515    globally: bool,
516) -> TeXResult<(), ET> {
517    let name = engine.read_control_sequence(&tk)?;
518    let i = engine.read_register_index(true, &tk)?;
519    let cmd = TeXCommand::MuSkipRegister(i);
520    engine.set_command(&name, Some(cmd), globally);
521    Ok(())
522}
523
524pub fn toksdef<ET: EngineTypes>(
525    engine: &mut EngineReferences<ET>,
526    tk: ET::Token,
527    globally: bool,
528) -> TeXResult<(), ET> {
529    let name = engine.read_control_sequence(&tk)?;
530    let i = engine.read_register_index(true, &tk)?;
531    let cmd = TeXCommand::ToksRegister(i);
532    engine.set_command(&name, Some(cmd), globally);
533    Ok(())
534}
535
536pub fn def<ET: EngineTypes>(
537    engine: &mut EngineReferences<ET>,
538    tk: ET::Token,
539    outer: bool,
540    long: bool,
541    protected: bool,
542    globally: bool,
543) -> TeXResult<(), ET> {
544    let cm = loop {
545        let t = engine.need_next(false, &tk)?;
546        match t.to_enum() {
547            StandardToken::Character(c, CommandCode::Active) => break CSOrActiveChar::Active(c),
548            StandardToken::Character(_, CommandCode::Space) => (),
549            StandardToken::ControlSequence(cs) => break CSOrActiveChar::Name(cs),
550            _ => {
551                return engine.general_error("Expected control sequence after `\\def`".to_string())
552            }
553        }
554    };
555    let mut parser = MacroParser::new();
556    engine.iterate(
557        |_, _, t| parser.do_signature_token(t),
558        |_, _, _| {
559            Err(TeXError::General(
560                "Unexpected Token in `\\def`-signature".to_string(),
561            ))
562        },
563    )?;
564    engine.read_until_endgroup(&tk, |_, _, t| parser.do_expansion_token(t))?;
565    let cmd = parser.close(long, outer, protected);
566    engine.set_command(&cm, Some(TeXCommand::Macro(cmd)), globally);
567    Ok(())
568}
569
570pub fn edef<ET: EngineTypes>(
571    engine: &mut EngineReferences<ET>,
572    tk: ET::Token,
573    outer: bool,
574    long: bool,
575    protected: bool,
576    globally: bool,
577) -> TeXResult<(), ET> {
578    let cm = loop {
579        let t = engine.need_next(false, &tk)?;
580        match t.to_enum() {
581            StandardToken::Character(c, CommandCode::Active) => break CSOrActiveChar::Active(c),
582            StandardToken::Character(_, CommandCode::Space) => (),
583            StandardToken::ControlSequence(cs) => break CSOrActiveChar::Name(cs),
584            _ => {
585                return engine.general_error("Expected control sequence after `\\edef`".to_string())
586            }
587        }
588    };
589
590    let mut parser = MacroParser::new();
591    engine.iterate(
592        |_, _, t| parser.do_signature_token(t),
593        |_, _, _| {
594            Err(TeXError::General(
595                "Unexpected token in `\\edef`-signature".to_string(),
596            ))
597        },
598    )?;
599    engine.expand_until_endgroup(false, true, &tk, |_, _, t| parser.do_expansion_token(t))?;
600    let cmd = parser.close(long, outer, protected);
601    engine.set_command(&cm, Some(TeXCommand::Macro(cmd)), globally);
602    Ok(())
603}
604
605pub fn xdef<ET: EngineTypes>(
606    engine: &mut EngineReferences<ET>,
607    tk: ET::Token,
608    outer: bool,
609    long: bool,
610    protected: bool,
611    _globally: bool,
612) -> TeXResult<(), ET> {
613    edef(engine, tk, outer, long, protected, true)
614}
615
616pub fn gdef<ET: EngineTypes>(
617    engine: &mut EngineReferences<ET>,
618    tk: ET::Token,
619    outer: bool,
620    long: bool,
621    protected: bool,
622    _globally: bool,
623) -> TeXResult<(), ET> {
624    def(engine, tk, outer, long, protected, true)
625}
626
627pub fn dp_get<ET: EngineTypes>(
628    engine: &mut EngineReferences<ET>,
629    tk: ET::Token,
630) -> TeXResult<ET::Dim, ET> {
631    let idx = engine.read_register_index(false, &tk)?;
632    Ok(match engine.state.get_box_register(idx) {
633        None => ET::Dim::default(),
634        Some(b) => b.depth(),
635    })
636}
637pub fn dp_set<ET: EngineTypes>(
638    engine: &mut EngineReferences<ET>,
639    tk: ET::Token,
640    _globally: bool,
641) -> TeXResult<(), ET> {
642    let idx = engine.read_register_index(false, &tk)?;
643    let dim = engine.read_dim(true, &tk)?;
644    if let Some(b) = engine.state.get_box_register_mut(idx) {
645        b.assign_depth(dim)
646    }
647    Ok(())
648}
649
650pub fn ht_get<ET: EngineTypes>(
651    engine: &mut EngineReferences<ET>,
652    tk: ET::Token,
653) -> TeXResult<ET::Dim, ET> {
654    let idx = engine.read_register_index(false, &tk)?;
655    Ok(match engine.state.get_box_register(idx) {
656        None => ET::Dim::default(),
657        Some(b) => b.height(),
658    })
659}
660pub fn ht_set<ET: EngineTypes>(
661    engine: &mut EngineReferences<ET>,
662    tk: ET::Token,
663    _globally: bool,
664) -> TeXResult<(), ET> {
665    let idx = engine.read_register_index(false, &tk)?;
666    let dim = engine.read_dim(true, &tk)?;
667    if let Some(b) = engine.state.get_box_register_mut(idx) {
668        b.assign_height(dim)
669    }
670    Ok(())
671}
672
673pub fn wd_get<ET: EngineTypes>(
674    engine: &mut EngineReferences<ET>,
675    tk: ET::Token,
676) -> TeXResult<ET::Dim, ET> {
677    let idx = engine.read_register_index(false, &tk)?;
678    Ok(match engine.state.get_box_register(idx) {
679        None => ET::Dim::default(),
680        Some(b) => b.width(),
681    })
682}
683pub fn wd_set<ET: EngineTypes>(
684    engine: &mut EngineReferences<ET>,
685    tk: ET::Token,
686    _globally: bool,
687) -> TeXResult<(), ET> {
688    let idx = engine.read_register_index(false, &tk)?;
689    let dim = engine.read_dim(true, &tk)?;
690    if let Some(b) = engine.state.get_box_register_mut(idx) {
691        b.assign_width(dim)
692    }
693    Ok(())
694}
695
696pub fn global<ET: EngineTypes>(
697    engine: &mut EngineReferences<ET>,
698    tk: ET::Token,
699    outer: bool,
700    long: bool,
701    protected: bool,
702    _globally: bool,
703) -> TeXResult<(), ET> {
704    let allow_others = !outer && !long && !protected;
705    crate::expand_loop!(engine,token,
706        ResolvedToken::Cmd(Some(TeXCommand::Primitive{name,cmd:PrimitiveCommand::Assignment(a)})) => match *name {
707            n if n == PRIMITIVES.outer => return self::outer(engine,token,outer,long,protected,true),
708            n if n == PRIMITIVES.long => return self::long(engine,token,outer,long,protected,true),
709            n if n == PRIMITIVES.protected => return super::etex::protected(engine,token,outer,long,protected,true),
710            n if n == PRIMITIVES.global => return self::global(engine,token,outer,long,protected,true),
711            n if n == PRIMITIVES.def => return self::def(engine,token,outer,long,protected,true),
712            n if n == PRIMITIVES.edef => return self::edef(engine,token,outer,long,protected,true),
713            n if n == PRIMITIVES.xdef => return self::xdef(engine,token,outer,long,protected,true),
714            n if n == PRIMITIVES.gdef => return self::gdef(engine,token,outer,long,protected,true),
715            n if allow_others => return ET::Stomach::do_assignment(engine,n,token,*a,true),
716            _ => return engine.general_error(format!("Illegal command after `\\global`: {}",name.display::<ET::Char>(Some(b'\\'.into()))))
717        }
718        ResolvedToken::Cmd(Some(TeXCommand::IntRegister(u))) if allow_others =>
719            return ET::Stomach::assign_int_register(engine,*u,true,tk),
720        ResolvedToken::Cmd(Some(TeXCommand::Primitive{name,cmd:PrimitiveCommand::PrimitiveInt})) if allow_others =>
721            return ET::Stomach::assign_primitive_int(engine,*name,true,tk),
722        ResolvedToken::Cmd(Some(TeXCommand::Primitive{cmd:PrimitiveCommand::Int {assign:Some(f),..},name})) if allow_others =>
723            return ET::Stomach::do_assignment(engine,*name,token,*f,true),
724        ResolvedToken::Cmd(Some(TeXCommand::DimRegister(u))) if allow_others =>
725            return ET::Stomach::assign_dim_register(engine,*u,true,tk),
726        ResolvedToken::Cmd(Some(TeXCommand::Primitive{name,cmd:PrimitiveCommand::PrimitiveDim})) if allow_others =>
727            return ET::Stomach::assign_primitive_dim(engine,*name,true,tk),
728        ResolvedToken::Cmd(Some(TeXCommand::Primitive{cmd:PrimitiveCommand::Dim {assign:Some(f),..},name})) if allow_others =>
729            return ET::Stomach::do_assignment(engine,*name,token,*f,true),
730        ResolvedToken::Cmd(Some(TeXCommand::SkipRegister(u))) if allow_others =>
731            return ET::Stomach::assign_skip_register(engine,*u,true,tk),
732        ResolvedToken::Cmd(Some(TeXCommand::Primitive{name,cmd:PrimitiveCommand::PrimitiveSkip})) if allow_others =>
733            return ET::Stomach::assign_primitive_skip(engine,*name,true,tk),
734        ResolvedToken::Cmd(Some(TeXCommand::Primitive{cmd:PrimitiveCommand::Skip {assign:Some(f),..},name})) if allow_others =>
735            return ET::Stomach::do_assignment(engine,*name,token,*f,true),
736        ResolvedToken::Cmd(Some(TeXCommand::MuSkipRegister(u))) if allow_others =>
737            return ET::Stomach::assign_muskip_register(engine,*u,true,tk),
738        ResolvedToken::Cmd(Some(TeXCommand::Primitive{name,cmd:PrimitiveCommand::PrimitiveMuSkip})) if allow_others =>
739            return ET::Stomach::assign_primitive_muskip(engine,*name,true,tk),
740        ResolvedToken::Cmd(Some(TeXCommand::Primitive{cmd:PrimitiveCommand::MuSkip {assign:Some(f),..},name})) if allow_others =>
741            return ET::Stomach::do_assignment(engine,*name,token,*f,true),
742        ResolvedToken::Cmd(Some(TeXCommand::ToksRegister(u))) if allow_others =>
743            return ET::Stomach::assign_toks_register(engine,token,*u,true),
744        ResolvedToken::Cmd(Some(TeXCommand::Primitive{name,cmd:PrimitiveCommand::PrimitiveToks})) if allow_others =>
745            return ET::Stomach::assign_primitive_toks(engine,token,*name,true),
746        ResolvedToken::Cmd(Some(TeXCommand::Primitive{cmd:PrimitiveCommand::FontCmd {assign:Some(f),..},name})) if allow_others =>
747            return ET::Stomach::do_assignment(engine,*name,token,*f,true),
748        ResolvedToken::Cmd(Some(TeXCommand::Primitive{cmd:PrimitiveCommand::Whatsit {get,..},name})) if allow_others =>
749            return ET::Stomach::do_whatsit(engine,*name,token,*get),
750        ResolvedToken::Cmd(Some(TeXCommand::Primitive {cmd:PrimitiveCommand::Relax,..})) => (),
751        ResolvedToken::Cmd(Some(_)) => {
752            let s = token.display(engine.aux.memory.cs_interner(),engine.state.get_catcode_scheme(),engine.state.get_escape_char()).to_string();
753            return engine.general_error(format!("Illegal command after `\\global`: {s}"))
754        }
755        _ => {
756            let s = token.display(engine.aux.memory.cs_interner(),engine.state.get_catcode_scheme(),engine.state.get_escape_char()).to_string();
757            return engine.general_error(format!("Illegal command after `\\global`: {s}"))
758        }
759    );
760    TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, &tk)
761}
762pub fn outer<ET: EngineTypes>(
763    engine: &mut EngineReferences<ET>,
764    tk: ET::Token,
765    _outer: bool,
766    long: bool,
767    protected: bool,
768    globally: bool,
769) -> TeXResult<(), ET> {
770    crate::expand_loop!(engine,token,
771        ResolvedToken::Cmd(Some(TeXCommand::Primitive{name,..})) => match *name {
772            n if n == PRIMITIVES.outer => return self::outer(engine,token,true,long,protected,globally),
773            n if n == PRIMITIVES.long => return self::long(engine,token,true,long,protected,globally),
774            n if n == PRIMITIVES.protected => return super::etex::protected(engine,token,true,long,protected,globally),
775            n if n == PRIMITIVES.global => return self::global(engine,token,true,long,protected,globally),
776            n if n == PRIMITIVES.def => return self::def(engine,token,true,long,protected,globally),
777            n if n == PRIMITIVES.edef => return self::edef(engine,token,true,long,protected,globally),
778            n if n == PRIMITIVES.xdef => return self::xdef(engine,token,true,long,protected,globally),
779            n if n == PRIMITIVES.gdef => return self::gdef(engine,token,true,long,protected,globally),
780            n if n == PRIMITIVES.relax => (),
781            n => {
782                let s = n.display(engine.state.get_escape_char()).to_string();
783                engine.requeue(token)?;
784                return Err(TeXError::General(format!("You can't use a prefix with '{}'",s)))
785            }
786        }
787        _ => {
788            let s = token.display(engine.aux.memory.cs_interner(),engine.state.get_catcode_scheme(),engine.state.get_escape_char()).to_string();
789            engine.requeue(token)?;
790            return Err(TeXError::General(format!("You can't use a prefix with '{}'",s)))
791        }
792    );
793    TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, &tk)
794}
795
796pub fn long<ET: EngineTypes>(
797    engine: &mut EngineReferences<ET>,
798    tk: ET::Token,
799    outer: bool,
800    _long: bool,
801    protected: bool,
802    globally: bool,
803) -> TeXResult<(), ET> {
804    crate::expand_loop!(engine,token,
805        ResolvedToken::Cmd(Some(TeXCommand::Primitive{name,..})) => match *name {
806            n if n == PRIMITIVES.outer => return self::outer(engine,token,outer,true,protected,globally),
807            n if n == PRIMITIVES.long => return self::long(engine,token,outer,true,protected,globally),
808            n if n == PRIMITIVES.protected => return super::etex::protected(engine,token,outer,true,protected,globally),
809            n if n == PRIMITIVES.global => return self::global(engine,token,outer,true,protected,globally),
810            n if n == PRIMITIVES.def => return self::def(engine,token,outer,true,protected,globally),
811            n if n == PRIMITIVES.edef => return self::edef(engine,token,outer,true,protected,globally),
812            n if n == PRIMITIVES.xdef => return self::xdef(engine,token,outer,true,protected,globally),
813            n if n == PRIMITIVES.gdef => return self::gdef(engine,token,outer,true,protected,globally),
814            n if n == PRIMITIVES.relax => (),
815            n => {
816                let s = n.display(engine.state.get_escape_char()).to_string();
817                engine.requeue(token)?;
818                return Err(TeXError::General(format!("You can't use a prefix with '{}'",s)))
819            }
820        }
821        _ => {
822            let s = token.display(engine.aux.memory.cs_interner(),engine.state.get_catcode_scheme(),engine.state.get_escape_char()).to_string();
823            engine.requeue(token)?;
824            return Err(TeXError::General(format!("You can't use a prefix with '{}'",s)))
825        }
826    );
827    TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, &tk)
828}
829
830macro_rules! modify_num {
831    ($engine:ident,$globally:ident,$int:expr,$dim:expr,$skip:expr) => {
832        crate::expand_loop!($engine,token,
833            ResolvedToken::Cmd(Some(cm)) => match cm {
834                TeXCommand::Primitive{name,..} if *name == PRIMITIVES.count => {
835                    let idx = $engine.read_register_index(false,&token)?;
836                    return crate::commands::methods::modify_int_register($engine,idx,$globally,$int)
837                }
838                TeXCommand::IntRegister(idx) => {
839                    return crate::commands::methods::modify_int_register($engine,*idx,$globally,$int)
840                }
841                TeXCommand::Primitive{name,cmd:PrimitiveCommand::PrimitiveInt} => {
842                    return crate::commands::methods::modify_primitive_int($engine,*name,$globally,$int)
843                }
844                TeXCommand::Primitive{name,..} if *name == PRIMITIVES.dimen => {
845                    let idx = $engine.read_register_index(false,&token)?;
846                    return crate::commands::methods::modify_dim_register($engine,idx,$globally,$dim)
847                }
848                TeXCommand::DimRegister(idx) => {
849                    return crate::commands::methods::modify_dim_register($engine,*idx,$globally,$dim)
850                }
851                TeXCommand::Primitive{name,cmd:PrimitiveCommand::PrimitiveDim} => {
852                    return crate::commands::methods::modify_primitive_dim($engine,*name,$globally,$dim)
853                }
854                TeXCommand::Primitive{name,..} if *name == PRIMITIVES.skip => {
855                    let idx = $engine.read_register_index(false,&token)?;
856                    return crate::commands::methods::modify_skip_register($engine,idx,$globally,$skip)
857                }
858                TeXCommand::SkipRegister(idx) => {
859                    return crate::commands::methods::modify_skip_register($engine,*idx,$globally,$skip)
860                }
861                TeXCommand::Primitive{name,cmd:PrimitiveCommand::PrimitiveSkip} => {
862                    return crate::commands::methods::modify_primitive_skip($engine,*name,$globally,$skip)
863                }
864                _ => {
865                    let s = token.display($engine.aux.memory.cs_interner(),$engine.state.get_catcode_scheme(),$engine.state.get_escape_char()).to_string();
866                    $engine.requeue(token)?;
867                    return Err(TeXError::General(format!("Unexpected token in \\divide/\\multiply: {s}")))
868                }
869            }
870            _ => {
871                let s = token.display($engine.aux.memory.cs_interner(),$engine.state.get_catcode_scheme(),$engine.state.get_escape_char()).to_string();
872                $engine.requeue(token)?;
873                return Err(TeXError::General(format!("Unexpected token in \\divide/\\multiply: {s}")))
874            }
875        );
876    };
877}
878
879pub fn advance<ET: EngineTypes>(
880    engine: &mut EngineReferences<ET>,
881    tk: ET::Token,
882    globally: bool,
883) -> TeXResult<(), ET> {
884    modify_num!(
885        engine,
886        globally,
887        |a, e| Ok(a + e.read_int(false, &tk)?),
888        |a, e| Ok(a + e.read_dim(false, &tk)?),
889        |a, e| Ok(a + e.read_skip(false, &tk)?)
890    );
891    TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, &tk)
892}
893pub fn divide<ET: EngineTypes>(
894    engine: &mut EngineReferences<ET>,
895    tk: ET::Token,
896    globally: bool,
897) -> TeXResult<(), ET> {
898    modify_num!(
899        engine,
900        globally,
901        |a, e| {
902            let b = e.read_int(false, &tk)?;
903            if b == ET::Int::default() {
904                return Err(TeXError::General("Division by zero".to_string()));
905            }
906            Ok(a / b)
907        },
908        |a, e| {
909            let b = e.read_int(false, &tk)?;
910            if b == ET::Int::default() {
911                return Err(TeXError::General("Division by zero".to_string()));
912            }
913            Ok(a / b)
914        },
915        |a, e| {
916            let b = e.read_int(false, &tk)?;
917            if b == ET::Int::default() {
918                return Err(TeXError::General("Division by zero".to_string()));
919            }
920            Ok(a / b)
921        }
922    );
923    TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, &tk)
924}
925pub fn multiply<ET: EngineTypes>(
926    engine: &mut EngineReferences<ET>,
927    tk: ET::Token,
928    globally: bool,
929) -> TeXResult<(), ET> {
930    modify_num!(
931        engine,
932        globally,
933        |a, e| Ok(a * e.read_int(false, &tk)?),
934        |a, e| {
935            let b = e.read_int(false, &tk)?;
936            Ok(a * b)
937        },
938        |a, e| {
939            let b = e.read_int(false, &tk)?;
940            Ok(a * b)
941        }
942    );
943    TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, &tk)
944}
945
946pub fn r#else<ET: EngineTypes>(
947    engine: &mut EngineReferences<ET>,
948    tk: ET::Token,
949) -> TeXResult<(), ET> {
950    let conds = engine.gullet.get_conditionals();
951    let name = match conds.pop() {
952        Some(ActiveConditional::True(id)) => {
953            conds.push(ActiveConditional::Else(id));
954            id
955        }
956        Some(c @ ActiveConditional::Case(_)) => {
957            conds.push(c);
958            PRIMITIVES.ifcase
959        }
960        Some(u @ ActiveConditional::Unfinished(_)) => {
961            conds.push(u);
962            engine.mouth.requeue(tk);
963            let relax = engine.aux.memory.cs_interner_mut().cs_from_str("relax");
964            engine.mouth.requeue(ET::Token::from_cs(relax));
965            return Ok(());
966        }
967        Some(ActiveConditional::Else(_)) => {
968            return Err(TeXError::General(
969                "Unexpected `\\else` in `\\else`-branch".to_string(),
970            ))
971        }
972        _ => {
973            return Err(TeXError::General(
974                "Unexpected `\\else` outside of a condition".to_string(),
975            ))
976        }
977    };
978    let trace = engine.state.get_primitive_int(PRIMITIVES.tracingifs) > ET::Int::default();
979    let index = conds.len();
980    if trace {
981        engine.aux.outputs.write_neg1(format_args!(
982            "{{{}else: {} (level {}) entered on line {}}}",
983            <ET::Char as Character>::display_opt(engine.state.get_escape_char()),
984            name.display(engine.state.get_escape_char()),
985            index,
986            engine.mouth.line_number()
987        ));
988    }
989    crate::engine::gullet::methods::false_loop(engine, index, false, false)?;
990    if trace {
991        engine.aux.outputs.write_neg1(format_args!(
992            "{{{}fi: {} (level {}) entered on line {}}}",
993            <ET::Char as Character>::display_opt(engine.state.get_escape_char()),
994            name.display(engine.state.get_escape_char()),
995            index,
996            engine.mouth.line_number()
997        ));
998    }
999    Ok(())
1000}
1001
1002pub fn or<ET: EngineTypes>(engine: &mut EngineReferences<ET>, tk: ET::Token) -> TeXResult<(), ET> {
1003    let conds = engine.gullet.get_conditionals();
1004    match conds.pop() {
1005        Some(ActiveConditional::Case(_)) => {
1006            conds.push(ActiveConditional::Else(PRIMITIVES.ifcase));
1007        }
1008        Some(u @ ActiveConditional::Unfinished(_)) => {
1009            conds.push(u);
1010            engine.mouth.requeue(tk);
1011            let relax = engine.aux.memory.cs_interner_mut().cs_from_str("relax");
1012            engine.mouth.requeue(ET::Token::from_cs(relax));
1013            return Ok(());
1014        }
1015        _ => return engine.general_error("`\\or` outside of `\\case`".to_string()),
1016    };
1017    let trace = engine.state.get_primitive_int(PRIMITIVES.tracingifs) > ET::Int::default();
1018    let index = conds.len();
1019    if trace {
1020        engine.aux.outputs.write_neg1(format_args!(
1021            "{{{}or: {}ifcase (level {}) entered on line {}}}",
1022            <ET::Char as Character>::display_opt(engine.state.get_escape_char()),
1023            <ET::Char as Character>::display_opt(engine.state.get_escape_char()),
1024            index,
1025            engine.mouth.line_number()
1026        ));
1027    }
1028    crate::engine::gullet::methods::false_loop(engine, index, false, true)?;
1029    if trace {
1030        engine.aux.outputs.write_neg1(format_args!(
1031            "{{{}fi: {}ifcase (level {}) entered on line {}}}",
1032            <ET::Char as Character>::display_opt(engine.state.get_escape_char()),
1033            <ET::Char as Character>::display_opt(engine.state.get_escape_char()),
1034            index,
1035            engine.mouth.line_number()
1036        ));
1037    }
1038    Ok(())
1039}
1040
1041pub fn endlinechar_get<ET: EngineTypes>(
1042    engine: &mut EngineReferences<ET>,
1043    _tk: ET::Token,
1044) -> TeXResult<ET::Int, ET> {
1045    Ok(ET::Int::from(match engine.state.get_endline_char() {
1046        Some(c) => c.into() as i32,
1047        _ => -1,
1048    }))
1049}
1050pub fn endlinechar_set<ET: EngineTypes>(
1051    engine: &mut EngineReferences<ET>,
1052    tk: ET::Token,
1053    globally: bool,
1054) -> TeXResult<(), ET> {
1055    let val: i64 = engine.read_int(true, &tk)?.into();
1056    let val = match val.cmp(&-1) {
1057        Ordering::Less => return engine.general_error(format!("Illegal endline character: {val}")),
1058        Ordering::Equal => None,
1059        Ordering::Greater => match ET::Char::try_from(val as u64) {
1060            Ok(c) => Some(c),
1061            _ => return engine.general_error(format!("Illegal endline character: {val}")),
1062        },
1063    };
1064    engine.state.set_endline_char(engine.aux, val, globally);
1065    Ok(())
1066}
1067
1068pub fn escapechar_get<ET: EngineTypes>(
1069    engine: &mut EngineReferences<ET>,
1070    _tk: ET::Token,
1071) -> TeXResult<ET::Int, ET> {
1072    Ok(ET::Int::from(match engine.state.get_escape_char() {
1073        Some(c) => c.into() as i32,
1074        _ => -1,
1075    }))
1076}
1077pub fn escapechar_set<ET: EngineTypes>(
1078    engine: &mut EngineReferences<ET>,
1079    tk: ET::Token,
1080    globally: bool,
1081) -> TeXResult<(), ET> {
1082    let val: i64 = engine.read_int(true, &tk)?.into();
1083    let val = match val.cmp(&-1) {
1084        Ordering::Less => return engine.general_error(format!("Illegal escape character: {val}")),
1085        Ordering::Equal => None,
1086        Ordering::Greater => match ET::Char::try_from(val as u64) {
1087            Ok(c) => Some(c),
1088            _ => return engine.general_error(format!("Illegal escape character: {val}")),
1089        },
1090    };
1091    engine.state.set_escape_char(engine.aux, val, globally);
1092    Ok(())
1093}
1094
1095pub fn font_get<ET: EngineTypes>(
1096    engine: &mut EngineReferences<ET>,
1097    _tk: ET::Token,
1098) -> TeXResult<ET::Font, ET> {
1099    Ok(engine.state.get_current_font().clone())
1100}
1101
1102pub fn font_set<ET: EngineTypes>(
1103    engine: &mut EngineReferences<ET>,
1104    tk: ET::Token,
1105    global: bool,
1106) -> TeXResult<(), ET> {
1107    let cs = match engine.read_control_sequence(&tk)? {
1108        CSOrActiveChar::Name(name) => name,
1109        _ => return engine.general_error("control sequence expected after \\font".to_string()),
1110    };
1111    let mut name = engine.aux.memory.get_string();
1112    engine.read_string(true, &mut name, &tk)?;
1113    let mut font = engine
1114        .fontsystem
1115        .new_font(&name, cs.clone(), engine.filesystem);
1116    engine.aux.memory.return_string(name);
1117    match engine.read_keywords(&[b"at", b"scaled"])? {
1118        Some(b"at") => {
1119            let size = engine.read_dim(false, &tk)?;
1120            font.set_at(size);
1121        }
1122        Some(b"scaled") => {
1123            let i = engine.read_int(false, &tk)?.into();
1124            let at = font.get_at();
1125            font.set_at(at.scale_float(i as f64 / 1000.0));
1126        }
1127        _ => (),
1128    }
1129    engine
1130        .state
1131        .set_command(engine.aux, cs, Some(TeXCommand::Font(font)), global);
1132    Ok(())
1133}
1134
1135pub fn textfont_get<ET: EngineTypes>(
1136    engine: &mut EngineReferences<ET>,
1137    tk: ET::Token,
1138) -> TeXResult<ET::Font, ET> {
1139    let num = engine.mathfont_index(false, &tk)?;
1140    Ok(engine.state.get_textfont(num).clone())
1141}
1142
1143pub fn textfont_set<ET: EngineTypes>(
1144    engine: &mut EngineReferences<ET>,
1145    tk: ET::Token,
1146    global: bool,
1147) -> TeXResult<(), ET> {
1148    let num = engine.mathfont_index(false, &tk)?;
1149    let fnt = engine.read_font(true, &tk)?;
1150    engine.state.set_textfont(engine.aux, num, fnt, global);
1151    Ok(())
1152}
1153
1154pub fn scriptfont_get<ET: EngineTypes>(
1155    engine: &mut EngineReferences<ET>,
1156    tk: ET::Token,
1157) -> TeXResult<ET::Font, ET> {
1158    let num = engine.mathfont_index(false, &tk)?;
1159    Ok(engine.state.get_scriptfont(num).clone())
1160}
1161
1162pub fn scriptfont_set<ET: EngineTypes>(
1163    engine: &mut EngineReferences<ET>,
1164    tk: ET::Token,
1165    global: bool,
1166) -> TeXResult<(), ET> {
1167    let num = engine.mathfont_index(false, &tk)?;
1168    let fnt = engine.read_font(true, &tk)?;
1169    engine.state.set_scriptfont(engine.aux, num, fnt, global);
1170    Ok(())
1171}
1172
1173pub fn scriptscriptfont_get<ET: EngineTypes>(
1174    engine: &mut EngineReferences<ET>,
1175    tk: ET::Token,
1176) -> TeXResult<ET::Font, ET> {
1177    let num = engine.mathfont_index(false, &tk)?;
1178    Ok(engine.state.get_scriptscriptfont(num).clone())
1179}
1180
1181pub fn scriptscriptfont_set<ET: EngineTypes>(
1182    engine: &mut EngineReferences<ET>,
1183    tk: ET::Token,
1184    global: bool,
1185) -> TeXResult<(), ET> {
1186    let num = engine.mathfont_index(false, &tk)?;
1187    let fnt = engine.read_font(true, &tk)?;
1188    engine
1189        .state
1190        .set_scriptscriptfont(engine.aux, num, fnt, global);
1191    Ok(())
1192}
1193
1194pub fn fontdimen_get<ET: EngineTypes>(
1195    engine: &mut EngineReferences<ET>,
1196    tk: ET::Token,
1197) -> TeXResult<ET::Dim, ET> {
1198    let idx = match engine.read_int(false, &tk)?.try_into() {
1199        Ok(i) if i > 0 && i - 1 <= u16::MAX.into() => (i - 1) as u16,
1200        _ => {
1201            engine.general_error("Illegal \\fontdimen index".to_string())?;
1202            return Ok(ET::Dim::default());
1203        }
1204    };
1205    let font = engine.read_font(false, &tk)?;
1206    Ok(font.get_dim(idx))
1207}
1208pub fn fontdimen_set<ET: EngineTypes>(
1209    engine: &mut EngineReferences<ET>,
1210    tk: ET::Token,
1211    _globally: bool,
1212) -> TeXResult<(), ET> {
1213    let i = engine.read_int(false, &tk)?;
1214    let idx = match i.try_into() {
1215        Ok(i) if i > 0 && i - 1 <= u16::MAX.into() => (i - 1) as u16,
1216        _ => return engine.general_error("Illegal \\fontdimen index".to_string()),
1217    };
1218    let mut font = engine.read_font(false, &tk)?;
1219    let dim = engine.read_dim(true, &tk)?;
1220    font.set_dim(idx, dim);
1221    Ok(())
1222}
1223
1224pub fn box_<ET: EngineTypes>(
1225    engine: &mut EngineReferences<ET>,
1226    tk: ET::Token,
1227) -> TeXResult<Either<Option<TeXBox<ET>>, BoxInfo<ET>>, ET> {
1228    let idx = engine.read_register_index(false, &tk)?;
1229    Ok(either::Left(engine.state.take_box_register(idx)))
1230}
1231pub fn copy<ET: EngineTypes>(
1232    engine: &mut EngineReferences<ET>,
1233    tk: ET::Token,
1234) -> TeXResult<Either<Option<TeXBox<ET>>, BoxInfo<ET>>, ET> {
1235    let idx = engine.read_register_index(false, &tk)?;
1236    Ok(either::Left(engine.state.get_box_register(idx).cloned()))
1237}
1238
1239pub fn unbox<ET: EngineTypes>(
1240    engine: &mut EngineReferences<ET>,
1241    tk: ET::Token,
1242    tp: BoxType,
1243    copy: bool,
1244) -> TeXResult<(), ET> {
1245    let idx = engine.read_register_index(false, &tk)?;
1246    let bx = if copy {
1247        engine.state.get_box_register(idx).cloned()
1248    } else {
1249        engine.state.take_box_register(idx)
1250    };
1251    match bx {
1252        None => (),
1253        Some(TeXBox::V { children, .. }) if tp == BoxType::Vertical => {
1254            for c in children.into_vec() {
1255                ET::Stomach::add_node_v(engine, c)?
1256            }
1257        }
1258        Some(TeXBox::H { children, .. }) if tp == BoxType::Horizontal => {
1259            match engine.stomach.data_mut().open_lists.last_mut() {
1260                Some(NodeList::Horizontal { children: ls, .. }) => ls.extend(children.into_vec()),
1261                _ => {
1262                    return engine.general_error(
1263                        "Cannot unbox \\hbox outside of horizontal mode".to_string(),
1264                    )
1265                }
1266            }
1267        }
1268        _ => return engine.general_error(format!("Cannot unbox box {idx} in {tp} mode")),
1269    }
1270    Ok(())
1271}
1272
1273pub fn unhbox<ET: EngineTypes>(
1274    engine: &mut EngineReferences<ET>,
1275    tk: ET::Token,
1276) -> TeXResult<(), ET> {
1277    unbox(engine, tk, BoxType::Horizontal, false)
1278}
1279pub fn unhcopy<ET: EngineTypes>(
1280    engine: &mut EngineReferences<ET>,
1281    tk: ET::Token,
1282) -> TeXResult<(), ET> {
1283    unbox(engine, tk, BoxType::Horizontal, true)
1284}
1285pub fn unvbox<ET: EngineTypes>(
1286    engine: &mut EngineReferences<ET>,
1287    tk: ET::Token,
1288) -> TeXResult<(), ET> {
1289    unbox(engine, tk, BoxType::Vertical, false)
1290}
1291pub fn unvcopy<ET: EngineTypes>(
1292    engine: &mut EngineReferences<ET>,
1293    tk: ET::Token,
1294) -> TeXResult<(), ET> {
1295    unbox(engine, tk, BoxType::Vertical, true)
1296}
1297
1298pub fn hbox<ET: EngineTypes>(
1299    engine: &mut EngineReferences<ET>,
1300    tk: ET::Token,
1301) -> TeXResult<Either<Option<TeXBox<ET>>, BoxInfo<ET>>, ET> {
1302    let scaled = super::methods::do_box_start(engine, GroupType::HBox, PRIMITIVES.everyhbox, &tk)?;
1303    Ok(either::Right(BoxInfo::H(HBoxInfo::new_box(scaled))))
1304}
1305
1306pub fn vbox<ET: EngineTypes>(
1307    engine: &mut EngineReferences<ET>,
1308    tk: ET::Token,
1309) -> TeXResult<Either<Option<TeXBox<ET>>, BoxInfo<ET>>, ET> {
1310    let scaled = super::methods::do_box_start(engine, GroupType::VBox, PRIMITIVES.everyvbox, &tk)?;
1311    Ok(either::Right(BoxInfo::V(VBoxInfo::new_box(scaled))))
1312}
1313
1314pub fn vtop<ET: EngineTypes>(
1315    engine: &mut EngineReferences<ET>,
1316    tk: ET::Token,
1317) -> TeXResult<Either<Option<TeXBox<ET>>, BoxInfo<ET>>, ET> {
1318    let scaled = super::methods::do_box_start(engine, GroupType::VTop, PRIMITIVES.everyvbox, &tk)?;
1319    Ok(either::Right(BoxInfo::V(VBoxInfo::new_top(scaled))))
1320}
1321
1322pub fn vcenter<ET: EngineTypes>(
1323    engine: &mut EngineReferences<ET>,
1324    tk: ET::Token,
1325) -> TeXResult<(), ET> {
1326    let scaled =
1327        super::methods::do_box_start(engine, GroupType::VCenter, PRIMITIVES.everyvbox, &tk)?;
1328    engine
1329        .stomach
1330        .data_mut()
1331        .open_lists
1332        .push(NodeList::Vertical {
1333            children: Vec::new(),
1334            tp: VerticalNodeListType::VCenter(engine.mouth.start_ref(), scaled),
1335        });
1336    Ok(())
1337}
1338
1339pub fn halign<ET: EngineTypes>(
1340    engine: &mut EngineReferences<ET>,
1341    tk: ET::Token,
1342) -> TeXResult<(), ET> {
1343    match engine.stomach.data_mut().mode() {
1344        TeXMode::Horizontal => {
1345            engine.requeue(tk)?;
1346            return ET::Stomach::close_paragraph(engine);
1347        }
1348        TeXMode::DisplayMath | TeXMode::Vertical | TeXMode::InternalVertical => (),
1349        _ => return engine.general_error("\\halign not allowed in math mode".to_string()),
1350    }
1351    let wd = if engine.read_keyword(b"to")? {
1352        Some(engine.read_dim(false, &tk)?)
1353    } else {
1354        None
1355    };
1356    super::methods::do_align(engine, BoxType::Horizontal, BoxType::Vertical, wd, &tk)
1357}
1358
1359pub fn valign<ET: EngineTypes>(
1360    engine: &mut EngineReferences<ET>,
1361    tk: ET::Token,
1362) -> TeXResult<(), ET> {
1363    let wd = if engine.read_keyword(b"to")? {
1364        Some(engine.read_dim(false, &tk)?)
1365    } else {
1366        None
1367    };
1368    super::methods::do_align(engine, BoxType::Vertical, BoxType::Horizontal, wd, &tk)
1369}
1370
1371pub fn hyphenchar_get<ET: EngineTypes>(
1372    engine: &mut EngineReferences<ET>,
1373    tk: ET::Token,
1374) -> TeXResult<ET::Int, ET> {
1375    let font = engine.read_font(false, &tk)?;
1376    Ok(font.get_hyphenchar())
1377}
1378pub fn hyphenchar_set<ET: EngineTypes>(
1379    engine: &mut EngineReferences<ET>,
1380    tk: ET::Token,
1381    _globally: bool,
1382) -> TeXResult<(), ET> {
1383    let mut font = engine.read_font(false, &tk)?;
1384    let val = engine.read_int(true, &tk)?;
1385    font.set_hyphenchar(val);
1386    Ok(())
1387}
1388
1389pub fn skewchar_get<ET: EngineTypes>(
1390    engine: &mut EngineReferences<ET>,
1391    tk: ET::Token,
1392) -> TeXResult<ET::Int, ET> {
1393    let font = engine.read_font(false, &tk)?;
1394    Ok(font.get_skewchar())
1395}
1396pub fn skewchar_set<ET: EngineTypes>(
1397    engine: &mut EngineReferences<ET>,
1398    tk: ET::Token,
1399    _globally: bool,
1400) -> TeXResult<(), ET> {
1401    let mut font = engine.read_font(false, &tk)?;
1402    let val = engine.read_int(true, &tk)?;
1403    font.set_skewchar(val);
1404    Ok(())
1405}
1406
1407pub fn r#if<ET: EngineTypes>(
1408    engine: &mut EngineReferences<ET>,
1409    tk: ET::Token,
1410) -> TeXResult<bool, ET> {
1411    let first = super::methods::get_if_token(engine, &tk)?;
1412    let second = super::methods::get_if_token(engine, &tk)?;
1413    Ok(first.0 == second.0)
1414}
1415
1416pub fn ifcase<ET: EngineTypes>(
1417    engine: &mut EngineReferences<ET>,
1418    tk: ET::Token,
1419) -> TeXResult<bool, ET> {
1420    let index = engine.gullet.get_conditionals().len() - 1;
1421    let num = engine.read_int(false, &tk)?;
1422    *engine.gullet.get_conditionals().get_mut(index).unwrap() = ActiveConditional::Case(num);
1423    Ok(num == <ET::Num as NumSet>::Int::default())
1424}
1425
1426pub fn ifcat<ET: EngineTypes>(
1427    engine: &mut EngineReferences<ET>,
1428    tk: ET::Token,
1429) -> TeXResult<bool, ET> {
1430    let first = super::methods::get_if_token(engine, &tk)?;
1431    let second = super::methods::get_if_token(engine, &tk)?;
1432    Ok(first.1 == second.1)
1433}
1434
1435pub fn ifdim<ET: EngineTypes>(
1436    engine: &mut EngineReferences<ET>,
1437    tk: ET::Token,
1438) -> TeXResult<bool, ET> {
1439    let first = engine.read_dim(false, &tk)?;
1440    let rel = match engine.read_chars(b"=<>")? {
1441        either::Left(b) => b,
1442        _ => {
1443            TeXError::missing_keyword(engine.aux, engine.state, engine.mouth, &["=", "<", ">"])?;
1444            b'='
1445        }
1446    };
1447    let second = engine.read_dim(false, &tk)?;
1448    Ok(match rel {
1449        b'=' => first == second,
1450        b'<' => first < second,
1451        b'>' => first > second,
1452        _ => unreachable!(),
1453    })
1454}
1455
1456pub fn ifeof<ET: EngineTypes>(
1457    engine: &mut EngineReferences<ET>,
1458    tk: ET::Token,
1459) -> TeXResult<bool, ET> {
1460    let idx = engine.read_file_index(&tk)?;
1461    Ok(engine.filesystem.eof(idx))
1462}
1463
1464pub fn ifhmode<ET: EngineTypes>(
1465    engine: &mut EngineReferences<ET>,
1466    _tk: ET::Token,
1467) -> TeXResult<bool, ET> {
1468    Ok(matches!(
1469        engine.stomach.data_mut().mode(),
1470        TeXMode::Horizontal | TeXMode::RestrictedHorizontal
1471    ))
1472}
1473pub fn ifinner<ET: EngineTypes>(
1474    engine: &mut EngineReferences<ET>,
1475    _tk: ET::Token,
1476) -> TeXResult<bool, ET> {
1477    Ok(matches!(
1478        engine.stomach.data_mut().mode(),
1479        TeXMode::RestrictedHorizontal | TeXMode::InternalVertical | TeXMode::InlineMath
1480    ))
1481}
1482pub fn ifmmode<ET: EngineTypes>(
1483    engine: &mut EngineReferences<ET>,
1484    _tk: ET::Token,
1485) -> TeXResult<bool, ET> {
1486    Ok(matches!(
1487        engine.stomach.data_mut().mode(),
1488        TeXMode::InlineMath | TeXMode::DisplayMath
1489    ))
1490}
1491
1492pub fn ifnum<ET: EngineTypes>(
1493    engine: &mut EngineReferences<ET>,
1494    tk: ET::Token,
1495) -> TeXResult<bool, ET> {
1496    let first = engine.read_int(false, &tk)?;
1497    let rel = match engine.read_chars(b"=<>")? {
1498        either::Left(b) => b,
1499        _ => {
1500            TeXError::missing_keyword(engine.aux, engine.state, engine.mouth, &["=", "<", ">"])?;
1501            b'='
1502        }
1503    };
1504    let second = engine.read_int(false, &tk)?;
1505    Ok(match rel {
1506        b'=' => first == second,
1507        b'<' => first < second,
1508        b'>' => first > second,
1509        _ => unreachable!(),
1510    })
1511}
1512
1513pub fn ifodd<ET: EngineTypes>(
1514    engine: &mut EngineReferences<ET>,
1515    tk: ET::Token,
1516) -> TeXResult<bool, ET> {
1517    Ok(engine.read_int(false, &tk)?.into() % 2 != 0)
1518}
1519
1520pub fn iftrue<ET: EngineTypes>(
1521    _engine: &mut EngineReferences<ET>,
1522    _tk: ET::Token,
1523) -> TeXResult<bool, ET> {
1524    Ok(true)
1525}
1526
1527pub fn iffalse<ET: EngineTypes>(
1528    _engine: &mut EngineReferences<ET>,
1529    _tk: ET::Token,
1530) -> TeXResult<bool, ET> {
1531    Ok(false)
1532}
1533
1534pub fn ifvmode<ET: EngineTypes>(
1535    engine: &mut EngineReferences<ET>,
1536    _tk: ET::Token,
1537) -> TeXResult<bool, ET> {
1538    Ok(matches!(
1539        engine.stomach.data_mut().mode(),
1540        TeXMode::Vertical | TeXMode::InternalVertical
1541    ))
1542}
1543pub fn ifvbox<ET: EngineTypes>(
1544    engine: &mut EngineReferences<ET>,
1545    tk: ET::Token,
1546) -> TeXResult<bool, ET> {
1547    let idx = engine.read_register_index(false, &tk)?;
1548    Ok(matches!(
1549        engine.state.get_box_register(idx),
1550        Some(TeXBox::V { .. })
1551    ))
1552}
1553pub fn ifvoid<ET: EngineTypes>(
1554    engine: &mut EngineReferences<ET>,
1555    tk: ET::Token,
1556) -> TeXResult<bool, ET> {
1557    let idx = engine.read_register_index(false, &tk)?;
1558    Ok(engine.state.get_box_register(idx).is_none())
1559}
1560pub fn ifhbox<ET: EngineTypes>(
1561    engine: &mut EngineReferences<ET>,
1562    tk: ET::Token,
1563) -> TeXResult<bool, ET> {
1564    let idx = engine.read_register_index(false, &tk)?;
1565    Ok(matches!(
1566        engine.state.get_box_register(idx),
1567        Some(TeXBox::H { .. })
1568    ))
1569}
1570
1571pub fn ifx<ET: EngineTypes>(
1572    engine: &mut EngineReferences<ET>,
1573    tk: ET::Token,
1574) -> TeXResult<bool, ET> {
1575    let first = IfxCmd::read(engine, &tk)?;
1576    let second = IfxCmd::read(engine, &tk)?;
1577    Ok(first == second)
1578}
1579
1580pub fn ignorespaces<ET: EngineTypes>(
1581    engine: &mut EngineReferences<ET>,
1582    _tk: ET::Token,
1583) -> TeXResult<(), ET> {
1584    while let Some(next) = engine.mouth.get_next(engine.aux, engine.state)? {
1585        if next.command_code() != CommandCode::Space {
1586            match ET::Gullet::char_or_primitive(engine.state, &next) {
1587                Some(CharOrPrimitive::Char(_, Some(CommandCode::Space))) => (),
1588                _ => {
1589                    engine.mouth.requeue(next);
1590                    break;
1591                }
1592            }
1593        }
1594    }
1595    Ok(())
1596}
1597
1598pub fn insert<ET: EngineTypes>(
1599    engine: &mut EngineReferences<ET>,
1600    tk: ET::Token,
1601) -> TeXResult<(), ET> {
1602    let n = engine.read_int(false, &tk)?.into();
1603    if n < 0 {
1604        return engine.general_error(format!("Illegal insert index {n}"));
1605    }
1606    engine.expand_until_bgroup(false, &tk)?;
1607    engine
1608        .state
1609        .push(engine.aux, GroupType::Insert, engine.mouth.line_number());
1610    engine
1611        .stomach
1612        .data_mut()
1613        .open_lists
1614        .push(NodeList::Vertical {
1615            children: vec![],
1616            tp: VerticalNodeListType::Insert(n as usize),
1617        });
1618    Ok(())
1619}
1620
1621pub fn immediate<ET: EngineTypes>(
1622    engine: &mut EngineReferences<ET>,
1623    _tk: ET::Token,
1624) -> TeXResult<(), ET> {
1625    expand_loop!(engine,token,
1626        ResolvedToken::Cmd(Some(TeXCommand::Primitive{cmd:PrimitiveCommand::Whatsit { immediate,.. },..})) => {
1627            return immediate(engine,token)
1628        },
1629        ResolvedToken::Tk {char,code} => return ET::Stomach::do_char(engine,token,char,code),
1630        ResolvedToken::Cmd(Some(TeXCommand::Char {char,code})) => return ET::Stomach::do_char(engine,token,*char,*code),
1631        ResolvedToken::Cmd(None) =>
1632            return TeXError::undefined(engine.aux,engine.state,engine.mouth,&token),
1633        ResolvedToken::Cmd(Some(c)) => {
1634            crate::do_cmd!(engine,token,c);
1635            return Ok(())
1636        }
1637    );
1638    Ok(())
1639}
1640
1641pub fn input<ET: EngineTypes>(
1642    engine: &mut EngineReferences<ET>,
1643    tk: ET::Token,
1644) -> TeXResult<(), ET> {
1645    let mut filename = engine.aux.memory.get_string();
1646    engine.read_string(false, &mut filename, &tk)?;
1647    if filename.is_empty() {
1648        return engine.general_error("empty file name in \\input".to_string());
1649    }
1650    let is_file = !filename.starts_with('|');
1651    let file = engine.filesystem.get(&filename);
1652    engine.aux.memory.return_string(filename);
1653    if is_file && !file.exists() {
1654        return engine.general_error(format!("File does not exist: {}", file.path().display()));
1655    }
1656    engine.aux.outputs.file_open(&file);
1657    engine.push_file(file);
1658    Ok(())
1659}
1660
1661pub fn fi<ET: EngineTypes>(engine: &mut EngineReferences<ET>, tk: ET::Token) -> TeXResult<(), ET> {
1662    let conds = engine.gullet.get_conditionals();
1663    let name = match conds.pop() {
1664        Some(ActiveConditional::True(id) | ActiveConditional::Else(id)) => id,
1665        Some(ActiveConditional::Case(_)) => PRIMITIVES.ifcase,
1666        Some(u @ ActiveConditional::Unfinished(_)) => {
1667            conds.push(u);
1668            engine.mouth.requeue(tk);
1669            let relax = engine.aux.memory.cs_interner_mut().cs_from_str("relax");
1670            engine.mouth.requeue(ET::Token::from_cs(relax));
1671            return Ok(());
1672        }
1673        _ => return engine.general_error("Unexpected \\fi".to_string()),
1674    };
1675    let trace = engine.state.get_primitive_int(PRIMITIVES.tracingifs) > ET::Int::default();
1676    let index = conds.len() + 1;
1677    if trace {
1678        engine.aux.outputs.write_neg1(format_args!(
1679            "{{{}fi: {} (level {}) entered on line {}}}",
1680            <ET::Char as Character>::display_opt(engine.state.get_escape_char()),
1681            name.display(engine.state.get_escape_char()),
1682            index,
1683            engine.mouth.line_number()
1684        ));
1685    }
1686    Ok(())
1687}
1688
1689pub fn jobname<ET: EngineTypes>(
1690    engine: &mut EngineReferences<ET>,
1691    exp: &mut Vec<ET::Token>,
1692    _tk: ET::Token,
1693) -> TeXResult<(), ET> {
1694    let mut fi = |t| exp.push(t);
1695    let mut f = Otherize::new(&mut fi);
1696    let escape = engine
1697        .aux
1698        .jobname
1699        .as_bytes()
1700        .iter()
1701        .any(|c| c.is_ascii_whitespace());
1702    if escape {
1703        write!(f, "\"{}\"", engine.aux.jobname)?;
1704    } else {
1705        write!(f, "{}", engine.aux.jobname)?;
1706    }
1707    Ok(())
1708}
1709
1710pub fn fontname<ET: EngineTypes>(
1711    engine: &mut EngineReferences<ET>,
1712    exp: &mut Vec<ET::Token>,
1713    tk: ET::Token,
1714) -> TeXResult<(), ET> {
1715    let font = engine.read_font(false, &tk)?;
1716    let mut fi = |t| exp.push(t);
1717    let mut f = Otherize::new(&mut fi);
1718    if font.has_at_set() {
1719        write!(f, "{} at {}", font.filename(), font.get_at())?;
1720    } else {
1721        write!(f, "{}", font.filename())?;
1722    }
1723    Ok(())
1724}
1725
1726pub fn let_<ET: EngineTypes>(
1727    engine: &mut EngineReferences<ET>,
1728    tk: ET::Token,
1729    globally: bool,
1730) -> TeXResult<(), ET> {
1731    let t = engine.need_next(false, &tk)?;
1732    let cm = match t.to_enum() {
1733        StandardToken::Character(c, CommandCode::Active) => CSOrActiveChar::Active(c),
1734        StandardToken::ControlSequence(cs) => CSOrActiveChar::Name(cs),
1735        _ => return engine.general_error("Control sequence expected after \\let".to_string()),
1736    };
1737    let mut after_eq = false;
1738    let mut after_space = false;
1739    loop {
1740        let next = engine.need_next(false, &tk)?;
1741        let cmd = match next.to_enum() {
1742            StandardToken::Character(_, CommandCode::Space) if !after_eq => continue,
1743            StandardToken::Character(_, CommandCode::Space) if !after_space => {
1744                after_space = true;
1745                continue;
1746            }
1747            StandardToken::Character(c, CommandCode::Other) if matches!(c.try_into(), Ok(b'=')) => {
1748                after_eq = true;
1749                continue;
1750            }
1751            StandardToken::ControlSequence(cs) => engine.state.get_command(&cs).cloned(),
1752            StandardToken::Primitive(id) => engine.state.primitives().get_id(id).cloned(),
1753            StandardToken::Character(c, CommandCode::Active) => {
1754                engine.state.get_ac_command(c).cloned()
1755            }
1756            StandardToken::Character(c, cc) => Some(TeXCommand::Char { char: c, code: cc }),
1757        };
1758        engine.set_command(&cm, cmd, globally);
1759        return Ok(());
1760    }
1761}
1762
1763pub fn futurelet<ET: EngineTypes>(
1764    engine: &mut EngineReferences<ET>,
1765    _tk: ET::Token,
1766    globally: bool,
1767) -> TeXResult<(), ET> {
1768    let t = engine.need_next(false, &_tk)?;
1769    let cm = match t.to_enum() {
1770        StandardToken::Character(c, CommandCode::Active) => CSOrActiveChar::Active(c),
1771        StandardToken::ControlSequence(cs) => CSOrActiveChar::Name(cs),
1772        _ => {
1773            return Err(TeXError::General(
1774                "Control sequence expected after \\futurelet".to_string(),
1775            ))
1776        }
1777    };
1778    let first = match engine.mouth.get_next(engine.aux, engine.state)? {
1779        Some(t) => t,
1780        _ => {
1781            return Err(TeXError::FileEndedWhileScanningUseOf(
1782                "futurelet".to_string(),
1783            ))
1784        }
1785    };
1786    let second = match engine.mouth.get_next(engine.aux, engine.state)? {
1787        Some(t) => t,
1788        _ => {
1789            return Err(TeXError::FileEndedWhileScanningUseOf(
1790                "futurelet".to_string(),
1791            ))
1792        }
1793    };
1794    let cmd = match second.to_enum() {
1795        StandardToken::ControlSequence(cs) => engine.state.get_command(&cs).cloned(),
1796        StandardToken::Character(c, CommandCode::Active) => engine.state.get_ac_command(c).cloned(),
1797        StandardToken::Character(c, cc) => Some(TeXCommand::Char { char: c, code: cc }),
1798        StandardToken::Primitive(id) => engine.state.primitives().get_id(id).cloned(),
1799    };
1800    engine.set_command(&cm, cmd, globally);
1801    engine.mouth.requeue(second);
1802    engine.mouth.requeue(first);
1803    Ok(())
1804}
1805
1806pub fn lowercase<ET: EngineTypes>(
1807    engine: &mut EngineReferences<ET>,
1808    tk: ET::Token,
1809) -> TeXResult<(), ET> {
1810    engine.expand_until_bgroup(false, &tk)?;
1811    let mut exp = Vec::new(); // ET::Gullet::get_expansion_container(engine);
1812    engine.read_until_endgroup(&tk, |_, state, t| match t.to_enum() {
1813        StandardToken::Character(c, cc) => {
1814            let lccode = state.get_lccode(c);
1815            if lccode == ET::Char::default() {
1816                exp.push(t)
1817            } else {
1818                exp.push(ET::Token::from_char_cat(lccode, cc))
1819            }
1820            Ok(())
1821        }
1822        _ => {
1823            exp.push(t);
1824            Ok(())
1825        }
1826    })?;
1827    engine.mouth.push_vec(exp);
1828    Ok(())
1829}
1830
1831pub fn uppercase<ET: EngineTypes>(
1832    engine: &mut EngineReferences<ET>,
1833    tk: ET::Token,
1834) -> TeXResult<(), ET> {
1835    engine.expand_until_bgroup(false, &tk)?;
1836    let mut exp = Vec::new(); //ET::Gullet::get_expansion_container(engine);
1837    engine.read_until_endgroup(&tk, |_, state, t| match t.to_enum() {
1838        StandardToken::Character(c, cc) => {
1839            let uccode = state.get_uccode(c);
1840            if uccode == ET::Char::default() {
1841                exp.push(t)
1842            } else {
1843                exp.push(ET::Token::from_char_cat(uccode, cc))
1844            }
1845            Ok(())
1846        }
1847        _ => {
1848            exp.push(t);
1849            Ok(())
1850        }
1851    })?;
1852    engine.mouth.push_vec(exp);
1853    Ok(())
1854}
1855
1856pub fn mathchardef<ET: EngineTypes>(
1857    engine: &mut EngineReferences<ET>,
1858    tk: ET::Token,
1859    globally: bool,
1860) -> TeXResult<(), ET> {
1861    let t = engine.need_next(false, &tk)?;
1862    let cm = match t.to_enum() {
1863        StandardToken::Character(c, CommandCode::Active) => CSOrActiveChar::Active(c),
1864        StandardToken::ControlSequence(cs) => CSOrActiveChar::Name(cs),
1865        _ => {
1866            return Err(TeXError::General(
1867                "Expected control sequence after \\mathchardef".to_string(),
1868            ))
1869        }
1870    };
1871    let i = engine.read_int(true, &tk)?.into();
1872    if i < 0 || i > u32::MAX as i64 {
1873        return engine.general_error(format!("Illegal math char: {i}"));
1874    }
1875    let i = i as u32;
1876    engine.set_command(&cm, Some(TeXCommand::MathChar(i)), globally);
1877    Ok(())
1878}
1879
1880pub fn mathchar<ET: EngineTypes>(
1881    engine: &mut EngineReferences<ET>,
1882    tk: ET::Token,
1883) -> TeXResult<(), ET> {
1884    let i = engine.read_int(false, &tk)?.into();
1885    if i < 0 || i > 32767 as i64 {
1886        return engine.general_error(format!("Illegal math char: {i}"));
1887    }
1888    let i = i as u32;
1889    let ret = MathChar::from_u32(i, engine.state, None);
1890    ET::Stomach::add_node_m(engine, MathNode::Atom(ret.to_atom()));
1891    Ok(())
1892}
1893
1894pub fn left<ET: EngineTypes>(
1895    engine: &mut EngineReferences<ET>,
1896    tk: ET::Token,
1897) -> TeXResult<(), ET> {
1898    let del = engine.read_opt_delimiter(&tk)?;
1899    engine.stomach.data_mut().open_lists.push(NodeList::Math {
1900        children: MathNodeList::default(),
1901        start: engine.mouth.start_ref(),
1902        tp: MathNodeListType::LeftRight(del),
1903    });
1904    engine
1905        .state
1906        .push(engine.aux, GroupType::LeftRight, engine.mouth.line_number());
1907    Ok(())
1908}
1909pub fn right<ET: EngineTypes>(
1910    engine: &mut EngineReferences<ET>,
1911    tk: ET::Token,
1912) -> TeXResult<(), ET> {
1913    if engine.state.get_group_type() != Some(GroupType::LeftRight) {
1914        return Err(TeXError::General(
1915            "\\right outside of \\left\\right group".to_string(),
1916        ));
1917    }
1918    let del = engine.read_opt_delimiter(&tk)?;
1919    match engine.stomach.data_mut().open_lists.pop() {
1920        Some(NodeList::Math {
1921            children,
1922            start,
1923            tp: MathNodeListType::LeftRight(left),
1924        }) => {
1925            engine.state.pop(engine.aux, engine.mouth);
1926            let (children, None) = children.close(start, engine.mouth.current_sourceref()) else {
1927                unreachable!()
1928            };
1929            ET::Stomach::add_node_m(
1930                engine,
1931                MathNode::Atom(MathAtom {
1932                    nucleus: MathNucleus::LeftRight {
1933                        left: left.map(|d| (d.large.char, d.large.style)),
1934                        right: del.map(|d| (d.large.char, d.large.style)),
1935                        start,
1936                        end: engine.mouth.current_sourceref(),
1937                        children: children.into(),
1938                    },
1939                    sub: None,
1940                    sup: None,
1941                }),
1942            );
1943        }
1944        _ => {
1945            return Err(TeXError::General(
1946                "Unexpected open list in \\right".to_string(),
1947            ))
1948        }
1949    }
1950    Ok(())
1951}
1952
1953pub fn meaning<ET: EngineTypes>(
1954    engine: &mut EngineReferences<ET>,
1955    exp: &mut Vec<ET::Token>,
1956    tk: ET::Token,
1957) -> TeXResult<(), ET> {
1958    let mut fi = |t| exp.push(t);
1959    let mut f = Otherize::new(&mut fi);
1960    let t = engine.need_next(false, &tk)?;
1961    match engine.resolve(&t) {
1962        ResolvedToken::Cmd(None) => {
1963            if let Some(c) = engine.state.get_escape_char() {
1964                f.push_char(c);
1965            }
1966            write!(f, "undefined")?;
1967        }
1968        ResolvedToken::Cmd(Some(cmd)) => cmd
1969            .meaning(
1970                engine.aux.memory.cs_interner(),
1971                engine.state.get_catcode_scheme(),
1972                engine.state.get_escape_char(),
1973            )
1974            .write_chars(&mut f)?,
1975        ResolvedToken::Tk { char, code, .. } => code.meaning(char, f)?,
1976    }
1977    Ok(())
1978}
1979
1980pub fn leaders<ET: EngineTypes>(
1981    engine: &mut EngineReferences<ET>,
1982    tk: ET::Token,
1983) -> TeXResult<(), ET> {
1984    super::methods::do_leaders(engine, LeaderType::Normal, &tk)
1985}
1986pub fn xleaders<ET: EngineTypes>(
1987    engine: &mut EngineReferences<ET>,
1988    tk: ET::Token,
1989) -> TeXResult<(), ET> {
1990    super::methods::do_leaders(engine, LeaderType::X, &tk)
1991}
1992pub fn cleaders<ET: EngineTypes>(
1993    engine: &mut EngineReferences<ET>,
1994    tk: ET::Token,
1995) -> TeXResult<(), ET> {
1996    super::methods::do_leaders(engine, LeaderType::C, &tk)
1997}
1998
1999pub fn message<ET: EngineTypes>(
2000    engine: &mut EngineReferences<ET>,
2001    tk: ET::Token,
2002) -> TeXResult<(), ET> {
2003    let mut out = engine.aux.memory.get_string();
2004    engine.read_braced_string(false, true, &tk, &mut out)?;
2005    engine.aux.outputs.message(&out);
2006    engine.aux.memory.return_string(out);
2007    Ok(())
2008}
2009
2010pub fn errmessage<ET: EngineTypes>(
2011    engine: &mut EngineReferences<ET>,
2012    tk: ET::Token,
2013) -> TeXResult<(), ET> {
2014    let mut out = String::new();
2015    engine.read_braced_string(false, true, &tk, &mut out)?;
2016    write!(out, " (line {})", engine.mouth.line_number()).unwrap();
2017    engine.general_error(out)
2018}
2019
2020pub fn newlinechar_get<ET: EngineTypes>(
2021    engine: &mut EngineReferences<ET>,
2022    _tk: ET::Token,
2023) -> TeXResult<ET::Int, ET> {
2024    Ok(ET::Int::from(match engine.state.get_newline_char() {
2025        Some(c) => c.into() as i32,
2026        _ => -1,
2027    }))
2028}
2029pub fn newlinechar_set<ET: EngineTypes>(
2030    engine: &mut EngineReferences<ET>,
2031    tk: ET::Token,
2032    globally: bool,
2033) -> TeXResult<(), ET> {
2034    let val: i64 = engine.read_int(true, &tk)?.into();
2035    let val = match val.cmp(&-1) {
2036        Ordering::Less => return engine.general_error(format!("Illegal newline character: {val}")),
2037        Ordering::Equal => None,
2038        Ordering::Greater => match ET::Char::try_from(val as u64) {
2039            Ok(c) => Some(c),
2040            _ => return engine.general_error(format!("Illegal newline character: {val}")),
2041        },
2042    };
2043    engine.state.set_newline_char(engine.aux, val, globally);
2044    Ok(())
2045}
2046
2047pub fn number<ET: EngineTypes>(
2048    engine: &mut EngineReferences<ET>,
2049    exp: &mut Vec<ET::Token>,
2050    tk: ET::Token,
2051) -> TeXResult<(), ET> {
2052    let val = engine.read_int(false, &tk)?;
2053    write!(Otherize::new(&mut |t| exp.push(t)), "{}", val)?;
2054    Ok(())
2055}
2056
2057pub fn noexpand<ET: EngineTypes>(
2058    engine: &mut EngineReferences<ET>,
2059    _tk: ET::Token,
2060) -> TeXResult<(), ET> {
2061    let token = match engine.mouth.get_next(engine.aux, engine.state)? {
2062        Some(t) if t == ET::Token::eof() => return Ok(()),
2063        Some(t) => t,
2064        _ => {
2065            return Err(TeXError::General(
2066                "control sequence expected after \\noexpand".to_string(),
2067            ))
2068        }
2069    };
2070    match engine.resolve(&token) {
2071        ResolvedToken::Tk {
2072            code: CommandCode::AlignmentTab,
2073            ..
2074        } => {
2075            engine.mouth.requeue(token);
2076            engine
2077                .mouth
2078                .requeue(ET::Token::primitive(PRIMITIVES.noexpand));
2079        }
2080        ResolvedToken::Tk { .. } => engine.mouth.requeue(token),
2081        ResolvedToken::Cmd(Some(cm)) => {
2082            match cm {
2083                TeXCommand::Macro(_)
2084                | TeXCommand::Primitive {
2085                    cmd:
2086                        PrimitiveCommand::Expandable(_)
2087                        | PrimitiveCommand::SimpleExpandable(_)
2088                        | PrimitiveCommand::Conditional(_),
2089                    ..
2090                } => {
2091                    engine.mouth.requeue(token);
2092                    engine
2093                        .mouth
2094                        .requeue(ET::Token::primitive(PRIMITIVES.noexpand));
2095                }
2096                TeXCommand::Primitive { name, .. }
2097                    if *name == PRIMITIVES.cr || *name == PRIMITIVES.crcr =>
2098                {
2099                    engine.mouth.requeue(token);
2100                    engine
2101                        .mouth
2102                        .requeue(ET::Token::primitive(PRIMITIVES.noexpand));
2103                }
2104                _ => engine.mouth.requeue(token),
2105            };
2106        }
2107        ResolvedToken::Cmd(_) => engine.mouth.requeue(token),
2108    }
2109    Ok(())
2110}
2111
2112pub fn noindent<ET: EngineTypes>(
2113    engine: &mut EngineReferences<ET>,
2114    tk: ET::Token,
2115) -> TeXResult<(), ET> {
2116    if ET::Stomach::maybe_switch_mode(
2117        engine,
2118        CommandScope::SwitchesToHorizontal,
2119        tk,
2120        PRIMITIVES.noindent,
2121    )? {
2122        match engine.stomach.data_mut().open_lists.last_mut() {
2123            Some(NodeList::Horizontal { children, .. }) => {
2124                if let Some(HNode::Box(TeXBox::H {
2125                    info: HBoxInfo::ParIndent { .. },
2126                    ..
2127                })) = children.last_mut()
2128                {
2129                    children.pop();
2130                }
2131            }
2132            _ => unreachable!(),
2133        }
2134    }
2135    Ok(())
2136}
2137
2138pub fn openout<ET: EngineTypes>(
2139    engine: &mut EngineReferences<ET>,
2140    tk: ET::Token,
2141) -> TeXResult<Option<Box<WhatsitFunction<ET>>>, ET> {
2142    let (idx, file) = engine.read_filename_and_index("./", &tk)?;
2143    Ok(Some(Box::new(move |engine| {
2144        engine.filesystem.open_out(idx, file);
2145        Ok(())
2146    })))
2147}
2148pub fn openout_immediate<ET: EngineTypes>(
2149    engine: &mut EngineReferences<ET>,
2150    tk: ET::Token,
2151) -> TeXResult<(), ET> {
2152    let (idx, file) = engine.read_filename_and_index("./", &tk)?;
2153    engine.filesystem.open_out(idx, file);
2154    Ok(())
2155}
2156
2157pub fn prevdepth_get<ET: EngineTypes>(
2158    engine: &mut EngineReferences<ET>,
2159    _tk: ET::Token,
2160) -> TeXResult<ET::Dim, ET> {
2161    Ok(engine.stomach.data_mut().prevdepth)
2162}
2163pub fn prevdepth_set<ET: EngineTypes>(
2164    engine: &mut EngineReferences<ET>,
2165    tk: ET::Token,
2166    _globally: bool,
2167) -> TeXResult<(), ET> {
2168    let val = engine.read_dim(true, &tk)?;
2169    engine.stomach.data_mut().prevdepth = val;
2170    Ok(())
2171}
2172
2173pub fn read<ET: EngineTypes>(
2174    engine: &mut EngineReferences<ET>,
2175    tk: ET::Token,
2176    globally: bool,
2177) -> TeXResult<(), ET> {
2178    let idx = engine.read_file_index(&tk)?;
2179    if !engine.read_keyword("to".as_bytes())? {
2180        TeXError::missing_keyword(engine.aux, engine.state, engine.mouth, &["to"])?;
2181    }
2182    let cs = engine.read_control_sequence(&tk)?;
2183    let mut ret = shared_vector::Vector::new();
2184    engine.filesystem.read::<ET, _>(
2185        idx,
2186        engine.aux.memory.cs_interner_mut(),
2187        engine.state,
2188        |t| ret.push(t),
2189    )?;
2190
2191    let m = Macro {
2192        long: false,
2193        outer: false,
2194        protected: false,
2195        expansion: ret.into(),
2196        signature: MacroSignature {
2197            arity: 0,
2198            params: engine.aux.memory.empty_list(),
2199        },
2200    };
2201    engine.set_command(&cs, Some(TeXCommand::Macro(m)), globally);
2202    Ok(())
2203}
2204
2205const ROMAN: &[(u8, i64)] = &[
2206    (b'm', 0),
2207    (b'd', 2),
2208    (b'c', 5),
2209    (b'l', 2),
2210    (b'x', 5),
2211    (b'v', 2),
2212    (b'i', 5),
2213];
2214#[allow(clippy::many_single_char_names)]
2215pub fn romannumeral<ET: EngineTypes>(
2216    engine: &mut EngineReferences<ET>,
2217    exp: &mut Vec<ET::Token>,
2218    tk: ET::Token,
2219) -> TeXResult<(), ET> {
2220    let mut n = engine.read_int(false, &tk)?.into();
2221    if n < 0 {
2222        return Ok(());
2223    }
2224    let mut v = 1000;
2225    let mut j = 0;
2226    loop {
2227        while n >= v {
2228            exp.push(ET::Token::from_char_cat(
2229                ET::Char::from(ROMAN[j].0),
2230                CommandCode::Other,
2231            ));
2232            n -= v;
2233        }
2234        if n <= 0 {
2235            return Ok(());
2236        }
2237        let mut k = ROMAN[j + 1];
2238        let mut u = v / k.1;
2239        if k.1 == 2 {
2240            k = ROMAN[j + 2];
2241            u /= k.1;
2242        }
2243        if n + u >= v {
2244            exp.push(ET::Token::from_char_cat(
2245                ET::Char::from(k.0),
2246                CommandCode::Other,
2247            ));
2248            n += u;
2249        } else {
2250            j += 1;
2251            v /= ROMAN[j].1;
2252        }
2253    }
2254}
2255
2256pub fn setbox<ET: EngineTypes>(
2257    engine: &mut EngineReferences<ET>,
2258    tk: ET::Token,
2259    globally: bool,
2260) -> TeXResult<(), ET> {
2261    let index = engine.read_register_index(false, &tk)?;
2262    match engine.read_box(true)? {
2263        either::Left(bx) => engine
2264            .state
2265            .set_box_register(engine.aux, index, bx, globally),
2266        either::Right(bi) => {
2267            let target = BoxTarget::<ET>::new(move |e, b| {
2268                e.state.set_box_register(e.aux, index, Some(b), globally);
2269                Ok(())
2270            });
2271            let mut ls = bi.open_list(engine.mouth.start_ref());
2272            match ls {
2273                NodeList::Horizontal {
2274                    tp: HorizontalNodeListType::Box(_, _, ref mut t),
2275                    ..
2276                } => *t = target,
2277                NodeList::Vertical {
2278                    tp: VerticalNodeListType::Box(_, _, ref mut t),
2279                    ..
2280                } => *t = target,
2281                _ => unreachable!(),
2282            }
2283            engine.stomach.data_mut().open_lists.push(ls);
2284        }
2285    }
2286    Ok(())
2287}
2288
2289pub fn moveright<ET: EngineTypes>(
2290    engine: &mut EngineReferences<ET>,
2291    tk: ET::Token,
2292) -> TeXResult<(), ET> {
2293    let dim = engine.read_dim(false, &tk)?;
2294    match engine.read_box(false)? {
2295        either::Left(Some(mut bx)) => {
2296            match bx {
2297                TeXBox::H { ref mut info, .. } => info.move_left(-dim),
2298                TeXBox::V { ref mut info, .. } => info.move_left(-dim),
2299            }
2300            ET::Stomach::add_node_v(engine, VNode::Box(bx))?;
2301        }
2302        either::Left(None) => (),
2303        either::Right(mut bi) => {
2304            bi.move_left(-dim);
2305            engine
2306                .stomach
2307                .data_mut()
2308                .open_lists
2309                .push(bi.open_list(engine.mouth.start_ref()));
2310        }
2311    }
2312    Ok(())
2313}
2314
2315pub fn moveleft<ET: EngineTypes>(
2316    engine: &mut EngineReferences<ET>,
2317    tk: ET::Token,
2318) -> TeXResult<(), ET> {
2319    let dim = engine.read_dim(false, &tk)?;
2320    match engine.read_box(false)? {
2321        either::Left(Some(mut bx)) => {
2322            match bx {
2323                TeXBox::H { ref mut info, .. } => info.move_left(dim),
2324                TeXBox::V { ref mut info, .. } => info.move_left(dim),
2325            }
2326            ET::Stomach::add_node_v(engine, VNode::Box(bx))?;
2327        }
2328        either::Left(None) => (),
2329        either::Right(mut bi) => {
2330            bi.move_left(dim);
2331            engine
2332                .stomach
2333                .data_mut()
2334                .open_lists
2335                .push(bi.open_list(engine.mouth.start_ref()));
2336        }
2337    }
2338    Ok(())
2339}
2340
2341pub fn raise<ET: EngineTypes>(
2342    engine: &mut EngineReferences<ET>,
2343    tk: ET::Token,
2344) -> TeXResult<(), ET> {
2345    let dim = engine.read_dim(false, &tk)?;
2346    match engine.read_box(false)? {
2347        either::Left(Some(mut bx)) => {
2348            match bx {
2349                TeXBox::H { ref mut info, .. } => info.raise(dim),
2350                TeXBox::V { ref mut info, .. } => info.raise(dim),
2351            }
2352            match engine.stomach.data_mut().mode() {
2353                TeXMode::Horizontal | TeXMode::RestrictedHorizontal => {
2354                    ET::Stomach::add_node_h(engine, HNode::Box(bx));
2355                }
2356                TeXMode::InlineMath | TeXMode::DisplayMath => {
2357                    ET::Stomach::add_node_m(engine, bx.to_math());
2358                }
2359                _ => unreachable!(),
2360            }
2361        }
2362        either::Left(None) => (),
2363        either::Right(mut bi) => {
2364            bi.raise(dim);
2365            engine
2366                .stomach
2367                .data_mut()
2368                .open_lists
2369                .push(bi.open_list(engine.mouth.start_ref()));
2370        }
2371    }
2372    Ok(())
2373}
2374
2375pub fn lower<ET: EngineTypes>(
2376    engine: &mut EngineReferences<ET>,
2377    tk: ET::Token,
2378) -> TeXResult<(), ET> {
2379    let dim = engine.read_dim(false, &tk)?;
2380    match engine.read_box(false)? {
2381        either::Left(Some(mut bx)) => {
2382            match bx {
2383                TeXBox::H { ref mut info, .. } => info.raise(-dim),
2384                TeXBox::V { ref mut info, .. } => info.raise(-dim),
2385            }
2386            match engine.stomach.data_mut().mode() {
2387                TeXMode::Horizontal | TeXMode::RestrictedHorizontal => {
2388                    ET::Stomach::add_node_h(engine, HNode::Box(bx));
2389                }
2390                TeXMode::InlineMath | TeXMode::DisplayMath => {
2391                    ET::Stomach::add_node_m(engine, bx.to_math());
2392                }
2393                _ => unreachable!(),
2394            }
2395        }
2396        either::Left(None) => (),
2397        either::Right(mut bi) => {
2398            bi.raise(-dim);
2399            engine
2400                .stomach
2401                .data_mut()
2402                .open_lists
2403                .push(bi.open_list(engine.mouth.start_ref()));
2404        }
2405    }
2406    Ok(())
2407}
2408
2409pub fn string<ET: EngineTypes>(
2410    engine: &mut EngineReferences<ET>,
2411    exp: &mut Vec<ET::Token>,
2412    tk: ET::Token,
2413) -> TeXResult<(), ET> {
2414    let t = engine.need_next(false, &tk)?;
2415    if t.command_code() == CommandCode::Space {
2416        exp.push(t)
2417    } else {
2418        match t.to_enum() {
2419            StandardToken::Character(c, _) => {
2420                exp.push(ET::Token::from_char_cat(c, CommandCode::Other))
2421            }
2422            StandardToken::ControlSequence(cs) => {
2423                let res = engine.aux.memory.cs_interner().resolve(&cs);
2424                if let Some(c) = engine.state.get_escape_char() {
2425                    exp.push(ET::Token::from_char_cat(c, CommandCode::Other))
2426                }
2427                for u in res.iter() {
2428                    match u.try_into() {
2429                        Ok(b' ') => exp.push(ET::Token::space()),
2430                        _ => exp.push(ET::Token::from_char_cat(u, CommandCode::Other)),
2431                    }
2432                }
2433            }
2434            _ => return Err(TeXError::EmergencyStop),
2435        }
2436    }
2437    Ok(())
2438}
2439
2440pub fn openin<ET: EngineTypes>(
2441    engine: &mut EngineReferences<ET>,
2442    tk: ET::Token,
2443) -> TeXResult<(), ET> {
2444    let (idx, file) = engine.read_filename_and_index("", &tk)?;
2445    engine.filesystem.open_in(idx, file);
2446    Ok(())
2447}
2448
2449pub fn closeout<ET: EngineTypes>(
2450    engine: &mut EngineReferences<ET>,
2451    tk: ET::Token,
2452) -> TeXResult<Option<Box<WhatsitFunction<ET>>>, ET> {
2453    let idx = engine.read_file_index(&tk)?;
2454    Ok(Some(Box::new(move |engine| {
2455        engine.filesystem.close_out(idx);
2456        Ok(())
2457    })))
2458}
2459pub fn closeout_immediate<ET: EngineTypes>(
2460    engine: &mut EngineReferences<ET>,
2461    tk: ET::Token,
2462) -> TeXResult<(), ET> {
2463    let idx = engine.read_file_index(&tk)?;
2464    engine.filesystem.close_out(idx);
2465    Ok(())
2466}
2467
2468pub fn closein<ET: EngineTypes>(
2469    engine: &mut EngineReferences<ET>,
2470    tk: ET::Token,
2471) -> TeXResult<(), ET> {
2472    let idx = engine.read_file_index(&tk)?;
2473    engine.filesystem.close_in(idx);
2474    Ok(())
2475}
2476
2477pub fn write<ET: EngineTypes>(
2478    engine: &mut EngineReferences<ET>,
2479    tk: ET::Token,
2480) -> TeXResult<Option<Box<WhatsitFunction<ET>>>, ET> {
2481    let idx = engine.read_int(false, &tk)?.into();
2482    let mut tks = Vec::new();
2483    tks.push(ET::Token::from_char_cat(
2484        b'{'.into(),
2485        CommandCode::BeginGroup,
2486    ));
2487    let t = engine.need_next(false, &tk)?;
2488    if t.command_code() != CommandCode::BeginGroup {
2489        TeXError::missing_begingroup(engine.aux, engine.state, engine.mouth)?;
2490    }
2491    engine.read_until_endgroup(&tk, |_, _, t| {
2492        tks.push(t);
2493        Ok(())
2494    })?;
2495    tks.push(ET::Token::from_char_cat(b'}'.into(), CommandCode::EndGroup));
2496    Ok(Some(Box::new(move |engine| do_write(engine, tk, idx, tks))))
2497}
2498
2499pub fn write_immediate<ET: EngineTypes>(
2500    engine: &mut EngineReferences<ET>,
2501    tk: ET::Token,
2502) -> TeXResult<(), ET> {
2503    let idx = engine.read_int(false, &tk)?.into();
2504    let mut out = engine.aux.memory.get_string();
2505    engine.read_braced_string(false, false, &tk, &mut out)?;
2506    engine
2507        .filesystem
2508        .write(idx, &out, engine.state.get_newline_char(), engine.aux);
2509    engine.aux.memory.return_string(out);
2510    Ok(())
2511}
2512pub fn do_write<ET: EngineTypes>(
2513    engine: &mut EngineReferences<ET>,
2514    tk: ET::Token,
2515    i: i64,
2516    v: Vec<ET::Token>,
2517) -> TeXResult<(), ET> {
2518    engine.mouth.push_vec(v);
2519    let mut out = engine.aux.memory.get_string();
2520    engine.read_braced_string(false, false, &tk, &mut out)?;
2521    engine
2522        .filesystem
2523        .write(i, &out, engine.state.get_newline_char(), engine.aux);
2524    engine.aux.memory.return_string(out);
2525    Ok(())
2526}
2527
2528pub fn par<ET: EngineTypes>(
2529    engine: &mut EngineReferences<ET>,
2530    _tk: ET::Token,
2531) -> TeXResult<(), ET> {
2532    let mode = engine.stomach.data_mut().mode();
2533    if mode.is_vertical() {
2534        return Ok(());
2535    }
2536    if mode == TeXMode::Horizontal {
2537        return ET::Stomach::close_paragraph(engine);
2538    }
2539    Err(TeXError::General(
2540        "\\par not allowed in math mode".to_string(),
2541    ))
2542}
2543
2544pub fn the<ET: EngineTypes>(
2545    engine: &mut EngineReferences<ET>,
2546    exp: &mut Vec<ET::Token>,
2547    _tk: ET::Token,
2548) -> TeXResult<(), ET> {
2549    engine.do_the(|_, _, _, t| {
2550        exp.push(t);
2551        Ok(())
2552    })
2553}
2554
2555pub fn toks<ET: EngineTypes>(
2556    engine: &mut EngineReferences<ET>,
2557    tk: ET::Token,
2558    global: bool,
2559) -> TeXResult<(), ET> {
2560    let idx = engine.read_register_index(false, &tk)?;
2561    ET::Stomach::assign_toks_register(engine, tk, idx, global)
2562}
2563
2564pub fn penalty<ET: EngineTypes>(
2565    engine: &mut EngineReferences<ET>,
2566    tk: ET::Token,
2567) -> TeXResult<(), ET> {
2568    let i = match engine.read_int(false, &tk)?.try_into() {
2569        Ok(i) => i,
2570        Err(_) => return engine.general_error("Number expected after \\penalty".to_string()),
2571    };
2572    crate::add_node!(ET::Stomach;engine, VNode::Penalty(i), HNode::Penalty(i), MathNode::Penalty(i));
2573    Ok(())
2574}
2575
2576pub fn kern<ET: EngineTypes>(
2577    engine: &mut EngineReferences<ET>,
2578    tk: ET::Token,
2579) -> TeXResult<(), ET> {
2580    let dim = engine.read_dim(false, &tk)?;
2581    crate::add_node!(ET::Stomach;engine,VNode::VKern(dim), HNode::HKern(dim), MathNode::HKern(dim));
2582    Ok(())
2583}
2584
2585pub fn vrule<ET: EngineTypes>(
2586    engine: &mut EngineReferences<ET>,
2587    tk: ET::Token,
2588) -> TeXResult<(), ET> {
2589    let start = engine.mouth.start_ref();
2590    let mut width = None;
2591    let mut height = None;
2592    let mut depth = None;
2593    loop {
2594        match engine.read_keywords(&[b"width", b"height", b"depth"])? {
2595            Some(b"width") => {
2596                width = Some(engine.read_dim(false, &tk)?);
2597            }
2598            Some(b"height") => {
2599                height = Some(engine.read_dim(false, &tk)?);
2600            }
2601            Some(b"depth") => {
2602                depth = Some(engine.read_dim(false, &tk)?);
2603            }
2604            _ => break,
2605        }
2606    }
2607    let end = engine.mouth.current_sourceref();
2608    match engine.stomach.data_mut().mode() {
2609        TeXMode::Horizontal | TeXMode::RestrictedHorizontal => ET::Stomach::add_node_h(
2610            engine,
2611            HNode::VRule {
2612                width,
2613                height,
2614                depth,
2615                start,
2616                end,
2617            },
2618        ),
2619        TeXMode::InlineMath | TeXMode::DisplayMath => ET::Stomach::add_node_m(
2620            engine,
2621            MathNode::VRule {
2622                width,
2623                height,
2624                depth,
2625                start,
2626                end,
2627            },
2628        ),
2629        _ => unreachable!(),
2630    }
2631    Ok(())
2632}
2633
2634pub fn hrule<ET: EngineTypes>(
2635    engine: &mut EngineReferences<ET>,
2636    tk: ET::Token,
2637) -> TeXResult<(), ET> {
2638    let start = engine.mouth.start_ref();
2639    let mut width = None;
2640    let mut height = None;
2641    let mut depth = None;
2642    loop {
2643        match engine.read_keywords(&[b"width", b"height", b"depth"])? {
2644            Some(b"width") => {
2645                width = Some(engine.read_dim(false, &tk)?);
2646            }
2647            Some(b"height") => {
2648                height = Some(engine.read_dim(false, &tk)?);
2649            }
2650            Some(b"depth") => {
2651                depth = Some(engine.read_dim(false, &tk)?);
2652            }
2653            _ => break,
2654        }
2655    }
2656    let end = engine.mouth.current_sourceref();
2657    ET::Stomach::add_node_v(
2658        engine,
2659        VNode::HRule {
2660            width,
2661            height,
2662            depth,
2663            start,
2664            end,
2665        },
2666    )
2667}
2668
2669pub fn vskip<ET: EngineTypes>(
2670    engine: &mut EngineReferences<ET>,
2671    tk: ET::Token,
2672) -> TeXResult<(), ET> {
2673    let sk = engine.read_skip(false, &tk)?;
2674    ET::Stomach::add_node_v(engine, VNode::VSkip(sk))
2675}
2676
2677pub fn vfil<ET: EngineTypes>(
2678    engine: &mut EngineReferences<ET>,
2679    _tk: ET::Token,
2680) -> TeXResult<(), ET> {
2681    ET::Stomach::add_node_v(engine, VNode::VFil)
2682}
2683pub fn vfill<ET: EngineTypes>(
2684    engine: &mut EngineReferences<ET>,
2685    _tk: ET::Token,
2686) -> TeXResult<(), ET> {
2687    ET::Stomach::add_node_v(engine, VNode::VFill)
2688}
2689pub fn vfilneg<ET: EngineTypes>(
2690    engine: &mut EngineReferences<ET>,
2691    _tk: ET::Token,
2692) -> TeXResult<(), ET> {
2693    ET::Stomach::add_node_v(engine, VNode::VFilneg)
2694}
2695pub fn vss<ET: EngineTypes>(
2696    engine: &mut EngineReferences<ET>,
2697    _tk: ET::Token,
2698) -> TeXResult<(), ET> {
2699    ET::Stomach::add_node_v(engine, VNode::Vss)
2700}
2701
2702#[allow(unreachable_code)]
2703pub fn hskip<ET: EngineTypes>(
2704    engine: &mut EngineReferences<ET>,
2705    tk: ET::Token,
2706) -> TeXResult<(), ET> {
2707    let sk = engine.read_skip(false, &tk)?;
2708    add_node!(ET::Stomach;engine,unreachable!(),HNode::HSkip(sk),MathNode::HSkip(sk));
2709    Ok(())
2710}
2711#[allow(unreachable_code)]
2712pub fn hfil<ET: EngineTypes>(
2713    engine: &mut EngineReferences<ET>,
2714    _tk: ET::Token,
2715) -> TeXResult<(), ET> {
2716    add_node!(ET::Stomach;engine,unreachable!(),HNode::HFil,MathNode::HFil);
2717    Ok(())
2718}
2719#[allow(unreachable_code)]
2720pub fn hfill<ET: EngineTypes>(
2721    engine: &mut EngineReferences<ET>,
2722    _tk: ET::Token,
2723) -> TeXResult<(), ET> {
2724    add_node!(ET::Stomach;engine,unreachable!(),HNode::HFill,MathNode::HFill);
2725    Ok(())
2726}
2727#[allow(unreachable_code)]
2728pub fn hfilneg<ET: EngineTypes>(
2729    engine: &mut EngineReferences<ET>,
2730    _tk: ET::Token,
2731) -> TeXResult<(), ET> {
2732    add_node!(ET::Stomach;engine,unreachable!(),HNode::HFilneg,MathNode::HFilneg);
2733    Ok(())
2734}
2735#[allow(unreachable_code)]
2736pub fn hss<ET: EngineTypes>(
2737    engine: &mut EngineReferences<ET>,
2738    _tk: ET::Token,
2739) -> TeXResult<(), ET> {
2740    add_node!(ET::Stomach;engine,unreachable!(),HNode::Hss,MathNode::Hss);
2741    Ok(())
2742}
2743
2744pub fn indent<ET: EngineTypes>(
2745    engine: &mut EngineReferences<ET>,
2746    _tk: ET::Token,
2747) -> TeXResult<(), ET> {
2748    let dim = engine.state.get_primitive_dim(PRIMITIVES.parindent);
2749    ET::Stomach::add_node_h(
2750        engine,
2751        HNode::Box(TeXBox::H {
2752            children: vec![].into(),
2753            info: HBoxInfo::ParIndent(dim),
2754            start: engine.mouth.start_ref(),
2755            end: engine.mouth.current_sourceref(),
2756            preskip: None,
2757        }),
2758    );
2759    Ok(())
2760}
2761
2762pub fn delimiter<ET: EngineTypes>(
2763    engine: &mut EngineReferences<ET>,
2764    tk: ET::Token,
2765) -> TeXResult<(), ET> {
2766    let num = engine.read_int(false, &tk)?;
2767    let delim = match Delimiter::from_int(num, engine.state) {
2768        either::Left(d) => d,
2769        either::Right((d, i)) => {
2770            engine.general_error(format!("Bad delimiter code ({})", i))?;
2771            d
2772        }
2773    };
2774    ET::Stomach::add_node_m(engine, MathNode::Atom(delim.small.to_atom()));
2775    Ok(())
2776}
2777
2778pub fn mskip<ET: EngineTypes>(
2779    engine: &mut EngineReferences<ET>,
2780    tk: ET::Token,
2781) -> TeXResult<(), ET> {
2782    let skip = engine.read_muskip(false, &tk)?;
2783    ET::Stomach::add_node_m(
2784        engine,
2785        MathNode::MSkip {
2786            skip,
2787            style: UnresolvedMathFontStyle::of_fam(0),
2788        },
2789    );
2790    Ok(())
2791}
2792
2793pub fn mkern<ET: EngineTypes>(
2794    engine: &mut EngineReferences<ET>,
2795    tk: ET::Token,
2796) -> TeXResult<(), ET> {
2797    let kern = engine.read_mudim(false, &tk)?;
2798    ET::Stomach::add_node_m(
2799        engine,
2800        MathNode::MKern {
2801            kern,
2802            style: UnresolvedMathFontStyle::of_fam(0),
2803        },
2804    );
2805    Ok(())
2806}
2807
2808pub fn unskip<ET: EngineTypes>(
2809    engine: &mut EngineReferences<ET>,
2810    _tk: ET::Token,
2811) -> TeXResult<(), ET> {
2812    super::methods::un_x(
2813        engine,
2814        |v| {
2815            matches!(
2816                v,
2817                VNode::VSkip(_) | VNode::VFil | VNode::VFill | VNode::VFilneg | VNode::Vss
2818            )
2819        },
2820        |h| {
2821            matches!(
2822                h,
2823                HNode::HSkip(_) | HNode::HFil | HNode::HFill | HNode::HFilneg | HNode::Hss
2824            )
2825        },
2826        |m| {
2827            matches!(
2828                m,
2829                MathNode::MSkip { .. }
2830                    | MathNode::HSkip(_)
2831                    | MathNode::HFil
2832                    | MathNode::HFill
2833                    | MathNode::HFilneg
2834                    | MathNode::Hss
2835            )
2836        },
2837    );
2838    Ok(())
2839}
2840pub fn unkern<ET: EngineTypes>(
2841    engine: &mut EngineReferences<ET>,
2842    _tk: ET::Token,
2843) -> TeXResult<(), ET> {
2844    super::methods::un_x(
2845        engine,
2846        |v| matches!(v, VNode::VKern(_)),
2847        |h| matches!(h, HNode::HKern(_)),
2848        |m| matches!(m, MathNode::MKern { .. } | MathNode::HKern(_)),
2849    );
2850    Ok(())
2851}
2852pub fn unpenalty<ET: EngineTypes>(
2853    engine: &mut EngineReferences<ET>,
2854    _tk: ET::Token,
2855) -> TeXResult<(), ET> {
2856    super::methods::un_x(
2857        engine,
2858        |v| matches!(v, VNode::Penalty(_)),
2859        |h| matches!(h, HNode::Penalty(_)),
2860        |m| matches!(m, MathNode::Penalty(_)),
2861    );
2862    Ok(())
2863}
2864
2865pub fn lastbox<ET: EngineTypes>(
2866    engine: &mut EngineReferences<ET>,
2867    _tk: ET::Token,
2868) -> TeXResult<Either<Option<TeXBox<ET>>, BoxInfo<ET>>, ET> {
2869    if engine.stomach.data_mut().mode() == TeXMode::Vertical {
2870        return Err(TeXError::General(
2871            "\\lastbox not allowed outside of vertical mode".to_string(),
2872        ));
2873    }
2874    let data = engine.stomach.data_mut();
2875    match data.open_lists.last_mut() {
2876        None => unreachable!(),
2877        Some(NodeList::Vertical { children, .. }) => {
2878            for (i, n) in children.iter().enumerate().rev() {
2879                if n.opaque() {
2880                    continue;
2881                }
2882                match n {
2883                    VNode::Box(_) => {
2884                        if let VNode::Box(bi) = children.remove(i) {
2885                            return Ok(either::Left(Some(bi)));
2886                        }
2887                        unreachable!()
2888                    }
2889                    _ => return Ok(either::Left(None)),
2890                }
2891            }
2892        }
2893        Some(NodeList::Horizontal { children, .. }) => {
2894            for (i, n) in children.iter().enumerate().rev() {
2895                if n.opaque() {
2896                    continue;
2897                }
2898                match n {
2899                    HNode::Box(_) => {
2900                        if let HNode::Box(bi) = children.remove(i) {
2901                            return Ok(either::Left(Some(bi)));
2902                        } else {
2903                            unreachable!()
2904                        }
2905                    }
2906                    _ => return Ok(either::Left(None)),
2907                }
2908            }
2909        }
2910        _ => (),
2911    }
2912    Ok(either::Left(None))
2913}
2914
2915pub fn lastkern<ET: EngineTypes>(
2916    engine: &mut EngineReferences<ET>,
2917    _tk: ET::Token,
2918) -> TeXResult<ET::Dim, ET> {
2919    Ok(super::methods::last_x(
2920        engine,
2921        |v| match v {
2922            VNode::VKern(k) => Some(*k),
2923            _ => None,
2924        },
2925        |h| match h {
2926            HNode::HKern(k) => Some(*k),
2927            _ => None,
2928        },
2929        |m| match m {
2930            MathNode::HKern(k) => Some(*k),
2931            _ => None,
2932        },
2933    )
2934    .unwrap_or_default())
2935}
2936
2937pub fn lastskip<ET: EngineTypes>(
2938    engine: &mut EngineReferences<ET>,
2939    _tk: ET::Token,
2940) -> TeXResult<Skip<ET::Dim>, ET> {
2941    Ok(super::methods::last_x(
2942        engine,
2943        |v| match v {
2944            VNode::VSkip(k) => Some(*k),
2945            _ => None,
2946        },
2947        |h| match h {
2948            HNode::HSkip(k) => Some(*k),
2949            _ => None,
2950        },
2951        |m| match m {
2952            MathNode::HSkip(k) => Some(*k),
2953            _ => None,
2954        },
2955    )
2956    .unwrap_or_default())
2957}
2958
2959pub fn lastpenalty<ET: EngineTypes>(
2960    engine: &mut EngineReferences<ET>,
2961    _tk: ET::Token,
2962) -> TeXResult<ET::Int, ET> {
2963    Ok(super::methods::last_x(
2964        engine,
2965        |v| match v {
2966            VNode::Penalty(k) => Some(*k),
2967            _ => None,
2968        },
2969        |h| match h {
2970            HNode::Penalty(k) => Some(*k),
2971            _ => None,
2972        },
2973        |m| match m {
2974            MathNode::Penalty(k) => Some(*k),
2975            _ => None,
2976        },
2977    )
2978    .unwrap_or_default()
2979    .into())
2980}
2981
2982pub fn pagegoal_get<ET: EngineTypes>(
2983    engine: &mut EngineReferences<ET>,
2984    _tk: ET::Token,
2985) -> TeXResult<ET::Dim, ET> {
2986    Ok(engine.stomach.data_mut().pagegoal)
2987}
2988pub fn pagegoal_set<ET: EngineTypes>(
2989    engine: &mut EngineReferences<ET>,
2990    tk: ET::Token,
2991    _globally: bool,
2992) -> TeXResult<(), ET> {
2993    let d = engine.read_dim(true, &tk)?;
2994    let data = engine.stomach.data_mut();
2995    if data.pagegoal != ET::Dim::from_sp(i32::MAX) {
2996        engine.stomach.data_mut().pagegoal = d;
2997    }
2998    Ok(())
2999}
3000pub fn pagetotal_get<ET: EngineTypes>(
3001    engine: &mut EngineReferences<ET>,
3002    _tk: ET::Token,
3003) -> TeXResult<ET::Dim, ET> {
3004    Ok(engine.stomach.data_mut().pagetotal)
3005}
3006pub fn pagetotal_set<ET: EngineTypes>(
3007    engine: &mut EngineReferences<ET>,
3008    tk: ET::Token,
3009    _globally: bool,
3010) -> TeXResult<(), ET> {
3011    let d = engine.read_dim(true, &tk)?;
3012    engine.stomach.data_mut().pagetotal = d;
3013    Ok(())
3014}
3015pub fn pagestretch_get<ET: EngineTypes>(
3016    _engine: &mut EngineReferences<ET>,
3017    _tk: ET::Token,
3018) -> TeXResult<ET::Dim, ET> {
3019    Ok(ET::Dim::default()) // TODO
3020}
3021pub fn pagestretch_set<ET: EngineTypes>(
3022    engine: &mut EngineReferences<ET>,
3023    tk: ET::Token,
3024    _globally: bool,
3025) -> TeXResult<(), ET> {
3026    let _ = engine.read_dim(true, &tk)?;
3027    // TODO
3028    Ok(())
3029}
3030pub fn pagefilstretch_get<ET: EngineTypes>(
3031    _engine: &mut EngineReferences<ET>,
3032    _tk: ET::Token,
3033) -> TeXResult<ET::Dim, ET> {
3034    Ok(ET::Dim::default()) // TODO
3035}
3036pub fn pagefilstretch_set<ET: EngineTypes>(
3037    engine: &mut EngineReferences<ET>,
3038    tk: ET::Token,
3039    _globally: bool,
3040) -> TeXResult<(), ET> {
3041    let _ = engine.read_dim(true, &tk)?;
3042    // TODO
3043    Ok(())
3044}
3045pub fn pagefillstretch_get<ET: EngineTypes>(
3046    _engine: &mut EngineReferences<ET>,
3047    _tk: ET::Token,
3048) -> TeXResult<ET::Dim, ET> {
3049    Ok(ET::Dim::default()) // TODO
3050}
3051pub fn pagefillstretch_set<ET: EngineTypes>(
3052    engine: &mut EngineReferences<ET>,
3053    tk: ET::Token,
3054    _globally: bool,
3055) -> TeXResult<(), ET> {
3056    let _ = engine.read_dim(true, &tk)?;
3057    // TODO
3058    Ok(())
3059}
3060pub fn pageshrink_get<ET: EngineTypes>(
3061    _engine: &mut EngineReferences<ET>,
3062    _tk: ET::Token,
3063) -> TeXResult<ET::Dim, ET> {
3064    Ok(ET::Dim::default()) // TODO
3065}
3066pub fn pageshrink_set<ET: EngineTypes>(
3067    engine: &mut EngineReferences<ET>,
3068    tk: ET::Token,
3069    _globally: bool,
3070) -> TeXResult<(), ET> {
3071    let _ = engine.read_dim(true, &tk)?;
3072    // TODO
3073    Ok(())
3074}
3075pub fn pagefilshrink_get<ET: EngineTypes>(
3076    _engine: &mut EngineReferences<ET>,
3077    _tk: ET::Token,
3078) -> TeXResult<ET::Dim, ET> {
3079    Ok(ET::Dim::default()) // TODO
3080}
3081pub fn pagefilshrink_set<ET: EngineTypes>(
3082    engine: &mut EngineReferences<ET>,
3083    tk: ET::Token,
3084    _globally: bool,
3085) -> TeXResult<(), ET> {
3086    let _ = engine.read_dim(true, &tk)?;
3087    // TODO
3088    Ok(())
3089}
3090pub fn pagefillshrink_get<ET: EngineTypes>(
3091    _engine: &mut EngineReferences<ET>,
3092    _tk: ET::Token,
3093) -> TeXResult<ET::Dim, ET> {
3094    Ok(ET::Dim::default()) // TODO
3095}
3096pub fn pagefillshrink_set<ET: EngineTypes>(
3097    engine: &mut EngineReferences<ET>,
3098    tk: ET::Token,
3099    _globally: bool,
3100) -> TeXResult<(), ET> {
3101    let _ = engine.read_dim(true, &tk)?;
3102    // TODO
3103    Ok(())
3104}
3105
3106pub fn pagedepth_get<ET: EngineTypes>(
3107    engine: &mut EngineReferences<ET>,
3108    _tk: ET::Token,
3109) -> TeXResult<ET::Dim, ET> {
3110    Ok(engine.stomach.data_mut().pagedepth)
3111}
3112pub fn pagedepth_set<ET: EngineTypes>(
3113    engine: &mut EngineReferences<ET>,
3114    tk: ET::Token,
3115    _globally: bool,
3116) -> TeXResult<(), ET> {
3117    let d = engine.read_dim(true, &tk)?;
3118    engine.stomach.data_mut().pagedepth = d;
3119    Ok(())
3120}
3121
3122pub fn prevgraf_get<ET: EngineTypes>(
3123    engine: &mut EngineReferences<ET>,
3124    _tk: ET::Token,
3125) -> TeXResult<ET::Int, ET> {
3126    Ok((engine.stomach.data_mut().prevgraf as i32).into())
3127}
3128pub fn prevgraf_set<ET: EngineTypes>(
3129    engine: &mut EngineReferences<ET>,
3130    tk: ET::Token,
3131    _globally: bool,
3132) -> TeXResult<(), ET> {
3133    let d = engine.read_int(true, &tk)?.into();
3134    if d < 0 {
3135        engine.general_error(format!("Bad prevgraf ({})", d))?;
3136    } else {
3137        engine.stomach.data_mut().prevgraf = d as u16;
3138    }
3139    Ok(())
3140}
3141
3142pub fn deadcycles_get<ET: EngineTypes>(
3143    engine: &mut EngineReferences<ET>,
3144    _tk: ET::Token,
3145) -> TeXResult<ET::Int, ET> {
3146    Ok((engine.stomach.data_mut().deadcycles as i32).into())
3147}
3148pub fn deadcycles_set<ET: EngineTypes>(
3149    engine: &mut EngineReferences<ET>,
3150    tk: ET::Token,
3151    _globally: bool,
3152) -> TeXResult<(), ET> {
3153    let i = engine.read_int(true, &tk)?.into();
3154    if i >= 0 {
3155        engine.stomach.data_mut().deadcycles = i as usize
3156    }
3157    Ok(())
3158}
3159
3160pub fn vsplit<ET: EngineTypes>(
3161    engine: &mut EngineReferences<ET>,
3162    tk: ET::Token,
3163) -> TeXResult<Either<Option<TeXBox<ET>>, BoxInfo<ET>>, ET> {
3164    let idx = engine.read_register_index(false, &tk)?;
3165    let (mut info, ls, start, end) = match engine.state.get_box_register_mut(idx) {
3166        Some(TeXBox::V {
3167            info,
3168            children,
3169            start,
3170            end,
3171        }) => (
3172            info.clone_for_split(),
3173            std::mem::take(children).into_vec(),
3174            *start,
3175            *end,
3176        ),
3177        _ => {
3178            if !engine.read_keyword(b"to")? {
3179                return Err(TeXError::General(
3180                    "`to` expected after \\vsplit".to_string(),
3181                ));
3182            }
3183            let _ = engine.read_dim(false, &tk)?;
3184            return Ok(either::Left(None));
3185        }
3186    };
3187    if !engine.read_keyword(b"to")? {
3188        return Err(TeXError::General(
3189            "`to` expected after \\vsplit".to_string(),
3190        ));
3191    }
3192    let target = engine.read_dim(false, &tk)?;
3193    match &mut info {
3194        VBoxInfo::VBox { scaled, .. } => {
3195            *scaled = ToOrSpread::To(target);
3196        }
3197        VBoxInfo::VTop { scaled, .. } => {
3198            *scaled = ToOrSpread::To(target);
3199        }
3200        _ => unreachable!(),
3201    }
3202    let SplitResult { first, rest, .. } = ET::Stomach::split_vertical(engine, ls, target);
3203    let ret = TeXBox::V {
3204        children: first.into(),
3205        info,
3206        start,
3207        end,
3208    };
3209    if rest.is_empty() {
3210        engine.state.set_box_register(engine.aux, idx, None, false);
3211    } else if let Some(TeXBox::V { children, .. }) = engine.state.get_box_register_mut(idx) {
3212        *children = rest.into();
3213    } else {
3214        unreachable!()
3215    }
3216    Ok(either::Left(Some(ret)))
3217}
3218
3219pub fn vadjust<ET: EngineTypes>(
3220    engine: &mut EngineReferences<ET>,
3221    tk: ET::Token,
3222) -> TeXResult<(), ET> {
3223    engine.expand_until_bgroup(true, &tk)?;
3224    engine
3225        .stomach
3226        .data_mut()
3227        .open_lists
3228        .push(NodeList::Vertical {
3229            children: vec![],
3230            tp: VerticalNodeListType::VAdjust,
3231        });
3232    engine
3233        .state
3234        .push(engine.aux, GroupType::VAdjust, engine.mouth.line_number());
3235    Ok(())
3236}
3237
3238pub fn inputlineno<ET: EngineTypes>(
3239    engine: &mut EngineReferences<ET>,
3240    _tk: ET::Token,
3241) -> TeXResult<ET::Int, ET> {
3242    Ok(ET::Int::from(engine.mouth.line_number() as i32))
3243}
3244
3245pub fn mark<ET: EngineTypes>(
3246    engine: &mut EngineReferences<ET>,
3247    tk: ET::Token,
3248) -> TeXResult<(), ET> {
3249    super::methods::do_marks(engine, 0, &tk)
3250}
3251
3252pub fn topmark<ET: EngineTypes>(
3253    engine: &mut EngineReferences<ET>,
3254    exp: &mut Vec<ET::Token>,
3255    _tk: ET::Token,
3256) -> TeXResult<(), ET> {
3257    super::methods::get_marks(engine, exp, |d| &mut d.topmarks, 0);
3258    Ok(())
3259}
3260
3261pub fn firstmark<ET: EngineTypes>(
3262    engine: &mut EngineReferences<ET>,
3263    exp: &mut Vec<ET::Token>,
3264    _tk: ET::Token,
3265) -> TeXResult<(), ET> {
3266    super::methods::get_marks(engine, exp, |d| &mut d.firstmarks, 0);
3267    Ok(())
3268}
3269
3270pub fn botmark<ET: EngineTypes>(
3271    engine: &mut EngineReferences<ET>,
3272    exp: &mut Vec<ET::Token>,
3273    _tk: ET::Token,
3274) -> TeXResult<(), ET> {
3275    super::methods::get_marks(engine, exp, |d| &mut d.botmarks, 0);
3276    Ok(())
3277}
3278
3279pub fn splitfirstmark<ET: EngineTypes>(
3280    engine: &mut EngineReferences<ET>,
3281    exp: &mut Vec<ET::Token>,
3282    _tk: ET::Token,
3283) -> TeXResult<(), ET> {
3284    super::methods::get_marks(engine, exp, |d| &mut d.splitfirstmarks, 0);
3285    Ok(())
3286}
3287
3288pub fn splitbotmark<ET: EngineTypes>(
3289    engine: &mut EngineReferences<ET>,
3290    exp: &mut Vec<ET::Token>,
3291    _tk: ET::Token,
3292) -> TeXResult<(), ET> {
3293    super::methods::get_marks(engine, exp, |d| &mut d.splitbotmarks, 0);
3294    Ok(())
3295}
3296
3297pub fn shipout<ET: EngineTypes>(
3298    engine: &mut EngineReferences<ET>,
3299    _tk: ET::Token,
3300) -> TeXResult<(), ET> {
3301    engine.stomach.data_mut().deadcycles = 0;
3302    match engine.read_box(false)? {
3303        either::Left(Some(bx)) => {
3304            engine.shipout(VNode::Box(bx))?;
3305            /*if let Some(n) = bx.as_node().shipout_top(engine) {
3306                engine.colon.out(n)
3307            }*/
3308        }
3309        either::Left(None) => (),
3310        either::Right(bi) => {
3311            let mut list = bi.open_list(engine.mouth.start_ref());
3312            let target = BoxTarget::new(|e, b| e.shipout(VNode::Box(b)));
3313            match list {
3314                NodeList::Horizontal {
3315                    tp: HorizontalNodeListType::Box(_, _, ref mut t),
3316                    ..
3317                } => *t = target,
3318                NodeList::Vertical {
3319                    tp: VerticalNodeListType::Box(_, _, ref mut t),
3320                    ..
3321                } => *t = target,
3322                _ => unreachable!(),
3323            }
3324            let data = engine.stomach.data_mut();
3325            data.open_lists.push(list);
3326        }
3327    }
3328    Ok(())
3329}
3330
3331pub fn displaylimits<ET: EngineTypes>(
3332    engine: &mut EngineReferences<ET>,
3333    _tk: ET::Token,
3334) -> TeXResult<(), ET> {
3335    let ls = engine.stomach.data_mut().open_lists.last_mut().unwrap();
3336    match ls {
3337        NodeList::Math { children, .. } => {
3338            if let Some(MathNode::Atom(MathAtom {
3339                sub: None,
3340                sup: None,
3341                nucleus: MathNucleus::Simple { ref mut limits, .. },
3342                ..
3343            })) = children.list_mut().last_mut()
3344            {
3345                *limits = None;
3346            }
3347        }
3348        _ => unreachable!(),
3349    }
3350    Ok(())
3351}
3352pub fn limits<ET: EngineTypes>(
3353    engine: &mut EngineReferences<ET>,
3354    _tk: ET::Token,
3355) -> TeXResult<(), ET> {
3356    let ls = engine.stomach.data_mut().open_lists.last_mut().unwrap();
3357    match ls {
3358        NodeList::Math { children, .. } => {
3359            if let Some(MathNode::Atom(MathAtom {
3360                sub: None,
3361                sup: None,
3362                nucleus:
3363                    MathNucleus::Simple {
3364                        cls: MathClass::Op,
3365                        ref mut limits,
3366                        ..
3367                    },
3368                ..
3369            })) = children.list_mut().last_mut()
3370            {
3371                *limits = Some(true);
3372            }
3373        }
3374        _ => unreachable!(),
3375    }
3376    Ok(())
3377}
3378pub fn nolimits<ET: EngineTypes>(
3379    engine: &mut EngineReferences<ET>,
3380    _tk: ET::Token,
3381) -> TeXResult<(), ET> {
3382    let ls = engine.stomach.data_mut().open_lists.last_mut().unwrap();
3383    match ls {
3384        NodeList::Math { children, .. } => {
3385            if let Some(MathNode::Atom(MathAtom {
3386                sub: None,
3387                sup: None,
3388                nucleus:
3389                    MathNucleus::Simple {
3390                        cls: MathClass::Op,
3391                        ref mut limits,
3392                        ..
3393                    },
3394                ..
3395            })) = children.list_mut().last_mut()
3396            {
3397                *limits = Some(false);
3398            }
3399        }
3400        _ => unreachable!(),
3401    }
3402    Ok(())
3403}
3404
3405pub fn underline<ET: EngineTypes>(
3406    engine: &mut EngineReferences<ET>,
3407    tk: ET::Token,
3408) -> TeXResult<(), ET> {
3409    engine.read_char_or_math_group(
3410        &tk,
3411        |_, engine, mc| {
3412            ET::Stomach::add_node_m(
3413                engine,
3414                MathNode::Atom(MathAtom {
3415                    nucleus: MathNucleus::Underline(MathKernel::Char {
3416                        char: mc.char,
3417                        style: mc.style,
3418                    }),
3419                    sub: None,
3420                    sup: None,
3421                }),
3422            );
3423            Ok(())
3424        },
3425        |_| {
3426            ListTarget::<ET, _>::new(|engine, children, start| {
3427                ET::Stomach::add_node_m(
3428                    engine,
3429                    MathNode::Atom(MathAtom {
3430                        sup: None,
3431                        sub: None,
3432                        nucleus: MathNucleus::Underline(MathKernel::List {
3433                            children: children.into(),
3434                            start,
3435                            end: engine.mouth.current_sourceref(),
3436                        }),
3437                    }),
3438                );
3439                Ok(())
3440            })
3441        },
3442        (),
3443    )
3444}
3445pub fn overline<ET: EngineTypes>(
3446    engine: &mut EngineReferences<ET>,
3447    tk: ET::Token,
3448) -> TeXResult<(), ET> {
3449    engine.read_char_or_math_group(
3450        &tk,
3451        |_, engine, mc| {
3452            ET::Stomach::add_node_m(
3453                engine,
3454                MathNode::Atom(MathAtom {
3455                    nucleus: MathNucleus::Overline(MathKernel::Char {
3456                        char: mc.char,
3457                        style: mc.style,
3458                    }),
3459                    sub: None,
3460                    sup: None,
3461                }),
3462            );
3463            Ok(())
3464        },
3465        |_| {
3466            ListTarget::<ET, _>::new(|engine, children, start| {
3467                ET::Stomach::add_node_m(
3468                    engine,
3469                    MathNode::Atom(MathAtom {
3470                        sup: None,
3471                        sub: None,
3472                        nucleus: MathNucleus::Overline(MathKernel::List {
3473                            children: children.into(),
3474                            start,
3475                            end: engine.mouth.current_sourceref(),
3476                        }),
3477                    }),
3478                );
3479                Ok(())
3480            })
3481        },
3482        (),
3483    )
3484}
3485
3486pub fn mathaccent<ET: EngineTypes>(
3487    engine: &mut EngineReferences<ET>,
3488    tk: ET::Token,
3489) -> TeXResult<(), ET> {
3490    let i = engine.read_int(false, &tk)?.into();
3491    if i < 0 || i > 32767 as i64 {
3492        return Err(TeXError::General(format!("Illegal math accent number {i}")));
3493    }
3494    let char = MathChar::from_u32(i as u32, engine.state, None);
3495    engine.read_char_or_math_group(
3496        &tk,
3497        |(char, style), engine, mc| {
3498            ET::Stomach::add_node_m(
3499                engine,
3500                MathNode::Atom(MathAtom {
3501                    nucleus: MathNucleus::Accent {
3502                        accent: (char, style),
3503                        inner: vec![MathNode::Atom(mc.to_atom())].into(),
3504                    },
3505                    sub: None,
3506                    sup: None,
3507                }),
3508            );
3509            Ok(())
3510        },
3511        |(char, style)| {
3512            ListTarget::<ET, _>::new(move |engine, children, _| {
3513                ET::Stomach::add_node_m(
3514                    engine,
3515                    MathNode::Atom(MathAtom {
3516                        sup: None,
3517                        sub: None,
3518                        nucleus: MathNucleus::Accent {
3519                            accent: (char, style),
3520                            inner: children.into(),
3521                        },
3522                    }),
3523                );
3524                Ok(())
3525            })
3526        },
3527        (char.char, char.style),
3528    )
3529}
3530
3531pub fn radical<ET: EngineTypes>(
3532    engine: &mut EngineReferences<ET>,
3533    tk: ET::Token,
3534) -> TeXResult<(), ET> {
3535    let i = engine.read_int(false, &tk)?;
3536    if i.into() < 0 || i.into() > u32::MAX as i64 {
3537        return Err(TeXError::General(format!("Illegal radical number {i}")));
3538    }
3539    let char = Delimiter::from_int(i, engine.state).left().unwrap().small; // MathChar::from_u32(i as u32, engine.state, None);
3540    engine.read_char_or_math_group(
3541        &tk,
3542        |(char, style), engine, mc| {
3543            ET::Stomach::add_node_m(
3544                engine,
3545                MathNode::Atom(MathAtom {
3546                    nucleus: MathNucleus::Radical {
3547                        rad: (char, style),
3548                        inner: vec![MathNode::Atom(mc.to_atom())].into(),
3549                    },
3550                    sub: None,
3551                    sup: None,
3552                }),
3553            );
3554            Ok(())
3555        },
3556        |(char, style)| {
3557            ListTarget::<ET, _>::new(move |engine, children, _| {
3558                ET::Stomach::add_node_m(
3559                    engine,
3560                    MathNode::Atom(MathAtom {
3561                        sup: None,
3562                        sub: None,
3563                        nucleus: MathNucleus::Radical {
3564                            rad: (char, style),
3565                            inner: children.into(),
3566                        },
3567                    }),
3568                );
3569                Ok(())
3570            })
3571        },
3572        (char.char, char.style),
3573    )
3574}
3575
3576fn do_eqno<ET: EngineTypes>(
3577    engine: &mut EngineReferences<ET>,
3578    pos: EqNoPosition,
3579) -> TeXResult<(), ET> {
3580    match engine.stomach.data_mut().open_lists.last_mut() {
3581        Some(NodeList::Math {
3582            children: ch @ MathNodeList::Simple(_) | ch @ MathNodeList::Over { .. },
3583            tp: MathNodeListType::Top { .. },
3584            start,
3585        }) => {
3586            let old = std::mem::replace(
3587                ch,
3588                MathNodeList::EqNo {
3589                    pos,
3590                    main: vec![],
3591                    eqno: vec![],
3592                },
3593            );
3594            let (children, None) = old.close(*start, engine.mouth.current_sourceref()) else {
3595                unreachable!()
3596            };
3597            let MathNodeList::EqNo { main, .. } = ch else {
3598                unreachable!()
3599            };
3600            *main = children;
3601        }
3602        _ => return Err(TeXError::General("\\eqno outside of math mode".to_string())),
3603    }
3604    Ok(())
3605}
3606
3607pub fn eqno<ET: EngineTypes>(
3608    engine: &mut EngineReferences<ET>,
3609    _tk: ET::Token,
3610) -> TeXResult<(), ET> {
3611    do_eqno(engine, EqNoPosition::Right)
3612}
3613
3614pub fn leqno<ET: EngineTypes>(
3615    engine: &mut EngineReferences<ET>,
3616    _tk: ET::Token,
3617) -> TeXResult<(), ET> {
3618    do_eqno(engine, EqNoPosition::Left)
3619}
3620
3621pub fn over<ET: EngineTypes>(
3622    engine: &mut EngineReferences<ET>,
3623    _tk: ET::Token,
3624) -> TeXResult<(), ET> {
3625    match engine.stomach.data_mut().open_lists.last_mut() {
3626        Some(NodeList::Math {
3627            children: ch @ MathNodeList::Simple(_),
3628            ..
3629        }) => {
3630            //let v = std::mem::take(v);
3631            let old = std::mem::replace(
3632                ch,
3633                MathNodeList::Over {
3634                    top: vec![],
3635                    bottom: vec![],
3636                    left: None,
3637                    right: None,
3638                    sep: None,
3639                },
3640            );
3641            if let MathNodeList::Over { top, .. } = ch {
3642                *top = if let MathNodeList::Simple(v) = old {
3643                    v
3644                } else {
3645                    unreachable!()
3646                };
3647            } else {
3648                unreachable!()
3649            }
3650        }
3651        Some(NodeList::Math { .. }) => {
3652            return Err(TeXError::General(
3653                "Incompatible list for \\over".to_string(),
3654            ))
3655        }
3656        _ => unreachable!(),
3657    }
3658    Ok(())
3659}
3660
3661pub fn overwithdelims<ET: EngineTypes>(
3662    engine: &mut EngineReferences<ET>,
3663    tk: ET::Token,
3664) -> TeXResult<(), ET> {
3665    let left = engine
3666        .read_opt_delimiter(&tk)?
3667        .map(|d| (d.small.char, d.small.style));
3668    let right = engine
3669        .read_opt_delimiter(&tk)?
3670        .map(|d| (d.small.char, d.small.style));
3671    match engine.stomach.data_mut().open_lists.last_mut() {
3672        Some(NodeList::Math {
3673            children: ch @ MathNodeList::Simple(_),
3674            ..
3675        }) => {
3676            //let v = std::mem::take(v);
3677            let old = std::mem::replace(
3678                ch,
3679                MathNodeList::Over {
3680                    top: vec![],
3681                    bottom: vec![],
3682                    left,
3683                    right,
3684                    sep: None,
3685                },
3686            );
3687            if let MathNodeList::Over { top, .. } = ch {
3688                *top = if let MathNodeList::Simple(v) = old {
3689                    v
3690                } else {
3691                    unreachable!()
3692                };
3693            } else {
3694                unreachable!()
3695            }
3696        }
3697        Some(NodeList::Math { .. }) => {
3698            return Err(TeXError::General(
3699                "Incompatible list for \\overwithdelims".to_string(),
3700            ))
3701        }
3702        _ => unreachable!(),
3703    }
3704    Ok(())
3705}
3706
3707pub fn above<ET: EngineTypes>(
3708    engine: &mut EngineReferences<ET>,
3709    tk: ET::Token,
3710) -> TeXResult<(), ET> {
3711    let sep = engine.read_dim(false, &tk)?;
3712    match engine.stomach.data_mut().open_lists.last_mut() {
3713        Some(NodeList::Math {
3714            children: ch @ MathNodeList::Simple(_),
3715            ..
3716        }) => {
3717            //let v = std::mem::take(v);
3718            let old = std::mem::replace(
3719                ch,
3720                MathNodeList::Over {
3721                    top: vec![],
3722                    bottom: vec![],
3723                    left: None,
3724                    right: None,
3725                    sep: Some(sep),
3726                },
3727            );
3728            if let MathNodeList::Over { top, .. } = ch {
3729                *top = if let MathNodeList::Simple(v) = old {
3730                    v
3731                } else {
3732                    unreachable!()
3733                };
3734            } else {
3735                unreachable!()
3736            }
3737        }
3738        Some(NodeList::Math { .. }) => {
3739            return Err(TeXError::General(
3740                "Incompatible list for \\above".to_string(),
3741            ))
3742        }
3743        _ => unreachable!(),
3744    }
3745    Ok(())
3746}
3747
3748pub fn abovewithdelims<ET: EngineTypes>(
3749    engine: &mut EngineReferences<ET>,
3750    tk: ET::Token,
3751) -> TeXResult<(), ET> {
3752    let left = engine
3753        .read_opt_delimiter(&tk)?
3754        .map(|d| (d.small.char, d.small.style));
3755    let right = engine
3756        .read_opt_delimiter(&tk)?
3757        .map(|d| (d.small.char, d.small.style));
3758    let sep = engine.read_dim(false, &tk)?;
3759    match engine.stomach.data_mut().open_lists.last_mut() {
3760        Some(NodeList::Math {
3761            children: ch @ MathNodeList::Simple(_),
3762            ..
3763        }) => {
3764            //let v = std::mem::take(v);
3765            let old = std::mem::replace(
3766                ch,
3767                MathNodeList::Over {
3768                    top: vec![],
3769                    bottom: vec![],
3770                    left,
3771                    right,
3772                    sep: Some(sep),
3773                },
3774            );
3775            if let MathNodeList::Over { top, .. } = ch {
3776                *top = if let MathNodeList::Simple(v) = old {
3777                    v
3778                } else {
3779                    unreachable!()
3780                };
3781            } else {
3782                unreachable!()
3783            }
3784        }
3785        Some(NodeList::Math { .. }) => {
3786            return Err(TeXError::General(
3787                "Incompatible list for \\abovewithdelims".to_string(),
3788            ))
3789        }
3790        _ => unreachable!(),
3791    }
3792    Ok(())
3793}
3794
3795pub fn atop<ET: EngineTypes>(
3796    engine: &mut EngineReferences<ET>,
3797    _tk: ET::Token,
3798) -> TeXResult<(), ET> {
3799    match engine.stomach.data_mut().open_lists.last_mut() {
3800        Some(NodeList::Math {
3801            children: ch @ MathNodeList::Simple(_),
3802            ..
3803        }) => {
3804            //let v = std::mem::take(v);
3805            let old = std::mem::replace(
3806                ch,
3807                MathNodeList::Over {
3808                    top: vec![],
3809                    bottom: vec![],
3810                    left: None,
3811                    right: None,
3812                    sep: Some(ET::Dim::default()),
3813                },
3814            );
3815            if let MathNodeList::Over { top, .. } = ch {
3816                *top = if let MathNodeList::Simple(v) = old {
3817                    v
3818                } else {
3819                    unreachable!()
3820                };
3821            } else {
3822                unreachable!()
3823            }
3824        }
3825        Some(NodeList::Math { .. }) => {
3826            return Err(TeXError::General(
3827                "Incompatible list for \\atop".to_string(),
3828            ))
3829        }
3830        _ => unreachable!(),
3831    }
3832    Ok(())
3833}
3834
3835pub fn atopwithdelims<ET: EngineTypes>(
3836    engine: &mut EngineReferences<ET>,
3837    tk: ET::Token,
3838) -> TeXResult<(), ET> {
3839    let left = engine
3840        .read_opt_delimiter(&tk)?
3841        .map(|d| (d.small.char, d.small.style));
3842    let right = engine
3843        .read_opt_delimiter(&tk)?
3844        .map(|d| (d.small.char, d.small.style));
3845    match engine.stomach.data_mut().open_lists.last_mut() {
3846        Some(NodeList::Math {
3847            children: ch @ MathNodeList::Simple(_),
3848            ..
3849        }) => {
3850            //let v = std::mem::take(v);
3851            let old = std::mem::replace(
3852                ch,
3853                MathNodeList::Over {
3854                    top: vec![],
3855                    bottom: vec![],
3856                    left,
3857                    right,
3858                    sep: Some(ET::Dim::default()),
3859                },
3860            );
3861            if let MathNodeList::Over { top, .. } = ch {
3862                *top = if let MathNodeList::Simple(v) = old {
3863                    v
3864                } else {
3865                    unreachable!()
3866                };
3867            } else {
3868                unreachable!()
3869            }
3870        }
3871        Some(NodeList::Math { .. }) => {
3872            return Err(TeXError::General(
3873                "Incompatible list for \\atopwithdelims".to_string(),
3874            ))
3875        }
3876        _ => unreachable!(),
3877    }
3878    Ok(())
3879}
3880
3881pub fn mathord<ET: EngineTypes>(
3882    engine: &mut EngineReferences<ET>,
3883    tk: ET::Token,
3884) -> TeXResult<(), ET> {
3885    super::methods::do_math_class(engine, Some(MathClass::Ord), &tk)
3886}
3887pub fn mathop<ET: EngineTypes>(
3888    engine: &mut EngineReferences<ET>,
3889    tk: ET::Token,
3890) -> TeXResult<(), ET> {
3891    super::methods::do_math_class(engine, Some(MathClass::Op), &tk)
3892}
3893pub fn mathbin<ET: EngineTypes>(
3894    engine: &mut EngineReferences<ET>,
3895    tk: ET::Token,
3896) -> TeXResult<(), ET> {
3897    super::methods::do_math_class(engine, Some(MathClass::Bin), &tk)
3898}
3899pub fn mathrel<ET: EngineTypes>(
3900    engine: &mut EngineReferences<ET>,
3901    tk: ET::Token,
3902) -> TeXResult<(), ET> {
3903    super::methods::do_math_class(engine, Some(MathClass::Rel), &tk)
3904}
3905pub fn mathopen<ET: EngineTypes>(
3906    engine: &mut EngineReferences<ET>,
3907    tk: ET::Token,
3908) -> TeXResult<(), ET> {
3909    super::methods::do_math_class(engine, Some(MathClass::Open), &tk)
3910}
3911pub fn mathclose<ET: EngineTypes>(
3912    engine: &mut EngineReferences<ET>,
3913    tk: ET::Token,
3914) -> TeXResult<(), ET> {
3915    super::methods::do_math_class(engine, Some(MathClass::Close), &tk)
3916}
3917pub fn mathpunct<ET: EngineTypes>(
3918    engine: &mut EngineReferences<ET>,
3919    tk: ET::Token,
3920) -> TeXResult<(), ET> {
3921    super::methods::do_math_class(engine, Some(MathClass::Punct), &tk)
3922}
3923pub fn mathinner<ET: EngineTypes>(
3924    engine: &mut EngineReferences<ET>,
3925    tk: ET::Token,
3926) -> TeXResult<(), ET> {
3927    super::methods::do_math_class(engine, None, &tk)
3928}
3929pub fn mathchoice<ET: EngineTypes>(
3930    engine: &mut EngineReferences<ET>,
3931    tk: ET::Token,
3932) -> TeXResult<(), ET> {
3933    engine.read_char_or_math_group(
3934        &tk.clone(),
3935        |tk, engine, mc| mathchoice_i(engine, vec![MathNode::Atom(mc.to_atom())].into(), tk),
3936        |tk| {
3937            ListTarget::<ET, _>::new(|engine, children, _| {
3938                mathchoice_i(engine, children.into(), tk)
3939            })
3940        },
3941        tk,
3942    )
3943}
3944type ML<ET> = Box<[MathNode<ET, UnresolvedMathFontStyle<ET>>]>;
3945pub fn mathchoice_i<ET: EngineTypes>(
3946    engine: &mut EngineReferences<ET>,
3947    d: ML<ET>,
3948    tk: ET::Token,
3949) -> TeXResult<(), ET> {
3950    engine.read_char_or_math_group(
3951        &tk.clone(),
3952        |(d, tk), engine, mc| {
3953            mathchoice_ii(engine, d, vec![MathNode::Atom(mc.to_atom())].into(), tk)
3954        },
3955        |(d, tk)| {
3956            ListTarget::<ET, _>::new(move |engine, children, _| {
3957                mathchoice_ii(engine, d, children.into(), tk)
3958            })
3959        },
3960        (d, tk),
3961    )
3962}
3963pub fn mathchoice_ii<ET: EngineTypes>(
3964    engine: &mut EngineReferences<ET>,
3965    d: ML<ET>,
3966    t: ML<ET>,
3967    tk: ET::Token,
3968) -> TeXResult<(), ET> {
3969    engine.read_char_or_math_group(
3970        &tk.clone(),
3971        |(d, t, tk), engine, mc| {
3972            mathchoice_iii(engine, d, t, vec![MathNode::Atom(mc.to_atom())].into(), tk)
3973        },
3974        |(d, t, tk)| {
3975            ListTarget::<ET, _>::new(|engine, children, _| {
3976                mathchoice_iii(engine, d, t, children.into(), tk)
3977            })
3978        },
3979        (d, t, tk),
3980    )
3981}
3982pub fn mathchoice_iii<ET: EngineTypes>(
3983    engine: &mut EngineReferences<ET>,
3984    d: ML<ET>,
3985    t: ML<ET>,
3986    s: ML<ET>,
3987    tk: ET::Token,
3988) -> TeXResult<(), ET> {
3989    engine.read_char_or_math_group(
3990        &tk,
3991        |(d, t, s), engine, mc| {
3992            ET::Stomach::add_node_m(
3993                engine,
3994                MathNode::Choice(UnresolvedMathChoice {
3995                    display: d,
3996                    text: t,
3997                    script: s,
3998                    scriptscript: vec![MathNode::Atom(mc.to_atom())].into(),
3999                }),
4000            );
4001            Ok(())
4002        },
4003        |(d, t, s)| {
4004            ListTarget::<ET, _>::new(|engine, children, _| {
4005                ET::Stomach::add_node_m(
4006                    engine,
4007                    MathNode::Choice(UnresolvedMathChoice {
4008                        display: d,
4009                        text: t,
4010                        script: s,
4011                        scriptscript: children.into(),
4012                    }),
4013                );
4014                Ok(())
4015            })
4016        },
4017        (d, t, s),
4018    )
4019}
4020
4021pub fn displaystyle<ET: EngineTypes>(
4022    engine: &mut EngineReferences<ET>,
4023    _tk: ET::Token,
4024) -> TeXResult<(), ET> {
4025    ET::Stomach::add_node_m(engine, MathNode::Marker(UnresolvedMarkers::Display));
4026    Ok(())
4027}
4028pub fn textstyle<ET: EngineTypes>(
4029    engine: &mut EngineReferences<ET>,
4030    _tk: ET::Token,
4031) -> TeXResult<(), ET> {
4032    ET::Stomach::add_node_m(engine, MathNode::Marker(UnresolvedMarkers::Text));
4033    Ok(())
4034}
4035pub fn scriptstyle<ET: EngineTypes>(
4036    engine: &mut EngineReferences<ET>,
4037    _tk: ET::Token,
4038) -> TeXResult<(), ET> {
4039    ET::Stomach::add_node_m(engine, MathNode::Marker(UnresolvedMarkers::Script));
4040    Ok(())
4041}
4042pub fn scriptscriptstyle<ET: EngineTypes>(
4043    engine: &mut EngineReferences<ET>,
4044    _tk: ET::Token,
4045) -> TeXResult<(), ET> {
4046    ET::Stomach::add_node_m(engine, MathNode::Marker(UnresolvedMarkers::ScriptScript));
4047    Ok(())
4048}
4049
4050const PRIMITIVE_INTS: &[&str] = &[
4051    "year",
4052    "month",
4053    "day",
4054    "time",
4055    "adjdemerits",
4056    "badness",
4057    "binoppenalty",
4058    "brokenpenalty",
4059    "clubpenalty",
4060    "defaulthyphenchar",
4061    "defaultskewchar",
4062    "delimiterfactor",
4063    "displaywidowpenalty",
4064    "doublehyphendemerits",
4065    "errorcontextlines",
4066    "exhyphenpenalty",
4067    "fam",
4068    "finalhyphendemerits",
4069    "floatingpenalty",
4070    "globaldefs",
4071    "hangafter",
4072    "hbadness",
4073    "holdinginserts",
4074    "hyphenpenalty",
4075    "interlinepenalty",
4076    "language",
4077    "lefthyphenmin",
4078    "linepenalty",
4079    "looseness",
4080    "mag",
4081    "maxdeadcycles",
4082    "outputpenalty",
4083    "pausing",
4084    "postdisplaypenalty",
4085    "predisplaypenalty",
4086    "relpenalty",
4087    "righthyphenmin",
4088    "pretolerance",
4089    "showboxbreadth",
4090    "showboxdepth",
4091    "tolerance",
4092    "tracingcommands",
4093    "tracinglostchars",
4094    "tracingmacros",
4095    "tracingonline",
4096    "tracingoutput",
4097    "tracingpages",
4098    "tracingparagraphs",
4099    "tracingrestores",
4100    "tracingstats",
4101    "uchyph",
4102    "vbadness",
4103    "widowpenalty",
4104    "synctex",         // not technically plain tex, but supported in basically all engines
4105    "insertpenalties", // todo
4106];
4107
4108const PRIMITIVE_DIMS: &[&str] = &[
4109    "boxmaxdepth",
4110    "delimitershortfall",
4111    "displayindent",
4112    "displaywidth",
4113    "hangindent",
4114    "emergencystretch",
4115    "hfuzz",
4116    "hoffset",
4117    "hsize",
4118    "lineskiplimit",
4119    "maxdepth",
4120    "mathsurround",
4121    "nulldelimiterspace",
4122    "overfullrule",
4123    "parindent",
4124    "predisplaysize",
4125    "scriptspace",
4126    "splitmaxdepth",
4127    "vfuzz",
4128    "voffset",
4129    "vsize",
4130];
4131
4132const PRIMITIVE_SKIPS: &[&str] = &[
4133    "abovedisplayshortskip",
4134    "abovedisplayskip",
4135    "baselineskip",
4136    "belowdisplayshortskip",
4137    "belowdisplayskip",
4138    "leftskip",
4139    "lineskip",
4140    "parfillskip",
4141    "parskip",
4142    "rightskip",
4143    "spaceskip",
4144    "splittopskip",
4145    "tabskip",
4146    "topskip",
4147    "xspaceskip",
4148];
4149
4150const PRIMITIVE_MUSKIPS: &[&str] = &["thinmuskip", "medmuskip", "thickmuskip"];
4151
4152const PRIMITIVE_TOKS: &[&str] = &[
4153    "everypar",
4154    "everymath",
4155    "everydisplay",
4156    "everyhbox",
4157    "everyvbox",
4158    "everyjob",
4159    "everycr",
4160    "output",
4161    "errhelp",
4162];
4163
4164#[allow(unreachable_code)]
4165pub fn char_space<ET: EngineTypes>(
4166    engine: &mut EngineReferences<ET>,
4167    _tk: ET::Token,
4168) -> TeXResult<(), ET> {
4169    crate::add_node!(ET::Stomach;engine,unreachable!(), HNode::Space, MathNode::Space);
4170    Ok(())
4171}
4172pub fn char_slash<ET: EngineTypes>(
4173    _engine: &mut EngineReferences<ET>,
4174    _tk: ET::Token,
4175) -> TeXResult<(), ET> {
4176    // TODO
4177    Ok(())
4178}
4179pub fn char_dash<ET: EngineTypes>(
4180    _engine: &mut EngineReferences<ET>,
4181    _tk: ET::Token,
4182) -> TeXResult<(), ET> {
4183    // TODO
4184    Ok(())
4185}
4186
4187pub fn end_template<ET: EngineTypes>(
4188    engine: &mut EngineReferences<ET>,
4189    _tk: ET::Token,
4190) -> TeXResult<(), ET> {
4191    match engine.gullet.get_align_data() {
4192        None => unreachable!(),
4193        Some(data) => {
4194            data.omit = false;
4195            let skip = data.columns[data.currindex].tabskip;
4196            match engine.stomach.data_mut().open_lists.last_mut() {
4197                Some(NodeList::Horizontal { children, .. }) => {
4198                    children.push(HNode::HSkip(skip));
4199                }
4200                Some(NodeList::Vertical { children, .. }) => {
4201                    children.push(VNode::VSkip(skip));
4202                }
4203                _ => unreachable!(),
4204            }
4205            data.currindex += 1;
4206            if data.columns.len() <= data.currindex {
4207                if let Some(i) = data.repeat_index {
4208                    data.currindex = i
4209                }
4210            }
4211            if data.span {
4212                match engine.stomach.data_mut().open_lists.last_mut() {
4213                    Some(NodeList::Horizontal {
4214                        tp: HorizontalNodeListType::HAlignCell(_, ref mut span),
4215                        ..
4216                    })
4217                    | Some(NodeList::Vertical {
4218                        tp: VerticalNodeListType::VAlignCell(_, ref mut span),
4219                        ..
4220                    }) => {
4221                        *span += 1;
4222                    }
4223                    _ => unreachable!(),
4224                }
4225            } else {
4226                super::methods::pop_align_cell(
4227                    engine.state,
4228                    engine.aux,
4229                    engine.stomach,
4230                    engine.mouth,
4231                    data.inner_mode,
4232                );
4233            }
4234            let mode = data.inner_mode;
4235            crate::expand_loop!(engine,token,
4236                ResolvedToken::Tk{code:CommandCode::EndGroup,..} |
4237                ResolvedToken::Cmd(Some(TeXCommand::Char {code:CommandCode::EndGroup,..})) => {
4238                    return Err(TeXError::General("Unexpected end group character in alignment".to_string()))
4239                }
4240                ResolvedToken::Tk{code:CommandCode::Space,..} => (),
4241                /*ResolvedToken::Cmd {cmd:Some(Command::Unexpandable(Unexpandable {name,..})),..}
4242                    if *name == PRIMITIVES.crcr => (),*/
4243                ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,..})) if *name == PRIMITIVES.omit => {
4244                    engine.gullet.get_align_data().unwrap().omit = true;
4245                    super::methods::open_align_cell(engine,mode);
4246                    return Ok(())
4247                },
4248                ResolvedToken::Tk{..} | ResolvedToken::Cmd(_) => {
4249                    engine.requeue(token)?;
4250                    super::methods::open_align_cell(engine,mode);
4251                    return Ok(())
4252                }
4253            );
4254            super::methods::open_align_cell(engine, mode);
4255            Ok(())
4256        }
4257    }
4258}
4259
4260pub fn end_template_row<ET: EngineTypes>(
4261    engine: &mut EngineReferences<ET>,
4262    _tk: ET::Token,
4263) -> TeXResult<(), ET> {
4264    match engine.gullet.get_align_data() {
4265        None => unreachable!(),
4266        Some(data) => {
4267            data.omit = false;
4268            let skip = data.columns[data.currindex].tabskip;
4269            match engine.stomach.data_mut().open_lists.last_mut() {
4270                Some(NodeList::Horizontal { children, .. }) => {
4271                    children.push(HNode::HSkip(skip));
4272                }
4273                Some(NodeList::Vertical { children, .. }) => {
4274                    children.push(VNode::VSkip(skip));
4275                }
4276                _ => unreachable!(),
4277            }
4278            let mode = data.inner_mode;
4279            super::methods::pop_align_cell(
4280                engine.state,
4281                engine.aux,
4282                engine.stomach,
4283                engine.mouth,
4284                mode,
4285            );
4286            super::methods::pop_align_row::<ET>(engine.stomach, engine.mouth, mode);
4287            super::methods::start_align_row(engine, mode)
4288        }
4289    }
4290}
4291
4292pub fn register_tex_primitives<E: TeXEngine>(engine: &mut E) {
4293    register_int(engine, "catcode", catcode_get, Some(catcode_set));
4294    register_int(engine, "hyphenchar", hyphenchar_get, Some(hyphenchar_set));
4295    register_int(engine, "skewchar", skewchar_get, Some(skewchar_set));
4296    register_int(engine, "sfcode", sfcode_get, Some(sfcode_set));
4297    register_int(engine, "lccode", lccode_get, Some(lccode_set));
4298    register_int(engine, "uccode", uccode_get, Some(uccode_set));
4299    register_int(engine, "mathcode", mathcode_get, Some(mathcode_set));
4300    register_int(engine, "delcode", delcode_get, Some(delcode_set));
4301    register_int(engine, "count", count_get, Some(count_set));
4302    register_int(
4303        engine,
4304        "endlinechar",
4305        endlinechar_get,
4306        Some(endlinechar_set),
4307    );
4308    register_int(engine, "escapechar", escapechar_get, Some(escapechar_set));
4309    register_int(
4310        engine,
4311        "newlinechar",
4312        newlinechar_get,
4313        Some(newlinechar_set),
4314    );
4315    register_int(engine, "inputlineno", inputlineno, None);
4316    register_int(
4317        engine,
4318        "spacefactor",
4319        spacefactor_get,
4320        Some(spacefactor_set),
4321    );
4322    register_int(engine, "lastpenalty", lastpenalty, None);
4323    register_int(engine, "parshape", parshape_get, Some(parshape_set));
4324    register_int(engine, "deadcycles", deadcycles_get, Some(deadcycles_set));
4325    register_int(engine, "prevgraf", prevgraf_get, Some(prevgraf_set));
4326
4327    register_dim(engine, "fontdimen", fontdimen_get, Some(fontdimen_set));
4328    register_dim(engine, "dimen", dimen_get, Some(dimen_set));
4329    register_dim(engine, "prevdepth", prevdepth_get, Some(prevdepth_set));
4330    register_dim(engine, "dp", dp_get, Some(dp_set));
4331    register_dim(engine, "ht", ht_get, Some(ht_set));
4332    register_dim(engine, "wd", wd_get, Some(wd_set));
4333    register_dim(engine, "lastkern", lastkern, None);
4334    register_dim(engine, "pagegoal", pagegoal_get, Some(pagegoal_set));
4335    register_dim(engine, "pagetotal", pagetotal_get, Some(pagegoal_set));
4336    register_dim(
4337        engine,
4338        "pagestretch",
4339        pagestretch_get,
4340        Some(pagestretch_set),
4341    );
4342    register_dim(
4343        engine,
4344        "pagefilstretch",
4345        pagefilstretch_get,
4346        Some(pagefilstretch_set),
4347    );
4348    register_dim(
4349        engine,
4350        "pagefillstretch",
4351        pagefillstretch_get,
4352        Some(pagefillstretch_set),
4353    );
4354    register_dim(engine, "pageshrink", pageshrink_get, Some(pageshrink_set));
4355    register_dim(
4356        engine,
4357        "pagefilshrink",
4358        pagefilshrink_get,
4359        Some(pagefilshrink_set),
4360    );
4361    register_dim(
4362        engine,
4363        "pagefillshrink",
4364        pagefillshrink_get,
4365        Some(pagefillshrink_set),
4366    );
4367    register_dim(engine, "pagedepth", pagedepth_get, Some(pagedepth_set));
4368
4369    register_skip(engine, "skip", skip_get, Some(skip_set));
4370    register_skip(engine, "lastskip", lastskip, None);
4371
4372    register_muskip(engine, "muskip", muskip_get, Some(muskip_set));
4373
4374    register_font(engine, "font", font_get, Some(font_set));
4375    register_font(engine, "textfont", textfont_get, Some(textfont_set));
4376    register_font(engine, "scriptfont", scriptfont_get, Some(scriptfont_set));
4377    register_font(
4378        engine,
4379        "scriptscriptfont",
4380        scriptscriptfont_get,
4381        Some(scriptscriptfont_set),
4382    );
4383
4384    register_assignment(engine, "advance", advance);
4385    register_assignment(engine, "chardef", chardef);
4386    register_assignment(engine, "countdef", countdef);
4387    register_assignment(engine, "dimendef", dimendef);
4388    register_assignment(engine, "skipdef", skipdef);
4389    register_assignment(engine, "muskipdef", muskipdef);
4390    register_assignment(engine, "toksdef", toksdef);
4391    register_assignment(engine, "def", |e, cmd, g| {
4392        def(e, cmd, false, false, false, g)
4393    });
4394    register_assignment(engine, "divide", divide);
4395    register_assignment(engine, "edef", |e, cmd, g| {
4396        edef(e, cmd, false, false, false, g)
4397    });
4398    register_assignment(engine, "xdef", |e, cmd, g| {
4399        xdef(e, cmd, false, false, false, g)
4400    });
4401    register_assignment(engine, "gdef", |e, cmd, g| {
4402        gdef(e, cmd, false, false, false, g)
4403    });
4404    register_assignment(engine, "outer", |e, cmd, g| {
4405        outer(e, cmd, false, false, false, g)
4406    });
4407    register_assignment(engine, "long", |e, cmd, g| {
4408        long(e, cmd, false, false, false, g)
4409    });
4410    register_assignment(engine, "global", |e, cmd, g| {
4411        global(e, cmd, false, false, false, g)
4412    });
4413    register_assignment(engine, "let", let_);
4414    register_assignment(engine, "futurelet", futurelet);
4415    register_assignment(engine, "mathchardef", mathchardef);
4416    register_assignment(engine, "multiply", multiply);
4417    register_assignment(engine, "read", read);
4418    register_assignment(engine, "setbox", setbox);
4419    register_assignment(engine, "toks", toks);
4420
4421    register_simple_expandable(engine, "csname", csname);
4422    register_simple_expandable(engine, "else", r#else);
4423    register_simple_expandable(engine, "endinput", endinput);
4424    register_simple_expandable(engine, "or", or);
4425    register_simple_expandable(engine, "expandafter", expandafter);
4426    register_simple_expandable(engine, "fi", fi);
4427    register_simple_expandable(engine, "input", input);
4428    register_simple_expandable(engine, "noexpand", noexpand);
4429
4430    register_expandable(engine, "jobname", jobname);
4431    register_expandable(engine, "fontname", fontname);
4432    register_expandable(engine, "meaning", meaning);
4433    register_expandable(engine, "number", number);
4434    register_expandable(engine, "romannumeral", romannumeral);
4435    register_expandable(engine, "string", string);
4436    register_expandable(engine, "the", the);
4437    register_expandable(engine, "topmark", topmark);
4438    register_expandable(engine, "firstmark", firstmark);
4439    register_expandable(engine, "botmark", botmark);
4440    register_expandable(engine, "splitfirstmark", splitfirstmark);
4441    register_expandable(engine, "splitbotmark", splitbotmark);
4442
4443    register_conditional(engine, "if", r#if);
4444    register_conditional(engine, "ifcase", ifcase);
4445    register_conditional(engine, "ifcat", ifcat);
4446    register_conditional(engine, "ifdim", ifdim);
4447    register_conditional(engine, "ifeof", ifeof);
4448    register_conditional(engine, "ifhbox", ifhbox);
4449    register_conditional(engine, "ifhmode", ifhmode);
4450    register_conditional(engine, "ifinner", ifinner);
4451    register_conditional(engine, "ifmmode", ifmmode);
4452    register_conditional(engine, "ifnum", ifnum);
4453    register_conditional(engine, "ifodd", ifodd);
4454    register_conditional(engine, "ifx", ifx);
4455    register_conditional(engine, "iftrue", iftrue);
4456    register_conditional(engine, "iffalse", iffalse);
4457    register_conditional(engine, "ifvbox", ifvbox);
4458    register_conditional(engine, "ifvmode", ifvmode);
4459    register_conditional(engine, "ifvoid", ifvoid);
4460
4461    register_unexpandable(
4462        engine,
4463        "afterassignment",
4464        CommandScope::Any,
4465        afterassignment,
4466    );
4467    register_unexpandable(engine, "aftergroup", CommandScope::Any, aftergroup);
4468    register_unexpandable(engine, "begingroup", CommandScope::Any, begingroup);
4469    register_unexpandable(engine, "closein", CommandScope::Any, closein);
4470    register_unexpandable(
4471        engine,
4472        "char",
4473        CommandScope::SwitchesToHorizontalOrMath,
4474        r#char,
4475    );
4476    register_unexpandable(engine, "discretionary", CommandScope::Any, discretionary);
4477    register_unexpandable(engine, "dump", CommandScope::Any, |_, _| Ok(()));
4478    register_unexpandable(engine, "endcsname", CommandScope::Any, endcsname);
4479    register_unexpandable(engine, "endgroup", CommandScope::Any, endgroup);
4480    register_unexpandable(engine, "end", CommandScope::Any, end);
4481    register_unexpandable(engine, "errorstopmode", CommandScope::Any, errorstopmode);
4482    register_unexpandable(engine, "halign", CommandScope::Any, halign);
4483    register_unexpandable(engine, "valign", CommandScope::SwitchesToHorizontal, valign);
4484    register_unexpandable(engine, "hyphenation", CommandScope::Any, |e, t| {
4485        e.skip_argument(&t)
4486    });
4487    register_unexpandable(engine, "ignorespaces", CommandScope::Any, ignorespaces);
4488    register_unexpandable(engine, "insert", CommandScope::Any, insert);
4489    register_unexpandable(engine, "immediate", CommandScope::Any, immediate);
4490    register_unexpandable(engine, "lowercase", CommandScope::Any, lowercase);
4491    register_unexpandable(engine, "uppercase", CommandScope::Any, uppercase);
4492    register_unexpandable(engine, "leaders", CommandScope::Any, leaders);
4493    register_unexpandable(engine, "xleaders", CommandScope::Any, xleaders);
4494    register_unexpandable(engine, "cleaders", CommandScope::Any, cleaders);
4495    register_unexpandable(engine, "message", CommandScope::Any, message);
4496    register_unexpandable(engine, "errmessage", CommandScope::Any, errmessage);
4497    register_unexpandable(engine, "noindent", CommandScope::Any, noindent);
4498    register_unexpandable(engine, "openin", CommandScope::Any, openin);
4499    register_unexpandable(engine, "par", CommandScope::Any, par);
4500    register_unexpandable(
4501        engine,
4502        "unhbox",
4503        CommandScope::SwitchesToHorizontalOrMath,
4504        unhbox,
4505    );
4506    register_unexpandable(engine, "unvbox", CommandScope::SwitchesToVertical, unvbox);
4507    register_unexpandable(
4508        engine,
4509        "unhcopy",
4510        CommandScope::SwitchesToHorizontalOrMath,
4511        unhcopy,
4512    );
4513    register_unexpandable(engine, "unvcopy", CommandScope::SwitchesToVertical, unvcopy);
4514    register_unexpandable(engine, "unskip", CommandScope::Any, unskip);
4515    register_unexpandable(engine, "unkern", CommandScope::Any, unkern);
4516    register_unexpandable(engine, "unpenalty", CommandScope::Any, unpenalty);
4517    register_unexpandable(
4518        engine,
4519        "moveleft",
4520        CommandScope::SwitchesToVertical,
4521        moveleft,
4522    );
4523    register_unexpandable(
4524        engine,
4525        "moveright",
4526        CommandScope::SwitchesToVertical,
4527        moveright,
4528    );
4529    register_unexpandable(
4530        engine,
4531        "raise",
4532        CommandScope::SwitchesToHorizontalOrMath,
4533        raise,
4534    );
4535    register_unexpandable(
4536        engine,
4537        "lower",
4538        CommandScope::SwitchesToHorizontalOrMath,
4539        lower,
4540    );
4541    register_unexpandable(engine, "shipout", CommandScope::Any, shipout);
4542    register_unexpandable(engine, "patterns", CommandScope::Any, |e, t| {
4543        e.skip_argument(&t)
4544    });
4545    register_unexpandable(
4546        engine,
4547        "vadjust",
4548        CommandScope::SwitchesToHorizontalOrMath,
4549        vadjust,
4550    );
4551    {
4552        let refs = engine.get_engine_refs();
4553        let relax = refs.aux.memory.cs_interner_mut().cs_from_str("relax");
4554        let nullfont = refs.aux.memory.cs_interner_mut().cs_from_str("nullfont");
4555        refs.state.set_command(
4556            refs.aux,
4557            relax,
4558            Some(TeXCommand::Primitive {
4559                name: PRIMITIVES.relax,
4560                cmd: PrimitiveCommand::Relax,
4561            }),
4562            true,
4563        );
4564        refs.state.set_command(
4565            refs.aux,
4566            nullfont,
4567            Some(TeXCommand::Font(refs.fontsystem.null())),
4568            true,
4569        )
4570    }
4571    register_unexpandable(engine, "mark", CommandScope::Any, mark);
4572    register_unexpandable(engine, "/", CommandScope::Any, char_slash);
4573    register_unexpandable(engine, "-", CommandScope::Any, char_dash);
4574    register_unexpandable(engine, "showlists", CommandScope::Any, |_, _| Ok(())); // TODO
4575    register_unexpandable(engine, "crcr", CommandScope::Any, |_, _| Ok(()));
4576    register_unexpandable(engine, "cr", CommandScope::Any, |_, _| {
4577        Err(TeXError::General("Unexpected \\cr".to_string()))
4578    });
4579    register_unexpandable(engine, END_TEMPLATE, CommandScope::Any, end_template);
4580    register_unexpandable(
4581        engine,
4582        END_TEMPLATE_ROW,
4583        CommandScope::Any,
4584        end_template_row,
4585    );
4586
4587    register_unexpandable(engine, "delimiter", CommandScope::MathOnly, delimiter);
4588    register_unexpandable(engine, "mskip", CommandScope::MathOnly, mskip);
4589    register_unexpandable(engine, "mkern", CommandScope::MathOnly, mkern);
4590    register_unexpandable(engine, "mathchar", CommandScope::MathOnly, mathchar);
4591    register_unexpandable(engine, "left", CommandScope::MathOnly, left);
4592    register_unexpandable(engine, "right", CommandScope::MathOnly, right);
4593    register_unexpandable(
4594        engine,
4595        "displaylimits",
4596        CommandScope::MathOnly,
4597        displaylimits,
4598    );
4599    register_unexpandable(engine, "limits", CommandScope::MathOnly, limits);
4600    register_unexpandable(engine, "nolimits", CommandScope::MathOnly, nolimits);
4601    register_unexpandable(engine, "penalty", CommandScope::Any, penalty);
4602    register_unexpandable(engine, "kern", CommandScope::Any, kern);
4603    register_unexpandable(
4604        engine,
4605        "vrule",
4606        CommandScope::SwitchesToHorizontalOrMath,
4607        vrule,
4608    );
4609    register_unexpandable(engine, "vskip", CommandScope::SwitchesToVertical, vskip);
4610    register_unexpandable(engine, "vfil", CommandScope::SwitchesToVertical, vfil);
4611    register_unexpandable(engine, "vfill", CommandScope::SwitchesToVertical, vfill);
4612    register_unexpandable(engine, "vfilneg", CommandScope::SwitchesToVertical, vfilneg);
4613    register_unexpandable(engine, "vss", CommandScope::SwitchesToVertical, vss);
4614    register_unexpandable(engine, "hrule", CommandScope::SwitchesToVertical, hrule);
4615    register_unexpandable(
4616        engine,
4617        "hskip",
4618        CommandScope::SwitchesToHorizontalOrMath,
4619        hskip,
4620    );
4621    register_unexpandable(
4622        engine,
4623        "hfil",
4624        CommandScope::SwitchesToHorizontalOrMath,
4625        hfil,
4626    );
4627    register_unexpandable(
4628        engine,
4629        "hfill",
4630        CommandScope::SwitchesToHorizontalOrMath,
4631        hfill,
4632    );
4633    register_unexpandable(
4634        engine,
4635        "hfilneg",
4636        CommandScope::SwitchesToHorizontalOrMath,
4637        hfilneg,
4638    );
4639    register_unexpandable(engine, "hss", CommandScope::SwitchesToHorizontalOrMath, hss);
4640    register_unexpandable(engine, "indent", CommandScope::SwitchesToHorizontal, indent);
4641    register_unexpandable(
4642        engine,
4643        " ",
4644        CommandScope::SwitchesToHorizontalOrMath,
4645        char_space,
4646    );
4647    register_unexpandable(engine, "vcenter", CommandScope::MathOnly, vcenter);
4648    register_unexpandable(engine, "accent", CommandScope::SwitchesToHorizontal, accent);
4649
4650    register_unexpandable(engine, "mathord", CommandScope::MathOnly, mathord);
4651    register_unexpandable(engine, "mathop", CommandScope::MathOnly, mathop);
4652    register_unexpandable(engine, "mathbin", CommandScope::MathOnly, mathbin);
4653    register_unexpandable(engine, "mathrel", CommandScope::MathOnly, mathrel);
4654    register_unexpandable(engine, "mathopen", CommandScope::MathOnly, mathopen);
4655    register_unexpandable(engine, "mathclose", CommandScope::MathOnly, mathclose);
4656    register_unexpandable(engine, "mathpunct", CommandScope::MathOnly, mathpunct);
4657    register_unexpandable(engine, "mathinner", CommandScope::MathOnly, mathinner);
4658    register_unexpandable(engine, "mathchoice", CommandScope::MathOnly, mathchoice);
4659    register_unexpandable(engine, "underline", CommandScope::MathOnly, underline);
4660    register_unexpandable(engine, "overline", CommandScope::MathOnly, overline);
4661    register_unexpandable(engine, "mathaccent", CommandScope::MathOnly, mathaccent);
4662    register_unexpandable(engine, "radical", CommandScope::MathOnly, radical);
4663    register_unexpandable(engine, "eqno", CommandScope::MathOnly, eqno);
4664    register_unexpandable(engine, "leqno", CommandScope::MathOnly, leqno);
4665
4666    register_unexpandable(engine, "over", CommandScope::MathOnly, over);
4667    register_unexpandable(
4668        engine,
4669        "overwithdelims",
4670        CommandScope::MathOnly,
4671        overwithdelims,
4672    );
4673    register_unexpandable(engine, "above", CommandScope::MathOnly, above);
4674    register_unexpandable(
4675        engine,
4676        "abovewithdelims",
4677        CommandScope::MathOnly,
4678        abovewithdelims,
4679    );
4680    register_unexpandable(engine, "atop", CommandScope::MathOnly, atop);
4681    register_unexpandable(
4682        engine,
4683        "atopwithdelims",
4684        CommandScope::MathOnly,
4685        atopwithdelims,
4686    );
4687
4688    register_unexpandable(engine, "displaystyle", CommandScope::MathOnly, displaystyle);
4689    register_unexpandable(engine, "textstyle", CommandScope::MathOnly, textstyle);
4690    register_unexpandable(engine, "scriptstyle", CommandScope::MathOnly, scriptstyle);
4691    register_unexpandable(
4692        engine,
4693        "scriptscriptstyle",
4694        CommandScope::MathOnly,
4695        scriptscriptstyle,
4696    );
4697
4698    register_unexpandable(engine, "nonscript", CommandScope::MathOnly, |_, _| Ok(())); // TODO
4699
4700    register_whatsit(engine, "closeout", closeout, closeout_immediate, None);
4701    register_whatsit(engine, "openout", openout, openout_immediate, None);
4702    register_whatsit(engine, "write", write, write_immediate, None);
4703
4704    register_box(engine, "hbox", hbox);
4705    register_box(engine, "vbox", vbox);
4706    register_box(engine, "vtop", vtop);
4707    register_box(engine, "box", box_);
4708    register_box(engine, "copy", copy);
4709    register_box(engine, "lastbox", lastbox);
4710    register_box(engine, "vsplit", vsplit);
4711
4712    cmstodos!(engine, noalign, omit, span);
4713
4714    cmtodos!(
4715        engine,
4716        scrollmode,
4717        nonstopmode,
4718        batchmode,
4719        show,
4720        showbox,
4721        showthe,
4722        noboundary,
4723        setlanguage,
4724        bye,
4725        italiccorr
4726    );
4727
4728    register_primitive_int(engine, PRIMITIVE_INTS);
4729    register_primitive_dim(engine, PRIMITIVE_DIMS);
4730    register_primitive_skip(engine, PRIMITIVE_SKIPS);
4731    register_primitive_muskip(engine, PRIMITIVE_MUSKIPS);
4732    register_primitive_toks(engine, PRIMITIVE_TOKS);
4733
4734    register_unexpandable(engine, "special", CommandScope::Any, |e, t| {
4735        e.skip_argument(&t)
4736    });
4737}