Add issues #25, #26, #27; update #7 and #22 statuses

- #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
This commit is contained in:
Cormac Shannon
2026-03-15 19:33:19 +00:00
parent 5135b16375
commit 08df164692
5 changed files with 353 additions and 13 deletions

View File

@@ -0,0 +1,129 @@
# 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()`)