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;