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