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