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
//! Loop execution for zshrs
//!
//! Port from zsh/Src/loop.c (802 lines)
//!
//! # of nested loops we are in // c:33
//! # of continue levels // c:38
//! # of break levels // c:43
//!
//! In C, loop.c contains execfor, execwhile, execif, execcase, execselect,
//! execrepeat, and exectry as separate functions operating on bytecode.
//! In Rust, all of these are implemented as match arms in
//! ShellExecutor::execute_compound() in exec.rs, operating on the typed AST
//! (CompoundCommand::For, While, If, Case, Select, Repeat, Try).
//!
//! This module provides the loop state management and helper functions
//! that support the executor's loop implementation.
use ;
use Write;
/// Number of nested loops.
/// Port of the global `loops` counter from Src/loop.c — every
/// `execfor`/`execwhile`/`execrepeat`/`execselect` entry bumps it
/// and decrements on exit.
static LOOP_DEPTH: AtomicI32 = new;
/// Continue flag / level.
/// Port of the global `contflag` from Src/loop.c — set by the
/// `continue` builtin (Src/builtin.c:bin_break) and consumed by
/// the loop body's exit check.
static CONT_FLAG: AtomicI32 = new;
/// Break level.
/// Port of the global `breaks` counter from Src/loop.c — set by
/// the `break` builtin (Src/builtin.c:bin_break) and tested by
/// each enclosing loop on exit.
static BREAK_LEVEL: AtomicI32 = new;
// Note: dead `LoopState` aggregate (and impl/tests) deleted per
// PORT_PLAN Phase 2. It was a Rust-only invention that double-tracked
// the same data already living in the file-statics LOOP_DEPTH /
// CONT_FLAG / BREAK_LEVEL above (and on `ShellExecutor.breaking` /
// `ShellExecutor.continuing` in src/exec.rs:572-573). Zero callers
// outside its own test module.
//
// C source's actual loop-control file-globals at `Src/loop.c`:
//
// int loops; // line 36
// mod_export int contflag; // line 41
// mod_export volatile int breaks; // line 46
//
// All `mod_export` (cross-compilation-unit), so they're PORT_PLAN
// Phase 3 bucket-2 (Arc<RwLock>) work. The canonical C-named ports
// (LOOPS / CONTFLAG / BREAKS) live in `src/ported/builtin.rs:3657-3659`
// where the bin_break dispatcher consults them; the local
// LOOP_DEPTH / CONT_FLAG / BREAK_LEVEL above are this file's
// internal mirrors.
// And this is used to print select lists. // c:347
/// Select-menu display.
/// Port of `selectlist(LinkList l, size_t start)` from Src/loop.c:347 — formats the
/// numbered menu the C source uses for `select var in words`. Picks
/// columns automatically when `columns == 0`, mirroring the C
/// source's terminal-width auto-detection.
/// WARNING: param names don't match C — Rust=(items, start) vs C=(l, start)
// Note: dead `ForIterator` / `CForState` / `TryState` aggregates
// removed per PORT_PLAN Phase 2. None had production callers (only
// internal test references). The actual control flow is lowered in
// the fusevm compiler — every `for`/`while`/`select`/`repeat`/`try`
// AST node becomes a fusevm Op (see `src/extensions/compile_zsh.rs`).
//
// C source's relevant try-block file-globals (loop.c:719-727):
//
// zlong try_errflag = -1; // line 719 (TRY_BLOCK_ERROR)
// zlong try_interrupt = -1; // line 727 (TRY_BLOCK_INTERRUPT)
//
// Exported via `IPDEF6` paramdef in `Src/params.c:364`, so they're
// cross-compilation-unit globals → PORT_PLAN Phase 3 bucket-2
// (Arc<RwLock>) work, not the Phase 2 bucket-1 (thread_local!) wave.
// ===========================================================
// Tree-walker control-flow dispatch entries.
//
// In zsh these seven functions are bytecode-tree walkers — each
// consumes a `Wordcode`/`Estate` cursor and recursively invokes
// `execlist()` for nested clauses. They run during the legacy
// `tree_walker` execution path.
//
// zshrs replaces the tree walker entirely with fusevm bytecode
// (see `tree_walker_absent.rs` / `no_tree_walker_dispatch.rs`
// invariant tests), so these entries exist to satisfy ABI/name
// parity. The actual control-flow lowering happens in the
// fusevm compiler (`crate::fusevm::compile`) where every
// `for`/`while`/`if`/`case`/`select`/`repeat`/`try` AST node
// becomes a fusevm `Op`.
// ===========================================================
// The seven entries below are zsh's tree-walker dispatch handlers
// from `Src/loop.c`. zshrs replaces the tree walker entirely with
// fusevm bytecode — every `for`/`while`/`if`/`case`/`select`/
// `repeat`/`try` AST node lowers to a fusevm Op in
// `src/extensions/compile_zsh.rs`. These entries exist purely for
// C-name parity (drift gate enforces every Rust fn maps to a C fn).
//
// The 96-test architectural invariant in `tree_walker_absent.rs` +
// `no_tree_walker_dispatch.rs` proves these are never reached in
// production. Each body is `unreachable!()` so ANY caller fails
// loudly rather than silently returning 0 — if a port regresses
// the bytecode lowering, we want the test suite to crash, not pass.
//
// Faithful per-fn port of the C bodies is intentionally NOT done:
// they read `Wordcode` / `Estate` cursors that zshrs doesn't model.
// The semantic equivalent lives in:
// execfor → compile_zsh.rs::compile_for
// execselect → compile_zsh.rs::compile_select
// execwhile → compile_zsh.rs::compile_while
// execrepeat → compile_zsh.rs::compile_repeat
// execif → compile_zsh.rs::compile_if
// execcase → compile_zsh.rs::compile_case
// exectry → compile_zsh.rs::compile_try
/// Port of `execfor(Estate state, int do_exec)` from `Src/loop.c:50`. See module-level note:
/// fusevm bytecode replaces the tree walker; this entry is
/// `unreachable!()` to crash if regressed.
/// WARNING: param names don't match C — Rust=(_do_exec) vs C=(state, do_exec)
/// Port of `execselect(Estate state, UNUSED(int do_exec))` from `Src/loop.c:217`.
/// WARNING: param names don't match C — Rust=(_do_exec) vs C=(state, do_exec)
/// Port of `execwhile(Estate state, UNUSED(int do_exec))` from `Src/loop.c:413`.
/// WARNING: param names don't match C — Rust=(_do_exec) vs C=(state, do_exec)
/// Port of `execrepeat(Estate state, UNUSED(int do_exec))` from `Src/loop.c:499`.
/// WARNING: param names don't match C — Rust=(_do_exec) vs C=(state, do_exec)
/// Port of `execif(Estate state, int do_exec)` from `Src/loop.c:553`.
/// WARNING: param names don't match C — Rust=(_do_exec) vs C=(state, do_exec)
/// Port of `execcase(Estate state, int do_exec)` from `Src/loop.c:600`.
/// WARNING: param names don't match C — Rust=(_do_exec) vs C=(state, do_exec)
/// Port of `exectry(Estate state, int do_exec)` from `Src/loop.c:735`.
/// WARNING: param names don't match C — Rust=(_do_exec) vs C=(state, do_exec)