mirror of
https://github.com/Reuh/candran.git
synced 2025-10-27 17:59:30 +00:00
Compare commits
22 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d9a79c47d | |||
| 1e118381f8 | |||
| 7131c5c8b1 | |||
| f0cacd6f08 | |||
| 7b0563b9dc | |||
| 56f1901f9b | |||
| b32d4d5600 | |||
| 53f715cc6f | |||
| d4102f1af6 | |||
| 008e7732bc | |||
| 01e808e2e6 | |||
| ff6d7f8feb | |||
| e9ae8e21a3 | |||
| dd22f2de3d | |||
| b72aff807c | |||
| ea54376aa6 | |||
| 496a4ddafd | |||
| a0eda5bc72 | |||
| 3c84d1fbdd | |||
| 584dac17db | |||
| 6a1f745015 | |||
| ebd36d7103 |
18 changed files with 6200 additions and 5082 deletions
111
README.md
111
README.md
|
|
@ -6,12 +6,19 @@ Unlike Moonscript, Candran tries to stay close to the Lua syntax, and existing L
|
||||||
|
|
||||||
````lua
|
````lua
|
||||||
#import("lib.thing") -- static import
|
#import("lib.thing") -- static import
|
||||||
#local debug or= false
|
#local DEBUG = false
|
||||||
|
|
||||||
|
#if DEBUG then
|
||||||
|
# define("log(...)", "print(...)") -- macro: calls to log() will be replaced with print() in compiled code
|
||||||
|
#else
|
||||||
|
# define("log(...)", "") -- remove calls to log from the compiled code when DEBUG is true
|
||||||
|
#end
|
||||||
|
log("example macro") -- preprocessor macros
|
||||||
|
|
||||||
local function calculate(toadd=25) -- default parameters
|
local function calculate(toadd=25) -- default parameters
|
||||||
local result = thing.do()
|
local result = thing.do()
|
||||||
result += toadd
|
result += toadd
|
||||||
#if debug then -- preprocessor conditionals
|
#if DEBUG then -- preprocessor conditionals
|
||||||
print("Did something")
|
print("Did something")
|
||||||
#end
|
#end
|
||||||
return result
|
return result
|
||||||
|
|
@ -76,7 +83,7 @@ Candran is released under the MIT License (see ```LICENSE``` for details).
|
||||||
#### Quick setup
|
#### Quick setup
|
||||||
Install Candran automatically using LuaRocks: ```sudo luarocks install candran```.
|
Install Candran automatically using LuaRocks: ```sudo luarocks install candran```.
|
||||||
|
|
||||||
Or manually install LPegLabel (```luarocks install lpeglabel```, version 1.5 or above), 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 and argparse (```luarocks install lpeglabel```, version 1.5 or above, and ```luarocks install argparse```, version 0.7 or above), 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 optionally install lua-linenoise (```luarocks install linenoise```, version 0.9 or above) for an improved REPL, and luacheck (```luarocks install luacheck```, version 0.23.0 or above) to be able to use ```cancheck```. Installing Candran using LuaRocks will install linenoise and luacheck by default.
|
You can optionally install lua-linenoise (```luarocks install linenoise```, version 0.9 or above) for an improved REPL, and luacheck (```luarocks install luacheck```, version 0.23.0 or above) to be able to use ```cancheck```. Installing Candran using LuaRocks will install linenoise and luacheck by default.
|
||||||
|
|
||||||
|
|
@ -90,6 +97,7 @@ Most editors should be able to use their existing Lua support for Candran code.
|
||||||
* [sublime-candran](https://github.com/Reuh/sublime-candran) support the full Candran syntax
|
* [sublime-candran](https://github.com/Reuh/sublime-candran) support the full Candran syntax
|
||||||
* [SublimeLinter-cancheck-contrib](https://github.com/Reuh/SublimeLinter-contrib-cancheck) SublimeLinter plugin for Candran using ```cancheck```
|
* [SublimeLinter-cancheck-contrib](https://github.com/Reuh/SublimeLinter-contrib-cancheck) SublimeLinter plugin for Candran using ```cancheck```
|
||||||
* [SublimeLinter-candran-contrib](https://github.com/Reuh/SublimeLinter-contrib-candran) SublimeLinter plugin for Candran using ```canc -parse``` (only checks for syntaxic errors, no linting)
|
* [SublimeLinter-candran-contrib](https://github.com/Reuh/SublimeLinter-contrib-candran) SublimeLinter plugin for Candran using ```canc -parse``` (only checks for syntaxic errors, no linting)
|
||||||
|
* **VS Code**: [vscode-candran](https://github.com/Reuh/vscode-candran) basic support for the Candran syntax
|
||||||
* **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
|
||||||
|
|
||||||
For linting, if your editor support [luacheck](https://github.com/luarocks/luacheck), you should be able to replace it with ```cancheck``` (in this repository ```bin/cancheck```, or installed automatically if Candran was installed using LuaRocks), which is a wrapper around luacheck that monkey-patch it to support Candran.
|
For linting, if your editor support [luacheck](https://github.com/luarocks/luacheck), you should be able to replace it with ```cancheck``` (in this repository ```bin/cancheck```, or installed automatically if Candran was installed using LuaRocks), which is a wrapper around luacheck that monkey-patch it to support Candran.
|
||||||
|
|
@ -442,15 +450,53 @@ Will output ````print("Bonjour")```` or ````print("Hello")```` depending of the
|
||||||
The preprocessor has access to the following variables:
|
The preprocessor has access to the following variables:
|
||||||
* ````candran````: the Candran library table.
|
* ````candran````: the Candran library table.
|
||||||
* ````output````: the current preprocessor output string. Can be redefined at any time. If you want to write something in the preprocessor output, it is preferred to use `write(...)` instead of directly modifying `output`.
|
* ````output````: the current preprocessor output string. Can be redefined at any time. If you want to write something in the preprocessor output, it is preferred to use `write(...)` instead of directly modifying `output`.
|
||||||
* ````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:
|
* ````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. Macros and preprocessor constants defined in the imported file (using `define` and `set`) will be made available 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")```)
|
* ```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).
|
* ```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.
|
* ````include(filename)````: a function which copy the contents of the file _filename_ to the output.
|
||||||
* ````write(...)````: write to the preprocessor output. For example, ````#write("hello()")```` will output ````hello()```` in the final file.
|
* ````write(...)````: write to the preprocessor output. For example, ````#write("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.
|
* ```placeholder(name)```: if the variable _name_ is defined in the preprocessor environement, its content will be inserted here.
|
||||||
|
* ```define(identifier, replacement)```: define a macro. See below.
|
||||||
|
* ```set(identifier, value)```: set a preprocessor constant.
|
||||||
* each arguments passed to the preprocessor is directly available in the environment.
|
* each arguments passed to the preprocessor is directly available in the environment.
|
||||||
* and every standard Lua library.
|
* and every standard Lua library.
|
||||||
|
|
||||||
|
#### Macros
|
||||||
|
|
||||||
|
Using `define(identifier, replacement)` in the preprocessor, you can define macros. `identifier` is expected to be string containing Candran/Lua code (representing either a identifier or a function call), and `replacement` can be either a string containing Candran/Lua code or a function.
|
||||||
|
|
||||||
|
There are two types of macros identifiers: variables, which replace every instance of the given identifier with the replacement; and functions, which will replace every call to this function with the replacement, also replacing its arguments. The `...` will be replaced with every remaining argument. Macros can not be recursive.
|
||||||
|
|
||||||
|
If `replacement` is a string, the macro will be replaced with this string, replacing the macros arguments in the string. If `replacement` is a function, the function will be called every time the macro is encoutered, with the macro arguments passed as strings, and is expected to return a string that will be used as a replacement.
|
||||||
|
|
||||||
|
If `replacement` is the empty empty, the macro will simply be removed from the compiled code.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Variable macro
|
||||||
|
#define("x", 42)
|
||||||
|
print(x) -- 42
|
||||||
|
|
||||||
|
-- Function macros
|
||||||
|
#define("f(x)", "print(x)")
|
||||||
|
f(42) -- replaced with print(42)
|
||||||
|
|
||||||
|
#define("log(s, ...)", "print(s..": ", ...)")
|
||||||
|
log("network", "error") -- network: error
|
||||||
|
|
||||||
|
#define("debug()", "")
|
||||||
|
debug() -- not present in complied code
|
||||||
|
|
||||||
|
#define("_assert(what, err)", function(what, err)
|
||||||
|
# return "if "..what.." then error("..err..") end"
|
||||||
|
#end)
|
||||||
|
_assert(5 = 2, "failed") -- replaced with if 5 = 2 then error("failed") end
|
||||||
|
```
|
||||||
|
|
||||||
|
Candran provide some predefined macros by default:
|
||||||
|
* `__STR__(expr)`: returns a string litteral representing the expression (e.g., `__STR__(5 + 2)` expands to `"5 + 2"`)
|
||||||
|
* `__CONSTEXPR__(expr)`: calculate the result of the expression in the preprocessor, and returns a representation of the returned value, i.e. precalculate an expression at compile time
|
||||||
|
You can disable these built-in macros using the `builtInMacros` compiler option.
|
||||||
|
|
||||||
Compile targets
|
Compile targets
|
||||||
---------------
|
---------------
|
||||||
Candran is based on the Lua 5.4 syntax, but can be compiled to Lua 5.4, Lua 5.3, Lua 5.2, LuaJIT, and Lua 5.1 compatible code.
|
Candran is based on the Lua 5.4 syntax, but can be compiled to Lua 5.4, Lua 5.3, Lua 5.2, LuaJIT, and Lua 5.1 compatible code.
|
||||||
|
|
@ -482,23 +528,41 @@ The library can be used standalone through the ```canc``` (for compiling Candran
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
_options_ 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 ````--no-map-lines -p --include module -d VAR 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 `--destination` or `-d` option: ```--destination destinationDirectory```.
|
||||||
|
|
||||||
You can choose the output filename using ```out=filename```. By default, compiled files have the same name as their input file, but with a ```.lua``` extension.
|
You can choose the output filename using `--output` or `-o` option: `--output filename`. By default, compiled files have the same name as their input file, but with a ```.lua``` extension.
|
||||||
|
|
||||||
```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``` or `-p` argument.
|
||||||
|
|
||||||
You can choose to run only the preprocessor or compile using the ```-preprocess``` and ```-compile``` flags.
|
You can choose to run only the preprocessor or compile using the ```--preprocess``` and ```--compile``` flags.
|
||||||
|
|
||||||
You can choose to only parse the file and check it for syntaxic errors using the ```-parse``` flag. Errors will be printed to stderr in a similar format to ```luac -p```.
|
You can choose to only parse the file and check it for syntaxic errors using the ```--parse``` flag. Errors will be printed to stderr in a similar format to ```luac -p```.
|
||||||
|
|
||||||
The ```-ast``` flag is also available for debugging, and will disable preprocessing, compiling and file writing, and instead directly dump the AST generated from the input file(s) to stdout.
|
The ```--ast``` flag is also available for debugging, and will disable preprocessing, compiling and file writing, and instead directly dump the AST generated from the input file(s) to stdout.
|
||||||
|
|
||||||
Instead of providing filenames, you can use ```-``` to read from standard input.
|
Instead of providing filenames, you can use ```-``` to read from standard input.
|
||||||
|
|
||||||
Use the ```-h``` or ```-help``` option to display a short help text.
|
You can change the compiler target using `--target` or `-t`: `--target luajit`.
|
||||||
|
|
||||||
|
You can change the identation and newline string using `--indentation` and `--newline`: `--identation luajit`.
|
||||||
|
|
||||||
|
You can change Candran's built-in variable prefix using `--variable-prefix`: `--variable-prefix __CAN_`.
|
||||||
|
|
||||||
|
You can disable line mapping (error rewriting will not work) using `--no-map-lines`.
|
||||||
|
|
||||||
|
You can disable built-in macros using `--no-builtin-macros`.
|
||||||
|
|
||||||
|
You can define preprocessor constants using `--define` or `-D`: `--define VAR 5`. `VAR` will be available and set to 5 in the preprocessor. If you specify no value, it defaults to true.
|
||||||
|
|
||||||
|
You can statically import modules using `--import` or `-I`: `--import module`. The module will be imported in compiled files using `#import("module",{loadLocal=false})`.
|
||||||
|
|
||||||
|
You can disable error rewriting using `--no-rewrite-errors`.
|
||||||
|
|
||||||
|
You can change the chunkname using `--chunkname`: `--chunkname filename`. This will change the filenames are reported in errors. By default, try to use the current file name, or stdin when using `-`.
|
||||||
|
|
||||||
|
Use the ```-h``` or ```--help``` option to display the help text.
|
||||||
|
|
||||||
Example uses:
|
Example uses:
|
||||||
|
|
||||||
|
|
@ -506,15 +570,15 @@ The library can be used standalone through the ```canc``` (for compiling Candran
|
||||||
|
|
||||||
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````
|
* ````canc --indentation " " foo.can````
|
||||||
|
|
||||||
preprocess and compile _foo.can_ with 2-space indentation (readable code!) and write the result in _foo.lua_.
|
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 -d verbose --print | lua````
|
||||||
|
|
||||||
preprocess _foo.can_ with _verbose_ set to _true_, compile it and execute it.
|
preprocess _foo.can_ with _verbose_ set to _true_ in the preprocessor, compile it and execute it.
|
||||||
|
|
||||||
* ````canc -parse foo.can````
|
* ````canc --parse foo.can````
|
||||||
|
|
||||||
checks foo.can for syntaxic errors.
|
checks foo.can for syntaxic errors.
|
||||||
|
|
||||||
|
|
@ -534,7 +598,9 @@ The library can be used standalone through the ```canc``` (for compiling Candran
|
||||||
|
|
||||||
Instead of providing a filename, you can use ```-``` to read from standard input.
|
Instead of providing a filename, you can use ```-``` to read from standard input.
|
||||||
|
|
||||||
Use the ```-h``` or ```-help``` option to display a short help text.
|
Use similar options as `canc`.
|
||||||
|
|
||||||
|
Use the ```-h``` or ```-help``` option to display the help text.
|
||||||
|
|
||||||
* ```cancheck```
|
* ```cancheck```
|
||||||
|
|
||||||
|
|
@ -551,7 +617,7 @@ local f = io.open("foo.can") -- read the file foo.can
|
||||||
local contents = f:read("*a")
|
local contents = f:read("*a")
|
||||||
f:close()
|
f:close()
|
||||||
|
|
||||||
local compiled = candran.make(contents, { debug = true }) -- compile foo.can with debug set to true
|
local compiled = candran.make(contents, { DEBUG = true }) -- compile foo.can with DEBUG set to true
|
||||||
|
|
||||||
load(compiled)() -- execute!
|
load(compiled)() -- execute!
|
||||||
|
|
||||||
|
|
@ -567,8 +633,8 @@ The table returned by _require("candran")_ gives you access to:
|
||||||
|
|
||||||
##### Compiler & preprocessor
|
##### Compiler & preprocessor
|
||||||
* ````candran.VERSION````: Candran's version string (e.g. `"0.10.0"`).
|
* ````candran.VERSION````: Candran's version string (e.g. `"0.10.0"`).
|
||||||
* ````candran.preprocess(code[, options])````: return the Candran code _code_, preprocessed with the _options_ options table; or nil, err in case of error.
|
* ````candran.preprocess(code[, options])````: return the Candran code _code_, `macros` table. The code is preprocessed with the _options_ options table; `macros` is indented to be passed to `candran.compile` to apply the defined macros. In case of error, returns nil, error.
|
||||||
* ````candran.compile(code[, options])````: return the Candran code compiled to Lua with the _options_ option table; or nil, err in case of error.
|
* ````candran.compile(code[, options[, macros]])````: return the Candran code compiled to Lua with the _options_ option table and the macros `macros` (table returned by the preprocessor); or nil, err in case of error.
|
||||||
* ````candran.make(code[, options])````: return the Candran code, preprocessed and compiled with the _options_ options table; or nil, err in case of error.
|
* ````candran.make(code[, options])````: return the Candran code, preprocessed and compiled with the _options_ options table; or nil, err in case of error.
|
||||||
|
|
||||||
##### Code loading helpers
|
##### Code loading helpers
|
||||||
|
|
@ -619,13 +685,16 @@ at the top of your main Lua file. If a Candran file is found when you call ```re
|
||||||
You can give arbitrary options to the compiler and preprocessor, but Candran already provide and uses these with their associated default values:
|
You can give arbitrary options to the compiler and preprocessor, but Candran already provide and uses these with their associated default values:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
target = "lua53" -- compiler target. "lua53", "lua52", "luajit" or "lua51" (default is automatically selected based on the Lua version used).
|
target = "lua53" -- compiler target. "lua54", "lua53", "lua52", "luajit" or "lua51" (default is automatically selected based on the Lua version used).
|
||||||
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.
|
||||||
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).
|
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().
|
||||||
|
builtInMacros = true -- false to disable built-in macros __*__
|
||||||
|
preprocessorEnv = {} -- environment to merge with the preprocessor environement
|
||||||
|
import = {} -- list of modules to automatically import in compiled files (using #import("module",{loadLocal=false}))
|
||||||
```
|
```
|
||||||
|
|
||||||
You can change the defaults used for these variables in the table `candran.default`.
|
You can change the defaults used for these variables in the table `candran.default`.
|
||||||
|
|
|
||||||
71
bin/can
71
bin/can
|
|
@ -1,28 +1,31 @@
|
||||||
#!/usr/bin/env lua
|
#!/usr/bin/env lua
|
||||||
|
|
||||||
local candran = require("candran").setup()
|
local candran = require("candran").setup()
|
||||||
local cmdline = require("candran.cmdline")
|
local util = require("candran.util")
|
||||||
|
local argparse = require("argparse")
|
||||||
|
local unpack = unpack or table.unpack
|
||||||
|
|
||||||
local args = cmdline(arg)
|
-- Parse args --
|
||||||
|
|
||||||
if args.help or args.h then
|
local parser = argparse()
|
||||||
print("Candran "..candran.VERSION.." interpreter by Reuh")
|
:name "can"
|
||||||
print("Usage: "..arg[0].." [options] filename")
|
:description("Candran "..candran.VERSION.." interpreter by Reuh.")
|
||||||
print("Specify no options to start the REPL.")
|
:epilog "For more info, see https://github.com/Reuh/candran"
|
||||||
print("Use - instead of a filename to read from the standard input.")
|
|
||||||
print("Interpreter options:")
|
parser:argument("filename", "Candran file to run. Use - to read from standard input. Start the REPL if no filename given.")
|
||||||
print(" -help or -h print this text")
|
:args "?"
|
||||||
print("Default options:")
|
|
||||||
for opt, val in pairs(candran.default) do
|
util.cli.addCandranOptions(parser)
|
||||||
if type(val) == "string" then val = val:gsub("\n", "\\n") end
|
|
||||||
print((" %s=%q"):format(opt, val))
|
local args = parser:parse()
|
||||||
end
|
|
||||||
return
|
local options = util.cli.makeCandranOptions(args)
|
||||||
end
|
|
||||||
|
-- Run --
|
||||||
|
|
||||||
-- stdin
|
-- stdin
|
||||||
if arg[#arg] == "-" then
|
if args.filename == "-" then
|
||||||
local f, err = candran.load(io.read("*a"), "stdin", nil, args)
|
local f, err = candran.load(io.read("*a"), "stdin", nil, options)
|
||||||
if not f then
|
if not f then
|
||||||
io.stderr:write("can: "..err.."\n")
|
io.stderr:write("can: "..err.."\n")
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
|
|
@ -33,8 +36,8 @@ if arg[#arg] == "-" then
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
end
|
end
|
||||||
-- file
|
-- file
|
||||||
elseif #args >= 1 then
|
elseif args.filename then
|
||||||
local f, err = candran.loadfile(args[1], nil, args)
|
local f, err = candran.loadfile(args.filename, nil, options)
|
||||||
if not f then
|
if not f then
|
||||||
io.stderr:write("can: "..err.."\n")
|
io.stderr:write("can: "..err.."\n")
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
|
|
@ -47,6 +50,8 @@ elseif #args >= 1 then
|
||||||
end
|
end
|
||||||
-- REPL
|
-- REPL
|
||||||
else
|
else
|
||||||
|
candran.default = util.merge(candran.default, options)
|
||||||
|
|
||||||
-- Setup linenoise
|
-- Setup linenoise
|
||||||
local s, l = pcall(require, "linenoise")
|
local s, l = pcall(require, "linenoise")
|
||||||
if not s then -- pure Lua compatibility thingy
|
if not s then -- pure Lua compatibility thingy
|
||||||
|
|
@ -113,6 +118,22 @@ else
|
||||||
print("Candran " .. candran.VERSION .. ", targeting " .. candran.default.target)
|
print("Candran " .. candran.VERSION .. ", targeting " .. candran.default.target)
|
||||||
candran.setup()
|
candran.setup()
|
||||||
|
|
||||||
|
-- check errors in static import
|
||||||
|
-- note: static imports will be run every line, as preprocessors macros and constants aren't kept between compilations...
|
||||||
|
do
|
||||||
|
local r, e = candran.load("local _", "stdin")
|
||||||
|
if not r then
|
||||||
|
print("In static import: "..e)
|
||||||
|
candran.default.import = {}
|
||||||
|
else
|
||||||
|
r, e = pcall(r)
|
||||||
|
if not r then
|
||||||
|
print("In static import: "..e)
|
||||||
|
candran.default.import = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- REPL loop
|
-- REPL loop
|
||||||
local multiline = false -- true if wait for another line
|
local multiline = false -- true if wait for another line
|
||||||
local buffer
|
local buffer
|
||||||
|
|
@ -152,15 +173,15 @@ else
|
||||||
end
|
end
|
||||||
|
|
||||||
-- exec
|
-- exec
|
||||||
local t = { pcall(candran.load, buffer, "stdin") }
|
local r, e = candran.load(buffer, "stdin")
|
||||||
if t[1] == false then
|
if not r then
|
||||||
if t[2]:match("expected '[end})]+' to close") then
|
if e:match("expected '[end})]+' to close") then
|
||||||
multiline = true
|
multiline = true
|
||||||
else
|
else
|
||||||
print(t[2])
|
print(e)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
t = { pcall(t[2]) }
|
local t = { pcall(r) }
|
||||||
if t[1] == false then
|
if t[1] == false then
|
||||||
print(t[2])
|
print(t[2])
|
||||||
elseif #t > 1 then
|
elseif #t > 1 then
|
||||||
|
|
|
||||||
85
bin/canc
85
bin/canc
|
|
@ -1,48 +1,63 @@
|
||||||
#!/usr/bin/env lua
|
#!/usr/bin/env lua
|
||||||
|
|
||||||
local candran = require("candran")
|
local candran = require("candran")
|
||||||
local cmdline = require("candran.cmdline")
|
|
||||||
local parse = require("candran.can-parser.parser").parse
|
local parse = require("candran.can-parser.parser").parse
|
||||||
local pp = require("candran.can-parser.pp")
|
local pp = require("candran.can-parser.pp")
|
||||||
|
local util = require("candran.util")
|
||||||
|
local argparse = require("argparse")
|
||||||
|
|
||||||
local args = cmdline(arg)
|
-- Parse args --
|
||||||
|
|
||||||
if #arg < 1 or args.help or args.h then
|
local parser = argparse()
|
||||||
print("Candran "..candran.VERSION.." compiler by Reuh")
|
:name "canc"
|
||||||
print("Usage: "..arg[0].." [options] filenames...")
|
:description("Candran "..candran.VERSION.." compiler by Reuh.")
|
||||||
print("Use - instead of filenames to read from the standard input. The output file will be named stdin.lua by default.")
|
:epilog "For more info, see https://github.com/Reuh/candran"
|
||||||
print("Compiler options:")
|
|
||||||
print(" dest=\"directory\" where compiled files should be written")
|
|
||||||
print(" out=\"name.lua\" output filename. By default, will use the same name as the input file with a .lua extension.")
|
|
||||||
print(" -print write to the standard output instead of creating files")
|
|
||||||
print(" -preprocess only run the preprocessor")
|
|
||||||
print(" -compile only run the compiler")
|
|
||||||
print(" -parse only parse the file and prints errors to stdout")
|
|
||||||
print(" -ast (for debugging purposes) only parse the files and dump the AST to stdout")
|
|
||||||
print(" -help or -h print this text")
|
|
||||||
print("Default options:")
|
|
||||||
for opt, val in pairs(candran.default) do
|
|
||||||
if type(val) == "string" then val = val:gsub("\n", "\\n") end
|
|
||||||
print((" %s=%q"):format(opt, val))
|
|
||||||
end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if arg[#arg] == "-" then
|
parser:argument("filename", "Candran files to compile. Use - to read from standard input; the output file will then be named stdin.lua by default.")
|
||||||
table.insert(args, io.stdin)
|
:args "+"
|
||||||
end
|
|
||||||
|
|
||||||
for _, file in ipairs(args) do
|
parser:group("Output options",
|
||||||
|
parser:option("-d --destination")
|
||||||
|
:description "Where compiled files should be written"
|
||||||
|
:argname "directory",
|
||||||
|
|
||||||
|
parser:option("-o --output")
|
||||||
|
:description "Output filename. (default: same name as the input file with a .lua extension)"
|
||||||
|
:argname "filename",
|
||||||
|
|
||||||
|
parser:flag("-p --print")
|
||||||
|
:description "Write to the standard output instead of creating files",
|
||||||
|
|
||||||
|
parser:flag("--preprocess")
|
||||||
|
:description "Only run the preprocessor",
|
||||||
|
|
||||||
|
parser:flag("--compile")
|
||||||
|
:description "Only run the compiler",
|
||||||
|
|
||||||
|
parser:flag("--parse")
|
||||||
|
:description "Only parse the file and prints syntax errors to stdout",
|
||||||
|
|
||||||
|
parser:flag("--ast")
|
||||||
|
:description"(for debugging purposes) Only parse the files and dump the AST to stdout"
|
||||||
|
)
|
||||||
|
|
||||||
|
util.cli.addCandranOptions(parser)
|
||||||
|
|
||||||
|
local args = parser:parse()
|
||||||
|
|
||||||
|
-- Compile --
|
||||||
|
|
||||||
|
for _, file in ipairs(args.filename) do
|
||||||
-- Read
|
-- Read
|
||||||
local dest, input
|
local dest, input
|
||||||
if file == io.stdin then
|
if file == "-" then
|
||||||
dest = args.out or "stdin.lua"
|
dest = args.output or "stdin.lua"
|
||||||
|
|
||||||
input = io.read("*a")
|
input = io.read("*a")
|
||||||
|
|
||||||
args.chunkname = "stdin"
|
args.chunkname = "stdin"
|
||||||
else
|
else
|
||||||
dest = args.out or (file:gsub("%.can$", "")..".lua")
|
dest = args.output or (file:gsub("%.can$", "")..".lua")
|
||||||
|
|
||||||
local inputFile, err = io.open(file, "r")
|
local inputFile, err = io.open(file, "r")
|
||||||
if not inputFile then
|
if not inputFile then
|
||||||
|
|
@ -69,8 +84,10 @@ for _, file in ipairs(args) do
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Compile and output
|
-- Compile and output
|
||||||
if args.dest then
|
local options = util.cli.makeCandranOptions(args)
|
||||||
dest = args.dest .. "/" .. dest
|
|
||||||
|
if args.destination then
|
||||||
|
dest = args.destination .. "/" .. dest
|
||||||
end
|
end
|
||||||
|
|
||||||
if not args.print then
|
if not args.print then
|
||||||
|
|
@ -79,7 +96,7 @@ for _, file in ipairs(args) do
|
||||||
|
|
||||||
local out = input
|
local out = input
|
||||||
if args.preprocess then
|
if args.preprocess then
|
||||||
local r, err = candran.preprocess(out, args)
|
local r, err = candran.preprocess(out, options)
|
||||||
if not r then
|
if not r then
|
||||||
io.stderr:write("canc: "..err.."\n")
|
io.stderr:write("canc: "..err.."\n")
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
|
|
@ -87,7 +104,7 @@ for _, file in ipairs(args) do
|
||||||
out = r
|
out = r
|
||||||
end
|
end
|
||||||
if args.compile then
|
if args.compile then
|
||||||
local r, err = candran.compile(out, args)
|
local r, err = candran.compile(out, options)
|
||||||
if not r then
|
if not r then
|
||||||
io.stderr:write("canc: "..err.."\n")
|
io.stderr:write("canc: "..err.."\n")
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
|
|
@ -95,7 +112,7 @@ for _, file in ipairs(args) do
|
||||||
out = r
|
out = r
|
||||||
end
|
end
|
||||||
if args.compile == nil and args.preprocess == nil then
|
if args.compile == nil and args.preprocess == nil then
|
||||||
local r, err = candran.make(input, args)
|
local r, err = candran.make(input, options)
|
||||||
if not r then
|
if not r then
|
||||||
io.stderr:write("canc: "..err.."\n")
|
io.stderr:write("canc: "..err.."\n")
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,7 @@ local oldRunner = runner.new
|
||||||
function runner.new(opts)
|
function runner.new(opts)
|
||||||
-- Disable max line length checking (it is compiled code...)
|
-- Disable max line length checking (it is compiled code...)
|
||||||
opts.max_line_length = false
|
opts.max_line_length = false
|
||||||
|
opts.ignore = { "4[23]1/__CAN_.*" }
|
||||||
return oldRunner(opts)
|
return oldRunner(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
93
candran.can
93
candran.can
|
|
@ -1,5 +1,10 @@
|
||||||
|
local candran = {
|
||||||
|
VERSION = "1.0.0"
|
||||||
|
}
|
||||||
|
package.loaded["candran"] = candran
|
||||||
|
|
||||||
#import("candran.util")
|
#import("candran.util")
|
||||||
#import("candran.cmdline")
|
#import("candran.serpent")
|
||||||
|
|
||||||
#import("compiler.lua54")
|
#import("compiler.lua54")
|
||||||
#import("compiler.lua53")
|
#import("compiler.lua53")
|
||||||
|
|
@ -14,11 +19,6 @@
|
||||||
|
|
||||||
local unpack = unpack or table.unpack
|
local unpack = unpack or table.unpack
|
||||||
|
|
||||||
local candran = {
|
|
||||||
VERSION = "0.14.0"
|
|
||||||
}
|
|
||||||
package.loaded["candran"] = candran
|
|
||||||
|
|
||||||
--- Default options.
|
--- Default options.
|
||||||
candran.default = {
|
candran.default = {
|
||||||
target = "lua54",
|
target = "lua54",
|
||||||
|
|
@ -27,7 +27,10 @@ candran.default = {
|
||||||
variablePrefix = "__CAN_",
|
variablePrefix = "__CAN_",
|
||||||
mapLines = true,
|
mapLines = true,
|
||||||
chunkname = "nil",
|
chunkname = "nil",
|
||||||
rewriteErrors = true
|
rewriteErrors = true,
|
||||||
|
builtInMacros = true,
|
||||||
|
preprocessorEnv = {},
|
||||||
|
import = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Autodetect version
|
-- Autodetect version
|
||||||
|
|
@ -47,10 +50,20 @@ end
|
||||||
-- @tparam input string input code
|
-- @tparam input string input code
|
||||||
-- @tparam options 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[1] output string output code
|
-- @treturn[1] output string output code
|
||||||
|
-- @treturn[1] macros registered macros
|
||||||
-- @treturn[2] nil nil if error
|
-- @treturn[2] nil nil if error
|
||||||
-- @treturn[2] error string error message
|
-- @treturn[2] error string error message
|
||||||
function candran.preprocess(input, options={})
|
function candran.preprocess(input, options={}, _env)
|
||||||
options = util.merge(candran.default, options)
|
options = util.merge(candran.default, options)
|
||||||
|
local macros = {
|
||||||
|
functions = {},
|
||||||
|
variables = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- add auto imports
|
||||||
|
for _, mod in ipairs(options.import) do
|
||||||
|
input =.. "#import(%q, {loadLocal=false})\n":format(mod)
|
||||||
|
end
|
||||||
|
|
||||||
-- generate preprocessor code
|
-- generate preprocessor code
|
||||||
local preprocessor = ""
|
local preprocessor = ""
|
||||||
|
|
@ -85,7 +98,8 @@ function candran.preprocess(input, options={})
|
||||||
preprocessor ..= "return output"
|
preprocessor ..= "return output"
|
||||||
|
|
||||||
-- make preprocessor environement
|
-- make preprocessor environement
|
||||||
local env = util.merge(_G, options)
|
local exportenv = {}
|
||||||
|
local env = util.merge(_G, options.preprocessorEnv)
|
||||||
--- Candran library table
|
--- Candran library table
|
||||||
env.candran = candran
|
env.candran = candran
|
||||||
--- Current preprocessor output
|
--- Current preprocessor output
|
||||||
|
|
@ -104,7 +118,10 @@ function candran.preprocess(input, options={})
|
||||||
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
|
||||||
|
|
||||||
margs = util.merge(options, { chunkname = filepath, loadLocal = true, loadPackage = true }, margs)
|
margs = util.merge(options, { chunkname = filepath, loadLocal = true, loadPackage = true }, margs)
|
||||||
local modcontent = assert(candran.preprocess(f:read("*a"), margs))
|
margs.import = {} -- no need for recursive import
|
||||||
|
local modcontent, modmacros, modenv = assert(candran.preprocess(f:read("*a"), margs))
|
||||||
|
macros = util.recmerge(macros, modmacros)
|
||||||
|
for k, v in pairs(modenv) do env[k] = v end
|
||||||
f:close()
|
f:close()
|
||||||
|
|
||||||
-- get module name (ex: module name of path.to.module is module)
|
-- get module name (ex: module name of path.to.module is module)
|
||||||
|
|
@ -140,6 +157,48 @@ function candran.preprocess(input, options={})
|
||||||
env.write(env[name])
|
env.write(env[name])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
env.define = function(identifier, replacement)
|
||||||
|
-- parse identifier
|
||||||
|
local iast, ierr = parser.parsemacroidentifier(identifier, options.chunkname)
|
||||||
|
if not iast then
|
||||||
|
return error("in macro identifier: %s":format(tostring(ierr)))
|
||||||
|
end
|
||||||
|
-- parse replacement value
|
||||||
|
if type(replacement) == "string" then
|
||||||
|
local rast, rerr = parser.parse(replacement, options.chunkname)
|
||||||
|
if not rast then
|
||||||
|
return error("in macro replacement: %s":format(tostring(rerr)))
|
||||||
|
end
|
||||||
|
-- when giving a single value as a replacement, bypass the implicit push
|
||||||
|
if #rast == 1 and rast[1].tag == "Push" and rast[1].implicit then
|
||||||
|
rast = rast[1][1]
|
||||||
|
end
|
||||||
|
replacement = rast
|
||||||
|
elseif type(replacement) ~= "function" then
|
||||||
|
error("bad argument #2 to 'define' (string or function expected)")
|
||||||
|
end
|
||||||
|
-- add macros
|
||||||
|
if iast.tag == "MacroFunction" then
|
||||||
|
macros.functions[iast[1][1]] = { args = iast[2], replacement = replacement }
|
||||||
|
elseif iast.tag == "Id" then
|
||||||
|
macros.variables[iast[1]] = replacement
|
||||||
|
else
|
||||||
|
error("invalid macro type %s":format(tostring(iast.tag)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
env.set = function(identifier, value)
|
||||||
|
exportenv[identifier] = value
|
||||||
|
env[identifier] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
-- default macros
|
||||||
|
if options.builtInMacros then
|
||||||
|
env.define("__STR__(x)", function(x) return ("%q"):format(x) end)
|
||||||
|
local s = require("candran.serpent")
|
||||||
|
env.define("__CONSTEXPR__(expr)", function(expr)
|
||||||
|
return s.block(assert(candran.load(expr))(), {fatal = true})
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
-- compile & load preprocessor
|
-- compile & load preprocessor
|
||||||
local preprocess, err = candran.compile(preprocessor, options)
|
local preprocess, err = candran.compile(preprocessor, options)
|
||||||
|
|
@ -158,16 +217,17 @@ function candran.preprocess(input, options={})
|
||||||
return nil, "in preprocessor: "..output
|
return nil, "in preprocessor: "..output
|
||||||
end
|
end
|
||||||
|
|
||||||
return output
|
return output, macros, exportenv
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Run the compiler
|
--- Run the compiler
|
||||||
-- @tparam input string input code
|
-- @tparam input string input code
|
||||||
-- @tparam options table options for the compiler
|
-- @tparam options table options for the compiler
|
||||||
|
-- @tparam macros table defined macros, as returned by the preprocessor
|
||||||
-- @treturn[1] output string output code
|
-- @treturn[1] output string output code
|
||||||
-- @treturn[2] nil nil if error
|
-- @treturn[2] nil nil if error
|
||||||
-- @treturn[2] error string error message
|
-- @treturn[2] error string error message
|
||||||
function candran.compile(input, options={})
|
function candran.compile(input, options={}, macros)
|
||||||
options = util.merge(candran.default, options)
|
options = util.merge(candran.default, options)
|
||||||
|
|
||||||
local ast, errmsg = parser.parse(input, options.chunkname)
|
local ast, errmsg = parser.parse(input, options.chunkname)
|
||||||
|
|
@ -176,7 +236,7 @@ function candran.compile(input, options={})
|
||||||
return nil, errmsg
|
return nil, errmsg
|
||||||
end
|
end
|
||||||
|
|
||||||
return require("compiler."..options.target)(input, ast, options)
|
return require("compiler."..options.target)(input, ast, options, macros)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Preprocess & compile code
|
--- Preprocess & compile code
|
||||||
|
|
@ -188,7 +248,7 @@ end
|
||||||
function candran.make(code, options)
|
function candran.make(code, options)
|
||||||
local r, err = candran.preprocess(code, options)
|
local r, err = candran.preprocess(code, options)
|
||||||
if r then
|
if r then
|
||||||
r, err = candran.compile(r, options)
|
r, err = candran.compile(r, options, err)
|
||||||
if r then
|
if r then
|
||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
|
|
@ -203,7 +263,7 @@ local codeCache = {}
|
||||||
function candran.loadfile(filepath, env, options)
|
function candran.loadfile(filepath, env, options)
|
||||||
local f, err = io.open(filepath)
|
local f, err = io.open(filepath)
|
||||||
if not f then
|
if not f then
|
||||||
return nil, "cannot open %s":format(err)
|
return nil, "cannot open %s":format(tostring(err))
|
||||||
end
|
end
|
||||||
local content = f:read("*a")
|
local content = f:read("*a")
|
||||||
f:close()
|
f:close()
|
||||||
|
|
@ -266,6 +326,7 @@ end
|
||||||
--- Candran error message handler.
|
--- Candran error message handler.
|
||||||
-- Use it in xpcall to rewrite stacktraces to display Candran source file lines instead of compiled Lua lines.
|
-- Use it in xpcall to rewrite stacktraces to display Candran source file lines instead of compiled Lua lines.
|
||||||
function candran.messageHandler(message, noTraceback)
|
function candran.messageHandler(message, noTraceback)
|
||||||
|
message = tostring(message)
|
||||||
if not noTraceback and not message:match("\nstack traceback:\n") then
|
if not noTraceback and not message:match("\nstack traceback:\n") then
|
||||||
message = debug.traceback(message, 2)
|
message = debug.traceback(message, 2)
|
||||||
end
|
end
|
||||||
|
|
@ -321,7 +382,7 @@ function candran.searcher(modpath)
|
||||||
if r then
|
if r then
|
||||||
return r(modpath, filepath)
|
return r(modpath, filepath)
|
||||||
else
|
else
|
||||||
error("error loading candran module '%s' from file '%s':\n\t%s":format(modpath, filepath, s), 0)
|
error("error loading candran module '%s' from file '%s':\n\t%s":format(modpath, filepath, tostring(s)), 0)
|
||||||
end
|
end
|
||||||
end, filepath
|
end, filepath
|
||||||
end
|
end
|
||||||
|
|
|
||||||
10365
candran.lua
10365
candran.lua
File diff suppressed because it is too large
Load diff
|
|
@ -306,6 +306,11 @@ local function fixShortFunc (t)
|
||||||
return t
|
return t
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function markImplicit (t)
|
||||||
|
t.implicit = true
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
local function statToExpr (t) -- tag a StatExpr
|
local function statToExpr (t) -- tag a StatExpr
|
||||||
t.tag = t.tag .. "Expr"
|
t.tag = t.tag .. "Expr"
|
||||||
return t
|
return t
|
||||||
|
|
@ -577,7 +582,7 @@ local G = { V"Lua",
|
||||||
RetStat = tagC("Return", kw("return") * commaSep(V"Expr", "RetList")^-1);
|
RetStat = tagC("Return", kw("return") * commaSep(V"Expr", "RetList")^-1);
|
||||||
|
|
||||||
PushStat = tagC("Push", kw("push") * commaSep(V"Expr", "RetList")^-1);
|
PushStat = tagC("Push", kw("push") * commaSep(V"Expr", "RetList")^-1);
|
||||||
ImplicitPushStat = tagC("Push", commaSep(V"Expr", "RetList"));
|
ImplicitPushStat = tagC("Push", commaSep(V"Expr", "RetList")) / markImplicit;
|
||||||
|
|
||||||
NameList = tagC("NameList", commaSep(V"Id"));
|
NameList = tagC("NameList", commaSep(V"Id"));
|
||||||
DestructuringNameList = tagC("NameList", commaSep(V"DestructuringId")),
|
DestructuringNameList = tagC("NameList", commaSep(V"DestructuringId")),
|
||||||
|
|
@ -757,6 +762,20 @@ local G = { V"Lua",
|
||||||
BinOp = V"OrOp" + V"AndOp" + V"BOrOp" + V"BXorOp" + V"BAndOp" + V"ShiftOp" + V"ConcatOp" + V"AddOp" + V"MulOp" + V"PowOp";
|
BinOp = V"OrOp" + V"AndOp" + V"BOrOp" + V"BXorOp" + V"BAndOp" + V"ShiftOp" + V"ConcatOp" + V"AddOp" + V"MulOp" + V"PowOp";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- used to parse macro indentifier in define() preprocessor function
|
||||||
|
local macroidentifier = {
|
||||||
|
expect(V"MacroIdentifier", "InvalidStat") * expect(P(-1), "Extra"),
|
||||||
|
|
||||||
|
MacroIdentifier = tagC("MacroFunction", V"Id" * sym("(") * V"MacroFunctionArgs" * expect(sym(")"), "CParenPList"))
|
||||||
|
+ V"Id";
|
||||||
|
|
||||||
|
MacroFunctionArgs = V"NameList" * (sym(",") * expect(tagC("Dots", sym("...")), "ParList"))^-1 / addDots
|
||||||
|
+ Ct(tagC("Dots", sym("...")))
|
||||||
|
+ Ct(Cc());
|
||||||
|
|
||||||
|
}
|
||||||
|
for k,v in pairs(G) do if macroidentifier[k] == nil then macroidentifier[k] = v end end -- copy other rules from main syntax
|
||||||
|
|
||||||
local parser = {}
|
local parser = {}
|
||||||
|
|
||||||
local validator = require("candran.can-parser.validator")
|
local validator = require("candran.can-parser.validator")
|
||||||
|
|
@ -774,4 +793,15 @@ function parser.parse (subject, filename)
|
||||||
return validate(ast, errorinfo)
|
return validate(ast, errorinfo)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function parser.parsemacroidentifier (subject, filename)
|
||||||
|
local errorinfo = { subject = subject, filename = filename }
|
||||||
|
lpeg.setmaxstack(1000)
|
||||||
|
local ast, label, errpos = lpeg.match(macroidentifier, subject, nil, errorinfo)
|
||||||
|
if not ast then
|
||||||
|
local errmsg = labels[label][2]
|
||||||
|
return ast, syntaxerror(errorinfo, errpos, errmsg)
|
||||||
|
end
|
||||||
|
return ast
|
||||||
|
end
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
-- started: 2008-04-12 by Shmuel Zeigerman
|
|
||||||
-- license: public domain
|
|
||||||
|
|
||||||
local ipairs,pairs,setfenv,tonumber,loadstring,type =
|
|
||||||
ipairs,pairs,setfenv,tonumber,loadstring,type
|
|
||||||
local tinsert, tconcat = table.insert, table.concat
|
|
||||||
|
|
||||||
local function commonerror (msg)
|
|
||||||
return nil, ("[cmdline]: " .. msg)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function argerror (msg, numarg)
|
|
||||||
msg = msg and (": " .. msg) or ""
|
|
||||||
return nil, ("[cmdline]: bad argument #" .. numarg .. msg)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function iderror (numarg)
|
|
||||||
return argerror("ID not valid", numarg)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function idcheck (id)
|
|
||||||
return id:match("^[%a_][%w_]*$") and true
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[------------------------------------------------------------------------
|
|
||||||
Syntax:
|
|
||||||
t_out = getparam(t_in [,options] [,params])
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
t_in: table - list of string arguments to be processed in order
|
|
||||||
(usually it is the `arg' table created by the Lua interpreter).
|
|
||||||
|
|
||||||
* if an argument begins with $, the $ is skipped and the rest is inserted
|
|
||||||
into the array part of the output table.
|
|
||||||
|
|
||||||
* if an argument begins with -, the rest is a sequence of variables
|
|
||||||
(separated by commas or semicolons) that are all set to true;
|
|
||||||
example: -var1,var2 --> var1,var2 = true,true
|
|
||||||
|
|
||||||
* if an argument begins with !, the rest is a Lua chunk;
|
|
||||||
example: !a=(40+3)*5;b=20;name="John";window={w=600,h=480}
|
|
||||||
|
|
||||||
* if an argument contains =, then it is an assignment in the form
|
|
||||||
var1,...=value (no space is allowed around the =)
|
|
||||||
* if value begins with $, the $ is skipped, the rest is a string
|
|
||||||
example: var1,var2=$ --> var1,var2 = "",""
|
|
||||||
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
|
|
||||||
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
|
|
||||||
example: name=John --> name = "John"
|
|
||||||
|
|
||||||
* if an argument neither begins with one of the special characters (-,!,$),
|
|
||||||
nor contains =, it is inserted as is into the array part of the output
|
|
||||||
table.
|
|
||||||
|
|
||||||
options (optional): a list of names of all command-line options and parameters
|
|
||||||
permitted in the application; used to check that each found option
|
|
||||||
is valid; no checks are done if not supplied.
|
|
||||||
|
|
||||||
params (optional): a list of names of all command-line parameters required
|
|
||||||
by the application; used to check that each required parameter is present;
|
|
||||||
no checks are done if not supplied.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
On success: the output table, e.g. { [1]="./myfile.txt", name="John", age=40 }
|
|
||||||
On error: nil followed by error message string.
|
|
||||||
|
|
||||||
--]]------------------------------------------------------------------------
|
|
||||||
return function(t_in, options, params)
|
|
||||||
local t_out = {}
|
|
||||||
for i,v in ipairs(t_in) do
|
|
||||||
local prefix, command = v:sub(1,1), v:sub(2)
|
|
||||||
if prefix == "$" then
|
|
||||||
tinsert(t_out, command)
|
|
||||||
elseif prefix == "-" then
|
|
||||||
for id in command:gmatch"[^,;]+" do
|
|
||||||
if not idcheck(id) then return iderror(i) end
|
|
||||||
t_out[id] = true
|
|
||||||
end
|
|
||||||
elseif prefix == "!" then
|
|
||||||
local f, err = loadstring(command)
|
|
||||||
if not f then return argerror(err, i) end
|
|
||||||
setfenv(f, t_out)()
|
|
||||||
elseif v:find("=") then
|
|
||||||
local ids, val = v:match("^([^=]+)%=(.*)") -- no space around =
|
|
||||||
if not ids then return argerror("invalid assignment syntax", i) end
|
|
||||||
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
|
|
||||||
if not idcheck(id) then return iderror(i) end
|
|
||||||
t_out[id] = val
|
|
||||||
end
|
|
||||||
else
|
|
||||||
tinsert(t_out, v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if options then
|
|
||||||
local lookup, unknown = {}, {}
|
|
||||||
for _,v in ipairs(options) do lookup[v] = true end
|
|
||||||
for k,_ in pairs(t_out) do
|
|
||||||
if lookup[k]==nil and type(k)=="string" then tinsert(unknown, k) end
|
|
||||||
end
|
|
||||||
if #unknown > 0 then
|
|
||||||
return commonerror("unknown options: " .. tconcat(unknown, ", "))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if params then
|
|
||||||
local missing = {}
|
|
||||||
for _,v in ipairs(params) do
|
|
||||||
if t_out[v]==nil then tinsert(missing, v) end
|
|
||||||
end
|
|
||||||
if #missing > 0 then
|
|
||||||
return commonerror("missing parameters: " .. tconcat(missing, ", "))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return t_out
|
|
||||||
end
|
|
||||||
163
candran/serpent.lua
Normal file
163
candran/serpent.lua
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
--[[
|
||||||
|
Serpent source is released under the MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2012-2018 Paul Kulchenko (paul@kulchenko.com)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
]]
|
||||||
|
local n, v = "serpent", "0.302" -- (C) 2012-18 Paul Kulchenko; MIT License
|
||||||
|
local c, d = "Paul Kulchenko", "Lua serializer and pretty printer"
|
||||||
|
local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'}
|
||||||
|
local badtype = {thread = true, userdata = true, cdata = true}
|
||||||
|
local getmetatable = debug and debug.getmetatable or getmetatable
|
||||||
|
local pairs = function(t) return next, t end -- avoid using __pairs in Lua 5.2+
|
||||||
|
local keyword, globals, G = {}, {}, (_G or _ENV)
|
||||||
|
for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false',
|
||||||
|
'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
|
||||||
|
'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end
|
||||||
|
for k,v in pairs(G) do globals[v] = k end -- build func to name mapping
|
||||||
|
for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do
|
||||||
|
for k,v in pairs(type(G[g]) == 'table' and G[g] or {}) do globals[v] = g..'.'..k end end
|
||||||
|
|
||||||
|
local function s(t, opts)
|
||||||
|
local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum
|
||||||
|
local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge
|
||||||
|
local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge)
|
||||||
|
local maxlen, metatostring = tonumber(opts.maxlength), opts.metatostring
|
||||||
|
local iname, comm = '_'..(name or ''), opts.comment and (tonumber(opts.comment) or math.huge)
|
||||||
|
local numformat = opts.numformat or "%.17g"
|
||||||
|
local seen, sref, syms, symn = {}, {'local '..iname..'={}'}, {}, 0
|
||||||
|
local function gensym(val) return '_'..(tostring(tostring(val)):gsub("[^%w]",""):gsub("(%d%w+)",
|
||||||
|
-- tostring(val) is needed because __tostring may return a non-string value
|
||||||
|
function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return tostring(syms[s]) end)) end
|
||||||
|
local function safestr(s) return type(s) == "number" and tostring(huge and snum[tostring(s)] or numformat:format(s))
|
||||||
|
or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026
|
||||||
|
or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end
|
||||||
|
local function comment(s,l) return comm and (l or 0) < comm and ' --[['..select(2, pcall(tostring, s))..']]' or '' end
|
||||||
|
local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal
|
||||||
|
and safestr(select(2, pcall(tostring, s))) or error("Can't serialize "..tostring(s)) end
|
||||||
|
local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r']
|
||||||
|
local n = name == nil and '' or name
|
||||||
|
local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n]
|
||||||
|
local safe = plain and n or '['..safestr(n)..']'
|
||||||
|
return (path or '')..(plain and path and '.' or '')..safe, safe end
|
||||||
|
local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(k, o, n) -- k=keys, o=originaltable, n=padding
|
||||||
|
local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'}
|
||||||
|
local function padnum(d) return ("%0"..tostring(maxn).."d"):format(tonumber(d)) end
|
||||||
|
table.sort(k, function(a,b)
|
||||||
|
-- sort numeric keys first: k[key] is not nil for numerical keys
|
||||||
|
return (k[a] ~= nil and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum))
|
||||||
|
< (k[b] ~= nil and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end
|
||||||
|
local function val2str(t, name, indent, insref, path, plainindex, level)
|
||||||
|
local ttype, level, mt = type(t), (level or 0), getmetatable(t)
|
||||||
|
local spath, sname = safename(path, name)
|
||||||
|
local tag = plainindex and
|
||||||
|
((type(name) == "number") and '' or name..space..'='..space) or
|
||||||
|
(name ~= nil and sname..space..'='..space or '')
|
||||||
|
if seen[t] then -- already seen this element
|
||||||
|
sref[#sref+1] = spath..space..'='..space..seen[t]
|
||||||
|
return tag..'nil'..comment('ref', level) end
|
||||||
|
-- protect from those cases where __tostring may fail
|
||||||
|
if type(mt) == 'table' and metatostring ~= false then
|
||||||
|
local to, tr = pcall(function() return mt.__tostring(t) end)
|
||||||
|
local so, sr = pcall(function() return mt.__serialize(t) end)
|
||||||
|
if (to or so) then -- knows how to serialize itself
|
||||||
|
seen[t] = insref or spath
|
||||||
|
t = so and sr or tr
|
||||||
|
ttype = type(t)
|
||||||
|
end -- new value falls through to be serialized
|
||||||
|
end
|
||||||
|
if ttype == "table" then
|
||||||
|
if level >= maxl then return tag..'{}'..comment('maxlvl', level) end
|
||||||
|
seen[t] = insref or spath
|
||||||
|
if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty
|
||||||
|
if maxlen and maxlen < 0 then return tag..'{}'..comment('maxlen', level) end
|
||||||
|
local maxn, o, out = math.min(#t, maxnum or #t), {}, {}
|
||||||
|
for key = 1, maxn do o[key] = key end
|
||||||
|
if not maxnum or #o < maxnum then
|
||||||
|
local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables
|
||||||
|
for key in pairs(t) do if o[key] ~= key then n = n + 1; o[n] = key end end end
|
||||||
|
if maxnum and #o > maxnum then o[maxnum+1] = nil end
|
||||||
|
if opts.sortkeys and #o > maxn then alphanumsort(o, t, opts.sortkeys) end
|
||||||
|
local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output)
|
||||||
|
for n, key in ipairs(o) do
|
||||||
|
local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse
|
||||||
|
if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing
|
||||||
|
or opts.keyallow and not opts.keyallow[key]
|
||||||
|
or opts.keyignore and opts.keyignore[key]
|
||||||
|
or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types
|
||||||
|
or sparse and value == nil then -- skipping nils; do nothing
|
||||||
|
elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then
|
||||||
|
if not seen[key] and not globals[key] then
|
||||||
|
sref[#sref+1] = 'placeholder'
|
||||||
|
local sname = safename(iname, gensym(key)) -- iname is table for local variables
|
||||||
|
sref[#sref] = val2str(key,sname,indent,sname,iname,true) end
|
||||||
|
sref[#sref+1] = 'placeholder'
|
||||||
|
local path = seen[t]..'['..tostring(seen[key] or globals[key] or gensym(key))..']'
|
||||||
|
sref[#sref] = path..space..'='..space..tostring(seen[value] or val2str(value,nil,indent,path))
|
||||||
|
else
|
||||||
|
out[#out+1] = val2str(value,key,indent,nil,seen[t],plainindex,level+1)
|
||||||
|
if maxlen then
|
||||||
|
maxlen = maxlen - #out[#out]
|
||||||
|
if maxlen < 0 then break end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local prefix = string.rep(indent or '', level)
|
||||||
|
local head = indent and '{\n'..prefix..indent or '{'
|
||||||
|
local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space))
|
||||||
|
local tail = indent and "\n"..prefix..'}' or '}'
|
||||||
|
return (custom and custom(tag,head,body,tail,level) or tag..head..body..tail)..comment(t, level)
|
||||||
|
elseif badtype[ttype] then
|
||||||
|
seen[t] = insref or spath
|
||||||
|
return tag..globerr(t, level)
|
||||||
|
elseif ttype == 'function' then
|
||||||
|
seen[t] = insref or spath
|
||||||
|
if opts.nocode then return tag.."function() --[[..skipped..]] end"..comment(t, level) end
|
||||||
|
local ok, res = pcall(string.dump, t)
|
||||||
|
local func = ok and "((loadstring or load)("..safestr(res)..",'@serialized'))"..comment(t, level)
|
||||||
|
return tag..(func or globerr(t, level))
|
||||||
|
else return tag..safestr(t) end -- handle all other types
|
||||||
|
end
|
||||||
|
local sepr = indent and "\n" or ";"..space
|
||||||
|
local body = val2str(t, name, indent) -- this call also populates sref
|
||||||
|
local tail = #sref>1 and table.concat(sref, sepr)..sepr or ''
|
||||||
|
local warn = opts.comment and #sref>1 and space.."--[[incomplete output with shared/self-references skipped]]" or ''
|
||||||
|
return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function deserialize(data, opts)
|
||||||
|
local env = (opts and opts.safe == false) and G
|
||||||
|
or setmetatable({}, {
|
||||||
|
__index = function(t,k) return t end,
|
||||||
|
__call = function(t,...) error("cannot call functions") end
|
||||||
|
})
|
||||||
|
local f, res = (loadstring or load)('return '..data, nil, nil, env)
|
||||||
|
if not f then f, res = (loadstring or load)(data, nil, nil, env) end
|
||||||
|
if not f then return f, res end
|
||||||
|
if setfenv then setfenv(f, env) end
|
||||||
|
return pcall(f)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end
|
||||||
|
return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s,
|
||||||
|
load = deserialize,
|
||||||
|
dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end,
|
||||||
|
line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end,
|
||||||
|
block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end }
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
local candran = require("candran")
|
||||||
local util = {}
|
local util = {}
|
||||||
|
|
||||||
function util.search(modpath, exts={})
|
function util.search(modpath, exts={})
|
||||||
|
|
@ -27,6 +28,20 @@ function util.load(str, name, env)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function util.recmerge(...)
|
||||||
|
local r = {}
|
||||||
|
for _, t in ipairs({...}) do
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
if type(v) == "table" then
|
||||||
|
r[k] = util.merge(v, r[k])
|
||||||
|
else
|
||||||
|
r[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
function util.merge(...)
|
function util.merge(...)
|
||||||
local r = {}
|
local r = {}
|
||||||
for _, t in ipairs({...}) do
|
for _, t in ipairs({...}) do
|
||||||
|
|
@ -37,4 +52,74 @@ function util.merge(...)
|
||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
|
|
||||||
|
util.cli = {
|
||||||
|
-- add option to set Candran options to an argparse parser
|
||||||
|
addCandranOptions = function(parser)
|
||||||
|
parser:group("Compiler options",
|
||||||
|
parser:option("-t --target")
|
||||||
|
:description "Target Lua version: lua54, lua53, lua52, luajit or lua51"
|
||||||
|
:default(candran.default.target),
|
||||||
|
|
||||||
|
parser:option("--indentation")
|
||||||
|
:description "Character(s) used for indentation in the compiled file"
|
||||||
|
:default(candran.default.indentation),
|
||||||
|
|
||||||
|
parser:option("--newline")
|
||||||
|
:description "Character(s) used for newlines in the compiled file"
|
||||||
|
:default(candran.default.newline),
|
||||||
|
|
||||||
|
parser:option("--variable-prefix")
|
||||||
|
:description "Prefix used when Candran needs to set a local variable to provide some functionality"
|
||||||
|
:default(candran.default.variablePrefix),
|
||||||
|
|
||||||
|
parser:flag("--no-map-lines")
|
||||||
|
:description "Do not add comments at the end of each line indicating the associated source line and file (error rewriting will not work)"
|
||||||
|
)
|
||||||
|
|
||||||
|
parser:group("Preprocessor options",
|
||||||
|
parser:flag("--no-builtin-macros")
|
||||||
|
:description "Disable built-in macros",
|
||||||
|
|
||||||
|
parser:option("-D --define")
|
||||||
|
:description "Define a preprocessor constant"
|
||||||
|
:args("1-2")
|
||||||
|
:argname{"name", "value"}
|
||||||
|
:count("*"),
|
||||||
|
|
||||||
|
parser:option("-I --import")
|
||||||
|
:description "Statically import a module into the compiled file"
|
||||||
|
:argname("module")
|
||||||
|
:count("*")
|
||||||
|
)
|
||||||
|
|
||||||
|
parser:option("--chunkname")
|
||||||
|
:description "Chunkname used when running the code"
|
||||||
|
|
||||||
|
parser:flag("--no-rewrite-errors")
|
||||||
|
:description "Disable error rewriting when running the code"
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- convert parsed arguments to a Candran options table
|
||||||
|
makeCandranOptions = function(args)
|
||||||
|
local preprocessorEnv = {}
|
||||||
|
for _, o in ipairs(args.define) do
|
||||||
|
preprocessorEnv[o[1]] = tonumber(o[2]) or o[2] or true
|
||||||
|
end
|
||||||
|
|
||||||
|
local options = {
|
||||||
|
target = args.target,
|
||||||
|
indentation = args.indentation,
|
||||||
|
newline = args.newline,
|
||||||
|
variablePrefix = args.variable_prefix,
|
||||||
|
mapLines = not args.no_map_lines,
|
||||||
|
chunkname = args.chunkname,
|
||||||
|
rewriteErrors = not args.no_rewrite_errors,
|
||||||
|
builtInMacros = not args.no_builtin_macros,
|
||||||
|
preprocessorEnv = preprocessorEnv,
|
||||||
|
import = args.import
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
return util
|
return util
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,6 @@ end
|
||||||
|
|
||||||
#local patch = output
|
#local patch = output
|
||||||
#output = ""
|
#output = ""
|
||||||
#import("compiler.luajit", { patch = patch, loadPackage = false })
|
#import("compiler.luajit", { preprocessorEnv = { patch = patch }, loadPackage = false })
|
||||||
|
|
||||||
return luajit
|
return luajit
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,6 @@ end
|
||||||
|
|
||||||
#local patch = output
|
#local patch = output
|
||||||
#output = ""
|
#output = ""
|
||||||
#import("compiler.lua53", { patch = patch, loadPackage = false })
|
#import("compiler.lua53", { preprocessorEnv = { patch = patch }, loadPackage = false })
|
||||||
|
|
||||||
return lua53
|
return lua53
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,6 @@ end
|
||||||
|
|
||||||
#local patch = output
|
#local patch = output
|
||||||
#output = ""
|
#output = ""
|
||||||
#import("compiler.lua54", { patch = patch, loadPackage = false })
|
#import("compiler.lua54", { preprocessorEnv = { patch = patch }, loadPackage = false })
|
||||||
|
|
||||||
return lua54
|
return lua54
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
|
local util = require("candran.util")
|
||||||
|
|
||||||
local targetName = "Lua 5.4"
|
local targetName = "Lua 5.4"
|
||||||
|
|
||||||
return function(code, ast, options)
|
local unpack = unpack or table.unpack
|
||||||
|
|
||||||
|
return function(code, ast, options, macros={functions={}, variables={}})
|
||||||
--- Line mapping
|
--- Line mapping
|
||||||
local lastInputPos = 1 -- last token position in the input code
|
local lastInputPos = 1 -- last token position in the input code
|
||||||
local prevLinePos = 1 -- last token position in the previous line of code in the input code
|
local prevLinePos = 1 -- last token position in the previous line of code in the input code
|
||||||
|
|
@ -47,7 +51,8 @@ return function(code, ast, options)
|
||||||
local states = {
|
local states = {
|
||||||
push = {}, -- push stack variable names
|
push = {}, -- push stack variable names
|
||||||
destructuring = {}, -- list of variable that need to be assigned from a destructure {id = "parent variable", "field1", "field2"...}
|
destructuring = {}, -- list of variable that need to be assigned from a destructure {id = "parent variable", "field1", "field2"...}
|
||||||
scope = {} -- list of variables defined in the current scope
|
scope = {}, -- list of variables defined in the current scope
|
||||||
|
macroargs = {} -- currently defined arguemnts from a macro function
|
||||||
}
|
}
|
||||||
-- Push a new value on top of the stack "name". Returns an empty string for chaining.
|
-- Push a new value on top of the stack "name". Returns an empty string for chaining.
|
||||||
local function push(name, state)
|
local function push(name, state)
|
||||||
|
|
@ -83,6 +88,9 @@ return function(code, ast, options)
|
||||||
return var
|
return var
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- indicate if currently processing a macro, so it cannot be applied recursively
|
||||||
|
local nomacro = { variables = {}, functions = {} }
|
||||||
|
|
||||||
--- Module management
|
--- Module management
|
||||||
local required = {} -- { ["full require expression"] = true, ... }
|
local required = {} -- { ["full require expression"] = true, ... }
|
||||||
local requireStr = ""
|
local requireStr = ""
|
||||||
|
|
@ -536,7 +544,15 @@ return function(code, ast, options)
|
||||||
end,
|
end,
|
||||||
-- Dots
|
-- Dots
|
||||||
Dots = ()
|
Dots = ()
|
||||||
return "..."
|
local macroargs = peek("macroargs")
|
||||||
|
if macroargs and not nomacro.variables["..."] and macroargs["..."] then
|
||||||
|
nomacro.variables["..."] = true
|
||||||
|
local r = lua(macroargs["..."], "_lhs")
|
||||||
|
nomacro.variables["..."] = nil
|
||||||
|
return r
|
||||||
|
else
|
||||||
|
return "..."
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
-- Boolean{ <boolean> }
|
-- Boolean{ <boolean> }
|
||||||
Boolean = (t)
|
Boolean = (t)
|
||||||
|
|
@ -723,6 +739,37 @@ return function(code, ast, options)
|
||||||
Call = (t)
|
Call = (t)
|
||||||
if t[1].tag == "String" or t[1].tag == "Table" then
|
if t[1].tag == "String" or t[1].tag == "Table" then
|
||||||
return "("..lua(t[1])..")("..lua(t, "_lhs", 2)..")"
|
return "("..lua(t[1])..")("..lua(t, "_lhs", 2)..")"
|
||||||
|
elseif t[1].tag == "Id" and not nomacro.functions[t[1][1]] and macros.functions[t[1][1]] then
|
||||||
|
local macro = macros.functions[t[1][1]]
|
||||||
|
local replacement = macro.replacement
|
||||||
|
local r
|
||||||
|
nomacro.functions[t[1][1]] = true
|
||||||
|
if type(replacement) == "function" then
|
||||||
|
local args = {}
|
||||||
|
for i=2, #t do
|
||||||
|
table.insert(args, lua(t[i]))
|
||||||
|
end
|
||||||
|
r = replacement(unpack(args))
|
||||||
|
else
|
||||||
|
local macroargs = util.merge(peek("macroargs"))
|
||||||
|
for i, arg in ipairs(macro.args) do
|
||||||
|
if arg.tag == "Dots" then
|
||||||
|
macroargs["..."] = [for j=i+1, #t do t[j] end]
|
||||||
|
elseif arg.tag == "Id" then
|
||||||
|
if t[i+1] == nil then
|
||||||
|
error("bad argument #%s to macro %s (value expected)":format(i, t[1][1]))
|
||||||
|
end
|
||||||
|
macroargs[arg[1]] = t[i+1]
|
||||||
|
else
|
||||||
|
error("unexpected argument type %s in macro %s":format(arg.tag, t[1][1]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
push("macroargs", macroargs)
|
||||||
|
r = lua(replacement)
|
||||||
|
pop("macroargs")
|
||||||
|
end
|
||||||
|
nomacro.functions[t[1][1]] = nil
|
||||||
|
return r
|
||||||
elseif t[1].tag == "MethodStub" then -- method call
|
elseif t[1].tag == "MethodStub" then -- method call
|
||||||
if t[1][1].tag == "String" or t[1][1].tag == "Table" then
|
if t[1][1].tag == "String" or t[1][1].tag == "Table" then
|
||||||
return "("..lua(t[1][1]).."):"..lua(t[1][2], "Id").."("..lua(t, "_lhs", 2)..")"
|
return "("..lua(t[1][1]).."):"..lua(t[1][2], "Id").."("..lua(t, "_lhs", 2)..")"
|
||||||
|
|
@ -757,7 +804,23 @@ return function(code, ast, options)
|
||||||
end,
|
end,
|
||||||
-- Id{ <string> }
|
-- Id{ <string> }
|
||||||
Id = (t)
|
Id = (t)
|
||||||
return t[1]
|
local r = t[1]
|
||||||
|
local macroargs = peek("macroargs")
|
||||||
|
if not nomacro.variables[t[1]] then
|
||||||
|
nomacro.variables[t[1]] = true
|
||||||
|
if macroargs and macroargs[t[1]] then -- replace with macro argument
|
||||||
|
r = lua(macroargs[t[1]])
|
||||||
|
elseif macros.variables[t[1]] ~= nil then -- replace with macro variable
|
||||||
|
local macro = macros.variables[t[1]]
|
||||||
|
if type(macro) == "function" then
|
||||||
|
r = macro()
|
||||||
|
else
|
||||||
|
r = lua(macro)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
nomacro.variables[t[1]] = nil
|
||||||
|
end
|
||||||
|
return r
|
||||||
end,
|
end,
|
||||||
-- AttributeId{ <string> <string>? }
|
-- AttributeId{ <string> <string>? }
|
||||||
AttributeId = (t)
|
AttributeId = (t)
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,6 @@ end
|
||||||
|
|
||||||
#local patch = output
|
#local patch = output
|
||||||
#output = ""
|
#output = ""
|
||||||
#import("compiler.lua52", { patch = patch, loadPackage = false })
|
#import("compiler.lua52", { preprocessorEnv = { patch = patch }, loadPackage = false })
|
||||||
|
|
||||||
return lua52
|
return lua52
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ rockspec_format = "3.0"
|
||||||
|
|
||||||
package = "candran"
|
package = "candran"
|
||||||
|
|
||||||
version = "0.14.0-1"
|
version = "1.0.0-1"
|
||||||
|
|
||||||
description = {
|
description = {
|
||||||
summary = "A simple Lua dialect and preprocessor.",
|
summary = "A simple Lua dialect and preprocessor.",
|
||||||
|
|
@ -19,14 +19,15 @@ description = {
|
||||||
|
|
||||||
source = {
|
source = {
|
||||||
url = "git://github.com/Reuh/candran",
|
url = "git://github.com/Reuh/candran",
|
||||||
tag = "v0.14.0"
|
tag = "v1.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies = {
|
dependencies = {
|
||||||
"lua >= 5.1",
|
"lua >= 5.1",
|
||||||
"lpeglabel >= 1.5.0",
|
"lpeglabel >= 1.5.0",
|
||||||
"linenoise >= 0.9",
|
"linenoise >= 0.9",
|
||||||
"luacheck >= 0.23.0"
|
"luacheck >= 0.23.0",
|
||||||
|
"argparse >= 0.7.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
build = {
|
build = {
|
||||||
|
|
@ -25,7 +25,8 @@ dependencies = {
|
||||||
"lua >= 5.1",
|
"lua >= 5.1",
|
||||||
"lpeglabel >= 1.5.0",
|
"lpeglabel >= 1.5.0",
|
||||||
"linenoise >= 0.9",
|
"linenoise >= 0.9",
|
||||||
"luacheck >= 0.23.0"
|
"luacheck >= 0.23.0",
|
||||||
|
"argparse >= 0.7.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
build = {
|
build = {
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ return a
|
||||||
|
|
||||||
test("preprocessor placeholder function", [[
|
test("preprocessor placeholder function", [[
|
||||||
#placeholder('foo')
|
#placeholder('foo')
|
||||||
]], 5, { foo = "return 5" })
|
]], 5, { preprocessorEnv = { foo = "return 5" } })
|
||||||
|
|
||||||
test("preprocessor options", [[
|
test("preprocessor options", [[
|
||||||
#if not foo == "sky" then
|
#if not foo == "sky" then
|
||||||
|
|
@ -151,6 +151,65 @@ return a
|
||||||
#error("preprocessor should ignore long strings")
|
#error("preprocessor should ignore long strings")
|
||||||
]])
|
]])
|
||||||
|
|
||||||
|
test("preprocessor macro remove function", [[
|
||||||
|
#define("log(...)", "")
|
||||||
|
log("test")
|
||||||
|
return true
|
||||||
|
]], true)
|
||||||
|
|
||||||
|
test("preprocessor macro replace function", [[
|
||||||
|
#define("log(x)", "a = x")
|
||||||
|
log("test")
|
||||||
|
return a
|
||||||
|
]], "test")
|
||||||
|
|
||||||
|
test("preprocessor macro identifier replace function", [[
|
||||||
|
#define("test(x)", "x = 42")
|
||||||
|
test(hello)
|
||||||
|
return hello
|
||||||
|
]], 42)
|
||||||
|
|
||||||
|
test("preprocessor macro replace function with vararg", [[
|
||||||
|
#define("log(...)", "a, b, c = ...")
|
||||||
|
log(1, 2, 3)
|
||||||
|
assert(a == 1)
|
||||||
|
assert(b == 2)
|
||||||
|
assert(c == 3)
|
||||||
|
return true
|
||||||
|
]], true)
|
||||||
|
|
||||||
|
test("preprocessor macro replace function with vararg and arg", [[
|
||||||
|
#define("log(x, ...)", "a, b, c = x, ...")
|
||||||
|
log(1, 2, 3)
|
||||||
|
assert(a == 1)
|
||||||
|
assert(b == 2)
|
||||||
|
assert(c == 3)
|
||||||
|
return true
|
||||||
|
]], true)
|
||||||
|
|
||||||
|
test("preprocessor macro replace variable", [[
|
||||||
|
#define("a", "42")
|
||||||
|
return a
|
||||||
|
]], 42)
|
||||||
|
|
||||||
|
test("preprocessor macro prevent recursive macro", [[
|
||||||
|
#define("f(x)", "x")
|
||||||
|
local x = 42
|
||||||
|
x = f(x)
|
||||||
|
return x
|
||||||
|
]], 42)
|
||||||
|
|
||||||
|
test("preprocessor macro replace variable with function", [[
|
||||||
|
#define("a", function() return "42" end)
|
||||||
|
return a
|
||||||
|
]], 42)
|
||||||
|
|
||||||
|
test("preprocessor macro replace function with function", [[
|
||||||
|
#define("test(x)", function(x) return ("%s = 42"):format(x) end)
|
||||||
|
test(hello)
|
||||||
|
return hello
|
||||||
|
]], 42)
|
||||||
|
|
||||||
----------------------
|
----------------------
|
||||||
-- SYNTAX ADDITIONS --
|
-- SYNTAX ADDITIONS --
|
||||||
----------------------
|
----------------------
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue