Skip to main content

tex_engine/engine/gullet/
hvalign.rs

1/*! Data structures for `\halign` and `\valign` */
2
3use crate::commands::methods::{END_TEMPLATE, END_TEMPLATE_ROW};
4use crate::commands::primitives::PRIMITIVES;
5use crate::engine::mouth::Mouth;
6use crate::engine::state::State;
7use crate::engine::{EngineAux, EngineTypes};
8use crate::prelude::{CommandCode, Token};
9use crate::tex::nodes::boxes::BoxType;
10use crate::tex::numerics::Skip;
11use crate::tex::tokens::control_sequences::CSHandler;
12
13/// Specification on a column in an `\halign` (or a row in a `\valign`); in particular:
14/// - the alignment template for the column (or row)
15/// - the [tabskip](crate::tex::numerics::Skip) to be inserted between the columns (or rows),
16/// - the number of braces to inserted at the beginning of the column (or row).
17///
18/// The latter is important for the [`Gullet`](crate::engine::gullet::Gullet) to know when an [`AlignmentTab`](crate::tex::catcodes::CommandCode::AlignmentTab)
19/// [`Token`] (or a `\cr`) should be passed on or replaced by the relevant template tokens
20#[derive(Debug)]
21pub struct AlignColumn<T: Token, D: crate::tex::numerics::TeXDimen> {
22    /// The tokens to be inserted at the beginning of the column (or row)
23    pub left: Box<[T]>,
24    /// The tokens to be inserted at the end of the column (or row)
25    pub right: Box<[T]>,
26    /// The number of braces that are opened by the template tokens
27    pub inbraces: u8,
28    /// The [tabskip](crate::tex::numerics::Skip) to be inserted between the columns (or rows)
29    pub tabskip: Skip<D>,
30}
31impl<T: Token, D: crate::tex::numerics::TeXDimen> AlignColumn<T, D> {
32    /// Create a new [`AlignColumn`] with the given template tokens, tabskip and number of braces
33    pub fn new(mut left: Vec<T>, mut right: Vec<T>, tabskip: Skip<D>, inbraces: u8) -> Self {
34        left.reverse();
35        right.reverse();
36        Self {
37            left: left.into(),
38            inbraces,
39            right: right.into(),
40            tabskip,
41        }
42    }
43}
44
45/// Data structure for a currently open `\halign` (or `\valign`)
46pub struct AlignData<T: Token, D: crate::tex::numerics::TeXDimen> {
47    pub token: T,
48    /// The number of braces that are currently open
49    pub ingroups: u8,
50    /// The index of the current column (or row)
51    pub currindex: usize,
52    /// The index of the column (or row) to be repeated, if the number of columns (or rows) in a particular
53    /// row exceeds the number of columns (or rows) in the template
54    pub repeat_index: Option<usize>,
55    /// The columns (or rows) of the template
56    pub columns: Box<[AlignColumn<T, D>]>,
57    /// Whether the current column (or row) should omit the template
58    pub omit: bool,
59    /// Whether the current column (or row) spans across the next column (or rows)
60    pub span: bool,
61    /// The mode of the current column (or row); this is either [`BoxType::Horizontal`] for an `\halign`
62    /// or [`BoxType::Vertical`] for a `\valign`
63    pub inner_mode: BoxType,
64    /// The mode between rows (or columns); this is either [`BoxType::Vertical`] for an `\halign`
65    /// or [`BoxType::Horizontal`] for a `\valign`
66    pub outer_mode: BoxType,
67}
68impl<T: Token, D: crate::tex::numerics::TeXDimen> AlignData<T, D> {
69    /// the number of open braces at which an [`AlignmentTab`](crate::tex::catcodes::CommandCode::AlignmentTab)
70    /// [`Token`] (or a `\cr`) should cause the template tokens to be inserted into the input stream
71    pub fn groupval(&self) -> u8 {
72        if self.omit {
73            0
74        } else {
75            self.columns[self.currindex].inbraces
76        }
77    }
78    pub fn check_token(&mut self, t: &T) -> Option<bool> {
79        match t.command_code() {
80            CommandCode::BeginGroup => {
81                self.ingroups += 1;
82                Some(true)
83            }
84            CommandCode::EndGroup => {
85                if self.ingroups == 0 {
86                    Some(false)
87                } else {
88                    self.ingroups -= 1;
89                    Some(true)
90                }
91            }
92            _ => None,
93        }
94    }
95
96    /// A dummy [`AlignData`] that makes sure that [`AlignmentTab`](crate::tex::catcodes::CommandCode::AlignmentTab)
97    /// [`Token`]s (and `\cr`s) are not replaced by a template; can be pushed to the [`Gullet`](crate::engine::gullet::Gullet) at the begin
98    /// of an `\halign` (or `\valign`) before the template is fully processed to avoid already open
99    /// [`AlignData`]s to be used.
100    pub fn dummy() -> Self {
101        Self {
102            token: T::space(),
103            ingroups: 125,
104            currindex: 0,
105            repeat_index: None,
106            columns: Box::new([AlignColumn::new(Vec::new(), Vec::new(), Skip::default(), 0)]),
107            omit: false,
108            span: false,
109            inner_mode: BoxType::Horizontal,
110            outer_mode: BoxType::Vertical,
111        }
112    }
113    /// Push the end-template tokens of the current column (or row) to the [`Mouth`];
114    /// this is called when an [`AlignmentTab`](crate::tex::catcodes::CommandCode::AlignmentTab)
115    /// [`Token`] (or a `\span`) is encountered and the number of currently open braces matches the current column's
116    /// `inbraces` value.
117    pub fn on_alignment_tab<ET: EngineTypes<Token = T, Dim = D>>(
118        &self,
119        mouth: &mut ET::Mouth,
120        aux: &mut EngineAux<ET>,
121    ) -> T {
122        let end_align =
123            <ET::Token as Token>::from_cs(aux.memory.cs_interner_mut().cs_from_str(END_TEMPLATE));
124        let ls = &*self.columns[self.currindex].right;
125        if self.omit || ls.is_empty() {
126            end_align
127        } else {
128            mouth.requeue(end_align);
129            let next = ls.last().unwrap().clone();
130            mouth.push_slice_rev(&ls[..ls.len() - 1]);
131            next
132        }
133    }
134
135    /// Push the end-template tokens of the current column (or row) to the [`Mouth`]
136    /// and insert `\everycr`;
137    /// this is called when a `\cr` or `\crcr` is encountered and the number of currently open braces matches the current column's
138    /// `inbraces` value.
139    pub fn on_cr<ET: EngineTypes<Token = T, Dim = D>>(
140        &self,
141        mouth: &mut ET::Mouth,
142        aux: &mut EngineAux<ET>,
143        state: &ET::State,
144    ) -> T {
145        let everycr = state.get_primitive_tokens(PRIMITIVES.everycr);
146        mouth.push_exp(everycr);
147        let end = <ET::Token as Token>::from_cs(
148            aux.memory.cs_interner_mut().cs_from_str(END_TEMPLATE_ROW),
149        );
150        let ls = &*self.columns[self.currindex].right;
151        if self.omit || ls.is_empty() {
152            end
153        } else {
154            mouth.requeue(end);
155            let next = ls.last().unwrap().clone();
156            mouth.push_slice_rev(&ls[..ls.len() - 1]);
157            next
158        }
159    }
160}