Add project issues for lush (Lua + shell) features

Defines syntax decisions and implementation plan across 7 issues:
- backtick command execution returning {code, stdout, stderr}
- ${expr} interpolation in backticks
- $SIGIL env var read/write
- argv parsing, piping, and redirection (future)
This commit is contained in:
Cormac Shannon
2026-02-18 19:08:51 +00:00
parent 4cf498210e
commit db6f39a2a4
7 changed files with 249 additions and 0 deletions

View File

@@ -0,0 +1,68 @@
# 01 — Design and finalize syntax for all new features
**Status:** resolved
**Blocks:** #02, #04, #05
## Backtick command execution
Backtick expressions return a plain table with three fields:
```lua
local output = `ls -lha`
print(
output.code,
output.stdout,
output.stderr
)
```
- `output.stdout` — captured stdout as a string
- `output.stderr` — captured stderr as a string
- `output.code` — integer exit code
- No `__tostring` metamethod — access `.stdout` explicitly
### String interpolation
Backticks support `${expr}` interpolation:
```lua
local dir = "/tmp"
local result = `ls -lha ${dir}`
local pattern = "*.lua"
local result2 = `find ${dir} -name ${pattern}`
```
`${expr}` evaluates the Lua expression, converts to string, and splices it into the command string before argv parsing.
## Environment variables — `$` sigil
```lua
local dir = $PWD -- getenv("PWD") → string or nil
$PWD = "/" -- setenv("PWD", "/")
$MY_ENV_VAR = "data" -- setenv("MY_ENV_VAR", "data")
```
- `$NAME` as an expression → `getenv("NAME")`, returns string or `nil`
- `$NAME = expr` as a statement → `setenv("NAME", tostring(expr))`
- `$` followed by an identifier is a new token type in the lexer
- Note: `$NAME` (env var) is distinct from `${expr}` inside backticks (interpolation)
## Piping (future)
```lua
local result = `ls -l` | `grep ".lua"`
```
Challenge: `|` conflicts with Lua's bitwise OR. Pipes only valid between command expressions.
## Redirection (future)
```lua
`ls -l` > "output.txt"
`ls -l` >> "output.txt"
`cmd` 2> "err.txt"
`cmd` 2>&1
`cmd` < "input.txt"
```
Challenge: `>` / `>>` conflict with Lua's comparison/shift operators. Only valid in command context.

View File

@@ -0,0 +1,23 @@
# 02 — Complete backtick command lexing and parsing
**Status:** open
**Blocked by:** #01
**Blocks:** #03
Backtick lexing is partially implemented:
- `llex.c`: TK_COMMAND token lexed via `read_string()` on backtick
- `llex.h`: TK_COMMAND enum added
- `lparser.c`: TK_COMMAND handled in `simpleexp()` (as string) and `suffixedexp()` (as func args)
## Remaining work
- Verify the lexer handles edge cases (escaped backticks, newlines, nested quotes)
- Parser must emit code that produces a result table (`{code, stdout, stderr}`) at runtime, not just a string literal
- May need a new opcode (e.g. `OP_COMMAND`) or compile as a call to a built-in execution function
- Add string interpolation inside backticks if decided in #01
## Files touched
- `llex.c` / `llex.h`
- `lparser.c`
- Possibly `lopcodes.h` / `lopcodes.c` if adding a new opcode

View File

@@ -0,0 +1,42 @@
# 03 — Implement direct command execution runtime (no shell)
**Status:** open
**Blocked by:** #02
**Blocks:** #06, #07
Implement the C runtime that executes commands when a backtick expression is evaluated.
**Critical constraint:** do NOT spawn a shell (no `bash`, `dash`, `sh`, `system()`, `popen()`).
## Return value
Backtick expressions return a table:
```lua
local r = `ls -lha`
r.code -- exit code (integer)
r.stdout -- captured stdout (string)
r.stderr -- captured stderr (string)
```
## Implementation approach
- `fork()` + `execvp()` on Unix/macOS
- Set up two pipes: one for stdout, one for stderr
- In child: `dup2()` pipes onto fd 1 and fd 2, then `execvp(argv[0], argv)`
- In parent: read both pipes into buffers, `waitpid()` for exit status
- Build a Lua table with `code`, `stdout`, `stderr` fields and push onto stack
- Depends on #04 (argv parsing) to tokenize the command string into argv[]
## Error handling
- Command not found → set `code` to an error value, `stderr` to error message
- Permission denied → same
- `fork()` failure → raise a Lua error
## Open questions
- New file (`lcmd.c` / `lcmd.h`) or add to an existing lib?
- Signal handling during child execution
- Binary output — return raw string bytes as-is?
- Set `__tostring` metamethod on result table?

