moonscript/bin/moonc

460 lines
9.4 KiB
Plaintext
Raw Normal View History

2011-08-13 04:07:44 +00:00
#!/usr/bin/env lua
2012-11-29 04:53:47 +00:00
local parse = require "moonscript.parse"
2012-11-28 08:42:43 +00:00
local compile = require "moonscript.compile"
2012-11-28 08:24:58 +00:00
local util = require "moonscript.util"
2013-01-11 01:53:47 +00:00
local dump_tree = require"moonscript.dump".tree
2013-02-28 03:17:16 +00:00
local alt_getopt = require "alt_getopt"
local lfs = require "lfs"
2014-06-17 17:45:42 +00:00
local dirsep = package.config:sub(1,1)
local opts, ind = alt_getopt.get_opts(arg, "lvhwt:o:pTXb", {
2013-12-26 09:29:00 +00:00
print = "p", tree = "T", version = "v", help = "h", lint = "l"
2011-08-13 04:07:44 +00:00
})
2012-01-22 19:53:13 +00:00
local read_stdin = arg[1] == "--"
local polling_rate = 1.0
2011-08-13 04:07:44 +00:00
local help = [[Usage: %s [options] files...
-h Print this message
-w Watch file/directory
-t path Specify where to place compiled files
-o file Write output to file
2011-08-13 04:07:44 +00:00
-p Write output to standard out
-T Write parse tree instead of code (to stdout)
2012-10-30 07:31:56 +00:00
-X Write line rewrite map instead of code (to stdout)
2013-12-26 09:29:00 +00:00
-l Perform lint on the file instead of compiling
2011-08-13 04:07:44 +00:00
-b Dump parse and compile time (doesn't write output)
2011-07-19 06:53:43 +00:00
-v Print version
2012-01-22 19:53:13 +00:00
-- Read from standard in, print to standard out
(Must be first and only argument)
]]
2011-07-19 06:53:43 +00:00
if opts.v then
local v = require "moonscript.version"
v.print_version()
os.exit()
end
function print_help(err)
2013-12-27 06:47:11 +00:00
local help_msg = help:format(arg[0])
2013-12-27 06:44:54 +00:00
if err then
io.stderr:write("Error: ".. err .. "\n")
2013-12-27 06:47:11 +00:00
io.stderr:write(help_msg .. "\n")
os.exit(1)
else
print(help_msg)
os.exit(0)
2013-12-27 06:44:54 +00:00
end
end
function mkdir(path)
2014-06-17 17:45:42 +00:00
local chunks = util.split(path, dirsep)
local accum
for _, dir in ipairs(chunks) do
2014-06-17 17:45:42 +00:00
accum = accum and accum.. dirsep ..dir or dir
lfs.mkdir(accum)
end
return lfs.attributes(path, "mode")
end
function normalize(path)
2014-06-17 17:45:42 +00:00
return path:match("^(.-)" .. dirsep .. "*$")..dirsep
end
function parse_dir(fname)
2014-06-17 17:45:42 +00:00
return fname:match("^(.-)[^" .. dirsep .. "]*$")
end
function parse_file(fname)
return fname:match("^.-([^" .. dirsep .. "]*)$")
end
-- convert .moon to .lua
function convert_path(path)
2014-06-17 17:45:42 +00:00
local new_path = path:gsub("%.moon$", ".lua")
if new_path == path then
new_path = path .. ".lua"
end
return new_path
end
2013-12-27 06:44:54 +00:00
function log_msg(...)
2011-08-13 04:07:44 +00:00
if not opts.p then
2013-12-27 06:44:54 +00:00
io.stderr:write(table.concat({...}, " ") .. "\n")
end
2011-08-13 04:07:44 +00:00
end
local gettime = nil
if opts.b then
pcall(function()
require "socket"
gettime = socket.gettime
end)
function format_time(time)
return ("%.3fms"):format(time*1000)
end
if not gettime then
print_help"LuaSocket needed for benchmark"
end
else
gettime = function() return 0 end
end
function write_file(fname, code)
if opts.p then
if code ~= "" then print(code) end
else
mkdir(parse_dir(fname))
2011-08-13 04:07:44 +00:00
local out_f = io.open(fname, "w")
if not out_f then
return nil, "Failed to write output: "..fname
end
out_f:write(code.."\n")
out_f:close()
end
return true
end
2011-08-13 04:07:44 +00:00
function compile_file(text, fname)
local parse_time = gettime()
local tree, err = parse.string(text)
2011-08-13 04:07:44 +00:00
parse_time = gettime() - parse_time
if not tree then
return nil, err
end
2011-08-13 04:07:44 +00:00
if opts.T then
opts.p = true
2013-01-11 01:53:47 +00:00
dump_tree(tree)
2011-08-13 04:07:44 +00:00
return ""
else
local compile_time = gettime()
2012-10-30 07:31:56 +00:00
local code, posmap_or_err, err_pos = compile.tree(tree)
2011-08-13 04:07:44 +00:00
compile_time = gettime() - compile_time
2012-10-30 07:31:56 +00:00
2011-08-13 04:07:44 +00:00
if not code then
2012-10-30 07:31:56 +00:00
return nil, compile.format_error(posmap_or_err, err_pos, text)
end
if opts.X then
opts.p = true
print("Pos", "Lua", ">>", "Moon")
print(util.debug_posmap(posmap_or_err, text, code))
return ""
2011-08-13 04:07:44 +00:00
end
if opts.b then
opts.p = true
return table.concat({
fname,
"Parse time \t" .. format_time(parse_time),
"Compile time\t" .. format_time(compile_time),
""
}, "\n")
end
return code
end
2011-08-13 04:07:44 +00:00
end
2011-08-13 04:07:44 +00:00
function compile_and_write(from, to)
local f = io.open(from)
if not f then
return nil, "Can't find file"
end
2011-08-13 04:07:44 +00:00
local text = f:read("*a")
2011-08-13 04:07:44 +00:00
local code, err = compile_file(text, from)
if not code then
return nil, err
end
return write_file(to, code)
end
function scan_directory(root, collected)
root = normalize(root)
collected = collected or {}
for fname in lfs.dir(root) do
2014-06-17 07:03:13 +00:00
if not fname:match("^%.") then
local full_path = root..fname
if lfs.attributes(full_path, "mode") == "directory" then
scan_directory(full_path, collected)
end
2013-01-14 07:38:38 +00:00
if fname:match("%.moon$") then
table.insert(collected, full_path)
end
end
end
return collected
end
2014-06-17 17:45:42 +00:00
function remove_dups(tbl, key_fn)
local hash = {}
local final = {}
for _, v in ipairs(tbl) do
2014-06-17 17:45:42 +00:00
local dup_key = key_fn and key_fn(v) or v
if not hash[dup_key] then
table.insert(final, v)
2014-06-17 17:45:42 +00:00
hash[dup_key] = true
end
end
return final
end
local function is_abs_path(path)
local first = path:sub(1, 1)
if dirsep == "\\" then
return first == "/" or first == "\\" or path:sub(2,1) == ":"
else
return first == dirsep
end
end
2014-06-17 17:45:42 +00:00
-- creates tuples of input and target
function get_files(fname, files)
files = files or {}
if lfs.attributes(fname, "mode") == "directory" then
2014-06-17 17:45:42 +00:00
local head = fname:match("^(.-)[^" .. dirsep .. "]*" .. dirsep .."?$")
for _, sub_fname in ipairs(scan_directory(fname)) do
local target_fname = convert_path(sub_fname)
if opts.t then
if head then
local start, stop = target_fname:find(head, 1, true)
if start == 1 then
target_fname = target_fname:sub(stop + 1)
end
end
target_fname = normalize(opts.t) .. target_fname
end
table.insert(files, {sub_fname, target_fname})
end
else
2014-06-17 17:45:42 +00:00
local target_fname = convert_path(fname)
if opts.t then
local prefix = normalize(opts.t)
if is_abs_path(target_fname) then
target_fname = parse_file(target_fname)
end
target_fname = prefix .. target_fname
2014-06-17 17:45:42 +00:00
end
table.insert(files, {fname, target_fname})
end
return files
end
if opts.h then print_help() end
2012-01-22 19:53:13 +00:00
if read_stdin then
local text = io.stdin:read("*a")
local tree, err = parse.string(text)
if not tree then error(err) end
local code, err, pos = compile.tree(tree)
if not code then
error(compile.format_error(err, pos, text))
end
print(code)
os.exit()
end
local inputs = {}
for i = ind, #arg do
table.insert(inputs, arg[i])
end
if #inputs == 0 then
print_help("No files specified")
end
local files = {}
for _, input in ipairs(inputs) do
get_files(input, files)
end
files = remove_dups(files, function(f)
return f[2]
end)
2014-06-17 17:45:42 +00:00
function get_sleep_func()
local sleep
if not pcall(function()
require "socket"
sleep = socket.sleep
end) then
2013-01-03 01:32:00 +00:00
-- This is set by moonc.c in windows binaries
sleep = require("moonscript")._sleep
end
if not sleep then
error("Missing sleep function; install LuaSocket")
end
return sleep
end
2011-10-09 21:52:50 +00:00
function plural(count, word)
if count ~= 1 then
word = word .. "s"
end
return table.concat({count, word}, " ")
end
-- returns an iterator that returns files that have been updated
function create_watcher(files)
local msg = "Starting watch loop (Ctrl-C to exit)"
local inotify
2011-10-09 09:03:09 +00:00
pcall(function()
inotify = require "inotify"
2011-10-09 09:03:09 +00:00
end)
2011-10-09 09:03:09 +00:00
if inotify then
local dirs = {}
for _, tuple in ipairs(files) do
local dir = parse_dir(tuple[1])
if dir == "" then
dir = "./"
end
table.insert(dirs, dir)
end
dirs = remove_dups(dirs)
return coroutine.wrap(function()
2013-12-27 06:44:54 +00:00
io.stderr:write(("%s with inotify [%s]"):format(msg, plural(#dirs, "dir")) .. "\n")
local wd_table = {}
2012-03-01 03:32:10 +00:00
local handle = inotify.init()
for _, dir in ipairs(dirs) do
2014-01-04 23:41:20 +00:00
local wd = handle:addwatch(dir, inotify.IN_CLOSE_WRITE, inotify.IN_MOVED_TO)
wd_table[wd] = dir
end
while true do
2011-10-09 21:52:50 +00:00
local events = handle:read()
if not events then
break
end
for _, ev in ipairs(events) do
local fname = ev.name
if fname:match("%.moon$") then
local dir = wd_table[ev.wd]
if dir ~= "./" then
fname = dir .. fname
end
-- TODO: check to make sure the file was in the original set
coroutine.yield(fname)
end
end
end
end)
else
-- poll the filesystem instead
local sleep = get_sleep_func()
return coroutine.wrap(function()
2013-12-27 06:44:54 +00:00
io.stderr:write(("%s with polling [%s]"):format(msg, plural(#files, "file")) .. "\n")
local mod_time = {}
while true do
for _, tuple in ipairs(files) do
local file = tuple[1]
local time = lfs.attributes(file, "modification")
if not mod_time[file] then
mod_time[file] = time
2011-05-30 08:23:35 +00:00
else
if time ~= mod_time[file] then
if time > mod_time[file] then
coroutine.yield(file)
mod_time[file] = time
end
end
2011-05-30 08:23:35 +00:00
end
end
sleep(polling_rate)
end
end)
end
end
if opts.w then
local watcher = create_watcher(files)
-- catches interrupt error for ctl-c
local protected = function()
local status, file = true, watcher()
if status then
return file
elseif file ~= "interrupted!" then
error(file)
end
end
for fname in protected do
local target = convert_path(fname)
local success, err = compile_and_write(fname, target)
if not success then
2013-12-27 06:44:54 +00:00
io.stderr:write(table.concat({
"",
"Error: " .. fname,
err,
"\n",
}, "\n"))
else
2013-12-27 06:44:54 +00:00
log_msg("Built:", fname, "->", target)
end
end
2013-12-27 06:44:54 +00:00
io.stderr:write("\nQuitting...\n")
2013-12-26 09:29:00 +00:00
elseif opts.l then
for _, tuple in pairs(files) do
local fname = tuple[1]
2013-12-26 09:29:00 +00:00
lint = require "moonscript.cmd.lint"
2013-12-27 23:32:06 +00:00
local res, err = lint.lint_file(fname)
2013-12-26 09:29:00 +00:00
if res then
2013-12-27 06:44:54 +00:00
io.stderr:write(res .. "\n\n")
2013-12-27 23:32:06 +00:00
elseif err then
io.stderr:write(fname .. "\n" .. err.. "\n\n")
2013-12-26 09:29:00 +00:00
end
end
else
for _, tuple in ipairs(files) do
local fname, target = unpack(tuple)
local success, err = compile_and_write(fname, target)
if not success then
2013-12-27 06:44:54 +00:00
io.stderr:write(fname .. "\t" .. err .. "\n")
2011-09-19 06:23:23 +00:00
os.exit(1)
2011-05-30 08:23:35 +00:00
else
log_msg("Built", fname, "->", target)
end
end
end