zsh/extensions/alias_input_frames.rs
1//! Per-thread record of alias input-stack frames that have already been
2//! popped.
3//!
4//! **zshrs-original infrastructure — no `Src/*.c` counterpart.** It exists
5//! because zshrs's input stack is a `Vec` and C's is a manually-indexed
6//! array.
7//!
8//! `Src/input.c:831 input_hasalias()` answers "is the text currently being
9//! lexed coming out of an alias expansion, and if so which alias?" It does
10//! that by walking DOWN `instack` from `instacktop` while the flags carry
11//! `INP_CONT`, remembering every frame that has an `alias` set
12//! (`Src/input.c:837-846`). `Src/parse.c:1840` captures the answer at the
13//! head of `par_simple` and `Src/parse.c:2061` compares it against a fresh
14//! call to decide whether `name() { … }` is being defined out of an alias
15//! body (the `ALIAS_FUNC_DEF` diagnostic).
16//!
17//! The subtlety is that by the time the parser sees the `()` the alias body
18//! has been fully lexed and its frame ALREADY POPPED. C survives that
19//! because `inpoptop` (`Src/input.c:757-763`) only decrements `instacktop` —
20//! the frame's storage is untouched — and `inungetc` walks BACK UP over
21//! exhausted alias continuations whenever the lexer pushes the terminator
22//! back at a segment start:
23//!
24//! ```c
25//! if (inbufptr == inbufpush && (inbufflags & (INP_ALCONT|INP_HISTCONT))) {
26//! do {
27//! if (instacktop->alias) instacktop->alias->inuse = 1;
28//! instacktop++;
29//! } while ((instacktop->flags & (INP_ALCONT|INP_HISTCONT)) && !instacktop->bufleft);
30//! ...
31//! }
32//! ```
33//! (`Src/input.c:587-605`)
34//!
35//! zshrs's `instack` is a `Vec<instacks>` whose `pop()` DESTROYS the frame,
36//! so that walk-up cannot be reconstructed. This module records the alias
37//! name of each frame `inpoptop` restores that carries `INP_ALCONT`, in
38//! push order, so `input_hasalias` can report the OUTERMOST one — the same
39//! answer C's downward walk produces for a nested expansion
40//! (`secondalias` → `firstalias` → body reports `secondalias`,
41//! `Test/A02alias.ztst:127-131`).
42//!
43//! The record is bounded to ONE `zshlex` call (`Src/lex.c:268`), which is
44//! the whole `gettok`/`exalias` chain that produces a single token — so a
45//! nested expansion still records every alias in the chain, while a chain
46//! that finished during an earlier token cannot answer for a later one.
47//! `inpoptop` additionally clears it when a pop lands on flags without
48//! `INP_ALCONT`.
49
50use std::cell::RefCell;
51
52thread_local! {
53 /// Alias names of popped `INP_ALCONT` frames, outermost first.
54 static ALCONT_ALIASES: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
55}
56
57/// Record the alias name of a just-popped `INP_ALCONT` frame.
58pub fn push(name: &str) {
59 ALCONT_ALIASES.with(|a| a.borrow_mut().push(name.to_string()));
60}
61
62/// Drop the whole record — the alias chain it described has ended.
63pub fn clear() {
64 ALCONT_ALIASES.with(|a| a.borrow_mut().clear());
65}
66
67/// The outermost recorded alias name, i.e. what C's downward `instack`
68/// walk would have settled on.
69pub fn outermost() -> Option<String> {
70 ALCONT_ALIASES.with(|a| a.borrow().first().cloned())
71}