diff --git a/README.md b/README.md index 1256365..1d81568 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,31 @@ Candran ======= -Candran is a dialect of the [Lua 5.3](http://www.lua.org) programming language which compiles to Lua 5.3 and Lua 5.1/LuaJit. It adds a preprocessor and several useful syntax additions. +Candran is a dialect of the [Lua 5.3](http://www.lua.org) 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. +Unlike Moonscript, Candran tries to stay close to the Lua syntax, and existing Lua code can run on Candran unmodified. ````lua -#import("lib.thing") +#import("lib.thing") -- static import #local debug or= false -local function calculate(toadd=25) +local function calculate(toadd=25) -- default parameters local result = thing.do() result += toadd - #if debug then + #if debug then -- preprocessor conditionals print("Did something") #end return result end -local a = { +let a = { hey = true, - newHop = :(foo, thing) - @hey = thing(foo) + newHop = :(foo, thing) -- short function declaration, with self + @hey = thing(foo) -- @ as an alias for self + end, + + selfReference = () -- short function declaration, without self + return a -- no need for a prior local declaration using let end } @@ -29,47 +33,36 @@ a:newHop(42, (foo) return "something " .. foo end) +local list = [ -- table comprehension (kind of) + for i=1, 10 do + if i%2 == 0 then + continue -- continue keyword + end + i + end +] + +local a = if condition then "one" else "two" end -- statement as expressions + ```` Candran is released under the MIT License (see ```LICENSE``` for details). #### Quick setup -Install Candran automatically using LuaRocks: ```sudo luarocks install rockspec/candran-0.4.0-1.rockspec```. +Install Candran automatically using LuaRocks: ```sudo luarocks install rockspec/candran-0.5.0-1.rockspec```. 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```. +You can register the Candran package searcher in your main Lua file (`require("candran").setup()`) and any subsequent `require` call in your project will automatically search for Candran modules. + #### Editor support Most editors should be able to use their existing Lua support for Candran code. If you want full support for the additional syntax in your editor: * **Atom**: [language-candran](https://atom.io/packages/language-candran) support the full Candran syntax The language ------------ -### Preprocessor -Before compiling, Candran's preprocessor is run. It execute every line starting with a _#_ (ignoring whitespace) as Candran code. -For example, - -````lua -#if lang == "fr" then - print("Bonjour") -#else - print("Hello") -#end -```` - -Will output ````print("Bonjour")```` or ````print("Hello")```` depending of the "lang" argument passed to the preprocessor. - -The preprocessor has access to the following variables : -* ````candran```` : the Candran library table. -* ````output```` : the current preprocessor output string. -* ````import(module[, [options])```` : a function which import a module. This should be equivalent to using _require(module)_ in the Candran code, except the module will be embedded in the current file. _options_ is an optional preprocessor arguments table for the imported module (current preprocessor arguments will be inherited). Options specific to this function: ```loadLocal``` (default ```true```): ```true``` to automatically load the module into a local variable (i.e. ```local thing = require("module.thing")```); ```loadPackage``` (default ```true```): ```true``` to automatically load the module into the loaded packages table (so it will be available for following ```require("module")``` calls). -* ````include(filename)```` : a function which copy the contents of the file _filename_ to the output. -* ````write(...)```` : write to the preprocessor output. For example, ````#print("hello()")```` will output ````hello()```` in the final file. -* ```placeholder(name)``` : if the variable _name_ is defined in the preprocessor environement, its content will be inserted here. -* ````...```` : each arguments passed to the preprocessor is directly available. -* and every standard Lua library. - ### Syntax additions -After the preprocessor is run the Candran code is compiled to Lua. The Candran code adds the folowing syntax to Lua : +After the preprocessor is run the Candran code is compiled to Lua. Candran code adds the folowing syntax to Lua: ##### Assignment operators * ````var += nb```` @@ -105,7 +98,7 @@ It is equivalent to doing ```if arg == nil then arg = default end``` for each ar The default values can be complete Lua expressions, and will be evaluated each time the function is run. -##### @ self aliases +##### `@` self aliases ```lua a = { foo = "Hoi" @@ -135,7 +128,7 @@ Anonymous function (functions values) can be created in a more concise way by om A ```:``` can prefix the parameters paranthesis to automatically add a ```self``` parameter. -##### let variable declaration +##### `let` variable declaration ```lua let a = { foo = function() @@ -148,7 +141,7 @@ Similar to ```local```, but the variable will be declared *before* the assignemn Can also be used as a shorter name for ```local```. -##### continue keyword +##### `continue` keyword ```lua for i=1, 10 do if i % 2 == 0 then @@ -160,16 +153,119 @@ end Will skip the current loop iteration. +##### `push` keyword +```lua +function a() + for i=1, 5 do + push i, "next" + end + return "done" +end +print(a()) -- 1, next, 2, next, 3, next, 4, next, 5, next, done + +push "hey" -- Does *not* work, because it is a valid Lua syntax for push("hey") +``` + +Add one or more value to the returned value list. If you use a `return` afterwards, the pushed values will be placed *before* the `return` values, otherwise the function will only return what was pushed. + +This keyword is mainly useful when used through implicit `push` with table comprehension and statement expressions. + +**Please note** that, in order to stay compatible with vanilla Lua syntax, any `push` immediatly followed by a `"string expression"`, `{table expression}` or `(paranthesis)` will be interpreted as a function call. Preferably use implicit `push` in these cases. + +##### Implicit `push` +```lua +function a() + for i=1, 5 do + i, next + end + return "done" +end +print(a()) -- 1, next, 2, next, 3, next, 4, next, 5, next, done + +-- or probably more useful... +local square = (x) x*x end -- function(x) return x*x end +``` + +Any list of expressions placed *at the end of a block* will be converted into a `push` automatically. + +**Please note** that this doesn't work with `v()` function calls, because these are already valid statements. Use `push v()` instead. + +##### Statement expressions +```lua +a = if false then + "foo" -- i.e. push "foo", i.e. return "foo" +else + "bar" +end +print(a) -- bar + +a, b, c = for i=1,2 do i end +print(a, b, c) -- 1, 2, nil +``` + +Candran allows to use `if`, `do`, `while`, `repeat` and `for` statements as expressions. Their content will be run as if they were run in a separate function which is immediatly run. + +##### Table comprehension +```lua +a = [ + for i=1, 10 do + i + end +] -- { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } + +a = [ + for i=1, 10 do + if i%2 == 0 then + @[i] = true + end + end +] -- { [2] = true, [4] = true, [6] = true, [8] = true, [10] = true } + +a = [push unpack(t1); push unpack(t2)] -- concatenate t1 and t2 +``` + +Comprehensions provide a shorter syntax for defining and initializing tables based on a block of code. + +You can write *any* code you want between `[` and `]`, this code will be run as if it was a separate function which is immediadtly run. + +Values returned by the function will be inserted in the generated table in the order they were returned. This way, each time you `push` value(s), they will be added to the table. + +The table generation function also have access to the `self` (or its alias `@`) variable, which is the table which is being created, so you can set arbitrary fields of the table. + +### Preprocessor +Before compiling, Candran's preprocessor is run. It execute every line starting with a _#_ (ignoring whitespace) as Candran code. +For example, + +````lua +#if lang == "fr" then + print("Bonjour") +#else + print("Hello") +#end +```` + +Will output ````print("Bonjour")```` or ````print("Hello")```` depending of the "lang" argument passed to the preprocessor. + +The preprocessor has access to the following variables : +* ````candran```` : the Candran library table. +* ````output```` : the current preprocessor output string. +* ````import(module[, [options])```` : a function which import a module. This should be equivalent to using _require(module)_ in the Candran code, except the module will be embedded in the current file. _options_ is an optional preprocessor arguments table for the imported module (current preprocessor arguments will be inherited). Options specific to this function: ```loadLocal``` (default ```true```): ```true``` to automatically load the module into a local variable (i.e. ```local thing = require("module.thing")```); ```loadPackage``` (default ```true```): ```true``` to automatically load the module into the loaded packages table (so it will be available for following ```require("module")``` calls). +* ````include(filename)```` : a function which copy the contents of the file _filename_ to the output. +* ````write(...)```` : write to the preprocessor output. For example, ````#print("hello()")```` will output ````hello()```` in the final file. +* ```placeholder(name)``` : if the variable _name_ is defined in the preprocessor environement, its content will be inserted here. +* ````...```` : each arguments passed to the preprocessor is directly available. +* and every standard Lua library. + Compile targets --------------- Candran is based on the Lua 5.3 syntax, but can be compiled to both Lua 5.3 and Lua 5.1/LuaJit. To chose a compile target, set the ```target``` option to ```lua53``` (default) or ```luajit``` in the option table when using the library or the command line tools. -Lua 5.3 specific syntax (bitwise operators, integer division) will automatically be translated in valid Lua 5.1 code, using LuaJIT's ```bit``` library if necessary. +Lua 5.3 specific syntax (bitwise operators, integer division) will automatically be translated in valid Lua 5.1 code, using LuaJIT's ```bit``` library if necessary. Unless you require LuaJIT's library, you won't be able to use bitwise operators with simple Lua 5.1. -The library ------------ +Usage +----- ### Command-line usage The library can be used standalone through the ```canc``` and ```can``` utility: @@ -197,6 +293,10 @@ The library can be used standalone through the ```canc``` and ```can``` utility: preprocess and compile _foo.can_ and write the result in _foo.lua_. + ````canc indentation=" " foo.can```` + + preprocess and compile _foo.can_ with 2-space indentation (readable code!) and write the result in _foo.lua_. + ````canc foo.can -verbose -print | lua```` preprocess _foo.can_ with _verbose_ set to _true_, compile it and execute it. @@ -284,7 +384,7 @@ You can give arbitrary options which will be gived to the preprocessor, but Cand target = "lua53" -- Compiler target. "lua53" or "luajit". indentation = "" -- Character(s) used for indentation in the compiled file. newline = "\n" -- Character(s) used for newlines in the compiled file. -requirePrefix = "CANDRAN_" -- Prefix used when Candran needs to require an external library to provide some functionality (example: LuaJIT's bit lib when using bitwise operators). +variablePrefix = "__CAN_" -- Prefix used when Candran needs to set a local variable to provide some functionality (example: to load LuaJIT's bit lib when using bitwise operators). mapLines = true -- If true, compiled files will contain comments at the end of each line indicating the associated line and source file. Needed for error rewriting. chunkname = "nil" -- The chunkname used when running code using the helper functions and writing the line origin comments. Candran will try to set it to the original filename if it knows it. rewriteErrors = true -- True to enable error rewriting when loading code using the helper functions. Will wrap the whole code in a xpcall(). @@ -303,7 +403,7 @@ This command will use the precompilled version of this repository (candran.lua) canc candran.can ```` -The Candran build included in this repository were made using the ```mapLines=false``` options. +The Candran build included in this repository was made using the ```mapLines=false``` option. You can then run the tests on your build : diff --git a/candran.can b/candran.can index 27d8c1b..24b59c9 100644 --- a/candran.can +++ b/candran.can @@ -10,7 +10,7 @@ #import("lib.lua-parser.parser") local candran = { - VERSION = "0.4.0" + VERSION = "0.5.0" } --- Default options. @@ -18,7 +18,7 @@ local default = { target = "lua53", indentation = "", newline = "\n", - requirePrefix = "CANDRAN_", + variablePrefix = "__CAN_", mapLines = true, chunkname = "nil", rewriteErrors = true diff --git a/candran.lua b/candran.lua index e11ac7b..af2608b 100644 --- a/candran.lua +++ b/candran.lua @@ -171,12 +171,12 @@ local required = {} local requireStr = "" 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"]) +requireStr = requireStr .. ("local " .. options["variablePrefix"] .. name .. (" = require(%q)"):format(mod) .. (field and "." .. field or "") .. options["newline"]) required[mod] = true end end -local function getRequire(name) -return options["requirePrefix"] .. name +local function var(name) +return options["variablePrefix"] .. name end local loop = { "While", @@ -238,16 +238,22 @@ lastInputPos = ast["pos"] end return tags[forceTag or ast["tag"]](ast, ...) end +local UNPACK = function(list, i, j) +return "table.unpack(" .. list .. (i and (", " .. i .. (j and (", " .. j) or "")) or "") .. ")" +end +local APPEND = function(t, toAppend) +return "do" .. indent() .. "local a = table.pack(" .. toAppend .. ")" .. newline() .. "table.move(a, 1, a.n, #" .. t .. "+1, " .. t .. ")" .. unindent() .. "end" +end tags = setmetatable({ ["Block"] = function(t) -local hasPush = not peek("push") and any(t, { "Push" }, func) +local hasPush = peek("push") == nil and any(t, { "Push" }, func) if hasPush and hasPush == t[# t] then hasPush["tag"] = "Return" hasPush = false end local r = "" if hasPush then -r = r .. (push("push", "__PUSH__") .. "local __PUSH__ = {}" .. newline()) +r = r .. (push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline()) end for i = 1, # t - 1, 1 do r = r .. (lua(t[i]) .. newline()) @@ -256,7 +262,7 @@ if t[# t] then r = r .. (lua(t[# t])) end if hasPush and (t[# t] and t[# t]["tag"] ~= "Return") then -r = r .. (newline() .. "return unpack(__PUSH__)" .. pop("push")) +r = r .. (newline() .. "return " .. UNPACK(var("push")) .. pop("push")) end return r end, @@ -455,14 +461,25 @@ local r = "" for _, val in ipairs(t) do r = r .. (push .. "[#" .. push .. "+1] = " .. lua(val) .. newline()) end -return r .. "return unpack(" .. push .. ")" +return r .. "return " .. UNPACK(push) else return "return " .. lua(t, "_lhs") end end, ["Push"] = function(t) local var = assert(peek("push"), "no context given for push") -return var .. "[#" .. var .. "+1] = " .. lua(t, "_lhs") +r = "" +for i = 1, # t - 1, 1 do +r = r .. (var .. "[#" .. var .. "+1] = " .. lua(t[i]) .. newline()) +end +if t[# t] then +if t[# t]["tag"] == "Call" or t[# t]["tag"] == "Invoke" then +r = r .. (APPEND(var, lua(t[# t]))) +else +r = r .. (var .. "[#" .. var .. "+1] = " .. lua(t[# t])) +end +end +return r end, ["Break"] = function() return "break" @@ -514,7 +531,21 @@ r = r .. (")" .. indent()) for _, d in ipairs(decl) do r = r .. (d .. newline()) end -return r .. lua(t[2]) .. unindent() .. "end" +if t[2][# t[2]] and t[2][# t[2]]["tag"] == "Push" then +t[2][# t[2]]["tag"] = "Return" +end +local hasPush = any(t[2], { "Push" }, func) +if hasPush then +r = r .. (push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline()) +else +push("push", false) +end +r = r .. (lua(t[2])) +if hasPush then +r = r .. (newline() .. "return " .. UNPACK(var("push"))) +end +pop("push") +return r .. unindent() .. "end" end, ["Function"] = function(t) return "function" .. lua(t, "_functionWithoutKeyword") @@ -558,12 +589,15 @@ end, local hasPush = any(t, { "Push" }, func) local r = "(function()" .. indent() if hasPush then -r = r .. (push("push", "__PUSH__") .. "local __PUSH__ = {}" .. newline()) +r = r .. (push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline()) +else +push("push", false) end r = r .. (lua(t, stat)) if hasPush then -r = r .. (newline() .. "return unpack(__PUSH__)" .. pop("push")) +r = r .. (newline() .. "return " .. UNPACK(var("push"))) end +pop("push") r = r .. (unindent() .. "end)()") return r end, @@ -696,12 +730,12 @@ local required = {} local requireStr = "" 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"]) +requireStr = requireStr .. ("local " .. options["variablePrefix"] .. name .. (" = require(%q)"):format(mod) .. (field and "." .. field or "") .. options["newline"]) required[mod] = true end end -local function getRequire(name) -return options["requirePrefix"] .. name +local function var(name) +return options["variablePrefix"] .. name end local loop = { "While", @@ -763,16 +797,22 @@ lastInputPos = ast["pos"] end return tags[forceTag or ast["tag"]](ast, ...) end +local UNPACK = function(list, i, j) +return "table.unpack(" .. list .. (i and (", " .. i .. (j and (", " .. j) or "")) or "") .. ")" +end +local APPEND = function(t, toAppend) +return "do" .. indent() .. "local a = table.pack(" .. toAppend .. ")" .. newline() .. "table.move(a, 1, a.n, #" .. t .. "+1, " .. t .. ")" .. unindent() .. "end" +end tags = setmetatable({ ["Block"] = function(t) -local hasPush = not peek("push") and any(t, { "Push" }, func) +local hasPush = peek("push") == nil and any(t, { "Push" }, func) if hasPush and hasPush == t[# t] then hasPush["tag"] = "Return" hasPush = false end local r = "" if hasPush then -r = r .. (push("push", "__PUSH__") .. "local __PUSH__ = {}" .. newline()) +r = r .. (push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline()) end for i = 1, # t - 1, 1 do r = r .. (lua(t[i]) .. newline()) @@ -781,7 +821,7 @@ if t[# t] then r = r .. (lua(t[# t])) end if hasPush and (t[# t] and t[# t]["tag"] ~= "Return") then -r = r .. (newline() .. "return unpack(__PUSH__)" .. pop("push")) +r = r .. (newline() .. "return " .. UNPACK(var("push")) .. pop("push")) end return r end, @@ -980,14 +1020,25 @@ local r = "" for _, val in ipairs(t) do r = r .. (push .. "[#" .. push .. "+1] = " .. lua(val) .. newline()) end -return r .. "return unpack(" .. push .. ")" +return r .. "return " .. UNPACK(push) else return "return " .. lua(t, "_lhs") end end, ["Push"] = function(t) local var = assert(peek("push"), "no context given for push") -return var .. "[#" .. var .. "+1] = " .. lua(t, "_lhs") +r = "" +for i = 1, # t - 1, 1 do +r = r .. (var .. "[#" .. var .. "+1] = " .. lua(t[i]) .. newline()) +end +if t[# t] then +if t[# t]["tag"] == "Call" or t[# t]["tag"] == "Invoke" then +r = r .. (APPEND(var, lua(t[# t]))) +else +r = r .. (var .. "[#" .. var .. "+1] = " .. lua(t[# t])) +end +end +return r end, ["Break"] = function() return "break" @@ -1039,7 +1090,21 @@ r = r .. (")" .. indent()) for _, d in ipairs(decl) do r = r .. (d .. newline()) end -return r .. lua(t[2]) .. unindent() .. "end" +if t[2][# t[2]] and t[2][# t[2]]["tag"] == "Push" then +t[2][# t[2]]["tag"] = "Return" +end +local hasPush = any(t[2], { "Push" }, func) +if hasPush then +r = r .. (push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline()) +else +push("push", false) +end +r = r .. (lua(t[2])) +if hasPush then +r = r .. (newline() .. "return " .. UNPACK(var("push"))) +end +pop("push") +return r .. unindent() .. "end" end, ["Function"] = function(t) return "function" .. lua(t, "_functionWithoutKeyword") @@ -1083,12 +1148,15 @@ end, local hasPush = any(t, { "Push" }, func) local r = "(function()" .. indent() if hasPush then -r = r .. (push("push", "__PUSH__") .. "local __PUSH__ = {}" .. newline()) +r = r .. (push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline()) +else +push("push", false) end r = r .. (lua(t, stat)) if hasPush then -r = r .. (newline() .. "return unpack(__PUSH__)" .. pop("push")) +r = r .. (newline() .. "return " .. UNPACK(var("push"))) end +pop("push") r = r .. (unindent() .. "end)()") return r end, @@ -1174,6 +1242,12 @@ end, }, { ["__index"] = function(self, key) error("don't know how to compile a " .. tostring(key) .. " to Lua 5.3") end }) +UNPACK = function(list, i, j) +return "unpack(" .. list .. (i and (", " .. i .. (j and (", " .. j) or "")) or "") .. ")" +end +APPEND = function(t, toAppend) +return "do" .. indent() .. "local a, p = { " .. toAppend .. " }, #" .. t .. "+1" .. newline() .. "for i=1, #a do" .. indent() .. t .. "[p] = a[i]" .. newline() .. "p = p + 1" .. unindent() .. "end" .. unindent() .. "end" +end tags["_opid"]["idiv"] = function(left, right) return "math.floor(" .. lua(left) .. " / " .. lua(right) .. ")" end @@ -1465,7 +1539,7 @@ local status, msg = traverse_varlist(env, stm[1]) if not status then return status, msg end -status, msg = traverse_explist(env, stm[2]) +status, msg = traverse_explist(env, stm[# stm]) if not status then return status, msg end @@ -2708,13 +2782,13 @@ return parser end local parser = _() or parser package["loaded"]["lib.lua-parser.parser"] = parser or true -local candran = { ["VERSION"] = "0.4.0" } +local candran = { ["VERSION"] = "0.5.0" } local default = { ["target"] = "lua53", ["indentation"] = "", ["newline"] = "\ ", -["requirePrefix"] = "CANDRAN_", +["variablePrefix"] = "__CAN_", ["mapLines"] = true, ["chunkname"] = "nil", ["rewriteErrors"] = true diff --git a/compiler/lua53.can b/compiler/lua53.can index 20b647e..23ddc75 100644 --- a/compiler/lua53.can +++ b/compiler/lua53.can @@ -46,13 +46,15 @@ return function(code, ast, options) -- Add the module "mod" to the list of modules to require, and load its field "field" (or the whole module if nil) into the variable "name". local function addRequire(mod, name, field) if not required[mod] then - requireStr ..= "local " .. options.requirePrefix .. name .. (" = require(%q)"):format(mod) .. (field and "."..field or "") .. options.newline + requireStr ..= "local " .. options.variablePrefix .. name .. (" = require(%q)"):format(mod) .. (field and "."..field or "") .. options.newline required[mod] = true end end - -- Returns the required module variable name. - local function getRequire(name) - return options.requirePrefix .. name + + --- Variable management + -- Returns the prefixed variable name. + local function var(name) + return options.variablePrefix .. name end --- AST traversal helpers @@ -112,18 +114,27 @@ return function(code, ast, options) end return tags[forceTag or ast.tag](ast, ...) end - -- Tag constructors + + --- Lua function calls writer + local UNPACK = (list, i, j) -- table.unpack + return "table.unpack(" .. list .. (i and (", " .. i .. (j and (", " .. j) or "")) or "") .. ")" + end + local APPEND = (t, toAppend) -- append values "toAppend" (multiple values possible) to t + return "do" .. indent() .. "local a = table.pack(" .. toAppend .. ")" .. newline() .. "table.move(a, 1, a.n, #" .. t .. "+1, " .. t .. ")" .. unindent() .. "end" + end + + --- Tag constructors tags = setmetatable({ -- block: { stat* } -- Block = (t) - local hasPush = not peek("push") and any(t, { "Push" }, func) -- push in block and push context not yet defined + local hasPush = peek("push") == nil and any(t, { "Push" }, func) -- push in block and push context not yet defined if hasPush and hasPush == t[#t] then -- if the first push is the last statement, it's just a return hasPush.tag = "Return" hasPush = false end local r = "" if hasPush then - r ..= push("push", "__PUSH__") .. "local __PUSH__ = {}" .. newline() + r ..= push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline() end for i=1, #t-1, 1 do r ..= lua(t[i]) .. newline() @@ -132,7 +143,7 @@ return function(code, ast, options) r ..= lua(t[#t]) end if hasPush and (t[#t] and t[#t].tag ~= "Return") then -- add return only if needed - r ..= newline() .. "return unpack(__PUSH__)" .. pop("push") + r ..= newline() .. "return " .. UNPACK(var("push")) .. pop("push") end return r end, @@ -291,7 +302,7 @@ return function(code, ast, options) for _, val in ipairs(t) do r ..= push .. "[#" .. push .. "+1] = " .. lua(val) .. newline() end - return r .. "return unpack(" .. push .. ")" + return r .. "return " .. UNPACK(push) else return "return "..lua(t, "_lhs") end @@ -299,7 +310,18 @@ return function(code, ast, options) -- Push{ } Push = (t) local var = assert(peek("push"), "no context given for push") - return var .. "[#" .. var .. "+1] = " .. lua(t, "_lhs") + r = "" + for i=1, #t-1, 1 do + r ..= var .. "[#" .. var .. "+1] = " .. lua(t[i]) .. newline() + end + if t[#t] then + if t[#t].tag == "Call" or t[#t].tag == "Invoke" then + r ..= APPEND(var, lua(t[#t])) + else + r ..= var .. "[#" .. var .. "+1] = " .. lua(t[#t]) + end + end + return r end, -- Break Break = () @@ -363,7 +385,21 @@ return function(code, ast, options) for _, d in ipairs(decl) do r ..= d .. newline() end - return r .. lua(t[2]) .. unindent() .. "end" + if t[2][#t[2]] and t[2][#t[2]].tag == "Push" then -- convert final push to return + t[2][#t[2]].tag = "Return" + end + local hasPush = any(t[2], { "Push" }, func) + if hasPush then + r ..= push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline() + else + push("push", false) -- no push here (make sure higher push don't affect us) + end + r ..= lua(t[2]) + if hasPush then + r ..= newline() .. "return " .. UNPACK(var("push")) + end + pop("push") + return r .. unindent() .. "end" end, Function = (t) return "function" .. lua(t, "_functionWithoutKeyword") @@ -416,12 +452,15 @@ return function(code, ast, options) local hasPush = any(t, { "Push" }, func) local r = "(function()" .. indent() if hasPush then - r ..= push("push", "__PUSH__") .. "local __PUSH__ = {}" .. newline() + r ..= push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline() + else + push("push", false) -- no push here (make sure higher push don't affect us) end r ..= lua(t, stat) if hasPush then - r ..= newline() .. "return unpack(__PUSH__)" .. pop("push") + r ..= newline() .. "return " .. UNPACK(var("push")) end + pop("push") r ..= unindent() .. "end)()" return r end, diff --git a/compiler/luajit.can b/compiler/luajit.can index e8929cc..f79f00e 100644 --- a/compiler/luajit.can +++ b/compiler/luajit.can @@ -1,27 +1,34 @@ -tags._opid.idiv = function(left, right) +UNPACK = (list, i, j) + return "unpack(" .. list .. (i and (", " .. i .. (j and (", " .. j) or "")) or "") .. ")" +end +APPEND = (t, toAppend) + return "do" .. indent() .. "local a, p = { " .. toAppend .. " }, #" .. t .. "+1" .. newline() .. "for i=1, #a do" .. indent() .. t .. "[p] = a[i]" .. newline() .. "p = p + 1" .. unindent() .. "end" .. unindent() .. "end" +end + +tags._opid.idiv = (left, right) return "math.floor(" .. lua(left) .. " / " .. lua(right) .. ")" end -tags._opid.band = function(left, right) +tags._opid.band = (left, right) addRequire("bit", "band", "band") return getRequire("band") .. "(" .. lua(left) .. ", " .. lua(right) .. ")" end -tags._opid.bor = function(left, right) +tags._opid.bor = (left, right) addRequire("bit", "bor", "bor") return getRequire("bor") .. "(" .. lua(left) .. ", " .. lua(right) .. ")" end -tags._opid.bxor = function(left, right) +tags._opid.bxor = (left, right) addRequire("bit", "bxor", "bxor") return getRequire("bxor") .. "(" .. lua(left) .. ", " .. lua(right) .. ")" end -tags._opid.shl = function(left, right) +tags._opid.shl = (left, right) addRequire("bit", "lshift", "lshift") return getRequire("lshift") .. "(" .. lua(left) .. ", " .. lua(right) .. ")" end -tags._opid.shr = function(left, right) +tags._opid.shr = (left, right) addRequire("bit", "rshift", "rshift") return getRequire("rshift") .. "(" .. lua(left) .. ", " .. lua(right) .. ")" end -tags._opid.bnot = function(right) +tags._opid.bnot = (right) addRequire("bit", "bnot", "bnot") return getRequire("bnot") .. "(" .. lua(right) .. ")" end diff --git a/lib/lua-parser/validator.lua b/lib/lua-parser/validator.lua index 002e679..7b4419a 100644 --- a/lib/lua-parser/validator.lua +++ b/lib/lua-parser/validator.lua @@ -171,7 +171,7 @@ end local function traverse_assignment (env, stm) local status, msg = traverse_varlist(env, stm[1]) if not status then return status, msg end - status, msg = traverse_explist(env, stm[2]) + status, msg = traverse_explist(env, stm[#stm]) if not status then return status, msg end return true end @@ -372,7 +372,7 @@ function traverse_stm (env, stm) local tag = stm.tag if tag == "Do" then -- `Do{ stat* } return traverse_block(env, stm) - elseif tag == "Set" then -- `Set{ {lhs+} {expr+} } + elseif tag == "Set" then -- `Set{ {lhs+} (opid? = opid?)? {expr+} } return traverse_assignment(env, stm) elseif tag == "While" then -- `While{ expr block } return traverse_while(env, stm) diff --git a/rockspec/candran-0.4.0-1.rockspec b/rockspec/candran-0.5.0-1.rockspec similarity index 74% rename from rockspec/candran-0.4.0-1.rockspec rename to rockspec/candran-0.5.0-1.rockspec index ca784f7..b3d2737 100644 --- a/rockspec/candran-0.4.0-1.rockspec +++ b/rockspec/candran-0.5.0-1.rockspec @@ -1,12 +1,12 @@ package = "candran" -version = "0.4.0-1" +version = "0.5.0-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", @@ -17,7 +17,7 @@ description = { source = { url = "git://github.com/Reuh/candran", - tag = "v0.4.0" + tag = "v0.5.0" } dependencies = {