1use std::collections::{HashMap, HashSet};
10use std::env;
11use std::sync::atomic::{AtomicI64, Ordering};
12use std::time::{Instant, SystemTime, UNIX_EPOCH};
13
14pub mod flags {
19 pub const SCALAR: u32 = 1 << 0;
20 pub const INTEGER: u32 = 1 << 1;
21 pub const EFLOAT: u32 = 1 << 2; pub const FFLOAT: u32 = 1 << 3; pub const ARRAY: u32 = 1 << 4;
24 pub const HASHED: u32 = 1 << 5; pub const READONLY: u32 = 1 << 6;
26 pub const SPECIAL: u32 = 1 << 7;
27 pub const LOCAL: u32 = 1 << 8;
28 pub const EXPORT: u32 = 1 << 9; pub const UNSET: u32 = 1 << 10;
30 pub const TIED: u32 = 1 << 11;
31 pub const UNIQUE: u32 = 1 << 12; pub const LOWER: u32 = 1 << 13; pub const UPPER: u32 = 1 << 14; pub const TAG: u32 = 1 << 15; pub const HIDE: u32 = 1 << 16;
36 pub const HIDEVAL: u32 = 1 << 17;
37 pub const NORESTORE: u32 = 1 << 18;
38 pub const NAMEREF: u32 = 1 << 19; pub const LEFT: u32 = 1 << 20; pub const RIGHT_B: u32 = 1 << 21; pub const RIGHT_Z: u32 = 1 << 22; pub const AUTOLOAD: u32 = 1 << 23; pub const DECLARED: u32 = 1 << 24; pub const REMOVABLE: u32 = 1 << 25; pub const HASHELEM: u32 = 1 << 26; pub const NAMEDDIR: u32 = 1 << 27; pub const DONTIMPORT: u32 = 1 << 28;
48 pub const DEFAULTED: u32 = 1 << 29;
49 pub const DONTIMPORT_SUID: u32 = 1 << 30;
50
51 pub const READONLY_SPECIAL: u32 = READONLY | SPECIAL;
53
54 pub const TYPE_MASK: u32 = SCALAR | INTEGER | EFLOAT | FFLOAT | ARRAY | HASHED | NAMEREF;
56
57 pub fn pm_type(flags: u32) -> u32 {
59 flags & TYPE_MASK
60 }
61
62 pub const FLOAT: u32 = FFLOAT;
64 pub const ASSOC: u32 = HASHED;
66}
67
68pub mod scan_flags {
73 pub const WANTVALS: u32 = 1 << 0;
74 pub const WANTKEYS: u32 = 1 << 1;
75 pub const WANTINDEX: u32 = 1 << 2;
76 pub const MATCHKEY: u32 = 1 << 3;
77 pub const MATCHVAL: u32 = 1 << 4;
78 pub const MATCHMANY: u32 = 1 << 5;
79 pub const KEYMATCH: u32 = 1 << 6;
80 pub const ARRONLY: u32 = 1 << 7;
81 pub const ISVAR_AT: u32 = 1 << 8;
82 pub const DQUOTED: u32 = 1 << 9;
83 pub const NOEXEC: u32 = 1 << 10;
84 pub const CHECKING: u32 = 1 << 11;
85 pub const ASSIGNING: u32 = 1 << 12;
86 pub const NONAMEREF: u32 = 1 << 13;
87 pub const NONAMESPC: u32 = 1 << 14;
88}
89
90pub mod assign_flags {
95 pub const AUGMENT: u32 = 1 << 0; pub const WARN: u32 = 1 << 1; pub const ENV_IMPORT: u32 = 1 << 2; pub const KEY_VALUE: u32 = 1 << 3; }
100
101pub mod val_flags {
106 pub const INV: u32 = 1 << 0; pub const EMPTY: u32 = 1 << 1; pub const SUBST: u32 = 1 << 2; pub const REFSLICE: u32 = 1 << 3; }
111
112pub mod print_flags {
117 pub const TYPE: u32 = 1 << 0;
118 pub const TYPESET: u32 = 1 << 1;
119 pub const NAMEONLY: u32 = 1 << 2;
120 pub const KV_PAIR: u32 = 1 << 3;
121 pub const LINE: u32 = 1 << 4;
122 pub const INCLUDEVALUE: u32 = 1 << 5;
123 pub const POSIX_READONLY: u32 = 1 << 6;
124 pub const POSIX_EXPORT: u32 = 1 << 7;
125 pub const WITH_NAMESPACE: u32 = 1 << 8;
126}
127
128#[derive(Clone, Debug)]
133pub enum ParamValue {
134 Scalar(String),
135 Integer(i64),
136 Float(f64),
137 Array(Vec<String>),
138 Assoc(HashMap<String, String>),
139 Unset,
140}
141
142impl Default for ParamValue {
143 fn default() -> Self {
144 ParamValue::Unset
145 }
146}
147
148impl ParamValue {
149 pub fn as_string(&self) -> String {
150 match self {
151 ParamValue::Scalar(s) => s.clone(),
152 ParamValue::Integer(i) => i.to_string(),
153 ParamValue::Float(f) => format_float(*f, 0, 0),
154 ParamValue::Array(a) => a.join(" "),
155 ParamValue::Assoc(h) => {
156 let mut vals: Vec<&String> = h.values().collect();
157 vals.sort();
158 vals.into_iter().cloned().collect::<Vec<_>>().join(" ")
159 }
160 ParamValue::Unset => String::new(),
161 }
162 }
163
164 pub fn as_integer(&self) -> i64 {
165 match self {
166 ParamValue::Scalar(s) => s.parse().unwrap_or(0),
167 ParamValue::Integer(i) => *i,
168 ParamValue::Float(f) => *f as i64,
169 ParamValue::Array(a) => a.len() as i64,
170 ParamValue::Assoc(h) => h.len() as i64,
171 ParamValue::Unset => 0,
172 }
173 }
174
175 pub fn as_float(&self) -> f64 {
176 match self {
177 ParamValue::Scalar(s) => s.parse().unwrap_or(0.0),
178 ParamValue::Integer(i) => *i as f64,
179 ParamValue::Float(f) => *f,
180 ParamValue::Array(a) => a.len() as f64,
181 ParamValue::Assoc(h) => h.len() as f64,
182 ParamValue::Unset => 0.0,
183 }
184 }
185
186 pub fn as_array(&self) -> Vec<String> {
187 match self {
188 ParamValue::Scalar(s) => {
189 if s.is_empty() {
190 Vec::new()
191 } else {
192 vec![s.clone()]
193 }
194 }
195 ParamValue::Integer(i) => vec![i.to_string()],
196 ParamValue::Float(f) => vec![format_float(*f, 0, 0)],
197 ParamValue::Array(a) => a.clone(),
198 ParamValue::Assoc(h) => h.values().cloned().collect(),
199 ParamValue::Unset => Vec::new(),
200 }
201 }
202
203 pub fn is_set(&self) -> bool {
204 !matches!(self, ParamValue::Unset)
205 }
206
207 pub fn type_flag(&self) -> u32 {
209 match self {
210 ParamValue::Scalar(_) => flags::SCALAR,
211 ParamValue::Integer(_) => flags::INTEGER,
212 ParamValue::Float(_) => flags::FFLOAT,
213 ParamValue::Array(_) => flags::ARRAY,
214 ParamValue::Assoc(_) => flags::HASHED,
215 ParamValue::Unset => flags::SCALAR,
216 }
217 }
218}
219
220#[derive(Clone, Debug)]
225pub enum MNumber {
226 Integer(i64),
227 Float(f64),
228}
229
230impl Default for MNumber {
231 fn default() -> Self {
232 MNumber::Integer(0)
233 }
234}
235
236impl MNumber {
237 pub fn as_integer(&self) -> i64 {
238 match self {
239 MNumber::Integer(i) => *i,
240 MNumber::Float(f) => *f as i64,
241 }
242 }
243
244 pub fn as_float(&self) -> f64 {
245 match self {
246 MNumber::Integer(i) => *i as f64,
247 MNumber::Float(f) => *f,
248 }
249 }
250
251 pub fn is_float(&self) -> bool {
252 matches!(self, MNumber::Float(_))
253 }
254}
255
256#[derive(Clone, Debug)]
261pub struct Value {
262 pub pm_name: String,
263 pub start: i64,
264 pub end: i64,
265 pub scan_flags: u32,
266 pub val_flags: u32,
267}
268
269impl Value {
270 pub fn new(name: &str) -> Self {
271 Value {
272 pm_name: name.to_string(),
273 start: 0,
274 end: -1,
275 scan_flags: 0,
276 val_flags: 0,
277 }
278 }
279
280 pub fn is_all(&self) -> bool {
281 self.start == 0 && self.end == -1
282 }
283}
284
285#[derive(Clone, Debug)]
290pub struct Param {
291 pub name: String,
292 pub value: ParamValue,
293 pub flags: u32,
294 pub base: i32, pub width: i32, pub level: i32, pub ename: Option<String>, pub old: Option<Box<Param>>, }
300
301impl Param {
302 pub fn new_scalar(name: &str, value: &str) -> Self {
303 Param {
304 name: name.to_string(),
305 value: ParamValue::Scalar(value.to_string()),
306 flags: flags::SCALAR,
307 base: 10,
308 width: 0,
309 level: 0,
310 ename: None,
311 old: None,
312 }
313 }
314
315 pub fn new_integer(name: &str, value: i64) -> Self {
316 Param {
317 name: name.to_string(),
318 value: ParamValue::Integer(value),
319 flags: flags::INTEGER,
320 base: 10,
321 width: 0,
322 level: 0,
323 ename: None,
324 old: None,
325 }
326 }
327
328 pub fn new_float(name: &str, value: f64) -> Self {
329 Param {
330 name: name.to_string(),
331 value: ParamValue::Float(value),
332 flags: flags::FFLOAT,
333 base: 10,
334 width: 0,
335 level: 0,
336 ename: None,
337 old: None,
338 }
339 }
340
341 pub fn new_array(name: &str, value: Vec<String>) -> Self {
342 Param {
343 name: name.to_string(),
344 value: ParamValue::Array(value),
345 flags: flags::ARRAY,
346 base: 10,
347 width: 0,
348 level: 0,
349 ename: None,
350 old: None,
351 }
352 }
353
354 pub fn new_assoc(name: &str, value: HashMap<String, String>) -> Self {
355 Param {
356 name: name.to_string(),
357 value: ParamValue::Assoc(value),
358 flags: flags::HASHED,
359 base: 10,
360 width: 0,
361 level: 0,
362 ename: None,
363 old: None,
364 }
365 }
366
367 pub fn new_nameref(name: &str, target: &str) -> Self {
368 Param {
369 name: name.to_string(),
370 value: ParamValue::Scalar(target.to_string()),
371 flags: flags::NAMEREF,
372 base: 0,
373 width: 0,
374 level: 0,
375 ename: None,
376 old: None,
377 }
378 }
379
380 pub fn is_readonly(&self) -> bool {
381 (self.flags & flags::READONLY) != 0
382 }
383
384 pub fn is_exported(&self) -> bool {
385 (self.flags & flags::EXPORT) != 0
386 }
387
388 pub fn is_local(&self) -> bool {
389 (self.flags & flags::LOCAL) != 0
390 }
391
392 pub fn is_special(&self) -> bool {
393 (self.flags & flags::SPECIAL) != 0
394 }
395
396 pub fn is_integer(&self) -> bool {
397 flags::pm_type(self.flags) == flags::INTEGER
398 }
399
400 pub fn is_float(&self) -> bool {
401 let t = flags::pm_type(self.flags);
402 t == flags::EFLOAT || t == flags::FFLOAT
403 }
404
405 pub fn is_array(&self) -> bool {
406 flags::pm_type(self.flags) == flags::ARRAY
407 }
408
409 pub fn is_assoc(&self) -> bool {
410 flags::pm_type(self.flags) == flags::HASHED
411 }
412
413 pub fn is_nameref(&self) -> bool {
414 (self.flags & flags::NAMEREF) != 0
415 }
416
417 pub fn is_unset(&self) -> bool {
418 (self.flags & flags::UNSET) != 0
419 }
420
421 pub fn is_tied(&self) -> bool {
422 (self.flags & flags::TIED) != 0
423 }
424
425 pub fn is_hidden(&self) -> bool {
426 (self.flags & flags::HIDE) != 0
427 }
428
429 pub fn is_unique(&self) -> bool {
430 (self.flags & flags::UNIQUE) != 0
431 }
432
433 pub fn get_str_value(&self) -> String {
435 let s = self.value.as_string();
436 self.apply_case_transform(&s)
437 }
438
439 fn apply_case_transform(&self, s: &str) -> String {
440 if (self.flags & flags::LOWER) != 0 {
441 s.to_lowercase()
442 } else if (self.flags & flags::UPPER) != 0 && !self.is_nameref() {
443 s.to_uppercase()
444 } else {
445 s.to_string()
446 }
447 }
448
449 pub fn get_int_str(&self) -> String {
451 let val = self.value.as_integer();
452 convbase(val, self.base as u32)
453 }
454}
455
456#[derive(Clone, Debug)]
461pub struct TiedData {
462 pub join_char: char,
463 pub scalar_name: String,
464 pub array_name: String,
465}
466
467#[derive(Clone, Debug, Default)]
472pub struct SubscriptFlags {
473 pub reverse: bool, pub down: bool, pub index: bool, pub key_match: bool, pub word: bool, pub num: i64, pub begin: i64, pub has_begin: bool,
481 pub separator: Option<String>, pub quote_arg: bool, }
484
485#[derive(Debug, Clone)]
490pub struct SubscriptIndex {
491 pub start: i64,
492 pub end: i64,
493 pub is_all: bool,
494}
495
496impl SubscriptIndex {
497 pub fn single(idx: i64) -> Self {
498 SubscriptIndex {
499 start: idx,
500 end: idx + 1,
501 is_all: false,
502 }
503 }
504
505 pub fn range(start: i64, end: i64) -> Self {
506 SubscriptIndex {
507 start,
508 end,
509 is_all: false,
510 }
511 }
512
513 pub fn all() -> Self {
514 SubscriptIndex {
515 start: 0,
516 end: -1,
517 is_all: true,
518 }
519 }
520}
521
522pub struct ParamTypeInfo {
527 pub bin_flag: u32,
528 pub string: &'static str,
529 pub type_flag: char,
530 pub use_base: bool,
531 pub use_width: bool,
532 pub test_level: bool,
533}
534
535pub const PM_TYPES: &[ParamTypeInfo] = &[
536 ParamTypeInfo {
537 bin_flag: flags::AUTOLOAD,
538 string: "undefined",
539 type_flag: '\0',
540 use_base: false,
541 use_width: false,
542 test_level: false,
543 },
544 ParamTypeInfo {
545 bin_flag: flags::INTEGER,
546 string: "integer",
547 type_flag: 'i',
548 use_base: true,
549 use_width: false,
550 test_level: false,
551 },
552 ParamTypeInfo {
553 bin_flag: flags::EFLOAT,
554 string: "float",
555 type_flag: 'E',
556 use_base: false,
557 use_width: false,
558 test_level: false,
559 },
560 ParamTypeInfo {
561 bin_flag: flags::FFLOAT,
562 string: "float",
563 type_flag: 'F',
564 use_base: false,
565 use_width: false,
566 test_level: false,
567 },
568 ParamTypeInfo {
569 bin_flag: flags::ARRAY,
570 string: "array",
571 type_flag: 'a',
572 use_base: false,
573 use_width: false,
574 test_level: false,
575 },
576 ParamTypeInfo {
577 bin_flag: flags::HASHED,
578 string: "association",
579 type_flag: 'A',
580 use_base: false,
581 use_width: false,
582 test_level: false,
583 },
584 ParamTypeInfo {
585 bin_flag: 0,
586 string: "local",
587 type_flag: '\0',
588 use_base: false,
589 use_width: false,
590 test_level: true,
591 },
592 ParamTypeInfo {
593 bin_flag: flags::HIDE,
594 string: "hide",
595 type_flag: 'h',
596 use_base: false,
597 use_width: false,
598 test_level: false,
599 },
600 ParamTypeInfo {
601 bin_flag: flags::LEFT,
602 string: "left justified",
603 type_flag: 'L',
604 use_base: false,
605 use_width: true,
606 test_level: false,
607 },
608 ParamTypeInfo {
609 bin_flag: flags::RIGHT_B,
610 string: "right justified",
611 type_flag: 'R',
612 use_base: false,
613 use_width: true,
614 test_level: false,
615 },
616 ParamTypeInfo {
617 bin_flag: flags::RIGHT_Z,
618 string: "zero filled",
619 type_flag: 'Z',
620 use_base: false,
621 use_width: true,
622 test_level: false,
623 },
624 ParamTypeInfo {
625 bin_flag: flags::LOWER,
626 string: "lowercase",
627 type_flag: 'l',
628 use_base: false,
629 use_width: false,
630 test_level: false,
631 },
632 ParamTypeInfo {
633 bin_flag: flags::UPPER,
634 string: "uppercase",
635 type_flag: 'u',
636 use_base: false,
637 use_width: false,
638 test_level: false,
639 },
640 ParamTypeInfo {
641 bin_flag: flags::READONLY,
642 string: "readonly",
643 type_flag: 'r',
644 use_base: false,
645 use_width: false,
646 test_level: false,
647 },
648 ParamTypeInfo {
649 bin_flag: flags::TAG,
650 string: "tagged",
651 type_flag: 't',
652 use_base: false,
653 use_width: false,
654 test_level: false,
655 },
656 ParamTypeInfo {
657 bin_flag: flags::EXPORT,
658 string: "exported",
659 type_flag: 'x',
660 use_base: false,
661 use_width: false,
662 test_level: false,
663 },
664 ParamTypeInfo {
665 bin_flag: flags::UNIQUE,
666 string: "unique",
667 type_flag: 'U',
668 use_base: false,
669 use_width: false,
670 test_level: false,
671 },
672 ParamTypeInfo {
673 bin_flag: flags::TIED,
674 string: "tied",
675 type_flag: 'T',
676 use_base: false,
677 use_width: false,
678 test_level: false,
679 },
680 ParamTypeInfo {
681 bin_flag: flags::NAMEREF,
682 string: "nameref",
683 type_flag: 'n',
684 use_base: false,
685 use_width: false,
686 test_level: false,
687 },
688];
689
690#[derive(Clone, Debug)]
695pub struct SpecialParamDef {
696 pub name: &'static str,
697 pub pm_type: u32, pub pm_flags: u32, pub tied_name: Option<&'static str>,
700}
701
702pub const SPECIAL_PARAMS: &[SpecialParamDef] = &[
704 SpecialParamDef {
706 name: "#",
707 pm_type: flags::INTEGER,
708 pm_flags: flags::READONLY,
709 tied_name: None,
710 },
711 SpecialParamDef {
712 name: "ERRNO",
713 pm_type: flags::INTEGER,
714 pm_flags: flags::UNSET,
715 tied_name: None,
716 },
717 SpecialParamDef {
718 name: "GID",
719 pm_type: flags::INTEGER,
720 pm_flags: flags::DONTIMPORT,
721 tied_name: None,
722 },
723 SpecialParamDef {
724 name: "EGID",
725 pm_type: flags::INTEGER,
726 pm_flags: flags::DONTIMPORT,
727 tied_name: None,
728 },
729 SpecialParamDef {
730 name: "HISTSIZE",
731 pm_type: flags::INTEGER,
732 pm_flags: 0,
733 tied_name: None,
734 },
735 SpecialParamDef {
736 name: "RANDOM",
737 pm_type: flags::INTEGER,
738 pm_flags: 0,
739 tied_name: None,
740 },
741 SpecialParamDef {
742 name: "SAVEHIST",
743 pm_type: flags::INTEGER,
744 pm_flags: 0,
745 tied_name: None,
746 },
747 SpecialParamDef {
748 name: "SECONDS",
749 pm_type: flags::INTEGER,
750 pm_flags: 0,
751 tied_name: None,
752 },
753 SpecialParamDef {
754 name: "UID",
755 pm_type: flags::INTEGER,
756 pm_flags: flags::DONTIMPORT,
757 tied_name: None,
758 },
759 SpecialParamDef {
760 name: "EUID",
761 pm_type: flags::INTEGER,
762 pm_flags: flags::DONTIMPORT,
763 tied_name: None,
764 },
765 SpecialParamDef {
766 name: "TTYIDLE",
767 pm_type: flags::INTEGER,
768 pm_flags: flags::READONLY,
769 tied_name: None,
770 },
771 SpecialParamDef {
773 name: "USERNAME",
774 pm_type: flags::SCALAR,
775 pm_flags: flags::DONTIMPORT,
776 tied_name: None,
777 },
778 SpecialParamDef {
779 name: "-",
780 pm_type: flags::SCALAR,
781 pm_flags: flags::READONLY,
782 tied_name: None,
783 },
784 SpecialParamDef {
785 name: "histchars",
786 pm_type: flags::SCALAR,
787 pm_flags: flags::DONTIMPORT,
788 tied_name: None,
789 },
790 SpecialParamDef {
791 name: "HOME",
792 pm_type: flags::SCALAR,
793 pm_flags: flags::UNSET,
794 tied_name: None,
795 },
796 SpecialParamDef {
797 name: "TERM",
798 pm_type: flags::SCALAR,
799 pm_flags: flags::UNSET,
800 tied_name: None,
801 },
802 SpecialParamDef {
803 name: "TERMINFO",
804 pm_type: flags::SCALAR,
805 pm_flags: flags::UNSET,
806 tied_name: None,
807 },
808 SpecialParamDef {
809 name: "TERMINFO_DIRS",
810 pm_type: flags::SCALAR,
811 pm_flags: flags::UNSET,
812 tied_name: None,
813 },
814 SpecialParamDef {
815 name: "WORDCHARS",
816 pm_type: flags::SCALAR,
817 pm_flags: 0,
818 tied_name: None,
819 },
820 SpecialParamDef {
821 name: "IFS",
822 pm_type: flags::SCALAR,
823 pm_flags: flags::DONTIMPORT,
824 tied_name: None,
825 },
826 SpecialParamDef {
827 name: "_",
828 pm_type: flags::SCALAR,
829 pm_flags: flags::DONTIMPORT,
830 tied_name: None,
831 },
832 SpecialParamDef {
833 name: "KEYBOARD_HACK",
834 pm_type: flags::SCALAR,
835 pm_flags: flags::DONTIMPORT,
836 tied_name: None,
837 },
838 SpecialParamDef {
839 name: "0",
840 pm_type: flags::SCALAR,
841 pm_flags: 0,
842 tied_name: None,
843 },
844 SpecialParamDef {
846 name: "!",
847 pm_type: flags::INTEGER,
848 pm_flags: flags::READONLY,
849 tied_name: None,
850 },
851 SpecialParamDef {
852 name: "$",
853 pm_type: flags::INTEGER,
854 pm_flags: flags::READONLY,
855 tied_name: None,
856 },
857 SpecialParamDef {
858 name: "?",
859 pm_type: flags::INTEGER,
860 pm_flags: flags::READONLY,
861 tied_name: None,
862 },
863 SpecialParamDef {
864 name: "HISTCMD",
865 pm_type: flags::INTEGER,
866 pm_flags: flags::READONLY,
867 tied_name: None,
868 },
869 SpecialParamDef {
870 name: "LINENO",
871 pm_type: flags::INTEGER,
872 pm_flags: flags::READONLY,
873 tied_name: None,
874 },
875 SpecialParamDef {
876 name: "PPID",
877 pm_type: flags::INTEGER,
878 pm_flags: flags::READONLY,
879 tied_name: None,
880 },
881 SpecialParamDef {
882 name: "ZSH_SUBSHELL",
883 pm_type: flags::INTEGER,
884 pm_flags: flags::READONLY,
885 tied_name: None,
886 },
887 SpecialParamDef {
889 name: "COLUMNS",
890 pm_type: flags::INTEGER,
891 pm_flags: 0,
892 tied_name: None,
893 },
894 SpecialParamDef {
895 name: "LINES",
896 pm_type: flags::INTEGER,
897 pm_flags: 0,
898 tied_name: None,
899 },
900 SpecialParamDef {
901 name: "ZLE_RPROMPT_INDENT",
902 pm_type: flags::INTEGER,
903 pm_flags: flags::UNSET,
904 tied_name: None,
905 },
906 SpecialParamDef {
907 name: "SHLVL",
908 pm_type: flags::INTEGER,
909 pm_flags: 0,
910 tied_name: None,
911 },
912 SpecialParamDef {
913 name: "FUNCNEST",
914 pm_type: flags::INTEGER,
915 pm_flags: 0,
916 tied_name: None,
917 },
918 SpecialParamDef {
919 name: "OPTIND",
920 pm_type: flags::INTEGER,
921 pm_flags: flags::DONTIMPORT,
922 tied_name: None,
923 },
924 SpecialParamDef {
925 name: "TRY_BLOCK_ERROR",
926 pm_type: flags::INTEGER,
927 pm_flags: flags::DONTIMPORT,
928 tied_name: None,
929 },
930 SpecialParamDef {
931 name: "TRY_BLOCK_INTERRUPT",
932 pm_type: flags::INTEGER,
933 pm_flags: flags::DONTIMPORT,
934 tied_name: None,
935 },
936 SpecialParamDef {
938 name: "OPTARG",
939 pm_type: flags::SCALAR,
940 pm_flags: 0,
941 tied_name: None,
942 },
943 SpecialParamDef {
944 name: "NULLCMD",
945 pm_type: flags::SCALAR,
946 pm_flags: 0,
947 tied_name: None,
948 },
949 SpecialParamDef {
950 name: "POSTEDIT",
951 pm_type: flags::SCALAR,
952 pm_flags: flags::UNSET,
953 tied_name: None,
954 },
955 SpecialParamDef {
956 name: "READNULLCMD",
957 pm_type: flags::SCALAR,
958 pm_flags: 0,
959 tied_name: None,
960 },
961 SpecialParamDef {
962 name: "PS1",
963 pm_type: flags::SCALAR,
964 pm_flags: 0,
965 tied_name: None,
966 },
967 SpecialParamDef {
968 name: "RPS1",
969 pm_type: flags::SCALAR,
970 pm_flags: flags::UNSET,
971 tied_name: None,
972 },
973 SpecialParamDef {
974 name: "RPROMPT",
975 pm_type: flags::SCALAR,
976 pm_flags: flags::UNSET,
977 tied_name: None,
978 },
979 SpecialParamDef {
980 name: "PS2",
981 pm_type: flags::SCALAR,
982 pm_flags: 0,
983 tied_name: None,
984 },
985 SpecialParamDef {
986 name: "RPS2",
987 pm_type: flags::SCALAR,
988 pm_flags: flags::UNSET,
989 tied_name: None,
990 },
991 SpecialParamDef {
992 name: "RPROMPT2",
993 pm_type: flags::SCALAR,
994 pm_flags: flags::UNSET,
995 tied_name: None,
996 },
997 SpecialParamDef {
998 name: "PS3",
999 pm_type: flags::SCALAR,
1000 pm_flags: 0,
1001 tied_name: None,
1002 },
1003 SpecialParamDef {
1004 name: "PS4",
1005 pm_type: flags::SCALAR,
1006 pm_flags: flags::DONTIMPORT_SUID,
1007 tied_name: None,
1008 },
1009 SpecialParamDef {
1010 name: "SPROMPT",
1011 pm_type: flags::SCALAR,
1012 pm_flags: 0,
1013 tied_name: None,
1014 },
1015 SpecialParamDef {
1017 name: "*",
1018 pm_type: flags::ARRAY,
1019 pm_flags: flags::READONLY | flags::DONTIMPORT,
1020 tied_name: None,
1021 },
1022 SpecialParamDef {
1023 name: "@",
1024 pm_type: flags::ARRAY,
1025 pm_flags: flags::READONLY | flags::DONTIMPORT,
1026 tied_name: None,
1027 },
1028 SpecialParamDef {
1030 name: "CDPATH",
1031 pm_type: flags::SCALAR,
1032 pm_flags: flags::TIED,
1033 tied_name: Some("cdpath"),
1034 },
1035 SpecialParamDef {
1036 name: "FIGNORE",
1037 pm_type: flags::SCALAR,
1038 pm_flags: flags::TIED,
1039 tied_name: Some("fignore"),
1040 },
1041 SpecialParamDef {
1042 name: "FPATH",
1043 pm_type: flags::SCALAR,
1044 pm_flags: flags::TIED,
1045 tied_name: Some("fpath"),
1046 },
1047 SpecialParamDef {
1048 name: "MAILPATH",
1049 pm_type: flags::SCALAR,
1050 pm_flags: flags::TIED,
1051 tied_name: Some("mailpath"),
1052 },
1053 SpecialParamDef {
1054 name: "PATH",
1055 pm_type: flags::SCALAR,
1056 pm_flags: flags::TIED,
1057 tied_name: Some("path"),
1058 },
1059 SpecialParamDef {
1060 name: "PSVAR",
1061 pm_type: flags::SCALAR,
1062 pm_flags: flags::TIED,
1063 tied_name: Some("psvar"),
1064 },
1065 SpecialParamDef {
1066 name: "ZSH_EVAL_CONTEXT",
1067 pm_type: flags::SCALAR,
1068 pm_flags: flags::READONLY | flags::TIED,
1069 tied_name: Some("zsh_eval_context"),
1070 },
1071 SpecialParamDef {
1072 name: "MODULE_PATH",
1073 pm_type: flags::SCALAR,
1074 pm_flags: flags::DONTIMPORT | flags::TIED,
1075 tied_name: Some("module_path"),
1076 },
1077 SpecialParamDef {
1078 name: "MANPATH",
1079 pm_type: flags::SCALAR,
1080 pm_flags: flags::TIED,
1081 tied_name: Some("manpath"),
1082 },
1083 SpecialParamDef {
1085 name: "LANG",
1086 pm_type: flags::SCALAR,
1087 pm_flags: flags::UNSET,
1088 tied_name: None,
1089 },
1090 SpecialParamDef {
1091 name: "LC_ALL",
1092 pm_type: flags::SCALAR,
1093 pm_flags: flags::UNSET,
1094 tied_name: None,
1095 },
1096 SpecialParamDef {
1097 name: "LC_COLLATE",
1098 pm_type: flags::SCALAR,
1099 pm_flags: flags::UNSET,
1100 tied_name: None,
1101 },
1102 SpecialParamDef {
1103 name: "LC_CTYPE",
1104 pm_type: flags::SCALAR,
1105 pm_flags: flags::UNSET,
1106 tied_name: None,
1107 },
1108 SpecialParamDef {
1109 name: "LC_MESSAGES",
1110 pm_type: flags::SCALAR,
1111 pm_flags: flags::UNSET,
1112 tied_name: None,
1113 },
1114 SpecialParamDef {
1115 name: "LC_NUMERIC",
1116 pm_type: flags::SCALAR,
1117 pm_flags: flags::UNSET,
1118 tied_name: None,
1119 },
1120 SpecialParamDef {
1121 name: "LC_TIME",
1122 pm_type: flags::SCALAR,
1123 pm_flags: flags::UNSET,
1124 tied_name: None,
1125 },
1126 SpecialParamDef {
1128 name: "ARGC",
1129 pm_type: flags::INTEGER,
1130 pm_flags: flags::READONLY,
1131 tied_name: None,
1132 },
1133 SpecialParamDef {
1134 name: "HISTCHARS",
1135 pm_type: flags::SCALAR,
1136 pm_flags: flags::DONTIMPORT,
1137 tied_name: None,
1138 },
1139 SpecialParamDef {
1140 name: "status",
1141 pm_type: flags::INTEGER,
1142 pm_flags: flags::READONLY,
1143 tied_name: None,
1144 },
1145 SpecialParamDef {
1146 name: "prompt",
1147 pm_type: flags::SCALAR,
1148 pm_flags: 0,
1149 tied_name: None,
1150 },
1151 SpecialParamDef {
1152 name: "PROMPT",
1153 pm_type: flags::SCALAR,
1154 pm_flags: 0,
1155 tied_name: None,
1156 },
1157 SpecialParamDef {
1158 name: "PROMPT2",
1159 pm_type: flags::SCALAR,
1160 pm_flags: 0,
1161 tied_name: None,
1162 },
1163 SpecialParamDef {
1164 name: "PROMPT3",
1165 pm_type: flags::SCALAR,
1166 pm_flags: 0,
1167 tied_name: None,
1168 },
1169 SpecialParamDef {
1170 name: "PROMPT4",
1171 pm_type: flags::SCALAR,
1172 pm_flags: 0,
1173 tied_name: None,
1174 },
1175 SpecialParamDef {
1176 name: "argv",
1177 pm_type: flags::ARRAY,
1178 pm_flags: 0,
1179 tied_name: None,
1180 },
1181 SpecialParamDef {
1183 name: "pipestatus",
1184 pm_type: flags::ARRAY,
1185 pm_flags: 0,
1186 tied_name: None,
1187 },
1188];
1189
1190pub struct ParamTable {
1195 params: HashMap<String, Param>,
1196 pub local_level: i32,
1197 shtimer_secs: u64,
1198 shtimer_instant: Instant,
1199 seconds_is_float: bool,
1200 pub histchars: [u8; 3],
1202 pub lastval: i64,
1204 pub mypid: i64,
1206 pub lastpid: i64,
1208 pub curhist: i64,
1210 pub lineno: i64,
1212 pub ppid: i64,
1214 pub zsh_subshell: i64,
1216 pub columns: i64,
1218 pub lines: i64,
1220 pub shlvl: i64,
1222 pub funcnest: i64,
1224 pub optind: i64,
1226 pub optarg: String,
1228 pub try_errflag: i64,
1230 pub try_interrupt: i64,
1232 pub rprompt_indent: i64,
1234 pub ifs: String,
1236 pub underscore: String,
1238 pub pparams: Vec<String>,
1240 pub argzero: String,
1242 pub posixzero: String,
1244 pub pipestats: Vec<i32>,
1246 pub prompt: String,
1248 pub prompt2: String,
1249 pub prompt3: String,
1250 pub prompt4: String,
1251 pub rprompt: String,
1252 pub rprompt2: String,
1253 pub sprompt: String,
1254 pub nullcmd: String,
1256 pub readnullcmd: String,
1257 pub postedit: String,
1259 pub wordchars: String,
1261 pub keyboard_hack_char: u8,
1263 pub home: String,
1265 pub term: String,
1267 pub terminfo: String,
1269 pub terminfo_dirs: String,
1271 pub tied: HashMap<String, TiedData>,
1273 pub histsize: i64,
1275 pub savehist: i64,
1277 pub ksh_arrays: bool,
1279 pub posix_argzero: bool,
1281 pub zsh_eval_context: Vec<String>,
1283 random_seed: u32,
1285}
1286
1287impl Default for ParamTable {
1288 fn default() -> Self {
1289 Self::new()
1290 }
1291}
1292
1293impl ParamTable {
1294 pub fn new() -> Self {
1295 let shtimer_secs = SystemTime::now()
1296 .duration_since(UNIX_EPOCH)
1297 .map(|d| d.as_secs())
1298 .unwrap_or(0);
1299
1300 let pid = std::process::id() as i64;
1301 let shlvl = env::var("SHLVL")
1302 .ok()
1303 .and_then(|s| s.parse::<i64>().ok())
1304 .unwrap_or(0)
1305 + 1;
1306
1307 let home = env::var("HOME").unwrap_or_default();
1308 let term = env::var("TERM").unwrap_or_default();
1309 let ifs = " \t\n\0".to_string();
1310
1311 let mut table = ParamTable {
1312 params: HashMap::new(),
1313 local_level: 0,
1314 shtimer_secs,
1315 shtimer_instant: Instant::now(),
1316 seconds_is_float: false,
1317 histchars: [b'!', b'^', b'#'],
1318 lastval: 0,
1319 mypid: pid,
1320 lastpid: 0,
1321 curhist: 0,
1322 lineno: 1,
1323 ppid: 0,
1324 zsh_subshell: 0,
1325 columns: 80,
1326 lines: 24,
1327 shlvl,
1328 funcnest: -1,
1329 optind: 1,
1330 optarg: String::new(),
1331 try_errflag: 0,
1332 try_interrupt: 0,
1333 rprompt_indent: 1,
1334 ifs,
1335 underscore: String::new(),
1336 pparams: Vec::new(),
1337 argzero: String::new(),
1338 posixzero: String::new(),
1339 pipestats: vec![0],
1340 prompt: "%m%# ".to_string(),
1341 prompt2: "%_> ".to_string(),
1342 prompt3: "?# ".to_string(),
1343 prompt4: "+%N:%i> ".to_string(),
1344 rprompt: String::new(),
1345 rprompt2: String::new(),
1346 sprompt: "zsh: correct '%R' to '%r' [nyae]? ".to_string(),
1347 nullcmd: "cat".to_string(),
1348 readnullcmd: "more".to_string(),
1349 postedit: String::new(),
1350 wordchars: "*?_-.[]~=/&;!#$%^(){}<>".to_string(),
1351 keyboard_hack_char: 0,
1352 home: home.clone(),
1353 term,
1354 terminfo: String::new(),
1355 terminfo_dirs: String::new(),
1356 tied: HashMap::new(),
1357 histsize: 30,
1358 savehist: 0,
1359 ksh_arrays: false,
1360 posix_argzero: false,
1361 zsh_eval_context: Vec::new(),
1362 random_seed: std::process::id(),
1363 };
1364
1365 #[cfg(unix)]
1366 {
1367 table.ppid = unsafe { libc::getppid() } as i64;
1368 }
1369
1370 #[cfg(unix)]
1372 {
1373 let mut ws: libc::winsize = unsafe { std::mem::zeroed() };
1374 if unsafe { libc::ioctl(1, libc::TIOCGWINSZ, &mut ws) } == 0 {
1375 if ws.ws_col > 0 {
1376 table.columns = ws.ws_col as i64;
1377 }
1378 if ws.ws_row > 0 {
1379 table.lines = ws.ws_row as i64;
1380 }
1381 }
1382 }
1383
1384 table.init_special_params();
1386
1387 table.init_tied_params();
1389
1390 table.import_environment();
1392
1393 table.init_standard_params();
1395
1396 table
1397 }
1398
1399 fn init_special_params(&mut self) {
1400 for def in SPECIAL_PARAMS {
1402 let pm_flags = def.pm_type | def.pm_flags | flags::SPECIAL;
1403 let value = self.get_special_initial_value(def.name, def.pm_type);
1404 let param = Param {
1405 name: def.name.to_string(),
1406 value,
1407 flags: pm_flags,
1408 base: 10,
1409 width: 0,
1410 level: 0,
1411 ename: def.tied_name.map(|s| s.to_string()),
1412 old: None,
1413 };
1414 self.params.insert(def.name.to_string(), param);
1415 }
1416 }
1417
1418 fn get_special_initial_value(&self, name: &str, pm_type: u32) -> ParamValue {
1419 match name {
1420 "$" => ParamValue::Integer(self.mypid),
1421 "?" | "status" => ParamValue::Integer(self.lastval),
1422 "!" => ParamValue::Integer(self.lastpid),
1423 "#" | "ARGC" => ParamValue::Integer(self.pparams.len() as i64),
1424 "PPID" => ParamValue::Integer(self.ppid),
1425 "LINENO" => ParamValue::Integer(self.lineno),
1426 "HISTCMD" => ParamValue::Integer(self.curhist),
1427 "ZSH_SUBSHELL" => ParamValue::Integer(self.zsh_subshell),
1428 "COLUMNS" => ParamValue::Integer(self.columns),
1429 "LINES" => ParamValue::Integer(self.lines),
1430 "SHLVL" => ParamValue::Integer(self.shlvl),
1431 "FUNCNEST" => ParamValue::Integer(self.funcnest),
1432 "OPTIND" => ParamValue::Integer(self.optind),
1433 "TRY_BLOCK_ERROR" => ParamValue::Integer(self.try_errflag),
1434 "TRY_BLOCK_INTERRUPT" => ParamValue::Integer(self.try_interrupt),
1435 "ZLE_RPROMPT_INDENT" => ParamValue::Integer(self.rprompt_indent),
1436 "RANDOM" => ParamValue::Integer(0),
1437 "SECONDS" => ParamValue::Integer(0),
1438 "HISTSIZE" => ParamValue::Integer(self.histsize),
1439 "SAVEHIST" => ParamValue::Integer(self.savehist),
1440 "ERRNO" => ParamValue::Integer(0),
1441 "TTYIDLE" => ParamValue::Integer(-1),
1442 "UID" => {
1443 #[cfg(unix)]
1444 {
1445 ParamValue::Integer(unsafe { libc::getuid() } as i64)
1446 }
1447 #[cfg(not(unix))]
1448 {
1449 ParamValue::Integer(0)
1450 }
1451 }
1452 "EUID" => {
1453 #[cfg(unix)]
1454 {
1455 ParamValue::Integer(unsafe { libc::geteuid() } as i64)
1456 }
1457 #[cfg(not(unix))]
1458 {
1459 ParamValue::Integer(0)
1460 }
1461 }
1462 "GID" => {
1463 #[cfg(unix)]
1464 {
1465 ParamValue::Integer(unsafe { libc::getgid() } as i64)
1466 }
1467 #[cfg(not(unix))]
1468 {
1469 ParamValue::Integer(0)
1470 }
1471 }
1472 "EGID" => {
1473 #[cfg(unix)]
1474 {
1475 ParamValue::Integer(unsafe { libc::getegid() } as i64)
1476 }
1477 #[cfg(not(unix))]
1478 {
1479 ParamValue::Integer(0)
1480 }
1481 }
1482 "USERNAME" => {
1483 let name = env::var("USER")
1484 .or_else(|_| env::var("LOGNAME"))
1485 .unwrap_or_else(|_| "unknown".to_string());
1486 ParamValue::Scalar(name)
1487 }
1488 "-" => ParamValue::Scalar(String::new()), "histchars" | "HISTCHARS" => {
1490 let s = String::from_utf8_lossy(&self.histchars).to_string();
1491 ParamValue::Scalar(s)
1492 }
1493 "HOME" => ParamValue::Scalar(self.home.clone()),
1494 "TERM" => ParamValue::Scalar(self.term.clone()),
1495 "TERMINFO" => ParamValue::Scalar(self.terminfo.clone()),
1496 "TERMINFO_DIRS" => ParamValue::Scalar(self.terminfo_dirs.clone()),
1497 "WORDCHARS" => ParamValue::Scalar(self.wordchars.clone()),
1498 "IFS" => ParamValue::Scalar(self.ifs.clone()),
1499 "_" => ParamValue::Scalar(self.underscore.clone()),
1500 "KEYBOARD_HACK" => ParamValue::Scalar(String::new()),
1501 "0" => ParamValue::Scalar(self.argzero.clone()),
1502 "OPTARG" => ParamValue::Scalar(self.optarg.clone()),
1503 "NULLCMD" => ParamValue::Scalar(self.nullcmd.clone()),
1504 "READNULLCMD" => ParamValue::Scalar(self.readnullcmd.clone()),
1505 "POSTEDIT" => ParamValue::Scalar(self.postedit.clone()),
1506 "PS1" | "prompt" | "PROMPT" => ParamValue::Scalar(self.prompt.clone()),
1507 "PS2" | "PROMPT2" => ParamValue::Scalar(self.prompt2.clone()),
1508 "PS3" | "PROMPT3" => ParamValue::Scalar(self.prompt3.clone()),
1509 "PS4" | "PROMPT4" => ParamValue::Scalar(self.prompt4.clone()),
1510 "RPS1" | "RPROMPT" => ParamValue::Scalar(self.rprompt.clone()),
1511 "RPS2" | "RPROMPT2" => ParamValue::Scalar(self.rprompt2.clone()),
1512 "SPROMPT" => ParamValue::Scalar(self.sprompt.clone()),
1513 "*" | "@" | "argv" => ParamValue::Array(self.pparams.clone()),
1514 "pipestatus" => {
1515 ParamValue::Array(self.pipestats.iter().map(|s| s.to_string()).collect())
1516 }
1517 "CDPATH" | "FIGNORE" | "FPATH" | "MAILPATH" | "PATH" | "PSVAR" | "ZSH_EVAL_CONTEXT"
1519 | "MODULE_PATH" | "MANPATH" => {
1520 let env_val = env::var(name).unwrap_or_default();
1521 ParamValue::Scalar(env_val)
1522 }
1523 "LANG" | "LC_ALL" | "LC_COLLATE" | "LC_CTYPE" | "LC_MESSAGES" | "LC_NUMERIC"
1525 | "LC_TIME" => {
1526 let env_val = env::var(name).unwrap_or_default();
1527 ParamValue::Scalar(env_val)
1528 }
1529 _ => {
1530 if pm_type == flags::INTEGER {
1531 ParamValue::Integer(0)
1532 } else if pm_type == flags::ARRAY {
1533 ParamValue::Array(Vec::new())
1534 } else {
1535 ParamValue::Scalar(String::new())
1536 }
1537 }
1538 }
1539 }
1540
1541 fn init_tied_params(&mut self) {
1542 let pairs: &[(&str, &str)] = &[
1544 ("CDPATH", "cdpath"),
1545 ("FIGNORE", "fignore"),
1546 ("FPATH", "fpath"),
1547 ("MAILPATH", "mailpath"),
1548 ("PATH", "path"),
1549 ("PSVAR", "psvar"),
1550 ("ZSH_EVAL_CONTEXT", "zsh_eval_context"),
1551 ("MODULE_PATH", "module_path"),
1552 ("MANPATH", "manpath"),
1553 ];
1554
1555 for (scalar, array) in pairs {
1556 let val = env::var(scalar).unwrap_or_default();
1557 let arr: Vec<String> = val
1558 .split(':')
1559 .filter(|s| !s.is_empty())
1560 .map(String::from)
1561 .collect();
1562
1563 let arr_flags = flags::ARRAY | flags::SPECIAL | flags::TIED;
1565 let arr_param = Param {
1566 name: array.to_string(),
1567 value: ParamValue::Array(arr),
1568 flags: arr_flags,
1569 base: 10,
1570 width: 0,
1571 level: 0,
1572 ename: Some(scalar.to_string()),
1573 old: None,
1574 };
1575 self.params.insert(array.to_string(), arr_param);
1576
1577 if let Some(p) = self.params.get_mut(*scalar) {
1579 p.flags |= flags::TIED;
1580 p.ename = Some(array.to_string());
1581 }
1582
1583 self.tied.insert(
1584 scalar.to_string(),
1585 TiedData {
1586 join_char: ':',
1587 scalar_name: scalar.to_string(),
1588 array_name: array.to_string(),
1589 },
1590 );
1591 }
1592 }
1593
1594 fn import_environment(&mut self) {
1595 for (key, value) in env::vars() {
1596 if !self.params.contains_key(&key) && isident(&key) {
1597 let mut param = Param::new_scalar(&key, &value);
1598 param.flags |= flags::EXPORT;
1599 self.params.insert(key, param);
1600 }
1601 }
1602 }
1603
1604 fn init_standard_params(&mut self) {
1605 let hostname = {
1607 #[cfg(unix)]
1608 {
1609 let mut buf = [0u8; 256];
1610 let ptr = buf.as_mut_ptr() as *mut libc::c_char;
1611 if unsafe { libc::gethostname(ptr, 256) } == 0 {
1612 let cstr = unsafe { std::ffi::CStr::from_ptr(ptr) };
1613 cstr.to_string_lossy().to_string()
1614 } else {
1615 "unknown".to_string()
1616 }
1617 }
1618 #[cfg(not(unix))]
1619 {
1620 "unknown".to_string()
1621 }
1622 };
1623 self.set_scalar_internal("HOST", &hostname, 0);
1624
1625 let logname = env::var("LOGNAME")
1627 .or_else(|_| env::var("USER"))
1628 .unwrap_or_else(|_| "unknown".to_string());
1629 self.set_scalar_internal("LOGNAME", &logname, 0);
1630
1631 self.set_scalar_internal("MACHTYPE", std::env::consts::ARCH, 0);
1633 self.set_scalar_internal("OSTYPE", std::env::consts::OS, 0);
1634 self.set_scalar_internal("VENDOR", "unknown", 0);
1635
1636 #[cfg(unix)]
1638 {
1639 let tty = unsafe {
1640 let ptr = libc::ttyname(0);
1641 if ptr.is_null() {
1642 String::new()
1643 } else {
1644 std::ffi::CStr::from_ptr(ptr).to_string_lossy().to_string()
1645 }
1646 };
1647 self.set_scalar_internal("TTY", &tty, 0);
1648 }
1649
1650 self.set_scalar_internal("ZSH_VERSION", "5.9", 0);
1652 self.set_scalar_internal("ZSH_PATCHLEVEL", "zshrs", 0);
1653
1654 self.set_integer_internal("MAILCHECK", 60, 0);
1656 self.set_integer_internal("KEYTIMEOUT", 40, 0);
1657 self.set_integer_internal("LISTMAX", 100, 0);
1658 self.set_scalar_internal("TMPPREFIX", "/tmp/zsh", 0);
1659 self.set_scalar_internal("TIMEFMT", "%J %U user %S system %P cpu %*E total", 0);
1660
1661 #[cfg(unix)]
1663 {
1664 let sigs = vec![
1665 "EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT", "FPE", "KILL", "BUS",
1666 "SEGV", "SYS", "PIPE", "ALRM", "TERM", "URG", "STOP", "TSTP", "CONT", "CHLD",
1667 "TTIN", "TTOU", "IO", "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "INFO", "USR1",
1668 "USR2",
1669 ];
1670 let sig_arr: Vec<String> = sigs.iter().map(|s| format!("SIG{}", s)).collect();
1671 self.set_array_internal("signals", sig_arr, flags::READONLY);
1672 }
1673 }
1674
1675 fn set_scalar_internal(&mut self, name: &str, value: &str, extra_flags: u32) {
1676 if !self.params.contains_key(name) {
1677 let mut param = Param::new_scalar(name, value);
1678 param.flags |= extra_flags;
1679 self.params.insert(name.to_string(), param);
1680 }
1681 }
1682
1683 fn set_integer_internal(&mut self, name: &str, value: i64, extra_flags: u32) {
1684 if !self.params.contains_key(name) {
1685 let mut param = Param::new_integer(name, value);
1686 param.flags |= extra_flags;
1687 self.params.insert(name.to_string(), param);
1688 }
1689 }
1690
1691 fn set_array_internal(&mut self, name: &str, value: Vec<String>, extra_flags: u32) {
1692 if !self.params.contains_key(name) {
1693 let mut param = Param::new_array(name, value);
1694 param.flags |= extra_flags;
1695 self.params.insert(name.to_string(), param);
1696 }
1697 }
1698
1699 fn get_special_value(&self, name: &str) -> Option<ParamValue> {
1706 match name {
1707 "$" => Some(ParamValue::Integer(self.mypid)),
1708 "?" | "status" => Some(ParamValue::Integer(self.lastval)),
1709 "!" => Some(ParamValue::Integer(self.lastpid)),
1710 "#" | "ARGC" => Some(ParamValue::Integer(self.pparams.len() as i64)),
1711 "PPID" => Some(ParamValue::Integer(self.ppid)),
1712 "LINENO" => Some(ParamValue::Integer(self.lineno)),
1713 "HISTCMD" => Some(ParamValue::Integer(self.curhist)),
1714 "ZSH_SUBSHELL" => Some(ParamValue::Integer(self.zsh_subshell)),
1715 "COLUMNS" => Some(ParamValue::Integer(self.columns)),
1716 "LINES" => Some(ParamValue::Integer(self.lines)),
1717 "SHLVL" => Some(ParamValue::Integer(self.shlvl)),
1718 "FUNCNEST" => Some(ParamValue::Integer(self.funcnest)),
1719 "OPTIND" => Some(ParamValue::Integer(self.optind)),
1720 "TRY_BLOCK_ERROR" => Some(ParamValue::Integer(self.try_errflag)),
1721 "TRY_BLOCK_INTERRUPT" => Some(ParamValue::Integer(self.try_interrupt)),
1722 "ZLE_RPROMPT_INDENT" => Some(ParamValue::Integer(self.rprompt_indent)),
1723 "HISTSIZE" => Some(ParamValue::Integer(self.histsize)),
1724 "SAVEHIST" => Some(ParamValue::Integer(self.savehist)),
1725 "RANDOM" => Some(ParamValue::Integer(self.get_random())),
1726 "SECONDS" => {
1727 if self.seconds_is_float {
1728 Some(ParamValue::Float(self.get_seconds_float()))
1729 } else {
1730 Some(ParamValue::Integer(self.get_seconds_int()))
1731 }
1732 }
1733 "ERRNO" => {
1734 #[cfg(unix)]
1735 {
1736 Some(ParamValue::Integer(
1737 std::io::Error::last_os_error().raw_os_error().unwrap_or(0) as i64,
1738 ))
1739 }
1740 #[cfg(not(unix))]
1741 {
1742 Some(ParamValue::Integer(0))
1743 }
1744 }
1745 "TTYIDLE" => Some(ParamValue::Integer(self.get_tty_idle())),
1746 "UID" => {
1747 #[cfg(unix)]
1748 {
1749 Some(ParamValue::Integer(unsafe { libc::getuid() } as i64))
1750 }
1751 #[cfg(not(unix))]
1752 {
1753 Some(ParamValue::Integer(0))
1754 }
1755 }
1756 "EUID" => {
1757 #[cfg(unix)]
1758 {
1759 Some(ParamValue::Integer(unsafe { libc::geteuid() } as i64))
1760 }
1761 #[cfg(not(unix))]
1762 {
1763 Some(ParamValue::Integer(0))
1764 }
1765 }
1766 "GID" => {
1767 #[cfg(unix)]
1768 {
1769 Some(ParamValue::Integer(unsafe { libc::getgid() } as i64))
1770 }
1771 #[cfg(not(unix))]
1772 {
1773 Some(ParamValue::Integer(0))
1774 }
1775 }
1776 "EGID" => {
1777 #[cfg(unix)]
1778 {
1779 Some(ParamValue::Integer(unsafe { libc::getegid() } as i64))
1780 }
1781 #[cfg(not(unix))]
1782 {
1783 Some(ParamValue::Integer(0))
1784 }
1785 }
1786 "USERNAME" => {
1787 let name = env::var("USER")
1788 .or_else(|_| env::var("LOGNAME"))
1789 .unwrap_or_else(|_| "unknown".to_string());
1790 Some(ParamValue::Scalar(name))
1791 }
1792 "-" => {
1793 Some(ParamValue::Scalar(String::new()))
1795 }
1796 "histchars" | "HISTCHARS" => {
1797 let s = String::from_utf8_lossy(&self.histchars).to_string();
1798 Some(ParamValue::Scalar(s))
1799 }
1800 "IFS" => Some(ParamValue::Scalar(self.ifs.clone())),
1801 "_" => Some(ParamValue::Scalar(self.underscore.clone())),
1802 "KEYBOARD_HACK" => {
1803 let s = if self.keyboard_hack_char != 0 {
1804 String::from(self.keyboard_hack_char as char)
1805 } else {
1806 String::new()
1807 };
1808 Some(ParamValue::Scalar(s))
1809 }
1810 "HOME" => Some(ParamValue::Scalar(self.home.clone())),
1811 "WORDCHARS" => Some(ParamValue::Scalar(self.wordchars.clone())),
1812 "TERM" => Some(ParamValue::Scalar(self.term.clone())),
1813 "TERMINFO" => Some(ParamValue::Scalar(self.terminfo.clone())),
1814 "TERMINFO_DIRS" => Some(ParamValue::Scalar(self.terminfo_dirs.clone())),
1815 "0" => {
1816 if self.posix_argzero {
1817 Some(ParamValue::Scalar(self.posixzero.clone()))
1818 } else {
1819 Some(ParamValue::Scalar(self.argzero.clone()))
1820 }
1821 }
1822 "OPTARG" => Some(ParamValue::Scalar(self.optarg.clone())),
1823 "NULLCMD" => Some(ParamValue::Scalar(self.nullcmd.clone())),
1824 "READNULLCMD" => Some(ParamValue::Scalar(self.readnullcmd.clone())),
1825 "POSTEDIT" => Some(ParamValue::Scalar(self.postedit.clone())),
1826 "PS1" | "prompt" | "PROMPT" => Some(ParamValue::Scalar(self.prompt.clone())),
1827 "PS2" | "PROMPT2" => Some(ParamValue::Scalar(self.prompt2.clone())),
1828 "PS3" | "PROMPT3" => Some(ParamValue::Scalar(self.prompt3.clone())),
1829 "PS4" | "PROMPT4" => Some(ParamValue::Scalar(self.prompt4.clone())),
1830 "RPS1" | "RPROMPT" => Some(ParamValue::Scalar(self.rprompt.clone())),
1831 "RPS2" | "RPROMPT2" => Some(ParamValue::Scalar(self.rprompt2.clone())),
1832 "SPROMPT" => Some(ParamValue::Scalar(self.sprompt.clone())),
1833 "*" | "@" | "argv" => Some(ParamValue::Array(self.pparams.clone())),
1834 "pipestatus" => Some(ParamValue::Array(
1835 self.pipestats.iter().map(|s| s.to_string()).collect(),
1836 )),
1837 _ => None,
1838 }
1839 }
1840
1841 fn handle_special_set(&mut self, name: &str, value: &ParamValue) {
1843 match name {
1844 "RANDOM" => {
1845 if let ParamValue::Integer(v) = value {
1846 self.random_seed = *v as u32;
1847 }
1849 }
1850 "SECONDS" => match value {
1851 ParamValue::Integer(x) => {
1852 let now = Instant::now();
1853 self.shtimer_instant = now - std::time::Duration::from_secs(*x as u64);
1854 self.seconds_is_float = false;
1855 }
1856 ParamValue::Float(x) => {
1857 let now = Instant::now();
1858 self.shtimer_instant = now - std::time::Duration::from_secs_f64(*x);
1859 self.seconds_is_float = true;
1860 }
1861 _ => {}
1862 },
1863 "HISTSIZE" => {
1864 if let ParamValue::Integer(v) = value {
1865 self.histsize = (*v).max(1);
1866 }
1867 }
1868 "SAVEHIST" => {
1869 if let ParamValue::Integer(v) = value {
1870 self.savehist = (*v).max(0);
1871 }
1872 }
1873 "COLUMNS" => {
1874 if let ParamValue::Integer(v) = value {
1875 self.columns = *v;
1876 }
1877 }
1878 "LINES" => {
1879 if let ParamValue::Integer(v) = value {
1880 self.lines = *v;
1881 }
1882 }
1883 "SHLVL" => {
1884 if let ParamValue::Integer(v) = value {
1885 self.shlvl = *v;
1886 }
1887 }
1888 "FUNCNEST" => {
1889 if let ParamValue::Integer(v) = value {
1890 self.funcnest = *v;
1891 }
1892 }
1893 "OPTIND" => {
1894 if let ParamValue::Integer(v) = value {
1895 self.optind = *v;
1896 }
1897 }
1898 "TRY_BLOCK_ERROR" => {
1899 if let ParamValue::Integer(v) = value {
1900 self.try_errflag = *v;
1901 }
1902 }
1903 "TRY_BLOCK_INTERRUPT" => {
1904 if let ParamValue::Integer(v) = value {
1905 self.try_interrupt = *v;
1906 }
1907 }
1908 "ZLE_RPROMPT_INDENT" => {
1909 if let ParamValue::Integer(v) = value {
1910 self.rprompt_indent = *v;
1911 }
1912 }
1913 "IFS" => {
1914 self.ifs = value.as_string();
1915 }
1916 "HOME" => {
1917 self.home = value.as_string();
1918 }
1919 "TERM" => {
1920 self.term = value.as_string();
1921 }
1922 "TERMINFO" => {
1923 self.terminfo = value.as_string();
1924 }
1925 "TERMINFO_DIRS" => {
1926 self.terminfo_dirs = value.as_string();
1927 }
1928 "WORDCHARS" => {
1929 self.wordchars = value.as_string();
1930 }
1931 "KEYBOARD_HACK" => {
1932 let s = value.as_string();
1933 self.keyboard_hack_char = s.as_bytes().first().copied().unwrap_or(0);
1934 }
1935 "histchars" | "HISTCHARS" => {
1936 let s = value.as_string();
1937 let bytes = s.as_bytes();
1938 self.histchars[0] = bytes.first().copied().unwrap_or(b'!');
1939 self.histchars[1] = bytes.get(1).copied().unwrap_or(b'^');
1940 self.histchars[2] = bytes.get(2).copied().unwrap_or(b'#');
1941 }
1942 "0" => {
1943 if !self.posix_argzero {
1944 self.argzero = value.as_string();
1945 }
1946 }
1947 "OPTARG" => {
1948 self.optarg = value.as_string();
1949 }
1950 "NULLCMD" => {
1951 self.nullcmd = value.as_string();
1952 }
1953 "READNULLCMD" => {
1954 self.readnullcmd = value.as_string();
1955 }
1956 "POSTEDIT" => {
1957 self.postedit = value.as_string();
1958 }
1959 "PS1" | "prompt" | "PROMPT" => {
1960 self.prompt = value.as_string();
1961 }
1962 "PS2" | "PROMPT2" => {
1963 self.prompt2 = value.as_string();
1964 }
1965 "PS3" | "PROMPT3" => {
1966 self.prompt3 = value.as_string();
1967 }
1968 "PS4" | "PROMPT4" => {
1969 self.prompt4 = value.as_string();
1970 }
1971 "RPS1" | "RPROMPT" => {
1972 self.rprompt = value.as_string();
1973 }
1974 "RPS2" | "RPROMPT2" => {
1975 self.rprompt2 = value.as_string();
1976 }
1977 "SPROMPT" => {
1978 self.sprompt = value.as_string();
1979 }
1980 "pipestatus" => {
1981 if let ParamValue::Array(arr) = value {
1982 self.pipestats = arr.iter().map(|s| s.parse::<i32>().unwrap_or(0)).collect();
1983 }
1984 }
1985 #[cfg(unix)]
1986 "UID" => {
1987 if let ParamValue::Integer(v) = value {
1988 unsafe {
1989 libc::setuid(*v as libc::uid_t);
1990 }
1991 }
1992 }
1993 #[cfg(unix)]
1994 "EUID" => {
1995 if let ParamValue::Integer(v) = value {
1996 unsafe {
1997 libc::seteuid(*v as libc::uid_t);
1998 }
1999 }
2000 }
2001 #[cfg(unix)]
2002 "GID" => {
2003 if let ParamValue::Integer(v) = value {
2004 unsafe {
2005 libc::setgid(*v as libc::gid_t);
2006 }
2007 }
2008 }
2009 #[cfg(unix)]
2010 "EGID" => {
2011 if let ParamValue::Integer(v) = value {
2012 unsafe {
2013 libc::setegid(*v as libc::gid_t);
2014 }
2015 }
2016 }
2017 _ => {}
2018 }
2019
2020 if let Some(tied) = self.tied.get(name).cloned() {
2022 if name == tied.scalar_name {
2023 let arr: Vec<String> = value
2025 .as_string()
2026 .split(tied.join_char)
2027 .filter(|s| !s.is_empty())
2028 .map(String::from)
2029 .collect();
2030 if let Some(p) = self.params.get_mut(&tied.array_name) {
2031 p.value = ParamValue::Array(arr);
2032 }
2033 } else if name == tied.array_name {
2034 let s = value.as_array().join(&tied.join_char.to_string());
2036 if let Some(p) = self.params.get_mut(&tied.scalar_name) {
2037 p.value = ParamValue::Scalar(s.clone());
2038 }
2039 if let Some(p) = self.params.get(&tied.scalar_name) {
2041 if p.is_exported() {
2042 env::set_var(&tied.scalar_name, &s);
2043 }
2044 }
2045 }
2046 }
2047 }
2048
2049 pub fn get_random(&self) -> i64 {
2054 static COUNTER: AtomicI64 = AtomicI64::new(0);
2056 let n = COUNTER.fetch_add(1, Ordering::Relaxed);
2057 let seed = self.random_seed as i64;
2058 let val = (seed
2060 .wrapping_mul(1103515245)
2061 .wrapping_add(12345)
2062 .wrapping_add(n))
2063 & 0x7fffffff;
2064 (val >> 16) & 0x7fff
2065 }
2066
2067 pub fn get_seconds_int(&self) -> i64 {
2068 self.shtimer_instant.elapsed().as_secs() as i64
2069 }
2070
2071 pub fn get_seconds_float(&self) -> f64 {
2072 self.shtimer_instant.elapsed().as_secs_f64()
2073 }
2074
2075 pub fn get_seconds(&self) -> f64 {
2077 self.get_seconds_float()
2078 }
2079
2080 pub fn set_seconds_type(&mut self, is_float: bool) {
2081 self.seconds_is_float = is_float;
2082 }
2083
2084 fn get_tty_idle(&self) -> i64 {
2085 #[cfg(unix)]
2086 {
2087 use std::os::unix::io::AsRawFd;
2088 let fd = std::io::stdin().as_raw_fd();
2089 let mut stat: libc::stat = unsafe { std::mem::zeroed() };
2090 if unsafe { libc::fstat(fd, &mut stat) } == 0 {
2091 let now = unsafe { libc::time(std::ptr::null_mut()) };
2092 return now as i64 - stat.st_atime as i64;
2093 }
2094 }
2095 -1
2096 }
2097
2098 pub fn get(&self, name: &str) -> Option<&ParamValue> {
2104 let resolved = self.resolve_nameref_name(name);
2106 let lookup = resolved.as_deref().unwrap_or(name);
2107
2108 self.params.get(lookup).map(|p| &p.value)
2109 }
2110
2111 pub fn get_value(&self, name: &str) -> Option<ParamValue> {
2113 let resolved = self.resolve_nameref_name(name);
2114 let lookup = resolved.as_deref().unwrap_or(name);
2115
2116 if let Some(p) = self.params.get(lookup) {
2118 if p.is_special() {
2119 if let Some(val) = self.get_special_value(lookup) {
2120 return Some(val);
2121 }
2122 }
2123 if !p.is_unset() {
2124 return Some(p.value.clone());
2125 }
2126 }
2127 None
2128 }
2129
2130 pub fn get_param(&self, name: &str) -> Option<&Param> {
2132 self.params.get(name)
2133 }
2134
2135 pub fn get_param_mut(&mut self, name: &str) -> Option<&mut Param> {
2137 self.params.get_mut(name)
2138 }
2139
2140 fn resolve_nameref_name(&self, name: &str) -> Option<String> {
2142 let param = self.params.get(name)?;
2143 if !param.is_nameref() || param.is_unset() {
2144 return None;
2145 }
2146 let target = param.value.as_string();
2147 if target.is_empty() || target == name {
2148 return None;
2149 }
2150 let mut visited = HashSet::new();
2152 visited.insert(name.to_string());
2153 let mut current = target;
2154 loop {
2155 if visited.contains(¤t) {
2156 return None; }
2158 visited.insert(current.clone());
2159 if let Some(p) = self.params.get(¤t) {
2160 if p.is_nameref() && !p.is_unset() {
2161 let next = p.value.as_string();
2162 if next.is_empty() {
2163 return Some(current);
2164 }
2165 current = next;
2166 } else {
2167 return Some(current);
2168 }
2169 } else {
2170 return Some(current);
2171 }
2172 }
2173 }
2174
2175 pub fn set_scalar(&mut self, name: &str, value: &str) -> bool {
2177 let resolved = self
2178 .resolve_nameref_name(name)
2179 .unwrap_or_else(|| name.to_string());
2180 let name = &resolved;
2181
2182 if let Some(param) = self.params.get_mut(name) {
2183 if param.is_readonly() {
2184 return false;
2185 }
2186 let value = if (param.flags & flags::LOWER) != 0 {
2187 value.to_lowercase()
2188 } else if (param.flags & flags::UPPER) != 0 {
2189 value.to_uppercase()
2190 } else {
2191 value.to_string()
2192 };
2193 let pv = ParamValue::Scalar(value);
2194 param.value = pv.clone();
2195 param.flags &= !flags::UNSET;
2196
2197 if param.is_exported() {
2198 env::set_var(name, param.value.as_string());
2199 }
2200
2201 self.handle_special_set(name, &pv);
2202 true
2203 } else {
2204 let param = Param::new_scalar(name, value);
2205 let pv = param.value.clone();
2206 self.params.insert(name.to_string(), param);
2207 self.handle_special_set(name, &pv);
2208 true
2209 }
2210 }
2211
2212 pub fn set_integer(&mut self, name: &str, value: i64) -> bool {
2214 let resolved = self
2215 .resolve_nameref_name(name)
2216 .unwrap_or_else(|| name.to_string());
2217 let name = &resolved;
2218
2219 if let Some(param) = self.params.get_mut(name) {
2220 if param.is_readonly() {
2221 return false;
2222 }
2223 let pv = ParamValue::Integer(value);
2224 param.value = pv.clone();
2225 param.flags &= !flags::UNSET;
2226 if param.is_exported() {
2227 env::set_var(name, value.to_string());
2228 }
2229 self.handle_special_set(name, &pv);
2230 true
2231 } else {
2232 let param = Param::new_integer(name, value);
2233 let pv = param.value.clone();
2234 self.params.insert(name.to_string(), param);
2235 self.handle_special_set(name, &pv);
2236 true
2237 }
2238 }
2239
2240 pub fn set_float(&mut self, name: &str, value: f64) -> bool {
2242 let resolved = self
2243 .resolve_nameref_name(name)
2244 .unwrap_or_else(|| name.to_string());
2245 let name = &resolved;
2246
2247 if let Some(param) = self.params.get_mut(name) {
2248 if param.is_readonly() {
2249 return false;
2250 }
2251 let pv = ParamValue::Float(value);
2252 param.value = pv.clone();
2253 param.flags &= !flags::UNSET;
2254 self.handle_special_set(name, &pv);
2255 true
2256 } else {
2257 let param = Param::new_float(name, value);
2258 let pv = param.value.clone();
2259 self.params.insert(name.to_string(), param);
2260 self.handle_special_set(name, &pv);
2261 true
2262 }
2263 }
2264
2265 pub fn set_array(&mut self, name: &str, value: Vec<String>) -> bool {
2267 let resolved = self
2268 .resolve_nameref_name(name)
2269 .unwrap_or_else(|| name.to_string());
2270 let name = &resolved;
2271
2272 if let Some(param) = self.params.get_mut(name) {
2273 if param.is_readonly() {
2274 return false;
2275 }
2276 let value = if param.is_unique() {
2277 uniq_array(value)
2278 } else {
2279 value
2280 };
2281 let pv = ParamValue::Array(value);
2282 param.value = pv.clone();
2283 param.flags &= !flags::UNSET;
2284 self.handle_special_set(name, &pv);
2285 true
2286 } else {
2287 let param = Param::new_array(name, value);
2288 let pv = param.value.clone();
2289 self.params.insert(name.to_string(), param);
2290 self.handle_special_set(name, &pv);
2291 true
2292 }
2293 }
2294
2295 pub fn set_assoc(&mut self, name: &str, value: HashMap<String, String>) -> bool {
2297 let resolved = self
2298 .resolve_nameref_name(name)
2299 .unwrap_or_else(|| name.to_string());
2300 let name = &resolved;
2301
2302 if let Some(param) = self.params.get_mut(name) {
2303 if param.is_readonly() {
2304 return false;
2305 }
2306 param.value = ParamValue::Assoc(value);
2307 param.flags &= !flags::UNSET;
2308 true
2309 } else {
2310 let param = Param::new_assoc(name, value);
2311 self.params.insert(name.to_string(), param);
2312 true
2313 }
2314 }
2315
2316 pub fn set_numeric(&mut self, name: &str, val: MNumber) -> bool {
2318 match val {
2319 MNumber::Integer(i) => self.set_integer(name, i),
2320 MNumber::Float(f) => self.set_float(name, f),
2321 }
2322 }
2323
2324 pub fn augment_scalar(&mut self, name: &str, value: &str) -> bool {
2326 if let Some(current) = self.get_value(name) {
2327 let new_val = format!("{}{}", current.as_string(), value);
2328 self.set_scalar(name, &new_val)
2329 } else {
2330 self.set_scalar(name, value)
2331 }
2332 }
2333
2334 pub fn augment_array(&mut self, name: &str, value: Vec<String>) -> bool {
2336 if let Some(current) = self.get_value(name) {
2337 let mut arr = current.as_array();
2338 arr.extend(value);
2339 self.set_array(name, arr)
2340 } else {
2341 self.set_array(name, value)
2342 }
2343 }
2344
2345 pub fn augment_integer(&mut self, name: &str, value: i64) -> bool {
2347 let current = self.get_value(name).map(|v| v.as_integer()).unwrap_or(0);
2348 self.set_integer(name, current + value)
2349 }
2350
2351 pub fn unset(&mut self, name: &str) -> bool {
2353 if let Some(param) = self.params.get(name) {
2354 if param.is_readonly() {
2355 return false;
2356 }
2357 }
2358
2359 if let Some(tied) = self.tied.get(name).cloned() {
2361 if name == tied.scalar_name {
2362 if let Some(p) = self.params.get_mut(&tied.array_name) {
2363 p.flags |= flags::UNSET;
2364 p.value = ParamValue::Array(Vec::new());
2365 }
2366 } else if name == tied.array_name {
2367 if let Some(p) = self.params.get_mut(&tied.scalar_name) {
2368 p.flags |= flags::UNSET;
2369 p.value = ParamValue::Scalar(String::new());
2370 }
2371 }
2372 }
2373
2374 if let Some(param) = self.params.get(name) {
2376 if param.is_special() {
2377 if let Some(p) = self.params.get_mut(name) {
2378 p.flags |= flags::UNSET;
2379 }
2380 return true;
2381 }
2382 }
2383
2384 if let Some(param) = self.params.get(name) {
2386 if param.level > 0 && param.level <= self.local_level {
2387 if let Some(p) = self.params.get_mut(name) {
2388 p.flags |= flags::UNSET;
2389 }
2390 return true;
2391 }
2392 }
2393
2394 env::remove_var(name);
2395
2396 let old = self.params.get(name).and_then(|p| p.old.clone());
2398 if let Some(old_param) = old {
2399 self.params.insert(name.to_string(), *old_param);
2400 if let Some(p) = self.params.get(name) {
2402 if p.is_exported() {
2403 env::set_var(name, p.value.as_string());
2404 }
2405 }
2406 } else {
2407 self.params.remove(name);
2408 }
2409 true
2410 }
2411
2412 pub fn export(&mut self, name: &str) -> bool {
2414 if let Some(param) = self.params.get_mut(name) {
2415 param.flags |= flags::EXPORT;
2416 env::set_var(name, param.value.as_string());
2417 true
2418 } else {
2419 false
2420 }
2421 }
2422
2423 pub fn unexport(&mut self, name: &str) {
2425 if let Some(param) = self.params.get_mut(name) {
2426 param.flags &= !flags::EXPORT;
2427 env::remove_var(name);
2428 }
2429 }
2430
2431 pub fn set_readonly(&mut self, name: &str) -> bool {
2433 if let Some(param) = self.params.get_mut(name) {
2434 param.flags |= flags::READONLY;
2435 true
2436 } else {
2437 false
2438 }
2439 }
2440
2441 pub fn push_scope(&mut self) {
2447 self.local_level += 1;
2448 }
2449
2450 pub fn pop_scope(&mut self) {
2452 let level = self.local_level;
2453 let names_to_check: Vec<String> = self.params.keys().cloned().collect();
2454
2455 for name in names_to_check {
2456 let should_remove = {
2457 if let Some(param) = self.params.get(&name) {
2458 param.level > level - 1
2459 } else {
2460 false
2461 }
2462 };
2463
2464 if should_remove {
2465 let is_special = self
2466 .params
2467 .get(&name)
2468 .map(|p| p.is_special())
2469 .unwrap_or(false);
2470
2471 if is_special {
2472 let old = self.params.get(&name).and_then(|p| p.old.clone());
2474 if let Some(old_param) = old {
2475 let old_value = old_param.value.clone();
2476 if let Some(p) = self.params.get_mut(&name) {
2477 p.flags = old_param.flags;
2478 p.level = old_param.level;
2479 p.base = old_param.base;
2480 p.width = old_param.width;
2481 p.old = old_param.old;
2482 if (old_param.flags & flags::NORESTORE) == 0 {
2483 p.value = old_value.clone();
2484 self.handle_special_set(&name, &old_value);
2485 }
2486 }
2487 }
2488 } else {
2489 let old = self.params.get(&name).and_then(|p| p.old.clone());
2491 if let Some(old_param) = old {
2492 self.params.insert(name.clone(), *old_param);
2493 if let Some(p) = self.params.get(&name) {
2494 if p.is_exported() {
2495 env::set_var(&name, p.value.as_string());
2496 }
2497 }
2498 } else {
2499 self.params.remove(&name);
2500 }
2501 }
2502 }
2503 }
2504
2505 self.local_level -= 1;
2506 }
2507
2508 pub fn make_local(&mut self, name: &str) {
2510 if let Some(param) = self.params.get(name) {
2511 if param.level == self.local_level {
2512 return;
2514 }
2515 let old = Box::new(param.clone());
2517 let mut new_param = Param {
2518 name: name.to_string(),
2519 value: ParamValue::Unset,
2520 flags: flags::SCALAR | flags::LOCAL | flags::UNSET,
2521 base: 10,
2522 width: 0,
2523 level: self.local_level,
2524 ename: None,
2525 old: Some(old),
2526 };
2527
2528 if param.is_special() {
2530 new_param.flags |= flags::SPECIAL;
2531 new_param.value = param.value.clone();
2532 new_param.flags &= !flags::UNSET;
2533 }
2534
2535 self.params.insert(name.to_string(), new_param);
2536 } else {
2537 let param = Param {
2539 name: name.to_string(),
2540 value: ParamValue::Unset,
2541 flags: flags::SCALAR | flags::LOCAL | flags::UNSET,
2542 base: 10,
2543 width: 0,
2544 level: self.local_level,
2545 ename: None,
2546 old: None,
2547 };
2548 self.params.insert(name.to_string(), param);
2549 }
2550 }
2551
2552 pub fn make_local_typed(&mut self, name: &str, pm_flags: u32) {
2554 self.make_local(name);
2555 if let Some(param) = self.params.get_mut(name) {
2556 param.flags =
2558 (param.flags & (flags::LOCAL | flags::SPECIAL | flags::EXPORT)) | pm_flags;
2559 param.value = match flags::pm_type(pm_flags) {
2561 flags::INTEGER => ParamValue::Integer(0),
2562 flags::EFLOAT | flags::FFLOAT => ParamValue::Float(0.0),
2563 flags::ARRAY => ParamValue::Array(Vec::new()),
2564 flags::HASHED => ParamValue::Assoc(HashMap::new()),
2565 _ => ParamValue::Scalar(String::new()),
2566 };
2567 param.flags &= !flags::UNSET;
2568 }
2569 }
2570
2571 pub fn createparam(&mut self, name: &str, pm_flags: u32) -> bool {
2577 if !isident(name) {
2578 return false;
2579 }
2580
2581 if let Some(existing) = self.params.get(name) {
2582 if existing.level == self.local_level {
2583 if !existing.is_unset() && !existing.is_special() {
2584 if let Some(p) = self.params.get_mut(name) {
2586 p.flags &= !flags::UNSET;
2587 }
2588 return false;
2589 }
2590 }
2591 }
2592
2593 let value = match flags::pm_type(pm_flags) {
2594 flags::INTEGER => ParamValue::Integer(0),
2595 flags::EFLOAT | flags::FFLOAT => ParamValue::Float(0.0),
2596 flags::ARRAY => ParamValue::Array(Vec::new()),
2597 flags::HASHED => ParamValue::Assoc(HashMap::new()),
2598 flags::NAMEREF => ParamValue::Scalar(String::new()),
2599 _ => ParamValue::Scalar(String::new()),
2600 };
2601
2602 let old = self.params.get(name).cloned().map(Box::new);
2603 let param = Param {
2604 name: name.to_string(),
2605 value,
2606 flags: pm_flags & !flags::LOCAL,
2607 base: 10,
2608 width: 0,
2609 level: if (pm_flags & flags::LOCAL) != 0 {
2610 self.local_level
2611 } else {
2612 0
2613 },
2614 ename: None,
2615 old,
2616 };
2617 self.params.insert(name.to_string(), param);
2618 true
2619 }
2620
2621 pub fn resetparam(&mut self, name: &str, new_flags: u32) -> bool {
2623 if let Some(param) = self.params.get(name) {
2624 if param.is_readonly() {
2625 return false;
2626 }
2627 }
2628 let exported = self
2630 .params
2631 .get(name)
2632 .map(|p| p.flags & flags::EXPORT)
2633 .unwrap_or(0);
2634 self.unset(name);
2635 self.createparam(name, new_flags | exported);
2636 true
2637 }
2638
2639 pub fn set_nameref(&mut self, name: &str, target: &str) -> bool {
2645 if !isident(name) || !valid_refname(target) {
2646 return false;
2647 }
2648 if name == target {
2650 return false;
2651 }
2652
2653 let level = self.local_level;
2654 let old = self.params.get(name).cloned().map(Box::new);
2655 let param = Param {
2656 name: name.to_string(),
2657 value: ParamValue::Scalar(target.to_string()),
2658 flags: flags::NAMEREF,
2659 base: 0,
2660 width: 0,
2661 level,
2662 ename: None,
2663 old,
2664 };
2665 self.params.insert(name.to_string(), param);
2666 true
2667 }
2668
2669 pub fn resolve_nameref<'a>(&'a self, name: &str) -> Option<&'a Param> {
2671 if let Some(target) = self.resolve_nameref_name(name) {
2672 self.params.get(&target)
2673 } else {
2674 self.params.get(name)
2675 }
2676 }
2677
2678 pub fn set_loop_var(&mut self, name: &str, value: &str) {
2680 if let Some(param) = self.params.get(name) {
2681 if param.is_nameref() {
2682 if param.is_readonly() {
2683 return;
2684 }
2685 if let Some(p) = self.params.get_mut(name) {
2687 p.value = ParamValue::Scalar(value.to_string());
2688 p.flags &= !flags::UNSET;
2689 }
2690 return;
2691 }
2692 }
2693 self.set_scalar(name, value);
2694 }
2695
2696 pub fn tie_param(&mut self, scalar: &str, array: &str, sep: char) {
2702 let current = self
2704 .get_value(scalar)
2705 .map(|v| v.as_string())
2706 .unwrap_or_default();
2707
2708 let arr: Vec<String> = current
2709 .split(sep)
2710 .filter(|s| !s.is_empty())
2711 .map(String::from)
2712 .collect();
2713
2714 if !self.params.contains_key(scalar) {
2716 let mut param = Param::new_scalar(scalar, ¤t);
2717 param.flags |= flags::TIED;
2718 param.ename = Some(array.to_string());
2719 self.params.insert(scalar.to_string(), param);
2720 } else if let Some(p) = self.params.get_mut(scalar) {
2721 p.flags |= flags::TIED;
2722 p.ename = Some(array.to_string());
2723 }
2724
2725 let arr_param = Param {
2727 name: array.to_string(),
2728 value: ParamValue::Array(arr),
2729 flags: flags::ARRAY | flags::TIED,
2730 base: 10,
2731 width: 0,
2732 level: 0,
2733 ename: Some(scalar.to_string()),
2734 old: None,
2735 };
2736 self.params.insert(array.to_string(), arr_param);
2737
2738 self.tied.insert(
2739 scalar.to_string(),
2740 TiedData {
2741 join_char: sep,
2742 scalar_name: scalar.to_string(),
2743 array_name: array.to_string(),
2744 },
2745 );
2746 self.tied.insert(
2747 array.to_string(),
2748 TiedData {
2749 join_char: sep,
2750 scalar_name: scalar.to_string(),
2751 array_name: array.to_string(),
2752 },
2753 );
2754 }
2755
2756 pub fn untie_param(&mut self, name: &str) {
2758 if let Some(tied) = self.tied.remove(name) {
2759 let other = if name == tied.scalar_name {
2760 &tied.array_name
2761 } else {
2762 &tied.scalar_name
2763 };
2764 self.tied.remove(other);
2765
2766 if let Some(p) = self.params.get_mut(name) {
2767 p.flags &= !flags::TIED;
2768 p.ename = None;
2769 }
2770 if let Some(p) = self.params.get_mut(other) {
2771 p.flags &= !flags::TIED;
2772 p.ename = None;
2773 }
2774 }
2775 }
2776
2777 pub fn set_array_element(&mut self, name: &str, index: i64, value: &str) -> bool {
2783 if let Some(param) = self.params.get_mut(name) {
2784 if param.is_readonly() {
2785 return false;
2786 }
2787 if let ParamValue::Array(ref mut arr) = param.value {
2788 let len = arr.len() as i64;
2789 let idx = if index < 0 { len + index + 1 } else { index };
2790 if idx < 1 {
2791 return false;
2792 }
2793 let idx = (idx - 1) as usize;
2794 while arr.len() <= idx {
2795 arr.push(String::new());
2796 }
2797 arr[idx] = value.to_string();
2798 let pv = ParamValue::Array(arr.clone());
2799 self.handle_special_set(name, &pv);
2800 return true;
2801 }
2802 }
2803 false
2804 }
2805
2806 pub fn get_array_element(&self, name: &str, index: i64) -> Option<String> {
2808 if let Some(param) = self.params.get(name) {
2809 if let ParamValue::Array(ref arr) = param.value {
2810 let len = arr.len() as i64;
2811 let idx = if index < 0 { len + index + 1 } else { index };
2812 if idx < 1 || idx > len {
2813 return None;
2814 }
2815 return Some(arr[(idx - 1) as usize].clone());
2816 }
2817 }
2818 None
2819 }
2820
2821 pub fn set_hash_element(&mut self, name: &str, key: &str, value: &str) -> bool {
2823 if let Some(param) = self.params.get_mut(name) {
2824 if param.is_readonly() {
2825 return false;
2826 }
2827 if let ParamValue::Assoc(ref mut hash) = param.value {
2828 hash.insert(key.to_string(), value.to_string());
2829 return true;
2830 }
2831 }
2832 false
2833 }
2834
2835 pub fn get_hash_element(&self, name: &str, key: &str) -> Option<String> {
2837 if let Some(param) = self.params.get(name) {
2838 if let ParamValue::Assoc(ref hash) = param.value {
2839 return hash.get(key).cloned();
2840 }
2841 }
2842 None
2843 }
2844
2845 pub fn unset_hash_element(&mut self, name: &str, key: &str) -> bool {
2847 if let Some(param) = self.params.get_mut(name) {
2848 if param.is_readonly() {
2849 return false;
2850 }
2851 if let ParamValue::Assoc(ref mut hash) = param.value {
2852 return hash.remove(key).is_some();
2853 }
2854 }
2855 false
2856 }
2857
2858 pub fn get_hash_keys(&self, name: &str) -> Vec<String> {
2860 if let Some(param) = self.params.get(name) {
2861 if let ParamValue::Assoc(ref hash) = param.value {
2862 return hash.keys().cloned().collect();
2863 }
2864 }
2865 Vec::new()
2866 }
2867
2868 pub fn get_hash_values(&self, name: &str) -> Vec<String> {
2870 if let Some(param) = self.params.get(name) {
2871 if let ParamValue::Assoc(ref hash) = param.value {
2872 return hash.values().cloned().collect();
2873 }
2874 }
2875 Vec::new()
2876 }
2877
2878 pub fn get_array_slice(&self, name: &str, start: i64, end: i64) -> Vec<String> {
2884 if let Some(param) = self.params.get(name) {
2885 if let ParamValue::Array(ref arr) = param.value {
2886 return getarrvalue(arr, start, end);
2887 }
2888 }
2889 Vec::new()
2890 }
2891
2892 pub fn set_array_slice(&mut self, name: &str, start: i64, end: i64, val: Vec<String>) -> bool {
2894 if let Some(param) = self.params.get_mut(name) {
2895 if param.is_readonly() {
2896 return false;
2897 }
2898 if let ParamValue::Array(ref mut arr) = param.value {
2899 setarrvalue(arr, start, end, val);
2900 let pv = ParamValue::Array(arr.clone());
2901 self.handle_special_set(name, &pv);
2902 return true;
2903 }
2904 }
2905 false
2906 }
2907
2908 pub fn get_str_slice(&self, name: &str, start: i64, end: i64) -> String {
2910 let val = self
2911 .get_value(name)
2912 .map(|v| v.as_string())
2913 .unwrap_or_default();
2914 let len = val.len() as i64;
2915
2916 let start = if start < 0 {
2917 (len + start).max(0) as usize
2918 } else {
2919 start.max(0) as usize
2920 };
2921 let end = if end < 0 {
2922 (len + end + 1).max(0) as usize
2923 } else {
2924 end.min(len) as usize
2925 };
2926
2927 if start >= val.len() || start >= end {
2928 return String::new();
2929 }
2930 val[start..end.min(val.len())].to_string()
2931 }
2932
2933 pub fn set_str_slice(&mut self, name: &str, start: i64, end: i64, val: &str) -> bool {
2935 let current = self
2936 .get_value(name)
2937 .map(|v| v.as_string())
2938 .unwrap_or_default();
2939 let len = current.len() as i64;
2940
2941 let s = if start < 0 {
2942 (len + start).max(0) as usize
2943 } else {
2944 start as usize
2945 };
2946 let e = if end < 0 {
2947 (len + end + 1).max(0) as usize
2948 } else {
2949 end as usize
2950 };
2951 let s = s.min(current.len());
2952 let e = e.min(current.len());
2953
2954 let mut result = String::with_capacity(s + val.len() + current.len() - e);
2955 result.push_str(¤t[..s]);
2956 result.push_str(val);
2957 if e < current.len() {
2958 result.push_str(¤t[e..]);
2959 }
2960 self.set_scalar(name, &result)
2961 }
2962
2963 pub fn export_param(&mut self, name: &str) {
2969 if let Some(param) = self.params.get_mut(name) {
2970 param.flags |= flags::EXPORT;
2971 let val = match flags::pm_type(param.flags) {
2972 flags::ARRAY | flags::HASHED => return, flags::INTEGER => convbase(param.value.as_integer(), param.base as u32),
2974 flags::EFLOAT | flags::FFLOAT => {
2975 format_float(param.value.as_float(), param.base, param.flags)
2976 }
2977 _ => param.value.as_string(),
2978 };
2979 env::set_var(name, &val);
2980 }
2981 }
2982
2983 pub fn arr_fix_env(&mut self, name: &str) {
2985 if let Some(tied) = self.tied.get(name).cloned() {
2986 if name == tied.array_name {
2987 let arr = self
2988 .params
2989 .get(name)
2990 .map(|p| p.value.as_array())
2991 .unwrap_or_default();
2992 let joined = arr.join(&tied.join_char.to_string());
2993 if let Some(p) = self.params.get(&tied.scalar_name) {
2994 if p.is_exported() {
2995 env::set_var(&tied.scalar_name, &joined);
2996 }
2997 }
2998 }
2999 }
3000 }
3001
3002 pub fn iter(&self) -> impl Iterator<Item = (&String, &Param)> {
3008 self.params.iter()
3009 }
3010
3011 pub fn contains(&self, name: &str) -> bool {
3013 self.params
3014 .get(name)
3015 .map(|p| !p.is_unset())
3016 .unwrap_or(false)
3017 }
3018
3019 pub fn len(&self) -> usize {
3021 self.params.values().filter(|p| !p.is_unset()).count()
3022 }
3023
3024 pub fn is_empty(&self) -> bool {
3025 self.len() == 0
3026 }
3027
3028 pub fn scan_match<F>(&self, pattern: &str, flag_filter: u32, mut callback: F)
3030 where
3031 F: FnMut(&str, &Param),
3032 {
3033 for (name, param) in &self.params {
3034 if param.is_unset() {
3035 continue;
3036 }
3037 if flag_filter != 0 && (param.flags & flag_filter) == 0 {
3038 continue;
3039 }
3040 if pattern.is_empty() || glob_match(pattern, name) {
3041 callback(name, param);
3042 }
3043 }
3044 }
3045
3046 pub fn paramnames(&self, pattern: Option<&str>) -> Vec<String> {
3048 let mut names: Vec<String> = self
3049 .params
3050 .iter()
3051 .filter(|(_, p)| !p.is_unset())
3052 .filter(|(name, _)| pattern.map_or(true, |p| glob_match(p, name)))
3053 .map(|(name, _)| name.clone())
3054 .collect();
3055 names.sort();
3056 names
3057 }
3058
3059 pub fn format_param(&self, name: &str, pf: u32) -> Option<String> {
3065 let param = self.params.get(name)?;
3066 if param.is_unset()
3067 && (pf & print_flags::POSIX_READONLY) == 0
3068 && (pf & print_flags::POSIX_EXPORT) == 0
3069 {
3070 return None;
3071 }
3072
3073 let mut out = String::new();
3074
3075 if (pf & (print_flags::TYPESET | print_flags::POSIX_READONLY | print_flags::POSIX_EXPORT))
3076 != 0
3077 {
3078 if (pf & print_flags::POSIX_EXPORT) != 0 {
3079 if (param.flags & flags::EXPORT) == 0 {
3080 return None;
3081 }
3082 out.push_str("export ");
3083 } else if (pf & print_flags::POSIX_READONLY) != 0 {
3084 if (param.flags & flags::READONLY) == 0 {
3085 return None;
3086 }
3087 out.push_str("readonly ");
3088 } else if (param.flags & flags::EXPORT) != 0
3089 && (param.flags & (flags::ARRAY | flags::HASHED)) == 0
3090 {
3091 out.push_str("export ");
3092 } else if self.local_level > 0 && param.level >= self.local_level {
3093 out.push_str("typeset ");
3094 } else {
3095 out.push_str("typeset ");
3096 }
3097 }
3098
3099 if (pf & (print_flags::TYPE | print_flags::TYPESET)) != 0 {
3101 let mut flag_chars = String::new();
3102 for pmt in PM_TYPES {
3103 if pmt.test_level {
3104 if param.level > 0 {
3105 }
3107 continue;
3108 }
3109 if pmt.bin_flag != 0 && (param.flags & pmt.bin_flag) != 0 {
3110 if (pf & print_flags::TYPESET) != 0 && pmt.type_flag != '\0' {
3111 flag_chars.push(pmt.type_flag);
3112 } else if (pf & print_flags::TYPE) != 0 {
3113 out.push_str(pmt.string);
3114 out.push(' ');
3115 }
3116 }
3117 }
3118 if !flag_chars.is_empty() {
3119 out.push('-');
3120 out.push_str(&flag_chars);
3121 out.push(' ');
3122 }
3123 }
3124
3125 out.push_str(¶m.name);
3127
3128 if (pf & print_flags::NAMEONLY) == 0 && (param.flags & flags::HIDEVAL) == 0 {
3129 out.push('=');
3130 match ¶m.value {
3131 ParamValue::Scalar(s) => {
3132 out.push_str(&shell_quote(s));
3133 }
3134 ParamValue::Integer(i) => {
3135 out.push_str(&convbase(*i, param.base as u32));
3136 }
3137 ParamValue::Float(f) => {
3138 out.push_str(&format_float(*f, param.base, param.flags));
3139 }
3140 ParamValue::Array(arr) => {
3141 out.push('(');
3142 for (i, elem) in arr.iter().enumerate() {
3143 if i > 0 {
3144 out.push(' ');
3145 }
3146 out.push_str(&shell_quote(elem));
3147 }
3148 out.push(')');
3149 }
3150 ParamValue::Assoc(hash) => {
3151 out.push('(');
3152 let mut pairs: Vec<_> = hash.iter().collect();
3153 pairs.sort_by_key(|(k, _)| (*k).clone());
3154 for (i, (k, v)) in pairs.iter().enumerate() {
3155 if i > 0 {
3156 out.push(' ');
3157 }
3158 out.push('[');
3159 out.push_str(&shell_quote(k));
3160 out.push_str("]=");
3161 out.push_str(&shell_quote(v));
3162 }
3163 out.push(')');
3164 }
3165 ParamValue::Unset => {}
3166 }
3167 }
3168
3169 Some(out)
3170 }
3171
3172 pub fn getparamtype(&self, name: &str) -> &'static str {
3174 if let Some(param) = self.params.get(name) {
3175 match flags::pm_type(param.flags) {
3176 flags::HASHED => "association",
3177 flags::ARRAY => "array",
3178 flags::INTEGER => "integer",
3179 flags::EFLOAT | flags::FFLOAT => "float",
3180 flags::NAMEREF => "nameref",
3181 _ => "scalar",
3182 }
3183 } else {
3184 ""
3185 }
3186 }
3187
3188 pub fn issetvar(&self, name: &str) -> bool {
3190 self.params
3191 .get(name)
3192 .map(|p| !p.is_unset())
3193 .unwrap_or(false)
3194 }
3195
3196 pub fn arrlen(&self, name: &str) -> usize {
3198 if let Some(param) = self.params.get(name) {
3199 match ¶m.value {
3200 ParamValue::Array(arr) => arr.len(),
3201 ParamValue::Assoc(hash) => hash.len(),
3202 ParamValue::Scalar(s) if s.is_empty() => 0,
3203 ParamValue::Scalar(_) => 1,
3204 ParamValue::Unset => 0,
3205 _ => 1,
3206 }
3207 } else {
3208 0
3209 }
3210 }
3211
3212 pub fn isarray(&self, name: &str) -> bool {
3214 self.params.get(name).map(|p| p.is_array()).unwrap_or(false)
3215 }
3216
3217 pub fn ishash(&self, name: &str) -> bool {
3219 self.params.get(name).map(|p| p.is_assoc()).unwrap_or(false)
3220 }
3221
3222 pub fn copyparam(&self, name: &str) -> Option<ParamValue> {
3224 self.params.get(name).map(|p| p.value.clone())
3225 }
3226}
3227
3228pub fn getiparam(table: &ParamTable, name: &str) -> i64 {
3234 table.get_value(name).map(|v| v.as_integer()).unwrap_or(0)
3235}
3236
3237pub fn getsparam(table: &ParamTable, name: &str) -> Option<String> {
3239 table.get_value(name).map(|v| v.as_string())
3240}
3241
3242pub fn getsparam_u(table: &ParamTable, name: &str, default: &str) -> String {
3244 getsparam(table, name).unwrap_or_else(|| default.to_string())
3245}
3246
3247pub fn getaparam(table: &ParamTable, name: &str) -> Option<Vec<String>> {
3249 match table.get_value(name)? {
3250 ParamValue::Array(arr) => Some(arr),
3251 _ => None,
3252 }
3253}
3254
3255pub fn gethparam(table: &ParamTable, name: &str) -> Option<Vec<String>> {
3257 match table.get_value(name)? {
3258 ParamValue::Assoc(h) => Some(h.values().cloned().collect()),
3259 _ => None,
3260 }
3261}
3262
3263pub fn gethkparam(table: &ParamTable, name: &str) -> Option<Vec<String>> {
3265 match table.get_value(name)? {
3266 ParamValue::Assoc(h) => Some(h.keys().cloned().collect()),
3267 _ => None,
3268 }
3269}
3270
3271pub fn getnparam(table: &ParamTable, name: &str) -> MNumber {
3273 match table.get_value(name) {
3274 Some(ParamValue::Integer(i)) => MNumber::Integer(i),
3275 Some(ParamValue::Float(f)) => MNumber::Float(f),
3276 Some(ParamValue::Scalar(s)) => {
3277 if let Ok(i) = s.parse::<i64>() {
3278 MNumber::Integer(i)
3279 } else if let Ok(f) = s.parse::<f64>() {
3280 MNumber::Float(f)
3281 } else {
3282 MNumber::default()
3283 }
3284 }
3285 _ => MNumber::default(),
3286 }
3287}
3288
3289pub fn assignsparam(table: &mut ParamTable, name: &str, val: &str) -> bool {
3291 table.set_scalar(name, val)
3292}
3293
3294pub fn assigniparam(table: &mut ParamTable, name: &str, val: i64) -> bool {
3296 table.set_integer(name, val)
3297}
3298
3299pub fn assignaparam(table: &mut ParamTable, name: &str, val: Vec<String>) -> bool {
3301 table.set_array(name, val)
3302}
3303
3304pub fn assignfparam(table: &mut ParamTable, name: &str, val: f64) -> bool {
3306 table.set_float(name, val)
3307}
3308
3309pub fn assignhparam(table: &mut ParamTable, name: &str, val: HashMap<String, String>) -> bool {
3311 table.set_assoc(name, val)
3312}
3313
3314pub fn unsetparam(table: &mut ParamTable, name: &str) -> bool {
3316 table.unset(name)
3317}
3318
3319pub fn isset_param(table: &ParamTable, name: &str) -> bool {
3321 table.contains(name)
3322}
3323
3324pub fn paramtype(table: &ParamTable, name: &str) -> u32 {
3326 table.params.get(name).map(|p| p.flags).unwrap_or(0)
3327}
3328
3329pub fn isexported(table: &ParamTable, name: &str) -> bool {
3331 table
3332 .params
3333 .get(name)
3334 .map(|p| p.is_exported())
3335 .unwrap_or(false)
3336}
3337
3338pub fn isreadonly(table: &ParamTable, name: &str) -> bool {
3340 table
3341 .params
3342 .get(name)
3343 .map(|p| p.is_readonly())
3344 .unwrap_or(false)
3345}
3346
3347pub fn export_param(table: &mut ParamTable, name: &str) {
3349 table.export_param(name);
3350}
3351
3352pub fn unexport_param(table: &mut ParamTable, name: &str) {
3354 table.unexport(name);
3355}
3356
3357pub fn startparamscope(table: &mut ParamTable) {
3359 table.push_scope();
3360}
3361
3362pub fn endparamscope(table: &mut ParamTable) {
3364 table.pop_scope();
3365}
3366
3367pub fn isident(s: &str) -> bool {
3373 if s.is_empty() {
3374 return false;
3375 }
3376 let mut chars = s.chars().peekable();
3377
3378 if chars.peek() == Some(&'.') {
3380 chars.next();
3381 if chars.peek().map_or(true, |c| c.is_ascii_digit()) {
3382 return false;
3383 }
3384 }
3385
3386 let first = match chars.next() {
3387 Some(c) => c,
3388 None => return false,
3389 };
3390
3391 if first.is_ascii_digit() {
3392 return chars.all(|c| c.is_ascii_digit());
3394 }
3395
3396 if !first.is_alphabetic() && first != '_' {
3397 return false;
3398 }
3399
3400 for c in chars {
3401 if c == '[' {
3402 return true;
3404 }
3405 if !c.is_alphanumeric() && c != '_' && c != '.' {
3406 return false;
3407 }
3408 }
3409 true
3410}
3411
3412pub fn valid_refname(val: &str) -> bool {
3414 if val.is_empty() {
3415 return false;
3416 }
3417 let first = val.chars().next().unwrap();
3418 if first.is_ascii_digit() {
3419 let rest = &val[1..];
3421 if let Some(bracket_pos) = rest.find('[') {
3422 return rest[..bracket_pos].chars().all(|c| c.is_ascii_digit());
3423 }
3424 return rest.chars().all(|c| c.is_ascii_digit());
3425 }
3426 if first == '!' || first == '?' || first == '$' || first == '-' {
3427 return val.len() == 1 || val.as_bytes().get(1) == Some(&b'[');
3428 }
3429 if !first.is_alphabetic() && first != '_' {
3430 return false;
3431 }
3432 for c in val[1..].chars() {
3433 if c == '[' {
3434 return true; }
3436 if !c.is_alphanumeric() && c != '_' && c != '.' {
3437 return false;
3438 }
3439 }
3440 true
3441}
3442
3443pub fn colonarr_to_array(s: &str) -> Vec<String> {
3445 s.split(':')
3446 .filter(|s| !s.is_empty())
3447 .map(String::from)
3448 .collect()
3449}
3450
3451pub fn array_to_colonarr(arr: &[String]) -> String {
3453 arr.join(":")
3454}
3455
3456pub fn uniq_array(arr: Vec<String>) -> Vec<String> {
3458 let mut seen = HashSet::new();
3459 arr.into_iter().filter(|s| seen.insert(s.clone())).collect()
3460}
3461
3462pub fn parse_subscript(subscript: &str, ksh_arrays: bool) -> Option<SubscriptIndex> {
3464 let s = subscript.trim();
3465
3466 if s == "@" || s == "*" {
3467 return Some(SubscriptIndex::all());
3468 }
3469
3470 if let Some(comma_pos) = s.find(',') {
3471 let start_str = s[..comma_pos].trim();
3472 let end_str = s[comma_pos + 1..].trim();
3473 let start = parse_index_value(start_str, ksh_arrays)?;
3474 let end = parse_index_value(end_str, ksh_arrays)?;
3475 return Some(SubscriptIndex::range(start, end));
3476 }
3477
3478 let idx = parse_index_value(s, ksh_arrays)?;
3479 Some(SubscriptIndex::single(idx))
3480}
3481
3482fn parse_index_value(s: &str, _ksh_arrays: bool) -> Option<i64> {
3483 let s = s.trim();
3484 if s.is_empty() {
3485 return None;
3486 }
3487 s.parse::<i64>().ok()
3488}
3489
3490pub fn parse_simple_subscript(s: &str) -> Option<(i64, i64)> {
3492 let s = s.trim();
3493 if !s.starts_with('[') || !s.ends_with(']') {
3494 return None;
3495 }
3496 let inner = &s[1..s.len() - 1];
3497 if let Some(comma) = inner.find(',') {
3498 let start = inner[..comma].trim().parse::<i64>().ok()?;
3499 let end = inner[comma + 1..].trim().parse::<i64>().ok()?;
3500 Some((start, end))
3501 } else {
3502 let idx = inner.trim().parse::<i64>().ok()?;
3503 Some((idx, idx))
3504 }
3505}
3506
3507pub fn getarrvalue(arr: &[String], start: i64, end: i64) -> Vec<String> {
3509 let len = arr.len() as i64;
3510 if len == 0 {
3511 return Vec::new();
3512 }
3513 let start = if start < 0 { len + start + 1 } else { start };
3514 let end = if end < 0 { len + end + 1 } else { end };
3515 let start = (start.max(1) - 1) as usize;
3516 let end = end.min(len) as usize;
3517 if start >= end || start >= arr.len() {
3518 return Vec::new();
3519 }
3520 arr[start..end].to_vec()
3521}
3522
3523pub fn setarrvalue(arr: &mut Vec<String>, start: i64, end: i64, val: Vec<String>) {
3525 let len = arr.len() as i64;
3526 let start = if start < 0 {
3527 (len + start + 1).max(0)
3528 } else {
3529 start
3530 };
3531 let end = if end < 0 { (len + end + 1).max(0) } else { end };
3532 let start = (start.max(1) - 1) as usize;
3533 let end = end.max(0) as usize;
3534
3535 while arr.len() < start {
3536 arr.push(String::new());
3537 }
3538
3539 let end = end.min(arr.len());
3540 if start <= end {
3541 arr.splice(start..end, val);
3542 } else {
3543 for (i, v) in val.into_iter().enumerate() {
3544 if start + i < arr.len() {
3545 arr[start + i] = v;
3546 } else {
3547 arr.push(v);
3548 }
3549 }
3550 }
3551}
3552
3553pub fn get_array_element(arr: &[String], idx: i64, ksh_arrays: bool) -> Option<String> {
3555 let len = arr.len() as i64;
3556 let actual_idx = if idx < 0 {
3557 let adj = len + idx;
3558 if adj < 0 {
3559 return None;
3560 }
3561 adj as usize
3562 } else if ksh_arrays {
3563 idx as usize
3564 } else {
3565 if idx > 0 {
3566 (idx - 1) as usize
3567 } else {
3568 return None;
3569 }
3570 };
3571 arr.get(actual_idx).cloned()
3572}
3573
3574pub fn get_array_slice(arr: &[String], idx: &SubscriptIndex, ksh_arrays: bool) -> Vec<String> {
3576 if idx.is_all {
3577 return arr.to_vec();
3578 }
3579 let len = arr.len() as i64;
3580 let start = if idx.start < 0 {
3581 (len + idx.start).max(0) as usize
3582 } else if ksh_arrays {
3583 idx.start as usize
3584 } else {
3585 if idx.start > 0 {
3586 (idx.start - 1) as usize
3587 } else {
3588 0
3589 }
3590 };
3591 let end = if idx.end < 0 {
3592 ((len + idx.end + 1).max(0) as usize).min(arr.len())
3593 } else if ksh_arrays {
3594 (idx.end as usize).min(arr.len())
3595 } else {
3596 (idx.end as usize).min(arr.len())
3597 };
3598 if start >= arr.len() || start >= end {
3599 return Vec::new();
3600 }
3601 arr[start..end].to_vec()
3602}
3603
3604fn glob_match(pattern: &str, name: &str) -> bool {
3606 if pattern == "*" {
3607 return true;
3608 }
3609 if pattern.ends_with('*') && !pattern[..pattern.len() - 1].contains('*') {
3610 return name.starts_with(&pattern[..pattern.len() - 1]);
3611 }
3612 if pattern.starts_with('*') && !pattern[1..].contains('*') {
3613 return name.ends_with(&pattern[1..]);
3614 }
3615 if pattern.starts_with('*') && pattern.ends_with('*') && pattern.len() > 2 {
3617 let inner = &pattern[1..pattern.len() - 1];
3618 if !inner.contains('*') {
3619 return name.contains(inner);
3620 }
3621 }
3622 pattern == name
3623}
3624
3625fn shell_quote(s: &str) -> String {
3627 if s.is_empty() {
3628 return "''".to_string();
3629 }
3630 if s.chars()
3632 .all(|c| c.is_alphanumeric() || c == '_' || c == '/' || c == '.' || c == '-' || c == ':')
3633 {
3634 return s.to_string();
3635 }
3636 let mut out = String::with_capacity(s.len() + 2);
3637 out.push('\'');
3638 for c in s.chars() {
3639 if c == '\'' {
3640 out.push_str("'\\''");
3641 } else {
3642 out.push(c);
3643 }
3644 }
3645 out.push('\'');
3646 out
3647}
3648
3649pub fn convbase(val: i64, base: u32) -> String {
3655 if base == 0 || base == 10 {
3656 return val.to_string();
3657 }
3658
3659 let negative = val < 0;
3660 let mut v = if negative { (-val) as u64 } else { val as u64 };
3661
3662 if v == 0 {
3663 return match base {
3664 16 => "0x0".to_string(),
3665 8 => "00".to_string(),
3666 _ => format!("{}#0", base),
3667 };
3668 }
3669
3670 let mut digits = Vec::new();
3671 while v > 0 {
3672 let dig = (v % base as u64) as u8;
3673 digits.push(if dig < 10 {
3674 b'0' + dig
3675 } else {
3676 b'A' + dig - 10
3677 });
3678 v /= base as u64;
3679 }
3680 digits.reverse();
3681
3682 let prefix = match base {
3683 16 => "0x",
3684 8 => "0",
3685 10 => "",
3686 _ => "",
3687 };
3688
3689 let base_prefix = if base != 10 && base != 16 && base != 8 {
3690 format!("{}#", base)
3691 } else {
3692 prefix.to_string()
3693 };
3694
3695 let sign = if negative { "-" } else { "" };
3696 format!(
3697 "{}{}{}",
3698 sign,
3699 base_prefix,
3700 String::from_utf8_lossy(&digits)
3701 )
3702}
3703
3704pub fn convbase_underscore(val: i64, base: u32, underscore: i32) -> String {
3706 let s = convbase(val, base);
3707 if underscore <= 0 {
3708 return s;
3709 }
3710
3711 let (prefix, digits) = if s.starts_with('-') {
3713 let rest = &s[1..];
3714 let digit_start = rest
3715 .find(|c: char| c.is_ascii_digit() || c.is_ascii_uppercase())
3716 .unwrap_or(0);
3717 (&s[..1 + digit_start], &rest[digit_start..])
3718 } else {
3719 let digit_start = s
3720 .find(|c: char| c.is_ascii_digit() || c.is_ascii_uppercase())
3721 .unwrap_or(0);
3722 (&s[..digit_start], &s[digit_start..])
3723 };
3724
3725 if digits.len() <= underscore as usize {
3726 return s;
3727 }
3728
3729 let u = underscore as usize;
3730 let mut result = prefix.to_string();
3731 let chars: Vec<char> = digits.chars().collect();
3732 let first_group = chars.len() % u;
3733 if first_group > 0 {
3734 result.extend(&chars[..first_group]);
3735 if first_group < chars.len() {
3736 result.push('_');
3737 }
3738 }
3739 for (i, chunk) in chars[first_group..].chunks(u).enumerate() {
3740 if i > 0 {
3741 result.push('_');
3742 }
3743 result.extend(chunk);
3744 }
3745 result
3746}
3747
3748pub fn format_float(dval: f64, digits: i32, pm_flags: u32) -> String {
3750 if dval.is_infinite() {
3751 return if dval < 0.0 {
3752 "-Inf".to_string()
3753 } else {
3754 "Inf".to_string()
3755 };
3756 }
3757 if dval.is_nan() {
3758 return "NaN".to_string();
3759 }
3760
3761 let digits = if digits <= 0 { 10 } else { digits as usize };
3762
3763 if (pm_flags & flags::EFLOAT) != 0 {
3764 format!("{:.*e}", digits.saturating_sub(1), dval)
3765 } else if (pm_flags & flags::FFLOAT) != 0 {
3766 format!("{:.*}", digits, dval)
3767 } else {
3768 let s = format!("{:.*}", 17, dval);
3770 if !s.contains('.') && !s.contains('e') {
3772 format!("{}.", s)
3773 } else {
3774 s
3775 }
3776 }
3777}
3778
3779pub fn convfloat_underscore(dval: f64, underscore: i32) -> String {
3781 let s = format_float(dval, 0, 0);
3782 if underscore <= 0 {
3783 return s;
3784 }
3785
3786 let u = underscore as usize;
3787 let (sign, rest) = if s.starts_with('-') {
3788 ("-", &s[1..])
3789 } else {
3790 ("", s.as_str())
3791 };
3792
3793 let (int_part, frac_exp) = if let Some(dot_pos) = rest.find('.') {
3794 (&rest[..dot_pos], &rest[dot_pos..])
3795 } else {
3796 (rest, "")
3797 };
3798
3799 let int_chars: Vec<char> = int_part.chars().collect();
3801 let mut result = sign.to_string();
3802 let first_group = int_chars.len() % u;
3803 if first_group > 0 {
3804 result.extend(&int_chars[..first_group]);
3805 if first_group < int_chars.len() {
3806 result.push('_');
3807 }
3808 }
3809 for (i, chunk) in int_chars[first_group..].chunks(u).enumerate() {
3810 if i > 0 {
3811 result.push('_');
3812 }
3813 result.extend(chunk);
3814 }
3815
3816 if frac_exp.starts_with('.') {
3818 result.push('.');
3819 let frac = &frac_exp[1..];
3820 let (frac_digits, exp) = if let Some(e_pos) = frac.find('e') {
3821 (&frac[..e_pos], &frac[e_pos..])
3822 } else {
3823 (frac, "")
3824 };
3825
3826 let frac_chars: Vec<char> = frac_digits.chars().collect();
3827 for (i, chunk) in frac_chars.chunks(u).enumerate() {
3828 if i > 0 {
3829 result.push('_');
3830 }
3831 result.extend(chunk);
3832 }
3833 result.push_str(exp);
3834 } else {
3835 result.push_str(frac_exp);
3836 }
3837
3838 result
3839}
3840
3841pub fn intgetfn(table: &ParamTable, name: &str, base: u32) -> String {
3843 let val = getiparam(table, name);
3844 convbase(val, base)
3845}
3846
3847pub fn strgetfn(table: &ParamTable, name: &str, lower: bool, upper: bool) -> Option<String> {
3849 let val = getsparam(table, name)?;
3850 Some(if lower {
3851 val.to_lowercase()
3852 } else if upper {
3853 val.to_uppercase()
3854 } else {
3855 val
3856 })
3857}
3858
3859pub fn parse_subscription_flags(s: &str) -> (SubscriptFlags, &str) {
3865 let mut flags = SubscriptFlags::default();
3866 flags.num = 1;
3867
3868 if !s.starts_with('(') {
3869 return (flags, s);
3870 }
3871
3872 let mut chars = s[1..].char_indices();
3873 let mut end_pos = 0;
3874
3875 while let Some((pos, c)) = chars.next() {
3876 match c {
3877 ')' => {
3878 end_pos = pos + 2; break;
3880 }
3881 'r' => {
3882 flags.reverse = true;
3883 flags.down = false;
3884 flags.index = false;
3885 flags.key_match = false;
3886 }
3887 'R' => {
3888 flags.reverse = true;
3889 flags.down = true;
3890 flags.index = false;
3891 flags.key_match = false;
3892 }
3893 'k' => {
3894 flags.key_match = true;
3895 flags.reverse = true;
3896 flags.down = false;
3897 flags.index = false;
3898 }
3899 'K' => {
3900 flags.key_match = true;
3901 flags.reverse = true;
3902 flags.down = true;
3903 flags.index = false;
3904 }
3905 'i' => {
3906 flags.reverse = true;
3907 flags.index = true;
3908 flags.down = false;
3909 flags.key_match = false;
3910 }
3911 'I' => {
3912 flags.reverse = true;
3913 flags.index = true;
3914 flags.down = true;
3915 flags.key_match = false;
3916 }
3917 'w' => {
3918 flags.word = true;
3919 }
3920 'f' => {
3921 flags.word = true;
3922 flags.separator = Some("\n".to_string());
3923 }
3924 'e' => {
3925 flags.quote_arg = true;
3926 }
3927 _ => {}
3928 }
3929 }
3930
3931 if end_pos > 0 && end_pos <= s.len() {
3932 (flags, &s[end_pos..])
3933 } else {
3934 (flags, s)
3935 }
3936}
3937
3938#[cfg(test)]
3943mod tests {
3944 use super::*;
3945
3946 #[test]
3947 fn test_param_value_conversions() {
3948 let scalar = ParamValue::Scalar("42".to_string());
3949 assert_eq!(scalar.as_integer(), 42);
3950 assert_eq!(scalar.as_float(), 42.0);
3951 assert_eq!(scalar.as_string(), "42");
3952 }
3953
3954 #[test]
3955 fn test_param_table_set_get() {
3956 let mut table = ParamTable::new();
3957 table.set_scalar("FOO", "bar");
3958 assert_eq!(table.get_value("FOO").unwrap().as_string(), "bar");
3959 }
3960
3961 #[test]
3962 fn test_param_readonly() {
3963 let mut table = ParamTable::new();
3964 table.set_scalar("TEST", "value");
3965 table.set_readonly("TEST");
3966 assert!(!table.set_scalar("TEST", "new_value"));
3967 assert_eq!(table.get_value("TEST").unwrap().as_string(), "value");
3968 }
3969
3970 #[test]
3971 fn test_param_array() {
3972 let mut table = ParamTable::new();
3973 table.set_array("arr", vec!["a".into(), "b".into(), "c".into()]);
3974 assert_eq!(
3975 table.get_value("arr").unwrap().as_array(),
3976 vec!["a", "b", "c"]
3977 );
3978 }
3979
3980 #[test]
3981 fn test_param_assoc() {
3982 let mut table = ParamTable::new();
3983 let mut hash = HashMap::new();
3984 hash.insert("key".to_string(), "value".to_string());
3985 table.set_assoc("hash", hash);
3986 if let ParamValue::Assoc(h) = table.get_value("hash").unwrap() {
3987 assert_eq!(h.get("key"), Some(&"value".to_string()));
3988 } else {
3989 panic!("Expected associative array");
3990 }
3991 }
3992
3993 #[test]
3994 fn test_colonarr_conversion() {
3995 let arr = colonarr_to_array("/bin:/usr/bin:/usr/local/bin");
3996 assert_eq!(arr, vec!["/bin", "/usr/bin", "/usr/local/bin"]);
3997 let path = array_to_colonarr(&arr);
3998 assert_eq!(path, "/bin:/usr/bin:/usr/local/bin");
3999 }
4000
4001 #[test]
4002 fn test_local_scope() {
4003 let mut table = ParamTable::new();
4004 table.set_scalar("GLOBAL", "value");
4005
4006 table.push_scope();
4007 table.make_local("LOCAL_VAR");
4008 table.set_scalar("LOCAL_VAR", "local_value");
4009 assert!(table.contains("LOCAL_VAR"));
4010
4011 table.pop_scope();
4012 assert!(!table.contains("LOCAL_VAR"));
4013 assert!(table.contains("GLOBAL"));
4014 }
4015
4016 #[test]
4017 fn test_special_params() {
4018 let table = ParamTable::new();
4019 let pid = table.get_value("$").unwrap().as_integer();
4021 assert!(pid > 0);
4022
4023 let shlvl = table.get_value("SHLVL").unwrap().as_integer();
4025 assert!(shlvl >= 1);
4026 }
4027
4028 #[test]
4029 fn test_isident() {
4030 assert!(isident("foo"));
4031 assert!(isident("_bar"));
4032 assert!(isident("FOO_BAR"));
4033 assert!(isident("x123"));
4034 assert!(isident("123")); assert!(!isident(""));
4036 assert!(!isident("foo bar"));
4037 }
4038
4039 #[test]
4040 fn test_nameref() {
4041 let mut table = ParamTable::new();
4042 table.set_scalar("target", "hello");
4043 table.set_nameref("ref", "target");
4044
4045 let val = table.get_value("ref").unwrap();
4047 assert_eq!(val.as_string(), "hello");
4048 }
4049
4050 #[test]
4051 fn test_tied_params() {
4052 let mut table = ParamTable::new();
4053 table.tie_param("MY_PATH", "my_path", ':');
4054 table.set_scalar("MY_PATH", "/bin:/usr/bin");
4055
4056 let arr = table.get_value("my_path").unwrap().as_array();
4058 assert_eq!(arr, vec!["/bin", "/usr/bin"]);
4059 }
4060
4061 #[test]
4062 fn test_unique_array() {
4063 let arr = vec!["a".into(), "b".into(), "a".into(), "c".into(), "b".into()];
4064 let result = uniq_array(arr);
4065 assert_eq!(result, vec!["a", "b", "c"]);
4066 }
4067
4068 #[test]
4069 fn test_convbase() {
4070 assert_eq!(convbase(255, 16), "0xFF");
4071 assert_eq!(convbase(10, 10), "10");
4072 assert_eq!(convbase(-5, 10), "-5");
4073 assert_eq!(convbase(7, 8), "07");
4074 assert_eq!(convbase(5, 2), "2#101");
4075 }
4076
4077 #[test]
4078 fn test_format_float() {
4079 let s = format_float(3.14, 2, flags::FFLOAT);
4080 assert!(s.starts_with("3.14"));
4081
4082 assert_eq!(format_float(f64::INFINITY, 0, 0), "Inf");
4083 assert_eq!(format_float(f64::NEG_INFINITY, 0, 0), "-Inf");
4084 assert_eq!(format_float(f64::NAN, 0, 0), "NaN");
4085 }
4086
4087 #[test]
4088 fn test_augment_scalar() {
4089 let mut table = ParamTable::new();
4090 table.set_scalar("foo", "hello");
4091 table.augment_scalar("foo", " world");
4092 assert_eq!(table.get_value("foo").unwrap().as_string(), "hello world");
4093 }
4094
4095 #[test]
4096 fn test_augment_integer() {
4097 let mut table = ParamTable::new();
4098 table.set_integer("count", 10);
4099 table.augment_integer("count", 5);
4100 assert_eq!(table.get_value("count").unwrap().as_integer(), 15);
4101 }
4102
4103 #[test]
4104 fn test_augment_array() {
4105 let mut table = ParamTable::new();
4106 table.set_array("arr", vec!["a".into(), "b".into()]);
4107 table.augment_array("arr", vec!["c".into(), "d".into()]);
4108 assert_eq!(
4109 table.get_value("arr").unwrap().as_array(),
4110 vec!["a", "b", "c", "d"]
4111 );
4112 }
4113
4114 #[test]
4115 fn test_array_element_access() {
4116 let mut table = ParamTable::new();
4117 table.set_array("arr", vec!["a".into(), "b".into(), "c".into()]);
4118
4119 assert_eq!(table.get_array_element("arr", 1), Some("a".to_string()));
4120 assert_eq!(table.get_array_element("arr", -1), Some("c".to_string()));
4121 assert_eq!(table.get_array_element("arr", 4), None);
4122
4123 table.set_array_element("arr", 2, "B");
4124 assert_eq!(table.get_array_element("arr", 2), Some("B".to_string()));
4125 }
4126
4127 #[test]
4128 fn test_hash_element_access() {
4129 let mut table = ParamTable::new();
4130 let mut hash = HashMap::new();
4131 hash.insert("k1".to_string(), "v1".to_string());
4132 table.set_assoc("h", hash);
4133
4134 assert_eq!(table.get_hash_element("h", "k1"), Some("v1".to_string()));
4135 table.set_hash_element("h", "k2", "v2");
4136 assert_eq!(table.get_hash_element("h", "k2"), Some("v2".to_string()));
4137
4138 table.unset_hash_element("h", "k1");
4139 assert_eq!(table.get_hash_element("h", "k1"), None);
4140 }
4141
4142 #[test]
4143 fn test_scope_special_restore() {
4144 let mut table = ParamTable::new();
4145
4146 let initial_shlvl = table.shlvl;
4147
4148 table.push_scope();
4149 table.make_local("SHLVL");
4150 table.set_integer("SHLVL", 99);
4151 assert_eq!(table.get_value("SHLVL").unwrap().as_integer(), 99);
4152
4153 table.pop_scope();
4154 assert_eq!(
4155 table.get_value("SHLVL").unwrap().as_integer(),
4156 initial_shlvl
4157 );
4158 }
4159
4160 #[test]
4161 fn test_export_unexport() {
4162 let mut table = ParamTable::new();
4163 table.set_scalar("MY_VAR", "test_val");
4164 table.export("MY_VAR");
4165 assert_eq!(env::var("MY_VAR").ok(), Some("test_val".to_string()));
4166
4167 table.unexport("MY_VAR");
4168 assert!(env::var("MY_VAR").is_err());
4169 }
4170
4171 #[test]
4172 fn test_parse_subscript() {
4173 let idx = parse_subscript("@", false).unwrap();
4174 assert!(idx.is_all);
4175
4176 let idx = parse_subscript("3", false).unwrap();
4177 assert_eq!(idx.start, 3);
4178
4179 let idx = parse_subscript("2,5", false).unwrap();
4180 assert_eq!(idx.start, 2);
4181 assert_eq!(idx.end, 5);
4182 }
4183
4184 #[test]
4185 fn test_getarrvalue() {
4186 let arr = vec!["a".into(), "b".into(), "c".into(), "d".into()];
4187 assert_eq!(getarrvalue(&arr, 2, 3), vec!["b", "c"]);
4188 assert_eq!(getarrvalue(&arr, -2, -1), vec!["c", "d"]);
4189 assert_eq!(getarrvalue(&arr, 1, 4), vec!["a", "b", "c", "d"]);
4190 }
4191
4192 #[test]
4193 fn test_setarrvalue() {
4194 let mut arr = vec!["a".into(), "b".into(), "c".into(), "d".into()];
4195 setarrvalue(&mut arr, 2, 3, vec!["X".into(), "Y".into()]);
4196 assert_eq!(arr, vec!["a", "X", "Y", "d"]);
4197 }
4198
4199 #[test]
4200 fn test_valid_refname() {
4201 assert!(valid_refname("foo"));
4202 assert!(valid_refname("_bar"));
4203 assert!(valid_refname("1"));
4204 assert!(valid_refname("!"));
4205 assert!(valid_refname("arr[1]"));
4206 assert!(!valid_refname(""));
4207 assert!(!valid_refname("foo bar"));
4208 }
4209
4210 #[test]
4211 fn test_glob_match() {
4212 assert!(glob_match("*", "anything"));
4213 assert!(glob_match("foo*", "foobar"));
4214 assert!(!glob_match("foo*", "barfoo"));
4215 assert!(glob_match("*bar", "foobar"));
4216 assert!(glob_match("exact", "exact"));
4217 assert!(!glob_match("exact", "other"));
4218 }
4219
4220 #[test]
4221 fn test_format_param() {
4222 let mut table = ParamTable::new();
4223 table.set_scalar("MY_VAR", "hello world");
4224 let out = table.format_param("MY_VAR", print_flags::TYPESET).unwrap();
4225 assert!(out.contains("MY_VAR"));
4226 assert!(out.contains("hello world"));
4227 }
4228
4229 #[test]
4230 fn test_seconds() {
4231 let table = ParamTable::new();
4232 let secs = table.get_seconds_int();
4233 assert!(secs >= 0);
4234
4235 let fsecs = table.get_seconds_float();
4236 assert!(fsecs >= 0.0);
4237 }
4238
4239 #[test]
4240 fn test_pipestatus() {
4241 let mut table = ParamTable::new();
4242 table.pipestats = vec![0, 1, 2];
4243 let val = table.get_value("pipestatus").unwrap();
4244 assert_eq!(val.as_array(), vec!["0", "1", "2"]);
4245 }
4246
4247 #[test]
4248 fn test_str_slice() {
4249 let mut table = ParamTable::new();
4250 table.set_scalar("s", "hello world");
4251
4252 let slice = table.get_str_slice("s", 0, 5);
4253 assert_eq!(slice, "hello");
4254
4255 table.set_str_slice("s", 0, 5, "goodbye");
4256 assert_eq!(table.get_value("s").unwrap().as_string(), "goodbye world");
4257 }
4258
4259 #[test]
4260 fn test_createparam() {
4261 let mut table = ParamTable::new();
4262 assert!(table.createparam("newvar", flags::SCALAR));
4263 assert!(table.contains("newvar"));
4264
4265 assert!(table.createparam("intvar", flags::INTEGER));
4266 assert_eq!(table.get_value("intvar").unwrap().as_integer(), 0);
4267 }
4268
4269 #[test]
4270 fn test_mnumber() {
4271 let i = MNumber::Integer(42);
4272 assert_eq!(i.as_integer(), 42);
4273 assert_eq!(i.as_float(), 42.0);
4274 assert!(!i.is_float());
4275
4276 let f = MNumber::Float(3.14);
4277 assert_eq!(f.as_integer(), 3);
4278 assert!((f.as_float() - 3.14).abs() < 1e-10);
4279 assert!(f.is_float());
4280 }
4281
4282 #[test]
4283 fn test_uniq_array_empty() {
4284 let empty: Vec<String> = Vec::new();
4285 assert!(uniq_array(empty).is_empty());
4286 }
4287
4288 #[test]
4289 fn test_convbase_underscore() {
4290 let s = convbase_underscore(1234567, 10, 3);
4291 assert_eq!(s, "1_234_567");
4292 }
4293
4294 #[test]
4295 fn test_subscription_flags() {
4296 let (flags, rest) = parse_subscription_flags("(r)3");
4297 assert!(flags.reverse);
4298 assert!(!flags.down);
4299 assert_eq!(rest, "3");
4300
4301 let (flags, _) = parse_subscription_flags("(I)foo");
4302 assert!(flags.reverse);
4303 assert!(flags.down);
4304 assert!(flags.index);
4305 }
4306}