mirror of
https://github.com/Reuh/candran.git
synced 2026-02-04 10:18:38 +00:00
Compare commits
No commits in common. "3dc6ad3cf5c20001e1ef61d923cb95617356a557" and "8d9a79c47dc7ad33bb2faaac6ff9df62e0a19cec" have entirely different histories.
3dc6ad3cf5
...
8d9a79c47d
11 changed files with 7136 additions and 8663 deletions
57
README.md
57
README.md
|
|
@ -1,6 +1,6 @@
|
|||
Candran
|
||||
=======
|
||||
Candran is a dialect of the [Lua 5.5](http://www.lua.org) programming language which compiles to Lua 5.5, Lua 5.4, Lua 5.3, Lua 5.2, LuaJIT and Lua 5.1 compatible code. It adds several useful syntax additions which aims to make Lua faster and easier to write, and a simple preprocessor.
|
||||
Candran is a dialect of the [Lua 5.4](http://www.lua.org) programming language which compiles to Lua 5.4, Lua 5.3, Lua 5.2, LuaJIT and Lua 5.1 compatible code. It adds several useful syntax additions which aims to make Lua faster and easier to write, and a simple preprocessor.
|
||||
|
||||
Unlike Moonscript, Candran tries to stay close to the Lua syntax, and existing Lua code should be able to run on Candran unmodified.
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ end)
|
|||
|
||||
a.child?:method?() -- safe navigation operator
|
||||
|
||||
local {hey, method} = a -- destructuring assignment
|
||||
local {hey, method} = a -- destructuring assignement
|
||||
|
||||
local odd = [ -- table comprehension
|
||||
for i=1, 10 do
|
||||
|
|
@ -66,9 +66,9 @@ local count = [for i=1,10 i] -- single line statements
|
|||
|
||||
local a = if condition then "one" else "two" end -- statement as expressions
|
||||
|
||||
print("Hello %s":format("world")) -- methods calls on strings (and tables) literals without enclosing parentheses
|
||||
print("Hello %s":format("world")) -- methods calls on strings (and tables) litterals without enclosing parentheses
|
||||
|
||||
if f, err = io.open("data") then -- if condition with assignments
|
||||
if f, err = io.open("data") then -- if condition with assignements
|
||||
thing.process(f)
|
||||
else
|
||||
error("can't open data: "..err)
|
||||
|
|
@ -76,7 +76,7 @@ end
|
|||
|
||||
````
|
||||
|
||||
**Current status**: Candran is stable ; I use it heavily used in several of my personal projects and maintain it. If there's no activity on the repo, it means there's no bug or new Lua version that requires an update.
|
||||
**Current status**: Candran is heavily used in several of my personal projects and works as expected.
|
||||
|
||||
Candran is released under the MIT License (see ```LICENSE``` for details).
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ Most editors should be able to use their existing Lua support for Candran code.
|
|||
* [sublime-candran](https://github.com/Reuh/sublime-candran) support the full Candran syntax
|
||||
* [SublimeLinter-cancheck-contrib](https://github.com/Reuh/SublimeLinter-contrib-cancheck) SublimeLinter plugin for Candran using ```cancheck```
|
||||
* [SublimeLinter-candran-contrib](https://github.com/Reuh/SublimeLinter-contrib-candran) SublimeLinter plugin for Candran using ```canc -parse``` (only checks for syntaxic errors, no linting)
|
||||
* **VS Code**: [vscode-candran](https://github.com/Reuh/vscode-candran) basic support for the full Candran syntax
|
||||
* **VS Code**: [vscode-candran](https://github.com/Reuh/vscode-candran) basic support for the Candran syntax
|
||||
* **Atom**: [language-candran](https://atom.io/packages/language-candran) support the full Candran syntax
|
||||
|
||||
For linting, if your editor support [luacheck](https://github.com/luarocks/luacheck), you should be able to replace it with ```cancheck``` (in this repository ```bin/cancheck```, or installed automatically if Candran was installed using LuaRocks), which is a wrapper around luacheck that monkey-patch it to support Candran.
|
||||
|
|
@ -105,7 +105,7 @@ For linting, if your editor support [luacheck](https://github.com/luarocks/luach
|
|||
The language
|
||||
------------
|
||||
### Syntax additions
|
||||
After the [preprocessor](#preprocessor) is run the Candran code is compiled to Lua. Candran code adds the folowing syntax to standard Lua syntax:
|
||||
After the [preprocessor](#preprocessor) is run the Candran code is compiled to Lua. Candran code adds the folowing syntax to Lua 5.4 syntax:
|
||||
|
||||
##### Assignment operators
|
||||
* ````var += nb````
|
||||
|
|
@ -125,7 +125,7 @@ After the [preprocessor](#preprocessor) is run the Candran code is compiled to L
|
|||
|
||||
For example, a ````var += nb```` assignment will be compiled into ````var = var + nb````.
|
||||
|
||||
All theses operators can also be put right of the assignment operator, in which case ```var =+ nb``` will be compiled into ```var = nb + var```.
|
||||
All theses operators can also be put right of the assigment operator, in which case ```var =+ nb``` will be compiled into ```var = nb + var```.
|
||||
|
||||
Right and left operator can be used at the same time.
|
||||
|
||||
|
|
@ -274,7 +274,7 @@ Values returned by the function will be inserted in the generated table in the o
|
|||
|
||||
The table generation function also have access to the `self` variable (and its alias `@`), which is the table which is being created, so you can set any of the table's field.
|
||||
|
||||
##### Destructuring assignment
|
||||
##### Destructuring assignement
|
||||
```lua
|
||||
t = { x = 1, y = 2, z = 3 }
|
||||
|
||||
|
|
@ -284,21 +284,21 @@ t = { x = 1, y = 2, z = 3 }
|
|||
|
||||
{["x"] = o} = t -- o = t["x"]
|
||||
|
||||
-- Also works with local, let, for ... in, if with assignment, +=, etc.
|
||||
-- Also works with local, let, for ... in, if with assignement, +=, etc.
|
||||
local {x, y} = t
|
||||
let {x, y} = t
|
||||
for i, {x, y} in ipairs{t} do end
|
||||
if {x, y} = t then end
|
||||
{x} += t -- x = x + t.x
|
||||
|
||||
-- Works as expected with multiple assignment.
|
||||
-- Works as expected with multiple assignement.
|
||||
a, {x, y, z}, b = 1, t, 2
|
||||
|
||||
```
|
||||
|
||||
Destruturing assignment allows to quickly extract fields from a table into a variable.
|
||||
Destruturing assignement allows to quickly extract fields from a table into a variable.
|
||||
|
||||
This is done by replacing the variable name in any assignment with a table literal, where every item is the name of the field and assigned variable. It is possible to use a different field name than the variable name by naming the table item (`fieldName = var` or `[fieldExpression] = var`).
|
||||
This is done by replacing the variable name in any assignement with a table literal, where every item is the name of the field and assigned variable. It is possible to use a different field name than the variable name by naming the table item (`fieldName = var` or `[fieldExpression] = var`).
|
||||
|
||||
|
||||
##### Safe navigation operators
|
||||
|
|
@ -329,7 +329,7 @@ print(a?()) -- nil if a is nil, other normal behaviour
|
|||
|
||||
Some operators can be prefixed by a `?` to turn into a safe version of the operator: if the base value if `nil`, the normal behaviour of the operator will be skipped and nil will be returned; otherwise, the operator run as usual. Is available safe dot index `?.`, safe array index `?[...]`, safe method stub `?:` and safe function call `?(...)`.
|
||||
|
||||
##### If and while with assignment in the condition
|
||||
##### If and while with assignement in the condition
|
||||
```lua
|
||||
if f, err = io.open("somefile") then -- condition if verified if f is a truthy value (not nil or false)
|
||||
-- do something with f
|
||||
|
|
@ -344,7 +344,7 @@ else
|
|||
end
|
||||
-- f, err, f2 and err2 are now out of scope
|
||||
|
||||
if (value = list[index = 2]) and yes = true then -- several assignments can be performed, anywhere in the expression; index is defined before value, yes is defined after these two. The condition is verified if both value and yes are thruthy.
|
||||
if (value = list[index = 2]) and yes = true then -- several assignements can be performed, anywhere in the expression; index is defined before value, yes is defined after these two. The condition is verified if both value and yes are thruthy.
|
||||
print(index, value)
|
||||
end
|
||||
|
||||
|
|
@ -353,13 +353,13 @@ while line = io.read() do
|
|||
print(line)
|
||||
end
|
||||
|
||||
-- The assignment have the same priority as regular assignments, i.e., the lowest.
|
||||
-- The assignement have the same priority as regular assignements, i.e., the lowest.
|
||||
if a = 1 and 2 then -- will be read as a = (1 and 2)
|
||||
elseif (a = 1) and 2 then -- will be read as (a = 1) and 2
|
||||
end
|
||||
```
|
||||
|
||||
Assignments can be used in the condition of if, elseif and while statements. Several variables can be assigned; only the first will be tested in the condition, for each assignment. The assigned variables will be in scope the duration of the block; for if statements, they will also be in scope for the following elseif(s) and else.
|
||||
Assignements can be used in the condition of if, elseif and while statements. Several variables can be assigned; only the first will be tested in the condition, for each assignement. The assigned variables will be in scope the duration of the block; for if statements, they will also be in scope for the following elseif(s) and else.
|
||||
|
||||
For while statements, the assigned expression will be reevaluated at each iteration.
|
||||
|
||||
|
|
@ -499,22 +499,19 @@ You can disable these built-in macros using the `builtInMacros` compiler option.
|
|||
|
||||
Compile targets
|
||||
---------------
|
||||
Candran is based on the Lua 5.5 syntax, but can be compiled to Lua 5.5, Lua 5.4, Lua 5.3, Lua 5.2, LuaJIT, and Lua 5.1 compatible code.
|
||||
Candran is based on the Lua 5.4 syntax, but can be compiled to Lua 5.4, Lua 5.3, Lua 5.2, LuaJIT, and Lua 5.1 compatible code.
|
||||
|
||||
To chose a compile target, set the ```target``` option to ```lua55```, ```lua54```, ```lua53```, ```lua52```, ```luajit```, or ```lua51``` in the option table when using the library or the command line tools. Candran will try to detect the currently used Lua version and use it as the default target.
|
||||
To chose a compile target, set the ```target``` option to ```lua54```, ```lua53```, ```lua52```, ```luajit```, or ```lua51``` in the option table when using the library or the command line tools. Candran will try to detect the currently used Lua version and use it as the default target.
|
||||
|
||||
Candran will try to translate Lua 5.5 syntax into something usable with the current target if possible. Some syntax features are only supported by specific targets:
|
||||
Candran will try to translate Lua 5.4 syntax into something usable with the current target if possible. Here is what is currently supported:
|
||||
|
||||
| Lua version | Candran target | Bitwise operators | Goto/Labels | Variable attributes | Global keyword |
|
||||
| Lua version | Candran target | Integer division operator // | Bitwise operators | Goto/Labels | Variable attributes |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Lua 5.5 | lua55 | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Lua 5.4 | lua54 | :white_check_mark: | :white_check_mark: | :white_check_mark: | X |
|
||||
| Lua 5.3 | lua53 | :white_check_mark: | :white_check_mark: | X | X |
|
||||
| Lua 5.2 | lua52 | :white_check_mark: (32bit) | :white_check_mark: | X | X |
|
||||
| LuaJIT | luajit | :white_check_mark: (32bit) | :white_check_mark: | X | X |
|
||||
| Lua 5.1 | lua51 | :white_check_mark: if LuaJIT bit library is available (32bit) | X | X | X |
|
||||
|
||||
All other Lua 5.5 syntax features not listed in this table, like the integer division operator `//`, named varargs, are supported by all targets.
|
||||
| Lua 5.4 | lua54 | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Lua 5.3 | lua53 | :white_check_mark: | :white_check_mark: | :white_check_mark: | X |
|
||||
| Lua 5.2 | lua52 | :white_check_mark: | :white_check_mark: (32bit) | :white_check_mark: | X |
|
||||
| LuaJIT | luajit | :white_check_mark: | :white_check_mark: (32bit) | :white_check_mark: | X |
|
||||
| Lua 5.1 | lua51 | :white_check_mark: | :white_check_mark: if LuaJIT bit library is available (32bit) | X | X |
|
||||
|
||||
**Please note** that Candran only translates syntax, and will not try to do anything about changes in the Lua standard library (for example, the new utf8 module). If you need this, you should be able to use [lua-compat-5.3](https://github.com/keplerproject/lua-compat-5.3) along with Candran.
|
||||
|
||||
|
|
@ -688,7 +685,7 @@ at the top of your main Lua file. If a Candran file is found when you call ```re
|
|||
You can give arbitrary options to the compiler and preprocessor, but Candran already provide and uses these with their associated default values:
|
||||
|
||||
```lua
|
||||
target = "lua55" -- compiler target. "lua55", "lua54", "lua53", "lua52", "luajit" or "lua51" (default is automatically selected based on the Lua version used).
|
||||
target = "lua53" -- compiler target. "lua54", "lua53", "lua52", "luajit" or "lua51" (default is automatically selected based on the Lua version used).
|
||||
indentation = "" -- character(s) used for indentation in the compiled file.
|
||||
newline = "\n" -- character(s) used for newlines in the compiled file.
|
||||
variablePrefix = "__CAN_" -- Prefix used when Candran needs to set a local variable to provide some functionality (example: to load LuaJIT's bit lib when using bitwise operators).
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
local candran = {
|
||||
VERSION = "1.1.0"
|
||||
VERSION = "1.0.0"
|
||||
}
|
||||
package.loaded["candran"] = candran
|
||||
|
||||
#import("candran.util")
|
||||
#import("candran.serpent")
|
||||
|
||||
#import("compiler.lua55")
|
||||
#import("compiler.lua54")
|
||||
#import("compiler.lua53")
|
||||
#import("compiler.lua52")
|
||||
|
|
@ -22,7 +21,7 @@ local unpack = unpack or table.unpack
|
|||
|
||||
--- Default options.
|
||||
candran.default = {
|
||||
target = "lua55",
|
||||
target = "lua54",
|
||||
indentation = "",
|
||||
newline = "\n",
|
||||
variablePrefix = "__CAN_",
|
||||
|
|
@ -45,8 +44,6 @@ elseif _VERSION == "Lua 5.2" then
|
|||
candran.default.target = "lua52"
|
||||
elseif _VERSION == "Lua 5.3" then
|
||||
candran.default.target = "lua53"
|
||||
elseif _VERSION == "Lua 5.4" then
|
||||
candran.default.target = "lua54"
|
||||
end
|
||||
|
||||
--- Run the preprocessor
|
||||
|
|
|
|||
13614
candran.lua
13614
candran.lua
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
--[[
|
||||
This module implements a parser for Lua 5.5 with LPeg,
|
||||
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
|
||||
|
|
@ -15,11 +15,8 @@ stat:
|
|||
| `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{ {attributeident+} {expr+}? } -- local i1, i2... = e1, e2...
|
||||
| `Global{ {attributeident+} {expr+}? } -- global i1, i2... = e1, e2...
|
||||
| `Let{ {ident+} {expr+}? } -- let i1, i2... = e1, e2...
|
||||
| `Localrec{ {ident} {expr} } -- only used for 'local function'
|
||||
| `Globalrec{ {ident} {expr} } -- only used for 'global function'
|
||||
| `GlobalAll{ attribute? } -- only used for 'global *'
|
||||
| `Goto{ <string> } -- goto str
|
||||
| `Label{ <string> } -- ::str::
|
||||
| `Return{ <expr*> } -- return e1, e2...
|
||||
|
|
@ -34,7 +31,7 @@ expr:
|
|||
| `Boolean{ <boolean> }
|
||||
| `Number{ <string> } -- we don't use convert to number to avoid losing precision when tostring()-ing it later
|
||||
| `String{ <string> }
|
||||
| `Function{ { ( `ParPair{ Id expr } | `Id{ <string> } )* `ParDots? } block }
|
||||
| `Function{ { ( `ParPair{ Id expr } | `Id{ <string> } )* `Dots? } block }
|
||||
| `Table{ ( `Pair{ expr expr } | expr )* }
|
||||
| `Op{ opid expr expr? }
|
||||
| `Paren{ expr } -- significant to cut multiple values returns
|
||||
|
|
@ -115,7 +112,6 @@ local labels = {
|
|||
{ "ErrEListFor", "expected one or more expressions after 'in'" },
|
||||
{ "ErrDoFor", "expected 'do' after the range of the for loop" },
|
||||
|
||||
{ "ErrDefGlobal", "expected a function definition or assignment after global" },
|
||||
{ "ErrDefLocal", "expected a function definition or assignment after local" },
|
||||
{ "ErrDefLet", "expected an assignment after let" },
|
||||
{ "ErrDefClose", "expected an assignment after close" },
|
||||
|
|
@ -344,10 +340,10 @@ end
|
|||
local function searchEndRec (block, isRecCall) -- recursively search potential "end" keyword wrongly consumed by a short anonymous function on stat end (yeah, too late to change the syntax to something easier to parse)
|
||||
for i, stat in ipairs(block) do
|
||||
-- Non recursive statements
|
||||
if stat.tag == "Set" or stat.tag == "Push" or stat.tag == "Return" or stat.tag == "Local" or stat.tag == "Let" or stat.tag == "Localrec" or stat.tag == "Global" or stat.tag == "Globalrec" then
|
||||
if stat.tag == "Set" or stat.tag == "Push" or stat.tag == "Return" or stat.tag == "Local" or stat.tag == "Let" or stat.tag == "Localrec" then
|
||||
local exprlist
|
||||
|
||||
if stat.tag == "Set" or stat.tag == "Local" or stat.tag == "Let" or stat.tag == "Localrec" or stat.tag == "Global" or stat.tag == "Globalrec" then
|
||||
if stat.tag == "Set" or stat.tag == "Local" or stat.tag == "Let" or stat.tag == "Localrec" then
|
||||
exprlist = stat[#stat]
|
||||
elseif stat.tag == "Push" or stat.tag == "Return" then
|
||||
exprlist = stat
|
||||
|
|
@ -519,7 +515,7 @@ local G = { V"Lua",
|
|||
|
||||
Block = tagC("Block", (V"Stat" + -V"BlockEnd" * throw("InvalidStat"))^0 * ((V"RetStat" + V"ImplicitPushStat") * sym(";")^-1)^-1);
|
||||
Stat = V"IfStat" + V"DoStat" + V"WhileStat" + V"RepeatStat" + V"ForStat"
|
||||
+ V"LocalStat" + V"GlobalStat" + V"FuncStat" + V"BreakStat" + V"LabelStat" + V"GoToStat"
|
||||
+ V"LocalStat" + V"FuncStat" + V"BreakStat" + V"LabelStat" + V"GoToStat"
|
||||
+ V"LetStat" + V"ConstStat" + V"CloseStat"
|
||||
+ V"FuncCall" + V"Assignment"
|
||||
+ V"ContinueStat" + V"PushStat"
|
||||
|
|
@ -546,12 +542,6 @@ local G = { V"Lua",
|
|||
ForIn = tagC("Forin", V"DestructuringNameList" * expect(kw("in"), "InFor") * expect(V"ExprList", "EListFor") * V"ForBody");
|
||||
ForBody = expectBlockOrSingleStatWithStartEnd(kw("do"), "DoFor", "EndFor");
|
||||
|
||||
GlobalStat = kw("global") * expect(V"GlobalFunc" + V"GlobalAssign", "DefGlobal");
|
||||
GlobalFunc = tagC("Globalrec", kw("function") * expect(V"Id", "NameLFunc") * V"FuncBody") / fixFuncStat;
|
||||
GlobalAssign = tagC("Global", V"AttributeNameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc())))
|
||||
+ tagC("Global", V"DestructuringNameList" * sym("=") * expect(V"ExprList", "EListLAssign"))
|
||||
+ tagC("GlobalAll", V "Attribute"^-1 * sym("*")),
|
||||
|
||||
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"AttributeNameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc())))
|
||||
|
|
@ -561,9 +551,9 @@ local G = { V"Lua",
|
|||
LetAssign = tagC("Let", V"NameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc())))
|
||||
+ tagC("Let", V"DestructuringNameList" * sym("=") * expect(V"ExprList", "EListLAssign"));
|
||||
|
||||
ConstStat = kw("const") * expect(V "LocalAssignNoAttribute" / setAttribute("const"), "DefConst"),
|
||||
CloseStat = kw("close") * expect(V "LocalAssignNoAttribute" / setAttribute("close"), "DefClose"),
|
||||
LocalAssignNoAttribute = tagC("Local", V"NameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc())))
|
||||
ConstStat = kw("const") * expect(V"AttributeAssign" / setAttribute("const"), "DefConst");
|
||||
CloseStat = kw("close") * expect(V"AttributeAssign" / setAttribute("close"), "DefClose");
|
||||
AttributeAssign = tagC("Local", V"NameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc())))
|
||||
+ tagC("Local", V"DestructuringNameList" * sym("=") * expect(V"ExprList", "EListLAssign"));
|
||||
|
||||
Assignment = tagC("Set", (V"VarList" + V"DestructuringNameList") * V"BinOp"^-1 * (P"=" / "=") * ((V"BinOp" - P"-") + #(P"-" * V"Space") * V"BinOp")^-1 * V"Skip" * expect(V"ExprList", "EListAssign"));
|
||||
|
|
@ -573,8 +563,8 @@ local G = { V"Lua",
|
|||
* (sym(":") * expect(V"StrId", "NameFunc2"))^-1 / markMethod;
|
||||
FuncBody = tagC("Function", V"FuncParams" * expectBlockWithEnd("EndFunc"));
|
||||
FuncParams = expect(sym("("), "OParenPList") * V"ParList" * expect(sym(")"), "CParenPList");
|
||||
ParList = V"NamedParList" * (sym(",") * expect(tagC("ParDots", sym("...") * V"Id"^-1), "ParList"))^-1 / addDots
|
||||
+ Ct(tagC("ParDots", sym("...") * V"Id"^-1))
|
||||
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
|
||||
|
||||
ShortFuncDef = tagC("Function", V"ShortFuncParams" * maybeBlockWithEnd()) / fixShortFunc;
|
||||
|
|
@ -595,9 +585,8 @@ local G = { V"Lua",
|
|||
ImplicitPushStat = tagC("Push", commaSep(V"Expr", "RetList")) / markImplicit;
|
||||
|
||||
NameList = tagC("NameList", commaSep(V"Id"));
|
||||
DestructuringNameList = tagC("NameList", commaSep(V"DestructuringId"));
|
||||
AttributeNameList = tagC("AttributeNameList", commaSep(V"AttributeId"))
|
||||
+ tagC("PrefixedAttributeNameList", V"Attribute" * commaSep(V"AttributeId"));
|
||||
DestructuringNameList = tagC("NameList", commaSep(V"DestructuringId")),
|
||||
AttributeNameList = tagC("AttributeNameList", commaSep(V"AttributeId"));
|
||||
VarList = tagC("VarList", commaSep(V"VarExpr"));
|
||||
ExprList = tagC("ExpList", commaSep(V"Expr", "ExprList"));
|
||||
|
||||
|
|
@ -687,7 +676,7 @@ local G = { V"Lua",
|
|||
Reserved = V"Keywords" * -V"IdRest";
|
||||
Keywords = P"and" + "break" + "do" + "elseif" + "else" + "end"
|
||||
+ "false" + "for" + "function" + "goto" + "if" + "in"
|
||||
+ "local" + "global" + "nil" + "not" + "or" + "repeat" + "return"
|
||||
+ "local" + "nil" + "not" + "or" + "repeat" + "return"
|
||||
+ "then" + "true" + "until" + "while";
|
||||
Ident = V"IdStart" * V"IdRest"^0;
|
||||
IdStart = alpha + P"_";
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ function stm2str (stm)
|
|||
str = str .. explist2str(stm[2]) .. ", "
|
||||
str = str .. block2str(stm[3])
|
||||
str = str .. " }"
|
||||
elseif tag == "Local" or tag == "Global" then -- `Local|Global{ {ident+} {expr+}? }
|
||||
elseif tag == "Local" then -- `Local{ {ident+} {expr+}? }
|
||||
str = str .. "{ "
|
||||
str = str .. varlist2str(stm[1])
|
||||
if #stm[2] > 0 then
|
||||
|
|
@ -254,7 +254,7 @@ function stm2str (stm)
|
|||
str = str .. ", " .. "{ }"
|
||||
end
|
||||
str = str .. " }"
|
||||
elseif tag == "Localrec" or tag == "Globalrec" then -- `Localrec|Globalrec{ ident expr }
|
||||
elseif tag == "Localrec" then -- `Localrec{ ident expr }
|
||||
str = str .. "{ "
|
||||
str = str .. "{ " .. var2str(stm[1][1]) .. " }, "
|
||||
str = str .. "{ " .. exp2str(stm[2][1]) .. " }"
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
--[[
|
||||
This module impements a validator for the AST.
|
||||
|
||||
TODO/Checks that could be added in the future:
|
||||
- Check if attributes are valid in declarations: in AttributeNameList, PrefixedAttributeNameList, and GlobalAll
|
||||
- Check global variable declarations
|
||||
This module impements a validator for the AST
|
||||
]]
|
||||
local scope = require "candran.can-parser.scope"
|
||||
|
||||
|
|
@ -71,7 +67,7 @@ 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 == "ParDots" then
|
||||
if len > 0 and parlist[len].tag == "Dots" then
|
||||
is_vararg = true
|
||||
end
|
||||
set_vararg(env, is_vararg)
|
||||
|
|
@ -348,7 +344,7 @@ function traverse_exp (env, exp)
|
|||
return true
|
||||
elseif tag == "Dots" then
|
||||
return traverse_vararg(env, exp)
|
||||
elseif tag == "Function" then -- `Function{ { `Id{ <string> }* `ParDots? } block }
|
||||
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)
|
||||
|
|
@ -399,14 +395,10 @@ function traverse_stm (env, stm)
|
|||
elseif tag == "Forin" then -- `Forin{ {ident+} {expr+} block }
|
||||
return traverse_forin(env, stm)
|
||||
elseif tag == "Local" or -- `Local{ {ident+} {expr+}? }
|
||||
tag == "Let" or -- `Let{ {ident+} {expr+}? }
|
||||
tag == "Global" then -- `Global{ {ident+} {expr+}? }
|
||||
tag == "Let" then -- `Let{ {ident+} {expr+}? }
|
||||
return traverse_let(env, stm)
|
||||
elseif tag == "Localrec" or -- `Localrec{ ident expr }
|
||||
tag == "Globalrec" then -- `Globalrec{ ident expr }
|
||||
elseif tag == "Localrec" then -- `Localrec{ ident expr }
|
||||
return traverse_letrec(env, stm)
|
||||
elseif tag == "GlobalAll" then -- GlobalAll{ attribute? }
|
||||
return true
|
||||
elseif tag == "Goto" then -- `Goto{ <string> }
|
||||
return traverse_goto(env, stm)
|
||||
elseif tag == "Label" then -- `Label{ <string> }
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@ tags.AttributeId = (t)
|
|||
return t[1]
|
||||
end
|
||||
end
|
||||
tags.PrefixedAttributeNameList = (t)
|
||||
error("target "..targetName.." does not support variable attributes")
|
||||
end
|
||||
|
||||
#placeholder("patch")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,50 +1,898 @@
|
|||
targetName = "Lua 5.4"
|
||||
local util = require("candran.util")
|
||||
|
||||
-- Note: global declaration could be backported to older Lua versions, but would require more work than I personally have use for the feature; but if you have an use-case, open an issue and I'll give it a try.
|
||||
tags.Global = (t)
|
||||
error("target "..targetName.." does not support global variable declaration")
|
||||
end
|
||||
tags.Globalrec = (t)
|
||||
error("target "..targetName.." does not support global variable declaration")
|
||||
end
|
||||
tags.GlobalAll = (t)
|
||||
if #t == 1 then
|
||||
error("target "..targetName.." does not support collective global variable declaration")
|
||||
local targetName = "Lua 5.4"
|
||||
|
||||
local unpack = unpack or table.unpack
|
||||
|
||||
return function(code, ast, options, macros={functions={}, variables={}})
|
||||
--- Line mapping
|
||||
local lastInputPos = 1 -- last token position in the input code
|
||||
local prevLinePos = 1 -- last token position in the previous line of code in the input code
|
||||
local lastSource = options.chunkname or "nil" -- last found code source name (from the original file)
|
||||
local lastLine = 1 -- last found line number (from the original file)
|
||||
|
||||
--- Newline management
|
||||
local indentLevel = 0
|
||||
-- Returns a newline.
|
||||
local function newline()
|
||||
local r = options.newline..string.rep(options.indentation, indentLevel)
|
||||
if options.mapLines then
|
||||
local sub = code:sub(lastInputPos)
|
||||
local source, line = sub:sub(1, sub:find("\n")):match(".*%-%- (.-)%:(%d+)\n")
|
||||
|
||||
if source and line then
|
||||
lastSource = source
|
||||
lastLine = tonumber(line)
|
||||
else
|
||||
return "" -- global * is no-op when used alone
|
||||
for _ in code:sub(prevLinePos, lastInputPos):gmatch("\n") do
|
||||
lastLine += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Named vararg
|
||||
tags._functionParameter.ParDots = (t, decl)
|
||||
if #t == 1 then
|
||||
local id = lua(t[1])
|
||||
prevLinePos = lastInputPos
|
||||
|
||||
r = " -- "..lastSource..":"..lastLine..r
|
||||
end
|
||||
return r
|
||||
end
|
||||
-- Returns a newline and add one level of indentation.
|
||||
local function indent()
|
||||
indentLevel += 1
|
||||
table.insert(decl, "local "..id.." = { ... }")
|
||||
return newline()
|
||||
end
|
||||
-- Returns a newline and remove one level of indentation.
|
||||
local function unindent()
|
||||
indentLevel -= 1
|
||||
return newline()
|
||||
end
|
||||
return "..."
|
||||
end
|
||||
|
||||
-- Prefixed attributes
|
||||
-- PrefixedAttributeNameList{ attribute {AttributeId+} }
|
||||
tags.PrefixedAttributeNameList = (t)
|
||||
local ids = {}
|
||||
for i=2, #t, 1 do
|
||||
if t[i][2] then
|
||||
error("target "..targetName.." does not support combining prefixed and suffixed attributes in variable declaration")
|
||||
--- State stacks
|
||||
-- Used for context-sensitive syntax.
|
||||
local states = {
|
||||
push = {}, -- push stack variable names
|
||||
destructuring = {}, -- list of variable that need to be assigned from a destructure {id = "parent variable", "field1", "field2"...}
|
||||
scope = {}, -- list of variables defined in the current scope
|
||||
macroargs = {} -- currently defined arguemnts from a macro function
|
||||
}
|
||||
-- Push a new value on top of the stack "name". Returns an empty string for chaining.
|
||||
local function push(name, state)
|
||||
table.insert(states[name], state)
|
||||
return ""
|
||||
end
|
||||
-- Remove the value on top of the stack "name". Returns an empty string for chaining.
|
||||
local function pop(name)
|
||||
table.remove(states[name])
|
||||
return ""
|
||||
end
|
||||
-- Set the value on top of the stack "name". Returns an empty string for chaining.
|
||||
local function set(name, state)
|
||||
states[name][#states[name]] = state
|
||||
return ""
|
||||
end
|
||||
-- Returns the value on top of the stack "name".
|
||||
local function peek(name)
|
||||
return states[name][#states[name]]
|
||||
end
|
||||
|
||||
--- Variable management
|
||||
-- Returns the prefixed variable name.
|
||||
local function var(name)
|
||||
return options.variablePrefix..name
|
||||
end
|
||||
|
||||
-- Returns the prefixed temporary variable name.
|
||||
local function tmp()
|
||||
local scope = peek("scope")
|
||||
local var = "%s_%s":format(options.variablePrefix, #scope)
|
||||
table.insert(scope, var)
|
||||
return var
|
||||
end
|
||||
|
||||
-- indicate if currently processing a macro, so it cannot be applied recursively
|
||||
local nomacro = { variables = {}, functions = {} }
|
||||
|
||||
--- Module management
|
||||
local required = {} -- { ["full require expression"] = true, ... }
|
||||
local requireStr = ""
|
||||
-- Add the module "mod" to the list of modules to require, and load its field "field" (or the whole module if nil) into the variable "name".
|
||||
local function addRequire(mod, name, field)
|
||||
local req = "require(%q)%s":format(mod, field and "."..field or "")
|
||||
if not required[req] then
|
||||
requireStr ..= "local %s = %s%s":format(var(name), req, options.newline)
|
||||
required[req] = true
|
||||
end
|
||||
end
|
||||
|
||||
--- AST traversal helpers
|
||||
local loop = { "While", "Repeat", "Fornum", "Forin", "WhileExpr", "RepeatExpr", "FornumExpr", "ForinExpr" } -- loops tags (can contain continue)
|
||||
local func = { "Function", "TableCompr", "DoExpr", "WhileExpr", "RepeatExpr", "IfExpr", "FornumExpr", "ForinExpr" } -- function scope tags (can contain push)
|
||||
|
||||
-- Returns the first node or subnode from the list "list" which tag is in the list "tags", or nil if there were none.
|
||||
-- Won't recursively follow nodes which have a tag in "nofollow".
|
||||
local function any(list, tags, nofollow={})
|
||||
local tagsCheck = {}
|
||||
for _, tag in ipairs(tags) do
|
||||
tagsCheck[tag] = true
|
||||
end
|
||||
local nofollowCheck = {}
|
||||
for _, tag in ipairs(nofollow) do
|
||||
nofollowCheck[tag] = true
|
||||
end
|
||||
for _, node in ipairs(list) do
|
||||
if type(node) == "table" then
|
||||
if tagsCheck[node.tag] then
|
||||
return node
|
||||
end
|
||||
if not nofollowCheck[node.tag] then
|
||||
local r = any(node, tags, nofollow)
|
||||
if r then return r end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Like any, but returns a list of every node found.
|
||||
-- Order: in the order of the list, from the deepest to the nearest
|
||||
local function search(list, tags, nofollow={})
|
||||
local tagsCheck = {}
|
||||
for _, tag in ipairs(tags) do
|
||||
tagsCheck[tag] = true
|
||||
end
|
||||
local nofollowCheck = {}
|
||||
for _, tag in ipairs(nofollow) do
|
||||
nofollowCheck[tag] = true
|
||||
end
|
||||
local found = {}
|
||||
for _, node in ipairs(list) do
|
||||
if type(node) == "table" then
|
||||
if not nofollowCheck[node.tag] then
|
||||
for _, n in ipairs(search(node, tags, nofollow)) do
|
||||
table.insert(found, n)
|
||||
end
|
||||
end
|
||||
if tagsCheck[node.tag] then
|
||||
table.insert(found, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
return found
|
||||
end
|
||||
|
||||
-- Returns true if the all the nodes in list have their type in tags.
|
||||
local function all(list, tags)
|
||||
for _, node in ipairs(list) do
|
||||
local ok = false
|
||||
for _, tag in ipairs(tags) do
|
||||
if node.tag == tag then
|
||||
ok = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not ok then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Lua compiler
|
||||
local tags
|
||||
-- Recursively returns the compiled AST Lua code, set "forceTag" to override the tag type and pass additional arguments to the tag constructor if needed.
|
||||
local function lua(ast, forceTag, ...)
|
||||
if options.mapLines and ast.pos then
|
||||
lastInputPos = ast.pos
|
||||
end
|
||||
return tags[forceTag or ast.tag](ast, ...)
|
||||
end
|
||||
|
||||
--- Lua function calls writer
|
||||
local UNPACK = (list, i, j) -- table.unpack
|
||||
return "table.unpack("..list..(i and (", "..i..(j and (", "..j) or "")) or "")..")"
|
||||
end
|
||||
local APPEND = (t, toAppend) -- append values "toAppend" (multiple values possible) to t
|
||||
return "do"..indent().."local "..var("a").." = table.pack("..toAppend..")"..newline().."table.move("..var("a")..", 1, "..var("a")..".n, #"..t.."+1, "..t..")"..unindent().."end"
|
||||
end
|
||||
local CONTINUE_START = () -- at the start of loops using continue
|
||||
return "do"..indent()
|
||||
end
|
||||
local CONTINUE_STOP = () -- at the start of loops using continue
|
||||
return unindent().."end"..newline().."::"..var"continue".."::"
|
||||
end
|
||||
local DESTRUCTURING_ASSIGN = (destructured, newlineAfter=false, noLocal=false) -- to define values from a destructuring assignement
|
||||
local vars = {}
|
||||
local values = {}
|
||||
for _, list in ipairs(destructured) do
|
||||
for _, v in ipairs(list) do
|
||||
local var, val
|
||||
if v.tag == "Id" or v.tag == "AttributeId" then
|
||||
var = v
|
||||
val = { tag = "Index", { tag = "Id", list.id }, { tag = "String", v[1] } }
|
||||
elseif v.tag == "Pair" then
|
||||
var = v[2]
|
||||
val = { tag = "Index", { tag = "Id", list.id }, v[1] }
|
||||
else
|
||||
t[i][2] = t[1]
|
||||
table.insert(ids, lua(t[i]))
|
||||
error("unknown destructuring element type: "..tostring(v.tag))
|
||||
end
|
||||
if destructured.rightOp and destructured.leftOp then
|
||||
val = { tag = "Op", destructured.rightOp, var, { tag = "Op", destructured.leftOp, val, var } }
|
||||
elseif destructured.rightOp then
|
||||
val = { tag = "Op", destructured.rightOp, var, val }
|
||||
elseif destructured.leftOp then
|
||||
val = { tag = "Op", destructured.leftOp, val, var }
|
||||
end
|
||||
table.insert(vars, lua(var))
|
||||
table.insert(values, lua(val))
|
||||
end
|
||||
end
|
||||
return table.concat(ids, ", ")
|
||||
if #vars > 0 then
|
||||
local decl = noLocal and "" or "local "
|
||||
if newlineAfter then
|
||||
return decl..table.concat(vars, ", ").." = "..table.concat(values, ", ")..newline()
|
||||
else
|
||||
return newline()..decl..table.concat(vars, ", ").." = "..table.concat(values, ", ")
|
||||
end
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
--- Tag constructors
|
||||
tags = setmetatable({
|
||||
-- block: { stat* } --
|
||||
Block = (t)
|
||||
local hasPush = peek("push") == nil and any(t, { "Push" }, func) -- push in block and push context not yet defined
|
||||
if hasPush and hasPush == t[#t] then -- if the first push is the last statement, it's just a return
|
||||
hasPush.tag = "Return"
|
||||
hasPush = false
|
||||
end
|
||||
local r = push("scope", {})
|
||||
if hasPush then
|
||||
r ..= push("push", var"push").."local "..var"push".." = {}"..newline()
|
||||
end
|
||||
for i=1, #t-1, 1 do
|
||||
r ..= lua(t[i])..newline()
|
||||
end
|
||||
if t[#t] then
|
||||
r ..= lua(t[#t])
|
||||
end
|
||||
if hasPush and (t[#t] and t[#t].tag ~= "Return") then -- add return only if needed
|
||||
r ..= newline().."return "..UNPACK(var"push")..pop("push")
|
||||
end
|
||||
return r..pop("scope")
|
||||
end,
|
||||
|
||||
-- stat --
|
||||
|
||||
-- Do{ stat* }
|
||||
Do = (t)
|
||||
return "do"..indent()..lua(t, "Block")..unindent().."end"
|
||||
end,
|
||||
-- Set{ {lhs+} (opid? = opid?)? {expr+} }
|
||||
Set = (t)
|
||||
-- extract vars and values
|
||||
local expr = t[#t]
|
||||
local vars, values = {}, {}
|
||||
local destructuringVars, destructuringValues = {}, {}
|
||||
for i, n in ipairs(t[1]) do
|
||||
if n.tag == "DestructuringId" then
|
||||
table.insert(destructuringVars, n)
|
||||
table.insert(destructuringValues, expr[i])
|
||||
else
|
||||
table.insert(vars, n)
|
||||
table.insert(values, expr[i])
|
||||
end
|
||||
end
|
||||
--
|
||||
if #t == 2 or #t == 3 then
|
||||
local r = ""
|
||||
if #vars > 0 then
|
||||
r = lua(vars, "_lhs").." = "..lua(values, "_lhs")
|
||||
end
|
||||
if #destructuringVars > 0 then
|
||||
local destructured = {}
|
||||
r ..= "local "..push("destructuring", destructured)..lua(destructuringVars, "_lhs")..pop("destructuring").." = "..lua(destructuringValues, "_lhs")
|
||||
return r..DESTRUCTURING_ASSIGN(destructured, nil, true)
|
||||
end
|
||||
return r
|
||||
elseif #t == 4 then
|
||||
if t[3] == "=" then
|
||||
local r = ""
|
||||
if #vars > 0 then
|
||||
r ..= lua(vars, "_lhs").." = "..lua({ t[2], vars[1], { tag = "Paren", values[1] } }, "Op")
|
||||
for i=2, math.min(#t[4], #vars), 1 do
|
||||
r ..= ", "..lua({ t[2], vars[i], { tag = "Paren", values[i] } }, "Op")
|
||||
end
|
||||
end
|
||||
if #destructuringVars > 0 then
|
||||
local destructured = { rightOp = t[2] }
|
||||
r ..= "local "..push("destructuring", destructured)..lua(destructuringVars, "_lhs")..pop("destructuring").." = "..lua(destructuringValues, "_lhs")
|
||||
return r..DESTRUCTURING_ASSIGN(destructured, nil, true)
|
||||
end
|
||||
return r
|
||||
else
|
||||
local r = ""
|
||||
if #vars > 0 then
|
||||
r ..= lua(vars, "_lhs").." = "..lua({ t[3], { tag = "Paren", values[1] }, vars[1] }, "Op")
|
||||
for i=2, math.min(#t[4], #t[1]), 1 do
|
||||
r ..= ", "..lua({ t[3], { tag = "Paren", values[i] }, vars[i] }, "Op")
|
||||
end
|
||||
end
|
||||
if #destructuringVars > 0 then
|
||||
local destructured = { leftOp = t[3] }
|
||||
r ..= "local "..push("destructuring", destructured)..lua(destructuringVars, "_lhs")..pop("destructuring").." = "..lua(destructuringValues, "_lhs")
|
||||
return r..DESTRUCTURING_ASSIGN(destructured, nil, true)
|
||||
end
|
||||
return r
|
||||
end
|
||||
else -- You are mad.
|
||||
local r = ""
|
||||
if #vars > 0 then
|
||||
r ..= lua(vars, "_lhs").." = "..lua({ t[2], vars[1], { tag = "Op", t[4], { tag = "Paren", values[1] }, vars[1] } }, "Op")
|
||||
for i=2, math.min(#t[5], #t[1]), 1 do
|
||||
r ..= ", "..lua({ t[2], vars[i], { tag = "Op", t[4], { tag = "Paren", values[i] }, vars[i] } }, "Op")
|
||||
end
|
||||
end
|
||||
if #destructuringVars > 0 then
|
||||
local destructured = { rightOp = t[2], leftOp = t[4] }
|
||||
r ..= "local "..push("destructuring", destructured)..lua(destructuringVars, "_lhs")..pop("destructuring").." = "..lua(destructuringValues, "_lhs")
|
||||
return r..DESTRUCTURING_ASSIGN(destructured, nil, true)
|
||||
end
|
||||
return r
|
||||
end
|
||||
end,
|
||||
-- While{ expr block }
|
||||
While = (t)
|
||||
local r = ""
|
||||
local hasContinue = any(t[2], { "Continue" }, loop)
|
||||
local lets = search({ t[1] }, { "LetExpr" })
|
||||
if #lets > 0 then
|
||||
r ..= "do"..indent()
|
||||
for _, l in ipairs(lets) do
|
||||
r ..= lua(l, "Let")..newline()
|
||||
end
|
||||
end
|
||||
r ..= "while "..lua(t[1]).." do"..indent()
|
||||
if #lets > 0 then
|
||||
r ..= "do"..indent()
|
||||
end
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_START()
|
||||
end
|
||||
r ..= lua(t[2])
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_STOP()
|
||||
end
|
||||
r ..= unindent().."end"
|
||||
if #lets > 0 then
|
||||
for _, l in ipairs(lets) do
|
||||
r ..= newline()..lua(l, "Set")
|
||||
end
|
||||
r ..= unindent().."end"..unindent().."end"
|
||||
end
|
||||
return r
|
||||
end,
|
||||
-- Repeat{ block expr }
|
||||
Repeat = (t)
|
||||
local hasContinue = any(t[1], { "Continue" }, loop)
|
||||
local r = "repeat"..indent()
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_START()
|
||||
end
|
||||
r ..= lua(t[1])
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_STOP()
|
||||
end
|
||||
r ..= unindent().."until "..lua(t[2])
|
||||
return r
|
||||
end,
|
||||
-- If{ (lexpr block)+ block? }
|
||||
If = (t)
|
||||
local r = ""
|
||||
local toClose = 0 -- blocks that need to be closed at the end of the if
|
||||
local lets = search({ t[1] }, { "LetExpr" })
|
||||
if #lets > 0 then
|
||||
r ..= "do"..indent()
|
||||
toClose += 1
|
||||
for _, l in ipairs(lets) do
|
||||
r ..= lua(l, "Let")..newline()
|
||||
end
|
||||
end
|
||||
r ..= "if "..lua(t[1]).." then"..indent()..lua(t[2])..unindent()
|
||||
for i=3, #t-1, 2 do
|
||||
lets = search({ t[i] }, { "LetExpr" })
|
||||
if #lets > 0 then
|
||||
r ..= "else"..indent()
|
||||
toClose += 1
|
||||
for _, l in ipairs(lets) do
|
||||
r ..= lua(l, "Let")..newline()
|
||||
end
|
||||
else
|
||||
r ..= "else"
|
||||
end
|
||||
r ..= "if "..lua(t[i]).." then"..indent()..lua(t[i+1])..unindent()
|
||||
end
|
||||
if #t % 2 == 1 then
|
||||
r ..= "else"..indent()..lua(t[#t])..unindent()
|
||||
end
|
||||
r ..= "end"
|
||||
for i=1, toClose do
|
||||
r ..= unindent().."end"
|
||||
end
|
||||
return r
|
||||
end,
|
||||
-- Fornum{ ident expr expr expr? block }
|
||||
Fornum = (t)
|
||||
local r = "for "..lua(t[1]).." = "..lua(t[2])..", "..lua(t[3])
|
||||
if #t == 5 then
|
||||
local hasContinue = any(t[5], { "Continue" }, loop)
|
||||
r ..= ", "..lua(t[4]).." do"..indent()
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_START()
|
||||
end
|
||||
r ..= lua(t[5])
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_STOP()
|
||||
end
|
||||
return r..unindent().."end"
|
||||
else
|
||||
local hasContinue = any(t[4], { "Continue" }, loop)
|
||||
r ..= " do"..indent()
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_START()
|
||||
end
|
||||
r ..= lua(t[4])
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_STOP()
|
||||
end
|
||||
return r..unindent().."end"
|
||||
end
|
||||
end,
|
||||
-- Forin{ {ident+} {expr+} block }
|
||||
Forin = (t)
|
||||
local destructured = {}
|
||||
local hasContinue = any(t[3], { "Continue" }, loop)
|
||||
local r = "for "..push("destructuring", destructured)..lua(t[1], "_lhs")..pop("destructuring").." in "..lua(t[2], "_lhs").." do"..indent()
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_START()
|
||||
end
|
||||
r ..= DESTRUCTURING_ASSIGN(destructured, true)..lua(t[3])
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_STOP()
|
||||
end
|
||||
return r..unindent().."end"
|
||||
end,
|
||||
-- Local{ {attributeident+} {expr+}? }
|
||||
Local = (t)
|
||||
local destructured = {}
|
||||
local r = "local "..push("destructuring", destructured)..lua(t[1], "_lhs")..pop("destructuring")
|
||||
if t[2][1] then
|
||||
r ..= " = "..lua(t[2], "_lhs")
|
||||
end
|
||||
return r..DESTRUCTURING_ASSIGN(destructured)
|
||||
end,
|
||||
-- Let{ {ident+} {expr+}? }
|
||||
Let = (t)
|
||||
local destructured = {}
|
||||
local nameList = push("destructuring", destructured)..lua(t[1], "_lhs")..pop("destructuring")
|
||||
local r = "local "..nameList
|
||||
if t[2][1] then
|
||||
if all(t[2], { "Nil", "Dots", "Boolean", "Number", "String" }) then -- predeclaration doesn't matter here
|
||||
r ..= " = "..lua(t[2], "_lhs")
|
||||
else
|
||||
r ..= newline()..nameList.." = "..lua(t[2], "_lhs")
|
||||
end
|
||||
end
|
||||
return r..DESTRUCTURING_ASSIGN(destructured)
|
||||
end,
|
||||
-- Localrec{ {ident} {expr} }
|
||||
Localrec = (t)
|
||||
return "local function "..lua(t[1][1])..lua(t[2][1], "_functionWithoutKeyword")
|
||||
end,
|
||||
-- Goto{ <string> }
|
||||
Goto = (t)
|
||||
return "goto "..lua(t, "Id")
|
||||
end,
|
||||
-- Label{ <string> }
|
||||
Label = (t)
|
||||
return "::"..lua(t, "Id").."::"
|
||||
end,
|
||||
-- Return{ <expr*> }
|
||||
Return = (t)
|
||||
local push = peek("push")
|
||||
if push then
|
||||
local r = ""
|
||||
for _, val in ipairs(t) do
|
||||
r ..= push.."[#"..push.."+1] = "..lua(val)..newline()
|
||||
end
|
||||
return r.."return "..UNPACK(push)
|
||||
else
|
||||
return "return "..lua(t, "_lhs")
|
||||
end
|
||||
end,
|
||||
-- Push{ <expr*> }
|
||||
Push = (t)
|
||||
local var = assert(peek("push"), "no context given for push")
|
||||
r = ""
|
||||
for i=1, #t-1, 1 do
|
||||
r ..= var.."[#"..var.."+1] = "..lua(t[i])..newline()
|
||||
end
|
||||
if t[#t] then
|
||||
if t[#t].tag == "Call" then
|
||||
r ..= APPEND(var, lua(t[#t]))
|
||||
else
|
||||
r ..= var.."[#"..var.."+1] = "..lua(t[#t])
|
||||
end
|
||||
end
|
||||
return r
|
||||
end,
|
||||
-- Break
|
||||
Break = ()
|
||||
return "break"
|
||||
end,
|
||||
-- Continue
|
||||
Continue = ()
|
||||
return "goto "..var"continue"
|
||||
end,
|
||||
-- apply (below)
|
||||
|
||||
-- expr --
|
||||
|
||||
-- Nil
|
||||
Nil = ()
|
||||
return "nil"
|
||||
end,
|
||||
-- Dots
|
||||
Dots = ()
|
||||
local macroargs = peek("macroargs")
|
||||
if macroargs and not nomacro.variables["..."] and macroargs["..."] then
|
||||
nomacro.variables["..."] = true
|
||||
local r = lua(macroargs["..."], "_lhs")
|
||||
nomacro.variables["..."] = nil
|
||||
return r
|
||||
else
|
||||
return "..."
|
||||
end
|
||||
end,
|
||||
-- Boolean{ <boolean> }
|
||||
Boolean = (t)
|
||||
return tostring(t[1])
|
||||
end,
|
||||
-- Number{ <string> }
|
||||
Number = (t)
|
||||
return tostring(t[1])
|
||||
end,
|
||||
-- String{ <string> }
|
||||
String = (t)
|
||||
return "%q":format(t[1])
|
||||
end,
|
||||
-- Function{ { ( `ParPair{ Id expr } | `Id{ <string> } )* `Dots? } block }
|
||||
_functionWithoutKeyword = (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, "if "..id.." == nil then "..id.." = "..lua(t[1][1][2]).." end")
|
||||
indentLevel -= 1
|
||||
r ..= id
|
||||
else
|
||||
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 ..= ", " ..id
|
||||
else
|
||||
r ..= ", "..lua(t[1][i])
|
||||
end
|
||||
end
|
||||
end
|
||||
r ..= ")"..indent()
|
||||
for _, d in ipairs(decl) do
|
||||
r ..= d..newline()
|
||||
end
|
||||
if t[2][#t[2]] and t[2][#t[2]].tag == "Push" then -- convert final push to return
|
||||
t[2][#t[2]].tag = "Return"
|
||||
end
|
||||
local hasPush = any(t[2], { "Push" }, func)
|
||||
if hasPush then
|
||||
r ..= push("push", var"push").."local "..var"push".." = {}"..newline()
|
||||
else
|
||||
push("push", false) -- no push here (make sure higher push doesn't affect us)
|
||||
end
|
||||
r ..= lua(t[2])
|
||||
if hasPush and (t[2][#t[2]] and t[2][#t[2]].tag ~= "Return") then -- add return only if needed
|
||||
r ..= newline().."return "..UNPACK(var"push")
|
||||
end
|
||||
pop("push")
|
||||
return r..unindent().."end"
|
||||
end,
|
||||
Function = (t)
|
||||
return "function"..lua(t, "_functionWithoutKeyword")
|
||||
end,
|
||||
-- Table{ ( `Pair{ expr expr } | expr )* }
|
||||
Pair = (t)
|
||||
return "["..lua(t[1]).."] = "..lua(t[2])
|
||||
end,
|
||||
Table = (t)
|
||||
if #t == 0 then
|
||||
return "{}"
|
||||
elseif #t == 1 then
|
||||
return "{ "..lua(t, "_lhs").." }"
|
||||
else
|
||||
return "{"..indent()..lua(t, "_lhs", nil, true)..unindent().."}"
|
||||
end
|
||||
end,
|
||||
-- TableCompr{ block }
|
||||
TableCompr = (t)
|
||||
return push("push", "self").."(function()"..indent().."local self = {}"..newline()..lua(t[1])..newline().."return self"..unindent().."end)()"..pop("push")
|
||||
end,
|
||||
-- Op{ opid expr expr? }
|
||||
Op = (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 = (t)
|
||||
return "("..lua(t[1])..")"
|
||||
end,
|
||||
-- MethodStub{ expr expr }
|
||||
MethodStub = (t)
|
||||
return "(function()"..indent() ..
|
||||
"local "..var"object".." = "..lua(t[1])..newline()..
|
||||
"local "..var"method".." = "..var"object".."."..lua(t[2], "Id")..newline() ..
|
||||
"if "..var"method".." == nil then return nil end"..newline()..
|
||||
"return function(...) return "..var"method".."("..var"object"..", ...) end"..unindent()..
|
||||
"end)()"
|
||||
end,
|
||||
-- SafeMethodStub{ expr expr }
|
||||
SafeMethodStub = (t)
|
||||
return "(function()"..indent() ..
|
||||
"local "..var"object".." = "..lua(t[1])..newline()..
|
||||
"if "..var"object".." == nil then return nil end"..newline()..
|
||||
"local "..var"method".." = "..var"object".."."..lua(t[2], "Id")..newline() ..
|
||||
"if "..var"method".." == nil then return nil end"..newline()..
|
||||
"return function(...) return "..var"method".."("..var"object"..", ...) end"..unindent()..
|
||||
"end)()"
|
||||
end,
|
||||
-- statexpr (below)
|
||||
-- apply (below)
|
||||
-- lhs (below)
|
||||
|
||||
-- lexpr --
|
||||
LetExpr = (t)
|
||||
return lua(t[1][1])
|
||||
end,
|
||||
|
||||
-- statexpr --
|
||||
_statexpr = (t, stat)
|
||||
local hasPush = any(t, { "Push" }, func)
|
||||
local r = "(function()"..indent()
|
||||
if hasPush then
|
||||
r ..= push("push", var"push").."local "..var"push".." = {}"..newline()
|
||||
else
|
||||
push("push", false) -- no push here (make sure higher push don't affect us)
|
||||
end
|
||||
r ..= lua(t, stat)
|
||||
if hasPush then
|
||||
r ..= newline().."return "..UNPACK(var"push")
|
||||
end
|
||||
pop("push")
|
||||
r ..= unindent().."end)()"
|
||||
return r
|
||||
end,
|
||||
-- DoExpr{ stat* }
|
||||
DoExpr = (t)
|
||||
if t[#t].tag == "Push" then -- convert final push to return
|
||||
t[#t].tag = "Return"
|
||||
end
|
||||
return lua(t, "_statexpr", "Do")
|
||||
end,
|
||||
-- WhileExpr{ expr block }
|
||||
WhileExpr = (t)
|
||||
return lua(t, "_statexpr", "While")
|
||||
end,
|
||||
-- RepeatExpr{ block expr }
|
||||
RepeatExpr = (t)
|
||||
return lua(t, "_statexpr", "Repeat")
|
||||
end,
|
||||
-- IfExpr{ (expr block)+ block? }
|
||||
IfExpr = (t)
|
||||
for i=2, #t do -- convert final pushes to returns
|
||||
local block = t[i]
|
||||
if block[#block] and block[#block].tag == "Push" then
|
||||
block[#block].tag = "Return"
|
||||
end
|
||||
end
|
||||
return lua(t, "_statexpr", "If")
|
||||
end,
|
||||
-- FornumExpr{ ident expr expr expr? block }
|
||||
FornumExpr = (t)
|
||||
return lua(t, "_statexpr", "Fornum")
|
||||
end,
|
||||
-- ForinExpr{ {ident+} {expr+} block }
|
||||
ForinExpr = (t)
|
||||
return lua(t, "_statexpr", "Forin")
|
||||
end,
|
||||
|
||||
-- apply --
|
||||
|
||||
-- Call{ expr expr* }
|
||||
Call = (t)
|
||||
if t[1].tag == "String" or t[1].tag == "Table" then
|
||||
return "("..lua(t[1])..")("..lua(t, "_lhs", 2)..")"
|
||||
elseif t[1].tag == "Id" and not nomacro.functions[t[1][1]] and macros.functions[t[1][1]] then
|
||||
local macro = macros.functions[t[1][1]]
|
||||
local replacement = macro.replacement
|
||||
local r
|
||||
nomacro.functions[t[1][1]] = true
|
||||
if type(replacement) == "function" then
|
||||
local args = {}
|
||||
for i=2, #t do
|
||||
table.insert(args, lua(t[i]))
|
||||
end
|
||||
r = replacement(unpack(args))
|
||||
else
|
||||
local macroargs = util.merge(peek("macroargs"))
|
||||
for i, arg in ipairs(macro.args) do
|
||||
if arg.tag == "Dots" then
|
||||
macroargs["..."] = [for j=i+1, #t do t[j] end]
|
||||
elseif arg.tag == "Id" then
|
||||
if t[i+1] == nil then
|
||||
error("bad argument #%s to macro %s (value expected)":format(i, t[1][1]))
|
||||
end
|
||||
macroargs[arg[1]] = t[i+1]
|
||||
else
|
||||
error("unexpected argument type %s in macro %s":format(arg.tag, t[1][1]))
|
||||
end
|
||||
end
|
||||
push("macroargs", macroargs)
|
||||
r = lua(replacement)
|
||||
pop("macroargs")
|
||||
end
|
||||
nomacro.functions[t[1][1]] = nil
|
||||
return r
|
||||
elseif t[1].tag == "MethodStub" then -- method call
|
||||
if t[1][1].tag == "String" or t[1][1].tag == "Table" then
|
||||
return "("..lua(t[1][1]).."):"..lua(t[1][2], "Id").."("..lua(t, "_lhs", 2)..")"
|
||||
else
|
||||
return lua(t[1][1])..":"..lua(t[1][2], "Id").."("..lua(t, "_lhs", 2)..")"
|
||||
end
|
||||
else
|
||||
return lua(t[1]).."("..lua(t, "_lhs", 2)..")"
|
||||
end
|
||||
end,
|
||||
-- SafeCall{ expr expr* }
|
||||
SafeCall = (t)
|
||||
if t[1].tag ~= "Id" then -- side effect possible, only evaluate each expr once (or already in a safe context)
|
||||
return lua(t, "SafeIndex")
|
||||
else -- no side effects possible
|
||||
return "("..lua(t[1]).." ~= nil and "..lua(t[1]).."("..lua(t, "_lhs", 2)..") or nil)"
|
||||
end
|
||||
end,
|
||||
|
||||
-- lhs --
|
||||
_lhs = (t, start=1, newlines)
|
||||
local r
|
||||
if t[start] then
|
||||
r = lua(t[start])
|
||||
for i=start+1, #t, 1 do
|
||||
r ..= ","..(newlines and newline() or " ")..lua(t[i])
|
||||
end
|
||||
else
|
||||
r = ""
|
||||
end
|
||||
return r
|
||||
end,
|
||||
-- Id{ <string> }
|
||||
Id = (t)
|
||||
local r = t[1]
|
||||
local macroargs = peek("macroargs")
|
||||
if not nomacro.variables[t[1]] then
|
||||
nomacro.variables[t[1]] = true
|
||||
if macroargs and macroargs[t[1]] then -- replace with macro argument
|
||||
r = lua(macroargs[t[1]])
|
||||
elseif macros.variables[t[1]] ~= nil then -- replace with macro variable
|
||||
local macro = macros.variables[t[1]]
|
||||
if type(macro) == "function" then
|
||||
r = macro()
|
||||
else
|
||||
r = lua(macro)
|
||||
end
|
||||
end
|
||||
nomacro.variables[t[1]] = nil
|
||||
end
|
||||
return r
|
||||
end,
|
||||
-- AttributeId{ <string> <string>? }
|
||||
AttributeId = (t)
|
||||
if t[2] then
|
||||
return t[1] .. " <" .. t[2] .. ">"
|
||||
else
|
||||
return t[1]
|
||||
end
|
||||
end,
|
||||
-- DestructuringId{ Id | Pair+ }
|
||||
DestructuringId = (t)
|
||||
if t.id then -- destructing already done before, use parent variable as id
|
||||
return t.id
|
||||
else
|
||||
local d = assert(peek("destructuring"), "DestructuringId not in a destructurable assignement")
|
||||
local vars = { id = tmp() }
|
||||
for j=1, #t, 1 do
|
||||
table.insert(vars, t[j])
|
||||
end
|
||||
table.insert(d, vars)
|
||||
t.id = vars.id
|
||||
return vars.id
|
||||
end
|
||||
end,
|
||||
-- Index{ expr expr }
|
||||
Index = (t)
|
||||
if t[1].tag == "String" or t[1].tag == "Table" then
|
||||
return "("..lua(t[1])..")["..lua(t[2]).."]"
|
||||
else
|
||||
return lua(t[1]).."["..lua(t[2]).."]"
|
||||
end
|
||||
end,
|
||||
-- SafeIndex{ expr expr }
|
||||
SafeIndex = (t)
|
||||
if t[1].tag ~= "Id" then -- side effect possible, only evaluate each expr once (or already in a safe context)
|
||||
local l = {} -- list of immediately chained safeindex, from deepest to nearest (to simply generated code)
|
||||
while t.tag == "SafeIndex" or t.tag == "SafeCall" do
|
||||
table.insert(l, 1, t)
|
||||
t = t[1]
|
||||
end
|
||||
local r = "(function()"..indent().."local "..var"safe".." = "..lua(l[1][1])..newline() -- base expr
|
||||
for _, e in ipairs(l) do
|
||||
r ..= "if "..var"safe".." == nil then return nil end"..newline()
|
||||
if e.tag == "SafeIndex" then
|
||||
r ..= var"safe".." = "..var"safe".."["..lua(e[2]).."]"..newline()
|
||||
else
|
||||
r ..= var"safe".." = "..var"safe".."("..lua(e, "_lhs", 2)..")"..newline()
|
||||
end
|
||||
end
|
||||
r ..= "return "..var"safe"..unindent().."end)()"
|
||||
return r
|
||||
else -- no side effects possible
|
||||
return "("..lua(t[1]).." ~= nil and "..lua(t[1]).."["..lua(t[2]).."] or nil)"
|
||||
end
|
||||
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 = (self, key)
|
||||
error("don't know how to compile a "..tostring(key).." to "..targetName)
|
||||
end
|
||||
})
|
||||
|
||||
#placeholder("patch")
|
||||
|
||||
local code = lua(ast)..newline()
|
||||
return requireStr..code
|
||||
end
|
||||
|
||||
#placeholder("patch")
|
||||
|
||||
#local patch = output
|
||||
#output = ""
|
||||
#import("compiler.lua55", { preprocessorEnv = { patch = patch }, loadPackage = false })
|
||||
|
||||
return lua55
|
||||
|
|
|
|||
|
|
@ -1,933 +0,0 @@
|
|||
local util = require("candran.util")
|
||||
|
||||
local targetName = "Lua 5.5"
|
||||
|
||||
local unpack = unpack or table.unpack
|
||||
|
||||
return function(code, ast, options, macros={functions={}, variables={}})
|
||||
--- Line mapping
|
||||
local lastInputPos = 1 -- last token position in the input code
|
||||
local prevLinePos = 1 -- last token position in the previous line of code in the input code
|
||||
local lastSource = options.chunkname or "nil" -- last found code source name (from the original file)
|
||||
local lastLine = 1 -- last found line number (from the original file)
|
||||
|
||||
--- Newline management
|
||||
local indentLevel = 0
|
||||
-- Returns a newline.
|
||||
local function newline()
|
||||
local r = options.newline..string.rep(options.indentation, indentLevel)
|
||||
if options.mapLines then
|
||||
local sub = code:sub(lastInputPos)
|
||||
local source, line = sub:sub(1, sub:find("\n")):match(".*%-%- (.-)%:(%d+)\n")
|
||||
|
||||
if source and line then
|
||||
lastSource = source
|
||||
lastLine = tonumber(line)
|
||||
else
|
||||
for _ in code:sub(prevLinePos, lastInputPos):gmatch("\n") do
|
||||
lastLine += 1
|
||||
end
|
||||
end
|
||||
|
||||
prevLinePos = lastInputPos
|
||||
|
||||
r = " -- "..lastSource..":"..lastLine..r
|
||||
end
|
||||
return r
|
||||
end
|
||||
-- Returns a newline and add one level of indentation.
|
||||
local function indent()
|
||||
indentLevel += 1
|
||||
return newline()
|
||||
end
|
||||
-- Returns a newline and remove one level of indentation.
|
||||
local function unindent()
|
||||
indentLevel -= 1
|
||||
return newline()
|
||||
end
|
||||
|
||||
--- State stacks
|
||||
-- Used for context-sensitive syntax.
|
||||
local states = {
|
||||
push = {}, -- push stack variable names
|
||||
destructuring = {}, -- list of variable that need to be assigned from a destructure {id = "parent variable", "field1", "field2"...}
|
||||
scope = {}, -- list of variables defined in the current scope
|
||||
macroargs = {} -- currently defined arguemnts from a macro function
|
||||
}
|
||||
-- Push a new value on top of the stack "name". Returns an empty string for chaining.
|
||||
local function push(name, state)
|
||||
table.insert(states[name], state)
|
||||
return ""
|
||||
end
|
||||
-- Remove the value on top of the stack "name". Returns an empty string for chaining.
|
||||
local function pop(name)
|
||||
table.remove(states[name])
|
||||
return ""
|
||||
end
|
||||
-- Set the value on top of the stack "name". Returns an empty string for chaining.
|
||||
local function set(name, state)
|
||||
states[name][#states[name]] = state
|
||||
return ""
|
||||
end
|
||||
-- Returns the value on top of the stack "name".
|
||||
local function peek(name)
|
||||
return states[name][#states[name]]
|
||||
end
|
||||
|
||||
--- Variable management
|
||||
-- Returns the prefixed variable name.
|
||||
local function var(name)
|
||||
return options.variablePrefix..name
|
||||
end
|
||||
|
||||
-- Returns the prefixed temporary variable name.
|
||||
local function tmp()
|
||||
local scope = peek("scope")
|
||||
local var = "%s_%s":format(options.variablePrefix, #scope)
|
||||
table.insert(scope, var)
|
||||
return var
|
||||
end
|
||||
|
||||
-- indicate if currently processing a macro, so it cannot be applied recursively
|
||||
local nomacro = { variables = {}, functions = {} }
|
||||
|
||||
--- Module management
|
||||
local required = {} -- { ["full require expression"] = true, ... }
|
||||
local requireStr = ""
|
||||
-- Add the module "mod" to the list of modules to require, and load its field "field" (or the whole module if nil) into the variable "name".
|
||||
local function addRequire(mod, name, field)
|
||||
local req = "require(%q)%s":format(mod, field and "."..field or "")
|
||||
if not required[req] then
|
||||
requireStr ..= "local %s = %s%s":format(var(name), req, options.newline)
|
||||
required[req] = true
|
||||
end
|
||||
end
|
||||
|
||||
--- AST traversal helpers
|
||||
local loop = { "While", "Repeat", "Fornum", "Forin", "WhileExpr", "RepeatExpr", "FornumExpr", "ForinExpr" } -- loops tags (can contain continue)
|
||||
local func = { "Function", "TableCompr", "DoExpr", "WhileExpr", "RepeatExpr", "IfExpr", "FornumExpr", "ForinExpr" } -- function scope tags (can contain push)
|
||||
|
||||
-- Returns the first node or subnode from the list "list" which tag is in the list "tags", or nil if there were none.
|
||||
-- Won't recursively follow nodes which have a tag in "nofollow".
|
||||
local function any(list, tags, nofollow={})
|
||||
local tagsCheck = {}
|
||||
for _, tag in ipairs(tags) do
|
||||
tagsCheck[tag] = true
|
||||
end
|
||||
local nofollowCheck = {}
|
||||
for _, tag in ipairs(nofollow) do
|
||||
nofollowCheck[tag] = true
|
||||
end
|
||||
for _, node in ipairs(list) do
|
||||
if type(node) == "table" then
|
||||
if tagsCheck[node.tag] then
|
||||
return node
|
||||
end
|
||||
if not nofollowCheck[node.tag] then
|
||||
local r = any(node, tags, nofollow)
|
||||
if r then return r end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Like any, but returns a list of every node found.
|
||||
-- Order: in the order of the list, from the deepest to the nearest
|
||||
local function search(list, tags, nofollow={})
|
||||
local tagsCheck = {}
|
||||
for _, tag in ipairs(tags) do
|
||||
tagsCheck[tag] = true
|
||||
end
|
||||
local nofollowCheck = {}
|
||||
for _, tag in ipairs(nofollow) do
|
||||
nofollowCheck[tag] = true
|
||||
end
|
||||
local found = {}
|
||||
for _, node in ipairs(list) do
|
||||
if type(node) == "table" then
|
||||
if not nofollowCheck[node.tag] then
|
||||
for _, n in ipairs(search(node, tags, nofollow)) do
|
||||
table.insert(found, n)
|
||||
end
|
||||
end
|
||||
if tagsCheck[node.tag] then
|
||||
table.insert(found, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
return found
|
||||
end
|
||||
|
||||
-- Returns true if the all the nodes in list have their type in tags.
|
||||
local function all(list, tags)
|
||||
for _, node in ipairs(list) do
|
||||
local ok = false
|
||||
for _, tag in ipairs(tags) do
|
||||
if node.tag == tag then
|
||||
ok = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not ok then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--- Lua compiler
|
||||
local tags
|
||||
-- Recursively returns the compiled AST Lua code, set "forceTag" to override the tag type and pass additional arguments to the tag constructor if needed.
|
||||
local function lua(ast, forceTag, ...)
|
||||
if options.mapLines and ast.pos then
|
||||
lastInputPos = ast.pos
|
||||
end
|
||||
return tags[forceTag or ast.tag](ast, ...)
|
||||
end
|
||||
|
||||
--- Lua function calls writer
|
||||
local UNPACK = (list, i, j) -- table.unpack
|
||||
return "table.unpack("..list..(i and (", "..i..(j and (", "..j) or "")) or "")..")"
|
||||
end
|
||||
local APPEND = (t, toAppend) -- append values "toAppend" (multiple values possible) to t
|
||||
return "do"..indent().."local "..var("a").." = table.pack("..toAppend..")"..newline().."table.move("..var("a")..", 1, "..var("a")..".n, #"..t.."+1, "..t..")"..unindent().."end"
|
||||
end
|
||||
local CONTINUE_START = () -- at the start of loops using continue
|
||||
return "do"..indent()
|
||||
end
|
||||
local CONTINUE_STOP = () -- at the start of loops using continue
|
||||
return unindent().."end"..newline().."::"..var"continue".."::"
|
||||
end
|
||||
local DESTRUCTURING_ASSIGN = (destructured, newlineAfter=false, noLocal=false) -- to define values from a destructuring assignment
|
||||
local vars = {}
|
||||
local values = {}
|
||||
for _, list in ipairs(destructured) do
|
||||
for _, v in ipairs(list) do
|
||||
local var, val
|
||||
if v.tag == "Id" or v.tag == "AttributeId" then
|
||||
var = v
|
||||
val = { tag = "Index", { tag = "Id", list.id }, { tag = "String", v[1] } }
|
||||
elseif v.tag == "Pair" then
|
||||
var = v[2]
|
||||
val = { tag = "Index", { tag = "Id", list.id }, v[1] }
|
||||
else
|
||||
error("unknown destructuring element type: "..tostring(v.tag))
|
||||
end
|
||||
if destructured.rightOp and destructured.leftOp then
|
||||
val = { tag = "Op", destructured.rightOp, var, { tag = "Op", destructured.leftOp, val, var } }
|
||||
elseif destructured.rightOp then
|
||||
val = { tag = "Op", destructured.rightOp, var, val }
|
||||
elseif destructured.leftOp then
|
||||
val = { tag = "Op", destructured.leftOp, val, var }
|
||||
end
|
||||
table.insert(vars, lua(var))
|
||||
table.insert(values, lua(val))
|
||||
end
|
||||
end
|
||||
if #vars > 0 then
|
||||
local decl = noLocal and "" or "local "
|
||||
if newlineAfter then
|
||||
return decl..table.concat(vars, ", ").." = "..table.concat(values, ", ")..newline()
|
||||
else
|
||||
return newline()..decl..table.concat(vars, ", ").." = "..table.concat(values, ", ")
|
||||
end
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
--- Tag constructors
|
||||
tags = setmetatable({
|
||||
-- block: { stat* } --
|
||||
Block = (t)
|
||||
local hasPush = peek("push") == nil and any(t, { "Push" }, func) -- push in block and push context not yet defined
|
||||
if hasPush and hasPush == t[#t] then -- if the first push is the last statement, it's just a return
|
||||
hasPush.tag = "Return"
|
||||
hasPush = false
|
||||
end
|
||||
local r = push("scope", {})
|
||||
if hasPush then
|
||||
r ..= push("push", var"push").."local "..var"push".." = {}"..newline()
|
||||
end
|
||||
for i=1, #t-1, 1 do
|
||||
r ..= lua(t[i])..newline()
|
||||
end
|
||||
if t[#t] then
|
||||
r ..= lua(t[#t])
|
||||
end
|
||||
if hasPush and (t[#t] and t[#t].tag ~= "Return") then -- add return only if needed
|
||||
r ..= newline().."return "..UNPACK(var"push")..pop("push")
|
||||
end
|
||||
return r..pop("scope")
|
||||
end,
|
||||
|
||||
-- stat --
|
||||
|
||||
-- Do{ stat* }
|
||||
Do = (t)
|
||||
return "do"..indent()..lua(t, "Block")..unindent().."end"
|
||||
end,
|
||||
-- Set{ {lhs+} (opid? = opid?)? {expr+} }
|
||||
Set = (t)
|
||||
-- extract vars and values
|
||||
local expr = t[#t]
|
||||
local vars, values = {}, {}
|
||||
local destructuringVars, destructuringValues = {}, {}
|
||||
for i, n in ipairs(t[1]) do
|
||||
if n.tag == "DestructuringId" then
|
||||
table.insert(destructuringVars, n)
|
||||
table.insert(destructuringValues, expr[i])
|
||||
else
|
||||
table.insert(vars, n)
|
||||
table.insert(values, expr[i])
|
||||
end
|
||||
end
|
||||
--
|
||||
if #t == 2 or #t == 3 then
|
||||
local r = ""
|
||||
if #vars > 0 then
|
||||
r = lua(vars, "_lhs").." = "..lua(values, "_lhs")
|
||||
end
|
||||
if #destructuringVars > 0 then
|
||||
local destructured = {}
|
||||
r ..= "local "..push("destructuring", destructured)..lua(destructuringVars, "_lhs")..pop("destructuring").." = "..lua(destructuringValues, "_lhs")
|
||||
return r..DESTRUCTURING_ASSIGN(destructured, nil, true)
|
||||
end
|
||||
return r
|
||||
elseif #t == 4 then
|
||||
if t[3] == "=" then
|
||||
local r = ""
|
||||
if #vars > 0 then
|
||||
r ..= lua(vars, "_lhs").." = "..lua({ t[2], vars[1], { tag = "Paren", values[1] } }, "Op")
|
||||
for i=2, math.min(#t[4], #vars), 1 do
|
||||
r ..= ", "..lua({ t[2], vars[i], { tag = "Paren", values[i] } }, "Op")
|
||||
end
|
||||
end
|
||||
if #destructuringVars > 0 then
|
||||
local destructured = { rightOp = t[2] }
|
||||
r ..= "local "..push("destructuring", destructured)..lua(destructuringVars, "_lhs")..pop("destructuring").." = "..lua(destructuringValues, "_lhs")
|
||||
return r..DESTRUCTURING_ASSIGN(destructured, nil, true)
|
||||
end
|
||||
return r
|
||||
else
|
||||
local r = ""
|
||||
if #vars > 0 then
|
||||
r ..= lua(vars, "_lhs").." = "..lua({ t[3], { tag = "Paren", values[1] }, vars[1] }, "Op")
|
||||
for i=2, math.min(#t[4], #t[1]), 1 do
|
||||
r ..= ", "..lua({ t[3], { tag = "Paren", values[i] }, vars[i] }, "Op")
|
||||
end
|
||||
end
|
||||
if #destructuringVars > 0 then
|
||||
local destructured = { leftOp = t[3] }
|
||||
r ..= "local "..push("destructuring", destructured)..lua(destructuringVars, "_lhs")..pop("destructuring").." = "..lua(destructuringValues, "_lhs")
|
||||
return r..DESTRUCTURING_ASSIGN(destructured, nil, true)
|
||||
end
|
||||
return r
|
||||
end
|
||||
else -- You are mad.
|
||||
local r = ""
|
||||
if #vars > 0 then
|
||||
r ..= lua(vars, "_lhs").." = "..lua({ t[2], vars[1], { tag = "Op", t[4], { tag = "Paren", values[1] }, vars[1] } }, "Op")
|
||||
for i=2, math.min(#t[5], #t[1]), 1 do
|
||||
r ..= ", "..lua({ t[2], vars[i], { tag = "Op", t[4], { tag = "Paren", values[i] }, vars[i] } }, "Op")
|
||||
end
|
||||
end
|
||||
if #destructuringVars > 0 then
|
||||
local destructured = { rightOp = t[2], leftOp = t[4] }
|
||||
r ..= "local "..push("destructuring", destructured)..lua(destructuringVars, "_lhs")..pop("destructuring").." = "..lua(destructuringValues, "_lhs")
|
||||
return r..DESTRUCTURING_ASSIGN(destructured, nil, true)
|
||||
end
|
||||
return r
|
||||
end
|
||||
end,
|
||||
-- While{ expr block }
|
||||
While = (t)
|
||||
local r = ""
|
||||
local hasContinue = any(t[2], { "Continue" }, loop)
|
||||
local lets = search({ t[1] }, { "LetExpr" })
|
||||
if #lets > 0 then
|
||||
r ..= "do"..indent()
|
||||
for _, l in ipairs(lets) do
|
||||
r ..= lua(l, "Let")..newline()
|
||||
end
|
||||
end
|
||||
r ..= "while "..lua(t[1]).." do"..indent()
|
||||
if #lets > 0 then
|
||||
r ..= "do"..indent()
|
||||
end
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_START()
|
||||
end
|
||||
r ..= lua(t[2])
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_STOP()
|
||||
end
|
||||
r ..= unindent().."end"
|
||||
if #lets > 0 then
|
||||
for _, l in ipairs(lets) do
|
||||
r ..= newline()..lua(l, "Set")
|
||||
end
|
||||
r ..= unindent().."end"..unindent().."end"
|
||||
end
|
||||
return r
|
||||
end,
|
||||
-- Repeat{ block expr }
|
||||
Repeat = (t)
|
||||
local hasContinue = any(t[1], { "Continue" }, loop)
|
||||
local r = "repeat"..indent()
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_START()
|
||||
end
|
||||
r ..= lua(t[1])
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_STOP()
|
||||
end
|
||||
r ..= unindent().."until "..lua(t[2])
|
||||
return r
|
||||
end,
|
||||
-- If{ (lexpr block)+ block? }
|
||||
If = (t)
|
||||
local r = ""
|
||||
local toClose = 0 -- blocks that need to be closed at the end of the if
|
||||
local lets = search({ t[1] }, { "LetExpr" })
|
||||
if #lets > 0 then
|
||||
r ..= "do"..indent()
|
||||
toClose += 1
|
||||
for _, l in ipairs(lets) do
|
||||
r ..= lua(l, "Let")..newline()
|
||||
end
|
||||
end
|
||||
r ..= "if "..lua(t[1]).." then"..indent()..lua(t[2])..unindent()
|
||||
for i=3, #t-1, 2 do
|
||||
lets = search({ t[i] }, { "LetExpr" })
|
||||
if #lets > 0 then
|
||||
r ..= "else"..indent()
|
||||
toClose += 1
|
||||
for _, l in ipairs(lets) do
|
||||
r ..= lua(l, "Let")..newline()
|
||||
end
|
||||
else
|
||||
r ..= "else"
|
||||
end
|
||||
r ..= "if "..lua(t[i]).." then"..indent()..lua(t[i+1])..unindent()
|
||||
end
|
||||
if #t % 2 == 1 then
|
||||
r ..= "else"..indent()..lua(t[#t])..unindent()
|
||||
end
|
||||
r ..= "end"
|
||||
for i=1, toClose do
|
||||
r ..= unindent().."end"
|
||||
end
|
||||
return r
|
||||
end,
|
||||
-- Fornum{ ident expr expr expr? block }
|
||||
Fornum = (t)
|
||||
local r = "for "..lua(t[1]).." = "..lua(t[2])..", "..lua(t[3])
|
||||
if #t == 5 then
|
||||
local hasContinue = any(t[5], { "Continue" }, loop)
|
||||
r ..= ", "..lua(t[4]).." do"..indent()
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_START()
|
||||
end
|
||||
r ..= lua(t[5])
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_STOP()
|
||||
end
|
||||
return r..unindent().."end"
|
||||
else
|
||||
local hasContinue = any(t[4], { "Continue" }, loop)
|
||||
r ..= " do"..indent()
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_START()
|
||||
end
|
||||
r ..= lua(t[4])
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_STOP()
|
||||
end
|
||||
return r..unindent().."end"
|
||||
end
|
||||
end,
|
||||
-- Forin{ {ident+} {expr+} block }
|
||||
Forin = (t)
|
||||
local destructured = {}
|
||||
local hasContinue = any(t[3], { "Continue" }, loop)
|
||||
local r = "for "..push("destructuring", destructured)..lua(t[1], "_lhs")..pop("destructuring").." in "..lua(t[2], "_lhs").." do"..indent()
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_START()
|
||||
end
|
||||
r ..= DESTRUCTURING_ASSIGN(destructured, true)..lua(t[3])
|
||||
if hasContinue then
|
||||
r ..= CONTINUE_STOP()
|
||||
end
|
||||
return r..unindent().."end"
|
||||
end,
|
||||
-- Local{ {attributeident+} {expr+}? }
|
||||
Local = (t)
|
||||
local destructured = {}
|
||||
local r = "local "..push("destructuring", destructured)..lua(t[1])..pop("destructuring")
|
||||
if t[2][1] then
|
||||
r ..= " = "..lua(t[2], "_lhs")
|
||||
end
|
||||
return r..DESTRUCTURING_ASSIGN(destructured)
|
||||
end,
|
||||
-- Global{ {attributeident+} {expr+}? }
|
||||
Global = (t)
|
||||
local destructured = {}
|
||||
local r = "global "..push("destructuring", destructured)..lua(t[1])..pop("destructuring")
|
||||
if t[2][1] then
|
||||
r ..= " = "..lua(t[2], "_lhs")
|
||||
end
|
||||
return r..DESTRUCTURING_ASSIGN(destructured)
|
||||
end,
|
||||
-- Let{ {ident+} {expr+}? }
|
||||
Let = (t)
|
||||
local destructured = {}
|
||||
local nameList = push("destructuring", destructured)..lua(t[1], "_lhs")..pop("destructuring")
|
||||
local r = "local "..nameList
|
||||
if t[2][1] then
|
||||
if all(t[2], { "Nil", "Dots", "Boolean", "Number", "String" }) then -- predeclaration doesn't matter here
|
||||
r ..= " = "..lua(t[2], "_lhs")
|
||||
else
|
||||
r ..= newline()..nameList.." = "..lua(t[2], "_lhs")
|
||||
end
|
||||
end
|
||||
return r..DESTRUCTURING_ASSIGN(destructured)
|
||||
end,
|
||||
-- Localrec{ {ident} {expr} }
|
||||
Localrec = (t)
|
||||
return "local function "..lua(t[1][1])..lua(t[2][1], "_functionWithoutKeyword")
|
||||
end,
|
||||
-- Globalrec{ {ident} {expr} }
|
||||
Globalrec = (t)
|
||||
return "global function "..lua(t[1][1])..lua(t[2][1], "_functionWithoutKeyword")
|
||||
end,
|
||||
-- GlobalAll{ attribute? }
|
||||
GlobalAll = (t)
|
||||
if #t == 1 then
|
||||
return "global <" .. t[1] .. "> *"
|
||||
else
|
||||
return "global *"
|
||||
end
|
||||
end,
|
||||
-- Goto{ <string> }
|
||||
Goto = (t)
|
||||
return "goto "..lua(t, "Id")
|
||||
end,
|
||||
-- Label{ <string> }
|
||||
Label = (t)
|
||||
return "::"..lua(t, "Id").."::"
|
||||
end,
|
||||
-- Return{ <expr*> }
|
||||
Return = (t)
|
||||
local push = peek("push")
|
||||
if push then
|
||||
local r = ""
|
||||
for _, val in ipairs(t) do
|
||||
r ..= push.."[#"..push.."+1] = "..lua(val)..newline()
|
||||
end
|
||||
return r.."return "..UNPACK(push)
|
||||
else
|
||||
return "return "..lua(t, "_lhs")
|
||||
end
|
||||
end,
|
||||
-- Push{ <expr*> }
|
||||
Push = (t)
|
||||
local var = assert(peek("push"), "no context given for push")
|
||||
r = ""
|
||||
for i=1, #t-1, 1 do
|
||||
r ..= var.."[#"..var.."+1] = "..lua(t[i])..newline()
|
||||
end
|
||||
if t[#t] then
|
||||
if t[#t].tag == "Call" then
|
||||
r ..= APPEND(var, lua(t[#t]))
|
||||
else
|
||||
r ..= var.."[#"..var.."+1] = "..lua(t[#t])
|
||||
end
|
||||
end
|
||||
return r
|
||||
end,
|
||||
-- Break
|
||||
Break = ()
|
||||
return "break"
|
||||
end,
|
||||
-- Continue
|
||||
Continue = ()
|
||||
return "goto "..var"continue"
|
||||
end,
|
||||
-- apply (below)
|
||||
|
||||
-- expr --
|
||||
|
||||
-- Nil
|
||||
Nil = ()
|
||||
return "nil"
|
||||
end,
|
||||
-- Dots
|
||||
Dots = ()
|
||||
local macroargs = peek("macroargs")
|
||||
if macroargs and not nomacro.variables["..."] and macroargs["..."] then
|
||||
nomacro.variables["..."] = true
|
||||
local r = lua(macroargs["..."], "_lhs")
|
||||
nomacro.variables["..."] = nil
|
||||
return r
|
||||
else
|
||||
return "..."
|
||||
end
|
||||
end,
|
||||
-- Boolean{ <boolean> }
|
||||
Boolean = (t)
|
||||
return tostring(t[1])
|
||||
end,
|
||||
-- Number{ <string> }
|
||||
Number = (t)
|
||||
return tostring(t[1])
|
||||
end,
|
||||
-- String{ <string> }
|
||||
String = (t)
|
||||
return "%q":format(t[1])
|
||||
end,
|
||||
-- Function{ { ( `ParPair{ Id expr } | `Id{ <string> } )* `ParDots? } block }
|
||||
_functionParameter = {
|
||||
ParPair = (t, decl)
|
||||
local id = lua(t[1])
|
||||
indentLevel += 1
|
||||
table.insert(decl, "if "..id.." == nil then "..id.." = "..lua(t[2]).." end")
|
||||
indentLevel -= 1
|
||||
return id
|
||||
end,
|
||||
ParDots = (t, decl)
|
||||
if #t == 1 then
|
||||
return "..." .. lua(t[1])
|
||||
else
|
||||
return "..."
|
||||
end
|
||||
end,
|
||||
},
|
||||
_functionWithoutKeyword = (t)
|
||||
local r = "("
|
||||
local decl = {}
|
||||
local pars = {}
|
||||
for i=1, #t[1], 1 do
|
||||
if tags._functionParameter[t[1][i].tag] then
|
||||
table.insert(pars, tags._functionParameter[t[1][i].tag](t[1][i], decl))
|
||||
else
|
||||
table.insert(pars, lua(t[1][i]))
|
||||
end
|
||||
end
|
||||
r ..= table.concat(pars, ", ")..")"..indent()
|
||||
for _, d in ipairs(decl) do
|
||||
r ..= d..newline()
|
||||
end
|
||||
if t[2][#t[2]] and t[2][#t[2]].tag == "Push" then -- convert final push to return
|
||||
t[2][#t[2]].tag = "Return"
|
||||
end
|
||||
local hasPush = any(t[2], { "Push" }, func)
|
||||
if hasPush then
|
||||
r ..= push("push", var"push").."local "..var"push".." = {}"..newline()
|
||||
else
|
||||
push("push", false) -- no push here (make sure higher push doesn't affect us)
|
||||
end
|
||||
r ..= lua(t[2])
|
||||
if hasPush and (t[2][#t[2]] and t[2][#t[2]].tag ~= "Return") then -- add return only if needed
|
||||
r ..= newline().."return "..UNPACK(var"push")
|
||||
end
|
||||
pop("push")
|
||||
return r..unindent().."end"
|
||||
end,
|
||||
Function = (t)
|
||||
return "function"..lua(t, "_functionWithoutKeyword")
|
||||
end,
|
||||
-- Table{ ( `Pair{ expr expr } | expr )* }
|
||||
Pair = (t)
|
||||
return "["..lua(t[1]).."] = "..lua(t[2])
|
||||
end,
|
||||
Table = (t)
|
||||
if #t == 0 then
|
||||
return "{}"
|
||||
elseif #t == 1 then
|
||||
return "{ "..lua(t, "_lhs").." }"
|
||||
else
|
||||
return "{"..indent()..lua(t, "_lhs", nil, true)..unindent().."}"
|
||||
end
|
||||
end,
|
||||
-- TableCompr{ block }
|
||||
TableCompr = (t)
|
||||
return push("push", "self").."(function()"..indent().."local self = {}"..newline()..lua(t[1])..newline().."return self"..unindent().."end)()"..pop("push")
|
||||
end,
|
||||
-- Op{ opid expr expr? }
|
||||
Op = (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 = (t)
|
||||
return "("..lua(t[1])..")"
|
||||
end,
|
||||
-- MethodStub{ expr expr }
|
||||
MethodStub = (t)
|
||||
return "(function()"..indent() ..
|
||||
"local "..var"object".." = "..lua(t[1])..newline()..
|
||||
"local "..var"method".." = "..var"object".."."..lua(t[2], "Id")..newline() ..
|
||||
"if "..var"method".." == nil then return nil end"..newline()..
|
||||
"return function(...) return "..var"method".."("..var"object"..", ...) end"..unindent()..
|
||||
"end)()"
|
||||
end,
|
||||
-- SafeMethodStub{ expr expr }
|
||||
SafeMethodStub = (t)
|
||||
return "(function()"..indent() ..
|
||||
"local "..var"object".." = "..lua(t[1])..newline()..
|
||||
"if "..var"object".." == nil then return nil end"..newline()..
|
||||
"local "..var"method".." = "..var"object".."."..lua(t[2], "Id")..newline() ..
|
||||
"if "..var"method".." == nil then return nil end"..newline()..
|
||||
"return function(...) return "..var"method".."("..var"object"..", ...) end"..unindent()..
|
||||
"end)()"
|
||||
end,
|
||||
-- statexpr (below)
|
||||
-- apply (below)
|
||||
-- lhs (below)
|
||||
|
||||
-- lexpr --
|
||||
LetExpr = (t)
|
||||
return lua(t[1][1])
|
||||
end,
|
||||
|
||||
-- statexpr --
|
||||
_statexpr = (t, stat)
|
||||
local hasPush = any(t, { "Push" }, func)
|
||||
local r = "(function()"..indent()
|
||||
if hasPush then
|
||||
r ..= push("push", var"push").."local "..var"push".." = {}"..newline()
|
||||
else
|
||||
push("push", false) -- no push here (make sure higher push don't affect us)
|
||||
end
|
||||
r ..= lua(t, stat)
|
||||
if hasPush then
|
||||
r ..= newline().."return "..UNPACK(var"push")
|
||||
end
|
||||
pop("push")
|
||||
r ..= unindent().."end)()"
|
||||
return r
|
||||
end,
|
||||
-- DoExpr{ stat* }
|
||||
DoExpr = (t)
|
||||
if t[#t].tag == "Push" then -- convert final push to return
|
||||
t[#t].tag = "Return"
|
||||
end
|
||||
return lua(t, "_statexpr", "Do")
|
||||
end,
|
||||
-- WhileExpr{ expr block }
|
||||
WhileExpr = (t)
|
||||
return lua(t, "_statexpr", "While")
|
||||
end,
|
||||
-- RepeatExpr{ block expr }
|
||||
RepeatExpr = (t)
|
||||
return lua(t, "_statexpr", "Repeat")
|
||||
end,
|
||||
-- IfExpr{ (expr block)+ block? }
|
||||
IfExpr = (t)
|
||||
for i=2, #t do -- convert final pushes to returns
|
||||
local block = t[i]
|
||||
if block[#block] and block[#block].tag == "Push" then
|
||||
block[#block].tag = "Return"
|
||||
end
|
||||
end
|
||||
return lua(t, "_statexpr", "If")
|
||||
end,
|
||||
-- FornumExpr{ ident expr expr expr? block }
|
||||
FornumExpr = (t)
|
||||
return lua(t, "_statexpr", "Fornum")
|
||||
end,
|
||||
-- ForinExpr{ {ident+} {expr+} block }
|
||||
ForinExpr = (t)
|
||||
return lua(t, "_statexpr", "Forin")
|
||||
end,
|
||||
|
||||
-- apply --
|
||||
|
||||
-- Call{ expr expr* }
|
||||
Call = (t)
|
||||
if t[1].tag == "String" or t[1].tag == "Table" then
|
||||
return "("..lua(t[1])..")("..lua(t, "_lhs", 2)..")"
|
||||
elseif t[1].tag == "Id" and not nomacro.functions[t[1][1]] and macros.functions[t[1][1]] then
|
||||
local macro = macros.functions[t[1][1]]
|
||||
local replacement = macro.replacement
|
||||
local r
|
||||
nomacro.functions[t[1][1]] = true
|
||||
if type(replacement) == "function" then
|
||||
local args = {}
|
||||
for i=2, #t do
|
||||
table.insert(args, lua(t[i]))
|
||||
end
|
||||
r = replacement(unpack(args))
|
||||
else
|
||||
local macroargs = util.merge(peek("macroargs"))
|
||||
for i, arg in ipairs(macro.args) do
|
||||
if arg.tag == "Dots" then
|
||||
macroargs["..."] = [for j=i+1, #t do t[j] end]
|
||||
elseif arg.tag == "Id" then
|
||||
if t[i+1] == nil then
|
||||
error("bad argument #%s to macro %s (value expected)":format(i, t[1][1]))
|
||||
end
|
||||
macroargs[arg[1]] = t[i+1]
|
||||
else
|
||||
error("unexpected argument type %s in macro %s":format(arg.tag, t[1][1]))
|
||||
end
|
||||
end
|
||||
push("macroargs", macroargs)
|
||||
r = lua(replacement)
|
||||
pop("macroargs")
|
||||
end
|
||||
nomacro.functions[t[1][1]] = nil
|
||||
return r
|
||||
elseif t[1].tag == "MethodStub" then -- method call
|
||||
if t[1][1].tag == "String" or t[1][1].tag == "Table" then
|
||||
return "("..lua(t[1][1]).."):"..lua(t[1][2], "Id").."("..lua(t, "_lhs", 2)..")"
|
||||
else
|
||||
return lua(t[1][1])..":"..lua(t[1][2], "Id").."("..lua(t, "_lhs", 2)..")"
|
||||
end
|
||||
else
|
||||
return lua(t[1]).."("..lua(t, "_lhs", 2)..")"
|
||||
end
|
||||
end,
|
||||
-- SafeCall{ expr expr* }
|
||||
SafeCall = (t)
|
||||
if t[1].tag ~= "Id" then -- side effect possible, only evaluate each expr once (or already in a safe context)
|
||||
return lua(t, "SafeIndex")
|
||||
else -- no side effects possible
|
||||
return "("..lua(t[1]).." ~= nil and "..lua(t[1]).."("..lua(t, "_lhs", 2)..") or nil)"
|
||||
end
|
||||
end,
|
||||
|
||||
-- lhs --
|
||||
_lhs = (t, start=1, newlines)
|
||||
local r
|
||||
if t[start] then
|
||||
r = lua(t[start])
|
||||
for i=start+1, #t, 1 do
|
||||
r ..= ","..(newlines and newline() or " ")..lua(t[i])
|
||||
end
|
||||
else
|
||||
r = ""
|
||||
end
|
||||
return r
|
||||
end,
|
||||
-- Id{ <string> }
|
||||
Id = (t)
|
||||
local r = t[1]
|
||||
local macroargs = peek("macroargs")
|
||||
if not nomacro.variables[t[1]] then
|
||||
nomacro.variables[t[1]] = true
|
||||
if macroargs and macroargs[t[1]] then -- replace with macro argument
|
||||
r = lua(macroargs[t[1]])
|
||||
elseif macros.variables[t[1]] ~= nil then -- replace with macro variable
|
||||
local macro = macros.variables[t[1]]
|
||||
if type(macro) == "function" then
|
||||
r = macro()
|
||||
else
|
||||
r = lua(macro)
|
||||
end
|
||||
end
|
||||
nomacro.variables[t[1]] = nil
|
||||
end
|
||||
return r
|
||||
end,
|
||||
-- PrefixedAttributeNameList{ attribute {AttributeId+} }
|
||||
PrefixedAttributeNameList = (t)
|
||||
return "<" .. t[1] .. "> " .. lua(t, "_lhs", 2)
|
||||
end,
|
||||
-- AttributeNameList{ {AttributeId+} }
|
||||
AttributeNameList = (t)
|
||||
return lua(t, "_lhs")
|
||||
end,
|
||||
-- NameList{ {Id+} }
|
||||
NameList = (t)
|
||||
return lua(t, "_lhs")
|
||||
end,
|
||||
-- AttributeId{ <string> <string>? }
|
||||
AttributeId = (t)
|
||||
if t[2] then
|
||||
return t[1] .. " <" .. t[2] .. ">"
|
||||
else
|
||||
return t[1]
|
||||
end
|
||||
end,
|
||||
-- DestructuringId{ Id | Pair+ }
|
||||
DestructuringId = (t)
|
||||
if t.id then -- destructing already done before, use parent variable as id
|
||||
return t.id
|
||||
else
|
||||
local d = assert(peek("destructuring"), "DestructuringId not in a destructurable assignment")
|
||||
local vars = { id = tmp() }
|
||||
for j=1, #t, 1 do
|
||||
table.insert(vars, t[j])
|
||||
end
|
||||
table.insert(d, vars)
|
||||
t.id = vars.id
|
||||
return vars.id
|
||||
end
|
||||
end,
|
||||
-- Index{ expr expr }
|
||||
Index = (t)
|
||||
if t[1].tag == "String" or t[1].tag == "Table" then
|
||||
return "("..lua(t[1])..")["..lua(t[2]).."]"
|
||||
else
|
||||
return lua(t[1]).."["..lua(t[2]).."]"
|
||||
end
|
||||
end,
|
||||
-- SafeIndex{ expr expr }
|
||||
SafeIndex = (t)
|
||||
if t[1].tag ~= "Id" then -- side effect possible, only evaluate each expr once (or already in a safe context)
|
||||
local l = {} -- list of immediately chained safeindex, from deepest to nearest (to simply generated code)
|
||||
while t.tag == "SafeIndex" or t.tag == "SafeCall" do
|
||||
table.insert(l, 1, t)
|
||||
t = t[1]
|
||||
end
|
||||
local r = "(function()"..indent().."local "..var"safe".." = "..lua(l[1][1])..newline() -- base expr
|
||||
for _, e in ipairs(l) do
|
||||
r ..= "if "..var"safe".." == nil then return nil end"..newline()
|
||||
if e.tag == "SafeIndex" then
|
||||
r ..= var"safe".." = "..var"safe".."["..lua(e[2]).."]"..newline()
|
||||
else
|
||||
r ..= var"safe".." = "..var"safe".."("..lua(e, "_lhs", 2)..")"..newline()
|
||||
end
|
||||
end
|
||||
r ..= "return "..var"safe"..unindent().."end)()"
|
||||
return r
|
||||
else -- no side effects possible
|
||||
return "("..lua(t[1]).." ~= nil and "..lua(t[1]).."["..lua(t[2]).."] or nil)"
|
||||
end
|
||||
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 = (self, key)
|
||||
error("don't know how to compile a "..tostring(key).." to "..targetName)
|
||||
end
|
||||
})
|
||||
|
||||
#placeholder("patch")
|
||||
|
||||
local code = lua(ast)..newline()
|
||||
return requireStr..code
|
||||
end
|
||||
|
|
@ -2,12 +2,12 @@ rockspec_format = "3.0"
|
|||
|
||||
package = "candran"
|
||||
|
||||
version = "1.1.0-1"
|
||||
version = "1.0.0-1"
|
||||
|
||||
description = {
|
||||
summary = "A simple Lua dialect and preprocessor.",
|
||||
detailed = [[
|
||||
Candran is a dialect of the Lua 5.4 programming language which compiles to Lua 5.5, Lua 5.4, Lua 5.3, Lua 5.2, LuaJIT and Lua 5.1 compatible code. It adds several useful syntax additions which aims to make Lua faster and easier to write, and a simple preprocessor.
|
||||
Candran is a dialect of the Lua 5.4 programming language which compiles to Lua 5.4, Lua 5.3, Lua 5.2, LuaJIT and Lua 5.1 compatible code. It adds several useful syntax additions which aims to make Lua faster and easier to write, and a simple preprocessor.
|
||||
Unlike Moonscript, Candran tries to stay close to the Lua syntax, and existing Lua code should be able to run on Candran unmodified.
|
||||
]],
|
||||
license = "MIT",
|
||||
|
|
@ -19,7 +19,7 @@ description = {
|
|||
|
||||
source = {
|
||||
url = "git://github.com/Reuh/candran",
|
||||
tag = "v1.1.0"
|
||||
tag = "v1.0.0"
|
||||
}
|
||||
|
||||
dependencies = {
|
||||
174
test/test.lua
174
test/test.lua
|
|
@ -41,11 +41,6 @@ local function test(name, candranCode, expectedResult, options)
|
|||
results[name] = { result = "not finished", message = "no info" }
|
||||
local self = results[name]
|
||||
|
||||
-- result
|
||||
if type(expectedResult) ~= "table" then
|
||||
expectedResult = { "return", expectedResult }
|
||||
end
|
||||
|
||||
-- options
|
||||
options = options or {}
|
||||
options.chunkname = name
|
||||
|
|
@ -61,33 +56,25 @@ local function test(name, candranCode, expectedResult, options)
|
|||
-- load code
|
||||
local env = {}
|
||||
for k, v in pairs(_G) do env[k] = v end
|
||||
local func, err = load(code, name, env)
|
||||
if err then
|
||||
if expectedResult[1] == "loadError" and err:match(expectedResult[2] or "") then
|
||||
self.result = "success"
|
||||
return
|
||||
end
|
||||
local success, func = pcall(load, code, nil, env)
|
||||
if not success then
|
||||
self.result = "error"
|
||||
self.message = c("/!\\ error while loading code:\n"..err.."\ngenerated code:\n", "bold", "red")..c(code, "red")
|
||||
self.message = c("/!\\ error while loading code:\n"..func.."\ngenerated code:\n", "bold", "red")..c(code, "red")
|
||||
return
|
||||
end
|
||||
|
||||
-- run code
|
||||
local success, output = pcall(func)
|
||||
if not success then
|
||||
if expectedResult[1] == "runtimeError" and output:match(expectedResult[2] or "") then
|
||||
self.result = "success"
|
||||
return
|
||||
end
|
||||
self.result = "error"
|
||||
self.message = c("/!\\ error while running code:\n"..output.."\ngenerated code:\n", "bold", "red")..c(code, "red")
|
||||
return
|
||||
end
|
||||
|
||||
-- check result
|
||||
if expectedResult[1] == "return" and output ~= expectedResult[2] then
|
||||
if output ~= expectedResult then
|
||||
self.result = "fail"
|
||||
self.message = c("/!\\ invalid result from the code; it returned "..tostring(output).." instead of "..tostring(expectedResult[2]).."; generated code:\n", "bold", "purple")..c(code, "purple")
|
||||
self.message = c("/!\\ invalid result from the code; it returned "..tostring(output).." instead of "..tostring(expectedResult).."; generated code:\n", "bold", "purple")..c(code, "purple")
|
||||
return
|
||||
else
|
||||
self.result = "success"
|
||||
|
|
@ -227,7 +214,7 @@ return hello
|
|||
-- SYNTAX ADDITIONS --
|
||||
----------------------
|
||||
|
||||
-- Assignment operators
|
||||
-- Assignement operators
|
||||
test("+=", [[
|
||||
local a = 5
|
||||
a += 2
|
||||
|
|
@ -299,7 +286,7 @@ test(">>=", [[
|
|||
return a
|
||||
]], 5)
|
||||
|
||||
test("right assignments operators", [[
|
||||
test("right assigments operators", [[
|
||||
local a = 5
|
||||
a =+ 2 assert(a == 7, "=+")
|
||||
a =- 2 assert(a == -5, "=-")
|
||||
|
|
@ -325,7 +312,7 @@ test("right assignments operators", [[
|
|||
a =>> 23 assert(a == 5, "=>>")
|
||||
]], nil)
|
||||
|
||||
test("some left+right assignments operators", [[
|
||||
test("some left+right assigments operators", [[
|
||||
local a = 5
|
||||
a -=+ 2 assert(a == 8, "-=+")
|
||||
|
||||
|
|
@ -333,17 +320,17 @@ test("some left+right assignments operators", [[
|
|||
a ..=.. " world " assert(a == "hello world hello", "..=..")
|
||||
]], nil)
|
||||
|
||||
test("left assignments operators priority", [[
|
||||
test("left assigments operators priority", [[
|
||||
local a = 5
|
||||
a *= 2 + 3
|
||||
return a
|
||||
]], 25)
|
||||
test("right assignments operators priority", [[
|
||||
test("right assigments operators priority", [[
|
||||
local a = 5
|
||||
a =/ 2 + 3
|
||||
return a
|
||||
]], 1)
|
||||
test("left+right assignments operators priority", [[
|
||||
test("left+right assigments operators priority", [[
|
||||
local a = 5
|
||||
a *=/ 2 + 3
|
||||
return a
|
||||
|
|
@ -928,63 +915,63 @@ test("safe prefixes, random chaining", [[
|
|||
assert(f.l?:o?() == nil)
|
||||
]])
|
||||
|
||||
-- Destructuring assignments
|
||||
test("destructuring assignment with an expression", [[
|
||||
-- Destructuring assigments
|
||||
test("destructuring assignement with an expression", [[
|
||||
local {x, y} = { x = 5, y = 1 }
|
||||
return x + y
|
||||
]], 6)
|
||||
test("destructuring assignment with local", [[
|
||||
test("destructuring assignement with local", [[
|
||||
t = { x = 5, y = 1 }
|
||||
local {x, y} = t
|
||||
return x + y
|
||||
]], 6)
|
||||
test("destructuring assignment", [[
|
||||
test("destructuring assignement", [[
|
||||
t = { x = 5, y = 1 }
|
||||
{x, y} = t
|
||||
return x + y
|
||||
]], 6)
|
||||
test("destructuring assignment with +=", [[
|
||||
test("destructuring assignement with +=", [[
|
||||
t = { x = 5, y = 1 }
|
||||
local x, y = 5, 9
|
||||
{x, y} += t
|
||||
return x + y
|
||||
]], 20)
|
||||
test("destructuring assignment with =-", [[
|
||||
test("destructuring assignement with =-", [[
|
||||
t = { x = 5, y = 1 }
|
||||
local x, y = 5, 9
|
||||
{x, y} =- t
|
||||
return x + y
|
||||
]], -8)
|
||||
test("destructuring assignment with +=-", [[
|
||||
test("destructuring assignement with +=-", [[
|
||||
t = { x = 5, y = 1 }
|
||||
local x, y = 5, 9
|
||||
{x, y} +=- t
|
||||
return x + y
|
||||
]], 6)
|
||||
test("destructuring assignment with =-", [[
|
||||
test("destructuring assignement with =-", [[
|
||||
t = { x = 5, y = 1 }
|
||||
local x, y = 5, 9
|
||||
{x, y} =- t
|
||||
return x + y
|
||||
]], -8)
|
||||
test("destructuring assignment with let", [[
|
||||
test("destructuring assignement with let", [[
|
||||
t = { x = 5, y = 1 }
|
||||
let {x, y} = t
|
||||
return x + y
|
||||
]], 6)
|
||||
test("destructuring assignment with for in", [[
|
||||
test("destructuring assignement with for in", [[
|
||||
t = {{ x = 5, y = 1 }}
|
||||
for k, {x, y} in pairs(t) do
|
||||
return x + y
|
||||
end
|
||||
]], 6)
|
||||
test("destructuring assignment with if with assignment", [[
|
||||
test("destructuring assignement with if with assignement", [[
|
||||
t = { x = 5, y = 1 }
|
||||
if {x, y} = t then
|
||||
return x + y
|
||||
end
|
||||
]], 6)
|
||||
test("destructuring assignment with if-elseif with assignment", [[
|
||||
test("destructuring assignement with if-elseif with assignement", [[
|
||||
t = { x = 5, y = 1 }
|
||||
if ({u} = t) and u then
|
||||
return 0
|
||||
|
|
@ -993,56 +980,56 @@ test("destructuring assignment with if-elseif with assignment", [[
|
|||
end
|
||||
]], 6)
|
||||
|
||||
test("destructuring assignment with an expression with custom name", [[
|
||||
test("destructuring assignement with an expression with custom name", [[
|
||||
local {o = x, y} = { o = 5, y = 1 }
|
||||
return x + y
|
||||
]], 6)
|
||||
test("destructuring assignment with local with custom name", [[
|
||||
test("destructuring assignement with local with custom name", [[
|
||||
t = { o = 5, y = 1 }
|
||||
local {o = x, y} = t
|
||||
return x + y
|
||||
]], 6)
|
||||
test("destructuring assignment with custom name", [[
|
||||
test("destructuring assignement with custom name", [[
|
||||
t = { o = 5, y = 1 }
|
||||
{o = x, y} = t
|
||||
return x + y
|
||||
]], 6)
|
||||
test("destructuring assignment with += with custom name", [[
|
||||
test("destructuring assignement with += with custom name", [[
|
||||
t = { o = 5, y = 1 }
|
||||
local x, y = 5, 9
|
||||
{o = x, y} += t
|
||||
return x + y
|
||||
]], 20)
|
||||
test("destructuring assignment with =- with custom name", [[
|
||||
test("destructuring assignement with =- with custom name", [[
|
||||
t = { o = 5, y = 1 }
|
||||
local x, y = 5, 9
|
||||
{o = x, y} =- t
|
||||
return x + y
|
||||
]], -8)
|
||||
test("destructuring assignment with +=- with custom name", [[
|
||||
test("destructuring assignement with +=- with custom name", [[
|
||||
t = { o = 5, y = 1 }
|
||||
local x, y = 5, 9
|
||||
{o = x, y} +=- t
|
||||
return x + y
|
||||
]], 6)
|
||||
test("destructuring assignment with let with custom name", [[
|
||||
test("destructuring assignement with let with custom name", [[
|
||||
t = { o = 5, y = 1 }
|
||||
let {o = x, y} = t
|
||||
return x + y
|
||||
]], 6)
|
||||
test("destructuring assignment with for in with custom name", [[
|
||||
test("destructuring assignement with for in with custom name", [[
|
||||
t = {{ o = 5, y = 1 }}
|
||||
for k, {o = x, y} in pairs(t) do
|
||||
return x + y
|
||||
end
|
||||
]], 6)
|
||||
test("destructuring assignment with if with assignment with custom name", [[
|
||||
test("destructuring assignement with if with assignement with custom name", [[
|
||||
t = { o = 5, y = 1 }
|
||||
if {o = x, y} = t then
|
||||
return x + y
|
||||
end
|
||||
]], 6)
|
||||
test("destructuring assignment with if-elseif with assignment with custom name", [[
|
||||
test("destructuring assignement with if-elseif with assignement with custom name", [[
|
||||
t = { o = 5, y = 1 }
|
||||
if ({x} = t) and x then
|
||||
return 0
|
||||
|
|
@ -1051,56 +1038,56 @@ test("destructuring assignment with if-elseif with assignment with custom name",
|
|||
end
|
||||
]], 6)
|
||||
|
||||
test("destructuring assignment with an expression with expression as key", [[
|
||||
test("destructuring assignement with an expression with expression as key", [[
|
||||
local {[1] = x, y} = { 5, y = 1 }
|
||||
return x + y
|
||||
]], 6)
|
||||
test("destructuring assignment with local with expression as key", [[
|
||||
test("destructuring assignement with local with expression as key", [[
|
||||
t = { 5, y = 1 }
|
||||
local {[1] = x, y} = t
|
||||
return x + y
|
||||
]], 6)
|
||||
test("destructuring assignment with expression as key", [[
|
||||
test("destructuring assignement with expression as key", [[
|
||||
t = { 5, y = 1 }
|
||||
{[1] = x, y} = t
|
||||
return x + y
|
||||
]], 6)
|
||||
test("destructuring assignment with += with expression as key", [[
|
||||
test("destructuring assignement with += with expression as key", [[
|
||||
t = { 5, y = 1 }
|
||||
local x, y = 5, 9
|
||||
{[1] = x, y} += t
|
||||
return x + y
|
||||
]], 20)
|
||||
test("destructuring assignment with =- with expression as key", [[
|
||||
test("destructuring assignement with =- with expression as key", [[
|
||||
t = { 5, y = 1 }
|
||||
local x, y = 5, 9
|
||||
{[1] = x, y} =- t
|
||||
return x + y
|
||||
]], -8)
|
||||
test("destructuring assignment with +=- with expression as key", [[
|
||||
test("destructuring assignement with +=- with expression as key", [[
|
||||
t = { 5, y = 1 }
|
||||
local x, y = 5, 9
|
||||
{[1] = x, y} +=- t
|
||||
return x + y
|
||||
]], 6)
|
||||
test("destructuring assignment with let with expression as key", [[
|
||||
test("destructuring assignement with let with expression as key", [[
|
||||
t = { 5, y = 1 }
|
||||
let {[1] = x, y} = t
|
||||
return x + y
|
||||
]], 6)
|
||||
test("destructuring assignment with for in with expression as key", [[
|
||||
test("destructuring assignement with for in with expression as key", [[
|
||||
t = {{ 5, y = 1 }}
|
||||
for k, {[1] = x, y} in pairs(t) do
|
||||
return x + y
|
||||
end
|
||||
]], 6)
|
||||
test("destructuring assignment with if with assignment with expression as key", [[
|
||||
test("destructuring assignement with if with assignement with expression as key", [[
|
||||
t = { 5, y = 1 }
|
||||
if {[1] = x, y} = t then
|
||||
return x + y
|
||||
end
|
||||
]], 6)
|
||||
test("destructuring assignment with if-elseif with assignment with expression as key", [[
|
||||
test("destructuring assignement with if-elseif with assignement with expression as key", [[
|
||||
t = { 5, y = 1 }
|
||||
if ({x} = t) and x then
|
||||
return 0
|
||||
|
|
@ -1109,81 +1096,6 @@ test("destructuring assignment with if-elseif with assignment with expression as
|
|||
end
|
||||
]], 6)
|
||||
|
||||
-- named varargs
|
||||
test("named varargs", [[
|
||||
function fn(...x)
|
||||
return table.concat(x, "-")
|
||||
end
|
||||
return fn("hello", "world")
|
||||
]], "hello-world")
|
||||
test("named varargs with preceding arguments", [[
|
||||
function fn(a, ...x)
|
||||
return a..table.concat(x, "-")
|
||||
end
|
||||
return fn("hey! ", "hello", "world")
|
||||
]], "hey! hello-world")
|
||||
|
||||
-- variable attributes
|
||||
if _VERSION >= "Lua 5.4" then
|
||||
test("variable attributes", [[
|
||||
local a <const>, b, c <close>
|
||||
b = 42
|
||||
]])
|
||||
|
||||
test("variable attributes: const violation", [[
|
||||
local a <const>, b, c <close>
|
||||
b = 42
|
||||
a = 42
|
||||
]], { "loadError", "attempt to assign to const variable 'a'" })
|
||||
|
||||
test("variable attributes: const violation 2", [[
|
||||
local <const> d, e, f
|
||||
e = 42
|
||||
]], { "loadError", "attempt to assign to const variable 'e'" })
|
||||
|
||||
test("variable attributes shortcut", [[
|
||||
const a
|
||||
a = 42
|
||||
]], { "loadError", "attempt to assign to const variable 'a'" })
|
||||
end
|
||||
|
||||
-- global keyword
|
||||
test("collective global variable declaration", [[
|
||||
foo = 42
|
||||
do
|
||||
global *
|
||||
end
|
||||
foo = 12
|
||||
]])
|
||||
if _VERSION >= "Lua 5.5" then
|
||||
test("global variable declaration", [[
|
||||
global foo = 42
|
||||
]])
|
||||
test("global variable declaration with error", [[
|
||||
global foo = 42
|
||||
bar = 12
|
||||
]], { "loadError", "variable 'bar' not declared" })
|
||||
test("global variable declaration with attribute", [[
|
||||
global <const> bar
|
||||
bar = 42
|
||||
]], { "loadError", "attempt to assign to const variable 'bar'" })
|
||||
test("collective global variable declaration with attribute", [[
|
||||
foo = 42
|
||||
global<const> *
|
||||
foo = 12
|
||||
]], { "loadError", "attempt to assign to const variable 'foo'" })
|
||||
end
|
||||
|
||||
-- bitwise operators
|
||||
if _VERSION >= "Lua 5.2" or bit then
|
||||
test("bitwise operators: &", "return 0xDEAD & 0xBEEF", 40621)
|
||||
test("bitwise operators: |", "return 0xDEAD | 0xBEEF", 65263)
|
||||
test("bitwise operators: ~", "return 0xDEAD ~ 0xBEEF", 24642)
|
||||
test("bitwise operators: >>", "return 0xDEAD >> 2", 14251)
|
||||
test("bitwise operators: <<", "return 0xDEAD << 2", 228020)
|
||||
test("bitwise operators: unary ~", "return ~0xDEAD", -57006)
|
||||
end
|
||||
|
||||
-- results
|
||||
local resultCounter = {}
|
||||
local testCounter = 0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue