@@ -2388,11 +2388,36 @@ impl<'a> Parser<'a> {
23882388 if chars. peek ( ) == Some ( & '[' ) {
23892389 chars. next ( ) ; // consume '['
23902390 let mut index = String :: new ( ) ;
2391+ // Track nesting so nested ${...} containing
2392+ // brackets (e.g. ${#arr[@]}) don't prematurely
2393+ // close the subscript.
2394+ let mut bracket_depth: i32 = 0 ;
2395+ let mut brace_depth: i32 = 0 ;
23912396 while let Some ( & c) = chars. peek ( ) {
2392- if c == ']' {
2397+ if c == ']' && bracket_depth == 0 && brace_depth == 0 {
23932398 chars. next ( ) ;
23942399 break ;
23952400 }
2401+ match c {
2402+ '[' => bracket_depth += 1 ,
2403+ ']' => bracket_depth -= 1 ,
2404+ '$' => {
2405+ index. push ( chars. next ( ) . unwrap ( ) ) ;
2406+ if chars. peek ( ) == Some ( & '{' ) {
2407+ brace_depth += 1 ;
2408+ index. push ( chars. next ( ) . unwrap ( ) ) ;
2409+ continue ;
2410+ }
2411+ continue ;
2412+ }
2413+ '{' => brace_depth += 1 ,
2414+ '}' => {
2415+ if brace_depth > 0 {
2416+ brace_depth -= 1 ;
2417+ }
2418+ }
2419+ _ => { }
2420+ }
23962421 index. push ( chars. next ( ) . unwrap ( ) ) ;
23972422 }
23982423 // Strip surrounding quotes from index (e.g. "foo" -> foo)
@@ -3045,4 +3070,44 @@ mod tests {
30453070 "non-empty while body should be accepted"
30463071 ) ;
30473072 }
3073+
3074+ /// Issue #600: Subscript reader must handle nested ${...} containing brackets.
3075+ #[ test]
3076+ fn test_nested_expansion_in_array_subscript ( ) {
3077+ // ${arr[$RANDOM % ${#arr[@]}]} must parse without error.
3078+ // The subscript contains ${#arr[@]} which has its own [ and ].
3079+ let parser = Parser :: new ( "echo ${arr[$RANDOM % ${#arr[@]}]}" ) ;
3080+ let script = parser. parse ( ) . unwrap ( ) ;
3081+ assert_eq ! ( script. commands. len( ) , 1 ) ;
3082+ if let Command :: Simple ( cmd) = & script. commands [ 0 ] {
3083+ assert_eq ! ( cmd. name. to_string( ) , "echo" ) ;
3084+ assert_eq ! ( cmd. args. len( ) , 1 ) ;
3085+ // The arg should contain an ArrayAccess with the full nested index
3086+ let arg = & cmd. args [ 0 ] ;
3087+ let has_array_access = arg. parts . iter ( ) . any ( |p| {
3088+ matches ! (
3089+ p,
3090+ WordPart :: ArrayAccess { name, index }
3091+ if name == "arr" && index. contains( "${#arr[@]}" )
3092+ )
3093+ } ) ;
3094+ assert ! (
3095+ has_array_access,
3096+ "expected ArrayAccess with nested index, got: {:?}" ,
3097+ arg. parts
3098+ ) ;
3099+ } else {
3100+ panic ! ( "expected simple command" ) ;
3101+ }
3102+ }
3103+
3104+ /// Assignment with nested subscript must parse (previously caused fuel exhaustion).
3105+ #[ test]
3106+ fn test_assignment_nested_subscript_parses ( ) {
3107+ let parser = Parser :: new ( "x=${arr[$RANDOM % ${#arr[@]}]}" ) ;
3108+ assert ! (
3109+ parser. parse( ) . is_ok( ) ,
3110+ "assignment with nested subscript should parse"
3111+ ) ;
3112+ }
30483113}
0 commit comments