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 = { let a = {
hey = true, hey = true,
child = nil,
method = :(foo, thing) -- short function declaration, with self method = :(foo, thing) -- short function declaration, with self
@hey = thing(foo) -- @ as an alias for self @hey = thing(foo) -- @ as an alias for self
end, end,
@ -33,6 +35,13 @@ a:method(42, (foo)
return "something " .. foo return "something " .. foo
end) 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 local odd = [ -- table comprehension
for i=1, 10 do for i=1, 10 do
if i%2 == 0 then 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 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. **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. 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 ##### Default function parameters
```lua ```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. 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 ##### Suffixable string and table litterals
```lua ```lua
"some text":upper() -- "SOME TEXT". Same as ("some text"):upper() in 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. **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 ##### Statement expressions
```lua ```lua
a = if false then a = if false then

View file

@ -86,7 +86,7 @@ function candran.preprocess(input, options={})
-- @tparam modpath string module path -- @tparam modpath string module path
-- @tparam margs table preprocessor options to use when preprocessessing the module -- @tparam margs table preprocessor options to use when preprocessessing the module
env.import = function(modpath, margs={}) 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 -- open module file
local f = io.open(filepath) 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 states.continue = {} -- when in a loop that use continue
CONTINUE_START = () CONTINUE_START = ()
@ -19,12 +21,9 @@ tags.Break = ()
end end
end end
tags.Goto = () -- Unsuported features
error("Lua 5.1 does not support the goto keyword") tags.Goto = nil
end tags.Label = nil
tags.Label = ()
error("Lua 5.1 does not support labels")
end
#local patch = output #local patch = output
#output = "" #output = ""

View file

@ -1,3 +1,5 @@
local targetName = "Lua 5.3"
return function(code, ast, options) return function(code, ast, options)
--- Line mapping --- Line mapping
local lastInputPos = 1 -- last token position in the input code local lastInputPos = 1 -- last token position in the input code
@ -9,7 +11,7 @@ return function(code, ast, options)
local indentLevel = 0 local indentLevel = 0
-- Returns a newline. -- Returns a newline.
local function 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 if options.mapLines then
local sub = code:sub(lastInputPos) 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")
@ -25,7 +27,7 @@ return function(code, ast, options)
prevLinePos = lastInputPos prevLinePos = lastInputPos
r = " -- " .. lastSource .. ":" .. lastLine .. r r = " -- "..lastSource..":"..lastLine..r
end end
return r return r
end 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". -- 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 function addRequire(mod, name, field)
if not required[mod] then 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 required[mod] = true
end end
end end
@ -54,12 +56,13 @@ return function(code, ast, options)
--- Variable management --- Variable management
-- Returns the prefixed variable name. -- Returns the prefixed variable name.
local function var(name) local function var(name)
return options.variablePrefix .. name return options.variablePrefix..name
end end
--- AST traversal helpers --- AST traversal helpers
local loop = { "While", "Repeat", "Fornum", "Forin" } -- loops 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 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. -- 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". -- Won't recursively follow nodes which have a tag in "nofollow".
local function any(list, tags, nofollow={}) local function any(list, tags, nofollow={})
@ -85,6 +88,50 @@ return function(code, ast, options)
return nil return nil
end 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 --- State stacks
-- Used for context-sensitive syntax. -- Used for context-sensitive syntax.
local states = { local states = {
@ -117,16 +164,16 @@ return function(code, ast, options)
--- Lua function calls writer --- Lua function calls writer
local UNPACK = (list, i, j) -- table.unpack 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 end
local APPEND = (t, toAppend) -- append values "toAppend" (multiple values possible) to t 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 end
local CONTINUE_START = () -- at the start of loops using continue local CONTINUE_START = () -- at the start of loops using continue
return "do" .. indent() return "do"..indent()
end end
local CONTINUE_STOP = () -- at the start of loops using continue local CONTINUE_STOP = () -- at the start of loops using continue
return unindent() .. "end" .. newline() .. "::" .. var("continue") .. "::" return unindent().."end"..newline().."::"..var"continue".."::"
end end
--- Tag constructors --- Tag constructors
@ -140,16 +187,16 @@ return function(code, ast, options)
end end
local r = "" local r = ""
if hasPush then if hasPush then
r ..= push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline() r ..= push("push", var"push").."local "..var"push".." = {}"..newline()
end end
for i=1, #t-1, 1 do for i=1, #t-1, 1 do
r ..= lua(t[i]) .. newline() r ..= lua(t[i])..newline()
end end
if t[#t] then if t[#t] then
r ..= lua(t[#t]) r ..= lua(t[#t])
end end
if hasPush and (t[#t] and t[#t].tag ~= "Return") then -- add return only if needed 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 end
return r return r
end, end,
@ -158,81 +205,122 @@ return function(code, ast, options)
-- Do{ stat* } -- Do{ stat* }
Do = (t) Do = (t)
return "do" .. indent() .. lua(t, "Block") .. unindent() .. "end" return "do"..indent()..lua(t, "Block")..unindent().."end"
end, end,
-- Set{ {lhs+} (opid? = opid?)? {expr+} } -- Set{ {lhs+} (opid? = opid?)? {expr+} }
Set = (t) Set = (t)
if #t == 2 then 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 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 elseif #t == 4 then
if t[3] == "=" 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 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 end
return r return r
else 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 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 end
return r return r
end end
else -- You are mad. 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 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 end
return r return r
end end
end, end,
-- While{ expr block } -- While{ expr block }
While = (t) While = (t)
local r = ""
local hasContinue = any(t[2], { "Continue" }, loop) 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 if hasContinue then
r ..= CONTINUE_START() r ..= CONTINUE_START()
end end
r .. = lua(t[2]) r ..= lua(t[2])
if hasContinue then if hasContinue then
r ..= CONTINUE_STOP() r ..= CONTINUE_STOP()
end 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 return r
end, end,
-- Repeat{ block expr } -- Repeat{ block expr }
Repeat = (t) Repeat = (t)
local hasContinue = any(t[1], { "Continue" }, loop) local hasContinue = any(t[1], { "Continue" }, loop)
local r = "repeat" .. indent() local r = "repeat"..indent()
if hasContinue then if hasContinue then
r ..= CONTINUE_START() r ..= CONTINUE_START()
end end
r .. = lua(t[1]) r ..= lua(t[1])
if hasContinue then if hasContinue then
r ..= CONTINUE_STOP() r ..= CONTINUE_STOP()
end end
r ..= unindent() .. "until " .. lua(t[2]) r ..= unindent().."until "..lua(t[2])
return r return r
end, end,
-- If{ (expr block)+ block? } -- If{ (lexpr block)+ block? }
If = (t) 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 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 end
if #t % 2 == 1 then if #t % 2 == 1 then
r ..= "else" .. indent() .. lua(t[#t]) .. unindent() r ..= "else"..indent()..lua(t[#t])..unindent()
end end
return r .. "end" r ..= "end"
for i=1, toClose do
r ..= unindent().."end"
end
return r
end, end,
-- Fornum{ ident expr expr expr? block } -- Fornum{ ident expr expr expr? block }
Fornum = (t) 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 if #t == 5 then
local hasContinue = any(t[5], { "Continue" }, loop) local hasContinue = any(t[5], { "Continue" }, loop)
r ..= ", " .. lua(t[4]) .. " do" .. indent() r ..= ", "..lua(t[4]).." do"..indent()
if hasContinue then if hasContinue then
r ..= CONTINUE_START() r ..= CONTINUE_START()
end end
@ -240,10 +328,10 @@ return function(code, ast, options)
if hasContinue then if hasContinue then
r ..= CONTINUE_STOP() r ..= CONTINUE_STOP()
end end
return r .. unindent() .. "end" return r..unindent().."end"
else else
local hasContinue = any(t[4], { "Continue" }, loop) local hasContinue = any(t[4], { "Continue" }, loop)
r ..= " do" .. indent() r ..= " do"..indent()
if hasContinue then if hasContinue then
r ..= CONTINUE_START() r ..= CONTINUE_START()
end end
@ -251,13 +339,13 @@ return function(code, ast, options)
if hasContinue then if hasContinue then
r ..= CONTINUE_STOP() r ..= CONTINUE_STOP()
end end
return r .. unindent() .. "end" return r..unindent().."end"
end end
end, end,
-- Forin{ {ident+} {expr+} block } -- Forin{ {ident+} {expr+} block }
Forin = (t) Forin = (t)
local hasContinue = any(t[3], { "Continue" }, loop) 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 if hasContinue then
r ..= CONTINUE_START() r ..= CONTINUE_START()
end end
@ -265,7 +353,7 @@ return function(code, ast, options)
if hasContinue then if hasContinue then
r ..= CONTINUE_STOP() r ..= CONTINUE_STOP()
end end
return r .. unindent() .. "end" return r..unindent().."end"
end, end,
-- Local{ {ident+} {expr+}? } -- Local{ {ident+} {expr+}? }
Local = (t) Local = (t)
@ -278,12 +366,12 @@ return function(code, ast, options)
-- Let{ {ident+} {expr+}? } -- Let{ {ident+} {expr+}? }
Let = (t) Let = (t)
local nameList = lua(t[1], "_lhs") local nameList = lua(t[1], "_lhs")
local r = "local " .. nameList local r = "local "..nameList
if t[2][1] then if t[2][1] then
if any(t[2], { "Function", "Table", "Paren" }) then -- predeclaration doesn't matter otherwise if all(t[2], { "Nil", "Dots", "Boolean", "Number", "String" }) then -- predeclaration doesn't matter here
r ..= newline() .. nameList .. " = " .. lua(t[2], "_lhs") r ..= " = "..lua(t[2], "_lhs")
else else
r ..= " = " .. lua(t[2], "_lhs") r ..= newline()..nameList.." = "..lua(t[2], "_lhs")
end end
end end
return r return r
@ -294,11 +382,11 @@ return function(code, ast, options)
end, end,
-- Goto{ <string> } -- Goto{ <string> }
Goto = (t) Goto = (t)
return "goto " .. lua(t, "Id") return "goto "..lua(t, "Id")
end, end,
-- Label{ <string> } -- Label{ <string> }
Label = (t) Label = (t)
return "::" .. lua(t, "Id") .. "::" return "::"..lua(t, "Id").."::"
end, end,
-- Return{ <expr*> } -- Return{ <expr*> }
Return = (t) Return = (t)
@ -306,9 +394,9 @@ return function(code, ast, options)
if push then if push then
local r = "" local r = ""
for _, val in ipairs(t) do for _, val in ipairs(t) do
r ..= push .. "[#" .. push .. "+1] = " .. lua(val) .. newline() r ..= push.."[#"..push.."+1] = "..lua(val)..newline()
end end
return r .. "return " .. UNPACK(push) return r.."return "..UNPACK(push)
else else
return "return "..lua(t, "_lhs") return "return "..lua(t, "_lhs")
end end
@ -318,13 +406,13 @@ return function(code, ast, options)
local var = assert(peek("push"), "no context given for push") local var = assert(peek("push"), "no context given for push")
r = "" r = ""
for i=1, #t-1, 1 do for i=1, #t-1, 1 do
r ..= var .. "[#" .. var .. "+1] = " .. lua(t[i]) .. newline() r ..= var.."[#"..var.."+1] = "..lua(t[i])..newline()
end end
if t[#t] then 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])) r ..= APPEND(var, lua(t[#t]))
else else
r ..= var .. "[#" .. var .. "+1] = " .. lua(t[#t]) r ..= var.."[#"..var.."+1] = "..lua(t[#t])
end end
end end
return r return r
@ -335,7 +423,7 @@ return function(code, ast, options)
end, end,
-- Continue -- Continue
Continue = () Continue = ()
return "goto " .. var("continue") return "goto "..var"continue"
end, end,
-- apply (below) -- apply (below)
@ -359,7 +447,7 @@ return function(code, ast, options)
end, end,
-- String{ <string> } -- String{ <string> }
String = (t) String = (t)
return ("%q"):format(t[1]) return "%q":format(t[1])
end, end,
-- Function{ { ( `ParPair{ Id expr } | `Id{ <string> } )* `Dots? } block } -- Function{ { ( `ParPair{ Id expr } | `Id{ <string> } )* `Dots? } block }
_functionWithoutKeyword = (t) _functionWithoutKeyword = (t)
@ -369,7 +457,7 @@ return function(code, ast, options)
if t[1][1].tag == "ParPair" then if t[1][1].tag == "ParPair" then
local id = lua(t[1][1][1]) local id = lua(t[1][1][1])
indentLevel += 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 indentLevel -= 1
r ..= id r ..= id
else else
@ -379,66 +467,66 @@ return function(code, ast, options)
if t[1][i].tag == "ParPair" then if t[1][i].tag == "ParPair" then
local id = lua(t[1][i][1]) local id = lua(t[1][i][1])
indentLevel += 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 indentLevel -= 1
r ..= ", " ..id r ..= ", " ..id
else else
r ..= ", " .. lua(t[1][i]) r ..= ", "..lua(t[1][i])
end end
end end
end end
r ..= ")" .. indent() r ..= ")"..indent()
for _, d in ipairs(decl) do for _, d in ipairs(decl) do
r ..= d .. newline() r ..= d..newline()
end end
if t[2][#t[2]] and t[2][#t[2]].tag == "Push" then -- convert final push to return if t[2][#t[2]] and t[2][#t[2]].tag == "Push" then -- convert final push to return
t[2][#t[2]].tag = "Return" t[2][#t[2]].tag = "Return"
end end
local hasPush = any(t[2], { "Push" }, func) local hasPush = any(t[2], { "Push" }, func)
if hasPush then if hasPush then
r ..= push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline() r ..= push("push", var"push").."local "..var"push".." = {}"..newline()
else 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 end
r ..= lua(t[2]) r ..= lua(t[2])
if hasPush and (t[2][#t[2]] and t[2][#t[2]].tag ~= "Return") then -- add return only if needed 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 end
pop("push") pop("push")
return r .. unindent() .. "end" return r..unindent().."end"
end, end,
Function = (t) Function = (t)
return "function" .. lua(t, "_functionWithoutKeyword") return "function"..lua(t, "_functionWithoutKeyword")
end, end,
-- Table{ ( `Pair{ expr expr } | expr )* } -- Table{ ( `Pair{ expr expr } | expr )* }
Pair = (t) Pair = (t)
return "[" .. lua(t[1]) .. "] = " .. lua(t[2]) return "["..lua(t[1]).."] = "..lua(t[2])
end, end,
Table = (t) Table = (t)
if #t == 0 then if #t == 0 then
return "{}" return "{}"
elseif #t == 1 then elseif #t == 1 then
return "{ " .. lua(t, "_lhs") .. " }" return "{ "..lua(t, "_lhs").." }"
else else
return "{" .. indent() .. lua(t, "_lhs", nil, true) .. unindent() .. "}" return "{"..indent()..lua(t, "_lhs", nil, true)..unindent().."}"
end end
end, end,
-- TableCompr{ block } -- TableCompr{ block }
TableCompr = (t) 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, end,
-- Op{ opid expr expr? } -- Op{ opid expr expr? }
Op = (t) Op = (t)
local r local r
if #t == 2 then if #t == 2 then
if type(tags._opid[t[1]]) == "string" 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 else
r = tags._opid[t[1]](t[2]) r = tags._opid[t[1]](t[2])
end end
else else
if type(tags._opid[t[1]]) == "string" then 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 else
r = tags._opid[t[1]](t[2], t[3]) r = tags._opid[t[1]](t[2], t[3])
end end
@ -447,27 +535,51 @@ return function(code, ast, options)
end, end,
-- Paren{ expr } -- Paren{ expr }
Paren = (t) 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, end,
-- statexpr (below) -- statexpr (below)
-- apply (below) -- apply (below)
-- lhs (below) -- lhs (below)
-- lexpr --
LetExpr = (t)
return lua(t[1][1])
end,
-- statexpr -- -- statexpr --
_statexpr = (t, stat) _statexpr = (t, stat)
local hasPush = any(t, { "Push" }, func) local hasPush = any(t, { "Push" }, func)
local r = "(function()" .. indent() local r = "(function()"..indent()
if hasPush then if hasPush then
r ..= push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline() r ..= push("push", var"push").."local "..var"push".." = {}"..newline()
else 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 don't affect us)
end end
r ..= lua(t, stat) r ..= lua(t, stat)
if hasPush then if hasPush then
r ..= newline() .. "return " .. UNPACK(var("push")) r ..= newline().."return "..UNPACK(var"push")
end end
pop("push") pop("push")
r ..= unindent() .. "end)()" r ..= unindent().."end)()"
return r return r
end, end,
-- DoExpr{ stat* } -- DoExpr{ stat* }
@ -509,18 +621,23 @@ return function(code, ast, options)
-- Call{ expr expr* } -- Call{ expr expr* }
Call = (t) Call = (t)
if t[1].tag == "String" or t[1].tag == "Table" then 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][1])..":"..lua(t[1][2], "Id").."("..lua(t, "_lhs", 2)..")"
end
else else
return lua(t[1]) .. "(" .. lua(t, "_lhs", 2) .. ")" return lua(t[1]).."("..lua(t, "_lhs", 2)..")"
end end
end, end,
-- SafeCall{ expr expr* }
-- Invoke{ expr `String{ <string> } expr* } SafeCall = (t)
Invoke = (t) if t[1].tag ~= "Id" then -- side effect possible, only evaluate each expr once (or already in a safe context)
if t[1].tag == "String" or t[1].tag == "Table" then return lua(t, "SafeIndex")
return "("..lua(t[1]).."):"..lua(t[2], "Id").."("..lua(t, "_lhs", 3)..")" else -- no side effects possible
else return "("..lua(t[1]).." ~= nil and "..lua(t[1]).."("..lua(t, "_lhs", 2)..") or nil)"
return lua(t[1])..":"..lua(t[2], "Id").."("..lua(t, "_lhs", 3)..")"
end end
end, end,
@ -530,7 +647,7 @@ return function(code, ast, options)
if t[start] then if t[start] then
r = lua(t[start]) r = lua(t[start])
for i=start+1, #t, 1 do for i=start+1, #t, 1 do
r ..= "," .. (newlines and newline() or " ") .. lua(t[i]) r ..= ","..(newlines and newline() or " ")..lua(t[i])
end end
else else
r = "" r = ""
@ -549,6 +666,29 @@ return function(code, ast, options)
return lua(t[1]).."["..lua(t[2]).."]" return lua(t[1]).."["..lua(t[2]).."]"
end end
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 --
_opid = { _opid = {
@ -560,12 +700,12 @@ return function(code, ast, options)
} }
}, { }, {
__index = (self, key) __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 end
}) })
#placeholder("patch") #placeholder("patch")
local code = lua(ast) .. newline() local code = lua(ast)..newline()
return requireStr .. code return requireStr..code
end end

View file

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

View file

@ -8,7 +8,7 @@ To be implemented, theese need to:
* be significantly useful compared to existing Candran/Lua code. * 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 supersede it.
Example currently rejected ideas: Example rejected ideas:
* Python-style function decorators (implemented in Candran 0.1.0): * 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). 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. 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() clean()
end 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 * static type checking
local a = externalFunc() -- unknown local a = externalFunc() -- unknown
if a == "hey" then if a == "hey" then
@ -103,13 +94,3 @@ meh
* Other potential inspiration * Other potential inspiration
https://www.ruby-lang.org/fr/ 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... | `Set{ {lhs+} (opid? = opid?)? {expr+} } -- lhs1, lhs2... op=op e1, e2...
| `While{ expr block } -- while e do b end | `While{ expr block } -- while e do b end
| `Repeat{ block expr } -- repeat b until e | `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 | `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{ {ident+} {expr+}? } -- local i1, i2... = e1, e2... | `Local{ {ident+} {expr+}? } -- local i1, i2... = e1, e2...
@ -36,10 +36,17 @@ 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
| `TableCompr{ block } | `TableCompr{ block }
| `MethodStub{ expr expr }
| `SafeMethodStub{ expr expr }
| `SafeIndex{ expr expr }
| statexpr | statexpr
| apply | apply
| lhs | lhs
lexpr:
`LetExpr{ {ident+} {expr+}? }
| every node from expr
statexpr: statexpr:
`DoExpr{ stat* } `DoExpr{ stat* }
| `WhileExpr{ expr block } -- while e do b end | `WhileExpr{ expr block } -- while e do b end
@ -50,7 +57,7 @@ statexpr:
apply: apply:
`Call{ expr expr* } `Call{ expr expr* }
| `Invoke{ expr `String{ <string> } expr* } | `SafeCall{ expr expr* }
lhs: `Id{ <string> } | `Index{ 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 } return { tag = "Index", pos = t.pos, [1] = t, [2] = index }
end end
local function markMethod(t, method) local function markMethod (t, method)
if method then if method then
return { tag = "Index", pos = t.pos, is_method = true, [1] = t, [2] = method } return { tag = "Index", pos = t.pos, is_method = true, [1] = t, [2] = method }
end end
return t return t
end end
local function makeIndexOrCall (t1, t2) local function makeSuffixedExpr (t1, t2)
if t2.tag == "Call" or t2.tag == "Invoke" then if t2.tag == "Call" or t2.tag == "SafeCall" then
local t = { tag = t2.tag, pos = t1.pos, [1] = t1 } local t = { tag = t2.tag, pos = t1.pos, [1] = t1 }
for k, v in ipairs(t2) do for k, v in ipairs(t2) do
table.insert(t, v) table.insert(t, v)
end end
return t return t
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
return { tag = "Index", pos = t1.pos, [1] = t1, [2] = t2[1] }
end end
local function fixShortFunc(t) local function fixShortFunc (t)
if t[1] == ":" then -- self method if t[1] == ":" then -- self method
table.insert(t[2], 1, { tag = "Id", "self" }) table.insert(t[2], 1, { tag = "Id", "self" })
table.remove(t, 1) table.remove(t, 1)
@ -283,12 +297,12 @@ local function fixShortFunc(t)
return t return t
end end
local function statToExpr(t) -- tag a StatExpr local function statToExpr (t) -- tag a StatExpr
t.tag = t.tag .. "Expr" t.tag = t.tag .. "Expr"
return t return t
end end
local function fixStructure(t) -- fix the AST structure if needed local function fixStructure (t) -- fix the AST structure if needed
local i = 1 local i = 1
while i <= #t do while i <= #t do
if type(t[i]) == "table" then if type(t[i]) == "table" then
@ -309,7 +323,7 @@ local function fixStructure(t) -- fix the AST structure if needed
return t return t
end 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" then
@ -404,7 +418,7 @@ local function searchEndRec(block, isRecCall) -- recursively search potential "e
return nil return nil
end 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)) local r = searchEndRec(fixStructure(t))
if not r then if not r then
return false return false
@ -412,7 +426,7 @@ local function searchEnd(s, p, t) -- match time capture which try to restructure
return true, r return true, r
end 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 if canFollow then
return (-start * V"SingleStatBlock" * canFollow^-1) return (-start * V"SingleStatBlock" * canFollow^-1)
+ (expect(start, startLabel) * ((V"Block" * (canFollow + kw("end"))) + (expect(start, startLabel) * ((V"Block" * (canFollow + kw("end")))
@ -424,16 +438,40 @@ local function expectBlockOrSingleStatWithStartEnd(start, startLabel, stopLabel,
end end
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")) return (V"Block" * kw("end"))
+ (Cmt(V"Block", searchEnd) + throw(label)) + (Cmt(V"Block", searchEnd) + throw(label))
end 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")) return (V"BlockNoErr" * kw("end"))
+ Cmt(V"BlockNoErr", searchEnd) + Cmt(V"BlockNoErr", searchEnd)
end 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 -- grammar
local G = { V"Lua", local G = { V"Lua",
Lua = (V"Shebang"^-1 * V"Skip" * V"Block" * expect(P(-1), "Extra")) / fixStructure; 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 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"); IfStat = tagC("If", V"IfPart");
IfPart = kw("if") * expect(V"Expr", "ExprIf") * expectBlockOrSingleStatWithStartEnd(kw("then"), "ThenIf", "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") * expect(V"Expr", "ExprEIf") * expectBlockOrSingleStatWithStartEnd(kw("then"), "ThenEIf", "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"); ElsePart = kw("else") * expectBlockWithEnd("EndIf");
DoStat = kw("do") * expectBlockWithEnd("EndDo") / tagDo; 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"); WhileBody = expectBlockOrSingleStatWithStartEnd(kw("do"), "DoWhile", "EndWhile");
RepeatStat = tagC("Repeat", kw("repeat") * V"Block" * expect(kw("until"), "UntilRep") * expect(V"Expr", "ExprRep")); 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"); 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())));
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; FuncStat = tagC("Set", kw("function") * expect(V"FuncName", "FuncName") * V"FuncBody") / fixFuncStat;
FuncName = Cf(V"Id" * (sym(".") * expect(V"StrId", "NameFunc1"))^0, insertIndex) 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 UnaryExpr = V"UnaryOp" * expect(V"UnaryExpr", "UnaryExpr") / unaryOp
+ V"PowExpr"; + V"PowExpr";
PowExpr = V"SimpleExpr" * (V"PowOp" * expect(V"UnaryExpr", "PowExpr"))^-1 / binaryOp; PowExpr = V"SimpleExpr" * (V"PowOp" * expect(V"UnaryExpr", "PowExpr"))^-1 / binaryOp;
SimpleExpr = tagC("Number", V"Number")
SimpleExpr = tagC("Number", V"Number") + tagC("Nil", kw("nil"))
+ tagC("Nil", kw("nil")) + tagC("Boolean", kw("false") * Cc(false))
+ tagC("Boolean", kw("false") * Cc(false)) + tagC("Boolean", kw("true") * Cc(true))
+ tagC("Boolean", kw("true") * Cc(true)) + tagC("Dots", sym("..."))
+ tagC("Dots", sym("...")) + V"FuncDef"
+ V"FuncDef" + (when("lexpr") * tagC("LetExpr", V"NameList" * sym("=") * -sym("=") * expect(V"ExprList", "EListLAssign")))
+ V"ShortFuncDef" + V"ShortFuncDef"
+ V"SuffixedExpr" + V"SuffixedExpr"
+ V"StatExpr"; + V"StatExpr";
StatExpr = (V"IfStat" + V"DoStat" + V"WhileStat" + V"RepeatStat" + V"ForStat") / statToExpr; 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); 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 SuffixedExpr = Cf(V"PrimaryExpr" * (V"Index" + V"MethodStub" + V"Call")^0
+ V"NoCallPrimaryExpr" * -V"Call" * (V"Index" + V"Invoke" + V"Call")^0 + V"NoCallPrimaryExpr" * -V"Call" * (V"Index" + V"MethodStub" + V"Call")^0
+ V"NoCallPrimaryExpr", makeIndexOrCall); + V"NoCallPrimaryExpr", makeSuffixedExpr);
PrimaryExpr = V"SelfId" * (V"SelfCall" + V"SelfIndex") PrimaryExpr = V"SelfId" * (V"SelfCall" + V"SelfIndex")
+ V"Id" + V"Id"
+ tagC("Paren", sym("(") * expect(V"Expr", "ExprParen") * expect(sym(")"), "CParenExpr")); + tagC("Paren", sym("(") * expect(V"Expr", "ExprParen") * expect(sym(")"), "CParenExpr"));
NoCallPrimaryExpr = tagC("String", V"String") + V"Table" + V"TableCompr"; NoCallPrimaryExpr = tagC("String", V"String") + V"Table" + V"TableCompr";
Index = tagC("DotIndex", sym("." * -P".") * expect(V"StrId", "NameIndex")) Index = tagC("DotIndex", sym("." * -P".") * expect(V"StrId", "NameIndex"))
+ tagC("ArrayIndex", sym("[" * -P(S"=[")) * expect(V"Expr", "ExprIndex") * expect(sym("]"), "CBracketIndex")); + tagC("ArrayIndex", sym("[" * -P(S"=[")) * expect(V"Expr", "ExprIndex") * expect(sym("]"), "CBracketIndex"))
Call = tagC("Call", V"FuncArgs"); + tagC("SafeDotIndex", sym("?." * -P".") * expect(V"StrId", "NameIndex"))
Invoke = tagC("Invoke", Cg(sym(":" * -P":") * expect(V"StrId", "NameMeth") * expect(V"FuncArgs", "MethArgs"))); + 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"); SelfIndex = tagC("DotIndex", V"StrId");
SelfCall = tagC("Invoke", Cg(V"StrId" * V"FuncArgs"));
FuncDef = (kw("function") * V"FuncBody"); FuncDef = (kw("function") * V"FuncBody");
FuncArgs = sym("(") * commaSep(V"Expr", "ArgList")^-1 * expect(sym(")"), "CParenArgs") FuncArgs = sym("(") * commaSep(V"Expr", "ArgList")^-1 * expect(sym(")"), "CParenArgs")

View file

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

View file

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

View file

@ -1,4 +1,8 @@
local candran = dofile(arg[1] or "../candran.lua") local candran = dofile(arg[1] or "../candran.lua")
candran.default.indentation = "\t"
candran.default.mapLines = false
local load = require("lib.util").load
-- test helper -- test helper
local results = {} -- tests result local results = {} -- tests result
@ -10,15 +14,17 @@ local function test(name, candranCode, expectedResult, options)
local success, code = pcall(candran.make, candranCode, options) local success, code = pcall(candran.make, candranCode, options)
if not success then if not success then
self.result = "error" self.result = "error"
self.message = "error while making code:\n"..code self.message = "/!\\ error while making code:\n"..code
return return
end end
-- load code -- 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 if not success then
self.result = "error" self.result = "error"
self.message = "error while loading code:\n"..func self.message = "/!\\ error while loading code:\n"..func.."\ngenerated code:\n"..code
return return
end end
@ -26,14 +32,14 @@ local function test(name, candranCode, expectedResult, options)
local success, output = pcall(func) local success, output = pcall(func)
if not success then if not success then
self.result = "error" self.result = "error"
self.message = "error while running code:\n"..output self.message = "/!\\ error while running code:\n"..output.."\ngenerated code:\n"..code
return return
end end
-- check result -- check result
if output ~= expectedResult then if output ~= expectedResult then
self.result = "fail" 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 return
else else
self.result = "success" self.result = "success"
@ -669,6 +675,154 @@ test("suffixable table comprehension array index", [[
return [@len=3]["len"] return [@len=3]["len"]
]], 3) ]], 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 -- results
local resultCounter = {} local resultCounter = {}
local testCounter = 0 local testCounter = 0