26
issues/04-argv-parsing.md Normal file
View File

@@ -0,0 +1,26 @@
# 04 — Implement argv parsing (tokenize command strings)
**Status:** open
**Blocked by:** #01
**Blocks:** #06
Since we're not using a shell, we need our own command-line tokenizer to split a command string into `argv[]`.
## Must handle
- Simple whitespace splitting: `ls -l /tmp``["ls", "-l", "/tmp"]`
- Single-quoted strings: `echo 'hello world'``["echo", "hello world"]`
- Double-quoted strings with escapes: `echo "hello \"world\""`
- Backslash escaping outside quotes: `echo hello\ world`
- Mixed quoting: `echo "it's a test"`
## May handle (depending on #01 decisions)
- Variable/expression interpolation: `echo $HOME` or `echo ${dir}`
- Glob expansion: `ls *.lua` (could also defer to a later issue)
## Implementation
- Standalone C module, likely `lcmd_parse.c` or part of `lcmd.c`
- Returns a `char **argv` (NULL-terminated) + argc count
- Must handle memory allocation/cleanup

View File

@@ -0,0 +1,37 @@
# 05 — Implement environment variable access (read/write)
**Status:** open
**Blocked by:** #01
Add syntax for reading and writing environment variables directly from Lua.
## Option 1 — `export` keyword
```lua
local var = "my data"
export var -- exports existing local to env
export other_var = "my data" -- creates + exports
```
- New reserved word `export` in lexer
- `export var` looks up the local's value by name and calls `setenv()`
- `export x = val` combines declaration with `setenv()`
## Option 2 — `$` sigil
```lua
local dir = $PWD -- getenv("PWD")
$PWD = "/" -- setenv("PWD", "/")
$MY_ENV_VAR = "data" -- setenv("MY_ENV_VAR", "data")
```
- `$NAME` as expression → `getenv("NAME")`, returns string or `nil`
- `$NAME = expr` as statement → `setenv("NAME", tostring(expr))`
- New token type in lexer for `$` + identifier
## Implementation touches
- `llex.c` / `llex.h` — new token(s) or reserved word
- `lparser.c` — new expression/statement rules
- Runtime calls `getenv()` / `setenv()` — standard C, no shell needed
- Non-existent env var → return `nil`

25
issues/06-piping.md Normal file
View File

@@ -0,0 +1,25 @@
# 06 — Implement piping between commands
**Status:** open (post-core)
**Blocked by:** #03, #04
## Syntax
```lua
local result = `ls -l` | `grep ".lua"`
```
## Implementation
- Connect stdout of one child to stdin of the next via `pipe()` + `dup2()`
- Build a pipeline of N processes, all forked and connected
- Only capture stdout/stderr of the **final** process
- `waitpid()` all children, return exit code of last process
## Challenge
Lua uses `|` for bitwise OR. The parser needs to disambiguate:
- `|` between two command expressions → pipe
- `|` between numeric expressions → bitwise OR
This is resolvable because the parser knows the type of each subexpression — a backtick expression is always a command.

28
issues/07-redirection.md Normal file
View File

@@ -0,0 +1,28 @@
# 07 — Implement I/O redirection for commands
**Status:** open (post-core)
**Blocked by:** #03
## Syntax
```lua
`ls -l` > "output.txt" -- redirect stdout to file
`ls -l` >> "output.txt" -- append stdout to file
`cmd` 2> "err.txt" -- redirect stderr to file
`cmd` 2>&1 -- merge stderr into stdout
`cmd` < "input.txt" -- redirect stdin from file
```
## Implementation
- Before `execvp()` in the child process, use `dup2()` to redirect file descriptors
- Parse redirection operators as part of the command syntax
- Open target files with appropriate flags:
- `>``O_WRONLY | O_CREAT | O_TRUNC`
- `>>``O_WRONLY | O_CREAT | O_APPEND`
- `<``O_RDONLY`
- `2>&1``dup2(stdout_fd, STDERR_FILENO)`
## Challenge
`>` and `>>` conflict with Lua's greater-than and right-shift operators. Like piping, these operators must only be valid in command context. The parser can disambiguate because the left-hand side is a command expression.