use std::collections::VecDeque;
use std::sync::atomic::Ordering;
use serde::{Deserialize, Serialize};
use crate::DPUTS;
use crate::ported::context::{zcontext_restore, zcontext_save};
use crate::ported::hashtable::{aliastab_lock, reswdtab_lock, sufaliastab_lock};
use crate::ported::hist::{hist_in_word, strinbeg, strinend};
use crate::ported::input::{inpop, inpush};
use crate::ported::parse::HDOCS;
use crate::ported::prompt::{cmdpop, cmdpush, CMDSTACK};
use crate::ported::string::dupstring_wlen;
use crate::ported::utils::{errflag, spckword, zerr, ERRFLAG_ERROR};
use crate::ported::zle::compcore::{WB, WE};
use crate::ported::zsh_h::{
alias, interact, isset, lex_stack, lexbufstate, unset, Bang, Bar, Bnull, Bnullkeep, Comma, Dash, Dnull, Equals,
Hat, Inang, Inbrace, Inbrack, Inpar, Inparmath, Marker, Nularg, Outang, OutangProc, Outbrace,
Outbrack, Outpar, Outparmath, Pound, Qstring, Qtick, Quest, Snull, Star, Stringg, Tick, Tilde,
ALIASESOPT, CORRECT, CORRECTALL, CSHJUNKIEQUOTES, CS_BQUOTE, CS_BRACE, CS_BRACEPAR,
CS_CMDSUBST, CS_CURSH, CS_DQUOTE, CS_HEREDOC, CS_HEREDOCD, CS_MATH, CS_MATHSUBST, CS_QUOTE,
ERRFLAG_INT, HISTALLOWCLOBBER, IGNOREBRACES, IGNORECLOSEBRACES, INP_ALIAS,
INTERACTIVECOMMENTS, KSHGLOB, Meta, POSIXALIASES, RCQUOTES, SHGLOB, SHINSTDIN, SHORTLOOPS,
SHORTREPEAT, ZCONTEXT_LEX, ZCONTEXT_PARSE,
};
use crate::ported::ztype_h::itok;
pub use super::zsh_h::{
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,
LEXFLAGS_ACTIVE, LEXFLAGS_COMMENTS, LEXFLAGS_COMMENTS_KEEP, LEXFLAGS_COMMENTS_STRIP,
LEXFLAGS_NEWLINE, LEXFLAGS_ZLE, 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, lextok,
};
pub use crate::heredoc_ast::HereDoc;
pub fn lex_context_save(ls: &mut lex_stack) {
ls.dbparens = LEX_DBPARENS.get() as i32;
ls.isfirstln = LEX_ISFIRSTLN.get() as i32;
ls.isfirstch = LEX_ISFIRSTCH.get() as i32;
ls.lexflags = LEX_LEXFLAGS.get();
ls.tok = tok();
ls.tokstr = LEX_TOKSTR.with_borrow_mut(|t| t.take());
ls.zshlextext = None;
LEX_LEXBUF.with_borrow_mut(|b| {
ls.lexbuf.ptr = b.ptr.take();
ls.lexbuf.siz = b.siz;
ls.lexbuf.len = b.len;
});
ls.lex_add_raw = LEX_LEX_ADD_RAW.get();
ls.tokstr_raw = LEX_TOKSTR_RAW.with_borrow_mut(|t| t.take());
LEX_LEXBUF_RAW.with_borrow_mut(|b| {
ls.lexbuf_raw.ptr = b.ptr.take();
ls.lexbuf_raw.siz = b.siz;
ls.lexbuf_raw.len = b.len;
});
ls.lexstop = LEX_LEXSTOP.get() as i32;
ls.toklineno = LEX_TOKLINENO.get() as i64;
set_tokstr(None);
LEX_LEXBUF.with_borrow_mut(|b| {
b.ptr = Some(String::with_capacity(256));
b.siz = 256;
b.len = 0;
});
LEX_TOKSTR_RAW.with_borrow_mut(|t| *t = None);
LEX_LEXBUF_RAW.with_borrow_mut(|b| {
b.ptr = None;
b.siz = 0;
b.len = 0;
});
LEX_LEX_ADD_RAW.set(0);
}
pub fn lex_context_restore(ls: &mut lex_stack) {
LEX_DBPARENS.set(ls.dbparens != 0);
LEX_ISFIRSTLN.set(ls.isfirstln != 0);
LEX_ISFIRSTCH.set(ls.isfirstch != 0);
LEX_LEXFLAGS.set(ls.lexflags);
set_tok(ls.tok);
set_tokstr(ls.tokstr.take());
let _ = ls.zshlextext.take();
LEX_LEXBUF.with_borrow_mut(|b| {
b.ptr = Some(ls.lexbuf.ptr.take().unwrap_or_default());
b.siz = ls.lexbuf.siz;
b.len = ls.lexbuf.len;
});
LEX_LEX_ADD_RAW.set(ls.lex_add_raw);
LEX_TOKSTR_RAW.with_borrow_mut(|t| *t = ls.tokstr_raw.take());
LEX_LEXBUF_RAW.with_borrow_mut(|b| {
b.ptr = ls.lexbuf_raw.ptr.take();
b.siz = ls.lexbuf_raw.siz;
b.len = ls.lexbuf_raw.len;
});
LEX_LEXSTOP.set(ls.lexstop != 0);
LEX_TOKLINENO.set(ls.toklineno as u64);
}
pub fn zshlex() {
if tok() == LEXERR {
return;
}
loop {
if LEX_INREPEAT.get() > 0 {
LEX_INREPEAT.set(LEX_INREPEAT.get() + 1);
}
if LEX_INREPEAT.get() == 3 && (isset(SHORTLOOPS) || isset(SHORTREPEAT)) {
LEX_INCMDPOS.set(true);
}
let _t = gettok();
set_tok(_t);
if tok() == ENDINPUT || !exalias() {
break;
}
}
LEX_NOCORRECT.set(LEX_NOCORRECT.get() & 1);
if tok() == NEWLIN || tok() == ENDINPUT {
while let Some(mut node) = HDOCS.with_borrow_mut(|h| h.take()) {
let next: Option<Box<crate::ported::zsh_h::heredocs>> = node.next.take();
let doc: Option<String>;
let mut munged_term: String;
cmdpush(if node.typ == crate::ported::zsh_h::REDIR_HEREDOC {
CS_HEREDOC as u8
} else {
CS_HEREDOCD as u8
});
munged_term = crate::ported::mem::dupstring(node.str.as_deref().unwrap_or(""));
doc = crate::ported::exec::gethere(&mut munged_term, node.typ);
cmdpop();
let Some(doc) = doc else {
zerr("here document too large");
HDOCS.with_borrow_mut(|h| *h = None);
set_tok(LEXERR);
break;
};
crate::ported::parse::setheredoc(
node.pc as usize,
crate::ported::zsh_h::REDIR_HERESTR,
&doc,
node.str.as_deref().unwrap_or(""),
&munged_term,
);
LEX_HEREDOCS.with_borrow_mut(|v| {
for h in v.iter_mut() {
if !h.processed {
h.content = doc;
h.processed = true;
return;
}
}
});
drop(node);
HDOCS.with_borrow_mut(|h| *h = next);
}
}
if tok() != NEWLIN {
LEX_ISNEWLIN.set(0);
} else {
LEX_ISNEWLIN.set(if LEX_POS.get() < LEX_INPUT.with_borrow(|s| s.len()) {
-1
} else {
1
});
}
if tok() == SEMI || (tok() == NEWLIN && LEX_LEXFLAGS.get() & LEXFLAGS_NEWLINE == 0) {
set_tok(SEPER);
}
}
pub fn ctxtlex() {
zshlex();
match tok() {
SEPER | NEWLIN | SEMI | DSEMI | SEMIAMP | SEMIBAR | AMPER | AMPERBANG | INPAR_TOK
| INBRACE_TOK | DBAR | DAMPER | BAR_TOK | BARAMP | INOUTPAR | DOLOOP | THEN | ELIF
| ELSE | DOUTBRACK => {
LEX_INCMDPOS.set(true);
}
STRING_LEX | TYPESET | ENVARRAY | OUTPAR_TOK | CASE | DINBRACK => {
LEX_INCMDPOS.set(false);
}
_ => {}
}
if tok() != DINPAR {
LEX_INFOR.set(if tok() == FOR { 2 } else { 0 });
}
if IS_REDIROP(tok()) || tok() == FOR || tok() == FOREACH || tok() == SELECT {
LEX_INREDIR.set(true);
LEX_OLDPOS.set(LEX_INCMDPOS.get());
LEX_INCMDPOS.set(false);
} else if LEX_INREDIR.get() {
LEX_INCMDPOS.set(LEX_OLDPOS.get());
LEX_INREDIR.set(false);
}
}
pub const LX1_BKSLASH: u8 = 0;
pub const LX1_COMMENT: u8 = 1;
pub const LX1_NEWLIN: u8 = 2;
pub const LX1_SEMI: u8 = 3;
pub const LX1_AMPER: u8 = 5;
pub const LX1_BAR: u8 = 6;
pub const LX1_INPAR: u8 = 7;
pub const LX1_OUTPAR: u8 = 8;
pub const LX1_INANG: u8 = 13;
pub const LX1_OUTANG: u8 = 14;
pub const LX1_OTHER: u8 = 15;
pub const LX2_BREAK: u8 = 0;
pub const LX2_OUTPAR: u8 = 1;
pub const LX2_BAR: u8 = 2;
pub const LX2_STRING: u8 = 3;
pub const LX2_INBRACK: u8 = 4;
pub const LX2_OUTBRACK: u8 = 5;
pub const LX2_TILDE: u8 = 6;
pub const LX2_INPAR: u8 = 7;
pub const LX2_INBRACE: u8 = 8;
pub const LX2_OUTBRACE: u8 = 9;
pub const LX2_OUTANG: u8 = 10;
pub const LX2_INANG: u8 = 11;
pub const LX2_EQUALS: u8 = 12;
pub const LX2_BKSLASH: u8 = 13;
pub const LX2_QUOTE: u8 = 14;
pub const LX2_DQUOTE: u8 = 15;
pub const LX2_BQUOTE: u8 = 16;
pub const LX2_COMMA: u8 = 17;
pub const LX2_DASH: u8 = 18;
pub const LX2_BANG: u8 = 19;
pub const LX2_OTHER: u8 = 20;
pub const LX2_META: u8 = 21;
pub fn initlextabs() {
let a1 = LEXACT1.get_or_init(|| std::sync::Mutex::new([0u8; 256]));
let a2 = LEXACT2.get_or_init(|| std::sync::Mutex::new([0u8; 256]));
let t2 = LEXTOK2.get_or_init(|| std::sync::Mutex::new([0u8; 256]));
let mut a1 = a1.lock().unwrap();
let mut a2 = a2.lock().unwrap();
let mut t2 = t2.lock().unwrap();
for i in 0..256 {
a1[i] = LX1_OTHER;
a2[i] = LX2_OTHER;
t2[i] = i as u8;
}
let lx1 = b"\\q\n;!&|(){}[]<>";
for (i, &c) in lx1.iter().enumerate() {
a1[c as usize] = i as u8;
}
let lx2 = b";)|$[]~({}><=\\'\"`,-!";
for (i, &c) in lx2.iter().enumerate() {
a2[c as usize] = i as u8;
}
a2[b'&' as usize] = LX2_BREAK;
a2[Meta as usize] = LX2_META;
t2[b'*' as usize] = Star as u8;
t2[b'?' as usize] = Quest as u8;
t2[b'{' as usize] = Inbrace as u8;
t2[b'[' as usize] = Inbrack as u8;
t2[b'$' as usize] = Stringg as u8;
t2[b'~' as usize] = Tilde as u8;
t2[b'#' as usize] = Pound as u8;
t2[b'^' as usize] = Hat as u8;
}
pub fn lexinit() {
LEX_NOCORRECT.set(0);
LEX_DBPARENS.set(false);
LEX_LEXSTOP.set(false);
set_tok(ENDINPUT);
}
fn add(c: char) {
LEX_LEXBUF.with_borrow_mut(|b| b.add(c));
}
fn cmd_or_math() -> i32 {
let oldlen = LEX_LEXBUF.with_borrow(|b| b.buf_len());
cmdpush(CS_MATH as u8);
if dquote_parse(')', false).is_err() {
cmdpop();
while LEX_LEXBUF.with_borrow(|b| b.buf_len()) > oldlen {
if let Some(c) = LEX_LEXBUF.with_borrow_mut(|b| b.pop()) {
hungetc(c);
}
}
hungetc('(');
LEX_LEXSTOP.set(false);
return if skipcomm().is_err() {
CMD_OR_MATH_ERR
} else {
CMD_OR_MATH_CMD
};
}
let c = hgetc();
if c == Some(')') {
cmdpop();
return CMD_OR_MATH_MATH;
}
cmdpop();
if let Some(c) = c {
hungetc(c);
}
LEX_LEXSTOP.set(false);
while LEX_LEXBUF.with_borrow(|b| b.buf_len()) > oldlen {
if let Some(c) = LEX_LEXBUF.with_borrow_mut(|b| b.pop()) {
hungetc(c);
}
}
hungetc('(');
if skipcomm().is_err() {
CMD_OR_MATH_ERR
} else {
CMD_OR_MATH_CMD
}
}
fn cmd_or_math_sub() -> i32 {
loop {
let c = hgetc();
if c == Some('\\') {
let c2 = hgetc();
if c2 != Some('\n') {
if let Some(c2) = c2 {
hungetc(c2);
}
hungetc('\\');
LEX_LEXSTOP.set(false);
return if skipcomm().is_err() {
CMD_OR_MATH_ERR
} else {
CMD_OR_MATH_CMD
};
}
continue;
}
if c == Some('(') {
let lexpos = LEX_LEXBUF.with_borrow(|b| b.buf_len());
add(Inpar);
add('(');
if dquote_parse(')', false).is_ok() {
let c2 = hgetc();
if c2 == Some(')') {
LEX_LEXBUF.with_borrow_mut(|b| b.set_char_at(lexpos, Inparmath));
add(')');
return CMD_OR_MATH_MATH;
}
if let Some(c2) = c2 {
hungetc(c2);
}
}
while LEX_LEXBUF.with_borrow(|b| b.buf_len()) > lexpos {
if let Some(ch) = LEX_LEXBUF.with_borrow_mut(|b| b.pop()) {
hungetc(ch);
}
}
hungetc('(');
LEX_LEXSTOP.set(false);
} else {
if let Some(c) = c {
hungetc(c);
}
LEX_LEXSTOP.set(false);
}
return if skipcomm().is_err() {
CMD_OR_MATH_ERR
} else {
CMD_OR_MATH_CMD
};
}
}
pub fn isnumglob() -> bool {
let mut ec: char = '-'; let mut ret = false; let mut buf: Vec<char> = Vec::new();
loop {
let cn = hgetc(); if LEX_LEXSTOP.get() {
LEX_LEXSTOP.set(false); break; }
let Some(cn) = cn else { break };
buf.push(cn); if !cn.is_ascii_digit() {
if cn != ec {
break; }
if ec == '>' {
ret = true; break; }
ec = '>'; }
}
while let Some(ch) = buf.pop() {
hungetc(ch);
}
ret }
impl lexbufstate {
pub(crate) fn new() -> Self {
Self {
ptr: Some(String::with_capacity(256)),
siz: 256,
len: 0,
}
}
pub(crate) fn add(&mut self, c: char) {
if let Some(p) = self.ptr.as_mut() {
p.push(c);
self.len = p.len() as i32;
if self.len >= self.siz {
self.siz *= 2;
let want = self.siz as usize;
let have = p.capacity();
if want > have {
p.reserve(want - have);
}
}
}
}
pub(crate) fn clear(&mut self) {
if let Some(p) = self.ptr.as_mut() {
p.clear();
}
self.len = 0;
}
pub(crate) fn as_str(&self) -> &str {
self.ptr.as_deref().unwrap_or("")
}
pub(crate) fn buf_len(&self) -> usize {
self.len as usize
}
pub(crate) fn pop(&mut self) -> Option<char> {
let c = self.ptr.as_mut().and_then(|p| p.pop());
if c.is_some() {
self.len -= 1;
}
c
}
pub(crate) fn set_char_at(&mut self, byte_idx: usize, c: char) {
let Some(buf) = self.ptr.as_mut() else { return };
if byte_idx >= buf.len() {
return;
}
if !buf.is_char_boundary(byte_idx) {
return;
}
let old_byte_end = buf[byte_idx..]
.chars()
.next()
.map(|ch| byte_idx + ch.len_utf8());
let Some(old_byte_end) = old_byte_end else {
return;
};
let mut new_bytes = [0u8; 4];
let new_str = c.encode_utf8(&mut new_bytes);
if new_str.len() == old_byte_end - byte_idx {
let new_owned = new_str.to_string();
buf.replace_range(byte_idx..old_byte_end, &new_owned);
}
}
}
thread_local! {
pub static LEX_INPUT: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
pub static LEX_POS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
pub static LEX_UNGET_BUF: std::cell::RefCell<std::collections::VecDeque<char>>
= const { std::cell::RefCell::new(std::collections::VecDeque::new()) };
pub static LEX_TOKSTR: std::cell::RefCell<Option<String>> = const { std::cell::RefCell::new(None) };
pub static LEX_TOK: std::cell::Cell<lextok> = const { std::cell::Cell::new(ENDINPUT) };
pub static LEX_TOKFD: std::cell::Cell<i32> = const { std::cell::Cell::new(-1) };
pub static LEX_TOKLINENO: std::cell::Cell<u64> = const { std::cell::Cell::new(1) };
pub static LEX_LINENO: std::cell::Cell<u64> = const { std::cell::Cell::new(1) };
pub static LEX_LEXSTOP: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
pub static LEX_INCMDPOS: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
pub static LEX_INCOND: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
pub static LEX_INCONDPAT: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
pub static LEX_INCASEPAT: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
pub static LEX_INREDIR: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
pub static LEX_OLDPOS: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
pub static LEX_INFOR: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
pub static LEX_INREPEAT: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
pub static LEX_INTYPESET: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
pub static LEX_DBPARENS: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
pub static LEX_NOALIASES: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
pub static LEX_NOCORRECT: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
pub static LEX_NOCOMMENTS: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
pub static LEX_LEXFLAGS: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
pub static LEX_ALIAS_SPACE_FLAG: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
pub static LEX_WORDBEG: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
pub static LEX_PARBEGIN: std::cell::Cell<i32> = const { std::cell::Cell::new(-1) };
pub static LEX_PAREND: std::cell::Cell<i32> = const { std::cell::Cell::new(-1) };
pub static LEX_ISFIRSTLN: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
pub static LEX_ISFIRSTCH: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
pub static LEX_HEREDOCS: std::cell::RefCell<Vec<HereDoc>> = const { std::cell::RefCell::new(Vec::new()) };
pub static LEX_LEXBUF: std::cell::RefCell<lexbufstate> = const { std::cell::RefCell::new(
lexbufstate { ptr: None, siz: 0, len: 0 }
)};
pub static LEX_ISNEWLIN: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
pub static LEX_LEX_ADD_RAW: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
pub static LEX_TOKSTR_RAW: std::cell::RefCell<Option<String>> =
const { std::cell::RefCell::new(None) };
pub static LEX_LEXBUF_RAW: std::cell::RefCell<lexbufstate> = const { std::cell::RefCell::new(
lexbufstate { ptr: None, siz: 0, len: 0 }
)};
}
fn gettok() -> lextok {
let mut peekfd: i32 = -1;
set_tokstr(None);
let c = loop {
match hgetc() {
Some(ch) if crate::ztype_h::iblank(ch as u8) => continue,
Some(ch) => break ch,
None => {
use std::sync::atomic::Ordering;
LEX_LEXSTOP.set(true);
return if errflag.load(Ordering::Relaxed) != 0 {
LEXERR
} else {
ENDINPUT
};
}
}
};
LEX_TOKLINENO.set(LEX_LINENO.get());
LEX_ISFIRSTLN.set(false);
let qbang_at_bang = crate::ported::hist::qbang.load(std::sync::atomic::Ordering::SeqCst)
&& c as i32 == crate::ported::hist::bangchar.load(std::sync::atomic::Ordering::SeqCst);
let qbang_adj: i32 = if qbang_at_bang { 1 } else { 0 };
if (LEX_LEXFLAGS.get() & LEXFLAGS_ZLE) != 0
&& (crate::ported::input::inbufflags.with(|f| f.get()) & INP_ALIAS)
== 0
{
LEX_WORDBEG.set(crate::ported::input::inbufct.with(|c| c.get()) - qbang_adj);
}
crate::ported::hist::ihwbegin(-1 - qbang_adj);
if LEX_DBPARENS.get() {
LEX_LEXBUF.with_borrow_mut(|b| b.clear());
hungetc(c);
let end_char = if LEX_INFOR.get() > 0 { ';' } else { ')' };
cmdpush(CS_MATH as u8);
let parse_ok = dquote_parse(end_char, false).is_ok();
cmdpop();
if !parse_ok {
return LEXERR;
}
set_tokstr(Some(LEX_LEXBUF.with_borrow(|b| b.as_str().to_string())));
if LEX_INFOR.get() > 0 {
LEX_INFOR.set(LEX_INFOR.get() - 1);
return DINPAR;
}
match hgetc() {
Some(')') => {
LEX_DBPARENS.set(false);
return DOUTPAR;
}
c => {
if let Some(c) = c {
hungetc(c);
}
return LEXERR;
}
}
}
let mut c = c;
if crate::ztype_h::idigit(c as u8) {
let d = hgetc();
match d {
Some('&') => {
let e = hgetc();
if e == Some('>') {
peekfd = (c as u8 - b'0') as i32;
hungetc('>');
c = '&';
} else {
if let Some(e) = e {
hungetc(e);
}
hungetc('&');
}
}
Some('>') | Some('<') => {
peekfd = (c as u8 - b'0') as i32;
c = d.unwrap();
}
Some(d) => {
hungetc(d);
}
None => {}
}
LEX_LEXSTOP.set(false);
}
let lexflags = LEX_LEXFLAGS.get();
let allow_comment_via_flags = (lexflags == 0 || (lexflags & LEXFLAGS_COMMENTS) != 0)
&& (!interact() || unset(SHINSTDIN));
if c as i32 == crate::ported::hist::hashchar.load(std::sync::atomic::Ordering::SeqCst)
&& !LEX_NOCOMMENTS.get()
&& (isset(INTERACTIVECOMMENTS) || allow_comment_via_flags)
{
if LEX_LEXFLAGS.get() & LEXFLAGS_COMMENTS_KEEP != 0 {
LEX_LEXBUF.with_borrow_mut(|b| b.clear());
add('#');
}
loop {
let c = hgetc();
match c {
Some('\n') | None => break,
Some(c) => {
if LEX_LEXFLAGS.get() & LEXFLAGS_COMMENTS_KEEP != 0 {
add(c);
}
}
}
}
if LEX_LEXFLAGS.get() & LEXFLAGS_COMMENTS_KEEP != 0 {
set_tokstr(Some(LEX_LEXBUF.with_borrow(|b| b.as_str().to_string())));
if !LEX_LEXSTOP.get() {
hungetc('\n');
}
return STRING_LEX;
}
if LEX_LEXFLAGS.get() & LEXFLAGS_COMMENTS_STRIP != 0 && LEX_LEXSTOP.get() {
return ENDINPUT;
}
return NEWLIN;
}
let act = lexact1_get(c);
match act {
LX1_BKSLASH => {
let d = hgetc();
if d == Some('\n') {
return gettok();
}
if let Some(d) = d {
hungetc(d);
}
LEX_LEXSTOP.set(false);
gettokstr(c, false)
}
LX1_NEWLIN => NEWLIN,
LX1_SEMI => {
let d = hgetc();
match d {
Some(';') => DSEMI,
Some('&') => SEMIAMP,
Some('|') => SEMIBAR,
_ => {
if let Some(d) = d {
hungetc(d);
}
LEX_LEXSTOP.set(false);
SEMI
}
}
}
LX1_AMPER => {
let d = hgetc();
match d {
Some('&') => DAMPER,
Some('!') | Some('|') => AMPERBANG,
Some('>') => {
LEX_TOKFD.set(peekfd);
let e = hgetc();
match e {
Some('!') | Some('|') => OUTANGAMPBANG,
Some('>') => {
let f = hgetc();
match f {
Some('!') | Some('|') => DOUTANGAMPBANG,
_ => {
if let Some(f) = f {
hungetc(f);
}
LEX_LEXSTOP.set(false);
DOUTANGAMP
}
}
}
_ => {
if let Some(e) = e {
hungetc(e);
}
LEX_LEXSTOP.set(false);
AMPOUTANG
}
}
}
_ => {
if let Some(d) = d {
hungetc(d);
}
LEX_LEXSTOP.set(false);
AMPER
}
}
}
LX1_BAR => {
let d = hgetc();
match d {
Some('|') if LEX_INCASEPAT.get() <= 0 => DBAR,
Some('&') => BARAMP,
_ => {
if let Some(d) = d {
hungetc(d);
}
LEX_LEXSTOP.set(false);
BAR_TOK
}
}
}
LX1_INPAR => {
let d = hgetc();
match d {
Some('(') => {
if LEX_INFOR.get() > 0 {
LEX_DBPARENS.set(true);
return DINPAR;
}
if LEX_INCMDPOS.get() || (isset(SHGLOB) && unset(KSHGLOB)) {
LEX_LEXBUF.with_borrow_mut(|b| b.clear());
match cmd_or_math() {
CMD_OR_MATH_MATH => {
set_tokstr(Some(
LEX_LEXBUF.with_borrow(|b| b.as_str().to_string()),
));
return DINPAR;
}
CMD_OR_MATH_CMD => {
set_tokstr(None);
return INPAR_TOK;
}
CMD_OR_MATH_ERR | _ => return LEXERR,
}
}
hungetc('(');
LEX_LEXSTOP.set(false);
gettokstr('(', false)
}
Some(')') => INOUTPAR,
_ => {
if let Some(d) = d {
hungetc(d);
}
LEX_LEXSTOP.set(false);
if isset(SHGLOB)
|| LEX_INCOND.get() == 1
|| LEX_INCMDPOS.get()
|| LEX_INCASEPAT.get() >= 1
{
INPAR_TOK
} else {
gettokstr('(', false)
}
}
}
}
LX1_OUTPAR => OUTPAR_TOK,
LX1_INANG => {
if LEX_INCONDPAT.get() || LEX_INCASEPAT.get() > 0 {
return gettokstr(c, false);
}
let d = hgetc();
let peek = match d {
Some('(') => {
hungetc('(');
LEX_LEXSTOP.set(false);
return gettokstr('<', false);
}
Some('>') => INOUTANG,
Some('<') => {
let e = hgetc();
match e {
Some('(') => {
hungetc('(');
hungetc('<');
INANG_TOK
}
Some('<') => TRINANG,
Some('-') => DINANGDASH,
_ => {
if let Some(e) = e {
hungetc(e);
}
LEX_LEXSTOP.set(false);
DINANG
}
}
}
Some('&') => INANGAMP,
_ => {
if let Some(d) = d {
hungetc(d);
}
if isnumglob() {
if peekfd != -1 {
hungetc(c); return gettokstr(
((b'0' as i32) + peekfd) as u8 as char,
false,
);
}
LEX_LEXSTOP.set(false);
return gettokstr(c, false);
}
LEX_LEXSTOP.set(false);
INANG_TOK
}
};
LEX_TOKFD.set(peekfd);
peek
}
LX1_OUTANG => {
if LEX_INCONDPAT.get() || LEX_INCASEPAT.get() > 0 {
return gettokstr(c, false);
}
let d = hgetc();
let peek = match d {
Some('(') => {
hungetc('(');
LEX_LEXSTOP.set(false);
return gettokstr('>', false);
}
Some('&') => {
let e = hgetc();
match e {
Some('!') | Some('|') => OUTANGAMPBANG,
_ => {
if let Some(e) = e {
hungetc(e);
}
LEX_LEXSTOP.set(false);
OUTANGAMP
}
}
}
Some('!') | Some('|') => OUTANGBANG,
Some('>') => {
let e = hgetc();
match e {
Some('&') => {
let f = hgetc();
match f {
Some('!') | Some('|') => DOUTANGAMPBANG,
_ => {
if let Some(f) = f {
hungetc(f);
}
LEX_LEXSTOP.set(false);
DOUTANGAMP
}
}
}
Some('!') | Some('|') => DOUTANGBANG,
Some('(') => {
hungetc('(');
hungetc('>');
OUTANG_TOK
}
_ => {
if let Some(e) = e {
hungetc(e);
}
LEX_LEXSTOP.set(false);
if isset(HISTALLOWCLOBBER) {
hwaddc('|');
}
DOUTANG
}
}
}
_ => {
if let Some(d) = d {
hungetc(d);
}
LEX_LEXSTOP.set(false);
if LEX_INCOND.get() == 0 && isset(HISTALLOWCLOBBER) {
hwaddc('|');
}
OUTANG_TOK
}
};
LEX_TOKFD.set(peekfd);
peek
}
_ => gettokstr(c, false),
}
}
fn gettokstr(c: char, sub: bool) -> lextok {
let mut bct = 0; let mut pct = 0; let mut brct = 0; let mut in_brace_param = 0;
let mut cmdsubst: bool = false;
let _ = &mut cmdsubst; let mut peek = STRING_LEX;
let mut intpos = 1;
let mut unmatched = '\0';
let mut c = c;
if !sub {
LEX_LEXBUF.with_borrow_mut(|b| b.clear());
}
loop {
let inbl = crate::ztype_h::inblank(c as u8);
if inbl && in_brace_param == 0 && pct == 0 {
break;
}
match lexact2_get(c) {
LX2_OUTPAR => {
if (sub || in_brace_param > 0) && isset(SHGLOB) {
break;
}
if in_brace_param > 0 || sub {
add(Outpar);
} else if pct > 0 {
pct -= 1;
add(Outpar);
} else {
break;
}
}
LX2_BAR => {
if pct == 0 && in_brace_param == 0 {
if sub {
add(c);
} else {
break;
}
} else if unset(SHGLOB) || (!sub && in_brace_param == 0) {
add(Bar);
} else {
add(c);
}
}
LX2_STRING => {
let e = hgetc();
match e {
Some('\\') => {
let f = hgetc();
if f != Some('\n') {
if let Some(f) = f {
hungetc(f);
}
hungetc('\\');
add(Stringg);
} else {
continue;
}
}
Some('[') => {
add(Stringg);
add(Inbrack);
cmdpush(CS_MATHSUBST as u8);
let r = dquote_parse(']', sub);
cmdpop();
if r.is_err() {
peek = LEXERR;
break;
}
add(Outbrack);
}
Some('(') => {
add(Stringg);
match cmd_or_math_sub() {
CMD_OR_MATH_CMD => add(Outpar),
CMD_OR_MATH_MATH => add(Outparmath),
CMD_OR_MATH_ERR | _ => {
peek = LEXERR;
break;
}
}
}
Some('{') => {
add(Stringg);
add(Inbrace);
bct += 1;
cmdpush(CS_BRACEPAR as u8);
if in_brace_param == 0 {
in_brace_param = bct;
}
}
Some('\'') => {
add(Stringg);
add(Snull);
loop {
let ch = hgetc();
match ch {
Some('\'') => break,
Some('\\') => {
let next = hgetc();
match next {
Some(n) => {
if n == '\\' || n == '\'' {
add(Bnull);
} else {
add('\\');
}
add(n);
}
None => {
LEX_LEXSTOP.set(true);
unmatched = '\'';
peek = LEXERR;
break;
}
}
}
Some(ch) => add(ch),
None => {
LEX_LEXSTOP.set(true);
unmatched = '\'';
peek = LEXERR;
break;
}
}
}
if unmatched != '\0' {
break;
}
add(Snull);
}
Some('"') => {
add(Stringg);
add(Dnull);
if dquote_parse('"', sub).is_err() {
peek = LEXERR;
break;
}
add(Dnull);
}
_ => {
if let Some(e) = e {
hungetc(e);
}
LEX_LEXSTOP.set(false);
add(Stringg);
}
}
}
LX2_INBRACK => {
if in_brace_param == 0 {
brct += 1;
}
add(Inbrack);
}
LX2_OUTBRACK => {
if in_brace_param == 0 && brct > 0 {
brct -= 1;
}
add(Outbrack);
}
LX2_INPAR => {
if isset(SHGLOB) {
if sub || in_brace_param > 0 {
break;
}
if unset(KSHGLOB) && LEX_LEXBUF.with_borrow(|b| b.len) > 0 {
break;
}
}
if in_brace_param == 0 && !sub {
let e = hgetc();
if let Some(ch) = e {
hungetc(ch);
}
LEX_LEXSTOP.set(false);
let is_inblank = matches!(e, Some(' ' | '\t'));
if e == Some(')')
|| (isset(SHGLOB)
&& is_inblank
&& bct == 0
&& brct == 0
&& intpos > 0
&& LEX_INCMDPOS.get())
{
break;
}
}
if in_brace_param == 0 {
pct += 1;
}
add(Inpar);
}
LX2_INBRACE => {
if (isset(IGNOREBRACES) && !cmdsubst) || sub {
add('{');
} else {
if LEX_LEXBUF.with_borrow(|b| b.len) == 0 && LEX_INCMDPOS.get() {
add('{');
set_tokstr(Some(LEX_LEXBUF.with_borrow(|b| b.as_str().to_string())));
return STRING_LEX;
}
if in_brace_param > 0 {
cmdpush(CS_BRACE as u8);
}
bct += 1;
add(Inbrace);
}
}
LX2_OUTBRACE => {
if (isset(IGNOREBRACES) || sub) && in_brace_param == 0 {
add('}');
} else if in_brace_param > 0 {
cmdpop(); if bct == in_brace_param {
if cmdsubst {
cmdpop();
}
in_brace_param = 0;
cmdsubst = false;
}
bct -= 1;
add(Outbrace);
} else if bct > 0 {
bct -= 1;
add(Outbrace);
} else {
add(c);
}
}
LX2_OUTANG => {
if in_brace_param > 0 || sub || LEX_INCONDPAT.get() || LEX_INCASEPAT.get() > 0 {
add(c);
} else {
let e = hgetc();
if e != Some('(') {
if let Some(e) = e {
hungetc(e);
}
LEX_LEXSTOP.set(false);
break;
}
add(OutangProc);
if skipcomm().is_err() {
peek = LEXERR;
break;
}
add(Outpar);
}
}
LX2_INANG => {
if isset(SHGLOB) && sub {
break;
}
let e = hgetc(); if !(in_brace_param > 0 || sub) && e == Some('(') {
add(Inang); if skipcomm().is_err() {
peek = LEXERR;
break;
}
add(Outpar); } else {
if let Some(e) = e {
hungetc(e);
}
if LEX_INCONDPAT.get() || LEX_INCASEPAT.get() > 0 {
add(c);
} else if isnumglob() {
add(Inang); while let Some(ch) = hgetc() {
if ch == '>' {
break;
}
add(ch); }
add(Outang); } else {
LEX_LEXSTOP.set(false); if in_brace_param > 0 || sub {
add(c);
} else {
break;
}
}
}
}
LX2_EQUALS => {
if !sub {
if intpos > 0 {
let e = hgetc();
if e == Some('(') {
add(Equals);
if skipcomm().is_err() {
peek = LEXERR;
break;
}
add(Outpar);
} else {
if let Some(e) = e {
hungetc(e);
}
LEX_LEXSTOP.set(false);
add(Equals);
}
} else if peek != ENVSTRING
&& (LEX_INCMDPOS.get() || LEX_INTYPESET.get())
&& bct == 0
&& brct == 0
&& LEX_INCASEPAT.get() == 0
{
let tok_so_far = LEX_LEXBUF.with_borrow(|b| b.as_str().to_string());
if is_valid_assignment_target(&tok_so_far) {
let next = hgetc();
if next == Some('(') {
set_tokstr(Some(
LEX_LEXBUF.with_borrow(|b| b.as_str().to_string()),
));
return ENVARRAY;
}
if let Some(next) = next {
hungetc(next);
}
LEX_LEXSTOP.set(false);
peek = ENVSTRING;
intpos = 2;
add(Equals);
} else {
add(Equals);
}
} else {
add(Equals);
}
} else {
add(Equals);
}
}
LX2_BKSLASH => {
let next = hgetc();
if next == Some('\n') {
let next = hgetc();
if let Some(next) = next {
c = next;
continue;
}
break;
} else {
add(Bnull);
if let Some(next) = next {
add(next);
}
}
}
LX2_QUOTE => {
cmdpush(CS_QUOTE as u8);
add(Snull);
loop {
let inner_loop_done = loop {
let ch = hgetc();
match ch {
Some('\'') => break false,
Some('\n') if !sub && isset(CSHJUNKIEQUOTES) => {
let last_was_bslash =
LEX_LEXBUF.with_borrow(|b| b.as_str().ends_with('\\'));
if last_was_bslash {
LEX_LEXBUF.with_borrow_mut(|b| {
if let Some(s) = b.ptr.as_mut() {
s.pop();
b.len = s.len() as i32;
}
});
} else {
break true; }
}
Some(ch) => add(ch),
None => {
LEX_LEXSTOP.set(true);
unmatched = '\'';
peek = LEXERR;
break true;
}
}
};
if inner_loop_done || unmatched != '\0' {
break;
}
if unset(RCQUOTES) {
break;
}
let e = hgetc();
if e != Some('\'') {
if let Some(e) = e {
hungetc(e);
}
LEX_LEXSTOP.set(false);
break;
}
add('\'');
}
cmdpop();
if unmatched != '\0' {
break;
}
add(Snull);
}
LX2_DQUOTE => {
add(Dnull);
cmdpush(CS_DQUOTE as u8);
let r = dquote_parse('"', sub);
cmdpop();
if r.is_err() {
unmatched = '"';
if LEX_LEXFLAGS.get() & LEXFLAGS_ACTIVE == 0 {
peek = LEXERR;
}
break;
}
add(Dnull);
}
LX2_BQUOTE => {
add(Tick);
cmdpush(CS_BQUOTE as u8);
loop {
let ch = hgetc();
match ch {
Some('`') => break,
Some('\\') => {
let next = hgetc();
match next {
Some('\n') => {
if !sub && isset(CSHJUNKIEQUOTES) {
add('\n');
}
continue;
}
Some(c) if c == '`' || c == '\\' || c == '$' => {
add(Bnull);
add(c);
}
Some(c) => {
add('\\');
add(c);
}
None => break,
}
}
Some('\n') if !sub && isset(CSHJUNKIEQUOTES) => {
break;
}
Some(ch) => add(ch),
None => {
LEX_LEXSTOP.set(true);
unmatched = '`';
peek = LEXERR;
break;
}
}
}
cmdpop();
if unmatched != '\0' {
break;
}
add(Tick);
}
LX2_TILDE => {
add(Tilde);
}
LX2_COMMA if unset(IGNOREBRACES) && !sub && bct > in_brace_param => {
add(Comma);
}
LX2_COMMA => {
add(c);
}
LX2_DASH => {
add(Dash);
}
LX2_BANG if brct > 0 => {
add(Bang);
}
LX2_BREAK if in_brace_param == 0 && pct == 0 && brct == 0 => {
break;
}
LX2_BREAK => {
add(c);
}
LX2_OTHER => {
if c == '\n' && in_brace_param == 0 && pct == 0 && brct == 0 {
break;
}
if (c as u32) >= 256 {
add(c);
} else {
add(lextok2_get(c) as char);
}
}
_ => {
add(c);
}
}
c = match hgetc() {
Some(c) => c,
None => {
LEX_LEXSTOP.set(true);
break;
}
};
if intpos > 0 {
intpos -= 1;
}
}
if !LEX_LEXSTOP.get() {
hungetc(c);
}
if unmatched != '\0' && LEX_LEXFLAGS.get() & LEXFLAGS_ACTIVE == 0 {
zerr(&format!("unmatched {}", unmatched));
}
if in_brace_param > 0 {
zerr("closing brace expected");
}
set_tokstr(Some(LEX_LEXBUF.with_borrow(|b| b.as_str().to_string())));
peek
}
fn dquote_parse(endchar: char, sub: bool) -> Result<(), ()> {
let mut pct = 0; let mut brct = 0; let mut bct = 0; let mut intick = false; let is_math = endchar == ')' || endchar == ']' || LEX_INFOR.get() > 0;
let cleanup = |intick: bool, bct: i32| {
if intick {
cmdpop();
}
for _ in 0..bct {
cmdpop(); }
};
loop {
let c = hgetc();
let c = match c {
Some(c) if c == endchar && !intick && bct == 0 => {
if is_math && (pct > 0 || brct > 0) {
add(c);
if c == ')' {
pct -= 1;
} else if c == ']' {
brct -= 1;
}
continue;
}
cleanup(intick, bct);
return Ok(());
}
Some(c) => c,
None => {
LEX_LEXSTOP.set(true);
cleanup(intick, bct);
return Err(());
}
};
match c {
'\\' => {
let next = hgetc();
match next {
Some('\n') => {
if sub || unset(CSHJUNKIEQUOTES) || endchar != '"' {
continue;
}
add('\n');
}
Some(c)
if c == '$'
|| c == '\\'
|| (c == '}' && !intick && bct > 0)
|| c == endchar
|| c == '`'
|| (endchar == ']'
&& (c == '['
|| c == ']'
|| c == '('
|| c == ')'
|| c == '{'
|| c == '}'
|| (c == '"' && sub))) =>
{
add(Bnull);
add(c);
}
Some(c) => {
add('\\');
hungetc(c);
continue;
}
None => {
add('\\');
}
}
}
'\n' if !sub && isset(CSHJUNKIEQUOTES) && endchar == '"' => {
return Err(());
}
'$' => {
if intick {
add(c);
continue;
}
let next = hgetc();
match next {
Some('(') => {
add(Qstring);
match cmd_or_math_sub() {
CMD_OR_MATH_CMD => add(Outpar),
CMD_OR_MATH_MATH => add(Outparmath),
CMD_OR_MATH_ERR | _ => return Err(()),
}
}
Some('[') => {
add(Stringg);
add(Inbrack);
cmdpush(CS_MATHSUBST as u8);
let r = dquote_parse(']', sub);
cmdpop();
r?;
add(Outbrack);
}
Some('{') => {
add(Qstring);
add(Inbrace);
cmdpush(CS_BRACEPAR as u8);
bct += 1;
}
Some('$') => {
add(Qstring);
add('$');
}
_ => {
if let Some(next) = next {
hungetc(next);
}
LEX_LEXSTOP.set(false);
add(Qstring);
}
}
}
'}' => {
if intick || bct == 0 {
add(c);
} else {
add(Outbrace);
cmdpop();
bct -= 1;
}
}
'`' => {
add(Qtick);
if intick {
cmdpop();
} else {
cmdpush(CS_BQUOTE as u8);
}
intick = !intick;
}
'(' => {
if !is_math || bct == 0 {
pct += 1;
}
add(c);
}
')' => {
if !is_math || bct == 0 {
if pct == 0 && is_math {
return Err(());
}
pct -= 1;
}
add(c);
}
'[' => {
if !is_math || bct == 0 {
brct += 1;
}
add(c);
}
']' => {
if !is_math || bct == 0 {
if brct == 0 && is_math {
return Err(());
}
brct -= 1;
}
add(c);
}
'"' => {
if intick || (endchar != '"' && bct == 0) {
add(c);
} else if bct > 0 {
add(Dnull);
cmdpush(CS_DQUOTE as u8);
let r = dquote_parse('"', sub);
cmdpop();
r?;
add(Dnull);
} else {
return Err(());
}
}
_ => {
add(c);
}
}
}
}
pub fn parsestr(s: &str) -> Result<String, String> {
match parsestrnoerr(s) {
Ok(result) => Ok(result),
Err(msg) => {
let untok = untokenize(s); let _ = untok;
let ef = errflag .load(std::sync::atomic::Ordering::Relaxed);
if (ef & crate::ported::zsh_h::ERRFLAG_INT) == 0 {
zerr(&msg); set_tok(LEXERR); }
Err(msg)
}
}
}
pub fn parsestrnoerr(s: &str) -> Result<String, String> {
let untok = untokenize(s); let dup = dupstring_wlen(&untok, untok.len()); zcontext_save();
let saved_lex_input = LEX_INPUT.with_borrow(|s| s.clone());
let saved_lex_pos = LEX_POS.get();
let mut input_with_nul = dup.clone();
input_with_nul.push('\0');
LEX_INPUT.with_borrow_mut(|b| b.clear());
LEX_POS.set(0);
LEX_LEXSTOP.set(false);
inpush(&input_with_nul, 0, None);
strinbeg(0);
LEX_LEXBUF.with_borrow_mut(|b| {
b.ptr = Some(String::with_capacity(untok.len() + 1));
b.siz = (untok.len() + 1) as i32;
b.len = 0;
});
set_tokstr(None);
let parse_err = dquote_parse('\0', true).is_err();
let result = LEX_LEXBUF.with_borrow(|b| b.as_str().to_string());
strinend();
inpop();
LEX_INPUT.with_borrow_mut(|b| *b = saved_lex_input);
LEX_POS.set(saved_lex_pos);
DPUTS!(
CMDSTACK.with(|s| !s.borrow().is_empty()), "BUG: parsestr: cmdstack not empty." );
zcontext_restore();
if parse_err {
Err("parse error".to_string())
} else {
Ok(result)
}
}
pub fn parse_subscript(s: &str, endchar: char) -> Option<usize> {
if s.is_empty() || s.starts_with(endchar) {
return None;
}
let l = s.len();
let untok = untokenize(s); let dup = dupstring_wlen(&untok, untok.len());
zcontext_save();
inpush(&dup, 0, None);
strinbeg(0);
LEX_LEXBUF.with_borrow_mut(|b| {
b.ptr = Some(String::with_capacity(l + 1));
b.siz = (l + 1) as i32;
b.len = 0;
});
let parse_err = dquote_parse(endchar, false).is_err();
let toklen = LEX_LEXBUF.with_borrow(|b| b.len) as usize;
DPUTS!(toklen > l, "Bad length for parsed subscript"); strinend();
inpop();
DPUTS!(
CMDSTACK.with(|s| !s.borrow().is_empty()), "BUG: parse_subscript: cmdstack not empty." );
zcontext_restore();
if parse_err {
return None;
}
Some(toklen)
}
pub fn parse_subst_string(s: &str) -> Result<String, String> {
if s.is_empty() || s == Nularg.to_string() {
return Ok(String::new());
}
let l = s.len();
let untok = untokenize(s); let dup = dupstring_wlen(&untok, untok.len());
zcontext_save();
inpush(&dup, 0, None);
strinbeg(0);
LEX_LEXBUF.with_borrow_mut(|b| {
b.ptr = Some(String::with_capacity(l + 1));
b.siz = (l + 1) as i32;
b.len = 0;
});
set_tokstr(None);
let c0 = hgetc();
let ctok = match c0 {
Some(ch) => gettokstr(ch, true),
None => LEXERR,
};
let err = errflag.load(Ordering::Relaxed); let result = LEX_LEXBUF.with_borrow(|b| b.as_str().to_string());
strinend();
inpop();
DPUTS!(
CMDSTACK.with(|s| !s.borrow().is_empty()), "BUG: parse_subst_string: cmdstack not empty." );
zcontext_restore();
let post_err = errflag.load(Ordering::Relaxed); errflag.store(err | (post_err & ERRFLAG_INT), Ordering::Relaxed); if ctok == LEXERR {
return Err("parse error".to_string());
}
Ok(result)
}
pub fn gotword() {
let zlemetacs = crate::ported::zle::compcore::ZLEMETACS.load(Ordering::SeqCst);
let zlemetall = crate::ported::zle::compcore::ZLEMETALL.load(Ordering::SeqCst);
let addedx = crate::ported::zle::compcore::ADDEDX.load(Ordering::SeqCst);
let inbufct = crate::ported::input::inbufct.with(|c| c.get());
let wordbeg = LEX_WORDBEG.get();
let nwe = zlemetall + 1 - inbufct + if addedx == 2 { 1 } else { 0 };
if zlemetacs <= nwe {
let nwb = zlemetall - wordbeg + addedx;
if zlemetacs >= nwb {
WB.store(nwb, Ordering::SeqCst);
WE.store(nwe, Ordering::SeqCst);
} else {
let wb_new = zlemetacs + addedx;
WB.store(wb_new, Ordering::SeqCst);
let we_cur = WE.load(Ordering::SeqCst);
if we_cur < wb_new {
WE.store(wb_new, Ordering::SeqCst);
}
}
LEX_LEXFLAGS.set(0);
}
}
fn checkalias(lextext: &str) -> bool {
if lextext.is_empty() {
return false;
}
if LEX_NOALIASES.get() || !isset(ALIASESOPT) {
return false;
}
if isset(POSIXALIASES) {
if tok() != STRING_LEX {
return false;
}
let is_reswd = reswdtab_lock()
.read()
.expect("reswdtab poisoned")
.get(lextext)
.is_some();
if is_reswd {
return false;
}
}
let alias_clone: Option<alias> = {
let guard = aliastab_lock()
.read()
.expect("aliastab poisoned");
guard.get(lextext).cloned()
};
if let Some(alias) = alias_clone {
let is_global = (alias.node.flags & crate::ported::zsh_h::ALIAS_GLOBAL) != 0;
if alias.inuse == 0 && (is_global || (LEX_INCMDPOS.get() && tok() == STRING_LEX)) {
while let Some(c) = LEX_UNGET_BUF.with_borrow(|b| b.front().copied()) {
if !crate::ztype_h::iblank(c as u8) {
break;
}
LEX_UNGET_BUF.with_borrow_mut(|b| { b.pop_front(); });
}
if !LEX_LEXSTOP.get() {
if let Some(c) = peek() {
if !crate::ztype_h::iblank(c as u8) {
inpush(" ", INP_ALIAS, None);
}
}
}
inpush(
&alias.text,
INP_ALIAS,
Some(lextext.to_string()),
);
if !is_global && alias.text.starts_with(' ') {
LEX_ALIAS_SPACE_FLAG.set(1);
}
let mut guard = aliastab_lock()
.write()
.expect("aliastab poisoned");
if let Some(a) = guard.get_mut(lextext) {
a.inuse = 1;
}
drop(guard);
LEX_LEXSTOP.set(false);
return true;
}
}
if LEX_INCMDPOS.get() {
if let Some(dot_pos) = lextext.rfind('.') {
if dot_pos > 0 && dot_pos + 1 < lextext.len() {
let suffix = &lextext[dot_pos + 1..];
let alias_clone: Option<alias> = {
let guard = sufaliastab_lock()
.read()
.expect("sufaliastab poisoned");
guard.get(suffix).cloned()
};
if let Some(alias) = alias_clone {
if alias.inuse == 0 {
inpush(
lextext,
INP_ALIAS,
Some(suffix.to_string()),
);
inpush(" ", INP_ALIAS, None);
inpush(
&alias.text,
INP_ALIAS,
None,
);
let mut guard = sufaliastab_lock()
.write()
.expect("sufaliastab poisoned");
if let Some(a) = guard.get_mut(suffix) {
a.inuse = 1;
}
drop(guard);
LEX_LEXSTOP.set(false);
return true;
}
}
}
}
}
false
}
pub fn exalias() -> bool {
let inbufflags_alias =
(crate::ported::input::inbufflags.with(|f| f.get()) & INP_ALIAS) != 0;
let strin_set = crate::ported::input::strin.with(|c| c.get()) != 0;
if interact()
&& isset(SHINSTDIN)
&& !strin_set
&& LEX_INCASEPAT.get() <= 0
&& tok() == STRING_LEX
&& LEX_NOCORRECT.get() == 0
&& !inbufflags_alias
&& crate::ported::hist::hist_is_in_word() == 0
&& (isset(CORRECTALL) || (isset(CORRECT) && LEX_INCMDPOS.get()))
{
if let Some(word) = tokstr() {
let mut buf = if has_token(&word) {
untokenize(&word)
} else {
word.clone()
};
crate::ported::utils::spckword(
&mut buf,
1, if LEX_INCMDPOS.get() { 1 } else { 0 }, 1, );
if buf != word {
set_tokstr(Some(buf));
}
}
}
if LEX_TOKSTR.with_borrow(|t| t.is_none()) {
if tok() == NEWLIN {
return false;
}
let text = match tok() {
SEMI => ";",
AMPER => "&",
BAR_TOK => "|",
_ => return false,
};
return checkalias(text);
}
let tokstr = tokstr().unwrap();
let lextext = if has_token(&tokstr) {
untokenize(&tokstr)
} else {
tokstr.clone()
};
if LEX_LEXFLAGS.get() & LEXFLAGS_ZLE != 0 {
let zp = LEX_LEXFLAGS.get();
gotword();
if (zp & LEXFLAGS_ZLE) != 0 && LEX_LEXFLAGS.get() == 0 {
return false;
}
}
if tok() == STRING_LEX {
let had_tokens = has_token(&tokstr);
if (!had_tokens || !isset(POSIXALIASES)) && checkalias(&lextext) {
return true;
}
let is_close_brace_special =
lextext == "}" && unset(IGNOREBRACES) && unset(IGNORECLOSEBRACES);
let reswd_path_eligible = LEX_INCMDPOS.get() || is_close_brace_special;
let rw_tok: Option<lextok> = if reswd_path_eligible {
let guard = reswdtab_lock()
.read()
.expect("reswdtab poisoned");
guard.get(&lextext).map(|r| r.token)
} else {
None
};
if let Some(rwtok) = rw_tok {
set_tok(rwtok);
if rwtok == REPEAT {
LEX_INREPEAT.set(1);
}
if rwtok == DINBRACK {
LEX_INCOND.set(1);
}
} else if LEX_INCOND.get() > 0 && lextext == "]]" {
set_tok(DOUTBRACK);
LEX_INCOND.set(0);
} else if LEX_INCOND.get() == 1 && lextext == "!" {
set_tok(BANG_TOK);
}
}
false
}
pub fn zshlex_raw_add(c: char) {
if LEX_LEX_ADD_RAW.get() == 0 {
return;
}
LEX_LEXBUF_RAW.with_borrow_mut(|b| b.add(c));
}
pub fn zshlex_raw_back() {
if LEX_LEX_ADD_RAW.get() == 0 {
return;
}
LEX_LEXBUF_RAW.with_borrow_mut(|b| b.pop());
}
pub fn zshlex_raw_mark(offset: i64) -> i64 {
if LEX_LEX_ADD_RAW.get() == 0 {
return 0;
}
(LEX_LEXBUF_RAW.with_borrow(|b| b.buf_len()) as i64) + offset
}
pub fn zshlex_raw_back_to_mark(mark: i64) {
if LEX_LEX_ADD_RAW.get() == 0 {
return;
}
let m = mark.max(0) as usize;
LEX_LEXBUF_RAW.with_borrow_mut(|b| {
if let Some(p) = b.ptr.as_mut() {
p.truncate(m);
}
b.len = m as i32;
});
}
fn skipcomm() -> Result<(), ()> {
let new_lex_add_raw = LEX_LEX_ADD_RAW.get() + 1;
let outer_was_recording = LEX_LEX_ADD_RAW.get() != 0;
cmdpush(CS_CMDSUBST as u8);
add(Inpar);
let new_tokstr_init: Option<String>;
let new_lexbuf_init_ptr: Option<String>;
let new_lexbuf_init_siz: i32;
let new_lexbuf_init_len: i32;
if outer_was_recording {
new_tokstr_init = LEX_TOKSTR_RAW.with_borrow_mut(|t| t.take());
let (p, s, l) = LEX_LEXBUF_RAW.with_borrow_mut(|b| (b.ptr.take(), b.siz, b.len));
new_lexbuf_init_ptr = p;
new_lexbuf_init_siz = s;
new_lexbuf_init_len = l;
} else {
new_tokstr_init = tokstr();
let (p, s, l) = LEX_LEXBUF.with_borrow(|b| (b.ptr.clone(), b.siz, b.len));
new_lexbuf_init_ptr = p;
new_lexbuf_init_siz = s;
new_lexbuf_init_len = l;
}
crate::ported::context::zcontext_save_partial(ZCONTEXT_LEX | ZCONTEXT_PARSE);
hist_in_word(1);
set_tokstr(new_tokstr_init);
LEX_LEXBUF.with_borrow_mut(|b| {
b.ptr = new_lexbuf_init_ptr.clone();
b.siz = if new_lexbuf_init_siz == 0 {
256
} else {
new_lexbuf_init_siz
};
b.len = new_lexbuf_init_len;
});
LEX_TOKSTR_RAW.with_borrow_mut(|t| *t = tokstr());
LEX_LEXBUF_RAW.with_borrow_mut(|b| {
b.ptr = new_lexbuf_init_ptr;
b.siz = if new_lexbuf_init_siz == 0 {
256
} else {
new_lexbuf_init_siz
};
b.len = new_lexbuf_init_len;
});
LEX_LEX_ADD_RAW.set(new_lex_add_raw);
struct SkipcommGuard {
outer_was_recording: bool,
}
impl Drop for SkipcommGuard {
fn drop(&mut self) {
let new_tokstr = LEX_TOKSTR_RAW.with_borrow_mut(|t| t.take());
let (new_lexbuf_ptr, new_lexbuf_siz, new_lexbuf_len) =
LEX_LEXBUF_RAW.with_borrow_mut(|b| (b.ptr.take(), b.siz, b.len));
let new_lexstop = LEX_LEXSTOP.get();
hist_in_word(0);
crate::ported::context::zcontext_restore_partial(ZCONTEXT_LEX | ZCONTEXT_PARSE);
if self.outer_was_recording {
LEX_TOKSTR_RAW.with_borrow_mut(|t| *t = new_tokstr);
LEX_LEXBUF_RAW.with_borrow_mut(|b| {
b.ptr = new_lexbuf_ptr;
b.siz = new_lexbuf_siz;
b.len = new_lexbuf_len;
});
} else {
let mut final_ptr = new_lexbuf_ptr;
let mut final_len = new_lexbuf_len;
if !new_lexstop {
if let Some(ref mut s) = final_ptr {
if s.ends_with(')') {
s.pop();
final_len -= 1;
}
}
}
set_tokstr(final_ptr.clone());
LEX_LEXBUF.with_borrow_mut(|b| {
b.ptr = final_ptr;
b.siz = new_lexbuf_siz;
b.len = final_len;
});
}
cmdpop();
}
}
let _guard = SkipcommGuard {
outer_was_recording,
};
let mut pct = 1;
let mut start = true;
loop {
let c = hgetc();
let c = match c {
Some(c) => c,
None => {
LEX_LEXSTOP.set(true);
return Err(());
}
};
let iswhite = crate::ztype_h::inblank(c as u8);
match c {
'(' => {
pct += 1;
add(c);
}
')' => {
pct -= 1;
if pct == 0 {
return Ok(());
}
add(c);
}
'\\' => {
add(c);
if let Some(c) = hgetc() {
add(c);
}
}
'\'' => {
add(c);
loop {
let ch = hgetc();
match ch {
Some('\'') => {
add('\'');
break;
}
Some(ch) => add(ch),
None => {
LEX_LEXSTOP.set(true);
return Err(());
}
}
}
}
'"' => {
add(c);
loop {
let ch = hgetc();
match ch {
Some('"') => {
add('"');
break;
}
Some('\\') => {
add('\\');
if let Some(ch) = hgetc() {
add(ch);
}
}
Some(ch) => add(ch),
None => {
LEX_LEXSTOP.set(true);
return Err(());
}
}
}
}
'`' => {
add(c);
loop {
let ch = hgetc();
match ch {
Some('`') => {
add('`');
break;
}
Some('\\') => {
add('\\');
if let Some(ch) = hgetc() {
add(ch);
}
}
Some(ch) => add(ch),
None => {
LEX_LEXSTOP.set(true);
return Err(());
}
}
}
}
'#' if start => {
add(c);
loop {
let ch = hgetc();
match ch {
Some('\n') => {
add('\n');
break;
}
Some(ch) => add(ch),
None => break,
}
}
}
_ => {
add(c);
}
}
start = iswhite;
}
}
pub const ztokens: &str = "#$^*(())$=|{}[]`<>>?~`,-!'\"\\\\";
pub static LEXACT1: std::sync::OnceLock<std::sync::Mutex<[u8; 256]>> = std::sync::OnceLock::new();
pub static LEXACT2: std::sync::OnceLock<std::sync::Mutex<[u8; 256]>> = std::sync::OnceLock::new();
pub static LEXTOK2: std::sync::OnceLock<std::sync::Mutex<[u8; 256]>> = std::sync::OnceLock::new();
static LEX_TABS_INITED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
#[inline]
fn ensure_tabs_inited() {
if !LEX_TABS_INITED.load(std::sync::atomic::Ordering::Acquire) {
initlextabs();
LEX_TABS_INITED.store(true, std::sync::atomic::Ordering::Release);
}
}
#[inline]
pub fn lexact1_get(c: char) -> u8 {
let idx = c as u32;
if idx >= 256 {
return LX1_OTHER;
}
ensure_tabs_inited();
let table = LEXACT1.get().unwrap();
table.lock().unwrap()[idx as usize]
}
#[inline]
pub fn lexact2_get(c: char) -> u8 {
let idx = c as u32;
if idx >= 256 {
return LX2_OTHER;
}
ensure_tabs_inited();
let table = LEXACT2.get().unwrap();
table.lock().unwrap()[idx as usize]
}
#[inline]
pub fn lextok2_get(c: char) -> u8 {
let idx = c as u32;
if idx >= 256 {
return c as u8;
}
ensure_tabs_inited();
let table = LEXTOK2.get().unwrap();
table.lock().unwrap()[idx as usize]
}
pub fn toklineno() -> u64 {
LEX_TOKLINENO.get()
}
pub fn set_toklineno(v: u64) {
LEX_TOKLINENO.set(v);
}
pub fn tokfd() -> i32 {
LEX_TOKFD.get()
}
pub fn set_tokfd(v: i32) {
LEX_TOKFD.set(v);
}
pub fn isnewlin() -> i32 {
LEX_ISNEWLIN.get()
}
pub fn set_isnewlin(v: i32) {
LEX_ISNEWLIN.set(v);
}
pub fn inrepeat() -> i32 {
LEX_INREPEAT.get()
}
pub fn set_inrepeat(v: i32) {
LEX_INREPEAT.set(v);
}
pub fn infor() -> i32 {
LEX_INFOR.get()
}
pub fn set_infor(v: i32) {
LEX_INFOR.set(v);
}
pub fn inredir() -> bool {
LEX_INREDIR.get()
}
pub fn set_inredir(v: bool) {
LEX_INREDIR.set(v);
}
pub fn intypeset() -> bool {
LEX_INTYPESET.get()
}
pub fn set_intypeset(v: bool) {
LEX_INTYPESET.set(v);
}
pub fn lineno() -> u64 {
LEX_LINENO.get()
}
pub fn set_lineno(v: u64) {
LEX_LINENO.set(v);
}
pub fn incmdpos() -> bool {
LEX_INCMDPOS.get()
}
pub fn set_incmdpos(v: bool) {
LEX_INCMDPOS.set(v);
}
pub fn nocorrect() -> i32 {
LEX_NOCORRECT.get()
}
pub fn set_nocorrect(v: i32) {
LEX_NOCORRECT.set(v);
}
pub fn noaliases() -> bool {
LEX_NOALIASES.get()
}
pub fn set_noaliases(v: bool) {
LEX_NOALIASES.set(v);
}
pub fn incond() -> i32 {
LEX_INCOND.get()
}
pub fn set_incond(v: i32) {
LEX_INCOND.set(v);
}
pub fn incasepat() -> i32 {
LEX_INCASEPAT.get()
}
pub fn set_incasepat(v: i32) {
LEX_INCASEPAT.set(v);
}
pub fn tokstr() -> Option<String> {
LEX_TOKSTR.with_borrow(|t| t.clone())
}
pub fn set_tokstr(v: Option<String>) {
LEX_TOKSTR.with_borrow_mut(|t| *t = v);
}
pub fn tok() -> lextok {
LEX_TOK.get()
}
pub fn set_tok(v: lextok) {
LEX_TOK.set(v);
}
pub fn pos() -> usize {
LEX_POS.get()
}
pub fn set_pos(v: usize) {
LEX_POS.set(v);
}
pub fn input_slice(start: usize, end: usize) -> Option<String> {
LEX_INPUT.with_borrow(|s| s.get(start..end).map(String::from))
}
pub fn lex_init(input: &str) {
crate::ported::utils::inittyptab();
LEX_UNGET_BUF.with_borrow_mut(|b| b.clear());
LEX_LEXBUF.with_borrow_mut(|b| *b = lexbufstate::new());
LEX_LEXBUF_RAW.with_borrow_mut(|b| *b = lexbufstate::new());
LEX_LEXSTOP.set(false);
LEX_INCONDPAT.set(false);
LEX_OLDPOS.set(true);
LEX_DBPARENS.set(false);
LEX_NOALIASES.set(false);
LEX_NOCORRECT.set(0);
LEX_NOCOMMENTS.set(false);
LEX_LEXFLAGS.set(0);
LEX_ISFIRSTLN.set(true);
LEX_LEX_ADD_RAW.set(0);
LEX_TOKFD.set(-1);
LEX_TOKLINENO.set(1);
LEX_INREDIR.set(false);
LEX_INFOR.set(0);
LEX_INREPEAT.set(0);
LEX_INTYPESET.set(false);
LEX_ISNEWLIN.set(0);
LEX_LINENO.set(1);
LEX_INCMDPOS.set(true);
LEX_INCOND.set(0);
LEX_INCASEPAT.set(0);
LEX_HEREDOCS.with_borrow_mut(|v| v.clear());
LEX_TOKSTR.with_borrow_mut(|t| *t = None);
LEX_TOK.set(ENDINPUT);
LEX_INPUT.with_borrow_mut(|s| {
s.clear();
s.push_str(input);
});
LEX_POS.set(0);
}
pub const CMD_OR_MATH_CMD: i32 = 0;
pub const CMD_OR_MATH_MATH: i32 = 1;
pub const CMD_OR_MATH_ERR: i32 = 2;
#[inline]
pub(crate) fn hgetc() -> Option<char> {
if let Some(c) = LEX_UNGET_BUF.with_borrow_mut(|b| b.pop_front()) {
if c == '\n' {
LEX_LINENO.set(LEX_LINENO.get() + 1);
}
zshlex_raw_add(c);
return Some(c);
}
let pos = LEX_POS.get();
let inbufct = crate::ported::input::inbufct.with(|c| c.get());
let flags = crate::ported::input::inbufflags.with(|f| f.get());
let inp_cont_pending = (flags & crate::ported::zsh_h::INP_CONT) != 0;
let try_inbuf = inbufct > 0 || inp_cont_pending;
let from_inbuf = if try_inbuf {
crate::ported::input::ingetc()
} else {
None
};
let c = if let Some(c) = from_inbuf {
c
} else if let Some(c) = LEX_INPUT.with_borrow(|s| s.get(pos..).and_then(|t| t.chars().next())) {
LEX_POS.set(pos + c.len_utf8());
c
} else {
crate::ported::input::ingetc()?
};
if c == '\n' {
LEX_LINENO.set(LEX_LINENO.get() + 1);
}
zshlex_raw_add(c);
Some(c)
}
fn hungetc(c: char) {
LEX_UNGET_BUF.with_borrow_mut(|b| b.push_front(c));
if c == '\n' {
let cur = LEX_LINENO.get();
if cur > 0 {
LEX_LINENO.set(cur - 1);
}
}
LEX_LEXSTOP.set(false);
zshlex_raw_back();
}
#[allow(dead_code)]
fn peek() -> Option<char> {
if let Some(c) = LEX_UNGET_BUF.with_borrow(|b| b.front().copied()) {
return Some(c);
}
LEX_INPUT.with_borrow(|s| s[LEX_POS.get()..].chars().next())
}
#[inline]
fn hwaddc(c: char) {
crate::ported::hist::ihwaddc(c as i32);
}
fn is_valid_assignment_target(s: &str) -> bool {
let mut chars = s.chars().peekable();
if let Some(&c) = chars.peek() {
if itok(c as u8) {
return false;
}
}
if let Some(&c) = chars.peek() {
if c.is_ascii_digit() {
while let Some(&c) = chars.peek() {
if !c.is_ascii_digit() {
break;
}
chars.next();
}
return chars.peek().is_none();
}
}
let mut has_ident = false;
while let Some(&c) = chars.peek() {
if c == Inbrack || c == '[' {
break;
}
if c == '+' {
chars.next();
return chars.peek().is_none() || chars.peek() == Some(&'=');
}
if !crate::ztype_h::iident(c as u8) && c != Stringg && !itok(c as u8) {
return false;
}
has_ident = true;
chars.next();
}
has_ident
}
pub fn untokenize_preserve_quotes(s: &str) -> String {
let mut result = String::with_capacity(s.len() + 4);
for c in s.chars() {
let cu = c as u32;
if (0x84..=0xa1).contains(&cu) {
match c {
c if c == Pound => result.push('#'),
c if c == Stringg => result.push('$'),
c if c == Hat => result.push('^'),
c if c == Star => result.push('*'),
c if c == Inpar => result.push('('),
c if c == Outpar => result.push(')'),
c if c == Inparmath => result.push('('),
c if c == Outparmath => result.push(')'),
c if c == Qstring => result.push(c),
c if c == Equals => result.push('='),
c if c == Bar => result.push('|'),
c if c == Inbrace => result.push('{'),
c if c == Outbrace => result.push('}'),
c if c == Inbrack => result.push('['),
c if c == Outbrack => result.push(']'),
c if c == Tick => result.push('`'),
c if c == Inang => result.push('<'),
c if c == Outang => result.push('>'),
c if c == OutangProc => result.push('>'),
c if c == Quest => result.push('?'),
c if c == Tilde => result.push('~'),
c if c == Qtick => result.push('`'),
c if c == Comma => result.push(','),
c if c == Dash => result.push('-'),
c if c == Bang => result.push('!'),
c if c == Snull => result.push('\''),
c if c == Dnull => result.push('"'),
c if c == Bnull => result.push('\\'),
_ => {
let idx = c as usize;
if idx < ztokens.len() {
result.push(ztokens.chars().nth(idx).unwrap_or(c));
} else {
result.push(c);
}
}
}
} else {
result.push(c);
}
}
result
}
fn getkeystring_dollar_quote(chars: &[char], start: usize) -> (String, usize) {
let mut out = String::new();
let mut i = start;
while i < chars.len() {
let c = chars[i];
if c == Snull {
return (out, i);
}
if c == Bnull {
i += 1;
if i < chars.len() {
out.push(chars[i]);
i += 1;
}
continue;
}
if c == '\\' && i + 1 < chars.len() {
let nc = chars[i + 1];
match nc {
'a' => {
out.push('\x07');
i += 2;
}
'b' => {
out.push('\x08');
i += 2;
}
'e' | 'E' => {
out.push('\x1b');
i += 2;
}
'f' => {
out.push('\x0c');
i += 2;
}
'n' => {
out.push('\n');
i += 2;
}
'r' => {
out.push('\r');
i += 2;
}
't' => {
out.push('\t');
i += 2;
}
'v' => {
out.push('\x0b');
i += 2;
}
'\\' | '\'' | '"' => {
out.push(nc);
i += 2;
}
'x' => {
let mut val: u32 = 0;
let mut consumed = 2; let mut got = 0;
while got < 2 && i + consumed < chars.len() {
let h = chars[i + consumed];
if let Some(d) = h.to_digit(16) {
val = val * 16 + d;
consumed += 1;
got += 1;
} else {
break;
}
}
if got == 0 {
out.push('\\');
out.push('x');
} else if let Some(ch) = char::from_u32(val) {
out.push(ch);
}
i += consumed;
}
'u' | 'U' => {
let n = if nc == 'u' { 4 } else { 8 };
let mut val: u32 = 0;
let mut consumed = 2; let mut got = 0;
while got < n && i + consumed < chars.len() {
let h = chars[i + consumed];
if let Some(d) = h.to_digit(16) {
val = val * 16 + d;
consumed += 1;
got += 1;
} else {
break;
}
}
if let Some(ch) = char::from_u32(val) {
out.push(ch);
}
i += consumed;
}
'0'..='7' => {
let mut val: u32 = 0;
let mut consumed = 1; let mut got = 0;
while got < 3 && i + consumed < chars.len() {
let h = chars[i + consumed];
if let Some(d) = h.to_digit(8) {
val = val * 8 + d;
consumed += 1;
got += 1;
} else {
break;
}
}
if let Some(ch) = char::from_u32(val) {
out.push(ch);
}
i += consumed;
}
_ => {
out.push('\\');
out.push(nc);
i += 2;
}
}
continue;
}
out.push(c);
i += 1;
}
(out, i)
}
pub fn untokenize(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let chars: Vec<char> = s.chars().collect();
let mut i = 0;
while i < chars.len() {
let c = chars[i];
let cu = c as u32;
if (0x84..=0xa1).contains(&cu) {
if (c == Qstring || c == Stringg) && i + 1 < chars.len() && chars[i + 1] == Snull {
let (decoded, end) = getkeystring_dollar_quote(&chars, i + 2);
result.push_str(&decoded);
i = if end < chars.len() { end + 1 } else { end };
continue;
}
match c {
c if c == Pound => result.push('#'),
c if c == Stringg => result.push('$'),
c if c == Hat => result.push('^'),
c if c == Star => result.push('*'),
c if c == Inpar => result.push('('),
c if c == Outpar => result.push(')'),
c if c == Inparmath => result.push('('),
c if c == Outparmath => result.push(')'),
c if c == Qstring => result.push('$'),
c if c == Equals => result.push('='),
c if c == Bar => result.push('|'),
c if c == Inbrace => result.push('{'),
c if c == Outbrace => result.push('}'),
c if c == Inbrack => result.push('['),
c if c == Outbrack => result.push(']'),
c if c == Tick => result.push('`'),
c if c == Inang => result.push('<'),
c if c == Outang => result.push('>'),
c if c == OutangProc => result.push('>'),
c if c == Quest => result.push('?'),
c if c == Tilde => result.push('~'),
c if c == Qtick => result.push('`'),
c if c == Comma => result.push(','),
c if c == Dash => result.push('-'),
c if c == Bang => result.push('!'),
c if c == Snull || c == Dnull || c == Bnull => {
}
c if c == Nularg => {
}
_ => {
let idx = c as usize;
if idx < ztokens.len() {
result.push(ztokens.chars().nth(idx).unwrap_or(c));
} else {
result.push(c);
}
}
}
} else {
result.push(c);
}
i += 1;
}
result
}
pub fn has_token(s: &str) -> bool {
s.bytes().any(itok)
}
#[cfg(test)]
mod tokens_tests {
use crate::ported::hashtable::reswdtab_lock;
use crate::ported::zsh_h::{
Bnull, Dnull, Snull, DINANG, IF, IS_REDIROP, OUTANG_TOK, STRING_LEX, THEN,
};
#[test]
fn test_token_values() {
let _g = crate::test_util::global_state_lock();
assert_eq!(Snull as u32, 0x9d);
assert_eq!(Dnull as u32, 0x9e);
assert_eq!(Bnull as u32, 0x9f);
}
#[test]
fn test_reserved_words() {
let _g = crate::test_util::global_state_lock();
let tab = reswdtab_lock().read().unwrap();
assert_eq!(tab.get("if").map(|r| r.token), Some(IF));
assert_eq!(tab.get("then").map(|r| r.token), Some(THEN));
assert!(tab.get("notakeyword").is_none());
}
#[test]
fn test_redirop() {
let _g = crate::test_util::global_state_lock();
assert!(IS_REDIROP(OUTANG_TOK));
assert!(IS_REDIROP(DINANG));
assert!(!IS_REDIROP(IF));
assert!(!IS_REDIROP(STRING_LEX));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_command() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("echo hello");
zshlex();
assert_eq!(tok(), STRING_LEX);
assert_eq!(tokstr(), Some("echo".to_string()));
zshlex();
assert_eq!(tok(), STRING_LEX);
assert_eq!(tokstr(), Some("hello".to_string()));
zshlex();
assert_eq!(tok(), ENDINPUT);
}
#[test]
fn test_pipeline() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("ls | grep foo");
zshlex();
assert_eq!(tok(), STRING_LEX);
zshlex();
assert_eq!(tok(), BAR_TOK);
zshlex();
assert_eq!(tok(), STRING_LEX);
zshlex();
assert_eq!(tok(), STRING_LEX);
}
#[test]
fn test_redirections() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("echo > file");
zshlex();
assert_eq!(tok(), STRING_LEX);
zshlex();
assert_eq!(tok(), OUTANG_TOK);
zshlex();
assert_eq!(tok(), STRING_LEX);
}
#[test]
fn test_heredoc() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("cat << EOF");
zshlex();
assert_eq!(tok(), STRING_LEX);
zshlex();
assert_eq!(tok(), DINANG);
zshlex();
assert_eq!(tok(), STRING_LEX);
}
#[test]
fn test_single_quotes() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("echo 'hello world'");
zshlex();
assert_eq!(tok(), STRING_LEX);
zshlex();
assert_eq!(tok(), STRING_LEX);
assert!(tokstr().is_some());
}
#[test]
fn test_function_tokens() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("function foo { }");
zshlex();
assert_eq!(tok(), FUNC, "expected Func, got {:?}", tok());
zshlex();
assert_eq!(
tok(),
STRING_LEX,
"expected String for 'foo', got {:?}",
tok()
);
assert_eq!(tokstr(), Some("foo".to_string()));
set_incmdpos(true);
zshlex();
assert_eq!(
tok(),
INBRACE_TOK,
"expected Inbrace, got {:?} tokstr={:?}",
tok(),
tokstr()
);
zshlex();
assert_eq!(
tok(),
OUTBRACE_TOK,
"expected Outbrace, got {:?} tokstr={:?} incmdpos={}",
tok(),
tokstr(),
incmdpos()
);
}
#[test]
fn test_double_quotes() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("echo \"hello $name\"");
zshlex();
assert_eq!(tok(), STRING_LEX);
zshlex();
assert_eq!(tok(), STRING_LEX);
assert!(tokstr().is_some());
}
#[test]
fn test_command_substitution() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("echo $(pwd)");
zshlex();
assert_eq!(tok(), STRING_LEX);
zshlex();
assert_eq!(tok(), STRING_LEX);
}
#[test]
fn test_env_assignment() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("FOO=bar echo");
set_incmdpos(true);
zshlex();
assert_eq!(tok(), ENVSTRING, "tok={:?} tokstr={:?}", tok(), tokstr());
zshlex();
assert_eq!(tok(), STRING_LEX);
}
#[test]
fn test_array_assignment() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("arr=(a b c)");
set_incmdpos(true);
zshlex();
assert_eq!(tok(), ENVARRAY);
}
#[test]
fn test_process_substitution() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("diff <(ls) >(cat)");
zshlex();
assert_eq!(tok(), STRING_LEX);
zshlex();
assert_eq!(tok(), STRING_LEX);
zshlex();
assert_eq!(tok(), STRING_LEX);
}
#[test]
fn test_arithmetic() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("echo $((1+2))");
zshlex();
assert_eq!(tok(), STRING_LEX);
zshlex();
assert_eq!(tok(), STRING_LEX);
}
#[test]
fn test_semicolon_variants() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("case x in a) cmd;; b) cmd;& c) cmd;| esac");
loop {
zshlex();
if tok() == DSEMI || tok() == ENDINPUT {
break;
}
}
assert_eq!(tok(), DSEMI);
loop {
zshlex();
if tok() == SEMIAMP || tok() == ENDINPUT {
break;
}
}
assert_eq!(tok(), SEMIAMP);
loop {
zshlex();
if tok() == SEMIBAR || tok() == ENDINPUT {
break;
}
}
assert_eq!(tok(), SEMIBAR);
}
#[test]
fn untokenize_passes_plain_string_through() {
let _g = crate::test_util::global_state_lock();
assert_eq!(untokenize("hello"), "hello");
assert_eq!(untokenize(""), "");
assert_eq!(untokenize("a/b/c"), "a/b/c");
}
#[test]
fn untokenize_strips_marker_sentinels() {
let _g = crate::test_util::global_state_lock();
let with_pound = format!("a{}b", Pound);
let cleaned = untokenize(&with_pound);
assert!(
!cleaned.contains(Pound),
"Pound (\\u{{84}}) sentinel must be replaced (got {cleaned:?})"
);
let with_marker = format!("x{}y", Marker);
let cleaned = untokenize(&with_marker);
assert!(
cleaned.contains(Marker),
"Marker (\\u{{a2}}) is NOT ITOK; must pass through untokenize verbatim"
);
}
#[test]
fn untokenize_range_matches_c_itok_endpoints() {
let _g = crate::test_util::global_state_lock();
let with_meta = format!("a{}b", '\u{83}');
let cleaned = untokenize(&with_meta);
assert!(
cleaned.contains('\u{83}'),
"c:4197 — META (\\u{{83}}) is IMETA-only, never ITOK"
);
let with_nularg = format!("a{}b", Nularg);
let cleaned = untokenize(&with_nularg);
assert!(
!cleaned.contains(Nularg),
"c:2089 — Nularg (\\u{{a1}}) must be DROPPED by untokenize"
);
}
#[test]
fn untokenize_preserve_quotes_plain_input_unchanged() {
let _g = crate::test_util::global_state_lock();
assert_eq!(untokenize_preserve_quotes("foo"), "foo");
assert_eq!(untokenize_preserve_quotes(""), "");
}
#[test]
fn toklineno_set_then_get_round_trips() {
let _g = crate::test_util::global_state_lock();
let saved = toklineno();
set_toklineno(12345);
assert_eq!(toklineno(), 12345);
set_toklineno(saved);
}
#[test]
fn tokfd_set_then_get_round_trips() {
let _g = crate::test_util::global_state_lock();
let saved = tokfd();
set_tokfd(7);
assert_eq!(tokfd(), 7);
set_tokfd(saved);
}
#[test]
fn lexact1_get_handles_high_chars_without_panic() {
let _g = crate::test_util::global_state_lock();
let _ = lexact1_get('a');
let _ = lexact1_get('\0');
let _ = lexact1_get('\u{ffff}');
}
#[test]
fn isnumglob_recognises_numeric_range_pattern() {
let _g = crate::test_util::global_state_lock();
lex_init("1-10>");
assert!(isnumglob(), "<1-10> shape recognised");
lex_init("0-100>");
assert!(isnumglob());
lex_init("9-9>");
assert!(isnumglob(), "single-value range");
}
#[test]
fn isnumglob_rejects_malformed_shapes() {
let _g = crate::test_util::global_state_lock();
lex_init("1-10");
assert!(!isnumglob(), "missing closing > → not numglob");
lex_init("1-");
assert!(!isnumglob(), "no closing");
lex_init("abc>");
assert!(!isnumglob(), "non-digit content");
lex_init(">");
assert!(!isnumglob(), "bare close");
lex_init("");
assert!(!isnumglob(), "empty input");
}
#[test]
fn isnumglob_rewinds_stream_on_match_and_non_match() {
let _g = crate::test_util::global_state_lock();
lex_init("1-10>tail");
assert!(isnumglob());
assert_eq!(hgetc(), Some('1'), "c:606-607 — rewind on match");
lex_init("abc>tail");
assert!(!isnumglob());
assert_eq!(hgetc(), Some('a'), "c:606-607 — rewind on non-match");
}
#[test]
fn isnumglob_accepts_empty_digit_runs_per_c_pattern() {
let _g = crate::test_util::global_state_lock();
lex_init("->");
assert!(
isnumglob(),
"c:577 — `<->` is the minimum valid numglob (both runs empty)"
);
lex_init("-10>");
assert!(isnumglob(), "c:577 — left run can be empty");
lex_init("1->");
assert!(isnumglob(), "c:577 — right run can be empty");
}
#[test]
fn isnumglob_rejects_second_dash_after_first() {
let _g = crate::test_util::global_state_lock();
lex_init("1-2-3>");
assert!(
!isnumglob(),
"c:597-602 — second `-` breaks the state machine"
);
lex_init("1--2>");
assert!(!isnumglob(), "c:597-602 — `--` not valid in numglob");
}
#[test]
fn parse_subst_string_handles_nulstring_sentinel() {
let _g = crate::test_util::global_state_lock();
errflag.store(0, Ordering::Relaxed);
assert!(parse_subst_string("").is_ok(), "c:1802 — empty input → Ok");
let nul = Nularg.to_string();
assert!(
parse_subst_string(&nul).is_ok(),
"c:1802 — nulstring sentinel → Ok"
);
}
#[test]
fn parse_subst_string_restores_errflag_after_parse() {
let _g = crate::test_util::global_state_lock();
errflag.store(0, Ordering::Relaxed);
let _ = parse_subst_string("foo");
assert_eq!(
errflag.load(Ordering::Relaxed) & ERRFLAG_ERROR,
0,
"c:1819 — parse-time errflag must NOT leak; clean input keeps errflag clear"
);
}
fn collect_tokens() -> Vec<lextok> {
let mut toks = Vec::new();
loop {
zshlex();
let t = tok();
if t == ENDINPUT {
break;
}
toks.push(t);
if toks.len() > 200 {
panic!("token stream too long — possible lex loop");
}
}
toks
}
#[test]
fn lex_double_ampersand_is_damper() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("a && b");
let toks = collect_tokens();
assert_eq!(toks.len(), 3, "got {toks:?}");
assert_eq!(toks[0], STRING_LEX);
assert_eq!(toks[1], DAMPER);
assert_eq!(toks[2], STRING_LEX);
}
#[test]
fn lex_double_pipe_is_dbar() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("a || b");
let toks = collect_tokens();
assert_eq!(toks.len(), 3);
assert_eq!(toks[1], DBAR);
}
#[test]
fn lex_semicolon_is_separator_token() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("a ; b");
let toks = collect_tokens();
assert!(!toks.is_empty(), "lex must produce at least one token");
assert!(
toks.iter().any(|t| matches!(*t, SEMI | SEPER | NEWLIN)),
"must contain a separator-class token; got {toks:?}"
);
}
#[test]
fn lex_single_ampersand_is_amper() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("a &");
let toks = collect_tokens();
assert_eq!(toks.len(), 2);
assert_eq!(toks[1], AMPER);
}
#[test]
fn lex_gt_is_outang_tok() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("echo > /tmp/x");
let toks = collect_tokens();
assert!(toks.contains(&OUTANG_TOK), "got toks={toks:?}");
}
#[test]
fn lex_double_gt_is_doutang() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("echo >> /tmp/x");
let toks = collect_tokens();
assert!(toks.contains(&DOUTANG), "got toks={toks:?}");
}
#[test]
fn lex_lt_is_inang_tok() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("cat < /tmp/x");
let toks = collect_tokens();
assert!(toks.contains(&INANG_TOK), "got toks={toks:?}");
}
#[test]
fn lex_double_lt_is_dhereshut_or_dinang() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("cat << EOF");
let toks = collect_tokens();
assert!(toks.len() >= 2, "got toks={toks:?}");
let t = toks[1];
assert!(
t == DINANG || IS_REDIROP(t),
"second token must be a redir kind for `<<`; got {t:?}"
);
}
#[test]
fn lex_triple_lt_is_tringang() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("cat <<< \"hi\"");
let toks = collect_tokens();
assert!(toks.contains(&TRINANG), "got toks={toks:?}");
}
#[test]
fn lex_if_then_fi_recognized_as_reserved_words() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("if true; then echo; fi");
let toks = collect_tokens();
assert!(toks.contains(&IF), "missing IF; got {toks:?}");
assert!(toks.contains(&THEN), "missing THEN; got {toks:?}");
assert!(toks.contains(&FI), "missing FI; got {toks:?}");
}
#[test]
fn lex_while_do_done_recognized() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("while x; do y; done");
let toks = collect_tokens();
assert!(toks.contains(&WHILE), "got {toks:?}");
assert!(toks.contains(&DOLOOP), "got {toks:?}");
assert!(toks.contains(&DONE), "got {toks:?}");
}
#[test]
fn lex_for_in_recognized() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("for i in 1 2 3; do echo $i; done");
let toks = collect_tokens();
assert!(toks.contains(&FOR), "got {toks:?}");
assert!(toks.contains(&DOLOOP), "got {toks:?}");
assert!(toks.contains(&DONE), "got {toks:?}");
}
#[test]
fn lex_case_esac_recognized() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("case x in y) echo;; esac");
let toks = collect_tokens();
assert!(toks.contains(&CASE), "got {toks:?}");
assert!(toks.contains(&ESAC), "got {toks:?}");
}
#[test]
fn lex_parens_subshell_tokens() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("(echo)");
let toks = collect_tokens();
assert!(
toks.contains(&INPAR_TOK) || toks.contains(&OUTPAR_TOK),
"expected paren tokens, got {toks:?}"
);
}
#[test]
fn lex_curly_braces_inbrace_outbrace() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("{ echo; }");
let toks = collect_tokens();
assert!(
toks.contains(&INBRACE_TOK) || toks.contains(&OUTBRACE_TOK),
"expected brace tokens, got {toks:?}"
);
}
#[test]
fn lex_simple_word_captures_tokstr() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("hello");
zshlex();
assert_eq!(tok(), STRING_LEX);
assert_eq!(tokstr(), Some("hello".to_string()));
}
#[test]
fn lex_numeric_word_is_still_string_lex() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("42");
zshlex();
assert_eq!(tok(), STRING_LEX);
assert_eq!(tokstr(), Some("42".to_string()));
}
#[test]
fn lex_multiple_spaces_are_skipped() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("a b");
let toks = collect_tokens();
assert_eq!(toks, vec![STRING_LEX, STRING_LEX]);
}
#[test]
fn lex_empty_input_only_endinput() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("");
zshlex();
assert_eq!(tok(), ENDINPUT);
}
#[test]
fn lex_corpus_single_pipe_is_bar() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("a | b");
let toks = collect_tokens();
assert!(toks.contains(&BAR_TOK), "expected BAR_TOK in {toks:?}");
}
#[test]
fn lex_corpus_pipe_amp_is_bar_amp() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("a |& b");
let toks = collect_tokens();
assert!(toks.contains(&BARAMP), "expected BARAMP in {toks:?}");
}
#[test]
fn lex_corpus_subshell_parens() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("(a)");
let toks = collect_tokens();
assert!(toks.contains(&INPAR_TOK), "expected INPAR_TOK in {toks:?}");
assert!(toks.contains(&OUTPAR_TOK), "expected OUTPAR_TOK in {toks:?}");
}
#[test]
fn lex_corpus_backtick_is_string_token() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("`echo a`");
zshlex();
assert_eq!(tok(), STRING_LEX, "backtick command-sub lexes as STRING_LEX");
}
#[test]
fn lex_corpus_single_quoted_is_one_token() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("'hello world'");
let toks = collect_tokens();
assert_eq!(toks.len(), 1, "single-quoted = 1 token, got {toks:?}");
assert_eq!(toks[0], STRING_LEX);
}
#[test]
fn lex_corpus_double_quoted_is_one_token() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("\"hello world\"");
let toks = collect_tokens();
assert_eq!(toks.len(), 1, "double-quoted = 1 token, got {toks:?}");
assert_eq!(toks[0], STRING_LEX);
}
#[test]
fn lex_corpus_redirect_fd_dup() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("cmd 2>&1");
let toks = collect_tokens();
let has_redir = toks.iter().any(|t| matches!(*t,
OUTANG_TOK | DOUTANG | INANG_TOK | OUTANGAMP | INANGAMP
| DOUTANGAMP | OUTANGAMPBANG | DOUTANGAMPBANG
));
assert!(has_redir, "expected a redirect token in {toks:?}");
}
#[test]
#[ignore = "ZSHRS BUG: # comment handling may differ from zsh in lexer test harness"]
fn lex_corpus_hash_comment_dropped() {
let _g = crate::test_util::global_state_lock();
let _ = lex_init("cmd # comment");
let toks = collect_tokens();
assert_eq!(toks, vec![STRING_LEX], "comment dropped");
}
}