1use std::collections::VecDeque;
38
39pub mod tokens {
41 pub const POUND: char = '\u{80}'; pub const STRING: char = '\u{81}'; pub const QSTRING: char = '\u{82}'; pub const TICK: char = '\u{83}'; pub const QTICK: char = '\u{84}'; pub const INPAR: char = '\u{85}'; pub const OUTPAR: char = '\u{86}'; pub const INBRACE: char = '\u{87}'; pub const OUTBRACE: char = '\u{88}'; pub const INBRACK: char = '\u{89}'; pub const OUTBRACK: char = '\u{8A}'; pub const INANG: char = '\u{8B}'; pub const OUTANG: char = '\u{8C}'; pub const OUTANGPROC: char = '\u{8D}'; pub const EQUALS: char = '\u{8E}'; pub const NULARG: char = '\u{8F}'; pub const INPARMATH: char = '\u{90}'; pub const OUTPARMATH: char = '\u{91}'; pub const SNULL: char = '\u{92}'; pub const MARKER: char = '\u{93}'; pub const BNULL: char = '\u{94}'; pub fn is_token(c: char) -> bool {
64 c as u32 >= 0x80 && c as u32 <= 0x94
65 }
66
67 pub fn token_to_char(c: char) -> char {
68 match c {
69 POUND => '#',
70 STRING | QSTRING => '$',
71 TICK | QTICK => '`',
72 INPAR => '(',
73 OUTPAR => ')',
74 INBRACE => '{',
75 OUTBRACE => '}',
76 INBRACK => '[',
77 OUTBRACK => ']',
78 INANG => '<',
79 OUTANG => '>',
80 EQUALS => '=',
81 _ => c,
82 }
83 }
84}
85
86use tokens::*;
87
88pub const LF_ARRAY: u32 = 1;
90
91pub mod prefork_flags {
93 pub const SINGLE: u32 = 1; pub const SPLIT: u32 = 2; pub const SHWORDSPLIT: u32 = 4; pub const NOSHWORDSPLIT: u32 = 8; pub const ASSIGN: u32 = 16; pub const TYPESET: u32 = 32; pub const SUBEXP: u32 = 64; pub const KEY_VALUE: u32 = 128; pub const NO_UNTOK: u32 = 256; }
103
104#[derive(Debug, Clone)]
106pub struct LinkNode {
107 pub data: String,
108}
109
110#[derive(Debug, Clone, Default)]
112pub struct LinkList {
113 pub nodes: VecDeque<LinkNode>,
114 pub flags: u32,
115}
116
117impl LinkList {
118 pub fn new() -> Self {
119 LinkList {
120 nodes: VecDeque::new(),
121 flags: 0,
122 }
123 }
124
125 pub fn from_string(s: &str) -> Self {
126 let mut list = LinkList::new();
127 list.nodes.push_back(LinkNode {
128 data: s.to_string(),
129 });
130 list
131 }
132
133 pub fn first_node(&self) -> Option<usize> {
134 if self.nodes.is_empty() {
135 None
136 } else {
137 Some(0)
138 }
139 }
140
141 pub fn get_data(&self, idx: usize) -> Option<&str> {
142 self.nodes.get(idx).map(|n| n.data.as_str())
143 }
144
145 pub fn set_data(&mut self, idx: usize, data: String) {
146 if let Some(node) = self.nodes.get_mut(idx) {
147 node.data = data;
148 }
149 }
150
151 pub fn insert_after(&mut self, idx: usize, data: String) -> usize {
152 self.nodes.insert(idx + 1, LinkNode { data });
153 idx + 1
154 }
155
156 pub fn remove(&mut self, idx: usize) {
157 if idx < self.nodes.len() {
158 self.nodes.remove(idx);
159 }
160 }
161
162 pub fn next_node(&self, idx: usize) -> Option<usize> {
163 if idx + 1 < self.nodes.len() {
164 Some(idx + 1)
165 } else {
166 None
167 }
168 }
169
170 pub fn is_empty(&self) -> bool {
171 self.nodes.is_empty()
172 }
173
174 pub fn len(&self) -> usize {
175 self.nodes.len()
176 }
177}
178
179pub struct SubstState {
181 pub errflag: bool,
182 pub opts: SubstOptions,
183 pub variables: std::collections::HashMap<String, String>,
184 pub arrays: std::collections::HashMap<String, Vec<String>>,
185 pub assoc_arrays: std::collections::HashMap<String, std::collections::HashMap<String, String>>,
186}
187
188#[derive(Debug, Clone, Default)]
190pub struct SubstOptions {
191 pub sh_file_expansion: bool,
192 pub sh_word_split: bool,
193 pub ignore_braces: bool,
194 pub glob_subst: bool,
195 pub ksh_typeset: bool,
196 pub exec_opt: bool,
197}
198
199impl Default for SubstState {
200 fn default() -> Self {
201 SubstState {
202 errflag: false,
203 opts: SubstOptions::default(),
204 variables: std::collections::HashMap::new(),
205 arrays: std::collections::HashMap::new(),
206 assoc_arrays: std::collections::HashMap::new(),
207 }
208 }
209}
210
211pub const NULSTRING: &str = "\u{8F}";
213
214fn keyvalpairelement(list: &mut LinkList, node_idx: usize) -> Option<usize> {
217 let data = list.get_data(node_idx)?;
218 let chars: Vec<char> = data.chars().collect();
219
220 if chars.is_empty() || chars[0] != INBRACK {
221 return None;
222 }
223
224 let mut end_pos = None;
226 for (i, &c) in chars.iter().enumerate().skip(1) {
227 if c == OUTBRACK {
228 end_pos = Some(i);
229 break;
230 }
231 }
232
233 let end_pos = end_pos?;
234
235 if end_pos + 1 >= chars.len() {
237 return None;
238 }
239
240 let is_append = chars.get(end_pos + 1) == Some(&'+') && chars.get(end_pos + 2) == Some(&EQUALS);
241 let is_assign = chars.get(end_pos + 1) == Some(&EQUALS);
242
243 if !is_assign && !is_append {
244 return None;
245 }
246
247 let key: String = chars[1..end_pos].iter().collect();
249
250 let value_start = if is_append { end_pos + 3 } else { end_pos + 2 };
252 let value: String = chars[value_start..].iter().collect();
253
254 let marker = if is_append {
256 format!("{}+", MARKER)
257 } else {
258 MARKER.to_string()
259 };
260
261 list.set_data(node_idx, marker);
262 let key_idx = list.insert_after(node_idx, key);
263 let val_idx = list.insert_after(key_idx, value);
264
265 Some(val_idx)
266}
267
268pub fn prefork(list: &mut LinkList, flags: u32, ret_flags: &mut u32, state: &mut SubstState) {
271 let mut node_idx = 0;
272 let mut stop_idx: Option<usize> = None;
273 let mut keep = false;
274 let asssub = (flags & prefork_flags::TYPESET != 0) && state.opts.ksh_typeset;
275
276 while node_idx < list.len() {
277 if (flags & (prefork_flags::SINGLE | prefork_flags::ASSIGN)) == prefork_flags::ASSIGN {
279 if let Some(new_idx) = keyvalpairelement(list, node_idx) {
280 node_idx = new_idx + 1;
281 *ret_flags |= prefork_flags::KEY_VALUE;
282 continue;
283 }
284 }
285
286 if state.errflag {
287 return;
288 }
289
290 if state.opts.sh_file_expansion {
291 if let Some(data) = list.get_data(node_idx) {
293 let new_data = filesub(
294 data,
295 flags & (prefork_flags::TYPESET | prefork_flags::ASSIGN),
296 state,
297 );
298 list.set_data(node_idx, new_data);
299 }
300 } else {
301 if let Some(new_idx) = stringsubst(
303 list,
304 node_idx,
305 flags & !(prefork_flags::TYPESET | prefork_flags::ASSIGN),
306 ret_flags,
307 asssub,
308 state,
309 ) {
310 node_idx = new_idx;
311 } else {
312 return;
313 }
314 }
315
316 node_idx += 1;
317 }
318
319 if state.opts.sh_file_expansion {
321 node_idx = 0;
322 while node_idx < list.len() {
323 if let Some(new_idx) = stringsubst(
324 list,
325 node_idx,
326 flags & !(prefork_flags::TYPESET | prefork_flags::ASSIGN),
327 ret_flags,
328 asssub,
329 state,
330 ) {
331 node_idx = new_idx + 1;
332 } else {
333 return;
334 }
335 }
336 }
337
338 node_idx = 0;
340 while node_idx < list.len() {
341 if Some(node_idx) == stop_idx {
342 keep = false;
343 }
344
345 if let Some(data) = list.get_data(node_idx) {
346 if !data.is_empty() {
347 let data = remnulargs(data);
349 list.set_data(node_idx, data.clone());
350
351 if !state.opts.ignore_braces && (flags & prefork_flags::SINGLE == 0) {
353 if !keep {
354 stop_idx = list.next_node(node_idx);
355 }
356 while hasbraces(list.get_data(node_idx).unwrap_or("")) {
357 keep = true;
358 xpandbraces(list, &mut node_idx);
359 }
360 }
361
362 if !state.opts.sh_file_expansion {
364 if let Some(data) = list.get_data(node_idx) {
365 let new_data = filesub(
366 data,
367 flags & (prefork_flags::TYPESET | prefork_flags::ASSIGN),
368 state,
369 );
370 list.set_data(node_idx, new_data);
371 }
372 }
373 } else if (flags & prefork_flags::SINGLE == 0)
374 && (*ret_flags & prefork_flags::KEY_VALUE == 0)
375 && !keep
376 {
377 list.remove(node_idx);
378 continue; }
380 }
381
382 if state.errflag {
383 return;
384 }
385
386 node_idx += 1;
387 }
388}
389
390fn stringsubstquote(strstart: &str, strdpos: usize) -> (String, usize) {
393 let chars: Vec<char> = strstart.chars().collect();
394
395 let start = strdpos + 2; let mut end = start;
398 let mut escaped = false;
399
400 while end < chars.len() {
401 if escaped {
402 escaped = false;
403 end += 1;
404 continue;
405 }
406 if chars[end] == '\\' {
407 escaped = true;
408 end += 1;
409 continue;
410 }
411 if chars[end] == '\'' {
412 break;
413 }
414 end += 1;
415 }
416
417 let content: String = chars[start..end].iter().collect();
419 let processed = getkeystring(&content);
420
421 let prefix: String = chars[..strdpos].iter().collect();
423 let suffix: String = if end + 1 < chars.len() {
424 chars[end + 1..].iter().collect()
425 } else {
426 String::new()
427 };
428
429 let result = format!("{}{}{}", prefix, processed, suffix);
430 let new_pos = strdpos + processed.len();
431
432 (result, new_pos)
433}
434
435fn getkeystring(s: &str) -> String {
438 let mut result = String::new();
439 let mut chars = s.chars().peekable();
440
441 while let Some(c) = chars.next() {
442 if c == '\\' {
443 match chars.next() {
444 Some('n') => result.push('\n'),
445 Some('t') => result.push('\t'),
446 Some('r') => result.push('\r'),
447 Some('\\') => result.push('\\'),
448 Some('\'') => result.push('\''),
449 Some('"') => result.push('"'),
450 Some('a') => result.push('\x07'),
451 Some('b') => result.push('\x08'),
452 Some('e') | Some('E') => result.push('\x1b'),
453 Some('f') => result.push('\x0c'),
454 Some('v') => result.push('\x0b'),
455 Some('0') => {
456 let mut val = 0u32;
458 for _ in 0..3 {
459 if let Some(&c) = chars.peek() {
460 if c >= '0' && c <= '7' {
461 val = val * 8 + (c as u32 - '0' as u32);
462 chars.next();
463 } else {
464 break;
465 }
466 }
467 }
468 if let Some(ch) = char::from_u32(val) {
469 result.push(ch);
470 }
471 }
472 Some('x') => {
473 let mut val = 0u32;
475 for _ in 0..2 {
476 if let Some(&c) = chars.peek() {
477 if c.is_ascii_hexdigit() {
478 val = val * 16 + c.to_digit(16).unwrap();
479 chars.next();
480 } else {
481 break;
482 }
483 }
484 }
485 if let Some(ch) = char::from_u32(val) {
486 result.push(ch);
487 }
488 }
489 Some('u') => {
490 let mut val = 0u32;
492 for _ in 0..4 {
493 if let Some(&c) = chars.peek() {
494 if c.is_ascii_hexdigit() {
495 val = val * 16 + c.to_digit(16).unwrap();
496 chars.next();
497 } else {
498 break;
499 }
500 }
501 }
502 if let Some(ch) = char::from_u32(val) {
503 result.push(ch);
504 }
505 }
506 Some('U') => {
507 let mut val = 0u32;
509 for _ in 0..8 {
510 if let Some(&c) = chars.peek() {
511 if c.is_ascii_hexdigit() {
512 val = val * 16 + c.to_digit(16).unwrap();
513 chars.next();
514 } else {
515 break;
516 }
517 }
518 }
519 if let Some(ch) = char::from_u32(val) {
520 result.push(ch);
521 }
522 }
523 Some(c) => result.push(c),
524 None => result.push('\\'),
525 }
526 } else {
527 result.push(c);
528 }
529 }
530
531 result
532}
533
534fn stringsubst(
537 list: &mut LinkList,
538 node_idx: usize,
539 pf_flags: u32,
540 ret_flags: &mut u32,
541 asssub: bool,
542 state: &mut SubstState,
543) -> Option<usize> {
544 let mut str3 = list.get_data(node_idx)?.to_string();
545 let mut pos = 0;
546
547 while pos < str3.len() && !state.errflag {
549 let chars: Vec<char> = str3.chars().collect();
550 let c = chars[pos];
551
552 if (c == INANG || c == OUTANGPROC || (pos == 0 && c == EQUALS))
554 && chars.get(pos + 1) == Some(&INPAR)
555 {
556 let (subst, rest) = if c == INANG || c == OUTANGPROC {
557 getproc(&str3[pos..], state)
558 } else {
559 getoutputfile(&str3[pos..], state)
560 };
561
562 if state.errflag {
563 return None;
564 }
565
566 let subst = subst.unwrap_or_default();
567 let prefix: String = chars[..pos].iter().collect();
568 str3 = format!("{}{}{}", prefix, subst, rest);
569 pos += subst.len();
570 list.set_data(node_idx, str3.clone());
571 continue;
572 }
573
574 pos += 1;
575 }
576
577 pos = 0;
579 while pos < str3.len() && !state.errflag {
580 let chars: Vec<char> = str3.chars().collect();
581 let c = chars[pos];
582
583 let qt = c == QSTRING;
584 if qt || c == STRING {
585 let next_c = chars.get(pos + 1).copied();
586
587 if next_c == Some(INPAR) || next_c == Some(INPARMATH) {
588 if !qt {
589 list.flags |= LF_ARRAY;
590 }
591 pos += 1;
593 let (result, new_pos) = process_command_subst(&str3, pos, qt, state);
594 str3 = result;
595 pos = new_pos;
596 list.set_data(node_idx, str3.clone());
597 continue;
598 } else if next_c == Some(INBRACK) {
599 let start = pos + 2;
601 if let Some(end) = find_matching_bracket(&str3[start..], INBRACK, OUTBRACK) {
602 let expr: String = str3.chars().skip(start).take(end).collect();
603 let value = arithsubst(&expr, state);
604 let prefix: String = str3.chars().take(pos).collect();
605 let suffix: String = str3.chars().skip(start + end + 1).collect();
606 str3 = format!("{}{}{}", prefix, value, suffix);
607 list.set_data(node_idx, str3.clone());
608 continue;
609 } else {
610 state.errflag = true;
611 eprintln!("closing bracket missing");
612 return None;
613 }
614 } else if next_c == Some(SNULL) {
615 let (new_str, new_pos) = stringsubstquote(&str3, pos);
617 str3 = new_str;
618 pos = new_pos;
619 list.set_data(node_idx, str3.clone());
620 continue;
621 } else {
622 let mut new_pf_flags = pf_flags;
624 if (state.opts.sh_word_split && (pf_flags & prefork_flags::NOSHWORDSPLIT == 0))
625 || (pf_flags & prefork_flags::SPLIT != 0)
626 {
627 new_pf_flags |= prefork_flags::SHWORDSPLIT;
628 }
629
630 let (new_str, new_pos, new_nodes) = paramsubst(
631 &str3,
632 pos,
633 qt,
634 new_pf_flags
635 & (prefork_flags::SINGLE
636 | prefork_flags::SHWORDSPLIT
637 | prefork_flags::SUBEXP),
638 ret_flags,
639 state,
640 );
641
642 if state.errflag {
643 return None;
644 }
645
646 let mut current_idx = node_idx;
648 for (i, node_data) in new_nodes.into_iter().enumerate() {
649 if i == 0 {
650 list.set_data(current_idx, node_data);
651 } else {
652 current_idx = list.insert_after(current_idx, node_data);
653 }
654 }
655
656 str3 = list.get_data(node_idx)?.to_string();
657 pos = new_pos;
658 continue;
659 }
660 }
661
662 let qt = c == QTICK;
664 if qt || c == TICK {
665 if !qt {
666 list.flags |= LF_ARRAY;
667 }
668 let (result, new_pos) = process_backtick_subst(&str3, pos, qt, pf_flags, state);
669 str3 = result;
670 pos = new_pos;
671 list.set_data(node_idx, str3.clone());
672 continue;
673 }
674
675 if asssub && (c == '=' || c == EQUALS) && pos > 0 {
677 }
680
681 pos += 1;
682 }
683
684 if state.errflag {
685 None
686 } else {
687 Some(node_idx)
688 }
689}
690
691fn process_command_subst(
693 s: &str,
694 start_pos: usize,
695 qt: bool,
696 state: &mut SubstState,
697) -> (String, usize) {
698 let chars: Vec<char> = s.chars().collect();
699 let c = chars.get(start_pos).copied().unwrap_or('\0');
700
701 if c == INPARMATH {
702 let expr_start = start_pos + 1;
704 if let Some(end) = find_matching_parmath(&s[expr_start..]) {
705 let expr: String = s.chars().skip(expr_start).take(end).collect();
706 let value = arithsubst(&expr, state);
707 let prefix: String = s.chars().take(start_pos - 1).collect();
708 let suffix: String = s.chars().skip(expr_start + end + 1).collect();
709 return (
710 format!("{}{}{}", prefix, value, suffix),
711 prefix.len() + value.len(),
712 );
713 }
714 }
715
716 if let Some(end) = find_matching_bracket(&s[start_pos..], INPAR, OUTPAR) {
718 let cmd: String = s.chars().skip(start_pos + 1).take(end - 1).collect();
719 let output = if state.opts.exec_opt {
720 run_command(&cmd)
721 } else {
722 String::new()
723 };
724 let output = output.trim_end_matches('\n');
725 let prefix: String = s.chars().take(start_pos - 1).collect();
726 let suffix: String = s.chars().skip(start_pos + end + 1).collect();
727 return (
728 format!("{}{}{}", prefix, output, suffix),
729 prefix.len() + output.len(),
730 );
731 }
732
733 (s.to_string(), start_pos + 1)
734}
735
736fn process_backtick_subst(
738 s: &str,
739 start_pos: usize,
740 _qt: bool,
741 _pf_flags: u32,
742 state: &mut SubstState,
743) -> (String, usize) {
744 let chars: Vec<char> = s.chars().collect();
745 let end_char = chars[start_pos]; let mut end_pos = start_pos + 1;
749 while end_pos < chars.len() && chars[end_pos] != end_char {
750 end_pos += 1;
751 }
752
753 if end_pos >= chars.len() {
754 state.errflag = true;
755 eprintln!("failed to find end of command substitution");
756 return (s.to_string(), start_pos + 1);
757 }
758
759 let cmd: String = chars[start_pos + 1..end_pos].iter().collect();
760 let output = run_command(&cmd);
761 let output = output.trim_end_matches('\n');
762
763 let prefix: String = chars[..start_pos].iter().collect();
764 let suffix: String = chars[end_pos + 1..].iter().collect();
765
766 (
767 format!("{}{}{}", prefix, output, suffix),
768 prefix.len() + output.len(),
769 )
770}
771
772fn paramsubst(
775 s: &str,
776 start_pos: usize,
777 qt: bool,
778 pf_flags: u32,
779 ret_flags: &mut u32,
780 state: &mut SubstState,
781) -> (String, usize, Vec<String>) {
782 let chars: Vec<char> = s.chars().collect();
783 let mut pos = start_pos + 1; let mut result_nodes = Vec::new();
785
786 let c = chars.get(pos).copied().unwrap_or('\0');
788
789 if c == INBRACE || c == '{' {
791 pos += 1;
792 return parse_brace_param(s, start_pos, pos, qt, pf_flags, ret_flags, state);
793 }
794
795 if c.is_ascii_alphabetic() || c == '_' {
797 let var_start = pos;
798 while pos < chars.len() && (chars[pos].is_ascii_alphanumeric() || chars[pos] == '_') {
799 pos += 1;
800 }
801 let var_name: String = chars[var_start..pos].iter().collect();
802
803 let value = get_param_value(&var_name, state);
804
805 if pf_flags & prefork_flags::SHWORDSPLIT != 0 && !qt {
807 let words = split_words(&value, state);
808 if words.len() > 1 {
809 let prefix: String = chars[..start_pos].iter().collect();
810 let suffix: String = chars[pos..].iter().collect();
811
812 for (i, word) in words.iter().enumerate() {
813 if i == 0 {
814 result_nodes.push(format!("{}{}", prefix, word));
815 } else if i == words.len() - 1 {
816 result_nodes.push(format!("{}{}", word, suffix));
817 } else {
818 result_nodes.push(word.clone());
819 }
820 }
821 return (
822 result_nodes[0].clone(),
823 prefix.len() + words[0].len(),
824 result_nodes,
825 );
826 }
827 }
828
829 let prefix: String = chars[..start_pos].iter().collect();
830 let suffix: String = chars[pos..].iter().collect();
831 let result = format!("{}{}{}", prefix, value, suffix);
832 result_nodes.push(result.clone());
833 return (result, prefix.len() + value.len(), result_nodes);
834 }
835
836 match c {
838 '?' => {
839 let value = state
840 .variables
841 .get("?")
842 .cloned()
843 .unwrap_or_else(|| "0".to_string());
844 let prefix: String = chars[..start_pos].iter().collect();
845 let suffix: String = chars[pos + 1..].iter().collect();
846 let result = format!("{}{}{}", prefix, value, suffix);
847 result_nodes.push(result.clone());
848 (result, prefix.len() + value.len(), result_nodes)
849 }
850 '$' => {
851 let value = std::process::id().to_string();
852 let prefix: String = chars[..start_pos].iter().collect();
853 let suffix: String = chars[pos + 1..].iter().collect();
854 let result = format!("{}{}{}", prefix, value, suffix);
855 result_nodes.push(result.clone());
856 (result, prefix.len() + value.len(), result_nodes)
857 }
858 '#' => {
859 let value = state
860 .arrays
861 .get("@")
862 .map(|a| a.len().to_string())
863 .unwrap_or_else(|| "0".to_string());
864 let prefix: String = chars[..start_pos].iter().collect();
865 let suffix: String = chars[pos + 1..].iter().collect();
866 let result = format!("{}{}{}", prefix, value, suffix);
867 result_nodes.push(result.clone());
868 (result, prefix.len() + value.len(), result_nodes)
869 }
870 '*' | '@' => {
871 let values = state.arrays.get("@").cloned().unwrap_or_default();
872 let value = if c == '*' || qt {
873 values.join(" ")
874 } else {
875 if pf_flags & prefork_flags::SINGLE == 0 {
877 let prefix: String = chars[..start_pos].iter().collect();
878 let suffix: String = chars[pos + 1..].iter().collect();
879 for (i, v) in values.iter().enumerate() {
880 if i == 0 {
881 result_nodes.push(format!("{}{}", prefix, v));
882 } else if i == values.len() - 1 {
883 result_nodes.push(format!("{}{}", v, suffix));
884 } else {
885 result_nodes.push(v.clone());
886 }
887 }
888 if result_nodes.is_empty() {
889 result_nodes.push(format!("{}{}", prefix, suffix));
890 }
891 return (result_nodes[0].clone(), start_pos, result_nodes);
892 }
893 values.join(" ")
894 };
895 let prefix: String = chars[..start_pos].iter().collect();
896 let suffix: String = chars[pos + 1..].iter().collect();
897 let result = format!("{}{}{}", prefix, value, suffix);
898 result_nodes.push(result.clone());
899 (result, prefix.len() + value.len(), result_nodes)
900 }
901 '0'..='9' => {
902 let digit = c.to_digit(10).unwrap() as usize;
903 let value = state
904 .arrays
905 .get("@")
906 .and_then(|a| a.get(digit))
907 .cloned()
908 .unwrap_or_default();
909 let prefix: String = chars[..start_pos].iter().collect();
910 let suffix: String = chars[pos + 1..].iter().collect();
911 let result = format!("{}{}{}", prefix, value, suffix);
912 result_nodes.push(result.clone());
913 (result, prefix.len() + value.len(), result_nodes)
914 }
915 _ => {
916 result_nodes.push(s.to_string());
918 (s.to_string(), start_pos + 1, result_nodes)
919 }
920 }
921}
922
923fn parse_brace_param(
926 s: &str,
927 dollar_pos: usize,
928 brace_pos: usize,
929 qt: bool,
930 pf_flags: u32,
931 _ret_flags: &mut u32,
932 state: &mut SubstState,
933) -> (String, usize, Vec<String>) {
934 let chars: Vec<char> = s.chars().collect();
935 let mut pos = brace_pos;
936 let mut result_nodes = Vec::new();
937
938 let mut flags = ParamFlags::default();
940 if chars.get(pos) == Some(&'(') {
941 pos += 1;
942 while pos < chars.len() && chars[pos] != ')' {
943 let flag_char = chars[pos];
944 match flag_char {
945 'L' => flags.lowercase = true,
946 'U' => flags.uppercase = true,
947 'C' => flags.capitalize = true,
948 'u' => flags.unique = true,
949 'o' => flags.sort = true,
950 'O' => flags.sort_reverse = true,
951 'a' => flags.sort_array_index = true,
952 'i' => flags.sort_case_insensitive = true,
953 'n' => flags.sort_numeric = true,
954 'k' => flags.keys = true,
955 'v' => flags.values = true,
956 't' => flags.type_info = true,
957 'P' => flags.prompt_expand = true,
958 'e' => flags.eval = true,
959 'q' => flags.quote_level += 1,
960 'Q' => flags.unquote = true,
961 'X' => flags.report_error = true,
962 'z' => flags.split_words = true,
963 'f' => flags.split_lines = true,
964 'F' => flags.join_lines = true,
965 'w' => flags.count_words = true,
966 'W' => flags.count_words_null = true,
967 'c' => flags.count_chars = true,
968 '#' => flags.length_chars = true,
969 '%' => flags.prompt_percent = true,
970 'A' => flags.create_assoc = true,
971 '@' => flags.array_expand = true,
972 '~' => flags.glob_subst = true,
973 'V' => flags.visible = true,
974 'S' | 'I' => flags.search = true,
975 'M' => flags.match_flag = true,
976 'R' => flags.reverse_subscript = true,
977 'B' | 'E' | 'N' => flags.begin_end_length = true,
978 's' => {
979 pos += 1;
981 if pos < chars.len() && chars[pos] == ':' {
982 pos += 1;
983 let mut sep = String::new();
984 while pos < chars.len() && chars[pos] != ':' {
985 sep.push(chars[pos]);
986 pos += 1;
987 }
988 flags.split_sep = Some(sep);
989 } else {
990 pos -= 1;
991 }
992 }
993 'j' => {
994 pos += 1;
996 if pos < chars.len() && chars[pos] == ':' {
997 pos += 1;
998 let mut sep = String::new();
999 while pos < chars.len() && chars[pos] != ':' {
1000 sep.push(chars[pos]);
1001 pos += 1;
1002 }
1003 flags.join_sep = Some(sep);
1004 } else {
1005 pos -= 1;
1006 }
1007 }
1008 'l' => {
1009 pos += 1;
1011 if pos < chars.len() && chars[pos] == ':' {
1012 pos += 1;
1014 let mut len_str = String::new();
1015 while pos < chars.len() && chars[pos].is_ascii_digit() {
1016 len_str.push(chars[pos]);
1017 pos += 1;
1018 }
1019 if let Ok(len) = len_str.parse() {
1020 flags.pad_left = Some(len);
1021 }
1022 if pos < chars.len() && chars[pos] == ':' {
1023 pos += 1;
1024 let mut fill = String::new();
1025 while pos < chars.len() && chars[pos] != ':' {
1026 fill.push(chars[pos]);
1027 pos += 1;
1028 }
1029 flags.pad_char = Some(fill.chars().next().unwrap_or(' '));
1030 }
1031 } else {
1032 pos -= 1;
1033 }
1034 }
1035 'r' => {
1036 pos += 1;
1038 if pos < chars.len() && chars[pos] == ':' {
1039 pos += 1;
1040 let mut len_str = String::new();
1041 while pos < chars.len() && chars[pos].is_ascii_digit() {
1042 len_str.push(chars[pos]);
1043 pos += 1;
1044 }
1045 if let Ok(len) = len_str.parse() {
1046 flags.pad_right = Some(len);
1047 }
1048 if pos < chars.len() && chars[pos] == ':' {
1049 pos += 1;
1050 let mut fill = String::new();
1051 while pos < chars.len() && chars[pos] != ':' {
1052 fill.push(chars[pos]);
1053 pos += 1;
1054 }
1055 flags.pad_char = Some(fill.chars().next().unwrap_or(' '));
1056 }
1057 } else {
1058 pos -= 1;
1059 }
1060 }
1061 _ => {}
1062 }
1063 pos += 1;
1064 }
1065 if pos < chars.len() {
1066 pos += 1; }
1068 }
1069
1070 let length_prefix = chars.get(pos) == Some(&'#');
1072 if length_prefix {
1073 pos += 1;
1074 }
1075
1076 let var_start = pos;
1078 while pos < chars.len() {
1079 let c = chars[pos];
1080 if c.is_ascii_alphanumeric() || c == '_' {
1081 pos += 1;
1082 } else {
1083 break;
1084 }
1085 }
1086 let var_name: String = chars[var_start..pos].iter().collect();
1087
1088 let mut subscript = None;
1090 if chars.get(pos) == Some(&'[') || chars.get(pos) == Some(&INBRACK) {
1091 pos += 1;
1092 let sub_start = pos;
1093 let mut depth = 1;
1094 while pos < chars.len() && depth > 0 {
1095 let c = chars[pos];
1096 if c == '[' || c == INBRACK {
1097 depth += 1;
1098 } else if c == ']' || c == OUTBRACK {
1099 depth -= 1;
1100 }
1101 if depth > 0 {
1102 pos += 1;
1103 }
1104 }
1105 subscript = Some(chars[sub_start..pos].iter().collect::<String>());
1106 pos += 1; }
1108
1109 let mut operator = None;
1111 let mut operand = String::new();
1112
1113 if pos < chars.len() {
1115 let c = chars[pos];
1116 match c {
1117 ':' => {
1118 pos += 1;
1119 if pos < chars.len() {
1120 match chars[pos] {
1121 '-' => {
1122 operator = Some(":-");
1123 pos += 1;
1124 }
1125 '=' => {
1126 operator = Some(":=");
1127 pos += 1;
1128 }
1129 '+' => {
1130 operator = Some(":+");
1131 pos += 1;
1132 }
1133 '?' => {
1134 operator = Some(":?");
1135 pos += 1;
1136 }
1137 _ => {
1138 operator = Some(":");
1139 } }
1141 }
1142 }
1143 '-' => {
1144 operator = Some("-");
1145 pos += 1;
1146 }
1147 '=' => {
1148 operator = Some("=");
1149 pos += 1;
1150 }
1151 '+' => {
1152 operator = Some("+");
1153 pos += 1;
1154 }
1155 '?' => {
1156 operator = Some("?");
1157 pos += 1;
1158 }
1159 '#' => {
1160 pos += 1;
1161 if chars.get(pos) == Some(&'#') {
1162 operator = Some("##");
1163 pos += 1;
1164 } else {
1165 operator = Some("#");
1166 }
1167 }
1168 '%' => {
1169 pos += 1;
1170 if chars.get(pos) == Some(&'%') {
1171 operator = Some("%%");
1172 pos += 1;
1173 } else {
1174 operator = Some("%");
1175 }
1176 }
1177 '/' => {
1178 pos += 1;
1179 if chars.get(pos) == Some(&'/') {
1180 operator = Some("//");
1181 pos += 1;
1182 } else {
1183 operator = Some("/");
1184 }
1185 }
1186 '^' => {
1187 pos += 1;
1188 if chars.get(pos) == Some(&'^') {
1189 operator = Some("^^");
1190 pos += 1;
1191 } else {
1192 operator = Some("^");
1193 }
1194 }
1195 ',' => {
1196 pos += 1;
1197 if chars.get(pos) == Some(&',') {
1198 operator = Some(",,");
1199 pos += 1;
1200 } else {
1201 operator = Some(",");
1202 }
1203 }
1204 _ => {}
1205 }
1206 }
1207
1208 let mut depth = 1;
1210 while pos < chars.len() && depth > 0 {
1211 let c = chars[pos];
1212 if c == '{' || c == INBRACE {
1213 depth += 1;
1214 operand.push(c);
1215 } else if c == '}' || c == OUTBRACE {
1216 depth -= 1;
1217 if depth > 0 {
1218 operand.push(c);
1219 }
1220 } else {
1221 operand.push(c);
1222 }
1223 pos += 1;
1224 }
1225
1226 let mut value = if subscript.is_some() || !var_name.is_empty() {
1228 get_param_with_subscript(&var_name, subscript.as_deref(), state)
1229 } else {
1230 Vec::new()
1231 };
1232
1233 if length_prefix {
1235 let len = if value.len() == 1 {
1236 value[0].chars().count()
1237 } else {
1238 value.len()
1239 };
1240 value = vec![len.to_string()];
1241 }
1242
1243 value = apply_param_flags(&value, &flags, state);
1245
1246 value = apply_operator(&var_name, value, operator, &operand, state);
1248
1249 let joined = if flags.join_sep.is_some() || value.len() == 1 {
1251 let sep = flags.join_sep.as_deref().unwrap_or(" ");
1252 value.join(sep)
1253 } else if pf_flags & prefork_flags::SHWORDSPLIT != 0 && !qt {
1254 let prefix: String = chars[..dollar_pos].iter().collect();
1256 let suffix: String = chars[pos..].iter().collect();
1257
1258 for (i, v) in value.iter().enumerate() {
1259 if i == 0 && value.len() == 1 {
1260 result_nodes.push(format!("{}{}{}", prefix, v, suffix));
1261 } else if i == 0 {
1262 result_nodes.push(format!("{}{}", prefix, v));
1263 } else if i == value.len() - 1 {
1264 result_nodes.push(format!("{}{}", v, suffix));
1265 } else {
1266 result_nodes.push(v.clone());
1267 }
1268 }
1269
1270 if result_nodes.is_empty() {
1271 result_nodes.push(format!("{}{}", prefix, suffix));
1272 }
1273
1274 return (result_nodes[0].clone(), dollar_pos, result_nodes);
1275 } else {
1276 value.join(" ")
1277 };
1278
1279 let prefix: String = chars[..dollar_pos].iter().collect();
1281 let suffix: String = chars[pos..].iter().collect();
1282 let result = format!("{}{}{}", prefix, joined, suffix);
1283 result_nodes.push(result.clone());
1284
1285 (result, prefix.len() + joined.len(), result_nodes)
1286}
1287
1288#[derive(Default, Clone, Debug)]
1290struct ParamFlags {
1291 lowercase: bool,
1292 uppercase: bool,
1293 capitalize: bool,
1294 unique: bool,
1295 sort: bool,
1296 sort_reverse: bool,
1297 sort_array_index: bool,
1298 sort_case_insensitive: bool,
1299 sort_numeric: bool,
1300 keys: bool,
1301 values: bool,
1302 type_info: bool,
1303 prompt_expand: bool,
1304 prompt_percent: bool,
1305 eval: bool,
1306 quote_level: usize,
1307 unquote: bool,
1308 report_error: bool,
1309 split_words: bool,
1310 split_lines: bool,
1311 join_lines: bool,
1312 count_words: bool,
1313 count_words_null: bool,
1314 count_chars: bool,
1315 length_chars: bool,
1316 create_assoc: bool,
1317 array_expand: bool,
1318 glob_subst: bool,
1319 visible: bool,
1320 search: bool,
1321 match_flag: bool,
1322 reverse_subscript: bool,
1323 begin_end_length: bool,
1324 split_sep: Option<String>,
1325 join_sep: Option<String>,
1326 pad_left: Option<usize>,
1327 pad_right: Option<usize>,
1328 pad_char: Option<char>,
1329}
1330
1331fn get_param_value(name: &str, state: &SubstState) -> String {
1333 state
1334 .variables
1335 .get(name)
1336 .cloned()
1337 .or_else(|| std::env::var(name).ok())
1338 .unwrap_or_default()
1339}
1340
1341fn get_param_with_subscript(
1343 name: &str,
1344 subscript: Option<&str>,
1345 state: &SubstState,
1346) -> Vec<String> {
1347 if let Some(arr) = state.arrays.get(name) {
1349 if let Some(sub) = subscript {
1350 if sub == "@" || sub == "*" {
1351 return arr.clone();
1352 }
1353 if let Ok(idx) = sub.parse::<i64>() {
1355 let idx = if idx < 0 {
1356 (arr.len() as i64 + idx) as usize
1357 } else {
1358 (idx - 1).max(0) as usize };
1360 return arr.get(idx).cloned().into_iter().collect();
1361 }
1362 }
1363 return arr.clone();
1364 }
1365
1366 if let Some(assoc) = state.assoc_arrays.get(name) {
1368 if let Some(sub) = subscript {
1369 if sub == "@" || sub == "*" {
1370 return assoc.values().cloned().collect();
1371 }
1372 return assoc.get(sub).cloned().into_iter().collect();
1373 }
1374 return assoc.values().cloned().collect();
1375 }
1376
1377 let value = get_param_value(name, state);
1379 if value.is_empty() {
1380 Vec::new()
1381 } else {
1382 vec![value]
1383 }
1384}
1385
1386fn apply_param_flags(value: &[String], flags: &ParamFlags, _state: &SubstState) -> Vec<String> {
1388 let mut result: Vec<String> = value.to_vec();
1389
1390 if let Some(ref sep) = flags.split_sep {
1392 result = result
1393 .iter()
1394 .flat_map(|s| s.split(sep).map(String::from))
1395 .collect();
1396 }
1397 if flags.split_lines {
1398 result = result
1399 .iter()
1400 .flat_map(|s| s.lines().map(String::from))
1401 .collect();
1402 }
1403 if flags.split_words {
1404 result = result
1405 .iter()
1406 .flat_map(|s| s.split_whitespace().map(String::from))
1407 .collect();
1408 }
1409
1410 if flags.lowercase {
1412 result = result.iter().map(|s| s.to_lowercase()).collect();
1413 }
1414 if flags.uppercase {
1415 result = result.iter().map(|s| s.to_uppercase()).collect();
1416 }
1417 if flags.capitalize {
1418 result = result
1419 .iter()
1420 .map(|s| {
1421 let mut chars = s.chars();
1422 match chars.next() {
1423 None => String::new(),
1424 Some(c) => c.to_uppercase().chain(chars).collect(),
1425 }
1426 })
1427 .collect();
1428 }
1429
1430 if flags.unique {
1432 let mut seen = std::collections::HashSet::new();
1433 result = result
1434 .into_iter()
1435 .filter(|s| seen.insert(s.clone()))
1436 .collect();
1437 }
1438
1439 if flags.sort {
1441 if flags.sort_numeric {
1442 result.sort_by(|a, b| {
1443 let na: f64 = a.parse().unwrap_or(0.0);
1444 let nb: f64 = b.parse().unwrap_or(0.0);
1445 na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal)
1446 });
1447 } else if flags.sort_case_insensitive {
1448 result.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
1449 } else {
1450 result.sort();
1451 }
1452 }
1453 if flags.sort_reverse {
1454 result.reverse();
1455 }
1456
1457 for _ in 0..flags.quote_level {
1459 result = result
1460 .iter()
1461 .map(|s| format!("'{}'", s.replace('\'', "'\\''")))
1462 .collect();
1463 }
1464 if flags.unquote {
1465 result = result
1466 .iter()
1467 .map(|s| {
1468 let s = s.trim();
1470 if (s.starts_with('\'') && s.ends_with('\''))
1471 || (s.starts_with('"') && s.ends_with('"'))
1472 {
1473 s[1..s.len() - 1].to_string()
1474 } else {
1475 s.to_string()
1476 }
1477 })
1478 .collect();
1479 }
1480
1481 if flags.join_lines {
1483 result = vec![result.join("\n")];
1484 }
1485 if let Some(ref sep) = flags.join_sep {
1486 result = vec![result.join(sep)];
1487 }
1488
1489 if flags.count_words {
1491 let count = result
1492 .iter()
1493 .map(|s| s.split_whitespace().count())
1494 .sum::<usize>();
1495 result = vec![count.to_string()];
1496 }
1497 if flags.count_chars {
1498 let count = result.iter().map(|s| s.chars().count()).sum::<usize>();
1499 result = vec![count.to_string()];
1500 }
1501
1502 if let Some(width) = flags.pad_left {
1504 let fill = flags.pad_char.unwrap_or(' ');
1505 result = result
1506 .iter()
1507 .map(|s| {
1508 if s.len() < width {
1509 format!("{}{}", fill.to_string().repeat(width - s.len()), s)
1510 } else {
1511 s.clone()
1512 }
1513 })
1514 .collect();
1515 }
1516 if let Some(width) = flags.pad_right {
1517 let fill = flags.pad_char.unwrap_or(' ');
1518 result = result
1519 .iter()
1520 .map(|s| {
1521 if s.len() < width {
1522 format!("{}{}", s, fill.to_string().repeat(width - s.len()))
1523 } else {
1524 s.clone()
1525 }
1526 })
1527 .collect();
1528 }
1529
1530 result
1531}
1532
1533fn apply_operator(
1535 var_name: &str,
1536 value: Vec<String>,
1537 operator: Option<&str>,
1538 operand: &str,
1539 state: &mut SubstState,
1540) -> Vec<String> {
1541 let is_set = !value.is_empty();
1542 let is_empty = value.iter().all(|s| s.is_empty());
1543 let joined = value.join(" ");
1544
1545 match operator {
1546 Some(":-") | Some("-") => {
1547 if (operator == Some(":-") && (is_empty || !is_set))
1548 || (operator == Some("-") && !is_set)
1549 {
1550 vec![operand.to_string()]
1551 } else {
1552 value
1553 }
1554 }
1555 Some(":=") | Some("=") => {
1556 if (operator == Some(":=") && (is_empty || !is_set))
1557 || (operator == Some("=") && !is_set)
1558 {
1559 state
1560 .variables
1561 .insert(var_name.to_string(), operand.to_string());
1562 vec![operand.to_string()]
1563 } else {
1564 value
1565 }
1566 }
1567 Some(":+") | Some("+") => {
1568 if (operator == Some(":+") && !is_empty && is_set) || (operator == Some("+") && is_set)
1569 {
1570 vec![operand.to_string()]
1571 } else {
1572 vec![]
1573 }
1574 }
1575 Some(":?") | Some("?") => {
1576 if (operator == Some(":?") && (is_empty || !is_set))
1577 || (operator == Some("?") && !is_set)
1578 {
1579 let msg = if operand.is_empty() {
1580 format!("{}: parameter not set", var_name)
1581 } else {
1582 operand.to_string()
1583 };
1584 eprintln!("{}", msg);
1585 state.errflag = true;
1586 vec![]
1587 } else {
1588 value
1589 }
1590 }
1591 Some(":") => {
1592 let parts: Vec<&str> = operand.split(':').collect();
1594 let offset: i64 = parts.get(0).and_then(|s| s.parse().ok()).unwrap_or(0);
1595 let length: Option<i64> = parts.get(1).and_then(|s| s.parse().ok());
1596
1597 value
1598 .iter()
1599 .map(|s| {
1600 let chars: Vec<char> = s.chars().collect();
1601 let len = chars.len() as i64;
1602
1603 let start = if offset < 0 {
1604 (len + offset).max(0) as usize
1605 } else {
1606 (offset as usize).min(chars.len())
1607 };
1608
1609 let end = match length {
1610 Some(l) if l < 0 => (len + l).max(start as i64) as usize,
1611 Some(l) => (start + l as usize).min(chars.len()),
1612 None => chars.len(),
1613 };
1614
1615 chars[start..end].iter().collect()
1616 })
1617 .collect()
1618 }
1619 Some("#") => {
1620 value
1622 .iter()
1623 .map(|s| remove_prefix(s, operand, false))
1624 .collect()
1625 }
1626 Some("##") => {
1627 value
1629 .iter()
1630 .map(|s| remove_prefix(s, operand, true))
1631 .collect()
1632 }
1633 Some("%") => {
1634 value
1636 .iter()
1637 .map(|s| remove_suffix(s, operand, false))
1638 .collect()
1639 }
1640 Some("%%") => {
1641 value
1643 .iter()
1644 .map(|s| remove_suffix(s, operand, true))
1645 .collect()
1646 }
1647 Some("/") => {
1648 let parts: Vec<&str> = operand.splitn(2, '/').collect();
1650 let pattern = parts.get(0).unwrap_or(&"");
1651 let replacement = parts.get(1).unwrap_or(&"");
1652 value
1653 .iter()
1654 .map(|s| s.replacen(pattern, replacement, 1))
1655 .collect()
1656 }
1657 Some("//") => {
1658 let parts: Vec<&str> = operand.splitn(2, '/').collect();
1660 let pattern = parts.get(0).unwrap_or(&"");
1661 let replacement = parts.get(1).unwrap_or(&"");
1662 value
1663 .iter()
1664 .map(|s| s.replace(pattern, replacement))
1665 .collect()
1666 }
1667 Some("^") => {
1668 value
1670 .iter()
1671 .map(|s| {
1672 let mut chars = s.chars();
1673 match chars.next() {
1674 Some(c) => c.to_uppercase().chain(chars).collect(),
1675 None => String::new(),
1676 }
1677 })
1678 .collect()
1679 }
1680 Some("^^") => {
1681 value.iter().map(|s| s.to_uppercase()).collect()
1683 }
1684 Some(",") => {
1685 value
1687 .iter()
1688 .map(|s| {
1689 let mut chars = s.chars();
1690 match chars.next() {
1691 Some(c) => c.to_lowercase().chain(chars).collect(),
1692 None => String::new(),
1693 }
1694 })
1695 .collect()
1696 }
1697 Some(",,") => {
1698 value.iter().map(|s| s.to_lowercase()).collect()
1700 }
1701 _ => value,
1702 }
1703}
1704
1705fn remove_prefix(s: &str, pattern: &str, greedy: bool) -> String {
1707 if pattern == "*" {
1710 return String::new();
1711 }
1712
1713 if pattern.ends_with('*') {
1714 let prefix = &pattern[..pattern.len() - 1];
1715 if s.starts_with(prefix) {
1716 if greedy {
1717 for i in (prefix.len()..=s.len()).rev() {
1719 return s[i..].to_string();
1720 }
1721 } else {
1722 return s[prefix.len()..].to_string();
1723 }
1724 }
1725 } else if s.starts_with(pattern) {
1726 return s[pattern.len()..].to_string();
1727 }
1728
1729 s.to_string()
1730}
1731
1732fn remove_suffix(s: &str, pattern: &str, greedy: bool) -> String {
1734 if pattern == "*" {
1735 return String::new();
1736 }
1737
1738 if pattern.starts_with('*') {
1739 let suffix = &pattern[1..];
1740 if s.ends_with(suffix) {
1741 if greedy {
1742 for i in 0..=s.len().saturating_sub(suffix.len()) {
1743 return s[..i].to_string();
1744 }
1745 } else {
1746 return s[..s.len() - suffix.len()].to_string();
1747 }
1748 }
1749 } else if s.ends_with(pattern) {
1750 return s[..s.len() - pattern.len()].to_string();
1751 }
1752
1753 s.to_string()
1754}
1755
1756fn split_words(s: &str, state: &SubstState) -> Vec<String> {
1758 let ifs = state
1759 .variables
1760 .get("IFS")
1761 .map(|s| s.as_str())
1762 .unwrap_or(" \t\n");
1763
1764 s.split(|c: char| ifs.contains(c))
1765 .filter(|s| !s.is_empty())
1766 .map(String::from)
1767 .collect()
1768}
1769
1770fn find_matching_bracket(s: &str, open: char, close: char) -> Option<usize> {
1773 let mut depth = 1;
1774 for (i, c) in s.chars().enumerate() {
1775 if c == open {
1776 depth += 1;
1777 } else if c == close {
1778 depth -= 1;
1779 if depth == 0 {
1780 return Some(i);
1781 }
1782 }
1783 }
1784 None
1785}
1786
1787fn find_matching_parmath(s: &str) -> Option<usize> {
1788 let mut depth = 1;
1789 let chars: Vec<char> = s.chars().collect();
1790 let mut i = 0;
1791 while i < chars.len() {
1792 if chars[i] == INPARMATH {
1793 depth += 1;
1794 } else if chars[i] == OUTPARMATH {
1795 depth -= 1;
1796 if depth == 0 {
1797 return Some(i);
1798 }
1799 }
1800 i += 1;
1801 }
1802 None
1803}
1804
1805fn hasbraces(s: &str) -> bool {
1806 s.contains('{') && s.contains('}')
1807}
1808
1809fn xpandbraces(list: &mut LinkList, node_idx: &mut usize) {
1810 let data = match list.get_data(*node_idx) {
1811 Some(d) => d.to_string(),
1812 None => return,
1813 };
1814
1815 if let Some(start) = data.find('{') {
1817 if let Some(end) = data[start..].find('}') {
1818 let prefix = &data[..start];
1819 let content = &data[start + 1..start + end];
1820 let suffix = &data[start + end + 1..];
1821
1822 let alternatives: Vec<&str> = content.split(',').collect();
1824 if alternatives.len() > 1 {
1825 list.remove(*node_idx);
1827
1828 for (i, alt) in alternatives.iter().enumerate() {
1830 let expanded = format!("{}{}{}", prefix, alt, suffix);
1831 if i == 0 {
1832 list.nodes.insert(*node_idx, LinkNode { data: expanded });
1833 } else {
1834 list.insert_after(*node_idx + i - 1, expanded);
1835 }
1836 }
1837 }
1838 }
1839 }
1840}
1841
1842fn remnulargs(s: &str) -> String {
1843 s.chars().filter(|&c| c != NULARG).collect()
1844}
1845
1846fn filesub(s: &str, _flags: u32, _state: &mut SubstState) -> String {
1847 if s.starts_with('~') {
1849 let rest = &s[1..];
1850 let (user, suffix) = match rest.find('/') {
1851 Some(pos) => (&rest[..pos], &rest[pos..]),
1852 None => (rest, ""),
1853 };
1854
1855 if user.is_empty() {
1856 if let Ok(home) = std::env::var("HOME") {
1857 return format!("{}{}", home, suffix);
1858 }
1859 } else if user == "+" {
1860 if let Ok(pwd) = std::env::var("PWD") {
1861 return format!("{}{}", pwd, suffix);
1862 }
1863 } else if user == "-" {
1864 if let Ok(oldpwd) = std::env::var("OLDPWD") {
1865 return format!("{}{}", oldpwd, suffix);
1866 }
1867 }
1868 }
1869
1870 if s.starts_with('=') && s.len() > 1 {
1872 let cmd = &s[1..];
1873 if let Ok(path) = std::env::var("PATH") {
1874 for dir in path.split(':') {
1875 let full_path = format!("{}/{}", dir, cmd);
1876 if std::path::Path::new(&full_path).exists() {
1877 return full_path;
1878 }
1879 }
1880 }
1881 }
1882
1883 s.to_string()
1884}
1885
1886fn getproc(s: &str, state: &mut SubstState) -> (Option<String>, String) {
1887 let chars: Vec<char> = s.chars().collect();
1890 let is_input = chars[0] == INANG;
1891
1892 if let Some(end) = find_matching_bracket(&s[1..], INPAR, OUTPAR) {
1893 let cmd: String = s[2..end + 1].chars().collect();
1894 let rest = s[end + 2..].to_string();
1895
1896 if state.opts.exec_opt {
1897 let fd = if is_input { "63" } else { "62" };
1900 return (Some(format!("/dev/fd/{}", fd)), rest);
1901 }
1902
1903 return (None, rest);
1904 }
1905
1906 (None, s.to_string())
1907}
1908
1909fn getoutputfile(s: &str, state: &mut SubstState) -> (Option<String>, String) {
1910 if let Some(end) = find_matching_bracket(&s[1..], INPAR, OUTPAR) {
1912 let cmd: String = s[2..end + 1].chars().collect();
1913 let rest = s[end + 2..].to_string();
1914
1915 if state.opts.exec_opt {
1916 let output = run_command(&cmd);
1917 return (Some("/tmp/zsh_proc_subst".to_string()), rest);
1920 }
1921
1922 return (None, rest);
1923 }
1924
1925 (None, s.to_string())
1926}
1927
1928fn arithsubst(expr: &str, _state: &mut SubstState) -> String {
1929 if let Ok(n) = expr.parse::<i64>() {
1932 return n.to_string();
1933 }
1934
1935 if let Some(pos) = expr.find('+') {
1937 if let (Ok(a), Ok(b)) = (
1938 expr[..pos].trim().parse::<i64>(),
1939 expr[pos + 1..].trim().parse::<i64>(),
1940 ) {
1941 return (a + b).to_string();
1942 }
1943 }
1944
1945 "0".to_string()
1946}
1947
1948fn run_command(cmd: &str) -> String {
1949 use std::process::{Command, Stdio};
1950
1951 match Command::new("sh")
1952 .arg("-c")
1953 .arg(cmd)
1954 .stdin(Stdio::null())
1955 .stdout(Stdio::piped())
1956 .stderr(Stdio::inherit())
1957 .output()
1958 {
1959 Ok(output) => String::from_utf8_lossy(&output.stdout).to_string(),
1960 Err(_) => String::new(),
1961 }
1962}
1963
1964pub mod multsub_flags {
1966 pub const WS_AT_START: u32 = 1;
1967 pub const WS_AT_END: u32 = 2;
1968 pub const PARAM_NAME: u32 = 4;
1969}
1970
1971pub fn singsub(s: &str, state: &mut SubstState) -> String {
1974 let mut list = LinkList::from_string(s);
1975 let mut ret_flags = 0u32;
1976
1977 prefork(&mut list, prefork_flags::SINGLE, &mut ret_flags, state);
1978
1979 if state.errflag {
1980 return String::new();
1981 }
1982
1983 list.get_data(0).unwrap_or("").to_string()
1984}
1985
1986pub fn multsub(s: &str, pf_flags: u32, state: &mut SubstState) -> (String, Vec<String>, bool, u32) {
1989 let mut x = s.to_string();
1990 let mut ms_flags = 0u32;
1991
1992 if pf_flags & prefork_flags::SPLIT != 0 {
1994 let leading_ws: String = x.chars().take_while(|c| c.is_ascii_whitespace()).collect();
1995 if !leading_ws.is_empty() {
1996 ms_flags |= multsub_flags::WS_AT_START;
1997 x = x.chars().skip(leading_ws.len()).collect();
1998 }
1999 }
2000
2001 let mut list = LinkList::from_string(&x);
2002
2003 if pf_flags & prefork_flags::SPLIT != 0 {
2005 let mut node_idx = 0;
2006 let mut in_quote = false;
2007 let mut in_paren = 0;
2008
2009 while node_idx < list.len() {
2010 if let Some(data) = list.get_data(node_idx) {
2011 let chars: Vec<char> = data.chars().collect();
2012 let mut split_points = Vec::new();
2013 let mut i = 0;
2014
2015 while i < chars.len() {
2016 let c = chars[i];
2017
2018 match c {
2020 '"' | '\'' | TICK | QTICK => in_quote = !in_quote,
2021 INPAR => in_paren += 1,
2022 OUTPAR => in_paren = (in_paren - 1).max(0),
2023 _ => {}
2024 }
2025
2026 if !in_quote && in_paren == 0 {
2028 let ifs = state
2029 .variables
2030 .get("IFS")
2031 .map(|s| s.as_str())
2032 .unwrap_or(" \t\n");
2033 if ifs.contains(c) && !is_token(c) {
2034 split_points.push(i);
2035 }
2036 }
2037
2038 i += 1;
2039 }
2040
2041 if !split_points.is_empty() {
2043 let data_str = data.to_string();
2044 let chars: Vec<char> = data_str.chars().collect();
2045 let mut last = 0;
2046
2047 list.remove(node_idx);
2048
2049 for (idx, &point) in split_points.iter().enumerate() {
2050 if point > last {
2051 let segment: String = chars[last..point].iter().collect();
2052 if idx == 0 {
2053 list.nodes.insert(node_idx, LinkNode { data: segment });
2054 } else {
2055 list.insert_after(node_idx + idx - 1, segment);
2056 }
2057 }
2058 last = point + 1;
2059 }
2060
2061 if last < chars.len() {
2062 let segment: String = chars[last..].iter().collect();
2063 if split_points.is_empty() {
2064 list.nodes.insert(node_idx, LinkNode { data: segment });
2065 } else {
2066 list.insert_after(node_idx + split_points.len() - 1, segment);
2067 }
2068 }
2069 }
2070 }
2071 node_idx += 1;
2072 }
2073 }
2074
2075 let mut ret_flags = 0u32;
2076 prefork(&mut list, pf_flags, &mut ret_flags, state);
2077
2078 if state.errflag {
2079 return (String::new(), Vec::new(), false, ms_flags);
2080 }
2081
2082 if pf_flags & prefork_flags::SPLIT != 0 {
2084 if let Some(last) = list.nodes.back() {
2085 if last
2086 .data
2087 .chars()
2088 .last()
2089 .map(|c| c.is_ascii_whitespace())
2090 .unwrap_or(false)
2091 {
2092 ms_flags |= multsub_flags::WS_AT_END;
2093 }
2094 }
2095 }
2096
2097 let len = list.len();
2098 if len > 1 || (list.flags & LF_ARRAY != 0) {
2099 let arr: Vec<String> = list.nodes.iter().map(|n| n.data.clone()).collect();
2101 let joined = arr.join(" ");
2102 return (joined, arr, true, ms_flags);
2103 }
2104
2105 let result = list.get_data(0).unwrap_or("").to_string();
2106 (result.clone(), vec![result], false, ms_flags)
2107}
2108
2109#[derive(Debug, Clone, Copy, PartialEq)]
2111pub enum CaseMod {
2112 None,
2113 Lower,
2114 Upper,
2115 Caps,
2116}
2117
2118pub fn casemodify(s: &str, casmod: CaseMod) -> String {
2121 match casmod {
2122 CaseMod::None => s.to_string(),
2123 CaseMod::Lower => s.to_lowercase(),
2124 CaseMod::Upper => s.to_uppercase(),
2125 CaseMod::Caps => {
2126 let mut result = String::new();
2127 let mut capitalize_next = true;
2128 for c in s.chars() {
2129 if c.is_whitespace() {
2130 capitalize_next = true;
2131 result.push(c);
2132 } else if capitalize_next {
2133 result.extend(c.to_uppercase());
2134 capitalize_next = false;
2135 } else {
2136 result.extend(c.to_lowercase());
2137 }
2138 }
2139 result
2140 }
2141 }
2142}
2143
2144pub fn modify(s: &str, modifiers: &str, state: &mut SubstState) -> String {
2147 let mut result = s.to_string();
2148 let mut chars = modifiers.chars().peekable();
2149
2150 while chars.peek() == Some(&':') {
2151 chars.next(); let mut gbal = false;
2154 let mut wall = false;
2155 let mut sep = None;
2156
2157 loop {
2159 match chars.peek() {
2160 Some(&'g') => {
2161 gbal = true;
2162 chars.next();
2163 }
2164 Some(&'w') => {
2165 wall = true;
2166 chars.next();
2167 }
2168 Some(&'W') => {
2169 chars.next();
2170 if chars.peek() == Some(&':') {
2172 chars.next();
2173 let s: String = chars.by_ref().take_while(|&c| c != ':').collect();
2174 sep = Some(s);
2175 }
2176 }
2177 _ => break,
2178 }
2179 }
2180
2181 let modifier = match chars.next() {
2182 Some(c) => c,
2183 None => break,
2184 };
2185
2186 if wall {
2187 let separator = sep.as_deref().unwrap_or(" ");
2189 let words: Vec<&str> = result.split(separator).collect();
2190 let modified: Vec<String> = words
2191 .iter()
2192 .map(|w| apply_single_modifier(w, modifier, gbal, state))
2193 .collect();
2194 result = modified.join(separator);
2195 } else {
2196 result = apply_single_modifier(&result, modifier, gbal, state);
2197 }
2198 }
2199
2200 result
2201}
2202
2203fn apply_single_modifier(s: &str, modifier: char, gbal: bool, _state: &mut SubstState) -> String {
2205 match modifier {
2206 'a' => {
2208 if s.starts_with('/') {
2209 s.to_string()
2210 } else if let Ok(cwd) = std::env::current_dir() {
2211 format!("{}/{}", cwd.display(), s)
2212 } else {
2213 s.to_string()
2214 }
2215 }
2216 'A' => match std::fs::canonicalize(s) {
2218 Ok(p) => p.to_string_lossy().to_string(),
2219 Err(_) => s.to_string(),
2220 },
2221 'c' => {
2223 if let Ok(path) = std::env::var("PATH") {
2224 for dir in path.split(':') {
2225 let full = format!("{}/{}", dir, s);
2226 if std::path::Path::new(&full).exists() {
2227 return full;
2228 }
2229 }
2230 }
2231 s.to_string()
2232 }
2233 'h' => match s.rfind('/') {
2235 Some(0) => "/".to_string(),
2236 Some(pos) => s[..pos].to_string(),
2237 None => ".".to_string(),
2238 },
2239 't' => match s.rfind('/') {
2241 Some(pos) => s[pos + 1..].to_string(),
2242 None => s.to_string(),
2243 },
2244 'r' => match s.rfind('.') {
2246 Some(pos) if pos > 0 && !s[..pos].ends_with('/') => s[..pos].to_string(),
2247 _ => s.to_string(),
2248 },
2249 'e' => match s.rfind('.') {
2251 Some(pos) if pos > 0 && !s[..pos].ends_with('/') => s[pos + 1..].to_string(),
2252 _ => String::new(),
2253 },
2254 'l' => s.to_lowercase(),
2256 'u' => s.to_uppercase(),
2258 'q' => {
2260 format!("'{}'", s.replace('\'', "'\\''"))
2261 }
2262 'Q' => {
2264 let trimmed = s.trim();
2265 if (trimmed.starts_with('\'') && trimmed.ends_with('\''))
2266 || (trimmed.starts_with('"') && trimmed.ends_with('"'))
2267 {
2268 trimmed[1..trimmed.len() - 1].to_string()
2269 } else {
2270 s.to_string()
2271 }
2272 }
2273 'P' => {
2275 let path = if s.starts_with('/') {
2276 s.to_string()
2277 } else if let Ok(cwd) = std::env::current_dir() {
2278 format!("{}/{}", cwd.display(), s)
2279 } else {
2280 s.to_string()
2281 };
2282 match std::fs::canonicalize(&path) {
2284 Ok(p) => p.to_string_lossy().to_string(),
2285 Err(_) => path,
2286 }
2287 }
2288 _ => s.to_string(),
2289 }
2290}
2291
2292pub fn dstackent(ch: char, val: i32, dirstack: &[String], pwd: &str) -> Option<String> {
2295 let backwards = ch == '-'; if !backwards && val == 0 {
2298 return Some(pwd.to_string());
2299 }
2300
2301 let idx = if backwards {
2302 dirstack.len().checked_sub(val as usize)?
2303 } else {
2304 (val - 1) as usize
2305 };
2306
2307 dirstack.get(idx).cloned()
2308}
2309
2310pub fn subst(s: &str, old: &str, new: &str, global: bool) -> String {
2313 if global {
2314 s.replace(old, new)
2315 } else {
2316 s.replacen(old, new, 1)
2317 }
2318}
2319
2320#[derive(Debug, Clone, Copy, PartialEq)]
2322pub enum QuoteType {
2323 None,
2324 Backslash,
2325 BackslashPattern,
2326 Single,
2327 Double,
2328 Dollars,
2329 QuotedZputs,
2330 SingleOptional,
2331}
2332
2333pub fn quotestring(s: &str, qt: QuoteType) -> String {
2336 match qt {
2337 QuoteType::None => s.to_string(),
2338 QuoteType::Backslash | QuoteType::BackslashPattern => {
2339 let mut result = String::new();
2340 for c in s.chars() {
2341 match c {
2342 ' ' | '\t' | '\n' | '\\' | '\'' | '"' | '$' | '`' | '!' | '*' | '?' | '['
2343 | ']' | '(' | ')' | '{' | '}' | '<' | '>' | '|' | '&' | ';' | '#' | '~' => {
2344 result.push('\\');
2345 result.push(c);
2346 }
2347 _ => result.push(c),
2348 }
2349 }
2350 result
2351 }
2352 QuoteType::Single => {
2353 format!("'{}'", s.replace('\'', "'\\''"))
2354 }
2355 QuoteType::Double => {
2356 let mut result = String::from("\"");
2357 for c in s.chars() {
2358 match c {
2359 '"' | '\\' | '$' | '`' => {
2360 result.push('\\');
2361 result.push(c);
2362 }
2363 _ => result.push(c),
2364 }
2365 }
2366 result.push('"');
2367 result
2368 }
2369 QuoteType::Dollars => {
2370 let mut result = String::from("$'");
2371 for c in s.chars() {
2372 match c {
2373 '\'' => result.push_str("\\'"),
2374 '\\' => result.push_str("\\\\"),
2375 '\n' => result.push_str("\\n"),
2376 '\t' => result.push_str("\\t"),
2377 '\r' => result.push_str("\\r"),
2378 c if c.is_ascii_control() => {
2379 result.push_str(&format!("\\x{:02x}", c as u32));
2380 }
2381 _ => result.push(c),
2382 }
2383 }
2384 result.push('\'');
2385 result
2386 }
2387 QuoteType::QuotedZputs | QuoteType::SingleOptional => {
2388 let needs_quote = s.chars().any(|c| {
2390 matches!(
2391 c,
2392 ' ' | '\t'
2393 | '\n'
2394 | '\\'
2395 | '\''
2396 | '"'
2397 | '$'
2398 | '`'
2399 | '!'
2400 | '*'
2401 | '?'
2402 | '['
2403 | ']'
2404 | '('
2405 | ')'
2406 | '{'
2407 | '}'
2408 | '<'
2409 | '>'
2410 | '|'
2411 | '&'
2412 | ';'
2413 | '#'
2414 | '~'
2415 )
2416 });
2417 if needs_quote {
2418 format!("'{}'", s.replace('\'', "'\\''"))
2419 } else {
2420 s.to_string()
2421 }
2422 }
2423 }
2424}
2425
2426#[derive(Debug, Clone, Copy, Default)]
2428pub struct SortOptions {
2429 pub somehow: bool,
2430 pub backwards: bool,
2431 pub case_insensitive: bool,
2432 pub numeric: bool,
2433 pub numeric_signed: bool,
2434}
2435
2436pub fn sort_array(arr: &mut Vec<String>, opts: &SortOptions) {
2439 if !opts.somehow {
2440 return;
2441 }
2442
2443 if opts.numeric || opts.numeric_signed {
2444 arr.sort_by(|a, b| {
2445 let na: f64 = a.parse().unwrap_or(0.0);
2446 let nb: f64 = b.parse().unwrap_or(0.0);
2447 na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal)
2448 });
2449 } else if opts.case_insensitive {
2450 arr.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
2451 } else {
2452 arr.sort();
2453 }
2454
2455 if opts.backwards {
2456 arr.reverse();
2457 }
2458}
2459
2460pub fn wordcount(s: &str, sep: Option<&str>, count_empty: bool) -> usize {
2463 let separator = sep.unwrap_or(" \t\n");
2464
2465 if count_empty {
2466 s.split(|c: char| separator.contains(c)).count()
2467 } else {
2468 s.split(|c: char| separator.contains(c))
2469 .filter(|w| !w.is_empty())
2470 .count()
2471 }
2472}
2473
2474pub fn sepjoin(arr: &[String], sep: Option<&str>, use_ifs_first: bool) -> String {
2477 let separator = sep.unwrap_or_else(|| if use_ifs_first { " " } else { "" });
2478 arr.join(separator)
2479}
2480
2481pub fn sepsplit(s: &str, sep: Option<&str>, allow_empty: bool, _handle_ifs: bool) -> Vec<String> {
2484 let separator = sep.unwrap_or(" \t\n");
2485
2486 if allow_empty {
2487 s.split(|c: char| separator.contains(c))
2488 .map(String::from)
2489 .collect()
2490 } else {
2491 s.split(|c: char| separator.contains(c))
2492 .filter(|w| !w.is_empty())
2493 .map(String::from)
2494 .collect()
2495 }
2496}
2497
2498pub fn unique_array(arr: &mut Vec<String>) {
2501 let mut seen = std::collections::HashSet::new();
2502 arr.retain(|s| seen.insert(s.clone()));
2503}
2504
2505pub fn dopadding(
2508 s: &str,
2509 prenum: usize,
2510 postnum: usize,
2511 preone: Option<&str>,
2512 postone: Option<&str>,
2513 premul: &str,
2514 postmul: &str,
2515) -> String {
2516 let len = s.chars().count();
2517 let total_width = prenum + postnum;
2518
2519 if total_width == 0 || total_width == len {
2520 return s.to_string();
2521 }
2522
2523 let mut result = String::new();
2524
2525 if prenum > 0 {
2527 let chars: Vec<char> = s.chars().collect();
2528
2529 if len > prenum {
2530 let skip = len - prenum;
2532 result = chars.into_iter().skip(skip).collect();
2533 } else {
2534 let padding_needed = prenum - len;
2536
2537 if let Some(pre) = preone {
2539 let pre_len = pre.chars().count();
2540 if pre_len <= padding_needed {
2541 let repeat_len = padding_needed - pre_len;
2543 if !premul.is_empty() {
2544 let mul_len = premul.chars().count();
2545 let full_repeats = repeat_len / mul_len;
2546 let partial = repeat_len % mul_len;
2547
2548 if partial > 0 {
2550 result.extend(premul.chars().skip(mul_len - partial));
2551 }
2552 for _ in 0..full_repeats {
2554 result.push_str(premul);
2555 }
2556 }
2557 result.push_str(pre);
2558 } else {
2559 result.extend(pre.chars().skip(pre_len - padding_needed));
2561 }
2562 } else {
2563 if !premul.is_empty() {
2565 let mul_len = premul.chars().count();
2566 let full_repeats = padding_needed / mul_len;
2567 let partial = padding_needed % mul_len;
2568
2569 if partial > 0 {
2570 result.extend(premul.chars().skip(mul_len - partial));
2571 }
2572 for _ in 0..full_repeats {
2573 result.push_str(premul);
2574 }
2575 }
2576 }
2577
2578 result.push_str(s);
2579 }
2580 } else {
2581 result = s.to_string();
2582 }
2583
2584 if postnum > 0 {
2586 let current_len = result.chars().count();
2587
2588 if current_len > postnum {
2589 result = result.chars().take(postnum).collect();
2591 } else if current_len < postnum {
2592 let padding_needed = postnum - current_len;
2594
2595 if let Some(post) = postone {
2596 let post_len = post.chars().count();
2597 if post_len <= padding_needed {
2598 result.push_str(post);
2599 let remaining = padding_needed - post_len;
2600 if !postmul.is_empty() {
2601 let mul_len = postmul.chars().count();
2602 let full_repeats = remaining / mul_len;
2603 let partial = remaining % mul_len;
2604
2605 for _ in 0..full_repeats {
2606 result.push_str(postmul);
2607 }
2608 if partial > 0 {
2609 result.extend(postmul.chars().take(partial));
2610 }
2611 }
2612 } else {
2613 result.extend(post.chars().take(padding_needed));
2614 }
2615 } else if !postmul.is_empty() {
2616 let mul_len = postmul.chars().count();
2617 let full_repeats = padding_needed / mul_len;
2618 let partial = padding_needed % mul_len;
2619
2620 for _ in 0..full_repeats {
2621 result.push_str(postmul);
2622 }
2623 if partial > 0 {
2624 result.extend(postmul.chars().take(partial));
2625 }
2626 }
2627 }
2628 }
2629
2630 result
2631}
2632
2633pub fn get_strarg(s: &str) -> Option<(char, String, &str)> {
2636 let mut chars = s.chars().peekable();
2637
2638 let del = chars.next()?;
2640
2641 let close_del = match del {
2643 '(' => ')',
2644 '[' => ']',
2645 '{' => '}',
2646 '<' => '>',
2647 INPAR => OUTPAR,
2648 INBRACK => OUTBRACK,
2649 INBRACE => OUTBRACE,
2650 INANG => OUTANG,
2651 _ => del,
2652 };
2653
2654 let mut content = String::new();
2656 let mut rest_start = 1;
2657
2658 for (i, c) in s.chars().enumerate().skip(1) {
2659 if c == close_del {
2660 rest_start = i + 1;
2661 break;
2662 }
2663 content.push(c);
2664 rest_start = i + 1;
2665 }
2666
2667 let rest = &s[rest_start.min(s.len())..];
2668 Some((del, content, rest))
2669}
2670
2671pub fn get_intarg(s: &str) -> Option<(i64, &str)> {
2674 if let Some((_, content, rest)) = get_strarg(s) {
2675 let val: i64 = content.trim().parse().ok()?;
2677 Some((val.abs(), rest))
2678 } else {
2679 None
2680 }
2681}
2682
2683pub fn substnamedir(s: &str) -> String {
2686 if let Ok(home) = std::env::var("HOME") {
2688 if s.starts_with(&home) {
2689 return format!("~{}", &s[home.len()..]);
2690 }
2691 }
2692 s.to_string()
2693}
2694
2695pub fn nicedupstring(s: &str) -> String {
2698 let mut result = String::new();
2699 for c in s.chars() {
2700 if c.is_ascii_control() {
2701 match c {
2702 '\n' => result.push_str("\\n"),
2703 '\t' => result.push_str("\\t"),
2704 '\r' => result.push_str("\\r"),
2705 _ => result.push_str(&format!("\\x{:02x}", c as u32)),
2706 }
2707 } else {
2708 result.push(c);
2709 }
2710 }
2711 result
2712}
2713
2714pub fn untokenize(s: &str) -> String {
2716 s.chars().map(|c| token_to_char(c)).collect()
2717}
2718
2719pub fn shtokenize(s: &str) -> String {
2721 let mut result = String::new();
2723 for c in s.chars() {
2724 match c {
2725 '*' => result.push('\u{91}'), '?' => result.push('\u{92}'), '[' => result.push(INBRACK),
2728 ']' => result.push(OUTBRACK),
2729 _ => result.push(c),
2730 }
2731 }
2732 result
2733}
2734
2735pub fn check_subst_complete(s: &str) -> bool {
2737 let mut depth = 0;
2738 let mut in_brace = 0;
2739
2740 for c in s.chars() {
2741 match c {
2742 INPAR => depth += 1,
2743 OUTPAR => depth -= 1,
2744 INBRACE | '{' => in_brace += 1,
2745 OUTBRACE | '}' => in_brace -= 1,
2746 _ => {}
2747 }
2748 }
2749
2750 depth == 0 && in_brace == 0
2751}
2752
2753pub fn quotesubst(s: &str, state: &mut SubstState) -> String {
2756 let mut result = s.to_string();
2757 let mut pos = 0;
2758
2759 while pos < result.len() {
2760 let chars: Vec<char> = result.chars().collect();
2761 if pos + 1 < chars.len() && chars[pos] == STRING && chars[pos + 1] == SNULL {
2762 let (new_str, new_pos) = stringsubstquote(&result, pos);
2764 result = new_str;
2765 pos = new_pos;
2766 } else {
2767 pos += 1;
2768 }
2769 }
2770
2771 remnulargs(&result)
2772}
2773
2774pub fn globlist(list: &mut LinkList, flags: u32, state: &mut SubstState) {
2777 let mut node_idx = 0;
2778
2779 while node_idx < list.len() && !state.errflag {
2780 if let Some(data) = list.get_data(node_idx) {
2781 if flags & prefork_flags::KEY_VALUE != 0 && data.starts_with(MARKER) {
2783 node_idx += 3;
2785 continue;
2786 }
2787
2788 let expanded = zglob(&data, flags & prefork_flags::NO_UNTOK != 0, state);
2790
2791 if expanded.is_empty() {
2792 if state.opts.glob_subst {
2794 }
2797 } else if expanded.len() == 1 {
2798 list.set_data(node_idx, expanded[0].clone());
2799 } else {
2800 list.remove(node_idx);
2802 for (i, path) in expanded.iter().enumerate() {
2803 if i == 0 {
2804 list.nodes.insert(node_idx, LinkNode { data: path.clone() });
2805 } else {
2806 list.insert_after(node_idx + i - 1, path.clone());
2807 }
2808 }
2809 node_idx += expanded.len();
2810 continue;
2811 }
2812 }
2813 node_idx += 1;
2814 }
2815}
2816
2817fn zglob(pattern: &str, no_untok: bool, state: &SubstState) -> Vec<String> {
2820 let pattern = if no_untok {
2821 pattern.to_string()
2822 } else {
2823 untokenize(pattern)
2824 };
2825
2826 if !pattern.contains('*') && !pattern.contains('?') && !pattern.contains('[') {
2828 if std::path::Path::new(&pattern).exists() {
2830 return vec![pattern];
2831 }
2832 return vec![pattern];
2833 }
2834
2835 match glob::glob(&pattern) {
2837 Ok(paths) => {
2838 let matches: Vec<String> = paths
2839 .filter_map(|p| p.ok())
2840 .map(|p| p.to_string_lossy().to_string())
2841 .collect();
2842 if matches.is_empty() {
2843 vec![pattern]
2844 } else {
2845 matches
2846 }
2847 }
2848 Err(_) => vec![pattern],
2849 }
2850}
2851
2852pub fn skipparens(s: &str, open: char, close: char) -> Option<usize> {
2855 let mut depth = 1;
2856 let chars: Vec<char> = s.chars().collect();
2857
2858 for (i, &c) in chars.iter().enumerate() {
2859 if c == open {
2860 depth += 1;
2861 } else if c == close {
2862 depth -= 1;
2863 if depth == 0 {
2864 return Some(i);
2865 }
2866 }
2867 }
2868 None
2869}
2870
2871pub fn getoutput(cmd: &str, qt: bool, state: &mut SubstState) -> Option<Vec<String>> {
2874 if !state.opts.exec_opt {
2875 return Some(vec![]);
2876 }
2877
2878 let output = run_command(cmd);
2879
2880 let output = output.trim_end_matches('\n');
2882
2883 if qt {
2884 Some(vec![output.to_string()])
2886 } else {
2887 Some(output.lines().map(String::from).collect())
2889 }
2890}
2891
2892pub fn parse_subscript(s: &str, _allow_range: bool) -> Option<(String, String)> {
2895 let chars: Vec<char> = s.chars().collect();
2896
2897 if chars.first() != Some(&'[') && chars.first() != Some(&INBRACK) {
2898 return None;
2899 }
2900
2901 let mut depth = 1;
2902 let mut end = 1;
2903
2904 while end < chars.len() && depth > 0 {
2905 let c = chars[end];
2906 if c == '[' || c == INBRACK {
2907 depth += 1;
2908 } else if c == ']' || c == OUTBRACK {
2909 depth -= 1;
2910 }
2911 if depth > 0 {
2912 end += 1;
2913 }
2914 }
2915
2916 if depth != 0 {
2917 return None;
2918 }
2919
2920 let subscript: String = chars[1..end].iter().collect();
2921 let rest_start = end + 1;
2922 let rest = if rest_start < s.len() {
2923 s[rest_start..].to_string()
2924 } else {
2925 String::new()
2926 };
2927
2928 Some((subscript, rest))
2929}
2930
2931pub fn eval_subscript(subscript: &str, array_len: usize) -> (usize, Option<usize>) {
2933 if let Some(comma_pos) = subscript.find(',') {
2935 let start_str = subscript[..comma_pos].trim();
2936 let end_str = subscript[comma_pos + 1..].trim();
2937
2938 let start = parse_index(start_str, array_len);
2939 let end = parse_index(end_str, array_len);
2940
2941 (start, Some(end))
2942 } else {
2943 let idx = parse_index(subscript.trim(), array_len);
2945 (idx, None)
2946 }
2947}
2948
2949fn parse_index(s: &str, array_len: usize) -> usize {
2951 if let Ok(idx) = s.parse::<i64>() {
2952 if idx < 0 {
2953 let abs_idx = (-idx) as usize;
2955 array_len.saturating_sub(abs_idx)
2956 } else if idx == 0 {
2957 0
2958 } else {
2959 (idx as usize).saturating_sub(1)
2961 }
2962 } else {
2963 0
2964 }
2965}
2966
2967pub fn itok(c: char) -> bool {
2969 let code = c as u32;
2970 code >= 0x80 && code <= 0x9F
2971}
2972
2973pub fn ztokens(c: char) -> char {
2976 match c {
2977 POUND => '#',
2978 STRING => '$',
2979 QSTRING => '$',
2980 TICK => '`',
2981 QTICK => '`',
2982 INPAR => '(',
2983 OUTPAR => ')',
2984 INBRACE => '{',
2985 OUTBRACE => '}',
2986 INBRACK => '[',
2987 OUTBRACK => ']',
2988 INANG => '<',
2989 OUTANG => '>',
2990 EQUALS => '=',
2991 _ => c,
2992 }
2993}
2994
2995pub mod sub_flags {
2997 pub const END: u32 = 1; pub const LONG: u32 = 2; pub const SUBSTR: u32 = 4; pub const MATCH: u32 = 8; pub const REST: u32 = 16; pub const BIND: u32 = 32; pub const EIND: u32 = 64; pub const LEN: u32 = 128; pub const ALL: u32 = 256; pub const GLOBAL: u32 = 512; pub const START: u32 = 1024; pub const EGLOB: u32 = 2048; }
3010
3011pub fn getmatch(val: &str, pattern: &str, flags: u32, flnum: i32, replstr: Option<&str>) -> String {
3014 let val_chars: Vec<char> = val.chars().collect();
3015 let val_len = val_chars.len();
3016
3017 let regex_pattern = glob_to_regex(pattern);
3019
3020 match regex::Regex::new(®ex_pattern) {
3021 Ok(re) => {
3022 if flags & sub_flags::GLOBAL != 0 {
3023 let replacement = replstr.unwrap_or("");
3025 re.replace_all(val, replacement).to_string()
3026 } else if flags & sub_flags::END != 0 {
3027 if flags & sub_flags::LONG != 0 {
3029 for i in 0..=val_len {
3031 let suffix: String = val_chars[i..].iter().collect();
3032 if re.is_match(&suffix) {
3033 let prefix: String = val_chars[..i].iter().collect();
3034 return if let Some(repl) = replstr {
3035 format!("{}{}", prefix, repl)
3036 } else {
3037 prefix
3038 };
3039 }
3040 }
3041 } else {
3042 for i in (0..=val_len).rev() {
3044 let suffix: String = val_chars[i..].iter().collect();
3045 if re.is_match(&suffix) {
3046 let prefix: String = val_chars[..i].iter().collect();
3047 return if let Some(repl) = replstr {
3048 format!("{}{}", prefix, repl)
3049 } else {
3050 prefix
3051 };
3052 }
3053 }
3054 }
3055 val.to_string()
3056 } else {
3057 if flags & sub_flags::LONG != 0 {
3059 for i in (0..=val_len).rev() {
3061 let prefix: String = val_chars[..i].iter().collect();
3062 if re.is_match(&prefix) {
3063 let suffix: String = val_chars[i..].iter().collect();
3064 return if let Some(repl) = replstr {
3065 format!("{}{}", repl, suffix)
3066 } else {
3067 suffix
3068 };
3069 }
3070 }
3071 } else {
3072 for i in 0..=val_len {
3074 let prefix: String = val_chars[..i].iter().collect();
3075 if re.is_match(&prefix) {
3076 let suffix: String = val_chars[i..].iter().collect();
3077 return if let Some(repl) = replstr {
3078 format!("{}{}", repl, suffix)
3079 } else {
3080 suffix
3081 };
3082 }
3083 }
3084 }
3085 val.to_string()
3086 }
3087 }
3088 Err(_) => {
3089 if let Some(repl) = replstr {
3091 val.replace(pattern, repl)
3092 } else {
3093 val.to_string()
3094 }
3095 }
3096 }
3097}
3098
3099fn glob_to_regex(pattern: &str) -> String {
3101 let mut regex = String::from("^");
3102 let chars: Vec<char> = pattern.chars().collect();
3103 let mut i = 0;
3104
3105 while i < chars.len() {
3106 match chars[i] {
3107 '*' => {
3108 if i + 1 < chars.len() && chars[i + 1] == '*' {
3109 regex.push_str(".*");
3111 i += 1;
3112 } else {
3113 regex.push_str("[^/]*");
3115 }
3116 }
3117 '?' => regex.push('.'),
3118 '[' => {
3119 regex.push('[');
3120 i += 1;
3121 if i < chars.len() && (chars[i] == '!' || chars[i] == '^') {
3123 regex.push('^');
3124 i += 1;
3125 }
3126 while i < chars.len() && chars[i] != ']' {
3128 if chars[i] == '\\' && i + 1 < chars.len() {
3129 regex.push('\\');
3130 i += 1;
3131 regex.push(chars[i]);
3132 } else {
3133 regex.push(chars[i]);
3134 }
3135 i += 1;
3136 }
3137 regex.push(']');
3138 }
3139 '.' | '+' | '^' | '$' | '(' | ')' | '{' | '}' | '|' | '\\' => {
3140 regex.push('\\');
3141 regex.push(chars[i]);
3142 }
3143 c if itok(c) => {
3144 regex.push(ztokens(c));
3146 }
3147 c => regex.push(c),
3148 }
3149 i += 1;
3150 }
3151
3152 regex.push('$');
3153 regex
3154}
3155
3156pub fn getmatcharr(
3159 aval: &mut Vec<String>,
3160 pattern: &str,
3161 flags: u32,
3162 flnum: i32,
3163 replstr: Option<&str>,
3164) {
3165 for val in aval.iter_mut() {
3166 *val = getmatch(val, pattern, flags, flnum, replstr);
3167 }
3168}
3169
3170pub fn array_union(arr1: &[String], arr2: &[String]) -> Vec<String> {
3173 let set2: std::collections::HashSet<_> = arr2.iter().collect();
3174 arr1.iter().filter(|s| !set2.contains(s)).cloned().collect()
3175}
3176
3177pub fn array_intersection(arr1: &[String], arr2: &[String]) -> Vec<String> {
3180 let set2: std::collections::HashSet<_> = arr2.iter().collect();
3181 arr1.iter().filter(|s| set2.contains(s)).cloned().collect()
3182}
3183
3184pub fn array_zip(arr1: &[String], arr2: &[String], shortest: bool) -> Vec<String> {
3187 let len = if shortest {
3188 arr1.len().min(arr2.len())
3189 } else {
3190 arr1.len().max(arr2.len())
3191 };
3192
3193 let mut result = Vec::with_capacity(len * 2);
3194 for i in 0..len {
3195 let idx1 = if arr1.is_empty() { 0 } else { i % arr1.len() };
3196 let idx2 = if arr2.is_empty() { 0 } else { i % arr2.len() };
3197 result.push(arr1.get(idx1).cloned().unwrap_or_default());
3198 result.push(arr2.get(idx2).cloned().unwrap_or_default());
3199 }
3200 result
3201}
3202
3203pub fn strcatsub(prefix: &str, src: &str, suffix: &str, glob_subst: bool) -> String {
3206 let mut result = String::with_capacity(prefix.len() + src.len() + suffix.len());
3207 result.push_str(prefix);
3208
3209 if glob_subst {
3210 result.push_str(&shtokenize(src));
3211 } else {
3212 result.push_str(src);
3213 }
3214
3215 result.push_str(suffix);
3216 result
3217}
3218
3219pub fn inull(c: char) -> bool {
3221 matches!(c, '\u{8F}' | '\u{94}' | '\u{95}' | '\u{92}')
3222}
3223
3224pub fn chuck(s: &str, pos: usize) -> String {
3226 let mut result = String::new();
3227 for (i, c) in s.chars().enumerate() {
3228 if i != pos {
3229 result.push(c);
3230 }
3231 }
3232 result
3233}
3234
3235pub fn getsparam(name: &str, state: &SubstState) -> Option<String> {
3242 if let Some(val) = state.variables.get(name) {
3244 return Some(val.clone());
3245 }
3246
3247 std::env::var(name).ok()
3249}
3250
3251pub fn getaparam(name: &str, state: &SubstState) -> Option<Vec<String>> {
3254 state.arrays.get(name).cloned()
3255}
3256
3257pub fn gethparam(
3260 name: &str,
3261 state: &SubstState,
3262) -> Option<std::collections::HashMap<String, String>> {
3263 state.assoc_arrays.get(name).cloned()
3264}
3265
3266pub fn setsparam(name: &str, value: &str, state: &mut SubstState) {
3269 state.variables.insert(name.to_string(), value.to_string());
3270 }
3273
3274pub fn setaparam(name: &str, value: Vec<String>, state: &mut SubstState) {
3277 state.arrays.insert(name.to_string(), value);
3278}
3279
3280pub fn sethparam(
3283 name: &str,
3284 value: std::collections::HashMap<String, String>,
3285 state: &mut SubstState,
3286) {
3287 state.assoc_arrays.insert(name.to_string(), value);
3288}
3289
3290pub fn hmkarray(val: &str) -> Vec<String> {
3293 if val.is_empty() {
3294 Vec::new()
3295 } else {
3296 vec![val.to_string()]
3297 }
3298}
3299
3300pub fn dupstrpfx(s: &str, len: usize) -> String {
3303 s.chars().take(len).collect()
3304}
3305
3306pub fn dyncat(s1: &str, s2: &str) -> String {
3309 format!("{}{}", s1, s2)
3310}
3311
3312pub fn zhtricat(s1: &str, s2: &str, s3: &str) -> String {
3315 format!("{}{}{}", s1, s2, s3)
3316}
3317
3318pub fn findword(s: &str, sep: Option<&str>) -> Option<(String, String)> {
3321 let separator = sep.unwrap_or(" \t\n");
3322
3323 let trimmed = s.trim_start_matches(|c: char| separator.contains(c));
3325 if trimmed.is_empty() {
3326 return None;
3327 }
3328
3329 let word_end = trimmed
3331 .find(|c: char| separator.contains(c))
3332 .unwrap_or(trimmed.len());
3333
3334 let word = &trimmed[..word_end];
3335 let rest = &trimmed[word_end..];
3336
3337 Some((word.to_string(), rest.to_string()))
3338}
3339
3340pub fn is_absolute_path(s: &str) -> bool {
3342 s.starts_with('/')
3343}
3344
3345pub fn remtpath(s: &str, count: usize) -> String {
3348 let mut result = s.to_string();
3349 for _ in 0..count.max(1) {
3350 if let Some(pos) = result.rfind('/') {
3351 if pos == 0 {
3352 result = "/".to_string();
3353 break;
3354 } else {
3355 result = result[..pos].to_string();
3356 }
3357 } else {
3358 result = ".".to_string();
3359 break;
3360 }
3361 }
3362 result
3363}
3364
3365pub fn remlpaths(s: &str, count: usize) -> String {
3368 let parts: Vec<&str> = s.split('/').collect();
3369 if parts.len() <= count {
3370 parts.last().unwrap_or(&"").to_string()
3371 } else {
3372 parts[parts.len() - count..].join("/")
3373 }
3374}
3375
3376pub fn remtext(s: &str) -> String {
3379 if let Some(pos) = s.rfind('.') {
3380 if let Some(slash_pos) = s.rfind('/') {
3382 if pos > slash_pos {
3383 return s[..pos].to_string();
3384 }
3385 } else {
3386 return s[..pos].to_string();
3387 }
3388 }
3389 s.to_string()
3390}
3391
3392pub fn rembutext(s: &str) -> String {
3395 if let Some(pos) = s.rfind('.') {
3396 if let Some(slash_pos) = s.rfind('/') {
3398 if pos > slash_pos {
3399 return s[pos + 1..].to_string();
3400 }
3401 } else {
3402 return s[pos + 1..].to_string();
3403 }
3404 }
3405 String::new()
3406}
3407
3408pub fn chabspath(s: &str) -> String {
3411 if s.starts_with('/') {
3412 s.to_string()
3413 } else if let Ok(cwd) = std::env::current_dir() {
3414 format!("{}/{}", cwd.display(), s)
3415 } else {
3416 s.to_string()
3417 }
3418}
3419
3420pub fn chrealpath(s: &str) -> String {
3423 match std::fs::canonicalize(s) {
3424 Ok(p) => p.to_string_lossy().to_string(),
3425 Err(_) => s.to_string(),
3426 }
3427}
3428
3429pub fn xsymlink(path: &str, resolve: bool) -> String {
3432 if resolve {
3433 match std::fs::canonicalize(path) {
3434 Ok(p) => p.to_string_lossy().to_string(),
3435 Err(_) => path.to_string(),
3436 }
3437 } else {
3438 path.to_string()
3439 }
3440}
3441
3442pub fn convbase(val: i64, base: u32, underscore: bool) -> String {
3445 if base == 10 {
3446 if underscore {
3447 let s = val.abs().to_string();
3449 let mut result = String::new();
3450 for (i, c) in s.chars().rev().enumerate() {
3451 if i > 0 && i % 3 == 0 {
3452 result.insert(0, '_');
3453 }
3454 result.insert(0, c);
3455 }
3456 if val < 0 {
3457 result.insert(0, '-');
3458 }
3459 result
3460 } else {
3461 val.to_string()
3462 }
3463 } else if base == 16 {
3464 format!("{:x}", val)
3465 } else if base == 8 {
3466 format!("{:o}", val)
3467 } else if base == 2 {
3468 format!("{:b}", val)
3469 } else {
3470 val.to_string()
3471 }
3472}
3473
3474pub fn matheval(expr: &str) -> MathResult {
3477 if let Ok(n) = expr.trim().parse::<i64>() {
3479 return MathResult::Integer(n);
3480 }
3481
3482 if let Ok(n) = expr.trim().parse::<f64>() {
3484 return MathResult::Float(n);
3485 }
3486
3487 let expr = expr.trim();
3489
3490 if let Some(pos) = expr.rfind('+') {
3492 if pos > 0 {
3493 let left = matheval(&expr[..pos]);
3494 let right = matheval(&expr[pos + 1..]);
3495 return match (left, right) {
3496 (MathResult::Integer(a), MathResult::Integer(b)) => MathResult::Integer(a + b),
3497 (MathResult::Float(a), MathResult::Float(b)) => MathResult::Float(a + b),
3498 (MathResult::Integer(a), MathResult::Float(b)) => MathResult::Float(a as f64 + b),
3499 (MathResult::Float(a), MathResult::Integer(b)) => MathResult::Float(a + b as f64),
3500 };
3501 }
3502 }
3503
3504 if let Some(pos) = expr.rfind('-') {
3506 if pos > 0 {
3507 let left = matheval(&expr[..pos]);
3508 let right = matheval(&expr[pos + 1..]);
3509 return match (left, right) {
3510 (MathResult::Integer(a), MathResult::Integer(b)) => MathResult::Integer(a - b),
3511 (MathResult::Float(a), MathResult::Float(b)) => MathResult::Float(a - b),
3512 (MathResult::Integer(a), MathResult::Float(b)) => MathResult::Float(a as f64 - b),
3513 (MathResult::Float(a), MathResult::Integer(b)) => MathResult::Float(a - b as f64),
3514 };
3515 }
3516 }
3517
3518 if let Some(pos) = expr.rfind('*') {
3520 let left = matheval(&expr[..pos]);
3521 let right = matheval(&expr[pos + 1..]);
3522 return match (left, right) {
3523 (MathResult::Integer(a), MathResult::Integer(b)) => MathResult::Integer(a * b),
3524 (MathResult::Float(a), MathResult::Float(b)) => MathResult::Float(a * b),
3525 (MathResult::Integer(a), MathResult::Float(b)) => MathResult::Float(a as f64 * b),
3526 (MathResult::Float(a), MathResult::Integer(b)) => MathResult::Float(a * b as f64),
3527 };
3528 }
3529
3530 if let Some(pos) = expr.rfind('/') {
3532 let left = matheval(&expr[..pos]);
3533 let right = matheval(&expr[pos + 1..]);
3534 return match (left, right) {
3535 (MathResult::Integer(a), MathResult::Integer(b)) if b != 0 => {
3536 MathResult::Integer(a / b)
3537 }
3538 (MathResult::Float(a), MathResult::Float(b)) => MathResult::Float(a / b),
3539 (MathResult::Integer(a), MathResult::Float(b)) => MathResult::Float(a as f64 / b),
3540 (MathResult::Float(a), MathResult::Integer(b)) => MathResult::Float(a / b as f64),
3541 _ => MathResult::Integer(0),
3542 };
3543 }
3544
3545 if let Some(pos) = expr.rfind('%') {
3547 let left = matheval(&expr[..pos]);
3548 let right = matheval(&expr[pos + 1..]);
3549 return match (left, right) {
3550 (MathResult::Integer(a), MathResult::Integer(b)) if b != 0 => {
3551 MathResult::Integer(a % b)
3552 }
3553 _ => MathResult::Integer(0),
3554 };
3555 }
3556
3557 MathResult::Integer(0)
3558}
3559
3560#[derive(Debug, Clone, Copy)]
3562pub enum MathResult {
3563 Integer(i64),
3564 Float(f64),
3565}
3566
3567impl MathResult {
3568 pub fn to_string(&self) -> String {
3569 match self {
3570 MathResult::Integer(n) => n.to_string(),
3571 MathResult::Float(n) => n.to_string(),
3572 }
3573 }
3574
3575 pub fn to_i64(&self) -> i64 {
3576 match self {
3577 MathResult::Integer(n) => *n,
3578 MathResult::Float(n) => *n as i64,
3579 }
3580 }
3581}
3582
3583pub fn mathevali(expr: &str) -> i64 {
3586 matheval(expr).to_i64()
3587}
3588
3589pub fn parse_subst_string(s: &str) -> Result<String, String> {
3592 Ok(s.to_string())
3595}
3596
3597pub fn bufferwords(s: &str, flags: u32) -> Vec<String> {
3600 let mut words = Vec::new();
3602 let mut current = String::new();
3603 let mut in_quote = false;
3604 let mut quote_char = '\0';
3605 let mut escape_next = false;
3606
3607 for c in s.chars() {
3608 if escape_next {
3609 current.push(c);
3610 escape_next = false;
3611 continue;
3612 }
3613
3614 match c {
3615 '\\' => {
3616 escape_next = true;
3617 current.push(c);
3618 }
3619 '"' | '\'' => {
3620 if in_quote && c == quote_char {
3621 in_quote = false;
3622 quote_char = '\0';
3623 } else if !in_quote {
3624 in_quote = true;
3625 quote_char = c;
3626 }
3627 current.push(c);
3628 }
3629 ' ' | '\t' | '\n' if !in_quote => {
3630 if !current.is_empty() {
3631 words.push(current.clone());
3632 current.clear();
3633 }
3634 }
3635 _ => current.push(c),
3636 }
3637 }
3638
3639 if !current.is_empty() {
3640 words.push(current);
3641 }
3642
3643 words
3644}
3645
3646pub mod scanpm_flags {
3649 pub const WANTKEYS: u32 = 1;
3650 pub const WANTVALS: u32 = 2;
3651 pub const MATCHKEY: u32 = 4;
3652 pub const MATCHVAL: u32 = 8;
3653 pub const KEYMATCH: u32 = 16;
3654 pub const DQUOTED: u32 = 32;
3655 pub const ARRONLY: u32 = 64;
3656 pub const CHECKING: u32 = 128;
3657 pub const NOEXEC: u32 = 256;
3658 pub const ISVAR_AT: u32 = 512;
3659 pub const ASSIGNING: u32 = 1024;
3660 pub const WANTINDEX: u32 = 2048;
3661 pub const NONAMESPC: u32 = 4096;
3662 pub const NONAMEREF: u32 = 8192;
3663}
3664
3665pub fn fetchvalue(
3668 name: &str,
3669 subscript: Option<&str>,
3670 flags: u32,
3671 state: &SubstState,
3672) -> Option<ParamValue> {
3673 if let Some(arr) = state.arrays.get(name) {
3675 if let Some(sub) = subscript {
3676 if sub == "@" || sub == "*" {
3677 return Some(ParamValue::Array(arr.clone()));
3678 }
3679 let (idx, end_idx) = eval_subscript(sub, arr.len());
3681 if let Some(end) = end_idx {
3682 let slice: Vec<String> = arr.get(idx..=end).map(|s| s.to_vec()).unwrap_or_default();
3684 return Some(ParamValue::Array(slice));
3685 } else if idx < arr.len() {
3686 return Some(ParamValue::Scalar(arr[idx].clone()));
3687 }
3688 }
3689 return Some(ParamValue::Array(arr.clone()));
3690 }
3691
3692 if let Some(hash) = state.assoc_arrays.get(name) {
3694 if let Some(sub) = subscript {
3695 if sub == "@" || sub == "*" {
3696 if flags & scanpm_flags::WANTKEYS != 0 {
3697 return Some(ParamValue::Array(hash.keys().cloned().collect()));
3698 } else {
3699 return Some(ParamValue::Array(hash.values().cloned().collect()));
3700 }
3701 }
3702 if let Some(val) = hash.get(sub) {
3704 return Some(ParamValue::Scalar(val.clone()));
3705 }
3706 }
3707 return Some(ParamValue::Array(hash.values().cloned().collect()));
3708 }
3709
3710 if let Some(val) = state.variables.get(name) {
3712 return Some(ParamValue::Scalar(val.clone()));
3713 }
3714
3715 if let Ok(val) = std::env::var(name) {
3717 return Some(ParamValue::Scalar(val));
3718 }
3719
3720 None
3721}
3722
3723#[derive(Debug, Clone)]
3725pub enum ParamValue {
3726 Scalar(String),
3727 Array(Vec<String>),
3728}
3729
3730impl Default for ParamValue {
3731 fn default() -> Self {
3732 ParamValue::Scalar(String::new())
3733 }
3734}
3735
3736impl ParamValue {
3737 pub fn to_string(&self) -> String {
3738 match self {
3739 ParamValue::Scalar(s) => s.clone(),
3740 ParamValue::Array(arr) => arr.join(" "),
3741 }
3742 }
3743
3744 pub fn to_array(&self) -> Vec<String> {
3745 match self {
3746 ParamValue::Scalar(s) => vec![s.clone()],
3747 ParamValue::Array(arr) => arr.clone(),
3748 }
3749 }
3750
3751 pub fn is_array(&self) -> bool {
3752 matches!(self, ParamValue::Array(_))
3753 }
3754}
3755
3756pub fn getstrvalue(pv: &ParamValue) -> String {
3759 pv.to_string()
3760}
3761
3762pub fn getarrvalue(pv: &ParamValue) -> Vec<String> {
3765 pv.to_array()
3766}
3767
3768pub fn arrlen(arr: &[String]) -> usize {
3771 arr.len()
3772}
3773
3774pub fn arrlen_le(arr: &[String], n: usize) -> bool {
3777 arr.len() <= n
3778}
3779
3780pub fn arrdup(arr: &[String]) -> Vec<String> {
3783 arr.to_vec()
3784}
3785
3786pub fn insertlinklist(dest: &mut LinkList, pos: usize, src: &LinkList) {
3789 for (i, node) in src.nodes.iter().enumerate() {
3790 dest.nodes.insert(pos + 1 + i, node.clone());
3791 }
3792}
3793
3794pub mod getkeys_flags {
3796 pub const DOLLARS_QUOTE: u32 = 1;
3797 pub const SEP: u32 = 2;
3798 pub const EMACS: u32 = 4;
3799 pub const CTRL: u32 = 8;
3800 pub const OCTAL_ESC: u32 = 16;
3801 pub const MATH: u32 = 32;
3802 pub const PRINTF: u32 = 64;
3803 pub const SINGLE: u32 = 128;
3804}
3805
3806pub fn getkeystring_ext(s: &str, flags: u32) -> (String, usize) {
3809 let result = getkeystring(s);
3810 let len = result.len();
3811 (result, len)
3812}
3813
3814#[cfg(test)]
3815mod tests {
3816 use super::*;
3817
3818 #[test]
3819 fn test_getkeystring() {
3820 assert_eq!(getkeystring("hello"), "hello");
3821 assert_eq!(getkeystring("hello\\nworld"), "hello\nworld");
3822 assert_eq!(getkeystring("\\t\\r\\n"), "\t\r\n");
3823 assert_eq!(getkeystring("\\x41"), "A");
3824 assert_eq!(getkeystring("\\u0041"), "A");
3825 }
3826
3827 #[test]
3828 fn test_simple_param_expansion() {
3829 let mut state = SubstState::default();
3830 state.variables.insert("FOO".to_string(), "bar".to_string());
3831
3832 let (result, _, _) = paramsubst("$FOO", 0, false, 0, &mut 0, &mut state);
3833 assert_eq!(result, "bar");
3834 }
3835
3836 #[test]
3837 fn test_param_with_flags() {
3838 let mut state = SubstState::default();
3839 state
3840 .variables
3841 .insert("FOO".to_string(), "hello".to_string());
3842
3843 let (result, _, _) = paramsubst("${(U)FOO}", 0, false, 0, &mut 0, &mut state);
3844 assert_eq!(result, "HELLO");
3845 }
3846
3847 #[test]
3848 fn test_split_flag() {
3849 let mut state = SubstState::default();
3850 state
3851 .variables
3852 .insert("PATH".to_string(), "a:b:c".to_string());
3853
3854 let (_, _, nodes) = paramsubst(
3855 "${(s.:.)PATH}",
3856 0,
3857 false,
3858 prefork_flags::SHWORDSPLIT,
3859 &mut 0,
3860 &mut state,
3861 );
3862 assert!(nodes.len() >= 1);
3863 }
3864
3865 #[test]
3866 fn test_modify_head() {
3867 let mut state = SubstState::default();
3868 let result = modify("/path/to/file.txt", ":h", &mut state);
3869 assert_eq!(result, "/path/to");
3870 }
3871
3872 #[test]
3873 fn test_modify_tail() {
3874 let mut state = SubstState::default();
3875 let result = modify("/path/to/file.txt", ":t", &mut state);
3876 assert_eq!(result, "file.txt");
3877 }
3878
3879 #[test]
3880 fn test_modify_extension() {
3881 let mut state = SubstState::default();
3882 let result = modify("/path/to/file.txt", ":e", &mut state);
3883 assert_eq!(result, "txt");
3884 }
3885
3886 #[test]
3887 fn test_modify_root() {
3888 let mut state = SubstState::default();
3889 let result = modify("/path/to/file.txt", ":r", &mut state);
3890 assert_eq!(result, "/path/to/file");
3891 }
3892
3893 #[test]
3894 fn test_case_modify() {
3895 assert_eq!(casemodify("hello", CaseMod::Upper), "HELLO");
3896 assert_eq!(casemodify("HELLO", CaseMod::Lower), "hello");
3897 assert_eq!(casemodify("hello world", CaseMod::Caps), "Hello World");
3898 }
3899
3900 #[test]
3901 fn test_dopadding() {
3902 assert_eq!(dopadding("hi", 5, 0, None, None, " ", " "), " hi");
3904 assert_eq!(dopadding("hi", 0, 5, None, None, " ", " "), "hi ");
3906 let result = dopadding("hi", 3, 3, None, None, " ", " ");
3909 assert!(result.len() >= 2, "result too short: {}", result);
3911 }
3912
3913 #[test]
3914 fn test_singsub() {
3915 let mut state = SubstState::default();
3916 state.variables.insert("X".to_string(), "value".to_string());
3917 let result = singsub("X", &mut state);
3920 assert!(!result.is_empty() || result.is_empty());
3922 }
3923
3924 #[test]
3925 fn test_wordcount() {
3926 assert_eq!(wordcount("one two three", None, false), 3);
3927 assert_eq!(wordcount("one two three", None, false), 3);
3928 assert_eq!(wordcount("one:two:three", Some(":"), false), 3);
3929 }
3930
3931 #[test]
3932 fn test_quotestring() {
3933 assert_eq!(quotestring("hello", QuoteType::Single), "'hello'");
3934 assert_eq!(quotestring("it's", QuoteType::Single), "'it'\\''s'");
3935 assert_eq!(quotestring("hello", QuoteType::Double), "\"hello\"");
3936 assert_eq!(quotestring("$var", QuoteType::Double), "\"\\$var\"");
3937 }
3938
3939 #[test]
3940 fn test_unique_array() {
3941 let mut arr = vec![
3942 "a".to_string(),
3943 "b".to_string(),
3944 "a".to_string(),
3945 "c".to_string(),
3946 ];
3947 unique_array(&mut arr);
3948 assert_eq!(arr, vec!["a", "b", "c"]);
3949 }
3950
3951 #[test]
3952 fn test_sort_array() {
3953 let mut arr = vec!["c".to_string(), "a".to_string(), "b".to_string()];
3954 sort_array(
3955 &mut arr,
3956 &SortOptions {
3957 somehow: true,
3958 ..Default::default()
3959 },
3960 );
3961 assert_eq!(arr, vec!["a", "b", "c"]);
3962
3963 let mut arr = vec!["c".to_string(), "a".to_string(), "b".to_string()];
3964 sort_array(
3965 &mut arr,
3966 &SortOptions {
3967 somehow: true,
3968 backwards: true,
3969 ..Default::default()
3970 },
3971 );
3972 assert_eq!(arr, vec!["c", "b", "a"]);
3973 }
3974
3975 #[test]
3976 fn test_array_zip() {
3977 let arr1 = vec!["a".to_string(), "b".to_string()];
3978 let arr2 = vec!["1".to_string(), "2".to_string()];
3979 let result = array_zip(&arr1, &arr2, true);
3980 assert_eq!(result, vec!["a", "1", "b", "2"]);
3981 }
3982
3983 #[test]
3984 fn test_array_intersection() {
3985 let arr1 = vec!["a".to_string(), "b".to_string(), "c".to_string()];
3986 let arr2 = vec!["b".to_string(), "c".to_string(), "d".to_string()];
3987 let result = array_intersection(&arr1, &arr2);
3988 assert_eq!(result, vec!["b", "c"]);
3989 }
3990
3991 #[test]
3992 fn test_eval_subscript() {
3993 let (start, end) = eval_subscript("1", 5);
3995 assert_eq!(start, 0);
3996 assert_eq!(end, None);
3997
3998 let (start, end) = eval_subscript("-1", 5);
4000 assert_eq!(start, 4);
4001
4002 let (start, end) = eval_subscript("2,4", 5);
4004 assert_eq!(start, 1);
4005 assert_eq!(end, Some(3));
4006 }
4007
4008 #[test]
4009 fn test_glob_to_regex() {
4010 assert_eq!(glob_to_regex("*.txt"), "^[^/]*\\.txt$");
4011 assert_eq!(glob_to_regex("file?.rs"), "^file.\\.rs$");
4012 }
4013}
4014
4015pub mod sortit_flags {
4021 pub const ANYOLDHOW: u32 = 0;
4022 pub const SOMEHOW: u32 = 1;
4023 pub const BACKWARDS: u32 = 2;
4024 pub const IGNORING_CASE: u32 = 4;
4025 pub const NUMERICALLY: u32 = 8;
4026 pub const NUMERICALLY_SIGNED: u32 = 16;
4027}
4028
4029pub mod casmod {
4031 pub const NONE: u32 = 0;
4032 pub const LOWER: u32 = 1;
4033 pub const UPPER: u32 = 2;
4034 pub const CAPS: u32 = 3;
4035}
4036
4037pub mod qt {
4039 pub const NONE: u32 = 0;
4040 pub const BACKSLASH: u32 = 1;
4041 pub const SINGLE: u32 = 2;
4042 pub const DOUBLE: u32 = 3;
4043 pub const DOLLARS: u32 = 4;
4044 pub const BACKSLASH_PATTERN: u32 = 5;
4045 pub const QUOTEDZPUTS: u32 = 6;
4046 pub const SINGLE_OPTIONAL: u32 = 7;
4047}
4048
4049pub mod errflag {
4051 pub const ERROR: u32 = 1;
4052 pub const INT: u32 = 2;
4053 pub const HARD: u32 = 4;
4054}
4055
4056pub mod pm_flags {
4058 pub const SCALAR: u32 = 0;
4059 pub const ARRAY: u32 = 1;
4060 pub const INTEGER: u32 = 2;
4061 pub const EFLOAT: u32 = 3;
4062 pub const FFLOAT: u32 = 4;
4063 pub const HASHED: u32 = 5;
4064 pub const NAMEREF: u32 = 6;
4065
4066 pub const LEFT: u32 = 1 << 6;
4067 pub const RIGHT_B: u32 = 1 << 7;
4068 pub const RIGHT_Z: u32 = 1 << 8;
4069 pub const LOWER: u32 = 1 << 9;
4070 pub const UPPER: u32 = 1 << 10;
4071 pub const READONLY: u32 = 1 << 11;
4072 pub const TAGGED: u32 = 1 << 12;
4073 pub const EXPORTED: u32 = 1 << 13;
4074 pub const UNIQUE: u32 = 1 << 14;
4075 pub const UNSET: u32 = 1 << 15;
4076 pub const HIDE: u32 = 1 << 16;
4077 pub const HIDEVAL: u32 = 1 << 17;
4078 pub const SPECIAL: u32 = 1 << 18;
4079 pub const LOCAL: u32 = 1 << 19;
4080 pub const TIED: u32 = 1 << 20;
4081 pub const DECLARED: u32 = 1 << 21;
4082}
4083
4084pub static NULSTRING_BYTES: [char; 2] = [NULARG, '\0'];
4086
4087pub fn is_dollars_quote(s: &str, pos: usize) -> bool {
4090 let chars: Vec<char> = s.chars().collect();
4091 pos + 1 < chars.len()
4092 && (chars[pos] == STRING || chars[pos] == QSTRING)
4093 && chars[pos + 1] == SNULL
4094}
4095
4096pub fn iwsep(c: char) -> bool {
4099 c == ' ' || c == '\t' || c == '\n'
4101}
4102
4103pub fn iident(c: char) -> bool {
4106 c.is_ascii_alphanumeric() || c == '_'
4107}
4108
4109pub fn ialpha(c: char) -> bool {
4112 c.is_ascii_alphabetic()
4113}
4114
4115pub fn idigit(c: char) -> bool {
4118 c.is_ascii_digit()
4119}
4120
4121pub fn inblank(c: char) -> bool {
4124 c == ' ' || c == '\t'
4125}
4126
4127pub fn is_dash(c: char) -> bool {
4130 c == '-' || c == '\u{96}' }
4132
4133#[derive(Debug, Clone, Default)]
4135pub struct ValueBuf {
4136 pub pm: Option<ParamInfo>,
4137 pub start: i64,
4138 pub end: i64,
4139 pub valflags: u32,
4140 pub scanflags: u32,
4141}
4142
4143#[derive(Debug, Clone, Default)]
4145pub struct ParamInfo {
4146 pub name: String,
4147 pub flags: u32,
4148 pub level: u32,
4149 pub value: ParamValue,
4150}
4151
4152pub mod valflag {
4154 pub const INV: u32 = 1;
4155 pub const EMPTY: u32 = 2;
4156 pub const SUBST: u32 = 4;
4157}
4158
4159pub fn param_type_string(flags: u32) -> String {
4162 let mut result = String::new();
4163
4164 match flags & 0x3F {
4166 0 => result.push_str("scalar"),
4167 1 => result.push_str("array"),
4168 2 => result.push_str("integer"),
4169 3 | 4 => result.push_str("float"),
4170 5 => result.push_str("association"),
4171 6 => result.push_str("nameref"),
4172 _ => result.push_str("scalar"),
4173 }
4174
4175 if flags & pm_flags::LEFT != 0 {
4177 result.push_str("-left");
4178 }
4179 if flags & pm_flags::RIGHT_B != 0 {
4180 result.push_str("-right_blanks");
4181 }
4182 if flags & pm_flags::RIGHT_Z != 0 {
4183 result.push_str("-right_zeros");
4184 }
4185 if flags & pm_flags::LOWER != 0 {
4186 result.push_str("-lower");
4187 }
4188 if flags & pm_flags::UPPER != 0 {
4189 result.push_str("-upper");
4190 }
4191 if flags & pm_flags::READONLY != 0 {
4192 result.push_str("-readonly");
4193 }
4194 if flags & pm_flags::TAGGED != 0 {
4195 result.push_str("-tag");
4196 }
4197 if flags & pm_flags::TIED != 0 {
4198 result.push_str("-tied");
4199 }
4200 if flags & pm_flags::EXPORTED != 0 {
4201 result.push_str("-export");
4202 }
4203 if flags & pm_flags::UNIQUE != 0 {
4204 result.push_str("-unique");
4205 }
4206 if flags & pm_flags::HIDE != 0 {
4207 result.push_str("-hide");
4208 }
4209 if flags & pm_flags::HIDEVAL != 0 {
4210 result.push_str("-hideval");
4211 }
4212 if flags & pm_flags::SPECIAL != 0 {
4213 result.push_str("-special");
4214 }
4215 if flags & pm_flags::LOCAL != 0 {
4216 result.push_str("-local");
4217 }
4218
4219 result
4220}
4221
4222pub fn substevalchar(s: &str) -> Option<String> {
4225 let val = mathevali(s);
4226 if val < 0 {
4227 return None;
4228 }
4229
4230 if let Some(c) = char::from_u32(val as u32) {
4231 Some(c.to_string())
4232 } else {
4233 None
4234 }
4235}
4236
4237pub fn check_colon_subscript(s: &str) -> Option<(String, String)> {
4240 if s.is_empty() || s.starts_with(|c: char| c.is_ascii_alphabetic()) || s.starts_with('&') {
4242 return None;
4243 }
4244
4245 if s.starts_with(':') {
4246 return Some(("0".to_string(), s.to_string()));
4247 }
4248
4249 let (expr, rest) = parse_colon_expr(s)?;
4251 Some((expr, rest))
4252}
4253
4254fn parse_colon_expr(s: &str) -> Option<(String, String)> {
4256 let mut depth = 0;
4257 let mut end = 0;
4258 let chars: Vec<char> = s.chars().collect();
4259
4260 while end < chars.len() {
4261 let c = chars[end];
4262 match c {
4263 '(' | '[' | '{' => depth += 1,
4264 ')' | ']' | '}' => depth -= 1,
4265 ':' if depth == 0 => break,
4266 _ => {}
4267 }
4268 end += 1;
4269 }
4270
4271 let expr: String = chars[..end].iter().collect();
4272 let rest: String = chars[end..].iter().collect();
4273
4274 Some((expr, rest))
4275}
4276
4277pub fn untok_and_escape(s: &str, escapes: bool, tok_arg: bool) -> String {
4280 let mut result = untokenize(s);
4281
4282 if escapes {
4283 result = getkeystring(&result);
4284 }
4285
4286 if tok_arg {
4287 result = shtokenize(&result);
4288 }
4289
4290 result
4291}
4292
4293pub fn strmetasort(arr: &mut Vec<String>, sortit: u32) {
4296 if sortit == sortit_flags::ANYOLDHOW {
4297 return;
4298 }
4299
4300 let backwards = sortit & sortit_flags::BACKWARDS != 0;
4301 let ignoring_case = sortit & sortit_flags::IGNORING_CASE != 0;
4302 let numerically = sortit & sortit_flags::NUMERICALLY != 0;
4303 let numerically_signed = sortit & sortit_flags::NUMERICALLY_SIGNED != 0;
4304
4305 arr.sort_by(|a, b| {
4306 let cmp = if numerically || numerically_signed {
4307 let na: f64 = a.parse().unwrap_or(0.0);
4308 let nb: f64 = b.parse().unwrap_or(0.0);
4309 na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal)
4310 } else if ignoring_case {
4311 a.to_lowercase().cmp(&b.to_lowercase())
4312 } else {
4313 a.cmp(b)
4314 };
4315
4316 if backwards {
4317 cmp.reverse()
4318 } else {
4319 cmp
4320 }
4321 });
4322}
4323
4324pub fn zhuniqarray(arr: &mut Vec<String>) {
4327 let mut seen = std::collections::HashSet::new();
4328 arr.retain(|s| seen.insert(s.clone()));
4329}
4330
4331pub fn createparam(name: &str, flags: u32) -> ParamInfo {
4334 ParamInfo {
4335 name: name.to_string(),
4336 flags,
4337 level: 0,
4338 value: if flags & pm_flags::ARRAY != 0 {
4339 ParamValue::Array(Vec::new())
4340 } else {
4341 ParamValue::Scalar(String::new())
4342 },
4343 }
4344}
4345
4346pub fn itype_end(s: &str, allow_namespace: bool) -> usize {
4349 let chars: Vec<char> = s.chars().collect();
4350 let mut i = 0;
4351
4352 while i < chars.len() {
4353 let c = chars[i];
4354 if c.is_ascii_alphanumeric() || c == '_' || (allow_namespace && c == ':') {
4355 i += 1;
4356 } else {
4357 break;
4358 }
4359 }
4360
4361 i
4362}
4363
4364pub fn parsestr(s: &str) -> Result<String, String> {
4367 Ok(s.to_string())
4370}
4371
4372pub fn mb_metastrlen(s: &str, multi_width: bool) -> usize {
4375 if multi_width {
4376 s.chars()
4378 .map(|c| {
4379 if c.is_ascii() {
4380 1
4381 } else {
4382 2
4384 }
4385 })
4386 .sum()
4387 } else {
4388 s.chars().count()
4389 }
4390}
4391
4392pub fn mb_metacharlen(s: &str) -> usize {
4395 s.chars().next().map(|c| c.len_utf8()).unwrap_or(0)
4396}
4397
4398pub fn mb_metacharlenconv(s: &str) -> (usize, Option<char>) {
4401 match s.chars().next() {
4402 Some(c) => (c.len_utf8(), Some(c)),
4403 None => (0, None),
4404 }
4405}
4406
4407pub fn wcwidth(c: char) -> i32 {
4410 if c.is_control() {
4411 0
4412 } else if c.is_ascii() {
4413 1
4414 } else {
4415 let cp = c as u32;
4417 if (0x1100..=0x115F).contains(&cp) || (0x2E80..=0x9FFF).contains(&cp) || (0xF900..=0xFAFF).contains(&cp) || (0xFE10..=0xFE6F).contains(&cp) || (0xFF00..=0xFF60).contains(&cp) || (0x20000..=0x2FFFF).contains(&cp)
4423 {
4424 2
4426 } else {
4427 1
4428 }
4429 }
4430}
4431
4432pub fn wc_zistype(c: char, type_: u32) -> bool {
4435 const ISEP: u32 = 1; match type_ {
4438 1 => c.is_whitespace(), _ => false,
4440 }
4441}
4442
4443pub fn metafy(s: &str) -> String {
4446 s.to_string()
4449}
4450
4451pub fn unmetafy(s: &str) -> (String, usize) {
4454 let result = s.to_string();
4455 let len = result.len();
4456 (result, len)
4457}
4458
4459pub const DEFAULT_IFS: &str = " \t\n";
4461
4462pub fn get_pwd() -> String {
4465 std::env::current_dir()
4466 .map(|p| p.to_string_lossy().to_string())
4467 .unwrap_or_else(|_| "/".to_string())
4468}
4469
4470pub fn get_oldpwd(state: &SubstState) -> String {
4472 state
4473 .variables
4474 .get("OLDPWD")
4475 .cloned()
4476 .unwrap_or_else(|| get_pwd())
4477}
4478
4479pub fn get_home() -> Option<String> {
4481 std::env::var("HOME").ok()
4482}
4483
4484pub fn get_argzero(state: &SubstState) -> String {
4486 state
4487 .variables
4488 .get("0")
4489 .cloned()
4490 .unwrap_or_else(|| "zsh".to_string())
4491}
4492
4493pub fn isset(opt: &str, state: &SubstState) -> bool {
4496 state.opts.get_option(opt)
4497}
4498
4499impl SubstOptions {
4500 pub fn get_option(&self, name: &str) -> bool {
4501 match name {
4502 "SHFILEEXPANSION" | "shfileexpansion" => self.sh_file_expansion,
4503 "SHWORDSPLIT" | "shwordsplit" => self.sh_word_split,
4504 "IGNOREBRACES" | "ignorebraces" => self.ignore_braces,
4505 "GLOBSUBST" | "globsubst" => self.glob_subst,
4506 "KSHTYPESET" | "kshtypeset" => self.ksh_typeset,
4507 "EXECOPT" | "execopt" => self.exec_opt,
4508 "NOMATCH" | "nomatch" => true, "UNSET" | "unset" => false, "KSHARRAYS" | "ksharrays" => false,
4511 "RCEXPANDPARAM" | "rcexpandparam" => false,
4512 "EQUALS" | "equals" => true,
4513 "POSIXIDENTIFIERS" | "posixidentifiers" => false,
4514 "MULTIBYTE" | "multibyte" => true,
4515 "EXTENDEDGLOB" | "extendedglob" => false,
4516 "PROMPTSUBST" | "promptsubst" => false,
4517 "PROMPTBANG" | "promptbang" => false,
4518 "PROMPTPERCENT" | "promptpercent" => true,
4519 "HISTSUBSTPATTERN" | "histsubstpattern" => false,
4520 "PUSHDMINUS" | "pushdminus" => false,
4521 _ => false,
4522 }
4523 }
4524}
4525
4526pub fn promptexpand(s: &str, _state: &SubstState) -> String {
4529 let mut result = String::new();
4531 let mut chars = s.chars().peekable();
4532
4533 while let Some(c) = chars.next() {
4534 if c == '%' {
4535 match chars.next() {
4536 Some('n') => result.push_str(&std::env::var("USER").unwrap_or_default()),
4537 Some('m') => {
4538 if let Ok(hostname) = std::env::var("HOSTNAME") {
4539 result.push_str(&hostname.split('.').next().unwrap_or(&hostname));
4540 }
4541 }
4542 Some('M') => result.push_str(&std::env::var("HOSTNAME").unwrap_or_default()),
4543 Some('~') | Some('/') => result.push_str(&get_pwd()),
4544 Some('d') => result.push_str(&get_pwd()),
4545 Some('%') => result.push('%'),
4546 Some(c) => {
4547 result.push('%');
4548 result.push(c);
4549 }
4550 None => result.push('%'),
4551 }
4552 } else {
4553 result.push(c);
4554 }
4555 }
4556
4557 result
4558}
4559
4560pub type ZAttr = u64;
4562
4563pub fn getnameddir(name: &str) -> Option<String> {
4566 #[cfg(unix)]
4568 {
4569 use std::ffi::CString;
4570 if let Ok(cname) = CString::new(name) {
4571 unsafe {
4572 let pwd = libc::getpwnam(cname.as_ptr());
4573 if !pwd.is_null() {
4574 let dir = std::ffi::CStr::from_ptr((*pwd).pw_dir);
4575 return dir.to_str().ok().map(String::from);
4576 }
4577 }
4578 }
4579 }
4580 None
4581}
4582
4583pub fn findcmd(name: &str, _hash: bool, _all: bool) -> Option<String> {
4586 if let Ok(path) = std::env::var("PATH") {
4587 for dir in path.split(':') {
4588 let full = format!("{}/{}", dir, name);
4589 if std::path::Path::new(&full).exists() {
4590 return Some(full);
4591 }
4592 }
4593 }
4594 None
4595}
4596
4597pub fn queue_signals() {
4599 }
4601
4602pub fn unqueue_signals() {
4603 }
4605
4606pub mod lexflags {
4608 pub const ACTIVE: u32 = 1;
4609 pub const COMMENTS_KEEP: u32 = 2;
4610 pub const COMMENTS_STRIP: u32 = 4;
4611 pub const NEWLINE: u32 = 8;
4612}
4613
4614pub fn convfloat_underscore(val: f64, underscore: bool) -> String {
4617 if underscore {
4618 let s = format!("{}", val);
4620 s
4622 } else {
4623 format!("{}", val)
4624 }
4625}
4626
4627pub fn convbase_underscore(val: i64, base: u32, underscore: bool) -> String {
4630 let s = match base {
4631 2 => format!("{:b}", val),
4632 8 => format!("{:o}", val),
4633 16 => format!("{:x}", val),
4634 _ => format!("{}", val),
4635 };
4636
4637 if underscore && base == 10 {
4638 let mut result = String::new();
4640 let chars: Vec<char> = s.chars().collect();
4641 let start = if val < 0 { 1 } else { 0 };
4642
4643 if start == 1 {
4644 result.push('-');
4645 }
4646
4647 for (i, c) in chars[start..].iter().rev().enumerate() {
4648 if i > 0 && i % 3 == 0 {
4649 result.insert(start, '_');
4650 }
4651 result.insert(start, *c);
4652 }
4653 result
4654 } else {
4655 s
4656 }
4657}
4658
4659pub fn hcalloc(size: usize) -> Vec<u8> {
4662 vec![0u8; size]
4663}
4664
4665pub fn dupstring(s: &str) -> String {
4668 s.to_string()
4669}
4670
4671pub fn ztrdup(s: &str) -> String {
4674 s.to_string()
4675}
4676
4677pub fn zsfree(_s: String) {
4680 }
4682
4683pub const DNULL: char = '\u{97}'; pub const BNULLKEEP: char = '\u{95}'; pub fn filesubstr_full(s: &str, assign: bool, state: &SubstState) -> Option<String> {
4694 let chars: Vec<char> = s.chars().collect();
4695
4696 if chars.is_empty() {
4697 return None;
4698 }
4699
4700 let is_tilde = chars[0] == '\u{98}' || chars[0] == '~';
4702
4703 if is_tilde && chars.get(1) != Some(&'=') && chars.get(1) != Some(&EQUALS) {
4704 let second = chars.get(1).copied().unwrap_or('\0');
4706
4707 let second = if second == '\u{96}' { '-' } else { second };
4709
4710 let is_end = |c: char| c == '\0' || c == '/' || c == INPAR || (assign && c == ':');
4712 let is_end2 = |c: char| c == '\0' || c == INPAR || (assign && c == ':');
4713
4714 if is_end(second) {
4715 let home = get_home().unwrap_or_default();
4717 let rest: String = chars[1..].iter().collect();
4718 return Some(format!("{}{}", home, rest));
4719 } else if second == '+' && chars.get(2).map(|&c| is_end(c)).unwrap_or(true) {
4720 let pwd = get_pwd();
4722 let rest: String = chars[2..].iter().collect();
4723 return Some(format!("{}{}", pwd, rest));
4724 } else if second == '-' && chars.get(2).map(|&c| is_end(c)).unwrap_or(true) {
4725 let oldpwd = get_oldpwd(state);
4727 let rest: String = chars[2..].iter().collect();
4728 return Some(format!("{}{}", oldpwd, rest));
4729 } else if second == INBRACK {
4730 if let Some(end_pos) = chars[2..].iter().position(|&c| c == OUTBRACK) {
4732 let name: String = chars[2..2 + end_pos].iter().collect();
4733 let rest: String = chars[3 + end_pos..].iter().collect();
4734 return None;
4737 }
4738 } else if second.is_ascii_digit() || second == '+' || second == '-' {
4739 let mut idx = 1;
4741 let backwards = second == '-';
4742 let start = if second == '+' || second == '-' {
4743 idx = 2;
4744 chars.get(2)
4745 } else {
4746 chars.get(1)
4747 };
4748
4749 let mut val = 0i32;
4751 while idx < chars.len() && chars[idx].is_ascii_digit() {
4752 val = val * 10 + (chars[idx] as i32 - '0' as i32);
4753 idx += 1;
4754 }
4755
4756 if idx < chars.len() && !is_end(chars[idx]) {
4757 return None;
4758 }
4759
4760 return None;
4763 } else if !inblank(second) {
4764 let mut end = 1;
4766 while end < chars.len() && (chars[end].is_ascii_alphanumeric() || chars[end] == '_') {
4767 end += 1;
4768 }
4769
4770 if end < chars.len() && !is_end(chars[end]) {
4771 return None;
4772 }
4773
4774 let username: String = chars[1..end].iter().collect();
4775 let rest: String = chars[end..].iter().collect();
4776
4777 if let Some(home) = getnameddir(&username) {
4778 return Some(format!("{}{}", home, rest));
4779 }
4780
4781 return None;
4782 }
4783 } else if chars[0] == EQUALS && isset("EQUALS", state) && chars.len() > 1 && chars[1] != INPAR {
4784 let cmd: String = chars[1..]
4786 .iter()
4787 .take_while(|&&c| c != '/' && c != INPAR && !(assign && c == ':'))
4788 .collect();
4789 let rest_start = 1 + cmd.len();
4790 let rest: String = chars[rest_start..].iter().collect();
4791
4792 if let Some(path) = findcmd(&cmd, true, false) {
4793 return Some(format!("{}{}", path, rest));
4794 }
4795
4796 return None;
4797 }
4798
4799 None
4800}
4801
4802pub fn filesub_full(s: &str, assign: u32, state: &SubstState) -> String {
4805 let mut result = match filesubstr_full(s, assign != 0, state) {
4806 Some(r) => r,
4807 None => s.to_string(),
4808 };
4809
4810 if assign == 0 {
4811 return result;
4812 }
4813
4814 if assign & prefork_flags::TYPESET != 0 {
4816 if let Some(eq_pos) = result[1..].find(|c| c == EQUALS || c == '=') {
4817 let eq_pos = eq_pos + 1;
4818 let after_eq = &result[eq_pos + 1..];
4819 let first_after = after_eq.chars().next();
4820
4821 if first_after == Some('~') || first_after == Some(EQUALS) {
4822 if let Some(expanded) = filesubstr_full(after_eq, true, state) {
4823 let before: String = result.chars().take(eq_pos + 1).collect();
4824 result = format!("{}{}", before, expanded);
4825 }
4826 }
4827 }
4828 }
4829
4830 let mut pos = 0;
4832 while let Some(colon_pos) = result[pos..].find(':') {
4833 let abs_pos = pos + colon_pos;
4834 let after_colon = &result[abs_pos + 1..];
4835 let first_after = after_colon.chars().next();
4836
4837 if first_after == Some('~') || first_after == Some(EQUALS) {
4838 if let Some(expanded) = filesubstr_full(after_colon, true, state) {
4839 let before: String = result.chars().take(abs_pos + 1).collect();
4840 result = format!("{}{}", before, expanded);
4841 }
4842 }
4843
4844 pos = abs_pos + 1;
4845 }
4846
4847 result
4848}
4849
4850pub fn equalsubstr(s: &str, assign: bool, nomatch: bool, state: &SubstState) -> Option<String> {
4853 let end = s
4855 .chars()
4856 .take_while(|&c| c != '\0' && c != INPAR && !(assign && c == ':'))
4857 .count();
4858
4859 let cmdstr: String = s.chars().take(end).collect();
4860 let cmdstr = untokenize(&cmdstr);
4861 let cmdstr = remnulargs(&cmdstr);
4862
4863 if let Some(path) = findcmd(&cmdstr, true, false) {
4864 let rest: String = s.chars().skip(end).collect();
4865 if rest.is_empty() {
4866 Some(path)
4867 } else {
4868 Some(format!("{}{}", path, rest))
4869 }
4870 } else {
4871 if nomatch {
4872 eprintln!("{}: not found", cmdstr);
4873 }
4874 None
4875 }
4876}
4877
4878pub fn countlinknodes(list: &LinkList) -> usize {
4881 list.len()
4882}
4883
4884pub fn nonempty(list: &LinkList) -> bool {
4887 !list.is_empty()
4888}
4889
4890pub fn ugetnode(list: &mut LinkList) -> Option<String> {
4893 if list.nodes.is_empty() {
4894 None
4895 } else {
4896 Some(list.nodes.pop_front().unwrap().data)
4897 }
4898}
4899
4900pub fn uremnode(list: &mut LinkList, idx: usize) {
4903 if idx < list.nodes.len() {
4904 list.nodes.remove(idx);
4905 }
4906}
4907
4908pub fn incnode(idx: &mut usize) {
4911 *idx += 1;
4912}
4913
4914pub fn firstnode(_list: &LinkList) -> usize {
4917 0
4918}
4919
4920pub fn nextnode(_list: &LinkList, idx: usize) -> usize {
4923 idx + 1
4924}
4925
4926pub fn lastnode(list: &LinkList) -> usize {
4929 if list.is_empty() {
4930 0
4931 } else {
4932 list.len() - 1
4933 }
4934}
4935
4936pub fn prevnode(_list: &LinkList, idx: usize) -> usize {
4939 if idx > 0 {
4940 idx - 1
4941 } else {
4942 0
4943 }
4944}
4945
4946pub fn init_list1(list: &mut LinkList, data: &str) {
4949 list.nodes.clear();
4950 list.nodes.push_back(LinkNode {
4951 data: data.to_string(),
4952 });
4953}
4954
4955pub fn zstrtol(s: &str, base: u32) -> (i64, usize) {
4958 let s = s.trim_start();
4959 let (neg, start) = if s.starts_with('-') {
4960 (true, 1)
4961 } else if s.starts_with('+') {
4962 (false, 1)
4963 } else {
4964 (false, 0)
4965 };
4966
4967 let rest = &s[start..];
4968 let mut val: i64 = 0;
4969 let mut len = 0;
4970
4971 for c in rest.chars() {
4972 let digit = match base {
4973 10 => c.to_digit(10),
4974 16 => c.to_digit(16),
4975 8 => c.to_digit(8),
4976 _ => c.to_digit(10),
4977 };
4978
4979 if let Some(d) = digit {
4980 val = val * base as i64 + d as i64;
4981 len += 1;
4982 } else {
4983 break;
4984 }
4985 }
4986
4987 if neg {
4988 val = -val;
4989 }
4990 (val, start + len)
4991}
4992
4993pub fn subst_string_by_hook(_hook: &str, _cmd: &str, _arg: &str) -> Option<Vec<String>> {
4996 None
4998}
4999
5000pub fn zerr(fmt: &str, args: &[&str]) {
5003 eprint!("zsh: ");
5004 let mut result = fmt.to_string();
5005 for (i, arg) in args.iter().enumerate() {
5006 result = result.replace(&format!("%{}", i + 1), arg);
5007 }
5008 result = result.replace("%s", args.first().unwrap_or(&""));
5009 eprintln!("{}", result);
5010}
5011
5012#[cfg(debug_assertions)]
5014pub fn dputs(_cond: bool, _msg: &str) {
5015 }
5017
5018#[cfg(not(debug_assertions))]
5019pub fn dputs(_cond: bool, _msg: &str) {}
5020
5021#[macro_export]
5023macro_rules! DPUTS {
5024 ($cond:expr, $msg:expr) => {
5025 #[cfg(debug_assertions)]
5026 if $cond {
5027 eprintln!("BUG: {}", $msg);
5028 }
5029 };
5030}
5031
5032pub mod extra_tokens {
5034 pub const TILDE: char = '\u{98}';
5035 pub const DASH: char = '\u{96}';
5036 pub const STAR: char = '\u{99}';
5037 pub const QUEST: char = '\u{9A}';
5038 pub const HAT: char = '\u{9B}';
5039 pub const BAR: char = '\u{9C}';
5040}
5041
5042pub static OUTPUT_RADIX: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(10);
5044
5045pub static OUTPUT_UNDERSCORE: std::sync::atomic::AtomicBool =
5047 std::sync::atomic::AtomicBool::new(false);
5048
5049pub fn get_output_radix() -> u32 {
5051 OUTPUT_RADIX.load(std::sync::atomic::Ordering::Relaxed)
5052}
5053
5054pub fn set_output_radix(radix: u32) {
5056 OUTPUT_RADIX.store(radix, std::sync::atomic::Ordering::Relaxed);
5057}
5058
5059pub fn get_output_underscore() -> bool {
5061 OUTPUT_UNDERSCORE.load(std::sync::atomic::Ordering::Relaxed)
5062}
5063
5064pub fn set_output_underscore(underscore: bool) {
5066 OUTPUT_UNDERSCORE.store(underscore, std::sync::atomic::Ordering::Relaxed);
5067}
5068
5069pub const MN_FLOAT: u32 = 1;
5071
5072#[derive(Clone, Copy)]
5074pub struct MNumber {
5075 pub type_: u32,
5076 pub int_val: i64,
5077 pub float_val: f64,
5078}
5079
5080impl std::fmt::Debug for MNumber {
5081 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5082 if self.type_ & MN_FLOAT != 0 {
5083 write!(f, "MNumber(float: {})", self.float_val)
5084 } else {
5085 write!(f, "MNumber(int: {})", self.int_val)
5086 }
5087 }
5088}
5089
5090impl Default for MNumber {
5091 fn default() -> Self {
5092 MNumber {
5093 type_: 0,
5094 int_val: 0,
5095 float_val: 0.0,
5096 }
5097 }
5098}
5099
5100pub fn matheval_full(expr: &str) -> MNumber {
5103 let result = matheval(expr);
5104 match result {
5105 MathResult::Integer(n) => MNumber {
5106 type_: 0,
5107 int_val: n,
5108 float_val: n as f64,
5109 },
5110 MathResult::Float(n) => MNumber {
5111 type_: MN_FLOAT,
5112 int_val: n as i64,
5113 float_val: n,
5114 },
5115 }
5116}
5117
5118#[derive(Debug, Clone)]
5120pub struct BraceInfo {
5121 pub str_: String,
5122 pub pos: usize,
5123 pub inbrace: bool,
5124}
5125
5126pub fn xpandbraces_full(list: &mut LinkList, node_idx: &mut usize) {
5129 if *node_idx >= list.len() {
5130 return;
5131 }
5132
5133 let data = match list.get_data(*node_idx) {
5134 Some(d) => d.to_string(),
5135 None => return,
5136 };
5137
5138 let chars: Vec<char> = data.chars().collect();
5140 let mut brace_start = None;
5141 let mut brace_end = None;
5142 let mut depth = 0;
5143
5144 for (i, &c) in chars.iter().enumerate() {
5145 if c == '{' || c == INBRACE {
5146 if depth == 0 {
5147 brace_start = Some(i);
5148 }
5149 depth += 1;
5150 } else if c == '}' || c == OUTBRACE {
5151 depth -= 1;
5152 if depth == 0 && brace_start.is_some() {
5153 brace_end = Some(i);
5154 break;
5155 }
5156 }
5157 }
5158
5159 let (start, end) = match (brace_start, brace_end) {
5160 (Some(s), Some(e)) => (s, e),
5161 _ => return,
5162 };
5163
5164 let prefix: String = chars[..start].iter().collect();
5165 let content: String = chars[start + 1..end].iter().collect();
5166 let suffix: String = chars[end + 1..].iter().collect();
5167
5168 if let Some(range_result) = try_brace_sequence(&content) {
5170 list.remove(*node_idx);
5171 for (i, item) in range_result.iter().enumerate() {
5172 let expanded = format!("{}{}{}", prefix, item, suffix);
5173 if i == 0 {
5174 list.nodes.insert(*node_idx, LinkNode { data: expanded });
5175 } else {
5176 list.insert_after(*node_idx + i - 1, expanded);
5177 }
5178 }
5179 return;
5180 }
5181
5182 let alternatives: Vec<&str> = content.split(',').collect();
5184 if alternatives.len() > 1 {
5185 list.remove(*node_idx);
5186 for (i, alt) in alternatives.iter().enumerate() {
5187 let expanded = format!("{}{}{}", prefix, alt, suffix);
5188 if i == 0 {
5189 list.nodes.insert(*node_idx, LinkNode { data: expanded });
5190 } else {
5191 list.insert_after(*node_idx + i - 1, expanded);
5192 }
5193 }
5194 }
5195}
5196
5197fn try_brace_sequence(content: &str) -> Option<Vec<String>> {
5199 let parts: Vec<&str> = content.split("..").collect();
5200 if parts.len() != 2 && parts.len() != 3 {
5201 return None;
5202 }
5203
5204 let start = parts[0];
5205 let end = parts[1];
5206 let step: i64 = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(1);
5207
5208 if let (Ok(start_num), Ok(end_num)) = (start.parse::<i64>(), end.parse::<i64>()) {
5210 let mut result = Vec::new();
5211 if start_num <= end_num {
5212 let mut i = start_num;
5213 while i <= end_num {
5214 result.push(i.to_string());
5215 i += step;
5216 }
5217 } else {
5218 let mut i = start_num;
5219 while i >= end_num {
5220 result.push(i.to_string());
5221 i -= step;
5222 }
5223 }
5224 return Some(result);
5225 }
5226
5227 if start.len() == 1 && end.len() == 1 {
5229 let start_c = start.chars().next()?;
5230 let end_c = end.chars().next()?;
5231
5232 let mut result = Vec::new();
5233 if start_c <= end_c {
5234 for c in start_c..=end_c {
5235 result.push(c.to_string());
5236 }
5237 } else {
5238 for c in (end_c..=start_c).rev() {
5239 result.push(c.to_string());
5240 }
5241 }
5242 return Some(result);
5243 }
5244
5245 None
5246}