Skip to main content

tex_engine/engine/
stomach.rs

1pub mod methods;
2
3use crate::commands::primitives::{PrimitiveIdentifier, PRIMITIVES};
4use crate::commands::TeXCommand;
5use crate::commands::{CharOrPrimitive, CommandScope, PrimitiveCommand, ResolvedToken};
6use crate::engine::filesystem::File;
7use crate::engine::filesystem::SourceReference;
8use crate::engine::gullet::Gullet;
9use crate::engine::mouth::Mouth;
10use crate::engine::state::{GroupType, State};
11use crate::engine::stomach::methods::{ParLine, ParLineSpec, SplitResult};
12use crate::engine::utils::outputs::Outputs;
13use crate::engine::{EngineAux, EngineReferences, EngineTypes};
14use crate::tex::catcodes::CommandCode;
15use crate::tex::nodes::boxes::{BoxInfo, BoxType, HBoxInfo, TeXBox, ToOrSpread};
16use crate::tex::nodes::horizontal::{HNode, HorizontalNodeListType};
17use crate::tex::nodes::math::{
18    Delimiter, MathAtom, MathChar, MathClass, MathKernel, MathNode, MathNodeList, MathNodeListType,
19    MathNucleus, UnresolvedMathFontStyle,
20};
21use crate::tex::nodes::vertical::{VNode, VerticalNodeListType};
22use crate::tex::nodes::{BoxTarget, ListTarget, NodeList, WhatsitFunction, WhatsitNode};
23use crate::tex::numerics::{Skip, TeXDimen};
24use crate::tex::tokens::token_lists::TokenList;
25use crate::tex::tokens::{StandardToken, Token};
26use crate::utils::errors::{TeXError, TeXResult};
27use crate::utils::HMap;
28use either::Either;
29use std::fmt::Display;
30
31/// The mode the engine is currently in, e.g. horizontal mode or vertical mode.
32#[derive(Clone, Copy, Eq, PartialEq, Debug)]
33pub enum TeXMode {
34    /// The mode the engine is in at the start of a document, outside of boxes or paragraphs
35    Vertical,
36    /// The mode the engine is in inside a vertical box
37    InternalVertical,
38    /// The mode the engine is in inside a paragraph
39    Horizontal,
40    /// The mode the engine is in inside a horizontal box
41    RestrictedHorizontal,
42    /// The mode the engine is in inside an inline math box
43    InlineMath,
44    /// The mode the engine is in inside a display math box
45    DisplayMath,
46}
47impl Display for TeXMode {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        match self {
50            TeXMode::Vertical => write!(f, "vertical"),
51            TeXMode::InternalVertical => write!(f, "internal vertical"),
52            TeXMode::Horizontal => write!(f, "horizontal"),
53            TeXMode::RestrictedHorizontal => write!(f, "restricted horizontal"),
54            TeXMode::InlineMath => write!(f, "inline math"),
55            TeXMode::DisplayMath => write!(f, "display math"),
56        }
57    }
58}
59impl TeXMode {
60    /// Returns true if the mode is vertical or internal vertical
61    pub fn is_vertical(&self) -> bool {
62        matches!(self, TeXMode::Vertical | TeXMode::InternalVertical)
63    }
64    /// Returns true if the mode is horizontal or restricted horizontal
65    pub fn is_horizontal(&self) -> bool {
66        matches!(self, TeXMode::Horizontal | TeXMode::RestrictedHorizontal)
67    }
68    /// Returns true if the mode is inline math or display math
69    pub fn is_math(&self) -> bool {
70        matches!(self, TeXMode::InlineMath | TeXMode::DisplayMath)
71    }
72    /// Returns true if the mode is horizontal, restricted horizontal, inline math, or display math
73    pub fn h_or_m(&self) -> bool {
74        matches!(
75            self,
76            TeXMode::Horizontal
77                | TeXMode::RestrictedHorizontal
78                | TeXMode::InlineMath
79                | TeXMode::DisplayMath
80        )
81    }
82}
83impl From<BoxType> for TeXMode {
84    fn from(bt: BoxType) -> Self {
85        match bt {
86            BoxType::Horizontal => TeXMode::RestrictedHorizontal,
87            BoxType::Vertical => TeXMode::InternalVertical,
88        }
89    }
90}
91
92/// The [`Stomach`] is the part of the engine that processes (unexpandable)
93/// commands, collects [nodes](crate::tex::nodes::NodeTrait) in (horizontal or vertical) lists, and builds pages.
94///
95/// The vast majority of the methods implemented by this trait take a [`EngineReferences`] as their first argument
96/// and have a default implementation already.
97/// As such, we could have attached them to [`EngineReferences`] directly, but we put them here in a
98/// separate trait instead so we can overwrite the methods easily - e.g. add `trigger` code when a paragraph is opened/closed etc.
99pub trait Stomach<ET: EngineTypes /*<Stomach = Self>*/> {
100    /// Constructs a new [`Stomach`].
101    fn new(aux: &mut EngineAux<ET>, state: &mut ET::State) -> Self;
102    /// Mutable reference to the current `\afterassignment` [`Token`].
103    fn afterassignment(&mut self) -> &mut Option<ET::Token>;
104    /// The current list(s)
105    fn data_mut(&mut self) -> &mut StomachData<ET>;
106    /// To be executed at every iteration of the top-level loop - i.e. in between all unexpandable commands
107    #[inline]
108    fn every_top(engine: &mut EngineReferences<ET>) {
109        engine.mouth.update_start_ref();
110    }
111    /// To be executed at the end of a document - flushes the current page
112    fn flush(engine: &mut EngineReferences<ET>) -> TeXResult<(), ET> {
113        use crate::engine::utils::outputs::Outputs;
114        let open_groups = std::mem::take(&mut engine.stomach.data_mut().open_lists);
115        if !open_groups.is_empty() {
116            engine.aux.outputs.message(format_args!(
117                "(\\end occurred inside a group at level {})",
118                engine.state.get_group_level()
119            ));
120            if let Some(g) = engine.state.get_group_type() {
121                engine.aux.outputs.message(format_args!("## {} group", g))
122            }
123            while engine.state.get_group_level() > 0 {
124                engine.state.pop(engine.aux, engine.mouth);
125            }
126        }
127        Self::add_node_v(engine, VNode::Penalty(-10000))?;
128        engine.stomach.data_mut().page.clear();
129        Ok(())
130    }
131    /// Execute the provided [Unexpandable](PrimitiveCommand::Unexpandable) command
132    fn do_unexpandable(
133        engine: &mut EngineReferences<ET>,
134        name: PrimitiveIdentifier,
135        scope: CommandScope,
136        token: ET::Token,
137        apply: fn(&mut EngineReferences<ET>, ET::Token) -> TeXResult<(), ET>,
138    ) -> TeXResult<(), ET> {
139        if Self::maybe_switch_mode(engine, scope, token.clone(), name)? {
140            engine.trace_command(|engine| name.display(engine.state.get_escape_char()));
141            apply(engine, token)
142        } else {
143            Ok(())
144        }
145    }
146
147    /// Execute the provided [Assignment](PrimitiveCommand::Assignment) command and insert `\afterassignment` if necessary
148    fn do_assignment(
149        engine: &mut EngineReferences<ET>,
150        name: PrimitiveIdentifier,
151        token: ET::Token,
152        assign: fn(&mut EngineReferences<ET>, ET::Token, bool) -> TeXResult<(), ET>,
153        global: bool,
154    ) -> TeXResult<(), ET> {
155        engine.trace_command(|engine| name.display(engine.state.get_escape_char()));
156        assign(engine, token, global)?;
157        methods::insert_afterassignment(engine);
158        Ok(())
159    }
160
161    /// Execute the provided [Font](TeXCommand::Font) assignment and insert `\afterassignment` if necessary
162    fn assign_font(
163        engine: &mut EngineReferences<ET>,
164        _token: ET::Token,
165        f: ET::Font,
166        global: bool,
167    ) -> TeXResult<(), ET> {
168        engine.state.set_current_font(engine.aux, f, global);
169        methods::insert_afterassignment(engine);
170        Ok(())
171    }
172    /// Assign a value to a [count register](TeXCommand::IntRegister) and insert `\afterassignment` if necessary
173    fn assign_int_register(
174        engine: &mut EngineReferences<ET>,
175        register: usize,
176        global: bool,
177        in_token: ET::Token,
178    ) -> TeXResult<(), ET> {
179        let val = engine.read_int(true, &in_token)?;
180        engine
181            .state
182            .set_int_register(engine.aux, register, val, global);
183        methods::insert_afterassignment(engine);
184        Ok(())
185    }
186    /// Assign a value to a [dimen register](TeXCommand::DimRegister) and insert `\afterassignment` if necessary
187    fn assign_dim_register(
188        engine: &mut EngineReferences<ET>,
189        register: usize,
190        global: bool,
191        in_token: ET::Token,
192    ) -> TeXResult<(), ET> {
193        let val = engine.read_dim(true, &in_token)?;
194        engine
195            .state
196            .set_dim_register(engine.aux, register, val, global);
197        methods::insert_afterassignment(engine);
198        Ok(())
199    }
200    /// Assign a value to a [skip register](TeXCommand::SkipRegister) and insert `\afterassignment` if necessary
201    fn assign_skip_register(
202        engine: &mut EngineReferences<ET>,
203        register: usize,
204        global: bool,
205        in_token: ET::Token,
206    ) -> TeXResult<(), ET> {
207        let val = engine.read_skip(true, &in_token)?;
208        engine
209            .state
210            .set_skip_register(engine.aux, register, val, global);
211        methods::insert_afterassignment(engine);
212        Ok(())
213    }
214    /// Assign a value to a [muskip register](TeXCommand::MuSkipRegister) and insert `\afterassignment` if necessary
215    fn assign_muskip_register(
216        engine: &mut EngineReferences<ET>,
217        register: usize,
218        global: bool,
219        in_token: ET::Token,
220    ) -> TeXResult<(), ET> {
221        let val = engine.read_muskip(true, &in_token)?;
222        engine
223            .state
224            .set_muskip_register(engine.aux, register, val, global);
225        methods::insert_afterassignment(engine);
226        Ok(())
227    }
228    /// Assign a value to a [token register](TeXCommand::ToksRegister) and insert `\afterassignment` if necessary
229    #[inline]
230    fn assign_toks_register(
231        engine: &mut EngineReferences<ET>,
232        token: ET::Token,
233        register: usize,
234        global: bool,
235    ) -> TeXResult<(), ET> {
236        methods::assign_toks_register(engine, token, register, global)
237    }
238    /// Assign a value to a [primitive token list](PrimitiveCommand::PrimitiveToks) and insert `\afterassignment` if necessary
239    #[inline]
240    fn assign_primitive_toks(
241        engine: &mut EngineReferences<ET>,
242        token: ET::Token,
243        name: PrimitiveIdentifier,
244        global: bool,
245    ) -> TeXResult<(), ET> {
246        methods::assign_primitive_toks(engine, token, name, global)
247    }
248    /// Assign a value to a [primitive integer value](PrimitiveCommand::PrimitiveInt) and insert `\afterassignment` if necessary
249    fn assign_primitive_int(
250        engine: &mut EngineReferences<ET>,
251        name: PrimitiveIdentifier,
252        global: bool,
253        in_token: ET::Token,
254    ) -> TeXResult<(), ET> {
255        engine.trace_command(|engine| format!("{}", name.display(engine.state.get_escape_char())));
256        let val = engine.read_int(true, &in_token)?;
257        engine
258            .state
259            .set_primitive_int(engine.aux, name, val, global);
260        methods::insert_afterassignment(engine);
261        Ok(())
262    }
263    /// Assign a value to a [primitive dimension value](PrimitiveCommand::PrimitiveDim) and insert `\afterassignment` if necessary
264    fn assign_primitive_dim(
265        engine: &mut EngineReferences<ET>,
266        name: PrimitiveIdentifier,
267        global: bool,
268        in_token: ET::Token,
269    ) -> TeXResult<(), ET> {
270        engine.trace_command(|engine| format!("{}", name.display(engine.state.get_escape_char())));
271        let val = engine.read_dim(true, &in_token)?;
272        engine
273            .state
274            .set_primitive_dim(engine.aux, name, val, global);
275        methods::insert_afterassignment(engine);
276        Ok(())
277    }
278    /// Assign a value to a [primitive skip value](PrimitiveCommand::PrimitiveSkip) and insert `\afterassignment` if necessary
279    fn assign_primitive_skip(
280        engine: &mut EngineReferences<ET>,
281        name: PrimitiveIdentifier,
282        global: bool,
283        in_token: ET::Token,
284    ) -> TeXResult<(), ET> {
285        engine.trace_command(|engine| format!("{}", name.display(engine.state.get_escape_char())));
286        let val = engine.read_skip(true, &in_token)?;
287        engine
288            .state
289            .set_primitive_skip(engine.aux, name, val, global);
290        methods::insert_afterassignment(engine);
291        Ok(())
292    }
293    /// Assign a value to a [primitive muskip value](PrimitiveCommand::PrimitiveMuSkip) and insert `\afterassignment` if necessary
294    fn assign_primitive_muskip(
295        engine: &mut EngineReferences<ET>,
296        name: PrimitiveIdentifier,
297        global: bool,
298        in_token: ET::Token,
299    ) -> TeXResult<(), ET> {
300        engine.trace_command(|engine| format!("{}", name.display(engine.state.get_escape_char())));
301        let val = engine.read_muskip(true, &in_token)?;
302        engine
303            .state
304            .set_primitive_muskip(engine.aux, name, val, global);
305        methods::insert_afterassignment(engine);
306        Ok(())
307    }
308    /// Executes a [Whatsit](PrimitiveCommand::Whatsit) command
309    fn do_whatsit(
310        engine: &mut EngineReferences<ET>,
311        name: PrimitiveIdentifier,
312        token: ET::Token,
313        read: fn(
314            &mut EngineReferences<ET>,
315            ET::Token,
316        ) -> TeXResult<Option<Box<WhatsitFunction<ET>>>, ET>,
317    ) -> TeXResult<(), ET> {
318        if let Some(ret) = read(engine, token)? {
319            let wi = WhatsitNode::new(ret, name);
320            match engine.stomach.data_mut().mode() {
321                TeXMode::Vertical | TeXMode::InternalVertical => {
322                    Self::add_node_v(engine, VNode::Whatsit(wi))?
323                }
324                TeXMode::Horizontal | TeXMode::RestrictedHorizontal => {
325                    Self::add_node_h(engine, HNode::Whatsit(wi))
326                }
327                TeXMode::InlineMath | TeXMode::DisplayMath => {
328                    Self::add_node_m(engine, MathNode::Whatsit(wi))
329                }
330            }
331        }
332        Ok(())
333    }
334    /// Executes a [Box](PrimitiveCommand::Box) command
335    fn do_box(
336        engine: &mut EngineReferences<ET>,
337        _name: PrimitiveIdentifier,
338        token: ET::Token,
339        bx: fn(
340            &mut EngineReferences<ET>,
341            ET::Token,
342        ) -> TeXResult<Either<Option<TeXBox<ET>>, BoxInfo<ET>>, ET>,
343    ) -> TeXResult<(), ET> {
344        match bx(engine, token)? {
345            either::Left(Some(bx)) => methods::add_box(engine, bx, BoxTarget::none()),
346            either::Left(None) => Ok(()),
347            either::Right(bi) => {
348                engine
349                    .stomach
350                    .data_mut()
351                    .open_lists
352                    .push(bi.open_list(engine.mouth.start_ref()));
353                Ok(())
354            }
355        }
356    }
357
358    /// Processes a character depending on the current [`TeXMode`] and its [`CommandCode`]
359    fn do_char(
360        engine: &mut EngineReferences<ET>,
361        token: ET::Token,
362        char: ET::Char,
363        code: CommandCode,
364    ) -> TeXResult<(), ET> {
365        methods::do_char(engine, token, char, code)
366    }
367
368    /// Processes a character depending on the current [`TeXMode`] and its [`CommandCode`]
369    fn do_defed_char(
370        engine: &mut EngineReferences<ET>,
371        token: ET::Token,
372        char: ET::Char,
373    ) -> TeXResult<(), ET> {
374        if engine.stomach.data_mut().mode().is_math() {
375            Self::do_char_in_math(engine, char)
376        } else {
377            methods::do_char(engine, token, char, CommandCode::Other)
378        }
379    }
380    fn do_char_in_math(engine: &mut EngineReferences<ET>, char: ET::Char) -> TeXResult<(), ET> {
381        ET::Stomach::add_node_m(
382            engine,
383            MathNode::Atom(MathAtom {
384                sup: None,
385                sub: None,
386                nucleus: MathNucleus::Simple {
387                    cls: MathClass::Ord,
388                    limits: None,
389                    kernel: MathKernel::Char {
390                        char,
391                        style: UnresolvedMathFontStyle::of_fam(0),
392                    },
393                },
394            }),
395        );
396        Ok(())
397    }
398    /// Processes a mathchar value (assumes we are in math mode)
399    fn do_mathchar(engine: &mut EngineReferences<ET>, code: u32, token: Option<ET::Token>) {
400        let ret = match token.map(|t| t.to_enum()) {
401            Some(StandardToken::Character(char, _)) if code == 32768 => {
402                engine
403                    .mouth
404                    .requeue(ET::Token::from_char_cat(char, CommandCode::Active));
405                return;
406            }
407            Some(StandardToken::Character(char, _)) => {
408                MathChar::from_u32(code, engine.state, Some(char))
409            }
410            _ => MathChar::from_u32(code, engine.state, None),
411        };
412        ET::Stomach::add_node_m(engine, MathNode::Atom(ret.to_atom()));
413    }
414
415    /// Closes a node list belonging to a [`TeXBox`] and adds it to the
416    /// corresponding node list
417    fn close_box(engine: &mut EngineReferences<ET>, bt: BoxType) -> TeXResult<(), ET> {
418        methods::close_box(engine, bt)
419    }
420
421    /// Switches the current [`TeXMode`] (if necessary) by opening/closing a paragraph, or throws an error
422    /// if neither action is possible or would not result in a compatible mode.
423    /// If a paragraph is opened or closed, the provided token is requeued to be reprocessed afterwards in
424    /// horizontal/vertical mode, and `false` is returned (as to not process the triggering command
425    /// further). Otherwise, all is well and `true` is returned.
426    fn maybe_switch_mode(
427        engine: &mut EngineReferences<ET>,
428        scope: CommandScope,
429        token: ET::Token,
430        name: PrimitiveIdentifier,
431    ) -> TeXResult<bool, ET> {
432        match (scope, engine.stomach.data_mut().mode()) {
433            (CommandScope::Any, _) => Ok(true),
434            (
435                CommandScope::SwitchesToHorizontal | CommandScope::SwitchesToHorizontalOrMath,
436                TeXMode::Horizontal | TeXMode::RestrictedHorizontal,
437            ) => Ok(true),
438            (CommandScope::SwitchesToVertical, TeXMode::Vertical | TeXMode::InternalVertical) => {
439                Ok(true)
440            }
441            (CommandScope::SwitchesToVertical, TeXMode::Horizontal) => {
442                engine.requeue(token)?;
443                Self::close_paragraph(engine)?;
444                Ok(false)
445            }
446            (
447                CommandScope::MathOnly | CommandScope::SwitchesToHorizontalOrMath,
448                TeXMode::InlineMath | TeXMode::DisplayMath,
449            ) => Ok(true),
450            (
451                CommandScope::SwitchesToHorizontal | CommandScope::SwitchesToHorizontalOrMath,
452                TeXMode::Vertical | TeXMode::InternalVertical,
453            ) => {
454                Self::open_paragraph(engine, token);
455                Ok(false)
456            }
457            (_, _) => {
458                TeXError::not_allowed_in_mode(
459                    engine.aux,
460                    engine.state,
461                    engine.mouth,
462                    name,
463                    engine.stomach.data_mut().mode(),
464                )?;
465                Ok(false)
466            }
467        }
468    }
469
470    /// Opens an `\halign` or `\valign`
471    fn open_align(engine: &mut EngineReferences<ET>, _inner: BoxType, between: BoxType) {
472        engine
473            .state
474            .push(engine.aux, GroupType::Align, engine.mouth.line_number());
475        engine
476            .stomach
477            .data_mut()
478            .open_lists
479            .push(if between == BoxType::Vertical {
480                NodeList::Vertical {
481                    tp: VerticalNodeListType::HAlign,
482                    children: vec![],
483                }
484            } else {
485                NodeList::Horizontal {
486                    tp: HorizontalNodeListType::VAlign,
487                    children: vec![],
488                }
489            });
490    }
491
492    /// Closes an `\halign` or `\valign`
493    fn close_align(engine: &mut EngineReferences<ET>) -> TeXResult<(), ET> {
494        match engine.stomach.data_mut().open_lists.pop() {
495            Some(NodeList::Vertical {
496                children,
497                tp: VerticalNodeListType::HAlign,
498            }) => {
499                engine.state.pop(engine.aux, engine.mouth);
500                match engine.stomach.data_mut().open_lists.last_mut() {
501                    Some(NodeList::Math { .. }) => {
502                        Self::add_node_m(
503                            engine,
504                            MathNode::Atom(MathAtom {
505                                nucleus: MathNucleus::VCenter {
506                                    children: children.into(),
507                                    start: engine.mouth.start_ref(),
508                                    end: engine.mouth.current_sourceref(),
509                                    scaled: ToOrSpread::None,
510                                },
511                                sup: None,
512                                sub: None,
513                            }),
514                        );
515                    }
516                    _ => {
517                        for c in children {
518                            Self::add_node_v(engine, c)?;
519                        }
520                    }
521                }
522            }
523            Some(NodeList::Horizontal {
524                children,
525                tp: HorizontalNodeListType::VAlign,
526            }) => {
527                engine.state.pop(engine.aux, engine.mouth);
528                for c in children {
529                    Self::add_node_h(engine, c);
530                }
531            }
532            _ => unreachable!("Stomach::close_align called outside of an align"),
533        };
534        Ok(())
535    }
536
537    /// Adds a node to the current math list (i.e. assumes we're in math mode)
538    fn add_node_m(
539        engine: &mut EngineReferences<ET>,
540        node: MathNode<ET, UnresolvedMathFontStyle<ET>>,
541    ) {
542        match engine.stomach.data_mut().open_lists.last_mut() {
543            Some(NodeList::Math { children, .. }) => {
544                children.push(node);
545            }
546            _ => unreachable!("Stomach::add_node_m called outside of math mode"),
547        }
548    }
549
550    /// Adds a node to the current horizontal list (i.e. assumes we're in (restricted) horizontal mode)
551    fn add_node_h(engine: &mut EngineReferences<ET>, node: HNode<ET>) {
552        if let HNode::Penalty(i) = node {
553            engine.stomach.data_mut().lastpenalty = i;
554        }
555        match engine.stomach.data_mut().open_lists.last_mut() {
556            Some(NodeList::Horizontal { children, .. }) => {
557                children.push(node);
558            }
559            _ => unreachable!("Stomach::add_node_h called outside of horizontal mode"),
560        }
561    }
562
563    /// Adds a node to the current vertical list (i.e. assumes we're in (internal) vertical mode)
564    #[inline]
565    fn add_node_v(engine: &mut EngineReferences<ET>, node: VNode<ET>) -> TeXResult<(), ET> {
566        methods::add_node_v(engine, node)
567    }
568
569    /// Checks whether the output routine should occur; either because the page is
570    /// full enough, or because the provided penalty is `Some`
571    /// (and assumed to be <= -10000) and the page is not empty.
572    fn maybe_do_output(
573        engine: &mut EngineReferences<ET>,
574        penalty: Option<i32>,
575    ) -> TeXResult<(), ET> {
576        let data = engine.stomach.data_mut();
577        if !data.in_output
578            && data.open_lists.is_empty()
579            && !data.page.is_empty()
580            && (data.pagetotal >= data.pagegoal || penalty.is_some())
581        {
582            Self::do_output(engine, penalty)
583        } else {
584            Ok(())
585        }
586    }
587
588    /// Actually calls the output routine
589    fn do_output(
590        engine: &mut EngineReferences<ET>,
591        caused_penalty: Option<i32>,
592    ) -> TeXResult<(), ET> {
593        methods::do_output(engine, caused_penalty)
594    }
595
596    /// Split a vertical list for the provided target height
597    fn split_vertical(
598        engine: &mut EngineReferences<ET>,
599        nodes: Vec<VNode<ET>>,
600        target: <ET as EngineTypes>::Dim,
601    ) -> SplitResult<ET> {
602        methods::vsplit_roughly(engine, nodes, target)
603    }
604
605    /// Open a new paragraph; assumed to be called in (internal) vertical mode
606    fn open_paragraph(engine: &mut EngineReferences<ET>, token: ET::Token) {
607        let sref = engine.mouth.start_ref();
608        let data = engine.stomach.data_mut();
609        data.prevgraf = 0;
610        data.open_lists.push(NodeList::Horizontal {
611            tp: HorizontalNodeListType::Paragraph(sref),
612            children: vec![],
613        });
614        match <ET as EngineTypes>::Gullet::char_or_primitive(engine.state, &token) {
615            Some(CharOrPrimitive::Primitive(name)) if name == PRIMITIVES.indent => {
616                Self::add_node_h(
617                    engine,
618                    HNode::Box(TeXBox::H {
619                        children: vec![].into(),
620                        info: HBoxInfo::ParIndent(
621                            engine.state.get_primitive_dim(PRIMITIVES.parindent),
622                        ),
623                        start: sref,
624                        end: sref,
625                        preskip: None,
626                    }),
627                )
628            }
629            Some(CharOrPrimitive::Primitive(name)) if name == PRIMITIVES.noindent => (),
630            _ => {
631                engine.mouth.requeue(token);
632                Self::add_node_h(
633                    engine,
634                    HNode::Box(TeXBox::H {
635                        children: vec![].into(),
636                        info: HBoxInfo::ParIndent(
637                            engine.state.get_primitive_dim(PRIMITIVES.parindent),
638                        ),
639                        start: sref,
640                        end: sref,
641                        preskip: None,
642                    }),
643                )
644            }
645        }
646        engine.push_every(PRIMITIVES.everypar)
647    }
648
649    /// Close a paragraph; assumed to be called in horizontal mode
650    fn close_paragraph(engine: &mut EngineReferences<ET>) -> TeXResult<(), ET> {
651        let ls = &mut engine.stomach.data_mut().open_lists;
652        match ls.pop() {
653            Some(NodeList::Horizontal {
654                tp: HorizontalNodeListType::Paragraph(sourceref),
655                children,
656            }) => {
657                if children.is_empty() {
658                    let _ = engine.state.take_parshape();
659                    engine.state.set_primitive_int(
660                        engine.aux,
661                        PRIMITIVES.hangafter,
662                        ET::Int::default(),
663                        false,
664                    );
665                    engine.state.set_primitive_dim(
666                        engine.aux,
667                        PRIMITIVES.hangindent,
668                        ET::Dim::default(),
669                        false,
670                    );
671                    return Ok(());
672                }
673                let spec = ParLineSpec::make(engine.state, engine.aux);
674                Self::split_paragraph(engine, spec, children, sourceref)?;
675            }
676            _ => unreachable!("Stomach::close_paragraph called outside of horizontal mode"),
677        }
678        Ok(())
679    }
680
681    /// Split a paragraph into lines and add them (as horizontal boxes) to the current vertical list
682    fn split_paragraph(
683        engine: &mut EngineReferences<ET>,
684        specs: Vec<ParLineSpec<ET>>,
685        children: Vec<HNode<ET>>,
686        start_ref: SourceReference<<<ET as EngineTypes>::File as File>::SourceRefID>,
687    ) -> TeXResult<(), ET> {
688        if children.is_empty() {
689            return Ok(());
690        }
691        let parskip = engine.state.get_primitive_skip(PRIMITIVES.parskip);
692        if parskip != Skip::default() {
693            Self::add_node_v(engine, VNode::VSkip(parskip))?;
694        }
695        let ret = methods::split_paragraph_roughly(engine, specs, children, start_ref);
696        for line in ret {
697            match line {
698                ParLine::Adjust(n) => Self::add_node_v(engine, n)?,
699                ParLine::Line(bx) => Self::add_node_v(engine, VNode::Box(bx))?,
700            }
701        }
702        Ok(())
703    }
704}
705
706/// All the mutable data of the [`Stomach`] - i.e. the current page, the current list(s), etc.
707///
708/// TODO: should be overhauled; this is just a rough approximation of what needs to happen and can be made
709/// more efficient *and* more correct.
710#[derive(Clone, Debug)]
711pub struct StomachData<ET: EngineTypes> {
712    pub page: Vec<VNode<ET>>,
713    pub open_lists: Vec<NodeList<ET>>,
714    pub pagegoal: ET::Dim,
715    pub pagetotal: ET::Dim,
716    pub pagestretch: ET::Dim,
717    pub pagefilstretch: ET::Dim,
718    pub pagefillstretch: ET::Dim,
719    pub pagefilllstretch: ET::Dim,
720    pub pageshrink: ET::Dim,
721    pub pagedepth: ET::Dim,
722    pub prevdepth: ET::Dim,
723    pub spacefactor: i32,
724    pub topmarks: HMap<usize, TokenList<ET::Token>>,
725    pub firstmarks: HMap<usize, TokenList<ET::Token>>,
726    pub botmarks: HMap<usize, TokenList<ET::Token>>,
727    pub splitfirstmarks: HMap<usize, TokenList<ET::Token>>,
728    pub splitbotmarks: HMap<usize, TokenList<ET::Token>>,
729    pub page_contains_boxes: bool,
730    pub lastpenalty: i32,
731    pub prevgraf: u16,
732    pub in_output: bool,
733    pub deadcycles: usize,
734    pub vadjusts: Vec<VNode<ET>>,
735    pub inserts: Vec<(usize, Box<[VNode<ET>]>)>,
736}
737impl<ET: EngineTypes> StomachData<ET> {
738    /// The current [`TeXMode`] (indicating the type of node list currently open)
739    pub fn mode(&self) -> TeXMode {
740        match self.open_lists.last() {
741            Some(NodeList::Horizontal {
742                tp: HorizontalNodeListType::Paragraph(..),
743                ..
744            }) => TeXMode::Horizontal,
745            Some(NodeList::Horizontal { .. }) => TeXMode::RestrictedHorizontal,
746            Some(NodeList::Vertical { .. }) => TeXMode::InternalVertical,
747            Some(NodeList::Math { .. }) => {
748                for ls in self.open_lists.iter().rev() {
749                    if let NodeList::Math {
750                        tp: MathNodeListType::Top { display },
751                        ..
752                    } = ls
753                    {
754                        if *display {
755                            return TeXMode::DisplayMath;
756                        } else {
757                            return TeXMode::InlineMath;
758                        }
759                    }
760                }
761                unreachable!()
762            }
763            None => TeXMode::Vertical,
764        }
765    }
766}
767
768impl<ET: EngineTypes> Default for StomachData<ET> {
769    fn default() -> Self {
770        StomachData {
771            page: vec![],
772            open_lists: vec![],
773            pagegoal: ET::Dim::from_sp(i32::MAX),
774            pagetotal: ET::Dim::default(),
775            pagestretch: ET::Dim::default(),
776            pagefilstretch: ET::Dim::default(),
777            pagefillstretch: ET::Dim::default(),
778            pagefilllstretch: ET::Dim::default(),
779            pageshrink: ET::Dim::default(),
780            pagedepth: ET::Dim::default(),
781            prevdepth: ET::Dim::from_sp(-65536000),
782            spacefactor: 1000,
783            topmarks: HMap::default(),
784            firstmarks: HMap::default(),
785            botmarks: HMap::default(),
786            splitfirstmarks: HMap::default(),
787            splitbotmarks: HMap::default(),
788            prevgraf: 0,
789            lastpenalty: 0,
790            page_contains_boxes: false,
791            in_output: false,
792            deadcycles: 0,
793            vadjusts: vec![],
794            inserts: vec![],
795        }
796    }
797}
798
799/// Default implementation of a [`Stomach`]
800pub struct DefaultStomach<ET: EngineTypes /*<Stomach=Self>*/> {
801    afterassignment: Option<ET::Token>,
802    data: StomachData<ET>,
803}
804impl<ET: EngineTypes /*<Stomach=Self>*/> Stomach<ET> for DefaultStomach<ET> {
805    fn new(_aux: &mut EngineAux<ET>, _state: &mut ET::State) -> Self {
806        DefaultStomach {
807            afterassignment: None,
808            data: StomachData::default(),
809        }
810    }
811
812    fn afterassignment(&mut self) -> &mut Option<ET::Token> {
813        &mut self.afterassignment
814    }
815
816    fn data_mut(&mut self) -> &mut StomachData<ET> {
817        &mut self.data
818    }
819}
820
821impl<ET: EngineTypes> EngineReferences<'_, ET> {
822    /// read a box from the current input stream
823    pub fn read_box(
824        &mut self,
825        skip_eq: bool,
826    ) -> TeXResult<Either<Option<TeXBox<ET>>, BoxInfo<ET>>, ET> {
827        let mut read_eq = !skip_eq;
828        crate::expand_loop!(self,token,
829            ResolvedToken::Tk {char,code:CommandCode::Other} if !read_eq && matches!(char.try_into(),Ok(b'=')) => read_eq = true,
830            ResolvedToken::Tk { code:CommandCode::Space,..} => (),
831            ResolvedToken::Cmd(Some(TeXCommand::Primitive {cmd:PrimitiveCommand::Box(b),..})) =>
832                return b(self,token),
833            _ => break
834        );
835        self.general_error("A <box> was supposed to be here".to_string())?;
836        Ok(Either::Left(None))
837    }
838
839    /// read a math char or group from the current input stream. Assumes we are in math mode.
840    /// (e.g. `\mathop X` or `\mathop{ \alpha + \beta }`).
841    /// In the latter case a new list is opened and processed "asynchronously". When the list is closed,
842    /// the second continuation is called with the list as argument.
843    pub fn read_char_or_math_group<
844        S,
845        F1: FnOnce(S, &mut Self, MathChar<ET>) -> TeXResult<(), ET>,
846        F2: FnOnce(S) -> ListTarget<ET, MathNode<ET, UnresolvedMathFontStyle<ET>>>,
847    >(
848        &mut self,
849        in_token: &ET::Token,
850        f: F1,
851        tp: F2,
852        s: S,
853    ) -> TeXResult<(), ET> {
854        crate::expand_loop!(self,token,
855            ResolvedToken::Tk {code:CommandCode::Space,..} => (),
856            ResolvedToken::Tk {code:CommandCode::BeginGroup,..} |
857            ResolvedToken::Cmd(Some(TeXCommand::Char {code:CommandCode::BeginGroup,..})) => {
858                self.state.push(self.aux,GroupType::Math,self.mouth.line_number());
859                let list = NodeList::Math{children:MathNodeList::default(),start:self.mouth.start_ref(),
860                    tp:MathNodeListType::Target(tp(s))};
861                self.stomach.data_mut().open_lists.push(list);
862                return Ok(())
863            },
864            ResolvedToken::Cmd(Some(TeXCommand::Primitive{cmd:PrimitiveCommand::Relax,..})) => (),
865            ResolvedToken::Tk {char,code:CommandCode::Other | CommandCode::Letter} => {
866                let code = self.state.get_mathcode(char);
867                if code == 32768 {
868                    self.mouth.requeue(ET::Token::from_char_cat(char, CommandCode::Active));
869                    continue
870                }
871                let mc = MathChar::from_u32(code,self.state, Some(char));
872                return f(s,self,mc)
873            },
874            ResolvedToken::Cmd(Some(TeXCommand::MathChar(u))) => {
875                let mc = MathChar::from_u32(*u, self.state,None);
876                return f(s,self,mc)
877            }
878            ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,..})) if *name == PRIMITIVES.delimiter => {
879                let int = self.read_int(true,&token)?;
880                match Delimiter::from_int(int,self.state) {
881                    either::Left(d) => return f(s,self,d.small),
882                    either::Right((d,i)) => {
883                        self.general_error(format!("Bad delimiter code ({})",i))?;
884                        return f(s,self,d.small)
885                    }
886                }
887            }
888            _ => return Err(TeXError::General("Begingroup or math character expected\nTODO: Better error message".to_string()))
889        );
890        TeXError::file_end_while_use(self.aux, self.state, self.mouth, in_token)
891    }
892}