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 let mut toks: Vec<Token> = Vec::with_capacity(list.len() + 2);
257 toks.push(Token::RIGHT_BRACE);
258 toks.push(Token::cs(self.cs.end_write));
259 let q: TokenList = Rc::from(toks);
260 self.ins_list(q);
261 self.begin_token_list(list.clone(), TokKind::WriteText);
262 let q: TokenList = Rc::from(vec![Token::LEFT_BRACE]);
263 self.ins_list(q);
264 let old_mode = self.mode;
265 self.mode = crate::maincontrol::Mode::None;
266 self.cur_cs = self.cs.end_write;
267 let save_def = self.def_ref_cs;
268 self.def_ref_cs = self.cs.end_write;
269 let expanded = self.scan_toks(false, true);
270 self.def_ref_cs = save_def;
271 self.get_token();
272 if self.cur_tok != Token::cs(self.cs.end_write) {
273 self.print_err("Unbalanced write command");
274 self.help(&[
275 "On this page there's a \\write with fewer real {'s than }'s.",
276 "I can't handle that very well; good luck.",
277 ]);
278 self.error();
279 loop {
280 self.get_token();
281 if self.cur_tok == Token::cs(self.cs.end_write) {
282 break;
283 }
284 }
285 }
286 self.mode = old_mode;
287 let old = self.printer.selector;
288 if self.diagnostics.is_some()
289 && j_is_terminal(stream, self.printer.write_open(stream as u32))
290 {
291 let text = self
292 .print_to_string(|e| e.token_show_list(&expanded, 0, expanded.len(), 10_000_000));
293 self.record_latex_warning(&String::from_utf8_lossy(&text));
294 }
295 let j = stream;
298 let to_file = (0..=127).contains(&j) && self.printer.write_open(j as u32);
299 if to_file {
300 self.printer.selector = crate::print::Selector::File(j as u32);
301 } else {
302 if j < 0 && old == crate::print::Selector::TermAndLog {
303 self.printer.selector = crate::print::Selector::LogOnly;
304 }
305 self.printer.print_nl("");
306 }
307 self.token_show_list(&expanded, 0, expanded.len(), 10_000_000);
308 self.printer.print_ln();
309 self.printer.selector = old;
310 }
311
312 pub fn do_normal(&mut self) {
314 match self.cur_chr {
315 0 => {
316 self.scan_int();
318 let n = self.cur_val as u32;
319 let table = self.eqtb.cur_cat_table();
320 let copy = self
321 .eqtb
322 .catcode_tables
323 .get(&table)
324 .cloned()
325 .unwrap_or_else(|| crate::eqtb::CodeTable::new(crate::eqtb::initex_catcode));
326 self.eqtb.catcode_tables.insert(n, copy);
327 }
328 1 => {
329 self.scan_int();
331 let n = self.cur_val as u32;
332 self.eqtb
333 .catcode_tables
334 .insert(n, crate::eqtb::CodeTable::new(crate::eqtb::initex_catcode));
335 }
336 2 => {
337 self.scan_int();
340 self.random_seed = self.cur_val.wrapping_abs();
341 self.randoms.init(self.cur_val);
342 }
343 3 => self.append_ext_whatsit(crate::nodes::Whatsit::SavePos),
344 4 | 5 => {
345 let data = self.scan_general_text();
347 self.append_ext_whatsit(crate::nodes::Whatsit::LateLua {
348 data: crate::nodes::LateData::Tokens(data),
349 name: Vec::new(),
350 });
351 }
352 6 => {
353 self.scan_font_ident();
356 let f = self.cur_val as u32;
357 self.scan_int();
358 let stretch = self.cur_val;
359 self.scan_int();
360 let shrink = self.cur_val;
361 self.scan_int();
362 let step = self.cur_val;
363 self.set_font_expansion(f, stretch, shrink, step);
364 if self.scan_keyword("autoexpand") {
365 self.scan_optional_space();
366 self.luatex_warning("font expansion", "autoexpand not supported");
367 }
368 }
369 _ => {
370 self.scan_font_ident();
371 }
372 }
373 }
374
375 pub fn scan_direction_spec(&mut self) -> i32 {
377 for (i, name) in ["TLT", "TRT", "LTL", "RTT"].iter().enumerate() {
378 if self.scan_keyword(name) {
379 self.scan_optional_space();
380 return i as i32;
381 }
382 }
383 self.get_x_token();
386 if self.cur_cmd == Cmd::AssignDir {
387 return self.dir_param(self.cur_chr);
388 }
389 self.back_input();
390 self.print_err("Bad direction");
391 self.help(&[]);
392 self.error();
393 0
394 }
395
396 pub fn assign_dir_param(&mut self, global: bool, p: u32, d: i32) {
399 let _ = global;
400 if (p as usize) < self.eqtb.dir_par.len() {
401 self.define(
402 false,
403 crate::eqtb::Loc::DirPar(p),
404 crate::eqtb::Val::Int(i64::from(d)),
405 );
406 }
407 }
408
409 pub fn dir_param(&self, p: u32) -> i32 {
411 self.eqtb.dir_par.get(p as usize).map_or(0, |s| s.val)
412 }
413
414 pub fn set_box_dir(&mut self, _n: u32, _d: i32) {}
416
417 pub fn uniform_deviate(&mut self, n: i32) -> i32 {
420 self.randoms.unif_rand(n)
421 }
422
423 pub fn normal_deviate(&mut self) -> i32 {
425 self.randoms.norm_rand()
426 }
427
428 pub fn immediate_assignment(&mut self, group: bool) {
430 if group {
431 let list = self.scan_general_text();
432 let _ = list;
433 } else {
434 self.get_x_token();
435 if self.cur_cmd > Cmd::MAX_NON_PREFIXED {
436 self.prefixed_command();
437 }
438 }
439 }
440
441 pub fn backend_expandable(&mut self) {
443 if self.cur_cmd == Cmd::Variable {
444 self.expand_backend_variable();
445 } else {
446 self.expand_backend_feedback();
447 }
448 }
449
450 pub fn last_saved_resource(&self, m: u32) -> i32 {
452 use crate::scan::last_item as li;
453 match m {
454 li::LAST_SAVED_IMAGE_RESOURCE_INDEX => self.last_image_index,
455 li::LAST_SAVED_IMAGE_RESOURCE_PAGES => self.last_image_pages,
456 li::LAST_SAVED_BOX_RESOURCE_INDEX => self.last_box_index,
457 _ => 0,
458 }
459 }
460
461 pub fn def_lua_call(&mut self, global: bool, protected: bool) {
464 self.get_r_token();
465 let p = self.cur_cs;
466 self.scan_optional_equals();
467 self.scan_int();
468 let cmd = if protected {
469 Cmd::LuaCall
470 } else {
471 Cmd::LuaExpandableCall
472 };
473 let m = crate::eqtb::Meaning::prim(cmd, self.cur_val as u32);
474 self.define(
475 global,
476 crate::eqtb::Loc::Cs(p),
477 crate::eqtb::Val::Meaning(m),
478 );
479 }
480
481 pub fn lua_code(&mut self, toks: &[Token]) -> Vec<u8> {
485 let par = self.hash.lookup(b"par").map(Token::cs);
486 let kept: Vec<Token>;
487 let toks = if par.is_some_and(|p| toks.contains(&p)) {
488 kept = toks.iter().copied().filter(|t| Some(*t) != par).collect();
489 &kept[..]
490 } else {
491 toks
492 };
493 self.print_to_string(|e| e.show_token_list(toks, 0, toks.len(), i64::MAX))
494 }
495
496 pub fn run_lua(&mut self, code: &[u8]) {
498 let Some(host) = self.lua.clone() else {
499 self.print_err("retex: \\directlua needs the Lua interpreter");
500 self.help(&[]);
501 self.error();
502 return;
503 };
504 let t0 = std::time::Instant::now();
505 let r = host.run(self, "\\directlua", code);
506 crate::timing::LUA.with(|a| a.add(t0));
507 if crate::timing::enabled() && t0.elapsed().as_millis() >= 5 {
508 let head = String::from_utf8_lossy(&code[..code.len().min(70)]).replace('\n', " ");
509 crate::timing::mark(&format!(
510 "slow Lua chunk ({:.1} ms): {head}",
511 t0.elapsed().as_secs_f64() * 1e3
512 ));
513 }
514 if let Err(msg) = r {
515 if std::env::var_os("RETEX_DEBUG_LUA").is_some() {
517 eprintln!(
518 "--- failing Lua chunk ---\n{}\n---",
519 String::from_utf8_lossy(code)
520 );
521 }
522 self.lua_error(&msg);
523 }
524 }
525
526 pub fn lua_error(&mut self, msg: &str) {
528 if self.diagnostics.is_some() {
529 self.print_err("LuaTeX error ");
530 self.printer.print_str(msg);
531 } else {
532 for (i, part) in msg.split('\n').enumerate() {
535 if i > 0 {
536 self.printer.print_ln();
537 }
538 self.printer.print_str(part);
539 }
540 }
541 self.help(&[
542 "The lua interpreter ran into a problem, so the",
543 "remainder of this lua chunk will be ignored.",
544 ]);
545 self.error();
546 }
547
548 pub fn call_lua_function(&mut self, n: i32) {
550 let Some(host) = self.lua.clone() else {
555 return self.run_lua(b"");
556 };
557 let t0 = std::time::Instant::now();
558 let r = host.call_function(self, n);
559 crate::timing::LUA.with(|a| a.add(t0));
560 if let Some(Err(msg)) = r {
561 self.lua_error(&msg);
562 }
563 }
564
565 pub fn call_lua_bytecode(&mut self, n: i32) {
567 let code = format!("return lua.bytecode[{n}]()");
568 self.run_lua(code.as_bytes());
569 }
570
571 pub fn lua_expandable_call(&mut self) {
573 let n = self.cur_chr as i32;
574 self.call_lua_function(n);
575 }
576
577 pub fn finish_output(&mut self) {
579 self.finish_backend();
580 if let Some(hook) = self.finish_hook.take() {
581 hook(self);
582 }
583 }
584
585 fn written_sink(&self) -> crate::files::WrittenSink {
587 self.written_sink.clone()
588 }
589
590 pub fn store_math_param_glue(&mut self, g: crate::glue::Glue) -> i32 {
592 self.math_glues.push(g);
593 (self.math_glues.len() - 1) as i32
594 }
595
596 pub fn math_param_glue(&self, v: i32) -> crate::glue::Glue {
598 self.math_glues
599 .get(v as usize)
600 .copied()
601 .unwrap_or(crate::glue::Glue::ZERO)
602 }
603}
604
605#[allow(dead_code)]
607const _PG: u32 = pg::assign_int::COUNT;
608
609impl Engine {
610 pub fn dimen_from_string(&mut self, s: &str) -> i64 {
613 let mut toks = crate::the::str_toks(s.as_bytes());
614 let stop = Token::cs(self.cs.frozen_relax);
615 toks.push(stop);
616 let depth = self.input.stack.len();
617 self.ins_list(Rc::from(toks));
618 self.scan_normal_dimen();
619 let v = self.cur_val;
620 while self.input.stack.len() > depth {
622 self.get_token();
623 if self.cur_tok == stop {
624 break;
625 }
626 }
627 i64::from(v)
628 }
629}
630
631fn whatsit_subtype(w: &crate::nodes::Whatsit) -> u16 {
633 use crate::nodes::{Whatsit, whatsit_sub as ws};
634 match w {
635 Whatsit::Open { .. } => ws::OPEN,
636 Whatsit::Write { .. } => ws::WRITE,
637 Whatsit::Close { .. } => ws::CLOSE,
638 Whatsit::Special { .. } => ws::SPECIAL,
639 Whatsit::LateLua { .. } => ws::LATE_LUA,
640 Whatsit::PdfLiteral { late, .. } => {
641 if *late {
642 ws::PDF_LATE_LITERAL
643 } else {
644 ws::PDF_LITERAL
645 }
646 }
647 Whatsit::SavePos => ws::SAVE_POS,
648 Whatsit::PdfColorstack { .. } => ws::PDF_COLORSTACK,
649 Whatsit::PdfSetmatrix { .. } => ws::PDF_SETMATRIX,
650 Whatsit::PdfSave => ws::PDF_SAVE,
651 Whatsit::PdfRestore => ws::PDF_RESTORE,
652 Whatsit::PdfAnnot { .. } => ws::PDF_ANNOT,
653 Whatsit::PdfStartLink { .. } => ws::PDF_START_LINK,
654 Whatsit::PdfEndLink => ws::PDF_END_LINK,
655 Whatsit::PdfDest { .. } => ws::PDF_DEST,
656 Whatsit::PdfRefobj { .. } => ws::PDF_REFOBJ,
657 Whatsit::Other { .. } | Whatsit::UserDefined { .. } => ws::USER_DEFINED,
658 Whatsit::PdfLinkState { .. } => ws::PDF_LINK_STATE,
659 }
660}