texlang/vm/mod.rs
1//! The Texlang virtual machine (VM).
2//!
3//! This module contains the definition of the runtime VM,
4//! various input streams that wrap the VM
5//! and the main function that is used to run Texlang.
6//! See the VM documentation in the Texlang book for full documentation.
7
8use super::token::CsName;
9use crate::command;
10use crate::command::BuiltIn;
11use crate::command::Command;
12use crate::error;
13use crate::prelude as txl;
14use crate::texmacro;
15use crate::token;
16use crate::token::lexer;
17use crate::token::trace;
18use crate::token::CsNameInterner;
19use crate::token::Token;
20use crate::token::Value;
21use crate::types;
22use crate::variable;
23use common::font;
24use std::collections::HashMap;
25use std::path::PathBuf;
26use texcraft_stdext::collections::groupingmap;
27
28#[cfg(feature = "serde")]
29pub mod serde;
30mod streams;
31pub use streams::*;
32
33/// Implementations of this trait determine how the VM handles non-execution-command tokens.
34///
35/// The main loop of the VM reads the next expanded token and performs
36/// some action based on the token.
37/// Many cases are handled automatically based on the semantics of the TeX language:
38///
39/// | token type | example | action |
40/// | -- | -- | -- |
41/// | execution command | `\def` | run the command |
42/// | variable command | `\count` | assign a value to the corresponding variable |
43/// | token alias | `\a` after `\let\a=a` | run the main VM loop for the token that is aliased |
44/// | begin group character | `{` | begin a group
45/// | end group character | `}` | end the current group
46///
47/// Note that the first three rows can arise from both control sequences and active character tokens.
48///
49/// The remaining cases are not specified by the TeX language but instead by
50/// the business logic of the TeX engine being built.
51/// The behavior in these cases is specified by implementing the associated handler.
52/// These cases and handlers are:
53///
54/// | token type | example | handler | default |
55/// | --- | --- | --- | --- |
56/// | character token | `b` | [character_handler](Handlers::character_handler) | do nothing
57/// | undefined command | `\b` where `\b` was never defined | [undefined_command_handler](Handlers::undefined_command_handler) | return an undefined control sequence error
58/// | unexpanded expansion command | `\the` in `\noexpand\the` | [unexpanded_expansion_command](Handlers::unexpanded_expansion_command) | do nothing
59///
60/// Each of the handlers has the same function signature as an execution command.
61pub trait Handlers<S: TexlangState> {
62 /// Handler to invoke for character tokens.
63 ///
64 /// This token is _not_ invoked for tokens whose category code is begin group (1), end group (2) or active character (13).
65 /// These cases are handled automatically by the VM based on the semantics of the TeX language.
66 ///
67 /// The default implementation is a no-op.
68 fn character_handler(
69 input: &mut ExecutionInput<S>,
70 token: token::Token,
71 character: char,
72 ) -> txl::Result<()> {
73 _ = (input, token, character);
74 Ok(())
75 }
76
77 /// Handler to invoke for math character tokens.
78 ///
79 /// The default implementation throws an error because math character tokens are
80 /// only valid in math mode which is implemented outside of the main VM loop.
81 fn math_character_handler(
82 input: &mut ExecutionInput<S>,
83 token: token::Token,
84 math_character: types::MathCode,
85 ) -> txl::Result<()> {
86 _ = math_character;
87 Err(input.fatal_error(error::SimpleTokenError::new(
88 token,
89 "math characters can only appear in math mode",
90 )))
91 }
92
93 /// Handler to invoke for a control sequence or active character for which no command is defined.
94 ///
95 /// The default implementation throws an undefined command error.
96 fn undefined_command_handler(
97 input: &mut ExecutionInput<S>,
98 token: token::Token,
99 ) -> txl::Result<()> {
100 Err(input.fatal_error(error::UndefinedCommandError::new(input.vm(), token)))
101 }
102
103 /// Handler to invoke for expansion commands that were not expanded.
104 ///
105 /// For example, in the TeX snippet `\noexpand\the`, this handler handles
106 /// the unexpanded `\the` token.
107 ///
108 /// The default implementation is a no-op.
109 fn unexpanded_expansion_command(
110 input: &mut ExecutionInput<S>,
111 token: token::Token,
112 ) -> txl::Result<()> {
113 _ = (token, input);
114 Ok(())
115 }
116
117 /// Handler to invoke when the input ends.
118 ///
119 /// In TeX the user is prompted to add additional input and if no
120 /// input is provided a fatal error is thrown.
121 /// To end the VM without an error the user has to write `\end`
122 /// or `\dump`.
123 ///
124 /// In this handler, if `Ok(())` is returned, the VM starts running again
125 /// under the assumption that additional TeX source has been added to the VM.
126 /// Otherwise the shutdown signal causes the VM to stop.
127 ///
128 /// The default implementation shuts down the VM with no error.
129 fn end_of_input_handler(input: &mut ExecutionInput<S>) -> txl::Result<()> {
130 Err(input.shutdown())
131 }
132}
133
134#[derive(Default)]
135pub struct DefaultHandlers;
136
137impl<S: TexlangState> Handlers<S> for DefaultHandlers {}
138
139impl<S: TexlangState> VM<S> {
140 /// Run the VM.
141 ///
142 /// It is assumed that the VM has been preloaded with TeX source code using the
143 /// [VM::push_source] method.
144 pub fn run<H: Handlers<S>>(&mut self) -> Result<(), Box<error::TracedTexError>> {
145 self.run_impl::<H>();
146 match self.internal.shutdown_status.take() {
147 ShutdownStatus::None => unreachable!(),
148 ShutdownStatus::Normal => Ok(()),
149 ShutdownStatus::Error(traced_error) => Err(Box::new(traced_error)),
150 }
151 }
152 fn run_impl<H: Handlers<S>>(&mut self) -> ShutdownSignal {
153 let input = ExecutionInput::new(self);
154
155 loop {
156 let token = match input.next() {
157 Ok(None) => match H::end_of_input_handler(input) {
158 Ok(_) => continue,
159 Err(signal) => return signal,
160 },
161 Ok(Some(token)) => token,
162 Err(signal) => return signal,
163 };
164 let r = match token.value() {
165 Value::CommandRef(command_ref) => {
166 match input.commands_map().get_command(&command_ref) {
167 Some(Command::Execution(cmd, _)) => {
168 let cmd = *cmd;
169 input
170 .vm_mut()
171 .stack_push(token, error::OperationKind::Execution);
172 let err_or = cmd(token, input);
173 input.vm_mut().stack_pop();
174 err_or
175 }
176 Some(Command::Variable(cmd)) => {
177 let cmd = cmd.clone();
178 let scope = S::variable_assignment_scope_hook(input.state_mut());
179 cmd.set_value_using_input(token, input, scope)
180 }
181 Some(Command::CharacterTokenAlias(token_value)) => {
182 // TODO: should add tests for when this is begin group and end group.
183 input.back(Token::new_from_value(*token_value, token.trace_key()));
184 Ok(())
185 }
186 Some(Command::Expansion(_, _)) | Some(Command::Macro(_)) => {
187 H::unexpanded_expansion_command(input, token)
188 }
189 Some(Command::Character(c)) => {
190 let token = Token::new_other(*c, token.trace_key()); // Remove
191 H::character_handler(input, token, *c)
192 }
193 Some(Command::MathCharacter(c)) => {
194 H::math_character_handler(input, token, *c)
195 }
196 Some(Command::Font(font)) => {
197 let font = *font;
198 let scope =
199 TexlangState::variable_assignment_scope_hook(input.state_mut());
200 let internal = &mut input.vm_mut().internal;
201 match scope {
202 groupingmap::Scope::Local => {
203 // If this is the first font assignment in this group,
204 // save the current value to the top of the stack. It will
205 // be restored from here when the group ends.
206 let current_font = internal.current_font;
207 if let Some(top) = internal.fonts_save_stack.last_mut() {
208 if top.is_none() {
209 *top = Some(current_font);
210 }
211 }
212 }
213 groupingmap::Scope::Global => {
214 // If this is a global font assignment, clear the stack
215 // entirely so that no font will be restored when groups end.
216 for font_or in &mut internal.fonts_save_stack {
217 *font_or = None;
218 }
219 }
220 }
221 internal.current_font = font;
222 input.state_mut().enable_font_hook(font);
223 Ok(())
224 }
225 None => H::undefined_command_handler(input, token),
226 }
227 }
228 Value::BeginGroup(_) => {
229 input.begin_group();
230 Ok(())
231 }
232 Value::EndGroup(_) => input.end_group(token),
233 Value::MathShift(c)
234 | Value::AlignmentTab(c)
235 | Value::Parameter(c)
236 | Value::Superscript(c)
237 | Value::Subscript(c)
238 | Value::Space(c)
239 | Value::Letter(c)
240 | Value::Other(c) => H::character_handler(input, token, c),
241 };
242 if let Err(signal) = r {
243 return signal;
244 }
245 }
246 }
247
248 pub(crate) fn shutdown(&mut self) -> ShutdownSignal {
249 self.internal.shutdown_status.transition_to_normal();
250 ShutdownSignal {}
251 }
252 pub(crate) fn fatal_error<E: error::TexError>(&mut self, err: E) -> ShutdownSignal {
253 let err: Box<dyn error::TexError> = Box::new(err);
254 let traced = error::TracedTexError::new(
255 err,
256 &self.internal.tracer,
257 &self.internal.cs_name_interner,
258 self.generate_stack_trace(),
259 );
260 self.internal.shutdown_status.transition_to_error(traced);
261 ShutdownSignal {}
262 }
263 pub(crate) fn error<E: error::TexError>(&mut self, err: E) -> txl::Result<()> {
264 let err: Box<dyn error::TexError> = Box::new(err);
265 let traced = error::TracedTexError::new(
266 err,
267 &self.internal.tracer,
268 &self.internal.cs_name_interner,
269 self.generate_stack_trace(),
270 );
271 match self.state.recoverable_error_hook(traced) {
272 Ok(_) => Ok(()),
273 Err(err) => {
274 let traced = error::TracedTexError::new(
275 err,
276 &self.internal.tracer,
277 &self.internal.cs_name_interner,
278 self.generate_stack_trace(),
279 );
280 self.internal.shutdown_status.transition_to_error(traced);
281 Err(ShutdownSignal {})
282 }
283 }
284 }
285}
286
287#[derive(Debug)]
288struct EndOfGroupError {
289 trace: token::Token,
290}
291
292impl error::TexError for EndOfGroupError {
293 fn kind(&self) -> error::Kind {
294 error::Kind::Token(self.trace)
295 }
296
297 fn title(&self) -> String {
298 "there is no group to end".into()
299 }
300}
301
302/// The Texlang virtual machine.
303pub struct VM<S> {
304 /// The state
305 pub state: S,
306
307 /// The commands map
308 pub commands_map: command::Map<S>,
309
310 /// The working directory which is used as the root for relative file paths
311 ///
312 /// This is [None] if the working directory could not be determined.
313 pub working_directory: Option<std::path::PathBuf>,
314
315 internal: Internal<S>,
316}
317
318/// Mutable references to different parts of the VM.
319pub struct Parts<'a, S> {
320 pub state: &'a mut S,
321 pub cs_name_interner: &'a mut token::CsNameInterner,
322 pub tracer: &'a mut trace::Tracer,
323}
324
325/// Implementations of this trait may be used as the state in a Texlang VM.
326///
327/// The most important thing to know about this trait is that it has no required methods.
328/// For any type it can be implemented trivially:
329/// ```
330/// # use texlang::traits::TexlangState;
331/// struct SomeNewType;
332///
333/// impl TexlangState for SomeNewType {}
334/// ```
335///
336/// Methods of the trait are invoked at certain points when the VM is running,
337/// and in general offer a way of customizing the behavior of the VM.
338/// The trait methods are all dispatched statically, which is important for performance.
339pub trait TexlangState: Sized {
340 /// Get the cat code for the provided character.
341 ///
342 /// The default implementation returns the cat code used in plain TeX.
343 fn cat_code(&self, c: char) -> types::CatCode {
344 types::CatCode::PLAIN_TEX_DEFAULTS
345 .get(c as usize)
346 .copied()
347 .unwrap_or_default()
348 }
349
350 /// Get current end line char, or [None] if it's undefined.
351 ///
352 /// The default implementation returns `Some(\r)`.
353 fn end_line_char(&self) -> Option<char> {
354 Some('\r')
355 }
356
357 /// Get the em width for the current font.
358 ///
359 /// The default implementation returns `12pt`.
360 fn em_width(&self) -> common::Scaled {
361 common::Scaled::ONE * 12
362 }
363
364 /// Get the ex height for the current font.
365 ///
366 /// The default implementation returns `12pt`.
367 fn ex_height(&self) -> common::Scaled {
368 common::Scaled::ONE * 12
369 }
370
371 /// Get the current magnification ratio (e.g. value of \mag).
372 ///
373 /// The default implementation returns `1000`, which corresponds to
374 /// no magnification.
375 fn magnification_ratio(&self) -> i32 {
376 1000
377 }
378
379 /// Hook that is invoked after a TeX macro is expanded.
380 ///
381 /// This hook is designed to support the `\tracingmacros` primitive.
382 fn post_macro_expansion_hook(
383 token: Token,
384 input: &ExpansionInput<Self>,
385 tex_macro: &texmacro::Macro,
386 arguments: &[&[Token]],
387 reversed_expansion: &[Token],
388 ) {
389 _ = (token, input, tex_macro, arguments, reversed_expansion);
390 }
391
392 /// Hook that potentially overrides the expansion of a command.
393 ///
394 /// This hook is invoked before an expandable token is expanded.
395 /// If the result of the hook is a non-empty, that result is considered the expansion of
396 /// the token
397 /// The result of the hook is not expanded before being returned.
398 ///
399 /// This hook is designed to support the `\noexpand` primitive.
400 fn expansion_override_hook(
401 token: token::Token,
402 input: &mut ExpansionInput<Self>,
403 tag: Option<command::Tag>,
404 ) -> txl::Result<Option<Token>> {
405 _ = (token, input, tag);
406 Ok(None)
407 }
408
409 /// Hook that determines the scope of a variable assignment.
410 ///
411 /// This hook is designed to support the \global and \globaldefs commands.
412 fn variable_assignment_scope_hook(state: &mut Self) -> groupingmap::Scope {
413 _ = state;
414 groupingmap::Scope::Local
415 }
416
417 /// Hook that determines what to do when a recoverable error occurs.
418 ///
419 /// If the hook returns `Ok(())` then the recovery process should run.
420 /// If the hook returns an error, then that error should be returned from the enclosing
421 /// function and propagated through the VM.
422 ///
423 /// Note that there is no requirement that an error returned from this hook
424 /// is the same as the error provided to the hook.
425 /// For example, when Knuth's TeX is running in batch mode errors are
426 /// logged but otherwise ignored.
427 /// However if 100 such errors occur, the interpreter fails.
428 /// To implement this in Texlang, the result of this function would be `Ok(())`
429 /// for the first 99 errors,
430 /// but after the 100th error a "too many errors" error would be returned from the hook.
431 /// Note that the returned error in this case is not the 100th error itself.
432 fn recoverable_error_hook(
433 &self,
434 error: error::TracedTexError,
435 ) -> Result<(), Box<dyn error::TexError>> {
436 _ = self;
437 Err(error.error)
438 }
439
440 /// Hook that is invoked when a font is enabled.
441 ///
442 /// For example, after the TeX snippet `\the \textfont 1`, this hook
443 /// is invoked for the font stored in `\textfont 1`.
444 /// The hook is also called if a font needs to be reenabled after
445 /// a group ends.
446 ///
447 /// The default implementation is a no-op.
448 fn enable_font_hook(&mut self, font: font::Id) {
449 _ = font
450 }
451
452 /// Returns whether the command corresponding to the provided tag references
453 /// the currnet font when provided as an argument to a variable.
454 ///
455 /// This is used to implement the `\font` primitive.
456 fn is_current_font_command(&self, tag: command::Tag) -> bool {
457 _ = tag;
458 false
459 }
460}
461
462impl TexlangState for () {}
463
464impl<S: Default> VM<S> {
465 /// Create a new VM with the provided built-in commands.
466 ///
467 /// If the state type satisfies the [`HasDefaultBuiltInCommands`] trait,
468 /// and you are using the default built-ins,
469 /// use the [`VM::new`] method instead.
470 pub fn new_with_built_in_commands(built_in_commands: HashMap<&str, BuiltIn<S>>) -> VM<S> {
471 let mut internal = Internal::new(Default::default());
472 let built_in_commands = built_in_commands
473 .into_iter()
474 .map(|(key, value)| (internal.cs_name_interner.get_or_intern(key), value))
475 .collect();
476 VM {
477 state: Default::default(),
478 commands_map: command::Map::new(built_in_commands),
479 internal,
480 working_directory: match std::env::current_dir() {
481 Ok(path_buf) => Some(path_buf),
482 Err(err) => {
483 println!("failed to determine the working directory: {err}");
484 None
485 }
486 },
487 }
488 }
489}
490
491impl<S: Default + HasDefaultBuiltInCommands> VM<S> {
492 /// Create a new VM.
493 pub fn new() -> VM<S> {
494 VM::<S>::new_with_built_in_commands(S::default_built_in_commands())
495 }
496}
497
498impl<S: Default + HasDefaultBuiltInCommands> Default for VM<S> {
499 fn default() -> Self {
500 Self::new()
501 }
502}
503
504/// Deserialize a Texlang VM using the provided built-in commands.
505///
506/// If the state type satisfies the [`HasDefaultBuiltInCommands`] trait,
507/// and you are deserializing using the default built-ins,
508/// you don't need to use this function.
509/// You can use the serde deserialize trait directly.
510/// See the [`serde` submodule](serde) for more information on deserialization.
511#[cfg(feature = "serde")]
512impl<'de, S: ::serde::Deserialize<'de>> VM<S> {
513 pub fn deserialize_with_built_in_commands<D: ::serde::Deserializer<'de>>(
514 deserializer: D,
515 built_in_commands: HashMap<&str, BuiltIn<S>>,
516 ) -> Result<Self, D::Error> {
517 serde::deserialize(deserializer, built_in_commands)
518 }
519}
520
521/// States that implement this trait have a default set of built-in commands associated to them.
522///
523/// In general in Texlang, the same state type can be used with different sets of built-in
524/// commands.
525/// However in many situations the state type has a specific set of built-ins
526/// associated to it.
527/// For example, the state type corresponding to pdfTeX is associated with the set of built-ins
528/// provided by pdfTeX.
529///
530/// This trait is used to specify this association.
531/// The benefit is that creating new VMs and deserializing VMs is a bit easier
532/// because the built-in commands don't need to be provided explicitly.
533/// Moreover, if a state implements this trait the associated VM implements serde's deserialize trait.
534pub trait HasDefaultBuiltInCommands: TexlangState {
535 fn default_built_in_commands() -> HashMap<&'static str, BuiltIn<Self>>;
536}
537
538#[cfg(feature = "serde")]
539impl<'de, S: ::serde::Deserialize<'de> + HasDefaultBuiltInCommands> ::serde::Deserialize<'de>
540 for VM<S>
541{
542 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
543 where
544 D: ::serde::Deserializer<'de>,
545 {
546 let built_ins = S::default_built_in_commands();
547 serde::deserialize(deserializer, built_ins)
548 }
549}
550
551impl<S: TexlangState> VM<S> {
552 /// Add new source code to the VM.
553 ///
554 /// TeX input source code is organized as a stack.
555 /// Pushing source code onto the stack will mean it is executed first.
556 pub fn push_source<T1: Into<PathBuf>, T2: Into<String>>(
557 &mut self,
558 file_name: T1,
559 source_code: T2,
560 ) -> txl::Result<()> {
561 self.internal
562 .push_source(None, file_name.into(), source_code.into())
563 }
564}
565
566impl<S> VM<S> {
567 /// Clear all source code from the VM.
568 pub fn clear_sources(&mut self) {
569 self.internal.clear_sources()
570 }
571
572 /// Return a regular hash map with all the commands as they are currently defined.
573 ///
574 /// This function is extremely slow and is only intended to be invoked on error paths.
575 pub fn get_commands_as_map_slow(&self) -> HashMap<&str, BuiltIn<S>> {
576 let map_1: HashMap<CsName, BuiltIn<S>> = self.commands_map.to_hash_map_slow();
577 let mut map = HashMap::new();
578 for (cs_name, cmd) in map_1 {
579 let cs_name_str = match self.internal.cs_name_interner.resolve(cs_name) {
580 None => continue,
581 Some(cs_name_str) => cs_name_str,
582 };
583 map.insert(cs_name_str, cmd);
584 }
585 map
586 }
587
588 /// Return a reference to the control sequence name string interner.
589 ///
590 /// This interner can be used to resolve [CsName] types into regular strings.
591 #[inline]
592 pub fn cs_name_interner(&self) -> &CsNameInterner {
593 &self.internal.cs_name_interner
594 }
595 #[inline]
596 /// TODO: just put the CS name interner in the VM?
597 pub fn cs_name_interner_mut(&mut self) -> &mut CsNameInterner {
598 &mut self.internal.cs_name_interner
599 }
600
601 fn begin_group(&mut self) {
602 self.commands_map.begin_group();
603 self.internal.save_stack.push(Default::default());
604 self.internal.fonts_save_stack.push(None);
605 }
606
607 pub fn trace(&self, token: Token) -> trace::SourceCodeTrace {
608 self.internal
609 .tracer
610 .trace(token, &self.internal.cs_name_interner)
611 }
612
613 pub fn trace_end_of_input(&self) -> trace::SourceCodeTrace {
614 self.internal.tracer.trace_end_of_input()
615 }
616
617 /// Returns the number of current sources on the source stack
618 pub fn num_current_sources(&self) -> usize {
619 self.internal.sources.len() + 1
620 }
621
622 pub fn generate_stack_trace(&self) -> Vec<error::StackTraceElement> {
623 self.internal
624 .execution_stack
625 .iter()
626 .map(|(op_kind, token)| error::StackTraceElement {
627 context: *op_kind,
628 token: *token,
629 trace: self
630 .internal
631 .tracer
632 .trace(*token, &self.internal.cs_name_interner),
633 })
634 .collect()
635 }
636 pub(crate) fn stack_push(&mut self, token: Token, op_kind: error::OperationKind) {
637 self.internal.execution_stack.push((op_kind, token));
638 }
639 pub(crate) fn stack_pop(&mut self) {
640 self.internal.execution_stack.pop();
641 }
642 pub fn current_font(&self) -> font::Id {
643 self.internal.current_font
644 }
645}
646
647impl<S: TexlangState> VM<S> {
648 fn end_group(&mut self, token: token::Token) -> txl::Result<()> {
649 // Restore commands
650 match self.commands_map.end_group() {
651 Ok(()) => (),
652 Err(_) => return Err(self.fatal_error(EndOfGroupError { trace: token })),
653 }
654 // Restore variable values
655 let group = self.internal.save_stack.pop().unwrap();
656 group.restore(ExecutionInput::new(self));
657 // Restore fonts
658 if let Some(font) = self.internal.fonts_save_stack.pop().unwrap() {
659 self.internal.current_font = font;
660 self.state.enable_font_hook(font);
661 }
662 Ok(())
663 }
664}
665
666/// Parts of the VM that are private.
667// We have serde(bound="") because otherwise serde tries to put a `Default` bound on S.
668#[cfg_attr(
669 feature = "serde",
670 derive(::serde::Serialize, ::serde::Deserialize),
671 serde(bound = "")
672)]
673struct Internal<S> {
674 // The sources form a stack. We store the top element directly on the VM
675 // for performance reasons.
676 current_source: Source,
677 sources: Vec<Source>,
678
679 cs_name_interner: CsNameInterner,
680
681 tracer: trace::Tracer,
682
683 // Token buffers are thrown away in serialization - there's nothing we need to keep.
684 #[cfg_attr(feature = "serde", serde(skip))]
685 token_buffers: std::collections::BinaryHeap<TokenBuffer>,
686
687 // The save stack is handled manually in (de)serialization.
688 // We need to use special logic in combination with the command map in order to serialize the
689 // variable pointers that are in the stack.
690 #[cfg_attr(feature = "serde", serde(skip))]
691 save_stack: Vec<variable::SaveStackElement<S>>,
692
693 current_font: font::Id,
694 fonts_save_stack: Vec<Option<font::Id>>,
695 execution_stack: Vec<(error::OperationKind, Token)>,
696
697 // We assume the VM is never saved during shutdown.
698 #[cfg_attr(feature = "serde", serde(skip))]
699 shutdown_status: ShutdownStatus,
700}
701
702impl<S> Internal<S> {
703 fn new(cs_name_interner: CsNameInterner) -> Self {
704 Internal {
705 current_source: Default::default(),
706 sources: Default::default(),
707 cs_name_interner,
708 tracer: Default::default(),
709 token_buffers: Default::default(),
710 save_stack: Default::default(),
711 current_font: font::Id::NULL,
712 fonts_save_stack: Default::default(),
713 execution_stack: Default::default(),
714 shutdown_status: Default::default(),
715 }
716 }
717}
718impl<S: TexlangState> Internal<S> {
719 fn push_source(
720 &mut self,
721 token: Option<Token>,
722 file_name: PathBuf,
723 source_code: String,
724 ) -> txl::Result<()> {
725 let trace_key_range =
726 self.tracer
727 .register_source_code(token, trace::Origin::File(file_name), &source_code);
728 let mut new_source = Source::new(source_code, trace_key_range);
729 std::mem::swap(&mut new_source, &mut self.current_source);
730 // TODO: if the current top source is empty, we should skip this.
731 // Check this is working by looking at the JSON serialization.
732 self.sources.push(new_source);
733 Ok(())
734 }
735
736 fn end_current_file(&mut self) {
737 self.current_source.root.end()
738 }
739}
740impl<S> Internal<S> {
741 fn clear_sources(&mut self) {
742 self.current_source = Default::default();
743 self.sources.clear();
744 }
745
746 #[inline]
747 fn push_expansion(&mut self, expansion: &[Token]) {
748 self.current_source
749 .expansions
750 .extend(expansion.iter().rev());
751 }
752
753 #[inline]
754 fn expansions(&self) -> &Vec<Token> {
755 &self.current_source.expansions
756 }
757
758 #[inline]
759 fn expansions_mut(&mut self) -> &mut Vec<Token> {
760 &mut self.current_source.expansions
761 }
762
763 fn pop_source(&mut self) -> bool {
764 // We should set the current_source to be Default::default() if there is no additional source.
765 // Check this is working by looking at the JSON serialization.
766 match self.sources.pop() {
767 None => false,
768 Some(source) => {
769 self.current_source = source;
770 true
771 }
772 }
773 }
774}
775
776#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
777struct Source {
778 expansions: Vec<Token>,
779 root: lexer::Lexer,
780}
781
782impl Source {
783 pub fn new(source_code: String, trace_key_range: trace::KeyRange) -> Source {
784 Source {
785 expansions: Vec::with_capacity(32),
786 root: lexer::Lexer::new(source_code, trace_key_range),
787 }
788 }
789}
790
791impl Default for Source {
792 fn default() -> Self {
793 Source::new("".into(), trace::KeyRange::empty())
794 }
795}
796
797#[derive(Default)]
798struct TokenBuffer(Vec<Token>);
799
800impl PartialEq for TokenBuffer {
801 fn eq(&self, other: &Self) -> bool {
802 self.0.capacity() == other.0.capacity()
803 }
804}
805
806impl Eq for TokenBuffer {}
807
808impl PartialOrd for TokenBuffer {
809 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
810 Some(self.cmp(other))
811 }
812}
813
814impl Ord for TokenBuffer {
815 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
816 self.0.capacity().cmp(&other.0.capacity())
817 }
818}
819
820/// A signal that the VM is shutting down.
821///
822/// A value of this type is returned in the error payload of
823/// the [`Result`](crate::prelude::Result) of Texlang commands and basically all other Texlang functions.
824/// The only thing to do with the signal is to propagate it up the
825/// Rust call stack using Rust's `?` operator.
826/// Eventually the signal will reach the main VM loop, and the VM will stop.
827///
828/// The stop signal should _not_ be ignored or otherwise "handled".
829/// For example, this code is incorrect:
830///
831/// ```
832/// # use texlang::token;
833/// # use texlang::vm;
834/// # use texlang::traits::*;
835/// # use texlang::prelude as txl;
836/// fn execution_primitive_fn<S: TexlangState>(
837/// token: token::Token,
838/// input: &mut vm::ExecutionInput<S>,
839///) -> txl::Result<()> {
840/// let i = match i32::parse(input) {
841/// Ok(i) => i,
842/// Err(_shutdown_signal) => {
843/// // This is incorrect - the shutdown signal must be propagated!
844/// 0
845/// }
846/// };
847/// println!["Parsed integer {i}"];
848/// Ok(())
849/// }
850/// ```
851///
852/// In this case the VM will eventually panic when it realizes that the shutdown was ignored.
853/// The correct code is this:
854///
855/// ```
856/// # use texlang::token;
857/// # use texlang::vm;
858/// # use texlang::traits::*;
859/// # use texlang::prelude as txl;
860/// fn execution_primitive_fn<S: TexlangState>(
861/// token: token::Token,
862/// input: &mut vm::ExecutionInput<S>,
863///) -> txl::Result<()> {
864/// let i = i32::parse(input)?;
865/// println!["Parsed integer {i}"];
866/// Ok(())
867/// }
868/// ```
869/// ## Generating the shutdown signal
870///
871/// The signal can originate either with a fatal error,
872/// or from a TeX control
873/// sequence that wants to stop execution (e.g. the `\end` primitive).
874#[derive(Debug)]
875pub struct ShutdownSignal {}
876
877#[derive(Debug, Default)]
878enum ShutdownStatus {
879 /// The VM is not shutting down.
880 #[default]
881 None,
882 /// The VM is shuting down for an expected reason.
883 Normal,
884 /// The VM is shuting down because of a fatal error.
885 Error(error::TracedTexError),
886}
887
888impl ShutdownStatus {
889 fn transition_to_normal(&mut self) {
890 if !matches!(self, ShutdownStatus::None) {
891 panic!("shutdown signal ignored")
892 }
893 *self = ShutdownStatus::Normal;
894 }
895 fn transition_to_error(&mut self, err: error::TracedTexError) {
896 if !matches!(self, ShutdownStatus::None) {
897 panic!("shutdown signal ignored")
898 }
899 *self = ShutdownStatus::Error(err);
900 }
901 fn take(&mut self) -> ShutdownStatus {
902 let mut s = ShutdownStatus::None;
903 std::mem::swap(self, &mut s);
904 s
905 }
906}
907
908/// Helper trait for implementing the component pattern in Texlang.
909///
910/// The component pattern is a ubiquitous design pattern in Texlang.
911/// It is used when implementing TeX commands that require state.
912/// An example of a stateful TeX command is `\year`, which needs to store the current year somewhere.
913///
914/// When the component pattern is used, a stateful TeX command
915/// can have a single implementation that
916/// is used by multiple TeX engines built with Texlang.
917/// Additionally, a specific TeX engine can compose many different
918/// stateful TeX commands together without worrying about conflicts between their state.
919/// The component pattern is Texlang's main solution to the problem of
920/// global mutable state that is pervasive in the original implementation of TeX.
921///
922/// In the component pattern, the state
923/// needed by a specific command like `\year` is isolated in a _component_, which is a concrete
924/// Rust type like a struct.
925/// This Rust type is the generic type `C` in the trait.
926/// The stateful command (e.g. `\year`) is defined in the same Rust module as the component.
927/// The internals of the component are made private to the module it is defined in.
928/// This means the state can only be mutated by the command (or commands) implemented in the module.
929///
930/// In order to function, the command needs to have access to an instance of the component in which
931/// the command will maintain its state.
932/// The `HasComponent` trait enforces this.
933/// Any VM state type that contains the component can implement the trait.
934/// The Rust code defining the
935/// command specifies the trait in its trait bounds, and uses the trait to access the component.
936///
937/// The pattern enables Texlang code to be composed as follows.
938/// Different VM states can include the same component and thus reuse the same commands.
939/// Combining multiple commands into one state just involves having the
940/// VM state include all of the relevant components.
941///
942/// Notes:
943///
944/// - In general state is shared by multiple commands. Such commands must be defined in the
945/// same Rust module to support this.
946/// For example, `\countdef` shares state with `\count`,
947/// and they are implemented together.
948///
949/// - Commands don't necessarily have state: for example, `\def`, `\advance` and `\the`.
950/// These commands
951/// are defined without trait bounds on the state, and work automatically with any TeX
952/// software built with Texlang.
953///
954/// - The easiest way to include a component in the state is to make it a direct field
955/// of the state.
956/// In this case the [implement_has_component] macro can be used to easily implement the
957/// trait.
958/// The Texlang standard library uses this approach.
959///
960/// ## The [TexlangState] requirement
961///
962/// This trait requires that the type also implements [TexlangState].
963/// This is only to reduce the number of trait bounds that need to be explicitly
964/// specified when implementing TeX commands.
965/// In general every command needs to have a bound of the form `S: TexlangState`.
966/// Commands that have a `HasComponent` bound don't need to include this other bound explicitly.
967pub trait HasComponent<C>: TexlangState {
968 /// Return a immutable reference to the component.
969 fn component(&self) -> &C;
970
971 /// Return a mutable reference to the component.
972 fn component_mut(&mut self) -> &mut C;
973}
974
975/// This macro is for implementing the [HasComponent] trait in the special (but common)
976/// case when the state is a struct and the component is a direct field of the struct.
977///
978/// ## Examples
979///
980/// Implementing a single component:
981///
982/// ```
983/// # mod library_1{
984/// # pub struct Component;
985/// # }
986/// # use texlang::vm::implement_has_component;
987/// # use texlang::traits::*;
988/// #
989/// struct MyState {
990/// component: library_1::Component,
991/// }
992///
993/// impl TexlangState for MyState {}
994///
995/// implement_has_component![MyState{
996/// component: library_1::Component,
997/// }];
998/// ```
999///
1000/// Implementing multiple components:
1001///
1002/// ```
1003/// # mod library_1{
1004/// # pub struct Component;
1005/// # }
1006/// # mod library_2{
1007/// # pub struct Component;
1008/// # }
1009/// # use texlang::vm::implement_has_component;
1010/// # use texlang::traits::*;
1011/// #
1012/// struct MyState {
1013/// component_1: library_1::Component,
1014/// component_2: library_2::Component,
1015/// }
1016///
1017/// impl TexlangState for MyState {}
1018///
1019/// implement_has_component![MyState{
1020/// component_1: library_1::Component,
1021/// component_2: library_2::Component,
1022/// }];
1023/// ```
1024#[macro_export]
1025macro_rules! implement_has_component {
1026 ($type: path {
1027 $( $field: ident: $component: path ),+ $(,)?
1028 }) => {
1029 $(
1030 impl ::texlang::vm::HasComponent<$component> for $type {
1031 #[inline]
1032 fn component(&self) -> &$component {
1033 &self.$field
1034 }
1035 #[inline]
1036 fn component_mut(&mut self) -> &mut $component {
1037 &mut self.$field
1038 }
1039 }
1040 )*
1041 };
1042}
1043
1044pub use implement_has_component;