2018-10-17 18:32:39 -07:00
|
|
|
#!/usr/bin/env lua
|
|
|
|
|
|
|
|
local fennel_dir = arg[0]:match("(.-)[^\\/]+$")
|
|
|
|
package.path = fennel_dir .. "?.lua;" .. package.path
|
|
|
|
local fennel = require('fennel')
|
2019-12-02 18:30:30 -08:00
|
|
|
local unpack = unpack or table.unpack
|
2018-10-17 18:32:39 -07:00
|
|
|
|
|
|
|
local help = [[
|
|
|
|
Usage: fennel [FLAG] [FILE]
|
|
|
|
|
2019-12-02 18:30:30 -08:00
|
|
|
Run fennel, a lisp programming language for the Lua runtime.
|
2018-10-17 18:32:39 -07:00
|
|
|
|
2020-03-07 17:41:37 -08:00
|
|
|
--repl : Command to launch an interactive repl session
|
|
|
|
--compile FILES : Command to compile files and write Lua to stdout
|
|
|
|
--eval SOURCE (-e) : Command to evaluate source code and print the result
|
2018-10-17 18:32:39 -07:00
|
|
|
|
2019-12-02 18:30:30 -08:00
|
|
|
--no-searcher : Skip installing package.searchers entry
|
|
|
|
--indent VAL : Indent compiler output with VAL
|
|
|
|
--add-package-path PATH : Add PATH to package.path for finding Lua modules
|
|
|
|
--add-fennel-path PATH : Add PATH to fennel.path for finding Fennel modules
|
|
|
|
--globals G1[,G2...] : Allow these globals in addition to standard ones
|
|
|
|
--globals-only G1[,G2] : Same as above, but exclude standard ones
|
2020-03-07 17:41:37 -08:00
|
|
|
--check-unused-locals : Raise error when compiling code with unused locals
|
2019-12-02 18:30:30 -08:00
|
|
|
--require-as-include : Inline required modules in the output
|
|
|
|
--metadata : Enable function metadata, even in compiled output
|
|
|
|
--no-metadata : Disable function metadata, even in REPL
|
|
|
|
--correlate : Make Lua output line numbers match Fennel input
|
2020-03-07 17:41:37 -08:00
|
|
|
--load FILE (-l) : Load the specified FILE before executing the command
|
|
|
|
--no-fennelrc : Skip loading ~/.fennelrc when launching repl
|
2019-12-02 18:30:30 -08:00
|
|
|
|
|
|
|
--help (-h) : Display this text
|
|
|
|
--version (-v) : Show version
|
|
|
|
|
|
|
|
Metadata is typically considered a development feature and is not recommended
|
|
|
|
for production. It is used for docstrings and enabled by default in the REPL.
|
|
|
|
|
2020-03-07 17:41:37 -08:00
|
|
|
When not given a command, runs the file given as the first argument.
|
|
|
|
When given neither command nor file, launches a repl.
|
2019-12-02 18:30:30 -08:00
|
|
|
|
|
|
|
If ~/.fennelrc exists, loads it before launching a repl.]]
|
|
|
|
|
|
|
|
local options = {}
|
|
|
|
|
|
|
|
local function dosafe(filename, opts, args)
|
2018-10-17 18:32:39 -07:00
|
|
|
local ok, val = xpcall(function()
|
2019-12-02 18:30:30 -08:00
|
|
|
return fennel.dofile(filename, opts, unpack(args))
|
2018-10-17 18:32:39 -07:00
|
|
|
end, fennel.traceback)
|
|
|
|
if not ok then
|
2019-12-02 18:30:30 -08:00
|
|
|
io.stderr:write(val .. "\n")
|
2018-10-17 18:32:39 -07:00
|
|
|
os.exit(1)
|
|
|
|
end
|
|
|
|
return val
|
|
|
|
end
|
|
|
|
|
2019-12-02 18:30:30 -08:00
|
|
|
local function allowGlobals(globalNames)
|
|
|
|
options.allowedGlobals = {}
|
|
|
|
for g in globalNames:gmatch("([^,]+),?") do
|
|
|
|
table.insert(options.allowedGlobals, g)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for i=#arg, 1, -1 do
|
|
|
|
if arg[i] == "--no-searcher" then
|
|
|
|
options.no_searcher = true
|
|
|
|
table.remove(arg, i)
|
|
|
|
elseif arg[i] == "--indent" then
|
|
|
|
options.indent = table.remove(arg, i+1)
|
2018-10-17 18:32:39 -07:00
|
|
|
if options.indent == "false" then options.indent = false end
|
2019-12-02 18:30:30 -08:00
|
|
|
table.remove(arg, i)
|
|
|
|
elseif arg[i] == "--add-package-path" then
|
|
|
|
local entry = table.remove(arg, i+1)
|
|
|
|
package.path = entry .. ";" .. package.path
|
|
|
|
table.remove(arg, i)
|
|
|
|
elseif arg[i] == "--add-fennel-path" then
|
|
|
|
local entry = table.remove(arg, i+1)
|
|
|
|
fennel.path = entry .. ";" .. fennel.path
|
|
|
|
table.remove(arg, i)
|
2020-03-07 17:41:37 -08:00
|
|
|
elseif arg[i] == "--load" or arg[i] == "-l" then
|
|
|
|
local file = table.remove(arg, i+1)
|
|
|
|
dosafe(file, options, {})
|
|
|
|
table.remove(arg, i)
|
|
|
|
elseif arg[i] == "--no-fennelrc" then
|
|
|
|
options.fennelrc = false
|
|
|
|
table.remove(arg, i)
|
2019-12-02 18:30:30 -08:00
|
|
|
elseif arg[i] == "--correlate" then
|
2019-04-20 18:08:03 -07:00
|
|
|
options.correlate = true
|
2019-12-02 18:30:30 -08:00
|
|
|
table.remove(arg, i)
|
2020-03-07 17:41:37 -08:00
|
|
|
elseif arg[i] == "--check-unused-locals" then
|
|
|
|
options.checkUnusedLocals = true
|
|
|
|
table.remove(arg, i)
|
2019-12-02 18:30:30 -08:00
|
|
|
elseif arg[i] == "--globals" then
|
|
|
|
allowGlobals(table.remove(arg, i+1))
|
|
|
|
for globalName in pairs(_G) do
|
|
|
|
table.insert(options.allowedGlobals, globalName)
|
|
|
|
end
|
|
|
|
table.remove(arg, i)
|
|
|
|
elseif arg[i] == "--globals-only" then
|
|
|
|
allowGlobals(table.remove(arg, i+1))
|
|
|
|
table.remove(arg, i)
|
|
|
|
elseif arg[i] == "--require-as-include" then
|
|
|
|
options.requireAsInclude = true
|
|
|
|
table.remove(arg, i)
|
|
|
|
elseif arg[i] == "--metadata" then
|
|
|
|
options.useMetadata = true
|
|
|
|
table.remove(arg, i)
|
|
|
|
elseif arg[i] == "--no-metadata" then
|
|
|
|
options.useMetadata = false
|
|
|
|
table.remove(arg, i)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if not options.no_searcher then
|
|
|
|
local opts = {}
|
|
|
|
for k,v in pairs(options) do opts[k] = v end
|
|
|
|
table.insert((package.loaders or package.searchers), fennel.make_searcher(opts))
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Try to load readline library
|
|
|
|
local function tryReadline(opts)
|
|
|
|
local ok, readline = pcall(require, "readline")
|
|
|
|
if ok then
|
|
|
|
readline.set_options({
|
|
|
|
keeplines = 1000
|
|
|
|
})
|
|
|
|
function opts.readChunk(parserState)
|
|
|
|
local prompt = parserState.stackSize > 0 and '.. ' or '>> '
|
|
|
|
local str = readline.readline(prompt)
|
|
|
|
if str then
|
|
|
|
return str .. "\n"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- completer is registered by the repl, until then returns empty list
|
|
|
|
local completer
|
|
|
|
function opts.registerCompleter(replCompleter)
|
|
|
|
completer = replCompleter
|
|
|
|
end
|
|
|
|
local function replCompleter(text, from, to)
|
|
|
|
if completer then
|
|
|
|
-- stop completed syms from adding extra space on match
|
|
|
|
readline.set_completion_append_character('')
|
|
|
|
return completer(text:sub(from, to))
|
|
|
|
else
|
|
|
|
return {}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
readline.set_complete_function(replCompleter)
|
|
|
|
end
|
|
|
|
end
|
2018-10-17 18:32:39 -07:00
|
|
|
|
|
|
|
if arg[1] == "--repl" or #arg == 0 then
|
|
|
|
local ppok, pp = pcall(fennel.dofile, fennel_dir .. "fennelview.fnl", options)
|
|
|
|
if ppok then
|
|
|
|
options.pp = pp
|
2019-12-02 18:30:30 -08:00
|
|
|
else
|
|
|
|
ppok, pp = pcall(require, "fennelview")
|
|
|
|
if ppok then
|
|
|
|
options.pp = pp
|
|
|
|
end
|
2018-10-17 18:32:39 -07:00
|
|
|
end
|
2019-12-02 18:30:30 -08:00
|
|
|
|
|
|
|
tryReadline(options)
|
|
|
|
|
2020-03-07 17:41:37 -08:00
|
|
|
if options.fennelrc ~= false then
|
|
|
|
local home = os.getenv("HOME")
|
|
|
|
local xdg_config_home = os.getenv("XDG_CONFIG_HOME") or home .. "/.config"
|
|
|
|
local xdg_initfile = xdg_config_home .. "/fennel/fennelrc"
|
|
|
|
local home_initfile = home .. "/.fennelrc"
|
|
|
|
local init, initFilename = io.open(xdg_initfile, "rb"), xdg_initfile
|
|
|
|
if not init then
|
|
|
|
init, initFilename = io.open(home_initfile, "rb"), home_initfile
|
|
|
|
end
|
|
|
|
|
|
|
|
if init then
|
|
|
|
init:close()
|
|
|
|
-- pass in options so fennerlrc can make changes to it
|
|
|
|
dosafe(initFilename, options, {options})
|
|
|
|
end
|
2018-10-17 18:32:39 -07:00
|
|
|
end
|
2020-03-07 17:41:37 -08:00
|
|
|
|
2019-12-02 18:30:30 -08:00
|
|
|
print("Welcome to Fennel " .. fennel.version .. "!")
|
|
|
|
if options.useMetadata ~= false then
|
|
|
|
print("Use (doc something) to view documentation.")
|
|
|
|
end
|
2018-10-17 18:32:39 -07:00
|
|
|
fennel.repl(options)
|
|
|
|
elseif arg[1] == "--compile" then
|
|
|
|
for i = 2, #arg do
|
2019-12-02 18:30:30 -08:00
|
|
|
local f = arg[i] == "-" and io.stdin or assert(io.open(arg[i], "rb"))
|
2018-10-17 18:32:39 -07:00
|
|
|
options.filename=arg[i]
|
|
|
|
local ok, val = xpcall(function()
|
|
|
|
return fennel.compileString(f:read("*all"), options)
|
|
|
|
end, fennel.traceback)
|
2019-12-02 18:30:30 -08:00
|
|
|
if ok then
|
|
|
|
print(val)
|
|
|
|
else
|
|
|
|
io.stderr:write(val .. "\n")
|
|
|
|
os.exit(1)
|
|
|
|
end
|
2018-10-17 18:32:39 -07:00
|
|
|
f:close()
|
|
|
|
end
|
2019-12-02 18:30:30 -08:00
|
|
|
elseif arg[1] == "--eval" or arg[1] == "-e" then
|
|
|
|
if arg[2] and arg[2] ~= "-" then
|
|
|
|
print(fennel.eval(arg[2], options))
|
|
|
|
else
|
|
|
|
local source = io.stdin:read("*a")
|
|
|
|
print(fennel.eval(source, options))
|
|
|
|
end
|
|
|
|
elseif arg[1] == "--version" or arg[1] == "-v" then
|
|
|
|
print("Fennel " .. fennel.version)
|
|
|
|
elseif #arg >= 1 and arg[1] ~= "--help" and arg[1] ~= "-h" then
|
2018-10-17 18:32:39 -07:00
|
|
|
local filename = table.remove(arg, 1) -- let the script have remaining args
|
2019-12-02 18:30:30 -08:00
|
|
|
if filename == "-" then
|
|
|
|
local source = io.stdin:read("*a")
|
|
|
|
fennel.eval(source, options)
|
|
|
|
else
|
|
|
|
dosafe(filename, options, arg)
|
|
|
|
end
|
2018-10-17 18:32:39 -07:00
|
|
|
else
|
|
|
|
print(help)
|
|
|
|
end
|