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}