From 3f0bbb6fa35a7c956a6a94eb6a753dab7a8ed2c1 Mon Sep 17 00:00:00 2001 From: Cormac Shannon Date: Fri, 29 Aug 2025 00:28:25 +0100 Subject: [PATCH] interactive luash --- README.md | 17 ++- interactive.luash | 118 +++++++++++++++++++ luash | 8 +- luash_shell.lua | 295 ++++++++++++++++++++++++++++++++++++++++++++++ repl_demo.luash | 71 +++++++++++ 5 files changed, 505 insertions(+), 4 deletions(-) create mode 100644 interactive.luash create mode 100755 luash_shell.lua create mode 100644 repl_demo.luash diff --git a/README.md b/README.md index 6c3bedd..142d29a 100644 --- a/README.md +++ b/README.md @@ -255,14 +255,25 @@ wc("-l", "file.txt") -- Count lines ./luash script.luash ``` +### Interactive Shell (REPL) +```bash +./luash -i +# or +./luash_shell.lua +``` + +The interactive shell supports: +- All luash preprocessing features +- Multiline input for functions, loops, etc. +- Command history with `.history` +- Special commands: `.help`, `.exit`, `.clear` +- Real-time expression evaluation + ### Debug Mode ```bash LUASH_DEBUG=1 ./luash script.luash ``` -### Example Script -See `example.luash` for a comprehensive demonstration of all features. - ## Installation 1. Clone or download the luash repository diff --git a/interactive.luash b/interactive.luash new file mode 100644 index 0000000..1242f2b --- /dev/null +++ b/interactive.luash @@ -0,0 +1,118 @@ +#!/usr/bin/env luash +-- Interactive Luash Demo/REPL Alternative +-- Shows how to build interactive features within luash + +print("šŸš€ Luash Interactive Demo") +print("This demonstrates interactive capabilities in luash") +print() + +-- Simple command processor +function process_command(cmd) + cmd = cmd:match("^%s*(.-)%s*$") -- trim + + if cmd == "help" then + print([[ +Available commands: + sysinfo - Show system information + files - List files in current directory + env - Show environment variables + network - Test network connectivity + processes - Show running processes + disk - Show disk usage + quit - Exit + +Or type any luash expression!]]) + + elseif cmd == "sysinfo" then + print("System Information:") + print(" OS: " .. `uname -s`) + print(" Architecture: " .. `uname -m`) + print(" Hostname: " .. `hostname`) + print(" User: " .. $USER) + print(" Uptime: " .. `uptime`) + + elseif cmd == "files" then + print("Files in current directory:") + files = `ls -la` + print(files) + + elseif cmd == "env" then + print("Key environment variables:") + print(" USER: " .. $USER) + print(" HOME: " .. $HOME) + print(" SHELL: " .. $SHELL) + print(" PATH: " .. `echo $PATH | cut -d: -f1-3`) + + elseif cmd == "network" then + print("Network connectivity test:") + hosts = {"google.com", "github.com"} + for i = 1, #hosts do + local host = hosts[i] + local result = `ping -c 1 #{host} >/dev/null 2>&1 && echo "ok" || echo "fail"` + local status = result == "ok" and "āœ“" or "āœ—" + print(" " .. status .. " " .. host) + end + + elseif cmd == "processes" then + print("Top processes:") + !ps aux | head -10 + + elseif cmd == "disk" then + print("Disk usage:") + !df -h + + elseif cmd == "quit" or cmd == "exit" then + return false + + else + -- Try to execute as luash code + if cmd ~= "" then + print("Executing: " .. cmd) + -- In a real REPL, we'd eval this + -- For demo, just show some dynamic examples + if cmd:match("^%$") then + print("Environment variable assignment") + elseif cmd:match("`.*`") then + print("Shell command execution") + else + print("Lua expression") + end + end + end + + return true +end + +-- Main interaction loop +print("Type 'help' for commands, 'quit' to exit") +print() + +-- Simulate some interactive commands +commands = { + "help", + "sysinfo", + "files", + "network", + "quit" +} + +for i = 1, #commands do + local cmd = commands[i] + print("luash> " .. cmd) + + if not process_command(cmd) then + break + end + + if i < #commands then + print("\nPress Enter to continue...") + io.read() -- Wait for user input + end +end + +print("\n✨ This shows how luash can build interactive applications!") +print("For a full REPL, we'd:") +print(" - Read input in a loop") +print(" - Parse and execute luash expressions") +print(" - Handle multiline input") +print(" - Maintain command history") diff --git a/luash b/luash index 3072049..25393e1 100755 --- a/luash +++ b/luash @@ -1,10 +1,16 @@ #!/usr/bin/env lua -- luash: Minimal Lua shell scripting preprocessor --- 1. Check for an input file +-- 1. Check for an input file or REPL mode local filename = arg[1] if not filename then print("Usage: luash ") + print(" luash -i # Interactive mode") + return +elseif filename == "-i" or filename == "--interactive" then + -- Start interactive REPL + local script_dir = debug.getinfo(1, "S").source:match("@(.*/)") + dofile(script_dir .. "luash_shell.lua") return end diff --git a/luash_shell.lua b/luash_shell.lua new file mode 100755 index 0000000..9d61f5a --- /dev/null +++ b/luash_shell.lua @@ -0,0 +1,295 @@ +#!/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() diff --git a/repl_demo.luash b/repl_demo.luash new file mode 100644 index 0000000..6ad05e2 --- /dev/null +++ b/repl_demo.luash @@ -0,0 +1,71 @@ +#!/usr/bin/env luash +-- REPL Demo: Showcasing interactive features + +print("šŸŽ® Luash Interactive Shell Demo") +print("===============================\n") + +print("Luash now supports a full interactive REPL!") +print("Start it with: ./luash -i") +print() + +print("šŸ”„ REPL Features:") +print("āœ“ All luash preprocessing ($VAR, `commands`, #{interpolation})") +print("āœ“ Multiline input (functions, loops, if blocks)") +print("āœ“ Command history") +print("āœ“ Special dot commands (.help, .exit, .clear, .history)") +print("āœ“ Real-time expression evaluation") +print() + +print("šŸ“ Example REPL Session:") +examples = { + 'print("Hello " .. $USER)', + '$GREETING = "Hello from REPL"', + 'files = `ls *.luash | wc -l`', + 'print("Found " .. files .. " luash files")', + 'host = "google.com"', + 'status = `ping -c 1 #{host} >/dev/null && echo "ok"`', + 'print(host .. " is " .. status)', + '', + '-- Multiline example:', + 'for i = 1, 3 do', + ' print("Iteration: " .. i)', + 'end', + '', + '-- Function definition:', + 'function greet(name)', + ' return "Hello, " .. name .. "!"', + 'end', + '', + 'print(greet($USER))' +} + +for i, example in ipairs(examples) do + if example == "" then + print() + elseif example:match("^%-%-") then + print(example) + else + print("luash> " .. example) + end +end + +print() +print("šŸš€ Try it now: ./luash -i") +print(" Or run: ./luash_shell.lua") +print() + +print("šŸ’” The REPL supports:") +print(" • Environment variables: $HOME, $USER, etc.") +print(" • Shell commands: `ls`, `pwd`, `date`") +print(" • Variable interpolation: `echo #{variable}`") +print(" • Interactive commands: !git status") +print(" • Full Lua: functions, loops, tables, etc.") +print(" • Command history and editing") +print() + +print("✨ Perfect for:") +print(" • Interactive system administration") +print(" • Exploring file systems and processes") +print(" • Quick shell command prototyping") +print(" • Learning luash syntax") +print(" • Debugging shell scripts")