Refactor REPL expression handling to improve syntax error reporting and execution flow

This commit is contained in:
2025-10-09 16:42:40 +01:00
parent cf19a6ec5d
commit da979811dd
2 changed files with 52 additions and 155 deletions

156
README.md
View File

@@ -110,144 +110,20 @@ Explore organized examples that demonstrate Luash features:
See [examples/README.md](examples/README.md) for the complete learning path.
### What You Get
### Built-ins
#### File System Operations
```lua
-- Check file existence
if fs.exists("config.txt") then
content = fs.read("config.txt")
end
-- File operations
fs.write("output.txt", "Hello World")
fs.append("log.txt", "New entry\n")
-- Advanced file operations
file.copy("source.txt", "backup.txt")
file.move("old.txt", "new.txt")
lines = file.lines("data.txt") -- Read as array of lines
```
#### Directory Operations
```lua
-- Directory management
dir.create("new_folder") -- mkdir -p equivalent
files = dir.list("/tmp") -- List directory contents
current = dir.pwd() -- Get current directory
-- Convenience aliases
mkdir("test")
cd("/home/user")
ls("/var/log")
```
#### String Utilities
```lua
-- String manipulation
parts = str.split("a,b,c", ",")
clean = str.trim(" whitespace ")
bool = str.starts_with("hello", "hel")
-- Text processing
result = text.grep("pattern", "file.txt")
lines = text.head(10, "large_file.txt")
count = text.wc("-l", "data.txt") -- Line count
```
#### Array/Table Operations
```lua
-- Array utilities
found = array.contains({"a", "b", "c"}, "b")
doubled = array.map({1, 2, 3}, function(x) return x * 2 end)
evens = array.filter({1, 2, 3, 4}, function(x) return x % 2 == 0 end)
```
#### Path Utilities
```lua
-- Path manipulation
full_path = path.join("/usr", "local", "bin", "luash")
dir_name = path.dirname("/path/to/file.txt") -- "/path/to"
base_name = path.basename("/path/to/file.txt") -- "file.txt"
extension = path.ext("document.pdf") -- "pdf"
```
#### System Information
```lua
-- System utilities
os_name = sys.os() -- "linux", "darwin", etc.
arch = sys.arch() -- "x86_64", "arm64", etc.
hostname = sys.hostname() -- Get machine name
user = sys.whoami() -- Current username
memory_info = sys.memory() -- Memory usage
disk_info = sys.disk("/") -- Disk usage
```
#### Network Operations
```lua
-- Network utilities
success = net.download("https://example.com/file.txt", "local_file.txt")
response = net.get("https://api.example.com/data")
post_result = net.post("https://api.example.com/submit", "key=value")
reachable = net.ping("google.com", 3) -- Ping 3 times
```
#### Process Management
```lua
-- Process operations
my_pid = proc.pid()
git_path = proc.which("git") -- Find executable path
-- Job control
job.background("long_running_command")
job.kill(1234, "TERM") -- Kill process by PID
job.killall("firefox") -- Kill all processes by name
processes = job.ps() -- Get process list
```
#### Logging and Colors
```lua
-- Colored logging
log.info("Information message")
log.warn("Warning message")
log.error("Error message")
log.success("Success message")
-- Color output
print(color.red("Error text"))
print(color.green("Success text"))
print(color.bold(color.yellow("Important notice")))
```
#### Archive Operations
```lua
-- Compression utilities
archive.tar_create("backup.tar.gz", {"file1.txt", "file2.txt"})
archive.tar_extract("backup.tar.gz", "/destination")
archive.zip_create("archive.zip", {"folder1", "folder2"})
archive.zip_extract("archive.zip", "/extract_here")
```
### Convenience Aliases
Common shell commands are available as Lua functions:
Luash intentionally stays minimal. The injected helpers are:
```lua
ls() -- ls -la
pwd() -- Get current directory
cd("/path") -- Change directory
mkdir("folder") -- Create directory
rm("file") -- Remove file
cp("src", "dst") -- Copy file
mv("old", "new") -- Move/rename file
cat("file.txt") -- Read file contents
echo("Hello") -- Print and return text
grep("pattern", "file.txt") -- Search in file
head(5, "file.txt") -- First 5 lines
tail(10, "file.txt") -- Last 10 lines
wc("-l", "file.txt") -- Count lines
-- Minimal helpers available globally
env_get(name) -- Get environment variable (with session overrides)
env_set(name, value) -- Set environment variable (session + subprocess export)
shell(cmd) -- Run shell command and return trimmed stdout
run(cmd) -- Run shell command and stream output (exit code returned)
```
Everything else is standard Lua.
## Usage
### Basic Usage
@@ -266,7 +142,6 @@ The interactive shell supports:
- All luash preprocessing features
- Multiline input for functions, loops, etc.
- Command history with `.history`
- Screen clearing with `.clear`
- Special commands: `.help`, `.exit`, `.clear`
- Real-time expression evaluation
@@ -329,13 +204,14 @@ file.write_lines("output.txt", processed)
## Preprocessor Pipeline
Luash applies several preprocessing steps:
Luash applies these preprocessing steps:
1. **Environment Variable Substitution**: `$VAR` and `${VAR}``env.get('VAR')`
2. **Interactive Command Processing**: `!command``run('command')`
3. **Shell Command Substitution**: `` `command` `` → `shell('command')`
4. **Variable Interpolation**: `#{var}` in commands → Lua string concatenation
5. **Library Injection**: Comprehensive shell utilities loaded automatically
1. **Shebang removal (line-preserving)**: `#!/usr/bin/env luash` is removed and replaced by a blank line to preserve line numbers.
2. **Environment variable assignment**: `$VAR = value``env_set('VAR', value)` (outside strings only)
3. **Environment variable substitution**: `$VAR``env_get('VAR')` (outside strings only; `${VAR}` is not supported)
4. **Interactive commands**: lines starting with `!cmd``run("cmd")`
5. **Command substitution**: `` `command` `` → `shell("command")`
6. **Lua variable interpolation in commands**: inside backticks, `#{var}` is replaced with the Lua value of `var`, with proper quoting handled by the caller
## License

