1use crate::parse::{
25 CaseTerminator, CompoundCommand, ListOp, Redirect, RedirectOp, ShellCommand, ShellWord,
26 SimpleCommand,
27};
28use std::fs::File;
29use std::io::{self, Read, Seek, SeekFrom};
30use std::path::Path;
31
32const FD_MAGIC: u32 = 0x04050607;
33const FD_OMAGIC: u32 = 0x07060504; const FD_PRELEN: usize = 12;
35use crate::ported::zsh_h::{
36 Bang, Bar, Bnull, Comma, Dash, Dnull, Equals, Hat, Inang, Inbrace, Inbrack, Inpar, Nularg,
37 Outang, Outbrace, Outbrack, Outpar, Pound, Quest, Snull, Star, Stringg, Tick, Tilde, WC_ARITH,
38 WC_ASSIGN, WC_ASSIGN_ARRAY, WC_ASSIGN_INC, WC_ASSIGN_SCALAR, WC_AUTOFN, WC_CASE, WC_CASE_AND,
39 WC_CASE_FREE, WC_CASE_HEAD, WC_CASE_OR, WC_CASE_TESTAND, WC_CODEBITS, WC_COND, WC_CURSH,
40 WC_END, WC_FOR, WC_FOR_COND, WC_FOR_LIST, WC_FOR_PPARAM, WC_FUNCDEF, WC_IF, WC_IF_ELIF,
41 WC_IF_ELSE, WC_IF_HEAD, WC_IF_IF, WC_LIST, WC_LIST_FREE, WC_PIPE, WC_PIPE_END, WC_PIPE_MID,
42 WC_REDIR, WC_REPEAT, WC_SELECT, WC_SELECT_LIST, WC_SELECT_PPARAM, WC_SIMPLE, WC_SUBLIST,
43 WC_SUBLIST_AND, WC_SUBLIST_COPROC, WC_SUBLIST_END, WC_SUBLIST_FREE, WC_SUBLIST_NOT,
44 WC_SUBLIST_OR, WC_SUBLIST_SIMPLE, WC_SUBSH, WC_TIMED, WC_TRY, WC_TYPESET, WC_WHILE,
45 WC_WHILE_UNTIL, WC_WHILE_WHILE,
46};
47use crate::ported::zsh_h::{
48 COND_AND, COND_EF, COND_EQ, COND_GE, COND_GT, COND_LE, COND_LT, COND_MOD, COND_MODI, COND_NE,
49 COND_NOT, COND_NT, COND_OR, COND_OT, COND_REGEX, COND_STRDEQ, COND_STREQ, COND_STRGTR,
50 COND_STRLT, COND_STRNEQ,
51};
52const Z_END: u32 = crate::ported::zsh_h::Z_END as u32;
55const Z_SIMPLE: u32 = crate::ported::zsh_h::Z_SIMPLE as u32;
56
57pub fn wordcode_pool_str(bytes: &[u8]) -> String {
72 let mut out = String::with_capacity(bytes.len());
73 let mut rest = bytes;
74 loop {
75 match std::str::from_utf8(rest) {
76 Ok(s) => {
77 out.push_str(s);
78 break;
79 }
80 Err(e) => {
81 let (valid, after) = rest.split_at(e.valid_up_to());
82 out.push_str(unsafe { std::str::from_utf8_unchecked(valid) });
84 let b = after[0];
85 if let Some(t) = crate::token_char::token_char_from_byte(b) {
86 out.push(t);
89 } else if b == crate::ported::zsh_h::Meta {
90 out.push(b as char);
93 } else {
94 out.push('\u{83}');
105 out.push(char::from(b ^ 32));
106 }
107 rest = &after[1..];
108 }
109 }
110 }
111 out
112}
113
114pub fn wordcode_pool_str_unmeta(slice: &[u8], metafied: bool) -> String {
124 if metafied && slice.contains(&0x83) {
125 let mut b = slice.to_vec();
126 crate::ported::utils::unmetafy(&mut b);
127 wordcode_pool_str(&b)
128 } else {
129 wordcode_pool_str(slice)
130 }
131}
132
133pub(crate) fn untokenize(bytes: &[u8]) -> String {
135 let mut result = String::new();
136 let mut i = 0;
137
138 while i < bytes.len() {
139 let b = bytes[i];
140 let c = b as char;
144 match c {
145 Pound => result.push('#'),
146 Stringg => result.push('$'),
147 Hat => result.push('^'),
148 Star => result.push('*'),
149 Inpar => result.push('('),
150 Outpar => result.push(')'),
151 Equals => result.push('='),
152 Bar => result.push('|'),
153 Inbrace => result.push('{'),
154 Outbrace => result.push('}'),
155 Inbrack => result.push('['),
156 Outbrack => result.push(']'),
157 Tick => result.push('`'),
158 Inang => result.push('<'),
159 Outang => result.push('>'),
160 Quest => result.push('?'),
161 Tilde => result.push('~'),
162 Comma => result.push(','),
163 Dash => result.push('-'),
164 Bang => result.push('!'),
165 Snull | Dnull | Bnull | Nularg => {
166 }
168 '\u{e189}' => result.push_str("(("), '\u{e18b}' => result.push_str("))"), _ if b >= 0x80 => {
171 }
173 _ => result.push(c),
174 }
175 i += 1;
176 }
177
178 result
179}
180
181#[inline]
184pub fn wc_code(c: u32) -> u32 {
185 c & ((1 << WC_CODEBITS) - 1)
186}
187
188#[inline]
191pub fn wc_data(c: u32) -> u32 {
192 c >> WC_CODEBITS
193}
194
195#[derive(Debug)]
200pub struct ZwcHeader {
201 pub magic: u32,
203 pub flags: u8,
205 pub version: String,
207 pub header_len: u32,
209 pub other_offset: u32,
211}
212
213#[derive(Debug)]
218pub struct ZwcFunction {
219 pub name: String,
221 pub start: u32,
223 pub len: u32,
225 pub npats: u32,
227 pub strs_offset: u32,
229 pub flags: u32,
231}
232
233#[derive(Debug)]
238pub struct ZwcFile {
239 pub header: ZwcHeader,
241 pub functions: Vec<ZwcFunction>,
243 pub wordcode: Vec<u32>,
245 pub strings: Vec<u8>,
247}
248
249impl ZwcFile {
250 pub fn load<P: AsRef<Path>>(path: P) -> io::Result<Self> {
252 let mut file = File::open(path)?;
253 let mut buf = vec![0u8; (FD_PRELEN + 1) * 4];
254
255 file.read_exact(&mut buf)?;
256
257 let magic = u32::from_ne_bytes([buf[0], buf[1], buf[2], buf[3]]);
258
259 let swap_bytes = if magic == FD_MAGIC {
260 false
261 } else if magic == FD_OMAGIC {
262 true
263 } else {
264 return Err(io::Error::new(
265 io::ErrorKind::InvalidData,
266 format!("Invalid ZWC magic: 0x{:08x}", magic),
267 ));
268 };
269
270 let read_u32 = |bytes: &[u8], offset: usize| -> u32 {
271 let b = &bytes[offset..offset + 4];
272 let val = u32::from_ne_bytes([b[0], b[1], b[2], b[3]]);
273 if swap_bytes {
274 val.swap_bytes()
275 } else {
276 val
277 }
278 };
279
280 let flags = buf[4];
281 let other_offset = (buf[5] as u32) | ((buf[6] as u32) << 8) | ((buf[7] as u32) << 16);
282
283 let version_start = 8;
285 let version_end = buf[version_start..]
286 .iter()
287 .position(|&b| b == 0)
288 .map(|p| version_start + p)
289 .unwrap_or(buf.len());
290 let version = String::from_utf8_lossy(&buf[version_start..version_end]).to_string();
291
292 let header_len = read_u32(&buf, FD_PRELEN * 4);
293
294 let header = ZwcHeader {
295 magic,
296 flags,
297 version,
298 header_len,
299 other_offset,
300 };
301
302 file.seek(SeekFrom::Start(0))?;
304 let full_header_size = (header_len as usize) * 4;
305 let mut header_buf = vec![0u8; full_header_size];
306 file.read_exact(&mut header_buf)?;
307
308 let mut functions = Vec::new();
310 let mut offset = FD_PRELEN * 4;
311
312 while offset < full_header_size {
313 if offset + 24 > full_header_size {
314 break;
315 }
316
317 let start = read_u32(&header_buf, offset);
318 let len = read_u32(&header_buf, offset + 4);
319 let npats = read_u32(&header_buf, offset + 8);
320 let strs = read_u32(&header_buf, offset + 12);
321 let hlen = read_u32(&header_buf, offset + 16);
322 let flags = read_u32(&header_buf, offset + 20);
323
324 let name_start = offset + 24;
326 let name_end = header_buf[name_start..]
327 .iter()
328 .position(|&b| b == 0)
329 .map(|p| name_start + p)
330 .unwrap_or(full_header_size);
331
332 let name = String::from_utf8_lossy(&header_buf[name_start..name_end]).to_string();
333
334 if name.is_empty() {
335 break;
336 }
337
338 functions.push(ZwcFunction {
339 name,
340 start,
341 len,
342 npats,
343 strs_offset: strs,
344 flags,
345 });
346
347 offset += (hlen as usize) * 4;
349 }
350
351 let mut rest = Vec::new();
353 file.read_to_end(&mut rest)?;
354
355 let mut wordcode = Vec::new();
357 let mut i = 0;
358 while i + 4 <= rest.len() {
359 let val = u32::from_ne_bytes([rest[i], rest[i + 1], rest[i + 2], rest[i + 3]]);
360 wordcode.push(if swap_bytes { val.swap_bytes() } else { val });
361 i += 4;
362 }
363
364 let strings = rest;
366
367 Ok(ZwcFile {
368 header,
369 functions,
370 wordcode,
371 strings,
372 })
373 }
374 pub fn list_functions(&self) -> Vec<&str> {
376 self.functions.iter().map(|f| f.name.as_str()).collect()
377 }
378 pub fn function_count(&self) -> usize {
380 self.functions.len()
381 }
382
383 pub fn get_function(&self, name: &str) -> Option<&ZwcFunction> {
385 self.functions
386 .iter()
387 .find(|f| f.name == name || f.name.ends_with(&format!("/{}", name)))
388 }
389 pub fn decode_function(&self, func: &ZwcFunction) -> Option<DecodedFunction> {
391 let header_words = self.header.header_len as usize;
392 let start_idx = (func.start as usize).saturating_sub(header_words);
393
394 if start_idx >= self.wordcode.len() {
395 return None;
396 }
397
398 let func_wordcode = &self.wordcode[start_idx..];
401
402 let mut string_bytes = Vec::new();
405 for &wc in func_wordcode {
406 string_bytes.extend_from_slice(&wc.to_ne_bytes());
407 }
408
409 let decoder = WordcodeDecoder::new(func_wordcode, &string_bytes, func.strs_offset as usize);
410
411 Some(DecodedFunction {
412 name: func.name.clone(),
413 body: decoder.decode(),
414 })
415 }
416}
417
418#[derive(Debug, Clone)]
423pub struct DecodedFunction {
424 pub name: String,
426 pub body: Vec<DecodedOp>,
428}
429
430#[derive(Debug, Clone)]
436pub enum DecodedOp {
437 End,
439 LineNo(u32),
441 List {
443 list_type: u32,
444 is_end: bool,
445 ops: Vec<DecodedOp>,
446 },
447 Sublist {
449 sublist_type: u32,
450 negated: bool,
451 ops: Vec<DecodedOp>,
452 },
453 Pipe { lineno: u32, ops: Vec<DecodedOp> },
455 Redir {
457 redir_type: u32,
458 fd: i32,
459 target: String,
460 varid: Option<String>,
461 },
462 Assign { name: String, value: String },
464 AssignArray { name: String, values: Vec<String> },
466 Simple { args: Vec<String> },
468 Typeset {
470 args: Vec<String>,
471 assigns: Vec<DecodedOp>,
472 },
473 Subsh { ops: Vec<DecodedOp> },
475 Cursh { ops: Vec<DecodedOp> },
477 Timed { cmd: Option<Box<DecodedOp>> },
479 FuncDef { name: String, body: Vec<DecodedOp> },
481 For {
483 var: String,
484 list: Vec<String>,
485 body: Vec<DecodedOp>,
486 },
487 ForCond {
489 init: String,
490 cond: String,
491 step: String,
492 body: Vec<DecodedOp>,
493 },
494 Select {
496 var: String,
497 list: Vec<String>,
498 body: Vec<DecodedOp>,
499 },
500 While {
502 cond: Vec<DecodedOp>,
503 body: Vec<DecodedOp>,
504 is_until: bool,
505 },
506 Repeat { count: String, body: Vec<DecodedOp> },
508 Case {
510 word: String,
511 cases: Vec<(String, Vec<DecodedOp>)>,
512 },
513 CaseItem {
515 pattern: String,
516 terminator: u32,
517 body: Vec<DecodedOp>,
518 },
519 If {
521 if_type: u32,
522 conditions: Vec<(Vec<DecodedOp>, Vec<DecodedOp>)>,
523 else_body: Option<Vec<DecodedOp>>,
524 },
525 Cond {
527 cond_type: u32,
528 args: Vec<String>,
529 operands: Vec<DecodedOp>,
530 },
531 Arith { expr: String },
533 AutoFn,
535 Try {
537 try_body: Vec<DecodedOp>,
538 always_body: Vec<DecodedOp>,
539 },
540 Unknown { code: u32, data: u32 },
542}
543
544pub struct WordcodeDecoder<'a> {
549 code: &'a [u32],
551 strings: &'a [u8],
553 strs_base: usize,
555 pub pos: usize,
557}
558
559impl<'a> WordcodeDecoder<'a> {
560 pub fn new(code: &'a [u32], strings: &'a [u8], strs_base: usize) -> Self {
562 Self {
563 code,
564 strings,
565 strs_base,
566 pos: 0,
567 }
568 }
569 pub fn at_end(&self) -> bool {
571 self.pos >= self.code.len()
572 }
573 pub fn peek(&self) -> Option<u32> {
575 self.code.get(self.pos).copied()
576 }
577 #[allow(clippy::should_implement_trait)]
579 pub fn next(&mut self) -> Option<u32> {
580 let val = self.code.get(self.pos).copied();
581 if val.is_some() {
582 self.pos += 1;
583 }
584 val
585 }
586 pub fn read_string(&mut self) -> String {
588 let wc = self.next().unwrap_or(0);
589 self.decode_string(wc)
590 }
591 pub fn decode_string(&self, wc: u32) -> String {
593 if wc == 6 || wc == 7 {
599 return String::new();
600 }
601
602 if (wc & 2) != 0 {
603 let mut s = String::new();
605 let c1 = ((wc >> 3) & 0xff) as u8;
606 let c2 = ((wc >> 11) & 0xff) as u8;
607 let c3 = ((wc >> 19) & 0xff) as u8;
608 if c1 != 0 {
609 s.push(crate::token_char::token_char_from_byte(c1).unwrap_or(c1 as char));
610 }
611 if c2 != 0 {
612 s.push(crate::token_char::token_char_from_byte(c2).unwrap_or(c2 as char));
613 }
614 if c3 != 0 {
615 s.push(crate::token_char::token_char_from_byte(c3).unwrap_or(c3 as char));
616 }
617 s
618 } else {
619 let offset = (wc >> 2) as usize;
621 self.get_string_at(self.strs_base + offset)
622 }
623 }
624
625 fn get_string_at(&self, offset: usize) -> String {
626 if offset >= self.strings.len() {
627 return String::new();
628 }
629
630 let end = self.strings[offset..]
631 .iter()
632 .position(|&b| b == 0)
633 .map(|p| offset + p)
634 .unwrap_or(self.strings.len());
635
636 let raw = &self.strings[offset..end];
638 untokenize(raw)
639 }
640
641 pub fn decode(&self) -> Vec<DecodedOp> {
643 let mut decoder = WordcodeDecoder::new(self.code, self.strings, self.strs_base);
644 decoder.decode_program()
645 }
646
647 fn decode_program(&mut self) -> Vec<DecodedOp> {
648 let mut ops = Vec::new();
649
650 while let Some(wc) = self.peek() {
651 let code = wc_code(wc);
652
653 if code == WC_END {
654 self.next();
655 ops.push(DecodedOp::End);
656 break;
657 }
658
659 if let Some(op) = self.decode_next_op() {
660 ops.push(op);
661 } else {
662 break;
663 }
664 }
665
666 ops
667 }
668
669 fn decode_next_op(&mut self) -> Option<DecodedOp> {
670 let wc = self.next()?;
671 let code = wc_code(wc);
672 let data = wc_data(wc);
673
674 let op = match code {
675 WC_END => DecodedOp::End,
676 WC_LIST => self.decode_list(data),
677 WC_SUBLIST => self.decode_sublist(data),
678 WC_PIPE => self.decode_pipe(data),
679 WC_REDIR => self.decode_redir(data),
680 WC_ASSIGN => self.decode_assign(data),
681 WC_SIMPLE => self.decode_simple(data),
682 WC_TYPESET => self.decode_typeset(data),
683 WC_SUBSH => self.decode_subsh(data),
684 WC_CURSH => self.decode_cursh(data),
685 WC_TIMED => self.decode_timed(data),
686 WC_FUNCDEF => self.decode_funcdef(data),
687 WC_FOR => self.decode_for(data),
688 WC_SELECT => self.decode_select(data),
689 WC_WHILE => self.decode_while(data),
690 WC_REPEAT => self.decode_repeat(data),
691 WC_CASE => self.decode_case(data),
692 WC_IF => self.decode_if(data),
693 WC_COND => self.decode_cond(data),
694 WC_ARITH => self.decode_arith(),
695 WC_AUTOFN => DecodedOp::AutoFn,
696 WC_TRY => self.decode_try(data),
697 _ => DecodedOp::Unknown { code, data },
698 };
699
700 Some(op)
701 }
702
703 fn decode_list(&mut self, data: u32) -> DecodedOp {
704 let list_type = data & ((1 << WC_LIST_FREE) - 1);
705 let is_end = (list_type & Z_END) != 0;
706 let is_simple = (list_type & Z_SIMPLE) != 0;
707 let _skip = data >> WC_LIST_FREE;
708
709 let mut body = Vec::new();
710
711 if is_simple {
712 let lineno = self.next().unwrap_or(0);
714 body.push(DecodedOp::LineNo(lineno));
715 }
716
717 if !is_simple {
719 while let Some(wc) = self.peek() {
720 let c = wc_code(wc);
721 if c == WC_END || c == WC_LIST {
722 break;
723 }
724 if let Some(op) = self.decode_next_op() {
725 body.push(op);
726 } else {
727 break;
728 }
729 }
730 }
731
732 DecodedOp::List {
733 list_type,
734 is_end,
735 ops: body,
736 }
737 }
738
739 fn decode_sublist(&mut self, data: u32) -> DecodedOp {
740 let sublist_type = data & 3;
741 let flags = data & 0x1c;
742 let negated = (flags & WC_SUBLIST_NOT) != 0;
743 let is_simple = (flags & WC_SUBLIST_SIMPLE) != 0;
744 let _skip = data >> WC_SUBLIST_FREE;
745
746 let mut body = Vec::new();
747
748 if is_simple {
749 let lineno = self.next().unwrap_or(0);
751 body.push(DecodedOp::LineNo(lineno));
752 }
753
754 DecodedOp::Sublist {
755 sublist_type,
756 negated,
757 ops: body,
758 }
759 }
760
761 fn decode_pipe(&mut self, data: u32) -> DecodedOp {
762 let pipe_type = data & 1;
763 let lineno = data >> 1;
764 let _is_end = pipe_type == WC_PIPE_END;
765
766 DecodedOp::Pipe {
767 lineno,
768 ops: vec![],
769 }
770 }
771
772 fn decode_redir(&mut self, data: u32) -> DecodedOp {
773 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;
778 let target = self.read_string();
779
780 let varid = if has_varid {
781 Some(self.read_string())
782 } else {
783 None
784 };
785
786 if from_heredoc {
787 self.next();
789 self.next();
790 }
791
792 DecodedOp::Redir {
793 redir_type,
794 fd,
795 target,
796 varid,
797 }
798 }
799
800 fn decode_assign(&mut self, data: u32) -> DecodedOp {
801 let is_array = (data & 1) != 0;
802 let num_elements = (data >> 2) as usize;
803
804 let name = self.read_string();
805
806 if is_array {
807 let mut values = Vec::with_capacity(num_elements);
808 for _ in 0..num_elements {
809 values.push(self.read_string());
810 }
811 DecodedOp::AssignArray { name, values }
812 } else {
813 let value = self.read_string();
814 DecodedOp::Assign { name, value }
815 }
816 }
817
818 fn decode_simple(&mut self, data: u32) -> DecodedOp {
819 let argc = data as usize;
820 let mut args = Vec::with_capacity(argc);
821 for _ in 0..argc {
822 args.push(self.read_string());
823 }
824 DecodedOp::Simple { args }
825 }
826
827 fn decode_typeset(&mut self, data: u32) -> DecodedOp {
828 let argc = data as usize;
829 let mut args = Vec::with_capacity(argc);
830 for _ in 0..argc {
831 args.push(self.read_string());
832 }
833
834 let num_assigns = self.next().unwrap_or(0) as usize;
836 let mut assigns = Vec::with_capacity(num_assigns);
837
838 for _ in 0..num_assigns {
839 if let Some(op) = self.decode_next_op() {
840 assigns.push(op);
841 }
842 }
843
844 DecodedOp::Typeset { args, assigns }
845 }
846
847 fn decode_subsh(&mut self, data: u32) -> DecodedOp {
848 let skip = data as usize;
849 let end_pos = self.pos + skip;
850
851 let mut body = Vec::new();
852 while self.pos < end_pos && !self.at_end() {
853 if let Some(op) = self.decode_next_op() {
854 body.push(op);
855 } else {
856 break;
857 }
858 }
859
860 DecodedOp::Subsh { ops: body }
861 }
862
863 fn decode_cursh(&mut self, data: u32) -> DecodedOp {
864 let skip = data as usize;
865 let end_pos = self.pos + skip;
866
867 let mut body = Vec::new();
868 while self.pos < end_pos && !self.at_end() {
869 if let Some(op) = self.decode_next_op() {
870 body.push(op);
871 } else {
872 break;
873 }
874 }
875
876 DecodedOp::Cursh { ops: body }
877 }
878
879 fn decode_timed(&mut self, data: u32) -> DecodedOp {
880 let timed_type = data;
881 let has_pipe = timed_type == 1; if has_pipe {
884 if let Some(op) = self.decode_next_op() {
886 return DecodedOp::Timed {
887 cmd: Some(Box::new(op)),
888 };
889 }
890 }
891
892 DecodedOp::Timed { cmd: None }
893 }
894
895 fn decode_funcdef(&mut self, data: u32) -> DecodedOp {
896 let skip = data as usize;
897
898 let num_names = self.next().unwrap_or(0) as usize;
899 let mut names = Vec::with_capacity(num_names);
900 for _ in 0..num_names {
901 names.push(self.read_string());
902 }
903
904 let _strs_offset = self.next();
906 let _strs_len = self.next();
907 let _npats = self.next();
908 let _tracing = self.next();
909
910 let _end_pos = self.pos + skip.saturating_sub(num_names + 5);
912
913 let name = names.first().cloned().unwrap_or_default();
914
915 DecodedOp::FuncDef { name, body: vec![] }
916 }
917
918 fn decode_for(&mut self, data: u32) -> DecodedOp {
919 let for_type = data & 3;
920 let _skip = data >> 2;
921
922 match for_type {
923 WC_FOR_COND => {
924 let init = self.read_string();
925 let cond = self.read_string();
926 let step = self.read_string();
927 DecodedOp::ForCond {
928 init,
929 cond,
930 step,
931 body: vec![],
932 }
933 }
934 WC_FOR_LIST => {
935 let var = self.read_string();
936 let num_words = self.next().unwrap_or(0) as usize;
937 let mut list = Vec::with_capacity(num_words);
938 for _ in 0..num_words {
939 list.push(self.read_string());
940 }
941 DecodedOp::For {
942 var,
943 list,
944 body: vec![],
945 }
946 }
947 _ => {
948 let var = self.read_string();
950 DecodedOp::For {
951 var,
952 list: vec![],
953 body: vec![],
954 }
955 }
956 }
957 }
958
959 fn decode_select(&mut self, data: u32) -> DecodedOp {
960 let select_type = data & 1;
961 let _skip = data >> 1;
962
963 let var = self.read_string();
964 let list = if select_type == 1 {
965 let num_words = self.next().unwrap_or(0) as usize;
967 let mut words = Vec::with_capacity(num_words);
968 for _ in 0..num_words {
969 words.push(self.read_string());
970 }
971 words
972 } else {
973 vec![]
974 };
975
976 DecodedOp::Select {
977 var,
978 list,
979 body: vec![],
980 }
981 }
982
983 fn decode_while(&mut self, data: u32) -> DecodedOp {
984 let is_until = (data & 1) != 0;
985 let _skip = data >> 1;
986 DecodedOp::While {
987 cond: vec![],
988 body: vec![],
989 is_until,
990 }
991 }
992
993 fn decode_repeat(&mut self, data: u32) -> DecodedOp {
994 let _skip = data;
995 let count = self.read_string();
996 DecodedOp::Repeat {
997 count,
998 body: vec![],
999 }
1000 }
1001
1002 fn decode_case(&mut self, data: u32) -> DecodedOp {
1003 let case_type = data & 7;
1004 let _skip = data >> WC_CASE_FREE;
1005
1006 if case_type == WC_CASE_HEAD {
1007 let word = self.read_string();
1008 DecodedOp::Case {
1009 word,
1010 cases: vec![],
1011 }
1012 } else {
1013 let pattern = self.read_string();
1015 let _npats = self.next();
1016 DecodedOp::CaseItem {
1017 pattern,
1018 terminator: case_type,
1019 body: vec![],
1020 }
1021 }
1022 }
1023
1024 fn decode_if(&mut self, data: u32) -> DecodedOp {
1025 let if_type = data & 3;
1026 let _skip = data >> 2;
1027
1028 DecodedOp::If {
1029 if_type,
1030 conditions: vec![],
1031 else_body: None,
1032 }
1033 }
1034
1035 fn decode_cond(&mut self, data: u32) -> DecodedOp {
1043 let cond_type = data & 127;
1044 let skip = data >> 7;
1045 let mut operands = Vec::new();
1046 let mut args = Vec::new();
1047
1048 match cond_type as i32 {
1049 COND_NOT => operands.extend(self.decode_next_op()),
1050 COND_AND | COND_OR => {
1051 operands.extend(self.decode_next_op());
1052 operands.extend(self.decode_next_op());
1053 }
1054 COND_STREQ..=COND_STRGTR => {
1055 args.push(self.read_string());
1056 args.push(self.read_string());
1057 let _npats = self.next();
1058 }
1059 COND_NT..=COND_REGEX => {
1060 args.push(self.read_string());
1061 args.push(self.read_string());
1062 }
1063 COND_MOD => {
1064 for _ in 0..=skip {
1065 args.push(self.read_string());
1066 }
1067 }
1068 COND_MODI => {
1069 for _ in 0..3 {
1070 args.push(self.read_string());
1071 }
1072 }
1073 _ => args.push(self.read_string()),
1074 }
1075
1076 DecodedOp::Cond {
1077 cond_type,
1078 args,
1079 operands,
1080 }
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<()> {
1102 let zwc = ZwcFile::load(&path)?;
1103
1104 println!("ZWC file: {:?}", path.as_ref());
1105 println!(
1106 " Magic: 0x{:08x} ({})",
1107 zwc.header.magic,
1108 if zwc.header.magic == FD_MAGIC {
1109 "native"
1110 } else {
1111 "swapped"
1112 }
1113 );
1114 println!(" Version: zsh-{}", zwc.header.version);
1115 println!(" Header length: {} words", zwc.header.header_len);
1116 println!(" Wordcode size: {} words", zwc.wordcode.len());
1117 println!(" Functions: {}", zwc.functions.len());
1118
1119 for func in &zwc.functions {
1120 println!(
1121 " {} (offset={}, len={}, npats={})",
1122 func.name, func.start, func.len, func.npats
1123 );
1124 }
1125
1126 Ok(())
1127}
1128
1129pub fn dump_zwc_function<P: AsRef<Path>>(path: P, func_name: &str) -> io::Result<()> {
1132 let zwc = ZwcFile::load(&path)?;
1133
1134 let func = zwc.get_function(func_name).ok_or_else(|| {
1135 io::Error::new(
1136 io::ErrorKind::NotFound,
1137 format!("Function '{}' not found", func_name),
1138 )
1139 })?;
1140
1141 println!("Function: {}", func.name);
1142 println!(" Offset: {} words", func.start);
1143 println!(" Length: {} words", func.len);
1144 println!(" Patterns: {}", func.npats);
1145 println!(" Strings offset: {}", func.strs_offset);
1146
1147 let header_words = zwc.header.header_len as usize;
1149 let start_idx = (func.start as usize).saturating_sub(header_words);
1150 let end_idx = start_idx + func.len as usize;
1151
1152 if start_idx < zwc.wordcode.len() {
1153 println!("\n Wordcode:");
1154 let end = end_idx.min(zwc.wordcode.len());
1155 for (i, &wc) in zwc.wordcode[start_idx..end].iter().enumerate().take(50) {
1156 let code = wc_code(wc);
1157 let data = wc_data(wc);
1158 let code_name = match code {
1159 WC_END => "END",
1160 WC_LIST => "LIST",
1161 WC_SUBLIST => "SUBLIST",
1162 WC_PIPE => "PIPE",
1163 WC_REDIR => "REDIR",
1164 WC_ASSIGN => "ASSIGN",
1165 WC_SIMPLE => "SIMPLE",
1166 WC_TYPESET => "TYPESET",
1167 WC_SUBSH => "SUBSH",
1168 WC_CURSH => "CURSH",
1169 WC_TIMED => "TIMED",
1170 WC_FUNCDEF => "FUNCDEF",
1171 WC_FOR => "FOR",
1172 WC_SELECT => "SELECT",
1173 WC_WHILE => "WHILE",
1174 WC_REPEAT => "REPEAT",
1175 WC_CASE => "CASE",
1176 WC_IF => "IF",
1177 WC_COND => "COND",
1178 WC_ARITH => "ARITH",
1179 WC_AUTOFN => "AUTOFN",
1180 WC_TRY => "TRY",
1181 _ => "???",
1182 };
1183 println!(" [{:3}] 0x{:08x} = {} (data={})", i, wc, code_name, data);
1184 }
1185 if end - start_idx > 50 {
1186 println!(" ... ({} more words)", end - start_idx - 50);
1187 }
1188 }
1189
1190 if let Some(decoded) = zwc.decode_function(func) {
1192 println!("\n Decoded ops:");
1193 for (i, op) in decoded.body.iter().enumerate().take(20) {
1194 println!(" [{:2}] {:?}", i, op);
1195 }
1196 if decoded.body.len() > 20 {
1197 println!(" ... ({} more ops)", decoded.body.len() - 20);
1198 }
1199 }
1200
1201 Ok(())
1202}
1203
1204impl DecodedOp {
1206 fn cond_words(&self, out: &mut Vec<ShellWord>) {
1209 let DecodedOp::Cond {
1210 cond_type,
1211 args,
1212 operands,
1213 } = self
1214 else {
1215 return;
1216 };
1217 let lit = |s: &str| ShellWord::Literal(s.to_string());
1218 let infix: Option<&str> = match *cond_type as i32 {
1219 COND_STREQ => Some("="),
1220 COND_STRDEQ => Some("=="),
1221 COND_STRNEQ => Some("!="),
1222 COND_STRLT => Some("<"),
1223 COND_STRGTR => Some(">"),
1224 COND_NT => Some("-nt"),
1225 COND_OT => Some("-ot"),
1226 COND_EF => Some("-ef"),
1227 COND_EQ => Some("-eq"),
1228 COND_NE => Some("-ne"),
1229 COND_LT => Some("-lt"),
1230 COND_GT => Some("-gt"),
1231 COND_LE => Some("-le"),
1232 COND_GE => Some("-ge"),
1233 COND_REGEX => Some("=~"),
1234 _ => None,
1235 };
1236 match *cond_type as i32 {
1237 COND_NOT => {
1238 out.push(lit("!"));
1239 for op in operands {
1240 op.cond_words(out);
1241 }
1242 }
1243 COND_AND | COND_OR => {
1244 let sep = if *cond_type as i32 == COND_AND { "&&" } else { "||" };
1245 for (i, op) in operands.iter().enumerate() {
1246 if i > 0 {
1247 out.push(lit(sep));
1248 }
1249 out.push(lit("("));
1250 op.cond_words(out);
1251 out.push(lit(")"));
1252 }
1253 }
1254 COND_MODI => {
1256 if let [op, a, b] = args.as_slice() {
1257 out.extend([lit(a), lit(op), lit(b)]);
1258 }
1259 }
1260 COND_MOD => out.extend(args.iter().map(|a| lit(a))),
1262 _ => match infix {
1263 Some(op) => {
1264 if let [a, b] = args.as_slice() {
1265 out.extend([lit(a), lit(op), lit(b)]);
1266 }
1267 }
1268 None => {
1270 out.push(lit(&format!("-{}", char::from(*cond_type as u8))));
1271 out.extend(args.iter().map(|a| lit(a)));
1272 }
1273 },
1274 }
1275 }
1276
1277 pub fn to_shell_command(&self) -> Option<ShellCommand> {
1279 match self {
1280 DecodedOp::Simple { args } => {
1281 if args.is_empty() {
1282 return None;
1283 }
1284 Some(ShellCommand::Simple(SimpleCommand {
1285 assignments: vec![],
1286 words: args.iter().map(|s| ShellWord::Literal(s.clone())).collect(),
1287 redirects: vec![],
1288 }))
1289 }
1290
1291 DecodedOp::Assign { name, value } => Some(ShellCommand::Simple(SimpleCommand {
1292 assignments: vec![(name.clone(), ShellWord::Literal(value.clone()), false)],
1293 words: vec![],
1294 redirects: vec![],
1295 })),
1296
1297 DecodedOp::AssignArray { name, values } => {
1298 let array_word = ShellWord::Concat(
1299 values
1300 .iter()
1301 .map(|s| ShellWord::Literal(s.clone()))
1302 .collect(),
1303 );
1304 Some(ShellCommand::Simple(SimpleCommand {
1305 assignments: vec![(name.clone(), array_word, false)],
1306 words: vec![],
1307 redirects: vec![],
1308 }))
1309 }
1310
1311 DecodedOp::List { ops, .. } => {
1312 let commands: Vec<(ShellCommand, ListOp)> = ops
1313 .iter()
1314 .filter_map(|op| op.to_shell_command())
1315 .map(|cmd| (cmd, ListOp::Semi))
1316 .collect();
1317
1318 if commands.is_empty() {
1319 None
1320 } else if commands.len() == 1 {
1321 Some(commands.into_iter().next().unwrap().0)
1322 } else {
1323 Some(ShellCommand::List(commands))
1324 }
1325 }
1326
1327 DecodedOp::Sublist { ops, negated, .. } => {
1328 let commands: Vec<ShellCommand> =
1329 ops.iter().filter_map(|op| op.to_shell_command()).collect();
1330
1331 if commands.is_empty() {
1332 None
1333 } else {
1334 Some(ShellCommand::Pipeline(commands, *negated))
1335 }
1336 }
1337
1338 DecodedOp::Pipe { ops, .. } => {
1339 let commands: Vec<ShellCommand> =
1340 ops.iter().filter_map(|op| op.to_shell_command()).collect();
1341
1342 if commands.is_empty() {
1343 None
1344 } else if commands.len() == 1 {
1345 Some(commands.into_iter().next().unwrap())
1346 } else {
1347 Some(ShellCommand::Pipeline(commands, false))
1348 }
1349 }
1350
1351 DecodedOp::Typeset { args, assigns } => {
1352 let mut words: Vec<ShellWord> =
1354 args.iter().map(|s| ShellWord::Literal(s.clone())).collect();
1355
1356 for assign in assigns {
1358 if let DecodedOp::Assign { name, value } = assign {
1359 words.push(ShellWord::Literal(format!("{}={}", name, value)));
1360 }
1361 }
1362
1363 Some(ShellCommand::Simple(SimpleCommand {
1364 assignments: vec![],
1365 words,
1366 redirects: vec![],
1367 }))
1368 }
1369
1370 DecodedOp::Subsh { ops } => {
1371 let commands: Vec<ShellCommand> =
1372 ops.iter().filter_map(|op| op.to_shell_command()).collect();
1373 Some(ShellCommand::Compound(CompoundCommand::Subshell(commands)))
1374 }
1375
1376 DecodedOp::Cursh { ops } => {
1377 let commands: Vec<ShellCommand> =
1378 ops.iter().filter_map(|op| op.to_shell_command()).collect();
1379 Some(ShellCommand::Compound(CompoundCommand::BraceGroup(
1380 commands,
1381 )))
1382 }
1383
1384 DecodedOp::For { var, list, body } => {
1385 let words = if list.is_empty() {
1386 None
1387 } else {
1388 Some(list.iter().map(|s| ShellWord::Literal(s.clone())).collect())
1389 };
1390 let body_cmds: Vec<ShellCommand> =
1391 body.iter().filter_map(|op| op.to_shell_command()).collect();
1392 Some(ShellCommand::Compound(CompoundCommand::For {
1393 var: var.clone(),
1394 words,
1395 body: body_cmds,
1396 }))
1397 }
1398
1399 DecodedOp::ForCond {
1400 init,
1401 cond,
1402 step,
1403 body,
1404 } => {
1405 let body_cmds: Vec<ShellCommand> =
1406 body.iter().filter_map(|op| op.to_shell_command()).collect();
1407 Some(ShellCommand::Compound(CompoundCommand::ForArith {
1408 init: init.clone(),
1409 cond: cond.clone(),
1410 step: step.clone(),
1411 body: body_cmds,
1412 }))
1413 }
1414
1415 DecodedOp::While {
1416 cond,
1417 body,
1418 is_until,
1419 } => {
1420 let cond_cmds: Vec<ShellCommand> =
1421 cond.iter().filter_map(|op| op.to_shell_command()).collect();
1422 let body_cmds: Vec<ShellCommand> =
1423 body.iter().filter_map(|op| op.to_shell_command()).collect();
1424
1425 if *is_until {
1426 Some(ShellCommand::Compound(CompoundCommand::Until {
1427 condition: cond_cmds,
1428 body: body_cmds,
1429 }))
1430 } else {
1431 Some(ShellCommand::Compound(CompoundCommand::While {
1432 condition: cond_cmds,
1433 body: body_cmds,
1434 }))
1435 }
1436 }
1437
1438 DecodedOp::FuncDef { name, body } => {
1439 let body_cmds: Vec<ShellCommand> =
1440 body.iter().filter_map(|op| op.to_shell_command()).collect();
1441
1442 let func_body = if body_cmds.is_empty() {
1443 ShellCommand::Simple(SimpleCommand {
1445 assignments: vec![],
1446 words: vec![ShellWord::Literal(":".to_string())],
1447 redirects: vec![],
1448 })
1449 } else if body_cmds.len() == 1 {
1450 body_cmds.into_iter().next().unwrap()
1451 } else {
1452 ShellCommand::List(body_cmds.into_iter().map(|c| (c, ListOp::Semi)).collect())
1453 };
1454
1455 Some(ShellCommand::FunctionDef(name.clone(), Box::new(func_body)))
1456 }
1457
1458 DecodedOp::Arith { expr } => {
1459 Some(ShellCommand::Compound(CompoundCommand::Arith(expr.clone())))
1460 }
1461
1462 DecodedOp::End | DecodedOp::LineNo(_) | DecodedOp::AutoFn => None,
1464
1465 DecodedOp::Redir { .. } => {
1466 None
1468 }
1469
1470 DecodedOp::If {
1471 conditions,
1472 else_body,
1473 ..
1474 } => {
1475 let cond_pairs: Vec<(Vec<ShellCommand>, Vec<ShellCommand>)> = conditions
1476 .iter()
1477 .map(|(c, b)| {
1478 (
1479 c.iter().filter_map(|op| op.to_shell_command()).collect(),
1480 b.iter().filter_map(|op| op.to_shell_command()).collect(),
1481 )
1482 })
1483 .collect();
1484 let else_part: Option<Vec<ShellCommand>> = else_body
1485 .as_ref()
1486 .map(|body| body.iter().filter_map(|op| op.to_shell_command()).collect());
1487 Some(ShellCommand::Compound(CompoundCommand::If {
1488 conditions: cond_pairs,
1489 else_part,
1490 }))
1491 }
1492
1493 DecodedOp::Case { word, cases } => {
1494 let mapped: Vec<(Vec<ShellWord>, Vec<ShellCommand>, CaseTerminator)> = cases
1495 .iter()
1496 .map(|(pat, body)| {
1497 (
1498 vec![ShellWord::Literal(pat.clone())],
1499 body.iter().filter_map(|op| op.to_shell_command()).collect(),
1500 CaseTerminator::Break,
1501 )
1502 })
1503 .collect();
1504 Some(ShellCommand::Compound(CompoundCommand::Case {
1505 word: ShellWord::Literal(word.clone()),
1506 cases: mapped,
1507 }))
1508 }
1509
1510 DecodedOp::CaseItem { .. } => {
1511 None
1516 }
1517
1518 DecodedOp::Repeat { count, body } => {
1519 let body_cmds: Vec<ShellCommand> =
1520 body.iter().filter_map(|op| op.to_shell_command()).collect();
1521 Some(ShellCommand::Compound(CompoundCommand::Repeat {
1522 count: count.clone(),
1523 body: body_cmds,
1524 }))
1525 }
1526
1527 DecodedOp::Try {
1528 try_body,
1529 always_body,
1530 } => {
1531 let try_cmds: Vec<ShellCommand> = try_body
1532 .iter()
1533 .filter_map(|op| op.to_shell_command())
1534 .collect();
1535 let always_cmds: Vec<ShellCommand> = always_body
1536 .iter()
1537 .filter_map(|op| op.to_shell_command())
1538 .collect();
1539 Some(ShellCommand::Compound(CompoundCommand::Try {
1540 try_body: try_cmds,
1541 always_body: always_cmds,
1542 }))
1543 }
1544
1545 DecodedOp::Select { var, list, body } => {
1546 let words = if list.is_empty() {
1548 None
1549 } else {
1550 Some(list.iter().map(|s| ShellWord::Literal(s.clone())).collect())
1551 };
1552 let body_cmds: Vec<ShellCommand> =
1553 body.iter().filter_map(|op| op.to_shell_command()).collect();
1554 Some(ShellCommand::Compound(CompoundCommand::Select {
1555 var: var.clone(),
1556 words,
1557 body: body_cmds,
1558 }))
1559 }
1560
1561 DecodedOp::Cond { .. } => {
1562 let mut words = vec![ShellWord::Literal("[[".to_string())];
1565 self.cond_words(&mut words);
1566 words.push(ShellWord::Literal("]]".to_string()));
1567 Some(ShellCommand::Simple(SimpleCommand {
1568 assignments: vec![],
1569 words,
1570 redirects: vec![],
1571 }))
1572 }
1573
1574 DecodedOp::Timed { cmd } => {
1575 let time = ShellWord::Literal("time".to_string());
1578 match cmd.as_deref().and_then(|op| op.to_shell_command()) {
1579 None => Some(ShellCommand::Simple(SimpleCommand {
1580 assignments: vec![],
1581 words: vec![time],
1582 redirects: vec![],
1583 })),
1584 Some(ShellCommand::Simple(mut sc)) => {
1585 sc.words.insert(0, time);
1586 Some(ShellCommand::Simple(sc))
1587 }
1588 Some(ShellCommand::Pipeline(mut cmds, neg)) => {
1589 if let Some(ShellCommand::Simple(sc)) = cmds.first_mut() {
1590 sc.words.insert(0, time);
1591 }
1592 Some(ShellCommand::Pipeline(cmds, neg))
1593 }
1594 Some(other) => Some(other),
1595 }
1596 }
1597
1598 DecodedOp::Unknown { .. } => None,
1599 }
1600 }
1601}
1602
1603#[allow(dead_code)]
1605fn redir_type_to_op(redir_type: u32) -> Option<RedirectOp> {
1606 const REDIR_WRITE: u32 = 0;
1608 const REDIR_WRITENOW: u32 = 1;
1609 const REDIR_APP: u32 = 2;
1610 const REDIR_APPNOW: u32 = 3;
1611 const REDIR_ERRWRITE: u32 = 4;
1612 const REDIR_ERRWRITENOW: u32 = 5;
1613 const REDIR_ERRAPP: u32 = 6;
1614 const REDIR_ERRAPPNOW: u32 = 7;
1615 const REDIR_READWRITE: u32 = 8;
1616 const REDIR_READ: u32 = 9;
1617 const REDIR_HEREDOC: u32 = 10;
1618 const REDIR_HEREDOCDASH: u32 = 11;
1619 const REDIR_HERESTR: u32 = 12;
1620 const REDIR_MERGEIN: u32 = 13;
1621 const REDIR_MERGEOUT: u32 = 14;
1622 const REDIR_CLOSE: u32 = 15;
1623 const REDIR_INPIPE: u32 = 16;
1624 const REDIR_OUTPIPE: u32 = 17;
1625
1626 match redir_type {
1627 REDIR_WRITE | REDIR_WRITENOW => Some(RedirectOp::Write),
1628 REDIR_APP | REDIR_APPNOW => Some(RedirectOp::Append),
1629 REDIR_ERRWRITE | REDIR_ERRWRITENOW => Some(RedirectOp::WriteBoth),
1630 REDIR_ERRAPP | REDIR_ERRAPPNOW => Some(RedirectOp::AppendBoth),
1631 REDIR_READWRITE => Some(RedirectOp::ReadWrite),
1632 REDIR_READ => Some(RedirectOp::Read),
1633 REDIR_HEREDOC | REDIR_HEREDOCDASH => Some(RedirectOp::HereDoc),
1634 REDIR_HERESTR => Some(RedirectOp::HereString),
1635 REDIR_MERGEIN => Some(RedirectOp::DupRead),
1636 REDIR_MERGEOUT => Some(RedirectOp::DupWrite),
1637 REDIR_CLOSE | REDIR_INPIPE | REDIR_OUTPIPE => None, _ => None,
1639 }
1640}
1641
1642impl DecodedFunction {
1643 pub fn to_shell_function(&self) -> Option<ShellCommand> {
1645 let body_cmds: Vec<ShellCommand> = self
1646 .body
1647 .iter()
1648 .filter_map(|op| op.to_shell_command())
1649 .collect();
1650
1651 let func_body = if body_cmds.is_empty() {
1652 ShellCommand::Simple(SimpleCommand {
1653 assignments: vec![],
1654 words: vec![ShellWord::Literal(":".to_string())],
1655 redirects: vec![],
1656 })
1657 } else if body_cmds.len() == 1 {
1658 body_cmds.into_iter().next().unwrap()
1659 } else {
1660 ShellCommand::List(body_cmds.into_iter().map(|c| (c, ListOp::Semi)).collect())
1661 };
1662
1663 let name = self
1665 .name
1666 .rsplit('/')
1667 .next()
1668 .unwrap_or(&self.name)
1669 .to_string();
1670
1671 Some(ShellCommand::FunctionDef(name, Box::new(func_body)))
1672 }
1673}
1674
1675#[cfg(test)]
1676mod tests {
1677 use super::*;
1678
1679 #[test]
1680 fn test_wc_code() {
1681 let _g = crate::test_util::global_state_lock();
1682 assert_eq!(wc_code(WC_LIST), WC_LIST);
1683 assert_eq!(wc_code(WC_SIMPLE | (5 << WC_CODEBITS)), WC_SIMPLE);
1684 }
1685
1686 #[test]
1687 fn test_wc_data() {
1688 let _g = crate::test_util::global_state_lock();
1689 let wc = WC_SIMPLE | (42 << WC_CODEBITS);
1690 assert_eq!(wc_data(wc), 42);
1691 }
1692
1693 #[test]
1694 fn test_load_src_zwc() {
1695 let _g = crate::test_util::global_state_lock();
1696 let path = "/Users/wizard/.zinit/plugins/MenkeTechnologies---zsh-more-completions/src.zwc";
1697 if !std::path::Path::new(path).exists() {
1698 eprintln!("Skipping test - {} not found", path);
1699 return;
1700 }
1701
1702 let zwc = ZwcFile::load(path).expect("Failed to load src.zwc");
1703 println!("Loaded {} functions from src.zwc", zwc.function_count());
1704
1705 assert!(
1707 zwc.function_count() > 1000,
1708 "Expected > 1000 functions, got {}",
1709 zwc.function_count()
1710 );
1711
1712 let funcs = zwc.list_functions();
1714 println!("First 10 functions: {:?}", &funcs[..10.min(funcs.len())]);
1715
1716 if let Some(func) = zwc.get_function("_ls") {
1718 println!("Found _ls function");
1719 if let Some(decoded) = zwc.decode_function(func) {
1720 println!("Decoded _ls: {} ops", decoded.body.len());
1721 }
1722 }
1723 }
1724
1725 #[test]
1726 fn test_load_zshrc_zwc() {
1727 let _g = crate::test_util::global_state_lock();
1728 let home = std::env::var("HOME").unwrap_or_default();
1729 let path = format!("{}/.zshrc.zwc", home);
1730 if !std::path::Path::new(&path).exists() {
1731 eprintln!("Skipping test - {} not found", path);
1732 return;
1733 }
1734
1735 let zwc = ZwcFile::load(&path).expect("Failed to load .zshrc.zwc");
1736 println!("Loaded {} functions from .zshrc.zwc", zwc.function_count());
1737
1738 for name in zwc.list_functions() {
1739 println!(" Function: {}", name);
1740 if let Some(func) = zwc.get_function(name) {
1741 if let Some(decoded) = zwc.decode_function(func) {
1742 println!(" Decoded: {} ops", decoded.body.len());
1743 for (i, op) in decoded.body.iter().take(3).enumerate() {
1744 if let Some(cmd) = op.to_shell_command() {
1745 println!(" [{}] -> ShellCommand OK", i);
1746 } else {
1747 println!(" [{}] {:?}", i, op);
1748 }
1749 }
1750 }
1751 }
1752 }
1753 }
1754
1755 #[test]
1756 fn decoded_op_if_converts_to_compound_if() {
1757 let _g = crate::test_util::global_state_lock();
1758 let cmd_a = DecodedOp::Simple {
1759 args: vec!["true".into()],
1760 };
1761 let cmd_b = DecodedOp::Simple {
1762 args: vec!["false".into()],
1763 };
1764 let op = DecodedOp::If {
1765 if_type: 0,
1766 conditions: vec![(vec![cmd_a], vec![cmd_b])],
1767 else_body: None,
1768 };
1769 let result = op.to_shell_command();
1770 match result {
1771 Some(ShellCommand::Compound(CompoundCommand::If {
1772 conditions,
1773 else_part,
1774 })) => {
1775 assert_eq!(conditions.len(), 1);
1776 assert!(else_part.is_none());
1777 assert_eq!(conditions[0].0.len(), 1);
1778 assert_eq!(conditions[0].1.len(), 1);
1779 }
1780 other => panic!("expected If, got {:?}", other),
1781 }
1782 }
1783
1784 #[test]
1785 fn decoded_op_repeat_converts_with_count_and_body() {
1786 let _g = crate::test_util::global_state_lock();
1787 let body = DecodedOp::Simple {
1788 args: vec!["echo".into(), "hi".into()],
1789 };
1790 let op = DecodedOp::Repeat {
1791 count: "3".into(),
1792 body: vec![body],
1793 };
1794 match op.to_shell_command() {
1795 Some(ShellCommand::Compound(CompoundCommand::Repeat { count, body })) => {
1796 assert_eq!(count, "3");
1797 assert_eq!(body.len(), 1);
1798 }
1799 other => panic!("expected Repeat, got {:?}", other),
1800 }
1801 }
1802
1803 #[test]
1804 fn decoded_op_case_converts_each_pattern_branch() {
1805 let _g = crate::test_util::global_state_lock();
1806 let body_one = DecodedOp::Simple {
1807 args: vec!["echo".into(), "one".into()],
1808 };
1809 let body_two = DecodedOp::Simple {
1810 args: vec!["echo".into(), "two".into()],
1811 };
1812 let op = DecodedOp::Case {
1813 word: "$x".into(),
1814 cases: vec![("a*".into(), vec![body_one]), ("b*".into(), vec![body_two])],
1815 };
1816 match op.to_shell_command() {
1817 Some(ShellCommand::Compound(CompoundCommand::Case { cases, .. })) => {
1818 assert_eq!(cases.len(), 2);
1819 assert_eq!(cases[0].0.len(), 1);
1820 assert_eq!(cases[1].0.len(), 1);
1821 }
1822 other => panic!("expected Case, got {:?}", other),
1823 }
1824 }
1825
1826 #[test]
1827 fn decoded_op_try_converts_both_arms() {
1828 let _g = crate::test_util::global_state_lock();
1829 let try_arm = DecodedOp::Simple {
1830 args: vec!["false".into()],
1831 };
1832 let always_arm = DecodedOp::Simple {
1833 args: vec!["echo".into(), "done".into()],
1834 };
1835 let op = DecodedOp::Try {
1836 try_body: vec![try_arm],
1837 always_body: vec![always_arm],
1838 };
1839 match op.to_shell_command() {
1840 Some(ShellCommand::Compound(CompoundCommand::Try {
1841 try_body,
1842 always_body,
1843 })) => {
1844 assert_eq!(try_body.len(), 1);
1845 assert_eq!(always_body.len(), 1);
1846 }
1847 other => panic!("expected Try, got {:?}", other),
1848 }
1849 }
1850
1851 #[test]
1852 fn test_load_zshenv_zwc() {
1853 let _g = crate::test_util::global_state_lock();
1854 let home = std::env::var("HOME").unwrap_or_default();
1855 let path = format!("{}/.zshenv.zwc", home);
1856 if !std::path::Path::new(&path).exists() {
1857 eprintln!("Skipping test - {} not found", path);
1858 return;
1859 }
1860
1861 let zwc = ZwcFile::load(&path).expect("Failed to load .zshenv.zwc");
1862 println!("Loaded {} functions from .zshenv.zwc", zwc.function_count());
1863
1864 for name in zwc.list_functions() {
1865 println!(" Function: {}", name);
1866 if let Some(func) = zwc.get_function(name) {
1867 if let Some(decoded) = zwc.decode_function(func) {
1868 println!(" Decoded: {} ops", decoded.body.len());
1869 }
1870 }
1871 }
1872 }
1873}