Skip to main content

zsh/
params.rs

1//! Parameter management for zshrs
2//!
3//! Port from zsh/Src/params.c (6511 lines → full Rust port)
4//!
5//! Provides shell parameters (variables), special parameters, arrays,
6//! associative arrays, parameter attributes, namerefs, scoping,
7//! tied parameters, and all special parameter get/set functions.
8
9use std::collections::{HashMap, HashSet};
10use std::env;
11use std::sync::atomic::{AtomicI64, Ordering};
12use std::time::{Instant, SystemTime, UNIX_EPOCH};
13
14// ---------------------------------------------------------------------------
15// Parameter flags (from zsh.h PM_* flags)
16// ---------------------------------------------------------------------------
17
18pub mod flags {
19    pub const SCALAR: u32 = 1 << 0;
20    pub const INTEGER: u32 = 1 << 1;
21    pub const EFLOAT: u32 = 1 << 2; // %e float format
22    pub const FFLOAT: u32 = 1 << 3; // %f float format
23    pub const ARRAY: u32 = 1 << 4;
24    pub const HASHED: u32 = 1 << 5; // Associative array (PM_HASHED)
25    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; // Exported to environment
29    pub const UNSET: u32 = 1 << 10;
30    pub const TIED: u32 = 1 << 11;
31    pub const UNIQUE: u32 = 1 << 12; // Array elements unique
32    pub const LOWER: u32 = 1 << 13; // Lowercase value
33    pub const UPPER: u32 = 1 << 14; // Uppercase value
34    pub const TAG: u32 = 1 << 15; // Tagged parameter
35    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; // Named reference
39    pub const LEFT: u32 = 1 << 20; // Left justified
40    pub const RIGHT_B: u32 = 1 << 21; // Right justified with blanks
41    pub const RIGHT_Z: u32 = 1 << 22; // Right justified with zeros
42    pub const AUTOLOAD: u32 = 1 << 23; // Autoloaded parameter
43    pub const DECLARED: u32 = 1 << 24; // Explicitly declared
44    pub const REMOVABLE: u32 = 1 << 25; // Can be removed from table
45    pub const HASHELEM: u32 = 1 << 26; // Element of hash
46    pub const NAMEDDIR: u32 = 1 << 27; // Named directory
47    pub const DONTIMPORT: u32 = 1 << 28;
48    pub const DEFAULTED: u32 = 1 << 29;
49    pub const DONTIMPORT_SUID: u32 = 1 << 30;
50
51    // Convenience combo - like PM_READONLY_SPECIAL in C
52    pub const READONLY_SPECIAL: u32 = READONLY | SPECIAL;
53
54    // Type mask
55    pub const TYPE_MASK: u32 = SCALAR | INTEGER | EFLOAT | FFLOAT | ARRAY | HASHED | NAMEREF;
56
57    /// Extract just the type bits
58    pub fn pm_type(flags: u32) -> u32 {
59        flags & TYPE_MASK
60    }
61
62    /// For backwards compat with old code using FLOAT
63    pub const FLOAT: u32 = FFLOAT;
64    /// For backwards compat with old code using ASSOC
65    pub const ASSOC: u32 = HASHED;
66}
67
68// ---------------------------------------------------------------------------
69// Subscription flags (SCANPM_*)
70// ---------------------------------------------------------------------------
71
72pub 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
90// ---------------------------------------------------------------------------
91// Assignment flags (ASSPM_*)
92// ---------------------------------------------------------------------------
93
94pub mod assign_flags {
95    pub const AUGMENT: u32 = 1 << 0; // += assignment
96    pub const WARN: u32 = 1 << 1; // Warn about global creation
97    pub const ENV_IMPORT: u32 = 1 << 2; // Importing from environment
98    pub const KEY_VALUE: u32 = 1 << 3; // key=value assignment syntax
99}
100
101// ---------------------------------------------------------------------------
102// Value flags (VALFLAG_*)
103// ---------------------------------------------------------------------------
104
105pub mod val_flags {
106    pub const INV: u32 = 1 << 0; // Inverse subscript
107    pub const EMPTY: u32 = 1 << 1; // Empty subscript range
108    pub const SUBST: u32 = 1 << 2; // Apply formatting
109    pub const REFSLICE: u32 = 1 << 3; // Nameref with subscript
110}
111
112// ---------------------------------------------------------------------------
113// Print flags (PRINT_*)
114// ---------------------------------------------------------------------------
115
116pub 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// ---------------------------------------------------------------------------
129// Parameter value types
130// ---------------------------------------------------------------------------
131
132#[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    /// Get the type flag for this value
208    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// ---------------------------------------------------------------------------
221// Numeric type for parameters (from params.c mnumber)
222// ---------------------------------------------------------------------------
223
224#[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// ---------------------------------------------------------------------------
257// Value struct - mirrors C's struct value for subscript access
258// ---------------------------------------------------------------------------
259
260#[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// ---------------------------------------------------------------------------
286// Shell parameter
287// ---------------------------------------------------------------------------
288
289#[derive(Clone, Debug)]
290pub struct Param {
291    pub name: String,
292    pub value: ParamValue,
293    pub flags: u32,
294    pub base: i32,               // Output base for integers
295    pub width: i32,              // Output field width
296    pub level: i32,              // Scope level
297    pub ename: Option<String>,   // Environment/tied name
298    pub old: Option<Box<Param>>, // Previous parameter at higher scope
299}
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    /// Get the string representation, applying formatting flags
434    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    /// Get the integer representation with base formatting
450    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// ---------------------------------------------------------------------------
457// Tied parameter data
458// ---------------------------------------------------------------------------
459
460#[derive(Clone, Debug)]
461pub struct TiedData {
462    pub join_char: char,
463    pub scalar_name: String,
464    pub array_name: String,
465}
466
467// ---------------------------------------------------------------------------
468// Subscript flags for getarg()
469// ---------------------------------------------------------------------------
470
471#[derive(Clone, Debug, Default)]
472pub struct SubscriptFlags {
473    pub reverse: bool,   // (r) or (R) - reverse search
474    pub down: bool,      // (R), (K), (I) - search from end
475    pub index: bool,     // (i) or (I) - return index
476    pub key_match: bool, // (k) or (K) - match keys in hash
477    pub word: bool,      // (w) - word subscript
478    pub num: i64,        // (n) - occurrence count
479    pub begin: i64,      // (b) - begin offset
480    pub has_begin: bool,
481    pub separator: Option<String>, // (s) - word separator
482    pub quote_arg: bool,           // (e) - exact/escape
483}
484
485// ---------------------------------------------------------------------------
486// Subscript index result
487// ---------------------------------------------------------------------------
488
489#[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
522// ---------------------------------------------------------------------------
523// Parameter table print types (from printparamnode)
524// ---------------------------------------------------------------------------
525
526pub 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// ---------------------------------------------------------------------------
691// Special parameter definitions table (mirrors special_params[] in C)
692// ---------------------------------------------------------------------------
693
694#[derive(Clone, Debug)]
695pub struct SpecialParamDef {
696    pub name: &'static str,
697    pub pm_type: u32,  // PM_INTEGER | PM_SCALAR | PM_ARRAY
698    pub pm_flags: u32, // PM_READONLY_SPECIAL, PM_DONTIMPORT, etc.
699    pub tied_name: Option<&'static str>,
700}
701
702/// All special parameters from params.c special_params[]
703pub const SPECIAL_PARAMS: &[SpecialParamDef] = &[
704    // Integer specials with custom GSU
705    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    // Scalar specials with custom GSU
772    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    // Readonly integer variables bound to C globals
845    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    // Settable integer variables
888    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    // Scalar variables bound to C globals
937    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    // Readonly arrays
1016    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    // Tied colon-separated/array pairs
1029    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    // Locale
1084    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    // Zsh-only aliases
1127    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    // pipestatus array
1182    SpecialParamDef {
1183        name: "pipestatus",
1184        pm_type: flags::ARRAY,
1185        pm_flags: 0,
1186        tied_name: None,
1187    },
1188];
1189
1190// ---------------------------------------------------------------------------
1191// Parameter table
1192// ---------------------------------------------------------------------------
1193
1194pub 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    /// Shell histchars: [bangchar, hatchar, hashchar]
1201    pub histchars: [u8; 3],
1202    /// Last exit status ($?)
1203    pub lastval: i64,
1204    /// PID ($$)
1205    pub mypid: i64,
1206    /// Last background PID ($!)
1207    pub lastpid: i64,
1208    /// Current history command number
1209    pub curhist: i64,
1210    /// Current line number ($LINENO)
1211    pub lineno: i64,
1212    /// Parent PID ($PPID)
1213    pub ppid: i64,
1214    /// Subshell nesting ($ZSH_SUBSHELL)
1215    pub zsh_subshell: i64,
1216    /// Terminal columns ($COLUMNS)
1217    pub columns: i64,
1218    /// Terminal lines ($LINES)
1219    pub lines: i64,
1220    /// $SHLVL
1221    pub shlvl: i64,
1222    /// Max function nesting ($FUNCNEST)
1223    pub funcnest: i64,
1224    /// $OPTIND
1225    pub optind: i64,
1226    /// $OPTARG
1227    pub optarg: String,
1228    /// TRY_BLOCK_ERROR
1229    pub try_errflag: i64,
1230    /// TRY_BLOCK_INTERRUPT
1231    pub try_interrupt: i64,
1232    /// ZLE_RPROMPT_INDENT
1233    pub rprompt_indent: i64,
1234    /// IFS value
1235    pub ifs: String,
1236    /// Underscore ($_)
1237    pub underscore: String,
1238    /// Positional parameters ($1, $2, ...)
1239    pub pparams: Vec<String>,
1240    /// $0
1241    pub argzero: String,
1242    /// Positional zero for POSIX
1243    pub posixzero: String,
1244    /// $pipestatus
1245    pub pipestats: Vec<i32>,
1246    /// Prompt strings
1247    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    /// NULLCMD / READNULLCMD
1255    pub nullcmd: String,
1256    pub readnullcmd: String,
1257    /// POSTEDIT
1258    pub postedit: String,
1259    /// WORDCHARS
1260    pub wordchars: String,
1261    /// KEYBOARD_HACK
1262    pub keyboard_hack_char: u8,
1263    /// HOME
1264    pub home: String,
1265    /// TERM
1266    pub term: String,
1267    /// TERMINFO
1268    pub terminfo: String,
1269    /// TERMINFO_DIRS
1270    pub terminfo_dirs: String,
1271    /// Tied parameter bindings
1272    pub tied: HashMap<String, TiedData>,
1273    /// HISTSIZE
1274    pub histsize: i64,
1275    /// SAVEHIST
1276    pub savehist: i64,
1277    /// Options state for KSH_ARRAYS etc.
1278    pub ksh_arrays: bool,
1279    /// Options state for POSIX_ARGZERO
1280    pub posix_argzero: bool,
1281    /// Eval context stack
1282    pub zsh_eval_context: Vec<String>,
1283    /// RANDOM seed
1284    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        // Try to get terminal size
1371        #[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        // Initialize special parameters
1385        table.init_special_params();
1386
1387        // Setup tied parameters
1388        table.init_tied_params();
1389
1390        // Import environment
1391        table.import_environment();
1392
1393        // Set standard non-special parameters
1394        table.init_standard_params();
1395
1396        table
1397    }
1398
1399    fn init_special_params(&mut self) {
1400        // All special params get the SPECIAL flag
1401        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()), // dash: current option flags
1489            "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            // Tied colon-separated paths
1518            "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            // Locale
1524            "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        // Set up tied parameter pairs (scalar PATH <-> array path)
1543        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            // Create the array side
1564            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            // Mark the scalar side as tied
1578            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        // HOST
1606        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        // LOGNAME
1626        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        // MACHTYPE, OSTYPE, VENDOR
1632        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        // TTY
1637        #[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        // ZSH_VERSION / ZSH_PATCHLEVEL
1651        self.set_scalar_internal("ZSH_VERSION", "5.9", 0);
1652        self.set_scalar_internal("ZSH_PATCHLEVEL", "zshrs", 0);
1653
1654        // Defaults
1655        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        // Signals array
1662        #[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    // -----------------------------------------------------------------------
1700    // Special parameter dynamic getters
1701    // -----------------------------------------------------------------------
1702
1703    /// Get a special parameter value dynamically.
1704    /// Returns None if not special (caller should use stored value).
1705    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                // Return current option string
1794                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    /// Handle special parameter set side-effects
1842    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                    // Re-seed
1848                }
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        // Handle tied parameter sync
2021        if let Some(tied) = self.tied.get(name).cloned() {
2022            if name == tied.scalar_name {
2023                // Scalar changed -> update array
2024                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                // Array changed -> update scalar
2035                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                // Update environment
2040                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    // -----------------------------------------------------------------------
2050    // Special value getters
2051    // -----------------------------------------------------------------------
2052
2053    pub fn get_random(&self) -> i64 {
2054        // Simple LCG PRNG matching zsh's rand() & 0x7fff
2055        static COUNTER: AtomicI64 = AtomicI64::new(0);
2056        let n = COUNTER.fetch_add(1, Ordering::Relaxed);
2057        let seed = self.random_seed as i64;
2058        // Linear congruential generator
2059        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    /// Get the SECONDS value
2076    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    // -----------------------------------------------------------------------
2099    // Core get/set/unset operations
2100    // -----------------------------------------------------------------------
2101
2102    /// Get a parameter value, resolving specials and namerefs
2103    pub fn get(&self, name: &str) -> Option<&ParamValue> {
2104        // Check for nameref resolution
2105        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    /// Get a parameter value, including dynamic specials
2112    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        // Check dynamic specials first
2117        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    /// Get the full parameter struct
2131    pub fn get_param(&self, name: &str) -> Option<&Param> {
2132        self.params.get(name)
2133    }
2134
2135    /// Get mutable parameter
2136    pub fn get_param_mut(&mut self, name: &str) -> Option<&mut Param> {
2137        self.params.get_mut(name)
2138    }
2139
2140    /// Resolve nameref chain, returning the final target name
2141    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        // Follow chain, with loop detection
2151        let mut visited = HashSet::new();
2152        visited.insert(name.to_string());
2153        let mut current = target;
2154        loop {
2155            if visited.contains(&current) {
2156                return None; // Loop detected
2157            }
2158            visited.insert(current.clone());
2159            if let Some(p) = self.params.get(&current) {
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    /// Set a scalar parameter
2176    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    /// Set an integer parameter
2213    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    /// Set a float parameter
2241    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    /// Set an array parameter
2266    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    /// Set an associative array parameter
2296    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    /// Set a numeric value (MNumber)
2317    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    /// Augmented assignment (+=)
2325    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    /// Augmented assignment for arrays (+=)
2335    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    /// Augmented assignment for integers (+=)
2346    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    /// Unset a parameter
2352    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        // Handle tied parameter cleanup
2360        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        // For special params, mark unset but don't remove
2375        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        // Check for local scope: keep struct but mark unset
2385        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        // If there's an old param, restore it
2397        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            // Re-export if needed
2401            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    /// Export a parameter
2413    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    /// Unexport a parameter
2424    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    /// Mark parameter as readonly
2432    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    // -----------------------------------------------------------------------
2442    // Scope management (from startparamscope/endparamscope)
2443    // -----------------------------------------------------------------------
2444
2445    /// Start a new local scope
2446    pub fn push_scope(&mut self) {
2447        self.local_level += 1;
2448    }
2449
2450    /// End a local scope, restoring parameters
2451    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                    // Restore special parameter from old
2473                    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                    // Remove local and restore old
2490                    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    /// Create a local variable (from typeset/local builtin)
2509    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                // Already at this level
2513                return;
2514            }
2515            // Save old and create new at current level
2516            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            // For special params, copy the special flag
2529            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            // Create new local
2538            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    /// Create a local variable with a specific type
2553    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            // Set type, preserve LOCAL
2557            param.flags =
2558                (param.flags & (flags::LOCAL | flags::SPECIAL | flags::EXPORT)) | pm_flags;
2559            // Set appropriate default value
2560            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    // -----------------------------------------------------------------------
2572    // Create parameter (from createparam in C)
2573    // -----------------------------------------------------------------------
2574
2575    /// Create a parameter with given flags. Returns false if already exists and set.
2576    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                    // Already exists and set at this level
2585                    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    /// Reset parameter to new type (from resetparam in C)
2622    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        // Unset and recreate
2629        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    // -----------------------------------------------------------------------
2640    // Named reference support (from resolve_nameref etc.)
2641    // -----------------------------------------------------------------------
2642
2643    /// Create a named reference
2644    pub fn set_nameref(&mut self, name: &str, target: &str) -> bool {
2645        if !isident(name) || !valid_refname(target) {
2646            return false;
2647        }
2648        // Don't allow self-reference
2649        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    /// Resolve a nameref to its ultimate target Param
2670    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    /// Set loop variable (for-loop nameref support)
2679    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                // Update the nameref target
2686                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    // -----------------------------------------------------------------------
2697    // Tied parameter support
2698    // -----------------------------------------------------------------------
2699
2700    /// Tie scalar to array with separator (from typeset -T)
2701    pub fn tie_param(&mut self, scalar: &str, array: &str, sep: char) {
2702        // Get current value from scalar
2703        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        // Create/update scalar
2715        if !self.params.contains_key(scalar) {
2716            let mut param = Param::new_scalar(scalar, &current);
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        // Create/update array
2726        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    /// Untie a parameter pair
2757    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    // -----------------------------------------------------------------------
2778    // Array/hash element access
2779    // -----------------------------------------------------------------------
2780
2781    /// Set array element by index (1-based, zsh style)
2782    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    /// Get array element by index (1-based, zsh style)
2807    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    /// Set associative array element
2822    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    /// Get associative array element
2836    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    /// Delete associative array element
2846    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    /// Get all keys from associative array
2859    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    /// Get all values from associative array
2869    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    // -----------------------------------------------------------------------
2879    // Array slice operations (from getarrvalue/setarrvalue)
2880    // -----------------------------------------------------------------------
2881
2882    /// Get array slice with subscript handling
2883    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    /// Set array slice with subscript handling
2893    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    /// Get string slice
2909    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    /// Set string slice
2934    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(&current[..s]);
2956        result.push_str(val);
2957        if e < current.len() {
2958            result.push_str(&current[e..]);
2959        }
2960        self.set_scalar(name, &result)
2961    }
2962
2963    // -----------------------------------------------------------------------
2964    // Environment operations
2965    // -----------------------------------------------------------------------
2966
2967    /// Export parameter to environment (full version from export_param)
2968    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, // Can't export arrays
2973                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    /// Fix environment after array change (from arrfixenv)
2984    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    // -----------------------------------------------------------------------
3003    // Scanning / iteration
3004    // -----------------------------------------------------------------------
3005
3006    /// Iterate over all parameters
3007    pub fn iter(&self) -> impl Iterator<Item = (&String, &Param)> {
3008        self.params.iter()
3009    }
3010
3011    /// Check if a parameter exists (and is set)
3012    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    /// Get parameter count
3020    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    /// Scan parameters matching pattern with optional flag filter
3029    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    /// Get all parameter names matching pattern
3047    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    // -----------------------------------------------------------------------
3060    // Parameter printing (from printparamnode)
3061    // -----------------------------------------------------------------------
3062
3063    /// Format a parameter for display (typeset -p output)
3064    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        // Print type flags
3100        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                        // local
3106                    }
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        // Print name and value
3126        out.push_str(&param.name);
3127
3128        if (pf & print_flags::NAMEONLY) == 0 && (param.flags & flags::HIDEVAL) == 0 {
3129            out.push('=');
3130            match &param.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    /// Get parameter type string (from getparamtype)
3173    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    /// Check if parameter is set (from issetvar)
3189    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    /// Get array length (from arrlen)
3197    pub fn arrlen(&self, name: &str) -> usize {
3198        if let Some(param) = self.params.get(name) {
3199            match &param.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    /// Check if parameter is an array
3213    pub fn isarray(&self, name: &str) -> bool {
3214        self.params.get(name).map(|p| p.is_array()).unwrap_or(false)
3215    }
3216
3217    /// Check if parameter is a hash
3218    pub fn ishash(&self, name: &str) -> bool {
3219        self.params.get(name).map(|p| p.is_assoc()).unwrap_or(false)
3220    }
3221
3222    /// Copy a parameter value
3223    pub fn copyparam(&self, name: &str) -> Option<ParamValue> {
3224        self.params.get(name).map(|p| p.value.clone())
3225    }
3226}
3227
3228// ---------------------------------------------------------------------------
3229// Free functions matching the C API
3230// ---------------------------------------------------------------------------
3231
3232/// Get integer parameter value (from params.c getiparam)
3233pub fn getiparam(table: &ParamTable, name: &str) -> i64 {
3234    table.get_value(name).map(|v| v.as_integer()).unwrap_or(0)
3235}
3236
3237/// Get scalar (string) parameter (from params.c getsparam)
3238pub fn getsparam(table: &ParamTable, name: &str) -> Option<String> {
3239    table.get_value(name).map(|v| v.as_string())
3240}
3241
3242/// Get scalar with default
3243pub fn getsparam_u(table: &ParamTable, name: &str, default: &str) -> String {
3244    getsparam(table, name).unwrap_or_else(|| default.to_string())
3245}
3246
3247/// Get array parameter (from params.c getaparam)
3248pub 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
3255/// Get hash parameter values as array (from params.c gethparam)
3256pub 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
3263/// Get hash parameter keys as array (from params.c gethkparam)
3264pub 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
3271/// Get numeric parameter (from params.c getnparam)
3272pub 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
3289/// Assign string parameter (from params.c assignsparam)
3290pub fn assignsparam(table: &mut ParamTable, name: &str, val: &str) -> bool {
3291    table.set_scalar(name, val)
3292}
3293
3294/// Assign integer parameter (from params.c assigniparam)
3295pub fn assigniparam(table: &mut ParamTable, name: &str, val: i64) -> bool {
3296    table.set_integer(name, val)
3297}
3298
3299/// Assign array parameter (from params.c assignaparam)
3300pub fn assignaparam(table: &mut ParamTable, name: &str, val: Vec<String>) -> bool {
3301    table.set_array(name, val)
3302}
3303
3304/// Assign float parameter
3305pub fn assignfparam(table: &mut ParamTable, name: &str, val: f64) -> bool {
3306    table.set_float(name, val)
3307}
3308
3309/// Assign hash parameter (from params.c sethparam)
3310pub fn assignhparam(table: &mut ParamTable, name: &str, val: HashMap<String, String>) -> bool {
3311    table.set_assoc(name, val)
3312}
3313
3314/// Unset parameter (from params.c unsetparam)
3315pub fn unsetparam(table: &mut ParamTable, name: &str) -> bool {
3316    table.unset(name)
3317}
3318
3319/// Check if parameter is set
3320pub fn isset_param(table: &ParamTable, name: &str) -> bool {
3321    table.contains(name)
3322}
3323
3324/// Get parameter type flags
3325pub fn paramtype(table: &ParamTable, name: &str) -> u32 {
3326    table.params.get(name).map(|p| p.flags).unwrap_or(0)
3327}
3328
3329/// Check if parameter is exported
3330pub 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
3338/// Check if parameter is readonly
3339pub 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
3347/// Export parameter to environment
3348pub fn export_param(table: &mut ParamTable, name: &str) {
3349    table.export_param(name);
3350}
3351
3352/// Unexport parameter
3353pub fn unexport_param(table: &mut ParamTable, name: &str) {
3354    table.unexport(name);
3355}
3356
3357/// Start a parameter scope
3358pub fn startparamscope(table: &mut ParamTable) {
3359    table.push_scope();
3360}
3361
3362/// End a parameter scope
3363pub fn endparamscope(table: &mut ParamTable) {
3364    table.pop_scope();
3365}
3366
3367// ---------------------------------------------------------------------------
3368// Utility functions
3369// ---------------------------------------------------------------------------
3370
3371/// Check if string is valid identifier (from params.c isident)
3372pub fn isident(s: &str) -> bool {
3373    if s.is_empty() {
3374        return false;
3375    }
3376    let mut chars = s.chars().peekable();
3377
3378    // Handle namespace prefix (e.g. "ns.var")
3379    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        // All-digit names are valid (positional params)
3393        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            // Subscript is OK at end
3403            return true;
3404        }
3405        if !c.is_alphanumeric() && c != '_' && c != '.' {
3406            return false;
3407        }
3408    }
3409    true
3410}
3411
3412/// Validate nameref target name (from valid_refname)
3413pub 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        // All digits OK for positional params
3420        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; // Subscript is fine
3435        }
3436        if !c.is_alphanumeric() && c != '_' && c != '.' {
3437            return false;
3438        }
3439    }
3440    true
3441}
3442
3443/// Colon-separated path to array
3444pub 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
3451/// Array to colon-separated path
3452pub fn array_to_colonarr(arr: &[String]) -> String {
3453    arr.join(":")
3454}
3455
3456/// Remove duplicate elements from array while preserving order
3457pub 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
3462/// Parse a subscript expression like `[1]`, `[1,5]`, `[@]`, `[*]`
3463pub 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
3490/// Parse simple subscript - extract index from `[n]` or `[m,n]` syntax
3491pub 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
3507/// Get array element with subscript handling (from params.c getarrvalue)
3508pub 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
3523/// Set array element with subscript handling (from params.c setarrvalue)
3524pub 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
3553/// Get single array element by index (handles ksh_arrays)
3554pub 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
3574/// Get array slice based on subscript index
3575pub 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
3604/// Simple glob match for parameter scanning
3605fn 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    // Simple two-star case: *foo*
3616    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
3625/// Shell-quote a string for display
3626fn shell_quote(s: &str) -> String {
3627    if s.is_empty() {
3628        return "''".to_string();
3629    }
3630    // Check if quoting is needed
3631    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
3649// ---------------------------------------------------------------------------
3650// Integer/Float conversion (from convbase/convfloat)
3651// ---------------------------------------------------------------------------
3652
3653/// Convert integer to string with base (from params.c convbase)
3654pub 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
3704/// Convert integer to string with underscores for readability
3705pub 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    // Find the digits portion
3712    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
3748/// Format a float value for output (from params.c convfloat)
3749pub 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        // General format
3769        let s = format!("{:.*}", 17, dval);
3770        // Ensure there's a decimal point
3771        if !s.contains('.') && !s.contains('e') {
3772            format!("{}.", s)
3773        } else {
3774            s
3775        }
3776    }
3777}
3778
3779/// Format float with underscores
3780pub 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    // Add underscores to integer part
3800    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    // Add underscores to fractional part
3817    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
3841/// Integer parameter with base formatting (from params.c intgetfn)
3842pub fn intgetfn(table: &ParamTable, name: &str, base: u32) -> String {
3843    let val = getiparam(table, name);
3844    convbase(val, base)
3845}
3846
3847/// String parameter with modifiers (from params.c strgetfn)
3848pub 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
3859// ---------------------------------------------------------------------------
3860// Subscript flag parsing (from getarg subscription flags)
3861// ---------------------------------------------------------------------------
3862
3863/// Parse subscription flags from (flags) prefix
3864pub 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; // +1 for '(' offset, +1 for ')'
3879                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// ---------------------------------------------------------------------------
3939// Tests
3940// ---------------------------------------------------------------------------
3941
3942#[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        // $$ should be the PID
4020        let pid = table.get_value("$").unwrap().as_integer();
4021        assert!(pid > 0);
4022
4023        // SHLVL should be at least 1
4024        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")); // positional params
4035        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        // Getting through nameref should resolve
4046        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        // Array should be synced
4057        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}