1
0
Fork 0
mirror of https://github.com/Reuh/candran.git synced 2026-02-04 10:18:38 +00:00

Compare commits

..

12 commits

11 changed files with 8664 additions and 7137 deletions

View file

@ -1,6 +1,6 @@
Candran Candran
======= =======
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. 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.
Unlike Moonscript, Candran tries to stay close to the Lua syntax, and existing Lua code should be able to run on Candran unmodified. 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 a.child?:method?() -- safe navigation operator
local {hey, method} = a -- destructuring assignement local {hey, method} = a -- destructuring assignment
local odd = [ -- table comprehension local odd = [ -- table comprehension
for i=1, 10 do 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 local a = if condition then "one" else "two" end -- statement as expressions
print("Hello %s":format("world")) -- methods calls on strings (and tables) litterals without enclosing parentheses print("Hello %s":format("world")) -- methods calls on strings (and tables) literals without enclosing parentheses
if f, err = io.open("data") then -- if condition with assignements if f, err = io.open("data") then -- if condition with assignments
thing.process(f) thing.process(f)
else else
error("can't open data: "..err) error("can't open data: "..err)
@ -76,7 +76,7 @@ end
```` ````
**Current status**: Candran is heavily used in several of my personal projects and works as expected. **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.
Candran is released under the MIT License (see ```LICENSE``` for details). 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 * [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-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) * [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 Candran syntax * **VS Code**: [vscode-candran](https://github.com/Reuh/vscode-candran) basic support for the full Candran syntax
* **Atom**: [language-candran](https://atom.io/packages/language-candran) support the full 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. 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 The language
------------ ------------
### Syntax additions ### Syntax additions
After the [preprocessor](#preprocessor) is run the Candran code is compiled to Lua. Candran code adds the folowing syntax to Lua 5.4 syntax: After the [preprocessor](#preprocessor) is run the Candran code is compiled to Lua. Candran code adds the folowing syntax to standard Lua syntax:
##### Assignment operators ##### Assignment operators
* ````var += nb```` * ````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````. For example, a ````var += nb```` assignment will be compiled into ````var = var + nb````.
All theses operators can also be put right of the assigment operator, in which case ```var =+ nb``` will be compiled into ```var = nb + var```. All theses operators can also be put right of the assignment operator, in which case ```var =+ nb``` will be compiled into ```var = nb + var```.
Right and left operator can be used at the same time. 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. 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 assignement ##### Destructuring assignment
```lua ```lua
t = { x = 1, y = 2, z = 3 } t = { x = 1, y = 2, z = 3 }
@ -284,21 +284,21 @@ t = { x = 1, y = 2, z = 3 }
{["x"] = o} = t -- o = t["x"] {["x"] = o} = t -- o = t["x"]
-- Also works with local, let, for ... in, if with assignement, +=, etc. -- Also works with local, let, for ... in, if with assignment, +=, etc.
local {x, y} = t local {x, y} = t
let {x, y} = t let {x, y} = t
for i, {x, y} in ipairs{t} do end for i, {x, y} in ipairs{t} do end
if {x, y} = t then end if {x, y} = t then end
{x} += t -- x = x + t.x {x} += t -- x = x + t.x
-- Works as expected with multiple assignement. -- Works as expected with multiple assignment.
a, {x, y, z}, b = 1, t, 2 a, {x, y, z}, b = 1, t, 2
``` ```
Destruturing assignement allows to quickly extract fields from a table into a variable. Destruturing assignment allows to quickly extract fields from a table into a variable.
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`). 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`).
##### Safe navigation operators ##### 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 `?(...)`. 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 assignement in the condition ##### If and while with assignment in the condition
```lua ```lua
if f, err = io.open("somefile") then -- condition if verified if f is a truthy value (not nil or false) if f, err = io.open("somefile") then -- condition if verified if f is a truthy value (not nil or false)
-- do something with f -- do something with f
@ -344,7 +344,7 @@ else
end end
-- f, err, f2 and err2 are now out of scope -- f, err, f2 and err2 are now out of scope
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. 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.
print(index, value) print(index, value)
end end
@ -353,13 +353,13 @@ while line = io.read() do
print(line) print(line)
end end
-- The assignement have the same priority as regular assignements, i.e., the lowest. -- The assignment have the same priority as regular assignments, i.e., the lowest.
if a = 1 and 2 then -- will be read as a = (1 and 2) 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 elseif (a = 1) and 2 then -- will be read as (a = 1) and 2
end end
``` ```
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. 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.
For while statements, the assigned expression will be reevaluated at each iteration. For while statements, the assigned expression will be reevaluated at each iteration.
@ -499,19 +499,22 @@ You can disable these built-in macros using the `builtInMacros` compiler option.
Compile targets Compile targets
--------------- ---------------
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. 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.
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. 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.
Candran will try to translate Lua 5.4 syntax into something usable with the current target if possible. Here is what is currently supported: 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:
| Lua version | Candran target | Integer division operator // | Bitwise operators | Goto/Labels | Variable attributes | | Lua version | Candran target | Bitwise operators | Goto/Labels | Variable attributes | Global keyword |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| Lua 5.4 | lua54 | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Lua 5.5 | lua55 | :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.4 | lua54 | :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 | | Lua 5.3 | lua53 | :white_check_mark: | :white_check_mark: | X | X |
| LuaJIT | luajit | :white_check_mark: | :white_check_mark: (32bit) | :white_check_mark: | X | | Lua 5.2 | lua52 | :white_check_mark: (32bit) | :white_check_mark: | X | X |
| Lua 5.1 | lua51 | :white_check_mark: | :white_check_mark: if LuaJIT bit library is available (32bit) | 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.
**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. **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.
@ -685,7 +688,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: You can give arbitrary options to the compiler and preprocessor, but Candran already provide and uses these with their associated default values:
```lua ```lua
target = "lua53" -- compiler target. "lua54", "lua53", "lua52", "luajit" or "lua51" (default is automatically selected based on the Lua version used). target = "lua55" -- compiler target. "lua55", "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. indentation = "" -- character(s) used for indentation in the compiled file.
newline = "\n" -- character(s) used for newlines 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). 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).

View file

@ -1,11 +1,12 @@
local candran = { local candran = {
VERSION = "1.0.0" VERSION = "1.1.0"
} }
package.loaded["candran"] = candran package.loaded["candran"] = candran
#import("candran.util") #import("candran.util")
#import("candran.serpent") #import("candran.serpent")
#import("compiler.lua55")
#import("compiler.lua54") #import("compiler.lua54")
#import("compiler.lua53") #import("compiler.lua53")
#import("compiler.lua52") #import("compiler.lua52")
@ -21,7 +22,7 @@ local unpack = unpack or table.unpack
--- Default options. --- Default options.
candran.default = { candran.default = {
target = "lua54", target = "lua55",
indentation = "", indentation = "",
newline = "\n", newline = "\n",
variablePrefix = "__CAN_", variablePrefix = "__CAN_",
@ -44,6 +45,8 @@ elseif _VERSION == "Lua 5.2" then
candran.default.target = "lua52" candran.default.target = "lua52"
elseif _VERSION == "Lua 5.3" then elseif _VERSION == "Lua 5.3" then
candran.default.target = "lua53" candran.default.target = "lua53"
elseif _VERSION == "Lua 5.4" then
candran.default.target = "lua54"
end end
--- Run the preprocessor --- Run the preprocessor

13616
candran.lua

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
--[[ --[[
This module implements a parser for Lua 5.3 with LPeg, This module implements a parser for Lua 5.5 with LPeg,
and generates an Abstract Syntax Tree that is similar to the one generated by Metalua. and generates an Abstract Syntax Tree that is similar to the one generated by Metalua.
For more information about Metalua, please, visit: For more information about Metalua, please, visit:
https://github.com/fab13n/metalua-parser https://github.com/fab13n/metalua-parser
@ -15,8 +15,11 @@ stat:
| `Fornum{ ident expr expr expr? block } -- for ident = e, e[, e] do b end | `Fornum{ ident expr expr expr? block } -- for ident = e, e[, e] do b end
| `Forin{ {ident+} {expr+} block } -- for i1, i2... in e1, e2... do b end | `Forin{ {ident+} {expr+} block } -- for i1, i2... in e1, e2... do b end
| `Local{ {attributeident+} {expr+}? } -- local i1, i2... = e1, e2... | `Local{ {attributeident+} {expr+}? } -- local i1, i2... = e1, e2...
| `Global{ {attributeident+} {expr+}? } -- global i1, i2... = e1, e2...
| `Let{ {ident+} {expr+}? } -- let i1, i2... = e1, e2... | `Let{ {ident+} {expr+}? } -- let i1, i2... = e1, e2...
| `Localrec{ {ident} {expr} } -- only used for 'local function' | `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 | `Goto{ <string> } -- goto str
| `Label{ <string> } -- ::str:: | `Label{ <string> } -- ::str::
| `Return{ <expr*> } -- return e1, e2... | `Return{ <expr*> } -- return e1, e2...
@ -31,7 +34,7 @@ expr:
| `Boolean{ <boolean> } | `Boolean{ <boolean> }
| `Number{ <string> } -- we don't use convert to number to avoid losing precision when tostring()-ing it later | `Number{ <string> } -- we don't use convert to number to avoid losing precision when tostring()-ing it later
| `String{ <string> } | `String{ <string> }
| `Function{ { ( `ParPair{ Id expr } | `Id{ <string> } )* `Dots? } block } | `Function{ { ( `ParPair{ Id expr } | `Id{ <string> } )* `ParDots? } block }
| `Table{ ( `Pair{ expr expr } | expr )* } | `Table{ ( `Pair{ expr expr } | expr )* }
| `Op{ opid expr expr? } | `Op{ opid expr expr? }
| `Paren{ expr } -- significant to cut multiple values returns | `Paren{ expr } -- significant to cut multiple values returns
@ -112,6 +115,7 @@ local labels = {
{ "ErrEListFor", "expected one or more expressions after 'in'" }, { "ErrEListFor", "expected one or more expressions after 'in'" },
{ "ErrDoFor", "expected 'do' after the range of the for loop" }, { "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" }, { "ErrDefLocal", "expected a function definition or assignment after local" },
{ "ErrDefLet", "expected an assignment after let" }, { "ErrDefLet", "expected an assignment after let" },
{ "ErrDefClose", "expected an assignment after close" }, { "ErrDefClose", "expected an assignment after close" },
@ -340,10 +344,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) 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 for i, stat in ipairs(block) do
-- Non recursive statements -- 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" 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" or stat.tag == "Global" or stat.tag == "Globalrec" then
local exprlist local exprlist
if stat.tag == "Set" or stat.tag == "Local" or stat.tag == "Let" or stat.tag == "Localrec" then 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
exprlist = stat[#stat] exprlist = stat[#stat]
elseif stat.tag == "Push" or stat.tag == "Return" then elseif stat.tag == "Push" or stat.tag == "Return" then
exprlist = stat exprlist = stat
@ -515,7 +519,7 @@ local G = { V"Lua",
Block = tagC("Block", (V"Stat" + -V"BlockEnd" * throw("InvalidStat"))^0 * ((V"RetStat" + V"ImplicitPushStat") * sym(";")^-1)^-1); 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" Stat = V"IfStat" + V"DoStat" + V"WhileStat" + V"RepeatStat" + V"ForStat"
+ V"LocalStat" + V"FuncStat" + V"BreakStat" + V"LabelStat" + V"GoToStat" + V"LocalStat" + V"GlobalStat" + V"FuncStat" + V"BreakStat" + V"LabelStat" + V"GoToStat"
+ V"LetStat" + V"ConstStat" + V"CloseStat" + V"LetStat" + V"ConstStat" + V"CloseStat"
+ V"FuncCall" + V"Assignment" + V"FuncCall" + V"Assignment"
+ V"ContinueStat" + V"PushStat" + V"ContinueStat" + V"PushStat"
@ -542,6 +546,12 @@ local G = { V"Lua",
ForIn = tagC("Forin", V"DestructuringNameList" * expect(kw("in"), "InFor") * expect(V"ExprList", "EListFor") * V"ForBody"); ForIn = tagC("Forin", V"DestructuringNameList" * expect(kw("in"), "InFor") * expect(V"ExprList", "EListFor") * V"ForBody");
ForBody = expectBlockOrSingleStatWithStartEnd(kw("do"), "DoFor", "EndFor"); 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"); LocalStat = kw("local") * expect(V"LocalFunc" + V"LocalAssign", "DefLocal");
LocalFunc = tagC("Localrec", kw("function") * expect(V"Id", "NameLFunc") * V"FuncBody") / fixFuncStat; LocalFunc = tagC("Localrec", kw("function") * expect(V"Id", "NameLFunc") * V"FuncBody") / fixFuncStat;
LocalAssign = tagC("Local", V"AttributeNameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc()))) LocalAssign = tagC("Local", V"AttributeNameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc())))
@ -551,9 +561,9 @@ local G = { V"Lua",
LetAssign = tagC("Let", V"NameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc()))) LetAssign = tagC("Let", V"NameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc())))
+ tagC("Let", V"DestructuringNameList" * sym("=") * expect(V"ExprList", "EListLAssign")); + tagC("Let", V"DestructuringNameList" * sym("=") * expect(V"ExprList", "EListLAssign"));
ConstStat = kw("const") * expect(V"AttributeAssign" / setAttribute("const"), "DefConst"); ConstStat = kw("const") * expect(V "LocalAssignNoAttribute" / setAttribute("const"), "DefConst"),
CloseStat = kw("close") * expect(V"AttributeAssign" / setAttribute("close"), "DefClose"); CloseStat = kw("close") * expect(V "LocalAssignNoAttribute" / setAttribute("close"), "DefClose"),
AttributeAssign = tagC("Local", V"NameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc()))) LocalAssignNoAttribute = tagC("Local", V"NameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc())))
+ tagC("Local", V"DestructuringNameList" * sym("=") * expect(V"ExprList", "EListLAssign")); + 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")); 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"));
@ -563,8 +573,8 @@ local G = { V"Lua",
* (sym(":") * expect(V"StrId", "NameFunc2"))^-1 / markMethod; * (sym(":") * expect(V"StrId", "NameFunc2"))^-1 / markMethod;
FuncBody = tagC("Function", V"FuncParams" * expectBlockWithEnd("EndFunc")); FuncBody = tagC("Function", V"FuncParams" * expectBlockWithEnd("EndFunc"));
FuncParams = expect(sym("("), "OParenPList") * V"ParList" * expect(sym(")"), "CParenPList"); FuncParams = expect(sym("("), "OParenPList") * V"ParList" * expect(sym(")"), "CParenPList");
ParList = V"NamedParList" * (sym(",") * expect(tagC("Dots", sym("...")), "ParList"))^-1 / addDots ParList = V"NamedParList" * (sym(",") * expect(tagC("ParDots", sym("...") * V"Id"^-1), "ParList"))^-1 / addDots
+ Ct(tagC("Dots", sym("..."))) + Ct(tagC("ParDots", sym("...") * V"Id"^-1))
+ Ct(Cc()); -- Cc({}) generates a bug since the {} would be shared across parses + Ct(Cc()); -- Cc({}) generates a bug since the {} would be shared across parses
ShortFuncDef = tagC("Function", V"ShortFuncParams" * maybeBlockWithEnd()) / fixShortFunc; ShortFuncDef = tagC("Function", V"ShortFuncParams" * maybeBlockWithEnd()) / fixShortFunc;
@ -585,8 +595,9 @@ local G = { V"Lua",
ImplicitPushStat = tagC("Push", commaSep(V"Expr", "RetList")) / markImplicit; ImplicitPushStat = tagC("Push", commaSep(V"Expr", "RetList")) / markImplicit;
NameList = tagC("NameList", commaSep(V"Id")); NameList = tagC("NameList", commaSep(V"Id"));
DestructuringNameList = tagC("NameList", commaSep(V"DestructuringId")), DestructuringNameList = tagC("NameList", commaSep(V"DestructuringId"));
AttributeNameList = tagC("AttributeNameList", commaSep(V"AttributeId")); AttributeNameList = tagC("AttributeNameList", commaSep(V"AttributeId"))
+ tagC("PrefixedAttributeNameList", V"Attribute" * commaSep(V"AttributeId"));
VarList = tagC("VarList", commaSep(V"VarExpr")); VarList = tagC("VarList", commaSep(V"VarExpr"));
ExprList = tagC("ExpList", commaSep(V"Expr", "ExprList")); ExprList = tagC("ExpList", commaSep(V"Expr", "ExprList"));
@ -676,7 +687,7 @@ local G = { V"Lua",
Reserved = V"Keywords" * -V"IdRest"; Reserved = V"Keywords" * -V"IdRest";
Keywords = P"and" + "break" + "do" + "elseif" + "else" + "end" Keywords = P"and" + "break" + "do" + "elseif" + "else" + "end"
+ "false" + "for" + "function" + "goto" + "if" + "in" + "false" + "for" + "function" + "goto" + "if" + "in"
+ "local" + "nil" + "not" + "or" + "repeat" + "return" + "local" + "global" + "nil" + "not" + "or" + "repeat" + "return"
+ "then" + "true" + "until" + "while"; + "then" + "true" + "until" + "while";
Ident = V"IdStart" * V"IdRest"^0; Ident = V"IdStart" * V"IdRest"^0;
IdStart = alpha + P"_"; IdStart = alpha + P"_";

View file

@ -245,7 +245,7 @@ function stm2str (stm)
str = str .. explist2str(stm[2]) .. ", " str = str .. explist2str(stm[2]) .. ", "
str = str .. block2str(stm[3]) str = str .. block2str(stm[3])
str = str .. " }" str = str .. " }"
elseif tag == "Local" then -- `Local{ {ident+} {expr+}? } elseif tag == "Local" or tag == "Global" then -- `Local|Global{ {ident+} {expr+}? }
str = str .. "{ " str = str .. "{ "
str = str .. varlist2str(stm[1]) str = str .. varlist2str(stm[1])
if #stm[2] > 0 then if #stm[2] > 0 then
@ -254,7 +254,7 @@ function stm2str (stm)
str = str .. ", " .. "{ }" str = str .. ", " .. "{ }"
end end
str = str .. " }" str = str .. " }"
elseif tag == "Localrec" then -- `Localrec{ ident expr } elseif tag == "Localrec" or tag == "Globalrec" then -- `Localrec|Globalrec{ ident expr }
str = str .. "{ " str = str .. "{ "
str = str .. "{ " .. var2str(stm[1][1]) .. " }, " str = str .. "{ " .. var2str(stm[1][1]) .. " }, "
str = str .. "{ " .. exp2str(stm[2][1]) .. " }" str = str .. "{ " .. exp2str(stm[2][1]) .. " }"

View file

@ -1,5 +1,9 @@
--[[ --[[
This module impements a validator for the AST 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
]] ]]
local scope = require "candran.can-parser.scope" local scope = require "candran.can-parser.scope"
@ -67,7 +71,7 @@ local traverse_block, traverse_explist, traverse_varlist, traverse_parlist
function traverse_parlist (env, parlist) function traverse_parlist (env, parlist)
local len = #parlist local len = #parlist
local is_vararg = false local is_vararg = false
if len > 0 and parlist[len].tag == "Dots" then if len > 0 and parlist[len].tag == "ParDots" then
is_vararg = true is_vararg = true
end end
set_vararg(env, is_vararg) set_vararg(env, is_vararg)
@ -344,7 +348,7 @@ function traverse_exp (env, exp)
return true return true
elseif tag == "Dots" then elseif tag == "Dots" then
return traverse_vararg(env, exp) return traverse_vararg(env, exp)
elseif tag == "Function" then -- `Function{ { `Id{ <string> }* `Dots? } block } elseif tag == "Function" then -- `Function{ { `Id{ <string> }* `ParDots? } block }
return traverse_function(env, exp) return traverse_function(env, exp)
elseif tag == "Table" then -- `Table{ ( `Pair{ expr expr } | expr )* } elseif tag == "Table" then -- `Table{ ( `Pair{ expr expr } | expr )* }
return traverse_table(env, exp) return traverse_table(env, exp)
@ -395,10 +399,14 @@ function traverse_stm (env, stm)
elseif tag == "Forin" then -- `Forin{ {ident+} {expr+} block } elseif tag == "Forin" then -- `Forin{ {ident+} {expr+} block }
return traverse_forin(env, stm) return traverse_forin(env, stm)
elseif tag == "Local" or -- `Local{ {ident+} {expr+}? } elseif tag == "Local" or -- `Local{ {ident+} {expr+}? }
tag == "Let" then -- `Let{ {ident+} {expr+}? } tag == "Let" or -- `Let{ {ident+} {expr+}? }
tag == "Global" then -- `Global{ {ident+} {expr+}? }
return traverse_let(env, stm) return traverse_let(env, stm)
elseif tag == "Localrec" then -- `Localrec{ ident expr } elseif tag == "Localrec" or -- `Localrec{ ident expr }
tag == "Globalrec" then -- `Globalrec{ ident expr }
return traverse_letrec(env, stm) return traverse_letrec(env, stm)
elseif tag == "GlobalAll" then -- GlobalAll{ attribute? }
return true
elseif tag == "Goto" then -- `Goto{ <string> } elseif tag == "Goto" then -- `Goto{ <string> }
return traverse_goto(env, stm) return traverse_goto(env, stm)
elseif tag == "Label" then -- `Label{ <string> } elseif tag == "Label" then -- `Label{ <string> }

