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