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