use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use crate::ported::prompt::{cmdpop, cmdpush};
use crate::ported::zsh_h::{
isset, 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, HISTALLOWCLOBBER,
IGNOREBRACES, IGNORECLOSEBRACES, INTERACTIVECOMMENTS, KSHGLOB, META, POSIXALIASES, RCQUOTES,
SHGLOB, SHINSTDIN, SHORTLOOPS, SHORTREPEAT,
};
use crate::zsh_h::lex_stack;
use crate::ztype_h::itok;
pub const ztokens: &str = "#$^*(())$=|{}[]`<>>?~`,-!'\"\\\\";
pub use super::zsh_h::{
lextok, 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,
};
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 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 initlextabs() {
use crate::ported::zsh_h::{Hat, Inbrace, Inbrack, Pound, Quest, Star, Stringg, Tilde, META};
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;
}
#[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() {
assert_eq!(Snull as u32, 0x9d);
assert_eq!(Dnull as u32, 0x9e);
assert_eq!(Bnull as u32, 0x9f);
}
#[test]
fn test_reserved_words() {
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() {
assert!(IS_REDIROP(OUTANG_TOK));
assert!(IS_REDIROP(DINANG));
assert!(!IS_REDIROP(IF));
assert!(!IS_REDIROP(STRING_LEX));
}
}
pub use super::zsh_h::{
LEXFLAGS_ACTIVE, LEXFLAGS_COMMENTS, LEXFLAGS_COMMENTS_KEEP, LEXFLAGS_COMMENTS_STRIP,
LEXFLAGS_NEWLINE, LEXFLAGS_ZLE,
};
use crate::ported::zsh_h::lexbufstate;
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 use crate::heredoc_ast::HereDoc;
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_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 }
)};
}
pub fn error() -> Option<String> {
None
}
pub fn set_error(_v: Option<String>) {}
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 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 heredocs_take() -> Vec<HereDoc> {
LEX_HEREDOCS.with_borrow_mut(|v| std::mem::take(v))
}
pub fn heredocs_set(v: Vec<HereDoc>) {
LEX_HEREDOCS.with_borrow_mut(|c| *c = v);
}
pub fn heredocs_clear() {
LEX_HEREDOCS.with_borrow_mut(|v| v.clear());
}
pub fn heredocs_is_empty() -> bool {
LEX_HEREDOCS.with_borrow(|v| v.is_empty())
}
pub fn heredocs_len() -> usize {
LEX_HEREDOCS.with_borrow(|v| v.len())
}
pub fn heredocs_clone() -> Vec<HereDoc> {
LEX_HEREDOCS.with_borrow(|v| v.clone())
}
pub fn heredocs_push(h: HereDoc) {
LEX_HEREDOCS.with_borrow_mut(|v| v.push(h));
}
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 tokstr_take() -> Option<String> {
LEX_TOKSTR.with_borrow_mut(|t| t.take())
}
pub fn tokstr_is_some() -> bool {
LEX_TOKSTR.with_borrow(|t| t.is_some())
}
pub fn tokstr_is_none() -> bool {
LEX_TOKSTR.with_borrow(|t| t.is_none())
}
pub fn tokstr_eq(s: &str) -> bool {
LEX_TOKSTR.with_borrow(|t| t.as_deref() == Some(s))
}
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 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 exalias() -> bool {
let inbufflags_alias =
(crate::ported::input::inbufflags.with(|f| f.get()) & crate::ported::zsh_h::INP_ALIAS) != 0;
let strin_set = crate::ported::input::strin.with(|c| c.get()) != 0;
if crate::ported::zsh_h::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()))
{
let candidates: Vec<String> = {
let mut v: Vec<String> = Vec::new();
if let Ok(t) = crate::ported::hashtable::shfunctab_lock().read() {
v.extend(t.iter().map(|(k, _)| k.clone()));
}
if let Ok(t) = crate::ported::hashtable::aliastab_lock().read() {
v.extend(t.iter().map(|(k, _)| k.clone()));
}
if let Ok(t) = crate::ported::hashtable::cmdnamtab_lock().read() {
v.extend(t.iter().map(|(k, _)| k.clone()));
}
v
};
let refs: Vec<&str> = candidates.iter().map(|s| s.as_str()).collect();
if let Some(word) = tokstr() {
let word_untok = if has_token(&word) {
untokenize(&word)
} else {
word.clone()
};
if let Some(corrected) = crate::ported::utils::spckword(&word_untok, &refs, 3) {
if corrected != word_untok {
if isset(CORRECTALL) {
set_tokstr(Some(corrected));
}
}
}
}
}
if tokstr_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);
if LEX_INCMDPOS.get() || is_close_brace_special {
let rw_tok: Option<lextok> = {
let guard = crate::ported::hashtable::reswdtab_lock()
.read()
.expect("reswdtab poisoned");
guard.get(&lextext).map(|r| r.token)
};
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
}
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 = crate::ported::hashtable::reswdtab_lock()
.read()
.expect("reswdtab poisoned")
.get(lextext)
.is_some();
if is_reswd {
return false;
}
}
let alias_clone: Option<crate::ported::zsh_h::alias> = {
let guard = crate::ported::hashtable::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)) {
if !LEX_LEXSTOP.get() {
if let Some(c) = peek() {
if !crate::ztype_h::iblank(c as u8) {
crate::ported::input::inpush(" ", crate::ported::zsh_h::INP_ALIAS, None);
}
}
}
crate::ported::input::inpush(
&alias.text,
crate::ported::zsh_h::INP_ALIAS,
Some(lextext.to_string()),
);
let mut guard = crate::ported::hashtable::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<crate::ported::zsh_h::alias> = {
let guard = crate::ported::hashtable::sufaliastab_lock()
.read()
.expect("sufaliastab poisoned");
guard.get(suffix).cloned()
};
if let Some(alias) = alias_clone {
if alias.inuse == 0 {
crate::ported::input::inpush(
lextext,
crate::ported::zsh_h::INP_ALIAS,
Some(suffix.to_string()),
);
crate::ported::input::inpush(" ", crate::ported::zsh_h::INP_ALIAS, None);
crate::ported::input::inpush(
&alias.text,
crate::ported::zsh_h::INP_ALIAS,
None,
);
let mut guard = crate::ported::hashtable::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 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;
});
}
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 = tokstr_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 lexinit() {
LEX_NOCORRECT.set(0);
LEX_DBPARENS.set(false);
LEX_LEXSTOP.set(false);
set_tok(ENDINPUT);
}
#[inline]
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 c = LEX_INPUT.with_borrow(|s| s[LEX_POS.get()..].chars().next())?;
LEX_POS.set(LEX_POS.get() + c.len_utf8());
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' && LEX_LINENO.get() > 1 {
LEX_LINENO.set(LEX_LINENO.get() - 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())
}
fn add(c: char) {
LEX_LEXBUF.with_borrow_mut(|b| b.add(c));
}
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 {
process_heredocs();
}
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);
}
}
fn process_heredocs() {
let n = LEX_HEREDOCS.with_borrow(|v| v.len());
for i in 0..n {
let (skip, strip_tabs, terminator) = LEX_HEREDOCS.with_borrow(|v| {
if v[i].processed || v[i].terminator.is_empty() {
(true, false, String::new())
} else {
(false, v[i].strip_tabs, v[i].terminator.clone())
}
});
if skip {
continue;
}
cmdpush(if strip_tabs {
CS_HEREDOCD as u8
} else {
CS_HEREDOC as u8
});
let mut content = String::new();
loop {
let line = read_line();
if line.is_none() {
crate::ported::utils::zerr("here document too large");
set_tok(LEXERR);
cmdpop();
return;
}
let line = line.unwrap();
let check_line = if strip_tabs {
line.trim_start_matches('\t')
} else {
line.as_str()
};
if check_line.trim_end_matches('\n') == terminator {
break;
}
if strip_tabs {
content.push_str(check_line);
} else {
content.push_str(&line);
}
}
cmdpop();
LEX_HEREDOCS.with_borrow_mut(|v| {
v[i].content = content;
v[i].processed = true;
});
}
}
fn read_line() -> Option<String> {
let mut line = String::new();
loop {
match hgetc() {
Some(c) => {
line.push(c);
if c == '\n' {
break;
}
}
None => {
if line.is_empty() {
return None;
}
break;
}
}
}
Some(line)
}
#[allow(non_upper_case_globals)]
const hashchar: char = '#';
#[allow(non_upper_case_globals)]
const bangchar: char = '!';
#[allow(non_upper_case_globals)]
#[allow(dead_code)]
const hatchar: char = '^';
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 crate::ported::utils::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 == bangchar;
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()) & crate::ported::zsh_h::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)
&& (!crate::ported::zsh_h::interact() || unset(SHINSTDIN));
if c == hashchar
&& !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);
}
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),
}
}
#[inline]
fn hwaddc(c: char) {
crate::ported::hist::ihwaddc(c as i32);
}
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(c);
add(Inbrace);
bct += 1;
cmdpush(CS_BRACEPAR as u8);
if in_brace_param == 0 {
in_brace_param = bct;
}
}
Some('\'') => {
add(Qstring);
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(Qstring);
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(c);
} 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;
}
if in_brace_param > 0 || sub || LEX_INCONDPAT.get() || LEX_INCASEPAT.get() > 0 {
add(c);
} else if {
let lookahead = LEX_INPUT.with_borrow(|s| {
s[LEX_POS.get()..].to_string()
});
isnumglob(&lookahead, 0)
} {
add(c);
while let Some(ch) = hgetc() {
add(ch);
if ch == '>' {
break;
}
}
} else {
let e = hgetc();
if e != Some('(') {
if let Some(e) = e {
hungetc(e);
}
LEX_LEXSTOP.set(false);
break;
}
add(Inang);
if skipcomm().is_err() {
peek = LEXERR;
break;
}
add(Outpar);
}
}
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;
}
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 {
crate::ported::utils::zerr(&format!("unmatched {}", unmatched));
}
if in_brace_param > 0 {
crate::ported::utils::zerr("closing brace expected");
}
set_tokstr(Some(LEX_LEXBUF.with_borrow(|b| b.as_str().to_string())));
peek
}
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
}
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);
}
}
}
}
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(')') {
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
};
}
}
fn skipcomm() -> Result<(), ()> {
use crate::ported::zsh_h::{ZCONTEXT_LEX, ZCONTEXT_PARSE};
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);
crate::ported::hist::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();
crate::ported::hist::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 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 fn gotword() {
use std::sync::atomic::Ordering;
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 {
crate::ported::zle::compcore::WB.store(nwb, Ordering::SeqCst);
crate::ported::zle::compcore::WE.store(nwe, Ordering::SeqCst);
} else {
let wb_new = zlemetacs + addedx;
crate::ported::zle::compcore::WB.store(wb_new, Ordering::SeqCst);
let we_cur = crate::ported::zle::compcore::WE.load(Ordering::SeqCst);
if we_cur < wb_new {
crate::ported::zle::compcore::WE.store(wb_new, Ordering::SeqCst);
}
}
LEX_LEXFLAGS.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;
pub fn isnumglob(input: &str, pos: usize) -> bool {
let chars: Vec<char> = input[pos..].chars().collect();
let mut i = 0;
let mut expect_close = false;
while i < chars.len() {
let c = chars[i];
if c.is_ascii_digit() {
i += 1;
} else if c == '-' && !expect_close {
expect_close = true;
i += 1;
} else if c == '>' && expect_close {
return true;
} else {
break;
}
}
false
}
pub fn parsestrnoerr(s: &str) -> Result<String, String> {
let untok = untokenize(s); let dup = crate::ported::string::dupstring_wlen(&untok, untok.len()); crate::ported::context::zcontext_save();
crate::ported::input::inpush(&dup, 0, None);
crate::ported::hist::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());
crate::ported::hist::strinend();
crate::ported::input::inpop();
crate::ported::context::zcontext_restore();
if parse_err {
Err("parse error".to_string())
} else {
Ok(result)
}
}
pub fn parsestr(s: &str) -> Result<String, String> {
parsestrnoerr(s)
}
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 = crate::ported::string::dupstring_wlen(&untok, untok.len());
crate::ported::context::zcontext_save();
crate::ported::input::inpush(&dup, 0, None);
crate::ported::hist::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;
crate::ported::hist::strinend();
crate::ported::input::inpop();
crate::ported::context::zcontext_restore();
if parse_err {
return None;
}
Some(toklen)
}
pub fn parse_subst_string(s: &str) -> Result<String, String> {
if s.is_empty() {
return Ok(String::new());
}
let l = s.len();
let untok = untokenize(s); let dup = crate::ported::string::dupstring_wlen(&untok, untok.len());
crate::ported::context::zcontext_save();
crate::ported::input::inpush(&dup, 0, None);
crate::ported::hist::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,
};
use std::sync::atomic::Ordering;
let saw_err = crate::ported::utils::errflag.load(Ordering::Relaxed) != 0;
let result = LEX_LEXBUF.with_borrow(|b| b.as_str().to_string());
crate::ported::hist::strinend();
crate::ported::input::inpop();
crate::ported::context::zcontext_restore();
if ctok == LEXERR || saw_err {
return Err("parse error".to_string());
}
Ok(result)
}
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 (0x83..=0x9f).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 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 (0x83..=0x9f).contains(&cu) {
if c == Qstring && 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 => {
}
_ => {
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.chars().any(|c| {
let cu = c as u32;
(0x83..=0x9f).contains(&cu)
})
}
pub fn tokens_to_printable(s: &str) -> String {
untokenize(s)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_command() {
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 _ = 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 _ = 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 _ = 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 _ = 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 _ = 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 _ = 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 _ = lex_init("echo $(pwd)");
zshlex();
assert_eq!(tok(), STRING_LEX);
zshlex();
assert_eq!(tok(), STRING_LEX);
}
#[test]
fn test_env_assignment() {
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 _ = lex_init("arr=(a b c)");
set_incmdpos(true);
zshlex();
assert_eq!(tok(), ENVARRAY);
}
#[test]
fn test_process_substitution() {
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 _ = lex_init("echo $((1+2))");
zshlex();
assert_eq!(tok(), STRING_LEX);
zshlex();
assert_eq!(tok(), STRING_LEX);
}
#[test]
fn test_semicolon_variants() {
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);
}
}