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

Added safe operators, if/while with assignement, method stubs

This commit is contained in:
Étienne Fildadut 2019-08-27 17:07:33 +02:00
parent 6be81267d2
commit 851e9f89d6
11 changed files with 3688 additions and 2845 deletions

100
README.md
View file

@ -20,6 +20,8 @@ end
let a = {
hey = true,
child = nil,
method = :(foo, thing) -- short function declaration, with self
@hey = thing(foo) -- @ as an alias for self
end,
@ -33,6 +35,13 @@ a:method(42, (foo)
return "something " .. foo
end)
local fn = a:method -- bundles an object and method in a function
fn(42, (foo)
return "something" .. foo
end)
a.child?:method?() -- safe navigation operator
local odd = [ -- table comprehension
for i=1, 10 do
if i%2 == 0 then
@ -48,6 +57,12 @@ 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
if f, err = io.open("data") then -- if condition with assignements
thing.process(f)
else
error("can't open data: "..err)
end
````
**Current status**: Candran is heavily used in several of my personal projects and works as expected.
@ -97,7 +112,7 @@ All theses operators can also be put right of the assigment operator, in which c
Right and left operator can be used at the same time.
**Please note** that the Lua code `a=-1` will be compiled into `a = 1 - a` and not `a = -1`! Write spaced code: `a = -1` works as expected.
**Please note** that the code `a=-1` will be compiled into `a = -1` and not `a = a - 1`, like in pure Lua. If you want the latter, spacing is required between the `=-` and the expression: `a=- 1`. Yes, this is also valid Lua code, but as far as I'm aware, nobody write code like this; people who really like spacing would write `a= - 1` or `a = - 1`, and Candran will read both of those as it is expected in pure Lua. This is the only incompatibility between Candran and pure Lua.
##### Default function parameters
```lua
@ -230,6 +245,68 @@ 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.
##### Safe navigation operators
```lua
a = nil
print(a?.b) -- nil
a = {b=true}
print(a?.b) -- true
-- So instead of typing
if object and object.child and object.child.isGreen then
-- stuff
end
-- you can type
if object?.child?.isGreen then
-- stuff
end
-- The ?. operator does not break the whole chain; make sure to use the operator on each index.
print(a?.undefined.field) -- a?.undefined returns nil, so this throws a "attempt to index a nil value"
-- Other safe navigator operators behave similarly:
print(a:method) -- nil if a is nil, other normal behaviour
print(a["key"]) -- nil if a is nil, other normal behaviour
print(a?()) -- nil if a is nil, other normal behaviour
```
Some operators can be prefixed by a `?` to turn into a safe version of the operator: if the base value if `nil`, the normal behaviour of the operator will be skipped and nil will be returned; otherwise, the operator run as usual. Is available safe dot index `?.`, safe array index `?[...]`, safe method stub `?:` and safe function call `?(...)`.
##### If and while with assignement in the condition
```lua
if f, err = io.open("somefile") then -- condition if verified if f is a truthy value (not nil or false)
-- do something with f
f:close()
elseif f2, err2 = io.open("anotherfile") then -- same behaviour on elseif
print("could not open somefile:", err) -- f and err stay in scope for the rest of the if-elseif-else block
-- do something with f2
f2:close()
else
print("could not open somefile:", err)
print("could not open anotherfile:", err2)
end
-- 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.
print(index, value)
end
-- When used in a while, the expression is evaluated at each iteration.
while line = io.read() do
print(line)
end
-- The assignement have the same priority as regular assignements, i.e., the lowest.
if a = 1 and 2 then -- will be read as a = (1 and 2)
elseif (a = 1) and 2 then -- will be read as (a = 1) and 2
end
```
Assignements can be used in the condition of if, elseif and while statements. Several variables can be assigned; only the first will be tested in the condition, for each assignement. The assigned variables will be in scope the duration of the block; for if statements, they will also be in scope for the following elseif(s) and else.
For while statements, the assigned expression will be reevaluated at each iteration.
##### Suffixable string and table litterals
```lua
"some text":upper() -- "SOME TEXT". Same as ("some text"):upper() in Lua.
@ -246,6 +323,27 @@ String litterals, table litterals, and comprehensions can be suffixed with `:` m
**Please note**, that "normal" functions calls have priority over this syntax, in order to maintain Lua compatibility.
##### Method stubs
```lua
object = {
value = 25,
method = function(self, str)
print(str, self.value)
end
}
stub = object:method
object.method = error -- stub stores the method as it was when stub was defined
object = nil -- also stores the object
print(stub("hello")) -- hello 25
```
Create a closure function which bundles the variable and its method; when called it will call the method on the variable, without requiring to pass the variable as a first argument.
The closure stores the value of the variable and method when created.
##### Statement expressions
```lua
a = if false then

View file

@ -86,7 +86,7 @@ function candran.preprocess(input, options={})
-- @tparam modpath string module path
-- @tparam margs table preprocessor options to use when preprocessessing the module
env.import = function(modpath, margs={})
local filepath = assert(util.search(modpath), "No module named \""..modpath.."\"")
local filepath = assert(util.search(modpath, {"can", "lua"}), "No module named \""..modpath.."\"")
-- open module file
local f = io.open(filepath)

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,5 @@
targetName = "Lua 5.1"
states.continue = {} -- when in a loop that use continue
CONTINUE_START = ()
@ -19,12 +21,9 @@ tags.Break = ()
end
end
tags.Goto = ()
error("Lua 5.1 does not support the goto keyword")
end
tags.Label = ()
error("Lua 5.1 does not support labels")
end
-- Unsuported features
tags.Goto = nil
tags.Label = nil
#local patch = output
#output = ""

View file

@ -1,3 +1,5 @@
local targetName = "Lua 5.3"
return function(code, ast, options)
--- Line mapping
local lastInputPos = 1 -- last token position in the input code
@ -9,7 +11,7 @@ return function(code, ast, options)
local indentLevel = 0
-- Returns a newline.
local function newline()
local r = options.newline .. string.rep(options.indentation, indentLevel)
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")
@ -25,7 +27,7 @@ return function(code, ast, options)
prevLinePos = lastInputPos
r = " -- " .. lastSource .. ":" .. lastLine .. r
r = " -- "..lastSource..":"..lastLine..r
end
return r
end
@ -46,7 +48,7 @@ return function(code, ast, options)
-- 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
requireStr ..= "local "..options.variablePrefix..name..(" = require(%q)"):format(mod)..(field and "."..field or "")..options.newline
required[mod] = true
end
end
@ -54,12 +56,13 @@ return function(code, ast, options)
--- Variable management
-- Returns the prefixed variable name.
local function var(name)
return options.variablePrefix .. name
return options.variablePrefix..name
end
--- AST traversal helpers
local loop = { "While", "Repeat", "Fornum", "Forin" } -- loops tags
local func = { "Function", "TableCompr", "DoExpr", "WhileExpr", "RepeatExpr", "IfExpr", "FornumExpr", "ForinExpr" } -- function scope tags
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={})
@ -85,6 +88,50 @@ return function(code, ast, options)
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
--- State stacks
-- Used for context-sensitive syntax.
local states = {
@ -117,16 +164,16 @@ return function(code, ast, options)
--- Lua function calls writer
local UNPACK = (list, i, j) -- table.unpack
return "table.unpack(" .. list .. (i and (", " .. i .. (j and (", " .. j) or "")) or "") .. ")"
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 a = table.pack(" .. toAppend .. ")" .. newline() .. "table.move(a, 1, a.n, #" .. t .. "+1, " .. t .. ")" .. unindent() .. "end"
return "do"..indent().."local a = table.pack("..toAppend..")"..newline().."table.move(a, 1, a.n, #"..t.."+1, "..t..")"..unindent().."end"
end
local CONTINUE_START = () -- at the start of loops using continue
return "do" .. indent()
return "do"..indent()
end
local CONTINUE_STOP = () -- at the start of loops using continue
return unindent() .. "end" .. newline() .. "::" .. var("continue") .. "::"
return unindent().."end"..newline().."::"..var"continue".."::"
end
--- Tag constructors
@ -140,16 +187,16 @@ return function(code, ast, options)
end
local r = ""
if hasPush then
r ..= push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline()
r ..= push("push", var"push").."local "..var"push".." = {}"..newline()
end
for i=1, #t-1, 1 do
r ..= lua(t[i]) .. newline()
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")
r ..= newline().."return "..UNPACK(var"push")..pop("push")
end
return r
end,
@ -158,81 +205,122 @@ return function(code, ast, options)
-- Do{ stat* }
Do = (t)
return "do" .. indent() .. lua(t, "Block") .. unindent() .. "end"
return "do"..indent()..lua(t, "Block")..unindent().."end"
end,
-- Set{ {lhs+} (opid? = opid?)? {expr+} }
Set = (t)
if #t == 2 then
return lua(t[1], "_lhs") .. " = " .. lua(t[2], "_lhs")
return lua(t[1], "_lhs").." = "..lua(t[2], "_lhs")
elseif #t == 3 then
return lua(t[1], "_lhs") .. " = " .. lua(t[3], "_lhs")
return lua(t[1], "_lhs").." = "..lua(t[3], "_lhs")
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")
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")
r ..= ", "..lua({ t[2], t[1][i], { tag = "Paren", t[4][i] } }, "Op")
end
return r
else
local r = lua(t[1], "_lhs") .. " = " .. lua({ t[3], { tag = "Paren", t[4][1] }, t[1][1] }, "Op")
local r = lua(t[1], "_lhs").." = "..lua({ t[3], { tag = "Paren", t[4][1] }, t[1][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", t[4][i] }, t[1][i] }, "Op")
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 = lua(t[1], "_lhs").." = "..lua({ t[2], t[1][1], { tag = "Op", t[4], { tag = "Paren", t[5][1] }, t[1][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], t[1][i], { tag = "Op", t[4], { tag = "Paren", t[5][i] }, t[1][i] } }, "Op")
end
return r
end
end,
-- While{ expr block }
While = (t)
local r = ""
local hasContinue = any(t[2], { "Continue" }, loop)
local r = "while " .. lua(t[1]) .. " do" .. indent()
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])
r ..= lua(t[2])
if hasContinue then
r ..= CONTINUE_STOP()
end
r ..= unindent() .. "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()
local r = "repeat"..indent()
if hasContinue then
r ..= CONTINUE_START()
end
r .. = lua(t[1])
r ..= lua(t[1])
if hasContinue then
r ..= CONTINUE_STOP()
end
r ..= unindent() .. "until " .. lua(t[2])
r ..= unindent().."until "..lua(t[2])
return r
end,
-- If{ (expr block)+ block? }
-- If{ (lexpr block)+ block? }
If = (t)
local r = "if " .. lua(t[1]) .. " then" .. indent() .. lua(t[2]) .. unindent()
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
r ..= "elseif " .. lua(t[i]) .. " then" .. indent() .. lua(t[i+1]) .. unindent()
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()
r ..= "else"..indent()..lua(t[#t])..unindent()
end
return r .. "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])
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()
r ..= ", "..lua(t[4]).." do"..indent()
if hasContinue then
r ..= CONTINUE_START()
end
@ -240,10 +328,10 @@ return function(code, ast, options)
if hasContinue then
r ..= CONTINUE_STOP()
end
return r .. unindent() .. "end"
return r..unindent().."end"
else
local hasContinue = any(t[4], { "Continue" }, loop)
r ..= " do" .. indent()
r ..= " do"..indent()
if hasContinue then
r ..= CONTINUE_START()
end
@ -251,13 +339,13 @@ return function(code, ast, options)
if hasContinue then
r ..= CONTINUE_STOP()
end
return r .. unindent() .. "end"
return r..unindent().."end"
end
end,
-- Forin{ {ident+} {expr+} block }
Forin = (t)
local hasContinue = any(t[3], { "Continue" }, loop)
local r = "for " .. lua(t[1], "_lhs") .. " in " .. lua(t[2], "_lhs") .. " do" .. indent()
local r = "for "..lua(t[1], "_lhs").." in "..lua(t[2], "_lhs").." do"..indent()
if hasContinue then
r ..= CONTINUE_START()
end
@ -265,7 +353,7 @@ return function(code, ast, options)
if hasContinue then
r ..= CONTINUE_STOP()
end
return r .. unindent() .. "end"
return r..unindent().."end"
end,
-- Local{ {ident+} {expr+}? }
Local = (t)
@ -278,12 +366,12 @@ return function(code, ast, options)
-- Let{ {ident+} {expr+}? }
Let = (t)
local nameList = lua(t[1], "_lhs")
local r = "local " .. nameList
local r = "local "..nameList
if t[2][1] then
if any(t[2], { "Function", "Table", "Paren" }) then -- predeclaration doesn't matter otherwise
r ..= newline() .. nameList .. " = " .. lua(t[2], "_lhs")
if all(t[2], { "Nil", "Dots", "Boolean", "Number", "String" }) then -- predeclaration doesn't matter here
r ..= " = "..lua(t[2], "_lhs")
else
r ..= " = " .. lua(t[2], "_lhs")
r ..= newline()..nameList.." = "..lua(t[2], "_lhs")
end
end
return r
@ -294,11 +382,11 @@ return function(code, ast, options)
end,
-- Goto{ <string> }
Goto = (t)
return "goto " .. lua(t, "Id")
return "goto "..lua(t, "Id")
end,
-- Label{ <string> }
Label = (t)
return "::" .. lua(t, "Id") .. "::"
return "::"..lua(t, "Id").."::"
end,
-- Return{ <expr*> }
Return = (t)
@ -306,9 +394,9 @@ return function(code, ast, options)
if push then
local r = ""
for _, val in ipairs(t) do
r ..= push .. "[#" .. push .. "+1] = " .. lua(val) .. newline()
r ..= push.."[#"..push.."+1] = "..lua(val)..newline()
end
return r .. "return " .. UNPACK(push)
return r.."return "..UNPACK(push)
else
return "return "..lua(t, "_lhs")
end
@ -318,13 +406,13 @@ return function(code, ast, options)
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()
r ..= var.."[#"..var.."+1] = "..lua(t[i])..newline()
end
if t[#t] then
if t[#t].tag == "Call" or t[#t].tag == "Invoke" then
if t[#t].tag == "Call" then
r ..= APPEND(var, lua(t[#t]))
else
r ..= var .. "[#" .. var .. "+1] = " .. lua(t[#t])
r ..= var.."[#"..var.."+1] = "..lua(t[#t])
end
end
return r
@ -335,7 +423,7 @@ return function(code, ast, options)
end,
-- Continue
Continue = ()
return "goto " .. var("continue")
return "goto "..var"continue"
end,
-- apply (below)
@ -359,7 +447,7 @@ return function(code, ast, options)
end,
-- String{ <string> }
String = (t)
return ("%q"):format(t[1])
return "%q":format(t[1])
end,
-- Function{ { ( `ParPair{ Id expr } | `Id{ <string> } )* `Dots? } block }
_functionWithoutKeyword = (t)
@ -369,7 +457,7 @@ return function(code, ast, options)
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")
table.insert(decl, "if "..id.." == nil then "..id.." = "..lua(t[1][1][2]).." end")
indentLevel -= 1
r ..= id
else
@ -379,66 +467,66 @@ return function(code, ast, options)
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")
table.insert(decl, "if "..id.." == nil then "..id.." = "..lua(t[1][i][2]).." end")
indentLevel -= 1
r ..= ", " ..id
else
r ..= ", " .. lua(t[1][i])
r ..= ", "..lua(t[1][i])
end
end
end
r ..= ")" .. indent()
r ..= ")"..indent()
for _, d in ipairs(decl) do
r ..= d .. newline()
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()
r ..= push("push", var"push").."local "..var"push".." = {}"..newline()
else
push("push", false) -- no push here (make sure higher push don't affect us)
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"))
r ..= newline().."return "..UNPACK(var"push")
end
pop("push")
return r .. unindent() .. "end"
return r..unindent().."end"
end,
Function = (t)
return "function" .. lua(t, "_functionWithoutKeyword")
return "function"..lua(t, "_functionWithoutKeyword")
end,
-- Table{ ( `Pair{ expr expr } | expr )* }
Pair = (t)
return "[" .. lua(t[1]) .. "] = " .. lua(t[2])
return "["..lua(t[1]).."] = "..lua(t[2])
end,
Table = (t)
if #t == 0 then
return "{}"
elseif #t == 1 then
return "{ " .. lua(t, "_lhs") .. " }"
return "{ "..lua(t, "_lhs").." }"
else
return "{" .. indent() .. lua(t, "_lhs", nil, true) .. unindent() .. "}"
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")
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])
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])
r = lua(t[2]).." "..tags._opid[t[1]].." "..lua(t[3])
else
r = tags._opid[t[1]](t[2], t[3])
end
@ -447,27 +535,51 @@ return function(code, ast, options)
end,
-- Paren{ expr }
Paren = (t)
return "(" .. lua(t[1]) .. ")"
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()
local r = "(function()"..indent()
if hasPush then
r ..= push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline()
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"))
r ..= newline().."return "..UNPACK(var"push")
end
pop("push")
r ..= unindent() .. "end)()"
r ..= unindent().."end)()"
return r
end,
-- DoExpr{ stat* }
@ -509,18 +621,23 @@ return function(code, ast, options)
-- Call{ expr expr* }
Call = (t)
if t[1].tag == "String" or t[1].tag == "Table" then
return "("..lua(t[1]) .. ")(" .. lua(t, "_lhs", 2) .. ")"
return "("..lua(t[1])..")("..lua(t, "_lhs", 2)..")"
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]) .. "(" .. lua(t, "_lhs", 2) .. ")"
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,
-- Invoke{ expr `String{ <string> } expr* }
Invoke = (t)
if t[1].tag == "String" or t[1].tag == "Table" then
return "("..lua(t[1]).."):"..lua(t[2], "Id").."("..lua(t, "_lhs", 3)..")"
else
return lua(t[1])..":"..lua(t[2], "Id").."("..lua(t, "_lhs", 3)..")"
-- 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,
@ -530,7 +647,7 @@ return function(code, ast, options)
if t[start] then
r = lua(t[start])
for i=start+1, #t, 1 do
r ..= "," .. (newlines and newline() or " ") .. lua(t[i])
r ..= ","..(newlines and newline() or " ")..lua(t[i])
end
else
r = ""
@ -549,6 +666,29 @@ return function(code, ast, options)
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 = {
@ -560,12 +700,12 @@ return function(code, ast, options)
}
}, {
__index = (self, key)
error("don't know how to compile a "..tostring(key).." to Lua 5.3")
error("don't know how to compile a "..tostring(key).." to "..targetName)
end
})
#placeholder("patch")
local code = lua(ast) .. newline()
return requireStr .. code
local code = lua(ast)..newline()
return requireStr..code
end

