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}