Files
luash/luash

241 lines
7.3 KiB
Lua
Executable File

#!/usr/bin/env lua
-- luash: Minimal Lua shell scripting preprocessor
-- Module path for local src/
package.path = (debug.getinfo(1, "S").source:match("@(.*/)") or "./") .. "src/?.lua;" .. package.path
-- Load injected library via module
local function load_injected_lib()
local injected = require('injected')
injected.load()
end
-- REPL (Interactive Shell) Implementation
function start_repl()
load_injected_lib()
local repl_mod = require('repl')
-- Preprocessing functions (simplified versions for REPL)
local function process_env_vars_repl(code)
local pre = require('preprocess')
return pre.process_env_vars(code)
end
local function process_shell_commands_repl(code)
local pre = require('preprocess')
return pre.process_shell_commands(code)
end
local function process_interactive_commands_repl(code)
local pre = require('preprocess')
return pre.process_interactive_commands(code)
end
-- Preprocessing functions (simplified versions for REPL)
local function process_env_vars_repl(code)
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
return table.concat(lines, "\n")
end
local function process_shell_commands_repl(code)
local 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
return table.concat(lines, "\n")
end
local function process_interactive_commands_repl(code)
local 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
return table.concat(lines, "\n")
end
-- Luash preprocessing function for REPL
local function preprocess_luash_repl(code)
local pre = require('preprocess')
code = pre.remove_shebang_preserve_lines(code)
code = process_env_vars_repl(code)
code = process_interactive_commands_repl(code)
code = process_shell_commands_repl(code)
return code
end
-- Check if expression needs more input
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
}
-- Utility function
local function trim(s)
return s:match("^%s*(.-)%s*$")
end
-- Delegate to module REPL
repl_mod.start(preprocess_luash_repl, load_injected_lib)
end
-- 1. Check for an input file or REPL mode
local filename = arg[1]
if filename == "-h" or filename == "--help" then
print("Usage: luash <script.luash>")
print(" luash -i # Interactive mode")
print(" luash -h # Show this help")
return
elseif not filename or filename == "-i" or filename == "--interactive" then
start_repl()
return
end
-- Delegate to runner for file execution
local runner = require('runner')
runner.run_file(filename)