@@ -5,6 +5,7 @@ import { parse } from "../src/parser.ts";
55import { computeWordParts } from "../src/parts.ts" ;
66import type {
77 ArithmeticBinary ,
8+ ArithmeticCommandExpansion ,
89 ArithmeticExpression ,
910 ArithmeticGroup ,
1011 ArithmeticTernary ,
@@ -439,3 +440,85 @@ test("arithmetic expressions parse without errors", () => {
439440 assert . ok ( ast . commands . length > 0 , `Failed: ${ script } ` ) ;
440441 }
441442} ) ;
443+
444+ // --- Command substitution in arithmetic ---
445+
446+ test ( "command substitution in arithmetic - raw parse" , ( ) => {
447+ const e = parseArithmeticExpression ( "$(cmd) + 1" ) ! ;
448+ assert . equal ( e . type , "ArithmeticBinary" ) ;
449+ assert . equal ( bin ( e ) . operator , "+" ) ;
450+ const left = bin ( e ) . left ;
451+ assert . equal ( left . type , "ArithmeticCommandExpansion" ) ;
452+ assert . equal ( ( left as ArithmeticCommandExpansion ) . text , "$(cmd)" ) ;
453+ assert . equal ( ( left as ArithmeticCommandExpansion ) . inner , "cmd" ) ;
454+ assert . equal ( ( left as ArithmeticCommandExpansion ) . script , undefined ) ;
455+ } ) ;
456+
457+ test ( "command substitution with argument in arithmetic" , ( ) => {
458+ const e = parseArithmeticExpression ( "$(echo hello) + x" ) ! ;
459+ assert . equal ( e . type , "ArithmeticBinary" ) ;
460+ const left = bin ( e ) . left as ArithmeticCommandExpansion ;
461+ assert . equal ( left . type , "ArithmeticCommandExpansion" ) ;
462+ assert . equal ( left . text , "$(echo hello)" ) ;
463+ assert . equal ( left . inner , "echo hello" ) ;
464+ } ) ;
465+
466+ test ( "nested command substitution in arithmetic" , ( ) => {
467+ const e = parseArithmeticExpression ( "$(echo $(inner))" ) ! ;
468+ assert . equal ( e . type , "ArithmeticCommandExpansion" ) ;
469+ assert . equal ( ( e as ArithmeticCommandExpansion ) . text , "$(echo $(inner))" ) ;
470+ assert . equal ( ( e as ArithmeticCommandExpansion ) . inner , "echo $(inner)" ) ;
471+ } ) ;
472+
473+ test ( "command substitution at start and end of expression" , ( ) => {
474+ const e = parseArithmeticExpression ( "$(a) + $(b)" ) ! ;
475+ assert . equal ( e . type , "ArithmeticBinary" ) ;
476+ const left = bin ( e ) . left as ArithmeticCommandExpansion ;
477+ const right = bin ( e ) . right as ArithmeticCommandExpansion ;
478+ assert . equal ( left . type , "ArithmeticCommandExpansion" ) ;
479+ assert . equal ( right . type , "ArithmeticCommandExpansion" ) ;
480+ assert . equal ( left . text , "$(a)" ) ;
481+ assert . equal ( right . text , "$(b)" ) ;
482+ } ) ;
483+
484+ test ( "command substitution resolved in arithmetic expansion" , ( ) => {
485+ const ast = parse ( "echo $(( $(cmd) + 1 ))" ) ;
486+ const parts = computeWordParts ( "echo $(( $(cmd) + 1 ))" , getCmd ( ast ) . suffix [ 0 ] ) ! ;
487+ const arith = parts [ 0 ] as import ( "../src/types.ts" ) . ArithmeticExpansionPart ;
488+ assert . equal ( arith . type , "ArithmeticExpansion" ) ;
489+ const binary = arith . expression as ArithmeticBinary ;
490+ assert . equal ( binary . type , "ArithmeticBinary" ) ;
491+ const left = binary . left as ArithmeticCommandExpansion ;
492+ assert . equal ( left . type , "ArithmeticCommandExpansion" ) ;
493+ assert . equal ( left . inner , undefined ) ; // cleared after resolution
494+ assert . ok ( left . script ) ; // now populated
495+ assert . equal ( left . script ! . commands [ 0 ] . command . type , "Command" ) ;
496+ } ) ;
497+
498+ test ( "command substitution in arithmetic command" , ( ) => {
499+ const ast = parse ( "(( $(cmd) ))" ) ;
500+ const arithCmd = ast . commands [ 0 ] . command as import ( "../src/types.ts" ) . ArithmeticCommand ;
501+ const expr = arithCmd . expression ! ;
502+ assert . equal ( expr . type , "ArithmeticCommandExpansion" ) ;
503+ assert . ok ( ( expr as ArithmeticCommandExpansion ) . script ) ;
504+ } ) ;
505+
506+ test ( "command substitution in arithmetic for loop" , ( ) => {
507+ const ast = parse ( "for (( i = $(start); i < $(limit); i++ )); do echo $i; done" ) ;
508+ const forLoop = ast . commands [ 0 ] . command as ArithmeticFor ;
509+ assert . ok ( forLoop . initialize ) ;
510+ const initBin = forLoop . initialize as ArithmeticBinary ;
511+ assert . equal ( initBin . type , "ArithmeticBinary" ) ;
512+ assert . equal ( initBin . operator , "=" ) ;
513+ const initRight = initBin . right as ArithmeticCommandExpansion ;
514+ assert . equal ( initRight . type , "ArithmeticCommandExpansion" ) ;
515+ assert . equal ( initRight . text , "$(start)" ) ;
516+ assert . ok ( initRight . script ) ;
517+
518+ assert . ok ( forLoop . test ) ;
519+ const testBin = forLoop . test as ArithmeticBinary ;
520+ assert . equal ( testBin . type , "ArithmeticBinary" ) ;
521+ const testRight = testBin . right as ArithmeticCommandExpansion ;
522+ assert . equal ( testRight . type , "ArithmeticCommandExpansion" ) ;
523+ assert . ok ( testRight . script ) ;
524+ } ) ;
0 commit comments