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:
parent
6be81267d2
commit
851e9f89d6
11 changed files with 3688 additions and 2845 deletions
100
README.md
100
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
5743
candran.lua
5743
candran.lua
File diff suppressed because it is too large
Load diff
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
targetName = "luajit"
|
||||
|
||||
UNPACK = (list, i, j)
|
||||
return "unpack(" .. list .. (i and (", " .. i .. (j and (", " .. j) or "")) or "") .. ")"
|
||||
end
|
||||
|
|
|
|||
21
ideas.txt
21
ideas.txt
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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>* }
|
||||
|
|
|
|||
|
|
@ -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("%.", "/")))
|
||||
|
|
|
|||
164
test/test.lua
164
test/test.lua
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue