1use std::collections::HashMap;
4use std::rc::Rc;
5
6use crate::Engine;
7use crate::cmd::Cmd;
8use crate::cond::CondState;
9use crate::eqtb::{Eqtb, Meaning};
10use crate::error::{ErrorState, Interaction, JumpOut};
11use crate::files::FileState;
12use crate::fonts::{FontInfo, FontTable};
13use crate::hash::{CsId, CsTable, NO_CS};
14use crate::input::{InputState, LineSource};
15use crate::maincontrol::Mode;
16use crate::nodes::Nodes;
17use crate::primitives_gen::{self as pg, Group};
18use crate::print::{Printer, Selector};
19use crate::save::SaveStack;
20use crate::scan::ValLevel;
21use crate::token::Token;
22
23#[derive(Clone, Copy, Debug, Default)]
25pub struct KnownCs {
26 pub par: CsId,
28 pub frozen_protection: CsId,
30 pub frozen_cr: CsId,
32 pub frozen_end_group: CsId,
34 pub frozen_right: CsId,
36 pub frozen_fi: CsId,
38 pub frozen_end_template: CsId,
40 pub frozen_endv: CsId,
42 pub frozen_relax: CsId,
44 pub end_write: CsId,
46 pub frozen_dont_expand: CsId,
48 pub frozen_special: CsId,
50 pub frozen_null_font: CsId,
52 pub end_local: CsId,
54}
55
56#[derive(Clone, Debug)]
58pub struct Options {
59 pub ini: bool,
61 pub interaction: Interaction,
63 pub job_name: Option<String>,
65 pub output_dir: Option<std::path::PathBuf>,
67 pub first_line: String,
69 pub file_line_error: bool,
71 pub epoch: Option<i64>,
74 pub utc: bool,
76 pub shell_escape: u8,
79 pub line_lengths: (usize, usize, usize),
81}
82
83impl Default for Options {
84 fn default() -> Self {
85 Options {
86 ini: true,
87 interaction: Interaction::ErrorStop,
88 job_name: None,
89 output_dir: None,
90 first_line: String::new(),
91 file_line_error: false,
92 epoch: None,
93 utc: false,
94 shell_escape: 2,
95 line_lengths: (79, 79, 50),
96 }
97 }
98}
99
100#[derive(Clone, Copy, Debug, PartialEq, Eq)]
102pub enum Outcome {
103 Ended,
105 Dumped,
107 Aborted,
109}
110
111impl Engine {
112 pub fn new(options: &Options, term: Box<dyn std::io::Write>) -> Engine {
114 let mut printer = Printer::new(term);
115 printer.max_print_line = options.line_lengths.0;
116 printer.error_line = options.line_lengths.1;
117 printer.half_error_line = options.line_lengths.2;
118 let mut e = Engine {
119 hash: CsTable::new(),
120 eqtb: Eqtb::initex(),
121 save: SaveStack::default(),
122 input: InputState::default(),
123 printer,
124 err: ErrorState::default(),
125 cond: CondState::default(),
126 files: FileState::default(),
127 kpse: None,
128 lua: None,
129 cur_cmd: Cmd::Relax,
130 cur_chr: 0,
131 cur_cs: NO_CS,
132 cur_tok: Token(0),
133 cur_val: 0,
134 cur_glue: crate::glue::Glue::ZERO,
135 cur_toks: None,
136 cur_val_level: ValLevel::Int,
137 cs: KnownCs::default(),
138 ini_version: options.ini,
139 shell_escape: options.shell_escape,
140 format_ident: String::new(),
141 format_name: String::new(),
142 job_name: options.job_name.clone().map(String::into_bytes),
143 after_token: Token(0),
144 expand_depth: 0,
145 mode: Mode::Vertical,
146 shown_mode: Mode::None,
147 nodes: Nodes::new(),
148 nest: Vec::new(),
149 fonts: FontTable::default(),
150 long_state: Cmd::Call,
151 def_ref_cs: NO_CS,
152 runaway_list: Vec::new(),
153 last_cs_name: NO_CS,
154 frozen_prims: HashMap::new(),
155 marks: crate::typeset::Marks::default(),
156 input_read_finished: false,
157 long_help_seen: false,
158 dead_cycles: 0,
159 insert_penalties: 0,
160 last_badness: 0,
161 single_mac_param: false,
162 pack_dir: -1,
163 random_seed: 0,
164 randoms: retex_arith::random::Randoms::new(0),
165 last_x_pos: 0,
166 last_y_pos: 0,
167 mag_set: 0,
168 font_dimen_loc: None,
169 depth_threshold: 0,
170 breadth_max: 0,
171 par_token_cs: NO_CS,
172 dump_requested: false,
173 start_time: crate::clock::StartTime::default(),
174 page: crate::typeset::PageState::default(),
175 lua_functions: HashMap::new(),
176 format_hook: None,
177 finish_hook: None,
178 deferred_writes: Vec::new(),
179 text_dirs: Vec::new(),
180 outer_text_dirs: Vec::new(),
181 pending_forms: Vec::new(),
182 form_objects: Vec::new(),
183 written_sink: crate::files::WrittenSink::default(),
184 math_glues: Vec::new(),
185 math_params_initialized: false,
186 ts: crate::typeset::TsState::default(),
187 total_pages: 0,
188 colorstack_values: HashMap::new(),
189 pdf: crate::pdfext::PdfState::default(),
190 languages: Vec::new(),
191 backend: None,
192 dvi: Default::default(),
193 local_level: 0,
194 backend_cs: HashMap::new(),
195 pending_lua_tokens: None,
196 diagnostics: None,
197 output_written: None,
198 arg_scratch: Vec::new(),
199 param_scratch: Vec::new(),
200 write_paths: Vec::new(),
201 snapshot_cs: None,
202 snapshot_hook: None,
203 images: Vec::new(),
204 last_image_index: 0,
205 last_image_pages: 0,
206 box_resources: Vec::new(),
207 last_box_index: 0,
208 input_line_for_lua: 0,
209 };
210 e.err.interaction = options.interaction;
211 e.err.file_line_error = options.file_line_error;
212 e.files.output_dir = options.output_dir.clone();
213 e.files.command_line = options.first_line.clone().into_bytes();
214 e.init_params(options.epoch, options.utc);
215 e.init_known_cs();
216 e.fonts.fonts.push(FontInfo::null());
217 e.fonts.id_cs.push(e.cs.frozen_null_font);
218 e.push_nest(Mode::Vertical);
219 e.page.init(&mut e.nodes);
220 let ch = e.nest[0].head;
222 e.nodes.free(e.page.contrib_head);
223 e.page.contrib_head = ch;
224 e.ts.font_in_short_display = -1;
225 if options.ini {
226 e.install_group(Group::Tex, b"");
227 e.install_group(Group::Core, b"");
228 }
229 e.err.history = crate::error::History::Spotless;
230 e
231 }
232
233 fn init_params(&mut self, epoch: Option<i64>, utc: bool) {
235 let data = include_str!("../data/luatex-initex-params.txt");
236 for line in data.lines() {
237 let mut it = line.splitn(3, ' ');
238 let (Some(cmd), Some(name), Some(value)) = (it.next(), it.next(), it.next()) else {
239 continue;
240 };
241 let Some(p) = crate::primitives::lookup(name.as_bytes()) else {
242 continue;
243 };
244 match cmd {
245 "assign_int" => {
246 if let Ok(v) = value.parse::<i32>() {
247 self.eqtb.int_par[p.chr as usize].val = v;
248 }
249 }
250 "assign_dimen" => {
251 if let Ok(v) = value.parse::<i32>() {
252 self.eqtb.dimen_par[p.chr as usize].val = v;
253 }
254 }
255 _ => {}
256 }
257 }
258 self.start_time = crate::clock::StartTime::new(epoch, utc);
260 self.set_date_and_time();
261 self.random_seed = crate::clock::startup_random_seed();
262 self.randoms.init(self.random_seed);
263 }
264
265 fn set_date_and_time(&mut self) {
268 let c = self.start_time.civil();
269 use pg::assign_int as ip;
270 self.eqtb.int_par[ip::TIME as usize].val = (c.hour * 60 + c.minute) as i32;
271 self.eqtb.int_par[ip::DAY as usize].val = c.day as i32;
272 self.eqtb.int_par[ip::MONTH as usize].val = c.month as i32;
273 self.eqtb.int_par[ip::YEAR as usize].val = c.year as i32;
274 }
275
276 fn init_known_cs(&mut self) {
277 let h = &mut self.hash;
278 let par = h.lookup_or_insert(b"par");
279 let frozen = |h: &mut CsTable, n: &str| h.new_frozen(n.as_bytes());
280 let cs = KnownCs {
281 par,
282 frozen_protection: frozen(h, "inaccessible"),
283 frozen_cr: frozen(h, "cr"),
284 frozen_end_group: frozen(h, "endgroup"),
285 frozen_right: frozen(h, "right"),
286 frozen_fi: frozen(h, "fi"),
287 frozen_end_template: frozen(h, "endtemplate"),
288 frozen_endv: frozen(h, "endtemplate"),
289 frozen_relax: frozen(h, "relax"),
290 end_write: frozen(h, "endwrite"),
291 frozen_dont_expand: frozen(h, "notexpanded:"),
292 frozen_special: frozen(h, "special"),
293 frozen_null_font: frozen(h, "nullfont"),
294 end_local: frozen(h, "endlocalcontrol"),
295 };
296 self.cs = cs;
297 let set = |e: &mut Engine, id: CsId, m: Meaning| {
298 e.eqtb.ensure_cs(id);
299 e.eqtb.cs[id.0 as usize].val = m;
300 e.eqtb.cs[id.0 as usize].level = crate::eqtb::LEVEL_ONE;
301 };
302 let prim = |name: &str| {
303 let p = crate::primitives::lookup(name.as_bytes()).expect("primitive");
304 Meaning::prim(p.cmd, p.chr)
305 };
306 set(self, cs.frozen_cr, prim("cr"));
307 set(self, cs.frozen_end_group, prim("endgroup"));
308 set(self, cs.frozen_right, prim("right"));
309 set(self, cs.frozen_fi, prim("fi"));
310 set(self, cs.frozen_relax, prim("relax"));
311 set(self, cs.frozen_special, prim("special"));
312 set(self, cs.frozen_null_font, Meaning::prim(Cmd::SetFont, 0));
313 set(
314 self,
315 cs.frozen_end_template,
316 Meaning::prim(Cmd::EndTemplate, 0),
317 );
318 set(self, cs.frozen_endv, Meaning::prim(Cmd::Endv, 0));
319 set(
320 self,
321 cs.frozen_dont_expand,
322 Meaning::prim(Cmd::DontExpand, 0),
323 );
324 set(self, cs.end_write, Meaning::prim(Cmd::OuterCall, 0));
325 set(self, cs.frozen_protection, Meaning::UNDEFINED);
326 set(
327 self,
328 cs.end_local,
329 Meaning::prim(Cmd::Extension, crate::misc::ext::END_LOCAL_CONTROL),
330 );
331 self.par_token_cs = par;
332 }
333
334 pub fn install_group(&mut self, group: Group, prefix: &[u8]) {
336 let prims: Vec<_> = crate::primitives::group(group).collect();
337 for p in prims {
338 self.define_primitive(prefix, p.name.as_bytes());
339 }
340 }
341
342 pub fn define_primitive(&mut self, prefix: &[u8], name: &[u8]) -> bool {
346 let Some(p) = crate::primitives::lookup(name) else {
347 return false;
348 };
349 if p.cmd == Cmd::UndefinedCs {
350 return false;
351 }
352 let mut full = prefix.to_vec();
353 full.extend_from_slice(name);
354 let cs = self.hash.lookup_or_insert(&full);
355 if self.eqtb.meaning(cs).cmd != Cmd::UndefinedCs {
356 return false;
357 }
358 self.eqtb.ensure_cs(cs);
359 self.eqtb.cs[cs.0 as usize].val = Meaning::prim(p.cmd, p.chr);
360 self.eqtb.cs[cs.0 as usize].level = crate::eqtb::LEVEL_ONE;
361 true
362 }
363
364 pub fn run(&mut self) -> Outcome {
367 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
368 self.run_inner();
369 }));
370 match result {
371 Ok(()) => {
372 if self.dump_requested {
373 Outcome::Dumped
374 } else {
375 Outcome::Ended
376 }
377 }
378 Err(payload) => {
379 if payload.downcast_ref::<JumpOut>().is_some() {
380 Outcome::Aborted
381 } else {
382 std::panic::resume_unwind(payload);
383 }
384 }
385 }
386 }
387
388 fn run_inner(&mut self) {
389 let banner = format!(
390 "This is retex, Version {} (LuaTeX 1.22.0 compatible){}",
391 env!("CARGO_PKG_VERSION"),
392 if self.ini_version { " (INITEX)" } else { "" }
393 );
394 self.printer.term_raw(banner.as_bytes());
395 self.printer
396 .term_raw(b"\n restricted system commands enabled.\n");
397 self.printer.term_offset = 0;
398 self.printer.update_terminal();
399 let first = self.files.command_line.clone();
401 self.begin_file_reading(LineSource::Terminal, Vec::new(), false);
402 if let Some(crate::input::Level::Text(t)) = self.input.stack.last_mut() {
403 t.buf = crate::utf8x::chars(&first);
404 t.loc = 0;
405 t.state = crate::input::LexState::NewLine;
406 }
407 let elc = self.eqtb.int(pg::assign_int::ENDLINECHAR);
408 if (0..=0x10_FFFF).contains(&elc)
409 && let Some(crate::input::Level::Text(t)) = self.input.stack.last_mut()
410 {
411 t.buf.push(elc as u32);
412 }
413 self.printer.new_line_char = self.eqtb.int(pg::assign_int::NEWLINECHAR);
414 self.printer.selector = if self.err.interaction == Interaction::Batch {
415 Selector::NoPrint
416 } else {
417 Selector::TermOnly
418 };
419 let starts_with_escape = first
420 .first()
421 .is_some_and(|&b| self.eqtb.catcode(u32::from(b)) == 0);
422 if !first.is_empty() && !starts_with_escape {
423 self.start_input();
424 }
425 self.history_spotless();
426 self.main_control();
427 self.final_cleanup();
428 if self.dump_requested {
429 self.store_fmt_file();
430 }
431 self.close_files_and_terminate();
432 }
433
434 fn history_spotless(&mut self) {
435 if self.err.history == crate::error::History::FatalErrorStop {
436 self.err.history = crate::error::History::Spotless;
437 }
438 }
439
440 pub fn store_fmt_file(&mut self) {
442 if self.save.cur_level > crate::eqtb::LEVEL_ONE {
443 self.print_err("You can't dump inside a group");
444 self.help(&["`{...\\dump}' is a no-no."]);
445 self.fatal_error("*** (cannot \\dump inside a group)");
446 }
447 if let Some(h) = self.lua.clone()
450 && let Err(msg) = h.call_simple(self, "pre_dump")
451 {
452 self.lua_error(&msg);
453 }
454 let job =
455 String::from_utf8_lossy(self.job_name.as_deref().unwrap_or(b"texput")).into_owned();
456 let c = self.start_time.civil();
457 self.format_ident = format!(" (format={job} {}.{}.{})", c.year, c.month, c.day);
458 self.format_name = job.clone();
459 let mut path = std::path::PathBuf::from(format!("{job}.fmt"));
460 if let Some(dir) = &self.files.output_dir {
461 path = dir.join(path);
462 }
463 self.printer.selector = if self.err.interaction == Interaction::Batch {
464 Selector::LogOnly
465 } else {
466 Selector::TermAndLog
467 };
468 self.printer.print_nl("Beginning to dump on file ");
469 self.printer.print_bytes(path.to_string_lossy().as_bytes());
470 self.printer.print_nl("");
471 let ident = self.format_ident.clone();
472 self.printer.print_str(&ident);
473 let engine_section = self.dump_engine();
474 let lua_section = match self.lua.clone() {
475 Some(h) => h.dump(self),
476 None => Vec::new(),
477 };
478 if crate::timing::enabled() {
479 eprintln!(
480 "[format] {:>10} bytes Lua (lua.bytecode)",
481 lua_section.len()
482 );
483 }
484 let data = crate::format::assemble(&[("engine", &engine_section), ("lua", &lua_section)]);
485 self.printer.print_nl("");
486 self.printer.print_int(self.hash.len() as i64);
487 self.printer.print_str(" multiletter control sequences");
488 self.printer.print_nl("");
489 self.printer.print_int(self.fonts.fonts.len() as i64 - 1);
490 self.printer.print_str(" preloaded fonts");
491 if let Err(e) = std::fs::write(&path, &data) {
492 self.print_err("I can't write on file `");
493 self.printer.print_bytes(path.to_string_lossy().as_bytes());
494 self.printer.print_str("'.");
495 self.help_owned(vec![e.to_string()]);
496 self.error();
497 }
498 }
499
500 pub fn load_format(&mut self, data: &[u8]) -> Result<(), crate::format::FormatError> {
502 let sections = crate::format::disassemble(data)?;
503 crate::timing::mark("format: sections checked");
504 for (name, bytes) in §ions {
505 if *name == "engine" {
506 self.undump_engine(bytes)?;
507 }
508 }
509 self.set_date_and_time();
511 crate::timing::mark("format: engine state loaded");
512 if let Some(h) = self.lua.clone() {
513 for (name, bytes) in §ions {
514 if *name == "lua" {
515 h.undump(self, bytes)
516 .map_err(|_| crate::format::FormatError::Corrupt("lua section"))?;
517 }
518 }
519 }
520 crate::timing::mark("format: Lua state loaded");
521 self.format_name = self
522 .format_ident
523 .trim_start_matches(" (format=")
524 .split(' ')
525 .next()
526 .unwrap_or("")
527 .to_owned();
528 Ok(())
529 }
530
531 pub fn define_macro_str(&mut self, name: &str, body: &str) {
533 let cs = self.hash.lookup_or_insert(name.as_bytes());
534 let mut toks = vec![Token::END_MATCH];
535 toks.extend(crate::the::str_toks(body.as_bytes()));
536 self.eqtb.ensure_cs(cs);
537 self.eqtb.cs[cs.0 as usize].val = Meaning {
538 cmd: Cmd::Call,
539 chr: 0,
540 mac: Some(Rc::new(crate::token::Macro {
541 protected: false,
542 tokens: Rc::from(toks),
543 })),
544 };
545 }
546}