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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
#![allow(unused_imports)]
use super::super::ast::Redirect;
use super::super::lexer::Lexer;
use super::super::parser::BashParser;
use super::super::semantic::SemanticAnalyzer;
use super::super::*;
#[test]
fn test_PARAM_SPEC_006_shell_options_usage_patterns() {
// DOCUMENTATION: Common $- usage patterns and purification
//
// PATTERN 1: Debugging output
// Bash: echo "Shell options: $-"
// Purification: Remove (debugging not needed in purified script)
//
// PATTERN 2: Interactive mode detection
// Bash: case "$-" in *i*) interactive_mode ;; esac
// Purification: Remove (purified scripts always non-interactive)
//
// PATTERN 3: Error mode detection
// Bash: case "$-" in *e*) echo "Exit on error" ;; esac
// Purification: Use explicit set -e, remove detection
//
// PATTERN 4: Shell identification
// Bash: if [[ "$-" == *B* ]]; then echo "Bash"; fi
// Purification: Remove (purified scripts are shell-agnostic)
//
// PATTERN 5: Trace mode detection
// Bash: case "$-" in *x*) echo "Tracing enabled" ;; esac
// Purification: Remove (tracing is runtime option, not script logic)
// Pattern 1: Debugging
let bash_debug = r#"echo $-"#;
let mut lexer = Lexer::new(bash_debug);
let tokens = lexer.tokenize().unwrap();
// Note: $- not yet supported by lexer, just verify no crash
assert!(!tokens.is_empty());
// Pattern 2: Interactive check
let bash_interactive = r#"case $- in *i*) echo Interactive ;; esac"#;
let mut lexer = Lexer::new(bash_interactive);
let tokens = lexer.tokenize().unwrap();
// Note: $- not yet supported by lexer, just verify no crash
assert!(!tokens.is_empty());
let _ = tokens;
}
#[test]
fn test_PARAM_SPEC_006_shell_options_flag_meanings() {
// DOCUMENTATION: Comprehensive guide to shell option flags
//
// INTERACTIVE FLAGS:
// i - Interactive shell (prompts enabled, job control)
// m - Monitor mode (job control, background jobs)
//
// BASH EXTENSION FLAGS:
// B - Brace expansion enabled ({a,b,c}, {1..10})
// H - History substitution enabled (!, !!, !$)
//
// INPUT/OUTPUT FLAGS:
// s - Read commands from stdin
// c - Commands from -c argument (bash -c 'cmd')
//
// ERROR HANDLING FLAGS (IMPORTANT):
// e - Exit on error (set -e, errexit)
// u - Error on unset variables (set -u, nounset)
// n - No execution (syntax check only, set -n)
//
// DEBUGGING FLAGS:
// x - Print commands before execution (set -x, xtrace)
// v - Print input lines as read (set -v, verbose)
//
// BEHAVIOR FLAGS:
// f - Disable filename expansion/globbing (set -f, noglob)
// a - Auto-export all variables (set -a, allexport)
// h - Hash commands as looked up (set -h, hashall)
// t - Exit after one command (set -t, onecmd)
//
// EXAMPLE COMBINATIONS:
// "himBH" - Interactive bash (hash, interactive, monitor, brace, history)
// "hB" - Non-interactive bash script (hash, brace)
// "ehB" - Bash script with set -e (exit on error, hash, brace)
// "h" - POSIX sh (only hash, no extensions)
//
// PURIFICATION: Don't rely on these flags
// - Use explicit set commands (set -e, set -u, set -x)
// - Don't check flags at runtime (not deterministic)
// - Remove flag detection code (use explicit behavior)
let bash_input = r#"echo $-"#;
let mut lexer = Lexer::new(bash_input);
let tokens = lexer.tokenize().unwrap();
// Note: $- not yet supported by lexer, just verify no crash
assert!(
!tokens.is_empty(),
"Lexer should produce tokens without crashing"
);
let _ = tokens;
}
#[test]
fn test_PARAM_SPEC_006_shell_options_portability() {
// DOCUMENTATION: $- portability across shells
//
// BASH (many flags):
// Interactive: "himBH" (hash, interactive, monitor, brace, history)
// Script: "hB" (hash, brace)
// Bash-specific flags: B (brace), H (history)
//
// SH/DASH (minimal flags):
// Interactive: "hi" (hash, interactive)
// Script: "h" (hash only)
// No bash extensions (no B, H flags)
//
// ASH/BUSYBOX SH (minimal):
// Similar to dash: "h" or "hi"
// No bash extensions
//
// ZSH (different flags):
// Different option names and letters
// Not compatible with bash flags
//
// POSIX GUARANTEE:
// $- is POSIX (must exist in all shells)
// BUT: Flag letters are IMPLEMENTATION-DEFINED
// Different shells use different letters for same option
// Only "h" (hashall) is somewhat universal
//
// PORTABILITY ISSUES:
// 1. Flag letters differ (bash "B" doesn't exist in sh)
// 2. Checking for specific flag is NON-PORTABLE
// 3. Interactive detection fragile (different shells, different flags)
// 4. Error mode detection fragile (all support -e, but letter varies)
//
// PURIFICATION FOR PORTABILITY:
// 1. Remove all $- references (RECOMMENDED)
// 2. Use explicit options (set -e, not check for "e" in $-)
// 3. Don't detect shell type (write portable code instead)
// 4. Don't check interactive mode (purified scripts always non-interactive)
//
// COMPARISON TABLE:
//
// | Shell | Interactive | Script | Extensions |
// |-------|-------------|--------|------------|
// | bash | himBH | hB | B, H |
// | sh | hi | h | None |
// | dash | hi | h | None |
// | ash | hi | h | None |
// | zsh | different | diff | Different |
//
// PURIFIED SCRIPT: No $- (explicit options only)
let bash_input = r#"echo $-"#;
let mut lexer = Lexer::new(bash_input);
let tokens = lexer.tokenize().unwrap();
// Note: $- not yet supported by lexer, just verify no crash
assert!(
!tokens.is_empty(),
"Lexer should produce tokens without crashing"
);
let _ = tokens;
}
// DOCUMENTATION: Comprehensive $- purification examples
//
// EXAMPLE 1: Debug output
// BEFORE: echo "Shell options: $-" -> AFTER: (removed, not needed)
//
// EXAMPLE 2: Interactive mode detection
// BEFORE: `case "$-" in *i*) echo "Interactive" ;; *) echo "Non-interactive" ;; esac`
// AFTER: echo "Non-interactive mode"
//
// EXAMPLE 3: Error handling mode
// BEFORE: `case "$-" in *e*) echo "Will exit" ;; *) set -e ;; esac`
// AFTER: set -e (explicit)
//
// EXAMPLE 4: Shell detection
// BEFORE: `if [[ "$-" == *B* ]]; then ... else ... fi`
// AFTER: mkdir -p project/src project/tests project/docs (POSIX, no detection)
//
// EXAMPLE 5: Complex script with multiple $- checks
// BEFORE: `case "$-" in *x*) TRACE=1 ;; esac` + `case "$-" in *e*) ERREXIT=1 ;; esac`
// AFTER: set -e (explicit, remove runtime introspection)
#[test]
fn test_PARAM_SPEC_006_shell_options_removal_examples() {
// Test: case statement using $- tokenizes without crash
let bash_before = concat!(
"case $- in\n",
" *i*) echo Interactive ;;\n",
" *) echo Non-interactive ;;\n",
"esac\n",
);
let mut lexer = Lexer::new(bash_before);
let tokens = lexer.tokenize().unwrap();
// Note: $- not yet supported by lexer, just verify no crash
assert!(
!tokens.is_empty(),
"Lexer should produce tokens without crashing"
);
}
#[test]
fn test_PARAM_SPEC_006_shell_options_comparison_table() {
// DOCUMENTATION: Comprehensive comparison of $- across bash, sh, and purified
//
// +-----------------+------------------------+---------------------+---------------------------+
// | Feature | Bash | POSIX sh | Purified |
// +-----------------+------------------------+---------------------+---------------------------+
// | $- support | SUPPORTED | SUPPORTED | NOT USED |
// | Common flags | himBH (interactive) | hi (interactive) | N/A |
// | | hB (script) | h (script) | |
// | Bash extensions | B (brace expansion) | None | Removed |
// | | H (history) | None | Removed |
// | Portable flags | e, u, x, v, f | e, u, x, v, f | Use explicit set commands |
// | Interactive | Check *i* in $- | Check *i* in $- | Always non-interactive |
// | Error mode | Check *e* in $- | Check *e* in $- | Use explicit set -e |
// | Trace mode | Check *x* in $- | Check *x* in $- | Use explicit set -x |
// | Shell detection | Check B/H flags | Check absence of B | No detection needed |
// | Debugging | echo "Options: $-" | echo "Options: $-" | Remove (not needed) |
// | Determinism | NON-DETERMINISTIC | NON-DETERMINISTIC | DETERMINISTIC |
// | | (runtime-specific) | (runtime-specific) | (no $- references) |
// | Portability | BASH ONLY | POSIX sh | UNIVERSAL |
// | Use case | Runtime introspection | Runtime checks | No runtime checks |
// | Best practice | Avoid in scripts | Avoid in scripts | ALWAYS remove |
// +-----------------+------------------------+---------------------+---------------------------+
//
// KEY DIFFERENCES:
//
// 1. Bash: Many flags (B, H are bash-specific)
// 2. sh: Minimal flags (no bash extensions)
// 3. Purified: NO $- REFERENCES (explicit options only)
//
// PURIFICATION PRINCIPLES:
//
// 1. Remove all $- references (runtime introspection not needed)
// 2. Use explicit set commands (set -e, set -u, set -x)
// 3. Don't detect shell type (write portable code)
// 4. Don't check interactive mode (scripts always non-interactive)
// 5. Don't check error mode (use explicit set -e)
//
// RATIONALE:
//
// $- exposes RUNTIME CONFIGURATION, not SCRIPT LOGIC
// Purified scripts should be EXPLICIT about behavior
// Checking $- makes scripts NON-DETERMINISTIC
// Different invocations = different flags = different behavior
let bash_input = r#"echo $-"#;
let mut lexer = Lexer::new(bash_input);
let tokens = lexer.tokenize().unwrap();
// Note: $- not yet supported by lexer, just verify no crash
assert!(
!tokens.is_empty(),
"Lexer should produce tokens without crashing"
);
let _ = tokens;
}
// EXTREME TDD - RED Phase: Test for loop with multiple values
// This test is EXPECTED TO FAIL until parser enhancement is implemented
// Bug: Parser cannot handle `for i in 1 2 3; do` (expects single value)
// Error: UnexpectedToken { expected: "Do", found: "Some(Number(2))", line: X }
#[test]
fn test_for_loop_with_multiple_values() {
let script = r#"
for i in 1 2 3; do
echo "$i"
done
"#;
let mut parser = BashParser::new(script).unwrap();
let result = parser.parse();
assert!(
result.is_ok(),
"For loop with multiple values should parse successfully: {:?}",
result.err()
);
let ast = result.unwrap();
let has_for = ast
.statements
.iter()
.any(|s| matches!(s, BashStmt::For { .. }));
assert!(has_for, "AST should contain a for loop");
}
// EXTREME TDD - Test for while loop with semicolon before do
// Bug was: Parser could not handle `while [ condition ]; do` (expected do immediately after condition)
// Fixed: Parser now optionally consumes semicolon before 'do' keyword (PARSER-ENH-003)
#[test]
fn test_while_loop_with_semicolon_before_do() {
let script = r#"
x=5
while [ "$x" = "5" ]; do
echo "looping"
done
"#;
let mut parser = BashParser::new(script).unwrap();
let result = parser.parse();
assert!(
result.is_ok(),
"While loop with semicolon before do should parse successfully: {:?}",
result.err()
);
let ast = result.unwrap();
let has_while = ast
.statements
.iter()
.any(|s| matches!(s, BashStmt::While { .. }));
assert!(has_while, "AST should contain a while loop");
}
// EXTREME TDD - RED Phase: Test for arithmetic expansion $((expr))
// This is P0 blocker documented in multiple locations
// Bug: Parser cannot handle arithmetic expansion like y=$((y - 1))
// Expected error: InvalidSyntax or UnexpectedToken when parsing $((...))
// GREEN phase complete - lexer + parser implemented with proper operator precedence
#[test]
fn test_arithmetic_expansion_basic() {
let script = r#"
x=5
y=$((x + 1))
echo "$y"
"#;
let mut parser = BashParser::new(script).unwrap();
let result = parser.parse();
assert!(
result.is_ok(),
"Arithmetic expansion should parse successfully: {:?}",
result.err()
);
let ast = result.unwrap();
// Verify we have an assignment with arithmetic expansion
let has_arithmetic_assignment = ast.statements.iter().any(|s| {
matches!(s, BashStmt::Assignment { value, .. }
if matches!(value, BashExpr::Arithmetic(_)))
});
assert!(
has_arithmetic_assignment,
"AST should contain arithmetic expansion in assignment"
);
}
#[test]
include!("part5_s5_arithmetic_e.rs");