diff options
author | Li Jin <dragon-fly@qq.com> | 2023-07-26 10:08:25 +0800 |
---|---|---|
committer | Li Jin <dragon-fly@qq.com> | 2023-07-26 10:08:25 +0800 |
commit | dd55e092906123232f521b5d3ff2b848806ec67a (patch) | |
tree | 60cc9b5344423464bb666cf45eafb81cff559ace /src/3rdParty/luaminify.lua | |
parent | cbcbefaa218a02389b6385ab83c501cd3d03bde8 (diff) | |
download | yuescript-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.lua | 2486 |
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 | --[[ | ||
2 | The MIT License (MIT) | ||
3 | |||
4 | Copyright (c) 2012-2013 Mark Langen, modified by Li Jin 2023 | ||
5 | |||
6 | Permission 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 | |||
8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
9 | |||
10 | THE 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 | |||
18 | local function lookupify(tb) | ||
19 | for _, v in pairs(tb) do | ||
20 | tb[v] = true | ||
21 | end | ||
22 | return tb | ||
23 | end | ||
24 | |||
25 | |||
26 | local function CountTable(tb) | ||
27 | local c = 0 | ||
28 | for _ in pairs(tb) do c = c + 1 end | ||
29 | return c | ||
30 | end | ||
31 | |||
32 | |||
33 | local 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 | ||
73 | end | ||
74 | |||
75 | |||
76 | local 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 | |||
88 | local insert, char = table.insert, string.char | ||
89 | |||
90 | local chars = {} | ||
91 | for i = 97, 122 do | ||
92 | insert(chars, char(i)) | ||
93 | end | ||
94 | for i = 65, 90 do | ||
95 | insert(chars, char(i)) | ||
96 | end | ||
97 | |||
98 | local 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 | ||
123 | end | ||
124 | |||
125 | local 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 | |||
319 | local 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'} | ||
322 | local 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'} | ||
325 | local Digits = lookupify{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'} | ||
326 | local 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 | |||
329 | local Symbols = lookupify{'+', '-', '*', '/', '^', '%', ',', '{', '}', '[', ']', '(', ')', ';', '#'} | ||
330 | |||
331 | local 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 | |||
338 | local 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 | ||
780 | end | ||
781 | |||
782 | |||
783 | local 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 | ||
1718 | end | ||
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 | |||
1728 | local 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) | ||
2073 | end | ||
2074 | |||
2075 | local 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) | ||
2444 | end | ||
2445 | |||
2446 | local 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 | ||
2462 | end | ||
2463 | |||
2464 | return { | ||
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 | } | ||