1use crate::parser::{
7 CaseTerminator, CompoundCommand, ListOp, Redirect, RedirectOp, ShellCommand, ShellWord,
8 SimpleCommand,
9};
10use std::fs::File;
11use std::io::{self, Read, Seek, SeekFrom};
12use std::path::Path;
13
14const FD_MAGIC: u32 = 0x04050607;
15const FD_OMAGIC: u32 = 0x07060504; const FD_PRELEN: usize = 12;
17
18pub const WC_END: u32 = 0;
20pub const WC_LIST: u32 = 1;
21pub const WC_SUBLIST: u32 = 2;
22pub const WC_PIPE: u32 = 3;
23pub const WC_REDIR: u32 = 4;
24pub const WC_ASSIGN: u32 = 5;
25pub const WC_SIMPLE: u32 = 6;
26pub const WC_TYPESET: u32 = 7;
27pub const WC_SUBSH: u32 = 8;
28pub const WC_CURSH: u32 = 9;
29pub const WC_TIMED: u32 = 10;
30pub const WC_FUNCDEF: u32 = 11;
31pub const WC_FOR: u32 = 12;
32pub const WC_SELECT: u32 = 13;
33pub const WC_WHILE: u32 = 14;
34pub const WC_REPEAT: u32 = 15;
35pub const WC_CASE: u32 = 16;
36pub const WC_IF: u32 = 17;
37pub const WC_COND: u32 = 18;
38pub const WC_ARITH: u32 = 19;
39pub const WC_AUTOFN: u32 = 20;
40pub const WC_TRY: u32 = 21;
41
42pub const Z_END: u32 = 1 << 4;
44pub const Z_SIMPLE: u32 = 1 << 5;
45pub const WC_LIST_FREE: u32 = 6;
46
47pub const WC_SUBLIST_END: u32 = 0;
49pub const WC_SUBLIST_AND: u32 = 1;
50pub const WC_SUBLIST_OR: u32 = 2;
51pub const WC_SUBLIST_COPROC: u32 = 4;
52pub const WC_SUBLIST_NOT: u32 = 8;
53pub const WC_SUBLIST_SIMPLE: u32 = 16;
54pub const WC_SUBLIST_FREE: u32 = 5;
55
56pub const WC_PIPE_END: u32 = 0;
58pub const WC_PIPE_MID: u32 = 1;
59
60pub const WC_FOR_PPARAM: u32 = 0;
62pub const WC_FOR_LIST: u32 = 1;
63pub const WC_FOR_COND: u32 = 2;
64
65pub const WC_WHILE_WHILE: u32 = 0;
67pub const WC_WHILE_UNTIL: u32 = 1;
68
69pub const WC_CASE_HEAD: u32 = 0;
71pub const WC_CASE_OR: u32 = 1;
72pub const WC_CASE_AND: u32 = 2;
73pub const WC_CASE_TESTAND: u32 = 3;
74pub const WC_CASE_FREE: u32 = 3;
75
76pub const WC_IF_HEAD: u32 = 0;
78pub const WC_IF_IF: u32 = 1;
79pub const WC_IF_ELIF: u32 = 2;
80pub const WC_IF_ELSE: u32 = 3;
81
82pub const WC_CODEBITS: u32 = 5;
83
84const POUND: u8 = 0x84;
86const STRING: u8 = 0x85; const HAT: u8 = 0x86; const STAR: u8 = 0x87; const INPAR: u8 = 0x88; const OUTPAR: u8 = 0x8a; const QSTRING: u8 = 0x8c; const EQUALS: u8 = 0x8d; const BAR: u8 = 0x8e; const INBRACE: u8 = 0x8f; const OUTBRACE: u8 = 0x90; const INBRACK: u8 = 0x91; const OUTBRACK: u8 = 0x92; const TICK: u8 = 0x93; const INANG: u8 = 0x94; const OUTANG: u8 = 0x95; const QUEST: u8 = 0x97; const TILDE: u8 = 0x98; const COMMA: u8 = 0x9a; const SNULL: u8 = 0x9d; const DNULL: u8 = 0x9e; const BNULL: u8 = 0x9f; const NULARG: u8 = 0xa1; fn untokenize(bytes: &[u8]) -> String {
111 let mut result = String::new();
112 let mut i = 0;
113
114 while i < bytes.len() {
115 let b = bytes[i];
116 match b {
117 POUND => result.push('#'),
118 STRING | QSTRING => result.push('$'),
119 HAT => result.push('^'),
120 STAR => result.push('*'),
121 INPAR => result.push('('),
122 OUTPAR => result.push(')'),
123 EQUALS => result.push('='),
124 BAR => result.push('|'),
125 INBRACE => result.push('{'),
126 OUTBRACE => result.push('}'),
127 INBRACK => result.push('['),
128 OUTBRACK => result.push(']'),
129 TICK => result.push('`'),
130 INANG => result.push('<'),
131 OUTANG => result.push('>'),
132 QUEST => result.push('?'),
133 TILDE => result.push('~'),
134 COMMA => result.push(','),
135 SNULL | DNULL | BNULL | NULARG => {
136 }
138 0x89 => result.push_str("(("), 0x8b => result.push_str("))"), _ if b >= 0x80 => {
141 }
143 _ => result.push(b as char),
144 }
145 i += 1;
146 }
147
148 result
149}
150
151#[inline]
152pub fn wc_code(c: u32) -> u32 {
153 c & ((1 << WC_CODEBITS) - 1)
154}
155
156#[inline]
157pub fn wc_data(c: u32) -> u32 {
158 c >> WC_CODEBITS
159}
160
161#[derive(Debug)]
162pub struct ZwcHeader {
163 pub magic: u32,
164 pub flags: u8,
165 pub version: String,
166 pub header_len: u32,
167 pub other_offset: u32,
168}
169
170#[derive(Debug)]
171pub struct ZwcFunction {
172 pub name: String,
173 pub start: u32,
174 pub len: u32,
175 pub npats: u32,
176 pub strs_offset: u32,
177 pub flags: u32,
178}
179
180#[derive(Debug)]
181pub struct ZwcFile {
182 pub header: ZwcHeader,
183 pub functions: Vec<ZwcFunction>,
184 pub wordcode: Vec<u32>,
185 pub strings: Vec<u8>,
186}
187
188impl ZwcFile {
189 pub fn load<P: AsRef<Path>>(path: P) -> io::Result<Self> {
190 let mut file = File::open(path)?;
191 let mut buf = vec![0u8; (FD_PRELEN + 1) * 4];
192
193 file.read_exact(&mut buf)?;
194
195 let magic = u32::from_ne_bytes([buf[0], buf[1], buf[2], buf[3]]);
196
197 let swap_bytes = if magic == FD_MAGIC {
198 false
199 } else if magic == FD_OMAGIC {
200 true
201 } else {
202 return Err(io::Error::new(
203 io::ErrorKind::InvalidData,
204 format!("Invalid ZWC magic: 0x{:08x}", magic),
205 ));
206 };
207
208 let read_u32 = |bytes: &[u8], offset: usize| -> u32 {
209 let b = &bytes[offset..offset + 4];
210 let val = u32::from_ne_bytes([b[0], b[1], b[2], b[3]]);
211 if swap_bytes {
212 val.swap_bytes()
213 } else {
214 val
215 }
216 };
217
218 let flags = buf[4];
219 let other_offset = (buf[5] as u32) | ((buf[6] as u32) << 8) | ((buf[7] as u32) << 16);
220
221 let version_start = 8;
223 let version_end = buf[version_start..]
224 .iter()
225 .position(|&b| b == 0)
226 .map(|p| version_start + p)
227 .unwrap_or(buf.len());
228 let version = String::from_utf8_lossy(&buf[version_start..version_end]).to_string();
229
230 let header_len = read_u32(&buf, FD_PRELEN * 4);
231
232 let header = ZwcHeader {
233 magic,
234 flags,
235 version,
236 header_len,
237 other_offset,
238 };
239
240 file.seek(SeekFrom::Start(0))?;
242 let full_header_size = (header_len as usize) * 4;
243 let mut header_buf = vec![0u8; full_header_size];
244 file.read_exact(&mut header_buf)?;
245
246 let mut functions = Vec::new();
248 let mut offset = FD_PRELEN * 4;
249
250 while offset < full_header_size {
251 if offset + 24 > full_header_size {
252 break;
253 }
254
255 let start = read_u32(&header_buf, offset);
256 let len = read_u32(&header_buf, offset + 4);
257 let npats = read_u32(&header_buf, offset + 8);
258 let strs = read_u32(&header_buf, offset + 12);
259 let hlen = read_u32(&header_buf, offset + 16);
260 let flags = read_u32(&header_buf, offset + 20);
261
262 let name_start = offset + 24;
264 let name_end = header_buf[name_start..]
265 .iter()
266 .position(|&b| b == 0)
267 .map(|p| name_start + p)
268 .unwrap_or(full_header_size);
269
270 let name = String::from_utf8_lossy(&header_buf[name_start..name_end]).to_string();
271
272 if name.is_empty() {
273 break;
274 }
275
276 functions.push(ZwcFunction {
277 name,
278 start,
279 len,
280 npats,
281 strs_offset: strs,
282 flags,
283 });
284
285 offset += (hlen as usize) * 4;
287 }
288
289 let mut rest = Vec::new();
291 file.read_to_end(&mut rest)?;
292
293 let mut wordcode = Vec::new();
295 let mut i = 0;
296 while i + 4 <= rest.len() {
297 let val = u32::from_ne_bytes([rest[i], rest[i + 1], rest[i + 2], rest[i + 3]]);
298 wordcode.push(if swap_bytes { val.swap_bytes() } else { val });
299 i += 4;
300 }
301
302 let strings = rest;
304
305 Ok(ZwcFile {
306 header,
307 functions,
308 wordcode,
309 strings,
310 })
311 }
312
313 pub fn list_functions(&self) -> Vec<&str> {
314 self.functions.iter().map(|f| f.name.as_str()).collect()
315 }
316
317 pub fn function_count(&self) -> usize {
318 self.functions.len()
319 }
320
321 pub fn new_builder() -> ZwcBuilder {
323 ZwcBuilder::new()
324 }
325
326 pub fn get_function(&self, name: &str) -> Option<&ZwcFunction> {
327 self.functions
328 .iter()
329 .find(|f| f.name == name || f.name.ends_with(&format!("/{}", name)))
330 }
331
332 pub fn decode_function(&self, func: &ZwcFunction) -> Option<DecodedFunction> {
333 let header_words = self.header.header_len as usize;
334 let start_idx = (func.start as usize).saturating_sub(header_words);
335
336 if start_idx >= self.wordcode.len() {
337 return None;
338 }
339
340 let func_wordcode = &self.wordcode[start_idx..];
343
344 let mut string_bytes = Vec::new();
347 for &wc in func_wordcode {
348 string_bytes.extend_from_slice(&wc.to_ne_bytes());
349 }
350
351 let decoder = WordcodeDecoder::new(func_wordcode, &string_bytes, func.strs_offset as usize);
352
353 Some(DecodedFunction {
354 name: func.name.clone(),
355 body: decoder.decode(),
356 })
357 }
358}
359
360#[derive(Debug)]
362pub struct ZwcBuilder {
363 functions: Vec<(String, Vec<u8>)>, }
365
366impl ZwcBuilder {
367 pub fn new() -> Self {
368 Self {
369 functions: Vec::new(),
370 }
371 }
372
373 pub fn add_source(&mut self, name: &str, source: &str) {
375 self.functions
376 .push((name.to_string(), source.as_bytes().to_vec()));
377 }
378
379 pub fn add_file(&mut self, path: &std::path::Path) -> io::Result<()> {
381 let name = path
382 .file_name()
383 .and_then(|n| n.to_str())
384 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Invalid filename"))?;
385 let source = std::fs::read(path)?;
386 self.functions.push((name.to_string(), source));
387 Ok(())
388 }
389
390 pub fn write<P: AsRef<std::path::Path>>(&self, path: P) -> io::Result<()> {
394 use std::io::Write;
395
396 let mut file = std::fs::File::create(path)?;
397
398 file.write_all(&FD_MAGIC.to_ne_bytes())?;
400
401 file.write_all(&[0u8])?;
403
404 file.write_all(&[0u8; 3])?;
406
407 let version = env!("CARGO_PKG_VERSION");
409 let version_bytes = version.as_bytes();
410 file.write_all(version_bytes)?;
411 file.write_all(&[0u8])?; let padding = (4 - ((version_bytes.len() + 1) % 4)) % 4;
414 file.write_all(&vec![0u8; padding])?;
415
416 let mut header_words = FD_PRELEN;
418 for (name, _) in &self.functions {
419 header_words += 6 + (name.len() + 1 + 3) / 4;
421 }
422
423 file.write_all(&(header_words as u32).to_ne_bytes())?;
425
426 let mut data_offset = header_words;
428 let mut func_data: Vec<(u32, u32, Vec<u8>)> = Vec::new(); for (name, source) in &self.functions {
432 let source_words = (source.len() + 3) / 4;
433
434 file.write_all(&(data_offset as u32).to_ne_bytes())?; file.write_all(&(source.len() as u32).to_ne_bytes())?; file.write_all(&0u32.to_ne_bytes())?; file.write_all(&0u32.to_ne_bytes())?; let hlen = 6 + (name.len() + 1 + 3) / 4;
440 file.write_all(&(hlen as u32).to_ne_bytes())?; file.write_all(&0u32.to_ne_bytes())?; file.write_all(name.as_bytes())?;
445 file.write_all(&[0u8])?;
446 let name_padding = (4 - ((name.len() + 1) % 4)) % 4;
447 file.write_all(&vec![0u8; name_padding])?;
448
449 func_data.push((data_offset as u32, source.len() as u32, source.clone()));
450 data_offset += source_words;
451 }
452
453 for (_, _, data) in &func_data {
455 file.write_all(data)?;
456 let padding = (4 - (data.len() % 4)) % 4;
457 file.write_all(&vec![0u8; padding])?;
458 }
459
460 Ok(())
461 }
462}
463
464#[derive(Debug, Clone)]
465pub struct DecodedFunction {
466 pub name: String,
467 pub body: Vec<DecodedOp>,
468}
469
470#[derive(Debug, Clone)]
471pub enum DecodedOp {
472 End,
473 LineNo(u32),
474 List {
475 list_type: u32,
476 is_end: bool,
477 ops: Vec<DecodedOp>,
478 },
479 Sublist {
480 sublist_type: u32,
481 negated: bool,
482 ops: Vec<DecodedOp>,
483 },
484 Pipe {
485 lineno: u32,
486 ops: Vec<DecodedOp>,
487 },
488 Redir {
489 redir_type: u32,
490 fd: i32,
491 target: String,
492 varid: Option<String>,
493 },
494 Assign {
495 name: String,
496 value: String,
497 },
498 AssignArray {
499 name: String,
500 values: Vec<String>,
501 },
502 Simple {
503 args: Vec<String>,
504 },
505 Typeset {
506 args: Vec<String>,
507 assigns: Vec<DecodedOp>,
508 },
509 Subsh {
510 ops: Vec<DecodedOp>,
511 },
512 Cursh {
513 ops: Vec<DecodedOp>,
514 },
515 Timed {
516 cmd: Option<Box<DecodedOp>>,
517 },
518 FuncDef {
519 name: String,
520 body: Vec<DecodedOp>,
521 },
522 For {
523 var: String,
524 list: Vec<String>,
525 body: Vec<DecodedOp>,
526 },
527 ForCond {
528 init: String,
529 cond: String,
530 step: String,
531 body: Vec<DecodedOp>,
532 },
533 Select {
534 var: String,
535 list: Vec<String>,
536 body: Vec<DecodedOp>,
537 },
538 While {
539 cond: Vec<DecodedOp>,
540 body: Vec<DecodedOp>,
541 is_until: bool,
542 },
543 Repeat {
544 count: String,
545 body: Vec<DecodedOp>,
546 },
547 Case {
548 word: String,
549 cases: Vec<(String, Vec<DecodedOp>)>,
550 },
551 CaseItem {
552 pattern: String,
553 terminator: u32,
554 body: Vec<DecodedOp>,
555 },
556 If {
557 if_type: u32,
558 conditions: Vec<(Vec<DecodedOp>, Vec<DecodedOp>)>,
559 else_body: Option<Vec<DecodedOp>>,
560 },
561 Cond {
562 cond_type: u32,
563 args: Vec<String>,
564 },
565 Arith {
566 expr: String,
567 },
568 AutoFn,
569 Try {
570 try_body: Vec<DecodedOp>,
571 always_body: Vec<DecodedOp>,
572 },
573 Unknown {
574 code: u32,
575 data: u32,
576 },
577}
578
579pub struct WordcodeDecoder<'a> {
580 code: &'a [u32],
581 strings: &'a [u8],
582 strs_base: usize,
583 pub pos: usize,
584}
585
586impl<'a> WordcodeDecoder<'a> {
587 pub fn new(code: &'a [u32], strings: &'a [u8], strs_base: usize) -> Self {
588 Self {
589 code,
590 strings,
591 strs_base,
592 pos: 0,
593 }
594 }
595
596 pub fn at_end(&self) -> bool {
597 self.pos >= self.code.len()
598 }
599
600 pub fn peek(&self) -> Option<u32> {
601 self.code.get(self.pos).copied()
602 }
603
604 pub fn next(&mut self) -> Option<u32> {
605 let val = self.code.get(self.pos).copied();
606 if val.is_some() {
607 self.pos += 1;
608 }
609 val
610 }
611
612 pub fn read_string(&mut self) -> String {
613 let wc = self.next().unwrap_or(0);
614 self.decode_string(wc)
615 }
616
617 pub fn decode_string(&self, wc: u32) -> String {
618 if wc == 6 || wc == 7 {
624 return String::new();
625 }
626
627 if (wc & 2) != 0 {
628 let mut s = String::new();
630 let c1 = ((wc >> 3) & 0xff) as u8;
631 let c2 = ((wc >> 11) & 0xff) as u8;
632 let c3 = ((wc >> 19) & 0xff) as u8;
633 if c1 != 0 {
634 s.push(c1 as char);
635 }
636 if c2 != 0 {
637 s.push(c2 as char);
638 }
639 if c3 != 0 {
640 s.push(c3 as char);
641 }
642 s
643 } else {
644 let offset = (wc >> 2) as usize;
646 self.get_string_at(self.strs_base + offset)
647 }
648 }
649
650 fn get_string_at(&self, offset: usize) -> String {
651 if offset >= self.strings.len() {
652 return String::new();
653 }
654
655 let end = self.strings[offset..]
656 .iter()
657 .position(|&b| b == 0)
658 .map(|p| offset + p)
659 .unwrap_or(self.strings.len());
660
661 let raw = &self.strings[offset..end];
663 untokenize(raw)
664 }
665
666 pub fn decode(&self) -> Vec<DecodedOp> {
668 let mut decoder = WordcodeDecoder::new(self.code, self.strings, self.strs_base);
669 decoder.decode_program()
670 }
671
672 fn decode_program(&mut self) -> Vec<DecodedOp> {
673 let mut ops = Vec::new();
674
675 while let Some(wc) = self.peek() {
676 let code = wc_code(wc);
677
678 if code == WC_END {
679 self.next();
680 ops.push(DecodedOp::End);
681 break;
682 }
683
684 if let Some(op) = self.decode_next_op() {
685 ops.push(op);
686 } else {
687 break;
688 }
689 }
690
691 ops
692 }
693
694 fn decode_next_op(&mut self) -> Option<DecodedOp> {
695 let wc = self.next()?;
696 let code = wc_code(wc);
697 let data = wc_data(wc);
698
699 let op = match code {
700 WC_END => DecodedOp::End,
701 WC_LIST => self.decode_list(data),
702 WC_SUBLIST => self.decode_sublist(data),
703 WC_PIPE => self.decode_pipe(data),
704 WC_REDIR => self.decode_redir(data),
705 WC_ASSIGN => self.decode_assign(data),
706 WC_SIMPLE => self.decode_simple(data),
707 WC_TYPESET => self.decode_typeset(data),
708 WC_SUBSH => self.decode_subsh(data),
709 WC_CURSH => self.decode_cursh(data),
710 WC_TIMED => self.decode_timed(data),
711 WC_FUNCDEF => self.decode_funcdef(data),
712 WC_FOR => self.decode_for(data),
713 WC_SELECT => self.decode_select(data),
714 WC_WHILE => self.decode_while(data),
715 WC_REPEAT => self.decode_repeat(data),
716 WC_CASE => self.decode_case(data),
717 WC_IF => self.decode_if(data),
718 WC_COND => self.decode_cond(data),
719 WC_ARITH => self.decode_arith(),
720 WC_AUTOFN => DecodedOp::AutoFn,
721 WC_TRY => self.decode_try(data),
722 _ => DecodedOp::Unknown { code, data },
723 };
724
725 Some(op)
726 }
727
728 fn decode_list(&mut self, data: u32) -> DecodedOp {
729 let list_type = data & ((1 << WC_LIST_FREE) - 1);
730 let is_end = (list_type & Z_END) != 0;
731 let is_simple = (list_type & Z_SIMPLE) != 0;
732 let _skip = data >> WC_LIST_FREE;
733
734 let mut body = Vec::new();
735
736 if is_simple {
737 let lineno = self.next().unwrap_or(0);
739 body.push(DecodedOp::LineNo(lineno));
740 }
741
742 if !is_simple {
744 while let Some(wc) = self.peek() {
745 let c = wc_code(wc);
746 if c == WC_END || c == WC_LIST {
747 break;
748 }
749 if let Some(op) = self.decode_next_op() {
750 body.push(op);
751 } else {
752 break;
753 }
754 }
755 }
756
757 DecodedOp::List {
758 list_type,
759 is_end,
760 ops: body,
761 }
762 }
763
764 fn decode_sublist(&mut self, data: u32) -> DecodedOp {
765 let sublist_type = data & 3;
766 let flags = data & 0x1c;
767 let negated = (flags & WC_SUBLIST_NOT) != 0;
768 let is_simple = (flags & WC_SUBLIST_SIMPLE) != 0;
769 let _skip = data >> WC_SUBLIST_FREE;
770
771 let mut body = Vec::new();
772
773 if is_simple {
774 let lineno = self.next().unwrap_or(0);
776 body.push(DecodedOp::LineNo(lineno));
777 }
778
779 DecodedOp::Sublist {
780 sublist_type,
781 negated,
782 ops: body,
783 }
784 }
785
786 fn decode_pipe(&mut self, data: u32) -> DecodedOp {
787 let pipe_type = data & 1;
788 let lineno = data >> 1;
789 let _is_end = pipe_type == WC_PIPE_END;
790
791 DecodedOp::Pipe {
792 lineno,
793 ops: vec![],
794 }
795 }
796
797 fn decode_redir(&mut self, data: u32) -> DecodedOp {
798 let redir_type = data & 0x1f; let has_varid = (data & 0x20) != 0; let from_heredoc = (data & 0x40) != 0; let fd = self.next().unwrap_or(0) as i32;
803 let target = self.read_string();
804
805 let varid = if has_varid {
806 Some(self.read_string())
807 } else {
808 None
809 };
810
811 if from_heredoc {
812 self.next();
814 self.next();
815 }
816
817 DecodedOp::Redir {
818 redir_type,
819 fd,
820 target,
821 varid,
822 }
823 }
824
825 fn decode_assign(&mut self, data: u32) -> DecodedOp {
826 let is_array = (data & 1) != 0;
827 let num_elements = (data >> 2) as usize;
828
829 let name = self.read_string();
830
831 if is_array {
832 let mut values = Vec::with_capacity(num_elements);
833 for _ in 0..num_elements {
834 values.push(self.read_string());
835 }
836 DecodedOp::AssignArray { name, values }
837 } else {
838 let value = self.read_string();
839 DecodedOp::Assign { name, value }
840 }
841 }
842
843 fn decode_simple(&mut self, data: u32) -> DecodedOp {
844 let argc = data as usize;
845 let mut args = Vec::with_capacity(argc);
846 for _ in 0..argc {
847 args.push(self.read_string());
848 }
849 DecodedOp::Simple { args }
850 }
851
852 fn decode_typeset(&mut self, data: u32) -> DecodedOp {
853 let argc = data as usize;
854 let mut args = Vec::with_capacity(argc);
855 for _ in 0..argc {
856 args.push(self.read_string());
857 }
858
859 let num_assigns = self.next().unwrap_or(0) as usize;
861 let mut assigns = Vec::with_capacity(num_assigns);
862
863 for _ in 0..num_assigns {
864 if let Some(op) = self.decode_next_op() {
865 assigns.push(op);
866 }
867 }
868
869 DecodedOp::Typeset { args, assigns }
870 }
871
872 fn decode_subsh(&mut self, data: u32) -> DecodedOp {
873 let skip = data as usize;
874 let end_pos = self.pos + skip;
875
876 let mut body = Vec::new();
877 while self.pos < end_pos && !self.at_end() {
878 if let Some(op) = self.decode_next_op() {
879 body.push(op);
880 } else {
881 break;
882 }
883 }
884
885 DecodedOp::Subsh { ops: body }
886 }
887
888 fn decode_cursh(&mut self, data: u32) -> DecodedOp {
889 let skip = data as usize;
890 let end_pos = self.pos + skip;
891
892 let mut body = Vec::new();
893 while self.pos < end_pos && !self.at_end() {
894 if let Some(op) = self.decode_next_op() {
895 body.push(op);
896 } else {
897 break;
898 }
899 }
900
901 DecodedOp::Cursh { ops: body }
902 }
903
904 fn decode_timed(&mut self, data: u32) -> DecodedOp {
905 let timed_type = data;
906 let has_pipe = timed_type == 1; if has_pipe {
909 if let Some(op) = self.decode_next_op() {
911 return DecodedOp::Timed {
912 cmd: Some(Box::new(op)),
913 };
914 }
915 }
916
917 DecodedOp::Timed { cmd: None }
918 }
919
920 fn decode_funcdef(&mut self, data: u32) -> DecodedOp {
921 let skip = data as usize;
922
923 let num_names = self.next().unwrap_or(0) as usize;
924 let mut names = Vec::with_capacity(num_names);
925 for _ in 0..num_names {
926 names.push(self.read_string());
927 }
928
929 let _strs_offset = self.next();
931 let _strs_len = self.next();
932 let _npats = self.next();
933 let _tracing = self.next();
934
935 let _end_pos = self.pos + skip.saturating_sub(num_names + 5);
937
938 let name = names.first().cloned().unwrap_or_default();
939
940 DecodedOp::FuncDef { name, body: vec![] }
941 }
942
943 fn decode_for(&mut self, data: u32) -> DecodedOp {
944 let for_type = data & 3;
945 let _skip = data >> 2;
946
947 match for_type {
948 WC_FOR_COND => {
949 let init = self.read_string();
950 let cond = self.read_string();
951 let step = self.read_string();
952 DecodedOp::ForCond {
953 init,
954 cond,
955 step,
956 body: vec![],
957 }
958 }
959 WC_FOR_LIST => {
960 let var = self.read_string();
961 let num_words = self.next().unwrap_or(0) as usize;
962 let mut list = Vec::with_capacity(num_words);
963 for _ in 0..num_words {
964 list.push(self.read_string());
965 }
966 DecodedOp::For {
967 var,
968 list,
969 body: vec![],
970 }
971 }
972 _ => {
973 let var = self.read_string();
975 DecodedOp::For {
976 var,
977 list: vec![],
978 body: vec![],
979 }
980 }
981 }
982 }
983
984 fn decode_select(&mut self, data: u32) -> DecodedOp {
985 let select_type = data & 1;
986 let _skip = data >> 1;
987
988 let var = self.read_string();
989 let list = if select_type == 1 {
990 let num_words = self.next().unwrap_or(0) as usize;
992 let mut words = Vec::with_capacity(num_words);
993 for _ in 0..num_words {
994 words.push(self.read_string());
995 }
996 words
997 } else {
998 vec![]
999 };
1000
1001 DecodedOp::Select {
1002 var,
1003 list,
1004 body: vec![],
1005 }
1006 }
1007
1008 fn decode_while(&mut self, data: u32) -> DecodedOp {
1009 let is_until = (data & 1) != 0;
1010 let _skip = data >> 1;
1011 DecodedOp::While {
1012 cond: vec![],
1013 body: vec![],
1014 is_until,
1015 }
1016 }
1017
1018 fn decode_repeat(&mut self, data: u32) -> DecodedOp {
1019 let _skip = data;
1020 let count = self.read_string();
1021 DecodedOp::Repeat {
1022 count,
1023 body: vec![],
1024 }
1025 }
1026
1027 fn decode_case(&mut self, data: u32) -> DecodedOp {
1028 let case_type = data & 7;
1029 let _skip = data >> WC_CASE_FREE;
1030
1031 if case_type == WC_CASE_HEAD {
1032 let word = self.read_string();
1033 DecodedOp::Case {
1034 word,
1035 cases: vec![],
1036 }
1037 } else {
1038 let pattern = self.read_string();
1040 let _npats = self.next();
1041 DecodedOp::CaseItem {
1042 pattern,
1043 terminator: case_type,
1044 body: vec![],
1045 }
1046 }
1047 }
1048
1049 fn decode_if(&mut self, data: u32) -> DecodedOp {
1050 let if_type = data & 3;
1051 let _skip = data >> 2;
1052
1053 DecodedOp::If {
1054 if_type,
1055 conditions: vec![],
1056 else_body: None,
1057 }
1058 }
1059
1060 fn decode_cond(&mut self, data: u32) -> DecodedOp {
1061 let cond_type = data & 127;
1062 let _skip = data >> 7;
1063
1064 let args = match cond_type {
1066 1 => vec![],
1068 2 | 3 => vec![],
1070 _ if cond_type >= 7 => {
1072 vec![self.read_string(), self.read_string()]
1073 }
1074 _ => {
1076 vec![self.read_string()]
1077 }
1078 };
1079
1080 DecodedOp::Cond { cond_type, args }
1081 }
1082
1083 fn decode_arith(&mut self) -> DecodedOp {
1084 let expr = self.read_string();
1085 DecodedOp::Arith { expr }
1086 }
1087
1088 fn decode_try(&mut self, data: u32) -> DecodedOp {
1089 let _skip = data;
1090 DecodedOp::Try {
1091 try_body: vec![],
1092 always_body: vec![],
1093 }
1094 }
1095}
1096
1097pub fn dump_zwc_info<P: AsRef<Path>>(path: P) -> io::Result<()> {
1098 let zwc = ZwcFile::load(&path)?;
1099
1100 println!("ZWC file: {:?}", path.as_ref());
1101 println!(
1102 " Magic: 0x{:08x} ({})",
1103 zwc.header.magic,
1104 if zwc.header.magic == FD_MAGIC {
1105 "native"
1106 } else {
1107 "swapped"
1108 }
1109 );
1110 println!(" Version: zsh-{}", zwc.header.version);
1111 println!(" Header length: {} words", zwc.header.header_len);
1112 println!(" Wordcode size: {} words", zwc.wordcode.len());
1113 println!(" Functions: {}", zwc.functions.len());
1114
1115 for func in &zwc.functions {
1116 println!(
1117 " {} (offset={}, len={}, npats={})",
1118 func.name, func.start, func.len, func.npats
1119 );
1120 }
1121
1122 Ok(())
1123}
1124
1125pub fn dump_zwc_function<P: AsRef<Path>>(path: P, func_name: &str) -> io::Result<()> {
1126 let zwc = ZwcFile::load(&path)?;
1127
1128 let func = zwc.get_function(func_name).ok_or_else(|| {
1129 io::Error::new(
1130 io::ErrorKind::NotFound,
1131 format!("Function '{}' not found", func_name),
1132 )
1133 })?;
1134
1135 println!("Function: {}", func.name);
1136 println!(" Offset: {} words", func.start);
1137 println!(" Length: {} words", func.len);
1138 println!(" Patterns: {}", func.npats);
1139 println!(" Strings offset: {}", func.strs_offset);
1140
1141 let header_words = zwc.header.header_len as usize;
1143 let start_idx = (func.start as usize).saturating_sub(header_words);
1144 let end_idx = start_idx + func.len as usize;
1145
1146 if start_idx < zwc.wordcode.len() {
1147 println!("\n Wordcode:");
1148 let end = end_idx.min(zwc.wordcode.len());
1149 for (i, &wc) in zwc.wordcode[start_idx..end].iter().enumerate().take(50) {
1150 let code = wc_code(wc);
1151 let data = wc_data(wc);
1152 let code_name = match code {
1153 WC_END => "END",
1154 WC_LIST => "LIST",
1155 WC_SUBLIST => "SUBLIST",
1156 WC_PIPE => "PIPE",
1157 WC_REDIR => "REDIR",
1158 WC_ASSIGN => "ASSIGN",
1159 WC_SIMPLE => "SIMPLE",
1160 WC_TYPESET => "TYPESET",
1161 WC_SUBSH => "SUBSH",
1162 WC_CURSH => "CURSH",
1163 WC_TIMED => "TIMED",
1164 WC_FUNCDEF => "FUNCDEF",
1165 WC_FOR => "FOR",
1166 WC_SELECT => "SELECT",
1167 WC_WHILE => "WHILE",
1168 WC_REPEAT => "REPEAT",
1169 WC_CASE => "CASE",
1170 WC_IF => "IF",
1171 WC_COND => "COND",
1172 WC_ARITH => "ARITH",
1173 WC_AUTOFN => "AUTOFN",
1174 WC_TRY => "TRY",
1175 _ => "???",
1176 };
1177 println!(" [{:3}] 0x{:08x} = {} (data={})", i, wc, code_name, data);
1178 }
1179 if end - start_idx > 50 {
1180 println!(" ... ({} more words)", end - start_idx - 50);
1181 }
1182 }
1183
1184 if let Some(decoded) = zwc.decode_function(func) {
1186 println!("\n Decoded ops:");
1187 for (i, op) in decoded.body.iter().enumerate().take(20) {
1188 println!(" [{:2}] {:?}", i, op);
1189 }
1190 if decoded.body.len() > 20 {
1191 println!(" ... ({} more ops)", decoded.body.len() - 20);
1192 }
1193 }
1194
1195 Ok(())
1196}
1197
1198impl DecodedOp {
1200 pub fn to_shell_command(&self) -> Option<ShellCommand> {
1201 match self {
1202 DecodedOp::Simple { args } => {
1203 if args.is_empty() {
1204 return None;
1205 }
1206 Some(ShellCommand::Simple(SimpleCommand {
1207 assignments: vec![],
1208 words: args.iter().map(|s| ShellWord::Literal(s.clone())).collect(),
1209 redirects: vec![],
1210 }))
1211 }
1212
1213 DecodedOp::Assign { name, value } => Some(ShellCommand::Simple(SimpleCommand {
1214 assignments: vec![(name.clone(), ShellWord::Literal(value.clone()), false)],
1215 words: vec![],
1216 redirects: vec![],
1217 })),
1218
1219 DecodedOp::AssignArray { name, values } => {
1220 let array_word = ShellWord::Concat(
1221 values
1222 .iter()
1223 .map(|s| ShellWord::Literal(s.clone()))
1224 .collect(),
1225 );
1226 Some(ShellCommand::Simple(SimpleCommand {
1227 assignments: vec![(name.clone(), array_word, false)],
1228 words: vec![],
1229 redirects: vec![],
1230 }))
1231 }
1232
1233 DecodedOp::List { ops, .. } => {
1234 let commands: Vec<(ShellCommand, ListOp)> = ops
1235 .iter()
1236 .filter_map(|op| op.to_shell_command())
1237 .map(|cmd| (cmd, ListOp::Semi))
1238 .collect();
1239
1240 if commands.is_empty() {
1241 None
1242 } else if commands.len() == 1 {
1243 Some(commands.into_iter().next().unwrap().0)
1244 } else {
1245 Some(ShellCommand::List(commands))
1246 }
1247 }
1248
1249 DecodedOp::Sublist { ops, negated, .. } => {
1250 let commands: Vec<ShellCommand> =
1251 ops.iter().filter_map(|op| op.to_shell_command()).collect();
1252
1253 if commands.is_empty() {
1254 None
1255 } else {
1256 Some(ShellCommand::Pipeline(commands, *negated))
1257 }
1258 }
1259
1260 DecodedOp::Pipe { ops, .. } => {
1261 let commands: Vec<ShellCommand> =
1262 ops.iter().filter_map(|op| op.to_shell_command()).collect();
1263
1264 if commands.is_empty() {
1265 None
1266 } else if commands.len() == 1 {
1267 Some(commands.into_iter().next().unwrap())
1268 } else {
1269 Some(ShellCommand::Pipeline(commands, false))
1270 }
1271 }
1272
1273 DecodedOp::Typeset { args, assigns } => {
1274 let mut words: Vec<ShellWord> =
1276 args.iter().map(|s| ShellWord::Literal(s.clone())).collect();
1277
1278 for assign in assigns {
1280 if let DecodedOp::Assign { name, value } = assign {
1281 words.push(ShellWord::Literal(format!("{}={}", name, value)));
1282 }
1283 }
1284
1285 Some(ShellCommand::Simple(SimpleCommand {
1286 assignments: vec![],
1287 words,
1288 redirects: vec![],
1289 }))
1290 }
1291
1292 DecodedOp::Subsh { ops } => {
1293 let commands: Vec<ShellCommand> =
1294 ops.iter().filter_map(|op| op.to_shell_command()).collect();
1295 Some(ShellCommand::Compound(CompoundCommand::Subshell(commands)))
1296 }
1297
1298 DecodedOp::Cursh { ops } => {
1299 let commands: Vec<ShellCommand> =
1300 ops.iter().filter_map(|op| op.to_shell_command()).collect();
1301 Some(ShellCommand::Compound(CompoundCommand::BraceGroup(
1302 commands,
1303 )))
1304 }
1305
1306 DecodedOp::For { var, list, body } => {
1307 let words = if list.is_empty() {
1308 None
1309 } else {
1310 Some(list.iter().map(|s| ShellWord::Literal(s.clone())).collect())
1311 };
1312 let body_cmds: Vec<ShellCommand> =
1313 body.iter().filter_map(|op| op.to_shell_command()).collect();
1314 Some(ShellCommand::Compound(CompoundCommand::For {
1315 var: var.clone(),
1316 words,
1317 body: body_cmds,
1318 }))
1319 }
1320
1321 DecodedOp::ForCond {
1322 init,
1323 cond,
1324 step,
1325 body,
1326 } => {
1327 let body_cmds: Vec<ShellCommand> =
1328 body.iter().filter_map(|op| op.to_shell_command()).collect();
1329 Some(ShellCommand::Compound(CompoundCommand::ForArith {
1330 init: init.clone(),
1331 cond: cond.clone(),
1332 step: step.clone(),
1333 body: body_cmds,
1334 }))
1335 }
1336
1337 DecodedOp::While {
1338 cond,
1339 body,
1340 is_until,
1341 } => {
1342 let cond_cmds: Vec<ShellCommand> =
1343 cond.iter().filter_map(|op| op.to_shell_command()).collect();
1344 let body_cmds: Vec<ShellCommand> =
1345 body.iter().filter_map(|op| op.to_shell_command()).collect();
1346
1347 if *is_until {
1348 Some(ShellCommand::Compound(CompoundCommand::Until {
1349 condition: cond_cmds,
1350 body: body_cmds,
1351 }))
1352 } else {
1353 Some(ShellCommand::Compound(CompoundCommand::While {
1354 condition: cond_cmds,
1355 body: body_cmds,
1356 }))
1357 }
1358 }
1359
1360 DecodedOp::FuncDef { name, body } => {
1361 let body_cmds: Vec<ShellCommand> =
1362 body.iter().filter_map(|op| op.to_shell_command()).collect();
1363
1364 let func_body = if body_cmds.is_empty() {
1365 ShellCommand::Simple(SimpleCommand {
1367 assignments: vec![],
1368 words: vec![ShellWord::Literal(":".to_string())],
1369 redirects: vec![],
1370 })
1371 } else if body_cmds.len() == 1 {
1372 body_cmds.into_iter().next().unwrap()
1373 } else {
1374 ShellCommand::List(body_cmds.into_iter().map(|c| (c, ListOp::Semi)).collect())
1375 };
1376
1377 Some(ShellCommand::FunctionDef(name.clone(), Box::new(func_body)))
1378 }
1379
1380 DecodedOp::Arith { expr } => {
1381 Some(ShellCommand::Compound(CompoundCommand::Arith(expr.clone())))
1382 }
1383
1384 DecodedOp::End | DecodedOp::LineNo(_) | DecodedOp::AutoFn => None,
1386
1387 DecodedOp::Redir { .. } => {
1388 None
1390 }
1391
1392 DecodedOp::If { .. }
1393 | DecodedOp::Case { .. }
1394 | DecodedOp::CaseItem { .. }
1395 | DecodedOp::Select { .. }
1396 | DecodedOp::Cond { .. }
1397 | DecodedOp::Repeat { .. }
1398 | DecodedOp::Try { .. }
1399 | DecodedOp::Timed { .. }
1400 | DecodedOp::Unknown { .. } => {
1401 None
1403 }
1404 }
1405 }
1406}
1407
1408#[allow(dead_code)]
1410fn redir_type_to_op(redir_type: u32) -> Option<RedirectOp> {
1411 const REDIR_WRITE: u32 = 0;
1413 const REDIR_WRITENOW: u32 = 1;
1414 const REDIR_APP: u32 = 2;
1415 const REDIR_APPNOW: u32 = 3;
1416 const REDIR_ERRWRITE: u32 = 4;
1417 const REDIR_ERRWRITENOW: u32 = 5;
1418 const REDIR_ERRAPP: u32 = 6;
1419 const REDIR_ERRAPPNOW: u32 = 7;
1420 const REDIR_READWRITE: u32 = 8;
1421 const REDIR_READ: u32 = 9;
1422 const REDIR_HEREDOC: u32 = 10;
1423 const REDIR_HEREDOCDASH: u32 = 11;
1424 const REDIR_HERESTR: u32 = 12;
1425 const REDIR_MERGEIN: u32 = 13;
1426 const REDIR_MERGEOUT: u32 = 14;
1427 const REDIR_CLOSE: u32 = 15;
1428 const REDIR_INPIPE: u32 = 16;
1429 const REDIR_OUTPIPE: u32 = 17;
1430
1431 match redir_type {
1432 REDIR_WRITE | REDIR_WRITENOW => Some(RedirectOp::Write),
1433 REDIR_APP | REDIR_APPNOW => Some(RedirectOp::Append),
1434 REDIR_ERRWRITE | REDIR_ERRWRITENOW => Some(RedirectOp::WriteBoth),
1435 REDIR_ERRAPP | REDIR_ERRAPPNOW => Some(RedirectOp::AppendBoth),
1436 REDIR_READWRITE => Some(RedirectOp::ReadWrite),
1437 REDIR_READ => Some(RedirectOp::Read),
1438 REDIR_HEREDOC | REDIR_HEREDOCDASH => Some(RedirectOp::HereDoc),
1439 REDIR_HERESTR => Some(RedirectOp::HereString),
1440 REDIR_MERGEIN => Some(RedirectOp::DupRead),
1441 REDIR_MERGEOUT => Some(RedirectOp::DupWrite),
1442 REDIR_CLOSE | REDIR_INPIPE | REDIR_OUTPIPE => None, _ => None,
1444 }
1445}
1446
1447impl DecodedFunction {
1448 pub fn to_shell_function(&self) -> Option<ShellCommand> {
1450 let body_cmds: Vec<ShellCommand> = self
1451 .body
1452 .iter()
1453 .filter_map(|op| op.to_shell_command())
1454 .collect();
1455
1456 let func_body = if body_cmds.is_empty() {
1457 ShellCommand::Simple(SimpleCommand {
1458 assignments: vec![],
1459 words: vec![ShellWord::Literal(":".to_string())],
1460 redirects: vec![],
1461 })
1462 } else if body_cmds.len() == 1 {
1463 body_cmds.into_iter().next().unwrap()
1464 } else {
1465 ShellCommand::List(body_cmds.into_iter().map(|c| (c, ListOp::Semi)).collect())
1466 };
1467
1468 let name = self
1470 .name
1471 .rsplit('/')
1472 .next()
1473 .unwrap_or(&self.name)
1474 .to_string();
1475
1476 Some(ShellCommand::FunctionDef(name, Box::new(func_body)))
1477 }
1478}
1479
1480#[cfg(test)]
1481mod tests {
1482 use super::*;
1483
1484 #[test]
1485 fn test_wc_code() {
1486 assert_eq!(wc_code(WC_LIST), WC_LIST);
1487 assert_eq!(wc_code(WC_SIMPLE | (5 << WC_CODEBITS)), WC_SIMPLE);
1488 }
1489
1490 #[test]
1491 fn test_wc_data() {
1492 let wc = WC_SIMPLE | (42 << WC_CODEBITS);
1493 assert_eq!(wc_data(wc), 42);
1494 }
1495
1496 #[test]
1497 fn test_load_src_zwc() {
1498 let path = "/Users/wizard/.zinit/plugins/MenkeTechnologies---zsh-more-completions/src.zwc";
1499 if !std::path::Path::new(path).exists() {
1500 eprintln!("Skipping test - {} not found", path);
1501 return;
1502 }
1503
1504 let zwc = ZwcFile::load(path).expect("Failed to load src.zwc");
1505 println!("Loaded {} functions from src.zwc", zwc.function_count());
1506
1507 assert!(
1509 zwc.function_count() > 1000,
1510 "Expected > 1000 functions, got {}",
1511 zwc.function_count()
1512 );
1513
1514 let funcs = zwc.list_functions();
1516 println!("First 10 functions: {:?}", &funcs[..10.min(funcs.len())]);
1517
1518 if let Some(func) = zwc.get_function("_ls") {
1520 println!("Found _ls function");
1521 if let Some(decoded) = zwc.decode_function(func) {
1522 println!("Decoded _ls: {} ops", decoded.body.len());
1523 }
1524 }
1525 }
1526
1527 #[test]
1528 fn test_load_zshrc_zwc() {
1529 let home = std::env::var("HOME").unwrap_or_default();
1530 let path = format!("{}/.zshrc.zwc", home);
1531 if !std::path::Path::new(&path).exists() {
1532 eprintln!("Skipping test - {} not found", path);
1533 return;
1534 }
1535
1536 let zwc = ZwcFile::load(&path).expect("Failed to load .zshrc.zwc");
1537 println!("Loaded {} functions from .zshrc.zwc", zwc.function_count());
1538
1539 for name in zwc.list_functions() {
1540 println!(" Function: {}", name);
1541 if let Some(func) = zwc.get_function(name) {
1542 if let Some(decoded) = zwc.decode_function(func) {
1543 println!(" Decoded: {} ops", decoded.body.len());
1544 for (i, op) in decoded.body.iter().take(3).enumerate() {
1545 if let Some(cmd) = op.to_shell_command() {
1546 println!(" [{}] -> ShellCommand OK", i);
1547 } else {
1548 println!(" [{}] {:?}", i, op);
1549 }
1550 }
1551 }
1552 }
1553 }
1554 }
1555
1556 #[test]
1557 fn test_load_zshenv_zwc() {
1558 let home = std::env::var("HOME").unwrap_or_default();
1559 let path = format!("{}/.zshenv.zwc", home);
1560 if !std::path::Path::new(&path).exists() {
1561 eprintln!("Skipping test - {} not found", path);
1562 return;
1563 }
1564
1565 let zwc = ZwcFile::load(&path).expect("Failed to load .zshenv.zwc");
1566 println!("Loaded {} functions from .zshenv.zwc", zwc.function_count());
1567
1568 for name in zwc.list_functions() {
1569 println!(" Function: {}", name);
1570 if let Some(func) = zwc.get_function(name) {
1571 if let Some(decoded) = zwc.decode_function(func) {
1572 println!(" Decoded: {} ops", decoded.body.len());
1573 }
1574 }
1575 }
1576 }
1577}