1
0
Fork 0
mirror of https://github.com/Reuh/candran.git synced 2025-10-27 17:59:30 +00:00
Fixed plenty of bugs.

Tests are lacking.
This commit is contained in:
Étienne Fildadut 2017-08-25 19:20:29 +02:00
parent 6b95bfb698
commit d249c353c5
7 changed files with 315 additions and 95 deletions

184
README.md
View file

@ -1,27 +1,31 @@
Candran 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 ````lua
#import("lib.thing") #import("lib.thing") -- static import
#local debug or= false #local debug or= false
local function calculate(toadd=25) local function calculate(toadd=25) -- default parameters
local result = thing.do() local result = thing.do()
result += toadd result += toadd
#if debug then #if debug then -- preprocessor conditionals
print("Did something") print("Did something")
#end #end
return result return result
end end
local a = { let a = {
hey = true, hey = true,
newHop = :(foo, thing) newHop = :(foo, thing) -- short function declaration, with self
@hey = thing(foo) @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 end
} }
@ -29,47 +33,36 @@ a:newHop(42, (foo)
return "something " .. foo return "something " .. foo
end) 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). Candran is released under the MIT License (see ```LICENSE``` for details).
#### Quick setup #### 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```. 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 #### 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: 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 * **Atom**: [language-candran](https://atom.io/packages/language-candran) support the full Candran syntax
The language 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 ### 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 ##### Assignment operators
* ````var += nb```` * ````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. The default values can be complete Lua expressions, and will be evaluated each time the function is run.
##### @ self aliases ##### `@` self aliases
```lua ```lua
a = { a = {
foo = "Hoi" 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. A ```:``` can prefix the parameters paranthesis to automatically add a ```self``` parameter.
##### let variable declaration ##### `let` variable declaration
```lua ```lua
let a = { let a = {
foo = function() 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```. Can also be used as a shorter name for ```local```.
##### continue keyword ##### `continue` keyword
```lua ```lua
for i=1, 10 do for i=1, 10 do
if i % 2 == 0 then if i % 2 == 0 then
@ -160,16 +153,119 @@ end
Will skip the current loop iteration. 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 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. 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. 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 ### Command-line usage
The library can be used standalone through the ```canc``` and ```can``` utility: 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_. 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```` ````canc foo.can -verbose -print | lua````
preprocess _foo.can_ with _verbose_ set to _true_, compile it and execute it. 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". target = "lua53" -- Compiler target. "lua53" or "luajit".
indentation = "" -- Character(s) used for indentation in the compiled file. indentation = "" -- Character(s) used for indentation in the compiled file.
newline = "\n" -- Character(s) used for newlines 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. 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. 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(). 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 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 : You can then run the tests on your build :

View file

@ -10,7 +10,7 @@
#import("lib.lua-parser.parser") #import("lib.lua-parser.parser")
local candran = { local candran = {
VERSION = "0.4.0" VERSION = "0.5.0"
} }
--- Default options. --- Default options.
@ -18,7 +18,7 @@ local default = {
target = "lua53", target = "lua53",
indentation = "", indentation = "",
newline = "\n", newline = "\n",
requirePrefix = "CANDRAN_", variablePrefix = "__CAN_",
mapLines = true, mapLines = true,
chunkname = "nil", chunkname = "nil",
rewriteErrors = true rewriteErrors = true

View file

@ -171,12 +171,12 @@ local required = {}
local requireStr = "" local requireStr = ""
local function addRequire(mod, name, field) local function addRequire(mod, name, field)
if not required[mod] then 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 required[mod] = true
end end
end end
local function getRequire(name) local function var(name)
return options["requirePrefix"] .. name return options["variablePrefix"] .. name
end end
local loop = { local loop = {
"While", "While",
@ -238,16 +238,22 @@ lastInputPos = ast["pos"]
end end
return tags[forceTag or ast["tag"]](ast, ...) return tags[forceTag or ast["tag"]](ast, ...)
end 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({ tags = setmetatable({
["Block"] = function(t) ["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 if hasPush and hasPush == t[# t] then
hasPush["tag"] = "Return" hasPush["tag"] = "Return"
hasPush = false hasPush = false
end end
local r = "" local r = ""
if hasPush then if hasPush then
r = r .. (push("push", "__PUSH__") .. "local __PUSH__ = {}" .. newline()) r = r .. (push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline())
end end
for i = 1, # t - 1, 1 do for i = 1, # t - 1, 1 do
r = r .. (lua(t[i]) .. newline()) r = r .. (lua(t[i]) .. newline())
@ -256,7 +262,7 @@ if t[# t] then
r = r .. (lua(t[# t])) r = r .. (lua(t[# t]))
end end
if hasPush and (t[# t] and t[# t]["tag"] ~= "Return") then 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 end
return r return r
end, end,
@ -455,14 +461,25 @@ local r = ""
for _, val in ipairs(t) do for _, val in ipairs(t) do
r = r .. (push .. "[#" .. push .. "+1] = " .. lua(val) .. newline()) r = r .. (push .. "[#" .. push .. "+1] = " .. lua(val) .. newline())
end end
return r .. "return unpack(" .. push .. ")" return r .. "return " .. UNPACK(push)
else else
return "return " .. lua(t, "_lhs") return "return " .. lua(t, "_lhs")
end end
end, end,
["Push"] = function(t) ["Push"] = function(t)
local var = assert(peek("push"), "no context given for push") 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, end,
["Break"] = function() ["Break"] = function()
return "break" return "break"
@ -514,7 +531,21 @@ r = r .. (")" .. indent())
for _, d in ipairs(decl) do for _, d in ipairs(decl) do
r = r .. (d .. newline()) r = r .. (d .. newline())
end 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, end,
["Function"] = function(t) ["Function"] = function(t)
return "function" .. lua(t, "_functionWithoutKeyword") return "function" .. lua(t, "_functionWithoutKeyword")
@ -558,12 +589,15 @@ end,
local hasPush = any(t, { "Push" }, func) local hasPush = any(t, { "Push" }, func)
local r = "(function()" .. indent() local r = "(function()" .. indent()
if hasPush then 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 end
r = r .. (lua(t, stat)) r = r .. (lua(t, stat))
if hasPush then if hasPush then
r = r .. (newline() .. "return unpack(__PUSH__)" .. pop("push")) r = r .. (newline() .. "return " .. UNPACK(var("push")))
end end
pop("push")
r = r .. (unindent() .. "end)()") r = r .. (unindent() .. "end)()")
return r return r
end, end,
@ -696,12 +730,12 @@ local required = {}
local requireStr = "" local requireStr = ""
local function addRequire(mod, name, field) local function addRequire(mod, name, field)
if not required[mod] then 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 required[mod] = true
end end
end end
local function getRequire(name) local function var(name)
return options["requirePrefix"] .. name return options["variablePrefix"] .. name
end end
local loop = { local loop = {
"While", "While",
@ -763,16 +797,22 @@ lastInputPos = ast["pos"]
end end
return tags[forceTag or ast["tag"]](ast, ...) return tags[forceTag or ast["tag"]](ast, ...)
end 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({ tags = setmetatable({
["Block"] = function(t) ["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 if hasPush and hasPush == t[# t] then
hasPush["tag"] = "Return" hasPush["tag"] = "Return"
hasPush = false hasPush = false
end end
local r = "" local r = ""
if hasPush then if hasPush then
r = r .. (push("push", "__PUSH__") .. "local __PUSH__ = {}" .. newline()) r = r .. (push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline())
end end
for i = 1, # t - 1, 1 do for i = 1, # t - 1, 1 do
r = r .. (lua(t[i]) .. newline()) r = r .. (lua(t[i]) .. newline())
@ -781,7 +821,7 @@ if t[# t] then
r = r .. (lua(t[# t])) r = r .. (lua(t[# t]))
end end
if hasPush and (t[# t] and t[# t]["tag"] ~= "Return") then 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 end
return r return r
end, end,
@ -980,14 +1020,25 @@ local r = ""
for _, val in ipairs(t) do for _, val in ipairs(t) do
r = r .. (push .. "[#" .. push .. "+1] = " .. lua(val) .. newline()) r = r .. (push .. "[#" .. push .. "+1] = " .. lua(val) .. newline())
end end
return r .. "return unpack(" .. push .. ")" return r .. "return " .. UNPACK(push)
else else
return "return " .. lua(t, "_lhs") return "return " .. lua(t, "_lhs")
end end
end, end,
["Push"] = function(t) ["Push"] = function(t)
local var = assert(peek("push"), "no context given for push") 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, end,
["Break"] = function() ["Break"] = function()
return "break" return "break"
@ -1039,7 +1090,21 @@ r = r .. (")" .. indent())
for _, d in ipairs(decl) do for _, d in ipairs(decl) do
r = r .. (d .. newline()) r = r .. (d .. newline())
end 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, end,
["Function"] = function(t) ["Function"] = function(t)
return "function" .. lua(t, "_functionWithoutKeyword") return "function" .. lua(t, "_functionWithoutKeyword")
@ -1083,12 +1148,15 @@ end,
local hasPush = any(t, { "Push" }, func) local hasPush = any(t, { "Push" }, func)
local r = "(function()" .. indent() local r = "(function()" .. indent()
if hasPush then 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 end
r = r .. (lua(t, stat)) r = r .. (lua(t, stat))
if hasPush then if hasPush then
r = r .. (newline() .. "return unpack(__PUSH__)" .. pop("push")) r = r .. (newline() .. "return " .. UNPACK(var("push")))
end end
pop("push")
r = r .. (unindent() .. "end)()") r = r .. (unindent() .. "end)()")
return r return r
end, end,
@ -1174,6 +1242,12 @@ end,
}, { ["__index"] = function(self, key) }, { ["__index"] = function(self, key)
error("don't know how to compile a " .. tostring(key) .. " to Lua 5.3") error("don't know how to compile a " .. tostring(key) .. " to Lua 5.3")
end }) 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) tags["_opid"]["idiv"] = function(left, right)
return "math.floor(" .. lua(left) .. " / " .. lua(right) .. ")" return "math.floor(" .. lua(left) .. " / " .. lua(right) .. ")"
end end
@ -1465,7 +1539,7 @@ local status, msg = traverse_varlist(env, stm[1])
if not status then if not status then
return status, msg return status, msg
end end
status, msg = traverse_explist(env, stm[2]) status, msg = traverse_explist(env, stm[# stm])
if not status then if not status then
return status, msg return status, msg
end end
@ -2708,13 +2782,13 @@ return parser
end end
local parser = _() or parser local parser = _() or parser
package["loaded"]["lib.lua-parser.parser"] = parser or true package["loaded"]["lib.lua-parser.parser"] = parser or true
local candran = { ["VERSION"] = "0.4.0" } local candran = { ["VERSION"] = "0.5.0" }
local default = { local default = {
["target"] = "lua53", ["target"] = "lua53",
["indentation"] = "", ["indentation"] = "",
["newline"] = "\ ["newline"] = "\
", ",
["requirePrefix"] = "CANDRAN_", ["variablePrefix"] = "__CAN_",
["mapLines"] = true, ["mapLines"] = true,
["chunkname"] = "nil", ["chunkname"] = "nil",
["rewriteErrors"] = true ["rewriteErrors"] = true

View file

@ -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". -- 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) local function addRequire(mod, name, field)
if not required[mod] then 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 required[mod] = true
end end
end end
-- Returns the required module variable name.
local function getRequire(name) --- Variable management
return options.requirePrefix .. name -- Returns the prefixed variable name.
local function var(name)
return options.variablePrefix .. name
end end
--- AST traversal helpers --- AST traversal helpers
@ -112,18 +114,27 @@ return function(code, ast, options)
end end
return tags[forceTag or ast.tag](ast, ...) return tags[forceTag or ast.tag](ast, ...)
end 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({ tags = setmetatable({
-- block: { stat* } -- -- block: { stat* } --
Block = (t) 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 if hasPush and hasPush == t[#t] then -- if the first push is the last statement, it's just a return
hasPush.tag = "Return" hasPush.tag = "Return"
hasPush = false hasPush = false
end end
local r = "" local r = ""
if hasPush then if hasPush then
r ..= push("push", "__PUSH__") .. "local __PUSH__ = {}" .. newline() r ..= push("push", var("push")) .. "local " .. var("push") .. " = {}" .. newline()
end end
for i=1, #t-1, 1 do for i=1, #t-1, 1 do
r ..= lua(t[i]) .. newline() r ..= lua(t[i]) .. newline()
@ -132,7 +143,7 @@ return function(code, ast, options)
r ..= lua(t[#t]) r ..= lua(t[#t])
end end
if hasPush and (t[#t] and t[#t].tag ~= "Return") then -- add return only if needed 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 end
return r return r
end, end,
@ -291,7 +302,7 @@ return function(code, ast, options)
for _, val in ipairs(t) do for _, val in ipairs(t) do
r ..= push .. "[#" .. push .. "+1] = " .. lua(val) .. newline() r ..= push .. "[#" .. push .. "+1] = " .. lua(val) .. newline()
end end
return r .. "return unpack(" .. push .. ")" return r .. "return " .. UNPACK(push)
else else
return "return "..lua(t, "_lhs") return "return "..lua(t, "_lhs")
end end
@ -299,7 +310,18 @@ return function(code, ast, options)
-- Push{ <expr*> } -- Push{ <expr*> }
Push = (t) Push = (t)
local var = assert(peek("push"), "no context given for push") 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, end,
-- Break -- Break
Break = () Break = ()
@ -363,7 +385,21 @@ return function(code, ast, options)
for _, d in ipairs(decl) do for _, d in ipairs(decl) do
r ..= d .. newline() r ..= d .. newline()
end 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, end,
Function = (t) Function = (t)
return "function" .. lua(t, "_functionWithoutKeyword") return "function" .. lua(t, "_functionWithoutKeyword")
@ -416,12 +452,15 @@ return function(code, ast, options)
local hasPush = any(t, { "Push" }, func) local hasPush = any(t, { "Push" }, func)
local r = "(function()" .. indent() local r = "(function()" .. indent()
if hasPush then 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 end
r ..= lua(t, stat) r ..= lua(t, stat)
if hasPush then if hasPush then
r ..= newline() .. "return unpack(__PUSH__)" .. pop("push") r ..= newline() .. "return " .. UNPACK(var("push"))
end end
pop("push")
r ..= unindent() .. "end)()" r ..= unindent() .. "end)()"
return r return r
end, end,

View file

@ -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) .. ")" return "math.floor(" .. lua(left) .. " / " .. lua(right) .. ")"
end end
tags._opid.band = function(left, right) tags._opid.band = (left, right)
addRequire("bit", "band", "band") addRequire("bit", "band", "band")
return getRequire("band") .. "(" .. lua(left) .. ", " .. lua(right) .. ")" return getRequire("band") .. "(" .. lua(left) .. ", " .. lua(right) .. ")"
end end
tags._opid.bor = function(left, right) tags._opid.bor = (left, right)
addRequire("bit", "bor", "bor") addRequire("bit", "bor", "bor")
return getRequire("bor") .. "(" .. lua(left) .. ", " .. lua(right) .. ")" return getRequire("bor") .. "(" .. lua(left) .. ", " .. lua(right) .. ")"
end end
tags._opid.bxor = function(left, right) tags._opid.bxor = (left, right)
addRequire("bit", "bxor", "bxor") addRequire("bit", "bxor", "bxor")
return getRequire("bxor") .. "(" .. lua(left) .. ", " .. lua(right) .. ")" return getRequire("bxor") .. "(" .. lua(left) .. ", " .. lua(right) .. ")"
end end
tags._opid.shl = function(left, right) tags._opid.shl = (left, right)
addRequire("bit", "lshift", "lshift") addRequire("bit", "lshift", "lshift")
return getRequire("lshift") .. "(" .. lua(left) .. ", " .. lua(right) .. ")" return getRequire("lshift") .. "(" .. lua(left) .. ", " .. lua(right) .. ")"
end end
tags._opid.shr = function(left, right) tags._opid.shr = (left, right)
addRequire("bit", "rshift", "rshift") addRequire("bit", "rshift", "rshift")
return getRequire("rshift") .. "(" .. lua(left) .. ", " .. lua(right) .. ")" return getRequire("rshift") .. "(" .. lua(left) .. ", " .. lua(right) .. ")"
end end
tags._opid.bnot = function(right) tags._opid.bnot = (right)
addRequire("bit", "bnot", "bnot") addRequire("bit", "bnot", "bnot")
return getRequire("bnot") .. "(" .. lua(right) .. ")" return getRequire("bnot") .. "(" .. lua(right) .. ")"
end end

View file

@ -171,7 +171,7 @@ end
local function traverse_assignment (env, stm) local function traverse_assignment (env, stm)
local status, msg = traverse_varlist(env, stm[1]) local status, msg = traverse_varlist(env, stm[1])
if not status then return status, msg end 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 if not status then return status, msg end
return true return true
end end
@ -372,7 +372,7 @@ function traverse_stm (env, stm)
local tag = stm.tag local tag = stm.tag
if tag == "Do" then -- `Do{ stat* } if tag == "Do" then -- `Do{ stat* }
return traverse_block(env, stm) 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) return traverse_assignment(env, stm)
elseif tag == "While" then -- `While{ expr block } elseif tag == "While" then -- `While{ expr block }
return traverse_while(env, stm) return traverse_while(env, stm)

View file

@ -1,12 +1,12 @@
package = "candran" package = "candran"
version = "0.4.0-1" version = "0.5.0-1"
description = { description = {
summary = "A simple Lua dialect and preprocessor.", summary = "A simple Lua dialect and preprocessor.",
detailed = [[ 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. 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. Unlike Moonscript, Candran tries to stay close to the Lua syntax, and existing Lua code can run on Candran unmodified.
]], ]],
license = "MIT", license = "MIT",
homepage = "https://github.com/Reuh/candran", homepage = "https://github.com/Reuh/candran",
@ -17,7 +17,7 @@ description = {
source = { source = {
url = "git://github.com/Reuh/candran", url = "git://github.com/Reuh/candran",
tag = "v0.4.0" tag = "v0.5.0"
} }
dependencies = { dependencies = {