diff --git a/bin/canc b/bin/canc index 962ad0b..c0e4d3b 100644 --- a/bin/canc +++ b/bin/canc @@ -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 + args.chunkname = file - if args.chunkname == nil then - args.chunkname = file + if args.ast then + pp.dump(assert(parse(input, args.chunkname))) + return end local out = input diff --git a/candran.can b/candran.can index 24b59c9..4620156 100644 --- a/candran.can +++ b/candran.can @@ -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) diff --git a/candran.lua b/candran.lua index 289a23e..feedec8 100644 --- a/candran.lua +++ b/candran.lua @@ -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 diff --git a/compiler/lua53.can b/compiler/lua53.can index 776912a..7d30b6c 100644 --- a/compiler/lua53.can +++ b/compiler/lua53.can @@ -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{ } Goto = (t) - return "goto " .. lua(t[1], "Id") + return "goto " .. lua(t, "Id") end, -- Label{ } Label = (t) - return "::" .. lua(t[1], "Id") .. "::" + return "::" .. lua(t, "Id") .. "::" end, -- Return{ } Return = (t) @@ -347,7 +347,7 @@ return function(code, ast, options) Boolean = (t) return tostring(t[1]) end, - -- Number{ } + -- Number{ } 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 diff --git a/ideas.txt b/ideas.txt index 0eac365..eff19c6 100644 --- a/ideas.txt +++ b/ideas.txt @@ -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. diff --git a/lib/lua-parser/parser.lua b/lib/lua-parser/parser.lua index f0c3723..7ca6fa9 100644 --- a/lib/lua-parser/parser.lua +++ b/lib/lua-parser/parser.lua @@ -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{ } -- goto str | `Label{ } -- ::str:: | `Return{ } -- return e1, e2... @@ -29,7 +29,7 @@ expr: `Nil | `Dots | `Boolean{ } - | `Number{ } + | `Number{ } -- we don't use convert to number to avoid losing precision when tostring()-ing it later | `String{ } | `Function{ { ( `ParPair{ Id expr } | `Id{ } )* `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"); diff --git a/rockspec/candran-0.5.0-1.rockspec b/rockspec/candran-0.6.0-1.rockspec similarity index 96% rename from rockspec/candran-0.5.0-1.rockspec rename to rockspec/candran-0.6.0-1.rockspec index b3d2737..fdee24d 100644 --- a/rockspec/candran-0.5.0-1.rockspec +++ b/rockspec/candran-0.6.0-1.rockspec @@ -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 = { diff --git a/rockspec/candran-scm-1.rockspec b/rockspec/candran-scm-1.rockspec index 73447e7..de5252a 100644 --- a/rockspec/candran-scm-1.rockspec +++ b/rockspec/candran-scm-1.rockspec @@ -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",