1use std::rc::Rc;
6
7use crate::Engine;
8use crate::cmd::Cmd;
9use crate::error::Interaction;
10use crate::hash::NO_CS;
11use crate::input::ScannerStatus;
12use crate::primitives_gen as pg;
13use crate::print::Selector;
14use crate::scan::ValLevel;
15use crate::token::{Token, TokenList};
16
17#[allow(missing_docs)]
19pub mod conv {
20 pub const NUMBER: u32 = 0;
21 pub const DIRECTLUA: u32 = 1;
22 pub const LUAFUNCTION: u32 = 2;
23 pub const LUABYTECODE: u32 = 3;
24 pub const EXPANDED: u32 = 4;
25 pub const IMMEDIATE_ASSIGNMENT: u32 = 5;
26 pub const IMMEDIATE_ASSIGNED: u32 = 6;
27 pub const MATH_STYLE: u32 = 7;
28 pub const STRING: u32 = 8;
29 pub const CSSTRING: u32 = 9;
30 pub const ROMAN_NUMERAL: u32 = 10;
31 pub const MEANING: u32 = 11;
32 pub const UCHAR: u32 = 12;
33 pub const LUA_ESCAPE_STRING: u32 = 13;
34 pub const FONT_ID: u32 = 14;
35 pub const FONT_NAME: u32 = 15;
36 pub const LEFT_MARGIN_KERN: u32 = 16;
37 pub const RIGHT_MARGIN_KERN: u32 = 17;
38 pub const UNIFORM_DEVIATE: u32 = 18;
39 pub const NORMAL_DEVIATE: u32 = 19;
40 pub const UMATHCHARCLASS: u32 = 20;
41 pub const UMATHCHARFAM: u32 = 21;
42 pub const UMATHCHARSLOT: u32 = 22;
43 pub const INSERT_HT: u32 = 23;
44 pub const JOB_NAME: u32 = 24;
45 pub const FORMAT_NAME: u32 = 25;
46 pub const LUATEX_BANNER: u32 = 26;
47 pub const LUATEX_REVISION: u32 = 27;
48 pub const ETEX_VERSION: u32 = 28;
49 pub const ETEX_REVISION: u32 = 29;
50}
51
52#[allow(missing_docs)]
54pub mod xray {
55 pub const SHOW: u32 = 0;
56 pub const SHOW_BOX: u32 = 1;
57 pub const SHOW_THE: u32 = 2;
58 pub const SHOW_LISTS: u32 = 3;
59 pub const SHOW_GROUPS: u32 = 4;
60 pub const SHOW_TOKENS: u32 = 5;
61 pub const SHOW_IFS: u32 = 6;
62}
63
64pub const LUATEX_BANNER: &str = "This is LuaTeX, Version 1.22.0";
66
67pub fn str_toks(s: &[u8]) -> Vec<Token> {
70 crate::utf8x::chars(s)
71 .into_iter()
72 .map(|c| {
73 if c == ' ' as u32 {
74 Token::SPACE
75 } else {
76 Token::other(c)
77 }
78 })
79 .collect()
80}
81
82impl Engine {
83 pub fn print_to_string(&mut self, f: impl FnOnce(&mut Engine)) -> Vec<u8> {
85 let old = self.printer.selector;
86 let old_string = std::mem::take(&mut self.printer.cur_string);
87 self.printer.selector = Selector::NewString;
88 f(self);
89 self.printer.selector = old;
90 std::mem::replace(&mut self.printer.cur_string, old_string)
91 }
92
93 pub fn scan_general_text(&mut self) -> TokenList {
96 let s = self.input.scanner_status;
97 let w = self.input.warning_index;
98 let d = self.def_ref_cs;
99 self.def_ref_cs = NO_CS;
100 let toks = self.scan_toks(false, false);
101 self.input.scanner_status = s;
102 self.input.warning_index = w;
103 self.def_ref_cs = d;
104 Rc::from(toks)
105 }
106
107 pub fn the_toks(&mut self) -> Vec<Token> {
109 if self.cur_chr & 1 != 0 {
110 let c = self.cur_chr;
112 let list = self.scan_general_text();
113 if c == 1 {
114 return list.to_vec();
115 }
116 let s = self.print_to_string(|e| {
117 e.show_token_list(&list, 0, list.len(), 10_000_000);
118 });
119 return str_toks(&s);
120 }
121 self.get_x_token();
122 if self.cur_cmd == Cmd::HyphData && self.cur_chr == crate::hyph::codes::HYPHENATION_MIN {
123 let v = self
126 .language(self.cur_lang())
127 .map_or(-1, |l| l.settings.hyphenation_min);
128 return str_toks(&self.print_to_string(|e| e.printer.print_int(v.into())));
129 }
130 self.scan_something_internal(ValLevel::Tok, false);
131 match self.cur_val_level {
132 ValLevel::Ident => {
133 let f = self.cur_val as u32;
134 vec![Token::cs(self.font_id_cs(f))]
135 }
136 ValLevel::Tok => self.cur_toks.take().map(|l| l.to_vec()).unwrap_or_default(),
137 level => {
138 let v = self.cur_val;
139 let g = self.cur_glue;
140 let s = self.print_to_string(|e| match level {
141 ValLevel::Int => e.printer.print_int(v.into()),
142 ValLevel::Dimen => {
143 e.printer.print_scaled(v);
144 e.printer.print_str("pt");
145 }
146 ValLevel::Glue => e.print_spec(&g, Some("pt")),
147 _ => e.print_spec(&g, Some("mu")),
148 });
149 str_toks(&s)
150 }
151 }
152 }
153
154 pub fn ins_the_toks(&mut self) {
156 let list = self.the_toks();
157 self.ins_list(Rc::from(list));
158 }
159
160 pub fn conv_toks(&mut self) {
162 let c = self.cur_chr;
163 let result: Vec<Token> = match c {
164 conv::NUMBER | conv::ROMAN_NUMERAL => {
165 self.scan_int();
166 let v = self.cur_val;
167 let s = self.print_to_string(|e| {
168 if c == conv::NUMBER {
169 e.printer.print_int(v.into());
170 } else {
171 e.printer.print_roman_int(v.into());
172 }
173 });
174 str_toks(&s)
175 }
176 conv::STRING | conv::CSSTRING | conv::MEANING => {
177 let save = self.input.scanner_status;
178 self.input.scanner_status = ScannerStatus::Normal;
179 self.get_token();
180 self.input.scanner_status = save;
181 let s = self.print_to_string(|e| {
182 if c == conv::MEANING {
183 e.print_meaning();
184 } else if e.cur_cs != NO_CS {
185 if c == conv::CSSTRING {
186 let name = e.cs_print_name(e.cur_cs);
187 e.printer.print_bytes(&name);
188 } else {
189 e.sprint_cs(e.cur_cs);
190 }
191 } else {
192 e.printer.print_char(e.cur_chr);
193 }
194 });
195 str_toks(&s)
196 }
197 conv::FONT_NAME => {
198 self.scan_font_ident();
199 let f = self.cur_val as u32;
200 let s = self.print_to_string(|e| e.print_font_name_and_size(f));
201 str_toks(&s)
202 }
203 conv::FONT_ID => {
204 self.scan_font_ident();
205 let v = self.cur_val;
206 str_toks(v.to_string().as_bytes())
207 }
208 conv::JOB_NAME => {
209 if self.job_name.is_none() {
210 self.open_log_file();
211 }
212 let name = self.job_name.clone().unwrap_or_default();
213 str_toks(&name)
214 }
215 conv::FORMAT_NAME => str_toks(self.format_name.clone().as_bytes()),
216 conv::LUATEX_BANNER => str_toks(LUATEX_BANNER.as_bytes()),
217 conv::LUATEX_REVISION => str_toks(crate::scan::LUATEX_REVISION.as_bytes()),
218 conv::ETEX_VERSION => str_toks(crate::scan::ETEX_VERSION.to_string().as_bytes()),
219 conv::ETEX_REVISION => str_toks(crate::scan::ETEX_REVISION.as_bytes()),
220 conv::EXPANDED => self.scan_toks_expanded(),
221 conv::UCHAR => {
222 self.scan_char_num();
223 let ch = self.cur_val as u32;
224 let cat = self.eqtb.catcode(ch);
225 let t = match cat {
228 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 => {
229 Token::char(Cmd::from_u8(cat), if cat == 10 { ' ' as u32 } else { ch })
230 }
231 _ => Token::other(ch),
232 };
233 vec![t]
234 }
235 conv::LUA_ESCAPE_STRING => {
236 let toks = self.scan_toks_expanded();
237 self.single_mac_param = true;
240 let s = self.print_to_string(|e| e.show_token_list(&toks, 0, toks.len(), i64::MAX));
241 self.single_mac_param = false;
242 let escaped = lua_escape(&s);
243 str_toks(&escaped)
244 }
245 conv::DIRECTLUA => {
246 let toks = self.scan_toks_expanded();
247 let code = self.lua_code(&toks);
248 self.run_lua(&code);
249 return;
250 }
251 conv::UNIFORM_DEVIATE => {
252 self.scan_int();
253 let v = self.uniform_deviate(self.cur_val);
254 str_toks(v.to_string().as_bytes())
255 }
256 conv::NORMAL_DEVIATE => {
257 let v = self.normal_deviate();
258 str_toks(v.to_string().as_bytes())
259 }
260 conv::UMATHCHARCLASS | conv::UMATHCHARFAM | conv::UMATHCHARSLOT => {
261 self.scan_char_num();
262 let code = self.eqtb.math.get(self.cur_val as u32);
263 let v = match c {
264 conv::UMATHCHARCLASS => crate::codes::math_class(code),
265 conv::UMATHCHARFAM => crate::codes::math_fam(code),
266 _ => crate::codes::math_char(code),
267 };
268 str_toks(v.to_string().as_bytes())
269 }
270 conv::MATH_STYLE => {
271 let v = self.cur_math_style();
272 str_toks(v.to_string().as_bytes())
273 }
274 conv::INSERT_HT => {
275 self.scan_register_num();
276 let v = self.insert_height(self.cur_val as u32);
277 let s = self.print_to_string(|e| {
278 e.printer.print_scaled(v);
279 e.printer.print_str("pt");
280 });
281 str_toks(&s)
282 }
283 conv::LEFT_MARGIN_KERN | conv::RIGHT_MARGIN_KERN => {
284 self.scan_register_num();
285 let v = self.margin_kern(self.cur_val as u32, c == conv::LEFT_MARGIN_KERN);
286 let s = match v {
287 Some(v) => self.print_to_string(|e| {
288 e.printer.print_scaled(v);
289 e.printer.print_str("pt");
290 }),
291 None => b"0pt".to_vec(),
292 };
293 str_toks(&s)
294 }
295 conv::IMMEDIATE_ASSIGNMENT | conv::IMMEDIATE_ASSIGNED => {
296 self.immediate_assignment(c == conv::IMMEDIATE_ASSIGNED);
297 return;
298 }
299 conv::LUAFUNCTION | conv::LUABYTECODE => {
300 self.scan_int();
301 let n = self.cur_val;
302 if c == conv::LUAFUNCTION {
303 self.call_lua_function(n);
304 } else {
305 self.call_lua_bytecode(n);
306 }
307 return;
308 }
309 _ => Vec::new(),
310 };
311 self.ins_list(Rc::from(result));
312 }
313
314 pub fn scan_toks_expanded(&mut self) -> Vec<Token> {
317 let s = self.input.scanner_status;
318 let w = self.input.warning_index;
319 let d = self.def_ref_cs;
320 self.def_ref_cs = NO_CS;
321 let toks = self.scan_toks(false, true);
322 self.input.scanner_status = s;
323 self.input.warning_index = w;
324 self.def_ref_cs = d;
325 toks
326 }
327
328 pub fn cs_print_name(&self, cs: crate::hash::CsId) -> Vec<u8> {
330 match self.hash.kind(cs) {
331 crate::hash::CsKind::Active(c) => crate::utf8x::encode(*c),
332 _ => self.hash.name(cs).to_vec(),
333 }
334 }
335
336 pub fn show_whatever(&mut self) {
339 let mut long = true;
340 let note = self.diagnostics.is_some();
343 let history = self.err.history;
344 let mut name = String::new();
345 if note {
346 let n = self.print_to_string(|e| e.print_cmd_chr(e.cur_cmd, e.cur_chr));
347 name = String::from_utf8_lossy(&n).into_owned();
348 }
349 let mut shown = format!("{name}: see the log");
350 match self.cur_chr {
351 xray::SHOW_LISTS => {
352 self.begin_diagnostic();
353 self.show_activities();
354 }
355 xray::SHOW_BOX => {
356 self.scan_register_num();
357 let n = self.cur_val;
358 self.begin_diagnostic();
359 self.printer.print_nl("> \\box");
360 self.printer.print_int(n.into());
361 self.printer.print_char('=' as u32);
362 let b = self.eqtb.boxes[n as usize].val;
363 if b.is_null() {
364 self.printer.print_str("void");
365 } else {
366 self.show_box(b);
367 }
368 }
369 xray::SHOW => {
370 self.get_token();
371 if note {
372 let text = self.print_to_string(|e| {
373 if e.cur_cs != NO_CS {
374 e.sprint_cs(e.cur_cs);
375 e.printer.print_char('=' as u32);
376 }
377 e.print_meaning();
378 });
379 shown = String::from_utf8_lossy(&text).into_owned();
380 }
381 self.printer.print_nl("> ");
382 if self.cur_cs != NO_CS {
383 self.sprint_cs(self.cur_cs);
384 self.printer.print_char('=' as u32);
385 }
386 self.print_meaning();
387 long = false;
388 }
389 xray::SHOW_GROUPS => {
390 self.begin_diagnostic();
391 self.show_save_groups();
392 }
393 xray::SHOW_IFS => {
394 self.begin_diagnostic();
395 self.printer.print_nl("");
396 self.printer.print_ln();
397 if self.cond.stack.is_empty() {
398 self.printer.print_nl("### ");
399 self.printer.print_str("no active conditionals");
400 } else {
401 let mut n = self.cond.stack.len();
402 let mut cur_if = self.cond.cur_if;
403 let mut l = self.cond.if_line;
404 let mut m = self.cond.if_limit;
405 loop {
406 self.printer.print_nl("### level ");
407 self.printer.print_int(n as i64);
408 self.printer.print_str(": ");
409 self.print_cmd_chr(Cmd::IfTest, cur_if);
410 if m == crate::cond::FI_CODE {
411 self.print_esc_str("else");
412 }
413 if l != 0 {
414 self.printer.print_str(" entered on line ");
415 self.printer.print_int(l.into());
416 }
417 n -= 1;
418 if n == 0 {
419 break;
420 }
421 let r = self.cond.stack[n];
422 cur_if = r.cur_if;
423 l = r.if_line;
424 m = r.if_limit;
425 }
426 }
427 }
428 _ => {
429 let list = if self.cur_chr == xray::SHOW_TOKENS {
431 self.scan_general_text().to_vec()
432 } else {
433 self.the_toks()
434 };
435 if note {
436 let text = self
437 .print_to_string(|e| e.token_show_list(&list, 0, list.len(), 10_000_000));
438 shown = format!("{name}: {}", String::from_utf8_lossy(&text));
439 }
440 self.printer.print_nl("> ");
441 self.token_show_list(&list, 0, list.len(), 10_000_000);
442 long = false;
443 }
444 }
445 if long {
446 self.end_diagnostic(true);
447 self.print_err("OK");
448 if note {
449 self.printer.capture = None;
451 }
452 let online = self.eqtb.int(pg::assign_int::TRACINGONLINE);
453 if self.printer.selector == Selector::TermAndLog && online <= 0 {
454 self.printer.selector = Selector::TermOnly;
455 self.printer.print_str(" (see the transcript file)");
456 self.printer.selector = Selector::TermAndLog;
457 }
458 }
459 if self.err.interaction < Interaction::ErrorStop {
461 self.help(&[]);
462 self.err.error_count -= 1;
463 } else if self.eqtb.int(pg::assign_int::TRACINGONLINE) > 0 {
464 self.help(&[
465 "This isn't an error message; I'm just \\showing something.",
466 "Type `I\\show...' to show more (e.g., \\show\\cs,",
467 "\\showthe\\count10, \\showbox255, \\showlists).",
468 ]);
469 } else {
470 self.help(&[
471 "This isn't an error message; I'm just \\showing something.",
472 "Type `I\\show...' to show more (e.g., \\show\\cs,",
473 "\\showthe\\count10, \\showbox255, \\showlists).",
474 "And type `I\\tracingonline=1\\show...' to show boxes and",
475 "lists on your terminal as well as in the transcript file.",
476 ]);
477 }
478 if note {
479 self.record_note(&shown);
480 }
481 self.error();
482 if note {
483 self.err.history = history;
484 }
485 }
486}
487
488pub fn lua_escape(s: &[u8]) -> Vec<u8> {
493 let mut out = Vec::with_capacity(s.len());
494 for &b in s {
495 match b {
496 b'\\' => out.extend_from_slice(b"\\\\"),
497 b'"' => out.extend_from_slice(b"\\\""),
498 b'\'' => out.extend_from_slice(b"\\'"),
499 b'\n' => out.extend_from_slice(b"\\n"),
500 b'\r' => out.extend_from_slice(b"\\r"),
501 _ => out.push(b),
502 }
503 }
504 out
505}
506
507#[cfg(test)]
508mod tests {
509 use super::lua_escape;
510
511 #[test]
512 fn lua_escapes() {
513 assert_eq!(
515 lua_escape(b"a\x001\x01\t\"\\x'\r\n"),
516 b"a\x001\x01\t\\\"\\\\x\\'\\r\\n".to_vec()
517 );
518 }
519}