diff --git a/examples/bad_shell.lua b/examples/bad_shell.lua new file mode 100644 index 0000000..eb5bbd9 --- /dev/null +++ b/examples/bad_shell.lua @@ -0,0 +1 @@ +print("GCC Version: $[[gc -v]]") --yikes diff --git a/examples/shell_output.lua b/examples/shell_output.lua new file mode 100644 index 0000000..d60a31c --- /dev/null +++ b/examples/shell_output.lua @@ -0,0 +1,4 @@ +--Using the command +print("Compiled with LuaComp $[[/tmp/luacomp -v]]") +--Or using the shell vars +print("Compiled with LuaComp $[{LUACOMP_VERSION}]") \ No newline at end of file diff --git a/src/application.lua b/src/application.lua new file mode 100644 index 0000000..84a09c0 --- /dev/null +++ b/src/application.lua @@ -0,0 +1,77 @@ +local function dprint(...) + local args = {...} + for i=1, #args do + args[i] = tostring(args[i]) + end + if (VERBOSE) then + io.stderr:write("DEBUG\t"..table.concat(args,"\t"),"\n") + end +end + +--#include "src/shell_var.lua" +--#include "src/luacomp_vars.lua" +--#include "src/ast.lua" +--#include "src/generator.lua" +--#include "src/directive_provider.lua" +--#include "src/cfg/minifier_providers.lua" + +local parser = argparse(arg[0], "LuaComp v"..LUACOMP_VERSION.."\nA Lua preprocessor+postprocessor.") +parser:argument("input", "Input file (- for STDIN)") +parser:option("-O --output", "Output file. (- for STDOUT)", "-") +parser:option("-m --minifier", "Sets the minifier", "none") +parser:option("-x --executable", "Makes the script an executable (default: current lua version)"):args "?" +parser:flag("--generator-code", "Outputs only the code from the generator.") +parser:flag("--verbose", "Verbose output. (Debugging)"):action(function() VERBOSE=true end) +parser:flag("-v --version", "Prints the version and exits"):action(function() + print(LUACOMP_VERSION) + os.exit(0) +end) +local args = parser:parse() +local file = args.input +_sv("LUACOMP_MINIFIER", args.minifier) +local f +if (file ~= "-") then + f = io.open(file, "r") + if not f then + io.stderr:write("ERROR: File `"..file.."' does not exist!\n") + os.exit(1) + end +else + f = io.stdin +end +dprint("Generating AST...") +local ast = mkast(f, file) +dprint("Generating code...") +local ocode = generate(ast, args.generator_code) + +local minifier = providers[args.minifier] +dprint("Minifier: "..args.minifier, minifier) +if not minifier then + io.stderr:write("ERROR: Minifier `"..args.minifier.."' not found!\n") + os.exit(1) +end +dprint("Running...") +local rcode, err = minifier(ocode) + +if (not rcode) then + io.stderr:write("ERROR: Error for minifier `"..args.minifier.."': \n") + io.stderr:write(err) + os.exit(1) +end + +local of +if (args.output == "-") then + of = io.stdout +else + of = io.open(args.output, "w") +end +local ver = _VERSION:lower():gsub(" ", "") +if jit then + ver = "luajit" +end +if (args.executable) then + of:write("#!/usr/bin/env ", args.executable[1] or ver, "\n") +end +of:write(rcode) +of:close() +f:close() \ No newline at end of file diff --git a/src/ast.lua b/src/ast.lua index 23cc348..53f0920 100644 --- a/src/ast.lua +++ b/src/ast.lua @@ -41,9 +41,9 @@ local ws = { local function parse_hex(f) local lc = " " local hex = "" - while (48 <= lc:byte() and lc:byte() <= 57) or (97 <= lc:byte() and lc:byte() <= 102) or (65 <= lc:byte() and lc:byte() <= 70) do + while (48 <= lc:byte() and lc:byte() <= 57) or (97 <= lc:byte() and lc:byte() <= 102) or (65 <= lc:byte() and lc:byte() <= 70) and lc do lc = nextc(f) - if (48 <= lc:byte() and lc:byte()) <= 57 or (97 <= lc:byte() and lc:byte() <= 102) or (65 <= lc:byte() and lc:byte() <= 70) then + if (48 <= lc:byte() and lc:byte()) <= 57 or (97 <= lc:byte() and lc:byte() <= 102) or (65 <= lc:byte() and lc:byte() <= 70) and lc then hex = hex .. lc end end @@ -53,9 +53,9 @@ end local function parse_number(f, c) local lc = " " local num = c - while 48 <= lc:byte() and lc:byte() <= 57 do + while 48 <= lc:byte() and lc:byte() <= 57 and lc do lc = nextc(f) - if (48 <= lc:byte() and lc:byte() <= 57) then + if (48 <= lc:byte() and lc:byte() <= 57) and lc then num = num .. lc end end @@ -79,6 +79,8 @@ local function parse_dblquote(f) local c = nextc(f) if (peek(f) == "\n" or peek(f) == "\r") then return nil, "Unexpected end of line" + elseif (not peek(f)) then + return nil, "Unexpected end of file" end if (c == "\\") then if (esct[peek(f)]) then @@ -100,6 +102,8 @@ local function parse_snglquote(f) local c = nextc(f) if (peek(f) == "\n" or peek(f) == "\r") then return nil, "Unexpected end of line" + elseif (not peek(f)) then + return nil, "Unexpected end of file" end if (c == "\\") then if (esct[peek(f)]) then @@ -120,6 +124,8 @@ local function parse_envarg(f) while peek(f) ~= ")" do if (peek(f) == "\n" or peek(f) == "\r") then return nil, "Unexpected end of line" + elseif (not peek(f)) then + return nil, "Unexpected end of file" end val = val .. nextc(f) end @@ -166,6 +172,14 @@ local function parse_directive(f) local val = parse_envarg(f) if not os.getenv(val) then return nil, "Enviroment variable `"..val.."' does not exist." end args[#args+1] = os.getenv(val) + elseif lc == "@" and peek(f, 2) == "[{" then + skip(f, 2) + local val = "" + while peek(f, 2) ~= "}]" do + val = val .. nextc(f) + end + args[#args+1] = {type="lua_var", val} + skip(f, 2) elseif not ws[lc] then return nil, "Syntax error" end @@ -248,6 +262,24 @@ local function mkast(f, n) end tree[#tree+1] = {type="lua_r", code=val, file=n, line=lpos} skip(f, 2) + elseif (lc == "$" and peek(f, 2) == "[[") then + add_code() + skip(f, 2) + local val = "" + while peek(f, 2) ~= "]]" do + val = val .. nextc(f) + end + tree[#tree+1] = {type="shell", code=val, file=n, line=lpos} + skip(f, 2) + elseif (lc == "$" and peek(f, 2) == "[{") then + add_code() + skip(f, 2) + local val = "" + while peek(f, 2) ~= "}]" do + val = val .. nextc(f) + end + tree[#tree+1] = {type="shell_r", code=val, file=n, line=lpos} + skip(f, 2) elseif (lc == "\r" or lc == "\n") then if (lc == "\r" and peek(f) == "\n") then skip(f) diff --git a/src/cfg/minifier_providers.lua b/src/cfg/minifier_providers.lua index cb1a3e5..1c03c45 100644 --- a/src/cfg/minifier_providers.lua +++ b/src/cfg/minifier_providers.lua @@ -26,6 +26,64 @@ function providers.luamin(cin) local lmh = io.popen("luamin -f "..fn.." 2>&1", "r") local dat = lmh:read("*a") local stat, _, code = lmh:close() + os.remove(fn) + if (code ~= 0) then + return false, dat + end + return dat +end + +function providers.bython(cin) + local fn = os.tmpname() + local fo = os.tmpname() + local fh = io.open(fn, "w") + fh:write(cin) + fh:close() + local lmh = io.popen("bython -c "..fn.." "..fo.." 2>&1", "r") + local out = lmh:read("*a") + local stat, _, code = lmh:close() + os.remove(fn) + if (code ~= 0) then + os.remove(fo) + return false, out + end + fh = io.open(fo, "r") + local dat = fh:read("*a") + fh:close() + os.remove(fo) + return dat +end + +function providers.bython2(cin) + local fn = os.tmpname() + local fo = os.tmpname() + local fh = io.open(fn, "w") + fh:write(cin) + fh:close() + local lmh = io.popen("bython -c2 "..fn.." "..fo.." 2>&1", "r") + local out = lmh:read("*a") + local stat, _, code = lmh:close() + os.remove(fn) + if (code ~= 0) then + os.remove(fo) + return false, out + end + fh = io.open(fo, "r") + local dat = fh:read("*a") + fh:close() + os.remove(fo) + return dat +end + +function providers.uglify(cin) + local fn = os.tmpname() + local fh = io.open(fn, "w") + fh:write(cin) + fh:close() + local lmh = io.popen("uglifyjs --compress --mangle -- "..fn.." 2>&1", "r") + local dat = lmh:read("*a") + local stat, _, code = lmh:close() + os.remove(fn) if (code ~= 0) then return false, dat end diff --git a/src/directive_provider.lua b/src/directive_provider.lua index f895ce6..3dcbec2 100644 --- a/src/directive_provider.lua +++ b/src/directive_provider.lua @@ -16,6 +16,7 @@ limitations under the License. ]] ----#include "directives/define.lua" ---#include "directives/include.lua" ---#include "directives/loadmod.lua" +--#include "src/directives/define.lua" +--#include "src/directives/include.lua" +--#include "src/directives/loadmod.lua" +--#include "src/directives/error.lua" diff --git a/src/directives/define.lua b/src/directives/define.lua index ab1625f..3e7a8f2 100644 --- a/src/directives/define.lua +++ b/src/directives/define.lua @@ -1,4 +1,4 @@ function directives.define(env, evar, val) - os.setenv(evar, val) + svar.set(evar, val) return true end \ No newline at end of file diff --git a/src/directives/error.lua b/src/directives/error.lua new file mode 100644 index 0000000..fa86c82 --- /dev/null +++ b/src/directives/error.lua @@ -0,0 +1,3 @@ +function directives.error(env, err) + return false, err +end \ No newline at end of file diff --git a/src/generator.lua b/src/generator.lua index c58e6ff..f9d18f1 100644 --- a/src/generator.lua +++ b/src/generator.lua @@ -20,9 +20,17 @@ local function lua_escape(code) return code:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n") end +local function shell_escape(code) + return code:gsub("\\%[", "[") +end + +local function svar_escape(code) + return code:gsub("\"", "\\\""):gsub("\'", "\\\'"):gsub("`", "\\`") +end + local directives = {} -local function generate(ast) +local function generate(ast, gencode) local lua_code = "" for i=1, #ast do local leaf = ast[i] @@ -36,7 +44,9 @@ local function generate(ast) stargs[i] = "\""..lua_escape(arg).."\"" elseif (type(arg) == "number") then stargs[i] = tostring(arg) - end + elseif (type(arg) == "table" and arg.type=="lua_var") then + stargs[i] = arg[1] + end end lua_code = lua_code .. "call_directive(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.name.."\","..table.concat(stargs, ",")..")" elseif (leaf.type == "envvar") then @@ -45,6 +55,10 @@ local function generate(ast) lua_code = lua_code .. "put_code(\""..leaf.file..":"..tostring(leaf.line).."\",\"" .. lua_escape(leaf.data) .. "\")" elseif (leaf.type == "lua_r") then lua_code = lua_code .. "put_code(\""..leaf.file..":"..tostring(leaf.line).."\",tostring("..leaf.code.."))" + elseif (leaf.type == "shell") then + lua_code = lua_code .. "put_shell_out(\""..leaf.file..":"..tostring(leaf.line).."\",\""..lua_escape(leaf.code).."\")" + elseif (leaf.type == "shell_r") then + lua_code = lua_code .. "put_svar(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.code.."\")" else io.stderr:write("ERROR: Internal catastrophic failure, unknown type "..leaf.type.."\n") os.exit(1) @@ -56,6 +70,9 @@ local function generate(ast) io.stdout:write("ERROR: "..fpos..": "..err.."\n") os.exit(1) end + local function bitch(fpos, err) + io.stdout:write("WARNING: "..fpos..": "..err.."\n") + end local function call_directive(fpos, dname, ...) if (not directives[dname]) then run_away_screaming(fpos, "Invalid directive name `"..dname.."'") @@ -66,7 +83,7 @@ local function generate(ast) end end local function put_env(fpos, evar) - local e = os.getenv(evar) + local e = svar.get(evar) if not e then run_away_screaming(fpos, "Enviroment variable `"..evar.."' does not exist!") end @@ -75,15 +92,56 @@ local function generate(ast) local function put_code(fpos, code) env.code = env.code .. code --not much that can fail here... end + local function put_shell_out(fpos, code) + local tname = os.tmpname() + local f = os.tmpname() + local fh = io.open(f, "w") + fh:write(code) + fh:close() + os.execute("chmod +x "..f) + local vars = svar.get_all() + local vstr = "" + for k, v in pairs(vars) do + vstr = vstr .. k.."=".."\""..svar_escape(v).."\" " + end + dprint("Shell", vstr .. f.." 2>"..tname) + local h = io.popen(vstr .. f.." 2>"..tname, "r") + local output = h:read("*a"):gsub("\n$", "") + local ok, sig, code = h:close() + fh = io.open(tname, "r") + local stderr = fh:read("*a"):gsub("\n$", "") + fh:close() + os.remove(f) + os.remove(tname) + if not ok then + run_away_screaming(fpos, "Shell exit code "..code..", SIG_"..sig:upper().."\n"..stderr) + elseif #stderr > 0 then + bitch(fpos, stderr) + end + env.code = env.code .. output + end + local function put_svar(fpos, evar) + local e = svar.get(evar) + if not e then + run_away_screaming(fpos, "Enviroment variable `"..evar.."' does not exist!") + end + env.code = env.code .. e + end local fenv = {} for k, v in pairs(_G) do fenv[k] = v end + if gencode then + return lua_code + end fenv._G = fenv fenv._ENV = fenv fenv.call_directive = call_directive fenv.put_code = put_code fenv.put_env = put_env + fenv.put_svar = put_svar + fenv.put_shell_out = put_shell_out + fenv._GENERATOR=env local func = assert(load(lua_code, "=(generated code)", "t", fenv)) func() return env.code diff --git a/src/init.lua b/src/init.lua index 54a1b69..2a56c2c 100644 --- a/src/init.lua +++ b/src/init.lua @@ -15,58 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. ]] - ---#include "luacomp_vars.lua" ---#include "ast.lua" ---#include "generator.lua" ---#include "directive_provider.lua" ---#include "cfg/minifier_providers.lua" - local argparse = require("argparse") -local parser = argparse(arg[0], "LuaComp v"..LUACOMP_VERSION.."\nA Lua preprocessor+postprocessor.") -parser:argument("input", "Input file (- for STDIN)") -parser:option("-O --output", "Output file. (- for STDOUT)", "-") -parser:option("-m --minifier", "Sets the minifier", "none") -parser:option("-x --executable", "Makes the script an executable (default: current lua version)"):args "?" -local args = parser:parse() -local file = args.input -local f -if (file ~= "-") then - f = io.open(file, "r") - if not f then - io.stderr:write("ERROR: File `"..file.."' does not exist!\n") - os.exit(1) - end -else - f = io.stdin -end -local ast = mkast(f, file) -local ocode = generate(ast) - -local minifier = providers[args.minifier] - -local rcode, err = minifier(ocode) - -if (not rcode) then - io.stderr:write("Error for minifier `"..args.minifier.."': \n") - io.stderr:write(err) - os.exit(1) -end - -local of -if (args.output == "-") then - of = io.stdout -else - of = io.open(args.output, "w") -end -local ver = _VERSION:lower():gsub(" ", "") -if jit then - ver = "luajit" -end -if (args.executable) then - of:write("#!/usr/bin/env ", args.executable[1] or ver, "\n") -end -of:write(rcode) -of:close() -f:close() +--#include "src/application.lua" +---#include "src/application.lua" @[{test}] diff --git a/src/luacomp_vars.lua b/src/luacomp_vars.lua index afd67ae..152635b 100644 --- a/src/luacomp_vars.lua +++ b/src/luacomp_vars.lua @@ -1,10 +1,11 @@ local function _sv(k, v) _G[k] = v + svar.set(k, v) --os.setenv(k, tostring(v)) end _sv("LUACOMP_V_MAJ", 1) -_sv("LUACOMP_V_MIN", 1) +_sv("LUACOMP_V_MIN", 2) _sv("LUACOMP_V_PAT", 0) _sv("LUACOMP_VERSION", LUACOMP_V_MAJ.."."..LUACOMP_V_MIN.."."..LUACOMP_V_PAT) _sv("LUACOMP_NAME", "LuaComp") \ No newline at end of file diff --git a/src/shell_var.lua b/src/shell_var.lua new file mode 100644 index 0000000..230ce1b --- /dev/null +++ b/src/shell_var.lua @@ -0,0 +1,15 @@ +local svar = {} + +local svars = {} + +function svar.get(var) + return svars[var] or os.getenv(var) +end + +function svar.set(var, val) + svars[var] = tostring(val) +end + +function svar.get_all() + return svars +end \ No newline at end of file diff --git a/src/staticinit.lua b/src/staticinit.lua index 34cd26e..9cfc844 100644 --- a/src/staticinit.lua +++ b/src/staticinit.lua @@ -16,53 +16,8 @@ limitations under the License. ]] ---#include "luacomp_vars.lua" ---#include "ast.lua" ---#include "generator.lua" ---#include "directive_provider.lua" ---#include "cfg/minifier_providers.lua" - local argparse = (function() ---#include "argparse.lua" +--#include "src/argparse.lua" end)() -local parser = argparse(arg[0], "LuaComp v"..LUACOMP_VERSION.."\nA Lua preprocessor+postprocessor.") -parser:argument("input", "Input file (- for STDIN)") -parser:option("-O --output", "Output file. (- for STDOUT)", "-") -parser:option("-m --minifier", "Sets the minifier", "none") -parser:option("-x --executable", "Makes the script an executable (default: current lua version)"):args "?" -local args = parser:parse() -local file = args.input -local f -if (file ~= "-") then - f = io.open(file, "r") - if not f then - io.stderr:write("ERROR: File `"..file.."' does not exist!\n") - os.exit(1) - end -else - f = io.stdin -end -local ast = mkast(f, file) -local ocode = generate(ast) - -local minifier = providers[args.minifier] - -local rcode = minifier(ocode) - -local of -if (args.output == "-") then - of = io.stdout -else - of = io.open(args.output, "w") -end -local ver = _VERSION:lower():gsub(" ", "") -if jit then - ver = "luajit" -end -if (args.executable) then - of:write("#!/usr/bin/env ", args.executable[1] or ver, "\n") -end -of:write(rcode) -of:close() -f:close() +--#include "src/application.lua"