1use std::{borrow::Cow, rc::Rc};
2
3use once_cell::sync::Lazy;
4use regex::Regex;
5use rustc_hash::FxHashMap as HashMap;
6
7use crate::binding::content::{merge_font, merge_font_ref};
9use crate::{
10 BoxOps, Digested,
11 binding::{counter::dialect::step_counter, def::traits::IntoDigestedResult},
12 common::{
13 arena, arena::SymHashMap, error::*, font::Font, number::Number, numeric_ops::NumericOps,
14 },
15 definition::{
16 BeforeDigestClosure, ConditionalClosure, ConstructionClosure, Definition, DigestionClosure,
17 ExpansionBody, FontDirective, PrimitiveBody, ReplacementClosure, Reversion, SizingClosure,
18 argument::ArgWrap,
19 conditional::{Conditional, ConditionalOptions, ConditionalType},
20 constructor::{Constructor, ConstructorOptions},
21 expandable::{Expandable, ExpandableOptions},
22 math_primitive::{MathPrimitive, MathPrimitiveOptions},
23 primitive::{Primitive, PrimitiveOptions},
24 register::{
25 Register, RegisterGetterClosure, RegisterSetterClosure, RegisterType, RegisterValue,
26 },
27 },
28 document::Document,
29 gullet, mouth,
30 parameter::{Parameter, Parameters},
31 pin,
32 state::*,
33 stomach::*,
34 tbox::Tbox,
35 token::*,
36 tokens::{TeXString, Tokens},
37 whatsit::Whatsit,
38};
39
40const MATH_CONSTRUCTOR_ATTRIBUTES: &[&str] = &[
41 "name",
42 "meaning",
43 "omcd",
44 "decl_id",
45 "mathstyle",
46 "lpadding",
47 "rpadding",
48];
49
50pub static CONDITIONAL_CS_RE: Lazy<Regex> =
52 Lazy::new(|| Regex::new(r"^\\(?:if(.*)|unless)$").unwrap());
53pub static LEADING_PROTOCOL_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\w+:").unwrap());
55pub static TRAILING_SLASH_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"/$").unwrap());
57pub static SPACES_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\s+").unwrap());
59pub static DIRTY_ID_IDIOM_RE: Lazy<Regex> =
61 Lazy::new(|| Regex::new(r"\$\{\}\^\{(?P<label>[^\}]*)\}\$").unwrap());
62pub static NON_ID_CHARSET_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[^\w_\-.]+").unwrap());
64pub static TILDE_NOISE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\\~\{\}").unwrap());
66pub static HAS_ARG_OR_CS: Lazy<Regex> = Lazy::new(|| Regex::new(r"#\d|\\.").unwrap());
68pub static ARG_HOLE: Lazy<Regex> = Lazy::new(|| Regex::new(r"#(\d)").unwrap());
70
71pub fn is_defined(name: &str) -> bool {
73 let cs = T_CS!(name);
74 is_defined_token(&cs)
75}
76
77pub fn is_defined_token(cs: &Token) -> bool {
79 match lookup_meaning(cs) {
80 Some(store) => match store {
81 Stored::Token(_) => true,
82 Stored::Expandable(ref m) => m.get_cs_name() != "\\relax",
83 Stored::Primitive(ref m) => m.get_cs_name() != "\\relax",
84 Stored::Constructor(ref m) => m.get_cs_name() != "\\relax",
85 Stored::Register(ref m) => m.get_cs_name() != "\\relax",
86 Stored::Conditional(ref m) => m.get_cs_name() != "\\relax",
87 Stored::MathPrimitive(ref m) => m.get_cs_name() != "\\relax",
88 _ => true, },
90 _ => false,
91 }
92}
93
94pub fn is_definable(token: &Token) -> bool {
96 if !token.get_catcode().is_active_or_cs() {
106 return true;
107 }
108 let meaning = lookup_meaning(token);
109 token.with_str(|name| name != "\\relax" && !name.starts_with("\\end"))
110 && (meaning.is_none()
111 || (meaning == lookup_meaning(&TOKEN_RELAX))
112 || lookup_bool("2.09_COMPATIBILITY"))
113}
114
115pub fn def_conditional(
130 cs: Token,
131 paramlist: Option<Parameters>,
132 test: Option<ConditionalClosure>,
133 options: ConditionalOptions,
134) -> Result<()> {
135 let locked_key_opt = if let Some(true) = options.locked {
136 Some(arena::with(cs.get_sym(), |cs_name| s!("{cs_name}:locked")))
137 } else {
138 None
139 };
140 if cs.with_str(|cs_name| matches!(cs_name, "\\fi" | "\\else" | "\\or" | "\\unless")) {
141 install_definition(
142 Conditional {
143 cs,
144 paramlist,
145 test,
146 conditional_type: cs.with_str(|cs_name| ConditionalType::from(cs_name)),
147 skipper: options.skipper,
148 },
149 options.scope,
150 )
151 } else {
152 let (name_opt, warn_misnamed) = cs.with_str(|custom| {
159 if let Some(captures) = CONDITIONAL_CS_RE.captures(custom) {
161 let name = captures.get(1).map_or("", |m| m.as_str()).to_string();
162 return (Some(name), false);
163 }
164 let bytes = custom.as_bytes();
167 if bytes.len() > 3 && bytes[0] == b'\\' {
168 let rest = std::str::from_utf8(&bytes[3..]).ok().map(str::to_string);
169 (rest, true)
170 } else {
171 (None, false)
172 }
173 });
174 if warn_misnamed {
175 let message = s!(
176 "The conditional {} is being defined but doesn't start with \\if",
177 cs
178 );
179 Warn!("misdefined", cs, message);
180 }
181 if let Some(name) = name_opt {
182 if !name.is_empty() && name != "case" && test.is_none() {
183 def_macro(
191 T_CS!(s!("\\{}true", name)),
192 None,
193 Tokens!(T_CS!("\\let"), cs, T_CS!("\\iftrue")),
194 None,
195 )?;
196 def_macro(
197 T_CS!(s!("\\{}false", name)),
198 None,
199 Tokens!(T_CS!("\\let"), cs, T_CS!("\\iffalse")),
200 None,
201 )?;
202 let_i(&cs, &T_CS!("\\iffalse"), None);
203 } else {
204 install_definition(
206 Conditional {
207 cs,
208 paramlist,
209 test,
210 conditional_type: ConditionalType::If,
211 skipper: options.skipper,
212 },
213 options.scope,
214 );
215 }
216 } else {
217 let message = s!(
218 "The conditional {} is being defined but doesn't start with \\if",
219 cs
220 );
221 Error!("misdefined", cs, message);
222 }
223 }
224
225 if let Some(locked_key) = locked_key_opt {
226 assign_value(&locked_key, true, Some(Scope::Global));
227 }
228 Ok(())
229}
230
231pub fn def_macro<T: Into<Option<ExpansionBody>>>(
237 cs: Token,
238 paramlist: Option<Parameters>,
239 expansion: T,
240 options_opt: Option<ExpandableOptions>,
241) -> Result<()> {
242 let expansion_opt: Option<ExpansionBody> = expansion.into();
243 let mut options = options_opt.unwrap_or_default();
246 let scope = options.scope.take();
247 if options.mathactive && cs.with_str(|s| s.len()) == 1 {
248 assign_mathcode(
249 cs.with_str(|cstr| cstr.chars().next().unwrap()),
250 0x8000u16,
251 scope,
252 );
253 }
254 let locked_key_opt = if options.locked {
255 Some(format!("{cs}:locked"))
256 } else {
257 None
258 };
259 let defcs = if options.robust {
260 def_robust_cs(cs, options.locked, options.scope)?
261 } else {
262 cs
263 };
264 install_definition(
265 Expandable::new(defcs, paramlist, expansion_opt, Some(options))?,
266 scope,
267 );
268 if let Some(locked_key) = locked_key_opt {
269 assign_value(&locked_key, true, Some(Scope::Global));
270 }
271 Ok(())
272}
273
274#[derive(Default)]
276pub struct RegisterOptions {
277 pub getter: Option<RegisterGetterClosure>,
279 pub setter: Option<RegisterSetterClosure>,
281 pub readonly: bool,
283 pub address: Option<String>,
285 pub allocate: Option<String>,
287}
288
289pub fn def_register<T: Into<RegisterValue>>(
297 cs: Token,
298 parameters: Option<Parameters>,
299 value: T,
300 options: Option<RegisterOptions>,
301) -> Result<()> {
302 let mut options: RegisterOptions = options.unwrap_or_default();
303 let value: RegisterValue = value.into();
304 let has_address_option = options.address.is_some();
305 let mut address = match options.address.take() {
306 Some(v) => v,
307 None => match options.allocate {
308 Some(v) => allocate_register(&v, &cs.to_string())?.unwrap_or_default(),
309 None => String::new(),
310 },
311 };
312 if address.is_empty() {
314 address = cs.to_string();
315 }
316 if !has_address_option || !has_value(&address) {
318 assign_value(&address, value.clone(), Some(Scope::Global));
319 }
320
321 let register_type: RegisterType = (&value).into();
322 install_definition(
323 Register {
324 cs,
325 address,
326 parameters,
327 register_type,
328 readonly: options.readonly,
329 getter: options.getter,
330 setter: options.setter,
331 default: Some(value),
332 value: None,
333 locator: gullet::get_locator(),
334 ..Register::default()
335 },
336 Some(Scope::Global),
337 );
338 Ok(())
339}
340
341pub fn def_primitive(
350 cs: Token,
351 paramlist: Option<Parameters>,
352 compiled_replacement: Option<PrimitiveBody>,
353 options: PrimitiveOptions,
354) -> Result<()> {
355 let options_locked = options.locked;
356 let scope = options.scope;
357 let mut before_digest_env: Vec<BeforeDigestClosure> = Vec::new();
358 let cs_name = cs.with_cs_name(ToString::to_string);
359
360 let mut needs_enter_horizontal = options.enter_horizontal;
362 let mode = if options.mode.as_deref() == Some("text") {
363 needs_enter_horizontal = true;
364 Some("restricted_horizontal".to_string())
365 } else {
366 options.mode
367 };
368
369 if options.require_math {
370 let cs_name_cloned = cs_name.clone();
371 let require_math_closure = before_digest_simple!({ requireMath!(cs_name_cloned) });
372 before_digest_env.push(require_math_closure);
373 }
374
375 if options.forbid_math {
376 let cs_name_cloned = cs_name.clone();
377 let forbid_math_closure = before_digest_simple!({ forbidMath!(cs_name_cloned) });
378 before_digest_env.push(forbid_math_closure);
379 }
380 if needs_enter_horizontal {
381 before_digest_env.push(before_digest_simple!({
382 enter_horizontal();
383 }));
384 }
385 if options.leave_horizontal {
386 before_digest_env.push(before_digest_simple!({
387 leave_horizontal()?;
388 }));
389 }
390 if let Some(ref mode) = mode {
391 let mode_clone = mode.clone();
392 let begin_mode_closure = before_digest_simple!({
393 begin_mode(&mode_clone)?;
394 });
395 before_digest_env.push(begin_mode_closure);
396 } else if options.bounded {
397 let bgroup_closure = before_digest_simple!({
398 bgroup();
399 });
400 before_digest_env.push(bgroup_closure);
401 }
402 match options.font {
403 Some(FontDirective::Asset(chosen_font)) => {
404 let merge_font_closure = before_digest_simple!({
407 merge_font_ref(&chosen_font);
408 });
409 before_digest_env.push(merge_font_closure);
410 },
411 Some(FontDirective::Closure(font_closure)) => {
412 let execute_font_closure = before_digest_simple!({
413 merge_font(font_closure(None)?);
414 });
415 before_digest_env.push(execute_font_closure);
416 },
417 None => {},
418 }
419 before_digest_env.extend(options.before_digest);
420
421 let mut after_digest_env: Vec<DigestionClosure> = options.after_digest;
422 if let Some(ref mode_str) = mode {
423 let mode_clone = mode_str.clone();
424 let end_mode_closure: DigestionClosure = after_digest_simple!(_whatsit, {
425 end_mode(&mode_clone)?;
426 });
427 after_digest_env.push(end_mode_closure);
428 } else if options.bounded {
429 let egroup_closure: DigestionClosure = after_digest_simple!(_whatsit, {
430 egroup()?;
431 });
432 after_digest_env.push(egroup_closure);
433 }
434 let defcs = if options.robust {
436 def_robust_cs(cs, options.locked, scope)?
437 } else {
438 cs
439 };
440
441 install_definition(
442 Primitive {
443 cs: defcs,
444 paramlist,
445 replacement: compiled_replacement,
446 before_digest: before_digest_env,
447 after_digest: after_digest_env,
448 alias: options.alias,
449 nargs: options.nargs,
450 is_prefix: options.is_prefix,
451 reversion: options.reversion,
452 font_id: options.font_id,
453 },
454 scope,
455 );
456 if options_locked {
457 assign_value(&s!("{}:locked", cs_name), true, Some(Scope::Global));
458 }
459 Ok(())
460}
461
462pub fn def_math_dual(
464 cs: Token,
465 paramlist: Option<Parameters>,
466 presentation: String,
467 options: MathPrimitiveOptions,
468) -> Result<()> {
469 let (cont_cs_str, pres_cs_str) =
470 cs.with_str(|csname| (s!("{csname}@content"), s!("{csname}@presentation")));
471 let cont_cs = T_CS!(cont_cs_str);
472 let pres_cs = T_CS!(pres_cs_str);
473 let defcs = if options.robust {
474 def_robust_cs(cs, options.locked, options.scope)?
475 } else {
476 cs
477 };
478 let presentation_toks = mouth::tokenize_internal(TeXString::assembled(presentation.clone()));
479
480 let captured_role = options.role.clone();
482 let captured_revert_as = options.revert_as.clone();
483 let captured_cont_cs = cont_cs;
484 let captured_pres_cs = pres_cs;
485 let captured_pres = presentation.clone();
486 install_definition(
487 Expandable::new(
488 defcs,
489 paramlist.clone(),
490 Some(ExpansionBody::Closure(Rc::new(move |args| {
491 let args_opt_tks = args
492 .into_iter()
493 .map(|arg| arg.into())
494 .collect::<Vec<Option<Tokens>>>();
495 let (cargs, pargs) = dualize_arglist(&captured_pres, args_opt_tks)?;
496
497 let mut dtks = vec![T_CS!("\\lx@dual")];
498 if captured_role.is_some() || captured_revert_as.is_some() {
500 dtks.push(T_OTHER!("["));
501 if let Some(ref role) = captured_role {
502 dtks.extend(vec![T_OTHER!("role"), T_OTHER!("="), T_OTHER!(role)]);
503 if let Some(ref _revert_as) = captured_revert_as {
504 dtks.push(T_OTHER!(","));
505 }
506 }
507 if let Some(ref revert_as) = captured_revert_as {
508 dtks.extend(vec![
509 T_OTHER!("revert_as"),
510 T_OTHER!("="),
511 T_OTHER!(revert_as),
512 ]);
513 }
514 dtks.push(T_OTHER!("]"));
515 }
516 dtks.push(T_BEGIN!());
520 dtks.push(captured_cont_cs);
521 for carg in cargs.into_iter().flatten() {
522 dtks.push(T_BEGIN!());
523 dtks.extend(carg.unlist());
524 dtks.push(T_END!());
525 }
526 dtks.push(T_END!());
527 dtks.push(T_BEGIN!());
528 dtks.push(captured_pres_cs);
529 for parg in pargs.into_iter().flatten() {
530 dtks.push(T_BEGIN!());
531 dtks.extend(parg.unlist());
532 dtks.push(T_END!());
533 }
534 dtks.push(T_END!());
535
536 Ok(Tokens::new(dtks))
537 }))),
538 Some(ExpandableOptions {
539 protected: options.protected,
540 ..ExpandableOptions::default()
541 }),
542 )?,
543 options.scope,
544 );
545
546 install_definition(
548 Expandable::new(
549 pres_cs,
550 paramlist.clone(),
551 Some(ExpansionBody::Tokens(presentation_toks)),
552 Some(ExpandableOptions {
553 protected: options.protected,
554 ..ExpandableOptions::default()
555 }),
556 )?,
557 options.scope,
558 );
559
560 let nargs = paramlist
563 .as_ref()
564 .map(|pl| pl.get_parameters().len())
565 .unwrap_or(0);
566 let content_closure: ReplacementClosure = if nargs == 0 {
567 Rc::new(|document, _args, props| {
568 let mut attrs = HashMap::default();
569 for key in ["role", "scriptpos", "stretchy"] {
570 if let Some(v) = props.get(key) {
571 attrs.insert(key.to_owned(), v.to_string());
572 }
573 }
574 for key in MATH_CONSTRUCTOR_ATTRIBUTES {
575 if let Some(v) = props.get(key) {
576 attrs.insert(key.to_string(), v.to_string());
577 }
578 }
579 document.insert_element("ltx:XMTok", Vec::new(), Some(attrs))?;
580 Ok(())
581 })
582 } else {
583 Rc::new(|document, args, props| {
584 let mut app_attrs = HashMap::default();
585 for key in ["role", "scriptpos"] {
586 if let Some(v) = props.get(key) {
587 app_attrs.insert(key.to_owned(), v.to_string());
588 }
589 }
590 document.open_element("ltx:XMApp", Some(app_attrs), None)?;
591 let mut op_attrs = HashMap::default();
592 if let Some(v) = props.get("operator_stretchy") {
593 op_attrs.insert("stretchy".to_owned(), v.to_string());
594 }
595 if let Some(v) = props.get("operator_role") {
596 op_attrs.insert("role".to_owned(), v.to_string());
597 }
598 if let Some(v) = props.get("operator_scriptpos") {
599 op_attrs.insert("scriptpos".to_owned(), v.to_string());
600 }
601 for key in MATH_CONSTRUCTOR_ATTRIBUTES {
602 if let Some(v) = props.get(key) {
603 op_attrs.insert(key.to_string(), v.to_string());
604 }
605 }
606 document.insert_element("ltx:XMTok", Vec::new(), Some(op_attrs))?;
608 for arg in args.iter().flatten() {
611 document.absorb(arg, None)?;
612 }
613 document.close_element("ltx:XMApp")?;
614 Ok(())
615 })
616 };
617 let mut content_constructor = Constructor {
619 cs: cont_cs,
620 paramlist,
621 replacement: Some(content_closure),
622 ..Constructor::default()
623 };
624 let scope = options.scope;
625 transfer_common_constructor_options(&cs, &presentation, options, &mut content_constructor);
626 install_definition(content_constructor, scope);
627 Ok(())
628}
629
630pub fn def_math_primitive(
633 cs: Token,
634 _paramlist: Option<Parameters>,
635 presentation: String,
636 options: MathPrimitiveOptions,
637) {
638 let scope = options.scope;
639 let reqfont_opt = options.font.clone();
640 let shared_options = Rc::new(options.clone());
645 let dynamic_mathstyle = shared_options.dynamic_mathstyle;
646 let dynamic_scriptpos = shared_options.dynamic_scriptpos;
647
648 install_definition(
649 MathPrimitive {
650 cs,
651 paramlist: None, replacement: Some(Rc::new(move |_args| {
653 let locator = gullet::get_locator();
654 let cur_mode = lookup_string_from_sym(pin!("MODE"));
659 let mode_static: &'static str = match cur_mode.as_str() {
660 "math" => "math",
661 "display_math" => "display_math",
662 "inline_math" => "inline_math",
663 "vertical" => "vertical",
664 "internal_vertical" => "internal_vertical",
665 "horizontal" => "horizontal",
666 "restricted_horizontal" => "restricted_horizontal",
667 _ => "math",
668 };
669 let state_font = lookup_font().unwrap();
670 let mathstyle_override: Option<&'static str> = if dynamic_mathstyle {
672 let is_display = state_font
673 .get_mathstyle()
674 .is_some_and(|s| s.as_ref() == "display");
675 Some(if is_display { "display" } else { "text" })
676 } else {
677 None
678 };
679 let scriptpos_override: Option<&'static str> = if dynamic_scriptpos {
681 let is_display = state_font
682 .get_mathstyle()
683 .is_some_and(|s| s.as_ref() == "display");
684 Some(if is_display { "mid" } else { "post" })
685 } else {
686 None
687 };
688 let font = Rc::new(if let Some(ref reqfont) = reqfont_opt {
689 let this_reqfont = reqfont.get_font(None)?;
690 state_font
691 .merge_ref(&this_reqfont)
692 .specialize(&presentation)
693 } else {
694 state_font.specialize(&presentation)
695 });
696
697 Ok(vec![Digested::from(Tbox {
698 text: arena::pin(&presentation),
699 tokens: Tokens!(cs),
700 font,
701 properties: shared_options.to_hash_stored_with_overrides(
702 Some(mode_static),
703 mathstyle_override,
704 scriptpos_override,
705 ),
706 locator: Some(locator),
707 })])
708 })),
709 options,
710 ..MathPrimitive::default()
711 },
712 scope,
713 );
714}
715
716pub fn def_math_constructor(
719 cs: Token,
720 paramlist: Option<Parameters>,
721 presentation: String,
722 mut options: MathPrimitiveOptions,
723) -> Result<()> {
724 let nargs = paramlist
726 .as_ref()
727 .map(|pl| pl.get_parameters().len())
728 .unwrap_or(0);
729 let defcs = if options.robust {
735 def_robust_cs(cs, options.locked, options.scope)?
736 } else {
737 cs
738 };
739 if options.reversion.is_none() && nargs == 0 && options.alias.is_none() {
740 if options.revert_as.is_none()
741 || options.revert_as == Some(Cow::Borrowed("content"))
742 || options.revert_as == Some(Cow::Borrowed("context"))
743 {
744 options.reversion = Some(Reversion::Tokens(Tokens!(cs)));
746 } else {
747 options.reversion = Some(Reversion::Tokens(Tokens::new(Explode!(presentation))));
751 }
752 }
753 let presentation_for_sizer = presentation.clone();
754 let presentation_for_replacement = presentation.clone();
755 let is_mathstyle = options.mathstyle.is_some();
756 let mathstyle_for_font = options.mathstyle.clone();
757 let presentation_for_font = presentation.clone();
758 options.font = Some(FontDirective::Closure(if is_mathstyle {
759 Rc::new(move |_whatsit| {
760 Ok(
761 lookup_font()
762 .unwrap()
763 .merge(Font {
764 mathstyle: mathstyle_for_font
765 .as_ref()
766 .map(|ms| Cow::Owned(ms.to_owned())),
767 ..Font::default()
768 })
769 .specialize(&presentation_for_font),
770 )
771 })
772 } else {
773 Rc::new(move |_whatsit| Ok(lookup_font().unwrap().specialize(&presentation_for_font)))
774 }));
775 let compiled_replacement: Option<ReplacementClosure> = Some(if nargs == 0 {
776 let presentation_is_trivial = !presentation_for_replacement
793 .chars()
794 .any(|c| c == '(' || c == ')' || c == '\\');
795 Rc::new(
796 move |document: &mut Document, _, props: &SymHashMap<Stored>| {
797 let font_opt = match props.get("font") {
798 Some(Stored::Font(f)) => Some(Cow::Borrowed(&**f)),
799 Some(Stored::FontDirective(FontDirective::Closure(code))) => {
800 Some(Cow::Owned(code(None)?))
801 },
802 Some(Stored::FontDirective(FontDirective::Asset(font))) => Some(Cow::Borrowed(&**font)),
803 _ => None,
804 };
805 let is_math = matches!(props.get("isMath"), Some(Stored::Bool(true)));
814 if !is_math && presentation_is_trivial {
815 document.absorb_string(&presentation_for_replacement, props)?;
817 return Ok(());
818 }
819 let mut attrs = HashMap::default();
820 for key in ["role", "scriptpos", "stretchy"] {
821 if let Some(v) = props.get(key) {
822 attrs.insert(key.to_owned(), v.to_string());
823 }
824 }
825 for key in MATH_CONSTRUCTOR_ATTRIBUTES {
826 if let Some(v) = props.get(key) {
827 attrs.insert(key.to_string(), v.to_string());
828 }
829 }
830 if let Some(font) = font_opt {
831 document.open_element("ltx:XMTok", Some(attrs), Some(&font))?;
832 } else {
833 document.open_element("ltx:XMTok", Some(attrs), None)?;
834 }
835 document.absorb_string(&presentation_for_replacement, props)?;
836 document.close_element("ltx:XMTok")?;
837 Ok(())
838 },
839 )
840 } else {
841 Rc::new(
848 move |document: &mut Document, args: &Vec<Option<Digested>>, props: &SymHashMap<Stored>| {
849 let mut attrs = HashMap::default();
850 for key in ["role", "scriptpos", "stretchy"] {
851 if let Some(v) = props.get(key) {
852 attrs.insert(key.to_owned(), v.to_string());
853 }
854 }
855 let font_opt = match props.get("font") {
856 Some(Stored::Font(f)) => Some(Cow::Borrowed(&**f)),
857 Some(Stored::FontDirective(FontDirective::Closure(code))) => {
858 Some(Cow::Owned(code(None)?))
859 },
860 Some(Stored::FontDirective(FontDirective::Asset(font))) => Some(Cow::Borrowed(&**font)),
861 _ => None,
862 };
863 if let Some(ref font) = font_opt {
864 document.open_element("ltx:XMApp", Some(attrs), Some(font))?;
865 } else {
866 document.open_element("ltx:XMApp", Some(attrs), None)?;
867 }
868 let mut op_attrs = HashMap::default();
870 if let Some(role) = props.get("operator_role") {
871 op_attrs.insert(String::from("role"), role.to_string());
872 }
873 if let Some(stretchy) = props.get("operator_stretchy") {
874 op_attrs.insert(String::from("stretchy"), stretchy.to_string());
875 }
876 if let Some(scriptpos) = props.get("operator_scriptpos") {
877 op_attrs.insert(String::from("scriptpos"), scriptpos.to_string());
878 }
879 for key in MATH_CONSTRUCTOR_ATTRIBUTES {
880 if let Some(v) = props.get(key) {
881 op_attrs.insert(key.to_string(), v.to_string());
882 }
883 }
884 if let Some(font) = font_opt {
885 document.open_element("ltx:XMTok", Some(op_attrs), Some(&font))?;
886 } else {
887 document.open_element("ltx:XMTok", Some(op_attrs), None)?;
888 }
889 document.absorb_string(&presentation_for_replacement, props)?;
890 document.close_element("ltx:XMTok")?;
891 for arg in args {
893 document.open_element("ltx:XMArg", None, None)?;
894 if let Some(arg_v) = arg {
895 document.absorb(arg_v, None)?;
896 }
897 document.close_element("ltx:XMArg")?;
898 }
899
900 document.close_element("ltx:XMApp")?;
901 Ok(())
902 },
903 )
904 });
905 let sizer: Option<SizingClosure> = Some(Rc::new(move |_| {
906 Ok(Font::math_default().compute_string_size(&presentation_for_sizer, SymHashMap::default()))
907 }));
908
909 let mut constructor = Constructor {
911 cs: defcs,
912 paramlist,
913 replacement: compiled_replacement,
914 nargs: Some(nargs),
915 sizer,
916 ..Constructor::default()
920 };
921 let scope = options.scope;
922 transfer_common_constructor_options(&cs, &presentation, options, &mut constructor);
923 install_definition(constructor, scope);
924 Ok(())
925}
926
927fn infer_sizer(
928 sizer: Option<&SizingClosure>,
929 _reversion: Option<&Reversion>,
930) -> Option<SizingClosure> {
931 sizer.map(Rc::clone)
936}
937
938fn def_robust_cs(cs: Token, locked: bool, scope: Option<Scope>) -> Result<Token> {
939 let cs_str = cs.with_str(|cstr| format!("{cstr} "));
940 let defcs = T_CS!(cs_str);
941 let return_cs = defcs;
942 let expansion = Tokens!(T_CS!("\\protect"), defcs);
943 let options = ExpandableOptions {
944 locked,
945 robust: true,
946 ..ExpandableOptions::default()
947 };
948 install_definition(
950 Expandable::new(cs, None, expansion.into(), Some(options))?,
951 scope,
952 );
953 Ok(return_cs)
954}
955
956pub fn def_constructor(
965 cs: Token,
966 paramlist: Option<Parameters>,
967 compiled_replacement: Option<ReplacementClosure>,
968 mut options: ConstructorOptions,
969) {
970 let scope = options.scope;
973 let cs = if options.robust {
974 if options.alias.is_none() {
1000 options.alias = Some(cs.with_cs_name(ToString::to_string));
1001 }
1002 def_robust_cs(cs, options.locked, scope).expect("def_robust_cs for constructor failed")
1003 } else {
1004 cs
1005 };
1006 let cs_name = cs.with_cs_name(ToString::to_string);
1007 let locked_key_opt = if options.locked {
1008 Some(s!("{cs_name}:locked"))
1009 } else {
1010 None
1011 };
1012
1013 let mut before_digest_closures: Vec<BeforeDigestClosure> = Vec::new();
1014
1015 let mut needs_enter_horizontal = options.enter_horizontal;
1017 let mode = if options.mode.as_deref() == Some("text") {
1018 needs_enter_horizontal = true;
1019 Some("restricted_horizontal".to_string())
1020 } else {
1021 options.mode
1022 };
1023
1024 if options.require_math {
1025 let cs_name_cloned = cs_name.clone();
1026 let require_math_closure = before_digest_simple!({ requireMath!(cs_name_cloned) });
1027 before_digest_closures.push(require_math_closure);
1028 }
1029 if options.forbid_math {
1030 let cs_name_cloned = cs_name;
1031 let forbid_math_closure = before_digest_simple!({ forbidMath!(cs_name_cloned) });
1032 before_digest_closures.push(forbid_math_closure);
1033 }
1034 if needs_enter_horizontal {
1035 before_digest_closures.push(before_digest_simple!({
1036 enter_horizontal();
1037 }));
1038 }
1039 if options.leave_horizontal {
1040 before_digest_closures.push(before_digest_simple!({
1041 leave_horizontal()?;
1042 }));
1043 }
1044 if let Some(ref mode) = mode {
1045 let mode_clone = mode.clone();
1046 let begin_mode_closure = before_digest_simple!({
1047 begin_mode(&mode_clone)?;
1048 });
1049 before_digest_closures.push(begin_mode_closure);
1050 } else if options.bounded {
1051 let bgroup_closure = before_digest_simple!({
1052 bgroup();
1053 });
1054 before_digest_closures.push(bgroup_closure);
1055 }
1056 match options.font {
1061 Some(FontDirective::Asset(chosen_font)) => {
1062 let merge_font_closure = before_digest_simple!({
1063 merge_font((*chosen_font).clone());
1064 });
1065 before_digest_closures.push(merge_font_closure);
1066 },
1067 Some(FontDirective::Closure(font_closure)) => {
1068 let execute_font_closure = before_digest_simple!({
1069 merge_font(font_closure(None)?);
1070 });
1071 before_digest_closures.push(execute_font_closure);
1072 },
1073 None => {},
1074 };
1075 before_digest_closures.extend(options.before_digest);
1076
1077 let mut after_digest_closures: Vec<DigestionClosure> = options.after_digest;
1078 if let Some(ref mode_str) = mode {
1079 let mode_clone = mode_str.clone();
1080 let end_mode_closure: DigestionClosure = after_digest_simple!(_whatsit, {
1081 end_mode(&mode_clone)?;
1082 });
1083 after_digest_closures.push(end_mode_closure);
1084 } else if options.bounded {
1085 let egroup_closure: DigestionClosure = after_digest_simple!(_whatsit, {
1086 egroup()?;
1087 });
1088 after_digest_closures.push(egroup_closure);
1089 }
1090
1091 let constructor = Constructor {
1092 cs,
1093 paramlist,
1094 replacement: compiled_replacement,
1095 before_digest: before_digest_closures,
1096 after_digest: after_digest_closures,
1097 before_construct: options.before_construct,
1098 after_construct: options.after_construct,
1099 nargs: options.nargs,
1100 alias: options.alias,
1101 sizer: infer_sizer(options.sizer.as_ref(), options.reversion.as_ref()),
1102 reversion: options.reversion,
1103 capture_body: options.capture_body,
1104 properties: options.properties,
1105 ..Constructor::default()
1108 };
1109 install_definition(constructor, scope);
1110
1111 if let Some(locked_key) = locked_key_opt {
1112 assign_value(&locked_key, true, Some(Scope::Global));
1113 }
1114}
1115
1116pub fn def_environment(
1129 name: String,
1130 paramlist: Option<Parameters>,
1131 compiled_replacement: Option<ReplacementClosure>,
1132 options: ConstructorOptions,
1133) {
1134 let begin_name = s!("\\begin{{{name}}}");
1136 let end_name = s!("\\end{{{name}}}");
1137 let mut before_digest_env: Vec<BeforeDigestClosure> = Vec::new();
1138
1139 let mode = match options.mode.as_deref() {
1143 None | Some("text") => Some("restricted_horizontal".to_string()),
1144 _ => options.mode,
1145 };
1146
1147 if options.require_math {
1148 let require_name = begin_name.clone();
1149 let require_math_closure = before_digest_simple!({ requireMath!(require_name) });
1150 before_digest_env.push(require_math_closure);
1151 }
1152 if options.forbid_math {
1153 let forbid_name = begin_name.clone();
1154 let forbid_math_closure = before_digest_simple!({ forbidMath!(forbid_name) });
1155 before_digest_env.push(forbid_math_closure);
1156 }
1157 let bgroup_closure = before_digest_simple!({
1158 bgroup();
1159 });
1160 before_digest_env.push(bgroup_closure);
1161 let atbegin_key = s!("@environment@{name}@atbegin");
1162 let atbegin_hook_closure = before_digest_simple!({
1163 if let Some(b) = lookup_tokens(&atbegin_key) {
1164 vec![digest(b.unlist())?]
1165 } else {
1166 Vec::new()
1167 }
1168 });
1169
1170 before_digest_env.push(atbegin_hook_closure);
1171 if options.enter_horizontal {
1172 before_digest_env.push(before_digest_simple!({
1173 enter_horizontal();
1174 }));
1175 }
1176 if options.leave_horizontal {
1177 before_digest_env.push(before_digest_simple!({
1178 leave_horizontal()?;
1179 }));
1180 }
1181 if let Some(ref mode) = mode {
1183 let bmode = mode.clone();
1184 let mode_closure = before_digest_simple!({
1185 begin_mode_opt(&bmode, true)?;
1186 });
1187 before_digest_env.push(mode_closure);
1188 }
1189
1190 let env_name = name.clone();
1191 let current_environment_closure = before_digest_simple!({
1192 assign_value_sym(crate::pin!("current_environment"), env_name.clone(), None);
1193 let body = T_LETTER!(env_name.clone());
1194 def_macro(
1195 T_CS!("\\@currenvir"),
1196 None,
1197 Some(ExpansionBody::Tokens(Tokens!(body))),
1198 None,
1199 )?;
1200 });
1201 before_digest_env.push(current_environment_closure);
1202
1203 match options.font {
1204 Some(FontDirective::Asset(chosen_font)) => {
1205 let merge_font_closure = before_digest_simple!({
1208 merge_font_ref(&chosen_font);
1209 });
1210 before_digest_env.push(merge_font_closure);
1211 },
1212 Some(FontDirective::Closure(font_closure)) => {
1213 let execute_font_closure = before_digest_simple!({
1214 merge_font(font_closure(None)?);
1215 });
1216 before_digest_env.push(execute_font_closure);
1217 },
1218 None => {},
1219 }
1220 let bare_before_digest = options.before_digest.clone();
1229 before_digest_env.extend(options.before_digest);
1230
1231 let bare_after_digest_begin = options.after_digest_begin.clone();
1234 let bare_after_digest_body = options.after_digest_body.clone();
1235 let bare_before_construct = options.before_construct.clone();
1236 let bare_after_construct = options.after_construct.clone();
1237 let bare_sizer = options.sizer.clone();
1238 let bare_reversion = options.reversion.clone();
1239 let bare_alias = options.alias.clone();
1240
1241 let push_frame_closure = Rc::new(|_document: &mut Document, _whatsit: &Whatsit| {
1242 push_frame();
1243 Ok(())
1244 });
1245 let mut before_construct_with_frame: Vec<ConstructionClosure> = vec![push_frame_closure];
1246 before_construct_with_frame.extend(options.before_construct);
1247
1248 let mut after_construct_with_frame: Vec<ConstructionClosure> = options.after_construct;
1249
1250 let pop_frame_closure = Rc::new(|_document: &mut Document, _whatsit: &Whatsit| {
1251 pop_frame()?;
1252 Ok(())
1253 });
1254 after_construct_with_frame.push(pop_frame_closure);
1255
1256 let paramlist_skips = match paramlist {
1260 Some(ref pl) if pl.get_num_args() > 0 => {
1261 let skip_spaces_param = Parameter {
1262 novalue: true,
1263 name: pin!("SkipSpaces"),
1264 spec: pin!("SkipSpaces"),
1265 reader: Rc::new(|_inner, _extra| {
1266 gullet::skip_spaces()?;
1267 Ok(ArgWrap::None)
1268 }),
1269 ..Parameter::default()
1270 };
1271 let mut params = vec![skip_spaces_param];
1272 params.extend(pl.get_parameters().into_iter().cloned());
1273 Some(Parameters::new(params))
1274 },
1275 _ => paramlist.clone(),
1276 };
1277
1278 let begin_name_constructor = Rc::new(Constructor {
1279 cs: T_CS!(begin_name),
1280 paramlist: paramlist_skips,
1281 replacement: compiled_replacement.clone(),
1282 nargs: options.nargs,
1283 before_digest: before_digest_env,
1284 after_digest: options.after_digest_begin,
1285 after_digest_body: options.after_digest_body,
1286 before_construct: before_construct_with_frame,
1287 after_construct: after_construct_with_frame,
1289 capture_body: true,
1290 properties: options.properties.clone(),
1291 sizer: infer_sizer(options.sizer.as_ref(), options.reversion.as_ref()),
1295 reversion: options.reversion,
1296 alias: options.alias,
1297 });
1298 install_definition(begin_name_constructor, options.scope);
1299
1300 let mut after_digest_env = options.after_digest.clone();
1301 let name_clone = name.clone();
1302 let end_name_clone = end_name.clone();
1303 let unexpected_end_closure = after_digest_simple!(_whatsit, {
1304 let env = lookup_string_from_sym(crate::pin!("current_environment"));
1305 if env.is_empty() || name_clone != env {
1306 let message1 = s!("Can't close environment {};", name_clone);
1309 let message2 = s!(
1310 "Current are: {}",
1311 with_stacked_values_sym(crate::pin!("current_environment"), |vals| vals
1312 .iter()
1313 .map(|x| s!("{:?}", x))
1314 .collect::<Vec<String>>()
1315 .join(", "))
1316 );
1317 Error!("unexpected", end_name_clone, message1, message2);
1318 }
1319 Ok(Vec::new())
1320 });
1321 after_digest_env.push(unexpected_end_closure);
1322
1323 match mode {
1324 Some(ref emode) => {
1325 let emode = emode.clone();
1326 let emode_closure = Rc::new(move |_whatsit: &mut Whatsit| {
1327 end_mode(&emode)?;
1336 Ok(Vec::new())
1337 });
1338 after_digest_env.push(emode_closure);
1339 },
1340 None => {
1341 let egroup_closure = Rc::new(|_whatsit: &mut Whatsit| {
1342 egroup()?;
1343 Ok(Vec::new())
1344 });
1345 after_digest_env.push(egroup_closure);
1346 },
1347 };
1348
1349 let (mut before_digest_for_endenv, before_digest_end_clone) = {
1350 let cloned = options.before_digest_end.clone();
1351 (options.before_digest_end, cloned)
1352 };
1353 let atend_key = s!("@environment@{name}@atend");
1354 let atend_hook_closure = before_digest_simple!({
1355 if let Some(e) = lookup_tokens(&atend_key) {
1356 vec![digest(e.unlist())?]
1357 } else {
1358 Vec::new()
1359 }
1360 });
1361 before_digest_for_endenv.push(atend_hook_closure);
1362
1363 let end_envname_constructor = Rc::new(Constructor {
1364 cs: T_CS!(end_name),
1365 replacement: None,
1366 paramlist: None,
1367 before_digest: before_digest_for_endenv,
1368 after_digest: after_digest_env,
1369 ..Constructor::default() });
1371 install_definition(end_envname_constructor, options.scope);
1372
1373 let mut before_digest_bare: Vec<BeforeDigestClosure> = Vec::new();
1376 before_digest_bare.push(before_digest_simple!({
1377 bgroup();
1378 }));
1379 if options.enter_horizontal {
1380 before_digest_bare.push(before_digest_simple!({
1381 enter_horizontal();
1382 }));
1383 }
1384 if options.leave_horizontal {
1385 before_digest_bare.push(before_digest_simple!({
1386 leave_horizontal()?;
1387 }));
1388 }
1389 if let Some(ref bmode) = mode {
1390 let bmode = bmode.clone();
1391 before_digest_bare.push(before_digest_simple!({
1392 begin_mode_opt(&bmode, true)?;
1393 }));
1394 }
1395 before_digest_bare.extend(bare_before_digest);
1400 let push_frame_bare = Rc::new(|_document: &mut Document, _whatsit: &Whatsit| {
1401 push_frame();
1402 Ok(())
1403 });
1404 let pop_frame_bare = Rc::new(|_document: &mut Document, _whatsit: &Whatsit| {
1405 pop_frame()?;
1406 Ok(())
1407 });
1408 let mut before_construct_bare: Vec<ConstructionClosure> = vec![push_frame_bare];
1411 before_construct_bare.extend(bare_before_construct);
1412 let mut after_construct_bare: Vec<ConstructionClosure> = bare_after_construct;
1413 after_construct_bare.push(pop_frame_bare);
1414 let name_constructor = Rc::new(Constructor {
1415 cs: T_CS!(s!("\\{}", &name)),
1416 paramlist,
1417 replacement: compiled_replacement,
1418 nargs: options.nargs,
1419 capture_body: true,
1420 properties: options.properties.clone(),
1421 before_digest: before_digest_bare,
1422 after_digest: bare_after_digest_begin,
1423 after_digest_body: bare_after_digest_body,
1424 before_construct: before_construct_bare,
1425 after_construct: after_construct_bare,
1426 sizer: infer_sizer(bare_sizer.as_ref(), bare_reversion.as_ref()),
1427 reversion: bare_reversion,
1428 alias: bare_alias,
1429 });
1430 install_definition(name_constructor, options.scope);
1431 let end_name = s!("\\end{}", &name);
1432 let mut after_digest_end = options.after_digest;
1433 match mode {
1435 Some(ref emode) => {
1436 let emode = emode.clone();
1437 after_digest_end.push(Rc::new(move |_whatsit: &mut Whatsit| {
1438 end_mode(&emode)?;
1439 Ok(Vec::new())
1440 }));
1441 },
1442 None => {
1443 },
1446 };
1447
1448 let end_name_constructor = Constructor {
1450 cs: T_CS!(end_name),
1451 paramlist: None,
1452 replacement: None,
1453 before_digest: before_digest_end_clone,
1454 after_digest: after_digest_end,
1455 ..Constructor::default()
1456 };
1457 install_definition(Rc::new(end_name_constructor), options.scope);
1458
1459 if options.locked {
1460 assign_value(
1461 &s!("\\begin{{{}}}:locked", &name),
1462 true,
1463 Some(Scope::Global),
1464 );
1465 assign_value(&s!("\\end{{{}}}:locked", &name), true, Some(Scope::Global));
1466 assign_value(&s!("\\{}:locked", &name), true, Some(Scope::Global));
1467 assign_value(&s!("\\end{}:locked", &name), true, Some(Scope::Global));
1468 }
1469}
1470
1471pub fn get_xmarg_id() -> Result<Tokens> {
1477 step_counter("@lx@xmarg", true)?;
1482 def_macro(
1483 T_CS!("\\@@lx@xmarg@ID"),
1484 None,
1485 Tokens!(Explode!(
1486 lookup_register("\\c@@lx@xmarg", Vec::new())?
1487 .unwrap()
1488 .value_of()
1489 )),
1490 Some(ExpandableOptions {
1491 scope: Some(Scope::Global),
1492 ..ExpandableOptions::default()
1493 }),
1494 )?;
1495 gullet::do_expand(T_CS!("\\the@lx@xmarg@ID"))
1496}
1497
1498type ArgsUnpacked = Vec<Option<Tokens>>;
1499pub fn dualize_arglist(
1515 presentation: &str,
1516 args: Vec<Option<Tokens>>,
1517) -> Result<(ArgsUnpacked, ArgsUnpacked)> {
1518 let mut used = HashMap::default();
1519 for cap in ARG_HOLE.captures_iter(presentation) {
1520 let argi = cap.get(1).unwrap().as_str();
1522 let entry = used.entry(argi.parse::<usize>().expect(argi)).or_insert(0);
1523 *entry += 1;
1524 }
1525 let mut cargs = Vec::new();
1526 let mut pargs = Vec::new();
1527 for (index, arg_opt) in args.into_iter().enumerate() {
1528 match arg_opt {
1529 None => {
1530 pargs.push(None);
1531 cargs.push(None);
1532 },
1533 Some(arg) if arg.unlist_ref().is_empty() => {
1534 pargs.push(Some(arg.clone()));
1535 cargs.push(Some(arg));
1536 },
1537 Some(arg_toks) => {
1538 if used.get(&(1 + index)).unwrap_or(&0) > &0 {
1539 let id = get_xmarg_id()?;
1541 pargs.push(Some(Tokens!(
1542 T_CS!("\\lx@xmarg"),
1543 T_BEGIN!(),
1544 id.clone().unlist(),
1545 T_END!(),
1546 T_BEGIN!(),
1547 arg_toks.unlist(),
1548 T_END!()
1549 ))); cargs.push(Some(Tokens!(
1551 T_CS!("\\lx@xmref"),
1552 T_BEGIN!(),
1553 id.unlist(),
1554 T_END!()
1555 )));
1556 } else {
1557 let id = get_xmarg_id()?;
1559 cargs.push(Some(Tokens!(
1560 T_CS!("\\lx@xmarg"),
1561 T_BEGIN!(),
1562 id.clone().unlist(),
1563 T_END!(),
1564 T_BEGIN!(),
1565 arg_toks.unlist(),
1566 T_END!()
1567 )));
1568 pargs.push(Some(Tokens!(
1569 T_CS!("\\lx@xmref"),
1570 T_BEGIN!(),
1571 id.unlist(),
1572 T_END!()
1573 )));
1574 }
1575 },
1576 }
1577 }
1578 Ok((cargs, pargs))
1579}
1580
1581pub fn def_math(
1597 cs: Token,
1598 paramlist: Option<Parameters>,
1599 presentation: String,
1600 mut options: MathPrimitiveOptions,
1601) -> Result<()> {
1602 let nargs = match paramlist {
1606 Some(ref plist) => plist.get_num_args(),
1607 None => 0,
1608 };
1609 let csname = cs.with_str(ToString::to_string);
1610 let name_opt = {
1611 let name = match options.name {
1612 Some(ref name) => Cow::Owned(name.to_owned()),
1613 None => {
1614 let mut inferred_name = match options.alias {
1615 Some(ref alias) => Cow::Owned(alias.to_owned()),
1616 None => Cow::Borrowed(&csname),
1617 };
1618 if inferred_name.starts_with('\\') {
1619 inferred_name = Cow::Owned(inferred_name.replacen('\\', "", 1))
1620 }
1621 inferred_name
1622 },
1623 };
1624 let meaning_check = options
1625 .meaning
1626 .as_ref()
1627 .map_or_else(|| Cow::Owned(String::new()), Cow::Borrowed);
1628 if (*name == presentation) || (name.is_empty()) || *name == *meaning_check {
1629 None
1630 } else {
1631 Some(name.into_owned())
1632 }
1633 };
1634 options.name = name_opt;
1635 if nargs == 0 && options.role.is_none() {
1636 options.role = Some(String::from("UNKNOWN"))
1637 }
1638 if nargs > 0 && options.operator_role.is_none() {
1639 options.operator_role = Some(String::from("UNKNOWN"))
1640 }
1641 if options.hide_content_reversion {
1642 options.revert_as = Some(Cow::Borrowed("context"));
1643 }
1644
1645 let locked = options.locked;
1646 if csname.len() == 1 {
1651 let mut math_attr_hash: HashMap<String, String> = HashMap::default();
1652 transfer_opt_default!(name, options, math_attr_hash);
1653 transfer_opt_default!(meaning, options, math_attr_hash);
1654 transfer_opt_default!(omcd, options, math_attr_hash);
1655 transfer_opt_default!(decl_id, options, math_attr_hash);
1656 transfer_opt_default!(role, options, math_attr_hash);
1657 transfer_opt_default!(replace, options, math_attr_hash);
1658 transfer_opt_default!(mathstyle, options, math_attr_hash);
1659 transfer_opt_default!(stretchy, options, math_attr_hash);
1660 assign_value(
1661 &s!("math_token_attributes_{}", csname),
1662 math_attr_hash,
1663 Some(Scope::Global),
1664 );
1665 }
1666 else if HAS_ARG_OR_CS.is_match(&presentation) {
1670 def_math_dual(cs, paramlist, presentation, options)?;
1675 }
1676 else if nargs == 0 && !options.has_complex_option() {
1679 def_math_primitive(cs, paramlist, presentation, options);
1680 } else {
1681 def_math_constructor(cs, paramlist, presentation, options)?;
1682 }
1683 if locked {
1684 assign_value(&format!("{csname}:locked"), true, Some(Scope::Global));
1685 }
1686 Ok(())
1687}
1688
1689fn transfer_common_constructor_options(
1691 cs: &Token,
1692 presentation: &str,
1693 options: MathPrimitiveOptions,
1694 cons: &mut Constructor,
1695) {
1696 let cs_str = cs.with_str(ToString::to_string);
1697 let mut properties = options.to_hash_stored();
1698 cons.alias = Some(options.alias.unwrap_or_else(|| cs_str.clone()));
1699 if let Some(sizer) = infer_sizer(options.sizer.as_ref(), options.reversion.as_ref()) {
1700 cons.sizer = Some(sizer);
1701 }
1702 if let Some(reversion) = options.reversion {
1703 cons.reversion = Some(reversion);
1704 }
1705 let mut before_digest_closures: Vec<BeforeDigestClosure> = Vec::new();
1715 if options.require_math {
1716 before_digest_closures.push(before_digest_simple!({
1717 requireMath!(cs_str);
1718 }));
1719 }
1720 if !options.nogroup {
1721 before_digest_closures.push(before_digest_simple!({
1722 bgroup();
1723 }));
1724 }
1725 if let Some(font) = options.font {
1726 before_digest_closures.push(before_digest_simple!({
1727 if let FontDirective::Asset(ref chosen_font) = font {
1728 merge_font((**chosen_font).clone());
1729 }
1730 }));
1731 }
1732 before_digest_closures.extend(options.before_digest);
1733 cons.before_digest = before_digest_closures;
1734 let mut after_digest_closures = options.after_digest;
1738 if options.dynamic_mathstyle {
1740 after_digest_closures.push(after_digest_simple!(_args, {
1741 let state_font = lookup_font().unwrap();
1742 let is_display = state_font
1743 .get_mathstyle()
1744 .is_some_and(|s| s.as_ref() == "display");
1745 let mathstyle = if is_display { "display" } else { "text" };
1746 _args.set_property("mathstyle", Stored::from(mathstyle.to_string()));
1747 }));
1748 }
1749 if !options.nogroup {
1750 after_digest_closures.push(after_digest_simple!(_args, {
1751 egroup()?;
1752 }));
1753 }
1754 cons.after_digest = after_digest_closures;
1755 cons.before_construct = options.before_construct;
1756 cons.after_construct = options.after_construct;
1757 let presentation_for_font = presentation.to_owned();
1758 properties.insert(
1759 "font",
1760 Stored::FontDirective(FontDirective::Closure(
1761 if let Some(mathstyle) = options.mathstyle {
1762 Rc::new(move |_whatsit| {
1763 Ok(
1764 lookup_font()
1765 .unwrap()
1766 .merge(Font {
1767 mathstyle: Some(Cow::Owned(mathstyle.clone())),
1768 ..Font::default()
1769 })
1770 .specialize(&presentation_for_font),
1771 )
1772 })
1773 } else {
1774 Rc::new(move |_whatsit| Ok(lookup_font().unwrap().specialize(&presentation_for_font)))
1775 },
1776 )),
1777 );
1778
1779 cons.properties = Rc::new(move |_args| Ok(properties.clone()));
1780}
1781
1782pub fn allocate_register(rtype: &str, cs: &str) -> Result<Option<String>> {
1788 let addr = match rtype {
1789 "\\count" => "\\count10",
1790 "\\dimen" => "\\count11",
1791 "\\skip" => "\\count12",
1792 "\\muskip" => "\\count13",
1793 "\\box" => "\\count14",
1794 "\\toks" => "\\count15",
1795 _ => "",
1796 };
1797 if !addr.is_empty() {
1798 if let Some(n) = lookup_number(addr) {
1800 let mut next = n.value_of() + 1;
1806 let mut loc = format!("{rtype}{next}");
1807 while is_value_bound(&loc, None) {
1808 next += 1;
1809 loc = format!("{rtype}{next}");
1810 }
1811 assign_value(addr, Number::new(next), Some(Scope::Global));
1812 Ok(Some(loc))
1813 } else {
1814 Ok(None)
1815 }
1816 } else {
1817 Error!(
1820 "misdefined",
1821 rtype,
1822 format!("Type {rtype} is not an allocated register type, for {cs}")
1823 );
1824 Ok(None)
1825 }
1826}