Skip to main content

zsh/
input.rs

1//! Input buffering and stack management for zshrs
2//!
3//! Direct port from zsh/Src/input.c
4//!
5//! This module handles:
6//! - Reading input from files, strings, and the line editor
7//! - Input stack for alias expansion and history substitution
8//! - Character-by-character input with push-back support
9//! - Meta-character encoding for internal tokens
10
11use std::collections::VecDeque;
12use std::io::{self, BufRead, BufReader, Read};
13
14/// Size of the shell input buffer
15const SHIN_BUF_SIZE: usize = 8192;
16
17/// Initial input stack size
18const INSTACK_INITIAL: usize = 4;
19
20/// Input flags
21pub mod flags {
22    pub const INP_FREE: u32 = 0x01; // Free input string when done
23    pub const INP_CONT: u32 = 0x02; // Continue to next stack element
24    pub const INP_ALIAS: u32 = 0x04; // Input is alias expansion
25    pub const INP_HIST: u32 = 0x08; // Input is history expansion
26    pub const INP_LINENO: u32 = 0x10; // Increment line number on newline
27    pub const INP_APPEND: u32 = 0x20; // Append to existing input
28    pub const INP_ALCONT: u32 = 0x40; // Alias continuation marker
29    pub const INP_HISTCONT: u32 = 0x80; // History continuation marker
30    pub const INP_RAW_KEEP: u32 = 0x100; // Keep raw input for history
31}
32
33/// An entry on the input stack
34#[derive(Debug, Clone)]
35struct InputStackEntry {
36    /// The input buffer
37    buf: String,
38    /// Current position in buffer
39    pos: usize,
40    /// Flags for this input level
41    flags: u32,
42    /// Associated alias name (if any)
43    alias: Option<String>,
44}
45
46impl Default for InputStackEntry {
47    fn default() -> Self {
48        InputStackEntry {
49            buf: String::new(),
50            pos: 0,
51            flags: 0,
52            alias: None,
53        }
54    }
55}
56
57/// Input buffer state
58pub struct InputBuffer {
59    /// Stack of input sources
60    stack: Vec<InputStackEntry>,
61    /// Current input buffer
62    buf: String,
63    /// Position in current buffer
64    pos: usize,
65    /// Current flags
66    flags: u32,
67    /// Total characters available
68    pub buf_ct: usize,
69    /// Whether we're reading from a string
70    pub strin: bool,
71    /// Current line number
72    pub lineno: usize,
73    /// Stop lexing flag
74    pub lexstop: bool,
75    /// Shell input file descriptor buffer
76    shin_buffer: String,
77    /// Position in SHIN buffer
78    shin_pos: usize,
79    /// Stack of saved SHIN buffers
80    shin_save_stack: Vec<(String, usize)>,
81    /// Push-back buffer for characters
82    pushback: VecDeque<char>,
83    /// Raw input accumulator for history
84    raw_input: String,
85}
86
87impl Default for InputBuffer {
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93impl InputBuffer {
94    pub fn new() -> Self {
95        InputBuffer {
96            stack: Vec::with_capacity(INSTACK_INITIAL),
97            buf: String::new(),
98            pos: 0,
99            flags: 0,
100            buf_ct: 0,
101            strin: false,
102            lineno: 1,
103            lexstop: false,
104            shin_buffer: String::new(),
105            shin_pos: 0,
106            shin_save_stack: Vec::new(),
107            pushback: VecDeque::new(),
108            raw_input: String::new(),
109        }
110    }
111
112    /// Reset the SHIN buffer
113    pub fn shin_buf_reset(&mut self) {
114        self.shin_buffer.clear();
115        self.shin_pos = 0;
116    }
117
118    /// Allocate a new SHIN buffer
119    pub fn shin_buf_alloc(&mut self) {
120        self.shin_buffer = String::with_capacity(SHIN_BUF_SIZE);
121        self.shin_buf_reset();
122    }
123
124    /// Save current SHIN buffer state
125    pub fn shin_buf_save(&mut self) {
126        self.shin_save_stack
127            .push((std::mem::take(&mut self.shin_buffer), self.shin_pos));
128        self.shin_buf_alloc();
129    }
130
131    /// Restore saved SHIN buffer state
132    pub fn shin_buf_restore(&mut self) {
133        if let Some((buffer, pos)) = self.shin_save_stack.pop() {
134            self.shin_buffer = buffer;
135            self.shin_pos = pos;
136        }
137    }
138
139    /// Get next character from a reader
140    pub fn shin_getchar<R: Read>(&mut self, reader: &mut BufReader<R>) -> Option<char> {
141        // First check if we have buffered data
142        if self.shin_pos < self.shin_buffer.len() {
143            let ch = self.shin_buffer.chars().nth(self.shin_pos)?;
144            self.shin_pos += 1;
145            return Some(ch);
146        }
147
148        // Need to read more data
149        self.shin_buf_reset();
150        let mut line = String::new();
151        match reader.read_line(&mut line) {
152            Ok(0) => None, // EOF
153            Ok(_) => {
154                self.shin_buffer = line;
155                self.shin_pos = 1;
156                self.shin_buffer.chars().next()
157            }
158            Err(_) => None,
159        }
160    }
161
162    /// Read a line from shell input, encoding meta characters
163    pub fn shin_getline<R: Read>(&mut self, reader: &mut BufReader<R>) -> Option<String> {
164        let mut result = String::new();
165
166        loop {
167            match self.shin_getchar(reader) {
168                None => {
169                    if result.is_empty() {
170                        return None;
171                    }
172                    return Some(result);
173                }
174                Some('\n') => {
175                    result.push('\n');
176                    return Some(result);
177                }
178                Some(c) => {
179                    if is_meta(c) {
180                        result.push(META);
181                        result.push(meta_encode(c));
182                    } else {
183                        result.push(c);
184                    }
185                }
186            }
187        }
188    }
189
190    /// Get the next character from input
191    pub fn ingetc(&mut self) -> Option<char> {
192        if self.lexstop {
193            return Some(' ');
194        }
195
196        // Check pushback buffer first
197        if let Some(c) = self.pushback.pop_front() {
198            self.raw_add(c);
199            return Some(c);
200        }
201
202        loop {
203            // Try to get from current buffer
204            if self.pos < self.buf.len() {
205                let c = self.buf.chars().nth(self.pos)?;
206                self.pos += 1;
207                self.buf_ct = self.buf_ct.saturating_sub(1);
208
209                // Skip internal tokens
210                if is_tok(c) {
211                    continue;
212                }
213
214                // Track line numbers
215                if ((self.flags & flags::INP_LINENO != 0) || !self.strin) && c == '\n' {
216                    self.lineno += 1;
217                }
218
219                self.raw_add(c);
220                return Some(c);
221            }
222
223            // Check if we've reached end of input
224            if self.buf_ct == 0 && (self.strin || self.lexstop) {
225                self.lexstop = true;
226                return None;
227            }
228
229            // If continuation, pop the stack
230            if self.flags & flags::INP_CONT != 0 {
231                self.inpop_top();
232                continue;
233            }
234
235            // No more input available
236            self.lexstop = true;
237            return None;
238        }
239    }
240
241    /// Push a character back into input
242    pub fn inungetc(&mut self, c: char) {
243        if self.lexstop {
244            return;
245        }
246
247        if self.pos > 0 {
248            self.pos -= 1;
249            self.buf_ct += 1;
250            if ((self.flags & flags::INP_LINENO != 0) || !self.strin) && c == '\n' {
251                self.lineno = self.lineno.saturating_sub(1);
252            }
253            self.raw_back();
254        } else if self.flags & flags::INP_CONT == 0 {
255            // Can't back up at start - push as new input
256            self.pushback.push_front(c);
257        } else {
258            // Push onto pushback for continuation
259            self.pushback.push_front(c);
260        }
261    }
262
263    /// Push a string onto the input stack
264    pub fn inpush(&mut self, s: &str, flags: u32, alias: Option<String>) {
265        // Save current state
266        let entry = InputStackEntry {
267            buf: std::mem::take(&mut self.buf),
268            pos: self.pos,
269            flags: self.flags,
270            alias: None,
271        };
272        self.stack.push(entry);
273
274        // Set up new input
275        self.buf = s.to_string();
276        self.pos = 0;
277
278        // Handle alias/history flags
279        let mut new_flags = flags;
280        if flags & (flags::INP_ALIAS | flags::INP_HIST) != 0 {
281            new_flags |= flags::INP_CONT | flags::INP_ALIAS;
282            if let Some(ref a) = alias {
283                // Mark alias as in use
284                if let Some(last) = self.stack.last_mut() {
285                    last.alias = Some(a.clone());
286                    if flags & flags::INP_HIST != 0 {
287                        last.flags |= flags::INP_HISTCONT;
288                    } else {
289                        last.flags |= flags::INP_ALCONT;
290                    }
291                }
292            }
293        }
294
295        // Update counts
296        if new_flags & flags::INP_CONT != 0 {
297            self.buf_ct += self.buf.len();
298        } else {
299            self.buf_ct = self.buf.len();
300        }
301        self.flags = new_flags;
302    }
303
304    /// Pop the top entry from the input stack
305    fn inpop_top(&mut self) {
306        if let Some(entry) = self.stack.pop() {
307            self.buf = entry.buf;
308            self.pos = entry.pos;
309            self.flags = entry.flags;
310            self.buf_ct = self.buf.len().saturating_sub(self.pos);
311
312            // Handle alias continuation
313            if self.flags & (flags::INP_ALCONT | flags::INP_HISTCONT) != 0 {
314                // Mark alias as no longer in use
315                // Check for trailing space (inalmore)
316            }
317        }
318    }
319
320    /// Pop the stack including all continuations
321    pub fn inpop(&mut self) {
322        loop {
323            let was_cont = self.flags & flags::INP_CONT != 0;
324            self.inpop_top();
325            if !was_cont {
326                break;
327            }
328        }
329    }
330
331    /// Expunge any aliases from the input stack
332    pub fn inpop_alias(&mut self) {
333        while self.flags & flags::INP_ALIAS != 0 {
334            self.inpop_top();
335        }
336    }
337
338    /// Set the input line directly
339    pub fn inputsetline(&mut self, s: &str, flags: u32) {
340        self.buf = s.to_string();
341        self.pos = 0;
342
343        if flags & flags::INP_CONT != 0 {
344            self.buf_ct += self.buf.len();
345        } else {
346            self.buf_ct = self.buf.len();
347        }
348        self.flags = flags;
349    }
350
351    /// Flush remaining input (on error)
352    pub fn inerrflush(&mut self) {
353        while !self.lexstop && self.buf_ct > 0 {
354            let _ = self.ingetc();
355        }
356    }
357
358    /// Get pointer to remaining input
359    pub fn ingetptr(&self) -> &str {
360        if self.pos < self.buf.len() {
361            &self.buf[self.pos..]
362        } else {
363            ""
364        }
365    }
366
367    /// Check if current input is from an alias
368    pub fn input_has_alias(&self) -> Option<&str> {
369        let mut flags = self.flags;
370
371        for entry in self.stack.iter().rev() {
372            if flags & flags::INP_CONT == 0 {
373                break;
374            }
375            if let Some(ref alias) = entry.alias {
376                return Some(alias);
377            }
378            flags = entry.flags;
379        }
380        None
381    }
382
383    /// Add character to raw input accumulator
384    fn raw_add(&mut self, c: char) {
385        self.raw_input.push(c);
386    }
387
388    /// Remove last character from raw input
389    fn raw_back(&mut self) {
390        self.raw_input.pop();
391    }
392
393    /// Get and clear raw input
394    pub fn take_raw_input(&mut self) -> String {
395        std::mem::take(&mut self.raw_input)
396    }
397
398    /// Check if we have pending input
399    pub fn has_input(&self) -> bool {
400        self.buf_ct > 0 || !self.pushback.is_empty()
401    }
402
403    /// Get remaining character count
404    pub fn remaining(&self) -> usize {
405        self.buf_ct + self.pushback.len()
406    }
407}
408
409/// Meta character marker
410pub const META: char = '\u{83}';
411
412/// Check if a character needs meta encoding
413fn is_meta(c: char) -> bool {
414    let b = c as u32;
415    b < 32 || (b >= 0x83 && b <= 0x9b)
416}
417
418/// Check if a character is an internal token
419fn is_tok(c: char) -> bool {
420    let b = c as u32;
421    b >= 0x83 && b <= 0x9b
422}
423
424/// Encode a meta character
425fn meta_encode(c: char) -> char {
426    char::from_u32((c as u32) ^ 32).unwrap_or(c)
427}
428
429/// Decode a meta character
430pub fn meta_decode(c: char) -> char {
431    char::from_u32((c as u32) ^ 32).unwrap_or(c)
432}
433
434/// Read entire file into memory
435pub fn zstuff(path: &str) -> io::Result<String> {
436    std::fs::read_to_string(path)
437}
438
439/// String input source for simple string parsing
440pub struct StringInput {
441    input: InputBuffer,
442}
443
444impl StringInput {
445    pub fn new(s: &str) -> Self {
446        let mut input = InputBuffer::new();
447        input.strin = true;
448        input.inputsetline(s, 0);
449        StringInput { input }
450    }
451
452    pub fn getc(&mut self) -> Option<char> {
453        self.input.ingetc()
454    }
455
456    pub fn ungetc(&mut self, c: char) {
457        self.input.inungetc(c);
458    }
459
460    pub fn is_eof(&self) -> bool {
461        self.input.lexstop
462    }
463}
464
465#[cfg(test)]
466mod tests {
467    use super::*;
468
469    #[test]
470    fn test_input_buffer_basic() {
471        let mut buf = InputBuffer::new();
472        buf.inputsetline("hello", 0);
473
474        assert_eq!(buf.ingetc(), Some('h'));
475        assert_eq!(buf.ingetc(), Some('e'));
476        assert_eq!(buf.ingetc(), Some('l'));
477        assert_eq!(buf.ingetc(), Some('l'));
478        assert_eq!(buf.ingetc(), Some('o'));
479        assert_eq!(buf.ingetc(), None);
480    }
481
482    #[test]
483    fn test_input_ungetc() {
484        let mut buf = InputBuffer::new();
485        buf.inputsetline("abc", 0);
486
487        assert_eq!(buf.ingetc(), Some('a'));
488        assert_eq!(buf.ingetc(), Some('b'));
489        buf.inungetc('b');
490        assert_eq!(buf.ingetc(), Some('b'));
491        assert_eq!(buf.ingetc(), Some('c'));
492    }
493
494    #[test]
495    fn test_input_stack() {
496        let mut buf = InputBuffer::new();
497        buf.inputsetline("outer", 0);
498
499        assert_eq!(buf.ingetc(), Some('o'));
500
501        // Push new input
502        buf.inpush("inner", flags::INP_CONT, None);
503        assert_eq!(buf.ingetc(), Some('i'));
504        assert_eq!(buf.ingetc(), Some('n'));
505        assert_eq!(buf.ingetc(), Some('n'));
506        assert_eq!(buf.ingetc(), Some('e'));
507        assert_eq!(buf.ingetc(), Some('r'));
508
509        // Should continue to outer
510        assert_eq!(buf.ingetc(), Some('u'));
511        assert_eq!(buf.ingetc(), Some('t'));
512    }
513
514    #[test]
515    fn test_line_number_tracking() {
516        let mut buf = InputBuffer::new();
517        buf.inputsetline("a\nb\nc", flags::INP_LINENO);
518
519        assert_eq!(buf.lineno, 1);
520        buf.ingetc(); // a
521        buf.ingetc(); // \n
522        assert_eq!(buf.lineno, 2);
523        buf.ingetc(); // b
524        buf.ingetc(); // \n
525        assert_eq!(buf.lineno, 3);
526    }
527
528    #[test]
529    fn test_string_input() {
530        let mut input = StringInput::new("test");
531
532        assert_eq!(input.getc(), Some('t'));
533        assert_eq!(input.getc(), Some('e'));
534        assert_eq!(input.getc(), Some('s'));
535        assert_eq!(input.getc(), Some('t'));
536        assert!(input.is_eof() || input.getc().is_none());
537    }
538
539    #[test]
540    fn test_meta_encoding() {
541        assert!(is_meta('\x00'));
542        assert!(is_meta('\x1f'));
543        assert!(!is_meta('a'));
544        assert!(!is_meta('Z'));
545
546        let encoded = meta_encode('\x00');
547        let decoded = meta_decode(encoded);
548        assert_eq!(decoded, '\x00');
549    }
550
551    #[test]
552    fn test_ingetptr() {
553        let mut buf = InputBuffer::new();
554        buf.inputsetline("hello world", 0);
555
556        buf.ingetc(); // h
557        buf.ingetc(); // e
558        buf.ingetc(); // l
559        buf.ingetc(); // l
560        buf.ingetc(); // o
561
562        assert_eq!(buf.ingetptr(), " world");
563    }
564
565    #[test]
566    fn test_inerrflush() {
567        let mut buf = InputBuffer::new();
568        buf.inputsetline("remaining input", 0);
569
570        buf.ingetc(); // consume one char
571        buf.inerrflush();
572
573        assert!(buf.lexstop || buf.buf_ct == 0);
574    }
575}