Skip to main content

latexml_core/
definition.rs

1#[macro_use]
2pub mod expandable;
3pub mod argument;
4pub mod conditional;
5pub mod constructor;
6pub mod math_primitive;
7pub mod primitive;
8pub mod register;
9
10use std::{borrow::Cow, fmt, rc::Rc};
11
12use libxml::tree::Node;
13
14use self::{
15  argument::ArgWrap,
16  register::{RegisterType, RegisterValue},
17};
18use crate::{
19  Digested,
20  common::{
21    arena::{self, SymHashMap, SymStr},
22    dimension::Dimension,
23    error::{emit_warn, *},
24    font::Font,
25    object::Object,
26    store::Stored,
27  },
28  definition::conditional::ConditionalType,
29  document::Document,
30  gullet::Gullet,
31  mouth,
32  parameter::Parameters,
33  state::{Scope, expire_state_unlocked, local_state_unlocked},
34  token::Token,
35  tokens::{NO_TOKENS, TeXString, Tokens},
36  whatsit::Whatsit,
37};
38
39pub type ExpansionClosure = Rc<dyn Fn(Vec<ArgWrap>) -> Result<Tokens>>;
40pub type ConditionalClosure = Rc<dyn Fn(Vec<ArgWrap>) -> Result<bool>>;
41pub type PrimitiveFn = dyn Fn(Vec<ArgWrap>) -> Result<Vec<Digested>>;
42pub type PrimitiveClosure = Rc<PrimitiveFn>;
43pub type BeforeDigestClosure = Rc<dyn Fn() -> Result<Vec<Digested>>>;
44pub type PropertiesClosure = Rc<dyn Fn(&Vec<Option<Digested>>) -> Result<SymHashMap<Stored>>>;
45pub type DigestionClosure = Rc<dyn Fn(&mut Whatsit) -> Result<Vec<Digested>>>;
46pub type ReplacementClosure =
47  Rc<dyn Fn(&mut Document, &Vec<Option<Digested>>, &SymHashMap<Stored>) -> Result<()>>;
48pub type ConstructionClosure = Rc<dyn Fn(&mut Document, &Whatsit) -> Result<()>>;
49pub type DigestedReversionClosure = Rc<dyn Fn(&Whatsit, &Vec<Option<Digested>>) -> Result<Tokens>>;
50pub type SizingClosure = Rc<dyn Fn(&Whatsit) -> Result<(Dimension, Dimension, Dimension)>>;
51pub type FontClosure = Rc<dyn Fn(Option<&Whatsit>) -> Result<Font>>;
52
53#[derive(Clone)]
54pub enum ExpansionBody {
55  Closure(ExpansionClosure),
56  Tokens(Tokens),
57}
58
59impl ExpansionBody {
60  /// A convenience method that pushes a token into the internal tokens of
61  /// an existing `ExpansionBody::Tokens`, re-wrapping the outer structs
62  pub fn push(&mut self, t: Token) {
63    match self {
64      ExpansionBody::Tokens(tks) => tks.unlist_mut().push(t),
65      ExpansionBody::Closure(_) => {
66        // Can't push a token into a closure-based expansion — silently ignore
67      },
68    }
69  }
70}
71
72impl fmt::Debug for ExpansionBody {
73  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74    match self {
75      ExpansionBody::Closure(code) => write!(f, "CODE({:p})", Rc::as_ptr(code)),
76      ExpansionBody::Tokens(ts) => write!(f, "{ts:?}"),
77    }
78  }
79}
80
81impl Default for ExpansionBody {
82  fn default() -> Self { ExpansionBody::Tokens(NO_TOKENS) }
83}
84
85impl PartialEq for ExpansionBody {
86  fn eq(&self, other: &ExpansionBody) -> bool {
87    match self {
88      ExpansionBody::Closure(self_closure) => match other {
89        ExpansionBody::Closure(other_closure) => Rc::ptr_eq(self_closure, other_closure),
90        ExpansionBody::Tokens(other_tokens) => {
91          // sometimes the \meaning game forces us into the same CODE(0x...) pointer footprint
92          // appearing as Tokens and as the original Closure. if we do this carefully, we
93          // can get the two .to_string() variants to match...
94          format!("CODE({:p})", Rc::as_ptr(self_closure)) == other_tokens.to_string()
95        },
96      },
97      ExpansionBody::Tokens(self_tks) => match other {
98        ExpansionBody::Tokens(other_tks) => self_tks == other_tks,
99        ExpansionBody::Closure(other_closure) => {
100          format!("CODE({:p})", Rc::as_ptr(other_closure)) == self_tks.to_string()
101        },
102      },
103    }
104  }
105}
106
107#[derive(Clone)]
108pub enum PrimitiveBody {
109  Closure(PrimitiveClosure),
110  String(SymStr),
111}
112impl From<char> for PrimitiveBody {
113  fn from(c: char) -> Self { PrimitiveBody::String(arena::pin_char(c)) }
114}
115
116#[derive(Clone)]
117pub enum Reversion {
118  Closure(DigestedReversionClosure),
119  Tokens(Tokens),
120}
121
122impl PartialEq for Reversion {
123  fn eq(&self, other: &Reversion) -> bool {
124    match self {
125      Reversion::Tokens(t) => match other {
126        Reversion::Tokens(t2) => t == t2,
127        _ => false,
128      },
129      // never compare pointers - i.e. never equal
130      _ => false,
131    }
132  }
133}
134
135/// NOTE (`TeXString` guard): this and `From<&str> for ExpansionBody` below are
136/// the residual back doors into the tokenizer — a `String` reaches them as
137/// `s.as_str()` without ever naming its intent. They stay on plain `&str`
138/// because narrowing them to `&'static str` would reject the many legitimate
139/// runtime-built bodies. Anything that flattens `Tokens` must still go through
140/// [`Tokens::untex_string`](crate::tokens::Tokens::untex_string) before it gets
141/// here.
142impl From<&str> for Reversion {
143  fn from(t: &str) -> Reversion {
144    Reversion::Tokens(
145      mouth::tokenize_internal(TeXString::assembled(t.to_string()))
146        .pack_parameters()
147        .unwrap(),
148    )
149  }
150}
151impl From<Tokens> for Reversion {
152  fn from(ts: Tokens) -> Reversion { Reversion::Tokens(ts) }
153}
154
155impl From<Token> for ExpansionBody {
156  fn from(t: Token) -> ExpansionBody { ExpansionBody::Tokens(Tokens!(t)) }
157}
158
159impl From<Token> for Option<ExpansionBody> {
160  fn from(t: Token) -> Option<ExpansionBody> { Some(ExpansionBody::Tokens(Tokens!(t))) }
161}
162
163impl From<Tokens> for ExpansionBody {
164  fn from(t: Tokens) -> ExpansionBody { ExpansionBody::Tokens(t) }
165}
166
167impl From<Tokens> for Option<ExpansionBody> {
168  fn from(t: Tokens) -> Option<ExpansionBody> { if t.is_empty() { None } else { Some(t.into()) } }
169}
170
171impl From<&str> for ExpansionBody {
172  fn from(s: &str) -> ExpansionBody {
173    mouth::tokenize_internal(TeXString::assembled(s.to_string())).into()
174  }
175}
176
177impl From<String> for ExpansionBody {
178  fn from(s: String) -> ExpansionBody { s.as_str().into() }
179}
180
181impl From<ArgWrap> for ExpansionBody {
182  fn from(t: ArgWrap) -> ExpansionBody {
183    ExpansionBody::Tokens(t.owned_tokens().unwrap_or_default())
184  }
185}
186impl From<ArgWrap> for Option<ExpansionBody> {
187  fn from(t: ArgWrap) -> Option<ExpansionBody> {
188    match t.owned_tokens() {
189      Some(tks) if !tks.is_empty() => Some(ExpansionBody::Tokens(tks)),
190      _ => None,
191    }
192  }
193}
194
195#[derive(Clone)]
196pub enum FontDirective {
197  Closure(FontClosure),
198  Asset(Rc<Font>),
199}
200
201impl From<Font> for FontDirective {
202  fn from(f: Font) -> Self { FontDirective::Asset(Rc::new(f)) }
203}
204impl From<FontClosure> for FontDirective {
205  fn from(fc: FontClosure) -> Self { FontDirective::Closure(fc) }
206}
207impl FontDirective {
208  pub fn get_font(&self, whatsit: Option<&Whatsit>) -> Result<Rc<Font>> {
209    match self {
210      FontDirective::Closure(fc) => Ok(Rc::new((fc)(whatsit)?)),
211      FontDirective::Asset(font) => Ok(Rc::clone(font)),
212    }
213  }
214  pub fn get_asset(&self) -> Option<Rc<Font>> {
215    if let FontDirective::Asset(font) = self {
216      Some(Rc::clone(font))
217    } else {
218      None
219    }
220  }
221}
222impl fmt::Debug for FontDirective {
223  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
224    match self {
225      FontDirective::Closure(_) => write!(f, "<FontClosure>"),
226      FontDirective::Asset(font) => write!(f, "{:?}", *font),
227    }
228  }
229}
230impl PartialEq for FontDirective {
231  fn eq(&self, other: &FontDirective) -> bool {
232    match self {
233      FontDirective::Closure(_) => false, // we can't compare them for now?
234      FontDirective::Asset(asset1) => match other {
235        FontDirective::Closure(_) => false,
236        FontDirective::Asset(asset2) => *asset1 == *asset2,
237      },
238    }
239  }
240}
241
242pub trait Definition: Object {
243  fn invoke(&self, once_only: bool) -> Result<Tokens>;
244  fn invoke_primitive(&self) -> Result<Vec<Digested>>;
245
246  /// We can almost always return the CS by reference, except in a Register's RefCell, where we are
247  /// forced to clone
248  fn get_cs(&self) -> Cow<'_, Token>;
249  fn get_cs_name(&self) -> Cow<'_, str>;
250  fn get_cs_or_alias(&self) -> Cow<'_, Token> {
251    match self.get_alias() {
252      Some(alias) => Cow::Owned(T_CS!(alias)),
253      None => self.get_cs(),
254    }
255  }
256  fn get_sizer(&self) -> Option<SizingClosure> { None }
257  fn get_alias(&self) -> Option<&String>;
258  fn is_protected(&self) -> bool { false }
259  fn is_register(&self) -> bool { false }
260  fn is_prefix(&self) -> bool { false }
261  fn is_readonly(&self) -> bool { false }
262  fn get_test(&self) -> Option<&ConditionalClosure> { None }
263  fn get_conditional_type(&self) -> Option<ConditionalType> { None }
264
265  fn read_arguments(&self) -> Result<Vec<ArgWrap>>
266  where Self: Sized {
267    match self.get_parameters() {
268      None => Ok(Vec::new()),
269      Some(params) => params.read_arguments(Some(self)),
270    }
271  }
272  fn get_parameters(&self) -> Option<&Parameters>;
273
274  // ======================================================================
275  // Return the Tokens that would invoke the given definition with arguments.
276  fn invocation(&mut self, args: Vec<Option<Tokens>>, _gullet: &mut Gullet) -> Result<Tokens> {
277    let mut invocation_result: Vec<Token> = vec![self.get_cs().into_owned()];
278
279    match self.get_parameters() {
280      None => {},
281      Some(params) => {
282        for result_token in params.revert_arguments(args)? {
283          invocation_result.push(result_token);
284        }
285      },
286    }
287    Ok(Tokens::new(invocation_result))
288  }
289
290  fn get_num_args(&self) -> usize { 0 }
291
292  fn do_absorption(&self, _document: &mut Document, _whatsit: &Whatsit) -> Result<Vec<Node>>;
293  fn before_digest(&self) -> Option<&Vec<BeforeDigestClosure>> { None }
294  fn after_digest(&self) -> Option<&Vec<DigestionClosure>> { None }
295  fn after_digest_body(&self) -> Option<&Vec<DigestionClosure>> { None }
296  fn capture_body(&self) -> bool { false }
297
298  fn execute_before_digest(&self) -> Result<Vec<Digested>> {
299    local_state_unlocked(true);
300    // Perl #2798 (S2): executeBeforeDigest pushes each hook's result DIRECTLY
301    // onto the active @LaTeXML::LIST as it runs, rather than collecting them to
302    // be prepended later. This lets a *later* before_digest side-effect see the
303    // earlier hooks' boxes in the current list — e.g. `\AtBeginEnvironment{
304    // equation}{…}` digests its text into the paragraph BEFORE the equation's
305    // own `before_equation` does `begin_mode("display_math")` → leave_horizontal,
306    // so the hook text stays in the (outside) paragraph instead of splitting off.
307    // The final box order is unchanged (these boxes still precede the whatsit,
308    // which the caller appends and the digestion loop pushes afterwards); only
309    // the *timing* moves earlier. Returns nothing.
310    if let Some(pre_list) = self.before_digest() {
311      for pre in pre_list.iter() {
312        for bx in pre()? {
313          crate::stomach::push_box_list(bx);
314        }
315      }
316    }
317    expire_state_unlocked();
318    Ok(Vec::new())
319  }
320  fn execute_after_digest(&self, whatsit: &mut Whatsit) -> Result<Vec<Digested>> {
321    local_state_unlocked(true);
322    let mut after_digested = Vec::new();
323    if let Some(post_list) = self.after_digest() {
324      for post in post_list.iter() {
325        after_digested.extend(post(whatsit)?);
326      }
327    }
328    expire_state_unlocked();
329    Ok(after_digested)
330  }
331
332  fn execute_after_digest_body(&self, whatsit: &mut Whatsit) -> Result<Vec<Digested>> {
333    local_state_unlocked(true);
334    let mut after_body_digested = Vec::new();
335    if let Some(post_list) = self.after_digest_body() {
336      // info!("Found {:?} after_digest_body closures, capture_body was: {:?}", post_list.len(),
337      // self.capture_body());
338      for post in post_list {
339        let after_body_digest_result = post(whatsit)?;
340        after_body_digested.extend(after_body_digest_result);
341      }
342    }
343    expire_state_unlocked();
344    Ok(after_body_digested)
345  }
346
347  fn value_of(&self, _args: Vec<ArgWrap>) -> Option<RegisterValue> { None }
348  /// runs the setter to assign the value for a register
349  fn set_value(&self, _value: RegisterValue, _scope: Option<Scope>, _args: Vec<ArgWrap>) {
350    emit_warn(
351      "internal",
352      "register",
353      "set_value called on non-register definition",
354    );
355  }
356  fn register_type(&self) -> Option<RegisterType> { None }
357  fn get_reversion_spec(&self) -> Option<Reversion> { None }
358  fn get_expansion(&self) -> Option<&ExpansionBody> { None }
359
360  fn stringify_type(&self, deftype: &str) -> String {
361    let name = match self.get_alias() {
362      Some(alias) => alias.clone(),
363      None => self.get_cs().with_cs_name(ToString::to_string),
364    };
365    if let Some(parameters) = self.get_parameters() {
366      s!("{}[{} {}]", deftype, name, parameters.stringify())
367    } else {
368      s!("{}[{}]", deftype, name)
369    }
370  }
371}
372
373// We need to compare definitions for the internal TeX logic to make sense, but we don't have Perl's
374// level of meta-programming, since cloning an `Rc<Definition>` for storage makes it impossible to
375// compare with the old `Rc<Definition>`. Hence, we need our own meta-programming "hack", via the
376// `stringify` method that is different for each `definition` implementation
377// (`Primitive`/`Constructor`/etc) and each control sequence
378//
379// This could evolve if Rust comes up with a best practice for implementing `PartialEq` on trait
380// objects.
381impl PartialEq for dyn Definition {
382  fn eq(&self, other: &dyn Definition) -> bool { self.stringify() == other.stringify() }
383}
384
385impl fmt::Display for dyn Definition {
386  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
387    if let Some(params) = self.get_parameters() {
388      write!(f, "{} {}", self.get_cs_name(), params)
389    } else {
390      write!(f, "{}", self.get_cs_name())
391    }
392  }
393}
394
395impl fmt::Display for ExpansionBody {
396  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
397    match self {
398      ExpansionBody::Tokens(t) => write!(f, "{t}"),
399      ExpansionBody::Closure(code) => {
400        write!(f, "ExpansionBody::Closure({:p})", Rc::as_ptr(code))
401      }, // what is the right way to serialize this, e.g. for the \meaning macro
402    }
403  }
404}