296 lines
9.5 KiB
Lua
Executable File
296 lines
9.5 KiB
Lua
Executable File
#!/usr/bin/env lua
|
|
-- Luash Interactive Shell - A proper REPL
|
|
-- Supports multiline input, command history, and all luash features
|
|
|
|
-- Load the injected library functions
|
|
local script_dir = debug.getinfo(1, "S").source:match("@(.*/)")
|
|
local lib_file = io.open(script_dir .. "__injected_lib.lua", "r")
|
|
if lib_file then
|
|
local lib_code = lib_file:read("*a")
|
|
lib_file:close()
|
|
local lib_func = load(lib_code)
|
|
if lib_func then lib_func() end
|
|
end
|
|
|
|
-- Luash preprocessing functions (copied from main luash script)
|
|
local function preprocess_luash(code)
|
|
-- Remove shebang
|
|
code = code:gsub("^#![^\r\n]*[\r\n]?", "")
|
|
|
|
-- Environment variable processing
|
|
local lines = {}
|
|
for line in code:gmatch("([^\r\n]*)") do
|
|
if not line:match("^%s*%-%-") then -- Skip comments
|
|
-- Handle environment variable assignment: $VAR=value
|
|
if line:match("^%$([%w_]+)%s*=") then
|
|
line = line:gsub("^%$([%w_]+)%s*=%s*(.+)", function(var, value)
|
|
return "env_set('" .. var .. "', " .. value .. ")"
|
|
end)
|
|
else
|
|
-- Handle $VAR substitution, but not inside strings
|
|
local in_string = false
|
|
local quote_char = nil
|
|
local result = ""
|
|
local i = 1
|
|
|
|
while i <= #line do
|
|
local char = line:sub(i, i)
|
|
|
|
if (char == '"' or char == "'") and (i == 1 or line:sub(i-1, i-1) ~= "\\") then
|
|
if not in_string then
|
|
in_string = true
|
|
quote_char = char
|
|
elseif char == quote_char then
|
|
in_string = false
|
|
quote_char = nil
|
|
end
|
|
result = result .. char
|
|
i = i + 1
|
|
elseif char == "$" and not in_string then
|
|
local var_match = line:sub(i):match("^%$([%w_]+)")
|
|
if var_match then
|
|
result = result .. "env_get('" .. var_match .. "')"
|
|
i = i + #var_match + 1
|
|
else
|
|
result = result .. char
|
|
i = i + 1
|
|
end
|
|
else
|
|
result = result .. char
|
|
i = i + 1
|
|
end
|
|
end
|
|
line = result
|
|
end
|
|
end
|
|
table.insert(lines, line)
|
|
end
|
|
code = table.concat(lines, "\n")
|
|
|
|
-- Shell command processing
|
|
lines = {}
|
|
for line in code:gmatch("([^\r\n]*)") do
|
|
local in_string = false
|
|
local quote_char = nil
|
|
local result = ""
|
|
local i = 1
|
|
|
|
while i <= #line do
|
|
local char = line:sub(i, i)
|
|
|
|
if (char == '"' or char == "'") and (i == 1 or line:sub(i-1, i-1) ~= "\\") then
|
|
if not in_string then
|
|
in_string = true
|
|
quote_char = char
|
|
elseif char == quote_char then
|
|
in_string = false
|
|
quote_char = nil
|
|
end
|
|
result = result .. char
|
|
i = i + 1
|
|
elseif char == "`" and not in_string then
|
|
local end_pos = line:find("`", i + 1)
|
|
if end_pos then
|
|
local command = line:sub(i + 1, end_pos - 1)
|
|
|
|
if command:find("#{") then
|
|
local escaped_cmd = command:gsub('"', '\\"')
|
|
local interpolated_cmd = escaped_cmd:gsub("#{([%w_.]+)}", '" .. tostring(%1) .. "')
|
|
result = result .. 'shell("' .. interpolated_cmd .. '")'
|
|
else
|
|
result = result .. 'shell("' .. command:gsub('"', '\\"') .. '")'
|
|
end
|
|
i = end_pos + 1
|
|
else
|
|
result = result .. char
|
|
i = i + 1
|
|
end
|
|
else
|
|
result = result .. char
|
|
i = i + 1
|
|
end
|
|
end
|
|
table.insert(lines, result)
|
|
end
|
|
code = table.concat(lines, "\n")
|
|
|
|
-- Interactive commands
|
|
lines = {}
|
|
for line in code:gmatch("([^\r\n]*)") do
|
|
local command = line:match("^%s*!(.*)")
|
|
if command then
|
|
local indent = line:match("^(%s*)")
|
|
table.insert(lines, indent .. 'run("' .. command:gsub('"', '\\"') .. '")')
|
|
else
|
|
table.insert(lines, line)
|
|
end
|
|
end
|
|
code = table.concat(lines, "\n")
|
|
|
|
return code
|
|
end
|
|
|
|
-- Check if expression needs more input (simple heuristic)
|
|
local function needs_continuation(code)
|
|
local open_blocks = 0
|
|
local open_parens = 0
|
|
local open_brackets = 0
|
|
local open_braces = 0
|
|
|
|
local in_string = false
|
|
local quote_char = nil
|
|
|
|
for i = 1, #code do
|
|
local char = code:sub(i, i)
|
|
|
|
if not in_string then
|
|
if char == '"' or char == "'" then
|
|
in_string = true
|
|
quote_char = char
|
|
elseif char == "(" then
|
|
open_parens = open_parens + 1
|
|
elseif char == ")" then
|
|
open_parens = open_parens - 1
|
|
elseif char == "[" then
|
|
open_brackets = open_brackets + 1
|
|
elseif char == "]" then
|
|
open_brackets = open_brackets - 1
|
|
elseif char == "{" then
|
|
open_braces = open_braces + 1
|
|
elseif char == "}" then
|
|
open_braces = open_braces - 1
|
|
end
|
|
else
|
|
if char == quote_char and code:sub(i-1, i-1) ~= "\\" then
|
|
in_string = false
|
|
quote_char = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Check for Lua keywords that need 'end'
|
|
local keywords = {"function", "if", "for", "while", "do", "then"}
|
|
local end_count = 0
|
|
|
|
for _, keyword in ipairs(keywords) do
|
|
local _, count = code:gsub("%f[%w]" .. keyword .. "%f[%W]", "")
|
|
open_blocks = open_blocks + count
|
|
end
|
|
|
|
_, end_count = code:gsub("%f[%w]end%f[%W]", "")
|
|
open_blocks = open_blocks - end_count
|
|
|
|
return open_blocks > 0 or open_parens > 0 or open_brackets > 0 or open_braces > 0 or in_string
|
|
end
|
|
|
|
-- REPL state
|
|
local repl = {
|
|
buffer = "",
|
|
history = {},
|
|
history_index = 0
|
|
}
|
|
|
|
-- Add utility functions
|
|
function trim(s)
|
|
return s:match("^%s*(.-)%s*$")
|
|
end
|
|
|
|
-- Main REPL function
|
|
local function run_repl()
|
|
print("🚀 Luash Interactive Shell")
|
|
print("Type Lua expressions, use $VAR for env vars, `commands` for shell")
|
|
print("Special commands: .help .exit .clear .history")
|
|
print("")
|
|
|
|
while true do
|
|
local prompt = repl.buffer == "" and "luash> " or " ... "
|
|
io.write(prompt)
|
|
io.flush()
|
|
|
|
local line = io.read("*line")
|
|
|
|
if not line then -- EOF (Ctrl+D)
|
|
print("\nGoodbye!")
|
|
break
|
|
end
|
|
|
|
line = trim(line)
|
|
|
|
-- Handle special dot commands
|
|
if line == ".exit" or line == ".quit" then
|
|
break
|
|
elseif line == ".clear" then
|
|
repl.buffer = ""
|
|
print("Buffer cleared")
|
|
elseif line == ".history" then
|
|
for i, cmd in ipairs(repl.history) do
|
|
print(string.format("%3d: %s", i, cmd))
|
|
end
|
|
elseif line == ".help" then
|
|
print([[
|
|
Luash Interactive Shell Help:
|
|
|
|
Luash Features:
|
|
$VAR - Access environment variables
|
|
$VAR = "value" - Set environment variables
|
|
`command` - Execute shell commands
|
|
#{var} - Variable interpolation in commands
|
|
!command - Interactive shell commands
|
|
|
|
Special Commands:
|
|
.help - Show this help
|
|
.exit, .quit - Exit the shell
|
|
.clear - Clear input buffer
|
|
.history - Show command history
|
|
|
|
Examples:
|
|
print("Hello " .. $USER)
|
|
files = `ls -la`
|
|
$GREETING = "Hello World"
|
|
result = `echo #{$GREETING}`
|
|
!ps aux | head -5
|
|
|
|
Multi-line input supported for functions, loops, etc.]])
|
|
else
|
|
-- Add to buffer
|
|
if repl.buffer ~= "" then
|
|
repl.buffer = repl.buffer .. "\n" .. line
|
|
else
|
|
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)
|
|
|
|
local processed = preprocess_luash(repl.buffer)
|
|
local func, err = load("return " .. processed, "@repl")
|
|
|
|
if not func then
|
|
-- Try without return (for statements)
|
|
func, err = load(processed, "@repl")
|
|
end
|
|
|
|
if func then
|
|
local success, result = pcall(func)
|
|
if success then
|
|
if result ~= nil then
|
|
print(tostring(result))
|
|
end
|
|
else
|
|
print("Error: " .. tostring(result))
|
|
end
|
|
else
|
|
print("Syntax error: " .. err)
|
|
end
|
|
|
|
repl.buffer = ""
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Start the REPL
|
|
run_repl()
|