--[[ This moudle allows you to minify gLua code Use: local x = require("glum.lua") local str =" --Here is some code to be minified!\n for a=1,10,2 do\n print(a)\n end " print(x.minify(str)) Dependencies: lua-parser ]] local strreps = { ["\\"] = "\\\\", ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\""] = "\\\"" } local function safe_str (str) local tep = {} --print("--------------Safeing str:" .. str) for c in str:gmatch(".") do if strreps[c] ~= nil then --print(string.format("safeing %s:%s",c,strreps[c])) end tep[#tep + 1] = strreps[c] or c end local output = table.concat(tep) --print("-------------Returning string:" .. output) return output end local parser = dofile("../src/parser.lua") local lpeg = require("lpeg") lpeg.locale(lpeg) local glum = {} --Creates a deep copy of a table local function deepcopy(orig) local orig_type = type(orig) local copy if orig_type == "table" then copy = {} for orig_key, orig_value in next, orig, nil do copy[deepcopy(orig_key)] = deepcopy(orig_value) end setmetatable(copy, deepcopy(getmetatable(orig))) else -- number, string, boolean, etc copy = orig end return copy end --A list of reserved words that cannot be used as variable names local nonames = {"if","for","end","do","local","then","else","elseif","return","goto","function","nil","false","true","repeat","return","break","and","or","not","in","repeat","until","while","continue"} local reservednames = {} for k,v in ipairs(nonames) do reservednames[v] = true end --A function that generates the next valid variable name from the last valid variable name. local varchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY" local function getnextvarname(lname) local place = string.find(lname,"[" .. varchars .. "]") local length = string.len(lname) if place == nil then return string.rep("a", length + 1) else local lbyte = string.byte(lname,place) local newchar = string.char(lbyte + (lbyte < 122 and 1 or -57)) local after = string.sub(lname, place + 1, length) local before = string.rep("a", place-1) local output = before .. newchar .. after while reservednames[output] or _G[output] do output = getnextvarname(output) end return output end end --A debugging function, a replacement for glua PrintTable local function printtable(tbl, tabset) tabset = tabset or 0 for k,v in pairs(tbl) do for i = 0,tabset do io.write("\t") end io.write(k .. ":") if type(v) == "table" then io.write("\n") printtable(v, tabset + 1) else io.write(tostring(v) .. "\n") end end end local syntax = {} local function stringfor(ast,tbl) if syntax[ast.tag] ~= nil then return syntax[ast.tag](ast,tbl) else error("Attempted to use unknown tag type:" .. ast.tag) end end --Abandon all hope, ye who enter here --Refer to the comments at the top of parser.lua for what each function should do. --If willox ever decides to add new language features, they need to be added to BOTH parser.lua and here. syntax = { ["Call"] = function(ast,tbl) local exprname = stringfor(ast[1],tbl) local argnames = {} local cursor = 2 while ast[cursor] ~= nil do argnames[cursor-1] = stringfor(ast[cursor],tbl) cursor = cursor + 1 end local argstring = table.concat(argnames,",") local ostring = table.concat({exprname,"(",argstring,")"}) return ostring end, ["Invoke"] = function(ast,tbl) local func = stringfor(ast[1],tbl) local invargs = {} for k = 3,#ast do invargs[#invargs + 1] = stringfor(ast[k],tbl) end local output = func local inv --A short hand if it's a simple thing if ast[2].tag == "String" and #ast[2][1] < (#func + 2) then inv = ast[2][1] output = output .. ":" .. inv .. "(" else inv = stringfor(ast[2],tbl) output = output .. "[" .. inv .. "](" .. func .. "," end output = output .. table.concat(invargs,",") output = output .. ")" return output end, ["String"] = function(ast,tbl) if tbl.strings[ast[1]] == nil then local fsstr = safe_str(ast[1]) local fstr = string.format("\"%s\"",fsstr) --fstr = safe_str(fstr) --local lstr = string.format("return %q",ast[1]) print("Loading string:" .. fstr) local rstring = loadstring("return " .. fstr) --print("Returning string:") --print(ast[1]) --print("fsstr") --print(fsstr) --print("formated:") --print(string.format("%q",ast[1])) local rstrs = rstring() --assert(rstrs == ast[1],string.format("%q is not equal to %q",rstrs,ast[1])) --print(string.format("%q is equal to %s:%s", rstrs, ast[1],fsstr)) --print("Returning " .. fstr .. " from " .. ast[1] .. " out of " .. fsstr) return fstr --return string.format("%q",ast[1]) --return safe_str(string.format("%q",ast[1])) --return string.format("%q",safe_str(ast[1])) --[[ if #ast[1] < 4 then return "\""..ast[1].."\"" end local nextvar = getnextvarname(tbl.lname) tbl.lname = nextvar tbl.strings[ast[1] ] = nextvar return nextvar ]] end return tbl.strings[ast[1]] end, ["Id"] = function(ast,tbl) if tbl.ids[ast[1]] == nil then return ast[1] end return tbl.ids[ast[1]] end, ["Index"] = function(ast,tbl) local globalvar = stringfor(ast[1],tbl) if ast[2].tag == "String" then return table.concat({globalvar, ".", ast[2][1]}) end return table.concat({globalvar, "[", stringfor(ast[2],tbl), "]"}) end, ["Paren"] = function(ast,tbl) return table.concat({"(" .. stringfor(ast[1],tbl) .. ")"}) end, ["Dots"] = function(ast,tbl) return "..." end, ["Forin"] = function(ast,tbl) local nadd = deepcopy(tbl) local nl = stringfor(ast[1],nadd) local el = stringfor(ast[2],nadd) local code = stringfor(ast[3],nadd) local output = table.concat({" for ", nl, " in ", el, " do ", code, " end "}) return output end, ["NameList"] = function(ast,tbl) local outputtbl = {} for k = 1,#ast do if ast[k].tag ~= "Id" then outputtbl[#outputtbl + 1] = stringfor(ast[k]) else if tbl.ids[ast[k][1]] ~= nil then outputtbl[#outputtbl + 1] = tbl.ids[ast[k][1]] else local newvar = getnextvarname(tbl.lname) tbl.lname = newvar tbl.ids[ast[k][1]] = newvar outputtbl[#outputtbl + 1] = newvar end end end local output = table.concat(outputtbl, ",") return output end, ["ExpList"] = function(ast,tbl) local exprs = {} for k = 1,#ast do exprs[#exprs + 1] = stringfor(ast[k],tbl) end return table.concat(exprs,",") end, ["Nil"] = function(ast,tbl) return "nil" end, ["True"] = function(ast,tbl) return "true" end, ["False"] = function(ast,tbl) return "false" end, ["Return"] = function(ast,tbl) local retargs = {} for k,v in ipairs(ast) do retargs[k] = stringfor(v,tbl) end return " return " .. table.concat(retargs,",") end, ["If"] = function(ast,tbl) local expr1 = stringfor(ast[1],tbl) local block1 = stringfor(ast[2],tbl) local codeblocks = {} codeblocks[#codeblocks + 1] = table.concat({" if ",expr1," then ",block1}) for k = 3,#ast-1,2 do local expr = stringfor(ast[k],tbl) local block = stringfor(ast[k + 1],tbl) codeblocks[#codeblocks + 1] = table.concat({" elseif " , expr , " then " , block}) end if #ast % 2 == 1 then local block = stringfor(ast[#ast],tbl) codeblocks[#codeblocks + 1] = " else " .. block end codeblocks[#codeblocks + 1] = " end " return table.concat(codeblocks) end, ["Fornum"] = function(ast,tbl) local var if ast[1].tag == "Id" then if tbl.ids[ast[1][1]] ~= nil then var = tbl.ids[ast[1][1]] else local newvar = getnextvarname(tbl.lname) tbl.lname = newvar tbl.ids[ast[1][1]] = newvar var = newvar end else var = stringfor(ast[1],tbl) end local start = stringfor(ast[2],tbl) local endnum = stringfor(ast[3],tbl) local incrementer = 1 local code = "" if ast[4].tag ~= "Block" then -- incrementer incrementer = stringfor(ast[4],tbl) code = stringfor(ast[5],tbl) else code = stringfor(ast[4],tbl) end local incstr = incrementer ~= 1 and ("," .. incrementer) or "" tbl[var] = nil return table.concat({" for ",var,"=",start,",",endnum,incstr," do ",code," end "}) end, ["Op"] = function(ast,tbl) local binop = { ["or"] = " or ", ["and"] = " and ", ["ne"] = "~=", ["eq"] = "==", ["le"] = "<=", ["ge"] = ">=", ["lt"] = "<", ["gt"] = ">", ["bor"] = "|", ["bxor"] = "~", ["band"] = "&", ["shl"] = "<<", ["shr"] = ">>", ["concat"] = "..", ["add"] = "+", ["sub"] = "-", ["mul"] = "*", ["div"] = "/", ["mod"] = "%", ["pow"] = "^", } local uniop = { ["len"] = "#", ["not"] = " not ", ["unm"] = "-", ["bnot"] = "~", } local opname = ast[1] if uniop[opname] ~= nil then local rhs = stringfor(ast[2],tbl) return uniop[opname] .. rhs end local lhs = stringfor(ast[2],tbl) local rhs = stringfor(ast[3],tbl) local output = table.concat({lhs,binop[opname],rhs}) return output end, ["Pair"] = function(ast,tbl) local lhs = stringfor(ast[1],tbl) local rhs = stringfor(ast[2],tbl) return table.concat({"[",lhs,"]=",rhs}) end, ["Table"] = function(ast,tbl) local fields = {} for k = 1, #ast do fields[#fields + 1] = stringfor(ast[k],tbl) end local fieldstr = table.concat(fields,",") return table.concat({"{",fieldstr,"}"}) end, ["Number"] = function(ast,tbl) return ast[1] end, ["Local"] = function(ast,tbl) local tblcpy = tbl local lhs,rhs = stringfor(ast[1],tblcpy),nil if ast[2].tag ~= nil then rhs = stringfor(ast[2],tblcpy) end local output = "local " .. lhs if ast[2].tag ~= nil then output = output .. "=" .. rhs .. ";" end return output end, ["VarList"] = function(ast,tbl) local vars = {} for k = 1,#ast do vars[#vars + 1] = stringfor(ast[k],tbl) end return table.concat(vars,",") end, ["Set"] = function(ast,tbl) local lhs = {} local a1 = ast[1].tag ~= nil and ast[1] or ast[1][1] for k = 1,#ast[1] do lhs[#lhs + 1] = stringfor(a1,tbl) end local rhs = {} local a2 = ast[2].tag ~= nil and ast[2] or ast[2][1] for k = 1,#ast[2] do rhs[#rhs + 1] = stringfor(a2,tbl) end local ostring = table.concat(lhs,",") ostring = ostring .. "=" .. table.concat(rhs,",") return ostring .. ";" end, ["Label"] = function(ast,tbl) if tbl.nids[ast[1]] == nil then local nextvar = getnextvarname(tbl.lname) tbl.lname = nextvar tbl.nids[ast[1]] = nextvar end return "::" .. tbl.nids[ast[1]] .. "::" end, ["Goto"] = function(ast,tbl) if tbl.nids[ast[1]] == nil then local nextvar = getnextvarname(tbl.lname) tbl.lname = nextvar tbl.nids[ast[1]] = nextvar end return " goto " .. tbl.nids[ast[1]] end, ["Function"] = function(ast,tbl) local funcargs = ast[1].tag ~= nil and stringfor(ast[1],tbl) or "" local code = stringfor(ast[2],tbl) return table.concat({" function(",funcargs,")",code," end "}) end, ["Localrec"] = function(ast,tbl) local ident if tbl.ids[ast[1][1]] ~= nil then ident = tbl.ids[ast[1][1]] else local newvar = getnextvarname(tbl.lname) tbl.lname = newvar tbl.ids[ast[1][1][1]] = newvar ident = newvar end local argstr = ast[2][1][1].tag ~= nil and stringfor(ast[2][1][1],tbl) or "" local expr = stringfor(ast[2][1][2],tbl) return table.concat({" local function ",ident,"(",argstr,")",expr," end "}) end, ["Continue"] = function(ast,tbl) return " continue " end, ["While"] = function(ast,tbl) local expr = stringfor(ast[1],tbl) local block = stringfor(ast[2],tbl) local output = table.concat({" while " , expr , " do " , block , " end "}) return output end, ["Break"] = function(ast,tbl) return " break " end, ["Block"] = function(ast,oldtbl) local tbl = deepcopy(oldtbl) oldtbl.block = true local codeblocks = {} for k = 1,#ast do codeblocks[#codeblocks + 1] = stringfor(ast[k],tbl) end local code = table.concat(codeblocks) local lhss,rhss = {},{} for k,v in pairs(tbl.strings) do if oldtbl.strings[k] ~= tbl.strings[k] then lhss[#lhss + 1] = v rhss[#rhss + 1] = string.format("%q",k) end end local inits = "" local lhs = " local " .. table.concat(lhss,",") local rhs = table.concat(rhss,",") if string.len(rhs) > 0 then inits = table.concat({lhs, "=", rhs, ";"}) end return inits .. code end, } --Removes extra spaces and duplicated ; from a string local function removespaces(str) local removables = { {"%s*%)%s*","%)"}, --Spaces before or after ) {"%s*%(%s*","%("}, --Spaces before or after ( {"%s*;%s*",";"}, --Spaces before or after ; {"%s*,%s*",","}, --Spaces before or after , {";+",";"}, --Multiple ; in a row {"^%s*",""}, --Spaces at the beginning of the file {"%s*$",""}, --Spaces at the end of the file {"%s+"," "}, --Multiple spaces in a row } --Order is important for k,v in ipairs(removables) do str = string.gsub(str,v[1],v[2]) end return str end --Compress the string, and adds a little decompression code at the top. local function compress(str) end glum.minify = function(str, name) name = name or "anonymous" local ast, error_msg = parser.parse(str, name) if not ast then error(error_msg) end local localvar = { ["strings"] = {}, ["ids"] = {}, ["lname"] = "", ["nids"] = {}, } --printtable(ast) return --[[removespaces(]]stringfor(ast,localvar)--) end return glum