aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ast_opts.lua251
-rw-r--r--src/glum.lua1611
-rw-r--r--src/minify.lua13
-rw-r--r--src/parser.lua1716
-rw-r--r--src/parser_min.lua804
-rw-r--r--src/scope.lua148
-rw-r--r--src/uglify.lua159
7 files changed, 2000 insertions, 2702 deletions
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{ <string> } -- goto str
- | `Label{ <string> } -- ::str::
- | `Return{ <expr*> } -- return e1, e2...
- | `Break -- break
- | apply
-
-expr:
- `Nil
- | `Dots
- | `True
- | `False
- | `Number{ <number> }
- | `String{ <string> }
- | `Function{ { `Id{ <string> }* `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{ <string> } expr* }
-
-lhs: `Id{ <string> } | `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 <goto>"
- 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 = "<break> 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 = "<continue> 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{ <string> }
- 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{ <number> }
- tag == "String" then -- `String{ <string> }
- return true
- elseif tag == "Dots" then
- return traverse_vararg(env, exp)
- elseif tag == "Function" then -- `Function{ { `Id{ <string> }* `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{ <string> expr* }
- return traverse_invoke(env, exp)
- elseif tag == "Id" or -- `Id{ <string> }
- 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{ <string> }
- return traverse_goto(env, stm)
- elseif tag == "Label" then -- `Label{ <string> }
- return traverse_label(env, stm)
- elseif tag == "Return" then -- `Return{ <expr>* }
- 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{ <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{ <string> } -- goto str
+ | `Label{ <string> } -- ::str::
+ | `Return{ <expr*> } -- return e1, e2...
+ | `Break -- break
+ | apply
+
+expr:
+ `Nil
+ | `Dots
+ | `True
+ | `False
+ | `Number{ <number> }
+ | `String{ <string> }
+ | `Function{ { `Id{ <string> }* `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{ <string> } expr* }
+
+lhs: `Id{ <string> } | `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 <goto>"
+ 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 = "<break> 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 = "<continue> 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{ <string> }
+ 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{ <number> }
+ tag == "String" then -- `String{ <string> }
+ return true
+ elseif tag == "Dots" then
+ return traverse_vararg(env, exp)
+ elseif tag == "Function" then -- `Function{ { `Id{ <string> }* `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{ <string> expr* }
+ return traverse_invoke(env, exp)
+ elseif tag == "Id" or -- `Id{ <string> }
+ 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{ <string> }
+ return traverse_goto(env, stm)
+ elseif tag == "Label" then -- `Label{ <string> }
+ return traverse_label(env, stm)
+ elseif tag == "Return" then -- `Return{ <expr>* }
+ 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{ <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 <goto>"
- 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 = "<break> 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 = "<continue> 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