Skip to main content

latexml_core/binding/def/
macros.rs

1// Macros requiring repetitions need to be handled outside of the main setup macro, as nested
2// macros currently don't support repetition Details at:
3// https://github.com/rust-lang/rust/issues/35853
4
5/// build a Font from key=>val pairs
6#[macro_export]
7macro_rules! Font {
8  ($($key:ident => $value:expr_2021),*) => (
9    Some(Font { $($key: $value.into_font_field(),)* .. Font::default() })
10)}
11
12/// build a FontDirective from key=>val pairs
13/// (currently only FontDirective::Asset is supported in this macro)
14#[macro_export]
15macro_rules! FontDirective {
16  ($($key:ident => $value:expr_2021),*) => (
17    Some(FontDirective::Asset(Rc::new(
18      Font { $($key: $value.into_font_field(),)* .. Font::default() }
19    ))))
20}
21
22/// given a struct `$name`, create a new instance of it using the given key=>val pairs
23/// and complete the remaining entries via the Default instance
24#[macro_export]
25macro_rules! NewDefault {
26  ($name:ident, $($key:ident => $value:expr_2021),*) => ($name {
27    $($key: $value,)*
28    ..$name::default()
29  })
30}
31
32/// Just like NewDefault, but adds a mandatory `.into_option()` to all values
33#[macro_export]
34macro_rules! NewDefaultV {
35  ($name:ident, $($key:ident => $value:expr_2021),*) => ($name {
36    $($key: $value.into_option(),)*
37    ..$name::default()
38  })
39}
40
41// Useful shorthand macros, to brainstorm ergonomics ideas,
42// and to aid binding development
43
44/// Transfers a mutable pointer to a hashmap entry, or fills in with a default if missing.
45///
46/// Assumption: `$receiver` is HashMap<String,String>.
47/// If, and only if, `$has_receiver` does not have a value at slot `$val`,
48/// and `$struct_source` has a set value at `$val`,
49/// then transfers (with ownership) the `$val` field of a `$struct_source` into the `$receiver`.
50#[macro_export]
51macro_rules! transfer_opt_default {
52  ($val:ident, $struct_source:ident, $receiver:ident) => {
53    if let Some(ref tval) = $struct_source.$val {
54      $receiver
55        .entry(stringify!($val).to_owned())
56        .or_insert(tval.to_string());
57    }
58  };
59}
60
61// Discussion: Ideally we wouldn't need any of these closure macros, just the way latexml proper
62// doesn't. In latexml, you could say:
63
64#[macro_export]
65macro_rules! before_digest {
66  ($(sub)? $body:block) => {
67    vec![before_digest_single!($body)]
68  };
69}
70
71#[macro_export]
72macro_rules! before_digest_single {
73  ($body:block) => {
74    Rc::new(move || $body.into_digested_result())
75  };
76}
77
78#[macro_export]
79macro_rules! before_digest_simple {
80  ($body:block) => {
81    Rc::new(move || $body.into_digested_result())
82  };
83}
84
85#[macro_export]
86macro_rules! tagsub {
87  // 2-argument form: sub[document, node] { ... }
88  ($document:ident, $node:ident, $body:block) => {
89    vec![Rc::new(
90      |$document: &mut Document, mut $node: &mut Node, _whatsit: Option<&Digested>| -> Result<()> {
91        $body
92        Ok(())
93      },
94    )]
95  };
96  // 3-argument form: sub[document, node, whatsit] { ... }
97  // Matches Perl's ($document, $node, $box) signature for Tag afterClose/afterOpen
98  ($document:ident, $node:ident, $whatsit:ident, $body:block) => {
99    vec![Rc::new(
100      |$document: &mut Document, mut $node: &mut Node, $whatsit: Option<&Digested>| -> Result<()> {
101        $body
102        Ok(())
103      },
104    )]
105  };
106}
107
108#[macro_export]
109macro_rules! sizersub {
110  ($whatsit:ident, $body:block) => {
111    Rc::new(
112      |$whatsit: &Whatsit| -> Result<(Dimension, Dimension, Dimension)> {
113        let macro_out = $body;
114        macro_out
115      },
116    )
117  };
118}
119
120#[macro_export]
121macro_rules! rewrite_replace_sub {
122  ($document:ident, $nodes:ident, $body:block) => {
123  Some(Rc::new(
124    |$document: &mut Document, mut $nodes: Vec<&mut Node>| -> Result<()> {
125      $body
126      Ok(())
127    },
128  ))
129  };
130}
131
132#[macro_export]
133macro_rules! noreplacement {
134  () => {
135    |doc, whatsit, props| Ok(())
136  };
137}
138
139#[macro_export]
140macro_rules! replacement {
141  ($doc:ident, $args:ident, $props:ident, $body:block) => (
142    move |$doc:&mut Document,$args: &Vec<Option<Digested>>,
143      $props: &SymHashMap<Stored>| -> Result<()> {
144    $body
145    Ok(())
146  })
147}
148
149#[macro_export]
150macro_rules! construct {
151  ($doc:ident, $whatsit:ident, $body:block) => {
152  vec![Rc::new(
153    move |$doc: &mut Document, $whatsit: &Whatsit| -> Result<()> {
154      $body
155      Ok(())
156    }
157  )]
158}}
159
160#[macro_export]
161macro_rules! properties {
162  (sub [$args:ident] $body:block) => {
163    properties!($args, $body)
164  };
165  ($args:ident, $body:block) => {
166    Rc::new(move |mut $args: &Vec<Option<Digested>>| -> Result<SymHashMap<Stored>> { $body })
167  };
168  ($(sub)? $body:block) => {
169    Rc::new(
170      move |_args: &Vec<Option<Digested>>| -> Result<SymHashMap<Stored>> {
171        $body.into_properties_result()
172      },
173    )
174  };
175  ($value:expr_2021) => {
176    Rc::new(
177      move |_args: &Vec<Option<Digested>>| -> Result<SymHashMap<Stored>> { Ok($value.clone()) },
178    )
179  };
180}
181
182#[macro_export]
183macro_rules! after_digest {
184  ($(sub)? $body:block) => {
185    vec![after_digest_single!(_whatsit, $body)]
186  };
187  ($whatsit:ident, $body:block) => {
188    vec![after_digest_single!($whatsit, $body)]
189  };
190}
191
192#[macro_export]
193macro_rules! after_digest_single {
194  ($whatsit:ident, $body:block) => {
195    Rc::new(move |$whatsit: &mut Whatsit| -> Result<Vec<Digested>> { $body.into_digested_result() })
196  };
197}
198#[macro_export]
199macro_rules! after_digest_simple {
200  ($whatsit:ident, $body:block) => {
201    Rc::new(move |$whatsit: &mut Whatsit| -> Result<Vec<Digested>> { $body.into_digested_result() })
202  };
203}
204
205#[macro_export]
206macro_rules! reader {
207  ($inner:ident, $extra:ident, $body:block) => {
208    Rc::new(
209      |$inner: Option<&Parameters>, $extra: &[Tokens]| -> Result<ArgWrap> {
210        $body.into_result_argwrap()
211      },
212    )
213  };
214}
215
216#[macro_export]
217macro_rules! predigest {
218  ($arg:ident, $body:block) => {
219    Some(Rc::new(
220      |$arg: ArgWrap, _: &[Tokens]| -> Result<Option<Digested>> {
221        $body.into_digested_option_result()
222      },
223    ))
224  };
225  ($arg:ident, $extra:ident, $body:block) => {
226    Some(Rc::new(
227      |$arg: ArgWrap, $extra: &[Tokens]| -> Result<Option<Digested>> {
228        $body.into_digested_option_result()
229      },
230    ))
231  };
232}
233
234/// A closure for obtaining a `RegisterValue`, usually owned by a `Register` getter.
235#[macro_export]
236macro_rules! getter {
237  ($args: ident, $body:block) => {
238    Some(Rc::new(
239      move |mut $args: Vec<ArgWrap>| -> Option<RegisterValue> {
240        $body.into_register_value_option()
241      },
242    ))
243  };
244}
245
246#[macro_export]
247macro_rules! setter {
248  ($value:ident, $args: ident, $body:block) => {
249    Some(Rc::new(
250      move |$value: RegisterValue, _scope: Option<Scope>, mut $args: Vec<ArgWrap>| $body,
251    ))
252  };
253  ($value:ident, $scope:ident, $args: ident, $body:block) => {
254    Some(Rc::new(
255      move |$value: RegisterValue, $scope: Option<Scope>, mut $args: Vec<ArgWrap>| $body,
256    ))
257  };
258}
259
260#[macro_export]
261macro_rules! reversion {
262  ($arg:ident, $inner:ident, $extra:ident, $body:block) => {
263    Some(Rc::new(
264      |mut $arg: Vec<Token>, $inner: Option<&Parameters>, $extra: &[Tokens]| -> Result<Tokens> {
265        $body
266      },
267    ))
268  };
269}
270
271#[macro_export]
272macro_rules! reversion_digested {
273  ($whatsit:ident, $args:ident, $body:block) => {
274    Some(Reversion::Closure(Rc::new(
275      move |$whatsit: &Whatsit, $args: &Vec<Option<Digested>>| -> Result<Tokens> { $body },
276    )))
277  };
278}
279
280// TODO: These .clone calls are silly... can we either
281// 1) Document::insert_element work with a &Vec<Digested>? or
282// 2) we can use mutable Whatsit properties in replacements, where we remove Vec<Digested> instances
283// for cases that will be absorbed? or something else that is lighter on memory allocations?
284
285#[macro_export]
286macro_rules! prop_digested {
287  ($props:ident, $key:expr_2021) => {
288    match $props.get($key) {
289      Some(Stored::VecDigested(vd)) => vd.iter().collect::<Vec<&Digested>>(),
290      Some(Stored::Digested(d)) => vec![&*d],
291      Some(Stored::String(s)) => panic!(
292        "prop_digested! called on a string property {:?} with value {:?}.",
293        $key, s
294      ),
295      None => Vec::new(),
296      other => {
297        $crate::common::error::emit_warn(
298          "unimplemented",
299          "prop_digested",
300          &format!("Please extend the api_macros::prop_digested macro to support: {other:?}"),
301        );
302        // Return empty vec instead of panicking
303        Vec::new()
304      },
305    }
306  };
307}
308
309// Discussion: It is unclear what the best authoring syntax is for our family of latexml binding
310// macros. One idea is to keep them very close to the Rust internals, but we suffer from a variety
311// of boilerplate, such as needing to spell out `key => Some(value.to_string())`, rather than a
312// direct `key => value`.
313//
314// For now I am making the decision to keep writing out the verbose form,
315// and will refactor at a later date, when the trade-offs become more clear. Smart use of the Cow
316// struct is another idea. I will use a helper though:
317
318#[macro_export]
319macro_rules! prop_str {
320  ($props:ident, $key:expr_2021) => {
321    match $props.get($key) {
322      Some(&Stored::String(ref id)) => *id,
323      _ => pin!(""),
324    }
325  };
326}
327
328#[macro_export]
329macro_rules! prop_string {
330  ($props:ident, $key:expr_2021) => {
331    match $props.get($key) {
332      Some(&Stored::String(id)) => arena::to_string(id),
333      _ => String::new(),
334    }
335  };
336}
337
338#[macro_export]
339macro_rules! prop_whatsit {
340  ($props:ident, $key:expr_2021) => {
341    match $props.get($key) {
342      // Cloning here is OK now, as there is an Rc<> guard over the DigestedData
343      Some(&Stored::Digested(ref rc)) => (**rc).clone(),
344      _ => Digested::Whatsit(Rc::new(RefCell::new(Whatsit::default()))),
345    }
346  };
347}
348
349#[macro_export]
350macro_rules! prop_bool {
351  ($props:ident, $key:expr_2021) => {
352    match $props.get($key) {
353      Some(&Stored::Bool(v)) => v,
354      _ => false,
355    }
356  };
357}
358
359/// Convenience macro to flexibly unpack a collection of `Vec<ArgWrap>` arguments into individual
360/// `Tokens` variables.
361#[macro_export]
362macro_rules! unref {
363  ($args:ident => $var:ident) => (count_unpack_ref!(0usize, $args => $var));
364  ($args:ident => $var:ident,$($tail:ident),*) => (
365    count_unpack_ref!(0usize,$args => $var,$($tail),*))
366}
367#[macro_export]
368macro_rules! count_unpack_ref {
369  ($index:expr_2021, $args:ident => $var:ident) => {
370    let $var = $args[$index].as_ref().unwrap();
371  };
372  ($index:expr_2021, $args:ident => $var:ident,$($tail:ident),*) => {
373    count_unpack_ref!($index,$args => $var);
374    count_unpack_ref!(1usize+$index, $args => $($tail),*)
375  };
376}
377
378/// Try to efficiently unwrap a `Vec<T>` into a `[T;n]` for `$arg1`...`$argn`
379#[macro_export]
380macro_rules! unpack_opt {
381  ($args:ident => $arg1:ident) => {
382    let [$arg1]: [_; 1] = $args.try_into().unwrap();
383  };
384  ($args:ident => $arg1:ident,$arg2:ident) => {
385    let [$arg1, $arg2]: [_; 2] = $args.try_into().unwrap();
386  };
387  ($args:ident => $arg1:ident,$arg2:ident,$arg3:ident) => {
388    let [$arg1, $arg2, $arg3]: [_; 3] = $args.try_into().unwrap();
389  };
390  ($args:ident => $arg1:ident,$arg2:ident,$arg3:ident,$arg4:ident) => {
391    let [$arg1, $arg2, $arg3, $arg4]: [_; 4] = $args.try_into().unwrap();
392  };
393  ($args:ident => $arg1:ident,$arg2:ident,$arg3:ident,$arg4:ident,$arg5:ident) => {
394    let [$arg1, $arg2, $arg3, $arg4, $arg5]: [_; 5] = $args.try_into().unwrap();
395  };
396}
397
398/// Try to efficiently unwrap a `&Vec<T>` into a `&[T;n]` for `$arg1`...`$argn`
399#[macro_export]
400macro_rules! unpack_opt_ref {
401  ($args:ident => $arg1:ident) => {
402    let [$arg1]: &[_; 1] = $args[..1].try_into().unwrap();
403  };
404  ($args:ident => $arg1:ident,$arg2:ident) => {
405    let [$arg1, $arg2]: &[_; 2] = $args[..2].try_into().unwrap();
406  };
407  ($args:ident => $arg1:ident,$arg2:ident,$arg3:ident) => {
408    let [$arg1, $arg2, $arg3]: &[_; 3] = $args[..3].try_into().unwrap();
409  };
410  ($args:ident => $arg1:ident,$arg2:ident,$arg3:ident,$arg4:ident) => {
411    let [$arg1, $arg2, $arg3, $arg4]: &[_; 4] = $args[..4].try_into().unwrap();
412  };
413  ($args:ident => $arg1:ident,$arg2:ident,$arg3:ident,$arg4:ident,$arg5:ident) => {
414    let [$arg1, $arg2, $arg3, $arg4, $arg5]: &[_; 5] = $args[..5].try_into().unwrap();
415  };
416}
417
418/// Convert the number to lower case roman numerals, returning a list of LaTeXML::Core::Token
419#[macro_export]
420macro_rules! roman {
421  ($stuff:expr_2021) => {
422    Tokens::new(ExplodeText!(roman_aux($stuff as i64)))
423  };
424}
425/// Convert the number to upper case roman numerals, returning a list of LaTeXML::Core::Token
426#[macro_export]
427macro_rules! Roman {
428  ($stuff:expr_2021) => {
429    Tokens::new(ExplodeText!(roman_aux($stuff as i64).to_ascii_uppercase()))
430  };
431}
432
433#[macro_export]
434macro_rules! requireMath {
435  ($cs_name:expr_2021) => {
436    if !$crate::state::lookup_bool_sym($crate::pin!("IN_MATH")) {
437      let message = s!("{} should only appear in math mode", $cs_name);
438      Warn!("unexpected", "mode", message);
439    }
440  };
441}
442#[macro_export]
443macro_rules! forbidMath {
444  ($cs_name:expr_2021) => {
445    if $crate::state::lookup_bool_sym($crate::pin!("IN_MATH")) {
446      let message = s!("{} should not appear in math mode", $cs_name);
447      Warn!("unexpected", "mode", message);
448    }
449  };
450}
451
452#[macro_export]
453macro_rules! AssignRegister {
454  ($cs:literal, $value:expr_2021) => {
455    AssignRegister!($cs, $value, Vec::new())
456  };
457  ($cs:literal, $value:expr_2021, $args:expr_2021) => {
458    let value_ident = { $value };
459    if let Some(defn) = state::lookup_register_definition(&T_CS!($cs)) {
460      (*defn).set_value(value_ident, None, $args);
461    } else {
462      let message = s!("The control sequence {} is not a register", $cs);
463      Warn!("expected", "register", message);
464    }
465  };
466}
467
468#[macro_export]
469macro_rules! SetCounter {
470  ($ctr:expr_2021 => $value:expr_2021) => {
471    SetCounter!($ctr, $value)
472  };
473  ($ctr:expr_2021, $value:expr_2021) => {
474    state::assign_register(
475      &s!("\\c@{}", $ctr),
476      $value.into(),
477      Some(Scope::Global),
478      Vec::new(),
479    )?;
480    after_assignment();
481    def_macro(
482      T_CS!(s!("\\@{}@ID", $ctr)),
483      None,
484      Tokens::new(Explode!($value.value_of())),
485      Some(ExpandableOptions {
486        scope: Some(Scope::Global),
487        ..ExpandableOptions::default()
488      }),
489    )?;
490  };
491}