use std::cell::RefCell;
use std::collections::VecDeque;
use std::io::{self, BufRead, BufReader, Read};
use std::io::Write;
const SHIN_BUF_SIZE: usize = 8192;
#[allow(dead_code)]
const INSTACK_INITIAL: usize = 4;
use crate::ported::zsh_h::{
INP_ALCONT, INP_ALIAS, INP_APPEND, INP_CONT, INP_FREE, INP_HIST,
INP_HISTCONT, INP_LINENO, INP_RAW_KEEP,
};
#[derive(Clone, Default)]
#[allow(non_camel_case_types)]
struct instacks { buf: String, bufpos: usize, flags: i32, alias: Option<String>, }
thread_local! {
#[allow(non_upper_case_globals)]
pub static SHIN: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
#[allow(non_upper_case_globals)]
pub static strin: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
#[allow(non_upper_case_globals)]
pub static inbufct: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
#[allow(non_upper_case_globals)]
pub static inbufflags: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
#[allow(non_upper_case_globals)]
static inbuf: RefCell<String> = const { RefCell::new(String::new()) };
#[allow(non_upper_case_globals)]
static inbufpos: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
static instack: RefCell<Vec<instacks>> = const { RefCell::new(Vec::new()) };
#[allow(non_upper_case_globals)]
pub static lexstop: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
#[allow(non_upper_case_globals)]
pub static lineno: std::cell::Cell<usize> = const { std::cell::Cell::new(1) };
#[allow(non_upper_case_globals)]
static shinbuffer: RefCell<String> = const { RefCell::new(String::new()) };
#[allow(non_upper_case_globals)]
static shinbufpos: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
static shinsavestack: RefCell<Vec<(String, usize)>> = const { RefCell::new(Vec::new()) };
static pushback: RefCell<VecDeque<char>> = const { RefCell::new(VecDeque::new()) };
static raw_input: RefCell<String> = const { RefCell::new(String::new()) };
}
pub fn shinbufreset() { shinbuffer.with(|b| b.borrow_mut().clear());
shinbufpos.with(|p| p.set(0));
}
pub fn shinbufalloc() { shinbuffer.with(|b| {
*b.borrow_mut() = String::with_capacity(SHIN_BUF_SIZE);
});
shinbufreset();
}
pub fn shinbufsave() { let (snap_buf, snap_pos) = (
shinbuffer.with(|b| std::mem::take(&mut *b.borrow_mut())),
shinbufpos.with(|p| p.replace(0)),
);
shinsavestack.with(|s| s.borrow_mut().push((snap_buf, snap_pos)));
shinbufalloc();
}
pub fn shinbufrestore() { if let Some((buf, pos)) = shinsavestack.with(|s| s.borrow_mut().pop()) {
shinbuffer.with(|b| *b.borrow_mut() = buf);
shinbufpos.with(|p| p.set(pos));
}
}
pub fn shingetchar() -> i32 { let bufd = shinbuffer.with(|b| b.borrow().clone());
let pos = shinbufpos.with(|p| p.get());
if pos < bufd.len() {
if let Some(ch) = bufd.chars().nth(pos) {
shinbufpos.with(|p| p.set(pos + 1));
return ch as i32;
}
}
shinbufreset();
let stdin = std::io::stdin();
let mut reader = BufReader::new(stdin.lock());
let mut line = String::new();
match reader.read_line(&mut line) {
Ok(0) => -1,
Ok(_) => {
let first = line.chars().next().map(|c| c as i32).unwrap_or(-1);
shinbuffer.with(|b| *b.borrow_mut() = line);
shinbufpos.with(|p| p.set(1));
first
}
Err(_) => -1,
}
}
pub fn shingetline() -> String { let mut result = String::new();
loop {
match shingetchar() {
-1 => return result,
ch_i32 => {
let c = char::from_u32(ch_i32 as u32).unwrap_or('\0');
if c == '\n' {
result.push('\n');
return result;
}
if imeta(c) {
result.push(META);
result.push(char::from_u32((c as u32) ^ 32).unwrap_or(c));
} else {
result.push(c);
}
}
}
}
}
pub fn inputline() -> String { let line = shingetline();
if line.is_empty() {
lexstop.with(|c| c.set(true));
}
line
}
pub fn ingetc() -> Option<char> { if lexstop.with(|c| c.get()) {
return Some(' ');
}
if let Some(c) = pushback.with(|p| p.borrow_mut().pop_front()) {
raw_input.with(|r| r.borrow_mut().push(c));
return Some(c);
}
loop {
let pos = inbufpos.with(|p| p.get());
let buf = inbuf.with(|b| b.borrow().clone());
if pos < buf.len() {
let c = buf.chars().nth(pos)?;
inbufpos.with(|p| p.set(pos + 1));
inbufct.with(|c| c.set(c.get().saturating_sub(1)));
if (0x83..=0x9b).contains(&(c as u32)) {
continue;
}
let inp_lineno =
(inbufflags.with(|f| f.get()) & INP_LINENO) != 0;
let is_strin = strin.with(|s| s.get()) != 0;
if (inp_lineno || !is_strin) && c == '\n' {
lineno.with(|l| l.set(l.get() + 1));
}
raw_input.with(|r| r.borrow_mut().push(c));
return Some(c);
}
let ct = inbufct.with(|c| c.get());
let is_strin = strin.with(|s| s.get()) != 0;
let is_stop = lexstop.with(|c| c.get());
if ct == 0 && (is_strin || is_stop) {
lexstop.with(|c| c.set(true));
return None;
}
if (inbufflags.with(|f| f.get()) & INP_CONT) != 0 {
inpoptop();
continue;
}
lexstop.with(|c| c.set(true));
return None;
}
}
pub fn inungetc(c: char) { if lexstop.with(|c| c.get()) {
return;
}
let pos = inbufpos.with(|p| p.get());
if pos > 0 {
inbufpos.with(|p| p.set(pos - 1));
inbufct.with(|cell| cell.set(cell.get() + 1));
let inp_lineno =
(inbufflags.with(|f| f.get()) & INP_LINENO) != 0;
let is_strin = strin.with(|s| s.get()) != 0;
if (inp_lineno || !is_strin) && c == '\n' {
lineno.with(|l| l.set(l.get().saturating_sub(1)));
}
raw_input.with(|r| { r.borrow_mut().pop(); });
} else {
pushback.with(|p| p.borrow_mut().push_front(c));
}
}
pub fn inpush(str: &str, flags: i32, inalias: Option<String>) { let saved = instacks {
buf: inbuf.with(|b| std::mem::take(&mut *b.borrow_mut())),
bufpos: inbufpos.with(|p| p.replace(0)),
flags: inbufflags.with(|f| f.get()),
alias: None,
};
instack.with(|st| st.borrow_mut().push(saved));
inbuf.with(|b| *b.borrow_mut() = str.to_string());
inbufpos.with(|p| p.set(0));
let mut combined = flags;
if (flags & (INP_ALIAS | INP_HIST)) != 0 {
combined |= INP_CONT | INP_ALIAS;
if let Some(a) = inalias {
instack.with(|st| {
if let Some(last) = st.borrow_mut().last_mut() {
last.alias = Some(a);
if (flags & INP_HIST) != 0 {
last.flags |= INP_HISTCONT;
} else {
last.flags |= INP_ALCONT;
}
}
});
}
}
let new_len = inbuf.with(|b| b.borrow().len()) as i32;
if (combined & INP_CONT) != 0 {
inbufct.with(|c| c.set(c.get() + new_len));
} else {
inbufct.with(|c| c.set(new_len));
}
inbufflags.with(|f| f.set(combined));
}
pub fn inpoptop() { if let Some(entry) = instack.with(|st| st.borrow_mut().pop()) {
inbuf.with(|b| *b.borrow_mut() = entry.buf);
inbufpos.with(|p| p.set(entry.bufpos));
inbufflags.with(|f| f.set(entry.flags));
let remaining = inbuf.with(|b| b.borrow().len())
.saturating_sub(entry.bufpos) as i32;
inbufct.with(|c| c.set(remaining));
}
}
pub fn inpop() { loop {
let was_cont = (inbufflags.with(|f| f.get()) & INP_CONT) != 0;
inpoptop();
if !was_cont {
break;
}
}
}
pub fn inpopalias() { while (inbufflags.with(|f| f.get()) & INP_ALIAS) != 0 {
inpoptop();
}
}
pub fn inputsetline(str: &str, flags: i32) { inbuf.with(|b| *b.borrow_mut() = str.to_string());
inbufpos.with(|p| p.set(0));
let len = str.len() as i32;
if (flags & INP_CONT) != 0 {
inbufct.with(|c| c.set(c.get() + len));
} else {
inbufct.with(|c| c.set(len));
}
inbufflags.with(|f| f.set(flags));
}
pub fn inerrflush() { while !lexstop.with(|c| c.get()) && inbufct.with(|c| c.get()) > 0 {
let _ = ingetc();
}
}
pub fn ingetptr() -> String { let pos = inbufpos.with(|p| p.get());
inbuf.with(|b| {
let s = b.borrow();
if pos < s.len() {
s[pos..].to_string()
} else {
String::new()
}
})
}
pub fn stuff(filename: &str) -> i32 { let buf = match std::fs::read_to_string(filename) {
Ok(b) => b,
Err(_) => return 1,
};
let _ = std::io::stderr().write_all(buf.as_bytes());
let _ = std::io::stderr().flush();
inpush(&buf, INP_FREE, None);
0
}
pub const META: char = '\u{83}';
fn imeta(c: char) -> bool {
let b = c as u32;
b < 32 || (0x83..=0x9b).contains(&b)
}
pub fn zstuff(path: &str) -> io::Result<String> { std::fs::read_to_string(path)
}
#[cfg(test)]
mod tests {
use super::*;
fn reset_input() {
super::inbuf.with(|b| b.borrow_mut().clear());
super::inbufpos.with(|p| p.set(0));
super::inbufct.with(|c| c.set(0));
super::inbufflags.with(|f| f.set(0));
super::lexstop.with(|c| c.set(false));
super::lineno.with(|l| l.set(1));
super::instack.with(|st| st.borrow_mut().clear());
super::pushback.with(|p| p.borrow_mut().clear());
super::raw_input.with(|r| r.borrow_mut().clear());
}
#[test]
fn test_input_buffer_basic() {
reset_input();
inputsetline("hello", 0);
assert_eq!(ingetc(), Some('h'));
assert_eq!(ingetc(), Some('e'));
assert_eq!(ingetc(), Some('l'));
assert_eq!(ingetc(), Some('l'));
assert_eq!(ingetc(), Some('o'));
assert_eq!(ingetc(), None);
}
#[test]
fn test_input_ungetc() {
reset_input();
inputsetline("abc", 0);
assert_eq!(ingetc(), Some('a'));
assert_eq!(ingetc(), Some('b'));
inungetc('b');
assert_eq!(ingetc(), Some('b'));
assert_eq!(ingetc(), Some('c'));
}
#[test]
fn test_input_stack() {
reset_input();
inputsetline("outer", 0);
assert_eq!(ingetc(), Some('o'));
inpush("inner", INP_CONT, None);
assert_eq!(ingetc(), Some('i'));
assert_eq!(ingetc(), Some('n'));
assert_eq!(ingetc(), Some('n'));
assert_eq!(ingetc(), Some('e'));
assert_eq!(ingetc(), Some('r'));
assert_eq!(ingetc(), Some('u'));
assert_eq!(ingetc(), Some('t'));
}
#[test]
fn test_line_number_tracking() {
reset_input();
inputsetline("a\nb\nc", INP_LINENO);
assert_eq!(lineno.with(|l| l.get()), 1);
ingetc(); ingetc(); assert_eq!(lineno.with(|l| l.get()), 2);
ingetc(); ingetc(); assert_eq!(lineno.with(|l| l.get()), 3);
}
#[test]
fn test_meta_encoding() {
assert!(imeta('\x00'));
assert!(imeta('\x1f'));
assert!(!imeta('a'));
assert!(!imeta('Z'));
let encoded = char::from_u32(('\x00' as u32) ^ 32).unwrap_or('\x00');
let decoded = char::from_u32((encoded as u32) ^ 32).unwrap_or(encoded);
assert_eq!(decoded, '\x00');
}
#[test]
fn test_ingetptr() {
reset_input();
inputsetline("hello world", 0);
ingetc(); ingetc(); ingetc(); ingetc(); ingetc(); assert_eq!(ingetptr(), " world");
}
#[test]
fn test_inerrflush() {
reset_input();
inputsetline("remaining input", 0);
ingetc();
inerrflush();
assert!(lexstop.with(|c| c.get()) || inbufct.with(|c| c.get()) == 0);
}
}