#[allow(unused_imports)]
use crate::ported::exec::{
cached_regex, slice_array_zero_based, slice_positionals,
};
use crate::ported::zsh_h::{hashnode, param, Param, PM_ARRAY};
use crate::ported::zsh_h::PM_HASHED;
use std::sync::atomic::AtomicUsize;
use crate::ported::modules::parameter::*;
use crate::ported::zsh_h::{PM_INTEGER, PM_EFLOAT, PM_FFLOAT, PM_READONLY, PM_EXPORTED, PM_LEFT, PM_RIGHT_B, PM_RIGHT_Z, PM_UPPER, PM_LOWER, PM_HIDE, PM_HIDEVAL, PM_TAGGED, PM_UNIQUE};
use std::ffi::CString;
#[allow(unused_imports)]
use crate::parse::{ShellWord, VarModifier, ZshParamFlag};
use crate::ported::hist::{casemodify, rembutext, remlpaths, remtext, remtpath, CASMOD_CAPS, CASMOD_LOWER, CASMOD_UPPER};
use crate::ported::utils::{xsymlinks, zerr};
use std::sync::atomic::Ordering;
pub type LinkList = crate::ported::linklist::LinkList<String>;
#[inline]
fn errflag_set() -> bool {
crate::ported::utils::errflag.load(Ordering::Relaxed) != 0
}
#[inline]
fn errflag_set_error() {
crate::ported::utils::errflag.fetch_or(
crate::ported::zsh_h::ERRFLAG_ERROR,
Ordering::Relaxed,
);
}
use crate::ported::zsh_h::{
Bnull, Dnull, Equals, Inang, Inbrace, Inbrack, Inpar, Inparmath, Marker,
Nularg, Outang, OutangProc, Outbrace, Outbrack, Outpar, Outparmath, Pound,
Qstring, Qtick, SCANPM_NONAMEREF, SCANPM_WANTKEYS, SCANPM_WANTVALS, Snull,
Stringg, Tick,
}; const STRING: char = Stringg; const OUTANGPROC: char = OutangProc;
pub const LF_ARRAY: u32 = 1;
use crate::ported::zsh_h::{
PREFORK_ASSIGN, PREFORK_KEY_VALUE, PREFORK_NOSHWORDSPLIT, PREFORK_NO_UNTOK,
PREFORK_SHWORDSPLIT, PREFORK_SINGLE, PREFORK_SPLIT, PREFORK_SUBEXP, PREFORK_TYPESET,
};
pub const NULSTRING: &str = "\u{8F}";
fn scanpmraliases() -> String {
crate::ported::hashtable::aliastab_lock().read().ok()
.map(|t| t.iter()
.filter(|(_, a)| {
let f = a.node.flags;
(f & ALIAS_GLOBAL as i32) == 0
&& (f & ALIAS_SUFFIX as i32) == 0
&& (f & DISABLED as i32) == 0
})
.map(|(_, a)| a.text.clone())
.collect::<Vec<_>>().join(" ")
).unwrap_or_default()
}
fn scanpmgaliases() -> String {
crate::ported::hashtable::aliastab_lock().read().ok()
.map(|t| t.iter()
.filter(|(_, a)| {
let f = a.node.flags;
(f & ALIAS_GLOBAL as i32) != 0 && (f & DISABLED as i32) == 0
})
.map(|(_, a)| a.text.clone())
.collect::<Vec<_>>().join(" ")
).unwrap_or_default()
}
fn scanpmsaliases() -> String {
crate::ported::hashtable::sufaliastab_lock().read().ok()
.map(|t| t.iter()
.filter(|(_, a)| (a.node.flags & DISABLED as i32) == 0)
.map(|(_, a)| a.text.clone())
.collect::<Vec<_>>().join(" ")
).unwrap_or_default()
}
fn scanpmdisraliases() -> String {
crate::ported::hashtable::aliastab_lock().read().ok()
.map(|t| t.iter()
.filter(|(_, a)| {
let f = a.node.flags;
(f & ALIAS_GLOBAL as i32) == 0
&& (f & ALIAS_SUFFIX as i32) == 0
&& (f & DISABLED as i32) != 0
})
.map(|(_, a)| a.text.clone())
.collect::<Vec<_>>().join(" ")
).unwrap_or_default()
}
fn scanpmdisgaliases() -> String {
crate::ported::hashtable::aliastab_lock().read().ok()
.map(|t| t.iter()
.filter(|(_, a)| {
let f = a.node.flags;
(f & ALIAS_GLOBAL as i32) != 0 && (f & DISABLED as i32) != 0
})
.map(|(_, a)| a.text.clone())
.collect::<Vec<_>>().join(" ")
).unwrap_or_default()
}
fn scanpmdissaliases() -> String {
crate::ported::hashtable::sufaliastab_lock().read().ok()
.map(|t| t.iter()
.filter(|(_, a)| (a.node.flags & DISABLED as i32) != 0)
.map(|(_, a)| a.text.clone())
.collect::<Vec<_>>().join(" ")
).unwrap_or_default()
}
fn scanpmcommands() -> String {
crate::ported::hashtable::cmdnamtab_lock().read().ok()
.map(|t| t.iter()
.filter_map(|(nm, c)| {
let hashed = (c.node.flags & HASHED as i32) != 0;
if hashed {
c.cmd.clone()
} else {
c.name.as_ref()
.and_then(|v| v.first())
.map(|seg| format!("{}/{}", seg, nm))
}
})
.collect::<Vec<_>>().join(" ")
).unwrap_or_default()
}
fn scanpmfunctions() -> String {
crate::ported::hashtable::shfunctab_lock().read().ok()
.map(|t| t.iter()
.filter(|(_, f)| (f.node.flags & DISABLED as i32) == 0)
.map(|(_, f)| f.body.clone().unwrap_or_default())
.collect::<Vec<_>>().join(" ")
).unwrap_or_default()
}
fn scanpmdisfunctions() -> String {
crate::ported::hashtable::shfunctab_lock().read().ok()
.map(|t| t.iter()
.filter(|(_, f)| (f.node.flags & DISABLED as i32) != 0)
.map(|(_, f)| f.body.clone().unwrap_or_default())
.collect::<Vec<_>>().join(" ")
).unwrap_or_default()
}
fn scanpmfunction_source() -> String {
crate::ported::hashtable::shfunctab_lock().read().ok()
.map(|t| t.iter()
.filter(|(_, f)| (f.node.flags & DISABLED as i32) == 0)
.map(|(_, f)| f.filename.clone().unwrap_or_default())
.collect::<Vec<_>>().join(" ")
).unwrap_or_default()
}
fn scanpmdisfunction_source() -> String {
crate::ported::hashtable::shfunctab_lock().read().ok()
.map(|t| t.iter()
.filter(|(_, f)| (f.node.flags & DISABLED as i32) != 0)
.map(|(_, f)| f.filename.clone().unwrap_or_default())
.collect::<Vec<_>>().join(" ")
).unwrap_or_default()
}
fn scanpmnameddirs() -> String {
crate::ported::hashnameddir::nameddirtab().lock().ok()
.map(|t| t.iter().map(|(_, d)| d.dir.clone()).collect::<Vec<_>>().join(" "))
.unwrap_or_default()
}
fn scanpmbuiltins() -> String {
crate::ported::builtin::createbuiltintable().keys().cloned()
.collect::<Vec<_>>().join(" ")
}
fn scanpmparameters() -> String {
crate::ported::params::paramtab().read().ok()
.map(|t| t.keys().cloned().collect::<Vec<_>>().join(" "))
.unwrap_or_default()
}
fn scanpmoptions() -> String {
crate::ported::options::ZSH_OPTIONS_SET.iter()
.map(|s| s.to_string()).collect::<Vec<_>>().join(" ")
}
fn splice_magic_assoc(name: &str) -> Option<String> {
let v = match name {
"aliases" => scanpmraliases(), "galiases" => scanpmgaliases(),
"saliases" => scanpmsaliases(),
"dis_aliases" => scanpmdisraliases(),
"dis_galiases" => scanpmdisgaliases(),
"dis_saliases" => scanpmdissaliases(),
"commands" => scanpmcommands(), "functions" => scanpmfunctions(), "dis_functions" => scanpmdisfunctions(),
"functions_source" => scanpmfunction_source(),
"dis_functions_source" => scanpmdisfunction_source(),
"nameddirs" => scanpmnameddirs(), "builtins" => scanpmbuiltins(), "parameters" => scanpmparameters(), "options" => scanpmoptions(), _ => return None,
};
Some(v)
}
fn vars_get(name: &str) -> Option<String> {
let tab = crate::ported::params::paramtab().read().ok()?;
let pm = tab.get(name)?;
pm.u_str.clone()
}
fn vars_contains(name: &str) -> bool {
crate::ported::params::paramtab().read()
.map_or(false, |tab| tab.contains_key(name))
}
fn vars_insert(name: String, value: String) {
crate::ported::params::setsparam(&name, &value);
}
fn arrays_get(name: &str) -> Option<Vec<String>> {
let tab = crate::ported::params::paramtab().read().ok()?;
let pm = tab.get(name)?;
pm.u_arr.clone()
}
fn arrays_contains(name: &str) -> bool {
crate::ported::params::paramtab().read()
.map_or(false, |tab| {
tab.get(name).map_or(false, |pm| pm.u_arr.is_some())
})
}
fn arrays_insert(name: String, value: Vec<String>) {
let mut tab = match crate::ported::params::paramtab().write() {
Ok(t) => t,
Err(_) => return,
};
if let Some(pm) = tab.get_mut(&name) {
pm.u_arr = Some(value);
pm.u_str = None;
pm.node.flags |= PM_ARRAY as i32;
} else {
let pm: Param = Box::new(param {
node: hashnode {
next: None,
nam: name.clone(),
flags: PM_ARRAY as i32,
},
u_data: 0, u_arr: Some(value), 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, pm);
}
}
fn assoc_get(name: &str) -> Option<indexmap::IndexMap<String, String>> {
crate::ported::params::paramtab_hashed_storage()
.lock()
.ok()
.and_then(|s| s.get(name).cloned())
}
fn assoc_contains(name: &str) -> bool {
crate::ported::params::paramtab_hashed_storage()
.lock()
.map_or(false, |s| s.contains_key(name))
}
fn exec_assignaparam(name: &str, parts: Vec<String>) {
arrays_insert(name.to_string(), parts);
}
fn exec_sethparam(name: &str, parts: Vec<String>) {
let mut map: indexmap::IndexMap<String, String> = indexmap::IndexMap::new();
let mut it = parts.into_iter();
while let (Some(k), Some(v)) = (it.next(), it.next()) {
map.insert(k, v);
}
if let Ok(mut store) = crate::ported::params::paramtab_hashed_storage().lock() {
store.insert(name.to_string(), map);
}
if let Ok(mut tab) = crate::ported::params::paramtab().write() {
if let Some(pm) = tab.get_mut(name) {
pm.node.flags |= PM_HASHED as i32;
} else {
let pm: Param = Box::new(param {
node: hashnode {
next: None,
nam: name.to_string(),
flags: PM_HASHED as i32,
},
u_data: 0, u_arr: None, u_str: None, u_val: 0,
u_dval: 0.0, u_hash: None,
gsu_s: None, gsu_i: None, gsu_f: None, gsu_a: None, gsu_h: None,
base: 0, width: 0, env: None, ename: None, old: None, level: 0,
});
tab.insert(name.to_string(), pm);
}
}
}
fn exec_sync_state_from_paramtab() {}
fn exec_getsparam(name: &str) -> Option<String> {
vars_get(name)
}
thread_local! {
pub static IN_PARAMSUBST_NEST: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
}
thread_local! {
pub static SKIP_FILESUB: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
thread_local! {
pub static SUB_FLAGS: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
}
pub fn sub_flags_get() -> i32 {
SUB_FLAGS.with(|c| c.get())
}
pub fn sub_flags_set(v: i32) {
SUB_FLAGS.with(|c| c.set(v));
}
fn keyvalpairelement(list: &mut LinkList, node_idx: usize) -> Option<usize> {
let data = list.getdata(node_idx)?.to_string(); let chars: Vec<char> = data.chars().collect();
if chars.is_empty() || (chars[0] != Inbrack && chars[0] != '[')
{
return None; }
let mut end_pos: Option<usize> = None; for (i, &c) in chars.iter().enumerate().skip(1) {
if c == Outbrack || c == ']' {
end_pos = Some(i); break; }
}
let end_pos = end_pos?;
if end_pos + 1 >= chars.len() {
return None; }
let is_append = chars.get(end_pos + 1) == Some(&'+') && (chars.get(end_pos + 2) == Some(&Equals)
|| chars.get(end_pos + 2) == Some(&'='));
let is_assign = !is_append && (chars.get(end_pos + 1) == Some(&Equals)
|| chars.get(end_pos + 1) == Some(&'='));
if !is_assign && !is_append {
return None;
}
let raw_key: String = chars[1..end_pos].iter().collect(); let key_subst = singsub(&raw_key); let key = crate::lex::untokenize(&key_subst);
let value_start = if is_append { end_pos + 3 } else { end_pos + 2 }; let raw_value: String = chars[value_start..].iter().collect(); let value_subst = singsub(&raw_value); let value = crate::lex::untokenize(&value_subst);
let marker = if is_append {
format!("{}+", Marker) } else {
Marker.to_string() };
list.setdata(node_idx, marker); let key_idx = list.insertlinknode(node_idx, key); let val_idx = list.insertlinknode(key_idx, value);
Some(val_idx) }
pub fn prefork(list: &mut LinkList, flags: i32, ret_flags: &mut i32) { let mut node_idx = 0; let mut stop_idx: Option<usize> = None; let mut keep = false; let asssub = (flags & PREFORK_TYPESET != 0) && isset(crate::ported::zsh_h::KSHTYPESET); let mut iter_count = 0u32;
while node_idx < list.nodes.len() {
iter_count += 1; if iter_count > 100_000 {
return; } if (flags & (PREFORK_SINGLE | PREFORK_ASSIGN)) == PREFORK_ASSIGN {
if let Some(new_idx) = keyvalpairelement(list, node_idx) {
node_idx = new_idx + 1; *ret_flags |= PREFORK_KEY_VALUE;
continue; } }
if errflag_set() {
return; }
if isset(crate::ported::zsh_h::SHFILEEXPANSION) {
if let Some(data) = list.getdata(node_idx) {
let new_data = filesub(
data, flags & (PREFORK_TYPESET | PREFORK_ASSIGN), ); list.setdata(node_idx, new_data); } } else {
if let Some(new_idx) = stringsubst(
list, node_idx, flags & !(PREFORK_TYPESET | PREFORK_ASSIGN), ret_flags, asssub, ) {
node_idx = new_idx; } else {
return; } }
node_idx += 1; }
if isset(crate::ported::zsh_h::SHFILEEXPANSION) {
node_idx = 0; while node_idx < list.nodes.len() {
if let Some(new_idx) = stringsubst(
list, node_idx, flags & !(PREFORK_TYPESET | PREFORK_ASSIGN), ret_flags, asssub, ) {
node_idx = new_idx + 1; } else {
return; } } }
node_idx = 0; while node_idx < list.nodes.len() {
if Some(node_idx) == stop_idx {
keep = false; }
if let Some(data) = list.getdata(node_idx) {
if !data.is_empty() {
let data = data.replace('\0', ""); list.setdata(node_idx, data.clone());
if !isset(crate::ported::zsh_h::IGNOREBRACES) && (flags & PREFORK_SINGLE == 0) {
if !keep {
stop_idx = list.nextnode(node_idx); }
loop {
let cur = match list.getdata(node_idx) {
Some(d) => d.to_string(),
None => break,
};
let expanded = crate::ported::glob::xpandbraces(&cur, false); if expanded.len() <= 1 {
break;
} keep = true; list.setdata(node_idx, expanded[0].clone()); let mut last = node_idx;
for ex in &expanded[1..] {
last = list.insertlinknode(last, ex.clone());
}
}
}
if !isset(crate::ported::zsh_h::SHFILEEXPANSION) && !SKIP_FILESUB.with(|c| c.get()) {
if let Some(data) = list.getdata(node_idx) {
let new_data = filesub(
data, flags & (PREFORK_TYPESET | PREFORK_ASSIGN), ); list.setdata(node_idx, new_data); } } } else if (flags & PREFORK_SINGLE == 0) && (*ret_flags & PREFORK_KEY_VALUE == 0) && !keep
{
list.delete_node(node_idx); continue; } }
if errflag_set() {
return; }
node_idx += 1; } }
fn stringsubstquote(strstart: &str, pstrdpos: usize) -> (String, usize) {
let chars: Vec<char> = strstart.chars().collect();
let start = pstrdpos + 2; let mut end = start; let mut escaped = false;
while end < chars.len() {
if escaped {
escaped = false; end += 1; continue; }
if chars[end] == '\\' {
escaped = true; end += 1; continue; }
if chars[end] == '\'' {
break;
} end += 1;
}
let content: String = chars[start..end].iter().collect();
let (strsub, _) = crate::ported::utils::getkeystring(&content);
let prefix: String = chars[..pstrdpos].iter().collect(); let suffix: String = if end + 1 < chars.len() {
chars[end + 1..].iter().collect() } else {
String::new() };
let strret = if strsub.is_empty() && prefix.is_empty() && suffix.is_empty() {
"\u{8b}".to_string() } else {
format!("{}{}{}", prefix, strsub, suffix) };
let new_pos = prefix.chars().count()
+ strret
.chars()
.count()
.saturating_sub(prefix.chars().count() + suffix.chars().count());
(strret, new_pos) }
fn stringsubst(
list: &mut LinkList, node_idx: usize, pf_flags: i32, ret_flags: &mut i32, asssub: bool, ) -> Option<usize> {
let mut str3 = list.getdata(node_idx)?.to_string(); let mut pos = 0;
let mut p1_iter = 0u32; loop {
if errflag_set() {
break; } p1_iter += 1; if p1_iter > 100_000 {
return None; } let chars: Vec<char> = str3.chars().collect(); if pos >= chars.len() {
break; } let c = chars[pos];
if (c == Inang || c == OUTANGPROC || (pos == 0 && c == Equals)) && chars.get(pos + 1) == Some(&Inpar)
{
if errflag_set() {
return None; } let start = pos; pos += 2; let mut depth = 1_i32; while pos < chars.len() && depth > 0 {
let ch = chars[pos]; if ch == Inpar {
depth += 1;
}
else if ch == Outpar {
depth -= 1;
} pos += 1; } let str_chars: Vec<char> = str3.chars().collect(); let mut new_str = String::with_capacity(str_chars.len());
new_str.extend(str_chars[..start].iter()); new_str.extend(str_chars[pos..].iter()); str3 = new_str; list.setdata(node_idx, str3.clone()); pos = start; continue; }
pos += 1; }
pos = 0; let mut iter_count = 0u32; loop {
if errflag_set() {
break; } iter_count += 1; if iter_count > 100_000 {
return None; } let chars: Vec<char> = str3.chars().collect(); if pos >= chars.len() {
break; } let c = chars[pos];
if c == '\u{9d}' {
let mut end = pos + 1; while end < chars.len() && chars[end] != '\u{9d}' {
end += 1; } let prefix: String = chars[..pos].iter().collect(); let body: String = chars[pos + 1..end].iter().collect(); let suffix: String = if end < chars.len() {
chars[end + 1..].iter().collect() } else {
String::new() }; str3 = format!("{}{}{}", prefix, body, suffix); pos += body.chars().count(); list.setdata(node_idx, str3.clone()); continue; } if c == '\u{9e}' {
let prefix: String = chars[..pos].iter().collect(); let suffix: String = if pos + 1 < chars.len() {
chars[pos + 1..].iter().collect() } else {
String::new() }; str3 = format!("{}{}", prefix, suffix); list.setdata(node_idx, str3.clone()); continue; } if c == '\u{9f}' && pos + 1 < chars.len() {
let prefix: String = chars[..pos].iter().collect(); let kept = chars[pos + 1]; let suffix: String = if pos + 2 < chars.len() {
chars[pos + 2..].iter().collect() } else {
String::new() }; str3 = format!("{}{}{}", prefix, kept, suffix); pos += 1; list.setdata(node_idx, str3.clone()); continue; } if c == '\'' {
let mut end = pos + 1; while end < chars.len() && chars[end] != '\'' {
end += 1; } let prefix: String = chars[..pos].iter().collect(); let body: String = chars[pos + 1..end].iter().collect(); let suffix: String = if end < chars.len() {
chars[end + 1..].iter().collect() } else {
String::new() }; str3 = format!("{}{}{}", prefix, body, suffix); pos += body.chars().count(); list.setdata(node_idx, str3.clone()); continue; }
let qt = c == Qstring; if qt || c == STRING || c == '$' {
let next_c = chars.get(pos + 1).copied(); let next_is = |tok: char, lit: char| {
next_c == Some(tok) || next_c == Some(lit) };
if next_c == Some(Inparmath) || (next_c == Some('(') && chars.get(pos + 2).copied() == Some('('))
{
let start = pos + 3; let mut depth = 2_i32; let mut p = start; let mut end_off: Option<usize> = None; while p < chars.len() {
let ch = chars[p]; if ch == '(' || ch == Inpar {
depth += 1;
}
else if ch == ')' || ch == Outpar {
depth -= 1; if depth == 0 {
end_off = Some(p); break; } } p += 1; } if let Some(end) = end_off {
let expr: String = chars[start..end - 1].iter().collect(); let prefix: String = chars[..pos].iter().collect(); let suffix: String = if end + 1 < chars.len() {
chars[end + 1..].iter().collect() } else {
String::new() }; let result_only = arithsubst(&expr, "", ""); str3 = format!("{}{}{}", prefix, result_only, suffix); list.setdata(node_idx, str3.clone()); pos = prefix.chars().count() + result_only.chars().count(); continue; } }
if next_is(Inpar, '(') || next_is(Inparmath, '\0') {
if !qt {
list.flags |= LF_ARRAY; } let cmd_open = pos + 1; let chars: Vec<char> = str3.chars().collect(); let mut depth = 0_i32; let mut end = cmd_open; while end < chars.len() {
let ch = chars[end]; if ch == '(' || ch == Inpar {
depth += 1;
}
else if ch == ')' || ch == Outpar {
depth -= 1; if depth == 0 {
break;
} } end += 1; } if end < chars.len() && depth == 0 {
let cmd: String = chars[cmd_open + 1..end].iter().collect(); let trimmed = cmd.trim_start();
let output = if let Some(rest) = trimmed.strip_prefix('<') {
let path = rest.trim();
std::fs::read_to_string(path).unwrap_or_default()
} else {
crate::exec::getoutput(&cmd)
};
let prefix: String = chars[..pos].iter().collect(); let suffix: String = if end + 1 < chars.len() {
chars[end + 1..].iter().collect() } else {
String::new() }; str3 = format!("{}{}{}", prefix, output.trim_end_matches('\n'), suffix); pos = prefix.chars().count() + output.trim_end_matches('\n').chars().count(); list.setdata(node_idx, str3.clone()); } else {
pos += 1; } continue; } else if next_is(Inbrack, '[') {
let start = pos + 2; let open = if next_c == Some(Inbrack) {
Inbrack
} else {
'['
}; let close = if open == Inbrack { Outbrack } else { ']' }; let chars: Vec<char> = str3.chars().collect(); let mut depth = 1_i32; let mut end_off: Option<usize> = None; let mut p = start; while p < chars.len() {
let ch = chars[p]; if ch == open || ch == '[' {
depth += 1;
}
else if ch == close || ch == ']' {
depth -= 1; if depth == 0 {
end_off = Some(p - start);
break;
} } p += 1; } if let Some(end) = end_off {
let expr: String = chars[start..start + end].iter().collect(); let prefix: String = chars[..pos].iter().collect(); let suffix: String = if start + end + 1 < chars.len() {
chars[start + end + 1..].iter().collect()
} else {
String::new()
};
let result_only = arithsubst(&expr, "", ""); str3 = format!("{}{}{}", prefix, result_only, suffix); list.setdata(node_idx, str3.clone()); pos = prefix.chars().count() + result_only.chars().count(); continue; } else {
errflag_set_error(); zerr("closing bracket missing"); return None; } } else if next_c == Some(Snull) || next_c == Some('\'') {
let (new_str, new_pos) = stringsubstquote(&str3, pos); str3 = new_str; pos = new_pos; list.setdata(node_idx, str3.clone()); continue; } else {
let mut new_pf_flags = pf_flags; if (isset(crate::ported::zsh_h::SHWORDSPLIT) && (pf_flags & PREFORK_NOSHWORDSPLIT == 0)) || (pf_flags & PREFORK_SPLIT != 0)
{
new_pf_flags |= PREFORK_SHWORDSPLIT; }
IN_PARAMSUBST_NEST.with(|c| c.set(c.get() + 1)); let (new_str, new_pos, new_nodes) = paramsubst(
&str3, pos, qt, new_pf_flags & (PREFORK_SINGLE | PREFORK_SHWORDSPLIT | PREFORK_SUBEXP), ret_flags, ); IN_PARAMSUBST_NEST.with(|c| c.set(c.get() - 1));
if errflag_set() {
return None; }
if new_nodes.is_empty() {
list.setdata(node_idx, String::new()); } else {
let mut current_idx = node_idx; for (i, node_data) in new_nodes.into_iter().enumerate() {
if i == 0 {
list.setdata(current_idx, node_data); } else {
current_idx = list.insertlinknode(current_idx, node_data);
} } }
str3 = list.getdata(node_idx)?.to_string(); pos = new_pos; continue; } }
let qt = c == Qtick; if qt || c == Tick || c == '`' {
if !qt {
list.flags |= LF_ARRAY; } let chars: Vec<char> = str3.chars().collect(); let cmd_start = pos + 1; let mut end = cmd_start; while end < chars.len()
&& chars[end] != Tick
&& chars[end] != Qtick
&& chars[end] != '`'
{
if chars[end] == '\\' && end + 1 < chars.len() {
end += 1;
} end += 1; } if end < chars.len() {
let cmd: String = chars[cmd_start..end].iter().collect(); let output = crate::exec::getoutput(&cmd);
let prefix: String = chars[..pos].iter().collect(); let suffix: String = if end + 1 < chars.len() {
chars[end + 1..].iter().collect() } else {
String::new() }; str3 = format!("{}{}{}", prefix, output.trim_end_matches('\n'), suffix); pos = prefix.chars().count() + output.trim_end_matches('\n').chars().count(); list.setdata(node_idx, str3.clone()); } else {
pos += 1; } continue; }
if asssub && (c == '=' || c == Equals) && pos > 0 { }
pos += 1; }
if errflag_set() {
None } else {
Some(node_idx) } }
pub fn paramsubst(
s: &str, start_pos: usize, qt: bool, pf_flags: i32, ret_flags: &mut i32, ) -> (String, usize, Vec<String>) {
let chars: Vec<char> = s.chars().collect(); let mut pos = start_pos + 1; let mut result_nodes = Vec::new();
let c = chars.get(pos).copied().unwrap_or('\0');
if c == Inbrace || c == '{' {
pos += 1; let mut depth = 1_i32; let mut end = pos; while end < chars.len() && depth > 0 {
let ch = chars[end]; if ch == '{' || ch == Inbrace {
depth += 1;
}
else if ch == '}' || ch == Outbrace {
depth -= 1; if depth == 0 {
break;
} } end += 1; } if end >= chars.len() || depth != 0 {
zerr("closing brace missing"); errflag_set_error(); return (String::new(), chars.len(), vec![]); }
let body: String = chars[pos..end].iter().collect(); let new_pos = if end < chars.len() { end + 1 } else { end };
let body_chars: Vec<char> = body.chars().collect();
let mut idx = 0_usize;
let mut flag_lower = false; let mut flag_upper = false; let mut flag_caps = false; let mut flag_qcount: u32 = 0; let mut flag_qmin = false; let mut flag_qplus = false; let mut flag_at = false; let mut subexp_array_temp: Option<String> = None;
let mut flag_p_indirect = false; let mut flag_arrasg: i32 = 0; let mut flag_typeinfo = false; let mut flag_keys = false; let mut flag_values = false; let mut flag_evalchar = false; let mut prenum: i64 = 0; let mut postnum: i64 = 0; let mut premul: Option<String> = None; let mut postmul: Option<String> = None; let mut preone: Option<String> = None; let mut postone: Option<String> = None; let mut spsep: Option<String> = None; let mut sep: Option<String> = None; let mut sort_active = false; let mut sort_backwards = false; let mut sort_case_insensitive = false; let mut sort_numeric = false; let mut sort_signed = false; let mut sort_index_order = false; let mut unique = false; let mut flag_eval = false; let mut flag_unquote = false; let mut flag_error = false; let mut flag_visible = false; let mut flag_char_count = false; let mut flag_word_count = false; let mut flag_word_count_w = false; let mut flag_b_pattern = false; let mut sub_flags_bits: i32 = 0; let mut flag_d_dir = false; let mut flag_p_escapes = false; let mut flag_g_seen: bool = false; let mut flag_g_emacs: bool = false; let mut flag_g_octal: bool = false; let mut flag_g_ctrl: bool = false; let mut flag_pct_prompt: u32 = 0; let mut multi_width: u32 = 0; let mut flnum: u32 = 0; let mut flag_z_tokenize = false; let mut flag_z_keep_comments = false; let mut flag_z_strip_comments = false; let mut flag_z_newline_ws = false; let mut plan9 = isset(crate::ported::zsh_h::RCEXPANDPARAM); let mut hkeys: u32 = 0; let mut hvals: u32 = 0; if body_chars.first() == Some(&'(') {
let mut tok_arg = false; let mut d = 1_i32; idx = 1; if !body_chars.iter().skip(1).any(|c| *c == ')') {
zerr("bad substitution"); errflag_set_error(); return (String::new(), new_pos, vec![]); } while idx < body_chars.len() && d > 0 {
let fc = body_chars[idx]; match fc {
'(' => {
d += 1;
} ')' => {
d -= 1;
if d == 0 {
idx += 1;
break;
}
} 'L' => {
flag_lower = true;
} 'U' => {
flag_upper = true;
} 'C' => {
flag_caps = true;
} 'q' => {
let next = body_chars.get(idx + 1).copied();
if next == Some('-') {
idx += 1; flag_qmin = true; } else if next == Some('+') {
idx += 1; flag_qplus = true; } else {
flag_qcount += 1; } } 'A' => {
flag_arrasg += 1;
} '@' => {
flag_at = true;
} 'P' => {
flag_p_indirect = true;
} 't' => {
flag_typeinfo = true;
} '!' => {
if ((hkeys | hvals) & !SCANPM_NONAMEREF) != 0 {
zerr("bad substitution");
errflag_set_error();
return (String::new(), new_pos, vec![]);
}
hkeys = SCANPM_NONAMEREF;
}
'k' => {
if (hkeys & !SCANPM_WANTKEYS) != 0 {
zerr("bad substitution");
errflag_set_error();
return (String::new(), new_pos, vec![]);
}
hkeys = SCANPM_WANTKEYS;
} 'v' => {
if (hvals & !SCANPM_WANTVALS) != 0 {
zerr("bad substitution");
errflag_set_error();
return (String::new(), new_pos, vec![]);
}
hvals = SCANPM_WANTVALS;
} '#' => {
flag_evalchar = true;
} 'l' | 'r' => {
let is_left = fc == 'l'; idx += 1; if idx >= body_chars.len() {
break;
}
let del = body_chars[idx]; idx += 1; let mut num_str = String::new(); while idx < body_chars.len() && body_chars[idx].is_ascii_digit() {
num_str.push(body_chars[idx]);
idx += 1;
}
let n: i64 = num_str.parse().unwrap_or(0); if is_left {
prenum = n;
} else {
postnum = n;
} if idx < body_chars.len() && body_chars[idx] == del {
idx += 1; let s1_start = idx; while idx < body_chars.len() && body_chars[idx] != del {
idx += 1;
}
let s1: String = body_chars[s1_start..idx].iter().collect();
let s1 = untok_and_escape(&s1, flag_p_escapes, tok_arg);
if is_left {
premul = Some(s1);
} else {
postmul = Some(s1);
}
if idx < body_chars.len() {
idx += 1; }
if idx < body_chars.len() && body_chars[idx] == del {
idx += 1; let s2_start = idx;
while idx < body_chars.len() && body_chars[idx] != del {
idx += 1;
}
let s2: String = body_chars[s2_start..idx].iter().collect();
let s2 = untok_and_escape(&s2, flag_p_escapes, tok_arg);
if is_left {
preone = Some(s2);
} else {
postone = Some(s2);
}
if idx < body_chars.len() {
idx += 1;
} }
}
continue; }
'o' => {
sort_active = true;
} 'O' => {
sort_backwards = true;
sort_active = true;
} 'i' => {
sort_case_insensitive = true;
sort_active = true;
} 'n' => {
sort_numeric = true;
sort_active = true;
} '-' => {
sort_signed = true;
sort_active = true;
} 'a' => {
sort_index_order = true;
sort_active = true;
} 'u' => {
unique = true;
} '_' => {
idx += 1;
if idx >= body_chars.len() {
zerr("bad substitution");
errflag_set_error();
return (String::new(), new_pos, vec![]);
}
let del = body_chars[idx];
idx += 1;
let inner_start = idx;
while idx < body_chars.len() && body_chars[idx] != del {
idx += 1;
}
if inner_start < idx {
zerr("bad substitution");
errflag_set_error();
return (String::new(), new_pos, vec![]);
}
if idx >= body_chars.len() {
zerr("bad substitution");
errflag_set_error();
return (String::new(), new_pos, vec![]);
}
idx += 1;
continue;
} '*' => {
sub_flags_bits |= SUB_EGLOB;
} 'I' => {
idx += 1; let mut digits = String::new(); while idx < body_chars.len() && body_chars[idx].is_ascii_digit()
{
digits.push(body_chars[idx]); idx += 1; } if let Ok(n) = digits.parse::<u32>() {
flnum = n; } continue; } 'M' => {
sub_flags_bits |= SUB_MATCH;
} 'R' => {
sub_flags_bits |= SUB_REST;
} 'B' => {
sub_flags_bits |= SUB_BIND;
} 'E' => {
sub_flags_bits |= SUB_EIND;
} 'N' => {
sub_flags_bits |= SUB_LEN;
} 'S' => {
sub_flags_bits |= SUB_SUBSTR;
} 'e' => {
flag_eval = true;
} 'Q' => {
flag_unquote = true;
} 'X' => {
flag_error = true;
} 'D' => {
flag_d_dir = true;
} 'V' => {
flag_visible = true;
} 'b' => {
flag_b_pattern = true;
} 'w' => {
flag_word_count = true;
} 'c' => {
flag_char_count = true;
} 'W' => {
flag_word_count_w = true;
} 'z' => {
flag_z_tokenize = true;
} 'Z' => {
flag_z_tokenize = true; idx += 1; if idx < body_chars.len() {
let del = body_chars[idx]; idx += 1; while idx < body_chars.len() && body_chars[idx] != del
{
let ch = body_chars[idx]; if ch == 'c' {
flag_z_keep_comments = true;
}
else if ch == 'C' {
flag_z_strip_comments = true;
}
else if ch == 'n' {
flag_z_newline_ws = true;
} idx += 1; } if idx < body_chars.len() {
idx += 1;
} } continue; } 'g' => {
idx += 1; flag_g_seen = true; let mut want_emacs = false; let mut want_octal = false; let mut want_ctrl = false; if idx < body_chars.len() {
let del = body_chars[idx]; idx += 1; while idx < body_chars.len() && body_chars[idx] != del
{
match body_chars[idx] {
'e' => want_emacs = true, 'o' => want_octal = true, 'c' => want_ctrl = true, _ => {
zerr("bad substitution");
errflag_set_error();
return (String::new(), new_pos, vec![]);
}
}
idx += 1; } if idx < body_chars.len() {
idx += 1;
} } flag_g_emacs |= want_emacs;
flag_g_octal |= want_octal;
flag_g_ctrl |= want_ctrl;
continue; } '~' => {
tok_arg = !tok_arg;
} 'm' => {
multi_width += 1;
} 'p' => {
flag_p_escapes = true;
} '%' => {
flag_pct_prompt += 1;
} 'f' => {
spsep = Some("\n".to_string());
} 'F' => {
sep = Some("\n".to_string());
} '0' => {
spsep = Some("\u{0}".to_string());
} 's' | 'j' => {
let is_split = fc == 's'; idx += 1; if idx >= body_chars.len() {
break;
}
let del = body_chars[idx]; idx += 1; let s_start = idx;
while idx < body_chars.len() && body_chars[idx] != del {
idx += 1;
}
let arg: String = body_chars[s_start..idx].iter().collect(); let arg = untok_and_escape(&arg, flag_p_escapes, tok_arg); if is_split {
spsep = Some(arg);
} else {
sep = Some(arg);
} if idx < body_chars.len() {
idx += 1;
} continue; }
_ => {
zerr("bad substitution");
errflag_set_error();
return (String::new(), new_pos, vec![]);
}
}
idx += 1;
}
flag_keys = (hkeys & SCANPM_WANTKEYS) != 0; flag_values = (hvals & SCANPM_WANTVALS) != 0; }
let mut force_split = false;
let mut suppress_split = false;
let mut length_op = false;
let mut chkset = false;
loop {
let c = match body_chars.get(idx).copied() {
Some(ch) => ch,
None => break,
};
if c == '^' {
if body_chars.get(idx + 1).copied() == Some('^') {
plan9 = false;
idx += 2;
} else {
plan9 = true;
idx += 1;
}
continue;
}
if c == '=' {
if body_chars.get(idx + 1).copied() == Some('=') {
suppress_split = true;
idx += 2;
} else {
force_split = true;
idx += 1;
}
continue;
}
if c == '#' {
let next = body_chars.get(idx + 1).copied();
let after_next = body_chars.get(idx + 2).copied();
let next_is_name_start = match next {
Some(ch) if ch.is_ascii_alphanumeric() => true,
Some(ch) if matches!(ch, '_' | '@' | '*' | '?' | '!' | '$' | '-' | '0') => {
true
}
Some(':') if after_next == Some('-') => true,
Some(ch) if ch == STRING || ch == Qstring => matches!(
body_chars.get(idx + 2).copied(),
Some(b) if b == Inbrace || b == '{' || b == Inpar || b == '('
),
Some('#') if after_next.is_none() => true,
_ => false,
};
if next_is_name_start {
length_op = true;
idx += 1;
continue;
}
}
if c == '~' {
if body_chars.get(idx + 1).copied() == Some('~') {
if !qt {
crate::ported::options::opt_state_set("globsubst", false);
}
idx += 2;
} else {
if !qt {
crate::ported::options::opt_state_set("globsubst", true);
}
idx += 1;
}
continue;
}
if c == '+' {
let nxt = body_chars.get(idx + 1).copied().unwrap_or('\0');
let aspar = flag_p_indirect;
let ok = nxt.is_ascii_alphanumeric()
|| nxt == '_'
|| matches!(nxt, '@' | '*' | '#' | '?')
|| (aspar
&& (nxt == STRING || nxt == Qstring)
&& matches!(
body_chars.get(idx + 2).copied(),
Some(b) if b == Inbrace || b == '{' || b == Inpar || b == '('
));
if ok {
chkset = true;
idx += 1;
continue;
}
zerr("bad substitution");
errflag_set_error();
return (String::new(), new_pos, vec![]);
}
if matches!(c, Snull | Dnull | STRING | Qstring) {
idx += 1;
continue;
}
break;
}
sub_flags_set(sub_flags_bits); let post_flags_start = idx;
let mut peeled_quotes = false; if idx + 1 < body_chars.len() && body_chars[idx] == '"' && body_chars[idx + 1] == '$'
{
let mut p = idx + 1; let mut paren_depth = 0_i32; let mut brace_depth = 0_i32; while p < body_chars.len() {
let ch = body_chars[p]; match ch {
'(' => paren_depth += 1, ')' => paren_depth -= 1, '{' => brace_depth += 1, '}' => brace_depth -= 1, '"' if paren_depth == 0 && brace_depth == 0 => {
idx += 1; peeled_quotes = true; let _ = p; break; } _ => {} } p += 1; } } let mut subexp_value: Option<String> = if idx < body_chars.len() && body_chars[idx] == '$'
{
let start = idx;
let mut p = idx + 1;
if p < body_chars.len() {
let nx = body_chars[p];
let (open, close) = match nx {
'{' => ('{', '}'),
'(' => ('(', ')'),
_ => ('\0', '\0'),
};
if open != '\0' {
let mut depth = 0_i32;
while p < body_chars.len() {
let ch = body_chars[p];
if ch == open {
depth += 1;
} else if ch == close {
depth -= 1;
if depth == 0 {
p += 1;
break;
}
}
p += 1;
}
} else {
p += 1;
while p < body_chars.len()
&& (body_chars[p].is_ascii_alphanumeric() || body_chars[p] == '_')
{
p += 1;
}
}
}
let inner: String = body_chars[start..p].iter().collect(); let expanded = if flag_at {
let (joined, arr_parts, isarr, _) = multsub(&inner, PREFORK_SPLIT);
if isarr && !arr_parts.is_empty() {
static SEQ: AtomicUsize = AtomicUsize::new(0);
let n = SEQ.fetch_add(1, Ordering::Relaxed);
let temp = format!("__subexp_arr_{}", n);
arrays_insert(temp.clone(), arr_parts);
subexp_array_temp = Some(temp.clone());
temp
} else {
joined
}
} else {
singsub(&inner) };
idx = p; if peeled_quotes && idx < body_chars.len() && body_chars[idx] == '"' {
idx += 1; } Some(expanded)
} else {
None
};
let name_start = idx;
while idx < body_chars.len() {
let bc = body_chars[idx];
let allowed = if idx == name_start {
bc.is_ascii_alphanumeric()
|| bc == '_'
|| bc == '@'
|| bc == '*'
|| bc == '#'
|| bc == '?'
|| bc == '0'
} else {
bc.is_ascii_alphanumeric() || bc == '_'
};
if allowed {
idx += 1;
if idx == name_start + 1
&& matches!(body_chars[name_start], '@' | '*' | '#' | '?' | '0')
{
break;
}
} else {
break;
}
}
let mut var_name: String = body_chars[name_start..idx].iter().collect();
if let Some(ref temp) = subexp_array_temp {
var_name = temp.clone();
subexp_value = None;
}
let mut subscript: Option<String> = None; if idx < body_chars.len() && body_chars[idx] == '[' {
idx += 1; let sub_start = idx;
let mut depth = 1_i32;
while idx < body_chars.len() && depth > 0 {
let bc = body_chars[idx];
if bc == '[' {
depth += 1;
}
else if bc == ']' {
depth -= 1;
if depth == 0 {
break;
}
}
idx += 1;
}
if idx > sub_start {
let raw_sub: String = body_chars[sub_start..idx].iter().collect();
subscript = Some(singsub(&raw_sub)); }
if idx < body_chars.len() {
idx += 1;
} }
let rest: String = body_chars[idx..].iter().collect();
if flag_p_indirect {
if let Some(sv) = subexp_value.clone() {
var_name = sv.trim().to_string(); subexp_value = None; } else {
let target = vars_get(&var_name) .or_else(|| arrays_get(&var_name).map(|a| a.join(" "))) .unwrap_or_default(); var_name = target; } }
let used_subexp = subexp_value.is_some();
let raw_value: String = if let Some(sv) = subexp_value {
sv } else if let Some(sub) = subscript.as_deref() {
if let Some(map) = assoc_get(&var_name) {
if let Some((flags, pat)) = (|s: &str| -> Option<(String, String)> {
let s = s.trim_start();
let rest = s.strip_prefix('(')?;
let close = rest.find(')')?;
let flags = rest[..close].to_string();
let pat = rest[close + 1..].to_string();
if flags
.chars()
.all(|c| matches!(c, 'I' | 'R' | 'i' | 'r' | 'k' | 'K' | 'n' | 'e' | 'b'))
{
Some((flags, pat))
} else {
None
}
})(sub)
{
let by_key = flags.contains('I') || flags.contains('i');
let return_all = flags.contains('I') || flags.contains('R');
let mut out: Vec<String> = Vec::new();
for (k, v) in map.iter() {
let hay = if by_key { k.as_str() } else { v.as_str() };
if crate::ported::pattern::patmatch(&pat, hay) {
out.push(if by_key { k.clone() } else { v.clone() });
if !return_all {
break;
}
}
}
out.join(" ")
} else {
map.get(sub).cloned().unwrap_or_default()
}
} else if let Some(arr) = arrays_get(&var_name) {
if sub == "*" || sub == "@" {
arr.join(" ")
} else if let Some((flags, pat)) = (|s: &str| -> Option<(String, String)> {
let s = s.trim_start();
let rest = s.strip_prefix('(')?;
let close = rest.find(')')?;
let flags = rest[..close].to_string();
let pat = rest[close + 1..].to_string();
if flags
.chars()
.all(|c| matches!(c, 'I' | 'R' | 'i' | 'r' | 'n' | 'e'))
{
Some((flags, pat))
} else {
None
}
})(sub)
{
let return_index = flags.contains('I') || flags.contains('i');
let return_all = flags.contains('I') || flags.contains('R');
let mut out: Vec<String> = Vec::new();
for (idx, elem) in arr.iter().enumerate() {
if crate::ported::pattern::patmatch(&pat, elem) {
if return_index {
out.push((idx + 1).to_string());
} else {
out.push(elem.clone());
}
if !return_all {
break;
}
}
}
if out.is_empty() && return_index {
(arr.len() + 1).to_string()
} else {
out.join(" ")
}
} else if let Ok(idx_n) = sub.parse::<i64>() {
let len = arr.len() as i64;
let i = if idx_n < 0 { len + idx_n } else { idx_n - 1 };
if i >= 0 && (i as usize) < arr.len() {
arr[i as usize].clone()
} else {
String::new()
}
} else if let Some((start_s, end_s)) = sub.split_once(',') {
let arr_clone = arr.clone();
let len = arr_clone.len() as i64;
let start_str = start_s.to_string();
let end_str = end_s.to_string();
let start: i64 = singsub(&start_str).parse().unwrap_or(1);
let end: i64 = singsub(&end_str).parse().unwrap_or(len);
let s = if start < 0 {
(len + start).max(0)
} else {
(start - 1).max(0)
} as usize;
let e = if end < 0 {
(len + end + 1).max(0)
} else {
end.min(len)
} as usize;
if s < arr_clone.len() && s < e {
arr_clone[s..e.min(arr_clone.len())].join(" ")
} else {
String::new()
}
} else {
String::new()
}
} else if let Some(magic_val) = {
let nul = std::ptr::null_mut();
let is_splice = sub == "@" || sub == "*";
let pm: Option<crate::ported::zsh_h::Param> = if is_splice {
None } else { match var_name.as_str() {
"aliases" => getpmralias(nul, sub), "galiases" => getpmgalias(nul, sub), "saliases" => getpmsalias(nul, sub), "dis_aliases" => getpmdisralias(nul, sub), "dis_galiases" => getpmdisgalias(nul, sub), "dis_saliases" => getpmdissalias(nul, sub), "builtins" => getpmbuiltin(nul, sub), "dis_builtins" => getpmdisbuiltin(nul, sub), "commands" => getpmcommand(nul, sub), "functions" => getpmfunction(nul, sub), "dis_functions" => getpmdisfunction(nul, sub), "functions_source" => getpmfunction_source(nul, sub), "dis_functions_source"=> getpmdisfunction_source(nul, sub), "nameddirs" => getpmnameddir(nul, sub), "userdirs" => getpmuserdir(nul, sub), "options" => getpmoption(nul, sub), "parameters" => getpmparameter(nul, sub), "history" => getpmhistory(nul, sub), "modules" => getpmmodule(nul, sub), "jobdirs" => getpmjobdir(nul, sub), "jobstates" => getpmjobstate(nul, sub), "jobtexts" => getpmjobtext(nul, sub), "usergroups" => getpmusergroups(nul, sub), _ => None,
}};
pm.and_then(|p| p.u_str).or_else(|| {
if is_splice {
splice_magic_assoc(&var_name)
} else {
None
}
})
} {
magic_val
} else {
let scalar = vars_get(&var_name).unwrap_or_default();
let s_chars: Vec<char> = scalar.chars().collect();
if let Some((flags, pat)) = (|s: &str| -> Option<(String, String)> {
let s = s.trim_start();
let rest = s.strip_prefix('(')?;
let close = rest.find(')')?;
let f = rest[..close].to_string();
let p = rest[close + 1..].to_string();
if f.chars()
.all(|c| matches!(c, 'I' | 'R' | 'i' | 'r' | 'n' | 'e' | 'b'))
{
Some((f, p))
} else {
None
}
})(sub)
{
let return_index = flags.contains('I') || flags.contains('i');
let want_last = flags.contains('I') || flags.contains('R');
let n = s_chars.len();
let mut found: Option<(usize, usize)> = None;
'outer: for start in 0..=n {
let lengths: Box<dyn Iterator<Item = usize>> = if want_last {
Box::new((1..=(n - start)).rev())
} else {
Box::new(1..=(n - start))
};
for len in lengths {
let cand: String = s_chars[start..start + len].iter().collect();
if crate::ported::pattern::patmatch(&pat, &cand) {
found = Some((start, start + len));
if !want_last {
break 'outer;
}
break;
}
}
}
if want_last {
for start in (0..=n).rev() {
for len in 1..=(n - start) {
let cand: String = s_chars[start..start + len].iter().collect();
if crate::ported::pattern::patmatch(&pat, &cand) {
found = Some((start, start + len));
break;
}
}
if found.is_some() && found.unwrap().0 >= start {
break;
}
}
}
match (found, return_index) {
(Some((s, _)), true) => (s + 1).to_string(),
(Some((s, e)), false) => s_chars[s..e].iter().collect(),
(None, true) => {
if flags.contains('i') {
(n + 1).to_string()
} else {
"0".to_string()
}
}
(None, false) => String::new(),
}
} else if let Ok(idx_n) = sub.parse::<i64>() {
let len = s_chars.len() as i64;
let i = if idx_n < 0 { len + idx_n } else { idx_n - 1 };
if i >= 0 && (i as usize) < s_chars.len() {
s_chars[i as usize].to_string()
} else {
String::new()
}
} else if let Some((lo, hi)) = sub.split_once(',') {
let lo: i64 = lo.trim().parse().unwrap_or(1);
let hi: i64 = hi.trim().parse().unwrap_or(s_chars.len() as i64);
let chars_arr: Vec<String> = s_chars.iter().map(|c| c.to_string()).collect();
crate::ported::params::getarrvalue(&chars_arr, lo, hi).concat()
} else {
String::new()
}
}
} else {
let is_special_name = (var_name.len() == 1
&& matches!(
var_name.chars().next().unwrap_or('\0'),
'#' | '?' | '!' | '$' | '*' | '@' | '-'
))
|| (!var_name.is_empty() && var_name.chars().all(|c| c.is_ascii_digit()));
exec_getsparam(&var_name)
.or_else(|| {
assoc_get(&var_name)
.map(|m| m.values().cloned().collect::<Vec<_>>().join(" "))
})
.or_else(|| {
if is_special_name {
crate::ported::params::lookup_special_var(&var_name)
} else {
None
}
})
.unwrap_or_default()
};
let is_set = if let Some(sub) = subscript.as_deref() {
used_subexp
|| assoc_get(&var_name)
.map(|m| m.contains_key(sub))
.unwrap_or(false)
|| arrays_get(&var_name).as_ref()
.map(|a| {
sub.parse::<i64>().ok().is_some_and(|i| {
let len = a.len() as i64;
let real = if i < 0 { len + i } else { i - 1 };
real >= 0 && (real as usize) < a.len()
})
})
.unwrap_or(false)
} else {
used_subexp
|| vars_contains(&var_name)
|| arrays_contains(&var_name)
|| assoc_contains(&var_name)
};
if chkset {
let set_str = if subscript.is_some() {
if !raw_value.is_empty() {
"1"
} else {
"0"
}
} else if is_set {
"1"
} else {
"0"
};
let prefix: String = chars[..start_pos].iter().collect();
let suffix: String = if new_pos < chars.len() {
chars[new_pos..].iter().collect()
} else {
String::new()
};
let full = format!("{}{}{}", prefix, set_str, suffix);
let new_pos_in_full = prefix.chars().count() + set_str.chars().count();
return (full.clone(), new_pos_in_full, vec![full]); }
if length_op {
let _ = post_flags_start;
let n = if let Some(arr) = arrays_get(&var_name) {
arr.len() } else if let Some(map) = assoc_get(&var_name) {
map.len() } else {
raw_value.chars().count() };
let n_str = n.to_string();
let prefix: String = chars[..start_pos].iter().collect();
let suffix: String = if new_pos < chars.len() {
chars[new_pos..].iter().collect()
} else {
String::new()
};
let full = format!("{}{}{}", prefix, n_str, suffix);
let new_pos_in_full = prefix.chars().count() + n_str.chars().count();
return (full.clone(), new_pos_in_full, vec![full]);
}
let mut value: String; if flag_keys && flag_values {
value = assoc_get(&var_name) .map(|m| {
let mut out: Vec<String> = Vec::with_capacity(m.len() * 2); for (k, v) in m {
out.push(k.clone()); out.push(v.clone()); } out.join(" ") }) .unwrap_or_default(); } else if flag_keys {
value = assoc_get(&var_name) .map(|m| m.keys().cloned().collect::<Vec<_>>().join(" ")) .or_else(|| {
match var_name.as_str() {
"aliases" => crate::ported::hashtable::aliastab_lock()
.read()
.ok()
.map(|t| {
let mut names: Vec<String> = t.iter()
.map(|(k, _)| k.clone())
.collect();
names.sort();
names.join(" ")
}),
"functions" | "dis_functions" =>
crate::ported::hashtable::shfunctab_lock()
.read()
.ok()
.map(|t| {
let mut names: Vec<String> = t.iter()
.map(|(k, _)| k.clone())
.collect();
names.sort();
names.join(" ")
}),
"commands" =>
crate::ported::hashtable::cmdnamtab_lock()
.read()
.ok()
.map(|t| {
let mut names: Vec<String> = t.iter()
.map(|(k, _)| k.clone())
.collect();
names.sort();
names.join(" ")
}),
_ => None, } }) .unwrap_or_default();
} else if flag_values {
value = assoc_get(&var_name) .map(|m| m.values().cloned().collect::<Vec<_>>().join(" ")) .unwrap_or_default();
} else if flag_at {
value = arrays_get(&var_name).as_ref()
.map(|a| a.join(" "))
.unwrap_or_else(|| raw_value.clone());
} else {
value = raw_value.clone();
}
if !plan9 && flag_at {
if let Some(ref a) = arrays_get(&var_name) {
if a.first().map_or(true, |s| s.is_empty()) {
value = String::new();
}
}
}
let mut split_parts: Option<Vec<String>> = None; if !rest.is_empty() {
let r = rest.as_str();
if let Some(pat) = r.strip_prefix(":#") {
let p = singsub(pat); let cur_sub_flags = sub_flags_get(); let invert = (cur_sub_flags & 0x0008) != 0; sub_flags_set(0); let has_subscript = subscript.is_some();
if let Some(arr) = arrays_get(&var_name)
.filter(|_| !has_subscript)
{
let kept: Vec<String> = arr
.into_iter() .filter(|elem| {
let m = crate::ported::pattern::patmatch(&p, elem); if invert {
m
} else {
!m
} }) .collect();
value = kept.join(" "); split_parts = Some(kept); } else {
let m = crate::ported::pattern::patmatch(&p, &raw_value); value = if invert {
if m {
raw_value.clone()
} else {
String::new()
} } else {
if m {
String::new()
} else {
raw_value.clone()
} }; } } else if let Some(default) = r.strip_prefix(":-") {
if !is_set || raw_value.is_empty() {
value = singsub(default);
}
} else if let Some(default) = r.strip_prefix('-') {
if !is_set {
value = singsub(default);
}
} else if let Some(default) = r.strip_prefix("::=") {
value = singsub(default);
if flag_arrasg == 1 {
let ifs = vars_get("IFS")
.unwrap_or_else(|| " \t\n".to_string());
let parts: Vec<String> = value
.split(|c: char| ifs.contains(c))
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
.collect();
exec_assignaparam(&var_name, parts);
} else if flag_arrasg == 2 {
let ifs = vars_get("IFS")
.unwrap_or_else(|| " \t\n".to_string());
let parts: Vec<String> = value
.split(|c: char| ifs.contains(c))
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
.collect();
exec_sethparam(&var_name, parts);
} else {
let __s = match subscript.as_deref() {
Some(k) => format!("{}[{}]", var_name, k),
None => var_name.clone(),
};
crate::ported::params::assignsparam(&__s, &value, 0);
exec_sync_state_from_paramtab();
}
} else if let Some(default) = r.strip_prefix(":=") {
if !is_set || raw_value.is_empty() {
value = singsub(default);
if flag_arrasg == 1 {
let ifs = vars_get("IFS")
.unwrap_or_else(|| " \t\n".to_string());
let parts: Vec<String> = value
.split(|c: char| ifs.contains(c))
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
.collect();
exec_assignaparam(&var_name, parts);
} else if flag_arrasg == 2 {
let ifs = vars_get("IFS")
.unwrap_or_else(|| " \t\n".to_string());
let parts: Vec<String> = value
.split(|c: char| ifs.contains(c))
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
.collect();
exec_sethparam(&var_name, parts);
} else {
let __s = match subscript.as_deref() {
Some(k) => format!("{}[{}]", var_name, k),
None => var_name.clone(),
};
crate::ported::params::assignsparam(&__s, &value, 0);
exec_sync_state_from_paramtab();
}
}
} else if let Some(default) = r.strip_prefix('=') {
if !is_set {
value = singsub(default);
if flag_arrasg == 1 {
let ifs = vars_get("IFS")
.unwrap_or_else(|| " \t\n".to_string());
let parts: Vec<String> = value
.split(|c: char| ifs.contains(c))
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
.collect();
exec_assignaparam(&var_name, parts);
} else if flag_arrasg == 2 {
let ifs = vars_get("IFS")
.unwrap_or_else(|| " \t\n".to_string());
let parts: Vec<String> = value
.split(|c: char| ifs.contains(c))
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
.collect();
exec_sethparam(&var_name, parts);
} else {
let __s = match subscript.as_deref() {
Some(k) => format!("{}[{}]", var_name, k),
None => var_name.clone(),
};
crate::ported::params::assignsparam(&__s, &value, 0);
exec_sync_state_from_paramtab();
}
}
} else if let Some(alt) = r.strip_prefix(":+") {
if is_set && !raw_value.is_empty() {
value = singsub(alt);
} else {
value = String::new();
}
} else if let Some(alt) = r.strip_prefix('+') {
if is_set {
value = singsub(alt);
} else {
value = String::new();
}
} else if let Some(msg) = r.strip_prefix(":?") {
if !is_set || raw_value.is_empty() {
let m = if msg.is_empty() {
"parameter null or not set".to_string() } else {
singsub(msg) }; zerr(&format!("{}: {}", var_name, m));
errflag_set_error();
}
} else if let Some(msg) = r.strip_prefix('?') {
if !is_set {
let m = if msg.is_empty() {
"parameter not set".to_string() } else {
singsub(msg) }; zerr(&format!("{}: {}", var_name, m));
errflag_set_error();
}
} else if let Some(rep) = r.strip_prefix(":/") {
let parts: Vec<&str> = rep.splitn(2, '/').collect();
let pat = singsub(parts[0]);
let repl = parts.get(1).map(|s| singsub(s)).unwrap_or_default();
if let Some(arr) = arrays_get(&var_name) {
let new_arr: Vec<String> = arr
.into_iter()
.map(|elem| {
if crate::ported::pattern::patmatch(&pat, &elem) {
repl.clone()
} else {
elem
}
})
.collect();
value = new_arr.join(" "); split_parts = Some(new_arr); } else if crate::ported::pattern::patmatch(&pat, &raw_value) {
value = repl; } else {
value = raw_value.clone(); }
} else if let Some(rep) = r.strip_prefix("//") {
let split_unescaped = |s: &str| -> (String, String) {
let cv: Vec<char> = s.chars().collect();
let mut pat_buf = String::new();
let mut i = 0;
while i < cv.len() {
let c = cv[i];
if (c == '\x00' || c == '\u{9f}') && i + 1 < cv.len() {
pat_buf.push('\\');
pat_buf.push(cv[i + 1]);
i += 2;
continue;
}
if c == '\\' && i + 1 < cv.len() && cv[i + 1] == '/' {
pat_buf.push(cv[i + 1]);
i += 2;
continue;
}
if c == '/' {
let rest: String = cv[i + 1..].iter().collect();
return (pat_buf, rest);
}
pat_buf.push(c);
i += 1;
}
(pat_buf, String::new())
};
let (raw_pat, raw_repl) = split_unescaped(rep);
let pat = singsub(&raw_pat);
let repl = {
let saved_skip = SKIP_FILESUB.with(|c| c.get());
SKIP_FILESUB.with(|c| c.set(true));
let s = crate::lex::untokenize(&singsub(&raw_repl));
SKIP_FILESUB.with(|c| c.set(saved_skip));
let mut out = String::with_capacity(s.len());
let mut it = s.chars().peekable();
while let Some(c) = it.next() {
if c == '\\' {
if let Some(&nx) = it.peek() {
if nx == '\\' {
out.push('\\');
it.next();
continue;
}
out.push(nx);
it.next();
continue;
}
}
out.push(c);
}
out
};
let replace_global = |val: &str| -> String {
let cv: Vec<char> = val.chars().collect();
let nn = cv.len();
let mut o = String::with_capacity(val.len());
let mut q = 0_usize;
while q < nn {
let mut m: Option<usize> = None;
for e in (q + 1..=nn).rev() {
let c: String = cv[q..e].iter().collect();
if crate::ported::pattern::patmatch(&pat, &c) {
m = Some(e);
break;
}
}
if let Some(e) = m {
o.push_str(&repl);
q = if e == q { q + 1 } else { e };
} else {
o.push(cv[q]);
q += 1;
}
}
o
};
let mut handled_array = false;
let has_scalar_subscript = subscript
.as_deref()
.map(|s| {
let t = s.trim();
t != "@" && t != "*" && !t.contains(',')
})
.unwrap_or(false);
let has_subscript = has_scalar_subscript;
if let Some(arr) = arrays_get(&var_name)
.filter(|_| !has_subscript)
{
let new_arr: Vec<String> = arr.iter().map(|e| replace_global(e)).collect();
value = new_arr.join(" "); split_parts = Some(new_arr); handled_array = true;
}
if handled_array {
let _ = handled_array;
} else {
let chars_v: Vec<char> = raw_value.chars().collect(); let n = chars_v.len(); let mut out = String::with_capacity(raw_value.len()); let mut p = 0_usize; while p < n {
let mut matched: Option<usize> = None; for end in (p + 1..=n).rev() {
let cand: String = chars_v[p..end].iter().collect(); if crate::ported::pattern::patmatch(&pat, &cand) {
matched = Some(end); break; } } if let Some(end) = matched {
out.push_str(&repl); p = if end == p { p + 1 } else { end }; } else {
out.push(chars_v[p]); p += 1; } } value = out; } } else if let Some(rep) = r.strip_prefix('/') {
let split_unescaped = |s: &str| -> (String, String) {
let cv: Vec<char> = s.chars().collect();
let mut pat_buf = String::with_capacity(s.len());
let mut i = 0;
while i < cv.len() {
let c = cv[i];
if (c == '\x00' || c == '\u{9f}' || c == '\\') && i + 1 < cv.len() {
if cv[i + 1] == '/' {
pat_buf.push('/');
i += 2;
continue;
}
pat_buf.push(c);
pat_buf.push(cv[i + 1]);
i += 2;
continue;
}
if c == '/' {
let rest: String = cv[i + 1..].iter().collect();
return (pat_buf, rest);
}
pat_buf.push(c);
i += 1;
}
(pat_buf, String::new())
};
let (raw_pat, raw_repl) = split_unescaped(rep);
let pat = singsub(&raw_pat);
let repl = {
let s = crate::lex::untokenize(&singsub(&raw_repl));
let mut out = String::with_capacity(s.len());
let mut it = s.chars().peekable();
while let Some(c) = it.next() {
if c == '\\' {
if let Some(&nx) = it.peek() {
if nx == '\\' {
out.push('\\');
it.next();
continue;
}
out.push(nx);
it.next();
continue;
}
}
out.push(c);
}
out
};
let replace_one = |val: &str| -> String {
if let Some(anchor_pat) = pat.strip_prefix('#') {
let cv: Vec<char> = val.chars().collect();
let nn = cv.len();
for end in (0..=nn).rev() {
let cand: String = cv[..end].iter().collect();
if crate::ported::pattern::patmatch(anchor_pat, &cand) {
return format!("{}{}", repl, cv[end..].iter().collect::<String>());
}
}
val.to_string()
} else if let Some(anchor_pat) = pat.strip_prefix('%') {
let cv: Vec<char> = val.chars().collect();
let nn = cv.len();
for start in 0..=nn {
let cand: String = cv[start..].iter().collect();
if crate::ported::pattern::patmatch(anchor_pat, &cand) {
return format!(
"{}{}",
cv[..start].iter().collect::<String>(),
repl
);
}
}
val.to_string()
} else {
let cv: Vec<char> = val.chars().collect();
let nn = cv.len();
for start in 0..nn {
for end in (start + 1..=nn).rev() {
let cand: String = cv[start..end].iter().collect();
if crate::ported::pattern::patmatch(&pat, &cand) {
let mut out = String::with_capacity(val.len());
out.extend(cv[..start].iter());
out.push_str(&repl);
out.extend(cv[end..].iter());
return out;
}
}
}
val.to_string()
}
};
let has_subscript_one = subscript
.as_deref()
.map(|s| {
let t = s.trim();
t != "@" && t != "*" && !t.contains(',')
})
.unwrap_or(false);
if let Some(arr) = arrays_get(&var_name)
.filter(|_| !has_subscript_one)
{
let new_arr: Vec<String> = arr.iter().map(|e| replace_one(e)).collect();
value = new_arr.join(" "); split_parts = Some(new_arr); } else {
value = replace_one(&raw_value); }
} else if let Some(pat) = r.strip_prefix("##") {
let p = singsub(pat);
let has_scalar_sub = subscript
.as_deref()
.map(|s| {
let t = s.trim();
t != "@" && t != "*" && !t.contains(',')
})
.unwrap_or(false);
let strip_one = |val: &str, op: u8| -> String {
let cv: Vec<char> = val.chars().collect();
let nn = cv.len();
match op {
1 => {
let mut k = nn;
loop {
let prefix: String = cv[..k].iter().collect();
if crate::ported::pattern::patmatch(&p, &prefix) {
return cv[k..].iter().collect();
}
if k == 0 {
break;
}
k -= 1;
}
val.to_string()
}
_ => val.to_string(),
}
};
if let Some(arr) = arrays_get(&var_name)
.filter(|_| !has_scalar_sub)
{
let new_arr: Vec<String> = arr.iter().map(|e| strip_one(e, 1)).collect();
value = new_arr.join(" "); split_parts = Some(new_arr); } else {
value = strip_one(&raw_value, 1); }
} else if let Some(pat) = r.strip_prefix('#') {
let p = singsub(pat);
let has_scalar_sub = subscript
.as_deref()
.map(|s| {
let t = s.trim();
t != "@" && t != "*" && !t.contains(',')
})
.unwrap_or(false);
let strip_one = |val: &str| -> String {
let cv: Vec<char> = val.chars().collect();
let total = cv.len();
for k in 0..=total {
let prefix: String = cv[..k].iter().collect();
if crate::ported::pattern::patmatch(&p, &prefix) {
return cv[k..].iter().collect();
}
}
val.to_string()
};
if let Some(arr) = arrays_get(&var_name)
.filter(|_| !has_scalar_sub)
{
let new_arr: Vec<String> = arr.iter().map(|e| strip_one(e)).collect();
value = new_arr.join(" "); split_parts = Some(new_arr); } else {
value = strip_one(&raw_value); }
} else if let Some(pat) = r.strip_prefix("%%") {
let p = singsub(pat);
let has_scalar_sub = subscript
.as_deref()
.map(|s| {
let t = s.trim();
t != "@" && t != "*" && !t.contains(',')
})
.unwrap_or(false);
let strip_one = |val: &str| -> String {
let cv: Vec<char> = val.chars().collect();
let total = cv.len();
let mut k = total;
loop {
let suffix: String = cv[total - k..].iter().collect();
if crate::ported::pattern::patmatch(&p, &suffix) {
return cv[..total - k].iter().collect();
}
if k == 0 {
break;
}
k -= 1;
}
val.to_string()
};
if let Some(arr) = arrays_get(&var_name)
.filter(|_| !has_scalar_sub)
{
let new_arr: Vec<String> = arr.iter().map(|e| strip_one(e)).collect();
value = new_arr.join(" "); split_parts = Some(new_arr); } else {
value = strip_one(&raw_value); }
} else if let Some(pat) = r.strip_prefix('%') {
let p = singsub(pat);
let has_scalar_sub = subscript
.as_deref()
.map(|s| {
let t = s.trim();
t != "@" && t != "*" && !t.contains(',')
})
.unwrap_or(false);
let strip_one = |val: &str| -> String {
let cv: Vec<char> = val.chars().collect();
let total = cv.len();
for k in 0..=total {
let suffix: String = cv[total - k..].iter().collect();
if crate::ported::pattern::patmatch(&p, &suffix) {
return cv[..total - k].iter().collect();
}
}
val.to_string()
};
if let Some(arr) = arrays_get(&var_name)
.filter(|_| !has_scalar_sub)
{
let new_arr: Vec<String> = arr.iter().map(|e| strip_one(e)).collect();
value = new_arr.join(" "); split_parts = Some(new_arr); } else {
value = strip_one(&raw_value); }
} else if let Some(rhs) = r.strip_prefix(":|") {
let arr = arrays_get(&var_name).unwrap_or_default();
let other_name = rhs.trim(); let other = arrays_get(other_name).unwrap_or_default();
let other_set: std::collections::HashSet<&String> = other.iter().collect();
let kept: Vec<String> = arr
.into_iter() .filter(|s| !other_set.contains(s)) .collect();
value = kept.join(" ");
split_parts = Some(kept); } else if let Some(rhs) = r.strip_prefix(":*") {
let arr = arrays_get(&var_name).unwrap_or_default();
let other_name = rhs.trim(); let other = arrays_get(other_name).unwrap_or_default();
let other_set: std::collections::HashSet<&String> = other.iter().collect();
let kept: Vec<String> = arr
.into_iter() .filter(|s| other_set.contains(s)) .collect();
value = kept.join(" ");
split_parts = Some(kept); } else if let Some(rhs) = r.strip_prefix(":^^") {
let arr = arrays_get(&var_name).unwrap_or_default();
let other = arrays_get(rhs.trim()).unwrap_or_default();
let n = arr.len().max(other.len());
let mut zipped: Vec<String> = Vec::with_capacity(n * 2);
for i in 0..n {
zipped.push(arr.get(i).cloned().unwrap_or_default());
zipped.push(other.get(i).cloned().unwrap_or_default());
}
value = zipped.join(" ");
split_parts = Some(zipped); } else if let Some(rhs) = r.strip_prefix(":^") {
let arr = arrays_get(&var_name).unwrap_or_default();
let other = arrays_get(rhs.trim()).unwrap_or_default();
let mut zipped: Vec<String> = Vec::with_capacity(arr.len() + other.len());
let n = arr.len().min(other.len());
for i in 0..n {
zipped.push(arr[i].clone());
zipped.push(other[i].clone());
}
value = zipped.join(" ");
split_parts = Some(zipped); } else if let Some(slice) = r.strip_prefix(':') {
let first = slice.chars().next().unwrap_or('\0');
let is_modifier = matches!(
first,
'h' | 't'
| 'r'
| 'e'
| 'l'
| 'u'
| 'q'
| 'Q'
| 'A'
| 'a'
| 'P'
| 'c'
| 's'
| 'S'
| '&'
| 'g'
| 'w'
| 'W'
);
if is_modifier {
let mod_str = format!(":{}", slice);
let mod_one =
|s: &str| -> String { modify(s, &mod_str) };
if let Some(parts) = split_parts.clone() {
let new_parts: Vec<String> =
parts.iter().map(|s| mod_one(s)).collect();
value = new_parts.join(" ");
split_parts = Some(new_parts);
} else if let Some(arr) = arrays_get(&var_name) {
let new_arr: Vec<String> = arr.iter().map(|s| mod_one(s)).collect();
value = new_arr.join(" ");
split_parts = Some(new_arr);
} else {
value = mod_one(&value);
}
} else {
let parts: Vec<&str> = slice.splitn(2, ':').collect();
let off = singsub(parts[0]).parse::<i64>().unwrap_or(0);
let array_source: Option<Vec<String>> = split_parts
.clone()
.or_else(|| arrays_get(&var_name));
if let Some(mut arr) = array_source {
if var_name == "@" || var_name == "*" || var_name == "argv" {
let s0 = vars_get("0").unwrap_or_default();
arr.insert(0, s0); }
let n = arr.len() as i64; let lo = if off < 0 {
(n + off).max(0)
} else {
off.min(n)
} as usize; let len = parts
.get(1) .map(|s| singsub(s).parse::<i64>().unwrap_or(0)); let kept: Vec<String> = match len {
Some(l) if l >= 0 => {
arr.iter().skip(lo).take(l as usize).cloned().collect()
} Some(l) => {
let end = ((n - lo as i64) + l).max(0) as usize; arr.iter().skip(lo).take(end).cloned().collect()
} None => arr.iter().skip(lo).cloned().collect(), };
value = kept.join(" "); split_parts = Some(kept); } else {
let total = raw_value.chars().count() as i64;
let start = if off < 0 {
(total + off).max(0)
} else {
off.min(total)
} as usize;
let len = parts
.get(1)
.map(|s| singsub(s).parse::<i64>().unwrap_or(0));
value = match len {
Some(l) if l >= 0 => {
raw_value.chars().skip(start).take(l as usize).collect()
}
Some(l) => {
let take = ((total - start as i64) + l).max(0) as usize;
raw_value.chars().skip(start).take(take).collect()
}
None => raw_value.chars().skip(start).collect(),
};
}
} }
}
if flag_typeinfo {
value = crate::ported::params::paramtab().read()
.ok()
.and_then(|tab| tab.get(&var_name).map(|pm| {
let f = pm.node.flags as u32;
let base = if f & PM_HASHED != 0 { "association" }
else if f & PM_ARRAY != 0 { "array" }
else if f & PM_INTEGER != 0 { "integer" }
else if f & (PM_EFLOAT | PM_FFLOAT) != 0 { "float" }
else { "scalar" };
let mut out = String::from(base);
if f & PM_LEFT != 0 { out.push_str("-left"); }
if f & PM_RIGHT_B != 0 { out.push_str("-right_blanks"); }
if f & PM_RIGHT_Z != 0 { out.push_str("-zero"); }
if f & PM_LOWER != 0 { out.push_str("-lower"); }
if f & PM_UPPER != 0 { out.push_str("-upper"); }
if f & PM_READONLY != 0{ out.push_str("-readonly"); }
if f & PM_TAGGED != 0 { out.push_str("-tag"); }
if f & PM_EXPORTED != 0{ out.push_str("-export"); }
if f & PM_UNIQUE != 0 { out.push_str("-unique"); }
if f & PM_HIDE != 0 { out.push_str("-hide"); }
if f & PM_HIDEVAL != 0 { out.push_str("-hideval"); }
out
}))
.unwrap_or_else(|| {
if assoc_contains(&var_name) {
"association".to_string() } else if arrays_contains(&var_name) {
"array".to_string() } else if matches!(
var_name.as_str(),
"aliases"
| "galiases"
| "saliases"
| "dis_aliases"
| "dis_galiases"
| "dis_saliases"
| "functions"
| "dis_functions"
| "builtins"
| "dis_builtins"
| "reswords"
| "dis_reswords"
| "options"
| "commands"
| "modules"
| "nameddirs"
| "userdirs"
| "jobtexts"
| "jobdirs"
| "jobstates"
| "parameters"
| "dirstack"
| "errnos"
| "sysparams"
| "mapfile"
) {
"association".to_string() } else if is_set {
"scalar".to_string()
} else {
String::new()
}
});
}
let cap_word = |s: &str| -> String {
let mut out = String::with_capacity(s.len());
let mut next_upper = true;
for c in s.chars() {
if c.is_whitespace() || matches!(c, '-' | '_' | '/' | '.' | ',') {
out.push(c);
next_upper = true;
} else if next_upper {
out.extend(c.to_uppercase());
next_upper = false;
} else {
out.extend(c.to_lowercase());
}
}
out
};
if flag_lower || flag_upper || flag_caps {
let transform = |s: &str| -> String {
if flag_lower {
s.to_lowercase()
} else if flag_upper {
s.to_uppercase()
} else {
cap_word(s)
}
};
if let Some(parts) = split_parts.clone() {
let new_parts: Vec<String> = parts.iter().map(|s| transform(s)).collect();
value = new_parts.join(" "); split_parts = Some(new_parts); } else if let Some(arr) = arrays_get(&var_name) {
let new_arr: Vec<String> = arr.iter().map(|s| transform(s)).collect();
value = new_arr.join(" "); split_parts = Some(new_arr); } else {
value = transform(&value); }
}
if sort_active || unique {
let parts: Vec<String> = if let Some(sp) = split_parts.clone() {
sp } else if let Some(arr) = arrays_get(&var_name) {
arr.clone() } else if let Some(map) = assoc_get(&var_name) {
map.values().cloned().collect() } else {
value.split_whitespace().map(String::from).collect() };
let mut sorted: Vec<String> = parts;
if unique {
let mut seen = std::collections::HashSet::new();
sorted.retain(|s| seen.insert(s.clone())); }
if sort_active {
if !sort_index_order {
if sort_numeric {
let _ = sort_signed; sorted.sort_by(|a, b| {
let na: f64 = a.parse().unwrap_or(0.0); let nb: f64 = b.parse().unwrap_or(0.0); na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal)
});
} else if sort_case_insensitive {
sorted.sort_by_key(|a| a.to_lowercase());
} else {
sorted.sort();
}
} if sort_backwards {
sorted.reverse();
} }
let join_with = sep.as_deref().unwrap_or(" ");
value = sorted.join(join_with);
split_parts = Some(sorted); }
if let Some(ref sp) = spsep {
let split_one = |s: &str| -> Vec<String> {
if sp.is_empty() {
s.chars().map(|c| c.to_string()).collect()
} else {
s.split(sp.as_str()).map(String::from).collect()
}
};
let parts: Vec<String> = if let Some(prev) = split_parts.clone() {
prev.iter().flat_map(|s| split_one(s)).collect()
} else if let Some(arr) = arrays_get(&var_name) {
arr.iter().flat_map(|s| split_one(s)).collect()
} else {
split_one(&value)
};
let join_with = sep.as_deref().unwrap_or(" "); value = parts.join(join_with);
split_parts = Some(parts); } else if let Some(ref sp) = sep {
if let Some(parts) = split_parts.clone() {
value = parts.join(sp); split_parts = None; } else if let Some(arr) = arrays_get(&var_name) {
value = arr.join(sp); } else if let Some(map) = assoc_get(&var_name) {
let vals: Vec<String> = map.values().cloned().collect();
value = vals.join(sp); } else if value.contains(' ') || value.contains('\n') {
let parts: Vec<&str> = value.split_whitespace().collect();
value = parts.join(sp);
}
}
if prenum > 0 || postnum > 0 {
let mul_default = " ".to_string(); let pad_one = |s: &str| -> String {
dopadding(
s,
prenum.max(0) as usize,
postnum.max(0) as usize,
preone.as_deref(),
postone.as_deref(),
premul.as_deref().unwrap_or(&mul_default),
postmul.as_deref().unwrap_or(&mul_default),
multi_width as i32, )
};
if let Some(parts) = split_parts.clone() {
let new_parts: Vec<String> = parts.iter().map(|s| pad_one(s)).collect();
value = new_parts.join(" ");
split_parts = Some(new_parts);
} else if let Some(arr) = arrays_get(&var_name) {
let new_arr: Vec<String> = arr.iter().map(|s| pad_one(s)).collect();
value = new_arr.join(" ");
split_parts = Some(new_arr);
} else {
value = pad_one(&value);
}
}
if flag_evalchar {
let eval_one = |s: &str| -> String { substevalchar(s.trim()).unwrap_or_default() };
if let Some(parts) = split_parts.clone() {
let new_parts: Vec<String> = parts.iter().map(|s| eval_one(s)).collect();
value = new_parts.join(" ");
split_parts = Some(new_parts);
} else if let Some(arr) = arrays_get(&var_name) {
let new_arr: Vec<String> = arr.iter().map(|s| eval_one(s)).collect();
value = new_arr.join(" ");
split_parts = Some(new_arr);
} else {
value = eval_one(&value);
}
}
if flag_eval {
if let Some(parts) = split_parts.clone() {
let new_parts: Vec<String> = parts.iter().map(|s| singsub(s)).collect();
value = new_parts.join(" ");
split_parts = Some(new_parts);
} else if let Some(arr) = arrays_get(&var_name) {
let new_arr: Vec<String> = arr.iter().map(|s| singsub(s)).collect();
value = new_arr.join(" ");
split_parts = Some(new_arr);
} else {
value = singsub(&value); }
}
if flag_pct_prompt > 0 {
let prompt_one = |s: &str| -> String {
let (expanded, _, _) = crate::ported::prompt::promptexpand(s, 0, None);
expanded
};
if let Some(parts) = split_parts.clone() {
let new_parts: Vec<String> = parts.iter().map(|s| prompt_one(s)).collect();
value = new_parts.join(" ");
split_parts = Some(new_parts);
} else if let Some(arr) = arrays_get(&var_name) {
let new_arr: Vec<String> = arr.iter().map(|s| prompt_one(s)).collect();
value = new_arr.join(" ");
split_parts = Some(new_arr);
} else {
value = prompt_one(&value); }
}
if flag_z_tokenize {
let mut words: Vec<String> = Vec::new(); let mut cur = String::new(); let mut in_sq = false; let mut in_dq = false; let mut in_comment = false; let chars_v: Vec<char> = value.chars().collect(); let push_word = |w: &mut String, words: &mut Vec<String>| {
if !w.is_empty() {
words.push(std::mem::take(w)); } }; let mut p = 0_usize; while p < chars_v.len() {
let ch = chars_v[p]; if in_comment {
if ch == '\n' {
in_comment = false; if flag_z_keep_comments {
cur.push(ch);
} } else if flag_z_keep_comments {
cur.push(ch); } p += 1; continue; } if in_sq {
cur.push(ch); if ch == '\'' {
in_sq = false;
} p += 1;
continue; } if in_dq {
cur.push(ch); if ch == '\\' && p + 1 < chars_v.len() {
p += 1; cur.push(chars_v[p]); } else if ch == '"' {
in_dq = false; } p += 1;
continue; } match ch {
'\\' if p + 1 < chars_v.len() => {
cur.push(ch); p += 1; cur.push(chars_v[p]); } '\'' => {
cur.push(ch);
in_sq = true;
} '"' => {
cur.push(ch);
in_dq = true;
} '#' if cur.is_empty() && !flag_z_strip_comments => {
in_comment = !flag_z_keep_comments; if flag_z_keep_comments {
cur.push(ch);
} } '#' if cur.is_empty() && flag_z_strip_comments => {
in_comment = true; } '\n' if flag_z_newline_ws => {
push_word(&mut cur, &mut words); } c if c.is_whitespace() => {
push_word(&mut cur, &mut words); } _ => cur.push(ch), } p += 1; } push_word(&mut cur, &mut words); value = words.join(" "); }
if flag_d_dir {
let home_opt = crate::ported::params::getsparam("HOME");
let mut named: Vec<(String, String)> = crate::ported::hashnameddir::nameddirtab()
.lock()
.map(|t| t.iter().map(|(k, nd)| (k.clone(), nd.dir.clone())).collect())
.unwrap_or_default();
named.sort_by(|a, b| b.1.len().cmp(&a.1.len()));
let dir_one = |s: &str| -> String {
for (name, path) in &named {
if !path.is_empty() && s.starts_with(path.as_str()) {
let r = &s[path.len()..];
if r.is_empty() || r.starts_with('/') {
return format!("~{}{}", name, r);
}
}
}
if let Some(ref h) = home_opt {
if !h.is_empty() && s.starts_with(h.as_str()) {
let r = &s[h.len()..]; if r.is_empty() || r.starts_with('/') {
return format!("~{}", r); } } } s.to_string() }; if let Some(parts) = split_parts.clone() {
let new_parts: Vec<String> = parts.iter().map(|s| dir_one(s)).collect();
value = new_parts.join(" ");
split_parts = Some(new_parts);
} else if let Some(arr) = arrays_get(&var_name) {
let new_arr: Vec<String> = arr.iter().map(|s| dir_one(s)).collect();
value = new_arr.join(" ");
split_parts = Some(new_arr);
} else {
value = dir_one(&value); }
}
let b_one = |s: &str| -> String {
let mut out = String::with_capacity(s.len() * 2);
for ch in s.chars() {
if matches!(
ch,
'*' | '?'
| '['
| ']'
| '('
| ')'
| '|'
| '^'
| '#'
| '~'
| '\\'
| '<'
| '>'
| '&'
| ';'
| '{'
| '}'
| '$'
| '`'
| '"'
| '\''
| ' '
| '\t'
| '\n'
) {
out.push('\\');
}
out.push(ch);
}
out
};
if flag_b_pattern {
if let Some(parts) = split_parts.clone() {
let new_parts: Vec<String> = parts.iter().map(|s| b_one(s)).collect();
value = new_parts.join(" ");
split_parts = Some(new_parts);
} else if let Some(arr) = arrays_get(&var_name) {
let new_arr: Vec<String> = arr.iter().map(|s| b_one(s)).collect();
value = new_arr.join(" ");
split_parts = Some(new_arr);
} else {
value = b_one(&value); } }
let unquote_one = |s: &str| -> String {
let chars_v: Vec<char> = s.chars().collect();
let mut out = String::with_capacity(s.len());
let mut i = 0_usize;
while i < chars_v.len() {
let c = chars_v[i];
if c == '$' && i + 1 < chars_v.len() && chars_v[i + 1] == '\'' {
let body_start = i + 2;
let mut j = body_start;
while j < chars_v.len() && chars_v[j] != '\'' {
if chars_v[j] == '\\' && j + 1 < chars_v.len() {
j += 2;
} else {
j += 1;
}
}
let body: String = chars_v[body_start..j].iter().collect();
let (decoded, _) = crate::ported::utils::getkeystring(&body); out.push_str(&decoded);
i = j + 1;
continue;
}
if c == '\\' {
if i + 1 < chars_v.len() {
out.push(chars_v[i + 1]);
i += 2;
continue;
}
} else if c == '\'' || c == '"' {
i += 1;
continue;
}
out.push(c);
i += 1;
}
out
};
if flag_unquote {
if let Some(parts) = split_parts.clone() {
let new_parts: Vec<String> = parts.iter().map(|s| unquote_one(s)).collect();
value = new_parts.join(" ");
split_parts = Some(new_parts);
} else if let Some(arr) = arrays_get(&var_name) {
let new_arr: Vec<String> = arr.iter().map(|s| unquote_one(s)).collect();
value = new_arr.join(" ");
split_parts = Some(new_arr);
} else {
value = unquote_one(&value);
}
}
if flag_error && value.is_empty() && !is_set {
zerr(&format!("{}: parameter not set or null", var_name)); errflag_set_error();
}
let visible_one = |s: &str| -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
let cp = c as u32;
if cp < 0x20 {
out.push('^');
out.push(((cp + b'@' as u32) as u8) as char);
} else if cp == 0x7f {
out.push_str("^?");
} else {
out.push(c);
}
}
out
};
if flag_visible {
if let Some(parts) = split_parts.clone() {
let new_parts: Vec<String> = parts.iter().map(|s| visible_one(s)).collect();
value = new_parts.join(" ");
split_parts = Some(new_parts);
} else if let Some(arr) = arrays_get(&var_name) {
let new_arr: Vec<String> = arr.iter().map(|s| visible_one(s)).collect();
value = new_arr.join(" ");
split_parts = Some(new_arr);
} else {
value = visible_one(&value);
}
}
if flag_char_count {
value = if multi_width > 0 {
value
.chars() .map(|c| wcpadwidth(c, multi_width as i32) as usize) .sum::<usize>() .to_string() } else {
value.chars().count().to_string() }; } else if flag_word_count {
value = value.split_whitespace().count().to_string(); } else if flag_word_count_w {
let parts: Vec<&str> = value.split(|c: char| c.is_whitespace()).collect();
value = parts.len().to_string(); }
let quote_one = |s: &str| -> String {
if flag_qmin {
let needs = s.chars().any(|c| {
c.is_whitespace()
|| matches!(
c,
'*' | '?'
| '['
| ']'
| '('
| ')'
| '|'
| '&'
| ';'
| '<'
| '>'
| '$'
| '`'
| '\\'
| '"'
| '\''
| '#'
| '~'
)
});
if needs {
crate::ported::utils::quotestring(s, crate::ported::zsh_h::QT_SINGLE)
} else {
s.to_string()
}
} else if flag_qplus {
crate::ported::utils::quotestring(s, crate::ported::zsh_h::QT_DOLLARS)
} else if flag_qcount > 0 {
match flag_qcount {
1 => crate::ported::utils::quotestring(
s,
crate::ported::zsh_h::QT_BACKSLASH,
),
2 => crate::ported::utils::quotestring(
s,
crate::ported::zsh_h::QT_SINGLE,
),
3 => crate::ported::utils::quotestring(
s,
crate::ported::zsh_h::QT_DOUBLE,
),
_ => crate::ported::utils::quotestring(
s,
crate::ported::zsh_h::QT_DOLLARS,
),
}
} else {
s.to_string()
}
};
if flag_qmin || flag_qplus || flag_qcount > 0 {
if let Some(parts) = split_parts.clone() {
let new_parts: Vec<String> = parts.iter().map(|s| quote_one(s)).collect();
value = new_parts.join(" ");
split_parts = Some(new_parts);
} else if let Some(arr) = arrays_get(&var_name) {
let new_arr: Vec<String> = arr.iter().map(|s| quote_one(s)).collect();
value = new_arr.join(" ");
split_parts = Some(new_arr);
} else {
value = quote_one(&value);
}
}
if flag_g_seen {
let _ = (flag_g_emacs, flag_g_octal, flag_g_ctrl); let decode_one = |s: &str| -> String { crate::ported::utils::getkeystring(s).0 };
if let Some(parts) = split_parts.clone() {
let new_parts: Vec<String> = parts.iter().map(|s| decode_one(s)).collect();
value = new_parts.join(" ");
split_parts = Some(new_parts);
} else if let Some(arr) = arrays_get(&var_name) {
let new_arr: Vec<String> = arr.iter().map(|s| decode_one(s)).collect();
value = new_arr.join(" ");
split_parts = Some(new_arr);
} else {
value = decode_one(&value);
}
}
if flag_d_dir || flag_visible {
let render_d = |s: &str| -> String {
if !flag_d_dir {
return s.to_string();
}
let mut out = s.to_string();
if let Some(home) = crate::ported::params::getsparam("HOME") {
if !home.is_empty() && out.starts_with(&home) {
out = format!("~{}", &out[home.len()..]);
}
}
if let Ok(t) = crate::ported::hashnameddir::nameddirtab().lock() {
let mut entries: Vec<(String, String)> = t.iter()
.map(|(k, nd)| (k.clone(), nd.dir.clone()))
.collect();
entries.sort_by_key(|(_, p)| std::cmp::Reverse(p.len()));
for (name, path) in &entries {
if !path.is_empty() && out.starts_with(path.as_str()) {
out = format!("~{}{}", name, &out[path.len()..]);
break;
}
}
}
out
};
let render_v = |s: &str| -> String {
if !flag_visible {
return s.to_string();
}
let mut out = String::with_capacity(s.len());
for ch in s.chars() {
let code = ch as u32;
if (0x20..=0x7e).contains(&code) {
out.push(ch);
} else if code == 0x7f {
out.push('^');
out.push('?');
} else if code == 0x0a {
out.push('\\');
out.push('n');
} else if code == 0x09 {
out.push('\\');
out.push('t');
} else if code < 0x20 {
out.push('^');
out.push((b'@' + (code as u8)) as char);
} else if code < 0x100 {
out.push_str("\\M-");
let stripped = code & 0x7f;
if (0x20..=0x7e).contains(&stripped) {
out.push(stripped as u8 as char);
} else if stripped < 0x20 {
out.push('^');
out.push((b'@' + (stripped as u8)) as char);
} else {
out.push('?');
}
} else {
out.push(ch);
}
}
out
};
let pipeline = |s: &str| -> String {
let s1 = render_d(s);
render_v(&s1)
};
if let Some(parts) = split_parts.clone() {
let new_parts: Vec<String> = parts.iter().map(|s| pipeline(s)).collect();
value = new_parts.join(" ");
split_parts = Some(new_parts);
} else if let Some(arr) = arrays_get(&var_name) {
let new_arr: Vec<String> = arr.iter().map(|s| pipeline(s)).collect();
value = new_arr.join(" ");
split_parts = Some(new_arr);
} else {
value = pipeline(&value);
}
}
let in_ssub = pf_flags & PREFORK_SINGLE != 0;
if force_split && !in_ssub && split_parts.is_none() {
let ifs = vars_get("IFS")
.unwrap_or_else(|| " \t\n".to_string());
let parts: Vec<String> = value
.split(|c: char| ifs.contains(c))
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
.collect();
if !parts.is_empty() {
value = parts.join(" ");
split_parts = Some(parts);
}
}
let _ = suppress_split;
let prefix: String = chars[..start_pos].iter().collect(); let suffix: String = if new_pos < chars.len() {
chars[new_pos..].iter().collect() } else {
String::new() };
let scripted_scalar = subscript
.as_deref() .map(|s| s != "@" && s != "*" && !s.contains(','))
.unwrap_or(false); let force_splat_from_eq = force_split
&& pf_flags & PREFORK_SINGLE == 0
&& rest.is_empty()
&& split_parts.is_some();
let auto_splat = force_splat_from_eq || (!flag_at && !qt && pf_flags & PREFORK_SINGLE == 0 && rest.is_empty() && !scripted_scalar && (arrays_contains(&var_name) || split_parts.is_some())); if flag_at || auto_splat {
let parts: Vec<String> = if let Some(sp) = split_parts.clone() {
sp } else if let Some(sub) = subscript.as_deref() {
if let Some((lo, hi)) = sub.split_once(',') {
let lo: i64 = lo.trim().parse().unwrap_or(1); let hi: i64 = hi.trim().parse().unwrap_or(0); arrays_get(&var_name).as_ref() .map(|arr| crate::ported::params::getarrvalue(arr, lo, hi))
.unwrap_or_default()
} else if let Some(arr) = arrays_get(&var_name) {
arr.clone() } else {
vec![value.clone()]
}
} else if let Some(arr) = arrays_get(&var_name) {
arr.clone() } else if let Some(map) = assoc_get(&var_name) {
if flag_keys && flag_values {
let mut out: Vec<String> = Vec::with_capacity(map.len() * 2); for (k, v) in map {
out.push(k.clone()); out.push(v.clone()); } out } else if flag_keys {
map.keys().cloned().collect()
} else if flag_values {
map.values().cloned().collect()
} else {
vec![value.clone()] }
} else {
vec![value.clone()] };
let mut nodes: Vec<String> = Vec::with_capacity(parts.len());
for (i, part) in parts.iter().enumerate() {
let s = if parts.len() == 1 {
format!("{}{}{}", prefix, part, suffix)
} else if i == 0 {
format!("{}{}", prefix, part)
} else if i == parts.len() - 1 {
format!("{}{}", part, suffix)
} else {
part.clone()
};
nodes.push(s);
}
let first = nodes.first().cloned().unwrap_or_default();
let new_pos_in_full = prefix.chars().count()
+ first.chars().count().saturating_sub(prefix.chars().count());
return (first, new_pos_in_full, nodes);
}
let full = format!("{}{}{}", prefix, value, suffix); let new_pos_in_full = prefix.chars().count() + value.chars().count();
return (full.clone(), new_pos_in_full, vec![full]);
}
if c.is_ascii_alphabetic() || c == '_' {
let var_start = pos; while pos < chars.len() && (chars[pos].is_ascii_alphanumeric() || chars[pos] == '_') {
pos += 1; } let var_name: String = chars[var_start..pos].iter().collect();
let mut subscript_str: Option<String> = None; if chars.get(pos).copied() == Some('[') {
let mut depth = 1; let mut q = pos + 1; while q < chars.len() && depth > 0 {
match chars[q] {
'[' => depth += 1, ']' => {
depth -= 1; if depth == 0 {
break; } } _ => {} } q += 1; } if depth == 0 {
let raw_sub: String = chars[pos + 1..q].iter().collect(); subscript_str = Some(singsub(&raw_sub)); pos = q + 1; } }
let value = if let Some(sub) = subscript_str.as_deref() {
if let Some(map) = assoc_get(&var_name) {
if let Some((flags, pat)) = (|s: &str| -> Option<(String, String)> {
let s = s.trim_start();
let rest = s.strip_prefix('(')?;
let close = rest.find(')')?;
let f = rest[..close].to_string();
let p = rest[close + 1..].to_string();
if f.chars()
.all(|c| matches!(c, 'I' | 'R' | 'i' | 'r' | 'k' | 'K' | 'n' | 'e' | 'b'))
{
Some((f, p))
} else {
None
}
})(sub)
{
let by_key = flags.contains('I') || flags.contains('i');
let return_all = flags.contains('I') || flags.contains('R');
let mut out: Vec<String> = Vec::new();
for (k, v) in map.iter() {
let hay = if by_key { k.as_str() } else { v.as_str() };
if crate::ported::pattern::patmatch(&pat, hay) {
out.push(if by_key { k.clone() } else { v.clone() });
if !return_all {
break;
}
}
}
out.join(" ")
} else {
map.get(sub).cloned().unwrap_or_default() }
} else if let Some(arr) = arrays_get(&var_name) {
if sub == "*" || sub == "@" {
arr.join(" ") } else if let Some((flags, pat)) = (|s: &str| -> Option<(String, String)> {
let s = s.trim_start();
let rest = s.strip_prefix('(')?;
let close = rest.find(')')?;
let f = rest[..close].to_string();
let p = rest[close + 1..].to_string();
if f.chars()
.all(|c| matches!(c, 'I' | 'R' | 'i' | 'r' | 'n' | 'e'))
{
Some((f, p))
} else {
None
}
})(sub)
{
let return_index = flags.contains('I') || flags.contains('i');
let return_all = flags.contains('I') || flags.contains('R');
let mut out: Vec<String> = Vec::new();
for (idx, elem) in arr.iter().enumerate() {
if crate::ported::pattern::patmatch(&pat, elem) {
if return_index {
out.push((idx + 1).to_string());
} else {
out.push(elem.clone());
}
if !return_all {
break;
}
}
}
if out.is_empty() && return_index {
(arr.len() + 1).to_string()
} else {
out.join(" ")
}
} else if let Some((lo, hi)) = sub.split_once(',') {
let lo: i64 = lo.trim().parse().unwrap_or(1); let hi: i64 = hi.trim().parse().unwrap_or(arr.len() as i64); crate::ported::params::getarrvalue(&arr, lo, hi).join(" ") } else if let Ok(idx) = sub.parse::<i32>() {
let n = arr.len() as i32; let i = if idx < 0 { n + idx } else { idx - 1 }; if i >= 0 && (i as usize) < arr.len() {
arr[i as usize].clone() } else {
String::new() } } else {
String::new() } } else if let Some(magic_val) = {
let nul = std::ptr::null_mut();
let pm: Option<crate::ported::zsh_h::Param> = if sub == "@" || sub == "*" {
None
} else { match var_name.as_str() {
"aliases" => getpmralias(nul, sub),
"galiases" => getpmgalias(nul, sub),
"saliases" => getpmsalias(nul, sub),
"dis_aliases" => getpmdisralias(nul, sub),
"dis_galiases" => getpmdisgalias(nul, sub),
"dis_saliases" => getpmdissalias(nul, sub),
"builtins" => getpmbuiltin(nul, sub),
"dis_builtins" => getpmdisbuiltin(nul, sub),
"commands" => getpmcommand(nul, sub),
"functions" => getpmfunction(nul, sub),
"dis_functions" => getpmdisfunction(nul, sub),
"functions_source" => getpmfunction_source(nul, sub),
"dis_functions_source"=> getpmdisfunction_source(nul, sub),
"nameddirs" => getpmnameddir(nul, sub),
"userdirs" => getpmuserdir(nul, sub),
"options" => getpmoption(nul, sub),
"parameters" => getpmparameter(nul, sub),
"history" => getpmhistory(nul, sub),
"modules" => getpmmodule(nul, sub),
"jobdirs" => getpmjobdir(nul, sub),
"jobstates" => getpmjobstate(nul, sub),
"jobtexts" => getpmjobtext(nul, sub),
"usergroups" => getpmusergroups(nul, sub),
_ => None,
}};
pm.and_then(|p| p.u_str).or_else(|| {
if sub == "@" || sub == "*" {
splice_magic_assoc(&var_name)
} else {
None
}
})
} {
magic_val
} else {
let s = vars_get(&var_name).unwrap_or_default(); let chars_v: Vec<char> = s.chars().collect(); if sub == "*" || sub == "@" {
s } else if let Some((lo, hi)) = sub.split_once(',') {
let lo: i64 = lo.trim().parse().unwrap_or(1); let hi: i64 = hi.trim().parse().unwrap_or(chars_v.len() as i64); let chars_arr: Vec<String> = chars_v.iter().map(|c| c.to_string()).collect(); crate::ported::params::getarrvalue(&chars_arr, lo, hi).concat()
} else if let Ok(idx) = sub.parse::<i32>() {
let n = chars_v.len() as i32; let i = if idx < 0 { n + idx } else { idx - 1 }; if i >= 0 && (i as usize) < chars_v.len() {
chars_v[i as usize].to_string() } else {
String::new() } } else {
String::new() } } } else {
exec_getsparam(&var_name)
.or_else(|| {
assoc_get(&var_name)
.map(|m| m.values().cloned().collect::<Vec<_>>().join(" "))
})
.unwrap_or_default() };
if pf_flags & PREFORK_SHWORDSPLIT != 0 && !qt {
let words = value
.split_whitespace()
.map(String::from)
.collect::<Vec<String>>(); if words.len() > 1 {
let prefix: String = chars[..start_pos].iter().collect(); let suffix: String = chars[pos..].iter().collect();
for (i, word) in words.iter().enumerate() {
if i == 0 {
result_nodes.push(format!("{}{}", prefix, word)); } else if i == words.len() - 1 {
result_nodes.push(format!("{}{}", word, suffix)); } else {
result_nodes.push(word.clone()); } } return (
result_nodes[0].clone(), prefix.len() + words[0].len(), result_nodes, ); } }
let splat_full = subscript_str.as_deref() == Some("@") || subscript_str.as_deref() == Some("*"); let splat_range = subscript_str
.as_deref()
.map(|s| s.contains(','))
.unwrap_or(false); let splat_assoc = (splat_full || splat_range) && assoc_contains(&var_name); if !qt && pf_flags & PREFORK_SINGLE == 0 && (subscript_str.is_none() || splat_full || splat_range) && (arrays_contains(&var_name) || splat_assoc)
{
let slice_arr: Option<Vec<String>> = if splat_range {
if let Some(sub) = subscript_str.as_deref() {
if let Some((lo, hi)) = sub.split_once(',') {
let lo: i64 = lo.trim().parse().unwrap_or(1); let hi: i64 = hi.trim().parse().unwrap_or(0); arrays_get(&var_name).as_ref()
.map(|arr| crate::ported::params::getarrvalue(arr, lo, hi))
} else {
None
}
} else {
None
}
} else {
None
};
let assoc_vals: Option<Vec<String>> = if splat_assoc {
assoc_get(&var_name) .map(|m| m.values().cloned().collect()) } else {
None
}; if let Some(arr) = slice_arr
.or(assoc_vals)
.or_else(|| arrays_get(&var_name))
{
let prefix: String = chars[..start_pos].iter().collect(); let suffix: String = chars[pos..].iter().collect(); let mut nodes: Vec<String> = Vec::with_capacity(arr.len()); for (i, part) in arr.iter().enumerate() {
let s = if arr.len() == 1 {
format!("{}{}{}", prefix, part, suffix) } else if i == 0 {
format!("{}{}", prefix, part) } else if i == arr.len() - 1 {
format!("{}{}", part, suffix) } else {
part.clone() }; nodes.push(s); } let first = nodes.first().cloned().unwrap_or_default(); return (first, prefix.len(), nodes); } }
let prefix: String = chars[..start_pos].iter().collect(); let suffix: String = chars[pos..].iter().collect(); let result = format!("{}{}{}", prefix, value, suffix); result_nodes.push(result.clone()); return (result, prefix.len() + value.len(), result_nodes); }
match c {
'?' => {
let value = vars_get("?") .unwrap_or_else(|| "0".to_string()); let prefix: String = chars[..start_pos].iter().collect(); let suffix: String = chars[pos + 1..].iter().collect(); let result = format!("{}{}{}", prefix, value, suffix); result_nodes.push(result.clone()); (result, prefix.len() + value.len(), result_nodes) } '$' => {
let value = std::process::id().to_string(); let prefix: String = chars[..start_pos].iter().collect(); let suffix: String = chars[pos + 1..].iter().collect(); let result = format!("{}{}{}", prefix, value, suffix); result_nodes.push(result.clone()); (result, prefix.len() + value.len(), result_nodes) } '#' => {
let value = arrays_get("@") .map(|a| a.len().to_string()) .unwrap_or_else(|| "0".to_string()); let prefix: String = chars[..start_pos].iter().collect(); let suffix: String = chars[pos + 1..].iter().collect(); let result = format!("{}{}{}", prefix, value, suffix); result_nodes.push(result.clone()); (result, prefix.len() + value.len(), result_nodes) } '*' | '@' => {
let values = arrays_get("@").unwrap_or_default(); let value = if c == '*' {
let join_sep = vars_get("IFS").as_ref()
.and_then(|s| s.chars().next())
.map(String::from)
.unwrap_or_else(|| " ".to_string());
values.join(&join_sep) } else {
if pf_flags & PREFORK_SINGLE == 0 {
let prefix: String = chars[..start_pos].iter().collect(); let suffix: String = chars[pos + 1..].iter().collect(); for (i, v) in values.iter().enumerate() {
if i == 0 {
result_nodes.push(format!("{}{}", prefix, v)); } else if i == values.len() - 1 {
result_nodes.push(format!("{}{}", v, suffix)); } else {
result_nodes.push(v.clone()); } } if result_nodes.is_empty() {
result_nodes.push(format!("{}{}", prefix, suffix)); } return (result_nodes[0].clone(), start_pos, result_nodes); } values.join(" ") }; let prefix: String = chars[..start_pos].iter().collect(); let suffix: String = chars[pos + 1..].iter().collect(); let result = format!("{}{}{}", prefix, value, suffix); result_nodes.push(result.clone()); (result, prefix.len() + value.len(), result_nodes) } '0'..='9' => {
let mut digit_str = String::from(c); let mut nx = pos + 1; while nx < chars.len() && chars[nx].is_ascii_digit() {
digit_str.push(chars[nx]); nx += 1; } let digit: usize = digit_str.parse().unwrap_or(0); let value = if digit == 0 {
vars_get("0").unwrap_or_default() } else {
arrays_get("@") .and_then(|a| a.get(digit.saturating_sub(1)).cloned()) .unwrap_or_default() }; let prefix: String = chars[..start_pos].iter().collect(); let suffix: String = chars[nx..].iter().collect(); let result = format!("{}{}{}", prefix, value, suffix); result_nodes.push(result.clone()); (result, prefix.len() + value.len(), result_nodes) } _ => {
result_nodes.push(s.to_string()); (s.to_string(), start_pos + 1, result_nodes) } } }
pub fn filesubstr(namptr: &str, assign: bool) -> Option<String> { if namptr.is_empty() {
return None; }
let chars: Vec<char> = namptr.chars().collect(); let first = chars[0];
if first == '~' || first == '\u{98}'
{
if chars.len() == 1 {
let home = crate::ported::params::getsparam("HOME").unwrap_or_default();
return Some(home);
}
let nx = chars[1]; if nx == '=' {
return None;
}
let isend = |c: char| -> bool {
c == '\0' || c == '/' || c == '\u{85}'
|| (assign && c == ':')
};
if isend(nx) {
let home = crate::ported::params::getsparam("HOME").unwrap_or_default();
let suffix: String = chars[1..].iter().collect();
return Some(format!("{}{}", home, suffix));
}
if nx == '+' && chars.len() >= 3 && isend(chars[2]) {
let pwd = crate::ported::params::getsparam("PWD").unwrap_or_default();
let suffix: String = chars[2..].iter().collect();
return Some(format!("{}{}", pwd, suffix));
}
if nx == '-' && chars.len() >= 3 && isend(chars[2]) {
let oldpwd = crate::ported::params::getsparam("OLDPWD")
.or_else(|| crate::ported::params::getsparam("PWD"))
.unwrap_or_default();
let suffix: String = chars[2..].iter().collect();
return Some(format!("{}{}", oldpwd, suffix));
}
if (nx == '+' || nx == '-' || nx.is_ascii_digit()) && !nx.is_whitespace() {
let mut p = 1_usize;
let neg = chars[p] == '-';
if chars[p] == '+' || chars[p] == '-' {
p += 1;
}
let dstart = p;
while p < chars.len() && chars[p].is_ascii_digit() {
p += 1;
}
if p > dstart && p < chars.len() && isend(chars[p]) {
let val: i32 = chars[dstart..p]
.iter()
.collect::<String>()
.parse()
.unwrap_or(0);
let val = if neg { -val } else { val };
let pwd = crate::ported::params::getsparam("PWD")
.unwrap_or_default();
let dirstack: Vec<String> = crate::ported::modules::parameter::DIRSTACK
.lock()
.map(|d| d.clone())
.unwrap_or_default();
let pushdminus = isset(crate::ported::zsh_h::PUSHDMINUS); let entry = dstackent(
if neg { '-' } else { '+' }, val, &dirstack, &pwd, pushdminus, );
if let Some(dir) = entry {
let suffix: String = chars[p..].iter().collect();
return Some(format!("{}{}", dir, suffix));
}
return None;
}
}
let mut p = 1_usize;
while p < chars.len() && (chars[p].is_ascii_alphanumeric() || chars[p] == '_') {
p += 1;
}
if p > 1 && p < chars.len() && isend(chars[p]) {
let user: String = chars[1..p].iter().collect();
let suffix: String = chars[p..].iter().collect();
let named = crate::ported::hashnameddir::nameddirtab()
.lock()
.ok()
.and_then(|t| t.get(&user).map(|nd| nd.dir.clone()));
if let Some(path) = named {
return Some(format!("{}{}", path, suffix));
}
if let Ok(cname) = CString::new(user.clone()) {
unsafe {
let pw = libc::getpwnam(cname.as_ptr());
if !pw.is_null() {
let home_ptr = (*pw).pw_dir;
if !home_ptr.is_null() {
let home = std::ffi::CStr::from_ptr(home_ptr)
.to_string_lossy()
.into_owned();
return Some(format!("{}{}", home, suffix));
}
}
}
}
return None;
}
return None;
}
if (first == '=' || first == '\u{86}') && chars.len() > 1 && chars[1] != '\u{85}'
{
let cmd_part: String = chars[1..].iter().collect();
let cmd = if assign {
cmd_part.split(':').next().unwrap_or(&cmd_part).to_string()
} else {
cmd_part.clone()
};
let path = crate::ported::params::getsparam("PATH").unwrap_or_default();
for dir in path.split(':') {
let full = format!("{}/{}", dir, cmd);
if std::path::Path::new(&full).exists() {
if assign && cmd_part.len() > cmd.len() {
let suffix = &cmd_part[cmd.len()..];
return Some(format!("{}{}", full, suffix));
}
return Some(full);
}
}
}
None
}
fn filesub(namptr: &str, assign: i32) -> String {
let mut namptr: String = filesubstr(namptr, assign != 0).unwrap_or_else(|| namptr.to_string());
if assign == 0 {
return namptr; }
let mut eql: Option<usize> = None;
if assign & PREFORK_TYPESET != 0 {
if namptr.len() >= 2 {
if let Some(sub) = namptr[1..].find('=').map(|p| p + 1) {
eql = Some(sub); let str_start = sub + 1; if str_start < namptr.len() && (namptr.as_bytes()[str_start] == b'~'
|| namptr.as_bytes()[str_start] == b'=')
{
let rhs = &namptr[str_start..]; if let Some(expanded) = filesubstr(rhs, true) {
namptr = format!("{}{}", &namptr[..str_start], expanded);
} } } else {
return namptr; } } else {
return namptr; } }
let mut ptr_off = 0_usize; loop {
let slice = &namptr[ptr_off..]; let colon_rel = match slice.find(':') {
Some(p) => p, None => break, }; let sub = ptr_off + colon_rel; let str_start = sub + 1; let len = sub; let past_eql = match eql {
Some(e) => sub > e, None => true, }; if past_eql && str_start < namptr.len() && (namptr.as_bytes()[str_start] == b'~'
|| namptr.as_bytes()[str_start] == b'=')
{
let rhs = &namptr[str_start..]; if let Some(expanded) = filesubstr(rhs, true) {
namptr = format!("{}{}", &namptr[..str_start], expanded); } } ptr_off = len + 1; if ptr_off >= namptr.len() {
break; } } namptr }
pub fn arithsubst(expr: &str, prefix: &str, rest: &str) -> String {
let expr = {
let bytes: Vec<char> = expr.chars().collect();
let mut out = String::with_capacity(expr.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i] == '$' && i + 1 < bytes.len() && bytes[i + 1] == '#' {
let name_start = i + 2;
let mut name_end = name_start;
while name_end < bytes.len()
&& (bytes[name_end].is_ascii_alphanumeric() || bytes[name_end] == '_')
{
name_end += 1;
}
if name_end > name_start {
let name: String = bytes[name_start..name_end].iter().collect();
let count = if let Some(arr) = arrays_get(&name) {
arr.len()
} else if let Some(assoc) = assoc_get(&name) {
assoc.len()
} else if name == "@" || name == "*" {
arrays_get("@").map(|a| a.len()).unwrap_or(0)
} else if let Some(s) = vars_get(&name) {
s.chars().count()
} else {
0
};
out.push_str(&count.to_string());
i = name_end;
continue;
}
}
out.push(bytes[i]);
i += 1;
}
out
};
let expanded = singsub(&expr);
let v = match crate::math::matheval(&expanded) { Ok(n) => n,
Err(_) => crate::math::mnumber { l: 0, d: 0.0, type_: crate::ported::zsh_h::MN_UNSET },
};
let outputunderscore: i32 = 0; let outputradix = vars_get("OUTPUT_RADIX").as_ref()
.and_then(|s| s.parse::<i32>().ok())
.unwrap_or(0); let b: String = if v.type_ == crate::ported::zsh_h::MN_UNSET {
"0".to_string() } else if (v.type_ == crate::ported::zsh_h::MN_FLOAT) && outputradix == 0 {
crate::ported::params::convfloat_underscore(v.d, outputunderscore)
} else {
let l = if (v.type_ == crate::ported::zsh_h::MN_FLOAT) {
v.d as i64
} else {
v.l
};
crate::ported::params::convbase_underscore(l, outputradix as u32, outputunderscore)
};
format!("{}{}{}", prefix, b, rest) }
use crate::ported::zsh_h::{MULTSUB_PARAM_NAME, MULTSUB_WS_AT_END, MULTSUB_WS_AT_START};
pub fn singsub(s: &str) -> String { let mut list = LinkList::default(); list.push_back(s.to_string()); let mut ret_flags = 0i32;
prefork(&mut list, PREFORK_SINGLE, &mut ret_flags);
if errflag_set() {
return String::new(); }
list.getdata(0).cloned().unwrap_or_default() }
pub fn multsub(s: &str, pf_flags: i32) -> (String, Vec<String>, bool, i32) { let mut ms_flags = 0i32; let mut x = s.to_string();
let ifs = vars_get("IFS")
.unwrap_or_else(|| " \t\n\0".to_string()); let is_ifs_sep = |c: char| -> bool {
ifs.contains(c) };
if pf_flags & PREFORK_SPLIT != 0 {
let leading: usize = x.chars().take_while(|&c| is_ifs_sep(c)).count(); if leading > 0 {
ms_flags |= MULTSUB_WS_AT_START; x = x.chars().skip(leading).collect(); }
}
let mut list = LinkList::default(); list.push_back(x.clone());
if pf_flags & PREFORK_SPLIT != 0 {
let chars: Vec<char> = x.chars().collect(); let mut nodes: Vec<String> = Vec::new(); let mut cur = String::new(); let mut inq = false; let mut inp = 0_i32; let mut i = 0_usize; while i < chars.len() {
let c = chars[i]; let is_token = matches!(c as u32, 0x80..=0x9F); if c == '\u{99}' || c == '\u{9a}' {
cur.push(c); i += 1; if i < chars.len() {
cur.push(chars[i]); i += 1; }
continue; }
match c { '\u{97}' | '\u{98}' | '\u{83}' => { inq = !inq; } '\u{85}' => { inp += 1; } '\u{86}' => { inp -= 1; } _ => {}
}
if !inq && inp == 0 && !is_token && is_ifs_sep(c) {
if !cur.is_empty() || nodes.is_empty() {
nodes.push(std::mem::take(&mut cur)); }
i += 1; while i < chars.len() && is_ifs_sep(chars[i]) {
i += 1; }
if i >= chars.len() {
ms_flags |= MULTSUB_WS_AT_END; break; }
continue; }
cur.push(c); i += 1; }
if !cur.is_empty() {
nodes.push(cur); }
list = LinkList::default(); for n in nodes {
list.push_back(n); }
}
let mut ret_flags = 0i32; prefork(&mut list, pf_flags, &mut ret_flags);
if errflag_set() {
return (String::new(), Vec::new(), false, ms_flags); }
let l = list.len(); if l > 1 || (list.flags & LF_ARRAY != 0) {
let arr: Vec<String> = list.iter().cloned().collect(); let join_sep = ifs.chars().next().map(String::from).unwrap_or_default(); let joined = arr.join(&join_sep); return (joined, arr, true, ms_flags); }
if l == 1 {
let result = list.getdata(0).cloned().unwrap_or_default(); return (result.clone(), vec![result], false, ms_flags); }
(String::new(), vec![String::new()], false, ms_flags) }
pub fn modify(s: &str, modifiers: &str) -> String { let mut result = s.to_string(); let mut chars: std::iter::Peekable<std::str::Chars> = modifiers.chars().peekable();
while chars.peek() == Some(&':') {
chars.next();
let mut gbal = false; let mut wall = false; let mut sep: Option<String> = None;
loop {
match chars.peek() {
Some(&'g') => {
gbal = true; chars.next(); } Some(&'w') => {
wall = true; chars.next(); } Some(&'W') => {
chars.next(); if chars.peek() == Some(&':') {
chars.next(); let collected: String = chars.by_ref().take_while(|&c| c != ':').collect(); sep = Some(collected); } } _ => break, } }
let modifier = match chars.next() {
Some(c) => c, None => break, };
let mut count: i32 = 0; if matches!(modifier, 'h' | 't') {
let mut count_str = String::new(); while let Some(&pc) = chars.peek() {
if pc.is_ascii_digit() {
count_str.push(pc);
chars.next();
} else {
break;
}
}
if !count_str.is_empty() {
count = count_str.parse().unwrap_or(1); }
}
if modifier == 's' || modifier == 'S' {
let delim = match chars.next() {
Some(c) => c, None => break, };
let mut pat = String::new(); while let Some(&c) = chars.peek() {
if c == delim {
chars.next();
break;
}
if c == '\\' {
chars.next();
if let Some(&nx) = chars.peek() {
pat.push(nx);
chars.next();
}
} else {
pat.push(c);
chars.next();
}
}
let mut repl = String::new(); while let Some(&c) = chars.peek() {
if c == delim {
chars.next();
break;
}
if c == '\\' {
chars.next();
if let Some(&nx) = chars.peek() {
repl.push(nx);
chars.next();
}
} else if c == '&' {
chars.next();
repl.push_str(&pat);
} else {
repl.push(c);
chars.next();
}
}
let (eff_pat, anchor_head, anchor_tail) = if modifier == 'S' {
if let Some(rest) = pat.strip_prefix('#') {
(rest.to_string(), true, false) } else if let Some(rest) = pat.strip_suffix('%') {
(rest.to_string(), false, true) } else {
(pat.clone(), false, false) }
} else {
(pat.clone(), false, false) };
let use_glob = modifier == 'S' || isset(crate::ported::zsh_h::HISTSUBSTPATTERN);
let do_match = |hay: &str| -> Option<(usize, usize)> {
if use_glob {
let cv: Vec<char> = hay.chars().collect();
let n = cv.len();
for start in 0..=n {
for end in start..=n {
let span: String = cv[start..end].iter().collect();
if crate::ported::pattern::patmatch(&eff_pat, &span) {
let bs: usize = cv[..start].iter().map(|c| c.len_utf8()).sum();
let be: usize =
bs + cv[start..end].iter().map(|c| c.len_utf8()).sum::<usize>();
return Some((bs, be));
}
}
}
None
} else {
hay.find(eff_pat.as_str()).map(|s| (s, s + eff_pat.len()))
}
};
result = if anchor_head {
if use_glob {
let cv: Vec<char> = result.chars().collect();
let n = cv.len();
let mut found: Option<usize> = None;
for end in 0..=n {
let span: String = cv[..end].iter().collect();
if crate::ported::pattern::patmatch(&eff_pat, &span) {
found = Some(cv[..end].iter().map(|c| c.len_utf8()).sum());
break;
}
}
if let Some(be) = found {
format!("{}{}", repl, &result[be..])
} else {
result
}
} else if result.starts_with(&eff_pat) {
format!("{}{}", repl, &result[eff_pat.len()..]) } else {
result
} } else if anchor_tail {
if use_glob {
let cv: Vec<char> = result.chars().collect();
let n = cv.len();
let mut found: Option<usize> = None;
for start in 0..=n {
let span: String = cv[start..].iter().collect();
if crate::ported::pattern::patmatch(&eff_pat, &span) {
found = Some(cv[..start].iter().map(|c| c.len_utf8()).sum());
break;
}
}
if let Some(bs) = found {
format!("{}{}", &result[..bs], repl)
} else {
result
}
} else if result.ends_with(&eff_pat) {
format!("{}{}", &result[..result.len() - eff_pat.len()], repl)
} else {
result
} } else if gbal {
if use_glob {
let mut out = String::with_capacity(result.len());
let mut rem = result.as_str();
while let Some((s, e)) = do_match(rem) {
out.push_str(&rem[..s]);
out.push_str(&repl);
if e == s {
let mut chars = rem[s..].char_indices();
chars.next();
let next_s = s + chars.next().map(|(b, _)| b).unwrap_or(rem.len() - s);
out.push_str(&rem[s..next_s]);
rem = &rem[next_s..];
} else {
rem = &rem[e..];
}
}
out.push_str(rem);
out
} else {
result.replace(eff_pat.as_str(), repl.as_str())
}
} else if use_glob {
if let Some((s, e)) = do_match(&result) {
format!("{}{}{}", &result[..s], repl, &result[e..])
} else {
result
}
} else {
result.replacen(eff_pat.as_str(), repl.as_str(), 1)
};
let mode: u8 = if modifier == 's' {
0
} else if anchor_head {
1
} else if anchor_tail {
2
} else {
3
};
*crate::ported::hist::hsubl.lock().unwrap() = Some(eff_pat.clone()); *crate::ported::hist::hsubr.lock().unwrap() = Some(repl.clone()); crate::ported::hist::hsubpatopt.store(mode as i32, std::sync::atomic::Ordering::Relaxed); if wall {
let separator = sep.as_deref().unwrap_or(" "); let words: Vec<&str> = result.split(separator).collect(); let modified: Vec<String> = words
.iter()
.map(|w| {
if gbal {
w.replace(pat.as_str(), repl.as_str())
}
else {
w.replacen(pat.as_str(), repl.as_str(), 1)
} })
.collect(); result = modified.join(separator); } continue; }
if modifier == '&' {
let last_subst = {
let p_opt = crate::ported::hist::hsubl.lock().unwrap().clone();
let r_opt = crate::ported::hist::hsubr.lock().unwrap().clone();
match (p_opt, r_opt) {
(Some(p), Some(r)) => {
let mode = crate::ported::hist::hsubpatopt.load(std::sync::atomic::Ordering::Relaxed) as u8;
Some((p, r, mode))
}
_ => None,
}
};
if let Some((p, r, mode)) = last_subst {
let apply = |w: &str| -> String {
match mode {
1 => {
if w.starts_with(p.as_str()) {
format!("{}{}", r, &w[p.len()..])
} else {
w.to_string()
}
}
2 => {
if w.ends_with(p.as_str()) {
format!("{}{}", &w[..w.len() - p.len()], r)
} else {
w.to_string()
}
}
_ => {
if gbal {
w.replace(p.as_str(), r.as_str())
} else {
w.replacen(p.as_str(), r.as_str(), 1)
}
}
}
};
if wall {
let separator = sep.as_deref().unwrap_or(" "); let words: Vec<&str> = result.split(separator).collect(); let modified: Vec<String> = words.iter().map(|w| apply(w)).collect();
result = modified.join(separator); } else {
result = apply(&result); } } continue; }
let dispatch = |w: &str| -> Option<String> {
match modifier {
'h' => Some(remtpath(w, count)), 't' => Some(remlpaths(w, count)), 'r' => Some(remtext(w)), 'e' => Some(rembutext(w)), 'l' => Some(casemodify(w, CASMOD_LOWER)), 'u' => Some(casemodify(w, CASMOD_UPPER)), 'q' => Some(crate::ported::utils::quotestring(
w,
crate::ported::zsh_h::QT_BACKSLASH,
)),
'Q' => {
let mut out = String::with_capacity(w.len());
let mut chs = w.chars().peekable();
while let Some(c) = chs.next() {
if c == '\\' {
if let Some(nc) = chs.next() {
out.push(nc);
}
} else if c == '\'' || c == '"' {
} else {
out.push(c);
}
}
Some(out)
}
'a' => xsymlinks(w).ok(), 'A' | 'P' => {
let canon = std::fs::canonicalize(w)
.ok()
.map(|p| p.to_string_lossy().into_owned());
if let Some(c) = canon {
Some(c)
} else {
let mut p = std::path::PathBuf::from(w);
let mut tail: Vec<std::ffi::OsString> = Vec::new();
let resolved_prefix = loop {
if let Ok(rp) = std::fs::canonicalize(&p) {
break Some(rp);
}
match (
p.parent().map(|x| x.to_path_buf()),
p.file_name().map(|x| x.to_os_string()),
) {
(Some(parent), Some(file)) if !parent.as_os_str().is_empty() => {
tail.push(file);
p = parent;
}
_ => break None,
}
};
if let Some(mut rp) = resolved_prefix {
for t in tail.into_iter().rev() {
rp.push(t);
}
Some(rp.to_string_lossy().into_owned())
} else {
xsymlinks(w).ok()
}
}
}
'c' => {
if w.starts_with('/') || w.starts_with("./") || w.starts_with("../") {
Some(w.to_string()) } else if let Some(path) = crate::ported::params::getsparam("PATH") {
let mut found = None;
for dir in path.split(':') {
let p = std::path::PathBuf::from(dir).join(w);
if p.is_file() {
found = Some(p.to_string_lossy().into_owned());
break;
}
}
Some(found.unwrap_or_else(|| w.to_string()))
} else {
Some(w.to_string())
}
}
_ => None, }
};
if wall {
let separator = sep.as_deref().unwrap_or(" "); let words: Vec<&str> = result.split(separator).collect();
let mut modified: Vec<String> = Vec::with_capacity(words.len());
for w in &words {
match dispatch(w) {
Some(m) => modified.push(m),
None => {
zerr(&format!("unrecognized modifier `{}'", modifier));
errflag_set_error();
return String::new();
}
}
}
result = modified.join(separator);
} else {
match dispatch(&result) {
Some(m) => result = m,
None => {
zerr(&format!("unrecognized modifier `{}'", modifier));
errflag_set_error();
return String::new();
}
}
}
}
result }
pub fn wcpadwidth(wc: char, multi_width: i32) -> i32 { let wcw = crate::ported::utils::zwcwidth(wc) as i32;
match multi_width {
0 => 1, 1 => {
if wcw >= 0 {
wcw
} else {
0
}
} _ => {
if wcw > 0 {
1
} else {
0
}
} }
}
pub fn subst_parse_str(sp: &str, single: bool, err: bool) -> Option<String> { let _ = err; let mut buf: String = sp.to_string();
if !single {
let mut chars: Vec<char> = buf.chars().collect(); let mut qt = false; for c in chars.iter_mut() {
if !qt {
if *c == '\u{82}'
{
*c = '\u{81}' ; } else if *c == '\u{84}'
{
*c = '\u{83}' ; }
}
if *c == '\u{97}'
{
qt = !qt; }
}
buf = chars.iter().collect(); }
Some(buf) }
pub fn dstackent( ch: char,
val: i32,
dirstack: &[String],
pwd: &str,
pushdminus_set: bool,
) -> Option<String> {
let backwards = ch == if pushdminus_set { '+' } else { '-' };
let mut val = val; if !backwards && val == 0 {
return Some(pwd.to_string()); }
if !backwards {
val -= 1;
}
let n = dirstack.len() as i32; let idx = if backwards {
let i = n - val; if i < 0 {
return None;
} i as usize } else {
if val < 0 || val >= n {
return None;
} val as usize };
dirstack.get(idx).cloned() }
pub fn dopadding( s: &str, prenum: usize, postnum: usize, preone: Option<&str>, postone: Option<&str>, premul: &str, postmul: &str, multi_width: i32, ) -> String {
let cells = |t: &str| -> usize {
if multi_width <= 0 {
t.chars().count() } else {
t.chars().map(|c| wcpadwidth(c, multi_width) as usize).sum() } };
let len = cells(s); let total_width = prenum + postnum;
if total_width == 0 || total_width == len {
return s.to_string(); }
let mut result = String::new();
if prenum > 0 {
let chars: Vec<char> = s.chars().collect();
if len > prenum {
let skip = len - prenum; result = chars.into_iter().skip(skip).collect(); } else {
let padding_needed = prenum - len;
if let Some(pre) = preone {
let pre_len = pre.chars().count(); if pre_len <= padding_needed {
let repeat_len = padding_needed - pre_len; if !premul.is_empty() {
let mul_len = premul.chars().count(); let full_repeats = repeat_len / mul_len; let partial = repeat_len % mul_len;
if partial > 0 {
result.extend(premul.chars().skip(mul_len - partial));
} for _ in 0..full_repeats {
result.push_str(premul); } } result.push_str(pre); } else {
result.extend(pre.chars().skip(pre_len - padding_needed)); } } else {
if !premul.is_empty() {
let mul_len = premul.chars().count(); let full_repeats = padding_needed / mul_len; let partial = padding_needed % mul_len;
if partial > 0 {
result.extend(premul.chars().skip(mul_len - partial)); } for _ in 0..full_repeats {
result.push_str(premul); } } }
result.push_str(s); } } else {
result = s.to_string(); }
if postnum > 0 {
let current_len = cells(&result);
if current_len > postnum {
result = result.chars().take(postnum).collect(); } else if current_len < postnum {
let padding_needed = postnum - current_len;
if let Some(post) = postone {
let post_len = post.chars().count(); if post_len <= padding_needed {
result.push_str(post); let remaining = padding_needed - post_len; if !postmul.is_empty() {
let mul_len = postmul.chars().count(); let full_repeats = remaining / mul_len; let partial = remaining % mul_len;
for _ in 0..full_repeats {
result.push_str(postmul); } if partial > 0 {
result.extend(postmul.chars().take(partial)); } } } else {
result.extend(post.chars().take(padding_needed)); } } else if !postmul.is_empty() {
let mul_len = postmul.chars().count(); let full_repeats = padding_needed / mul_len; let partial = padding_needed % mul_len;
for _ in 0..full_repeats {
result.push_str(postmul); } if partial > 0 {
result.extend(postmul.chars().take(partial)); } } } }
result }
pub fn get_strarg(s: &str) -> Option<(char, String, &str)> { let mut chars = s.chars().peekable();
let del = chars.next()?;
let close_del = match del {
'(' => ')', '[' => ']', '{' => '}', '<' => '>', Inpar => Outpar, Inbrack => Outbrack, Inbrace => Outbrace, Inang => Outang, _ => del, };
let mut content = String::new(); let mut rest_start = 1;
for (i, c) in s.chars().enumerate().skip(1) {
if c == close_del {
rest_start = i + 1; break; } content.push(c); rest_start = i + 1; }
let rest = &s[rest_start.min(s.len())..]; Some((del, content, rest)) }
pub fn get_intarg(s: &str) -> Option<(i64, &str)> { let (_del, content, rest) = get_strarg(s)?;
if rest.is_empty() && content.is_empty() {
return None;
}
let parsed = subst_parse_str(&content, false, true)?;
let mut __exec = crate::exec::ShellExecutor::new();
let _ctx = crate::fusevm_bridge::ExecutorContext::enter(&mut __exec);
let expanded = singsub(&parsed); if errflag_set() {
return None;
}
let ret = match crate::ported::math::mathevali(&expanded) {
Ok(n) => n, Err(_) => return None, };
let abs_ret = if ret < 0 { -ret } else { ret };
Some((abs_ret, rest)) }
pub fn quotesubst(str: &str) -> String { let mut result = str.to_string(); let mut pos = 0_usize;
loop {
let chars: Vec<char> = result.chars().collect(); if pos >= chars.len() {
break;
} if pos + 1 < chars.len() && chars[pos] == STRING && chars[pos + 1] == Snull
{
let (new_str, new_pos) = stringsubstquote(&result, pos); result = new_str; pos = new_pos; } else {
pos += 1; } }
result.replace('\u{0}', "") }
pub fn globlist(list: &mut LinkList, flags: i32) { let mut node_idx = 0;
while node_idx < list.nodes.len() && !errflag_set() {
let data = match list.getdata(node_idx) {
Some(d) => d.to_string(), None => {
node_idx += 1;
continue;
} };
if flags & PREFORK_KEY_VALUE != 0 && data.chars().next() == Some(Marker) {
node_idx += 3; continue; }
let no_untok = flags & PREFORK_NO_UNTOK != 0; let _ = no_untok; let expanded: Vec<String> = crate::ported::glob::glob_path(&data);
if expanded.is_empty() {
node_idx += 1;
} else if expanded.len() == 1 {
list.setdata(node_idx, expanded.into_iter().next().unwrap());
node_idx += 1;
} else {
list.delete_node(node_idx);
for (i, p) in expanded.iter().enumerate() {
if i == 0 {
list.insert_at(node_idx, p.clone());
} else {
list.insertlinknode(node_idx + i - 1, p.clone());
}
}
node_idx += expanded.len(); }
}
}
use crate::ported::zsh_h::{
SUB_ALL, SUB_BIND, SUB_DOSUBST, SUB_EGLOB, SUB_EIND, SUB_END, SUB_GLOBAL, SUB_LEN,
SUB_LIST, SUB_LONG, SUB_MATCH, SUB_REST, SUB_RETFAIL, SUB_START, SUB_SUBSTR,
};
pub fn strcatsub(prefix: &str, src: &str, suffix: &str, glob_subst: bool) -> String { if prefix.is_empty() && suffix.is_empty() {
if glob_subst {
}
return src.to_string(); }
let mut result = String::with_capacity(
prefix.len() + src.len() + suffix.len() + 1,
);
result.push_str(prefix); result.push_str(src); if glob_subst {
}
result.push_str(suffix); result }
#[cfg(test)] #[allow(non_snake_case)] mod tests {
use super::*;
#[test] fn test_getkeystring() {
assert_eq!(crate::ported::utils::getkeystring("hello").0, "hello"); assert_eq!(
crate::ported::utils::getkeystring("hello\\nworld").0,
"hello\nworld"
); assert_eq!(crate::ported::utils::getkeystring("\\t\\r\\n").0, "\t\r\n"); assert_eq!(crate::ported::utils::getkeystring("\\x41").0, "A"); assert_eq!(crate::ported::utils::getkeystring("\\u0041").0, "A"); }
#[test] fn test_simple_param_expansion() {
vars_insert("FOO".to_string(), "bar".to_string());
let (result, _, _) = paramsubst("$FOO", 0, false, 0, &mut 0); assert_eq!(result, "bar"); }
#[test] fn test_modify_head() {
let result = modify("/path/to/file.txt", ":h"); assert_eq!(result, "/path/to"); }
#[test] fn test_modify_tail() {
let result = modify("/path/to/file.txt", ":t"); assert_eq!(result, "file.txt"); }
#[test] fn test_modify_extension() {
let result = modify("/path/to/file.txt", ":e"); assert_eq!(result, "txt"); }
#[test] fn test_modify_root() {
let result = modify("/path/to/file.txt", ":r"); assert_eq!(result, "/path/to/file"); }
#[test] fn test_dopadding() {
assert_eq!(dopadding("hi", 5, 0, None, None, " ", " ", 0), " hi"); assert_eq!(dopadding("hi", 0, 5, None, None, " ", " ", 0), "hi "); let result = dopadding("hi", 3, 3, None, None, " ", " ", 0); assert!(result.len() >= 2, "result too short: {}", result); }
#[test] fn test_singsub() {
vars_insert("X".to_string(), "value".to_string()); let result = singsub("X"); assert!(!result.is_empty() || result.is_empty()); }
#[test] fn casemodify_lower_uppercases_via_lowercase() {
assert_eq!(casemodify("Hello World", CASMOD_LOWER), "hello world"); assert_eq!(casemodify("MIXED-Case_42", CASMOD_LOWER), "mixed-case_42"); assert_eq!(casemodify("", CASMOD_LOWER), ""); }
#[test] fn casemodify_upper_uppercases_each_char() {
assert_eq!(casemodify("Hello World", CASMOD_UPPER), "HELLO WORLD"); assert_eq!(casemodify("ünicode", CASMOD_UPPER), "ÜNICODE"); assert_eq!(casemodify("", CASMOD_UPPER), ""); }
#[test] fn casemodify_caps_titlecases_each_word() {
assert_eq!(casemodify("hello world", CASMOD_CAPS), "Hello World"); assert_eq!(casemodify("FOO Bar", CASMOD_CAPS), "Foo Bar"); }
#[test] fn casemodify_caps_treats_punctuation_as_word_boundary() {
assert_eq!(casemodify("a-b c.d", CASMOD_CAPS), "A-B C.D"); assert_eq!(casemodify("foo_bar.baz", CASMOD_CAPS), "Foo_Bar.Baz"); }
#[test] fn remtpath_count_zero_strips_last_component() {
assert_eq!(remtpath("/a/b/c", 0), "/a/b"); assert_eq!(remtpath("a/b/c", 0), "a/b"); assert_eq!(remtpath("foo", 0), "."); assert_eq!(remtpath("/foo", 0), "/"); assert_eq!(remtpath("/a/b/c/", 0), "/a/b"); assert_eq!(remtpath("/a/b//c//", 0), "/a/b"); }
#[test] fn remtpath_positive_count_keeps_n_components_from_front() {
assert_eq!(remtpath("/a/b/c", 1), "/"); assert_eq!(remtpath("/a/b/c", 2), "/a"); assert_eq!(remtpath("/a/b/c", 3), "/a/b"); assert_eq!(remtpath("a/b/c", 1), "a"); assert_eq!(remtpath("a/b/c", 2), "a/b"); }
#[test] fn remtpath_root_is_always_root() {
assert_eq!(remtpath("/", 0), "/"); assert_eq!(remtpath("///", 0), "/"); }
#[test] fn remlpaths_returns_last_n_components() {
assert_eq!(remlpaths("/a/b/c", 1), "c"); assert_eq!(remlpaths("/a/b/c", 2), "b/c"); assert_eq!(remlpaths("/a/b/c", 3), "a/b/c"); assert_eq!(remlpaths("a/b/c", 1), "c"); assert_eq!(remlpaths("a/b/c", 2), "b/c"); }
#[test] fn remtext_strips_extension() {
assert_eq!(remtext("file.txt"), "file"); assert_eq!(remtext("/path/to/file.txt"), "/path/to/file"); assert_eq!(remtext("file.tar.gz"), "file.tar"); assert_eq!(remtext("noext"), "noext"); assert_eq!(remtext("/path.with.dot/noext"), "/path.with.dot/noext"); }
#[test] fn rembutext_keeps_only_extension() {
assert_eq!(rembutext("file.txt"), "txt"); assert_eq!(rembutext("/path/to/file.rs"), "rs"); assert_eq!(rembutext("file.tar.gz"), "gz"); assert_eq!(rembutext("noext"), ""); assert_eq!(rembutext("/path.with.dot/noext"), ""); }
#[test] fn chabspath_collapses_dot_and_dotdot() {
assert_eq!(xsymlinks("/a/b/../c").unwrap(), "/a/c"); assert_eq!(xsymlinks("/a/./b/c").unwrap(), "/a/b/c"); assert_eq!(xsymlinks("/a/b/..").unwrap(), "/a"); }
#[test] fn getkeystring_decodes_basic_escapes() {
assert_eq!(crate::ported::utils::getkeystring("\\n").0, "\n"); assert_eq!(crate::ported::utils::getkeystring("\\t").0, "\t"); assert_eq!(crate::ported::utils::getkeystring("\\r").0, "\r"); assert_eq!(crate::ported::utils::getkeystring("\\\\").0, "\\"); assert_eq!(crate::ported::utils::getkeystring("plain").0, "plain"); }
#[test] fn getkeystring_decodes_hex_escape() {
assert_eq!(crate::ported::utils::getkeystring("\\x41").0, "A"); assert_eq!(crate::ported::utils::getkeystring("\\x7e").0, "~"); }
#[test] fn getkeystring_decodes_unicode_escape() {
assert_eq!(crate::ported::utils::getkeystring("\\u00e9").0, "é"); assert_eq!(crate::ported::utils::getkeystring("\\u4e2d").0, "中"); }
#[test] fn paramsubst_default_when_unset() {
crate::ported::utils::errflag
.store(0, std::sync::atomic::Ordering::Relaxed);
let (result, _, _) = paramsubst("${__default_unset_var:-fallback}", 0, false, 0, &mut 0); assert_eq!(result, "fallback"); }
#[test] fn paramsubst_assign_default_writes_indexed_array_slot() {
crate::ported::utils::errflag
.store(0, std::sync::atomic::Ordering::Relaxed);
let name = format!(
"__sub_arr_{}_{}",
module_path!().replace("::", "_"),
line!()
);
let mut exec = crate::exec::ShellExecutor::new();
let _ctx = crate::fusevm_bridge::ExecutorContext::enter(&mut exec);
arrays_insert(name.clone(), Vec::new()); let pat = format!("${{{}[3]:=val}}", name);
let (_result, _, _) = paramsubst(&pat, 0, false, 0, &mut 0); let arr = arrays_get(&name).unwrap(); assert_eq!(arr.len(), 3); assert_eq!(arr[2], "val"); assert_eq!(arr[0], ""); assert_eq!(arr[1], ""); }
#[test] fn paramsubst_alternative_when_unset() {
let (result, _, _) = paramsubst("${__alt_unset_var:+yes}", 0, false, 0, &mut 0); assert_eq!(result, ""); }
}
pub static NULSTRING_BYTES: [char; 2] = [Nularg, '\0'];
pub fn substevalchar(ptr: &str) -> Option<String> {
let ires = match crate::ported::math::mathevali(ptr) {
Ok(n) => n, Err(_) => {
return Some(String::new()); } }; if ires < 0 {
zerr("character not in range"); return Some(String::new()); }
if let Some(ch) = char::from_u32(ires as u32) {
let mut buf = [0u8; 4]; return Some(ch.encode_utf8(&mut buf).to_string()); }
let byte = (ires as u32 & 0xFF) as u8; Some(String::from_utf8_lossy(&[byte]).into_owned()) }
pub fn check_colon_subscript(s: &str) -> Option<(String, String)> {
if s.is_empty() || s.starts_with(|c: char| c.is_ascii_alphabetic()) || s.starts_with('&')
{
return None; }
if s.starts_with(':') {
return Some(("0".to_string(), s.to_string())); }
let chars: Vec<char> = s.chars().collect(); let mut depth: i32 = 0; let mut end: Option<usize> = None; for (i, &c) in chars.iter().enumerate() {
match c { '[' | '\u{91}' => depth += 1, ']' | '\u{92}' => depth -= 1, '(' | '\u{85}' => depth += 1, ')' | '\u{86}' => depth -= 1, ':' if depth == 0 => { end = Some(i); break; } _ => {}
}
}
let end = end.unwrap_or(s.len()); let expr: String = chars[..end].iter().collect();
let parsed = subst_parse_str(&expr, false, true)?; let expanded = singsub(&parsed); if errflag_set() {
return None;
} let stripped = expanded.replace('\u{0}', ""); let untoked = crate::lex::untokenize(&stripped);
let rest: String = chars[end..].iter().collect(); Some((untoked, rest)) }
pub fn untok_and_escape(s: &str, escapes: bool, tok_arg: bool) -> String {
let mut dst: Option<String> = None;
let chars: Vec<char> = s.chars().collect(); if escapes && chars.len() >= 2 && (chars[0] == STRING || chars[0] == Qstring)
{
let mut pend = 1_usize; while pend < chars.len() {
let c = chars[pend]; if !(c.is_ascii_alphanumeric() || c == '_') {
break; }
pend += 1; }
if pend == chars.len() {
let name: String = chars[1..].iter().collect(); dst = vars_get(&name); }
}
let result = match dst {
Some(d) => d, None => {
let untoked = crate::lex::untokenize(s); if escapes {
crate::ported::utils::getkeystring(&untoked).0 } else {
untoked }
}
};
if tok_arg {
}
result }
use crate::ported::zsh_h::Bnullkeep;
use crate::zsh_h::{isset, ALIAS_GLOBAL, ALIAS_SUFFIX, DISABLED, HASHED};
pub fn equalsubstr(s: &str, assign: bool, nomatch: bool) -> Option<String> {
let end = s .chars() .take_while(|&c| {
c != '\0' && c != Inpar && c != '\u{85}' && !(assign && c == ':') })
.count();
let cmdstr_raw: String = s.chars().take(end).collect(); let cmdstr = crate::lex::untokenize(&cmdstr_raw); let cmdstr = cmdstr.replace('\u{0}', "");
let cnam = crate::ported::builtin::findcmd(&cmdstr, 1, 0);
match cnam {
Some(path) => {
if end < s.chars().count() {
let rest: String = s.chars().skip(end).collect(); Some(format!("{}{}", path, rest)) } else {
Some(path) }
}
None => {
if nomatch {
zerr(&format!("{}: not found", cmdstr)); }
None }
}
}