1
0
Fork 0
mirror of https://github.com/Reuh/candran.git synced 2025-10-27 09:59:29 +00:00

Candran 0.3.0

* Added @ self alias
* Added short anonymous functions declaration
* Made assignment operators works in every direction, except up, down,
behind and below, because this would be hard to visualize.
* Moved files around.
* Error rewriting.
* Discover the amazing can commandline tool, which includes a fantastic°
REPL and program running abilities.
* Added functions which plagiarize Lua.
* Added 0.1.0 to the version number.
* If you still love pineapple flavored bread, don't hesitate to show
your feelings.

Also, the tests are out of date. Sad.

°: not really.
This commit is contained in:
Étienne Fildadut 2017-08-16 22:33:44 +02:00
parent 2a1e293aa5
commit 4af2b41a0d
17 changed files with 2413 additions and 1865 deletions

154
README.md
View file

@ -4,11 +4,9 @@ Candran is a dialect of the [Lua 5.3](http://www.lua.org) programming language w
Unlike Moonscript, Candran tries to stay close to the Lua syntax. Unlike Moonscript, Candran tries to stay close to the Lua syntax.
Candran code example :
````lua ````lua
#import("lib.thing") #import("lib.thing")
#local debug = debug or false #local debug or= false
local function calculate(toadd=25) local function calculate(toadd=25)
local result = thing.do() local result = thing.do()
@ -19,11 +17,22 @@ local function calculate(toadd=25)
return result return result
end end
print(calculate()) local a = {
hey = true,
newHop = :(foo, thing)
@hey = thing(foo)
end
}
a:newHop(42, (foo)
return "something " .. foo
end)
```` ````
##### Quick setup #### Quick setup
Install LPegLabel (```luarocks install LPegLabel```), download this repository and use Candran through ```canc.lua``` or ```candran.lua```. 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 The language
------------ ------------
@ -53,7 +62,8 @@ The preprocessor has access to the following variables :
### 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. The Candran code adds the folowing syntax to Lua :
##### New assignment operators
##### Assignment operators
* ````var += nb```` * ````var += nb````
* ````var -= nb```` * ````var -= nb````
* ````var *= nb```` * ````var *= nb````
@ -71,6 +81,10 @@ After the preprocessor is run the Candran code is compiled to Lua. The Candran c
For example, a ````var += nb```` assignment will be compiled into ````var = var + nb````. For example, a ````var += nb```` assignment will be compiled into ````var = var + nb````.
All theses operators can also be put right of the assigment operator, in which case ```var =+ nb``` will be compiled into ```var = nb + var```.
If you feel like writing hard to understand code, right and left operator can be used at the same time.
##### Default function parameters ##### Default function parameters
```lua ```lua
function foo(bar = "default", other = thing.do()) function foo(bar = "default", other = thing.do())
@ -83,43 +97,87 @@ 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
```lua
a = {
foo = "Hoi"
}
function a:hey()
print(@foo) -- Hoi
print(@["foo"]) -- also works
print(@ == self) -- true
end
```
When a variable name is prefied with ```@```, the name will be accessed in ```self```.
When used by itself, ```@``` is an alias for ```self```.
##### Short anonymous function declaration
```lua
a = (arg1, arg2)
print(arg1)
end
b = :(hop)
print(self, hop)
end
```
Anonymous function (functions values) can be created in a more concise way by omitting the ```function``` keyword.
A ```:``` can prefix the parameters paranthesis to automatically add a ```self``` parameter.
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, either explicitly give ```lua53``` or ```luajit``` as a second argument to ```candran.compile```, or set the ```target``` preprocessor argument when using ```candran.make``` 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.
The library The library
----------- -----------
### Command-line usage ### Command-line usage
The library can be used standalone through the ```canc``` utility: The library can be used standalone through the ```canc``` and ```can``` utility:
* ````lua canc.lua```` * ````canc````
Display the information text (version and basic command-line usage). Display the information text (version and basic command-line usage).
* ````lua canc.lua [arguments] filename...```` * ````canc [options] filename...````
Preprocess and compile each _filename_ Candran files, and creates the assiociated ```.lua``` files in the same directories. Preprocess and compile each _filename_ Candran files, and creates the assiociated ```.lua``` files in the same directories.
_arguments_ is of type ````-somearg -anotherarg thing=somestring other=5 ...````, which will generate a Lua table ```{ somearg = true, anotherarg = true, thing = "somestring", other = 5 }```. _options_ is of type ````-somearg -anotherarg thing=somestring other=5 ...````, which will generate a Lua table ```{ somearg = true, anotherarg = true, thing = "somestring", other = 5 }```.
You can choose to use another directory where files should be written using the ```dest=destinationDirectory``` argument. You can choose to use another directory where files should be written using the ```dest=destinationDirectory``` argument.
```canc``` can write to the standard output instead of creating files using the ```-print``` argument. ```canc``` can write to the standard output instead of creating files using the ```-print``` argument.
You can choosed to run only the preprocessor or compile using the ```-preprocess``` and ```-compile``` flags.
* example uses : * example uses :
````lua canc.lua foo.can```` ````canc foo.can````
preprocess and compile _foo.can_ and write the result in _foo.lua_. preprocess and compile _foo.can_ and write the result in _foo.lua_.
````lua canc.lua 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.
* ```can```
Start a simplisitic Candran REPL.
* ````canc [options] filename````
Preprocess, compile and run _filename_ using the options provided.
This will automatically register the Candran package searcher so required file will be compiled as they are needed.
This command will use error rewriting if enabled.
### Library usage ### Library usage
Candran can also be used as a Lua library. For example, Candran can also be used as a Lua library. For example,
````lua ````lua
@ -132,14 +190,70 @@ f:close()
local compiled = candran.make(contents, { lang = "fr" }) local compiled = candran.make(contents, { lang = "fr" })
load(compiled)() load(compiled)()
-- or simpler...
candran.dofile("foo.can")
```` ````
Will load Candran, read the file _foo.can_, compile its contents with the argument _lang_ set to _"fr"_, and then execute the result. Will load Candran, read the file _foo.can_, compile its contents with the argument _lang_ set to _"fr"_, and then execute the result.
The table returned by _require("candran")_ gives you access to : The table returned by _require("candran")_ gives you access to :
##### Compiler & preprocessor API
* ````candran.VERSION```` : Candran's version string. * ````candran.VERSION```` : Candran's version string.
* ````candran.preprocess(code[, args])```` : return the Candran code _code_, preprocessed with _args_ as argument table. * ````candran.preprocess(code[, options])```` : return the Candran code _code_, preprocessed with _options_ as options table.
* ````candran.compile(code[, target])```` : return the Candran code compiled to Lua. * ````candran.compile(code[, options])```` : return the Candran code compiled to Lua with _options_ as the option table.
* ````candran.make(code[, args])```` : return the Candran code, preprocessed with _args_ as argument table and compilled to Lua. * ````candran.make(code[, options])```` : return the Candran code, preprocessed and compiled with _options_ as options table.
##### Code loading helpers
* ```candran.loadfile(filepath, env, options)``` : Candran equivalent to the Lua 5.3's loadfile funtion. Will rewrite errors by default.
* ```candran.load(chunk, chunkname, env, options)``` : Candran equivalent to the Lua 5.3's load funtion. Will rewrite errors by default.
* ```candran.dofile(filepath, options)``` : Candran equivalent to the Lua 5.3's dofile funtion. Will rewrite errors by default.
#### Error rewriting
When using the command-line tools or the code loading helpers, Candran will automatically setup error rewriting: because the code is reformated when
compiled and preprocessed, lines numbers given by Lua in case of error are hardly usable. To fix that, Candran map each line from the compiled file to
the lines from the original file(s), inspired by MoonScript. Errors will be displayed as:
```
example.can:12(5): attempt to call a nil value (global 'iWantAnError')
```
12 is the line number in the original Candran file, and 5 is the line number in the compiled file.
If you are using the preprocessor ```import()``` function, the source Candran file and destination Lua file might not have the same name. In this case, the error will be:
```
example.can:12(final.lua:5): attempt to call a nil value (global 'iWantAnError')
```
* ```candran.messageHandler(message)``` : The error message handler used by Candran. Use it in xpcall to rewrite stacktraces to display Candran source file lines instead of compiled Lua lines.
##### Package searching helpers
Candran comes with a custom package searcher which will automatically find, preprocesses and compile ```.can``` files. If you want to use Candran in your project without worrying about
compiling the files, you can simply call
```lua
require("candran").setup()
```
at the top of your main Lua file. If a Candran is found when you call ```require()```, it will be automatically compiled and loaded. If both a Lua and Candran file match a module name, the Candran
file will be loaded.
* ```candran.searcher(modpath)``` : Candran package searcher function. Use the existing package.path.
* ```candran.setup()``` : Register the Candran package searcher.
##### Available compiler & preprocessor options
You can give arbitrary options which will be gived to the preprocessor, but Candran already provide and uses these with their associated default values:
```lua
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).
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().
```
### Compiling the library ### Compiling the library
The Candran library itself is written is Candran, so you have to compile it with an already compiled Candran library. The Candran library itself is written is Candran, so you have to compile it with an already compiled Candran library.
@ -149,9 +263,11 @@ The compiled _candran.lua_ should include every Lua library needed to run it. Yo
This command will use the precompilled version of this repository (candran.lua) to compile _candran.can_ and write the result in _candran.lua_ : This command will use the precompilled version of this repository (candran.lua) to compile _candran.can_ and write the result in _candran.lua_ :
```` ````
lua canc.lua candran.can canc candran.can
```` ````
The Candran build included in this repository were made using the ```mapLines=false``` options.
You can then run the tests on your build : You can then run the tests on your build :
```` ````

43
bin/can Normal file
View file

@ -0,0 +1,43 @@
#!/bin/lua
local candran = require("candran")
local cmdline = require("lib.cmdline")
local args = cmdline(arg)
if args.help or args.h then
print("Candran interpreter version "..candran.VERSION.." by Reuh")
print("Usage: "..arg[0].." [target=<target>] [options] filename")
return
end
if #args >= 1 then
candran.dofile(args[1], args)
else -- REPL
print("Candran " .. candran.VERSION)
candran.setup()
while true do
io.write("> ")
local line = io.read()
if line:match("^=") then
line = line:gsub("^=", "return tostring(") .. ")"
end
local p = dofile("lib/lua-parser/parser.lua")
local d = dofile("lib/lua-parser/pp.lua")
print(p.parse(line))
print(d.dump(p.parse(line)))
print(require"compiler.lua53"(p.parse(line)))
local t = { pcall(candran.load, line, "stdin") }
if t[1] == false then
print(t[2])
else
t = { pcall(t[2]) }
if t[1] == false then
print(t[2])
elseif #t > 1 then
print(unpack(t, 2))
end
end
end
end

View file

@ -1,10 +1,10 @@
#!/bin/lua #!/bin/lua
local candran = require("candran") local candran = require("candran")
local cmdline = require("cmdline") local cmdline = require("lib.cmdline")
if #arg < 1 then if #arg < 1 then
print("Candran compiler version "..candran.VERSION.." by Reuh") print("Candran compiler version "..candran.VERSION.." by Reuh")
print("Usage: "..arg[0].." [target=<target>] [dest=<destination directory>] [-print] [preprocessor arguments] filename...") print("Usage: "..arg[0].." [target=<target>] [dest=<destination directory>] [-print] [-preprocess] [-compile] [options] filename...")
return return
end end
@ -25,7 +25,20 @@ for _, file in ipairs(args) do
local input = inputFile:read("*a") local input = inputFile:read("*a")
inputFile:close() inputFile:close()
local out = candran.make(input, args) if args.chunkname == nil then
args.chunkname = file
end
local out = input
if args.preprocess then
out = candran.preprocess(out, args)
end
if args.compile then
out = candran.compile(out, args)
end
if args.compile == nil and args.preprocess == nil then
out = candran.make(input, args)
end
if args.print then if args.print then
print(out) print(out)

View file

@ -1,28 +1,45 @@
#import("util") #import("lib.util")
#import("lib.cmdline")
#import("compiler.lua53") #import("compiler.lua53")
#import("compiler.luajit") #import("compiler.luajit")
#import("lua-parser.scope")
#import("lua-parser.validator")
#import("lua-parser.pp")
#import("lua-parser.parser")
#import("cmdline")
local util = require("util") #import("lib.lua-parser.scope")
#import("lib.lua-parser.validator")
#import("lib.lua-parser.pp")
#import("lib.lua-parser.parser")
local candran = { local candran = {
VERSION = "0.2.0" VERSION = "0.3.0"
}
--- Default options.
local default = {
target = "lua53",
indentation = "",
newline = "\n",
requirePrefix = "CANDRAN_",
mapLines = true,
chunkname = "nil",
rewriteErrors = true
} }
--- Run the preprocessor --- Run the preprocessor
-- @tparam input string input code -- @tparam input string input code
-- @tparam args table arguments for the preprocessor. They will be inserted into the preprocessor environement. -- @tparam options table arguments for the preprocessor. They will be inserted into the preprocessor environement.
-- @treturn output string output code -- @treturn output string output code
function candran.preprocess(input, args={}) function candran.preprocess(input, options={})
options = util.merge(default, options)
-- generate preprocessor code -- generate preprocessor code
local preprocessor = "" local preprocessor = ""
local i = 0
for line in (input.."\n"):gmatch("(.-\n)") do for line in (input.."\n"):gmatch("(.-\n)") do
i += 1
if line:match("^%s*#") and not line:match("^#!") then -- exclude shebang if line:match("^%s*#") and not line:match("^#!") then -- exclude shebang
preprocessor ..= line:gsub("^%s*#", "") preprocessor ..= line:gsub("^%s*#", "")
elseif options.mapLines then
preprocessor ..= ("write(%q)"):format(line:sub(1, -2) .. " -- "..options.chunkname..":" .. i) .. "\n"
else else
preprocessor ..= ("write(%q)"):format(line:sub(1, -2)) .. "\n" preprocessor ..= ("write(%q)"):format(line:sub(1, -2)) .. "\n"
end end
@ -30,13 +47,7 @@ function candran.preprocess(input, args={})
preprocessor ..= "return output" preprocessor ..= "return output"
-- make preprocessor environement -- make preprocessor environement
local env = {} local env = util.merge(_G, options)
for k,v in pairs(_G) do
env[k] = v
end
for k, v in pairs(args) do
env[k] = v
end
--- Candran library table --- Candran library table
env.candran = candran env.candran = candran
--- Current preprocessor output --- Current preprocessor output
@ -45,15 +56,14 @@ function candran.preprocess(input, args={})
-- @tparam modpath string module path -- @tparam modpath string module path
-- @tparam margs table preprocessor arguments to use when preprocessessing the module -- @tparam margs table preprocessor arguments to use when preprocessessing the module
-- @tparam autoRequire[opt=true] boolean true to automatically load the module into a local variable -- @tparam autoRequire[opt=true] boolean true to automatically load the module into a local variable
env.import = function(modpath, margs=args, autoRequire=true) env.import = function(modpath, margs={}, autoRequire=true)
local filepath = assert(util.search(modpath), "No module named \""..modpath.."\"") local filepath = assert(util.search(modpath), "No module named \""..modpath.."\"")
-- open module file -- open module file
local f = io.open(filepath) local f = io.open(filepath)
if not f then error("Can't open the module file to import") end if not f then error("Can't open the module file to import") end
for k, v in pairs(args) do
if margs[k] == nil then margs[k] = v end margs = util.merge(options, { chunkname = filepath }, margs)
end
local modcontent = candran.preprocess(f:read("*a"), margs) local modcontent = candran.preprocess(f:read("*a"), margs)
f:close() f:close()
@ -92,57 +102,143 @@ function candran.preprocess(input, args={})
end end
-- compile & load preprocessor -- compile & load preprocessor
local preprocess, err = util.loadenv(candran.compile(preprocessor, args.target), "candran preprocessor", env) local preprocess, err = util.load(candran.compile(preprocessor, args), "candran preprocessor", env)
if not preprocess then error("Error while creating Candran preprocessor: " .. err) end if not preprocess then error("Error while creating Candran preprocessor: " .. err) end
-- execute preprocessor -- execute preprocessor
local success, output = pcall(preprocess) local success, output = pcall(preprocess)
if not success then error("Error while preprocessing file: " .. output .. "\nWith preprocessor : \n" .. preprocessor) end if not success then error("Error while preprocessing file: " .. output) end
return output return output
end end
-- Compiler --- Run the compiler
function candran.compile(input, target="lua53") -- @tparam input string input code
local parse = require("lua-parser.parser").parse -- @tparam options table options for the compiler
-- @treturn output string output code
function candran.compile(input, options={})
options = util.merge(default, options)
local ast, errmsg = parse(input, "candran") local ast, errmsg = parser.parse(input, "candran")
if not ast then if not ast then
error("Compiler: error while parsing file: "..errmsg) error("Compiler: error while parsing file: "..errmsg)
end end
return require("compiler."..target)(ast) return require("compiler."..options.target)(input, ast, options)
end end
-- Preprocess & compile --- Preprocess & compile code
function candran.make(code, args={}) -- @tparam code string input code
return candran.compile(candran.preprocess(code, args), args.target) -- @tparam options table arguments for the preprocessor and compiler
-- @treturn output string output code
function candran.make(code, options)
return candran.compile(candran.preprocess(code, options), options)
end end
function candran.searcher(modpath) local errorRewritingActive = false
-- get module filepath local codeCache = {}
local notfound = "" --- Candran equivalent to the Lua 5.3's loadfile funtion.
local filepath -- Will rewrite errors by default.
for path in package.path:gsub("%.lua", ".can"):gmatch("[^;]+") do function candran.loadfile(filepath, env, options)
local path = path:gsub("%?", (modpath:gsub("%.", "/"))) local f, err = io.open(filepath)
local f = io.open(path) if not f then error("can't open the file: "..err) end
if f then local content = f:read("*a")
f:close()
filepath = path
else
notfound = notfound .. "\n\tno Candran file '"..path.."'"
end
end
if not filepath then return notfound end
-- open module file
local f = io.open(filepath)
if not f then error("Can't open the module file to import") end
local modcontent = f:read("*a")
f:close() f:close()
return load(candran.make(modcontent)) return candran.load(content, filepath, env, options)
end
--- Candran equivalent to the Lua 5.3's load funtion.
-- Will rewrite errors by default.
function candran.load(chunk, chunkname, env, options={})
options = util.merge({ chunkname = tostring(chunkname or chunk) }, options)
codeCache[options.chunkname] = candran.make(chunk, options)
local f = util.load(codeCache[options.chunkname], options.chunkname, env)
if options.rewriteErrors == false then
return f
else
return function()
if not errorRewritingActive then
errorRewritingActive = true
local t = { xpcall(f, candran.messageHandler) }
errorRewritingActive = false
if t[1] == false then
error(t[2], 0)
end
return unpack(t, 2)
else
return f()
end
end
end
end
--- Candran equivalent to the Lua 5.3's dofile funtion.
-- Will rewrite errors by default.
function candran.dofile(filename, options)
return candran.loadfile(filename, nil, options)()
end
--- Candran error message handler.
-- Use it in xpcall to rewrite stacktraces to display Candran source file lines instead of compiled Lua lines.
function candran.messageHandler(message)
return debug.traceback(message, 2):gsub("(\n?%s*)([^\n]-)%:(%d+)%:", function(indentation, source, line)
line = tonumber(line)
local originalFile
local strName = source:match("%[string \"(.-)\"%]")
if strName then
if codeCache[strName] then
originalFile = codeCache[strName]
source = strName
end
else
local fi = io.open(source, "r")
if fi then
originalFile = fi:read("*a")
end
fi:close()
end
if originalFile then
local i = 0
for l in originalFile:gmatch("([^\n]*)") do
i = i +1
if i == line then
local extSource, lineMap = l:match("%-%- (.-)%:(%d+)$")
if lineMap then
if extSource ~= source then
return indentation .. extSource .. ":" .. lineMap .. "(" .. extSource .. ":" .. line .. "):"
else
return indentation .. extSource .. ":" .. lineMap .. "(" .. line .. "):"
end
end
break
end
end
end
end)
end
--- Candran package searcher function. Use the existing package.path.
function candran.searcher(modpath)
local filepath = util.search(modpath)
if not filepath then
return "\n\tno candran file in package.path"
end
return candran.loadfile(filepath)
end
--- Register the Candran package searcher.
function candran.setup()
if _VERSION == "Lua 5.1" then
table.insert(package.loaders, 2, candran.searcher)
else
table.insert(package.searchers, 2, candran.searcher)
end
end end
return candran return candran

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,30 @@
return function(ast, opts) return function(code, ast, options)
local options = { local lastInputPos = 1
indentation = "\t", local prevLinePos = 1
newline = "\n", local lastSource = "nil"
requirePrefix = "CANDRAN_" local lastLine = 1
}
local indentLevel = 0 local indentLevel = 0
local function newline() local function newline()
return options.newline .. string.rep(options.indentation, indentLevel) local r = options.newline .. string.rep(options.indentation, indentLevel)
if options.mapLines then
local sub = code:sub(lastInputPos)
local source, line = sub:sub(1, sub:find("\n")):match("%-%- (.-)%:(%d+)\n")
if source and line then
lastSource = source
lastLine = tonumber(line)
else
for _ in code:sub(prevLinePos, lastInputPos):gmatch("\n") do
lastLine += 1
end
end
prevLinePos = lastInputPos
r = " -- " .. lastSource .. ":" .. lastLine .. r
end
return r
end end
local function indent() local function indent()
indentLevel += 1 indentLevel += 1
@ -32,6 +49,9 @@ return function(ast, opts)
local tags local tags
local function lua(ast, forceTag, ...) local function lua(ast, forceTag, ...)
if options.mapLines and ast.pos then
lastInputPos = ast.pos
end
return tags[forceTag or ast.tag](ast, ...) return tags[forceTag or ast.tag](ast, ...)
end end
@ -54,14 +74,30 @@ return function(ast, opts)
Do = function(t) Do = function(t)
return "do" .. indent() .. lua(t, "Block") .. unindent() .. "end" return "do" .. indent() .. lua(t, "Block") .. unindent() .. "end"
end, end,
-- Set{ {lhs+} opid? {expr+} } -- Set{ {lhs+} (opid? = opid?)? {expr+} }
Set = function(t) Set = function(t)
if #t == 2 then if #t == 2 then
return lua(t[1], "_lhs") .. " = " .. lua(t[2], "_lhs") return lua(t[1], "_lhs") .. " = " .. lua(t[2], "_lhs")
else elseif #t == 3 then
local r = lua(t[1], "_lhs") .. " = " .. lua({ t[2], t[1][1], t[3][1] }, "Op") return lua(t[1], "_lhs") .. " = " .. lua(t[3], "_lhs")
for i=2, math.min(#t[3], #t[1]), 1 do elseif #t == 4 then
r = r .. ", " .. lua({ t[2], t[1][i], t[3][i] }, "Op") 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")
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")
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")
end end
return r return r
end end
@ -274,12 +310,5 @@ return function(ast, opts)
#placeholder("patch") #placeholder("patch")
if opts then return requireStr .. lua(ast) .. newline()
for k, v in pairs(opts) do
options[k] = v
end
end
local r = lua(ast)
return requireStr .. r
end end

View file

@ -48,6 +48,8 @@ Parameters:
example: var1,var2=$$125 --> var1,var2 = "$125","$125" example: var1,var2=$$125 --> var1,var2 = "$125","$125"
* if value is convertible to number, it is a number * if value is convertible to number, it is a number
example: var1,var2=125 --> var1,var2 = 125,125 example: var1,var2=125 --> var1,var2 = 125,125
* if value is true of false, it is a boolean
example: var1=false --> var1 = false
* otherwise it is a string * otherwise it is a string
example: name=John --> name = "John" example: name=John --> name = "John"
@ -86,7 +88,13 @@ return function(t_in, options, params)
elseif v:find("=") then elseif v:find("=") then
local ids, val = v:match("^([^=]+)%=(.*)") -- no space around = local ids, val = v:match("^([^=]+)%=(.*)") -- no space around =
if not ids then return argerror("invalid assignment syntax", i) end if not ids then return argerror("invalid assignment syntax", i) end
val = val:sub(1,1)=="$" and val:sub(2) or tonumber(val) or val if val == "false" then
val = false
elseif val == "true" then
val = true
else
val = val:sub(1,1)=="$" and val:sub(2) or tonumber(val) or val
end
for id in ids:gmatch"[^,;]+" do for id in ids:gmatch"[^,;]+" do
if not idcheck(id) then return iderror(i) end if not idcheck(id) then return iderror(i) end
t_out[id] = val t_out[id] = val

View file

@ -8,7 +8,7 @@ block: { stat* }
stat: stat:
`Do{ stat* } `Do{ stat* }
| `Set{ {lhs+} {expr+} } -- lhs1, lhs2... = e1, e2... | `Set{ {lhs+} (opid? = opid?)? {expr+} } -- lhs1, lhs2... op=op e1, e2...
| `While{ expr block } -- while e do b end | `While{ expr block } -- while e do b end
| `Repeat{ block expr } -- repeat b until e | `Repeat{ block expr } -- repeat b until e
| `If{ (expr block)+ block? } -- if e1 then b1 [elseif e2 then b2] ... [else bn] end | `If{ (expr block)+ block? } -- if e1 then b1 [elseif e2 then b2] ... [else bn] end
@ -28,7 +28,7 @@ expr:
| `Boolean{ <boolean> } | `Boolean{ <boolean> }
| `Number{ <number> } | `Number{ <number> }
| `String{ <string> } | `String{ <string> }
| `Function{ { `Id{ <string> }* `Dots? } block } | `Function{ { ( `ParPair{ Id expr } | `Id{ <string> } )* `Dots? } block }
| `Table{ ( `Pair{ expr expr } | expr )* } | `Table{ ( `Pair{ expr expr } | expr )* }
| `Op{ opid expr expr? } | `Op{ opid expr expr? }
| `Paren{ expr } -- significant to cut multiple values returns | `Paren{ expr } -- significant to cut multiple values returns
@ -257,6 +257,14 @@ local function makeIndexOrCall (t1, t2)
return { tag = "Index", pos = t1.pos, [1] = t1, [2] = t2[1] } return { tag = "Index", pos = t1.pos, [1] = t1, [2] = t2[1] }
end end
local function fixAnonymousMethodParams(t1, t2)
if t1 == ":" then
t1 = t2
table.insert(t1, 1, { tag = "Id", "self" })
end
return t1
end
-- grammar -- grammar
local G = { V"Lua", local G = { V"Lua",
Lua = V"Shebang"^-1 * V"Skip" * V"Block" * expect(P(-1), "Extra"); Lua = V"Shebang"^-1 * V"Skip" * V"Block" * expect(P(-1), "Extra");
@ -288,7 +296,7 @@ local G = { V"Lua",
LocalStat = kw("local") * expect(V"LocalFunc" + V"LocalAssign", "DefLocal"); LocalStat = kw("local") * expect(V"LocalFunc" + V"LocalAssign", "DefLocal");
LocalFunc = tagC("Localrec", kw("function") * expect(V"Id", "NameLFunc") * V"FuncBody") / fixFuncStat; LocalFunc = tagC("Localrec", kw("function") * expect(V"Id", "NameLFunc") * V"FuncBody") / fixFuncStat;
LocalAssign = tagC("Local", V"NameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc()))); LocalAssign = tagC("Local", V"NameList" * (sym("=") * expect(V"ExprList", "EListLAssign") + Ct(Cc())));
Assignment = tagC("Set", V"VarList" * V"AssignmentOp" * expect(V"ExprList", "EListAssign")); 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; FuncStat = tagC("Set", kw("function") * expect(V"FuncName", "FuncName") * V"FuncBody") / fixFuncStat;
FuncName = Cf(V"Id" * (sym(".") * expect(V"StrId", "NameFunc1"))^0, insertIndex) FuncName = Cf(V"Id" * (sym(".") * expect(V"StrId", "NameFunc1"))^0, insertIndex)
@ -342,13 +350,20 @@ local G = { V"Lua",
VarExpr = Cmt(V"SuffixedExpr", function(s, i, exp) return exp.tag == "Id" or exp.tag == "Index", exp end); VarExpr = Cmt(V"SuffixedExpr", function(s, i, exp) return exp.tag == "Id" or exp.tag == "Index", exp end);
SuffixedExpr = Cf(V"PrimaryExpr" * (V"Index" + V"Call")^0, makeIndexOrCall); SuffixedExpr = Cf(V"PrimaryExpr" * (V"Index" + V"Call")^0, makeIndexOrCall);
PrimaryExpr = V"Id" + tagC("Paren", sym("(") * expect(V"Expr", "ExprParen") * expect(sym(")"), "CParenExpr")); PrimaryExpr = V"SelfId" * (V"SelfCall" + V"SelfIndex")
+ V"Id"
+ tagC("Paren", sym("(") * expect(V"Expr", "ExprParen") * expect(sym(")"), "CParenExpr"));
Index = tagC("DotIndex", sym("." * -P".") * expect(V"StrId", "NameIndex")) Index = tagC("DotIndex", sym("." * -P".") * expect(V"StrId", "NameIndex"))
+ tagC("ArrayIndex", sym("[" * -P(S"=[")) * expect(V"Expr", "ExprIndex") * expect(sym("]"), "CBracketIndex")); + tagC("ArrayIndex", sym("[" * -P(S"=[")) * expect(V"Expr", "ExprIndex") * expect(sym("]"), "CBracketIndex"));
Call = tagC("Invoke", Cg(sym(":" * -P":") * expect(V"StrId", "NameMeth") * expect(V"FuncArgs", "MethArgs"))) Call = tagC("Invoke", Cg(sym(":" * -P":") * expect(V"StrId", "NameMeth") * expect(V"FuncArgs", "MethArgs")))
+ tagC("Call", V"FuncArgs"); + tagC("Call", V"FuncArgs");
SelfIndex = tagC("DotIndex", V"StrId");
SelfCall = tagC("Invoke", Cg(V"StrId" * V"FuncArgs"));
FuncDef = kw("function") * V"FuncBody"; ShortFuncDef = tagC("Function", V"ShortFuncParams" * V"Block" * expect(kw("end"), "EndFunc"));
ShortFuncParams = (sym(":") / ":")^-1 * sym("(") * V"ParList" * sym(")") / fixAnonymousMethodParams;
FuncDef = (kw("function") * V"FuncBody") + V"ShortFuncDef";
FuncArgs = sym("(") * commaSep(V"Expr", "ArgList")^-1 * expect(sym(")"), "CParenArgs") FuncArgs = sym("(") * commaSep(V"Expr", "ArgList")^-1 * expect(sym(")"), "CParenArgs")
+ V"Table" + V"Table"
+ tagC("String", V"String"); + tagC("String", V"String");
@ -361,7 +376,8 @@ local G = { V"Lua",
+ V"StrId" * #("=" * -P"="); + V"StrId" * #("=" * -P"=");
FieldSep = sym(",") + sym(";"); FieldSep = sym(",") + sym(";");
Id = tagC("Id", V"Name"); SelfId = tagC("Id", sym"@" / "self");
Id = tagC("Id", V"Name") + V"SelfId";
StrId = tagC("String", V"Name"); StrId = tagC("String", V"Name");
-- lexer -- lexer
@ -455,12 +471,12 @@ local G = { V"Lua",
+ sym("#") / "len" + sym("#") / "len"
+ sym("~") / "bnot"; + sym("~") / "bnot";
PowOp = sym("^") / "pow"; PowOp = sym("^") / "pow";
AssignmentOp = (V"OrOp" + V"AndOp" + V"BOrOp" + V"BXorOp" + V"BAndOp" + V"ShiftOp" + V"ConcatOp" + V"AddOp" + V"MulOp" + V"PowOp")^-1 * sym("=") BinOp = V"OrOp" + V"AndOp" + V"BOrOp" + V"BXorOp" + V"BAndOp" + V"ShiftOp" + V"ConcatOp" + V"AddOp" + V"MulOp" + V"PowOp";
} }
local parser = {} local parser = {}
local validator = require("lua-parser.validator") local validator = require("lib.lua-parser.validator")
local validate = validator.validate local validate = validator.validate
local syntaxerror = validator.syntaxerror local syntaxerror = validator.syntaxerror

View file

@ -1,7 +1,7 @@
--[[ --[[
This module impements a validator for the AST This module impements a validator for the AST
]] ]]
local scope = require "lua-parser.scope" local scope = require "lib.lua-parser.scope"
local lineno = scope.lineno local lineno = scope.lineno
local new_scope, end_scope = scope.new_scope, scope.end_scope local new_scope, end_scope = scope.new_scope, scope.end_scope

View file

@ -13,14 +13,28 @@ function util.search(modpath, exts={"can", "lua"})
end end
end end
function util.loadenv(str, name, env) function util.load(str, name, env)
if _VERSION == "Lua 5.1" then if _VERSION == "Lua 5.1" then
local fn, err = loadstring(str, name) local fn, err = loadstring(str, name)
if not fn then return fn, err end if not fn then return fn, err end
return env ~= nil and setfenv(fn, env) or fn return env ~= nil and setfenv(fn, env) or fn
else else
return load(str, name, nil, env) if env then
return load(str, name, nil, env)
else
return load(str, name)
end
end end
end end
function util.merge(...)
local r = {}
for _, t in ipairs({...}) do
for k, v in pairs(t) do
r[k] = v
end
end
return r
end
return util return util

View file

@ -0,0 +1,37 @@
package = "Candran"
version = "0.3.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.
]],
license = "MIT",
homepage = "https://github.com/Reuh/Candran",
issues_url = "https://github.com/Reuh/Candran",
maintener = "Étienne 'Reuh' Fildadut <fildadut@reuh.eu>",
--labels = {}
}
source = {
url = "git://github.com/Reuh/Candran",
tag = "v0.3.0"
}
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" }
}