1use std::fmt::Write as _;
14use std::sync::atomic::Ordering;
15
16use crate::lex;
17use crate::parse;
18use crate::tokens::{
19 lextok, AMPER, AMPERBANG, AMPOUTANG, BANG_TOK, BARAMP, BAR_TOK, CASE, COPROC, DAMPER, DBAR,
20 DINANG, DINANGDASH, DINBRACK, DINPAR, DOLOOP, DONE, DOUTANG, DOUTANGAMP, DOUTANGAMPBANG,
21 DOUTANGBANG, DOUTBRACK, DOUTPAR, DSEMI, ELIF, ELSE, ENDINPUT, ENVARRAY, ENVSTRING, ESAC, FI,
22 FOR, FOREACH, FUNC, IF, INANGAMP, INANG_TOK, INBRACE_TOK, INOUTANG, INOUTPAR, INPAR_TOK,
23 LEXERR, NEWLIN, NOCORRECT, NULLTOK, OUTANGAMP, OUTANGAMPBANG, OUTANGBANG, OUTANG_TOK,
24 OUTBRACE_TOK, OUTPAR_TOK, REPEAT, SELECT, SEMI, SEMIAMP, SEMIBAR, SEPER, STRING_LEX, THEN,
25 TIME, TRINANG, TYPESET, UNTIL, WHILE, ZEND,
26};
27use crate::utils::{errflag, ERRFLAG_ERROR};
28use crate::zsh_h::{wc_code, wc_data, wordcode};
29
30fn tok_name(t: lextok) -> &'static str {
35 match t {
36 NULLTOK => "NULLTOK",
37 SEPER => "SEPER",
38 NEWLIN => "NEWLIN",
39 SEMI => "SEMI",
40 DSEMI => "DSEMI",
41 AMPER => "AMPER",
42 INPAR_TOK => "INPAR",
43 OUTPAR_TOK => "OUTPAR",
44 DBAR => "DBAR",
45 DAMPER => "DAMPER",
46 OUTANG_TOK => "OUTANG",
47 OUTANGBANG => "OUTANGBANG",
48 DOUTANG => "DOUTANG",
49 DOUTANGBANG => "DOUTANGBANG",
50 INANG_TOK => "INANG",
51 INOUTANG => "INOUTANG",
52 DINANG => "DINANG",
53 DINANGDASH => "DINANGDASH",
54 INANGAMP => "INANGAMP",
55 OUTANGAMP => "OUTANGAMP",
56 AMPOUTANG => "AMPOUTANG",
57 OUTANGAMPBANG => "OUTANGAMPBANG",
58 DOUTANGAMP => "DOUTANGAMP",
59 DOUTANGAMPBANG => "DOUTANGAMPBANG",
60 TRINANG => "TRINANG",
61 BAR_TOK => "BAR",
62 BARAMP => "BARAMP",
63 INOUTPAR => "INOUTPAR",
64 DINPAR => "DINPAR",
65 DOUTPAR => "DOUTPAR",
66 AMPERBANG => "AMPERBANG",
67 SEMIAMP => "SEMIAMP",
68 SEMIBAR => "SEMIBAR",
69 DOUTBRACK => "DOUTBRACK",
70 STRING_LEX => "STRING",
71 ENVSTRING => "ENVSTRING",
72 ENVARRAY => "ENVARRAY",
73 ENDINPUT => "ENDINPUT",
74 LEXERR => "LEXERR",
75 BANG_TOK => "BANG",
76 DINBRACK => "DINBRACK",
77 INBRACE_TOK => "INBRACE",
78 OUTBRACE_TOK => "OUTBRACE",
79 CASE => "CASE",
80 COPROC => "COPROC",
81 DOLOOP => "DOLOOP",
82 DONE => "DONE",
83 ELIF => "ELIF",
84 ELSE => "ELSE",
85 ZEND => "ZEND",
86 ESAC => "ESAC",
87 FI => "FI",
88 FOR => "FOR",
89 FOREACH => "FOREACH",
90 FUNC => "FUNC",
91 IF => "IF",
92 NOCORRECT => "NOCORRECT",
93 REPEAT => "REPEAT",
94 SELECT => "SELECT",
95 THEN => "THEN",
96 TIME => "TIME",
97 UNTIL => "UNTIL",
98 WHILE => "WHILE",
99 TYPESET => "TYPESET",
100 _ => "UNKNOWN",
101 }
102}
103
104const WCNAMES: &[&str] = &[
105 "WC_END",
106 "WC_LIST",
107 "WC_SUBLIST",
108 "WC_PIPE",
109 "WC_REDIR",
110 "WC_ASSIGN",
111 "WC_SIMPLE",
112 "WC_TYPESET",
113 "WC_SUBSH",
114 "WC_CURSH",
115 "WC_TIMED",
116 "WC_FUNCDEF",
117 "WC_FOR",
118 "WC_SELECT",
119 "WC_WHILE",
120 "WC_REPEAT",
121 "WC_CASE",
122 "WC_IF",
123 "WC_COND",
124 "WC_ARITH",
125 "WC_AUTOFN",
126 "WC_TRY",
127];
128
129fn wc_name(kind: wordcode) -> &'static str {
130 let i = kind as usize;
131 if i < WCNAMES.len() {
132 WCNAMES[i]
133 } else {
134 "WC_?"
135 }
136}
137
138fn esc_bytes(out: &mut String, bytes: &[u8]) {
139 for &b in bytes {
140 match b {
141 b'\n' => out.push_str("\\n"),
142 b'\t' => out.push_str("\\t"),
143 b'\\' => out.push_str("\\\\"),
144 b'"' => out.push_str("\\\""),
145 0 => out.push_str("\\0"),
146 c if c < 0x20 || c >= 0x7f => {
147 let _ = write!(out, "\\x{:02x}", c);
148 }
149 c => out.push(c as char),
150 }
151 }
152}
153
154pub fn dump_tokens(src: &str) -> String {
158 let mut out = String::new();
159 lex::lex_init(src);
160 loop {
161 lex::ctxtlex();
162 let tok = lex::tok();
163 if tok == ENDINPUT {
164 out.push_str("ENDINPUT\n");
165 return out;
166 }
167 if tok == LEXERR {
168 out.push_str("LEXERR\n");
169 return out;
170 }
171 let raw = lex::tokstr().unwrap_or_default();
172 let plain =
184 lex::untokenize_preserve_quotes(&raw).replace(crate::ported::zsh_h::Qstring, "$");
185 out.push_str(tok_name(tok));
186 out.push('\t');
187 out.push_str(&plain);
188 out.push('\n');
189 }
190}
191
192pub fn dump_ast(src: &str) -> String {
197 lex::lex_init(src);
198 let saved = errflag.load(Ordering::Relaxed);
199 errflag.fetch_and(!ERRFLAG_ERROR, Ordering::Relaxed);
200 let prog = parse::parse();
201 let had_err = (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0;
202 errflag.store(saved, Ordering::Relaxed);
203 if had_err {
204 return "PARSE_ERR\n".to_string();
205 }
206 let mut out = crate::ast_sexp::ast_to_sexp(&prog);
207 if !out.ends_with('\n') {
208 out.push('\n');
209 }
210 out
211}
212
213pub fn dump_wordcode(src: &str) -> String {
218 lex::lex_init(src);
219 lex::set_tok(ENDINPUT);
220 parse::init_parse();
221 lex::zshlex();
222 let mut cmplx: i32 = 0;
223 parse::par_list_wordcode(&mut cmplx);
224 if lex::tok() != ENDINPUT {
225 return "PARSE_ERR\n".to_string();
226 }
227 let prog = parse::bld_eprog(true);
228
229 let mut buf = String::new();
230 let _ = writeln!(
231 buf,
232 "EPROG flags=0x{:x} len={} npats={}",
233 prog.flags, prog.len, prog.npats
234 );
235 let wc_count = prog.prog.len();
236 let _ = writeln!(buf, "WORDS {}", wc_count);
237 for (i, w) in prog.prog.iter().enumerate() {
238 let _ = writeln!(
239 buf,
240 "WC[{}]=0x{:08x} KIND={} DATA=0x{:x}",
241 i,
242 w,
243 wc_name(wc_code(*w)),
244 wc_data(*w)
245 );
246 }
247 let strs_str = prog.strs.unwrap_or_default();
248 let strs_bytes = strs_str.as_bytes();
249 let mut entries: Vec<&[u8]> = Vec::new();
250 let mut start = 0;
251 for (i, &b) in strs_bytes.iter().enumerate() {
252 if b == 0 {
253 entries.push(&strs_bytes[start..i]);
254 start = i + 1;
255 }
256 }
257 let _ = writeln!(buf, "STRS {}", entries.len());
258 for (i, e) in entries.iter().enumerate() {
259 let _ = write!(buf, "STR[{}]=\"", i);
260 esc_bytes(&mut buf, e);
261 buf.push_str("\"\n");
262 }
263 buf
264}
265
266#[cfg(test)]
267mod tests {
268 use super::{esc_bytes, tok_name, wc_name};
269 use crate::tokens::{
270 AMPER, AMPERBANG, BAR_TOK, CASE, ENDINPUT, ESAC, FI, FOR, IF, LEXERR, NEWLIN, SEMI,
271 STRING_LEX, THEN, WHILE,
272 };
273
274 #[test]
277 fn tok_name_basic_separators() {
278 assert_eq!(tok_name(NEWLIN), "NEWLIN");
279 assert_eq!(tok_name(SEMI), "SEMI");
280 assert_eq!(tok_name(AMPER), "AMPER");
281 }
282
283 #[test]
284 fn tok_name_string_and_terminators() {
285 assert_eq!(tok_name(STRING_LEX), "STRING");
286 assert_eq!(tok_name(ENDINPUT), "ENDINPUT");
287 assert_eq!(tok_name(LEXERR), "LEXERR");
288 }
289
290 #[test]
291 fn tok_name_logical_ops() {
292 assert_eq!(tok_name(BAR_TOK), "BAR");
293 assert_eq!(tok_name(AMPERBANG), "AMPERBANG");
294 }
295
296 #[test]
297 fn tok_name_control_keywords() {
298 assert_eq!(tok_name(IF), "IF");
299 assert_eq!(tok_name(THEN), "THEN");
300 assert_eq!(tok_name(FI), "FI");
301 assert_eq!(tok_name(FOR), "FOR");
302 assert_eq!(tok_name(WHILE), "WHILE");
303 assert_eq!(tok_name(CASE), "CASE");
304 assert_eq!(tok_name(ESAC), "ESAC");
305 }
306
307 #[test]
308 fn tok_name_unknown_falls_back_to_literal() {
309 let bogus: super::lextok = i32::MAX;
311 assert_eq!(tok_name(bogus), "UNKNOWN");
312 }
313
314 #[test]
317 fn wc_name_inside_range_returns_table_entry() {
318 assert!(!wc_name(0).is_empty(), "0 must hit the static table");
320 }
321
322 #[test]
323 fn wc_name_overflow_falls_back() {
324 assert_eq!(wc_name(u32::MAX), "WC_?");
326 }
327
328 fn esc(bytes: &[u8]) -> String {
331 let mut s = String::new();
332 esc_bytes(&mut s, bytes);
333 s
334 }
335
336 #[test]
337 fn esc_newline() {
338 assert_eq!(esc(b"\n"), "\\n");
339 }
340
341 #[test]
342 fn esc_tab() {
343 assert_eq!(esc(b"\t"), "\\t");
344 }
345
346 #[test]
347 fn esc_backslash_doubles() {
348 assert_eq!(esc(b"\\"), "\\\\");
349 }
350
351 #[test]
352 fn esc_double_quote() {
353 assert_eq!(esc(b"\""), "\\\"");
354 }
355
356 #[test]
357 fn esc_nul_byte() {
358 assert_eq!(esc(b"\0"), "\\0");
359 }
360
361 #[test]
362 fn esc_printable_ascii_passes_through() {
363 assert_eq!(esc(b"hello world"), "hello world");
365 assert_eq!(esc(b"!#$%&'()*+,-./0123456789"), "!#$%&'()*+,-./0123456789");
366 }
367
368 #[test]
369 fn esc_low_control_uses_hex() {
370 assert_eq!(esc(&[0x01]), "\\x01");
372 assert_eq!(esc(&[0x1f]), "\\x1f");
373 }
374
375 #[test]
376 fn esc_high_bit_uses_hex() {
377 assert_eq!(esc(&[0x7f]), "\\x7f");
379 assert_eq!(esc(&[0xff]), "\\xff");
380 assert_eq!(esc(&[0x80]), "\\x80");
381 }
382
383 #[test]
384 fn esc_mixed_buffer_preserves_order() {
385 assert_eq!(esc(b"a\nb\tc"), "a\\nb\\tc");
386 assert_eq!(esc(b"x\0y"), "x\\0y");
387 assert_eq!(esc(b"\"hi\""), "\\\"hi\\\"");
388 }
389
390 #[test]
391 fn esc_appends_to_existing_buffer() {
392 let mut buf = String::from("prefix:");
394 esc_bytes(&mut buf, b"\n");
395 assert_eq!(buf, "prefix:\\n");
396 }
397
398 #[test]
399 fn esc_empty_input_yields_empty_output() {
400 assert_eq!(esc(b""), "");
401 }
402}