use std::sync::Mutex;
use std::sync::atomic::{AtomicI32, AtomicI64};
use crate::ported::zle::comp_h::Cmatcher;
use crate::ported::zle::comp_h::{Cpattern, CPAT_CCLASS, CPAT_NCLASS, CPAT_EQUIV, CPAT_CHAR};
use crate::ported::zle::comp_h::CAF_MATSORT;
use crate::ported::zsh_h::{PM_TYPE, PM_SCALAR, PM_ARRAY, PM_HASHED};
use crate::ported::utils::zwarnnam;
use std::sync::atomic::Ordering;
use crate::ported::pattern::{patcompile, pattry};
#[allow(unused_imports)]
#[allow(unused_imports)]
use crate::ported::zle::zle_main::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_misc::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_hist::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_move::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_word::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_params::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_vi::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_utils::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_refresh::*;
#[allow(unused_imports)]
use crate::ported::zle::zle_tricky::*;
#[allow(unused_imports)]
use crate::ported::zle::textobjects::*;
#[allow(unused_imports)]
use crate::ported::zle::deltochar::*;
pub fn freecmlist(l: Option<Box<crate::ported::zle::comp_h::Cmlist>>) { let mut cur = l;
while let Some(node) = cur { cur = node.next; }
}
pub fn freecmatcher(m: Option<Box<crate::ported::zle::comp_h::Cmatcher>>) { let mut cur = m;
while let Some(node) = cur { cur = node.next; }
}
pub fn freecpattern(p: Option<Box<crate::ported::zle::comp_h::Cpattern>>) { let mut cur = p;
while let Some(node) = cur { cur = node.next; }
}
pub fn cpcmatcher(m: Option<&crate::ported::zle::comp_h::Cmatcher>) -> Option<Box<crate::ported::zle::comp_h::Cmatcher>> {
let mut head: Option<Box<Cmatcher>> = None; let mut tail_ref: *mut Option<Box<Cmatcher>> = &mut head;
let mut cur = m;
while let Some(src) = cur { let n = Box::new(Cmatcher { refc: 1, next: None, flags: src.flags, line: cpcpattern(src.line.as_deref()), llen: src.llen, word: cpcpattern(src.word.as_deref()), wlen: src.wlen, left: cpcpattern(src.left.as_deref()), lalen: src.lalen, right: cpcpattern(src.right.as_deref()), ralen: src.ralen, });
unsafe {
*tail_ref = Some(n);
if let Some(ref mut new_node) = *tail_ref { tail_ref = &mut new_node.next as *mut _;
}
}
cur = src.next.as_deref(); }
head }
pub fn cp_cpattern_element(o: &crate::ported::zle::comp_h::Cpattern) -> Box<crate::ported::zle::comp_h::Cpattern>
{
let mut n = Cpattern::default(); n.next = None; n.tp = o.tp; match o.tp { CPAT_CCLASS | CPAT_NCLASS | CPAT_EQUIV => { n.str = o.str.clone(); }
CPAT_CHAR => { n.chr = o.chr; }
_ => {} }
Box::new(n) }
pub fn cpcpattern(o: Option<&crate::ported::zle::comp_h::Cpattern>)
-> Option<Box<crate::ported::zle::comp_h::Cpattern>> {
let mut head: Option<Box<Cpattern>> = None; let mut tail_ref: *mut Option<Box<Cpattern>> = &mut head;
let mut cur = o;
while let Some(src) = cur { unsafe {
*tail_ref = Some(cp_cpattern_element(src)); if let Some(ref mut new_node) = *tail_ref { tail_ref = &mut new_node.next as *mut _;
}
}
cur = src.next.as_deref(); }
head }
pub static INCOMPFUNC: AtomicI32 = AtomicI32::new(0);
pub static COMPCURRENT: AtomicI32 = AtomicI32::new(0);
pub static COMPLISTMAX: AtomicI64 = AtomicI64::new(0);
pub static NMATCHES_GLOBAL: AtomicI64 = AtomicI64::new(0);
pub static COMPLISTLINES: AtomicI64 = AtomicI64::new(0);
pub static COMPIGNORED: AtomicI64 = AtomicI64::new(0);
macro_rules! comp_string_global {
($vis:vis $name:ident, $cname:literal, $cline:literal) => {
#[doc = concat!("Port of `char *", $cname, "` from complete.c:", stringify!($cline), ".")]
$vis static $name: std::sync::OnceLock<Mutex<String>> = std::sync::OnceLock::new();
};
}
comp_string_global!(pub COMPPREFIX, "compprefix", 47);
comp_string_global!(pub COMPSUFFIX, "compsuffix", 48);
comp_string_global!(pub COMPLASTPREFIX,"complastprefix",49);
comp_string_global!(pub COMPLASTSUFFIX,"complastsuffix",50);
comp_string_global!(pub COMPIPREFIX, "compiprefix", 58);
comp_string_global!(pub COMPISUFFIX, "compisuffix", 51);
comp_string_global!(pub COMPQIPREFIX, "compqiprefix", 52);
comp_string_global!(pub COMPQISUFFIX, "compqisuffix", 53);
comp_string_global!(pub COMPQUOTE, "compquote", 54);
comp_string_global!(pub COMPQSTACK, "compqstack", 55);
comp_string_global!(pub COMPLIST, "complist", 65);
comp_string_global!(pub COMPCONTEXT, "compcontext", 59);
comp_string_global!(pub COMPPARAMETER, "compparameter", 60);
comp_string_global!(pub COMPREDIRECT, "compredirect", 61);
pub static COMPWORDS: std::sync::OnceLock<Mutex<Vec<String>>> = std::sync::OnceLock::new();
fn lock_str(g: &'static std::sync::OnceLock<Mutex<String>>) -> &'static Mutex<String> {
g.get_or_init(|| Mutex::new(String::new()))
}
fn lock_vec(g: &'static std::sync::OnceLock<Mutex<Vec<String>>>) -> &'static Mutex<Vec<String>> {
g.get_or_init(|| Mutex::new(Vec::new()))
}
pub fn ignore_prefix(l: i32) { if l > 0 { let mut prefix = lock_str(&COMPPREFIX).lock().unwrap();
let pl = prefix.len() as i32; let take = l.min(pl) as usize; let head: String = prefix[..take].to_string(); let tail: String = prefix[take..].to_string(); let mut iprefix = lock_str(&COMPIPREFIX).lock().unwrap();
iprefix.push_str(&head); *prefix = tail; }
}
pub fn ignore_suffix(l: i32) { if l > 0 { let mut suffix = lock_str(&COMPSUFFIX).lock().unwrap();
let sl = suffix.len() as i32; let mut split = sl - l; if split < 0 { split = 0; } let split = split as usize;
let head: String = suffix[..split].to_string(); let tail: String = suffix[split..].to_string(); let mut isuffix = lock_str(&COMPISUFFIX).lock().unwrap();
let mut new_isuffix = tail; new_isuffix.push_str(&isuffix);
*isuffix = new_isuffix;
*suffix = head; }
}
pub fn restrict_range(b: i32, e: i32) { let mut words = lock_vec(&COMPWORDS).lock().unwrap();
let wl = words.len() as i32 - 1; if wl > 0 && b >= 0 && e >= 0 && (b > 0 || e < wl) { let mut e = e;
if e > wl { e = wl; } let count = (e - b + 1) as usize; let new_words: Vec<String> = words.iter() .skip(b as usize).take(count).cloned().collect();
*words = new_words; let cur = COMPCURRENT.load(std::sync::atomic::Ordering::Relaxed);
COMPCURRENT.store(cur - b, std::sync::atomic::Ordering::Relaxed); }
}
pub fn comp_check() -> i32 { if INCOMPFUNC.load(std::sync::atomic::Ordering::Relaxed) != 1 { crate::ported::utils::zerr( "condition can only be used in completion function");
return 0; }
1 }
#[allow(unused_variables)]
pub fn get_compstate(pm: *mut crate::ported::zsh_h::param) -> Option<usize> { None }
#[allow(unused_variables)]
pub fn get_nmatches(pm: *mut crate::ported::zsh_h::param) -> i64 { NMATCHES_GLOBAL.load(std::sync::atomic::Ordering::Relaxed) }
#[allow(unused_variables)]
pub fn get_listlines(pm: *mut crate::ported::zsh_h::param) -> i64 { let _ = crate::ported::zle::compresult::calclist(0);
let listdat = crate::ported::zle::compcore::listdat
.get()
.and_then(|m| m.lock().ok().map(|g| g.clone()));
if let Some(ld) = listdat {
return ld.nlines as i64;
}
COMPLISTLINES.load(std::sync::atomic::Ordering::Relaxed)
}
#[allow(unused_variables)]
pub fn set_complist(pm: *mut crate::ported::zsh_h::param, v: &str) { if let Ok(mut s) = lock_str(&COMPLIST).lock() {
*s = v.to_string(); }
}
#[allow(unused_variables)]
pub fn get_complist(pm: *mut crate::ported::zsh_h::param) -> String { lock_str(&COMPLIST).lock().map(|s| s.clone()).unwrap_or_default() }
#[allow(unused_variables)]
pub fn get_unambig(pm: *mut crate::ported::zsh_h::param) -> String { use std::sync::atomic::Ordering;
use crate::ported::zle::comp_h::CMF_HIDE;
let groups = crate::ported::zle::compcore::amatches
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock().ok().map(|g| g.clone()).unwrap_or_default();
let mut strs: Vec<String> = Vec::new();
for g in &groups {
for m in &g.matches {
if (m.flags & CMF_HIDE) != 0 { continue; }
if let Some(s) = m.str.as_deref() {
strs.push(s.to_string());
}
}
}
let _ = Ordering::Relaxed;
crate::ported::zle::compresult::unambig_data(&strs)
}
#[allow(unused_variables)]
pub fn get_unambig_curs(pm: *mut crate::ported::zsh_h::param) -> i64 { let prefix = get_unambig(std::ptr::null_mut());
prefix.chars().count() as i64
}
#[allow(unused_variables)]
pub fn get_unambig_pos(pm: *mut crate::ported::zsh_h::param) -> String { use crate::ported::zle::comp_h::CMF_HIDE;
let groups = crate::ported::zle::compcore::amatches
.get_or_init(|| std::sync::Mutex::new(Vec::new()))
.lock().ok().map(|g| g.clone()).unwrap_or_default();
let mut strs: Vec<String> = Vec::new();
for g in &groups {
for m in &g.matches {
if (m.flags & CMF_HIDE) != 0 { continue; }
if let Some(s) = m.str.as_deref() {
strs.push(s.to_string());
}
}
}
if strs.len() < 2 {
return String::new();
}
let lcp = crate::ported::zle::compresult::unambig_data(&strs);
let any_longer = strs.iter().any(|s| s.chars().count() > lcp.chars().count());
if any_longer {
format!("{}", lcp.chars().count())
} else {
String::new()
}
}
#[allow(unused_variables)]
pub fn get_insert_pos(pm: *mut crate::ported::zsh_h::param) -> String { get_unambig_pos(std::ptr::null_mut())
}
#[allow(unused_variables)]
pub fn get_compqstack(pm: *mut crate::ported::zsh_h::param) -> String { let stack = lock_str(&COMPQSTACK).lock()
.map(|s| s.clone()).unwrap_or_default();
if stack.is_empty() {
return String::new();
}
let mut out = String::with_capacity(stack.len());
for cqp in stack.chars() {
let cqp_byte = cqp as i32;
let s = crate::ported::zle::compcore::comp_quoting_string(cqp_byte);
if let Some(first) = s.chars().next() {
out.push(first);
}
}
out
}
#[allow(unused_variables)]
pub fn cond_psfix(a: &[String], id: i32) -> i32 { if comp_check() != 0 { let _ = a;
return 0;
}
0 }
pub const COMPSTATENAME: &str = "compstate";
pub const CVT_RANGENUM: i32 = 0; pub const CVT_RANGEPAT: i32 = 1; pub const CVT_PRENUM: i32 = 2; pub const CVT_PREPAT: i32 = 3; pub const CVT_SUFNUM: i32 = 4; pub const CVT_SUFPAT: i32 = 5;
#[allow(non_snake_case)]
struct OrderOpt { name: &'static str, abbrev: usize, oflag: i32 }
static ORDEROPTS: &[OrderOpt] = &[ OrderOpt { name: "nosort", abbrev: 2,
oflag: crate::ported::zle::comp_h::CAF_NOSORT }, OrderOpt { name: "match", abbrev: 3,
oflag: crate::ported::zle::comp_h::CAF_MATSORT }, OrderOpt { name: "numeric", abbrev: 3,
oflag: crate::ported::zle::comp_h::CAF_NUMSORT }, OrderOpt { name: "reverse", abbrev: 3,
oflag: crate::ported::zle::comp_h::CAF_REVSORT }, ];
pub fn parse_ordering(arg: &str, flags: &mut Option<i32>) -> i32 { let mut fl = 0i32; for opt_token in arg.split(',') { let mut found = false; for o in ORDEROPTS.iter().rev() { if opt_token.len() >= o.abbrev && o.name.starts_with(opt_token)
{
fl |= o.oflag; found = true;
break;
}
}
if !found { if let Some(ref mut f) = flags { *f = CAF_MATSORT; }
return -1; }
}
if let Some(ref mut f) = flags { *f |= fl; }
0 }
#[allow(unused_variables)]
pub fn addcompparams(cp: &[compparam], pp: &mut Vec<*mut crate::ported::zsh_h::param>) { for entry in cp {
let _ = entry.name;
}
}
#[allow(non_camel_case_types)]
pub struct compparam { pub name: &'static str, pub r#type: i32, pub var: usize, pub gsu: usize, }
pub fn makecompparams() { }
pub fn compunsetfn(pm: *mut crate::ported::zsh_h::param, exp: i32) { if pm.is_null() { return; }
if exp != 0 { match PM_TYPE(unsafe { (*pm).node.flags } as u32) {
PM_SCALAR => unsafe { (*pm).u_str = Some(String::new()); }, PM_ARRAY => unsafe { (*pm).u_arr = Some(Vec::new()); }, PM_HASHED => unsafe { (*pm).u_hash = None; }, _ => {}
}
} else if PM_TYPE(unsafe { (*pm).node.flags } as u32) == PM_HASHED { unsafe { (*pm).u_hash = None; } }
}
#[allow(unused_variables)]
pub fn comp_setunset(rset: i32, runset: i32, kset: i32, kunset: i32) { }
pub fn comp_wrapper(_prog: *const crate::ported::zsh_h::eprog, _w: *const crate::ported::zsh_h::funcwrap,
_name: &str) -> i32 {
if INCOMPFUNC.load(std::sync::atomic::Ordering::Relaxed) != 1 { return 1; }
0 }
pub fn cond_range(a: &[String], id: i32) -> i32 { let _ = (a, id); 0 }
pub fn bin_compadd(name: &str, argv: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
if INCOMPFUNC.load(std::sync::atomic::Ordering::Relaxed) != 1 { zwarnnam(name, "can only be called from completion function"); return 1; }
let mut idx = 0usize;
while idx < argv.len() { let arg = &argv[idx];
if arg == "--" { idx += 1; break; } if !arg.starts_with('-') { break; } idx += 1;
if matches!(arg.as_str(),
"-J"|"-V"|"-X"|"-x"|"-d"|"-l"|"-O"|"-A"|"-D"|"-E"|"-W"|"-R"|
"-F"|"-P"|"-S"|"-i"|"-I"|"-p"|"-s"|"-r"|"-q"|"-Q"|"-M"|"-o")
&& idx < argv.len()
{
idx += 1; }
}
let matches = &argv[idx..]; let mut dat = crate::ported::zle::comp_h::Cadata::default();
dat.dummies = -1;
crate::ported::zle::compcore::addmatches(&mut dat, matches) }
pub fn bin_compset(name: &str, argv: &[String], _ops: &crate::ported::zsh_h::options, _func: i32) -> i32 {
let mut test = 0i32; let mut na = 0i32;
let mut nb;
if INCOMPFUNC.load(std::sync::atomic::Ordering::Relaxed) != 1 { zwarnnam(name, "can only be called from completion function"); return 1; }
if argv.is_empty() || !argv[0].starts_with('-') { zwarnnam(name, "missing option"); return 1; }
let arg0 = &argv[0];
let opt = arg0.as_bytes().get(1).copied().unwrap_or(0); match opt {
b'n' => test = CVT_RANGENUM, b'N' => test = CVT_RANGEPAT, b'p' => test = CVT_PRENUM, b'P' => test = CVT_PREPAT, b's' => test = CVT_SUFNUM, b'S' => test = CVT_SUFPAT, b'q' => return crate::ported::zle::compcore::set_comp_sep() as i32, _ => { zwarnnam(name, &format!("bad option -{}", opt as char)); return 1; }
}
let (sa, sb, na_consumed): (Option<String>, Option<String>, usize);
if arg0.len() > 2 { sa = Some(arg0[2..].to_string()); sb = argv.get(1).cloned(); na_consumed = 2; } else {
let Some(s1) = argv.get(1).cloned() else { zwarnnam(name,
&format!("missing string for option -{}", opt as char)); return 1; };
sa = Some(s1);
sb = argv.get(2).cloned();
na_consumed = 3; }
let too_many = if test == CVT_PRENUM || test == CVT_SUFNUM {
sb.is_some()
} else {
sb.is_some() && argv.len() > na_consumed
};
if too_many { zwarnnam(name, "too many arguments"); return 1; }
let sa_ref = sa.as_deref().unwrap_or("");
let sb_ref = sb.as_deref();
match test {
CVT_RANGENUM => { na = sa_ref.parse::<i32>().unwrap_or(0); nb = sb_ref.and_then(|s| s.parse::<i32>().ok()).unwrap_or(-1); }
CVT_RANGEPAT => { let _ = sa_ref;
nb = 0;
}
CVT_PRENUM | CVT_SUFNUM => { na = sa_ref.parse::<i32>().unwrap_or(0); nb = 0;
}
CVT_PREPAT | CVT_SUFPAT => { if let Some(s2) = sb_ref { na = sa_ref.parse::<i32>().unwrap_or(0); let _ = s2; nb = 0;
} else {
nb = 0;
}
}
_ => { nb = 0; }
}
let _ = (na, nb);
do_comp_vars(test, na, sa_ref, nb, sb_ref.unwrap_or(""), 0) }
pub fn do_comp_vars(test: i32, mut na: i32, sa: &str, mut nb: i32, sb: &str, mod_: i32) -> i32 {
match test { CVT_RANGENUM => { let words = COMPWORDS.get()
.map(|m| m.lock().map(|g| g.clone()).unwrap_or_default())
.unwrap_or_default();
let l = words.len() as i32; if na < 0 { na += l; } else { na -= 1; } if nb < 0 { nb += l; } else { nb -= 1; } let cur = COMPCURRENT.load(Ordering::Relaxed);
if cur - 1 < na || cur - 1 > nb { return 0; } if mod_ != 0 { restrict_range(na, nb); } 1 }
CVT_RANGEPAT => { let words = COMPWORDS.get()
.map(|m| m.lock().map(|g| g.clone()).unwrap_or_default())
.unwrap_or_default();
let l = words.len() as i32;
let mut t = 0i32; let mut b = 0i32;
let mut e = l - 1;
let mut i = COMPCURRENT.load(Ordering::Relaxed) - 1; if i < 0 || i >= l { return 0; } let pp = patcompile(sa, crate::ported::zsh_h::PAT_HEAPDUP, None); i -= 1; while i >= 0 {
if let Some(ref prog) = pp {
if pattry(prog, &words[i as usize]) { b = i + 1; t = 1; break;
}
}
i -= 1;
}
if t != 0 && !sb.is_empty() { let mut tt = 0i32;
let pp2 = patcompile(sb, crate::ported::zsh_h::PAT_HEAPDUP, None); i += 1; while i < l {
if let Some(ref prog) = pp2 {
if pattry(prog, &words[i as usize]) { e = i - 1; tt = 1;
break;
}
}
i += 1;
}
if tt != 0 && i < COMPCURRENT.load(Ordering::Relaxed) { t = 0; }
}
if e < b { t = 0; } if t != 0 && mod_ != 0 { restrict_range(b, e); } t }
CVT_PRENUM | CVT_SUFNUM => { if na < 0 { return 0; } if na > 0 && mod_ != 0 { let target_str = if test == CVT_PRENUM {
lock_str(&COMPPREFIX).lock()
.map(|s| s.clone()).unwrap_or_default()
} else {
lock_str(&COMPSUFFIX).lock()
.map(|s| s.clone()).unwrap_or_default()
};
if (target_str.chars().count() as i32) < na { return 0;
}
if test == CVT_PRENUM { ignore_prefix(na); } else {
ignore_suffix(na); }
}
1 }
CVT_PREPAT | CVT_SUFPAT => { if na == 0 { return 0; } let pp = match patcompile(sa, crate::ported::zsh_h::PAT_HEAPDUP, None) { Some(p) => p,
None => return 0,
};
if test == CVT_PREPAT { let prefix = lock_str(&COMPPREFIX).lock()
.map(|s| s.clone()).unwrap_or_default();
let l = prefix.chars().count() as i32;
if l == 0 { let hit = (na == 1 || na == -1) && pattry(&pp, &prefix);
return if hit { 1 } else { 0 };
}
let chars: Vec<char> = prefix.chars().collect();
let (mut p, add): (i32, i32) = if na < 0 { (l, -1) } else {
(1, 1) };
if na < 0 { na = -na; }
loop { let p_uz = p.max(0).min(l) as usize;
let head: String = chars[..p_uz].iter().collect(); let hit = pattry(&pp, &head); if hit {
na -= 1;
if na == 0 { break; } }
p += add; if add > 0 && p > l { return 0; } if add < 0 && p < 0 { return 0; } }
if mod_ != 0 { ignore_prefix(p); } } else {
let suffix = lock_str(&COMPSUFFIX).lock()
.map(|s| s.clone()).unwrap_or_default();
let l = suffix.chars().count() as i32;
if l == 0 { let hit = (na == 1 || na == -1) && pattry(&pp, &suffix);
return if hit { 1 } else { 0 };
}
let chars: Vec<char> = suffix.chars().collect();
let (mut p, add): (i32, i32) = if na < 0 { (0, 1)
} else {
(l - 1, -1)
};
if na < 0 { na = -na; }
loop { let p_uz = p.max(0).min(l) as usize;
let tail: String = chars[p_uz..].iter().collect();
let hit = pattry(&pp, &tail); if hit {
na -= 1;
if na == 0 { break; }
}
p += add; if add > 0 && p > l { return 0; }
if add < 0 && p < 0 { return 0; }
}
if mod_ != 0 { ignore_suffix(l - p); } }
1 }
_ => 0, }
}
pub fn parse_cmatcher(name: &str, s: &str) -> Option<Box<crate::ported::zle::comp_h::Cmatcher>>
{
use crate::ported::zle::comp_h::{
CMF_INTER, CMF_LEFT, CMF_LINE, CMF_RIGHT, Cmatcher
};
if s.is_empty() { return None;
}
let mut ret: Option<Box<Cmatcher>> = None;
let mut tail_ptr: *mut Option<Box<Cmatcher>> = &mut ret;
let mut rest = s;
while !rest.is_empty() { rest = rest.trim_start_matches(|c: char| c == ' ' || c == '\t');
if rest.is_empty() { break; }
let c = rest.chars().next().unwrap();
let (fl, fl2) = match c {
'b' => (CMF_LEFT, CMF_INTER), 'l' => (CMF_LEFT, 0), 'e' => (CMF_RIGHT, CMF_INTER), 'r' => (CMF_RIGHT, 0), 'm' => (0, 0), 'B' => (CMF_LEFT | CMF_LINE, CMF_INTER), 'L' => (CMF_LEFT | CMF_LINE, 0), 'E' => (CMF_RIGHT | CMF_LINE, CMF_INTER), 'R' => (CMF_RIGHT | CMF_LINE, 0), 'M' => (CMF_LINE, 0), 'x' => (0, 0), _ => { if !name.is_empty() {
crate::ported::utils::zwarnnam(name,
&format!("unknown match specification character `{}'", c));
}
return None; }
};
let mut chars = rest.chars();
chars.next();
if chars.clone().next() != Some(':') {
if !name.is_empty() {
crate::ported::utils::zwarnnam(name, "missing `:'");
}
return None;
}
chars.next();
if c == 'x' {
if let Some(next) = chars.clone().next() {
if next != ' ' && next != '\t' {
if !name.is_empty() {
crate::ported::utils::zwarnnam(name,
"unexpected pattern following x: specification");
}
return None;
}
}
return ret;
}
rest = chars.as_str();
let mut left: Option<Box<crate::ported::zle::comp_h::Cpattern>> = None;
let mut lal: i32 = 0;
let mut both: bool = false;
if (fl & CMF_LEFT) != 0 && fl2 == 0 {
let (lt, r2, l, err) = parse_pattern(name, rest, '|'); if err { return None; }
left = lt;
lal = l;
rest = r2;
let mut peek = rest.chars();
peek.next();
if peek.clone().next() == Some('|') {
both = true;
let mut adv = rest.chars();
adv.next();
rest = adv.as_str();
}
if rest.len() <= 1 {
if !name.is_empty() {
crate::ported::utils::zwarnnam(name,
if both { "missing right anchor" } else { "missing line pattern" });
}
return None;
}
let mut adv = rest.chars();
adv.next();
rest = adv.as_str();
}
let line_end = if (fl & CMF_RIGHT) != 0 && fl2 == 0 { '|' } else { '=' };
let (mut line_pat, r2, mut ll, err) = parse_pattern(name, rest, line_end);
if err { return None; }
rest = r2;
let (mut right, mut ral) = (None, 0i32);
if both {
right = line_pat;
ral = ll;
line_pat = None;
ll = 0;
}
if (fl & CMF_RIGHT) != 0 && fl2 == 0 && rest.len() <= 1 {
if !name.is_empty() {
crate::ported::utils::zwarnnam(name, "missing right anchor");
}
return None;
}
if (fl & CMF_RIGHT) == 0 || fl2 != 0 {
if rest.is_empty() {
if !name.is_empty() {
crate::ported::utils::zwarnnam(name, "missing word pattern");
}
return None;
}
let mut adv = rest.chars();
adv.next();
rest = adv.as_str();
}
if (fl & CMF_RIGHT) != 0 && fl2 == 0 {
if rest.chars().next() == Some('|') {
left = line_pat.take();
lal = ll;
ll = 0;
let mut adv = rest.chars();
adv.next();
rest = adv.as_str();
}
let (rt, r3, r_len, err) = parse_pattern(name, rest, '=');
if err { return None; }
right = rt;
ral = r_len;
rest = r3;
if rest.is_empty() {
if !name.is_empty() {
crate::ported::utils::zwarnnam(name, "missing word pattern");
}
return None;
}
let mut adv = rest.chars();
adv.next();
rest = adv.as_str();
}
let (word_pat, wl): (Option<Box<crate::ported::zle::comp_h::Cpattern>>, i32);
if rest.chars().next() == Some('*') {
if (fl & (CMF_LEFT | CMF_RIGHT)) == 0 {
if !name.is_empty() {
crate::ported::utils::zwarnnam(name, "need anchor for `*'");
}
return None;
}
let mut adv = rest.chars();
adv.next();
rest = adv.as_str();
if rest.chars().next() == Some('*') {
let mut adv2 = rest.chars();
adv2.next();
rest = adv2.as_str();
word_pat = None;
wl = -2;
} else {
word_pat = None;
wl = -1;
}
} else {
let (w, r4, w_len, err) = parse_pattern(name, rest, '\0');
if err { return None; }
if w.is_none() && line_pat.is_none() {
if !name.is_empty() {
crate::ported::utils::zwarnnam(name,
"need non-empty word or line pattern");
}
return None;
}
word_pat = w;
wl = w_len;
rest = r4;
}
let node = Box::new(Cmatcher {
refc: 0,
next: None,
flags: fl | fl2,
line: line_pat,
llen: ll,
word: word_pat,
wlen: wl,
left,
lalen: lal,
right,
ralen: ral,
});
unsafe {
*tail_ptr = Some(node);
if let Some(boxed) = (*tail_ptr).as_mut() {
tail_ptr = &mut boxed.next as *mut _;
}
}
}
ret
}
pub fn parse_pattern<'a>(name: &str, s: &'a str, end: char) -> (Option<Box<crate::ported::zle::comp_h::Cpattern>>, &'a str, i32, bool)
{
use crate::ported::zle::comp_h::{Cpattern, CPAT_ANY, CPAT_CHAR};
let mut ret: Option<Box<Cpattern>> = None;
let mut tail_ptr: *mut Option<Box<Cpattern>> = &mut ret;
let mut rest = s;
let mut len = 0i32;
loop {
let next_ch = match rest.chars().next() {
Some(c) => c,
None => break,
};
if end != '\0' {
if next_ch == end { break; }
} else if next_ch == ' ' || next_ch == '\t' {
break;
}
let mut node = Box::new(Cpattern::default());
if next_ch == '[' || next_ch == '{' { let before_len = rest.len();
rest = parse_class(&mut node, rest);
if rest.len() == before_len {
if !name.is_empty() {
crate::ported::utils::zwarnnam(name,
"unterminated character class");
}
return (None, rest, 0, true);
}
} else if next_ch == '?' { node.tp = CPAT_ANY;
let mut it = rest.chars();
it.next();
rest = it.as_str();
} else if matches!(next_ch, '*' | '(' | ')' | '=') { if !name.is_empty() {
crate::ported::utils::zwarnnam(name,
&format!("invalid pattern character `{}'", next_ch));
}
return (None, rest, 0, true);
} else { if next_ch == '\\' {
let mut it = rest.chars();
it.next();
if it.clone().next().is_some() {
rest = it.as_str();
}
}
let ch = rest.chars().next().unwrap();
node.tp = CPAT_CHAR;
node.chr = ch as u32;
let mut it = rest.chars();
it.next();
rest = it.as_str();
}
unsafe {
*tail_ptr = Some(node);
if let Some(boxed) = (*tail_ptr).as_mut() {
tail_ptr = &mut boxed.next as *mut _;
}
}
len += 1;
}
(ret, rest, len, false)
}
pub fn parse_class<'a>(p: &mut crate::ported::zle::comp_h::Cpattern, iptr: &'a str) -> &'a str {
use crate::ported::zle::comp_h::{CPAT_CCLASS, CPAT_EQUIV, CPAT_NCLASS};
use crate::ported::zsh_h::PP_UNKWN;
use crate::ported::pattern::range_type;
let bytes = iptr.as_bytes();
if bytes.is_empty() {
return iptr;
}
let opener = bytes[0];
let endchar: u8;
let mut i = 1;
if opener == b'[' {
endchar = b']';
if i < bytes.len() && (bytes[i] == b'!' || bytes[i] == b'^')
&& i + 1 < bytes.len() && bytes[i + 1] != b']'
{
p.tp = CPAT_NCLASS;
i += 1;
} else {
p.tp = CPAT_CCLASS;
}
} else {
endchar = 0x7d; p.tp = CPAT_EQUIV;
}
let start = i;
let mut optr_idx = i;
while optr_idx < bytes.len() && (optr_idx == start || bytes[optr_idx] != endchar) {
optr_idx += 1;
}
if optr_idx >= bytes.len() {
return &iptr[bytes.len()..];
}
let mut out: Vec<u8> = Vec::with_capacity(optr_idx - i + 1);
let mut firsttime = true;
while firsttime || (i < bytes.len() && bytes[i] != endchar) {
if bytes[i] == b'[' && i + 1 < bytes.len() && bytes[i + 1] == b':' {
if let Some(nptr) = bytes[i + 2..].iter().position(|&b| b == b':') {
let nptr = i + 2 + nptr;
if nptr + 1 < bytes.len() && bytes[nptr + 1] == b']' {
let name = std::str::from_utf8(&bytes[i + 2..nptr]).unwrap_or("");
let ch = range_type(name).unwrap_or(PP_UNKWN as usize);
i = nptr + 2;
if ch != PP_UNKWN as usize {
out.push(0x80u8.wrapping_add(ch as u8));
}
firsttime = false;
continue;
}
}
}
let ptr1 = i;
if bytes[i] == 0x83 { i += 1;
}
if i >= bytes.len() { break; }
i += 1;
if i < bytes.len() && bytes[i] == b'-'
&& i + 1 < bytes.len() && bytes[i + 1] != endchar
{
i += 1; out.push(0x80u8.wrapping_add(crate::ported::zsh_h::PP_RANGE as u8));
if bytes[ptr1] == 0x83 && ptr1 + 1 < bytes.len() {
out.push(0x83);
out.push(bytes[ptr1 + 1] ^ 32);
} else {
out.push(bytes[ptr1]);
}
if i < bytes.len() && bytes[i] == 0x83 && i + 1 < bytes.len() {
out.push(bytes[i]);
out.push(bytes[i + 1]);
i += 2;
} else if i < bytes.len() {
out.push(bytes[i]);
i += 1;
}
} else {
if bytes[ptr1] == 0x83 && ptr1 + 1 < bytes.len() {
out.push(0x83);
out.push(bytes[ptr1 + 1] ^ 32);
} else {
out.push(bytes[ptr1]);
}
}
firsttime = false;
}
p.str = Some(String::from_utf8_lossy(&out).into_owned());
let consumed = (i + 1).min(bytes.len());
&iptr[consumed..]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ported::zle::comp_h::{CPAT_CCLASS, CPAT_EQUIV, CPAT_NCLASS, Cpattern};
#[test]
fn classes_basic_cclass() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut p = Cpattern::default();
let rest = parse_class(&mut p, "[abc]rest");
assert_eq!(p.tp, CPAT_CCLASS);
assert_eq!(p.str.as_deref(), Some("abc"));
assert_eq!(rest, "rest");
}
#[test]
fn classes_negated_cclass_via_bang() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut p = Cpattern::default();
let _ = parse_class(&mut p, "[!abc]");
assert_eq!(p.tp, CPAT_NCLASS);
}
#[test]
fn classes_negated_cclass_via_caret() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut p = Cpattern::default();
let _ = parse_class(&mut p, "[^abc]");
assert_eq!(p.tp, CPAT_NCLASS);
}
#[test]
fn classes_equiv_braces() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut p = Cpattern::default();
let _ = parse_class(&mut p, "{abc}");
assert_eq!(p.tp, CPAT_EQUIV);
}
#[test]
fn classes_range_consumes_input() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut p = Cpattern::default();
let rest = parse_class(&mut p, "[a-z]rest");
assert_eq!(p.tp, CPAT_CCLASS);
assert_eq!(rest, "rest");
assert!(p.str.is_some());
}
#[test]
fn cmatcher_empty_input_returns_none() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert!(parse_cmatcher("", "").is_none());
}
#[test]
fn cmatcher_x_early_return() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert!(parse_cmatcher("", "x:").is_none());
}
#[test]
fn cmatcher_unknown_letter_errors() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert!(parse_cmatcher("", "q:abc").is_none());
}
#[test]
fn cmatcher_missing_colon_errors() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert!(parse_cmatcher("", "rabc").is_none());
}
#[test]
fn cmatcher_x_with_trailing_pattern_errors() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
assert!(parse_cmatcher("", "x:foo").is_none());
}
#[test]
fn cmatcher_valid_letters_dont_panic() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
for c in ['b', 'l', 'e', 'r', 'm', 'B', 'L', 'E', 'R', 'M'] {
let spec = format!("{}:body", c);
let _ = parse_cmatcher("", &spec);
}
}
#[test]
fn cmatcher_m_rule_emits_cmatcher() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let r = parse_cmatcher("", "m:foo=bar");
assert!(r.is_some(), "m: rule should produce a Cmatcher");
let cm = r.unwrap();
assert_eq!(cm.flags, 0); assert_eq!(cm.llen, 3); assert_eq!(cm.wlen, 3); assert!(cm.line.is_some());
assert!(cm.word.is_some());
assert!(cm.left.is_none());
assert!(cm.right.is_none());
}
#[test]
fn cmatcher_r_rule_emits_anchored_cmatcher() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let r = parse_cmatcher("", "r:abc|xy=def");
assert!(r.is_some(), "r: rule should produce a Cmatcher");
let cm = r.unwrap();
use crate::ported::zle::comp_h::CMF_RIGHT;
assert_eq!(cm.flags, CMF_RIGHT);
assert_eq!(cm.lalen, 3); assert_eq!(cm.ralen, 2); assert_eq!(cm.wlen, 3); assert!(cm.left.is_some());
assert!(cm.right.is_some());
}
#[test]
fn cmatcher_l_rule_emits_left_anchor() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let r = parse_cmatcher("", "l:ab|cd=ef");
assert!(r.is_some(), "l: rule should produce a Cmatcher");
let cm = r.unwrap();
use crate::ported::zle::comp_h::CMF_LEFT;
assert_eq!(cm.flags, CMF_LEFT);
assert!(cm.left.is_some());
assert_eq!(cm.lalen, 2);
assert_eq!(cm.llen, 2);
assert_eq!(cm.wlen, 2);
}
#[test]
fn cmatcher_star_word_with_anchor() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let r = parse_cmatcher("", "r:|=*");
assert!(r.is_some(), "r:|=* should produce a Cmatcher");
let cm = r.unwrap();
assert_eq!(cm.wlen, -1); assert!(cm.word.is_none());
}
#[test]
fn cmatcher_double_star_word() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let r = parse_cmatcher("", "r:|=**");
assert!(r.is_some());
let cm = r.unwrap();
assert_eq!(cm.wlen, -2); }
#[test]
fn cmatcher_star_without_anchor_errors() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let r = parse_cmatcher("", "m:=*");
assert!(r.is_none(), "*-without-anchor should error");
}
#[test]
fn cmatcher_chain_multiple_rules() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let r = parse_cmatcher("", "m:foo=bar m:baz=qux");
assert!(r.is_some());
let head = r.unwrap();
assert!(head.next.is_some(), "second rule should be linked");
}
#[test]
fn pattern_single_char_emits_cpat_char() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let (chain, rest, len, err) = parse_pattern("", "abc", '\0');
assert!(!err);
assert_eq!(len, 3);
assert_eq!(rest, ""); use crate::ported::zle::comp_h::CPAT_CHAR;
let mut count = 0;
let mut cur = chain.as_deref();
while let Some(n) = cur {
assert_eq!(n.tp, CPAT_CHAR);
count += 1;
cur = n.next.as_deref();
}
assert_eq!(count, 3);
}
#[test]
fn pattern_question_mark_is_cpat_any() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let (chain, _, len, err) = parse_pattern("", "?", '\0');
assert!(!err);
assert_eq!(len, 1);
use crate::ported::zle::comp_h::CPAT_ANY;
assert_eq!(chain.as_ref().unwrap().tp, CPAT_ANY);
}
#[test]
fn pattern_invalid_chars_error() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
for c in ['*', '(', ')', '='] {
let s = format!("{}", c);
let (chain, _, _, err) = parse_pattern("", &s, '\0');
assert!(err, "char {} should error", c);
assert!(chain.is_none());
}
}
#[test]
fn pattern_backslash_escapes_next() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let (chain, _, len, err) = parse_pattern("", r"\*", '\0');
assert!(!err);
assert_eq!(len, 1);
use crate::ported::zle::comp_h::CPAT_CHAR;
let n = chain.as_ref().unwrap();
assert_eq!(n.tp, CPAT_CHAR);
assert_eq!(n.chr, '*' as u32);
}
#[test]
fn pattern_stops_at_end_char() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let (_, rest, len, err) = parse_pattern("", "ab=cd", '=');
assert!(!err);
assert_eq!(len, 2);
assert_eq!(rest, "=cd");
}
#[test]
fn pattern_stops_at_whitespace_when_no_end_char() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let (_, rest, len, err) = parse_pattern("", "ab cd", '\0');
assert!(!err);
assert_eq!(len, 2);
assert_eq!(rest, " cd");
}
#[test]
fn pattern_bracket_class_routes_to_parse_class() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let (chain, rest, len, err) = parse_pattern("", "[abc]xy=q", '=');
assert!(!err);
assert_eq!(len, 3);
assert_eq!(rest, "=q");
use crate::ported::zle::comp_h::CPAT_CCLASS;
assert_eq!(chain.as_ref().unwrap().tp, CPAT_CCLASS);
}
#[test]
fn classes_unterminated_returns_eos() {
let _g = crate::ported::zle::zle_main::zle_test_setup();
let mut p = Cpattern::default();
let rest = parse_class(&mut p, "[abc");
assert_eq!(rest, "");
}
}