use std::cell::RefCell;
use std::collections::VecDeque;
use std::io::{BufRead, BufReader, Read, Write, self};
use crate::ported::zsh_h::{
INP_ALCONT, INP_ALIAS, INP_CONT, INP_FREE, INP_HIST, INP_HISTCONT, INP_LINENO, INP_RAW_KEEP, Meta
};
#[derive(Clone, Default)]
#[allow(non_camel_case_types)]
struct instacks {
buf: String, bufpos: usize, flags: i32, alias: Option<String>, }
#[allow(dead_code)]
const INSTACK_INITIAL: usize = 4;
pub fn shinbufreset() {
shinbuffer.with(|b| b.borrow_mut().clear());
shinbufpos.with(|p| p.set(0));
}
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 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 as char);
result.push(char::from_u32((c as u32) ^ 32).unwrap_or(c));
} else {
result.push(c);
}
}
}
}
}
pub fn ingetc() -> Option<char> {
if lexstop.with(|c| c.get()) {
return None; }
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)));
let cu32 = c as u32;
if cu32 < 256 && crate::ported::ztype_h::itok(cu32 as u8) {
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 inputline() -> String {
let line = shingetline();
if line.is_empty() {
lexstop.with(|c| c.set(true));
}
line
}
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));
lexstop.with(|c| c.set(false));
}
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 zstuff(path: &str) -> Result<(String, i64), i32> {
use std::io::Read;
let mut path_bytes = path.as_bytes().to_vec();
crate::ported::utils::unmetafy(&mut path_bytes);
let real_path = String::from_utf8_lossy(&path_bytes);
let mut file = match std::fs::File::open(real_path.as_ref()) {
Ok(f) => f,
Err(_) => {
crate::ported::utils::zerr(&format!("can't open {}", path)); return Err(-1); }
};
crate::ported::signals_h::queue_signals();
let len = match file.metadata() {
Ok(m) => m.len() as i64,
Err(_) => 0,
};
let mut buf = String::new(); if file.read_to_string(&mut buf).is_err() {
crate::ported::utils::zerr(&format!("read error on {}", path)); crate::ported::signals_h::unqueue_signals(); return Err(-1); }
crate::ported::signals_h::unqueue_signals(); Ok((buf, len)) }
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 fn inerrflush() {
while !lexstop.with(|c| c.get()) && inbufct.with(|c| c.get()) > 0 {
let _ = ingetc();
}
}
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));
lexstop.with(|c| c.set(false));
crate::ported::lex::LEX_LEXSTOP.with(|c| c.set(false));
}
pub fn inpoptop() {
if !crate::ported::lex::LEX_LEXSTOP.with(|c| c.get()) {
inbufflags.with(|f| f.set(f.get() & !(INP_ALCONT | INP_HISTCONT)));
let was_alias =
(inbufflags.with(|f| f.get()) & (INP_ALIAS | INP_HIST | INP_RAW_KEEP)) == INP_ALIAS;
let unread = inbuf.with(|b| {
let blen = b.borrow().len();
blen.saturating_sub(inbufpos.with(|p| p.get()))
});
if was_alias {
for _ in 0..unread {
crate::ported::lex::zshlex_raw_back(); }
}
}
if let Some(entry) = instack.with(|st| st.borrow_mut().pop()) {
if let Some(name) = &entry.alias {
{
let mut tab = crate::ported::hashtable::aliastab_lock()
.write()
.expect("aliastab poisoned");
if let Some(a) = tab.get_mut(name) {
a.inuse = 0; }
}
if entry.buf.ends_with(' ') {
crate::ported::hist::histbackword(); }
}
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 ingetptr() -> String {
let pos = inbufpos.with(|p| p.get());
inbuf.with(|b| {
b.borrow()
.get(pos..)
.map(str::to_string)
.unwrap_or_default()
})
}
const SHIN_BUF_SIZE: usize = 8192;
fn imeta(c: char) -> bool {
let b = c as u32;
if b > 0xff {
return false;
}
crate::ported::ztype_h::imeta(b as u8)
}
#[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() {
let _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
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() {
let _g = crate::test_util::global_state_lock();
let _g = crate::ported::ztype_h::TYPTAB_TEST_LOCK
.lock()
.unwrap_or_else(|e| e.into_inner());
crate::ported::utils::inittyptab();
assert!(imeta('\x00'));
assert!(imeta('\u{83}'));
assert!(imeta('\u{a2}'));
assert!(imeta('\u{84}'));
assert!(imeta('\u{9c}'));
assert!(imeta('\u{9d}'));
assert!(imeta('\u{a1}'));
assert!(!imeta('a'));
assert!(!imeta('Z'));
assert!(!imeta('0'));
assert!(!imeta('\x01'));
assert!(!imeta('\x1f'));
assert!(!imeta('\u{a3}'));
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() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("hello world", 0);
ingetc(); ingetc(); ingetc(); ingetc(); ingetc(); assert_eq!(ingetptr(), " world");
}
#[test]
fn test_inerrflush() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("remaining input", 0);
ingetc();
inerrflush();
assert!(lexstop.with(|c| c.get()) || inbufct.with(|c| c.get()) == 0);
}
#[test]
fn shinbufreset_clears_buffer_and_zeros_pos() {
let _g = crate::test_util::global_state_lock();
super::shinbuffer.with(|b| {
*b.borrow_mut() = "leftover".to_string();
});
super::shinbufpos.with(|p| p.set(7));
super::shinbufreset();
super::shinbuffer.with(|b| {
assert!(
b.borrow().is_empty(),
"c:161 — shinbuffer must be empty after reset"
);
});
assert_eq!(
super::shinbufpos.with(|p| p.get()),
0,
"c:161 — shinbufpos must be 0 after reset"
);
}
#[test]
fn shinbufalloc_resets_and_capacity_hints() {
let _g = crate::test_util::global_state_lock();
super::shinbuffer.with(|b| {
*b.borrow_mut() = "stale".to_string();
});
super::shinbufpos.with(|p| p.set(3));
super::shinbufalloc();
super::shinbuffer.with(|b| {
assert!(
b.borrow().is_empty(),
"c:173 — fresh shinbuffer must be empty"
);
assert!(b.borrow().capacity() >= 1);
});
assert_eq!(super::shinbufpos.with(|p| p.get()), 0);
}
#[test]
fn shinbufsave_restore_round_trip() {
let _g = crate::test_util::global_state_lock();
super::shinsavestack.with(|s| s.borrow_mut().clear());
super::shinbuffer.with(|b| *b.borrow_mut() = "abc".to_string());
super::shinbufpos.with(|p| p.set(2));
super::shinbufsave();
super::shinbuffer.with(|b| {
assert!(
b.borrow().is_empty(),
"c:193 — shinbufsave invokes shinbufalloc (resets to empty)"
);
});
assert_eq!(
super::shinbufpos.with(|p| p.get()),
0,
"c:193 — pos must be 0 after save"
);
super::shinbufrestore();
super::shinbuffer.with(|b| {
assert_eq!(
*b.borrow(),
"abc",
"c:200-209 — shinbufrestore restores saved buffer"
);
});
assert_eq!(
super::shinbufpos.with(|p| p.get()),
2,
"c:200-209 — shinbufrestore restores saved pos"
);
}
#[test]
fn shinbufrestore_on_empty_stack_is_noop() {
let _g = crate::test_util::global_state_lock();
super::shinsavestack.with(|s| s.borrow_mut().clear());
super::shinbuffer.with(|b| *b.borrow_mut() = "persist".to_string());
super::shinbufrestore();
super::shinbuffer.with(|b| {
assert_eq!(
*b.borrow(),
"persist",
"empty-stack restore must leave buffer untouched"
);
});
}
#[test]
fn ingetc_skips_token_bytes_via_itok_predicate() {
let _g = crate::test_util::global_state_lock();
reset_input();
crate::ported::utils::inittyptab();
let bang: char = '\u{009c}'; let nularg: char = '\u{00a1}'; let mut s = String::new();
s.push('a');
s.push(bang);
s.push('b');
s.push(nularg);
s.push('c');
inputsetline(&s, 0);
assert_eq!(ingetc(), Some('a'));
assert_eq!(
ingetc(),
Some('b'),
"c:328 — Bang (0x9c) must be skipped (ITOK bit set per inittyptab)"
);
assert_eq!(
ingetc(),
Some('c'),
"c:328 — Nularg (0xa1) must be skipped (ITOK bit set per inittyptab)"
);
}
#[test]
fn ingetc_does_not_skip_imeta_only_bytes() {
let _g = crate::test_util::global_state_lock();
reset_input();
crate::ported::utils::inittyptab();
let meta: char = '\u{0083}'; let marker: char = '\u{00a2}'; let mut s = String::new();
s.push('x');
s.push(meta);
s.push('y');
s.push(marker);
s.push('z');
inputsetline(&s, 0);
assert_eq!(ingetc(), Some('x'));
assert_eq!(
ingetc(),
Some(meta),
"c:328 — Meta (0x83) is IMETA-only, NOT ITOK; must pass through"
);
assert_eq!(ingetc(), Some('y'));
assert_eq!(
ingetc(),
Some(marker),
"c:328 / c:4197 — Marker (0xa2) is IMETA-only, NOT ITOK; must pass through"
);
assert_eq!(ingetc(), Some('z'));
}
#[test]
fn input_empty_line_yields_none_first_read() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("", 0);
assert_eq!(ingetc(), None);
}
#[test]
fn input_repeated_reads_past_end_keep_returning_none_eventually() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("a", 0);
assert_eq!(ingetc(), Some('a'));
let mut seen_none = false;
for _ in 0..10 {
if ingetc().is_none() {
seen_none = true;
break;
}
}
assert!(seen_none, "should reach None within 10 reads past end");
for _ in 0..3 {
assert_eq!(ingetc(), None);
}
}
#[test]
fn input_inungetc_before_any_read() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("xyz", 0);
inungetc('Q');
assert_eq!(ingetc(), Some('Q'));
assert_eq!(ingetc(), Some('x'));
}
#[test]
fn input_inungetc_lifo_order() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("z", 0);
inungetc('A');
inungetc('B');
inungetc('C');
assert_eq!(ingetc(), Some('C'));
assert_eq!(ingetc(), Some('B'));
assert_eq!(ingetc(), Some('A'));
assert_eq!(ingetc(), Some('z'));
}
#[test]
fn input_inpush_then_pop_returns_to_outer() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("outer", 0);
assert_eq!(ingetc(), Some('o'));
inpush("inner", INP_CONT, None);
assert_eq!(ingetc(), Some('i'));
inpoptop();
assert_eq!(ingetc(), Some('u'));
}
#[test]
fn input_set_empty_then_set_nonempty_reads_new_content() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("", 0);
for _ in 0..5 {
if ingetc().is_none() {
break;
}
}
inputsetline("xyz", 0);
assert_eq!(ingetc(), Some('x'));
assert_eq!(ingetc(), Some('y'));
assert_eq!(ingetc(), Some('z'));
}
#[test]
fn input_ascii_passthrough_byte_for_byte() {
let _g = crate::test_util::global_state_lock();
reset_input();
let src = "the quick brown fox 0123!";
inputsetline(src, 0);
let mut got = String::new();
while let Some(c) = ingetc() {
got.push(c);
}
assert_eq!(got, src);
}
#[test]
fn input_no_lineno_flag_means_lineno_unchanged_on_newline_anchored() {
let _g = crate::test_util::global_state_lock();
reset_input();
let saved_strin = strin.with(|s| s.get());
strin.with(|s| s.set(1));
let start = lineno.with(|l| l.get());
inputsetline("a\nb\n", 0);
while ingetc().is_some() {}
let end = lineno.with(|l| l.get());
strin.with(|s| s.set(saved_strin));
assert_eq!(end, start, "c:330 — strin && !INP_LINENO → lineno stable");
}
#[test]
fn input_lineno_flag_increments_on_each_newline() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("x\ny\nz\n", INP_LINENO);
let start = lineno.with(|l| l.get());
while ingetc().is_some() {}
let end = lineno.with(|l| l.get());
assert_eq!(end - start, 3, "three `\\n`s should advance lineno by 3");
}
#[test]
fn input_inpush_priorities_inner_over_outer() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("abc", 0);
inpush("123", INP_CONT, None);
assert_eq!(ingetc(), Some('1'));
assert_eq!(ingetc(), Some('2'));
assert_eq!(ingetc(), Some('3'));
assert_eq!(ingetc(), Some('a'));
assert_eq!(ingetc(), Some('b'));
assert_eq!(ingetc(), Some('c'));
}
#[test]
fn input_multibyte_utf8_char_passes_through() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("日", 0);
let c = ingetc();
assert_eq!(c, Some('日'));
assert_eq!(ingetc(), None);
}
#[test]
fn input_corpus_ascii_returns_chars_in_order() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("abc", 0);
assert_eq!(ingetc(), Some('a'));
assert_eq!(ingetc(), Some('b'));
assert_eq!(ingetc(), Some('c'));
assert_eq!(ingetc(), None);
}
#[test]
fn input_corpus_inungetc_after_read_rewinds_to_buf_char() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("xy", 0);
let _ = ingetc(); inungetc('Z'); assert_eq!(ingetc(), Some('x'), "buf[pos-1] returned, NOT the unget char");
assert_eq!(ingetc(), Some('y'));
}
#[test]
fn input_corpus_inungetc_multiple_lifo() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("end", 0);
inungetc('1');
inungetc('2');
inungetc('3');
assert_eq!(ingetc(), Some('3'), "last-pushed first");
assert_eq!(ingetc(), Some('2'));
assert_eq!(ingetc(), Some('1'));
assert_eq!(ingetc(), Some('e'), "then original buffer");
}
#[test]
fn input_corpus_empty_line_returns_none() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("", 0);
assert_eq!(ingetc(), None);
}
#[test]
fn input_corpus_multibyte_two_codepoints() {
let _g = crate::test_util::global_state_lock();
reset_input();
inputsetline("日本", 0);
assert_eq!(ingetc(), Some('日'));
assert_eq!(ingetc(), Some('本'));
assert_eq!(ingetc(), None);
}
}