Advertisement
SquidDev

LuaMinifier.lua

Jan 11th, 2015
596
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 53.96 KB | None | 0 0
  1. local function _W(f) local e=setmetatable({}, {__index = getfenv()}) return setfenv(f,e)() or e end
  2. Utils=_W(function()
  3. return {
  4.     CreateLookup = function(tbl)
  5.         for _, v in ipairs(tbl) do
  6.             tbl[v] = true
  7.         end
  8.         return tbl
  9.     end
  10. }
  11. end)
  12. Constants=_W(function()
  13. --- Lexer constants
  14. -- @module lexer.Constants
  15.  
  16. createLookup = Utils.CreateLookup
  17.  
  18. --- List of white chars
  19. WhiteChars = createLookup{' ', '\n', '\t', '\r'}
  20.  
  21. --- Lookup of escape characters
  22. EscapeLookup = {['\r'] = '\\r', ['\n'] = '\\n', ['\t'] = '\\t', ['"'] = '\\"', ["'"] = "\\'"}
  23.  
  24. --- Lookup of lower case characters
  25. LowerChars = createLookup{
  26.     'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
  27.     'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
  28. }
  29.  
  30. --- Lookup of upper case characters
  31. UpperChars = createLookup{
  32.     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
  33.     'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
  34. }
  35.  
  36. --- Lookup of digits
  37. Digits = createLookup{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
  38.  
  39. --- Lookup of hex digits
  40. HexDigits = createLookup{
  41.     '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
  42.     'A', 'a', 'B', 'b', 'C', 'c', 'D', 'd', 'E', 'e', 'F', 'f'
  43. }
  44.  
  45. --- Lookup of valid symbols
  46. Symbols = createLookup{'+', '-', '*', '/', '^', '%', ',', '{', '}', '[', ']', '(', ')', ';', '#'}
  47.  
  48. --- Lookup of valid keywords
  49. Keywords = createLookup{
  50.     'and', 'break', 'do', 'else', 'elseif',
  51.     'end', 'false', 'for', 'function', 'goto', 'if',
  52.     'in', 'local', 'nil', 'not', 'or', 'repeat',
  53.     'return', 'then', 'true', 'until', 'while',
  54. }
  55.  
  56. --- Keywords that end a block
  57. StatListCloseKeywords = createLookup{'end', 'else', 'elseif', 'until'}
  58.  
  59. --- Unary operators
  60. UnOps = createLookup{'-', 'not', '#'}
  61. end)
  62. Scope=_W(function()
  63. --- Holds variables for one scope
  64. -- This implementation is inefficient. Instead of using hashes,
  65. -- a linear search is used instead to look up variables
  66. -- @module lexer.Scope
  67.  
  68. local keywords = Constants.Keywords
  69.  
  70. --- Holds the data for one variable
  71. -- @table Variable
  72. -- @tfield Scope Scope The parent scope
  73. -- @tfield string Name The name of the variable
  74. -- @tfield boolean IsGlobal Is the variable global
  75. -- @tfield boolean CanRename If the variable can be renamed
  76. -- @tfield int References Number of references
  77.  
  78. --- Holds variables for one scope
  79. -- @type Scope
  80. -- @tfield ?|Scope Parent The parent scope
  81. -- @tfield table Locals A list of locals variables
  82. -- @tfield table Globals A list of global variables
  83. -- @tfield table Children A list of children @{Scope|scopes}
  84.  
  85. local Scope = {}
  86.  
  87. --- Add a local to this scope
  88. -- @tparam Variable variable The local object
  89. function Scope:AddLocal(variable)
  90.     table.insert(self.Locals, variable)
  91. end
  92.  
  93. --- Create a @{Variable} and add it to the scope
  94. -- @tparam string name The name of the local
  95. -- @treturn Variable The created local
  96. function Scope:CreateLocal(name)
  97.     local variable = self:GetLocal(name)
  98.     if variable then return variable end
  99.  
  100.     variable = {
  101.         Scope = self,
  102.         Name= name,
  103.         IsGlobal = false,
  104.         CanRename = true,
  105.         References = 1,
  106.     }
  107.  
  108.     self:AddLocal(variable)
  109.     return variable
  110. end
  111.  
  112. --- Get a local variable
  113. -- @tparam string name The name of the local
  114. -- @treturn ?|Variable The variable
  115. function Scope:GetLocal(name)
  116.     for k, var in pairs(self.Locals) do
  117.         if var.Name == name then return var end
  118.     end
  119.  
  120.     if self.Parent then
  121.         return self.Parent:GetLocal(name)
  122.     end
  123. end
  124.  
  125. --- Find an local variable by its old name
  126. -- @tparam string name The old name of the local
  127. -- @treturn ?|Variable The local variable
  128. function Scope:GetOldLocal(name)
  129.     if self.oldLocalNamesMap[name] then
  130.         return self.oldLocalNamesMap[name]
  131.     end
  132.     return self:GetLocal(name)
  133. end
  134.  
  135. --- Rename a local variable
  136. -- @tparam string|Variable oldName The old variable name
  137. -- @tparam string newName The new variable name
  138. function Scope:RenameLocal(oldName, newName)
  139.     oldName = type(oldName) == 'string' and oldName or oldName.Name
  140.     local found = false
  141.     local var = self:GetLocal(oldName)
  142.     if var then
  143.         var.Name = newName
  144.         self.oldLocalNamesMap[oldName] = var
  145.         found = true
  146.     end
  147.     if not found and self.Parent then
  148.         self.Parent:RenameLocal(oldName, newName)
  149.     end
  150. end
  151.  
  152. --- Add a global to this scope
  153. -- @tparam Variable name The name of the global
  154. function Scope:AddGlobal(name)
  155.     table.insert(self.Globals, name)
  156. end
  157.  
  158. --- Create a @{Variable} and add it to the scope
  159. -- @tparam string name The name of the global
  160. -- @treturn Variable The created global
  161. function Scope:CreateGlobal(name)
  162.     local variable = self:GetGlobal(name)
  163.     if variable then return variable end
  164.  
  165.     variable = {
  166.         Scope = self,
  167.         Name= name,
  168.         IsGlobal = true,
  169.         CanRename = true,
  170.         References = 1,
  171.     }
  172.  
  173.     self:AddGlobal(variable)
  174.     return variable
  175. end
  176.  
  177. --- Get a global variable
  178. -- @tparam string name The name of the global
  179. -- @treturn ?|Variable The variable
  180. function Scope:GetGlobal(name)
  181.     for k, v in pairs(self.Globals) do
  182.         if v.Name == name then return v end
  183.     end
  184.  
  185.     if self.Parent then
  186.         return self.Parent:GetGlobal(name)
  187.     end
  188. end
  189.  
  190. --- Find a Global by its old name
  191. -- @tparam string name The old name of the global
  192. -- @treturn ?|Variable The variable
  193. function Scope:GetOldGlobal(name)
  194.     if self.oldGlobalNamesMap[name] then
  195.         return self.oldGlobalNamesMap[name]
  196.     end
  197.     return self:GetGlobal(name)
  198. end
  199.  
  200. --- Rename a global variable
  201. -- @tparam string|Variable oldName The old variable name
  202. -- @tparam string newName The new variable name
  203. function Scope:RenameGlobal(oldName, newName)
  204.     oldName = type(oldName) == 'string' and oldName or oldName.Name
  205.     local found = false
  206.     local var = self:GetGlobal(oldName)
  207.     if var then
  208.         var.Name = newName
  209.         self.oldGlobalNamesMap[oldName] = var
  210.         found = true
  211.     end
  212.     if not found and self.Parent then
  213.         self.Parent:RenameGlobal(oldName, newName)
  214.     end
  215. end
  216.  
  217. --- Get a variable by name
  218. -- @tparam string name The name of the variable
  219. -- @treturn ?|Variable The found variable
  220. -- @fixme This is a very inefficient implementation, as with @{Scope:GetLocal} and @{Scope:GetGlocal}
  221. function Scope:GetVariable(name)
  222.     return self:GetLocal(name) or self:GetGlobal(name)
  223. end
  224.  
  225. --- Find an variable by its old name
  226. -- @tparam string name The old name of the variable
  227. -- @treturn ?|Variable The variable
  228. function Scope:GetOldVariable(name)
  229.     return self:GetOldLocal(name) or self:GetOldGlobal(name)
  230. end
  231.  
  232. --- Rename a variable
  233. -- @tparam string|Variable oldName The old variable name
  234. -- @tparam string newName The new variable name
  235. function Scope:RenameVariable(oldName, newName)
  236.     oldName = type(oldName) == 'string' and oldName or oldName.Name
  237.     if self:GetLocal(oldName) then
  238.         self:RenameLocal(oldName, newName)
  239.     else
  240.         self:RenameGlobal(oldName, newName)
  241.     end
  242. end
  243.  
  244. --- Get all variables in the scope
  245. -- @treturn table A list of @{Variable|variables}
  246. function Scope:GetAllVariables()
  247.     return self:getVars(true, self:getVars(true))
  248. end
  249.  
  250. --- Get all variables
  251. -- @tparam boolean top If this values is the 'top' of the function stack
  252. -- @tparam table ret Table to fill with return values (optional)
  253. -- @treturn table The variables
  254. -- @local
  255. function Scope:getVars(top, ret)
  256.     local ret = ret or {}
  257.     if top then
  258.         for k, v in pairs(self.Children) do
  259.             v:getVars(true, ret)
  260.         end
  261.     else
  262.         for k, v in pairs(self.Locals) do
  263.             table.insert(ret, v)
  264.         end
  265.         for k, v in pairs(self.Globals) do
  266.             table.insert(ret, v)
  267.         end
  268.         if self.Parent then
  269.             self.Parent:getVars(false, ret)
  270.         end
  271.     end
  272.     return ret
  273. end
  274.  
  275. --- Rename all locals to smaller values
  276. -- @tparam string validNameChars All characters that can be used to make a variable name
  277. -- @fixme Some of the string generation happens a lot, this could be looked at
  278. function Scope:ObfuscateLocals(validNameChars)
  279.     -- Use values sorted for letter frequency instead
  280.     local startChars = validNameChars or "etaoinshrdlucmfwypvbgkqjxz_ETAOINSHRDLUCMFWYPVBGKQJXZ"
  281.     local otherChars = validNameChars or "etaoinshrdlucmfwypvbgkqjxz_0123456789ETAOINSHRDLUCMFWYPVBGKQJXZ"
  282.  
  283.     local startCharsLength, otherCharsLength = #startChars, #otherChars
  284.     local index = 0
  285.     local floor = math.floor
  286.     for _, var in pairs(self.Locals) do
  287.         local name
  288.  
  289.         repeat
  290.             if index < startCharsLength then
  291.                 index = index + 1
  292.                 name = startChars:sub(index, index)
  293.             else
  294.                 if index < startCharsLength then
  295.                     index = index + 1
  296.                     name = startChars:sub(index, index)
  297.                 else
  298.                     local varIndex = floor(index / startCharsLength)
  299.                     local offset = index % startCharsLength
  300.                     name = startChars:sub(offset, offset)
  301.  
  302.                     while varIndex > 0 do
  303.                         offset = varIndex % otherCharsLength
  304.                         name = otherChars:sub(offset, offset) .. name
  305.                         varIndex = floor(varIndex / otherCharsLength)
  306.                     end
  307.                     index = index + 1
  308.                 end
  309.             end
  310.         until not (keywords[name] or self:GetVariable(name))
  311.         self:RenameLocal(var.Name, name)
  312.     end
  313. end
  314.  
  315. --- Converts the scope to a string
  316. -- No, it actually just returns '&lt;scope&gt;'
  317. -- @treturn string '&lt;scope&gt;'
  318. function Scope:ToString()
  319.     return '<Scope>'
  320. end
  321.  
  322. --- Create a new scope
  323. -- @tparam Scope parent The parent scope
  324. -- @treturn Scope The created scope
  325. local function NewScope(parent)
  326.     local scope = setmetatable({
  327.         Parent = parent,
  328.         Locals = { },
  329.         Globals = { },
  330.         oldLocalNamesMap = { },
  331.         oldGlobalNamesMap = { },
  332.         Children = { },
  333.     }, { __index = Scope })
  334.  
  335.     if parent then
  336.         table.insert(parent.Children, scope)
  337.     end
  338.  
  339.     return scope
  340. end
  341.  
  342. return NewScope
  343. end)
  344. TokenList=_W(function()
  345. --- Provides utilities for reading tokens from a 'stream'
  346. -- @module lexer.TokenList
  347.  
  348. --- Stores a list of tokens
  349. -- @type TokenList
  350. -- @tfield table tokens List of tokens
  351. -- @tfield number pointer Pointer to the current
  352. -- @tfield table savedPointers A save point
  353. local TokenList = {}
  354.  
  355. --- Get this element in the token list
  356. -- @tparam int offset The offset in the token list
  357. function TokenList:Peek(offset)
  358.     local tokens = self.tokens
  359.     offset = offset or 0
  360.     return tokens[math.min(#tokens, self.pointer+offset)]
  361. end
  362.  
  363. --- Get the next token in the list
  364. -- @tparam table tokenList Add the token onto this table
  365. -- @treturn Token The token
  366. function TokenList:Get(tokenList)
  367.     local tokens = self.tokens
  368.     local pointer = self.pointer
  369.     local token = tokens[pointer]
  370.     self.pointer = math.min(pointer + 1, #tokens)
  371.     if tokenList then
  372.         table.insert(tokenList, token)
  373.     end
  374.     return token
  375. end
  376.  
  377. --- Check if the next token is of a type
  378. -- @tparam string type The type to compare it with
  379. -- @treturn bool If the type matches
  380. function TokenList:Is(type)
  381.     return self:Peek().Type == type
  382. end
  383.  
  384. --- Save position in a stream
  385. function TokenList:Save()
  386.     table.insert(self.savedPointers, self.pointer)
  387. end
  388.  
  389. --- Remove the last position in the stream
  390. function TokenList:Commit()
  391.     local savedPointers = self.savedPointers
  392.     savedPointers[#savedPointers] = nil
  393. end
  394.  
  395. --- Restore to the previous save point
  396. function TokenList:Restore()
  397.     local savedPointers = self.savedPointers
  398.     local sPLength = #savedPointers
  399.     self.pointer = savedP[sPLength]
  400.     savedPointers[sPLength] = nil
  401. end
  402.  
  403. --- Check if the next token is a symbol and return it
  404. -- @tparam string symbol Symbol to check (Optional)
  405. -- @tparam table tokenList Add the token onto this table
  406. -- @treturn[0] ?|token If symbol is not specified, return the token
  407. -- @treturn[1] boolean If symbol is specified, return true if it matches
  408. function TokenList:ConsumeSymbol(symbol, tokenList)
  409.     local token = self:Peek()
  410.     if token.Type == 'Symbol' then
  411.         if symbol then
  412.             if token.Data == symbol then
  413.                 self:Get(tokenList)
  414.                 return true
  415.             else
  416.                 return nil
  417.             end
  418.         else
  419.             self:Get(tokenList)
  420.             return token
  421.         end
  422.     else
  423.         return nil
  424.     end
  425. end
  426.  
  427. --- Check if the next token is a keyword and return it
  428. -- @tparam string kw Keyword to check (Optional)
  429. -- @tparam table tokenList Add the token onto this table
  430. -- @treturn[0] ?|token If kw is not specified, return the token
  431. -- @treturn[1] boolean If kw is specified, return true if it matches
  432. function TokenList:ConsumeKeyword(kw, tokenList)
  433.     local token = self:Peek()
  434.     if token.Type == 'Keyword' and token.Data == kw then
  435.         self:Get(tokenList)
  436.         return true
  437.     else
  438.         return nil
  439.     end
  440. end
  441.  
  442. --- Check if the next token matches is a keyword
  443. -- @tparam string kw The particular keyword
  444. -- @treturn boolean If it matches or not
  445. function TokenList:IsKeyword(kw)
  446.     local token = self:Peek()
  447.     return token.Type == 'Keyword' and token.Data == kw
  448. end
  449.  
  450. --- Check if the next token matches is a symbol
  451. -- @tparam string symbol The particular symbol
  452. -- @treturn boolean If it matches or not
  453. function TokenList:IsSymbol(symbol)
  454.     local token = self:Peek()
  455.     return token.Type == 'Symbol' and token.Data == symbol
  456. end
  457.  
  458. --- Check if the next token is an end of file
  459. -- @treturn boolean If the next token is an end of file
  460. function TokenList:IsEof()
  461.     return self:Peek().Type == 'Eof'
  462. end
  463.  
  464. --- Produce a string off all tokens
  465. -- @tparam boolean includeLeading Include the leading whitespace
  466. -- @treturn string The resulting string
  467. function TokenList:Print(includeLeading)
  468.     includeLeading = (includeLeading == nil and true or includeLeading)
  469.  
  470.     local out = ""
  471.     for _, token in ipairs(self.tokens) do
  472.         if includeLeading then
  473.             for _, whitespace in ipairs(token.LeadingWhite) do
  474.                 out = out .. whitespace:Print() .. "\n"
  475.             end
  476.         end
  477.         out = out .. token:Print() .. "\n"
  478.     end
  479.  
  480.     return out
  481. end
  482.  
  483. return TokenList
  484. end)
  485. Parse=_W(function()
  486. --- The main lua parser and lexer.
  487. -- LexLua returns a Lua token stream, with tokens that preserve
  488. -- all whitespace formatting information.
  489. -- ParseLua returns an AST, internally relying on LexLua.
  490. -- @module lexer.Parse
  491.  
  492. local createLookup = Utils.CreateLookup
  493.  
  494. local lowerChars = Constants.LowerChars
  495. local upperChars = Constants.UpperChars
  496. local digits = Constants.Digits
  497. local symbols = Constants.Symbols
  498. local hexDigits = Constants.HexDigits
  499. local keywords = Constants.Keywords
  500. local statListCloseKeywords = Constants.StatListCloseKeywords
  501. local unops = Constants.UnOps
  502. local setmeta = setmetatable
  503.  
  504. --- One token
  505. -- @table Token
  506. -- @tparam string Type The token type
  507. -- @param Data Data about the token
  508. -- @tparam string CommentType The type of comment  (Optional)
  509. -- @tparam number Line Line number (Optional)
  510. -- @tparam number Char Character number (Optional)
  511. local Token = {}
  512.  
  513. --- Creates a string representation of the token
  514. -- @treturn string The resulting string
  515. function Token:Print()
  516.     return "<"..(self.Type .. string.rep(' ', math.max(3, 12-#self.Type))).."  "..(self.Data or '').." >"
  517. end
  518.  
  519. local tokenMeta = { __index = Token }
  520.  
  521. --- Create a list of @{Token|tokens} from a Lua source
  522. -- @tparam string src Lua source code
  523. -- @treturn TokenList The list of @{Token|tokens}
  524. local function LexLua(src)
  525.     --token dump
  526.     local tokens = {}
  527.  
  528.     do -- Main bulk of the work
  529.         --line / char / pointer tracking
  530.         local pointer = 1
  531.         local line = 1
  532.         local char = 1
  533.  
  534.         --get / peek functions
  535.         local function get()
  536.             local c = src:sub(pointer,pointer)
  537.             if c == '\n' then
  538.                 char = 1
  539.                 line = line + 1
  540.             else
  541.                 char = char + 1
  542.             end
  543.             pointer = pointer + 1
  544.             return c
  545.         end
  546.         local function peek(n)
  547.             n = n or 0
  548.             return src:sub(pointer+n,pointer+n)
  549.         end
  550.         local function consume(chars)
  551.             local c = peek()
  552.             for i = 1, #chars do
  553.                 if c == chars:sub(i,i) then return get() end
  554.             end
  555.         end
  556.  
  557.         --shared stuff
  558.         local function generateError(err)
  559.             error(">> :"..line..":"..char..": "..err, 0)
  560.         end
  561.  
  562.         local function tryGetLongString()
  563.             local start = pointer
  564.             if peek() == '[' then
  565.                 local equalsCount = 0
  566.                 local depth = 1
  567.                 while peek(equalsCount+1) == '=' do
  568.                     equalsCount = equalsCount + 1
  569.                 end
  570.                 if peek(equalsCount+1) == '[' then
  571.                     --start parsing the string. Strip the starting bit
  572.                     for _ = 0, equalsCount+1 do get() end
  573.  
  574.                     --get the contents
  575.                     local contentStart = pointer
  576.                     while true do
  577.                         --check for eof
  578.                         if peek() == '' then
  579.                             generateError("Expected `]"..string.rep('=', equalsCount).."]` near <eof>.", 3)
  580.                         end
  581.  
  582.                         --check for the end
  583.                         local foundEnd = true
  584.                         if peek() == ']' then
  585.                             for i = 1, equalsCount do
  586.                                 if peek(i) ~= '=' then foundEnd = false end
  587.                             end
  588.                             if peek(equalsCount+1) ~= ']' then
  589.                                 foundEnd = false
  590.                             end
  591.                         else
  592.                             if peek() == '[' then
  593.                                 -- is there an embedded long string?
  594.                                 local embedded = true
  595.                                 for i = 1, equalsCount do
  596.                                     if peek(i) ~= '=' then
  597.                                         embedded = false
  598.                                         break
  599.                                     end
  600.                                 end
  601.                                 if peek(equalsCount + 1) == '[' and embedded then
  602.                                     -- oh look, there was
  603.                                     depth = depth + 1
  604.                                     for i = 1, (equalsCount + 2) do
  605.                                         get()
  606.                                     end
  607.                                 end
  608.                             end
  609.                             foundEnd = false
  610.                         end
  611.  
  612.                         if foundEnd then
  613.                             depth = depth - 1
  614.                             if depth == 0 then
  615.                                 break
  616.                             else
  617.                                 for i = 1, equalsCount + 2 do
  618.                                     get()
  619.                                 end
  620.                             end
  621.                         else
  622.                             get()
  623.                         end
  624.                     end
  625.  
  626.                     --get the interior string
  627.                     local contentString = src:sub(contentStart, pointer-1)
  628.  
  629.                     --found the end. Get rid of the trailing bit
  630.                     for i = 0, equalsCount+1 do get() end
  631.  
  632.                     --get the exterior string
  633.                     local longString = src:sub(start, pointer-1)
  634.  
  635.                     --return the stuff
  636.                     return contentString, longString
  637.                 else
  638.                     return nil
  639.                 end
  640.             else
  641.                 return nil
  642.             end
  643.         end
  644.  
  645.         --main token emitting loop
  646.         while true do
  647.             --get leading whitespace. The leading whitespace will include any comments
  648.             --preceding the token. This prevents the parser needing to deal with comments
  649.             --separately.
  650.             local leading = { }
  651.             local leadingWhite = ''
  652.             local longStr = false
  653.             while true do
  654.                 local c = peek()
  655.                 if c == '#' and peek(1) == '!' and line == 1 then
  656.                     -- #! shebang for linux scripts
  657.                     get()
  658.                     get()
  659.                     leadingWhite = "#!"
  660.                     while peek() ~= '\n' and peek() ~= '' do
  661.                         leadingWhite = leadingWhite .. get()
  662.                     end
  663.  
  664.                     table.insert(leading, setmeta({
  665.                         Type = 'Comment',
  666.                         CommentType = 'Shebang',
  667.                         Data = leadingWhite,
  668.                         Line = line,
  669.                         Char = char
  670.                     }, tokenMeta))
  671.                     leadingWhite = ""
  672.                 end
  673.                 if c == ' ' or c == '\t' then
  674.                     --whitespace
  675.                     --leadingWhite = leadingWhite..get()
  676.                     local c2 = get() -- ignore whitespace
  677.                     table.insert(leading, setmeta({
  678.                         Type = 'Whitespace',
  679.                         Line = line,
  680.                         Char = char,
  681.                         Data = c2
  682.                     }, tokenMeta))
  683.                 elseif c == '\n' or c == '\r' then
  684.                     local nl = get()
  685.                     if leadingWhite ~= "" then
  686.                         table.insert(leading, setmeta({
  687.                             Type = 'Comment',
  688.                             CommentType = longStr and 'LongComment' or 'Comment',
  689.                             Data = leadingWhite,
  690.                             Line = line,
  691.                             Char = char,
  692.                         }, tokenMeta))
  693.                         leadingWhite = ""
  694.                     end
  695.                     table.insert(leading, setmeta({
  696.                         Type = 'Whitespace',
  697.                         Line = line,
  698.                         Char = char,
  699.                         Data = nl,
  700.                     }, tokenMeta))
  701.                 elseif c == '-' and peek(1) == '-' then
  702.                     --comment
  703.                     get()
  704.                     get()
  705.                     leadingWhite = leadingWhite .. '--'
  706.                     local _, wholeText = tryGetLongString()
  707.                     if wholeText then
  708.                         leadingWhite = leadingWhite..wholeText
  709.                         longStr = true
  710.                     else
  711.                         while peek() ~= '\n' and peek() ~= '' do
  712.                             leadingWhite = leadingWhite..get()
  713.                         end
  714.                     end
  715.                 else
  716.                     break
  717.                 end
  718.             end
  719.             if leadingWhite ~= "" then
  720.                 table.insert(leading, setmeta(
  721.                 {
  722.                     Type = 'Comment',
  723.                     CommentType = longStr and 'LongComment' or 'Comment',
  724.                     Data = leadingWhite,
  725.                     Line = line,
  726.                     Char = char,
  727.                 }, tokenMeta))
  728.             end
  729.  
  730.             --get the initial char
  731.             local thisLine = line
  732.             local thisChar = char
  733.             local errorAt = ":"..line..":"..char..":> "
  734.             local c = peek()
  735.  
  736.             --symbol to emit
  737.             local toEmit = nil
  738.  
  739.             --branch on type
  740.             if c == '' then
  741.                 --eof
  742.                 toEmit = { Type = 'Eof' }
  743.  
  744.             elseif upperChars[c] or lowerChars[c] or c == '_' then
  745.                 --ident or keyword
  746.                 local start = pointer
  747.                 repeat
  748.                     get()
  749.                     c = peek()
  750.                 until not (upperChars[c] or lowerChars[c] or digits[c] or c == '_')
  751.                 local dat = src:sub(start, pointer-1)
  752.                 if keywords[dat] then
  753.                     toEmit = {Type = 'Keyword', Data = dat}
  754.                 else
  755.                     toEmit = {Type = 'Ident', Data = dat}
  756.                 end
  757.  
  758.             elseif digits[c] or (peek() == '.' and digits[peek(1)]) then
  759.                 --number const
  760.                 local start = pointer
  761.                 if c == '0' and peek(1) == 'x' then
  762.                     get();get()
  763.                     while hexDigits[peek()] do get() end
  764.                     if consume('Pp') then
  765.                         consume('+-')
  766.                         while digits[peek()] do get() end
  767.                     end
  768.                 else
  769.                     while digits[peek()] do get() end
  770.                     if consume('.') then
  771.                         while digits[peek()] do get() end
  772.                     end
  773.                     if consume('Ee') then
  774.                         consume('+-')
  775.                         while digits[peek()] do get() end
  776.                     end
  777.                 end
  778.                 toEmit = {Type = 'Number', Data = src:sub(start, pointer-1)}
  779.  
  780.             elseif c == '\'' or c == '\"' then
  781.                 local start = pointer
  782.                 --string const
  783.                 local delim = get()
  784.                 local contentStart = pointer
  785.                 while true do
  786.                     local c = get()
  787.                     if c == '\\' then
  788.                         get() --get the escape char
  789.                     elseif c == delim then
  790.                         break
  791.                     elseif c == '' then
  792.                         generateError("Unfinished string near <eof>")
  793.                     end
  794.                 end
  795.                 local content = src:sub(contentStart, pointer-2)
  796.                 local constant = src:sub(start, pointer-1)
  797.                 toEmit = {Type = 'String', Data = constant, Constant = content}
  798.  
  799.             elseif c == '[' then
  800.                 local content, wholetext = tryGetLongString()
  801.                 if wholetext then
  802.                     toEmit = {Type = 'String', Data = wholetext, Constant = content}
  803.                 else
  804.                     get()
  805.                     toEmit = {Type = 'Symbol', Data = '['}
  806.                 end
  807.  
  808.             elseif consume('>=<') then
  809.                 if consume('=') then
  810.                     toEmit = {Type = 'Symbol', Data = c..'='}
  811.                 else
  812.                     toEmit = {Type = 'Symbol', Data = c}
  813.                 end
  814.  
  815.             elseif consume('~') then
  816.                 if consume('=') then
  817.                     toEmit = {Type = 'Symbol', Data = '~='}
  818.                 else
  819.                     generateError("Unexpected symbol `~` in source.", 2)
  820.                 end
  821.  
  822.             elseif consume('.') then
  823.                 if consume('.') then
  824.                     if consume('.') then
  825.                         toEmit = {Type = 'Symbol', Data = '...'}
  826.                     else
  827.                         toEmit = {Type = 'Symbol', Data = '..'}
  828.                     end
  829.                 else
  830.                     toEmit = {Type = 'Symbol', Data = '.'}
  831.                 end
  832.  
  833.             elseif consume(':') then
  834.                 if consume(':') then
  835.                     toEmit = {Type = 'Symbol', Data = '::'}
  836.                 else
  837.                     toEmit = {Type = 'Symbol', Data = ':'}
  838.                 end
  839.  
  840.             elseif symbols[c] then
  841.                 get()
  842.                 toEmit = {Type = 'Symbol', Data = c}
  843.  
  844.             else
  845.                 local contents, all = tryGetLongString()
  846.                 if contents then
  847.                     toEmit = {Type = 'String', Data = all, Constant = contents}
  848.                 else
  849.                     generateError("Unexpected Symbol `"..c.."` in source.", 2)
  850.                 end
  851.             end
  852.  
  853.             --add the emitted symbol, after adding some common data
  854.             toEmit.LeadingWhite = leading -- table of leading whitespace/comments
  855.  
  856.             toEmit.Line = thisLine
  857.             toEmit.Char = thisChar
  858.             tokens[#tokens+1] = setmeta(toEmit, tokenMeta)
  859.  
  860.             --halt after eof has been emitted
  861.             if toEmit.Type == 'Eof' then break end
  862.         end
  863.     end
  864.  
  865.     --public interface:
  866.     local tokenList = setmetatable({
  867.         tokens = tokens,
  868.         savedPointers = {},
  869.         pointer = 1
  870.     }, {__index = TokenList})
  871.  
  872.     return tokenList
  873. end
  874.  
  875. --- Create a AST tree from a Lua Source
  876. -- @tparam TokenList tok List of tokens from @{LexLua}
  877. -- @treturn table The AST tree
  878. local function ParseLua(tok)
  879.     --- Generate an error
  880.     -- @tparam string msg The error message
  881.     -- @raise The produces error message
  882.     local function GenerateError(msg)
  883.         local err = ">> :"..tok:Peek().Line..":"..tok:Peek().Char..": "..msg.."\n"
  884.         --find the line
  885.         local lineNum = 0
  886.         if type(src) == 'string' then
  887.             for line in src:gmatch("[^\n]*\n?") do
  888.                 if line:sub(-1,-1) == '\n' then line = line:sub(1,-2) end
  889.                 lineNum = lineNum+1
  890.                 if lineNum == tok:Peek().Line then
  891.                     err = err..">> `"..line:gsub('\t','    ').."`\n"
  892.                     for i = 1, tok:Peek().Char do
  893.                         local c = line:sub(i,i)
  894.                         if c == '\t' then
  895.                             err = err..'    '
  896.                         else
  897.                             err = err..' '
  898.                         end
  899.                     end
  900.                     err = err.."   ^^^^"
  901.                     break
  902.                 end
  903.             end
  904.         end
  905.         error(err)
  906.     end
  907.  
  908.     local ParseExpr,
  909.           ParseStatementList,
  910.           ParseSimpleExpr,
  911.           ParsePrimaryExpr,
  912.           ParseSuffixedExpr
  913.  
  914.     --- Parse the function definition and its arguments
  915.     -- @tparam Scope.Scope scope The current scope
  916.     -- @tparam table tokenList A table to fill with tokens
  917.     -- @treturn Node A function Node
  918.     local function ParseFunctionArgsAndBody(scope, tokenList)
  919.         local funcScope = Scope(scope)
  920.         if not tok:ConsumeSymbol('(', tokenList) then
  921.             GenerateError("`(` expected.")
  922.         end
  923.  
  924.         --arg list
  925.         local argList = {}
  926.         local isVarArg = false
  927.         while not tok:ConsumeSymbol(')', tokenList) do
  928.             if tok:Is('Ident') then
  929.                 local arg = funcScope:CreateLocal(tok:Get(tokenList).Data)
  930.                 argList[#argList+1] = arg
  931.                 if not tok:ConsumeSymbol(',', tokenList) then
  932.                     if tok:ConsumeSymbol(')', tokenList) then
  933.                         break
  934.                     else
  935.                         GenerateError("`)` expected.")
  936.                     end
  937.                 end
  938.             elseif tok:ConsumeSymbol('...', tokenList) then
  939.                 isVarArg = true
  940.                 if not tok:ConsumeSymbol(')', tokenList) then
  941.                     GenerateError("`...` must be the last argument of a function.")
  942.                 end
  943.                 break
  944.             else
  945.                 GenerateError("Argument name or `...` expected")
  946.             end
  947.         end
  948.  
  949.         --body
  950.         local body = ParseStatementList(funcScope)
  951.  
  952.         --end
  953.         if not tok:ConsumeKeyword('end', tokenList) then
  954.             GenerateError("`end` expected after function body")
  955.         end
  956.  
  957.         return {
  958.             AstType   = 'Function',
  959.             Scope     = funcScope,
  960.             Arguments = argList,
  961.             Body      = body,
  962.             VarArg    = isVarArg,
  963.             Tokens    = tokenList,
  964.         }
  965.     end
  966.  
  967.     --- Parse a simple expression
  968.     -- @tparam Scope.Scope scope The current scope
  969.     -- @treturn Node the resulting node
  970.     function ParsePrimaryExpr(scope)
  971.         local tokenList = {}
  972.  
  973.         if tok:ConsumeSymbol('(', tokenList) then
  974.             local ex = ParseExpr(scope)
  975.             if not tok:ConsumeSymbol(')', tokenList) then
  976.                 GenerateError("`)` Expected.")
  977.             end
  978.  
  979.             return {
  980.                 AstType = 'Parentheses',
  981.                 Inner   = ex,
  982.                 Tokens  = tokenList,
  983.             }
  984.  
  985.         elseif tok:Is('Ident') then
  986.             local id = tok:Get(tokenList)
  987.             local var = scope:GetLocal(id.Data)
  988.             if not var then
  989.                 var = scope:GetGlobal(id.Data)
  990.                 if not var then
  991.                     var = scope:CreateGlobal(id.Data)
  992.                 else
  993.                     var.References = var.References + 1
  994.                 end
  995.             else
  996.                 var.References = var.References + 1
  997.             end
  998.  
  999.             return {
  1000.                 AstType  = 'VarExpr',
  1001.                 Name     = id.Data,
  1002.                 Variable = var,
  1003.                 Tokens   = tokenList,
  1004.             }
  1005.         else
  1006.             GenerateError("primary expression expected")
  1007.         end
  1008.     end
  1009.  
  1010.     --- Parse some table related expressions
  1011.     -- @tparam Scope.Scope scope The current scope
  1012.     -- @tparam boolean onlyDotColon Only allow '.' or ':' nodes
  1013.     -- @treturn Node The resulting node
  1014.     function ParseSuffixedExpr(scope, onlyDotColon)
  1015.         --base primary expression
  1016.         local prim = ParsePrimaryExpr(scope)
  1017.  
  1018.         while true do
  1019.             local tokenList = {}
  1020.  
  1021.             if tok:IsSymbol('.') or tok:IsSymbol(':') then
  1022.                 local symb = tok:Get(tokenList).Data
  1023.                 if not tok:Is('Ident') then
  1024.                     GenerateError("<Ident> expected.")
  1025.                 end
  1026.                 local id = tok:Get(tokenList)
  1027.  
  1028.                 prim = {
  1029.                     AstType  = 'MemberExpr',
  1030.                     Base     = prim,
  1031.                     Indexer  = symb,
  1032.                     Ident    = id,
  1033.                     Tokens   = tokenList,
  1034.                 }
  1035.  
  1036.             elseif not onlyDotColon and tok:ConsumeSymbol('[', tokenList) then
  1037.                 local ex = ParseExpr(scope)
  1038.                 if not tok:ConsumeSymbol(']', tokenList) then
  1039.                     GenerateError("`]` expected.")
  1040.                 end
  1041.  
  1042.                 prim = {
  1043.                     AstType  = 'IndexExpr',
  1044.                     Base     = prim,
  1045.                     Index    = ex,
  1046.                     Tokens   = tokenList,
  1047.                 }
  1048.  
  1049.             elseif not onlyDotColon and tok:ConsumeSymbol('(', tokenList) then
  1050.                 local args = {}
  1051.                 while not tok:ConsumeSymbol(')', tokenList) do
  1052.                     args[#args+1] = ParseExpr(scope)
  1053.                     if not tok:ConsumeSymbol(',', tokenList) then
  1054.                         if tok:ConsumeSymbol(')', tokenList) then
  1055.                             break
  1056.                         else
  1057.                             GenerateError("`)` Expected.")
  1058.                         end
  1059.                     end
  1060.                 end
  1061.  
  1062.                 prim = {
  1063.                     AstType   = 'CallExpr',
  1064.                     Base      = prim,
  1065.                     Arguments = args,
  1066.                     Tokens    = tokenList,
  1067.                 }
  1068.  
  1069.             elseif not onlyDotColon and tok:Is('String') then
  1070.                 --string call
  1071.                 prim = {
  1072.                     AstType    = 'StringCallExpr',
  1073.                     Base       = prim,
  1074.                     Arguments  = { tok:Get(tokenList) },
  1075.                     Tokens     = tokenList,
  1076.                 }
  1077.  
  1078.             elseif not onlyDotColon and tok:IsSymbol('{') then
  1079.                 --table call
  1080.                 local ex = ParseSimpleExpr(scope)
  1081.                 -- FIX: ParseExpr(scope) parses the table AND and any following binary expressions.
  1082.                 -- We just want the table
  1083.  
  1084.                 prim = {
  1085.                     AstType   = 'TableCallExpr',
  1086.                     Base      = prim,
  1087.                     Arguments = { ex },
  1088.                     Tokens    = tokenList,
  1089.                 }
  1090.  
  1091.             else
  1092.                 break
  1093.             end
  1094.         end
  1095.         return prim
  1096.     end
  1097.  
  1098.     --- Parse a simple expression (strings, numbers, booleans, varargs)
  1099.     -- @tparam Scope.Scope scope The current scope
  1100.     -- @treturn Node The resulting node
  1101.     function ParseSimpleExpr(scope)
  1102.         local tokenList = {}
  1103.  
  1104.         if tok:Is('Number') then
  1105.             return {
  1106.                 AstType = 'NumberExpr',
  1107.                 Value   = tok:Get(tokenList),
  1108.                 Tokens  = tokenList,
  1109.             }
  1110.  
  1111.         elseif tok:Is('String') then
  1112.             return {
  1113.                 AstType = 'StringExpr',
  1114.                 Value   = tok:Get(tokenList),
  1115.                 Tokens  = tokenList,
  1116.             }
  1117.  
  1118.         elseif tok:ConsumeKeyword('nil', tokenList) then
  1119.             return {
  1120.                 AstType = 'NilExpr',
  1121.                 Tokens  = tokenList,
  1122.             }
  1123.  
  1124.         elseif tok:IsKeyword('false') or tok:IsKeyword('true') then
  1125.             return {
  1126.                 AstType = 'BooleanExpr',
  1127.                 Value   = (tok:Get(tokenList).Data == 'true'),
  1128.                 Tokens  = tokenList,
  1129.             }
  1130.  
  1131.         elseif tok:ConsumeSymbol('...', tokenList) then
  1132.             return {
  1133.                 AstType  = 'DotsExpr',
  1134.                 Tokens   = tokenList,
  1135.             }
  1136.  
  1137.         elseif tok:ConsumeSymbol('{', tokenList) then
  1138.             local entryList = {}
  1139.             local v = {
  1140.                 AstType = 'ConstructorExpr',
  1141.                 EntryList = entryList,
  1142.                 Tokens  = tokenList,
  1143.             }
  1144.  
  1145.             while true do
  1146.                 if tok:IsSymbol('[', tokenList) then
  1147.                     --key
  1148.                     tok:Get(tokenList)
  1149.                     local key = ParseExpr(scope)
  1150.                     if not tok:ConsumeSymbol(']', tokenList) then
  1151.                         GenerateError("`]` Expected")
  1152.                     end
  1153.                     if not tok:ConsumeSymbol('=', tokenList) then
  1154.                         GenerateError("`=` Expected")
  1155.                     end
  1156.                     local value = ParseExpr(scope)
  1157.                     entryList[#entryList+1] = {
  1158.                         Type  = 'Key',
  1159.                         Key   = key,
  1160.                         Value = value,
  1161.                     }
  1162.  
  1163.                 elseif tok:Is('Ident') then
  1164.                     --value or key
  1165.                     local lookahead = tok:Peek(1)
  1166.                     if lookahead.Type == 'Symbol' and lookahead.Data == '=' then
  1167.                         --we are a key
  1168.                         local key = tok:Get(tokenList)
  1169.                         if not tok:ConsumeSymbol('=', tokenList) then
  1170.                             GenerateError("`=` Expected")
  1171.                         end
  1172.                         local value = ParseExpr(scope)
  1173.                         entryList[#entryList+1] = {
  1174.                             Type  = 'KeyString',
  1175.                             Key   = key.Data,
  1176.                             Value = value,
  1177.                         }
  1178.  
  1179.                     else
  1180.                         --we are a value
  1181.                         local value = ParseExpr(scope)
  1182.                         entryList[#entryList+1] = {
  1183.                             Type = 'Value',
  1184.                             Value = value,
  1185.                         }
  1186.  
  1187.                     end
  1188.                 elseif tok:ConsumeSymbol('}', tokenList) then
  1189.                     break
  1190.  
  1191.                 else
  1192.                     --value
  1193.                     local value = ParseExpr(scope)
  1194.                     entryList[#entryList+1] = {
  1195.                         Type = 'Value',
  1196.                         Value = value,
  1197.                     }
  1198.                 end
  1199.  
  1200.                 if tok:ConsumeSymbol(';', tokenList) or tok:ConsumeSymbol(',', tokenList) then
  1201.                     --all is good
  1202.                 elseif tok:ConsumeSymbol('}', tokenList) then
  1203.                     break
  1204.                 else
  1205.                     GenerateError("`}` or table entry Expected")
  1206.                 end
  1207.             end
  1208.             return v
  1209.  
  1210.         elseif tok:ConsumeKeyword('function', tokenList) then
  1211.             local func = ParseFunctionArgsAndBody(scope, tokenList)
  1212.  
  1213.             func.IsLocal = true
  1214.             return func
  1215.  
  1216.         else
  1217.             return ParseSuffixedExpr(scope)
  1218.         end
  1219.     end
  1220.  
  1221.     local unopprio = 8
  1222.     local priority = {
  1223.         ['+'] = {6,6},
  1224.         ['-'] = {6,6},
  1225.         ['%'] = {7,7},
  1226.         ['/'] = {7,7},
  1227.         ['*'] = {7,7},
  1228.         ['^'] = {10,9},
  1229.         ['..'] = {5,4},
  1230.         ['=='] = {3,3},
  1231.         ['<'] = {3,3},
  1232.         ['<='] = {3,3},
  1233.         ['~='] = {3,3},
  1234.         ['>'] = {3,3},
  1235.         ['>='] = {3,3},
  1236.         ['and'] = {2,2},
  1237.         ['or'] = {1,1},
  1238.     }
  1239.  
  1240.     --- Parse an expression
  1241.     -- @tparam Skcope.Scope scope The current scope
  1242.     -- @tparam int level Current level (Optional)
  1243.     -- @treturn Node The resulting node
  1244.     function ParseExpr(scope, level)
  1245.         level = level or 0
  1246.         --base item, possibly with unop prefix
  1247.         local exp
  1248.         if unops[tok:Peek().Data] then
  1249.             local tokenList = {}
  1250.             local op = tok:Get(tokenList).Data
  1251.             exp = ParseExpr(scope, unopprio)
  1252.  
  1253.             local nodeEx = {
  1254.                 AstType = 'UnopExpr',
  1255.                 Rhs     = exp,
  1256.                 Op      = op,
  1257.                 OperatorPrecedence = unopprio,
  1258.                 Tokens  = tokenList,
  1259.             }
  1260.  
  1261.             exp = nodeEx
  1262.         else
  1263.             exp = ParseSimpleExpr(scope)
  1264.         end
  1265.  
  1266.         --next items in chain
  1267.         while true do
  1268.             local prio = priority[tok:Peek().Data]
  1269.             if prio and prio[1] > level then
  1270.                 local tokenList = {}
  1271.                 local op = tok:Get(tokenList).Data
  1272.                 local rhs = ParseExpr(scope, prio[2])
  1273.  
  1274.                 local nodeEx = {
  1275.                     AstType = 'BinopExpr',
  1276.                     Lhs     = exp,
  1277.                     Op      = op,
  1278.                     OperatorPrecedence = prio[1],
  1279.                     Rhs     = rhs,
  1280.                     Tokens  = tokenList,
  1281.                 }
  1282.  
  1283.                 exp = nodeEx
  1284.             else
  1285.                 break
  1286.             end
  1287.         end
  1288.  
  1289.         return exp
  1290.     end
  1291.  
  1292.     --- Parse a statement (if, for, while, etc...)
  1293.     -- @tparam Scope.Scope scope The current scope
  1294.     -- @treturn Node The resulting node
  1295.     local function ParseStatement(scope)
  1296.         local stat = nil
  1297.         local tokenList = {}
  1298.         if tok:ConsumeKeyword('if', tokenList) then
  1299.             --setup
  1300.             local clauses = {}
  1301.             local nodeIfStat = {
  1302.                 AstType = 'IfStatement',
  1303.                 Clauses = clauses,
  1304.             }
  1305.             --clauses
  1306.             repeat
  1307.                 local nodeCond = ParseExpr(scope)
  1308.  
  1309.                 if not tok:ConsumeKeyword('then', tokenList) then
  1310.                     GenerateError("`then` expected.")
  1311.                 end
  1312.                 local nodeBody = ParseStatementList(scope)
  1313.                 clauses[#clauses+1] = {
  1314.                     Condition = nodeCond,
  1315.                     Body = nodeBody,
  1316.                 }
  1317.             until not tok:ConsumeKeyword('elseif', tokenList)
  1318.  
  1319.             --else clause
  1320.             if tok:ConsumeKeyword('else', tokenList) then
  1321.                 local nodeBody = ParseStatementList(scope)
  1322.                 clauses[#clauses+1] = {
  1323.                     Body = nodeBody,
  1324.                 }
  1325.             end
  1326.  
  1327.             --end
  1328.             if not tok:ConsumeKeyword('end', tokenList) then
  1329.                 GenerateError("`end` expected.")
  1330.             end
  1331.  
  1332.             nodeIfStat.Tokens = tokenList
  1333.             stat = nodeIfStat
  1334.         elseif tok:ConsumeKeyword('while', tokenList) then
  1335.             --condition
  1336.             local nodeCond = ParseExpr(scope)
  1337.  
  1338.             --do
  1339.             if not tok:ConsumeKeyword('do', tokenList) then
  1340.                 return GenerateError("`do` expected.")
  1341.             end
  1342.  
  1343.             --body
  1344.             local nodeBody = ParseStatementList(scope)
  1345.  
  1346.             --end
  1347.             if not tok:ConsumeKeyword('end', tokenList) then
  1348.                 GenerateError("`end` expected.")
  1349.             end
  1350.  
  1351.             --return
  1352.             stat = {
  1353.                 AstType = 'WhileStatement',
  1354.                 Condition = nodeCond,
  1355.                 Body      = nodeBody,
  1356.                 Tokens    = tokenList,
  1357.             }
  1358.         elseif tok:ConsumeKeyword('do', tokenList) then
  1359.             --do block
  1360.             local nodeBlock = ParseStatementList(scope)
  1361.             if not tok:ConsumeKeyword('end', tokenList) then
  1362.                 GenerateError("`end` expected.")
  1363.             end
  1364.  
  1365.             stat = {
  1366.                 AstType = 'DoStatement',
  1367.                 Body    = nodeBlock,
  1368.                 Tokens  = tokenList,
  1369.             }
  1370.         elseif tok:ConsumeKeyword('for', tokenList) then
  1371.             --for block
  1372.             if not tok:Is('Ident') then
  1373.                 GenerateError("<ident> expected.")
  1374.             end
  1375.             local baseVarName = tok:Get(tokenList)
  1376.             if tok:ConsumeSymbol('=', tokenList) then
  1377.                 --numeric for
  1378.                 local forScope = Scope(scope)
  1379.                 local forVar = forScope:CreateLocal(baseVarName.Data)
  1380.  
  1381.                 local startEx = ParseExpr(scope)
  1382.                 if not tok:ConsumeSymbol(',', tokenList) then
  1383.                     GenerateError("`,` Expected")
  1384.                 end
  1385.                 local endEx = ParseExpr(scope)
  1386.                 local stepEx
  1387.                 if tok:ConsumeSymbol(',', tokenList) then
  1388.                     stepEx = ParseExpr(scope)
  1389.                 end
  1390.                 if not tok:ConsumeKeyword('do', tokenList) then
  1391.                     GenerateError("`do` expected")
  1392.                 end
  1393.  
  1394.                 local body = ParseStatementList(forScope)
  1395.                 if not tok:ConsumeKeyword('end', tokenList) then
  1396.                     GenerateError("`end` expected")
  1397.                 end
  1398.  
  1399.                 stat = {
  1400.                     AstType  = 'NumericForStatement',
  1401.                     Scope    = forScope,
  1402.                     Variable = forVar,
  1403.                     Start    = startEx,
  1404.                     End      = endEx,
  1405.                     Step     = stepEx,
  1406.                     Body     = body,
  1407.                     Tokens   = tokenList,
  1408.                 }
  1409.             else
  1410.                 --generic for
  1411.                 local forScope = Scope(scope)
  1412.  
  1413.                 local varList = { forScope:CreateLocal(baseVarName.Data) }
  1414.                 while tok:ConsumeSymbol(',', tokenList) do
  1415.                     if not tok:Is('Ident') then
  1416.                         GenerateError("for variable expected.")
  1417.                     end
  1418.                     varList[#varList+1] = forScope:CreateLocal(tok:Get(tokenList).Data)
  1419.                 end
  1420.                 if not tok:ConsumeKeyword('in', tokenList) then
  1421.                     GenerateError("`in` expected.")
  1422.                 end
  1423.                 local generators = {ParseExpr(scope)}
  1424.                 while tok:ConsumeSymbol(',', tokenList) do
  1425.                     generators[#generators+1] = ParseExpr(scope)
  1426.                 end
  1427.  
  1428.                 if not tok:ConsumeKeyword('do', tokenList) then
  1429.                     GenerateError("`do` expected.")
  1430.                 end
  1431.  
  1432.                 local body = ParseStatementList(forScope)
  1433.                 if not tok:ConsumeKeyword('end', tokenList) then
  1434.                     GenerateError("`end` expected.")
  1435.                 end
  1436.  
  1437.                 stat = {
  1438.                     AstType      = 'GenericForStatement',
  1439.                     Scope        = forScope,
  1440.                     VariableList = varList,
  1441.                     Generators   = generators,
  1442.                     Body         = body,
  1443.                     Tokens       = tokenList,
  1444.                 }
  1445.             end
  1446.         elseif tok:ConsumeKeyword('repeat', tokenList) then
  1447.             local body = ParseStatementList(scope)
  1448.  
  1449.             if not tok:ConsumeKeyword('until', tokenList) then
  1450.                 GenerateError("`until` expected.")
  1451.             end
  1452.  
  1453.             cond = ParseExpr(body.Scope)
  1454.  
  1455.             stat = {
  1456.                 AstType   = 'RepeatStatement',
  1457.                 Condition = cond,
  1458.                 Body      = body,
  1459.                 Tokens    = tokenList,
  1460.             }
  1461.         elseif tok:ConsumeKeyword('function', tokenList) then
  1462.             if not tok:Is('Ident') then
  1463.                 GenerateError("Function name expected")
  1464.             end
  1465.             local name = ParseSuffixedExpr(scope, true) --true => only dots and colons
  1466.  
  1467.             local func = ParseFunctionArgsAndBody(scope, tokenList)
  1468.  
  1469.             func.IsLocal = false
  1470.             func.Name    = name
  1471.             stat = func
  1472.         elseif tok:ConsumeKeyword('local', tokenList) then
  1473.             if tok:Is('Ident') then
  1474.                 local varList = { tok:Get(tokenList).Data }
  1475.                 while tok:ConsumeSymbol(',', tokenList) do
  1476.                     if not tok:Is('Ident') then
  1477.                         GenerateError("local var name expected")
  1478.                     end
  1479.                     varList[#varList+1] = tok:Get(tokenList).Data
  1480.                 end
  1481.  
  1482.                 local initList = {}
  1483.                 if tok:ConsumeSymbol('=', tokenList) then
  1484.                     repeat
  1485.                         initList[#initList+1] = ParseExpr(scope)
  1486.                     until not tok:ConsumeSymbol(',', tokenList)
  1487.                 end
  1488.  
  1489.                 --now patch var list
  1490.                 --we can't do this before getting the init list, because the init list does not
  1491.                 --have the locals themselves in scope.
  1492.                 for i, v in pairs(varList) do
  1493.                     varList[i] = scope:CreateLocal(v)
  1494.                 end
  1495.  
  1496.                 stat = {
  1497.                     AstType   = 'LocalStatement',
  1498.                     LocalList = varList,
  1499.                     InitList  = initList,
  1500.                     Tokens    = tokenList,
  1501.                 }
  1502.  
  1503.             elseif tok:ConsumeKeyword('function', tokenList) then
  1504.                 if not tok:Is('Ident') then
  1505.                     GenerateError("Function name expected")
  1506.                 end
  1507.                 local name = tok:Get(tokenList).Data
  1508.                 local localVar = scope:CreateLocal(name)
  1509.  
  1510.                 local func = ParseFunctionArgsAndBody(scope, tokenList)
  1511.  
  1512.                 func.Name    = localVar
  1513.                 func.IsLocal = true
  1514.                 stat = func
  1515.  
  1516.             else
  1517.                 GenerateError("local var or function def expected")
  1518.             end
  1519.         elseif tok:ConsumeSymbol('::', tokenList) then
  1520.             if not tok:Is('Ident') then
  1521.                 GenerateError('Label name expected')
  1522.             end
  1523.             local label = tok:Get(tokenList).Data
  1524.             if not tok:ConsumeSymbol('::', tokenList) then
  1525.                 GenerateError("`::` expected")
  1526.             end
  1527.             stat = {
  1528.                 AstType = 'LabelStatement',
  1529.                 Label   = label,
  1530.                 Tokens  = tokenList,
  1531.             }
  1532.         elseif tok:ConsumeKeyword('return', tokenList) then
  1533.             local exList = {}
  1534.             if not tok:IsKeyword('end') then
  1535.                 -- Use PCall as this may produce an error
  1536.                 local st, firstEx = pcall(function() return ParseExpr(scope) end)
  1537.                 if st then
  1538.                     exList[1] = firstEx
  1539.                     while tok:ConsumeSymbol(',', tokenList) do
  1540.                         exList[#exList+1] = ParseExpr(scope)
  1541.                     end
  1542.                 end
  1543.             end
  1544.             stat = {
  1545.                 AstType   = 'ReturnStatement',
  1546.                 Arguments = exList,
  1547.                 Tokens    = tokenList,
  1548.             }
  1549.         elseif tok:ConsumeKeyword('break', tokenList) then
  1550.             stat = {
  1551.                 AstType = 'BreakStatement',
  1552.                 Tokens  = tokenList,
  1553.             }
  1554.         elseif tok:ConsumeKeyword('goto', tokenList) then
  1555.             if not tok:Is('Ident') then
  1556.                 GenerateError("Label expected")
  1557.             end
  1558.             local label = tok:Get(tokenList).Data
  1559.             stat = {
  1560.                 AstType = 'GotoStatement',
  1561.                 Label   = label,
  1562.                 Tokens  = tokenList,
  1563.             }
  1564.         else
  1565.             --statementParseExpr
  1566.             local suffixed = ParseSuffixedExpr(scope)
  1567.  
  1568.             --assignment or call?
  1569.             if tok:IsSymbol(',') or tok:IsSymbol('=') then
  1570.                 --check that it was not parenthesized, making it not an lvalue
  1571.                 if (suffixed.ParenCount or 0) > 0 then
  1572.                     GenerateError("Can not assign to parenthesized expression, is not an lvalue")
  1573.                 end
  1574.  
  1575.                 --more processing needed
  1576.                 local lhs = { suffixed }
  1577.                 while tok:ConsumeSymbol(',', tokenList) do
  1578.                     lhs[#lhs+1] = ParseSuffixedExpr(scope)
  1579.                 end
  1580.  
  1581.                 --equals
  1582.                 if not tok:ConsumeSymbol('=', tokenList) then
  1583.                     GenerateError("`=` Expected.")
  1584.                 end
  1585.  
  1586.                 --rhs
  1587.                 local rhs = {ParseExpr(scope)}
  1588.                 while tok:ConsumeSymbol(',', tokenList) do
  1589.                     rhs[#rhs+1] = ParseExpr(scope)
  1590.                 end
  1591.  
  1592.                 --done
  1593.                 stat = {
  1594.                     AstType = 'AssignmentStatement',
  1595.                     Lhs     = lhs,
  1596.                     Rhs     = rhs,
  1597.                     Tokens  = tokenList,
  1598.                 }
  1599.  
  1600.             elseif suffixed.AstType == 'CallExpr' or
  1601.                    suffixed.AstType == 'TableCallExpr' or
  1602.                    suffixed.AstType == 'StringCallExpr'
  1603.             then
  1604.                 --it's a call statement
  1605.                 stat = {
  1606.                     AstType    = 'CallStatement',
  1607.                     Expression = suffixed,
  1608.                     Tokens     = tokenList,
  1609.                 }
  1610.             else
  1611.                 GenerateError("Assignment Statement Expected")
  1612.             end
  1613.         end
  1614.  
  1615.         if tok:IsSymbol(';') then
  1616.             stat.Semicolon = tok:Get( stat.Tokens )
  1617.         end
  1618.         return stat
  1619.     end
  1620.  
  1621.     --- Parse a a list of statements
  1622.     -- @tparam Scope.Scope scope The current scope
  1623.     -- @treturn Node The resulting node
  1624.     function ParseStatementList(scope)
  1625.         local body = {}
  1626.         local nodeStatlist   = {
  1627.             Scope   = Scope(scope),
  1628.             AstType = 'Statlist',
  1629.             Body    = body,
  1630.             Tokens  = {},
  1631.         }
  1632.  
  1633.         while not statListCloseKeywords[tok:Peek().Data] and not tok:IsEof() do
  1634.             local nodeStatement = ParseStatement(nodeStatlist.Scope)
  1635.             --stats[#stats+1] = nodeStatement
  1636.             body[#body + 1] = nodeStatement
  1637.         end
  1638.  
  1639.         if tok:IsEof() then
  1640.             local nodeEof = {}
  1641.             nodeEof.AstType = 'Eof'
  1642.             nodeEof.Tokens  = { tok:Get() }
  1643.             body[#body + 1] = nodeEof
  1644.         end
  1645.  
  1646.         --nodeStatlist.Body = stats
  1647.         return nodeStatlist
  1648.     end
  1649.  
  1650.     return ParseStatementList(Scope())
  1651. end
  1652.  
  1653. --- @export
  1654. return { LexLua = LexLua, ParseLua = ParseLua }
  1655. end)
  1656. Rebuild=_W(function()
  1657. --- Rebuild source code from an AST
  1658. -- Does not preserve whitespace
  1659. -- @module lexer.Rebuild
  1660.  
  1661. local lowerChars = Constants.LowerChars
  1662. local upperChars = Constants.UpperChars
  1663. local digits = Constants.Digits
  1664. local symbols = Constants.Symbols
  1665.  
  1666. --- Join two statements together
  1667. -- @tparam string left The left statement
  1668. -- @tparam string right The right statement
  1669. -- @tparam string sep The string used to separate the characters
  1670. -- @treturn string The joined strings
  1671. local function JoinStatements(left, right, sep)
  1672.     sep = sep or ' '
  1673.     local leftEnd, rightStart = left:sub(-1,-1), right:sub(1,1)
  1674.     if upperChars[leftEnd] or lowerChars[leftEnd] or leftEnd == '_' then
  1675.         if not (rightStart == '_' or upperChars[rightStart] or lowerChars[rightStart] or digits[rightStart]) then
  1676.             --rightStart is left symbol, can join without seperation
  1677.             return left .. right
  1678.         else
  1679.             return left .. sep .. right
  1680.         end
  1681.     elseif digits[leftEnd] then
  1682.         if rightStart == '(' then
  1683.             --can join statements directly
  1684.             return left .. right
  1685.         elseif symbols[rightStart] then
  1686.             return left .. right
  1687.         else
  1688.             return left .. sep .. right
  1689.         end
  1690.     elseif leftEnd == '' then
  1691.         return left .. right
  1692.     else
  1693.         if rightStart == '(' then
  1694.             --don't want to accidentally call last statement, can't join directly
  1695.             return left .. sep .. right
  1696.         else
  1697.             return left .. right
  1698.         end
  1699.     end
  1700. end
  1701.  
  1702. --- Returns the minified version of an AST. Operations which are performed:
  1703. --  - All comments and whitespace are ignored
  1704. --  - All local variables are renamed
  1705. -- @tparam Node ast The AST tree
  1706. -- @treturn string The minified string
  1707. -- @todo Ability to control minification level
  1708. local function Minify(ast)
  1709.     local formatStatlist, formatExpr
  1710.     local count = 0
  1711.     local function joinStatements(left, right, set)
  1712.         if count > 150 then
  1713.             count = 0
  1714.             return left .. "\n" .. right
  1715.         else
  1716.             return JoinStatements(left, right, sep)
  1717.         end
  1718.     end
  1719.  
  1720.     formatExpr = function(expr, precedence)
  1721.         local precedence = precedence or 0
  1722.         local currentPrecedence = 0
  1723.         local skipParens = false
  1724.         local out = ""
  1725.         if expr.AstType == 'VarExpr' then
  1726.             if expr.Variable then
  1727.                 out = out..expr.Variable.Name
  1728.             else
  1729.                 out = out..expr.Name
  1730.             end
  1731.  
  1732.         elseif expr.AstType == 'NumberExpr' then
  1733.             out = out..expr.Value.Data
  1734.  
  1735.         elseif expr.AstType == 'StringExpr' then
  1736.             out = out..expr.Value.Data
  1737.  
  1738.         elseif expr.AstType == 'BooleanExpr' then
  1739.             out = out..tostring(expr.Value)
  1740.  
  1741.         elseif expr.AstType == 'NilExpr' then
  1742.             out = joinStatements(out, "nil")
  1743.  
  1744.         elseif expr.AstType == 'BinopExpr' then
  1745.             currentPrecedence = expr.OperatorPrecedence
  1746.             out = joinStatements(out, formatExpr(expr.Lhs, currentPrecedence))
  1747.             out = joinStatements(out, expr.Op)
  1748.             out = joinStatements(out, formatExpr(expr.Rhs))
  1749.             if expr.Op == '^' or expr.Op == '..' then
  1750.                 currentPrecedence = currentPrecedence - 1
  1751.             end
  1752.  
  1753.             if currentPrecedence < precedence then
  1754.                 skipParens = false
  1755.             else
  1756.                 skipParens = true
  1757.             end
  1758.         elseif expr.AstType == 'UnopExpr' then
  1759.             out = joinStatements(out, expr.Op)
  1760.             out = joinStatements(out, formatExpr(expr.Rhs))
  1761.  
  1762.         elseif expr.AstType == 'DotsExpr' then
  1763.             out = out.."..."
  1764.  
  1765.         elseif expr.AstType == 'CallExpr' then
  1766.             out = out..formatExpr(expr.Base)
  1767.             out = out.."("
  1768.             for i = 1, #expr.Arguments do
  1769.                 out = out..formatExpr(expr.Arguments[i])
  1770.                 if i ~= #expr.Arguments then
  1771.                     out = out..","
  1772.                 end
  1773.             end
  1774.             out = out..")"
  1775.  
  1776.         elseif expr.AstType == 'TableCallExpr' then
  1777.             out = out..formatExpr(expr.Base)
  1778.             out = out..formatExpr(expr.Arguments[1])
  1779.  
  1780.         elseif expr.AstType == 'StringCallExpr' then
  1781.             out = out..formatExpr(expr.Base)
  1782.             out = out..expr.Arguments[1].Data
  1783.  
  1784.         elseif expr.AstType == 'IndexExpr' then
  1785.             out = out..formatExpr(expr.Base).."["..formatExpr(expr.Index).."]"
  1786.  
  1787.         elseif expr.AstType == 'MemberExpr' then
  1788.             out = out..formatExpr(expr.Base)..expr.Indexer..expr.Ident.Data
  1789.  
  1790.         elseif expr.AstType == 'Function' then
  1791.             expr.Scope:ObfuscateLocals()
  1792.             out = out.."function("
  1793.             if #expr.Arguments > 0 then
  1794.                 for i = 1, #expr.Arguments do
  1795.                     out = out..expr.Arguments[i].Name
  1796.                     if i ~= #expr.Arguments then
  1797.                         out = out..","
  1798.                     elseif expr.VarArg then
  1799.                         out = out..",..."
  1800.                     end
  1801.                 end
  1802.             elseif expr.VarArg then
  1803.                 out = out.."..."
  1804.             end
  1805.             out = out..")"
  1806.             out = joinStatements(out, formatStatlist(expr.Body))
  1807.             out = joinStatements(out, "end")
  1808.  
  1809.         elseif expr.AstType == 'ConstructorExpr' then
  1810.             out = out.."{"
  1811.             for i = 1, #expr.EntryList do
  1812.                 local entry = expr.EntryList[i]
  1813.                 if entry.Type == 'Key' then
  1814.                     out = out.."["..formatExpr(entry.Key).."]="..formatExpr(entry.Value)
  1815.                 elseif entry.Type == 'Value' then
  1816.                     out = out..formatExpr(entry.Value)
  1817.                 elseif entry.Type == 'KeyString' then
  1818.                     out = out..entry.Key.."="..formatExpr(entry.Value)
  1819.                 end
  1820.                 if i ~= #expr.EntryList then
  1821.                     out = out..","
  1822.                 end
  1823.             end
  1824.             out = out.."}"
  1825.  
  1826.         elseif expr.AstType == 'Parentheses' then
  1827.             out = out.."("..formatExpr(expr.Inner)..")"
  1828.  
  1829.         end
  1830.         if not skipParens then
  1831.             out = string.rep('(', expr.ParenCount or 0) .. out
  1832.             out = out .. string.rep(')', expr.ParenCount or 0)
  1833.         end
  1834.         count = count + #out
  1835.         return out
  1836.     end
  1837.  
  1838.     local formatStatement = function(statement)
  1839.         local out = ''
  1840.         if statement.AstType == 'AssignmentStatement' then
  1841.             for i = 1, #statement.Lhs do
  1842.                 out = out..formatExpr(statement.Lhs[i])
  1843.                 if i ~= #statement.Lhs then
  1844.                     out = out..","
  1845.                 end
  1846.             end
  1847.             if #statement.Rhs > 0 then
  1848.                 out = out.."="
  1849.                 for i = 1, #statement.Rhs do
  1850.                     out = out..formatExpr(statement.Rhs[i])
  1851.                     if i ~= #statement.Rhs then
  1852.                         out = out..","
  1853.                     end
  1854.                 end
  1855.             end
  1856.  
  1857.         elseif statement.AstType == 'CallStatement' then
  1858.             out = formatExpr(statement.Expression)
  1859.  
  1860.         elseif statement.AstType == 'LocalStatement' then
  1861.             out = out.."local "
  1862.             for i = 1, #statement.LocalList do
  1863.                 out = out..statement.LocalList[i].Name
  1864.                 if i ~= #statement.LocalList then
  1865.                     out = out..","
  1866.                 end
  1867.             end
  1868.             if #statement.InitList > 0 then
  1869.                 out = out.."="
  1870.                 for i = 1, #statement.InitList do
  1871.                     out = out..formatExpr(statement.InitList[i])
  1872.                     if i ~= #statement.InitList then
  1873.                         out = out..","
  1874.                     end
  1875.                 end
  1876.             end
  1877.  
  1878.         elseif statement.AstType == 'IfStatement' then
  1879.             out = joinStatements("if", formatExpr(statement.Clauses[1].Condition))
  1880.             out = joinStatements(out, "then")
  1881.             out = joinStatements(out, formatStatlist(statement.Clauses[1].Body))
  1882.             for i = 2, #statement.Clauses do
  1883.                 local st = statement.Clauses[i]
  1884.                 if st.Condition then
  1885.                     out = joinStatements(out, "elseif")
  1886.                     out = joinStatements(out, formatExpr(st.Condition))
  1887.                     out = joinStatements(out, "then")
  1888.                 else
  1889.                     out = joinStatements(out, "else")
  1890.                 end
  1891.                 out = joinStatements(out, formatStatlist(st.Body))
  1892.             end
  1893.             out = joinStatements(out, "end")
  1894.  
  1895.         elseif statement.AstType == 'WhileStatement' then
  1896.             out = joinStatements("while", formatExpr(statement.Condition))
  1897.             out = joinStatements(out, "do")
  1898.             out = joinStatements(out, formatStatlist(statement.Body))
  1899.             out = joinStatements(out, "end")
  1900.  
  1901.         elseif statement.AstType == 'DoStatement' then
  1902.             out = joinStatements(out, "do")
  1903.             out = joinStatements(out, formatStatlist(statement.Body))
  1904.             out = joinStatements(out, "end")
  1905.  
  1906.         elseif statement.AstType == 'ReturnStatement' then
  1907.             out = "return"
  1908.             for i = 1, #statement.Arguments do
  1909.                 out = joinStatements(out, formatExpr(statement.Arguments[i]))
  1910.                 if i ~= #statement.Arguments then
  1911.                     out = out..","
  1912.                 end
  1913.             end
  1914.  
  1915.         elseif statement.AstType == 'BreakStatement' then
  1916.             out = "break"
  1917.  
  1918.         elseif statement.AstType == 'RepeatStatement' then
  1919.             out = "repeat"
  1920.             out = joinStatements(out, formatStatlist(statement.Body))
  1921.             out = joinStatements(out, "until")
  1922.             out = joinStatements(out, formatExpr(statement.Condition))
  1923.  
  1924.         elseif statement.AstType == 'Function' then
  1925.             statement.Scope:ObfuscateLocals()
  1926.             if statement.IsLocal then
  1927.                 out = "local"
  1928.             end
  1929.             out = joinStatements(out, "function ")
  1930.             if statement.IsLocal then
  1931.                 out = out..statement.Name.Name
  1932.             else
  1933.                 out = out..formatExpr(statement.Name)
  1934.             end
  1935.             out = out.."("
  1936.             if #statement.Arguments > 0 then
  1937.                 for i = 1, #statement.Arguments do
  1938.                     out = out..statement.Arguments[i].Name
  1939.                     if i ~= #statement.Arguments then
  1940.                         out = out..","
  1941.                     elseif statement.VarArg then
  1942.                         out = out..",..."
  1943.                     end
  1944.                 end
  1945.             elseif statement.VarArg then
  1946.                 out = out.."..."
  1947.             end
  1948.             out = out..")"
  1949.             out = joinStatements(out, formatStatlist(statement.Body))
  1950.             out = joinStatements(out, "end")
  1951.  
  1952.         elseif statement.AstType == 'GenericForStatement' then
  1953.             statement.Scope:ObfuscateLocals()
  1954.             out = "for "
  1955.             for i = 1, #statement.VariableList do
  1956.                 out = out..statement.VariableList[i].Name
  1957.                 if i ~= #statement.VariableList then
  1958.                     out = out..","
  1959.                 end
  1960.             end
  1961.             out = out.." in"
  1962.             for i = 1, #statement.Generators do
  1963.                 out = joinStatements(out, formatExpr(statement.Generators[i]))
  1964.                 if i ~= #statement.Generators then
  1965.                     out = joinStatements(out, ',')
  1966.                 end
  1967.             end
  1968.             out = joinStatements(out, "do")
  1969.             out = joinStatements(out, formatStatlist(statement.Body))
  1970.             out = joinStatements(out, "end")
  1971.  
  1972.         elseif statement.AstType == 'NumericForStatement' then
  1973.             statement.Scope:ObfuscateLocals()
  1974.             out = "for "
  1975.             out = out..statement.Variable.Name.."="
  1976.             out = out..formatExpr(statement.Start)..","..formatExpr(statement.End)
  1977.             if statement.Step then
  1978.                 out = out..","..formatExpr(statement.Step)
  1979.             end
  1980.             out = joinStatements(out, "do")
  1981.             out = joinStatements(out, formatStatlist(statement.Body))
  1982.             out = joinStatements(out, "end")
  1983.         elseif statement.AstType == 'LabelStatement' then
  1984.             out = "::" .. statement.Label .. "::"
  1985.         elseif statement.AstType == 'GotoStatement' then
  1986.             out = "goto " .. statement.Label
  1987.         elseif statement.AstType == 'Comment' then
  1988.             -- ignore
  1989.         elseif statement.AstType == 'Eof' then
  1990.             -- ignore
  1991.         else
  1992.             error("Unknown AST Type: " .. statement.AstType)
  1993.         end
  1994.         count = count + #out
  1995.         return out
  1996.     end
  1997.  
  1998.     formatStatlist = function(statList)
  1999.         local out = ''
  2000.         statList.Scope:ObfuscateLocals()
  2001.         for _, stat in pairs(statList.Body) do
  2002.             out = joinStatements(out, formatStatement(stat), ';')
  2003.         end
  2004.         return out
  2005.     end
  2006.  
  2007.     return formatStatlist(ast)
  2008. end
  2009.  
  2010. local push, pop = os.queueEvent, coroutine.yield
  2011.  
  2012. --- Minify a string
  2013. -- @tparam string input The input string
  2014. -- @treturn string The minifyied string
  2015. local function MinifyString(input)
  2016.     local lex = Parse.LexLua(input)
  2017.     push("sleep") pop("sleep") -- Minifying often takes too long
  2018.  
  2019.     lex = Parse.ParseLua(lex)
  2020.     push("sleep") pop("sleep")
  2021.  
  2022.     return Minify(lex)
  2023. end
  2024.  
  2025. --- Minify a file
  2026. -- @tparam string inputFile File to read from
  2027. -- @tparam string outputFile File to write to (Defaults to inputFile)
  2028. local function MinifyFile(inputFile, outputFile)
  2029.     outputFile = outputFile or inputFile
  2030.  
  2031.     local input = fs.open(inputFile, "r")
  2032.     local contents = input.readAll()
  2033.     input.close()
  2034.  
  2035.     contents = MinifyString(contents)
  2036.  
  2037.     local result = fs.open(outputFile, "w")
  2038.     result.write(contents)
  2039.     result.close()
  2040. end
  2041.  
  2042. --- @export
  2043. return {
  2044.     JoinStatements = JoinStatements,
  2045.     Minify = Minify,
  2046.     MinifyString = MinifyString,
  2047.     MinifyFile = MinifyFile,
  2048. }
  2049. end)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement