- #25: $VAR expansion in commands - #26: Shell abbreviations and user-extensible builtins - #27: $(cmd) subcommand syntax - #22: Updated with implementation details (in progress) - #7: Standardize status label
130 lines
4.0 KiB
Markdown
130 lines
4.0 KiB
Markdown
# 27 — Subcommand syntax in commands
|
|
|
|
**Status:** open
|
|
|
|
## 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()`)
|