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
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
#![allow(clippy::unwrap_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::*;
/// Helper: parse a script and return whether parsing succeeded.
/// Used by documentation tests that only need to verify parsability.
#[test]
fn test_DIRSTACK_001_popd_not_supported() {
// DOCUMENTATION: popd command is NOT SUPPORTED (implicit state)
//
// popd pops directory from stack and changes to it:
// $ pushd /tmp
// /tmp /home/user
// $ pushd /var
// /var /tmp /home/user
// $ popd
// /tmp /home/user
// $ pwd
// /tmp
//
// NOT SUPPORTED because:
// - Depends on pushd (directory stack)
// - Implicit state management
// - Scripts should use explicit cd
// - Clearer with saved directory variable
let popd_script = r#"
pushd /tmp
pushd /var
popd
popd
"#;
let result = BashParser::new(popd_script);
if let Ok(mut parser) = result {
let parse_result = parser.parse();
assert!(
parse_result.is_ok() || parse_result.is_err(),
"popd uses implicit directory stack, NOT SUPPORTED in scripts"
);
}
// popd issues:
// - Stack underflow if used incorrectly
// - Hard to debug (what's on the stack?)
// - Explicit variables prevent errors
}
#[test]
fn test_DIRSTACK_001_dirs_command() {
// DOCUMENTATION: dirs command (display directory stack)
//
// dirs command displays the directory stack:
// $ pushd /tmp
// /tmp ~
// $ pushd /var
// /var /tmp ~
// $ dirs
// /var /tmp ~
// $ dirs -v # Numbered list
// 0 /var
// 1 /tmp
// 2 ~
//
// NOT SUPPORTED because:
// - Displays directory stack state
// - No directory stack in scripts
// - Use pwd to show current directory
let dirs_script = r#"
pushd /tmp
dirs
dirs -v
"#;
let result = BashParser::new(dirs_script);
if let Ok(mut parser) = result {
let parse_result = parser.parse();
assert!(
parse_result.is_ok() || parse_result.is_err(),
"dirs command displays directory stack, NOT SUPPORTED"
);
}
// dirs command options (all NOT SUPPORTED):
// -c: Clear directory stack
// -l: Print with full pathnames
// -p: Print one per line
// -v: Print with indices
// +N: Display Nth directory (counting from left)
// -N: Display Nth directory (counting from right)
}
#[test]
fn test_DIRSTACK_001_purification_uses_explicit_cd() {
// DOCUMENTATION: Purification uses explicit cd with variables
//
// Before (with pushd/popd):
// #!/bin/bash
// pushd /tmp
// tar -czf /tmp/backup.tar.gz /home/user/data
// popd
// echo "Backup complete"
//
// After (purified, explicit cd):
// #!/bin/sh
// _prev_dir="$(pwd)"
// cd /tmp || exit 1
// tar -czf /tmp/backup.tar.gz /home/user/data
// cd "$_prev_dir" || exit 1
// printf '%s\n' "Backup complete"
//
// Benefits:
// - Explicit directory tracking
// - Clear intent (save, change, restore)
// - Error handling (|| exit 1)
// - No hidden state
let purified_explicit_cd = r#"
#!/bin/sh
_prev_dir="$(pwd)"
cd /tmp || exit 1
tar -czf /tmp/backup.tar.gz /home/user/data
cd "$_prev_dir" || exit 1
printf '%s\n' "Backup complete"
"#;
let result = BashParser::new(purified_explicit_cd);
if let Ok(mut parser) = result {
let parse_result = parser.parse();
assert!(
parse_result.is_ok() || parse_result.is_err(),
"Purified scripts use explicit cd with variables"
);
}
// Purification strategy:
// 1. Save current directory: _prev_dir="$(pwd)"
// 2. Change directory with error checking: cd /path || exit 1
// 3. Do work in new directory
// 4. Restore directory: cd "$_prev_dir" || exit 1
}
#[test]
fn test_DIRSTACK_001_pushd_popd_options() {
// DOCUMENTATION: pushd/popd options (all NOT SUPPORTED)
//
// pushd options:
// pushd - Swap top two directories
// pushd /path - Push /path and cd to it
// pushd +N - Rotate stack, bring Nth dir to top
// pushd -N - Rotate stack, bring Nth dir from bottom to top
// pushd -n /path - Push without cd
//
// popd options:
// popd - Pop top directory and cd to new top
// popd +N - Remove Nth directory (counting from left)
// popd -N - Remove Nth directory (counting from right)
// popd -n - Pop without cd
//
// All options manipulate directory stack, NOT SUPPORTED.
let pushd_options = r#"
pushd /tmp # Push and cd
pushd /var # Push and cd
pushd # Swap top two
pushd +1 # Rotate
"#;
let result = BashParser::new(pushd_options);
if let Ok(mut parser) = result {
let parse_result = parser.parse();
assert!(
parse_result.is_ok() || parse_result.is_err(),
"pushd/popd options manipulate directory stack"
);
}
// Why options don't help:
// - Still use implicit stack state
// - More complex = harder to understand
// - Explicit variables are simpler
}
#[test]
fn test_DIRSTACK_001_dirstack_variable() {
// DOCUMENTATION: DIRSTACK variable (array, NOT SUPPORTED)
//
// DIRSTACK is a bash array containing the directory stack:
// $ pushd /tmp
// $ pushd /var
// $ echo "${DIRSTACK[@]}"
// /var /tmp /home/user
// $ echo "${DIRSTACK[0]}"
// /var
// $ echo "${DIRSTACK[1]}"
// /tmp
//
// NOT SUPPORTED because:
// - Bash-specific array variable
// - Tied to pushd/popd state
// - Scripts don't use directory stack
// - No POSIX equivalent
let dirstack_var = r#"
pushd /tmp
echo "${DIRSTACK[@]}"
echo "${DIRSTACK[0]}"
"#;
let result = BashParser::new(dirstack_var);
if let Ok(mut parser) = result {
let parse_result = parser.parse();
assert!(
parse_result.is_ok() || parse_result.is_err(),
"DIRSTACK variable is Bash-specific array"
);
}
// DIRSTACK is read-only:
// - Can't modify directly
// - Only modified by pushd/popd/dirs
// - Reflects current stack state
}
#[test]
fn test_DIRSTACK_001_cd_minus_alternative() {
// DOCUMENTATION: cd - (alternative to popd, uses OLDPWD)
//
// cd - changes to previous directory (uses OLDPWD):
// $ pwd
// /home/user
// $ cd /tmp
// $ pwd
// /tmp
// $ cd -
// /home/user
// $ pwd
// /home/user
//
// cd - is better than popd because:
// - POSIX-compliant (OLDPWD is standard)
// - No stack state (simpler)
// - Only remembers one directory (sufficient)
// - Explicit and predictable
let cd_minus = r#"
cd /tmp
# do work
cd - # Return to previous directory
"#;
let result = BashParser::new(cd_minus);
if let Ok(mut parser) = result {
let parse_result = parser.parse();
assert!(
parse_result.is_ok() || parse_result.is_err(),
"cd - uses OLDPWD, simpler than popd"
);
}
// cd - advantages over pushd/popd:
// - POSIX-compliant
// - No hidden stack
// - One previous directory (usually enough)
// - More predictable behavior
}
#[test]
fn test_DIRSTACK_001_interactive_vs_script_directory_navigation() {
// DOCUMENTATION: Interactive vs script directory navigation
//
// Interactive navigation (uses pushd/popd):
// - Navigate between multiple directories
// - Directory stack for quick switching
// - pushd/popd for convenience
// - dirs to see stack
// - Useful for manual exploration
//
// Script navigation (uses explicit cd):
// - Deterministic directory changes
// - Save/restore with variables
// - cd with error checking
// - pwd to show current location
// - Explicit and traceable
let script_navigation = r#"
#!/bin/sh
# Script-style directory navigation (explicit)
# Save starting directory
start_dir="$(pwd)"
# Work in first location
cd /tmp || exit 1
printf '%s\n' "Working in /tmp"
# do work
# Work in second location
cd /var/log || exit 1
printf '%s\n' "Working in /var/log"
# do work
# Return to start
cd "$start_dir" || exit 1
printf '%s\n' "Back to $start_dir"
"#;
let result = BashParser::new(script_navigation);
if let Ok(mut parser) = result {
let parse_result = parser.parse();
assert!(
parse_result.is_ok() || parse_result.is_err(),
"Scripts use explicit cd with error checking"
);
}
// Summary:
// Interactive: pushd/popd with implicit stack
// Script: cd with explicit variables and error checking
//
// bashrs: Remove pushd/popd, use explicit cd
}
// ============================================================================
// ARRAY-002: Associative Arrays (Bash 4.0+, NOT SUPPORTED)
// ============================================================================
//
// Task: ARRAY-002 - Document associative arrays
// Status: DOCUMENTED (NOT SUPPORTED - Bash 4.0+ extension, not POSIX)
// Priority: LOW (associative arrays not in POSIX sh)
//
// Associative arrays (hash maps/dictionaries) were introduced in Bash 4.0.
// They allow key-value pairs with string keys, unlike indexed arrays.
//
// Bash behavior:
// - declare -A name: Declare associative array
// - array[key]=value: Set value for key
// - ${array[key]}: Get value for key
// - ${!array[@]}: Get all keys
// - ${array[@]}: Get all values
// - Bash 4.0+ only (2009)
//
// bashrs policy:
// - NOT SUPPORTED (Bash 4.0+ extension, not POSIX)
// - Use separate variables with consistent naming
// - Use indexed arrays if order doesn't matter
// - More portable, works on older shells
//
// Transformation:
// Bash input:
// declare -A config
// config[host]="localhost"
// config[port]="8080"
// echo "${config[host]}"
//
// Purified POSIX sh:
// config_host="localhost"
// config_port="8080"
// printf '%s\n' "$config_host"
//
// Related features:
// - Indexed arrays (ARRAY-001) - supported
// - declare -A - associative array declaration
// - readarray/mapfile - not supported (Bash 4.0+)
#[test]
fn test_ARRAY_002_associative_arrays_not_supported() {
// DOCUMENTATION: Associative arrays are NOT SUPPORTED (Bash 4.0+)
//
// Associative arrays use string keys:
// $ declare -A config
// $ config[host]="localhost"
// $ config[port]="8080"
// $ echo "${config[host]}"
// localhost
// $ echo "${!config[@]}"
// host port
//
// NOT SUPPORTED because:
// - Bash 4.0+ extension (2009)
// - Not available in POSIX sh, dash, ash
// - Not portable to older systems
// - Use separate variables instead
let assoc_array_script = r#"
declare -A config
config[host]="localhost"
config[port]="8080"
echo "${config[host]}"
"#;
let result = BashParser::new(assoc_array_script);
match result {
Ok(mut parser) => {
let parse_result = parser.parse();
assert!(
parse_result.is_ok() || parse_result.is_err(),
"Associative arrays are Bash 4.0+ only, NOT SUPPORTED"
);
}
Err(_) => {
// Parse error acceptable - Bash extension
}
}
// Why associative arrays are problematic:
// - Requires Bash 4.0+ (not available everywhere)
// - macOS ships with Bash 3.2 (2006, pre-associative arrays)
// - Alpine Linux uses ash (no associative arrays)
// - Separate variables are more portable
}
#[test]
fn test_ARRAY_002_declare_uppercase_a() {
// DOCUMENTATION: declare -A (associative array declaration)
//
// declare -A declares an associative array:
// $ declare -A map
// $ map[key1]="value1"
// $ map[key2]="value2"
// $ declare -p map
// declare -A map=([key1]="value1" [key2]="value2")
//
// NOT SUPPORTED because:
// - Bash 4.0+ only
// - No POSIX equivalent
// - Use individual variables instead
let declare_a = r#"
declare -A map
map[name]="John"
map[age]="30"
"#;
let result = BashParser::new(declare_a);
if let Ok(mut parser) = result {
let parse_result = parser.parse();
assert!(
parse_result.is_ok() || parse_result.is_err(),
"declare -A is Bash 4.0+ only, NOT SUPPORTED"
);
}
// Note: declare -a (lowercase) is for indexed arrays (supported)
// declare -A (uppercase) is for associative arrays (NOT supported)
}
#[test]
fn test_ARRAY_002_associative_array_operations() {
// DOCUMENTATION: Associative array operations (all Bash 4.0+)
//
// Operations:
// ${array[key]} - Get value for key
// ${!array[@]} - Get all keys
// ${array[@]} - Get all values
// ${#array[@]} - Get number of elements
// unset array[key] - Delete key
// [[ -v array[key] ]] - Check if key exists
//
// All operations are Bash 4.0+ only, NOT SUPPORTED.
let assoc_operations = r#"
declare -A data
data[x]="10"
data[y]="20"
echo "${data[x]}" # Get value
echo "${!data[@]}" # Get keys
echo "${data[@]}" # Get values
echo "${#data[@]}" # Get count
unset data[x] # Delete key
[[ -v data[y] ]] && echo "exists" # Check existence
"#;
let result = BashParser::new(assoc_operations);
if let Ok(mut parser) = result {
let parse_result = parser.parse();
assert!(
parse_result.is_ok() || parse_result.is_err(),
"Associative array operations are Bash 4.0+ only"
);
}
// All these operations require:
// - Bash 4.0+ (not available on older systems)
// - No POSIX equivalent
// - Use separate variables for portability
}