# 27 — Subcommand syntax in commands **Status:** done ## Problem Running a command inside another command is unnecessarily verbose: ```lua `ls ${`pwd`.stdout}` ``` The inner backtick returns a result table (`{code, stdout, stderr}`), so `.stdout` is required to extract the string. This is clunky compared to other shells. ## Proposed syntax: `$(cmd)` Use `$()` for inline subcommands, consistent with the existing `$` interpolation family: ```lua `ls $(pwd)` !echo $(whoami) `tar -czf $(date +%F).tar.gz src/` ``` This is consistent with existing lush syntax: | Syntax | Context | Meaning | |--------|---------|---------| | `$VAR` | Lua code | `getenv("VAR")` | | `${expr}` | command body | interpolate Lua expression | | **`$(cmd)`** | command body | **run subcommand, insert stdout** | ### Comparison with other shells | Shell | Syntax | |-------|--------| | bash | `$(cmd)` | | fish | `(cmd)` | | lush current | `` `ls ${`pwd`.stdout}` `` | | **lush proposed** | `` `ls $(pwd)` `` | ## Behavior `$(cmd)` runs a shell command (same as backtick) and inserts its stdout into the outer command, with trailing newline stripped. ```lua `ls $(pwd)` -- list files in pwd's output !echo $(whoami)@$(hostname) -- multiple subcommands `echo $(ls $(pwd))` -- nested: inner runs first ``` `$(cmd)` is **not** for Lua expressions — that's what `${expr}` is for. `$()` runs a shell command; `${}` evaluates Lua. ### Nesting Nested subcommands are supported: ```lua `echo $(ls $(pwd))` ``` This works naturally because `$(cmd)` enters command parsing, which can itself contain `$()`. ## Syntax clash analysis Currently in `read_command_body()` (`llex.c:508`), `$` followed by anything other than `{` saves a literal `$`. Adding `(` as a second trigger alongside `{` is a minimal change. No conflicts: - `$VAR` in command mode is currently a literal `$` + `VAR` (see issue #25) — not affected - `${expr}` continues to work unchanged - Literal `$(` in commands is not meaningful today (falls through as literal text) ## Implementation sketch ### Lexer (`llex.c`) In `read_command_body()`, extend the `$` case to also trigger on `(`. This starts a new command body parse (not a Lua expression like `${}`): ```c case '$': { next(ls); if (ls->current == '{') { next(ls); /* skip '{' */ /* existing ${expr} interpolation path */ seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff), luaZ_bufflen(ls->buff)); ls->saved_cmd_mode = ls->cmd_mode; return interactive ? TK_INTERACTIVE : TK_COMMAND; } else if (ls->current == '(') { next(ls); /* skip '(' */ /* subcommand: start a new command parse, closed by ')' */ seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff), luaZ_bufflen(ls->buff)); ls->saved_cmd_mode = ls->cmd_mode; /* signal parser that this is a subcommand, not a Lua expr */ ... return interactive ? TK_INTERACTIVE : TK_COMMAND; } else { save(ls, '$'); } break; } ``` The `$()` body is parsed as a command (like backtick), terminated by `)` instead of `` ` ``. The result is run via `lushCmd_command` and `.stdout` is extracted with trailing newline stripped. ### Parser (`lparser.c`) The parser needs to distinguish `$()` from `${}`: - `${expr}` → parse Lua expression, `tostring()` the result (existing behavior) - `$(cmd)` → parse as a command (like backtick), run it, extract `.stdout`, strip trailing `\n` ### Lexer state (`llex.h`) Track whether the current interpolation is a subcommand (`$(`) or expression (`${`) so the parser knows which path to take. ## Files affected | File | Changes | |------|---------| | `llex.h` | Add field to `LexState` to distinguish `$()` from `${}` | | `llex.c` | Extend `$` case in `read_command_body()`, handle `)` as command terminator | | `lparser.c` | Add subcommand path: parse as command, extract `.stdout` | ## Related - Issue #25 — `$VAR` expansion in commands (also touches `$` handling in `read_command_body()`)