1use std::rc::Rc;
6
7use crate::Engine;
8use crate::cmd::Cmd;
9use crate::input::TokKind;
10use crate::primitives_gen as pg;
11use crate::token::{Token, TokenList};
12
13#[allow(missing_docs)]
15pub mod ext {
16 pub const OPEN: u32 = 0;
17 pub const WRITE: u32 = 1;
18 pub const CLOSE: u32 = 2;
19 pub const IMMEDIATE: u32 = 4;
20 pub const DEFERRED: u32 = 5;
21 pub const SPECIAL: u32 = 6;
22 pub const SAVE_BOX_RESOURCE: u32 = 7;
23 pub const USE_BOX_RESOURCE: u32 = 8;
24 pub const SAVE_IMAGE_RESOURCE: u32 = 9;
25 pub const USE_IMAGE_RESOURCE: u32 = 10;
26 pub const END_LOCAL_CONTROL: u32 = 11;
27 pub const DVI_EXTENSION: u32 = 12;
28 pub const PDF_EXTENSION: u32 = 13;
29}
30
31fn j_is_terminal(j: i32, open: bool) -> bool {
33 j >= 0 && !((0..=127).contains(&j) && open)
34}
35
36impl Engine {
37 pub fn record_latex_warning(&mut self, text: &str) {
40 let Some((origin, message, line)) = crate::diag::parse_latex_warning(text) else {
41 return;
42 };
43 let mut d = crate::diag::Diagnostic::new(crate::diag::Severity::Warning, message);
44 d.origin = Some(origin);
45 if let Some((file, cur, _, _)) = self.source_location() {
46 d.file = Some(file);
47 d.line = Some(line.unwrap_or(cur));
48 }
49 if let Some(list) = self.diagnostics.as_mut() {
50 list.push(d);
51 }
52 }
53
54 pub fn do_extension(&mut self) {
56 match self.cur_chr {
57 ext::IMMEDIATE => {
58 self.get_x_token();
59 if self.cur_cmd == Cmd::Extension {
60 match self.cur_chr {
61 ext::OPEN => {
62 self.scan_stream_and_open(true);
63 return;
64 }
65 ext::WRITE => {
66 let (stream, list) = self.scan_write();
67 self.write_out(stream, &list);
68 return;
69 }
70 ext::CLOSE => {
71 self.scan_int();
72 let n = self.cur_val;
73 self.close_write_stream(n);
74 return;
75 }
76 ext::PDF_EXTENSION => {
77 self.pdf_extension(true);
78 return;
79 }
80 ext::SAVE_IMAGE_RESOURCE => {
81 self.save_image_resource(true);
82 return;
83 }
84 ext::SAVE_BOX_RESOURCE => {
85 self.save_box_resource(true);
86 return;
87 }
88 _ => {}
89 }
90 }
91 self.back_input();
92 }
93 ext::OPEN => self.scan_stream_and_open(false),
94 ext::WRITE => {
95 let (stream, list) = self.scan_write();
96 self.append_ext_whatsit(crate::nodes::Whatsit::Write { stream, data: list });
97 }
98 ext::CLOSE => {
99 self.scan_limited_stream();
100 let stream = self.cur_val as u32;
101 self.append_ext_whatsit(crate::nodes::Whatsit::Close { stream });
102 }
103 ext::SPECIAL => {
104 let toks = self.scan_toks_expanded();
105 let data =
106 self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
107 self.append_ext_whatsit(crate::nodes::Whatsit::Special { data });
108 }
109 ext::PDF_EXTENSION => self.pdf_extension(false),
110 ext::SAVE_IMAGE_RESOURCE => self.save_image_resource(false),
111 ext::USE_IMAGE_RESOURCE => self.use_image_resource(),
112 ext::SAVE_BOX_RESOURCE => self.save_box_resource(false),
113 ext::USE_BOX_RESOURCE => self.use_box_resource(),
114 ext::END_LOCAL_CONTROL => {}
115 _ => {
116 let (c, r) = (self.cur_cmd, self.cur_chr);
117 let s = self.print_to_string(|e| e.print_cmd_chr(c, r));
118 self.not_implemented(&String::from_utf8_lossy(&s));
119 }
120 }
121 }
122
123 fn scan_stream_and_open(&mut self, immediate: bool) {
124 self.scan_limited_stream();
125 let n = self.cur_val;
126 self.scan_optional_equals();
127 let name = self.scan_file_name();
128 if immediate {
129 self.open_write_stream(n, &name);
130 } else {
131 self.append_ext_whatsit(crate::nodes::Whatsit::Open {
132 stream: n as u32,
133 name,
134 });
135 }
136 }
137
138 fn scan_limited_stream(&mut self) {
140 self.scan_int();
141 let n = self.cur_val;
142 if !(0..=127).contains(&n) {
143 self.print_err("Bad number");
144 self.printer.print_str(" (");
145 self.printer.print_int(i64::from(n));
146 self.printer.print_char(')' as u32);
147 self.help(&[
148 "Since I expected to read a number between 0 and 127,",
149 "I changed this one to zero.",
150 ]);
151 self.int_error_plain();
152 self.cur_val = 0;
153 }
154 }
155
156 fn int_error_plain(&mut self) {
158 self.error();
159 }
160
161 pub fn append_ext_whatsit(&mut self, w: crate::nodes::Whatsit) {
163 let sub = whatsit_subtype(&w);
164 let p = self.new_node(
165 crate::nodes::id::WHATSIT,
166 sub,
167 crate::nodes::NodeData::Whatsit(Box::new(w)),
168 );
169 self.tail_append(p);
170 }
171
172 pub fn open_write_stream(&mut self, n: i32, name: &[u8]) {
175 if !(0..=127).contains(&n) {
176 return;
177 }
178 let mut s = String::from_utf8_lossy(name).into_owned();
179 if std::path::Path::new(&s).extension().is_none() {
180 s.push_str(".tex");
181 }
182 let mut path = std::path::PathBuf::from(&s);
183 if let Some(dir) = &self.files.output_dir {
184 path = dir.join(path);
185 }
186 match std::fs::File::create(&path) {
187 Ok(f) => {
188 let w = crate::files::RecordingWriter::new(f, path.clone(), self.written_sink());
189 if self.write_paths.len() <= n as usize {
190 self.write_paths.resize(n as usize + 1, None);
191 }
192 self.write_paths[n as usize] = Some(path.clone());
193 self.printer.open_write(n as u32, Box::new(w));
194 if self.printer.log_opened() {
197 let mut line = format!("\n\\openout{n} = ").into_bytes();
198 line.extend_from_slice(s.as_bytes());
199 line.push(b'\n');
200 self.printer.log_raw(&line);
201 }
202 }
203 Err(_) => {
204 self.prompt_file_name(name, "output file name", ".tex");
205 }
206 }
207 }
208
209 pub fn close_write_stream(&mut self, n: i32) {
211 if (0..=127).contains(&n) {
212 self.printer.close_write(n as u32);
213 }
214 }
215
216 fn scan_write(&mut self) -> (i32, TokenList) {
218 self.scan_int();
219 let n = self.cur_val;
220 let list = self.scan_general_text();
221 (n, list)
222 }
223
224 pub fn write_out(&mut self, stream: i32, list: &TokenList) {
226 let mut toks: Vec<Token> = Vec::with_capacity(list.len() + 2);
228 toks.push(Token::RIGHT_BRACE);
229 toks.push(Token::cs(self.cs.end_write));
230 let q: TokenList = Rc::from(toks);
231 self.ins_list(q);
232 self.begin_token_list(list.clone(), TokKind::WriteText);
233 let q: TokenList = Rc::from(vec![Token::LEFT_BRACE]);
234 self.ins_list(q);
235 let old_mode = self.mode;
236 self.mode = crate::maincontrol::Mode::None;
237 self.cur_cs = self.cs.end_write;
238 let save_def = self.def_ref_cs;
239 self.def_ref_cs = self.cs.end_write;
240 let expanded = self.scan_toks(false, true);
241 self.def_ref_cs = save_def;
242 self.get_token();
243 if self.cur_tok != Token::cs(self.cs.end_write) {
244 self.print_err("Unbalanced write command");
245 self.help(&[
246 "On this page there's a \\write with fewer real {'s than }'s.",
247 "I can't handle that very well; good luck.",
248 ]);
249 self.error();
250 loop {
251 self.get_token();
252 if self.cur_tok == Token::cs(self.cs.end_write) {
253 break;
254 }
255 }
256 }
257 self.mode = old_mode;
258 let old = self.printer.selector;
259 if self.diagnostics.is_some()
260 && j_is_terminal(stream, self.printer.write_open(stream as u32))
261 {
262 let text = self
263 .print_to_string(|e| e.token_show_list(&expanded, 0, expanded.len(), 10_000_000));
264 self.record_latex_warning(&String::from_utf8_lossy(&text));
265 }
266 let j = stream;
269 let to_file = (0..=127).contains(&j) && self.printer.write_open(j as u32);
270 if to_file {
271 self.printer.selector = crate::print::Selector::File(j as u32);
272 } else {
273 if j < 0 && old == crate::print::Selector::TermAndLog {
274 self.printer.selector = crate::print::Selector::LogOnly;
275 }
276 self.printer.print_nl("");
277 }
278 self.token_show_list(&expanded, 0, expanded.len(), 10_000_000);
279 self.printer.print_ln();
280 self.printer.selector = old;
281 }
282
283 pub fn do_normal(&mut self) {
285 match self.cur_chr {
286 0 => {
287 self.scan_int();
289 let n = self.cur_val as u32;
290 let table = self.eqtb.cur_cat_table();
291 let copy = self
292 .eqtb
293 .catcode_tables
294 .get(&table)
295 .cloned()
296 .unwrap_or_else(|| crate::eqtb::CodeTable::new(crate::eqtb::initex_catcode));
297 self.eqtb.catcode_tables.insert(n, copy);
298 }
299 1 => {
300 self.scan_int();
302 let n = self.cur_val as u32;
303 self.eqtb
304 .catcode_tables
305 .insert(n, crate::eqtb::CodeTable::new(crate::eqtb::initex_catcode));
306 }
307 2 => {
308 self.scan_int();
311 self.random_seed = self.cur_val.wrapping_abs();
312 self.randoms.init(self.cur_val);
313 }
314 3 => self.append_ext_whatsit(crate::nodes::Whatsit::SavePos),
315 4 | 5 => {
316 let data = self.scan_general_text();
318 self.append_ext_whatsit(crate::nodes::Whatsit::LateLua {
319 data: crate::nodes::LateData::Tokens(data),
320 name: Vec::new(),
321 });
322 }
323 6 => {
324 self.scan_font_ident();
327 let f = self.cur_val as u32;
328 self.scan_int();
329 let stretch = self.cur_val;
330 self.scan_int();
331 let shrink = self.cur_val;
332 self.scan_int();
333 let step = self.cur_val;
334 self.set_font_expansion(f, stretch, shrink, step);
335 if self.scan_keyword("autoexpand") {
336 self.scan_optional_space();
337 self.luatex_warning("font expansion", "autoexpand not supported");
338 }
339 }
340 _ => {
341 self.scan_font_ident();
342 }
343 }
344 }
345
346 pub fn scan_direction_spec(&mut self) -> i32 {
348 for (i, name) in ["TLT", "TRT", "LTL", "RTT"].iter().enumerate() {
349 if self.scan_keyword(name) {
350 self.scan_optional_space();
351 return i as i32;
352 }
353 }
354 self.get_x_token();
357 if self.cur_cmd == Cmd::AssignDir {
358 return self.dir_param(self.cur_chr);
359 }
360 self.back_input();
361 self.print_err("Bad direction");
362 self.help(&[]);
363 self.error();
364 0
365 }
366
367 pub fn assign_dir_param(&mut self, global: bool, p: u32, d: i32) {
370 let _ = global;
371 if (p as usize) < self.eqtb.dir_par.len() {
372 self.define(
373 false,
374 crate::eqtb::Loc::DirPar(p),
375 crate::eqtb::Val::Int(i64::from(d)),
376 );
377 }
378 }
379
380 pub fn dir_param(&self, p: u32) -> i32 {
382 self.eqtb.dir_par.get(p as usize).map_or(0, |s| s.val)
383 }
384
385 pub fn set_box_dir(&mut self, _n: u32, _d: i32) {}
387
388 pub fn uniform_deviate(&mut self, n: i32) -> i32 {
391 self.randoms.unif_rand(n)
392 }
393
394 pub fn normal_deviate(&mut self) -> i32 {
396 self.randoms.norm_rand()
397 }
398
399 pub fn immediate_assignment(&mut self, group: bool) {
401 if group {
402 let list = self.scan_general_text();
403 let _ = list;
404 } else {
405 self.get_x_token();
406 if self.cur_cmd > Cmd::MAX_NON_PREFIXED {
407 self.prefixed_command();
408 }
409 }
410 }
411
412 pub fn backend_expandable(&mut self) {
414 if self.cur_cmd == Cmd::Variable {
415 self.expand_backend_variable();
416 } else {
417 self.expand_backend_feedback();
418 }
419 }
420
421 pub fn last_saved_resource(&self, m: u32) -> i32 {
423 use crate::scan::last_item as li;
424 match m {
425 li::LAST_SAVED_IMAGE_RESOURCE_INDEX => self.last_image_index,
426 li::LAST_SAVED_IMAGE_RESOURCE_PAGES => self.last_image_pages,
427 li::LAST_SAVED_BOX_RESOURCE_INDEX => self.last_box_index,
428 _ => 0,
429 }
430 }
431
432 pub fn def_lua_call(&mut self, global: bool, protected: bool) {
435 self.get_r_token();
436 let p = self.cur_cs;
437 self.scan_optional_equals();
438 self.scan_int();
439 let cmd = if protected {
440 Cmd::LuaCall
441 } else {
442 Cmd::LuaExpandableCall
443 };
444 let m = crate::eqtb::Meaning::prim(cmd, self.cur_val as u32);
445 self.define(
446 global,
447 crate::eqtb::Loc::Cs(p),
448 crate::eqtb::Val::Meaning(m),
449 );
450 }
451
452 pub fn lua_code(&mut self, toks: &[Token]) -> Vec<u8> {
456 let par = self.hash.lookup(b"par").map(Token::cs);
457 let kept: Vec<Token>;
458 let toks = if par.is_some_and(|p| toks.contains(&p)) {
459 kept = toks.iter().copied().filter(|t| Some(*t) != par).collect();
460 &kept[..]
461 } else {
462 toks
463 };
464 self.print_to_string(|e| e.show_token_list(toks, 0, toks.len(), i64::MAX))
465 }
466
467 pub fn run_lua(&mut self, code: &[u8]) {
469 let Some(host) = self.lua.clone() else {
470 self.print_err("retex: \\directlua needs the Lua interpreter");
471 self.help(&[]);
472 self.error();
473 return;
474 };
475 let t0 = std::time::Instant::now();
476 let r = host.run(self, "\\directlua", code);
477 crate::timing::LUA.with(|a| a.add(t0));
478 if crate::timing::enabled() && t0.elapsed().as_millis() >= 5 {
479 let head = String::from_utf8_lossy(&code[..code.len().min(70)]).replace('\n', " ");
480 crate::timing::mark(&format!(
481 "slow Lua chunk ({:.1} ms): {head}",
482 t0.elapsed().as_secs_f64() * 1e3
483 ));
484 }
485 if let Err(msg) = r {
486 if std::env::var_os("RETEX_DEBUG_LUA").is_some() {
488 eprintln!(
489 "--- failing Lua chunk ---\n{}\n---",
490 String::from_utf8_lossy(code)
491 );
492 }
493 self.lua_error(&msg);
494 }
495 }
496
497 pub fn lua_error(&mut self, msg: &str) {
499 self.print_err("LuaTeX error ");
500 self.printer.print_str(msg);
501 self.help(&[
502 "The lua interpreter ran into a problem, so the",
503 "remainder of this lua chunk will be ignored.",
504 ]);
505 self.error();
506 }
507
508 pub fn call_lua_function(&mut self, n: i32) {
510 let code = format!("return lua.get_functions_table()[{n}]({n})");
512 self.run_lua(code.as_bytes());
513 }
514
515 pub fn call_lua_bytecode(&mut self, n: i32) {
517 let code = format!("return lua.bytecode[{n}]()");
518 self.run_lua(code.as_bytes());
519 }
520
521 pub fn lua_expandable_call(&mut self) {
523 let n = self.cur_chr as i32;
524 self.call_lua_function(n);
525 }
526
527 pub fn finish_output(&mut self) {
529 self.finish_backend();
530 if let Some(hook) = self.finish_hook.take() {
531 hook(self);
532 }
533 }
534
535 fn written_sink(&self) -> crate::files::WrittenSink {
537 self.written_sink.clone()
538 }
539
540 pub fn store_math_param_glue(&mut self, g: crate::glue::Glue) -> i32 {
542 self.math_glues.push(g);
543 (self.math_glues.len() - 1) as i32
544 }
545
546 pub fn math_param_glue(&self, v: i32) -> crate::glue::Glue {
548 self.math_glues
549 .get(v as usize)
550 .copied()
551 .unwrap_or(crate::glue::Glue::ZERO)
552 }
553}
554
555#[allow(dead_code)]
557const _PG: u32 = pg::assign_int::COUNT;
558
559impl Engine {
560 pub fn dimen_from_string(&mut self, s: &str) -> i64 {
563 let mut toks = crate::the::str_toks(s.as_bytes());
564 let stop = Token::cs(self.cs.frozen_relax);
565 toks.push(stop);
566 let depth = self.input.stack.len();
567 self.ins_list(Rc::from(toks));
568 self.scan_normal_dimen();
569 let v = self.cur_val;
570 while self.input.stack.len() > depth {
572 self.get_token();
573 if self.cur_tok == stop {
574 break;
575 }
576 }
577 i64::from(v)
578 }
579}
580
581fn whatsit_subtype(w: &crate::nodes::Whatsit) -> u16 {
583 use crate::nodes::{Whatsit, whatsit_sub as ws};
584 match w {
585 Whatsit::Open { .. } => ws::OPEN,
586 Whatsit::Write { .. } => ws::WRITE,
587 Whatsit::Close { .. } => ws::CLOSE,
588 Whatsit::Special { .. } => ws::SPECIAL,
589 Whatsit::LateLua { .. } => ws::LATE_LUA,
590 Whatsit::PdfLiteral { late, .. } => {
591 if *late {
592 ws::PDF_LATE_LITERAL
593 } else {
594 ws::PDF_LITERAL
595 }
596 }
597 Whatsit::SavePos => ws::SAVE_POS,
598 Whatsit::PdfColorstack { .. } => ws::PDF_COLORSTACK,
599 Whatsit::PdfSetmatrix { .. } => ws::PDF_SETMATRIX,
600 Whatsit::PdfSave => ws::PDF_SAVE,
601 Whatsit::PdfRestore => ws::PDF_RESTORE,
602 Whatsit::PdfAnnot { .. } => ws::PDF_ANNOT,
603 Whatsit::PdfStartLink { .. } => ws::PDF_START_LINK,
604 Whatsit::PdfEndLink => ws::PDF_END_LINK,
605 Whatsit::PdfDest { .. } => ws::PDF_DEST,
606 Whatsit::PdfRefobj { .. } => ws::PDF_REFOBJ,
607 Whatsit::Other { .. } => ws::USER_DEFINED,
608 }
609}