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:
68
issues/01-syntax-design.md
Normal file
68
issues/01-syntax-design.md
Normal 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.
|
||||
23
issues/02-backtick-lexing-parsing.md
Normal file
23
issues/02-backtick-lexing-parsing.md
Normal 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
|
||||
42
issues/03-command-execution-runtime.md
Normal file
42
issues/03-command-execution-runtime.md
Normal 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
26
issues/04-argv-parsing.md
Normal 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
|
||||
37
issues/05-env-var-access.md
Normal file
37
issues/05-env-var-access.md
Normal 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
25
issues/06-piping.md
Normal 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
28
issues/07-redirection.md
Normal 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.
|
||||
Reference in New Issue
Block a user