1use std::borrow::Cow;
2
3use libxml::tree::Node;
4
5use crate::{
6 Digested,
7 common::{arena::SymHashMap, error::*, object::Object, store::Stored},
8 definition::{
9 BeforeDigestClosure, Definition, DigestionClosure, FontDirective, PrimitiveBody, Reversion,
10 },
11 document::Document,
12 parameter::Parameters,
13 pin,
14 state::Scope,
15 tbox::Tbox,
16 token::*,
17 tokens::Tokens,
18 whatsit::Whatsit,
19};
20
21#[derive(Clone, Default)]
22pub struct PrimitiveOptions {
23 pub bounded: bool,
24 pub is_prefix: bool,
25 pub require_math: bool,
26 pub forbid_math: bool,
27 pub robust: bool,
28 pub locked: bool,
29 pub enter_horizontal: bool,
30 pub leave_horizontal: bool,
31 pub nargs: Option<usize>,
32 pub scope: Option<Scope>,
33 pub font: Option<FontDirective>,
34 pub mode: Option<String>,
35 pub alias: Option<String>,
36 pub before_digest: Vec<BeforeDigestClosure>,
37 pub after_digest: Vec<DigestionClosure>,
38 pub reversion: Option<Reversion>,
39 pub font_id: Option<crate::common::arena::data::SymStr>,
42}
43
44#[derive(Clone)]
45pub struct Primitive {
46 pub cs: Token,
47 pub paramlist: Option<Parameters>,
48 pub replacement: Option<PrimitiveBody>,
51 pub before_digest: Vec<BeforeDigestClosure>,
52 pub after_digest: Vec<DigestionClosure>,
53 pub alias: Option<String>,
54 pub nargs: Option<usize>,
55 pub reversion: Option<Reversion>,
56 pub is_prefix: bool,
57 pub font_id: Option<crate::common::arena::data::SymStr>,
65}
66impl Default for Primitive {
67 fn default() -> Self {
68 Primitive {
69 cs: T_CS!("Primitive"),
70 paramlist: None,
71 replacement: None,
72 alias: None,
73 before_digest: Vec::new(),
74 after_digest: Vec::new(),
75 nargs: None,
76 reversion: None,
77 is_prefix: false,
78 font_id: None,
79 }
80 }
81}
82impl PartialEq for Primitive {
83 fn eq(&self, other: &Primitive) -> bool { self.cs == other.cs }
84}
85
86impl Object for Primitive {
92 fn stringify(&self) -> String { <Self as Definition>::stringify_type(self, "Primitive") }
93}
94impl Definition for Primitive {
95 fn before_digest(&self) -> Option<&Vec<BeforeDigestClosure>> { Some(&self.before_digest) }
96 fn after_digest(&self) -> Option<&Vec<DigestionClosure>> { Some(&self.after_digest) }
97 fn is_prefix(&self) -> bool { self.is_prefix }
98
99 fn invoke(&self, _once_only: bool) -> Result<Tokens> { Ok(Tokens!()) }
100 fn invoke_primitive(&self) -> Result<Vec<Digested>> {
101 Debug!("primitive invoke for {:?}", self.cs);
102 let mut invoked_boxes: Vec<Digested> = self.execute_before_digest()?;
107 let args = self.read_arguments()?;
108 match self.replacement {
110 Some(PrimitiveBody::Closure(ref closure)) => invoked_boxes.extend(closure(args)?),
111 Some(PrimitiveBody::String(symbol)) => {
112 crate::stomach::enter_horizontal();
114 let cs_token = self
115 .alias
116 .as_ref()
117 .map(|alias| Token::from(alias.as_str()))
118 .unwrap_or(self.cs);
119 let mut box_tokens = vec![cs_token];
120 if let Some(ref params) = self.paramlist {
122 for arg in &args {
123 box_tokens.extend(arg.revert()?.unlist());
124 }
125 let _ = params; }
127 let box_props = SymHashMap::default();
128 invoked_boxes.push(Digested::from(Tbox::new(
129 symbol,
130 None,
131 None,
132 Tokens::new(box_tokens),
133 box_props,
134 )));
135 },
136 None => {
137 let cs_token = self
141 .alias
142 .as_ref()
143 .map(|alias| Token::from(alias.as_str()))
144 .unwrap_or(self.cs);
145 let box_tokens = vec![cs_token];
146 let mut box_props = SymHashMap::default();
148 box_props.insert("isEmpty", Stored::Bool(true));
149 invoked_boxes.push(Digested::from(Tbox::new(
150 pin!(""),
151 None,
152 None,
153 Tokens::new(box_tokens),
154 box_props,
155 )));
156 },
157 }
158 if !self.after_digest.is_empty() {
159 let mut w = Whatsit::default();
161 let after_boxes = self.execute_after_digest(&mut w)?;
162 invoked_boxes.extend(after_boxes);
163 }
164
165 Ok(invoked_boxes)
167 }
168
169 fn do_absorption(&self, _document: &mut Document, _whatsit: &Whatsit) -> Result<Vec<Node>> {
170 fatal!(
171 Definition,
172 Unexpected,
173 "do_absorption on Primitive should never be called!"
174 );
175 }
176
177 fn get_cs(&self) -> Cow<'_, Token> { Cow::Borrowed(&self.cs) }
178 fn get_cs_name(&self) -> Cow<'_, str> { Cow::Owned(self.cs.with_cs_name(ToString::to_string)) }
179 fn get_alias(&self) -> Option<&String> { self.alias.as_ref() }
180 fn get_parameters(&self) -> Option<&Parameters> { self.paramlist.as_ref() }
181
182 fn get_num_args(&self) -> usize {
183 match self.nargs {
184 Some(n) => n,
185 None => match self.paramlist {
186 Some(ref params) => params.get_num_args(),
187 None => 0,
188 },
189 }
190 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use crate::common::arena;
199
200 #[test]
201 fn primitive_default_fields() {
202 let p = Primitive::default();
203 assert_eq!(arena::to_string(p.cs.text), "Primitive");
204 assert!(p.paramlist.is_none());
205 assert!(p.replacement.is_none());
206 assert!(p.alias.is_none());
207 assert!(p.before_digest.is_empty());
208 assert!(p.after_digest.is_empty());
209 assert!(p.nargs.is_none());
210 assert!(p.reversion.is_none());
211 assert!(!p.is_prefix);
212 }
213
214 #[test]
215 fn primitive_partial_eq_by_cs() {
216 let mut a = Primitive::default();
221 let mut b = Primitive::default();
222 a.cs = T_CS!("\\foo");
223 b.cs = T_CS!("\\foo");
224 assert!(a == b, "same cs should compare equal");
225 b.cs = T_CS!("\\bar");
226 assert!(!(a == b), "different cs should not be equal");
227 }
228
229 #[test]
230 fn primitive_is_prefix_reflects_field() {
231 let mut p = Primitive::default();
232 assert!(!p.is_prefix());
233 p.is_prefix = true;
234 assert!(p.is_prefix());
235 }
236
237 #[test]
238 fn primitive_get_num_args_zero_without_params() {
239 let p = Primitive::default();
240 assert_eq!(p.get_num_args(), 0);
241 }
242
243 #[test]
244 fn primitive_get_num_args_uses_nargs_override() {
245 let p = Primitive {
247 nargs: Some(3),
248 ..Default::default()
249 };
250 assert_eq!(p.get_num_args(), 3);
251 }
252
253 #[test]
254 fn primitive_before_digest_ref_returns_some_empty() {
255 let p = Primitive::default();
256 let bd = p.before_digest().expect("Some(&Vec)");
257 assert!(bd.is_empty());
258 }
259
260 #[test]
261 fn primitive_after_digest_ref_returns_some_empty() {
262 let p = Primitive::default();
263 let ad = p.after_digest().expect("Some(&Vec)");
264 assert!(ad.is_empty());
265 }
266
267 #[test]
268 fn primitive_get_parameters_none_by_default() {
269 let p = Primitive::default();
270 assert!(p.get_parameters().is_none());
271 }
272
273 #[test]
274 fn primitive_options_default_all_false() {
275 let o = PrimitiveOptions::default();
276 assert!(!o.bounded);
277 assert!(!o.is_prefix);
278 assert!(!o.require_math);
279 assert!(!o.forbid_math);
280 assert!(!o.robust);
281 assert!(!o.locked);
282 assert!(!o.enter_horizontal);
283 assert!(!o.leave_horizontal);
284 assert!(o.nargs.is_none());
285 assert!(o.scope.is_none());
286 assert!(o.font.is_none());
287 assert!(o.mode.is_none());
288 assert!(o.alias.is_none());
289 assert!(o.before_digest.is_empty());
290 assert!(o.after_digest.is_empty());
291 assert!(o.reversion.is_none());
292 }
293}