Skip to main content

retex_engine/
lib.rs

1//! The retex TeX engine: a clean-room implementation of LuaTeX's
2//! typesetting machine.
3//!
4//! The [`Engine`] holds all state of a TeX run. Its methods are spread
5//! over modules following the parts of *TeX: The Program*:
6//!
7//! * [`print`] and [`error`] — terminal and log output, error recovery;
8//! * [`hash`], [`eqtb`], [`save`] — control sequences, equivalents, grouping;
9//! * [`input`] — the input stack and the tokenizer (`get_next`);
10//! * [`expand`], [`cond`], [`scan`] — expansion, conditionals, scanning;
11//! * [`prefixed`] — assignments;
12//! * [`maincontrol`] — the chief executive.
13
14#![forbid(unsafe_code)]
15#![allow(clippy::too_many_lines)]
16
17pub mod align;
18pub mod backend;
19mod boxnote;
20pub mod build;
21pub mod clock;
22pub mod cmd;
23mod cmd_gen;
24pub mod codes;
25pub mod cond;
26pub mod diag;
27pub mod dirs;
28pub mod display;
29pub mod dvi;
30pub mod eqtb;
31pub mod error;
32pub mod expand;
33pub mod expansion;
34pub mod files;
35pub mod fonts;
36pub mod format;
37pub mod glue;
38pub mod hash;
39pub mod hmode;
40pub mod hyph;
41pub mod image;
42pub mod init;
43pub mod input;
44pub mod linebreak;
45pub mod maincontrol;
46pub mod math;
47pub mod mathdisplay;
48pub mod mathparams;
49pub mod meaning;
50pub mod misc;
51pub mod mlist;
52pub mod nodeproc;
53pub mod nodes;
54pub mod pack;
55pub mod page;
56pub mod pagenote;
57pub mod pdfext;
58pub(crate) mod pdfship;
59pub mod prefixed;
60pub mod primitives;
61pub mod primitives_gen;
62pub mod print;
63pub mod protrusion;
64pub mod save;
65pub mod scan;
66pub mod shipout;
67pub mod showbox;
68pub mod snapshot;
69pub mod synctex;
70pub mod the;
71pub mod timing;
72pub mod token;
73pub mod typeset;
74pub mod utf8x;
75
76use cmd::Cmd;
77use cond::CondState;
78use eqtb::Eqtb;
79use error::ErrorState;
80use files::FileState;
81use hash::{CsId, CsTable};
82use input::InputState;
83use print::Printer;
84use save::SaveStack;
85use token::Token;
86
87/// Callbacks from the engine into an embedded Lua interpreter.
88///
89/// The engine does not depend on Lua. `\directlua` and related primitives
90/// call this trait, which `retex-lua` implements. The host is shared
91/// (`Rc`), so nested calls (Lua → TeX → Lua) reach it too.
92pub trait LuaHost {
93    /// Runs a chunk of Lua code for `\directlua` (or `\latelua`, etc.).
94    /// `name` is the chunk name. Text printed with `tex.print` must be
95    /// pushed back into the engine's input by the host.
96    fn run(&self, engine: &mut Engine, name: &str, code: &[u8]) -> Result<(), String>;
97
98    /// Data to store in the format file (e.g. `lua.bytecode`).
99    fn dump(&self, _engine: &mut Engine) -> Vec<u8> {
100        Vec::new()
101    }
102
103    /// Restores data stored by [`dump`](Self::dump).
104    fn undump(&self, _engine: &mut Engine, _data: &[u8]) -> Result<(), String> {
105        Ok(())
106    }
107
108    /// Calls callback `name` without arguments, ignoring its results
109    /// (`pre_dump`, `stop_run`, …). Does nothing if none is registered.
110    fn call_simple(&self, _engine: &mut Engine, _name: &str) -> Result<(), String> {
111        Ok(())
112    }
113
114    /// Whether a Lua function is registered for callback `name`.
115    fn has_callback(&self, _name: &str) -> bool {
116        false
117    }
118
119    /// Calls the `define_font` callback. Returns the id of the font it
120    /// defined, or `None` if the font could not be loaded.
121    fn define_font(&self, _engine: &mut Engine, _name: &[u8], _size: i32) -> Option<u32> {
122        None
123    }
124
125    /// Calls a node-list filter callback (`pre_linebreak_filter`,
126    /// `hpack_filter`, `post_linebreak_filter`, …) on the list starting at
127    /// `head`. Returns the new head; [`ListFilter::Flush`] means the list
128    /// must be discarded.
129    fn list_filter(
130        &self,
131        _engine: &mut Engine,
132        _callback: &str,
133        head: nodes::NodeRef,
134        _group: &str,
135    ) -> ListFilter {
136        ListFilter::Keep(head)
137    }
138
139    /// Calls the `mlist_to_hlist` callback on math list `head` in style
140    /// `style` (0–7); `None` if the callback failed or none is set.
141    fn mlist_to_hlist(
142        &self,
143        _engine: &mut Engine,
144        _head: nodes::NodeRef,
145        _style: i32,
146        _penalties: bool,
147    ) -> Option<nodes::NodeRef> {
148        None
149    }
150
151    /// Runs shell command `cmd` under the shell escape rules (as Lua's
152    /// `io.popen` does) and returns its output; `None` if it may not run.
153    fn pipe_read(&self, _engine: &mut Engine, _cmd: &[u8]) -> Option<Vec<u8>> {
154        None
155    }
156
157    /// Calls the `input_level_string` callback with input-stack depth `n`;
158    /// `None` if it returned no string.
159    fn input_level_string(&self, _engine: &mut Engine, _n: i64) -> Option<Vec<u8>> {
160        None
161    }
162
163    /// Calls the `glyph_info` callback for glyph node `p`: `Some(text)` is
164    /// what to print, `None` means the callback returned no string.
165    fn glyph_info(&self, _engine: &mut Engine, _p: nodes::NodeRef) -> Option<Vec<u8>> {
166        None
167    }
168}
169
170/// Result of a node-list filter callback.
171#[derive(Clone, Copy, Debug, PartialEq, Eq)]
172pub enum ListFilter {
173    /// Continue with this list (possibly the original head).
174    Keep(nodes::NodeRef),
175    /// The callback returned `false`: discard the list.
176    Flush,
177}
178
179impl Engine {
180    /// Whether the Lua callback `name` is set.
181    pub fn has_callback(&self, name: &str) -> bool {
182        self.lua.as_ref().is_some_and(|h| h.has_callback(name))
183    }
184
185    /// Runs a node-list filter callback if one is registered (the hook
186    /// typesetting code calls at LuaTeX's callback points).
187    pub fn run_list_filter(
188        &mut self,
189        callback: &str,
190        head: nodes::NodeRef,
191        group: &str,
192    ) -> ListFilter {
193        match self.lua.clone() {
194            Some(h) if h.has_callback(callback) => h.list_filter(self, callback, head, group),
195            _ => ListFilter::Keep(head),
196        }
197    }
198}
199
200impl Engine {
201    /// Converts a finished formula's math list into an hlist, through the
202    /// `mlist_to_hlist` callback when one is set (LuaTeX calls it for the
203    /// outermost list of each formula and equation number; observed: not
204    /// for an empty one).
205    pub fn formula_to_hlist(
206        &mut self,
207        head: nodes::NodeRef,
208        style: i32,
209        penalties: bool,
210    ) -> nodes::NodeRef {
211        if head.is_null() {
212            return nodes::NodeRef::NULL;
213        }
214        if let Some(h) = self.lua.clone()
215            && h.has_callback("mlist_to_hlist")
216        {
217            if let Some(r) = h.mlist_to_hlist(self, head, style, penalties) {
218                if !r.is_null() {
219                    self.nodes.set_prev(r, nodes::NodeRef::NULL);
220                }
221                return r;
222            }
223            return nodes::NodeRef::NULL;
224        }
225        self.mlist_to_hlist(head, style, penalties)
226    }
227
228    /// Prints the input level before a line of macro tracing through the
229    /// `input_level_string` callback (LaTeX's `\tracingstacklevels`);
230    /// returns false if no callback is set. A newline in the string ends
231    /// the line.
232    pub fn print_input_level(&mut self) -> bool {
233        let Some(h) = self.lua.clone() else {
234            return false;
235        };
236        if !h.has_callback("input_level_string") {
237            return false;
238        }
239        let n = self.input.stack.len().saturating_sub(1) as i64;
240        if let Some(s) = h.input_level_string(self, n) {
241            for (i, part) in s.split(|&b| b == b'\n').enumerate() {
242                if i > 0 {
243                    self.printer.print_ln();
244                }
245                self.printer.print_bytes(part);
246            }
247        }
248        true
249    }
250
251    /// Runs the `glyph_info` callback for glyph `p` when one is set:
252    /// `None` without a callback, `Some(None)` when it returned no string.
253    pub fn run_glyph_info(&mut self, p: nodes::NodeRef) -> Option<Option<Vec<u8>>> {
254        match self.lua.clone() {
255            Some(h) if h.has_callback("glyph_info") => Some(h.glyph_info(self, p)),
256            _ => None,
257        }
258    }
259}
260
261/// A one-shot callback into the host, run with the engine borrowed.
262pub type EngineHook = Box<dyn FnOnce(&mut Engine)>;
263
264/// Shows diagnostics before the terminal is read (see
265/// [`Engine::prompt_hook`]).
266pub type PromptHook = Box<dyn FnMut(&[diag::Diagnostic], &str) -> bool>;
267
268/// The global state of a TeX run.
269pub struct Engine {
270    /// Control-sequence names.
271    pub hash: CsTable,
272    /// The table of equivalents.
273    pub eqtb: Eqtb,
274    /// The save stack and grouping state.
275    pub save: SaveStack,
276    /// The input stack.
277    pub input: InputState,
278    /// Terminal, log and `\write` output.
279    pub printer: Printer,
280    /// Error-handling state.
281    pub err: ErrorState,
282    /// Conditionals.
283    pub cond: CondState,
284    /// Files: `\openin`/`\read`, `\openout`, input file stack.
285    pub files: FileState,
286    /// The file finder.
287    pub kpse: Option<retex_kpath::Shared>,
288    /// The Lua interpreter, when available.
289    pub lua: Option<std::rc::Rc<dyn LuaHost>>,
290
291    /// The current command code (`cur_cmd`).
292    pub cur_cmd: Cmd,
293    /// The current modifier (`cur_chr`).
294    pub cur_chr: u32,
295    /// The current control sequence (`cur_cs`); [`hash::NO_CS`] for
296    /// characters.
297    pub cur_cs: CsId,
298    /// The current token (`cur_tok`).
299    pub cur_tok: Token,
300
301    /// The value most recently scanned (`cur_val`).
302    pub cur_val: i32,
303    /// The glue most recently scanned, when `cur_val_level` is glue.
304    pub cur_glue: glue::Glue,
305    /// The token list most recently scanned, when `cur_val_level` is tokens.
306    pub cur_toks: Option<token::TokenList>,
307    /// The level of `cur_val`.
308    pub cur_val_level: scan::ValLevel,
309
310    /// Well-known control sequences.
311    pub cs: init::KnownCs,
312    /// Whether the run is in IniTeX mode.
313    pub ini_version: bool,
314    /// Shell escape mode (see [`init::Options::shell_escape`]).
315    pub shell_escape: u8,
316    /// The format identification printed in the banner.
317    pub format_ident: String,
318    /// The job name, once known.
319    pub job_name: Option<Vec<u8>>,
320    /// Token saved by `\afterassignment` (`after_token`).
321    pub after_token: Token,
322    /// Number of nested expansions (LuaTeX's `expand_depth_count`).
323    pub expand_depth: u32,
324    /// The current mode (copy of the innermost nest level's mode).
325    pub mode: maincontrol::Mode,
326    /// The mode last shown by `show_cur_cmd_chr`.
327    pub shown_mode: maincontrol::Mode,
328    /// The format name (`\formatname`).
329    pub format_name: String,
330    /// Node memory.
331    pub nodes: nodes::Nodes,
332    /// The semantic nest; the last entry is the current list.
333    pub nest: Vec<maincontrol::ListState>,
334    /// Loaded fonts.
335    pub fonts: fonts::FontTable,
336    /// The `long_state` of the macro being called.
337    pub long_state: Cmd,
338    /// The control sequence being defined (for diagnostics).
339    pub def_ref_cs: CsId,
340    /// The partial token list printed by `runaway`.
341    pub runaway_list: Vec<Token>,
342    /// The control sequence last made by `\csname` (`\lastnamedcs`).
343    pub last_cs_name: CsId,
344    /// Frozen copies made by `\primitive`.
345    pub frozen_prims: std::collections::HashMap<Vec<u8>, CsId>,
346    /// Mark texts.
347    pub marks: typeset::Marks,
348    /// Set when a `\read` line has been consumed.
349    pub input_read_finished: bool,
350    /// Whether the long `\errmessage` help has been shown.
351    pub long_help_seen: bool,
352    /// `\deadcycles`.
353    pub dead_cycles: i32,
354    /// `\insertpenalties`.
355    pub insert_penalties: i32,
356    /// `\badness`.
357    pub last_badness: i32,
358    /// Token lists show a macro parameter character once, not doubled
359    /// (`\luaescapestring`).
360    pub single_mac_param: bool,
361    /// The direction the next box made by `hpack` or `vpackage` gets
362    /// (from `dir`/`bdir` of a box specification), or -1. It is set before
363    /// packing because over- and underfull box reports show it.
364    pub pack_dir: i8,
365    /// `\randomseed`: the seed `\setrandomseed` last gave (its absolute
366    /// value), or the one chosen at startup.
367    pub random_seed: i32,
368    /// The state of the random number generator (METAFONT's, see
369    /// [`retex_arith::random`]) behind `\uniformdeviate`, `\normaldeviate`
370    /// and the `tex` library's randomizers.
371    pub randoms: retex_arith::random::Randoms,
372    /// `\lastxpos`.
373    pub last_x_pos: i32,
374    /// `\lastypos`.
375    pub last_y_pos: i32,
376    /// The magnification frozen by `prepare_mag`.
377    pub mag_set: i32,
378    /// The font dimension located by `find_font_dimen`.
379    pub font_dimen_loc: Option<(u32, u32)>,
380    /// `\showboxdepth` in effect while displaying boxes.
381    pub depth_threshold: i32,
382    /// `\showboxbreadth` in effect while displaying boxes.
383    pub breadth_max: i32,
384    /// The paragraph-ending control sequence (`\partokenname`).
385    pub par_token_cs: CsId,
386    /// Whether `\dump` ended the run.
387    pub dump_requested: bool,
388    /// Integer parameters set from the command line when the job starts
389    /// ([`init::Options::int_params`]).
390    pub start_ints: Vec<(u32, i32)>,
391    /// `-output-comment`.
392    pub output_comment: Option<String>,
393    /// The job's start time (`\time`, the banner, the PDF creation date).
394    pub start_time: clock::StartTime,
395    /// The page builder.
396    pub page: typeset::PageState,
397    /// Lua functions registered for `\luafunction` (by number).
398    pub lua_functions: std::collections::HashMap<i32, u32>,
399    /// Called to store the format when `\dump` is executed.
400    pub format_hook: Option<EngineHook>,
401    /// Called at the end of the run to finish output files.
402    pub finish_hook: Option<EngineHook>,
403    /// Non-immediate `\write`s waiting for shipout.
404    pub deferred_writes: Vec<(i32, token::TokenList)>,
405    /// Text directions opened in the current box: (group level,
406    /// direction), innermost last (see [`dirs`]).
407    pub text_dirs: Vec<(u16, i8)>,
408    /// Box resources to write after the page or form being written, one
409    /// list per open page or form.
410    pub pending_forms: Vec<Vec<u32>>,
411    /// Objects referred to (`\pdfextension refobj`) in the box resources
412    /// being written, one list per open form.
413    pub form_objects: Vec<Vec<pdfext::PdfObject>>,
414    /// The `text_dirs` of the enclosing boxes.
415    pub outer_text_dirs: Vec<Vec<(u16, i8)>>,
416    /// The math and text directions outside the open formulas, with the
417    /// save level of each formula's group.
418    pub outer_math_dirs: Vec<(u16, i32, i32)>,
419    /// Contents of files written by `\write`, for the pass driver.
420    pub written_sink: files::WrittenSink,
421    /// State of the typesetting routines.
422    pub ts: typeset::TsState,
423    /// Tokens printed from Lua waiting to be pushed as a token list.
424    pub pending_lua_tokens: Option<Vec<Token>>,
425    /// Default output mode: errors and warnings recorded for the driver
426    /// (`None` in compatibility mode).
427    pub diagnostics: Option<Vec<diag::Diagnostic>>,
428    /// Default output mode, interactive: shows the diagnostics recorded
429    /// since the last prompt, and the prompt, before the terminal is read.
430    /// Returns `false` if it showed nothing (the error only repeats one
431    /// already reported): TeX then goes on without asking.
432    pub prompt_hook: Option<PromptHook>,
433    /// Default output mode: called when a page is shipped or a file opened,
434    /// to show progress and the errors recorded so far.
435    pub progress_hook: Option<fn(&mut Engine)>,
436    /// The number of diagnostics the prompt hook has shown.
437    pub diagnostics_shown: usize,
438    /// Whether the terminal was read (an error or a `\read` prompt).
439    pub prompted: bool,
440    /// Image resources (`\saveimageresource`), in the order saved (their
441    /// indices are their object numbers).
442    pub images: Vec<image::ImageResource>,
443    /// `\lastsavedimageresourceindex`.
444    pub last_image_index: i32,
445    /// `\lastsavedimageresourcepages`.
446    pub last_image_pages: i32,
447    /// Box resources (`\saveboxresource`), in the order saved (their
448    /// indices are their object numbers).
449    pub box_resources: Vec<image::BoxResource>,
450    /// `\lastsavedboxresourceindex`.
451    pub last_box_index: i32,
452    /// Scratch buffer for macro arguments (reused by `macro_call`).
453    pub arg_scratch: Vec<Token>,
454    /// Scratch buffer for the argument list of a macro call.
455    pub param_scratch: Vec<token::TokenList>,
456    /// The file of each `\write` stream opened (see [`snapshot`]).
457    pub write_paths: Vec<Option<std::path::PathBuf>>,
458    /// The macro at whose expansion [`snapshot_hook`](Self::snapshot_hook)
459    /// runs (LaTeX's `\document`).
460    pub snapshot_cs: Option<CsId>,
461    /// Called once, when `snapshot_cs` is expanded (see [`snapshot`]).
462    pub snapshot_hook: Option<snapshot::Hook>,
463    /// The file and line the user asked to edit at an error (`E`); the
464    /// program runs the editor at the end.
465    pub edit_request: Option<(Vec<u8>, i32)>,
466    /// The output file written at the end: name, pages and bytes.
467    pub output_written: Option<(String, u32, u64)>,
468    /// The line number reported while reading Lua output.
469    pub input_line_for_lua: i32,
470    /// Internal tokens made by `\pdfvariable`, by variable name.
471    pub backend_cs: std::collections::HashMap<&'static str, CsId>,
472    /// Nesting depth of local control loops (`tex.runtoks`).
473    pub local_level: u32,
474    /// The output backend (PDF writer), if any.
475    pub backend: Option<Box<dyn shipout::Backend>>,
476    /// The DVI writer (`\outputmode=0`).
477    pub dvi: dvi::DviWriter,
478    /// Hyphenation languages, by `\language` number.
479    pub languages: Vec<Option<retex_hyph::Language>>,
480    /// Document-level PDF data from `\pdfextension`.
481    pub pdf: pdfext::PdfState,
482    /// Current values of the colour stacks during shipout.
483    pub colorstack_values: std::collections::HashMap<i32, Vec<Vec<u8>>>,
484    /// Number of pages shipped out.
485    pub total_pages: u32,
486    /// Muglue values of `\Umath…` spacing parameters.
487    pub math_glues: Vec<glue::Glue>,
488    /// Whether the parameters set by the first formula are initialized
489    /// (see [`mathparams`]).
490    pub math_params_initialized: bool,
491    /// SyncTeX output.
492    pub synctex: synctex::Synctex,
493}
494
495impl std::fmt::Debug for Engine {
496    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
497        f.debug_struct("Engine")
498            .field("cur_cmd", &self.cur_cmd)
499            .field("cur_chr", &self.cur_chr)
500            .finish_non_exhaustive()
501    }
502}