#[allow(unused_imports)]
use crate::ported::utils::zerr;
use crate::func_body_fmt::FuncBodyFmt;
use indexmap::IndexMap;
use std::collections::{HashMap, HashSet};
use std::env;
use std::sync::atomic::{AtomicI64, Ordering};
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use crate::ported::zsh_h::{
gsu_array, gsu_float, gsu_hash, gsu_integer, gsu_scalar
};
use crate::ported::zsh_h::VALFLAG_SUBST;
use crate::ported::zsh_h::{Param, hashnode, isset as isset_opt};
use fusevm::Value;
use crate::ported::zsh_h::Marker;
use crate::ported::zsh_h::{SCANPM_ISVAR_AT, SCANPM_NONAMEREF};
use crate::ported::zsh_h::SCANPM_WANTINDEX;
#[allow(unused_imports)]
use crate::ported::zsh_h::{
PM_TYPE, PM_SCALAR, PM_NAMEREF, PM_INTEGER, PM_EFLOAT, PM_FFLOAT,
PM_ARRAY, PM_HASHED, PM_HASHELEM, PM_NAMEDDIR, PM_UNIQUE,
PM_READONLY, PM_UNSET, PM_EXPORTED, PM_AUTOLOAD, PM_DEFAULTED,
PM_DECLARED, PM_REMOVABLE, PM_NORESTORE, PM_LOCAL, PM_RO_BY_DESIGN,
PM_LEFT, PM_RIGHT_B, PM_RIGHT_Z, PM_SPECIAL, PM_TAGGED, PM_TIED, PM_UPPER,
SCANPM_CHECKING, SCANPM_MATCHMANY, SCANPM_MATCHKEY, SCANPM_MATCHVAL,
SCANPM_KEYMATCH, SCANPM_WANTKEYS, SCANPM_WANTVALS, SCANPM_ARRONLY,
VALFLAG_EMPTY, VALFLAG_INV,
ASSPM_WARN, ASSPM_AUGMENT, ASSPM_ENV_IMPORT,
PRINT_NAMEONLY, PRINT_TYPESET, PRINT_INCLUDEVALUE, PRINT_KV_PAIR,
PRINT_LINE, PRINT_POSIX_READONLY, PRINT_POSIX_EXPORT,
EXECOPT, KSHARRAYS, AUTONAMEDIRS, ALLEXPORT,
WARNCREATEGLOBAL, WARNNESTEDVAR,
isset, unset,
};
#[allow(unused_imports)]
use crate::ported::math::{mnumber, MN_INTEGER, MN_FLOAT};
#[allow(unused_imports)]
use crate::ported::utils::errflag;
#[allow(unused_imports)]
use crate::ported::signals::{queue_signals, unqueue_signals};
pub static LC_UPDATE_NEEDED: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub static FOUNDPARAM: std::sync::OnceLock<std::sync::Mutex<Option<String>>> =
std::sync::OnceLock::new();
fn foundparam_lock() -> &'static std::sync::Mutex<Option<String>> {
FOUNDPARAM.get_or_init(|| std::sync::Mutex::new(None))
}
pub fn foundparam() -> Option<String> {
foundparam_lock().lock().unwrap().clone()
}
pub fn set_foundparam(nam: Option<String>) {
*foundparam_lock().lock().unwrap() = nam;
}
#[allow(non_upper_case_globals)]
pub static locallevel: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub use crate::ported::zsh_h::param;
#[inline] #[allow(non_snake_case)]
pub fn IPDEF1(A: &str, B: usize, C: i32) -> paramdef { paramdef {
name: A.to_string(),
flags: (PM_INTEGER | PM_SPECIAL) as i32 | C,
var: 0, gsu: B,
getnfn: None, scantfn: None, pm: None,
}
}
#[inline] #[allow(non_snake_case)]
pub fn IPDEF2(A: &str, B: usize, C: i32) -> paramdef { paramdef {
name: A.to_string(),
flags: (PM_SCALAR | PM_SPECIAL) as i32 | C,
var: 0, gsu: B,
getnfn: None, scantfn: None, pm: None,
}
}
#[inline] #[allow(non_snake_case)]
pub fn IPDEF4(A: &str, B: usize) -> paramdef { paramdef {
name: A.to_string(),
flags: (PM_INTEGER | PM_READONLY_SPECIAL) as i32,
var: B, gsu: 0,
getnfn: None, scantfn: None, pm: None,
}
}
#[inline] #[allow(non_snake_case)]
pub fn IPDEF5(A: &str, B: usize, F: usize) -> paramdef { paramdef {
name: A.to_string(),
flags: (PM_INTEGER | PM_SPECIAL) as i32,
var: B, gsu: F,
getnfn: None, scantfn: None, pm: None,
}
}
#[inline] #[allow(non_snake_case)]
pub fn IPDEF5U(A: &str, B: usize, F: usize) -> paramdef { paramdef {
name: A.to_string(),
flags: (PM_INTEGER | PM_SPECIAL | PM_UNSET) as i32,
var: B, gsu: F,
getnfn: None, scantfn: None, pm: None,
}
}
#[inline] #[allow(non_snake_case)]
pub fn IPDEF6(A: &str, B: usize, F: usize) -> paramdef { paramdef {
name: A.to_string(),
flags: (PM_INTEGER | PM_SPECIAL | PM_DONTIMPORT) as i32,
var: B, gsu: F,
getnfn: None, scantfn: None, pm: None,
}
}
#[inline] #[allow(non_snake_case)]
pub fn IPDEF7(A: &str, B: usize) -> paramdef { paramdef {
name: A.to_string(),
flags: (PM_SCALAR | PM_SPECIAL) as i32,
var: B, gsu: 0,
getnfn: None, scantfn: None, pm: None,
}
}
#[inline] #[allow(non_snake_case)]
pub fn IPDEF7R(A: &str, B: usize) -> paramdef { paramdef {
name: A.to_string(),
flags: (PM_SCALAR | PM_SPECIAL | PM_DONTIMPORT_SUID) as i32,
var: B, gsu: 0,
getnfn: None, scantfn: None, pm: None,
}
}
#[inline] #[allow(non_snake_case)]
pub fn IPDEF7U(A: &str, B: usize) -> paramdef { paramdef {
name: A.to_string(),
flags: (PM_SCALAR | PM_SPECIAL | PM_UNSET) as i32,
var: B, gsu: 0,
getnfn: None, scantfn: None, pm: None,
}
}
#[inline] #[allow(non_snake_case)]
pub fn IPDEF8(A: &str, B: usize, C: usize, D: i32) -> paramdef { paramdef {
name: A.to_string(),
flags: (PM_SCALAR | PM_SPECIAL) as i32 | D,
var: B, gsu: 0,
getnfn: None, scantfn: None, pm: None,
}
}
#[inline] #[allow(non_snake_case)]
pub fn IPDEF9(A: &str, B: usize, C: usize, D: i32) -> paramdef { paramdef {
name: A.to_string(),
flags: (PM_ARRAY | PM_SPECIAL | PM_DONTIMPORT) as i32 | D,
var: B, gsu: 0,
getnfn: None, scantfn: None, pm: None,
}
}
#[inline] #[allow(non_snake_case)]
pub fn IPDEF10(A: &str, B: usize) -> paramdef { paramdef {
name: A.to_string(),
flags: (PM_ARRAY | PM_SPECIAL) as i32,
var: 0, gsu: B,
getnfn: None, scantfn: None, pm: None,
}
}
#[inline] #[allow(non_snake_case)]
pub fn LCIPDEF(name: &str) -> paramdef { IPDEF2(name, 0, PM_UNSET as i32) }
#[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: crate::ported::zsh_h::PM_INTEGER,
pm_flags: crate::ported::zsh_h::PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "?",
pm_type: crate::ported::zsh_h::PM_INTEGER,
pm_flags: crate::ported::zsh_h::PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "HISTCMD",
pm_type: crate::ported::zsh_h::PM_INTEGER,
pm_flags: crate::ported::zsh_h::PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "LINENO",
pm_type: crate::ported::zsh_h::PM_INTEGER,
pm_flags: crate::ported::zsh_h::PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "PPID",
pm_type: crate::ported::zsh_h::PM_INTEGER,
pm_flags: crate::ported::zsh_h::PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "ZSH_SUBSHELL",
pm_type: crate::ported::zsh_h::PM_INTEGER,
pm_flags: crate::ported::zsh_h::PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "COLUMNS",
pm_type: crate::ported::zsh_h::PM_INTEGER,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "LINES",
pm_type: crate::ported::zsh_h::PM_INTEGER,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "ZLE_RPROMPT_INDENT",
pm_type: crate::ported::zsh_h::PM_INTEGER,
pm_flags: crate::ported::zsh_h::PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "SHLVL",
pm_type: crate::ported::zsh_h::PM_INTEGER,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "FUNCNEST",
pm_type: crate::ported::zsh_h::PM_INTEGER,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "OPTIND",
pm_type: crate::ported::zsh_h::PM_INTEGER,
pm_flags: crate::ported::zsh_h::PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "TRY_BLOCK_ERROR",
pm_type: crate::ported::zsh_h::PM_INTEGER,
pm_flags: crate::ported::zsh_h::PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "TRY_BLOCK_INTERRUPT",
pm_type: crate::ported::zsh_h::PM_INTEGER,
pm_flags: crate::ported::zsh_h::PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "OPTARG",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "NULLCMD",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "POSTEDIT",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "READNULLCMD",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "PS1",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "RPS1",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "RPROMPT",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "PS2",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "RPS2",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "RPROMPT2",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "PS3",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "PS4",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_DONTIMPORT_SUID,
tied_name: None,
},
special_paramdef {
name: "SPROMPT",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "*",
pm_type: crate::ported::zsh_h::PM_ARRAY,
pm_flags: crate::ported::zsh_h::PM_READONLY | crate::ported::zsh_h::PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "@",
pm_type: crate::ported::zsh_h::PM_ARRAY,
pm_flags: crate::ported::zsh_h::PM_READONLY | crate::ported::zsh_h::PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "CDPATH",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_TIED,
tied_name: Some("cdpath"),
},
special_paramdef {
name: "FIGNORE",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_TIED,
tied_name: Some("fignore"),
},
special_paramdef {
name: "FPATH",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_TIED,
tied_name: Some("fpath"),
},
special_paramdef {
name: "MAILPATH",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_TIED,
tied_name: Some("mailpath"),
},
special_paramdef {
name: "PATH",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_TIED,
tied_name: Some("path"),
},
special_paramdef {
name: "PSVAR",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_TIED,
tied_name: Some("psvar"),
},
special_paramdef {
name: "ZSH_EVAL_CONTEXT",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_READONLY | crate::ported::zsh_h::PM_TIED,
tied_name: Some("zsh_eval_context"),
},
special_paramdef {
name: "MODULE_PATH",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_DONTIMPORT | crate::ported::zsh_h::PM_TIED,
tied_name: Some("module_path"),
},
special_paramdef {
name: "MANPATH",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_TIED,
tied_name: Some("manpath"),
},
special_paramdef {
name: "LANG",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "LC_ALL",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "LC_COLLATE",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "LC_CTYPE",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "LC_MESSAGES",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "LC_NUMERIC",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "LC_TIME",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_UNSET,
tied_name: None,
},
special_paramdef {
name: "ARGC",
pm_type: crate::ported::zsh_h::PM_INTEGER,
pm_flags: crate::ported::zsh_h::PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "HISTCHARS",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_DONTIMPORT,
tied_name: None,
},
special_paramdef {
name: "status",
pm_type: crate::ported::zsh_h::PM_INTEGER,
pm_flags: crate::ported::zsh_h::PM_READONLY,
tied_name: None,
},
special_paramdef {
name: "prompt",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "PROMPT",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "PROMPT2",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "PROMPT3",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "PROMPT4",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "argv",
pm_type: crate::ported::zsh_h::PM_ARRAY,
pm_flags: 0,
tied_name: None,
},
special_paramdef {
name: "pipestatus",
pm_type: crate::ported::zsh_h::PM_ARRAY,
pm_flags: 0,
tied_name: None,
},
];
pub const special_params_sh: &[special_paramdef] = &[
special_paramdef { name: "CDPATH",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef { name: "FIGNORE",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef { name: "FPATH",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef { name: "MAILPATH",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef { name: "PATH",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef { name: "PSVAR",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: 0,
tied_name: None,
},
special_paramdef { name: "ZSH_EVAL_CONTEXT",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_READONLY,
tied_name: None,
},
special_paramdef { name: "MODULE_PATH",
pm_type: crate::ported::zsh_h::PM_SCALAR,
pm_flags: crate::ported::zsh_h::PM_DONTIMPORT,
tied_name: None,
},
];
pub fn getintvalue(v: Option<&mut crate::ported::zsh_h::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();
strgetfn(pm).parse::<i64>().unwrap_or(0)
}
pub fn getstrvalue(v: Option<&mut crate::ported::zsh_h::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 { intgetfn(pm).to_string()
} else if t == PM_EFLOAT || t == PM_FFLOAT { floatgetfn(pm).to_string()
} else if t == PM_SCALAR || t == PM_NAMEREF { strgetfn(pm)
} else {
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 =
crate::ported::options::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 getsparam_u(v: Option<&mut crate::ported::zsh_h::value>) -> Option<String> {
let v = v?;
let pm = v.pm.as_ref()?;
if PM_TYPE(pm.node.flags as u32) != PM_SCALAR {
return None;
}
Some(getstrvalue(Some(v)))
}
pub fn getaparam(s: Option<&mut crate::ported::zsh_h::value>) -> Option<Vec<String>> {
let s = s?;
let pm = s.pm.as_mut()?;
if PM_TYPE(pm.node.flags as u32) != PM_ARRAY {
return None;
}
Some(arrgetfn(pm))
}
pub fn gethparam(s: Option<&mut crate::ported::zsh_h::value>) -> Option<Vec<String>> {
let s = s?;
let pm = s.pm.as_mut()?;
if PM_TYPE(pm.node.flags as u32) != PM_HASHED {
return None;
}
let _ = hashgetfn(pm);
Some(Vec::new())
}
pub fn gethkparam(s: Option<&mut crate::ported::zsh_h::value>) -> Option<Vec<String>> {
let s = s?;
let pm = s.pm.as_mut()?;
if PM_TYPE(pm.node.flags as u32) != PM_HASHED {
return None;
}
let _ = hashgetfn(pm);
Some(Vec::new())
}
pub fn getnumvalue(v: Option<&mut crate::ported::zsh_h::value>) -> crate::ported::math::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);
if let Ok(i) = s.parse::<i64>() { return mnumber { l: i, d: 0.0, type_: MN_INTEGER }; }
if let Ok(f) = s.parse::<f64>() { return mnumber { l: 0, d: f, type_: MN_FLOAT }; }
mnumber { l: 0, d: 0.0, type_: MN_INTEGER }
}
pub fn setstrvalue(v: Option<&mut crate::ported::zsh_h::value>, val: &str) {
assignstrvalue(v, Some(val.to_string()), 0);
}
pub fn assigniparam(vbuf: &str, t: i64) {
assignnparam(vbuf, crate::ported::math::mnumber { l: t, d: 0.0, type_: MN_INTEGER }, crate::ported::zsh_h::ASSPM_WARN);
}
pub fn setaparam(name: &str, val: Vec<String>) -> Option<crate::ported::zsh_h::Param>
{
assignaparam(name, val, crate::ported::zsh_h::ASSPM_WARN)
}
pub fn assignsparam(s: &str, val: &str, flags: i32) -> Option<crate::ported::zsh_h::Param>
{
if !isident(s) {
zerr(&format!("not an identifier: {}", s)); errflag.fetch_or( crate::ported::utils::ERRFLAG_ERROR,
std::sync::atomic::Ordering::Relaxed,
);
return None; }
crate::ported::signals::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 {
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);
crate::ported::signals::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::IndexMap<String, String> = indexmap::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);
crate::ported::signals::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_opt(ALLEXPORT) { pm_flags |= PM_EXPORTED as i32;
}
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: 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);
crate::ported::signals::unqueue_signals(); return None; }
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(crate::ported::zsh_h::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( crate::ported::utils::ERRFLAG_ERROR,
std::sync::atomic::Ordering::Relaxed,
);
crate::ported::signals::unqueue_signals(); return None; }
}
let pm = tab.get_mut(name).unwrap();
pm.node.flags &= !(PM_DEFAULTED as i32);
pm.u_str = Some(val.to_string());
let cloned = pm.clone();
drop(tab);
crate::ported::signals::unqueue_signals(); Some(cloned) }
static PARAMTAB_HASHED_STORAGE_INNER: OnceLock<
Mutex<HashMap<String, indexmap::IndexMap<String, String>>>,
> = OnceLock::new();
pub(crate) fn paramtab_hashed_storage()
-> &'static Mutex<HashMap<String, indexmap::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::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 assignaparam(
name: &str,
val: Vec<String>,
flags: i32,
) -> Option<crate::ported::zsh_h::Param> { if !isident(name) {
crate::ported::utils::zerr(&format!("not an identifier: {}", name));
return None;
}
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 {
createparam(name, PM_ARRAY as i32)?;
}
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); }
}
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 };
pm.u_arr = Some(val_final.clone());
pm.u_str = None;
pm.u_hash = None;
let cloned = pm.clone();
drop(tab);
let _ = val_final;
Some(cloned)
}
pub fn sethparam(name: &str, val: Vec<String>) -> Option<crate::ported::zsh_h::Param>
{
if !isident(name) {
crate::ported::utils::zerr(&format!("not an identifier: {}", name));
return None;
}
if name.contains('[') {
crate::ported::utils::zerr("nested associative arrays not yet supported");
return None;
}
let exists = paramtab().read().unwrap().contains_key(name);
if !exists {
createparam(name, PM_HASHED as i32)?;
}
let mut map: indexmap::IndexMap<String, String> = indexmap::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)
}
#[allow(unused_variables)]
pub fn unsetparam_pm(pm: &mut crate::ported::zsh_h::param, altflag: i32, exp: i32) -> i32 {
if (pm.node.flags as u32 & PM_READONLY) != 0 && pm.level <= 0 {
let _kind = if (pm.node.flags as u32 & PM_NAMEREF) != 0 {
"reference"
} else {
"variable"
};
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 shempty() {}
pub fn setsparam(s: &str, val: &str) -> Option<crate::ported::zsh_h::Param>
{
assignsparam(s, val, ASSPM_WARN as i32) }
pub fn setiparam(s: &str, val: i64) -> Option<crate::ported::zsh_h::Param>
{
assignsparam(s, &val.to_string(), ASSPM_WARN as i32)
}
pub fn setiparam_no_convert(s: &str, val: i64) -> Option<crate::ported::zsh_h::Param>
{
assignsparam(s, &val.to_string(), ASSPM_WARN as i32)
}
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) {
if let Some(s) = pm.u_str.as_ref() {
return Some(s.clone());
}
if let Some(arr) = pm.u_arr.as_ref() {
return Some(arr.join(" "));
}
}
}
std::env::var(name).ok()
}
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 & crate::ported::zsh_h::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 & (crate::ported::zsh_h::PM_EFLOAT
| crate::ported::zsh_h::PM_FFLOAT)) != 0
{
return (pm.u_dval as i64, pm.u_dval, true);
}
if (fl & crate::ported::zsh_h::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 resetparam(pm: &mut crate::ported::zsh_h::param, flags: i32) -> i32 { let s = pm.node.nam.clone(); crate::ported::signals::queue_signals(); unsetparam_pm(pm, 0, 1); crate::ported::signals::unqueue_signals(); let _ = createparam(&s, flags); 0 }
pub fn unsetparam( variables: &mut std::collections::HashMap<String, String>,
arrays: &mut std::collections::HashMap<String, Vec<String>>,
assoc_arrays: &mut std::collections::HashMap<String, indexmap::IndexMap<String, String>>,
name: &str,
) {
variables.remove(name);
arrays.remove(name);
assoc_arrays.remove(name);
}
pub fn export_param(pm: &mut crate::ported::zsh_h::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 {
format!("{}", intgetfn(pm))
} else if (pm.node.flags as u32 & (PM_EFLOAT | PM_FFLOAT)) != 0 {
format!("{}", floatgetfn(pm))
} else {
strgetfn(pm)
};
addenv(&pm.node.nam, &val);
pm.env = Some(val);
}
pub fn startparamscope(_table: &mut crate::ported::zsh_h::HashTable) {
crate::ported::utils::inc_locallevel();
}
pub fn endparamscope() {
queue_signals();
crate::ported::utils::dec_locallevel(); crate::ported::hist::saveandpophiststack(crate::ported::zsh_h::HFILE_USE_OPTIONS as i32);
let ll = crate::ported::utils::locallevel();
if let Ok(mut tab) = paramtab().write() {
let stale: Vec<String> = tab.iter()
.filter_map(|(k, pm)| if pm.level > ll { Some(k.clone()) } else { None })
.collect();
for n in stale {
if let Some(pm) = tab.remove(&n) {
if let Some(prev) = pm.old { tab.insert(n, prev); }
}
}
}
unqueue_signals();
}
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 == '[' {
return true;
}
if !c.is_alphanumeric() && c != '_' && c != '.' {
return false;
}
}
true
}
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 colonsplit(s: &str) -> Vec<String> {
s.split(':')
.filter(|s| !s.is_empty())
.map(String::from)
.collect()
}
pub fn colonarrgetfn(arr: &[String]) -> String {
arr.join(":")
}
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 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 setarrvalue(v: &mut crate::ported::zsh_h::value, val: Vec<String>) {
let pm = match v.pm.as_mut() { Some(p) => p, None => return };
if pm.node.flags & PM_READONLY as i32 != 0 {
crate::ported::utils::zerr(&format!("read-only variable: {}", pm.node.nam));
return;
}
let t = PM_TYPE(pm.node.flags as u32);
if t & (crate::ported::zsh_h::PM_ARRAY | PM_HASHED) == 0 {
crate::ported::utils::zerr(&format!(
"{}: attempt to assign array value to non-array",
pm.node.nam
));
return;
}
if v.valflags & VALFLAG_EMPTY != 0 {
crate::ported::utils::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, crate::ported::zsh_h::ASSPM_AUGMENT);
return;
}
if t == PM_HASHED {
crate::ported::utils::zerr(&format!(
"{}: attempt to set slice of associative array",
pm.node.nam
));
return;
}
if v.valflags & VALFLAG_INV != 0
&& !isset(crate::ported::zsh_h::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());
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);
}
}
}
}
pub fn convbase(val: i64, base: u32) -> String {
if base == 0 || base == 10 {
return val.to_string();
}
let negative = val < 0;
let mut v = if negative { (-val) as u64 } else { val as u64 };
if v == 0 {
return match base {
16 => "0x0".to_string(),
8 => "00".to_string(),
_ => format!("{}#0", base),
};
}
let mut digits = Vec::new();
while v > 0 {
let dig = (v % base as u64) as u8;
digits.push(if dig < 10 {
b'0' + dig
} else {
b'A' + dig - 10
});
v /= base as u64;
}
digits.reverse();
let prefix = match base {
16 => "0x",
8 => "0",
10 => "",
_ => "",
};
let base_prefix = if base != 10 && base != 16 && base != 8 {
format!("{}#", base)
} else {
prefix.to_string()
};
let sign = if negative { "-" } else { "" };
format!(
"{}{}{}",
sign,
base_prefix,
String::from_utf8_lossy(&digits)
)
}
pub fn convbase_underscore(val: i64, base: u32, underscore: i32) -> String {
let s = convbase(val, base);
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 & crate::ported::zsh_h::PM_EFLOAT) != 0 { let d = if digits <= 0 { 10 } else { digits }; ('e', (d - 1).max(0)) } else if (pm_flags & crate::ported::zsh_h::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 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
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_colonarr_conversion() {
let arr = colonsplit("/bin:/usr/bin:/usr/local/bin");
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() {
assert!(isident("foo"));
assert!(isident("_bar"));
assert!(isident("FOO_BAR"));
assert!(isident("x123"));
assert!(isident("123")); assert!(!isident(""));
assert!(!isident("foo bar"));
}
#[test]
fn test_unique_array() {
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() {
assert_eq!(convbase(255, 16), "0xFF");
assert_eq!(convbase(10, 10), "10");
assert_eq!(convbase(-5, 10), "-5");
assert_eq!(convbase(7, 8), "07");
assert_eq!(convbase(5, 2), "2#101");
}
#[test]
fn test_convfloat() {
let s = convfloat(2.5, 2, crate::ported::zsh_h::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 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() {
use crate::ported::zsh_h::{hashnode, param, PM_ARRAY};
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 = crate::ported::zsh_h::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"]);
}
#[test]
fn test_valid_refname() {
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 empty: Vec<String> = Vec::new();
assert!(uniqarray(empty).is_empty());
}
#[test]
fn test_convbase_underscore() {
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 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 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 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 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 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 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 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 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 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 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 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 mut h: indexmap::IndexMap<String, String> = indexmap::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 mut h: indexmap::IndexMap<String, String> = indexmap::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 mut h: indexmap::IndexMap<String, String> = indexmap::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 mut h: indexmap::IndexMap<String, String> = indexmap::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 mut h: indexmap::IndexMap<String, String> = indexmap::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 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 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 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 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 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 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 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 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 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 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 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 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 out = getarg("(en.2.r)b", None, None, Some("abab")).expect("Some");
assert_eq!(val_str(out), "b");
}
}
#[allow(non_camel_case_types)]
pub enum getarg_out<'a> {
Flags { flags: &'a str, rest: &'a str },
Value(fusevm::Value),
}
pub(crate) fn getarg<'a>(
idx: &'a str,
arr: Option<&[String]>,
assoc: Option<&indexmap::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 {
crate::ported::pattern::patmatch(pat, 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 {
crate::ported::pattern::patmatch(pat_used, 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 {
crate::ported::pattern::patmatch(pat, &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 })
}
use std::sync::{Arc, Mutex, OnceLock, RwLock};
use std::time::Duration;
use crate::config_h::DEFAULT_TMPPREFIX;
use crate::zsh_h::{paramdef, ERRFLAG_ERROR, PM_DONTIMPORT, PM_DONTIMPORT_SUID, PM_READONLY_SPECIAL};
static PARAMTAB_INNER: OnceLock<RwLock<HashMap<String, crate::ported::zsh_h::Param>>> =
OnceLock::new();
static REALPARAMTAB_INNER: OnceLock<RwLock<HashMap<String, crate::ported::zsh_h::Param>>> =
OnceLock::new();
pub fn paramtab() -> &'static RwLock<HashMap<String, crate::ported::zsh_h::Param>> {
PARAMTAB_INNER.get_or_init(|| RwLock::new(HashMap::new()))
}
pub fn realparamtab() -> &'static RwLock<HashMap<String, crate::ported::zsh_h::Param>> {
REALPARAMTAB_INNER.get_or_init(|| RwLock::new(HashMap::new()))
}
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()))
}
fn initial_username() -> String {
#[cfg(unix)]
{
let uid = unsafe { libc::getuid() };
let mut pwd: libc::passwd = unsafe { std::mem::zeroed() };
let mut buf = vec![0i8; 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()))
}
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>> {
&crate::ported::builtin::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 uidgetfn() -> i64 {
unsafe { libc::getuid() as i64 }
}
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 errnogetfn() -> i64 {
std::io::Error::last_os_error().raw_os_error().unwrap_or(0) as i64
}
pub fn errnosetfn(x: i64) {
extern "C" {
#[cfg(target_os = "macos")]
fn __error() -> *mut libc::c_int;
#[cfg(target_os = "linux")]
fn __errno_location() -> *mut libc::c_int;
}
let truncated = x as i32;
unsafe {
#[cfg(target_os = "macos")]
{
*__error() = truncated;
}
#[cfg(target_os = "linux")]
{
*__errno_location() = truncated;
}
}
if truncated as i64 != x {
zerr("errno truncated on assignment");
}
}
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 ttyidlegetfn() -> i64 {
if unsafe { libc::isatty(0) } == 0 {
return -1;
}
let mut st: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(0, &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 intsecondsgetfn() -> i64 {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
let timer = *shtimer_lock().lock().expect("shtimer poisoned");
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;
now_sec - timer_sec - i64::from(now_nsec < timer_nsec)
}
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 {
zerr("SECONDS truncated on assignment");
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 crate::ported::zsh_h::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 argzerogetfn() -> String {
crate::ported::utils::argzero().unwrap_or_default()
}
pub fn argzerosetfn(x: String) { if !x.is_empty() {
if isset(crate::ported::zsh_h::POSIXARGZERO) {
crate::ported::utils::zerr("read-only variable: 0"); } else {
crate::ported::utils::set_argzero(Some(crate::ported::utils::ztrdup(&x)));
}
}
}
pub fn poundgetfn() -> i64 {
pparams_lock().lock().expect("pparams poisoned").len() as i64
}
pub fn usernamegetfn() -> String {
cached_username_lock()
.lock()
.expect("username poisoned")
.clone()
}
pub fn usernamesetfn(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::geteuid() as libc::uid_t;
if (*pwd).pw_uid != cached_uid { let _ = libc::initgroups(cstr.as_ptr(), (*pwd).pw_gid as _);
if libc::setgid((*pwd).pw_gid) != 0 { crate::ported::utils::zwarn(&format!(
"failed to change group ID: {}",
std::io::Error::last_os_error()
));
} else if libc::setuid((*pwd).pw_uid) != 0 { crate::ported::utils::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") =
crate::ported::utils::ztrdup_metafy(&name_str);
}
}
}
}
}
drop(x);
}
pub fn ifsgetfn() -> String {
ifs_lock().lock().expect("ifs poisoned").clone()
}
pub fn ifssetfn(x: String) {
*ifs_lock().lock().expect("ifs poisoned") = x;
}
pub fn homegetfn() -> String {
home_lock().lock().expect("home poisoned").clone()
}
pub fn homesetfn(x: String) {
*home_lock().lock().expect("home poisoned") = x;
}
pub fn termgetfn() -> String {
term_lock().lock().expect("term poisoned").clone()
}
pub fn termsetfn(x: String) {
*term_lock().lock().expect("term poisoned") = x;
term_reinit_from_pm();
}
pub fn terminfogetfn() -> String {
zsh_terminfo_lock()
.lock()
.expect("zsh_terminfo poisoned")
.clone()
}
pub fn terminfosetfn(x: String) {
*zsh_terminfo_lock()
.lock()
.expect("zsh_terminfo poisoned") = x.clone();
env::set_var("TERMINFO", &x);
term_reinit_from_pm();
}
pub fn terminfodirsgetfn() -> String {
zsh_terminfodirs_lock()
.lock()
.expect("zsh_terminfodirs poisoned")
.clone()
}
pub fn terminfodirssetfn(x: String) {
*zsh_terminfodirs_lock()
.lock()
.expect("zsh_terminfodirs poisoned") = x.clone();
env::set_var("TERMINFO_DIRS", &x);
term_reinit_from_pm();
}
pub fn term_reinit_from_pm() { let interactive = crate::ported::zsh_h::isset(crate::ported::options::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 static TERMFLAGS: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(0);
pub const TERM_UNKNOWN: i32 = 1 << 0;
pub fn wordcharsgetfn() -> String {
wordchars_lock()
.lock()
.expect("wordchars poisoned")
.clone()
}
pub fn wordcharssetfn(x: String) {
*wordchars_lock().lock().expect("wordchars poisoned") = x;
}
pub fn keyboardhackgetfn() -> String {
let c = *keyboardhack_lock()
.lock()
.expect("keyboardhack poisoned");
if c == 0 {
String::new()
} else {
(c as char).to_string()
}
}
pub fn keyboardhacksetfn(x: String) {
let bytes = x.as_bytes();
if bytes.len() > 1 {
zerr("Only one KEYBOARD_HACK character can be defined");
}
let c = bytes.first().copied().unwrap_or(0);
if c >= 0x80 {
zerr("KEYBOARD_HACK can only contain ASCII characters");
return;
}
*keyboardhack_lock().lock().expect("keyboardhack poisoned") = c;
}
pub fn histcharsgetfn() -> String {
let chars = *histchars_lock().lock().expect("histchars poisoned");
let mut s = String::new();
for &b in chars.iter() {
if b != 0 {
s.push(b as char);
}
}
s
}
pub fn histcharssetfn(x: Option<String>) {
match x {
None => {
*histchars_lock().lock().expect("histchars poisoned") = [b'!', b'^', b'#'];
}
Some(s) => {
let bytes = s.as_bytes();
for &b in bytes.iter().take(3) {
if b >= 0x80 {
zerr("HISTCHARS can only contain ASCII characters");
return;
}
}
let mut chars = [0u8; 3];
for (i, &b) in bytes.iter().take(3).enumerate() {
chars[i] = b;
}
*histchars_lock().lock().expect("histchars poisoned") = chars;
}
}
}
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;
}
pub fn underscoregetfn() -> String {
zunderscore_lock()
.lock()
.expect("zunderscore poisoned")
.clone()
}
pub fn histsizegetfn() -> i64 {
*histsiz_lock().lock().expect("histsiz poisoned")
}
pub fn histsizesetfn(v: i64) {
*histsiz_lock().lock().expect("histsiz poisoned") = v.max(1);
}
pub fn savehistsizegetfn() -> i64 {
*savehistsiz_lock().lock().expect("savehistsiz poisoned")
}
pub fn savehistsizesetfn(v: i64) {
*savehistsiz_lock().lock().expect("savehistsiz poisoned") = v.max(0);
}
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 clear_mbstate() {
}
pub fn setlang(x: Option<&str>) {
if let Ok(lc_all) = env::var("LC_ALL") {
if !lc_all.is_empty() {
return;
}
}
if let Some(s) = x {
env::set_var("LANG", s);
}
clear_mbstate();
}
pub fn langsetfn(x: String) {
setlang(Some(&x));
}
pub fn lc_allsetfn(x: Option<String>) {
match x {
None => setlang(env::var("LANG").as_deref().ok()),
Some(s) if s.is_empty() => setlang(env::var("LANG").as_deref().ok()),
Some(s) => {
env::set_var("LC_ALL", &s);
clear_mbstate();
}
}
}
pub fn lcsetfn(pm: &str, x: Option<String>) {
if let Ok(lc_all) = env::var("LC_ALL") {
if !lc_all.is_empty() {
return;
}
}
let val = x
.filter(|s| !s.is_empty())
.or_else(|| env::var("LANG").ok().filter(|s| !s.is_empty()));
if let Some(v) = val {
env::set_var(pm, v);
}
clear_mbstate();
}
pub fn zgetenv(name: &str) -> Option<String> {
env::var(name).ok()
}
pub fn zputenv(str: &str) -> i32 { 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..];
env::set_var(name, value);
0
} else { 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 std::env::vars_os().enumerate() {
if let Some(s) = k.to_str() {
if s == bare {
return Some(i); }
}
}
None }
pub fn delenvvalue(name: &str) { env::remove_var(name); }
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 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 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 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 == crate::ported::zsh_h::META as u8 { ch = match it.next() {
Some(next) => next ^ 32, None => break,
};
}
if flags_u & crate::ported::zsh_h::PM_LOWER != 0 { ch = ch.to_ascii_lowercase(); } else if flags_u & crate::ported::zsh_h::PM_UPPER != 0 { ch = ch.to_ascii_uppercase(); }
buf.push(ch as char);
}
}
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 arrfixenv(s: &str, t: Option<&[String]>) {
if s == "PATH" || s == "path" {
crate::ported::hashtable::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 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(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 zhuniqarray(x: Vec<String>) -> Vec<String> { arrayuniq(x, 0) }
pub fn arrayuniq_freenode() {}
pub fn newuniqtable(size: i64) -> HashSet<String> { HashSet::with_capacity(size.max(0) as usize) }
#[allow(unused_variables)]
pub fn nullintsetfn(pm: &mut crate::ported::zsh_h::param, x: i64) {}
#[allow(unused_variables)]
pub fn nullsethashfn(pm: &mut crate::ported::zsh_h::param, x: crate::ported::zsh_h::HashTable) {
}
#[allow(unused_variables)]
pub fn nullstrsetfn(pm: &mut crate::ported::zsh_h::param, x: String) {}
#[allow(unused_variables)]
pub fn nullunsetfn(pm: &mut crate::ported::zsh_h::param, exp: i32) {}
#[allow(unused_variables)]
pub fn stdunsetfn(pm: &mut crate::ported::zsh_h::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;
}
pub fn rprompt_indent_unsetfn(pm: &mut crate::ported::zsh_h::param, exp: i32) {
stdunsetfn(pm, exp);
*RPROMPT_INDENT.lock().unwrap() = 1;
}
pub static RPROMPT_INDENT: std::sync::Mutex<i32> = std::sync::Mutex::new(1);
pub fn intgetfn(pm: &crate::ported::zsh_h::param) -> i64 {
pm.u_val
}
pub fn intsetfn(pm: &mut crate::ported::zsh_h::param, x: i64) {
pm.u_val = x;
}
pub fn floatgetfn(pm: &crate::ported::zsh_h::param) -> f64 {
pm.u_dval
}
pub fn floatsetfn(pm: &mut crate::ported::zsh_h::param, x: f64) {
pm.u_dval = x;
}
pub fn strgetfn(pm: &crate::ported::zsh_h::param) -> String {
pm.u_str.clone().unwrap_or_default()
}
pub fn strsetfn(pm: &mut crate::ported::zsh_h::param, x: String) {
pm.u_str = Some(x.clone());
if (pm.node.flags as u32 & PM_HASHELEM) == 0 {
if (pm.node.flags as u32 & PM_NAMEDDIR) != 0 {
pm.node.flags |= PM_NAMEDDIR as i32;
crate::ported::utils::adduserdir(&pm.node.nam, &x, 0, false);
}
}
}
pub fn arrgetfn(pm: &crate::ported::zsh_h::param) -> Vec<String> {
pm.u_arr.clone().unwrap_or_default()
}
pub fn arrsetfn(pm: &mut crate::ported::zsh_h::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: &crate::ported::zsh_h::param) -> Option<&crate::ported::zsh_h::HashTable> {
pm.u_hash.as_ref()
}
pub fn hashsetfn(pm: &mut crate::ported::zsh_h::param, x: crate::ported::zsh_h::HashTable) {
pm.u_hash = Some(x);
}
pub fn arrhashsetfn( pm: &mut crate::ported::zsh_h::param,
val: Vec<String>,
_flags: i32,
) {
let alen: usize = val
.iter()
.filter(|s| !s.starts_with(Marker as char))
.count();
if alen % 2 != 0 {
crate::ported::utils::zerr(
"bad set of key/value pairs for associative array",
);
return;
}
pm.u_hash = Some(Box::new(crate::ported::zsh_h::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,
}));
}
pub fn intvargetfn(pm: &crate::ported::zsh_h::param) -> i64 {
pm.u_val
}
pub fn intvarsetfn(pm: &mut crate::ported::zsh_h::param, x: i64) {
pm.u_val = x;
}
pub fn zlevarsetfn(pm: &mut crate::ported::zsh_h::param, x: i64) {
pm.u_val = x;
if pm.node.nam == "LINES" || pm.node.nam == "COLUMNS" {
let _ = crate::ported::utils::adjustwinsize();
}
}
pub fn strvarsetfn(pm: &mut crate::ported::zsh_h::param, x: Option<String>) {
pm.u_str = x;
}
pub fn strvargetfn(pm: &crate::ported::zsh_h::param) -> String {
pm.u_str.clone().unwrap_or_default()
}
pub fn arrvargetfn(pm: &crate::ported::zsh_h::param) -> Vec<String> {
pm.u_arr.clone().unwrap_or_default()
}
pub fn arrvarsetfn(pm: &mut crate::ported::zsh_h::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);
}
pub fn colonarrsetfn(pm: &mut crate::ported::zsh_h::param, x: Option<String>) {
let arr = match x {
Some(s) => colonsplit(&s),
None => Vec::new(),
};
arrvarsetfn(pm, arr);
}
pub fn tiedarrgetfn(pm: &crate::ported::zsh_h::param) -> Vec<String> {
pm.u_arr.clone().unwrap_or_default()
}
pub fn tiedarrsetfn(pm: &mut crate::ported::zsh_h::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 crate::ported::zsh_h::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 assignnparam(
s: &str,
val: crate::ported::math::mnumber,
flags: i32,
) -> Option<Box<crate::ported::zsh_h::param>> {
if !isident(s) {
zerr(&format!("not an identifier: {}", s)); errflag.fetch_or( crate::ported::utils::ERRFLAG_ERROR,
std::sync::atomic::Ordering::Relaxed,
);
return None; }
if unset(EXECOPT) {
return None;
}
let mut vbuf = crate::ported::zsh_h::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;
return None;
}
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 Some(vv) = v {
if let Some(pm) = vv.pm.as_mut() {
pm.node.flags &= !(PM_DEFAULTED as i32);
}
setnumvalue(Some(vv), val);
}
None
}
pub fn assignstrvalue(
v: Option<&mut crate::ported::zsh_h::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 {
return;
}
if (pm.node.flags as u32 & PM_HASHED) != 0
&& (v.scanflags as u32 & (SCANPM_MATCHMANY | SCANPM_ARRONLY)) != 0
{
return;
}
if (v.valflags & VALFLAG_EMPTY) != 0 {
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 v_str = val.take().unwrap_or_default();
if v.start == 0 && v.end == -1 {
let len = v_str.len();
strsetfn(pm, v_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(crate::ported::zsh_h::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(crate::ported::zsh_h::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 {
crate::ported::math::mathevali(s).unwrap_or(0)
};
intsetfn(pm, ival);
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 = crate::ported::math::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 {
crate::ported::math::mnumber { l: 0, d: s.parse::<f64>().unwrap_or(0.0), type_: MN_FLOAT }
} else {
crate::ported::math::matheval(s).unwrap_or(crate::ported::math::mnumber { l: 0, d: 0.0, type_: MN_FLOAT })
};
let d = if (mn.type_ & MN_FLOAT) != 0 { mn.d } else { mn.l as f64 };
floatsetfn(pm, 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(std::sync::atomic::Ordering::Relaxed) != 0
|| ((pm.env.is_none() && (pm.node.flags as u32 & PM_EXPORTED) == 0
&& !(isset(crate::ported::zsh_h::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 assigngetset(pm: &mut crate::ported::zsh_h::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,
}));
}
_ => {
}
}
}
#[allow(unused_variables)]
pub fn check_warn_pm(
pm: &crate::ported::zsh_h::param,
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(std::sync::atomic::Ordering::Relaxed);
let forklevel: i32 = 0;
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;
}
}
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(crate::ported::zsh_h::CBASES) && b == 16 {
s.push_str("0x");
} else if isset(crate::ported::zsh_h::CBASES)
&& b == 8
&& isset(crate::ported::zsh_h::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 copyparamtable(ht: Option<&crate::ported::zsh_h::HashTable>, name: &str)
-> Option<crate::ported::zsh_h::HashTable>
{
let ht = ht?;
newparamtable(ht.hsize, name)
}
fn dontimport(flags: i32) -> i32 { let flags = flags as u32;
if flags & crate::ported::zsh_h::PM_DONTIMPORT != 0 { return 1; }
if flags & crate::ported::zsh_h::PM_EXPORTED != 0 { return 1; }
if flags & crate::ported::zsh_h::PM_DONTIMPORT_SUID != 0 && isset(crate::ported::zsh_h::PRIVILEGED)
{
return 1; }
0 }
pub fn createparamtable() {
let _ = paramtab();
let _ = realparamtab();
let add_special = |ip: &special_paramdef,
tab: &mut std::collections::HashMap<
String,
crate::ported::zsh_h::Param,
>| {
let pm = Box::new(crate::ported::zsh_h::param {
node: crate::ported::zsh_h::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: 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(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 = crate::ported::zsh_h::EMULATION(
crate::ported::zsh_h::EMULATE_SH | crate::ported::zsh_h::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",
&crate::ported::utils::ztrdup_metafy(DEFAULT_TMPPREFIX),
); setsparam(
"TIMEFMT",
&crate::ported::utils::ztrdup_metafy(
crate::ported::zsh_system_h::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", &crate::ported::utils::ztrdup_metafy(&hostname));
let logname = std::env::var("LOGNAME")
.or_else(|_| std::env::var("USER"))
.unwrap_or_default();
setsparam("LOGNAME", &crate::ported::utils::ztrdup_metafy(&logname));
crate::ported::mem::pushheap();
for (iname, ivalue) in std::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 = crate::ported::utils::metafy(&ivalue);
let _ = assignsparam(
&iname,
&metafied,
crate::ported::zsh_h::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);
}
}
crate::ported::mem::popheap();
let is_zsh = crate::ported::zsh_h::EMULATION(
crate::ported::zsh_h::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 = std::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", &crate::ported::utils::ztrdup_metafy(&cputype)); setsparam( "MACHTYPE",
&crate::ported::utils::ztrdup_metafy(crate::ported::config_h::MACHTYPE),
);
setsparam( "OSTYPE",
&crate::ported::utils::ztrdup_metafy(crate::ported::config_h::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", &crate::ported::utils::ztrdup_metafy(&tty_str)); setsparam( "VENDOR",
&crate::ported::utils::ztrdup_metafy(crate::ported::config_h::VENDOR),
);
let argv0 = std::env::args().next().unwrap_or_default();
setsparam(
"ZSH_ARGZERO",
&crate::ported::utils::ztrdup(&argv0),
); setsparam(
"ZSH_VERSION",
&crate::ported::utils::ztrdup_metafy("5.9"),
); setsparam(
"ZSH_PATCHLEVEL",
&crate::ported::utils::ztrdup_metafy(
crate::ported::patchlevel::ZSH_PATCHLEVEL,
),
);
let mut signals_arr: Vec<String> = Vec::new();
for &(name, _num) in
crate::ported::signals_h::SIGS.iter()
{
signals_arr.push(crate::ported::utils::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(crate::ported::utils::ztrdup_metafy(&nm));
}
}
}
{
let mut tab = paramtab().write().unwrap();
let pm = Box::new(crate::ported::zsh_h::param {
node: crate::ported::zsh_h::hashnode {
next: None,
nam: "signals".to_string(),
flags: (crate::ported::zsh_h::PM_ARRAY
| crate::ported::zsh_h::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);
}
}
pub fn createspecialhash(name: &str, flags: i32) -> Option<crate::ported::zsh_h::Param>
{
let mut pm = createparam(name, (PM_SPECIAL | PM_HASHED) as i32 | flags)?;
if pm.old.is_some() {
let ll = {
0_i32
};
pm.level = ll;
}
let ht = Box::new(crate::ported::zsh_h::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 createparam( name: &str,
mut flags: i32,
) -> Option<crate::ported::zsh_h::Param> {
let oldpm: Option<crate::ported::zsh_h::Param> = if !name.is_empty() {
paramtab().read().ok().and_then(|t| t.get(name).cloned())
} else {
None
};
if !name.is_empty() {
if isset(crate::ported::zsh_h::ALLEXPORT)
&& (flags as u32 & PM_HASHELEM) == 0
{
flags |= PM_EXPORTED as i32;
}
}
let cur_locallevel = locallevel.load(std::sync::atomic::Ordering::Relaxed);
let reuse = match &oldpm {
Some(op) => op.level == cur_locallevel || (flags as u32 & PM_LOCAL) == 0,
None => false,
};
let mut pm: crate::ported::zsh_h::Param = if reuse {
let mut existing = oldpm.unwrap(); existing.base = 0; existing.width = 0; existing
} else {
Box::new(crate::ported::zsh_h::param {
node: crate::ported::zsh_h::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: cur_locallevel, })
};
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 copyparam( tpm: &mut crate::ported::zsh_h::param,
pm: &mut crate::ported::zsh_h::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 deleteparamtable(t: Option<crate::ported::zsh_h::HashTable>) {
let odelunset =
DELUNSET.swap(1, std::sync::atomic::Ordering::Relaxed); if let Some(table) = t {
drop(table);
}
DELUNSET.store(odelunset, std::sync::atomic::Ordering::Relaxed); }
pub static DELUNSET: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
pub fn freeparamnode(mut _hn: crate::ported::zsh_h::Param) { if DELUNSET.load(std::sync::atomic::Ordering::Relaxed) != 0 {
stdunsetfn(_hn.as_mut(), 1); }
}
pub fn getparamnode(ht: &crate::ported::zsh_h::HashTable, nam: &str) -> Option<crate::ported::zsh_h::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 getvalue<'a>(
v: Option<&'a mut crate::ported::zsh_h::value>,
pptr: &mut &str,
bracks: i32,
) -> Option<&'a mut crate::ported::zsh_h::value> {
fetchvalue(v, pptr, bracks, SCANPM_CHECKING as i32)
}
pub fn fetchvalue<'a>( v: Option<&'a mut crate::ported::zsh_h::value>,
pptr: &mut &str,
bracks: i32,
scanflags: i32,
) -> Option<&'a mut crate::ported::zsh_h::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 crate::ported::utils::itype_end(s, true) > 0 { end_pos = crate::ported::utils::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 = crate::ported::zsh_h::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 = crate::ported::zsh_h::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(crate::ported::zsh_h::Inbrack))
{
if getindex(pptr, v, scanflags) != 0 { return Some(v); }
} else if (scanflags & crate::ported::zsh_h::SCANPM_ASSIGNING as i32) == 0
&& v.scanflags != 0
&& crate::ported::zsh_h::isset(crate::ported::options::optlookup("ksharrays"))
{
v.end = 1;
v.scanflags = 0;
}
return Some(v);
}
None
}
pub fn getindex(pptr: &mut &str, v: &mut crate::ported::zsh_h::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 = crate::lex::parse_subscript(after_lbrack, ']');
let close_pos = match close_pos {
Some(p) => p,
None => {
crate::ported::utils::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 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 { 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 issetvar(name: &str) -> i32 { let mut vbuf = crate::ported::zsh_h::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 crate::ported::utils::arrlen_ge(&arr, bound) { 1 } else { 0 }
}
pub fn getvaluearr(v: Option<&mut crate::ported::zsh_h::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 loadparamnode( _ht: &crate::ported::zsh_h::HashTable,
pm: Option<crate::ported::zsh_h::Param>,
nam: &str,
) -> Option<crate::ported::zsh_h::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| crate::ported::zsh_h::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;
crate::ported::utils::zerr(&format!(
"autoloading module {} failed to define parameter: {}",
modname, nam
));
}
pm }
#[allow(unused_variables)]
pub fn newparamtable(size: i32, name: &str)
-> Option<crate::ported::zsh_h::HashTable>
{
let hsize = if size == 0 { 17 } else { size };
let mut nodes: Vec<Option<crate::ported::zsh_h::HashNode>> =
Vec::with_capacity(hsize as usize);
for _ in 0..hsize {
nodes.push(None);
}
Some(Box::new(crate::ported::zsh_h::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,
}))
}
#[allow(unused_variables)]
pub fn paramvalarr(ht: &crate::ported::zsh_h::HashTable, flags: i32) -> Vec<String> {
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 printparamnode(hn: &mut crate::ported::zsh_h::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 {
if hn.level != 0 {
return;
}
}
if (printflags & PRINT_POSIX_EXPORT) != 0 {
if (f & PM_EXPORTED) == 0 { return; }
print!("export ");
} else if (printflags & PRINT_POSIX_READONLY) != 0 {
if (f & PM_READONLY) == 0 { return; }
print!("readonly ");
} else {
print!("typeset ");
}
}
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 printparamvalue(p: &mut crate::ported::zsh_h::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 s = strgetfn(p);
print!("{}", s);
} else if t == PM_INTEGER {
print!("{}", intgetfn(p));
} else if t == PM_EFLOAT || t == PM_FFLOAT {
print!("{}", floatgetfn(p));
} 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 (printflags & PRINT_KV_PAIR) == 0 {
print!(")");
}
}
}
pub fn resolve_nameref( pm: Option<crate::ported::zsh_h::Param>,
) -> Option<crate::ported::zsh_h::Param> {
resolve_nameref_rec(pm, None, 0) }
#[allow(unused_variables)]
pub fn resolve_nameref_rec(
pm: Option<crate::ported::zsh_h::Param>,
stop: Option<&crate::ported::zsh_h::param>,
keep_lastref: i32,
) -> Option<crate::ported::zsh_h::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 {
return None;
}
pm
}
pub fn scancopyparams(
pm: &crate::ported::zsh_h::param,
_flags: i32,
outtable: &mut std::collections::HashMap<String, Box<crate::ported::zsh_h::param>>,
) {
let tpm = crate::ported::zsh_h::param {
node: crate::ported::zsh_h::hashnode {
next: None,
nam: pm.node.nam.clone(),
flags: pm.node.flags,
},
u_data: pm.u_data,
u_arr: pm.u_arr.clone(),
u_str: pm.u_str.clone(),
u_val: pm.u_val,
u_dval: pm.u_dval,
u_hash: None,
gsu_s: None,
gsu_i: None,
gsu_f: None,
gsu_a: None,
gsu_h: None,
base: pm.base,
width: pm.width,
env: None,
ename: None,
old: None,
level: pm.level,
};
let nam = tpm.node.nam.clone();
outtable.insert(nam, Box::new(tpm));
}
pub fn scancountparams(_hn: &crate::ported::zsh_h::param, flags: i32, numparamvals: &mut u32) {
*numparamvals += 1;
if (flags as u32 & SCANPM_WANTKEYS) != 0 && (flags as u32 & SCANPM_WANTVALS) != 0 {
*numparamvals += 1;
}
}
pub fn scanendscope(pm: &mut crate::ported::zsh_h::param, _flags: i32) { let cur_local = locallevel.load(std::sync::atomic::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, std::sync::atomic::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 static NUMPARAMVALS: std::sync::atomic::AtomicU32 =
std::sync::atomic::AtomicU32::new(0); pub static SCANPROG: std::sync::OnceLock<std::sync::Mutex<Option<String>>> =
std::sync::OnceLock::new(); pub static SCANSTR: std::sync::OnceLock<std::sync::Mutex<Option<String>>> =
std::sync::OnceLock::new(); pub static PARAMVALS: std::sync::OnceLock<std::sync::Mutex<Vec<String>>> =
std::sync::OnceLock::new();
fn scanprog_lock() -> &'static std::sync::Mutex<Option<String>> {
SCANPROG.get_or_init(|| std::sync::Mutex::new(None))
}
fn scanstr_lock() -> &'static std::sync::Mutex<Option<String>> {
SCANSTR.get_or_init(|| std::sync::Mutex::new(None))
}
fn paramvals_lock() -> &'static std::sync::Mutex<Vec<String>> {
PARAMVALS.get_or_init(|| std::sync::Mutex::new(Vec::new()))
}
pub fn scanparamvals( pm: &mut crate::ported::zsh_h::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 {
if !pattry(&pm.node.nam, &s) { return; }
} else {
return;
}
} else if (f & SCANPM_MATCHKEY) != 0 {
let prog = scanprog_lock().lock().unwrap().clone();
if let Some(p) = prog {
if !pattry(&p, &pm.node.nam) { 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 = crate::ported::zsh_h::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.map(|p| pattry(&p, &s)).unwrap_or(false);
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);
}
fn pattry(prog: &str, s: &str) -> bool {
prog == s
}
#[allow(unused_variables)]
pub fn setloopvar(name: &str, value: &str) {
}
pub fn setnparam(s: &str, val: f64) {
assignnparam(s, crate::ported::math::mnumber { l: 0, d: val, type_: MN_FLOAT }, crate::ported::zsh_h::ASSPM_WARN);
}
pub fn setnumvalue(v: Option<&mut crate::ported::zsh_h::value>, val: crate::ported::math::mnumber) {
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 {
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 {
val.l.to_string()
} else {
val.d.to_string()
};
let _ = 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 setscope(pm: &mut crate::ported::zsh_h::param) {
crate::ported::signals::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 {
} else {
}
}
}
crate::ported::signals::unqueue_signals();
}
pub fn setscope_base(pm: &mut crate::ported::zsh_h::param, base: i32) {
pm.base = base;
if base > pm.level {
}
}
pub fn upscope(
mut pm: crate::ported::zsh_h::Param,
reference: &crate::ported::zsh_h::param,
) -> crate::ported::zsh_h::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 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 crate::ported::utils::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()),
"RANDOM" => Some(randomgetfn().to_string()),
"TTYIDLE" => Some(ttyidlegetfn().to_string()),
"ERRNO" => Some(errnogetfn().to_string()),
"SECONDS" => Some(intsecondsgetfn().to_string()),
"USERNAME" => Some(usernamegetfn()),
"HOME" => Some(homegetfn()),
"TERM" => Some(termgetfn()),
"WORDCHARS" => Some(wordcharsgetfn()),
"IFS" => Some(ifsgetfn()),
"TERMINFO" => Some(terminfogetfn()),
"TERMINFO_DIRS" => Some(terminfodirsgetfn()),
"KEYBOARD_HACK" => Some(keyboardhackgetfn()),
"histchars" | "HISTCHARS" => Some(histcharsgetfn()),
"_" => Some(underscoregetfn()),
"HISTSIZE" => Some(histsizegetfn().to_string()),
"SAVEHIST" => Some(savehistsizegetfn().to_string()),
"#" | "ARGC" => Some(poundgetfn().to_string()),
"0" => crate::ported::utils::argzero(),
"?" => Some(crate::ported::builtin::LASTVAL
.load(std::sync::atomic::Ordering::Relaxed)
.to_string()),
"$" => Some(std::process::id().to_string()),
"!" => {
let tab = paramtab().read().ok()?;
Some(tab.get("!").and_then(|pm| pm.u_str.clone())
.unwrap_or_else(|| "0".to_string()))
}
"*" | "@" => {
let sep = ifsgetfn().chars().next().unwrap_or(' ').to_string();
pparams_lock().lock().ok().map(|p| p.join(&sep))
}
"-" => {
let mut letters = String::from("569X");
let opt = |n: &str| {
crate::ported::options::opt_state_get(n).unwrap_or(false)
};
if opt("errexit") { letters.push('e'); }
if !opt("rcs") { letters.push('f'); }
if opt("login") { letters.push('l'); }
if opt("nounset") { letters.push('u'); }
if opt("xtrace") { letters.push('x'); }
if opt("verbose") { letters.push('v'); }
if opt("noexec") { letters.push('n'); }
if opt("hashall") { letters.push('h'); }
Some(letters)
}
"pipestatus" => {
let arr = pipestatgetfn();
if arr.is_empty() {
None
} else {
Some(arr.join(" "))
}
}
_ => None,
}
}
#[cfg(test)]
mod gsu_tests {
use super::*;
#[test]
fn test_libc_id_callbacks_match_libc() {
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 test_random_returns_15_bit_value() {
for _ in 0..100 {
let v = randomgetfn();
assert!(v >= 0 && v < 0x8000);
}
}
#[test]
fn test_random_set_seeds_deterministically() {
randomsetfn(42);
let a = randomgetfn();
randomsetfn(42);
let b = randomgetfn();
assert_eq!(a, b);
}
#[test]
fn test_ifs_round_trip() {
let original = ifsgetfn();
ifssetfn(":,;".to_string());
assert_eq!(ifsgetfn(), ":,;");
ifssetfn(original);
}
#[test]
fn test_histsiz_clamps_to_1() {
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 original = savehistsizegetfn();
savehistsizesetfn(-5);
assert_eq!(savehistsizegetfn(), 0);
savehistsizesetfn(100);
assert_eq!(savehistsizegetfn(), 100);
savehistsizesetfn(original);
}
#[test]
fn test_pipestat_round_trip() {
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 test_simple_arrayuniq_first_wins() {
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() {
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() {
assert_eq!(mkenvstr("PATH", "/usr/bin", 0), "PATH=/usr/bin");
assert_eq!(mkenvstr("EMPTY", "", 0), "EMPTY=");
}
#[test]
fn test_seconds_round_trip() {
intsecondssetfn(0);
let s1 = intsecondsgetfn();
std::thread::sleep(std::time::Duration::from_millis(5));
let s2 = intsecondsgetfn();
assert!(s2 >= s1);
setrawseconds(100.0);
assert_eq!(getrawseconds(), 100.0);
}
#[test]
fn test_argzero_round_trip() {
argzerosetfn("/bin/zsh".to_string());
assert_eq!(argzerogetfn(), "/bin/zsh");
argzerosetfn(String::new());
}
#[test]
fn test_env_get_set() {
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() {
keyboardhacksetfn("\\".to_string());
assert_eq!(keyboardhackgetfn(), "\\");
keyboardhacksetfn(String::new());
assert_eq!(keyboardhackgetfn(), "");
}
#[test]
fn test_histchars_default() {
histcharssetfn(None);
assert_eq!(histcharsgetfn(), "!^#");
histcharssetfn(Some("@$&".to_string()));
assert_eq!(histcharsgetfn(), "@$&");
histcharssetfn(None);
}
}