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