diff --git a/LICENSE b/LICENSE index 67b94b7..cee3c53 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2017 Étienne "Reuh" Fildadut +Copyright (c) 2017 Étienne "Reuh" Fildadut Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 3172e98..1f7439d 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,9 @@ end) ```` #### Quick setup -Install LPegLabel (```luarocks install LPegLabel```), download this repository and use Candran through the scripts in ```bin/``` or use it as a library with the self-contained ```candran.lua```. +Install Candran automatically using LuaRocks: ```sudo luarocks install candran```. + +Or manually install LPegLabel (```luarocks install LPegLabel```), download this repository and use Candran through the scripts in ```bin/``` or use it as a library with the self-contained ```candran.lua```. The language ------------ diff --git a/candran.can b/candran.can index a824673..899dd31 100644 --- a/candran.can +++ b/candran.can @@ -10,7 +10,7 @@ #import("lib.lua-parser.parser") local candran = { - VERSION = "0.3.1" + VERSION = "0.4.0" } --- Default options. diff --git a/candran.lua b/candran.lua index 59277ab..3704f58 100644 --- a/candran.lua +++ b/candran.lua @@ -168,15 +168,35 @@ return newline() end local required = {} local requireStr = "" -local function addRequire(str, name, field) -if not required[str] then -requireStr = requireStr .. "local " .. options["requirePrefix"] .. name .. (" = require(%q)"):format(str) .. (field and "." .. field or "") .. options["newline"] -required[str] = true +local function addRequire(mod, name, field) +if not required[mod] then +requireStr = requireStr .. "local " .. options["requirePrefix"] .. name .. (" = require(%q)"):format(mod) .. (field and "." .. field or "") .. options["newline"] +required[mod] = true end end local function getRequire(name) return options["requirePrefix"] .. name end +local function any(list, tags) +local tagsCheck = {} +for _, tag in ipairs(tags) do +tagsCheck[tag] = true +end +for _, node in ipairs(list) do +if tagsCheck[node["tag"]] then +return node +end +end +return nil +end +local namedNodes = {} +local function wrap(name, prefix, suffix) +local node = namedNodes[name] +if not node then +error("not inside a " .. name) +end +node["prefix"], node["suffix"] = prefix .. newline(), newline() .. suffix +end local tags local function lua(ast, forceTag, ...) if options["mapLines"] and ast["pos"] then @@ -184,6 +204,13 @@ lastInputPos = ast["pos"] end return tags[forceTag or ast["tag"]](ast, ...) end +local function namedLua(name, ast, ...) +local old = namedNodes[name] +namedNodes[name] = ast +local code = lua(ast, ...) +namedNodes[name] = old +return (ast["prefix"] or "") .. code .. (ast["suffix"] or "") +end tags = setmetatable({ ["Block"] = function(t) local r = "" @@ -239,9 +266,9 @@ end return r end end, ["While"] = function(t) -return "while " .. lua(t[1]) .. " do" .. indent() .. lua(t[2]) .. unindent() .. "end" +return "while " .. lua(t[1]) .. " do" .. indent() .. namedLua("loop", t[2]) .. unindent() .. "end" end, ["Repeat"] = function(t) -return "repeat" .. indent() .. lua(t[1]) .. unindent() .. "until " .. lua(t[2]) +return "repeat" .. indent() .. namedLua("loop", t[1]) .. unindent() .. "until " .. lua(t[2]) end, ["If"] = function(t) local r = "if " .. lua(t[1]) .. " then" .. indent() .. lua(t[2]) .. unindent() for i = 3, # t - 1, 2 do @@ -256,16 +283,29 @@ local r = "for " .. lua(t[1]) .. " = " .. lua(t[2]) .. ", " .. lua(t[3]) if # t == 5 then return r .. ", " .. lua(t[4]) .. " do" .. indent() .. lua(t[5]) .. unindent() .. "end" else -return r .. " do" .. indent() .. lua(t[4]) .. unindent() .. "end" +return r .. " do" .. indent() .. namedLua("loop", t[4]) .. unindent() .. "end" end end, ["Forin"] = function(t) -return "for " .. lua(t[1], "_lhs") .. " in " .. lua(t[2], "_lhs") .. " do" .. indent() .. lua(t[3]) .. unindent() .. "end" +return "for " .. lua(t[1], "_lhs") .. " in " .. lua(t[2], "_lhs") .. " do" .. indent() .. namedLua("loop", t[3]) .. unindent() .. "end" end, ["Local"] = function(t) local r = "local " .. lua(t[1], "_lhs") if t[2][1] then r = r .. " = " .. lua(t[2], "_lhs") end return r +end, ["Let"] = function(t) +local nameList = lua(t[1], "_lhs") +local r = "local " .. nameList +if t[2][1] then +if any(t[2], { +"Function", "Table", "Paren" +}) then +r = r .. newline() .. nameList .. " = " .. lua(t[2], "_lhs") +else +r = r .. " = " .. lua(t[2], "_lhs") +end +end +return r end, ["Localrec"] = function(t) return "local function " .. lua(t[1][1]) .. lua(t[2][1], "_functionWithoutKeyword") end, ["Goto"] = function(t) @@ -276,6 +316,9 @@ end, ["Return"] = function(t) return "return " .. lua(t, "_lhs") end, ["Break"] = function() return "break" +end, ["Continue"] = function() +wrap("loop", "repeat", "until true") +return "break" end, ["Nil"] = function() return "nil" end, ["Dots"] = function() @@ -372,7 +415,8 @@ end, ["_opid"] = { }, { ["__index"] = function(self, key) error("don't know how to compile a " .. tostring(key) .. " to Lua 5.3") end }) -return requireStr .. lua(ast) .. newline() +local code = lua(ast) .. newline() +return requireStr .. code end end local lua53 = _() or lua53 @@ -416,15 +460,35 @@ return newline() end local required = {} local requireStr = "" -local function addRequire(str, name, field) -if not required[str] then -requireStr = requireStr .. "local " .. options["requirePrefix"] .. name .. (" = require(%q)"):format(str) .. (field and "." .. field or "") .. options["newline"] -required[str] = true +local function addRequire(mod, name, field) +if not required[mod] then +requireStr = requireStr .. "local " .. options["requirePrefix"] .. name .. (" = require(%q)"):format(mod) .. (field and "." .. field or "") .. options["newline"] +required[mod] = true end end local function getRequire(name) return options["requirePrefix"] .. name end +local function any(list, tags) +local tagsCheck = {} +for _, tag in ipairs(tags) do +tagsCheck[tag] = true +end +for _, node in ipairs(list) do +if tagsCheck[node["tag"]] then +return node +end +end +return nil +end +local namedNodes = {} +local function wrap(name, prefix, suffix) +local node = namedNodes[name] +if not node then +error("not inside a " .. name) +end +node["prefix"], node["suffix"] = prefix .. newline(), newline() .. suffix +end local tags local function lua(ast, forceTag, ...) if options["mapLines"] and ast["pos"] then @@ -432,6 +496,13 @@ lastInputPos = ast["pos"] end return tags[forceTag or ast["tag"]](ast, ...) end +local function namedLua(name, ast, ...) +local old = namedNodes[name] +namedNodes[name] = ast +local code = lua(ast, ...) +namedNodes[name] = old +return (ast["prefix"] or "") .. code .. (ast["suffix"] or "") +end tags = setmetatable({ ["Block"] = function(t) local r = "" @@ -487,9 +558,9 @@ end return r end end, ["While"] = function(t) -return "while " .. lua(t[1]) .. " do" .. indent() .. lua(t[2]) .. unindent() .. "end" +return "while " .. lua(t[1]) .. " do" .. indent() .. namedLua("loop", t[2]) .. unindent() .. "end" end, ["Repeat"] = function(t) -return "repeat" .. indent() .. lua(t[1]) .. unindent() .. "until " .. lua(t[2]) +return "repeat" .. indent() .. namedLua("loop", t[1]) .. unindent() .. "until " .. lua(t[2]) end, ["If"] = function(t) local r = "if " .. lua(t[1]) .. " then" .. indent() .. lua(t[2]) .. unindent() for i = 3, # t - 1, 2 do @@ -504,16 +575,29 @@ local r = "for " .. lua(t[1]) .. " = " .. lua(t[2]) .. ", " .. lua(t[3]) if # t == 5 then return r .. ", " .. lua(t[4]) .. " do" .. indent() .. lua(t[5]) .. unindent() .. "end" else -return r .. " do" .. indent() .. lua(t[4]) .. unindent() .. "end" +return r .. " do" .. indent() .. namedLua("loop", t[4]) .. unindent() .. "end" end end, ["Forin"] = function(t) -return "for " .. lua(t[1], "_lhs") .. " in " .. lua(t[2], "_lhs") .. " do" .. indent() .. lua(t[3]) .. unindent() .. "end" +return "for " .. lua(t[1], "_lhs") .. " in " .. lua(t[2], "_lhs") .. " do" .. indent() .. namedLua("loop", t[3]) .. unindent() .. "end" end, ["Local"] = function(t) local r = "local " .. lua(t[1], "_lhs") if t[2][1] then r = r .. " = " .. lua(t[2], "_lhs") end return r +end, ["Let"] = function(t) +local nameList = lua(t[1], "_lhs") +local r = "local " .. nameList +if t[2][1] then +if any(t[2], { +"Function", "Table", "Paren" +}) then +r = r .. newline() .. nameList .. " = " .. lua(t[2], "_lhs") +else +r = r .. " = " .. lua(t[2], "_lhs") +end +end +return r end, ["Localrec"] = function(t) return "local function " .. lua(t[1][1]) .. lua(t[2][1], "_functionWithoutKeyword") end, ["Goto"] = function(t) @@ -524,6 +608,9 @@ end, ["Return"] = function(t) return "return " .. lua(t, "_lhs") end, ["Break"] = function() return "break" +end, ["Continue"] = function() +wrap("loop", "repeat", "until true") +return "break" end, ["Nil"] = function() return "nil" end, ["Dots"] = function() @@ -647,7 +734,8 @@ tags["_opid"]["bnot"] = function(right) addRequire("bit", "bnot", "bnot") return getRequire("bnot") .. "(" .. lua(right) .. ")" end -return requireStr .. lua(ast) .. newline() +local code = lua(ast) .. newline() +return requireStr .. code end end local lua53 = _() or lua53 @@ -898,6 +986,13 @@ return nil, syntaxerror(env["errorinfo"], stm["pos"], msg) end return true end +local function traverse_continue(env, stm) +if not insideloop(env) then +local msg = " not inside a loop" +return nil, syntaxerror(env["errorinfo"], stm["pos"], msg) +end +return true +end local function traverse_forin(env, stm) begin_loop(env) new_scope(env) @@ -1112,7 +1207,7 @@ elseif tag == "Fornum" then return traverse_fornum(env, stm) elseif tag == "Forin" then return traverse_forin(env, stm) -elseif tag == "Local" then +elseif tag == "Local" or tag == "Let" then return traverse_let(env, stm) elseif tag == "Localrec" then return traverse_letrec(env, stm) @@ -1124,6 +1219,8 @@ elseif tag == "Return" then return traverse_return(env, stm) elseif tag == "Break" then return traverse_break(env, stm) +elseif tag == "Continue" then +return traverse_continue(env, stm) elseif tag == "Call" then return traverse_call(env, stm) elseif tag == "Invoke" then @@ -1552,6 +1649,8 @@ local labels = { }, { "ErrDefLocal", "expected a function definition or assignment after local" }, { +"ErrDefLet", "expected a function definition or assignment after let" +}, { "ErrNameLFunc", "expected a function name after 'function'" }, { "ErrEListLAssign", "expected one or more expressions after '='" @@ -1766,7 +1865,7 @@ return t1 end local G = { V("Lua"), ["Lua"] = V("Shebang") ^ - 1 * V("Skip") * V("Block") * expect(P(- 1), "Extra"), ["Shebang"] = P("#!") * (P(1) - P("\ -")) ^ 0, ["Block"] = tagC("Block", V("Stat") ^ 0 * V("RetStat") ^ - 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") + sym(";") + - V("BlockEnd") * throw("InvalidStat"), ["BlockEnd"] = P("return") + "end" + "elseif" + "else" + "until" + - 1, ["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, ["WhileStat"] = tagC("While", kw("while") * expect(V("Expr"), "ExprWhile") * V("WhileBody")), ["WhileBody"] = expect(kw("do"), "DoWhile") * V("Block") * expect(kw("end"), "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"), ["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"), ["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()))), ["Assignment"] = tagC("Set", V("VarList") * V("BinOp") ^ - 1 * (sym("=") / "=") * V("BinOp") ^ - 1 * 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")), ["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()), ["NamedParList"] = tagC("NamedParList", commaSep(V("NamedPar"))), ["NamedPar"] = tagC("ParPair", V("ParKey") * expect(sym("="), "EqField") * expect(V("Expr"), "ExprField")) + V("Id"), ["ParKey"] = V("Id") * # ("=" * - P("=")), ["LabelStat"] = tagC("Label", sym("::") * expect(V("Name"), "Label") * expect(sym("::"), "CloseLabel")), ["GoToStat"] = tagC("Goto", kw("goto") * expect(V("Name"), "Goto")), ["BreakStat"] = tagC("Break", kw("break")), ["RetStat"] = tagC("Return", kw("return") * commaSep(V("Expr"), "RetList") ^ - 1 * sym(";") ^ - 1), ["NameList"] = tagC("NameList", commaSep(V("Id"))), ["VarList"] = tagC("VarList", commaSep(V("VarExpr"), "VarList")), ["ExprList"] = tagC("ExpList", commaSep(V("Expr"), "ExprList")), ["Expr"] = V("OrExpr"), ["OrExpr"] = chainOp(V("AndExpr"), V("OrOp"), "OrExpr"), ["AndExpr"] = chainOp(V("RelExpr"), V("AndOp"), "AndExpr"), ["RelExpr"] = chainOp(V("BOrExpr"), V("RelOp"), "RelExpr"), ["BOrExpr"] = chainOp(V("BXorExpr"), V("BOrOp"), "BOrExpr"), ["BXorExpr"] = chainOp(V("BAndExpr"), V("BXorOp"), "BXorExpr"), ["BAndExpr"] = chainOp(V("ShiftExpr"), V("BAndOp"), "BAndExpr"), ["ShiftExpr"] = chainOp(V("ConcatExpr"), V("ShiftOp"), "ShiftExpr"), ["ConcatExpr"] = V("AddExpr") * (V("ConcatOp") * expect(V("ConcatExpr"), "ConcatExpr")) ^ - 1 / binaryOp, ["AddExpr"] = chainOp(V("MulExpr"), V("AddOp"), "AddExpr"), ["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"), ["FuncCall"] = Cmt(V("SuffixedExpr"), function(s, i, exp) +")) ^ 0, ["Block"] = tagC("Block", V("Stat") ^ 0 * V("RetStat") ^ - 1), ["Stat"] = V("IfStat") + V("DoStat") + V("WhileStat") + V("RepeatStat") + V("ForStat") + V("LocalStat") + V("LetStat") + V("FuncStat") + V("BreakStat") + V("ContinueStat") + V("LabelStat") + V("GoToStat") + V("FuncCall") + V("Assignment") + sym(";") + - V("BlockEnd") * throw("InvalidStat"), ["BlockEnd"] = P("return") + "end" + "elseif" + "else" + "until" + - 1, ["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, ["WhileStat"] = tagC("While", kw("while") * expect(V("Expr"), "ExprWhile") * V("WhileBody")), ["WhileBody"] = expect(kw("do"), "DoWhile") * V("Block") * expect(kw("end"), "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"), ["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"), ["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")), ["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")), ["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()), ["NamedParList"] = tagC("NamedParList", commaSep(V("NamedPar"))), ["NamedPar"] = tagC("ParPair", V("ParKey") * expect(sym("="), "EqField") * expect(V("Expr"), "ExprField")) + V("Id"), ["ParKey"] = V("Id") * # ("=" * - P("=")), ["LabelStat"] = tagC("Label", sym("::") * expect(V("Name"), "Label") * expect(sym("::"), "CloseLabel")), ["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), ["NameList"] = tagC("NameList", commaSep(V("Id"))), ["VarList"] = tagC("VarList", commaSep(V("VarExpr"), "VarList")), ["ExprList"] = tagC("ExpList", commaSep(V("Expr"), "ExprList")), ["Expr"] = V("OrExpr"), ["OrExpr"] = chainOp(V("AndExpr"), V("OrOp"), "OrExpr"), ["AndExpr"] = chainOp(V("RelExpr"), V("AndOp"), "AndExpr"), ["RelExpr"] = chainOp(V("BOrExpr"), V("RelOp"), "RelExpr"), ["BOrExpr"] = chainOp(V("BXorExpr"), V("BOrOp"), "BOrExpr"), ["BXorExpr"] = chainOp(V("BAndExpr"), V("BXorOp"), "BXorExpr"), ["BAndExpr"] = chainOp(V("ShiftExpr"), V("BAndOp"), "BAndExpr"), ["ShiftExpr"] = chainOp(V("ConcatExpr"), V("ShiftOp"), "ShiftExpr"), ["ConcatExpr"] = V("AddExpr") * (V("ConcatOp") * expect(V("ConcatExpr"), "ConcatExpr")) ^ - 1 / binaryOp, ["AddExpr"] = chainOp(V("MulExpr"), V("AddOp"), "AddExpr"), ["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"), ["FuncCall"] = Cmt(V("SuffixedExpr"), function(s, i, exp) return exp["tag"] == "Call" or exp["tag"] == "Invoke", exp end), ["VarExpr"] = Cmt(V("SuffixedExpr"), function(s, i, exp) return exp["tag"] == "Id" or exp["tag"] == "Index", exp @@ -1807,7 +1906,7 @@ return parser end local parser = _() or parser package["loaded"]["lib.lua-parser.parser"] = parser or true -local candran = { ["VERSION"] = "0.3.1" } +local candran = { ["VERSION"] = "0.4.0" } local default = { ["target"] = "lua53", ["indentation"] = "", ["newline"] = "\ ", ["requirePrefix"] = "CANDRAN_", ["mapLines"] = true, ["chunkname"] = "nil", ["rewriteErrors"] = true diff --git a/compiler/lua53.can b/compiler/lua53.can index 11621e3..ab5ea36 100644 --- a/compiler/lua53.can +++ b/compiler/lua53.can @@ -1,10 +1,13 @@ return function(code, ast, options) - local lastInputPos = 1 - local prevLinePos = 1 - local lastSource = "nil" - local lastLine = 1 + --- 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 lastLine = 1 -- last found line number (from the original file) + --- Newline management local indentLevel = 0 + -- Returns a newline. local function newline() local r = options.newline .. string.rep(options.indentation, indentLevel) if options.mapLines then @@ -26,44 +29,83 @@ return function(code, ast, options) end return r end + -- Returns a newline and add one level of indentation. local function indent() indentLevel += 1 return newline() end + -- Returns a newline and remove one level of indentation. local function unindent() indentLevel -= 1 return newline() end - local required = {} + --- Module management + local required = {} -- { ["module"] = true, ... } local requireStr = "" - local function addRequire(str, name, field) - if not required[str] then - requireStr ..= "local " .. options.requirePrefix .. name .. (" = require(%q)"):format(str) .. (field and "."..field or "") .. options.newline - required[str] = true + -- 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.requirePrefix .. name .. (" = require(%q)"):format(mod) .. (field and "."..field or "") .. options.newline + required[mod] = true end end + -- Returns the required module variable name. local function getRequire(name) return options.requirePrefix .. name end + --- AST traversal helpers + -- Returns the first node from the list "list" which tag is in the list "tags", or nil if there were none. + local function any(list, tags) + local tagsCheck = {} + for _, tag in ipairs(tags) do + tagsCheck[tag] = true + end + for _, node in ipairs(list) do + if tagsCheck[node.tag] then + return node + end + end + return nil + end + + --- Modification helpers + local namedNodes = {} -- { ["name"] = ast, ... } + -- Wrap the compiled code of a named node with suffix and prefix. + local function wrap(name, prefix, suffix) + local node = namedNodes[name] + if not node then error("not inside a " .. name) end + node.prefix, node.suffix = prefix .. newline(), newline() .. suffix + end + + --- Lua compiler local tags + -- Recursively returns the compiled AST Lua code, set "forceTag" to override the tag type and pass additional arguments to the tag constructor if needed. local function lua(ast, forceTag, ...) if options.mapLines and ast.pos then lastInputPos = ast.pos end return tags[forceTag or ast.tag](ast, ...) end - + -- Same as lua(), but gives the AST node a name which can be used for further access & modification. + local function namedLua(name, ast, ...) + local old = namedNodes[name] + namedNodes[name] = ast + local code = lua(ast, ...) + namedNodes[name] = old + return (ast.prefix or "") .. code .. (ast.suffix or "") + end + -- Tag constructors tags = setmetatable({ -- block: { stat* } -- - Block = function(t) + Block = (t) local r = "" for i=1, #t-1, 1 do - r = r .. lua(t[i]) .. newline() + r ..= lua(t[i]) .. newline() end if t[#t] then - r = r .. lua(t[#t]) + r ..= lua(t[#t]) end return r end, @@ -71,11 +113,11 @@ return function(code, ast, options) -- stat -- -- Do{ stat* } - Do = function(t) + Do = (t) return "do" .. indent() .. lua(t, "Block") .. unindent() .. "end" end, -- Set{ {lhs+} (opid? = opid?)? {expr+} } - Set = function(t) + Set = (t) if #t == 2 then return lua(t[1], "_lhs") .. " = " .. lua(t[2], "_lhs") elseif #t == 3 then @@ -84,82 +126,100 @@ return function(code, ast, options) if t[3] == "=" then local r = lua(t[1], "_lhs") .. " = " .. lua({ t[2], t[1][1], t[4][1] }, "Op") for i=2, math.min(#t[4], #t[1]), 1 do - r = r .. ", " .. lua({ t[2], t[1][i], t[4][i] }, "Op") + r ..= ", " .. lua({ t[2], t[1][i], t[4][i] }, "Op") end return r else local r = lua(t[1], "_lhs") .. " = " .. lua({ t[3], t[4][1], t[1][1] }, "Op") for i=2, math.min(#t[4], #t[1]), 1 do - r = r .. ", " .. lua({ t[3], t[4][i], t[1][i] }, "Op") + r ..= ", " .. lua({ t[3], 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], t[5][1], t[1][1] } }, "Op") for i=2, math.min(#t[5], #t[1]), 1 do - r = r .. ", " .. lua({ t[2], t[1][i], { tag = "Op", t[4], t[5][i], t[1][i] } }, "Op") + r ..= ", " .. lua({ t[2], t[1][i], { tag = "Op", t[4], t[5][i], t[1][i] } }, "Op") end return r end end, -- While{ expr block } - While = function(t) - return "while " .. lua(t[1]) .. " do" .. indent() .. lua(t[2]) .. unindent() .. "end" + While = (t) + return "while " .. lua(t[1]) .. " do" .. indent() .. namedLua("loop", t[2]) .. unindent() .. "end" end, -- Repeat{ block expr } - Repeat = function(t) - return "repeat".. indent() .. lua(t[1]) .. unindent() .. "until " .. lua(t[2]) + Repeat = (t) + return "repeat".. indent() .. namedLua("loop", t[1]) .. unindent() .. "until " .. lua(t[2]) end, -- If{ (expr block)+ block? } - If = function(t) + If = (t) local r = "if " .. lua(t[1]) .. " then" .. indent() .. lua(t[2]) .. unindent() for i=3, #t-1, 2 do - r = r .. "elseif " .. lua(t[i]) .. " then" .. indent() .. lua(t[i+1]) .. unindent() + r ..= "elseif " .. lua(t[i]) .. " then" .. indent() .. lua(t[i+1]) .. unindent() end if #t % 2 == 1 then - r = r .. "else" .. indent() .. lua(t[#t]) .. unindent() + r ..= "else" .. indent() .. lua(t[#t]) .. unindent() end return r .. "end" end, -- Fornum{ ident expr expr expr? block } - Fornum = function(t) + Fornum = (t) local r = "for " .. lua(t[1]) .. " = " .. lua(t[2]) .. ", " .. lua(t[3]) if #t == 5 then return r .. ", " .. lua(t[4]) .. " do" .. indent() .. lua(t[5]) .. unindent() .. "end" else - return r .. " do" .. indent() .. lua(t[4]) .. unindent() .. "end" + return r .. " do" .. indent() .. namedLua("loop", t[4]) .. unindent() .. "end" end end, -- Forin{ {ident+} {expr+} block } - Forin = function(t) - return "for " .. lua(t[1], "_lhs") .. " in " .. lua(t[2], "_lhs") .. " do" .. indent() .. lua(t[3]) .. unindent() .. "end" + Forin = (t) + return "for " .. lua(t[1], "_lhs") .. " in " .. lua(t[2], "_lhs") .. " do" .. indent() .. namedLua("loop", t[3]) .. unindent() .. "end" end, -- Local{ {ident+} {expr+}? } - Local = function(t) + Local = (t) local r = "local "..lua(t[1], "_lhs") if t[2][1] then - r = r .. " = "..lua(t[2], "_lhs") + r ..= " = "..lua(t[2], "_lhs") + end + return r + end, + -- Let{ {ident+} {expr+}? } + Let = (t) + local nameList = lua(t[1], "_lhs") + 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") + else + r ..= " = " .. lua(t[2], "_lhs") + end end return r end, -- Localrec{ ident expr } - Localrec = function(t) + Localrec = (t) return "local function "..lua(t[1][1])..lua(t[2][1], "_functionWithoutKeyword") end, -- Goto{ } - Goto = function(t) + Goto = (t) return "goto " .. lua(t[1], "Id") end, -- Label{ } - Label = function(t) + Label = (t) return "::" .. lua(t[1], "Id") .. "::" end, -- Return{ } - Return = function(t) + Return = (t) return "return "..lua(t, "_lhs") end, -- Break - Break = function() + Break = () + return "break" + end, + -- Continue + Continue = () + wrap("loop", "repeat", "until true") return "break" end, -- apply (below) @@ -167,27 +227,27 @@ return function(code, ast, options) -- expr -- -- Nil - Nil = function() + Nil = () return "nil" end, -- Dots - Dots = function() + Dots = () return "..." end, -- Boolean{ } - Boolean = function(t) + Boolean = (t) return tostring(t[1]) end, -- Number{ } - Number = function(t) + Number = (t) return tostring(t[1]) end, -- String{ } - String = function(t) + String = (t) return ("%q"):format(t[1]) end, -- Function{ { ( `ParPair{ Id expr } | `Id{ } )* `Dots? } block } - _functionWithoutKeyword = function(t) + _functionWithoutKeyword = (t) local r = "(" local decl = {} if t[1][1] then @@ -196,9 +256,9 @@ return function(code, ast, options) indentLevel += 1 table.insert(decl, id .. " = " .. id .. " == nil and " .. lua(t[1][1][2]) .. " or " .. id) indentLevel -= 1 - r = r .. id + r ..= id else - r = r .. lua(t[1][1]) + r ..= lua(t[1][1]) end for i=2, #t[1], 1 do if t[1][i].tag == "ParPair" then @@ -206,26 +266,26 @@ return function(code, ast, options) indentLevel += 1 table.insert(decl, "if " .. id .. " == nil then " .. id .. " = " .. lua(t[1][i][2]) .. " end") indentLevel -= 1 - r = r .. ", " ..id + r ..= ", " ..id else - r = r .. ", " .. lua(t[1][i]) + r ..= ", " .. lua(t[1][i]) end end end - r = r .. ")" .. indent() + r ..= ")" .. indent() for _, d in ipairs(decl) do - r = r .. d .. newline() + r ..= d .. newline() end return r .. lua(t[2]) .. unindent() .. "end" end, - Function = function(t) + Function = (t) return "function" .. lua(t, "_functionWithoutKeyword") end, -- Table{ ( `Pair{ expr expr } | expr )* } - Pair = function(t) + Pair = (t) return "[" .. lua(t[1]) .. "] = " .. lua(t[2]) end, - Table = function(t) + Table = (t) if #t == 0 then return "{}" elseif #t == 1 then @@ -235,7 +295,7 @@ return function(code, ast, options) end end, -- Op{ opid expr expr? } - Op = function(t) + Op = (t) local r if #t == 2 then if type(tags._opid[t[1]]) == "string" then @@ -253,7 +313,7 @@ return function(code, ast, options) return r end, -- Paren{ expr } - Paren = function(t) + Paren = (t) return "(" .. lua(t[1]) .. ")" end, -- apply (below) @@ -262,23 +322,23 @@ return function(code, ast, options) -- apply -- -- Call{ expr expr* } - Call = function(t) + Call = (t) return lua(t[1]) .. "(" .. lua(t, "_lhs", 2) .. ")" end, -- Invoke{ expr `String{ } expr* } - Invoke = function(t) + Invoke = (t) return lua(t[1])..":"..lua(t[2], "Id").."("..lua(t, "_lhs", 3)..")" end, -- lhs -- - _lhs = function(t, start) + _lhs = (t, start) start = start or 1 local r if t[start] then r = lua(t[start]) for i=start+1, #t, 1 do - r = r .. ", "..lua(t[i]) + r ..= ", "..lua(t[i]) end else r = "" @@ -286,11 +346,11 @@ return function(code, ast, options) return r end, -- Id{ } - Id = function(t) + Id = (t) return t[1] end, -- Index{ expr expr } - Index = function(t) + Index = (t) return lua(t[1]).."["..lua(t[2]).."]" end, @@ -303,12 +363,13 @@ return function(code, ast, options) ["and"] = "and", ["or"] = "or", unm = "-", len = "#", bnot = "~", ["not"] = "not" } }, { - __index = function(self, key) + __index = (self, key) error("don't know how to compile a "..tostring(key).." to Lua 5.3") end }) #placeholder("patch") - return requireStr .. lua(ast) .. newline() + local code = lua(ast) .. newline() + return requireStr .. code end diff --git a/ideas.txt b/ideas.txt index 84e2205..93f7bcb 100644 --- a/ideas.txt +++ b/ideas.txt @@ -40,23 +40,6 @@ local a = new Thing() -> (TODO: define how classes work. May even use ClassCommons) -* continue keyword for loops -while true do - stuff() - if thing then - continue - end -end --> -while true do - repeat - stuff() - if thing then - break - end - until true -end - * list comprehension local a = [x for x in pairs(stuff)] local a = [x for x in pairs(stuff) if x == true] @@ -91,6 +74,8 @@ local a = if x == true then return a end +With implicits returns?... + local stuff = for ... (accumulate in a table) * try / except|catch / finally / else / other keywords @@ -139,11 +124,6 @@ local x, y $= pos And in implicit assignments: for i, {x, y} in ipairs(positions) do -* local short alias -let a -> local a -or -var a - * Other potential inspiration https://love2d.org/forums/viewtopic.php?f=3&t=82650&sid=b6d9a8dec64afcc1c67806cb5ba65458 https://www.ruby-lang.org/fr/ diff --git a/lib/lua-parser/parser.lua b/lib/lua-parser/parser.lua index 55d36a8..f584b43 100644 --- a/lib/lua-parser/parser.lua +++ b/lib/lua-parser/parser.lua @@ -15,11 +15,13 @@ stat: | `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... + | `Let{ {ident+} {expr+}? } -- let i1, i2... = e1, e2... | `Localrec{ ident expr } -- only used for 'local function' | `Goto{ } -- goto str | `Label{ } -- ::str:: | `Return{ } -- return e1, e2... | `Break -- break + | `Continue -- continue | apply expr: @@ -93,6 +95,7 @@ local labels = { { "ErrDoFor", "expected 'do' after the range of the for loop" }, { "ErrDefLocal", "expected a function definition or assignment after local" }, + { "ErrDefLet", "expected a function definition or assignment after let" }, { "ErrNameLFunc", "expected a function name after 'function'" }, { "ErrEListLAssign", "expected one or more expressions after '='" }, { "ErrEListAssign", "expected one or more expressions after '='" }, @@ -272,7 +275,7 @@ local G = { V"Lua", Block = tagC("Block", V"Stat"^0 * V"RetStat"^-1); Stat = V"IfStat" + V"DoStat" + V"WhileStat" + V"RepeatStat" + V"ForStat" - + V"LocalStat" + V"FuncStat" + V"BreakStat" + V"LabelStat" + V"GoToStat" + + V"LocalStat" + V"LetStat" + V"FuncStat" + V"BreakStat" + V"ContinueStat" + V"LabelStat" + V"GoToStat" + V"FuncCall" + V"Assignment" + sym(";") + -V"BlockEnd" * throw("InvalidStat"); BlockEnd = P"return" + "end" + "elseif" + "else" + "until" + -1; @@ -296,6 +299,10 @@ local G = { V"Lua", 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")); FuncStat = tagC("Set", kw("function") * expect(V"FuncName", "FuncName") * V"FuncBody") / fixFuncStat; @@ -312,10 +319,11 @@ local G = { V"Lua", + V"Id"; ParKey = V"Id" * #("=" * -P"="); - LabelStat = tagC("Label", sym("::") * expect(V"Name", "Label") * expect(sym("::"), "CloseLabel")); - GoToStat = tagC("Goto", kw("goto") * expect(V"Name", "Goto")); - BreakStat = tagC("Break", kw("break")); - RetStat = tagC("Return", kw("return") * commaSep(V"Expr", "RetList")^-1 * sym(";")^-1); + LabelStat = tagC("Label", sym("::") * expect(V"Name", "Label") * expect(sym("::"), "CloseLabel")); + 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); NameList = tagC("NameList", commaSep(V"Id")); VarList = tagC("VarList", commaSep(V"VarExpr", "VarList")); diff --git a/lib/lua-parser/validator.lua b/lib/lua-parser/validator.lua index 9ae9160..5f178ce 100644 --- a/lib/lua-parser/validator.lua +++ b/lib/lua-parser/validator.lua @@ -162,6 +162,14 @@ local function traverse_break (env, stm) return true end +local function traverse_continue (env, stm) + if not insideloop(env) then + local msg = " not inside a loop" + return nil, syntaxerror(env.errorinfo, stm.pos, msg) + end + return true +end + local function traverse_forin (env, stm) begin_loop(env) new_scope(env) @@ -344,7 +352,8 @@ function traverse_stm (env, stm) return traverse_fornum(env, stm) elseif tag == "Forin" then -- `Forin{ {ident+} {expr+} block } return traverse_forin(env, stm) - elseif tag == "Local" then -- `Local{ {ident+} {expr+}? } + elseif tag == "Local" or -- `Local{ {ident+} {expr+}? } + tag == "Let" then -- `Let{ {ident+} {expr+}? } return traverse_let(env, stm) elseif tag == "Localrec" then -- `Localrec{ ident expr } return traverse_letrec(env, stm) @@ -356,6 +365,8 @@ function traverse_stm (env, stm) return traverse_return(env, stm) elseif tag == "Break" then return traverse_break(env, stm) +elseif tag == "Continue" then + return traverse_continue(env, stm) elseif tag == "Call" then -- `Call{ expr expr* } return traverse_call(env, stm) elseif tag == "Invoke" then -- `Invoke{ expr `String{ } expr* } diff --git a/rockspec/candran-0.3.1-1.rockspec b/rockspec/candran-0.4.0-1.rockspec similarity index 76% rename from rockspec/candran-0.3.1-1.rockspec rename to rockspec/candran-0.4.0-1.rockspec index 296bf54..ca784f7 100644 --- a/rockspec/candran-0.3.1-1.rockspec +++ b/rockspec/candran-0.4.0-1.rockspec @@ -1,6 +1,6 @@ -package = "Candran" +package = "candran" -version = "0.3.1-1" +version = "0.4.0-1" description = { summary = "A simple Lua dialect and preprocessor.", @@ -9,15 +9,15 @@ description = { Unlike Moonscript, Candran tries to stay close to the Lua syntax. ]], license = "MIT", - homepage = "https://github.com/Reuh/Candran", - --issues_url = "https://github.com/Reuh/Candran", -- LuaRocks 3.0 + homepage = "https://github.com/Reuh/candran", + --issues_url = "https://github.com/Reuh/candran", -- LuaRocks 3.0 maintainer = "Étienne 'Reuh' Fildadut ", --labels = {} -- LuaRocks 3.0 } source = { - url = "git://github.com/Reuh/Candran", - tag = "v0.3.1" + url = "git://github.com/Reuh/candran", + tag = "v0.4.0" } dependencies = { diff --git a/rockspec/candran-scm-1.rockspec b/rockspec/candran-scm-1.rockspec new file mode 100644 index 0000000..73447e7 --- /dev/null +++ b/rockspec/candran-scm-1.rockspec @@ -0,0 +1,36 @@ +package = "candran" + +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. + ]], + license = "MIT", + homepage = "https://github.com/Reuh/candran", + --issues_url = "https://github.com/Reuh/candran", -- LuaRocks 3.0 + maintainer = "Étienne 'Reuh' Fildadut ", + --labels = {} -- LuaRocks 3.0 +} + +source = { + url = "git://github.com/Reuh/candran" +} + +dependencies = { + "lua >= 5.1", + "lpeglabel >= 1.0.0" +} + +build = { + type = "builtin", + modules = { + candran = "candran.lua" + }, + install = { + bin = { "bin/can", "bin/canc" } + } + --copy_directories = { "doc", "test" } +}