use super::lex::{
lextok, set_tok, AMPER, AMPERBANG, AMPOUTANG, BANG_TOK, BARAMP, BAR_TOK, CASE, COPROC, DAMPER,
DBAR, DINANG, DINANGDASH, DINBRACK, DINPAR, DOLOOP, DONE, DOUTANG, DOUTANGAMP, DOUTANGAMPBANG,
DOUTANGBANG, DOUTBRACK, DOUTPAR, DSEMI, ELIF, ELSE, ENDINPUT, ENVARRAY, ENVSTRING, ESAC, FI,
FOR, FOREACH, FUNC, IF, INANGAMP, INANG_TOK, INBRACE_TOK, INOUTANG, INOUTPAR, INPAR_TOK,
IS_REDIROP, LEXERR, NEWLIN, NOCORRECT, NULLTOK, OUTANGAMP, OUTANGAMPBANG, OUTANGBANG,
OUTANG_TOK, OUTBRACE_TOK, OUTPAR_TOK, REPEAT, SELECT, SEMI, SEMIAMP, SEMIBAR, SEPER,
STRING_LEX, THEN, TIME, TRINANG, TYPESET, UNTIL, WHILE, ZEND,
};
use super::zsh_h::{
eprog, estate, isset, redir, unset, wc_code, wordcode, Bang, Dash, Equals, Inang, Inpar,
Outang, Outpar, Stringg, ALIASFUNCDEF, COND_AND, COND_MOD, COND_MODI, COND_NOT, COND_NT,
COND_REGEX, COND_STRDEQ, COND_STREQ, COND_STRGTR, COND_STRLT, COND_STRNEQ, CSHJUNKIELOOPS,
EC_DUP, EC_NODUP, EF_HEAP, EF_REAL, EXECOPT, IGNOREBRACES, IS_DASH, MULTIFUNCDEF, OPT_ISSET,
PM_UNDEFINED, POSIXBUILTINS, REDIRF_FROM_HEREDOC, REDIR_APP, REDIR_APPNOW, REDIR_ERRAPP,
REDIR_ERRAPPNOW, REDIR_ERRWRITE, REDIR_ERRWRITENOW, REDIR_HEREDOC, REDIR_HEREDOCDASH,
REDIR_HERESTR, REDIR_INPIPE, REDIR_MERGEIN, REDIR_MERGEOUT, REDIR_OUTPIPE, REDIR_READ,
REDIR_READWRITE, REDIR_WRITE, REDIR_WRITENOW, SHORTLOOPS, SHORTREPEAT, WCB_COND, WCB_SIMPLE,
WC_REDIR, WC_REDIR_FROM_HEREDOC, WC_REDIR_TYPE, WC_REDIR_VARID, WC_SUBLIST_COPROC,
WC_SUBLIST_NOT,
};
use crate::ported::utils::{zerr, zwarnnam};
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::{Read, Seek, SeekFrom, Write};
use std::sync::atomic::{AtomicUsize, Ordering};
thread_local! {
pub static ECBUF: std::cell::RefCell<Vec<u32>> = std::cell::RefCell::new(Vec::new());
static ECLEN: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static ECUSED: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static ECNPATS: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static ECSOFFS: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static ECSSUB: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static ECNFUNC: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
static ECSTRS_INDEX: std::cell::RefCell<std::collections::HashMap<(i32, String), u32>>
= std::cell::RefCell::new(std::collections::HashMap::new());
pub static ECSTRS_REVERSE: std::cell::RefCell<std::collections::HashMap<u32, Vec<u8>>>
= std::cell::RefCell::new(std::collections::HashMap::new());
}
const EC_INIT_SIZE: i32 = 256;
const EC_DOUBLE_THRESHOLD: i32 = 32768;
const EC_INCREMENT: i32 = 1024;
thread_local! {
pub static PARSER_RECURSION_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
pub static PARSER_GLOBAL_ITERATIONS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
pub fn ecgetstr(s: &mut estate, dup: i32, tokflag: Option<&mut i32>) -> String {
let prog = &s.prog.prog;
if s.pc >= prog.len() {
return String::new();
}
let c = prog[s.pc]; s.pc += 1;
if let Some(tf) = tokflag {
*tf = i32::from((c & 1) != 0); }
if c == 6 || c == 7 {
return String::new();
}
let r: String = if (c & 2) != 0 {
let b0 = ((c >> 3) & 0xff) as u8;
let b1 = ((c >> 11) & 0xff) as u8;
let b2 = ((c >> 19) & 0xff) as u8;
let mut v = vec![b0, b1, b2];
v.retain(|&x| x != 0);
String::from_utf8_lossy(&v).into_owned()
} else {
let off = (c >> 2) as usize + s.strs_offset;
let strs_bytes = s.strs.as_deref().unwrap_or("").as_bytes();
if off >= strs_bytes.len() {
String::new()
} else {
let tail = &strs_bytes[off..];
let end = tail.iter().position(|&b| b == 0).unwrap_or(tail.len());
String::from_utf8_lossy(&tail[..end]).into_owned()
}
};
let _ = (dup, EC_DUP, EC_NODUP);
r
}
pub fn ecgetredirs(s: &mut estate) -> Vec<redir> {
let mut ret: Vec<redir> = Vec::new(); let prog_len = s.prog.prog.len();
if s.pc >= prog_len {
return ret;
}
let mut code = s.prog.prog[s.pc]; s.pc += 1;
loop {
if wc_code(code) != WC_REDIR {
s.pc = s.pc.saturating_sub(1);
break;
}
let typ = WC_REDIR_TYPE(code); if s.pc >= prog_len {
break;
}
let fd1_w = s.prog.prog[s.pc]; s.pc += 1;
let name = ecgetstr(s, EC_DUP, None);
let (flags, here_terminator, munged_here_terminator) = if WC_REDIR_FROM_HEREDOC(code) != 0 {
let term = ecgetstr(s, EC_DUP, None);
let munged = ecgetstr(s, EC_DUP, None);
(REDIRF_FROM_HEREDOC, Some(term), Some(munged))
} else {
(0, None, None)
};
let varid = if WC_REDIR_VARID(code) != 0 {
Some(ecgetstr(s, EC_DUP, None))
} else {
None };
ret.push(redir {
typ,
flags,
fd1: fd1_w as i32,
fd2: 0,
name: Some(name),
varid,
here_terminator,
munged_here_terminator,
});
if s.pc >= prog_len {
break;
}
code = s.prog.prog[s.pc]; s.pc += 1;
}
ret }
pub use crate::heredoc_ast::HereDoc;
pub use crate::zsh_ast::{
CaseArm, CaseTerm, CaseTerminator, CompoundCommand, ForList, HereDocInfo, ListFlags, ListOp,
Redirect, RedirectOp, ShellCommand, ShellWord, SimpleCommand, SublistFlags, SublistOp,
VarModifier, ZshAssign, ZshAssignValue, ZshCase, ZshCommand, ZshCond, ZshFor, ZshFuncDef,
ZshIf, ZshList, ZshParamFlag, ZshPipe, ZshProgram, ZshRedir, ZshRepeat, ZshSimple, ZshSublist,
ZshTry, ZshWhile,
};
use crate::ported::lex::{
heredocs_clear, heredocs_clone, heredocs_is_empty, heredocs_len, heredocs_push, heredocs_set,
heredocs_take, incasepat, incmdpos, incond, infor, input_slice, inredir, inrepeat, intypeset,
isnewlin, lex_init, lineno, pos, set_incasepat, set_incmdpos, set_incond, set_infor,
set_inredir, set_inrepeat, set_intypeset, set_isnewlin, set_pos, set_tokfd, set_toklineno,
set_tokstr, tok, tokfd, toklineno, tokstr, tokstr_eq, tokstr_is_none, tokstr_is_some,
tokstr_take, zshlex,
};
use crate::prompt::{cmdpop, cmdpush};
use crate::zsh_h::{
wc_bdata, CS_CASE, CS_CMDAND, CS_CMDOR, CS_COND, CS_CURSH, CS_ELIF, CS_ELSE, CS_ERRPIPE,
CS_FOR, CS_FOREACH, CS_FUNCDEF, CS_IF, CS_IFTHEN, CS_PIPE, CS_REPEAT, CS_SELECT, CS_SUBSH,
CS_UNTIL, CS_WHILE, EF_RUN, WCB_ARITH, WCB_CASE, WCB_CURSH, WCB_END, WCB_FOR, WCB_FUNCDEF,
WCB_IF, WCB_LIST, WCB_PIPE, WCB_REDIR, WCB_REPEAT, WCB_SELECT, WCB_SUBLIST, WCB_SUBSH,
WCB_TIMED, WCB_TRY, WCB_WHILE, WC_CASE_AND, WC_CASE_HEAD, WC_CASE_OR, WC_CASE_TESTAND,
WC_FOR_COND, WC_FOR_LIST, WC_FOR_PPARAM, WC_IF_HEAD, WC_IF_IF, WC_PIPE_END, WC_PIPE_LINENO,
WC_PIPE_MID, WC_REDIR_WORDS, WC_SELECT_LIST, WC_SELECT_PPARAM, WC_SUBLIST_AND, WC_SUBLIST_END,
WC_SUBLIST_FLAGS, WC_SUBLIST_OR, WC_SUBLIST_SIMPLE, WC_SUBLIST_TYPE, WC_TIMED_EMPTY,
WC_TIMED_PIPE, WC_WHILE_UNTIL, WC_WHILE_WHILE, Z_ASYNC, Z_DISOWN, Z_END, Z_SIMPLE, Z_SYNC,
};
const MAX_RECURSION_DEPTH: usize = 500;
#[allow(non_camel_case_types)]
#[derive(Debug, Default, Clone)]
pub struct parse_stack {
pub hdocs: Vec<HereDoc>,
pub incmdpos: bool,
pub aliasspaceflag: i32,
pub incond: i32,
pub inredir: bool,
pub incasepat: i32,
pub isnewlin: i32,
pub infor: i32,
pub inrepeat_: i32,
pub intypeset: bool,
pub eclen: i32,
pub ecused: i32,
pub ecnpats: i32,
pub ecbuf: Option<Vec<u32>>,
pub ecstrs: Option<Vec<u8>>,
pub ecsoffs: i32,
pub ecssub: i32,
pub ecnfunc: i32,
}
#[allow(non_camel_case_types)]
pub type ParseStack = parse_stack;
fn fill_heredoc_bodies(prog: &mut ZshProgram, bodies: &[HereDocInfo]) {
for list in &mut prog.lists {
fill_in_sublist(&mut list.sublist, bodies);
}
}
fn fill_in_sublist(sub: &mut ZshSublist, bodies: &[HereDocInfo]) {
fill_in_pipe(&mut sub.pipe, bodies);
if let Some(next) = &mut sub.next {
fill_in_sublist(&mut next.1, bodies);
}
}
fn fill_in_pipe(pipe: &mut ZshPipe, bodies: &[HereDocInfo]) {
fill_in_command(&mut pipe.cmd, bodies);
if let Some(next) = &mut pipe.next {
fill_in_pipe(next, bodies);
}
}
fn fill_in_command(cmd: &mut ZshCommand, bodies: &[HereDocInfo]) {
match cmd {
ZshCommand::Simple(s) => {
for r in &mut s.redirs {
resolve_redir(r, bodies);
}
}
ZshCommand::Subsh(p) | ZshCommand::Cursh(p) => fill_heredoc_bodies(p, bodies),
ZshCommand::FuncDef(f) => fill_heredoc_bodies(&mut f.body, bodies),
ZshCommand::If(i) => {
fill_heredoc_bodies(&mut i.cond, bodies);
fill_heredoc_bodies(&mut i.then, bodies);
for (c, b) in &mut i.elif {
fill_heredoc_bodies(c, bodies);
fill_heredoc_bodies(b, bodies);
}
if let Some(e) = &mut i.else_ {
fill_heredoc_bodies(e, bodies);
}
}
ZshCommand::While(w) | ZshCommand::Until(w) => {
fill_heredoc_bodies(&mut w.cond, bodies);
fill_heredoc_bodies(&mut w.body, bodies);
}
ZshCommand::For(f) => fill_heredoc_bodies(&mut f.body, bodies),
ZshCommand::Case(c) => {
for arm in &mut c.arms {
fill_heredoc_bodies(&mut arm.body, bodies);
}
}
ZshCommand::Repeat(r) => fill_heredoc_bodies(&mut r.body, bodies),
ZshCommand::Time(Some(sublist)) => fill_in_sublist(sublist, bodies),
ZshCommand::Try(t) => {
fill_heredoc_bodies(&mut t.try_block, bodies);
fill_heredoc_bodies(&mut t.always, bodies);
}
ZshCommand::Redirected(inner, redirs) => {
for r in redirs {
resolve_redir(r, bodies);
}
fill_in_command(inner, bodies);
}
ZshCommand::Time(None) | ZshCommand::Cond(_) | ZshCommand::Arith(_) => {}
}
}
fn resolve_redir(r: &mut ZshRedir, bodies: &[HereDocInfo]) {
if let Some(idx) = r.heredoc_idx {
if let Some(info) = bodies.get(idx) {
r.heredoc = Some(info.clone());
}
}
}
fn simple_name_with_inoutpar(list: &ZshList) -> Option<(Vec<String>, Vec<String>)> {
if list.flags.async_ || list.sublist.next.is_some() {
return None;
}
let pipe = &list.sublist.pipe;
if pipe.next.is_some() {
return None;
}
let simple = match &pipe.cmd {
ZshCommand::Simple(s) => s,
_ => return None,
};
if simple.words.is_empty() || !simple.assigns.is_empty() {
return None;
}
let suffix = "\u{88}\u{8a}"; let par_idx = simple.words.iter().position(|w| w.ends_with(suffix))?;
let mut names: Vec<String> = Vec::with_capacity(par_idx + 1);
for w in &simple.words[..par_idx] {
let bare = super::lex::untokenize(w);
let valid = !bare.is_empty()
&& bare
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.' || c == '$');
if !valid {
return None;
}
names.push(bare);
}
let last = &simple.words[par_idx];
let bare = &last[..last.len() - suffix.len()];
if bare.is_empty() {
return None;
}
names.push(super::lex::untokenize(bare));
let rest = simple.words[par_idx + 1..].to_vec();
Some((names, rest))
}
pub fn parse_init(input: &str) {
PARSER_GLOBAL_ITERATIONS.set(0);
PARSER_RECURSION_DEPTH.set(0);
for (name, default) in [
("shortloops", true),
("shortrepeat", false),
("multifuncdef", true),
("aliasfuncdef", false),
("ignorebraces", false),
("cshjunkieloops", false),
("posixbuiltins", false),
("execopt", true),
("kshautoload", false),
("aliases", true),
] {
if crate::ported::options::opt_state_get(name).is_none() {
crate::ported::options::opt_state_set(name, default);
}
}
lex_init(input);
}
#[inline]
fn check_limit() -> bool {
false
}
#[inline]
fn check_recursion() -> bool {
false
}
pub fn parse_context_save(ps: &mut parse_stack) {
ps.hdocs = heredocs_take();
ps.incmdpos = incmdpos();
ps.aliasspaceflag = 0;
ps.incond = incond();
ps.inredir = inredir();
ps.incasepat = incasepat();
ps.isnewlin = isnewlin();
ps.infor = infor();
ps.inrepeat_ = inrepeat();
ps.intypeset = intypeset();
ps.eclen = 0;
ps.ecused = 0;
ps.ecnpats = 0;
ps.ecbuf = None;
ps.ecstrs = None;
ps.ecsoffs = 0;
ps.ecssub = 0;
ps.ecnfunc = 0;
PARSER_RECURSION_DEPTH.set(0);
PARSER_GLOBAL_ITERATIONS.set(0);
set_incmdpos(true);
set_incond(0);
set_inredir(false);
set_incasepat(0);
set_infor(0);
set_inrepeat(0);
set_intypeset(false);
}
pub fn parse_context_restore(ps: &parse_stack) {
heredocs_set(ps.hdocs.clone());
set_incmdpos(ps.incmdpos);
set_incond(ps.incond);
set_inredir(ps.inredir);
set_incasepat(ps.incasepat);
set_isnewlin(ps.isnewlin);
set_infor(ps.infor);
set_inrepeat(ps.inrepeat_);
set_intypeset(ps.intypeset);
crate::ported::utils::errflag.fetch_and(
!crate::ported::utils::ERRFLAG_ERROR,
std::sync::atomic::Ordering::Relaxed,
);
}
pub fn init_parse_status() {
set_incasepat(0);
set_incond(0);
set_inredir(false);
set_infor(0);
set_intypeset(false);
set_incmdpos(true);
}
pub fn init_parse() {
ECBUF.with_borrow_mut(|buf| {
buf.clear();
buf.resize(EC_INIT_SIZE as usize, 0);
});
ECLEN.set(EC_INIT_SIZE);
ECUSED.set(0);
ECNPATS.set(0);
ECSOFFS.set(0);
ECSSUB.set(0);
ECNFUNC.set(0);
ECSTRS_INDEX.with_borrow_mut(|m| m.clear());
ECSTRS_REVERSE.with_borrow_mut(|m| m.clear());
PARSER_RECURSION_DEPTH.set(0);
PARSER_GLOBAL_ITERATIONS.set(0);
init_parse_status();
}
pub fn empty_eprog(p: &crate::ported::zsh_h::eprog) -> bool {
p.prog.is_empty() || p.prog[0] == crate::ported::zsh_h::WCB_END()
}
pub fn clear_hdocs() {
heredocs_clear();
}
pub fn parse_event(endtok: lextok) -> Option<ZshProgram> {
set_tok(ENDINPUT);
set_incmdpos(true);
zshlex();
init_parse();
if !par_event(endtok) {
clear_hdocs();
return None;
}
if endtok != ENDINPUT {
return Some(ZshProgram { lists: Vec::new() });
}
Some(parse_program_until(None))
}
pub fn par_event(endtok: lextok) -> bool {
while tok() == SEPER {
if isnewlin() > 0 && endtok == ENDINPUT {
return false;
}
zshlex();
}
if tok() == ENDINPUT {
return false;
}
if tok() == endtok {
return true;
}
match par_sublist() {
Some(_) => {
true
}
None => false,
}
}
pub fn par_list1() -> Option<ZshSublist> {
par_sublist()
}
pub fn setheredoc(_pc: usize, _redir_type: i32, _doc: &str, _term: &str, _munged_term: &str) {
}
pub fn par_wordlist() -> Vec<String> {
let mut out = Vec::new();
while tok() == STRING_LEX {
if let Some(text) = tokstr() {
out.push(text);
}
zshlex();
}
out
}
pub fn par_nl_wordlist() -> Vec<String> {
while tok() == NEWLIN {
zshlex();
}
let out = par_wordlist();
while tok() == NEWLIN {
zshlex();
}
out
}
pub fn read_cond_num() -> Option<i64> {
if tok() != STRING_LEX {
return None;
}
let text = tokstr()?;
let parsed = text.parse::<i64>().ok()?;
zshlex();
Some(parsed)
}
pub fn get_cond_num(tst: &str) -> i32 {
const CONDSTRS: [&str; 9] = [
"nt", "ot", "ef", "eq", "ne", "lt", "gt", "le", "ge", ];
for (i, &c) in CONDSTRS.iter().enumerate() {
if c == tst {
return i as i32; }
}
-1 }
pub fn yyerror(msg: &str) {
error(msg);
}
pub fn set_list_code(p: usize, type_code: i32, cmplx: bool) {
let _ = wc_bdata;
let sublist_code = ECBUF.with_borrow(|b| b.get(p + 1).copied().unwrap_or(0));
let z = type_code;
let qualifies = !cmplx
&& (z == Z_SYNC || z == (Z_SYNC | Z_END))
&& WC_SUBLIST_TYPE(sublist_code) == WC_SUBLIST_END;
if qualifies {
let ispipe = (WC_SUBLIST_FLAGS(sublist_code) & WC_SUBLIST_SIMPLE) == 0;
let used = ECUSED.get() as usize;
let off = used.saturating_sub(2 + p);
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_LIST((z | Z_SIMPLE) as wordcode, off as wordcode);
}
});
ecdel(p + 1);
if ispipe {
ECBUF.with_borrow_mut(|b| {
if p + 1 < b.len() {
b[p + 1] = WC_PIPE_LINENO(b[p + 1]);
}
});
}
} else {
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_LIST(z as wordcode, 0);
}
});
}
}
pub fn set_sublist_code(p: usize, type_code: i32, flags: i32, skip: i32, cmplx: bool) {
if cmplx {
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_SUBLIST(type_code as wordcode, flags as wordcode, skip as wordcode);
}
});
} else {
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_SUBLIST(
type_code as wordcode,
(flags as wordcode) | WC_SUBLIST_SIMPLE,
skip as wordcode,
);
}
});
ECBUF.with_borrow_mut(|b| {
if p + 1 < b.len() {
b[p + 1] = WC_PIPE_LINENO(b[p + 1]);
}
});
}
}
pub fn ecadd(c: u32) -> usize {
if (ECLEN.get() - ECUSED.get()) < 1 {
let cur = ECLEN.get();
let a = if cur < EC_DOUBLE_THRESHOLD {
cur
} else {
EC_INCREMENT
};
ECBUF.with_borrow_mut(|buf| {
buf.resize((cur + a) as usize, 0);
});
ECLEN.set(cur + a);
}
let idx = ECUSED.get();
ECBUF.with_borrow_mut(|buf| {
if (idx as usize) >= buf.len() {
buf.resize((idx + 1) as usize, 0);
}
buf[idx as usize] = c;
});
ECUSED.set(idx + 1);
idx as usize
}
pub fn ecdel(p: usize) {
let n = ECUSED.get() as usize - p - 1;
if n > 0 {
ECBUF.with_borrow_mut(|buf| {
for i in 0..n {
buf[p + i] = buf[p + i + 1];
}
});
}
ECUSED.set(ECUSED.get() - 1);
ecadjusthere(p, -1);
}
pub fn ecstrcode(s: &str) -> u32 {
let mut c_bytes: Vec<u8> = Vec::with_capacity(s.len());
for ch in s.chars() {
let cu = ch as u32;
if cu <= 0xff {
c_bytes.push(cu as u8);
} else {
let mut tmp = [0u8; 4];
c_bytes.extend_from_slice(ch.encode_utf8(&mut tmp).as_bytes());
}
}
let t = c_bytes.iter().any(|&b| (0x83..=0x9f).contains(&b));
let l = c_bytes.len() + 1; if l <= 4 {
let mut c: u32 = if t { 3 } else { 2 };
match l {
4 => {
c |= (c_bytes[2] as u32) << 19;
c |= (c_bytes[1] as u32) << 11;
c |= (c_bytes[0] as u32) << 3;
}
3 => {
c |= (c_bytes[1] as u32) << 11;
c |= (c_bytes[0] as u32) << 3;
}
2 => {
c |= (c_bytes[0] as u32) << 3;
}
1 => {
c = if t { 7 } else { 6 };
}
_ => {}
}
c
} else {
let key = (ECNFUNC.get(), s.to_string());
if let Some(&offs) = ECSTRS_INDEX.with_borrow(|m| m.get(&key).copied()).as_ref() {
return offs;
}
let offs =
(((ECSOFFS.get() - ECSSUB.get()) as u32) << 2) | if t { 1 } else { 0 };
ECSTRS_INDEX.with_borrow_mut(|m| {
m.insert(key, offs);
});
let stored = c_bytes.clone();
let stored_len = stored.len();
ECSTRS_REVERSE.with_borrow_mut(|m| {
m.insert(offs, stored);
});
let _ = l;
ECSOFFS.set(ECSOFFS.get() + (stored_len + 1) as i32);
offs
}
}
pub fn ecgetstr_wordcode(buf: &[u32], pc: usize) -> (String, usize) {
if pc >= buf.len() {
return (String::new(), pc);
}
let c = buf[pc];
let next = pc + 1;
if c == 6 || c == 7 {
return (String::new(), next);
}
if (c & 2) != 0 {
let b0 = ((c >> 3) & 0xff) as u8;
let b1 = ((c >> 11) & 0xff) as u8;
let b2 = ((c >> 19) & 0xff) as u8;
let mut bytes: Vec<u8> = Vec::new();
for b in [b0, b1, b2] {
if b == 0 {
break;
}
bytes.push(b);
}
return (String::from_utf8_lossy(&bytes).into_owned(), next);
}
let s = ECSTRS_REVERSE
.with_borrow(|m| m.get(&c).cloned())
.map(|v| String::from_utf8_lossy(&v).into_owned())
.unwrap_or_default();
(s, next)
}
pub fn ecispace(p: usize, n: usize) {
let need = n as i32;
if (ECLEN.get() - ECUSED.get()) < need {
let cur = ECLEN.get();
let mut a = if cur < EC_DOUBLE_THRESHOLD {
cur
} else {
EC_INCREMENT
};
if need > a {
a = need;
}
ECBUF.with_borrow_mut(|buf| {
buf.resize((cur + a) as usize, 0);
});
ECLEN.set(cur + a);
}
let m = ECUSED.get() as usize - p;
if m > 0 {
ECBUF.with_borrow_mut(|buf| {
let needed = (ECUSED.get() as usize) + n;
if buf.len() < needed {
buf.resize(needed, 0);
}
for i in (0..m).rev() {
buf[p + n + i] = buf[p + i];
}
for i in 0..n {
buf[p + i] = 0;
}
});
}
ECUSED.set(ECUSED.get() + need);
ecadjusthere(p, need);
}
#[allow(unused_variables)]
pub fn ecadjusthere(p: usize, d: i32) {
}
pub fn dupeprog(p: &crate::ported::zsh_h::eprog, heap: bool) -> crate::ported::zsh_h::eprog {
let dummy_pat = || crate::ported::zsh_h::patprog {
startoff: 0,
size: 0,
mustoff: 0,
patmlen: 0,
globflags: 0,
globend: 0,
flags: 0,
patnpar: 0,
patstartch: 0,
};
let r = crate::ported::zsh_h::eprog {
flags: (if heap { EF_HEAP } else { EF_REAL }) | (p.flags & EF_RUN),
len: p.len,
npats: p.npats,
nref: if heap { -1 } else { 1 },
prog: p.prog.clone(),
strs: p.strs.clone(),
pats: (0..p.npats).map(|_| Box::new(dummy_pat())).collect(),
shf: None,
dump: None,
};
r
}
pub fn useeprog(p: &mut crate::ported::zsh_h::eprog) {
if p.nref >= 0 {
p.nref += 1; }
}
pub fn freeeprog(p: &mut crate::ported::zsh_h::eprog) {
if p.nref > 0 {
p.nref -= 1; if p.nref == 0 {
if let Some(dump) = p.dump.take() {
let dumped = (*dump).clone();
decrdumpcount(&dumped); }
p.prog.clear();
p.strs = None;
p.pats.clear();
}
}
}
pub fn ecrawstr(p: &eprog, pc: usize, tokflag: Option<&mut i32>) -> String {
if pc >= p.prog.len() {
return String::new();
}
let c = p.prog[pc]; if let Some(tf) = tokflag {
*tf = i32::from((c & 1) != 0); }
if c == 6 || c == 7 {
return String::new();
}
if (c & 2) != 0 {
let b0 = ((c >> 3) & 0xff) as u8;
let b1 = ((c >> 11) & 0xff) as u8;
let b2 = ((c >> 19) & 0xff) as u8;
let mut v = vec![b0, b1, b2];
v.retain(|&x| x != 0);
String::from_utf8_lossy(&v).into_owned()
} else {
let off = (c >> 2) as usize;
let strs_bytes = p.strs.as_deref().unwrap_or("").as_bytes();
if off >= strs_bytes.len() {
return String::new();
}
let tail = &strs_bytes[off..];
let end = tail.iter().position(|&b| b == 0).unwrap_or(tail.len());
String::from_utf8_lossy(&tail[..end]).into_owned()
}
}
pub fn ecgetarr(s: &mut estate, num: usize, dup: i32, tokflag: Option<&mut i32>) -> Vec<String> {
let mut ret: Vec<String> = Vec::with_capacity(num); let mut tf: i32 = 0;
for _ in 0..num {
let mut tmp = 0;
ret.push(ecgetstr(s, dup, Some(&mut tmp))); tf |= tmp; }
if let Some(out) = tokflag {
*out = tf;
}
ret
}
pub fn ecgetlist(
s: &mut crate::ported::zsh_h::estate,
num: usize,
dup: i32,
tokflag: Option<&mut i32>,
) -> Vec<String> {
if num == 0 {
if let Some(tf) = tokflag {
*tf = 0;
}
return Vec::new();
}
ecgetarr(s, num, dup, tokflag)
}
pub fn eccopyredirs(s: &mut crate::ported::zsh_h::estate) -> Option<crate::ported::zsh_h::eprog> {
let prog_len = s.prog.prog.len();
if s.pc >= prog_len {
return None;
}
let first_code = s.prog.prog[s.pc];
if wc_code(first_code) != WC_REDIR {
return None;
}
init_parse();
let mut probe = s.pc;
let mut ncodes = 0usize;
loop {
if probe >= prog_len {
break;
}
let code = s.prog.prog[probe];
if wc_code(code) != WC_REDIR {
break;
}
let mut ncode = if WC_REDIR_FROM_HEREDOC(code) != 0 {
5
} else {
3
};
if WC_REDIR_VARID(code) != 0 {
ncode += 1;
}
probe += ncode;
ncodes += ncode;
}
let r0 = ECUSED.get() as usize;
ecispace(r0, ncodes);
let mut r = r0;
loop {
if s.pc >= prog_len {
break;
}
let code = s.prog.prog[s.pc];
if wc_code(code) != WC_REDIR {
break;
}
s.pc += 1;
ECBUF.with_borrow_mut(|buf| {
if r >= buf.len() {
buf.resize(r + 1, 0);
}
buf[r] = code;
});
r += 1;
let fd1 = s.prog.prog[s.pc];
s.pc += 1;
ECBUF.with_borrow_mut(|buf| {
if r >= buf.len() {
buf.resize(r + 1, 0);
}
buf[r] = fd1;
});
r += 1;
let name = ecgetstr(s, EC_NODUP, None);
let nc = ecstrcode(&name);
ECBUF.with_borrow_mut(|buf| {
if r >= buf.len() {
buf.resize(r + 1, 0);
}
buf[r] = nc;
});
r += 1;
if WC_REDIR_FROM_HEREDOC(code) != 0 {
let term = ecgetstr(s, EC_NODUP, None);
let tc = ecstrcode(&term);
ECBUF.with_borrow_mut(|buf| {
if r >= buf.len() {
buf.resize(r + 1, 0);
}
buf[r] = tc;
});
r += 1;
let munged = ecgetstr(s, EC_NODUP, None);
let mc = ecstrcode(&munged);
ECBUF.with_borrow_mut(|buf| {
if r >= buf.len() {
buf.resize(r + 1, 0);
}
buf[r] = mc;
});
r += 1;
}
if WC_REDIR_VARID(code) != 0 {
let varid = ecgetstr(s, EC_NODUP, None);
let vc = ecstrcode(&varid);
ECBUF.with_borrow_mut(|buf| {
if r >= buf.len() {
buf.resize(r + 1, 0);
}
buf[r] = vc;
});
r += 1;
}
}
Some(bld_eprog(false))
}
pub static DUMMY_EPROG: std::sync::Mutex<crate::ported::zsh_h::eprog> =
std::sync::Mutex::new(crate::ported::zsh_h::eprog {
flags: 0,
len: 0,
npats: 0,
nref: 0,
prog: Vec::new(),
strs: None,
pats: Vec::new(),
shf: None,
dump: None,
});
pub fn init_eprog() {
let mut d = DUMMY_EPROG.lock().unwrap();
d.prog = vec![crate::ported::zsh_h::WCB_END()]; d.len = std::mem::size_of::<wordcode>() as i32; d.strs = None; d.flags = 0;
d.npats = 0;
d.nref = 0;
}
pub fn parse() -> ZshProgram {
zshlex();
let mut program = parse_program_until(None);
if let Some(msg) = crate::ported::lex::error() {
crate::ported::utils::zerr(&msg);
}
let bodies: Vec<HereDocInfo> = heredocs_clone()
.into_iter()
.map(|h| HereDocInfo {
content: h.content,
terminator: h.terminator,
quoted: h.quoted,
})
.collect();
if !bodies.is_empty() {
fill_heredoc_bodies(&mut program, &bodies);
}
program
}
pub fn par_event_wordcode() -> usize {
let start = ECUSED.get() as usize;
while tok() != ENDINPUT && tok() != LEXERR {
par_list_wordcode();
match tok() {
SEMI | NEWLIN | AMPER | AMPERBANG | SEPER => {
zshlex();
}
_ => break,
}
}
ecadd(crate::ported::zsh_h::WCB_END());
start
}
thread_local! {
static PARSER_CMPLX: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
static PARSER_INPARTIME: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
#[inline]
fn cmplx_get() -> bool {
PARSER_CMPLX.with(|c| c.get())
}
#[inline]
fn cmplx_or(b: bool) {
PARSER_CMPLX.with(|c| c.set(c.get() | b));
}
#[inline]
fn cmplx_set(b: bool) {
PARSER_CMPLX.with(|c| c.set(b));
}
pub fn par_list_wordcode() {
let mut lp: Option<usize> = None;
loop {
while tok() == SEPER {
zshlex();
}
let p = ecadd(0);
let outer = cmplx_get();
cmplx_set(false);
let sublist_ok = par_sublist_wordcode();
let c = cmplx_get();
cmplx_set(outer | c);
if sublist_ok {
let t = tok();
if t == SEPER || t == AMPER || t == AMPERBANG {
if t != SEPER {
cmplx_set(true);
}
let z = if t == SEPER {
Z_SYNC
} else if t == AMPER {
Z_ASYNC
} else {
Z_ASYNC | Z_DISOWN
};
set_list_code(p, z, c);
set_incmdpos(true);
loop {
zshlex();
if tok() != SEPER {
break;
}
}
lp = Some(p);
continue; } else {
set_list_code(p, Z_SYNC | Z_END, c);
}
} else {
ECUSED.set((ECUSED.get() - 1).max(0));
if let Some(prev) = lp {
ECBUF.with_borrow_mut(|b| {
if prev < b.len() {
b[prev] |= wc_bdata(Z_END as wordcode);
}
});
}
}
break;
}
}
pub fn par_list1_wordcode() {
let p = ecadd(0);
let outer = cmplx_get();
cmplx_set(false);
let ok = par_sublist_wordcode();
let c = cmplx_get();
cmplx_set(outer | c);
if ok {
set_list_code(p, Z_SYNC | Z_END, c);
} else {
ECUSED.set((ECUSED.get() - 1).max(0));
}
}
pub fn par_sublist_wordcode() -> bool {
let p = ecadd(0);
let outer = cmplx_get();
cmplx_set(false);
let mut c2 = 0i32;
let f = par_sublist2(&mut c2);
let c = c2 != 0;
cmplx_set(outer | c);
match f {
Some(flags) => {
let e = ECUSED.get() as usize;
if tok() == DBAR || tok() == DAMPER {
let qtok = tok();
cmdpush(if qtok == DBAR {
CS_CMDOR as u8
} else {
CS_CMDAND as u8
});
zshlex();
while tok() == SEPER {
zshlex();
}
let sl = par_sublist_wordcode();
let st = if sl {
if qtok == DBAR {
WC_SUBLIST_OR
} else {
WC_SUBLIST_AND
}
} else {
WC_SUBLIST_END
};
set_sublist_code(p, st as i32, flags, (e - 1 - p) as i32, c);
cmdpop();
} else {
let c_final = if tok() == AMPER || tok() == AMPERBANG {
cmplx_set(true);
true
} else {
c
};
set_sublist_code(p, WC_SUBLIST_END as i32, flags, (e - 1 - p) as i32, c_final);
}
true
}
None => {
ECUSED.set((ECUSED.get() - 1).max(0));
false
}
}
}
pub fn par_pipe_wordcode() -> bool {
let line = toklineno() as i64;
let p = ecadd(0);
if !par_cmd_wordcode(false) {
ECUSED.set((ECUSED.get() - 1).max(0));
return false;
}
if tok() == BAR_TOK {
cmplx_set(true);
cmdpush(CS_PIPE as u8);
zshlex();
while tok() == SEPER {
zshlex();
}
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_PIPE(
WC_PIPE_MID,
if line >= 0 { (line + 1) as wordcode } else { 0 },
);
}
});
ecispace(p + 1, 1);
let used = ECUSED.get() as usize;
ECBUF.with_borrow_mut(|b| {
if p + 1 < b.len() {
b[p + 1] = (used.saturating_sub(1 + p)) as wordcode;
}
});
if !par_pipe_wordcode() {
set_tok(LEXERR);
}
cmdpop();
true
} else if tok() == BARAMP {
let mut r = p + 1;
loop {
let code = ECBUF.with_borrow(|b| b.get(r).copied().unwrap_or(0));
if wc_code(code) != WC_REDIR {
break;
}
r += WC_REDIR_WORDS(code) as usize;
}
ecispace(r, 3);
ECBUF.with_borrow_mut(|b| {
if r + 2 < b.len() {
b[r] = WCB_REDIR(REDIR_MERGEOUT as wordcode);
b[r + 1] = 2;
b[r + 2] = ecstrcode("1");
}
});
cmplx_set(true);
cmdpush(CS_ERRPIPE as u8);
zshlex();
while tok() == SEPER {
zshlex();
}
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_PIPE(
WC_PIPE_MID,
if line >= 0 { (line + 1) as wordcode } else { 0 },
);
}
});
ecispace(p + 1, 1);
let used = ECUSED.get() as usize;
ECBUF.with_borrow_mut(|b| {
if p + 1 < b.len() {
b[p + 1] = (used.saturating_sub(1 + p)) as wordcode;
}
});
if !par_pipe_wordcode() {
set_tok(LEXERR);
}
cmdpop();
true
} else {
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_PIPE(
WC_PIPE_END,
if line >= 0 { (line + 1) as wordcode } else { 0 },
);
}
});
true
}
}
pub fn par_cmd_wordcode(zsh_construct: bool) -> bool {
let mut nr = 0i32;
let mut r = ECUSED.get();
if IS_REDIROP(tok()) {
cmplx_set(true);
while IS_REDIROP(tok()) {
if let Some(_) = par_redir() {
nr += 1;
} else {
break;
}
}
}
match tok() {
FOR => {
cmdpush(CS_FOR as u8);
par_for_wordcode();
cmdpop();
}
FOREACH => {
cmdpush(CS_FOREACH as u8);
par_for_wordcode();
cmdpop();
}
SELECT => {
cmplx_set(true);
cmdpush(CS_SELECT as u8);
par_for_wordcode();
cmdpop();
}
CASE => {
cmdpush(CS_CASE as u8);
par_case_wordcode();
cmdpop();
}
IF => {
par_if_wordcode();
}
WHILE => {
cmdpush(CS_WHILE as u8);
par_while_wordcode();
cmdpop();
}
UNTIL => {
cmdpush(CS_UNTIL as u8);
par_while_wordcode();
cmdpop();
}
REPEAT => {
cmdpush(CS_REPEAT as u8);
par_repeat_wordcode();
cmdpop();
}
INPAR_TOK => {
cmplx_set(true);
cmdpush(CS_SUBSH as u8);
par_subsh_wordcode_impl(zsh_construct);
cmdpop();
}
INBRACE_TOK => {
cmdpush(CS_CURSH as u8);
par_subsh_wordcode_impl(zsh_construct);
cmdpop();
}
FUNC => {
cmdpush(CS_FUNCDEF as u8);
par_funcdef_wordcode();
cmdpop();
}
DINBRACK => {
cmdpush(CS_COND as u8);
par_cond_wordcode();
cmdpop();
}
DINPAR => {
par_arith_wordcode();
}
TIME => {
if !PARSER_INPARTIME.with(|c| c.get()) {
cmplx_set(true);
PARSER_INPARTIME.with(|c| c.set(true));
par_time_wordcode();
PARSER_INPARTIME.with(|c| c.set(false));
} else {
set_tok(STRING_LEX);
let sr = par_simple_wordcode_impl(nr);
if sr == 0 && nr == 0 {
return false;
}
if sr > 1 {
cmplx_set(true);
r += sr - 1;
}
}
}
_ => {
let sr = par_simple_wordcode_impl(nr);
if sr == 0 {
if nr == 0 {
return false;
}
} else if sr > 1 {
cmplx_set(true);
r += sr - 1;
}
}
}
if IS_REDIROP(tok()) {
cmplx_set(true);
while IS_REDIROP(tok()) {
let _ = par_redir();
}
}
set_incmdpos(true);
set_incasepat(0);
set_incond(0);
set_intypeset(false);
let _ = r;
true
}
pub fn par_cmd_wordcode_noargs() {
par_cmd_wordcode(false);
}
pub fn par_for_wordcode() {
let csh = tok() == FOREACH;
let sel = tok() == SELECT;
let p = ecadd(0);
set_incmdpos(false);
set_infor(if tok() == FOR { 2 } else { 0 });
zshlex();
let type_code: wordcode;
if tok() == DINPAR {
zshlex();
if tok() != DINPAR {
error("par_for: expected init");
return;
}
ecstr(&tokstr().unwrap_or_default());
zshlex();
if tok() != DINPAR {
error("par_for: expected cond");
return;
}
ecstr(&tokstr().unwrap_or_default());
zshlex();
if tok() != DOUTPAR {
error("par_for: expected ))");
return;
}
ecstr(&tokstr().unwrap_or_default());
set_infor(0);
set_incmdpos(true);
zshlex();
type_code = WC_FOR_COND;
} else {
set_infor(0);
if tok() != STRING_LEX {
error("par_for: expected identifier");
return;
}
let np = if !sel { Some(ecadd(0)) } else { None };
let mut n = 0u32;
set_incmdpos(true);
loop {
n += 1;
ecstr(&tokstr().unwrap_or_default());
zshlex();
if tok() != STRING_LEX || sel {
break;
}
if tokstr().as_deref() == Some("in") {
break;
}
}
if let Some(np) = np {
ECBUF.with_borrow_mut(|b| {
if np < b.len() {
b[np] = n;
}
});
}
let posix_in = isnewlin() != 0;
while isnewlin() != 0 {
zshlex();
}
if tok() == STRING_LEX && tokstr().as_deref() == Some("in") {
set_incmdpos(false);
zshlex();
let np = ecadd(0);
let mut n = 0u32;
while tok() == STRING_LEX {
if let Some(s) = tokstr() {
ecstr(&s);
}
n += 1;
zshlex();
}
if tok() != SEPER {
error("par_for: expected separator after `in`");
return;
}
ECBUF.with_borrow_mut(|b| {
if np < b.len() {
b[np] = n as wordcode;
}
});
type_code = if sel { WC_SELECT_LIST } else { WC_FOR_LIST };
} else if !posix_in && tok() == INPAR_TOK {
set_incmdpos(false);
zshlex();
let np = ecadd(0);
let mut n = 0u32;
while tok() == NEWLIN {
zshlex();
}
while tok() == STRING_LEX {
if let Some(s) = tokstr() {
ecstr(&s);
}
n += 1;
zshlex();
}
while tok() == NEWLIN {
zshlex();
}
if tok() != OUTPAR_TOK {
error("par_for: expected `)`");
return;
}
ECBUF.with_borrow_mut(|b| {
if np < b.len() {
b[np] = n as wordcode;
}
});
set_incmdpos(true);
zshlex();
type_code = if sel { WC_SELECT_LIST } else { WC_FOR_LIST };
} else {
type_code = if sel { WC_SELECT_PPARAM } else { WC_FOR_PPARAM };
}
}
set_incmdpos(true);
while tok() == SEPER {
zshlex();
}
par_loop_body_wordcode(csh);
let used = ECUSED.get() as usize;
let off = used.saturating_sub(1 + p) as wordcode;
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = if sel {
WCB_SELECT(type_code, off)
} else {
WCB_FOR(type_code, off)
};
}
});
}
fn par_loop_body_wordcode(csh: bool) {
if tok() == DOLOOP {
zshlex();
par_list_wordcode();
if tok() != DONE {
error("missing `done`");
return;
}
set_incmdpos(false);
zshlex();
} else if tok() == INBRACE_TOK {
zshlex();
par_list_wordcode();
if tok() != OUTBRACE_TOK {
error("missing `}`");
return;
}
set_incmdpos(false);
zshlex();
} else if csh || isset(CSHJUNKIELOOPS) {
par_list_wordcode();
if tok() != ZEND {
error("missing `end`");
return;
}
set_incmdpos(false);
zshlex();
} else if unset(SHORTLOOPS) {
error("short loop form requires SHORTLOOPS");
} else {
par_list1_wordcode();
}
}
pub fn par_select_wordcode() {
par_for_wordcode();
}
pub fn par_case_wordcode() {
let p = ecadd(0);
set_incmdpos(false);
zshlex();
if tok() != STRING_LEX {
error("par_case: expected scrutinee");
return;
}
ecstr(&tokstr().unwrap_or_default());
set_incmdpos(true);
zshlex();
while tok() == SEPER {
zshlex();
}
let saw_brace = tok() == INBRACE_TOK;
if !saw_brace && !(tok() == STRING_LEX && tokstr().as_deref() == Some("in")) {
error("par_case: expected `in` or `{`");
return;
}
zshlex();
loop {
while tok() == SEPER {
zshlex();
}
if (saw_brace && tok() == OUTBRACE_TOK)
|| (!saw_brace && tok() == STRING_LEX && tokstr().as_deref() == Some("esac"))
{
zshlex();
break;
}
if tok() == INPAR_TOK {
zshlex();
}
let arm = ecadd(0);
let np = ecadd(0);
let mut pat_n = 0u32;
loop {
if tok() != STRING_LEX {
error("par_case: expected pattern");
return;
}
ecstr(&tokstr().unwrap_or_default());
pat_n += 1;
zshlex();
if tok() != BAR_TOK {
break;
}
zshlex();
}
ECBUF.with_borrow_mut(|b| {
if np < b.len() {
b[np] = pat_n;
}
});
if tok() != OUTPAR_TOK {
error("par_case: expected `)`");
return;
}
set_incmdpos(true);
zshlex();
par_list_wordcode();
let arm_type = match tok() {
DSEMI => WC_CASE_OR,
SEMIAMP => WC_CASE_AND,
SEMIBAR => WC_CASE_TESTAND,
_ => WC_CASE_OR,
};
let used = ECUSED.get() as usize;
let arm_off = used.saturating_sub(1 + arm) as wordcode;
ECBUF.with_borrow_mut(|b| {
if arm < b.len() {
b[arm] = (arm_type as wordcode) | (arm_off << 2);
}
});
if tok() == DSEMI || tok() == SEMIAMP || tok() == SEMIBAR {
zshlex();
}
}
let used = ECUSED.get() as usize;
let off = used.saturating_sub(1 + p) as wordcode;
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_CASE(WC_CASE_HEAD, off);
}
});
}
pub fn par_if_wordcode() {
let p = ecadd(0);
cmdpush(CS_IF as u8);
loop {
let arm = ecadd(0);
zshlex();
par_list_wordcode();
let body_brace = tok() == INBRACE_TOK;
if !body_brace {
while tok() == SEPER {
zshlex();
}
if tok() != THEN {
error("par_if: expected `then`");
cmdpop();
return;
}
}
cmdpop();
cmdpush(CS_IFTHEN as u8);
zshlex();
par_list_wordcode();
cmdpop();
let used = ECUSED.get() as usize;
let arm_off = used.saturating_sub(1 + arm) as wordcode;
ECBUF.with_borrow_mut(|b| {
if arm < b.len() {
b[arm] = WCB_IF(WC_IF_IF, arm_off);
}
});
match tok() {
ELIF => {
cmdpush(CS_ELIF as u8);
continue;
}
ELSE => {
cmdpush(CS_ELSE as u8);
let arm = ecadd(0);
zshlex();
par_list_wordcode();
let used = ECUSED.get() as usize;
let arm_off = used.saturating_sub(1 + arm) as wordcode;
ECBUF.with_borrow_mut(|b| {
if arm < b.len() {
b[arm] = WCB_IF(WC_IF_IF, arm_off);
}
});
cmdpop();
if tok() != FI {
error("par_if: expected `fi`");
return;
}
zshlex();
break;
}
FI => {
zshlex();
break;
}
_ => {
if body_brace && tok() == OUTBRACE_TOK {
zshlex();
break;
}
error("par_if: expected `elif`/`else`/`fi`");
return;
}
}
}
let used = ECUSED.get() as usize;
let off = used.saturating_sub(1 + p) as wordcode;
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_IF(WC_IF_HEAD, off);
}
});
}
pub fn par_while_wordcode() {
let until = tok() == UNTIL;
let p = ecadd(0);
zshlex();
par_list_wordcode();
while tok() == SEPER {
zshlex();
}
par_loop_body_wordcode(false);
let type_code = if until {
WC_WHILE_UNTIL
} else {
WC_WHILE_WHILE
};
let used = ECUSED.get() as usize;
let off = used.saturating_sub(1 + p) as wordcode;
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_WHILE(type_code, off);
}
});
}
pub fn par_until_wordcode() {
par_while_wordcode();
}
pub fn par_repeat_wordcode() {
let p = ecadd(0);
set_incmdpos(false);
zshlex();
if tok() != STRING_LEX {
error("par_repeat: expected count");
return;
}
ecstr(&tokstr().unwrap_or_default());
set_incmdpos(true);
zshlex();
while tok() == SEPER {
zshlex();
}
par_loop_body_wordcode(false);
let used = ECUSED.get() as usize;
let off = used.saturating_sub(1 + p) as wordcode;
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_REPEAT(off);
}
});
}
pub fn par_funcdef_wordcode() {
let p = ecadd(0);
zshlex();
let np = ecadd(0);
let mut n = 0u32;
set_incmdpos(false);
while tok() == STRING_LEX {
ecstr(&tokstr().unwrap_or_default());
n += 1;
zshlex();
}
ECBUF.with_borrow_mut(|b| {
if np < b.len() {
b[np] = n;
}
});
set_incmdpos(true);
if tok() == INOUTPAR {
zshlex();
}
while tok() == SEPER {
zshlex();
}
if tok() == INBRACE_TOK {
zshlex();
par_list_wordcode();
if tok() != OUTBRACE_TOK {
error("par_funcdef: expected `}`");
return;
}
zshlex();
} else if unset(SHORTLOOPS) {
error("par_funcdef: short body requires SHORTLOOPS");
return;
} else {
par_list1_wordcode();
}
let used = ECUSED.get() as usize;
let off = used.saturating_sub(1 + p) as wordcode;
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_FUNCDEF(off);
}
});
}
pub fn par_subsh_wordcode_impl(zsh_construct: bool) {
let otok = tok();
let p = ecadd(0);
let pp = ecadd(0);
zshlex();
par_list_wordcode();
ecadd(WCB_END());
let want = if otok == INPAR_TOK {
OUTPAR_TOK
} else {
OUTBRACE_TOK
};
if tok() != want {
error("par_subsh: missing closing token");
return;
}
set_incmdpos(!zsh_construct);
zshlex();
let is_always =
otok == INBRACE_TOK && tok() == STRING_LEX && tokstr().as_deref() == Some("always");
if is_always {
let used = ECUSED.get() as usize;
let off = used.saturating_sub(1 + pp);
ECBUF.with_borrow_mut(|b| {
if pp < b.len() {
b[pp] = WCB_TRY(off as wordcode);
}
});
set_incmdpos(true);
loop {
zshlex();
if tok() != SEPER {
break;
}
}
if tok() != INBRACE_TOK {
error("par_subsh: 'always' expects '{'");
return;
}
zshlex();
par_list_wordcode();
while tok() == SEPER {
zshlex();
}
set_incmdpos(true);
if tok() != OUTBRACE_TOK {
error("par_subsh: 'always' block missing '}'");
return;
}
zshlex();
let used = ECUSED.get() as usize;
let off = used.saturating_sub(1 + p);
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_TRY(off as wordcode);
}
});
} else {
let used = ECUSED.get() as usize;
let off = used.saturating_sub(1 + p);
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = if otok == INPAR_TOK {
WCB_SUBSH(off as wordcode)
} else {
WCB_CURSH(off as wordcode)
};
}
});
}
}
pub fn par_subsh_wordcode() {
par_subsh_wordcode_impl(false);
}
pub fn par_cursh_wordcode() {
par_subsh_wordcode_impl(true);
}
pub fn par_time_wordcode() {
zshlex();
let p = ecadd(0);
ecadd(0);
let mut c = 0i32;
let f = par_sublist2(&mut c);
match f {
Some(flags) => {
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_TIMED(WC_TIMED_PIPE);
}
});
let used = ECUSED.get() as usize;
let skip = used.saturating_sub(2 + p) as i32;
set_sublist_code(p + 1, WC_SUBLIST_END as i32, flags, skip, c != 0);
}
None => {
ECUSED.set((ECUSED.get() - 1).max(0));
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_TIMED(WC_TIMED_EMPTY);
}
});
}
}
}
pub fn par_cond_wordcode() {
let oecused = ECUSED.get();
set_incond(1);
set_incmdpos(false);
zshlex();
let _ = par_cond();
if tok() != DOUTBRACK {
let _ = oecused;
error("missing ]]");
return;
}
set_incond(0);
set_incmdpos(true);
zshlex();
}
pub fn par_arith_wordcode() {
ecadd(WCB_ARITH());
let expr = tokstr().unwrap_or_default();
ecstr(&expr);
zshlex();
}
pub fn par_simple_wordcode_impl(_nr: i32) -> i32 {
let p = ecadd(0);
let mut nwords: u32 = 0;
let mut redir_words: i32 = 0;
loop {
match tok() {
STRING_LEX | ENVSTRING | TYPESET => {
cmplx_set(true);
let s = tokstr().unwrap_or_default();
let coded = ecstrcode(&s);
ecadd(coded);
nwords += 1;
zshlex();
}
t if IS_REDIROP(t) => {
if let Some(_) = par_redir() {
redir_words += 3;
} else {
break;
}
}
_ => break,
}
}
if nwords == 0 && redir_words == 0 {
ECUSED.set(p as i32);
return 0;
}
ECBUF.with_borrow_mut(|b| {
if p < b.len() {
b[p] = WCB_SIMPLE(nwords);
}
});
1 + redir_words
}
pub fn par_simple_wordcode() {
par_simple_wordcode_impl(0);
}
fn parse_program() -> ZshProgram {
parse_program_until(None)
}
fn parse_program_until(end_tokens: Option<&[lextok]>) -> ZshProgram {
let mut lists = Vec::new();
loop {
if check_limit() {
error("parser exceeded global iteration limit");
break;
}
while tok() == SEPER || tok() == NEWLIN {
if check_limit() {
error("parser exceeded global iteration limit");
return ZshProgram { lists };
}
zshlex();
}
if tok() == ENDINPUT || tok() == LEXERR {
break;
}
if let Some(end_toks) = end_tokens {
if end_toks.contains(&tok()) {
break;
}
}
match tok() {
OUTBRACE_TOK | DSEMI | SEMIAMP | SEMIBAR | DONE | FI | ESAC | ZEND => break,
_ => {}
}
match par_list() {
Some(list) => {
let detected = simple_name_with_inoutpar(&list);
lists.push(list);
if let Some((names, body_argv)) = detected {
if !body_argv.is_empty() {
lists.pop();
let body_simple = ZshCommand::Simple(ZshSimple {
assigns: Vec::new(),
words: body_argv,
redirs: Vec::new(),
});
let body_list = ZshList {
sublist: ZshSublist {
pipe: ZshPipe {
cmd: body_simple,
next: None,
lineno: lineno(),
merge_stderr: false,
},
next: None,
flags: SublistFlags::default(),
},
flags: ListFlags::default(),
};
let funcdef = ZshCommand::FuncDef(ZshFuncDef {
names,
body: Box::new(ZshProgram {
lists: vec![body_list],
}),
tracing: false,
auto_call_args: None,
body_source: None,
});
let synthetic = ZshList {
sublist: ZshSublist {
pipe: ZshPipe {
cmd: funcdef,
next: None,
lineno: lineno(),
merge_stderr: false,
},
next: None,
flags: SublistFlags::default(),
},
flags: ListFlags::default(),
};
lists.push(synthetic);
continue;
}
while tok() == SEPER || tok() == NEWLIN {
zshlex();
}
if tok() == INBRACE_TOK {
let body_start = pos();
zshlex();
let body = parse_program();
let body_end = if tok() == OUTBRACE_TOK {
pos().saturating_sub(1)
} else {
pos()
};
let body_source = input_slice(body_start, body_end)
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty());
if tok() == OUTBRACE_TOK {
zshlex();
}
lists.pop();
let funcdef = ZshCommand::FuncDef(ZshFuncDef {
names,
body: Box::new(body),
tracing: false,
auto_call_args: None,
body_source,
});
let synthetic = ZshList {
sublist: ZshSublist {
pipe: ZshPipe {
cmd: funcdef,
next: None,
lineno: lineno(),
merge_stderr: false,
},
next: None,
flags: SublistFlags::default(),
},
flags: ListFlags::default(),
};
lists.push(synthetic);
} else if !matches!(tok(), ENDINPUT | OUTBRACE_TOK | SEPER | NEWLIN) {
let body_cmd = par_cmd();
if let Some(cmd) = body_cmd {
let body_list = ZshList {
sublist: ZshSublist {
pipe: ZshPipe {
cmd,
next: None,
lineno: lineno(),
merge_stderr: false,
},
next: None,
flags: SublistFlags::default(),
},
flags: ListFlags::default(),
};
lists.pop();
let funcdef = ZshCommand::FuncDef(ZshFuncDef {
names: names.clone(),
body: Box::new(ZshProgram {
lists: vec![body_list],
}),
tracing: false,
auto_call_args: None,
body_source: None,
});
let synthetic = ZshList {
sublist: ZshSublist {
pipe: ZshPipe {
cmd: funcdef,
next: None,
lineno: lineno(),
merge_stderr: false,
},
next: None,
flags: SublistFlags::default(),
},
flags: ListFlags::default(),
};
lists.push(synthetic);
}
}
}
}
None => break,
}
}
ZshProgram { lists }
}
fn par_list() -> Option<ZshList> {
let sublist = par_sublist()?;
let flags = match tok() {
AMPER => {
zshlex();
ListFlags {
async_: true,
disown: false,
}
}
AMPERBANG => {
zshlex();
ListFlags {
async_: true,
disown: true,
}
}
SEPER | SEMI | NEWLIN => {
zshlex();
ListFlags::default()
}
_ => ListFlags::default(),
};
Some(ZshList { sublist, flags })
}
fn par_sublist() -> Option<ZshSublist> {
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get() + 1);
if check_recursion() {
error("par_sublist: max recursion depth exceeded");
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
return None;
}
let mut flags = SublistFlags::default();
if tok() == COPROC {
flags.coproc = true;
zshlex();
} else if tok() == BANG_TOK {
flags.not = true;
zshlex();
}
let pipe = match par_pline() {
Some(p) => p,
None => {
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
return None;
}
};
let next = match tok() {
DAMPER => {
zshlex();
skip_separators();
par_sublist().map(|s| (SublistOp::And, Box::new(s)))
}
DBAR => {
zshlex();
skip_separators();
par_sublist().map(|s| (SublistOp::Or, Box::new(s)))
}
_ => None,
};
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
Some(ZshSublist { pipe, next, flags })
}
fn par_pline() -> Option<ZshPipe> {
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get() + 1);
if check_recursion() {
error("par_pline: max recursion depth exceeded");
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
return None;
}
let lineno = toklineno();
let cmd = match par_cmd() {
Some(c) => c,
None => {
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
return None;
}
};
let mut merge_stderr = false;
let next = match tok() {
BAR_TOK | BARAMP => {
merge_stderr = tok() == BARAMP;
zshlex();
skip_separators();
par_pline().map(Box::new)
}
_ => None,
};
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
Some(ZshPipe {
cmd,
next,
lineno,
merge_stderr,
})
}
fn par_cmd() -> Option<ZshCommand> {
let mut redirs = Vec::new();
while IS_REDIROP(tok()) {
if let Some(redir) = par_redir() {
redirs.push(redir);
}
}
let cmd = match tok() {
FOR | FOREACH => par_for(),
SELECT => parse_select(),
CASE => par_case(),
IF => par_if(),
WHILE => par_while(false),
UNTIL => par_while(true),
REPEAT => par_repeat(),
INPAR_TOK => par_subsh(),
INOUTPAR => parse_anon_funcdef(),
INBRACE_TOK => parse_cursh(),
FUNC => par_funcdef(),
DINBRACK => par_cond(),
DINPAR => parse_arith(),
TIME => par_time(),
_ => par_simple(redirs),
};
if let Some(inner) = cmd {
let mut trailing: Vec<ZshRedir> = Vec::new();
while IS_REDIROP(tok()) {
if let Some(redir) = par_redir() {
trailing.push(redir);
}
}
if trailing.is_empty() {
return Some(inner);
}
if matches!(inner, ZshCommand::Simple(_)) {
if let ZshCommand::Simple(mut s) = inner {
s.redirs.extend(trailing);
return Some(ZshCommand::Simple(s));
}
unreachable!()
}
return Some(ZshCommand::Redirected(Box::new(inner), trailing));
}
None
}
fn par_simple(mut redirs: Vec<ZshRedir>) -> Option<ZshCommand> {
let mut assigns = Vec::new();
let mut words = Vec::new();
const MAX_ITERATIONS: usize = 10_000;
let mut iterations = 0;
let saw_brace_fd_candidate = false;
if !isset(IGNOREBRACES) && saw_brace_fd_candidate {
}
while tok() == ENVSTRING || tok() == ENVARRAY {
iterations += 1;
if iterations > MAX_ITERATIONS {
error("par_simple: exceeded max iterations in assignments");
return None;
}
if let Some(assign) = parse_assign() {
assigns.push(assign);
}
zshlex();
}
loop {
iterations += 1;
if iterations > MAX_ITERATIONS {
error("par_simple: exceeded max iterations");
return None;
}
match tok() {
ENVSTRING | ENVARRAY => {
if let Some(assign) = parse_assign() {
let synthetic = match &assign.value {
ZshAssignValue::Scalar(v) => format!("{}={}", assign.name, v),
ZshAssignValue::Array(elems) => {
format!("{}=({})", assign.name, elems.join(" "))
}
};
words.push(synthetic);
}
zshlex();
}
STRING_LEX | TYPESET => {
let s = tokstr();
if let Some(s) = s {
words.push(s);
}
zshlex();
if words.len() == 1 && peek_inoutpar() {
return parse_inline_funcdef(words.pop().unwrap());
}
if !words.is_empty() && IS_REDIROP(tok()) {
let last = words.last().unwrap();
let untoked = super::lex::untokenize(last);
if untoked.starts_with('{') && untoked.ends_with('}') && untoked.len() > 2 {
let name = &untoked[1..untoked.len() - 1];
if !name.is_empty()
&& name.chars().all(|c| c == '_' || c.is_ascii_alphanumeric())
&& name
.chars()
.next()
.map(|c| c == '_' || c.is_ascii_alphabetic())
.unwrap_or(false)
{
let varid = name.to_string();
words.pop();
if let Some(mut redir) = par_redir() {
redir.varid = Some(varid);
redirs.push(redir);
}
continue;
}
}
}
}
_ if IS_REDIROP(tok()) => {
match par_redir() {
Some(redir) => redirs.push(redir),
None => break, }
}
INOUTPAR if !words.is_empty() => {
if !isset(MULTIFUNCDEF) && words.len() > 1 {
error(
"parse error: multiple names in function definition without MULTIFUNCDEF",
);
return None;
}
let had_alias = false;
if isset(EXECOPT) && had_alias && !isset(ALIASFUNCDEF) && !words.is_empty() {
crate::ported::utils::zwarn("defining function based on alias `(unknown)'");
return None;
}
return parse_inline_funcdef(words.pop().unwrap());
}
_ => break,
}
}
if assigns.is_empty() && words.is_empty() && redirs.is_empty() {
return None;
}
Some(ZshCommand::Simple(ZshSimple {
assigns,
words,
redirs,
}))
}
fn parse_assign() -> Option<ZshAssign> {
fn find_assign_equals(s: &str) -> Option<usize> {
let target = crate::ported::zsh_h::Equals;
let mut brace = 0i32;
let mut bracket = 0i32;
let mut paren = 0i32;
for (i, c) in s.char_indices() {
match c {
'{' | '\u{8f}' => brace += 1,
'}' | '\u{90}' => {
if brace > 0 {
brace -= 1;
}
}
'[' | '\u{91}' => bracket += 1,
']' | '\u{92}' => {
if bracket > 0 {
bracket -= 1;
}
}
'(' | '\u{88}' => paren += 1,
')' | '\u{8a}' => {
if paren > 0 {
paren -= 1;
}
}
_ if c == target && brace == 0 && bracket == 0 && paren == 0 => {
return Some(i);
}
_ => {}
}
}
None
}
let _ts_tokstr = tokstr()?;
let tokstr = _ts_tokstr.as_str();
let (name, value_str, append) = if tok() == ENVARRAY {
let (name, append) = if let Some(stripped) = tokstr.strip_suffix('+') {
(stripped, true)
} else {
(tokstr, false)
};
(name.to_string(), String::new(), append)
} else if let Some(pos) = find_assign_equals(tokstr) {
let name_part = &tokstr[..pos];
let (name, append) = if let Some(stripped) = name_part.strip_suffix('+') {
(stripped, true)
} else {
(name_part, false)
};
(
name.to_string(),
tokstr[pos + Equals.len_utf8()..].to_string(),
append,
)
} else if let Some(pos) = tokstr.find('=') {
let name_part = &tokstr[..pos];
let (name, append) = if let Some(stripped) = name_part.strip_suffix('+') {
(stripped, true)
} else {
(name_part, false)
};
(name.to_string(), tokstr[pos + 1..].to_string(), append)
} else {
return None;
};
let value = if tok() == ENVARRAY {
let mut elements = Vec::new();
zshlex();
let mut arr_iters = 0;
const MAX_ARRAY_ELEMENTS: usize = 10_000;
while matches!(tok(), STRING_LEX | SEPER | NEWLIN) {
arr_iters += 1;
if arr_iters > MAX_ARRAY_ELEMENTS {
error("array assignment exceeded maximum elements");
break;
}
if tok() == STRING_LEX {
let _ts_s = crate::ported::lex::tokstr();
if let Some(s) = _ts_s.as_deref() {
elements.push(s.to_string());
}
}
zshlex();
}
if tok() == OUTPAR_TOK {
set_incmdpos(true);
}
ZshAssignValue::Array(elements)
} else {
ZshAssignValue::Scalar(value_str)
};
Some(ZshAssign {
name,
value,
append,
})
}
fn par_redir() -> Option<ZshRedir> {
let rtype = match tok() {
OUTANG_TOK => REDIR_WRITE,
OUTANGBANG => REDIR_WRITENOW,
DOUTANG => REDIR_APP,
DOUTANGBANG => REDIR_APPNOW,
INANG_TOK => REDIR_READ,
INOUTANG => REDIR_READWRITE,
DINANG => REDIR_HEREDOC,
DINANGDASH => REDIR_HEREDOCDASH,
TRINANG => REDIR_HERESTR,
INANGAMP => REDIR_MERGEIN,
OUTANGAMP => REDIR_MERGEOUT,
AMPOUTANG => REDIR_ERRWRITE,
OUTANGAMPBANG => REDIR_ERRWRITENOW,
DOUTANGAMP => REDIR_ERRAPP,
DOUTANGAMPBANG => REDIR_ERRAPPNOW,
_ => return None,
};
let fd = if tokfd() >= 0 {
tokfd()
} else if matches!(
rtype,
REDIR_READ
| REDIR_READWRITE
| REDIR_MERGEIN
| REDIR_HEREDOC
| REDIR_HEREDOCDASH
| REDIR_HERESTR
) {
0
} else {
1
};
zshlex();
let name = match tok() {
STRING_LEX | ENVSTRING => {
let n = tokstr().unwrap_or_default();
zshlex();
n
}
_ => {
error("expected word after redirection");
return None;
}
};
let heredoc_idx = if matches!(rtype, REDIR_HEREDOC | REDIR_HEREDOCDASH) {
let strip_tabs = rtype == REDIR_HEREDOCDASH;
let quoted = name.contains('\u{9d}')
|| name.contains('\u{9e}')
|| name.contains('\u{9f}')
|| name.starts_with('\'')
|| name.starts_with('"');
let term = name
.chars()
.filter(|c| {
*c != '\'' && *c != '"' && *c != '\u{9d}' && *c != '\u{9e}' && *c != '\u{9f}'
})
.collect::<String>();
crate::ported::lex::heredocs_push(crate::ported::lex::HereDoc {
terminator: term,
strip_tabs,
content: String::new(),
quoted,
processed: false,
});
Some(heredocs_len() - 1)
} else {
None
};
Some(ZshRedir {
rtype,
fd,
name,
heredoc: None,
varid: None,
heredoc_idx,
})
}
fn par_for() -> Option<ZshCommand> {
let is_foreach = tok() == FOREACH;
zshlex();
if tok() == DINPAR {
return parse_for_cstyle();
}
let mut names: Vec<String> = Vec::new();
while tok() == STRING_LEX {
let v = tokstr().unwrap_or_default();
if v == "in" {
break;
}
names.push(v);
zshlex();
}
if names.is_empty() {
error("expected variable name in for");
return None;
}
let var = names.join(" ");
skip_separators();
let list = if tok() == STRING_LEX
&& tokstr()
.map(|s| s.starts_with('\u{88}') && s.ends_with('\u{8a}'))
.unwrap_or(false)
{
let raw = tokstr().unwrap_or_default();
let inner = &raw[raw.char_indices().nth(1).map(|(i, _)| i).unwrap_or(0)
..raw
.char_indices()
.last()
.map(|(i, _)| i)
.unwrap_or(raw.len())];
let cleaned = super::lex::untokenize(inner);
let words: Vec<String> = cleaned.split_whitespace().map(|s| s.to_string()).collect();
zshlex();
ForList::Words(words)
} else if tok() == STRING_LEX {
let s = tokstr();
if s.map(|s| s == "in").unwrap_or(false) {
zshlex();
let mut words = Vec::new();
let mut word_count = 0;
while tok() == STRING_LEX {
word_count += 1;
if word_count > 500 || check_limit() {
error("for: too many words");
return None;
}
let _ts_s = tokstr();
if let Some(s) = _ts_s.as_deref() {
words.push(s.to_string());
}
zshlex();
}
ForList::Words(words)
} else {
ForList::Positional
}
} else if tok() == INPAR_TOK {
zshlex();
let mut words = Vec::new();
let mut word_count = 0;
while tok() == STRING_LEX || tok() == SEPER {
word_count += 1;
if word_count > 500 || check_limit() {
error("for: too many words in parens");
return None;
}
if tok() == STRING_LEX {
let _ts_s = tokstr();
if let Some(s) = _ts_s.as_deref() {
words.push(s.to_string());
}
}
zshlex();
}
if tok() == OUTPAR_TOK {
set_incmdpos(true);
zshlex();
}
ForList::Words(words)
} else {
ForList::Positional
};
skip_separators();
let body = parse_loop_body(is_foreach)?;
Some(ZshCommand::For(ZshFor {
var,
list,
body: Box::new(body),
is_select: false,
}))
}
fn parse_for_cstyle() -> Option<ZshCommand> {
zshlex();
if tok() != DINPAR {
error("expected init expression in for ((");
return None;
}
let init = tokstr().unwrap_or_default();
zshlex();
if tok() != DINPAR {
error("expected condition in for ((");
return None;
}
let cond = tokstr().unwrap_or_default();
zshlex();
if tok() != DOUTPAR {
error("expected )) in for");
return None;
}
let step = tokstr().unwrap_or_default();
zshlex();
skip_separators();
let body = parse_loop_body(false)?;
Some(ZshCommand::For(ZshFor {
var: String::new(),
list: ForList::CStyle { init, cond, step },
body: Box::new(body),
is_select: false,
}))
}
fn parse_select() -> Option<ZshCommand> {
match par_for()? {
ZshCommand::For(mut f) => {
f.is_select = true;
Some(ZshCommand::For(f))
}
other => Some(other),
}
}
fn par_case() -> Option<ZshCommand> {
zshlex();
let word = match tok() {
STRING_LEX => {
let w = tokstr().unwrap_or_default();
zshlex();
w
}
_ => {
error("expected word after case");
return None;
}
};
skip_separators();
let use_brace = tok() == INBRACE_TOK;
if tok() == STRING_LEX {
let s = tokstr();
if s.map(|s| s != "in").unwrap_or(true) {
error("expected 'in' in case");
return None;
}
} else if !use_brace {
error("expected 'in' or '{' in case");
return None;
}
set_incasepat(1);
zshlex();
let mut arms = Vec::new();
const MAX_ARMS: usize = 10_000;
loop {
if arms.len() > MAX_ARMS {
error("par_case: too many arms");
break;
}
set_incasepat(1);
skip_separators();
let is_esac = tok() == ESAC
|| (tok() == STRING_LEX && tokstr().map(|s| s == "esac").unwrap_or(false));
if (use_brace && tok() == OUTBRACE_TOK) || (!use_brace && is_esac) {
set_incasepat(0);
zshlex();
break;
}
if tok() == ENDINPUT || tok() == LEXERR {
set_incasepat(0);
break;
}
let had_leading_paren = tok() == INPAR_TOK;
if had_leading_paren {
zshlex();
}
let mut patterns = Vec::new();
let mut pattern_iterations = 0;
loop {
pattern_iterations += 1;
if pattern_iterations > 1000 {
error("par_case: too many pattern iterations");
set_incasepat(0);
return None;
}
if tok() == STRING_LEX {
let s = tokstr();
if s.map(|s| s == "esac").unwrap_or(false) {
break;
}
patterns.push(tokstr().unwrap_or_default());
set_incasepat(2);
zshlex();
} else if tok() != BAR_TOK {
break;
}
if tok() == BAR_TOK {
set_incasepat(1);
zshlex();
} else {
break;
}
}
set_incasepat(0);
if had_leading_paren && patterns.len() > 1 {
let joined = patterns.join("|");
patterns = vec![joined];
}
if tok() != OUTPAR_TOK {
error("expected ')' in case pattern");
return None;
}
set_incmdpos(true);
zshlex();
if had_leading_paren && tok() == OUTPAR_TOK {
set_incmdpos(true);
zshlex();
}
let body = parse_program();
let terminator = match tok() {
DSEMI => {
set_incasepat(1);
zshlex();
CaseTerm::Break
}
SEMIAMP => {
set_incasepat(1);
zshlex();
CaseTerm::Continue
}
SEMIBAR => {
set_incasepat(1);
zshlex();
CaseTerm::TestNext
}
_ => CaseTerm::Break,
};
if !patterns.is_empty() {
arms.push(CaseArm {
patterns,
body,
terminator,
});
}
}
Some(ZshCommand::Case(ZshCase { word, arms }))
}
fn par_if() -> Option<ZshCommand> {
zshlex();
let cond = Box::new(parse_program_until(Some(&[THEN, INBRACE_TOK])));
skip_separators();
let use_brace = tok() == INBRACE_TOK;
if tok() != THEN && !use_brace {
error("expected 'then' or '{' after if condition");
return None;
}
zshlex();
let then = if use_brace {
let body = parse_program_until(Some(&[OUTBRACE_TOK]));
if tok() == OUTBRACE_TOK {
zshlex();
}
Box::new(body)
} else {
Box::new(parse_program_until(Some(&[ELSE, ELIF, FI])))
};
let mut elif = Vec::new();
let mut else_ = None;
{
loop {
skip_separators();
match tok() {
ELIF => {
zshlex();
let econd = parse_program_until(Some(&[THEN, INBRACE_TOK]));
skip_separators();
let elif_use_brace = tok() == INBRACE_TOK;
if tok() != THEN && !elif_use_brace {
error("expected 'then' after elif");
return None;
}
zshlex();
let ebody = if elif_use_brace {
let body = parse_program_until(Some(&[OUTBRACE_TOK]));
if tok() == OUTBRACE_TOK {
zshlex();
}
body
} else {
parse_program_until(Some(&[ELSE, ELIF, FI]))
};
elif.push((econd, ebody));
}
ELSE => {
zshlex();
skip_separators();
let else_use_brace = tok() == INBRACE_TOK;
if else_use_brace {
zshlex();
}
else_ = Some(Box::new(if else_use_brace {
let body = parse_program_until(Some(&[OUTBRACE_TOK]));
if tok() == OUTBRACE_TOK {
zshlex();
}
body
} else {
parse_program_until(Some(&[FI]))
}));
if !else_use_brace && tok() == FI {
zshlex();
}
break;
}
FI => {
zshlex();
break;
}
_ => break,
}
}
}
Some(ZshCommand::If(ZshIf {
cond,
then,
elif,
else_,
}))
}
fn par_while(until: bool) -> Option<ZshCommand> {
zshlex();
let cond = Box::new(parse_program());
skip_separators();
let body = parse_loop_body(false)?;
Some(ZshCommand::While(ZshWhile {
cond,
body: Box::new(body),
until,
}))
}
fn par_repeat() -> Option<ZshCommand> {
zshlex();
let count = match tok() {
STRING_LEX => {
let c = tokstr().unwrap_or_default();
zshlex();
c
}
_ => {
error("expected count after repeat");
return None;
}
};
skip_separators();
let body = parse_loop_body_kind(false, true)?;
Some(ZshCommand::Repeat(ZshRepeat {
count,
body: Box::new(body),
}))
}
fn parse_loop_body(foreach_style: bool) -> Option<ZshProgram> {
parse_loop_body_kind(foreach_style, false)
}
fn parse_loop_body_kind(foreach_style: bool, is_repeat: bool) -> Option<ZshProgram> {
if tok() == DOLOOP {
zshlex();
let body = parse_program();
if tok() == DONE {
zshlex();
}
Some(body)
} else if tok() == INBRACE_TOK {
zshlex();
let body = parse_program();
if tok() == OUTBRACE_TOK {
zshlex();
}
Some(body)
} else if foreach_style || isset(CSHJUNKIELOOPS) {
let body = parse_program();
if tok() == ZEND {
zshlex();
}
Some(body)
} else {
if unset(SHORTLOOPS) && (!is_repeat || unset(SHORTREPEAT)) {
error("parse error: short loop form requires SHORTLOOPS option");
return None;
}
par_list().map(|list| ZshProgram { lists: vec![list] })
}
}
fn par_subsh() -> Option<ZshCommand> {
zshlex(); let prog = parse_program();
if tok() == OUTPAR_TOK {
zshlex();
}
Some(ZshCommand::Subsh(Box::new(prog)))
}
fn parse_anon_funcdef() -> Option<ZshCommand> {
zshlex(); skip_separators();
if tok() != INBRACE_TOK {
return Some(ZshCommand::Subsh(Box::new(ZshProgram {
lists: Vec::new(),
})));
}
zshlex(); let body = parse_program();
if tok() == OUTBRACE_TOK {
zshlex();
}
let mut args = Vec::new();
while tok() == STRING_LEX {
if let Some(s) = tokstr() {
args.push(s);
}
zshlex();
}
static ANON_COUNTER: AtomicUsize = AtomicUsize::new(0);
let n = ANON_COUNTER.fetch_add(1, Ordering::Relaxed);
let name = format!("_zshrs_anon_{}", n);
Some(ZshCommand::FuncDef(ZshFuncDef {
names: vec![name],
body: Box::new(body),
tracing: false,
auto_call_args: Some(args),
body_source: None,
}))
}
fn parse_cursh() -> Option<ZshCommand> {
zshlex(); let prog = parse_program();
if tok() == OUTBRACE_TOK {
set_incmdpos(true); zshlex();
if tok() == STRING_LEX {
let s = tokstr();
if s.map(|s| s == "always").unwrap_or(false) {
set_incmdpos(true); zshlex();
skip_separators();
if tok() == INBRACE_TOK {
zshlex();
let always = parse_program();
if tok() == OUTBRACE_TOK {
zshlex();
}
return Some(ZshCommand::Try(ZshTry {
try_block: Box::new(prog),
always: Box::new(always),
}));
}
}
}
}
Some(ZshCommand::Cursh(Box::new(prog)))
}
fn par_funcdef() -> Option<ZshCommand> {
zshlex();
let mut names = Vec::new();
let mut tracing = false;
loop {
match tok() {
STRING_LEX => {
let _ts_s = tokstr()?;
let s = _ts_s.as_str();
if s == "{" || s == "\u{8f}" {
break;
}
let first = s.chars().next();
if matches!(first, Some('-') | Some('+')) || matches!(first, Some(c) if c == Dash) {
if s.contains('T') {
tracing = true;
}
zshlex();
continue;
}
names.push(s.to_string());
zshlex();
}
INBRACE_TOK | INOUTPAR | SEPER | NEWLIN => break,
_ => break,
}
}
let saw_paren = tok() == INOUTPAR;
if saw_paren {
zshlex();
}
skip_separators();
let body_opener_is_string_brace =
tok() == STRING_LEX && (tokstr_eq("{") || tokstr_eq("\u{8f}"));
if tok() == INBRACE_TOK || body_opener_is_string_brace {
let body_start = pos();
zshlex();
let body = parse_program();
let body_end = if tok() == OUTBRACE_TOK {
pos().saturating_sub(1)
} else {
pos()
};
let body_source = input_slice(body_start, body_end)
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty());
if tok() == OUTBRACE_TOK {
zshlex();
}
if names.is_empty() {
let mut args = Vec::new();
while tok() == STRING_LEX {
if let Some(s) = tokstr() {
args.push(s);
}
zshlex();
}
static ANON_COUNTER: AtomicUsize = AtomicUsize::new(0);
let n = ANON_COUNTER.fetch_add(1, Ordering::Relaxed);
let name = format!("_zshrs_anon_kw_{}", n);
return Some(ZshCommand::FuncDef(ZshFuncDef {
names: vec![name],
body: Box::new(body),
tracing,
auto_call_args: Some(args),
body_source,
}));
}
Some(ZshCommand::FuncDef(ZshFuncDef {
names,
body: Box::new(body),
tracing,
auto_call_args: None,
body_source,
}))
} else {
par_list().map(|list| {
ZshCommand::FuncDef(ZshFuncDef {
names,
body: Box::new(ZshProgram { lists: vec![list] }),
tracing,
auto_call_args: None,
body_source: None,
})
})
}
}
fn parse_inline_funcdef(name: String) -> Option<ZshCommand> {
if tok() == INOUTPAR {
zshlex();
}
skip_separators();
if tok() == INBRACE_TOK {
let body_start = pos();
zshlex();
let body = parse_program();
let body_end = if tok() == OUTBRACE_TOK {
pos().saturating_sub(1)
} else {
pos()
};
let body_source = input_slice(body_start, body_end)
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty());
if tok() == OUTBRACE_TOK {
zshlex();
}
Some(ZshCommand::FuncDef(ZshFuncDef {
names: vec![name],
body: Box::new(body),
tracing: false,
auto_call_args: None,
body_source,
}))
} else if unset(SHORTLOOPS) {
error("parse error: short function body form requires SHORTLOOPS option");
None
} else {
match par_cmd() {
Some(cmd) => {
let list = ZshList {
sublist: ZshSublist {
pipe: ZshPipe {
cmd,
next: None,
lineno: lineno(),
merge_stderr: false,
},
next: None,
flags: SublistFlags::default(),
},
flags: ListFlags::default(),
};
Some(ZshCommand::FuncDef(ZshFuncDef {
names: vec![name],
body: Box::new(ZshProgram { lists: vec![list] }),
tracing: false,
auto_call_args: None,
body_source: None,
}))
}
None => None,
}
}
}
fn par_cond() -> Option<ZshCommand> {
zshlex(); if tok() == DOUTBRACK {
error("parse error near `]]'");
zshlex();
return None;
}
let cond = parse_cond_expr();
if tok() == DOUTBRACK {
zshlex();
}
cond.map(ZshCommand::Cond)
}
fn parse_cond_expr() -> Option<ZshCond> {
parse_cond_or()
}
fn parse_cond_or() -> Option<ZshCond> {
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get() + 1);
if check_recursion() {
error("parse_cond_or: max recursion depth exceeded");
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
return None;
}
let left = match parse_cond_and() {
Some(l) => l,
None => {
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
return None;
}
};
skip_cond_separators();
let result = if tok() == DBAR {
zshlex();
skip_cond_separators();
parse_cond_or().map(|right| ZshCond::Or(Box::new(left), Box::new(right)))
} else {
Some(left)
};
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
result
}
fn parse_cond_and() -> Option<ZshCond> {
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get() + 1);
if check_recursion() {
error("parse_cond_and: max recursion depth exceeded");
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
return None;
}
let left = match parse_cond_not() {
Some(l) => l,
None => {
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
return None;
}
};
skip_cond_separators();
let result = if tok() == DAMPER {
zshlex();
skip_cond_separators();
parse_cond_and().map(|right| ZshCond::And(Box::new(left), Box::new(right)))
} else {
Some(left)
};
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
result
}
fn parse_cond_not() -> Option<ZshCond> {
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get() + 1);
if check_recursion() {
error("parse_cond_not: max recursion depth exceeded");
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
return None;
}
skip_cond_separators();
let is_not =
tok() == BANG_TOK || (tok() == STRING_LEX && tokstr().map(|s| s == "!").unwrap_or(false));
if is_not {
zshlex();
let inner = match parse_cond_not() {
Some(i) => i,
None => {
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
return None;
}
};
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
return Some(ZshCond::Not(Box::new(inner)));
}
if tok() == INPAR_TOK {
zshlex();
skip_cond_separators();
let inner = match parse_cond_expr() {
Some(i) => i,
None => {
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
return None;
}
};
skip_cond_separators();
if tok() == OUTPAR_TOK {
zshlex();
}
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
return Some(inner);
}
let result = parse_cond_primary();
PARSER_RECURSION_DEPTH.set(PARSER_RECURSION_DEPTH.get().saturating_sub(1));
result
}
fn parse_cond_primary() -> Option<ZshCond> {
let s1 = match tok() {
STRING_LEX => {
let s = tokstr().unwrap_or_default();
zshlex();
s
}
_ => return None,
};
skip_cond_separators();
let s1_chars: Vec<char> = s1.chars().collect();
if s1_chars.len() == 2 && IS_DASH(s1_chars[0]) {
let s2 = match tok() {
STRING_LEX => {
let s = tokstr().unwrap_or_default();
zshlex();
s
}
_ => return Some(ZshCond::Unary("-n".to_string(), s1)),
};
return Some(ZshCond::Unary(s1, s2));
}
let op = match tok() {
STRING_LEX => {
let s = tokstr().unwrap_or_default();
set_incond(incond() + 1);
zshlex();
set_incond(incond() - 1);
s
}
INANG_TOK => {
set_incond(incond() + 1);
zshlex();
set_incond(incond() - 1);
"<".to_string()
}
OUTANG_TOK => {
set_incond(incond() + 1);
zshlex();
set_incond(incond() - 1);
">".to_string()
}
_ => return Some(ZshCond::Unary("-n".to_string(), s1)),
};
skip_cond_separators();
let s2 = match tok() {
STRING_LEX => {
let s = tokstr().unwrap_or_default();
zshlex();
s
}
_ => return Some(ZshCond::Binary(s1, op, String::new())),
};
if op == "=~" {
Some(ZshCond::Regex(s1, s2))
} else {
Some(ZshCond::Binary(s1, op, s2))
}
}
fn skip_cond_separators() {
while tok() == SEPER && {
let s = tokstr();
s.map(|s| !s.contains(';')).unwrap_or(true)
} {
zshlex();
}
}
fn parse_arith() -> Option<ZshCommand> {
let expr = tokstr().unwrap_or_default();
zshlex();
Some(ZshCommand::Arith(expr))
}
fn par_time() -> Option<ZshCommand> {
zshlex();
if tok() == SEPER || tok() == NEWLIN || tok() == ENDINPUT {
Some(ZshCommand::Time(None))
} else {
let sublist = par_sublist();
Some(ZshCommand::Time(sublist.map(Box::new)))
}
}
fn peek_inoutpar() -> bool {
tok() == INOUTPAR
}
fn skip_separators() {
let mut iterations = 0;
while tok() == SEPER || tok() == NEWLIN {
iterations += 1;
if iterations > 100_000 {
error("skip_separators: too many iterations");
return;
}
zshlex();
}
}
fn error(msg: &str) {
crate::ported::utils::zerr(msg);
}
pub const FD_EXT: &str = ".zwc";
pub const FD_MINMAP: usize = 4096;
pub const FD_PRELEN: usize = 12;
pub const FD_MAGIC: u32 = 0x04050607;
pub const FD_OMAGIC: u32 = 0x07060504;
pub const FDF_MAP: u32 = 1;
pub const FDF_OTHER: u32 = 2;
pub const FDHF_KSHLOAD: u32 = 1;
pub const FDHF_ZSHLOAD: u32 = 2;
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy)]
pub struct fdhead {
pub start: u32, pub len: u32, pub npats: u32, pub strs: u32, pub hlen: u32, pub flags: u32, }
pub const FDHEAD_WORDS: usize = std::mem::size_of::<fdhead>() / 4;
#[allow(non_camel_case_types)]
#[derive(Debug, Clone)]
pub struct wcfunc {
pub name: String, pub flags: u32, pub body: Vec<u32>,
}
#[inline]
pub fn fdheaderlen(f: &[u32]) -> u32 {
f[FD_PRELEN]
}
#[inline]
pub fn fdmagic(f: &[u32]) -> u32 {
f[0]
}
#[inline]
pub fn fdflags(f: &[u32]) -> u32 {
f[1] & 0xff
}
#[inline]
pub fn fdsetflags(f: &mut [u32], v: u8) {
f[1] = (f[1] & !0xff) | (v as u32);
}
#[inline]
pub fn fdother(f: &[u32]) -> u32 {
(f[1] >> 8) & 0x00ff_ffff
}
#[inline]
pub fn fdsetother(f: &mut [u32], o: u32) {
f[1] = (f[1] & 0xff) | ((o & 0x00ff_ffff) << 8);
}
pub fn fdversion(f: &[u32]) -> String {
let bytes: Vec<u8> = f[2..]
.iter()
.take(10)
.flat_map(|w| w.to_le_bytes().into_iter())
.collect();
let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
String::from_utf8_lossy(&bytes[..end]).into_owned()
}
#[inline]
pub fn firstfdhead_offset() -> usize {
FD_PRELEN
}
#[inline]
pub fn nextfdhead_offset(f: &[u32], cur: usize) -> usize {
cur + (f[cur + 4] as usize) }
#[inline]
pub fn fdhflags(h: &fdhead) -> u32 {
h.flags & 0x3
}
#[inline]
pub fn fdhtail(h: &fdhead) -> u32 {
h.flags >> 2
}
#[inline]
pub fn fdhbldflags(flags: u32, tail: u32) -> u32 {
flags | (tail << 2)
}
pub fn fdname(buf: &[u32], header_offset: usize) -> String {
let name_word_off = header_offset + FDHEAD_WORDS;
let bytes: Vec<u8> = buf[name_word_off..]
.iter()
.flat_map(|w| w.to_le_bytes().into_iter())
.take_while(|&b| b != 0)
.collect();
String::from_utf8_lossy(&bytes).into_owned()
}
pub fn read_fdhead(buf: &[u32], offset: usize) -> Option<fdhead> {
if offset + FDHEAD_WORDS > buf.len() {
return None;
}
Some(fdhead {
start: buf[offset],
len: buf[offset + 1],
npats: buf[offset + 2],
strs: buf[offset + 3],
hlen: buf[offset + 4],
flags: buf[offset + 5],
})
}
pub fn fdswap(p: &mut [u32]) {
for w in p.iter_mut() {
*w = w.swap_bytes();
}
}
pub fn dump_find_func(h: &[u32], name: &str) -> bool {
let header_words = fdheaderlen(h) as usize;
let end = header_words; let mut cur = firstfdhead_offset();
while cur < end {
if let Some(fh) = read_fdhead(h, cur) {
let full = fdname(h, cur);
let tail = fdhtail(&fh) as usize;
let basename = if tail <= full.len() {
&full[tail..]
} else {
""
};
if basename == name {
return true;
}
cur = nextfdhead_offset(h, cur);
} else {
break;
}
}
false
}
pub fn load_dump_header(nam: &str, name: &str, err: i32) -> Option<Vec<u32>> {
let mut f = match File::open(name) {
Ok(h) => h,
Err(_) => {
if err != 0 {
zwarnnam(nam, &format!("can't open zwc file: {}", name)); }
return None;
}
};
let mut buf_bytes = vec![0u8; (FD_PRELEN + 1) * 4];
if f.read_exact(&mut buf_bytes).is_err() {
if err != 0 {
zwarnnam(nam, &format!("invalid zwc file: {}", name)); }
return None;
}
let mut buf: Vec<u32> = buf_bytes
.chunks_exact(4)
.map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect();
let magic_ok = fdmagic(&buf) == FD_MAGIC || fdmagic(&buf) == FD_OMAGIC;
let v_ok = fdversion(&buf) == "5.9";
if !magic_ok {
if err != 0 {
zwarnnam(nam, &format!("invalid zwc file: {}", name)); }
return None;
}
if !v_ok {
if err != 0 {
zwarnnam(
nam,
&format!(
"zwc file has wrong version (zsh-{}): {}", fdversion(&buf),
name
),
);
}
return None;
}
if fdmagic(&buf) != FD_MAGIC {
let other = fdother(&buf) as u64; if f.seek(SeekFrom::Start(other)).is_err() || f.read_exact(&mut buf_bytes).is_err() {
zwarnnam(nam, &format!("invalid zwc file: {}", name)); return None;
}
buf = buf_bytes
.chunks_exact(4)
.map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect();
}
let total_words = fdheaderlen(&buf) as usize; if total_words < FD_PRELEN + 1 {
zwarnnam(nam, &format!("invalid zwc file: {}", name));
return None;
}
let mut head: Vec<u32> = Vec::with_capacity(total_words);
head.extend_from_slice(&buf);
let remaining_words = total_words - (FD_PRELEN + 1);
if remaining_words > 0 {
let mut rest_bytes = vec![0u8; remaining_words * 4]; if f.read_exact(&mut rest_bytes).is_err() {
zwarnnam(nam, &format!("invalid zwc file: {}", name)); return None;
}
for c in rest_bytes.chunks_exact(4) {
head.push(u32::from_le_bytes([c[0], c[1], c[2], c[3]]));
}
}
Some(head) }
pub fn build_dump(
nam: &str, dump: &str,
_files: &[String],
_ali: i32,
_map: i32,
_flags: u32,
) -> i32 {
crate::ported::utils::zwarnnam(nam, &format!("{}: wordcode dump emit not yet ported", dump));
1
}
pub fn build_cur_dump(
nam: &str, dump: &str,
_names: &[String],
_match_: i32,
_map: i32,
_what: i32,
) -> i32 {
crate::ported::utils::zwarnnam(
nam,
&format!("{}: wordcode dump-current emit not yet ported", dump),
);
1
}
pub fn zwcstat(filename: &str) -> Option<std::fs::Metadata> {
if let Ok(m) = std::fs::metadata(filename) {
return Some(m);
}
let old = format!("{}.old", filename);
std::fs::metadata(&old).ok()
}
pub fn load_dump_file(
dump: &str, _sbuf: &std::fs::Metadata,
other: i32,
_len: usize,
) -> Option<Vec<u32>> {
let mut f = File::open(dump).ok()?;
if other != 0 {
f.seek(SeekFrom::Start(other as u64)).ok()?;
}
let mut bytes = Vec::new();
f.read_to_end(&mut bytes).ok()?;
Some(
bytes
.chunks_exact(4)
.map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect(),
)
}
pub fn try_dump_file(
_path: &str,
_name: &str,
_file: &str, _test_only: bool,
) -> Option<(bool, bool)> {
None
}
pub fn try_source_file(_file: &str) -> Option<String> {
None
}
pub fn check_dump_file(
_file: &str, _sbuf: &std::fs::Metadata,
_name: &str,
_test_only: bool,
) -> Option<(Vec<u32>, bool)> {
None
}
pub static DUMPS: std::sync::Mutex<Vec<crate::ported::zsh_h::funcdump>> =
std::sync::Mutex::new(Vec::new());
pub fn incrdumpcount(f: &crate::ported::zsh_h::funcdump) {
let key = f.filename.as_deref();
let mut g = DUMPS.lock().unwrap();
for d in g.iter_mut() {
if d.filename.as_deref() == key {
d.count += 1; return;
}
}
}
fn freedump_locked(
g: &mut std::sync::MutexGuard<'_, Vec<crate::ported::zsh_h::funcdump>>,
filename: &str,
) {
g.retain(|d| d.filename.as_deref() != Some(filename));
}
pub fn freedump(f: &crate::ported::zsh_h::funcdump) {
let mut g = DUMPS.lock().unwrap();
if let Some(name) = f.filename.as_deref() {
freedump_locked(&mut g, name);
}
}
pub fn decrdumpcount(f: &crate::ported::zsh_h::funcdump) {
let key = f.filename.clone();
let mut g = DUMPS.lock().unwrap();
let mut hit_zero: Option<String> = None;
for d in g.iter_mut() {
if d.filename == key {
d.count -= 1; if d.count == 0 {
hit_zero = d.filename.clone();
}
break;
}
}
if let Some(name) = hit_zero {
freedump_locked(&mut g, &name);
}
}
pub fn closedumps() {
let mut g = DUMPS.lock().unwrap();
g.clear(); }
pub fn dump_autoload(
nam: &str,
file: &str, _on: i32,
_ops: &crate::ported::zsh_h::options,
_func: i32,
) -> i32 {
zwarnnam(nam, &format!("{}: zwc-based autoload not yet ported", file));
1
}
pub fn bin_zcompile(
nam: &str, args: &[String],
ops: &crate::ported::zsh_h::options,
_func: i32,
) -> i32 {
if (OPT_ISSET(ops, b'k') && OPT_ISSET(ops, b'z'))
|| (OPT_ISSET(ops, b'R') && OPT_ISSET(ops, b'M'))
|| (OPT_ISSET(ops, b'c')
&& (OPT_ISSET(ops, b'U') || OPT_ISSET(ops, b'k') || OPT_ISSET(ops, b'z')))
|| (!(OPT_ISSET(ops, b'c') || OPT_ISSET(ops, b'a')) && OPT_ISSET(ops, b'm'))
{
zwarnnam(nam, "illegal combination of options"); return 1;
}
if (OPT_ISSET(ops, b'c') || OPT_ISSET(ops, b'a')) && isset(crate::ported::zsh_h::KSHAUTOLOAD) {
zwarnnam(nam, "functions will use zsh style autoloading"); }
let flags: u32 = if OPT_ISSET(ops, b'k') {
FDHF_KSHLOAD
} else if OPT_ISSET(ops, b'z') {
FDHF_ZSHLOAD
} else {
0
};
if OPT_ISSET(ops, b't') {
if args.is_empty() {
zwarnnam(nam, "too few arguments"); return 1;
}
let dump_name = if args[0].ends_with(FD_EXT) {
args[0].clone()
} else {
format!("{}{}", args[0], FD_EXT)
};
let f = match load_dump_header(nam, &dump_name, 1) {
Some(buf) => buf,
None => return 1,
};
if args.len() > 1 {
for name in &args[1..] {
if !dump_find_func(&f, name) {
return 1;
}
}
return 0;
}
let mapped = if (fdflags(&f) & FDF_MAP) != 0 {
"mapped"
} else {
"read"
};
println!("zwc file ({}) for zsh-{}", mapped, fdversion(&f));
let header_words = fdheaderlen(&f) as usize;
let mut cur = firstfdhead_offset();
while cur < header_words {
if read_fdhead(&f, cur).is_none() {
break;
}
println!("{}", fdname(&f, cur));
cur = nextfdhead_offset(&f, cur);
}
return 0;
}
if args.is_empty() {
zwarnnam(nam, "too few arguments"); return 1;
}
let map: i32 = if OPT_ISSET(ops, b'M') {
2
} else if OPT_ISSET(ops, b'R') {
0
} else {
1
};
if args.len() == 1 && !(OPT_ISSET(ops, b'c') || OPT_ISSET(ops, b'a')) {
let dump = format!("{}{}", args[0], FD_EXT);
return build_dump(nam, &dump, args, OPT_ISSET(ops, b'U') as i32, map, flags);
}
let dump = if args[0].ends_with(FD_EXT) {
args[0].clone()
} else {
format!("{}{}", args[0], FD_EXT)
};
let rest = &args[1..];
if OPT_ISSET(ops, b'c') || OPT_ISSET(ops, b'a') {
let what =
(if OPT_ISSET(ops, b'c') { 1 } else { 0 }) | (if OPT_ISSET(ops, b'a') { 2 } else { 0 });
build_cur_dump(nam, &dump, rest, OPT_ISSET(ops, b'm') as i32, map, what)
} else {
build_dump(nam, &dump, rest, OPT_ISSET(ops, b'U') as i32, map, flags)
}
}
#[inline]
pub fn ecstr(s: &str) {
let code = ecstrcode(s);
ecadd(code);
}
#[inline]
pub fn condlex() {
zshlex();
}
#[inline]
pub fn COND_SEP() -> bool {
matches!(tok(), NEWLIN | SEMI | AMPER)
}
pub fn copy_ecstr(table: &std::collections::HashMap<u32, Vec<u8>>, p: &mut [u8]) {
for (&offs, bytes) in table.iter() {
let off = (offs >> 2) as usize;
let need = off + bytes.len() + 1;
if need > p.len() {
continue;
}
p[off..off + bytes.len()].copy_from_slice(bytes);
p[off + bytes.len()] = 0;
}
}
pub fn bld_eprog(heap: bool) -> crate::ported::zsh_h::eprog {
ecadd(0);
let ecused = ECUSED.with(|c| c.get()) as usize;
let ecnpats = ECNPATS.with(|c| c.get()) as usize;
let ecsoffs = ECSOFFS.with(|c| c.get()) as usize;
let prog_bytes = ecused * 4; let len = (ecnpats * 4) + prog_bytes + ecsoffs;
let prog_words: Vec<u32> = ECBUF.with(|c| c.borrow()[..ecused].to_vec());
let mut strs_bytes = vec![0u8; ecsoffs];
ECSTRS_REVERSE.with(|c| copy_ecstr(&c.borrow(), &mut strs_bytes));
let strs_string = unsafe { String::from_utf8_unchecked(strs_bytes) };
let ret = eprog {
flags: if heap { EF_HEAP } else { EF_REAL }, len: len as i32, npats: ecnpats as i32, nref: if heap { -1 } else { 1 }, pats: Vec::new(), prog: prog_words, strs: Some(strs_string),
shf: None,
dump: None,
};
ECBUF.with(|c| c.borrow_mut().clear());
ECLEN.with(|c| c.set(0));
ECUSED.with(|c| c.set(0));
ECNPATS.with(|c| c.set(0));
ECSOFFS.with(|c| c.set(0));
ECSTRS_INDEX.with(|c| c.borrow_mut().clear());
ECSTRS_REVERSE.with(|c| c.borrow_mut().clear());
ret
}
pub fn parse_list() -> Option<eprog> {
set_tok(ENDINPUT);
init_parse();
zshlex();
let _ = par_list();
if tok() != ENDINPUT {
clear_hdocs();
set_tok(LEXERR);
yyerror("syntax error");
return None;
}
Some(bld_eprog(true))
}
pub fn parse_cond() -> Option<eprog> {
init_parse();
if par_cond().is_none() {
clear_hdocs();
return None;
}
Some(bld_eprog(true))
}
pub fn par_sublist2(cmplx: &mut i32) -> Option<i32> {
let mut f = 0i32;
if tok() == COPROC {
*cmplx = 1;
f |= WC_SUBLIST_COPROC as i32;
zshlex();
} else if tok() == BANG_TOK {
*cmplx = 1;
f |= WC_SUBLIST_NOT as i32;
zshlex();
}
let outer = cmplx_get();
cmplx_set(false);
let ok = par_pipe_wordcode();
*cmplx |= cmplx_get() as i32;
cmplx_set(outer | cmplx_get());
if !ok && f == 0 {
return None;
}
Some(f)
}
pub fn par_dinbrack() -> Option<()> {
set_incond(1); set_incmdpos(false); zshlex(); let _ = par_cond(); if tok() != DOUTBRACK {
yyerror("missing ]]");
return None;
}
set_incond(0); set_incmdpos(true); zshlex(); Some(())
}
pub fn par_cond_1() -> i32 {
let p = ECUSED.with(|c| c.get()) as usize;
let r = par_cond_2();
while COND_SEP() {
condlex();
}
if tok() == DAMPER {
condlex();
while COND_SEP() {
condlex();
}
ecispace(p, 1);
par_cond_1();
let ecused = ECUSED.with(|c| c.get()) as usize;
ECBUF.with(|c| {
c.borrow_mut()[p] = WCB_COND(COND_AND as u32, (ecused - 1 - p) as u32);
});
return 1;
}
r
}
fn check_cond(input: &str, cond: &str) -> bool {
let mut chars = input.chars();
match chars.next() {
Some(c) if IS_DASH(c) => chars.as_str() == cond,
_ => false,
}
}
pub fn par_cond_2() -> i32 {
let n_testargs: i32 = 0;
while COND_SEP() {
condlex();
}
if tok() == BANG_TOK {
condlex();
ecadd(WCB_COND(COND_NOT as u32, 0));
return par_cond_2();
}
if tok() == INPAR_TOK {
condlex();
while COND_SEP() {
condlex();
}
let r = par_cond();
while COND_SEP() {
condlex();
}
if tok() != OUTPAR_TOK {
yyerror("missing )");
return 0;
}
condlex();
return r.map_or(0, |_| 1);
}
let s1 = tokstr().unwrap_or_default();
let dble = s1.starts_with('-')
&& s1.len() == 2
&& "abcdefghknoprstuvwxzLONGS".contains(s1.chars().nth(1).unwrap_or('?'));
if tok() != STRING_LEX {
if !s1.is_empty() && tok() != LEXERR && (!dble || n_testargs != 0) {
if n_testargs == 1 && unset(POSIXBUILTINS) && check_cond(&s1, "t") {
condlex();
return par_cond_double(&s1, "1");
}
condlex();
while COND_SEP() {
condlex();
}
return par_cond_double("-n", &s1);
}
yyerror("condition expected");
return 0;
}
condlex();
while COND_SEP() {
condlex();
}
if tok() == INANG_TOK || tok() == OUTANG_TOK {
let xtok = tok();
condlex();
while COND_SEP() {
condlex();
}
if tok() != STRING_LEX {
yyerror("string expected");
return 0;
}
let s3 = tokstr().unwrap_or_default();
condlex();
while COND_SEP() {
condlex();
}
let op = if xtok == INANG_TOK {
COND_STRLT
} else {
COND_STRGTR
};
ecadd(WCB_COND(op as u32, 0));
ecstr(&s1);
ecstr(&s3);
return 1;
}
if tok() != STRING_LEX {
if tok() != LEXERR {
if !dble || n_testargs != 0 {
return par_cond_double("-n", &s1);
}
return par_cond_multi(&s1, &[]);
}
yyerror("syntax error");
return 0;
}
let s2 = tokstr().unwrap_or_default();
set_incond(incond() + 1);
condlex();
while COND_SEP() {
condlex();
}
set_incond(incond() - 1);
if tok() == STRING_LEX && !dble {
let s3 = tokstr().unwrap_or_default();
condlex();
while COND_SEP() {
condlex();
}
if tok() == STRING_LEX {
let mut l: Vec<String> = vec![s2, s3];
while tok() == STRING_LEX {
l.push(tokstr().unwrap_or_default());
condlex();
while COND_SEP() {
condlex();
}
}
return par_cond_multi(&s1, &l);
}
return par_cond_triple(&s1, &s2, &s3);
}
par_cond_double(&s1, &s2)
}
pub fn par_cond_double(a: &str, b: &str) -> i32 {
if !a.starts_with('-') || a.len() < 2 {
crate::ported::utils::zerr(&format!("parse error: condition expected: {}", a));
return 1;
}
let unary_set = "abcdefgknoprstuvwxzhLONGS";
if a.len() == 2 && unary_set.contains(a.chars().nth(1).unwrap_or('?')) {
ecadd(WCB_COND(a.as_bytes()[1] as u32, 0));
ecstr(b);
} else {
ecadd(WCB_COND(COND_MOD as u32, 1));
ecstr(a);
ecstr(b);
}
1
}
pub fn par_cond_triple(a: &str, b: &str, c: &str) -> i32 {
let bb = b.as_bytes();
if (bb == b"=" || bb == b"==") && bb.len() <= 2 {
let dbl = bb.len() == 2;
let op = if dbl { COND_STRDEQ } else { COND_STREQ };
ecadd(WCB_COND(op as u32, 0));
ecstr(a);
ecstr(c);
let np = ECNPATS.with(|cc| {
let v = cc.get();
cc.set(v + 1);
v
}) as u32;
ecadd(np);
return 1;
}
if (bb == b">" || bb == b"<") && bb.len() == 1 {
let op = if bb[0] == b'>' {
COND_STRGTR
} else {
COND_STRLT
};
ecadd(WCB_COND(op as u32, 0));
ecstr(a);
ecstr(c);
let np = ECNPATS.with(|cc| {
let v = cc.get();
cc.set(v + 1);
v
}) as u32;
ecadd(np);
return 1;
}
if bb == b"!=" {
ecadd(WCB_COND(COND_STRNEQ as u32, 0));
ecstr(a);
ecstr(c);
let np = ECNPATS.with(|cc| {
let v = cc.get();
cc.set(v + 1);
v
}) as u32;
ecadd(np);
return 1;
}
if bb == b"=~" {
ecadd(WCB_COND(COND_REGEX as u32, 0));
ecstr(a);
ecstr(c);
return 1;
}
if b.starts_with('-') {
let t = get_cond_num(&b[1..]);
if t > -1 {
ecadd(WCB_COND((t + COND_NT) as u32, 0));
ecstr(a);
ecstr(c);
return 1;
}
ecadd(WCB_COND(COND_MODI as u32, 0));
ecstr(b);
ecstr(a);
ecstr(c);
return 1;
}
if a.starts_with('-') && a.len() > 1 {
ecadd(WCB_COND(COND_MOD as u32, 2));
ecstr(a);
ecstr(b);
ecstr(c);
return 1;
}
crate::ported::utils::zerr(&format!("condition expected: {}", b));
1
}
pub fn par_cond_multi(a: &str, l: &[String]) -> i32 {
if !a.starts_with('-') || a.len() < 2 {
crate::ported::utils::zerr(&format!("condition expected: {}", a));
return 1;
}
ecadd(WCB_COND(COND_MOD as u32, l.len() as u32));
ecstr(a);
for item in l {
ecstr(item);
}
1
}
pub fn cur_add_func(
nam: &str, shf_name: &str,
shf_flags: i32,
names: &mut Vec<String>,
progs: &mut Vec<wcfunc>,
hlen: &mut i32,
tlen: &mut i32,
what: i32,
) -> i32 {
let is_undef = (shf_flags as u32 & PM_UNDEFINED) != 0;
if is_undef {
if (what & 2) == 0 {
zwarnnam(nam, &format!("function is not loaded: {}", shf_name));
return 1;
}
zwarnnam(nam, &format!("can't load function: {}", shf_name));
return 1;
} else if (what & 1) == 0 {
zwarnnam(nam, &format!("function is already loaded: {}", shf_name)); return 1;
}
let wcf = wcfunc {
name: shf_name.to_string(),
flags: FDHF_ZSHLOAD,
body: Vec::new(),
};
progs.push(wcf);
names.push(shf_name.to_string());
let name_words = (shf_name.len() as i32 + 4) / 4;
*hlen += (FDHEAD_WORDS as i32) + name_words;
*tlen += 0;
0
}
pub fn write_dump(
dfd: &mut std::fs::File, progs: &[wcfunc],
mut map: i32,
hlen: i32,
tlen: i32,
) -> std::io::Result<()> {
if map == 1 && (tlen as usize) >= FD_MINMAP {
map = 1;
} else if map == 1 {
map = 0;
}
let mut other = 0u32; let ohlen = hlen;
let mut cur_hlen = hlen;
loop {
cur_hlen = ohlen;
let mut pre = vec![0u32; FD_PRELEN];
pre[0] = if other != 0 { FD_OMAGIC } else { FD_MAGIC }; let flags = (if map != 0 { FDF_MAP } else { 0 }) | other;
fdsetflags(&mut pre, flags as u8); fdsetother(&mut pre, tlen as u32); let ver = b"5.9";
for (i, &b) in ver.iter().enumerate() {
let word = 2 + i / 4;
let shift = (i % 4) * 8;
pre[word] |= (b as u32) << shift;
}
for w in &pre {
dfd.write_all(&w.to_le_bytes())?;
}
for wcf in progs {
let n = &wcf.name;
let prog = &wcf.body;
let mut head = fdhead {
start: cur_hlen as u32, len: (prog.len() * 4) as u32, npats: 0, strs: 0, hlen: ((FDHEAD_WORDS as u32) + ((n.len() as u32 + 4) / 4)), flags: 0,
};
cur_hlen += prog.len() as i32; let tail = n.rfind('/').map(|p| p + 1).unwrap_or(0);
head.flags = fdhbldflags(wcf.flags, tail as u32); let mut head_words: Vec<u32> = vec![
head.start, head.len, head.npats, head.strs, head.hlen, head.flags,
];
if other != 0 {
fdswap(&mut head_words);
}
for w in &head_words {
dfd.write_all(&w.to_le_bytes())?;
}
dfd.write_all(n.as_bytes())?;
dfd.write_all(&[0u8])?;
let pad = (4 - ((n.len() + 1) & 3)) & 3;
if pad > 0 {
dfd.write_all(&vec![0u8; pad])?;
}
}
for wcf in progs {
let mut body = wcf.body.clone();
if other != 0 {
fdswap(&mut body);
}
for w in &body {
dfd.write_all(&w.to_le_bytes())?;
}
}
if other != 0 {
break;
}
other = FDF_OTHER; }
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::{errflag, ERRFLAG_ERROR};
use std::fs;
use std::path::Path;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, Instant};
fn parse(input: &str) -> Result<ZshProgram, String> {
let saved = errflag.load(Ordering::Relaxed);
errflag.fetch_and(!ERRFLAG_ERROR, Ordering::Relaxed);
crate::ported::parse::parse_init(input);
let prog = crate::ported::parse::parse();
let had_err = (errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR) != 0;
errflag.store(saved, Ordering::Relaxed);
if had_err {
Err("parse error".to_string())
} else {
Ok(prog)
}
}
#[test]
fn test_simple_command() {
let prog = parse("echo hello world").unwrap();
assert_eq!(prog.lists.len(), 1);
match &prog.lists[0].sublist.pipe.cmd {
ZshCommand::Simple(s) => {
assert_eq!(s.words, vec!["echo", "hello", "world"]);
}
_ => panic!("expected simple command"),
}
}
#[test]
fn test_pipeline() {
let prog = parse("ls | grep foo | wc -l").unwrap();
assert_eq!(prog.lists.len(), 1);
let pipe = &prog.lists[0].sublist.pipe;
assert!(pipe.next.is_some());
let pipe2 = pipe.next.as_ref().unwrap();
assert!(pipe2.next.is_some());
}
#[test]
fn test_and_or() {
let prog = parse("cmd1 && cmd2 || cmd3").unwrap();
let sublist = &prog.lists[0].sublist;
assert!(sublist.next.is_some());
let (op, _) = sublist.next.as_ref().unwrap();
assert_eq!(*op, SublistOp::And);
}
#[test]
fn test_if_then() {
let prog = parse("if test -f foo; then echo yes; fi").unwrap();
match &prog.lists[0].sublist.pipe.cmd {
ZshCommand::If(_) => {}
_ => panic!("expected if command"),
}
}
#[test]
fn test_for_loop() {
let prog = parse("for i in a b c; do echo $i; done").unwrap();
match &prog.lists[0].sublist.pipe.cmd {
ZshCommand::For(f) => {
assert_eq!(f.var, "i");
match &f.list {
ForList::Words(w) => assert_eq!(w, &vec!["a", "b", "c"]),
_ => panic!("expected word list"),
}
}
_ => panic!("expected for command"),
}
}
#[test]
fn test_case() {
let prog = parse("case $x in a) echo a;; b) echo b;; esac").unwrap();
match &prog.lists[0].sublist.pipe.cmd {
ZshCommand::Case(c) => {
assert_eq!(c.arms.len(), 2);
}
_ => panic!("expected case command"),
}
}
#[test]
fn test_function() {
let prog = parse("function foo { }").unwrap();
match &prog.lists[0].sublist.pipe.cmd {
ZshCommand::FuncDef(f) => {
assert_eq!(f.names, vec!["foo"]);
}
_ => panic!(
"expected function, got {:?}",
prog.lists[0].sublist.pipe.cmd
),
}
}
#[test]
fn test_redirection() {
let prog = parse("echo hello > file.txt").unwrap();
match &prog.lists[0].sublist.pipe.cmd {
ZshCommand::Simple(s) => {
assert_eq!(s.redirs.len(), 1);
assert_eq!(s.redirs[0].rtype, REDIR_WRITE);
}
_ => panic!("expected simple command"),
}
}
#[test]
fn test_assignment() {
let prog = parse("FOO=bar echo $FOO").unwrap();
match &prog.lists[0].sublist.pipe.cmd {
ZshCommand::Simple(s) => {
assert_eq!(s.assigns.len(), 1);
assert_eq!(s.assigns[0].name, "FOO");
}
_ => panic!("expected simple command"),
}
}
#[test]
fn test_parse_completion_function() {
let input = r#"_2to3_fixes() {
local -a fixes
fixes=( ${${(M)${(f)"$(2to3 --list-fixes 2>/dev/null)"}:#*}//[[:space:]]/} )
(( ${#fixes} )) && _describe -t fixes 'fix' fixes
}"#;
let result = parse(input);
assert!(
result.is_ok(),
"Failed to parse completion function: {:?}",
result.err()
);
let prog = result.unwrap();
assert!(
!prog.lists.is_empty(),
"Expected at least one list in program"
);
}
#[test]
fn test_parse_array_with_complex_elements() {
let input = r#"arguments=(
'(- * :)'{-h,--help}'[show this help message and exit]'
{-d,--doctests_only}'[fix up doctests only]'
'*:filename:_files'
)"#;
let result = parse(input);
assert!(
result.is_ok(),
"Failed to parse array assignment: {:?}",
result.err()
);
}
#[test]
fn test_parse_full_completion_file() {
let input = r##"#compdef 2to3
# zsh completions for '2to3'
_2to3_fixes() {
local -a fixes
fixes=( ${${(M)${(f)"$(2to3 --list-fixes 2>/dev/null)"}:#*}//[[:space:]]/} )
(( ${#fixes} )) && _describe -t fixes 'fix' fixes
}
local -a arguments
arguments=(
'(- * :)'{-h,--help}'[show this help message and exit]'
{-d,--doctests_only}'[fix up doctests only]'
{-f,--fix}'[each FIX specifies a transformation; default: all]:fix name:_2to3_fixes'
{-j,--processes}'[run 2to3 concurrently]:number: '
{-x,--nofix}'[prevent a transformation from being run]:fix name:_2to3_fixes'
{-l,--list-fixes}'[list available transformations]'
{-p,--print-function}'[modify the grammar so that print() is a function]'
{-v,--verbose}'[more verbose logging]'
'--no-diffs[do not show diffs of the refactoring]'
{-w,--write}'[write back modified files]'
{-n,--nobackups}'[do not write backups for modified files]'
{-o,--output-dir}'[put output files in this directory instead of overwriting]:directory:_directories'
{-W,--write-unchanged-files}'[also write files even if no changes were required]'
'--add-suffix[append this string to all output filenames]:suffix: '
'*:filename:_files'
)
_arguments -s -S $arguments
"##;
let result = parse(input);
assert!(
result.is_ok(),
"Failed to parse full completion file: {:?}",
result.err()
);
let prog = result.unwrap();
assert!(!prog.lists.is_empty(), "Expected at least one list");
}
#[test]
fn test_parse_logs_sh() {
let input = r#"#!/usr/bin/env bash
shopt -s globstar
if [[ $(uname) == Darwin ]]; then
tail -f /var/log/**/*.log /var/log/**/*.out | lolcat
else
if [[ $ZPWR_DISTRO_NAME == raspbian ]]; then
tail -f /var/log/**/*.log | lolcat
else
printf "Unsupported...\n" >&2
fi
fi
"#;
let result = parse(input);
assert!(
result.is_ok(),
"Failed to parse logs.sh: {:?}",
result.err()
);
}
#[test]
fn test_parse_case_with_glob() {
let input = r#"case "$ZPWR_OS_TYPE" in
darwin*) open_cmd='open'
;;
cygwin*) open_cmd='cygstart'
;;
linux*)
open_cmd='xdg-open'
;;
esac"#;
let result = parse(input);
assert!(
result.is_ok(),
"Failed to parse case with glob: {:?}",
result.err()
);
}
#[test]
fn test_parse_case_with_nested_if() {
let input = r##"function zpwrGetOpenCommand(){
local open_cmd
case "$ZPWR_OS_TYPE" in
darwin*) open_cmd='open' ;;
cygwin*) open_cmd='cygstart' ;;
linux*)
if [[ "$_zpwr_uname_r" != *icrosoft* ]];then
open_cmd='nohup xdg-open'
fi
;;
esac
}"##;
let result = parse(input);
assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
}
#[test]
fn test_parse_zpwr_scripts() {
let scripts_dir = Path::new("/Users/wizard/.zpwr/scripts");
if !scripts_dir.exists() {
eprintln!("Skipping test: scripts directory not found");
return;
}
let mut total = 0;
let mut passed = 0;
let mut failed_files = Vec::new();
let mut timeout_files = Vec::new();
for ext in &["sh", "zsh"] {
let pattern = scripts_dir.join(format!("*.{}", ext));
if let Ok(entries) = glob::glob(pattern.to_str().unwrap()) {
for entry in entries.flatten() {
total += 1;
let file_path = entry.display().to_string();
let content = match fs::read_to_string(&entry) {
Ok(c) => c,
Err(e) => {
failed_files.push((file_path, format!("read error: {}", e)));
continue;
}
};
let content_clone = content.clone();
let (tx, rx) = mpsc::channel();
let handle = thread::spawn(move || {
let result = parse(&content_clone);
let _ = tx.send(result);
});
match rx.recv_timeout(Duration::from_secs(2)) {
Ok(Ok(_)) => passed += 1,
Ok(Err(err)) => {
failed_files.push((file_path, err));
}
Err(_) => {
timeout_files.push(file_path);
}
}
}
}
}
eprintln!("\n=== ZPWR Scripts Parse Results ===");
eprintln!("Passed: {}/{}", passed, total);
if !timeout_files.is_empty() {
eprintln!("\nTimeout files (>2s):");
for file in &timeout_files {
eprintln!(" {}", file);
}
}
if !failed_files.is_empty() {
eprintln!("\nFailed files:");
for (file, err) in &failed_files {
eprintln!(" {} - {}", file, err);
}
}
let pass_rate = if total > 0 {
(passed as f64 / total as f64) * 100.0
} else {
0.0
};
eprintln!("Pass rate: {:.1}%", pass_rate);
assert!(pass_rate >= 50.0, "Pass rate too low: {:.1}%", pass_rate);
}
#[test]
#[ignore] fn test_parse_zsh_stdlib_functions() {
let functions_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/zsh_functions");
if !functions_dir.exists() {
eprintln!(
"Skipping test: zsh_functions directory not found at {:?}",
functions_dir
);
return;
}
let mut total = 0;
let mut passed = 0;
let mut failed_files = Vec::new();
let mut timeout_files = Vec::new();
if let Ok(entries) = fs::read_dir(&functions_dir) {
for entry in entries.flatten() {
let path = entry.path();
if !path.is_file() {
continue;
}
total += 1;
let file_path = path.display().to_string();
let content = match fs::read_to_string(&path) {
Ok(c) => c,
Err(e) => {
failed_files.push((file_path, format!("read error: {}", e)));
continue;
}
};
let content_clone = content.clone();
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let result = parse(&content_clone);
let _ = tx.send(result);
});
match rx.recv_timeout(Duration::from_secs(2)) {
Ok(Ok(_)) => passed += 1,
Ok(Err(err)) => {
failed_files.push((file_path, err));
}
Err(_) => {
timeout_files.push(file_path);
}
}
}
}
eprintln!("\n=== Zsh Stdlib Functions Parse Results ===");
eprintln!("Passed: {}/{}", passed, total);
if !timeout_files.is_empty() {
eprintln!("\nTimeout files (>2s): {}", timeout_files.len());
for file in timeout_files.iter().take(10) {
eprintln!(" {}", file);
}
if timeout_files.len() > 10 {
eprintln!(" ... and {} more", timeout_files.len() - 10);
}
}
if !failed_files.is_empty() {
eprintln!("\nFailed files: {}", failed_files.len());
for (file, err) in failed_files.iter().take(20) {
let filename = Path::new(file)
.file_name()
.unwrap_or_default()
.to_string_lossy();
eprintln!(" {} - {}", filename, err);
}
if failed_files.len() > 20 {
eprintln!(" ... and {} more", failed_files.len() - 20);
}
}
let pass_rate = if total > 0 {
(passed as f64 / total as f64) * 100.0
} else {
0.0
};
eprintln!("Pass rate: {:.1}%", pass_rate);
assert!(pass_rate >= 50.0, "Pass rate too low: {:.1}%", pass_rate);
}
}