Skip to main content

retex_engine/
math.rs

1//! Building math lists (*TeX: The Program* §1136–§1206, with e-TeX's
2//! `\middle` and LuaTeX's extensions).
3//!
4//! A formula is read into an mlist of noads (see [`crate::nodes`]), which
5//! [`crate::mlist`] converts into an hlist when the formula ends. LuaTeX
6//! differences implemented here (established with black-box tests):
7//!
8//! * there is no "insufficient symbol fonts" check;
9//! * `\mathchar`, `\Umathchar` and math characters given by `\mathchardef`
10//!   are accepted in horizontal mode, where they typeset the character
11//!   from the text font of the family (vertical mode starts a paragraph);
12//! * the `Missing delimiter` help text has no full stop after "proceed";
13//! * the `\Umath…` variants (`\Uradical`, `\Uroot`, `\Umathaccent`,
14//!   `\Uleft`, `\Uskewed`, `\Ustack`, `\Usuperscript`, …) are supported with
15//!   their keywords.
16
17use std::rc::Rc;
18
19use crate::Engine;
20use crate::build::PackSpec;
21use crate::cmd::Cmd;
22use crate::eqtb::{Loc, Val};
23use crate::input::TokKind;
24use crate::maincontrol::{IGNORE_DEPTH, Mode};
25use crate::nodes::{
26    DEFAULT_CODE, FenceData, FractionData, NodeData, NodeRef, accent_sub, fence_sub, id, noad_opt,
27    noad_sub, radical_sub,
28};
29use crate::primitives_gen::{assign_dimen as dp, assign_int as ip, assign_toks as tp};
30use crate::save::GroupCode;
31use crate::token::Token;
32
33/// A noad field that `scan_math` fills.
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum Field {
36    /// The nucleus.
37    Nucleus = 0,
38    /// The superscript.
39    Sup = 1,
40    /// The subscript.
41    Sub = 2,
42    /// The degree of a `\Uroot`.
43    Degree = 3,
44}
45
46impl Field {
47    fn from_i64(v: i64) -> Field {
48        match v & 3 {
49            0 => Field::Nucleus,
50            1 => Field::Sup,
51            2 => Field::Sub,
52            _ => Field::Degree,
53        }
54    }
55}
56
57fn field_ref(noad: NodeRef, f: Field) -> i64 {
58    (i64::from(noad.0) << 2) | f as i64
59}
60
61fn field_from_ref(v: i64) -> (NodeRef, Field) {
62    (NodeRef((v >> 2) as u32), Field::from_i64(v))
63}
64
65/// A math character: class (0–7, 8 for "math active"), family, character.
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67pub struct MathChar {
68    /// The class.
69    pub class: u32,
70    /// The family.
71    pub fam: u32,
72    /// The character.
73    pub chr: u32,
74}
75
76impl MathChar {
77    /// A `math_given` value (see [`Engine::scan_math_given`]).
78    pub fn from_given(g: u32) -> MathChar {
79        if g & crate::codes::MATH_GIVEN_PACKED != 0 {
80            MathChar::from_packed(i64::from(g & !crate::codes::MATH_GIVEN_PACKED))
81        } else {
82            MathChar::from_classic(g as i32)
83        }
84    }
85
86    /// Decodes TeX's 15-bit form (`"8000` is math active).
87    pub fn from_classic(c: i32) -> MathChar {
88        if c >= 0x8000 {
89            return MathChar {
90                class: 8,
91                fam: 0,
92                chr: 0,
93            };
94        }
95        let c = c.max(0) as u32;
96        MathChar {
97            class: c >> 12,
98            fam: (c >> 8) & 0xF,
99            chr: c & 0xFF,
100        }
101    }
102
103    /// Decodes LuaTeX's packed form (`\Umathcharnum`, `\mathcode`).
104    pub fn from_packed(v: i64) -> MathChar {
105        if v == crate::codes::MATH_ACTIVE {
106            return MathChar {
107                class: 8,
108                fam: 0,
109                chr: 0,
110            };
111        }
112        MathChar {
113            class: crate::codes::math_class(v),
114            fam: crate::codes::math_fam(v),
115            chr: crate::codes::math_char(v),
116        }
117    }
118}
119
120/// The parts (small family, small character, large family, large
121/// character) of an internal delimiter code, or `None` for −1.
122pub fn delcode_parts(v: i64) -> Option<(u32, u32, u32, u32)> {
123    if v < 0 {
124        return None;
125    }
126    if v & (1 << 62) != 0 {
127        Some((
128            ((v >> 53) & 0xF) as u32,
129            ((v >> 32) & 0xFF) as u32,
130            ((v >> 21) & 0xF) as u32,
131            (v & 0xFF) as u32,
132        ))
133    } else {
134        Some((((v >> 21) & 0xFF) as u32, (v & 0x1F_FFFF) as u32, 0, 0))
135    }
136}
137
138/// The noad subtype for a math class.
139fn class_subtype(class: u32) -> u16 {
140    match class {
141        1 => noad_sub::OP_DISPLAY_LIMITS,
142        2 => noad_sub::BIN,
143        3 => noad_sub::REL,
144        4 => noad_sub::OPEN,
145        5 => noad_sub::CLOSE,
146        6 => noad_sub::PUNCT,
147        _ => noad_sub::ORD,
148    }
149}
150
151/// `cramped_style`.
152pub const fn cramped_style(s: i32) -> i32 {
153    2 * (s / 2) + 1
154}
155
156/// `sub_style`.
157pub const fn sub_style(s: i32) -> i32 {
158    2 * (s / 4) + 4 + 1
159}
160
161/// `sup_style`.
162pub const fn sup_style(s: i32) -> i32 {
163    2 * (s / 4) + 4 + (s % 2)
164}
165
166/// `num_style`.
167pub const fn num_style(s: i32) -> i32 {
168    s + 2 - 2 * (s / 6)
169}
170
171/// `denom_style`.
172pub const fn denom_style(s: i32) -> i32 {
173    2 * (s / 2) + 1 + 2 - 2 * (s / 6)
174}
175
176impl Engine {
177    // ----- node constructors ------------------------------------------
178
179    /// A new noad of the given subtype with empty fields (`new_noad`).
180    pub fn new_noad(&mut self, sub: u16) -> NodeRef {
181        self.new_node(id::NOAD, sub, NodeData::Noad(Box::default()))
182    }
183
184    /// A new `math_char` kernel.
185    pub fn new_math_char(&mut self, fam: u32, chr: u32) -> NodeRef {
186        self.new_node(id::MATH_CHAR, 0, NodeData::MathChar { fam, chr })
187    }
188
189    /// A new `sub_box` or `sub_mlist` kernel.
190    pub fn new_sub_list(&mut self, mlist: bool, head: NodeRef) -> NodeRef {
191        self.new_node(
192            if mlist { id::SUB_MLIST } else { id::SUB_BOX },
193            0,
194            NodeData::SubList { head },
195        )
196    }
197
198    /// A new delimiter node.
199    pub fn new_delim(&mut self, parts: (u32, u32, u32, u32)) -> NodeRef {
200        self.new_node(
201            id::DELIM,
202            0,
203            NodeData::Delim {
204                small_fam: parts.0,
205                small_char: parts.1,
206                large_fam: parts.2,
207                large_char: parts.3,
208            },
209        )
210    }
211
212    /// A new style node.
213    pub fn new_style(&mut self, s: u8) -> NodeRef {
214        self.new_node(id::STYLE, u16::from(s), NodeData::Style { style: s })
215    }
216
217    /// The kernel in field `f` of noad `p`.
218    pub fn noad_field(&self, p: NodeRef, f: Field) -> NodeRef {
219        match self.nodes.noad(p) {
220            Some(n) => match f {
221                Field::Nucleus => n.nucleus,
222                Field::Sup => n.sup,
223                Field::Sub => n.sub,
224                Field::Degree => n.degree,
225            },
226            None => NodeRef::NULL,
227        }
228    }
229
230    /// Replaces the kernel in field `f` of noad `p` (the old one is not
231    /// freed).
232    pub fn set_noad_field(&mut self, p: NodeRef, f: Field, k: NodeRef) {
233        if let Some(n) = self.nodes.noad_mut(p) {
234            match f {
235                Field::Nucleus => n.nucleus = k,
236                Field::Sup => n.sup = k,
237                Field::Sub => n.sub = k,
238                Field::Degree => n.degree = k,
239            }
240        }
241    }
242
243    // ----- entering math mode -----------------------------------------
244
245    /// Enters a math level (`push_math`).
246    fn push_math(&mut self, c: GroupCode, style: i32) {
247        self.push_nest(Mode::Math);
248        self.cur_list_mut().incompleat_noad = NodeRef::NULL;
249        self.cur_list_mut().math_style = style;
250        self.new_save_level(c);
251        if c == GroupCode::MathShift {
252            self.begin_math_dirs();
253        }
254    }
255
256    /// `$` in horizontal mode (`init_math`).
257    pub fn init_math(&mut self) {
258        let shift_cs = self.cur_cmd == Cmd::MathShiftCs;
259        let display = if shift_cs {
260            // \Ustartmath (2) or \Ustartdisplaymath (0).
261            self.cur_chr == 0 && self.mode == Mode::Horizontal
262        } else {
263            self.get_token();
264            if self.cur_cmd == Cmd::MathShift && self.mode == Mode::Horizontal {
265                true
266            } else {
267                self.back_input();
268                false
269            }
270        };
271        if display {
272            self.go_into_display_math();
273        } else {
274            self.go_into_ordinary_math();
275        }
276    }
277
278    fn go_into_ordinary_math(&mut self) {
279        self.push_math(GroupCode::MathShift, 2);
280        self.eq_define(Loc::IntPar(ip::FAM), Val::Int(-1));
281        let t = self.eqtb.toks_param(tp::EVERYMATH).clone();
282        if !t.is_empty() {
283            self.begin_token_list(t, TokKind::EveryMathText);
284        }
285    }
286
287    /// Starts a display: breaks the paragraph so far into lines and
288    /// computes `\predisplaysize`, `\displaywidth` and `\displayindent`.
289    fn go_into_display_math(&mut self) {
290        let max_dimen = 0o7777777777;
291        let mut w = -max_dimen;
292        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
293        let only_local_par = self.nodes.next(head) == tail
294            && matches!(self.nodes.data(tail), NodeData::LocalPar { .. });
295        if head == tail || only_local_par {
296            // `\noindent$$` or `$${ }$$`.
297            if only_local_par {
298                self.nodes.flush_list(tail);
299                self.nodes.set_next(head, NodeRef::NULL);
300            }
301            self.pop_nest();
302        } else {
303            let wp = self.eqtb.int(ip::DISPLAYWIDOWPENALTY);
304            self.line_break(wp);
305            w = self.pre_display_width();
306        }
307        // Now in vertical mode: the length and shift of the display lines.
308        let prev_graf = self.cur_list().prev_graf;
309        let (l, s) = self.display_line_size(prev_graf);
310        self.push_math(GroupCode::MathShift, 0);
311        self.mode = Mode::DisplayMath;
312        self.cur_list_mut().mode = Mode::DisplayMath;
313        self.eq_define(Loc::IntPar(ip::FAM), Val::Int(-1));
314        self.eq_define(Loc::DimenPar(dp::PREDISPLAYSIZE), Val::Int(i64::from(w)));
315        self.eq_define(Loc::DimenPar(dp::DISPLAYWIDTH), Val::Int(i64::from(l)));
316        self.eq_define(Loc::DimenPar(dp::DISPLAYINDENT), Val::Int(i64::from(s)));
317        let t = self.eqtb.toks_param(tp::EVERYDISPLAY).clone();
318        if !t.is_empty() {
319            self.begin_token_list(t, TokKind::EveryDisplayText);
320        }
321        if self.nest.len() == 2 {
322            self.build_page();
323        }
324    }
325
326    /// The natural width by which the last line of the paragraph before a
327    /// display extends to the right, plus two quads (`\predisplaygapfactor`
328    /// thousandths of a quad); `max_dimen` if it depends on glue setting.
329    fn pre_display_width(&mut self) -> i32 {
330        let max_dimen = 0o7777777777;
331        let jb = self.ts.just_box;
332        if jb.is_null() {
333            return -max_dimen;
334        }
335        let Some(b) = self.nodes.box_data(jb).cloned() else {
336            return -max_dimen;
337        };
338        let f = self.eqtb.cur_font.val;
339        let gap = self.eqtb.int(ip::PREDISPLAYGAPFACTOR);
340        let q = self.quad(f);
341        // Observed: (quad div 1000) * \predisplaygapfactor.
342        let extra = (q / 1000).wrapping_mul(gap);
343        let mut v = b.shift + extra;
344        let mut w = -max_dimen;
345        let mut p = b.head;
346        while !p.is_null() {
347            let (d, visible) = match self.nodes.data(p) {
348                NodeData::Glyph(_) => (self.glyph_dimensions(p).0, true),
349                NodeData::Box(bx) => (bx.width, true),
350                NodeData::Rule { width, .. } => (*width, true),
351                NodeData::Kern { kern, .. } => (*kern, false),
352                NodeData::MarginKern { width, .. } => (*width, false),
353                NodeData::Math { .. } => (self.nodes.width(p), false),
354                NodeData::Glue { spec, .. } => {
355                    let spec = *spec;
356                    if b.glue_sign == 1 {
357                        if b.glue_order == spec.stretch_order && spec.stretch != 0 {
358                            v = max_dimen;
359                        }
360                    } else if b.glue_sign == 2
361                        && b.glue_order == spec.shrink_order
362                        && spec.shrink != 0
363                    {
364                        v = max_dimen;
365                    }
366                    (
367                        spec.width,
368                        self.nodes.subtype(p) >= crate::nodes::glue_sub::A_LEADERS,
369                    )
370                }
371                _ => (0, false),
372            };
373            if visible {
374                if v < max_dimen {
375                    v += d;
376                    w = v;
377                } else {
378                    w = max_dimen;
379                    break;
380                }
381            } else if v < max_dimen {
382                v += d;
383            }
384            p = self.nodes.next(p);
385        }
386        w
387    }
388
389    /// The length and shift of line `prev_graf + 2` (§1149).
390    fn display_line_size(&self, prev_graf: i32) -> (i32, i32) {
391        let hsize = self.eqtb.dimen(dp::HSIZE);
392        match &self.eqtb.par_shape.val {
393            None => {
394                let hi = self.eqtb.dimen(dp::HANGINDENT);
395                let ha = self.eqtb.int(ip::HANGAFTER);
396                if hi != 0 && ((ha >= 0 && prev_graf + 2 > ha) || (prev_graf + 1 < -ha)) {
397                    (hsize - hi.abs(), if hi > 0 { hi } else { 0 })
398                } else {
399                    (hsize, 0)
400                }
401            }
402            Some(shape) => {
403                let n = (shape.len() / 2) as i32;
404                let i = if prev_graf + 2 >= n { n } else { prev_graf + 2 };
405                let k = 2 * (i.max(1) as usize - 1);
406                let indent = shape.get(k).copied().unwrap_or(0);
407                let len = shape.get(k + 1).copied().unwrap_or(hsize);
408                (len, indent)
409            }
410        }
411    }
412
413    // ----- the main switch --------------------------------------------
414
415    /// Commands in math mode (the `mmode+…` cases of `main_control`).
416    pub fn math_command(&mut self) {
417        let cmd = self.cur_cmd;
418        match cmd {
419            Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven => {
420                let code = self.eqtb.math.get(self.cur_chr);
421                self.set_math_char(MathChar::from_packed(code));
422            }
423            Cmd::CharNum => {
424                self.scan_char_num();
425                self.cur_chr = self.cur_val as u32;
426                let code = self.eqtb.math.get(self.cur_chr);
427                self.set_math_char(MathChar::from_packed(code));
428            }
429            Cmd::MathCharNum => {
430                let mc = self.scan_math_char_num();
431                self.set_math_char(mc);
432            }
433            Cmd::MathGiven => {
434                let mc = MathChar::from_given(self.cur_chr);
435                self.set_math_char(mc);
436            }
437            Cmd::XmathGiven => {
438                let mc = MathChar::from_packed(i64::from(self.cur_chr));
439                self.set_math_char(mc);
440            }
441            Cmd::DelimNum => {
442                let mc = self.scan_delim_num_char();
443                self.set_math_char(mc);
444            }
445            Cmd::LeftBrace => {
446                let n = self.new_noad(noad_sub::ORD);
447                self.tail_append(n);
448                self.back_input();
449                let st = self.cur_list().math_style;
450                self.scan_math(n, Field::Nucleus, st);
451            }
452            Cmd::MathComp => {
453                let sub = self.cur_chr as u16;
454                let n = self.new_noad(sub);
455                self.tail_append(n);
456                let st = self.cur_list().math_style;
457                let st = if sub == noad_sub::OVER || sub == noad_sub::UNDER {
458                    if sub == noad_sub::OVER {
459                        cramped_style(st)
460                    } else {
461                        st
462                    }
463                } else {
464                    st
465                };
466                self.scan_math(n, Field::Nucleus, st);
467            }
468            Cmd::LimitSwitch => self.math_limit_switch(),
469            Cmd::SupMark | Cmd::SubMark => {
470                let sup = cmd == Cmd::SupMark;
471                self.sub_sup(sup, false);
472            }
473            Cmd::SuperSubScript => {
474                let sup = self.cur_chr == Cmd::SupMark as u32;
475                self.sub_sup(sup, false);
476            }
477            Cmd::NoSuperSubScript => {
478                let sup = self.cur_chr == Cmd::SupMark as u32;
479                self.sub_sup(sup, true);
480            }
481            Cmd::Radical => self.math_radical(),
482            Cmd::Accent | Cmd::MathAccent => self.math_ac(),
483            Cmd::Vcenter => {
484                self.scan_spec(GroupCode::Vcenter, None);
485                self.normal_paragraph();
486                self.push_nest(Mode::InternalVertical);
487                self.cur_list_mut().prev_depth = IGNORE_DEPTH;
488                let t = self.eqtb.toks_param(tp::EVERYVBOX).clone();
489                if !t.is_empty() {
490                    self.begin_token_list(t, TokKind::EveryVboxText);
491                }
492            }
493            Cmd::MathStyle => {
494                let s = self.cur_chr.min(7) as u8;
495                let n = self.new_style(s);
496                self.tail_append(n);
497                self.cur_list_mut().math_style = i32::from(s);
498            }
499            Cmd::NonScript => {
500                let g = self.new_glue_node(
501                    crate::glue::Glue::ZERO,
502                    crate::nodes::glue_sub::COND_MATH_GLUE,
503                );
504                self.tail_append(g);
505            }
506            Cmd::MathChoice => {
507                if self.cur_chr == 0 {
508                    self.append_choices();
509                } else {
510                    // \Ustack: a subformula in numerator style.
511                    let n = self.new_noad(noad_sub::ORD);
512                    self.tail_append(n);
513                    let st = num_style(self.cur_list().math_style);
514                    self.scan_left_brace();
515                    let sub = self.new_sub_list(true, NodeRef::NULL);
516                    self.set_noad_field(n, Field::Nucleus, sub);
517                    self.push_saved(field_ref(n, Field::Nucleus));
518                    self.push_math(GroupCode::Math, st);
519                }
520            }
521            Cmd::Above => self.math_fraction(),
522            Cmd::MathShift => {
523                if self.save.cur_group == GroupCode::MathShift {
524                    self.after_math();
525                } else {
526                    self.off_save();
527                }
528            }
529            Cmd::MathShiftCs => {
530                // \Ustopmath (3) and \Ustopdisplaymath (1) end the
531                // formula; the start commands are not allowed here.
532                let c = self.cur_chr;
533                let ok = (c == 3 && self.mode == Mode::Math)
534                    || (c == 1 && self.mode == Mode::DisplayMath);
535                if (c == 1 || c == 3) && self.save.cur_group == GroupCode::MathShift && ok {
536                    self.after_math();
537                } else if c == 1 || c == 3 {
538                    if self.save.cur_group == GroupCode::MathShift {
539                        self.print_err("Invalid math code");
540                        self.help(&["I'm going to ignore this math shift command."]);
541                        self.error();
542                    } else {
543                        self.off_save();
544                    }
545                } else {
546                    self.report_illegal_case();
547                }
548            }
549            Cmd::EqNo => {
550                if self.privileged() {
551                    if self.save.cur_group == GroupCode::MathShift {
552                        self.start_eq_no();
553                    } else {
554                        self.off_save();
555                    }
556                }
557            }
558            Cmd::LeftRight => self.math_left_right(),
559            Cmd::Endv
560            | Cmd::ParEnd
561            | Cmd::Stop
562            | Cmd::Vskip
563            | Cmd::UnVbox
564            | Cmd::Valign
565            | Cmd::Hrule => self.insert_dollar_sign(),
566            Cmd::Halign => {
567                if self.privileged() {
568                    if self.save.cur_group == GroupCode::MathShift {
569                        self.init_align();
570                    } else {
571                        self.off_save();
572                    }
573                }
574            }
575            _ => {
576                let (c, r) = (self.cur_cmd, self.cur_chr);
577                let s = self.print_to_string(|e| e.print_cmd_chr(c, r));
578                self.not_implemented(&String::from_utf8_lossy(&s));
579            }
580        }
581    }
582
583    /// `\mathchar`, `\Umathchar` and `\Umathcharnum`.
584    fn scan_math_char_num(&mut self) -> MathChar {
585        match self.cur_chr {
586            0 => {
587                let g = self.scan_math_given();
588                MathChar::from_given(g)
589            }
590            1 => {
591                let v = self.scan_umath_triple();
592                MathChar::from_packed(v)
593            }
594            _ => {
595                self.scan_int();
596                MathChar::from_packed(i64::from(self.cur_val))
597            }
598        }
599    }
600
601    /// `\delimiter` (its 15-bit part) and `\Udelimiter` as a math character.
602    fn scan_delim_num_char(&mut self) -> MathChar {
603        if self.cur_chr == 0 {
604            self.scan_twenty_seven_bit_int();
605            MathChar::from_classic(self.cur_val / 0o10000)
606        } else {
607            let v = self.scan_umath_triple();
608            MathChar::from_packed(v)
609        }
610    }
611
612    /// Whether `\fam` is a valid family (`fam_in_range`).
613    fn fam_in_range(&self) -> Option<u32> {
614        let f = self.eqtb.int(ip::FAM);
615        if (0..crate::eqtb::NUM_FAMILIES as i32).contains(&f) {
616            Some(f as u32)
617        } else {
618            None
619        }
620    }
621
622    /// The family of a math character, taking class 7 and `\fam` into
623    /// account.
624    fn effective_fam(&self, mc: MathChar) -> u32 {
625        if mc.class == 7
626            && let Some(f) = self.fam_in_range()
627        {
628            return f;
629        }
630        let vf = self.eqtb.int(ip::VARIABLEFAM);
631        if vf >= 0
632            && mc.fam == vf as u32
633            && let Some(f) = self.fam_in_range()
634        {
635            return f;
636        }
637        mc.fam
638    }
639
640    /// Treats `cur_chr` as an active character (§1152).
641    fn treat_as_active(&mut self) {
642        let cs = self.hash.active(self.cur_chr);
643        self.cur_cs = cs;
644        let m = self.eqtb.meaning(cs).clone();
645        self.cur_cmd = m.cmd;
646        self.cur_chr = m.chr;
647        self.x_token();
648        self.back_input();
649    }
650
651    /// Appends a noad for a math character (`set_math_char`).
652    fn set_math_char(&mut self, mc: MathChar) {
653        if mc.class >= 8 {
654            self.treat_as_active();
655            return;
656        }
657        let fam = self.effective_fam(mc);
658        let k = self.new_math_char(fam, mc.chr);
659        let sub = class_subtype(mc.class);
660        let p = self.new_noad(sub);
661        self.set_noad_field(p, Field::Nucleus, k);
662        self.tail_append(p);
663    }
664
665    /// Fills field `f` of noad `p` with the next math character or
666    /// subformula (`scan_math`); `style` is the style of a subformula.
667    /// Returns true if a subformula group was opened.
668    fn scan_math(&mut self, p: NodeRef, f: Field, style: i32) -> bool {
669        self.scan_math_flag(p, f, style, 0)
670    }
671
672    /// [`scan_math`](Self::scan_math) with extra bits in the saved field
673    /// reference (bit 62: scan the nucleus when the group ends).
674    fn scan_math_flag(&mut self, p: NodeRef, f: Field, style: i32, flag: i64) -> bool {
675        self.get_nonblank_nonrelax_noncall();
676        let mc = loop {
677            match self.cur_cmd {
678                Cmd::Letter | Cmd::OtherChar | Cmd::CharGiven => {
679                    let code = self.eqtb.math.get(self.cur_chr);
680                    let mc = MathChar::from_packed(code);
681                    if mc.class == 8 {
682                        self.treat_as_active();
683                        self.get_nonblank_nonrelax_noncall();
684                        continue;
685                    }
686                    break mc;
687                }
688                Cmd::CharNum => {
689                    self.scan_char_num();
690                    self.cur_chr = self.cur_val as u32;
691                    self.cur_cmd = Cmd::CharGiven;
692                }
693                Cmd::MathCharNum => break self.scan_math_char_num(),
694                Cmd::MathGiven => break MathChar::from_given(self.cur_chr),
695                Cmd::XmathGiven => break MathChar::from_packed(i64::from(self.cur_chr)),
696                Cmd::DelimNum => break self.scan_delim_num_char(),
697                _ => {
698                    // A subformula enclosed in braces.
699                    self.back_input();
700                    self.scan_left_brace();
701                    let sub = self.new_sub_list(true, NodeRef::NULL);
702                    let old = self.noad_field(p, f);
703                    self.nodes.flush_list(old);
704                    self.set_noad_field(p, f, sub);
705                    self.push_saved(field_ref(p, f) | flag);
706                    self.push_math(GroupCode::Math, style);
707                    return true;
708                }
709            }
710        };
711        self.finish_scan_math(p, f, mc);
712        false
713    }
714
715    fn finish_scan_math(&mut self, p: NodeRef, f: Field, mc: MathChar) {
716        let fam = if mc.class == 8 {
717            0
718        } else {
719            self.effective_fam(mc)
720        };
721        let k = self.new_math_char(fam, mc.chr);
722        let old = self.noad_field(p, f);
723        self.nodes.flush_list(old);
724        self.set_noad_field(p, f, k);
725    }
726
727    /// `\limits`, `\nolimits`, `\displaylimits` (`math_limit_switch`).
728    fn math_limit_switch(&mut self) {
729        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
730        if head != tail
731            && self.nodes.id(tail) == id::NOAD
732            && matches!(
733                self.nodes.subtype(tail),
734                noad_sub::OP_DISPLAY_LIMITS | noad_sub::OP_LIMITS | noad_sub::OP_NO_LIMITS
735            )
736        {
737            self.nodes.set_subtype(tail, self.cur_chr as u16);
738            return;
739        }
740        self.print_err("Limit controls must follow a math operator");
741        self.help(&["I'm ignoring this misplaced \\limits or \\nolimits command."]);
742        self.error();
743    }
744
745    /// Scans a delimiter (`scan_delimiter`); `radical` selects TeX's
746    /// 27-bit form, `fam_char` LuaTeX's `<fam> <char>` form. Returns a new
747    /// delimiter node.
748    fn scan_delimiter(&mut self, radical: bool, fam_char: bool) -> NodeRef {
749        let parts: Option<(u32, u32, u32, u32)> = if fam_char {
750            self.scan_math_family();
751            let fam = self.cur_val as u32;
752            self.scan_char_num();
753            Some((fam, self.cur_val as u32, 0, 0))
754        } else if radical {
755            self.scan_twenty_seven_bit_int();
756            classic_delimiter(self.cur_val)
757        } else {
758            self.get_nonblank_nonrelax_noncall();
759            match self.cur_cmd {
760                Cmd::Letter | Cmd::OtherChar => delcode_parts(self.eqtb.del.get(self.cur_chr)),
761                Cmd::DelimNum => {
762                    if self.cur_chr == 0 {
763                        self.scan_twenty_seven_bit_int();
764                        classic_delimiter(self.cur_val)
765                    } else {
766                        let v = self.scan_umath_triple();
767                        let mc = MathChar::from_packed(v);
768                        Some((mc.fam, mc.chr, 0, 0))
769                    }
770                }
771                _ => None,
772            }
773        };
774        let parts = match parts {
775            Some(p) => p,
776            None => {
777                self.print_err("Missing delimiter (. inserted)");
778                self.help(&[
779                    "I was expecting to see something like `(' or `\\{' or",
780                    "`\\}' here. If you typed, e.g., `{' instead of `\\{', you",
781                    "should probably delete the `{' by typing `1' now, so that",
782                    "braces don't get unbalanced. Otherwise just proceed",
783                    "Acceptable delimiters are characters whose \\delcode is",
784                    "nonnegative, or you can use `\\delimiter <delimiter code>'.",
785                ]);
786                self.back_error();
787                (0, 0, 0, 0)
788            }
789        };
790        self.new_delim(parts)
791    }
792
793    /// `\radical` and LuaTeX's radical-like commands (`math_radical`).
794    fn math_radical(&mut self) {
795        let c = self.cur_chr as u16;
796        let p = self.new_node(id::RADICAL, c, NodeData::Noad(Box::default()));
797        self.tail_append(p);
798        let mut opts = 0u32;
799        let mut width = 0i32;
800        if c == radical_sub::UHEXTENSIBLE || c >= radical_sub::UUNDERDELIMITER {
801            loop {
802                if self.scan_keyword("width") {
803                    self.scan_normal_dimen();
804                    width = self.cur_val;
805                } else if self.scan_keyword("left") {
806                    opts |= noad_opt::LEFT;
807                } else if self.scan_keyword("middle") {
808                    opts |= noad_opt::MIDDLE;
809                } else if self.scan_keyword("right") {
810                    opts |= noad_opt::RIGHT;
811                } else {
812                    break;
813                }
814            }
815        }
816        let d = self.scan_delimiter(c == radical_sub::RADICAL, c != radical_sub::RADICAL);
817        if let Some(n) = self.nodes.noad_mut(p) {
818            n.left = d;
819            n.options = opts;
820            n.width = width;
821        }
822        let st = self.cur_list().math_style;
823        match c {
824            radical_sub::UHEXTENSIBLE => {}
825            radical_sub::UROOT => {
826                // The degree first, then the radicand. The degree is a
827                // subformula in scriptscript style.
828                // The radicand follows when the degree group ends.
829                if !self.scan_math_flag(p, Field::Degree, 6, 1 << 62) {
830                    self.scan_math(p, Field::Nucleus, cramped_style(st));
831                }
832            }
833            radical_sub::UUNDERDELIMITER => {
834                self.scan_math(p, Field::Nucleus, sub_style(st));
835            }
836            radical_sub::UOVERDELIMITER => {
837                self.scan_math(p, Field::Nucleus, sup_style(st));
838            }
839            radical_sub::UDELIMITERUNDER => {
840                self.scan_math(p, Field::Nucleus, st);
841            }
842            _ => {
843                self.scan_math(p, Field::Nucleus, cramped_style(st));
844            }
845        }
846    }
847
848    /// Scans `[fixed] <class> <fam> <char>` for `\Umathaccent`.
849    fn scan_accent_char(&mut self, umath: bool) -> (NodeRef, bool) {
850        let fixed = umath && self.scan_keyword("fixed");
851        let mc = if umath {
852            let v = self.scan_umath_triple();
853            MathChar::from_packed(v)
854        } else {
855            let g = self.scan_math_given();
856            MathChar::from_given(g)
857        };
858        let fam = if mc.class == 8 {
859            0
860        } else {
861            self.effective_fam(mc)
862        };
863        (self.new_math_char(fam, mc.chr), fixed)
864    }
865
866    /// `\mathaccent` and `\Umathaccent` (`math_ac`).
867    fn math_ac(&mut self) {
868        if self.cur_cmd == Cmd::Accent {
869            self.back_input();
870            self.get_token();
871            self.print_err("Please use ");
872            self.print_esc_str("mathaccent");
873            self.printer.print_str(" for accents in math mode");
874            self.help(&[
875                "I'm changing \\accent to \\mathaccent here; wish me luck.",
876                "(Accents are not the same in formulas as they are in text.)",
877            ]);
878            self.error();
879        }
880        let umath = self.cur_cmd == Cmd::MathAccent && self.cur_chr == 1;
881        let p = self.new_node(
882            id::ACCENT,
883            accent_sub::BOTH_FLEXIBLE,
884            NodeData::Noad(Box::default()),
885        );
886        self.tail_append(p);
887        let mut sub = accent_sub::BOTH_FLEXIBLE;
888        let (mut top, mut bot) = (NodeRef::NULL, NodeRef::NULL);
889        let fraction = 0;
890        let mut overlay = false;
891        if umath {
892            let (want_top, want_bot) = if self.scan_keyword("bottom") {
893                (false, true)
894            } else if self.scan_keyword("both") {
895                (true, true)
896            } else if self.scan_keyword("overlay") {
897                overlay = true;
898                (true, false)
899            } else {
900                let _ = self.scan_keyword("top");
901                (true, false)
902            };
903            if want_top {
904                let (k, fixed) = self.scan_accent_char(true);
905                top = k;
906                if fixed {
907                    sub |= accent_sub::FIXED_TOP;
908                }
909            }
910            if want_bot {
911                let (k, fixed) = self.scan_accent_char(true);
912                bot = k;
913                if fixed {
914                    sub |= accent_sub::FIXED_BOTTOM;
915                }
916            }
917        } else {
918            top = self.scan_accent_char(false).0;
919        }
920        self.nodes.set_subtype(p, sub);
921        if let Some(n) = self.nodes.noad_mut(p) {
922            n.accent = top;
923            n.bot_accent = bot;
924            n.fraction = fraction;
925            if overlay {
926                n.options |= noad_opt::OVERLAY;
927            }
928        }
929        let st = cramped_style(self.cur_list().math_style);
930        self.scan_math(p, Field::Nucleus, st);
931    }
932
933    /// `^` and `_` (`sub_sup`); `no_style` is set for `\Unosuperscript`
934    /// and `\Unosubscript`.
935    fn sub_sup(&mut self, sup: bool, no_style: bool) {
936        let field = if sup { Field::Sup } else { Field::Sub };
937        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
938        let mut target = NodeRef::NULL;
939        let mut t_nonempty = false;
940        if tail != head && self.nodes.scripts_allowed(tail) {
941            target = tail;
942            t_nonempty = !self.noad_field(tail, field).is_null();
943        }
944        if target.is_null() || t_nonempty {
945            // Observed: the new noad's nucleus is an empty sub-list.
946            let n = self.new_noad(noad_sub::ORD);
947            let k = self.new_sub_list(true, NodeRef::NULL);
948            self.set_noad_field(n, Field::Nucleus, k);
949            self.tail_append(n);
950            target = n;
951            if t_nonempty {
952                if sup {
953                    self.print_err("Double superscript");
954                    self.help(&["I treat `x^1^2' essentially like `x^1{}^2'."]);
955                } else {
956                    self.print_err("Double subscript");
957                    self.help(&["I treat `x_1_2' essentially like `x_1{}_2'."]);
958                }
959                self.error();
960            }
961        }
962        let st = self.cur_list().math_style;
963        let style = if no_style {
964            if let Some(n) = self.nodes.noad_mut(target) {
965                n.options |= if sup {
966                    noad_opt::NO_SUPER_SCRIPT
967                } else {
968                    noad_opt::NO_SUB_SCRIPT
969                };
970            }
971            st
972        } else if sup {
973            sup_style(st)
974        } else {
975            sub_style(st)
976        };
977        self.scan_math(target, field, style);
978    }
979
980    /// `\over`, `\atop`, `\above` and their variants (`math_fraction`).
981    fn math_fraction(&mut self) {
982        let c = self.cur_chr;
983        // LuaTeX codes: above 0, over 1, atop 2, skewed 3; +4 with delims.
984        let delimited = c >= 4;
985        let kind = c % 4;
986        if !self.cur_list().incompleat_noad.is_null() {
987            if delimited {
988                let a = self.scan_delimiter(false, false);
989                let b = self.scan_delimiter(false, false);
990                self.nodes.flush_list(a);
991                self.nodes.flush_list(b);
992            }
993            if kind == 3 {
994                let m = self.scan_delimiter(false, false);
995                self.nodes.flush_list(m);
996                self.scan_fraction_options();
997            } else if kind == 0 {
998                let _ = self.scan_keyword("exact");
999                self.scan_normal_dimen();
1000            }
1001            self.print_err("Ambiguous; you need another { and }");
1002            self.help(&[
1003                "I'm ignoring this fraction specification, since I don't",
1004                "know whether a construction like `x \\over y \\over z'",
1005                "means `{x \\over y} \\over z' or `x \\over {y \\over z}'.",
1006            ]);
1007            self.error();
1008            return;
1009        }
1010        let head = self.cur_list().head;
1011        let first = self.nodes.next(head);
1012        let num = self.new_sub_list(true, first);
1013        let mut fr = FractionData {
1014            thickness: DEFAULT_CODE,
1015            num,
1016            denom: NodeRef::NULL,
1017            left: NodeRef::NULL,
1018            right: NodeRef::NULL,
1019            middle: NodeRef::NULL,
1020            options: 0,
1021            new_hlist: NodeRef::NULL,
1022        };
1023        self.nodes.set_next(head, NodeRef::NULL);
1024        self.cur_list_mut().tail = head;
1025        if delimited {
1026            fr.left = self.scan_delimiter(false, false);
1027            fr.right = self.scan_delimiter(false, false);
1028        }
1029        match kind {
1030            0 => {
1031                if self.scan_keyword("exact") {
1032                    fr.options |= noad_opt::EXACT;
1033                }
1034                self.scan_normal_dimen();
1035                fr.thickness = self.cur_val;
1036            }
1037            1 => fr.thickness = DEFAULT_CODE,
1038            2 => fr.thickness = 0,
1039            _ => {
1040                fr.middle = self.scan_delimiter(false, false);
1041                fr.options |= self.scan_fraction_options();
1042                fr.thickness = 0;
1043            }
1044        }
1045        let n = self.new_node(id::FRACTION, 0, NodeData::Fraction(Box::new(fr)));
1046        self.cur_list_mut().incompleat_noad = n;
1047        // The denominator is read in the cramped style (observed with
1048        // \mathstyle; in \Ustack this gives the denominator style).
1049        let st = cramped_style(self.cur_list().math_style);
1050        self.cur_list_mut().math_style = st;
1051    }
1052
1053    /// The `noaxis`/`exact` options of `\Uskewed`.
1054    fn scan_fraction_options(&mut self) -> u32 {
1055        let mut o = 0;
1056        loop {
1057            if self.scan_keyword("noaxis") {
1058                o |= noad_opt::NO_AXIS;
1059            } else if self.scan_keyword("exact") {
1060                o |= noad_opt::EXACT;
1061            } else {
1062                return o;
1063            }
1064        }
1065    }
1066
1067    /// Finishes the current mlist and pops the nest (`fin_mlist`); `p`, if
1068    /// not null, is a right (or middle) fence ending the list.
1069    fn fin_mlist(&mut self, p: NodeRef) -> NodeRef {
1070        let inc = self.cur_list().incompleat_noad;
1071        let head = self.cur_list().head;
1072        let q;
1073        if !inc.is_null() {
1074            let first = self.nodes.next(head);
1075            let denom = self.new_sub_list(true, first);
1076            if let NodeData::Fraction(f) = self.nodes.data_mut(inc) {
1077                f.denom = denom;
1078            }
1079            if p.is_null() {
1080                q = inc;
1081            } else {
1082                // A \left…\over…\right: the left fence (and any middle
1083                // fences up to delim_ptr) stays outside the fraction.
1084                let num = match self.nodes.data(inc) {
1085                    NodeData::Fraction(f) => f.num,
1086                    _ => NodeRef::NULL,
1087                };
1088                let num_list = self.nodes.list_ptr_kernel(num);
1089                q = num_list;
1090                let dp = self.cur_list().delim_ptr;
1091                if num_list.is_null() || self.nodes.id(num_list) != id::FENCE || dp.is_null() {
1092                    self.confusion("right");
1093                }
1094                let after = self.nodes.next(dp);
1095                self.nodes.set_kernel_list(num, after);
1096                if !after.is_null() {
1097                    self.nodes.set_prev(after, NodeRef::NULL);
1098                }
1099                self.nodes.link(dp, inc);
1100                self.nodes.link(inc, p);
1101            }
1102        } else {
1103            let tail = self.cur_list().tail;
1104            self.nodes.link(tail, p);
1105            q = self.nodes.next(head);
1106        }
1107        self.nodes.set_next(head, NodeRef::NULL);
1108        if !q.is_null() {
1109            self.nodes.set_prev(q, NodeRef::NULL);
1110        }
1111        self.pop_nest();
1112        q
1113    }
1114
1115    /// Right braces ending math groups, `\mathchoice` groups and
1116    /// `\vcenter` (cases of `handle_right_brace`).
1117    pub fn math_right_brace(&mut self) {
1118        match self.save.cur_group {
1119            GroupCode::Math => {
1120                self.unsave();
1121                let r = self.pop_saved();
1122                let root_follows = r & (1 << 62) != 0;
1123                let (noad, f) = field_from_ref(r & !(1 << 62));
1124                let p = self.fin_mlist(NodeRef::NULL);
1125                let k = self.noad_field(noad, f);
1126                self.nodes.set_kernel_list(k, p);
1127                if !p.is_null() && self.nodes.next(p).is_null() {
1128                    let pid = self.nodes.id(p);
1129                    // Observed: \mathflattenmode says which single noads
1130                    // are flattened (1 ord, 2 bin, 4 rel, 8 punct,
1131                    // 16 inner).
1132                    let flatten = self.eqtb.int(ip::MATHFLATTENMODE);
1133                    let bit = match self.nodes.subtype(p) {
1134                        noad_sub::ORD => 1,
1135                        noad_sub::BIN => 2,
1136                        noad_sub::REL => 4,
1137                        noad_sub::PUNCT => 8,
1138                        noad_sub::INNER => 16,
1139                        _ => 0,
1140                    };
1141                    if pid == id::NOAD && flatten & bit != 0 {
1142                        let (sub, sup) = (
1143                            self.noad_field(p, Field::Sub),
1144                            self.noad_field(p, Field::Sup),
1145                        );
1146                        if sub.is_null() && sup.is_null() {
1147                            // {x}: the field becomes x's nucleus.
1148                            let nuc = self.noad_field(p, Field::Nucleus);
1149                            self.set_noad_field(p, Field::Nucleus, NodeRef::NULL);
1150                            self.nodes.flush_list(p);
1151                            self.nodes.set_kernel_list(k, NodeRef::NULL);
1152                            self.nodes.flush_list(k);
1153                            self.set_noad_field(noad, f, nuc);
1154                        }
1155                    } else if pid == id::ACCENT
1156                        && f == Field::Nucleus
1157                        && noad == self.cur_list().tail
1158                        && self.nodes.id(noad) == id::NOAD
1159                        && self.nodes.subtype(noad) == noad_sub::ORD
1160                    {
1161                        // Replace the tail of the list by the accent noad.
1162                        let head = self.cur_list().head;
1163                        let mut q = head;
1164                        while self.nodes.next(q) != noad {
1165                            q = self.nodes.next(q);
1166                        }
1167                        self.nodes.set_kernel_list(k, NodeRef::NULL);
1168                        self.nodes.link(q, p);
1169                        self.nodes.set_next(noad, NodeRef::NULL);
1170                        self.nodes.flush_list(noad);
1171                        self.cur_list_mut().tail = p;
1172                    }
1173                }
1174                if root_follows {
1175                    let st = cramped_style(self.cur_list().math_style);
1176                    self.scan_math(noad, Field::Nucleus, st);
1177                }
1178            }
1179            GroupCode::MathChoice => self.build_choices(),
1180            GroupCode::Vcenter => {
1181                if self.par_token_before_group_end(1) {
1182                    return;
1183                }
1184                self.end_graf();
1185                self.unsave();
1186                let (spec, _dir) = self.pop_spec_pub();
1187                let head = self.cur_list().head;
1188                let first = self.nodes.next(head);
1189                self.nodes.set_next(head, NodeRef::NULL);
1190                let first = self.filter_list("vpack_filter", first, "vcenter");
1191                let d = self.eqtb.dimen(dp::BOXMAXDEPTH);
1192                let b = self.vpackage(first, spec, d);
1193                self.pop_nest();
1194                let n = self.new_noad(noad_sub::VCENTER);
1195                let k = self.new_sub_list(false, b);
1196                self.set_noad_field(n, Field::Nucleus, k);
1197                self.tail_append(n);
1198            }
1199            _ => self.confusion("rightbrace"),
1200        }
1201    }
1202
1203    /// `\mathchoice` (`append_choices`).
1204    fn append_choices(&mut self) {
1205        let c = self.new_node(
1206            id::CHOICE,
1207            0,
1208            NodeData::Choice(Box::new([NodeRef::NULL; 4])),
1209        );
1210        self.tail_append(c);
1211        self.push_saved(0);
1212        self.push_math(GroupCode::MathChoice, 0);
1213        self.scan_left_brace();
1214    }
1215
1216    /// The end of one of the four `\mathchoice` lists (`build_choices`).
1217    fn build_choices(&mut self) {
1218        self.unsave();
1219        let p = self.fin_mlist(NodeRef::NULL);
1220        let k = self.pop_saved();
1221        let tail = self.cur_list().tail;
1222        if let NodeData::Choice(c) = self.nodes.data_mut(tail) {
1223            c[(k as usize).min(3)] = p;
1224        }
1225        if k >= 3 {
1226            return;
1227        }
1228        self.push_saved(k + 1);
1229        self.push_math(GroupCode::MathChoice, 2 * (k as i32 + 1));
1230        self.scan_left_brace();
1231    }
1232
1233    /// `\left`, `\middle`, `\right` and their `\U…` forms
1234    /// (`math_left_right`).
1235    fn math_left_right(&mut self) {
1236        let c = self.cur_chr;
1237        // LuaTeX codes: left 1, middle 2, right 3; \Uleft 11, \Umiddle 12,
1238        // \Uright 13, \Uvextensible 14.
1239        if c == 14 {
1240            self.math_vextensible();
1241            return;
1242        }
1243        let t = match c % 10 {
1244            1 => fence_sub::LEFT,
1245            2 => fence_sub::MIDDLE,
1246            _ => fence_sub::RIGHT,
1247        };
1248        if t != fence_sub::LEFT && self.save.cur_group != GroupCode::MathLeft {
1249            if self.save.cur_group == GroupCode::MathShift {
1250                // LuaTeX reads the delimiter without checking it.
1251                self.get_nonblank_nonrelax_noncall();
1252                if self.cur_cmd == Cmd::DelimNum {
1253                    if self.cur_chr == 0 {
1254                        self.scan_twenty_seven_bit_int();
1255                    } else {
1256                        self.scan_umath_triple();
1257                    }
1258                }
1259                self.print_err("Extra ");
1260                if t == fence_sub::MIDDLE {
1261                    self.print_esc_str("middle");
1262                    self.help(&["I'm ignoring a \\middle that had no matching \\left."]);
1263                } else {
1264                    self.print_esc_str("right");
1265                    self.help(&["I'm ignoring a \\right that had no matching \\left."]);
1266                }
1267                self.error();
1268            } else {
1269                self.off_save();
1270            }
1271            return;
1272        }
1273        let mut fe = FenceData {
1274            class: -1,
1275            ..FenceData::default()
1276        };
1277        if c >= 10 {
1278            loop {
1279                if self.scan_keyword("height") {
1280                    self.scan_normal_dimen();
1281                    fe.height = self.cur_val;
1282                } else if self.scan_keyword("depth") {
1283                    self.scan_normal_dimen();
1284                    fe.depth = self.cur_val;
1285                } else if self.scan_keyword("axis") {
1286                    fe.options |= noad_opt::AXIS;
1287                } else if self.scan_keyword("noaxis") {
1288                    fe.options |= noad_opt::NO_AXIS;
1289                } else if self.scan_keyword("exact") {
1290                    fe.options |= noad_opt::EXACT;
1291                } else if self.scan_keyword("class") {
1292                    self.scan_int();
1293                    fe.class = self.cur_val;
1294                } else {
1295                    break;
1296                }
1297            }
1298        }
1299        fe.delim = self.scan_delimiter(false, false);
1300        let p = self.new_node(id::FENCE, t, NodeData::Fence(Box::new(fe)));
1301        let q = if t == fence_sub::LEFT {
1302            p
1303        } else {
1304            let q = self.fin_mlist(p);
1305            self.unsave();
1306            q
1307        };
1308        if t != fence_sub::RIGHT {
1309            let st = self.cur_list().math_style;
1310            self.push_math(GroupCode::MathLeft, st);
1311            let head = self.cur_list().head;
1312            self.nodes.link(head, q);
1313            self.cur_list_mut().tail = p;
1314            self.cur_list_mut().delim_ptr = p;
1315        } else {
1316            let n = self.new_noad(noad_sub::INNER);
1317            let k = self.new_sub_list(true, q);
1318            self.set_noad_field(n, Field::Nucleus, k);
1319            self.tail_append(n);
1320        }
1321    }
1322
1323    /// `\Uvextensible`: a stand-alone extensible delimiter.
1324    fn math_vextensible(&mut self) {
1325        let mut fe = FenceData {
1326            class: -1,
1327            ..FenceData::default()
1328        };
1329        loop {
1330            if self.scan_keyword("height") {
1331                self.scan_normal_dimen();
1332                fe.height = self.cur_val;
1333            } else if self.scan_keyword("depth") {
1334                self.scan_normal_dimen();
1335                fe.depth = self.cur_val;
1336            } else if self.scan_keyword("axis") {
1337                fe.options |= noad_opt::AXIS;
1338            } else if self.scan_keyword("noaxis") {
1339                fe.options |= noad_opt::NO_AXIS;
1340            } else if self.scan_keyword("exact") {
1341                fe.options |= noad_opt::EXACT;
1342            } else {
1343                break;
1344            }
1345        }
1346        fe.delim = self.scan_delimiter(false, false);
1347        // Observed: a fence of subtype "no".
1348        let p = self.new_node(id::FENCE, fence_sub::NO, NodeData::Fence(Box::new(fe)));
1349        let n = self.new_noad(noad_sub::INNER);
1350        let k = self.new_sub_list(true, p);
1351        self.set_noad_field(n, Field::Nucleus, k);
1352        self.tail_append(n);
1353    }
1354
1355    /// `\eqno` and `\leqno` in display math (`start_eq_no`).
1356    fn start_eq_no(&mut self) {
1357        self.push_saved(i64::from(self.cur_chr));
1358        self.go_into_ordinary_math();
1359    }
1360
1361    /// Appends a box in math mode as an ord noad with a `sub_box`
1362    /// nucleus (§1076).
1363    pub fn math_box_end(&mut self, b: NodeRef) {
1364        let n = self.new_noad(noad_sub::ORD);
1365        let k = self.new_sub_list(false, b);
1366        self.set_noad_field(n, Field::Nucleus, k);
1367        self.tail_append(n);
1368    }
1369
1370    /// Mathchars in horizontal mode: LuaTeX typesets the character in the
1371    /// text font of its family.
1372    pub fn math_char_in_text(&mut self) {
1373        let mc = match self.cur_cmd {
1374            Cmd::MathCharNum => self.scan_math_char_num(),
1375            Cmd::MathGiven => MathChar::from_given(self.cur_chr),
1376            _ => MathChar::from_packed(i64::from(self.cur_chr)),
1377        };
1378        if mc.class >= 8 {
1379            self.treat_as_active();
1380            return;
1381        }
1382        let f = self.fam_fnt(mc.fam, 0);
1383        let g = self.new_char_node(f, mc.chr);
1384        self.tail_append(g);
1385        self.cur_list_mut().space_factor = 1000;
1386    }
1387
1388    /// The font of family `fam` in size `size` (0 text, 1 script,
1389    /// 2 scriptscript): `fam_fnt`.
1390    pub fn fam_fnt(&self, fam: u32, size: u32) -> u32 {
1391        self.eqtb
1392            .math_font
1393            .get((size * crate::eqtb::NUM_FAMILIES as u32 + fam) as usize)
1394            .map_or(0, |s| s.val)
1395    }
1396
1397    // ----- leaving math mode ------------------------------------------
1398
1399    /// The end of a formula (`after_math`).
1400    fn after_math(&mut self) {
1401        let mut m = self.mode;
1402        let mut l = false;
1403        let mut p = self.fin_mlist(NodeRef::NULL);
1404        let a;
1405        if self.mode == Mode::DisplayMath && m == Mode::Math {
1406            // The end of an equation number.
1407            self.check_dollar_follows();
1408            let hl = self.formula_to_hlist(p, 2, false);
1409            a = self.hpack(hl, PackSpec::NATURAL);
1410            self.take_outer_math_dirs();
1411            self.unsave();
1412            if self.pop_saved() == 1 {
1413                l = true;
1414            }
1415            m = self.mode;
1416            p = self.fin_mlist(NodeRef::NULL);
1417        } else {
1418            a = NodeRef::NULL;
1419        }
1420        if m == Mode::Math {
1421            self.finish_math_in_text(p);
1422        } else {
1423            if a.is_null() {
1424                self.check_dollar_follows();
1425            }
1426            self.finish_displayed_math(p, a, l);
1427        }
1428    }
1429
1430    /// The math and text directions outside the formula whose group is
1431    /// the current one.
1432    fn take_outer_math_dirs(&mut self) -> Option<(i32, i32)> {
1433        let level = self.save.cur_level;
1434        while let Some(&(l, m, t)) = self.outer_math_dirs.last() {
1435            if l < level {
1436                break;
1437            }
1438            self.outer_math_dirs.pop();
1439            if l == level {
1440                return Some((m, t));
1441            }
1442        }
1443        None
1444    }
1445
1446    /// Checks that a second `$` ends a display.
1447    fn check_dollar_follows(&mut self) {
1448        self.get_x_token();
1449        if self.cur_cmd != Cmd::MathShift
1450            && !(self.cur_cmd == Cmd::MathShiftCs && self.cur_chr == 1)
1451        {
1452            self.print_err("Display math should end with $$");
1453            self.help(&[
1454                "The `$' that I just saw supposedly matches a previous `$$'.",
1455                "So I shall assume that you typed `$$' both times.",
1456            ]);
1457            self.back_error();
1458        }
1459    }
1460
1461    fn finish_math_in_text(&mut self, p: NodeRef) {
1462        let ms = self.eqtb.dimen(dp::MATHSURROUND);
1463        let mg = self
1464            .eqtb
1465            .glue(crate::primitives_gen::assign_glue::MATHSURROUNDSKIP);
1466        let pen = self.mode == Mode::Horizontal;
1467        // As in TeX, the formula's group ends last: the opening math node
1468        // is the tail while the mlist_to_hlist callback runs (luamml reads
1469        // it), and the nodes take the attributes inside the formula.
1470        // A formula whose direction differs from the text's is wrapped in
1471        // direction nodes (see `dirs`).
1472        // The directions are those outside the formula.
1473        let (math_dir, text_dir) = self.take_outer_math_dirs().unwrap_or((
1474            self.dir_param(crate::primitives_gen::assign_dir::MATHDIR),
1475            self.dir_param(crate::primitives_gen::assign_dir::TEXTDIR),
1476        ));
1477        let wrap = (math_dir != text_dir).then(|| math_dir.rem_euclid(4) as i8);
1478        let b = self.new_math_node(ms, mg, 0);
1479        self.tail_append(b);
1480        if let Some(d) = wrap {
1481            let p = self.new_dir_node(d, false);
1482            self.tail_append(p);
1483        }
1484        let hl = self.formula_to_hlist(p, 2, pen);
1485        let tail = self.cur_list().tail;
1486        self.nodes.link(tail, hl);
1487        let mut t = tail;
1488        while !self.nodes.next(t).is_null() {
1489            t = self.nodes.next(t);
1490        }
1491        self.cur_list_mut().tail = t;
1492        if let Some(d) = wrap {
1493            let p = self.new_dir_node(d, true);
1494            self.tail_append(p);
1495        }
1496        let e = self.new_math_node(ms, mg, 1);
1497        self.tail_append(e);
1498        self.cur_list_mut().space_factor = 1000;
1499        self.unsave();
1500    }
1501
1502    /// A math node with `\mathsurround` and `\mathsurroundskip`.
1503    fn new_math_node(&mut self, surround: i32, skip: crate::glue::Glue, sub: u16) -> NodeRef {
1504        let p = self.new_node(
1505            id::MATH,
1506            sub,
1507            NodeData::Math {
1508                surround,
1509                glue: crate::glue::Glue::ZERO,
1510            },
1511        );
1512        if (skip.width != 0 || skip.stretch != 0 || skip.shrink != 0)
1513            && let NodeData::Math { glue, .. } = self.nodes.data_mut(p)
1514        {
1515            *glue = skip;
1516        }
1517        p
1518    }
1519
1520    /// Places a finished display (§1199–§1205).
1521    fn finish_displayed_math(&mut self, p: NodeRef, a: NodeRef, l: bool) {
1522        use crate::nodes::glue_sub as gs;
1523        self.take_outer_math_dirs();
1524        let hl = self.formula_to_hlist(p, 0, false);
1525        let adj_head = self.nodes.new_temp();
1526        self.ts.adjust_tail = Some(adj_head);
1527        let mut b = self.hpack(hl, PackSpec::NATURAL);
1528        let p = self.nodes.list_ptr(b);
1529        let t = self.ts.adjust_tail.unwrap_or(adj_head);
1530        self.ts.adjust_tail = None;
1531        let mut w = self.nodes.box_data(b).map_or(0, |x| x.width);
1532        let z = self.eqtb.dimen(dp::DISPLAYWIDTH);
1533        let s = self.eqtb.dimen(dp::DISPLAYINDENT);
1534        let (mut e, q) = if a.is_null() {
1535            (0, 0)
1536        } else {
1537            let e = self.nodes.box_data(a).map_or(0, |x| x.width);
1538            let f2 = self.fam_fnt(2, 0);
1539            let mq = self.math_quad_text(f2);
1540            let step = self.eqtb.int(ip::MATHEQNOGAPSTEP);
1541            let gap = if step == 1000 {
1542                mq
1543            } else {
1544                retex_arith::xn_over_d(mq, step, 1000).map_or(0, |r| r.quotient)
1545            };
1546            (e, e + gap)
1547        };
1548        if w + q > z {
1549            // Squeeze the equation as much as possible.
1550            let tot = self.ts.last_totals;
1551            if e != 0
1552                && (w - tot.1[0] + q <= z
1553                    || tot.1[1] != 0
1554                    || tot.1[2] != 0
1555                    || tot.1[3] != 0
1556                    || tot.1[4] != 0)
1557            {
1558                self.nodes.set_list_ptr(b, NodeRef::NULL);
1559                self.nodes.free(b);
1560                b = self.hpack(p, PackSpec::Exactly(z - q));
1561            } else {
1562                e = 0;
1563                if w > z {
1564                    self.nodes.set_list_ptr(b, NodeRef::NULL);
1565                    self.nodes.free(b);
1566                    b = self.hpack(p, PackSpec::Exactly(z));
1567                }
1568            }
1569            w = self.nodes.box_data(b).map_or(0, |x| x.width);
1570        }
1571        self.nodes.set_subtype(b, 6); // equation
1572        // The displacement of the left edge of the equation.
1573        let mut d = half(z - w);
1574        if e > 0 && d < 2 * e {
1575            d = half(z - w - e);
1576            let p = self.nodes.list_ptr(b);
1577            if !p.is_null() && self.nodes.id(p) == id::GLUE {
1578                d = 0;
1579            }
1580        }
1581        // The glue or equation number preceding the display.
1582        let pen = self.new_penalty_node(self.eqtb.int(ip::PREDISPLAYPENALTY));
1583        self.tail_append(pen);
1584        let pds = self.eqtb.dimen(dp::PREDISPLAYSIZE);
1585        let (g1, mut g2) = if d + s <= pds || l {
1586            (gs::ABOVE_DISPLAY, gs::BELOW_DISPLAY)
1587        } else {
1588            (gs::ABOVE_DISPLAY_SHORT, gs::BELOW_DISPLAY_SHORT)
1589        };
1590        let mode = self.eqtb.int(ip::MATHDISPLAYSKIPMODE);
1591        if l && e == 0 {
1592            if let Some(bd) = self.nodes.box_data_mut(a) {
1593                bd.shift = s;
1594            }
1595            self.nodes.set_subtype(a, 7);
1596            self.append_to_vlist(a);
1597            let pen = self.new_penalty_node(10000);
1598            self.tail_append(pen);
1599        } else {
1600            self.append_display_skip(g1, mode, true);
1601        }
1602        // The display and perhaps the equation number. LuaTeX packs an
1603        // equation with its number into a box of the display width:
1604        // kern d, equation, kern, number (or number, kern, equation,
1605        // kern d for \leqno), not shifted by d.
1606        if e != 0 {
1607            let r = self.new_kern_node(z - w - e - d, 0);
1608            self.nodes.set_subtype(a, 7);
1609            // Observed: the kern after a \leqno equation is z - w - d
1610            // (which makes the line wider than z when d was reduced).
1611            let k = self.new_kern_node(if l { z - w - d } else { d }, 0);
1612            if l {
1613                self.nodes.link(a, r);
1614                self.nodes.link(r, b);
1615                self.nodes.link(b, k);
1616                b = a;
1617            } else {
1618                self.nodes.link(k, b);
1619                self.nodes.link(b, r);
1620                self.nodes.link(r, a);
1621                b = k;
1622            }
1623            b = self.hpack(b, PackSpec::NATURAL);
1624            d = 0;
1625        }
1626        if let Some(bd) = self.nodes.box_data_mut(b) {
1627            bd.shift = s + d;
1628        }
1629        self.append_to_vlist(b);
1630        // The glue or equation number following the display.
1631        if !a.is_null() && e == 0 && !l {
1632            let pen = self.new_penalty_node(10000);
1633            self.tail_append(pen);
1634            let aw = self.nodes.box_data(a).map_or(0, |x| x.width);
1635            if let Some(bd) = self.nodes.box_data_mut(a) {
1636                bd.shift = s + z - aw;
1637            }
1638            self.nodes.set_subtype(a, 7);
1639            self.append_to_vlist(a);
1640            g2 = 0;
1641        }
1642        if t != adj_head {
1643            let first = self.nodes.next(adj_head);
1644            let tail = self.cur_list().tail;
1645            self.nodes.link(tail, first);
1646            self.cur_list_mut().tail = t;
1647        }
1648        self.nodes.free(adj_head);
1649        let pen = self.new_penalty_node(self.eqtb.int(ip::POSTDISPLAYPENALTY));
1650        self.tail_append(pen);
1651        if g2 > 0 {
1652            self.append_display_skip(g2, mode, false);
1653        }
1654        self.resume_after_display();
1655    }
1656
1657    /// Appends `\abovedisplayskip` and friends, honouring
1658    /// `\mathdisplayskipmode` (0 and 1: TeX's rule, an above skip always
1659    /// and a below skip always; 2: only when not zero; 3: never).
1660    fn append_display_skip(&mut self, sub: u16, mode: i32, _above: bool) {
1661        let g = self.eqtb.glue(crate::build::glue_param_of_subtype(sub));
1662        let zero = g.width == 0 && g.stretch == 0 && g.shrink == 0;
1663        let skip = match mode {
1664            2 => zero,
1665            3 => true,
1666            _ => false,
1667        };
1668        if !skip {
1669            let n = self.new_param_glue(sub);
1670            self.tail_append(n);
1671        }
1672    }
1673
1674    /// `math_quad(text_size)` from the font in family 2.
1675    fn math_quad_text(&self, f2: u32) -> i32 {
1676        let v = self.math_param_value(0, 2);
1677        if v != crate::codes::UNDEFINED_MATH_PARAM {
1678            v
1679        } else {
1680            self.quad(f2)
1681        }
1682    }
1683
1684    /// The value of math parameter `p` in style `s`.
1685    pub fn math_param_value(&self, p: u32, s: u32) -> i32 {
1686        self.eqtb
1687            .math_param
1688            .get(&(p * 8 + s))
1689            .map_or(crate::codes::UNDEFINED_MATH_PARAM, |x| x.val)
1690    }
1691
1692    /// Resumes the paragraph after a display (`resume_after_display`).
1693    fn resume_after_display(&mut self) {
1694        if self.save.cur_group != GroupCode::MathShift {
1695            self.confusion("display");
1696        }
1697        self.unsave();
1698        let pg = self.cur_list().prev_graf + 3;
1699        self.cur_list_mut().prev_graf = pg;
1700        self.push_nest(Mode::Horizontal);
1701        self.cur_list_mut().space_factor = 1000;
1702        let l = self.cur_lang() as i32;
1703        self.cur_list_mut().clang = l;
1704        let lp = self.new_local_par();
1705        self.tail_append(lp);
1706        // Scan an optional space.
1707        self.get_x_token();
1708        if self.cur_cmd != Cmd::Spacer {
1709            self.back_input();
1710        }
1711        if self.nest.len() == 2 {
1712            self.build_page();
1713        }
1714    }
1715
1716    /// Finishes an alignment in a display (§1206).
1717    pub fn finish_display_alignment(&mut self, p: NodeRef, q: NodeRef, aux_save: i32) {
1718        self.do_assignments();
1719        if self.cur_cmd != Cmd::MathShift {
1720            self.print_err("Missing $$ inserted");
1721            self.help(&[
1722                "Displays can use special alignments (like \\eqalignno)",
1723                "only if nothing but the alignment itself is between $$'s.",
1724            ]);
1725            self.back_error();
1726        } else {
1727            self.check_dollar_follows();
1728        }
1729        self.pop_nest();
1730        let pen = self.new_penalty_node(self.eqtb.int(ip::PREDISPLAYPENALTY));
1731        self.tail_append(pen);
1732        let g = self.new_param_glue(crate::nodes::glue_sub::ABOVE_DISPLAY);
1733        self.tail_append(g);
1734        let tail = self.cur_list().tail;
1735        self.nodes.link(tail, p);
1736        if !p.is_null() {
1737            self.cur_list_mut().tail = q;
1738        }
1739        let pen = self.new_penalty_node(self.eqtb.int(ip::POSTDISPLAYPENALTY));
1740        self.tail_append(pen);
1741        let g = self.new_param_glue(crate::nodes::glue_sub::BELOW_DISPLAY);
1742        self.tail_append(g);
1743        self.cur_list_mut().prev_depth = aux_save;
1744        self.resume_after_display();
1745    }
1746
1747    /// Empties the current math list (`flush_math`).
1748    pub fn flush_math(&mut self) {
1749        let head = self.cur_list().head;
1750        let first = self.nodes.next(head);
1751        self.nodes.flush_list(first);
1752        let inc = self.cur_list().incompleat_noad;
1753        self.nodes.flush_list(inc);
1754        self.nodes.set_next(head, NodeRef::NULL);
1755        self.cur_list_mut().tail = head;
1756        self.cur_list_mut().incompleat_noad = NodeRef::NULL;
1757    }
1758
1759    /// Inserts the tokens of a list as if typed (used by error recovery).
1760    pub fn ins_tokens(&mut self, toks: Vec<Token>) {
1761        self.ins_list(Rc::from(toks));
1762    }
1763}
1764
1765pub use retex_arith::half;
1766
1767/// The parts of a TeX 27-bit delimiter code.
1768fn classic_delimiter(v: i32) -> Option<(u32, u32, u32, u32)> {
1769    if v < 0 {
1770        return None;
1771    }
1772    let v = v as u32;
1773    Some((
1774        (v / 0o4000000) % 16,
1775        (v / 0o10000) % 256,
1776        (v / 256) % 16,
1777        v % 256,
1778    ))
1779}