1
0
Fork 0
mirror of https://github.com/Reuh/candran.git synced 2025-10-27 17:59:30 +00:00
* Fixed HORRIBLE parsing bugs with short functions and right assignemnt
operators
* Allowed to omit then, do and end for some statements
* Fixed hexa numbers parsing
* Run the Lua 5.3 test suite through Candran, everything that should
work worked! Yay!

Lacks tests and README
This commit is contained in:
Étienne Fildadut 2017-08-31 19:17:34 +02:00
parent 724249555f
commit 70d3aba121
8 changed files with 369 additions and 95 deletions

View file

@ -27,13 +27,11 @@ for _, file in ipairs(args) do
local input = inputFile:read("*a")
inputFile:close()
if args.ast then
pp.dump(assert(parse(input)))
return
end
if args.chunkname == nil then
args.chunkname = file
if args.ast then
pp.dump(assert(parse(input, args.chunkname)))
return
end
local out = input

View file

@ -10,7 +10,7 @@
#import("lib.lua-parser.parser")
local candran = {
VERSION = "0.5.0"
VERSION = "0.6.0"
}
--- Default options.
@ -124,7 +124,7 @@ end
function candran.compile(input, options={})
options = util.merge(default, options)
local ast, errmsg = parser.parse(input, "candran")
local ast, errmsg = parser.parse(input, options.chunkname)
if not ast then
error("Compiler: error while parsing file: "..errmsg)

View file