View file

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

View file

@ -8,7 +8,7 @@ To be implemented, theese need to:
* 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.
Example currently rejected ideas:
Example rejected ideas:
* Python-style function decorators (implemented in Candran 0.1.0):
Useless 95% of the time because most Lua APIs applying something to a function are used like applySomething(someArg,func) instead of func=applySomething(someArg)(func).
This could be adapted, but this will mean unecessary named functions in the environment and it will only works when the decorator returns the functions.
@ -48,15 +48,6 @@ finally
clean()
end
* Safe navigation operator
local name = articles?[0].author?.name (?[] and ?. index opeators)
or
local zip = lottery.drawWinner?().address?.zipcode (expr? existence test suffix)
if one is nil, returns nil
See http://coffeescript.org/#existential-operator
* static type checking
local a = externalFunc() -- unknown
if a == "hey" then
@ -103,13 +94,3 @@ meh
* Other potential inspiration
https://www.ruby-lang.org/fr/
* If with assignement
if f = io.open("file") then
f:close()
end
f is in the if scope
* Method self bundler
obj:method -- returns (...) return method(obj, ...) end

View file

@ -11,7 +11,7 @@ stat:
| `Set{ {lhs+} (opid? = opid?)? {expr+} } -- lhs1, lhs2... op=op e1, e2...
| `While{ expr block } -- while e do b end
| `Repeat{ block expr } -- repeat b until e
| `If{ (expr block)+ block? } -- if e1 then b1 [elseif e2 then b2] ... [else bn] end
| `If{ (lexpr block)+ block? } -- if e1 then b1 [elseif e2 then b2] ... [else bn] end
| `Fornum{ ident expr expr expr? block } -- for ident = e, e[, e] do b end
| `Forin{ {ident+} {expr+} block } -- for i1, i2... in e1, e2... do b end
| `Local{ {ident+} {expr+}? } -- local i1, i2... = e1, e2...
@ -36,10 +36,17 @@ expr:
| `Op{ opid expr expr? }
| `Paren{ expr } -- significant to cut multiple values returns
| `TableCompr{ block }
| `MethodStub{ expr expr }
| `SafeMethodStub{ expr expr }
| `SafeIndex{ expr expr }
| statexpr
| apply
| lhs
lexpr:
`LetExpr{ {ident+} {expr+}? }
| every node from expr
statexpr:
`DoExpr{ stat* }
| `WhileExpr{ expr block } -- while e do b end
@ -50,7 +57,7 @@ statexpr:
apply:
`Call{ expr expr* }
| `Invoke{ expr `String{ <string> } expr* }
| `SafeCall{ expr expr* }
lhs: `Id{ <string> } | `Index{ expr expr }
@ -255,25 +262,32 @@ local function insertIndex (t, index)
return { tag = "Index", pos = t.pos, [1] = t, [2] = index }
end
local function markMethod(t, method)
local function markMethod (t, method)
if method then
return { tag = "Index", pos = t.pos, is_method = true, [1] = t, [2] = method }
end
return t
end
local function makeIndexOrCall (t1, t2)
if t2.tag == "Call" or t2.tag == "Invoke" then
local function makeSuffixedExpr (t1, t2)
if t2.tag == "Call" or t2.tag == "SafeCall" then
local t = { tag = t2.tag, pos = t1.pos, [1] = t1 }
for k, v in ipairs(t2) do
table.insert(t, v)
end
return t
end
elseif t2.tag == "MethodStub" or t2.tag == "SafeMethodStub" then
return { tag = t2.tag, pos = t1.pos, [1] = t1, [2] = t2[1] }
elseif t2.tag == "SafeDotIndex" or t2.tag == "SafeArrayIndex" then
return { tag = "SafeIndex", pos = t1.pos, [1] = t1, [2] = t2[1] }
elseif t2.tag == "DotIndex" or t2.tag == "ArrayIndex" then
return { tag = "Index", pos = t1.pos, [1] = t1, [2] = t2[1] }
else
error("unexpected tag in suffixed expression")
end
end
local function fixShortFunc(t)
local function fixShortFunc (t)
if t[1] == ":" then -- self method
table.insert(t[2], 1, { tag = "Id", "self" })
table.remove(t, 1)
@ -283,12 +297,12 @@ local function fixShortFunc(t)
return t
end
local function statToExpr(t) -- tag a StatExpr
local function statToExpr (t) -- tag a StatExpr
t.tag = t.tag .. "Expr"
return t
end
local function fixStructure(t) -- fix the AST structure if needed
local function fixStructure (t) -- fix the AST structure if needed
local i = 1
while i <= #t do
if type(t[i]) == "table" then
@ -309,7 +323,7 @@ local function fixStructure(t) -- fix the AST structure if needed
return t
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
-- 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
@ -404,7 +418,7 @@ local function searchEndRec(block, isRecCall) -- recursively search potential "e
return nil
end
local function searchEnd(s, p, t) -- match time capture which try to restructure the AST to free an "end" for us
local function searchEnd (s, p, t) -- match time capture which try to restructure the AST to free an "end" for us
local r = searchEndRec(fixStructure(t))
if not r then
return false
@ -412,7 +426,7 @@ local function searchEnd(s, p, t) -- match time capture which try to restructure
return true, r
end
local function expectBlockOrSingleStatWithStartEnd(start, startLabel, stopLabel, canFollow) -- will try a SingleStat if start doesn't match
local function expectBlockOrSingleStatWithStartEnd (start, startLabel, stopLabel, canFollow) -- will try a SingleStat if start doesn't match
if canFollow then
return (-start * V"SingleStatBlock" * canFollow^-1)
+ (expect(start, startLabel) * ((V"Block" * (canFollow + kw("end")))
@ -424,16 +438,40 @@ local function expectBlockOrSingleStatWithStartEnd(start, startLabel, stopLabel,
end
end
local function expectBlockWithEnd(label) -- can't work *optionnaly* with SingleStat unfortunatly
local function expectBlockWithEnd (label) -- can't work *optionnaly* with SingleStat unfortunatly
return (V"Block" * kw("end"))
+ (Cmt(V"Block", searchEnd) + throw(label))
end
local function maybeBlockWithEnd() -- same as above but don't error if it doesn't match
local function maybeBlockWithEnd () -- same as above but don't error if it doesn't match
return (V"BlockNoErr" * kw("end"))
+ Cmt(V"BlockNoErr", searchEnd)
end
local stacks = {
lexpr = {}
}
local function push (f)
return Cmt(P"", function()
table.insert(stacks[f], true)
return true
end)
end
local function pop (f)
return Cmt(P"", function()
table.remove(stacks[f])
return true
end)
end
local function when (f)
return Cmt(P"", function()
return #stacks[f] > 0
end)
end
local function set (f, patt) -- patt *must* succeed (or throw an error) to preserve stack integrity
return push(f) * patt * pop(f)
end
-- grammar
local G = { V"Lua",
Lua = (V"Shebang"^-1 * V"Skip" * V"Block" * expect(P(-1), "Extra")) / fixStructure;
@ -451,12 +489,12 @@ local G = { V"Lua",
BlockNoErr = tagC("Block", V"Stat"^0 * ((V"RetStat" + V"ImplicitPushStat") * sym(";")^-1)^-1); -- used to check if something a valid block without throwing an error
IfStat = tagC("If", V"IfPart");
IfPart = kw("if") * expect(V"Expr", "ExprIf") * expectBlockOrSingleStatWithStartEnd(kw("then"), "ThenIf", "EndIf", V"ElseIfPart" + V"ElsePart");
ElseIfPart = kw("elseif") * expect(V"Expr", "ExprEIf") * expectBlockOrSingleStatWithStartEnd(kw("then"), "ThenEIf", "EndIf", V"ElseIfPart" + V"ElsePart");
IfPart = kw("if") * set("lexpr", expect(V"Expr", "ExprIf")) * expectBlockOrSingleStatWithStartEnd(kw("then"), "ThenIf", "EndIf", V"ElseIfPart" + V"ElsePart");
ElseIfPart = kw("elseif") * set("lexpr", expect(V"Expr", "ExprEIf")) * expectBlockOrSingleStatWithStartEnd(kw("then"), "ThenEIf", "EndIf", V"ElseIfPart" + V"ElsePart");
ElsePart = kw("else") * expectBlockWithEnd("EndIf");
DoStat = kw("do") * expectBlockWithEnd("EndDo") / tagDo;
WhileStat = tagC("While", kw("while") * expect(V"Expr", "ExprWhile") * V"WhileBody");
WhileStat = tagC("While", kw("while") * set("lexpr", expect(V"Expr", "ExprWhile")) * V"WhileBody");
WhileBody = expectBlockOrSingleStatWithStartEnd(kw("do"), "DoWhile", "EndWhile");
RepeatStat = tagC("Repeat", kw("repeat") * V"Block" * expect(kw("until"), "UntilRep") * expect(V"Expr", "ExprRep"));
@ -474,7 +512,7 @@ local G = { V"Lua",
LetStat = kw("let") * expect(V"LetAssign", "DefLet");
LetAssign = tagC("Let", V"NameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc())));
Assignment = tagC("Set", V"VarList" * V"BinOp"^-1 * (P"=" / "=") * V"BinOp"^-1 * V"Skip" * expect(V"ExprList", "EListAssign"));
Assignment = tagC("Set", V"VarList" * 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)
@ -520,35 +558,39 @@ local G = { V"Lua",
UnaryExpr = V"UnaryOp" * expect(V"UnaryExpr", "UnaryExpr") / unaryOp
+ V"PowExpr";
PowExpr = V"SimpleExpr" * (V"PowOp" * expect(V"UnaryExpr", "PowExpr"))^-1 / binaryOp;
SimpleExpr = tagC("Number", V"Number")
+ tagC("Nil", kw("nil"))
+ tagC("Boolean", kw("false") * Cc(false))
+ tagC("Boolean", kw("true") * Cc(true))
+ tagC("Dots", sym("..."))
+ V"FuncDef"
+ (when("lexpr") * tagC("LetExpr", V"NameList" * sym("=") * -sym("=") * expect(V"ExprList", "EListLAssign")))
+ V"ShortFuncDef"
+ V"SuffixedExpr"
+ V"StatExpr";
StatExpr = (V"IfStat" + V"DoStat" + V"WhileStat" + V"RepeatStat" + V"ForStat") / statToExpr;
FuncCall = Cmt(V"SuffixedExpr", function(s, i, exp) return exp.tag == "Call" or exp.tag == "Invoke", exp end);
FuncCall = Cmt(V"SuffixedExpr", function(s, i, exp) return exp.tag == "Call" or exp.tag == "SafeCall", exp end);
VarExpr = Cmt(V"SuffixedExpr", function(s, i, exp) return exp.tag == "Id" or exp.tag == "Index", exp end);
SuffixedExpr = Cf(V"PrimaryExpr" * (V"Index" + V"Invoke" + V"Call")^0
+ V"NoCallPrimaryExpr" * -V"Call" * (V"Index" + V"Invoke" + V"Call")^0
+ V"NoCallPrimaryExpr", makeIndexOrCall);
SuffixedExpr = Cf(V"PrimaryExpr" * (V"Index" + V"MethodStub" + V"Call")^0
+ V"NoCallPrimaryExpr" * -V"Call" * (V"Index" + V"MethodStub" + V"Call")^0
+ V"NoCallPrimaryExpr", makeSuffixedExpr);
PrimaryExpr = V"SelfId" * (V"SelfCall" + V"SelfIndex")
+ V"Id"
+ tagC("Paren", sym("(") * expect(V"Expr", "ExprParen") * expect(sym(")"), "CParenExpr"));
NoCallPrimaryExpr = tagC("String", V"String") + V"Table" + V"TableCompr";
Index = tagC("DotIndex", sym("." * -P".") * expect(V"StrId", "NameIndex"))
+ tagC("ArrayIndex", sym("[" * -P(S"=[")) * expect(V"Expr", "ExprIndex") * expect(sym("]"), "CBracketIndex"));
Call = tagC("Call", V"FuncArgs");
Invoke = tagC("Invoke", Cg(sym(":" * -P":") * expect(V"StrId", "NameMeth") * expect(V"FuncArgs", "MethArgs")));
+ tagC("ArrayIndex", sym("[" * -P(S"=[")) * expect(V"Expr", "ExprIndex") * expect(sym("]"), "CBracketIndex"))
+ tagC("SafeDotIndex", sym("?." * -P".") * expect(V"StrId", "NameIndex"))
+ tagC("SafeArrayIndex", sym("?[" * -P(S"=[")) * expect(V"Expr", "ExprIndex") * expect(sym("]"), "CBracketIndex"));
MethodStub = tagC("MethodStub", sym(":" * -P":") * expect(V"StrId", "NameMeth"))
+ tagC("SafeMethodStub", sym("?:" * -P":") * expect(V"StrId", "NameMeth"));
Call = tagC("Call", V"FuncArgs")
+ tagC("SafeCall", P"?" * V"FuncArgs");
SelfCall = tagC("MethodStub", V"StrId") * V"Call";
SelfIndex = tagC("DotIndex", V"StrId");
SelfCall = tagC("Invoke", Cg(V"StrId" * V"FuncArgs"));
FuncDef = (kw("function") * V"FuncBody");
FuncArgs = sym("(") * commaSep(V"Expr", "ArgList")^-1 * expect(sym(")"), "CParenArgs")

View file

@ -158,16 +158,6 @@ local function traverse_call (env, call)
return true
end
local function traverse_invoke (env, invoke)
local status, msg = traverse_exp(env, invoke[1])
if not status then return status, msg end
for i=3, #invoke do
status, msg = traverse_exp(env, invoke[i])
if not status then return status, msg end
end
return true
end
local function traverse_assignment (env, stm)
local status, msg = traverse_varlist(env, stm[1])
if not status then return status, msg end
@ -238,6 +228,18 @@ local function traverse_goto (env, stm)
return true
end
local function traverse_let (env, stm)
local status, msg = traverse_explist(env, stm[2])
if not status then return status, msg end
return true
end
local function traverse_letrec (env, stm)
local status, msg = traverse_exp(env, stm[2][1])
if not status then return status, msg end
return true
end
local function traverse_if (env, stm)
local len = #stm
if len % 2 == 0 then
@ -266,18 +268,6 @@ local function traverse_label (env, stm)
return true
end
local function traverse_let (env, stm)
local status, msg = traverse_explist(env, stm[2])
if not status then return status, msg end
return true
end
local function traverse_letrec (env, stm)
local status, msg = traverse_exp(env, stm[2][1])
if not status then return status, msg end
return true
end
local function traverse_repeat (env, stm)
begin_loop(env)
local status, msg = traverse_block(env, stm[1])
@ -327,6 +317,22 @@ function traverse_varlist (env, varlist)
return true
end
local function traverse_methodstub (env, var)
local status, msg = traverse_exp(env, var[1])
if not status then return status, msg end
status, msg = traverse_exp(env, var[2])
if not status then return status, msg end
return true
end
local function traverse_safeindex (env, var)
local status, msg = traverse_exp(env, var[1])
if not status then return status, msg end
status, msg = traverse_exp(env, var[2])
if not status then return status, msg end
return true
end
function traverse_exp (env, exp)
local tag = exp.tag
if tag == "Nil" or
@ -344,15 +350,17 @@ function traverse_exp (env, exp)
return traverse_op(env, exp)
elseif tag == "Paren" then -- `Paren{ expr }
return traverse_paren(env, exp)
elseif tag == "Call" then -- `Call{ expr expr* }
elseif tag == "Call" or tag == "SafeCall" then -- `(Safe)Call{ expr expr* }
return traverse_call(env, exp)
elseif tag == "Invoke" then -- `Invoke{ expr `String{ <string> } expr* }
return traverse_invoke(env, exp)
elseif tag == "Id" or -- `Id{ <string> }
tag == "Index" then -- `Index{ expr expr }
return traverse_var(env, exp)
elseif tag == "SafeIndex" then -- `SafeIndex{ expr expr }
return traverse_safeindex(env, exp)
elseif tag == "TableCompr" then -- `TableCompr{ block }
return traverse_tablecompr(env, exp)
elseif tag == "MethodStub" or tag == "SafeMethodStub" then -- `(Safe)MethodStub{ expr expr }
return traverse_methodstub(env, exp)
elseif tag:match("Expr$") then -- `StatExpr{ ... }
return traverse_statexpr(env, exp)
else
@ -399,8 +407,6 @@ function traverse_stm (env, stm)
return traverse_break(env, stm)
elseif tag == "Call" then -- `Call{ expr expr* }
return traverse_call(env, stm)
elseif tag == "Invoke" then -- `Invoke{ expr `String{ <string> } expr* }
return traverse_invoke(env, stm)
elseif tag == "Continue" then
return traverse_continue(env, stm)
elseif tag == "Push" then -- `Push{ <expr>* }

View file

@ -1,6 +1,6 @@
local util = {}
function util.search(modpath, exts={"can", "lua"})
function util.search(modpath, exts={})
for _, ext in ipairs(exts) do
for path in package.path:gmatch("[^;]+") do
local fpath = path:gsub("%.lua", "."..ext):gsub("%?", (modpath:gsub("%.", "/")))

View file

@ -1,4 +1,8 @@
local candran = dofile(arg[1] or "../candran.lua")
candran.default.indentation = "\t"
candran.default.mapLines = false
local load = require("lib.util").load
-- test helper
local results = {} -- tests result
@ -10,15 +14,17 @@ local function test(name, candranCode, expectedResult, options)
local success, code = pcall(candran.make, candranCode, options)
if not success then
self.result = "error"
self.message = "error while making code:\n"..code
self.message = "/!\\ error while making code:\n"..code
return
end
-- load code
local success, func = pcall(loadstring or load, code)
local env = {}
for k, v in pairs(_G) do env[k] = v end
local success, func = pcall(load, code, nil, env)
if not success then
self.result = "error"
self.message = "error while loading code:\n"..func
self.message = "/!\\ error while loading code:\n"..func.."\ngenerated code:\n"..code
return
end
@ -26,14 +32,14 @@ local function test(name, candranCode, expectedResult, options)
local success, output = pcall(func)
if not success then
self.result = "error"
self.message = "error while running code:\n"..output
self.message = "/!\\ error while running code:\n"..output.."\ngenerated code:\n"..code
return
end
-- check result
if output ~= expectedResult then
self.result = "fail"
self.message = "invalid result from the code; it returned "..tostring(output).." instead of "..tostring(expectedResult)
self.message = "/!\\ invalid result from the code; it returned "..tostring(output).." instead of "..tostring(expectedResult).."; generated code:\n"..code
return
else
self.result = "success"
@ -669,6 +675,154 @@ test("suffixable table comprehension array index", [[
return [@len=3]["len"]
]], 3)
-- let in condition expression
test("let in while condition, evaluation each iteration", [[
local s = ""
local i = 0
while (a = i+2) and i < 3 do
s = s .. tostring(a)
i = i + 1
a = 0
end
return s
]], "234")
test("let in while condition, scope", [[
local s = ""
local i = 0
while (a = i+2) and i < 3 do
s = s .. tostring(a)
i = i + 1
a = 0
end
return a
]], nil)
test("several let in while condition, evaluation order", [[
local s = ""
local i = 0
while (a = (b=i+1)+1) and i < 3 do
assert(b==i+1)
s = s .. tostring(a)
i = i + 1
a = 0
end
return s
]], "234")
test("several let in while condition, only test the first", [[
local s = ""
local i = 0
while (a,b = false,i) and i < 3 do
s = s .. tostring(a)
i = i + 1
end
return s
]], "")
test("let in if condition", [[
if a = false then
error("condition was false")
elseif b = nil then
error("condition was nil")
elseif c = true then
return "ok"
elseif d = true then
error("should not be reachable")
end
]], "ok")
test("let in if condition, scope", [[
local r
if a = false then
error("condition was false")
elseif b = nil then
error("condition was nil")
elseif c = true then
assert(a == false)
assert(d == nil)
r = "ok"
elseif d = true then
error("should not be reachable")
end
assert(c == nil)
return r
]], "ok")
test("several let in if condition, only test the first", [[
if a = false then
error("condition was false")
elseif b = nil then
error("condition was nil")
elseif c, d = false, "ok" then
error("should have tested against c")
else
return d
end
]], "ok")
test("several let in if condition, evaluation order", [[
local t = { k = "ok" }
if a = t[b,c = "k", "l"] then
assert(c == "l")
assert(b == "k")
return a
end
]], "ok")
-- Method stub
test("method stub, basic", [[
local t = { s = "ok", m = function(self) return self.s end }
local f = t:m
return f()
]], "ok")
test("method stub, store method", [[
local t = { s = "ok", m = function(self) return self.s end }
local f = t:m
t.m = function() return "not ok" end
return f()
]], "ok")
test("method stub, store object", [[
local t = { s = "ok", m = function(self) return self.s end }
local f = t:m
t = {}
return f()
]], "ok")
test("method stub, returns nil if method nil", [[
local t = { m = nil }
return t:m
]], nil)
-- Safe prefixes
test("safe method stub, when nil", [[
return t?:m
]], nil)
test("safe method stub, when non-nil", [[
local t = { s = "ok", m = function(self) return self.s end }
return t?:m()
]], "ok")
test("safe call, when nil", [[
return f?()
]], nil)
test("safe call, when non nil", [[
f = function() return "ok" end
return f?()
]], "ok")
test("safe index, when nil", [[
return f?.l
]], nil)
test("safe index, when non nil", [[
f = { l = "ok" }
return f?.l
]], "ok")
test("safe prefixes, random chaining", [[
f = { l = { m = function(s) return s or "ok" end } }
assert(f?.l?.m() == "ok")
assert(f?.l?.o == nil)
assert(f?.l?.o?() == nil)
assert(f?.lo?.o?() == nil)
assert(f?.l?:m?() == f.l)
assert(f?.l:mo == nil)
assert(f.l?:o?() == nil)
]])
-- results
local resultCounter = {}
local testCounter = 0