@@ -2475,6 +2475,11 @@ impl Interpreter {
24752475 return self . execute_getopts ( & args, & command. redirects ) . await ;
24762476 }
24772477
2478+ // Handle `mapfile`/`readarray` - needs direct access to arrays
2479+ if name == "mapfile" || name == "readarray" {
2480+ return self . execute_mapfile ( & args, stdin. as_deref ( ) ) . await ;
2481+ }
2482+
24782483 // Check for builtins
24792484 if let Some ( builtin) = self . builtins . get ( name) {
24802485 let ctx = builtins:: Context {
@@ -2854,6 +2859,53 @@ impl Interpreter {
28542859
28552860 /// Execute the `getopts` builtin (POSIX option parsing).
28562861 ///
2862+ /// Execute mapfile/readarray builtin — reads lines into an indexed array.
2863+ /// Handled inline because it needs direct access to self.arrays.
2864+ async fn execute_mapfile (
2865+ & mut self ,
2866+ args : & [ String ] ,
2867+ stdin_data : Option < & str > ,
2868+ ) -> Result < ExecResult > {
2869+ let mut trim_trailing = false ; // -t: strip trailing newlines
2870+ let mut array_name = "MAPFILE" . to_string ( ) ;
2871+ let mut positional = Vec :: new ( ) ;
2872+
2873+ for arg in args {
2874+ match arg. as_str ( ) {
2875+ "-t" => trim_trailing = true ,
2876+ a if a. starts_with ( '-' ) => { } // skip unknown flags
2877+ _ => positional. push ( arg. clone ( ) ) ,
2878+ }
2879+ }
2880+
2881+ if let Some ( name) = positional. first ( ) {
2882+ array_name = name. clone ( ) ;
2883+ }
2884+
2885+ let input = stdin_data. unwrap_or ( "" ) ;
2886+
2887+ // Clear existing array
2888+ self . arrays . remove ( & array_name) ;
2889+
2890+ // Split into lines and populate array
2891+ if !input. is_empty ( ) {
2892+ let mut arr = HashMap :: new ( ) ;
2893+ for ( idx, line) in input. lines ( ) . enumerate ( ) {
2894+ let value = if trim_trailing {
2895+ line. to_string ( )
2896+ } else {
2897+ format ! ( "{}\n " , line)
2898+ } ;
2899+ arr. insert ( idx, value) ;
2900+ }
2901+ if !arr. is_empty ( ) {
2902+ self . arrays . insert ( array_name, arr) ;
2903+ }
2904+ }
2905+
2906+ Ok ( ExecResult :: ok ( String :: new ( ) ) )
2907+ }
2908+
28572909 /// Usage: `getopts optstring name [args...]`
28582910 ///
28592911 /// Parses options from positional params (or `args`).
@@ -4416,9 +4468,12 @@ impl Interpreter {
44164468 fn expand_arithmetic_vars ( & self , expr : & str ) -> String {
44174469 let mut result = String :: new ( ) ;
44184470 let mut chars = expr. chars ( ) . peekable ( ) ;
4471+ // Track whether we're in a numeric literal context (after # or 0x)
4472+ let mut in_numeric_literal = false ;
44194473
44204474 while let Some ( ch) = chars. next ( ) {
44214475 if ch == '$' {
4476+ in_numeric_literal = false ;
44224477 // Handle $var syntax (common in arithmetic)
44234478 let mut name = String :: new ( ) ;
44244479 while let Some ( & c) = chars. peek ( ) {
@@ -4438,7 +4493,26 @@ impl Interpreter {
44384493 } else {
44394494 result. push ( ch) ;
44404495 }
4496+ } else if ch == '#' {
4497+ // base#value syntax: digits before # are base, chars after are literal digits
4498+ result. push ( ch) ;
4499+ in_numeric_literal = true ;
4500+ } else if in_numeric_literal && ( ch. is_ascii_alphanumeric ( ) || ch == '_' ) {
4501+ // Part of a base#value literal — don't expand as variable
4502+ result. push ( ch) ;
4503+ } else if ch. is_ascii_digit ( ) {
4504+ result. push ( ch) ;
4505+ // Check for 0x/0X hex prefix
4506+ if ch == '0' {
4507+ if let Some ( & next) = chars. peek ( ) {
4508+ if next == 'x' || next == 'X' {
4509+ result. push ( chars. next ( ) . unwrap ( ) ) ;
4510+ in_numeric_literal = true ;
4511+ }
4512+ }
4513+ }
44414514 } else if ch. is_ascii_alphabetic ( ) || ch == '_' {
4515+ in_numeric_literal = false ;
44424516 // Could be a variable name
44434517 let mut name = String :: new ( ) ;
44444518 name. push ( ch) ;
@@ -4457,6 +4531,7 @@ impl Interpreter {
44574531 result. push_str ( & value) ;
44584532 }
44594533 } else {
4534+ in_numeric_literal = false ;
44604535 result. push ( ch) ;
44614536 }
44624537 }
@@ -4688,17 +4763,28 @@ impl Interpreter {
46884763 }
46894764 }
46904765
4691- // Multiplication/Division/Modulo (higher precedence)
4766+ // Multiplication/Division/Modulo (higher precedence, skip ** which is power )
46924767 depth = 0 ;
46934768 for i in ( 0 ..chars. len ( ) ) . rev ( ) {
46944769 match chars[ i] {
46954770 '(' => depth += 1 ,
46964771 ')' => depth -= 1 ,
4697- '*' | '/' | '%' if depth == 0 => {
4772+ '*' if depth == 0 => {
4773+ // Skip ** (power operator handled below)
4774+ if i + 1 < chars. len ( ) && chars[ i + 1 ] == '*' {
4775+ continue ;
4776+ }
4777+ if i > 0 && chars[ i - 1 ] == '*' {
4778+ continue ;
4779+ }
4780+ let left = self . parse_arithmetic_impl ( & expr[ ..i] , arith_depth + 1 ) ;
4781+ let right = self . parse_arithmetic_impl ( & expr[ i + 1 ..] , arith_depth + 1 ) ;
4782+ return left * right;
4783+ }
4784+ '/' | '%' if depth == 0 => {
46984785 let left = self . parse_arithmetic_impl ( & expr[ ..i] , arith_depth + 1 ) ;
46994786 let right = self . parse_arithmetic_impl ( & expr[ i + 1 ..] , arith_depth + 1 ) ;
47004787 return match chars[ i] {
4701- '*' => left * right,
47024788 '/' => {
47034789 if right != 0 {
47044790 left / right
@@ -4720,6 +4806,62 @@ impl Interpreter {
47204806 }
47214807 }
47224808
4809+ // Exponentiation ** (right-associative, higher precedence than */%)
4810+ depth = 0 ;
4811+ for i in 0 ..chars. len ( ) {
4812+ match chars[ i] {
4813+ '(' => depth += 1 ,
4814+ ')' => depth -= 1 ,
4815+ '*' if depth == 0 && i + 1 < chars. len ( ) && chars[ i + 1 ] == '*' => {
4816+ let left = self . parse_arithmetic_impl ( & expr[ ..i] , arith_depth + 1 ) ;
4817+ // Right-associative: parse from i+2 onward (may contain more **)
4818+ let right = self . parse_arithmetic_impl ( & expr[ i + 2 ..] , arith_depth + 1 ) ;
4819+ return left. pow ( right as u32 ) ;
4820+ }
4821+ _ => { }
4822+ }
4823+ }
4824+
4825+ // Unary negation and bitwise NOT
4826+ if let Some ( rest) = expr. strip_prefix ( '-' ) {
4827+ let rest = rest. trim ( ) ;
4828+ if !rest. is_empty ( ) {
4829+ return -self . parse_arithmetic_impl ( rest, arith_depth + 1 ) ;
4830+ }
4831+ }
4832+ if let Some ( rest) = expr. strip_prefix ( '~' ) {
4833+ let rest = rest. trim ( ) ;
4834+ if !rest. is_empty ( ) {
4835+ return !self . parse_arithmetic_impl ( rest, arith_depth + 1 ) ;
4836+ }
4837+ }
4838+ if let Some ( rest) = expr. strip_prefix ( '!' ) {
4839+ let rest = rest. trim ( ) ;
4840+ if !rest. is_empty ( ) {
4841+ let val = self . parse_arithmetic_impl ( rest, arith_depth + 1 ) ;
4842+ return if val == 0 { 1 } else { 0 } ;
4843+ }
4844+ }
4845+
4846+ // Base conversion: base#value (e.g., 16#ff = 255, 2#1010 = 10)
4847+ if let Some ( hash_pos) = expr. find ( '#' ) {
4848+ let base_str = & expr[ ..hash_pos] ;
4849+ let value_str = & expr[ hash_pos + 1 ..] ;
4850+ if let Ok ( base) = base_str. parse :: < u32 > ( ) {
4851+ if ( 2 ..=64 ) . contains ( & base) {
4852+ return i64:: from_str_radix ( value_str, base) . unwrap_or ( 0 ) ;
4853+ }
4854+ }
4855+ }
4856+
4857+ // Hex (0x...), octal (0...) literals
4858+ if expr. starts_with ( "0x" ) || expr. starts_with ( "0X" ) {
4859+ return i64:: from_str_radix ( & expr[ 2 ..] , 16 ) . unwrap_or ( 0 ) ;
4860+ }
4861+ if expr. starts_with ( '0' ) && expr. len ( ) > 1 && expr. chars ( ) . all ( |c| c. is_ascii_digit ( ) ) {
4862+ return i64:: from_str_radix ( & expr[ 1 ..] , 8 ) . unwrap_or ( 0 ) ;
4863+ }
4864+
47234865 // Parse as number
47244866 expr. trim ( ) . parse ( ) . unwrap_or ( 0 )
47254867 }
0 commit comments