Skip to main content

latexml_core/definition/
register.rs

1use std::{borrow::Cow, fmt, rc::Rc};
2
3use libxml::tree::Node;
4
5use super::argument::ArgWrap;
6use crate::{
7  Digested, Locator,
8  common::{
9    arena::{self, SymHashMap as HashMap, SymStr},
10    dimension::Dimension,
11    error::*,
12    font,
13    glue::Glue,
14    mudimension::MuDimension,
15    muglue::MuGlue,
16    number::Number,
17    numeric_ops::NumericOps,
18    object::Object,
19  },
20  definition::{BeforeDigestClosure, Definition, DigestionClosure},
21  document::Document,
22  gullet,
23  parameter::Parameters,
24  pin, state,
25  state::{Scope, Stored},
26  tbox::Tbox,
27  token::*,
28  tokens::Tokens,
29  whatsit::Whatsit,
30};
31
32/// The values that can be read by, and stored in, a Register
33#[derive(Clone, PartialEq)]
34pub enum RegisterValue {
35  /// A unitless number
36  Number(Number),
37  /// A dimension with associated unit
38  Dimension(Dimension),
39  ///A "mu" dimension with associated unit
40  MuDimension(MuDimension),
41  /// A Glue space
42  Glue(Glue),
43  ///A Mu Glue space
44  MuGlue(MuGlue),
45  /// used by token registers
46  Token(Token),
47  /// used by token registers
48  Tokens(Tokens),
49  /// A coordinate pair (x,y) — used by picture environment
50  Pair(crate::common::pair::Pair),
51}
52impl From<Number> for RegisterValue {
53  fn from(n: Number) -> RegisterValue { RegisterValue::Number(n) }
54}
55impl From<Number> for Option<RegisterValue> {
56  fn from(n: Number) -> Option<RegisterValue> { Some(n.into()) }
57}
58impl From<Dimension> for RegisterValue {
59  fn from(n: Dimension) -> RegisterValue { RegisterValue::Dimension(n) }
60}
61impl From<MuDimension> for RegisterValue {
62  fn from(n: MuDimension) -> RegisterValue { RegisterValue::MuDimension(n) }
63}
64impl From<Glue> for RegisterValue {
65  fn from(n: Glue) -> RegisterValue { RegisterValue::Glue(n) }
66}
67impl From<MuGlue> for RegisterValue {
68  fn from(n: MuGlue) -> RegisterValue { RegisterValue::MuGlue(n) }
69}
70impl From<Token> for RegisterValue {
71  fn from(n: Token) -> RegisterValue { RegisterValue::Token(n) }
72}
73impl From<Tokens> for RegisterValue {
74  fn from(n: Tokens) -> RegisterValue { RegisterValue::Tokens(n) }
75}
76impl From<crate::common::pair::Pair> for RegisterValue {
77  fn from(p: crate::common::pair::Pair) -> RegisterValue { RegisterValue::Pair(p) }
78}
79impl From<&RegisterValue> for RegisterType {
80  fn from(v: &RegisterValue) -> RegisterType {
81    match *v {
82      RegisterValue::Number(_) => RegisterType::Number,
83      RegisterValue::Dimension(_) => RegisterType::Dimension,
84      RegisterValue::MuDimension(_) => RegisterType::MuDimension,
85      RegisterValue::Glue(_) => RegisterType::Glue,
86      RegisterValue::MuGlue(_) => RegisterType::MuGlue,
87      RegisterValue::Token(_) => RegisterType::Token,
88      RegisterValue::Tokens(_) => RegisterType::Tokens,
89      RegisterValue::Pair(_) => RegisterType::Dimension,
90    }
91  }
92}
93
94impl Default for RegisterValue {
95  fn default() -> Self { RegisterValue::Number(Number::new(0)) }
96}
97impl Object for RegisterValue {
98  fn stringify(&self) -> String { s!("RegisterValue[{}]", self) }
99
100  fn revert(&self) -> Result<Tokens> {
101    match self {
102      // ExplodeText($self->toString);
103      RegisterValue::Number(value) => Ok(Tokens::new(ExplodeText!(value))),
104      RegisterValue::Dimension(value) => Ok(Tokens::new(ExplodeText!(value))),
105      RegisterValue::MuDimension(value) => Ok(Tokens::new(ExplodeText!(value))),
106      RegisterValue::Glue(value) => Ok(Tokens::new(ExplodeText!(value))),
107      RegisterValue::MuGlue(value) => Ok(Tokens::new(ExplodeText!(value))),
108      RegisterValue::Token(value) => Ok(Tokens!(value.revert())),
109      RegisterValue::Tokens(value) => Ok(Tokens::new(value.clone().revert())), // clone?
110      RegisterValue::Pair(value) => value.revert(),
111    }
112  }
113}
114
115impl RegisterValue {
116  /// Coerce a numeric value to the requested register type, preserving the raw
117  /// scaled value. eTeX `\dimexpr`/`\numexpr`/`\glueexpr`/`\muexpr` fix the
118  /// result type by the *command*, not by the operands — but our expression
119  /// evaluator's "missing number, treated as zero" recovery and LHS-typed
120  /// arithmetic can return e.g. `Number(0)` for a `\dimexpr` (Dimension)
121  /// request, which then panics the register getter's `expect_dimension()`
122  /// (witness: `\dimexpr` inside a pgf-calc coordinate — arXiv 2302.02182 and
123  /// ~8 siblings in the large-scale canvas crashed here). Coercing the final
124  /// value to `rtype` keeps the getter total. Non-numeric values (Token/Tokens/
125  /// Pair) and already-matching types pass through unchanged.
126  pub fn coerce_to(self, rtype: RegisterType) -> RegisterValue {
127    let cur = self.register_type();
128    if cur == rtype {
129      return self;
130    }
131    let numeric = matches!(
132      cur,
133      RegisterType::Number
134        | RegisterType::Dimension
135        | RegisterType::MuDimension
136        | RegisterType::Glue
137        | RegisterType::MuGlue
138    );
139    if !numeric {
140      return self;
141    }
142    let raw = self.value_of();
143    match rtype {
144      RegisterType::Number => RegisterValue::Number(Number::new(raw)),
145      RegisterType::Dimension => RegisterValue::Dimension(Dimension::new(raw)),
146      RegisterType::MuDimension => RegisterValue::MuDimension(MuDimension::new(raw)),
147      RegisterType::Glue => RegisterValue::Glue(Glue::new(raw)),
148      RegisterType::MuGlue => RegisterValue::MuGlue(MuGlue::new(raw)),
149      // Token/Tokens/Pair targets are nonsensical for a numeric expr result;
150      // fall back to a plain number rather than panicking downstream.
151      _ => RegisterValue::Number(Number::new(raw)),
152    }
153  }
154}
155
156impl NumericOps for RegisterValue {
157  fn new(number: i64) -> Self { RegisterValue::Number(Number::new(number)) }
158  fn new_f64(number: f64) -> Self { RegisterValue::Number(Number::new_f64(number)) }
159  fn value_of(self) -> i64 {
160    match self {
161      RegisterValue::Number(v) => v.value_of(),
162      RegisterValue::Dimension(v) => v.value_of(),
163      RegisterValue::MuDimension(v) => v.value_of(),
164      RegisterValue::Glue(v) => v.value_of(),
165      RegisterValue::MuGlue(v) => v.value_of(),
166      RegisterValue::Token(v) => {
167        let message = s!(".value_of called on Token {:?}", v);
168        Warn!("register", "value_of", message);
169        -1
170      },
171      RegisterValue::Tokens(v) => {
172        let message = s!(".value_of called on Tokens {:?}", v);
173        Warn!("register", "value_of", message);
174        -1
175      },
176      RegisterValue::Pair(_) => 0,
177    }
178  }
179  fn register_type(&self) -> RegisterType {
180    match self {
181      RegisterValue::Number(_) => RegisterType::Number,
182      RegisterValue::Dimension(_) => RegisterType::Dimension,
183      RegisterValue::MuDimension(_) => RegisterType::MuDimension,
184      RegisterValue::Glue(_) => RegisterType::Glue,
185      RegisterValue::MuGlue(_) => RegisterType::MuGlue,
186      RegisterValue::Token(_) => RegisterType::Token,
187      RegisterValue::Tokens(_) => RegisterType::Tokens,
188      RegisterValue::Pair(_) => RegisterType::Dimension,
189    }
190  }
191  fn add<T: NumericOps>(self, other: T) -> Self {
192    match self {
193      RegisterValue::Number(v) => RegisterValue::Number(v.add(other)),
194      RegisterValue::Dimension(v) => RegisterValue::Dimension(v.add(other)),
195      RegisterValue::MuDimension(v) => RegisterValue::MuDimension(v.add(other)),
196      RegisterValue::Glue(v) => RegisterValue::Glue(v.add(other)),
197      RegisterValue::MuGlue(v) => RegisterValue::MuGlue(v.add(other)),
198      RegisterValue::Token(_) | RegisterValue::Tokens(_) | RegisterValue::Pair(_) => self,
199    }
200  }
201  fn subtract<T: NumericOps>(self, other: T) -> Self {
202    match self {
203      RegisterValue::Number(v) => RegisterValue::Number(v.subtract(other)),
204      RegisterValue::Dimension(v) => RegisterValue::Dimension(v.subtract(other)),
205      RegisterValue::MuDimension(v) => RegisterValue::MuDimension(v.subtract(other)),
206      RegisterValue::Glue(v) => RegisterValue::Glue(v.subtract(other)),
207      RegisterValue::MuGlue(v) => RegisterValue::MuGlue(v.subtract(other)),
208      RegisterValue::Token(_) | RegisterValue::Tokens(_) | RegisterValue::Pair(_) => self,
209    }
210  }
211  fn multiply<T: NumericOps>(self, other: T) -> Self {
212    match self {
213      RegisterValue::Number(v) => RegisterValue::Number(v.multiply(other)),
214      RegisterValue::Dimension(v) => RegisterValue::Dimension(v.multiply(other)),
215      RegisterValue::MuDimension(v) => RegisterValue::MuDimension(v.multiply(other)),
216      RegisterValue::Glue(v) => RegisterValue::Glue(v.multiply(other)),
217      RegisterValue::MuGlue(v) => RegisterValue::MuGlue(v.multiply(other)),
218      RegisterValue::Token(_) | RegisterValue::Tokens(_) | RegisterValue::Pair(_) => self,
219    }
220  }
221  fn negate(self) -> Self {
222    match self {
223      RegisterValue::Number(v) => RegisterValue::Number(v.negate()),
224      RegisterValue::Dimension(v) => RegisterValue::Dimension(v.negate()),
225      RegisterValue::MuDimension(v) => RegisterValue::MuDimension(v.negate()),
226      RegisterValue::Glue(v) => RegisterValue::Glue(v.negate()),
227      RegisterValue::MuGlue(v) => RegisterValue::MuGlue(v.negate()),
228      RegisterValue::Token(_) | RegisterValue::Tokens(_) | RegisterValue::Pair(_) => self,
229    }
230  }
231  fn divide<T: NumericOps>(self, other: T) -> Self
232  where Self: Sized {
233    match self {
234      RegisterValue::Number(v) => RegisterValue::Number(v.divide(other)),
235      RegisterValue::Dimension(v) => RegisterValue::Dimension(v.divide(other)),
236      RegisterValue::MuDimension(v) => RegisterValue::MuDimension(v.divide(other)),
237      RegisterValue::Glue(v) => RegisterValue::Glue(v.divide(other)),
238      RegisterValue::MuGlue(v) => RegisterValue::MuGlue(v.divide(other)),
239      RegisterValue::Token(_) | RegisterValue::Tokens(_) | RegisterValue::Pair(_) => self,
240    }
241  }
242  fn smaller<T: NumericOps>(self, other: T) -> Self
243  where Self: Sized {
244    match self {
245      RegisterValue::Number(v) => RegisterValue::Number(v.smaller(other)),
246      RegisterValue::Dimension(v) => RegisterValue::Dimension(v.smaller(other)),
247      RegisterValue::MuDimension(v) => RegisterValue::MuDimension(v.smaller(other)),
248      RegisterValue::Glue(v) => RegisterValue::Glue(v.smaller(other)),
249      RegisterValue::MuGlue(v) => RegisterValue::MuGlue(v.smaller(other)),
250      RegisterValue::Token(_) | RegisterValue::Tokens(_) | RegisterValue::Pair(_) => self,
251    }
252  }
253  fn larger<T: NumericOps>(self, other: T) -> Self
254  where Self: Sized {
255    match self {
256      RegisterValue::Number(v) => RegisterValue::Number(v.larger(other)),
257      RegisterValue::Dimension(v) => RegisterValue::Dimension(v.larger(other)),
258      RegisterValue::MuDimension(v) => RegisterValue::MuDimension(v.larger(other)),
259      RegisterValue::Glue(v) => RegisterValue::Glue(v.larger(other)),
260      RegisterValue::MuGlue(v) => RegisterValue::MuGlue(v.larger(other)),
261      RegisterValue::Token(_) | RegisterValue::Tokens(_) | RegisterValue::Pair(_) => self,
262    }
263  }
264  /// For now only meant as a type cast, unimplemented in other cases
265  /// DO NOT use this method to cast into a Glue object, define a `.to_glue()` instead
266  fn into_glue_type(self) -> Glue {
267    match self {
268      RegisterValue::Glue(v) => v,
269      _ => Glue::default(), // Non-glue register value → zero glue
270    }
271  }
272  fn to_attribute(&self) -> String
273  where Self: fmt::Display {
274    match self {
275      RegisterValue::Number(v) => v.to_attribute(),
276      RegisterValue::Dimension(v) => v.to_attribute(),
277      RegisterValue::MuDimension(v) => v.to_attribute(),
278      RegisterValue::Glue(v) => v.to_attribute(),
279      RegisterValue::MuGlue(v) => v.to_attribute(),
280      // Token, Tokens?
281      other => other.to_string(),
282    }
283  }
284}
285
286impl From<&RegisterValue> for Number {
287  fn from(v: &RegisterValue) -> Number {
288    match v {
289      RegisterValue::Number(n) => *n,
290      RegisterValue::Dimension(other) => Number::new(other.value_of()),
291      RegisterValue::MuDimension(other) => Number::new(other.value_of()),
292      RegisterValue::Glue(other) => Number::new(other.value_of()),
293      RegisterValue::MuGlue(other) => Number::new(other.value_of()),
294      RegisterValue::Token(other) => other.to_number(),
295      RegisterValue::Tokens(other) => other.to_number(),
296      RegisterValue::Pair(_) => Number::new(0),
297    }
298  }
299}
300impl From<RegisterValue> for Number {
301  fn from(v: RegisterValue) -> Number { (&v).into() }
302}
303impl From<RegisterValue> for Dimension {
304  fn from(v: RegisterValue) -> Dimension { (&v).into() }
305}
306impl From<RegisterValue> for Glue {
307  fn from(v: RegisterValue) -> Glue { (&v).into() }
308}
309impl From<RegisterValue> for MuGlue {
310  fn from(v: RegisterValue) -> MuGlue { (&v).into() }
311}
312impl From<RegisterValue> for f64 {
313  fn from(v: RegisterValue) -> f64 { v.value_of() as f64 }
314}
315impl From<RegisterValue> for i64 {
316  fn from(v: RegisterValue) -> i64 { v.value_of() }
317}
318
319impl From<Number> for Dimension {
320  fn from(n: Number) -> Dimension { Dimension::new(n.value_of()) }
321}
322impl From<Number> for Glue {
323  fn from(n: Number) -> Glue { Glue::new(n.value_of()) }
324}
325impl From<Number> for MuGlue {
326  fn from(n: Number) -> MuGlue { MuGlue::new(n.value_of()) }
327}
328
329// TODO: Does this successfully emulate the behavior in latexml?
330//   see example use in gullet::read_tokens_value
331impl From<RegisterValue> for Tokens {
332  fn from(v: RegisterValue) -> Tokens {
333    match v {
334      RegisterValue::Tokens(tks) => tks,
335      RegisterValue::Token(t) => Tokens!(t),
336      _ => Tokens!(T_OTHER!(v.value_of().to_string())),
337    }
338  }
339}
340
341impl From<&RegisterValue> for Dimension {
342  fn from(v: &RegisterValue) -> Dimension {
343    match v {
344      RegisterValue::Dimension(n) => *n,
345      RegisterValue::MuDimension(other) => Dimension::new(other.value_of()),
346      RegisterValue::Number(other) => Dimension::new(other.value_of()),
347      RegisterValue::Glue(other) => Dimension::new(other.value_of()),
348      RegisterValue::MuGlue(other) => Dimension::new(other.value_of()),
349      RegisterValue::Token(other) => other.to_number().into(),
350      RegisterValue::Tokens(other) => {
351        let message = s!(
352          "Token register can not be cast into a dimension: {:?}",
353          other
354        );
355        // silence a potential Fatal from 100 errors,
356        // until a better place is reached in the high-level conversion logic
357        let err = || {
358          Error!("expected", "dimension", message);
359          Ok(())
360        };
361        err().ok();
362        Dimension::new(0)
363      },
364      RegisterValue::Pair(_) => Dimension::new(0),
365    }
366  }
367}
368impl From<&RegisterValue> for Glue {
369  fn from(v: &RegisterValue) -> Glue {
370    match v {
371      RegisterValue::Glue(n) => *n,
372      RegisterValue::Number(other) => Glue::new(other.value_of()),
373      RegisterValue::Dimension(other) => Glue::new(other.value_of()),
374      RegisterValue::MuDimension(other) => Glue::new(other.value_of()),
375      RegisterValue::MuGlue(other) => Glue::new(other.value_of()),
376      RegisterValue::Token(other) => other.to_number().into(),
377      RegisterValue::Tokens(other) => {
378        let message = s!("Token register can not be cast into a Glue: {other:?}");
379        // silence a potential Fatal from 100 errors,
380        // until a better place is reached in the high-level conversion logic
381        let err = || {
382          Error!("expected", "dimension", message);
383          Ok(())
384        };
385        err().ok();
386        Glue::new(0)
387      },
388      RegisterValue::Pair(_) => Glue::new(0),
389    }
390  }
391}
392
393impl From<&RegisterValue> for MuGlue {
394  fn from(v: &RegisterValue) -> MuGlue {
395    match v {
396      RegisterValue::MuGlue(n) => *n,
397      RegisterValue::Number(other) => MuGlue::new(other.value_of()),
398      RegisterValue::Dimension(other) => MuGlue::new(other.value_of()),
399      RegisterValue::MuDimension(other) => MuGlue::new(other.value_of()),
400      RegisterValue::Glue(other) => MuGlue {
401        skip:  other.skip,
402        plus:  other.plus,
403        pfill: other.pfill,
404        minus: other.minus,
405        mfill: other.mfill,
406      },
407      RegisterValue::Token(other) => other.to_number().into(),
408      RegisterValue::Tokens(other) => {
409        let message = s!("Token register can not be cast into a Glue: {:?}", other);
410        let err = || {
411          Error!("expected", "dimension", message);
412          Ok(())
413        };
414        err().ok();
415        MuGlue::new(0)
416      },
417      RegisterValue::Pair(_) => MuGlue::new(0),
418    }
419  }
420}
421
422// passthrough the Debug print to the inner value, RegisterValue is transparent
423impl fmt::Debug for RegisterValue {
424  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
425    match self {
426      RegisterValue::Number(n) => write!(f, "{n:?}"),
427      RegisterValue::Dimension(d) => write!(f, "{d:?}"),
428      RegisterValue::MuDimension(d) => write!(f, "{d:?}"),
429      RegisterValue::Glue(g) => write!(f, "{g:?}"),
430      RegisterValue::MuGlue(g) => write!(f, "{g:?}"),
431      RegisterValue::Tokens(t) => write!(f, "{t:?}"),
432      RegisterValue::Token(t) => write!(f, "{t:?}"),
433      RegisterValue::Pair(p) => write!(f, "{p:?}"),
434    }
435  }
436}
437
438// passthrough the Display print to the inner value, RegisterValue is transparent
439impl fmt::Display for RegisterValue {
440  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
441    match self {
442      RegisterValue::Number(n) => write!(f, "{n}"),
443      RegisterValue::Dimension(d) => write!(f, "{d}"),
444      RegisterValue::MuDimension(d) => write!(f, "{d}"),
445      RegisterValue::Glue(g) => write!(f, "{g}"),
446      RegisterValue::MuGlue(g) => write!(f, "{g}"),
447      RegisterValue::Tokens(t) => write!(f, "{t}"),
448      RegisterValue::Token(t) => write!(f, "{t}"),
449      RegisterValue::Pair(p) => write!(f, "{p}"),
450    }
451  }
452}
453
454/// The type of a TeX Register
455#[derive(Debug, Copy, Clone, Eq)]
456pub enum RegisterType {
457  /// simple scalar number
458  Number,
459  /// a TeX dimension
460  Dimension,
461  /// a TeX mu dimension
462  MuDimension,
463  /// a TeX glue
464  Glue,
465  /// a TeX mu glue
466  MuGlue,
467  /// a TeX Token
468  Token,
469  /// multiple Tokens
470  Tokens,
471  /// a character definition
472  CharDef,
473  /// Placeholder for any argument accepted
474  Any,
475}
476// subtle detail - and maybe we need to refactor this entirely -
477// a CharDef and a Number type should be seen as equal
478impl PartialEq for RegisterType {
479  fn eq(&self, other: &Self) -> bool {
480    use RegisterType::*;
481    match self {
482      Number | CharDef => matches!(other, Number | CharDef),
483      Dimension => matches!(other, Dimension),
484      MuDimension => matches!(other, MuDimension),
485      Glue => matches!(other, Glue),
486      MuGlue => matches!(other, MuGlue),
487      Token => matches!(other, Token),
488      Tokens => matches!(other, Tokens),
489      Any => matches!(other, Any),
490    }
491  }
492}
493
494/// looks up a stored value from the state frame (at a constant key, or key based on the arguments)
495pub type RegisterGetterClosure = Rc<dyn Fn(Vec<ArgWrap>) -> Option<RegisterValue>>;
496/// sets a register value in the state::frame
497pub type RegisterSetterClosure = Rc<dyn Fn(RegisterValue, Option<Scope>, Vec<ArgWrap>)>;
498
499/// A struct representing a TeX register
500#[derive(Clone)]
501pub struct Register {
502  /// the public command sequence for this register
503  pub cs:            Token,
504  /// the internal address for this register
505  pub address:       String,
506  /// associated parameters, if any
507  pub parameters:    Option<Parameters>,
508  /// the type of values accepted by this register (Number, Dimension, ...)
509  pub register_type: RegisterType,
510  /// read-only flag (default: false)
511  pub readonly:      bool,
512  /// the current value
513  pub value:         Option<RegisterValue>,
514  /// reader for a value
515  pub getter:        Option<RegisterGetterClosure>,
516  /// setter for a value
517  pub setter:        Option<RegisterSetterClosure>,
518  /// a default value
519  pub default:       Option<RegisterValue>,
520  /// the unicode corresponding to the \mathchar of `value` (for chardef)
521  pub mathglyph:     Option<char>,
522  /// role attribute for math chardef (e.g. "MULOP", "BINOP")
523  pub role:          Option<SymStr>,
524  /// additional properties for math chardefs (meaning, stretchy, scriptpos, mathstyle, etc.)
525  pub chardef_props: HashMap<Stored>,
526  /// the source point of origin for this register definition
527  pub locator:       Locator,
528}
529impl Default for Register {
530  fn default() -> Self {
531    Register {
532      cs:            T_CS!("Register"),
533      address:       String::from("Register"),
534      locator:       Locator::default(),
535      parameters:    None,
536      register_type: RegisterType::Number,
537      getter:        None,
538      setter:        None,
539      readonly:      false,
540      value:         None,
541      mathglyph:     None,
542      role:          None,
543      chardef_props: HashMap::default(),
544      default:       None,
545    }
546  }
547}
548impl PartialEq for Register {
549  fn eq(&self, other: &Register) -> bool {
550    self.register_type == other.register_type
551      && self.parameters == other.parameters
552      && self.value == other.value
553      && self.address == other.address
554  }
555}
556impl fmt::Debug for Register {
557  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
558    write!(
559      f,
560      "Register[cs:{:?}, address:{:?}, parameters:{:?}, type:{:?}, readonly:{:?}, value:{:?}, default:{:?}]",
561      self.cs,
562      self.address,
563      self.parameters,
564      self.register_type,
565      self.readonly,
566      self.value,
567      self.default,
568    )
569  }
570}
571impl fmt::Display for Register {
572  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{self:?}") }
573}
574
575impl Object for Register {
576  fn stringify(&self) -> String { format!("{self:?}") }
577}
578
579impl Definition for Register {
580  fn is_register(&self) -> bool { true }
581  fn is_prefix(&self) -> bool { false }
582  fn is_readonly(&self) -> bool { self.readonly }
583  // Registers are not expandable — invoke should not be called
584  fn invoke(&self, _once_only: bool) -> Result<Tokens> { Ok(Tokens!()) }
585  fn get_parameters(&self) -> Option<&Parameters> { self.parameters.as_ref() }
586  fn get_cs(&self) -> Cow<'_, Token> { Cow::Owned(self.cs) }
587  fn get_cs_name(&self) -> Cow<'_, str> { Cow::Owned(self.cs.with_cs_name(ToString::to_string)) }
588  fn get_alias(&self) -> Option<&String> { None }
589
590  fn set_value(&self, value: RegisterValue, scope: Option<Scope>, args: Vec<ArgWrap>) {
591    if matches!(self.register_type, RegisterType::CharDef) {
592      let message = self
593        .cs
594        .with_cs_name(|cs_str| s!("Can't assign to chardef {}", cs_str));
595      let err = || {
596        Error!("unexpected", "chardef", message);
597        Ok(())
598      };
599      err().ok();
600    } else if let Some(setter) = &self.setter {
601      setter(value, scope, args);
602    } else {
603      // default setter
604      if self.readonly {
605        let message = s!("Can't assign to register {}", self.address);
606        Warn!("unexpected", self.address, message);
607      } else {
608        let loc = if args.is_empty() {
609          Cow::Borrowed(&self.address)
610        } else {
611          // If an arg fails to revert to tokens (e.g. an unexpected Pair
612          // or KV variant), contribute an empty string to the location
613          // key rather than panicking. This keeps register assignment
614          // alive on edge cases where the caller passes an atypical arg.
615          let args_string: String = args
616            .into_iter()
617            .map(|a| {
618              a.as_tokens()
619                .ok()
620                .flatten()
621                .map_or_else(String::new, |tks| tks.to_string())
622            })
623            .collect::<Vec<String>>()
624            .join("");
625          Cow::Owned(format!("{}{args_string}", self.address))
626        };
627        state::assign_value(&loc, value, scope);
628      }
629    }
630  }
631  // No before/after daemons ???
632  // (other than afterassign)
633  fn invoke_primitive(&self) -> Result<Vec<Digested>> {
634    // CharDef case
635    // A dilemma: If the \chardef were in a style file, you're prefer to revert to the $cs
636    // but if defined in the document source, better to use \char ###\relax, so it still "works"
637    if matches!(self.register_type, RegisterType::CharDef) {
638      let mut props = HashMap::default();
639      if let Some(role) = self.role {
640        props.insert("role", Stored::String(role));
641      }
642      // Copy extra math chardef properties (meaning, stretchy, scriptpos, etc.)
643      // Resolve need_scriptpos/need_mathstyle to actual values (Perl: Package.pm lines 2981-2984)
644      let in_display = state::lookup_string("IN_MATH_DISPLAY") == "true"
645        || state::lookup_font()
646          .map(|f| f.get_mathstyle().map(|s| s.as_ref()) == Some("display"))
647          .unwrap_or(false);
648      for (k, v) in &self.chardef_props {
649        let key_str = arena::with(*k, |s| s.to_string());
650        match key_str.as_str() {
651          "need_scriptpos" => {
652            // Resolve to actual scriptpos value based on display context
653            if !props.contains_key("scriptpos") {
654              let val = if in_display { "mid" } else { "post" };
655              props.insert("scriptpos", Stored::String(arena::pin_static(val)));
656            }
657          },
658          "need_mathstyle" => {
659            // Resolve to actual mathstyle value based on display context
660            if !props.contains_key("mathstyle") {
661              let val = if in_display { "display" } else { "text" };
662              props.insert("mathstyle", Stored::String(arena::pin_static(val)));
663            }
664          },
665          _ => {
666            props.insert_sym(*k, v.clone());
667          },
668        }
669      }
670      return Ok(vec![Digested::from(
671        if let Some(mathglyph) = self.mathglyph {
672          // Perl: for math chardefs, synthesize name from CS if no name/meaning set
673          if !props.contains_key("name") {
674            let cs_name = self.get_cs_name();
675            let n = cs_name.trim_start_matches('\\');
676            let has_matching_meaning = props.get("meaning").is_some_and(|m| {
677              if let Stored::String(s) = m {
678                arena::with(*s, |ms| ms == n)
679              } else {
680                false
681              }
682            });
683            if !has_matching_meaning {
684              props.insert("name", Stored::String(arena::pin(n)));
685            }
686          }
687          Tbox::new(
688            arena::pin_char(mathglyph),
689            None,
690            None,
691            Tokens!(
692              T_CS!("\\mathchar"),
693              self.value.as_ref().unwrap().revert()?,
694              T_CS!("\\relax")
695            ),
696            props,
697          )
698        } else {
699          Tbox::new(
700            font::decode_str(self.value.clone().unwrap().value_of() as u8, None, false)
701              .unwrap_or_else(|| pin!("")),
702            None,
703            None,
704            Tokens!(
705              T_CS!("\\char"),
706              self.value.as_ref().unwrap().revert()?,
707              T_CS!("\\relax")
708            ),
709            props,
710          )
711        },
712      )]);
713    }
714
715    // my $profiled = $state->lookupValue('PROFILING') && ($LaTeXML::CURRENT_TOKEN || $$self{cs});
716    // LaTeXML::Core::Definition::startProfiling($profiled, 'digest') if $profiled;
717    let args = self.read_arguments()?;
718    gullet::read_keyword(&["="])?;
719    let value = gullet::read_value(self.register_type().unwrap())?;
720
721    self.set_value(value, None, args);
722
723    state::after_assignment();
724    // # Tracing ?
725    // LaTeXML::Core::Definition::stopProfiling($profiled, 'digest') if $profiled;
726
727    Ok(Vec::new())
728  }
729
730  fn before_digest(&self) -> Option<&Vec<BeforeDigestClosure>> { None }
731  fn after_digest(&self) -> Option<&Vec<DigestionClosure>> { None }
732  fn read_arguments(&self) -> Result<Vec<ArgWrap>> {
733    let params = &self.parameters;
734    match params {
735      None => Ok(Vec::new()),
736      Some(params) => params.read_arguments(Some(self)),
737    }
738  }
739
740  fn do_absorption(&self, _document: &mut Document, _whatsit: &Whatsit) -> Result<Vec<Node>> {
741    fatal!(
742      Definition,
743      Unexpected,
744      "do_absorption on Primitive should never be called!"
745    );
746  }
747  fn value_of(&self, args: Vec<ArgWrap>) -> Option<RegisterValue> {
748    if matches!(self.register_type, RegisterType::CharDef) {
749      self.value.clone()
750    } else if let Some(ref getter) = self.getter {
751      getter(args)
752    } else {
753      let key = if args.is_empty() {
754        Cow::Borrowed(&self.address)
755      } else {
756        let args_string: String = args
757          .iter()
758          .map(ToString::to_string)
759          .collect::<Vec<String>>()
760          .join("");
761        Cow::Owned(format!("{}{args_string}", self.address))
762      };
763      state::with_value(&key, |v_opt| match v_opt {
764        Some(v) => v.into(),
765        None => self.default.clone(),
766      })
767    }
768  }
769  fn register_type(&self) -> Option<RegisterType> { Some(self.register_type) }
770}
771
772impl Register {
773  /// checks the readonly flag
774  pub fn is_readonly(&self) -> bool { self.readonly }
775  /// A CharDef is a specialized register;
776  /// You can't assign it; when you invoke the control sequence, it returns
777  /// the result of evaluating the character (more like a regular primitive).
778  /// When `mathglyph` is provided, it is the unicode corresponding to the `\mathchar` of `value`
779  pub fn new_chardef(
780    cs: Token,
781    value: Option<RegisterValue>,
782    mathglyph: Option<char>,
783    role: Option<SymStr>,
784  ) -> Self {
785    Register {
786      cs,
787      parameters: None,
788      value,
789      mathglyph,
790      role,
791      chardef_props: HashMap::default(),
792      register_type: RegisterType::CharDef,
793      readonly: true,
794      locator: gullet::get_locator(),
795      ..Register::default()
796    }
797  }
798
799  /// A math CharDef is like a regular CharDef but stores additional decoded math properties
800  /// (meaning, stretchy, scriptpos, mathstyle, chardef_name, etc.)
801  /// Perl: LaTeXML::Core::Definition::CharDef->new($cs, 'math', $value)
802  pub fn new_math_chardef(
803    cs: Token,
804    value: Option<RegisterValue>,
805    mathglyph: Option<char>,
806    role: Option<SymStr>,
807    props: CharDefProps,
808  ) -> Self {
809    let chardef_props = props.to_hashmap();
810    Register {
811      cs,
812      parameters: None,
813      value,
814      mathglyph,
815      role,
816      chardef_props,
817      register_type: RegisterType::CharDef,
818      readonly: true,
819      locator: gullet::get_locator(),
820      ..Register::default()
821    }
822  }
823
824  pub fn get_address(&self) -> Cow<'_, str> {
825    if self.address.is_empty() {
826      self.get_cs_name()
827    } else {
828      Cow::Borrowed(&self.address)
829    }
830  }
831}
832
833pub struct CharDefProps {
834  pub meaning:        Option<SymStr>,
835  // pub name: Option<SymStr>,  // chardef_name: synthesized at invoke time from CS name
836  pub stretchy:       Option<SymStr>,
837  pub scriptpos:      Option<SymStr>,
838  pub mathstyle:      Option<SymStr>,
839  pub need_scriptpos: bool,
840  pub need_mathstyle: bool,
841}
842impl CharDefProps {
843  fn to_hashmap(&self) -> HashMap<Stored> {
844    let mut chardef_props = HashMap::default();
845    if let Some(m) = self.meaning {
846      chardef_props.insert("meaning", Stored::String(m));
847    }
848    // if let Some(n) = self.name {
849    //   chardef_props.insert("name", Stored::String(n));
850    // }
851    if let Some(s) = self.stretchy {
852      chardef_props.insert("stretchy", Stored::String(s));
853    }
854    if let Some(sp) = self.scriptpos {
855      chardef_props.insert("scriptpos", Stored::String(sp));
856    }
857    if let Some(ms) = self.mathstyle {
858      chardef_props.insert("mathstyle", Stored::String(ms));
859    }
860    if self.need_scriptpos {
861      chardef_props.insert("need_scriptpos", Stored::Bool(true));
862    }
863    if self.need_mathstyle {
864      chardef_props.insert("need_mathstyle", Stored::Bool(true));
865    }
866    chardef_props
867  }
868}
869
870#[cfg(test)]
871mod tests {
872  use super::*;
873
874  #[test]
875  fn register_type_number_and_chardef_equal() {
876    // Perl parity: Number and CharDef are considered equal — a
877    // CharDef register can be assigned/compared as a Number.
878    // Documented in the `RegisterType::PartialEq trap` wisdom memory.
879    assert_eq!(RegisterType::Number, RegisterType::CharDef);
880    assert_eq!(RegisterType::CharDef, RegisterType::Number);
881  }
882
883  #[test]
884  fn register_type_distinct_between_other_variants() {
885    assert_ne!(RegisterType::Number, RegisterType::Dimension);
886    assert_ne!(RegisterType::Dimension, RegisterType::MuDimension);
887    assert_ne!(RegisterType::Glue, RegisterType::MuGlue);
888    assert_ne!(RegisterType::Token, RegisterType::Tokens);
889  }
890
891  #[test]
892  fn register_type_self_equality() {
893    // Each variant equals itself (except Any interactions).
894    for rt in [
895      RegisterType::Number,
896      RegisterType::Dimension,
897      RegisterType::MuDimension,
898      RegisterType::Glue,
899      RegisterType::MuGlue,
900      RegisterType::Token,
901      RegisterType::Tokens,
902    ] {
903      assert_eq!(rt, rt.clone(), "{rt:?}");
904    }
905  }
906
907  #[test]
908  fn register_value_from_number() {
909    let n = Number::new(42);
910    let rv: RegisterValue = n.into();
911    match rv {
912      RegisterValue::Number(v) => assert_eq!(v.value_of(), 42),
913      other => panic!("expected Number, got {other:?}"),
914    }
915  }
916
917  #[test]
918  fn register_value_from_dimension() {
919    let d = Dimension::new(65536);
920    let rv: RegisterValue = d.into();
921    match rv {
922      RegisterValue::Dimension(v) => assert_eq!(v.value_of(), 65536),
923      other => panic!("expected Dimension, got {other:?}"),
924    }
925  }
926
927  #[test]
928  fn register_value_from_pair() {
929    use crate::common::{float::Float, pair::Pair};
930    let p = Pair::new(Float(1.0), Float(2.0));
931    let rv: RegisterValue = p.into();
932    match rv {
933      RegisterValue::Pair(_) => {},
934      other => panic!("expected Pair, got {other:?}"),
935    }
936  }
937
938  #[test]
939  fn register_type_from_value() {
940    // From<&RegisterValue> for RegisterType extracts the type tag.
941    let n = RegisterValue::Number(Number::new(1));
942    let d = RegisterValue::Dimension(Dimension::new(1));
943    assert_eq!(RegisterType::from(&n), RegisterType::Number);
944    assert_eq!(RegisterType::from(&d), RegisterType::Dimension);
945  }
946
947  #[test]
948  fn register_value_equality() {
949    let a = RegisterValue::Number(Number::new(42));
950    let b = RegisterValue::Number(Number::new(42));
951    let c = RegisterValue::Number(Number::new(43));
952    assert_eq!(a, b);
953    assert_ne!(a, c);
954  }
955}
956
957//===============================================================================