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

Add destructuring assignement

This commit is contained in:
Étienne Fildadut 2020-01-15 20:42:27 +01:00
parent 851e9f89d6
commit 842536b561
10 changed files with 3823 additions and 2985 deletions

View file

@ -42,6 +42,8 @@ end)
a.child?:method?() -- safe navigation operator
local {hey, method} = a -- destructuring assignement
local odd = [ -- table comprehension
for i=1, 10 do
if i%2 == 0 then
@ -70,11 +72,11 @@ end
Candran is released under the MIT License (see ```LICENSE``` for details).
#### Quick setup
Install Candran automatically using LuaRocks: ```sudo luarocks install rockspec/candran-0.10.0-1.rockspec```.
Install Candran automatically using LuaRocks: ```sudo luarocks install rockspec/candran-0.11.0-1.rockspec```.
Or manually install LPegLabel (```luarocks install lpeglabel```, version 1.5 or above), download this repository and use Candran through the scripts in ```bin/``` or use it as a library with the self-contained ```candran.lua```.
You can optionally install lua-linenoise (```luarocks install linenoise```) for an improved REPL. The rockspec does not install linenoise by default.
You can optionally install lua-linenoise (```luarocks install linenoise```, version 0.9 or above) for an improved REPL. The rockspec will install linenoise by default.
You can register the Candran package searcher in your main Lua file (`require("candran").setup()`) and any subsequent `require` call in your project will automatically search for Candran modules.
@ -245,6 +247,33 @@ Values returned by the function will be inserted in the generated table in the o
The table generation function also have access to the `self` variable (and its alias `@`), which is the table which is being created, so you can set any of the table's field.
##### Destructuring assignement
```lua
t = { x = 1, y = 2, z = 3 }
{x, y, z} = t -- x, y, z = t.x, t.y, t.z
{x = o} = t -- o = t.x
{["x"] = o} = t -- o = t["x"]
-- Also works with local, let, for ... in, if with assignement, +=, etc.
local {x, y} = t
let {x, y} = t
for i, {x, y} in ipairs{t} do end
if {x, y} = t then end
{x} += t -- x = x + t.x
-- Works as expected with multiple assignement.
a, {x, y, z}, b = 1, t, 2
```
Destruturing assignement 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`).
##### Safe navigation operators
```lua
a = nil

View file

@ -11,7 +11,7 @@
#import("lib.lua-parser.parser")
local candran = {
VERSION = "0.10.0"
VERSION = "0.11.0"
}
--- Default options.

File diff suppressed because it is too large Load diff

View file

@ -14,7 +14,7 @@ return function(code, ast, options)
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")
local source, line = sub:sub(1, sub:find("\n")):match(".*%-%- (.-)%:(%d+)\n")
if source and line then
lastSource = source
@ -42,15 +42,31 @@ return function(code, ast, options)
return newline()
end
--- Module management
local required = {} -- { ["module"] = 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)
if not required[mod] then
requireStr ..= "local "..options.variablePrefix..name..(" = require(%q)"):format(mod)..(field and "."..field or "")..options.newline
required[mod] = true
--- 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
}
-- 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
@ -59,6 +75,26 @@ return function(code, ast, options)
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
--- 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)
@ -132,26 +168,6 @@ return function(code, ast, options)
return true
end
--- State stacks
-- Used for context-sensitive syntax.
local states = {
push = {} -- push stack variable names
}
-- 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
-- Returns the value on top of the stack "name".
local function peek(name)
return states[name][#states[name]]
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.
@ -175,6 +191,43 @@ return function(code, ast, options)
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" 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({
@ -185,7 +238,7 @@ return function(code, ast, options)
hasPush.tag = "Return"
hasPush = false
end
local r = ""
local r = push("scope", {})
if hasPush then
r ..= push("push", var"push").."local "..var"push".." = {}"..newline()
end
@ -198,7 +251,7 @@ return function(code, ast, options)
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
return r..pop("scope")
end,
-- stat --
@ -209,28 +262,73 @@ return function(code, ast, options)
end,
-- Set{ {lhs+} (opid? = opid?)? {expr+} }
Set = (t)
if #t == 2 then
return lua(t[1], "_lhs").." = "..lua(t[2], "_lhs")
elseif #t == 3 then
return lua(t[1], "_lhs").." = "..lua(t[3], "_lhs")
-- 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 = lua(t[1], "_lhs").." = "..lua({ t[2], t[1][1], { tag = "Paren", t[4][1] } }, "Op")
for i=2, math.min(#t[4], #t[1]), 1 do
r ..= ", "..lua({ t[2], t[1][i], { tag = "Paren", t[4][i] } }, "Op")
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 = lua(t[1], "_lhs").." = "..lua({ t[3], { tag = "Paren", t[4][1] }, t[1][1] }, "Op")
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", t[4][i] }, t[1][i] }, "Op")
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 = lua(t[1], "_lhs").." = "..lua({ t[2], t[1][1], { tag = "Op", t[4], { tag = "Paren", t[5][1] }, t[1][1] } }, "Op")
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], t[1][i], { tag = "Op", t[4], { tag = "Paren", t[5][i] }, t[1][i] } }, "Op")
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
@ -344,12 +442,13 @@ return function(code, ast, options)
end,
-- Forin{ {ident+} {expr+} block }
Forin = (t)
local destructured = {}
local hasContinue = any(t[3], { "Continue" }, loop)
local r = "for "..lua(t[1], "_lhs").." in "..lua(t[2], "_lhs").." do"..indent()
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 ..= lua(t[3])
r ..= DESTRUCTURING_ASSIGN(destructured, true)..lua(t[3])
if hasContinue then
r ..= CONTINUE_STOP()
end
@ -357,15 +456,17 @@ return function(code, ast, options)
end,
-- Local{ {ident+} {expr+}? }
Local = (t)
local r = "local "..lua(t[1], "_lhs")
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
return r..DESTRUCTURING_ASSIGN(destructured)
end,
-- Let{ {ident+} {expr+}? }
Let = (t)
local nameList = lua(t[1], "_lhs")
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
@ -374,7 +475,7 @@ return function(code, ast, options)
r ..= newline()..nameList.." = "..lua(t[2], "_lhs")
end
end
return r
return r..DESTRUCTURING_ASSIGN(destructured)
end,
-- Localrec{ {ident} {expr} }
Localrec = (t)
@ -658,6 +759,21 @@ return function(code, ast, options)
Id = (t)
return t[1]
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

