From b38df089cc2c4694c542e97e9b990a6fda65643b Mon Sep 17 00:00:00 2001 From: Alexander Pickering Date: Fri, 21 Aug 2020 12:45:19 -0400 Subject: Add rockspec Add a rockspec for easy packaging --- src/ast_opts.lua | 251 +++++--- src/glum.lua | 1611 ++++++++++++++++++++++++------------------------ src/minify.lua | 13 + src/parser.lua | 1716 ++++++++++++++++++++++++++-------------------------- src/parser_min.lua | 804 ------------------------ src/scope.lua | 148 ++--- src/uglify.lua | 159 +++-- 7 files changed, 2000 insertions(+), 2702 deletions(-) create mode 100644 src/minify.lua delete mode 100644 src/parser_min.lua (limited to 'src') diff --git a/src/ast_opts.lua b/src/ast_opts.lua index de49932..060c0ac 100644 --- a/src/ast_opts.lua +++ b/src/ast_opts.lua @@ -1,100 +1,151 @@ ---[[ - Optimizatoins for abstract syntax trees -]] -local msg = io.write ---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 msg("\t") end - msg(k .. ":") - if type(v) == "table" then - msg("\n") - printtable(v, tabset + 1) - else - msg(tostring(v) .. "\n") - end - end -end - ---A function to see if two ast's are equal-ish (does not compare position) -local function deepcompare(tbl1, tbl2) - if type(tbl1) ~= type(tbl2) then return false end - for k,v in pairs(tbl1) do - print("Checking ", k, " from tbl1") - if k == "pos" then goto cont end - if type(v) == "table" then - print("It is a table! going deeper") - if not deepcompare(v,tbl2[k]) then - return false - end - else - print("Checking ", v , " against ", tbl2[k]) - if v ~= tbl2[k] then - return false - end - end - ::cont:: - end - return true -end - -local opts = {} - ---Optimization 1 ---Folds things with an operator when the fold results in a smaller string -local foldables = { - ["add"] = function(a,b) return a + b end, - ["mul"] = function(a,b) return a * b end, - ["mod"] = function(a,b) return a % b end, - ["sub"] = function(a,b) return a - b end, - --["div"] = function(a,b) return a / b end, division has the chance to give us really long strings! -} -opts[1] = function(ast) - if ast.tag ~= "Op" then return false end - local opname = ast[1] - local func = foldables[opname] - if ast[3] ~= nil and func ~= nil and ast[2].tag == "Number" and ast[3].tag == "Number" then - ast.tag = "Number" - ast[1] = func(ast[2][1],ast[3][1]) - for i = 2,#ast do - ast[i] = nil - end - return true - end - return false -end - ---Optimization 2 ---Find places where we can replace calls with invokes. -opts[2] = function(ast) - if ast.tag == "Call" and ast.pos == 160 then - print("Ast:") - printtable(ast) - print("ast[1][1]") - printtable(ast[1][1]) - print("ast[2]") - printtable(ast[2]) - local dcr = deepcompare(ast[1][1],ast[2]) - print("Deepcompare:",dcr) - --error("stopping") - end - if ast.tag == "Call" and deepcompare(ast[1][1][1], ast[2][1]) then - print("Before correcting for invoke, ast is") - printtable(ast) - for i = 2,#ast[2] do - ast[i] = ast[i+1] - end - ast.tag = "Invoke" - ast[2] = ast[1][2] - ast[1] = ast[1][1] - print("After correcting for invoke, ast is") - printtable(ast) - - --error("Call that should be invoke detected") - return true - end - -end - -return opts +--[[ + Optimizatoins for abstract syntax trees +]] +local msg = io.write + +--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 msg("\t") end + msg(k .. ":") + if type(v) == "table" then + msg("\n") + printtable(v, tabset + 1) + else + msg(tostring(v) .. "\n") + end + end +end + +--A function to see if two tables are equal-ish +local function deepcompare(tbl1, tbl2) + if type(tbl1) ~= type(tbl2) then return false end + for k,v in pairs(tbl1) do + print("Checking ", k, " from tbl1") + if k == "pos" then goto cont end + if type(v) == "table" then + print("It is a table! going deeper") + if not deepcompare(v,tbl2[k]) then + return false + end + else + print("Checking ", v , " against ", tbl2[k]) + if v ~= tbl2[k] then + return false + end + end + ::cont:: + end + return true +end + +--Makes sure 2 things are refrenceing the same index +local function indexcompare(tbl1,tbl2) + if type(tbl1) ~= "table" or type(tbl2) ~= "table" then return false end + --print("indexcompare is checking ",tbl1,tbl2) + --printtable(tbl1) + --print("is the same as") + --printtable(tbl2) + if tbl1.tag == "Id" and tbl2.tag == "Id" then + return tbl1[1] == tbl2[1] + elseif tbl1.tag == "Index" and tbl2.tag == "Index" then + return indexcompare(tbl1[1],tbl2[1]) and indexcompare(tbl1[2],tbl2[2]) + elseif tbl1.tag == "String" and tbl2.tag == "String" then + return tbl1[1] == tbl2[1] + else + return false + end +end + +local opts = {} + +--Optimization 1 +--Folds things with an operator when we have numbers on both sides, and the fold results in a smaller string +local foldables = { + ["add"] = function(a,b) return a + b end, + ["mul"] = function(a,b) return a * b end, + ["mod"] = function(a,b) return a % b end, + ["sub"] = function(a,b) return a - b end, + --["div"] = function(a,b) return a / b end,-- division has the chance to give us really long strings! +} +opts[1] = function(ast) + if ast.tag ~= "Op" then return false end + local opname = ast[1] + local func = foldables[opname] + if ast[3] ~= nil and func ~= nil and ast[2].tag == "Number" and ast[3].tag == "Number" then + ast.tag = "Number" + ast[1] = func(ast[2][1],ast[3][1]) + for i = 2,#ast do + ast[i] = nil + end + return true + end + return false +end + +--Optimization 2 +--Find places where we can replace calls with invokes. +--Lua provides syntax sugar where +-- table.method(table,arg1,arg2,...) +--is the same as +-- table:method(arg1,arg2,...) +opts[2] = function(ast) + --[[for debugging + if ast.tag == "Call" then + --print("Ast:") + --printtable(ast) + --print("ast[1][1]") + --printtable(ast[1][1]) + --print("ast[2]") + --printtable(ast[2]) + --local dcr = indexcompare(ast[1][1],ast[2]) + --print("indexcompare:",dcr) + --error("stopping") + end + ]] + + --The ands are to make sure we short circut before indexing a nil + if ast.tag == "Call" and ast[1][1] and ast[2] and indexcompare(ast[1][1][1], ast[2][1]) then + --print("Before correcting for invoke, ast is") + --printtable(ast) + for i = 2,#ast[2] do + ast[i] = ast[i + 1] + end + ast.tag = "Invoke" + ast[2] = ast[1][2] + ast[1] = ast[1][1] + + return true + end + return false +end + +--Find places where we have multiple variables declared, but not initalized +--and turn it into a 1-liner +--so local a local b local c +--becomes +--local a,b,c +--TODO: This can be done faster and in 1 pass with a little extra complexity +opts[3] = function(ast) + if ast.tag == "Block" then + for i = 1,#ast do + if ast[i].tag == "Local" and next(ast[i][2]) == nil then + local cursor = ast[i] + local r + i = i + 1 + while ast[i].tag == "Local" and next(ast[i][2]) == nil do + table.insert(cursor[1],ast[i][1][1]) + table.remove(ast,i) + i = i + 1 + r = true + end + if r then return true end + end + end + return false + end + return false +end + +return opts diff --git a/src/glum.lua b/src/glum.lua index bcdd5e6..446be40 100644 --- a/src/glum.lua +++ b/src/glum.lua @@ -1,785 +1,826 @@ ---[[ -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 - lpeg -]] - ---Abandon all hope, ye who enter here ---Refer to the comments at the top of parser.lua for what each function should do. ---If anyone ever decides to add new language features, they need to be added to BOTH parser.lua and here. ---Someone should rewrite this much cleaner. - -local parser -local msg -local optimi -if include ~= nil then - parser = include("./parser.lua") - msg = Msg - optimi = include("./ast_opts.lua") -else - parser = dofile("../src/parser.lua") - msg = io.write - optimi = dofile("../src/ast_opts.lua") - print("Just did optimi, it is",type(optimi)) -end -local lpeg = require("lpeg") -lpeg.locale(lpeg) - -local glum = {} - ---Checks if two tables are the same -local function deepcompare(tbl1, tbl2) - for k,v in pairs(tbl1) do - if type(v) == "table" then - if not deepcompare(v,tbl2[k]) then - return false - end - else - if v ~= tbl2[k] then - return false - end - end - end -end - ---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 - ---Is the last character in the built-so-far string a character? ---Used to know if we should insert a space after it -local last = false - ---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 msg("\t") end - msg(k .. ":") - if type(v) == "table" then - msg("\n") - printtable(v, tabset + 1) - else - msg(tostring(v) .. "\n") - end - end -end - -local stringreps -local function findstrings(ast) - if type(ast) ~= "table" then return end - if ast and ast.tag == "String" then - local lnum = stringreps[ast[1]] - stringreps[ast[1]] = lnum and lnum + 1 or 1 - return - end - for k = 1, #ast do - findstrings(ast[k]) - end -end - -local getstringreps = function(ast) - stringreps = {} - findstrings(ast) - - local function bytessaved(str,instances,bytereplacement) - local spacetaken = string.len(str) * instances - local minspacetaken = instances * bytereplacement - return spacetaken - minspacetaken - end - - local sstbl = {} - for k,v in pairs(stringreps) do table.insert(sstbl,{k,bytessaved(k,v,2)}) end - table.sort(sstbl,function(a,b) return a[2] > b[2] end) - - return sstbl -end - -local function astwalker(ast) - for i,j in pairs(optimi) do - local new = j(ast) - changed = changed or new - end - for k,v in pairs(ast) do - if type(v) == "table" then - astwalker(v) - end - end - for i,j in pairs(optimi) do - local new = j(ast) - changed = changed or new - end -end - -local syntax = {} - -local function stringfor(ast,tbl) - if syntax[ast.tag] ~= nil then - return syntax[ast.tag](ast,tbl) - else - print("Valid tags are:") - for k,v in pairs(syntax) do - print(k) - end - printtable(ast) - error("Attempted to use unknown tag type:" .. ast.tag) - end -end - -syntax = { - ["Call"] = function(ast,tbl) - local exprname = stringfor(ast[1],tbl) - last = false - local argnames = {} - local cursor = 2 - while ast[cursor] ~= nil do - argnames[cursor-1] = stringfor(ast[cursor],tbl) - cursor = cursor + 1 - last = false - end - local argstring = table.concat(argnames,",") - local ostring = table.concat({exprname,"(",argstring,")"}) - last = false - return ostring - end, - ["Invoke"] = function(ast,tbl) - local ret = {} - ret[1] = stringfor(ast[1],tbl) -- The table - last = false - --If it's a . then use oo notation - if ast[2].tag == "String" and ast[2][1]:find(" ") == nil and tbl.strings[ast[2][1]] == nil then - ret[2] = ":" - ret[3] = ast[2][1] - ret[4] = "(" - elseif tbl.strings[ast[2][1]] ~= nil then - ret[2] = "[" - ret[3] = tbl.strings[ast[2][1]] - ret[4] = "](" - else - last = false - ret[2] = "[" - ret[3] = stringfor(ast[2],tbl) - ret[4] = "](" - ret[5] = ast[1] - ret[6] = "," - end - last = false - local args = {} - for k = 3,#ast do - args[#args + 1] = stringfor(ast[k],tbl) - last = false - end - ret[#ret+1] = table.concat(args,",") - ret[#ret+1] = ")" - last = false - return table.concat(ret) - end, - ["String"] = function(ast,tbl) - local sop,eop = "\"","\"" - --print("looking for",ast[1],"in") - --printtable(tbl.strings) - if tbl.strings[ast[1]] then - --print("Found it, it is", tbl.strings[ast[1]]) - return tbl.strings[ast[1]] - end - if tbl.strings[ast[1]] == nil then - if string.find(ast[1],"\"") then - sop = "[[" - eop = "]]" - end - return table.concat({sop,ast[1],eop}) - end - --print("Returning non-catated string") - last = false - return tbl.strings[ast[1]] - end, - ["Id"] = function(ast,tbl) - local ret - if last then ret = " " else ret = "" end - if tbl.ids[ast[1]] == nil then - ret = ret .. ast[1] - last = true - return ret - end - ret = ret .. tbl.ids[ast[1]] - last = true - return ret - end, - ["Index"] = function(ast,tbl) - local globalvar = stringfor(ast[1],tbl) - if ast[2].tag == "String" and tbl.strings[ast[2][1]] == nil then - last = true - return table.concat({globalvar, ".", ast[2][1]}) - end - last = false - local ret = table.concat({globalvar, "[", stringfor(ast[2],tbl), "]"}) - last = false - return ret - end, - ["Paren"] = function(ast,tbl) - last = false - return table.concat({"(",stringfor(ast[1],tbl),")"}) - end, - ["Dots"] = function(ast,tbl) - last = false - return "..." - end, - ["Forin"] = function(ast,tbl) - local codetbl = {} - if last then codetbl[1] = " for" else codetbl[1] = "for" end - last = true - local nadd = deepcopy(tbl) - local nl = stringfor(ast[1],nadd) - codetbl[2] = nl - if last then codetbl[3] = " in" else codetbl[3] = "in" end - last = true - nadd.numlocals = nadd.numlocals + #ast[1] - local el = stringfor(ast[2],nadd) - codetbl[4] = el - if last then codetbl[5] = " do" else codetbl[5] = "do" end - last = true - local code = stringfor(ast[3],nadd) - codetbl[6] = code - if last then codetbl[7] = " end" else codetbl[7] = "end" end - last = true - local output = table.concat(codetbl) - tbl.numlocals = nadd.numlocals - return output - end, - ["NameList"] = function(ast,tbl) - local outputtbl = {} - local bef - if last then bef = " " else bef = "" end - 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 - print("Found id in id table") - 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 - print("Not found, output is ", newvar) - end - last = false - end - end - local output = bef .. table.concat(outputtbl, ",") - print("Output from namelist is ", output) - last = true - return output - end, - ["ExpList"] = function(ast,tbl) - local exprs = {} - local bef - if last then bef = " " else bef = "" end - for k = 1,#ast do - exprs[k] = stringfor(ast[k],tbl) - last = false - end - last = true - return table.concat(exprs,",") - end, - ["Nil"] = function(ast,tbl) - local ret - if last then ret = " nil" else ret = "nil" end - last = true - return ret - end, - ["True"] = function(ast,tbl) - local ret = "!!1" - last = true - return ret - end, - ["False"] = function(ast,tbl) - local ret = "!1" - last = true - return ret - end, - ["Return"] = function(ast,tbl) - local retargs = {} - local ccat - if last then - ccat = " return" - else - ccat = "return" - end - last = true - for k,v in ipairs(ast) do - retargs[k] = stringfor(v,tbl) - last = false - end - last = true - return ccat .. table.concat(retargs,",") - end, - ["Do"] = function(ast,tbl) - local ntbl = deepcopy(tbl) - local argparts = {} - if last then argparts[1] = " do" else argparts[1] = "do" end - last = true - local allst = {} - for k = 1,#ast do - allst[k] = stringfor(ast[k],ntbl) - end - local code = table.concat(allst,";") - argparts[2] = code - tbl.numlocals = ntbl.numlocals - if last then argparts[3] = " end" else argparts[3] = "end" end - last = true - return table.concat(argparts) - end, - ["If"] = function(ast,tbl) - local exparts = {} - if last then exparts[1] = " if" else exparts[1] = "if" end - last = true - local expr1 = stringfor(ast[1],tbl) - exparts[2] = expr1 - if last then exparts[3] = " then" else exparts[3] = "then" end - last = true - local block1 = stringfor(ast[2],tbl) - exparts[4] = block1 - local codeblocks = {} - codeblocks[#codeblocks + 1] = table.concat(exparts) - - for k = 3,#ast-1,2 do - local efargs = {} - if last then efargs[1] = " elseif" else efargs[1] = "elseif" end - last = true - local expr = stringfor(ast[k],tbl) - efargs[2] = expr - if last then efargs[3] = " then" else efargs[3] = "then" end - last = true - local block = stringfor(ast[k + 1],tbl) - codeblocks[#codeblocks + 1] = table.concat(efargs) - end - - if #ast % 2 == 1 then - local block = stringfor(ast[#ast],tbl) - if block ~= "" then --If for some reason there's an empty else block, forget about it. - if last then - codeblocks[#codeblocks + 1] = " else" .. block - else - codeblocks[#codeblocks + 1] = "else" .. block - end - end - end - local estr - if last then estr = " end" else estr = "end" end - codeblocks[#codeblocks + 1] = estr - last = true - return table.concat(codeblocks) - end, - ["Fornum"] = function(ast,tbl) - local spargs = {} - if last then spargs[1] = " for" else spargs[1] = "for" end - last = true - local var - assert(ast[1].tag == "Id","Oh no, I was expecting an ID!") - 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 - spargs[2] = var - spargs[3] = "=" - last = false - local start = stringfor(ast[2],tbl) - spargs[4] = start - spargs[5] = "," - last = false - local endnum = stringfor(ast[3],tbl) - spargs[6] = endnum - local incrementer = 1 - local code = "" - spargs[7] = "" - if ast[4].tag ~= "Block" then -- incrementer - last = false - incrementer = stringfor(ast[4],tbl) - if incrementer ~= 1 then - spargs[7] = "," .. incrementer - else - last = true - end - if last then spargs[8] = " do" else spargs[8] = "do" end - last = true - code = stringfor(ast[5],tbl) - spargs[9] = code - if last then spargs[10] = " end" else spargs[10] = "end" end - last = true - else - if last then spargs[8] = " do" else spargs[8] = "do" end - last = true - code = stringfor(ast[4],tbl) - spargs[9] = code - if last then spargs[10] = " end" else spargs[10] = "end" end - last = true - end - local incstr = incrementer ~= 1 and ("," .. incrementer) or "" - tbl[var] = nil - tbl.numlocals = tbl.numlocals + 1 - --print("Found 1 locals as Fornum") - return table.concat(spargs) - end, - ["Op"] = function(ast,tbl) - --NOTE: Bitwise operators << and >> are not supported in LuaJIT (lua 5.1) and were introduced in lua 5.3, if the operators are ever supported, stuff should just work. - 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 - --Some special case where the parser messes up, fix it here. - --It translates ~= into not ==, but the order of operations makes it so == is evaluated first, and not second. - local bef - if opname == "not" and ast[2]["tag"] == "Op" and ast[2][1] == "eq" then - ast[2][1] = "ne" - local ret = stringfor(ast[2],tbl) - return ret - end - if last then bef = " " else bef = "" end - local rhs = stringfor(ast[2],tbl) - return bef .. uniop[opname] .. rhs - end - local sargs = {} - local lhs = stringfor(ast[2],tbl) - sargs[1] = lhs - if last and (opname == "or" or opname == "and") then - sargs[2] = " " - last = true - else - sargs[2] = "" - last = false - end - sargs[3] = binop[opname] - local rhs = stringfor(ast[3],tbl) - sargs[4] = rhs - local output = table.concat(sargs) - 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 = {} - last = false - for k = 1, #ast do - fields[#fields + 1] = stringfor(ast[k],tbl) - last = false - end - local fieldstr = table.concat(fields,",") - last = false - return table.concat({"{",fieldstr,"}"}) - end, - ["Number"] = function(ast,tbl) - local ret - if last then ret = " " .. ast[1] else ret = ast[1] end - last = true - return ret - end, - ["Local"] = function(ast,tbl) - local bef - if last then bef = " " else bef = "" end - last = true - local lhs,rhs = stringfor(ast[1],tbl),nil - tbl.numlocals = tbl.numlocals + #ast[1] - --print("Found",#ast[1],"locals as Local") - local output = bef .. "local" .. lhs - if ast[2].tag ~= nil then - last = false - rhs = stringfor(ast[2],tbl) - 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) - last = false - end - last = true - 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) - last = false - end - local rhs = {} - local a2 = ast[2].tag ~= nil and ast[2] or ast[2][1] - for k = 1,#ast[2] do - last = false - 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 - last = false - 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 - last = true - return (last and " " or "") .. "goto " .. tbl.nids[ast[1]] - end, - ["Function"] = function(ast,tbl) - --Sometimes the parser fucks up, correct it here - if ast[1][1] ~= nil and ast[1][1].tag == nil then - ast[1] = ast[1][1] - error("Detected parser fuckup") - end - --end of parser-fuckup-fix code - local funcstr - if last then funcstr = " function(" else funcstr = "function(" end - last = false - local funcargs = ast[1].tag ~= nil and stringfor(ast[1],tbl) or "" - last = false - local code = stringfor(ast[2],tbl) - local endstr - if last then endstr = " end" else endstr = "end" end - last = true - return table.concat({funcstr,funcargs,")",code,endstr}) - end, - ["Localrec"] = function(ast,tbl) - local ident = ast[1][1] - local args = ast[2][1][1] - local func = ast[2][1][2] - local bf = {} - if last then bf[1] = " local" else bf[1] = "local" end - bf[2] = stringfor(ident,tbl) --ident - bf[3] = " function" - bf[4] = "(" - last = false - bf[5] = stringfor(args,tbl) --args - bf[6] = ")" - last = false - bf[7] = stringfor(func,tbl) -- function - if last then bf[8] = " end" else bf[8] = "end" end - last = true - return table.concat(bf) - - --[==[ - --Sometimes the parser fucks up, correct it here - print("in localrec, ast is") - printtable(ast) - if ast[1][1] ~= nil and ast[1].tag == nil then - ast[1] = ast[1][1] - --error("Detected parser fuckup") - print("after fixing fuckup, ast was") - printtable(ast) - else - print("ast[1][1] is",ast[1][1]) - printtable(ast[1][1]) - end - --end of parser-fuckup-fix code - local ident = stringfor(ast[1],tbl) - --[=[ - 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 locfuncstr - if last then locfuncstr = " local function " else locfuncstr = "local function " end - last = false - local argstr = ast[2][1][1].tag ~= nil and stringfor(ast[2][1][1],tbl) or "" - last = false - local expr = stringfor(ast[2][1][2],tbl) - local endstr - if last then endstr = " end" else endstr = "end" end - last = true - tbl.numlocals = tbl.numlocals + 1 - print(string.format("At localrec, locfuncstr:%q ident:%q argstr:%q expr:%q endstr:%q last:%q",locfuncstr,ident,argstr,expr,endstr,tostring(last))) - --print("Found 1 local as Localrec") - return table.concat({locfuncstr,ident,"(",argstr,")",expr,endstr}) - ]==] - end, - ["Continue"] = function(ast,tbl) - local ret - if last then ret = " continue" else ret = "continue" end - last = true - return ret - end, - ["While"] = function(ast,tbl) - local whilestr - if last then whilestr = " while" else whilestr = "while" end - last = true - local expr = stringfor(ast[1],tbl) - local dostr - if last then dostr = " do" else dostr = "do" end - last = true - local block = stringfor(ast[2],tbl) - local endstr - if last then endstr = " end" else endstr = "end" end - last = true - local output = table.concat({whilestr, expr , dostr, block , endstr}) - return output - end, - ["Break"] = function(ast,tbl) - local ret - if last then ret = " break" else ret = "break" end - last = true - return ret - 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) - oldtbl.numlocals = tbl.numlocals - return code - 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) - return nil - end - astwalker(ast) - print("After astwalker, ast is") - printtable(ast) - print("Finding string reps") - local strreps = getstringreps(ast) - printtable(strreps) - - local olocalvar = { - ["numlocals"] = 0, - ["strings"] = {}, - ["ids"] = {}, - ["lname"] = "", - ["nids"] = {}, - } - - --printtable(ast) - local lvt = deepcopy(olocalvar) - local numstrreplaced = 0 - local maxlocals = lvt.numlocals - while - (numstrreplaced + maxlocals < 200) and --We have some locals left - (numstrreplaced < #strreps) and --We have more strings to replace - ((strreps[numstrreplaced + 1][2] * #strreps[numstrreplaced + 1][1]) > (strreps[numstrreplaced + 1][2] * 4 + 6)) do --Replaceing this string will at least cover the cost of "local " and [] - numstrreplaced = numstrreplaced + 1 - local nvar = getnextvarname(olocalvar.lname) - olocalvar.strings[strreps[numstrreplaced][1]] = nvar - olocalvar.lname = nvar - end - - local lhss,rhss = {},{} - for k,v in pairs(olocalvar.strings) do - lhss[#lhss + 1] = v - rhss[#rhss + 1] = string.format("%q",k) - end - local inits = "" - --print("lhss is") - --printtable(lhss) - local lhs = " local " .. table.concat(lhss,",") - local rhs = table.concat(rhss,",") - if string.len(rhs) > 0 then - inits = table.concat({lhs, "=", rhs, ";"}) - end - - --print("Before doing stringfor for the second time, olocalvar is") - --printtable(olocalvar) - - return inits .. stringfor(ast,olocalvar) -end - -return glum +--[[ +This moudle allows you to minify gLua code + Use: + local x = require("glum") + 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[,name])) + Dependencies: + lua-parser + lpeg +]] + +--Abandon all hope, ye who enter here +--Refer to the comments at the top of parser.lua for what each function should do. +--If anyone ever decides to add new language features, they need to be added to BOTH parser.lua and here. +--Someone should rewrite this much cleaner. + +local parser +local msg +local optimi +parser = require("lua-parser.parser") +msg = io.write +optimi = require("glum.ast_opts") +local lpeg = require("lpeg") +lpeg.locale(lpeg) + +local glum = {} + +--Checks if two tables are the same +local function deepcompare(tbl1, tbl2) + for k,v in pairs(tbl1) do + if type(v) == "table" then + if not deepcompare(v,tbl2[k]) then + return false + end + else + if v ~= tbl2[k] then + return false + end + end + end +end + +--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 + +--Is the last character in the built-so-far string a character? +--Used to know if we should insert a space after it +local last = true --we can start with no space + +--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 msg("\t") end + msg(k .. ":") + if type(v) == "table" then + msg("\n") + printtable(v, tabset + 1) + else + msg(tostring(v) .. "\n") + end + end +end + +local stringreps +local function findstrings(ast) + if type(ast) ~= "table" then return end + if ast and ast.tag == "String" then + local lnum = stringreps[ast[1]] + stringreps[ast[1]] = lnum and lnum + 1 or 1 + return + end + for k = 1, #ast do + findstrings(ast[k]) + end +end + +local getstringreps = function(ast) + stringreps = {} + findstrings(ast) + + local function bytessaved(str,instances,bytereplacement) + local spacetaken = (string.len(str) + 2) * instances + local minspacetaken = (instances * bytereplacement) + string.len(str) + 2 + return spacetaken - minspacetaken + end + + local sstbl = {} + for k,v in pairs(stringreps) do + table.insert(sstbl,{k,bytessaved(k,v,2)}) + end + table.sort(sstbl,function(a,b) return a[2] > b[2] end) + return sstbl +end + +local function astwalker(ast) + --print("ast walker looking at") + --printtable(ast) + local changed + repeat + changed = false + for i,j in pairs(optimi) do + local new = j(ast) + changed = changed or new + end + for k,v in pairs(ast) do + if type(v) == "table" then + astwalker(v) + end + end + for i,j in pairs(optimi) do + local new = j(ast) + changed = changed or new + end + until changed == false +end + +local syntax = {} + +local function stringfor(ast,tbl) + if syntax[ast.tag] ~= nil then + local r = syntax[ast.tag](ast,tbl) + assert(type(r) == "string", "Stringfor did not return a string! returned a " .. type(r) .. " for tag:" .. ast.tag) + return r + elseif ast.tag == nil and ast[1] and ast[1].tag ~= nil then + --TODO: Is this a bug in the parser? + --sometimes parts of the ast will be 1 extra level deep + --for seemingly no reason + return stringfor(ast[1],tbl) + else + print("Valid tags are:") + for k,v in pairs(syntax) do + print(k) + end + print("Tried to get stringfor on:\n----------------") + printtable(ast) + print("----------------") + error("Attempted to use unknown tag type:" .. tostring(ast.tag)) + end +end + +syntax = { + ["Call"] = function(ast,tbl) + local exprname = stringfor(ast[1],tbl) + last = false + local argnames = {} + local cursor = 2 + while ast[cursor] ~= nil do + argnames[cursor-1] = stringfor(ast[cursor],tbl) + cursor = cursor + 1 + last = false + end + local argstring = table.concat(argnames,",") + local ostring = table.concat({exprname,"(",argstring,")"}) + last = false + return ostring + end, + ["Invoke"] = function(ast,tbl) + local ret = {} + ret[1] = stringfor(ast[1],tbl) -- The table + last = false + --If it's a . then use oo notation + if ast[2].tag == "String" and ast[2][1]:find(" ") == nil and tbl.strings[ast[2][1]] == nil then + ret[2] = ":" + ret[3] = ast[2][1] + ret[4] = "(" + elseif tbl.strings[ast[2][1]] ~= nil then + ret[2] = "[" + ret[3] = tbl.strings[ast[2][1]] + ret[4] = "](" + else + last = false + ret[2] = "[" + ret[3] = stringfor(ast[2],tbl) + ret[4] = "](" + ret[5] = stringfor(ast[1],tbl) + ret[6] = "," + end + last = false + local args = {} + for k = 3,#ast do + local nar = stringfor(ast[k],tbl) + args[#args + 1] = nar + last = false + end + ret[#ret + 1] = table.concat(args,",") + ret[#ret + 1] = ")" + last = false + return table.concat(ret) + end, + ["String"] = function(ast,tbl) + local sop,eop = "\"","\"" + --print("looking for",ast[1],"in") + --printtable(tbl.strings) + if tbl.strings[ast[1]] then + --print("Found it, it is", tbl.strings[ast[1]]) + return tbl.strings[ast[1]] + end + if tbl.strings[ast[1]] == nil then + if string.find(ast[1],"\"") then + sop = "[[" + eop = "]]" + end + return table.concat({sop,ast[1],eop}) + end + --print("Returning non-catated string") + last = false + return tbl.strings[ast[1]] + end, + ["Id"] = function(ast,tbl) + local ret + if last then ret = " " else ret = "" end + if tbl.ids[ast[1]] == nil then + ret = ret .. ast[1] + last = true + return ret + end + ret = ret .. tbl.ids[ast[1]] + last = true + return ret + end, + ["Index"] = function(ast,tbl) + local globalvar = stringfor(ast[1],tbl) + if ast[2].tag == "String" and tbl.strings[ast[2][1]] == nil and ast[2][1]:find(" ") == nil then + last = true + return table.concat({globalvar, ".", ast[2][1]}) + end + last = false + local ret = table.concat({globalvar, "[", stringfor(ast[2],tbl), "]"}) + last = false + return ret + end, + ["Paren"] = function(ast,tbl) + last = false + return table.concat({"(",stringfor(ast[1],tbl),")"}) + end, + ["Dots"] = function(ast,tbl) + last = false + return "..." + end, + ["Repeat"] = function(ast,tbl) + local codetbl = {} + if last then codetbl[1] = " repeat" else codetbl[1] = "repeat" end + last = true + local scoped = deepcopy(tbl) + local block = stringfor(ast[1],scoped) + codetbl[2] = block + if last then codetbl[3] = " until" else codetbl[3] = "until" end + local condition = stringfor(ast[2],scoped) + codetbl[4] = condition + local output = table.concat(codetbl) + tbl.numlocals = scoped.numlocals + return output + end, + ["Forin"] = function(ast,tbl) + local codetbl = {} + if last then codetbl[1] = " for" else codetbl[1] = "for" end + last = true + local nadd = deepcopy(tbl) + local nl = stringfor(ast[1],nadd) + codetbl[2] = nl + if last then codetbl[3] = " in" else codetbl[3] = "in" end + last = true + nadd.numlocals = nadd.numlocals + #ast[1] + local el = stringfor(ast[2],nadd) + codetbl[4] = el + if last then codetbl[5] = " do" else codetbl[5] = "do" end + last = true + local code = stringfor(ast[3],nadd) + codetbl[6] = code + if last then codetbl[7] = " end" else codetbl[7] = "end" end + last = true + local output = table.concat(codetbl) + tbl.numlocals = nadd.numlocals + return output + end, + ["NameList"] = function(ast,tbl) + local outputtbl = {} + local bef + if last then bef = " " else bef = "" end + 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 + --print("Found id in id table") + 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 + last = false + end + end + local output = bef .. table.concat(outputtbl, ",") + last = true + return output + end, + ["ExpList"] = function(ast,tbl) + local exprs = {} + -- local bef + -- if last then bef = " " else bef = "" end + for k = 1,#ast do + exprs[k] = stringfor(ast[k],tbl) + last = false + end + last = true + return table.concat(exprs,",") + end, + ["Nil"] = function(ast,tbl) + local ret + if last then ret = " nil" else ret = "nil" end + last = true + return ret + end, + ["True"] = function(ast,tbl) + local ret = "!!1" + last = true + return ret + end, + ["False"] = function(ast,tbl) + local ret = "!1" + last = true + return ret + end, + ["Return"] = function(ast,tbl) + local retargs = {} + local ccat + if last then + ccat = " return" + else + ccat = "return" + end + last = true + for k,v in ipairs(ast) do + retargs[k] = stringfor(v,tbl) + last = false + end + last = true + return ccat .. table.concat(retargs,",") + end, + ["Do"] = function(ast,tbl) + local ntbl = deepcopy(tbl) + local argparts = {} + if last then argparts[1] = " do" else argparts[1] = "do" end + last = true + local allst = {} + for k = 1,#ast do + allst[k] = stringfor(ast[k],ntbl) + end + local code = table.concat(allst,";") + argparts[2] = code + tbl.numlocals = ntbl.numlocals + if last then argparts[3] = " end" else argparts[3] = "end" end + last = true + return table.concat(argparts) + end, + ["If"] = function(ast,tbl) + local exparts = {} + if last then exparts[1] = " if" else exparts[1] = "if" end + last = true + local expr1 = stringfor(ast[1],tbl) + exparts[2] = expr1 + if last then exparts[3] = " then" else exparts[3] = "then" end + last = true + local block1 = stringfor(ast[2],tbl) + exparts[4] = block1 + local codeblocks = {} + codeblocks[#codeblocks + 1] = table.concat(exparts) + + for k = 3,#ast-1,2 do + local efargs = {} + if last then efargs[1] = " elseif" else efargs[1] = "elseif" end + last = true + local expr = stringfor(ast[k],tbl) + efargs[2] = expr + if last then efargs[3] = " then" else efargs[3] = "then" end + last = true + -- local block = stringfor(ast[k + 1],tbl) + codeblocks[#codeblocks + 1] = table.concat(efargs) + end + + if #ast % 2 == 1 then + local block = stringfor(ast[#ast],tbl) + if block ~= "" then --If for some reason there's an empty else block, forget about it. + if last then + codeblocks[#codeblocks + 1] = " else" .. block + else + codeblocks[#codeblocks + 1] = "else" .. block + end + end + end + local estr + if last then estr = " end" else estr = "end" end + codeblocks[#codeblocks + 1] = estr + last = true + return table.concat(codeblocks) + end, + ["Fornum"] = function(ast,tbl) + local spargs = {} + if last then spargs[1] = " for" else spargs[1] = "for" end + last = true + local var + assert(ast[1].tag == "Id","Oh no, I was expecting an ID!") + 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 + spargs[2] = var + spargs[3] = "=" + last = false + local start = stringfor(ast[2],tbl) + spargs[4] = start + spargs[5] = "," + last = false + local endnum = stringfor(ast[3],tbl) + spargs[6] = endnum + local incrementer = 1 + local code = "" + spargs[7] = "" + if ast[4].tag ~= "Block" then -- incrementer + last = false + incrementer = stringfor(ast[4],tbl) + if incrementer ~= 1 then + spargs[7] = "," .. incrementer + else + last = true + end + if last then spargs[8] = " do" else spargs[8] = "do" end + last = true + code = stringfor(ast[5],tbl) + spargs[9] = code + if last then spargs[10] = " end" else spargs[10] = "end" end + last = true + else + if last then spargs[8] = " do" else spargs[8] = "do" end + last = true + code = stringfor(ast[4],tbl) + spargs[9] = code + if last then spargs[10] = " end" else spargs[10] = "end" end + last = true + end + -- local incstr = incrementer ~= 1 and ("," .. incrementer) or "" + tbl[var] = nil + tbl.numlocals = tbl.numlocals + 1 + --print("Found 1 locals as Fornum") + return table.concat(spargs) + end, + ["Op"] = function(ast,tbl) + --NOTE: Bitwise operators << and >> are not supported in LuaJIT (lua 5.1) and were introduced in lua 5.3, if the operators are ever supported, stuff should just work. + 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 + --Some special case where the parser messes up, fix it here. + --It translates ~= into not ==, but the order of operations makes it so == is evaluated first, and not second. + local bef + if opname == "not" and ast[2]["tag"] == "Op" and ast[2][1] == "eq" then + ast[2][1] = "ne" + local ret = stringfor(ast[2],tbl) + return ret + end + if last then bef = " " else bef = "" end + local rhs = stringfor(ast[2],tbl) + return bef .. uniop[opname] .. rhs + end + local sargs = {} + local lhs = stringfor(ast[2],tbl) + sargs[1] = lhs + if opname == "or" or opname == "and" then + if last then + sargs[2] = " " + else + sargs[2] = "" + end + last = true + else + sargs[2] = "" + last = false + end + sargs[3] = binop[opname] + local rhs = stringfor(ast[3],tbl) + sargs[4] = rhs + local output = table.concat(sargs) + return output + end, + ["Pair"] = function(ast,tbl) + if ast[1].tag == "String" and tbl.strings[ast[1][1]] == nil and ast[1][1]:find(" ") == nil then + local lhs = ast[1][1] + last=false + local rhs = stringfor(ast[2],tbl) + return table.concat({lhs,"=",rhs}) + else + local lhs = stringfor(ast[1],tbl) + last=false + local rhs = stringfor(ast[2],tbl) + return table.concat({"[",lhs,"]=",rhs}) + end + end, + ["Table"] = function(ast,tbl) + local fields = {} + last = false + for k = 1, #ast do + fields[#fields + 1] = stringfor(ast[k],tbl) + last = false + end + local fieldstr = table.concat(fields,",") + last = false + return table.concat({"{",fieldstr,"}"}) + end, + ["Number"] = function(ast,tbl) + local ret + if last then ret = " " .. ast[1] else ret = "" .. ast[1] end + last = true + return ret + end, + ["Local"] = function(ast,tbl) + local bef + if last then bef = " " else bef = "" end + last = true + local lhs,rhs = stringfor(ast[1],tbl),nil + tbl.numlocals = tbl.numlocals + #ast[1] + --print("Found",#ast[1],"locals as Local") + local output = bef .. "local" .. lhs + if ast[2].tag ~= nil then + last = false + rhs = stringfor(ast[2],tbl) + 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) + last = false + end + last = true + 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) + last = false + end + local rhs = {} + local a2 = ast[2].tag ~= nil and ast[2] or ast[2][1] + for k = 1,#ast[2] do + last = false + 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 + last = false + 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 + last = true + return (last and " " or "") .. "goto " .. tbl.nids[ast[1]] + end, + ["Function"] = function(ast,tbl) + --Sometimes the parser fucks up, correct it here + if ast[1][1] ~= nil and ast[1][1].tag == nil then + ast[1] = ast[1][1] + error("Detected parser fuckup") + end + --end of parser-fuckup-fix code + local funcstr + if last then funcstr = " function(" else funcstr = "function(" end + last = false + local funcargs = ast[1].tag ~= nil and stringfor(ast[1],tbl) or "" + last = false + local code = stringfor(ast[2],tbl) + local endstr + if last then endstr = " end" else endstr = "end" end + last = true + return table.concat({funcstr,funcargs,")",code,endstr}) + end, + ["Localrec"] = function(ast,tbl) + local ident = ast[1][1] + local args = ast[2][1][1] + local func = ast[2][1][2] + local bf = {} + if last then bf[1] = " local function" else bf[1] = "local function" end + last = true + bf[2] = stringfor(ident,tbl) --ident + bf[3] = "(" + last = false + if #args ~= 0 then + bf[4] = stringfor(args,tbl) --args + else + bf[4] = "" + end + bf[5] = ")" + last = false + bf[6] = stringfor(func,tbl) -- function + if last then bf[7] = " end" else bf[7] = "end" end + last = true + return table.concat(bf) + + --[==[ + --Sometimes the parser fucks up, correct it here + print("in localrec, ast is") + printtable(ast) + if ast[1][1] ~= nil and ast[1].tag == nil then + ast[1] = ast[1][1] + --error("Detected parser fuckup") + print("after fixing fuckup, ast was") + printtable(ast) + else + print("ast[1][1] is",ast[1][1]) + printtable(ast[1][1]) + end + --end of parser-fuckup-fix code + local ident = stringfor(ast[1],tbl) + --[=[ + 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 locfuncstr + if last then locfuncstr = " local function " else locfuncstr = "local function " end + last = false + local argstr = ast[2][1][1].tag ~= nil and stringfor(ast[2][1][1],tbl) or "" + last = false + local expr = stringfor(ast[2][1][2],tbl) + local endstr + if last then endstr = " end" else endstr = "end" end + last = true + tbl.numlocals = tbl.numlocals + 1 + print(string.format("At localrec, locfuncstr:%q ident:%q argstr:%q expr:%q endstr:%q last:%q",locfuncstr,ident,argstr,expr,endstr,tostring(last))) + --print("Found 1 local as Localrec") + return table.concat({locfuncstr,ident,"(",argstr,")",expr,endstr}) + ]==] + end, + ["Continue"] = function(ast,tbl) + local ret + if last then ret = " continue" else ret = "continue" end + last = true + return ret + end, + ["While"] = function(ast,tbl) + local whilestr + if last then whilestr = " while" else whilestr = "while" end + last = true + local expr = stringfor(ast[1],tbl) + local dostr + if last then dostr = " do" else dostr = "do" end + last = true + local block = stringfor(ast[2],tbl) + local endstr + if last then endstr = " end" else endstr = "end" end + last = true + local output = table.concat({whilestr, expr , dostr, block , endstr}) + return output + end, + ["Break"] = function(ast,tbl) + local ret + if last then ret = " break" else ret = "break" end + last = true + return ret + 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) + oldtbl.numlocals = tbl.numlocals + return code + 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) + return nil + end + astwalker(ast) + --print("After astwalker, ast is") + --printtable(ast) + --print("Finding string reps") + local strreps = getstringreps(ast) + --printtable(strreps) + + local olocalvar = { + ["numlocals"] = 0, + ["strings"] = {}, + ["ids"] = {}, + ["lname"] = "", + ["nids"] = {}, + } + + --printtable(ast) + local lvt = deepcopy(olocalvar) + local numstrreplaced = 0 + local maxlocals = lvt.numlocals + local function shouldreplace(strrep) + return strreps[strrep + 1][2] >= 7 + end + while + (numstrreplaced + maxlocals < 200) and --We have some locals left + (numstrreplaced < #strreps) and --We have more strings to replace + shouldreplace(numstrreplaced) do --Replaceing this string will at least cover the cost of "local " and [] + numstrreplaced = numstrreplaced + 1 + local nvar = getnextvarname(olocalvar.lname) + olocalvar.strings[strreps[numstrreplaced][1]] = nvar + olocalvar.lname = nvar + end + + local lhss,rhss = {},{} + for k,v in pairs(olocalvar.strings) do + lhss[#lhss + 1] = v + rhss[#rhss + 1] = string.format("%q",k) + end + local inits = "" + --print("lhss is") + --printtable(lhss) + local lhs = " local " .. table.concat(lhss,",") + local rhs = table.concat(rhss,",") + if string.len(rhs) > 0 then + inits = table.concat({lhs, "=", rhs, ";"}) + end + + --print("Before doing stringfor for the second time, olocalvar is") + --printtable(olocalvar) + + return inits .. stringfor(ast,olocalvar) +end + +return glum diff --git a/src/minify.lua b/src/minify.lua new file mode 100644 index 0000000..6664dac --- /dev/null +++ b/src/minify.lua @@ -0,0 +1,13 @@ +local glum = require("glum") +local glumu = require("glum.uglify") +local filename = ... +local file = assert(io.open(filename,"r")) +local filedata = file:read("*a") +file:close() +local a = glum.minify(filedata,filename) +local b = glumu.uglify(a) +if string.len(a) < string.len(b) then + print(a) +else + print(b) +end diff --git a/src/parser.lua b/src/parser.lua index 56375d6..8194f12 100644 --- a/src/parser.lua +++ b/src/parser.lua @@ -1,858 +1,858 @@ ---[[ -This module implements a parser for Lua 5.2 with LPeg, -and generates an Abstract Syntax Tree in the Metalua format. -For more information about Metalua, please, visit: -https://github.com/fab13n/metalua-parser - -block: { stat* } - -stat: - `Do{ stat* } - | `Set{ {lhs+} {expr+} } -- lhs1, lhs2... = e1, e2... - | `While{ expr block } -- while e do b end - | `Repeat{ block expr } -- repeat b until e - | `If{ (expr block)+ block? } -- if e1 then b1 [elseif e2 then b2] ... [else bn] end - | `Fornum{ ident expr expr expr? block } -- for ident = e, e[, e] do b end - | `Forin{ {ident+} {expr+} block } -- for i1, i2... in e1, e2... do b end - | `Local{ {ident+} {expr+}? } -- local i1, i2... = e1, e2... - | `Localrec{ ident expr } -- only used for 'local function' - | `Goto{ } -- goto str - | `Label{ } -- ::str:: - | `Return{ } -- return e1, e2... - | `Break -- break - | apply - -expr: - `Nil - | `Dots - | `True - | `False - | `Number{ } - | `String{ } - | `Function{ { `Id{ }* `Dots? } block } - | `Table{ ( `Pair{ expr expr } | expr )* } - | `Op{ opid expr expr? } - | `Paren{ expr } -- significant to cut multiple values returns - | apply - | lhs - -apply: - `Call{ expr expr* } - | `Invoke{ expr `String{ } expr* } - -lhs: `Id{ } | `Index{ expr expr } - -opid: 'add' | 'sub' | 'mul' | 'div' | 'idiv' | 'mod' | 'pow' | 'concat' - | 'band' | 'bor' | 'bxor' | 'shl' | 'shr' | 'eq' | 'lt' | 'le' - | 'and' | 'or' | 'not' | 'unm' | 'len' | 'bnot' -]] -local parser = {} -local lpeg = require("lpeg") -local scope -if include ~= nil then - scope = include("./scope.lua") -else - scope = dofile("../src/scope.lua") -end - -lpeg.locale(lpeg) - -local P, S, V = lpeg.P, lpeg.S, lpeg.V -local C, Carg, Cb, Cc = lpeg.C, lpeg.Carg, lpeg.Cb, lpeg.Cc -local Cf, Cg, Cmt, Cp, Ct = lpeg.Cf, lpeg.Cg, lpeg.Cmt, lpeg.Cp, lpeg.Ct -local alpha, digit, alnum = lpeg.alpha, lpeg.digit, lpeg.alnum -local xdigit = lpeg.xdigit -local space = lpeg.space - -local lineno = scope.lineno -local new_scope, end_scope = scope.new_scope, scope.end_scope -local new_function, end_function = scope.new_function, scope.end_function -local begin_loop, end_loop = scope.begin_loop, scope.end_loop -local insideloop = scope.insideloop - ---some stuff for getting glua comments -local BEGIN_COMMENT = lpeg.P("/*") -local END_COMMENT = lpeg.P("*/") -local NOT_BEGIN = (1 - BEGIN_COMMENT)^0 -local NOT_END = (1 - END_COMMENT)^0 -local FULL_COMMENT_CONTENTS = BEGIN_COMMENT * NOT_END * END_COMMENT - --- error message auxiliary functions - --- creates an error message for the input string -local function syntaxerror (errorinfo, pos, msg) - local l, c = lineno(errorinfo.subject, pos) - local error_msg = "%s:%d:%d: syntax error, %s" - return string.format(error_msg, errorinfo.filename, l, c, msg) -end - --- gets the farthest failure position -local function getffp (s, i, t) - return t.ffp or i, t -end - --- gets the table that contains the error information -local function geterrorinfo () - return Cmt(Carg(1), getffp) * (C(V"OneWord") + Cc("EOF")) / - function (t, u) - t.unexpected = u - return t - end -end - --- creates an errror message using the farthest failure position -local function errormsg () - return geterrorinfo() / - function (t) - local p = t.ffp or 1 - local msg = "unexpected '%s', expecting %s" - msg = string.format(msg, t.unexpected, t.expected) - return nil, syntaxerror(t, p, msg) - end -end - --- reports a syntactic error -local function report_error () - return errormsg() -end - --- sets the farthest failure position and the expected tokens -local function setffp (s, i, t, n) - if not t.ffp or i > t.ffp then - t.ffp = i - t.list = {} ; t.list[n] = n - t.expected = "'" .. n .. "'" - elseif i == t.ffp then - if not t.list[n] then - t.list[n] = n - t.expected = "'" .. n .. "', " .. t.expected - end - end - return false -end - -local function updateffp (name) - return Cmt(Carg(1) * Cc(name), setffp) -end - ---Fixes strings -local function fix_str (str) - str = string.gsub(str, "\\a", "\a") - str = string.gsub(str, "\\b", "\b") - str = string.gsub(str, "\\f", "\f") - str = string.gsub(str, "\\n", "\n") - str = string.gsub(str, "\\r", "\r") - str = string.gsub(str, "\\t", "\t") - str = string.gsub(str, "\\v", "\v") - str = string.gsub(str, "\\\n", "\n") - str = string.gsub(str, "\\\r", "\n") - str = string.gsub(str, "\\'", "'") - str = string.gsub(str, '\\"', '"') - str = string.gsub(str, '\\\\', '\\') - return str -end - --- regular combinators and auxiliary functions - -local function token (pat, name) - return pat * V"Skip" + updateffp(name) * P(false) -end - -local function symb (str) - return token (P(str), str) -end - -local function kw (str) - return token (P(str) * -V"idRest", str) -end - -local function taggedCap (tag, pat) - return Ct(Cg(Cp(), "pos") * Cg(Cc(tag), "tag") * pat) -end - -local function unaryop (op, e) - return { tag = "Op", pos = e.pos, [1] = op, [2] = e } -end - -local function binaryop (e1, op, e2) - if not op then - return e1 - elseif op == "add" or - op == "sub" or - op == "mul" or - op == "div" or - op == "idiv" or - op == "mod" or - op == "pow" or - op == "concat" or - op == "band" or - op == "bor" or - op == "bxor" or - op == "shl" or - op == "shr" or - op == "eq" or - op == "lt" or - op == "le" or - op == "and" or - op == "or" then - return { tag = "Op", pos = e1.pos, [1] = op, [2] = e1, [3] = e2 } - elseif op == "ne" then - return unaryop ("not", { tag = "Op", pos = e1.pos, [1] = "eq", [2] = e1, [3] = e2 }) - elseif op == "gt" then - return { tag = "Op", pos = e1.pos, [1] = "lt", [2] = e2, [3] = e1 } - elseif op == "ge" then - return { tag = "Op", pos = e1.pos, [1] = "le", [2] = e2, [3] = e1 } - end -end - -local function chainl (pat, sep, a) - return Cf(pat * Cg(sep * pat)^0, binaryop) + a -end - -local function chainl1 (pat, sep) - return Cf(pat * Cg(sep * pat)^0, binaryop) -end - -local function sepby (pat, sep, tag) - return taggedCap(tag, (pat * (sep * pat)^0)^(-1)) -end - -local function sepby1 (pat, sep, tag) - return taggedCap(tag, pat * (sep * pat)^0) -end - --- grammar - -local G = { V"Lua", - Lua = V"Shebang"^(-1) * V"Skip" * V"Chunk" * -1 + report_error(); - -- parser - Chunk = V"Block"; - StatList = (symb(";") + V"Stat")^0; - Var = V"Id"; - Id = taggedCap("Id", token(V"Name", "Name")); - FunctionDef = kw("function") * V"FuncBody"; - FieldSep = symb(",") + symb(";"); - Field = taggedCap("Pair", (symb("[") * V"Expr" * symb("]") * symb("=") * V"Expr") + - (taggedCap("String", token(V"Name", "Name")) * symb("=") * V"Expr")) + - V"Expr"; - FieldList = (V"Field" * (V"FieldSep" * V"Field")^0 * V"FieldSep"^(-1))^(-1); - Constructor = taggedCap("Table", symb("{") * V"FieldList" * symb("}")); - NameList = sepby1(V"Id", symb(","), "NameList"); - ExpList = sepby1(V"Expr", symb(","), "ExpList"); - FuncArgs = symb("(") * (V"Expr" * (symb(",") * V"Expr")^0)^(-1) * symb(")") + - V"Constructor" + - taggedCap("String", token(V"String", "String")); - Expr = V"SubExpr_1"; - SubExpr_1 = chainl1(V"SubExpr_2", V"OrOp"); - SubExpr_2 = chainl1(V"SubExpr_3", V"AndOp"); - SubExpr_3 = chainl1(V"SubExpr_4", V"RelOp"); - SubExpr_4 = chainl1(V"SubExpr_5", V"BOrOp"); - SubExpr_5 = chainl1(V"SubExpr_6", V"BXorOp"); - SubExpr_6 = chainl1(V"SubExpr_7", V"BAndOp"); - SubExpr_7 = chainl1(V"SubExpr_8", V"ShiftOp"); - SubExpr_8 = V"SubExpr_9" * V"ConOp" * V"SubExpr_8" / binaryop + - V"SubExpr_9"; - SubExpr_9 = chainl1(V"SubExpr_10", V"AddOp"); - SubExpr_10 = chainl1(V"SubExpr_11", V"MulOp"); - SubExpr_11 = V"UnOp" * V"SubExpr_11" / unaryop + - V"SubExpr_12"; - SubExpr_12 = V"SimpleExp" * (V"PowOp" * V"SubExpr_11")^(-1) / binaryop; - SimpleExp = taggedCap("Number", token(V"Number", "Number")) + - taggedCap("String", token(V"String", "String")) + - taggedCap("Nil", kw("nil")) + - taggedCap("False", kw("false")) + - taggedCap("True", kw("true")) + - taggedCap("Dots", symb("...")) + - V"FunctionDef" + - V"Constructor" + - V"SuffixedExp"; - SuffixedExp = Cf(V"PrimaryExp" * ( - taggedCap("DotIndex", symb(".") * taggedCap("String", token(V"Name", "Name"))) + - taggedCap("ArrayIndex", symb("[") * V"Expr" * symb("]")) + - taggedCap("Invoke", Cg(symb(":") * taggedCap("String", token(V"Name", "Name")) * V"FuncArgs")) + - taggedCap("Call", V"FuncArgs") - )^0, function (t1, t2) - if t2 then - if t2.tag == "Call" or t2.tag == "Invoke" then - local t = {tag = t2.tag, pos = t1.pos, [1] = t1} - for k, v in ipairs(t2) do - table.insert(t, v) - end - return t - else - return {tag = "Index", pos = t1.pos, [1] = t1, [2] = t2[1]} - end - end - return t1 - end); - PrimaryExp = V"Var" + - taggedCap("Paren", symb("(") * V"Expr" * symb(")")); - Block = taggedCap("Block", V"StatList" * V"RetStat"^(-1)); - IfStat = taggedCap("If", - kw("if") * V"Expr" * kw("then") * V"Block" * - (kw("elseif") * V"Expr" * kw("then") * V"Block")^0 * - (kw("else") * V"Block")^(-1) * - kw("end")); - WhileStat = taggedCap("While", kw("while") * V"Expr" * - kw("do") * V"Block" * kw("end")); - DoStat = kw("do") * V"Block" * kw("end") / - function (t) - t.tag = "Do" - return t - end; - ForBody = kw("do") * V"Block"; - ForNum = taggedCap("Fornum", - V"Id" * symb("=") * V"Expr" * symb(",") * - V"Expr" * (symb(",") * V"Expr")^(-1) * - V"ForBody"); - ForGen = taggedCap("Forin", V"NameList" * kw("in") * V"ExpList" * V"ForBody"); - ForStat = kw("for") * (V"ForNum" + V"ForGen") * kw("end"); - RepeatStat = taggedCap("Repeat", kw("repeat") * V"Block" * - kw("until") * V"Expr"); - FuncName = Cf(V"Id" * (symb(".") * taggedCap("String", token(V"Name", "Name")))^0, - function (t1, t2) - if t2 then - return {tag = "Index", pos = t1.pos, [1] = t1, [2] = t2} - end - return t1 - end) * (symb(":") * taggedCap("String", token(V"Name", "Name")))^(-1) / - function (t1, t2) - if t2 then - return {tag = "Index", pos = t1.pos, is_method = true, [1] = t1, [2] = t2} - end - return t1 - end; - ParList = V"NameList" * (symb(",") * symb("...") * taggedCap("Dots", Cp()))^(-1) / - function (t, v) - if v then table.insert(t, v) end - return t - end + - symb("...") * taggedCap("Dots", Cp()) / - function (v) - return {v} - end + - P(true) / function () return {} end; - -- Cc({}) generates a strange bug when parsing [[function t:a() end ; function t.a() end]] - -- the bug is to add the parameter self to the second function definition - --FuncBody = taggedCap("Function", symb("(") * (V"ParList" + Cc({})) * symb(")") * V"Block" * kw("end")); - FuncBody = taggedCap("Function", symb("(") * V"ParList" * symb(")") * V"Block" * kw("end")); - FuncStat = taggedCap("Set", kw("function") * V"FuncName" * V"FuncBody") / - function (t) - if t[1].is_method then table.insert(t[2][1], 1, {tag = "Id", [1] = "self"}) end - t[1] = {t[1]} - t[2] = {t[2]} - return t - end; - LocalFunc = taggedCap("Localrec", kw("function") * V"Id" * V"FuncBody") / - function (t) - t[1] = {t[1]} - t[2] = {t[2]} - return t - end; - LocalAssign = taggedCap("Local", V"NameList" * ((symb("=") * V"ExpList") + Ct(Cc()))); - LocalStat = kw("local") * (V"LocalFunc" + V"LocalAssign"); - LabelStat = taggedCap("Label", symb("::") * token(V"Name", "Name") * symb("::")); - BreakStat = taggedCap("Break", kw("break")); - ContinueStat = taggedCap("Continue", kw("continue")); - GoToStat = taggedCap("Goto", kw("goto") * token(V"Name", "Name")); - RetStat = taggedCap("Return", kw("return") * (V"Expr" * (symb(",") * V"Expr")^0)^(-1) * symb(";")^(-1)); - ExprStat = Cmt( - (V"SuffixedExp" * - (Cc(function (...) - local vl = {...} - local el = vl[#vl] - table.remove(vl) - for k, v in ipairs(vl) do - if v.tag == "Id" or v.tag == "Index" then - vl[k] = v - else - -- invalid assignment - return false - end - end - vl.tag = "VarList" - vl.pos = vl[1].pos - return true, {tag = "Set", pos = vl.pos, [1] = vl, [2] = el} - end) * V"Assignment")) - + - (V"SuffixedExp" * - (Cc(function (s) - if s.tag == "Call" or - s.tag == "Invoke" then - return true, s - end - -- invalid statement - return false - end))) - , function (s, i, s1, f, ...) return f(s1, ...) end); - Assignment = ((symb(",") * V"SuffixedExp")^1)^(-1) * symb("=") * V"ExpList"; - Stat = V"IfStat" + V"WhileStat" + V"DoStat" + V"ForStat" + - V"RepeatStat" + V"FuncStat" + V"LocalStat" + V"LabelStat" + - V"BreakStat" + V"GoToStat" + V"ExprStat" + V"ContinueStat"; - -- lexer - Space = space^1; - Equals = P"="^0; - Open = "[" * Cg(V"Equals", "init") * "[" * P"\n"^(-1); - Close = "]" * C(V"Equals") * "]"; - CloseEQ = Cmt(V"Close" * Cb("init"), - function (s, i, a, b) return a == b end); - LongString = V"Open" * C((P(1) - V"CloseEQ")^0) * V"Close" / - function (s, o) return s end; - Comment = P"--" * V"LongString" / function () return end + - P"--" * (P(1) - P"\n")^0 + - P"//" * (P(1) - P"\n")^0 + - C(FULL_COMMENT_CONTENTS) / function() return end; - Skip = (V"Space" + V"Comment")^0; - idStart = alpha + P("_"); - idRest = alnum + P("_"); - Keywords = P("and") + "break" + "do" + "elseif" + "else" + "end" + - "false" + "for" + "function" + "goto" + "if" + "in" + - "local" + "nil" + "not" + "or" + "repeat" + "return" + - "then" + "true" + "until" + "while" + "continue"; - Reserved = V"Keywords" * -V"idRest"; - Identifier = V"idStart" * V"idRest"^0; - Name = -V"Reserved" * C(V"Identifier") * -V"idRest"; - Hex = (P("0x") + P("0X")) * xdigit^1; - Expo = S("eE") * S("+-")^(-1) * digit^1; - Float = (((digit^1 * P(".") * digit^0) + - (P(".") * digit^1)) * V"Expo"^(-1)) + - (digit^1 * V"Expo"); - Int = digit^1; - Number = C(V"Hex" + V"Float" + V"Int") / - function (n) return tonumber(n) end; - ShortString = P'"' * C(((P'\\' * P(1)) + (P(1) - P'"'))^0) * P'"' + - P"'" * C(((P"\\" * P(1)) + (P(1) - P"'"))^0) * P"'"; - String = V"LongString" + (V"ShortString" / function (s) - --print("instead of " .. s .. " i return " .. fix_str(s)) - return s end); - --return fix_str(s) end); - OrOp = kw("or") / "or" + - symb("||") / "or"; - AndOp = kw("and") / "and" + - symb("&&") / "and"; - RelOp = symb("~=") / "ne" + - symb("==") / "eq" + - symb("<=") / "le" + - symb(">=") / "ge" + - symb("<") / "lt" + - symb(">") / "gt" + - symb("!=") / "ne"; - BOrOp = symb("|") / "bor"; - BXorOp = symb("~") / "bxor"; - BAndOp = symb("&") / "band"; - ShiftOp = symb("<<") / "shl" + - symb(">>") / "shr"; - ConOp = symb("..") / "concat"; - AddOp = symb("+") / "add" + - symb("-") / "sub"; - MulOp = symb("*") / "mul" + - --symb("//") / "idiv" + - symb("/") / "div" + - symb("%") / "mod"; - UnOp = kw("not") / "not" + - symb("-") / "unm" + - symb("#") / "len" + - symb("~") / "bnot" + - symb("!") / "not"; - PowOp = symb("^") / "pow"; - Shebang = P"#" * (P(1) - P"\n")^0 * P"\n"; - -- for error reporting - OneWord = V"Name" + V"Number" + V"String" + V"Reserved" + P("...") + P(1); -} - -local function exist_label (env, scope, stm) - local l = stm[1] - for s=scope, 0, -1 do - if env[s]["label"][l] then return true end - end - return false -end - -local function set_label (env, label, pos) - local scope = env.scope - local l = env[scope]["label"][label] - if not l then - env[scope]["label"][label] = { name = label, pos = pos } - return true - else - local msg = "label '%s' already defined at line %d" - local line = lineno(env.errorinfo.subject, l.pos) - msg = string.format(msg, label, line) - return nil, syntaxerror(env.errorinfo, pos, msg) - end -end - -local function set_pending_goto (env, stm) - local scope = env.scope - table.insert(env[scope]["goto"], stm) - return true -end - -local function verify_pending_gotos (env) - for s=env.maxscope, 0, -1 do - for k, v in ipairs(env[s]["goto"]) do - if not exist_label(env, s, v) then - local msg = "no visible label '%s' for " - msg = string.format(msg, v[1]) - return nil, syntaxerror(env.errorinfo, v.pos, msg) - end - end - end - return true -end - -local function set_vararg (env, is_vararg) - env["function"][env.fscope].is_vararg = is_vararg -end - -local traverse_stm, traverse_exp, traverse_var -local traverse_block, traverse_explist, traverse_varlist, traverse_parlist - -function traverse_parlist (env, parlist) - local len = #parlist - local is_vararg = false - if len > 0 and parlist[len].tag == "Dots" then - is_vararg = true - end - set_vararg(env, is_vararg) - return true -end - -local function traverse_function (env, exp) - new_function(env) - new_scope(env) - local status, msg = traverse_parlist(env, exp[1]) - if not status then return status, msg end - status, msg = traverse_block(env, exp[2]) - if not status then return status, msg end - end_scope(env) - end_function(env) - return true -end - -local function traverse_op (env, exp) - local status, msg = traverse_exp(env, exp[2]) - if not status then return status, msg end - if exp[3] then - status, msg = traverse_exp(env, exp[3]) - if not status then return status, msg end - end - return true -end - -local function traverse_paren (env, exp) - local status, msg = traverse_exp(env, exp[1]) - if not status then return status, msg end - return true -end - -local function traverse_table (env, fieldlist) - for k, v in ipairs(fieldlist) do - local tag = v.tag - if tag == "Pair" then - local status, msg = traverse_exp(env, v[1]) - if not status then return status, msg end - status, msg = traverse_exp(env, v[2]) - if not status then return status, msg end - else - local status, msg = traverse_exp(env, v) - if not status then return status, msg end - end - end - return true -end - -local function traverse_vararg (env, exp) - if not env["function"][env.fscope].is_vararg then - local msg = "cannot use '...' outside a vararg function" - return nil, syntaxerror(env.errorinfo, exp.pos, msg) - end - return true -end - -local function traverse_call (env, call) - local status, msg = traverse_exp(env, call[1]) - if not status then return status, msg end - for i=2, #call do - status, msg = traverse_exp(env, call[i]) - if not status then return status, msg end - end - return true -end - -local function traverse_invoke (env, invoke) - local status, msg = traverse_exp(env, invoke[1]) - if not status then return status, msg end - for i=3, #invoke do - status, msg = traverse_exp(env, invoke[i]) - if not status then return status, msg end - end - return true -end - -local function traverse_assignment (env, stm) - local status, msg = traverse_varlist(env, stm[1]) - if not status then return status, msg end - status, msg = traverse_explist(env, stm[2]) - if not status then return status, msg end - return true -end - -local function traverse_break (env, stm) - if not insideloop(env) then - local msg = " not inside a loop" - return nil, syntaxerror(env.errorinfo, stm.pos, msg) - end - return true -end - -local function traverse_continue (env, stm) - if not insideloop(env) then - local msg = " not inside a loop" - return nil, syntaxerror(env.errorinfo, stm.pos, msg) - end - return true -end - -local function traverse_forin (env, stm) - begin_loop(env) - new_scope(env) - local status, msg = traverse_explist(env, stm[2]) - if not status then return status, msg end - status, msg = traverse_block(env, stm[3]) - if not status then return status, msg end - end_scope(env) - end_loop(env) - return true -end - -local function traverse_fornum (env, stm) - local status, msg - begin_loop(env) - new_scope(env) - status, msg = traverse_exp(env, stm[2]) - if not status then return status, msg end - status, msg = traverse_exp(env, stm[3]) - if not status then return status, msg end - if stm[5] then - status, msg = traverse_exp(env, stm[4]) - if not status then return status, msg end - status, msg = traverse_block(env, stm[5]) - if not status then return status, msg end - else - status, msg = traverse_block(env, stm[4]) - if not status then return status, msg end - end - end_scope(env) - end_loop(env) - return true -end - -local function traverse_goto (env, stm) - local status, msg = set_pending_goto(env, stm) - if not status then return status, msg end - return true -end - -local function traverse_if (env, stm) - local len = #stm - if len % 2 == 0 then - for i=1, len, 2 do - local status, msg = traverse_exp(env, stm[i]) - if not status then return status, msg end - status, msg = traverse_block(env, stm[i+1]) - if not status then return status, msg end - end - else - for i=1, len-1, 2 do - local status, msg = traverse_exp(env, stm[i]) - if not status then return status, msg end - status, msg = traverse_block(env, stm[i+1]) - if not status then return status, msg end - end - local status, msg = traverse_block(env, stm[len]) - if not status then return status, msg end - end - return true -end - -local function traverse_label (env, stm) - local status, msg = set_label(env, stm[1], stm.pos) - if not status then return status, msg end - return true -end - -local function traverse_let (env, stm) - local status, msg = traverse_explist(env, stm[2]) - if not status then return status, msg end - return true -end - -local function traverse_letrec (env, stm) - local status, msg = traverse_exp(env, stm[2][1]) - if not status then return status, msg end - return true -end - -local function traverse_repeat (env, stm) - begin_loop(env) - local status, msg = traverse_block(env, stm[1]) - if not status then return status, msg end - status, msg = traverse_exp(env, stm[2]) - if not status then return status, msg end - end_loop(env) - return true -end - -local function traverse_return (env, stm) - local status, msg = traverse_explist(env, stm) - if not status then return status, msg end - return true -end - -local function traverse_while (env, stm) - begin_loop(env) - local status, msg = traverse_exp(env, stm[1]) - if not status then return status, msg end - status, msg = traverse_block(env, stm[2]) - if not status then return status, msg end - end_loop(env) - return true -end - -function traverse_var (env, var) - local tag = var.tag - if tag == "Id" then -- `Id{ } - return true - elseif tag == "Index" then -- `Index{ expr expr } - local status, msg = traverse_exp(env, var[1]) - if not status then return status, msg end - status, msg = traverse_exp(env, var[2]) - if not status then return status, msg end - return true - else - error("expecting a variable, but got a " .. tag) - end -end - -function traverse_varlist (env, varlist) - for k, v in ipairs(varlist) do - local status, msg = traverse_var(env, v) - if not status then return status, msg end - end - return true -end - -function traverse_exp (env, exp) - local tag = exp.tag - if tag == "Nil" or - tag == "True" or - tag == "False" or - tag == "Number" or -- `Number{ } - tag == "String" then -- `String{ } - return true - elseif tag == "Dots" then - return traverse_vararg(env, exp) - elseif tag == "Function" then -- `Function{ { `Id{ }* `Dots? } block } - return traverse_function(env, exp) - elseif tag == "Table" then -- `Table{ ( `Pair{ expr expr } | expr )* } - return traverse_table(env, exp) - elseif tag == "Op" then -- `Op{ opid expr expr? } - return traverse_op(env, exp) - elseif tag == "Paren" then -- `Paren{ expr } - return traverse_paren(env, exp) - elseif tag == "Call" then -- `Call{ expr expr* } - return traverse_call(env, exp) - elseif tag == "Invoke" then -- `Invoke{ expr `String{ expr* } - return traverse_invoke(env, exp) - elseif tag == "Id" or -- `Id{ } - tag == "Index" then -- `Index{ expr expr } - return traverse_var(env, exp) - else - error("expecting an expression, but got a " .. tag) - end -end - -function traverse_explist (env, explist) - for k, v in ipairs(explist) do - local status, msg = traverse_exp(env, v) - if not status then return status, msg end - end - return true -end - -function traverse_stm (env, stm) - local tag = stm.tag - if tag == "Do" then -- `Do{ stat* } - return traverse_block(env, stm) - elseif tag == "Set" then -- `Set{ {lhs+} {expr+} } - return traverse_assignment(env, stm) - elseif tag == "While" then -- `While{ expr block } - return traverse_while(env, stm) - elseif tag == "Repeat" then -- `Repeat{ block expr } - return traverse_repeat(env, stm) - elseif tag == "If" then -- `If{ (expr block)+ block? } - return traverse_if(env, stm) - elseif tag == "Fornum" then -- `Fornum{ ident expr expr expr? block } - return traverse_fornum(env, stm) - elseif tag == "Forin" then -- `Forin{ {ident+} {expr+} block } - return traverse_forin(env, stm) - elseif tag == "Local" then -- `Local{ {ident+} {expr+}? } - return traverse_let(env, stm) - elseif tag == "Localrec" then -- `Localrec{ ident expr } - return traverse_letrec(env, stm) - elseif tag == "Goto" then -- `Goto{ } - return traverse_goto(env, stm) - elseif tag == "Label" then -- `Label{ } - return traverse_label(env, stm) - elseif tag == "Return" then -- `Return{ * } - return traverse_return(env, stm) - elseif tag == "Break" then - return traverse_break(env, stm) - elseif tag == "Continue" then - return traverse_continue(env,stm) - elseif tag == "Call" then -- `Call{ expr expr* } - return traverse_call(env, stm) - elseif tag == "Invoke" then -- `Invoke{ expr `String{ } expr* } - return traverse_invoke(env, stm) - else - error("expecting a statement, but got a " .. tag) - end -end - -function traverse_block (env, block) - local l = {} - new_scope(env) - for k, v in ipairs(block) do - local status, msg = traverse_stm(env, v) - if not status then return status, msg end - end - end_scope(env) - return true -end - - -local function traverse (ast, errorinfo) - assert(type(ast) == "table") - assert(type(errorinfo) == "table") - local env = { errorinfo = errorinfo, ["function"] = {} } - new_function(env) - set_vararg(env, true) - local status, msg = traverse_block(env, ast) - if not status then return status, msg end - end_function(env) - status, msg = verify_pending_gotos(env) - if not status then return status, msg end - return ast -end - -function parser.parse (subject, filename) - local errorinfo = { subject = subject, filename = filename } - lpeg.setmaxstack(1000) - --debug.getregistry()["lpeg-maxstack"] = 1000 - local ast, error_msg = lpeg.match(G, subject, nil, errorinfo) - if not ast then return ast, error_msg end - return traverse(ast, errorinfo) -end - -return parser +--[[ +This module implements a parser for Lua 5.2 with LPeg, +and generates an Abstract Syntax Tree in the Metalua format. +For more information about Metalua, please, visit: +https://github.com/fab13n/metalua-parser + +block: { stat* } + +stat: + `Do{ stat* } + | `Set{ {lhs+} {expr+} } -- lhs1, lhs2... = e1, e2... + | `While{ expr block } -- while e do b end + | `Repeat{ block expr } -- repeat b until e + | `If{ (expr block)+ block? } -- if e1 then b1 [elseif e2 then b2] ... [else bn] end + | `Fornum{ ident expr expr expr? block } -- for ident = e, e[, e] do b end + | `Forin{ {ident+} {expr+} block } -- for i1, i2... in e1, e2... do b end + | `Local{ {ident+} {expr+}? } -- local i1, i2... = e1, e2... + | `Localrec{ ident expr } -- only used for 'local function' + | `Goto{ } -- goto str + | `Label{ } -- ::str:: + | `Return{ } -- return e1, e2... + | `Break -- break + | apply + +expr: + `Nil + | `Dots + | `True + | `False + | `Number{ } + | `String{ } + | `Function{ { `Id{ }* `Dots? } block } + | `Table{ ( `Pair{ expr expr } | expr )* } + | `Op{ opid expr expr? } + | `Paren{ expr } -- significant to cut multiple values returns + | apply + | lhs + +apply: + `Call{ expr expr* } + | `Invoke{ expr `String{ } expr* } + +lhs: `Id{ } | `Index{ expr expr } + +opid: 'add' | 'sub' | 'mul' | 'div' | 'idiv' | 'mod' | 'pow' | 'concat' + | 'band' | 'bor' | 'bxor' | 'shl' | 'shr' | 'eq' | 'lt' | 'le' + | 'and' | 'or' | 'not' | 'unm' | 'len' | 'bnot' +]] +local parser = {} +local lpeg = require("lpeg") +local scope +if include ~= nil then + scope = include("./scope.lua") +else + scope = dofile("../src/scope.lua") +end + +lpeg.locale(lpeg) + +local P, S, V = lpeg.P, lpeg.S, lpeg.V +local C, Carg, Cb, Cc = lpeg.C, lpeg.Carg, lpeg.Cb, lpeg.Cc +local Cf, Cg, Cmt, Cp, Ct = lpeg.Cf, lpeg.Cg, lpeg.Cmt, lpeg.Cp, lpeg.Ct +local alpha, digit, alnum = lpeg.alpha, lpeg.digit, lpeg.alnum +local xdigit = lpeg.xdigit +local space = lpeg.space + +local lineno = scope.lineno +local new_scope, end_scope = scope.new_scope, scope.end_scope +local new_function, end_function = scope.new_function, scope.end_function +local begin_loop, end_loop = scope.begin_loop, scope.end_loop +local insideloop = scope.insideloop + +--some stuff for getting glua comments +local BEGIN_COMMENT = lpeg.P("/*") +local END_COMMENT = lpeg.P("*/") +local NOT_BEGIN = (1 - BEGIN_COMMENT)^0 +local NOT_END = (1 - END_COMMENT)^0 +local FULL_COMMENT_CONTENTS = BEGIN_COMMENT * NOT_END * END_COMMENT + +-- error message auxiliary functions + +-- creates an error message for the input string +local function syntaxerror (errorinfo, pos, msg) + local l, c = lineno(errorinfo.subject, pos) + local error_msg = "%s:%d:%d: syntax error, %s" + return string.format(error_msg, errorinfo.filename, l, c, msg) +end + +-- gets the farthest failure position +local function getffp (s, i, t) + return t.ffp or i, t +end + +-- gets the table that contains the error information +local function geterrorinfo () + return Cmt(Carg(1), getffp) * (C(V"OneWord") + Cc("EOF")) / + function (t, u) + t.unexpected = u + return t + end +end + +-- creates an errror message using the farthest failure position +local function errormsg () + return geterrorinfo() / + function (t) + local p = t.ffp or 1 + local msg = "unexpected '%s', expecting %s" + msg = string.format(msg, t.unexpected, t.expected) + return nil, syntaxerror(t, p, msg) + end +end + +-- reports a syntactic error +local function report_error () + return errormsg() +end + +-- sets the farthest failure position and the expected tokens +local function setffp (s, i, t, n) + if not t.ffp or i > t.ffp then + t.ffp = i + t.list = {} ; t.list[n] = n + t.expected = "'" .. n .. "'" + elseif i == t.ffp then + if not t.list[n] then + t.list[n] = n + t.expected = "'" .. n .. "', " .. t.expected + end + end + return false +end + +local function updateffp (name) + return Cmt(Carg(1) * Cc(name), setffp) +end + +--Fixes strings +local function fix_str (str) + str = string.gsub(str, "\\a", "\a") + str = string.gsub(str, "\\b", "\b") + str = string.gsub(str, "\\f", "\f") + str = string.gsub(str, "\\n", "\n") + str = string.gsub(str, "\\r", "\r") + str = string.gsub(str, "\\t", "\t") + str = string.gsub(str, "\\v", "\v") + str = string.gsub(str, "\\\n", "\n") + str = string.gsub(str, "\\\r", "\n") + str = string.gsub(str, "\\'", "'") + str = string.gsub(str, '\\"', '"') + str = string.gsub(str, '\\\\', '\\') + return str +end + +-- regular combinators and auxiliary functions + +local function token (pat, name) + return pat * V"Skip" + updateffp(name) * P(false) +end + +local function symb (str) + return token (P(str), str) +end + +local function kw (str) + return token (P(str) * -V"idRest", str) +end + +local function taggedCap (tag, pat) + return Ct(Cg(Cp(), "pos") * Cg(Cc(tag), "tag") * pat) +end + +local function unaryop (op, e) + return { tag = "Op", pos = e.pos, [1] = op, [2] = e } +end + +local function binaryop (e1, op, e2) + if not op then + return e1 + elseif op == "add" or + op == "sub" or + op == "mul" or + op == "div" or + op == "idiv" or + op == "mod" or + op == "pow" or + op == "concat" or + op == "band" or + op == "bor" or + op == "bxor" or + op == "shl" or + op == "shr" or + op == "eq" or + op == "lt" or + op == "le" or + op == "and" or + op == "or" then + return { tag = "Op", pos = e1.pos, [1] = op, [2] = e1, [3] = e2 } + elseif op == "ne" then + return unaryop ("not", { tag = "Op", pos = e1.pos, [1] = "eq", [2] = e1, [3] = e2 }) + elseif op == "gt" then + return { tag = "Op", pos = e1.pos, [1] = "lt", [2] = e2, [3] = e1 } + elseif op == "ge" then + return { tag = "Op", pos = e1.pos, [1] = "le", [2] = e2, [3] = e1 } + end +end + +local function chainl (pat, sep, a) + return Cf(pat * Cg(sep * pat)^0, binaryop) + a +end + +local function chainl1 (pat, sep) + return Cf(pat * Cg(sep * pat)^0, binaryop) +end + +local function sepby (pat, sep, tag) + return taggedCap(tag, (pat * (sep * pat)^0)^(-1)) +end + +local function sepby1 (pat, sep, tag) + return taggedCap(tag, pat * (sep * pat)^0) +end + +-- grammar + +local G = { V"Lua", + Lua = V"Shebang"^(-1) * V"Skip" * V"Chunk" * -1 + report_error(); + -- parser + Chunk = V"Block"; + StatList = (symb(";") + V"Stat")^0; + Var = V"Id"; + Id = taggedCap("Id", token(V"Name", "Name")); + FunctionDef = kw("function") * V"FuncBody"; + FieldSep = symb(",") + symb(";"); + Field = taggedCap("Pair", (symb("[") * V"Expr" * symb("]") * symb("=") * V"Expr") + + (taggedCap("String", token(V"Name", "Name")) * symb("=") * V"Expr")) + + V"Expr"; + FieldList = (V"Field" * (V"FieldSep" * V"Field")^0 * V"FieldSep"^(-1))^(-1); + Constructor = taggedCap("Table", symb("{") * V"FieldList" * symb("}")); + NameList = sepby1(V"Id", symb(","), "NameList"); + ExpList = sepby1(V"Expr", symb(","), "ExpList"); + FuncArgs = symb("(") * (V"Expr" * (symb(",") * V"Expr")^0)^(-1) * symb(")") + + V"Constructor" + + taggedCap("String", token(V"String", "String")); + Expr = V"SubExpr_1"; + SubExpr_1 = chainl1(V"SubExpr_2", V"OrOp"); + SubExpr_2 = chainl1(V"SubExpr_3", V"AndOp"); + SubExpr_3 = chainl1(V"SubExpr_4", V"RelOp"); + SubExpr_4 = chainl1(V"SubExpr_5", V"BOrOp"); + SubExpr_5 = chainl1(V"SubExpr_6", V"BXorOp"); + SubExpr_6 = chainl1(V"SubExpr_7", V"BAndOp"); + SubExpr_7 = chainl1(V"SubExpr_8", V"ShiftOp"); + SubExpr_8 = V"SubExpr_9" * V"ConOp" * V"SubExpr_8" / binaryop + + V"SubExpr_9"; + SubExpr_9 = chainl1(V"SubExpr_10", V"AddOp"); + SubExpr_10 = chainl1(V"SubExpr_11", V"MulOp"); + SubExpr_11 = V"UnOp" * V"SubExpr_11" / unaryop + + V"SubExpr_12"; + SubExpr_12 = V"SimpleExp" * (V"PowOp" * V"SubExpr_11")^(-1) / binaryop; + SimpleExp = taggedCap("Number", token(V"Number", "Number")) + + taggedCap("String", token(V"String", "String")) + + taggedCap("Nil", kw("nil")) + + taggedCap("False", kw("false")) + + taggedCap("True", kw("true")) + + taggedCap("Dots", symb("...")) + + V"FunctionDef" + + V"Constructor" + + V"SuffixedExp"; + SuffixedExp = Cf(V"PrimaryExp" * ( + taggedCap("DotIndex", symb(".") * taggedCap("String", token(V"Name", "Name"))) + + taggedCap("ArrayIndex", symb("[") * V"Expr" * symb("]")) + + taggedCap("Invoke", Cg(symb(":") * taggedCap("String", token(V"Name", "Name")) * V"FuncArgs")) + + taggedCap("Call", V"FuncArgs") + )^0, function (t1, t2) + if t2 then + if t2.tag == "Call" or t2.tag == "Invoke" then + local t = {tag = t2.tag, pos = t1.pos, [1] = t1} + for k, v in ipairs(t2) do + table.insert(t, v) + end + return t + else + return {tag = "Index", pos = t1.pos, [1] = t1, [2] = t2[1]} + end + end + return t1 + end); + PrimaryExp = V"Var" + + taggedCap("Paren", symb("(") * V"Expr" * symb(")")); + Block = taggedCap("Block", V"StatList" * V"RetStat"^(-1)); + IfStat = taggedCap("If", + kw("if") * V"Expr" * kw("then") * V"Block" * + (kw("elseif") * V"Expr" * kw("then") * V"Block")^0 * + (kw("else") * V"Block")^(-1) * + kw("end")); + WhileStat = taggedCap("While", kw("while") * V"Expr" * + kw("do") * V"Block" * kw("end")); + DoStat = kw("do") * V"Block" * kw("end") / + function (t) + t.tag = "Do" + return t + end; + ForBody = kw("do") * V"Block"; + ForNum = taggedCap("Fornum", + V"Id" * symb("=") * V"Expr" * symb(",") * + V"Expr" * (symb(",") * V"Expr")^(-1) * + V"ForBody"); + ForGen = taggedCap("Forin", V"NameList" * kw("in") * V"ExpList" * V"ForBody"); + ForStat = kw("for") * (V"ForNum" + V"ForGen") * kw("end"); + RepeatStat = taggedCap("Repeat", kw("repeat") * V"Block" * + kw("until") * V"Expr"); + FuncName = Cf(V"Id" * (symb(".") * taggedCap("String", token(V"Name", "Name")))^0, + function (t1, t2) + if t2 then + return {tag = "Index", pos = t1.pos, [1] = t1, [2] = t2} + end + return t1 + end) * (symb(":") * taggedCap("String", token(V"Name", "Name")))^(-1) / + function (t1, t2) + if t2 then + return {tag = "Index", pos = t1.pos, is_method = true, [1] = t1, [2] = t2} + end + return t1 + end; + ParList = V"NameList" * (symb(",") * symb("...") * taggedCap("Dots", Cp()))^(-1) / + function (t, v) + if v then table.insert(t, v) end + return t + end + + symb("...") * taggedCap("Dots", Cp()) / + function (v) + return {v} + end + + P(true) / function () return {} end; + -- Cc({}) generates a strange bug when parsing [[function t:a() end ; function t.a() end]] + -- the bug is to add the parameter self to the second function definition + --FuncBody = taggedCap("Function", symb("(") * (V"ParList" + Cc({})) * symb(")") * V"Block" * kw("end")); + FuncBody = taggedCap("Function", symb("(") * V"ParList" * symb(")") * V"Block" * kw("end")); + FuncStat = taggedCap("Set", kw("function") * V"FuncName" * V"FuncBody") / + function (t) + if t[1].is_method then table.insert(t[2][1], 1, {tag = "Id", [1] = "self"}) end + t[1] = {t[1]} + t[2] = {t[2]} + return t + end; + LocalFunc = taggedCap("Localrec", kw("function") * V"Id" * V"FuncBody") / + function (t) + t[1] = {t[1]} + t[2] = {t[2]} + return t + end; + LocalAssign = taggedCap("Local", V"NameList" * ((symb("=") * V"ExpList") + Ct(Cc()))); + LocalStat = kw("local") * (V"LocalFunc" + V"LocalAssign"); + LabelStat = taggedCap("Label", symb("::") * token(V"Name", "Name") * symb("::")); + BreakStat = taggedCap("Break", kw("break")); + ContinueStat = taggedCap("Continue", kw("continue")); + GoToStat = taggedCap("Goto", kw("goto") * token(V"Name", "Name")); + RetStat = taggedCap("Return", kw("return") * (V"Expr" * (symb(",") * V"Expr")^0)^(-1) * symb(";")^(-1)); + ExprStat = Cmt( + (V"SuffixedExp" * + (Cc(function (...) + local vl = {...} + local el = vl[#vl] + table.remove(vl) + for k, v in ipairs(vl) do + if v.tag == "Id" or v.tag == "Index" then + vl[k] = v + else + -- invalid assignment + return false + end + end + vl.tag = "VarList" + vl.pos = vl[1].pos + return true, {tag = "Set", pos = vl.pos, [1] = vl, [2] = el} + end) * V"Assignment")) + + + (V"SuffixedExp" * + (Cc(function (s) + if s.tag == "Call" or + s.tag == "Invoke" then + return true, s + end + -- invalid statement + return false + end))) + , function (s, i, s1, f, ...) return f(s1, ...) end); + Assignment = ((symb(",") * V"SuffixedExp")^1)^(-1) * symb("=") * V"ExpList"; + Stat = V"IfStat" + V"WhileStat" + V"DoStat" + V"ForStat" + + V"RepeatStat" + V"FuncStat" + V"LocalStat" + V"LabelStat" + + V"BreakStat" + V"GoToStat" + V"ExprStat" + V"ContinueStat"; + -- lexer + Space = space^1; + Equals = P"="^0; + Open = "[" * Cg(V"Equals", "init") * "[" * P"\n"^(-1); + Close = "]" * C(V"Equals") * "]"; + CloseEQ = Cmt(V"Close" * Cb("init"), + function (s, i, a, b) return a == b end); + LongString = V"Open" * C((P(1) - V"CloseEQ")^0) * V"Close" / + function (s, o) return s end; + Comment = P"--" * V"LongString" / function () return end + + P"--" * (P(1) - P"\n")^0 + + P"//" * (P(1) - P"\n")^0 + + C(FULL_COMMENT_CONTENTS) / function() return end; + Skip = (V"Space" + V"Comment")^0; + idStart = alpha + P("_"); + idRest = alnum + P("_"); + Keywords = P("and") + "break" + "do" + "elseif" + "else" + "end" + + "false" + "for" + "function" + "goto" + "if" + "in" + + "local" + "nil" + "not" + "or" + "repeat" + "return" + + "then" + "true" + "until" + "while" + "continue"; + Reserved = V"Keywords" * -V"idRest"; + Identifier = V"idStart" * V"idRest"^0; + Name = -V"Reserved" * C(V"Identifier") * -V"idRest"; + Hex = (P("0x") + P("0X")) * xdigit^1; + Expo = S("eE") * S("+-")^(-1) * digit^1; + Float = (((digit^1 * P(".") * digit^0) + + (P(".") * digit^1)) * V"Expo"^(-1)) + + (digit^1 * V"Expo"); + Int = digit^1; + Number = C(V"Hex" + V"Float" + V"Int") / + function (n) return tonumber(n) end; + ShortString = P'"' * C(((P'\\' * P(1)) + (P(1) - P'"'))^0) * P'"' + + P"'" * C(((P"\\" * P(1)) + (P(1) - P"'"))^0) * P"'"; + String = V"LongString" + (V"ShortString" / function (s) + --print("instead of " .. s .. " i return " .. fix_str(s)) + return s end); + --return fix_str(s) end); + OrOp = kw("or") / "or" + + symb("||") / "or"; + AndOp = kw("and") / "and" + + symb("&&") / "and"; + RelOp = symb("~=") / "ne" + + symb("==") / "eq" + + symb("<=") / "le" + + symb(">=") / "ge" + + symb("<") / "lt" + + symb(">") / "gt" + + symb("!=") / "ne"; + BOrOp = symb("|") / "bor"; + BXorOp = symb("~") / "bxor"; + BAndOp = symb("&") / "band"; + ShiftOp = symb("<<") / "shl" + + symb(">>") / "shr"; + ConOp = symb("..") / "concat"; + AddOp = symb("+") / "add" + + symb("-") / "sub"; + MulOp = symb("*") / "mul" + + --symb("//") / "idiv" + + symb("/") / "div" + + symb("%") / "mod"; + UnOp = kw("not") / "not" + + symb("-") / "unm" + + symb("#") / "len" + + symb("~") / "bnot" + + symb("!") / "not"; + PowOp = symb("^") / "pow"; + Shebang = P"#" * (P(1) - P"\n")^0 * P"\n"; + -- for error reporting + OneWord = V"Name" + V"Number" + V"String" + V"Reserved" + P("...") + P(1); +} + +local function exist_label (env, scope, stm) + local l = stm[1] + for s=scope, 0, -1 do + if env[s]["label"][l] then return true end + end + return false +end + +local function set_label (env, label, pos) + local scope = env.scope + local l = env[scope]["label"][label] + if not l then + env[scope]["label"][label] = { name = label, pos = pos } + return true + else + local msg = "label '%s' already defined at line %d" + local line = lineno(env.errorinfo.subject, l.pos) + msg = string.format(msg, label, line) + return nil, syntaxerror(env.errorinfo, pos, msg) + end +end + +local function set_pending_goto (env, stm) + local scope = env.scope + table.insert(env[scope]["goto"], stm) + return true +end + +local function verify_pending_gotos (env) + for s=env.maxscope, 0, -1 do + for k, v in ipairs(env[s]["goto"]) do + if not exist_label(env, s, v) then + local msg = "no visible label '%s' for " + msg = string.format(msg, v[1]) + return nil, syntaxerror(env.errorinfo, v.pos, msg) + end + end + end + return true +end + +local function set_vararg (env, is_vararg) + env["function"][env.fscope].is_vararg = is_vararg +end + +local traverse_stm, traverse_exp, traverse_var +local traverse_block, traverse_explist, traverse_varlist, traverse_parlist + +function traverse_parlist (env, parlist) + local len = #parlist + local is_vararg = false + if len > 0 and parlist[len].tag == "Dots" then + is_vararg = true + end + set_vararg(env, is_vararg) + return true +end + +local function traverse_function (env, exp) + new_function(env) + new_scope(env) + local status, msg = traverse_parlist(env, exp[1]) + if not status then return status, msg end + status, msg = traverse_block(env, exp[2]) + if not status then return status, msg end + end_scope(env) + end_function(env) + return true +end + +local function traverse_op (env, exp) + local status, msg = traverse_exp(env, exp[2]) + if not status then return status, msg end + if exp[3] then + status, msg = traverse_exp(env, exp[3]) + if not status then return status, msg end + end + return true +end + +local function traverse_paren (env, exp) + local status, msg = traverse_exp(env, exp[1]) + if not status then return status, msg end + return true +end + +local function traverse_table (env, fieldlist) + for k, v in ipairs(fieldlist) do + local tag = v.tag + if tag == "Pair" then + local status, msg = traverse_exp(env, v[1]) + if not status then return status, msg end + status, msg = traverse_exp(env, v[2]) + if not status then return status, msg end + else + local status, msg = traverse_exp(env, v) + if not status then return status, msg end + end + end + return true +end + +local function traverse_vararg (env, exp) + if not env["function"][env.fscope].is_vararg then + local msg = "cannot use '...' outside a vararg function" + return nil, syntaxerror(env.errorinfo, exp.pos, msg) + end + return true +end + +local function traverse_call (env, call) + local status, msg = traverse_exp(env, call[1]) + if not status then return status, msg end + for i=2, #call do + status, msg = traverse_exp(env, call[i]) + if not status then return status, msg end + end + return true +end + +local function traverse_invoke (env, invoke) + local status, msg = traverse_exp(env, invoke[1]) + if not status then return status, msg end + for i=3, #invoke do + status, msg = traverse_exp(env, invoke[i]) + if not status then return status, msg end + end + return true +end + +local function traverse_assignment (env, stm) + local status, msg = traverse_varlist(env, stm[1]) + if not status then return status, msg end + status, msg = traverse_explist(env, stm[2]) + if not status then return status, msg end + return true +end + +local function traverse_break (env, stm) + if not insideloop(env) then + local msg = " not inside a loop" + return nil, syntaxerror(env.errorinfo, stm.pos, msg) + end + return true +end + +local function traverse_continue (env, stm) + if not insideloop(env) then + local msg = " not inside a loop" + return nil, syntaxerror(env.errorinfo, stm.pos, msg) + end + return true +end + +local function traverse_forin (env, stm) + begin_loop(env) + new_scope(env) + local status, msg = traverse_explist(env, stm[2]) + if not status then return status, msg end + status, msg = traverse_block(env, stm[3]) + if not status then return status, msg end + end_scope(env) + end_loop(env) + return true +end + +local function traverse_fornum (env, stm) + local status, msg + begin_loop(env) + new_scope(env) + status, msg = traverse_exp(env, stm[2]) + if not status then return status, msg end + status, msg = traverse_exp(env, stm[3]) + if not status then return status, msg end + if stm[5] then + status, msg = traverse_exp(env, stm[4]) + if not status then return status, msg end + status, msg = traverse_block(env, stm[5]) + if not status then return status, msg end + else + status, msg = traverse_block(env, stm[4]) + if not status then return status, msg end + end + end_scope(env) + end_loop(env) + return true +end + +local function traverse_goto (env, stm) + local status, msg = set_pending_goto(env, stm) + if not status then return status, msg end + return true +end + +local function traverse_if (env, stm) + local len = #stm + if len % 2 == 0 then + for i=1, len, 2 do + local status, msg = traverse_exp(env, stm[i]) + if not status then return status, msg end + status, msg = traverse_block(env, stm[i+1]) + if not status then return status, msg end + end + else + for i=1, len-1, 2 do + local status, msg = traverse_exp(env, stm[i]) + if not status then return status, msg end + status, msg = traverse_block(env, stm[i+1]) + if not status then return status, msg end + end + local status, msg = traverse_block(env, stm[len]) + if not status then return status, msg end + end + return true +end + +local function traverse_label (env, stm) + local status, msg = set_label(env, stm[1], stm.pos) + if not status then return status, msg end + return true +end + +local function traverse_let (env, stm) + local status, msg = traverse_explist(env, stm[2]) + if not status then return status, msg end + return true +end + +local function traverse_letrec (env, stm) + local status, msg = traverse_exp(env, stm[2][1]) + if not status then return status, msg end + return true +end + +local function traverse_repeat (env, stm) + begin_loop(env) + local status, msg = traverse_block(env, stm[1]) + if not status then return status, msg end + status, msg = traverse_exp(env, stm[2]) + if not status then return status, msg end + end_loop(env) + return true +end + +local function traverse_return (env, stm) + local status, msg = traverse_explist(env, stm) + if not status then return status, msg end + return true +end + +local function traverse_while (env, stm) + begin_loop(env) + local status, msg = traverse_exp(env, stm[1]) + if not status then return status, msg end + status, msg = traverse_block(env, stm[2]) + if not status then return status, msg end + end_loop(env) + return true +end + +function traverse_var (env, var) + local tag = var.tag + if tag == "Id" then -- `Id{ } + return true + elseif tag == "Index" then -- `Index{ expr expr } + local status, msg = traverse_exp(env, var[1]) + if not status then return status, msg end + status, msg = traverse_exp(env, var[2]) + if not status then return status, msg end + return true + else + error("expecting a variable, but got a " .. tag) + end +end + +function traverse_varlist (env, varlist) + for k, v in ipairs(varlist) do + local status, msg = traverse_var(env, v) + if not status then return status, msg end + end + return true +end + +function traverse_exp (env, exp) + local tag = exp.tag + if tag == "Nil" or + tag == "True" or + tag == "False" or + tag == "Number" or -- `Number{ } + tag == "String" then -- `String{ } + return true + elseif tag == "Dots" then + return traverse_vararg(env, exp) + elseif tag == "Function" then -- `Function{ { `Id{ }* `Dots? } block } + return traverse_function(env, exp) + elseif tag == "Table" then -- `Table{ ( `Pair{ expr expr } | expr )* } + return traverse_table(env, exp) + elseif tag == "Op" then -- `Op{ opid expr expr? } + return traverse_op(env, exp) + elseif tag == "Paren" then -- `Paren{ expr } + return traverse_paren(env, exp) + elseif tag == "Call" then -- `Call{ expr expr* } + return traverse_call(env, exp) + elseif tag == "Invoke" then -- `Invoke{ expr `String{ expr* } + return traverse_invoke(env, exp) + elseif tag == "Id" or -- `Id{ } + tag == "Index" then -- `Index{ expr expr } + return traverse_var(env, exp) + else + error("expecting an expression, but got a " .. tag) + end +end + +function traverse_explist (env, explist) + for k, v in ipairs(explist) do + local status, msg = traverse_exp(env, v) + if not status then return status, msg end + end + return true +end + +function traverse_stm (env, stm) + local tag = stm.tag + if tag == "Do" then -- `Do{ stat* } + return traverse_block(env, stm) + elseif tag == "Set" then -- `Set{ {lhs+} {expr+} } + return traverse_assignment(env, stm) + elseif tag == "While" then -- `While{ expr block } + return traverse_while(env, stm) + elseif tag == "Repeat" then -- `Repeat{ block expr } + return traverse_repeat(env, stm) + elseif tag == "If" then -- `If{ (expr block)+ block? } + return traverse_if(env, stm) + elseif tag == "Fornum" then -- `Fornum{ ident expr expr expr? block } + return traverse_fornum(env, stm) + elseif tag == "Forin" then -- `Forin{ {ident+} {expr+} block } + return traverse_forin(env, stm) + elseif tag == "Local" then -- `Local{ {ident+} {expr+}? } + return traverse_let(env, stm) + elseif tag == "Localrec" then -- `Localrec{ ident expr } + return traverse_letrec(env, stm) + elseif tag == "Goto" then -- `Goto{ } + return traverse_goto(env, stm) + elseif tag == "Label" then -- `Label{ } + return traverse_label(env, stm) + elseif tag == "Return" then -- `Return{ * } + return traverse_return(env, stm) + elseif tag == "Break" then + return traverse_break(env, stm) + elseif tag == "Continue" then + return traverse_continue(env,stm) + elseif tag == "Call" then -- `Call{ expr expr* } + return traverse_call(env, stm) + elseif tag == "Invoke" then -- `Invoke{ expr `String{ } expr* } + return traverse_invoke(env, stm) + else + error("expecting a statement, but got a " .. tag) + end +end + +function traverse_block (env, block) + local l = {} + new_scope(env) + for k, v in ipairs(block) do + local status, msg = traverse_stm(env, v) + if not status then return status, msg end + end + end_scope(env) + return true +end + + +local function traverse (ast, errorinfo) + assert(type(ast) == "table") + assert(type(errorinfo) == "table") + local env = { errorinfo = errorinfo, ["function"] = {} } + new_function(env) + set_vararg(env, true) + local status, msg = traverse_block(env, ast) + if not status then return status, msg end + end_function(env) + status, msg = verify_pending_gotos(env) + if not status then return status, msg end + return ast +end + +function parser.parse (subject, filename) + local errorinfo = { subject = subject, filename = filename } + lpeg.setmaxstack(1000) + --debug.getregistry()["lpeg-maxstack"] = 1000 + local ast, error_msg = lpeg.match(G, subject, nil, errorinfo) + if not ast then return ast, error_msg end + return traverse(ast, errorinfo) +end + +return parser diff --git a/src/parser_min.lua b/src/parser_min.lua deleted file mode 100644 index 8750cfd..0000000 --- a/src/parser_min.lua +++ /dev/null @@ -1,804 +0,0 @@ -local a, b, d, e, c = "String", "errorinfo", "function", "cannot use '...' outside a vararg function", "SuffixedExp" -local f = {} -local g = require("lpeg") -local h - -if include ~= nil then - h = include("./scope.lua") -else - h = dofile("../src/scope.lua") -end - -g.locale(g) -local i, j, k = g.P, g.S, g.V -local l, m, n, o = g.C, g.Carg, g.Cb, g.Cc -local p, q, r, s, t = g.Cf, g.Cg, g.Cmt, g.Cp, g.Ct -local u, v, w = g.alpha, g.digit, g.alnum -local x = g.xdigit -local y = g.space -local z = h.lineno -local A, B = h.new_scope, h.end_scope -local C, D = h.new_function, h.end_function -local E, F = h.begin_loop, h.end_loop -local G = h.insideloop -local H = g.P("/*") -local I = g.P("*/") -local J = (1 - H) ^ 0 -local K = (1 - I) ^ 0 -local L = H * K * I - -local function M(N, O, P) - local Q, R = z(N.subject, O) - local S = "%s:%d:%d: syntax error, %s" - - return string.format(S, N.filename, Q, R, P) -end - -local function Q(R, S, T) - return T.ffp or S, T -end - -local function U() - return r(m(1), Q) * (l(k("OneWord")) + o("EOF")) / function(T, V) - T.unexpected = V - - return T - end -end - -local function V() - return U() / function(T) - local W = T.ffp or 1 - local P = "unexpected '%s', expecting %s" - P = string.format(P, T.unexpected, T.expected) - - return nil, M(T, W, P) - end -end - -local function W() - return V() -end - -local function X(R, S, T, Y) - if not T.ffp or T.ffp < S then - T.ffp = S - T.list = {} - T.list[Y] = Y - T.expected = "'" .. Y .. "'" - elseif S == T.ffp then - end - - return false -end - -local function Z(aa) - return r(m(1) * o(aa), X) -end - -local function ba(ca) - ca = string.gsub(ca, "\\a", "\a") - ca = string.gsub(ca, "\\b", "\b") - ca = string.gsub(ca, "\\f", "\f") - ca = string.gsub(ca, "\\n", "\n") - ca = string.gsub(ca, "\\r", "\r") - ca = string.gsub(ca, "\\t", "\t") - ca = string.gsub(ca, "\\v", "\v") - ca = string.gsub(ca, "\\\n", "\n") - ca = string.gsub(ca, "\\\r", "\n") - ca = string.gsub(ca, "\\'", "'") - ca = string.gsub(ca, [[\\"]], [["]]) - ca = string.gsub(ca, "\\\\", "\\") - - return ca -end - -local function da(ea, aa) - return ea * k("Skip") + Z(aa) * i(false) -end - -local function fa(ca) - return da(i(ca), ca) -end - -local function ga(ca) - return da(i(ca) * -k("idRest"), ca) -end - -local function ha(ia, ea) - return t(q(s(), "pos") * q(o(ia), "tag") * ea) -end - -local function ja(ka, la) - return { - ["tag"] = "Op", - ["pos"] = la.pos, - [1] = ka, - [2] = la - } -end - -local function ma(na, ka, oa) - if not ka then - return na - elseif ka == "add" then - orka"sub" - orka"mul" - orka"div" - orka"idiv" - orka"mod" - orka"pow" - orka"concat" - orka"band" - orka"bor" - orka"bxor" - orka"shl" - orka"shr" - orka"eq" - orka"lt" - orka"le" - orka"and" - orka"or" - elseif ka == "ne" then - elseif ka == "gt" then - elseif ka == "ge" then - end -end - -local function pa(ea, qa, ra) - return p(ea * q(qa * ea) ^ 0, ma) + ra -end - -local function sa(ea, qa) - return p(ea * q(qa * ea) ^ 0, ma) -end - -local function ta(ea, qa, ia) - return ha(ia, (ea * (qa * ea) ^ 0) ^ (-1)) -end - -local function ua(ea, qa, ia) - return ha(ia, ea * (qa * ea) ^ 0) -end - -local va = { - k("Lua"), - ["Lua"] = k("Shebang") ^ (-1) * k("Skip") * k("Chunk") * -1 + W(), - ["Chunk"] = k("Block"), - ["StatList"] = (fa(";") + k("Stat")) ^ 0, - ["Var"] = k("Id"), - ["Id"] = ha("Id", da(k("Name"), "Name")), - ["FunctionDef"] = ga(d) * k("FuncBody"), - ["FieldSep"] = fa(",") + fa(";"), - ["Field"] = ha("Pair", (fa("[") * k("Expr") * fa("]") * fa("=") * k("Expr")) + (ha(a, da(k("Name"), "Name")) * fa("=") * k("Expr"))) + k("Expr"), - ["FieldList"] = (k("Field") * (k("FieldSep") * k("Field")) ^ 0 * k("FieldSep") ^ (-1)) ^ (-1), - ["Constructor"] = ha("Table", fa("{") * k("FieldList") * fa("}")), - ["NameList"] = ua(k("Id"), fa(","), "NameList"), - ["ExpList"] = ua(k("Expr"), fa(","), "ExpList"), - ["FuncArgs"] = fa("(") * (k("Expr") * (fa(",") * k("Expr")) ^ 0) ^ (-1) * fa(")") + k("Constructor") + ha(a, da(k(a), a)), - ["Expr"] = k("SubExpr_1"), - ["SubExpr_1"] = sa(k("SubExpr_2"), k("OrOp")), - ["SubExpr_2"] = sa(k("SubExpr_3"), k("AndOp")), - ["SubExpr_3"] = sa(k("SubExpr_4"), k("RelOp")), - ["SubExpr_4"] = sa(k("SubExpr_5"), k("BOrOp")), - ["SubExpr_5"] = sa(k("SubExpr_6"), k("BXorOp")), - ["SubExpr_6"] = sa(k("SubExpr_7"), k("BAndOp")), - ["SubExpr_7"] = sa(k("SubExpr_8"), k("ShiftOp")), - ["SubExpr_8"] = k("SubExpr_9") * k("ConOp") * k("SubExpr_8") / ma + k("SubExpr_9"), - ["SubExpr_9"] = sa(k("SubExpr_10"), k("AddOp")), - ["SubExpr_10"] = sa(k("SubExpr_11"), k("MulOp")), - ["SubExpr_11"] = k("UnOp") * k("SubExpr_11") / ja + k("SubExpr_12"), - ["SubExpr_12"] = k("SimpleExp") * (k("PowOp") * k("SubExpr_11")) ^ (-1) / ma, - ["SimpleExp"] = ha("Number", da(k("Number"), "Number")) + ha(a, da(k(a), a)) + ha("Nil", ga("nil")) + ha("False", ga("false")) + ha("True", ga("true")) + ha("Dots", fa("...")) + k("FunctionDef") + k("Constructor") + k(c), - [c] = p(k("PrimaryExp") * (ha("DotIndex", fa(".") * ha(a, da(k("Name"), "Name"))) + ha("ArrayIndex", fa("[") * k("Expr") * fa("]")) + ha("Invoke", q(fa(":") * ha(a, da(k("Name"), "Name")) * k("FuncArgs"))) + ha("Call", k("FuncArgs"))) ^ 0, function(wa, xa) - if xa then - if xa.tag == "Call" then - orxa.tag"Invoke" - - local T = { - ["tag"] = xa.tag, - ["pos"] = wa.pos, - [1] = wa - } - - for ya, za in ipairs(xa) do - table.insert(T, za) - end - - return T - else - return { - ["tag"] = "Index", - ["pos"] = wa.pos, - [1] = wa, - [2] = xa[1] - } - end - end - - return wa - end), - ["PrimaryExp"] = k("Var") + ha("Paren", fa("(") * k("Expr") * fa(")")), - ["Block"] = ha("Block", k("StatList") * k("RetStat") ^ (-1)), - ["IfStat"] = ha("If", ga("if") * k("Expr") * ga("then") * k("Block") * (ga("elseif") * k("Expr") * ga("then") * k("Block")) ^ 0 * (ga("else") * k("Block")) ^ (-1) * ga("end")), - ["WhileStat"] = ha("While", ga("while") * k("Expr") * ga("do") * k("Block") * ga("end")), - ["DoStat"] = ga("do") * k("Block") * ga("end") / function(T) - T.tag = "Do" - - return T - end, - ["ForBody"] = ga("do") * k("Block"), - ["ForNum"] = ha("Fornum", k("Id") * fa("=") * k("Expr") * fa(",") * k("Expr") * (fa(",") * k("Expr")) ^ (-1) * k("ForBody")), - ["ForGen"] = ha("Forin", k("NameList") * ga("in") * k("ExpList") * k("ForBody")), - ["ForStat"] = ga("for") * (k("ForNum") + k("ForGen")) * ga("end"), - ["RepeatStat"] = ha("Repeat", ga("repeat") * k("Block") * ga("until") * k("Expr")), - ["FuncName"] = p(k("Id") * (fa(".") * ha(a, da(k("Name"), "Name"))) ^ 0, function(wa, xa) - if xa then - return { - ["tag"] = "Index", - ["pos"] = wa.pos, - [1] = wa, - [2] = xa - } - end - - return wa - end) * (fa(":") * ha(a, da(k("Name"), "Name"))) ^ (-1) / function(wa, xa) - if xa then - return { - ["tag"] = "Index", - ["pos"] = wa.pos, - ["is_method"] = true, - [1] = wa, - [2] = xa - } - end - - return wa - end, - ["ParList"] = k("NameList") * (fa(",") * fa("...") * ha("Dots", s())) ^ (-1) / function(T, ya) - if ya then - table.insert(T, ya) - end - - return T - end + fa("...") * ha("Dots", s()) / function(ya) return {ya} end + i(true) / function() return {} end, - ["FuncBody"] = ha("Function", fa("(") * k("ParList") * fa(")") * k("Block") * ga("end")), - ["FuncStat"] = ha("Set", ga(d) * k("FuncName") * k("FuncBody")) / function(T) - if T[1].is_method then - table.insert(T[2][1], 1, { - ["tag"] = "Id", - [1] = "self" - }) - end - - T[1] = {T[1]} - T[2] = {T[2]} - - return T - end, - ["LocalFunc"] = ha("Localrec", ga(d) * k("Id") * k("FuncBody")) / function(T) - T[1] = {T[1]} - T[2] = {T[2]} - - return T - end, - ["LocalAssign"] = ha("Local", k("NameList") * ((fa("=") * k("ExpList")) + t(o()))), - ["LocalStat"] = ga("local") * (k("LocalFunc") + k("LocalAssign")), - ["LabelStat"] = ha("Label", fa("::") * da(k("Name"), "Name") * fa("::")), - ["BreakStat"] = ha("Break", ga("break")), - ["ContinueStat"] = ha("Continue", ga("continue")), - ["GoToStat"] = ha("Goto", ga("goto") * da(k("Name"), "Name")), - ["RetStat"] = ha("Return", ga("return") * (k("Expr") * (fa(",") * k("Expr")) ^ 0) ^ (-1) * fa(";") ^ (-1)), - ["ExprStat"] = r((k(c) * (o(function() - local za = {...} - local Aa = za[#za] - table.remove(za) - - for Ba, ya in ipairs(za) do - if ya.tag == "Id" then - orya.tag"Index" - else - za[Ba] = ya - - return false - end - end - - za.tag = "VarList" - za.pos = za[1].pos - - return true, { - ["tag"] = "Set", - ["pos"] = za.pos, - [1] = za, - [2] = Aa - } - end) * k("Assignment"))) + (k(c) * (o(function(R) - if R.tag == "Call" then - orR.tag"Invoke" - else - return true, R - end - - return false - end))), function(R, S, za, Aa, ...) return Aa(za, ...) end), - ["Assignment"] = ((fa(",") * k(c)) ^ 1) ^ (-1) * fa("=") * k("ExpList"), - ["Stat"] = k("IfStat") + k("WhileStat") + k("DoStat") + k("ForStat") + k("RepeatStat") + k("FuncStat") + k("LocalStat") + k("LabelStat") + k("BreakStat") + k("GoToStat") + k("ExprStat") + k("ContinueStat"), - ["Space"] = y ^ 1, - ["Equals"] = i("=") ^ 0, - ["Open"] = "[" * q(k("Equals"), "init") * "[" * i("\n") ^ (-1), - ["Close"] = "]" * l(k("Equals")) * "]", - ["CloseEQ"] = r(k("Close") * n("init"), function(R, S, ra, Ba) return ra == Ba end), - ["LongString"] = k("Open") * l((i(1) - k("CloseEQ")) ^ 0) * k("Close") / function(R, Ca) return R end, - ["Comment"] = i("--") * k("LongString") / function() return end + i("--") * (i(1) - i("\n")) ^ 0 + i("//") * (i(1) - i("\n")) ^ 0 + l(L) / function() return end, - ["Skip"] = (k("Space") + k("Comment")) ^ 0, - ["idStart"] = u + i("_"), - ["idRest"] = w + i("_"), - ["Keywords"] = i("and") + "break" + "do" + "elseif" + "else" + "end" + "false" + "for" + d + "goto" + "if" + "in" + "local" + "nil" + "not" + "or" + "repeat" + "return" + "then" + "true" + "until" + "while" + "continue", - ["Reserved"] = k("Keywords") * -k("idRest"), - ["Identifier"] = k("idStart") * k("idRest") ^ 0, - ["Name"] = -k("Reserved") * l(k("Identifier")) * -k("idRest"), - ["Hex"] = (i("0x") + i("0X")) * x ^ 1, - ["Expo"] = j("eE") * j("+-") ^ (-1) * v ^ 1, - ["Float"] = (((v ^ 1 * i(".") * v ^ 0) + (i(".") * v ^ 1)) * k("Expo") ^ (-1)) + (v ^ 1 * k("Expo")), - ["Int"] = v ^ 1, - ["Number"] = l(k("Hex") + k("Float") + k("Int")) / function(Y) return tonumber(Y) end, - ["ShortString"] = i([["]]) * l(((i("\\") * i(1)) + (i(1) - i([["]]))) ^ 0) * i([["]]) + i("'") * l(((i("\\") * i(1)) + (i(1) - i("'"))) ^ 0) * i("'"), - [a] = k("LongString") + (k("ShortString") / function(R) return R end), - ["OrOp"] = ga("or") / "or" + fa("||") / "or", - ["AndOp"] = ga("and") / "and" + fa("&&") / "and", - ["RelOp"] = fa("~=") / "ne" + fa("==") / "eq" + fa("<=") / "le" + fa(">=") / "ge" + fa("<") / "lt" + fa(">") / "gt" + fa("!=") / "ne", - ["BOrOp"] = fa("|") / "bor", - ["BXorOp"] = fa("~") / "bxor", - ["BAndOp"] = fa("&") / "band", - ["ShiftOp"] = fa("<<") / "shl" + fa(">>") / "shr", - ["ConOp"] = fa("..") / "concat", - ["AddOp"] = fa("+") / "add" + fa("-") / "sub", - ["MulOp"] = fa("*") / "mul" + fa("/") / "div" + fa("%") / "mod", - ["UnOp"] = ga("not") / "not" + fa("-") / "unm" + fa("#") / "len" + fa("~") / "bnot" + fa("!") / "not", - ["PowOp"] = fa("^") / "pow", - ["Shebang"] = i("#") * (i(1) - i("\n")) ^ 0 * i("\n"), - ["OneWord"] = k("Name") + k("Number") + k(a) + k("Reserved") + i("...") + i(1) - } - - local function Da(Ea, h, Fa) - local Ga = Fa[1] - forR = h - - do - if Ea[R].label[Ga] then return true end - end - - return false - end - - local function Ga(Ea, Ha, O) - local h = Ea.scope - local Ia = Ea[h].label[Ha] - - if not Ia then - Ea[h].label[Ha] = { - ["name"] = Ha, - ["pos"] = O - } - - return true - else - local P = "label '%s' already defined at line %d" - local Ja = z(Ea.errorinfo.subject, Ia.pos) - P = string.format(P, Ha, Ja) - - return nil, M(Ea.errorinfo, O, P) - end - end - - local function Ia(Ea, Fa) - local h = Ea.scope - table.insert(Ea[h].goto, Fa) - - return true - end - - local function Ja(Ea) - forR = Ea.maxscope - - do - for Ka, ya in ipairs(Ea[R].goto) do - if not Da(Ea, R, ya) then - local P = "no visible label '%s' for " - P = string.format(P, ya[1]) - - return nil, M(Ea.errorinfo, ya.pos, P) - end - end - end - - return true - end - - local function Ka(Ea, La) - Ea.Ea.fscope.is_vararg = La - end - - local Ma, Na, Oa - local Pa, Qa, Ra, Sa - - Sa = function(Ea, Ta) - local Ua = #Ta - local La = false - - if 0 < Ua and Ta[Ua].tag == "Dots" then - La = true - end - - Ka(Ea, La) - - return true - end - - local function Ua(Ea, Va) - C(Ea) - A(Ea) - local Wa, P = Sa(Ea, Va[1]) - if not Wa then return Wa, P end - Wa, P, Wa, P = Pa(Ea, Va[2]) - if not Wa then return Wa, P end - B(Ea) - D(Ea) - - return true - end - - local function Wa(Ea, Va) - local Xa, P = Na(Ea, Va[2]) - if not Xa then return Xa, P end - - if Va[3] then - Xa, P, Xa, P = Na(Ea, Va[3]) - if not Xa then return Xa, P end - end - - return true - end - - local function Xa(Ea, Va) - local Ya, P = Na(Ea, Va[1]) - if not Ya then return Ya, P end - - return true - end - - local function Ya(Ea, Za) - for ab, ya in ipairs(Za) do - local ia = ya.tag - - if ia == "Pair" then - local bb, P = Na(Ea, ya[1]) - if not bb then return bb, P end - bb, P, bb, P = Na(Ea, ya[2]) - if not bb then return bb, P end - else - local bb, P = Na(Ea, ya) - if not bb then return bb, P end - end - end - - return true - end - - local function ab(Ea, Va) - if not Ea.Ea.fscope.is_vararg then - local P = e - - return nil, M(Ea.errorinfo, Va.pos, P) - end - - return true - end - - local function bb(Ea, cb) - local db, P = Na(Ea, cb[1]) - if not db then return db, P end - forS = 2 - - do - db, P, db, P = Na(Ea, cb[S]) - if not db then return db, P end - end - - return true - end - - local function db(Ea, eb) - local fb, P = Na(Ea, eb[1]) - if not fb then return fb, P end - forS = 3 - - do - fb, P, fb, P = Na(Ea, eb[S]) - if not fb then return fb, P end - end - - return true - end - - local function fb(Ea, Fa) - local gb, P = Ra(Ea, Fa[1]) - if not gb then return gb, P end - gb, P, gb, P = Qa(Ea, Fa[2]) - if not gb then return gb, P end - - return true - end - - local function gb(Ea, Fa) - if not G(Ea) then - local P = " not inside a loop" - - return nil, M(Ea.errorinfo, Fa.pos, P) - end - - return true - end - - local function hb(Ea, Fa) - if not G(Ea) then - local P = " not inside a loop" - - return nil, M(Ea.errorinfo, Fa.pos, P) - end - - return true - end - - local function ib(Ea, Fa) - E(Ea) - A(Ea) - local jb, P = Qa(Ea, Fa[2]) - if not jb then return jb, P end - jb, P, jb, P = Pa(Ea, Fa[3]) - if not jb then return jb, P end - B(Ea) - F(Ea) - - return true - end - - local function jb(Ea, Fa) - local kb, P - E(Ea) - A(Ea) - kb, P, kb, P = Na(Ea, Fa[2]) - if not kb then return kb, P end - kb, P, kb, P = Na(Ea, Fa[3]) - if not kb then return kb, P end - - if Fa[5] then - kb, P, kb, P = Na(Ea, Fa[4]) - if not kb then return kb, P end - kb, P, kb, P = Pa(Ea, Fa[5]) - if not kb then return kb, P end - else - kb, P, kb, P = Pa(Ea, Fa[4]) - if not kb then return kb, P end - end - - B(Ea) - F(Ea) - - return true - end - - local function kb(Ea, Fa) - local lb, P = Ia(Ea, Fa) - if not lb then return lb, P end - - return true - end - - local function lb(Ea, Fa) - local mb = #Fa - - if mb % 2 == 0 then - forS = 1 - - do - local nb, P = Na(Ea, Fa[S]) - if not nb then return nb, P end - nb, P, nb, P = Pa(Ea, Fa[S + 1]) - if not nb then return nb, P end - end - else - forS = 1 - - do - local nb, P = Na(Ea, Fa[S]) - if not nb then return nb, P end - nb, P, nb, P = Pa(Ea, Fa[S + 1]) - if not nb then return nb, P end - end - - local nb, P = Pa(Ea, Fa[mb]) - if not nb then return nb, P end - end - - return true - end - - local function mb(Ea, Fa) - local nb, P = Ga(Ea, Fa[1], Fa.pos) - if not nb then return nb, P end - - return true - end - - local function nb(Ea, Fa) - local ob, P = Qa(Ea, Fa[2]) - if not ob then return ob, P end - - return true - end - - local function ob(Ea, Fa) - local pb, P = Na(Ea, Fa[2][1]) - if not pb then return pb, P end - - return true - end - - local function pb(Ea, Fa) - E(Ea) - local qb, P = Pa(Ea, Fa[1]) - if not qb then return qb, P end - qb, P, qb, P = Na(Ea, Fa[2]) - if not qb then return qb, P end - F(Ea) - - return true - end - - local function qb(Ea, Fa) - local rb, P = Qa(Ea, Fa) - if not rb then return rb, P end - - return true - end - - local function rb(Ea, Fa) - E(Ea) - local sb, P = Na(Ea, Fa[1]) - if not sb then return sb, P end - sb, P, sb, P = Pa(Ea, Fa[2]) - if not sb then return sb, P end - F(Ea) - - return true - end - - Oa = function(Ea, sb) - local ia = sb.tag - - if ia == "Id" then - return true - elseif ia == "Index" then - thenelse"expecting a variable, but got a " - ia() - end - end - - Ra = function(Ea, tb) - for ub, ya in ipairs(tb) do - local vb, P = Oa(Ea, ya) - if not vb then return vb, P end - end - - return true - end - - Na = function(Ea, Va) - local ia = Va.tag - - if ia == "Nil" then - oria"True" - oria"False" - oria"Number" - oria"someString" - - return true - elseif ia == "Dots" then - elseif ia == "Function" then - elseif ia == "Table" then - elseif ia == "Op" then - elseif ia == "Paren" then - elseif ia == "Call" then - elseif ia == "Invoke" then - elseif ia == "Id" then - oria"Index" - thenelse"expecting an expression, but got a " - ia() - end - end - - Qa = function(Ea, ub) - for vb, ya in ipairs(ub) do - local wb, P = Na(Ea, ya) - if not wb then return wb, P end - end - - return true - end - - Ma = function(Ea, Fa) - local ia = Fa.tag - - if ia == "Do" then - return Pa(Ea, Fa) - elseif ia == "Set" then - elseif ia == "While" then - elseif ia == "Repeat" then - elseif ia == "If" then - elseif ia == "Fornum" then - elseif ia == "Forin" then - elseif ia == "Local" then - elseif ia == "Localrec" then - elseif ia == "Goto" then - elseif ia == "Label" then - elseif ia == "Return" then - elseif ia == "Break" then - elseif ia == "Continue" then - elseif ia == "Call" then - elseif ia == "Invoke" then - thenelse"expecting a statement, but got a " - ia() - end - end - - Pa = function(Ea, vb) - local wb = {} - A(Ea) - - for xb, ya in ipairs(vb) do - local yb, P = Ma(Ea, ya) - if not yb then return yb, P end - end - - B(Ea) - - return true - end - - local function wb(xb, N) - assert(type(xb) == "table") - assert(type(N) == "table") - - local Ea = { - [b] = N, - [d] = {} - } - - C(Ea) - Ka(Ea, true) - local yb, P = Pa(Ea, xb) - if not yb then return yb, P end - D(Ea) - yb, P, yb, P = Ja(Ea) - if not yb then return yb, P end - - return xb - end - - f.parse = function(yb, zb) - local N = { - ["subject"] = yb, - ["filename"] = zb - } - - g.setmaxstack(1000) - local xb, Ab = g.match(va, yb, nil, N) - if not xb then return xb, Ab end - - return wb(xb, N) - end - - return f diff --git a/src/scope.lua b/src/scope.lua index b5fd3c6..acf8eb5 100644 --- a/src/scope.lua +++ b/src/scope.lua @@ -1,74 +1,74 @@ ---[[ -This module implements functions that handle scoping rules -]] -local scope = {} - -function scope.lineno (s, i) - if i == 1 then return 1, 1 end - local l, lastline = 0, "" - s = s:sub(1, i) .. "\n" - for line in s:gmatch("[^\n]*[\n]") do - l = l + 1 - lastline = line - end - local c = lastline:len() - 1 - return l, c ~= 0 and c or 1 -end - -function scope.new_scope (env) - if not env.scope then - env.scope = 0 - else - env.scope = env.scope + 1 - end - local scope = env.scope - env.maxscope = scope - env[scope] = {} - env[scope]["label"] = {} - env[scope]["local"] = {} - env[scope]["goto"] = {} -end - -function scope.begin_scope (env) - env.scope = env.scope + 1 -end - -function scope.end_scope (env) - env.scope = env.scope - 1 -end - -function scope.new_function (env) - if not env.fscope then - env.fscope = 0 - else - env.fscope = env.fscope + 1 - end - local fscope = env.fscope - env["function"][fscope] = {} -end - -function scope.begin_function (env) - env.fscope = env.fscope + 1 -end - -function scope.end_function (env) - env.fscope = env.fscope - 1 -end - -function scope.begin_loop (env) - if not env.loop then - env.loop = 1 - else - env.loop = env.loop + 1 - end -end - -function scope.end_loop (env) - env.loop = env.loop - 1 -end - -function scope.insideloop (env) - return env.loop and env.loop > 0 -end - -return scope +--[[ +This module implements functions that handle scoping rules +]] +local scope = {} + +function scope.lineno (s, i) + if i == 1 then return 1, 1 end + local l, lastline = 0, "" + s = s:sub(1, i) .. "\n" + for line in s:gmatch("[^\n]*[\n]") do + l = l + 1 + lastline = line + end + local c = lastline:len() - 1 + return l, c ~= 0 and c or 1 +end + +function scope.new_scope (env) + if not env.scope then + env.scope = 0 + else + env.scope = env.scope + 1 + end + local scope = env.scope + env.maxscope = scope + env[scope] = {} + env[scope]["label"] = {} + env[scope]["local"] = {} + env[scope]["goto"] = {} +end + +function scope.begin_scope (env) + env.scope = env.scope + 1 +end + +function scope.end_scope (env) + env.scope = env.scope - 1 +end + +function scope.new_function (env) + if not env.fscope then + env.fscope = 0 + else + env.fscope = env.fscope + 1 + end + local fscope = env.fscope + env["function"][fscope] = {} +end + +function scope.begin_function (env) + env.fscope = env.fscope + 1 +end + +function scope.end_function (env) + env.fscope = env.fscope - 1 +end + +function scope.begin_loop (env) + if not env.loop then + env.loop = 1 + else + env.loop = env.loop + 1 + end +end + +function scope.end_loop (env) + env.loop = env.loop - 1 +end + +function scope.insideloop (env) + return env.loop and env.loop > 0 +end + +return scope diff --git a/src/uglify.lua b/src/uglify.lua index 143f67f..f529fcb 100644 --- a/src/uglify.lua +++ b/src/uglify.lua @@ -1,81 +1,78 @@ -local u = {} -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"} - -u.uglify = function(str) - assert(str ~= nil, "Cannot uglify a nil string") - local avalchars = {} - local capture_chars = {"%", "(", "[", "\13"} - local skipchars = {} - - for k, v in pairs(capture_chars) do - skipchars[string.byte(v, 1)] = true - end - - for k = 1, 128 do - if not skipchars[k] then - if string.find(str, string.char(k)) then - avalchars[k] = false - else - avalchars[k] = true - end - end - end - - for k, v in pairs(skipchars) do - avalchars[k] = false - end - - local counts = {} - for k, v in ipairs(nonames) do - counts[v] = 0 - end - for k,_ in pairs(counts) do - for i,j in string.gmatch(str,k) do - counts[k] = counts[k] + 1 - end - end - - local prettifycode = [[ - local function p(s) local r = {%s} for k,v in pairs(r) do s = s:gsub(v[2],v[1]) end return s end]] - local replacementtbl = {} - local cursor = 1 --Start at 1 because 0 is the null character - - for k, v in ipairs(nonames) do - if counts[v] ~= 0 then - while not avalchars[cursor] do - cursor = cursor + 1 - end - - replacementtbl[v] = cursor - avalchars[cursor] = false - end - end - - assert(cursor < 128, "Unable to uglify file, not enough unused characters!") - local replacementstr = {} - - for k, v in pairs(replacementtbl) do - local trs = string.format("{%q,%q}", k, string.char(v)) - replacementstr[#replacementstr + 1] = trs - end - - local frepstr = table.concat(replacementstr, ",") - local pcd = string.format(prettifycode, frepstr) - - for k, v in pairs(replacementtbl) do - str = str:gsub(k, string.char(v)) - end - - local numdeepcom = 1 - - local uglified = table.concat({pcd, "\n", "return assert(loadstring(p([", string.rep("=", numdeepcom), "[", str, "]", string.rep("=", numdeepcom), "])))()"}) - - if #uglified > #str then - print("WARNING: Uglification hurt rather than helped! Put more code in one file!") - end - - return uglified -end - ---prettifycode = string.format(prettifycode,) -return u +local u = {} +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"} + +u.uglify = function(str) + assert(str ~= nil, "Cannot uglify a nil string") + assert(type(str) == "string", "Can only uglify strings") + local avalchars = {} + local capture_chars = {"%", "(", "[", "\13"} + local skipchars = {} + + for k, v in pairs(capture_chars) do + skipchars[string.byte(v, 1)] = true + end + + for k = 1, 128 do + if not skipchars[k] then + if string.find(str, string.char(k)) then + avalchars[k] = false + else + avalchars[k] = true + end + end + end + + for k, v in pairs(skipchars) do + avalchars[k] = false + end + + local counts = {} + for k, v in ipairs(nonames) do + counts[v] = 0 + end + for k,_ in pairs(counts) do + for i,j in string.gmatch(str,k) do + counts[k] = counts[k] + 1 + end + end + + local prettifycode = [[ + local function p(s) local r = {%s} for k,v in pairs(r) do s = s:gsub(v[2],v[1]) end return s end]] + local replacementtbl = {} + local cursor = 1 --Start at 1 because 0 is the null character + + for k, v in ipairs(nonames) do + if counts[v] ~= 0 then + while not avalchars[cursor] do + cursor = cursor + 1 + end + + replacementtbl[v] = cursor + avalchars[cursor] = false + end + end + + assert(cursor < 128, "Unable to uglify file, not enough unused characters!") + local replacementstr = {} + + for k, v in pairs(replacementtbl) do + local trs = string.format("{%q,%q}", k, string.char(v)) + replacementstr[#replacementstr + 1] = trs + end + + local frepstr = table.concat(replacementstr, ",") + local pcd = string.format(prettifycode, frepstr) + + for k, v in pairs(replacementtbl) do + str = str:gsub(k, string.char(v)) + end + + local numdeepcom = 1 + + local uglified = table.concat({pcd, "\n", "return assert(loadstring(p([", string.rep("=", numdeepcom), "[", str, "]", string.rep("=", numdeepcom), "])))()"}) + + return uglified +end + +--prettifycode = string.format(prettifycode,) +return u -- cgit v1.2.3-70-g09d2