diff --git a/build/luacomp-5.1-static.lua b/build/luacomp-5.1-static.lua deleted file mode 100644 index 381afe5..0000000 --- a/build/luacomp-5.1-static.lua +++ /dev/null @@ -1,2044 +0,0 @@ -#!/usr/bin/env lua5.1 ---[[ - staticinit.lua - Main file of LuaComp, directly includes argparse. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - - -local function _sv(k, v) - _G[k] = v - --os.setenv(k, tostring(v)) -end - -_sv("LUACOMP_V_MAJ", 1) -_sv("LUACOMP_V_MIN", 1) -_sv("LUACOMP_V_PAT", 0) -_sv("LUACOMP_VERSION", LUACOMP_V_MAJ.."."..LUACOMP_V_MIN.."."..LUACOMP_V_PAT) -_sv("LUACOMP_NAME", "LuaComp") - ---[[ - ast.lua - Generates a structure for use in preprocessing. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function nextc(f, c) - c = c or 1 - return f:read(c) -end - -local function peek(f, c) - c = c or 1 - local z = f:read(c) - f:seek("cur", -c) - return z -end - -local function skip(f, c) - c = c or 1 - return f:seek("cur", c) -end - -local ws = { - ["\t"] = true, - [" "] = true -} - -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 - 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 - hex = hex .. lc - end - end - return tonumber(hex, 16) -end - -local function parse_number(f, c) - local lc = " " - local num = c - while 48 <= lc:byte() and lc:byte() <= 57 do - lc = nextc(f) - if (48 <= lc:byte() and lc:byte() <= 57) then - num = num .. lc - end - end - return tonumber(hex, 10) -end - -local esct = { - ["t"] = "\t", - ["n"] = "\n", - ["r"] = "\r", - ["\\"] = "\\\\" -} - -for i=0, 9 do - esct[tostring(i)] = string.char(i) -end - -local function parse_dblquote(f) - local val = "" - while peek(f) ~= "\"" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_snglquote(f) - local val = "" - while peek(f) ~= "\'" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_envarg(f) - local val = "" - while peek(f) ~= ")" do - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - val = val .. nextc(f) - end - skip(f) - return val -end - -local function parse_directive(f) - local lc = "_" - local name = "" - local args = {} - local carg = "" - while not ws[lc] do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name} - elseif not ws[lc] then - name = name .. lc - end - end - while true do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name, args=args} - elseif lc == "0" and peek(f) == "x" then - skip(f) - local val = parse_hex(f) - args[#args+1] = val - elseif 48 <= lc:byte() and lc:byte() <= 57 then - local val = parse_number(f, lc) - args[#args+1] = val - elseif lc == "\"" then - local val, e = parse_dblquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "\'" then - local val, e = parse_snglquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "$" and peek(f) == "(" then - skip(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 not ws[lc] then - return nil, "Syntax error" - end - end -end - -local function mkast(f, n) - io.stderr:write("PROC\t",n,"\n") - local lc = " " - local lpos = 1 - local ilpos = 1 - local tree = {} - local code = "" - local branches = {} - local function add_code() - tree[#tree+1] = {type="code", data=code, file=n, line=lpos} - code = "" - end - local function parse_error(e) - io.stderr:write("ERROR:"..n..":"..lpos..": "..e.."\n") - os.exit(1) - end - while lc and lc ~= "" do - lc = nextc(f) - if (lc == "-" and ilpos == 1) then - if (peek(f, 2) == "-#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "/" and ilpos == 1) then - if (peek(f, 2) == "/#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "$" and peek(f) == "(") then - add_code() - skip(f) - local val, e = parse_envarg(f) - if not val then - parse_error(e) - end - tree[#tree+1] = {type="envvar", var=val, file=n, line=lpos} - 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="lua", 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="lua_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) - end - lpos = lpos+1 - ilpos = 1 - code = code .. "\n" - else - code = code .. (lc or "") - ilpos = ilpos+1 - end - end - add_code() - return tree -end - ---[[ - generator.lua - Generates the code. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function lua_escape(code) - return code:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n") -end - -local directives = {} - -local function generate(ast) - local lua_code = "" - for i=1, #ast do - local leaf = ast[i] - if (leaf.type == "lua") then - lua_code = lua_code .. leaf.code - elseif (leaf.type == "directive") then - local stargs = {} - for i=1, #leaf.args do - local arg = leaf.args[i] - if (type(arg) == "string") then - stargs[i] = "\""..lua_escape(arg).."\"" - elseif (type(arg) == "number") then - stargs[i] = tostring(arg) - end - end - lua_code = lua_code .. "call_directive(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.name.."\","..table.concat(stargs, ",")..")" - elseif (leaf.type == "envvar") then - lua_code = lua_code .. "put_env(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.var.."\")" - elseif (leaf.type == "code") then - 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.."))" - else - io.stderr:write("ERROR: Internal catastrophic failure, unknown type "..leaf.type.."\n") - os.exit(1) - end - lua_code = lua_code .. "\n" - end - local env = {code = ""} - local function run_away_screaming(fpos, err) - io.stdout:write("ERROR: "..fpos..": "..err.."\n") - os.exit(1) - end - local function call_directive(fpos, dname, ...) - if (not directives[dname]) then - run_away_screaming(fpos, "Invalid directive name `"..dname.."'") - end - local r, er = directives[dname](env, ...) - if (not r) then - run_away_screaming(fpos, er) - end - end - local function put_env(fpos, evar) - local e = os.getenv(evar) - if not e then - run_away_screaming(fpos, "Enviroment variable `"..evar.."' does not exist!") - end - env.code = env.code .. "\""..lua_escape(e).."\"" - end - local function put_code(fpos, code) - env.code = env.code .. code --not much that can fail here... - end - local fenv = {} - for k, v in pairs(_G) do - fenv[k] = v - end - fenv._G = fenv - fenv._ENV = fenv - fenv.call_directive = call_directive - fenv.put_code = put_code - fenv.put_env = put_env - local func = assert(load(lua_code, "=(generated code)", "t", fenv)) - func() - return env.code -end - ---[[ - directive_provider.lua - Provides preprocessor directives - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - ----#include "directives/define.lua" - -function directives.include(env, file) - if (not os.execute("stat "..file..">/dev/null")) then - return false, "File `"..file.."' does not exist!" - end - local f = io.open(file, "r") - local fast = mkast(f, file) - local code = generate(fast) - env.code = env.code .. "\n" .. code .. "\n" - return true -end - -local warned = false -function directives.loadmod(env, mod) - if not warned then - io.stderr:write("Warning: loadmod is depreciated and unsafe. The API differs from luapreproc.\n") - warned = true - end - if (not os.execute("stat "..file..">/dev/null")) then - return false, "Module `"..file.."' does not exist!" - end - local modname, func = dofile(mod) - directives[modname] = func - return true -end - - ---[[ - cfg/minifier_providers.lua - Provides minifier providers. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local providers = {} - -function providers.luamin(cin) - local fn = os.tmpname() - local fh = io.open(fn, "w") - fh:write(cin) - fh:close() - local lmh = io.popen("luamin -f "..fn.." 2>&1", "r") - local dat = lmh:read("*a") - local stat, _, code = lmh:close() - if (code ~= 0) then - return false, dat - end - return dat -end - -function providers.none(cin) - return cin -end - -local argparse = (function() - --- The MIT License (MIT) - --- Copyright (c) 2013 - 2018 Peter Melnichenko - --- Permission is hereby granted, free of charge, to any person obtaining a copy of --- this software and associated documentation files (the "Software"), to deal in --- the Software without restriction, including without limitation the rights to --- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of --- the Software, and to permit persons to whom the Software is furnished to do so, --- subject to the following conditions: - --- The above copyright notice and this permission notice shall be included in all --- copies or substantial portions of the Software. - --- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS --- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR --- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER --- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN --- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -local function deep_update(t1, t2) - for k, v in pairs(t2) do - if type(v) == "table" then - v = deep_update({}, v) - end - - t1[k] = v - end - - return t1 -end - --- A property is a tuple {name, callback}. --- properties.args is number of properties that can be set as arguments --- when calling an object. -local function class(prototype, properties, parent) - -- Class is the metatable of its instances. - local cl = {} - cl.__index = cl - - if parent then - cl.__prototype = deep_update(deep_update({}, parent.__prototype), prototype) - else - cl.__prototype = prototype - end - - if properties then - local names = {} - - -- Create setter methods and fill set of property names. - for _, property in ipairs(properties) do - local name, callback = property[1], property[2] - - cl[name] = function(self, value) - if not callback(self, value) then - self["_" .. name] = value - end - - return self - end - - names[name] = true - end - - function cl.__call(self, ...) - -- When calling an object, if the first argument is a table, - -- interpret keys as property names, else delegate arguments - -- to corresponding setters in order. - if type((...)) == "table" then - for name, value in pairs((...)) do - if names[name] then - self[name](self, value) - end - end - else - local nargs = select("#", ...) - - for i, property in ipairs(properties) do - if i > nargs or i > properties.args then - break - end - - local arg = select(i, ...) - - if arg ~= nil then - self[property[1]](self, arg) - end - end - end - - return self - end - end - - -- If indexing class fails, fallback to its parent. - local class_metatable = {} - class_metatable.__index = parent - - function class_metatable.__call(self, ...) - -- Calling a class returns its instance. - -- Arguments are delegated to the instance. - local object = deep_update({}, self.__prototype) - setmetatable(object, self) - return object(...) - end - - return setmetatable(cl, class_metatable) -end - -local function typecheck(name, types, value) - for _, type_ in ipairs(types) do - if type(value) == type_ then - return true - end - end - - error(("bad property '%s' (%s expected, got %s)"):format(name, table.concat(types, " or "), type(value))) -end - -local function typechecked(name, ...) - local types = {...} - return {name, function(_, value) typecheck(name, types, value) end} -end - -local multiname = {"name", function(self, value) - typecheck("name", {"string"}, value) - - for alias in value:gmatch("%S+") do - self._name = self._name or alias - table.insert(self._aliases, alias) - end - - -- Do not set _name as with other properties. - return true -end} - -local function parse_boundaries(str) - if tonumber(str) then - return tonumber(str), tonumber(str) - end - - if str == "*" then - return 0, math.huge - end - - if str == "+" then - return 1, math.huge - end - - if str == "?" then - return 0, 1 - end - - if str:match "^%d+%-%d+$" then - local min, max = str:match "^(%d+)%-(%d+)$" - return tonumber(min), tonumber(max) - end - - if str:match "^%d+%+$" then - local min = str:match "^(%d+)%+$" - return tonumber(min), math.huge - end -end - -local function boundaries(name) - return {name, function(self, value) - typecheck(name, {"number", "string"}, value) - - local min, max = parse_boundaries(value) - - if not min then - error(("bad property '%s'"):format(name)) - end - - self["_min" .. name], self["_max" .. name] = min, max - end} -end - -local actions = {} - -local option_action = {"action", function(_, value) - typecheck("action", {"function", "string"}, value) - - if type(value) == "string" and not actions[value] then - error(("unknown action '%s'"):format(value)) - end -end} - -local option_init = {"init", function(self) - self._has_init = true -end} - -local option_default = {"default", function(self, value) - if type(value) ~= "string" then - self._init = value - self._has_init = true - return true - end -end} - -local add_help = {"add_help", function(self, value) - typecheck("add_help", {"boolean", "string", "table"}, value) - - if self._has_help then - table.remove(self._options) - self._has_help = false - end - - if value then - local help = self:flag() - :description "Show this help message and exit." - :action(function() - print(self:get_help()) - os.exit(0) - end) - - if value ~= true then - help = help(value) - end - - if not help._name then - help "-h" "--help" - end - - self._has_help = true - end -end} - -local Parser = class({ - _arguments = {}, - _options = {}, - _commands = {}, - _mutexes = {}, - _groups = {}, - _require_command = true, - _handle_options = true -}, { - args = 3, - typechecked("name", "string"), - typechecked("description", "string"), - typechecked("epilog", "string"), - typechecked("usage", "string"), - typechecked("help", "string"), - typechecked("require_command", "boolean"), - typechecked("handle_options", "boolean"), - typechecked("action", "function"), - typechecked("command_target", "string"), - typechecked("help_vertical_space", "number"), - typechecked("usage_margin", "number"), - typechecked("usage_max_width", "number"), - typechecked("help_usage_margin", "number"), - typechecked("help_description_margin", "number"), - typechecked("help_max_width", "number"), - add_help -}) - -local Command = class({ - _aliases = {} -}, { - args = 3, - multiname, - typechecked("description", "string"), - typechecked("epilog", "string"), - typechecked("target", "string"), - typechecked("usage", "string"), - typechecked("help", "string"), - typechecked("require_command", "boolean"), - typechecked("handle_options", "boolean"), - typechecked("action", "function"), - typechecked("command_target", "string"), - typechecked("help_vertical_space", "number"), - typechecked("usage_margin", "number"), - typechecked("usage_max_width", "number"), - typechecked("help_usage_margin", "number"), - typechecked("help_description_margin", "number"), - typechecked("help_max_width", "number"), - typechecked("hidden", "boolean"), - add_help -}, Parser) - -local Argument = class({ - _minargs = 1, - _maxargs = 1, - _mincount = 1, - _maxcount = 1, - _defmode = "unused", - _show_default = true -}, { - args = 5, - typechecked("name", "string"), - typechecked("description", "string"), - option_default, - typechecked("convert", "function", "table"), - boundaries("args"), - typechecked("target", "string"), - typechecked("defmode", "string"), - typechecked("show_default", "boolean"), - typechecked("argname", "string", "table"), - typechecked("hidden", "boolean"), - option_action, - option_init -}) - -local Option = class({ - _aliases = {}, - _mincount = 0, - _overwrite = true -}, { - args = 6, - multiname, - typechecked("description", "string"), - option_default, - typechecked("convert", "function", "table"), - boundaries("args"), - boundaries("count"), - typechecked("target", "string"), - typechecked("defmode", "string"), - typechecked("show_default", "boolean"), - typechecked("overwrite", "boolean"), - typechecked("argname", "string", "table"), - typechecked("hidden", "boolean"), - option_action, - option_init -}, Argument) - -function Parser:_inherit_property(name, default) - local element = self - - while true do - local value = element["_" .. name] - - if value ~= nil then - return value - end - - if not element._parent then - return default - end - - element = element._parent - end -end - -function Argument:_get_argument_list() - local buf = {} - local i = 1 - - while i <= math.min(self._minargs, 3) do - local argname = self:_get_argname(i) - - if self._default and self._defmode:find "a" then - argname = "[" .. argname .. "]" - end - - table.insert(buf, argname) - i = i+1 - end - - while i <= math.min(self._maxargs, 3) do - table.insert(buf, "[" .. self:_get_argname(i) .. "]") - i = i+1 - - if self._maxargs == math.huge then - break - end - end - - if i < self._maxargs then - table.insert(buf, "...") - end - - return buf -end - -function Argument:_get_usage() - local usage = table.concat(self:_get_argument_list(), " ") - - if self._default and self._defmode:find "u" then - if self._maxargs > 1 or (self._minargs == 1 and not self._defmode:find "a") then - usage = "[" .. usage .. "]" - end - end - - return usage -end - -function actions.store_true(result, target) - result[target] = true -end - -function actions.store_false(result, target) - result[target] = false -end - -function actions.store(result, target, argument) - result[target] = argument -end - -function actions.count(result, target, _, overwrite) - if not overwrite then - result[target] = result[target] + 1 - end -end - -function actions.append(result, target, argument, overwrite) - result[target] = result[target] or {} - table.insert(result[target], argument) - - if overwrite then - table.remove(result[target], 1) - end -end - -function actions.concat(result, target, arguments, overwrite) - if overwrite then - error("'concat' action can't handle too many invocations") - end - - result[target] = result[target] or {} - - for _, argument in ipairs(arguments) do - table.insert(result[target], argument) - end -end - -function Argument:_get_action() - local action, init - - if self._maxcount == 1 then - if self._maxargs == 0 then - action, init = "store_true", nil - else - action, init = "store", nil - end - else - if self._maxargs == 0 then - action, init = "count", 0 - else - action, init = "append", {} - end - end - - if self._action then - action = self._action - end - - if self._has_init then - init = self._init - end - - if type(action) == "string" then - action = actions[action] - end - - return action, init -end - --- Returns placeholder for `narg`-th argument. -function Argument:_get_argname(narg) - local argname = self._argname or self:_get_default_argname() - - if type(argname) == "table" then - return argname[narg] - else - return argname - end -end - -function Argument:_get_default_argname() - return "<" .. self._name .. ">" -end - -function Option:_get_default_argname() - return "<" .. self:_get_default_target() .. ">" -end - --- Returns labels to be shown in the help message. -function Argument:_get_label_lines() - return {self._name} -end - -function Option:_get_label_lines() - local argument_list = self:_get_argument_list() - - if #argument_list == 0 then - -- Don't put aliases for simple flags like `-h` on different lines. - return {table.concat(self._aliases, ", ")} - end - - local longest_alias_length = -1 - - for _, alias in ipairs(self._aliases) do - longest_alias_length = math.max(longest_alias_length, #alias) - end - - local argument_list_repr = table.concat(argument_list, " ") - local lines = {} - - for i, alias in ipairs(self._aliases) do - local line = (" "):rep(longest_alias_length - #alias) .. alias .. " " .. argument_list_repr - - if i ~= #self._aliases then - line = line .. "," - end - - table.insert(lines, line) - end - - return lines -end - -function Command:_get_label_lines() - return {table.concat(self._aliases, ", ")} -end - -function Argument:_get_description() - if self._default and self._show_default then - if self._description then - return ("%s (default: %s)"):format(self._description, self._default) - else - return ("default: %s"):format(self._default) - end - else - return self._description or "" - end -end - -function Command:_get_description() - return self._description or "" -end - -function Option:_get_usage() - local usage = self:_get_argument_list() - table.insert(usage, 1, self._name) - usage = table.concat(usage, " ") - - if self._mincount == 0 or self._default then - usage = "[" .. usage .. "]" - end - - return usage -end - -function Argument:_get_default_target() - return self._name -end - -function Option:_get_default_target() - local res - - for _, alias in ipairs(self._aliases) do - if alias:sub(1, 1) == alias:sub(2, 2) then - res = alias:sub(3) - break - end - end - - res = res or self._name:sub(2) - return (res:gsub("-", "_")) -end - -function Option:_is_vararg() - return self._maxargs ~= self._minargs -end - -function Parser:_get_fullname() - local parent = self._parent - local buf = {self._name} - - while parent do - table.insert(buf, 1, parent._name) - parent = parent._parent - end - - return table.concat(buf, " ") -end - -function Parser:_update_charset(charset) - charset = charset or {} - - for _, command in ipairs(self._commands) do - command:_update_charset(charset) - end - - for _, option in ipairs(self._options) do - for _, alias in ipairs(option._aliases) do - charset[alias:sub(1, 1)] = true - end - end - - return charset -end - -function Parser:argument(...) - local argument = Argument(...) - table.insert(self._arguments, argument) - return argument -end - -function Parser:option(...) - local option = Option(...) - - if self._has_help then - table.insert(self._options, #self._options, option) - else - table.insert(self._options, option) - end - - return option -end - -function Parser:flag(...) - return self:option():args(0)(...) -end - -function Parser:command(...) - local command = Command():add_help(true)(...) - command._parent = self - table.insert(self._commands, command) - return command -end - -function Parser:mutex(...) - local elements = {...} - - for i, element in ipairs(elements) do - local mt = getmetatable(element) - assert(mt == Option or mt == Argument, ("bad argument #%d to 'mutex' (Option or Argument expected)"):format(i)) - end - - table.insert(self._mutexes, elements) - return self -end - -function Parser:group(name, ...) - assert(type(name) == "string", ("bad argument #1 to 'group' (string expected, got %s)"):format(type(name))) - - local group = {name = name, ...} - - for i, element in ipairs(group) do - local mt = getmetatable(element) - assert(mt == Option or mt == Argument or mt == Command, - ("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(i + 1)) - end - - table.insert(self._groups, group) - return self -end - -local usage_welcome = "Usage: " - -function Parser:get_usage() - if self._usage then - return self._usage - end - - local usage_margin = self:_inherit_property("usage_margin", #usage_welcome) - local max_usage_width = self:_inherit_property("usage_max_width", 70) - local lines = {usage_welcome .. self:_get_fullname()} - - local function add(s) - if #lines[#lines]+1+#s <= max_usage_width then - lines[#lines] = lines[#lines] .. " " .. s - else - lines[#lines+1] = (" "):rep(usage_margin) .. s - end - end - - -- Normally options are before positional arguments in usage messages. - -- However, vararg options should be after, because they can't be reliable used - -- before a positional argument. - -- Mutexes come into play, too, and are shown as soon as possible. - -- Overall, output usages in the following order: - -- 1. Mutexes that don't have positional arguments or vararg options. - -- 2. Options that are not in any mutexes and are not vararg. - -- 3. Positional arguments - on their own or as a part of a mutex. - -- 4. Remaining mutexes. - -- 5. Remaining options. - - local elements_in_mutexes = {} - local added_elements = {} - local added_mutexes = {} - local argument_to_mutexes = {} - - local function add_mutex(mutex, main_argument) - if added_mutexes[mutex] then - return - end - - added_mutexes[mutex] = true - local buf = {} - - for _, element in ipairs(mutex) do - if not element._hidden and not added_elements[element] then - if getmetatable(element) == Option or element == main_argument then - table.insert(buf, element:_get_usage()) - added_elements[element] = true - end - end - end - - if #buf == 1 then - add(buf[1]) - elseif #buf > 1 then - add("(" .. table.concat(buf, " | ") .. ")") - end - end - - local function add_element(element) - if not element._hidden and not added_elements[element] then - add(element:_get_usage()) - added_elements[element] = true - end - end - - for _, mutex in ipairs(self._mutexes) do - local is_vararg = false - local has_argument = false - - for _, element in ipairs(mutex) do - if getmetatable(element) == Option then - if element:_is_vararg() then - is_vararg = true - end - else - has_argument = true - argument_to_mutexes[element] = argument_to_mutexes[element] or {} - table.insert(argument_to_mutexes[element], mutex) - end - - elements_in_mutexes[element] = true - end - - if not is_vararg and not has_argument then - add_mutex(mutex) - end - end - - for _, option in ipairs(self._options) do - if not elements_in_mutexes[option] and not option:_is_vararg() then - add_element(option) - end - end - - -- Add usages for positional arguments, together with one mutex containing them, if they are in a mutex. - for _, argument in ipairs(self._arguments) do - -- Pick a mutex as a part of which to show this argument, take the first one that's still available. - local mutex - - if elements_in_mutexes[argument] then - for _, argument_mutex in ipairs(argument_to_mutexes[argument]) do - if not added_mutexes[argument_mutex] then - mutex = argument_mutex - end - end - end - - if mutex then - add_mutex(mutex, argument) - else - add_element(argument) - end - end - - for _, mutex in ipairs(self._mutexes) do - add_mutex(mutex) - end - - for _, option in ipairs(self._options) do - add_element(option) - end - - if #self._commands > 0 then - if self._require_command then - add("") - else - add("[]") - end - - add("...") - end - - return table.concat(lines, "\n") -end - -local function split_lines(s) - if s == "" then - return {} - end - - local lines = {} - - if s:sub(-1) ~= "\n" then - s = s .. "\n" - end - - for line in s:gmatch("([^\n]*)\n") do - table.insert(lines, line) - end - - return lines -end - -local function autowrap_line(line, max_length) - -- Algorithm for splitting lines is simple and greedy. - local result_lines = {} - - -- Preserve original indentation of the line, put this at the beginning of each result line. - -- If the first word looks like a list marker ('*', '+', or '-'), add spaces so that starts - -- of the second and the following lines vertically align with the start of the second word. - local indentation = line:match("^ *") - - if line:find("^ *[%*%+%-]") then - indentation = indentation .. " " .. line:match("^ *[%*%+%-]( *)") - end - - -- Parts of the last line being assembled. - local line_parts = {} - - -- Length of the current line. - local line_length = 0 - - -- Index of the next character to consider. - local index = 1 - - while true do - local word_start, word_finish, word = line:find("([^ ]+)", index) - - if not word_start then - -- Ignore trailing spaces, if any. - break - end - - local preceding_spaces = line:sub(index, word_start - 1) - index = word_finish + 1 - - if (#line_parts == 0) or (line_length + #preceding_spaces + #word <= max_length) then - -- Either this is the very first word or it fits as an addition to the current line, add it. - table.insert(line_parts, preceding_spaces) -- For the very first word this adds the indentation. - table.insert(line_parts, word) - line_length = line_length + #preceding_spaces + #word - else - -- Does not fit, finish current line and put the word into a new one. - table.insert(result_lines, table.concat(line_parts)) - line_parts = {indentation, word} - line_length = #indentation + #word - end - end - - if #line_parts > 0 then - table.insert(result_lines, table.concat(line_parts)) - end - - if #result_lines == 0 then - -- Preserve empty lines. - result_lines[1] = "" - end - - return result_lines -end - --- Automatically wraps lines within given array, --- attempting to limit line length to `max_length`. --- Existing line splits are preserved. -local function autowrap(lines, max_length) - local result_lines = {} - - for _, line in ipairs(lines) do - local autowrapped_lines = autowrap_line(line, max_length) - - for _, autowrapped_line in ipairs(autowrapped_lines) do - table.insert(result_lines, autowrapped_line) - end - end - - return result_lines -end - -function Parser:_get_element_help(element) - local label_lines = element:_get_label_lines() - local description_lines = split_lines(element:_get_description()) - - local result_lines = {} - - -- All label lines should have the same length (except the last one, it has no comma). - -- If too long, start description after all the label lines. - -- Otherwise, combine label and description lines. - - local usage_margin_len = self:_inherit_property("help_usage_margin", 3) - local usage_margin = (" "):rep(usage_margin_len) - local description_margin_len = self:_inherit_property("help_description_margin", 25) - local description_margin = (" "):rep(description_margin_len) - - local help_max_width = self:_inherit_property("help_max_width") - - if help_max_width then - local description_max_width = math.max(help_max_width - description_margin_len, 10) - description_lines = autowrap(description_lines, description_max_width) - end - - if #label_lines[1] >= (description_margin_len - usage_margin_len) then - for _, label_line in ipairs(label_lines) do - table.insert(result_lines, usage_margin .. label_line) - end - - for _, description_line in ipairs(description_lines) do - table.insert(result_lines, description_margin .. description_line) - end - else - for i = 1, math.max(#label_lines, #description_lines) do - local label_line = label_lines[i] - local description_line = description_lines[i] - - local line = "" - - if label_line then - line = usage_margin .. label_line - end - - if description_line and description_line ~= "" then - line = line .. (" "):rep(description_margin_len - #line) .. description_line - end - - table.insert(result_lines, line) - end - end - - return table.concat(result_lines, "\n") -end - -local function get_group_types(group) - local types = {} - - for _, element in ipairs(group) do - types[getmetatable(element)] = true - end - - return types -end - -function Parser:_add_group_help(blocks, added_elements, label, elements) - local buf = {label} - - for _, element in ipairs(elements) do - if not element._hidden and not added_elements[element] then - added_elements[element] = true - table.insert(buf, self:_get_element_help(element)) - end - end - - if #buf > 1 then - table.insert(blocks, table.concat(buf, ("\n"):rep(self:_inherit_property("help_vertical_space", 0) + 1))) - end -end - -function Parser:get_help() - if self._help then - return self._help - end - - local blocks = {self:get_usage()} - - local help_max_width = self:_inherit_property("help_max_width") - - if self._description then - local description = self._description - - if help_max_width then - description = table.concat(autowrap(split_lines(description), help_max_width), "\n") - end - - table.insert(blocks, description) - end - - -- 1. Put groups containing arguments first, then other arguments. - -- 2. Put remaining groups containing options, then other options. - -- 3. Put remaining groups containing commands, then other commands. - -- Assume that an element can't be in several groups. - local groups_by_type = { - [Argument] = {}, - [Option] = {}, - [Command] = {} - } - - for _, group in ipairs(self._groups) do - local group_types = get_group_types(group) - - for _, mt in ipairs({Argument, Option, Command}) do - if group_types[mt] then - table.insert(groups_by_type[mt], group) - break - end - end - end - - local default_groups = { - {name = "Arguments", type = Argument, elements = self._arguments}, - {name = "Options", type = Option, elements = self._options}, - {name = "Commands", type = Command, elements = self._commands} - } - - local added_elements = {} - - for _, default_group in ipairs(default_groups) do - local type_groups = groups_by_type[default_group.type] - - for _, group in ipairs(type_groups) do - self:_add_group_help(blocks, added_elements, group.name .. ":", group) - end - - local default_label = default_group.name .. ":" - - if #type_groups > 0 then - default_label = "Other " .. default_label:gsub("^.", string.lower) - end - - self:_add_group_help(blocks, added_elements, default_label, default_group.elements) - end - - if self._epilog then - local epilog = self._epilog - - if help_max_width then - epilog = table.concat(autowrap(split_lines(epilog), help_max_width), "\n") - end - - table.insert(blocks, epilog) - end - - return table.concat(blocks, "\n\n") -end - -local function get_tip(context, wrong_name) - local context_pool = {} - local possible_name - local possible_names = {} - - for name in pairs(context) do - if type(name) == "string" then - for i = 1, #name do - possible_name = name:sub(1, i - 1) .. name:sub(i + 1) - - if not context_pool[possible_name] then - context_pool[possible_name] = {} - end - - table.insert(context_pool[possible_name], name) - end - end - end - - for i = 1, #wrong_name + 1 do - possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1) - - if context[possible_name] then - possible_names[possible_name] = true - elseif context_pool[possible_name] then - for _, name in ipairs(context_pool[possible_name]) do - possible_names[name] = true - end - end - end - - local first = next(possible_names) - - if first then - if next(possible_names, first) then - local possible_names_arr = {} - - for name in pairs(possible_names) do - table.insert(possible_names_arr, "'" .. name .. "'") - end - - table.sort(possible_names_arr) - return "\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?" - else - return "\nDid you mean '" .. first .. "'?" - end - else - return "" - end -end - -local ElementState = class({ - invocations = 0 -}) - -function ElementState:__call(state, element) - self.state = state - self.result = state.result - self.element = element - self.target = element._target or element:_get_default_target() - self.action, self.result[self.target] = element:_get_action() - return self -end - -function ElementState:error(fmt, ...) - self.state:error(fmt, ...) -end - -function ElementState:convert(argument, index) - local converter = self.element._convert - - if converter then - local ok, err - - if type(converter) == "function" then - ok, err = converter(argument) - elseif type(converter[index]) == "function" then - ok, err = converter[index](argument) - else - ok = converter[argument] - end - - if ok == nil then - self:error(err and "%s" or "malformed argument '%s'", err or argument) - end - - argument = ok - end - - return argument -end - -function ElementState:default(mode) - return self.element._defmode:find(mode) and self.element._default -end - -local function bound(noun, min, max, is_max) - local res = "" - - if min ~= max then - res = "at " .. (is_max and "most" or "least") .. " " - end - - local number = is_max and max or min - return res .. tostring(number) .. " " .. noun .. (number == 1 and "" or "s") -end - -function ElementState:set_name(alias) - self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name) -end - -function ElementState:invoke() - self.open = true - self.overwrite = false - - if self.invocations >= self.element._maxcount then - if self.element._overwrite then - self.overwrite = true - else - local num_times_repr = bound("time", self.element._mincount, self.element._maxcount, true) - self:error("%s must be used %s", self.name, num_times_repr) - end - else - self.invocations = self.invocations + 1 - end - - self.args = {} - - if self.element._maxargs <= 0 then - self:close() - end - - return self.open -end - -function ElementState:pass(argument) - argument = self:convert(argument, #self.args + 1) - table.insert(self.args, argument) - - if #self.args >= self.element._maxargs then - self:close() - end - - return self.open -end - -function ElementState:complete_invocation() - while #self.args < self.element._minargs do - self:pass(self.element._default) - end -end - -function ElementState:close() - if self.open then - self.open = false - - if #self.args < self.element._minargs then - if self:default("a") then - self:complete_invocation() - else - if #self.args == 0 then - if getmetatable(self.element) == Argument then - self:error("missing %s", self.name) - elseif self.element._maxargs == 1 then - self:error("%s requires an argument", self.name) - end - end - - self:error("%s requires %s", self.name, bound("argument", self.element._minargs, self.element._maxargs)) - end - end - - local args - - if self.element._maxargs == 0 then - args = self.args[1] - elseif self.element._maxargs == 1 then - if self.element._minargs == 0 and self.element._mincount ~= self.element._maxcount then - args = self.args - else - args = self.args[1] - end - else - args = self.args - end - - self.action(self.result, self.target, args, self.overwrite) - end -end - -local ParseState = class({ - result = {}, - options = {}, - arguments = {}, - argument_i = 1, - element_to_mutexes = {}, - mutex_to_element_state = {}, - command_actions = {} -}) - -function ParseState:__call(parser, error_handler) - self.parser = parser - self.error_handler = error_handler - self.charset = parser:_update_charset() - self:switch(parser) - return self -end - -function ParseState:error(fmt, ...) - self.error_handler(self.parser, fmt:format(...)) -end - -function ParseState:switch(parser) - self.parser = parser - - if parser._action then - table.insert(self.command_actions, {action = parser._action, name = parser._name}) - end - - for _, option in ipairs(parser._options) do - option = ElementState(self, option) - table.insert(self.options, option) - - for _, alias in ipairs(option.element._aliases) do - self.options[alias] = option - end - end - - for _, mutex in ipairs(parser._mutexes) do - for _, element in ipairs(mutex) do - if not self.element_to_mutexes[element] then - self.element_to_mutexes[element] = {} - end - - table.insert(self.element_to_mutexes[element], mutex) - end - end - - for _, argument in ipairs(parser._arguments) do - argument = ElementState(self, argument) - table.insert(self.arguments, argument) - argument:set_name() - argument:invoke() - end - - self.handle_options = parser._handle_options - self.argument = self.arguments[self.argument_i] - self.commands = parser._commands - - for _, command in ipairs(self.commands) do - for _, alias in ipairs(command._aliases) do - self.commands[alias] = command - end - end -end - -function ParseState:get_option(name) - local option = self.options[name] - - if not option then - self:error("unknown option '%s'%s", name, get_tip(self.options, name)) - else - return option - end -end - -function ParseState:get_command(name) - local command = self.commands[name] - - if not command then - if #self.commands > 0 then - self:error("unknown command '%s'%s", name, get_tip(self.commands, name)) - else - self:error("too many arguments") - end - else - return command - end -end - -function ParseState:check_mutexes(element_state) - if self.element_to_mutexes[element_state.element] then - for _, mutex in ipairs(self.element_to_mutexes[element_state.element]) do - local used_element_state = self.mutex_to_element_state[mutex] - - if used_element_state and used_element_state ~= element_state then - self:error("%s can not be used together with %s", element_state.name, used_element_state.name) - else - self.mutex_to_element_state[mutex] = element_state - end - end - end -end - -function ParseState:invoke(option, name) - self:close() - option:set_name(name) - self:check_mutexes(option, name) - - if option:invoke() then - self.option = option - end -end - -function ParseState:pass(arg) - if self.option then - if not self.option:pass(arg) then - self.option = nil - end - elseif self.argument then - self:check_mutexes(self.argument) - - if not self.argument:pass(arg) then - self.argument_i = self.argument_i + 1 - self.argument = self.arguments[self.argument_i] - end - else - local command = self:get_command(arg) - self.result[command._target or command._name] = true - - if self.parser._command_target then - self.result[self.parser._command_target] = command._name - end - - self:switch(command) - end -end - -function ParseState:close() - if self.option then - self.option:close() - self.option = nil - end -end - -function ParseState:finalize() - self:close() - - for i = self.argument_i, #self.arguments do - local argument = self.arguments[i] - if #argument.args == 0 and argument:default("u") then - argument:complete_invocation() - else - argument:close() - end - end - - if self.parser._require_command and #self.commands > 0 then - self:error("a command is required") - end - - for _, option in ipairs(self.options) do - option.name = option.name or ("option '%s'"):format(option.element._name) - - if option.invocations == 0 then - if option:default("u") then - option:invoke() - option:complete_invocation() - option:close() - end - end - - local mincount = option.element._mincount - - if option.invocations < mincount then - if option:default("a") then - while option.invocations < mincount do - option:invoke() - option:close() - end - elseif option.invocations == 0 then - self:error("missing %s", option.name) - else - self:error("%s must be used %s", option.name, bound("time", mincount, option.element._maxcount)) - end - end - end - - for i = #self.command_actions, 1, -1 do - self.command_actions[i].action(self.result, self.command_actions[i].name) - end -end - -function ParseState:parse(args) - for _, arg in ipairs(args) do - local plain = true - - if self.handle_options then - local first = arg:sub(1, 1) - - if self.charset[first] then - if #arg > 1 then - plain = false - - if arg:sub(2, 2) == first then - if #arg == 2 then - if self.options[arg] then - local option = self:get_option(arg) - self:invoke(option, arg) - else - self:close() - end - - self.handle_options = false - else - local equals = arg:find "=" - if equals then - local name = arg:sub(1, equals - 1) - local option = self:get_option(name) - - if option.element._maxargs <= 0 then - self:error("option '%s' does not take arguments", name) - end - - self:invoke(option, name) - self:pass(arg:sub(equals + 1)) - else - local option = self:get_option(arg) - self:invoke(option, arg) - end - end - else - for i = 2, #arg do - local name = first .. arg:sub(i, i) - local option = self:get_option(name) - self:invoke(option, name) - - if i ~= #arg and option.element._maxargs > 0 then - self:pass(arg:sub(i + 1)) - break - end - end - end - end - end - end - - if plain then - self:pass(arg) - end - end - - self:finalize() - return self.result -end - -function Parser:error(msg) - io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg)) - os.exit(1) -end - --- Compatibility with strict.lua and other checkers: -local default_cmdline = rawget(_G, "arg") or {} - -function Parser:_parse(args, error_handler) - return ParseState(self, error_handler):parse(args or default_cmdline) -end - -function Parser:parse(args) - return self:_parse(args, self.error) -end - -local function xpcall_error_handler(err) - return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2) -end - -function Parser:pparse(args) - local parse_error - - local ok, result = xpcall(function() - return self:_parse(args, function(_, err) - parse_error = err - error(err, 0) - end) - end, xpcall_error_handler) - - if ok then - return true, result - elseif not parse_error then - error(result, 0) - else - return false, parse_error - end -end - -local argparse = {} - -argparse.version = "0.6.0" - -setmetatable(argparse, {__call = function(_, ...) - return Parser(default_cmdline[0]):add_help(true)(...) -end}) - -return argparse - -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() diff --git a/build/luacomp-5.1.lua b/build/luacomp-5.1.lua deleted file mode 100644 index 329bda1..0000000 --- a/build/luacomp-5.1.lua +++ /dev/null @@ -1,514 +0,0 @@ -#!/usr/bin/env lua5.1 ---[[ - init.lua - Main file of LuaComp - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - - -local function _sv(k, v) - _G[k] = v - --os.setenv(k, tostring(v)) -end - -_sv("LUACOMP_V_MAJ", 1) -_sv("LUACOMP_V_MIN", 1) -_sv("LUACOMP_V_PAT", 0) -_sv("LUACOMP_VERSION", LUACOMP_V_MAJ.."."..LUACOMP_V_MIN.."."..LUACOMP_V_PAT) -_sv("LUACOMP_NAME", "LuaComp") - ---[[ - ast.lua - Generates a structure for use in preprocessing. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function nextc(f, c) - c = c or 1 - return f:read(c) -end - -local function peek(f, c) - c = c or 1 - local z = f:read(c) - f:seek("cur", -c) - return z -end - -local function skip(f, c) - c = c or 1 - return f:seek("cur", c) -end - -local ws = { - ["\t"] = true, - [" "] = true -} - -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 - 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 - hex = hex .. lc - end - end - return tonumber(hex, 16) -end - -local function parse_number(f, c) - local lc = " " - local num = c - while 48 <= lc:byte() and lc:byte() <= 57 do - lc = nextc(f) - if (48 <= lc:byte() and lc:byte() <= 57) then - num = num .. lc - end - end - return tonumber(hex, 10) -end - -local esct = { - ["t"] = "\t", - ["n"] = "\n", - ["r"] = "\r", - ["\\"] = "\\\\" -} - -for i=0, 9 do - esct[tostring(i)] = string.char(i) -end - -local function parse_dblquote(f) - local val = "" - while peek(f) ~= "\"" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_snglquote(f) - local val = "" - while peek(f) ~= "\'" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_envarg(f) - local val = "" - while peek(f) ~= ")" do - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - val = val .. nextc(f) - end - skip(f) - return val -end - -local function parse_directive(f) - local lc = "_" - local name = "" - local args = {} - local carg = "" - while not ws[lc] do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name} - elseif not ws[lc] then - name = name .. lc - end - end - while true do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name, args=args} - elseif lc == "0" and peek(f) == "x" then - skip(f) - local val = parse_hex(f) - args[#args+1] = val - elseif 48 <= lc:byte() and lc:byte() <= 57 then - local val = parse_number(f, lc) - args[#args+1] = val - elseif lc == "\"" then - local val, e = parse_dblquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "\'" then - local val, e = parse_snglquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "$" and peek(f) == "(" then - skip(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 not ws[lc] then - return nil, "Syntax error" - end - end -end - -local function mkast(f, n) - io.stderr:write("PROC\t",n,"\n") - local lc = " " - local lpos = 1 - local ilpos = 1 - local tree = {} - local code = "" - local branches = {} - local function add_code() - tree[#tree+1] = {type="code", data=code, file=n, line=lpos} - code = "" - end - local function parse_error(e) - io.stderr:write("ERROR:"..n..":"..lpos..": "..e.."\n") - os.exit(1) - end - while lc and lc ~= "" do - lc = nextc(f) - if (lc == "-" and ilpos == 1) then - if (peek(f, 2) == "-#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "/" and ilpos == 1) then - if (peek(f, 2) == "/#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "$" and peek(f) == "(") then - add_code() - skip(f) - local val, e = parse_envarg(f) - if not val then - parse_error(e) - end - tree[#tree+1] = {type="envvar", var=val, file=n, line=lpos} - 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="lua", 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="lua_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) - end - lpos = lpos+1 - ilpos = 1 - code = code .. "\n" - else - code = code .. (lc or "") - ilpos = ilpos+1 - end - end - add_code() - return tree -end - ---[[ - generator.lua - Generates the code. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function lua_escape(code) - return code:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n") -end - -local directives = {} - -local function generate(ast) - local lua_code = "" - for i=1, #ast do - local leaf = ast[i] - if (leaf.type == "lua") then - lua_code = lua_code .. leaf.code - elseif (leaf.type == "directive") then - local stargs = {} - for i=1, #leaf.args do - local arg = leaf.args[i] - if (type(arg) == "string") then - stargs[i] = "\""..lua_escape(arg).."\"" - elseif (type(arg) == "number") then - stargs[i] = tostring(arg) - end - end - lua_code = lua_code .. "call_directive(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.name.."\","..table.concat(stargs, ",")..")" - elseif (leaf.type == "envvar") then - lua_code = lua_code .. "put_env(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.var.."\")" - elseif (leaf.type == "code") then - 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.."))" - else - io.stderr:write("ERROR: Internal catastrophic failure, unknown type "..leaf.type.."\n") - os.exit(1) - end - lua_code = lua_code .. "\n" - end - local env = {code = ""} - local function run_away_screaming(fpos, err) - io.stdout:write("ERROR: "..fpos..": "..err.."\n") - os.exit(1) - end - local function call_directive(fpos, dname, ...) - if (not directives[dname]) then - run_away_screaming(fpos, "Invalid directive name `"..dname.."'") - end - local r, er = directives[dname](env, ...) - if (not r) then - run_away_screaming(fpos, er) - end - end - local function put_env(fpos, evar) - local e = os.getenv(evar) - if not e then - run_away_screaming(fpos, "Enviroment variable `"..evar.."' does not exist!") - end - env.code = env.code .. "\""..lua_escape(e).."\"" - end - local function put_code(fpos, code) - env.code = env.code .. code --not much that can fail here... - end - local fenv = {} - for k, v in pairs(_G) do - fenv[k] = v - end - fenv._G = fenv - fenv._ENV = fenv - fenv.call_directive = call_directive - fenv.put_code = put_code - fenv.put_env = put_env - local func = assert(load(lua_code, "=(generated code)", "t", fenv)) - func() - return env.code -end - ---[[ - directive_provider.lua - Provides preprocessor directives - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - ----#include "directives/define.lua" - -function directives.include(env, file) - if (not os.execute("stat "..file..">/dev/null")) then - return false, "File `"..file.."' does not exist!" - end - local f = io.open(file, "r") - local fast = mkast(f, file) - local code = generate(fast) - env.code = env.code .. "\n" .. code .. "\n" - return true -end - -local warned = false -function directives.loadmod(env, mod) - if not warned then - io.stderr:write("Warning: loadmod is depreciated and unsafe. The API differs from luapreproc.\n") - warned = true - end - if (not os.execute("stat "..file..">/dev/null")) then - return false, "Module `"..file.."' does not exist!" - end - local modname, func = dofile(mod) - directives[modname] = func - return true -end - - ---[[ - cfg/minifier_providers.lua - Provides minifier providers. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local providers = {} - -function providers.luamin(cin) - local fn = os.tmpname() - local fh = io.open(fn, "w") - fh:write(cin) - fh:close() - local lmh = io.popen("luamin -f "..fn.." 2>&1", "r") - local dat = lmh:read("*a") - local stat, _, code = lmh:close() - if (code ~= 0) then - return false, dat - end - return dat -end - -function providers.none(cin) - return cin -end - -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 = 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() diff --git a/build/luacomp-5.2-static.lua b/build/luacomp-5.2-static.lua deleted file mode 100644 index 32a1657..0000000 --- a/build/luacomp-5.2-static.lua +++ /dev/null @@ -1,2044 +0,0 @@ -#!/usr/bin/env lua5.2 ---[[ - staticinit.lua - Main file of LuaComp, directly includes argparse. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - - -local function _sv(k, v) - _G[k] = v - --os.setenv(k, tostring(v)) -end - -_sv("LUACOMP_V_MAJ", 1) -_sv("LUACOMP_V_MIN", 1) -_sv("LUACOMP_V_PAT", 0) -_sv("LUACOMP_VERSION", LUACOMP_V_MAJ.."."..LUACOMP_V_MIN.."."..LUACOMP_V_PAT) -_sv("LUACOMP_NAME", "LuaComp") - ---[[ - ast.lua - Generates a structure for use in preprocessing. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function nextc(f, c) - c = c or 1 - return f:read(c) -end - -local function peek(f, c) - c = c or 1 - local z = f:read(c) - f:seek("cur", -c) - return z -end - -local function skip(f, c) - c = c or 1 - return f:seek("cur", c) -end - -local ws = { - ["\t"] = true, - [" "] = true -} - -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 - 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 - hex = hex .. lc - end - end - return tonumber(hex, 16) -end - -local function parse_number(f, c) - local lc = " " - local num = c - while 48 <= lc:byte() and lc:byte() <= 57 do - lc = nextc(f) - if (48 <= lc:byte() and lc:byte() <= 57) then - num = num .. lc - end - end - return tonumber(hex, 10) -end - -local esct = { - ["t"] = "\t", - ["n"] = "\n", - ["r"] = "\r", - ["\\"] = "\\\\" -} - -for i=0, 9 do - esct[tostring(i)] = string.char(i) -end - -local function parse_dblquote(f) - local val = "" - while peek(f) ~= "\"" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_snglquote(f) - local val = "" - while peek(f) ~= "\'" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_envarg(f) - local val = "" - while peek(f) ~= ")" do - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - val = val .. nextc(f) - end - skip(f) - return val -end - -local function parse_directive(f) - local lc = "_" - local name = "" - local args = {} - local carg = "" - while not ws[lc] do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name} - elseif not ws[lc] then - name = name .. lc - end - end - while true do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name, args=args} - elseif lc == "0" and peek(f) == "x" then - skip(f) - local val = parse_hex(f) - args[#args+1] = val - elseif 48 <= lc:byte() and lc:byte() <= 57 then - local val = parse_number(f, lc) - args[#args+1] = val - elseif lc == "\"" then - local val, e = parse_dblquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "\'" then - local val, e = parse_snglquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "$" and peek(f) == "(" then - skip(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 not ws[lc] then - return nil, "Syntax error" - end - end -end - -local function mkast(f, n) - io.stderr:write("PROC\t",n,"\n") - local lc = " " - local lpos = 1 - local ilpos = 1 - local tree = {} - local code = "" - local branches = {} - local function add_code() - tree[#tree+1] = {type="code", data=code, file=n, line=lpos} - code = "" - end - local function parse_error(e) - io.stderr:write("ERROR:"..n..":"..lpos..": "..e.."\n") - os.exit(1) - end - while lc and lc ~= "" do - lc = nextc(f) - if (lc == "-" and ilpos == 1) then - if (peek(f, 2) == "-#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "/" and ilpos == 1) then - if (peek(f, 2) == "/#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "$" and peek(f) == "(") then - add_code() - skip(f) - local val, e = parse_envarg(f) - if not val then - parse_error(e) - end - tree[#tree+1] = {type="envvar", var=val, file=n, line=lpos} - 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="lua", 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="lua_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) - end - lpos = lpos+1 - ilpos = 1 - code = code .. "\n" - else - code = code .. (lc or "") - ilpos = ilpos+1 - end - end - add_code() - return tree -end - ---[[ - generator.lua - Generates the code. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function lua_escape(code) - return code:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n") -end - -local directives = {} - -local function generate(ast) - local lua_code = "" - for i=1, #ast do - local leaf = ast[i] - if (leaf.type == "lua") then - lua_code = lua_code .. leaf.code - elseif (leaf.type == "directive") then - local stargs = {} - for i=1, #leaf.args do - local arg = leaf.args[i] - if (type(arg) == "string") then - stargs[i] = "\""..lua_escape(arg).."\"" - elseif (type(arg) == "number") then - stargs[i] = tostring(arg) - end - end - lua_code = lua_code .. "call_directive(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.name.."\","..table.concat(stargs, ",")..")" - elseif (leaf.type == "envvar") then - lua_code = lua_code .. "put_env(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.var.."\")" - elseif (leaf.type == "code") then - 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.."))" - else - io.stderr:write("ERROR: Internal catastrophic failure, unknown type "..leaf.type.."\n") - os.exit(1) - end - lua_code = lua_code .. "\n" - end - local env = {code = ""} - local function run_away_screaming(fpos, err) - io.stdout:write("ERROR: "..fpos..": "..err.."\n") - os.exit(1) - end - local function call_directive(fpos, dname, ...) - if (not directives[dname]) then - run_away_screaming(fpos, "Invalid directive name `"..dname.."'") - end - local r, er = directives[dname](env, ...) - if (not r) then - run_away_screaming(fpos, er) - end - end - local function put_env(fpos, evar) - local e = os.getenv(evar) - if not e then - run_away_screaming(fpos, "Enviroment variable `"..evar.."' does not exist!") - end - env.code = env.code .. "\""..lua_escape(e).."\"" - end - local function put_code(fpos, code) - env.code = env.code .. code --not much that can fail here... - end - local fenv = {} - for k, v in pairs(_G) do - fenv[k] = v - end - fenv._G = fenv - fenv._ENV = fenv - fenv.call_directive = call_directive - fenv.put_code = put_code - fenv.put_env = put_env - local func = assert(load(lua_code, "=(generated code)", "t", fenv)) - func() - return env.code -end - ---[[ - directive_provider.lua - Provides preprocessor directives - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - ----#include "directives/define.lua" - -function directives.include(env, file) - if (not os.execute("stat "..file..">/dev/null")) then - return false, "File `"..file.."' does not exist!" - end - local f = io.open(file, "r") - local fast = mkast(f, file) - local code = generate(fast) - env.code = env.code .. "\n" .. code .. "\n" - return true -end - -local warned = false -function directives.loadmod(env, mod) - if not warned then - io.stderr:write("Warning: loadmod is depreciated and unsafe. The API differs from luapreproc.\n") - warned = true - end - if (not os.execute("stat "..file..">/dev/null")) then - return false, "Module `"..file.."' does not exist!" - end - local modname, func = dofile(mod) - directives[modname] = func - return true -end - - ---[[ - cfg/minifier_providers.lua - Provides minifier providers. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local providers = {} - -function providers.luamin(cin) - local fn = os.tmpname() - local fh = io.open(fn, "w") - fh:write(cin) - fh:close() - local lmh = io.popen("luamin -f "..fn.." 2>&1", "r") - local dat = lmh:read("*a") - local stat, _, code = lmh:close() - if (code ~= 0) then - return false, dat - end - return dat -end - -function providers.none(cin) - return cin -end - -local argparse = (function() - --- The MIT License (MIT) - --- Copyright (c) 2013 - 2018 Peter Melnichenko - --- Permission is hereby granted, free of charge, to any person obtaining a copy of --- this software and associated documentation files (the "Software"), to deal in --- the Software without restriction, including without limitation the rights to --- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of --- the Software, and to permit persons to whom the Software is furnished to do so, --- subject to the following conditions: - --- The above copyright notice and this permission notice shall be included in all --- copies or substantial portions of the Software. - --- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS --- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR --- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER --- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN --- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -local function deep_update(t1, t2) - for k, v in pairs(t2) do - if type(v) == "table" then - v = deep_update({}, v) - end - - t1[k] = v - end - - return t1 -end - --- A property is a tuple {name, callback}. --- properties.args is number of properties that can be set as arguments --- when calling an object. -local function class(prototype, properties, parent) - -- Class is the metatable of its instances. - local cl = {} - cl.__index = cl - - if parent then - cl.__prototype = deep_update(deep_update({}, parent.__prototype), prototype) - else - cl.__prototype = prototype - end - - if properties then - local names = {} - - -- Create setter methods and fill set of property names. - for _, property in ipairs(properties) do - local name, callback = property[1], property[2] - - cl[name] = function(self, value) - if not callback(self, value) then - self["_" .. name] = value - end - - return self - end - - names[name] = true - end - - function cl.__call(self, ...) - -- When calling an object, if the first argument is a table, - -- interpret keys as property names, else delegate arguments - -- to corresponding setters in order. - if type((...)) == "table" then - for name, value in pairs((...)) do - if names[name] then - self[name](self, value) - end - end - else - local nargs = select("#", ...) - - for i, property in ipairs(properties) do - if i > nargs or i > properties.args then - break - end - - local arg = select(i, ...) - - if arg ~= nil then - self[property[1]](self, arg) - end - end - end - - return self - end - end - - -- If indexing class fails, fallback to its parent. - local class_metatable = {} - class_metatable.__index = parent - - function class_metatable.__call(self, ...) - -- Calling a class returns its instance. - -- Arguments are delegated to the instance. - local object = deep_update({}, self.__prototype) - setmetatable(object, self) - return object(...) - end - - return setmetatable(cl, class_metatable) -end - -local function typecheck(name, types, value) - for _, type_ in ipairs(types) do - if type(value) == type_ then - return true - end - end - - error(("bad property '%s' (%s expected, got %s)"):format(name, table.concat(types, " or "), type(value))) -end - -local function typechecked(name, ...) - local types = {...} - return {name, function(_, value) typecheck(name, types, value) end} -end - -local multiname = {"name", function(self, value) - typecheck("name", {"string"}, value) - - for alias in value:gmatch("%S+") do - self._name = self._name or alias - table.insert(self._aliases, alias) - end - - -- Do not set _name as with other properties. - return true -end} - -local function parse_boundaries(str) - if tonumber(str) then - return tonumber(str), tonumber(str) - end - - if str == "*" then - return 0, math.huge - end - - if str == "+" then - return 1, math.huge - end - - if str == "?" then - return 0, 1 - end - - if str:match "^%d+%-%d+$" then - local min, max = str:match "^(%d+)%-(%d+)$" - return tonumber(min), tonumber(max) - end - - if str:match "^%d+%+$" then - local min = str:match "^(%d+)%+$" - return tonumber(min), math.huge - end -end - -local function boundaries(name) - return {name, function(self, value) - typecheck(name, {"number", "string"}, value) - - local min, max = parse_boundaries(value) - - if not min then - error(("bad property '%s'"):format(name)) - end - - self["_min" .. name], self["_max" .. name] = min, max - end} -end - -local actions = {} - -local option_action = {"action", function(_, value) - typecheck("action", {"function", "string"}, value) - - if type(value) == "string" and not actions[value] then - error(("unknown action '%s'"):format(value)) - end -end} - -local option_init = {"init", function(self) - self._has_init = true -end} - -local option_default = {"default", function(self, value) - if type(value) ~= "string" then - self._init = value - self._has_init = true - return true - end -end} - -local add_help = {"add_help", function(self, value) - typecheck("add_help", {"boolean", "string", "table"}, value) - - if self._has_help then - table.remove(self._options) - self._has_help = false - end - - if value then - local help = self:flag() - :description "Show this help message and exit." - :action(function() - print(self:get_help()) - os.exit(0) - end) - - if value ~= true then - help = help(value) - end - - if not help._name then - help "-h" "--help" - end - - self._has_help = true - end -end} - -local Parser = class({ - _arguments = {}, - _options = {}, - _commands = {}, - _mutexes = {}, - _groups = {}, - _require_command = true, - _handle_options = true -}, { - args = 3, - typechecked("name", "string"), - typechecked("description", "string"), - typechecked("epilog", "string"), - typechecked("usage", "string"), - typechecked("help", "string"), - typechecked("require_command", "boolean"), - typechecked("handle_options", "boolean"), - typechecked("action", "function"), - typechecked("command_target", "string"), - typechecked("help_vertical_space", "number"), - typechecked("usage_margin", "number"), - typechecked("usage_max_width", "number"), - typechecked("help_usage_margin", "number"), - typechecked("help_description_margin", "number"), - typechecked("help_max_width", "number"), - add_help -}) - -local Command = class({ - _aliases = {} -}, { - args = 3, - multiname, - typechecked("description", "string"), - typechecked("epilog", "string"), - typechecked("target", "string"), - typechecked("usage", "string"), - typechecked("help", "string"), - typechecked("require_command", "boolean"), - typechecked("handle_options", "boolean"), - typechecked("action", "function"), - typechecked("command_target", "string"), - typechecked("help_vertical_space", "number"), - typechecked("usage_margin", "number"), - typechecked("usage_max_width", "number"), - typechecked("help_usage_margin", "number"), - typechecked("help_description_margin", "number"), - typechecked("help_max_width", "number"), - typechecked("hidden", "boolean"), - add_help -}, Parser) - -local Argument = class({ - _minargs = 1, - _maxargs = 1, - _mincount = 1, - _maxcount = 1, - _defmode = "unused", - _show_default = true -}, { - args = 5, - typechecked("name", "string"), - typechecked("description", "string"), - option_default, - typechecked("convert", "function", "table"), - boundaries("args"), - typechecked("target", "string"), - typechecked("defmode", "string"), - typechecked("show_default", "boolean"), - typechecked("argname", "string", "table"), - typechecked("hidden", "boolean"), - option_action, - option_init -}) - -local Option = class({ - _aliases = {}, - _mincount = 0, - _overwrite = true -}, { - args = 6, - multiname, - typechecked("description", "string"), - option_default, - typechecked("convert", "function", "table"), - boundaries("args"), - boundaries("count"), - typechecked("target", "string"), - typechecked("defmode", "string"), - typechecked("show_default", "boolean"), - typechecked("overwrite", "boolean"), - typechecked("argname", "string", "table"), - typechecked("hidden", "boolean"), - option_action, - option_init -}, Argument) - -function Parser:_inherit_property(name, default) - local element = self - - while true do - local value = element["_" .. name] - - if value ~= nil then - return value - end - - if not element._parent then - return default - end - - element = element._parent - end -end - -function Argument:_get_argument_list() - local buf = {} - local i = 1 - - while i <= math.min(self._minargs, 3) do - local argname = self:_get_argname(i) - - if self._default and self._defmode:find "a" then - argname = "[" .. argname .. "]" - end - - table.insert(buf, argname) - i = i+1 - end - - while i <= math.min(self._maxargs, 3) do - table.insert(buf, "[" .. self:_get_argname(i) .. "]") - i = i+1 - - if self._maxargs == math.huge then - break - end - end - - if i < self._maxargs then - table.insert(buf, "...") - end - - return buf -end - -function Argument:_get_usage() - local usage = table.concat(self:_get_argument_list(), " ") - - if self._default and self._defmode:find "u" then - if self._maxargs > 1 or (self._minargs == 1 and not self._defmode:find "a") then - usage = "[" .. usage .. "]" - end - end - - return usage -end - -function actions.store_true(result, target) - result[target] = true -end - -function actions.store_false(result, target) - result[target] = false -end - -function actions.store(result, target, argument) - result[target] = argument -end - -function actions.count(result, target, _, overwrite) - if not overwrite then - result[target] = result[target] + 1 - end -end - -function actions.append(result, target, argument, overwrite) - result[target] = result[target] or {} - table.insert(result[target], argument) - - if overwrite then - table.remove(result[target], 1) - end -end - -function actions.concat(result, target, arguments, overwrite) - if overwrite then - error("'concat' action can't handle too many invocations") - end - - result[target] = result[target] or {} - - for _, argument in ipairs(arguments) do - table.insert(result[target], argument) - end -end - -function Argument:_get_action() - local action, init - - if self._maxcount == 1 then - if self._maxargs == 0 then - action, init = "store_true", nil - else - action, init = "store", nil - end - else - if self._maxargs == 0 then - action, init = "count", 0 - else - action, init = "append", {} - end - end - - if self._action then - action = self._action - end - - if self._has_init then - init = self._init - end - - if type(action) == "string" then - action = actions[action] - end - - return action, init -end - --- Returns placeholder for `narg`-th argument. -function Argument:_get_argname(narg) - local argname = self._argname or self:_get_default_argname() - - if type(argname) == "table" then - return argname[narg] - else - return argname - end -end - -function Argument:_get_default_argname() - return "<" .. self._name .. ">" -end - -function Option:_get_default_argname() - return "<" .. self:_get_default_target() .. ">" -end - --- Returns labels to be shown in the help message. -function Argument:_get_label_lines() - return {self._name} -end - -function Option:_get_label_lines() - local argument_list = self:_get_argument_list() - - if #argument_list == 0 then - -- Don't put aliases for simple flags like `-h` on different lines. - return {table.concat(self._aliases, ", ")} - end - - local longest_alias_length = -1 - - for _, alias in ipairs(self._aliases) do - longest_alias_length = math.max(longest_alias_length, #alias) - end - - local argument_list_repr = table.concat(argument_list, " ") - local lines = {} - - for i, alias in ipairs(self._aliases) do - local line = (" "):rep(longest_alias_length - #alias) .. alias .. " " .. argument_list_repr - - if i ~= #self._aliases then - line = line .. "," - end - - table.insert(lines, line) - end - - return lines -end - -function Command:_get_label_lines() - return {table.concat(self._aliases, ", ")} -end - -function Argument:_get_description() - if self._default and self._show_default then - if self._description then - return ("%s (default: %s)"):format(self._description, self._default) - else - return ("default: %s"):format(self._default) - end - else - return self._description or "" - end -end - -function Command:_get_description() - return self._description or "" -end - -function Option:_get_usage() - local usage = self:_get_argument_list() - table.insert(usage, 1, self._name) - usage = table.concat(usage, " ") - - if self._mincount == 0 or self._default then - usage = "[" .. usage .. "]" - end - - return usage -end - -function Argument:_get_default_target() - return self._name -end - -function Option:_get_default_target() - local res - - for _, alias in ipairs(self._aliases) do - if alias:sub(1, 1) == alias:sub(2, 2) then - res = alias:sub(3) - break - end - end - - res = res or self._name:sub(2) - return (res:gsub("-", "_")) -end - -function Option:_is_vararg() - return self._maxargs ~= self._minargs -end - -function Parser:_get_fullname() - local parent = self._parent - local buf = {self._name} - - while parent do - table.insert(buf, 1, parent._name) - parent = parent._parent - end - - return table.concat(buf, " ") -end - -function Parser:_update_charset(charset) - charset = charset or {} - - for _, command in ipairs(self._commands) do - command:_update_charset(charset) - end - - for _, option in ipairs(self._options) do - for _, alias in ipairs(option._aliases) do - charset[alias:sub(1, 1)] = true - end - end - - return charset -end - -function Parser:argument(...) - local argument = Argument(...) - table.insert(self._arguments, argument) - return argument -end - -function Parser:option(...) - local option = Option(...) - - if self._has_help then - table.insert(self._options, #self._options, option) - else - table.insert(self._options, option) - end - - return option -end - -function Parser:flag(...) - return self:option():args(0)(...) -end - -function Parser:command(...) - local command = Command():add_help(true)(...) - command._parent = self - table.insert(self._commands, command) - return command -end - -function Parser:mutex(...) - local elements = {...} - - for i, element in ipairs(elements) do - local mt = getmetatable(element) - assert(mt == Option or mt == Argument, ("bad argument #%d to 'mutex' (Option or Argument expected)"):format(i)) - end - - table.insert(self._mutexes, elements) - return self -end - -function Parser:group(name, ...) - assert(type(name) == "string", ("bad argument #1 to 'group' (string expected, got %s)"):format(type(name))) - - local group = {name = name, ...} - - for i, element in ipairs(group) do - local mt = getmetatable(element) - assert(mt == Option or mt == Argument or mt == Command, - ("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(i + 1)) - end - - table.insert(self._groups, group) - return self -end - -local usage_welcome = "Usage: " - -function Parser:get_usage() - if self._usage then - return self._usage - end - - local usage_margin = self:_inherit_property("usage_margin", #usage_welcome) - local max_usage_width = self:_inherit_property("usage_max_width", 70) - local lines = {usage_welcome .. self:_get_fullname()} - - local function add(s) - if #lines[#lines]+1+#s <= max_usage_width then - lines[#lines] = lines[#lines] .. " " .. s - else - lines[#lines+1] = (" "):rep(usage_margin) .. s - end - end - - -- Normally options are before positional arguments in usage messages. - -- However, vararg options should be after, because they can't be reliable used - -- before a positional argument. - -- Mutexes come into play, too, and are shown as soon as possible. - -- Overall, output usages in the following order: - -- 1. Mutexes that don't have positional arguments or vararg options. - -- 2. Options that are not in any mutexes and are not vararg. - -- 3. Positional arguments - on their own or as a part of a mutex. - -- 4. Remaining mutexes. - -- 5. Remaining options. - - local elements_in_mutexes = {} - local added_elements = {} - local added_mutexes = {} - local argument_to_mutexes = {} - - local function add_mutex(mutex, main_argument) - if added_mutexes[mutex] then - return - end - - added_mutexes[mutex] = true - local buf = {} - - for _, element in ipairs(mutex) do - if not element._hidden and not added_elements[element] then - if getmetatable(element) == Option or element == main_argument then - table.insert(buf, element:_get_usage()) - added_elements[element] = true - end - end - end - - if #buf == 1 then - add(buf[1]) - elseif #buf > 1 then - add("(" .. table.concat(buf, " | ") .. ")") - end - end - - local function add_element(element) - if not element._hidden and not added_elements[element] then - add(element:_get_usage()) - added_elements[element] = true - end - end - - for _, mutex in ipairs(self._mutexes) do - local is_vararg = false - local has_argument = false - - for _, element in ipairs(mutex) do - if getmetatable(element) == Option then - if element:_is_vararg() then - is_vararg = true - end - else - has_argument = true - argument_to_mutexes[element] = argument_to_mutexes[element] or {} - table.insert(argument_to_mutexes[element], mutex) - end - - elements_in_mutexes[element] = true - end - - if not is_vararg and not has_argument then - add_mutex(mutex) - end - end - - for _, option in ipairs(self._options) do - if not elements_in_mutexes[option] and not option:_is_vararg() then - add_element(option) - end - end - - -- Add usages for positional arguments, together with one mutex containing them, if they are in a mutex. - for _, argument in ipairs(self._arguments) do - -- Pick a mutex as a part of which to show this argument, take the first one that's still available. - local mutex - - if elements_in_mutexes[argument] then - for _, argument_mutex in ipairs(argument_to_mutexes[argument]) do - if not added_mutexes[argument_mutex] then - mutex = argument_mutex - end - end - end - - if mutex then - add_mutex(mutex, argument) - else - add_element(argument) - end - end - - for _, mutex in ipairs(self._mutexes) do - add_mutex(mutex) - end - - for _, option in ipairs(self._options) do - add_element(option) - end - - if #self._commands > 0 then - if self._require_command then - add("") - else - add("[]") - end - - add("...") - end - - return table.concat(lines, "\n") -end - -local function split_lines(s) - if s == "" then - return {} - end - - local lines = {} - - if s:sub(-1) ~= "\n" then - s = s .. "\n" - end - - for line in s:gmatch("([^\n]*)\n") do - table.insert(lines, line) - end - - return lines -end - -local function autowrap_line(line, max_length) - -- Algorithm for splitting lines is simple and greedy. - local result_lines = {} - - -- Preserve original indentation of the line, put this at the beginning of each result line. - -- If the first word looks like a list marker ('*', '+', or '-'), add spaces so that starts - -- of the second and the following lines vertically align with the start of the second word. - local indentation = line:match("^ *") - - if line:find("^ *[%*%+%-]") then - indentation = indentation .. " " .. line:match("^ *[%*%+%-]( *)") - end - - -- Parts of the last line being assembled. - local line_parts = {} - - -- Length of the current line. - local line_length = 0 - - -- Index of the next character to consider. - local index = 1 - - while true do - local word_start, word_finish, word = line:find("([^ ]+)", index) - - if not word_start then - -- Ignore trailing spaces, if any. - break - end - - local preceding_spaces = line:sub(index, word_start - 1) - index = word_finish + 1 - - if (#line_parts == 0) or (line_length + #preceding_spaces + #word <= max_length) then - -- Either this is the very first word or it fits as an addition to the current line, add it. - table.insert(line_parts, preceding_spaces) -- For the very first word this adds the indentation. - table.insert(line_parts, word) - line_length = line_length + #preceding_spaces + #word - else - -- Does not fit, finish current line and put the word into a new one. - table.insert(result_lines, table.concat(line_parts)) - line_parts = {indentation, word} - line_length = #indentation + #word - end - end - - if #line_parts > 0 then - table.insert(result_lines, table.concat(line_parts)) - end - - if #result_lines == 0 then - -- Preserve empty lines. - result_lines[1] = "" - end - - return result_lines -end - --- Automatically wraps lines within given array, --- attempting to limit line length to `max_length`. --- Existing line splits are preserved. -local function autowrap(lines, max_length) - local result_lines = {} - - for _, line in ipairs(lines) do - local autowrapped_lines = autowrap_line(line, max_length) - - for _, autowrapped_line in ipairs(autowrapped_lines) do - table.insert(result_lines, autowrapped_line) - end - end - - return result_lines -end - -function Parser:_get_element_help(element) - local label_lines = element:_get_label_lines() - local description_lines = split_lines(element:_get_description()) - - local result_lines = {} - - -- All label lines should have the same length (except the last one, it has no comma). - -- If too long, start description after all the label lines. - -- Otherwise, combine label and description lines. - - local usage_margin_len = self:_inherit_property("help_usage_margin", 3) - local usage_margin = (" "):rep(usage_margin_len) - local description_margin_len = self:_inherit_property("help_description_margin", 25) - local description_margin = (" "):rep(description_margin_len) - - local help_max_width = self:_inherit_property("help_max_width") - - if help_max_width then - local description_max_width = math.max(help_max_width - description_margin_len, 10) - description_lines = autowrap(description_lines, description_max_width) - end - - if #label_lines[1] >= (description_margin_len - usage_margin_len) then - for _, label_line in ipairs(label_lines) do - table.insert(result_lines, usage_margin .. label_line) - end - - for _, description_line in ipairs(description_lines) do - table.insert(result_lines, description_margin .. description_line) - end - else - for i = 1, math.max(#label_lines, #description_lines) do - local label_line = label_lines[i] - local description_line = description_lines[i] - - local line = "" - - if label_line then - line = usage_margin .. label_line - end - - if description_line and description_line ~= "" then - line = line .. (" "):rep(description_margin_len - #line) .. description_line - end - - table.insert(result_lines, line) - end - end - - return table.concat(result_lines, "\n") -end - -local function get_group_types(group) - local types = {} - - for _, element in ipairs(group) do - types[getmetatable(element)] = true - end - - return types -end - -function Parser:_add_group_help(blocks, added_elements, label, elements) - local buf = {label} - - for _, element in ipairs(elements) do - if not element._hidden and not added_elements[element] then - added_elements[element] = true - table.insert(buf, self:_get_element_help(element)) - end - end - - if #buf > 1 then - table.insert(blocks, table.concat(buf, ("\n"):rep(self:_inherit_property("help_vertical_space", 0) + 1))) - end -end - -function Parser:get_help() - if self._help then - return self._help - end - - local blocks = {self:get_usage()} - - local help_max_width = self:_inherit_property("help_max_width") - - if self._description then - local description = self._description - - if help_max_width then - description = table.concat(autowrap(split_lines(description), help_max_width), "\n") - end - - table.insert(blocks, description) - end - - -- 1. Put groups containing arguments first, then other arguments. - -- 2. Put remaining groups containing options, then other options. - -- 3. Put remaining groups containing commands, then other commands. - -- Assume that an element can't be in several groups. - local groups_by_type = { - [Argument] = {}, - [Option] = {}, - [Command] = {} - } - - for _, group in ipairs(self._groups) do - local group_types = get_group_types(group) - - for _, mt in ipairs({Argument, Option, Command}) do - if group_types[mt] then - table.insert(groups_by_type[mt], group) - break - end - end - end - - local default_groups = { - {name = "Arguments", type = Argument, elements = self._arguments}, - {name = "Options", type = Option, elements = self._options}, - {name = "Commands", type = Command, elements = self._commands} - } - - local added_elements = {} - - for _, default_group in ipairs(default_groups) do - local type_groups = groups_by_type[default_group.type] - - for _, group in ipairs(type_groups) do - self:_add_group_help(blocks, added_elements, group.name .. ":", group) - end - - local default_label = default_group.name .. ":" - - if #type_groups > 0 then - default_label = "Other " .. default_label:gsub("^.", string.lower) - end - - self:_add_group_help(blocks, added_elements, default_label, default_group.elements) - end - - if self._epilog then - local epilog = self._epilog - - if help_max_width then - epilog = table.concat(autowrap(split_lines(epilog), help_max_width), "\n") - end - - table.insert(blocks, epilog) - end - - return table.concat(blocks, "\n\n") -end - -local function get_tip(context, wrong_name) - local context_pool = {} - local possible_name - local possible_names = {} - - for name in pairs(context) do - if type(name) == "string" then - for i = 1, #name do - possible_name = name:sub(1, i - 1) .. name:sub(i + 1) - - if not context_pool[possible_name] then - context_pool[possible_name] = {} - end - - table.insert(context_pool[possible_name], name) - end - end - end - - for i = 1, #wrong_name + 1 do - possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1) - - if context[possible_name] then - possible_names[possible_name] = true - elseif context_pool[possible_name] then - for _, name in ipairs(context_pool[possible_name]) do - possible_names[name] = true - end - end - end - - local first = next(possible_names) - - if first then - if next(possible_names, first) then - local possible_names_arr = {} - - for name in pairs(possible_names) do - table.insert(possible_names_arr, "'" .. name .. "'") - end - - table.sort(possible_names_arr) - return "\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?" - else - return "\nDid you mean '" .. first .. "'?" - end - else - return "" - end -end - -local ElementState = class({ - invocations = 0 -}) - -function ElementState:__call(state, element) - self.state = state - self.result = state.result - self.element = element - self.target = element._target or element:_get_default_target() - self.action, self.result[self.target] = element:_get_action() - return self -end - -function ElementState:error(fmt, ...) - self.state:error(fmt, ...) -end - -function ElementState:convert(argument, index) - local converter = self.element._convert - - if converter then - local ok, err - - if type(converter) == "function" then - ok, err = converter(argument) - elseif type(converter[index]) == "function" then - ok, err = converter[index](argument) - else - ok = converter[argument] - end - - if ok == nil then - self:error(err and "%s" or "malformed argument '%s'", err or argument) - end - - argument = ok - end - - return argument -end - -function ElementState:default(mode) - return self.element._defmode:find(mode) and self.element._default -end - -local function bound(noun, min, max, is_max) - local res = "" - - if min ~= max then - res = "at " .. (is_max and "most" or "least") .. " " - end - - local number = is_max and max or min - return res .. tostring(number) .. " " .. noun .. (number == 1 and "" or "s") -end - -function ElementState:set_name(alias) - self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name) -end - -function ElementState:invoke() - self.open = true - self.overwrite = false - - if self.invocations >= self.element._maxcount then - if self.element._overwrite then - self.overwrite = true - else - local num_times_repr = bound("time", self.element._mincount, self.element._maxcount, true) - self:error("%s must be used %s", self.name, num_times_repr) - end - else - self.invocations = self.invocations + 1 - end - - self.args = {} - - if self.element._maxargs <= 0 then - self:close() - end - - return self.open -end - -function ElementState:pass(argument) - argument = self:convert(argument, #self.args + 1) - table.insert(self.args, argument) - - if #self.args >= self.element._maxargs then - self:close() - end - - return self.open -end - -function ElementState:complete_invocation() - while #self.args < self.element._minargs do - self:pass(self.element._default) - end -end - -function ElementState:close() - if self.open then - self.open = false - - if #self.args < self.element._minargs then - if self:default("a") then - self:complete_invocation() - else - if #self.args == 0 then - if getmetatable(self.element) == Argument then - self:error("missing %s", self.name) - elseif self.element._maxargs == 1 then - self:error("%s requires an argument", self.name) - end - end - - self:error("%s requires %s", self.name, bound("argument", self.element._minargs, self.element._maxargs)) - end - end - - local args - - if self.element._maxargs == 0 then - args = self.args[1] - elseif self.element._maxargs == 1 then - if self.element._minargs == 0 and self.element._mincount ~= self.element._maxcount then - args = self.args - else - args = self.args[1] - end - else - args = self.args - end - - self.action(self.result, self.target, args, self.overwrite) - end -end - -local ParseState = class({ - result = {}, - options = {}, - arguments = {}, - argument_i = 1, - element_to_mutexes = {}, - mutex_to_element_state = {}, - command_actions = {} -}) - -function ParseState:__call(parser, error_handler) - self.parser = parser - self.error_handler = error_handler - self.charset = parser:_update_charset() - self:switch(parser) - return self -end - -function ParseState:error(fmt, ...) - self.error_handler(self.parser, fmt:format(...)) -end - -function ParseState:switch(parser) - self.parser = parser - - if parser._action then - table.insert(self.command_actions, {action = parser._action, name = parser._name}) - end - - for _, option in ipairs(parser._options) do - option = ElementState(self, option) - table.insert(self.options, option) - - for _, alias in ipairs(option.element._aliases) do - self.options[alias] = option - end - end - - for _, mutex in ipairs(parser._mutexes) do - for _, element in ipairs(mutex) do - if not self.element_to_mutexes[element] then - self.element_to_mutexes[element] = {} - end - - table.insert(self.element_to_mutexes[element], mutex) - end - end - - for _, argument in ipairs(parser._arguments) do - argument = ElementState(self, argument) - table.insert(self.arguments, argument) - argument:set_name() - argument:invoke() - end - - self.handle_options = parser._handle_options - self.argument = self.arguments[self.argument_i] - self.commands = parser._commands - - for _, command in ipairs(self.commands) do - for _, alias in ipairs(command._aliases) do - self.commands[alias] = command - end - end -end - -function ParseState:get_option(name) - local option = self.options[name] - - if not option then - self:error("unknown option '%s'%s", name, get_tip(self.options, name)) - else - return option - end -end - -function ParseState:get_command(name) - local command = self.commands[name] - - if not command then - if #self.commands > 0 then - self:error("unknown command '%s'%s", name, get_tip(self.commands, name)) - else - self:error("too many arguments") - end - else - return command - end -end - -function ParseState:check_mutexes(element_state) - if self.element_to_mutexes[element_state.element] then - for _, mutex in ipairs(self.element_to_mutexes[element_state.element]) do - local used_element_state = self.mutex_to_element_state[mutex] - - if used_element_state and used_element_state ~= element_state then - self:error("%s can not be used together with %s", element_state.name, used_element_state.name) - else - self.mutex_to_element_state[mutex] = element_state - end - end - end -end - -function ParseState:invoke(option, name) - self:close() - option:set_name(name) - self:check_mutexes(option, name) - - if option:invoke() then - self.option = option - end -end - -function ParseState:pass(arg) - if self.option then - if not self.option:pass(arg) then - self.option = nil - end - elseif self.argument then - self:check_mutexes(self.argument) - - if not self.argument:pass(arg) then - self.argument_i = self.argument_i + 1 - self.argument = self.arguments[self.argument_i] - end - else - local command = self:get_command(arg) - self.result[command._target or command._name] = true - - if self.parser._command_target then - self.result[self.parser._command_target] = command._name - end - - self:switch(command) - end -end - -function ParseState:close() - if self.option then - self.option:close() - self.option = nil - end -end - -function ParseState:finalize() - self:close() - - for i = self.argument_i, #self.arguments do - local argument = self.arguments[i] - if #argument.args == 0 and argument:default("u") then - argument:complete_invocation() - else - argument:close() - end - end - - if self.parser._require_command and #self.commands > 0 then - self:error("a command is required") - end - - for _, option in ipairs(self.options) do - option.name = option.name or ("option '%s'"):format(option.element._name) - - if option.invocations == 0 then - if option:default("u") then - option:invoke() - option:complete_invocation() - option:close() - end - end - - local mincount = option.element._mincount - - if option.invocations < mincount then - if option:default("a") then - while option.invocations < mincount do - option:invoke() - option:close() - end - elseif option.invocations == 0 then - self:error("missing %s", option.name) - else - self:error("%s must be used %s", option.name, bound("time", mincount, option.element._maxcount)) - end - end - end - - for i = #self.command_actions, 1, -1 do - self.command_actions[i].action(self.result, self.command_actions[i].name) - end -end - -function ParseState:parse(args) - for _, arg in ipairs(args) do - local plain = true - - if self.handle_options then - local first = arg:sub(1, 1) - - if self.charset[first] then - if #arg > 1 then - plain = false - - if arg:sub(2, 2) == first then - if #arg == 2 then - if self.options[arg] then - local option = self:get_option(arg) - self:invoke(option, arg) - else - self:close() - end - - self.handle_options = false - else - local equals = arg:find "=" - if equals then - local name = arg:sub(1, equals - 1) - local option = self:get_option(name) - - if option.element._maxargs <= 0 then - self:error("option '%s' does not take arguments", name) - end - - self:invoke(option, name) - self:pass(arg:sub(equals + 1)) - else - local option = self:get_option(arg) - self:invoke(option, arg) - end - end - else - for i = 2, #arg do - local name = first .. arg:sub(i, i) - local option = self:get_option(name) - self:invoke(option, name) - - if i ~= #arg and option.element._maxargs > 0 then - self:pass(arg:sub(i + 1)) - break - end - end - end - end - end - end - - if plain then - self:pass(arg) - end - end - - self:finalize() - return self.result -end - -function Parser:error(msg) - io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg)) - os.exit(1) -end - --- Compatibility with strict.lua and other checkers: -local default_cmdline = rawget(_G, "arg") or {} - -function Parser:_parse(args, error_handler) - return ParseState(self, error_handler):parse(args or default_cmdline) -end - -function Parser:parse(args) - return self:_parse(args, self.error) -end - -local function xpcall_error_handler(err) - return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2) -end - -function Parser:pparse(args) - local parse_error - - local ok, result = xpcall(function() - return self:_parse(args, function(_, err) - parse_error = err - error(err, 0) - end) - end, xpcall_error_handler) - - if ok then - return true, result - elseif not parse_error then - error(result, 0) - else - return false, parse_error - end -end - -local argparse = {} - -argparse.version = "0.6.0" - -setmetatable(argparse, {__call = function(_, ...) - return Parser(default_cmdline[0]):add_help(true)(...) -end}) - -return argparse - -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() diff --git a/build/luacomp-5.2.lua b/build/luacomp-5.2.lua deleted file mode 100644 index d14a9c5..0000000 --- a/build/luacomp-5.2.lua +++ /dev/null @@ -1,514 +0,0 @@ -#!/usr/bin/env lua5.2 ---[[ - init.lua - Main file of LuaComp - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - - -local function _sv(k, v) - _G[k] = v - --os.setenv(k, tostring(v)) -end - -_sv("LUACOMP_V_MAJ", 1) -_sv("LUACOMP_V_MIN", 1) -_sv("LUACOMP_V_PAT", 0) -_sv("LUACOMP_VERSION", LUACOMP_V_MAJ.."."..LUACOMP_V_MIN.."."..LUACOMP_V_PAT) -_sv("LUACOMP_NAME", "LuaComp") - ---[[ - ast.lua - Generates a structure for use in preprocessing. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function nextc(f, c) - c = c or 1 - return f:read(c) -end - -local function peek(f, c) - c = c or 1 - local z = f:read(c) - f:seek("cur", -c) - return z -end - -local function skip(f, c) - c = c or 1 - return f:seek("cur", c) -end - -local ws = { - ["\t"] = true, - [" "] = true -} - -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 - 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 - hex = hex .. lc - end - end - return tonumber(hex, 16) -end - -local function parse_number(f, c) - local lc = " " - local num = c - while 48 <= lc:byte() and lc:byte() <= 57 do - lc = nextc(f) - if (48 <= lc:byte() and lc:byte() <= 57) then - num = num .. lc - end - end - return tonumber(hex, 10) -end - -local esct = { - ["t"] = "\t", - ["n"] = "\n", - ["r"] = "\r", - ["\\"] = "\\\\" -} - -for i=0, 9 do - esct[tostring(i)] = string.char(i) -end - -local function parse_dblquote(f) - local val = "" - while peek(f) ~= "\"" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_snglquote(f) - local val = "" - while peek(f) ~= "\'" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_envarg(f) - local val = "" - while peek(f) ~= ")" do - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - val = val .. nextc(f) - end - skip(f) - return val -end - -local function parse_directive(f) - local lc = "_" - local name = "" - local args = {} - local carg = "" - while not ws[lc] do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name} - elseif not ws[lc] then - name = name .. lc - end - end - while true do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name, args=args} - elseif lc == "0" and peek(f) == "x" then - skip(f) - local val = parse_hex(f) - args[#args+1] = val - elseif 48 <= lc:byte() and lc:byte() <= 57 then - local val = parse_number(f, lc) - args[#args+1] = val - elseif lc == "\"" then - local val, e = parse_dblquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "\'" then - local val, e = parse_snglquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "$" and peek(f) == "(" then - skip(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 not ws[lc] then - return nil, "Syntax error" - end - end -end - -local function mkast(f, n) - io.stderr:write("PROC\t",n,"\n") - local lc = " " - local lpos = 1 - local ilpos = 1 - local tree = {} - local code = "" - local branches = {} - local function add_code() - tree[#tree+1] = {type="code", data=code, file=n, line=lpos} - code = "" - end - local function parse_error(e) - io.stderr:write("ERROR:"..n..":"..lpos..": "..e.."\n") - os.exit(1) - end - while lc and lc ~= "" do - lc = nextc(f) - if (lc == "-" and ilpos == 1) then - if (peek(f, 2) == "-#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "/" and ilpos == 1) then - if (peek(f, 2) == "/#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "$" and peek(f) == "(") then - add_code() - skip(f) - local val, e = parse_envarg(f) - if not val then - parse_error(e) - end - tree[#tree+1] = {type="envvar", var=val, file=n, line=lpos} - 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="lua", 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="lua_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) - end - lpos = lpos+1 - ilpos = 1 - code = code .. "\n" - else - code = code .. (lc or "") - ilpos = ilpos+1 - end - end - add_code() - return tree -end - ---[[ - generator.lua - Generates the code. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function lua_escape(code) - return code:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n") -end - -local directives = {} - -local function generate(ast) - local lua_code = "" - for i=1, #ast do - local leaf = ast[i] - if (leaf.type == "lua") then - lua_code = lua_code .. leaf.code - elseif (leaf.type == "directive") then - local stargs = {} - for i=1, #leaf.args do - local arg = leaf.args[i] - if (type(arg) == "string") then - stargs[i] = "\""..lua_escape(arg).."\"" - elseif (type(arg) == "number") then - stargs[i] = tostring(arg) - end - end - lua_code = lua_code .. "call_directive(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.name.."\","..table.concat(stargs, ",")..")" - elseif (leaf.type == "envvar") then - lua_code = lua_code .. "put_env(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.var.."\")" - elseif (leaf.type == "code") then - 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.."))" - else - io.stderr:write("ERROR: Internal catastrophic failure, unknown type "..leaf.type.."\n") - os.exit(1) - end - lua_code = lua_code .. "\n" - end - local env = {code = ""} - local function run_away_screaming(fpos, err) - io.stdout:write("ERROR: "..fpos..": "..err.."\n") - os.exit(1) - end - local function call_directive(fpos, dname, ...) - if (not directives[dname]) then - run_away_screaming(fpos, "Invalid directive name `"..dname.."'") - end - local r, er = directives[dname](env, ...) - if (not r) then - run_away_screaming(fpos, er) - end - end - local function put_env(fpos, evar) - local e = os.getenv(evar) - if not e then - run_away_screaming(fpos, "Enviroment variable `"..evar.."' does not exist!") - end - env.code = env.code .. "\""..lua_escape(e).."\"" - end - local function put_code(fpos, code) - env.code = env.code .. code --not much that can fail here... - end - local fenv = {} - for k, v in pairs(_G) do - fenv[k] = v - end - fenv._G = fenv - fenv._ENV = fenv - fenv.call_directive = call_directive - fenv.put_code = put_code - fenv.put_env = put_env - local func = assert(load(lua_code, "=(generated code)", "t", fenv)) - func() - return env.code -end - ---[[ - directive_provider.lua - Provides preprocessor directives - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - ----#include "directives/define.lua" - -function directives.include(env, file) - if (not os.execute("stat "..file..">/dev/null")) then - return false, "File `"..file.."' does not exist!" - end - local f = io.open(file, "r") - local fast = mkast(f, file) - local code = generate(fast) - env.code = env.code .. "\n" .. code .. "\n" - return true -end - -local warned = false -function directives.loadmod(env, mod) - if not warned then - io.stderr:write("Warning: loadmod is depreciated and unsafe. The API differs from luapreproc.\n") - warned = true - end - if (not os.execute("stat "..file..">/dev/null")) then - return false, "Module `"..file.."' does not exist!" - end - local modname, func = dofile(mod) - directives[modname] = func - return true -end - - ---[[ - cfg/minifier_providers.lua - Provides minifier providers. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local providers = {} - -function providers.luamin(cin) - local fn = os.tmpname() - local fh = io.open(fn, "w") - fh:write(cin) - fh:close() - local lmh = io.popen("luamin -f "..fn.." 2>&1", "r") - local dat = lmh:read("*a") - local stat, _, code = lmh:close() - if (code ~= 0) then - return false, dat - end - return dat -end - -function providers.none(cin) - return cin -end - -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 = 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() diff --git a/build/luacomp-5.3-static.lua b/build/luacomp-5.3-static.lua deleted file mode 100644 index 1922b08..0000000 --- a/build/luacomp-5.3-static.lua +++ /dev/null @@ -1,2044 +0,0 @@ -#!/usr/bin/env lua5.3 ---[[ - staticinit.lua - Main file of LuaComp, directly includes argparse. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - - -local function _sv(k, v) - _G[k] = v - --os.setenv(k, tostring(v)) -end - -_sv("LUACOMP_V_MAJ", 1) -_sv("LUACOMP_V_MIN", 1) -_sv("LUACOMP_V_PAT", 0) -_sv("LUACOMP_VERSION", LUACOMP_V_MAJ.."."..LUACOMP_V_MIN.."."..LUACOMP_V_PAT) -_sv("LUACOMP_NAME", "LuaComp") - ---[[ - ast.lua - Generates a structure for use in preprocessing. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function nextc(f, c) - c = c or 1 - return f:read(c) -end - -local function peek(f, c) - c = c or 1 - local z = f:read(c) - f:seek("cur", -c) - return z -end - -local function skip(f, c) - c = c or 1 - return f:seek("cur", c) -end - -local ws = { - ["\t"] = true, - [" "] = true -} - -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 - 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 - hex = hex .. lc - end - end - return tonumber(hex, 16) -end - -local function parse_number(f, c) - local lc = " " - local num = c - while 48 <= lc:byte() and lc:byte() <= 57 do - lc = nextc(f) - if (48 <= lc:byte() and lc:byte() <= 57) then - num = num .. lc - end - end - return tonumber(hex, 10) -end - -local esct = { - ["t"] = "\t", - ["n"] = "\n", - ["r"] = "\r", - ["\\"] = "\\\\" -} - -for i=0, 9 do - esct[tostring(i)] = string.char(i) -end - -local function parse_dblquote(f) - local val = "" - while peek(f) ~= "\"" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_snglquote(f) - local val = "" - while peek(f) ~= "\'" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_envarg(f) - local val = "" - while peek(f) ~= ")" do - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - val = val .. nextc(f) - end - skip(f) - return val -end - -local function parse_directive(f) - local lc = "_" - local name = "" - local args = {} - local carg = "" - while not ws[lc] do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name} - elseif not ws[lc] then - name = name .. lc - end - end - while true do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name, args=args} - elseif lc == "0" and peek(f) == "x" then - skip(f) - local val = parse_hex(f) - args[#args+1] = val - elseif 48 <= lc:byte() and lc:byte() <= 57 then - local val = parse_number(f, lc) - args[#args+1] = val - elseif lc == "\"" then - local val, e = parse_dblquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "\'" then - local val, e = parse_snglquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "$" and peek(f) == "(" then - skip(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 not ws[lc] then - return nil, "Syntax error" - end - end -end - -local function mkast(f, n) - io.stderr:write("PROC\t",n,"\n") - local lc = " " - local lpos = 1 - local ilpos = 1 - local tree = {} - local code = "" - local branches = {} - local function add_code() - tree[#tree+1] = {type="code", data=code, file=n, line=lpos} - code = "" - end - local function parse_error(e) - io.stderr:write("ERROR:"..n..":"..lpos..": "..e.."\n") - os.exit(1) - end - while lc and lc ~= "" do - lc = nextc(f) - if (lc == "-" and ilpos == 1) then - if (peek(f, 2) == "-#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "/" and ilpos == 1) then - if (peek(f, 2) == "/#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "$" and peek(f) == "(") then - add_code() - skip(f) - local val, e = parse_envarg(f) - if not val then - parse_error(e) - end - tree[#tree+1] = {type="envvar", var=val, file=n, line=lpos} - 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="lua", 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="lua_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) - end - lpos = lpos+1 - ilpos = 1 - code = code .. "\n" - else - code = code .. (lc or "") - ilpos = ilpos+1 - end - end - add_code() - return tree -end - ---[[ - generator.lua - Generates the code. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function lua_escape(code) - return code:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n") -end - -local directives = {} - -local function generate(ast) - local lua_code = "" - for i=1, #ast do - local leaf = ast[i] - if (leaf.type == "lua") then - lua_code = lua_code .. leaf.code - elseif (leaf.type == "directive") then - local stargs = {} - for i=1, #leaf.args do - local arg = leaf.args[i] - if (type(arg) == "string") then - stargs[i] = "\""..lua_escape(arg).."\"" - elseif (type(arg) == "number") then - stargs[i] = tostring(arg) - end - end - lua_code = lua_code .. "call_directive(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.name.."\","..table.concat(stargs, ",")..")" - elseif (leaf.type == "envvar") then - lua_code = lua_code .. "put_env(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.var.."\")" - elseif (leaf.type == "code") then - 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.."))" - else - io.stderr:write("ERROR: Internal catastrophic failure, unknown type "..leaf.type.."\n") - os.exit(1) - end - lua_code = lua_code .. "\n" - end - local env = {code = ""} - local function run_away_screaming(fpos, err) - io.stdout:write("ERROR: "..fpos..": "..err.."\n") - os.exit(1) - end - local function call_directive(fpos, dname, ...) - if (not directives[dname]) then - run_away_screaming(fpos, "Invalid directive name `"..dname.."'") - end - local r, er = directives[dname](env, ...) - if (not r) then - run_away_screaming(fpos, er) - end - end - local function put_env(fpos, evar) - local e = os.getenv(evar) - if not e then - run_away_screaming(fpos, "Enviroment variable `"..evar.."' does not exist!") - end - env.code = env.code .. "\""..lua_escape(e).."\"" - end - local function put_code(fpos, code) - env.code = env.code .. code --not much that can fail here... - end - local fenv = {} - for k, v in pairs(_G) do - fenv[k] = v - end - fenv._G = fenv - fenv._ENV = fenv - fenv.call_directive = call_directive - fenv.put_code = put_code - fenv.put_env = put_env - local func = assert(load(lua_code, "=(generated code)", "t", fenv)) - func() - return env.code -end - ---[[ - directive_provider.lua - Provides preprocessor directives - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - ----#include "directives/define.lua" - -function directives.include(env, file) - if (not os.execute("stat "..file..">/dev/null")) then - return false, "File `"..file.."' does not exist!" - end - local f = io.open(file, "r") - local fast = mkast(f, file) - local code = generate(fast) - env.code = env.code .. "\n" .. code .. "\n" - return true -end - -local warned = false -function directives.loadmod(env, mod) - if not warned then - io.stderr:write("Warning: loadmod is depreciated and unsafe. The API differs from luapreproc.\n") - warned = true - end - if (not os.execute("stat "..file..">/dev/null")) then - return false, "Module `"..file.."' does not exist!" - end - local modname, func = dofile(mod) - directives[modname] = func - return true -end - - ---[[ - cfg/minifier_providers.lua - Provides minifier providers. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local providers = {} - -function providers.luamin(cin) - local fn = os.tmpname() - local fh = io.open(fn, "w") - fh:write(cin) - fh:close() - local lmh = io.popen("luamin -f "..fn.." 2>&1", "r") - local dat = lmh:read("*a") - local stat, _, code = lmh:close() - if (code ~= 0) then - return false, dat - end - return dat -end - -function providers.none(cin) - return cin -end - -local argparse = (function() - --- The MIT License (MIT) - --- Copyright (c) 2013 - 2018 Peter Melnichenko - --- Permission is hereby granted, free of charge, to any person obtaining a copy of --- this software and associated documentation files (the "Software"), to deal in --- the Software without restriction, including without limitation the rights to --- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of --- the Software, and to permit persons to whom the Software is furnished to do so, --- subject to the following conditions: - --- The above copyright notice and this permission notice shall be included in all --- copies or substantial portions of the Software. - --- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS --- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR --- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER --- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN --- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -local function deep_update(t1, t2) - for k, v in pairs(t2) do - if type(v) == "table" then - v = deep_update({}, v) - end - - t1[k] = v - end - - return t1 -end - --- A property is a tuple {name, callback}. --- properties.args is number of properties that can be set as arguments --- when calling an object. -local function class(prototype, properties, parent) - -- Class is the metatable of its instances. - local cl = {} - cl.__index = cl - - if parent then - cl.__prototype = deep_update(deep_update({}, parent.__prototype), prototype) - else - cl.__prototype = prototype - end - - if properties then - local names = {} - - -- Create setter methods and fill set of property names. - for _, property in ipairs(properties) do - local name, callback = property[1], property[2] - - cl[name] = function(self, value) - if not callback(self, value) then - self["_" .. name] = value - end - - return self - end - - names[name] = true - end - - function cl.__call(self, ...) - -- When calling an object, if the first argument is a table, - -- interpret keys as property names, else delegate arguments - -- to corresponding setters in order. - if type((...)) == "table" then - for name, value in pairs((...)) do - if names[name] then - self[name](self, value) - end - end - else - local nargs = select("#", ...) - - for i, property in ipairs(properties) do - if i > nargs or i > properties.args then - break - end - - local arg = select(i, ...) - - if arg ~= nil then - self[property[1]](self, arg) - end - end - end - - return self - end - end - - -- If indexing class fails, fallback to its parent. - local class_metatable = {} - class_metatable.__index = parent - - function class_metatable.__call(self, ...) - -- Calling a class returns its instance. - -- Arguments are delegated to the instance. - local object = deep_update({}, self.__prototype) - setmetatable(object, self) - return object(...) - end - - return setmetatable(cl, class_metatable) -end - -local function typecheck(name, types, value) - for _, type_ in ipairs(types) do - if type(value) == type_ then - return true - end - end - - error(("bad property '%s' (%s expected, got %s)"):format(name, table.concat(types, " or "), type(value))) -end - -local function typechecked(name, ...) - local types = {...} - return {name, function(_, value) typecheck(name, types, value) end} -end - -local multiname = {"name", function(self, value) - typecheck("name", {"string"}, value) - - for alias in value:gmatch("%S+") do - self._name = self._name or alias - table.insert(self._aliases, alias) - end - - -- Do not set _name as with other properties. - return true -end} - -local function parse_boundaries(str) - if tonumber(str) then - return tonumber(str), tonumber(str) - end - - if str == "*" then - return 0, math.huge - end - - if str == "+" then - return 1, math.huge - end - - if str == "?" then - return 0, 1 - end - - if str:match "^%d+%-%d+$" then - local min, max = str:match "^(%d+)%-(%d+)$" - return tonumber(min), tonumber(max) - end - - if str:match "^%d+%+$" then - local min = str:match "^(%d+)%+$" - return tonumber(min), math.huge - end -end - -local function boundaries(name) - return {name, function(self, value) - typecheck(name, {"number", "string"}, value) - - local min, max = parse_boundaries(value) - - if not min then - error(("bad property '%s'"):format(name)) - end - - self["_min" .. name], self["_max" .. name] = min, max - end} -end - -local actions = {} - -local option_action = {"action", function(_, value) - typecheck("action", {"function", "string"}, value) - - if type(value) == "string" and not actions[value] then - error(("unknown action '%s'"):format(value)) - end -end} - -local option_init = {"init", function(self) - self._has_init = true -end} - -local option_default = {"default", function(self, value) - if type(value) ~= "string" then - self._init = value - self._has_init = true - return true - end -end} - -local add_help = {"add_help", function(self, value) - typecheck("add_help", {"boolean", "string", "table"}, value) - - if self._has_help then - table.remove(self._options) - self._has_help = false - end - - if value then - local help = self:flag() - :description "Show this help message and exit." - :action(function() - print(self:get_help()) - os.exit(0) - end) - - if value ~= true then - help = help(value) - end - - if not help._name then - help "-h" "--help" - end - - self._has_help = true - end -end} - -local Parser = class({ - _arguments = {}, - _options = {}, - _commands = {}, - _mutexes = {}, - _groups = {}, - _require_command = true, - _handle_options = true -}, { - args = 3, - typechecked("name", "string"), - typechecked("description", "string"), - typechecked("epilog", "string"), - typechecked("usage", "string"), - typechecked("help", "string"), - typechecked("require_command", "boolean"), - typechecked("handle_options", "boolean"), - typechecked("action", "function"), - typechecked("command_target", "string"), - typechecked("help_vertical_space", "number"), - typechecked("usage_margin", "number"), - typechecked("usage_max_width", "number"), - typechecked("help_usage_margin", "number"), - typechecked("help_description_margin", "number"), - typechecked("help_max_width", "number"), - add_help -}) - -local Command = class({ - _aliases = {} -}, { - args = 3, - multiname, - typechecked("description", "string"), - typechecked("epilog", "string"), - typechecked("target", "string"), - typechecked("usage", "string"), - typechecked("help", "string"), - typechecked("require_command", "boolean"), - typechecked("handle_options", "boolean"), - typechecked("action", "function"), - typechecked("command_target", "string"), - typechecked("help_vertical_space", "number"), - typechecked("usage_margin", "number"), - typechecked("usage_max_width", "number"), - typechecked("help_usage_margin", "number"), - typechecked("help_description_margin", "number"), - typechecked("help_max_width", "number"), - typechecked("hidden", "boolean"), - add_help -}, Parser) - -local Argument = class({ - _minargs = 1, - _maxargs = 1, - _mincount = 1, - _maxcount = 1, - _defmode = "unused", - _show_default = true -}, { - args = 5, - typechecked("name", "string"), - typechecked("description", "string"), - option_default, - typechecked("convert", "function", "table"), - boundaries("args"), - typechecked("target", "string"), - typechecked("defmode", "string"), - typechecked("show_default", "boolean"), - typechecked("argname", "string", "table"), - typechecked("hidden", "boolean"), - option_action, - option_init -}) - -local Option = class({ - _aliases = {}, - _mincount = 0, - _overwrite = true -}, { - args = 6, - multiname, - typechecked("description", "string"), - option_default, - typechecked("convert", "function", "table"), - boundaries("args"), - boundaries("count"), - typechecked("target", "string"), - typechecked("defmode", "string"), - typechecked("show_default", "boolean"), - typechecked("overwrite", "boolean"), - typechecked("argname", "string", "table"), - typechecked("hidden", "boolean"), - option_action, - option_init -}, Argument) - -function Parser:_inherit_property(name, default) - local element = self - - while true do - local value = element["_" .. name] - - if value ~= nil then - return value - end - - if not element._parent then - return default - end - - element = element._parent - end -end - -function Argument:_get_argument_list() - local buf = {} - local i = 1 - - while i <= math.min(self._minargs, 3) do - local argname = self:_get_argname(i) - - if self._default and self._defmode:find "a" then - argname = "[" .. argname .. "]" - end - - table.insert(buf, argname) - i = i+1 - end - - while i <= math.min(self._maxargs, 3) do - table.insert(buf, "[" .. self:_get_argname(i) .. "]") - i = i+1 - - if self._maxargs == math.huge then - break - end - end - - if i < self._maxargs then - table.insert(buf, "...") - end - - return buf -end - -function Argument:_get_usage() - local usage = table.concat(self:_get_argument_list(), " ") - - if self._default and self._defmode:find "u" then - if self._maxargs > 1 or (self._minargs == 1 and not self._defmode:find "a") then - usage = "[" .. usage .. "]" - end - end - - return usage -end - -function actions.store_true(result, target) - result[target] = true -end - -function actions.store_false(result, target) - result[target] = false -end - -function actions.store(result, target, argument) - result[target] = argument -end - -function actions.count(result, target, _, overwrite) - if not overwrite then - result[target] = result[target] + 1 - end -end - -function actions.append(result, target, argument, overwrite) - result[target] = result[target] or {} - table.insert(result[target], argument) - - if overwrite then - table.remove(result[target], 1) - end -end - -function actions.concat(result, target, arguments, overwrite) - if overwrite then - error("'concat' action can't handle too many invocations") - end - - result[target] = result[target] or {} - - for _, argument in ipairs(arguments) do - table.insert(result[target], argument) - end -end - -function Argument:_get_action() - local action, init - - if self._maxcount == 1 then - if self._maxargs == 0 then - action, init = "store_true", nil - else - action, init = "store", nil - end - else - if self._maxargs == 0 then - action, init = "count", 0 - else - action, init = "append", {} - end - end - - if self._action then - action = self._action - end - - if self._has_init then - init = self._init - end - - if type(action) == "string" then - action = actions[action] - end - - return action, init -end - --- Returns placeholder for `narg`-th argument. -function Argument:_get_argname(narg) - local argname = self._argname or self:_get_default_argname() - - if type(argname) == "table" then - return argname[narg] - else - return argname - end -end - -function Argument:_get_default_argname() - return "<" .. self._name .. ">" -end - -function Option:_get_default_argname() - return "<" .. self:_get_default_target() .. ">" -end - --- Returns labels to be shown in the help message. -function Argument:_get_label_lines() - return {self._name} -end - -function Option:_get_label_lines() - local argument_list = self:_get_argument_list() - - if #argument_list == 0 then - -- Don't put aliases for simple flags like `-h` on different lines. - return {table.concat(self._aliases, ", ")} - end - - local longest_alias_length = -1 - - for _, alias in ipairs(self._aliases) do - longest_alias_length = math.max(longest_alias_length, #alias) - end - - local argument_list_repr = table.concat(argument_list, " ") - local lines = {} - - for i, alias in ipairs(self._aliases) do - local line = (" "):rep(longest_alias_length - #alias) .. alias .. " " .. argument_list_repr - - if i ~= #self._aliases then - line = line .. "," - end - - table.insert(lines, line) - end - - return lines -end - -function Command:_get_label_lines() - return {table.concat(self._aliases, ", ")} -end - -function Argument:_get_description() - if self._default and self._show_default then - if self._description then - return ("%s (default: %s)"):format(self._description, self._default) - else - return ("default: %s"):format(self._default) - end - else - return self._description or "" - end -end - -function Command:_get_description() - return self._description or "" -end - -function Option:_get_usage() - local usage = self:_get_argument_list() - table.insert(usage, 1, self._name) - usage = table.concat(usage, " ") - - if self._mincount == 0 or self._default then - usage = "[" .. usage .. "]" - end - - return usage -end - -function Argument:_get_default_target() - return self._name -end - -function Option:_get_default_target() - local res - - for _, alias in ipairs(self._aliases) do - if alias:sub(1, 1) == alias:sub(2, 2) then - res = alias:sub(3) - break - end - end - - res = res or self._name:sub(2) - return (res:gsub("-", "_")) -end - -function Option:_is_vararg() - return self._maxargs ~= self._minargs -end - -function Parser:_get_fullname() - local parent = self._parent - local buf = {self._name} - - while parent do - table.insert(buf, 1, parent._name) - parent = parent._parent - end - - return table.concat(buf, " ") -end - -function Parser:_update_charset(charset) - charset = charset or {} - - for _, command in ipairs(self._commands) do - command:_update_charset(charset) - end - - for _, option in ipairs(self._options) do - for _, alias in ipairs(option._aliases) do - charset[alias:sub(1, 1)] = true - end - end - - return charset -end - -function Parser:argument(...) - local argument = Argument(...) - table.insert(self._arguments, argument) - return argument -end - -function Parser:option(...) - local option = Option(...) - - if self._has_help then - table.insert(self._options, #self._options, option) - else - table.insert(self._options, option) - end - - return option -end - -function Parser:flag(...) - return self:option():args(0)(...) -end - -function Parser:command(...) - local command = Command():add_help(true)(...) - command._parent = self - table.insert(self._commands, command) - return command -end - -function Parser:mutex(...) - local elements = {...} - - for i, element in ipairs(elements) do - local mt = getmetatable(element) - assert(mt == Option or mt == Argument, ("bad argument #%d to 'mutex' (Option or Argument expected)"):format(i)) - end - - table.insert(self._mutexes, elements) - return self -end - -function Parser:group(name, ...) - assert(type(name) == "string", ("bad argument #1 to 'group' (string expected, got %s)"):format(type(name))) - - local group = {name = name, ...} - - for i, element in ipairs(group) do - local mt = getmetatable(element) - assert(mt == Option or mt == Argument or mt == Command, - ("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(i + 1)) - end - - table.insert(self._groups, group) - return self -end - -local usage_welcome = "Usage: " - -function Parser:get_usage() - if self._usage then - return self._usage - end - - local usage_margin = self:_inherit_property("usage_margin", #usage_welcome) - local max_usage_width = self:_inherit_property("usage_max_width", 70) - local lines = {usage_welcome .. self:_get_fullname()} - - local function add(s) - if #lines[#lines]+1+#s <= max_usage_width then - lines[#lines] = lines[#lines] .. " " .. s - else - lines[#lines+1] = (" "):rep(usage_margin) .. s - end - end - - -- Normally options are before positional arguments in usage messages. - -- However, vararg options should be after, because they can't be reliable used - -- before a positional argument. - -- Mutexes come into play, too, and are shown as soon as possible. - -- Overall, output usages in the following order: - -- 1. Mutexes that don't have positional arguments or vararg options. - -- 2. Options that are not in any mutexes and are not vararg. - -- 3. Positional arguments - on their own or as a part of a mutex. - -- 4. Remaining mutexes. - -- 5. Remaining options. - - local elements_in_mutexes = {} - local added_elements = {} - local added_mutexes = {} - local argument_to_mutexes = {} - - local function add_mutex(mutex, main_argument) - if added_mutexes[mutex] then - return - end - - added_mutexes[mutex] = true - local buf = {} - - for _, element in ipairs(mutex) do - if not element._hidden and not added_elements[element] then - if getmetatable(element) == Option or element == main_argument then - table.insert(buf, element:_get_usage()) - added_elements[element] = true - end - end - end - - if #buf == 1 then - add(buf[1]) - elseif #buf > 1 then - add("(" .. table.concat(buf, " | ") .. ")") - end - end - - local function add_element(element) - if not element._hidden and not added_elements[element] then - add(element:_get_usage()) - added_elements[element] = true - end - end - - for _, mutex in ipairs(self._mutexes) do - local is_vararg = false - local has_argument = false - - for _, element in ipairs(mutex) do - if getmetatable(element) == Option then - if element:_is_vararg() then - is_vararg = true - end - else - has_argument = true - argument_to_mutexes[element] = argument_to_mutexes[element] or {} - table.insert(argument_to_mutexes[element], mutex) - end - - elements_in_mutexes[element] = true - end - - if not is_vararg and not has_argument then - add_mutex(mutex) - end - end - - for _, option in ipairs(self._options) do - if not elements_in_mutexes[option] and not option:_is_vararg() then - add_element(option) - end - end - - -- Add usages for positional arguments, together with one mutex containing them, if they are in a mutex. - for _, argument in ipairs(self._arguments) do - -- Pick a mutex as a part of which to show this argument, take the first one that's still available. - local mutex - - if elements_in_mutexes[argument] then - for _, argument_mutex in ipairs(argument_to_mutexes[argument]) do - if not added_mutexes[argument_mutex] then - mutex = argument_mutex - end - end - end - - if mutex then - add_mutex(mutex, argument) - else - add_element(argument) - end - end - - for _, mutex in ipairs(self._mutexes) do - add_mutex(mutex) - end - - for _, option in ipairs(self._options) do - add_element(option) - end - - if #self._commands > 0 then - if self._require_command then - add("") - else - add("[]") - end - - add("...") - end - - return table.concat(lines, "\n") -end - -local function split_lines(s) - if s == "" then - return {} - end - - local lines = {} - - if s:sub(-1) ~= "\n" then - s = s .. "\n" - end - - for line in s:gmatch("([^\n]*)\n") do - table.insert(lines, line) - end - - return lines -end - -local function autowrap_line(line, max_length) - -- Algorithm for splitting lines is simple and greedy. - local result_lines = {} - - -- Preserve original indentation of the line, put this at the beginning of each result line. - -- If the first word looks like a list marker ('*', '+', or '-'), add spaces so that starts - -- of the second and the following lines vertically align with the start of the second word. - local indentation = line:match("^ *") - - if line:find("^ *[%*%+%-]") then - indentation = indentation .. " " .. line:match("^ *[%*%+%-]( *)") - end - - -- Parts of the last line being assembled. - local line_parts = {} - - -- Length of the current line. - local line_length = 0 - - -- Index of the next character to consider. - local index = 1 - - while true do - local word_start, word_finish, word = line:find("([^ ]+)", index) - - if not word_start then - -- Ignore trailing spaces, if any. - break - end - - local preceding_spaces = line:sub(index, word_start - 1) - index = word_finish + 1 - - if (#line_parts == 0) or (line_length + #preceding_spaces + #word <= max_length) then - -- Either this is the very first word or it fits as an addition to the current line, add it. - table.insert(line_parts, preceding_spaces) -- For the very first word this adds the indentation. - table.insert(line_parts, word) - line_length = line_length + #preceding_spaces + #word - else - -- Does not fit, finish current line and put the word into a new one. - table.insert(result_lines, table.concat(line_parts)) - line_parts = {indentation, word} - line_length = #indentation + #word - end - end - - if #line_parts > 0 then - table.insert(result_lines, table.concat(line_parts)) - end - - if #result_lines == 0 then - -- Preserve empty lines. - result_lines[1] = "" - end - - return result_lines -end - --- Automatically wraps lines within given array, --- attempting to limit line length to `max_length`. --- Existing line splits are preserved. -local function autowrap(lines, max_length) - local result_lines = {} - - for _, line in ipairs(lines) do - local autowrapped_lines = autowrap_line(line, max_length) - - for _, autowrapped_line in ipairs(autowrapped_lines) do - table.insert(result_lines, autowrapped_line) - end - end - - return result_lines -end - -function Parser:_get_element_help(element) - local label_lines = element:_get_label_lines() - local description_lines = split_lines(element:_get_description()) - - local result_lines = {} - - -- All label lines should have the same length (except the last one, it has no comma). - -- If too long, start description after all the label lines. - -- Otherwise, combine label and description lines. - - local usage_margin_len = self:_inherit_property("help_usage_margin", 3) - local usage_margin = (" "):rep(usage_margin_len) - local description_margin_len = self:_inherit_property("help_description_margin", 25) - local description_margin = (" "):rep(description_margin_len) - - local help_max_width = self:_inherit_property("help_max_width") - - if help_max_width then - local description_max_width = math.max(help_max_width - description_margin_len, 10) - description_lines = autowrap(description_lines, description_max_width) - end - - if #label_lines[1] >= (description_margin_len - usage_margin_len) then - for _, label_line in ipairs(label_lines) do - table.insert(result_lines, usage_margin .. label_line) - end - - for _, description_line in ipairs(description_lines) do - table.insert(result_lines, description_margin .. description_line) - end - else - for i = 1, math.max(#label_lines, #description_lines) do - local label_line = label_lines[i] - local description_line = description_lines[i] - - local line = "" - - if label_line then - line = usage_margin .. label_line - end - - if description_line and description_line ~= "" then - line = line .. (" "):rep(description_margin_len - #line) .. description_line - end - - table.insert(result_lines, line) - end - end - - return table.concat(result_lines, "\n") -end - -local function get_group_types(group) - local types = {} - - for _, element in ipairs(group) do - types[getmetatable(element)] = true - end - - return types -end - -function Parser:_add_group_help(blocks, added_elements, label, elements) - local buf = {label} - - for _, element in ipairs(elements) do - if not element._hidden and not added_elements[element] then - added_elements[element] = true - table.insert(buf, self:_get_element_help(element)) - end - end - - if #buf > 1 then - table.insert(blocks, table.concat(buf, ("\n"):rep(self:_inherit_property("help_vertical_space", 0) + 1))) - end -end - -function Parser:get_help() - if self._help then - return self._help - end - - local blocks = {self:get_usage()} - - local help_max_width = self:_inherit_property("help_max_width") - - if self._description then - local description = self._description - - if help_max_width then - description = table.concat(autowrap(split_lines(description), help_max_width), "\n") - end - - table.insert(blocks, description) - end - - -- 1. Put groups containing arguments first, then other arguments. - -- 2. Put remaining groups containing options, then other options. - -- 3. Put remaining groups containing commands, then other commands. - -- Assume that an element can't be in several groups. - local groups_by_type = { - [Argument] = {}, - [Option] = {}, - [Command] = {} - } - - for _, group in ipairs(self._groups) do - local group_types = get_group_types(group) - - for _, mt in ipairs({Argument, Option, Command}) do - if group_types[mt] then - table.insert(groups_by_type[mt], group) - break - end - end - end - - local default_groups = { - {name = "Arguments", type = Argument, elements = self._arguments}, - {name = "Options", type = Option, elements = self._options}, - {name = "Commands", type = Command, elements = self._commands} - } - - local added_elements = {} - - for _, default_group in ipairs(default_groups) do - local type_groups = groups_by_type[default_group.type] - - for _, group in ipairs(type_groups) do - self:_add_group_help(blocks, added_elements, group.name .. ":", group) - end - - local default_label = default_group.name .. ":" - - if #type_groups > 0 then - default_label = "Other " .. default_label:gsub("^.", string.lower) - end - - self:_add_group_help(blocks, added_elements, default_label, default_group.elements) - end - - if self._epilog then - local epilog = self._epilog - - if help_max_width then - epilog = table.concat(autowrap(split_lines(epilog), help_max_width), "\n") - end - - table.insert(blocks, epilog) - end - - return table.concat(blocks, "\n\n") -end - -local function get_tip(context, wrong_name) - local context_pool = {} - local possible_name - local possible_names = {} - - for name in pairs(context) do - if type(name) == "string" then - for i = 1, #name do - possible_name = name:sub(1, i - 1) .. name:sub(i + 1) - - if not context_pool[possible_name] then - context_pool[possible_name] = {} - end - - table.insert(context_pool[possible_name], name) - end - end - end - - for i = 1, #wrong_name + 1 do - possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1) - - if context[possible_name] then - possible_names[possible_name] = true - elseif context_pool[possible_name] then - for _, name in ipairs(context_pool[possible_name]) do - possible_names[name] = true - end - end - end - - local first = next(possible_names) - - if first then - if next(possible_names, first) then - local possible_names_arr = {} - - for name in pairs(possible_names) do - table.insert(possible_names_arr, "'" .. name .. "'") - end - - table.sort(possible_names_arr) - return "\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?" - else - return "\nDid you mean '" .. first .. "'?" - end - else - return "" - end -end - -local ElementState = class({ - invocations = 0 -}) - -function ElementState:__call(state, element) - self.state = state - self.result = state.result - self.element = element - self.target = element._target or element:_get_default_target() - self.action, self.result[self.target] = element:_get_action() - return self -end - -function ElementState:error(fmt, ...) - self.state:error(fmt, ...) -end - -function ElementState:convert(argument, index) - local converter = self.element._convert - - if converter then - local ok, err - - if type(converter) == "function" then - ok, err = converter(argument) - elseif type(converter[index]) == "function" then - ok, err = converter[index](argument) - else - ok = converter[argument] - end - - if ok == nil then - self:error(err and "%s" or "malformed argument '%s'", err or argument) - end - - argument = ok - end - - return argument -end - -function ElementState:default(mode) - return self.element._defmode:find(mode) and self.element._default -end - -local function bound(noun, min, max, is_max) - local res = "" - - if min ~= max then - res = "at " .. (is_max and "most" or "least") .. " " - end - - local number = is_max and max or min - return res .. tostring(number) .. " " .. noun .. (number == 1 and "" or "s") -end - -function ElementState:set_name(alias) - self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name) -end - -function ElementState:invoke() - self.open = true - self.overwrite = false - - if self.invocations >= self.element._maxcount then - if self.element._overwrite then - self.overwrite = true - else - local num_times_repr = bound("time", self.element._mincount, self.element._maxcount, true) - self:error("%s must be used %s", self.name, num_times_repr) - end - else - self.invocations = self.invocations + 1 - end - - self.args = {} - - if self.element._maxargs <= 0 then - self:close() - end - - return self.open -end - -function ElementState:pass(argument) - argument = self:convert(argument, #self.args + 1) - table.insert(self.args, argument) - - if #self.args >= self.element._maxargs then - self:close() - end - - return self.open -end - -function ElementState:complete_invocation() - while #self.args < self.element._minargs do - self:pass(self.element._default) - end -end - -function ElementState:close() - if self.open then - self.open = false - - if #self.args < self.element._minargs then - if self:default("a") then - self:complete_invocation() - else - if #self.args == 0 then - if getmetatable(self.element) == Argument then - self:error("missing %s", self.name) - elseif self.element._maxargs == 1 then - self:error("%s requires an argument", self.name) - end - end - - self:error("%s requires %s", self.name, bound("argument", self.element._minargs, self.element._maxargs)) - end - end - - local args - - if self.element._maxargs == 0 then - args = self.args[1] - elseif self.element._maxargs == 1 then - if self.element._minargs == 0 and self.element._mincount ~= self.element._maxcount then - args = self.args - else - args = self.args[1] - end - else - args = self.args - end - - self.action(self.result, self.target, args, self.overwrite) - end -end - -local ParseState = class({ - result = {}, - options = {}, - arguments = {}, - argument_i = 1, - element_to_mutexes = {}, - mutex_to_element_state = {}, - command_actions = {} -}) - -function ParseState:__call(parser, error_handler) - self.parser = parser - self.error_handler = error_handler - self.charset = parser:_update_charset() - self:switch(parser) - return self -end - -function ParseState:error(fmt, ...) - self.error_handler(self.parser, fmt:format(...)) -end - -function ParseState:switch(parser) - self.parser = parser - - if parser._action then - table.insert(self.command_actions, {action = parser._action, name = parser._name}) - end - - for _, option in ipairs(parser._options) do - option = ElementState(self, option) - table.insert(self.options, option) - - for _, alias in ipairs(option.element._aliases) do - self.options[alias] = option - end - end - - for _, mutex in ipairs(parser._mutexes) do - for _, element in ipairs(mutex) do - if not self.element_to_mutexes[element] then - self.element_to_mutexes[element] = {} - end - - table.insert(self.element_to_mutexes[element], mutex) - end - end - - for _, argument in ipairs(parser._arguments) do - argument = ElementState(self, argument) - table.insert(self.arguments, argument) - argument:set_name() - argument:invoke() - end - - self.handle_options = parser._handle_options - self.argument = self.arguments[self.argument_i] - self.commands = parser._commands - - for _, command in ipairs(self.commands) do - for _, alias in ipairs(command._aliases) do - self.commands[alias] = command - end - end -end - -function ParseState:get_option(name) - local option = self.options[name] - - if not option then - self:error("unknown option '%s'%s", name, get_tip(self.options, name)) - else - return option - end -end - -function ParseState:get_command(name) - local command = self.commands[name] - - if not command then - if #self.commands > 0 then - self:error("unknown command '%s'%s", name, get_tip(self.commands, name)) - else - self:error("too many arguments") - end - else - return command - end -end - -function ParseState:check_mutexes(element_state) - if self.element_to_mutexes[element_state.element] then - for _, mutex in ipairs(self.element_to_mutexes[element_state.element]) do - local used_element_state = self.mutex_to_element_state[mutex] - - if used_element_state and used_element_state ~= element_state then - self:error("%s can not be used together with %s", element_state.name, used_element_state.name) - else - self.mutex_to_element_state[mutex] = element_state - end - end - end -end - -function ParseState:invoke(option, name) - self:close() - option:set_name(name) - self:check_mutexes(option, name) - - if option:invoke() then - self.option = option - end -end - -function ParseState:pass(arg) - if self.option then - if not self.option:pass(arg) then - self.option = nil - end - elseif self.argument then - self:check_mutexes(self.argument) - - if not self.argument:pass(arg) then - self.argument_i = self.argument_i + 1 - self.argument = self.arguments[self.argument_i] - end - else - local command = self:get_command(arg) - self.result[command._target or command._name] = true - - if self.parser._command_target then - self.result[self.parser._command_target] = command._name - end - - self:switch(command) - end -end - -function ParseState:close() - if self.option then - self.option:close() - self.option = nil - end -end - -function ParseState:finalize() - self:close() - - for i = self.argument_i, #self.arguments do - local argument = self.arguments[i] - if #argument.args == 0 and argument:default("u") then - argument:complete_invocation() - else - argument:close() - end - end - - if self.parser._require_command and #self.commands > 0 then - self:error("a command is required") - end - - for _, option in ipairs(self.options) do - option.name = option.name or ("option '%s'"):format(option.element._name) - - if option.invocations == 0 then - if option:default("u") then - option:invoke() - option:complete_invocation() - option:close() - end - end - - local mincount = option.element._mincount - - if option.invocations < mincount then - if option:default("a") then - while option.invocations < mincount do - option:invoke() - option:close() - end - elseif option.invocations == 0 then - self:error("missing %s", option.name) - else - self:error("%s must be used %s", option.name, bound("time", mincount, option.element._maxcount)) - end - end - end - - for i = #self.command_actions, 1, -1 do - self.command_actions[i].action(self.result, self.command_actions[i].name) - end -end - -function ParseState:parse(args) - for _, arg in ipairs(args) do - local plain = true - - if self.handle_options then - local first = arg:sub(1, 1) - - if self.charset[first] then - if #arg > 1 then - plain = false - - if arg:sub(2, 2) == first then - if #arg == 2 then - if self.options[arg] then - local option = self:get_option(arg) - self:invoke(option, arg) - else - self:close() - end - - self.handle_options = false - else - local equals = arg:find "=" - if equals then - local name = arg:sub(1, equals - 1) - local option = self:get_option(name) - - if option.element._maxargs <= 0 then - self:error("option '%s' does not take arguments", name) - end - - self:invoke(option, name) - self:pass(arg:sub(equals + 1)) - else - local option = self:get_option(arg) - self:invoke(option, arg) - end - end - else - for i = 2, #arg do - local name = first .. arg:sub(i, i) - local option = self:get_option(name) - self:invoke(option, name) - - if i ~= #arg and option.element._maxargs > 0 then - self:pass(arg:sub(i + 1)) - break - end - end - end - end - end - end - - if plain then - self:pass(arg) - end - end - - self:finalize() - return self.result -end - -function Parser:error(msg) - io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg)) - os.exit(1) -end - --- Compatibility with strict.lua and other checkers: -local default_cmdline = rawget(_G, "arg") or {} - -function Parser:_parse(args, error_handler) - return ParseState(self, error_handler):parse(args or default_cmdline) -end - -function Parser:parse(args) - return self:_parse(args, self.error) -end - -local function xpcall_error_handler(err) - return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2) -end - -function Parser:pparse(args) - local parse_error - - local ok, result = xpcall(function() - return self:_parse(args, function(_, err) - parse_error = err - error(err, 0) - end) - end, xpcall_error_handler) - - if ok then - return true, result - elseif not parse_error then - error(result, 0) - else - return false, parse_error - end -end - -local argparse = {} - -argparse.version = "0.6.0" - -setmetatable(argparse, {__call = function(_, ...) - return Parser(default_cmdline[0]):add_help(true)(...) -end}) - -return argparse - -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() diff --git a/build/luacomp-5.3.lua b/build/luacomp-5.3.lua deleted file mode 100644 index fb2355c..0000000 --- a/build/luacomp-5.3.lua +++ /dev/null @@ -1,514 +0,0 @@ -#!/usr/bin/env lua5.3 ---[[ - init.lua - Main file of LuaComp - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - - -local function _sv(k, v) - _G[k] = v - --os.setenv(k, tostring(v)) -end - -_sv("LUACOMP_V_MAJ", 1) -_sv("LUACOMP_V_MIN", 1) -_sv("LUACOMP_V_PAT", 0) -_sv("LUACOMP_VERSION", LUACOMP_V_MAJ.."."..LUACOMP_V_MIN.."."..LUACOMP_V_PAT) -_sv("LUACOMP_NAME", "LuaComp") - ---[[ - ast.lua - Generates a structure for use in preprocessing. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function nextc(f, c) - c = c or 1 - return f:read(c) -end - -local function peek(f, c) - c = c or 1 - local z = f:read(c) - f:seek("cur", -c) - return z -end - -local function skip(f, c) - c = c or 1 - return f:seek("cur", c) -end - -local ws = { - ["\t"] = true, - [" "] = true -} - -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 - 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 - hex = hex .. lc - end - end - return tonumber(hex, 16) -end - -local function parse_number(f, c) - local lc = " " - local num = c - while 48 <= lc:byte() and lc:byte() <= 57 do - lc = nextc(f) - if (48 <= lc:byte() and lc:byte() <= 57) then - num = num .. lc - end - end - return tonumber(hex, 10) -end - -local esct = { - ["t"] = "\t", - ["n"] = "\n", - ["r"] = "\r", - ["\\"] = "\\\\" -} - -for i=0, 9 do - esct[tostring(i)] = string.char(i) -end - -local function parse_dblquote(f) - local val = "" - while peek(f) ~= "\"" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_snglquote(f) - local val = "" - while peek(f) ~= "\'" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_envarg(f) - local val = "" - while peek(f) ~= ")" do - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - val = val .. nextc(f) - end - skip(f) - return val -end - -local function parse_directive(f) - local lc = "_" - local name = "" - local args = {} - local carg = "" - while not ws[lc] do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name} - elseif not ws[lc] then - name = name .. lc - end - end - while true do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name, args=args} - elseif lc == "0" and peek(f) == "x" then - skip(f) - local val = parse_hex(f) - args[#args+1] = val - elseif 48 <= lc:byte() and lc:byte() <= 57 then - local val = parse_number(f, lc) - args[#args+1] = val - elseif lc == "\"" then - local val, e = parse_dblquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "\'" then - local val, e = parse_snglquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "$" and peek(f) == "(" then - skip(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 not ws[lc] then - return nil, "Syntax error" - end - end -end - -local function mkast(f, n) - io.stderr:write("PROC\t",n,"\n") - local lc = " " - local lpos = 1 - local ilpos = 1 - local tree = {} - local code = "" - local branches = {} - local function add_code() - tree[#tree+1] = {type="code", data=code, file=n, line=lpos} - code = "" - end - local function parse_error(e) - io.stderr:write("ERROR:"..n..":"..lpos..": "..e.."\n") - os.exit(1) - end - while lc and lc ~= "" do - lc = nextc(f) - if (lc == "-" and ilpos == 1) then - if (peek(f, 2) == "-#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "/" and ilpos == 1) then - if (peek(f, 2) == "/#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "$" and peek(f) == "(") then - add_code() - skip(f) - local val, e = parse_envarg(f) - if not val then - parse_error(e) - end - tree[#tree+1] = {type="envvar", var=val, file=n, line=lpos} - 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="lua", 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="lua_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) - end - lpos = lpos+1 - ilpos = 1 - code = code .. "\n" - else - code = code .. (lc or "") - ilpos = ilpos+1 - end - end - add_code() - return tree -end - ---[[ - generator.lua - Generates the code. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function lua_escape(code) - return code:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n") -end - -local directives = {} - -local function generate(ast) - local lua_code = "" - for i=1, #ast do - local leaf = ast[i] - if (leaf.type == "lua") then - lua_code = lua_code .. leaf.code - elseif (leaf.type == "directive") then - local stargs = {} - for i=1, #leaf.args do - local arg = leaf.args[i] - if (type(arg) == "string") then - stargs[i] = "\""..lua_escape(arg).."\"" - elseif (type(arg) == "number") then - stargs[i] = tostring(arg) - end - end - lua_code = lua_code .. "call_directive(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.name.."\","..table.concat(stargs, ",")..")" - elseif (leaf.type == "envvar") then - lua_code = lua_code .. "put_env(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.var.."\")" - elseif (leaf.type == "code") then - 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.."))" - else - io.stderr:write("ERROR: Internal catastrophic failure, unknown type "..leaf.type.."\n") - os.exit(1) - end - lua_code = lua_code .. "\n" - end - local env = {code = ""} - local function run_away_screaming(fpos, err) - io.stdout:write("ERROR: "..fpos..": "..err.."\n") - os.exit(1) - end - local function call_directive(fpos, dname, ...) - if (not directives[dname]) then - run_away_screaming(fpos, "Invalid directive name `"..dname.."'") - end - local r, er = directives[dname](env, ...) - if (not r) then - run_away_screaming(fpos, er) - end - end - local function put_env(fpos, evar) - local e = os.getenv(evar) - if not e then - run_away_screaming(fpos, "Enviroment variable `"..evar.."' does not exist!") - end - env.code = env.code .. "\""..lua_escape(e).."\"" - end - local function put_code(fpos, code) - env.code = env.code .. code --not much that can fail here... - end - local fenv = {} - for k, v in pairs(_G) do - fenv[k] = v - end - fenv._G = fenv - fenv._ENV = fenv - fenv.call_directive = call_directive - fenv.put_code = put_code - fenv.put_env = put_env - local func = assert(load(lua_code, "=(generated code)", "t", fenv)) - func() - return env.code -end - ---[[ - directive_provider.lua - Provides preprocessor directives - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - ----#include "directives/define.lua" - -function directives.include(env, file) - if (not os.execute("stat "..file..">/dev/null")) then - return false, "File `"..file.."' does not exist!" - end - local f = io.open(file, "r") - local fast = mkast(f, file) - local code = generate(fast) - env.code = env.code .. "\n" .. code .. "\n" - return true -end - -local warned = false -function directives.loadmod(env, mod) - if not warned then - io.stderr:write("Warning: loadmod is depreciated and unsafe. The API differs from luapreproc.\n") - warned = true - end - if (not os.execute("stat "..file..">/dev/null")) then - return false, "Module `"..file.."' does not exist!" - end - local modname, func = dofile(mod) - directives[modname] = func - return true -end - - ---[[ - cfg/minifier_providers.lua - Provides minifier providers. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local providers = {} - -function providers.luamin(cin) - local fn = os.tmpname() - local fh = io.open(fn, "w") - fh:write(cin) - fh:close() - local lmh = io.popen("luamin -f "..fn.." 2>&1", "r") - local dat = lmh:read("*a") - local stat, _, code = lmh:close() - if (code ~= 0) then - return false, dat - end - return dat -end - -function providers.none(cin) - return cin -end - -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 = 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() diff --git a/build/luacomp-generic-static.lua b/build/luacomp-generic-static.lua deleted file mode 100644 index 3caef96..0000000 --- a/build/luacomp-generic-static.lua +++ /dev/null @@ -1,2044 +0,0 @@ -#!/usr/bin/env lua ---[[ - staticinit.lua - Main file of LuaComp, directly includes argparse. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - - -local function _sv(k, v) - _G[k] = v - --os.setenv(k, tostring(v)) -end - -_sv("LUACOMP_V_MAJ", 1) -_sv("LUACOMP_V_MIN", 1) -_sv("LUACOMP_V_PAT", 0) -_sv("LUACOMP_VERSION", LUACOMP_V_MAJ.."."..LUACOMP_V_MIN.."."..LUACOMP_V_PAT) -_sv("LUACOMP_NAME", "LuaComp") - ---[[ - ast.lua - Generates a structure for use in preprocessing. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function nextc(f, c) - c = c or 1 - return f:read(c) -end - -local function peek(f, c) - c = c or 1 - local z = f:read(c) - f:seek("cur", -c) - return z -end - -local function skip(f, c) - c = c or 1 - return f:seek("cur", c) -end - -local ws = { - ["\t"] = true, - [" "] = true -} - -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 - 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 - hex = hex .. lc - end - end - return tonumber(hex, 16) -end - -local function parse_number(f, c) - local lc = " " - local num = c - while 48 <= lc:byte() and lc:byte() <= 57 do - lc = nextc(f) - if (48 <= lc:byte() and lc:byte() <= 57) then - num = num .. lc - end - end - return tonumber(hex, 10) -end - -local esct = { - ["t"] = "\t", - ["n"] = "\n", - ["r"] = "\r", - ["\\"] = "\\\\" -} - -for i=0, 9 do - esct[tostring(i)] = string.char(i) -end - -local function parse_dblquote(f) - local val = "" - while peek(f) ~= "\"" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_snglquote(f) - local val = "" - while peek(f) ~= "\'" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_envarg(f) - local val = "" - while peek(f) ~= ")" do - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - val = val .. nextc(f) - end - skip(f) - return val -end - -local function parse_directive(f) - local lc = "_" - local name = "" - local args = {} - local carg = "" - while not ws[lc] do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name} - elseif not ws[lc] then - name = name .. lc - end - end - while true do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name, args=args} - elseif lc == "0" and peek(f) == "x" then - skip(f) - local val = parse_hex(f) - args[#args+1] = val - elseif 48 <= lc:byte() and lc:byte() <= 57 then - local val = parse_number(f, lc) - args[#args+1] = val - elseif lc == "\"" then - local val, e = parse_dblquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "\'" then - local val, e = parse_snglquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "$" and peek(f) == "(" then - skip(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 not ws[lc] then - return nil, "Syntax error" - end - end -end - -local function mkast(f, n) - io.stderr:write("PROC\t",n,"\n") - local lc = " " - local lpos = 1 - local ilpos = 1 - local tree = {} - local code = "" - local branches = {} - local function add_code() - tree[#tree+1] = {type="code", data=code, file=n, line=lpos} - code = "" - end - local function parse_error(e) - io.stderr:write("ERROR:"..n..":"..lpos..": "..e.."\n") - os.exit(1) - end - while lc and lc ~= "" do - lc = nextc(f) - if (lc == "-" and ilpos == 1) then - if (peek(f, 2) == "-#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "/" and ilpos == 1) then - if (peek(f, 2) == "/#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "$" and peek(f) == "(") then - add_code() - skip(f) - local val, e = parse_envarg(f) - if not val then - parse_error(e) - end - tree[#tree+1] = {type="envvar", var=val, file=n, line=lpos} - 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="lua", 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="lua_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) - end - lpos = lpos+1 - ilpos = 1 - code = code .. "\n" - else - code = code .. (lc or "") - ilpos = ilpos+1 - end - end - add_code() - return tree -end - ---[[ - generator.lua - Generates the code. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function lua_escape(code) - return code:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n") -end - -local directives = {} - -local function generate(ast) - local lua_code = "" - for i=1, #ast do - local leaf = ast[i] - if (leaf.type == "lua") then - lua_code = lua_code .. leaf.code - elseif (leaf.type == "directive") then - local stargs = {} - for i=1, #leaf.args do - local arg = leaf.args[i] - if (type(arg) == "string") then - stargs[i] = "\""..lua_escape(arg).."\"" - elseif (type(arg) == "number") then - stargs[i] = tostring(arg) - end - end - lua_code = lua_code .. "call_directive(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.name.."\","..table.concat(stargs, ",")..")" - elseif (leaf.type == "envvar") then - lua_code = lua_code .. "put_env(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.var.."\")" - elseif (leaf.type == "code") then - 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.."))" - else - io.stderr:write("ERROR: Internal catastrophic failure, unknown type "..leaf.type.."\n") - os.exit(1) - end - lua_code = lua_code .. "\n" - end - local env = {code = ""} - local function run_away_screaming(fpos, err) - io.stdout:write("ERROR: "..fpos..": "..err.."\n") - os.exit(1) - end - local function call_directive(fpos, dname, ...) - if (not directives[dname]) then - run_away_screaming(fpos, "Invalid directive name `"..dname.."'") - end - local r, er = directives[dname](env, ...) - if (not r) then - run_away_screaming(fpos, er) - end - end - local function put_env(fpos, evar) - local e = os.getenv(evar) - if not e then - run_away_screaming(fpos, "Enviroment variable `"..evar.."' does not exist!") - end - env.code = env.code .. "\""..lua_escape(e).."\"" - end - local function put_code(fpos, code) - env.code = env.code .. code --not much that can fail here... - end - local fenv = {} - for k, v in pairs(_G) do - fenv[k] = v - end - fenv._G = fenv - fenv._ENV = fenv - fenv.call_directive = call_directive - fenv.put_code = put_code - fenv.put_env = put_env - local func = assert(load(lua_code, "=(generated code)", "t", fenv)) - func() - return env.code -end - ---[[ - directive_provider.lua - Provides preprocessor directives - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - ----#include "directives/define.lua" - -function directives.include(env, file) - if (not os.execute("stat "..file..">/dev/null")) then - return false, "File `"..file.."' does not exist!" - end - local f = io.open(file, "r") - local fast = mkast(f, file) - local code = generate(fast) - env.code = env.code .. "\n" .. code .. "\n" - return true -end - -local warned = false -function directives.loadmod(env, mod) - if not warned then - io.stderr:write("Warning: loadmod is depreciated and unsafe. The API differs from luapreproc.\n") - warned = true - end - if (not os.execute("stat "..file..">/dev/null")) then - return false, "Module `"..file.."' does not exist!" - end - local modname, func = dofile(mod) - directives[modname] = func - return true -end - - ---[[ - cfg/minifier_providers.lua - Provides minifier providers. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local providers = {} - -function providers.luamin(cin) - local fn = os.tmpname() - local fh = io.open(fn, "w") - fh:write(cin) - fh:close() - local lmh = io.popen("luamin -f "..fn.." 2>&1", "r") - local dat = lmh:read("*a") - local stat, _, code = lmh:close() - if (code ~= 0) then - return false, dat - end - return dat -end - -function providers.none(cin) - return cin -end - -local argparse = (function() - --- The MIT License (MIT) - --- Copyright (c) 2013 - 2018 Peter Melnichenko - --- Permission is hereby granted, free of charge, to any person obtaining a copy of --- this software and associated documentation files (the "Software"), to deal in --- the Software without restriction, including without limitation the rights to --- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of --- the Software, and to permit persons to whom the Software is furnished to do so, --- subject to the following conditions: - --- The above copyright notice and this permission notice shall be included in all --- copies or substantial portions of the Software. - --- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS --- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR --- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER --- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN --- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -local function deep_update(t1, t2) - for k, v in pairs(t2) do - if type(v) == "table" then - v = deep_update({}, v) - end - - t1[k] = v - end - - return t1 -end - --- A property is a tuple {name, callback}. --- properties.args is number of properties that can be set as arguments --- when calling an object. -local function class(prototype, properties, parent) - -- Class is the metatable of its instances. - local cl = {} - cl.__index = cl - - if parent then - cl.__prototype = deep_update(deep_update({}, parent.__prototype), prototype) - else - cl.__prototype = prototype - end - - if properties then - local names = {} - - -- Create setter methods and fill set of property names. - for _, property in ipairs(properties) do - local name, callback = property[1], property[2] - - cl[name] = function(self, value) - if not callback(self, value) then - self["_" .. name] = value - end - - return self - end - - names[name] = true - end - - function cl.__call(self, ...) - -- When calling an object, if the first argument is a table, - -- interpret keys as property names, else delegate arguments - -- to corresponding setters in order. - if type((...)) == "table" then - for name, value in pairs((...)) do - if names[name] then - self[name](self, value) - end - end - else - local nargs = select("#", ...) - - for i, property in ipairs(properties) do - if i > nargs or i > properties.args then - break - end - - local arg = select(i, ...) - - if arg ~= nil then - self[property[1]](self, arg) - end - end - end - - return self - end - end - - -- If indexing class fails, fallback to its parent. - local class_metatable = {} - class_metatable.__index = parent - - function class_metatable.__call(self, ...) - -- Calling a class returns its instance. - -- Arguments are delegated to the instance. - local object = deep_update({}, self.__prototype) - setmetatable(object, self) - return object(...) - end - - return setmetatable(cl, class_metatable) -end - -local function typecheck(name, types, value) - for _, type_ in ipairs(types) do - if type(value) == type_ then - return true - end - end - - error(("bad property '%s' (%s expected, got %s)"):format(name, table.concat(types, " or "), type(value))) -end - -local function typechecked(name, ...) - local types = {...} - return {name, function(_, value) typecheck(name, types, value) end} -end - -local multiname = {"name", function(self, value) - typecheck("name", {"string"}, value) - - for alias in value:gmatch("%S+") do - self._name = self._name or alias - table.insert(self._aliases, alias) - end - - -- Do not set _name as with other properties. - return true -end} - -local function parse_boundaries(str) - if tonumber(str) then - return tonumber(str), tonumber(str) - end - - if str == "*" then - return 0, math.huge - end - - if str == "+" then - return 1, math.huge - end - - if str == "?" then - return 0, 1 - end - - if str:match "^%d+%-%d+$" then - local min, max = str:match "^(%d+)%-(%d+)$" - return tonumber(min), tonumber(max) - end - - if str:match "^%d+%+$" then - local min = str:match "^(%d+)%+$" - return tonumber(min), math.huge - end -end - -local function boundaries(name) - return {name, function(self, value) - typecheck(name, {"number", "string"}, value) - - local min, max = parse_boundaries(value) - - if not min then - error(("bad property '%s'"):format(name)) - end - - self["_min" .. name], self["_max" .. name] = min, max - end} -end - -local actions = {} - -local option_action = {"action", function(_, value) - typecheck("action", {"function", "string"}, value) - - if type(value) == "string" and not actions[value] then - error(("unknown action '%s'"):format(value)) - end -end} - -local option_init = {"init", function(self) - self._has_init = true -end} - -local option_default = {"default", function(self, value) - if type(value) ~= "string" then - self._init = value - self._has_init = true - return true - end -end} - -local add_help = {"add_help", function(self, value) - typecheck("add_help", {"boolean", "string", "table"}, value) - - if self._has_help then - table.remove(self._options) - self._has_help = false - end - - if value then - local help = self:flag() - :description "Show this help message and exit." - :action(function() - print(self:get_help()) - os.exit(0) - end) - - if value ~= true then - help = help(value) - end - - if not help._name then - help "-h" "--help" - end - - self._has_help = true - end -end} - -local Parser = class({ - _arguments = {}, - _options = {}, - _commands = {}, - _mutexes = {}, - _groups = {}, - _require_command = true, - _handle_options = true -}, { - args = 3, - typechecked("name", "string"), - typechecked("description", "string"), - typechecked("epilog", "string"), - typechecked("usage", "string"), - typechecked("help", "string"), - typechecked("require_command", "boolean"), - typechecked("handle_options", "boolean"), - typechecked("action", "function"), - typechecked("command_target", "string"), - typechecked("help_vertical_space", "number"), - typechecked("usage_margin", "number"), - typechecked("usage_max_width", "number"), - typechecked("help_usage_margin", "number"), - typechecked("help_description_margin", "number"), - typechecked("help_max_width", "number"), - add_help -}) - -local Command = class({ - _aliases = {} -}, { - args = 3, - multiname, - typechecked("description", "string"), - typechecked("epilog", "string"), - typechecked("target", "string"), - typechecked("usage", "string"), - typechecked("help", "string"), - typechecked("require_command", "boolean"), - typechecked("handle_options", "boolean"), - typechecked("action", "function"), - typechecked("command_target", "string"), - typechecked("help_vertical_space", "number"), - typechecked("usage_margin", "number"), - typechecked("usage_max_width", "number"), - typechecked("help_usage_margin", "number"), - typechecked("help_description_margin", "number"), - typechecked("help_max_width", "number"), - typechecked("hidden", "boolean"), - add_help -}, Parser) - -local Argument = class({ - _minargs = 1, - _maxargs = 1, - _mincount = 1, - _maxcount = 1, - _defmode = "unused", - _show_default = true -}, { - args = 5, - typechecked("name", "string"), - typechecked("description", "string"), - option_default, - typechecked("convert", "function", "table"), - boundaries("args"), - typechecked("target", "string"), - typechecked("defmode", "string"), - typechecked("show_default", "boolean"), - typechecked("argname", "string", "table"), - typechecked("hidden", "boolean"), - option_action, - option_init -}) - -local Option = class({ - _aliases = {}, - _mincount = 0, - _overwrite = true -}, { - args = 6, - multiname, - typechecked("description", "string"), - option_default, - typechecked("convert", "function", "table"), - boundaries("args"), - boundaries("count"), - typechecked("target", "string"), - typechecked("defmode", "string"), - typechecked("show_default", "boolean"), - typechecked("overwrite", "boolean"), - typechecked("argname", "string", "table"), - typechecked("hidden", "boolean"), - option_action, - option_init -}, Argument) - -function Parser:_inherit_property(name, default) - local element = self - - while true do - local value = element["_" .. name] - - if value ~= nil then - return value - end - - if not element._parent then - return default - end - - element = element._parent - end -end - -function Argument:_get_argument_list() - local buf = {} - local i = 1 - - while i <= math.min(self._minargs, 3) do - local argname = self:_get_argname(i) - - if self._default and self._defmode:find "a" then - argname = "[" .. argname .. "]" - end - - table.insert(buf, argname) - i = i+1 - end - - while i <= math.min(self._maxargs, 3) do - table.insert(buf, "[" .. self:_get_argname(i) .. "]") - i = i+1 - - if self._maxargs == math.huge then - break - end - end - - if i < self._maxargs then - table.insert(buf, "...") - end - - return buf -end - -function Argument:_get_usage() - local usage = table.concat(self:_get_argument_list(), " ") - - if self._default and self._defmode:find "u" then - if self._maxargs > 1 or (self._minargs == 1 and not self._defmode:find "a") then - usage = "[" .. usage .. "]" - end - end - - return usage -end - -function actions.store_true(result, target) - result[target] = true -end - -function actions.store_false(result, target) - result[target] = false -end - -function actions.store(result, target, argument) - result[target] = argument -end - -function actions.count(result, target, _, overwrite) - if not overwrite then - result[target] = result[target] + 1 - end -end - -function actions.append(result, target, argument, overwrite) - result[target] = result[target] or {} - table.insert(result[target], argument) - - if overwrite then - table.remove(result[target], 1) - end -end - -function actions.concat(result, target, arguments, overwrite) - if overwrite then - error("'concat' action can't handle too many invocations") - end - - result[target] = result[target] or {} - - for _, argument in ipairs(arguments) do - table.insert(result[target], argument) - end -end - -function Argument:_get_action() - local action, init - - if self._maxcount == 1 then - if self._maxargs == 0 then - action, init = "store_true", nil - else - action, init = "store", nil - end - else - if self._maxargs == 0 then - action, init = "count", 0 - else - action, init = "append", {} - end - end - - if self._action then - action = self._action - end - - if self._has_init then - init = self._init - end - - if type(action) == "string" then - action = actions[action] - end - - return action, init -end - --- Returns placeholder for `narg`-th argument. -function Argument:_get_argname(narg) - local argname = self._argname or self:_get_default_argname() - - if type(argname) == "table" then - return argname[narg] - else - return argname - end -end - -function Argument:_get_default_argname() - return "<" .. self._name .. ">" -end - -function Option:_get_default_argname() - return "<" .. self:_get_default_target() .. ">" -end - --- Returns labels to be shown in the help message. -function Argument:_get_label_lines() - return {self._name} -end - -function Option:_get_label_lines() - local argument_list = self:_get_argument_list() - - if #argument_list == 0 then - -- Don't put aliases for simple flags like `-h` on different lines. - return {table.concat(self._aliases, ", ")} - end - - local longest_alias_length = -1 - - for _, alias in ipairs(self._aliases) do - longest_alias_length = math.max(longest_alias_length, #alias) - end - - local argument_list_repr = table.concat(argument_list, " ") - local lines = {} - - for i, alias in ipairs(self._aliases) do - local line = (" "):rep(longest_alias_length - #alias) .. alias .. " " .. argument_list_repr - - if i ~= #self._aliases then - line = line .. "," - end - - table.insert(lines, line) - end - - return lines -end - -function Command:_get_label_lines() - return {table.concat(self._aliases, ", ")} -end - -function Argument:_get_description() - if self._default and self._show_default then - if self._description then - return ("%s (default: %s)"):format(self._description, self._default) - else - return ("default: %s"):format(self._default) - end - else - return self._description or "" - end -end - -function Command:_get_description() - return self._description or "" -end - -function Option:_get_usage() - local usage = self:_get_argument_list() - table.insert(usage, 1, self._name) - usage = table.concat(usage, " ") - - if self._mincount == 0 or self._default then - usage = "[" .. usage .. "]" - end - - return usage -end - -function Argument:_get_default_target() - return self._name -end - -function Option:_get_default_target() - local res - - for _, alias in ipairs(self._aliases) do - if alias:sub(1, 1) == alias:sub(2, 2) then - res = alias:sub(3) - break - end - end - - res = res or self._name:sub(2) - return (res:gsub("-", "_")) -end - -function Option:_is_vararg() - return self._maxargs ~= self._minargs -end - -function Parser:_get_fullname() - local parent = self._parent - local buf = {self._name} - - while parent do - table.insert(buf, 1, parent._name) - parent = parent._parent - end - - return table.concat(buf, " ") -end - -function Parser:_update_charset(charset) - charset = charset or {} - - for _, command in ipairs(self._commands) do - command:_update_charset(charset) - end - - for _, option in ipairs(self._options) do - for _, alias in ipairs(option._aliases) do - charset[alias:sub(1, 1)] = true - end - end - - return charset -end - -function Parser:argument(...) - local argument = Argument(...) - table.insert(self._arguments, argument) - return argument -end - -function Parser:option(...) - local option = Option(...) - - if self._has_help then - table.insert(self._options, #self._options, option) - else - table.insert(self._options, option) - end - - return option -end - -function Parser:flag(...) - return self:option():args(0)(...) -end - -function Parser:command(...) - local command = Command():add_help(true)(...) - command._parent = self - table.insert(self._commands, command) - return command -end - -function Parser:mutex(...) - local elements = {...} - - for i, element in ipairs(elements) do - local mt = getmetatable(element) - assert(mt == Option or mt == Argument, ("bad argument #%d to 'mutex' (Option or Argument expected)"):format(i)) - end - - table.insert(self._mutexes, elements) - return self -end - -function Parser:group(name, ...) - assert(type(name) == "string", ("bad argument #1 to 'group' (string expected, got %s)"):format(type(name))) - - local group = {name = name, ...} - - for i, element in ipairs(group) do - local mt = getmetatable(element) - assert(mt == Option or mt == Argument or mt == Command, - ("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(i + 1)) - end - - table.insert(self._groups, group) - return self -end - -local usage_welcome = "Usage: " - -function Parser:get_usage() - if self._usage then - return self._usage - end - - local usage_margin = self:_inherit_property("usage_margin", #usage_welcome) - local max_usage_width = self:_inherit_property("usage_max_width", 70) - local lines = {usage_welcome .. self:_get_fullname()} - - local function add(s) - if #lines[#lines]+1+#s <= max_usage_width then - lines[#lines] = lines[#lines] .. " " .. s - else - lines[#lines+1] = (" "):rep(usage_margin) .. s - end - end - - -- Normally options are before positional arguments in usage messages. - -- However, vararg options should be after, because they can't be reliable used - -- before a positional argument. - -- Mutexes come into play, too, and are shown as soon as possible. - -- Overall, output usages in the following order: - -- 1. Mutexes that don't have positional arguments or vararg options. - -- 2. Options that are not in any mutexes and are not vararg. - -- 3. Positional arguments - on their own or as a part of a mutex. - -- 4. Remaining mutexes. - -- 5. Remaining options. - - local elements_in_mutexes = {} - local added_elements = {} - local added_mutexes = {} - local argument_to_mutexes = {} - - local function add_mutex(mutex, main_argument) - if added_mutexes[mutex] then - return - end - - added_mutexes[mutex] = true - local buf = {} - - for _, element in ipairs(mutex) do - if not element._hidden and not added_elements[element] then - if getmetatable(element) == Option or element == main_argument then - table.insert(buf, element:_get_usage()) - added_elements[element] = true - end - end - end - - if #buf == 1 then - add(buf[1]) - elseif #buf > 1 then - add("(" .. table.concat(buf, " | ") .. ")") - end - end - - local function add_element(element) - if not element._hidden and not added_elements[element] then - add(element:_get_usage()) - added_elements[element] = true - end - end - - for _, mutex in ipairs(self._mutexes) do - local is_vararg = false - local has_argument = false - - for _, element in ipairs(mutex) do - if getmetatable(element) == Option then - if element:_is_vararg() then - is_vararg = true - end - else - has_argument = true - argument_to_mutexes[element] = argument_to_mutexes[element] or {} - table.insert(argument_to_mutexes[element], mutex) - end - - elements_in_mutexes[element] = true - end - - if not is_vararg and not has_argument then - add_mutex(mutex) - end - end - - for _, option in ipairs(self._options) do - if not elements_in_mutexes[option] and not option:_is_vararg() then - add_element(option) - end - end - - -- Add usages for positional arguments, together with one mutex containing them, if they are in a mutex. - for _, argument in ipairs(self._arguments) do - -- Pick a mutex as a part of which to show this argument, take the first one that's still available. - local mutex - - if elements_in_mutexes[argument] then - for _, argument_mutex in ipairs(argument_to_mutexes[argument]) do - if not added_mutexes[argument_mutex] then - mutex = argument_mutex - end - end - end - - if mutex then - add_mutex(mutex, argument) - else - add_element(argument) - end - end - - for _, mutex in ipairs(self._mutexes) do - add_mutex(mutex) - end - - for _, option in ipairs(self._options) do - add_element(option) - end - - if #self._commands > 0 then - if self._require_command then - add("") - else - add("[]") - end - - add("...") - end - - return table.concat(lines, "\n") -end - -local function split_lines(s) - if s == "" then - return {} - end - - local lines = {} - - if s:sub(-1) ~= "\n" then - s = s .. "\n" - end - - for line in s:gmatch("([^\n]*)\n") do - table.insert(lines, line) - end - - return lines -end - -local function autowrap_line(line, max_length) - -- Algorithm for splitting lines is simple and greedy. - local result_lines = {} - - -- Preserve original indentation of the line, put this at the beginning of each result line. - -- If the first word looks like a list marker ('*', '+', or '-'), add spaces so that starts - -- of the second and the following lines vertically align with the start of the second word. - local indentation = line:match("^ *") - - if line:find("^ *[%*%+%-]") then - indentation = indentation .. " " .. line:match("^ *[%*%+%-]( *)") - end - - -- Parts of the last line being assembled. - local line_parts = {} - - -- Length of the current line. - local line_length = 0 - - -- Index of the next character to consider. - local index = 1 - - while true do - local word_start, word_finish, word = line:find("([^ ]+)", index) - - if not word_start then - -- Ignore trailing spaces, if any. - break - end - - local preceding_spaces = line:sub(index, word_start - 1) - index = word_finish + 1 - - if (#line_parts == 0) or (line_length + #preceding_spaces + #word <= max_length) then - -- Either this is the very first word or it fits as an addition to the current line, add it. - table.insert(line_parts, preceding_spaces) -- For the very first word this adds the indentation. - table.insert(line_parts, word) - line_length = line_length + #preceding_spaces + #word - else - -- Does not fit, finish current line and put the word into a new one. - table.insert(result_lines, table.concat(line_parts)) - line_parts = {indentation, word} - line_length = #indentation + #word - end - end - - if #line_parts > 0 then - table.insert(result_lines, table.concat(line_parts)) - end - - if #result_lines == 0 then - -- Preserve empty lines. - result_lines[1] = "" - end - - return result_lines -end - --- Automatically wraps lines within given array, --- attempting to limit line length to `max_length`. --- Existing line splits are preserved. -local function autowrap(lines, max_length) - local result_lines = {} - - for _, line in ipairs(lines) do - local autowrapped_lines = autowrap_line(line, max_length) - - for _, autowrapped_line in ipairs(autowrapped_lines) do - table.insert(result_lines, autowrapped_line) - end - end - - return result_lines -end - -function Parser:_get_element_help(element) - local label_lines = element:_get_label_lines() - local description_lines = split_lines(element:_get_description()) - - local result_lines = {} - - -- All label lines should have the same length (except the last one, it has no comma). - -- If too long, start description after all the label lines. - -- Otherwise, combine label and description lines. - - local usage_margin_len = self:_inherit_property("help_usage_margin", 3) - local usage_margin = (" "):rep(usage_margin_len) - local description_margin_len = self:_inherit_property("help_description_margin", 25) - local description_margin = (" "):rep(description_margin_len) - - local help_max_width = self:_inherit_property("help_max_width") - - if help_max_width then - local description_max_width = math.max(help_max_width - description_margin_len, 10) - description_lines = autowrap(description_lines, description_max_width) - end - - if #label_lines[1] >= (description_margin_len - usage_margin_len) then - for _, label_line in ipairs(label_lines) do - table.insert(result_lines, usage_margin .. label_line) - end - - for _, description_line in ipairs(description_lines) do - table.insert(result_lines, description_margin .. description_line) - end - else - for i = 1, math.max(#label_lines, #description_lines) do - local label_line = label_lines[i] - local description_line = description_lines[i] - - local line = "" - - if label_line then - line = usage_margin .. label_line - end - - if description_line and description_line ~= "" then - line = line .. (" "):rep(description_margin_len - #line) .. description_line - end - - table.insert(result_lines, line) - end - end - - return table.concat(result_lines, "\n") -end - -local function get_group_types(group) - local types = {} - - for _, element in ipairs(group) do - types[getmetatable(element)] = true - end - - return types -end - -function Parser:_add_group_help(blocks, added_elements, label, elements) - local buf = {label} - - for _, element in ipairs(elements) do - if not element._hidden and not added_elements[element] then - added_elements[element] = true - table.insert(buf, self:_get_element_help(element)) - end - end - - if #buf > 1 then - table.insert(blocks, table.concat(buf, ("\n"):rep(self:_inherit_property("help_vertical_space", 0) + 1))) - end -end - -function Parser:get_help() - if self._help then - return self._help - end - - local blocks = {self:get_usage()} - - local help_max_width = self:_inherit_property("help_max_width") - - if self._description then - local description = self._description - - if help_max_width then - description = table.concat(autowrap(split_lines(description), help_max_width), "\n") - end - - table.insert(blocks, description) - end - - -- 1. Put groups containing arguments first, then other arguments. - -- 2. Put remaining groups containing options, then other options. - -- 3. Put remaining groups containing commands, then other commands. - -- Assume that an element can't be in several groups. - local groups_by_type = { - [Argument] = {}, - [Option] = {}, - [Command] = {} - } - - for _, group in ipairs(self._groups) do - local group_types = get_group_types(group) - - for _, mt in ipairs({Argument, Option, Command}) do - if group_types[mt] then - table.insert(groups_by_type[mt], group) - break - end - end - end - - local default_groups = { - {name = "Arguments", type = Argument, elements = self._arguments}, - {name = "Options", type = Option, elements = self._options}, - {name = "Commands", type = Command, elements = self._commands} - } - - local added_elements = {} - - for _, default_group in ipairs(default_groups) do - local type_groups = groups_by_type[default_group.type] - - for _, group in ipairs(type_groups) do - self:_add_group_help(blocks, added_elements, group.name .. ":", group) - end - - local default_label = default_group.name .. ":" - - if #type_groups > 0 then - default_label = "Other " .. default_label:gsub("^.", string.lower) - end - - self:_add_group_help(blocks, added_elements, default_label, default_group.elements) - end - - if self._epilog then - local epilog = self._epilog - - if help_max_width then - epilog = table.concat(autowrap(split_lines(epilog), help_max_width), "\n") - end - - table.insert(blocks, epilog) - end - - return table.concat(blocks, "\n\n") -end - -local function get_tip(context, wrong_name) - local context_pool = {} - local possible_name - local possible_names = {} - - for name in pairs(context) do - if type(name) == "string" then - for i = 1, #name do - possible_name = name:sub(1, i - 1) .. name:sub(i + 1) - - if not context_pool[possible_name] then - context_pool[possible_name] = {} - end - - table.insert(context_pool[possible_name], name) - end - end - end - - for i = 1, #wrong_name + 1 do - possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1) - - if context[possible_name] then - possible_names[possible_name] = true - elseif context_pool[possible_name] then - for _, name in ipairs(context_pool[possible_name]) do - possible_names[name] = true - end - end - end - - local first = next(possible_names) - - if first then - if next(possible_names, first) then - local possible_names_arr = {} - - for name in pairs(possible_names) do - table.insert(possible_names_arr, "'" .. name .. "'") - end - - table.sort(possible_names_arr) - return "\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?" - else - return "\nDid you mean '" .. first .. "'?" - end - else - return "" - end -end - -local ElementState = class({ - invocations = 0 -}) - -function ElementState:__call(state, element) - self.state = state - self.result = state.result - self.element = element - self.target = element._target or element:_get_default_target() - self.action, self.result[self.target] = element:_get_action() - return self -end - -function ElementState:error(fmt, ...) - self.state:error(fmt, ...) -end - -function ElementState:convert(argument, index) - local converter = self.element._convert - - if converter then - local ok, err - - if type(converter) == "function" then - ok, err = converter(argument) - elseif type(converter[index]) == "function" then - ok, err = converter[index](argument) - else - ok = converter[argument] - end - - if ok == nil then - self:error(err and "%s" or "malformed argument '%s'", err or argument) - end - - argument = ok - end - - return argument -end - -function ElementState:default(mode) - return self.element._defmode:find(mode) and self.element._default -end - -local function bound(noun, min, max, is_max) - local res = "" - - if min ~= max then - res = "at " .. (is_max and "most" or "least") .. " " - end - - local number = is_max and max or min - return res .. tostring(number) .. " " .. noun .. (number == 1 and "" or "s") -end - -function ElementState:set_name(alias) - self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name) -end - -function ElementState:invoke() - self.open = true - self.overwrite = false - - if self.invocations >= self.element._maxcount then - if self.element._overwrite then - self.overwrite = true - else - local num_times_repr = bound("time", self.element._mincount, self.element._maxcount, true) - self:error("%s must be used %s", self.name, num_times_repr) - end - else - self.invocations = self.invocations + 1 - end - - self.args = {} - - if self.element._maxargs <= 0 then - self:close() - end - - return self.open -end - -function ElementState:pass(argument) - argument = self:convert(argument, #self.args + 1) - table.insert(self.args, argument) - - if #self.args >= self.element._maxargs then - self:close() - end - - return self.open -end - -function ElementState:complete_invocation() - while #self.args < self.element._minargs do - self:pass(self.element._default) - end -end - -function ElementState:close() - if self.open then - self.open = false - - if #self.args < self.element._minargs then - if self:default("a") then - self:complete_invocation() - else - if #self.args == 0 then - if getmetatable(self.element) == Argument then - self:error("missing %s", self.name) - elseif self.element._maxargs == 1 then - self:error("%s requires an argument", self.name) - end - end - - self:error("%s requires %s", self.name, bound("argument", self.element._minargs, self.element._maxargs)) - end - end - - local args - - if self.element._maxargs == 0 then - args = self.args[1] - elseif self.element._maxargs == 1 then - if self.element._minargs == 0 and self.element._mincount ~= self.element._maxcount then - args = self.args - else - args = self.args[1] - end - else - args = self.args - end - - self.action(self.result, self.target, args, self.overwrite) - end -end - -local ParseState = class({ - result = {}, - options = {}, - arguments = {}, - argument_i = 1, - element_to_mutexes = {}, - mutex_to_element_state = {}, - command_actions = {} -}) - -function ParseState:__call(parser, error_handler) - self.parser = parser - self.error_handler = error_handler - self.charset = parser:_update_charset() - self:switch(parser) - return self -end - -function ParseState:error(fmt, ...) - self.error_handler(self.parser, fmt:format(...)) -end - -function ParseState:switch(parser) - self.parser = parser - - if parser._action then - table.insert(self.command_actions, {action = parser._action, name = parser._name}) - end - - for _, option in ipairs(parser._options) do - option = ElementState(self, option) - table.insert(self.options, option) - - for _, alias in ipairs(option.element._aliases) do - self.options[alias] = option - end - end - - for _, mutex in ipairs(parser._mutexes) do - for _, element in ipairs(mutex) do - if not self.element_to_mutexes[element] then - self.element_to_mutexes[element] = {} - end - - table.insert(self.element_to_mutexes[element], mutex) - end - end - - for _, argument in ipairs(parser._arguments) do - argument = ElementState(self, argument) - table.insert(self.arguments, argument) - argument:set_name() - argument:invoke() - end - - self.handle_options = parser._handle_options - self.argument = self.arguments[self.argument_i] - self.commands = parser._commands - - for _, command in ipairs(self.commands) do - for _, alias in ipairs(command._aliases) do - self.commands[alias] = command - end - end -end - -function ParseState:get_option(name) - local option = self.options[name] - - if not option then - self:error("unknown option '%s'%s", name, get_tip(self.options, name)) - else - return option - end -end - -function ParseState:get_command(name) - local command = self.commands[name] - - if not command then - if #self.commands > 0 then - self:error("unknown command '%s'%s", name, get_tip(self.commands, name)) - else - self:error("too many arguments") - end - else - return command - end -end - -function ParseState:check_mutexes(element_state) - if self.element_to_mutexes[element_state.element] then - for _, mutex in ipairs(self.element_to_mutexes[element_state.element]) do - local used_element_state = self.mutex_to_element_state[mutex] - - if used_element_state and used_element_state ~= element_state then - self:error("%s can not be used together with %s", element_state.name, used_element_state.name) - else - self.mutex_to_element_state[mutex] = element_state - end - end - end -end - -function ParseState:invoke(option, name) - self:close() - option:set_name(name) - self:check_mutexes(option, name) - - if option:invoke() then - self.option = option - end -end - -function ParseState:pass(arg) - if self.option then - if not self.option:pass(arg) then - self.option = nil - end - elseif self.argument then - self:check_mutexes(self.argument) - - if not self.argument:pass(arg) then - self.argument_i = self.argument_i + 1 - self.argument = self.arguments[self.argument_i] - end - else - local command = self:get_command(arg) - self.result[command._target or command._name] = true - - if self.parser._command_target then - self.result[self.parser._command_target] = command._name - end - - self:switch(command) - end -end - -function ParseState:close() - if self.option then - self.option:close() - self.option = nil - end -end - -function ParseState:finalize() - self:close() - - for i = self.argument_i, #self.arguments do - local argument = self.arguments[i] - if #argument.args == 0 and argument:default("u") then - argument:complete_invocation() - else - argument:close() - end - end - - if self.parser._require_command and #self.commands > 0 then - self:error("a command is required") - end - - for _, option in ipairs(self.options) do - option.name = option.name or ("option '%s'"):format(option.element._name) - - if option.invocations == 0 then - if option:default("u") then - option:invoke() - option:complete_invocation() - option:close() - end - end - - local mincount = option.element._mincount - - if option.invocations < mincount then - if option:default("a") then - while option.invocations < mincount do - option:invoke() - option:close() - end - elseif option.invocations == 0 then - self:error("missing %s", option.name) - else - self:error("%s must be used %s", option.name, bound("time", mincount, option.element._maxcount)) - end - end - end - - for i = #self.command_actions, 1, -1 do - self.command_actions[i].action(self.result, self.command_actions[i].name) - end -end - -function ParseState:parse(args) - for _, arg in ipairs(args) do - local plain = true - - if self.handle_options then - local first = arg:sub(1, 1) - - if self.charset[first] then - if #arg > 1 then - plain = false - - if arg:sub(2, 2) == first then - if #arg == 2 then - if self.options[arg] then - local option = self:get_option(arg) - self:invoke(option, arg) - else - self:close() - end - - self.handle_options = false - else - local equals = arg:find "=" - if equals then - local name = arg:sub(1, equals - 1) - local option = self:get_option(name) - - if option.element._maxargs <= 0 then - self:error("option '%s' does not take arguments", name) - end - - self:invoke(option, name) - self:pass(arg:sub(equals + 1)) - else - local option = self:get_option(arg) - self:invoke(option, arg) - end - end - else - for i = 2, #arg do - local name = first .. arg:sub(i, i) - local option = self:get_option(name) - self:invoke(option, name) - - if i ~= #arg and option.element._maxargs > 0 then - self:pass(arg:sub(i + 1)) - break - end - end - end - end - end - end - - if plain then - self:pass(arg) - end - end - - self:finalize() - return self.result -end - -function Parser:error(msg) - io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg)) - os.exit(1) -end - --- Compatibility with strict.lua and other checkers: -local default_cmdline = rawget(_G, "arg") or {} - -function Parser:_parse(args, error_handler) - return ParseState(self, error_handler):parse(args or default_cmdline) -end - -function Parser:parse(args) - return self:_parse(args, self.error) -end - -local function xpcall_error_handler(err) - return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2) -end - -function Parser:pparse(args) - local parse_error - - local ok, result = xpcall(function() - return self:_parse(args, function(_, err) - parse_error = err - error(err, 0) - end) - end, xpcall_error_handler) - - if ok then - return true, result - elseif not parse_error then - error(result, 0) - else - return false, parse_error - end -end - -local argparse = {} - -argparse.version = "0.6.0" - -setmetatable(argparse, {__call = function(_, ...) - return Parser(default_cmdline[0]):add_help(true)(...) -end}) - -return argparse - -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() diff --git a/build/luacomp-generic.lua b/build/luacomp-generic.lua deleted file mode 100644 index 8a9d4b6..0000000 --- a/build/luacomp-generic.lua +++ /dev/null @@ -1,514 +0,0 @@ -#!/usr/bin/env lua ---[[ - init.lua - Main file of LuaComp - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - - -local function _sv(k, v) - _G[k] = v - --os.setenv(k, tostring(v)) -end - -_sv("LUACOMP_V_MAJ", 1) -_sv("LUACOMP_V_MIN", 1) -_sv("LUACOMP_V_PAT", 0) -_sv("LUACOMP_VERSION", LUACOMP_V_MAJ.."."..LUACOMP_V_MIN.."."..LUACOMP_V_PAT) -_sv("LUACOMP_NAME", "LuaComp") - ---[[ - ast.lua - Generates a structure for use in preprocessing. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function nextc(f, c) - c = c or 1 - return f:read(c) -end - -local function peek(f, c) - c = c or 1 - local z = f:read(c) - f:seek("cur", -c) - return z -end - -local function skip(f, c) - c = c or 1 - return f:seek("cur", c) -end - -local ws = { - ["\t"] = true, - [" "] = true -} - -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 - 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 - hex = hex .. lc - end - end - return tonumber(hex, 16) -end - -local function parse_number(f, c) - local lc = " " - local num = c - while 48 <= lc:byte() and lc:byte() <= 57 do - lc = nextc(f) - if (48 <= lc:byte() and lc:byte() <= 57) then - num = num .. lc - end - end - return tonumber(hex, 10) -end - -local esct = { - ["t"] = "\t", - ["n"] = "\n", - ["r"] = "\r", - ["\\"] = "\\\\" -} - -for i=0, 9 do - esct[tostring(i)] = string.char(i) -end - -local function parse_dblquote(f) - local val = "" - while peek(f) ~= "\"" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_snglquote(f) - local val = "" - while peek(f) ~= "\'" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_envarg(f) - local val = "" - while peek(f) ~= ")" do - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - val = val .. nextc(f) - end - skip(f) - return val -end - -local function parse_directive(f) - local lc = "_" - local name = "" - local args = {} - local carg = "" - while not ws[lc] do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name} - elseif not ws[lc] then - name = name .. lc - end - end - while true do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name, args=args} - elseif lc == "0" and peek(f) == "x" then - skip(f) - local val = parse_hex(f) - args[#args+1] = val - elseif 48 <= lc:byte() and lc:byte() <= 57 then - local val = parse_number(f, lc) - args[#args+1] = val - elseif lc == "\"" then - local val, e = parse_dblquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "\'" then - local val, e = parse_snglquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "$" and peek(f) == "(" then - skip(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 not ws[lc] then - return nil, "Syntax error" - end - end -end - -local function mkast(f, n) - io.stderr:write("PROC\t",n,"\n") - local lc = " " - local lpos = 1 - local ilpos = 1 - local tree = {} - local code = "" - local branches = {} - local function add_code() - tree[#tree+1] = {type="code", data=code, file=n, line=lpos} - code = "" - end - local function parse_error(e) - io.stderr:write("ERROR:"..n..":"..lpos..": "..e.."\n") - os.exit(1) - end - while lc and lc ~= "" do - lc = nextc(f) - if (lc == "-" and ilpos == 1) then - if (peek(f, 2) == "-#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "/" and ilpos == 1) then - if (peek(f, 2) == "/#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "$" and peek(f) == "(") then - add_code() - skip(f) - local val, e = parse_envarg(f) - if not val then - parse_error(e) - end - tree[#tree+1] = {type="envvar", var=val, file=n, line=lpos} - 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="lua", 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="lua_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) - end - lpos = lpos+1 - ilpos = 1 - code = code .. "\n" - else - code = code .. (lc or "") - ilpos = ilpos+1 - end - end - add_code() - return tree -end - ---[[ - generator.lua - Generates the code. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function lua_escape(code) - return code:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n") -end - -local directives = {} - -local function generate(ast) - local lua_code = "" - for i=1, #ast do - local leaf = ast[i] - if (leaf.type == "lua") then - lua_code = lua_code .. leaf.code - elseif (leaf.type == "directive") then - local stargs = {} - for i=1, #leaf.args do - local arg = leaf.args[i] - if (type(arg) == "string") then - stargs[i] = "\""..lua_escape(arg).."\"" - elseif (type(arg) == "number") then - stargs[i] = tostring(arg) - end - end - lua_code = lua_code .. "call_directive(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.name.."\","..table.concat(stargs, ",")..")" - elseif (leaf.type == "envvar") then - lua_code = lua_code .. "put_env(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.var.."\")" - elseif (leaf.type == "code") then - 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.."))" - else - io.stderr:write("ERROR: Internal catastrophic failure, unknown type "..leaf.type.."\n") - os.exit(1) - end - lua_code = lua_code .. "\n" - end - local env = {code = ""} - local function run_away_screaming(fpos, err) - io.stdout:write("ERROR: "..fpos..": "..err.."\n") - os.exit(1) - end - local function call_directive(fpos, dname, ...) - if (not directives[dname]) then - run_away_screaming(fpos, "Invalid directive name `"..dname.."'") - end - local r, er = directives[dname](env, ...) - if (not r) then - run_away_screaming(fpos, er) - end - end - local function put_env(fpos, evar) - local e = os.getenv(evar) - if not e then - run_away_screaming(fpos, "Enviroment variable `"..evar.."' does not exist!") - end - env.code = env.code .. "\""..lua_escape(e).."\"" - end - local function put_code(fpos, code) - env.code = env.code .. code --not much that can fail here... - end - local fenv = {} - for k, v in pairs(_G) do - fenv[k] = v - end - fenv._G = fenv - fenv._ENV = fenv - fenv.call_directive = call_directive - fenv.put_code = put_code - fenv.put_env = put_env - local func = assert(load(lua_code, "=(generated code)", "t", fenv)) - func() - return env.code -end - ---[[ - directive_provider.lua - Provides preprocessor directives - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - ----#include "directives/define.lua" - -function directives.include(env, file) - if (not os.execute("stat "..file..">/dev/null")) then - return false, "File `"..file.."' does not exist!" - end - local f = io.open(file, "r") - local fast = mkast(f, file) - local code = generate(fast) - env.code = env.code .. "\n" .. code .. "\n" - return true -end - -local warned = false -function directives.loadmod(env, mod) - if not warned then - io.stderr:write("Warning: loadmod is depreciated and unsafe. The API differs from luapreproc.\n") - warned = true - end - if (not os.execute("stat "..file..">/dev/null")) then - return false, "Module `"..file.."' does not exist!" - end - local modname, func = dofile(mod) - directives[modname] = func - return true -end - - ---[[ - cfg/minifier_providers.lua - Provides minifier providers. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local providers = {} - -function providers.luamin(cin) - local fn = os.tmpname() - local fh = io.open(fn, "w") - fh:write(cin) - fh:close() - local lmh = io.popen("luamin -f "..fn.." 2>&1", "r") - local dat = lmh:read("*a") - local stat, _, code = lmh:close() - if (code ~= 0) then - return false, dat - end - return dat -end - -function providers.none(cin) - return cin -end - -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 = 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() diff --git a/build/luacomp-jit-static.lua b/build/luacomp-jit-static.lua deleted file mode 100644 index 77aab44..0000000 --- a/build/luacomp-jit-static.lua +++ /dev/null @@ -1,2044 +0,0 @@ -#!/usr/bin/env luajit ---[[ - staticinit.lua - Main file of LuaComp, directly includes argparse. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - - -local function _sv(k, v) - _G[k] = v - --os.setenv(k, tostring(v)) -end - -_sv("LUACOMP_V_MAJ", 1) -_sv("LUACOMP_V_MIN", 1) -_sv("LUACOMP_V_PAT", 0) -_sv("LUACOMP_VERSION", LUACOMP_V_MAJ.."."..LUACOMP_V_MIN.."."..LUACOMP_V_PAT) -_sv("LUACOMP_NAME", "LuaComp") - ---[[ - ast.lua - Generates a structure for use in preprocessing. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function nextc(f, c) - c = c or 1 - return f:read(c) -end - -local function peek(f, c) - c = c or 1 - local z = f:read(c) - f:seek("cur", -c) - return z -end - -local function skip(f, c) - c = c or 1 - return f:seek("cur", c) -end - -local ws = { - ["\t"] = true, - [" "] = true -} - -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 - 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 - hex = hex .. lc - end - end - return tonumber(hex, 16) -end - -local function parse_number(f, c) - local lc = " " - local num = c - while 48 <= lc:byte() and lc:byte() <= 57 do - lc = nextc(f) - if (48 <= lc:byte() and lc:byte() <= 57) then - num = num .. lc - end - end - return tonumber(hex, 10) -end - -local esct = { - ["t"] = "\t", - ["n"] = "\n", - ["r"] = "\r", - ["\\"] = "\\\\" -} - -for i=0, 9 do - esct[tostring(i)] = string.char(i) -end - -local function parse_dblquote(f) - local val = "" - while peek(f) ~= "\"" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_snglquote(f) - local val = "" - while peek(f) ~= "\'" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_envarg(f) - local val = "" - while peek(f) ~= ")" do - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - val = val .. nextc(f) - end - skip(f) - return val -end - -local function parse_directive(f) - local lc = "_" - local name = "" - local args = {} - local carg = "" - while not ws[lc] do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name} - elseif not ws[lc] then - name = name .. lc - end - end - while true do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name, args=args} - elseif lc == "0" and peek(f) == "x" then - skip(f) - local val = parse_hex(f) - args[#args+1] = val - elseif 48 <= lc:byte() and lc:byte() <= 57 then - local val = parse_number(f, lc) - args[#args+1] = val - elseif lc == "\"" then - local val, e = parse_dblquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "\'" then - local val, e = parse_snglquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "$" and peek(f) == "(" then - skip(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 not ws[lc] then - return nil, "Syntax error" - end - end -end - -local function mkast(f, n) - io.stderr:write("PROC\t",n,"\n") - local lc = " " - local lpos = 1 - local ilpos = 1 - local tree = {} - local code = "" - local branches = {} - local function add_code() - tree[#tree+1] = {type="code", data=code, file=n, line=lpos} - code = "" - end - local function parse_error(e) - io.stderr:write("ERROR:"..n..":"..lpos..": "..e.."\n") - os.exit(1) - end - while lc and lc ~= "" do - lc = nextc(f) - if (lc == "-" and ilpos == 1) then - if (peek(f, 2) == "-#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "/" and ilpos == 1) then - if (peek(f, 2) == "/#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "$" and peek(f) == "(") then - add_code() - skip(f) - local val, e = parse_envarg(f) - if not val then - parse_error(e) - end - tree[#tree+1] = {type="envvar", var=val, file=n, line=lpos} - 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="lua", 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="lua_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) - end - lpos = lpos+1 - ilpos = 1 - code = code .. "\n" - else - code = code .. (lc or "") - ilpos = ilpos+1 - end - end - add_code() - return tree -end - ---[[ - generator.lua - Generates the code. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function lua_escape(code) - return code:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n") -end - -local directives = {} - -local function generate(ast) - local lua_code = "" - for i=1, #ast do - local leaf = ast[i] - if (leaf.type == "lua") then - lua_code = lua_code .. leaf.code - elseif (leaf.type == "directive") then - local stargs = {} - for i=1, #leaf.args do - local arg = leaf.args[i] - if (type(arg) == "string") then - stargs[i] = "\""..lua_escape(arg).."\"" - elseif (type(arg) == "number") then - stargs[i] = tostring(arg) - end - end - lua_code = lua_code .. "call_directive(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.name.."\","..table.concat(stargs, ",")..")" - elseif (leaf.type == "envvar") then - lua_code = lua_code .. "put_env(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.var.."\")" - elseif (leaf.type == "code") then - 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.."))" - else - io.stderr:write("ERROR: Internal catastrophic failure, unknown type "..leaf.type.."\n") - os.exit(1) - end - lua_code = lua_code .. "\n" - end - local env = {code = ""} - local function run_away_screaming(fpos, err) - io.stdout:write("ERROR: "..fpos..": "..err.."\n") - os.exit(1) - end - local function call_directive(fpos, dname, ...) - if (not directives[dname]) then - run_away_screaming(fpos, "Invalid directive name `"..dname.."'") - end - local r, er = directives[dname](env, ...) - if (not r) then - run_away_screaming(fpos, er) - end - end - local function put_env(fpos, evar) - local e = os.getenv(evar) - if not e then - run_away_screaming(fpos, "Enviroment variable `"..evar.."' does not exist!") - end - env.code = env.code .. "\""..lua_escape(e).."\"" - end - local function put_code(fpos, code) - env.code = env.code .. code --not much that can fail here... - end - local fenv = {} - for k, v in pairs(_G) do - fenv[k] = v - end - fenv._G = fenv - fenv._ENV = fenv - fenv.call_directive = call_directive - fenv.put_code = put_code - fenv.put_env = put_env - local func = assert(load(lua_code, "=(generated code)", "t", fenv)) - func() - return env.code -end - ---[[ - directive_provider.lua - Provides preprocessor directives - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - ----#include "directives/define.lua" - -function directives.include(env, file) - if (not os.execute("stat "..file..">/dev/null")) then - return false, "File `"..file.."' does not exist!" - end - local f = io.open(file, "r") - local fast = mkast(f, file) - local code = generate(fast) - env.code = env.code .. "\n" .. code .. "\n" - return true -end - -local warned = false -function directives.loadmod(env, mod) - if not warned then - io.stderr:write("Warning: loadmod is depreciated and unsafe. The API differs from luapreproc.\n") - warned = true - end - if (not os.execute("stat "..file..">/dev/null")) then - return false, "Module `"..file.."' does not exist!" - end - local modname, func = dofile(mod) - directives[modname] = func - return true -end - - ---[[ - cfg/minifier_providers.lua - Provides minifier providers. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local providers = {} - -function providers.luamin(cin) - local fn = os.tmpname() - local fh = io.open(fn, "w") - fh:write(cin) - fh:close() - local lmh = io.popen("luamin -f "..fn.." 2>&1", "r") - local dat = lmh:read("*a") - local stat, _, code = lmh:close() - if (code ~= 0) then - return false, dat - end - return dat -end - -function providers.none(cin) - return cin -end - -local argparse = (function() - --- The MIT License (MIT) - --- Copyright (c) 2013 - 2018 Peter Melnichenko - --- Permission is hereby granted, free of charge, to any person obtaining a copy of --- this software and associated documentation files (the "Software"), to deal in --- the Software without restriction, including without limitation the rights to --- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of --- the Software, and to permit persons to whom the Software is furnished to do so, --- subject to the following conditions: - --- The above copyright notice and this permission notice shall be included in all --- copies or substantial portions of the Software. - --- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS --- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR --- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER --- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN --- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -local function deep_update(t1, t2) - for k, v in pairs(t2) do - if type(v) == "table" then - v = deep_update({}, v) - end - - t1[k] = v - end - - return t1 -end - --- A property is a tuple {name, callback}. --- properties.args is number of properties that can be set as arguments --- when calling an object. -local function class(prototype, properties, parent) - -- Class is the metatable of its instances. - local cl = {} - cl.__index = cl - - if parent then - cl.__prototype = deep_update(deep_update({}, parent.__prototype), prototype) - else - cl.__prototype = prototype - end - - if properties then - local names = {} - - -- Create setter methods and fill set of property names. - for _, property in ipairs(properties) do - local name, callback = property[1], property[2] - - cl[name] = function(self, value) - if not callback(self, value) then - self["_" .. name] = value - end - - return self - end - - names[name] = true - end - - function cl.__call(self, ...) - -- When calling an object, if the first argument is a table, - -- interpret keys as property names, else delegate arguments - -- to corresponding setters in order. - if type((...)) == "table" then - for name, value in pairs((...)) do - if names[name] then - self[name](self, value) - end - end - else - local nargs = select("#", ...) - - for i, property in ipairs(properties) do - if i > nargs or i > properties.args then - break - end - - local arg = select(i, ...) - - if arg ~= nil then - self[property[1]](self, arg) - end - end - end - - return self - end - end - - -- If indexing class fails, fallback to its parent. - local class_metatable = {} - class_metatable.__index = parent - - function class_metatable.__call(self, ...) - -- Calling a class returns its instance. - -- Arguments are delegated to the instance. - local object = deep_update({}, self.__prototype) - setmetatable(object, self) - return object(...) - end - - return setmetatable(cl, class_metatable) -end - -local function typecheck(name, types, value) - for _, type_ in ipairs(types) do - if type(value) == type_ then - return true - end - end - - error(("bad property '%s' (%s expected, got %s)"):format(name, table.concat(types, " or "), type(value))) -end - -local function typechecked(name, ...) - local types = {...} - return {name, function(_, value) typecheck(name, types, value) end} -end - -local multiname = {"name", function(self, value) - typecheck("name", {"string"}, value) - - for alias in value:gmatch("%S+") do - self._name = self._name or alias - table.insert(self._aliases, alias) - end - - -- Do not set _name as with other properties. - return true -end} - -local function parse_boundaries(str) - if tonumber(str) then - return tonumber(str), tonumber(str) - end - - if str == "*" then - return 0, math.huge - end - - if str == "+" then - return 1, math.huge - end - - if str == "?" then - return 0, 1 - end - - if str:match "^%d+%-%d+$" then - local min, max = str:match "^(%d+)%-(%d+)$" - return tonumber(min), tonumber(max) - end - - if str:match "^%d+%+$" then - local min = str:match "^(%d+)%+$" - return tonumber(min), math.huge - end -end - -local function boundaries(name) - return {name, function(self, value) - typecheck(name, {"number", "string"}, value) - - local min, max = parse_boundaries(value) - - if not min then - error(("bad property '%s'"):format(name)) - end - - self["_min" .. name], self["_max" .. name] = min, max - end} -end - -local actions = {} - -local option_action = {"action", function(_, value) - typecheck("action", {"function", "string"}, value) - - if type(value) == "string" and not actions[value] then - error(("unknown action '%s'"):format(value)) - end -end} - -local option_init = {"init", function(self) - self._has_init = true -end} - -local option_default = {"default", function(self, value) - if type(value) ~= "string" then - self._init = value - self._has_init = true - return true - end -end} - -local add_help = {"add_help", function(self, value) - typecheck("add_help", {"boolean", "string", "table"}, value) - - if self._has_help then - table.remove(self._options) - self._has_help = false - end - - if value then - local help = self:flag() - :description "Show this help message and exit." - :action(function() - print(self:get_help()) - os.exit(0) - end) - - if value ~= true then - help = help(value) - end - - if not help._name then - help "-h" "--help" - end - - self._has_help = true - end -end} - -local Parser = class({ - _arguments = {}, - _options = {}, - _commands = {}, - _mutexes = {}, - _groups = {}, - _require_command = true, - _handle_options = true -}, { - args = 3, - typechecked("name", "string"), - typechecked("description", "string"), - typechecked("epilog", "string"), - typechecked("usage", "string"), - typechecked("help", "string"), - typechecked("require_command", "boolean"), - typechecked("handle_options", "boolean"), - typechecked("action", "function"), - typechecked("command_target", "string"), - typechecked("help_vertical_space", "number"), - typechecked("usage_margin", "number"), - typechecked("usage_max_width", "number"), - typechecked("help_usage_margin", "number"), - typechecked("help_description_margin", "number"), - typechecked("help_max_width", "number"), - add_help -}) - -local Command = class({ - _aliases = {} -}, { - args = 3, - multiname, - typechecked("description", "string"), - typechecked("epilog", "string"), - typechecked("target", "string"), - typechecked("usage", "string"), - typechecked("help", "string"), - typechecked("require_command", "boolean"), - typechecked("handle_options", "boolean"), - typechecked("action", "function"), - typechecked("command_target", "string"), - typechecked("help_vertical_space", "number"), - typechecked("usage_margin", "number"), - typechecked("usage_max_width", "number"), - typechecked("help_usage_margin", "number"), - typechecked("help_description_margin", "number"), - typechecked("help_max_width", "number"), - typechecked("hidden", "boolean"), - add_help -}, Parser) - -local Argument = class({ - _minargs = 1, - _maxargs = 1, - _mincount = 1, - _maxcount = 1, - _defmode = "unused", - _show_default = true -}, { - args = 5, - typechecked("name", "string"), - typechecked("description", "string"), - option_default, - typechecked("convert", "function", "table"), - boundaries("args"), - typechecked("target", "string"), - typechecked("defmode", "string"), - typechecked("show_default", "boolean"), - typechecked("argname", "string", "table"), - typechecked("hidden", "boolean"), - option_action, - option_init -}) - -local Option = class({ - _aliases = {}, - _mincount = 0, - _overwrite = true -}, { - args = 6, - multiname, - typechecked("description", "string"), - option_default, - typechecked("convert", "function", "table"), - boundaries("args"), - boundaries("count"), - typechecked("target", "string"), - typechecked("defmode", "string"), - typechecked("show_default", "boolean"), - typechecked("overwrite", "boolean"), - typechecked("argname", "string", "table"), - typechecked("hidden", "boolean"), - option_action, - option_init -}, Argument) - -function Parser:_inherit_property(name, default) - local element = self - - while true do - local value = element["_" .. name] - - if value ~= nil then - return value - end - - if not element._parent then - return default - end - - element = element._parent - end -end - -function Argument:_get_argument_list() - local buf = {} - local i = 1 - - while i <= math.min(self._minargs, 3) do - local argname = self:_get_argname(i) - - if self._default and self._defmode:find "a" then - argname = "[" .. argname .. "]" - end - - table.insert(buf, argname) - i = i+1 - end - - while i <= math.min(self._maxargs, 3) do - table.insert(buf, "[" .. self:_get_argname(i) .. "]") - i = i+1 - - if self._maxargs == math.huge then - break - end - end - - if i < self._maxargs then - table.insert(buf, "...") - end - - return buf -end - -function Argument:_get_usage() - local usage = table.concat(self:_get_argument_list(), " ") - - if self._default and self._defmode:find "u" then - if self._maxargs > 1 or (self._minargs == 1 and not self._defmode:find "a") then - usage = "[" .. usage .. "]" - end - end - - return usage -end - -function actions.store_true(result, target) - result[target] = true -end - -function actions.store_false(result, target) - result[target] = false -end - -function actions.store(result, target, argument) - result[target] = argument -end - -function actions.count(result, target, _, overwrite) - if not overwrite then - result[target] = result[target] + 1 - end -end - -function actions.append(result, target, argument, overwrite) - result[target] = result[target] or {} - table.insert(result[target], argument) - - if overwrite then - table.remove(result[target], 1) - end -end - -function actions.concat(result, target, arguments, overwrite) - if overwrite then - error("'concat' action can't handle too many invocations") - end - - result[target] = result[target] or {} - - for _, argument in ipairs(arguments) do - table.insert(result[target], argument) - end -end - -function Argument:_get_action() - local action, init - - if self._maxcount == 1 then - if self._maxargs == 0 then - action, init = "store_true", nil - else - action, init = "store", nil - end - else - if self._maxargs == 0 then - action, init = "count", 0 - else - action, init = "append", {} - end - end - - if self._action then - action = self._action - end - - if self._has_init then - init = self._init - end - - if type(action) == "string" then - action = actions[action] - end - - return action, init -end - --- Returns placeholder for `narg`-th argument. -function Argument:_get_argname(narg) - local argname = self._argname or self:_get_default_argname() - - if type(argname) == "table" then - return argname[narg] - else - return argname - end -end - -function Argument:_get_default_argname() - return "<" .. self._name .. ">" -end - -function Option:_get_default_argname() - return "<" .. self:_get_default_target() .. ">" -end - --- Returns labels to be shown in the help message. -function Argument:_get_label_lines() - return {self._name} -end - -function Option:_get_label_lines() - local argument_list = self:_get_argument_list() - - if #argument_list == 0 then - -- Don't put aliases for simple flags like `-h` on different lines. - return {table.concat(self._aliases, ", ")} - end - - local longest_alias_length = -1 - - for _, alias in ipairs(self._aliases) do - longest_alias_length = math.max(longest_alias_length, #alias) - end - - local argument_list_repr = table.concat(argument_list, " ") - local lines = {} - - for i, alias in ipairs(self._aliases) do - local line = (" "):rep(longest_alias_length - #alias) .. alias .. " " .. argument_list_repr - - if i ~= #self._aliases then - line = line .. "," - end - - table.insert(lines, line) - end - - return lines -end - -function Command:_get_label_lines() - return {table.concat(self._aliases, ", ")} -end - -function Argument:_get_description() - if self._default and self._show_default then - if self._description then - return ("%s (default: %s)"):format(self._description, self._default) - else - return ("default: %s"):format(self._default) - end - else - return self._description or "" - end -end - -function Command:_get_description() - return self._description or "" -end - -function Option:_get_usage() - local usage = self:_get_argument_list() - table.insert(usage, 1, self._name) - usage = table.concat(usage, " ") - - if self._mincount == 0 or self._default then - usage = "[" .. usage .. "]" - end - - return usage -end - -function Argument:_get_default_target() - return self._name -end - -function Option:_get_default_target() - local res - - for _, alias in ipairs(self._aliases) do - if alias:sub(1, 1) == alias:sub(2, 2) then - res = alias:sub(3) - break - end - end - - res = res or self._name:sub(2) - return (res:gsub("-", "_")) -end - -function Option:_is_vararg() - return self._maxargs ~= self._minargs -end - -function Parser:_get_fullname() - local parent = self._parent - local buf = {self._name} - - while parent do - table.insert(buf, 1, parent._name) - parent = parent._parent - end - - return table.concat(buf, " ") -end - -function Parser:_update_charset(charset) - charset = charset or {} - - for _, command in ipairs(self._commands) do - command:_update_charset(charset) - end - - for _, option in ipairs(self._options) do - for _, alias in ipairs(option._aliases) do - charset[alias:sub(1, 1)] = true - end - end - - return charset -end - -function Parser:argument(...) - local argument = Argument(...) - table.insert(self._arguments, argument) - return argument -end - -function Parser:option(...) - local option = Option(...) - - if self._has_help then - table.insert(self._options, #self._options, option) - else - table.insert(self._options, option) - end - - return option -end - -function Parser:flag(...) - return self:option():args(0)(...) -end - -function Parser:command(...) - local command = Command():add_help(true)(...) - command._parent = self - table.insert(self._commands, command) - return command -end - -function Parser:mutex(...) - local elements = {...} - - for i, element in ipairs(elements) do - local mt = getmetatable(element) - assert(mt == Option or mt == Argument, ("bad argument #%d to 'mutex' (Option or Argument expected)"):format(i)) - end - - table.insert(self._mutexes, elements) - return self -end - -function Parser:group(name, ...) - assert(type(name) == "string", ("bad argument #1 to 'group' (string expected, got %s)"):format(type(name))) - - local group = {name = name, ...} - - for i, element in ipairs(group) do - local mt = getmetatable(element) - assert(mt == Option or mt == Argument or mt == Command, - ("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(i + 1)) - end - - table.insert(self._groups, group) - return self -end - -local usage_welcome = "Usage: " - -function Parser:get_usage() - if self._usage then - return self._usage - end - - local usage_margin = self:_inherit_property("usage_margin", #usage_welcome) - local max_usage_width = self:_inherit_property("usage_max_width", 70) - local lines = {usage_welcome .. self:_get_fullname()} - - local function add(s) - if #lines[#lines]+1+#s <= max_usage_width then - lines[#lines] = lines[#lines] .. " " .. s - else - lines[#lines+1] = (" "):rep(usage_margin) .. s - end - end - - -- Normally options are before positional arguments in usage messages. - -- However, vararg options should be after, because they can't be reliable used - -- before a positional argument. - -- Mutexes come into play, too, and are shown as soon as possible. - -- Overall, output usages in the following order: - -- 1. Mutexes that don't have positional arguments or vararg options. - -- 2. Options that are not in any mutexes and are not vararg. - -- 3. Positional arguments - on their own or as a part of a mutex. - -- 4. Remaining mutexes. - -- 5. Remaining options. - - local elements_in_mutexes = {} - local added_elements = {} - local added_mutexes = {} - local argument_to_mutexes = {} - - local function add_mutex(mutex, main_argument) - if added_mutexes[mutex] then - return - end - - added_mutexes[mutex] = true - local buf = {} - - for _, element in ipairs(mutex) do - if not element._hidden and not added_elements[element] then - if getmetatable(element) == Option or element == main_argument then - table.insert(buf, element:_get_usage()) - added_elements[element] = true - end - end - end - - if #buf == 1 then - add(buf[1]) - elseif #buf > 1 then - add("(" .. table.concat(buf, " | ") .. ")") - end - end - - local function add_element(element) - if not element._hidden and not added_elements[element] then - add(element:_get_usage()) - added_elements[element] = true - end - end - - for _, mutex in ipairs(self._mutexes) do - local is_vararg = false - local has_argument = false - - for _, element in ipairs(mutex) do - if getmetatable(element) == Option then - if element:_is_vararg() then - is_vararg = true - end - else - has_argument = true - argument_to_mutexes[element] = argument_to_mutexes[element] or {} - table.insert(argument_to_mutexes[element], mutex) - end - - elements_in_mutexes[element] = true - end - - if not is_vararg and not has_argument then - add_mutex(mutex) - end - end - - for _, option in ipairs(self._options) do - if not elements_in_mutexes[option] and not option:_is_vararg() then - add_element(option) - end - end - - -- Add usages for positional arguments, together with one mutex containing them, if they are in a mutex. - for _, argument in ipairs(self._arguments) do - -- Pick a mutex as a part of which to show this argument, take the first one that's still available. - local mutex - - if elements_in_mutexes[argument] then - for _, argument_mutex in ipairs(argument_to_mutexes[argument]) do - if not added_mutexes[argument_mutex] then - mutex = argument_mutex - end - end - end - - if mutex then - add_mutex(mutex, argument) - else - add_element(argument) - end - end - - for _, mutex in ipairs(self._mutexes) do - add_mutex(mutex) - end - - for _, option in ipairs(self._options) do - add_element(option) - end - - if #self._commands > 0 then - if self._require_command then - add("") - else - add("[]") - end - - add("...") - end - - return table.concat(lines, "\n") -end - -local function split_lines(s) - if s == "" then - return {} - end - - local lines = {} - - if s:sub(-1) ~= "\n" then - s = s .. "\n" - end - - for line in s:gmatch("([^\n]*)\n") do - table.insert(lines, line) - end - - return lines -end - -local function autowrap_line(line, max_length) - -- Algorithm for splitting lines is simple and greedy. - local result_lines = {} - - -- Preserve original indentation of the line, put this at the beginning of each result line. - -- If the first word looks like a list marker ('*', '+', or '-'), add spaces so that starts - -- of the second and the following lines vertically align with the start of the second word. - local indentation = line:match("^ *") - - if line:find("^ *[%*%+%-]") then - indentation = indentation .. " " .. line:match("^ *[%*%+%-]( *)") - end - - -- Parts of the last line being assembled. - local line_parts = {} - - -- Length of the current line. - local line_length = 0 - - -- Index of the next character to consider. - local index = 1 - - while true do - local word_start, word_finish, word = line:find("([^ ]+)", index) - - if not word_start then - -- Ignore trailing spaces, if any. - break - end - - local preceding_spaces = line:sub(index, word_start - 1) - index = word_finish + 1 - - if (#line_parts == 0) or (line_length + #preceding_spaces + #word <= max_length) then - -- Either this is the very first word or it fits as an addition to the current line, add it. - table.insert(line_parts, preceding_spaces) -- For the very first word this adds the indentation. - table.insert(line_parts, word) - line_length = line_length + #preceding_spaces + #word - else - -- Does not fit, finish current line and put the word into a new one. - table.insert(result_lines, table.concat(line_parts)) - line_parts = {indentation, word} - line_length = #indentation + #word - end - end - - if #line_parts > 0 then - table.insert(result_lines, table.concat(line_parts)) - end - - if #result_lines == 0 then - -- Preserve empty lines. - result_lines[1] = "" - end - - return result_lines -end - --- Automatically wraps lines within given array, --- attempting to limit line length to `max_length`. --- Existing line splits are preserved. -local function autowrap(lines, max_length) - local result_lines = {} - - for _, line in ipairs(lines) do - local autowrapped_lines = autowrap_line(line, max_length) - - for _, autowrapped_line in ipairs(autowrapped_lines) do - table.insert(result_lines, autowrapped_line) - end - end - - return result_lines -end - -function Parser:_get_element_help(element) - local label_lines = element:_get_label_lines() - local description_lines = split_lines(element:_get_description()) - - local result_lines = {} - - -- All label lines should have the same length (except the last one, it has no comma). - -- If too long, start description after all the label lines. - -- Otherwise, combine label and description lines. - - local usage_margin_len = self:_inherit_property("help_usage_margin", 3) - local usage_margin = (" "):rep(usage_margin_len) - local description_margin_len = self:_inherit_property("help_description_margin", 25) - local description_margin = (" "):rep(description_margin_len) - - local help_max_width = self:_inherit_property("help_max_width") - - if help_max_width then - local description_max_width = math.max(help_max_width - description_margin_len, 10) - description_lines = autowrap(description_lines, description_max_width) - end - - if #label_lines[1] >= (description_margin_len - usage_margin_len) then - for _, label_line in ipairs(label_lines) do - table.insert(result_lines, usage_margin .. label_line) - end - - for _, description_line in ipairs(description_lines) do - table.insert(result_lines, description_margin .. description_line) - end - else - for i = 1, math.max(#label_lines, #description_lines) do - local label_line = label_lines[i] - local description_line = description_lines[i] - - local line = "" - - if label_line then - line = usage_margin .. label_line - end - - if description_line and description_line ~= "" then - line = line .. (" "):rep(description_margin_len - #line) .. description_line - end - - table.insert(result_lines, line) - end - end - - return table.concat(result_lines, "\n") -end - -local function get_group_types(group) - local types = {} - - for _, element in ipairs(group) do - types[getmetatable(element)] = true - end - - return types -end - -function Parser:_add_group_help(blocks, added_elements, label, elements) - local buf = {label} - - for _, element in ipairs(elements) do - if not element._hidden and not added_elements[element] then - added_elements[element] = true - table.insert(buf, self:_get_element_help(element)) - end - end - - if #buf > 1 then - table.insert(blocks, table.concat(buf, ("\n"):rep(self:_inherit_property("help_vertical_space", 0) + 1))) - end -end - -function Parser:get_help() - if self._help then - return self._help - end - - local blocks = {self:get_usage()} - - local help_max_width = self:_inherit_property("help_max_width") - - if self._description then - local description = self._description - - if help_max_width then - description = table.concat(autowrap(split_lines(description), help_max_width), "\n") - end - - table.insert(blocks, description) - end - - -- 1. Put groups containing arguments first, then other arguments. - -- 2. Put remaining groups containing options, then other options. - -- 3. Put remaining groups containing commands, then other commands. - -- Assume that an element can't be in several groups. - local groups_by_type = { - [Argument] = {}, - [Option] = {}, - [Command] = {} - } - - for _, group in ipairs(self._groups) do - local group_types = get_group_types(group) - - for _, mt in ipairs({Argument, Option, Command}) do - if group_types[mt] then - table.insert(groups_by_type[mt], group) - break - end - end - end - - local default_groups = { - {name = "Arguments", type = Argument, elements = self._arguments}, - {name = "Options", type = Option, elements = self._options}, - {name = "Commands", type = Command, elements = self._commands} - } - - local added_elements = {} - - for _, default_group in ipairs(default_groups) do - local type_groups = groups_by_type[default_group.type] - - for _, group in ipairs(type_groups) do - self:_add_group_help(blocks, added_elements, group.name .. ":", group) - end - - local default_label = default_group.name .. ":" - - if #type_groups > 0 then - default_label = "Other " .. default_label:gsub("^.", string.lower) - end - - self:_add_group_help(blocks, added_elements, default_label, default_group.elements) - end - - if self._epilog then - local epilog = self._epilog - - if help_max_width then - epilog = table.concat(autowrap(split_lines(epilog), help_max_width), "\n") - end - - table.insert(blocks, epilog) - end - - return table.concat(blocks, "\n\n") -end - -local function get_tip(context, wrong_name) - local context_pool = {} - local possible_name - local possible_names = {} - - for name in pairs(context) do - if type(name) == "string" then - for i = 1, #name do - possible_name = name:sub(1, i - 1) .. name:sub(i + 1) - - if not context_pool[possible_name] then - context_pool[possible_name] = {} - end - - table.insert(context_pool[possible_name], name) - end - end - end - - for i = 1, #wrong_name + 1 do - possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1) - - if context[possible_name] then - possible_names[possible_name] = true - elseif context_pool[possible_name] then - for _, name in ipairs(context_pool[possible_name]) do - possible_names[name] = true - end - end - end - - local first = next(possible_names) - - if first then - if next(possible_names, first) then - local possible_names_arr = {} - - for name in pairs(possible_names) do - table.insert(possible_names_arr, "'" .. name .. "'") - end - - table.sort(possible_names_arr) - return "\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?" - else - return "\nDid you mean '" .. first .. "'?" - end - else - return "" - end -end - -local ElementState = class({ - invocations = 0 -}) - -function ElementState:__call(state, element) - self.state = state - self.result = state.result - self.element = element - self.target = element._target or element:_get_default_target() - self.action, self.result[self.target] = element:_get_action() - return self -end - -function ElementState:error(fmt, ...) - self.state:error(fmt, ...) -end - -function ElementState:convert(argument, index) - local converter = self.element._convert - - if converter then - local ok, err - - if type(converter) == "function" then - ok, err = converter(argument) - elseif type(converter[index]) == "function" then - ok, err = converter[index](argument) - else - ok = converter[argument] - end - - if ok == nil then - self:error(err and "%s" or "malformed argument '%s'", err or argument) - end - - argument = ok - end - - return argument -end - -function ElementState:default(mode) - return self.element._defmode:find(mode) and self.element._default -end - -local function bound(noun, min, max, is_max) - local res = "" - - if min ~= max then - res = "at " .. (is_max and "most" or "least") .. " " - end - - local number = is_max and max or min - return res .. tostring(number) .. " " .. noun .. (number == 1 and "" or "s") -end - -function ElementState:set_name(alias) - self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name) -end - -function ElementState:invoke() - self.open = true - self.overwrite = false - - if self.invocations >= self.element._maxcount then - if self.element._overwrite then - self.overwrite = true - else - local num_times_repr = bound("time", self.element._mincount, self.element._maxcount, true) - self:error("%s must be used %s", self.name, num_times_repr) - end - else - self.invocations = self.invocations + 1 - end - - self.args = {} - - if self.element._maxargs <= 0 then - self:close() - end - - return self.open -end - -function ElementState:pass(argument) - argument = self:convert(argument, #self.args + 1) - table.insert(self.args, argument) - - if #self.args >= self.element._maxargs then - self:close() - end - - return self.open -end - -function ElementState:complete_invocation() - while #self.args < self.element._minargs do - self:pass(self.element._default) - end -end - -function ElementState:close() - if self.open then - self.open = false - - if #self.args < self.element._minargs then - if self:default("a") then - self:complete_invocation() - else - if #self.args == 0 then - if getmetatable(self.element) == Argument then - self:error("missing %s", self.name) - elseif self.element._maxargs == 1 then - self:error("%s requires an argument", self.name) - end - end - - self:error("%s requires %s", self.name, bound("argument", self.element._minargs, self.element._maxargs)) - end - end - - local args - - if self.element._maxargs == 0 then - args = self.args[1] - elseif self.element._maxargs == 1 then - if self.element._minargs == 0 and self.element._mincount ~= self.element._maxcount then - args = self.args - else - args = self.args[1] - end - else - args = self.args - end - - self.action(self.result, self.target, args, self.overwrite) - end -end - -local ParseState = class({ - result = {}, - options = {}, - arguments = {}, - argument_i = 1, - element_to_mutexes = {}, - mutex_to_element_state = {}, - command_actions = {} -}) - -function ParseState:__call(parser, error_handler) - self.parser = parser - self.error_handler = error_handler - self.charset = parser:_update_charset() - self:switch(parser) - return self -end - -function ParseState:error(fmt, ...) - self.error_handler(self.parser, fmt:format(...)) -end - -function ParseState:switch(parser) - self.parser = parser - - if parser._action then - table.insert(self.command_actions, {action = parser._action, name = parser._name}) - end - - for _, option in ipairs(parser._options) do - option = ElementState(self, option) - table.insert(self.options, option) - - for _, alias in ipairs(option.element._aliases) do - self.options[alias] = option - end - end - - for _, mutex in ipairs(parser._mutexes) do - for _, element in ipairs(mutex) do - if not self.element_to_mutexes[element] then - self.element_to_mutexes[element] = {} - end - - table.insert(self.element_to_mutexes[element], mutex) - end - end - - for _, argument in ipairs(parser._arguments) do - argument = ElementState(self, argument) - table.insert(self.arguments, argument) - argument:set_name() - argument:invoke() - end - - self.handle_options = parser._handle_options - self.argument = self.arguments[self.argument_i] - self.commands = parser._commands - - for _, command in ipairs(self.commands) do - for _, alias in ipairs(command._aliases) do - self.commands[alias] = command - end - end -end - -function ParseState:get_option(name) - local option = self.options[name] - - if not option then - self:error("unknown option '%s'%s", name, get_tip(self.options, name)) - else - return option - end -end - -function ParseState:get_command(name) - local command = self.commands[name] - - if not command then - if #self.commands > 0 then - self:error("unknown command '%s'%s", name, get_tip(self.commands, name)) - else - self:error("too many arguments") - end - else - return command - end -end - -function ParseState:check_mutexes(element_state) - if self.element_to_mutexes[element_state.element] then - for _, mutex in ipairs(self.element_to_mutexes[element_state.element]) do - local used_element_state = self.mutex_to_element_state[mutex] - - if used_element_state and used_element_state ~= element_state then - self:error("%s can not be used together with %s", element_state.name, used_element_state.name) - else - self.mutex_to_element_state[mutex] = element_state - end - end - end -end - -function ParseState:invoke(option, name) - self:close() - option:set_name(name) - self:check_mutexes(option, name) - - if option:invoke() then - self.option = option - end -end - -function ParseState:pass(arg) - if self.option then - if not self.option:pass(arg) then - self.option = nil - end - elseif self.argument then - self:check_mutexes(self.argument) - - if not self.argument:pass(arg) then - self.argument_i = self.argument_i + 1 - self.argument = self.arguments[self.argument_i] - end - else - local command = self:get_command(arg) - self.result[command._target or command._name] = true - - if self.parser._command_target then - self.result[self.parser._command_target] = command._name - end - - self:switch(command) - end -end - -function ParseState:close() - if self.option then - self.option:close() - self.option = nil - end -end - -function ParseState:finalize() - self:close() - - for i = self.argument_i, #self.arguments do - local argument = self.arguments[i] - if #argument.args == 0 and argument:default("u") then - argument:complete_invocation() - else - argument:close() - end - end - - if self.parser._require_command and #self.commands > 0 then - self:error("a command is required") - end - - for _, option in ipairs(self.options) do - option.name = option.name or ("option '%s'"):format(option.element._name) - - if option.invocations == 0 then - if option:default("u") then - option:invoke() - option:complete_invocation() - option:close() - end - end - - local mincount = option.element._mincount - - if option.invocations < mincount then - if option:default("a") then - while option.invocations < mincount do - option:invoke() - option:close() - end - elseif option.invocations == 0 then - self:error("missing %s", option.name) - else - self:error("%s must be used %s", option.name, bound("time", mincount, option.element._maxcount)) - end - end - end - - for i = #self.command_actions, 1, -1 do - self.command_actions[i].action(self.result, self.command_actions[i].name) - end -end - -function ParseState:parse(args) - for _, arg in ipairs(args) do - local plain = true - - if self.handle_options then - local first = arg:sub(1, 1) - - if self.charset[first] then - if #arg > 1 then - plain = false - - if arg:sub(2, 2) == first then - if #arg == 2 then - if self.options[arg] then - local option = self:get_option(arg) - self:invoke(option, arg) - else - self:close() - end - - self.handle_options = false - else - local equals = arg:find "=" - if equals then - local name = arg:sub(1, equals - 1) - local option = self:get_option(name) - - if option.element._maxargs <= 0 then - self:error("option '%s' does not take arguments", name) - end - - self:invoke(option, name) - self:pass(arg:sub(equals + 1)) - else - local option = self:get_option(arg) - self:invoke(option, arg) - end - end - else - for i = 2, #arg do - local name = first .. arg:sub(i, i) - local option = self:get_option(name) - self:invoke(option, name) - - if i ~= #arg and option.element._maxargs > 0 then - self:pass(arg:sub(i + 1)) - break - end - end - end - end - end - end - - if plain then - self:pass(arg) - end - end - - self:finalize() - return self.result -end - -function Parser:error(msg) - io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg)) - os.exit(1) -end - --- Compatibility with strict.lua and other checkers: -local default_cmdline = rawget(_G, "arg") or {} - -function Parser:_parse(args, error_handler) - return ParseState(self, error_handler):parse(args or default_cmdline) -end - -function Parser:parse(args) - return self:_parse(args, self.error) -end - -local function xpcall_error_handler(err) - return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2) -end - -function Parser:pparse(args) - local parse_error - - local ok, result = xpcall(function() - return self:_parse(args, function(_, err) - parse_error = err - error(err, 0) - end) - end, xpcall_error_handler) - - if ok then - return true, result - elseif not parse_error then - error(result, 0) - else - return false, parse_error - end -end - -local argparse = {} - -argparse.version = "0.6.0" - -setmetatable(argparse, {__call = function(_, ...) - return Parser(default_cmdline[0]):add_help(true)(...) -end}) - -return argparse - -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() diff --git a/build/luacomp-jit.lua b/build/luacomp-jit.lua deleted file mode 100644 index 6aa4c78..0000000 --- a/build/luacomp-jit.lua +++ /dev/null @@ -1,514 +0,0 @@ -#!/usr/bin/env luajit ---[[ - init.lua - Main file of LuaComp - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - - -local function _sv(k, v) - _G[k] = v - --os.setenv(k, tostring(v)) -end - -_sv("LUACOMP_V_MAJ", 1) -_sv("LUACOMP_V_MIN", 1) -_sv("LUACOMP_V_PAT", 0) -_sv("LUACOMP_VERSION", LUACOMP_V_MAJ.."."..LUACOMP_V_MIN.."."..LUACOMP_V_PAT) -_sv("LUACOMP_NAME", "LuaComp") - ---[[ - ast.lua - Generates a structure for use in preprocessing. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function nextc(f, c) - c = c or 1 - return f:read(c) -end - -local function peek(f, c) - c = c or 1 - local z = f:read(c) - f:seek("cur", -c) - return z -end - -local function skip(f, c) - c = c or 1 - return f:seek("cur", c) -end - -local ws = { - ["\t"] = true, - [" "] = true -} - -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 - 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 - hex = hex .. lc - end - end - return tonumber(hex, 16) -end - -local function parse_number(f, c) - local lc = " " - local num = c - while 48 <= lc:byte() and lc:byte() <= 57 do - lc = nextc(f) - if (48 <= lc:byte() and lc:byte() <= 57) then - num = num .. lc - end - end - return tonumber(hex, 10) -end - -local esct = { - ["t"] = "\t", - ["n"] = "\n", - ["r"] = "\r", - ["\\"] = "\\\\" -} - -for i=0, 9 do - esct[tostring(i)] = string.char(i) -end - -local function parse_dblquote(f) - local val = "" - while peek(f) ~= "\"" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_snglquote(f) - local val = "" - while peek(f) ~= "\'" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_envarg(f) - local val = "" - while peek(f) ~= ")" do - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - val = val .. nextc(f) - end - skip(f) - return val -end - -local function parse_directive(f) - local lc = "_" - local name = "" - local args = {} - local carg = "" - while not ws[lc] do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name} - elseif not ws[lc] then - name = name .. lc - end - end - while true do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name, args=args} - elseif lc == "0" and peek(f) == "x" then - skip(f) - local val = parse_hex(f) - args[#args+1] = val - elseif 48 <= lc:byte() and lc:byte() <= 57 then - local val = parse_number(f, lc) - args[#args+1] = val - elseif lc == "\"" then - local val, e = parse_dblquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "\'" then - local val, e = parse_snglquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "$" and peek(f) == "(" then - skip(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 not ws[lc] then - return nil, "Syntax error" - end - end -end - -local function mkast(f, n) - io.stderr:write("PROC\t",n,"\n") - local lc = " " - local lpos = 1 - local ilpos = 1 - local tree = {} - local code = "" - local branches = {} - local function add_code() - tree[#tree+1] = {type="code", data=code, file=n, line=lpos} - code = "" - end - local function parse_error(e) - io.stderr:write("ERROR:"..n..":"..lpos..": "..e.."\n") - os.exit(1) - end - while lc and lc ~= "" do - lc = nextc(f) - if (lc == "-" and ilpos == 1) then - if (peek(f, 2) == "-#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "/" and ilpos == 1) then - if (peek(f, 2) == "/#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "$" and peek(f) == "(") then - add_code() - skip(f) - local val, e = parse_envarg(f) - if not val then - parse_error(e) - end - tree[#tree+1] = {type="envvar", var=val, file=n, line=lpos} - 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="lua", 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="lua_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) - end - lpos = lpos+1 - ilpos = 1 - code = code .. "\n" - else - code = code .. (lc or "") - ilpos = ilpos+1 - end - end - add_code() - return tree -end - ---[[ - generator.lua - Generates the code. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function lua_escape(code) - return code:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n") -end - -local directives = {} - -local function generate(ast) - local lua_code = "" - for i=1, #ast do - local leaf = ast[i] - if (leaf.type == "lua") then - lua_code = lua_code .. leaf.code - elseif (leaf.type == "directive") then - local stargs = {} - for i=1, #leaf.args do - local arg = leaf.args[i] - if (type(arg) == "string") then - stargs[i] = "\""..lua_escape(arg).."\"" - elseif (type(arg) == "number") then - stargs[i] = tostring(arg) - end - end - lua_code = lua_code .. "call_directive(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.name.."\","..table.concat(stargs, ",")..")" - elseif (leaf.type == "envvar") then - lua_code = lua_code .. "put_env(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.var.."\")" - elseif (leaf.type == "code") then - 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.."))" - else - io.stderr:write("ERROR: Internal catastrophic failure, unknown type "..leaf.type.."\n") - os.exit(1) - end - lua_code = lua_code .. "\n" - end - local env = {code = ""} - local function run_away_screaming(fpos, err) - io.stdout:write("ERROR: "..fpos..": "..err.."\n") - os.exit(1) - end - local function call_directive(fpos, dname, ...) - if (not directives[dname]) then - run_away_screaming(fpos, "Invalid directive name `"..dname.."'") - end - local r, er = directives[dname](env, ...) - if (not r) then - run_away_screaming(fpos, er) - end - end - local function put_env(fpos, evar) - local e = os.getenv(evar) - if not e then - run_away_screaming(fpos, "Enviroment variable `"..evar.."' does not exist!") - end - env.code = env.code .. "\""..lua_escape(e).."\"" - end - local function put_code(fpos, code) - env.code = env.code .. code --not much that can fail here... - end - local fenv = {} - for k, v in pairs(_G) do - fenv[k] = v - end - fenv._G = fenv - fenv._ENV = fenv - fenv.call_directive = call_directive - fenv.put_code = put_code - fenv.put_env = put_env - local func = assert(load(lua_code, "=(generated code)", "t", fenv)) - func() - return env.code -end - ---[[ - directive_provider.lua - Provides preprocessor directives - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - ----#include "directives/define.lua" - -function directives.include(env, file) - if (not os.execute("stat "..file..">/dev/null")) then - return false, "File `"..file.."' does not exist!" - end - local f = io.open(file, "r") - local fast = mkast(f, file) - local code = generate(fast) - env.code = env.code .. "\n" .. code .. "\n" - return true -end - -local warned = false -function directives.loadmod(env, mod) - if not warned then - io.stderr:write("Warning: loadmod is depreciated and unsafe. The API differs from luapreproc.\n") - warned = true - end - if (not os.execute("stat "..file..">/dev/null")) then - return false, "Module `"..file.."' does not exist!" - end - local modname, func = dofile(mod) - directives[modname] = func - return true -end - - ---[[ - cfg/minifier_providers.lua - Provides minifier providers. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local providers = {} - -function providers.luamin(cin) - local fn = os.tmpname() - local fh = io.open(fn, "w") - fh:write(cin) - fh:close() - local lmh = io.popen("luamin -f "..fn.." 2>&1", "r") - local dat = lmh:read("*a") - local stat, _, code = lmh:close() - if (code ~= 0) then - return false, dat - end - return dat -end - -function providers.none(cin) - return cin -end - -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 = 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() diff --git a/luacomp2.lua b/luacomp2.lua deleted file mode 100644 index f6646ac..0000000 --- a/luacomp2.lua +++ /dev/null @@ -1,494 +0,0 @@ -#!/usr/bin/env lua5.3 ---[[ - init.lua - Main file of LuaComp - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - - -local function _sv(k, v) - _G[k] = v - --os.setenv(k, tostring(v)) -end - -_sv("LUACOMP_V_MAJ", 1) -_sv("LUACOMP_V_MIN", 0) -_sv("LUACOMP_V_PAT", 0) -_sv("LUACOMP_VERSION", LUACOMP_V_MAJ.."."..LUACOMP_V_MIN.."."..LUACOMP_V_PAT) -_sv("LUACOMP_NAME", "LuaComp") - ---[[ - ast.lua - Generates a structure for use in preprocessing. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function nextc(f, c) - c = c or 1 - return f:read(c) -end - -local function peek(f, c) - c = c or 1 - local z = f:read(c) - f:seek("cur", -c) - return z -end - -local function skip(f, c) - c = c or 1 - return f:seek("cur", c) -end - -local ws = { - ["\t"] = true, - [" "] = true -} - -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 - 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 - hex = hex .. lc - end - end - return tonumber(hex, 16) -end - -local function parse_number(f, c) - local lc = " " - local num = c - while 48 <= lc:byte() and lc:byte() <= 57 do - lc = nextc(f) - if (48 <= lc:byte() and lc:byte() <= 57) then - num = num .. lc - end - end - return tonumber(hex, 10) -end - -local esct = { - ["t"] = "\t", - ["n"] = "\n", - ["r"] = "\r", - ["\\"] = "\\\\" -} - -for i=0, 9 do - esct[tostring(i)] = string.char(i) -end - -local function parse_dblquote(f) - local val = "" - while peek(f) ~= "\"" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_snglquote(f) - local val = "" - while peek(f) ~= "\'" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_envarg(f) - local val = "" - while peek(f) ~= ")" do - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - val = val .. nextc(f) - end - skip(f) - return val -end - -local function parse_directive(f) - local lc = "_" - local name = "" - local args = {} - local carg = "" - while not ws[lc] do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name} - elseif not ws[lc] then - name = name .. lc - end - end - while true do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name, args=args} - elseif lc == "0" and peek(f) == "x" then - skip(f) - local val = parse_hex(f) - args[#args+1] = val - elseif 48 <= lc:byte() and lc:byte() <= 57 then - local val = parse_number(f, lc) - args[#args+1] = val - elseif lc == "\"" then - local val, e = parse_dblquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "\'" then - local val, e = parse_snglquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "$" and peek(f) == "(" then - skip(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 not ws[lc] then - return nil, "Syntax error" - end - end -end - -local function mkast(f, n) - io.stderr:write("PROC\t",n,"\n") - local lc = " " - local lpos = 1 - local ilpos = 1 - local tree = {} - local code = "" - local branches = {} - local function add_code() - tree[#tree+1] = {type="code", data=code, file=n, line=lpos} - code = "" - end - local function parse_error(e) - io.stderr:write("ERROR:"..n..":"..lpos..": "..e.."\n") - os.exit(1) - end - while lc and lc ~= "" do - lc = nextc(f) - if (lc == "-" and ilpos == 1) then - if (peek(f, 2) == "-#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "$" and peek(f) == "(") then - add_code() - skip(f) - local val, e = parse_envarg(f) - if not val then - parse_error(e) - end - tree[#tree+1] = {type="envvar", var=val, file=n, line=lpos} - 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="lua", 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="lua_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) - end - lpos = lpos+1 - ilpos = 1 - code = code .. "\n" - else - code = code .. (lc or "") - ilpos = ilpos+1 - end - end - add_code() - return tree -end - ---[[ - generator.lua - Generates the code. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function lua_escape(code) - return code:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n") -end - -local directives = {} - -local function generate(ast) - local lua_code = "" - for i=1, #ast do - local leaf = ast[i] - if (leaf.type == "lua") then - lua_code = lua_code .. leaf.code - elseif (leaf.type == "directive") then - local stargs = {} - for i=1, #leaf.args do - local arg = leaf.args[i] - if (type(arg) == "string") then - stargs[i] = "\""..lua_escape(arg).."\"" - elseif (type(arg) == "number") then - stargs[i] = tostring(arg) - end - end - lua_code = lua_code .. "call_directive(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.name.."\","..table.concat(stargs, ",")..")" - elseif (leaf.type == "envvar") then - lua_code = lua_code .. "put_env(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.var.."\")" - elseif (leaf.type == "code") then - 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.."))" - else - io.stderr:write("ERROR: Internal catastrophic failure, unknown type "..leaf.type.."\n") - os.exit(1) - end - lua_code = lua_code .. "\n" - end - local env = {code = ""} - local function run_away_screaming(fpos, err) - io.stdout:write("ERROR: "..fpos..": "..err.."\n") - os.exit(1) - end - local function call_directive(fpos, dname, ...) - if (not directives[dname]) then - run_away_screaming(fpos, "Invalid directive name `"..dname.."'") - end - local r, er = directives[dname](env, ...) - if (not r) then - run_away_screaming(fpos, er) - end - end - local function put_env(fpos, evar) - local e = os.getenv(evar) - if not e then - run_away_screaming(fpos, "Enviroment variable `"..evar.."' does not exist!") - end - env.code = env.code .. "\""..lua_escape(e).."\"" - end - local function put_code(fpos, code) - env.code = env.code .. code --not much that can fail here... - end - local fenv = {} - for k, v in pairs(_G) do - fenv[k] = v - end - fenv._G = fenv - fenv._ENV = fenv - fenv.call_directive = call_directive - fenv.put_code = put_code - fenv.put_env = put_env - local func = assert(load(lua_code, "=(generated code)", "t", fenv)) - func() - return env.code -end - ---[[ - directive_provider.lua - Provides preprocessor directives - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - ----#include "directives/define.lua" - -function directives.include(env, file) - if (not os.execute("stat "..file..">/dev/null")) then - return false, "File `"..file.."' does not exist!" - end - local f = io.open(file, "r") - local fast = mkast(f, file) - local code = generate(fast) - env.code = env.code .. "\n" .. code .. "\n" - return true -end - -local warned = false -function directives.loadmod(env, mod) - if not warned then - io.stderr:write("Warning: loadmod is depreciated and unsafe. The API differs from luapreproc.\n") - warned = true - end - if (not os.execute("stat "..file..">/dev/null")) then - return false, "Module `"..file.."' does not exist!" - end - local modname, func = dofile(mod) - directives[modname] = func - return true -end - - ---[[ - cfg/minifier_providers.lua - Provides minifier providers. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local providers = {} - -function providers.luamin(cin) - local fn = os.tmpname() - local fh = io.open(fn, "w") - fh:write(cin) - fh:close() - local lmh = io.popen("luamin -f "..fn.." 2>&1", "r") - local dat = lmh:read("*a") - local stat, _, code = lmh:close() - if (code ~= 0) then - return false, dat - end - return dat -end - -function providers.none(cin) - return cin -end - -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:flag("-x --executable", "Makes the script an executable") -local args = parser:parse() -local file = args.input -local f -if (file ~= "-") then - if (not os.execute("stat "..file..">/dev/null")) then - io.stderr:write("ERROR: File `"..file.."' does not exist!\n") - os.exit(1) - end - f = io.open(file, "r") -else - file = 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 -if (args.executable) then - of:write("#!/usr/bin/env lua5.3\n") -end -of:write(rcode) -of:close() -f:close() diff --git a/luacomp_test.lua b/luacomp_test.lua deleted file mode 100644 index f40ddc7..0000000 --- a/luacomp_test.lua +++ /dev/null @@ -1,2044 +0,0 @@ -#!/usr/bin/env lua5.3 ---[[ - staticinit.lua - Main file of LuaComp, directly includes argparse. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - - -local function _sv(k, v) - _G[k] = v - --os.setenv(k, tostring(v)) -end - -_sv("LUACOMP_V_MAJ", 1) -_sv("LUACOMP_V_MIN", 0) -_sv("LUACOMP_V_PAT", 0) -_sv("LUACOMP_VERSION", LUACOMP_V_MAJ.."."..LUACOMP_V_MIN.."."..LUACOMP_V_PAT) -_sv("LUACOMP_NAME", "LuaComp") - ---[[ - ast.lua - Generates a structure for use in preprocessing. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function nextc(f, c) - c = c or 1 - return f:read(c) -end - -local function peek(f, c) - c = c or 1 - local z = f:read(c) - f:seek("cur", -c) - return z -end - -local function skip(f, c) - c = c or 1 - return f:seek("cur", c) -end - -local ws = { - ["\t"] = true, - [" "] = true -} - -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 - 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 - hex = hex .. lc - end - end - return tonumber(hex, 16) -end - -local function parse_number(f, c) - local lc = " " - local num = c - while 48 <= lc:byte() and lc:byte() <= 57 do - lc = nextc(f) - if (48 <= lc:byte() and lc:byte() <= 57) then - num = num .. lc - end - end - return tonumber(hex, 10) -end - -local esct = { - ["t"] = "\t", - ["n"] = "\n", - ["r"] = "\r", - ["\\"] = "\\\\" -} - -for i=0, 9 do - esct[tostring(i)] = string.char(i) -end - -local function parse_dblquote(f) - local val = "" - while peek(f) ~= "\"" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_snglquote(f) - local val = "" - while peek(f) ~= "\'" do - local c = nextc(f) - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - if (c == "\\") then - if (esct[peek(f)]) then - c = esct[peek(f)] - skip(f) - else - c = nextc(f) - end - end - val = val .. c - end - skip(f) - return val -end - -local function parse_envarg(f) - local val = "" - while peek(f) ~= ")" do - if (peek(f) == "\n" or peek(f) == "\r") then - return nil, "Unexpected end of line" - end - val = val .. nextc(f) - end - skip(f) - return val -end - -local function parse_directive(f) - local lc = "_" - local name = "" - local args = {} - local carg = "" - while not ws[lc] do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name} - elseif not ws[lc] then - name = name .. lc - end - end - while true do - lc = nextc(f) - if (lc == "\n" or lc == "\r") then - if (lc == "\r" and peek(f) == "\n") then skip(f) end - return {type="directive", name=name, args=args} - elseif lc == "0" and peek(f) == "x" then - skip(f) - local val = parse_hex(f) - args[#args+1] = val - elseif 48 <= lc:byte() and lc:byte() <= 57 then - local val = parse_number(f, lc) - args[#args+1] = val - elseif lc == "\"" then - local val, e = parse_dblquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "\'" then - local val, e = parse_snglquote(f) - if not val then return val, e end - args[#args+1] = val - elseif lc == "$" and peek(f) == "(" then - skip(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 not ws[lc] then - return nil, "Syntax error" - end - end -end - -local function mkast(f, n) - io.stderr:write("PROC\t",n,"\n") - local lc = " " - local lpos = 1 - local ilpos = 1 - local tree = {} - local code = "" - local branches = {} - local function add_code() - tree[#tree+1] = {type="code", data=code, file=n, line=lpos} - code = "" - end - local function parse_error(e) - io.stderr:write("ERROR:"..n..":"..lpos..": "..e.."\n") - os.exit(1) - end - while lc and lc ~= "" do - lc = nextc(f) - if (lc == "-" and ilpos == 1) then - if (peek(f, 2) == "-#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "/" and ilpos == 1) then - if (peek(f, 2) == "/#") then --Directive - add_code() - skip(f, 2) - local d, r = parse_directive(f) - if not d then - parse_error(r) - end - d.line = lpos - d.file = n - lpos = lpos+1 - tree[#tree+1] = d - else - code = code .. lc - ilpos = ilpos+1 - end - elseif (lc == "$" and peek(f) == "(") then - add_code() - skip(f) - local val, e = parse_envarg(f) - if not val then - parse_error(e) - end - tree[#tree+1] = {type="envvar", var=val, file=n, line=lpos} - 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="lua", 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="lua_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) - end - lpos = lpos+1 - ilpos = 1 - code = code .. "\n" - else - code = code .. (lc or "") - ilpos = ilpos+1 - end - end - add_code() - return tree -end - ---[[ - generator.lua - Generates the code. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local function lua_escape(code) - return code:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n") -end - -local directives = {} - -local function generate(ast) - local lua_code = "" - for i=1, #ast do - local leaf = ast[i] - if (leaf.type == "lua") then - lua_code = lua_code .. leaf.code - elseif (leaf.type == "directive") then - local stargs = {} - for i=1, #leaf.args do - local arg = leaf.args[i] - if (type(arg) == "string") then - stargs[i] = "\""..lua_escape(arg).."\"" - elseif (type(arg) == "number") then - stargs[i] = tostring(arg) - end - end - lua_code = lua_code .. "call_directive(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.name.."\","..table.concat(stargs, ",")..")" - elseif (leaf.type == "envvar") then - lua_code = lua_code .. "put_env(\""..leaf.file..":"..tostring(leaf.line).."\",\""..leaf.var.."\")" - elseif (leaf.type == "code") then - 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.."))" - else - io.stderr:write("ERROR: Internal catastrophic failure, unknown type "..leaf.type.."\n") - os.exit(1) - end - lua_code = lua_code .. "\n" - end - local env = {code = ""} - local function run_away_screaming(fpos, err) - io.stdout:write("ERROR: "..fpos..": "..err.."\n") - os.exit(1) - end - local function call_directive(fpos, dname, ...) - if (not directives[dname]) then - run_away_screaming(fpos, "Invalid directive name `"..dname.."'") - end - local r, er = directives[dname](env, ...) - if (not r) then - run_away_screaming(fpos, er) - end - end - local function put_env(fpos, evar) - local e = os.getenv(evar) - if not e then - run_away_screaming(fpos, "Enviroment variable `"..evar.."' does not exist!") - end - env.code = env.code .. "\""..lua_escape(e).."\"" - end - local function put_code(fpos, code) - env.code = env.code .. code --not much that can fail here... - end - local fenv = {} - for k, v in pairs(_G) do - fenv[k] = v - end - fenv._G = fenv - fenv._ENV = fenv - fenv.call_directive = call_directive - fenv.put_code = put_code - fenv.put_env = put_env - local func = assert(load(lua_code, "=(generated code)", "t", fenv)) - func() - return env.code -end - ---[[ - directive_provider.lua - Provides preprocessor directives - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - ----#include "directives/define.lua" - -function directives.include(env, file) - if (not os.execute("stat "..file..">/dev/null")) then - return false, "File `"..file.."' does not exist!" - end - local f = io.open(file, "r") - local fast = mkast(f, file) - local code = generate(fast) - env.code = env.code .. "\n" .. code .. "\n" - return true -end - -local warned = false -function directives.loadmod(env, mod) - if not warned then - io.stderr:write("Warning: loadmod is depreciated and unsafe. The API differs from luapreproc.\n") - warned = true - end - if (not os.execute("stat "..file..">/dev/null")) then - return false, "Module `"..file.."' does not exist!" - end - local modname, func = dofile(mod) - directives[modname] = func - return true -end - - ---[[ - cfg/minifier_providers.lua - Provides minifier providers. - - Copyright 2019 Adorable-Catgirl - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -local providers = {} - -function providers.luamin(cin) - local fn = os.tmpname() - local fh = io.open(fn, "w") - fh:write(cin) - fh:close() - local lmh = io.popen("luamin -f "..fn.." 2>&1", "r") - local dat = lmh:read("*a") - local stat, _, code = lmh:close() - if (code ~= 0) then - return false, dat - end - return dat -end - -function providers.none(cin) - return cin -end - -local argparse = (function() - --- The MIT License (MIT) - --- Copyright (c) 2013 - 2018 Peter Melnichenko - --- Permission is hereby granted, free of charge, to any person obtaining a copy of --- this software and associated documentation files (the "Software"), to deal in --- the Software without restriction, including without limitation the rights to --- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of --- the Software, and to permit persons to whom the Software is furnished to do so, --- subject to the following conditions: - --- The above copyright notice and this permission notice shall be included in all --- copies or substantial portions of the Software. - --- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS --- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR --- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER --- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN --- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -local function deep_update(t1, t2) - for k, v in pairs(t2) do - if type(v) == "table" then - v = deep_update({}, v) - end - - t1[k] = v - end - - return t1 -end - --- A property is a tuple {name, callback}. --- properties.args is number of properties that can be set as arguments --- when calling an object. -local function class(prototype, properties, parent) - -- Class is the metatable of its instances. - local cl = {} - cl.__index = cl - - if parent then - cl.__prototype = deep_update(deep_update({}, parent.__prototype), prototype) - else - cl.__prototype = prototype - end - - if properties then - local names = {} - - -- Create setter methods and fill set of property names. - for _, property in ipairs(properties) do - local name, callback = property[1], property[2] - - cl[name] = function(self, value) - if not callback(self, value) then - self["_" .. name] = value - end - - return self - end - - names[name] = true - end - - function cl.__call(self, ...) - -- When calling an object, if the first argument is a table, - -- interpret keys as property names, else delegate arguments - -- to corresponding setters in order. - if type((...)) == "table" then - for name, value in pairs((...)) do - if names[name] then - self[name](self, value) - end - end - else - local nargs = select("#", ...) - - for i, property in ipairs(properties) do - if i > nargs or i > properties.args then - break - end - - local arg = select(i, ...) - - if arg ~= nil then - self[property[1]](self, arg) - end - end - end - - return self - end - end - - -- If indexing class fails, fallback to its parent. - local class_metatable = {} - class_metatable.__index = parent - - function class_metatable.__call(self, ...) - -- Calling a class returns its instance. - -- Arguments are delegated to the instance. - local object = deep_update({}, self.__prototype) - setmetatable(object, self) - return object(...) - end - - return setmetatable(cl, class_metatable) -end - -local function typecheck(name, types, value) - for _, type_ in ipairs(types) do - if type(value) == type_ then - return true - end - end - - error(("bad property '%s' (%s expected, got %s)"):format(name, table.concat(types, " or "), type(value))) -end - -local function typechecked(name, ...) - local types = {...} - return {name, function(_, value) typecheck(name, types, value) end} -end - -local multiname = {"name", function(self, value) - typecheck("name", {"string"}, value) - - for alias in value:gmatch("%S+") do - self._name = self._name or alias - table.insert(self._aliases, alias) - end - - -- Do not set _name as with other properties. - return true -end} - -local function parse_boundaries(str) - if tonumber(str) then - return tonumber(str), tonumber(str) - end - - if str == "*" then - return 0, math.huge - end - - if str == "+" then - return 1, math.huge - end - - if str == "?" then - return 0, 1 - end - - if str:match "^%d+%-%d+$" then - local min, max = str:match "^(%d+)%-(%d+)$" - return tonumber(min), tonumber(max) - end - - if str:match "^%d+%+$" then - local min = str:match "^(%d+)%+$" - return tonumber(min), math.huge - end -end - -local function boundaries(name) - return {name, function(self, value) - typecheck(name, {"number", "string"}, value) - - local min, max = parse_boundaries(value) - - if not min then - error(("bad property '%s'"):format(name)) - end - - self["_min" .. name], self["_max" .. name] = min, max - end} -end - -local actions = {} - -local option_action = {"action", function(_, value) - typecheck("action", {"function", "string"}, value) - - if type(value) == "string" and not actions[value] then - error(("unknown action '%s'"):format(value)) - end -end} - -local option_init = {"init", function(self) - self._has_init = true -end} - -local option_default = {"default", function(self, value) - if type(value) ~= "string" then - self._init = value - self._has_init = true - return true - end -end} - -local add_help = {"add_help", function(self, value) - typecheck("add_help", {"boolean", "string", "table"}, value) - - if self._has_help then - table.remove(self._options) - self._has_help = false - end - - if value then - local help = self:flag() - :description "Show this help message and exit." - :action(function() - print(self:get_help()) - os.exit(0) - end) - - if value ~= true then - help = help(value) - end - - if not help._name then - help "-h" "--help" - end - - self._has_help = true - end -end} - -local Parser = class({ - _arguments = {}, - _options = {}, - _commands = {}, - _mutexes = {}, - _groups = {}, - _require_command = true, - _handle_options = true -}, { - args = 3, - typechecked("name", "string"), - typechecked("description", "string"), - typechecked("epilog", "string"), - typechecked("usage", "string"), - typechecked("help", "string"), - typechecked("require_command", "boolean"), - typechecked("handle_options", "boolean"), - typechecked("action", "function"), - typechecked("command_target", "string"), - typechecked("help_vertical_space", "number"), - typechecked("usage_margin", "number"), - typechecked("usage_max_width", "number"), - typechecked("help_usage_margin", "number"), - typechecked("help_description_margin", "number"), - typechecked("help_max_width", "number"), - add_help -}) - -local Command = class({ - _aliases = {} -}, { - args = 3, - multiname, - typechecked("description", "string"), - typechecked("epilog", "string"), - typechecked("target", "string"), - typechecked("usage", "string"), - typechecked("help", "string"), - typechecked("require_command", "boolean"), - typechecked("handle_options", "boolean"), - typechecked("action", "function"), - typechecked("command_target", "string"), - typechecked("help_vertical_space", "number"), - typechecked("usage_margin", "number"), - typechecked("usage_max_width", "number"), - typechecked("help_usage_margin", "number"), - typechecked("help_description_margin", "number"), - typechecked("help_max_width", "number"), - typechecked("hidden", "boolean"), - add_help -}, Parser) - -local Argument = class({ - _minargs = 1, - _maxargs = 1, - _mincount = 1, - _maxcount = 1, - _defmode = "unused", - _show_default = true -}, { - args = 5, - typechecked("name", "string"), - typechecked("description", "string"), - option_default, - typechecked("convert", "function", "table"), - boundaries("args"), - typechecked("target", "string"), - typechecked("defmode", "string"), - typechecked("show_default", "boolean"), - typechecked("argname", "string", "table"), - typechecked("hidden", "boolean"), - option_action, - option_init -}) - -local Option = class({ - _aliases = {}, - _mincount = 0, - _overwrite = true -}, { - args = 6, - multiname, - typechecked("description", "string"), - option_default, - typechecked("convert", "function", "table"), - boundaries("args"), - boundaries("count"), - typechecked("target", "string"), - typechecked("defmode", "string"), - typechecked("show_default", "boolean"), - typechecked("overwrite", "boolean"), - typechecked("argname", "string", "table"), - typechecked("hidden", "boolean"), - option_action, - option_init -}, Argument) - -function Parser:_inherit_property(name, default) - local element = self - - while true do - local value = element["_" .. name] - - if value ~= nil then - return value - end - - if not element._parent then - return default - end - - element = element._parent - end -end - -function Argument:_get_argument_list() - local buf = {} - local i = 1 - - while i <= math.min(self._minargs, 3) do - local argname = self:_get_argname(i) - - if self._default and self._defmode:find "a" then - argname = "[" .. argname .. "]" - end - - table.insert(buf, argname) - i = i+1 - end - - while i <= math.min(self._maxargs, 3) do - table.insert(buf, "[" .. self:_get_argname(i) .. "]") - i = i+1 - - if self._maxargs == math.huge then - break - end - end - - if i < self._maxargs then - table.insert(buf, "...") - end - - return buf -end - -function Argument:_get_usage() - local usage = table.concat(self:_get_argument_list(), " ") - - if self._default and self._defmode:find "u" then - if self._maxargs > 1 or (self._minargs == 1 and not self._defmode:find "a") then - usage = "[" .. usage .. "]" - end - end - - return usage -end - -function actions.store_true(result, target) - result[target] = true -end - -function actions.store_false(result, target) - result[target] = false -end - -function actions.store(result, target, argument) - result[target] = argument -end - -function actions.count(result, target, _, overwrite) - if not overwrite then - result[target] = result[target] + 1 - end -end - -function actions.append(result, target, argument, overwrite) - result[target] = result[target] or {} - table.insert(result[target], argument) - - if overwrite then - table.remove(result[target], 1) - end -end - -function actions.concat(result, target, arguments, overwrite) - if overwrite then - error("'concat' action can't handle too many invocations") - end - - result[target] = result[target] or {} - - for _, argument in ipairs(arguments) do - table.insert(result[target], argument) - end -end - -function Argument:_get_action() - local action, init - - if self._maxcount == 1 then - if self._maxargs == 0 then - action, init = "store_true", nil - else - action, init = "store", nil - end - else - if self._maxargs == 0 then - action, init = "count", 0 - else - action, init = "append", {} - end - end - - if self._action then - action = self._action - end - - if self._has_init then - init = self._init - end - - if type(action) == "string" then - action = actions[action] - end - - return action, init -end - --- Returns placeholder for `narg`-th argument. -function Argument:_get_argname(narg) - local argname = self._argname or self:_get_default_argname() - - if type(argname) == "table" then - return argname[narg] - else - return argname - end -end - -function Argument:_get_default_argname() - return "<" .. self._name .. ">" -end - -function Option:_get_default_argname() - return "<" .. self:_get_default_target() .. ">" -end - --- Returns labels to be shown in the help message. -function Argument:_get_label_lines() - return {self._name} -end - -function Option:_get_label_lines() - local argument_list = self:_get_argument_list() - - if #argument_list == 0 then - -- Don't put aliases for simple flags like `-h` on different lines. - return {table.concat(self._aliases, ", ")} - end - - local longest_alias_length = -1 - - for _, alias in ipairs(self._aliases) do - longest_alias_length = math.max(longest_alias_length, #alias) - end - - local argument_list_repr = table.concat(argument_list, " ") - local lines = {} - - for i, alias in ipairs(self._aliases) do - local line = (" "):rep(longest_alias_length - #alias) .. alias .. " " .. argument_list_repr - - if i ~= #self._aliases then - line = line .. "," - end - - table.insert(lines, line) - end - - return lines -end - -function Command:_get_label_lines() - return {table.concat(self._aliases, ", ")} -end - -function Argument:_get_description() - if self._default and self._show_default then - if self._description then - return ("%s (default: %s)"):format(self._description, self._default) - else - return ("default: %s"):format(self._default) - end - else - return self._description or "" - end -end - -function Command:_get_description() - return self._description or "" -end - -function Option:_get_usage() - local usage = self:_get_argument_list() - table.insert(usage, 1, self._name) - usage = table.concat(usage, " ") - - if self._mincount == 0 or self._default then - usage = "[" .. usage .. "]" - end - - return usage -end - -function Argument:_get_default_target() - return self._name -end - -function Option:_get_default_target() - local res - - for _, alias in ipairs(self._aliases) do - if alias:sub(1, 1) == alias:sub(2, 2) then - res = alias:sub(3) - break - end - end - - res = res or self._name:sub(2) - return (res:gsub("-", "_")) -end - -function Option:_is_vararg() - return self._maxargs ~= self._minargs -end - -function Parser:_get_fullname() - local parent = self._parent - local buf = {self._name} - - while parent do - table.insert(buf, 1, parent._name) - parent = parent._parent - end - - return table.concat(buf, " ") -end - -function Parser:_update_charset(charset) - charset = charset or {} - - for _, command in ipairs(self._commands) do - command:_update_charset(charset) - end - - for _, option in ipairs(self._options) do - for _, alias in ipairs(option._aliases) do - charset[alias:sub(1, 1)] = true - end - end - - return charset -end - -function Parser:argument(...) - local argument = Argument(...) - table.insert(self._arguments, argument) - return argument -end - -function Parser:option(...) - local option = Option(...) - - if self._has_help then - table.insert(self._options, #self._options, option) - else - table.insert(self._options, option) - end - - return option -end - -function Parser:flag(...) - return self:option():args(0)(...) -end - -function Parser:command(...) - local command = Command():add_help(true)(...) - command._parent = self - table.insert(self._commands, command) - return command -end - -function Parser:mutex(...) - local elements = {...} - - for i, element in ipairs(elements) do - local mt = getmetatable(element) - assert(mt == Option or mt == Argument, ("bad argument #%d to 'mutex' (Option or Argument expected)"):format(i)) - end - - table.insert(self._mutexes, elements) - return self -end - -function Parser:group(name, ...) - assert(type(name) == "string", ("bad argument #1 to 'group' (string expected, got %s)"):format(type(name))) - - local group = {name = name, ...} - - for i, element in ipairs(group) do - local mt = getmetatable(element) - assert(mt == Option or mt == Argument or mt == Command, - ("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(i + 1)) - end - - table.insert(self._groups, group) - return self -end - -local usage_welcome = "Usage: " - -function Parser:get_usage() - if self._usage then - return self._usage - end - - local usage_margin = self:_inherit_property("usage_margin", #usage_welcome) - local max_usage_width = self:_inherit_property("usage_max_width", 70) - local lines = {usage_welcome .. self:_get_fullname()} - - local function add(s) - if #lines[#lines]+1+#s <= max_usage_width then - lines[#lines] = lines[#lines] .. " " .. s - else - lines[#lines+1] = (" "):rep(usage_margin) .. s - end - end - - -- Normally options are before positional arguments in usage messages. - -- However, vararg options should be after, because they can't be reliable used - -- before a positional argument. - -- Mutexes come into play, too, and are shown as soon as possible. - -- Overall, output usages in the following order: - -- 1. Mutexes that don't have positional arguments or vararg options. - -- 2. Options that are not in any mutexes and are not vararg. - -- 3. Positional arguments - on their own or as a part of a mutex. - -- 4. Remaining mutexes. - -- 5. Remaining options. - - local elements_in_mutexes = {} - local added_elements = {} - local added_mutexes = {} - local argument_to_mutexes = {} - - local function add_mutex(mutex, main_argument) - if added_mutexes[mutex] then - return - end - - added_mutexes[mutex] = true - local buf = {} - - for _, element in ipairs(mutex) do - if not element._hidden and not added_elements[element] then - if getmetatable(element) == Option or element == main_argument then - table.insert(buf, element:_get_usage()) - added_elements[element] = true - end - end - end - - if #buf == 1 then - add(buf[1]) - elseif #buf > 1 then - add("(" .. table.concat(buf, " | ") .. ")") - end - end - - local function add_element(element) - if not element._hidden and not added_elements[element] then - add(element:_get_usage()) - added_elements[element] = true - end - end - - for _, mutex in ipairs(self._mutexes) do - local is_vararg = false - local has_argument = false - - for _, element in ipairs(mutex) do - if getmetatable(element) == Option then - if element:_is_vararg() then - is_vararg = true - end - else - has_argument = true - argument_to_mutexes[element] = argument_to_mutexes[element] or {} - table.insert(argument_to_mutexes[element], mutex) - end - - elements_in_mutexes[element] = true - end - - if not is_vararg and not has_argument then - add_mutex(mutex) - end - end - - for _, option in ipairs(self._options) do - if not elements_in_mutexes[option] and not option:_is_vararg() then - add_element(option) - end - end - - -- Add usages for positional arguments, together with one mutex containing them, if they are in a mutex. - for _, argument in ipairs(self._arguments) do - -- Pick a mutex as a part of which to show this argument, take the first one that's still available. - local mutex - - if elements_in_mutexes[argument] then - for _, argument_mutex in ipairs(argument_to_mutexes[argument]) do - if not added_mutexes[argument_mutex] then - mutex = argument_mutex - end - end - end - - if mutex then - add_mutex(mutex, argument) - else - add_element(argument) - end - end - - for _, mutex in ipairs(self._mutexes) do - add_mutex(mutex) - end - - for _, option in ipairs(self._options) do - add_element(option) - end - - if #self._commands > 0 then - if self._require_command then - add("") - else - add("[]") - end - - add("...") - end - - return table.concat(lines, "\n") -end - -local function split_lines(s) - if s == "" then - return {} - end - - local lines = {} - - if s:sub(-1) ~= "\n" then - s = s .. "\n" - end - - for line in s:gmatch("([^\n]*)\n") do - table.insert(lines, line) - end - - return lines -end - -local function autowrap_line(line, max_length) - -- Algorithm for splitting lines is simple and greedy. - local result_lines = {} - - -- Preserve original indentation of the line, put this at the beginning of each result line. - -- If the first word looks like a list marker ('*', '+', or '-'), add spaces so that starts - -- of the second and the following lines vertically align with the start of the second word. - local indentation = line:match("^ *") - - if line:find("^ *[%*%+%-]") then - indentation = indentation .. " " .. line:match("^ *[%*%+%-]( *)") - end - - -- Parts of the last line being assembled. - local line_parts = {} - - -- Length of the current line. - local line_length = 0 - - -- Index of the next character to consider. - local index = 1 - - while true do - local word_start, word_finish, word = line:find("([^ ]+)", index) - - if not word_start then - -- Ignore trailing spaces, if any. - break - end - - local preceding_spaces = line:sub(index, word_start - 1) - index = word_finish + 1 - - if (#line_parts == 0) or (line_length + #preceding_spaces + #word <= max_length) then - -- Either this is the very first word or it fits as an addition to the current line, add it. - table.insert(line_parts, preceding_spaces) -- For the very first word this adds the indentation. - table.insert(line_parts, word) - line_length = line_length + #preceding_spaces + #word - else - -- Does not fit, finish current line and put the word into a new one. - table.insert(result_lines, table.concat(line_parts)) - line_parts = {indentation, word} - line_length = #indentation + #word - end - end - - if #line_parts > 0 then - table.insert(result_lines, table.concat(line_parts)) - end - - if #result_lines == 0 then - -- Preserve empty lines. - result_lines[1] = "" - end - - return result_lines -end - --- Automatically wraps lines within given array, --- attempting to limit line length to `max_length`. --- Existing line splits are preserved. -local function autowrap(lines, max_length) - local result_lines = {} - - for _, line in ipairs(lines) do - local autowrapped_lines = autowrap_line(line, max_length) - - for _, autowrapped_line in ipairs(autowrapped_lines) do - table.insert(result_lines, autowrapped_line) - end - end - - return result_lines -end - -function Parser:_get_element_help(element) - local label_lines = element:_get_label_lines() - local description_lines = split_lines(element:_get_description()) - - local result_lines = {} - - -- All label lines should have the same length (except the last one, it has no comma). - -- If too long, start description after all the label lines. - -- Otherwise, combine label and description lines. - - local usage_margin_len = self:_inherit_property("help_usage_margin", 3) - local usage_margin = (" "):rep(usage_margin_len) - local description_margin_len = self:_inherit_property("help_description_margin", 25) - local description_margin = (" "):rep(description_margin_len) - - local help_max_width = self:_inherit_property("help_max_width") - - if help_max_width then - local description_max_width = math.max(help_max_width - description_margin_len, 10) - description_lines = autowrap(description_lines, description_max_width) - end - - if #label_lines[1] >= (description_margin_len - usage_margin_len) then - for _, label_line in ipairs(label_lines) do - table.insert(result_lines, usage_margin .. label_line) - end - - for _, description_line in ipairs(description_lines) do - table.insert(result_lines, description_margin .. description_line) - end - else - for i = 1, math.max(#label_lines, #description_lines) do - local label_line = label_lines[i] - local description_line = description_lines[i] - - local line = "" - - if label_line then - line = usage_margin .. label_line - end - - if description_line and description_line ~= "" then - line = line .. (" "):rep(description_margin_len - #line) .. description_line - end - - table.insert(result_lines, line) - end - end - - return table.concat(result_lines, "\n") -end - -local function get_group_types(group) - local types = {} - - for _, element in ipairs(group) do - types[getmetatable(element)] = true - end - - return types -end - -function Parser:_add_group_help(blocks, added_elements, label, elements) - local buf = {label} - - for _, element in ipairs(elements) do - if not element._hidden and not added_elements[element] then - added_elements[element] = true - table.insert(buf, self:_get_element_help(element)) - end - end - - if #buf > 1 then - table.insert(blocks, table.concat(buf, ("\n"):rep(self:_inherit_property("help_vertical_space", 0) + 1))) - end -end - -function Parser:get_help() - if self._help then - return self._help - end - - local blocks = {self:get_usage()} - - local help_max_width = self:_inherit_property("help_max_width") - - if self._description then - local description = self._description - - if help_max_width then - description = table.concat(autowrap(split_lines(description), help_max_width), "\n") - end - - table.insert(blocks, description) - end - - -- 1. Put groups containing arguments first, then other arguments. - -- 2. Put remaining groups containing options, then other options. - -- 3. Put remaining groups containing commands, then other commands. - -- Assume that an element can't be in several groups. - local groups_by_type = { - [Argument] = {}, - [Option] = {}, - [Command] = {} - } - - for _, group in ipairs(self._groups) do - local group_types = get_group_types(group) - - for _, mt in ipairs({Argument, Option, Command}) do - if group_types[mt] then - table.insert(groups_by_type[mt], group) - break - end - end - end - - local default_groups = { - {name = "Arguments", type = Argument, elements = self._arguments}, - {name = "Options", type = Option, elements = self._options}, - {name = "Commands", type = Command, elements = self._commands} - } - - local added_elements = {} - - for _, default_group in ipairs(default_groups) do - local type_groups = groups_by_type[default_group.type] - - for _, group in ipairs(type_groups) do - self:_add_group_help(blocks, added_elements, group.name .. ":", group) - end - - local default_label = default_group.name .. ":" - - if #type_groups > 0 then - default_label = "Other " .. default_label:gsub("^.", string.lower) - end - - self:_add_group_help(blocks, added_elements, default_label, default_group.elements) - end - - if self._epilog then - local epilog = self._epilog - - if help_max_width then - epilog = table.concat(autowrap(split_lines(epilog), help_max_width), "\n") - end - - table.insert(blocks, epilog) - end - - return table.concat(blocks, "\n\n") -end - -local function get_tip(context, wrong_name) - local context_pool = {} - local possible_name - local possible_names = {} - - for name in pairs(context) do - if type(name) == "string" then - for i = 1, #name do - possible_name = name:sub(1, i - 1) .. name:sub(i + 1) - - if not context_pool[possible_name] then - context_pool[possible_name] = {} - end - - table.insert(context_pool[possible_name], name) - end - end - end - - for i = 1, #wrong_name + 1 do - possible_name = wrong_name:sub(1, i - 1) .. wrong_name:sub(i + 1) - - if context[possible_name] then - possible_names[possible_name] = true - elseif context_pool[possible_name] then - for _, name in ipairs(context_pool[possible_name]) do - possible_names[name] = true - end - end - end - - local first = next(possible_names) - - if first then - if next(possible_names, first) then - local possible_names_arr = {} - - for name in pairs(possible_names) do - table.insert(possible_names_arr, "'" .. name .. "'") - end - - table.sort(possible_names_arr) - return "\nDid you mean one of these: " .. table.concat(possible_names_arr, " ") .. "?" - else - return "\nDid you mean '" .. first .. "'?" - end - else - return "" - end -end - -local ElementState = class({ - invocations = 0 -}) - -function ElementState:__call(state, element) - self.state = state - self.result = state.result - self.element = element - self.target = element._target or element:_get_default_target() - self.action, self.result[self.target] = element:_get_action() - return self -end - -function ElementState:error(fmt, ...) - self.state:error(fmt, ...) -end - -function ElementState:convert(argument, index) - local converter = self.element._convert - - if converter then - local ok, err - - if type(converter) == "function" then - ok, err = converter(argument) - elseif type(converter[index]) == "function" then - ok, err = converter[index](argument) - else - ok = converter[argument] - end - - if ok == nil then - self:error(err and "%s" or "malformed argument '%s'", err or argument) - end - - argument = ok - end - - return argument -end - -function ElementState:default(mode) - return self.element._defmode:find(mode) and self.element._default -end - -local function bound(noun, min, max, is_max) - local res = "" - - if min ~= max then - res = "at " .. (is_max and "most" or "least") .. " " - end - - local number = is_max and max or min - return res .. tostring(number) .. " " .. noun .. (number == 1 and "" or "s") -end - -function ElementState:set_name(alias) - self.name = ("%s '%s'"):format(alias and "option" or "argument", alias or self.element._name) -end - -function ElementState:invoke() - self.open = true - self.overwrite = false - - if self.invocations >= self.element._maxcount then - if self.element._overwrite then - self.overwrite = true - else - local num_times_repr = bound("time", self.element._mincount, self.element._maxcount, true) - self:error("%s must be used %s", self.name, num_times_repr) - end - else - self.invocations = self.invocations + 1 - end - - self.args = {} - - if self.element._maxargs <= 0 then - self:close() - end - - return self.open -end - -function ElementState:pass(argument) - argument = self:convert(argument, #self.args + 1) - table.insert(self.args, argument) - - if #self.args >= self.element._maxargs then - self:close() - end - - return self.open -end - -function ElementState:complete_invocation() - while #self.args < self.element._minargs do - self:pass(self.element._default) - end -end - -function ElementState:close() - if self.open then - self.open = false - - if #self.args < self.element._minargs then - if self:default("a") then - self:complete_invocation() - else - if #self.args == 0 then - if getmetatable(self.element) == Argument then - self:error("missing %s", self.name) - elseif self.element._maxargs == 1 then - self:error("%s requires an argument", self.name) - end - end - - self:error("%s requires %s", self.name, bound("argument", self.element._minargs, self.element._maxargs)) - end - end - - local args - - if self.element._maxargs == 0 then - args = self.args[1] - elseif self.element._maxargs == 1 then - if self.element._minargs == 0 and self.element._mincount ~= self.element._maxcount then - args = self.args - else - args = self.args[1] - end - else - args = self.args - end - - self.action(self.result, self.target, args, self.overwrite) - end -end - -local ParseState = class({ - result = {}, - options = {}, - arguments = {}, - argument_i = 1, - element_to_mutexes = {}, - mutex_to_element_state = {}, - command_actions = {} -}) - -function ParseState:__call(parser, error_handler) - self.parser = parser - self.error_handler = error_handler - self.charset = parser:_update_charset() - self:switch(parser) - return self -end - -function ParseState:error(fmt, ...) - self.error_handler(self.parser, fmt:format(...)) -end - -function ParseState:switch(parser) - self.parser = parser - - if parser._action then - table.insert(self.command_actions, {action = parser._action, name = parser._name}) - end - - for _, option in ipairs(parser._options) do - option = ElementState(self, option) - table.insert(self.options, option) - - for _, alias in ipairs(option.element._aliases) do - self.options[alias] = option - end - end - - for _, mutex in ipairs(parser._mutexes) do - for _, element in ipairs(mutex) do - if not self.element_to_mutexes[element] then - self.element_to_mutexes[element] = {} - end - - table.insert(self.element_to_mutexes[element], mutex) - end - end - - for _, argument in ipairs(parser._arguments) do - argument = ElementState(self, argument) - table.insert(self.arguments, argument) - argument:set_name() - argument:invoke() - end - - self.handle_options = parser._handle_options - self.argument = self.arguments[self.argument_i] - self.commands = parser._commands - - for _, command in ipairs(self.commands) do - for _, alias in ipairs(command._aliases) do - self.commands[alias] = command - end - end -end - -function ParseState:get_option(name) - local option = self.options[name] - - if not option then - self:error("unknown option '%s'%s", name, get_tip(self.options, name)) - else - return option - end -end - -function ParseState:get_command(name) - local command = self.commands[name] - - if not command then - if #self.commands > 0 then - self:error("unknown command '%s'%s", name, get_tip(self.commands, name)) - else - self:error("too many arguments") - end - else - return command - end -end - -function ParseState:check_mutexes(element_state) - if self.element_to_mutexes[element_state.element] then - for _, mutex in ipairs(self.element_to_mutexes[element_state.element]) do - local used_element_state = self.mutex_to_element_state[mutex] - - if used_element_state and used_element_state ~= element_state then - self:error("%s can not be used together with %s", element_state.name, used_element_state.name) - else - self.mutex_to_element_state[mutex] = element_state - end - end - end -end - -function ParseState:invoke(option, name) - self:close() - option:set_name(name) - self:check_mutexes(option, name) - - if option:invoke() then - self.option = option - end -end - -function ParseState:pass(arg) - if self.option then - if not self.option:pass(arg) then - self.option = nil - end - elseif self.argument then - self:check_mutexes(self.argument) - - if not self.argument:pass(arg) then - self.argument_i = self.argument_i + 1 - self.argument = self.arguments[self.argument_i] - end - else - local command = self:get_command(arg) - self.result[command._target or command._name] = true - - if self.parser._command_target then - self.result[self.parser._command_target] = command._name - end - - self:switch(command) - end -end - -function ParseState:close() - if self.option then - self.option:close() - self.option = nil - end -end - -function ParseState:finalize() - self:close() - - for i = self.argument_i, #self.arguments do - local argument = self.arguments[i] - if #argument.args == 0 and argument:default("u") then - argument:complete_invocation() - else - argument:close() - end - end - - if self.parser._require_command and #self.commands > 0 then - self:error("a command is required") - end - - for _, option in ipairs(self.options) do - option.name = option.name or ("option '%s'"):format(option.element._name) - - if option.invocations == 0 then - if option:default("u") then - option:invoke() - option:complete_invocation() - option:close() - end - end - - local mincount = option.element._mincount - - if option.invocations < mincount then - if option:default("a") then - while option.invocations < mincount do - option:invoke() - option:close() - end - elseif option.invocations == 0 then - self:error("missing %s", option.name) - else - self:error("%s must be used %s", option.name, bound("time", mincount, option.element._maxcount)) - end - end - end - - for i = #self.command_actions, 1, -1 do - self.command_actions[i].action(self.result, self.command_actions[i].name) - end -end - -function ParseState:parse(args) - for _, arg in ipairs(args) do - local plain = true - - if self.handle_options then - local first = arg:sub(1, 1) - - if self.charset[first] then - if #arg > 1 then - plain = false - - if arg:sub(2, 2) == first then - if #arg == 2 then - if self.options[arg] then - local option = self:get_option(arg) - self:invoke(option, arg) - else - self:close() - end - - self.handle_options = false - else - local equals = arg:find "=" - if equals then - local name = arg:sub(1, equals - 1) - local option = self:get_option(name) - - if option.element._maxargs <= 0 then - self:error("option '%s' does not take arguments", name) - end - - self:invoke(option, name) - self:pass(arg:sub(equals + 1)) - else - local option = self:get_option(arg) - self:invoke(option, arg) - end - end - else - for i = 2, #arg do - local name = first .. arg:sub(i, i) - local option = self:get_option(name) - self:invoke(option, name) - - if i ~= #arg and option.element._maxargs > 0 then - self:pass(arg:sub(i + 1)) - break - end - end - end - end - end - end - - if plain then - self:pass(arg) - end - end - - self:finalize() - return self.result -end - -function Parser:error(msg) - io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(), msg)) - os.exit(1) -end - --- Compatibility with strict.lua and other checkers: -local default_cmdline = rawget(_G, "arg") or {} - -function Parser:_parse(args, error_handler) - return ParseState(self, error_handler):parse(args or default_cmdline) -end - -function Parser:parse(args) - return self:_parse(args, self.error) -end - -local function xpcall_error_handler(err) - return tostring(err) .. "\noriginal " .. debug.traceback("", 2):sub(2) -end - -function Parser:pparse(args) - local parse_error - - local ok, result = xpcall(function() - return self:_parse(args, function(_, err) - parse_error = err - error(err, 0) - end) - end, xpcall_error_handler) - - if ok then - return true, result - elseif not parse_error then - error(result, 0) - else - return false, parse_error - end -end - -local argparse = {} - -argparse.version = "0.6.0" - -setmetatable(argparse, {__call = function(_, ...) - return Parser(default_cmdline[0]):add_help(true)(...) -end}) - -return argparse - -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()