1use std::{borrow::Cow, fmt, rc::Rc};
2
3use libxml::tree::Node;
4
5use crate::{
6 BoxOps, Digested,
7 common::{arena::SymHashMap, error::*, font::Font, locator::Locator, object::Object},
8 definition::{
9 BeforeDigestClosure, ConstructionClosure, Definition, DigestionClosure, FontDirective,
10 PropertiesClosure, ReplacementClosure, Reversion, SizingClosure,
11 },
12 document::Document,
13 parameter::Parameters,
14 state::*,
15 stomach::digest_next_body,
16 token::*,
17 tokens::Tokens,
18 whatsit::Whatsit,
19};
20
21fn assemble_locator(args: &[Option<Digested>]) -> Option<Locator> {
25 args
26 .iter()
27 .flatten()
28 .filter_map(child_span)
29 .reduce(|a, b| Locator::new_range(a, b).unwrap_or(a))
30}
31
32pub fn child_span(d: &Digested) -> Option<Locator> {
43 if let Some(l) = d.get_locator().filter(|l| l.from_line != 0) {
44 return Some(l);
45 }
46 #[cfg(feature = "token-locators")]
47 {
48 let reverted = d.revert().ok()?;
49 let origins: Vec<crate::token::TokenStart> = reverted
50 .unlist_ref()
51 .iter()
52 .filter_map(|t| crate::token::get_token_origin(t.loc))
53 .collect();
54 let span = |it: &mut dyn Iterator<Item = &crate::token::TokenStart>| {
61 it.map(|o| {
62 crate::common::arena::with(o.source, |s| Locator::new(s, o.line, o.col, o.line, o.col))
63 })
64 .reduce(|a, b| Locator::new_range(a, b).unwrap_or(a))
65 };
66 span(&mut origins.iter().filter(|o| !o.inherited))
67 .or_else(|| span(&mut origins.iter().filter(|o| o.inherited)))
68 }
69 #[cfg(not(feature = "token-locators"))]
70 {
71 None
72 }
73}
74
75#[derive(Clone)]
77pub struct ConstructorOptions {
78 pub nargs: Option<usize>,
80 pub bounded: bool,
82 pub mode: Option<String>,
84 pub sizer: Option<SizingClosure>,
86 pub before_digest: Vec<BeforeDigestClosure>,
88 pub after_digest: Vec<DigestionClosure>,
90 pub before_construct: Vec<ConstructionClosure>,
92 pub after_construct: Vec<ConstructionClosure>,
94
95 pub enter_horizontal: bool,
97 pub leave_horizontal: bool,
99
100 pub require_math: bool,
103 pub forbid_math: bool,
105 pub properties: PropertiesClosure,
107 pub capture_body: bool,
109 pub font: Option<FontDirective>,
111 pub after_digest_begin: Vec<DigestionClosure>,
113 pub before_digest_end: Vec<BeforeDigestClosure>,
115 pub after_digest_body: Vec<DigestionClosure>,
117 pub reversion: Option<Reversion>,
119 pub scope: Option<Scope>,
121 pub robust: bool,
123 pub locked: bool,
125 pub alias: Option<String>,
127}
128impl Default for ConstructorOptions {
129 fn default() -> Self {
130 ConstructorOptions {
131 nargs: None,
132 bounded: false,
133 before_digest: vec![],
134 after_digest: vec![],
135 before_construct: vec![],
136 after_construct: vec![],
137 mode: None,
138 enter_horizontal: false,
139 leave_horizontal: false,
140 require_math: false,
142 forbid_math: false,
143 properties: Rc::new(|_whatsit| Ok(SymHashMap::default())),
144 capture_body: false,
145 font: None,
146 after_digest_begin: vec![],
147 before_digest_end: vec![],
148 after_digest_body: vec![],
149 scope: None,
150 robust: false,
151 locked: false,
152 alias: None,
153 reversion: None,
154 sizer: None,
155 }
156 }
157}
158impl fmt::Debug for ConstructorOptions {
159 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
160 write!(
161 f,
162 "\nConstructorOptions {{nargs:{:?}, bounded:{:?}, mode:{:?}, \n\tbefore_digest:{:?}, \
163 after_digest_begin:{:?}, before_digest_end:{:?},\n\tafter_digest:{:?}, \
164 after_digest_body:{:?}, before_construct:{:?}, after_construct:{:?},\n\trequire_math:{:?}, \
165 forbid_math:{:?}, capture_body:{:?}, scope:{:?},\n\tlocked:{:?}, alias:{:?} }}\n",
166 self.nargs,
167 self.bounded,
168 self.mode,
169 self.before_digest.len(),
170 self.after_digest_begin.len(),
171 self.before_digest_end.len(),
172 self.after_digest.len(),
173 self.after_digest_body.len(),
174 self.before_construct.len(),
175 self.after_construct.len(),
176 self.require_math,
177 self.forbid_math,
178 self.capture_body,
179 self.scope,
180 self.locked,
181 self.alias
182 )
183 }
184}
185
186#[derive(Clone)]
187pub struct Constructor {
188 pub cs: Token,
189 pub nargs: Option<usize>,
190 pub paramlist: Option<Parameters>,
191 pub replacement: Option<ReplacementClosure>,
192 pub sizer: Option<SizingClosure>,
193 pub before_digest: Vec<BeforeDigestClosure>,
194 pub after_digest: Vec<DigestionClosure>,
195 pub before_construct: Vec<ConstructionClosure>,
196 pub after_construct: Vec<ConstructionClosure>,
197 pub properties: PropertiesClosure,
198 pub capture_body: bool,
199 pub after_digest_body: Vec<DigestionClosure>,
201 pub reversion: Option<Reversion>,
202 pub alias: Option<String>,
203}
204impl Default for Constructor {
205 fn default() -> Self {
206 Constructor {
207 cs: T_CS!("Constructor"),
208 nargs: None,
209 paramlist: None,
210 replacement: None,
211 before_digest: vec![],
212 after_digest: vec![],
213 before_construct: vec![],
214 after_construct: vec![],
215 properties: Rc::new(|_whatsit| Ok(SymHashMap::default())),
216 capture_body: false,
217 after_digest_body: vec![],
218 reversion: None,
219 alias: None,
220 sizer: None,
221 }
222 }
223}
224impl fmt::Debug for Constructor {
225 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
226 write!(
227 f,
228 "\nConstructor {{
229 cs:{:?}
230 nargs:{:?}
231 paramlist:{:?}
232 replacement:{:?}
233 before_digest:{:?}
234 after_digest:{:?}
235 before_construct:{:?}
236 after_construct:{:?}
237 capture_body:{:?}
238 after_digest_body:{:?}
239 reversion:{:?}
240 alias:{:?}
241 sizer:{:?} }}\n",
242 self.cs,
243 self.nargs,
244 self.paramlist,
245 self.replacement.is_some(),
246 self.before_digest.len(),
247 self.after_digest.len(),
248 self.before_construct.len(),
249 self.after_construct.len(),
250 self.capture_body,
251 self.after_digest_body.len(),
252 self.reversion.is_some(),
253 self.alias,
254 self.sizer.is_some(),
255 )
256 }
257}
258
259impl PartialEq for Constructor {
260 fn eq(&self, other: &Constructor) -> bool { self.cs == other.cs }
261}
262
263impl fmt::Display for Constructor {
264 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
265 write!(
266 f,
267 "{}",
268 <Self as Definition>::stringify_type(self, "Constructor")
269 )
270 }
271}
272impl Object for Constructor {
273 fn stringify(&self) -> String { <Self as Definition>::stringify_type(self, "Constructor") }
274}
275impl Constructor {
276 pub fn invoke_primitive_shared(me: &Rc<Constructor>) -> Result<Vec<Digested>> {
299 me.digest_to_whatsit(Rc::clone(me) as Rc<dyn Definition>)
300 }
301
302 fn digest_to_whatsit(&self, definition: Rc<dyn Definition>) -> Result<Vec<Digested>> {
305 Debug!("invoke_primitive for {:?}", self.get_cs());
306 let mut result = self.execute_before_digest()?;
313
314 let state_font = lookup_font();
317 let ismath = lookup_bool_sym(crate::pin!("IN_MATH"));
318 let mut args: Vec<Option<Digested>> = match self.get_parameters() {
321 None => Vec::new(),
322 Some(params) => params.read_arguments_and_digest(self)?,
323 };
324 let nargs = self.get_num_args();
326 args.truncate(nargs);
327
328 let mut properties = (self.properties)(&args)?;
331 properties
336 .entry("font")
337 .or_insert_with(|| match state_font {
338 Some(f) => Stored::Font(Rc::clone(&f)),
339 None => Stored::Font(Rc::new(Font::text_default())), });
341 properties
344 .entry("isMath")
345 .or_insert_with(|| Stored::Bool(ismath));
346 properties.entry("mode").or_insert_with(|| {
349 let mode = lookup_string_from_sym(crate::pin!("MODE"));
350 Stored::String(crate::common::arena::pin(if mode.is_empty() {
351 "restricted_horizontal"
352 } else {
353 &mode
354 }))
355 });
356 let mut whatsit = Whatsit {
365 definition,
366 args,
367 properties,
368 reversion: None,
369 dual_reversion: None,
370 locator: None,
371 };
372 if source_map_enabled() {
383 whatsit.locator =
387 assemble_locator(&whatsit.args).or_else(|| Some(crate::gullet::get_locator()));
388 }
389
390 let mut post = self.execute_after_digest(&mut whatsit)?;
392
393 if self.capture_body {
394 let captured = digest_next_body(None)?;
395 post.extend(captured);
397
398 #[cfg(feature = "token-locators")]
404 if crate::state::source_map_enabled() {
405 if let Some(body_span) = post
406 .iter()
407 .filter_map(child_span)
408 .reduce(|a, b| Locator::new_range(a, b).unwrap_or(a))
409 {
410 whatsit.locator = Some(match whatsit.locator {
411 Some(prev) if prev.from_line != 0 => {
412 Locator::new_range(prev, body_span).unwrap_or(body_span)
413 },
414 _ => body_span,
415 });
416 }
417 }
418 whatsit.set_body(post);
419 post = vec![];
420 }
423 let post_post = self.execute_after_digest_body(&mut whatsit)?;
424 result.push(whatsit.into());
428 result.extend(post);
429 result.extend(post_post);
430 Ok(result)
431 }
432}
433
434impl Definition for Constructor {
435 fn before_digest(&self) -> Option<&Vec<BeforeDigestClosure>> { Some(&self.before_digest) }
436 fn after_digest(&self) -> Option<&Vec<DigestionClosure>> { Some(&self.after_digest) }
437 fn after_digest_body(&self) -> Option<&Vec<DigestionClosure>> { Some(&self.after_digest_body) }
438 fn capture_body(&self) -> bool { self.capture_body }
439 fn get_sizer(&self) -> Option<SizingClosure> { self.sizer.clone() }
440 fn invoke(&self, _once_only: bool) -> Result<Tokens> { Ok(Tokens!()) }
441 fn invoke_primitive(&self) -> Result<Vec<Digested>> {
449 self.digest_to_whatsit(Rc::new(self.clone()))
450 }
451
452 fn get_cs(&self) -> Cow<'_, Token> { Cow::Borrowed(&self.cs) }
453 fn get_cs_name(&self) -> Cow<'_, str> { Cow::Owned(self.cs.with_cs_name(ToString::to_string)) }
454 fn get_alias(&self) -> Option<&String> { self.alias.as_ref() }
455 fn get_parameters(&self) -> Option<&Parameters> { self.paramlist.as_ref() }
456 fn get_num_args(&self) -> usize {
457 match self.nargs {
458 Some(n) => n,
459 None => match self.paramlist {
460 Some(ref params) => params.get_num_args(),
461 None => 0,
462 },
463 }
464 }
466
467 fn do_absorption(&self, document: &mut Document, whatsit: &Whatsit) -> Result<Vec<Node>> {
468 for pre_closure in &self.before_construct {
469 pre_closure(document, whatsit)?;
470 }
471
472 match self.replacement {
473 None => {
474 },
476 Some(ref main_closure) => {
477 main_closure(document, whatsit.get_args(), whatsit.get_properties())?
478 },
479 };
480
481 for post_closure in &self.after_construct {
482 post_closure(document, whatsit)?;
483 }
484 Ok(Vec::new())
485 }
486 fn get_reversion_spec(&self) -> Option<Reversion> { self.reversion.clone() }
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492
493 #[test]
494 fn constructor_options_default_all_false_none_empty() {
495 let o = ConstructorOptions::default();
496 assert!(o.nargs.is_none());
497 assert!(!o.bounded);
498 assert!(o.mode.is_none());
499 assert!(!o.enter_horizontal);
500 assert!(!o.leave_horizontal);
501 assert!(!o.require_math);
502 assert!(!o.forbid_math);
503 assert!(!o.capture_body);
504 assert!(o.font.is_none());
505 assert!(o.scope.is_none());
506 assert!(!o.robust);
507 assert!(!o.locked);
508 assert!(o.alias.is_none());
509 assert!(o.reversion.is_none());
510 assert!(o.sizer.is_none());
511 assert!(o.before_digest.is_empty());
512 assert!(o.after_digest.is_empty());
513 assert!(o.before_construct.is_empty());
514 assert!(o.after_construct.is_empty());
515 assert!(o.after_digest_begin.is_empty());
516 assert!(o.before_digest_end.is_empty());
517 assert!(o.after_digest_body.is_empty());
518 }
519
520 #[test]
521 fn constructor_default_fields() {
522 let c = Constructor::default();
523 assert!(c.nargs.is_none());
524 assert!(c.paramlist.is_none());
525 assert!(c.replacement.is_none());
526 assert!(c.sizer.is_none());
527 assert!(!c.capture_body);
528 assert!(c.alias.is_none());
529 assert!(c.reversion.is_none());
530 assert!(c.before_digest.is_empty());
531 assert!(c.after_digest.is_empty());
532 assert!(c.before_construct.is_empty());
533 assert!(c.after_construct.is_empty());
534 assert!(c.after_digest_body.is_empty());
535 }
536
537 #[test]
538 fn constructor_options_debug_includes_fields() {
539 let o = ConstructorOptions::default();
543 let s = format!("{o:?}");
544 assert!(s.contains("nargs"), "got {s:?}");
545 assert!(s.contains("bounded"), "got {s:?}");
546 assert!(s.contains("capture_body"), "got {s:?}");
547 }
548}