1use crate::alias;
36use crate::def;
37use crate::math;
38use crate::registers;
39use std::collections::HashSet;
40use texcraft_stdext::collections::groupingmap;
41use texlang::prelude as txl;
42use texlang::traits::*;
43use texlang::*;
44
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47pub struct Component {
48 scope: groupingmap::Scope,
49 global_defs_value: i32,
50 #[cfg_attr(feature = "serde", serde(skip))]
51 tags: Tags,
52}
53
54impl Default for Component {
55 fn default() -> Self {
56 Component {
57 scope: groupingmap::Scope::Local,
58 global_defs_value: 0,
59 tags: Default::default(),
60 }
61 }
62}
63
64struct Tags {
65 can_be_prefixed_with_any: HashSet<command::Tag>,
66 can_be_prefixed_with_global: HashSet<command::Tag>,
67 global_tag: command::Tag,
68 long_tag: command::Tag,
69 outer_tag: command::Tag,
70}
71
72impl Default for Tags {
73 fn default() -> Self {
74 Self {
75 can_be_prefixed_with_any: vec![def::def_tag()].into_iter().collect(),
76 can_be_prefixed_with_global: vec![
77 alias::let_tag(),
78 math::variable_op_tag(),
79 registers::countdef_tag(),
80 ]
81 .into_iter()
82 .collect(),
83 global_tag: GLOBAL_TAG.get(),
84 long_tag: LONG_TAG.get(),
85 outer_tag: OUTER_TAG.get(),
86 }
87 }
88}
89
90impl Component {
91 pub fn register_globally_prefixable_command(&mut self, tag: command::Tag) {
93 self.tags.can_be_prefixed_with_global.insert(tag);
94 }
95
96 #[inline]
98 fn read_and_reset_global(&mut self) -> groupingmap::Scope {
99 match self.global_defs_value.cmp(&0) {
100 std::cmp::Ordering::Less => groupingmap::Scope::Local,
101 std::cmp::Ordering::Equal => {
102 std::mem::replace(&mut self.scope, groupingmap::Scope::Local)
103 }
104 std::cmp::Ordering::Greater => groupingmap::Scope::Global,
105 }
106 }
107
108 fn set_scope(&mut self, scope: groupingmap::Scope) {
109 self.scope = if self.global_defs_value == 0 {
110 scope
111 } else {
112 groupingmap::Scope::Local
115 }
116 }
117}
118
119#[derive(Default, Clone, Copy)]
120struct Prefix {
121 global: Option<token::Token>,
122 long: Option<token::Token>,
123 outer: Option<token::Token>,
124}
125
126impl Prefix {
127 fn get_one(&self) -> (token::Token, Kind) {
128 if let Some(global_token) = self.global {
129 (global_token, Kind::Global)
130 } else if let Some(long_token) = self.long {
131 (long_token, Kind::Long)
132 } else if let Some(outer_token) = self.outer {
133 (outer_token, Kind::Outer)
134 } else {
135 panic!("")
136 }
137 }
138}
139
140pub fn get_globaldefs<S: HasComponent<Component>>() -> command::BuiltIn<S> {
142 command::BuiltIn::new_variable(variable::Command::new_singleton(
143 |s, _| &s.component().global_defs_value,
144 |s, _| &mut s.component_mut().global_defs_value,
145 ))
146}
147
148pub fn get_global<S: HasComponent<Component>>() -> command::BuiltIn<S> {
150 command::BuiltIn::new_execution(global_primitive_fn).with_tag(GLOBAL_TAG.get())
151}
152
153static GLOBAL_TAG: command::StaticTag = command::StaticTag::new();
154
155#[inline]
157pub fn variable_assignment_scope_hook<S: HasComponent<Component>>(
158 state: &mut S,
159) -> groupingmap::Scope {
160 state.component_mut().read_and_reset_global()
161}
162
163pub fn get_long<S: HasComponent<Component>>() -> command::BuiltIn<S> {
165 command::BuiltIn::new_execution(long_primitive_fn).with_tag(LONG_TAG.get())
166}
167
168static LONG_TAG: command::StaticTag = command::StaticTag::new();
169
170pub fn get_outer<S: HasComponent<Component>>() -> command::BuiltIn<S> {
172 command::BuiltIn::new_execution(outer_primitive_fn).with_tag(OUTER_TAG.get())
173}
174
175static OUTER_TAG: command::StaticTag = command::StaticTag::new();
176
177fn global_primitive_fn<S: HasComponent<Component>>(
178 global_token: token::Token,
179 input: &mut vm::ExecutionInput<S>,
180) -> txl::Result<()> {
181 process_prefixes(
182 Prefix {
183 global: Some(global_token),
184 long: None,
185 outer: None,
186 },
187 input,
188 )
189}
190
191fn long_primitive_fn<S: HasComponent<Component>>(
192 long_token: token::Token,
193 input: &mut vm::ExecutionInput<S>,
194) -> txl::Result<()> {
195 process_prefixes(
196 Prefix {
197 global: None,
198 long: Some(long_token),
199 outer: None,
200 },
201 input,
202 )
203}
204
205fn outer_primitive_fn<S: HasComponent<Component>>(
206 outer_token: token::Token,
207 input: &mut vm::ExecutionInput<S>,
208) -> txl::Result<()> {
209 process_prefixes(
210 Prefix {
211 global: None,
212 long: None,
213 outer: Some(outer_token),
214 },
215 input,
216 )
217}
218
219fn process_prefixes<S: HasComponent<Component>>(
220 mut prefix: Prefix,
221 input: &mut vm::ExecutionInput<S>,
222) -> txl::Result<()> {
223 complete_prefix(&mut prefix, input)?;
224 let t = input.next_or_err(PrefixEndOfInputError {})?;
225 input.back(t);
226 match t.value() {
227 token::Value::CommandRef(command_ref) => {
228 if let Some(command::Command::Variable(_) | command::Command::Font(_)) =
230 input.commands_map_mut().get_command(&command_ref)
231 {
232 assert_only_global_prefix(input, t, prefix)?;
233 if prefix.global.is_some() {
234 input
235 .state_mut()
236 .component_mut()
237 .set_scope(groupingmap::Scope::Global);
238 }
239 return Ok(());
240 }
241 let component = input.state().component();
243 let tag = input.commands_map().get_tag(&command_ref);
244 if let Some(tag) = tag {
245 if component.tags.can_be_prefixed_with_any.contains(&tag) {
246 if prefix.global.is_some() {
247 input
248 .state_mut()
249 .component_mut()
250 .set_scope(groupingmap::Scope::Global);
251 }
252 return Ok(());
253 }
254 if component.tags.can_be_prefixed_with_global.contains(&tag) {
257 assert_only_global_prefix(input, t, prefix)?;
258 if prefix.global.is_some() {
259 input
260 .state_mut()
261 .component_mut()
262 .set_scope(groupingmap::Scope::Global);
263 }
264 return Ok(());
265 }
266 }
267 let (prefix_token, kind) = prefix.get_one();
269 Err(input.fatal_error(Error {
270 kind,
271 got: t,
272 prefix: prefix_token,
273 prefix_kind: kind,
274 }))
275 }
276 _ => {
277 let (prefix_token, kind) = prefix.get_one();
278 Err(input.fatal_error(Error {
279 kind,
280 got: t,
281 prefix: prefix_token,
282 prefix_kind: kind,
283 }))
284 }
285 }
286}
287
288#[derive(Debug)]
289struct PrefixEndOfInputError;
290
291impl error::EndOfInputError for PrefixEndOfInputError {
292 fn doing(&self) -> String {
293 "scanning a command to prefix".into()
294 }
295 fn notes(&self) -> Vec<error::display::Note> {
296 vec![
297 r"prefix commands (\global, \long, \outer) must be followed by a command to prefix"
298 .into(),
299 ]
300 }
301}
302
303fn complete_prefix<S: HasComponent<Component>>(
304 prefix: &mut Prefix,
305 input: &mut vm::ExecutionInput<S>,
306) -> txl::Result<()> {
307 let found_prefix = match input.next()? {
309 None => false,
310 Some(t) => match t.value() {
311 token::Value::CommandRef(command_ref) => {
312 let tag = input.commands_map().get_tag(&command_ref);
313 if tag == Some(input.state().component().tags.global_tag) {
314 prefix.global = Some(t);
315 true
316 } else if tag == Some(input.state().component().tags.outer_tag) {
317 prefix.outer = Some(t);
318 true
319 } else if tag == Some(input.state().component().tags.long_tag) {
320 prefix.long = Some(t);
321 true
322 } else {
323 input.back(t);
324 false
325 }
326 }
327 _ => {
328 input.back(t);
329 false
330 }
331 },
332 };
333 if !found_prefix {
334 return Ok(());
335 }
336 complete_prefix(prefix, input)
337}
338
339fn assert_only_global_prefix<S: TexlangState>(
340 input: &mut vm::ExecutionInput<S>,
341 token: token::Token,
342 prefix: Prefix,
343) -> txl::Result<()> {
344 if let Some(outer_token) = prefix.outer {
345 Err(input.fatal_error(Error {
346 kind: Kind::Outer,
347 got: token,
348 prefix: outer_token,
349 prefix_kind: Kind::Outer,
350 }))
351 } else if let Some(long_token) = prefix.long {
352 Err(input.fatal_error(Error {
353 kind: Kind::Long,
354 got: token,
355 prefix: long_token,
356 prefix_kind: Kind::Long,
357 }))
358 } else {
359 Ok(())
360 }
361}
362
363#[derive(Debug, Clone, Copy)]
364enum Kind {
365 Global,
366 Long,
367 Outer,
368}
369
370impl Kind {
371 fn control_sequence(&self) -> &'static str {
372 match self {
373 Kind::Global => r"\global",
374 Kind::Long => r"\long",
375 Kind::Outer => r"\outer",
376 }
377 }
378}
379
380#[derive(Debug)]
381struct Error {
382 kind: Kind,
383 got: token::Token,
384 prefix: token::Token,
385 prefix_kind: Kind,
386}
387
388impl error::TexError for Error {
389 fn kind(&self) -> error::Kind {
390 error::Kind::Token(self.got)
391 }
392
393 fn title(&self) -> String {
394 match self.got.value() {
395 token::Value::CommandRef(_) => {
396 format![
397 "this command cannot be prefixed by {}",
398 self.prefix_kind.control_sequence()
399 ]
400 }
401 _ => format![
402 "character tokens cannot be prefixed by {}",
403 self.prefix_kind.control_sequence()
404 ],
405 }
406 }
407
408 fn source_annotation(&self) -> String {
409 format![
410 "cannot by prefixed by {}",
411 self.prefix_kind.control_sequence()
412 ]
413 }
414
415 fn notes(&self) -> Vec<error::display::Note> {
416 let guidance = match self.kind {
417 Kind::Global => {
418 r"see the documentation for \global for the list of commands it can be used with"
419 }
420 Kind::Long => {
421 r"the \long prefix can only be used with \def, \gdef, \edef and \xdef (or their aliases)"
422 }
423 Kind::Outer => {
424 r"the \outer prefix can only be used with \def, \gdef, \edef and \xdef (or their aliases)"
425 }
426 };
427 vec![
428 guidance.into(),
429 error::display::Note::SourceCodeTrace("the prefix appeared here:".into(), self.prefix),
430 ]
431 }
432}
433
434pub fn get_assert_global_is_false<S: HasComponent<Component>>() -> command::BuiltIn<S> {
444 fn noop_execution_cmd_fn<S: HasComponent<Component>>(
445 token: token::Token,
446 input: &mut vm::ExecutionInput<S>,
447 ) -> txl::Result<()> {
448 match input.state_mut().component_mut().read_and_reset_global() {
449 groupingmap::Scope::Global => Err(input.fatal_error(error::SimpleTokenError::new(
450 token,
451 "assertion failed: global is true",
452 ))),
453 groupingmap::Scope::Local => Ok(()),
454 }
455 }
456 command::BuiltIn::new_execution(noop_execution_cmd_fn)
457}
458
459#[cfg(test)]
460mod test {
461 use super::*;
462 use crate::the;
463 use std::collections::HashMap;
464 use texlang::vm::implement_has_component;
465 use texlang_testing::*;
466
467 #[derive(Default)]
468 struct State {
469 prefix: Component,
470 testing: TestingComponent,
471 }
472
473 impl TexlangState for State {
474 fn variable_assignment_scope_hook(state: &mut Self) -> groupingmap::Scope {
475 variable_assignment_scope_hook(state)
476 }
477 }
478 impl the::TheCompatible for State {}
479
480 implement_has_component![State {
481 prefix: Component,
482 testing: TestingComponent,
483 }];
484
485 fn built_in_commands() -> HashMap<&'static str, command::BuiltIn<State>> {
486 HashMap::from([
487 ("global", get_global()),
488 ("globaldefs", get_globaldefs()),
489 ("long", get_long()),
490 ("outer", get_outer()),
491 ("i", TestingComponent::get_integer()),
492 ("the", the::get_the()),
493 ("def", def::get_def()),
494 ("advance", math::get_advance()),
495 (
496 "noOpExpansion",
497 command::BuiltIn::new_expansion(|_, _| Ok(())),
498 ),
499 (
500 "noOpExecution",
501 command::BuiltIn::new_execution(|_, _| Ok(())),
502 ),
503 ])
504 }
505
506 test_suite![
507 expansion_equality_tests(
508 (non_global, r"\i=5{\i=8}\the\i", "5"),
509 (non_global_2, r"\i=5\i=6{\i=8}\the\i", "6"),
510 (non_global_3, r"\i=5{\i=6{\i=8 \the\i}\the\i}\the\i", "865"),
511 (global, r"\i=5{\global\i=8}\the\i", "8"),
512 (global_squared, r"\i=5{\global\global\i=8}\the\i", "8"),
513 (long, r"\long\def\A{Hello}\A", "Hello"),
514 (outer, r"\outer\def\A{Hello}\A", "Hello"),
515 (
516 many_prefixes,
517 r"\long\outer\global\long\global\outer\def\A{Hello}\A",
518 "Hello"
519 ),
520 (global_defs_1, r"\i=5{\globaldefs=1 \i=8}\the\i", "8"),
521 (global_defs_2, r"\i=5{\globaldefs=-1\global\i=8}\the\i", "5"),
522 ),
523 fatal_error_tests(
524 (global_end_of_input, r"\global"),
525 (global_with_character, r"\global a"),
526 (global_with_undefined_command, r"\global \undefinedCommand"),
527 (
528 global_with_no_op_expansion_command,
529 r"\global \noOpExpansion"
530 ),
531 (
532 global_with_no_op_execution_command,
533 r"\global \noOpExecution"
534 ),
535 (long_prefix_when_global_allowed, r"\long\advance\i 0"),
536 (outer_prefix_when_global_allowed, r"\outer\advance\i 0"),
537 ),
538 ];
539}