View file

@ -8,6 +8,9 @@ tags.AttributeId = (t)
return t[1] return t[1]
end end
end end
tags.PrefixedAttributeNameList = (t)
error("target "..targetName.." does not support variable attributes")
end
#placeholder("patch") #placeholder("patch")

View file

@ -1,898 +1,50 @@
local util = require("candran.util") targetName = "Lua 5.4"
local targetName = "Lua 5.4" -- 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)
local unpack = unpack or table.unpack error("target "..targetName.." does not support global variable declaration")
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 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
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], "_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 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")
else
return "" -- global * is no-op when used alone
end
end
-- Named vararg
tags._functionParameter.ParDots = (t, decl)
if #t == 1 then
local id = lua(t[1])
indentLevel += 1
table.insert(decl, "local "..id.." = { ... }")
indentLevel -= 1
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")
else
t[i][2] = t[1]
table.insert(ids, lua(t[i]))
end
end
return table.concat(ids, ", ")
end
#placeholder("patch")
#local patch = output
#output = ""
#import("compiler.lua55", { preprocessorEnv = { patch = patch }, loadPackage = false })
return lua55

933
compiler/lua55.can Normal file
View file

@ -0,0 +1,933 @@
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

View file

@ -2,12 +2,12 @@ rockspec_format = "3.0"
package = "candran" package = "candran"
version = "1.0.0-1" version = "1.1.0-1"
description = { description = {
summary = "A simple Lua dialect and preprocessor.", summary = "A simple Lua dialect and preprocessor.",
detailed = [[ detailed = [[
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. 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.
Unlike Moonscript, Candran tries to stay close to the Lua syntax, and existing Lua code should be able to run on Candran unmodified. 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", license = "MIT",
@ -19,7 +19,7 @@ description = {
source = { source = {
url = "git://github.com/Reuh/candran", url = "git://github.com/Reuh/candran",
tag = "v1.0.0" tag = "v1.1.0"
} }
dependencies = { dependencies = {

View file

@ -41,6 +41,11 @@ local function test(name, candranCode, expectedResult, options)
results[name] = { result = "not finished", message = "no info" } results[name] = { result = "not finished", message = "no info" }
local self = results[name] local self = results[name]
-- result
if type(expectedResult) ~= "table" then
expectedResult = { "return", expectedResult }
end
-- options -- options
options = options or {} options = options or {}
options.chunkname = name options.chunkname = name
@ -56,25 +61,33 @@ local function test(name, candranCode, expectedResult, options)
-- load code -- load code
local env = {} local env = {}
for k, v in pairs(_G) do env[k] = v end for k, v in pairs(_G) do env[k] = v end
local success, func = pcall(load, code, nil, env) local func, err = load(code, name, env)
if not success then if err then
if expectedResult[1] == "loadError" and err:match(expectedResult[2] or "") then
self.result = "success"
return
end
self.result = "error" self.result = "error"
self.message = c("/!\\ error while loading code:\n"..func.."\ngenerated code:\n", "bold", "red")..c(code, "red") self.message = c("/!\\ error while loading code:\n"..err.."\ngenerated code:\n", "bold", "red")..c(code, "red")
return return
end end
-- run code -- run code
local success, output = pcall(func) local success, output = pcall(func)
if not success then if not success then
if expectedResult[1] == "runtimeError" and output:match(expectedResult[2] or "") then
self.result = "success"
return
end
self.result = "error" self.result = "error"
self.message = c("/!\\ error while running code:\n"..output.."\ngenerated code:\n", "bold", "red")..c(code, "red") self.message = c("/!\\ error while running code:\n"..output.."\ngenerated code:\n", "bold", "red")..c(code, "red")
return return
end end
-- check result -- check result
if output ~= expectedResult then if expectedResult[1] == "return" and output ~= expectedResult[2] then
self.result = "fail" self.result = "fail"
self.message = c("/!\\ invalid result from the code; it returned "..tostring(output).." instead of "..tostring(expectedResult).."; generated code:\n", "bold", "purple")..c(code, "purple") 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")
return return
else else
self.result = "success" self.result = "success"
@ -214,7 +227,7 @@ return hello
-- SYNTAX ADDITIONS -- -- SYNTAX ADDITIONS --
---------------------- ----------------------
-- Assignement operators -- Assignment operators
test("+=", [[ test("+=", [[
local a = 5 local a = 5
a += 2 a += 2
@ -286,7 +299,7 @@ test(">>=", [[
return a return a
]], 5) ]], 5)
test("right assigments operators", [[ test("right assignments operators", [[
local a = 5 local a = 5
a =+ 2 assert(a == 7, "=+") a =+ 2 assert(a == 7, "=+")
a =- 2 assert(a == -5, "=-") a =- 2 assert(a == -5, "=-")
@ -312,7 +325,7 @@ test("right assigments operators", [[
a =>> 23 assert(a == 5, "=>>") a =>> 23 assert(a == 5, "=>>")
]], nil) ]], nil)
test("some left+right assigments operators", [[ test("some left+right assignments operators", [[
local a = 5 local a = 5
a -=+ 2 assert(a == 8, "-=+") a -=+ 2 assert(a == 8, "-=+")
@ -320,17 +333,17 @@ test("some left+right assigments operators", [[
a ..=.. " world " assert(a == "hello world hello", "..=..") a ..=.. " world " assert(a == "hello world hello", "..=..")
]], nil) ]], nil)
test("left assigments operators priority", [[ test("left assignments operators priority", [[
local a = 5 local a = 5
a *= 2 + 3 a *= 2 + 3
return a return a
]], 25) ]], 25)
test("right assigments operators priority", [[ test("right assignments operators priority", [[
local a = 5 local a = 5
a =/ 2 + 3 a =/ 2 + 3
return a return a
]], 1) ]], 1)
test("left+right assigments operators priority", [[ test("left+right assignments operators priority", [[
local a = 5 local a = 5
a *=/ 2 + 3 a *=/ 2 + 3
return a return a
@ -915,63 +928,63 @@ test("safe prefixes, random chaining", [[
assert(f.l?:o?() == nil) assert(f.l?:o?() == nil)
]]) ]])
-- Destructuring assigments -- Destructuring assignments
test("destructuring assignement with an expression", [[ test("destructuring assignment with an expression", [[
local {x, y} = { x = 5, y = 1 } local {x, y} = { x = 5, y = 1 }
return x + y return x + y
]], 6) ]], 6)
test("destructuring assignement with local", [[ test("destructuring assignment with local", [[
t = { x = 5, y = 1 } t = { x = 5, y = 1 }
local {x, y} = t local {x, y} = t
return x + y return x + y
]], 6) ]], 6)
test("destructuring assignement", [[ test("destructuring assignment", [[
t = { x = 5, y = 1 } t = { x = 5, y = 1 }
{x, y} = t {x, y} = t
return x + y return x + y
]], 6) ]], 6)
test("destructuring assignement with +=", [[ test("destructuring assignment with +=", [[
t = { x = 5, y = 1 } t = { x = 5, y = 1 }
local x, y = 5, 9 local x, y = 5, 9
{x, y} += t {x, y} += t
return x + y return x + y
]], 20) ]], 20)
test("destructuring assignement with =-", [[ test("destructuring assignment with =-", [[
t = { x = 5, y = 1 } t = { x = 5, y = 1 }
local x, y = 5, 9 local x, y = 5, 9
{x, y} =- t {x, y} =- t
return x + y return x + y
]], -8) ]], -8)
test("destructuring assignement with +=-", [[ test("destructuring assignment with +=-", [[
t = { x = 5, y = 1 } t = { x = 5, y = 1 }
local x, y = 5, 9 local x, y = 5, 9
{x, y} +=- t {x, y} +=- t
return x + y return x + y
]], 6) ]], 6)
test("destructuring assignement with =-", [[ test("destructuring assignment with =-", [[
t = { x = 5, y = 1 } t = { x = 5, y = 1 }
local x, y = 5, 9 local x, y = 5, 9
{x, y} =- t {x, y} =- t
return x + y return x + y
]], -8) ]], -8)
test("destructuring assignement with let", [[ test("destructuring assignment with let", [[
t = { x = 5, y = 1 } t = { x = 5, y = 1 }
let {x, y} = t let {x, y} = t
return x + y return x + y
]], 6) ]], 6)
test("destructuring assignement with for in", [[ test("destructuring assignment with for in", [[
t = {{ x = 5, y = 1 }} t = {{ x = 5, y = 1 }}
for k, {x, y} in pairs(t) do for k, {x, y} in pairs(t) do
return x + y return x + y
end end
]], 6) ]], 6)
test("destructuring assignement with if with assignement", [[ test("destructuring assignment with if with assignment", [[
t = { x = 5, y = 1 } t = { x = 5, y = 1 }
if {x, y} = t then if {x, y} = t then
return x + y return x + y
end end
]], 6) ]], 6)
test("destructuring assignement with if-elseif with assignement", [[ test("destructuring assignment with if-elseif with assignment", [[
t = { x = 5, y = 1 } t = { x = 5, y = 1 }
if ({u} = t) and u then if ({u} = t) and u then
return 0 return 0
@ -980,56 +993,56 @@ test("destructuring assignement with if-elseif with assignement", [[
end end
]], 6) ]], 6)
test("destructuring assignement with an expression with custom name", [[ test("destructuring assignment with an expression with custom name", [[
local {o = x, y} = { o = 5, y = 1 } local {o = x, y} = { o = 5, y = 1 }
return x + y return x + y
]], 6) ]], 6)
test("destructuring assignement with local with custom name", [[ test("destructuring assignment with local with custom name", [[
t = { o = 5, y = 1 } t = { o = 5, y = 1 }
local {o = x, y} = t local {o = x, y} = t
return x + y return x + y
]], 6) ]], 6)
test("destructuring assignement with custom name", [[ test("destructuring assignment with custom name", [[
t = { o = 5, y = 1 } t = { o = 5, y = 1 }
{o = x, y} = t {o = x, y} = t
return x + y return x + y
]], 6) ]], 6)
test("destructuring assignement with += with custom name", [[ test("destructuring assignment with += with custom name", [[
t = { o = 5, y = 1 } t = { o = 5, y = 1 }
local x, y = 5, 9 local x, y = 5, 9
{o = x, y} += t {o = x, y} += t
return x + y return x + y
]], 20) ]], 20)
test("destructuring assignement with =- with custom name", [[ test("destructuring assignment with =- with custom name", [[
t = { o = 5, y = 1 } t = { o = 5, y = 1 }
local x, y = 5, 9 local x, y = 5, 9
{o = x, y} =- t {o = x, y} =- t
return x + y return x + y
]], -8) ]], -8)
test("destructuring assignement with +=- with custom name", [[ test("destructuring assignment with +=- with custom name", [[
t = { o = 5, y = 1 } t = { o = 5, y = 1 }
local x, y = 5, 9 local x, y = 5, 9
{o = x, y} +=- t {o = x, y} +=- t
return x + y return x + y
]], 6) ]], 6)
test("destructuring assignement with let with custom name", [[ test("destructuring assignment with let with custom name", [[
t = { o = 5, y = 1 } t = { o = 5, y = 1 }
let {o = x, y} = t let {o = x, y} = t
return x + y return x + y
]], 6) ]], 6)
test("destructuring assignement with for in with custom name", [[ test("destructuring assignment with for in with custom name", [[
t = {{ o = 5, y = 1 }} t = {{ o = 5, y = 1 }}
for k, {o = x, y} in pairs(t) do for k, {o = x, y} in pairs(t) do
return x + y return x + y
end end
]], 6) ]], 6)
test("destructuring assignement with if with assignement with custom name", [[ test("destructuring assignment with if with assignment with custom name", [[
t = { o = 5, y = 1 } t = { o = 5, y = 1 }
if {o = x, y} = t then if {o = x, y} = t then
return x + y return x + y
end end
]], 6) ]], 6)
test("destructuring assignement with if-elseif with assignement with custom name", [[ test("destructuring assignment with if-elseif with assignment with custom name", [[
t = { o = 5, y = 1 } t = { o = 5, y = 1 }
if ({x} = t) and x then if ({x} = t) and x then
return 0 return 0
@ -1038,56 +1051,56 @@ test("destructuring assignement with if-elseif with assignement with custom name
end end
]], 6) ]], 6)
test("destructuring assignement with an expression with expression as key", [[ test("destructuring assignment with an expression with expression as key", [[
local {[1] = x, y} = { 5, y = 1 } local {[1] = x, y} = { 5, y = 1 }
return x + y return x + y
]], 6) ]], 6)
test("destructuring assignement with local with expression as key", [[ test("destructuring assignment with local with expression as key", [[
t = { 5, y = 1 } t = { 5, y = 1 }
local {[1] = x, y} = t local {[1] = x, y} = t
return x + y return x + y
]], 6) ]], 6)
test("destructuring assignement with expression as key", [[ test("destructuring assignment with expression as key", [[
t = { 5, y = 1 } t = { 5, y = 1 }
{[1] = x, y} = t {[1] = x, y} = t
return x + y return x + y
]], 6) ]], 6)
test("destructuring assignement with += with expression as key", [[ test("destructuring assignment with += with expression as key", [[
t = { 5, y = 1 } t = { 5, y = 1 }
local x, y = 5, 9 local x, y = 5, 9
{[1] = x, y} += t {[1] = x, y} += t
return x + y return x + y
]], 20) ]], 20)
test("destructuring assignement with =- with expression as key", [[ test("destructuring assignment with =- with expression as key", [[
t = { 5, y = 1 } t = { 5, y = 1 }
local x, y = 5, 9 local x, y = 5, 9
{[1] = x, y} =- t {[1] = x, y} =- t
return x + y return x + y
]], -8) ]], -8)
test("destructuring assignement with +=- with expression as key", [[ test("destructuring assignment with +=- with expression as key", [[
t = { 5, y = 1 } t = { 5, y = 1 }
local x, y = 5, 9 local x, y = 5, 9
{[1] = x, y} +=- t {[1] = x, y} +=- t
return x + y return x + y
]], 6) ]], 6)
test("destructuring assignement with let with expression as key", [[ test("destructuring assignment with let with expression as key", [[
t = { 5, y = 1 } t = { 5, y = 1 }
let {[1] = x, y} = t let {[1] = x, y} = t
return x + y return x + y
]], 6) ]], 6)
test("destructuring assignement with for in with expression as key", [[ test("destructuring assignment with for in with expression as key", [[
t = {{ 5, y = 1 }} t = {{ 5, y = 1 }}
for k, {[1] = x, y} in pairs(t) do for k, {[1] = x, y} in pairs(t) do
return x + y return x + y
end end
]], 6) ]], 6)
test("destructuring assignement with if with assignement with expression as key", [[ test("destructuring assignment with if with assignment with expression as key", [[
t = { 5, y = 1 } t = { 5, y = 1 }
if {[1] = x, y} = t then if {[1] = x, y} = t then
return x + y return x + y
end end
]], 6) ]], 6)
test("destructuring assignement with if-elseif with assignement with expression as key", [[ test("destructuring assignment with if-elseif with assignment with expression as key", [[
t = { 5, y = 1 } t = { 5, y = 1 }
if ({x} = t) and x then if ({x} = t) and x then
return 0 return 0
@ -1096,6 +1109,81 @@ test("destructuring assignement with if-elseif with assignement with expression
end end
]], 6) ]], 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 -- results
local resultCounter = {} local resultCounter = {}
local testCounter = 0 local testCounter = 0