diff --git a/issues/01-syntax-design.md b/issues/01-syntax-design.md new file mode 100644 index 00000000..ea69d4e4 --- /dev/null +++ b/issues/01-syntax-design.md @@ -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. diff --git a/issues/02-backtick-lexing-parsing.md b/issues/02-backtick-lexing-parsing.md new file mode 100644 index 00000000..2a6d05bf --- /dev/null +++ b/issues/02-backtick-lexing-parsing.md @@ -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 diff --git a/issues/03-command-execution-runtime.md b/issues/03-command-execution-runtime.md new file mode 100644 index 00000000..93444f47 --- /dev/null +++ b/issues/03-command-execution-runtime.md @@ -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? diff --git a/issues/04-argv-parsing.md b/issues/04-argv-parsing.md new file mode 100644 index 00000000..a750cd34 --- /dev/null +++ b/issues/04-argv-parsing.md @@ -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 diff --git a/issues/05-env-var-access.md b/issues/05-env-var-access.md new file mode 100644 index 00000000..3ef5655e --- /dev/null +++ b/issues/05-env-var-access.md @@ -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` diff --git a/issues/06-piping.md b/issues/06-piping.md new file mode 100644 index 00000000..02dd4a38 --- /dev/null +++ b/issues/06-piping.md @@ -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. diff --git a/issues/07-redirection.md b/issues/07-redirection.md new file mode 100644 index 00000000..a5748f51 --- /dev/null +++ b/issues/07-redirection.md @@ -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.