@ -135,7 +135,7 @@ local function _()
return function(code, ast, options)
local lastInputPos = 1
local prevLinePos = 1
local lastSource = "nil"
local lastSource = options["chunkname"] or "nil"
local lastLine = 1
local indentLevel = 0
local function newline()
@ -449,10 +449,10 @@ end,
return "local function " .. lua(t[1][1]) .. lua(t[2][1], "_functionWithoutKeyword")
end,
["Goto"] = function(t)
return "goto " .. lua(t[1], "Id")
return "goto " .. lua(t, "Id")
end,
["Label"] = function(t)
return "::" .. lua(t[1], "Id") .. "::"
return "::" .. lua(t, "Id") .. "::"
end,
["Return"] = function(t)
local push = peek("push")
@ -509,7 +509,7 @@ if t[1][1] then
if t[1][1]["tag"] == "ParPair" then
local id = lua(t[1][1][1])
indentLevel = indentLevel + (1)
table["insert"](decl, id .. " = " .. id .. " == nil and " .. lua(t[1][1][2]) .. " or " .. id)
table["insert"](decl, "if " .. id .. " == nil then " .. id .. " = " .. lua(t[1][1][2]) .. " end")
indentLevel = indentLevel - (1)
r = r .. (id)
else
@ -616,7 +616,7 @@ end,
["IfExpr"] = function(t)
for i = 2, # t do
local block = t[i]
if block[# block]["tag"] == "Push" then
if block[# block] and block[# block]["tag"] == "Push" then
block[# block]["tag"] = "Return"
end
end
@ -694,7 +694,7 @@ local function _()
return function(code, ast, options)
local lastInputPos = 1
local prevLinePos = 1
local lastSource = "nil"
local lastSource = options["chunkname"] or "nil"
local lastLine = 1
local indentLevel = 0
local function newline()
@ -1008,10 +1008,10 @@ end,
return "local function " .. lua(t[1][1]) .. lua(t[2][1], "_functionWithoutKeyword")
end,
["Goto"] = function(t)
return "goto " .. lua(t[1], "Id")
return "goto " .. lua(t, "Id")
end,
["Label"] = function(t)
return "::" .. lua(t[1], "Id") .. "::"
return "::" .. lua(t, "Id") .. "::"
end,
["Return"] = function(t)
local push = peek("push")
@ -1068,7 +1068,7 @@ if t[1][1] then
if t[1][1]["tag"] == "ParPair" then
local id = lua(t[1][1][1])
indentLevel = indentLevel + (1)
table["insert"](decl, id .. " = " .. id .. " == nil and " .. lua(t[1][1][2]) .. " or " .. id)
table["insert"](decl, "if " .. id .. " == nil then " .. id .. " = " .. lua(t[1][1][2]) .. " end")
indentLevel = indentLevel - (1)
r = r .. (id)
else
@ -1175,7 +1175,7 @@ end,
["IfExpr"] = function(t)
for i = 2, # t do
local block = t[i]
if block[# block]["tag"] == "Push" then
if block[# block] and block[# block]["tag"] == "Push" then
block[# block]["tag"] = "Return"
end
end
@ -2178,7 +2178,7 @@ lpeg["locale"](lpeg)
local P, S, V = lpeg["P"], lpeg["S"], lpeg["V"]
local C, Carg, Cb, Cc = lpeg["C"], lpeg["Carg"], lpeg["Cb"], lpeg["Cc"]
local Cf, Cg, Cmt, Cp, Cs, Ct = lpeg["Cf"], lpeg["Cg"], lpeg["Cmt"], lpeg["Cp"], lpeg["Cs"], lpeg["Ct"]
local Lc, T = lpeg["Lc"], lpeg["T"]
local Rec, T = lpeg["Rec"], lpeg["T"]
local alpha, digit, alnum = lpeg["alpha"], lpeg["digit"], lpeg["alnum"]
local xdigit = lpeg["xdigit"]
local space = lpeg["space"]
@ -2610,52 +2610,176 @@ return {
[2] = t2[1]
}
end
local function fixAnonymousMethodParams(t1, t2)
if t1 == ":" then
t1 = t2
table["insert"](t1, 1, {
local function fixShortFunc(t)
if t[1] == ":" then
table["insert"](t[2], 1, {
["tag"] = "Id",
"self"
})
table["remove"](t, 1)
t["is_method"] = true
end
return t1
t["is_short"] = true
return t
end
local function statToExpr(t)
t["tag"] = t["tag"] .. "Expr"
return t
end
local function fixStructure(t)
local i = 1
while i <= # t do
if type(t[i]) == "table" then
fixStructure(t[i])
for j = # t[i], 1, - 1 do
local stat = t[i][j]
if type(stat) == "table" and stat["move_up_block"] and stat["move_up_block"] > 0 then
table["remove"](t[i], j)
table["insert"](t, i + 1, stat)
if t["tag"] == "Block" or t["tag"] == "Do" then
stat["move_up_block"] = stat["move_up_block"] - 1
end
end
end
end
i = i + 1
end
return t
end
local function searchEndRec(block, isRecCall)
for i, stat in ipairs(block) do
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
local exprlist
if stat["tag"] == "Set" or stat["tag"] == "Local" or stat["tag"] == "Let" or stat["tag"] == "Localrec" then
exprlist = stat[# stat]
elseif stat["tag"] == "Push" or stat["tag"] == "Return" then
exprlist = stat
end
local last = exprlist[# exprlist]
if last["tag"] == "Function" and last["is_short"] and not last["is_method"] and # last[1] == 1 then
local p = i
for j, fstat in ipairs(last[2]) do
p = i + j
table["insert"](block, p, fstat)
if stat["move_up_block"] then
fstat["move_up_block"] = (fstat["move_up_block"] or 0) + stat["move_up_block"]
end
if block["is_singlestatblock"] then
fstat["move_up_block"] = (fstat["move_up_block"] or 0) + 1
end
end
exprlist[# exprlist] = last[1]
exprlist[# exprlist]["tag"] = "Paren"
if not isRecCall then
for j = p + 1, # block, 1 do
block[j]["move_up_block"] = (block[j]["move_up_block"] or 0) + 1
end
end
return block, i
elseif last["tag"]:match("Expr$") then
local r = searchEndRec({ last })
if r then
for j = 2, # r, 1 do
table["insert"](block, i + j - 1, r[j])
end
return block, i
end
elseif last["tag"] == "Function" then
local r = searchEndRec(last[2])
if r then
return block, i
end
end
elseif stat["tag"]:match("^If") or stat["tag"]:match("^While") or stat["tag"]:match("^Repeat") or stat["tag"]:match("^Do") or stat["tag"]:match("^Fornum") or stat["tag"]:match("^Forin") then
local blocks
if stat["tag"]:match("^If") or stat["tag"]:match("^While") or stat["tag"]:match("^Repeat") or stat["tag"]:match("^Fornum") or stat["tag"]:match("^Forin") then
blocks = stat
elseif stat["tag"]:match("^Do") then
blocks = { stat }
end
for _, iblock in ipairs(blocks) do
if iblock["tag"] == "Block" then
local oldLen = # iblock
local newiBlock, newEnd = searchEndRec(iblock, true)
if newiBlock then
local p = i
for j = newEnd + (# iblock - oldLen) + 1, # iblock, 1 do
p = p + 1
table["insert"](block, p, iblock[j])
iblock[j] = nil
end
if not isRecCall then
for j = p + 1, # block, 1 do
block[j]["move_up_block"] = (block[j]["move_up_block"] or 0) + 1
end
end
return block, i
end
end
end
end
end
return nil
end
local function searchEnd(s, p, t)
local r = searchEndRec(fixStructure(t))
if not r then
return false
end
return true, r
end
local function expectBlockOrSingleStatWithStartEnd(start, startLabel, stopLabel, canFollow)
if canFollow then
return (- start * V("SingleStatBlock") * canFollow ^ - 1) + (expect(start, startLabel) * ((V("Block") * (canFollow + kw("end"))) + (Cmt(V("Block"), searchEnd) + throw(stopLabel))))
else
return (- start * V("SingleStatBlock")) + (expect(start, startLabel) * ((V("Block") * kw("end")) + (Cmt(V("Block"), searchEnd) + throw(stopLabel))))
end
end
local function expectBlockWithEnd(label)
return (V("Block") * kw("end")) + (Cmt(V("Block"), searchEnd) + throw(label))
end
local function maybeBlockWithEnd()
return (V("BlockNoErr") * kw("end")) + Cmt(V("BlockNoErr"), searchEnd)
end
local G = {
V("Lua"),
["Lua"] = V("Shebang") ^ - 1 * V("Skip") * V("Block") * expect(P(- 1), "Extra"),
["Lua"] = (V("Shebang") ^ - 1 * V("Skip") * V("Block") * expect(P(- 1), "Extra")) / fixStructure,
["Shebang"] = P("#!") * (P(1) - P("\
")) ^ 0,
["Block"] = tagC("Block", V("Stat") ^ 0 * (V("RetStat") + V("ImplicitPushStat")) ^ - 1),
["Stat"] = V("IfStat") + V("DoStat") + V("WhileStat") + V("RepeatStat") + V("ForStat") + V("LocalStat") + V("FuncStat") + V("BreakStat") + V("LabelStat") + V("GoToStat") + V("FuncCall") + V("Assignment") + V("LetStat") + V("ContinueStat") + V("PushStat") + sym(";") + - V("BlockEnd") * throw("InvalidStat"),
["BlockEnd"] = P("return") + "end" + "elseif" + "else" + "until" + "]" + - 1 + V("ImplicitPushStat"),
["IfStat"] = tagC("If", V("IfPart") * V("ElseIfPart") ^ 0 * V("ElsePart") ^ - 1 * expect(kw("end"), "EndIf")),
["IfPart"] = kw("if") * expect(V("Expr"), "ExprIf") * expect(kw("then"), "ThenIf") * V("Block"),
["ElseIfPart"] = kw("elseif") * expect(V("Expr"), "ExprEIf") * expect(kw("then"), "ThenEIf") * V("Block"),
["ElsePart"] = kw("else") * V("Block"),
["DoStat"] = kw("do") * V("Block") * expect(kw("end"), "EndDo") / tagDo,
["Block"] = tagC("Block", (V("Stat") + - V("BlockEnd") * throw("InvalidStat")) ^ 0 * ((V("RetStat") + V("ImplicitPushStat")) * sym(";") ^ - 1) ^ - 1),
["Stat"] = V("IfStat") + V("DoStat") + V("WhileStat") + V("RepeatStat") + V("ForStat") + V("LocalStat") + V("FuncStat") + V("BreakStat") + V("LabelStat") + V("GoToStat") + V("FuncCall") + V("Assignment") + V("LetStat") + V("ContinueStat") + V("PushStat") + sym(";"),
["BlockEnd"] = P("return") + "end" + "elseif" + "else" + "until" + "]" + - 1 + V("ImplicitPushStat") + V("Assignment"),
["SingleStatBlock"] = tagC("Block", V("Stat") + V("RetStat") + V("ImplicitPushStat")) / function(t)
t["is_singlestatblock"] = true
return t
end,
["BlockNoErr"] = tagC("Block", V("Stat") ^ 0 * ((V("RetStat") + V("ImplicitPushStat")) * sym(";") ^ - 1) ^ - 1),
["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")),
["ElsePart"] = kw("else") * expectBlockWithEnd("EndIf"),
["DoStat"] = kw("do") * expectBlockWithEnd("EndDo") / tagDo,
["WhileStat"] = tagC("While", kw("while") * expect(V("Expr"), "ExprWhile") * V("WhileBody")),
["WhileBody"] = expect(kw("do"), "DoWhile") * V("Block") * expect(kw("end"), "EndWhile"),
["WhileBody"] = expectBlockOrSingleStatWithStartEnd(kw("do"), "DoWhile", "EndWhile"),
["RepeatStat"] = tagC("Repeat", kw("repeat") * V("Block") * expect(kw("until"), "UntilRep") * expect(V("Expr"), "ExprRep")),
["ForStat"] = kw("for") * expect(V("ForNum") + V("ForIn"), "ForRange") * expect(kw("end"), "EndFor"),
["ForStat"] = kw("for") * expect(V("ForNum") + V("ForIn"), "ForRange"),
["ForNum"] = tagC("Fornum", V("Id") * sym("=") * V("NumRange") * V("ForBody")),
["NumRange"] = expect(V("Expr"), "ExprFor1") * expect(sym(","), "CommaFor") * expect(V("Expr"), "ExprFor2") * (sym(",") * expect(V("Expr"), "ExprFor3")) ^ - 1,
["ForIn"] = tagC("Forin", V("NameList") * expect(kw("in"), "InFor") * expect(V("ExprList"), "EListFor") * V("ForBody")),
["ForBody"] = expect(kw("do"), "DoFor") * V("Block"),
["ForBody"] = expectBlockOrSingleStatWithStartEnd(kw("do"), "DoFor", "EndFor"),
["LocalStat"] = kw("local") * expect(V("LocalFunc") + V("LocalAssign"), "DefLocal"),
["LocalFunc"] = tagC("Localrec", kw("function") * expect(V("Id"), "NameLFunc") * V("FuncBody")) / fixFuncStat,
["LocalAssign"] = tagC("Local", V("NameList") * (sym("=") * expect(V("ExprList"), "EListLAssign") + Ct(Cc()))),
["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 * (sym("=") / "=") * V("BinOp") ^ - 1 * expect(V("ExprList"), "EListAssign")),
["Assignment"] = tagC("Set", V("VarList") * V("BinOp") ^ - 1 * (P("=") / "=") * 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) * (sym(":") * expect(V("StrId"), "NameFunc2")) ^ - 1 / markMethod,
["FuncBody"] = tagC("Function", V("FuncParams") * V("Block") * expect(kw("end"), "EndFunc")),
["FuncBody"] = tagC("Function", V("FuncParams") * expectBlockWithEnd("EndFunc")),
["FuncParams"] = expect(sym("("), "OParenPList") * V("ParList") * expect(sym(")"), "CParenPList"),
["ParList"] = V("NamedParList") * (sym(",") * expect(tagC("Dots", sym("...")), "ParList")) ^ - 1 / addDots + Ct(tagC("Dots", sym("..."))) + Ct(Cc()),
["ShortFuncDef"] = tagC("Function", V("ShortFuncParams") * maybeBlockWithEnd()) / fixShortFunc,
["ShortFuncParams"] = (sym(":") / ":") ^ - 1 * sym("(") * V("ParList") * sym(")"),
["NamedParList"] = tagC("NamedParList", commaSep(V("NamedPar"))),
["NamedPar"] = tagC("ParPair", V("ParKey") * expect(sym("="), "EqField") * expect(V("Expr"), "ExprField")) + V("Id"),
["ParKey"] = V("Id") * # ("=" * - P("=")),
@ -2663,9 +2787,9 @@ V("Lua"),
["GoToStat"] = tagC("Goto", kw("goto") * expect(V("Name"), "Goto")),
["BreakStat"] = tagC("Break", kw("break")),
["ContinueStat"] = tagC("Continue", kw("continue")),
["RetStat"] = tagC("Return", kw("return") * commaSep(V("Expr"), "RetList") ^ - 1 * sym(";") ^ - 1),
["PushStat"] = tagC("Push", kw("push") * commaSep(V("Expr"), "RetList") ^ - 1 * sym(";") ^ - 1),
["ImplicitPushStat"] = tagC("Push", commaSep(V("Expr"), "RetList") * sym(";") ^ - 1),
["RetStat"] = tagC("Return", kw("return") * commaSep(V("Expr"), "RetList") ^ - 1),
["PushStat"] = tagC("Push", kw("push") * commaSep(V("Expr"), "RetList") ^ - 1),
["ImplicitPushStat"] = tagC("Push", commaSep(V("Expr"), "RetList")),
["NameList"] = tagC("NameList", commaSep(V("Id"))),
["VarList"] = tagC("VarList", commaSep(V("VarExpr"))),
["ExprList"] = tagC("ExpList", commaSep(V("Expr"), "ExprList")),
@ -2682,7 +2806,7 @@ V("Lua"),
["MulExpr"] = chainOp(V("UnaryExpr"), V("MulOp"), "MulExpr"),
["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("String", V("String")) + tagC("Nil", kw("nil")) + tagC("Boolean", kw("false") * Cc(false)) + tagC("Boolean", kw("true") * Cc(true)) + tagC("Dots", sym("...")) + V("FuncDef") + V("Table") + V("SuffixedExpr") + V("TableCompr") + V("StatExpr"),
["SimpleExpr"] = tagC("Number", V("Number")) + tagC("String", V("String")) + tagC("Nil", kw("nil")) + tagC("Boolean", kw("false") * Cc(false)) + tagC("Boolean", kw("true") * Cc(true)) + tagC("Dots", sym("...")) + V("FuncDef") + V("Table") + V("ShortFuncDef") + V("SuffixedExpr") + V("TableCompr") + 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
@ -2696,9 +2820,7 @@ end),
["Call"] = tagC("Invoke", Cg(sym(":" * - P(":")) * expect(V("StrId"), "NameMeth") * expect(V("FuncArgs"), "MethArgs"))) + tagC("Call", V("FuncArgs")),
["SelfIndex"] = tagC("DotIndex", V("StrId")),
["SelfCall"] = tagC("Invoke", Cg(V("StrId") * V("FuncArgs"))),
["ShortFuncDef"] = tagC("Function", V("ShortFuncParams") * V("Block") * expect(kw("end"), "EndFunc")),
["ShortFuncParams"] = (sym(":") / ":") ^ - 1 * sym("(") * V("ParList") * sym(")") / fixAnonymousMethodParams,
["FuncDef"] = (kw("function") * V("FuncBody")) + V("ShortFuncDef"),
["FuncDef"] = (kw("function") * V("FuncBody")),
["FuncArgs"] = sym("(") * commaSep(V("Expr"), "ArgList") ^ - 1 * expect(sym(")"), "CParenArgs") + V("Table") + tagC("String", V("String")),
["Table"] = tagC("Table", sym("{") * V("FieldList") ^ - 1 * expect(sym("}"), "CBraceTable")),
["FieldList"] = sepBy(V("Field"), V("FieldSep")) * V("FieldSep") ^ - 1,
@ -2721,11 +2843,13 @@ end + P("--") * (P(1) - P("\
["Ident"] = V("IdStart") * V("IdRest") ^ 0,
["IdStart"] = alpha + P("_"),
["IdRest"] = alnum + P("_"),
["Number"] = token((V("Hex") + V("Float") + V("Int")) / tonumber),
["Hex"] = (P("0x") + "0X") * expect(xdigit ^ 1, "DigitHex"),
["Number"] = token(C(V("Hex") + V("Float") + V("Int"))),
["Hex"] = (P("0x") + "0X") * ((xdigit ^ 0 * V("DeciHex")) + (expect(xdigit ^ 1, "DigitHex") * V("DeciHex") ^ - 1)) * V("ExpoHex") ^ - 1,
["Float"] = V("Decimal") * V("Expo") ^ - 1 + V("Int") * V("Expo"),
["Decimal"] = digit ^ 1 * "." * digit ^ 0 + P(".") * - P(".") * expect(digit ^ 1, "DigitDeci"),
["DeciHex"] = P(".") * xdigit ^ 0,
["Expo"] = S("eE") * S("+-") ^ - 1 * expect(digit ^ 1, "DigitExpo"),
["ExpoHex"] = S("pP") * S("+-") ^ - 1 * expect(xdigit ^ 1, "DigitExpo"),
["Int"] = digit ^ 1,
["String"] = token(V("ShortStr") + V("LongStr")),
["ShortStr"] = P("\"") * Cs((V("EscSeq") + (P(1) - S("\"\
@ -2782,7 +2906,7 @@ return parser
end
local parser = _() or parser
package["loaded"]["lib.lua-parser.parser"] = parser or true
local candran = { ["VERSION"] = "0.5.0" }
local candran = { ["VERSION"] = "0.6.0" }
local default = {
["target"] = "lua53",
["indentation"] = "",
@ -2872,7 +2996,7 @@ end
candran["compile"] = function(input, options)
if options == nil then options = {} end
options = util["merge"](default, options)
local ast, errmsg = parser["parse"](input, "candran")
local ast, errmsg = parser["parse"](input, options["chunkname"])
if not ast then
error("Compiler: error while parsing file: " .. errmsg)
end

View file

@ -2,7 +2,7 @@ return function(code, ast, options)
--- Line mapping
local lastInputPos = 1 -- last token position in the input code
local prevLinePos = 1 -- last token position in the previous line of code in the input code
local lastSource = "nil" -- last found code source name (from the original file)
local lastSource = options.chunkname or "nil" -- last found code source name (from the original file)
local lastLine = 1 -- last found line number (from the original file)
--- Newline management
@ -282,17 +282,17 @@ return function(code, ast, options)
end
return r
end,
-- Localrec{ ident expr }
-- Localrec{ {ident} {expr} }
Localrec = (t)
return "local function "..lua(t[1][1])..lua(t[2][1], "_functionWithoutKeyword")
end,
-- Goto{ <string> }
Goto = (t)
return "goto " .. lua(t[1], "Id")
return "goto " .. lua(t, "Id")
end,
-- Label{ <string> }
Label = (t)
return "::" .. lua(t[1], "Id") .. "::"
return "::" .. lua(t, "Id") .. "::"
end,
-- Return{ <expr*> }
Return = (t)
@ -347,7 +347,7 @@ return function(code, ast, options)
Boolean = (t)
return tostring(t[1])
end,
-- Number{ <number> }
-- Number{ <string> }
Number = (t)
return tostring(t[1])
end,
@ -363,7 +363,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, id .. " = " .. id .. " == nil and " .. lua(t[1][1][2]) .. " or " .. id)
table.insert(decl, "if " .. id .. " == nil then " .. id .. " = " .. lua(t[1][1][2]) .. " end")
indentLevel -= 1
r ..= id
else
@ -483,7 +483,7 @@ return function(code, ast, options)
IfExpr = (t)
for i=2, #t do -- convert final pushes to returns
local block = t[i]
if block[#block].tag == "Push" then
if block[#block] and block[#block].tag == "Push" then
block[#block].tag = "Return"
end
end

View file

@ -8,13 +8,11 @@ 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 rejected ideas:
Example currently 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.
In this state, these didn't provide anothing useful over short anonymous functions.
* Making then and do optional:
This actually doesn't work because if condition then (doStuff)() end would become ambigous ("condition(doStuff)() then" or "condition then doStuff()"?).
* Whitespace significance.
Doesn't fit with Lua's verbose keywords.

View file

@ -16,7 +16,7 @@ stat:
| `Forin{ {ident+} {expr+} block } -- for i1, i2... in e1, e2... do b end
| `Local{ {ident+} {expr+}? } -- local i1, i2... = e1, e2...
| `Let{ {ident+} {expr+}? } -- let i1, i2... = e1, e2...
| `Localrec{ ident expr } -- only used for 'local function'
| `Localrec{ {ident} {expr} } -- only used for 'local function'
| `Goto{ <string> } -- goto str
| `Label{ <string> } -- ::str::
| `Return{ <expr*> } -- return e1, e2...
@ -29,7 +29,7 @@ expr:
`Nil
| `Dots
| `Boolean{ <boolean> }
| `Number{ <number> }
| `Number{ <string> } -- we don't use convert to number to avoid losing precision when tostring()-ing it later
| `String{ <string> }
| `Function{ { ( `ParPair{ Id expr } | `Id{ <string> } )* `Dots? } block }
| `Table{ ( `Pair{ expr expr } | expr )* }
@ -69,7 +69,7 @@ lpeg.locale(lpeg)
local P, S, V = lpeg.P, lpeg.S, lpeg.V
local C, Carg, Cb, Cc = lpeg.C, lpeg.Carg, lpeg.Cb, lpeg.Cc
local Cf, Cg, Cmt, Cp, Cs, Ct = lpeg.Cf, lpeg.Cg, lpeg.Cmt, lpeg.Cp, lpeg.Cs, lpeg.Ct
local Lc, T = lpeg.Lc, lpeg.T
local Rec, T = lpeg.Rec, lpeg.T
local alpha, digit, alnum = lpeg.alpha, lpeg.digit, lpeg.alnum
local xdigit = lpeg.xdigit
@ -273,48 +273,199 @@ local function makeIndexOrCall (t1, t2)
return { tag = "Index", pos = t1.pos, [1] = t1, [2] = t2[1] }
end
local function fixAnonymousMethodParams(t1, t2)
if t1 == ":" then
t1 = t2
table.insert(t1, 1, { tag = "Id", "self" })
local function fixShortFunc(t)
if t[1] == ":" then -- self method
table.insert(t[2], 1, { tag = "Id", "self" })
table.remove(t, 1)
t.is_method = true
end
return t1
t.is_short = true
return t
end
local function statToExpr(t)
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 i = 1
while i <= #t do
if type(t[i]) == "table" then
fixStructure(t[i])
for j=#t[i], 1, -1 do
local stat = t[i][j]
if type(stat) == "table" and stat.move_up_block and stat.move_up_block > 0 then
table.remove(t[i], j)
table.insert(t, i+1, stat)
if t.tag == "Block" or t.tag == "Do" then
stat.move_up_block = stat.move_up_block - 1
end
end
end
end
i = i + 1
end
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)
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
local exprlist
if stat.tag == "Set" or stat.tag == "Local" or stat.tag == "Let" or stat.tag == "Localrec" then
exprlist = stat[#stat]
elseif stat.tag == "Push" or stat.tag == "Return" then
exprlist = stat
end
local last = exprlist[#exprlist] -- last value in ExprList
-- Stuff parse shittily only for short function declaration which are not method and whith strictly one variable name between the parenthesis.
-- Otherwise it's invalid Lua anyway, so not my problem.
if last.tag == "Function" and last.is_short and not last.is_method and #last[1] == 1 then
local p = i
for j, fstat in ipairs(last[2]) do
p = i + j
table.insert(block, p, fstat) -- copy stats from func body to block
if stat.move_up_block then -- extracted stats inherit move_up_block from statement
fstat.move_up_block = (fstat.move_up_block or 0) + stat.move_up_block
end
if block.is_singlestatblock then -- if it's a single stat block, mark them to move them outside of the block
fstat.move_up_block = (fstat.move_up_block or 0) + 1
end
end
exprlist[#exprlist] = last[1] -- replace func with paren and expressions
exprlist[#exprlist].tag = "Paren"
if not isRecCall then -- if superfluous statements won't be moved in a next recursion, let fixStructure handle things
for j=p+1, #block, 1 do
block[j].move_up_block = (block[j].move_up_block or 0) + 1
end
end
return block, i
-- I lied, stuff can also be recursive here (StatExpr & Function)
elseif last.tag:match("Expr$") then
local r = searchEndRec({ last })
if r then
for j=2, #r, 1 do
table.insert(block, i+j-1, r[j]) -- move back superflous statements from our new table to our real block
end
return block, i
end
elseif last.tag == "Function" then
local r = searchEndRec(last[2])
if r then
return block, i
end
end
-- Recursive statements
elseif stat.tag:match("^If") or stat.tag:match("^While") or stat.tag:match("^Repeat") or stat.tag:match("^Do") or stat.tag:match("^Fornum") or stat.tag:match("^Forin") then
local blocks
if stat.tag:match("^If") or stat.tag:match("^While") or stat.tag:match("^Repeat") or stat.tag:match("^Fornum") or stat.tag:match("^Forin") then
blocks = stat
elseif stat.tag:match("^Do") then
blocks = { stat }
end
for _, iblock in ipairs(blocks) do
if iblock.tag == "Block" then -- blocks
local oldLen = #iblock
local newiBlock, newEnd = searchEndRec(iblock, true)
if newiBlock then -- if end in the block
local p = i
for j=newEnd+(#iblock-oldLen)+1, #iblock, 1 do -- move all statements after the newely added statements to the parent block
p = p + 1
table.insert(block, p, iblock[j])
iblock[j] = nil
end
if not isRecCall then -- if superfluous statements won't be moved in a next recursion, let fixStructure handle things
for j=p+1, #block, 1 do
block[j].move_up_block = (block[j].move_up_block or 0) + 1
end
end
return block, i
end
end
end
end
end
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 r = searchEndRec(fixStructure(t))
if not r then
return false
end
return true, r
end
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")))
+ (Cmt(V"Block", searchEnd) + throw(stopLabel))))
else
return (-start * V"SingleStatBlock")
+ (expect(start, startLabel) * ((V"Block" * kw("end"))
+ (Cmt(V"Block", searchEnd) + throw(stopLabel))))
end
end
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
return (V"BlockNoErr" * kw("end"))
+ Cmt(V"BlockNoErr", searchEnd)
end
-- grammar
local G = { V"Lua",
Lua = V"Shebang"^-1 * V"Skip" * V"Block" * expect(P(-1), "Extra");
Lua = (V"Shebang"^-1 * V"Skip" * V"Block" * expect(P(-1), "Extra")) / fixStructure;
Shebang = P"#!" * (P(1) - P"\n")^0;
Block = tagC("Block", V"Stat"^0 * (V"RetStat" + V"ImplicitPushStat")^-1);
Block = tagC("Block", (V"Stat" + -V"BlockEnd" * throw("InvalidStat"))^0 * ((V"RetStat" + V"ImplicitPushStat") * sym(";")^-1)^-1);
Stat = V"IfStat" + V"DoStat" + V"WhileStat" + V"RepeatStat" + V"ForStat"
+ V"LocalStat" + V"FuncStat" + V"BreakStat" + V"LabelStat" + V"GoToStat"
+ V"FuncCall" + V"Assignment"
+ V"LetStat" + V"ContinueStat" + V"PushStat"
+ sym(";") + -V"BlockEnd" * throw("InvalidStat");
BlockEnd = P"return" + "end" + "elseif" + "else" + "until" + "]" + -1 + V"ImplicitPushStat";
+ sym(";");
BlockEnd = P"return" + "end" + "elseif" + "else" + "until" + "]" + -1 + V"ImplicitPushStat" + V"Assignment";
IfStat = tagC("If", V"IfPart" * V"ElseIfPart"^0 * V"ElsePart"^-1 * expect(kw("end"), "EndIf"));
IfPart = kw("if") * expect(V"Expr", "ExprIf") * expect(kw("then"), "ThenIf") * V"Block";
ElseIfPart = kw("elseif") * expect(V"Expr", "ExprEIf") * expect(kw("then"), "ThenEIf") * V"Block";
ElsePart = kw("else") * V"Block";
SingleStatBlock = tagC("Block", V"Stat" + V"RetStat" + V"ImplicitPushStat") / function(t) t.is_singlestatblock = true return t end;
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
DoStat = kw("do") * V"Block" * expect(kw("end"), "EndDo") / tagDo;
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");
ElsePart = kw("else") * expectBlockWithEnd("EndIf");
DoStat = kw("do") * expectBlockWithEnd("EndDo") / tagDo;
WhileStat = tagC("While", kw("while") * expect(V"Expr", "ExprWhile") * V"WhileBody");
WhileBody = expect(kw("do"), "DoWhile") * V"Block" * expect(kw("end"), "EndWhile");
WhileBody = expectBlockOrSingleStatWithStartEnd(kw("do"), "DoWhile", "EndWhile");
RepeatStat = tagC("Repeat", kw("repeat") * V"Block" * expect(kw("until"), "UntilRep") * expect(V"Expr", "ExprRep"));
ForStat = kw("for") * expect(V"ForNum" + V"ForIn", "ForRange") * expect(kw("end"), "EndFor");
ForStat = kw("for") * expect(V"ForNum" + V"ForIn", "ForRange");
ForNum = tagC("Fornum", V"Id" * sym("=") * V"NumRange" * V"ForBody");
NumRange = expect(V"Expr", "ExprFor1") * expect(sym(","), "CommaFor") *expect(V"Expr", "ExprFor2")
* (sym(",") * expect(V"Expr", "ExprFor3"))^-1;
ForIn = tagC("Forin", V"NameList" * expect(kw("in"), "InFor") * expect(V"ExprList", "EListFor") * V"ForBody");
ForBody = expect(kw("do"), "DoFor") * V"Block";
ForBody = expectBlockOrSingleStatWithStartEnd(kw("do"), "DoFor", "EndFor");
LocalStat = kw("local") * expect(V"LocalFunc" + V"LocalAssign", "DefLocal");
LocalFunc = tagC("Localrec", kw("function") * expect(V"Id", "NameLFunc") * V"FuncBody") / fixFuncStat;
@ -323,17 +474,20 @@ 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 * (sym("=") / "=") * V"BinOp"^-1 * expect(V"ExprList", "EListAssign"));
Assignment = tagC("Set", V"VarList" * V"BinOp"^-1 * (P"=" / "=") * 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)
* (sym(":") * expect(V"StrId", "NameFunc2"))^-1 / markMethod;
FuncBody = tagC("Function", V"FuncParams" * V"Block" * expect(kw("end"), "EndFunc"));
FuncBody = tagC("Function", V"FuncParams" * expectBlockWithEnd("EndFunc"));
FuncParams = expect(sym("("), "OParenPList") * V"ParList" * expect(sym(")"), "CParenPList");
ParList = V"NamedParList" * (sym(",") * expect(tagC("Dots", sym("...")), "ParList"))^-1 / addDots
+ Ct(tagC("Dots", sym("...")))
+ Ct(Cc()); -- Cc({}) generates a bug since the {} would be shared across parses
ShortFuncDef = tagC("Function", V"ShortFuncParams" * maybeBlockWithEnd()) / fixShortFunc;
ShortFuncParams = (sym(":") / ":")^-1 * sym("(") * V"ParList" * sym(")");
NamedParList = tagC("NamedParList", commaSep(V"NamedPar"));
NamedPar = tagC("ParPair", V"ParKey" * expect(sym("="), "EqField") * expect(V"Expr", "ExprField"))
+ V"Id";
@ -343,10 +497,10 @@ local G = { V"Lua",
GoToStat = tagC("Goto", kw("goto") * expect(V"Name", "Goto"));
BreakStat = tagC("Break", kw("break"));
ContinueStat = tagC("Continue", kw("continue"));
RetStat = tagC("Return", kw("return") * commaSep(V"Expr", "RetList")^-1 * sym(";")^-1);
RetStat = tagC("Return", kw("return") * commaSep(V"Expr", "RetList")^-1);
PushStat = tagC("Push", kw("push") * commaSep(V"Expr", "RetList")^-1 * sym(";")^-1);
ImplicitPushStat = tagC("Push", commaSep(V"Expr", "RetList") * sym(";")^-1);
PushStat = tagC("Push", kw("push") * commaSep(V"Expr", "RetList")^-1);
ImplicitPushStat = tagC("Push", commaSep(V"Expr", "RetList"));
NameList = tagC("NameList", commaSep(V"Id"));
VarList = tagC("VarList", commaSep(V"VarExpr"));
@ -375,6 +529,7 @@ local G = { V"Lua",
+ tagC("Dots", sym("..."))
+ V"FuncDef"
+ V"Table"
+ V"ShortFuncDef"
+ V"SuffixedExpr"
+ V"TableCompr"
+ V"StatExpr";
@ -395,10 +550,7 @@ local G = { V"Lua",
SelfIndex = tagC("DotIndex", V"StrId");
SelfCall = tagC("Invoke", Cg(V"StrId" * V"FuncArgs"));
ShortFuncDef = tagC("Function", V"ShortFuncParams" * V"Block" * expect(kw("end"), "EndFunc"));
ShortFuncParams = (sym(":") / ":")^-1 * sym("(") * V"ParList" * sym(")") / fixAnonymousMethodParams;
FuncDef = (kw("function") * V"FuncBody") + V"ShortFuncDef";
FuncDef = (kw("function") * V"FuncBody");
FuncArgs = sym("(") * commaSep(V"Expr", "ArgList")^-1 * expect(sym(")"), "CParenArgs")
+ V"Table"
+ tagC("String", V"String");
@ -433,13 +585,15 @@ local G = { V"Lua",
IdStart = alpha + P"_";
IdRest = alnum + P"_";
Number = token((V"Hex" + V"Float" + V"Int") / tonumber);
Hex = (P"0x" + "0X") * expect(xdigit^1, "DigitHex");
Number = token(C(V"Hex" + V"Float" + V"Int"));
Hex = (P"0x" + "0X") * ((xdigit^0 * V"DeciHex") + (expect(xdigit^1, "DigitHex") * V"DeciHex"^-1)) * V"ExpoHex"^-1;
Float = V"Decimal" * V"Expo"^-1
+ V"Int" * V"Expo";
Decimal = digit^1 * "." * digit^0
+ P"." * -P"." * expect(digit^1, "DigitDeci");
DeciHex = P"." * xdigit^0;
Expo = S"eE" * S"+-"^-1 * expect(digit^1, "DigitExpo");
ExpoHex = S"pP" * S"+-"^-1 * expect(xdigit^1, "DigitExpo");
Int = digit^1;
String = token(V"ShortStr" + V"LongStr");

View file

@ -1,6 +1,6 @@
package = "candran"
version = "0.5.0-1"
version = "0.6.0-1"
description = {
summary = "A simple Lua dialect and preprocessor.",
@ -17,7 +17,7 @@ description = {
source = {
url = "git://github.com/Reuh/candran",
tag = "v0.5.0"
tag = "v0.6.0"
}
dependencies = {

View file

@ -5,8 +5,8 @@ version = "scm-1"
description = {
summary = "A simple Lua dialect and preprocessor.",
detailed = [[
Candran is a dialect of the Lua 5.3 programming language which compiles to Lua 5.3 and Lua 5.1/LuaJit. It adds a preprocessor and several useful syntax additions.
Unlike Moonscript, Candran tries to stay close to the Lua syntax.
Candran is a dialect of the Lua 5.3 programming language which compiles to Lua 5.3 and Lua 5.1/LuaJit. It adds several useful syntax additions which aims to make Lua faster and easier to write, and a simple preprocessor.
Unlike Moonscript, Candran tries to stay close to the Lua syntax, and existing Lua code can run on Candran unmodified.
]],
license = "MIT",
homepage = "https://github.com/Reuh/candran",