texlang/command/
mod.rs

1//! Texlang commands API
2//!
3//! # Texcraft commands API
4//!
5//! One of the most important parts of any TeX engine is the primitives that it provides.
6//!  This documentation describes the *Texcraft commands API*,
7//! which is the mechanism by which TeX engines add new primitives.
8//!
9//! A note on terminology: *commands* can be categorized into primitives,
10//! which are implemented in the TeX engine, and user defined macros,
11//!  which are created in specific TeX documents using primitives like `\def`.
12//! We often use the word command and primitive interchangeably here because in the context
13//! of implementing TeX engines they’re basically synonymous.
14//! A TeX engine could theoretically provide a native user defined macro...but it’s unlikely.
15//!
16//! ## Expansion vs execution
17//!
18//! Expansion and execution commands seem similar because they both optionally
19//! read input tokens and then make changes to the VM.
20//! However the differences are pretty significant in practice:
21//!
22//! |                                          | Expansion | Execution
23//! |------------------------------------------|-----------|-----------
24//! Can read tokens from the input stream?     | Yes       | Yes
25//! Can add tokens to the input stream>        | Yes       | It’s possible, but the API discourages it.[^futurelet]
26//! Can make changes to the state?             | No        | Yes
27//! Is evaluated when tokens are only being expanded, like in `\edef` | Yes | No
28//!
29//!
30//! [^futurelet]: `\futurelet` is an example of an execution command that does this.
31//!
32
33use crate::prelude as txl;
34use crate::texmacro;
35use crate::token;
36use crate::types;
37use crate::variable;
38use crate::vm;
39use common::font;
40use std::num;
41use std::rc;
42use std::sync;
43
44pub(crate) mod map;
45
46pub use map::Map;
47
48/// The Rust type of expansion primitive functions.
49pub type ExpansionFn<S> =
50    fn(token: token::Token, input: &mut vm::ExpansionInput<S>) -> txl::Result<()>;
51
52/// The Rust type of execution primitive functions.
53pub type ExecutionFn<S> =
54    fn(token: token::Token, input: &mut vm::ExecutionInput<S>) -> txl::Result<()>;
55
56/// A TeX command.
57pub enum Command<S> {
58    /// An expansion primitive that is implemented in the engine.
59    ///
60    /// Examples: `\the`, `\ifnum`.
61    Expansion(ExpansionFn<S>, Option<Tag>),
62
63    /// A user defined macro.
64    ///
65    /// Examples: `\newcommand` and `\include` in LaTeX.
66    Macro(rc::Rc<texmacro::Macro>),
67
68    /// A non-expansion primitive that performs operations on the state.
69    ///
70    /// Examples: `\def`, `\par`.
71    Execution(ExecutionFn<S>, Option<Tag>),
72
73    /// A command that is used to reference a variable, like a parameter or a register.
74    ///
75    /// Such a command is *resolved* to get the variable using the function pointer it holds.
76    ///
77    /// Examples: `\count`, `\year`.
78    Variable(rc::Rc<variable::Command<S>>),
79
80    /// A command that aliases a character token.
81    ///
82    /// Depending on the context in which this command appears it may behave like a
83    ///   character (when typesetting) or like an unexpandable command (when parsing integers).
84    /// Created using `\let\cmd=<character>`.
85    CharacterTokenAlias(token::Value),
86
87    /// A command that references a character.
88    ///
89    /// These commands are generally created using `\countdef`.
90    /// In the main inner loop they result in a character being typeset.
91    /// In other contexts they are interpreted as numbers.
92    /// In Plain TeX, `\countdef 255` is used as a more efficient version of `\def{255 }`.
93    Character(char),
94
95    /// A command that references a math character.
96    ///
97    /// These commands are generally created using `\mathchardef`.
98    MathCharacter(types::MathCode),
99
100    /// A command that enables a font.
101    Font(font::Id),
102}
103
104impl<S> std::fmt::Display for Command<S> {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        match self {
107            Command::Expansion(_, _) => write![f, "an expansion command"],
108            Command::Macro(_) => write![f, "a user-defined macro"],
109            Command::Execution(_, _) => write![f, "an execution command"],
110            Command::Variable(_) => write![f, "a variable command"],
111            Command::CharacterTokenAlias(_) => write![f, "a character token alias"],
112            Command::Character(_) => write![f, "a character command"],
113            Command::MathCharacter(_) => write![f, "a math character command"],
114            Command::Font(_) => write![f, "a font command"],
115        }
116    }
117}
118
119impl<S> Command<S> {
120    /// Gets the tag associated to this command, or [None] if the command has no tag.
121    pub fn tag(&self) -> Option<Tag> {
122        match self {
123            Command::Expansion(_, tag) => *tag,
124            Command::Execution(_, tag) => *tag,
125            Command::Macro(_)
126            | Command::Variable(_)
127            | Command::CharacterTokenAlias(_)
128            | Command::Character(_)
129            | Command::MathCharacter(_)
130            | Command::Font(_) => None,
131        }
132    }
133}
134
135/// A built-in command. This is a command provided at VM initialization.
136///
137/// This struct is simply a combination of a [Command] and a documentation string for the command.
138/// It is used when providing the built-in commands for a VM.
139pub struct BuiltIn<S> {
140    cmd: Command<S>,
141    doc: Option<&'static str>,
142}
143
144impl<S> BuiltIn<S> {
145    /// Create a new expansion built-in command.
146    pub fn new_expansion(t: ExpansionFn<S>) -> BuiltIn<S> {
147        t.into()
148    }
149
150    /// Create a new expansion built-in command.
151    pub fn new_execution(t: ExecutionFn<S>) -> BuiltIn<S> {
152        t.into()
153    }
154
155    /// Create a new variable built-in command.
156    pub fn new_variable(cmd: variable::Command<S>) -> BuiltIn<S> {
157        Command::Variable(rc::Rc::new(cmd)).into()
158    }
159
160    /// Create a new font built-in command.
161    pub fn new_font(font: font::Id) -> BuiltIn<S> {
162        Command::Font(font).into()
163    }
164
165    /// Set the tag for this built-in command.
166    pub fn with_tag(mut self, tag: Tag) -> BuiltIn<S> {
167        match &mut self.cmd {
168            Command::Expansion(_, t) => *t = Some(tag),
169            Command::Execution(_, t) => *t = Some(tag),
170            Command::Macro(_)
171            | Command::Variable(_)
172            | Command::CharacterTokenAlias(_)
173            | Command::Character(_)
174            | Command::MathCharacter(_)
175            | Command::Font(_) => {
176                panic!("cannot add a tag to this type of command")
177            }
178        }
179        self
180    }
181
182    // Set the doc for this built-in command.
183    pub fn with_doc(mut self, doc: &'static str) -> BuiltIn<S> {
184        self.doc = Some(doc);
185        self
186    }
187
188    pub fn cmd(&self) -> &Command<S> {
189        &self.cmd
190    }
191
192    pub fn doc(&self) -> Option<&'static str> {
193        self.doc
194    }
195}
196
197// We need to implement Clone manually as the derived implementation requires S to be Clone.
198impl<S> Clone for Command<S> {
199    fn clone(&self) -> Self {
200        match self {
201            Command::Expansion(e, t) => Command::Expansion::<S>(*e, *t),
202            Command::Macro(m) => Command::Macro(m.clone()),
203            Command::Execution(e, t) => Command::Execution(*e, *t),
204            Command::Variable(v) => Command::Variable(v.clone()),
205            Command::CharacterTokenAlias(tv) => Command::CharacterTokenAlias(*tv),
206            Command::Character(c) => Command::Character(*c),
207            Command::MathCharacter(c) => Command::MathCharacter(*c),
208            Command::Font(font) => Command::Font(*font),
209        }
210    }
211}
212
213// We need to implement Clone manually as the derived implementation requires S to be Clone.
214impl<S> Clone for BuiltIn<S> {
215    fn clone(&self) -> Self {
216        Self {
217            cmd: self.cmd.clone(),
218            doc: self.doc,
219        }
220    }
221}
222
223impl<S> From<ExpansionFn<S>> for BuiltIn<S> {
224    fn from(cmd: ExpansionFn<S>) -> Self {
225        Command::Expansion(cmd, None).into()
226    }
227}
228
229impl<S> From<rc::Rc<texmacro::Macro>> for BuiltIn<S> {
230    fn from(cmd: rc::Rc<texmacro::Macro>) -> Self {
231        Command::Macro(cmd).into()
232    }
233}
234
235impl<S> From<ExecutionFn<S>> for BuiltIn<S> {
236    fn from(cmd: ExecutionFn<S>) -> Self {
237        Command::Execution(cmd, None).into()
238    }
239}
240
241impl<S> From<variable::Command<S>> for BuiltIn<S> {
242    fn from(cmd: variable::Command<S>) -> Self {
243        Command::Variable(rc::Rc::new(cmd)).into()
244    }
245}
246
247impl<S> From<Command<S>> for BuiltIn<S> {
248    fn from(cmd: Command<S>) -> Self {
249        BuiltIn { cmd, doc: None }
250    }
251}
252
253/// A tag is a piece of metadata that is optionally attached to a command.
254///
255/// Tags are used to implement certain TeX language semantics.
256/// An example is TeX conditionals.
257/// When a TeX conditional statement evaluates to false, the `\if` command must scan
258///     the input stream until it finds either an `\else` or `\fi` command.
259/// (The tokens scanned in this process are in the true branch of the conditional,
260///     and must thus be discarded.)
261/// Tags are the mechanism by which the scanning algorithm can
262///     determine if a token corresponds to an `\else` of `\fi` command.
263/// Concretely, both `\else` of `\fi` command have unique tags associated to them.
264/// When scanning the stream,
265///     if a token is a command token then the tag for the associated command is
266///     compared to the known tags for `\else` and `\fi`.
267/// If the tags match, the true branch is finished.
268///
269/// In general, TeX commands interface with the VM in two ways.
270/// The first most common way is when the main VM loop or expansion loop encounters a command.
271/// The loop invokes the command's associated Rust function.
272/// One can think of the Rust function as providing the behavior of the command in this context.
273///
274/// The second way is when a different command, like a conditional command, performs some operation
275///     that is dependent on the commands it reads out of the input stream.
276/// In this context the commands in the input stream provide behavior using tags.
277/// The `\else` command having the specific else tag results in the conditional branch processing completing.
278///
279/// Note that the same tag can be used for multiple commands,
280/// but each command can only have one tag.
281///
282/// ## Implementation details
283///
284/// Tags are non-zero 32 bit integers.
285/// The first tag created has value 1, the second tag has value 2, and so on.
286/// A global mutex is used to store the next tag value.
287/// Tags have the property that `Option<Tag>` takes up 4 bytes in memory.
288#[derive(PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord, Hash)]
289pub struct Tag(num::NonZeroU32);
290
291static NEXT_TAG_VALUE: sync::Mutex<u32> = sync::Mutex::new(1);
292
293impl Tag {
294    /// Creates a new unique tag.
295    ///
296    /// ```
297    /// # use texlang::command::Tag;
298    /// let tag_1 = Tag::new();
299    /// let tag_2 = Tag::new();
300    /// assert_ne!(tag_1, tag_2);
301    /// ```
302    // We suppress the clippy warning because creating a new tag is a global operation and
303    // shouldn't be done without explicit intention.
304    #[allow(clippy::new_without_default)]
305    pub fn new() -> Tag {
306        let mut n = NEXT_TAG_VALUE.lock().unwrap();
307        let tag = Tag(num::NonZeroU32::new(*n).unwrap());
308        *n = n.checked_add(1).unwrap();
309        tag
310    }
311}
312
313/// A static tag enables creating a tag in a static variable.
314///
315/// ```
316/// # use texlang::command::StaticTag;
317/// static TAG: StaticTag = StaticTag::new();
318///
319/// let first_get = TAG.get();
320/// let second_get = TAG.get();
321/// assert_eq!(first_get, second_get);
322/// ```
323pub struct StaticTag(std::sync::OnceLock<Tag>);
324
325impl Default for StaticTag {
326    fn default() -> Self {
327        StaticTag::new()
328    }
329}
330
331impl StaticTag {
332    /// Create a new static tag.
333    pub const fn new() -> StaticTag {
334        StaticTag(std::sync::OnceLock::new())
335    }
336
337    /// Get the actual [Tag] out of this [StaticTag].
338    /// Repeated calls to this function return the same tag.
339    ///
340    /// This is not a trivial getter.
341    /// The [Tag] is lazily constructed so even subsequent calls to this getter must do some work to check if the [Tag]
342    ///     exists or not.
343    /// For very hot code paths it is advised to cache the return value somewhere, for example in a relevant command's state.
344    pub fn get(&self) -> Tag {
345        *self.0.get_or_init(Tag::new)
346    }
347}
348
349/// A primitive key uniquely identifies a primitive.
350///
351/// If two commands have the same key, they are the same primitive (expansion, execution, or variable primitive)
352/// The function returns [None] if the command is not a primitive (a macro or a token alias).
353#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
354pub(crate) enum PrimitiveKey {
355    Execution(usize, Option<Tag>),
356    Expansion(usize, Option<Tag>),
357    Variable(variable::CommandKey),
358}
359
360impl PrimitiveKey {
361    pub(crate) fn new<S>(command: &Command<S>) -> Option<Self> {
362        match command {
363            Command::Expansion(f, tag) => Some(PrimitiveKey::Expansion(*f as usize, *tag)),
364            Command::Execution(f, tag) => Some(PrimitiveKey::Execution(*f as usize, *tag)),
365            Command::Variable(v) => Some(PrimitiveKey::Variable(v.key())),
366            Command::Macro(_)
367            | Command::CharacterTokenAlias(_)
368            | Command::Character(_)
369            | Command::MathCharacter(_)
370            | Command::Font(_) => None,
371        }
372    }
373}
374
375#[cfg(test)]
376mod tests {
377    use super::*;
378
379    #[test]
380    fn func_size() {
381        assert_eq!(std::mem::size_of::<Command<()>>(), 16);
382    }
383
384    static STATIC_TAG_1: StaticTag = StaticTag::new();
385    static STATIC_TAG_2: StaticTag = StaticTag::new();
386
387    #[test]
388    fn tag() {
389        let tag_1_val_1 = STATIC_TAG_1.get();
390        let tag_2_val_1 = STATIC_TAG_2.get();
391        let other_tag_1 = Tag::new();
392        let tag_1_val_2 = STATIC_TAG_1.get();
393        let tag_2_val_2 = STATIC_TAG_2.get();
394        let other_tag_2 = Tag::new();
395
396        assert_eq!(tag_1_val_1, tag_1_val_2);
397        assert_eq!(tag_2_val_1, tag_2_val_2);
398
399        assert_ne!(tag_1_val_1, tag_2_val_2);
400        assert_ne!(tag_1_val_1, other_tag_1);
401        assert_ne!(tag_1_val_1, other_tag_2);
402    }
403
404    #[test]
405    fn tag_size() {
406        assert_eq!(std::mem::size_of::<Option<Tag>>(), 4);
407    }
408}