1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
//! !!! WARNING: RUST-ONLY MODULE — NOT A PORT OF ANY ZSH C FILE !!!
//!
//! Verbatim capture of function-body source text as the lexer consumes it.
//!
//! C zsh does not store function source at all. `par_funcdef`
//! (Src/parse.c:1672) compiles the body into the `ecbuf` wordcode array,
//! and `functions` / `typeset -f` / `whence -f` RECONSTRUCT the printed
//! text from that wordcode via `getpermtext` -> `gettext2`
//! (Src/text.c:189 / :296) — which is why zsh always prints canonical
//! tab-indented text regardless of how the user spaced the original.
//!
//! zshrs runs function bodies through the fusevm compile path, which never
//! builds a C-shaped `Eprog`; `shfunc.funcdef` (zsh_h.rs:868) is therefore
//! always `None` and `printshfuncnode` (hashtable.rs:1796) prints the RAW
//! SOURCE the parser captured instead. Until the fusevm path grows a real
//! `Eprog`, that raw source has to come from somewhere for EVERY input
//! source.
//!
//! It used to come only from a slice of the Rust-only `LEX_INPUT` window
//! (`lex::input_slice`), which exists for file / `-c` / `eval` input.
//! Interactive and `-s` stdin input reaches the lexer through
//! `hgetc` -> `ingetc` -> the `inbuf` stack and never touches `LEX_INPUT`,
//! so `LEX_POS` never moved and every function defined at a prompt or piped
//! in on stdin printed as `name () { }`.
//!
//! C's own history-line buffer (`chline`) is NOT a usable substitute: at
//! c:Src/hist.c:1119 `hbegin` sets
//! `stophist = (!interact || unset(SHINSTDIN)) ? 2 : 0` and then c:1129
//! `chline = hptr = NULL`, so for non-interactive stdin there is no history
//! line at all. The one funnel every input source does pass through is
//! `hgetc`, so this module hangs an echo buffer off it, active only between
//! [`body_mark_begin`] and [`body_text`].
//!
//! Lives outside `src/ported/` because it has no C counterpart (same
//! reasoning as `crate::tolerant_sort`).
use crate;
thread_local!
/// !!! WARNING: RUST-ONLY HELPER !!!
///
/// A parser-side bookmark for "where the text of a function body starts".
/// C has no analogue — `par_funcdef` (Src/parse.c:1672) writes the body
/// into the `ecbuf` wordcode array and `functions` re-derives the printed
/// text from it (`getpermtext`, Src/text.c:189). zshrs's fusevm path never
/// builds that wordcode `Eprog`, so the parser must keep the raw source.
///
/// Carries BOTH cursors because zshrs has two input sources: the Rust-only
/// `LEX_INPUT` window (file / `-c` / `eval` text) and the ported `inbuf`
/// stack that `hgetc` reads for interactive and `-s` stdin input. See
/// [`LEX_SRC_CAPTURE`] for why the second one needs an echo buffer.
/// !!! WARNING: RUST-ONLY HELPER !!!
///
/// Open a function-body capture and return the mark to close it with.
/// Every character `hgetc` returns from now until the matching
/// [`body_text`] is echoed into [`LEX_SRC_CAPTURE`]. See that static for
/// why this exists.
/// !!! WARNING: RUST-ONLY HELPER !!!
///
/// Move an already-open mark forward to the current input position,
/// discarding what has been captured so far. The funcdef parsers take the
/// mark before `()` and then re-take it past every separator (`pos()` after
/// a `zshlex` is already one token ahead, so the mark has to be re-read
/// BEFORE each advance); this is that re-read for the capture side.
/// !!! WARNING: RUST-ONLY HELPER !!!
///
/// Close the capture opened by [`body_mark_begin`] and return the raw body
/// text, untrimmed. `LEX_INPUT` wins when it actually advanced (the file /
/// `-c` / `eval` paths, whose slice behaviour is unchanged by this
/// helper); the `hgetc` echo buffer is the fallback for the interactive
/// and `-s` stdin paths, where `LEX_POS` never moves.
/// !!! WARNING: RUST-ONLY HELPER !!!
///
/// [`body_text`] for a top-level EVENT whose text will be parsed again
/// (loop()'s preexec `$2`/`$3`, src/ported/init.rs). Keeps the alias NAMES
/// and leaves out what their expansions pushed: the re-parse expands each
/// name again, so keeping both would render `ll x` (alias ll='print aliased')
/// as `llprint aliased x`. See [`LEX_SRC_CAPTURE_ALIAS`].
/// !!! WARNING: RUST-ONLY HELPER !!!
///
/// Drop one level of capture nesting; returns the remaining depth.
/// !!! WARNING: RUST-ONLY HELPER !!!
///
/// Drop any capture left open by a parse that bailed out, so a syntax error
/// in one function body cannot leak text into the next one. Called from
/// `lex_init` (src/ported/lex.rs) alongside the other lexer-state resets;
/// the `BodyMark` `Drop` guard covers the ordinary error returns.
/// !!! WARNING: RUST-ONLY HELPER !!!
///
/// [`LEX_SRC_CAPTURE_ALIAS`] flag: an alias expansion pushed this character
/// (`inpush(an->text, INP_ALIAS, an)`, c:Src/lex.c:1928).
pub const CAP_ALIAS_TEXT: u8 = 1;
/// [`LEX_SRC_CAPTURE_ALIAS`] flag: this character belongs to an alias name the
/// lexer replaced with its expansion.
pub const CAP_ALIAS_NAME: u8 = 2;
/// !!! WARNING: RUST-ONLY HELPER !!!
///
/// The capture from byte offset `cap_pos` on, without the characters whose
/// flags intersect `drop`.
/// !!! WARNING: RUST-ONLY HELPER !!!
///
/// Mark the alias name the lexer is about to replace (`inpush(an->text,
/// INP_ALIAS, an)`, c:Src/lex.c:1928, and the suffix-alias pushes at
/// c:1938-1940) as [`CAP_ALIAS_NAME`]. By then `hungetc` has already taken
/// back the character that ended the word, so the capture ends with `name`;
/// when it does not (capture closed, name not recorded) nothing is marked.
///
/// Inside a command substitution the name stays: skipcomm keeps the raw
/// text as the word (c:Src/lex.c:2253-2283), from which `inpoptop` has
/// taken the expansion back out (c:Src/input.c:751-752), so zsh lists
/// `$(ll)`. `hgetc` flags that expansion text for both renderings to drop.
/// !!! WARNING: RUST-ONLY HELPER !!!
///
/// Echo one consumed character into [`LEX_SRC_CAPTURE`] with its
/// [`LEX_SRC_CAPTURE_ALIAS`] flags. Returns whether it was recorded, so
/// `hungetc`'s pop and `hgetc`'s re-read stay symmetric. Called from `hgetc`
/// (src/ported/lex.rs) only.
/// !!! WARNING: RUST-ONLY HELPER !!!
///
/// Undo the last [`src_capture_add`], but only if the buffer really ends
/// with `c` — a character the `counts_lineno` gate refused was never
/// recorded and must not be taken from an earlier one. Returns the removed
/// character's flags, or `None` when nothing was removed, so the re-read in
/// `hgetc` restores both. Called from `hungetc` (src/ported/lex.rs) only.