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