1use crate::commands::primitives::{PrimitiveIdentifier, PRIMITIVES};
5use crate::commands::{
6 CharOrPrimitive, Macro, MacroSignature, PrimitiveCommand, ResolvedToken, TeXCommand,
7};
8use crate::engine::filesystem::FileSystem;
9use crate::engine::fontsystem::{Font, FontSystem};
10use crate::engine::gullet::hvalign::{AlignColumn, AlignData};
11use crate::engine::gullet::Gullet;
12use crate::engine::mouth::Mouth;
13use crate::engine::state::{GroupType, State};
14use crate::engine::stomach::TeXMode;
15use crate::engine::stomach::{Stomach, StomachData};
16use crate::engine::{EngineAux, EngineReferences, EngineTypes};
17use crate::expand_loop;
18use crate::tex::catcodes::CommandCode;
19use crate::tex::nodes::boxes::{BoxType, HBoxInfo, TeXBox, ToOrSpread, VBoxInfo};
20use crate::tex::nodes::horizontal::{HNode, HorizontalNodeListType};
21use crate::tex::nodes::math::{
22 Delimiter, MathAtom, MathClass, MathKernel, MathNode, MathNucleus, UnresolvedMathFontStyle,
23};
24use crate::tex::nodes::vertical::{VNode, VerticalNodeListType};
25use crate::tex::nodes::NodeTrait;
26use crate::tex::nodes::{
27 BoxTarget, LeaderBody, LeaderSkip, LeaderType, Leaders, ListTarget, NodeList,
28};
29use crate::tex::numerics::Skip;
30use crate::tex::tokens::control_sequences::CSHandler;
31use crate::tex::tokens::token_lists::TokenList;
32use crate::tex::tokens::Token;
33use crate::utils::errors::{TeXError, TeXResult};
34use crate::utils::HMap;
35
36pub(crate) struct MacroParser<T: Token> {
37 arity: u8,
38 params: shared_vector::Vector<T>,
39 inparam: bool,
40 ends_with_brace: Option<T>,
41 exp: shared_vector::Vector<T>,
42}
43impl<T: Token> MacroParser<T> {
44 pub(crate) fn new() -> Self {
45 Self {
46 arity: 0,
47 params: shared_vector::Vector::new(),
48 inparam: false,
49 ends_with_brace: None,
50 exp: shared_vector::Vector::new(),
51 }
52 }
53 pub(crate) fn do_signature_token<ET: EngineTypes<Token = T>>(
54 &mut self,
55 t: T,
56 ) -> TeXResult<Option<()>, ET> {
57 match t.command_code() {
58 CommandCode::BeginGroup => {
59 if self.inparam {
60 self.inparam = false;
61 self.params.push(t.clone());
62 self.ends_with_brace = Some(t);
63 }
64 return Ok(Some(()));
65 }
66 CommandCode::Parameter if self.inparam => {
67 self.inparam = false;
68 self.params.push(t);
69 }
70 CommandCode::Parameter => {
71 self.inparam = true;
72 }
73 _ if self.inparam => {
74 self.inparam = false;
75 match t.char_value() {
76 Some(c) => match c.try_into() {
77 Ok(u) if u > 48 && u == 49 + self.arity => {
78 self.params.push(T::argument_marker(self.arity))
79 }
80 _ => {
81 return Err(TeXError::General(
82 "Invalid argument number\nTODO: Better error message".to_string(),
83 ))
84 }
85 },
86 None => {
87 return Err(TeXError::General(
88 "Missing argument number\nTODO: Better error message".to_string(),
89 ))
90 }
91 }
92 self.arity += 1;
93 }
94 _ => self.params.push(t),
95 }
96 Ok(None)
97 }
98
99 pub(crate) fn do_expansion_token<ET: EngineTypes>(&mut self, t: T) -> TeXResult<(), ET> {
100 match t.command_code() {
101 CommandCode::Parameter if self.inparam => {
102 self.inparam = false;
103 self.exp.push(t);
104 }
105 CommandCode::Parameter => self.inparam = true,
106 _ if self.inparam => {
107 self.inparam = false;
108 match t.char_value() {
109 Some(c) => match c.try_into() {
110 Ok(u) if u > 48 && u - 49 < self.arity => {
111 self.exp.push(T::argument_marker(u - 49))
112 }
113 _ => {
114 return Err(TeXError::General(
115 "Invalid argument number\nTODO: Better error message".to_string(),
116 ))
117 }
118 },
119 None => {
120 return Err(TeXError::General(
121 "Missing argument number\nTODO: Better error message".to_string(),
122 ))
123 }
124 }
125 }
126 _ => self.exp.push(t),
127 }
128 Ok(())
129 }
130
131 pub(crate) fn close(mut self, long: bool, outer: bool, protected: bool) -> Macro<T> {
132 if let Some(t) = self.ends_with_brace {
133 self.exp.push(t);
134 }
135 Macro {
136 long,
137 outer,
138 protected,
139 expansion: self.exp.into(),
140 signature: MacroSignature {
141 arity: self.arity,
142 params: self.params.into(),
143 },
144 }
145 }
146}
147
148pub(in crate::commands) fn modify_int_register<
149 ET: EngineTypes,
150 O: FnOnce(ET::Int, &mut EngineReferences<ET>) -> TeXResult<ET::Int, ET>,
151>(
152 engine: &mut EngineReferences<ET>,
153 idx: usize,
154 globally: bool,
155 op: O,
156) -> TeXResult<(), ET> {
157 engine.read_keyword(b"by")?;
158 let old = engine.state.get_int_register(idx);
159 let new = op(old, engine)?;
160 engine
161 .state
162 .set_int_register(engine.aux, idx, new, globally);
163 Ok(())
164}
165
166pub(in crate::commands) fn modify_primitive_int<
167 ET: EngineTypes,
168 O: FnOnce(ET::Int, &mut EngineReferences<ET>) -> TeXResult<ET::Int, ET>,
169>(
170 engine: &mut EngineReferences<ET>,
171 name: PrimitiveIdentifier,
172 globally: bool,
173 op: O,
174) -> TeXResult<(), ET> {
175 engine.read_keyword(b"by")?;
176 let old = engine.state.get_primitive_int(name);
177 let new = op(old, engine)?;
178 engine
179 .state
180 .set_primitive_int(engine.aux, name, new, globally);
181 Ok(())
182}
183
184pub(in crate::commands) fn modify_dim_register<
185 ET: EngineTypes,
186 O: FnOnce(ET::Dim, &mut EngineReferences<ET>) -> TeXResult<ET::Dim, ET>,
187>(
188 engine: &mut EngineReferences<ET>,
189 idx: usize,
190 globally: bool,
191 op: O,
192) -> TeXResult<(), ET> {
193 engine.read_keyword(b"by")?;
194 let old = engine.state.get_dim_register(idx);
195 let new = op(old, engine)?;
196 engine
197 .state
198 .set_dim_register(engine.aux, idx, new, globally);
199 Ok(())
200}
201
202pub(in crate::commands) fn modify_primitive_dim<
203 ET: EngineTypes,
204 O: FnOnce(ET::Dim, &mut EngineReferences<ET>) -> TeXResult<ET::Dim, ET>,
205>(
206 engine: &mut EngineReferences<ET>,
207 name: PrimitiveIdentifier,
208 globally: bool,
209 op: O,
210) -> TeXResult<(), ET> {
211 engine.read_keyword(b"by")?;
212 let old = engine.state.get_primitive_dim(name);
213 let new = op(old, engine)?;
214 engine
215 .state
216 .set_primitive_dim(engine.aux, name, new, globally);
217 Ok(())
218}
219
220pub(in crate::commands) fn modify_skip_register<
221 ET: EngineTypes,
222 O: FnOnce(Skip<ET::Dim>, &mut EngineReferences<ET>) -> TeXResult<Skip<ET::Dim>, ET>,
223>(
224 engine: &mut EngineReferences<ET>,
225 idx: usize,
226 globally: bool,
227 op: O,
228) -> TeXResult<(), ET> {
229 engine.read_keyword(b"by")?;
230 let old = engine.state.get_skip_register(idx);
231 let new = op(old, engine)?;
232 engine
233 .state
234 .set_skip_register(engine.aux, idx, new, globally);
235 Ok(())
236}
237
238pub(in crate::commands) fn modify_primitive_skip<
239 ET: EngineTypes,
240 O: FnOnce(Skip<ET::Dim>, &mut EngineReferences<ET>) -> TeXResult<Skip<ET::Dim>, ET>,
241>(
242 engine: &mut EngineReferences<ET>,
243 name: PrimitiveIdentifier,
244 globally: bool,
245 op: O,
246) -> TeXResult<(), ET> {
247 engine.read_keyword(b"by")?;
248 let old = engine.state.get_primitive_skip(name);
249 let new = op(old, engine)?;
250 engine
251 .state
252 .set_primitive_skip(engine.aux, name, new, globally);
253 Ok(())
254}
255
256pub(in crate::commands) fn do_box_start<ET: EngineTypes>(
257 engine: &mut EngineReferences<ET>,
258 tp: GroupType,
259 every: PrimitiveIdentifier,
260 tk: &ET::Token,
261) -> TeXResult<ToOrSpread<ET::Dim>, ET> {
262 let scaled = match engine.read_keywords(&[b"to", b"spread"])? {
263 Some(b"to") => {
264 let to = engine.read_dim(false, tk)?;
265 ToOrSpread::To(to)
266 }
267 Some(b"spread") => {
268 let spread = engine.read_dim(false, tk)?;
269 ToOrSpread::Spread(spread)
270 }
271 _ => ToOrSpread::None,
272 };
273 crate::expand_loop!(engine,token,
274 ResolvedToken::Tk {code:CommandCode::Space,..} => (),
275 ResolvedToken::Cmd(Some(TeXCommand::Primitive{cmd:PrimitiveCommand::Relax,..})) => (),
276 ResolvedToken::Tk {code:CommandCode::BeginGroup,..} |
277 ResolvedToken::Cmd(Some(TeXCommand::Char{code:CommandCode::BeginGroup,..})) => {
278 engine.state.push(engine.aux,tp,engine.mouth.line_number());
279 engine.push_every(every);
280 return Ok(scaled)
281 }
282 _ => return Err(TeXError::General("Begin group expected in box start\nTODO: Better error message".to_string()))
283 );
284 Err(TeXError::General(
285 "File ended unexpectedly\nTODO: Better error message".to_string(),
286 ))
287}
288
289pub(in crate::commands) fn get_if_token<ET: EngineTypes>(
290 engine: &mut EngineReferences<ET>,
291 in_token: &ET::Token,
292) -> TeXResult<(Option<ET::Char>, CommandCode), ET> {
293 let mut exp = true;
294 loop {
295 let token = engine.need_next(!exp, in_token)?;
296 if token.is_primitive() == Some(PRIMITIVES.noexpand) {
297 exp = false;
298 continue;
299 }
300 if !exp {
301 if let crate::tex::tokens::StandardToken::Character(c, CommandCode::Active) =
302 token.to_enum()
303 {
304 return Ok((Some(c), CommandCode::Active));
305 }
306 }
307 match engine.resolve(&token) {
308 ResolvedToken::Tk { char, code } => return Ok((Some(char), code)),
309 ResolvedToken::Cmd(cmd) => match cmd {
310 Some(TeXCommand::Macro(m)) if exp => {
311 ET::Gullet::do_macro(engine, m.clone(), token)?;
312 }
313 Some(TeXCommand::Primitive { name, .. }) if exp && *name == PRIMITIVES.noexpand => {
314 exp = false;
315 }
316 Some(TeXCommand::Primitive {
317 name,
318 cmd: PrimitiveCommand::Conditional(cond),
319 }) if exp => ET::Gullet::do_conditional(engine, *name, token, *cond, false)?,
320 Some(TeXCommand::Primitive {
321 name,
322 cmd: PrimitiveCommand::Expandable(e),
323 }) if exp => ET::Gullet::do_expandable(engine, *name, token, *e)?,
324 Some(TeXCommand::Primitive {
325 name,
326 cmd: PrimitiveCommand::SimpleExpandable(e),
327 }) if exp => ET::Gullet::do_simple_expandable(engine, *name, token, *e)?,
328 Some(TeXCommand::Char { char, code }, ..) => return Ok((Some(*char), *code)),
329 _ => return Ok((None, CommandCode::Escape)),
330 },
331 }
332 }
333}
334
335pub(in crate::commands) enum IfxCmd<ET: EngineTypes> {
336 Char(ET::Char, CommandCode),
337 Undefined,
338 Primitive(PrimitiveIdentifier),
339 Noexpand(ET::Token),
340 Chardef(ET::Char),
341 Font(<ET::FontSystem as FontSystem>::Font),
342 MathChar(u32),
343 Macro(Macro<ET::Token>),
344 IntRegister(usize),
345 DimRegister(usize),
346 SkipRegister(usize),
347 MuSkipRegister(usize),
348 ToksRegister(usize),
349}
350impl<ET: EngineTypes> IfxCmd<ET> {
351 pub(in crate::commands) fn read(
352 engine: &mut EngineReferences<ET>,
353 in_token: &ET::Token,
354 ) -> TeXResult<Self, ET> {
355 let next = engine.need_next(true, in_token)?;
356 Ok(if next.is_primitive() == Some(PRIMITIVES.noexpand) {
357 IfxCmd::Noexpand(engine.need_next(true, &next)?)
358 } else {
359 Self::resolve(engine.resolve(&next))
360 })
361 }
362
363 fn resolve(r: ResolvedToken<ET>) -> Self {
364 match r {
365 ResolvedToken::Tk { char, code, .. } => Self::Char(char, code),
366 ResolvedToken::Cmd(cmd) => match cmd {
367 Some(TeXCommand::Char { char, code }) => Self::Char(*char, *code),
368 None => Self::Undefined,
369 Some(TeXCommand::Macro(m)) => Self::Macro(m.clone()),
370 Some(TeXCommand::CharDef(c)) => Self::Chardef(*c),
371 Some(TeXCommand::Font(f)) => Self::Font(f.clone()),
372 Some(TeXCommand::MathChar(u)) => Self::MathChar(*u),
373 Some(TeXCommand::IntRegister(u)) => Self::IntRegister(*u),
374 Some(TeXCommand::DimRegister(u)) => Self::DimRegister(*u),
375 Some(TeXCommand::SkipRegister(u)) => Self::SkipRegister(*u),
376 Some(TeXCommand::MuSkipRegister(u)) => Self::MuSkipRegister(*u),
377 Some(TeXCommand::ToksRegister(u)) => Self::ToksRegister(*u),
378 Some(TeXCommand::Primitive { name, .. }) => Self::Primitive(*name),
379 },
380 }
381 }
382}
383
384impl<ET: EngineTypes> PartialEq for IfxCmd<ET> {
385 fn eq(&self, other: &Self) -> bool {
386 match (self, other) {
387 (Self::Char(_, CommandCode::Space), Self::Char(_, CommandCode::Space)) => true,
388 (Self::Char(c1, cc1), Self::Char(c2, cc2)) => c1 == c2 && cc1 == cc2,
389 (Self::Undefined, Self::Undefined) => true,
390 (Self::Primitive(id), Self::Primitive(id2)) => id == id2,
391 (Self::Noexpand(t1), Self::Noexpand(t2)) => t1 == t2,
392 (Self::Chardef(c), Self::Chardef(c2)) => c == c2,
393 (Self::Font(f1), Self::Font(f2)) => f1.name() == f2.name(),
394 (Self::MathChar(u1), Self::MathChar(u2)) => u1 == u2,
395 (Self::IntRegister(u1), Self::IntRegister(u2)) => u1 == u2,
396 (Self::DimRegister(u1), Self::DimRegister(u2)) => u1 == u2,
397 (Self::SkipRegister(u1), Self::SkipRegister(u2)) => u1 == u2,
398 (Self::MuSkipRegister(u1), Self::MuSkipRegister(u2)) => u1 == u2,
399 (Self::ToksRegister(u1), Self::ToksRegister(u2)) => u1 == u2,
400 (Self::Macro(m1), Self::Macro(m2)) => {
401 m1.long == m2.long
402 && m1.outer == m2.outer
403 && m1.protected == m2.protected
404 && m1.signature.params == m2.signature.params
405 && m1.expansion == m2.expansion
406 }
407 _ => false,
408 }
409 }
410}
411
412pub(crate) fn do_marks<ET: EngineTypes>(
413 engine: &mut EngineReferences<ET>,
414 idx: usize,
415 in_token: &ET::Token,
416) -> TeXResult<(), ET> {
417 let mut v = shared_vector::Vector::new();
418 engine.expand_until_bgroup(false, in_token)?;
419 engine.expand_until_endgroup(true, true, in_token, |_, _, t| {
420 v.push(t);
421 Ok(())
422 })?;
423 let data = engine.stomach.data_mut();
424 for list in data.open_lists.iter_mut().rev() {
425 match list {
426 NodeList::Horizontal {
427 children,
428 tp: HorizontalNodeListType::Paragraph(..),
429 } => {
430 children.push(HNode::Mark(idx, v.into()));
431 return Ok(());
432 }
433 NodeList::Vertical { children, .. } => {
434 children.push(VNode::Mark(idx, v.into()));
435 return Ok(());
436 }
437 NodeList::Math { children, .. } => {
438 children.push(MathNode::Mark(idx, v.into()));
439 return Ok(());
440 }
441 _ => (),
442 }
443 }
444 data.page.push(VNode::Mark(idx, v.into()));
445 Ok(())
446}
447
448pub(crate) fn get_marks<ET: EngineTypes>(
449 engine: &mut EngineReferences<ET>,
450 exp: &mut Vec<ET::Token>,
451 f: fn(&mut StomachData<ET>) -> &mut HMap<usize, TokenList<ET::Token>>,
452 idx: usize,
453) {
454 if let Some(v) = f(engine.stomach.data_mut()).get(&idx) {
455 exp.extend(v.0.iter().cloned())
456 }
457}
458
459pub(crate) fn do_align<ET: EngineTypes>(
460 engine: &mut EngineReferences<ET>,
461 inner: BoxType,
462 between: BoxType,
463 _to: Option<ET::Dim>,
464 tk: &ET::Token,
465) -> TeXResult<(), ET> {
466 engine.gullet.push_align(AlignData::dummy());
468 engine.expand_until_bgroup(true, tk)?;
469 let data = read_align_preamble(engine, inner, between, tk)?;
470 *engine.gullet.get_align_data().unwrap() = data;
471 ET::Stomach::open_align(engine, inner, between);
472 start_align_row(engine, inner)
473}
474
475fn read_align_preamble<ET: EngineTypes>(
476 engine: &mut EngineReferences<ET>,
477 inner_mode: BoxType,
478 between_mode: BoxType,
479 in_token: &ET::Token,
480) -> TeXResult<AlignData<ET::Token, ET::Dim>, ET> {
481 struct AlignmentDataBuilder<ET: EngineTypes> {
482 columns: Vec<AlignColumn<ET::Token, ET::Dim>>,
483 recindex: Option<usize>,
484 current_u: Vec<ET::Token>,
485 current_v: Vec<ET::Token>,
486 ingroups: u8,
487 in_v: bool,
488 tabskip: Skip<ET::Dim>,
489 inner_mode: BoxType,
490 between_mode: BoxType,
491 }
492 impl<ET: EngineTypes> AlignmentDataBuilder<ET> {
493 fn push(&mut self, tk: ET::Token) {
494 if self.in_v {
495 self.current_v.push(tk)
496 } else {
497 self.current_u.push(tk)
498 }
499 }
500 fn build(mut self, token: ET::Token) -> AlignData<ET::Token, ET::Dim> {
501 self.columns.push(AlignColumn::new(
502 self.current_u,
503 self.current_v,
504 self.tabskip,
505 self.ingroups,
506 ));
507 AlignData {
508 token,
509 columns: self.columns.into(),
510 ingroups: 0,
511 currindex: 0,
512 repeat_index: self.recindex,
513 omit: false,
514 span: false,
515 inner_mode: self.inner_mode,
516 outer_mode: self.between_mode,
517 }
518 }
519 }
520
521 let tabskip = engine.state.get_primitive_skip(PRIMITIVES.tabskip);
522 let mut cols = AlignmentDataBuilder::<ET> {
523 columns: Vec::new(),
524 recindex: None,
525 ingroups: 0,
526 current_u: Vec::new(),
527 current_v: Vec::new(),
528 in_v: false,
529 tabskip,
530 inner_mode,
531 between_mode,
532 };
533 let mut ingroups = 0;
534
535 while let Some(next) = engine.mouth.get_next(engine.aux, engine.state)? {
536 if next.is_primitive() == Some(PRIMITIVES.noexpand) {
537 let Ok(Some(n)) = engine.mouth.get_next(engine.aux, engine.state) else {
538 unreachable!()
539 };
540 cols.push(n);
541 continue;
542 }
543 match next.command_code() {
544 CommandCode::BeginGroup => {
545 ingroups += 1;
546 cols.push(next);
547 continue;
548 }
549 CommandCode::EndGroup => {
550 ingroups -= 1;
551 cols.push(next);
552 continue;
553 }
554 _ => (),
555 }
556 match ET::Gullet::char_or_primitive(engine.state, &next) {
557 Some(CharOrPrimitive::Char(_, Some(CommandCode::Parameter))) => {
558 if cols.in_v {
559 return Err(TeXError::General(
560 "Unexpected # in alignment\nTODO: Better error message".to_string(),
561 ));
562 }
563 cols.in_v = true;
564 cols.ingroups = ingroups;
565 }
566 Some(CharOrPrimitive::Char(_, Some(CommandCode::AlignmentTab))) => {
567 if ingroups != 0 {
568 return Err(TeXError::General(
569 "Unbalanced number of braces in alignment\nTODO: Better error message"
570 .to_string(),
571 ));
572 }
573 if !cols.in_v && cols.current_u.is_empty() {
574 cols.recindex = Some(cols.columns.len());
575 } else {
576 let (u, v, g) = (
577 std::mem::take(&mut cols.current_u),
578 std::mem::take(&mut cols.current_v),
579 std::mem::take(&mut cols.ingroups),
580 );
581 cols.columns.push(AlignColumn::new(u, v, cols.tabskip, g));
582 cols.tabskip = tabskip;
583 cols.in_v = false;
584 }
585 }
586 Some(CharOrPrimitive::Primitive(name)) if name == PRIMITIVES.tabskip => {
587 cols.tabskip = engine.read_skip(true, in_token)?
588 }
589 Some(CharOrPrimitive::Primitive(name))
590 if name == PRIMITIVES.cr || name == PRIMITIVES.crcr =>
591 {
592 if ingroups != 0 {
593 return Err(TeXError::General(
594 "Unbalanced number of braces in alignment\nTODO: Better error message"
595 .to_string(),
596 ));
597 }
598 engine.push_every(PRIMITIVES.everycr);
599 return Ok(cols.build(in_token.clone()));
600 }
601 Some(CharOrPrimitive::Primitive(name)) if name == PRIMITIVES.span => {
602 let t = match engine.mouth.get_next(engine.aux, engine.state)? {
603 Some(t) => t,
604 _ => {
605 TeXError::file_end_while_use(
606 engine.aux,
607 engine.state,
608 engine.mouth,
609 in_token,
610 )?;
611 continue;
612 }
613 };
614 engine.expand(t)?;
615 }
616 _ => cols.push(next),
617 }
618 }
619 TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, in_token)?;
620 Ok(cols.build(in_token.clone()))
621}
622
623pub(in crate::commands) fn start_align_row<ET: EngineTypes>(
624 engine: &mut EngineReferences<ET>,
625 mode: BoxType,
626) -> TeXResult<(), ET> {
627 if let Some(d) = engine.gullet.get_align_data() {
628 d.currindex = 0
629 } else {
630 unreachable!()
631 }
632 while let Some(token) = engine.mouth.get_next(engine.aux, engine.state)? {
634 crate::expand!(engine,token;
635 ResolvedToken::Tk{code:CommandCode::EndGroup,..} |
636 ResolvedToken::Cmd(Some(TeXCommand::Char {code:CommandCode::EndGroup,..})) => {
637 engine.gullet.pop_align();
638 ET::Stomach::close_align(engine)?;
639 return Ok(())
640 }
641 ResolvedToken::Tk{code:CommandCode::Space,..} => (),
642 ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,..})) if *name == PRIMITIVES.crcr => (),
643 ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,..})) if *name == PRIMITIVES.noalign => {
644 engine.expand_until_bgroup(true,&token)?;
645 engine.state.push(engine.aux,GroupType::Noalign,engine.mouth.line_number());
646 engine.stomach.data_mut().open_lists.push(
647 match mode {
648 BoxType::Vertical => NodeList::Horizontal {
649 children:Vec::new(),
650 tp:HorizontalNodeListType::Box(HBoxInfo::HAlignRow,engine.mouth.start_ref(),BoxTarget::new(
651 move |engine,l| {
652 if let TeXBox::H {children,info:HBoxInfo::HAlignRow,..} = l {
653 for c in children.into_vec() {
654 ET::Stomach::add_node_h(engine,c);
655 }
656 } else {unreachable!()}
657 start_align_row(engine,mode)
658 }
659 ))
660 },
661 _ => NodeList::Vertical {
662 children:Vec::new(),
663 tp:VerticalNodeListType::Box(VBoxInfo::VAlignColumn,engine.mouth.start_ref(),BoxTarget::new(
664 move |engine,l| {
665 if let TeXBox::V {children,info:VBoxInfo::VAlignColumn,..} = l {
666 for c in children.into_vec() {
667 ET::Stomach::add_node_v(engine,c)?;
668 }
669 } else {unreachable!()}
670 start_align_row(engine,mode)
671 }
672 ))
673 }
674 }
675 );
676 return Ok(())
677 }
678 ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,..})) if *name == PRIMITIVES.omit => {
679 engine.stomach.data_mut().open_lists.push(
680 match mode {
681 BoxType::Vertical => NodeList::Vertical {
682 children:Vec::new(),
683 tp:VerticalNodeListType::VAlignColumn(engine.mouth.start_ref())
684 },
685 _ => NodeList::Horizontal {
686 children:Vec::new(),
687 tp:HorizontalNodeListType::HAlignRow(engine.mouth.start_ref())
688 }
689 }
690 );
691 engine.gullet.get_align_data().unwrap().omit = true;
692 open_align_cell(engine,mode);
693 return Ok(())
694 }
695 _ => {
696 engine.stomach.data_mut().open_lists.push(
697 match mode {
698 BoxType::Vertical => NodeList::Vertical {
699 children:Vec::new(),
700 tp:VerticalNodeListType::VAlignColumn(engine.mouth.start_ref())
701 },
702 _ => NodeList::Horizontal {
703 children:Vec::new(),
704 tp:HorizontalNodeListType::HAlignRow(engine.mouth.start_ref())
705 }
706 }
707 );
708 engine.mouth.requeue(token);
709 open_align_cell(engine,mode);
710 return Ok(())
711 }
712 );
713 }
714 let Some(ad) = engine.gullet.get_align_data() else {
715 return Err(TeXError::General("Not in align".to_string()));
716 };
717 TeXError::file_end_while_use(engine.aux, engine.state, engine.mouth, &ad.token)
718}
719
720pub(in crate::commands) fn open_align_cell<ET: EngineTypes>(
721 engine: &mut EngineReferences<ET>,
722 mode: BoxType,
723) {
724 match engine.gullet.get_align_data() {
725 None => unreachable!(),
726 Some(data) => {
727 if !data.omit {
728 engine
729 .mouth
730 .push_slice_rev(&data.columns[data.currindex].left);
731 }
732 if data.span {
733 data.span = false
734 } else {
735 engine
736 .state
737 .push(engine.aux, GroupType::Align, engine.mouth.line_number());
738 engine.stomach.data_mut().open_lists.push(match mode {
739 BoxType::Vertical => NodeList::Vertical {
740 children: vec![],
741 tp: VerticalNodeListType::VAlignCell(engine.mouth.start_ref(), 0),
742 },
743 _ => NodeList::Horizontal {
744 children: vec![],
745 tp: HorizontalNodeListType::HAlignCell(engine.mouth.start_ref(), 0),
746 },
747 })
748 }
749 }
750 }
751}
752
753pub(in crate::commands) fn pop_align_cell<ET: EngineTypes>(
754 state: &mut ET::State,
755 aux: &mut EngineAux<ET>,
756 stomach: &mut ET::Stomach,
757 mouth: &mut ET::Mouth,
758 inner_mode: BoxType,
759) {
760 match inner_mode {
761 BoxType::Vertical => pop_align_cell_v(state, aux, stomach, mouth),
762 _ => pop_align_cell_h(state, aux, stomach, mouth),
763 }
764}
765
766fn pop_align_cell_v<ET: EngineTypes>(
767 state: &mut ET::State,
768 aux: &mut EngineAux<ET>,
769 stomach: &mut ET::Stomach,
770 mouth: &mut ET::Mouth,
771) {
772 let (children, start, spans) = match stomach.data_mut().open_lists.pop() {
773 Some(NodeList::Vertical {
774 children,
775 tp: VerticalNodeListType::VAlignCell(start, i),
776 }) => (children, start, i),
777 _ => unreachable!(),
778 };
779 state.pop(aux, mouth);
780 let bx = TeXBox::V {
781 children: children.into(),
782 start,
783 info: VBoxInfo::VAlignCell { to: None, spans },
784 end: mouth.current_sourceref(),
785 };
786 match stomach.data_mut().open_lists.last_mut() {
787 Some(NodeList::Vertical {
788 children,
789 tp: VerticalNodeListType::VAlignColumn(..),
790 }) => children.push(VNode::Box(bx)),
791 _ => unreachable!(),
792 }
793}
794fn pop_align_cell_h<ET: EngineTypes>(
795 state: &mut ET::State,
796 aux: &mut EngineAux<ET>,
797 stomach: &mut ET::Stomach,
798 mouth: &mut ET::Mouth,
799) {
800 let (children, start, spans) = match stomach.data_mut().open_lists.pop() {
801 Some(NodeList::Horizontal {
802 children,
803 tp: HorizontalNodeListType::HAlignCell(start, i),
804 }) => (children, start, i),
805 _ => unreachable!(),
806 };
807 state.pop(aux, mouth);
808 let bx = TeXBox::H {
809 children: children.into(),
810 start,
811 info: HBoxInfo::new_cell(spans),
812 end: mouth.current_sourceref(),
813 preskip: None,
814 };
815 match stomach.data_mut().open_lists.last_mut() {
816 Some(NodeList::Horizontal {
817 children,
818 tp: HorizontalNodeListType::HAlignRow(..),
819 }) => children.push(HNode::Box(bx)),
820 _ => unreachable!(),
821 }
822}
823
824pub(in crate::commands) fn pop_align_row<ET: EngineTypes>(
825 stomach: &mut ET::Stomach,
826 mouth: &mut ET::Mouth,
827 inner_mode: BoxType,
828) {
829 match inner_mode {
830 BoxType::Vertical => pop_align_row_v::<ET>(stomach, mouth),
831 _ => pop_align_row_h::<ET>(stomach, mouth),
832 }
833}
834
835fn pop_align_row_v<ET: EngineTypes>(stomach: &mut ET::Stomach, mouth: &mut ET::Mouth) {
836 let (children, start) = match stomach.data_mut().open_lists.pop() {
837 Some(NodeList::Vertical {
838 children,
839 tp: VerticalNodeListType::VAlignColumn(start),
840 }) => (children, start),
841 _ => unreachable!(),
842 };
843 let bx = TeXBox::V {
844 children: children.into(),
845 start,
846 info: VBoxInfo::VAlignColumn,
847 end: mouth.current_sourceref(),
848 };
849 match stomach.data_mut().open_lists.last_mut() {
850 Some(NodeList::Horizontal {
851 children,
852 tp: HorizontalNodeListType::VAlign,
853 }) => children.push(HNode::Box(bx)),
854 _ => unreachable!(),
855 }
856}
857
858fn pop_align_row_h<ET: EngineTypes>(stomach: &mut ET::Stomach, mouth: &mut ET::Mouth) {
859 let (children, start) = match stomach.data_mut().open_lists.pop() {
860 Some(NodeList::Horizontal {
861 children,
862 tp: HorizontalNodeListType::HAlignRow(start),
863 }) => (children, start),
864 _ => unreachable!(),
865 };
866 let bx = TeXBox::H {
867 children: children.into(),
868 start,
869 info: HBoxInfo::HAlignRow,
870 end: mouth.current_sourceref(),
871 preskip: None,
872 };
873 match stomach.data_mut().open_lists.last_mut() {
874 Some(NodeList::Vertical {
875 children,
876 tp: VerticalNodeListType::HAlign,
877 }) => children.push(VNode::Box(bx)),
878 _ => unreachable!(),
879 }
880}
881
882pub(crate) const END_TEMPLATE: &str = "!\"$%&/(endtemplate)\\&%$\"!";
883pub(crate) const END_TEMPLATE_ROW: &str = "!\"$%&/(endtemplate_row)\\&%$\"!";
884
885pub(crate) fn un_x<ET: EngineTypes>(
886 engine: &mut EngineReferences<ET>,
887 v: fn(&VNode<ET>) -> bool,
888 h: fn(&HNode<ET>) -> bool,
889 m: fn(&MathNode<ET, UnresolvedMathFontStyle<ET>>) -> bool,
890) {
891 let data = engine.stomach.data_mut();
892 match data.open_lists.last_mut() {
893 None => (), Some(NodeList::Vertical { children, .. }) => {
895 for (i, n) in children.iter().enumerate().rev() {
896 if n.opaque() {
897 continue;
898 }
899 if v(n) {
900 children.remove(i);
901 return;
902 }
903 break;
904 }
905 }
906 Some(NodeList::Horizontal { children, .. }) => {
907 for (i, n) in children.iter().enumerate().rev() {
908 if n.opaque() {
909 continue;
910 }
911 if h(n) {
912 children.remove(i);
913 return;
914 }
915 break;
916 }
917 }
918 Some(NodeList::Math { children, .. }) => {
919 for (i, n) in children.list().iter().enumerate().rev() {
920 if n.opaque() {
921 continue;
922 }
923 if m(n) {
924 children.list_mut().remove(i);
925 return;
926 }
927 break;
928 }
929 }
930 }
931}
932
933pub(crate) fn last_x<R, ET: EngineTypes>(
934 engine: &mut EngineReferences<ET>,
935 v: fn(&VNode<ET>) -> Option<R>,
936 h: fn(&HNode<ET>) -> Option<R>,
937 m: fn(&MathNode<ET, UnresolvedMathFontStyle<ET>>) -> Option<R>,
938) -> Option<R> {
939 let data = engine.stomach.data_mut();
940 match data.open_lists.last_mut() {
941 None => {
942 for n in data.page.iter().rev() {
943 if n.opaque() {
944 continue;
945 }
946 return v(n);
947 }
948 }
949 Some(NodeList::Vertical { children, .. }) => {
950 for n in children.iter().rev() {
951 if n.opaque() {
952 continue;
953 }
954 return v(n);
955 }
956 }
957 Some(NodeList::Horizontal { children, .. }) => {
958 for n in children.iter().rev() {
959 if n.opaque() {
960 continue;
961 }
962 return h(n);
963 }
964 }
965 Some(NodeList::Math { children, .. }) => {
966 for n in children.list_mut().iter().rev() {
967 if n.opaque() {
968 continue;
969 }
970 return m(n);
971 }
972 }
973 }
974 None
975}
976
977pub(crate) fn do_leaders<ET: EngineTypes>(
978 engine: &mut EngineReferences<ET>,
979 tp: LeaderType,
980 tk: &ET::Token,
981) -> TeXResult<(), ET> {
982 match engine.read_keywords(&[b"width", b"height", b"depth"])? {
983 Some(_) => {
984 return Err(TeXError::General(
985 "Not yet implemented: leaders with width/height/depth".to_string(),
986 ))
987 }
988 _ => crate::expand_loop!(engine,token,
989 ResolvedToken::Cmd(Some(TeXCommand::Primitive {cmd:PrimitiveCommand::Box(read),..})) => {
990 match read(engine,token)? {
991 either::Left(None) => return Err(TeXError::General("Box expected for leaders\nTODO: Better error message".to_string())),
992 either::Left(Some(bx)) => return leaders_skip(engine,LeaderBody::Box(bx),tp,tk),
993 either::Right(bi) => {
994 let tk = tk.clone();
995 let target = BoxTarget::<ET>::new(move |e,b| leaders_skip(e,LeaderBody::Box(b),tp,&tk));
996 let mut ls = bi.open_list(engine.mouth.start_ref());
997 match ls {
998 NodeList::Horizontal {tp:HorizontalNodeListType::Box(_,_,ref mut t),..} => *t = target,
999 NodeList::Vertical {tp:VerticalNodeListType::Box(_,_,ref mut t),..} => *t = target,
1000 _ => unreachable!()
1001 }
1002 engine.stomach.data_mut().open_lists.push(ls);
1003 return Ok(())
1004 }
1005 }
1006 }
1007 ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,..})) if *name == PRIMITIVES.hrule || *name == PRIMITIVES.vrule => {
1008 let mut width = None;
1009 let mut height = None;
1010 let mut depth = None;
1011 loop {
1012 match engine.read_keywords(&[b"width",b"height",b"depth"])? {
1013 Some(b"width") => {
1014 width = Some(engine.read_dim(false,tk)?);
1015 }
1016 Some(b"height") => {
1017 height = Some(engine.read_dim(false,tk)?);
1018 }
1019 Some(b"depth") => {
1020 depth = Some(engine.read_dim(false,tk)?);
1021 }
1022 _ => break
1023 }
1024 }
1025 return leaders_skip(engine,LeaderBody::Rule {width,height,depth},tp,tk)
1026 }
1027 _ => return Err(TeXError::General("Box expected for leaders\nTODO: Better error message".to_string()))
1028 ),
1029 }
1030 Err(TeXError::General(
1031 "File ended unexpectedly\nTODO: Better error message".to_string(),
1032 ))
1033}
1034
1035fn leaders_skip<ET: EngineTypes>(
1036 engine: &mut EngineReferences<ET>,
1037 body: LeaderBody<ET>,
1038 tp: LeaderType,
1039 tk: &ET::Token,
1040) -> TeXResult<(), ET> {
1041 crate::expand_loop!(engine,token,
1042 ResolvedToken::Cmd(Some(TeXCommand::Primitive{name,..})) => {
1043 let skip = match *name {
1044 n if n == PRIMITIVES.vskip => LeaderSkip::VSkip(engine.read_skip(false,tk)?),
1045 n if n == PRIMITIVES.hskip => LeaderSkip::HSkip(engine.read_skip(false,tk)?),
1046 n if n == PRIMITIVES.vfil => LeaderSkip::VFil,
1047 n if n == PRIMITIVES.hfil => LeaderSkip::HFil,
1048 n if n == PRIMITIVES.vfill => LeaderSkip::VFill,
1049 n if n == PRIMITIVES.hfill => LeaderSkip::HFill,
1050 _ => return Err(TeXError::General("Glue expected for leaders\nTODO: Better error message".to_string()))
1051 };
1052 let leaders = Leaders {skip,body,tp};
1053 crate::add_node!(ET::Stomach;engine,VNode::Leaders(leaders),HNode::Leaders(leaders),MathNode::Leaders(leaders));
1054 return Ok(())
1055 }
1056 _ => return Err(TeXError::General("Glue expected for leaders\nTODO: Better error message".to_string()))
1057 );
1058 Err(TeXError::General(
1059 "File ended unexpectedly\nTODO: Better error message".to_string(),
1060 ))
1061}
1062
1063pub(crate) fn do_math_class<ET: EngineTypes>(
1064 engine: &mut EngineReferences<ET>,
1065 cls: Option<MathClass>,
1066 in_token: &ET::Token,
1067) -> TeXResult<(), ET> {
1068 engine.read_char_or_math_group(
1069 in_token,
1070 |(), engine, mc| {
1071 let mut atom = mc.to_atom();
1072 if let Some(cls) = cls {
1073 let MathNucleus::Simple { cls: ocls, .. } = &mut atom.nucleus else {
1074 unreachable!()
1075 };
1076 *ocls = cls;
1077 }
1078 ET::Stomach::add_node_m(engine, MathNode::Atom(atom));
1079 Ok(())
1080 },
1081 move |()| {
1082 ListTarget::<ET, _>::new(move |engine, mut children, start| {
1083 if children.len() == 1 {
1084 let mut child = children.pop().unwrap_or_else(|| unreachable!());
1085 if let Some(cls) = cls {
1086 if let MathNode::Atom(MathAtom {
1087 sub: None,
1088 sup: None,
1089 nucleus: MathNucleus::Simple { cls: ocls, .. },
1090 }) = &mut child
1091 {
1092 *ocls = cls;
1093 }
1094 }
1095 ET::Stomach::add_node_m(engine, child);
1096 return Ok(());
1097 }
1098
1099 let node = MathNode::Atom(MathAtom {
1100 sub: None,
1101 sup: None,
1102 nucleus: match cls {
1103 None => MathNucleus::Inner(MathKernel::List {
1104 start,
1105 children: children.into(),
1106 end: engine.mouth.current_sourceref(),
1107 }),
1108 Some(cls) => MathNucleus::Simple {
1109 kernel: MathKernel::List {
1110 start,
1111 children: children.into(),
1112 end: engine.mouth.current_sourceref(),
1113 },
1114 cls,
1115 limits: None,
1116 },
1117 },
1118 });
1119 ET::Stomach::add_node_m(engine, node);
1120 Ok(())
1121 })
1122 },
1123 (),
1124 )
1125}
1126
1127impl<ET: EngineTypes> EngineReferences<'_, ET> {
1128 pub fn expand(&mut self, token: ET::Token) -> TeXResult<(), ET> {
1130 match self.resolve(&token) {
1131 ResolvedToken::Cmd(Some(cmd)) => match cmd {
1132 TeXCommand::Macro(m) => ET::Gullet::do_macro(self, m.clone(), token),
1133 TeXCommand::Primitive {
1134 name,
1135 cmd: PrimitiveCommand::Conditional(cond),
1136 } => ET::Gullet::do_conditional(self, *name, token, *cond, false),
1137 TeXCommand::Primitive {
1138 name,
1139 cmd: PrimitiveCommand::Expandable(expand),
1140 } => ET::Gullet::do_expandable(self, *name, token, *expand),
1141 TeXCommand::Primitive {
1142 name,
1143 cmd: PrimitiveCommand::SimpleExpandable(exp),
1144 } => ET::Gullet::do_simple_expandable(self, *name, token, *exp),
1145 _ => self.requeue(token),
1146 },
1147 _ => self.requeue(token),
1148 }
1149 }
1150
1151 pub fn read_register_index(
1154 &mut self,
1155 skip_eq: bool,
1156 in_token: &ET::Token,
1157 ) -> TeXResult<usize, ET> {
1158 let idx = self.read_int(skip_eq, in_token)?;
1159 match ET::State::register_index(idx) {
1160 Some(idx) => Ok(idx),
1161 None => Err(TeXError::General(
1162 "Invalid register index\nTODO: Better error message".to_string(),
1163 )),
1164 }
1165 }
1166 pub fn mathfont_index(&mut self, skip_eq: bool, in_token: &ET::Token) -> TeXResult<u8, ET> {
1168 let idx = self.read_int(skip_eq, in_token)?.into();
1169 if !(0..=15).contains(&idx) {
1170 return Err(TeXError::General(
1171 "Invalid math font index\nTODO: Better error message".to_string(),
1172 ));
1173 }
1174 Ok(idx as u8)
1175 }
1176 pub fn skip_argument(&mut self, token: &ET::Token) -> TeXResult<(), ET> {
1180 let t = self.need_next(false, token)?;
1181 if t.command_code() != CommandCode::BeginGroup {
1182 TeXError::missing_begingroup(self.aux, self.state, self.mouth)?;
1183 }
1184 self.read_until_endgroup(token, |_, _, _| Ok(()))?;
1185 Ok(())
1186 }
1187
1188 pub fn read_csname(&mut self) -> TeXResult<ET::CSName, ET> {
1192 *self.gullet.csnames() += 1;
1193 let mut namev = vec![];
1194 crate::expand_loop!(self,token,
1195 ResolvedToken::Tk {char,..} => namev.push(char),
1196 ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,..})) if *name == PRIMITIVES.endcsname => {
1197 *self.gullet.csnames() -= 1;
1198 let id = self.aux.memory.cs_interner_mut().cs_from_chars(&namev);
1199 return Ok(id)
1201 }
1202 ResolvedToken::Cmd(_) => return Err(TeXError::General("Unexpandable command in \\csname\nTODO: Better error message".to_string()))
1203 );
1204 Err(TeXError::General(
1205 "File ended while reading \\csname\nTODO: Better error message".to_string(),
1206 ))
1207 }
1208 pub fn read_file_index(&mut self, in_token: &ET::Token) -> TeXResult<u8, ET> {
1211 let idx = self.read_int(false, in_token)?.into();
1212 if !(0..=255).contains(&idx) {
1213 return Err(TeXError::General(
1214 "Invalid file stream index\nTODO: Better error message".to_string(),
1215 ));
1216 }
1217 Ok(idx as u8)
1218 }
1219
1220 pub fn read_filename_and_index(
1224 &mut self,
1225 prefix: &str,
1226 in_token: &ET::Token,
1227 ) -> TeXResult<(u8, ET::File), ET> {
1228 let idx = self.read_file_index(in_token)?;
1229 let mut filename = self.aux.memory.get_string();
1230 self.read_string(true, &mut filename, in_token)?;
1231 filename.insert_str(0, prefix);
1232 let file = self.filesystem.get(&filename);
1233 self.aux.memory.return_string(filename);
1234 Ok((idx, file))
1235 }
1236
1237 pub fn do_the<
1241 F: FnMut(&mut EngineAux<ET>, &ET::State, &mut ET::Gullet, ET::Token) -> TeXResult<(), ET>,
1242 >(
1243 &mut self,
1244 mut cont: F,
1245 ) -> TeXResult<(), ET> {
1246 expand_loop!(self,token,
1247 ResolvedToken::Cmd(Some(c)) => return c.clone().the(self,token,|a,s,g,t|cont(a,s,g,t).expect("the continuation function should not throw errors on Other characters")),
1248 _ => return Err(TeXError::General("command expected after \\the\nTODO: Better error message".to_string()))
1249 );
1250 Err(TeXError::General(
1251 "File ended while reading command for \\the\nTODO: Better error message".to_string(),
1252 ))
1253 }
1254
1255 pub fn read_opt_delimiter(
1258 &mut self,
1259 in_token: &ET::Token,
1260 ) -> TeXResult<Option<Delimiter<ET>>, ET> {
1261 crate::expand_loop!(self,token,
1262 ResolvedToken::Cmd(Some(TeXCommand::Primitive {name,..})) if *name == PRIMITIVES.delimiter => {
1263 let num = self.read_int(false,in_token)?;
1264 return Ok(Some(match Delimiter::from_int(num,self.state) {
1265 either::Left(d) => d,
1266 either::Right((d,i)) => {
1267 self.general_error(format!("Bad delimiter code ({})",i))?;
1268 d
1269 }
1270 }))
1271 }
1272 ResolvedToken::Tk{char,code:CommandCode::Letter|CommandCode::Other,..} => {
1273 let num = self.state.get_delcode(char);
1274 if num == ET::Int::default() {return Ok(None)}
1275 return Ok(Some(match Delimiter::from_int(num,self.state) {
1276 either::Left(d) => d,
1277 either::Right((d,i)) => {
1278 self.general_error(format!("Bad delimiter code ({})",i))?;
1279 d
1280 }
1281 }))
1282 }
1283 _ => return Err(TeXError::General("Unexpected token for delimiter\nTODO: Better error message".to_string()))
1284 );
1285 Err(TeXError::General(
1286 "File ended while expecting delimiter\nTODO: Better error message".to_string(),
1287 ))
1288 }
1289}