nomsu/nomsu.lua

1711 lines
57 KiB
Lua
Raw Normal View History

local _pairs, _ipairs = pairs, ipairs
if jit then
package.cpath = "./luajit_lpeg/?.so;" .. package.cpath
package.path = "./luajit_lpeg/?.lua;" .. package.path
bit32 = require('bit')
end
lpeg = require('lpeg')
re = require('re')
2018-04-11 20:05:12 -07:00
lpeg.setmaxstack(10000)
local P, R, V, S, Cg, C, Cp, B, Cmt, Carg
P, R, V, S, Cg, C, Cp, B, Cmt, Carg = lpeg.P, lpeg.R, lpeg.V, lpeg.S, lpeg.Cg, lpeg.C, lpeg.Cp, lpeg.B, lpeg.Cmt, lpeg.Carg
2017-12-18 16:26:26 -08:00
local utils = require('utils')
local new_uuid = require('uuid')
2017-12-18 16:25:56 -08:00
local repr, stringify, min, max, equivalent, set, is_list, sum
repr, stringify, min, max, equivalent, set, is_list, sum = utils.repr, utils.stringify, utils.min, utils.max, utils.equivalent, utils.set, utils.is_list, utils.sum
local colors = setmetatable({ }, {
__index = function()
return ""
end
})
2018-04-19 17:23:44 -07:00
colored = setmetatable({ }, {
__index = function(_, color)
return (function(msg)
2018-04-11 20:05:12 -07:00
return colors[color] .. tostring(msg or '') .. colors.reset
end)
end
})
local insert, remove, concat
do
local _obj_0 = table
insert, remove, concat = _obj_0.insert, _obj_0.remove, _obj_0.concat
end
local match, sub, rep, gsub, format, byte
do
local _obj_0 = string
match, sub, rep, gsub, format, byte, match = _obj_0.match, _obj_0.sub, _obj_0.rep, _obj_0.gsub, _obj_0.format, _obj_0.byte, _obj_0.match
end
2018-04-08 18:11:44 -07:00
local debug_getinfo = debug.getinfo
local Nomsu, Lua, Source
2018-04-11 21:07:13 -07:00
do
2018-04-26 14:00:01 -07:00
local _obj_0 = require("code_obj")
Nomsu, Lua, Source = _obj_0.Nomsu, _obj_0.Lua, _obj_0.Source
2018-04-11 21:07:13 -07:00
end
local STDIN, STDOUT, STDERR = "/dev/fd/0", "/dev/fd/1", "/dev/fd/2"
2018-05-30 13:46:40 -07:00
string.as_lua_id = function(str)
return "_" .. (gsub(str, "%W", function(c)
2018-05-30 13:46:40 -07:00
if c == "_" then
return "__"
else
return format("_%x", byte(c))
2018-05-30 13:46:40 -07:00
end
end))
end
2018-04-13 14:54:35 -07:00
FILE_CACHE = setmetatable({ }, {
2018-04-11 20:05:12 -07:00
__index = function(self, filename)
local file = io.open(filename)
if not (file) then
return nil
end
local contents = file:read("*a")
2018-04-11 20:05:12 -07:00
file:close()
2018-04-20 16:23:53 -07:00
self[filename] = contents
return contents
2018-04-11 20:05:12 -07:00
end
})
local iterate_single
iterate_single = function(item, prev)
if item == prev then
return nil
else
return item
end
end
local all_files
all_files = function(path)
if match(path, "%.nom$") or match(path, "%.lua$") or match(path, "^/dev/fd/[012]$") then
return iterate_single, path
end
path = gsub(path, "\\", "\\\\")
path = gsub(path, "`", "")
path = gsub(path, '"', '\\"')
path = gsub(path, "$", "")
2018-05-24 15:51:06 -07:00
return coroutine.wrap(function()
local f = io.popen('find -L "' .. path .. '" -not -path "*/\\.*" -type f -name "*.nom"')
2018-05-24 15:51:06 -07:00
for line in f:lines() do
coroutine.yield(line)
end
local success = f:close()
if not (success) then
return error("Invalid file path: " .. tostring(path))
end
end)
end
2018-04-11 20:05:12 -07:00
local line_counter = re.compile([[ lines <- {| line (%nl line)* |}
line <- {} (!%nl .)*
]], {
nl = P("\r") ^ -1 * P("\n")
})
2018-04-11 21:07:13 -07:00
LINE_STARTS = setmetatable({ }, {
2018-04-11 20:05:12 -07:00
__mode = "k",
__index = function(self, k)
2018-04-18 15:45:58 -07:00
if type(k) ~= 'string' then
k = tostring(k)
do
local v = rawget(self, k)
if v then
return v
end
end
end
local line_starts = line_counter:match(k)
2018-04-11 20:05:12 -07:00
self[k] = line_starts
return line_starts
end
})
local pos_to_line
pos_to_line = function(str, pos)
local line_starts = LINE_STARTS[str]
local lo, hi = 1, #line_starts
while lo <= hi do
local mid = math.floor((lo + hi) / 2)
if line_starts[mid] > pos then
hi = mid - 1
else
lo = mid + 1
end
end
return hi
end
do
local STRING_METATABLE = getmetatable("")
STRING_METATABLE.__add = function(self, other)
return self .. stringify(other)
end
STRING_METATABLE.__index = function(self, i)
2018-05-14 14:45:38 -07:00
local ret = string[i]
if ret ~= nil then
return ret
end
if type(i) == 'number' then
return sub(self, i, i)
elseif type(i) == 'table' then
return sub(self, i[1], i[2])
end
end
end
local AST = require("nomsu_tree")
2018-01-19 17:28:40 -08:00
local NOMSU_DEFS
2017-12-30 14:31:07 -08:00
do
2018-01-19 17:28:40 -08:00
local _with_0 = { }
_with_0.nl = P("\r") ^ -1 * P("\n")
2018-01-19 17:28:40 -08:00
_with_0.ws = S(" \t")
_with_0.tonumber = tonumber
local string_escapes = {
n = "\n",
t = "\t",
b = "\b",
a = "\a",
v = "\v",
f = "\f",
r = "\r"
}
local digit, hex = R('09'), R('09', 'af', 'AF')
_with_0.escaped_char = (P("\\") * S("xX") * C(hex * hex)) / function(self)
return string.char(tonumber(self, 16))
end
_with_0.escaped_char = _with_0.escaped_char + ((P("\\") * C(digit * (digit ^ -2))) / function(self)
return string.char(tonumber(self))
end)
_with_0.escaped_char = _with_0.escaped_char + ((P("\\") * C(S("ntbavfr"))) / string_escapes)
_with_0.operator_char = S("'~`!@$^&*-+=|<>?/")
_with_0.utf8_char = (R("\194\223") * R("\128\191") + R("\224\239") * R("\128\191") * R("\128\191") + R("\240\244") * R("\128\191") * R("\128\191") * R("\128\191"))
_with_0.ident_char = R("az", "AZ", "09") + P("_") + _with_0.utf8_char
_with_0.indent = Cmt(Carg(1), function(self, start, userdata)
local indented = userdata.indent .. ' '
if sub(self, start, start + #indented - 1) == indented then
userdata.indent = indented
return start + #indented
end
2017-12-30 14:31:07 -08:00
end)
_with_0.dedent = Cmt(Carg(1), function(self, start, userdata)
local dedented = sub(userdata.indent, 1, -5)
if #match(self, "^[ ]*", start) <= #dedented then
userdata.indent = dedented
2017-12-30 14:31:07 -08:00
return start
end
2017-12-30 14:31:07 -08:00
end)
_with_0.nodent = Cmt(Carg(1), function(self, start, userdata)
if sub(self, start, start + #userdata.indent - 1) == userdata.indent then
return start + #userdata.indent
end
2017-12-30 14:31:07 -08:00
end)
_with_0.userdata = Carg(1)
_with_0.error = function(src, end_pos, start_pos, err_msg, userdata)
local seen_errors = userdata.errors
2018-05-03 16:30:55 -07:00
if seen_errors[start_pos] then
return true
2018-01-19 17:28:40 -08:00
end
2018-05-30 13:07:08 -07:00
if utils.size(seen_errors) >= 10 then
seen_errors[start_pos + 1] = colored.bright(colored.yellow(colored.onred("Too many errors, canceling parsing...")))
return #src + 1
2018-05-30 13:07:08 -07:00
end
2018-05-03 16:30:55 -07:00
local err_pos = start_pos
local line_no = pos_to_line(src, err_pos)
src = FILE_CACHE[userdata.source.filename]
local line_starts = LINE_STARTS[src]
local prev_line = line_no == 1 and "" or src:sub(line_starts[line_no - 1] or 1, line_starts[line_no] - 2)
local err_line = src:sub(line_starts[line_no], (line_starts[line_no + 1] or 0) - 2)
local next_line = src:sub(line_starts[line_no + 1] or -1, (line_starts[line_no + 2] or 0) - 2)
local i = err_pos - line_starts[line_no]
2018-05-30 13:07:08 -07:00
local pointer = ("-"):rep(i) .. "^"
err_msg = colored.bright(colored.yellow(colored.onred((err_msg or "Parse error") .. " at " .. tostring(userdata.source.filename) .. ":" .. tostring(line_no) .. ":")))
2018-05-03 16:30:55 -07:00
if #prev_line > 0 then
2018-05-30 13:07:08 -07:00
err_msg = err_msg .. ("\n" .. colored.dim(prev_line))
2018-05-03 16:30:55 -07:00
end
2018-05-30 13:07:08 -07:00
err_line = colored.white(err_line:sub(1, i)) .. colored.bright(colored.red(err_line:sub(i + 1, i + 1))) .. colored.dim(err_line:sub(i + 2, -1))
err_msg = err_msg .. "\n" .. tostring(err_line) .. "\n" .. tostring(colored.red(pointer))
2018-05-03 16:30:55 -07:00
if #next_line > 0 then
2018-05-30 13:07:08 -07:00
err_msg = err_msg .. ("\n" .. colored.dim(next_line))
2018-05-03 16:30:55 -07:00
end
seen_errors[start_pos] = err_msg
return true
2018-01-19 17:28:40 -08:00
end
NOMSU_DEFS = _with_0
end
setmetatable(NOMSU_DEFS, {
__index = function(self, key)
local make_node
make_node = function(start, value, stop, userdata)
local source
do
local _with_0 = userdata.source
source = Source(_with_0.filename, _with_0.start + start - 1, _with_0.start + stop - 1)
end
value.source = source
setmetatable(value, AST[key])
if value.__init then
value:__init()
2018-05-26 15:04:31 -07:00
end
return value
end
2018-01-19 17:28:40 -08:00
self[key] = make_node
return make_node
end
})
2018-04-11 20:05:12 -07:00
local NOMSU_PATTERN
2018-01-19 17:28:40 -08:00
do
2017-12-30 14:31:07 -08:00
local peg_tidier = re.compile([[ file <- {~ %nl* (def/comment) (%nl+ (def/comment))* %nl* ~}
def <- anon_def / captured_def
anon_def <- ({ident} (" "*) ":"
{((%nl " "+ [^%nl]*)+) / ([^%nl]*)}) -> "%1 <- %2"
captured_def <- ({ident} (" "*) "(" {ident} ")" (" "*) ":"
{((%nl " "+ [^%nl]*)+) / ([^%nl]*)}) -> "%1 <- (({} %3 {} %%userdata) -> %2)"
2017-12-30 14:31:07 -08:00
ident <- [a-zA-Z_][a-zA-Z0-9_]*
comment <- "--" [^%nl]*
]])
2018-04-11 20:05:12 -07:00
local nomsu_peg = peg_tidier:match(FILE_CACHE["nomsu.peg"])
NOMSU_PATTERN = re.compile(nomsu_peg, NOMSU_DEFS)
2017-12-30 14:31:07 -08:00
end
2017-09-13 16:22:04 -07:00
local NomsuCompiler
do
local _class_0
local compile_error, stub_pattern, var_pattern, _running_files, MAX_LINE, math_expression
local _base_0 = {
2018-05-03 22:33:44 -07:00
define_action = function(self, signature, fn, is_compile_action)
if is_compile_action == nil then
is_compile_action = false
end
if type(fn) ~= 'function' then
error("Not a function: " .. tostring(repr(fn)))
end
if type(signature) == 'string' then
signature = {
signature
}
elseif type(signature) ~= 'table' then
error("Invalid signature, expected list of strings, but got: " .. tostring(repr(signature)), 0)
end
local fn_info = debug_getinfo(fn, "u")
assert(not fn_info.isvararg, "Vararg functions aren't supported. Sorry, use a list instead.")
local fn_arg_positions
do
local _tbl_0 = { }
for i = 1, fn_info.nparams do
_tbl_0[debug.getlocal(fn, i)] = i
end
fn_arg_positions = _tbl_0
end
local actions = (is_compile_action and self.environment.COMPILE_ACTIONS or self.environment.ACTIONS)
local arg_orders = { }
for _index_0 = 1, #signature do
local alias = signature[_index_0]
2018-05-30 13:41:14 -07:00
local stub = concat(assert(stub_pattern:match(alias)), ' ')
local stub_args = assert(var_pattern:match(alias))
actions[stub] = fn
do
local _accum_0 = { }
local _len_0 = 1
for _index_1 = 1, #stub_args do
local a = stub_args[_index_1]
2018-05-30 13:46:40 -07:00
_accum_0[_len_0] = fn_arg_positions[string.as_lua_id(a)]
_len_0 = _len_0 + 1
end
arg_orders[stub] = _accum_0
end
end
self.environment.ARG_ORDERS[fn] = arg_orders
end,
define_compile_action = function(self, signature, fn)
return self:define_action(signature, fn, true)
end,
parse = function(self, nomsu_code)
2018-05-30 14:29:08 -07:00
assert(type(nomsu_code) ~= 'string')
local userdata = {
source_code = nomsu_code,
indent = "",
2018-05-24 20:27:08 -07:00
errors = { },
2018-05-26 15:04:31 -07:00
source = nomsu_code.source
}
local tree = NOMSU_PATTERN:match(tostring(nomsu_code), nil, userdata)
if not (tree) then
error("In file " .. tostring(colored.blue(filename)) .. " failed to parse:\n" .. tostring(colored.onyellow(colored.black(nomsu_code))))
end
2018-05-03 16:30:55 -07:00
if next(userdata.errors) then
local keys = utils.keys(userdata.errors)
table.sort(keys)
local errors
do
local _accum_0 = { }
local _len_0 = 1
for _index_0 = 1, #keys do
local k = keys[_index_0]
_accum_0[_len_0] = userdata.errors[k]
_len_0 = _len_0 + 1
end
errors = _accum_0
end
2018-05-30 13:07:08 -07:00
io.stderr:write(concat(errors, "\n\n") .. "\n")
os.exit()
2018-05-03 16:30:55 -07:00
end
2018-05-26 15:04:31 -07:00
return tree
end,
2018-04-19 19:43:23 -07:00
run = function(self, nomsu_code, compile_fn)
if compile_fn == nil then
compile_fn = nil
end
2018-05-30 14:29:08 -07:00
local tree = assert(self:parse(nomsu_code))
if type(tree) == 'number' then
return nil
2017-11-01 20:11:44 -07:00
end
local lua = self:tree_to_lua(tree):as_statements()
2018-04-13 14:54:35 -07:00
lua:declare_locals()
2018-04-19 19:43:23 -07:00
lua:prepend("-- File: " .. tostring(nomsu_code.source or "") .. "\n")
if compile_fn then
compile_fn(lua)
end
return self:run_lua(lua)
end,
run_file = function(self, filename, compile_fn)
2018-04-19 19:43:23 -07:00
if compile_fn == nil then
compile_fn = nil
end
2018-05-14 15:37:15 -07:00
local loaded = self.environment.LOADED
if loaded[filename] then
return loaded[filename]
end
local ret = nil
for filename in all_files(filename) do
local _continue_0 = false
repeat
2018-05-14 15:37:15 -07:00
if loaded[filename] then
ret = loaded[filename]
_continue_0 = true
break
end
for i, running in ipairs(_running_files) do
if running == filename then
local loop
do
local _accum_0 = { }
local _len_0 = 1
for j = i, #_running_files do
_accum_0[_len_0] = _running_files[j]
_len_0 = _len_0 + 1
end
loop = _accum_0
end
insert(loop, filename)
error("Circular import, this loops forever: " .. tostring(concat(loop, " -> ")))
end
end
insert(_running_files, filename)
if match(filename, "%.lua$") then
local file = assert(FILE_CACHE[filename], "Could not find file: " .. tostring(filename))
ret = self:run_lua(Lua(Source(filename, 1, #file), file))
elseif match(filename, "%.nom$") or match(filename, "^/dev/fd/[012]$") then
if not self.skip_precompiled then
local lua_filename = gsub(filename, "%.nom$", ".lua")
local file = FILE_CACHE[lua_filename]
if file then
ret = self:run_lua(Lua(Source(filename, 1, #file), file))
remove(_running_files)
_continue_0 = true
break
end
end
local file = file or FILE_CACHE[filename]
if not file then
error("File does not exist: " .. tostring(filename), 0)
end
ret = self:run(Nomsu(Source(filename, 1, #file), file), compile_fn)
else
error("Invalid filetype for " .. tostring(filename), 0)
end
2018-05-14 15:37:15 -07:00
loaded[filename] = ret or true
remove(_running_files)
_continue_0 = true
until true
if not _continue_0 then
break
end
end
2018-05-14 15:37:15 -07:00
loaded[filename] = ret or true
return ret
end,
run_lua = function(self, lua)
assert(type(lua) ~= 'string', "Attempt to run lua string instead of Lua (object)")
2018-04-24 20:17:19 -07:00
local lua_string = tostring(lua)
local run_lua_fn, err = load(lua_string, tostring(lua.source), "t", self.environment)
if not run_lua_fn then
local n = 1
local fn
fn = function()
n = n + 1
return ("\n%-3d|"):format(n)
end
local line_numbered_lua = "1 |" .. lua_string:gsub("\n", fn)
2018-04-11 20:05:12 -07:00
error("Failed to compile generated code:\n" .. tostring(colored.bright(colored.blue(colored.onblack(line_numbered_lua)))) .. "\n\n" .. tostring(err), 0)
end
local source_key = tostring(lua.source)
if not (self.source_map[source_key]) then
local map = { }
local offset = 1
local source = lua.source
local nomsu_str = tostring(FILE_CACHE[source.filename]:sub(source.start, source.stop))
local lua_line = 1
local nomsu_line = pos_to_line(nomsu_str, lua.source.start)
local fn
fn = function(s)
if type(s) == 'string' then
for nl in s:gmatch("\n") do
map[lua_line] = map[lua_line] or nomsu_line
lua_line = lua_line + 1
end
else
local old_line = nomsu_line
if s.source then
nomsu_line = pos_to_line(nomsu_str, s.source.start)
end
local _list_0 = s.bits
for _index_0 = 1, #_list_0 do
local b = _list_0[_index_0]
fn(b)
end
end
end
fn(lua)
map[lua_line] = map[lua_line] or nomsu_line
map[0] = 0
self.source_map[source_key] = map
end
return run_lua_fn()
2017-09-24 20:20:43 -07:00
end,
2018-05-26 15:04:31 -07:00
tree_to_lua = function(self, tree)
local _exp_0 = tree.type
if "Action" == _exp_0 then
local stub = tree.stub
local compile_action = self.environment.COMPILE_ACTIONS[stub]
if compile_action then
local args
do
local _accum_0 = { }
local _len_0 = 1
for _index_0 = 1, #tree do
local arg = tree[_index_0]
if type(arg) ~= "string" then
_accum_0[_len_0] = arg
_len_0 = _len_0 + 1
end
end
args = _accum_0
end
local arg_orders = self.environment.ARG_ORDERS[compile_action]
do
local _accum_0 = { }
local _len_0 = 1
local _list_0 = arg_orders[stub]
for _index_0 = 1, #_list_0 do
local p = _list_0[_index_0]
_accum_0[_len_0] = args[p - 1]
_len_0 = _len_0 + 1
end
args = _accum_0
end
local ret = compile_action(tree, unpack(args))
if not ret then
compile_error(tree, "Compile-time action:\n%s\nfailed to produce any Lua")
end
return ret
end
local action = rawget(self.environment.ACTIONS, stub)
2018-05-26 15:04:31 -07:00
local lua = Lua.Value(tree.source)
if not action and math_expression:match(stub) then
for i, tok in ipairs(tree) do
if type(tok) == 'string' then
lua:append(tok)
else
2018-05-26 15:04:31 -07:00
local tok_lua = self:tree_to_lua(tok)
if not (tok_lua.is_value) then
compile_error(tok, "Non-expression value inside math expression:\n%s")
end
if tok.type == "Action" then
tok_lua:parenthesize()
end
lua:append(tok_lua)
end
if i < #tree then
lua:append(" ")
end
end
return lua
end
local args = { }
for i, tok in ipairs(tree) do
local _continue_0 = false
repeat
if type(tok) == "string" then
_continue_0 = true
break
end
2018-05-26 15:04:31 -07:00
local arg_lua = self:tree_to_lua(tok)
if not (arg_lua.is_value) then
compile_error(tok, "Cannot use:\n%s\nas an argument to %s, since it's not an expression, it produces: %s", stub, repr(arg_lua))
end
insert(args, arg_lua)
_continue_0 = true
until true
if not _continue_0 then
break
end
end
if action then
do
local _accum_0 = { }
local _len_0 = 1
local _list_0 = self.environment.ARG_ORDERS[action][stub]
for _index_0 = 1, #_list_0 do
local p = _list_0[_index_0]
_accum_0[_len_0] = args[p]
_len_0 = _len_0 + 1
end
args = _accum_0
end
end
lua:append("ACTIONS[", repr(stub), "](")
for i, arg in ipairs(args) do
lua:append(arg)
if i < #args then
lua:append(", ")
end
end
lua:append(")")
return lua
elseif "EscapedNomsu" == _exp_0 then
local make_tree
make_tree = function(t)
if not (AST.is_syntax_tree(t)) then
return repr(t)
end
local bits
do
local _accum_0 = { }
local _len_0 = 1
for _index_0 = 1, #t do
local bit = t[_index_0]
_accum_0[_len_0] = make_tree(bit)
_len_0 = _len_0 + 1
end
bits = _accum_0
end
return t.type .. "(" .. repr(tostring(t.source)) .. ", " .. table.concat(bits, ", ") .. ")"
end
return Lua.Value(tree.source, make_tree(tree[1]))
elseif "Block" == _exp_0 then
2018-05-26 15:04:31 -07:00
local lua = Lua(tree.source)
for i, line in ipairs(tree) do
2018-05-26 15:04:31 -07:00
local line_lua = self:tree_to_lua(line)
if i > 1 then
lua:append("\n")
end
lua:append(line_lua:as_statements())
end
return lua
elseif "Text" == _exp_0 then
2018-05-26 15:04:31 -07:00
local lua = Lua.Value(tree.source)
local string_buffer = ""
for i, bit in ipairs(tree) do
local _continue_0 = false
repeat
if type(bit) == "string" then
string_buffer = string_buffer .. bit
_continue_0 = true
break
end
if string_buffer ~= "" then
if #lua.bits > 0 then
lua:append("..")
end
lua:append(repr(string_buffer))
string_buffer = ""
end
2018-05-26 15:04:31 -07:00
local bit_lua = self:tree_to_lua(bit)
if not (bit_lua.is_value) then
local src = ' ' .. gsub(tostring(self:tree_to_nomsu(bit)), '\n', '\n ')
local line = tostring(bit.source.filename) .. ":" .. tostring(pos_to_line(FILE_CACHE[bit.source.filename], bit.source.start))
compile_error(bit, "Cannot use:\n%s\nas a string interpolation value, since it's not an expression.")
end
if #lua.bits > 0 then
lua:append("..")
end
if bit.type ~= "Text" then
2018-05-26 15:04:31 -07:00
bit_lua = Lua.Value(bit.source, "stringify(", bit_lua, ")")
end
lua:append(bit_lua)
_continue_0 = true
until true
if not _continue_0 then
break
end
end
if string_buffer ~= "" or #lua.bits == 0 then
if #lua.bits > 0 then
lua:append("..")
end
lua:append(repr(string_buffer))
end
if #lua.bits > 1 then
lua:parenthesize()
end
return lua
elseif "List" == _exp_0 then
local lua = Lua.Value(tree.source, "list{")
local line_length = 0
for i, item in ipairs(tree) do
2018-05-26 15:04:31 -07:00
local item_lua = self:tree_to_lua(item)
if not (item_lua.is_value) then
compile_error(item, "Cannot use:\n%s\nas a list item, since it's not an expression.")
end
lua:append(item_lua)
local item_string = tostring(item_lua)
local last_line = match(item_string, "[^\n]*$")
if match(item_string, "\n") then
line_length = #last_line
else
line_length = line_length + #last_line
end
if i < #tree then
if line_length >= MAX_LINE then
lua:append(",\n ")
line_length = 0
else
lua:append(", ")
line_length = line_length + 2
end
end
end
lua:append("}")
return lua
elseif "Dict" == _exp_0 then
local lua = Lua.Value(tree.source, "dict{")
local line_length = 0
for i, entry in ipairs(tree) do
2018-05-26 15:04:31 -07:00
local entry_lua = self:tree_to_lua(entry)
lua:append(entry_lua)
local entry_lua_str = tostring(entry_lua)
local last_line = match(entry_lua_str, "\n([^\n]*)$")
if last_line then
line_length = #last_line
else
line_length = line_length + #entry_lua_str
end
if i < #tree then
if line_length >= MAX_LINE then
lua:append(",\n ")
line_length = 0
else
lua:append(", ")
line_length = line_length + 2
end
end
end
lua:append("}")
return lua
elseif "DictEntry" == _exp_0 then
local key, value = tree[1], tree[2]
2018-05-26 15:04:31 -07:00
local key_lua = self:tree_to_lua(key)
if not (key_lua.is_value) then
compile_error(tree[1], "Cannot use:\n%s\nas a dict key, since it's not an expression.")
end
2018-05-26 15:04:31 -07:00
local value_lua = value and self:tree_to_lua(value) or Lua.Value(key.source, "true")
if not (value_lua.is_value) then
compile_error(tree[2], "Cannot use:\n%s\nas a dict value, since it's not an expression.")
end
local key_str = match(tostring(key_lua), [=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=])
if key_str then
2018-05-26 15:04:31 -07:00
return Lua(tree.source, key_str, "=", value_lua)
elseif sub(tostring(key_lua), 1, 1) == "[" then
2018-05-26 15:04:31 -07:00
return Lua(tree.source, "[ ", key_lua, "]=", value_lua)
else
2018-05-26 15:04:31 -07:00
return Lua(tree.source, "[", key_lua, "]=", value_lua)
end
elseif "IndexChain" == _exp_0 then
local lua = self:tree_to_lua(tree[1])
if not (lua.is_value) then
compile_error(tree[1], "Cannot index:\n%s\nsince it's not an expression.")
end
local first_char = sub(tostring(lua), 1, 1)
if first_char == "{" or first_char == '"' or first_char == "[" then
lua:parenthesize()
end
for i = 2, #tree do
local key = tree[i]
2018-05-26 15:04:31 -07:00
local key_lua = self:tree_to_lua(key)
if not (key_lua.is_value) then
compile_error(key, "Cannot use:\n%s\nas an index, since it's not an expression.")
end
local key_lua_str = tostring(key_lua)
do
local lua_id = match(key_lua_str, "^['\"]([a-zA-Z_][a-zA-Z0-9_]*)['\"]$")
if lua_id then
lua:append("." .. tostring(lua_id))
elseif sub(key_lua_str, 1, 1) == '[' then
lua:append("[ ", key_lua, " ]")
else
lua:append("[", key_lua, "]")
end
end
end
return lua
elseif "Number" == _exp_0 then
return Lua.Value(tree.source, tostring(tree[1]))
elseif "Var" == _exp_0 then
return Lua.Value(tree.source, string.as_lua_id(tree[1]))
else
return error("Unknown type: " .. tostring(tree.type))
end
end,
tree_to_nomsu = function(self, tree, inline, can_use_colon)
if inline == nil then
inline = false
end
if can_use_colon == nil then
can_use_colon = false
end
local _exp_0 = tree.type
if "Action" == _exp_0 then
if inline then
2018-05-26 15:04:31 -07:00
local nomsu = Nomsu(tree.source)
for i, bit in ipairs(tree) do
if type(bit) == "string" then
if i > 1 then
nomsu:append(" ")
end
nomsu:append(bit)
else
local arg_nomsu = self:tree_to_nomsu(bit, true)
if not (arg_nomsu) then
return nil
end
if not (i == 1) then
nomsu:append(" ")
end
if bit.type == "Action" or bit.type == "Block" then
arg_nomsu:parenthesize()
end
nomsu:append(arg_nomsu)
end
end
return nomsu
else
2018-05-26 15:04:31 -07:00
local nomsu = Nomsu(tree.source)
local next_space = ""
local last_colon = nil
for i, bit in ipairs(tree) do
if type(bit) == "string" then
nomsu:append(next_space, bit)
next_space = " "
else
local arg_nomsu
if last_colon == i - 1 and bit.type == "Action" then
arg_nomsu = nil
elseif bit.type == "Block" then
arg_nomsu = nil
else
arg_nomsu = self:tree_to_nomsu(bit, true)
end
if arg_nomsu and #arg_nomsu < MAX_LINE then
if bit.type == "Action" then
if can_use_colon and i > 1 then
nomsu:append(match(next_space, "[^ ]*"), ": ", arg_nomsu)
next_space = "\n.."
last_colon = i
else
nomsu:append(next_space, "(", arg_nomsu, ")")
next_space = " "
end
else
nomsu:append(next_space, arg_nomsu)
next_space = " "
end
else
arg_nomsu = self:tree_to_nomsu(bit, nil, true)
if not (nomsu) then
return nil
end
if bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" then
if i == 1 then
2018-05-26 15:04:31 -07:00
arg_nomsu = Nomsu(bit.source, "(..)\n ", arg_nomsu)
else
2018-05-26 15:04:31 -07:00
arg_nomsu = Nomsu(bit.source, "\n ", arg_nomsu)
end
end
if last_colon == i - 1 and (bit.type == "Action" or bit.type == "Block") then
next_space = ""
end
nomsu:append(next_space, arg_nomsu)
next_space = "\n.."
end
if next_space == " " and #(match(tostring(nomsu), "[^\n]*$")) > MAX_LINE then
next_space = "\n.."
end
end
end
return nomsu
end
elseif "EscapedNomsu" == _exp_0 then
local nomsu = self:tree_to_nomsu(tree[1], true)
if nomsu == nil and not inline then
nomsu = self:tree_to_nomsu(tree[1])
2018-05-26 15:04:31 -07:00
return nomsu and Nomsu(tree.source, "\\:\n ", nomsu)
end
2018-05-26 15:04:31 -07:00
return nomsu and Nomsu(tree.source, "\\(", nomsu, ")")
elseif "Block" == _exp_0 then
if inline then
2018-05-26 15:04:31 -07:00
local nomsu = Nomsu(tree.source)
for i, line in ipairs(tree) do
if i > 1 then
nomsu:append("; ")
end
local line_nomsu = self:tree_to_nomsu(line, true)
if not (line_nomsu) then
return nil
end
nomsu:append(line_nomsu)
end
return nomsu
end
2018-05-26 15:04:31 -07:00
local nomsu = Nomsu(tree.source)
for i, line in ipairs(tree) do
line = assert(self:tree_to_nomsu(line, nil, true), "Could not convert line to nomsu")
nomsu:append(line)
if i < #tree then
nomsu:append("\n")
if match(tostring(line), "\n") then
nomsu:append("\n")
end
end
end
return nomsu
elseif "Text" == _exp_0 then
if inline then
2018-05-26 15:04:31 -07:00
local nomsu = Nomsu(tree.source, '"')
for _index_0 = 1, #tree do
local bit = tree[_index_0]
if type(bit) == 'string' then
nomsu:append((gsub(gsub(bit, "\\", "\\\\"), "\n", "\\n")))
else
local interp_nomsu = self:tree_to_nomsu(bit, true)
if interp_nomsu then
if bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" then
interp_nomsu:parenthesize()
end
nomsu:append("\\", interp_nomsu)
else
return nil
end
end
end
nomsu:append('"')
return nomsu
else
local inline_version = self:tree_to_nomsu(tree, true)
if inline_version and #inline_version <= MAX_LINE then
return inline_version
end
2018-05-26 15:04:31 -07:00
local nomsu = Nomsu(tree.source, '".."\n ')
for i, bit in ipairs(self) do
if type(bit) == 'string' then
nomsu:append((gsub(gsub(bit, "\\", "\\\\"), "\n", "\\n")))
else
local interp_nomsu = self:tree_to_nomsu(bit, true)
if interp_nomsu then
if bit.type ~= "List" and bit.type ~= "Dict" and bit.type ~= "Text" then
interp_nomsu:parenthesize()
end
nomsu:append("\\", interp_nomsu)
else
interp_nomsu = self:tree_to_nomsu(bit)
if not (interp_nomsu) then
return nil
end
nomsu:append("\\\n ", interp_nomsu)
if i < #self then
nomsu:append("\n ..")
end
end
end
end
return nomsu
end
elseif "List" == _exp_0 then
if inline then
2018-05-26 15:04:31 -07:00
local nomsu = Nomsu(tree.source, "[")
for i, item in ipairs(tree) do
local item_nomsu = self:tree_to_nomsu(item, true)
if not (item_nomsu) then
return nil
end
if i > 1 then
nomsu:append(", ")
end
nomsu:append(item_nomsu)
end
nomsu:append("]")
return nomsu
else
local inline_version = self:tree_to_nomsu(tree, true)
if inline_version and #inline_version <= MAX_LINE then
return inline_version
end
2018-05-26 15:04:31 -07:00
local nomsu = Nomsu(tree.source, "[..]")
local line = Nomsu(tree.source, "\n ")
for _index_0 = 1, #tree do
local item = tree[_index_0]
local item_nomsu = self:tree_to_nomsu(item, true)
if item_nomsu and #line + #", " + #item_nomsu <= MAX_LINE then
if #line.bits > 1 then
line:append(", ")
end
line:append(item_nomsu)
else
if not (item_nomsu) then
item_nomsu = self:tree_to_nomsu(item)
if not (item_nomsu) then
return nil
end
end
if #line.bits > 1 then
nomsu:append(line)
2018-05-26 15:04:31 -07:00
line = Nomsu(line.source, "\n ")
end
line:append(item_nomsu)
end
end
if #line.bits > 1 then
nomsu:append(line)
end
return nomsu
end
elseif "Dict" == _exp_0 then
if inline then
2018-05-26 15:04:31 -07:00
local nomsu = Nomsu(tree.source, "{")
for i, entry in ipairs(tree) do
local entry_nomsu = self:tree_to_nomsu(entry, true)
if not (entry_nomsu) then
return nil
end
if i > 1 then
nomsu:append(", ")
end
nomsu:append(entry_nomsu)
end
nomsu:append("}")
return nomsu
else
local inline_version = self:tree_to_nomsu(tree, true)
if inline_version then
return inline_version
end
2018-05-26 15:04:31 -07:00
local nomsu = Nomsu(tree.source, "{..}")
local line = Nomsu(tree.source, "\n ")
for _index_0 = 1, #tree do
local entry = tree[_index_0]
local entry_nomsu = self:tree_to_nomsu(entry)
if not (entry_nomsu) then
return nil
end
if #line + #tostring(entry_nomsu) <= MAX_LINE then
if #line.bits > 1 then
line:append(", ")
end
line:append(entry_nomsu)
else
if #line.bits > 1 then
nomsu:append(line)
2018-05-26 15:04:31 -07:00
line = Nomsu(line.source, "\n ")
end
line:append(entry_nomsu)
end
end
if #line.bits > 1 then
nomsu:append(line)
end
return nomsu
end
elseif "DictEntry" == _exp_0 then
local key, value = tree[1], tree[2]
local key_nomsu = self:tree_to_nomsu(key, true)
if not (key_nomsu) then
return nil
end
if key.type == "Action" or key.type == "Block" then
key_nomsu:parenthesize()
end
local value_nomsu
if value then
value_nomsu = self:tree_to_nomsu(value, true)
else
2018-05-26 15:04:31 -07:00
value_nomsu = Nomsu(tree.source, "")
end
if inline and not value_nomsu then
return nil
end
if not value_nomsu then
if inline then
return nil
end
value_nomsu = self:tree_to_nomsu(value)
if not (value_nomsu) then
return nil
end
end
2018-05-26 15:04:31 -07:00
return Nomsu(tree.source, key_nomsu, ":", value_nomsu)
elseif "IndexChain" == _exp_0 then
2018-05-26 15:04:31 -07:00
local nomsu = Nomsu(tree.source)
for i, bit in ipairs(tree) do
if i > 1 then
nomsu:append(".")
end
local bit_nomsu = self:tree_to_nomsu(bit, true)
if not (bit_nomsu) then
return nil
end
if bit.type == "Action" or bit.type == "Block" then
bit_nomsu:parenthesize()
end
nomsu:append(bit_nomsu)
end
return nomsu
elseif "Number" == _exp_0 then
return Nomsu(tree.source, tostring(tree[1]))
elseif "Var" == _exp_0 then
return Nomsu(tree.source, "%", tree[1])
else
return error("Unknown type: " .. tostring(tree.type))
end
end,
initialize_core = function(self)
local nomsu = self
self:define_compile_action("immediately %block", function(self, _block)
local lua = nomsu:tree_to_lua(_block):as_statements()
2018-04-11 20:05:12 -07:00
lua:declare_locals()
nomsu:run_lua(lua)
2018-05-26 15:04:31 -07:00
return Lua(_block.source, "if IMMEDIATE then\n ", lua, "\nend")
2018-04-13 14:54:35 -07:00
end)
2018-05-15 14:53:37 -07:00
local add_lua_string_bits
add_lua_string_bits = function(lua, code)
2018-04-20 16:23:53 -07:00
if code.type ~= "Text" then
lua:append(", ", nomsu:tree_to_lua(code))
2018-04-20 16:23:53 -07:00
return
2018-04-13 14:54:35 -07:00
end
for _index_0 = 1, #code do
local bit = code[_index_0]
2018-04-13 14:54:35 -07:00
lua:append(", ")
if type(bit) == "string" then
lua:append(repr(bit))
else
local bit_lua = nomsu:tree_to_lua(bit)
2018-04-13 14:54:35 -07:00
if not (bit_lua.is_value) then
compile_error(bit, "Cannot use:\n%s\nas a string interpolation value, since it's not an expression.")
2018-04-13 14:54:35 -07:00
end
lua:append(bit_lua)
end
end
2018-04-20 16:23:53 -07:00
end
self:define_compile_action("Lua %code", function(self, _code)
local lua = Lua.Value(_code.source, "Lua(", repr(tostring(_code.source)))
2018-05-15 14:53:37 -07:00
add_lua_string_bits(lua, _code)
2018-04-20 16:23:53 -07:00
lua:append(")")
return lua
end)
self:define_compile_action("Lua value %code", function(self, _code)
local lua = Lua.Value(_code.source, "Lua.Value(", repr(tostring(_code.source)))
2018-05-15 14:53:37 -07:00
add_lua_string_bits(lua, _code)
2018-04-13 14:54:35 -07:00
lua:append(")")
return lua
2018-04-11 20:05:12 -07:00
end)
2018-05-15 14:53:37 -07:00
local add_lua_bits
add_lua_bits = function(lua, code)
for _index_0 = 1, #code do
local bit = code[_index_0]
2017-10-19 18:16:48 -07:00
if type(bit) == "string" then
2018-04-11 20:05:12 -07:00
lua:append(bit)
2017-10-19 18:16:48 -07:00
else
local bit_lua = nomsu:tree_to_lua(bit)
2018-04-11 21:07:13 -07:00
if not (bit_lua.is_value) then
compile_error(bit, "Cannot use:\n%s\nas a string interpolation value, since it's not an expression.")
end
2018-04-11 20:05:12 -07:00
lua:append(bit_lua)
2017-10-19 18:16:48 -07:00
end
end
2018-04-11 20:05:12 -07:00
return lua
2018-05-15 14:53:37 -07:00
end
self:define_compile_action("lua> %code", function(self, _code)
if _code.type ~= "Text" then
2018-05-26 15:04:31 -07:00
return Lua(self.source, "nomsu:run_lua(", nomsu:tree_to_lua(_code), ");")
2018-05-15 14:53:37 -07:00
end
2018-05-26 15:04:31 -07:00
return add_lua_bits(Lua(self.source), _code)
end)
self:define_compile_action("=lua %code", function(self, _code)
2018-04-11 20:05:12 -07:00
if _code.type ~= "Text" then
2018-05-26 15:04:31 -07:00
return Lua.Value(self.source, "nomsu:run_lua(", nomsu:tree_to_lua(_code), ":as_statements('return '))")
end
2018-05-26 15:04:31 -07:00
return add_lua_bits(Lua.Value(self.source), _code)
end)
return self:define_compile_action("use %path", function(self, _path)
if not (_path.type == 'Text' and #_path == 1 and type(_path[1]) == 'string') then
return Lua(_path.source, "nomsu:run_file(" .. tostring(nomsu:tree_to_lua(_path)) .. ");")
2018-05-30 14:29:08 -07:00
end
local path = _path[1]
2018-05-14 15:37:15 -07:00
nomsu:run_file(path)
2018-05-26 15:04:31 -07:00
return Lua(_path.source, "nomsu:run_file(" .. tostring(repr(path)) .. ");")
end)
end
}
_base_0.__index = _base_0
_class_0 = setmetatable({
2018-01-19 17:28:40 -08:00
__init = function(self)
local NaN_surrogate = { }
local nil_surrogate = { }
self.ids = setmetatable({ }, {
__mode = "k",
__index = function(self, key)
if key == nil then
return self[nil_surrogate]
elseif key ~= key then
return self[NaN_surrogate]
end
local id = new_uuid()
self[key] = id
return id
end
})
self.source_map = { }
local _list_mt = {
__eq = utils.equivalent,
__tostring = function(self)
return "[" .. concat((function()
local _accum_0 = { }
local _len_0 = 1
for _index_0 = 1, #self do
local b = self[_index_0]
_accum_0[_len_0] = repr(b)
_len_0 = _len_0 + 1
end
return _accum_0
end)(), ", ") .. "]"
end
}
local list
list = function(t)
return setmetatable(t, _list_mt)
end
local _dict_mt = {
__eq = utils.equivalent,
__tostring = function(self)
return "{" .. concat((function()
local _accum_0 = { }
local _len_0 = 1
for k, v in pairs(self) do
_accum_0[_len_0] = tostring(repr(k)) .. ": " .. tostring(repr(v))
_len_0 = _len_0 + 1
end
return _accum_0
end)(), ", ") .. "}"
end
}
local dict
dict = function(t)
return setmetatable(t, _dict_mt)
end
self.environment = {
nomsu = self,
repr = repr,
stringify = stringify,
utils = utils,
lpeg = lpeg,
re = re,
compile_error = compile_error,
next = next,
unpack = unpack,
setmetatable = setmetatable,
coroutine = coroutine,
rawequal = rawequal,
getmetatable = getmetatable,
pcall = pcall,
error = error,
package = package,
os = os,
require = require,
tonumber = tonumber,
tostring = tostring,
string = string,
xpcall = xpcall,
module = module,
print = print,
loadfile = loadfile,
rawset = rawset,
_VERSION = _VERSION,
collectgarbage = collectgarbage,
rawget = rawget,
bit32 = bit32,
rawlen = rawlen,
table = table,
assert = assert,
dofile = dofile,
loadstring = loadstring,
type = type,
select = select,
debug = debug,
math = math,
io = io,
load = load,
pairs = pairs,
ipairs = ipairs,
list = list,
dict = dict
}
for k, v in pairs(AST) do
2018-03-05 18:44:26 -08:00
self.environment[k] = v
end
2018-04-12 18:01:51 -07:00
self.environment.Lua = Lua
self.environment.Nomsu = Nomsu
self.environment.Source = Source
self.environment.ACTIONS = setmetatable({ }, {
2018-01-19 17:28:40 -08:00
__index = function(self, key)
return function(...)
return error("Attempt to run undefined action: " .. tostring(key), 0)
end
2018-01-19 17:28:40 -08:00
end
})
2018-05-03 22:33:44 -07:00
self.environment.COMPILE_ACTIONS = { }
self.environment.ARG_ORDERS = setmetatable({ }, {
__mode = "k"
})
2018-01-19 17:28:40 -08:00
self.environment.LOADED = { }
self.environment.AST = AST
2018-01-19 17:28:40 -08:00
return self:initialize_core()
end,
__base = _base_0,
2017-09-13 16:22:04 -07:00
__name = "NomsuCompiler"
}, {
__index = _base_0,
__call = function(cls, ...)
local _self_0 = setmetatable({}, _base_0)
cls.__init(_self_0, ...)
return _self_0
end
})
_base_0.__class = _class_0
local self = _class_0
compile_error = function(tok, err_format_string, ...)
local file = FILE_CACHE[tok.source.filename]
local line_no = pos_to_line(file, tok.source.start)
local line_start = LINE_STARTS[file][line_no]
local src = colored.dim(file:sub(line_start, tok.source.start - 1))
src = src .. colored.underscore(colored.bright(colored.red(file:sub(tok.source.start, tok.source.stop - 1))))
local end_of_line = (LINE_STARTS[file][pos_to_line(file, tok.source.stop) + 1] or 0) - 1
src = src .. colored.dim(file:sub(tok.source.stop, end_of_line - 1))
src = ' ' .. src:gsub('\n', '\n ')
local err_msg = err_format_string:format(src, ...)
return error(tostring(tok.source.filename) .. ":" .. tostring(line_no) .. ": " .. err_msg, 0)
end
2018-05-30 12:06:44 -07:00
local stub_defs
do
stub_defs = {
word = (-R("09") * NOMSU_DEFS.ident_char ^ 1) + NOMSU_DEFS.operator_char ^ 1,
varname = (NOMSU_DEFS.ident_char ^ 1 * ((-P("'") * NOMSU_DEFS.operator_char ^ 1) + NOMSU_DEFS.ident_char ^ 1) ^ 0) ^ -1
}
end
stub_pattern = re.compile([=[ stub <- {| tok (([ ])* tok)* |} !.
2018-05-30 13:41:14 -07:00
tok <- ({'%'} %varname) / {%word}
]=], stub_defs)
var_pattern = re.compile("{| ((('%' {%varname}) / %word) ([ ])*)+ !. |}", stub_defs)
2018-05-14 15:37:15 -07:00
_running_files = { }
MAX_LINE = 80
math_expression = re.compile([[ ([+-] " ")* "%" (" " [*/^+-] (" " [+-])* " %")+ !. ]])
2017-09-13 16:22:04 -07:00
NomsuCompiler = _class_0
end
2018-04-08 18:11:44 -07:00
if arg and debug_getinfo(2).func ~= require then
colors = require('consolecolors')
2018-05-03 16:30:55 -07:00
local parser = re.compile([[ args <- {| (flag ";")* {:inputs: {| ({file} ";")* |} :} {:nomsu_args: {| ("--;" ({[^;]*} ";")*)? |} :} ";"? |} !.
2018-04-28 19:16:39 -07:00
flag <-
{:interactive: ("-i" -> true) :}
/ {:optimized: ("-O" -> true) :}
/ {:format: ("-f" -> true) :}
/ {:syntax: ("-s" -> true) :}
/ {:print_file: "-p" ";" {file} :}
/ {:output_file: "-o" ";" {file} :}
/ {:help: (("-h" / "--help") -> true) :}
file <- "-" / [^;]+
]], {
2018-04-28 19:16:39 -07:00
["true"] = function()
return true
end
})
local args = concat(arg, ";") .. ";"
2018-04-28 19:16:39 -07:00
args = parser:match(args)
if not args or args.help then
print([=[Nomsu Compiler
2018-05-03 16:30:55 -07:00
Usage: (lua nomsu.lua | moon nomsu.moon) [-i] [-O] [-f] [-s] [--help] [-o output] [-p print_file] file1 file2... [-- nomsu args...]
OPTIONS
-i Run the compiler in interactive mode (REPL)
-O Run the compiler in optimized mode (use precompiled .lua versions of Nomsu files, when available)
-f Auto-format the given Nomsu file and print the result.
-s Check the program for syntax errors.
-h/--help Print this message.
-o <file> Output the compiled Lua file to the given file (use "-" to output to stdout; if outputting to stdout and -p is not specified, -p will default to /dev/null)
-p <file> Print to the specified file instead of stdout.
2018-04-28 19:16:39 -07:00
<input> Input file can be "-" to use stdin.
]=])
os.exit()
end
local nomsu = NomsuCompiler()
2018-05-03 16:30:55 -07:00
nomsu.environment.arg = args.nomsu_args
2018-04-08 18:11:44 -07:00
local ok, to_lua = pcall(function()
return require('moonscript.base').to_lua
end)
if not ok then
to_lua = nil
end
local moonscript_line_tables = setmetatable({ }, {
__index = function(self, filename)
if not (to_lua) then
return nil
end
2018-04-11 20:05:12 -07:00
local _, line_table = to_lua(FILE_CACHE[filename])
2018-04-08 18:11:44 -07:00
self[filename] = line_table
return line_table
end
})
debug.getinfo = function(thread, f, what)
2018-04-12 18:01:51 -07:00
if what == nil then
f, what, thread = thread, f, nil
end
if type(f) == 'number' then
f = f + 1
end
local info
if thread == nil then
info = debug_getinfo(f, what)
else
info = debug_getinfo(thread, f, what)
end
2018-04-08 18:11:44 -07:00
if not info or not info.func then
return info
end
2018-04-12 20:39:17 -07:00
if info.short_src or info.source or info.linedefine or info.currentline then
do
local arg_orders = nomsu.environment.ARG_ORDERS[info.func]
if arg_orders then
info.name = next(arg_orders)
end
end
do
local map = nomsu.source_map[info.source]
if map then
if info.currentline then
info.currentline = assert(map[info.currentline])
end
if info.linedefined then
info.linedefined = assert(map[info.linedefined])
end
if info.lastlinedefined then
info.lastlinedefined = assert(map[info.lastlinedefined])
end
end
end
2018-04-08 18:11:44 -07:00
end
return info
end
local print_err_msg
print_err_msg = function(error_message, stack_offset)
if stack_offset == nil then
stack_offset = 3
end
io.stderr:write(tostring(colored.red("ERROR:")) .. " " .. tostring(colored.bright(colored.red((error_message or "")))) .. "\n")
io.stderr:write("stack traceback:\n")
2018-04-08 18:11:44 -07:00
ok, to_lua = pcall(function()
return require('moonscript.base').to_lua
end)
if not ok then
to_lua = function()
return nil
end
end
2018-04-20 16:23:53 -07:00
local nomsu_source = FILE_CACHE["nomsu.moon"]
local LINE_TABLES = setmetatable({ }, {
__index = function(self, file)
local _, line_table = to_lua(file)
self[file] = line_table or false
return line_table or false
end
})
local get_line
get_line = function(file, line_no)
local start = LINE_STARTS[file][line_no] or 1
local stop = (LINE_STARTS[file][line_no + 1] or 0) - 1
return file:sub(start, stop)
end
local level = stack_offset
2017-09-14 15:35:06 -07:00
while true do
local _continue_0 = false
repeat
2018-04-08 18:11:44 -07:00
local calling_fn = debug_getinfo(level)
if not calling_fn then
break
end
if calling_fn.func == run then
break
end
level = level + 1
2018-05-29 19:10:03 -07:00
local name = calling_fn.name and "function '" .. tostring(calling_fn.name) .. "'" or nil
if calling_fn.linedefined == 0 then
name = "main chunk"
end
if name == "run_lua_fn" then
_continue_0 = true
break
end
local line = nil
do
local map = nomsu.source_map[calling_fn.source]
if map then
if calling_fn.currentline then
calling_fn.currentline = assert(map[calling_fn.currentline])
end
if calling_fn.linedefined then
calling_fn.linedefined = assert(map[calling_fn.linedefined])
end
if calling_fn.lastlinedefined then
calling_fn.lastlinedefined = assert(map[calling_fn.lastlinedefined])
end
local filename, start, stop = calling_fn.source:match('@([^[]*)%[([0-9]+):([0-9]+)]')
assert(filename)
local file = FILE_CACHE[filename]:sub(tonumber(start), tonumber(stop))
local err_line = get_line(file, calling_fn.currentline):sub(1, -2)
2018-05-29 19:10:03 -07:00
local offending_statement = colored.bright(colored.red(err_line:match("^[ ]*(.*)")))
do
local arg_orders = nomsu.environment.ARG_ORDERS[calling_fn.func]
if arg_orders then
2018-05-29 19:10:03 -07:00
name = "action '" .. tostring(next(arg_orders)) .. "'"
else
2018-05-29 19:10:03 -07:00
name = "main chunk"
end
end
2018-05-29 19:10:03 -07:00
line = colored.yellow(tostring(filename) .. ":" .. tostring(calling_fn.currentline) .. " in " .. tostring(name) .. "\n " .. tostring(offending_statement))
else
local file
ok, file = pcall(function()
return FILE_CACHE[calling_fn.short_src]
end)
if not ok then
file = nil
end
local line_num
2018-05-29 19:10:03 -07:00
if name == nil then
local search_level = level
local _info = debug.getinfo(search_level)
while _info and (_info.func == pcall or _info.func == xpcall) do
search_level = search_level + 1
_info = debug.getinfo(search_level)
end
if _info then
for i = 1, 999 do
local varname, val = debug.getlocal(search_level, i)
if not varname then
break
end
2018-05-29 19:10:03 -07:00
if val == calling_fn.func then
name = "local '" .. tostring(varname) .. "'"
if not varname:match("%(") then
break
end
end
end
if not (name) then
for i = 1, _info.nups do
local varname, val = debug.getupvalue(_info.func, i)
if not varname then
break
end
if val == calling_fn.func then
name = "upvalue '" .. tostring(varname) .. "'"
if not varname:match("%(") then
break
end
end
end
end
end
end
if file and calling_fn.short_src:match(".moon$") and LINE_TABLES[file] then
local char = LINE_TABLES[file][calling_fn.currentline]
line_num = 1
for _ in file:sub(1, char):gmatch("\n") do
line_num = line_num + 1
end
2018-05-29 19:10:03 -07:00
line = colored.cyan(tostring(calling_fn.short_src) .. ":" .. tostring(line_num) .. " in " .. tostring(name or '?'))
else
line_num = calling_fn.currentline
2018-05-29 19:10:03 -07:00
if calling_fn.short_src == '[C]' then
line = colored.green(tostring(calling_fn.short_src) .. " in " .. tostring(name or '?'))
else
line = colored.blue(tostring(calling_fn.short_src) .. ":" .. tostring(calling_fn.currentline) .. " in " .. tostring(name or '?'))
end
end
if file then
local err_line = get_line(file, line_num):sub(1, -2)
2018-05-29 19:10:03 -07:00
local offending_statement = colored.bright(colored.red(err_line:match("^[ ]*(.*)$")))
line = line .. ("\n " .. offending_statement)
end
end
end
2018-05-29 19:10:03 -07:00
io.stderr:write(" " .. tostring(line) .. "\n")
if calling_fn.istailcall then
io.stderr:write(" " .. tostring(colored.dim(colored.white(" (...tail calls...)"))) .. "\n")
end
_continue_0 = true
until true
if not _continue_0 then
2017-09-14 15:35:06 -07:00
break
end
2017-09-14 18:18:42 -07:00
end
return io.stderr:flush()
end
local run
run = function()
2018-04-28 19:16:39 -07:00
for i, input in ipairs(args.inputs) do
if input == "-" then
args.inputs[i] = STDIN
end
end
if #args.inputs == 0 and not args.interactive then
args.inputs = {
"core"
}
args.interactive = true
end
local print_file
if args.print_file == "-" then
print_file = io.stdout
elseif args.print_file then
print_file = io.open(args.print_file, 'w')
elseif args.output_file == '-' then
print_file = nil
else
print_file = io.stdout
end
2018-04-28 19:16:39 -07:00
nomsu.skip_precompiled = not args.optimized
if print_file == nil then
nomsu.environment.print = function() end
elseif print_file ~= io.stdout then
nomsu.environment.print = function(...)
local N = select("#", ...)
if N > 0 then
print_file:write(tostring(select(1, ...)))
for i = 2, N do
print_file:write('\t', tostring(select(1, ...)))
end
end
2018-04-28 19:16:39 -07:00
print_file:write('\n')
return print_file:flush()
end
2018-04-28 19:16:39 -07:00
end
local compile_fn
if args.output_file then
2018-04-28 19:16:39 -07:00
compile_fn = function(code)
local output_file
if args.output_file == "-" then
output_file = io.stdout
elseif args.output_file then
output_file = io.open(args.output_file, 'w')
end
2018-04-28 19:16:39 -07:00
output_file:write("local IMMEDIATE = true;\n" .. tostring(code))
return output_file:flush()
end
else
compile_fn = nil
2018-04-28 19:16:39 -07:00
end
2018-05-03 16:30:55 -07:00
local parse_errs = { }
2018-04-28 19:16:39 -07:00
local _list_0 = args.inputs
for _index_0 = 1, #_list_0 do
local input = _list_0[_index_0]
if args.syntax then
for input_file in all_files(input) do
2018-05-03 16:30:55 -07:00
local err
ok, err = pcall(nomsu.parse, nomsu, Nomsu(input_file, io.open(input_file):read("*a")))
if not ok then
insert(parse_errs, err)
elseif print_file then
print_file:write("Parse succeeded: " .. tostring(input_file) .. "\n")
print_file:flush()
end
end
2018-04-28 19:16:39 -07:00
elseif args.format then
for input_file in all_files(input) do
local tree = nomsu:parse(io.open(input_file):read("*a"))
local formatted = tostring(self:tree_to_nomsu(tree))
if output_file then
output_file:write(formatted, "\n")
output_file:flush()
end
if print_file then
print_file:write(formatted, "\n")
print_file:flush()
end
end
2018-04-28 19:16:39 -07:00
elseif input == STDIN then
2018-06-05 16:44:36 -07:00
local file = io.input():read("*a")
FILE_CACHE.stdin = file
nomsu:run(Nomsu(Source('stdin', 1, #file), file), compile_fn)
else
2018-04-28 19:16:39 -07:00
nomsu:run_file(input, compile_fn)
end
end
2018-05-03 16:30:55 -07:00
if #parse_errs > 0 then
io.stderr:write(concat(parse_errs, "\n\n"))
io.stderr:flush()
os.exit(false, true)
elseif args.syntax then
os.exit(true, true)
end
2018-04-28 19:16:39 -07:00
if args.interactive then
while true do
io.write(colored.bright(colored.yellow(">> ")))
local buff = ""
while true do
local line = io.read("*L")
if line == "\n" or not line then
if #buff > 0 then
io.write("\027[1A\027[2K")
end
break
end
line = line:gsub("\t", " ")
buff = buff .. line
io.write(colored.dim(colored.yellow(".. ")))
end
if #buff == 0 then
break
end
local ret
ok, ret = pcall(nomsu.run, nomsu, buff)
if ok and ret ~= nil then
print("= " .. repr(ret))
elseif not ok then
print_err_msg(ret)
end
end
end
end
local err_hand
err_hand = function(error_message)
print_err_msg(error_message)
return os.exit(false, true)
2017-09-14 15:35:06 -07:00
end
2018-05-14 14:45:38 -07:00
do
local ldt
ok, ldt = pcall(require, 'ldt')
2018-05-29 19:23:28 -07:00
if ok then
2018-05-14 14:45:38 -07:00
ldt.guard(run)
else
xpcall(run, err_hand)
end
end
end
2017-09-13 16:22:04 -07:00
return NomsuCompiler