use std::collections::{HashMap, HashSet};
use std::env;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Mutex, OnceLock, RwLock};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use fusevm::Value;
use indexmap::IndexMap;
use crate::config_h::DEFAULT_TMPPREFIX;
use crate::{DPUTS, DPUTS2};
use crate::func_body_fmt::FuncBodyFmt;
use crate::lex::parse_subscript;
use crate::ported::builtin::{LASTVAL, PPARAMS};
use crate::ported::config_h::{MACHTYPE, OSTYPE, VENDOR};
use crate::ported::exec::FORKLEVEL;
use crate::ported::hashtable::emptycmdnamtable;
use crate::ported::hist::{bangchar, hashchar, hatchar, histsiz, resizehistents, saveandpophiststack, savehistsiz};
use crate::ported::init::SHTTY;
use crate::ported::lex::untokenize;
#[allow(unused_imports)]
use crate::ported::math::{
matheval, mathevali, MN_FLOAT, MN_FLOAT as MN_FLT, MN_INTEGER, MN_INTEGER as MN_INT,
};
use crate::ported::math::lastbase;
use crate::ported::mem::{popheap, pushheap};
use crate::ported::modules::parameter::FUNCSTACK;
#[allow(unused_imports)]
use crate::ported::options::{opt_state_get, opt_state_set, optlookup};
use crate::ported::patchlevel::{ZSH_PATCHLEVEL, ZSH_VERSION};
use crate::ported::pattern::{patcompile, pattry};
use crate::ported::zsh_h::PAT_HEAPDUP;
#[allow(unused_imports)]
use crate::ported::signals::{queue_signals, unqueue_signals};
use crate::ported::signals_h::SIGS;
use crate::ported::string::ztrdup;
#[allow(unused_imports)]
use crate::ported::utils::{
adjustwinsize, argzero, colonsplit, errflag, get_username, inittyptab, itype_end,
locallevel as locallevel_fn, posixzero, set_argzero, set_locallevel, set_posixzero, unmeta,
ztrdup_metafy, zerr, zwarn,
};
use crate::ported::utils::{adduserdir, arrlen_ge, dec_locallevel, inc_locallevel, metafy, quotedzputs, xsymlink};
#[allow(unused_imports)]
use crate::ported::zsh_h::{
gsu_array, gsu_float, gsu_hash, gsu_integer, gsu_scalar, hashnode, hashtable, isset, mnumber, paramdef,
unset, value, HashTable, Marker, Param, ALLEXPORT, ASSPM_AUGMENT,
ASSPM_ENV_IMPORT, ASSPM_WARN, AUTONAMEDIRS, EMULATE_KSH, EMULATE_SH, EMULATE_ZSH, EMULATION,
ERRFLAG_ERROR, EXECOPT, FS_FUNC, KSHARRAYS, PM_ARRAY, PM_AUTOLOAD, PM_DECLARED, PM_DEFAULTED,
PM_DONTIMPORT, PM_DONTIMPORT_SUID, PM_EFLOAT, PM_EXPORTED, PM_FFLOAT, PM_HASHED, PM_HASHELEM,
PM_INTEGER, PM_LEFT, PM_LOCAL, PM_NAMEDDIR, PM_NAMEREF, PM_NORESTORE, PM_READONLY,
PM_READONLY_SPECIAL, PM_REMOVABLE, PM_RIGHT_B, PM_RIGHT_Z, PM_RO_BY_DESIGN, PM_SCALAR,
PM_HIDE, PM_SPECIAL, PM_TAGGED, PM_TIED, PM_TYPE, PM_UNIQUE, PM_UNSET, PM_UPPER, PRINT_INCLUDEVALUE,
PRINT_KV_PAIR, PRINT_LINE, PRINT_NAMEONLY, PRINT_POSIX_EXPORT, PRINT_POSIX_READONLY,
PRINT_TYPE, PRINT_TYPESET, SCANPM_ARRONLY, SCANPM_CHECKING, SCANPM_ISVAR_AT, SCANPM_KEYMATCH,
SCANPM_MATCHKEY, SCANPM_MATCHMANY, SCANPM_MATCHVAL, SCANPM_NONAMEREF, SCANPM_WANTINDEX,
SCANPM_WANTKEYS, SCANPM_WANTVALS, TERM_BAD, VALFLAG_EMPTY, VALFLAG_INV, VALFLAG_SUBST,
WARNCREATEGLOBAL, WARNNESTEDVAR,TERM_UNKNOWN, param, POSIXARGZERO
};
use crate::ported::zsh_h::{HashNode, Inbrack, CBASES, CHASELINKS, HFILE_USE_OPTIONS, Meta, OCTALZEROES, PM_LOWER, PRIVILEGED, SCANPM_ASSIGNING};
use crate::ported::zsh_system_h::DEFAULT_TIMEFMT;
pub static LC_UPDATE_NEEDED: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub static FOUNDPARAM: OnceLock<Mutex<Option<String>>> = OnceLock::new();
pub fn rprompt_indent_unsetfn(pm: &mut param, exp: i32) {
stdunsetfn(pm, exp);
*RPROMPT_INDENT.lock().unwrap() = 1;
}
#[inline]
#[allow(non_snake_case)]
pub fn IPDEF1(A: &str, B: usize, C: i32) -> paramdef {
paramdef {
name: A.into(),
flags: (PM_INTEGER | PM_SPECIAL) as i32 | C,
gsu: B,
..Default::default()
}
}
#[inline]
#[allow(non_snake_case)]
pub fn IPDEF2(A: &str, B: usize, C: i32) -> paramdef {
paramdef {
name: A.into(),
flags: (PM_SCALAR | PM_SPECIAL) as i32 | C,
gsu: B,
..Default::default()
}
}
#[allow(non_upper_case_globals)]
pub static locallevel: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
#[inline]
#[allow(non_snake_case)]
pub fn LCIPDEF(name: &str) -> paramdef {
IPDEF2(name, 0, PM_UNSET as i32) }
#[inline]
#[allow(non_snake_case)]
pub fn IPDEF4(A: &str, B: usize) -> paramdef {
paramdef {
name: A.into(),
flags: (PM_INTEGER | PM_READONLY_SPECIAL) as i32,
var: B,
..Default::default()
}
}
#[inline]
#[allow(non_snake_case)]
pub fn IPDEF5(A: &str, B: usize, F: usize) -> paramdef {
paramdef {
name: A.into(),
flags: (PM_INTEGER | PM_SPECIAL) as i32,
var: B,
gsu: F,
..Default::default()
}
}
#[inline]
#[allow(non_snake_case)]
pub fn IPDEF5U(A: &str, B: usize, F: usize) -> paramdef {
paramdef {
name: A.into(),
flags: (PM_INTEGER | PM_SPECIAL | PM_UNSET) as i32,
var: B,
gsu: F,
..Default::default()
}
}
#[inline]
#[allow(non_snake_case)]
pub fn IPDEF6(A: &str, B: usize, F: usize) -> paramdef {
paramdef {
name: A.into(),
flags: (PM_INTEGER | PM_SPECIAL | PM_DONTIMPORT) as i32,
var: B,
gsu: F,
..Default::default()
}
}
#[inline]
#[allow(non_snake_case)]
pub fn IPDEF7(A: &str, B: usize) -> paramdef {
paramdef {
name: A.into(),
flags: (PM_SCALAR | PM_SPECIAL) as i32,
var: B,
..Default::default()
}
}
#[inline]
#[allow(non_snake_case)]
pub fn IPDEF7U(A: &str, B: usize) -> paramdef {
paramdef {
name: A.into(),
flags: (PM_SCALAR | PM_SPECIAL | PM_UNSET) as i32,
var: B,
..Default::default()
}
}
#[inline]
#[allow(non_snake_case)]
pub fn IPDEF7R(A: &str, B: usize) -> paramdef {
paramdef {
name: A.into(),
flags: (PM_SCALAR | PM_SPECIAL | PM_DONTIMPORT_SUID) as i32,
var: B,
..Default::default()
}
}
#[inline]
#[allow(non_snake_case)]
pub fn IPDEF9(A: &str, B: usize, C: usize, D: i32) -> paramdef {
paramdef {
name: A.into(),
flags: (PM_ARRAY | PM_SPECIAL | PM_DONTIMPORT) as i32 | D,
var: B,
..Default::default()
}
}
#[inline]
#[allow(non_snake_case)]
pub fn IPDEF8(A: &str, B: usize, C: usize, D: i32) -> paramdef {
paramdef {
name: A.into(),
flags: (PM_SCALAR | PM_SPECIAL) as i32 | D,
var: B,
..Default::default()
}
}
#[inline]
#[allow(non_snake_case)]
pub fn IPDEF10(A: &str, B: usize) -> paramdef {
paramdef {
name: A.into(),
flags: (PM_ARRAY | PM_SPECIAL) as i32,
gsu: B,
..Default::default()
}
}
#[allow(unused_variables)]
pub fn newparamtable(size: i32, name: &str) -> Option<HashTable> {
let hsize = if size == 0 { 17 } else { size };
let mut nodes: Vec<Option<HashNode>> = Vec::with_capacity(hsize as usize);
for _ in 0..hsize {
nodes.push(None);
}
Some(Box::new(hashtable {
hsize,
ct: 0,
nodes,
tmpdata: 0,
hash: None,
emptytable: None,
filltable: None,
cmpnodes: None,
addnode: None,
getnode: None,
getnode2: None,
removenode: None,
disablenode: None,
enablenode: None,
freenode: None,
printnode: None,
scantab: None,
}))
}
pub fn loadparamnode(
_ht: &HashTable,
pm: Option<Param>,
nam: &str,
) -> Option<Param> {
let (level, modname) = match &pm {
Some(p) if p.node.flags & PM_AUTOLOAD as i32 != 0 && p.u_str.is_some() => {
(p.level, p.u_str.clone().unwrap())
}
_ => return pm, };
let mut pm = paramtab().write().unwrap().get(nam).cloned();
while let Some(ref p) = pm {
if p.level > level {
pm = p.old.clone().map(|b| Param::from(b));
} else {
break;
}
}
let still_bad = match &pm {
Some(p) => p.level != level || p.node.flags & PM_AUTOLOAD as i32 != 0,
None => true,
};
if still_bad {
pm = None;
zerr(&format!(
"autoloading module {} failed to define parameter: {}",
modname, nam
));
}
pm }
#[allow(non_camel_case_types)]
#[derive(Clone, Debug)]
pub struct special_paramdef {
pub name: &'static str,
pub pm_type: u32, pub pm_flags: u32, pub tied_name: Option<&'static str>,
}
pub const SPECIAL_PARAMS_ZSH_START: usize = 54;
pub const special_params: &[special_paramdef] = &[
special_paramdef {
name: "#",
pm_type: PM_INTEGER,
pm_flags: PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "ERRNO",
pm_type: PM_INTEGER,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "GID",
pm_type: PM_INTEGER,
pm_flags: PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "EGID",
pm_type: PM_INTEGER,
pm_flags: PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "HISTSIZE",
pm_type: PM_INTEGER,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "RANDOM",
pm_type: PM_INTEGER,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "SAVEHIST",
pm_type: PM_INTEGER,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "SECONDS",
pm_type: PM_INTEGER,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "UID",
pm_type: PM_INTEGER,
pm_flags: PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "EUID",
pm_type: PM_INTEGER,
pm_flags: PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "TTYIDLE",
pm_type: PM_INTEGER,
pm_flags: PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "USERNAME",
pm_type: PM_SCALAR,
pm_flags: PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "-",
pm_type: PM_SCALAR,
pm_flags: PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "histchars",
pm_type: PM_SCALAR,
pm_flags: PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "HOME",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "TERM",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "TERMINFO",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "TERMINFO_DIRS",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "WORDCHARS",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "IFS",
pm_type: PM_SCALAR,
pm_flags: PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "_",
pm_type: PM_SCALAR,
pm_flags: PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "KEYBOARD_HACK",
pm_type: PM_SCALAR,
pm_flags: PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "0",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "!",
pm_type: PM_INTEGER,
pm_flags: PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "$",
pm_type: PM_INTEGER,
pm_flags: PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "?",
pm_type: PM_INTEGER,
pm_flags: PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "HISTCMD",
pm_type: PM_INTEGER,
pm_flags: PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "LINENO",
pm_type: PM_INTEGER,
pm_flags: PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "PPID",
pm_type: PM_INTEGER,
pm_flags: PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "ZSH_SUBSHELL",
pm_type: PM_INTEGER,
pm_flags: PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "COLUMNS",
pm_type: PM_INTEGER,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "LINES",
pm_type: PM_INTEGER,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "ZLE_RPROMPT_INDENT",
pm_type: PM_INTEGER,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "SHLVL",
pm_type: PM_INTEGER,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "FUNCNEST",
pm_type: PM_INTEGER,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "OPTIND",
pm_type: PM_INTEGER,
pm_flags: PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "TRY_BLOCK_ERROR",
pm_type: PM_INTEGER,
pm_flags: PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "TRY_BLOCK_INTERRUPT",
pm_type: PM_INTEGER,
pm_flags: PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "OPTARG",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "NULLCMD",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "POSTEDIT",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "READNULLCMD",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "PS1",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "RPS1",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "RPROMPT",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "PS2",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "RPS2",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "RPROMPT2",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "PS3",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "PS4",
pm_type: PM_SCALAR,
pm_flags: PM_DONTIMPORT_SUID,
tied_name: None,
},
special_paramdef {
name: "SPROMPT",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "*",
pm_type: PM_ARRAY,
pm_flags: PM_READONLY | PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "@",
pm_type: PM_ARRAY,
pm_flags: PM_READONLY | PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "CDPATH",
pm_type: PM_SCALAR,
pm_flags: PM_TIED,
tied_name: Some("cdpath"),
},
special_paramdef {
name: "FIGNORE",
pm_type: PM_SCALAR,
pm_flags: PM_TIED,
tied_name: Some("fignore"),
},
special_paramdef {
name: "FPATH",
pm_type: PM_SCALAR,
pm_flags: PM_TIED,
tied_name: Some("fpath"),
},
special_paramdef {
name: "MAILPATH",
pm_type: PM_SCALAR,
pm_flags: PM_TIED,
tied_name: Some("mailpath"),
},
special_paramdef {
name: "PATH",
pm_type: PM_SCALAR,
pm_flags: PM_TIED,
tied_name: Some("path"),
},
special_paramdef {
name: "PSVAR",
pm_type: PM_SCALAR,
pm_flags: PM_TIED,
tied_name: Some("psvar"),
},
special_paramdef {
name: "ZSH_EVAL_CONTEXT",
pm_type: PM_SCALAR,
pm_flags: PM_READONLY | PM_TIED,
tied_name: Some("zsh_eval_context"),
},
special_paramdef {
name: "MODULE_PATH",
pm_type: PM_SCALAR,
pm_flags: PM_DONTIMPORT | PM_TIED,
tied_name: Some("module_path"),
},
special_paramdef {
name: "MANPATH",
pm_type: PM_SCALAR,
pm_flags: PM_TIED,
tied_name: Some("manpath"),
},
special_paramdef {
name: "LANG",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "LC_ALL",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "LC_COLLATE",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "LC_CTYPE",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "LC_MESSAGES",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "LC_NUMERIC",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "LC_TIME",
pm_type: PM_SCALAR,
pm_flags: PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "ARGC",
pm_type: PM_INTEGER,
pm_flags: PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "HISTCHARS",
pm_type: PM_SCALAR,
pm_flags: PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "status",
pm_type: PM_INTEGER,
pm_flags: PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "prompt",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "PROMPT",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "PROMPT2",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "PROMPT3",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "PROMPT4",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "argv",
pm_type: PM_ARRAY,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "fignore",
pm_type: PM_ARRAY,
pm_flags: PM_TIED,
tied_name: Some("FIGNORE"),
},
special_paramdef {
name: "cdpath",
pm_type: PM_ARRAY,
pm_flags: PM_TIED,
tied_name: Some("CDPATH"),
},
special_paramdef {
name: "fpath",
pm_type: PM_ARRAY,
pm_flags: PM_TIED,
tied_name: Some("FPATH"),
},
special_paramdef {
name: "mailpath",
pm_type: PM_ARRAY,
pm_flags: PM_TIED,
tied_name: Some("MAILPATH"),
},
special_paramdef {
name: "manpath",
pm_type: PM_ARRAY,
pm_flags: PM_TIED,
tied_name: Some("MANPATH"),
},
special_paramdef {
name: "psvar",
pm_type: PM_ARRAY,
pm_flags: PM_TIED,
tied_name: Some("PSVAR"),
},
special_paramdef {
name: "zsh_eval_context",
pm_type: PM_ARRAY,
pm_flags: PM_TIED | PM_READONLY,
tied_name: Some("ZSH_EVAL_CONTEXT"),
},
special_paramdef {
name: "module_path",
pm_type: PM_ARRAY,
pm_flags: PM_TIED,
tied_name: Some("MODULE_PATH"),
},
special_paramdef {
name: "path",
pm_type: PM_ARRAY,
pm_flags: PM_TIED,
tied_name: Some("PATH"),
},
special_paramdef {
name: "pipestatus",
pm_type: PM_ARRAY,
pm_flags: 0,
tied_name: None,
},
];
pub const special_params_sh: &[special_paramdef] = &[
special_paramdef {
name: "CDPATH",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "FIGNORE",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "FPATH",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "MAILPATH",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "PATH",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "PSVAR",
pm_type: PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "ZSH_EVAL_CONTEXT",
pm_type: PM_SCALAR,
pm_flags: PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "MODULE_PATH",
pm_type: PM_SCALAR,
pm_flags: PM_DONTIMPORT,
tied_name: None,
},
];
pub fn getparamnode(ht: &HashTable, nam: &str) -> Option<Param> {
let pm = paramtab().read().unwrap().get(nam).cloned();
let pm = loadparamnode(ht, pm, nam);
if let Some(p) = pm {
if p.node.flags & PM_UNSET as i32 == 0 {
return resolve_nameref(Some(p));
}
return Some(p);
}
None
}
pub fn scancopyparams(pm: &mut param, _flags: i32, outtable: &mut HashMap<String, Box<param>>) {
let mut tpm = Box::new(pm.clone()); tpm.old = None;
tpm.env = None;
tpm.ename = None; copyparam(&mut tpm, pm, 0); let nam = tpm.node.nam.clone();
outtable.insert(nam, tpm); }
pub fn copyparamtable(ht: Option<&HashTable>, name: &str) -> Option<HashTable> {
let ht = ht?;
newparamtable(ht.hsize, name)
}
pub fn deleteparamtable(t: Option<HashTable>) {
let odelunset = DELUNSET.swap(1, Ordering::Relaxed); if let Some(table) = t {
drop(table);
}
DELUNSET.store(odelunset, Ordering::Relaxed); }
pub fn scancountparams(_hn: ¶m, flags: i32, numparamvals: &mut u32) {
*numparamvals += 1;
if (flags as u32 & SCANPM_WANTKEYS) != 0 && (flags as u32 & SCANPM_WANTVALS) != 0 {
*numparamvals += 1;
}
}
pub fn scanparamvals(
pm: &mut param,
flags: i32,
) {
let f = flags as u32;
if NUMPARAMVALS.load(Ordering::Relaxed) != 0
&& (f & SCANPM_MATCHMANY) == 0
&& (f & (SCANPM_MATCHVAL | SCANPM_MATCHKEY | SCANPM_KEYMATCH)) != 0
{
return;
}
if (f & SCANPM_KEYMATCH) != 0 {
let scanstr = scanstr_lock().lock().unwrap().clone();
if let Some(s) = scanstr {
let matched = patcompile(&pm.node.nam, PAT_HEAPDUP as i32, None)
.map_or(false, |p| pattry(&p, &s));
if !matched {
return;
}
} else {
return;
}
} else if (f & SCANPM_MATCHKEY) != 0 {
let prog = scanprog_lock().lock().unwrap().clone();
if let Some(p) = prog {
let matched = patcompile(&p, PAT_HEAPDUP as i32, None)
.map_or(false, |prog| pattry(&prog, &pm.node.nam));
if !matched {
return;
}
} else {
return;
}
}
set_foundparam(Some(pm.node.nam.clone()));
if (f & SCANPM_WANTKEYS) != 0 {
paramvals_lock().lock().unwrap().push(pm.node.nam.clone());
NUMPARAMVALS.fetch_add(1, Ordering::Relaxed);
if (f & (SCANPM_WANTVALS | SCANPM_MATCHVAL)) == 0 {
return;
}
}
let mut vbuf = value {
pm: None, arr: Vec::new(),
scanflags: 0,
valflags: 0,
start: 0,
end: -1,
};
let s = strgetfn(pm);
let _ = vbuf;
if (f & SCANPM_MATCHVAL) != 0 {
let prog = scanprog_lock().lock().unwrap().clone();
let matched = prog
.and_then(|p| patcompile(&p, PAT_HEAPDUP as i32, None))
.map_or(false, |prog| pattry(&prog, &s));
if matched {
paramvals_lock().lock().unwrap().push(s);
let inc = if (f & SCANPM_WANTVALS) != 0 {
1
} else if (f & SCANPM_WANTKEYS) == 0 {
1
} else {
0
};
NUMPARAMVALS.fetch_add(inc, Ordering::Relaxed);
} else if (f & SCANPM_WANTKEYS) != 0 {
paramvals_lock().lock().unwrap().pop();
NUMPARAMVALS.fetch_sub(1, Ordering::Relaxed);
}
} else {
paramvals_lock().lock().unwrap().push(s);
NUMPARAMVALS.fetch_add(1, Ordering::Relaxed);
}
set_foundparam(None);
}
#[allow(unused_variables)]
pub fn paramvalarr(ht: &HashTable, flags: i32) -> Vec<String> {
let scanprog_set = scanprog_lock().lock().unwrap().is_some(); DPUTS!(
(flags as u32 & (SCANPM_MATCHKEY | SCANPM_MATCHVAL)) != 0 && !scanprog_set, "BUG: scanning hash without scanprog set" );
let flags_u = flags as u32;
let want_keys = (flags_u & SCANPM_WANTKEYS) != 0;
let want_vals = (flags_u & SCANPM_WANTVALS) != 0;
let want_index = (flags_u & SCANPM_WANTINDEX) != 0;
let tab = paramtab().read().unwrap();
let mut out: Vec<String> = Vec::with_capacity(tab.len() * 2);
let mut idx: i64 = 0;
for (k, pm) in tab.iter() {
let pflags = pm.node.flags;
idx += 1; if pflags & PM_UNSET as i32 != 0 {
continue;
}
if pflags & PM_HASHELEM as i32 != 0 {
continue;
}
if want_index {
out.push(idx.to_string());
}
if want_keys {
out.push(k.clone());
}
if want_vals || (!want_keys && !want_index) {
let v = pm.u_str.clone().unwrap_or_default();
out.push(v);
}
}
out
}
pub fn getvaluearr(v: Option<&mut value>) -> Vec<String> {
let v = match v {
Some(v) => v,
None => return Vec::new(),
};
if !v.arr.is_empty() {
return v.arr.clone();
}
let pm = match v.pm.as_mut() {
Some(p) => p,
None => return Vec::new(),
};
let t = PM_TYPE(pm.node.flags as u32);
if t == PM_ARRAY {
v.arr = arrgetfn(pm);
return v.arr.clone();
}
if t == PM_HASHED {
v.arr = Vec::new();
v.start = 0;
v.end = 1; return v.arr.clone();
}
Vec::new()
}
pub fn issetvar(name: &str) -> i32 {
let mut vbuf = value {
pm: None,
arr: Vec::new(),
scanflags: 0,
valflags: 0,
start: 0,
end: -1,
};
let mut cursor: &str = name;
let v = match getvalue(Some(&mut vbuf), &mut cursor, 1) {
Some(v) => v,
None => return 0,
};
if !cursor.is_empty() {
return 0; }
if (v.scanflags as u32 & !SCANPM_ARRONLY) != 0 {
return if v.end > 1 { 1 } else { 0 }; }
let slice = v.start != 0 || v.end != -1; let pm = match v.pm.as_ref() {
Some(p) => p,
None => return 0,
};
if PM_TYPE(pm.node.flags as u32) != PM_ARRAY || !slice {
return if !slice && (pm.node.flags as u32 & PM_UNSET) == 0 {
1
} else {
0
}; }
if v.end == 0 {
return 0; }
let arr = getvaluearr(Some(v));
if arr.is_empty() {
return 0; }
let bound: usize = if v.end < 0 {
(-v.end) as usize
} else {
v.end as usize
};
if arrlen_ge(&arr, bound) {
1
} else {
0
}
}
pub fn split_env_string(env: &str) -> Option<(String, String)> {
if env.is_empty() {
return None;
}
let bytes = env.as_bytes();
let mut i = 0;
while i < bytes.len() && bytes[i] != b'=' {
if bytes[i] >= 128 {
return None; }
i += 1;
}
if i > 0 && i < bytes.len() && bytes[i] == b'=' {
let name = String::from_utf8_lossy(&bytes[..i]).into_owned(); let value = String::from_utf8_lossy(&bytes[i + 1..]).into_owned(); Some((name, value)) } else {
None }
}
pub fn createparamtable() {
let _ = paramtab();
let _ = realparamtab();
let add_special = |ip: &special_paramdef, tab: &mut HashMap<String, Param>| {
let gsu_s: Option<Box<gsu_scalar>> = match ip.name {
"HOME" => Some(Box::new(HOME_GSU.clone())), "IFS" => Some(Box::new(IFS_GSU.clone())), "TERM" => Some(Box::new(TERM_GSU.clone())), "TERMINFO" => Some(Box::new(TERMINFO_GSU.clone())), "TERMINFO_DIRS" => Some(Box::new(TERMINFODIRS_GSU.clone())), "WORDCHARS" => Some(Box::new(WORDCHARS_GSU.clone())), "USERNAME" => Some(Box::new(USERNAME_GSU.clone())), "KEYBOARD_HACK" => Some(Box::new(KEYBOARDHACK_GSU.clone())), "HISTCHARS" | "histchars" => Some(Box::new(HISTCHARS_GSU.clone())), _ => None,
};
let pm = Box::new(param {
node: hashnode {
next: None,
nam: ip.name.to_string(),
flags: (ip.pm_type | ip.pm_flags | PM_SPECIAL) as i32,
},
u_data: 0,
u_arr: None,
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s, gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
});
tab.insert(ip.name.to_string(), pm);
};
{
let mut tab = paramtab().write().unwrap();
for ip in special_params[..SPECIAL_PARAMS_ZSH_START].iter() {
add_special(ip, &mut tab);
}
}
let is_sh_ksh = EMULATION(EMULATE_SH | EMULATE_KSH);
{
let mut tab = paramtab().write().unwrap();
if is_sh_ksh {
for ip in special_params_sh.iter() {
add_special(ip, &mut tab);
}
} else {
for ip in special_params[SPECIAL_PARAMS_ZSH_START..].iter() {
add_special(ip, &mut tab);
}
}
}
setiparam("MAILCHECK", 60); setiparam("KEYTIMEOUT", 40); setiparam("LISTMAX", 100);
setsparam("TMPPREFIX", &ztrdup_metafy(DEFAULT_TMPPREFIX)); setsparam(
"TIMEFMT",
&ztrdup_metafy(DEFAULT_TIMEFMT),
);
let mut host_buf = [0u8; 256];
let host_rc = unsafe { libc::gethostname(host_buf.as_mut_ptr() as *mut libc::c_char, 256) };
let hostname = if host_rc == 0 {
std::ffi::CStr::from_bytes_until_nul(&host_buf)
.ok()
.and_then(|c| c.to_str().ok())
.unwrap_or("")
.to_string()
} else {
String::new()
};
setsparam("HOST", &ztrdup_metafy(&hostname));
let logname = unsafe {
let p = libc::getlogin();
if p.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned()
}
}; let logname = if logname.is_empty() {
get_username()
} else {
logname
};
setsparam("LOGNAME", &ztrdup_metafy(&logname));
pushheap();
for (iname, ivalue) in env::vars() {
if iname.is_empty() {
continue;
}
if iname.as_bytes()[0].is_ascii_digit() {
continue;
}
if !isident(&iname) {
continue;
}
if iname.contains('[') {
continue;
}
let blocked = {
let tab = paramtab().read().unwrap();
tab.get(&iname)
.map(|pm| dontimport(pm.node.flags) != 0)
.unwrap_or(false)
};
if blocked {
continue;
}
let metafied = metafy(&ivalue);
let _ = assignsparam(&iname, &metafied, ASSPM_ENV_IMPORT);
let mut tab = paramtab().write().unwrap();
if let Some(pm) = tab.get_mut(&iname) {
pm.node.flags |= PM_EXPORTED as i32;
let env_str = if pm.node.flags & PM_SPECIAL as i32 != 0 {
mkenvstr(&iname, &ivalue, pm.node.flags)
} else {
format!("{}={}", iname, ivalue)
};
pm.env = Some(env_str);
}
}
popheap();
let is_zsh = EMULATION(EMULATE_ZSH);
let home_val = home_lock().lock().expect("home poisoned").clone();
let home_action: Option<bool> = {
let mut tab = paramtab().write().unwrap();
if let Some(pm) = tab.get_mut("HOME") {
if is_zsh {
pm.node.flags &= !(PM_UNSET as i32); if pm.node.flags & PM_EXPORTED as i32 == 0 {
Some(true)
} else {
Some(false)
}
} else if home_val.is_empty() {
pm.node.flags |= PM_UNSET as i32; Some(false)
} else {
Some(false)
}
} else {
None
}
};
if let Some(true) = home_action {
addenv("HOME", &home_val); }
let logname_export: Option<String> = {
let tab = paramtab().read().unwrap();
tab.get("LOGNAME").and_then(|pm| {
if pm.node.flags & PM_EXPORTED as i32 == 0 {
pm.u_str.clone()
} else {
None
}
})
};
if let Some(ustr) = logname_export {
addenv("LOGNAME", &ustr); }
let new_shlvl: i32 = getsparam("SHLVL")
.or_else(|| env::var("SHLVL").ok())
.and_then(|s| s.parse().ok())
.unwrap_or(0)
+ 1; setiparam("SHLVL", new_shlvl as i64);
addenv("SHLVL", &new_shlvl.to_string());
let utsname = nix::sys::utsname::uname().ok();
let cputype = utsname
.as_ref()
.map(|u| u.machine().to_string_lossy().to_string())
.unwrap_or_else(|| "unknown".to_string());
setsparam("CPUTYPE", &ztrdup_metafy(&cputype)); setsparam(
"MACHTYPE",
&ztrdup_metafy(MACHTYPE),
);
setsparam(
"OSTYPE",
&ztrdup_metafy(OSTYPE),
);
let tty_str = {
let p = unsafe { libc::ttyname(0) };
if !p.is_null() {
unsafe { std::ffi::CStr::from_ptr(p) }
.to_string_lossy()
.to_string()
} else {
String::new()
}
};
setsparam("TTY", &ztrdup_metafy(&tty_str)); setsparam(
"VENDOR",
&ztrdup_metafy(VENDOR),
);
let argv0 = env::args().next().unwrap_or_default();
setsparam("ZSH_ARGZERO", &ztrdup(&argv0)); setsparam(
"ZSH_VERSION",
&ztrdup_metafy(ZSH_VERSION),
); setsparam(
"ZSH_PATCHLEVEL",
&ztrdup_metafy(ZSH_PATCHLEVEL),
);
let mut signals_arr: Vec<String> = Vec::new();
for &(name, _num) in SIGS.iter() {
signals_arr.push(ztrdup_metafy(name));
}
#[cfg(target_os = "linux")]
{
for sig in libc::SIGRTMIN()..=libc::SIGRTMAX() {
let nm = crate::ported::signals::rtsigname(sig);
if !nm.is_empty() {
signals_arr.push(ztrdup_metafy(&nm));
}
}
}
{
let mut tab = paramtab().write().unwrap();
let pm = Box::new(param {
node: hashnode {
next: None,
nam: "signals".to_string(),
flags: (PM_ARRAY | PM_SPECIAL) as i32,
},
u_data: 0,
u_arr: Some(signals_arr),
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
});
tab.insert("signals".to_string(), pm);
}
}
static PARAMTAB_HASHED_STORAGE_INNER: OnceLock<Mutex<HashMap<String, IndexMap<String, String>>>> =
OnceLock::new();
pub fn assigngetset(pm: &mut param) {
match PM_TYPE(pm.node.flags as u32) {
x if x == PM_SCALAR || x == PM_NAMEREF => {
pm.gsu_s = Some(Box::new(gsu_scalar {
getfn: strgetfn,
setfn: strsetfn,
unsetfn: stdunsetfn,
}));
}
x if x == PM_INTEGER => {
pm.gsu_i = Some(Box::new(gsu_integer {
getfn: intgetfn,
setfn: intsetfn,
unsetfn: stdunsetfn,
}));
}
x if x == PM_EFLOAT || x == PM_FFLOAT => {
pm.gsu_f = Some(Box::new(gsu_float {
getfn: floatgetfn,
setfn: floatsetfn,
unsetfn: stdunsetfn,
}));
}
x if x == PM_ARRAY => {
pm.gsu_a = Some(Box::new(gsu_array {
getfn: arrgetfn,
setfn: arrsetfn,
unsetfn: stdunsetfn,
}));
}
x if x == PM_HASHED => {
pm.gsu_h = Some(Box::new(gsu_hash {
getfn: hashgetfn,
setfn: hashsetfn,
unsetfn: stdunsetfn,
}));
}
_ => {
DPUTS!(true, "BUG: tried to create param node without valid flag");
}
}
}
pub fn createparam(
name: &str,
mut flags: i32,
) -> Option<Param> {
let oldpm: Option<Param> = if !name.is_empty() {
paramtab().read().ok().and_then(|t| t.get(name).cloned())
} else {
None
};
if !name.is_empty() {
if isset(ALLEXPORT) && (flags as u32 & PM_HASHELEM) == 0 {
flags |= PM_EXPORTED as i32;
}
}
let cur_locallevel = locallevel.load(Ordering::Relaxed);
DPUTS!(
match &oldpm {
Some(op) => op.level > cur_locallevel, None => false, },
"BUG: old local parameter not deleted" );
let reuse = match &oldpm {
Some(op) => op.level == cur_locallevel || (flags as u32 & PM_LOCAL) == 0,
None => false,
};
let mut pm: Param = if reuse {
let mut existing = oldpm.unwrap(); existing.base = 0; existing.width = 0; existing
} else {
Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: 0,
},
u_data: 0,
u_arr: None,
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: oldpm, level: if (flags as u32 & PM_LOCAL) != 0 {
cur_locallevel
} else {
0
},
})
};
pm.node.flags = flags & !(PM_LOCAL as i32); if (pm.node.flags as u32 & PM_SPECIAL) == 0 {
assigngetset(&mut pm); }
if !name.is_empty() {
let cloned = pm.clone();
paramtab().write().unwrap().insert(name.to_string(), pm);
return Some(cloned);
}
Some(pm) }
pub fn shempty() {}
pub fn setsparam(s: &str, val: &str) -> Option<Param> {
assignsparam(s, val, ASSPM_WARN as i32) }
pub fn createspecialhash(name: &str, flags: i32) -> Option<Param> {
let mut pm = createparam(name, (PM_SPECIAL | PM_HASHED) as i32 | flags)?;
if pm.old.is_some() {
pm.level = locallevel.load(Ordering::Relaxed) as i32;
}
let ht = Box::new(hashtable {
hsize: 0,
ct: 0,
nodes: Vec::new(),
tmpdata: 0,
hash: None,
emptytable: None,
filltable: None,
cmpnodes: None,
addnode: None,
getnode: None,
getnode2: None,
removenode: None,
disablenode: None,
enablenode: None,
freenode: None,
printnode: None,
scantab: None,
});
pm.u_hash = Some(ht);
let _ = name;
Some(pm) }
pub fn copyparam(
tpm: &mut param,
pm: &mut param,
fakecopy: i32,
) {
tpm.node.flags = pm.node.flags; tpm.base = pm.base; tpm.width = pm.width; tpm.level = pm.level; if fakecopy == 0 {
tpm.old = pm.old.take(); tpm.node.flags &= !(PM_SPECIAL as i32); }
match PM_TYPE(pm.node.flags as u32) {
t if t == PM_SCALAR || t == PM_NAMEREF => {
tpm.u_str = Some(strgetfn(pm)); }
t if t == PM_INTEGER => {
tpm.u_val = intgetfn(pm); }
t if t == PM_EFLOAT || t == PM_FFLOAT => {
tpm.u_dval = floatgetfn(pm); }
t if t == PM_ARRAY => {
tpm.u_arr = Some(arrgetfn(pm)); }
t if t == PM_HASHED => {
tpm.u_hash = copyparamtable(pm.u_hash.as_ref(), &pm.node.nam);
}
_ => {}
}
if fakecopy == 0 {
assigngetset(tpm); }
}
pub fn isident(s: &str) -> bool {
if s.is_empty() {
return false;
}
let mut chars = s.chars().peekable();
if chars.peek() == Some(&'.') {
chars.next();
if chars.peek().is_none_or(|c| c.is_ascii_digit()) {
return false;
}
}
let first = match chars.next() {
Some(c) => c,
None => return false,
};
if first.is_ascii_digit() {
return chars.all(|c| c.is_ascii_digit());
}
if !first.is_alphabetic() && first != '_' {
return false;
}
for c in chars {
if c == '[' {
let mut depth = 1i32;
let saw_close = s.split('[').skip(1).next().is_some_and(|tail| {
for ch in tail.chars() {
match ch {
'[' => depth += 1,
']' => {
depth -= 1;
if depth == 0 {
return true;
}
}
_ => {}
}
}
false
});
return saw_close;
}
if !c.is_alphanumeric() && c != '_' && c != '.' {
return false;
}
}
true
}
pub(crate) fn getarg<'a>(
idx: &'a str,
arr: Option<&[String]>,
assoc: Option<&IndexMap<String, String>>,
scalar: Option<&str>,
) -> Option<getarg_out<'a>> {
let rest = idx.strip_prefix('(')?;
if rest.starts_with(')') || rest.contains('[') {
return None;
}
let bytes = rest.as_bytes();
let mut i: usize = 0;
let mut num: i64 = 1;
let mut beg: i64 = 0;
let mut has_beg = false;
let flags_start = 0_usize;
let mut flags_end = 0_usize;
let mut bad = false;
while i < bytes.len() && bytes[i] != b')' {
let c = bytes[i] as char;
match c {
'r' | 'R' | 'i' | 'I' | 'e' | 'k' | 'K' | 'w' | 'f' | 'p' => {
i += 1;
flags_end = i;
}
'n' | 'b' => {
if i + 1 >= bytes.len() {
bad = true;
break;
}
let delim = bytes[i + 1];
let arg_start = i + 2;
let mut arg_end = arg_start;
while arg_end < bytes.len() && bytes[arg_end] != delim {
arg_end += 1;
}
if arg_end >= bytes.len() {
bad = true;
break;
}
let arg = std::str::from_utf8(&bytes[arg_start..arg_end]).ok()?;
let parsed: i64 = arg.trim().parse().ok()?;
if c == 'n' {
num = if parsed == 0 { 1 } else { parsed };
} else {
has_beg = true;
beg = if parsed > 0 { parsed - 1 } else { parsed };
}
i = arg_end + 1;
flags_end = i;
}
's' => {
let close = match rest[i..].find(')') {
Some(p) => i + p,
None => return None,
};
let flags = &rest[flags_start..close];
return Some(getarg_out::Flags {
flags,
rest: &rest[close + 1..],
});
}
_ => {
bad = true;
break;
}
}
}
if bad {
return None;
}
if i >= bytes.len() || bytes[i] != b')' {
return None;
}
if flags_end == flags_start {
return None;
}
let flags = &rest[flags_start..flags_end];
let pat = &rest[i + 1..];
let neg_num_flips = num < 0;
if neg_num_flips {
num = -num;
}
if let Some(map) = assoc {
let exact = flags.contains('e');
let key_match = flags.contains('k') || flags.contains('K');
let return_index = flags.contains('i') || flags.contains('I');
let is_uppercase = flags.contains('I') || flags.contains('R') || flags.contains('K');
let return_all = is_uppercase ^ neg_num_flips;
let len = map.len() as i64;
let mut start = beg;
if start < 0 {
start += len;
}
if !return_all && start >= len {
return Some(getarg_out::Value(Value::str("")));
}
let skip = if start < 0 { 0 } else { start as usize };
let key_compare = |target: &str| -> bool {
if key_match {
target == pat
} else if exact {
target == pat
} else {
patcompile(pat, PAT_HEAPDUP as i32, None)
.map_or(false, |p| pattry(&p, target))
}
};
if return_all {
let mut out: Vec<String> = Vec::new();
for (k, v) in map.iter().skip(skip) {
let target = if key_match { k.as_str() } else { v.as_str() };
if key_compare(target) {
out.push(if key_match {
v.clone()
} else if return_index {
k.clone()
} else {
v.clone()
});
}
}
return Some(getarg_out::Value(Value::str(out.join(" "))));
}
let mut remaining = num;
for (k, v) in map.iter().skip(skip) {
let target = if key_match { k.as_str() } else { v.as_str() };
if key_compare(target) {
remaining -= 1;
if remaining == 0 {
return Some(getarg_out::Value(Value::str(if key_match {
v.clone()
} else if return_index {
k.clone()
} else {
v.clone()
})));
}
}
}
return Some(getarg_out::Value(Value::str("")));
}
if let Some(arr) = arr {
if flags.contains('w') || flags.contains('f') {
if let Ok(n) = pat.parse::<i64>() {
let sep_chars: &[char] = if flags.contains('f') {
&['\n']
} else {
&[' ', '\t', '\n']
};
let joined = arr.join(" ");
let words: Vec<&str> = joined
.split(|c: char| sep_chars.contains(&c))
.filter(|w| !w.is_empty())
.collect();
let len = words.len() as i64;
let idx_into = if n > 0 {
(n - 1) as usize
} else if n < 0 {
let off = len + n;
if off < 0 {
return Some(getarg_out::Value(Value::str("")));
}
off as usize
} else {
return Some(getarg_out::Value(Value::str("")));
};
return Some(getarg_out::Value(Value::str(
words
.get(idx_into)
.map(|s| s.to_string())
.unwrap_or_default(),
)));
}
}
let exact = flags.contains('e');
let word = flags.contains('w') || flags.contains('f');
let _ = word;
let return_index = flags.contains('i') || flags.contains('I');
let any_search_flag = flags.contains('r')
|| flags.contains('R')
|| flags.contains('i')
|| flags.contains('I')
|| flags.contains('k')
|| flags.contains('K');
if !any_search_flag {
return None;
}
let reverse = (flags.contains('R') || flags.contains('I')) ^ neg_num_flips;
let pat_used: &str = pat;
let len = arr.len() as i64;
let mut start = beg;
if start < 0 {
start += len;
}
if reverse {
if start < 0 {
return Some(getarg_out::Value(if return_index {
Value::str("0")
} else {
Value::str("")
}));
}
} else if start >= len {
return Some(getarg_out::Value(if return_index {
Value::str((arr.len() + 1).to_string())
} else {
Value::str("")
}));
}
if reverse && !has_beg {
start = len - 1;
}
let iter: Box<dyn Iterator<Item = (usize, &String)>> = if reverse {
let s_idx = if start < 0 { 0 } else { start as usize };
let s_idx = s_idx.min(arr.len().saturating_sub(1));
Box::new(arr[..=s_idx].iter().enumerate().rev())
} else {
let s_idx = if start < 0 { 0 } else { start as usize };
Box::new(arr.iter().enumerate().skip(s_idx))
};
let mut remaining = num;
for (i, s) in iter {
let hit = if exact {
s == pat
} else {
patcompile(pat_used, PAT_HEAPDUP as i32, None)
.map_or(false, |p| pattry(&p, s))
};
if hit {
remaining -= 1;
if remaining == 0 {
return Some(getarg_out::Value(if return_index {
Value::str((i + 1).to_string())
} else {
Value::str(s.clone())
}));
}
}
}
return Some(getarg_out::Value(if return_index {
if flags.contains('I') {
Value::str("0")
} else {
Value::str((arr.len() + 1).to_string())
}
} else {
Value::str("")
}));
}
if let Some(s) = scalar {
if flags.contains('w') || flags.contains('f') {
if let Ok(n) = pat.parse::<i64>() {
let sep_chars: &[char] = if flags.contains('f') {
&['\n']
} else {
&[' ', '\t', '\n']
};
let words: Vec<&str> = s
.split(|c: char| sep_chars.contains(&c))
.filter(|w| !w.is_empty())
.collect();
let len = words.len() as i64;
let idx_into = if n > 0 {
(n - 1) as usize
} else if n < 0 {
let off = len + n;
if off < 0 {
return Some(getarg_out::Value(Value::str("")));
}
off as usize
} else {
return Some(getarg_out::Value(Value::str("")));
};
return Some(getarg_out::Value(Value::str(
words
.get(idx_into)
.map(|s| s.to_string())
.unwrap_or_default(),
)));
}
}
let any_search = flags.contains('r')
|| flags.contains('R')
|| flags.contains('i')
|| flags.contains('I');
if any_search {
let return_index = flags.contains('i') || flags.contains('I');
let want_last = flags.contains('I') || flags.contains('R');
let want_last = want_last ^ neg_num_flips;
let s_chars: Vec<char> = s.chars().collect();
let n = s_chars.len();
let positions: Box<dyn Iterator<Item = usize>> = if want_last {
Box::new((0..=n).rev())
} else {
Box::new(0..=n)
};
let beg_idx_opt: Option<usize> = if has_beg {
let beg_norm = if beg < 0 { beg + n as i64 } else { beg };
Some(if beg_norm < 0 {
0
} else {
(beg_norm as usize).min(n)
})
} else {
None
};
let mut found: Option<(usize, usize)> = None;
let mut remaining = num;
'outer: for start in positions {
if let Some(b_idx) = beg_idx_opt {
if want_last {
if start > b_idx {
continue;
}
} else if start < b_idx {
continue;
}
}
for span_len in 1..=(n - start) {
let cand: String = s_chars[start..start + span_len].iter().collect();
let hit = if flags.contains('e') {
cand == pat
} else {
patcompile(pat, PAT_HEAPDUP as i32, None)
.map_or(false, |p| pattry(&p, &cand))
};
if hit {
remaining -= 1;
if remaining == 0 {
found = Some((start, start + span_len));
break 'outer;
}
break;
}
}
}
return Some(getarg_out::Value(match (found, return_index) {
(Some((s_pos, _)), true) => Value::str((s_pos + 1).to_string()),
(Some((s_pos, _)), false) => Value::str(
s_chars
.get(s_pos)
.map(|c| c.to_string())
.unwrap_or_default(),
),
(None, true) => Value::str(if flags.contains('i') {
(n + 1).to_string()
} else {
"0".to_string()
}),
(None, false) => Value::str(String::new()),
}));
}
}
Some(getarg_out::Flags { flags, rest: pat })
}
pub fn getindex(pptr: &mut &str, v: &mut value, scanflags: i32) -> i32 {
let s = *pptr;
if s.is_empty() || (s.as_bytes()[0] != b'[' && s.as_bytes()[0] != 0xa9) {
return 1;
}
let after_lbrack = &s[1..];
let close_pos = parse_subscript(after_lbrack, ']');
let close_pos = match close_pos {
Some(p) => p,
None => {
zerr("invalid subscript");
*pptr = ""; return 1; }
};
let body = &after_lbrack[..close_pos];
if body == "*" || body == "@" {
if body == "@" && (v.scanflags != 0 || v.pm.is_none()) {
v.scanflags |= SCANPM_ISVAR_AT as i32; }
v.start = 0; v.end = -1; *pptr = &after_lbrack[close_pos + 1..];
return 0; }
let _ = scanflags;
let (start_str, end_str) = match body.split_once(',') {
Some((a, b)) => (a, Some(b)),
None => (body, None),
};
let start: i64 = match start_str.parse() {
Ok(n) => n,
Err(_) => {
*pptr = &after_lbrack[close_pos + 1..];
return 0;
}
};
let mut end: i64 = match end_str {
Some(s) => match s.parse() {
Ok(n) => n,
Err(_) => {
*pptr = &after_lbrack[close_pos + 1..];
return 0;
}
},
None => start,
};
let mut start = start;
let com = end_str.is_some() || start != end;
if start == 0 && end == 0 {
if crate::ported::zsh_h::isset(crate::ported::zsh_h::KSHZEROSUBSCRIPT) {
end = 1; } else {
v.valflags |= VALFLAG_EMPTY; start = -1; }
}
if v.scanflags != 0
&& !com
&& (v.scanflags as u32 & SCANPM_MATCHMANY == 0
|| v.scanflags as u32 & (SCANPM_MATCHKEY | SCANPM_MATCHVAL | SCANPM_KEYMATCH) == 0)
{
v.scanflags = 0;
}
let _ = (SCANPM_ISVAR_AT, SCANPM_WANTINDEX, VALFLAG_INV);
v.start = start as i32; v.end = end as i32;
*pptr = &after_lbrack[close_pos + 1..];
0 }
pub fn getvalue<'a>(
v: Option<&'a mut value>,
pptr: &mut &str,
bracks: i32,
) -> Option<&'a mut value> {
fetchvalue(v, pptr, bracks, SCANPM_CHECKING as i32)
}
pub fn fetchvalue<'a>(
v: Option<&'a mut value>,
pptr: &mut &str,
bracks: i32,
scanflags: i32,
) -> Option<&'a mut value> {
let s = *pptr;
let bytes = s.as_bytes();
if bytes.is_empty() {
return None; }
let c = bytes[0];
let mut ppar: i32 = 0;
let mut end_pos = 0usize;
if c.is_ascii_digit() {
if bracks >= 0 {
let mut idx = 0;
while idx < bytes.len() && bytes[idx].is_ascii_digit() {
ppar = ppar * 10 + (bytes[idx] - b'0') as i32;
idx += 1;
}
end_pos = idx;
} else {
ppar = (c - b'0') as i32;
end_pos = 1;
}
} else if itype_end(s, true) > 0 {
end_pos = itype_end(s, true);
} else if matches!(c, b'?' | b'#' | b'$' | b'!' | b'@' | b'*' | b'-') {
end_pos = 1;
} else {
return None; }
let name = &s[..end_pos];
*pptr = &s[end_pos..];
if ppar > 0 {
if let Some(v) = v {
*v = value {
pm: None,
arr: Vec::new(),
scanflags: 0,
valflags: 0,
start: ppar - 1,
end: ppar,
};
return Some(v);
}
return None;
}
let pm = {
let tab = paramtab().read().unwrap();
let key = if name == "0" { "0" } else { name };
tab.get(key).cloned()
};
let pm = pm?;
if pm.node.flags & PM_UNSET as i32 != 0 && pm.node.flags & PM_DECLARED as i32 == 0 {
return None;
}
let pm = if pm.node.flags & PM_NAMEREF as i32 != 0 && (scanflags as u32) & SCANPM_NONAMEREF == 0
{
resolve_nameref(Some(pm))?
} else {
pm
};
if let Some(v) = v {
*v = value {
pm: Some(pm.clone()),
arr: Vec::new(),
scanflags: 0,
valflags: 0,
start: 0,
end: -1,
};
let pmflags = pm.node.flags;
let isvar_at = name == "@";
if PM_TYPE(pmflags as u32) & (PM_ARRAY | PM_HASHED) != 0 {
let mut sf = scanflags;
if isvar_at {
sf |= SCANPM_ISVAR_AT as i32;
}
if sf == 0 {
sf = SCANPM_ARRONLY as i32;
}
v.scanflags = sf;
}
if bracks > 0 && (pptr.starts_with('[') || pptr.starts_with(Inbrack))
{
if getindex(pptr, v, scanflags) != 0 {
return Some(v); }
} else if (scanflags & SCANPM_ASSIGNING as i32) == 0
&& v.scanflags != 0
&& isset(optlookup("ksharrays"))
{
v.end = 1;
v.scanflags = 0;
} else {}
return Some(v);
}
None
}
pub fn getstrvalue(v: Option<&mut value>) -> String {
let v = match v {
Some(v) => v,
None => return String::new(),
};
if (v.valflags & VALFLAG_INV) != 0 {
let hashed =
v.pm.as_ref()
.map(|p| (p.node.flags as u32 & PM_HASHED) != 0)
.unwrap_or(false);
if !hashed {
return v.start.to_string();
}
}
let pm = match v.pm.as_mut() {
Some(p) => p,
None => return String::new(),
};
let t = PM_TYPE(pm.node.flags as u32);
let pmflags = pm.node.flags as u32;
let mut s: String = if t == PM_HASHED || t == PM_ARRAY {
let arr = arrgetfn(pm);
if v.scanflags != 0 {
arr.join(" ")
} else {
let mut start = v.start;
if start < 0 {
start += arr.len() as i32;
} if start < 0 || (start as usize) >= arr.len() {
String::new()
} else {
arr[start as usize].clone()
}
}
} else if t == PM_INTEGER {
convbase_underscore(
intgetfn(pm),
if pm.base > 0 { pm.base } else { 10 }, pm.width, )
} else if t == PM_EFLOAT || t == PM_FFLOAT {
convfloat_underscore(floatgetfn(pm), pm.width)
} else if t == PM_SCALAR || t == PM_NAMEREF {
strgetfn(pm)
} else {
DPUTS!(true, "BUG: param node without valid type"); String::new() };
if v.valflags & VALFLAG_SUBST != 0 {
let pad_flags = pmflags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z);
if pad_flags != 0 {
let fwidth = if pm.width > 0 {
pm.width as usize
} else {
s.chars().count()
};
if pad_flags == PM_LEFT || pad_flags == (PM_LEFT | PM_RIGHT_Z) {
let trimmed: &str = if pad_flags & PM_RIGHT_Z != 0 {
s.trim_start_matches('0')
} else {
s.trim_start_matches(|c: char| c == ' ' || c == '\t')
};
let len = trimmed.chars().count();
let take = len.min(fwidth);
let mut out: String = trimmed.chars().take(take).collect();
if fwidth > take {
out.extend(std::iter::repeat(' ').take(fwidth - take));
}
s = out;
} else if pad_flags & (PM_RIGHT_B | PM_RIGHT_Z) != 0 {
let charlen = s.chars().count();
if charlen < fwidth {
let mut zero = true;
let mut valprefend: usize = 0;
let numeric_pm = (pmflags & (PM_INTEGER | PM_EFLOAT | PM_FFLOAT)) != 0;
if pad_flags & PM_RIGHT_Z != 0 {
let bytes = s.as_bytes();
let mut t = 0usize;
while t < bytes.len() && (bytes[t] == b' ' || bytes[t] == b'\t') {
t += 1; }
if numeric_pm && t < bytes.len() && bytes[t] == b'-' {
t += 1; }
if (pmflags & PM_INTEGER) != 0 {
let cbases = optlookup("cbases") > 0;
if cbases
&& t + 1 < bytes.len()
&& bytes[t] == b'0'
&& bytes[t + 1] == b'x'
{
t += 2; } else if let Some(hash_off) =
bytes[t..].iter().position(|&b| b == b'#')
{
t += hash_off + 1; }
}
valprefend = t;
if t == bytes.len() {
zero = false; } else if !numeric_pm && !bytes[t].is_ascii_digit() {
zero = false; }
}
let pad_char = if (pad_flags & PM_RIGHT_B) != 0 || !zero {
' '
} else {
'0'
};
let need = fwidth - charlen;
let prefix = &s[..valprefend];
let rest = &s[valprefend..];
let mut out = String::with_capacity(need + s.len());
out.push_str(prefix); out.extend(std::iter::repeat(pad_char).take(need)); out.push_str(rest); s = out;
} else if charlen > fwidth {
let skip = charlen - fwidth;
s = s.chars().skip(skip).collect();
}
}
}
}
s
}
pub fn getarrvalue(arr: &[String], start: i64, end: i64) -> Vec<String> {
let len = arr.len() as i64;
if len == 0 {
return Vec::new();
}
if start > len {
return Vec::new();
}
if end < 0 && (len + end + 1) < 1 {
return Vec::new();
}
if start < 0 && end < 0 && start > end {
return Vec::new();
}
if start < 0 && start < -len {
return Vec::new();
}
let resolve_start = |i: i64| -> i64 {
if i < 0 {
(len + i + 1).max(1)
} else if i == 0 {
1
} else {
i.min(len)
}
};
let resolve_end = |i: i64| -> i64 {
if i < 0 {
(len + i + 1).max(0)
} else if i == 0 {
0
} else {
i.min(len)
}
};
let s = resolve_start(start);
let e = resolve_end(end);
if e < 1 || s > e {
return Vec::new();
}
let s_idx = (s - 1) as usize;
let e_idx = e as usize;
arr[s_idx..e_idx.min(arr.len())].to_vec()
}
pub fn getintvalue(v: Option<&mut value>) -> i64 {
let v = match v {
Some(v) => v,
None => return 0,
};
if (v.valflags & VALFLAG_INV) != 0 {
return v.start as i64;
}
if v.scanflags != 0 {
return 0;
}
let pm = match v.pm.as_mut() {
Some(p) => p,
None => return 0,
};
if PM_TYPE(pm.node.flags as u32) == PM_INTEGER {
return intgetfn(pm);
}
if (pm.node.flags as u32 & (PM_EFLOAT | PM_FFLOAT)) != 0 {
return floatgetfn(pm) as i64;
}
let pm = v.pm.as_mut().unwrap();
let s = strgetfn(pm);
mathevali(&s).unwrap_or(0) }
pub fn getnumvalue(v: Option<&mut value>) -> mnumber {
let v = match v {
Some(v) => v,
None => {
return mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
}
}
};
if (v.valflags & VALFLAG_INV) != 0 {
return mnumber {
l: v.start as i64,
d: 0.0,
type_: MN_INTEGER,
};
}
if v.scanflags != 0 {
return mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
};
}
let pm = match v.pm.as_mut() {
Some(p) => p,
None => {
return mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
}
}
};
let t = PM_TYPE(pm.node.flags as u32);
if t == PM_INTEGER {
return mnumber {
l: intgetfn(pm),
d: 0.0,
type_: MN_INTEGER,
};
}
if t == PM_EFLOAT || t == PM_FFLOAT {
return mnumber {
l: 0,
d: floatgetfn(pm),
type_: MN_FLOAT,
};
}
let s = strgetfn(pm);
matheval(&s) .unwrap_or(mnumber {
l: 0,
d: 0.0,
type_: MN_INTEGER,
})
}
pub fn export_param(pm: &mut param) {
let t = PM_TYPE(pm.node.flags as u32);
if (t & (PM_ARRAY | PM_HASHED)) != 0 {
return;
}
let val: String = if t == PM_INTEGER {
let base = if pm.base > 0 { pm.base } else { 10 };
convbase(intgetfn(pm), base as u32) } else if (pm.node.flags as u32 & (PM_EFLOAT | PM_FFLOAT)) != 0 {
convfloat(floatgetfn(pm), pm.base, pm.node.flags as u32)
} else {
strgetfn(pm)
};
addenv(&pm.node.nam, &val);
pm.env = Some(val);
}
pub fn setstrvalue(v: Option<&mut value>, val: &str) {
assignstrvalue(v, Some(val.to_string()), 0);
}
pub fn assignstrvalue(v: Option<&mut value>, val: Option<String>, flags: i32) {
if unset(EXECOPT) {
return;
}
let v = match v {
Some(v) => v,
None => return,
};
let pm = match v.pm.as_mut() {
Some(p) => p,
None => return,
};
if (pm.node.flags as u32 & PM_READONLY) != 0 {
zerr(&format!("read-only variable: {}", pm.node.nam)); return;
}
if (pm.node.flags as u32 & PM_HASHED) != 0
&& (v.scanflags as u32 & (SCANPM_MATCHMANY | SCANPM_ARRONLY)) != 0
{
zerr(&format!(
"{}: attempt to set slice of associative array",
pm.node.nam
)); return;
}
if (v.valflags & VALFLAG_EMPTY) != 0 {
zerr(&format!(
"{}: assignment to invalid subscript range",
pm.node.nam
)); return;
}
pm.node.flags &= !(PM_UNSET as i32);
let mut val = val;
match PM_TYPE(pm.node.flags as u32) {
t if t == PM_SCALAR || t == PM_NAMEREF => {
let mut v_str = val.take().unwrap_or_default();
let pf = pm.node.flags as u32;
if pf & PM_LOWER != 0 {
v_str = v_str.to_ascii_lowercase();
} else if pf & PM_UPPER != 0 {
v_str = v_str.to_ascii_uppercase();
}
if v.start == 0 && v.end == -1 {
let final_str = if (flags & ASSPM_AUGMENT) != 0 {
let prev = pm.u_str.clone().unwrap_or_default();
format!("{}{}", prev, v_str)
} else {
v_str
};
let len = final_str.len();
let setfn_ptr = pm.gsu_s.as_ref().map(|g| g.setfn);
if let Some(setfn) = setfn_ptr {
setfn(pm, final_str); } else {
strsetfn(pm, final_str); }
if (pm.node.flags as u32 & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) != 0
&& pm.width == 0
{
pm.width = len as i32;
}
} else {
let z = strgetfn(pm);
let zlen = z.len() as i32;
let mut start = v.start;
let mut end = v.end;
if (v.valflags & VALFLAG_INV) != 0 && !isset(KSHARRAYS) {
start -= 1;
end -= 1;
}
if start < 0 {
start += zlen;
if start < 0 {
start = 0;
}
}
if start > zlen {
start = zlen;
}
if end < 0 {
end += zlen;
if end < 0 {
end = 0;
} else if end >= zlen {
end = zlen;
} else {
end += 1;
}
} else if end > zlen {
end = zlen;
}
let vlen = v_str.len() as i32;
let newsize = start + vlen + (zlen - end);
let s = start as usize;
let e = end as usize;
let mut x = String::with_capacity(newsize as usize);
x.push_str(&z[..s.min(z.len())]);
x.push_str(&v_str);
if e <= z.len() {
x.push_str(&z[e..]);
}
strsetfn(pm, x);
if (pm.node.flags as u32 & PM_HASHELEM) == 0
&& ((pm.node.flags as u32 & PM_NAMEDDIR) != 0 || isset(AUTONAMEDIRS))
{
pm.node.flags |= PM_NAMEDDIR as i32;
}
}
}
t if t == PM_INTEGER => {
if let Some(ref s) = val {
let ival: i64 = if (flags & ASSPM_ENV_IMPORT) != 0 {
s.parse::<i64>().unwrap_or(0)
} else {
mathevali(s).unwrap_or(0)
};
let final_val = if (flags & ASSPM_AUGMENT) != 0 {
pm.u_val.wrapping_add(ival)
} else {
ival
};
intsetfn(pm, final_val);
if (pm.node.flags as u32 & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) != 0
&& pm.width == 0
{
pm.width = s.len() as i32;
}
if pm.base == 0 {
let lb = lastbase();
if lb != -1 {
pm.base = lb;
}
}
}
}
t if t == PM_EFLOAT || t == PM_FFLOAT => {
if let Some(ref s) = val {
let mn = if (flags & ASSPM_ENV_IMPORT) != 0 {
mnumber {
l: 0,
d: s.parse::<f64>().unwrap_or(0.0),
type_: MN_FLOAT,
}
} else {
matheval(s).unwrap_or(mnumber {
l: 0,
d: 0.0,
type_: MN_FLOAT,
})
};
let d = if (mn.type_ & MN_FLOAT) != 0 {
mn.d
} else {
mn.l as f64
};
let final_d = if (flags & ASSPM_AUGMENT) != 0 {
pm.u_dval + d
} else {
d
};
floatsetfn(pm, final_d);
if (pm.node.flags as u32 & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) != 0
&& pm.width == 0
{
pm.width = s.len() as i32;
}
}
}
t if t == PM_ARRAY => {
let one = vec![val.take().unwrap_or_default()];
if v.start == 0 && v.end == -1 {
pm.u_arr = Some(one);
} else {
let arr = pm.u_arr.get_or_insert_with(Vec::new);
let len = arr.len() as i64;
let start_raw = v.start as i64;
let end_raw = v.end as i64;
let start = if start_raw < 0 {
(len + start_raw + 1).max(0)
} else {
start_raw
};
let end = if end_raw < 0 {
(len + end_raw + 1).max(0)
} else {
end_raw
};
let start_idx = (start.max(1) - 1) as usize;
let end_idx = end.max(0) as usize;
while arr.len() < start_idx {
arr.push(String::new());
}
let end_idx = end_idx.min(arr.len());
if start_idx <= end_idx {
arr.splice(start_idx..end_idx, one);
} else {
for (i, x) in one.into_iter().enumerate() {
if start_idx + i < arr.len() {
arr[start_idx + i] = x;
} else {
arr.push(x);
}
}
}
}
}
t if t == PM_HASHED => {
if let Some(nam) = foundparam() {
if let Some(ref h) = pm.u_hash {
let _ = (nam, h);
}
}
set_foundparam(None);
}
_ => {}
}
setscope(pm);
if errflag.load(Ordering::Relaxed) != 0
|| ((pm.env.is_none()
&& (pm.node.flags as u32 & PM_EXPORTED) == 0
&& !(isset(ALLEXPORT) && (pm.node.flags as u32 & PM_HASHELEM) == 0))
|| (pm.node.flags as u32 & PM_ARRAY) != 0
|| pm.ename.is_some())
{
return;
}
export_param(pm);
}
pub fn setnumvalue(v: Option<&mut value>, val: mnumber) {
if unset(EXECOPT) {
return;
}
let v = match v {
Some(v) => v,
None => return,
};
let pm = match v.pm.as_mut() {
Some(p) => p,
None => return,
};
if (pm.node.flags as u32 & PM_READONLY) != 0 {
zerr(&format!("read-only variable: {}", pm.node.nam)); return;
}
let t = PM_TYPE(pm.node.flags as u32);
if t == PM_SCALAR || t == PM_NAMEREF || t == PM_ARRAY {
let s = if (val.type_ & MN_INTEGER) != 0 {
convbase_underscore(
val.l,
if pm.base > 0 { pm.base } else { 10 },
pm.width,
)
} else {
convfloat_underscore(val.d, pm.width)
};
pm.u_str = Some(s); } else if t == PM_INTEGER {
pm.u_val = if (val.type_ & MN_INTEGER) != 0 {
val.l
} else {
val.d as i64
};
} else if t == PM_EFLOAT || t == PM_FFLOAT {
pm.u_dval = if (val.type_ & MN_INTEGER) != 0 {
val.l as f64
} else {
val.d
};
}
}
pub fn setarrvalue(v: &mut value, val: Vec<String>) {
if unset(EXECOPT) {
return;
}
let pm = match v.pm.as_mut() {
Some(p) => p,
None => return,
};
if pm.node.flags & PM_READONLY as i32 != 0 {
zerr(&format!("read-only variable: {}", pm.node.nam));
return;
}
let t = PM_TYPE(pm.node.flags as u32);
if t & (PM_ARRAY | PM_HASHED) == 0 {
zerr(&format!(
"{}: attempt to assign array value to non-array",
pm.node.nam
));
return;
}
if v.valflags & VALFLAG_EMPTY != 0 {
zerr(&format!(
"{}: assignment to invalid subscript range",
pm.node.nam
));
return;
}
if v.start == 0 && v.end == -1 {
if t == PM_HASHED {
arrhashsetfn(pm, val, 0);
} else {
arrsetfn(pm, val);
}
return;
}
if v.start == -1 && v.end == 0 && t == PM_HASHED {
arrhashsetfn(pm, val, ASSPM_AUGMENT);
return;
}
if t == PM_HASHED {
zerr(&format!(
"{}: attempt to set slice of associative array",
pm.node.nam
));
return;
}
if v.valflags & VALFLAG_INV != 0 && !isset(KSHARRAYS) {
if v.start > 0 {
v.start -= 1;
}
v.end -= 1;
}
let arr = pm.u_arr.get_or_insert_with(Vec::new);
let len = arr.len() as i64;
let start = if v.start < 0 {
(len + v.start as i64).max(0)
} else {
v.start as i64
};
let end = if v.end < 0 {
(len + v.end as i64 + 1).max(0)
} else {
v.end as i64
};
let start_idx = (start.max(1) - 1) as usize;
let end_idx = end.max(0) as usize;
while arr.len() < start_idx {
arr.push(String::new());
}
let end_idx = end_idx.min(arr.len());
let val_len = val.len(); let pre_len = arr.len(); if start_idx <= end_idx {
arr.splice(start_idx..end_idx, val);
} else {
for (i, x) in val.into_iter().enumerate() {
if start_idx + i < arr.len() {
arr[start_idx + i] = x;
} else {
arr.push(x);
}
}
}
let expected = if start_idx <= end_idx {
pre_len - (end_idx - start_idx) + val_len } else {
(start_idx + val_len).max(pre_len) };
DPUTS2!(
arr.len() != expected, "setarrvalue: wrong allocation: {} 1= {}",
expected,
arr.len() );
if (pm.node.flags as u32 & PM_UNIQUE) != 0 {
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
arr.retain(|s| seen.insert(s.clone())); }
}
pub fn getiparam(s: &str) -> i64 {
if let Ok(tab) = paramtab().read() {
if let Some(pm) = tab.get(s) {
if (pm.node.flags as u32 & PM_INTEGER) != 0 {
return pm.u_val;
}
}
}
getsparam(s)
.and_then(|s| s.parse::<i64>().ok())
.unwrap_or(0)
}
pub fn getnparam(s: &str) -> (i64, f64, bool) {
if let Ok(tab) = paramtab().read() {
if let Some(pm) = tab.get(s) {
let fl = pm.node.flags as u32;
if (fl & (PM_EFLOAT | PM_FFLOAT)) != 0 {
return (pm.u_dval as i64, pm.u_dval, true);
}
if (fl & PM_INTEGER) != 0 {
return (pm.u_val, pm.u_val as f64, false);
}
}
}
let s = match getsparam(s) {
Some(s) => s,
None => return (0, 0.0, false),
};
if s.contains('.') || s.contains('e') || s.contains('E') {
if let Ok(f) = s.parse::<f64>() {
return (f as i64, f, true);
}
}
if let Ok(i) = s.parse::<i64>() {
return (i, i as f64, false);
}
(0, 0.0, false)
}
pub fn getsparam(name: &str) -> Option<String> {
if let Some(v) = lookup_special_var(name) {
return Some(v);
}
if let Ok(tab) = paramtab().read() {
if let Some(pm) = tab.get(name) {
let t = PM_TYPE(pm.node.flags as u32);
let pad_flags = (pm.node.flags as u32) & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z);
let raw: Option<String> = if t == PM_INTEGER {
let base = if pm.base > 0 { pm.base } else { 10 };
Some(convbase(pm.u_val, base as u32))
} else if t == PM_EFLOAT || t == PM_FFLOAT {
Some(convfloat(pm.u_dval, pm.base, pm.node.flags as u32))
} else if let Some(s) = pm.u_str.as_ref() {
Some(s.clone())
} else { pm.u_arr.as_ref().map(|arr| arr.join(" ")) };
if let Some(mut s) = raw {
if pad_flags != 0 && pm.width > 0 {
let fwidth = pm.width as usize;
let numeric_pm = (pm.node.flags as u32 & (PM_INTEGER | PM_EFLOAT | PM_FFLOAT)) != 0;
if pad_flags == PM_LEFT || pad_flags == (PM_LEFT | PM_RIGHT_Z) {
let trimmed: &str = if pad_flags & PM_RIGHT_Z != 0 {
s.trim_start_matches('0')
} else {
s.trim_start_matches(|c: char| c == ' ' || c == '\t')
};
let len = trimmed.chars().count();
let take = len.min(fwidth);
let mut out: String = trimmed.chars().take(take).collect();
if fwidth > take {
out.extend(std::iter::repeat(' ').take(fwidth - take));
}
s = out;
} else if pad_flags & (PM_RIGHT_B | PM_RIGHT_Z) != 0 {
let charlen = s.chars().count();
if charlen < fwidth {
let mut zero = true;
let mut valprefend: usize = 0;
if pad_flags & PM_RIGHT_Z != 0 {
let bytes = s.as_bytes();
let mut tpos = 0usize;
while tpos < bytes.len() && (bytes[tpos] == b' ' || bytes[tpos] == b'\t') {
tpos += 1;
}
if numeric_pm && tpos < bytes.len() && bytes[tpos] == b'-' {
tpos += 1;
}
if (pm.node.flags as u32 & PM_INTEGER) != 0
&& tpos + 1 < bytes.len()
&& bytes[tpos] == b'0'
&& bytes[tpos + 1] == b'x'
{
tpos += 2;
} else if (pm.node.flags as u32 & PM_INTEGER) != 0 {
if let Some(hash_off) = bytes[tpos..].iter().position(|&b| b == b'#') {
tpos += hash_off + 1;
}
}
valprefend = tpos;
if tpos == bytes.len() {
zero = false;
} else if !numeric_pm && !bytes[tpos].is_ascii_digit() {
zero = false;
}
}
let pad_char = if (pad_flags & PM_RIGHT_B) != 0 || !zero { ' ' } else { '0' };
let pad_count = fwidth - charlen;
let prefix = &s[..valprefend];
let rest = &s[valprefend..];
let mut out = String::with_capacity(fwidth);
out.push_str(prefix);
out.extend(std::iter::repeat(pad_char).take(pad_count));
out.push_str(rest);
s = out;
}
}
}
return Some(s);
}
}
}
env::var(name).ok()
}
pub fn getsparam_u(s: &str) -> Option<String> {
getsparam(s).map(|v| unmeta(&v))
}
pub fn getaparam(name: &str) -> Option<Vec<String>> {
if name.starts_with(|c: char| c.is_ascii_digit()) {
return None;
}
if let Ok(tab) = paramtab().read() {
if let Some(pm) = tab.get(name) {
if PM_TYPE(pm.node.flags as u32) == PM_ARRAY {
if let Some(arr) = pm.u_arr.as_ref() {
return Some(arr.clone());
}
}
}
}
None }
pub fn gethparam(name: &str) -> Option<Vec<String>> {
if name.starts_with(|c: char| c.is_ascii_digit()) {
return None;
}
if let Ok(tab) = paramtab().read() {
if let Some(pm) = tab.get(name) {
if PM_TYPE(pm.node.flags as u32) == PM_HASHED {
let store = paramtab_hashed_storage().lock().ok()?;
let vals = store
.get(name)
.map(|m| m.values().cloned().collect())
.unwrap_or_default();
return Some(vals); }
}
}
None }
pub fn gethkparam(name: &str) -> Option<Vec<String>> {
if name.starts_with(|c: char| c.is_ascii_digit()) {
return None;
}
if let Ok(tab) = paramtab().read() {
if let Some(pm) = tab.get(name) {
if PM_TYPE(pm.node.flags as u32) == PM_HASHED {
let store = paramtab_hashed_storage().lock().ok()?;
let keys = store
.get(name)
.map(|m| m.keys().cloned().collect())
.unwrap_or_default();
return Some(keys); }
}
}
None }
pub fn check_warn_pm(pm: ¶m, pmtype: &str, created: i32, may_warn_about_nested_vars: i32) {
if may_warn_about_nested_vars == 0 && created == 0 {
return;
}
let cur_local: i32 = locallevel.load(Ordering::Relaxed);
let forklevel: i32 = FORKLEVEL.load(Ordering::Relaxed); if created != 0 && isset(WARNCREATEGLOBAL) {
if cur_local <= forklevel || pm.level != 0 {
return;
}
} else if created == 0 && isset(WARNNESTEDVAR) {
if pm.level >= cur_local {
return;
}
} else {
return;
}
if (pm.node.flags as u32 & (PM_SPECIAL | PM_NAMEREF)) != 0 {
return;
}
let stack = match FUNCSTACK.lock() {
Ok(s) => s,
Err(_) => return,
};
for frame in stack.iter().rev() {
if frame.tp == FS_FUNC {
let msg = if created != 0 {
format!(
"{} parameter {} created globally in function {}",
pmtype, pm.node.nam, frame.name
)
} else {
format!(
"{} parameter {} set in enclosing scope in function {}",
pmtype, pm.node.nam, frame.name
)
};
zwarn(&msg); break; }
}
}
#[allow(non_camel_case_types)]
pub enum getarg_out<'a> {
Flags { flags: &'a str, rest: &'a str },
Value(Value),
}
pub fn assignsparam(s: &str, val: &str, flags: i32) -> Option<Param> {
if !isident(s) {
zerr(&format!("not an identifier: {}", s)); errflag.fetch_or(
ERRFLAG_ERROR,
Ordering::Relaxed,
);
return None; }
queue_signals();
let (name, subscript) = match s.find('[') {
Some(i) => {
let close = s.rfind(']').unwrap_or(s.len());
let key_end = if close > i { close } else { s.len() };
(&s[..i], Some(&s[i + 1..key_end]))
}
None => (s, None),
};
if let Some(key) = subscript {
match name {
"functions" => {
use crate::ported::zsh_h::param as ParamStruct;
use crate::ported::zsh_h::hashnode;
let pm: Box<ParamStruct> = Box::new(ParamStruct {
node: hashnode {
next: None,
nam: key.to_string(),
flags: 0,
},
u_data: 0,
u_arr: None,
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
});
crate::ported::modules::parameter::setpmfunction(pm.clone(), val.to_string());
unqueue_signals();
return Some(pm);
}
"dis_functions" => {
use crate::ported::zsh_h::param as ParamStruct;
use crate::ported::zsh_h::hashnode;
let pm: Box<ParamStruct> = Box::new(ParamStruct {
node: hashnode {
next: None,
nam: key.to_string(),
flags: 0,
},
u_data: 0,
u_arr: None,
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
});
crate::ported::modules::parameter::setpmdisfunction(pm.clone(), val.to_string());
unqueue_signals();
return Some(pm);
}
"aliases" => {
if let Ok(mut tab) = crate::ported::hashtable::aliastab_lock().write() {
let node = crate::ported::hashtable::createaliasnode(
key,
val,
0u32,
);
tab.add(node);
}
unqueue_signals();
return Some(Box::new(crate::ported::zsh_h::param {
node: crate::ported::zsh_h::hashnode {
next: None,
nam: key.to_string(),
flags: 0,
},
u_data: 0, u_arr: None, u_str: Some(val.to_string()),
u_val: 0, u_dval: 0.0, u_hash: None,
gsu_s: None, gsu_i: None, gsu_f: None, gsu_a: None, gsu_h: None,
base: 0, width: 0, env: None, ename: None, old: None, level: 0,
}));
}
"galiases" => {
if let Ok(mut tab) = crate::ported::hashtable::aliastab_lock().write() {
let node = crate::ported::hashtable::createaliasnode(
key,
val,
crate::ported::zsh_h::ALIAS_GLOBAL as u32,
);
tab.add(node);
}
unqueue_signals();
return Some(Box::new(crate::ported::zsh_h::param {
node: crate::ported::zsh_h::hashnode {
next: None,
nam: key.to_string(),
flags: 0,
},
u_data: 0, u_arr: None, u_str: Some(val.to_string()),
u_val: 0, u_dval: 0.0, u_hash: None,
gsu_s: None, gsu_i: None, gsu_f: None, gsu_a: None, gsu_h: None,
base: 0, width: 0, env: None, ename: None, old: None, level: 0,
}));
}
"saliases" => {
if let Ok(mut tab) = crate::ported::hashtable::sufaliastab_lock().write() {
let node = crate::ported::hashtable::createaliasnode(
key,
val,
crate::ported::zsh_h::ALIAS_SUFFIX as u32,
);
tab.add(node);
}
unqueue_signals();
return Some(Box::new(crate::ported::zsh_h::param {
node: crate::ported::zsh_h::hashnode {
next: None,
nam: key.to_string(),
flags: 0,
},
u_data: 0, u_arr: None, u_str: Some(val.to_string()),
u_val: 0, u_dval: 0.0, u_hash: None,
gsu_s: None, gsu_i: None, gsu_f: None, gsu_a: None, gsu_h: None,
base: 0, width: 0, env: None, ename: None, old: None, level: 0,
}));
}
_ => {}
}
}
if let Some(key) = subscript {
let mut tab = paramtab().write().unwrap();
let exists = tab.contains_key(name); if !exists {
let pm: Param = Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: PM_ARRAY as i32,
},
u_data: 0,
u_arr: Some(Vec::new()),
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
});
tab.insert(name.to_string(), pm);
} else {
let pm = tab.get(name).unwrap();
if (pm.node.flags as u32 & PM_READONLY) != 0 {
zerr(&format!("read-only variable: {}", pm.node.nam)); drop(tab);
unqueue_signals(); return None; }
}
let pm = tab.get_mut(name).unwrap();
pm.node.flags &= !(PM_DEFAULTED as i32); if (pm.node.flags as u32 & PM_HASHED) != 0 {
let mut store = paramtab_hashed_storage().lock().unwrap();
store
.entry(name.to_string())
.or_default()
.insert(key.to_string(), val.to_string());
} else if let Ok(idx) = key.parse::<i64>() {
let arr = pm.u_arr.get_or_insert_with(Vec::new);
let len = arr.len() as i64;
let real_idx = if idx < 0 { len + idx } else { idx - 1 };
let real_idx = real_idx.max(0) as usize;
while arr.len() <= real_idx {
arr.push(String::new());
}
arr[real_idx] = val.to_string();
pm.u_str = None;
} else {
pm.node.flags = (pm.node.flags & !(PM_TYPE(u32::MAX) as i32)) | PM_HASHED as i32;
pm.u_arr = None;
pm.u_str = None;
let mut map: IndexMap<String, String> = IndexMap::new();
map.insert(key.to_string(), val.to_string());
paramtab_hashed_storage()
.lock()
.unwrap()
.insert(name.to_string(), map);
}
let cloned = pm.clone();
drop(tab);
unqueue_signals(); return Some(cloned); }
let mut tab = paramtab().write().unwrap();
let existing = tab.contains_key(name);
if !existing {
let mut pm_flags = PM_SCALAR as i32;
if isset(ALLEXPORT) {
pm_flags |= PM_EXPORTED as i32;
}
let gsu_s: Option<Box<gsu_scalar>> = match name {
"HOME" => {
pm_flags |= PM_SPECIAL as i32;
Some(Box::new(HOME_GSU.clone())) }
"IFS" => {
pm_flags |= PM_SPECIAL as i32;
Some(Box::new(IFS_GSU.clone())) }
"TERM" => {
pm_flags |= PM_SPECIAL as i32;
Some(Box::new(TERM_GSU.clone())) }
"TERMINFO" => {
pm_flags |= PM_SPECIAL as i32;
Some(Box::new(TERMINFO_GSU.clone())) }
"TERMINFO_DIRS" => {
pm_flags |= PM_SPECIAL as i32;
Some(Box::new(TERMINFODIRS_GSU.clone())) }
"WORDCHARS" => {
pm_flags |= PM_SPECIAL as i32;
Some(Box::new(WORDCHARS_GSU.clone())) }
"USERNAME" => {
pm_flags |= PM_SPECIAL as i32;
Some(Box::new(USERNAME_GSU.clone())) }
"KEYBOARD_HACK" => {
pm_flags |= PM_SPECIAL as i32;
Some(Box::new(KEYBOARDHACK_GSU.clone())) }
"HISTCHARS" | "histchars" => {
pm_flags |= PM_SPECIAL as i32;
Some(Box::new(HISTCHARS_GSU.clone())) }
_ => None,
};
let pm: Param = Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: pm_flags,
},
u_data: 0,
u_arr: None,
u_str: Some(String::new()),
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s, gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
});
tab.insert(name.to_string(), pm);
} else {
let pm = tab.get(name).unwrap();
if (pm.node.flags as u32 & PM_READONLY) != 0 {
zerr(&format!("read-only variable: {}", pm.node.nam)); drop(tab);
unqueue_signals(); return None; }
let pm = tab.get_mut(name).unwrap();
if pm.gsu_s.is_none() {
let new_gsu: Option<Box<gsu_scalar>> = match name {
"HOME" => Some(Box::new(HOME_GSU.clone())), "IFS" => Some(Box::new(IFS_GSU.clone())), "TERM" => Some(Box::new(TERM_GSU.clone())), "TERMINFO" => Some(Box::new(TERMINFO_GSU.clone())), "TERMINFO_DIRS" => Some(Box::new(TERMINFODIRS_GSU.clone())), "WORDCHARS" => Some(Box::new(WORDCHARS_GSU.clone())), "USERNAME" => Some(Box::new(USERNAME_GSU.clone())), "KEYBOARD_HACK" => Some(Box::new(KEYBOARDHACK_GSU.clone())), "HISTCHARS" | "histchars" => Some(Box::new(HISTCHARS_GSU.clone())), _ => None,
};
if new_gsu.is_some() {
pm.gsu_s = new_gsu;
pm.node.flags |= PM_SPECIAL as i32;
}
}
let pm = tab.get(name).unwrap();
let f = pm.node.flags as u32;
let is_array_or_hash = (f & PM_ARRAY) != 0 || (f & PM_HASHED) != 0;
let is_special_or_tied = (f & (PM_SPECIAL | PM_TIED)) != 0;
let augment_bit = (flags & ASSPM_AUGMENT) != 0;
if is_array_or_hash && !is_special_or_tied && !augment_bit && !isset(KSHARRAYS) {
let pm_mut = tab.get_mut(name).unwrap();
pm_mut.node.flags =
(pm_mut.node.flags & !(PM_TYPE(u32::MAX) as i32)) | PM_SCALAR as i32;
pm_mut.u_arr = None;
paramtab_hashed_storage().lock().unwrap().remove(name);
}
}
let pm = tab.get(name).unwrap();
if !val.is_empty() && (pm.node.flags as u32 & PM_NAMEREF) != 0 {
if !valid_refname(val, pm.node.flags) {
zerr(&format!("invalid name reference: {}", val)); drop(tab);
errflag.fetch_or(
ERRFLAG_ERROR,
Ordering::Relaxed,
);
unqueue_signals(); return None; }
}
let pm = tab.get_mut(name).unwrap(); pm.node.flags &= !(PM_DEFAULTED as i32);
let pm_clone = tab.get(name).unwrap().clone(); drop(tab); let mut v = value {
pm: Some(pm_clone), arr: Vec::new(), scanflags: 0, valflags: 0, start: 0, end: -1, }; assignstrvalue(Some(&mut v), Some(val.to_string()), flags); let cloned = v.pm.as_ref().cloned(); if let Some(pm_back) = v.pm {
paramtab()
.write()
.unwrap()
.insert(name.to_string(), pm_back); } unqueue_signals(); cloned }
static PARAMTAB_INNER: OnceLock<RwLock<HashMap<String, Param>>> = OnceLock::new();
static REALPARAMTAB_INNER: OnceLock<RwLock<HashMap<String, Param>>> = OnceLock::new();
pub fn assignaparam(name: &str, val: Vec<String>, flags: i32) -> Option<Param> {
if !isident(name) {
zerr(&format!("not an identifier: {}", name));
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
return None;
}
if let Some(_bracket) = name.find('[') {
let base = name.split('[').next().unwrap_or(name);
let is_hashed = {
let tab = paramtab().read().unwrap();
tab.get(base)
.map(|pm| (pm.node.flags as u32 & PM_HASHED) != 0)
.unwrap_or(false)
};
if is_hashed {
zerr(&format!(
"{}: attempt to set slice of associative array",
base
));
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
return None;
}
let exists = paramtab().read().unwrap().contains_key(base);
if !exists {
createparam(base, PM_ARRAY as i32)?;
}
return paramtab().read().unwrap().get(base).cloned();
}
let (existed, prior_scalar, prior_flags) = {
let tab = paramtab().read().unwrap();
match tab.get(name) {
Some(pm) => (true, pm.u_str.clone(), pm.node.flags),
None => (false, None, 0),
}
};
if existed && (prior_flags as u32 & PM_NAMEREF) != 0 {
zwarn(&format!("{}: can't change type of a named reference", name));
return None;
}
if !existed {
createparam(name, PM_ARRAY as i32)?;
}
if existed && (prior_flags as u32 & PM_READONLY) != 0 {
zerr(&format!("read-only variable: {}", name));
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
return None;
}
if existed {
let pm_type = prior_flags as u32 & PM_TYPE(u32::MAX);
if pm_type != PM_ARRAY && (prior_flags as u32 & PM_SPECIAL) != 0 {
zerr(&format!("{}: can't change type of a special parameter", name));
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
return None;
}
}
let was_scalar_array_target = existed
&& prior_flags & (PM_ARRAY | PM_HASHED) as i32 == 0
&& prior_flags & PM_SPECIAL as i32 == 0;
let mut val = val;
if (flags & ASSPM_AUGMENT) != 0 && was_scalar_array_target && prior_flags & PM_UNSET as i32 == 0
{
if let Some(old_scalar) = prior_scalar {
val.insert(0, old_scalar); }
}
if (flags & ASSPM_AUGMENT) != 0
&& existed
&& (prior_flags as u32 & PM_ARRAY) != 0
&& (prior_flags as u32 & PM_UNSET) == 0
{
let prior_arr = {
let tab = paramtab().read().unwrap();
tab.get(name).and_then(|pm| pm.u_arr.clone()).unwrap_or_default()
};
let appended: Vec<String> =
prior_arr.into_iter().chain(val.into_iter()).collect();
val = appended;
}
let mut tab = paramtab().write().unwrap();
let pm = tab.get_mut(name)?;
let uniq = pm.node.flags & PM_UNIQUE as i32 != 0; if pm.node.flags & PM_SPECIAL as i32 == 0 {
let type_mask = PM_ARRAY | PM_INTEGER | PM_EFLOAT | PM_FFLOAT | PM_HASHED | PM_NAMEREF;
pm.node.flags = (pm.node.flags & !type_mask as i32) | PM_ARRAY as i32;
}
if uniq {
pm.node.flags |= PM_UNIQUE as i32;
}
let val_final = if uniq { simple_arrayuniq(val) } else { val };
let setfn_ptr = pm.gsu_a.as_ref().map(|g| g.setfn);
if let Some(setfn) = setfn_ptr {
setfn(pm, val_final.clone()); } else {
pm.u_arr = Some(val_final.clone());
pm.u_str = None;
pm.u_hash = None;
}
let cloned = pm.clone();
drop(tab);
if name == "argv" || name == "@" || name == "*" {
if let Ok(mut pp) = crate::ported::builtin::PPARAMS.lock() {
*pp = val_final.clone();
}
}
let _ = val_final;
Some(cloned)
}
pub fn setaparam(name: &str, val: Vec<String>) -> Option<Param> {
assignaparam(name, val, ASSPM_WARN)
}
pub fn sethparam(name: &str, val: Vec<String>) -> Option<Param> {
if !isident(name) {
zerr(&format!("not an identifier: {}", name));
return None;
}
if name.contains('[') {
zerr("nested associative arrays not yet supported");
return None;
}
{
let tab = paramtab().read().unwrap();
if let Some(pm) = tab.get(name) {
if (pm.node.flags as u32 & PM_READONLY) != 0 {
zerr(&format!("read-only variable: {}", name));
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
return None;
}
let pm_type = pm.node.flags as u32 & PM_TYPE(u32::MAX);
if pm_type != PM_HASHED && (pm.node.flags as u32 & PM_SPECIAL) != 0 {
zerr(&format!("{}: can't change type of a special parameter", name));
errflag.fetch_or(ERRFLAG_ERROR, Ordering::Relaxed);
return None;
}
}
}
let exists = paramtab().read().unwrap().contains_key(name);
if !exists {
createparam(name, PM_HASHED as i32)?;
}
let mut map: IndexMap<String, String> = IndexMap::new();
let mut iter = val.into_iter();
while let Some(k) = iter.next() {
let v = iter.next().unwrap_or_default();
map.insert(k, v);
}
let mut tab = paramtab().write().unwrap();
let pm = tab.get_mut(name)?;
if pm.node.flags & PM_SPECIAL as i32 == 0 {
let type_mask = PM_ARRAY | PM_INTEGER | PM_EFLOAT | PM_FFLOAT | PM_HASHED | PM_NAMEREF;
pm.node.flags = (pm.node.flags & !type_mask as i32) | PM_HASHED as i32;
}
pm.u_arr = None;
pm.u_str = None;
let cloned = pm.clone();
drop(tab);
paramtab_hashed_storage()
.lock()
.unwrap()
.insert(name.to_string(), map);
Some(cloned)
}
pub fn assignnparam(s: &str, val: mnumber, flags: i32) -> Option<Box<param>> {
if !isident(s) {
zerr(&format!("not an identifier: {}", s)); errflag.fetch_or(
ERRFLAG_ERROR,
Ordering::Relaxed,
);
return None; }
if unset(EXECOPT) {
return None;
}
let mut vbuf = value {
pm: None,
arr: Vec::new(),
scanflags: 0,
valflags: 0,
start: 0,
end: -1,
};
let mut cursor: &str = s;
let has_sub = s.contains('[');
let mut was_unset = false;
let v = getvalue(Some(&mut vbuf), &mut cursor, 1);
let need_create = match v {
Some(ref vv) => {
if let Some(pm) = vv.pm.as_ref() {
let f = pm.node.flags as u32;
if (f & (PM_ARRAY | PM_HASHED)) != 0
&& (f & (PM_SPECIAL | PM_TIED)) == 0
&& unset(KSHARRAYS)
&& !has_sub
{
was_unset = true;
true
} else {
false
}
} else {
true
}
}
None => true,
};
if need_create {
let _ = was_unset;
let new_type = if val.type_ == MN_FLOAT {
PM_FFLOAT } else {
PM_INTEGER };
let inherited_base = if val.type_ == MN_FLOAT {
0
} else {
let lb = crate::ported::math::lastbase();
if lb > 0 { lb } else { 0 }
};
let pm: Param = Box::new(param {
node: hashnode {
next: None,
nam: s.to_string(),
flags: new_type as i32,
},
u_data: 0,
u_arr: None,
u_str: None,
u_val: if val.type_ == MN_FLOAT { 0 } else { val.l },
u_dval: if val.type_ == MN_FLOAT { val.d } else { 0.0 },
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: inherited_base,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
});
if let Ok(mut tab) = paramtab().write() {
tab.insert(s.to_string(), pm.clone());
}
return Some(pm);
}
if (flags & ASSPM_WARN) != 0 {
if let Some(ref vv) = v {
if let Some(ref pm) = vv.pm {
check_warn_pm(pm, "numeric", 0, 1);
}
}
}
if let Ok(mut tab) = paramtab().write() {
if let Some(pm) = tab.get_mut(s) {
pm.node.flags &= !(PM_DEFAULTED as i32);
let t = PM_TYPE(pm.node.flags as u32);
if t == PM_INTEGER {
pm.u_val = if val.type_ == MN_FLOAT {
val.d as i64
} else {
val.l
};
} else if t == PM_EFLOAT || t == PM_FFLOAT {
pm.u_dval = if val.type_ == MN_FLOAT {
val.d
} else {
val.l as f64
};
} else if t == PM_SCALAR || t == PM_NAMEREF || t == PM_ARRAY {
let s_rendered = if val.type_ == MN_FLOAT {
convfloat_underscore(val.d, pm.width)
} else {
convbase_underscore(
val.l,
if pm.base > 0 { pm.base } else { 10 },
pm.width,
)
};
pm.u_str = Some(s_rendered);
}
let cloned = pm.clone();
return Some(cloned);
}
}
None
}
pub fn setnparam(s: &str, val: mnumber) -> Option<Param> {
assignnparam(s, val, ASSPM_WARN as i32) }
pub fn assigniparam(s: &str, val: i64, flags: i32) -> Option<Param> {
let mnval = mnumber {
l: val,
d: 0.0,
type_: MN_INTEGER,
};
assignnparam(s, mnval, flags) }
pub fn setiparam(s: &str, val: i64) -> Option<Param> {
let mnval = mnumber {
l: val,
d: 0.0,
type_: MN_INTEGER,
};
assignnparam(s, mnval, ASSPM_WARN as i32) }
pub fn setiparam_no_convert(s: &str, val: i64) -> Option<Param> {
assignsparam(s, &val.to_string(), ASSPM_WARN as i32)
}
pub fn resetparam(pm: &mut param, flags: i32) -> i32 {
let s = pm.node.nam.clone(); queue_signals(); unsetparam_pm(pm, 0, 1); unqueue_signals(); let _ = createparam(&s, flags); 0 }
pub fn unsetparam(name: &str) {
queue_signals(); let (found, is_nameref) = {
let tab = paramtab().read().unwrap();
match tab.get(name) {
Some(pm) => (true, (pm.node.flags as u32 & PM_NAMEREF) != 0),
None => (false, false),
}
};
if found && !is_nameref {
let mut pm_owned = paramtab().write().unwrap().remove(name).unwrap();
let rejected = unsetparam_pm(&mut pm_owned, 0, 1); if rejected != 0 {
paramtab()
.write()
.unwrap()
.insert(name.to_string(), pm_owned);
} else if pm_owned.old.is_some() {
paramtab()
.write()
.unwrap()
.insert(name.to_string(), pm_owned);
}
}
unqueue_signals(); }
#[allow(unused_variables)]
pub fn unsetparam_pm(pm: &mut param, altflag: i32, exp: i32) -> i32 {
let cur_ll = locallevel.load(Ordering::Relaxed) as i32; if (pm.node.flags as u32 & PM_READONLY) != 0 && pm.level <= cur_ll {
let kind = if (pm.node.flags as u32 & PM_NAMEREF) != 0 {
"reference"
} else {
"variable"
};
zerr(&format!("read-only {}: {}", kind, pm.node.nam));
return 1; }
pm.node.flags &= !(PM_DECLARED as i32); if (pm.node.flags as u32 & PM_UNSET) == 0 || (pm.node.flags as u32 & PM_REMOVABLE) != 0 {
stdunsetfn(pm, exp);
}
if pm.env.is_some() {
delenv(&pm.node.nam); pm.env = None;
}
pm.node.flags |= PM_UNSET as i32;
0
}
pub fn intgetfn(pm: ¶m) -> i64 {
pm.u_val
}
pub fn intsetfn(pm: &mut param, x: i64) {
match pm.node.nam.as_str() {
"SECONDS" => {
intsecondssetfn(x);
return;
}
"RANDOM" => {
randomsetfn(x);
return;
}
_ => {}
}
pm.u_val = x;
}
pub fn floatgetfn(pm: ¶m) -> f64 {
pm.u_dval
}
pub fn floatsetfn(pm: &mut param, x: f64) {
if pm.node.nam == "SECONDS" {
floatsecondssetfn(x);
return;
}
pm.u_dval = x;
}
pub fn strgetfn(pm: ¶m) -> String {
pm.u_str.clone().unwrap_or_default()
}
pub fn strsetfn(pm: &mut param, x: String) {
pm.u_str = Some(x.clone()); if (pm.node.flags as u32 & PM_HASHELEM) == 0
&& ((pm.node.flags as u32 & PM_NAMEDDIR) != 0 || isset(AUTONAMEDIRS))
{
pm.node.flags |= PM_NAMEDDIR as i32; adduserdir(&pm.node.nam, &x, 0, false); }
}
pub fn arrgetfn(pm: ¶m) -> Vec<String> {
pm.u_arr.clone().unwrap_or_default()
}
pub fn arrsetfn(pm: &mut param, x: Vec<String>) {
let val = if (pm.node.flags as u32 & PM_UNIQUE) != 0 {
simple_arrayuniq(x)
} else {
x
};
pm.u_arr = Some(val.clone());
if let Some(ename) = pm.ename.clone() {
arrfixenv(&ename, Some(&val));
}
}
pub fn hashgetfn(pm: ¶m) -> Option<&HashTable> {
pm.u_hash.as_ref()
}
pub fn hashsetfn(pm: &mut param, x: HashTable) {
pm.u_hash = Some(x);
}
pub fn arrhashsetfn(
pm: &mut param,
val: Vec<String>,
_flags: i32,
) {
let alen: usize = val
.iter()
.filter(|s| !s.starts_with(Marker as char))
.count();
if alen % 2 != 0 {
zerr("bad set of key/value pairs for associative array");
return;
}
pm.u_hash = Some(Box::new(hashtable {
hsize: 0,
ct: 0,
nodes: Vec::new(),
tmpdata: 0,
hash: None,
emptytable: None,
filltable: None,
cmpnodes: None,
addnode: None,
getnode: None,
getnode2: None,
removenode: None,
disablenode: None,
enablenode: None,
freenode: None,
printnode: None,
scantab: None,
}));
}
#[allow(unused_variables)]
pub fn nullstrsetfn(pm: &mut param, x: String) {}
#[allow(unused_variables)]
pub fn nullunsetfn(pm: &mut param, exp: i32) {}
#[allow(unused_variables)]
pub fn stdunsetfn(pm: &mut param, exp: i32) {
match PM_TYPE(pm.node.flags as u32) {
PM_SCALAR | PM_NAMEREF => {
pm.u_str = None;
}
PM_ARRAY => {
pm.u_arr = None;
}
PM_HASHED => {
pm.u_hash = None;
}
_ => {
if (pm.node.flags as u32 & PM_SPECIAL) == 0 {
pm.u_str = None;
}
}
}
if (pm.node.flags as u32 & (PM_SPECIAL | PM_TIED)) == PM_TIED {
pm.ename = None;
pm.node.flags &= !(PM_TIED as i32);
}
pm.node.flags |= PM_UNSET as i32;
}
#[allow(unused_variables)]
pub fn nullintsetfn(pm: &mut param, x: i64) {}
#[allow(unused_variables)]
pub fn nullsethashfn(pm: &mut param, x: HashTable) {
}
pub fn intvargetfn(pm: ¶m) -> i64 {
pm.u_val
}
pub fn intvarsetfn(pm: &mut param, x: i64) {
pm.u_val = x;
}
pub fn zlevarsetfn(pm: &mut param, x: i64) {
pm.u_val = x; if pm.node.nam == "LINES" {
let _ = adjustwinsize(2); } else if pm.node.nam == "COLUMNS" {
let _ = adjustwinsize(3); }
}
pub fn strvarsetfn(pm: &mut param, x: Option<String>) {
pm.u_str = x;
}
pub fn strvargetfn(pm: ¶m) -> String {
pm.u_str.clone().unwrap_or_default()
}
pub fn arrvargetfn(pm: ¶m) -> Vec<String> {
pm.u_arr.clone().unwrap_or_default()
}
pub fn arrvarsetfn(pm: &mut param, x: Option<Vec<String>>) {
let uniq_applied: Option<Vec<String>> = match x {
Some(v) if (pm.node.flags as u32 & PM_UNIQUE) != 0 => Some(simple_arrayuniq(v)),
other => other,
};
let final_val: Vec<String> = match uniq_applied {
Some(v) => v, None => {
if (pm.node.flags as u32 & PM_SPECIAL) != 0 {
crate::ported::utils::mkarray(None) } else {
Vec::new() }
}
};
if let Some(ename) = pm.ename.clone() {
if !final_val.is_empty() || pm.u_arr.is_some() {
arrfixenv(&ename, Some(&final_val));
} else if pm.node.nam == "path" {
crate::ported::hashtable::pathchecked.store(0, Ordering::SeqCst);
}
}
pm.u_arr = Some(final_val);
}
pub fn colonarrgetfn(arr: &[String]) -> String {
arr.join(":")
}
pub fn colonarrsetfn(pm: &mut param, x: Option<String>) {
let uniq = (pm.node.flags as u32 & PM_UNIQUE) != 0; let arr = x.map(|s| colonsplit(&s, uniq)); arrvarsetfn(pm, arr);
}
pub fn tiedarrgetfn(pm: ¶m) -> Vec<String> {
pm.u_arr.clone().unwrap_or_default()
}
pub fn tiedarrsetfn(pm: &mut param, x: Option<String>) {
if pm.u_arr.is_none() {
if let Some(ename) = pm.ename.clone() {
let mut tab = paramtab().write().unwrap();
if let Some(altpm) = tab.get_mut(&ename) {
altpm.node.flags &= !(PM_DEFAULTED as i32); }
}
}
if let Some(s) = x {
let arr: Vec<String> = s.split(':').map(|t| t.to_string()).collect();
let arr = if pm.node.flags & PM_UNIQUE as i32 != 0 {
uniqarray(arr) } else {
arr
};
pm.u_arr = Some(arr);
} else {
pm.u_arr = None; }
if pm.ename.is_some() {
let nam = pm.node.nam.clone();
let arr_ref = pm.u_arr.as_deref();
arrfixenv(&nam, arr_ref);
}
}
pub fn tiedarrunsetfn(pm: &mut param, _exp: i32) {
tiedarrsetfn(pm, None);
pm.u_data = 0;
pm.u_arr = None;
pm.ename = None;
pm.node.flags &= !(PM_TIED as i32);
pm.node.flags |= PM_UNSET as i32;
}
pub fn simple_arrayuniq(x: Vec<String>) -> Vec<String> {
let mut seen: HashSet<String> = HashSet::new();
let mut out = Vec::with_capacity(x.len());
for s in x {
if seen.insert(s.clone()) {
out.push(s);
}
}
out
}
pub fn arrayuniq_freenode() {}
pub fn newuniqtable(size: i64) -> HashSet<String> {
HashSet::with_capacity(size.max(0) as usize) }
pub fn arrayuniq(x: Vec<String>, freeok: i32) -> Vec<String> {
let _ = freeok;
let array_size = x.len();
if array_size == 0 {
return x;
}
if array_size < 10 {
return simple_arrayuniq(x); }
let mut ht = newuniqtable(array_size as i64 + 1);
let mut out: Vec<String> = Vec::with_capacity(array_size);
for s in x {
if ht.insert(s.clone()) {
out.push(s); }
}
drop(ht); out
}
pub fn uniqarray(arr: Vec<String>) -> Vec<String> {
let mut seen = HashSet::new();
arr.into_iter().filter(|s| seen.insert(s.clone())).collect()
}
pub fn zhuniqarray(x: Vec<String>) -> Vec<String> {
arrayuniq(x, 0) }
pub fn poundgetfn() -> i64 {
pparams_lock().lock().expect("pparams poisoned").len() as i64
}
pub fn randomgetfn() -> i64 {
(unsafe { libc::rand() } & 0x7fff) as i64
}
pub fn randomsetfn(v: i64) {
unsafe { libc::srand(v as libc::c_uint) };
}
pub fn intsecondsgetfn() -> i64 {
let timer = *shtimer_lock().lock().expect("shtimer poisoned");
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
let now_sec = now.as_secs() as i64;
let timer_sec = timer.as_secs() as i64;
let now_nsec = now.subsec_nanos() as i64;
let timer_nsec = timer.subsec_nanos() as i64;
let diff = now_sec - timer_sec - i64::from(now_nsec < timer_nsec);
diff.max(0)
}
pub fn intsecondssetfn(x: i64) {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
let now_sec = now.as_secs() as i64;
let new_sec = now_sec - x;
if new_sec < 0 {
zwarn("SECONDS truncated on assignment");
*shtimer_lock().lock().expect("shtimer poisoned") = Duration::new(0, now.subsec_nanos());
return;
}
*shtimer_lock().lock().expect("shtimer poisoned") =
Duration::new(new_sec as u64, now.subsec_nanos());
}
pub fn floatsecondsgetfn() -> f64 {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
let timer = *shtimer_lock().lock().expect("shtimer poisoned");
(now - timer).as_secs_f64()
}
pub fn floatsecondssetfn(x: f64) {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
let new = now
.checked_sub(Duration::from_secs_f64(x))
.unwrap_or_default();
*shtimer_lock().lock().expect("shtimer poisoned") = new;
}
pub fn getrawseconds() -> f64 {
shtimer_lock()
.lock()
.expect("shtimer poisoned")
.as_secs_f64()
}
pub fn setrawseconds(x: f64) {
*shtimer_lock().lock().expect("shtimer poisoned") = Duration::from_secs_f64(x);
}
pub fn setsecondstype(
pm: &mut param,
on: i32,
off: i32,
) -> i32 {
let newflags = (pm.node.flags | on) & !off;
let tp = PM_TYPE(newflags as u32);
if tp == PM_EFLOAT || tp == PM_FFLOAT {
pm.gsu_i = None;
} else if tp == PM_INTEGER {
pm.gsu_f = None;
} else {
return 1; }
pm.node.flags = newflags; 0 }
pub fn usernamegetfn(_pm: ¶m) -> String {
get_username() }
pub fn usernamesetfn(_pm: &mut param, x: String) {
let target = std::ffi::CString::new(x.as_bytes()).ok();
if let Some(cstr) = target {
unsafe {
let pwd = libc::getpwnam(cstr.as_ptr()); if !pwd.is_null() {
let cached_uid = libc::getuid(); if (*pwd).pw_uid != cached_uid {
let _ = libc::initgroups(cstr.as_ptr(), (*pwd).pw_gid as _);
if libc::setgid((*pwd).pw_gid) != 0 {
zwarn(&format!(
"failed to change group ID: {}",
std::io::Error::last_os_error()
));
} else if libc::setuid((*pwd).pw_uid) != 0 {
zwarn(&format!(
"failed to change user ID: {}",
std::io::Error::last_os_error()
));
} else {
let name_cstr = std::ffi::CStr::from_ptr((*pwd).pw_name);
let name_str = name_cstr.to_string_lossy().to_string();
*cached_username_lock().lock().expect("username poisoned") =
ztrdup_metafy(&name_str);
}
}
}
}
}
drop(x);
}
pub fn uidgetfn() -> i64 {
unsafe { libc::getuid() as i64 }
}
pub static TERMFLAGS: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub fn uidsetfn(x: i64) {
if unsafe { libc::setuid(x as libc::uid_t) } != 0 {
zerr(&format!(
"failed to change user ID: {}",
std::io::Error::last_os_error()
)); }
}
pub fn euidgetfn() -> i64 {
unsafe { libc::geteuid() as i64 }
}
pub fn euidsetfn(x: i64) {
if unsafe { libc::seteuid(x as libc::uid_t) } != 0 {
zerr(&format!(
"failed to change effective user ID: {}",
std::io::Error::last_os_error()
)); }
}
pub fn gidgetfn() -> i64 {
unsafe { libc::getgid() as i64 }
}
pub fn gidsetfn(x: i64) {
if unsafe { libc::setgid(x as libc::gid_t) } != 0 {
zerr(&format!(
"failed to change group ID: {}",
std::io::Error::last_os_error()
)); }
}
pub fn egidgetfn() -> i64 {
unsafe { libc::getegid() as i64 }
}
pub fn egidsetfn(x: i64) {
if unsafe { libc::setegid(x as libc::gid_t) } != 0 {
zerr(&format!(
"failed to change effective group ID: {}",
std::io::Error::last_os_error()
)); }
}
pub fn ttyidlegetfn() -> i64 {
let shtty = SHTTY.load(Ordering::SeqCst);
if shtty == -1 {
return -1;
}
let mut st: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(shtty, &mut st) } != 0 {
return -1;
}
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64;
now - st.st_atime as i64 }
pub fn ifsgetfn(_pm: ¶m) -> String {
ifs_lock().lock().expect("ifs poisoned").clone()
}
pub fn ifssetfn(_pm: &mut param, x: String) {
*ifs_lock().lock().expect("ifs poisoned") = x;
inittyptab();
}
pub fn clear_mbstate() {
crate::ported::utils::mb_charinit(); crate::ported::pattern::clear_shiftstate(); }
const LC_NAMES: &[(&str, libc::c_int)] = &[
("LC_COLLATE", libc::LC_COLLATE), ("LC_CTYPE", libc::LC_CTYPE), ("LC_MESSAGES", libc::LC_MESSAGES), ("LC_NUMERIC", libc::LC_NUMERIC), ("LC_TIME", libc::LC_TIME), ];
pub fn setlang(x: Option<&str>) {
if let Some(lc_all) = getsparam_u("LC_ALL") {
if !lc_all.is_empty() {
return;
}
}
let locale_arg = match x {
Some(s) => unmeta(s),
None => String::new(),
};
let cstr = std::ffi::CString::new(locale_arg.as_bytes()).unwrap_or_default();
unsafe {
libc::setlocale(libc::LC_ALL, cstr.as_ptr()); }
if let Some(s) = x {
env::set_var("LANG", s);
}
clear_mbstate(); for (name, category) in LC_NAMES {
if let Some(val) = getsparam_u(name) {
if !val.is_empty() {
let cat_cstr = std::ffi::CString::new(val.as_bytes()).unwrap_or_default();
unsafe {
libc::setlocale(*category, cat_cstr.as_ptr()); }
}
}
}
inittyptab();
}
pub fn lc_allsetfn(x: Option<String>) {
match x {
None => setlang(getsparam_u("LANG").as_deref()), Some(s) if s.is_empty() => {
setlang(getsparam_u("LANG").as_deref()); }
Some(s) => {
let unmeta = unmeta(&s); let cstr = std::ffi::CString::new(unmeta.as_bytes()).unwrap_or_default();
unsafe {
libc::setlocale(libc::LC_ALL, cstr.as_ptr()); }
env::set_var("LC_ALL", &s);
clear_mbstate(); inittyptab(); }
}
}
pub fn langsetfn(x: String) {
let unmeta_x = unmeta(&x); setlang(Some(&unmeta_x));
}
pub fn lcsetfn(pm: &str, x: Option<String>) {
if let Some(lc_all) = getsparam("LC_ALL") {
if !lc_all.is_empty() {
return;
}
}
let val = x
.filter(|s| !s.is_empty())
.or_else(|| getsparam("LANG").filter(|s| !s.is_empty())); if let Some(v) = val {
let unmeta = unmeta(&v); env::set_var(pm, &unmeta);
for (name, category) in LC_NAMES {
if *name == pm {
let cstr = std::ffi::CString::new(unmeta.as_bytes()).unwrap_or_default();
unsafe {
libc::setlocale(*category, cstr.as_ptr()); }
break;
}
}
}
clear_mbstate();
inittyptab(); }
pub fn argzerosetfn(x: String) {
if !x.is_empty() {
if isset(POSIXARGZERO) {
zerr("read-only variable: 0"); } else {
set_argzero(Some(ztrdup(&x)));
}
}
}
pub fn argzerogetfn() -> String {
if isset(POSIXARGZERO) {
posixzero().unwrap_or_default() } else {
argzero().unwrap_or_default() }
}
pub fn histsizegetfn() -> i64 {
*histsiz_lock().lock().expect("histsiz poisoned")
}
pub fn histsizesetfn(v: i64) {
*histsiz_lock().lock().expect("histsiz poisoned") = v.max(1);
histsiz.store(v.max(1), Ordering::SeqCst);
resizehistents(); }
pub fn savehistsizegetfn() -> i64 {
*savehistsiz_lock().lock().expect("savehistsiz poisoned")
}
pub fn savehistsizesetfn(v: i64) {
let clamped = v.max(0); *savehistsiz_lock().lock().expect("savehistsiz poisoned") = clamped;
savehistsiz.store(clamped, Ordering::SeqCst);
}
pub fn errnosetfn(x: i64) {
let truncated = x as i32;
unsafe {
*errno_ptr() = truncated;
} if truncated as i64 != x {
zwarn("errno truncated on assignment"); }
}
#[inline]
unsafe fn errno_ptr() -> *mut libc::c_int {
#[cfg(target_os = "macos")]
{
libc::__error()
}
#[cfg(target_os = "linux")]
{
libc::__errno_location()
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
{
std::ptr::null_mut()
}
}
pub fn errnogetfn() -> i64 {
let p = unsafe { errno_ptr() }; if p.is_null() {
std::io::Error::last_os_error().raw_os_error().unwrap_or(0) as i64
} else {
unsafe { *p as i64 }
}
}
pub fn keyboardhackgetfn(_pm: ¶m) -> String {
let c = *keyboardhack_lock().lock().expect("keyboardhack poisoned");
if c == 0 {
String::new()
} else {
(c as char).to_string()
}
}
pub fn keyboardhacksetfn(_pm: &mut param, x: String) {
let unmeta = unmeta(&x); let bytes = unmeta.as_bytes();
if bytes.len() > 1 {
zwarn("Only one KEYBOARD_HACK character can be defined");
}
let c = bytes.first().copied().unwrap_or(0);
if c >= 0x80 {
zwarn("KEYBOARD_HACK can only contain ASCII characters");
return;
}
*keyboardhack_lock().lock().expect("keyboardhack poisoned") = c;
}
pub fn histcharsgetfn(_pm: ¶m) -> String {
let b = bangchar.load(Ordering::SeqCst) as u8;
let h = hatchar.load(Ordering::SeqCst) as u8;
let p = hashchar.load(Ordering::SeqCst) as u8;
let mut s = String::new();
for &byte in &[b, h, p] {
if byte != 0 {
s.push(byte as char);
}
}
s
}
pub fn histcharssetfn(_pm: &mut param, x: String) {
let new_chars: [u8; 3] = if x.is_empty() {
[b'!', b'^', b'#']
} else {
let s = x;
{
let unmeta = unmeta(&s); let bytes = unmeta.as_bytes();
let bytes = if bytes.len() > 3 { &bytes[..3] } else { bytes };
for &b in bytes.iter() {
if b >= 0x80 {
zwarn("HISTCHARS can only contain ASCII characters");
return;
}
}
let mut chars = [0u8; 3];
for (i, &b) in bytes.iter().enumerate() {
chars[i] = b;
}
chars
}
};
*histchars_lock().lock().expect("histchars poisoned") = new_chars;
bangchar.store(new_chars[0] as i32, Ordering::SeqCst);
hatchar.store(new_chars[1] as i32, Ordering::SeqCst);
hashchar.store(new_chars[2] as i32, Ordering::SeqCst);
inittyptab();
}
pub const HOME_GSU: gsu_scalar = gsu_scalar { getfn: homegetfn,
setfn: homesetfn,
unsetfn: stdunsetfn,
};
pub const IFS_GSU: gsu_scalar = gsu_scalar { getfn: ifsgetfn,
setfn: ifssetfn,
unsetfn: stdunsetfn,
};
pub const TERM_GSU: gsu_scalar = gsu_scalar { getfn: termgetfn,
setfn: termsetfn,
unsetfn: stdunsetfn,
};
pub const TERMINFO_GSU: gsu_scalar = gsu_scalar { getfn: terminfogetfn,
setfn: terminfosetfn,
unsetfn: stdunsetfn,
};
pub const TERMINFODIRS_GSU: gsu_scalar = gsu_scalar { getfn: terminfodirsgetfn,
setfn: terminfodirssetfn,
unsetfn: stdunsetfn,
};
pub const WORDCHARS_GSU: gsu_scalar = gsu_scalar { getfn: wordcharsgetfn,
setfn: wordcharssetfn,
unsetfn: stdunsetfn,
};
pub const USERNAME_GSU: gsu_scalar = gsu_scalar { getfn: usernamegetfn,
setfn: usernamesetfn,
unsetfn: stdunsetfn,
};
pub const KEYBOARDHACK_GSU: gsu_scalar = gsu_scalar { getfn: keyboardhackgetfn,
setfn: keyboardhacksetfn,
unsetfn: stdunsetfn,
};
pub const HISTCHARS_GSU: gsu_scalar = gsu_scalar { getfn: histcharsgetfn,
setfn: histcharssetfn,
unsetfn: stdunsetfn,
};
pub fn homegetfn(_pm: ¶m) -> String {
home_lock().lock().expect("home poisoned").clone()
}
pub fn homesetfn(_pm: &mut param, x: String) {
let resolved = if !x.is_empty() && isset(CHASELINKS) {
xsymlink(&x).unwrap_or(x)
} else {
x
};
*home_lock().lock().expect("home poisoned") = resolved;
}
pub fn wordcharsgetfn(_pm: ¶m) -> String {
wordchars_lock().lock().expect("wordchars poisoned").clone()
}
pub fn wordcharssetfn(_pm: &mut param, x: String) {
*wordchars_lock().lock().expect("wordchars poisoned") = x;
inittyptab();
}
pub fn underscoregetfn() -> String {
let u = zunderscore_lock()
.lock()
.expect("zunderscore poisoned")
.clone();
untokenize(&u) }
pub fn term_reinit_from_pm() {
let interactive = isset(optlookup("interactive"));
let term = term_lock().lock().map(|s| s.clone()).unwrap_or_default();
if !interactive || term.is_empty() {
TERMFLAGS.fetch_or(TERM_UNKNOWN, Ordering::Relaxed); } else {
TERMFLAGS.fetch_or(TERM_UNKNOWN, Ordering::Relaxed); }
}
pub fn termgetfn(_pm: ¶m) -> String {
term_lock().lock().expect("term poisoned").clone()
}
pub fn termsetfn(_pm: &mut param, x: String) {
*term_lock().lock().expect("term poisoned") = x;
term_reinit_from_pm();
}
pub fn terminfogetfn(_pm: ¶m) -> String {
zsh_terminfo_lock()
.lock()
.expect("zsh_terminfo poisoned")
.clone()
}
pub static RPROMPT_INDENT: Mutex<i32> = Mutex::new(1);
pub fn terminfosetfn(_pm: &mut param, x: String) {
*zsh_terminfo_lock().lock().expect("zsh_terminfo poisoned") = x.clone();
env::set_var("TERMINFO", &x);
term_reinit_from_pm();
}
pub fn terminfodirsgetfn(_pm: ¶m) -> String {
zsh_terminfodirs_lock()
.lock()
.expect("zsh_terminfodirs poisoned")
.clone()
}
pub fn terminfodirssetfn(_pm: &mut param, x: String) {
*zsh_terminfodirs_lock()
.lock()
.expect("zsh_terminfodirs poisoned") = x.clone();
env::set_var("TERMINFO_DIRS", &x);
term_reinit_from_pm();
}
pub fn pipestatgetfn() -> Vec<String> {
pipestats_lock()
.lock()
.expect("pipestats poisoned")
.iter()
.map(|n| n.to_string())
.collect()
}
pub fn pipestatsetfn(x: Option<Vec<String>>) {
const MAX_PIPESTATS: usize = 256;
let mut guard = pipestats_lock().lock().expect("pipestats poisoned");
guard.clear();
if let Some(v) = x {
for s in v.iter().take(MAX_PIPESTATS) {
guard.push(s.parse::<i32>().unwrap_or(0));
}
}
}
pub fn arrfixenv(s: &str, t: Option<&[String]>) {
if s == "PATH" || s == "path" {
emptycmdnamtable();
}
let pm_arc_data = {
let tab = paramtab().read().unwrap();
tab.get(s).map(|pm| (pm.node.flags, pm.gsu_a.is_some()))
};
let (flags, _has_gsu_a) = match pm_arc_data {
Some(x) => x,
None => {
let val = t.map(|v| v.join(":")).unwrap_or_default();
env::set_var(s, val);
return;
}
};
if flags & PM_HASHELEM as i32 != 0 {
return;
}
let allexport = isset(ALLEXPORT);
{
let mut tab = paramtab().write().unwrap();
if let Some(pm) = tab.get_mut(s) {
if allexport {
pm.node.flags |= PM_EXPORTED as i32;
}
pm.node.flags &= !(PM_DEFAULTED as i32);
}
}
let new_flags = {
let tab = paramtab().read().unwrap();
tab.get(s).map(|pm| pm.node.flags).unwrap_or(0)
};
if new_flags & PM_EXPORTED as i32 == 0 {
return;
}
let joinchar = if new_flags & PM_SPECIAL as i32 != 0 {
':' } else {
':'
};
let joined = match t {
Some(arr) => arr.join(&joinchar.to_string()),
None => String::new(),
};
addenv(s, &joined);
}
pub fn zputenv(str: &str) -> i32 {
DPUTS!(
str.is_empty(),
"Attempt to put null string into environment."
); if str.is_empty() {
return 0;
}
let bytes = str.as_bytes();
let mut ptr = 0;
while ptr < bytes.len() && bytes[ptr] != b'=' && bytes[ptr] < 128 {
ptr += 1;
}
if ptr < bytes.len() && bytes[ptr] >= 128 {
return 1;
}
if ptr < bytes.len() {
let name = &str[..ptr];
let value = &str[ptr + 1..];
let safe_value: &str = match value.find('\0') {
Some(n) => &value[..n],
None => value,
};
if name.as_bytes().contains(&b'\0') {
return 1;
}
env::set_var(name, safe_value);
0
} else {
DPUTS!(true, "bad environment string"); if str.as_bytes().contains(&b'\0') {
return 1;
}
env::set_var(str, ""); 0
}
}
pub fn findenv(name: &str) -> Option<usize> {
let nlen = name.find('=').unwrap_or(name.len()); let bare = &name[..nlen];
for (i, (k, _)) in env::vars_os().enumerate() {
if let Some(s) = k.to_str() {
if s == bare {
return Some(i); }
}
}
None }
pub fn zgetenv(name: &str) -> Option<String> {
env::var(name).ok()
}
pub fn copyenvstr(buf: &mut String, value: &str, flags: i32) {
let flags_u = flags as u32;
let mut it = value.bytes();
while let Some(b) = it.next() {
let mut ch = b;
if ch == Meta {
ch = match it.next() {
Some(next) => next ^ 32, None => break,
};
}
if flags_u & PM_LOWER != 0 {
ch = ch.to_ascii_lowercase(); } else if flags_u & PM_UPPER != 0 {
ch = ch.to_ascii_uppercase(); }
buf.push(ch as char);
}
}
pub fn addenv(name: &str, value: &str) -> i32 {
let flags = {
let tab = paramtab().read().unwrap();
tab.get(name).map(|pm| pm.node.flags).unwrap_or(0)
};
let newenv = mkenvstr(name, value, flags);
if zputenv(&newenv) != 0 {
let mut tab = paramtab().write().unwrap();
if let Some(pm) = tab.get_mut(name) {
pm.env = None;
}
return 1;
}
let mut tab = paramtab().write().unwrap();
if let Some(pm) = tab.get_mut(name) {
pm.env = Some(newenv);
pm.node.flags |= PM_EXPORTED as i32;
}
0
}
pub fn mkenvstr(name: &str, value: &str, flags: i32) -> String {
let mut buf = String::with_capacity(name.len() + value.len() + 2);
buf.push_str(name); buf.push('='); if !value.is_empty() {
copyenvstr(&mut buf, value, flags); }
buf }
pub fn delenvvalue(name: &str) {
env::remove_var(name); }
pub fn delenv(name: &str) {
env::remove_var(name);
let mut tab = paramtab().write().unwrap();
if let Some(pm) = tab.get_mut(name) {
pm.env = None;
}
}
pub fn convbase_ptr(v: i64, base: i32) -> (String, i32) {
let mut s = String::new();
let mut value = v;
if value < 0 {
s.push('-');
value = -value;
}
let mut b = base;
if (-1..=1).contains(&b) {
b = -10;
}
if b > 0 {
if isset(CBASES) && b == 16 {
s.push_str("0x");
} else if isset(CBASES)
&& b == 8
&& isset(OCTALZEROES)
{
s.push('0');
} else if b != 10 {
s.push_str(&format!("{}#", b));
}
} else {
b = -b;
}
let base_u = b as u64;
let mut x = value as u64;
let mut digs: i32 = 0;
while x != 0 {
x /= base_u;
digs += 1;
}
if digs == 0 {
digs = 1;
}
let mut digits: Vec<u8> = vec![0u8; digs as usize];
let mut i = digs - 1;
let mut x = value as u64;
while i >= 0 {
let dig = (x % base_u) as u8;
digits[i as usize] = if dig < 10 {
b'0' + dig
} else {
b'A' + dig - 10
};
x /= base_u;
i -= 1;
}
s.push_str(std::str::from_utf8(&digits).unwrap_or(""));
(s, digs)
}
pub fn convbase(val: i64, base: u32) -> String {
convbase_ptr(val, base as i32).0 }
pub fn convbase_underscore(val: i64, base: i32, underscore: i32) -> String {
let s = convbase_ptr(val, base).0;
if underscore <= 0 {
return s;
}
let (prefix, digits) = if let Some(rest) = s.strip_prefix('-') {
let digit_start = rest
.find(|c: char| c.is_ascii_digit() || c.is_ascii_uppercase())
.unwrap_or(0);
(&s[..1 + digit_start], &rest[digit_start..])
} else {
let digit_start = s
.find(|c: char| c.is_ascii_digit() || c.is_ascii_uppercase())
.unwrap_or(0);
(&s[..digit_start], &s[digit_start..])
};
if digits.len() <= underscore as usize {
return s;
}
let u = underscore as usize;
let mut result = prefix.to_string();
let chars: Vec<char> = digits.chars().collect();
let first_group = chars.len() % u;
if first_group > 0 {
result.extend(&chars[..first_group]);
if first_group < chars.len() {
result.push('_');
}
}
for (i, chunk) in chars[first_group..].chunks(u).enumerate() {
if i > 0 {
result.push('_');
}
result.extend(chunk);
}
result
}
pub fn convfloat(dval: f64, digits: i32, pm_flags: u32) -> String {
if dval.is_infinite() {
return if dval < 0.0 {
"-Inf".to_string()
} else {
"Inf".to_string()
};
}
if dval.is_nan() {
return "NaN".to_string();
}
let (fmt_char, digits) = if (pm_flags & PM_EFLOAT) != 0 {
let d = if digits <= 0 { 10 } else { digits }; ('e', (d - 1).max(0)) } else if (pm_flags & PM_FFLOAT) != 0 {
let d = if digits <= 0 { 10 } else { digits }; ('f', d)
} else {
let d = if digits == 0 { 17 } else { digits }; ('g', d)
};
let buf_len = 512usize + digits as usize + 4;
let mut buf = vec![0u8; buf_len];
let fmt = match fmt_char {
'e' => c"%.*e",
'f' => c"%.*f",
_ => c"%.*g",
};
let n = unsafe {
libc::snprintf(
buf.as_mut_ptr() as *mut libc::c_char,
buf_len,
fmt.as_ptr(),
digits as libc::c_int,
dval,
)
};
if n < 0 {
return format!("{}", dval);
}
let len = (n as usize).min(buf_len - 1);
buf.truncate(len);
let mut s = String::from_utf8(buf).unwrap_or_else(|_| format!("{}", dval));
if fmt_char == 'g' && !s.contains('e') && !s.contains('.') {
s.push('.');
}
s
}
pub fn startparamscope(_table: &mut HashTable) {
inc_locallevel();
}
pub fn endparamscope() {
queue_signals();
let old_ll = locallevel_fn();
let refs_snapshot: Vec<String> = SCOPEREFS.with(|sr| {
let sr = sr.borrow();
if (old_ll as usize) < sr.len() {
sr[old_ll as usize].clone()
} else {
Vec::new()
}
});
dec_locallevel(); saveandpophiststack(0, HFILE_USE_OPTIONS as i32);
let ll = locallevel_fn();
if let Ok(mut tab) = paramtab().write() {
let stale: Vec<(String, bool)> = tab
.iter()
.filter_map(|(k, pm)| {
if pm.level > ll {
Some((k.clone(), (pm.node.flags as u32 & PM_HASHED) != 0))
} else {
None
}
})
.collect();
for (n, was_assoc) in stale {
if let Some(pm) = tab.remove(&n) {
let had_outer = pm.old.is_some();
if let Some(prev) = pm.old {
tab.insert(n.clone(), prev); }
if was_assoc && !had_outer {
let _ = paramtab_hashed_storage()
.lock()
.ok()
.as_deref_mut()
.map(|m| m.remove(&n));
}
}
}
}
if !refs_snapshot.is_empty() {
if let Ok(mut tab) = paramtab().write() {
for name in refs_snapshot.iter() {
if let Some(pm) = tab.get_mut(name) {
let f = pm.node.flags as u32;
if (f & PM_NAMEREF) != 0
&& (f & PM_UNSET) == 0
&& (f & PM_UPPER) == 0
&& pm.base > ll
{
pm.base = 0; }
}
}
}
}
SCOPEREFS.with(|sr| {
let mut sr = sr.borrow_mut();
if (old_ll as usize) < sr.len() {
sr[old_ll as usize].clear();
}
});
unqueue_signals();
}
pub fn scanendscope(pm: &mut param, _flags: i32) {
let cur_local = locallevel.load(Ordering::Relaxed);
if pm.level <= cur_local {
return;
}
let pmflags = pm.node.flags as u32;
if (pmflags & (PM_SPECIAL | PM_REMOVABLE)) == PM_SPECIAL {
let mut tpm = match pm.old.take() {
Some(t) => t,
None => {
return;
}
};
if pm.node.nam.starts_with("LC_") || pm.node.nam == "LANG" {
LC_UPDATE_NEEDED.store(1, Ordering::SeqCst);
}
if pm.node.nam == "SECONDS" {
tpm.node.flags |= PM_NORESTORE as i32;
}
pm.old = tpm.old.take();
pm.node.flags = (tpm.node.flags as u32 & !PM_NORESTORE) as i32;
pm.level = tpm.level;
pm.base = tpm.base;
pm.width = tpm.width;
if pm.env.is_some() {
delenv(&pm.node.nam);
pm.env = None;
}
let restore = (tpm.node.flags as u32 & (PM_NORESTORE | PM_READONLY)) == 0;
if restore {
match PM_TYPE(pm.node.flags as u32) {
t if t == PM_SCALAR || t == PM_NAMEREF => {
pm.u_str = tpm.u_str.clone();
}
t if t == PM_INTEGER => {
pm.u_val = tpm.u_val;
}
t if t == PM_EFLOAT || t == PM_FFLOAT => {
pm.u_dval = tpm.u_dval;
}
t if t == PM_ARRAY => {
pm.u_arr = tpm.u_arr.clone();
}
t if t == PM_HASHED => {
pm.u_hash = tpm.u_hash.take();
}
_ => {}
}
}
drop(tpm);
if (pm.node.flags as u32 & PM_EXPORTED) != 0 {
export_param(pm);
}
} else {
unsetparam_pm(pm, 0, 0);
}
}
pub fn freeparamnode(mut _hn: Param) {
if DELUNSET.load(Ordering::Relaxed) != 0 {
stdunsetfn(_hn.as_mut(), 1); }
}
pub fn printparamvalue(p: &mut param, printflags: i32) {
if (printflags & PRINT_KV_PAIR) == 0 {
print!("=");
}
let t = PM_TYPE(p.node.flags as u32);
if t == PM_SCALAR || t == PM_NAMEREF {
let mut s = if let Some(gsu) = &p.gsu_s {
(gsu.getfn)(p)
} else {
strgetfn(p)
};
if s.is_empty() && (p.node.flags as u32 & PM_EXPORTED) != 0 {
if let Ok(v) = std::env::var(&p.node.nam) {
s = v;
}
}
print!("{}", quotedzputs(&s)); } else if t == PM_INTEGER {
print!("{}", intgetfn(p));
} else if t == PM_EFLOAT || t == PM_FFLOAT {
print!(
"{}",
convfloat(floatgetfn(p), p.base, p.node.flags as u32)
); } else if t == PM_ARRAY {
if (printflags & PRINT_KV_PAIR) == 0 {
print!("(");
if (printflags & PRINT_LINE) == 0 {
print!(" ");
}
}
let arr = arrgetfn(p);
if !arr.is_empty() {
if (printflags & PRINT_LINE) != 0 {
if (printflags & PRINT_KV_PAIR) != 0 {
print!(" ");
} else {
print!("\n ");
}
}
print!("{}", arr[0]);
for el in &arr[1..] {
if (printflags & PRINT_LINE) != 0 {
print!("\n ");
} else {
print!(" ");
}
print!("{}", el);
}
if (printflags & (PRINT_LINE | PRINT_KV_PAIR)) == PRINT_LINE {
println!();
}
}
if (printflags & PRINT_KV_PAIR) == 0 {
if (printflags & PRINT_LINE) == 0 {
print!(" ");
}
print!(")");
}
} else if t == PM_HASHED {
if (printflags & PRINT_KV_PAIR) == 0 {
print!("(");
if (printflags & PRINT_LINE) == 0 {
print!(" ");
}
}
if let Ok(stor) = paramtab_hashed_storage().lock() {
if let Some(map) = stor.get(&p.node.nam) {
let mut first = true;
for (k, v) in map {
if first {
first = false;
} else if (printflags & PRINT_LINE) != 0 {
print!("\n ");
} else {
print!(" ");
}
print!("{} {}", k, v);
}
}
}
if (printflags & PRINT_KV_PAIR) == 0 {
if (printflags & PRINT_LINE) == 0 {
print!(" ");
}
print!(")");
}
}
}
pub fn printparamnode(hn: &mut param, mut printflags: i32) {
const PRINT_WITH_NAMESPACE: i32 = 1 << 8; let f = hn.node.flags as u32;
if (f & PM_HASHELEM) == 0
&& (printflags & PRINT_WITH_NAMESPACE) == 0
&& hn.node.nam.starts_with('.')
{
return;
}
if (f & PM_UNSET) != 0 {
let posix_keep = (printflags & (PRINT_POSIX_READONLY | PRINT_POSIX_EXPORT)) != 0
&& (f & (PM_READONLY | PM_EXPORTED)) != 0;
let defaulted = (f & PM_DEFAULTED) == PM_DEFAULTED; if posix_keep || defaulted {
printflags |= PRINT_NAMEONLY;
} else {
return;
}
}
if (f & PM_AUTOLOAD) != 0 {
printflags |= PRINT_NAMEONLY;
}
if (printflags & (PRINT_TYPESET | PRINT_POSIX_READONLY | PRINT_POSIX_EXPORT)) != 0 {
if (f & PM_AUTOLOAD) != 0 {
return;
}
if (f & PM_RO_BY_DESIGN) != 0 {
let cur_ll = locallevel.load(Ordering::Relaxed) as i32;
if hn.level != cur_ll {
return;
}
}
let mut altname: u8 = 0;
if (printflags & PRINT_POSIX_EXPORT) != 0 {
if (f & PM_EXPORTED) == 0 {
return;
}
altname = b'x';
print!("export ");
} else if (printflags & PRINT_POSIX_READONLY) != 0 {
if (f & PM_READONLY) == 0 {
return;
}
altname = b'r';
print!("readonly ");
} else if (f & PM_EXPORTED) != 0 && (f & (PM_ARRAY | PM_HASHED)) == 0 {
let cur_ll = locallevel.load(Ordering::Relaxed) as i32;
if hn.level != 0 && hn.level >= cur_ll {
print!("local ");
} else {
altname = b'x';
print!("export ");
}
} else {
let cur_ll = locallevel.load(Ordering::Relaxed) as i32;
if cur_ll != 0 && hn.level >= cur_ll {
if (f & PM_EXPORTED) != 0 {
print!("local ");
} else {
print!("typeset ");
}
} else if cur_ll != 0 {
print!("typeset -g ");
} else {
print!("typeset ");
}
}
const PMTF_USE_BASE: u32 = 1 << 0;
const PMTF_USE_WIDTH: u32 = 1 << 1;
const PMTF_TEST_LEVEL: u32 = 1 << 2;
struct PmType {
binflag: u32,
string: &'static str,
typeflag: u8,
flags: u32,
}
const PMTYPES: &[PmType] = &[
PmType { binflag: PM_AUTOLOAD, string: "undefined", typeflag: 0, flags: 0 },
PmType { binflag: PM_INTEGER, string: "integer", typeflag: b'i', flags: PMTF_USE_BASE },
PmType { binflag: PM_EFLOAT, string: "float", typeflag: b'E', flags: 0 },
PmType { binflag: PM_FFLOAT, string: "float", typeflag: b'F', flags: 0 },
PmType { binflag: PM_ARRAY, string: "array", typeflag: b'a', flags: 0 },
PmType { binflag: PM_HASHED, string: "association", typeflag: b'A', flags: 0 },
PmType { binflag: 0, string: "local", typeflag: 0, flags: PMTF_TEST_LEVEL },
PmType { binflag: PM_HIDE, string: "hide", typeflag: b'h', flags: 0 },
PmType { binflag: PM_LEFT, string: "left justified", typeflag: b'L', flags: PMTF_USE_WIDTH },
PmType { binflag: PM_RIGHT_B, string: "right justified", typeflag: b'R', flags: PMTF_USE_WIDTH },
PmType { binflag: PM_RIGHT_Z, string: "zero filled", typeflag: b'Z', flags: PMTF_USE_WIDTH },
PmType { binflag: PM_LOWER, string: "lowercase", typeflag: b'l', flags: 0 },
PmType { binflag: PM_UPPER, string: "uppercase", typeflag: b'u', flags: 0 },
PmType { binflag: PM_READONLY, string: "readonly", typeflag: b'r', flags: 0 },
PmType { binflag: PM_TAGGED, string: "tagged", typeflag: b't', flags: 0 },
PmType { binflag: PM_EXPORTED, string: "exported", typeflag: b'x', flags: 0 },
PmType { binflag: PM_UNIQUE, string: "unique", typeflag: b'U', flags: 0 },
PmType { binflag: PM_TIED, string: "tied", typeflag: b'T', flags: 0 },
PmType { binflag: PM_NAMEREF, string: "nameref", typeflag: b'n', flags: 0 },
];
if (printflags & (PRINT_TYPE | PRINT_TYPESET)) != 0 {
let mut doneminus = false; for pmptr in PMTYPES.iter() { if altname != 0 && altname == pmptr.typeflag { continue;
}
let doprint = if (pmptr.flags & PMTF_TEST_LEVEL) != 0 { hn.level != 0 } else if (pmptr.binflag != PM_EXPORTED
|| hn.level != 0
|| (f & (PM_LOCAL | PM_ARRAY | PM_HASHED)) != 0)
&& (f & pmptr.binflag) != 0
{
true
} else {
false
};
if doprint { if (printflags & PRINT_TYPESET) != 0 { if pmptr.typeflag != 0 { if !doneminus { print!("-"); doneminus = true;
}
print!("{}", pmptr.typeflag as char); }
} else {
print!("{} ", pmptr.string); }
if (pmptr.flags & PMTF_USE_BASE) != 0 && hn.base != 0 { print!("{} ", hn.base); doneminus = false;
}
if (pmptr.flags & PMTF_USE_WIDTH) != 0 && hn.width != 0 { print!("{} ", hn.width); doneminus = false;
}
}
}
if doneminus { print!(" ");
}
}
}
if (printflags & PRINT_KV_PAIR) != 0 {
}
print!("{}", hn.node.nam);
if (printflags & PRINT_NAMEONLY) != 0 {
if (printflags & PRINT_KV_PAIR) == 0 {
println!();
}
return;
}
if (printflags & (PRINT_INCLUDEVALUE | PRINT_TYPESET)) != 0
|| (printflags & PRINT_NAMEONLY) == 0
{
printparamvalue(hn, printflags);
}
if (printflags & PRINT_KV_PAIR) == 0 {
println!();
}
}
pub fn resolve_nameref(
pm: Option<Param>,
) -> Option<Param> {
resolve_nameref_rec(pm, None, 0) }
#[allow(unused_variables)]
pub fn resolve_nameref_rec(
pm: Option<Param>,
stop: Option<¶m>,
keep_lastref: i32,
) -> Option<Param> {
let pm_ref = pm.as_deref()?;
let f = pm_ref.node.flags as u32;
if (f & PM_NAMEREF) == 0 || (f & PM_UNSET) != 0 || pm_ref.width != 0 {
return pm;
}
let refname = pm_ref.u_str.as_deref().unwrap_or("");
if refname.is_empty() {
return pm;
}
if (f & PM_TAGGED) != 0 {
let nam = pm.as_ref().map(|p| p.node.nam.clone()).unwrap_or_default();
zerr(&format!("{}: invalid self reference", nam));
return None;
}
pm
}
pub fn setloopvar(name: &str, value: &str) {
let nameref_branch = {
let mut tab = realparamtab().write().unwrap();
if let Some(pm) = tab.get_mut(name) {
if (pm.node.flags as u32 & PM_NAMEREF) != 0 {
if (pm.node.flags as u32 & PM_READONLY) != 0 {
zerr(&format!("read-only reference: {}", pm.node.nam));
return;
}
pm.base = 0;
pm.width = 0;
pm.u_str = Some(value.to_string());
pm.node.flags &= !(PM_UNSET as i32);
true
} else {
false
}
} else {
false
}
};
if nameref_branch {
let mut tab = realparamtab().write().unwrap();
if let Some(pm) = tab.get_mut(name) {
setscope(pm);
}
} else {
setsparam(name, value);
}
}
pub fn setscope(pm: &mut param) {
queue_signals();
if (pm.node.flags as u32 & PM_NAMEREF) != 0 {
let refname = pm.u_str.clone();
if let Some(rn) = refname {
let head: &str = match rn.find('[') {
Some(i) => {
pm.width = i as i32;
&rn[..i]
}
None => rn.as_str(),
};
if !head.is_empty() && head == pm.node.nam {
zerr(&format!("{}: invalid self reference", rn));
pm.node.flags |= PM_UNSET as i32;
} else {
}
}
}
unqueue_signals();
}
pub fn setscope_base(pm: &mut param, base: i32) {
pm.base = base;
if base > pm.level {
SCOPEREFS.with(|sr| {
let mut sr = sr.borrow_mut();
if (base as usize) >= sr.len() {
let new_num = (2 * base as usize).max(8);
sr.resize(new_num, Vec::new());
}
sr[base as usize].insert(0, pm.node.nam.clone()); });
}
}
thread_local! {
pub static SCOPEREFS: std::cell::RefCell<Vec<Vec<String>>>
= const { std::cell::RefCell::new(Vec::new()) };
}
pub fn upscope(mut pm: Param, reference: ¶m) -> Param {
if (reference.node.flags as u32 & PM_UPPER) != 0 {
while pm.level > reference.level - 1 {
match pm.old.take() {
Some(o) => pm = o,
None => break,
}
}
} else {
loop {
let next_level = pm.old.as_ref().map(|o| o.level);
match next_level {
Some(l) if l >= reference.base => {
pm = pm.old.take().unwrap();
}
_ => break,
}
}
}
pm
}
pub fn valid_refname(val: &str, flags: i32) -> bool {
if val.is_empty() {
return false;
}
let first = val.chars().next().unwrap();
let pm_upper = (flags as u32 & PM_UPPER) != 0;
let mut t: usize;
if pm_upper {
if first.is_ascii_digit() {
return false; }
t = val
.char_indices()
.find(|(_, c)| !(c.is_alphanumeric() || *c == '_'))
.map(|(i, _)| i)
.unwrap_or(val.len());
if t - 0 == 4 && (val.starts_with("argv") || val.starts_with("ARGC"))
{
return false; }
} else if first.is_ascii_digit() {
t = 1;
for (i, c) in val.char_indices().skip(1) {
if !c.is_ascii_digit() {
t = i;
break;
}
t = i + c.len_utf8();
}
if t < val.len() && val.as_bytes()[t] != b'[' {
return false; }
} else {
t = val
.char_indices()
.find(|(_, c)| !(c.is_alphanumeric() || *c == '_' || *c == '.'))
.map(|(i, _)| i)
.unwrap_or(val.len());
}
if t == 0 {
let c = val.as_bytes()[0];
if !(c == b'!' || c == b'?' || c == b'$' || c == b'-' || c == b'_') {
return false; }
t = 1; }
if t < val.len() && val.as_bytes()[t] == b'[' {
let tail = &val[t + 1..];
if let Some(close) = tail.find(']') {
if close + 1 < tail.len() {
return false;
}
} else {
return false;
}
}
true }
pub fn foundparam() -> Option<String> {
foundparam_lock().lock().unwrap().clone()
}
pub fn set_foundparam(nam: Option<String>) {
*foundparam_lock().lock().unwrap() = nam;
}
pub static DELUNSET: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub(crate) fn paramtab_hashed_storage() -> &'static Mutex<HashMap<String, IndexMap<String, String>>>
{
PARAMTAB_HASHED_STORAGE_INNER.get_or_init(|| Mutex::new(HashMap::new()))
}
pub fn sync_state_from_paramtab(
variables: &mut HashMap<String, String>,
arrays: &mut HashMap<String, Vec<String>>,
assoc_arrays: &mut HashMap<String, IndexMap<String, String>>,
) {
let tab = paramtab().read().unwrap();
for (name, pm) in tab.iter() {
let f = pm.node.flags as u32;
if (f & PM_ARRAY) != 0 {
if let Some(arr) = pm.u_arr.as_ref() {
arrays.insert(name.clone(), arr.clone());
}
variables.remove(name);
assoc_arrays.remove(name);
} else if (f & PM_HASHED) != 0 {
if let Some(map) = paramtab_hashed_storage().lock().unwrap().get(name) {
assoc_arrays.insert(name.clone(), map.clone());
}
variables.remove(name);
arrays.remove(name);
} else if let Some(s) = pm.u_str.as_ref() {
variables.insert(name.clone(), s.clone());
arrays.remove(name);
assoc_arrays.remove(name);
}
}
}
pub fn convfloat_underscore(dval: f64, underscore: i32) -> String {
let s = convfloat(dval, 0, 0);
if underscore <= 0 {
return s;
}
let u = underscore as usize;
let (sign, rest) = if let Some(after) = s.strip_prefix('-') {
("-", after)
} else {
("", s.as_str())
};
let (int_part, frac_exp) = if let Some(dot_pos) = rest.find('.') {
(&rest[..dot_pos], &rest[dot_pos..])
} else {
(rest, "")
};
let int_chars: Vec<char> = int_part.chars().collect();
let mut result = sign.to_string();
let first_group = int_chars.len() % u;
if first_group > 0 {
result.extend(&int_chars[..first_group]);
if first_group < int_chars.len() {
result.push('_');
}
}
for (i, chunk) in int_chars[first_group..].chunks(u).enumerate() {
if i > 0 {
result.push('_');
}
result.extend(chunk);
}
if let Some(frac) = frac_exp.strip_prefix('.') {
result.push('.');
let (frac_digits, exp) = if let Some(e_pos) = frac.find('e') {
(&frac[..e_pos], &frac[e_pos..])
} else {
(frac, "")
};
let frac_chars: Vec<char> = frac_digits.chars().collect();
for (i, chunk) in frac_chars.chunks(u).enumerate() {
if i > 0 {
result.push('_');
}
result.extend(chunk);
}
result.push_str(exp);
} else {
result.push_str(frac_exp);
}
result
}
fn ifs_lock() -> &'static Mutex<String> {
static IFS_VAR: OnceLock<Mutex<String>> = OnceLock::new();
IFS_VAR.get_or_init(|| Mutex::new(" \t\n\0".to_string()))
}
fn home_lock() -> &'static Mutex<String> {
static HOME_VAR: OnceLock<Mutex<String>> = OnceLock::new();
HOME_VAR.get_or_init(|| Mutex::new(env::var("HOME").unwrap_or_default()))
}
fn term_lock() -> &'static Mutex<String> {
static TERM_VAR: OnceLock<Mutex<String>> = OnceLock::new();
TERM_VAR.get_or_init(|| Mutex::new(env::var("TERM").unwrap_or_default()))
}
fn wordchars_lock() -> &'static Mutex<String> {
static WORDCHARS_VAR: OnceLock<Mutex<String>> = OnceLock::new();
WORDCHARS_VAR.get_or_init(|| Mutex::new("*?_-.[]~=/&;!#$%^(){}<>".to_string()))
}
fn histchars_lock() -> &'static Mutex<[u8; 3]> {
static HISTCHARS_VAR: OnceLock<Mutex<[u8; 3]>> = OnceLock::new();
HISTCHARS_VAR.get_or_init(|| Mutex::new([b'!', b'^', b'#']))
}
fn keyboardhack_lock() -> &'static Mutex<u8> {
static KEYBOARDHACK_VAR: OnceLock<Mutex<u8>> = OnceLock::new();
KEYBOARDHACK_VAR.get_or_init(|| Mutex::new(0))
}
fn histsiz_lock() -> &'static Mutex<i64> {
static HISTSIZ_VAR: OnceLock<Mutex<i64>> = OnceLock::new();
HISTSIZ_VAR.get_or_init(|| Mutex::new(999_999_999))
}
fn savehistsiz_lock() -> &'static Mutex<i64> {
static SAVEHISTSIZ_VAR: OnceLock<Mutex<i64>> = OnceLock::new();
SAVEHISTSIZ_VAR.get_or_init(|| Mutex::new(99_999_999))
}
fn zsh_terminfo_lock() -> &'static Mutex<String> {
static TERMINFO_VAR: OnceLock<Mutex<String>> = OnceLock::new();
TERMINFO_VAR.get_or_init(|| Mutex::new(env::var("TERMINFO").unwrap_or_default()))
}
fn zsh_terminfodirs_lock() -> &'static Mutex<String> {
static TERMINFODIRS_VAR: OnceLock<Mutex<String>> = OnceLock::new();
TERMINFODIRS_VAR.get_or_init(|| Mutex::new(env::var("TERMINFO_DIRS").unwrap_or_default()))
}
fn cached_username_lock() -> &'static Mutex<String> {
static USERNAME_VAR: OnceLock<Mutex<String>> = OnceLock::new();
USERNAME_VAR.get_or_init(|| Mutex::new(initial_username()))
}
pub static NUMPARAMVALS: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0); pub static SCANPROG: OnceLock<Mutex<Option<String>>> = OnceLock::new(); pub static SCANSTR: OnceLock<Mutex<Option<String>>> = OnceLock::new(); pub static PARAMVALS: OnceLock<Mutex<Vec<String>>> = OnceLock::new();
fn initial_username() -> String {
#[cfg(unix)]
{
let uid = unsafe { libc::getuid() };
let mut pwd: libc::passwd = unsafe { std::mem::zeroed() };
let mut buf: Vec<libc::c_char> = vec![0; 1024];
let mut result: *mut libc::passwd = std::ptr::null_mut();
let rc =
unsafe { libc::getpwuid_r(uid, &mut pwd, buf.as_mut_ptr(), buf.len(), &mut result) };
if rc == 0 && !result.is_null() && !pwd.pw_name.is_null() {
let cstr = unsafe { std::ffi::CStr::from_ptr(pwd.pw_name) };
return cstr.to_string_lossy().into_owned();
}
}
env::var("USER")
.or_else(|_| env::var("LOGNAME"))
.unwrap_or_default()
}
fn pipestats_lock() -> &'static Mutex<Vec<i32>> {
static PIPESTATS_VAR: OnceLock<Mutex<Vec<i32>>> = OnceLock::new();
PIPESTATS_VAR.get_or_init(|| Mutex::new(Vec::new()))
}
pub fn shtimer_lock() -> &'static Mutex<Duration> {
static SHTIMER_VAR: OnceLock<Mutex<Duration>> = OnceLock::new();
SHTIMER_VAR.get_or_init(|| {
Mutex::new(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default(),
)
})
}
fn pparams_lock() -> &'static Mutex<Vec<String>> {
&PPARAMS
}
fn zunderscore_lock() -> &'static Mutex<String> {
static ZUNDERSCORE_VAR: OnceLock<Mutex<String>> = OnceLock::new();
ZUNDERSCORE_VAR.get_or_init(|| Mutex::new(String::new()))
}
pub fn set_zunderscore(argv: &[String]) {
let new = if let Some(last) = argv.last() {
last.clone()
} else {
String::new()
};
*zunderscore_lock().lock().expect("zunderscore poisoned") = new;
}
fn dontimport(flags: i32) -> i32 {
let flags = flags as u32;
if flags & PM_DONTIMPORT != 0 {
return 1; }
if flags & PM_EXPORTED != 0 {
return 1; }
if flags & PM_DONTIMPORT_SUID != 0 && isset(PRIVILEGED)
{
return 1; }
0 }
pub fn lookup_special_var(name: &str) -> Option<String> {
if !name.is_empty() && name.chars().all(|c| c.is_ascii_digit()) {
let n: usize = name.parse().ok()?;
if n == 0 {
return argzero();
}
let pp = pparams_lock().lock().ok()?;
return pp.get(n - 1).cloned();
}
match name {
"UID" => Some(uidgetfn().to_string()),
"GID" => Some(gidgetfn().to_string()),
"EUID" => Some(euidgetfn().to_string()),
"EGID" => Some(egidgetfn().to_string()),
"PPID" => Some(
(unsafe { libc::getppid() } as i64).to_string(),
),
"RANDOM" => Some(randomgetfn().to_string()),
"TTYIDLE" => Some(ttyidlegetfn().to_string()),
"ERRNO" => Some(errnogetfn().to_string()),
"SECONDS" => {
let pm_type = paramtab()
.read()
.ok()
.and_then(|t| t.get("SECONDS").map(|pm| PM_TYPE(pm.node.flags as u32)))
.unwrap_or(PM_INTEGER);
if pm_type == PM_EFLOAT || pm_type == PM_FFLOAT {
let v = floatsecondsgetfn();
let base = paramtab()
.read()
.ok()
.and_then(|t| t.get("SECONDS").map(|pm| pm.base))
.unwrap_or(0);
Some(convfloat(v, base, pm_type))
} else {
Some(intsecondsgetfn().to_string())
}
}
"EPOCHSECONDS" => {
Some(crate::ported::modules::datetime::getcurrentsecs().to_string())
}
"EPOCHREALTIME" => {
let v = crate::ported::modules::datetime::getcurrentrealtime();
Some(format!("{:.10}", v))
}
"USERNAME" | "HOME" | "TERM" | "WORDCHARS" | "IFS" | "TERMINFO"
| "TERMINFO_DIRS" | "KEYBOARD_HACK" | "histchars" | "HISTCHARS" => {
let tab = paramtab().read().ok()?;
let pm = tab.get(name)?;
Some(match name {
"USERNAME" => usernamegetfn(pm),
"HOME" => homegetfn(pm),
"TERM" => termgetfn(pm),
"WORDCHARS" => wordcharsgetfn(pm),
"IFS" => ifsgetfn(pm),
"TERMINFO" => terminfogetfn(pm),
"TERMINFO_DIRS" => terminfodirsgetfn(pm),
"KEYBOARD_HACK" => keyboardhackgetfn(pm),
"histchars" | "HISTCHARS" => histcharsgetfn(pm),
_ => unreachable!(),
})
}
"_" => Some(underscoregetfn()),
"HISTSIZE" => Some(histsizegetfn().to_string()),
"SAVEHIST" => Some(savehistsizegetfn().to_string()),
"#" | "ARGC" => Some(poundgetfn().to_string()),
"TIMEFMT" => {
let tab_val = paramtab().read().ok().and_then(|t| {
t.get("TIMEFMT").and_then(|pm| pm.u_str.clone())
});
if let Some(v) = tab_val {
if !v.is_empty() {
return Some(v);
}
}
Some(crate::ported::zsh_system_h::DEFAULT_TIMEFMT.to_string())
}
"NULLCMD" => {
let tab_val = paramtab().read().ok().and_then(|t| {
t.get("NULLCMD").and_then(|pm| pm.u_str.clone())
});
if let Some(v) = tab_val {
if !v.is_empty() {
return Some(v);
}
}
Some("cat".to_string()) }
"0" => argzero(),
"?" | "status" => Some(
LASTVAL
.load(Ordering::Relaxed)
.to_string(),
),
"TRY_BLOCK_ERROR" => {
let v = paramtab().read().ok().and_then(|t| {
t.get("TRY_BLOCK_ERROR").and_then(|pm| {
if (pm.node.flags as u32 & PM_UNSET) != 0 {
return None;
}
if let Some(ref s) = pm.u_str {
return Some(s.parse::<i64>().unwrap_or(0));
}
Some(pm.u_val)
})
});
Some(v.unwrap_or(-1).to_string())
}
"TRY_BLOCK_INTERRUPT" => {
let v = paramtab().read().ok().and_then(|t| {
t.get("TRY_BLOCK_INTERRUPT").and_then(|pm| {
if (pm.node.flags as u32 & PM_UNSET) != 0 {
None
} else if pm.u_val != 0 {
Some(pm.u_val)
} else {
None
}
})
});
Some(v.unwrap_or(-1).to_string())
}
"$" => Some(std::process::id().to_string()),
"!" => {
let pid = crate::ported::modules::clone::lastpid
.load(std::sync::atomic::Ordering::Relaxed);
Some(pid.to_string())
}
"*" | "@" => {
let sep = paramtab()
.read()
.ok()
.and_then(|t| t.get("IFS").map(|pm| ifsgetfn(pm)))
.unwrap_or_else(|| " ".to_string())
.chars()
.next()
.unwrap_or(' ')
.to_string();
pparams_lock().lock().ok().map(|p| p.join(&sep))
}
"-" => Some(crate::ported::options::dashgetfn()),
"pipestatus" => {
let arr = pipestatgetfn();
if arr.is_empty() {
None
} else {
Some(arr.join(" "))
}
}
_ => None,
}
}
#[cfg(test)]
pub(crate) static HISTSIZ_TEST_LOCK: Mutex<()> = Mutex::new(());
#[cfg(test)]
pub(crate) static HISTCHARS_TEST_LOCK_SHARED: Mutex<()> = Mutex::new(());
#[cfg(test)]
mod gsu_tests {
use super::*;
#[test]
fn test_libc_id_callbacks_match_libc() {
let _g = crate::test_util::global_state_lock();
assert_eq!(uidgetfn(), unsafe { libc::getuid() } as i64);
assert_eq!(gidgetfn(), unsafe { libc::getgid() } as i64);
assert_eq!(euidgetfn(), unsafe { libc::geteuid() } as i64);
assert_eq!(egidgetfn(), unsafe { libc::getegid() } as i64);
}
#[test]
fn usernamegetfn_matches_libc_getpwuid_for_current_uid() {
let _g = crate::test_util::global_state_lock();
let __pm = crate::ported::zsh_h::param::default();
let uname = usernamegetfn(&__pm);
let direct = unsafe {
let pw = libc::getpwuid(libc::getuid());
if pw.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr((*pw).pw_name)
.to_string_lossy()
.into_owned()
}
};
assert_eq!(
uname, direct,
"c:4658 — usernamegetfn must match getpwuid(getuid())->pw_name"
);
}
#[test]
fn test_random_returns_15_bit_value() {
let _g = crate::test_util::global_state_lock();
for _ in 0..100 {
let v = randomgetfn();
assert!(v >= 0 && v < 0x8000);
}
}
#[test]
fn test_random_set_seeds_deterministically() {
let _g = crate::test_util::global_state_lock();
randomsetfn(42);
let a = randomgetfn();
randomsetfn(42);
let b = randomgetfn();
assert_eq!(a, b);
}
#[test]
fn test_ifs_round_trip() {
let _g = crate::test_util::global_state_lock();
let mut __pm = crate::ported::zsh_h::param::default();
let original = ifsgetfn(&__pm);
ifssetfn(&mut __pm, ":,;".to_string());
assert_eq!(ifsgetfn(&__pm), ":,;");
ifssetfn(&mut __pm, original);
}
#[test]
fn test_histsiz_clamps_to_1() {
let _g = crate::test_util::global_state_lock();
let _g = HISTSIZ_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let original = histsizegetfn();
histsizesetfn(0);
assert_eq!(histsizegetfn(), 1);
histsizesetfn(-5);
assert_eq!(histsizegetfn(), 1);
histsizesetfn(500);
assert_eq!(histsizegetfn(), 500);
histsizesetfn(original);
}
#[test]
fn test_savehistsiz_clamps_to_0() {
let _g = crate::test_util::global_state_lock();
let original = savehistsizegetfn();
savehistsizesetfn(-5);
assert_eq!(savehistsizegetfn(), 0);
savehistsizesetfn(100);
assert_eq!(savehistsizegetfn(), 100);
savehistsizesetfn(original);
}
#[test]
fn savehistsizesetfn_syncs_to_hist_module() {
let _g = crate::test_util::global_state_lock();
let original_params = savehistsizegetfn();
let original_hist = savehistsiz.load(Ordering::SeqCst);
savehistsizesetfn(12345);
assert_eq!(
savehistsizegetfn(),
12345,
"c:4994 — params.rs Mutex<i64> reflects new value"
);
assert_eq!(
savehistsiz.load(Ordering::SeqCst),
12345,
"c:4994 — hist.rs AtomicI64 synced (was the previous gap)"
);
savehistsizesetfn(-99);
assert_eq!(savehistsizegetfn(), 0, "c:4998 — params.rs clamps to 0");
assert_eq!(
savehistsiz.load(Ordering::SeqCst),
0,
"c:4998 — hist.rs clamps to 0 too"
);
savehistsizesetfn(original_params);
savehistsiz.store(original_hist, Ordering::SeqCst);
}
#[test]
fn test_pipestat_round_trip() {
let _g = crate::test_util::global_state_lock();
pipestatsetfn(Some(vec![
"1".to_string(),
"0".to_string(),
"127".to_string(),
]));
let v = pipestatgetfn();
assert_eq!(v, vec!["1", "0", "127"]);
pipestatsetfn(None);
assert_eq!(pipestatgetfn(), Vec::<String>::new());
}
#[test]
fn setnumvalue_stores_int_value_into_scalar_pm() {
let _g = crate::test_util::global_state_lock();
let saved_exec = opt_state_get("exec").unwrap_or(false);
opt_state_set("exec", true);
let mut pm = Box::new(param {
node: hashnode {
next: None,
nam: "x".to_string(),
flags: PM_SCALAR as i32,
},
u_data: 0,
u_arr: None,
u_str: Some(String::new()),
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
});
let mut v = value {
pm: Some(pm.clone()),
arr: Vec::new(),
scanflags: 0,
valflags: 0,
start: 0,
end: -1,
};
let val = mnumber {
l: 42,
d: 0.0,
type_: MN_INTEGER,
};
setnumvalue(Some(&mut v), val);
let stored = v.pm.as_ref().unwrap().u_str.clone().unwrap_or_default();
assert_eq!(
stored, "42",
"c:2871 — setnumvalue must store the rendered integer; \
was previously dropped via `let _ = s;`"
);
let _ = pm;
opt_state_set("exec", saved_exec);
}
#[test]
fn test_simple_arrayuniq_first_wins() {
let _g = crate::test_util::global_state_lock();
let v = vec![
"a".to_string(),
"b".to_string(),
"a".to_string(),
"c".to_string(),
];
assert_eq!(simple_arrayuniq(v), vec!["a", "b", "c"]);
}
#[test]
fn test_split_env_string() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
split_env_string("PATH=/usr/bin:/bin"),
Some(("PATH".to_string(), "/usr/bin:/bin".to_string()))
);
assert_eq!(
split_env_string("EMPTY="),
Some(("EMPTY".to_string(), "".to_string()))
);
assert_eq!(split_env_string("NOEQUALS"), None);
}
#[test]
fn test_mkenvstr() {
let _g = crate::test_util::global_state_lock();
assert_eq!(mkenvstr("PATH", "/usr/bin", 0), "PATH=/usr/bin");
assert_eq!(mkenvstr("EMPTY", "", 0), "EMPTY=");
}
#[test]
fn test_seconds_round_trip() {
let _g = crate::test_util::global_state_lock();
intsecondssetfn(0);
let s1 = intsecondsgetfn();
std::thread::sleep(Duration::from_millis(5));
let s2 = intsecondsgetfn();
assert!(s2 >= s1);
setrawseconds(100.0);
assert_eq!(getrawseconds(), 100.0);
}
#[test]
fn test_argzero_round_trip() {
let _g = crate::test_util::global_state_lock();
argzerosetfn("/bin/zsh".to_string());
assert_eq!(argzerogetfn(), "/bin/zsh");
argzerosetfn(String::new());
}
#[test]
fn test_env_get_set() {
let _g = crate::test_util::global_state_lock();
let result = zputenv("ZSHRS_TEST_VAR=hello");
assert_eq!(result, 0);
assert_eq!(zgetenv("ZSHRS_TEST_VAR"), Some("hello".to_string()));
delenv("ZSHRS_TEST_VAR");
assert_eq!(zgetenv("ZSHRS_TEST_VAR"), None);
}
#[test]
fn test_keyboardhack_one_char() {
let _g = crate::test_util::global_state_lock();
let mut __pm = crate::ported::zsh_h::param::default();
keyboardhacksetfn(&mut __pm, "\\".to_string());
assert_eq!(keyboardhackgetfn(&__pm), "\\");
keyboardhacksetfn(&mut __pm, String::new());
assert_eq!(keyboardhackgetfn(&__pm), "");
}
#[test]
fn keyboardhacksetfn_handles_ascii_and_empty() {
let _g = crate::test_util::global_state_lock();
let mut __pm = crate::ported::zsh_h::param::default();
keyboardhacksetfn(&mut __pm, ";".to_string());
assert_eq!(
keyboardhackgetfn(&__pm),
";",
"c:5056 — single ASCII char stored verbatim"
);
keyboardhacksetfn(&mut __pm, ",".to_string());
assert_eq!(keyboardhackgetfn(&__pm), ",");
keyboardhacksetfn(&mut __pm, String::new());
assert_eq!(keyboardhackgetfn(&__pm), "");
}
#[test]
fn test_histchars_default() {
let _g = crate::test_util::global_state_lock();
let _g = HISTCHARS_TEST_LOCK_SHARED
.lock()
.unwrap_or_else(|e| e.into_inner());
histcharssetfn(&mut param::default(), String::new());
assert_eq!(histcharsgetfn(¶m::default()), "!^#");
histcharssetfn(&mut param::default(), "@$&".to_string());
assert_eq!(histcharsgetfn(¶m::default()), "@$&");
histcharssetfn(&mut param::default(), String::new());
}
#[test]
fn histcharssetfn_handles_1_2_3_char_inputs() {
let _g = crate::test_util::global_state_lock();
let _g = HISTCHARS_TEST_LOCK_SHARED
.lock()
.unwrap_or_else(|e| e.into_inner());
histcharssetfn(&mut param::default(), "Q".to_string());
assert_eq!(bangchar.load(Ordering::SeqCst), b'Q' as i32);
assert_eq!(hatchar.load(Ordering::SeqCst), 0);
assert_eq!(hashchar.load(Ordering::SeqCst), 0);
histcharssetfn(&mut param::default(), "XY".to_string());
assert_eq!(bangchar.load(Ordering::SeqCst), b'X' as i32);
assert_eq!(hatchar.load(Ordering::SeqCst), b'Y' as i32);
assert_eq!(hashchar.load(Ordering::SeqCst), 0);
histcharssetfn(&mut param::default(), "ABC".to_string());
assert_eq!(bangchar.load(Ordering::SeqCst), b'A' as i32);
assert_eq!(hatchar.load(Ordering::SeqCst), b'B' as i32);
assert_eq!(hashchar.load(Ordering::SeqCst), b'C' as i32);
histcharssetfn(&mut param::default(), "WXYZ".to_string());
assert_eq!(bangchar.load(Ordering::SeqCst), b'W' as i32);
assert_eq!(hatchar.load(Ordering::SeqCst), b'X' as i32);
assert_eq!(hashchar.load(Ordering::SeqCst), b'Y' as i32);
histcharssetfn(&mut param::default(), String::new());
assert_eq!(bangchar.load(Ordering::SeqCst), b'!' as i32);
assert_eq!(hatchar.load(Ordering::SeqCst), b'^' as i32);
assert_eq!(hashchar.load(Ordering::SeqCst), b'#' as i32);
}
}
fn foundparam_lock() -> &'static Mutex<Option<String>> {
FOUNDPARAM.get_or_init(|| Mutex::new(None))
}
pub fn paramtab() -> &'static RwLock<HashMap<String, Param>> {
PARAMTAB_INNER.get_or_init(|| RwLock::new(HashMap::new()))
}
pub fn realparamtab() -> &'static RwLock<HashMap<String, Param>> {
REALPARAMTAB_INNER.get_or_init(|| RwLock::new(HashMap::new()))
}
fn scanprog_lock() -> &'static Mutex<Option<String>> {
SCANPROG.get_or_init(|| Mutex::new(None))
}
fn scanstr_lock() -> &'static Mutex<Option<String>> {
SCANSTR.get_or_init(|| Mutex::new(None))
}
fn paramvals_lock() -> &'static Mutex<Vec<String>> {
PARAMVALS.get_or_init(|| Mutex::new(Vec::new()))
}
#[cfg(test)]
mod tests {
use crate::ported::zsh_h::Pound;
use super::*;
use crate::zsh_h::hashnode;
#[test]
fn setscope_base_pushes_name_when_base_above_level() {
let _g = crate::test_util::global_state_lock();
SCOPEREFS.with(|s| s.borrow_mut().clear());
let mut pm = param {
node: hashnode {
next: None,
nam: "foo".to_string(),
flags: 0,
},
u_data: 0,
u_arr: None,
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 2,
};
setscope_base(&mut pm, 5);
assert_eq!(pm.base, 5);
SCOPEREFS.with(|s| {
let s = s.borrow();
assert!(s.len() >= 6, "SCOPEREFS grew to fit index 5");
assert_eq!(s[5], vec!["foo".to_string()]);
});
}
#[test]
fn assignaparam_rejects_slice_into_hashed() {
let _g = crate::test_util::global_state_lock();
opt_state_set("exec", true);
unsetparam("aa_h");
sethparam("aa_h", vec!["k".to_string(), "v".to_string()]);
let before = paramtab().read().unwrap().contains_key("aa_h");
assert!(before);
let result = assignaparam("aa_h[idx]", vec!["x".to_string()], 0);
assert!(result.is_none(), "slice into hashed must return None");
unsetparam("aa_h");
opt_state_set("exec", false);
}
#[test]
fn assignaparam_rejects_nameref_type_change() {
let _g = crate::test_util::global_state_lock();
opt_state_set("exec", true);
unsetparam("aa_nr");
let pm = param {
node: hashnode {
next: None,
nam: "aa_nr".to_string(),
flags: (PM_NAMEREF | PM_SCALAR) as i32,
},
u_data: 0,
u_arr: None,
u_str: Some("target".to_string()),
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
};
paramtab()
.write()
.unwrap()
.insert("aa_nr".to_string(), Box::new(pm));
let result = assignaparam("aa_nr", vec!["a".to_string(), "b".to_string()], 0);
assert!(result.is_none(), "nameref type change must return None");
let pm = paramtab().read().unwrap().get("aa_nr").cloned().unwrap();
assert_ne!(pm.node.flags as u32 & PM_NAMEREF, 0, "PM_NAMEREF preserved");
unsetparam("aa_nr");
opt_state_set("exec", false);
}
#[test]
fn assignaparam_augment_prepends_old_scalar() {
let _g = crate::test_util::global_state_lock();
opt_state_set("exec", true);
unsetparam("aa_aug");
setsparam("aa_aug", "old");
let pm = assignaparam(
"aa_aug",
vec!["new1".to_string(), "new2".to_string()],
ASSPM_AUGMENT,
)
.expect("augment should succeed");
let arr = pm.u_arr.expect("ASSPM_AUGMENT must produce u_arr");
assert_eq!(
arr,
vec!["old".to_string(), "new1".to_string(), "new2".to_string()],
"c:3408-3411 — scalar prepended at index 0, then new values follow"
);
unsetparam("aa_aug");
opt_state_set("exec", false);
}
#[test]
fn assignaparam_unique_flag_dedupes_values() {
let _g = crate::test_util::global_state_lock();
opt_state_set("exec", true);
unsetparam("aa_uniq");
setaparam("aa_uniq", vec![]);
{
let mut tab = paramtab().write().unwrap();
let pm = tab.get_mut("aa_uniq").expect("aa_uniq must exist");
pm.node.flags |= PM_UNIQUE as i32;
}
let pm = assignaparam(
"aa_uniq",
vec!["a".into(), "b".into(), "a".into(), "c".into(), "b".into()],
0,
)
.expect("assignment succeeds");
let arr = pm.u_arr.expect("u_arr populated");
assert_eq!(
arr,
vec!["a".to_string(), "b".to_string(), "c".to_string()],
"c:3401 — PM_UNIQUE collapses duplicates, keeping first occurrence"
);
let pm_check = paramtab().read().unwrap().get("aa_uniq").cloned().unwrap();
assert_ne!(
pm_check.node.flags as u32 & PM_UNIQUE,
0,
"PM_UNIQUE flag preserved across assignment"
);
unsetparam("aa_uniq");
opt_state_set("exec", false);
}
#[test]
fn getsparam_returns_integer_via_convbase() {
let _g = crate::test_util::global_state_lock();
opt_state_set("exec", true);
unsetparam("gs_int");
setiparam("gs_int", 999);
assert_eq!(getsparam("gs_int").as_deref(), Some("999"));
unsetparam("gs_int");
opt_state_set("exec", false);
}
#[test]
fn getsparam_returns_float_via_convfloat() {
let _g = crate::test_util::global_state_lock();
opt_state_set("exec", true);
unsetparam("gs_f");
let v = mnumber {
l: 0,
d: 2.5,
type_: MN_FLOAT,
};
setnparam("gs_f", v);
let s = getsparam("gs_f").expect("PM_FFLOAT should serialize");
assert!(
s.parse::<f64>()
.map(|f| (f - 2.5).abs() < 1e-6)
.unwrap_or(false),
"expected ~2.5 round-trip, got {:?}",
s
);
unsetparam("gs_f");
opt_state_set("exec", false);
}
#[test]
fn getsparam_integer_honors_pm_base() {
let _g = crate::test_util::global_state_lock();
opt_state_set("exec", true);
unsetparam("hex_param");
let pm = param {
node: hashnode {
next: None,
nam: "hex_param".to_string(),
flags: PM_INTEGER as i32,
},
u_data: 0,
u_arr: None,
u_str: None,
u_val: 255,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 16,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
};
paramtab()
.write()
.unwrap()
.insert("hex_param".to_string(), Box::new(pm));
let s = getsparam("hex_param").expect("PM_INTEGER must serialize");
assert!(
s.contains("FF") || s.contains("ff"),
"c:2364 — base-16 must render hex digits; got {:?}",
s
);
unsetparam("hex_param");
opt_state_set("exec", false);
}
#[test]
fn endparamscope_resets_scoperefs_and_nameref_base() {
let _g = crate::test_util::global_state_lock();
SCOPEREFS.with(|s| s.borrow_mut().clear());
set_locallevel(3);
let pm = param {
node: hashnode {
next: None,
nam: "ref1".to_string(),
flags: (PM_NAMEREF | PM_SCALAR) as i32,
},
u_data: 0,
u_arr: None,
u_str: Some(String::new()),
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 5,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
};
paramtab()
.write()
.unwrap()
.insert("ref1".to_string(), Box::new(pm));
SCOPEREFS.with(|sr| {
let mut sr = sr.borrow_mut();
sr.resize(8, Vec::new());
sr[3].push("ref1".to_string());
});
endparamscope();
let pm_after = paramtab().read().unwrap().get("ref1").cloned();
assert!(pm_after.is_some(), "ref1 should still exist (level=0)");
assert_eq!(pm_after.unwrap().base, 0, "PM_NAMEREF.base reset to 0");
SCOPEREFS.with(|sr| {
assert!(sr.borrow()[3].is_empty(), "SCOPEREFS[3] cleared");
});
paramtab().write().unwrap().remove("ref1");
set_locallevel(0);
}
#[test]
fn endparamscope_preserves_pm_upper_nameref_base() {
let _g = crate::test_util::global_state_lock();
SCOPEREFS.with(|s| s.borrow_mut().clear());
set_locallevel(3);
let pm = param {
node: hashnode {
next: None,
nam: "up_ref".to_string(),
flags: (PM_NAMEREF | PM_SCALAR | PM_UPPER) as i32,
},
u_data: 0,
u_arr: None,
u_str: Some(String::new()),
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 5,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
};
paramtab()
.write()
.unwrap()
.insert("up_ref".to_string(), Box::new(pm));
SCOPEREFS.with(|sr| {
let mut sr = sr.borrow_mut();
sr.resize(8, Vec::new());
sr[3].push("up_ref".to_string());
});
endparamscope();
let pm_after = paramtab()
.read()
.unwrap()
.get("up_ref")
.cloned()
.expect("up_ref must survive scope pop");
assert_eq!(
pm_after.base, 5,
"c:5891 — PM_UPPER nameref base MUST be preserved (was 5, must stay 5)"
);
assert_ne!(
pm_after.node.flags as u32 & PM_UPPER,
0,
"PM_UPPER flag itself must persist"
);
paramtab().write().unwrap().remove("up_ref");
set_locallevel(0);
}
#[test]
fn setscope_base_no_push_when_base_below_level() {
let _g = crate::test_util::global_state_lock();
SCOPEREFS.with(|s| s.borrow_mut().clear());
let mut pm = param {
node: hashnode {
next: None,
nam: "bar".to_string(),
flags: 0,
},
u_data: 0,
u_arr: None,
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 10,
};
setscope_base(&mut pm, 3); assert_eq!(pm.base, 3);
SCOPEREFS.with(|s| {
assert!(s.borrow().is_empty() || s.borrow().iter().all(|v| v.is_empty()));
});
}
#[test]
fn setscope_base_equal_level_does_not_push() {
let _g = crate::test_util::global_state_lock();
SCOPEREFS.with(|s| s.borrow_mut().clear());
let mut pm = param {
node: hashnode {
next: None,
nam: "edge".to_string(),
flags: 0,
},
u_data: 0,
u_arr: None,
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 5,
};
setscope_base(&mut pm, 5); assert_eq!(pm.base, 5, "base assignment always happens");
SCOPEREFS.with(|sr| {
let any_push = sr.borrow().iter().any(|v| !v.is_empty());
assert!(
!any_push,
"c:6440 — `base > pm->level` is STRICT; equal must not push"
);
});
}
#[test]
fn test_colonarr_conversion() {
let _g = crate::test_util::global_state_lock();
let arr = colonsplit("/bin:/usr/bin:/usr/local/bin", false);
assert_eq!(arr, vec!["/bin", "/usr/bin", "/usr/local/bin"]);
let path = colonarrgetfn(&arr);
assert_eq!(path, "/bin:/usr/bin:/usr/local/bin");
}
#[test]
fn test_isident() {
let _g = crate::test_util::global_state_lock();
assert!(isident("foo"));
assert!(isident("_bar"));
assert!(isident("FOO_BAR"));
assert!(isident("x123"));
assert!(isident("123")); assert!(!isident(""));
assert!(!isident("foo bar"));
}
#[test]
fn isident_requires_balanced_subscript_brackets() {
let _g = crate::test_util::global_state_lock();
assert!(
isident("foo[0]"),
"c:1330 — balanced [0] passes parse_subscript"
);
assert!(
isident("foo[bar]"),
"c:1330 — balanced [bar] passes parse_subscript"
);
assert!(
!isident("foo["),
"c:1330 — `foo[` missing `]` MUST be rejected"
);
assert!(isident("a[1]"), "c:1330 — short balanced subscript valid");
}
#[test]
fn test_unique_array() {
let _g = crate::test_util::global_state_lock();
let arr = vec!["a".into(), "b".into(), "a".into(), "c".into(), "b".into()];
let result = uniqarray(arr);
assert_eq!(result, vec!["a", "b", "c"]);
}
#[test]
fn test_convbase() {
let _g = crate::test_util::global_state_lock();
assert_eq!(convbase(255, 16), "16#FF");
assert_eq!(convbase(10, 10), "10");
assert_eq!(convbase(-5, 10), "-5");
assert_eq!(convbase(7, 8), "8#7");
assert_eq!(convbase(5, 2), "2#101");
}
#[test]
fn test_convfloat() {
let _g = crate::test_util::global_state_lock();
let s = convfloat(2.5, 2, PM_FFLOAT);
assert!(s.starts_with("2.50"));
assert_eq!(convfloat(f64::INFINITY, 0, 0), "Inf");
assert_eq!(convfloat(f64::NEG_INFINITY, 0, 0), "-Inf");
assert_eq!(convfloat(f64::NAN, 0, 0), "NaN");
}
#[test]
fn test_getarrvalue() {
let _g = crate::test_util::global_state_lock();
let arr = vec!["a".into(), "b".into(), "c".into(), "d".into()];
assert_eq!(getarrvalue(&arr, 2, 3), vec!["b", "c"]);
assert_eq!(getarrvalue(&arr, -2, -1), vec!["c", "d"]);
assert_eq!(getarrvalue(&arr, 1, 4), vec!["a", "b", "c", "d"]);
}
#[test]
fn test_setarrvalue() {
let _g = crate::test_util::global_state_lock();
let saved_exec = opt_state_get("exec").unwrap_or(false);
opt_state_set("exec", true);
let pm = Box::new(param {
node: hashnode {
next: None,
nam: "test".to_string(),
flags: PM_ARRAY as i32,
},
u_data: 0,
u_arr: Some(vec!["a".into(), "b".into(), "c".into(), "d".into()]),
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
});
let mut v = value {
pm: Some(pm),
arr: Vec::new(),
scanflags: 0,
valflags: 0,
start: 2,
end: 3,
};
setarrvalue(&mut v, vec!["X".into(), "Y".into()]);
let arr = v.pm.unwrap().u_arr.unwrap();
assert_eq!(arr, vec!["a", "X", "Y", "d"]);
opt_state_set("exec", saved_exec);
}
#[test]
fn test_valid_refname() {
let _g = crate::test_util::global_state_lock();
assert!(valid_refname("foo", 0));
assert!(valid_refname("_bar", 0));
assert!(valid_refname("1", 0));
assert!(valid_refname("!", 0));
assert!(valid_refname("arr[1]", 0));
assert!(!valid_refname("", 0));
assert!(!valid_refname(" ", 0));
assert!(!valid_refname("1", PM_UPPER as i32));
assert!(!valid_refname("argv", PM_UPPER as i32));
assert!(!valid_refname("ARGC", PM_UPPER as i32));
}
#[test]
fn test_uniq_array_empty() {
let _g = crate::test_util::global_state_lock();
let empty: Vec<String> = Vec::new();
assert!(uniqarray(empty).is_empty());
}
#[test]
fn test_convbase_underscore() {
let _g = crate::test_util::global_state_lock();
let s = convbase_underscore(1234567, 10, 3);
assert_eq!(s, "1_234_567");
}
fn val_str(v: getarg_out<'_>) -> String {
match v {
getarg_out::Value(v) => v.to_str(),
getarg_out::Flags { .. } => panic!("expected Value, got Flags"),
}
}
#[test]
fn getarg_n_flag_picks_second_exact_match() {
let _g = crate::test_util::global_state_lock();
let arr: Vec<String> = vec!["foo".into(), "bar".into(), "foo".into(), "baz".into()];
let out = getarg("(en.2.r)foo", Some(&arr), None, None).expect("Some");
assert_eq!(val_str(out), "foo");
}
#[test]
fn getarg_n_flag_third_exact_match() {
let _g = crate::test_util::global_state_lock();
let arr: Vec<String> = vec!["a".into(), "a".into(), "a".into(), "b".into()];
let out = getarg("(en.3.r)a", Some(&arr), None, None).expect("Some");
assert_eq!(val_str(out), "a");
}
#[test]
fn getarg_n_flag_returns_index_with_i() {
let _g = crate::test_util::global_state_lock();
let arr: Vec<String> = vec!["x".into(), "y".into(), "x".into(), "y".into()];
let out = getarg("(en.2.i)x", Some(&arr), None, None).expect("Some");
assert_eq!(val_str(out), "3");
}
#[test]
fn getarg_negative_n_flips_search_direction() {
let _g = crate::test_util::global_state_lock();
let arr: Vec<String> = vec!["a".into(), "a".into(), "a".into()];
let out = getarg("(en.-1.i)a", Some(&arr), None, None).expect("Some");
assert_eq!(val_str(out), "3");
}
#[test]
fn getarg_n_flag_zero_treated_as_one() {
let _g = crate::test_util::global_state_lock();
let arr: Vec<String> = vec!["x".into(), "y".into()];
let out = getarg("(en.0.r)x", Some(&arr), None, None).expect("Some");
assert_eq!(val_str(out), "x");
}
#[test]
fn getarg_unknown_flag_char_returns_none() {
let _g = crate::test_util::global_state_lock();
let arr: Vec<String> = vec!["x".into()];
assert!(getarg("(z)x", Some(&arr), None, None).is_none());
}
#[test]
fn getarg_n_flag_unterminated_arg_returns_none() {
let _g = crate::test_util::global_state_lock();
let arr: Vec<String> = vec!["x".into()];
assert!(getarg("(n.5", Some(&arr), None, None).is_none());
}
#[test]
fn getarg_b_flag_starts_search_at_index() {
let _g = crate::test_util::global_state_lock();
let arr: Vec<String> = vec!["x".into(), "y".into(), "x".into(), "y".into()];
let out = getarg("(b.3.ei)x", Some(&arr), None, None).expect("Some");
assert_eq!(val_str(out), "3");
}
#[test]
fn getarg_b_flag_with_R_reverse_from_offset() {
let _g = crate::test_util::global_state_lock();
let arr: Vec<String> = vec!["x".into(), "y".into(), "x".into(), "y".into()];
let out = getarg("(b.3.eIR)x", Some(&arr), None, None).expect("Some");
assert_eq!(val_str(out), "3");
}
#[test]
fn getarg_b_flag_out_of_bounds_forward_returns_empty() {
let _g = crate::test_util::global_state_lock();
let arr: Vec<String> = vec!["x".into()];
let out = getarg("(b.5.er)x", Some(&arr), None, None).expect("Some");
assert_eq!(val_str(out), "");
}
#[test]
fn getarg_b_flag_out_of_bounds_index_mode_returns_len_plus_one() {
let _g = crate::test_util::global_state_lock();
let arr: Vec<String> = vec!["x".into(), "y".into()];
let out = getarg("(b.5.ei)x", Some(&arr), None, None).expect("Some");
assert_eq!(val_str(out), "3");
}
#[test]
fn getarg_hash_neg_num_on_lowercase_r_returns_all() {
let _g = crate::test_util::global_state_lock();
let mut h: IndexMap<String, String> = IndexMap::new();
h.insert("a".into(), "1".into());
h.insert("b".into(), "1".into());
h.insert("c".into(), "2".into());
let out = getarg("(en.-1.r)1", None, Some(&h), None).expect("Some");
assert_eq!(val_str(out), "1 1");
}
#[test]
fn getarg_hash_neg_num_on_uppercase_R_returns_single() {
let _g = crate::test_util::global_state_lock();
let mut h: IndexMap<String, String> = IndexMap::new();
h.insert("a".into(), "1".into());
h.insert("b".into(), "1".into());
h.insert("c".into(), "2".into());
let out = getarg("(en.-1.R)1", None, Some(&h), None).expect("Some");
assert_eq!(val_str(out), "1");
}
#[test]
fn getarg_hash_b_flag_skips_first_n_entries() {
let _g = crate::test_util::global_state_lock();
let mut h: IndexMap<String, String> = IndexMap::new();
h.insert("a".into(), "1".into());
h.insert("b".into(), "1".into());
h.insert("c".into(), "1".into());
let out = getarg("(b.3.ei)1", None, Some(&h), None).expect("Some");
assert_eq!(val_str(out), "c");
}
#[test]
fn getarg_hash_b_flag_with_R_collects_from_offset() {
let _g = crate::test_util::global_state_lock();
let mut h: IndexMap<String, String> = IndexMap::new();
h.insert("a".into(), "1".into());
h.insert("b".into(), "1".into());
h.insert("c".into(), "1".into());
let out = getarg("(b.2.eI)1", None, Some(&h), None).expect("Some");
assert_eq!(val_str(out), "b c");
}
#[test]
fn getarg_hash_b_flag_out_of_bounds_returns_empty() {
let _g = crate::test_util::global_state_lock();
let mut h: IndexMap<String, String> = IndexMap::new();
h.insert("a".into(), "1".into());
let out = getarg("(b.5.e)1", None, Some(&h), None).expect("Some");
assert_eq!(val_str(out), "");
}
#[test]
fn getarg_w_flag_splits_multi_word_array_elements() {
let _g = crate::test_util::global_state_lock();
let arr: Vec<String> = vec!["a b".into(), "c d".into()];
let out = getarg("(w)2", Some(&arr), None, None).expect("Some");
assert_eq!(val_str(out), "b");
}
#[test]
fn getarg_w_flag_simple_array_indexing_still_works() {
let _g = crate::test_util::global_state_lock();
let arr: Vec<String> = vec!["one".into(), "two".into(), "three".into()];
let out = getarg("(w)2", Some(&arr), None, None).expect("Some");
assert_eq!(val_str(out), "two");
}
#[test]
fn getarg_f_flag_splits_by_newline() {
let _g = crate::test_util::global_state_lock();
let arr: Vec<String> = vec!["a b\nc d".into()];
let out = getarg("(f)2", Some(&arr), None, None).expect("Some");
assert_eq!(val_str(out), "c d");
}
#[test]
fn getarg_scalar_w_flag_picks_nth_word() {
let _g = crate::test_util::global_state_lock();
let out = getarg("(w)2", None, None, Some("hello world foo")).expect("Some");
assert_eq!(val_str(out), "world");
}
#[test]
fn getarg_scalar_w_flag_negative_index_counts_from_end() {
let _g = crate::test_util::global_state_lock();
let out = getarg("(w)-1", None, None, Some("alpha beta gamma")).expect("Some");
assert_eq!(val_str(out), "gamma");
}
#[test]
fn getarg_scalar_re_returns_char_at_match_position() {
let _g = crate::test_util::global_state_lock();
let out = getarg("(re)bc", None, None, Some("abcdef")).expect("Some");
assert_eq!(val_str(out), "b");
}
#[test]
fn getarg_scalar_ie_returns_position_of_first_match() {
let _g = crate::test_util::global_state_lock();
let out = getarg("(ie)cd", None, None, Some("abcdef")).expect("Some");
assert_eq!(val_str(out), "3");
}
#[test]
fn getarg_scalar_Ie_returns_position_of_last_match() {
let _g = crate::test_util::global_state_lock();
let out = getarg("(Ie)b", None, None, Some("abcabc")).expect("Some");
assert_eq!(val_str(out), "5");
}
#[test]
fn getarg_scalar_ie_no_match_returns_len_plus_one() {
let _g = crate::test_util::global_state_lock();
let out = getarg("(ie)z", None, None, Some("abc")).expect("Some");
assert_eq!(val_str(out), "4");
}
#[test]
fn getarg_scalar_Ie_no_match_returns_zero() {
let _g = crate::test_util::global_state_lock();
let out = getarg("(Ie)z", None, None, Some("abc")).expect("Some");
assert_eq!(val_str(out), "0");
}
#[test]
fn getarg_scalar_n_flag_picks_second_match() {
let _g = crate::test_util::global_state_lock();
let out = getarg("(en.2.i)a", None, None, Some("abcabc")).expect("Some");
assert_eq!(val_str(out), "4");
}
#[test]
fn getarg_scalar_b_flag_starts_from_offset() {
let _g = crate::test_util::global_state_lock();
let out = getarg("(b.4.ei)b", None, None, Some("abcbc")).expect("Some");
assert_eq!(val_str(out), "4");
}
#[test]
fn getarg_scalar_re_n2_picks_second_substring() {
let _g = crate::test_util::global_state_lock();
let out = getarg("(en.2.r)b", None, None, Some("abab")).expect("Some");
assert_eq!(val_str(out), "b");
}
#[test]
fn assignsparam_then_getsparam_round_trips() {
let _g = crate::test_util::global_state_lock(); let saved_exec = opt_state_get("exec") .unwrap_or(false); opt_state_set("exec", true); let name = "ZSHRS_TEST_ASSIGN_GET"; assignsparam(name, "test_value_42", 0); assert_eq!(
getsparam(name).as_deref(), Some("test_value_42") ); let _ = paramtab().write().unwrap().remove(name); opt_state_set("exec", saved_exec); }
#[test]
fn getsparam_unknown_param_returns_none() {
let _g = crate::test_util::global_state_lock();
assert!(getsparam("ZSHRS_TEST_DEFINITELY_UNSET").is_none());
}
#[test]
fn paramtab_remove_makes_getsparam_return_none() {
let _g = crate::test_util::global_state_lock();
let name = "ZSHRS_TEST_UNSET_FLOW";
assignsparam(name, "to_be_removed", 0);
assert!(
getsparam(name).is_some(),
"param must be set before remove path"
);
let _ = paramtab().write().unwrap().remove(name);
assert!(
getsparam(name).is_none(),
"after remove, getsparam must return None"
);
}
#[test]
fn assignaparam_populates_paramtab_with_array() {
let _g = crate::test_util::global_state_lock();
let name = "ZSHRS_TEST_ARR_X";
assignaparam(name, vec!["a".into(), "b".into(), "c".into()], 0);
let tab = paramtab().read().expect("paramtab poisoned");
let pm = tab.get(name).expect("param installed");
assert_eq!(
pm.u_arr.as_deref(),
Some(&["a".to_string(), "b".to_string(), "c".to_string()][..]),
"assignaparam stores all three elements"
);
drop(tab);
let _ = paramtab().write().unwrap().remove(name);
}
#[test]
fn histcharssetfn_syncs_all_three_histchar_globals() {
let _g = crate::test_util::global_state_lock();
let _g = HISTCHARS_TEST_LOCK_SHARED
.lock()
.unwrap_or_else(|e| e.into_inner());
histcharssetfn(&mut param::default(), String::new());
assert_eq!(bangchar.load(Ordering::SeqCst), b'!' as i32);
assert_eq!(hatchar.load(Ordering::SeqCst), b'^' as i32);
assert_eq!(hashchar.load(Ordering::SeqCst), b'#' as i32);
histcharssetfn(&mut param::default(), "@:%".to_string());
assert_eq!(
bangchar.load(Ordering::SeqCst),
b'@' as i32,
"c:5095 — bangchar = first byte of HISTCHARS"
);
assert_eq!(
hatchar.load(Ordering::SeqCst),
b':' as i32,
"c:5096 — hatchar = second byte of HISTCHARS"
);
assert_eq!(
hashchar.load(Ordering::SeqCst),
b'%' as i32,
"c:5097 — hashchar = third byte of HISTCHARS"
);
histcharssetfn(&mut param::default(), String::new());
assert_eq!(bangchar.load(Ordering::SeqCst), b'!' as i32);
assert_eq!(hashchar.load(Ordering::SeqCst), b'#' as i32);
}
#[test]
fn histcharsgetfn_round_trips_with_histcharssetfn() {
let _g = crate::test_util::global_state_lock();
let _g = HISTCHARS_TEST_LOCK_SHARED
.lock()
.unwrap_or_else(|e| e.into_inner());
histcharssetfn(&mut param::default(), "@&%".to_string());
assert_eq!(
histcharsgetfn(¶m::default()),
"@&%",
"c:5068-5073 — getfn reads atomic globals setfn wrote"
);
histcharssetfn(&mut param::default(), String::new());
assert_eq!(
histcharsgetfn(¶m::default()),
"!^#",
"default `!^#` round-trips through atomics"
);
}
#[test]
fn homesetfn_stores_value_for_getfn() {
let _g = crate::test_util::global_state_lock();
let mut __pm = crate::ported::zsh_h::param::default();
let saved = homegetfn(&__pm);
homesetfn(&mut __pm, "/tmp/zshrs_test_home".to_string());
assert_eq!(
homegetfn(&__pm),
"/tmp/zshrs_test_home",
"c:5121-5126 — homesetfn → homegetfn round-trip"
);
homesetfn(&mut __pm, saved);
}
#[test]
fn homesetfn_empty_input_stores_empty() {
let _g = crate::test_util::global_state_lock();
let mut __pm = crate::ported::zsh_h::param::default();
let saved = homegetfn(&__pm);
homesetfn(&mut __pm, String::new());
assert_eq!(homegetfn(&__pm), "", "c:5126 — empty x stores empty (no panic)");
homesetfn(&mut __pm, saved);
}
#[test]
#[cfg(any(target_os = "macos", target_os = "linux"))]
fn errnosetfn_writes_through_to_libc_errno_getfn() {
let _g = crate::test_util::global_state_lock();
errnosetfn(42);
assert_eq!(
errnogetfn(),
42,
"c:5006 — errno = (int)x; subsequent getfn must read it back"
);
errnosetfn(0);
assert_eq!(errnogetfn(), 0);
}
#[test]
#[cfg(any(target_os = "macos", target_os = "linux"))]
fn errnosetfn_does_not_panic_on_truncation() {
let _g = crate::test_util::global_state_lock();
errnosetfn(i64::MAX);
let _ = errnogetfn();
errnosetfn(0);
}
#[test]
fn histcharssetfn_rejects_non_ascii_chars() {
let _g = crate::test_util::global_state_lock();
let _g = HISTCHARS_TEST_LOCK_SHARED
.lock()
.unwrap_or_else(|e| e.into_inner());
histcharssetfn(&mut param::default(), String::new());
let bang_before = bangchar.load(Ordering::SeqCst);
let hat_before = hatchar.load(Ordering::SeqCst);
histcharssetfn(&mut param::default(), "é".to_string());
assert_eq!(
bangchar.load(Ordering::SeqCst),
bang_before,
"c:5092 — bangchar unchanged after non-ASCII rejection"
);
assert_eq!(hatchar.load(Ordering::SeqCst), hat_before);
}
static ARGZERO_TEST_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn histsizesetfn_floors_at_one_and_mirrors_to_hist_module() {
let _g = crate::test_util::global_state_lock();
let _g = HISTSIZ_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let saved_param = histsizegetfn();
let saved_hist = histsiz.load(Ordering::SeqCst);
histsizesetfn(0);
assert_eq!(histsizegetfn(), 1, "c:4976 — HISTSIZE 0 must floor at 1");
assert_eq!(
histsiz.load(Ordering::SeqCst),
1,
"c:4977 — mirror into hist::histsiz so resizehistents sees it"
);
histsizesetfn(-5);
assert_eq!(histsizegetfn(), 1, "c:4976 — negative floors at 1");
histsizesetfn(500);
assert_eq!(histsizegetfn(), 500);
assert_eq!(histsiz.load(Ordering::SeqCst), 500);
*histsiz_lock().lock().unwrap() = saved_param;
histsiz.store(saved_hist, Ordering::SeqCst);
}
#[test]
fn underscoregetfn_runs_untokenize_on_zunderscore() {
let _g = crate::test_util::global_state_lock();
let saved = zunderscore_lock().lock().unwrap().clone();
let pound = Pound;
let mut s = String::new();
s.push('a');
s.push(pound);
s.push('b');
*zunderscore_lock().lock().unwrap() = s;
let result = underscoregetfn();
assert!(
!result.contains(pound),
"c:5156 — untokenize must strip Pound token byte from $_"
);
assert!(
result.contains('#') || result.contains("a"),
"c:5156 — Pound (ITOK) maps to '#' via ztokens[0]"
);
*zunderscore_lock().lock().unwrap() = saved;
}
#[test]
fn argzerogetfn_respects_posixargzero_option() {
let _g = crate::test_util::global_state_lock();
let _g = ARGZERO_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let saved_argzero = argzero();
let saved_posixzero = posixzero();
let saved_pos_option = opt_state_get("posixargzero").unwrap_or(false);
set_posixzero(Some("/bin/zsh".to_string()));
set_argzero(Some("rewritten-name".to_string()));
opt_state_set("posixargzero", false);
assert_eq!(
argzerogetfn(),
"rewritten-name",
"c:4960 — !POSIXARGZERO returns argzero (current display name)"
);
opt_state_set("posixargzero", true);
assert_eq!(
argzerogetfn(),
"/bin/zsh",
"c:4959 — POSIXARGZERO on returns posixzero (original startup argv[0])"
);
set_argzero(saved_argzero);
set_posixzero(saved_posixzero);
opt_state_set("posixargzero", saved_pos_option);
}
#[test]
fn set_argzero_mirrors_to_posixzero_only_on_first_call() {
let _g = crate::test_util::global_state_lock();
let _g = ARGZERO_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let saved_argzero = argzero();
let saved_posixzero = posixzero();
set_argzero(None);
set_posixzero(None);
set_argzero(Some("/usr/local/bin/zsh".to_string()));
assert_eq!(
posixzero().as_deref(),
Some("/usr/local/bin/zsh"),
"c:271 — first set_argzero mirrors to posixzero (was None)"
);
set_argzero(Some("function-name".to_string()));
assert_eq!(
posixzero().as_deref(),
Some("/usr/local/bin/zsh"),
"c:271 — second set_argzero does NOT clobber posixzero"
);
assert_eq!(
argzero().as_deref(),
Some("function-name"),
"argzero updated as normal"
);
set_posixzero(saved_posixzero);
set_argzero(saved_argzero);
}
fn locale_test_lock() -> &'static Mutex<()> {
static L: OnceLock<Mutex<()>> = OnceLock::new();
L.get_or_init(|| Mutex::new(()))
}
#[test]
fn lc_names_match_zsh_canonical_table() {
let _g = crate::test_util::global_state_lock();
let names: Vec<&str> = LC_NAMES.iter().map(|(n, _)| *n).collect();
assert_eq!(
names,
vec![
"LC_COLLATE",
"LC_CTYPE",
"LC_MESSAGES",
"LC_NUMERIC",
"LC_TIME"
],
"Src/params.c:4805-4825 — lc_names entry order must be preserved"
);
let cats: Vec<libc::c_int> = LC_NAMES.iter().map(|(_, c)| *c).collect();
let mut sorted = cats.clone();
sorted.sort();
sorted.dedup();
assert_eq!(sorted.len(), 5, "all five LC_* categories must be distinct");
assert!(cats.contains(&libc::LC_COLLATE));
assert!(cats.contains(&libc::LC_CTYPE));
assert!(cats.contains(&libc::LC_MESSAGES));
assert!(cats.contains(&libc::LC_NUMERIC));
assert!(cats.contains(&libc::LC_TIME));
}
#[test]
fn lcsetfn_invokes_libc_setlocale_for_matching_category() {
let _g = crate::test_util::global_state_lock();
let _g = locale_test_lock().lock().unwrap_or_else(|e| e.into_inner());
let saved_lc_all = env::var("LC_ALL").ok();
let saved_lc_ctype = env::var("LC_CTYPE").ok();
env::remove_var("LC_ALL");
let before = unsafe {
let p = libc::setlocale(libc::LC_CTYPE, std::ptr::null());
if p.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned()
}
};
lcsetfn("LC_CTYPE", Some("C".to_string()));
let after = unsafe {
let p = libc::setlocale(libc::LC_CTYPE, std::ptr::null());
if p.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned()
}
};
assert_eq!(
after, "C",
"Src/params.c:4927 — lcsetfn must call setlocale(LC_CTYPE, \"C\")"
);
assert_eq!(env::var("LC_CTYPE").unwrap_or_default(), "C");
let _ = unsafe {
let c = std::ffi::CString::new(before.as_bytes()).unwrap_or_default();
libc::setlocale(libc::LC_CTYPE, c.as_ptr())
};
match saved_lc_all {
Some(v) => env::set_var("LC_ALL", v),
None => env::remove_var("LC_ALL"),
}
match saved_lc_ctype {
Some(v) => env::set_var("LC_CTYPE", v),
None => env::remove_var("LC_CTYPE"),
}
}
#[test]
fn lcsetfn_short_circuits_when_lc_all_set() {
let _g = crate::test_util::global_state_lock();
let _g = locale_test_lock().lock().unwrap_or_else(|e| e.into_inner());
let saved_lc_all = env::var("LC_ALL").ok();
let saved_lc_ctype = env::var("LC_CTYPE").ok();
env::set_var("LC_ALL", "C");
let before = unsafe {
let p = libc::setlocale(libc::LC_CTYPE, std::ptr::null());
if p.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned()
}
};
lcsetfn("LC_CTYPE", Some("POSIX".to_string()));
let after = unsafe {
let p = libc::setlocale(libc::LC_CTYPE, std::ptr::null());
if p.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned()
}
};
assert_eq!(
before, after,
"c:4912-4913 — lcsetfn must early-return when LC_ALL is non-empty"
);
match saved_lc_all {
Some(v) => env::set_var("LC_ALL", v),
None => env::remove_var("LC_ALL"),
}
match saved_lc_ctype {
Some(v) => env::set_var("LC_CTYPE", v),
None => env::remove_var("LC_CTYPE"),
}
}
#[test]
fn getsparam_u_unmetas_getsparam_result() {
let _g = crate::test_util::global_state_lock();
let _g = locale_test_lock().lock().unwrap_or_else(|e| e.into_inner());
let saved = env::var("ZSHRS_TEST_LOCALE_GSU").ok();
env::set_var("ZSHRS_TEST_LOCALE_GSU", "en_US.UTF-8");
assert_eq!(
getsparam_u("ZSHRS_TEST_LOCALE_GSU"),
Some("en_US.UTF-8".to_string()),
"Src/params.c:3092 — getsparam_u returns unmeta(getsparam(s)) for ASCII"
);
env::remove_var("ZSHRS_TEST_LOCALE_GSU_MISSING");
assert_eq!(
getsparam_u("ZSHRS_TEST_LOCALE_GSU_MISSING"),
None,
"Src/params.c:3094 — getsparam_u returns NULL when getsparam returns NULL"
);
match saved {
Some(v) => env::set_var("ZSHRS_TEST_LOCALE_GSU", v),
None => env::remove_var("ZSHRS_TEST_LOCALE_GSU"),
}
}
#[test]
fn setarrvalue_bails_under_no_exec() {
let _g = crate::test_util::global_state_lock();
let saved_exec = opt_state_get("exec").unwrap_or(false);
opt_state_set("exec", false);
let pm = Box::new(param {
node: hashnode {
next: None,
nam: "noexec_arr".to_string(),
flags: PM_ARRAY as i32,
},
u_data: 0,
u_arr: Some(vec!["initial".to_string()]),
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
});
let mut v = value {
pm: Some(pm),
arr: Vec::new(),
scanflags: 0,
valflags: 0,
start: 0,
end: -1,
};
setarrvalue(&mut v, vec!["new1".to_string(), "new2".to_string()]);
let arr = v.pm.as_ref().unwrap().u_arr.clone().unwrap_or_default();
assert_eq!(
arr,
vec!["initial".to_string()],
"c:2897 — NO_EXEC: setarrvalue must NOT replace u_arr"
);
opt_state_set("exec", true);
setarrvalue(&mut v, vec!["new1".to_string(), "new2".to_string()]);
let arr = v.pm.as_ref().unwrap().u_arr.clone().unwrap_or_default();
assert_eq!(
arr,
vec!["new1".to_string(), "new2".to_string()],
"with EXEC set, setarrvalue replaces u_arr"
);
opt_state_set("exec", saved_exec);
}
#[test]
fn setnumvalue_bails_under_no_exec() {
let _g = crate::test_util::global_state_lock();
let saved_exec = opt_state_get("exec").unwrap_or(false);
opt_state_set("exec", false);
let mut pm = Box::new(param {
node: hashnode {
next: None,
nam: "ne".to_string(),
flags: PM_INTEGER as i32,
},
u_data: 0,
u_arr: None,
u_str: None,
u_val: 999,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
});
let mut v = value {
pm: Some(pm.clone()),
arr: Vec::new(),
scanflags: 0,
valflags: 0,
start: 0,
end: -1,
};
let val = mnumber {
l: 42,
d: 0.0,
type_: MN_INTEGER,
};
setnumvalue(Some(&mut v), val);
let stored = v.pm.as_ref().unwrap().u_val;
assert_eq!(
stored, 999,
"c:2860 — NO_EXEC: setnumvalue must NOT mutate pm.u_val \
(was {} but should stay 999)",
stored
);
opt_state_set("exec", true);
setnumvalue(Some(&mut v), val);
let stored = v.pm.as_ref().unwrap().u_val;
assert_eq!(stored, 42, "with EXEC set, setnumvalue stores u_val = 42");
let _ = pm;
opt_state_set("exec", saved_exec);
}
#[test]
fn dash_param_rendering_honors_noexec_via_exec_negation() {
let _g = crate::test_util::global_state_lock();
let saved = opt_state_get("exec").unwrap_or(false);
opt_state_set("exec", true);
let s = lookup_special_var("-").unwrap_or_default();
assert!(
!s.contains('n'),
"exec=true → $-=`{}` must NOT include 'n'",
s
);
opt_state_set("exec", false);
let s = lookup_special_var("-").unwrap_or_default();
assert!(
s.contains('n'),
"exec=false → $-=`{}` MUST include 'n' (was silently dropped \
when reading non-existent option name `noexec`)",
s
);
opt_state_set("exec", saved);
}
#[test]
fn term_unknown_bit_value_matches_c() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
TERM_UNKNOWN, 0x02,
"Src/zsh.h:1986 — TERM_UNKNOWN must be 0x02, got {:#x}",
TERM_UNKNOWN
);
assert_eq!(
TERM_BAD, 0x01,
"Src/zsh.h:1985 — TERM_BAD must be 0x01 (and != TERM_UNKNOWN)"
);
assert_ne!(
TERM_BAD, TERM_UNKNOWN,
"TERM_BAD and TERM_UNKNOWN must be distinct (caught the 1<<0 drift bug)"
);
}
#[test]
fn getstrvalue_pm_integer_honors_pm_base() {
let _g = crate::test_util::global_state_lock();
let saved_cbases_top = opt_state_get("cbases").unwrap_or(false);
opt_state_set("cbases", true);
let mut pm = Box::new(param {
node: hashnode {
next: None,
nam: "test_hex_var".to_string(),
flags: PM_INTEGER as i32,
},
u_data: 0,
u_arr: None,
u_str: None,
u_val: 255,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 16,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
});
let mut v = value {
pm: Some(pm.clone()),
arr: Vec::new(),
scanflags: 0,
valflags: 0,
start: 0,
end: -1,
};
let rendered = getstrvalue(Some(&mut v));
assert_eq!(
rendered, "0xFF",
"c:2373 / c:5621 — PM_INTEGER base=16 + u_val=255 with CBASES \
renders as `0xFF` (uppercase per C `dig - 10 + 'A'`), got {:?}",
rendered
);
let saved_oct = opt_state_get("octalzeroes").unwrap_or(false);
let saved_cbases = opt_state_get("cbases").unwrap_or(false);
opt_state_set("cbases", true);
opt_state_set("octalzeroes", true);
pm.base = 8;
pm.u_val = 8;
v.pm = Some(pm.clone());
let rendered = getstrvalue(Some(&mut v));
assert_eq!(
rendered, "010",
"c:2373 — PM_INTEGER base=8 with OCTALZEROES renders as `010`, got {:?}",
rendered
);
opt_state_set("cbases", saved_cbases);
opt_state_set("octalzeroes", saved_oct);
pm.base = 0;
pm.u_val = 42;
v.pm = Some(pm.clone());
let rendered = getstrvalue(Some(&mut v));
assert_eq!(
rendered, "42",
"c:2373 — PM_INTEGER base=0 defaults to base-10"
);
opt_state_set("cbases", saved_cbases_top);
}
#[test]
fn unsetparam_skips_nameref_and_readonly() {
let _g = crate::test_util::global_state_lock();
let saved_exec = opt_state_get("exec").unwrap_or(false);
opt_state_set("exec", true);
fn install(name: &str, value: &str, flags: u32) {
let mut tab = paramtab().write().unwrap();
tab.insert(
name.to_string(),
Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: (PM_SCALAR | flags) as i32,
},
u_data: 0,
u_arr: None,
u_str: Some(value.to_string()),
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
}),
);
}
let nameref_name = "zshrs_test_unsetparam_nameref";
install(nameref_name, "target_var_name", PM_NAMEREF);
unsetparam(nameref_name);
{
let tab = paramtab().read().unwrap();
assert!(
tab.contains_key(nameref_name),
"c:3830 — PM_NAMEREF param survives unsetparam"
);
}
let ro_name = "zshrs_test_unsetparam_readonly";
install(ro_name, "locked", PM_READONLY);
unsetparam(ro_name);
{
let tab = paramtab().read().unwrap();
assert!(
tab.contains_key(ro_name),
"c:3850 — PM_READONLY param survives unsetparam"
);
}
let plain_name = "zshrs_test_unsetparam_plain";
install(plain_name, "removable", 0);
unsetparam(plain_name);
{
let tab = paramtab().read().unwrap();
assert!(
!tab.contains_key(plain_name),
"plain scalar successfully removed"
);
}
{
let mut tab = paramtab().write().unwrap();
tab.remove(nameref_name);
tab.remove(ro_name);
tab.remove(plain_name);
}
opt_state_set("exec", saved_exec);
}
#[test]
fn assigniparam_takes_flags_arg_and_returns_param() {
let _g = crate::test_util::global_state_lock();
let saved_exec = opt_state_get("exec").unwrap_or(false);
opt_state_set("exec", true);
let name = "zshrs_test_assigniparam_x";
{
let mut tab = paramtab().write().unwrap();
tab.remove(name);
}
let r = assigniparam(name, 77, ASSPM_WARN as i32);
assert!(
r.is_some(),
"c:3760 — returns Some(Param) for new int param"
);
{
let tab = paramtab().read().unwrap();
let pm = tab.get(name).expect("integer param created");
assert_ne!(
(pm.node.flags as u32) & PM_INTEGER,
0,
"c:3757-3760 — PM_INTEGER flag set"
);
assert_eq!(pm.u_val, 77, "c:3759 — value stored in u_val");
}
let r = assigniparam(name, 88, 0);
assert!(r.is_some(), "reassign returns Some");
{
let tab = paramtab().read().unwrap();
let pm = tab.get(name).expect("param still present");
assert_eq!(pm.u_val, 88, "reassign updates u_val");
}
{
let mut tab = paramtab().write().unwrap();
tab.remove(name);
}
opt_state_set("exec", saved_exec);
}
#[test]
fn setnparam_accepts_both_integer_and_float() {
let _g = crate::test_util::global_state_lock();
let saved_exec = opt_state_get("exec").unwrap_or(false);
opt_state_set("exec", true);
let int_name = "zshrs_test_setnparam_i";
let flt_name = "zshrs_test_setnparam_f";
{
let mut tab = paramtab().write().unwrap();
tab.remove(int_name);
tab.remove(flt_name);
}
let r = setnparam(
int_name,
mnumber {
l: 999,
d: 0.0,
type_: MN_INTEGER,
},
);
assert!(r.is_some(), "setnparam returns Some for new param");
{
let tab = paramtab().read().unwrap();
let pm = tab.get(int_name).expect("integer param created");
assert_ne!(
(pm.node.flags as u32) & PM_INTEGER,
0,
"c:3748 — PM_INTEGER flag set for integer mnumber"
);
assert_eq!(pm.u_val, 999, "c:3748 — integer value stored in u_val");
}
let r = setnparam(
flt_name,
mnumber {
l: 0,
d: 3.14,
type_: MN_FLOAT,
},
);
assert!(r.is_some(), "setnparam returns Some for new float param");
{
let tab = paramtab().read().unwrap();
let pm = tab.get(flt_name).expect("float param created");
assert_ne!(
(pm.node.flags as u32) & PM_FFLOAT,
0,
"c:3748 — PM_FFLOAT flag set for float mnumber"
);
assert!(
(pm.u_dval - 3.14).abs() < 1e-10,
"c:3748 — float value stored in u_dval"
);
}
{
let mut tab = paramtab().write().unwrap();
tab.remove(int_name);
tab.remove(flt_name);
}
opt_state_set("exec", saved_exec);
}
#[test]
fn setiparam_creates_pm_integer_param() {
let _g = crate::test_util::global_state_lock();
let name = "zshrs_test_setiparam_x";
let saved_exec = opt_state_get("exec").unwrap_or(false);
opt_state_set("exec", true);
{
let mut tab = paramtab().write().unwrap();
tab.remove(name);
}
setiparam(name, 42);
{
let tab = paramtab().read().unwrap();
let pm = tab.get(name).expect("setiparam must create the param");
assert_ne!(
(pm.node.flags as u32) & PM_INTEGER,
0,
"c:3770-3772 — created param must have PM_INTEGER flag set, \
got flags = {:#x}",
pm.node.flags
);
assert_eq!(pm.u_val, 42, "c:3771 — integer value stored in pm.u_val");
}
setiparam(name, 100);
{
let tab = paramtab().read().unwrap();
let pm = tab.get(name).expect("setiparam reassign must keep param");
assert_eq!(pm.u_val, 100, "reassign updates the integer value");
assert_ne!(
(pm.node.flags as u32) & PM_INTEGER,
0,
"reassign keeps PM_INTEGER flag"
);
}
{
let mut tab = paramtab().write().unwrap();
tab.remove(name);
}
opt_state_set("exec", saved_exec);
}
#[test]
fn gethparam_and_gethkparam_signature_matches_c() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
gethparam("123abc"),
None,
"c:3122 — digit-first name rejected"
);
assert_eq!(
gethkparam("123abc"),
None,
"c:3136 — digit-first name rejected"
);
assert_eq!(
gethparam("zshrs_test_hashparam_xyz"),
None,
"missing param returns None"
);
assert_eq!(
gethkparam("zshrs_test_hashparam_xyz"),
None,
"missing param returns None"
);
{
let mut tab = paramtab().write().unwrap();
tab.insert(
"zshrs_test_gethp_scalar".to_string(),
Box::new(param {
node: hashnode {
next: None,
nam: "zshrs_test_gethp_scalar".to_string(),
flags: PM_SCALAR as i32,
},
u_data: 0,
u_arr: None,
u_str: Some("scalar value".to_string()),
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
}),
);
}
assert_eq!(
gethparam("zshrs_test_gethp_scalar"),
None,
"c:3123 — non-PM_HASHED returns None"
);
assert_eq!(
gethkparam("zshrs_test_gethp_scalar"),
None,
"c:3137 — non-PM_HASHED returns None"
);
{
let mut tab = paramtab().write().unwrap();
tab.insert(
"zshrs_test_gethp_hash".to_string(),
Box::new(param {
node: hashnode {
next: None,
nam: "zshrs_test_gethp_hash".to_string(),
flags: PM_HASHED as i32,
},
u_data: 0,
u_arr: None,
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
}),
);
}
assert_eq!(
gethparam("zshrs_test_gethp_hash"),
Some(Vec::new()),
"c:3123-3124 — PM_HASHED empty-storage returns Some(empty vec)"
);
assert_eq!(
gethkparam("zshrs_test_gethp_hash"),
Some(Vec::new()),
"c:3137-3138 — PM_HASHED empty-storage returns Some(empty vec)"
);
{
let mut store = paramtab_hashed_storage().lock().unwrap();
let mut map: IndexMap<String, String> = IndexMap::new();
map.insert("k1".to_string(), "v1".to_string());
map.insert("k2".to_string(), "v2".to_string());
store.insert("zshrs_test_gethp_hash".to_string(), map);
}
assert_eq!(
gethparam("zshrs_test_gethp_hash"),
Some(vec!["v1".to_string(), "v2".to_string()]),
"c:3124 — paramvalarr(SCANPM_WANTVALS) returns values from hashed-storage"
);
assert_eq!(
gethkparam("zshrs_test_gethp_hash"),
Some(vec!["k1".to_string(), "k2".to_string()]),
"c:3138 — paramvalarr(SCANPM_WANTKEYS) returns keys from hashed-storage"
);
{
let mut tab = paramtab().write().unwrap();
tab.remove("zshrs_test_gethp_scalar");
tab.remove("zshrs_test_gethp_hash");
}
paramtab_hashed_storage()
.lock()
.unwrap()
.remove("zshrs_test_gethp_hash");
}
#[test]
fn getaparam_returns_array_for_pm_array_only() {
let _g = crate::test_util::global_state_lock();
assert_eq!(
getaparam("123abc"),
None,
"c:3107 — digit-first name rejected"
);
assert_eq!(
getaparam("zshrs_test_arr_nonexistent_xyz"),
None,
"c:3110 — missing param returns None"
);
fn build_arr(name: &str, arr: Vec<String>) {
let mut tab = paramtab().write().unwrap();
tab.insert(
name.to_string(),
Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: PM_ARRAY as i32,
},
u_data: 0,
u_arr: Some(arr),
u_str: None,
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
}),
);
}
fn build_scalar(name: &str, s: &str) {
let mut tab = paramtab().write().unwrap();
tab.insert(
name.to_string(),
Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: PM_SCALAR as i32,
},
u_data: 0,
u_arr: None,
u_str: Some(s.to_string()),
u_val: 0,
u_dval: 0.0,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: 0,
width: 0,
env: None,
ename: None,
old: None,
level: 0,
}),
);
}
build_arr(
"zshrs_test_getaparam_arr",
vec!["one".to_string(), "two".to_string(), "three".to_string()],
);
assert_eq!(
getaparam("zshrs_test_getaparam_arr"),
Some(vec![
"one".to_string(),
"two".to_string(),
"three".to_string()
]),
"c:3108-3109 — PM_ARRAY param returns its array"
);
build_scalar("zshrs_test_getaparam_scalar", "not an array");
assert_eq!(
getaparam("zshrs_test_getaparam_scalar"),
None,
"c:3108 — non-PM_ARRAY param returns None"
);
{
let mut tab = paramtab().write().unwrap();
tab.remove("zshrs_test_getaparam_arr");
tab.remove("zshrs_test_getaparam_scalar");
}
}
fn with_exec<F: FnOnce()>(body: F) {
let _g = crate::test_util::global_state_lock();
let saved = opt_state_get("exec").unwrap_or(false);
opt_state_set("exec", true);
body();
opt_state_set("exec", saved);
}
#[test]
fn setsparam_then_getsparam_roundtrip_basic() {
with_exec(|| {
unsetparam("zshrs_rt_s1");
setsparam("zshrs_rt_s1", "hello");
assert_eq!(getsparam("zshrs_rt_s1").as_deref(), Some("hello"));
unsetparam("zshrs_rt_s1");
});
}
#[test]
fn setsparam_empty_string_roundtrips_as_empty() {
with_exec(|| {
unsetparam("zshrs_rt_s2");
setsparam("zshrs_rt_s2", "");
assert_eq!(getsparam("zshrs_rt_s2").as_deref(), Some(""));
unsetparam("zshrs_rt_s2");
});
}
#[test]
fn setsparam_multibyte_utf8_roundtrips() {
with_exec(|| {
unsetparam("zshrs_rt_s3");
setsparam("zshrs_rt_s3", "日本語");
assert_eq!(getsparam("zshrs_rt_s3").as_deref(), Some("日本語"));
unsetparam("zshrs_rt_s3");
});
}
#[test]
fn setsparam_embedded_newline_roundtrips() {
with_exec(|| {
unsetparam("zshrs_rt_s4");
setsparam("zshrs_rt_s4", "a\nb\nc");
assert_eq!(getsparam("zshrs_rt_s4").as_deref(), Some("a\nb\nc"));
unsetparam("zshrs_rt_s4");
});
}
#[test]
fn setsparam_overwrite_replaces_previous_value() {
with_exec(|| {
unsetparam("zshrs_rt_s5");
setsparam("zshrs_rt_s5", "first");
setsparam("zshrs_rt_s5", "second");
assert_eq!(getsparam("zshrs_rt_s5").as_deref(), Some("second"));
unsetparam("zshrs_rt_s5");
});
}
#[test]
fn setiparam_then_getiparam_roundtrip_basic() {
with_exec(|| {
unsetparam("zshrs_rt_i1");
setiparam("zshrs_rt_i1", 42);
assert_eq!(getiparam("zshrs_rt_i1"), 42);
unsetparam("zshrs_rt_i1");
});
}
#[test]
fn setiparam_negative_value_roundtrips() {
with_exec(|| {
unsetparam("zshrs_rt_i2");
setiparam("zshrs_rt_i2", -12345);
assert_eq!(getiparam("zshrs_rt_i2"), -12345);
unsetparam("zshrs_rt_i2");
});
}
#[test]
fn setiparam_zero_roundtrips() {
with_exec(|| {
unsetparam("zshrs_rt_i3");
setiparam("zshrs_rt_i3", 0);
assert_eq!(getiparam("zshrs_rt_i3"), 0);
unsetparam("zshrs_rt_i3");
});
}
#[test]
fn setiparam_i64_extremes_roundtrip() {
with_exec(|| {
unsetparam("zshrs_rt_i4");
setiparam("zshrs_rt_i4", i64::MAX);
assert_eq!(getiparam("zshrs_rt_i4"), i64::MAX);
setiparam("zshrs_rt_i4", i64::MIN);
assert_eq!(getiparam("zshrs_rt_i4"), i64::MIN);
unsetparam("zshrs_rt_i4");
});
}
#[test]
fn setiparam_then_getsparam_coerces_to_decimal_string() {
with_exec(|| {
unsetparam("zshrs_rt_x1");
setiparam("zshrs_rt_x1", 42);
assert_eq!(getsparam("zshrs_rt_x1").as_deref(), Some("42"));
unsetparam("zshrs_rt_x1");
});
}
#[test]
fn setiparam_negative_int_to_string_carries_minus_sign() {
with_exec(|| {
unsetparam("zshrs_rt_x2");
setiparam("zshrs_rt_x2", -7);
assert_eq!(getsparam("zshrs_rt_x2").as_deref(), Some("-7"));
unsetparam("zshrs_rt_x2");
});
}
#[test]
fn setsparam_numeric_string_then_getiparam_coerces_to_int() {
with_exec(|| {
unsetparam("zshrs_rt_x3");
setsparam("zshrs_rt_x3", "42");
assert_eq!(getiparam("zshrs_rt_x3"), 42);
unsetparam("zshrs_rt_x3");
});
}
#[test]
fn setsparam_non_numeric_then_getiparam_returns_zero() {
with_exec(|| {
unsetparam("zshrs_rt_x4");
setsparam("zshrs_rt_x4", "not a number");
assert_eq!(getiparam("zshrs_rt_x4"), 0);
unsetparam("zshrs_rt_x4");
});
}
#[test]
fn setaparam_then_getaparam_roundtrip_basic() {
with_exec(|| {
unsetparam("zshrs_rt_a1");
setaparam(
"zshrs_rt_a1",
vec!["a".into(), "b".into(), "c".into()],
);
assert_eq!(
getaparam("zshrs_rt_a1"),
Some(vec!["a".into(), "b".into(), "c".into()])
);
unsetparam("zshrs_rt_a1");
});
}
#[test]
fn setaparam_empty_array_roundtrips_as_empty_vec() {
with_exec(|| {
unsetparam("zshrs_rt_a2");
setaparam("zshrs_rt_a2", vec![]);
assert_eq!(getaparam("zshrs_rt_a2"), Some(vec![]));
unsetparam("zshrs_rt_a2");
});
}
#[test]
fn setaparam_element_with_space_stays_one_element() {
with_exec(|| {
unsetparam("zshrs_rt_a3");
setaparam(
"zshrs_rt_a3",
vec!["hi there".into(), "world".into()],
);
assert_eq!(
getaparam("zshrs_rt_a3"),
Some(vec!["hi there".into(), "world".into()])
);
unsetparam("zshrs_rt_a3");
});
}
#[test]
fn unsetparam_on_never_set_param_is_noop() {
let _g = crate::test_util::global_state_lock();
unsetparam("zshrs_rt_never_existed");
}
#[test]
fn unsetparam_then_getsparam_returns_none() {
with_exec(|| {
setsparam("zshrs_rt_u1", "value");
unsetparam("zshrs_rt_u1");
assert_eq!(getsparam("zshrs_rt_u1"), None);
});
}
#[test]
fn unsetparam_twice_is_idempotent() {
with_exec(|| {
setsparam("zshrs_rt_u2", "value");
unsetparam("zshrs_rt_u2");
unsetparam("zshrs_rt_u2"); assert_eq!(getsparam("zshrs_rt_u2"), None);
});
}
#[test]
fn setiparam_then_getaparam_returns_none_not_an_array() {
with_exec(|| {
unsetparam("zshrs_rt_x5");
setiparam("zshrs_rt_x5", 42);
assert_eq!(
getaparam("zshrs_rt_x5"),
None,
"PM_INTEGER is not PM_ARRAY — getaparam must return None"
);
unsetparam("zshrs_rt_x5");
});
}
#[test]
fn getsparam_on_unset_returns_none() {
let _g = crate::test_util::global_state_lock();
unsetparam("zshrs_rt_missing");
assert_eq!(getsparam("zshrs_rt_missing"), None);
}
#[test]
fn getiparam_on_unset_returns_zero() {
let _g = crate::test_util::global_state_lock();
unsetparam("zshrs_rt_missing2");
assert_eq!(getiparam("zshrs_rt_missing2"), 0);
}
#[test]
fn getaparam_on_unset_returns_none() {
let _g = crate::test_util::global_state_lock();
unsetparam("zshrs_rt_missing3");
assert_eq!(getaparam("zshrs_rt_missing3"), None);
}
#[test]
fn params_corpus_scalar_round_trip_simple() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_RT1");
setsparam("ZP_RT1", "bar");
assert_eq!(getsparam("ZP_RT1").as_deref(), Some("bar"));
unsetparam("ZP_RT1");
}
#[test]
fn params_corpus_empty_scalar_is_set_not_unset() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_EMP");
setsparam("ZP_EMP", "");
assert_eq!(getsparam("ZP_EMP").as_deref(), Some(""), "empty-set is set");
unsetparam("ZP_EMP");
}
#[test]
fn params_corpus_unset_after_set_returns_none() {
let _g = crate::test_util::global_state_lock();
setsparam("ZP_UR", "v");
unsetparam("ZP_UR");
assert_eq!(getsparam("ZP_UR"), None, "unsetparam removes param");
}
#[test]
fn params_corpus_integer_round_trip() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_I");
setiparam("ZP_I", 42);
assert_eq!(getiparam("ZP_I"), 42);
unsetparam("ZP_I");
}
#[test]
fn params_corpus_integer_read_as_string() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_IS");
setiparam("ZP_IS", 42);
assert_eq!(getsparam("ZP_IS").as_deref(), Some("42"),
"integer param reads as string repr");
unsetparam("ZP_IS");
}
#[test]
fn params_corpus_array_round_trip() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_A");
setaparam("ZP_A", vec!["one".into(), "two".into(), "three".into()]);
assert_eq!(
getaparam("ZP_A").as_deref(),
Some(&["one".into(), "two".into(), "three".into()][..]),
"array round-trip",
);
unsetparam("ZP_A");
}
#[test]
fn params_corpus_empty_array_is_set_not_unset() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_EA");
setaparam("ZP_EA", vec![]);
let got = getaparam("ZP_EA");
assert!(got.is_some(), "empty array is set, not None");
assert_eq!(got.unwrap().len(), 0, "len = 0");
unsetparam("ZP_EA");
}
#[test]
fn params_corpus_scalar_overwrite() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_O");
setsparam("ZP_O", "first");
setsparam("ZP_O", "second");
assert_eq!(getsparam("ZP_O").as_deref(), Some("second"),
"scalar overwrites cleanly");
unsetparam("ZP_O");
}
#[test]
fn params_corpus_scalar_to_array_replace_type() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_ST");
setsparam("ZP_ST", "scalar_val");
setaparam("ZP_ST", vec!["a".into(), "b".into()]);
assert_eq!(
getaparam("ZP_ST").as_deref(),
Some(&["a".into(), "b".into()][..]),
"setaparam replaces scalar type",
);
unsetparam("ZP_ST");
}
#[test]
fn params_corpus_unset_on_missing_is_noop() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_NEVER_EXISTED_42_xyz");
assert_eq!(getsparam("ZP_NEVER_EXISTED_42_xyz"), None);
}
#[test]
fn params_corpus_integer_zero_is_set() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_IZ");
setiparam("ZP_IZ", 0);
assert_eq!(getiparam("ZP_IZ"), 0);
assert_eq!(getsparam("ZP_IZ").as_deref(), Some("0"));
unsetparam("ZP_IZ");
}
#[test]
fn params_corpus_integer_negative_preserves_sign() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_IN");
setiparam("ZP_IN", -42);
assert_eq!(getiparam("ZP_IN"), -42);
assert_eq!(getsparam("ZP_IN").as_deref(), Some("-42"));
unsetparam("ZP_IN");
}
#[test]
#[ignore = "ZSHRS BUG: sethparam/gethparam round-trip drops or reshapes hash elements"]
fn params_corpus_hash_round_trip_basic() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_H");
sethparam("ZP_H", vec![
"key1".into(), "val1".into(),
"key2".into(), "val2".into(),
]);
let got = gethparam("ZP_H");
assert!(got.is_some(), "hash param set");
let g = got.unwrap();
assert_eq!(g.len(), 4, "4 elements (2 k/v pairs) preserved");
unsetparam("ZP_H");
}
#[test]
fn params_corpus_hash_missing_returns_none() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_HM_xyz");
assert!(gethparam("ZP_HM_xyz").is_none());
}
#[test]
fn params_corpus_hash_keys_only_returns_keys() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_HK");
sethparam("ZP_HK", vec!["a".into(), "1".into(), "b".into(), "2".into()]);
let keys = gethkparam("ZP_HK");
assert!(keys.is_some(), "keys-only view exists");
let k = keys.unwrap();
assert_eq!(k.len(), 2, "2 keys");
let mut sorted = k.clone();
sorted.sort();
assert_eq!(sorted, vec!["a".to_string(), "b".to_string()]);
unsetparam("ZP_HK");
}
#[test]
#[ignore = "ZSHRS BUG: sethparam round-trip differs from expectation when clearing"]
fn params_corpus_hash_set_empty_clears_entries() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_HC");
sethparam("ZP_HC", vec!["a".into(), "1".into()]);
assert_eq!(gethparam("ZP_HC").map(|v| v.len()), Some(2));
sethparam("ZP_HC", vec![]);
let after = gethparam("ZP_HC");
if let Some(v) = after {
assert!(v.is_empty(), "empty hash has 0 elements");
}
unsetparam("ZP_HC");
}
#[test]
fn params_corpus_integer_max_round_trips() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_IMX");
setiparam("ZP_IMX", i64::MAX);
assert_eq!(getiparam("ZP_IMX"), i64::MAX);
unsetparam("ZP_IMX");
}
#[test]
fn params_corpus_integer_min_round_trips() {
let _g = crate::test_util::global_state_lock();
unsetparam("ZP_IMN");
setiparam("ZP_IMN", i64::MIN);
assert_eq!(getiparam("ZP_IMN"), i64::MIN);
unsetparam("ZP_IMN");
}
}