View file

@ -1,4 +1,4 @@
targetName = "luajit"
targetName = "LuaJIT"
UNPACK = (list, i, j)
return "unpack(" .. list .. (i and (", " .. i .. (j and (", " .. j) or "")) or "") .. ")"

View file

@ -6,7 +6,7 @@ To be implemented, theese need to:
* are invalid vanilla Lua syntax.
* are not ambigous with any vanilla Lua syntax.
* be significantly useful compared to existing Candran/Lua code.
* be useful without having to rewrite APIs specifically for Candran. Candran intends to make Lua easier, not supersede it.
* be useful without having to rewrite APIs specifically for Candran. Candran intends to make Lua easier, not replace it.
Example rejected ideas:
* Python-style function decorators (implemented in Candran 0.1.0):
@ -37,6 +37,7 @@ end
local a = new Thing()
->
(TODO: define how classes work. May even use ClassCommons)
Not very Lua-ey to impose how to make your classes?
* try / except|catch / finally / else / other keywords
@ -48,6 +49,8 @@ finally
clean()
end
may be doable using if with assignement + pcall
* static type checking
local a = externalFunc() -- unknown
if a == "hey" then
@ -70,20 +73,26 @@ local b = a[3:5:1]
is it actually useful? even in python I rarely use it, apart from extracting a row or column for a matrix (and we don't have >1D arrays in Lua so...)
OR return multiple value instead of a list?
or list of incices:
local a, b, c = l[1, 2, 3]
how to handle hash table?
local a, b, c = l.(a, b, c)
or
local a, b, c = l.a, .b, .c
but
local a, b, c = l[1], [2], [3]
conflicts with table comprehension: change or use .[n]?
or create some syntax akin to destructuring assignemnts but for numeric indexes:
local [a, b, c] = t
* Destructuring assignment
local pos = { x = 5, y = 12 }
local {x, y} = pos -- x, y = pos.x, pos.y
local {a, b, x = x, y = y} = pos -- x, y = pos.x, pos.y, a = pos[1], b = pos[2]
local {a, b, :x, :y} = pos -- shorthand for the above line. Or .x, .y
local {:x.u} = pos OR {:x:u} OR {.x.u} -- u = pos.x.u
local [x, y] = pos -- x, y = pos[0], pos[1]
local x, y $= pos
And in implicit assignments:
for i, {x, y} in ipairs(positions) do
Sounds useful, at least the key-value part.
Allow recursive destructing assignements
* String interpolation
Delimited by ``:
@ -93,4 +102,7 @@ Also allows multi-line with this maybe?
meh
* Other potential inspiration
https://www.ruby-lang.org/fr/
https://www.ruby-lang.org/
* Lua 5.4 stuff
const, to-be-closed variables: they're fun but as of now the syntax is awful

View file

@ -59,7 +59,7 @@ apply:
`Call{ expr expr* }
| `SafeCall{ expr expr* }
lhs: `Id{ <string> } | `Index{ expr expr }
lhs: `Id{ <string> } | `Index{ expr expr } | ˇDestructuringId{ Id | Pair+ }
opid: -- includes additional operators from Lua 5.3 and all relational operators
'add' | 'sub' | 'mul' | 'div'
@ -164,6 +164,10 @@ local labels = {
{ "ErrExprFKey", "expected an expression after '[' for the table key" },
{ "ErrCBracketFKey", "expected ']' to close the table key" },
{ "ErrCBraceDestructuring", "expected '}' to close the destructuring variable list" },
{ "ErrDestructuringEqField", "expected '=' after the table key in destructuring variable list" },
{ "ErrDestructuringExprField", "expected an identifier after '=' in destructuring variable list" },
{ "ErrCBracketTableCompr", "expected ']' to close the table comprehension" },
{ "ErrDigitHex", "expected one or more hexadecimal digits after '0x'" },
@ -480,8 +484,9 @@ local G = { V"Lua",
Block = tagC("Block", (V"Stat" + -V"BlockEnd" * throw("InvalidStat"))^0 * ((V"RetStat" + V"ImplicitPushStat") * sym(";")^-1)^-1);
Stat = V"IfStat" + V"DoStat" + V"WhileStat" + V"RepeatStat" + V"ForStat"
+ V"LocalStat" + V"FuncStat" + V"BreakStat" + V"LabelStat" + V"GoToStat"
+ V"LetStat"
+ V"FuncCall" + V"Assignment"
+ V"LetStat" + V"ContinueStat" + V"PushStat"
+ V"ContinueStat" + V"PushStat"
+ sym(";");
BlockEnd = P"return" + "end" + "elseif" + "else" + "until" + "]" + -1 + V"ImplicitPushStat" + V"Assignment";
@ -502,17 +507,19 @@ local G = { V"Lua",
ForNum = tagC("Fornum", V"Id" * sym("=") * V"NumRange" * V"ForBody");
NumRange = expect(V"Expr", "ExprFor1") * expect(sym(","), "CommaFor") *expect(V"Expr", "ExprFor2")
* (sym(",") * expect(V"Expr", "ExprFor3"))^-1;
ForIn = tagC("Forin", V"NameList" * expect(kw("in"), "InFor") * expect(V"ExprList", "EListFor") * V"ForBody");
ForIn = tagC("Forin", V"DestructuringNameList" * expect(kw("in"), "InFor") * expect(V"ExprList", "EListFor") * V"ForBody");
ForBody = expectBlockOrSingleStatWithStartEnd(kw("do"), "DoFor", "EndFor");
LocalStat = kw("local") * expect(V"LocalFunc" + V"LocalAssign", "DefLocal");
LocalFunc = tagC("Localrec", kw("function") * expect(V"Id", "NameLFunc") * V"FuncBody") / fixFuncStat;
LocalAssign = tagC("Local", V"NameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc())));
LocalAssign = tagC("Local", V"NameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc())))
+ tagC("Local", V"DestructuringNameList" * sym("=") * expect(V"ExprList", "EListLAssign"));
LetStat = kw("let") * expect(V"LetAssign", "DefLet");
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"));
Assignment = tagC("Set", V"VarList" * 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"));
FuncStat = tagC("Set", kw("function") * expect(V"FuncName", "FuncName") * V"FuncBody") / fixFuncStat;
FuncName = Cf(V"Id" * (sym(".") * expect(V"StrId", "NameFunc1"))^0, insertIndex)
@ -541,9 +548,15 @@ local G = { V"Lua",
ImplicitPushStat = tagC("Push", commaSep(V"Expr", "RetList"));
NameList = tagC("NameList", commaSep(V"Id"));
DestructuringNameList = tagC("NameList", commaSep(V"DestructuringId")),
VarList = tagC("VarList", commaSep(V"VarExpr"));
ExprList = tagC("ExpList", commaSep(V"Expr", "ExprList"));
DestructuringId = tagC("DestructuringId", sym("{") * V"DestructuringIdFieldList" * expect(sym("}"), "CBraceDestructuring")) + V"Id",
DestructuringIdFieldList = sepBy(V"DestructuringIdField", V"FieldSep") * V"FieldSep"^-1;
DestructuringIdField = tagC("Pair", V"FieldKey" * expect(sym("="), "DestructuringEqField") * expect(V"Id", "DestructuringExprField"))
+ V"Id";
Expr = V"OrExpr";
OrExpr = chainOp(V"AndExpr", V"OrOp", "OrExpr");
AndExpr = chainOp(V"RelExpr", V"AndOp", "AndExpr");
@ -564,7 +577,7 @@ local G = { V"Lua",
+ tagC("Boolean", kw("true") * Cc(true))
+ tagC("Dots", sym("..."))
+ V"FuncDef"
+ (when("lexpr") * tagC("LetExpr", V"NameList" * sym("=") * -sym("=") * expect(V"ExprList", "EListLAssign")))
+ (when("lexpr") * tagC("LetExpr", V"DestructuringNameList" * sym("=") * -sym("=") * expect(V"ExprList", "EListLAssign")))
+ V"ShortFuncDef"
+ V"SuffixedExpr"
+ V"StatExpr";

View file

@ -304,6 +304,8 @@ function traverse_var (env, var)
status, msg = traverse_exp(env, var[2])
if not status then return status, msg end
return true
elseif tag == "DestructuringId" then
return traverse_table(env, var)
else
error("expecting a variable, but got a " .. tag)
end

View file

@ -19,12 +19,13 @@ description = {
source = {
url = "git://github.com/Reuh/candran",
tag = "v0.10.0"
tag = "v0.11.0"
}
dependencies = {
"lua >= 5.1",
"lpeglabel >= 1.5.0"
"lpeglabel >= 1.5.0",
"linenoise >= 0.9"
}
build = {

View file

@ -10,6 +10,10 @@ local function test(name, candranCode, expectedResult, options)
results[name] = { result = "not finished", message = "no info" }
local self = results[name]
-- options
options = options or {}
options.chunkname = name
-- make code
local success, code = pcall(candran.make, candranCode, options)
if not success then
@ -125,203 +129,203 @@ return a
-- Assignement operators
test("+=", [[
local a = 5
a += 2
return a
local a = 5
a += 2
return a
]], 7)
test("-=", [[
local a = 5
a -= 2
return a
local a = 5
a -= 2
return a
]], 3)
test("*=", [[
local a = 5
a *= 2
return a
local a = 5
a *= 2
return a
]], 10)
test("/=", [[
local a = 5
a /= 2
return a
local a = 5
a /= 2
return a
]], 5/2)
test("//=", [[
local a = 5
a //= 2
return a
local a = 5
a //= 2
return a
]], 2)
test("^=", [[
local a = 5
a ^= 2
return a
local a = 5
a ^= 2
return a
]], 25)
test("%=", [[
local a = 5
a %= 2
return a
local a = 5
a %= 2
return a
]], 5%2)
test("..=", [[
local a = "hello"
a ..= " world"
return a
local a = "hello"
a ..= " world"
return a
]], "hello world")
test("and=", [[
local a = true
a and= "world"
return a
local a = true
a and= "world"
return a
]], "world")
test("or=", [[
local a = false
a or= "world"
return a
local a = false
a or= "world"
return a
]], "world")
test("&=", [[
local a = 5
a &= 3
return a
local a = 5
a &= 3
return a
]], 1)
test("|=", [[
local a = 5
a |= 3
return a
local a = 5
a |= 3
return a
]], 7)
test("<<=", [[
local a = 23
a <<= 2
return a
local a = 23
a <<= 2
return a
]], 92)
test(">>=", [[
local a = 23
a >>= 2
return a
local a = 23
a >>= 2
return a
]], 5)
test("right assigments operators", [[
local a = 5
a =+ 2 assert(a == 7, "=+")
a =- 2 assert(a == -5, "=-")
a =* -2 assert(a == 10, "=*")
a =/ 2 assert(a == 0.2, "=/")
a =// 2 assert(a == 10, "=//")
a =^ 2 assert(a == 1024, "=^")
a =% 2000 assert(a == 976, "=%")
local a = 5
a =+ 2 assert(a == 7, "=+")
a =- 2 assert(a == -5, "=-")
a =* -2 assert(a == 10, "=*")
a =/ 2 assert(a == 0.2, "=/")
a =// 2 assert(a == 10, "=//")
a =^ 2 assert(a == 1024, "=^")
a =% 2000 assert(a == 976, "=%")
a = "world"
a =.. "hello " assert(a == "hello world", "=..")
a =and true assert(a == "hello world", "=and")
a = "world"
a =.. "hello " assert(a == "hello world", "=..")
a =and true assert(a == "hello world", "=and")
a = false
a =or nil assert(a == false, "=or")
a = false
a =or nil assert(a == false, "=or")
a = 3
a =& 5 assert(a == 1, '=&')
a =| 20 assert(a == 21, "=|")
a =<< 1 assert(a == 2097152, "=<<")
a = 3
a =& 5 assert(a == 1, '=&')
a =| 20 assert(a == 21, "=|")
a =<< 1 assert(a == 2097152, "=<<")
a = 2
a =>> 23 assert(a == 5, "=>>")
a = 2
a =>> 23 assert(a == 5, "=>>")
]], nil)
test("some left+right assigments operators", [[
local a = 5
a -=+ 2 assert(a == 8, "-=+")
local a = 5
a -=+ 2 assert(a == 8, "-=+")
a = "hello"
a ..=.. " world " assert(a == "hello world hello", "..=..")
a = "hello"
a ..=.. " world " assert(a == "hello world hello", "..=..")
]], nil)
test("left assigments operators priority", [[
local a = 5
a *= 2 + 3
return a
local a = 5
a *= 2 + 3
return a
]], 25)
test("right assigments operators priority", [[
local a = 5
a =/ 2 + 3
return a
local a = 5
a =/ 2 + 3
return a
]], 1)
test("left+right assigments operators priority", [[
local a = 5
a *=/ 2 + 3
return a
local a = 5
a *=/ 2 + 3
return a
]], 5)
-- Default function parameters
test("default parameters", [[
local function test(hey, def="re", no, foo=("bar"):gsub("bar", "batru"))
local function test(hey, def="re", no, foo=("bar"):gsub("bar", "batru"))
return def..foo
end
return test(78, "SANDWICH", true)
end
return test(78, "SANDWICH", true)
]], "SANDWICHbatru")
-- @ self alias
test("@ as self alias", [[
local a = {}
function a:hey()
local a = {}
function a:hey()
return @ == self
end
return a:hey()
end
return a:hey()
]], true)
test("@ as self alias and indexation", [[
local a = {
local a = {
foo = "Hoi"
}
function a:hey()
}
function a:hey()
return @.foo
end
return a:hey()
end
return a:hey()
]], "Hoi")
test("@name indexation", [[
local a = {
local a = {
foo = "Hoi"
}
function a:hey()
}
function a:hey()
return @foo
end
return a:hey()
end
return a:hey()
]], "Hoi")
test("@name method call", [[
local a = {
local a = {
foo = "Hoi",
bar = function(self)
return self.foo
end
}
function a:hey()
}
function a:hey()
return @bar()
end
return a:hey()
end
return a:hey()
]], "Hoi")
test("@[expt] indexation", [[
local a = {
local a = {
foo = "Hoi"
}
function a:hey()
}
function a:hey()
return @["foo"]
end
return a:hey()
end
return a:hey()
]], "Hoi")
-- Short anonymous functions declaration
test("short anonymous function declaration", [[
local a = (arg1)
local a = (arg1)
return arg1
end
return a(5)
end
return a(5)
]], 5)
test("short anonymous method declaration", [[
local a = :(arg1)
local a = :(arg1)
return self + arg1
end
return a(2, 3)
end
return a(2, 3)
]], 5)
test("short anonymous method parsing edge cases", [[
-- Taken from the file I used when solving this horror, too tired to make separate tests.
x = ""
function a(s)
-- Taken from the file I used when solving this horror, too tired to make separate tests.
x = ""
function a(s)
x = x .. tostring(s or "+")
end
k=true
while k do
end
k=true
while k do
k=false
cap = {[0] = op, a}
a(tostring(h))
@ -334,30 +338,30 @@ while k do
a()
end
a()
end
a()
a("l")
let h = (h)
end
a()
a("l")
let h = (h)
a("h")
end
h()
a("lol")
if false then exit() end
a("pmo")
if true then
end
h()
a("lol")
if false then exit() end
a("pmo")
if true then
if false
a = (h)
a()
a("pom")
end
a("lo")
a("kol")
if false then
end
a("lo")
a("kol")
if false then
j()
p()
end
do
end
do
b = [
k = () end
if false
@ -367,45 +371,45 @@ do
k()
a()]
end
if a() then h() end
local function f (...)
end
if a() then h() end
local function f (...)
if select('#', ...) == 1 then
return (...)
else
return "***"
end
end
return f(x)
end
return f(x)
]], "nil++++lhlolpmo+pomlokol++")
-- let variable declaration
test("let variable declaration", [[
let a = {
let a = {
foo = function()
return type(a)
end
}
return a.foo()
}
return a.foo()
]], "table")
-- continue keyword
test("continue keyword in while", [[
local a = ""
local i = 0
while i < 10 do
local a = ""
local i = 0
while i < 10 do
i = i + 1
if i % 2 == 0 then
continue
end
a = a .. i
end
return a
end
return a
]], "13579")
test("continue keyword in while, used with break", [[
local a = ""
local i = 0
while i < 10 do
local a = ""
local i = 0
while i < 10 do
i = i + 1
if i % 2 == 0 then
continue
@ -414,25 +418,25 @@ while i < 10 do
if i == 5 then
break
end
end
return a
end
return a
]], "135")
test("continue keyword in repeat", [[
local a = ""
local i = 0
repeat
local a = ""
local i = 0
repeat
i = i + 1
if i % 2 == 0 then
continue
end
a = a .. i
until i == 10
return a
until i == 10
return a
]], "13579")
test("continue keyword in repeat, used with break", [[
local a = ""
local i = 0
repeat
local a = ""
local i = 0
repeat
i = i + 1
if i % 2 == 0 then
continue
@ -441,22 +445,22 @@ repeat
if i == 5 then
break
end
until i == 10
return a
until i == 10
return a
]], "135")
test("continue keyword in fornum", [[
local a = ""
for i=1, 10 do
local a = ""
for i=1, 10 do
if i % 2 == 0 then
continue
end
a = a .. i
end
return a
end
return a
]], "13579")
test("continue keyword in fornum, used with break", [[
local a = ""
for i=1, 10 do
local a = ""
for i=1, 10 do
if i % 2 == 0 then
continue
end
@ -464,24 +468,24 @@ for i=1, 10 do
if i == 5 then
break
end
end
return a
end
return a
]], "135")
test("continue keyword in for", [[
local t = {1,2,3,4,5,6,7,8,9,10}
local a = ""
for _, i in ipairs(t) do
local t = {1,2,3,4,5,6,7,8,9,10}
local a = ""
for _, i in ipairs(t) do
if i % 2 == 0 then
continue
end
a = a .. i
end
return a
end
return a
]], "13579")
test("continue keyword in for, used with break", [[
local t = {1,2,3,4,5,6,7,8,9,10}
local a = ""
for _, i in ipairs(t) do
local t = {1,2,3,4,5,6,7,8,9,10}
local a = ""
for _, i in ipairs(t) do
if i % 2 == 0 then
continue
end
@ -489,190 +493,190 @@ for _, i in ipairs(t) do
if i == 5 then
break
end
end
return a
end
return a
]], "135")
-- push keyword
test("push keyword", [[
function a()
function a()
for i=1, 5 do
push i, "next"
end
return "done"
end
return table.concat({a()})
end
return table.concat({a()})
]], "1next2next3next4next5nextdone")
test("push keyword variable length", [[
function v()
function v()
return "hey", "hop"
end
function w()
end
function w()
return "foo", "bar"
end
function a()
end
function a()
push 5, v(), w()
return
end
return table.concat({a()})
end
return table.concat({a()})
]], "5heyfoobar")
-- implicit push
test("implicit push", [[
function a()
function a()
for i=1, 5 do
i, "next"
end
return "done"
end
return table.concat({a()})
end
return table.concat({a()})
]], "1next2next3next4next5nextdone")
test("implicit push variable length", [[
function v()
function v()
return "hey", "hop"
end
function w()
end
function w()
return "foo", "bar"
end
function a()
end
function a()
if true then
5, v(), w()
end
end
return table.concat({a()})
end
return table.concat({a()})
]], "5heyfoobar")
-- statement expressions
test("if statement expressions", [[
a = if false then
a = if false then
"foo" -- i.e. push "foo", i.e. return "foo"
else
else
"bar"
end
return a
end
return a
]], "bar")
test("do statement expressions", [[
a = do
a = do
"bar"
end
return a
end
return a
]], "bar")
test("while statement expressions", [[
i=0
a, b, c = while i<2 do i=i+1; i end
return table.concat({a, b, tostring(c)})
i=0
a, b, c = while i<2 do i=i+1; i end
return table.concat({a, b, tostring(c)})
]], "12nil")
test("repeat statement expressions", [[
local i = 0
a, b, c = repeat i=i+1; i until i==2
return table.concat({a, b, tostring(c)})
local i = 0
a, b, c = repeat i=i+1; i until i==2
return table.concat({a, b, tostring(c)})
]], "12nil")
test("for statement expressions", [[
a, b, c = for i=1,2 do i end
return table.concat({a, b, tostring(c)})
a, b, c = for i=1,2 do i end
return table.concat({a, b, tostring(c)})
]], "12nil")
-- table comprehension
test("table comprehension sequence", [[
return table.concat([for i=1,10 do i end])
return table.concat([for i=1,10 do i end])
]], "12345678910")
test("table comprehension associative/self", [[
a = [for i=1, 10 do @[i] = true end]
return a[1] and a[10]
a = [for i=1, 10 do @[i] = true end]
return a[1] and a[10]
]], true)
test("table comprehension variable length", [[
t1 = {"hey", "hop"}
t2 = {"foo", "bar"}
return table.concat([push unpack(t1); push unpack(t2)])
t1 = {"hey", "hop"}
t2 = {"foo", "bar"}
return table.concat([push unpack(t1); push unpack(t2)])
]], "heyhopfoobar")
-- one line statements
test("one line if", [[
a = 5
if false
a = 5
if false
a = 0
return a
return a
]], 5)
test("one line if-elseif", [[
a = 5
if false
a = 5
if false
a = 0
elseif true
elseif true
a = 3
elseif false
elseif false
a = -1
return a
return a
]], 3)
test("one line for", [[
a = 0
for i=1,5
a = 0
for i=1,5
a = a + 1
return a
return a
]], 5)
test("one line while", [[
a = 0
while a < 5
a = 0
while a < 5
a = a + 1
return a
return a
]], 5)
-- suffixable string litals, table, table comprehension
test("suffixable string litteral method", [[
return "foo":len()
return "foo":len()
]], 3)
test("suffixable string litteral method lua conflict", [[
local s = function() return "four" end
return s"foo":len()
local s = function() return "four" end
return s"foo":len()
]], 4)
test("suffixable string litteral dot index", [[
local a = "foo".len
return a("foo")
local a = "foo".len
return a("foo")
]], 3)
test("suffixable string litteral dot index lua conflict", [[
local s = function() return {len=4} end
local a = s"foo".len
return a
local s = function() return {len=4} end
local a = s"foo".len
return a
]], 4)
test("suffixable string litteral array index", [[
local a = "foo"["len"]
return a("foo")
local a = "foo"["len"]
return a("foo")
]], 3)
test("suffixable string litteral dot index lua conflict", [[
local s = function() return {len=4} end
local a = s"foo"["len"]
return a
local s = function() return {len=4} end
local a = s"foo"["len"]
return a
]], 4)
test("suffixable table litteral method", [[
return {a=3,len=function(t) return t.a end}:len()
return {a=3,len=function(t) return t.a end}:len()
]], 3)
test("suffixable table litteral method lua conflict", [[
local s = function() return "four" end
return s{a=3,len=function(t) return t.a end}:len()
local s = function() return "four" end
return s{a=3,len=function(t) return t.a end}:len()
]], 4)
test("suffixable table litteral dot index", [[
return {len=3}.len
return {len=3}.len
]], 3)
test("suffixable table litteral dot index lua conflict", [[
local s = function() return {len=4} end
return s{len=3}.len
local s = function() return {len=4} end
return s{len=3}.len
]], 4)
test("suffixable table litteral array index", [[
return {len=3}["len"]
return {len=3}["len"]
]], 3)
test("suffixable table litteral dot index lua conflict", [[
local s = function() return {len=4} end
return s{len=3}["len"]
local s = function() return {len=4} end
return s{len=3}["len"]
]], 4)
test("suffixable table comprehension method", [[
return [@len = function() return 3 end]:len()
return [@len = function() return 3 end]:len()
]], 3)
test("suffixable table comprehension dot index", [[
return [@len = 3].len
return [@len = 3].len
]], 3)
test("suffixable table comprehension array index", [[
return [@len=3]["len"]
return [@len=3]["len"]
]], 3)
-- let in condition expression
@ -823,6 +827,187 @@ test("safe prefixes, random chaining", [[
assert(f.l?:o?() == nil)
]])
-- Destructuring assigments
test("destructuring assignement with an expression", [[
local {x, y} = { x = 5, y = 1 }
return x + y
]], 6)
test("destructuring assignement with local", [[
t = { x = 5, y = 1 }
local {x, y} = t
return x + y
]], 6)
test("destructuring assignement", [[
t = { x = 5, y = 1 }
{x, y} = t
return x + y
]], 6)
test("destructuring assignement with +=", [[
t = { x = 5, y = 1 }
local x, y = 5, 9
{x, y} += t
return x + y
]], 20)
test("destructuring assignement with =-", [[
t = { x = 5, y = 1 }
local x, y = 5, 9
{x, y} =- t
return x + y
]], -8)
test("destructuring assignement with +=-", [[
t = { x = 5, y = 1 }
local x, y = 5, 9
{x, y} +=- t
return x + y
]], 6)
test("destructuring assignement with =-", [[
t = { x = 5, y = 1 }
local x, y = 5, 9
{x, y} =- t
return x + y
]], -8)
test("destructuring assignement with let", [[
t = { x = 5, y = 1 }
let {x, y} = t
return x + y
]], 6)
test("destructuring assignement with for in", [[
t = {{ x = 5, y = 1 }}
for k, {x, y} in pairs(t) do
return x + y
end
]], 6)
test("destructuring assignement with if with assignement", [[
t = { x = 5, y = 1 }
if {x, y} = t then
return x + y
end
]], 6)
test("destructuring assignement with if-elseif with assignement", [[
t = { x = 5, y = 1 }
if ({u} = t) and u then
return 0
elseif {x, y} = t then
return x + y
end
]], 6)
test("destructuring assignement with an expression with custom name", [[
local {o = x, y} = { o = 5, y = 1 }
return x + y
]], 6)
test("destructuring assignement with local with custom name", [[
t = { o = 5, y = 1 }
local {o = x, y} = t
return x + y
]], 6)
test("destructuring assignement with custom name", [[
t = { o = 5, y = 1 }
{o = x, y} = t
return x + y
]], 6)
test("destructuring assignement with += with custom name", [[
t = { o = 5, y = 1 }
local x, y = 5, 9
{o = x, y} += t
return x + y
]], 20)
test("destructuring assignement with =- with custom name", [[
t = { o = 5, y = 1 }
local x, y = 5, 9
{o = x, y} =- t
return x + y
]], -8)
test("destructuring assignement with +=- with custom name", [[
t = { o = 5, y = 1 }
local x, y = 5, 9
{o = x, y} +=- t
return x + y
]], 6)
test("destructuring assignement with let with custom name", [[
t = { o = 5, y = 1 }
let {o = x, y} = t
return x + y
]], 6)
test("destructuring assignement with for in with custom name", [[
t = {{ o = 5, y = 1 }}
for k, {o = x, y} in pairs(t) do
return x + y
end
]], 6)
test("destructuring assignement with if with assignement with custom name", [[
t = { o = 5, y = 1 }
if {o = x, y} = t then
return x + y
end
]], 6)
test("destructuring assignement with if-elseif with assignement with custom name", [[
t = { o = 5, y = 1 }
if ({x} = t) and x then
return 0
elseif {o = x, y} = t then
return x + y
end
]], 6)
test("destructuring assignement with an expression with expression as key", [[
local {[1] = x, y} = { 5, y = 1 }
return x + y
]], 6)
test("destructuring assignement with local with expression as key", [[
t = { 5, y = 1 }
local {[1] = x, y} = t
return x + y
]], 6)
test("destructuring assignement with expression as key", [[
t = { 5, y = 1 }
{[1] = x, y} = t
return x + y
]], 6)
test("destructuring assignement with += with expression as key", [[
t = { 5, y = 1 }
local x, y = 5, 9
{[1] = x, y} += t
return x + y
]], 20)
test("destructuring assignement with =- with expression as key", [[
t = { 5, y = 1 }
local x, y = 5, 9
{[1] = x, y} =- t
return x + y
]], -8)
test("destructuring assignement with +=- with expression as key", [[
t = { 5, y = 1 }
local x, y = 5, 9
{[1] = x, y} +=- t
return x + y
]], 6)
test("destructuring assignement with let with expression as key", [[
t = { 5, y = 1 }
let {[1] = x, y} = t
return x + y
]], 6)
test("destructuring assignement with for in with expression as key", [[
t = {{ 5, y = 1 }}
for k, {[1] = x, y} in pairs(t) do
return x + y
end
]], 6)
test("destructuring assignement with if with assignement with expression as key", [[
t = { 5, y = 1 }
if {[1] = x, y} = t then
return x + y
end
]], 6)
test("destructuring assignement with if-elseif with assignement with expression as key", [[
t = { 5, y = 1 }
if ({x} = t) and x then
return 0
elseif {[1] = x, y} = t then
return x + y
end
]], 6)
-- results
local resultCounter = {}
local testCounter = 0