1
0
Fork 0
mirror of https://github.com/Reuh/candran.git synced 2025-10-27 17:59:30 +00:00

Candran 0.2

Changed a LOT. Notable changes:
* Removed decorators, as they're not that useful, unless rewriting most
Lua libraries API.
* Added functions parameters default values.
* Added Lua 5.3 stuff and building to Lua 5.1.
* Remplaced the LuaMinify parser by lua-parser. It now requires some
non-Lua dependencies (LPeg) unfortunately, but it's waaaaaay easier to
handle. Code should be adaptable to any Metalua-like AST generator
anyway.
* The generated code now look like shit, and comment are stripped,
because the parser ignore them. Oh well.
* Changed a few things in the preprocessor environment.
* Nobody will read this commit message I guess. If you did, create an
issue saying "I love pineapple flavored bread".
This commit is contained in:
Reuh 2017-08-06 18:45:52 +02:00
parent 1875ea31de
commit 2a1e293aa5
45 changed files with 3899 additions and 11569 deletions

7
LICENSE Normal file
View file

@ -0,0 +1,7 @@
Copyright 2017 Étienne "Reuh" Fildadut
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:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.

114
README.md
View file

@ -1,34 +1,30 @@
Candran Candran
======= =======
Candran is a dialect of the [Lua](http://www.lua.org) programming language which compiles to Lua. It adds a preprocessor and several useful syntax additions. Candran is a dialect of the [Lua 5.3](http://www.lua.org) programming language which compiles to Lua 5.3 and Lua 5.1/LuaJit. It adds a preprocessor and several useful syntax additions.
Unlike Moonscript, Candran tries to stay close to the Lua syntax.
Candran code example : Candran code example :
````lua ````lua
#import("lib.thing") #import("lib.thing")
#local debug = args.debug or false #local debug = debug or false
local function debugArgs(func) local function calculate(toadd=25)
return function(...)
#if debug then
for _,arg in pairs({...}) do
print(arg, type(arg))
end
#end
return func(...)
end
end
@debugArgs
local function calculate()
local result = thing.do() local result = thing.do()
result += 25 result += toadd
#if debug then
print("Did something")
#end
return result return result
end end
print(calculate()) print(calculate())
```` ````
##### Quick setup
Install LPegLabel (```luarocks install LPegLabel```), download this repository and use Candran through ```canc.lua``` or ```candran.lua```.
The language The language
------------ ------------
### Preprocessor ### Preprocessor
@ -36,7 +32,7 @@ Before compiling, Candran's preprocessor is run. It execute every line starting
For example, For example,
````lua ````lua
#if args.lang == "fr" then #if lang == "fr" then
print("Bonjour") print("Bonjour")
#else #else
print("Hello") print("Hello")
@ -47,11 +43,12 @@ Will output ````print("Bonjour")```` or ````print("Hello")```` depending of the
The preprocessor has access to the following variables : The preprocessor has access to the following variables :
* ````candran```` : the Candran library table. * ````candran```` : the Candran library table.
* ````output```` : the preprocessor output string. * ````output```` : the current preprocessor output string.
* ````import(module[, autoRequire])```` : a function which import a module. This is equivalent to use _require(module)_ in the Candran code, except the module will be embedded in the current file. _autoRequire_ (boolean, default true) indicate if the module should be automaticaly loaded in a local variable or not. If true, the local variable will have the name of the module. * ````import(module[, [args, autoRequire]])```` : a function which import a module. This is equivalent to use _require(module)_ in the Candran code, except the module will be embedded in the current file. _args_ is an optional preprocessor arguments table for the imported module (current preprocessor arguments will be inherited). _autoRequire_ (boolean, default true) indicate if the module should be automaticaly loaded in a local variable or not. If true, the local variable will have the name of the module.
* ````include(filename)```` : a function which copy the contents of the file _filename_ to the output. * ````include(filename)```` : a function which copy the contents of the file _filename_ to the output.
* ````print(...)```` : instead of writing to stdout, _print(...)_ will write to the preprocessor output. For example, ````#print("hello()")```` will output ````hello()````. * ````write(...)```` : write to the preprocessor output. For example, ````#print("hello()")```` will output ````hello()```` in the final file.
* ````args```` : the arguments table passed to the compiler. Example use : ````withDebugTools = args["debug"]````. * ```placeholder(name)``` : if the variable _name_ is defined in the preprocessor environement, its content will be inserted here.
* ````...```` : each arguments passed to the preprocessor is directly available.
* and every standard Lua library. * and every standard Lua library.
### Syntax additions ### Syntax additions
@ -61,56 +58,70 @@ After the preprocessor is run the Candran code is compiled to Lua. The Candran c
* ````var -= nb```` * ````var -= nb````
* ````var *= nb```` * ````var *= nb````
* ````var /= nb```` * ````var /= nb````
* ````var //= nb````
* ````var ^= nb```` * ````var ^= nb````
* ````var %= nb```` * ````var %= nb````
* ````var ..= str```` * ````var ..= str````
* ````var and= str````
* ````var or= str````
* ````var &= nb````
* ````var |= nb````
* ````var <<= nb````
* ````var >>= nb````
For example, a ````var += nb```` assignment will be compiled into ````var = var + nb````. For example, a ````var += nb```` assignment will be compiled into ````var = var + nb````.
##### Decorators ##### Default function parameters
Candran supports function decorators similar to Python. A decorator is a function returning another function, and allows easy function modification with this syntax : ```lua
````lua function foo(bar = "default", other = thing.do())
@decorator -- stuff
function name(...)
...
end end
```` ```
This is equivalent to : If an argument isn't provided or ```nil``` when the function is called, it will be automatically set to a default value.
````lua
function name(...) It is equivalent to doing ```if arg == nil then arg = default end``` for each argument at the start of the function.
...
end The default values can be complete Lua expressions, and will be evaluated each time the function is run.
name = decorator(name)
```` Compile targets
The decorators can be chained. Note that Candran allows this syntax for every variable, not only functions. ---------------
Candran is based on the Lua 5.3 syntax, but can be compiled to both Lua 5.3 and Lua 5.1/LuaJit.
To chose a compile target, either explicitly give ```lua53``` or ```luajit``` as a second argument to ```candran.compile```, or set the ```target``` preprocessor argument when using ```candran.make``` or the command line tools.
Lua 5.3 specific syntax (bitwise operators, integer division) will automatically be translated in valid Lua 5.1 code, using LuaJit's ```bit``` library if necessary.
The library The library
----------- -----------
### Command-line usage ### Command-line usage
The library can be used standalone : The library can be used standalone through the ```canc``` utility:
* ````lua canc.lua````
* ````lua candran.lua````
Display the information text (version and basic command-line usage). Display the information text (version and basic command-line usage).
* ````lua candran.lua <filename> [arguments]```` * ````lua canc.lua [arguments] filename...````
Output to stdout the _filename_ Candran file, preprocessed (with _arguments_) and compiled to Lua.
_arguments_ is of type ````--somearg value --anotherarg anothervalue ...````. Preprocess and compile each _filename_ Candran files, and creates the assiociated ```.lua``` files in the same directories.
_arguments_ is of type ````-somearg -anotherarg thing=somestring other=5 ...````, which will generate a Lua table ```{ somearg = true, anotherarg = true, thing = "somestring", other = 5 }```.
You can choose to use another directory where files should be written using the ```dest=destinationDirectory``` argument.
```canc``` can write to the standard output instead of creating files using the ```-print``` argument.
* example uses : * example uses :
````lua candran.lua foo.can > foo.lua```` ````lua canc.lua foo.can````
preprocess and compile _foo.can_ and write the result in _foo.lua_. preprocess and compile _foo.can_ and write the result in _foo.lua_.
````lua candran.lua foo.can --verbose true | lua```` ````lua canc.lua foo.can -verbose -print | lua````
preprocess _foo.can_ with _verbose_ set to _true_, compile it and execute it. preprocess _foo.can_ with _verbose_ set to _true_, compile it and execute it.
### Library usage ### Library usage
Candran can also be used as a normal Lua library. For example, Candran can also be used as a Lua library. For example,
````lua ````lua
local candran = require("candran") local candran = require("candran")
@ -126,18 +137,19 @@ Will load Candran, read the file _foo.can_, compile its contents with the argume
The table returned by _require("candran")_ gives you access to : The table returned by _require("candran")_ gives you access to :
* ````candran.VERSION```` : Candran's version string. * ````candran.VERSION```` : Candran's version string.
* ````candran.syntax```` : table containing all the syntax additions of Candran.
* ````candran.preprocess(code[, args])```` : return the Candran code _code_, preprocessed with _args_ as argument table. * ````candran.preprocess(code[, args])```` : return the Candran code _code_, preprocessed with _args_ as argument table.
* ````candran.compile(code)```` : return the Candran code compiled to Lua. * ````candran.compile(code[, target])```` : return the Candran code compiled to Lua.
* ````candran.make(code[, args])```` : return the Candran code, preprocessed with _args_ as argument table and compilled to Lua. * ````candran.make(code[, args])```` : return the Candran code, preprocessed with _args_ as argument table and compilled to Lua.
### Compiling the library ### Compiling the library
The Candran library itself is written is Candran, so you have to compile it with an already compiled Candran library. The Candran library itself is written is Candran, so you have to compile it with an already compiled Candran library.
This command will use the precompilled version of this repository (build/candran.lua) to compile _candran.can_ and write the result in _candran.lua_ : The compiled _candran.lua_ should include every Lua library needed to run it. You will still need to install LPegLabel.
This command will use the precompilled version of this repository (candran.lua) to compile _candran.can_ and write the result in _candran.lua_ :
```` ````
lua build/candran.lua candran.can > candran.lua lua canc.lua candran.can
```` ````
You can then run the tests on your build : You can then run the tests on your build :
@ -145,4 +157,4 @@ You can then run the tests on your build :
```` ````
cd tests cd tests
lua test.lua ../candran.lua lua test.lua ../candran.lua
```` ````

File diff suppressed because it is too large Load diff

44
canc.lua Normal file
View file

@ -0,0 +1,44 @@
#!/bin/lua
local candran = require("candran")
local cmdline = require("cmdline")
if #arg < 1 then
print("Candran compiler version "..candran.VERSION.." by Reuh")
print("Usage: "..arg[0].." [target=<target>] [dest=<destination directory>] [-print] [preprocessor arguments] filename...")
return
end
local args = cmdline(arg)
for _, file in ipairs(args) do
local dest = file:gsub("%.can$", "")..".lua"
if args.dest then
dest = args.dest .. "/" .. dest
end
if not args.print then
print("Compiling "..file.." in "..dest)
end
local inputFile, err = io.open(file, "r")
if not inputFile then error("Error while opening input file: "..err) end
local input = inputFile:read("*a")
inputFile:close()
local out = candran.make(input, args)
if args.print then
print(out)
else
local outFile = io.open(dest, "w")
if not outFile then
os.execute("mkdir -p "..dest:gsub("[^/]+%.lua$", ""))
outFile, err = io.open(dest, "w")
if not outFile then
error("Error while writing output file: "..err)
end
end
outFile:write(out)
outFile:close()
end
end

View file

@ -1,170 +1,148 @@
--[[ #import("util")
Candran language, preprocessor and compiler by Thomas99. #import("compiler.lua53")
#import("compiler.luajit")
#import("lua-parser.scope")
#import("lua-parser.validator")
#import("lua-parser.pp")
#import("lua-parser.parser")
#import("cmdline")
LICENSE : local util = require("util")
Copyright (c) 2015 Thomas99
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the
use of this software.
Permission is granted to anyone to use this software for any purpose, including
commercial applications, and to alter it and redistribute it freely, subject
to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in a
product, an acknowledgment in the product documentation would be appreciated
but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
]]
local candran = { local candran = {
VERSION = "0.1.0", VERSION = "0.2.0"
syntax = {
assignment = { "+=", "-=", "*=", "/=", "^=", "%=", "..=" },
decorator = "@"
}
} }
package.loaded["candran"] = candran
#import("lib.table") --- Run the preprocessor
#import("lib.LuaMinify.Util") -- @tparam input string input code
#import("lib.LuaMinify.Scope") -- @tparam args table arguments for the preprocessor. They will be inserted into the preprocessor environement.
#import("lib.LuaMinify.ParseCandran") -- @treturn output string output code
#import("lib.LuaMinify.FormatIdentityCandran") function candran.preprocess(input, args={})
-- generate preprocessor code
-- Preprocessor local preprocessor = ""
function candran.preprocess(input, args) for line in (input.."\n"):gmatch("(.-\n)") do
-- generate preprocessor if line:match("^%s*#") and not line:match("^#!") then -- exclude shebang
local preprocessor = "return function()\n" preprocessor ..= line:gsub("^%s*#", "")
local lines = {}
for line in (input.."\n"):gmatch("(.-)\n") do
table.insert(lines, line)
-- preprocessor instructions (exclude shebang)
if line:match("^%s*#") and not (line:match("^#!") and #lines == 1) then
preprocessor ..= line:gsub("^%s*#", "") .. "\n"
else else
preprocessor ..= "output ..= lines[" .. #lines .. "] .. \"\\n\"\n" preprocessor ..= ("write(%q)"):format(line:sub(1, -2)) .. "\n"
end end
end end
preprocessor ..= "return output\nend" preprocessor ..= "return output"
-- make preprocessor environement -- make preprocessor environement
local env = table.copy(_G) local env = {}
for k,v in pairs(_G) do
env[k] = v
end
for k, v in pairs(args) do
env[k] = v
end
--- Candran library table
env.candran = candran env.candran = candran
--- Current preprocessor output
env.output = "" env.output = ""
env.import = function(modpath, autoRequire) --- Import an external Candran/Lua module into the generated file
local autoRequire = (autoRequire == nil) or autoRequire -- @tparam modpath string module path
-- @tparam margs table preprocessor arguments to use when preprocessessing the module
-- get module filepath -- @tparam autoRequire[opt=true] boolean true to automatically load the module into a local variable
local filepath env.import = function(modpath, margs=args, autoRequire=true)
for _,search in ipairs(package.searchers) do local filepath = assert(util.search(modpath), "No module named \""..modpath.."\"")
local loader, path = search(modpath)
if type(loader) == "function" and type(path) == "string" then
filepath = path
break
end
end
if not filepath then error("No module named \""..modpath.."\"") end
-- open module file -- open module file
local f = io.open(filepath) local f = io.open(filepath)
if not f then error("Can't open the module file to import") end if not f then error("Can't open the module file to import") end
local modcontent = f:read("*a") for k, v in pairs(args) do
if margs[k] == nil then margs[k] = v end
end
local modcontent = candran.preprocess(f:read("*a"), margs)
f:close() f:close()
-- get module name (ex: module name of path.to.module is module) -- get module name (ex: module name of path.to.module is module)
local modname = modpath:match("[^%.]+$") local modname = modpath:match("[^%.]+$")
-- env.write(
env.output ..= "-- MODULE \""..modpath.."\" --\n"..
"-- IMPORT OF MODULE \""..modpath.."\" --\n"..
"local function _()\n".. "local function _()\n"..
modcontent.."\n".. modcontent.."\n"..
"end\n".. "end\n"..
(autoRequire and "local "..modname.." = _() or "..modname.."\n" or "").. -- auto require (autoRequire and "local "..modname.." = _() or "..modname.."\n" or "").. -- auto require
"package.loaded[\""..modpath.."\"] = "..(autoRequire and modname or "_()").." or true\n".. -- add to package.loaded "package.loaded[\""..modpath.."\"] = "..(autoRequire and modname or "_()").." or true\n".. -- add to package.loaded
"-- END OF IMPORT OF MODULE \""..modpath.."\" --\n" "-- END OF MODULE \""..modpath.."\" --"
)
end end
--- Include another file content in the preprocessor output.
-- @tparam file string filepath
env.include = function(file) env.include = function(file)
local f = io.open(file) local f = io.open(file)
if not f then error("Can't open the file to include") end if not f then error("Can't open the file "..file.." to include") end
env.output ..= f:read("*a").."\n" env.write(f:read("*a"))
f:close() f:close()
end end
env.print = function(...) --- Write a line in the preprocessor output.
-- @tparam ... string strings to write (similar to print)
env.write = function(...)
env.output ..= table.concat({...}, "\t") .. "\n" env.output ..= table.concat({...}, "\t") .. "\n"
end end
env.args = args or {} --- Will be replaced with the content of the variable with the given name, if it exists.
env.lines = lines -- @tparam name string variable name
env.placeholder = function(name)
if env[name] then
env.write(env[name])
end
end
-- load preprocessor -- compile & load preprocessor
local preprocess, err = load(candran.compile(preprocessor), "Preprocessor", nil, env) local preprocess, err = util.loadenv(candran.compile(preprocessor, args.target), "candran preprocessor", env)
if not preprocess then error("Error while creating preprocessor :\n" .. err) end if not preprocess then error("Error while creating Candran preprocessor: " .. err) end
-- execute preprocessor -- execute preprocessor
local success, output = pcall(preprocess()) local success, output = pcall(preprocess)
if not success then error("Error while preprocessing file :\n" .. output .. "\nWith preprocessor : \n" .. preprocessor) end if not success then error("Error while preprocessing file: " .. output .. "\nWith preprocessor : \n" .. preprocessor) end
return output return output
end end
-- Compiler -- Compiler
function candran.compile(input) function candran.compile(input, target="lua53")
local parse = require("lib.LuaMinify.ParseCandran") local parse = require("lua-parser.parser").parse
local format = require("lib.LuaMinify.FormatIdentityCandran")
local success, ast = parse.ParseLua(input) local ast, errmsg = parse(input, "candran")
if not success then error("Error while parsing the file :\n"..tostring(ast)) end
local success, output = format(ast)
if not success then error("Error while formating the file :\n"..tostring(output)) end
return output if not ast then
error("Compiler: error while parsing file: "..errmsg)
end
return require("compiler."..target)(ast)
end end
-- Preprocess & compile -- Preprocess & compile
function candran.make(code, args) function candran.make(code, args={})
local preprocessed = candran.preprocess(code, args or {}) return candran.compile(candran.preprocess(code, args), args.target)
local output = candran.compile(preprocessed)
return output
end end
-- Standalone mode function candran.searcher(modpath)
if debug.getinfo(3) == nil and arg then -- get module filepath
-- Check args local notfound = ""
if #arg < 1 then local filepath
print("Candran version "..candran.VERSION.." by Thomas99") for path in package.path:gsub("%.lua", ".can"):gmatch("[^;]+") do
print("Command-line usage :") local path = path:gsub("%?", (modpath:gsub("%.", "/")))
print("lua candran.lua <filename> [preprocessor arguments]") local f = io.open(path)
return candran if f then
end f:close()
filepath = path
-- Parse args else
local inputFilepath = arg[1] notfound = notfound .. "\n\tno Candran file '"..path.."'"
local args = {}
for i=2, #arg, 1 do
if arg[i]:sub(1,2) == "--" then
args[arg[i]:sub(3)] = arg[i+1]
i = i + 1 -- skip argument value
end end
end end
if not filepath then return notfound end
-- Open & read input file -- open module file
local inputFile, err = io.open(inputFilepath, "r") local f = io.open(filepath)
if not inputFile then error("Error while opening input file : "..err) end if not f then error("Can't open the module file to import") end
local input = inputFile:read("*a") local modcontent = f:read("*a")
inputFile:close() f:close()
-- Make return load(candran.make(modcontent))
print(candran.make(input, args))
end end
return candran return candran

1813
candran.lua Normal file

File diff suppressed because it is too large Load diff

118
cmdline.lua Normal file
View file

@ -0,0 +1,118 @@
-- started: 2008-04-12 by Shmuel Zeigerman
-- license: public domain
local ipairs,pairs,setfenv,tonumber,loadstring,type =
ipairs,pairs,setfenv,tonumber,loadstring,type
local tinsert, tconcat = table.insert, table.concat
local function commonerror (msg)
return nil, ("[cmdline]: " .. msg)
end
local function argerror (msg, numarg)
msg = msg and (": " .. msg) or ""
return nil, ("[cmdline]: bad argument #" .. numarg .. msg)
end
local function iderror (numarg)
return argerror("ID not valid", numarg)
end
local function idcheck (id)
return id:match("^[%a_][%w_]*$") and true
end
--[[------------------------------------------------------------------------
Syntax:
t_out = getparam(t_in [,options] [,params])
Parameters:
t_in: table - list of string arguments to be processed in order
(usually it is the `arg' table created by the Lua interpreter).
* if an argument begins with $, the $ is skipped and the rest is inserted
into the array part of the output table.
* if an argument begins with -, the rest is a sequence of variables
(separated by commas or semicolons) that are all set to true;
example: -var1,var2 --> var1,var2 = true,true
* if an argument begins with !, the rest is a Lua chunk;
example: !a=(40+3)*5;b=20;name="John";window={w=600,h=480}
* if an argument contains =, then it is an assignment in the form
var1,...=value (no space is allowed around the =)
* if value begins with $, the $ is skipped, the rest is a string
example: var1,var2=$ --> var1,var2 = "",""
example: var1,var2=$125 --> var1,var2 = "125","125"
example: var1,var2=$$125 --> var1,var2 = "$125","$125"
* if value is convertible to number, it is a number
example: var1,var2=125 --> var1,var2 = 125,125
* otherwise it is a string
example: name=John --> name = "John"
* if an argument neither begins with one of the special characters (-,!,$),
nor contains =, it is inserted as is into the array part of the output
table.
options (optional): a list of names of all command-line options and parameters
permitted in the application; used to check that each found option
is valid; no checks are done if not supplied.
params (optional): a list of names of all command-line parameters required
by the application; used to check that each required parameter is present;
no checks are done if not supplied.
Returns:
On success: the output table, e.g. { [1]="./myfile.txt", name="John", age=40 }
On error: nil followed by error message string.
--]]------------------------------------------------------------------------
return function(t_in, options, params)
local t_out = {}
for i,v in ipairs(t_in) do
local prefix, command = v:sub(1,1), v:sub(2)
if prefix == "$" then
tinsert(t_out, command)
elseif prefix == "-" then
for id in command:gmatch"[^,;]+" do
if not idcheck(id) then return iderror(i) end
t_out[id] = true
end
elseif prefix == "!" then
local f, err = loadstring(command)
if not f then return argerror(err, i) end
setfenv(f, t_out)()
elseif v:find("=") then
local ids, val = v:match("^([^=]+)%=(.*)") -- no space around =
if not ids then return argerror("invalid assignment syntax", i) end
val = val:sub(1,1)=="$" and val:sub(2) or tonumber(val) or val
for id in ids:gmatch"[^,;]+" do
if not idcheck(id) then return iderror(i) end
t_out[id] = val
end
else
tinsert(t_out, v)
end
end
if options then
local lookup, unknown = {}, {}
for _,v in ipairs(options) do lookup[v] = true end
for k,_ in pairs(t_out) do
if lookup[k]==nil and type(k)=="string" then tinsert(unknown, k) end
end
if #unknown > 0 then
return commonerror("unknown options: " .. tconcat(unknown, ", "))
end
end
if params then
local missing = {}
for _,v in ipairs(params) do
if t_out[v]==nil then tinsert(missing, v) end
end
if #missing > 0 then
return commonerror("missing parameters: " .. tconcat(missing, ", "))
end
end
return t_out
end

285
compiler/lua53.can Normal file
View file

@ -0,0 +1,285 @@
return function(ast, opts)
local options = {
indentation = "\t",
newline = "\n",
requirePrefix = "CANDRAN_"
}
local indentLevel = 0
local function newline()
return options.newline .. string.rep(options.indentation, indentLevel)
end
local function indent()
indentLevel += 1
return newline()
end
local function unindent()
indentLevel -= 1
return newline()
end
local required = {}
local requireStr = ""
local function addRequire(str, name, field)
if not required[str] then
requireStr ..= "local " .. options.requirePrefix .. name .. (" = require(%q)"):format(str) .. (field and "."..field or "") .. options.newline
required[str] = true
end
end
local function getRequire(name)
return options.requirePrefix .. name
end
local tags
local function lua(ast, forceTag, ...)
return tags[forceTag or ast.tag](ast, ...)
end
tags = setmetatable({
-- block: { stat* } --
Block = function(t)
local r = ""
for i=1, #t-1, 1 do
r = r .. lua(t[i]) .. newline()
end
if t[#t] then
r = r .. lua(t[#t])
end
return r
end,
-- stat --
-- Do{ stat* }
Do = function(t)
return "do" .. indent() .. lua(t, "Block") .. unindent() .. "end"
end,
-- Set{ {lhs+} opid? {expr+} }
Set = function(t)
if #t == 2 then
return lua(t[1], "_lhs") .. " = " .. lua(t[2], "_lhs")
else
local r = lua(t[1], "_lhs") .. " = " .. lua({ t[2], t[1][1], t[3][1] }, "Op")
for i=2, math.min(#t[3], #t[1]), 1 do
r = r .. ", " .. lua({ t[2], t[1][i], t[3][i] }, "Op")
end
return r
end
end,
-- While{ expr block }
While = function(t)
return "while " .. lua(t[1]) .. " do" .. indent() .. lua(t[2]) .. unindent() .. "end"
end,
-- Repeat{ block expr }
Repeat = function(t)
return "repeat".. indent() .. lua(t[1]) .. unindent() .. "until " .. lua(t[2])
end,
-- If{ (expr block)+ block? }
If = function(t)
local r = "if " .. lua(t[1]) .. " then" .. indent() .. lua(t[2]) .. unindent()
for i=3, #t-1, 2 do
r = r .. "elseif " .. lua(t[i]) .. " then" .. indent() .. lua(t[i+1]) .. unindent()
end
if #t % 2 == 1 then
r = r .. "else" .. indent() .. lua(t[#t]) .. unindent()
end
return r .. "end"
end,
-- Fornum{ ident expr expr expr? block }
Fornum = function(t)
local r = "for " .. lua(t[1]) .. " = " .. lua(t[2]) .. ", " .. lua(t[3])
if #t == 5 then
return r .. ", " .. lua(t[4]) .. " do" .. indent() .. lua(t[5]) .. unindent() .. "end"
else
return r .. " do" .. indent() .. lua(t[4]) .. unindent() .. "end"
end
end,
-- Forin{ {ident+} {expr+} block }
Forin = function(t)
return "for " .. lua(t[1], "_lhs") .. " in " .. lua(t[2], "_lhs") .. " do" .. indent() .. lua(t[3]) .. unindent() .. "end"
end,
-- Local{ {ident+} {expr+}? }
Local = function(t)
local r = "local "..lua(t[1], "_lhs")
if t[2][1] then
r = r .. " = "..lua(t[2], "_lhs")
end
return r
end,
-- Localrec{ ident expr }
Localrec = function(t)
return "local function "..lua(t[1][1])..lua(t[2][1], "_functionWithoutKeyword")
end,
-- Goto{ <string> }
Goto = function(t)
return "goto " .. lua(t[1], "Id")
end,
-- Label{ <string> }
Label = function(t)
return "::" .. lua(t[1], "Id") .. "::"
end,
-- Return{ <expr*> }
Return = function(t)
return "return "..lua(t, "_lhs")
end,
-- Break
Break = function()
return "break"
end,
-- apply (below)
-- expr --
-- Nil
Nil = function()
return "nil"
end,
-- Dots
Dots = function()
return "..."
end,
-- Boolean{ <boolean> }
Boolean = function(t)
return tostring(t[1])
end,
-- Number{ <number> }
Number = function(t)
return tostring(t[1])
end,
-- String{ <string> }
String = function(t)
return ("%q"):format(t[1])
end,
-- Function{ { ( `ParPair{ Id expr } | `Id{ <string> } )* `Dots? } block }
_functionWithoutKeyword = function(t)
local r = "("
local decl = {}
if t[1][1] then
if t[1][1].tag == "ParPair" then
local id = lua(t[1][1][1])
indentLevel += 1
table.insert(decl, id .. " = " .. id .. " == nil and " .. lua(t[1][1][2]) .. " or " .. id)
indentLevel -= 1
r = r .. id
else
r = r .. lua(t[1][1])
end
for i=2, #t[1], 1 do
if t[1][i].tag == "ParPair" then
local id = lua(t[1][i][1])
indentLevel += 1
table.insert(decl, "if " .. id .. " == nil then " .. id .. " = " .. lua(t[1][i][2]) .. " end")
indentLevel -= 1
r = r .. ", " ..id
else
r = r .. ", " .. lua(t[1][i])
end
end
end
r = r .. ")" .. indent()
for _, d in ipairs(decl) do
r = r .. d .. newline()
end
return r .. lua(t[2]) .. unindent() .. "end"
end,
Function = function(t)
return "function" .. lua(t, "_functionWithoutKeyword")
end,
-- Table{ ( `Pair{ expr expr } | expr )* }
Pair = function(t)
return "[" .. lua(t[1]) .. "] = " .. lua(t[2])
end,
Table = function(t)
if #t == 0 then
return "{}"
elseif #t == 1 then
return "{ " .. lua(t, "_lhs") .. " }"
else
return "{" .. indent() .. lua(t, "_lhs") .. unindent() .. "}"
end
end,
-- Op{ opid expr expr? }
Op = function(t)
local r
if #t == 2 then
if type(tags._opid[t[1]]) == "string" then
r = tags._opid[t[1]] .. " " .. lua(t[2])
else
r = tags._opid[t[1]](t[2])
end
else
if type(tags._opid[t[1]]) == "string" then
r = lua(t[2]) .. " " .. tags._opid[t[1]] .. " " .. lua(t[3])
else
r = tags._opid[t[1]](t[2], t[3])
end
end
return r
end,
-- Paren{ expr }
Paren = function(t)
return "(" .. lua(t[1]) .. ")"
end,
-- apply (below)
-- lhs (below)
-- apply --
-- Call{ expr expr* }
Call = function(t)
return lua(t[1]) .. "(" .. lua(t, "_lhs", 2) .. ")"
end,
-- Invoke{ expr `String{ <string> } expr* }
Invoke = function(t)
return lua(t[1])..":"..lua(t[2], "Id").."("..lua(t, "_lhs", 3)..")"
end,
-- lhs --
_lhs = function(t, start)
start = start or 1
local r
if t[start] then
r = lua(t[start])
for i=start+1, #t, 1 do
r = r .. ", "..lua(t[i])
end
else
r = ""
end
return r
end,
-- Id{ <string> }
Id = function(t)
return t[1]
end,
-- Index{ expr expr }
Index = function(t)
return lua(t[1]).."["..lua(t[2]).."]"
end,
-- opid --
_opid = {
add = "+", sub = "-", mul = "*", div = "/",
idiv = "//", mod = "%", pow = "^", concat = "..",
band = "&", bor = "|", bxor = "~", shl = "<<", shr = ">>",
eq = "==", ne = "~=", lt = "<", gt = ">", le = "<=", ge = ">=",
["and"] = "and", ["or"] = "or", unm = "-", len = "#", bnot = "~", ["not"] = "not"
}
}, {
__index = function(self, key)
error("don't know how to compile a "..tostring(key).." to Lua 5.3")
end
})
#placeholder("patch")
if opts then
for k, v in pairs(opts) do
options[k] = v
end
end
local r = lua(ast)
return requireStr .. r
end

33
compiler/luajit.can Normal file
View file

@ -0,0 +1,33 @@
tags._opid.idiv = function(left, right)
return "math.floor(" .. lua(left) .. " / " .. lua(right) .. ")"
end
tags._opid.band = function(left, right)
addRequire("bit", "band", "band")
return getRequire("band") .. "(" .. lua(left) .. ", " .. lua(right) .. ")"
end
tags._opid.bor = function(left, right)
addRequire("bit", "bor", "bor")
return getRequire("bor") .. "(" .. lua(left) .. ", " .. lua(right) .. ")"
end
tags._opid.bxor = function(left, right)
addRequire("bit", "bxor", "bxor")
return getRequire("bxor") .. "(" .. lua(left) .. ", " .. lua(right) .. ")"
end
tags._opid.shl = function(left, right)
addRequire("bit", "lshift", "lshift")
return getRequire("lshift") .. "(" .. lua(left) .. ", " .. lua(right) .. ")"
end
tags._opid.shr = function(left, right)
addRequire("bit", "rshift", "rshift")
return getRequire("rshift") .. "(" .. lua(left) .. ", " .. lua(right) .. ")"
end
tags._opid.bnot = function(right)
addRequire("bit", "bnot", "bnot")
return getRequire("bnot") .. "(" .. lua(right) .. ")"
end
#local patch = output
#output = ""
#import("compiler.lua53", { patch = patch })
return lua53

View file

@ -1,121 +0,0 @@
--
-- beautify
--
-- A command line utility for beautifying lua source code using the beautifier.
--
local util = require'Util'
local Parser = require'ParseLua'
local Format_Beautify = require'FormatBeautiful'
local ParseLua = Parser.ParseLua
local PrintTable = util.PrintTable
local function splitFilename(name)
--table.foreach(arg, print)
if name:find(".") then
local p, ext = name:match("()%.([^%.]*)$")
if p and ext then
if #ext == 0 then
return name, nil
else
local filename = name:sub(1,p-1)
return filename, ext
end
else
return name, nil
end
else
return name, nil
end
end
if #arg == 1 then
local name, ext = splitFilename(arg[1])
local outname = name.."_formatted"
if ext then outname = outname.."."..ext end
--
local inf = io.open(arg[1], 'r')
if not inf then
print("Failed to open '"..arg[1].."' for reading")
return
end
--
local sourceText = inf:read('*all')
inf:close()
--
local st, ast = ParseLua(sourceText)
if not st then
--we failed to parse the file, show why
print(ast)
return
end
--
local outf = io.open(outname, 'w')
if not outf then
print("Failed to open '"..outname.."' for writing")
return
end
--
outf:write(Format_Beautify(ast))
outf:close()
--
print("Beautification complete")
elseif #arg == 2 then
--keep the user from accidentally overwriting their non-minified file with
if arg[1]:find("_formatted") then
print("Did you mix up the argument order?\n"..
"Current command will beautify '"..arg[1].."' and overwrite '"..arg[2].."' with the results")
while true do
io.write("Confirm (yes/no): ")
local msg = io.read('*line')
if msg == 'yes' then
break
elseif msg == 'no' then
return
end
end
end
local inf = io.open(arg[1], 'r')
if not inf then
print("Failed to open '"..arg[1].."' for reading")
return
end
--
local sourceText = inf:read('*all')
inf:close()
--
local st, ast = ParseLua(sourceText)
if not st then
--we failed to parse the file, show why
print(ast)
return
end
--
if arg[1] == arg[2] then
print("Are you SURE you want to overwrite the source file with a beautified version?\n"..
"You will be UNABLE to get the original source back!")
while true do
io.write("Confirm (yes/no): ")
local msg = io.read('*line')
if msg == 'yes' then
break
elseif msg == 'no' then
return
end
end
end
local outf = io.open(arg[2], 'w')
if not outf then
print("Failed to open '"..arg[2].."' for writing")
return
end
--
outf:write(Format_Beautify(ast))
outf:close()
--
print("Beautification complete")
else
print("Invalid arguments!\nUsage: lua CommandLineLuaBeautify.lua source_file [destination_file]")
end

View file

@ -1,47 +0,0 @@
--
-- beautify.interactive
--
-- For testing: Lets you enter lines of text to be beautified to verify the
-- correctness of their implementation.
--
local util = require'Util'
local Parser = require'ParseLua'
local Format_Beautify = require'FormatBeautiful'
local ParseLua = Parser.ParseLua
local PrintTable = util.PrintTable
while true do
io.write('> ')
local line = io.read('*line')
local fileFrom, fileTo = line:match("^file (.*) (.*)")
if fileFrom and fileTo then
local file = io.open(fileFrom, 'r')
local fileTo = io.open(fileTo, 'w')
if file and fileTo then
local st, ast = ParseLua(file:read('*all'))
if st then
fileTo:write(Format_Beautify(ast)..'\n')
io.write("Beautification Complete\n")
else
io.write(""..tostring(ast).."\n")
end
file:close()
fileTo:close()
else
io.write("File does not exist\n")
end
else
local st, ast = ParseLua(line)
if st then
io.write("====== AST =======\n")
io.write(PrintTable(ast)..'\n')
io.write("==== BEAUTIFIED ====\n")
io.write(Format_Beautify(ast))
io.write("==================\n")
else
io.write(""..tostring(ast).."\n")
end
end
end

View file

@ -1,47 +0,0 @@
--
-- CommandLineLiveMinify.lua
--
-- For testing: Lets you enter lines of text to be minified to verify the
-- correctness of their implementation.
--
local util = require'Util'
local Parser = require'ParseLua'
local Format_Mini = require'FormatMini'
local ParseLua = Parser.ParseLua
local PrintTable = util.PrintTable
while true do
io.write('> ')
local line = io.read('*line')
local fileFrom, fileTo = line:match("^file (.*) (.*)")
if fileFrom and fileTo then
local file = io.open(fileFrom, 'r')
local fileTo = io.open(fileTo, 'w')
if file and fileTo then
local st, ast = ParseLua(file:read('*all'))
if st then
fileTo:write(Format_Mini(ast)..'\n')
io.write("Minification Complete\n")
else
io.write(""..tostring(ast).."\n")
end
file:close()
fileTo:close()
else
io.write("File does not exist\n")
end
else
local st, ast = ParseLua(line)
if st then
io.write("====== AST =======\n")
io.write(PrintTable(ast)..'\n')
io.write("==== MINIFIED ====\n")
io.write(Format_Mini(ast)..'\n')
io.write("==================\n")
else
io.write(""..tostring(ast).."\n")
end
end
end

View file

@ -1,122 +0,0 @@
--
-- CommandlineMinify.lua
--
-- A command line utility for minifying lua source code using the minifier.
--
local util = require'Util'
local Parser = require'ParseLua'
local Format_Mini = require'FormatMini'
local ParseLua = Parser.ParseLua
local PrintTable = util.PrintTable
local function splitFilename(name)
table.foreach(arg, print)
if name:find(".") then
local p, ext = name:match("()%.([^%.]*)$")
if p and ext then
if #ext == 0 then
return name, nil
else
local filename = name:sub(1,p-1)
return filename, ext
end
else
return name, nil
end
else
return name, nil
end
end
if #arg == 1 then
local name, ext = splitFilename(arg[1])
local outname = name.."_min"
if ext then outname = outname.."."..ext end
--
local inf = io.open(arg[1], 'r')
if not inf then
print("Failed to open `"..arg[1].."` for reading")
return
end
--
local sourceText = inf:read('*all')
inf:close()
--
local st, ast = ParseLua(sourceText)
if not st then
--we failed to parse the file, show why
print(ast)
return
end
--
local outf = io.open(outname, 'w')
if not outf then
print("Failed to open `"..outname.."` for writing")
return
end
--
outf:write(Format_Mini(ast))
outf:close()
--
print("Minification complete")
elseif #arg == 2 then
--keep the user from accidentally overwriting their non-minified file with
if arg[1]:find("_min") then
print("Did you mix up the argument order?\n"..
"Current command will minify `"..arg[1].."` and OVERWRITE `"..arg[2].."` with the results")
while true do
io.write("Confirm (yes/cancel): ")
local msg = io.read('*line')
if msg == 'yes' then
break
elseif msg == 'cancel' then
return
end
end
end
local inf = io.open(arg[1], 'r')
if not inf then
print("Failed to open `"..arg[1].."` for reading")
return
end
--
local sourceText = inf:read('*all')
inf:close()
--
local st, ast = ParseLua(sourceText)
if not st then
--we failed to parse the file, show why
print(ast)
return
end
--
if arg[1] == arg[2] then
print("Are you SURE you want to overwrite the source file with a minified version?\n"..
"You will be UNABLE to get the original source back!")
while true do
io.write("Confirm (yes/cancel): ")
local msg = io.read('*line')
if msg == 'yes' then
break
elseif msg == 'cancel' then
return
end
end
end
local outf = io.open(arg[2], 'w')
if not outf then
print("Failed to open `"..arg[2].."` for writing")
return
end
--
outf:write(Format_Mini(ast))
outf:close()
--
print("Minification complete")
else
print("Invalid arguments, Usage:\nLuaMinify source [destination]")
end

View file

@ -1,347 +0,0 @@
--
-- Beautifier
--
-- Returns a beautified version of the code, including comments
--
local parser = require"ParseLua"
local ParseLua = parser.ParseLua
local util = require'Util'
local lookupify = util.lookupify
local LowerChars = lookupify{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
local UpperChars = lookupify{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}
local Digits = lookupify{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
local function Format_Beautify(ast)
local formatStatlist, formatExpr
local indent = 0
local EOL = "\n"
local function getIndentation()
return string.rep(" ", indent)
end
local function joinStatementsSafe(a, b, sep)
sep = sep or ''
local aa, bb = a:sub(-1,-1), b:sub(1,1)
if UpperChars[aa] or LowerChars[aa] or aa == '_' then
if not (UpperChars[bb] or LowerChars[bb] or bb == '_' or Digits[bb]) then
--bb is a symbol, can join without sep
return a .. b
elseif bb == '(' then
--prevent ambiguous syntax
return a..sep..b
else
return a..sep..b
end
elseif Digits[aa] then
if bb == '(' then
--can join statements directly
return a..b
else
return a..sep..b
end
elseif aa == '' then
return a..b
else
if bb == '(' then
--don't want to accidentally call last statement, can't join directly
return a..sep..b
else
return a..b
end
end
end
formatExpr = function(expr)
local out = string.rep('(', expr.ParenCount or 0)
if expr.AstType == 'VarExpr' then
if expr.Variable then
out = out .. expr.Variable.Name
else
out = out .. expr.Name
end
elseif expr.AstType == 'NumberExpr' then
out = out..expr.Value.Data
elseif expr.AstType == 'StringExpr' then
out = out..expr.Value.Data
elseif expr.AstType == 'BooleanExpr' then
out = out..tostring(expr.Value)
elseif expr.AstType == 'NilExpr' then
out = joinStatementsSafe(out, "nil")
elseif expr.AstType == 'BinopExpr' then
out = joinStatementsSafe(out, formatExpr(expr.Lhs)) .. " "
out = joinStatementsSafe(out, expr.Op) .. " "
out = joinStatementsSafe(out, formatExpr(expr.Rhs))
elseif expr.AstType == 'UnopExpr' then
out = joinStatementsSafe(out, expr.Op) .. (#expr.Op ~= 1 and " " or "")
out = joinStatementsSafe(out, formatExpr(expr.Rhs))
elseif expr.AstType == 'DotsExpr' then
out = out.."..."
elseif expr.AstType == 'CallExpr' then
out = out..formatExpr(expr.Base)
out = out.."("
for i = 1, #expr.Arguments do
out = out..formatExpr(expr.Arguments[i])
if i ~= #expr.Arguments then
out = out..", "
end
end
out = out..")"
elseif expr.AstType == 'TableCallExpr' then
out = out..formatExpr(expr.Base) .. " "
out = out..formatExpr(expr.Arguments[1])
elseif expr.AstType == 'StringCallExpr' then
out = out..formatExpr(expr.Base) .. " "
out = out..expr.Arguments[1].Data
elseif expr.AstType == 'IndexExpr' then
out = out..formatExpr(expr.Base).."["..formatExpr(expr.Index).."]"
elseif expr.AstType == 'MemberExpr' then
out = out..formatExpr(expr.Base)..expr.Indexer..expr.Ident.Data
elseif expr.AstType == 'Function' then
-- anonymous function
out = out.."function("
if #expr.Arguments > 0 then
for i = 1, #expr.Arguments do
out = out..expr.Arguments[i].Name
if i ~= #expr.Arguments then
out = out..", "
elseif expr.VarArg then
out = out..", ..."
end
end
elseif expr.VarArg then
out = out.."..."
end
out = out..")" .. EOL
indent = indent + 1
out = joinStatementsSafe(out, formatStatlist(expr.Body))
indent = indent - 1
out = joinStatementsSafe(out, getIndentation() .. "end")
elseif expr.AstType == 'ConstructorExpr' then
out = out.."{ "
for i = 1, #expr.EntryList do
local entry = expr.EntryList[i]
if entry.Type == 'Key' then
out = out.."["..formatExpr(entry.Key).."] = "..formatExpr(entry.Value)
elseif entry.Type == 'Value' then
out = out..formatExpr(entry.Value)
elseif entry.Type == 'KeyString' then
out = out..entry.Key.." = "..formatExpr(entry.Value)
end
if i ~= #expr.EntryList then
out = out..", "
end
end
out = out.." }"
elseif expr.AstType == 'Parentheses' then
out = out.."("..formatExpr(expr.Inner)..")"
end
out = out..string.rep(')', expr.ParenCount or 0)
return out
end
local formatStatement = function(statement)
local out = ""
if statement.AstType == 'AssignmentStatement' then
out = getIndentation()
for i = 1, #statement.Lhs do
out = out..formatExpr(statement.Lhs[i])
if i ~= #statement.Lhs then
out = out..", "
end
end
if #statement.Rhs > 0 then
out = out.." = "
for i = 1, #statement.Rhs do
out = out..formatExpr(statement.Rhs[i])
if i ~= #statement.Rhs then
out = out..", "
end
end
end
elseif statement.AstType == 'CallStatement' then
out = getIndentation() .. formatExpr(statement.Expression)
elseif statement.AstType == 'LocalStatement' then
out = getIndentation() .. out.."local "
for i = 1, #statement.LocalList do
out = out..statement.LocalList[i].Name
if i ~= #statement.LocalList then
out = out..", "
end
end
if #statement.InitList > 0 then
out = out.." = "
for i = 1, #statement.InitList do
out = out..formatExpr(statement.InitList[i])
if i ~= #statement.InitList then
out = out..", "
end
end
end
elseif statement.AstType == 'IfStatement' then
out = getIndentation() .. joinStatementsSafe("if ", formatExpr(statement.Clauses[1].Condition))
out = joinStatementsSafe(out, " then") .. EOL
indent = indent + 1
out = joinStatementsSafe(out, formatStatlist(statement.Clauses[1].Body))
indent = indent - 1
for i = 2, #statement.Clauses do
local st = statement.Clauses[i]
if st.Condition then
out = getIndentation() .. joinStatementsSafe(out, getIndentation() .. "elseif ")
out = joinStatementsSafe(out, formatExpr(st.Condition))
out = joinStatementsSafe(out, " then") .. EOL
else
out = joinStatementsSafe(out, getIndentation() .. "else") .. EOL
end
indent = indent + 1
out = joinStatementsSafe(out, formatStatlist(st.Body))
indent = indent - 1
end
out = joinStatementsSafe(out, getIndentation() .. "end") .. EOL
elseif statement.AstType == 'WhileStatement' then
out = getIndentation() .. joinStatementsSafe("while ", formatExpr(statement.Condition))
out = joinStatementsSafe(out, " do") .. EOL
indent = indent + 1
out = joinStatementsSafe(out, formatStatlist(statement.Body))
indent = indent - 1
out = joinStatementsSafe(out, getIndentation() .. "end") .. EOL
elseif statement.AstType == 'DoStatement' then
out = getIndentation() .. joinStatementsSafe(out, "do") .. EOL
indent = indent + 1
out = joinStatementsSafe(out, formatStatlist(statement.Body))
indent = indent - 1
out = joinStatementsSafe(out, getIndentation() .. "end") .. EOL
elseif statement.AstType == 'ReturnStatement' then
out = getIndentation() .. "return "
for i = 1, #statement.Arguments do
out = joinStatementsSafe(out, formatExpr(statement.Arguments[i]))
if i ~= #statement.Arguments then
out = out..", "
end
end
elseif statement.AstType == 'BreakStatement' then
out = getIndentation() .. "break"
elseif statement.AstType == 'RepeatStatement' then
out = getIndentation() .. "repeat" .. EOL
indent = indent + 1
out = joinStatementsSafe(out, formatStatlist(statement.Body))
indent = indent - 1
out = joinStatementsSafe(out, getIndentation() .. "until ")
out = joinStatementsSafe(out, formatExpr(statement.Condition)) .. EOL
elseif statement.AstType == 'Function' then
if statement.IsLocal then
out = "local "
end
out = joinStatementsSafe(out, "function ")
out = getIndentation() .. out
if statement.IsLocal then
out = out..statement.Name.Name
else
out = out..formatExpr(statement.Name)
end
out = out.."("
if #statement.Arguments > 0 then
for i = 1, #statement.Arguments do
out = out..statement.Arguments[i].Name
if i ~= #statement.Arguments then
out = out..", "
elseif statement.VarArg then
out = out..",..."
end
end
elseif statement.VarArg then
out = out.."..."
end
out = out..")" .. EOL
indent = indent + 1
out = joinStatementsSafe(out, formatStatlist(statement.Body))
indent = indent - 1
out = joinStatementsSafe(out, getIndentation() .. "end") .. EOL
elseif statement.AstType == 'GenericForStatement' then
out = getIndentation() .. "for "
for i = 1, #statement.VariableList do
out = out..statement.VariableList[i].Name
if i ~= #statement.VariableList then
out = out..", "
end
end
out = out.." in "
for i = 1, #statement.Generators do
out = joinStatementsSafe(out, formatExpr(statement.Generators[i]))
if i ~= #statement.Generators then
out = joinStatementsSafe(out, ', ')
end
end
out = joinStatementsSafe(out, " do") .. EOL
indent = indent + 1
out = joinStatementsSafe(out, formatStatlist(statement.Body))
indent = indent - 1
out = joinStatementsSafe(out, getIndentation() .. "end") .. EOL
elseif statement.AstType == 'NumericForStatement' then
out = getIndentation() .. "for "
out = out..statement.Variable.Name.." = "
out = out..formatExpr(statement.Start)..", "..formatExpr(statement.End)
if statement.Step then
out = out..", "..formatExpr(statement.Step)
end
out = joinStatementsSafe(out, " do") .. EOL
indent = indent + 1
out = joinStatementsSafe(out, formatStatlist(statement.Body))
indent = indent - 1
out = joinStatementsSafe(out, getIndentation() .. "end") .. EOL
elseif statement.AstType == 'LabelStatement' then
out = getIndentation() .. "::" .. statement.Label .. "::" .. EOL
elseif statement.AstType == 'GotoStatement' then
out = getIndentation() .. "goto " .. statement.Label .. EOL
elseif statement.AstType == 'Comment' then
if statement.CommentType == 'Shebang' then
out = getIndentation() .. statement.Data
--out = out .. EOL
elseif statement.CommentType == 'Comment' then
out = getIndentation() .. statement.Data
--out = out .. EOL
elseif statement.CommentType == 'LongComment' then
out = getIndentation() .. statement.Data
--out = out .. EOL
end
elseif statement.AstType == 'Eof' then
-- Ignore
else
print("Unknown AST Type: ", statement.AstType)
end
return out
end
formatStatlist = function(statList)
local out = ''
for _, stat in pairs(statList.Body) do
out = joinStatementsSafe(out, formatStatement(stat) .. EOL)
end
return out
end
return formatStatlist(ast)
end
return Format_Beautify

View file

@ -1,440 +0,0 @@
require'strict'
require'ParseLua'
local util = require'Util'
local function debug_printf(...)
--[[
util.printf(...)
--]]
end
--
-- FormatIdentity.lua
--
-- Returns the exact source code that was used to create an AST, preserving all
-- comments and whitespace.
-- This can be used to get back a Lua source after renaming some variables in
-- an AST.
--
local function Format_Identity(ast)
local out = {
rope = {}, -- List of strings
line = 1,
char = 1,
appendStr = function(self, str)
table.insert(self.rope, str)
local lines = util.splitLines(str)
if #lines == 1 then
self.char = self.char + #str
else
self.line = self.line + #lines - 1
local lastLine = lines[#lines]
self.char = #lastLine
end
end,
appendToken = function(self, token)
self:appendWhite(token)
--[*[
--debug_printf("appendToken(%q)", token.Data)
local data = token.Data
local lines = util.splitLines(data)
while self.line + #lines < token.Line do
print("Inserting extra line")
self.str = self.str .. '\n'
self.line = self.line + 1
self.char = 1
end
--]]
self:appendStr(token.Data)
end,
appendTokens = function(self, tokens)
for _,token in ipairs(tokens) do
self:appendToken( token )
end
end,
appendWhite = function(self, token)
if token.LeadingWhite then
self:appendTokens( token.LeadingWhite )
--self.str = self.str .. ' '
end
end
}
local formatStatlist, formatExpr;
formatExpr = function(expr)
local tok_it = 1
local function appendNextToken(str)
local tok = expr.Tokens[tok_it];
if str and tok.Data ~= str then
error("Expected token '" .. str .. "'. Tokens: " .. util.PrintTable(expr.Tokens))
end
out:appendToken( tok )
tok_it = tok_it + 1
end
local function appendToken(token)
out:appendToken( token )
tok_it = tok_it + 1
end
local function appendWhite()
local tok = expr.Tokens[tok_it];
if not tok then error(util.PrintTable(expr)) end
out:appendWhite( tok )
tok_it = tok_it + 1
end
local function appendStr(str)
appendWhite()
out:appendStr(str)
end
local function peek()
if tok_it < #expr.Tokens then
return expr.Tokens[tok_it].Data
end
end
local function appendComma(mandatory, seperators)
if true then
seperators = seperators or { "," }
seperators = util.lookupify( seperators )
if not mandatory and not seperators[peek()] then
return
end
assert(seperators[peek()], "Missing comma or semicolon")
appendNextToken()
else
local p = peek()
if p == "," or p == ";" then
appendNextToken()
end
end
end
debug_printf("formatExpr(%s) at line %i", expr.AstType, expr.Tokens[1] and expr.Tokens[1].Line or -1)
if expr.AstType == 'VarExpr' then
if expr.Variable then
appendStr( expr.Variable.Name )
else
appendStr( expr.Name )
end
elseif expr.AstType == 'NumberExpr' then
appendToken( expr.Value )
elseif expr.AstType == 'StringExpr' then
appendToken( expr.Value )
elseif expr.AstType == 'BooleanExpr' then
appendNextToken( expr.Value and "true" or "false" )
elseif expr.AstType == 'NilExpr' then
appendNextToken( "nil" )
elseif expr.AstType == 'BinopExpr' then
formatExpr(expr.Lhs)
appendStr( expr.Op )
formatExpr(expr.Rhs)
elseif expr.AstType == 'UnopExpr' then
appendStr( expr.Op )
formatExpr(expr.Rhs)
elseif expr.AstType == 'DotsExpr' then
appendNextToken( "..." )
elseif expr.AstType == 'CallExpr' then
formatExpr(expr.Base)
appendNextToken( "(" )
for i,arg in ipairs( expr.Arguments ) do
formatExpr(arg)
appendComma( i ~= #expr.Arguments )
end
appendNextToken( ")" )
elseif expr.AstType == 'TableCallExpr' then
formatExpr( expr.Base )
formatExpr( expr.Arguments[1] )
elseif expr.AstType == 'StringCallExpr' then
formatExpr(expr.Base)
appendToken( expr.Arguments[1] )
elseif expr.AstType == 'IndexExpr' then
formatExpr(expr.Base)
appendNextToken( "[" )
formatExpr(expr.Index)
appendNextToken( "]" )
elseif expr.AstType == 'MemberExpr' then
formatExpr(expr.Base)
appendNextToken() -- . or :
appendToken(expr.Ident)
elseif expr.AstType == 'Function' then
-- anonymous function
appendNextToken( "function" )
appendNextToken( "(" )
if #expr.Arguments > 0 then
for i = 1, #expr.Arguments do
appendStr( expr.Arguments[i].Name )
if i ~= #expr.Arguments then
appendNextToken(",")
elseif expr.VarArg then
appendNextToken(",")
appendNextToken("...")
end
end
elseif expr.VarArg then
appendNextToken("...")
end
appendNextToken(")")
formatStatlist(expr.Body)
appendNextToken("end")
elseif expr.AstType == 'ConstructorExpr' then
appendNextToken( "{" )
for i = 1, #expr.EntryList do
local entry = expr.EntryList[i]
if entry.Type == 'Key' then
appendNextToken( "[" )
formatExpr(entry.Key)
appendNextToken( "]" )
appendNextToken( "=" )
formatExpr(entry.Value)
elseif entry.Type == 'Value' then
formatExpr(entry.Value)
elseif entry.Type == 'KeyString' then
appendStr(entry.Key)
appendNextToken( "=" )
formatExpr(entry.Value)
end
appendComma( i ~= #expr.EntryList, { ",", ";" } )
end
appendNextToken( "}" )
elseif expr.AstType == 'Parentheses' then
appendNextToken( "(" )
formatExpr(expr.Inner)
appendNextToken( ")" )
else
print("Unknown AST Type: ", statement.AstType)
end
assert(tok_it == #expr.Tokens + 1)
debug_printf("/formatExpr")
end
local formatStatement = function(statement)
local tok_it = 1
local function appendNextToken(str)
local tok = statement.Tokens[tok_it];
assert(tok, string.format("Not enough tokens for %q. First token at %i:%i",
str, statement.Tokens[1].Line, statement.Tokens[1].Char))
assert(tok.Data == str,
string.format('Expected token %q, got %q', str, tok.Data))
out:appendToken( tok )
tok_it = tok_it + 1
end
local function appendToken(token)
out:appendToken( str )
tok_it = tok_it + 1
end
local function appendWhite()
local tok = statement.Tokens[tok_it];
out:appendWhite( tok )
tok_it = tok_it + 1
end
local function appendStr(str)
appendWhite()
out:appendStr(str)
end
local function appendComma(mandatory)
if mandatory
or (tok_it < #statement.Tokens and statement.Tokens[tok_it].Data == ",") then
appendNextToken( "," )
end
end
debug_printf("")
debug_printf(string.format("formatStatement(%s) at line %i", statement.AstType, statement.Tokens[1] and statement.Tokens[1].Line or -1))
if statement.AstType == 'AssignmentStatement' then
for i,v in ipairs(statement.Lhs) do
formatExpr(v)
appendComma( i ~= #statement.Lhs )
end
if #statement.Rhs > 0 then
appendNextToken( "=" )
for i,v in ipairs(statement.Rhs) do
formatExpr(v)
appendComma( i ~= #statement.Rhs )
end
end
elseif statement.AstType == 'CallStatement' then
formatExpr(statement.Expression)
elseif statement.AstType == 'LocalStatement' then
appendNextToken( "local" )
for i = 1, #statement.LocalList do
appendStr( statement.LocalList[i].Name )
appendComma( i ~= #statement.LocalList )
end
if #statement.InitList > 0 then
appendNextToken( "=" )
for i = 1, #statement.InitList do
formatExpr(statement.InitList[i])
appendComma( i ~= #statement.InitList )
end
end
elseif statement.AstType == 'IfStatement' then
appendNextToken( "if" )
formatExpr( statement.Clauses[1].Condition )
appendNextToken( "then" )
formatStatlist( statement.Clauses[1].Body )
for i = 2, #statement.Clauses do
local st = statement.Clauses[i]
if st.Condition then
appendNextToken( "elseif" )
formatExpr(st.Condition)
appendNextToken( "then" )
else
appendNextToken( "else" )
end
formatStatlist(st.Body)
end
appendNextToken( "end" )
elseif statement.AstType == 'WhileStatement' then
appendNextToken( "while" )
formatExpr(statement.Condition)
appendNextToken( "do" )
formatStatlist(statement.Body)
appendNextToken( "end" )
elseif statement.AstType == 'DoStatement' then
appendNextToken( "do" )
formatStatlist(statement.Body)
appendNextToken( "end" )
elseif statement.AstType == 'ReturnStatement' then
appendNextToken( "return" )
for i = 1, #statement.Arguments do
formatExpr(statement.Arguments[i])
appendComma( i ~= #statement.Arguments )
end
elseif statement.AstType == 'BreakStatement' then
appendNextToken( "break" )
elseif statement.AstType == 'RepeatStatement' then
appendNextToken( "repeat" )
formatStatlist(statement.Body)
appendNextToken( "until" )
formatExpr(statement.Condition)
elseif statement.AstType == 'Function' then
--print(util.PrintTable(statement))
if statement.IsLocal then
appendNextToken( "local" )
end
appendNextToken( "function" )
if statement.IsLocal then
appendStr(statement.Name.Name)
else
formatExpr(statement.Name)
end
appendNextToken( "(" )
if #statement.Arguments > 0 then
for i = 1, #statement.Arguments do
appendStr( statement.Arguments[i].Name )
appendComma( i ~= #statement.Arguments or statement.VarArg )
if i == #statement.Arguments and statement.VarArg then
appendNextToken( "..." )
end
end
elseif statement.VarArg then
appendNextToken( "..." )
end
appendNextToken( ")" )
formatStatlist(statement.Body)
appendNextToken( "end" )
elseif statement.AstType == 'GenericForStatement' then
appendNextToken( "for" )
for i = 1, #statement.VariableList do
appendStr( statement.VariableList[i].Name )
appendComma( i ~= #statement.VariableList )
end
appendNextToken( "in" )
for i = 1, #statement.Generators do
formatExpr(statement.Generators[i])
appendComma( i ~= #statement.Generators )
end
appendNextToken( "do" )
formatStatlist(statement.Body)
appendNextToken( "end" )
elseif statement.AstType == 'NumericForStatement' then
appendNextToken( "for" )
appendStr( statement.Variable.Name )
appendNextToken( "=" )
formatExpr(statement.Start)
appendNextToken( "," )
formatExpr(statement.End)
if statement.Step then
appendNextToken( "," )
formatExpr(statement.Step)
end
appendNextToken( "do" )
formatStatlist(statement.Body)
appendNextToken( "end" )
elseif statement.AstType == 'LabelStatement' then
appendNextToken( "::" )
appendStr( statement.Label )
appendNextToken( "::" )
elseif statement.AstType == 'GotoStatement' then
appendNextToken( "goto" )
appendStr( statement.Label )
elseif statement.AstType == 'Eof' then
appendWhite()
else
print("Unknown AST Type: ", statement.AstType)
end
if statement.Semicolon then
appendNextToken(";")
end
assert(tok_it == #statement.Tokens + 1)
debug_printf("/formatStatment")
end
formatStatlist = function(statList)
for _, stat in ipairs(statList.Body) do
formatStatement(stat)
end
end
formatStatlist(ast)
return true, table.concat(out.rope)
end
return Format_Identity

View file

@ -1,601 +0,0 @@
--
-- CANDRAN
-- Based on the FormatIdentity.lua of LuaMinify.
-- Modified by Thomas99 to format valid Lua code from Candran AST.
--
-- Modified parts are marked with "-- CANDRAN" comments.
--
--[[
This file is part of LuaMinify by stravant (https://github.com/stravant/LuaMinify).
LICENSE :
The MIT License (MIT)
Copyright (c) 2012-2013
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.
]]
--require'strict' -- CANDRAN : comment, useless here
-- CANDRAN : add Candran syntaxic additions
local candran = require("candran").syntax
require'lib.LuaMinify.ParseCandran'
local util = require'lib.LuaMinify.Util'
local function debug_printf(...)
--[[
util.printf(...)
--]]
end
--
-- FormatIdentity.lua
--
-- Returns the exact source code that was used to create an AST, preserving all
-- comments and whitespace.
-- This can be used to get back a Lua source after renaming some variables in
-- an AST.
--
local function Format_Identity(ast)
local out = {
rope = {}, -- List of strings
line = 1,
char = 1,
appendStr = function(self, str)
table.insert(self.rope, str)
local lines = util.splitLines(str)
if #lines == 1 then
self.char = self.char + #str
else
self.line = self.line + #lines - 1
local lastLine = lines[#lines]
self.char = #lastLine
end
end,
-- CANDRAN : options
appendToken = function(self, token, options)
local options = options or {} -- CANDRAN
self:appendWhite(token, options)
--[*[
--debug_printf("appendToken(%q)", token.Data)
local data = token.Data
local lines = util.splitLines(data)
while self.line + #lines < token.Line do
if not options.no_newline then self:appendStr('\n') end -- CANDRAN : options
self.line = self.line + 1
self.char = 1
end
--]]
if options.no_newline then data = data:gsub("[\n\r]*", "") end -- CANDRAN : options
if options.no_leading_white then data = data:gsub("^%s+", "") end
self:appendStr(data)
end,
-- CANDRAN : options
appendTokens = function(self, tokens, options)
for _,token in ipairs(tokens) do
self:appendToken( token, options ) -- CANDRAN : options
end
end,
-- CANDRAN : options
appendWhite = function(self, token, options)
if token.LeadingWhite then
self:appendTokens( token.LeadingWhite, options ) -- CANDRAN : options
--self.str = self.str .. ' '
end
end
}
local formatStatlist, formatExpr, formatStatement;
-- CANDRAN : added options argument
-- CANDRAN : options = { no_newline = false, no_leading_white = false }
formatExpr = function(expr, options)
local options = options or {} -- CANDRAN
local tok_it = 1
local function appendNextToken(str)
local tok = expr.Tokens[tok_it];
if str and tok.Data ~= str then
error("Expected token '" .. str .. "'. Tokens: " .. util.PrintTable(expr.Tokens))
end
out:appendToken( tok, options ) -- CANDRAN : options
tok_it = tok_it + 1
options.no_leading_white = false -- CANDRAN : not the leading token anymore
end
local function appendToken(token)
out:appendToken( token, options ) -- CANDRAN : options
tok_it = tok_it + 1
options.no_leading_white = false -- CANDRAN : not the leading token anymore
end
local function appendWhite()
local tok = expr.Tokens[tok_it];
if not tok then error(util.PrintTable(expr)) end
out:appendWhite( tok, options ) -- CANDRAN : options
tok_it = tok_it + 1
options.no_leading_white = false -- CANDRAN : not the leading token anymore
end
local function appendStr(str)
appendWhite()
out:appendStr(str)
end
local function peek()
if tok_it < #expr.Tokens then
return expr.Tokens[tok_it].Data
end
end
local function appendComma(mandatory, seperators)
if true then
seperators = seperators or { "," }
seperators = util.lookupify( seperators )
if not mandatory and not seperators[peek()] then
return
end
assert(seperators[peek()], "Missing comma or semicolon")
appendNextToken()
else
local p = peek()
if p == "," or p == ";" then
appendNextToken()
end
end
end
debug_printf("formatExpr(%s) at line %i", expr.AstType, expr.Tokens[1] and expr.Tokens[1].Line or -1)
if expr.AstType == 'VarExpr' then
if expr.Variable then
appendStr( expr.Variable.Name )
else
appendStr( expr.Name )
end
elseif expr.AstType == 'NumberExpr' then
appendToken( expr.Value )
elseif expr.AstType == 'StringExpr' then
appendToken( expr.Value )
elseif expr.AstType == 'BooleanExpr' then
appendNextToken( expr.Value and "true" or "false" )
elseif expr.AstType == 'NilExpr' then
appendNextToken( "nil" )
elseif expr.AstType == 'BinopExpr' then
formatExpr(expr.Lhs)
appendStr( expr.Op )
formatExpr(expr.Rhs)
elseif expr.AstType == 'UnopExpr' then
appendStr( expr.Op )
formatExpr(expr.Rhs)
elseif expr.AstType == 'DotsExpr' then
appendNextToken( "..." )
elseif expr.AstType == 'CallExpr' then
formatExpr(expr.Base)
appendNextToken( "(" )
for i,arg in ipairs( expr.Arguments ) do
formatExpr(arg)
appendComma( i ~= #expr.Arguments )
end
appendNextToken( ")" )
elseif expr.AstType == 'TableCallExpr' then
formatExpr( expr.Base )
formatExpr( expr.Arguments[1] )
elseif expr.AstType == 'StringCallExpr' then
formatExpr(expr.Base)
appendToken( expr.Arguments[1] )
elseif expr.AstType == 'IndexExpr' then
formatExpr(expr.Base)
appendNextToken( "[" )
formatExpr(expr.Index)
appendNextToken( "]" )
elseif expr.AstType == 'MemberExpr' then
formatExpr(expr.Base)
appendNextToken() -- . or :
appendToken(expr.Ident)
elseif expr.AstType == 'Function' then
-- anonymous function
appendNextToken( "function" )
appendNextToken( "(" )
if #expr.Arguments > 0 then
for i = 1, #expr.Arguments do
appendStr( expr.Arguments[i].Name )
if i ~= #expr.Arguments then
appendNextToken(",")
elseif expr.VarArg then
appendNextToken(",")
appendNextToken("...")
end
end
elseif expr.VarArg then
appendNextToken("...")
end
appendNextToken(")")
formatStatlist(expr.Body)
appendNextToken("end")
elseif expr.AstType == 'ConstructorExpr' then
-- CANDRAN : function to get a value with its applied decorators
local function appendValue(entry)
out:appendStr(" ")
if entry.Decorated then
for _,d in ipairs(entry.DecoratorChain) do
formatExpr(d)
out:appendStr("(")
end
end
formatExpr(entry.Value, { no_leading_white = true })
if entry.Decorated then
for _ in ipairs(entry.DecoratorChain) do
out:appendStr(")")
end
end
end
appendNextToken( "{" )
for i = 1, #expr.EntryList do
local entry = expr.EntryList[i]
if entry.Type == 'Key' then
appendNextToken( "[" )
formatExpr(entry.Key)
appendNextToken( "]" )
appendNextToken( "=" )
appendValue(entry) -- CANDRAN : respect decorators
elseif entry.Type == 'Value' then
appendValue(entry) -- CANDRAN : respect decorators
elseif entry.Type == 'KeyString' then
appendStr(entry.Key)
appendNextToken( "=" )
appendValue(entry) -- CANDRAN : respect decorators
end
appendComma( i ~= #expr.EntryList, { ",", ";" } )
end
appendNextToken( "}" )
elseif expr.AstType == 'Parentheses' then
appendNextToken( "(" )
formatExpr(expr.Inner)
appendNextToken( ")" )
else
print("Unknown AST Type: ", statement.AstType)
end
assert(tok_it == #expr.Tokens + 1)
debug_printf("/formatExpr")
end
formatStatement = function(statement)
local tok_it = 1
local function appendNextToken(str)
local tok = statement.Tokens[tok_it];
assert(tok, string.format("Not enough tokens for %q. First token at %i:%i",
str, statement.Tokens[1].Line, statement.Tokens[1].Char))
assert(tok.Data == str,
string.format('Expected token %q, got %q', str, tok.Data))
out:appendToken( tok )
tok_it = tok_it + 1
end
local function appendToken(token)
out:appendToken( str )
tok_it = tok_it + 1
end
local function appendWhite()
local tok = statement.Tokens[tok_it];
out:appendWhite( tok )
tok_it = tok_it + 1
end
local function appendStr(str)
appendWhite()
out:appendStr(str)
end
local function appendComma(mandatory)
if mandatory
or (tok_it < #statement.Tokens and statement.Tokens[tok_it].Data == ",") then
appendNextToken( "," )
end
end
debug_printf("")
debug_printf(string.format("formatStatement(%s) at line %i", statement.AstType, statement.Tokens[1] and statement.Tokens[1].Line or -1))
if statement.AstType == 'AssignmentStatement' then
local newlineToCheck -- CANDRAN : position of a potential newline to eliminate in some edge cases
for i,v in ipairs(statement.Lhs) do
formatExpr(v)
appendComma( i ~= #statement.Lhs )
end
if #statement.Rhs > 0 then
-- CANDRAN : get the assignment operator used (default to =)
local assignmentToken = "="
local candranAssignmentExists = util.lookupify(candran.assignment)
for i,v in pairs(statement.Tokens) do
if candranAssignmentExists[v.Data] then
assignmentToken = v.Data
break
end
end
appendNextToken(assignmentToken) -- CANDRAN : accept Candran assignments operators
--appendNextToken( "=" )
newlineToCheck = #out.rope + 1 -- CANDRAN : the potential newline position afte the =
if assignmentToken == "=" then
for i,v in ipairs(statement.Rhs) do
formatExpr(v)
appendComma( i ~= #statement.Rhs )
end
else
out.rope[#out.rope] = "= " -- CANDRAN : remplace +=, -=, etc. with =
for i,v in ipairs(statement.Rhs) do
if i <= #statement.Lhs then -- CANDRAN : impossible to assign more variables than indicated in Lhs
formatExpr(statement.Lhs[i], { no_newline = true }) -- CANDRAN : write variable to assign
out:appendStr(" "..assignmentToken:gsub("=$","")) -- CANDRAN : assignment operation
formatExpr(v) -- CANDRAN : write variable to add/sub/etc.
if i ~= #statement.Rhs then -- CANDRAN : add comma to allow multi-assignment
appendComma( i ~= #statement.Rhs )
if i >= #statement.Lhs then
out.rope[#out.rope] = "" -- CANDRAN : if this was the last element, remove the comma
end
end
end
end
end
end
-- CANDRAN : eliminate the bad newlines
if out.rope[newlineToCheck] == "\n" then
out.rope[newlineToCheck] = ""
end
elseif statement.AstType == 'CallStatement' then
formatExpr(statement.Expression)
elseif statement.AstType == 'LocalStatement' then
appendNextToken( "local" )
for i = 1, #statement.LocalList do
appendStr( statement.LocalList[i].Name )
appendComma( i ~= #statement.LocalList )
end
if #statement.InitList > 0 then
appendNextToken( "=" )
for i = 1, #statement.InitList do
formatExpr(statement.InitList[i])
appendComma( i ~= #statement.InitList )
end
end
elseif statement.AstType == 'IfStatement' then
appendNextToken( "if" )
formatExpr( statement.Clauses[1].Condition )
appendNextToken( "then" )
formatStatlist( statement.Clauses[1].Body )
for i = 2, #statement.Clauses do
local st = statement.Clauses[i]
if st.Condition then
appendNextToken( "elseif" )
formatExpr(st.Condition)
appendNextToken( "then" )
else
appendNextToken( "else" )
end
formatStatlist(st.Body)
end
appendNextToken( "end" )
elseif statement.AstType == 'WhileStatement' then
appendNextToken( "while" )
formatExpr(statement.Condition)
appendNextToken( "do" )
formatStatlist(statement.Body)
appendNextToken( "end" )
elseif statement.AstType == 'DoStatement' then
appendNextToken( "do" )
formatStatlist(statement.Body)
appendNextToken( "end" )
elseif statement.AstType == 'ReturnStatement' then
appendNextToken( "return" )
for i = 1, #statement.Arguments do
formatExpr(statement.Arguments[i])
appendComma( i ~= #statement.Arguments )
end
elseif statement.AstType == 'BreakStatement' then
appendNextToken( "break" )
elseif statement.AstType == 'RepeatStatement' then
appendNextToken( "repeat" )
formatStatlist(statement.Body)
appendNextToken( "until" )
formatExpr(statement.Condition)
-- CANDRAN : add decorator support (@)
elseif statement.AstType == 'DecoratedStatement' then
-- CANDRAN : list of the chained decorators
local decoratorChain = {statement}
-- CANDRAN : get the decorated statement
local decorated = statement.Decorated
while decorated.AstType == "DecoratedStatement" do
table.insert(decoratorChain, decorated)
decorated = decorated.Decorated
end
-- CANDRAN : write the decorated statement like a normal statement
formatStatement(decorated)
-- CANDRAN : mark the decorator token as used (and add whitespace)
appendNextToken(candran.decorator)
out.rope[#out.rope] = ""
-- CANDRAN : get the variable(s) to decorate name(s)
local names = {}
if decorated.AstType == "Function" then
table.insert(names, decorated.Name.Name)
elseif decorated.AstType == "AssignmentStatement" then
for _,var in ipairs(decorated.Lhs) do
table.insert(names, var.Name)
end
elseif decorated.AstType == "LocalStatement" then
for _,var in ipairs(decorated.LocalList) do
table.insert(names, var.Name)
end
else
error("Invalid statement type to decorate : "..decorated.AstType)
end
-- CANDRAN : redefine the variable(s) ( name, name2, ... = ... )
for i,name in ipairs(names) do
out:appendStr(name)
if i ~= #names then out:appendStr(", ") end
end
out:appendStr(" = ")
for i,name in ipairs(names) do
-- CANDRAN : write the decorator chain ( a(b(c(... )
for _,v in pairs(decoratorChain) do
formatExpr(v.Decorator)
out:appendStr("(")
end
-- CANDRAN : pass the undecorated variable name to the decorator chain
out:appendStr(name)
-- CANDRAN : close parantheses
for _ in pairs(decoratorChain) do
out:appendStr(")")
end
if i ~= #names then out:appendStr(", ") end
end
elseif statement.AstType == 'Function' then
--print(util.PrintTable(statement))
if statement.IsLocal then
appendNextToken( "local" )
end
appendNextToken( "function" )
if statement.IsLocal then
appendStr(statement.Name.Name)
else
formatExpr(statement.Name)
end
appendNextToken( "(" )
if #statement.Arguments > 0 then
for i = 1, #statement.Arguments do
appendStr( statement.Arguments[i].Name )
appendComma( i ~= #statement.Arguments or statement.VarArg )
if i == #statement.Arguments and statement.VarArg then
appendNextToken( "..." )
end
end
elseif statement.VarArg then
appendNextToken( "..." )
end
appendNextToken( ")" )
formatStatlist(statement.Body)
appendNextToken( "end" )
elseif statement.AstType == 'GenericForStatement' then
appendNextToken( "for" )
for i = 1, #statement.VariableList do
appendStr( statement.VariableList[i].Name )
appendComma( i ~= #statement.VariableList )
end
appendNextToken( "in" )
for i = 1, #statement.Generators do
formatExpr(statement.Generators[i])
appendComma( i ~= #statement.Generators )
end
appendNextToken( "do" )
formatStatlist(statement.Body)
appendNextToken( "end" )
elseif statement.AstType == 'NumericForStatement' then
appendNextToken( "for" )
appendStr( statement.Variable.Name )
appendNextToken( "=" )
formatExpr(statement.Start)
appendNextToken( "," )
formatExpr(statement.End)
if statement.Step then
appendNextToken( "," )
formatExpr(statement.Step)
end
appendNextToken( "do" )
formatStatlist(statement.Body)
appendNextToken( "end" )
elseif statement.AstType == 'LabelStatement' then
appendNextToken( "::" )
appendStr( statement.Label )
appendNextToken( "::" )
elseif statement.AstType == 'GotoStatement' then
appendNextToken( "goto" )
appendStr( statement.Label )
elseif statement.AstType == 'Eof' then
appendWhite()
else
print("Unknown AST Type: ", statement.AstType)
end
if statement.Semicolon then
appendNextToken(";")
end
assert(tok_it == #statement.Tokens + 1)
debug_printf("/formatStatment")
end
formatStatlist = function(statList)
for _, stat in ipairs(statList.Body) do
formatStatement(stat)
end
end
formatStatlist(ast)
return true, table.concat(out.rope)
end
return Format_Identity

View file

@ -1,364 +0,0 @@
local parser = require'ParseLua'
local ParseLua = parser.ParseLua
local util = require'Util'
local lookupify = util.lookupify
--
-- FormatMini.lua
--
-- Returns the minified version of an AST. Operations which are performed:
-- - All comments and whitespace are ignored
-- - All local variables are renamed
--
local LowerChars = lookupify{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
local UpperChars = lookupify{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}
local Digits = lookupify{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
local Symbols = lookupify{'+', '-', '*', '/', '^', '%', ',', '{', '}', '[', ']', '(', ')', ';', '#'}
local function Format_Mini(ast)
local formatStatlist, formatExpr;
local count = 0
--
local function joinStatementsSafe(a, b, sep)
--print(a, b)
if count > 150 then
count = 0
return a.."\n"..b
end
sep = sep or ' '
local aa, bb = a:sub(-1,-1), b:sub(1,1)
if UpperChars[aa] or LowerChars[aa] or aa == '_' then
if not (UpperChars[bb] or LowerChars[bb] or bb == '_' or Digits[bb]) then
--bb is a symbol, can join without sep
return a..b
elseif bb == '(' then
print("==============>>>",aa,bb)
--prevent ambiguous syntax
return a..sep..b
else
return a..sep..b
end
elseif Digits[aa] then
if bb == '(' then
--can join statements directly
return a..b
elseif Symbols[bb] then
return a .. b
else
return a..sep..b
end
elseif aa == '' then
return a..b
else
if bb == '(' then
--don't want to accidentally call last statement, can't join directly
return a..sep..b
else
--print("asdf", '"'..a..'"', '"'..b..'"')
return a..b
end
end
end
formatExpr = function(expr, precedence)
local precedence = precedence or 0
local currentPrecedence = 0
local skipParens = false
local out = ""
if expr.AstType == 'VarExpr' then
if expr.Variable then
out = out..expr.Variable.Name
else
out = out..expr.Name
end
elseif expr.AstType == 'NumberExpr' then
out = out..expr.Value.Data
elseif expr.AstType == 'StringExpr' then
out = out..expr.Value.Data
elseif expr.AstType == 'BooleanExpr' then
out = out..tostring(expr.Value)
elseif expr.AstType == 'NilExpr' then
out = joinStatementsSafe(out, "nil")
elseif expr.AstType == 'BinopExpr' then
currentPrecedence = expr.OperatorPrecedence
out = joinStatementsSafe(out, formatExpr(expr.Lhs, currentPrecedence))
out = joinStatementsSafe(out, expr.Op)
out = joinStatementsSafe(out, formatExpr(expr.Rhs))
if expr.Op == '^' or expr.Op == '..' then
currentPrecedence = currentPrecedence - 1
end
if currentPrecedence < precedence then
skipParens = false
else
skipParens = true
end
--print(skipParens, precedence, currentPrecedence)
elseif expr.AstType == 'UnopExpr' then
out = joinStatementsSafe(out, expr.Op)
out = joinStatementsSafe(out, formatExpr(expr.Rhs))
elseif expr.AstType == 'DotsExpr' then
out = out.."..."
elseif expr.AstType == 'CallExpr' then
out = out..formatExpr(expr.Base)
out = out.."("
for i = 1, #expr.Arguments do
out = out..formatExpr(expr.Arguments[i])
if i ~= #expr.Arguments then
out = out..","
end
end
out = out..")"
elseif expr.AstType == 'TableCallExpr' then
out = out..formatExpr(expr.Base)
out = out..formatExpr(expr.Arguments[1])
elseif expr.AstType == 'StringCallExpr' then
out = out..formatExpr(expr.Base)
out = out..expr.Arguments[1].Data
elseif expr.AstType == 'IndexExpr' then
out = out..formatExpr(expr.Base).."["..formatExpr(expr.Index).."]"
elseif expr.AstType == 'MemberExpr' then
out = out..formatExpr(expr.Base)..expr.Indexer..expr.Ident.Data
elseif expr.AstType == 'Function' then
expr.Scope:ObfuscateVariables()
out = out.."function("
if #expr.Arguments > 0 then
for i = 1, #expr.Arguments do
out = out..expr.Arguments[i].Name
if i ~= #expr.Arguments then
out = out..","
elseif expr.VarArg then
out = out..",..."
end
end
elseif expr.VarArg then
out = out.."..."
end
out = out..")"
out = joinStatementsSafe(out, formatStatlist(expr.Body))
out = joinStatementsSafe(out, "end")
elseif expr.AstType == 'ConstructorExpr' then
out = out.."{"
for i = 1, #expr.EntryList do
local entry = expr.EntryList[i]
if entry.Type == 'Key' then
out = out.."["..formatExpr(entry.Key).."]="..formatExpr(entry.Value)
elseif entry.Type == 'Value' then
out = out..formatExpr(entry.Value)
elseif entry.Type == 'KeyString' then
out = out..entry.Key.."="..formatExpr(entry.Value)
end
if i ~= #expr.EntryList then
out = out..","
end
end
out = out.."}"
elseif expr.AstType == 'Parentheses' then
out = out.."("..formatExpr(expr.Inner)..")"
end
--print(">>", skipParens, expr.ParenCount, out)
if not skipParens then
--print("hehe")
out = string.rep('(', expr.ParenCount or 0) .. out
out = out .. string.rep(')', expr.ParenCount or 0)
--print("", out)
end
count = count + #out
return --[[print(out) or]] out
end
local formatStatement = function(statement)
local out = ''
if statement.AstType == 'AssignmentStatement' then
for i = 1, #statement.Lhs do
out = out..formatExpr(statement.Lhs[i])
if i ~= #statement.Lhs then
out = out..","
end
end
if #statement.Rhs > 0 then
out = out.."="
for i = 1, #statement.Rhs do
out = out..formatExpr(statement.Rhs[i])
if i ~= #statement.Rhs then
out = out..","
end
end
end
elseif statement.AstType == 'CallStatement' then
out = formatExpr(statement.Expression)
elseif statement.AstType == 'LocalStatement' then
out = out.."local "
for i = 1, #statement.LocalList do
out = out..statement.LocalList[i].Name
if i ~= #statement.LocalList then
out = out..","
end
end
if #statement.InitList > 0 then
out = out.."="
for i = 1, #statement.InitList do
out = out..formatExpr(statement.InitList[i])
if i ~= #statement.InitList then
out = out..","
end
end
end
elseif statement.AstType == 'IfStatement' then
out = joinStatementsSafe("if", formatExpr(statement.Clauses[1].Condition))
out = joinStatementsSafe(out, "then")
out = joinStatementsSafe(out, formatStatlist(statement.Clauses[1].Body))
for i = 2, #statement.Clauses do
local st = statement.Clauses[i]
if st.Condition then
out = joinStatementsSafe(out, "elseif")
out = joinStatementsSafe(out, formatExpr(st.Condition))
out = joinStatementsSafe(out, "then")
else
out = joinStatementsSafe(out, "else")
end
out = joinStatementsSafe(out, formatStatlist(st.Body))
end
out = joinStatementsSafe(out, "end")
elseif statement.AstType == 'WhileStatement' then
out = joinStatementsSafe("while", formatExpr(statement.Condition))
out = joinStatementsSafe(out, "do")
out = joinStatementsSafe(out, formatStatlist(statement.Body))
out = joinStatementsSafe(out, "end")
elseif statement.AstType == 'DoStatement' then
out = joinStatementsSafe(out, "do")
out = joinStatementsSafe(out, formatStatlist(statement.Body))
out = joinStatementsSafe(out, "end")
elseif statement.AstType == 'ReturnStatement' then
out = "return"
for i = 1, #statement.Arguments do
out = joinStatementsSafe(out, formatExpr(statement.Arguments[i]))
if i ~= #statement.Arguments then
out = out..","
end
end
elseif statement.AstType == 'BreakStatement' then
out = "break"
elseif statement.AstType == 'RepeatStatement' then
out = "repeat"
out = joinStatementsSafe(out, formatStatlist(statement.Body))
out = joinStatementsSafe(out, "until")
out = joinStatementsSafe(out, formatExpr(statement.Condition))
elseif statement.AstType == 'Function' then
statement.Scope:ObfuscateVariables()
if statement.IsLocal then
out = "local"
end
out = joinStatementsSafe(out, "function ")
if statement.IsLocal then
out = out..statement.Name.Name
else
out = out..formatExpr(statement.Name)
end
out = out.."("
if #statement.Arguments > 0 then
for i = 1, #statement.Arguments do
out = out..statement.Arguments[i].Name
if i ~= #statement.Arguments then
out = out..","
elseif statement.VarArg then
--print("Apply vararg")
out = out..",..."
end
end
elseif statement.VarArg then
out = out.."..."
end
out = out..")"
out = joinStatementsSafe(out, formatStatlist(statement.Body))
out = joinStatementsSafe(out, "end")
elseif statement.AstType == 'GenericForStatement' then
statement.Scope:ObfuscateVariables()
out = "for "
for i = 1, #statement.VariableList do
out = out..statement.VariableList[i].Name
if i ~= #statement.VariableList then
out = out..","
end
end
out = out.." in"
for i = 1, #statement.Generators do
out = joinStatementsSafe(out, formatExpr(statement.Generators[i]))
if i ~= #statement.Generators then
out = joinStatementsSafe(out, ',')
end
end
out = joinStatementsSafe(out, "do")
out = joinStatementsSafe(out, formatStatlist(statement.Body))
out = joinStatementsSafe(out, "end")
elseif statement.AstType == 'NumericForStatement' then
out = "for "
out = out..statement.Variable.Name.."="
out = out..formatExpr(statement.Start)..","..formatExpr(statement.End)
if statement.Step then
out = out..","..formatExpr(statement.Step)
end
out = joinStatementsSafe(out, "do")
out = joinStatementsSafe(out, formatStatlist(statement.Body))
out = joinStatementsSafe(out, "end")
elseif statement.AstType == 'LabelStatement' then
out = getIndentation() .. "::" .. statement.Label .. "::"
elseif statement.AstType == 'GotoStatement' then
out = getIndentation() .. "goto " .. statement.Label
elseif statement.AstType == 'Comment' then
-- ignore
elseif statement.AstType == 'Eof' then
-- ignore
else
print("Unknown AST Type: " .. statement.AstType)
end
count = count + #out
return out
end
formatStatlist = function(statList)
local out = ''
statList.Scope:ObfuscateVariables()
for _, stat in pairs(statList.Body) do
out = joinStatementsSafe(out, formatStatement(stat), ';')
end
return out
end
ast.Scope:ObfuscateVariables()
return formatStatlist(ast)
end
return Format_Mini

View file

@ -1,2 +0,0 @@
@echo off
lua CommandLineMinify.lua %*

View file

@ -1,2 +0,0 @@
#!/bin/bash
lua CommandLineMinify.lua $@

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,44 +0,0 @@
Lua Parsing and Refactorization tools
=========
A collection of tools for working with Lua source code. Primarily a Lua source code minifier, but also includes some static analysis tools and a general Lua lexer and parser.
Currently the minifier performs:
- Stripping of all comments and whitespace
- True semantic renaming of all local variables to a reduced form
- Reduces the source to the minimal spacing, spaces are only inserted where actually needed.
LuaMinify Command Line Utility Usage
------------------------------------
The `LuaMinify` shell and batch files are given as shortcuts to running a command line instance of the minifier with the following usage:
LuaMinify sourcefile [destfile]
Which will minify to a given destination file, or to a copy of the source file with _min appended to the filename if no output file is given.
LuaMinify Roblox Plugin Usage
-----------------------------
First, download the source code, which you can do by hitting this button:
![Click That](http://github.com/stravant/LuaMinify/raw/master/RobloxPluginInstructions.png)
Then copy the `RobloxPlugin` folder from the source into your Roblox Plugins directory, which can be found by hitting `Tools->Open Plugins Folder` in Roblox Studio.
Features/Todo
-------------
Features:
- Lua scanner/parser, which generates a full AST
- Lua reconstructor
- minimal
- full reconstruction (TODO: options, comments)
- TODO: exact reconstructor
- support for embedded long strings/comments e.g. [[abc [[ def ]] ghi]]
Todo:
- use table.concat instead of appends in the reconstructors

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 B

View file

@ -1,93 +0,0 @@
--
-- MinifyToolbar.lua
--
-- The main script that generates a toolbar for studio that allows minification of selected
-- scripts, calling on the _G.Minify function defined in `Minify.lua`
--
local plugin = PluginManager():CreatePlugin()
local toolbar = plugin:CreateToolbar("Minify")
local minifyButton = toolbar:CreateButton("", "Minify Selected Script", 'MinifyButtonIcon.png')
local toggleReplaceButton = toolbar:CreateButton("Replace", "If enabled, selected script will be REPLACED "..
"with a minified version",
'ReplaceButtonIcon.png')
local replace = false
toggleReplaceButton.Click:connect(function()
replace = not replace
toggleReplaceButton:SetActive(replace)
end)
minifyButton.Click:connect(function()
for _, o in pairs(game.Selection:Get()) do
if o:IsA('Script') then
--can't read linkedsource, bail out
if o.LinkedSource ~= '' then
Spawn(function()
error("Minify Plugin: Cannot Minify a script with a LinkedSource", 0)
end)
return
end
--see if it has been minified
if o.Name:sub(-4,-1) == '_Min' then
local original = o:FindFirstChild(o.Name:sub(1,-5))
if original then
local st, min = _G.Minify(original.Source)
if st then
game:GetService("ChangeHistoryService"):SetWaypoint("Minify `"..original.Name.."`")
if replace then
o.Source = min
original:Destroy()
else
o.Source = min
end
else
Spawn(function()
error("Minify Plugin: "..min, 0)
end)
return
end
else
if replace then
local st, min = _G.Minify(o.Source)
if st then
game:GetService("ChangeHistoryService"):SetWaypoint("Minify `"..original.Name.."`")
o.Source = min
else
Spawn(function()
error("Minify Plugin: "..min, 0)
end)
return
end
else
Spawn(function()
error("Minify Plugin: Missing original script `"..o.Name:sub(1,-5).."`", 0)
end)
end
end
else
local st, min = _G.Minify(o.Source)
if st then
game:GetService("ChangeHistoryService"):SetWaypoint("Minify `"..o.Name.."`")
if replace then
o.Source = min
o.Name = o.Name.."_Min"
else
local original = o:Clone()
original.Parent = o
original.Disabled = true
o.Name = o.Name.."_Min"
o.Source = min
end
else
Spawn(function()
error("Minify Plugin: "..min, 0)
end)
return
end
end
end
end
end)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,221 +0,0 @@
--[[
This file is part of LuaMinify by stravant (https://github.com/stravant/LuaMinify).
LICENSE :
The MIT License (MIT)
Copyright (c) 2012-2013
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.
]]
local Scope = {
new = function(self, parent)
local s = {
Parent = parent,
Locals = { },
Globals = { },
oldLocalNamesMap = { },
oldGlobalNamesMap = { },
Children = { },
}
if parent then
table.insert(parent.Children, s)
end
return setmetatable(s, { __index = self })
end,
AddLocal = function(self, v)
table.insert(self.Locals, v)
end,
AddGlobal = function(self, v)
table.insert(self.Globals, v)
end,
CreateLocal = function(self, name)
local v
v = self:GetLocal(name)
if v then return v end
v = { }
v.Scope = self
v.Name = name
v.IsGlobal = false
v.CanRename = true
v.References = 1
self:AddLocal(v)
return v
end,
GetLocal = function(self, name)
for k, var in pairs(self.Locals) do
if var.Name == name then return var end
end
if self.Parent then
return self.Parent:GetLocal(name)
end
end,
GetOldLocal = function(self, name)
if self.oldLocalNamesMap[name] then
return self.oldLocalNamesMap[name]
end
return self:GetLocal(name)
end,
mapLocal = function(self, name, var)
self.oldLocalNamesMap[name] = var
end,
GetOldGlobal = function(self, name)
if self.oldGlobalNamesMap[name] then
return self.oldGlobalNamesMap[name]
end
return self:GetGlobal(name)
end,
mapGlobal = function(self, name, var)
self.oldGlobalNamesMap[name] = var
end,
GetOldVariable = function(self, name)
return self:GetOldLocal(name) or self:GetOldGlobal(name)
end,
RenameLocal = function(self, oldName, newName)
oldName = type(oldName) == 'string' and oldName or oldName.Name
local found = false
local var = self:GetLocal(oldName)
if var then
var.Name = newName
self:mapLocal(oldName, var)
found = true
end
if not found and self.Parent then
self.Parent:RenameLocal(oldName, newName)
end
end,
RenameGlobal = function(self, oldName, newName)
oldName = type(oldName) == 'string' and oldName or oldName.Name
local found = false
local var = self:GetGlobal(oldName)
if var then
var.Name = newName
self:mapGlobal(oldName, var)
found = true
end
if not found and self.Parent then
self.Parent:RenameGlobal(oldName, newName)
end
end,
RenameVariable = function(self, oldName, newName)
oldName = type(oldName) == 'string' and oldName or oldName.Name
if self:GetLocal(oldName) then
self:RenameLocal(oldName, newName)
else
self:RenameGlobal(oldName, newName)
end
end,
GetAllVariables = function(self)
local ret = self:getVars(true) -- down
for k, v in pairs(self:getVars(false)) do -- up
table.insert(ret, v)
end
return ret
end,
getVars = function(self, top)
local ret = { }
if top then
for k, v in pairs(self.Children) do
for k2, v2 in pairs(v:getVars(true)) do
table.insert(ret, v2)
end
end
else
for k, v in pairs(self.Locals) do
table.insert(ret, v)
end
for k, v in pairs(self.Globals) do
table.insert(ret, v)
end
if self.Parent then
for k, v in pairs(self.Parent:getVars(false)) do
table.insert(ret, v)
end
end
end
return ret
end,
CreateGlobal = function(self, name)
local v
v = self:GetGlobal(name)
if v then return v end
v = { }
v.Scope = self
v.Name = name
v.IsGlobal = true
v.CanRename = true
v.References = 1
self:AddGlobal(v)
return v
end,
GetGlobal = function(self, name)
for k, v in pairs(self.Globals) do
if v.Name == name then return v end
end
if self.Parent then
return self.Parent:GetGlobal(name)
end
end,
GetVariable = function(self, name)
return self:GetLocal(name) or self:GetGlobal(name)
end,
ObfuscateLocals = function(self, recommendedMaxLength, validNameChars)
recommendedMaxLength = recommendedMaxLength or 7
local chars = validNameChars or "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuioplkjhgfdsazxcvbnm_"
local chars2 = validNameChars or "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuioplkjhgfdsazxcvbnm_1234567890"
for _, var in pairs(self.Locals) do
local id = ""
local tries = 0
repeat
local n = math.random(1, #chars)
id = id .. chars:sub(n, n)
for i = 1, math.random(0, tries > 5 and 30 or recommendedMaxLength) do
local n = math.random(1, #chars2)
id = id .. chars2:sub(n, n)
end
tries = tries + 1
until not self:GetVariable(id)
self:RenameLocal(var.Name, id)
end
end,
}
return Scope

View file

@ -1,116 +0,0 @@
--[[
This file is part of LuaMinify by stravant (https://github.com/stravant/LuaMinify).
LICENSE :
The MIT License (MIT)
Copyright (c) 2012-2013
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.
]]
--
-- Util.lua
--
-- Provides some common utilities shared throughout the project.
--
local function lookupify(tb)
for _, v in pairs(tb) do
tb[v] = true
end
return tb
end
local function CountTable(tb)
local c = 0
for _ in pairs(tb) do c = c + 1 end
return c
end
local function PrintTable(tb, atIndent)
if tb.Print then
return tb.Print()
end
atIndent = atIndent or 0
local useNewlines = (CountTable(tb) > 1)
local baseIndent = string.rep(' ', atIndent+1)
local out = "{"..(useNewlines and '\n' or '')
for k, v in pairs(tb) do
if type(v) ~= 'function' then
--do
out = out..(useNewlines and baseIndent or '')
if type(k) == 'number' then
--nothing to do
elseif type(k) == 'string' and k:match("^[A-Za-z_][A-Za-z0-9_]*$") then
out = out..k.." = "
elseif type(k) == 'string' then
out = out.."[\""..k.."\"] = "
else
out = out.."["..tostring(k).."] = "
end
if type(v) == 'string' then
out = out.."\""..v.."\""
elseif type(v) == 'number' then
out = out..v
elseif type(v) == 'table' then
out = out..PrintTable(v, atIndent+(useNewlines and 1 or 0))
else
out = out..tostring(v)
end
if next(tb, k) then
out = out..","
end
if useNewlines then
out = out..'\n'
end
end
end
out = out..(useNewlines and string.rep(' ', atIndent) or '').."}"
return out
end
local function splitLines(str)
if str:match("\n") then
local lines = {}
for line in str:gmatch("[^\n]*") do
table.insert(lines, line)
end
assert(#lines > 0)
return lines
else
return { str }
end
end
local function printf(fmt, ...)
return print(string.format(fmt, ...))
end
return {
PrintTable = PrintTable,
CountTable = CountTable,
lookupify = lookupify,
splitLines = splitLines,
printf = printf,
}

View file

@ -1,39 +0,0 @@
-- From http://metalua.luaforge.net/src/lib/strict.lua.html
--
-- strict.lua
-- checks uses of undeclared global variables
-- All global variables must be 'declared' through a regular assignment
-- (even assigning nil will do) in a main chunk before being used
-- anywhere or assigned to inside a function.
--
local mt = getmetatable(_G)
if mt == nil then
mt = {}
setmetatable(_G, mt)
end
__STRICT = true
mt.__declared = {}
mt.__newindex = function (t, n, v)
if __STRICT and not mt.__declared[n] then
local w = debug.getinfo(2, "S").what
if w ~= "main" and w ~= "C" then
error("assign to undeclared variable '"..n.."'", 2)
end
mt.__declared[n] = true
end
rawset(t, n, v)
end
mt.__index = function (t, n)
if not mt.__declared[n] and debug.getinfo(2, "S").what ~= "C" then
error("variable '"..n.."' is not declared", 2)
end
return rawget(t, n)
end
function global(...)
for _, v in ipairs{...} do mt.__declared[v] = true end
end

View file

@ -1,60 +0,0 @@
-- Adapted from Yueliang
package.path = "../?.lua;" .. package.path
local util = require'Util'
local Parser = require'ParseLua'
local Format = require'FormatBeautiful'
for w in io.lines("test_lines.txt") do
--print(w)
local success, ast = Parser.ParseLua(w)
if w:find("FAIL") then
--[[if success then
print("ERROR PARSING LINE:")
print("Should fail: true. Did fail: " .. tostring(not success))
print("Line: " .. w)
else
--print("Suceeded!")
end]]
else
if not success then
print("ERROR PARSING LINE:")
print("Should fail: false. Did fail: " .. tostring(not success))
print("Line: " .. w)
else
success, ast = Format(ast)
--print(success, ast)
if not success then
print("ERROR BEAUTIFYING LINE:")
print("Message: " .. ast)
print("Line: " .. w)
end
local success_ = success
success, ast = loadstring(success)
if not success then
print("ERROR PARSING BEAUTIFIED LINE:")
print("Message: " .. ast)
print("Line: " .. success_)
end
--print("Suceeded!")
end
end
end
print"Done!"
os.remove("tmp")
--[[
function readAll(file)
local f = io.open(file, "rb")
local content = f:read("*all")
f:close()
return content
end
local text = readAll('../ParseLua.lua')
local success, ast = Parser.ParseLua(text)
local nice
nice = Format(ast)
print(nice)
--]]

View file

@ -1,124 +0,0 @@
package.path = "../?.lua;" .. package.path
local Parser = require'ParseLua'
local util = require'Util'
local FormatIdentity = require'FormatIdentity'
local FormatMini = require'FormatMini'
local FormatBeautiful = require'FormatBeautiful'
require'strict'
function readAll(file)
local f = io.open(file, "rb")
local content = f:read("*all")
f:close()
return content
end
local g_lexTime = 0
local g_parseTime = 0
local g_reconstructTime = 0
function reconstructText(text)
local preLex = os.clock()
local success, tokens, ast, reconstructed
success, tokens = Parser.LexLua(text)
if not success then
print("ERROR: " .. tokens)
return
end
local preParse = os.clock()
success, ast = Parser.ParseLua(tokens)
if not success then
print("ERROR: " .. ast)
return
end
local preReconstruct = os.clock()
local DO_MINI = false
local DO_CHECK = false
if DO_MINI then
success, reconstructed = FormatMini(ast)
else
success, reconstructed = FormatIdentity(ast)
end
if not success then
print("ERROR: " .. reconstructed)
return
end
local post = os.clock()
g_lexTime = g_lexTime + (preParse - preLex)
g_parseTime = g_parseTime + (preReconstruct - preParse)
g_reconstructTime = g_reconstructTime + (post - preReconstruct)
if DO_CHECK then
--[[
print()
print("Reconstructed: ")
print("--------------------")
print(reconstructed)
print("--------------------")
print("Done. ")
--]]
if reconstructed == text then
--print("Reconstruction succeeded")
else
print("Reconstruction failed")
local inputLines = util.splitLines(text)
local outputLines = util.splitLines(reconstructed)
local n = math.max(#inputLines, #outputLines)
for i = 1,n do
if inputLines[i] ~= outputLines[i] then
util.printf("ERROR on line %i", i)
util.printf("Input: %q", inputLines[i])
util.printf("Output: %q", outputLines[i])
break
end
end
end
end
end
--[*[
local files = {
"../ParseLua.lua",
"../FormatIdentity.lua",
"../Scope.lua",
"../strict.lua",
"../Type.lua",
"Test_identity.lua"
}
for _,path in ipairs(files) do
print(path)
local text = readAll(path)
reconstructText(text)
end
--]]
print("test_lines.txt")
local line_nr = 0
for text in io.lines("test_lines.txt") do
line_nr = line_nr + 1
if not text:find("FAIL") then
--util.printf("\nText: %q", text)
reconstructText(text)
end
end
reconstructText('function a(p,q,r,...) end')
util.printf("Lex time: %f s", g_lexTime)
util.printf("Parse time: %f s", g_parseTime)
util.printf("Format time: %f s", g_reconstructTime)

View file

@ -1,523 +0,0 @@
; -- FAIL
local -- FAIL
local; -- FAIL
local = -- FAIL
local end -- FAIL
local a
local a;
local a, b, c
local a; local b local c;
local a = 1
local a local b = a
local a, b = 1, 2
local a, b, c = 1, 2, 3
local a, b, c = 1
local a = 1, 2, 3
local a, local -- FAIL
local 1 -- FAIL
local "foo" -- FAIL
local a = local -- FAIL
local a, b, = -- FAIL
local a, b = 1, local -- FAIL
local a, b = , local -- FAIL
do -- FAIL
end -- FAIL
do end
do ; end -- FAIL
do 1 end -- FAIL
do "foo" end -- FAIL
do local a, b end
do local a local b end
do local a; local b; end
do local a = 1 end
do do end end
do do end; end
do do do end end end
do do do end; end; end
do do do return end end end
do end do -- FAIL
do end end -- FAIL
do return end
do return return end -- FAIL
do break end -- FAIL
while -- FAIL
while do -- FAIL
while = -- FAIL
while 1 do -- FAIL
while 1 do end
while 1 do local a end
while 1 do local a local b end
while 1 do local a; local b; end
while 1 do 2 end -- FAIL
while 1 do "foo" end -- FAIL
while true do end
while 1 do ; end -- FAIL
while 1 do while -- FAIL
while 1 end -- FAIL
while 1 2 do -- FAIL
while 1 = 2 do -- FAIL
while 1 do return end
while 1 do return return end -- FAIL
while 1 do do end end
while 1 do do return end end
while 1 do break end
while 1 do break break end -- FAIL
while 1 do do break end end
repeat -- FAIL
repeat until -- FAIL
repeat until 0
repeat until false
repeat until local -- FAIL
repeat end -- FAIL
repeat 1 -- FAIL
repeat = -- FAIL
repeat local a until 1
repeat local a local b until 0
repeat local a; local b; until 0
repeat ; until 1 -- FAIL
repeat 2 until 1 -- FAIL
repeat "foo" until 1 -- FAIL
repeat return until 0
repeat return return until 0 -- FAIL
repeat break until 0
repeat break break until 0 -- FAIL
repeat do end until 0
repeat do return end until 0
repeat do break end until 0
for -- FAIL
for do -- FAIL
for end -- FAIL
for 1 -- FAIL
for a -- FAIL
for true -- FAIL
for a, in -- FAIL
for a in -- FAIL
for a do -- FAIL
for a in do -- FAIL
for a in b do -- FAIL
for a in b end -- FAIL
for a in b, do -- FAIL
for a in b do end
for a in b do local a local b end
for a in b do local a; local b; end
for a in b do 1 end -- FAIL
for a in b do "foo" end -- FAIL
for a b in -- FAIL
for a, b, c in p do end
for a, b, c in p, q, r do end
for a in 1 do end
for a in true do end
for a in "foo" do end
for a in b do break end
for a in b do break break end -- FAIL
for a in b do return end
for a in b do return return end -- FAIL
for a in b do do end end
for a in b do do break end end
for a in b do do return end end
for = -- FAIL
for a = -- FAIL
for a, b = -- FAIL
for a = do -- FAIL
for a = 1, do -- FAIL
for a = p, q, do -- FAIL
for a = p q do -- FAIL
for a = b do end -- FAIL
for a = 1, 2, 3, 4 do end -- FAIL
for a = p, q do end
for a = 1, 2 do end
for a = 1, 2 do local a local b end
for a = 1, 2 do local a; local b; end
for a = 1, 2 do 3 end -- FAIL
for a = 1, 2 do "foo" end -- FAIL
for a = p, q, r do end
for a = 1, 2, 3 do end
for a = p, q do break end
for a = p, q do break break end -- FAIL
for a = 1, 2 do return end
for a = 1, 2 do return return end -- FAIL
for a = p, q do do end end
for a = p, q do do break end end
for a = p, q do do return end end
break -- FAIL
return
return;
return return -- FAIL
return 1
return local -- FAIL
return "foo"
return 1, -- FAIL
return 1,2,3
return a,b,c,d
return 1,2;
return ...
return 1,a,...
if -- FAIL
elseif -- FAIL
else -- FAIL
then -- FAIL
if then -- FAIL
if 1 -- FAIL
if 1 then -- FAIL
if 1 else -- FAIL
if 1 then else -- FAIL
if 1 then elseif -- FAIL
if 1 then end
if 1 then local a end
if 1 then local a local b end
if 1 then local a; local b; end
if 1 then else end
if 1 then local a else local b end
if 1 then local a; else local b; end
if 1 then elseif 2 -- FAIL
if 1 then elseif 2 then -- FAIL
if 1 then elseif 2 then end
if 1 then local a elseif 2 then local b end
if 1 then local a; elseif 2 then local b; end
if 1 then elseif 2 then else end
if 1 then else if 2 then end end
if 1 then else if 2 then end -- FAIL
if 1 then break end -- FAIL
if 1 then return end
if 1 then return return end -- FAIL
if 1 then end; if 1 then end;
function -- FAIL
function 1 -- FAIL
function end -- FAIL
function a -- FAIL
function a end -- FAIL
function a( end -- FAIL
function a() end
function a(1 -- FAIL
function a("foo" -- FAIL
function a(p -- FAIL
function a(p,) -- FAIL
function a(p q -- FAIL
function a(p) end
function a(p,q,) end -- FAIL
function a(p,q,r) end
function a(p,q,1 -- FAIL
function a(p) do -- FAIL
function a(p) 1 end -- FAIL
function a(p) return end
function a(p) break end -- FAIL
function a(p) return return end -- FAIL
function a(p) do end end
function a.( -- FAIL
function a.1 -- FAIL
function a.b() end
function a.b, -- FAIL
function a.b.( -- FAIL
function a.b.c.d() end
function a: -- FAIL
function a:1 -- FAIL
function a:b() end
function a:b: -- FAIL
function a:b. -- FAIL
function a.b.c:d() end
function a(...) end
function a(..., -- FAIL
function a(p,...) end
function a(p,q,r,...) end
function a() local a local b end
function a() local a; local b; end
function a() end; function a() end;
local function -- FAIL
local function 1 -- FAIL
local function end -- FAIL
local function a -- FAIL
local function a end -- FAIL
local function a( end -- FAIL
local function a() end
local function a(1 -- FAIL
local function a("foo" -- FAIL
local function a(p -- FAIL
local function a(p,) -- FAIL
local function a(p q -- FAIL
local function a(p) end
local function a(p,q,) end -- FAIL
local function a(p,q,r) end
local function a(p,q,1 -- FAIL
local function a(p) do -- FAIL
local function a(p) 1 end -- FAIL
local function a(p) return end
local function a(p) break end -- FAIL
local function a(p) return return end -- FAIL
local function a(p) do end end
local function a. -- FAIL
local function a: -- FAIL
local function a(...) end
local function a(..., -- FAIL
local function a(p,...) end
local function a(p,q,r,...) end
local function a() local a local b end
local function a() local a; local b; end
local function a() end; local function a() end;
a -- FAIL
a, -- FAIL
a,b,c -- FAIL
a,b = -- FAIL
a = 1
a = 1,2,3
a,b,c = 1
a,b,c = 1,2,3
a.b = 1
a.b.c = 1
a[b] = 1
a[b][c] = 1
a.b[c] = 1
a[b].c = 1
0 = -- FAIL
"foo" = -- FAIL
true = -- FAIL
(a) = -- FAIL
{} = -- FAIL
a:b() = -- FAIL
a() = -- FAIL
a.b:c() = -- FAIL
a[b]() = -- FAIL
a = a b -- FAIL
a = 1 2 -- FAIL
a = a = 1 -- FAIL
a( -- FAIL
a()
a(1)
a(1,) -- FAIL
a(1,2,3)
1() -- FAIL
a()()
a.b()
a[b]()
a.1 -- FAIL
a.b -- FAIL
a[b] -- FAIL
a.b.( -- FAIL
a.b.c()
a[b][c]()
a[b].c()
a.b[c]()
a:b()
a:b -- FAIL
a:1 -- FAIL
a.b:c()
a[b]:c()
a:b: -- FAIL
a:b():c()
a:b().c[d]:e()
a:b()[c].d:e()
(a)()
()() -- FAIL
(1)()
("foo")()
(true)()
(a)()()
(a.b)()
(a[b])()
(a).b()
(a)[b]()
(a):b()
(a).b[c]:d()
(a)[b].c:d()
(a):b():c()
(a):b().c[d]:e()
(a):b()[c].d:e()
a"foo"
a[[foo]]
a.b"foo"
a[b]"foo"
a:b"foo"
a{}
a.b{}
a[b]{}
a:b{}
a()"foo"
a"foo"()
a"foo".b()
a"foo"[b]()
a"foo":c()
a"foo""bar"
a"foo"{}
(a):b"foo".c[d]:e"bar"
(a):b"foo"[c].d:e"bar"
a(){}
a{}()
a{}.b()
a{}[b]()
a{}:c()
a{}"foo"
a{}{}
(a):b{}.c[d]:e{}
(a):b{}[c].d:e{}
a = -- FAIL
a = a
a = nil
a = false
a = 1
a = "foo"
a = [[foo]]
a = {}
a = (a)
a = (nil)
a = (true)
a = (1)
a = ("foo")
a = ([[foo]])
a = ({})
a = a.b
a = a.b. -- FAIL
a = a.b.c
a = a:b -- FAIL
a = a[b]
a = a[1]
a = a["foo"]
a = a[b][c]
a = a.b[c]
a = a[b].c
a = (a)[b]
a = (a).c
a = () -- FAIL
a = a()
a = a.b()
a = a[b]()
a = a:b()
a = (a)()
a = (a).b()
a = (a)[b]()
a = (a):b()
a = a"foo"
a = a{}
a = function -- FAIL
a = function 1 -- FAIL
a = function a -- FAIL
a = function end -- FAIL
a = function( -- FAIL
a = function() end
a = function(1 -- FAIL
a = function(p) end
a = function(p,) -- FAIL
a = function(p q -- FAIL
a = function(p,q,r) end
a = function(p,q,1 -- FAIL
a = function(...) end
a = function(..., -- FAIL
a = function(p,...) end
a = function(p,q,r,...) end
a = ...
a = a, b, ...
a = (...)
a = ..., 1, 2
a = function() return ... end -- FAIL
a = -10
a = -"foo"
a = -a
a = -nil
a = -true
a = -{}
a = -function() end
a = -a()
a = -(a)
a = - -- FAIL
a = not 10
a = not "foo"
a = not a
a = not nil
a = not true
a = not {}
a = not function() end
a = not a()
a = not (a)
a = not -- FAIL
a = #10
a = #"foo"
a = #a
a = #nil
a = #true
a = #{}
a = #function() end
a = #a()
a = #(a)
a = # -- FAIL
a = 1 + 2; a = 1 - 2
a = 1 * 2; a = 1 / 2
a = 1 ^ 2; a = 1 % 2
a = 1 .. 2
a = 1 + -- FAIL
a = 1 .. -- FAIL
a = 1 * / -- FAIL
a = 1 + -2; a = 1 - -2
a = 1 * - -- FAIL
a = 1 * not 2; a = 1 / not 2
a = 1 / not -- FAIL
a = 1 * #"foo"; a = 1 / #"foo"
a = 1 / # -- FAIL
a = 1 + 2 - 3 * 4 / 5 % 6 ^ 7
a = ((1 + 2) - 3) * (4 / (5 % 6 ^ 7))
a = (1 + (2 - (3 * (4 / (5 % 6 ^ ((7)))))))
a = ((1 -- FAIL
a = ((1 + 2) -- FAIL
a = 1) -- FAIL
a = a + b - c
a = "foo" + "bar"
a = "foo".."bar".."baz"
a = true + false - nil
a = {} * {}
a = function() end / function() end
a = a() ^ b()
a = ... % ...
a = 1 == 2; a = 1 ~= 2
a = 1 < 2; a = 1 <= 2
a = 1 > 2; a = 1 >= 2
a = 1 < 2 < 3
a = 1 >= 2 >= 3
a = 1 == -- FAIL
a = ~= 2 -- FAIL
a = "foo" == "bar"
a = "foo" > "bar"
a = a ~= b
a = true == false
a = 1 and 2; a = 1 or 2
a = 1 and -- FAIL
a = or 1 -- FAIL
a = 1 and 2 and 3
a = 1 or 2 or 3
a = 1 and 2 or 3
a = a and b or c
a = a() and (b)() or c.d
a = "foo" and "bar"
a = true or false
a = {} and {} or {}
a = (1) and ("foo") or (nil)
a = function() end == function() end
a = function() end or function() end
a = { -- FAIL
a = {}
a = {,} -- FAIL
a = {;} -- FAIL
a = {,,} -- FAIL
a = {;;} -- FAIL
a = {{ -- FAIL
a = {{{}}}
a = {{},{},{{}},}
a = { 1 }
a = { 1, }
a = { 1; }
a = { 1, 2 }
a = { a, b, c, }
a = { true; false, nil; }
a = { a.b, a[b]; a:c(), }
a = { 1 + 2, a > b, "a" or "b" }
a = { a=1, }
a = { a=1, b="foo", c=nil }
a = { a -- FAIL
a = { a= -- FAIL
a = { a=, -- FAIL
a = { a=; -- FAIL
a = { 1, a="foo" -- FAIL
a = { 1, a="foo"; b={}, d=true; }
a = { [ -- FAIL
a = { [1 -- FAIL
a = { [1] -- FAIL
a = { [a]= -- FAIL
a = { ["foo"]="bar" }
a = { [1]=a, [2]=b, }
a = { true, a=1; ["foo"]="bar", }

View file

@ -1,61 +0,0 @@
-- Adapted from Yueliang
package.path = "../?.lua;" .. package.path
local util = require'Util'
local Parser = require'ParseLua'
local Format_Mini = require'FormatMini'
local line_nr = 0
for w in io.lines("test_lines.txt") do
line_nr = line_nr + 1
--print(w)
local success, ast = Parser.ParseLua(w)
if w:find("FAIL") then
--[[if success then
print("ERROR PARSING LINE:")
print("Should fail: true. Did fail: " .. tostring(not success))
print("Line: " .. w)
else
--print("Suceeded!")
end]]
else
if not success then
print("ERROR PARSING LINE:")
print("Should fail: false. Did fail: " .. tostring(not success))
print("Line: " .. w)
else
success, ast = Format_Mini(ast)
--print(success, ast)
if not success then
print("ERROR MINIFYING LINE:")
print("Message: " .. ast)
print("Line: " .. w)
end
success, ast = loadstring(success)
if not success then
print("ERROR PARSING MINIFIED LINE:")
print("Message: " .. ast)
print("Line nr: " .. line_nr)
print("Line: " .. w)
end
--print("Suceeded!")
end
end
end
print"Done!"
os.remove("tmp")
--[[
function readAll(file)
local f = io.open(file, "rb")
local content = f:read("*all")
f:close()
return content
end
local text = readAll('../ParseLua.lua')
local success, ast = Parser.ParseLua(text)
local nice
nice = Format_Mini(ast)
print(nice)
--]]

View file

@ -1,561 +0,0 @@
-- Adapted from Yueliang
local source = [=[
; -- FAIL
local -- FAIL
local; -- FAIL
local = -- FAIL
local end -- FAIL
local a
local a;
local a, b, c
local a; local b local c;
local a = 1
local a local b = a
local a, b = 1, 2
local a, b, c = 1, 2, 3
local a, b, c = 1
local a = 1, 2, 3
local a, local -- FAIL
local 1 -- FAIL
local "foo" -- FAIL
local a = local -- FAIL
local a, b, = -- FAIL
local a, b = 1, local -- FAIL
local a, b = , local -- FAIL
do -- FAIL
end -- FAIL
do end
do ; end -- FAIL
do 1 end -- FAIL
do "foo" end -- FAIL
do local a, b end
do local a local b end
do local a; local b; end
do local a = 1 end
do do end end
do do end; end
do do do end end end
do do do end; end; end
do do do return end end end
do end do -- FAIL
do end end -- FAIL
do return end
do return return end -- FAIL
do break end -- FAIL
while -- FAIL
while do -- FAIL
while = -- FAIL
while 1 do -- FAIL
while 1 do end
while 1 do local a end
while 1 do local a local b end
while 1 do local a; local b; end
while 1 do 2 end -- FAIL
while 1 do "foo" end -- FAIL
while true do end
while 1 do ; end -- FAIL
while 1 do while -- FAIL
while 1 end -- FAIL
while 1 2 do -- FAIL
while 1 = 2 do -- FAIL
while 1 do return end
while 1 do return return end -- FAIL
while 1 do do end end
while 1 do do return end end
while 1 do break end
while 1 do break break end -- FAIL
while 1 do do break end end
repeat -- FAIL
repeat until -- FAIL
repeat until 0
repeat until false
repeat until local -- FAIL
repeat end -- FAIL
repeat 1 -- FAIL
repeat = -- FAIL
repeat local a until 1
repeat local a local b until 0
repeat local a; local b; until 0
repeat ; until 1 -- FAIL
repeat 2 until 1 -- FAIL
repeat "foo" until 1 -- FAIL
repeat return until 0
repeat return return until 0 -- FAIL
repeat break until 0
repeat break break until 0 -- FAIL
repeat do end until 0
repeat do return end until 0
repeat do break end until 0
for -- FAIL
for do -- FAIL
for end -- FAIL
for 1 -- FAIL
for a -- FAIL
for true -- FAIL
for a, in -- FAIL
for a in -- FAIL
for a do -- FAIL
for a in do -- FAIL
for a in b do -- FAIL
for a in b end -- FAIL
for a in b, do -- FAIL
for a in b do end
for a in b do local a local b end
for a in b do local a; local b; end
for a in b do 1 end -- FAIL
for a in b do "foo" end -- FAIL
for a b in -- FAIL
for a, b, c in p do end
for a, b, c in p, q, r do end
for a in 1 do end
for a in true do end
for a in "foo" do end
for a in b do break end
for a in b do break break end -- FAIL
for a in b do return end
for a in b do return return end -- FAIL
for a in b do do end end
for a in b do do break end end
for a in b do do return end end
for = -- FAIL
for a = -- FAIL
for a, b = -- FAIL
for a = do -- FAIL
for a = 1, do -- FAIL
for a = p, q, do -- FAIL
for a = p q do -- FAIL
for a = b do end -- FAIL
for a = 1, 2, 3, 4 do end -- FAIL
for a = p, q do end
for a = 1, 2 do end
for a = 1, 2 do local a local b end
for a = 1, 2 do local a; local b; end
for a = 1, 2 do 3 end -- FAIL
for a = 1, 2 do "foo" end -- FAIL
for a = p, q, r do end
for a = 1, 2, 3 do end
for a = p, q do break end
for a = p, q do break break end -- FAIL
for a = 1, 2 do return end
for a = 1, 2 do return return end -- FAIL
for a = p, q do do end end
for a = p, q do do break end end
for a = p, q do do return end end
break -- FAIL
return
return;
return return -- FAIL
return 1
return local -- FAIL
return "foo"
return 1, -- FAIL
return 1,2,3
return a,b,c,d
return 1,2;
return ...
return 1,a,...
if -- FAIL
elseif -- FAIL
else -- FAIL
then -- FAIL
if then -- FAIL
if 1 -- FAIL
if 1 then -- FAIL
if 1 else -- FAIL
if 1 then else -- FAIL
if 1 then elseif -- FAIL
if 1 then end
if 1 then local a end
if 1 then local a local b end
if 1 then local a; local b; end
if 1 then else end
if 1 then local a else local b end
if 1 then local a; else local b; end
if 1 then elseif 2 -- FAIL
if 1 then elseif 2 then -- FAIL
if 1 then elseif 2 then end
if 1 then local a elseif 2 then local b end
if 1 then local a; elseif 2 then local b; end
if 1 then elseif 2 then else end
if 1 then else if 2 then end end
if 1 then else if 2 then end -- FAIL
if 1 then break end -- FAIL
if 1 then return end
if 1 then return return end -- FAIL
if 1 then end; if 1 then end;
function -- FAIL
function 1 -- FAIL
function end -- FAIL
function a -- FAIL
function a end -- FAIL
function a( end -- FAIL
function a() end
function a(1 -- FAIL
function a("foo" -- FAIL
function a(p -- FAIL
function a(p,) -- FAIL
function a(p q -- FAIL
function a(p) end
function a(p,q,) end -- FAIL
function a(p,q,r) end
function a(p,q,1 -- FAIL
function a(p) do -- FAIL
function a(p) 1 end -- FAIL
function a(p) return end
function a(p) break end -- FAIL
function a(p) return return end -- FAIL
function a(p) do end end
function a.( -- FAIL
function a.1 -- FAIL
function a.b() end
function a.b, -- FAIL
function a.b.( -- FAIL
function a.b.c.d() end
function a: -- FAIL
function a:1 -- FAIL
function a:b() end
function a:b: -- FAIL
function a:b. -- FAIL
function a.b.c:d() end
function a(...) end
function a(..., -- FAIL
function a(p,...) end
function a(p,q,r,...) end
function a() local a local b end
function a() local a; local b; end
function a() end; function a() end;
local function -- FAIL
local function 1 -- FAIL
local function end -- FAIL
local function a -- FAIL
local function a end -- FAIL
local function a( end -- FAIL
local function a() end
local function a(1 -- FAIL
local function a("foo" -- FAIL
local function a(p -- FAIL
local function a(p,) -- FAIL
local function a(p q -- FAIL
local function a(p) end
local function a(p,q,) end -- FAIL
local function a(p,q,r) end
local function a(p,q,1 -- FAIL
local function a(p) do -- FAIL
local function a(p) 1 end -- FAIL
local function a(p) return end
local function a(p) break end -- FAIL
local function a(p) return return end -- FAIL
local function a(p) do end end
local function a. -- FAIL
local function a: -- FAIL
local function a(...) end
local function a(..., -- FAIL
local function a(p,...) end
local function a(p,q,r,...) end
local function a() local a local b end
local function a() local a; local b; end
local function a() end; local function a() end;
a -- FAIL
a, -- FAIL
a,b,c -- FAIL
a,b = -- FAIL
a = 1
a = 1,2,3
a,b,c = 1
a,b,c = 1,2,3
a.b = 1
a.b.c = 1
a[b] = 1
a[b][c] = 1
a.b[c] = 1
a[b].c = 1
0 = -- FAIL
"foo" = -- FAIL
true = -- FAIL
(a) = -- FAIL
{} = -- FAIL
a:b() = -- FAIL
a() = -- FAIL
a.b:c() = -- FAIL
a[b]() = -- FAIL
a = a b -- FAIL
a = 1 2 -- FAIL
a = a = 1 -- FAIL
a( -- FAIL
a()
a(1)
a(1,) -- FAIL
a(1,2,3)
1() -- FAIL
a()()
a.b()
a[b]()
a.1 -- FAIL
a.b -- FAIL
a[b] -- FAIL
a.b.( -- FAIL
a.b.c()
a[b][c]()
a[b].c()
a.b[c]()
a:b()
a:b -- FAIL
a:1 -- FAIL
a.b:c()
a[b]:c()
a:b: -- FAIL
a:b():c()
a:b().c[d]:e()
a:b()[c].d:e()
(a)()
()() -- FAIL
(1)()
("foo")()
(true)()
(a)()()
(a.b)()
(a[b])()
(a).b()
(a)[b]()
(a):b()
(a).b[c]:d()
(a)[b].c:d()
(a):b():c()
(a):b().c[d]:e()
(a):b()[c].d:e()
a"foo"
a[[foo]]
a.b"foo"
a[b]"foo"
a:b"foo"
a{}
a.b{}
a[b]{}
a:b{}
a()"foo"
a"foo"()
a"foo".b()
a"foo"[b]()
a"foo":c()
a"foo""bar"
a"foo"{}
(a):b"foo".c[d]:e"bar"
(a):b"foo"[c].d:e"bar"
a(){}
a{}()
a{}.b()
a{}[b]()
a{}:c()
a{}"foo"
a{}{}
(a):b{}.c[d]:e{}
(a):b{}[c].d:e{}
a = -- FAIL
a = a
a = nil
a = false
a = 1
a = "foo"
a = [[foo]]
a = {}
a = (a)
a = (nil)
a = (true)
a = (1)
a = ("foo")
a = ([[foo]])
a = ({})
a = a.b
a = a.b. -- FAIL
a = a.b.c
a = a:b -- FAIL
a = a[b]
a = a[1]
a = a["foo"]
a = a[b][c]
a = a.b[c]
a = a[b].c
a = (a)[b]
a = (a).c
a = () -- FAIL
a = a()
a = a.b()
a = a[b]()
a = a:b()
a = (a)()
a = (a).b()
a = (a)[b]()
a = (a):b()
a = a"foo"
a = a{}
a = function -- FAIL
a = function 1 -- FAIL
a = function a -- FAIL
a = function end -- FAIL
a = function( -- FAIL
a = function() end
a = function(1 -- FAIL
a = function(p) end
a = function(p,) -- FAIL
a = function(p q -- FAIL
a = function(p,q,r) end
a = function(p,q,1 -- FAIL
a = function(...) end
a = function(..., -- FAIL
a = function(p,...) end
a = function(p,q,r,...) end
a = ...
a = a, b, ...
a = (...)
a = ..., 1, 2
a = function() return ... end -- FAIL
a = -10
a = -"foo"
a = -a
a = -nil
a = -true
a = -{}
a = -function() end
a = -a()
a = -(a)
a = - -- FAIL
a = not 10
a = not "foo"
a = not a
a = not nil
a = not true
a = not {}
a = not function() end
a = not a()
a = not (a)
a = not -- FAIL
a = #10
a = #"foo"
a = #a
a = #nil
a = #true
a = #{}
a = #function() end
a = #a()
a = #(a)
a = # -- FAIL
a = 1 + 2; a = 1 - 2
a = 1 * 2; a = 1 / 2
a = 1 ^ 2; a = 1 % 2
a = 1 .. 2
a = 1 + -- FAIL
a = 1 .. -- FAIL
a = 1 * / -- FAIL
a = 1 + -2; a = 1 - -2
a = 1 * - -- FAIL
a = 1 * not 2; a = 1 / not 2
a = 1 / not -- FAIL
a = 1 * #"foo"; a = 1 / #"foo"
a = 1 / # -- FAIL
a = 1 + 2 - 3 * 4 / 5 % 6 ^ 7
a = ((1 + 2) - 3) * (4 / (5 % 6 ^ 7))
a = (1 + (2 - (3 * (4 / (5 % 6 ^ ((7)))))))
a = ((1 -- FAIL
a = ((1 + 2) -- FAIL
a = 1) -- FAIL
a = a + b - c
a = "foo" + "bar"
a = "foo".."bar".."baz"
a = true + false - nil
a = {} * {}
a = function() end / function() end
a = a() ^ b()
a = ... % ...
a = 1 == 2; a = 1 ~= 2
a = 1 < 2; a = 1 <= 2
a = 1 > 2; a = 1 >= 2
a = 1 < 2 < 3
a = 1 >= 2 >= 3
a = 1 == -- FAIL
a = ~= 2 -- FAIL
a = "foo" == "bar"
a = "foo" > "bar"
a = a ~= b
a = true == false
a = 1 and 2; a = 1 or 2
a = 1 and -- FAIL
a = or 1 -- FAIL
a = 1 and 2 and 3
a = 1 or 2 or 3
a = 1 and 2 or 3
a = a and b or c
a = a() and (b)() or c.d
a = "foo" and "bar"
a = true or false
a = {} and {} or {}
a = (1) and ("foo") or (nil)
a = function() end == function() end
a = function() end or function() end
a = { -- FAIL
a = {}
a = {,} -- FAIL
a = {;} -- FAIL
a = {,,} -- FAIL
a = {;;} -- FAIL
a = {{ -- FAIL
a = {{{}}}
a = {{},{},{{}},}
a = { 1 }
a = { 1, }
a = { 1; }
a = { 1, 2 }
a = { a, b, c, }
a = { true; false, nil; }
a = { a.b, a[b]; a:c(), }
a = { 1 + 2, a > b, "a" or "b" }
a = { a=1, }
a = { a=1, b="foo", c=nil }
a = { a -- FAIL
a = { a= -- FAIL
a = { a=, -- FAIL
a = { a=; -- FAIL
a = { 1, a="foo" -- FAIL
a = { 1, a="foo"; b={}, d=true; }
a = { [ -- FAIL
a = { [1 -- FAIL
a = { [1] -- FAIL
a = { [a]= -- FAIL
a = { ["foo"]="bar" }
a = { [1]=a, [2]=b, }
a = { true, a=1; ["foo"]="bar", }
]=]
package.path = "../?.lua;" .. package.path
local util = require'Util'
local Parser = require'ParseLua'
local Format_Mini = require'FormatMini'
local f = io.open("tmp", 'wb')
f:write(source)
f:close()
for w in io.lines("tmp") do
--print(w)
local success, ast = Parser.ParseLua(w)
if w:find("FAIL") then
if success then
print("ERROR PARSING LINE:")
print("Should fail: true. Did fail: " .. tostring(not success))
--print("Message: " .. ast)
print("Line: " .. w)
else
--print("Suceeded!")
end
else
if not success then
print("ERROR PARSING LINE:")
print("Should fail: false. Did fail: " .. tostring(not success))
print("Message: " .. ast)
print("Line: " .. w)
else
--print("Suceeded!")
end
end
end
print"Done!"
os.remove("tmp")

View file

@ -1,111 +0,0 @@
--[[
Table utility by Thomas99.
LICENSE :
Copyright (c) 2015 Thomas99
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the
use of this software.
Permission is granted to anyone to use this software for any purpose, including
commercial applications, and to alter it and redistribute it freely, subject
to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in a
product, an acknowledgment in the product documentation would be appreciated
but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
]]
-- Diverses fonctions en rapport avec les tables.
-- v0.1.0
--
-- Changements :
-- - v0.1.0 :
-- Première version versionnée. Il a dû se passer des trucs avant mais j'ai pas noté :p
-- Copie récursivement la table t dans la table dest (ou une table vide si non précisé) et la retourne
-- replace (false) : indique si oui ou non, les clefs existant déjà dans dest doivent être écrasées par celles de t
-- metatable (true) : copier ou non également les metatables
-- filter (function) : filtre, si retourne true copie l'objet, sinon ne le copie pas
-- Note : les metatables des objets ne sont jamais re-copiées (mais référence à la place), car sinon lors de la copie
-- la classe de ces objets changera pour une nouvelle classe, et c'est pas pratique :p
function table.copy(t, dest, replace, metatable, filter, copied)
local copied = copied or {}
local replace = replace or false
local metatable = (metatable==nil or metatable) and true
local filter = filter or function(name, source, destination) return true end
if type(t) ~= "table" then
return t
elseif copied[t] then -- si la table a déjà été copiée
return copied[t]
end
local dest = dest or {} -- la copie
copied[t] = dest -- on marque la table comme copiée
for k, v in pairs(t) do
if filter(k, t, dest) then
if replace then
dest[k] = table.copy(v, dest[k], replace, metatable, filter, copied)
else
if dest[k] == nil or type(v) == "table" then -- si la clef n'existe pas déjà dans dest ou si c'est une table à copier
dest[k] = table.copy(v, dest[k], replace, metatable, filter, copied)
end
end
end
end
-- copie des metatables
if metatable then
if t.__classe then
setmetatable(dest, getmetatable(t))
else
setmetatable(dest, table.copy(getmetatable(t), getmetatable(dest), replace, filter))
end
end
return dest
end
-- retourne true si value est dans la table
function table.isIn(table, value)
for _,v in pairs(table) do
if v == value then
return true
end
end
return false
end
-- retourne la longueur exacte d'une table (fonctionne sur les tables à clef)
function table.len(t)
local len=0
for i in pairs(t) do
len=len+1
end
return len
end
-- Sépare str en éléments séparés par le pattern et retourne une table
function string.split(str, pattern)
local t = {}
local pos = 0
for i,p in string.gmatch(str, "(.-)"..pattern.."()") do
table.insert(t, i)
pos = p
end
table.insert(t, str:sub(pos))
return t
end

View file

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2012-2013 Copyright (c) 2014 Andre Murbach Maidl
Permission is hereby granted, free of charge, to any person obtaining a copy of 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 this software and associated documentation files (the "Software"), to deal in

127
lua-parser/README.md Normal file
View file

@ -0,0 +1,127 @@
lua-parser
==========
[![Build Status](https://travis-ci.org/andremm/lua-parser.svg?branch=master)](https://travis-ci.org/andremm/lua-parser)
This is a Lua 5.3 parser written with [LPegLabel](https://github.com/sqmedeiros/lpeglabel) that
generates an AST in a format that is similar to the one specified by [Metalua](https://github.com/fab13n/metalua-parser).
The parser uses LPegLabel to provide more specific error messages.
Requirements
------------
lua >= 5.1
lpeglabel >= 1.0.0
API
---
The package `lua-parser` has two modules: `lua-parser.parser`
and `lua-parser.pp`.
The module `lua-parser.parser` implements the function `parser.parse`:
* `parser.parse (subject, filename)`
Both subject and filename should be strings.
It tries to parse subject and returns the AST in case of success.
It returns **nil** plus an error message in case of error.
In case of error, the parser uses the string filename to build an
error message.
The module `lua-parser.pp` implements a pretty printer to the AST and
a dump function:
* `pp.tostring (ast)`
It converts the AST to a string and returns this string.
* `pp.print (ast)`
It converts the AST to a string and prints this string.
* `pp.dump (ast[, i])`
It dumps the AST to the screen.
The parameter **i** sets the indentation level.
AST format
----------
block: { stat* }
stat:
`Do{ stat* }
| `Set{ {lhs+} {expr+} } -- lhs1, lhs2... = e1, e2...
| `While{ expr block } -- while e do b end
| `Repeat{ block expr } -- repeat b until e
| `If{ (expr block)+ block? } -- if e1 then b1 [elseif e2 then b2] ... [else bn] end
| `Fornum{ ident expr expr expr? block } -- for ident = e, e[, e] do b end
| `Forin{ {ident+} {expr+} block } -- for i1, i2... in e1, e2... do b end
| `Local{ {ident+} {expr+}? } -- local i1, i2... = e1, e2...
| `Localrec{ ident expr } -- only used for 'local function'
| `Goto{ <string> } -- goto str
| `Label{ <string> } -- ::str::
| `Return{ <expr*> } -- return e1, e2...
| `Break -- break
| apply
expr:
`Nil
| `Dots
| `Boolean{ <boolean> }
| `Number{ <number> }
| `String{ <string> }
| `Function{ { `Id{ <string> }* `Dots? } block }
| `Table{ ( `Pair{ expr expr } | expr )* }
| `Op{ opid expr expr? }
| `Paren{ expr } -- significant to cut multiple values returns
| apply
| lhs
apply:
`Call{ expr expr* }
| `Invoke{ expr `String{ <string> } expr* }
lhs: `Id{ <string> } | `Index{ expr expr }
opid: -- includes additional operators from Lua 5.3 and all relational operators
'add' | 'sub' | 'mul' | 'div'
| 'idiv' | 'mod' | 'pow' | 'concat'
| 'band' | 'bor' | 'bxor' | 'shl' | 'shr'
| 'eq' | 'ne' | 'lt' | 'gt' | 'le' | 'ge'
| 'and' | 'or' | 'unm' | 'len' | 'bnot' | 'not'
Usage
--------
**Code example for parsing a string:**
local parser = require "lua-parser.parser"
local pp = require "lua-parser.pp"
if #arg ~= 1 then
print("Usage: parse.lua <string>")
os.exit(1)
end
local ast, error_msg = parser.parse(arg[1], "example.lua")
if not ast then
print(error_msg)
os.exit(1)
end
pp.print(ast)
os.exit(0)
**Running the above code example using a string without syntax error:**
$ lua parse.lua "for i=1, 10 do print(i) end"
{ `Fornum{ `Id "i", `Number "1", `Number "10", { `Call{ `Id "print", `Id "i" } } } }
**Running the above code example using a string with syntax error:**
$ lua parse.lua "for i=1, 10 do print(i) "
example.lua:1:24: syntax error, expected 'end' to close the for loop

479
lua-parser/parser.lua Normal file
View file

@ -0,0 +1,479 @@
--[[
This module implements a parser for Lua 5.3 with LPeg,
and generates an Abstract Syntax Tree that is similar to the one generated by Metalua.
For more information about Metalua, please, visit:
https://github.com/fab13n/metalua-parser
block: { stat* }
stat:
`Do{ stat* }
| `Set{ {lhs+} {expr+} } -- lhs1, lhs2... = e1, e2...
| `While{ expr block } -- while e do b end
| `Repeat{ block expr } -- repeat b until e
| `If{ (expr block)+ block? } -- if e1 then b1 [elseif e2 then b2] ... [else bn] end
| `Fornum{ ident expr expr expr? block } -- for ident = e, e[, e] do b end
| `Forin{ {ident+} {expr+} block } -- for i1, i2... in e1, e2... do b end
| `Local{ {ident+} {expr+}? } -- local i1, i2... = e1, e2...
| `Localrec{ ident expr } -- only used for 'local function'
| `Goto{ <string> } -- goto str
| `Label{ <string> } -- ::str::
| `Return{ <expr*> } -- return e1, e2...
| `Break -- break
| apply
expr:
`Nil
| `Dots
| `Boolean{ <boolean> }
| `Number{ <number> }
| `String{ <string> }
| `Function{ { `Id{ <string> }* `Dots? } block }
| `Table{ ( `Pair{ expr expr } | expr )* }
| `Op{ opid expr expr? }
| `Paren{ expr } -- significant to cut multiple values returns
| apply
| lhs
apply:
`Call{ expr expr* }
| `Invoke{ expr `String{ <string> } expr* }
lhs: `Id{ <string> } | `Index{ expr expr }
opid: -- includes additional operators from Lua 5.3 and all relational operators
'add' | 'sub' | 'mul' | 'div'
| 'idiv' | 'mod' | 'pow' | 'concat'
| 'band' | 'bor' | 'bxor' | 'shl' | 'shr'
| 'eq' | 'ne' | 'lt' | 'gt' | 'le' | 'ge'
| 'and' | 'or' | 'unm' | 'len' | 'bnot' | 'not'
]]
local lpeg = require "lpeglabel"
lpeg.locale(lpeg)
local P, S, V = lpeg.P, lpeg.S, lpeg.V
local C, Carg, Cb, Cc = lpeg.C, lpeg.Carg, lpeg.Cb, lpeg.Cc
local Cf, Cg, Cmt, Cp, Cs, Ct = lpeg.Cf, lpeg.Cg, lpeg.Cmt, lpeg.Cp, lpeg.Cs, lpeg.Ct
local Lc, T = lpeg.Lc, lpeg.T
local alpha, digit, alnum = lpeg.alpha, lpeg.digit, lpeg.alnum
local xdigit = lpeg.xdigit
local space = lpeg.space
-- error message auxiliary functions
local labels = {
{ "ErrExtra", "unexpected character(s), expected EOF" },
{ "ErrInvalidStat", "unexpected token, invalid start of statement" },
{ "ErrEndIf", "expected 'end' to close the if statement" },
{ "ErrExprIf", "expected a condition after 'if'" },
{ "ErrThenIf", "expected 'then' after the condition" },
{ "ErrExprEIf", "expected a condition after 'elseif'" },
{ "ErrThenEIf", "expected 'then' after the condition" },
{ "ErrEndDo", "expected 'end' to close the do block" },
{ "ErrExprWhile", "expected a condition after 'while'" },
{ "ErrDoWhile", "expected 'do' after the condition" },
{ "ErrEndWhile", "expected 'end' to close the while loop" },
{ "ErrUntilRep", "expected 'until' at the end of the repeat loop" },
{ "ErrExprRep", "expected a conditions after 'until'" },
{ "ErrForRange", "expected a numeric or generic range after 'for'" },
{ "ErrEndFor", "expected 'end' to close the for loop" },
{ "ErrExprFor1", "expected a starting expression for the numeric range" },
{ "ErrCommaFor", "expected ',' to split the start and end of the range" },
{ "ErrExprFor2", "expected an ending expression for the numeric range" },
{ "ErrExprFor3", "expected a step expression for the numeric range after ','" },
{ "ErrInFor", "expected '=' or 'in' after the variable(s)" },
{ "ErrEListFor", "expected one or more expressions after 'in'" },
{ "ErrDoFor", "expected 'do' after the range of the for loop" },
{ "ErrDefLocal", "expected a function definition or assignment after local" },
{ "ErrNameLFunc", "expected a function name after 'function'" },
{ "ErrEListLAssign", "expected one or more expressions after '='" },
{ "ErrEListAssign", "expected one or more expressions after '='" },
{ "ErrFuncName", "expected a function name after 'function'" },
{ "ErrNameFunc1", "expected a function name after '.'" },
{ "ErrNameFunc2", "expected a method name after ':'" },
{ "ErrOParenPList", "expected '(' for the parameter list" },
{ "ErrCParenPList", "expected ')' to close the parameter list" },
{ "ErrEndFunc", "expected 'end' to close the function body" },
{ "ErrParList", "expected a variable name or '...' after ','" },
{ "ErrLabel", "expected a label name after '::'" },
{ "ErrCloseLabel", "expected '::' after the label" },
{ "ErrGoto", "expected a label after 'goto'" },
{ "ErrRetList", "expected an expression after ',' in the return statement" },
{ "ErrVarList", "expected a variable name after ','" },
{ "ErrExprList", "expected an expression after ','" },
{ "ErrOrExpr", "expected an expression after 'or'" },
{ "ErrAndExpr", "expected an expression after 'and'" },
{ "ErrRelExpr", "expected an expression after the relational operator" },
{ "ErrBOrExpr", "expected an expression after '|'" },
{ "ErrBXorExpr", "expected an expression after '~'" },
{ "ErrBAndExpr", "expected an expression after '&'" },
{ "ErrShiftExpr", "expected an expression after the bit shift" },
{ "ErrConcatExpr", "expected an expression after '..'" },
{ "ErrAddExpr", "expected an expression after the additive operator" },
{ "ErrMulExpr", "expected an expression after the multiplicative operator" },
{ "ErrUnaryExpr", "expected an expression after the unary operator" },
{ "ErrPowExpr", "expected an expression after '^'" },
{ "ErrExprParen", "expected an expression after '('" },
{ "ErrCParenExpr", "expected ')' to close the expression" },
{ "ErrNameIndex", "expected a field name after '.'" },
{ "ErrExprIndex", "expected an expression after '['" },
{ "ErrCBracketIndex", "expected ']' to close the indexing expression" },
{ "ErrNameMeth", "expected a method name after ':'" },
{ "ErrMethArgs", "expected some arguments for the method call (or '()')" },
{ "ErrArgList", "expected an expression after ',' in the argument list" },
{ "ErrCParenArgs", "expected ')' to close the argument list" },
{ "ErrCBraceTable", "expected '}' to close the table constructor" },
{ "ErrEqField", "expected '=' after the table key" },
{ "ErrExprField", "expected an expression after '='" },
{ "ErrExprFKey", "expected an expression after '[' for the table key" },
{ "ErrCBracketFKey", "expected ']' to close the table key" },
{ "ErrDigitHex", "expected one or more hexadecimal digits after '0x'" },
{ "ErrDigitDeci", "expected one or more digits after the decimal point" },
{ "ErrDigitExpo", "expected one or more digits for the exponent" },
{ "ErrQuote", "unclosed string" },
{ "ErrHexEsc", "expected exactly two hexadecimal digits after '\\x'" },
{ "ErrOBraceUEsc", "expected '{' after '\\u'" },
{ "ErrDigitUEsc", "expected one or more hexadecimal digits for the UTF-8 code point" },
{ "ErrCBraceUEsc", "expected '}' after the code point" },
{ "ErrEscSeq", "invalid escape sequence" },
{ "ErrCloseLStr", "unclosed long string" },
}
local function throw(label)
label = "Err" .. label
for i, labelinfo in ipairs(labels) do
if labelinfo[1] == label then
return T(i)
end
end
error("Label not found: " .. label)
end
local function expect (patt, label)
return patt + throw(label)
end
-- regular combinators and auxiliary functions
local function token (patt)
return patt * V"Skip"
end
local function sym (str)
return token(P(str))
end
local function kw (str)
return token(P(str) * -V"IdRest")
end
local function tagC (tag, patt)
return Ct(Cg(Cp(), "pos") * Cg(Cc(tag), "tag") * patt)
end
local function unaryOp (op, e)
return { tag = "Op", pos = e.pos, [1] = op, [2] = e }
end
local function binaryOp (e1, op, e2)
if not op then
return e1
else
return { tag = "Op", pos = e1.pos, [1] = op, [2] = e1, [3] = e2 }
end
end
local function sepBy (patt, sep, label)
if label then
return patt * Cg(sep * expect(patt, label))^0
else
return patt * Cg(sep * patt)^0
end
end
local function chainOp (patt, sep, label)
return Cf(sepBy(patt, sep, label), binaryOp)
end
local function commaSep (patt, label)
return sepBy(patt, sym(","), label)
end
local function tagDo (block)
block.tag = "Do"
return block
end
local function fixFuncStat (func)
if func[1].is_method then table.insert(func[2][1], 1, { tag = "Id", [1] = "self" }) end
func[1] = {func[1]}
func[2] = {func[2]}
return func
end
local function addDots (params, dots)
if dots then table.insert(params, dots) end
return params
end
local function insertIndex (t, index)
return { tag = "Index", pos = t.pos, [1] = t, [2] = index }
end
local function markMethod(t, method)
if method then
return { tag = "Index", pos = t.pos, is_method = true, [1] = t, [2] = method }
end
return t
end
local function makeIndexOrCall (t1, t2)
if t2.tag == "Call" or t2.tag == "Invoke" then
local t = { tag = t2.tag, pos = t1.pos, [1] = t1 }
for k, v in ipairs(t2) do
table.insert(t, v)
end
return t
end
return { tag = "Index", pos = t1.pos, [1] = t1, [2] = t2[1] }
end
-- grammar
local G = { V"Lua",
Lua = V"Shebang"^-1 * V"Skip" * V"Block" * expect(P(-1), "Extra");
Shebang = P"#!" * (P(1) - P"\n")^0;
Block = tagC("Block", V"Stat"^0 * V"RetStat"^-1);
Stat = V"IfStat" + V"DoStat" + V"WhileStat" + V"RepeatStat" + V"ForStat"
+ V"LocalStat" + V"FuncStat" + V"BreakStat" + V"LabelStat" + V"GoToStat"
+ V"FuncCall" + V"Assignment" + sym(";") + -V"BlockEnd" * throw("InvalidStat");
BlockEnd = P"return" + "end" + "elseif" + "else" + "until" + -1;
IfStat = tagC("If", V"IfPart" * V"ElseIfPart"^0 * V"ElsePart"^-1 * expect(kw("end"), "EndIf"));
IfPart = kw("if") * expect(V"Expr", "ExprIf") * expect(kw("then"), "ThenIf") * V"Block";
ElseIfPart = kw("elseif") * expect(V"Expr", "ExprEIf") * expect(kw("then"), "ThenEIf") * V"Block";
ElsePart = kw("else") * V"Block";
DoStat = kw("do") * V"Block" * expect(kw("end"), "EndDo") / tagDo;
WhileStat = tagC("While", kw("while") * expect(V"Expr", "ExprWhile") * V"WhileBody");
WhileBody = expect(kw("do"), "DoWhile") * V"Block" * expect(kw("end"), "EndWhile");
RepeatStat = tagC("Repeat", kw("repeat") * V"Block" * expect(kw("until"), "UntilRep") * expect(V"Expr", "ExprRep"));
ForStat = kw("for") * expect(V"ForNum" + V"ForIn", "ForRange") * expect(kw("end"), "EndFor");
ForNum = tagC("Fornum", V"Id" * sym("=") * V"NumRange" * V"ForBody");
NumRange = expect(V"Expr", "ExprFor1") * expect(sym(","), "CommaFor") *expect(V"Expr", "ExprFor2")
* (sym(",") * expect(V"Expr", "ExprFor3"))^-1;
ForIn = tagC("Forin", V"NameList" * expect(kw("in"), "InFor") * expect(V"ExprList", "EListFor") * V"ForBody");
ForBody = expect(kw("do"), "DoFor") * V"Block";
LocalStat = kw("local") * expect(V"LocalFunc" + V"LocalAssign", "DefLocal");
LocalFunc = tagC("Localrec", kw("function") * expect(V"Id", "NameLFunc") * V"FuncBody") / fixFuncStat;
LocalAssign = tagC("Local", V"NameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc())));
Assignment = tagC("Set", V"VarList" * V"AssignmentOp" * expect(V"ExprList", "EListAssign"));
FuncStat = tagC("Set", kw("function") * expect(V"FuncName", "FuncName") * V"FuncBody") / fixFuncStat;
FuncName = Cf(V"Id" * (sym(".") * expect(V"StrId", "NameFunc1"))^0, insertIndex)
* (sym(":") * expect(V"StrId", "NameFunc2"))^-1 / markMethod;
FuncBody = tagC("Function", V"FuncParams" * V"Block" * expect(kw("end"), "EndFunc"));
FuncParams = expect(sym("("), "OParenPList") * V"ParList" * expect(sym(")"), "CParenPList");
ParList = V"NamedParList" * (sym(",") * expect(tagC("Dots", sym("...")), "ParList"))^-1 / addDots
+ Ct(tagC("Dots", sym("...")))
+ Ct(Cc()); -- Cc({}) generates a bug since the {} would be shared across parses
NamedParList = tagC("NamedParList", commaSep(V"NamedPar"));
NamedPar = tagC("ParPair", V"ParKey" * expect(sym("="), "EqField") * expect(V"Expr", "ExprField"))
+ V"Id";
ParKey = V"Id" * #("=" * -P"=");
LabelStat = tagC("Label", sym("::") * expect(V"Name", "Label") * expect(sym("::"), "CloseLabel"));
GoToStat = tagC("Goto", kw("goto") * expect(V"Name", "Goto"));
BreakStat = tagC("Break", kw("break"));
RetStat = tagC("Return", kw("return") * commaSep(V"Expr", "RetList")^-1 * sym(";")^-1);
NameList = tagC("NameList", commaSep(V"Id"));
VarList = tagC("VarList", commaSep(V"VarExpr", "VarList"));
ExprList = tagC("ExpList", commaSep(V"Expr", "ExprList"));
Expr = V"OrExpr";
OrExpr = chainOp(V"AndExpr", V"OrOp", "OrExpr");
AndExpr = chainOp(V"RelExpr", V"AndOp", "AndExpr");
RelExpr = chainOp(V"BOrExpr", V"RelOp", "RelExpr");
BOrExpr = chainOp(V"BXorExpr", V"BOrOp", "BOrExpr");
BXorExpr = chainOp(V"BAndExpr", V"BXorOp", "BXorExpr");
BAndExpr = chainOp(V"ShiftExpr", V"BAndOp", "BAndExpr");
ShiftExpr = chainOp(V"ConcatExpr", V"ShiftOp", "ShiftExpr");
ConcatExpr = V"AddExpr" * (V"ConcatOp" * expect(V"ConcatExpr", "ConcatExpr"))^-1 / binaryOp;
AddExpr = chainOp(V"MulExpr", V"AddOp", "AddExpr");
MulExpr = chainOp(V"UnaryExpr", V"MulOp", "MulExpr");
UnaryExpr = V"UnaryOp" * expect(V"UnaryExpr", "UnaryExpr") / unaryOp
+ V"PowExpr";
PowExpr = V"SimpleExpr" * (V"PowOp" * expect(V"UnaryExpr", "PowExpr"))^-1 / binaryOp;
SimpleExpr = tagC("Number", V"Number")
+ tagC("String", V"String")
+ tagC("Nil", kw("nil"))
+ tagC("Boolean", kw("false") * Cc(false))
+ tagC("Boolean", kw("true") * Cc(true))
+ tagC("Dots", sym("..."))
+ V"FuncDef"
+ V"Table"
+ V"SuffixedExpr";
FuncCall = Cmt(V"SuffixedExpr", function(s, i, exp) return exp.tag == "Call" or exp.tag == "Invoke", exp end);
VarExpr = Cmt(V"SuffixedExpr", function(s, i, exp) return exp.tag == "Id" or exp.tag == "Index", exp end);
SuffixedExpr = Cf(V"PrimaryExpr" * (V"Index" + V"Call")^0, makeIndexOrCall);
PrimaryExpr = V"Id" + tagC("Paren", sym("(") * expect(V"Expr", "ExprParen") * expect(sym(")"), "CParenExpr"));
Index = tagC("DotIndex", sym("." * -P".") * expect(V"StrId", "NameIndex"))
+ tagC("ArrayIndex", sym("[" * -P(S"=[")) * expect(V"Expr", "ExprIndex") * expect(sym("]"), "CBracketIndex"));
Call = tagC("Invoke", Cg(sym(":" * -P":") * expect(V"StrId", "NameMeth") * expect(V"FuncArgs", "MethArgs")))
+ tagC("Call", V"FuncArgs");
FuncDef = kw("function") * V"FuncBody";
FuncArgs = sym("(") * commaSep(V"Expr", "ArgList")^-1 * expect(sym(")"), "CParenArgs")
+ V"Table"
+ tagC("String", V"String");
Table = tagC("Table", sym("{") * V"FieldList"^-1 * expect(sym("}"), "CBraceTable"));
FieldList = sepBy(V"Field", V"FieldSep") * V"FieldSep"^-1;
Field = tagC("Pair", V"FieldKey" * expect(sym("="), "EqField") * expect(V"Expr", "ExprField"))
+ V"Expr";
FieldKey = sym("[" * -P(S"=[")) * expect(V"Expr", "ExprFKey") * expect(sym("]"), "CBracketFKey")
+ V"StrId" * #("=" * -P"=");
FieldSep = sym(",") + sym(";");
Id = tagC("Id", V"Name");
StrId = tagC("String", V"Name");
-- lexer
Skip = (V"Space" + V"Comment")^0;
Space = space^1;
Comment = P"--" * V"LongStr" / function () return end
+ P"--" * (P(1) - P"\n")^0;
Name = token(-V"Reserved" * C(V"Ident"));
Reserved = V"Keywords" * -V"IdRest";
Keywords = P"and" + "break" + "do" + "elseif" + "else" + "end"
+ "false" + "for" + "function" + "goto" + "if" + "in"
+ "local" + "nil" + "not" + "or" + "repeat" + "return"
+ "then" + "true" + "until" + "while";
Ident = V"IdStart" * V"IdRest"^0;
IdStart = alpha + P"_";
IdRest = alnum + P"_";
Number = token((V"Hex" + V"Float" + V"Int") / tonumber);
Hex = (P"0x" + "0X") * expect(xdigit^1, "DigitHex");
Float = V"Decimal" * V"Expo"^-1
+ V"Int" * V"Expo";
Decimal = digit^1 * "." * digit^0
+ P"." * -P"." * expect(digit^1, "DigitDeci");
Expo = S"eE" * S"+-"^-1 * expect(digit^1, "DigitExpo");
Int = digit^1;
String = token(V"ShortStr" + V"LongStr");
ShortStr = P'"' * Cs((V"EscSeq" + (P(1)-S'"\n'))^0) * expect(P'"', "Quote")
+ P"'" * Cs((V"EscSeq" + (P(1)-S"'\n"))^0) * expect(P"'", "Quote");
EscSeq = P"\\" / "" -- remove backslash
* ( P"a" / "\a"
+ P"b" / "\b"
+ P"f" / "\f"
+ P"n" / "\n"
+ P"r" / "\r"
+ P"t" / "\t"
+ P"v" / "\v"
+ P"\n" / "\n"
+ P"\r" / "\n"
+ P"\\" / "\\"
+ P"\"" / "\""
+ P"\'" / "\'"
+ P"z" * space^0 / ""
+ digit * digit^-2 / tonumber / string.char
+ P"x" * expect(C(xdigit * xdigit), "HexEsc") * Cc(16) / tonumber / string.char
+ P"u" * expect("{", "OBraceUEsc")
* expect(C(xdigit^1), "DigitUEsc") * Cc(16)
* expect("}", "CBraceUEsc")
/ tonumber
/ (utf8 and utf8.char or string.char) -- true max is \u{10FFFF}
-- utf8.char needs Lua 5.3
-- string.char works only until \u{FF}
+ throw("EscSeq")
);
LongStr = V"Open" * C((P(1) - V"CloseEq")^0) * expect(V"Close", "CloseLStr") / function (s, eqs) return s end;
Open = "[" * Cg(V"Equals", "openEq") * "[" * P"\n"^-1;
Close = "]" * C(V"Equals") * "]";
Equals = P"="^0;
CloseEq = Cmt(V"Close" * Cb("openEq"), function (s, i, closeEq, openEq) return #openEq == #closeEq end);
OrOp = kw("or") / "or";
AndOp = kw("and") / "and";
RelOp = sym("~=") / "ne"
+ sym("==") / "eq"
+ sym("<=") / "le"
+ sym(">=") / "ge"
+ sym("<") / "lt"
+ sym(">") / "gt";
BOrOp = sym("|") / "bor";
BXorOp = sym("~" * -P"=") / "bxor";
BAndOp = sym("&") / "band";
ShiftOp = sym("<<") / "shl"
+ sym(">>") / "shr";
ConcatOp = sym("..") / "concat";
AddOp = sym("+") / "add"
+ sym("-") / "sub";
MulOp = sym("*") / "mul"
+ sym("//") / "idiv"
+ sym("/") / "div"
+ sym("%") / "mod";
UnaryOp = kw("not") / "not"
+ sym("-") / "unm"
+ sym("#") / "len"
+ sym("~") / "bnot";
PowOp = sym("^") / "pow";
AssignmentOp = (V"OrOp" + V"AndOp" + V"BOrOp" + V"BXorOp" + V"BAndOp" + V"ShiftOp" + V"ConcatOp" + V"AddOp" + V"MulOp" + V"PowOp")^-1 * sym("=")
}
local parser = {}
local validator = require("lua-parser.validator")
local validate = validator.validate
local syntaxerror = validator.syntaxerror
function parser.parse (subject, filename)
local errorinfo = { subject = subject, filename = filename }
lpeg.setmaxstack(1000)
local ast, label, sfail = lpeg.match(G, subject, nil, errorinfo)
if not ast then
local errpos = #subject-#sfail+1
local errmsg = labels[label][2]
return ast, syntaxerror(errorinfo, errpos, errmsg)
end
return validate(ast, errorinfo)
end
return parser

327
lua-parser/pp.lua Normal file
View file

@ -0,0 +1,327 @@
--[[
This module impements a pretty printer to the AST
]]
local pp = {}
local block2str, stm2str, exp2str, var2str
local explist2str, varlist2str, parlist2str, fieldlist2str
local function iscntrl (x)
if (x >= 0 and x <= 31) or (x == 127) then return true end
return false
end
local function isprint (x)
return not iscntrl(x)
end
local function fixed_string (str)
local new_str = ""
for i=1,string.len(str) do
char = string.byte(str, i)
if char == 34 then new_str = new_str .. string.format("\\\"")
elseif char == 92 then new_str = new_str .. string.format("\\\\")
elseif char == 7 then new_str = new_str .. string.format("\\a")
elseif char == 8 then new_str = new_str .. string.format("\\b")
elseif char == 12 then new_str = new_str .. string.format("\\f")
elseif char == 10 then new_str = new_str .. string.format("\\n")
elseif char == 13 then new_str = new_str .. string.format("\\r")
elseif char == 9 then new_str = new_str .. string.format("\\t")
elseif char == 11 then new_str = new_str .. string.format("\\v")
else
if isprint(char) then
new_str = new_str .. string.format("%c", char)
else
new_str = new_str .. string.format("\\%03d", char)
end
end
end
return new_str
end
local function name2str (name)
return string.format('"%s"', name)
end
local function boolean2str (b)
return string.format('"%s"', tostring(b))
end
local function number2str (n)
return string.format('"%s"', tostring(n))
end
local function string2str (s)
return string.format('"%s"', fixed_string(s))
end
function var2str (var)
local tag = var.tag
local str = "`" .. tag
if tag == "Id" then -- `Id{ <string> }
str = str .. " " .. name2str(var[1])
elseif tag == "Index" then -- `Index{ expr expr }
str = str .. "{ "
str = str .. exp2str(var[1]) .. ", "
str = str .. exp2str(var[2])
str = str .. " }"
else
error("expecting a variable, but got a " .. tag)
end
return str
end
function varlist2str (varlist)
local l = {}
for k, v in ipairs(varlist) do
l[k] = var2str(v)
end
return "{ " .. table.concat(l, ", ") .. " }"
end
function parlist2str (parlist)
local l = {}
local len = #parlist
local is_vararg = false
if len > 0 and parlist[len].tag == "Dots" then
is_vararg = true
len = len - 1
end
local i = 1
while i <= len do
l[i] = var2str(parlist[i])
i = i + 1
end
if is_vararg then
l[i] = "`" .. parlist[i].tag
end
return "{ " .. table.concat(l, ", ") .. " }"
end
function fieldlist2str (fieldlist)
local l = {}
for k, v in ipairs(fieldlist) do
local tag = v.tag
if tag == "Pair" then -- `Pair{ expr expr }
l[k] = "`" .. tag .. "{ "
l[k] = l[k] .. exp2str(v[1]) .. ", " .. exp2str(v[2])
l[k] = l[k] .. " }"
else -- expr
l[k] = exp2str(v)
end
end
if #l > 0 then
return "{ " .. table.concat(l, ", ") .. " }"
else
return ""
end
end
function exp2str (exp)
local tag = exp.tag
local str = "`" .. tag
if tag == "Nil" or
tag == "Dots" then
elseif tag == "Boolean" then -- `Boolean{ <boolean> }
str = str .. " " .. boolean2str(exp[1])
elseif tag == "Number" then -- `Number{ <number> }
str = str .. " " .. number2str(exp[1])
elseif tag == "String" then -- `String{ <string> }
str = str .. " " .. string2str(exp[1])
elseif tag == "Function" then -- `Function{ { `Id{ <string> }* `Dots? } block }
str = str .. "{ "
str = str .. parlist2str(exp[1]) .. ", "
str = str .. block2str(exp[2])
str = str .. " }"
elseif tag == "Table" then -- `Table{ ( `Pair{ expr expr } | expr )* }
str = str .. fieldlist2str(exp)
elseif tag == "Op" then -- `Op{ opid expr expr? }
str = str .. "{ "
str = str .. name2str(exp[1]) .. ", "
str = str .. exp2str(exp[2])
if exp[3] then
str = str .. ", " .. exp2str(exp[3])
end
str = str .. " }"
elseif tag == "Paren" then -- `Paren{ expr }
str = str .. "{ " .. exp2str(exp[1]) .. " }"
elseif tag == "Call" then -- `Call{ expr expr* }
str = str .. "{ "
str = str .. exp2str(exp[1])
if exp[2] then
for i=2, #exp do
str = str .. ", " .. exp2str(exp[i])
end
end
str = str .. " }"
elseif tag == "Invoke" then -- `Invoke{ expr `String{ <string> } expr* }
str = str .. "{ "
str = str .. exp2str(exp[1]) .. ", "
str = str .. exp2str(exp[2])
if exp[3] then
for i=3, #exp do
str = str .. ", " .. exp2str(exp[i])
end
end
str = str .. " }"
elseif tag == "Id" or -- `Id{ <string> }
tag == "Index" then -- `Index{ expr expr }
str = var2str(exp)
else
error("expecting an expression, but got a " .. tag)
end
return str
end
function explist2str (explist)
local l = {}
for k, v in ipairs(explist) do
l[k] = exp2str(v)
end
if #l > 0 then
return "{ " .. table.concat(l, ", ") .. " }"
else
return ""
end
end
function stm2str (stm)
local tag = stm.tag
local str = "`" .. tag
if tag == "Do" then -- `Do{ stat* }
local l = {}
for k, v in ipairs(stm) do
l[k] = stm2str(v)
end
str = str .. "{ " .. table.concat(l, ", ") .. " }"
elseif tag == "Set" then -- `Set{ {lhs+} {expr+} }
str = str .. "{ "
str = str .. varlist2str(stm[1]) .. ", "
str = str .. explist2str(stm[2])
str = str .. " }"
elseif tag == "While" then -- `While{ expr block }
str = str .. "{ "
str = str .. exp2str(stm[1]) .. ", "
str = str .. block2str(stm[2])
str = str .. " }"
elseif tag == "Repeat" then -- `Repeat{ block expr }
str = str .. "{ "
str = str .. block2str(stm[1]) .. ", "
str = str .. exp2str(stm[2])
str = str .. " }"
elseif tag == "If" then -- `If{ (expr block)+ block? }
str = str .. "{ "
local len = #stm
if len % 2 == 0 then
local l = {}
for i=1,len-2,2 do
str = str .. exp2str(stm[i]) .. ", " .. block2str(stm[i+1]) .. ", "
end
str = str .. exp2str(stm[len-1]) .. ", " .. block2str(stm[len])
else
local l = {}
for i=1,len-3,2 do
str = str .. exp2str(stm[i]) .. ", " .. block2str(stm[i+1]) .. ", "
end
str = str .. exp2str(stm[len-2]) .. ", " .. block2str(stm[len-1]) .. ", "
str = str .. block2str(stm[len])
end
str = str .. " }"
elseif tag == "Fornum" then -- `Fornum{ ident expr expr expr? block }
str = str .. "{ "
str = str .. var2str(stm[1]) .. ", "
str = str .. exp2str(stm[2]) .. ", "
str = str .. exp2str(stm[3]) .. ", "
if stm[5] then
str = str .. exp2str(stm[4]) .. ", "
str = str .. block2str(stm[5])
else
str = str .. block2str(stm[4])
end
str = str .. " }"
elseif tag == "Forin" then -- `Forin{ {ident+} {expr+} block }
str = str .. "{ "
str = str .. varlist2str(stm[1]) .. ", "
str = str .. explist2str(stm[2]) .. ", "
str = str .. block2str(stm[3])
str = str .. " }"
elseif tag == "Local" then -- `Local{ {ident+} {expr+}? }
str = str .. "{ "
str = str .. varlist2str(stm[1])
if #stm[2] > 0 then
str = str .. ", " .. explist2str(stm[2])
else
str = str .. ", " .. "{ }"
end
str = str .. " }"
elseif tag == "Localrec" then -- `Localrec{ ident expr }
str = str .. "{ "
str = str .. "{ " .. var2str(stm[1][1]) .. " }, "
str = str .. "{ " .. exp2str(stm[2][1]) .. " }"
str = str .. " }"
elseif tag == "Goto" or -- `Goto{ <string> }
tag == "Label" then -- `Label{ <string> }
str = str .. "{ " .. name2str(stm[1]) .. " }"
elseif tag == "Return" then -- `Return{ <expr>* }
str = str .. explist2str(stm)
elseif tag == "Break" then
elseif tag == "Call" then -- `Call{ expr expr* }
str = str .. "{ "
str = str .. exp2str(stm[1])
if stm[2] then
for i=2, #stm do
str = str .. ", " .. exp2str(stm[i])
end
end
str = str .. " }"
elseif tag == "Invoke" then -- `Invoke{ expr `String{ <string> } expr* }
str = str .. "{ "
str = str .. exp2str(stm[1]) .. ", "
str = str .. exp2str(stm[2])
if stm[3] then
for i=3, #stm do
str = str .. ", " .. exp2str(stm[i])
end
end
str = str .. " }"
else
error("expecting a statement, but got a " .. tag)
end
return str
end
function block2str (block)
local l = {}
for k, v in ipairs(block) do
l[k] = stm2str(v)
end
return "{ " .. table.concat(l, ", ") .. " }"
end
function pp.tostring (t)
assert(type(t) == "table")
return block2str(t)
end
function pp.print (t)
assert(type(t) == "table")
print(pp.tostring(t))
end
function pp.dump (t, i)
if i == nil then i = 0 end
io.write(string.format("{\n"))
io.write(string.format("%s[tag] = %s\n", string.rep(" ", i+2), t.tag or "nil"))
io.write(string.format("%s[pos] = %s\n", string.rep(" ", i+2), t.pos or "nil"))
for k,v in ipairs(t) do
io.write(string.format("%s[%s] = ", string.rep(" ", i+2), tostring(k)))
if type(v) == "table" then
pp.dump(v,i+2)
else
io.write(string.format("%s\n", tostring(v)))
end
end
io.write(string.format("%s}\n", string.rep(" ", i)))
end
return pp

74
lua-parser/scope.lua Normal file
View file

@ -0,0 +1,74 @@
--[[
This module implements functions that handle scoping rules
]]
local scope = {}
function scope.lineno (s, i)
if i == 1 then return 1, 1 end
local l, lastline = 0, ""
s = s:sub(1, i) .. "\n"
for line in s:gmatch("[^\n]*[\n]") do
l = l + 1
lastline = line
end
local c = lastline:len() - 1
return l, c ~= 0 and c or 1
end
function scope.new_scope (env)
if not env.scope then
env.scope = 0
else
env.scope = env.scope + 1
end
local scope = env.scope
env.maxscope = scope
env[scope] = {}
env[scope]["label"] = {}
env[scope]["local"] = {}
env[scope]["goto"] = {}
end
function scope.begin_scope (env)
env.scope = env.scope + 1
end
function scope.end_scope (env)
env.scope = env.scope - 1
end
function scope.new_function (env)
if not env.fscope then
env.fscope = 0
else
env.fscope = env.fscope + 1
end
local fscope = env.fscope
env["function"][fscope] = {}
end
function scope.begin_function (env)
env.fscope = env.fscope + 1
end
function scope.end_function (env)
env.fscope = env.fscope - 1
end
function scope.begin_loop (env)
if not env.loop then
env.loop = 1
else
env.loop = env.loop + 1
end
end
function scope.end_loop (env)
env.loop = env.loop - 1
end
function scope.insideloop (env)
return env.loop and env.loop > 0
end
return scope

394
lua-parser/validator.lua Normal file
View file

@ -0,0 +1,394 @@
--[[
This module impements a validator for the AST
]]
local scope = require "lua-parser.scope"
local lineno = scope.lineno
local new_scope, end_scope = scope.new_scope, scope.end_scope
local new_function, end_function = scope.new_function, scope.end_function
local begin_loop, end_loop = scope.begin_loop, scope.end_loop
local insideloop = scope.insideloop
-- creates an error message for the input string
local function syntaxerror (errorinfo, pos, msg)
local l, c = lineno(errorinfo.subject, pos)
local error_msg = "%s:%d:%d: syntax error, %s"
return string.format(error_msg, errorinfo.filename, l, c, msg)
end
local function exist_label (env, scope, stm)
local l = stm[1]
for s=scope, 0, -1 do
if env[s]["label"][l] then return true end
end
return false
end
local function set_label (env, label, pos)
local scope = env.scope
local l = env[scope]["label"][label]
if not l then
env[scope]["label"][label] = { name = label, pos = pos }
return true
else
local msg = "label '%s' already defined at line %d"
local line = lineno(env.errorinfo.subject, l.pos)
msg = string.format(msg, label, line)
return nil, syntaxerror(env.errorinfo, pos, msg)
end
end
local function set_pending_goto (env, stm)
local scope = env.scope
table.insert(env[scope]["goto"], stm)
return true
end
local function verify_pending_gotos (env)
for s=env.maxscope, 0, -1 do
for k, v in ipairs(env[s]["goto"]) do
if not exist_label(env, s, v) then
local msg = "no visible label '%s' for <goto>"
msg = string.format(msg, v[1])
return nil, syntaxerror(env.errorinfo, v.pos, msg)
end
end
end
return true
end
local function set_vararg (env, is_vararg)
env["function"][env.fscope].is_vararg = is_vararg
end
local traverse_stm, traverse_exp, traverse_var
local traverse_block, traverse_explist, traverse_varlist, traverse_parlist
function traverse_parlist (env, parlist)
local len = #parlist
local is_vararg = false
if len > 0 and parlist[len].tag == "Dots" then
is_vararg = true
end
set_vararg(env, is_vararg)
return true
end
local function traverse_function (env, exp)
new_function(env)
new_scope(env)
local status, msg = traverse_parlist(env, exp[1])
if not status then return status, msg end
status, msg = traverse_block(env, exp[2])
if not status then return status, msg end
end_scope(env)
end_function(env)
return true
end
local function traverse_op (env, exp)
local status, msg = traverse_exp(env, exp[2])
if not status then return status, msg end
if exp[3] then
status, msg = traverse_exp(env, exp[3])
if not status then return status, msg end
end
return true
end
local function traverse_paren (env, exp)
local status, msg = traverse_exp(env, exp[1])
if not status then return status, msg end
return true
end
local function traverse_table (env, fieldlist)
for k, v in ipairs(fieldlist) do
local tag = v.tag
if tag == "Pair" then
local status, msg = traverse_exp(env, v[1])
if not status then return status, msg end
status, msg = traverse_exp(env, v[2])
if not status then return status, msg end
else
local status, msg = traverse_exp(env, v)
if not status then return status, msg end
end
end
return true
end
local function traverse_vararg (env, exp)
if not env["function"][env.fscope].is_vararg then
local msg = "cannot use '...' outside a vararg function"
return nil, syntaxerror(env.errorinfo, exp.pos, msg)
end
return true
end
local function traverse_call (env, call)
local status, msg = traverse_exp(env, call[1])
if not status then return status, msg end
for i=2, #call do
status, msg = traverse_exp(env, call[i])
if not status then return status, msg end
end
return true
end
local function traverse_invoke (env, invoke)
local status, msg = traverse_exp(env, invoke[1])
if not status then return status, msg end
for i=3, #invoke do
status, msg = traverse_exp(env, invoke[i])
if not status then return status, msg end
end
return true
end
local function traverse_assignment (env, stm)
local status, msg = traverse_varlist(env, stm[1])
if not status then return status, msg end
status, msg = traverse_explist(env, stm[2])
if not status then return status, msg end
return true
end
local function traverse_break (env, stm)
if not insideloop(env) then
local msg = "<break> not inside a loop"
return nil, syntaxerror(env.errorinfo, stm.pos, msg)
end
return true
end
local function traverse_forin (env, stm)
begin_loop(env)
new_scope(env)
local status, msg = traverse_explist(env, stm[2])
if not status then return status, msg end
status, msg = traverse_block(env, stm[3])
if not status then return status, msg end
end_scope(env)
end_loop(env)
return true
end
local function traverse_fornum (env, stm)
local status, msg
begin_loop(env)
new_scope(env)
status, msg = traverse_exp(env, stm[2])
if not status then return status, msg end
status, msg = traverse_exp(env, stm[3])
if not status then return status, msg end
if stm[5] then
status, msg = traverse_exp(env, stm[4])
if not status then return status, msg end
status, msg = traverse_block(env, stm[5])
if not status then return status, msg end
else
status, msg = traverse_block(env, stm[4])
if not status then return status, msg end
end
end_scope(env)
end_loop(env)
return true
end
local function traverse_goto (env, stm)
local status, msg = set_pending_goto(env, stm)
if not status then return status, msg end
return true
end
local function traverse_if (env, stm)
local len = #stm
if len % 2 == 0 then
for i=1, len, 2 do
local status, msg = traverse_exp(env, stm[i])
if not status then return status, msg end
status, msg = traverse_block(env, stm[i+1])
if not status then return status, msg end
end
else
for i=1, len-1, 2 do
local status, msg = traverse_exp(env, stm[i])
if not status then return status, msg end
status, msg = traverse_block(env, stm[i+1])
if not status then return status, msg end
end
local status, msg = traverse_block(env, stm[len])
if not status then return status, msg end
end
return true
end
local function traverse_label (env, stm)
local status, msg = set_label(env, stm[1], stm.pos)
if not status then return status, msg end
return true
end
local function traverse_let (env, stm)
local status, msg = traverse_explist(env, stm[2])
if not status then return status, msg end
return true
end
local function traverse_letrec (env, stm)
local status, msg = traverse_exp(env, stm[2][1])
if not status then return status, msg end
return true
end
local function traverse_repeat (env, stm)
begin_loop(env)
local status, msg = traverse_block(env, stm[1])
if not status then return status, msg end
status, msg = traverse_exp(env, stm[2])
if not status then return status, msg end
end_loop(env)
return true
end
local function traverse_return (env, stm)
local status, msg = traverse_explist(env, stm)
if not status then return status, msg end
return true
end
local function traverse_while (env, stm)
begin_loop(env)
local status, msg = traverse_exp(env, stm[1])
if not status then return status, msg end
status, msg = traverse_block(env, stm[2])
if not status then return status, msg end
end_loop(env)
return true
end
function traverse_var (env, var)
local tag = var.tag
if tag == "Id" then -- `Id{ <string> }
return true
elseif tag == "Index" then -- `Index{ expr expr }
local status, msg = traverse_exp(env, var[1])
if not status then return status, msg end
status, msg = traverse_exp(env, var[2])
if not status then return status, msg end
return true
else
error("expecting a variable, but got a " .. tag)
end
end
function traverse_varlist (env, varlist)
for k, v in ipairs(varlist) do
local status, msg = traverse_var(env, v)
if not status then return status, msg end
end
return true
end
function traverse_exp (env, exp)
local tag = exp.tag
if tag == "Nil" or
tag == "Boolean" or -- `Boolean{ <boolean> }
tag == "Number" or -- `Number{ <number> }
tag == "String" then -- `String{ <string> }
return true
elseif tag == "Dots" then
return traverse_vararg(env, exp)
elseif tag == "Function" then -- `Function{ { `Id{ <string> }* `Dots? } block }
return traverse_function(env, exp)
elseif tag == "Table" then -- `Table{ ( `Pair{ expr expr } | expr )* }
return traverse_table(env, exp)
elseif tag == "Op" then -- `Op{ opid expr expr? }
return traverse_op(env, exp)
elseif tag == "Paren" then -- `Paren{ expr }
return traverse_paren(env, exp)
elseif tag == "Call" then -- `Call{ expr expr* }
return traverse_call(env, exp)
elseif tag == "Invoke" then -- `Invoke{ expr `String{ <string> } expr* }
return traverse_invoke(env, exp)
elseif tag == "Id" or -- `Id{ <string> }
tag == "Index" then -- `Index{ expr expr }
return traverse_var(env, exp)
else
error("expecting an expression, but got a " .. tag)
end
end
function traverse_explist (env, explist)
for k, v in ipairs(explist) do
local status, msg = traverse_exp(env, v)
if not status then return status, msg end
end
return true
end
function traverse_stm (env, stm)
local tag = stm.tag
if tag == "Do" then -- `Do{ stat* }
return traverse_block(env, stm)
elseif tag == "Set" then -- `Set{ {lhs+} {expr+} }
return traverse_assignment(env, stm)
elseif tag == "While" then -- `While{ expr block }
return traverse_while(env, stm)
elseif tag == "Repeat" then -- `Repeat{ block expr }
return traverse_repeat(env, stm)
elseif tag == "If" then -- `If{ (expr block)+ block? }
return traverse_if(env, stm)
elseif tag == "Fornum" then -- `Fornum{ ident expr expr expr? block }
return traverse_fornum(env, stm)
elseif tag == "Forin" then -- `Forin{ {ident+} {expr+} block }
return traverse_forin(env, stm)
elseif tag == "Local" then -- `Local{ {ident+} {expr+}? }
return traverse_let(env, stm)
elseif tag == "Localrec" then -- `Localrec{ ident expr }
return traverse_letrec(env, stm)
elseif tag == "Goto" then -- `Goto{ <string> }
return traverse_goto(env, stm)
elseif tag == "Label" then -- `Label{ <string> }
return traverse_label(env, stm)
elseif tag == "Return" then -- `Return{ <expr>* }
return traverse_return(env, stm)
elseif tag == "Break" then
return traverse_break(env, stm)
elseif tag == "Call" then -- `Call{ expr expr* }
return traverse_call(env, stm)
elseif tag == "Invoke" then -- `Invoke{ expr `String{ <string> } expr* }
return traverse_invoke(env, stm)
else
error("expecting a statement, but got a " .. tag)
end
end
function traverse_block (env, block)
local l = {}
new_scope(env)
for k, v in ipairs(block) do
local status, msg = traverse_stm(env, v)
if not status then return status, msg end
end
end_scope(env)
return true
end
local function traverse (ast, errorinfo)
assert(type(ast) == "table")
assert(type(errorinfo) == "table")
local env = { errorinfo = errorinfo, ["function"] = {} }
new_function(env)
set_vararg(env, true)
local status, msg = traverse_block(env, ast)
if not status then return status, msg end
end_function(env)
status, msg = verify_pending_gotos(env)
if not status then return status, msg end
return ast
end
return { validate = traverse, syntaxerror = syntaxerror }

View file

@ -2,7 +2,7 @@ print("========================")
print("|| CANDRAN TESTS ||") print("|| CANDRAN TESTS ||")
print("========================") print("========================")
local candran = dofile(arg[1] or "../build/candran.lua") local candran = dofile(arg[1] or "../candran.lua")
-- test helper -- test helper
local results = {} -- tests result local results = {} -- tests result
@ -19,7 +19,7 @@ local function test(name, candranCode, result, args)
end end
-- load code -- load code
local success, func = pcall(load, code) local success, func = pcall(loadstring or load, code)
if not success then if not success then
self.result = "error" self.result = "error"
self.message = "error while loading code :\n"..func self.message = "error while loading code :\n"..func
@ -61,21 +61,21 @@ test("preprocessor condition", [[
#end #end
]], true) ]], true)
test("preprocessor args table", [[ test("preprocessor args table", [[
#if not (args and args.foo == "sky") then #if not foo == "sky" then
# error("Invalid foo argument") # error("Invalid foo argument")
#end #end
return true return true
]], true, { foo = "sky" }) ]], true, { foo = "sky" })
test("preprocessor print function", [[ test("preprocessor write function", [[
#print("local a = true") #write("local a = true")
return a return a
]], true) ]], true)
test("preprocessor import function", [[ test("preprocessor import function", [[
#import("toInclude") #import("toInclude")
return toInclude return toInclude
]], 5) ]], 5)
test("preprocessor include function", "a = [[\n#include('toInclude.lua')\n]]\nreturn a", test("preprocessor include function", "a = [[\n#include('toInclude.lua')\n]]\nreturn a",
"local a = 5\nreturn a\n") "local a = 5\nreturn a\n\n")
test("+=", [[ test("+=", [[
local a = 5 local a = 5
@ -112,85 +112,12 @@ local a = "hello"
a ..= " world" a ..= " world"
return a return a
]], "hello world") ]], "hello world")
test("default parameters", [[
test("decorator", [[ local function test(hey, def="re", no, foo=("bar"):gsub("bar", "batru"))
local a = function(func) return def..foo
local wrapper = function(...)
local b = func(...)
return b + 5
end
return wrapper
end end
@a return test(78, "SANDWICH", true)
function c(nb) ]], "SANDWICHbatru")
return nb^2
end
return c(5)
]], 30)
test("decorator with arguments", [[
local a = function(add)
local b = function(func)
local wrapper = function(...)
local c = func(...)
return c + add
end
return wrapper
end
return b
end
@a(10)
function d(nb)
return nb^2
end
return d(5)
]], 35)
test("multiple decorators", [[
local a = function(func)
local wrapper = function(...)
local b = func(...)
return b + 5
end
return wrapper
end
local c = function(func)
local wrapper = function(...)
local d = func(...)
return d * 2
end
return wrapper
end
@a
@c
function e(nb)
return nb^2
end
return e(5)
]], 55)
test("multiple decorators with arguments", [[
local a = function(func)
local wrapper = function(...)
local b = func(...)
return b + 5
end
return wrapper
end
local c = function(mul)
local d = function(func)
local wrapper = function(...)
local e = func(...)
return e * mul
end
return wrapper
end
return d
end
@a
@c(3)
function f(nb)
return nb^2
end
return f(5)
]], 80)
-- results -- results
print("=====================") print("=====================")
@ -215,4 +142,4 @@ end
for name, count in pairs(resultCounter) do for name, count in pairs(resultCounter) do
print(count.." "..name.." (" .. math.floor((count / testCounter * 100)*100)/100 .. "%)") print(count.." "..name.." (" .. math.floor((count / testCounter * 100)*100)/100 .. "%)")
end end
print(testCounter.." total") print(testCounter.." total")

View file

@ -1,2 +1,2 @@
local a = 5 local a = 5
return a return a

26
util.can Normal file
View file

@ -0,0 +1,26 @@
local util = {}
function util.search(modpath, exts={"can", "lua"})
for _, ext in ipairs(exts) do
for path in package.path:gmatch("[^;]+") do
local fpath = path:gsub("%.lua", "."..ext):gsub("%?", (modpath:gsub("%.", "/")))
local f = io.open(fpath)
if f then
f:close()
return fpath
end
end
end
end
function util.loadenv(str, name, env)
if _VERSION == "Lua 5.1" then
local fn, err = loadstring(str, name)
if not fn then return fn, err end
return env ~= nil and setfenv(fn, env) or fn
else
return load(str, name, nil, env)
end
end
return util