47
luash
View File

@@ -273,20 +273,30 @@ Multi-line input supported for functions, loops, etc.]])
repl.buffer = line
end
-- Check if we have a complete expression
if not needs_continuation(repl.buffer) and line ~= "" then
-- Process and execute
table.insert(repl.history, repl.buffer)
-- Try to compile to determine if we need continuation (parser-driven)
if line ~= "" then
local processed = preprocess_luash_repl(repl.buffer)
-- First, try as an expression (so results print automatically)
local func, err = load("return " .. processed, "@repl")
if not func then
-- Try without return (for statements)
if err and err:match("<eof>") then
-- Incomplete chunk; wait for more input
-- keep buffer as-is
else
-- Try as a statement block
func, err = load(processed, "@repl")
if not func then
if err and err:match("<eof>") then
-- Incomplete; wait for more input
else
-- Definitive syntax error; report and clear buffer
print("Syntax error: " .. err)
repl.buffer = ""
end
if func then
else
-- Executable statement block
table.insert(repl.history, repl.buffer)
local success, result = pcall(func)
if success then
if result ~= nil then
@@ -295,13 +305,24 @@ Multi-line input supported for functions, loops, etc.]])
else
print("Error: " .. tostring(result))
end
else
print("Syntax error: " .. err)
end
repl.buffer = ""
end
end
else
-- Executable expression
table.insert(repl.history, repl.buffer)
local success, result = pcall(func)
if success then
if result ~= nil then
print(tostring(result))
end
else
print("Error: " .. tostring(result))
end
repl.buffer = ""
end
end
end
end
end