#!/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()