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