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_from("before_display");
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.print_err("Please use ");
870            self.print_esc_str("mathaccent");
871            self.printer.print_str(" for accents in math mode");
872            self.help(&[
873                "I'm changing \\accent to \\mathaccent here; wish me luck.",
874                "(Accents are not the same in formulas as they are in text.)",
875            ]);
876            self.error();
877        }
878        let umath = self.cur_cmd == Cmd::MathAccent && self.cur_chr == 1;
879        let p = self.new_node(
880            id::ACCENT,
881            accent_sub::BOTH_FLEXIBLE,
882            NodeData::Noad(Box::default()),
883        );
884        self.tail_append(p);
885        let mut sub = accent_sub::BOTH_FLEXIBLE;
886        let (mut top, mut bot) = (NodeRef::NULL, NodeRef::NULL);
887        let fraction = 0;
888        let mut overlay = false;
889        if umath {
890            let (want_top, want_bot) = if self.scan_keyword("bottom") {
891                (false, true)
892            } else if self.scan_keyword("both") {
893                (true, true)
894            } else if self.scan_keyword("overlay") {
895                overlay = true;
896                (true, false)
897            } else {
898                let _ = self.scan_keyword("top");
899                (true, false)
900            };
901            if want_top {
902                let (k, fixed) = self.scan_accent_char(true);
903                top = k;
904                if fixed {
905                    sub |= accent_sub::FIXED_TOP;
906                }
907            }
908            if want_bot {
909                let (k, fixed) = self.scan_accent_char(true);
910                bot = k;
911                if fixed {
912                    sub |= accent_sub::FIXED_BOTTOM;
913                }
914            }
915        } else {
916            top = self.scan_accent_char(false).0;
917        }
918        self.nodes.set_subtype(p, sub);
919        if let Some(n) = self.nodes.noad_mut(p) {
920            n.accent = top;
921            n.bot_accent = bot;
922            n.fraction = fraction;
923            if overlay {
924                n.options |= noad_opt::OVERLAY;
925            }
926        }
927        let st = cramped_style(self.cur_list().math_style);
928        self.scan_math(p, Field::Nucleus, st);
929    }
930
931    /// `^` and `_` (`sub_sup`); `no_style` is set for `\Unosuperscript`
932    /// and `\Unosubscript`.
933    fn sub_sup(&mut self, sup: bool, no_style: bool) {
934        let field = if sup { Field::Sup } else { Field::Sub };
935        let (head, tail) = (self.cur_list().head, self.cur_list().tail);
936        let mut target = NodeRef::NULL;
937        let mut t_nonempty = false;
938        if tail != head && self.nodes.scripts_allowed(tail) {
939            target = tail;
940            t_nonempty = !self.noad_field(tail, field).is_null();
941        }
942        if target.is_null() || t_nonempty {
943            // Observed: the new noad's nucleus is an empty sub-list.
944            let n = self.new_noad(noad_sub::ORD);
945            let k = self.new_sub_list(true, NodeRef::NULL);
946            self.set_noad_field(n, Field::Nucleus, k);
947            self.tail_append(n);
948            target = n;
949            if t_nonempty {
950                if sup {
951                    self.print_err("Double superscript");
952                    self.help(&["I treat `x^1^2' essentially like `x^1{}^2'."]);
953                } else {
954                    self.print_err("Double subscript");
955                    self.help(&["I treat `x_1_2' essentially like `x_1{}_2'."]);
956                }
957                self.error();
958            }
959        }
960        let st = self.cur_list().math_style;
961        let style = if no_style {
962            if let Some(n) = self.nodes.noad_mut(target) {
963                n.options |= if sup {
964                    noad_opt::NO_SUPER_SCRIPT
965                } else {
966                    noad_opt::NO_SUB_SCRIPT
967                };
968            }
969            st
970        } else if sup {
971            sup_style(st)
972        } else {
973            sub_style(st)
974        };
975        self.scan_math(target, field, style);
976    }
977
978    /// `\over`, `\atop`, `\above` and their variants (`math_fraction`).
979    fn math_fraction(&mut self) {
980        let c = self.cur_chr;
981        // LuaTeX codes: above 0, over 1, atop 2, skewed 3; +4 with delims.
982        let delimited = c >= 4;
983        let kind = c % 4;
984        if !self.cur_list().incompleat_noad.is_null() {
985            if delimited {
986                let a = self.scan_delimiter(false, false);
987                let b = self.scan_delimiter(false, false);
988                self.nodes.flush_list(a);
989                self.nodes.flush_list(b);
990            }
991            if kind == 3 {
992                let m = self.scan_delimiter(false, false);
993                self.nodes.flush_list(m);
994                self.scan_fraction_options();
995            } else if kind == 0 {
996                let _ = self.scan_keyword("exact");
997                self.scan_normal_dimen();
998            }
999            self.print_err("Ambiguous; you need another { and }");
1000            self.help(&[
1001                "I'm ignoring this fraction specification, since I don't",
1002                "know whether a construction like `x \\over y \\over z'",
1003                "means `{x \\over y} \\over z' or `x \\over {y \\over z}'.",
1004            ]);
1005            self.error();
1006            return;
1007        }
1008        let head = self.cur_list().head;
1009        let first = self.nodes.next(head);
1010        let num = self.new_sub_list(true, first);
1011        let mut fr = FractionData {
1012            thickness: DEFAULT_CODE,
1013            num,
1014            denom: NodeRef::NULL,
1015            left: NodeRef::NULL,
1016            right: NodeRef::NULL,
1017            middle: NodeRef::NULL,
1018            options: 0,
1019            new_hlist: NodeRef::NULL,
1020        };
1021        self.nodes.set_next(head, NodeRef::NULL);
1022        self.cur_list_mut().tail = head;
1023        if delimited {
1024            fr.left = self.scan_delimiter(false, false);
1025            fr.right = self.scan_delimiter(false, false);
1026        }
1027        match kind {
1028            0 => {
1029                if self.scan_keyword("exact") {
1030                    fr.options |= noad_opt::EXACT;
1031                }
1032                self.scan_normal_dimen();
1033                fr.thickness = self.cur_val;
1034            }
1035            1 => fr.thickness = DEFAULT_CODE,
1036            2 => fr.thickness = 0,
1037            _ => {
1038                fr.middle = self.scan_delimiter(false, false);
1039                fr.options |= self.scan_fraction_options();
1040                fr.thickness = 0;
1041            }
1042        }
1043        let n = self.new_node(id::FRACTION, 0, NodeData::Fraction(Box::new(fr)));
1044        self.cur_list_mut().incompleat_noad = n;
1045        // The denominator is read in the cramped style (observed with
1046        // \mathstyle; in \Ustack this gives the denominator style).
1047        let st = cramped_style(self.cur_list().math_style);
1048        self.cur_list_mut().math_style = st;
1049    }
1050
1051    /// The `noaxis`/`exact` options of `\Uskewed`.
1052    fn scan_fraction_options(&mut self) -> u32 {
1053        let mut o = 0;
1054        loop {
1055            if self.scan_keyword("noaxis") {
1056                o |= noad_opt::NO_AXIS;
1057            } else if self.scan_keyword("exact") {
1058                o |= noad_opt::EXACT;
1059            } else {
1060                return o;
1061            }
1062        }
1063    }
1064
1065    /// Finishes the current mlist and pops the nest (`fin_mlist`); `p`, if
1066    /// not null, is a right (or middle) fence ending the list.
1067    fn fin_mlist(&mut self, p: NodeRef) -> NodeRef {
1068        let inc = self.cur_list().incompleat_noad;
1069        let head = self.cur_list().head;
1070        let q;
1071        if !inc.is_null() {
1072            let first = self.nodes.next(head);
1073            let denom = self.new_sub_list(true, first);
1074            if let NodeData::Fraction(f) = self.nodes.data_mut(inc) {
1075                f.denom = denom;
1076            }
1077            if p.is_null() {
1078                q = inc;
1079            } else {
1080                // A \left…\over…\right: the left fence (and any middle
1081                // fences up to delim_ptr) stays outside the fraction.
1082                let num = match self.nodes.data(inc) {
1083                    NodeData::Fraction(f) => f.num,
1084                    _ => NodeRef::NULL,
1085                };
1086                let num_list = self.nodes.list_ptr_kernel(num);
1087                q = num_list;
1088                let dp = self.cur_list().delim_ptr;
1089                if num_list.is_null() || self.nodes.id(num_list) != id::FENCE || dp.is_null() {
1090                    self.confusion("right");
1091                }
1092                let after = self.nodes.next(dp);
1093                self.nodes.set_kernel_list(num, after);
1094                if !after.is_null() {
1095                    self.nodes.set_prev(after, NodeRef::NULL);
1096                }
1097                self.nodes.link(dp, inc);
1098                self.nodes.link(inc, p);
1099            }
1100        } else {
1101            let tail = self.cur_list().tail;
1102            self.nodes.link(tail, p);
1103            q = self.nodes.next(head);
1104        }
1105        self.nodes.set_next(head, NodeRef::NULL);
1106        if !q.is_null() {
1107            self.nodes.set_prev(q, NodeRef::NULL);
1108        }
1109        self.pop_nest();
1110        q
1111    }
1112
1113    /// Right braces ending math groups, `\mathchoice` groups and
1114    /// `\vcenter` (cases of `handle_right_brace`).
1115    pub fn math_right_brace(&mut self) {
1116        match self.save.cur_group {
1117            GroupCode::Math => {
1118                self.unsave();
1119                let r = self.pop_saved();
1120                let root_follows = r & (1 << 62) != 0;
1121                let (noad, f) = field_from_ref(r & !(1 << 62));
1122                let p = self.fin_mlist(NodeRef::NULL);
1123                let k = self.noad_field(noad, f);
1124                self.nodes.set_kernel_list(k, p);
1125                if !p.is_null() && self.nodes.next(p).is_null() {
1126                    let pid = self.nodes.id(p);
1127                    // Observed: \mathflattenmode says which single noads
1128                    // are flattened (1 ord, 2 bin, 4 rel, 8 punct,
1129                    // 16 inner).
1130                    let flatten = self.eqtb.int(ip::MATHFLATTENMODE);
1131                    let bit = match self.nodes.subtype(p) {
1132                        noad_sub::ORD => 1,
1133                        noad_sub::BIN => 2,
1134                        noad_sub::REL => 4,
1135                        noad_sub::PUNCT => 8,
1136                        noad_sub::INNER => 16,
1137                        _ => 0,
1138                    };
1139                    if pid == id::NOAD && flatten & bit != 0 {
1140                        let (sub, sup) = (
1141                            self.noad_field(p, Field::Sub),
1142                            self.noad_field(p, Field::Sup),
1143                        );
1144                        if sub.is_null() && sup.is_null() {
1145                            // {x}: the field becomes x's nucleus.
1146                            let nuc = self.noad_field(p, Field::Nucleus);
1147                            self.set_noad_field(p, Field::Nucleus, NodeRef::NULL);
1148                            self.nodes.flush_list(p);
1149                            self.nodes.set_kernel_list(k, NodeRef::NULL);
1150                            self.nodes.flush_list(k);
1151                            self.set_noad_field(noad, f, nuc);
1152                        }
1153                    } else if pid == id::ACCENT
1154                        && f == Field::Nucleus
1155                        && noad == self.cur_list().tail
1156                        && self.nodes.id(noad) == id::NOAD
1157                        && self.nodes.subtype(noad) == noad_sub::ORD
1158                    {
1159                        // Replace the tail of the list by the accent noad.
1160                        let head = self.cur_list().head;
1161                        let mut q = head;
1162                        while self.nodes.next(q) != noad {
1163                            q = self.nodes.next(q);
1164                        }
1165                        self.nodes.set_kernel_list(k, NodeRef::NULL);
1166                        self.nodes.link(q, p);
1167                        self.nodes.set_next(noad, NodeRef::NULL);
1168                        self.nodes.flush_list(noad);
1169                        self.cur_list_mut().tail = p;
1170                    }
1171                }
1172                if root_follows {
1173                    let st = cramped_style(self.cur_list().math_style);
1174                    self.scan_math(noad, Field::Nucleus, st);
1175                }
1176            }
1177            GroupCode::MathChoice => self.build_choices(),
1178            GroupCode::Vcenter => {
1179                if self.par_token_before_group_end(1) {
1180                    return;
1181                }
1182                self.end_graf();
1183                self.unsave();
1184                let (spec, _dir) = self.pop_spec_pub();
1185                let head = self.cur_list().head;
1186                let first = self.nodes.next(head);
1187                self.nodes.set_next(head, NodeRef::NULL);
1188                let first = self.filter_list("vpack_filter", first, "vcenter");
1189                let d = self.eqtb.dimen(dp::BOXMAXDEPTH);
1190                let b = self.vpackage(first, spec, d);
1191                self.pop_nest();
1192                let n = self.new_noad(noad_sub::VCENTER);
1193                let k = self.new_sub_list(false, b);
1194                self.set_noad_field(n, Field::Nucleus, k);
1195                self.tail_append(n);
1196            }
1197            _ => self.confusion("rightbrace"),
1198        }
1199    }
1200
1201    /// `\mathchoice` (`append_choices`).
1202    fn append_choices(&mut self) {
1203        let c = self.new_node(
1204            id::CHOICE,
1205            0,
1206            NodeData::Choice(Box::new([NodeRef::NULL; 4])),
1207        );
1208        self.tail_append(c);
1209        self.push_saved(0);
1210        self.push_math(GroupCode::MathChoice, 0);
1211        self.scan_left_brace();
1212    }
1213
1214    /// The end of one of the four `\mathchoice` lists (`build_choices`).
1215    fn build_choices(&mut self) {
1216        self.unsave();
1217        let p = self.fin_mlist(NodeRef::NULL);
1218        let k = self.pop_saved();
1219        let tail = self.cur_list().tail;
1220        if let NodeData::Choice(c) = self.nodes.data_mut(tail) {
1221            c[(k as usize).min(3)] = p;
1222        }
1223        if k >= 3 {
1224            return;
1225        }
1226        self.push_saved(k + 1);
1227        self.push_math(GroupCode::MathChoice, 2 * (k as i32 + 1));
1228        self.scan_left_brace();
1229    }
1230
1231    /// `\left`, `\middle`, `\right` and their `\U…` forms
1232    /// (`math_left_right`).
1233    fn math_left_right(&mut self) {
1234        let c = self.cur_chr;
1235        // LuaTeX codes: left 1, middle 2, right 3; \Uleft 11, \Umiddle 12,
1236        // \Uright 13, \Uvextensible 14.
1237        if c == 14 {
1238            self.math_vextensible();
1239            return;
1240        }
1241        let t = match c % 10 {
1242            1 => fence_sub::LEFT,
1243            2 => fence_sub::MIDDLE,
1244            _ => fence_sub::RIGHT,
1245        };
1246        if t != fence_sub::LEFT && self.save.cur_group != GroupCode::MathLeft {
1247            if self.save.cur_group == GroupCode::MathShift {
1248                // LuaTeX reads the delimiter without checking it.
1249                self.get_nonblank_nonrelax_noncall();
1250                if self.cur_cmd == Cmd::DelimNum {
1251                    if self.cur_chr == 0 {
1252                        self.scan_twenty_seven_bit_int();
1253                    } else {
1254                        self.scan_umath_triple();
1255                    }
1256                }
1257                self.print_err("Extra ");
1258                if t == fence_sub::MIDDLE {
1259                    self.print_esc_str("middle");
1260                    self.help(&["I'm ignoring a \\middle that had no matching \\left."]);
1261                } else {
1262                    self.print_esc_str("right");
1263                    self.help(&["I'm ignoring a \\right that had no matching \\left."]);
1264                }
1265                self.error();
1266            } else {
1267                self.off_save();
1268            }
1269            return;
1270        }
1271        let mut fe = FenceData {
1272            class: -1,
1273            ..FenceData::default()
1274        };
1275        if c >= 10 {
1276            loop {
1277                if self.scan_keyword("height") {
1278                    self.scan_normal_dimen();
1279                    fe.height = self.cur_val;
1280                } else if self.scan_keyword("depth") {
1281                    self.scan_normal_dimen();
1282                    fe.depth = self.cur_val;
1283                } else if self.scan_keyword("axis") {
1284                    fe.options |= noad_opt::AXIS;
1285                } else if self.scan_keyword("noaxis") {
1286                    fe.options |= noad_opt::NO_AXIS;
1287                } else if self.scan_keyword("exact") {
1288                    fe.options |= noad_opt::EXACT;
1289                } else if self.scan_keyword("class") {
1290                    self.scan_int();
1291                    fe.class = self.cur_val;
1292                } else {
1293                    break;
1294                }
1295            }
1296        }
1297        fe.delim = self.scan_delimiter(false, false);
1298        let p = self.new_node(id::FENCE, t, NodeData::Fence(Box::new(fe)));
1299        let q = if t == fence_sub::LEFT {
1300            p
1301        } else {
1302            let q = self.fin_mlist(p);
1303            self.unsave();
1304            q
1305        };
1306        if t != fence_sub::RIGHT {
1307            let st = self.cur_list().math_style;
1308            self.push_math(GroupCode::MathLeft, st);
1309            let head = self.cur_list().head;
1310            self.nodes.link(head, q);
1311            self.cur_list_mut().tail = p;
1312            self.cur_list_mut().delim_ptr = p;
1313        } else {
1314            let n = self.new_noad(noad_sub::INNER);
1315            let k = self.new_sub_list(true, q);
1316            self.set_noad_field(n, Field::Nucleus, k);
1317            self.tail_append(n);
1318        }
1319    }
1320
1321    /// `\Uvextensible`: a stand-alone extensible delimiter.
1322    fn math_vextensible(&mut self) {
1323        let mut fe = FenceData {
1324            class: -1,
1325            ..FenceData::default()
1326        };
1327        loop {
1328            if self.scan_keyword("height") {
1329                self.scan_normal_dimen();
1330                fe.height = self.cur_val;
1331            } else if self.scan_keyword("depth") {
1332                self.scan_normal_dimen();
1333                fe.depth = self.cur_val;
1334            } else if self.scan_keyword("axis") {
1335                fe.options |= noad_opt::AXIS;
1336            } else if self.scan_keyword("noaxis") {
1337                fe.options |= noad_opt::NO_AXIS;
1338            } else if self.scan_keyword("exact") {
1339                fe.options |= noad_opt::EXACT;
1340            } else {
1341                break;
1342            }
1343        }
1344        fe.delim = self.scan_delimiter(false, false);
1345        // Observed: a fence of subtype "no".
1346        let p = self.new_node(id::FENCE, fence_sub::NO, NodeData::Fence(Box::new(fe)));
1347        let n = self.new_noad(noad_sub::INNER);
1348        let k = self.new_sub_list(true, p);
1349        self.set_noad_field(n, Field::Nucleus, k);
1350        self.tail_append(n);
1351    }
1352
1353    /// `\eqno` and `\leqno` in display math (`start_eq_no`).
1354    fn start_eq_no(&mut self) {
1355        self.push_saved(i64::from(self.cur_chr));
1356        self.go_into_ordinary_math();
1357    }
1358
1359    /// Appends a box in math mode as an ord noad with a `sub_box`
1360    /// nucleus (§1076).
1361    pub fn math_box_end(&mut self, b: NodeRef) {
1362        let n = self.new_noad(noad_sub::ORD);
1363        let k = self.new_sub_list(false, b);
1364        self.set_noad_field(n, Field::Nucleus, k);
1365        self.tail_append(n);
1366    }
1367
1368    /// Mathchars in horizontal mode: LuaTeX typesets the character in the
1369    /// text font of its family.
1370    pub fn math_char_in_text(&mut self) {
1371        let mc = match self.cur_cmd {
1372            Cmd::MathCharNum => self.scan_math_char_num(),
1373            Cmd::MathGiven => MathChar::from_given(self.cur_chr),
1374            _ => MathChar::from_packed(i64::from(self.cur_chr)),
1375        };
1376        if mc.class >= 8 {
1377            self.treat_as_active();
1378            return;
1379        }
1380        let f = self.fam_fnt(mc.fam, 0);
1381        let g = self.new_char_node(f, mc.chr);
1382        self.tail_append(g);
1383        self.cur_list_mut().space_factor = 1000;
1384    }
1385
1386    /// The font of family `fam` in size `size` (0 text, 1 script,
1387    /// 2 scriptscript): `fam_fnt`.
1388    pub fn fam_fnt(&self, fam: u32, size: u32) -> u32 {
1389        self.eqtb
1390            .math_font
1391            .get((size * crate::eqtb::NUM_FAMILIES as u32 + fam) as usize)
1392            .map_or(0, |s| s.val)
1393    }
1394
1395    // ----- leaving math mode ------------------------------------------
1396
1397    /// The end of a formula (`after_math`).
1398    fn after_math(&mut self) {
1399        let mut m = self.mode;
1400        let mut l = false;
1401        let mut p = self.fin_mlist(NodeRef::NULL);
1402        let a;
1403        if self.mode == Mode::DisplayMath && m == Mode::Math {
1404            // The end of an equation number.
1405            self.check_dollar_follows();
1406            let hl = self.formula_to_hlist(p, 2, false);
1407            a = self.hpack(hl, PackSpec::NATURAL);
1408            self.take_outer_math_dirs();
1409            self.unsave();
1410            if self.pop_saved() == 1 {
1411                l = true;
1412            }
1413            m = self.mode;
1414            p = self.fin_mlist(NodeRef::NULL);
1415        } else {
1416            a = NodeRef::NULL;
1417        }
1418        if m == Mode::Math {
1419            self.finish_math_in_text(p);
1420        } else {
1421            if a.is_null() {
1422                self.check_dollar_follows();
1423            }
1424            self.finish_displayed_math(p, a, l);
1425        }
1426    }
1427
1428    /// The math and text directions outside the formula whose group is
1429    /// the current one.
1430    fn take_outer_math_dirs(&mut self) -> Option<(i32, i32)> {
1431        let level = self.save.cur_level;
1432        while let Some(&(l, m, t)) = self.outer_math_dirs.last() {
1433            if l < level {
1434                break;
1435            }
1436            self.outer_math_dirs.pop();
1437            if l == level {
1438                return Some((m, t));
1439            }
1440        }
1441        None
1442    }
1443
1444    /// Checks that a second `$` ends a display.
1445    fn check_dollar_follows(&mut self) {
1446        self.get_x_token();
1447        if self.cur_cmd != Cmd::MathShift
1448            && !(self.cur_cmd == Cmd::MathShiftCs && self.cur_chr == 1)
1449        {
1450            self.print_err("Display math should end with $$");
1451            self.help(&[
1452                "The `$' that I just saw supposedly matches a previous `$$'.",
1453                "So I shall assume that you typed `$$' both times.",
1454            ]);
1455            self.back_error();
1456        }
1457    }
1458
1459    fn finish_math_in_text(&mut self, p: NodeRef) {
1460        let ms = self.eqtb.dimen(dp::MATHSURROUND);
1461        let mg = self
1462            .eqtb
1463            .glue(crate::primitives_gen::assign_glue::MATHSURROUNDSKIP);
1464        let pen = self.mode == Mode::Horizontal;
1465        // As in TeX, the formula's group ends last: the opening math node
1466        // is the tail while the mlist_to_hlist callback runs (luamml reads
1467        // it), and the nodes take the attributes inside the formula.
1468        // A formula whose direction differs from the text's is wrapped in
1469        // direction nodes (see `dirs`).
1470        // The directions are those outside the formula.
1471        let (math_dir, text_dir) = self.take_outer_math_dirs().unwrap_or((
1472            self.dir_param(crate::primitives_gen::assign_dir::MATHDIR),
1473            self.dir_param(crate::primitives_gen::assign_dir::TEXTDIR),
1474        ));
1475        let wrap = (math_dir != text_dir).then(|| math_dir.rem_euclid(4) as i8);
1476        let b = self.new_math_node(ms, mg, 0);
1477        self.tail_append(b);
1478        if let Some(d) = wrap {
1479            let p = self.new_dir_node(d, false);
1480            self.tail_append(p);
1481        }
1482        let hl = self.formula_to_hlist(p, 2, pen);
1483        let tail = self.cur_list().tail;
1484        self.nodes.link(tail, hl);
1485        let mut t = tail;
1486        while !self.nodes.next(t).is_null() {
1487            t = self.nodes.next(t);
1488        }
1489        self.cur_list_mut().tail = t;
1490        if let Some(d) = wrap {
1491            let p = self.new_dir_node(d, true);
1492            self.tail_append(p);
1493        }
1494        let e = self.new_math_node(ms, mg, 1);
1495        self.tail_append(e);
1496        self.cur_list_mut().space_factor = 1000;
1497        self.unsave();
1498    }
1499
1500    /// A math node with `\mathsurround` and `\mathsurroundskip`.
1501    fn new_math_node(&mut self, surround: i32, skip: crate::glue::Glue, sub: u16) -> NodeRef {
1502        let p = self.new_node(
1503            id::MATH,
1504            sub,
1505            NodeData::Math {
1506                surround,
1507                glue: crate::glue::Glue::ZERO,
1508            },
1509        );
1510        if (skip.width != 0 || skip.stretch != 0 || skip.shrink != 0)
1511            && let NodeData::Math { glue, .. } = self.nodes.data_mut(p)
1512        {
1513            *glue = skip;
1514        }
1515        p
1516    }
1517
1518    /// Places a finished display (§1199–§1205).
1519    fn finish_displayed_math(&mut self, p: NodeRef, a: NodeRef, l: bool) {
1520        use crate::nodes::glue_sub as gs;
1521        self.take_outer_math_dirs();
1522        let hl = self.formula_to_hlist(p, 0, false);
1523        let adj_head = self.nodes.new_temp();
1524        self.ts.adjust_tail = Some(adj_head);
1525        let mut b = self.hpack(hl, PackSpec::NATURAL);
1526        let p = self.nodes.list_ptr(b);
1527        let t = self.ts.adjust_tail.unwrap_or(adj_head);
1528        self.ts.adjust_tail = None;
1529        let mut w = self.nodes.box_data(b).map_or(0, |x| x.width);
1530        let z = self.eqtb.dimen(dp::DISPLAYWIDTH);
1531        let s = self.eqtb.dimen(dp::DISPLAYINDENT);
1532        let (mut e, q) = if a.is_null() {
1533            (0, 0)
1534        } else {
1535            let e = self.nodes.box_data(a).map_or(0, |x| x.width);
1536            let f2 = self.fam_fnt(2, 0);
1537            let mq = self.math_quad_text(f2);
1538            let step = self.eqtb.int(ip::MATHEQNOGAPSTEP);
1539            let gap = if step == 1000 {
1540                mq
1541            } else {
1542                retex_arith::xn_over_d(mq, step, 1000).map_or(0, |r| r.quotient)
1543            };
1544            (e, e + gap)
1545        };
1546        if w + q > z {
1547            // Squeeze the equation as much as possible.
1548            let tot = self.ts.last_totals;
1549            if e != 0
1550                && (w - tot.1[0] + q <= z
1551                    || tot.1[1] != 0
1552                    || tot.1[2] != 0
1553                    || tot.1[3] != 0
1554                    || tot.1[4] != 0)
1555            {
1556                self.nodes.set_list_ptr(b, NodeRef::NULL);
1557                self.nodes.free(b);
1558                b = self.hpack(p, PackSpec::Exactly(z - q));
1559            } else {
1560                e = 0;
1561                if w > z {
1562                    self.nodes.set_list_ptr(b, NodeRef::NULL);
1563                    self.nodes.free(b);
1564                    b = self.hpack(p, PackSpec::Exactly(z));
1565                }
1566            }
1567            w = self.nodes.box_data(b).map_or(0, |x| x.width);
1568        }
1569        self.nodes.set_subtype(b, 6); // equation
1570        // The displacement of the left edge of the equation.
1571        let mut d = half(z - w);
1572        if e > 0 && d < 2 * e {
1573            d = half(z - w - e);
1574            let p = self.nodes.list_ptr(b);
1575            if !p.is_null() && self.nodes.id(p) == id::GLUE {
1576                d = 0;
1577            }
1578        }
1579        // The glue or equation number preceding the display.
1580        let pen = self.new_penalty_node(self.eqtb.int(ip::PREDISPLAYPENALTY));
1581        self.tail_append(pen);
1582        let pds = self.eqtb.dimen(dp::PREDISPLAYSIZE);
1583        let (g1, mut g2) = if d + s <= pds || l {
1584            (gs::ABOVE_DISPLAY, gs::BELOW_DISPLAY)
1585        } else {
1586            (gs::ABOVE_DISPLAY_SHORT, gs::BELOW_DISPLAY_SHORT)
1587        };
1588        let mode = self.eqtb.int(ip::MATHDISPLAYSKIPMODE);
1589        if l && e == 0 {
1590            if let Some(bd) = self.nodes.box_data_mut(a) {
1591                bd.shift = s;
1592            }
1593            self.nodes.set_subtype(a, 7);
1594            self.append_to_vlist(a);
1595            let pen = self.new_penalty_node(10000);
1596            self.tail_append(pen);
1597        } else {
1598            self.append_display_skip(g1, mode, true);
1599        }
1600        // The display and perhaps the equation number. LuaTeX packs an
1601        // equation with its number into a box of the display width:
1602        // kern d, equation, kern, number (or number, kern, equation,
1603        // kern d for \leqno), not shifted by d.
1604        if e != 0 {
1605            let r = self.new_kern_node(z - w - e - d, 0);
1606            self.nodes.set_subtype(a, 7);
1607            // Observed: the kern after a \leqno equation is z - w - d
1608            // (which makes the line wider than z when d was reduced).
1609            let k = self.new_kern_node(if l { z - w - d } else { d }, 0);
1610            if l {
1611                self.nodes.link(a, r);
1612                self.nodes.link(r, b);
1613                self.nodes.link(b, k);
1614                b = a;
1615            } else {
1616                self.nodes.link(k, b);
1617                self.nodes.link(b, r);
1618                self.nodes.link(r, a);
1619                b = k;
1620            }
1621            b = self.hpack(b, PackSpec::NATURAL);
1622            d = 0;
1623        }
1624        if let Some(bd) = self.nodes.box_data_mut(b) {
1625            bd.shift = s + d;
1626        }
1627        self.append_to_vlist(b);
1628        // The glue or equation number following the display.
1629        if !a.is_null() && e == 0 && !l {
1630            let pen = self.new_penalty_node(10000);
1631            self.tail_append(pen);
1632            let aw = self.nodes.box_data(a).map_or(0, |x| x.width);
1633            if let Some(bd) = self.nodes.box_data_mut(a) {
1634                bd.shift = s + z - aw;
1635            }
1636            self.nodes.set_subtype(a, 7);
1637            self.append_to_vlist(a);
1638            g2 = 0;
1639        }
1640        if t != adj_head {
1641            let first = self.nodes.next(adj_head);
1642            let tail = self.cur_list().tail;
1643            self.nodes.link(tail, first);
1644            self.cur_list_mut().tail = t;
1645        }
1646        self.nodes.free(adj_head);
1647        let pen = self.new_penalty_node(self.eqtb.int(ip::POSTDISPLAYPENALTY));
1648        self.tail_append(pen);
1649        if g2 > 0 {
1650            self.append_display_skip(g2, mode, false);
1651        }
1652        self.resume_after_display();
1653    }
1654
1655    /// Appends `\abovedisplayskip` and friends, honouring
1656    /// `\mathdisplayskipmode` (0 and 1: TeX's rule, an above skip always
1657    /// and a below skip always; 2: only when not zero; 3: never).
1658    fn append_display_skip(&mut self, sub: u16, mode: i32, _above: bool) {
1659        let g = self.eqtb.glue(crate::build::glue_param_of_subtype(sub));
1660        let zero = g.width == 0 && g.stretch == 0 && g.shrink == 0;
1661        let skip = match mode {
1662            2 => zero,
1663            3 => true,
1664            _ => false,
1665        };
1666        if !skip {
1667            let n = self.new_param_glue(sub);
1668            self.tail_append(n);
1669        }
1670    }
1671
1672    /// `math_quad(text_size)` from the font in family 2.
1673    fn math_quad_text(&self, f2: u32) -> i32 {
1674        let v = self.math_param_value(0, 2);
1675        if v != crate::codes::UNDEFINED_MATH_PARAM {
1676            v
1677        } else {
1678            self.quad(f2)
1679        }
1680    }
1681
1682    /// The value of math parameter `p` in style `s`.
1683    pub fn math_param_value(&self, p: u32, s: u32) -> i32 {
1684        self.eqtb
1685            .math_param
1686            .get(&(p * 8 + s))
1687            .map_or(crate::codes::UNDEFINED_MATH_PARAM, |x| x.val)
1688    }
1689
1690    /// Resumes the paragraph after a display (`resume_after_display`).
1691    fn resume_after_display(&mut self) {
1692        if self.save.cur_group != GroupCode::MathShift {
1693            self.confusion("display");
1694        }
1695        self.unsave();
1696        let pg = self.cur_list().prev_graf + 3;
1697        self.cur_list_mut().prev_graf = pg;
1698        self.push_nest(Mode::Horizontal);
1699        self.cur_list_mut().space_factor = 1000;
1700        let l = self.cur_lang() as i32;
1701        self.cur_list_mut().clang = l;
1702        let lp = self.new_local_par();
1703        self.tail_append(lp);
1704        // Scan an optional space.
1705        self.get_x_token();
1706        if self.cur_cmd != Cmd::Spacer {
1707            self.back_input();
1708        }
1709        if self.nest.len() == 2 {
1710            self.build_page_from("after_display");
1711        }
1712    }
1713
1714    /// Finishes an alignment in a display (§1206).
1715    pub fn finish_display_alignment(&mut self, p: NodeRef, q: NodeRef, aux_save: i32) {
1716        self.do_assignments();
1717        if self.cur_cmd != Cmd::MathShift {
1718            self.print_err("Missing $$ inserted");
1719            self.help(&[
1720                "Displays can use special alignments (like \\eqalignno)",
1721                "only if nothing but the alignment itself is between $$'s.",
1722            ]);
1723            self.back_error();
1724        } else {
1725            self.check_dollar_follows();
1726        }
1727        self.pop_nest();
1728        let pen = self.new_penalty_node(self.eqtb.int(ip::PREDISPLAYPENALTY));
1729        self.tail_append(pen);
1730        let g = self.new_param_glue(crate::nodes::glue_sub::ABOVE_DISPLAY);
1731        self.tail_append(g);
1732        let tail = self.cur_list().tail;
1733        self.nodes.link(tail, p);
1734        if !p.is_null() {
1735            self.cur_list_mut().tail = q;
1736        }
1737        let pen = self.new_penalty_node(self.eqtb.int(ip::POSTDISPLAYPENALTY));
1738        self.tail_append(pen);
1739        let g = self.new_param_glue(crate::nodes::glue_sub::BELOW_DISPLAY);
1740        self.tail_append(g);
1741        self.cur_list_mut().prev_depth = aux_save;
1742        self.resume_after_display();
1743    }
1744
1745    /// Empties the current math list (`flush_math`).
1746    pub fn flush_math(&mut self) {
1747        let head = self.cur_list().head;
1748        let first = self.nodes.next(head);
1749        self.nodes.flush_list(first);
1750        let inc = self.cur_list().incompleat_noad;
1751        self.nodes.flush_list(inc);
1752        self.nodes.set_next(head, NodeRef::NULL);
1753        self.cur_list_mut().tail = head;
1754        self.cur_list_mut().incompleat_noad = NodeRef::NULL;
1755    }
1756
1757    /// Inserts the tokens of a list as if typed (used by error recovery).
1758    pub fn ins_tokens(&mut self, toks: Vec<Token>) {
1759        self.ins_list(Rc::from(toks));
1760    }
1761}
1762
1763pub use retex_arith::half;
1764
1765/// The parts of a TeX 27-bit delimiter code.
1766fn classic_delimiter(v: i32) -> Option<(u32, u32, u32, u32)> {
1767    if v < 0 {
1768        return None;
1769    }
1770    let v = v as u32;
1771    Some((
1772        (v / 0o4000000) % 16,
1773        (v / 0o10000) % 256,
1774        (v / 256) % 16,
1775        v % 256,
1776    ))
1777}