#!/usr/bin/env lua local parse = require "moonscript.parse" local compile = require "moonscript.compile" local util = require "moonscript.util" local dump_tree = require"moonscript.dump".tree local alt_getopt = require "alt_getopt" local lfs = require "lfs" local opts, ind = alt_getopt.get_opts(arg, "lvhwt:pTXb", { print = "p", tree = "T", version = "v", help = "h", lint = "l" }) local read_stdin = arg[1] == "--" local polling_rate = 1.0 local help = [[Usage: %s [options] files... -h Print this message -w Watch file/directory -t path Specify where to place compiled files -p Write output to standard out -T Write parse tree instead of code (to stdout) -X Write line rewrite map instead of code (to stdout) -l Perform lint on the file instead of compiling -b Dump parse and compile time (doesn't write output) -v Print version -- Read from standard in, print to standard out (Must be first and only argument) ]] if opts.v then local v = require "moonscript.version" v.print_version() os.exit() end function print_help(err) local help_msg = help:format(arg[0]) if err then io.stderr:write("Error: ".. err .. "\n") io.stderr:write(help_msg .. "\n") os.exit(1) else print(help_msg) os.exit(0) end end function mkdir(path) local chunks = util.split(path, "/") local accum for _, dir in ipairs(chunks) do accum = accum and accum.."/"..dir or dir lfs.mkdir(accum) end return lfs.attributes(path, "mode") end function normalize(path) return path:match("(.-)/*$").."/" end function get_dir(fname) return fname:match("^(.-)[^/]*$") end -- convert .moon to .lua function convert_path(path) return (path:gsub("%.moon$", ".lua")) end function log_msg(...) if not opts.p then io.stderr:write(table.concat({...}, " ") .. "\n") end 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(get_dir(fname)) 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 function compile_file(text, fname) local parse_time = gettime() local tree, err = parse.string(text) parse_time = gettime() - parse_time if not tree then return nil, err end if opts.T then opts.p = true dump_tree(tree) return "" else local compile_time = gettime() local code, posmap_or_err, err_pos = compile.tree(tree) compile_time = gettime() - compile_time if not code then 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 "" 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 end function compile_and_write(from, to) local f = io.open(from) if not f then return nil, "Can't find file" end local text = f:read("*a") 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 if not fname:match("^%.") then local full_path = root..fname if lfs.attributes(full_path, "mode") == "directory" then scan_directory(full_path, collected) end if fname:match("%.moon$") then table.insert(collected, full_path) end end end return collected end function append(a, b) for _, v in ipairs(b) do table.insert(a, v) end end function remove_dups(tbl) local hash = {} local final = {} for _, v in ipairs(tbl) do if not hash[v] then table.insert(final, v) hash[v] = true end end return final end function get_files(fname, files) files = files or {} if lfs.attributes(fname, "mode") == "directory" then append(files, scan_directory(fname)) else table.insert(files, "./"..fname) end return files end if opts.h then print_help() end 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 target_dir = "." if opts.t then if mkdir(opts.t) ~= "directory" then print_help("Invalid target dir") end target_dir = opts.t end target_dir = target_dir.."/" local files = {} for _, input in ipairs(inputs) do get_files(input, files) end files = remove_dups(files) function get_sleep_func() local sleep if not pcall(function() require "socket" sleep = socket.sleep end) then -- 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 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 pcall(function() inotify = require "inotify" end) if inotify then local dirs = {} for _, fname in ipairs(files) do table.insert(dirs, get_dir(fname)) end dirs = remove_dups(dirs) return coroutine.wrap(function() io.stderr:write(("%s with inotify [%s]"):format(msg, plural(#dirs, "dir")) .. "\n") local wd_table = {} local handle = inotify.init() for _, dir in ipairs(dirs) do local wd = handle:addwatch(dir, inotify.IN_CLOSE_WRITE) wd_table[wd] = dir end while true do local events = handle:read() if events then for _, ev in ipairs(events) do local fname = wd_table[ev.wd]..ev.name if fname:match("%.moon$") then coroutine.yield(fname) end end else break end end end) else -- poll the filesystem instead local sleep = get_sleep_func() return coroutine.wrap(function() io.stderr:write(("%s with polling [%s]"):format(msg, plural(#files, "file")) .. "\n") local mod_time = {} while true do for _, file in ipairs(files) do local time = lfs.attributes(file, "modification") if not mod_time[file] then mod_time[file] = time else if time ~= mod_time[file] then if time > mod_time[file] then coroutine.yield(file) mod_time[file] = time end end 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 = pcall(watcher) if status then return file elseif file ~= "interrupted!" then error(file) end end for fname in protected do local target = target_dir..convert_path(fname) local success, err = compile_and_write(fname, target) if not success then io.stderr:write(table.concat({ "", "Error: " .. fname, err, "\n", }, "\n")) else log_msg("Built:", fname, "->", target) end end io.stderr:write("\nQuitting...\n") elseif opts.l then for _, fname in pairs(files) do lint = require "moonscript.cmd.lint" local res = lint.lint_file(fname) if res then io.stderr:write(res .. "\n\n") end end else for _, fname in ipairs(files) do local success, err = compile_and_write(fname, target_dir..convert_path(fname)) if not success then io.stderr:write(fname .. "\t" .. err .. "\n") os.exit(1) else log_msg("Built", fname) end end end