Some stuff changed to allow escaped args and some other ports from the
two_defs branch.
This commit is contained in:
parent
8c0816995a
commit
b3b8c4d731
@ -93,7 +93,7 @@ rule [dict from entries %items] =:
|
||||
%dict -> (%pair -> 1) = (%pair -> 2)
|
||||
%dict
|
||||
|
||||
compile [dict %items] to:
|
||||
compile [dict %items, d %items] to:
|
||||
if ((%items's "type") == "Thunk"):
|
||||
%item_codes = []
|
||||
for %func_call in (%items's "value"):
|
||||
|
@ -218,11 +218,48 @@ compile [when %branch_value == ? %body] to code:
|
||||
|end --when == ?
|
||||
%result
|
||||
|
||||
# With statement
|
||||
compile [with %thing = %value %action] to code: ".."
|
||||
# Try/except
|
||||
compile [..]
|
||||
try %action and if it succeeds %success or if it fails %fallback
|
||||
try %action and if it fails %fallback or if it succeeds %success
|
||||
..to code: ".."
|
||||
|do
|
||||
| local old_value = \(%thing as lua);
|
||||
| \(%thing as lua) = \(%value as lua);
|
||||
| \(%action as lua statements);
|
||||
| \(%thing as lua) = old_value;
|
||||
| local fell_through = false;
|
||||
| local ok, ret1, ret2 = pcall(function(nomsu, vars)
|
||||
| \(%action as lua statements)
|
||||
| fell_through = true;
|
||||
| end, nomsu, vars);
|
||||
| if ok then
|
||||
| \(%success as lua statements)
|
||||
| end
|
||||
| if not ok then
|
||||
| \(%fallback as lua statements)
|
||||
| elseif not fell_through then
|
||||
| return ret1, ret2;
|
||||
| end
|
||||
|end
|
||||
parse [try %action] as:
|
||||
try %action and if it succeeds {pass} or if it fails {pass}
|
||||
parse [try %action and if it fails %fallback] as:
|
||||
try %action and if it succeeds {pass} or if it fails %fallback
|
||||
parse [try %action and if it succeeds %success] as:
|
||||
try %action and if it succeeds %success or if it fails {pass}
|
||||
|
||||
# Do/finally:
|
||||
compile [do %action then always %final_action] to code: ".."
|
||||
|do
|
||||
| local fell_through = false;
|
||||
| local ok, ret1, ret2 = pcall(function(nomsu, vars)
|
||||
| \(%action as lua statements)
|
||||
| fell_through = true;
|
||||
| end, nomsu, vars);
|
||||
| local ok2, _ = pcall(function(nomsu, vars)
|
||||
| \(%final_action as lua statements)
|
||||
| end, nomsu, vars);
|
||||
| if not ok then nomsu:error(ret1); end
|
||||
| if not ok2 then nomsu:error(ret2); end
|
||||
| if not fell_through then
|
||||
| return ret1, ret2;
|
||||
| end
|
||||
|end
|
||||
|
||||
|
@ -3,52 +3,16 @@ require "lib/control_flow.nom"
|
||||
require "lib/operators.nom"
|
||||
require "lib/collections.nom"
|
||||
|
||||
# Permission functions
|
||||
rule [standardize rules %rules] =:
|
||||
if ((type of %rules) == "string"): %rules = [%rules]
|
||||
%stubs = (nomsu "get_stubs" [%rules])
|
||||
%result = []
|
||||
for %stub in %stubs:
|
||||
%def = ((nomsu's "defs")->%stub)
|
||||
if %def:
|
||||
%aliases = (%def's "aliases")
|
||||
for all %aliases: add % to %result
|
||||
..else: add %def to %result
|
||||
unique %result
|
||||
rule [called by %whitelist] =:
|
||||
if ((%whitelist's "type") != "List"): %whitelist = [%whitelist]
|
||||
%defs = (..)
|
||||
dict ([(nomsu's "defs")->(nomsu "get_stub" [%]), yes] for all %whitelist)
|
||||
for %caller in (nomsu's "callstack"):
|
||||
if (%caller == "#macro"): do next %caller
|
||||
if (%defs -> (nomsu "get_stub" [%caller's 1])): return (yes)
|
||||
return (no)
|
||||
|
||||
rule [restrict %rules to within %elite_rules] =:
|
||||
%rules = (standardize rules %rules)
|
||||
%elite_rules = (standardize rules %elite_rules)
|
||||
for all (flatten [%elite_rules, %rules]):
|
||||
assert ((nomsu's "defs") has key %) "Undefined function: \(%)"
|
||||
for %rule in %rules:
|
||||
assert (nomsu "check_permission" [%]) ".."
|
||||
|You do not have permission to restrict permissions for function: \(%)
|
||||
((nomsu) ->* ["defs",%rule,"whiteset"]) = (..)
|
||||
dict ([%, yes] for all %elite_rules)
|
||||
parse [fail unless called by %whitelist] as:
|
||||
unless (called by %whitelist): error "Failed to find \(%whitelist) in callstack."
|
||||
|
||||
rule [allow %elite_rules to use %rules] =:
|
||||
%rules = (standardize rules %rules)
|
||||
%elite_rules = (standardize rules %elite_rules)
|
||||
for all (flatten [%elite_rules, %rules]):
|
||||
assert ((nomsu's "defs") has key %) "Undefined function: \(%)"
|
||||
for %rule in %rules:
|
||||
assert (nomsu "check_permission" [%rule]) ".."
|
||||
|You do not have permission to grant permissions for function: \(%rule)
|
||||
%whiteset = ((nomsu) ->* ["defs",%rule,"whiteset"])
|
||||
if (not %whiteset): go to next %rule
|
||||
for all %elite_rules: %whiteset -> % = (yes)
|
||||
|
||||
rule [forbid %pleb_rules to use %rules] =:
|
||||
%rules = (standardize rules %rules)
|
||||
%pleb_rules = (standardize rules %pleb_rules)
|
||||
for all (flatten [%pleb_rules, %used]):
|
||||
assert ((nomsu's "defs") has key %) "Undefined function: \(%)"
|
||||
for all %rules:
|
||||
assert (nomsu "check_permission" [%]) ".."
|
||||
|You do not have permission to grant permissions for function: \(%)
|
||||
%whiteset = ((nomsu) ->* ["defs",%,"whiteset"])
|
||||
assert %whiteset ".."
|
||||
|Cannot individually restrict permissions for \(%) because it is currently
|
||||
|available to everyone. Perhaps you meant to use "restrict % to within %" instead?
|
||||
for all %pleb_rules: %whiteset's % = (nil)
|
||||
|
@ -130,24 +130,3 @@ lua> ".."
|
||||
| end;
|
||||
|end;
|
||||
|
||||
compile [try %action and if it fails %fallback] to code: ".."
|
||||
|do
|
||||
| local _write_err = nomsu.write_err
|
||||
| nomsu.write_err = function() end;
|
||||
| local had_return = true;
|
||||
| local ok, ret1, ret2 = pcall(function(nomsu, vars)
|
||||
| local ret
|
||||
| \(%action as lua statements)
|
||||
| had_return = false;
|
||||
| return ret;
|
||||
| end, nomsu, vars);
|
||||
| nomsu.write_err = _write_err;
|
||||
| if not ok then
|
||||
| \(%fallback as lua statements)
|
||||
| elseif had_return then
|
||||
| return ret1, ret2;
|
||||
| else
|
||||
| ret = ret1;
|
||||
| end
|
||||
|end
|
||||
|
||||
|
@ -2,9 +2,51 @@ require "lib/metaprogramming.nom"
|
||||
require "lib/utils.nom"
|
||||
require "lib/control_flow.nom"
|
||||
require "lib/operators.nom"
|
||||
require "lib/collections.nom"
|
||||
|
||||
|
||||
compile [say %str] to:
|
||||
if ((%str's "type") == "String"):
|
||||
"nomsu:writeln(\(%str as lua))"
|
||||
..else:
|
||||
"nomsu:writeln(nomsu:stringify(\(%str as lua)))"
|
||||
|
||||
compile [do %action] to code:
|
||||
if ((%action's "type") == "Thunk"):
|
||||
%action as lua statements
|
||||
..else:
|
||||
"(\(%action as lua))(nomsu, vars);"
|
||||
|
||||
# With statement
|
||||
compile [with %assignments %action] to code:
|
||||
%data = []
|
||||
for %i = %assignment in (%assignments' "value"):
|
||||
%tokens = (%assignment's "value")
|
||||
%var = (%tokens -> 1)
|
||||
%eq = (%tokens -> 2)
|
||||
assert (=lua "vars.eq and vars.eq.type == 'Word' and vars.eq.value == '='") ".."
|
||||
|Invalid format for 'with' statement. List entries must have the form %var = (value)
|
||||
%value = (%tokens -> 3)
|
||||
add (d{i=%i; var=%var; value=%value}) to %data
|
||||
%foo = (..)
|
||||
join (..)
|
||||
"local old_value\(%->"i") = \((%->"var") as lua); \((%->"var") as lua) = \((%->"value") as lua);"
|
||||
..for all %data
|
||||
..with glue "\n "
|
||||
".."
|
||||
|do
|
||||
| \(%foo)
|
||||
| local fell_through = false;
|
||||
| local ok, ret1, ret2 = pcall(function(nomsu, vars)
|
||||
| \(%action as lua statements);
|
||||
| fell_through = true;
|
||||
| end, nomsu, vars);
|
||||
| \(join ("\((%->"var") as lua) = old_value\(%->"i");" for all %data) with glue "\n ")
|
||||
| if not ok then nomsu:error(ret1); end
|
||||
| if not fell_through then
|
||||
| return ret1, ret2;
|
||||
| end
|
||||
|end
|
||||
parse [with %thing = %value %action] as: with [%thing = %value] %action
|
||||
|
||||
|
||||
|
376
nomsu.lua
376
nomsu.lua
@ -19,6 +19,19 @@ do
|
||||
local _obj_0 = table
|
||||
insert, remove, concat = _obj_0.insert, _obj_0.remove, _obj_0.concat
|
||||
end
|
||||
if _VERSION == "Lua 5.1" then
|
||||
local xp = xpcall
|
||||
local xpcall
|
||||
xpcall = function(f, errhandler, ...)
|
||||
local args = {
|
||||
n = select("#", ...),
|
||||
...
|
||||
}
|
||||
return xp(function(...)
|
||||
return f(unpack(args, 1, args.n))
|
||||
end), errhandler
|
||||
end
|
||||
end
|
||||
lpeg.setmaxstack(10000)
|
||||
local P, V, S, Cg, C, Cp, B, Cmt
|
||||
P, V, S, Cg, C, Cp, B, Cmt = lpeg.P, lpeg.V, lpeg.S, lpeg.Cg, lpeg.C, lpeg.Cp, lpeg.B, lpeg.Cmt
|
||||
@ -166,12 +179,23 @@ local defs = {
|
||||
return pos, tostring(CURRENT_FILE) .. ":" .. tostring(line_no)
|
||||
end,
|
||||
FunctionCall = function(src, line_no, value, errors)
|
||||
local stub = concat((function()
|
||||
local _accum_0 = { }
|
||||
local _len_0 = 1
|
||||
for _index_0 = 1, #value do
|
||||
local t = value[_index_0]
|
||||
_accum_0[_len_0] = (t.type == "Word" and t.value or "%")
|
||||
_len_0 = _len_0 + 1
|
||||
end
|
||||
return _accum_0
|
||||
end)(), " ")
|
||||
return {
|
||||
type = "FunctionCall",
|
||||
src = src,
|
||||
line_no = line_no,
|
||||
value = value,
|
||||
errors = errors
|
||||
errors = errors,
|
||||
stub = stub
|
||||
}
|
||||
end,
|
||||
error = function(src, pos, errors, err_msg)
|
||||
@ -241,12 +265,22 @@ do
|
||||
end
|
||||
assert(type(thunk) == 'function', "Bad thunk: " .. tostring(repr(thunk)))
|
||||
local canonical_args = nil
|
||||
local canonical_escaped_args = nil
|
||||
local aliases = { }
|
||||
self.def_number = self.def_number + 1
|
||||
self.__class.def_number = self.__class.def_number + 1
|
||||
local def = {
|
||||
thunk = thunk,
|
||||
src = src,
|
||||
is_macro = is_macro,
|
||||
aliases = { },
|
||||
def_number = self.__class.def_number,
|
||||
defs = self.defs
|
||||
}
|
||||
local where_defs_go = ((getmetatable(self.defs) or { }).__newindex) or self.defs
|
||||
for _index_0 = 1, #signature do
|
||||
local _des_0 = signature[_index_0]
|
||||
local stub, arg_names
|
||||
stub, arg_names = _des_0[1], _des_0[2]
|
||||
local stub, arg_names, escaped_args
|
||||
stub, arg_names, escaped_args = _des_0[1], _des_0[2], _des_0[3]
|
||||
assert(stub, "NO STUB FOUND: " .. tostring(repr(signature)))
|
||||
if self.debug then
|
||||
self:writeln(tostring(colored.bright("DEFINING RULE:")) .. " " .. tostring(colored.underscore(colored.magenta(repr(stub)))) .. " " .. tostring(colored.bright("WITH ARGS")) .. " " .. tostring(colored.dim(repr(arg_names))))
|
||||
@ -263,38 +297,104 @@ do
|
||||
else
|
||||
canonical_args = utils.set(arg_names)
|
||||
end
|
||||
insert(aliases, stub)
|
||||
self.defs[stub] = {
|
||||
thunk = thunk,
|
||||
if canonical_escaped_args then
|
||||
assert(utils.equivalent(escaped_args, canonical_escaped_args), "Mismatched escaped args")
|
||||
else
|
||||
canonical_escaped_args = escaped_args
|
||||
def.escaped_args = escaped_args
|
||||
end
|
||||
insert(def.aliases, stub)
|
||||
local stub_def = setmetatable({
|
||||
stub = stub,
|
||||
arg_names = arg_names,
|
||||
src = src,
|
||||
is_macro = is_macro,
|
||||
aliases = aliases,
|
||||
def_number = self.def_number
|
||||
}
|
||||
escaped_args = escaped_args
|
||||
}, {
|
||||
__index = def
|
||||
})
|
||||
rawset(where_defs_go, stub, stub_def)
|
||||
end
|
||||
end,
|
||||
defmacro = function(self, signature, thunk, src)
|
||||
return self:def(signature, thunk, src, true)
|
||||
end,
|
||||
serialize_defs = function(self, after)
|
||||
scoped = function(self, thunk)
|
||||
local old_defs = self.defs
|
||||
self.defs = setmetatable({ }, {
|
||||
__index = old_defs
|
||||
})
|
||||
local ok, ret1, ret2 = pcall(thunk, self)
|
||||
self.defs = old_defs
|
||||
if not ok then
|
||||
self:error(ret1)
|
||||
end
|
||||
return ret1, ret2
|
||||
end,
|
||||
serialize_defs = function(self, scope, after)
|
||||
if scope == nil then
|
||||
scope = nil
|
||||
end
|
||||
if after == nil then
|
||||
after = 0
|
||||
end
|
||||
defs = { }
|
||||
for _, def in pairs(self.defs) do
|
||||
defs[def.def_number] = def.src or ""
|
||||
scope = scope or self.defs
|
||||
local defs_by_num = { }
|
||||
for stub, def in pairs(scope) do
|
||||
if def and stub:sub(1, 1) ~= "#" then
|
||||
defs_by_num[def.def_number] = def
|
||||
end
|
||||
end
|
||||
local keys
|
||||
do
|
||||
local _accum_0 = { }
|
||||
local _len_0 = 1
|
||||
for k, v in pairs(defs_by_num) do
|
||||
_accum_0[_len_0] = k
|
||||
_len_0 = _len_0 + 1
|
||||
end
|
||||
keys = _accum_0
|
||||
end
|
||||
local keys = utils.keys(defs)
|
||||
table.sort(keys)
|
||||
local buff = { }
|
||||
for _index_0 = 1, #keys do
|
||||
local i = keys[_index_0]
|
||||
if i > after and #defs[i] > 0 then
|
||||
insert(buff, defs[i])
|
||||
local k_i = 1
|
||||
local _using = nil
|
||||
local _using_do = { }
|
||||
for k_i, i in ipairs(keys) do
|
||||
local _continue_0 = false
|
||||
repeat
|
||||
if i <= after then
|
||||
_continue_0 = true
|
||||
break
|
||||
end
|
||||
local def = defs_by_num[i]
|
||||
if def.defs == scope then
|
||||
if def.src then
|
||||
insert(buff, def.src)
|
||||
end
|
||||
_continue_0 = true
|
||||
break
|
||||
end
|
||||
if _using == def.defs then
|
||||
if def.src then
|
||||
insert(_using_do, def.src)
|
||||
end
|
||||
else
|
||||
_using = def.defs
|
||||
_using_do = {
|
||||
def.src
|
||||
}
|
||||
end
|
||||
if k_i == #keys or defs_by_num[keys[k_i + 1]].defs ~= _using then
|
||||
insert(buff, "using:\n " .. tostring(self:indent(self:serialize_defs(_using))) .. "\n..do:\n " .. tostring(self:indent(concat(_using_do, "\n"))))
|
||||
end
|
||||
_continue_0 = true
|
||||
until true
|
||||
if not _continue_0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
for k, v in pairs(scope["#vars"] or { }) do
|
||||
insert(buff, "<@" .. tostring(k) .. "> = " .. tostring(self:value_to_nomsu(v)))
|
||||
end
|
||||
return concat(buff, "\n")
|
||||
end,
|
||||
call = function(self, stub, line_no, ...)
|
||||
@ -306,7 +406,7 @@ do
|
||||
stub,
|
||||
line_no
|
||||
})
|
||||
if def == nil then
|
||||
if not (def) then
|
||||
self:error("Attempt to call undefined function: " .. tostring(stub))
|
||||
end
|
||||
if not (def.is_macro) then
|
||||
@ -326,14 +426,16 @@ do
|
||||
self:write(tostring(colored.bright("CALLING")) .. " " .. tostring(colored.magenta(colored.underscore(stub))) .. " ")
|
||||
self:writeln(tostring(colored.bright("WITH ARGS:")) .. " " .. tostring(colored.dim(repr(args))))
|
||||
end
|
||||
local old_defs
|
||||
old_defs, self.defs = self.defs, def.defs
|
||||
local rets = {
|
||||
thunk(self, args)
|
||||
}
|
||||
self.defs = old_defs
|
||||
remove(self.callstack)
|
||||
return unpack(rets)
|
||||
end,
|
||||
run_macro = function(self, tree)
|
||||
local stub = self:get_stub(tree)
|
||||
local args
|
||||
do
|
||||
local _accum_0 = { }
|
||||
@ -349,14 +451,55 @@ do
|
||||
args = _accum_0
|
||||
end
|
||||
if self.debug then
|
||||
self:write(tostring(colored.bright("RUNNING MACRO")) .. " " .. tostring(colored.underscore(colored.magenta(stub))) .. " ")
|
||||
self:write(tostring(colored.bright("RUNNING MACRO")) .. " " .. tostring(colored.underscore(colored.magenta(tree.stub))) .. " ")
|
||||
self:writeln(tostring(colored.bright("WITH ARGS:")) .. " " .. tostring(colored.dim(repr(args))))
|
||||
end
|
||||
insert(self.callstack, "#macro")
|
||||
local expr, statement = self:call(stub, tree.line_no, unpack(args))
|
||||
local expr, statement = self:call(tree.stub, tree.line_no, unpack(args))
|
||||
remove(self.callstack)
|
||||
return expr, statement
|
||||
end,
|
||||
dedent = function(self, code)
|
||||
if not (code:find("\n")) then
|
||||
return code
|
||||
end
|
||||
local spaces, indent_spaces = math.huge, math.huge
|
||||
for line in code:gmatch("\n([^\n]*)") do
|
||||
local _continue_0 = false
|
||||
repeat
|
||||
if line:match("^%s*#.*") then
|
||||
_continue_0 = true
|
||||
break
|
||||
else
|
||||
do
|
||||
local s = line:match("^(%s*)%.%..*")
|
||||
if s then
|
||||
spaces = math.min(spaces, #s)
|
||||
else
|
||||
do
|
||||
s = line:match("^(%s*)%S.*")
|
||||
if s then
|
||||
indent_spaces = math.min(indent_spaces, #s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
_continue_0 = true
|
||||
until true
|
||||
if not _continue_0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
if spaces ~= math.huge and spaces < indent_spaces then
|
||||
return (code:gsub("\n" .. (" "):rep(spaces), "\n"))
|
||||
else
|
||||
return (code:gsub("\n" .. (" "):rep(indent_spaces), "\n "))
|
||||
end
|
||||
end,
|
||||
indent = function(self, code)
|
||||
return (code:gsub("\n", "\n "))
|
||||
end,
|
||||
assert_permission = function(self, stub)
|
||||
local fn_def = self.defs[stub]
|
||||
if not (fn_def) then
|
||||
@ -451,7 +594,7 @@ do
|
||||
local ok, expr, statements = pcall(self.tree_to_lua, self, statement)
|
||||
if not ok then
|
||||
self:errorln(tostring(colored.red("Error occurred in statement:")) .. "\n" .. tostring(colored.bright(colored.yellow(statement.src))))
|
||||
self:error(expr)
|
||||
error(expr)
|
||||
end
|
||||
local code_for_statement = ([[return (function(nomsu, vars)
|
||||
%s
|
||||
@ -480,7 +623,7 @@ end);]]):format(statements or "", expr or "ret")
|
||||
if not ok then
|
||||
self:errorln(tostring(colored.red("Error occurred in statement:")) .. "\n" .. tostring(colored.yellow(statement.src)))
|
||||
self:errorln(debug.traceback())
|
||||
self:error(ret)
|
||||
error(ret)
|
||||
end
|
||||
if statements then
|
||||
insert(buffer, statements)
|
||||
@ -514,10 +657,6 @@ end);]]):format(concat(buffer, "\n"))
|
||||
if force_inline == nil then
|
||||
force_inline = false
|
||||
end
|
||||
local indent
|
||||
indent = function(s)
|
||||
return s:gsub("\n", "\n ")
|
||||
end
|
||||
assert(tree, "No tree provided.")
|
||||
if not tree.type then
|
||||
self:errorln(debug.traceback())
|
||||
@ -553,7 +692,7 @@ end);]]):format(concat(buffer, "\n"))
|
||||
return _accum_0
|
||||
end)(), "; ")), true
|
||||
else
|
||||
return ":" .. indent("\n" .. concat((function()
|
||||
return ":" .. self:indent("\n" .. concat((function()
|
||||
local _accum_0 = { }
|
||||
local _len_0 = 1
|
||||
local _list_0 = tree.value
|
||||
@ -569,17 +708,23 @@ end);]]):format(concat(buffer, "\n"))
|
||||
local buff = ""
|
||||
local sep = ""
|
||||
local inline = true
|
||||
local do_arg
|
||||
do_arg = function(arg) end
|
||||
local line_len = 0
|
||||
local _list_0 = tree.value
|
||||
for _index_0 = 1, #_list_0 do
|
||||
local arg = _list_0[_index_0]
|
||||
local arg_inline
|
||||
nomsu, arg_inline = self:tree_to_nomsu(arg, force_inline)
|
||||
buff = buff .. sep
|
||||
if sep == " " and line_len + #nomsu > 80 then
|
||||
sep = "\n.."
|
||||
end
|
||||
if not (sep == " " and not arg_inline and nomsu:sub(1, 1) == ":") then
|
||||
buff = buff .. sep
|
||||
end
|
||||
if arg_inline then
|
||||
sep = " "
|
||||
line_len = line_len + (1 + #nomsu)
|
||||
else
|
||||
line_len = 0
|
||||
inline = false
|
||||
sep = "\n.."
|
||||
end
|
||||
@ -587,7 +732,7 @@ end);]]):format(concat(buffer, "\n"))
|
||||
if arg_inline then
|
||||
buff = buff .. "(" .. tostring(nomsu) .. ")"
|
||||
else
|
||||
buff = buff .. "(..)\n " .. tostring(indent(nomsu))
|
||||
buff = buff .. "(..)\n " .. tostring(self:indent(nomsu))
|
||||
end
|
||||
else
|
||||
buff = buff .. nomsu
|
||||
@ -658,6 +803,41 @@ end);]]):format(concat(buffer, "\n"))
|
||||
return self:error("Unknown/unimplemented thingy: " .. tostring(tree.type))
|
||||
end
|
||||
end,
|
||||
value_to_nomsu = function(self, value)
|
||||
local _exp_0 = type(value)
|
||||
if "nil" == _exp_0 then
|
||||
return "(nil)"
|
||||
elseif "bool" == _exp_0 then
|
||||
return value and "(yes)" or "(no)"
|
||||
elseif "number" == _exp_0 then
|
||||
return repr(value)
|
||||
elseif "table" == _exp_0 then
|
||||
if utils.is_list(value) then
|
||||
return "[" .. tostring(concat((function()
|
||||
local _accum_0 = { }
|
||||
local _len_0 = 1
|
||||
for _index_0 = 1, #value do
|
||||
local v = value[_index_0]
|
||||
_accum_0[_len_0] = self:value_to_nomsu(v)
|
||||
_len_0 = _len_0 + 1
|
||||
end
|
||||
return _accum_0
|
||||
end)(), ", ")) .. "]"
|
||||
else
|
||||
return "(d{" .. tostring(concat((function()
|
||||
local _accum_0 = { }
|
||||
local _len_0 = 1
|
||||
for k, v in pairs(value) do
|
||||
_accum_0[_len_0] = tostring(self:value_to_nomsu(k)) .. "=" .. tostring(self:value_to_nomsu(v))
|
||||
_len_0 = _len_0 + 1
|
||||
end
|
||||
return _accum_0
|
||||
end)(), "; ")) .. "})"
|
||||
end
|
||||
else
|
||||
return error("Unsupported value_to_nomsu type: " .. tostring(type(value)))
|
||||
end
|
||||
end,
|
||||
tree_to_lua = function(self, tree)
|
||||
assert(tree, "No tree provided.")
|
||||
if not tree.type then
|
||||
@ -668,7 +848,7 @@ end);]]):format(concat(buffer, "\n"))
|
||||
if "File" == _exp_0 then
|
||||
return error("Should not be converting File to lua through this function.")
|
||||
elseif "Nomsu" == _exp_0 then
|
||||
return "nomsu:parse(" .. tostring(repr(tree.value.src)) .. ").value[1]", nil
|
||||
return "nomsu:parse(" .. tostring(repr(tree.value.src)) .. ", " .. tostring(repr(CURRENT_FILE)) .. ").value[1]", nil
|
||||
elseif "Thunk" == _exp_0 then
|
||||
local lua_bits = { }
|
||||
local _list_0 = tree.value
|
||||
@ -688,24 +868,42 @@ local ret;
|
||||
return ret;
|
||||
end)]]):format(concat(lua_bits, "\n"))
|
||||
elseif "FunctionCall" == _exp_0 then
|
||||
local stub = self:get_stub(tree)
|
||||
local def = self.defs[stub]
|
||||
local def = self.defs[tree.stub]
|
||||
if def and def.is_macro then
|
||||
local expr, statement = self:run_macro(tree)
|
||||
if def.whiteset then
|
||||
if expr then
|
||||
expr = "(nomsu:assert_permission(" .. tostring(repr(stub)) .. ") and " .. tostring(expr) .. ")"
|
||||
expr = "(nomsu:assert_permission(" .. tostring(repr(tree.stub)) .. ") and " .. tostring(expr) .. ")"
|
||||
end
|
||||
if statement then
|
||||
statement = "nomsu:assert_permission(" .. tostring(repr(stub)) .. ");\n" .. statement
|
||||
statement = "nomsu:assert_permission(" .. tostring(repr(tree.stub)) .. ");\n" .. statement
|
||||
end
|
||||
end
|
||||
return expr, statement
|
||||
end
|
||||
local args = {
|
||||
repr(stub),
|
||||
repr(tree.stub),
|
||||
repr(tree.line_no)
|
||||
}
|
||||
local arg_names, escaped_args
|
||||
if def then
|
||||
arg_names, escaped_args = def.arg_names, def.escaped_args
|
||||
else
|
||||
arg_names, escaped_args = (function()
|
||||
local _accum_0 = { }
|
||||
local _len_0 = 1
|
||||
local _list_0 = tree.value
|
||||
for _index_0 = 1, #_list_0 do
|
||||
local w = _list_0[_index_0]
|
||||
if w.type == "Word" then
|
||||
_accum_0[_len_0] = w.value
|
||||
_len_0 = _len_0 + 1
|
||||
end
|
||||
end
|
||||
return _accum_0
|
||||
end)(), { }
|
||||
end
|
||||
local arg_num = 1
|
||||
local _list_0 = tree.value
|
||||
for _index_0 = 1, #_list_0 do
|
||||
local _continue_0 = false
|
||||
@ -715,11 +913,18 @@ end)]]):format(concat(lua_bits, "\n"))
|
||||
_continue_0 = true
|
||||
break
|
||||
end
|
||||
if escaped_args[arg_names[arg_num]] then
|
||||
arg = {
|
||||
type = "Nomsu",
|
||||
value = arg
|
||||
}
|
||||
end
|
||||
local expr, statement = self:tree_to_lua(arg)
|
||||
if statement then
|
||||
self:error("Cannot use [[" .. tostring(arg.src) .. "]] as a function argument, since it's not an expression.")
|
||||
end
|
||||
insert(args, expr)
|
||||
arg_num = arg_num + 1
|
||||
_continue_0 = true
|
||||
until true
|
||||
if not _continue_0 then
|
||||
@ -883,41 +1088,38 @@ end)]]):format(concat(lua_bits, "\n"))
|
||||
self:error("Nothing to get stub from")
|
||||
end
|
||||
if type(x) == 'string' then
|
||||
local stub = x:gsub("([" .. tostring(wordbreaker) .. "]+)", " %1 "):gsub("%%%S+", "%%"):gsub("%s+", " "):gsub("^%s*", ""):gsub("%s*$", "")
|
||||
x = x:gsub("\n%s*%.%.", " "):gsub("([" .. tostring(wordbreaker) .. "]+)", " %1 "):gsub("%s+", " ")
|
||||
x = x:gsub("^%s*", ""):gsub("%s*$", "")
|
||||
local stub = x:gsub("%%%S+", "%%"):gsub("\\", "")
|
||||
local arg_names
|
||||
do
|
||||
local _accum_0 = { }
|
||||
local _len_0 = 1
|
||||
for arg in x:gmatch("%%([^%s']*)") do
|
||||
for arg in x:gmatch("%%([^%s]*)") do
|
||||
_accum_0[_len_0] = arg
|
||||
_len_0 = _len_0 + 1
|
||||
end
|
||||
arg_names = _accum_0
|
||||
end
|
||||
return stub, arg_names
|
||||
local escaped_args = utils.set((function()
|
||||
local _accum_0 = { }
|
||||
local _len_0 = 1
|
||||
for arg in x:gmatch("\\%%([^%s]*)") do
|
||||
_accum_0[_len_0] = arg
|
||||
_len_0 = _len_0 + 1
|
||||
end
|
||||
return _accum_0
|
||||
end)())
|
||||
return stub, arg_names, escaped_args
|
||||
end
|
||||
if type(x) ~= 'table' then
|
||||
self:error("Invalid type for getting stub: " .. tostring(type(x)) .. " for:\n" .. tostring(repr(x)))
|
||||
end
|
||||
local _exp_0 = x.type
|
||||
if "String" == _exp_0 then
|
||||
return self:get_stub(x.value)
|
||||
elseif "FunctionCall" == _exp_0 then
|
||||
local stub, arg_names = { }, { }, { }
|
||||
local _list_0 = x.value
|
||||
for _index_0 = 1, #_list_0 do
|
||||
local token = _list_0[_index_0]
|
||||
local _exp_1 = token.type
|
||||
if "Word" == _exp_1 then
|
||||
insert(stub, token.value)
|
||||
elseif "Var" == _exp_1 then
|
||||
insert(stub, "%")
|
||||
if arg_names then
|
||||
insert(arg_names, token.value)
|
||||
end
|
||||
else
|
||||
insert(stub, "%")
|
||||
arg_names = nil
|
||||
end
|
||||
end
|
||||
return concat(stub, " "), arg_names
|
||||
return self:get_stub(x.src)
|
||||
else
|
||||
return self:error("Unsupported get stub type: " .. tostring(x.type) .. " for " .. tostring(repr(x)))
|
||||
end
|
||||
@ -974,11 +1176,11 @@ end)]]):format(concat(lua_bits, "\n"))
|
||||
end))
|
||||
end,
|
||||
error = function(self, msg)
|
||||
self:errorln((colored.red("ERROR!")))
|
||||
local error_msg = colored.red("ERROR!")
|
||||
if msg then
|
||||
self:errorln(colored.bright(colored.yellow(colored.onred(msg))))
|
||||
error_msg = error_msg .. ("\n" .. (colored.bright(colored.yellow(colored.onred(msg)))))
|
||||
end
|
||||
self:errorln("Callstack:")
|
||||
error_msg = error_msg .. "\nCallstack:"
|
||||
local maxlen = utils.max((function()
|
||||
local _accum_0 = { }
|
||||
local _len_0 = 1
|
||||
@ -994,12 +1196,12 @@ end)]]):format(concat(lua_bits, "\n"))
|
||||
end)())
|
||||
for i = #self.callstack, 1, -1 do
|
||||
if self.callstack[i] ~= "#macro" then
|
||||
self:errorln(" " .. tostring(("%-" .. tostring(maxlen) .. "s"):format(self.callstack[i][2])) .. "| " .. tostring(self.callstack[i][1]))
|
||||
error_msg = error_msg .. "\n " .. tostring(("%-" .. tostring(maxlen) .. "s"):format(self.callstack[i][2])) .. "| " .. tostring(self.callstack[i][1])
|
||||
end
|
||||
end
|
||||
self:errorln(" <top level>")
|
||||
error_msg = error_msg .. "\n <top level>"
|
||||
self.callstack = { }
|
||||
return error()
|
||||
return error(error_msg, 3)
|
||||
end,
|
||||
typecheck = function(self, vars, varname, desired_type)
|
||||
local x = vars[varname]
|
||||
@ -1009,17 +1211,19 @@ end)]]):format(concat(lua_bits, "\n"))
|
||||
if type(x) == 'table' and x.type == desired_type then
|
||||
return x
|
||||
end
|
||||
return self:error("Invalid type for %" .. tostring(varname) .. ". Expected " .. tostring(desired_type) .. ", but got " .. tostring(x.type) .. ".")
|
||||
return self:error("Invalid type for %" .. tostring(varname) .. ". Expected " .. tostring(desired_type) .. ", but got " .. tostring(repr(x)) .. ".")
|
||||
end,
|
||||
initialize_core = function(self)
|
||||
local nomsu_string_as_lua
|
||||
nomsu_string_as_lua = function(self, code)
|
||||
nomsu_string_as_lua = function(self, code, tree)
|
||||
local concat_parts = { }
|
||||
local _list_0 = code.value
|
||||
for _index_0 = 1, #_list_0 do
|
||||
local bit = _list_0[_index_0]
|
||||
if type(bit) == "string" then
|
||||
insert(concat_parts, bit)
|
||||
elseif type(bit) == "table" and bit.type == "FunctionCall" and bit.src == "__src__" then
|
||||
insert(concat_parts, repr(tree.src))
|
||||
else
|
||||
local expr, statement = self:tree_to_lua(bit)
|
||||
if statement then
|
||||
@ -1070,12 +1274,13 @@ end)]]):format(concat(lua_bits, "\n"))
|
||||
self:def("run file %filename", run_file)
|
||||
local _require
|
||||
_require = function(self, vars)
|
||||
if not self.loaded_files[vars.filename] then
|
||||
self.loaded_files[vars.filename] = run_file(self, {
|
||||
local loaded = self.defs["#loaded_files"]
|
||||
if not loaded[vars.filename] then
|
||||
loaded[vars.filename] = run_file(self, {
|
||||
filename = vars.filename
|
||||
}) or true
|
||||
end
|
||||
return self.loaded_files[vars.filename]
|
||||
return loaded[vars.filename]
|
||||
end
|
||||
return self:def("require %filename", _require)
|
||||
end
|
||||
@ -1089,10 +1294,21 @@ end)]]):format(concat(lua_bits, "\n"))
|
||||
self.write_err = function(self, ...)
|
||||
return io.stderr:write(...)
|
||||
end
|
||||
self.defs = setmetatable({ }, {
|
||||
__index = parent and parent.defs
|
||||
})
|
||||
self.def_number = 0
|
||||
self.defs = {
|
||||
["#vars"] = { },
|
||||
["#loaded_files"] = { }
|
||||
}
|
||||
if parent then
|
||||
setmetatable(self.defs, {
|
||||
__index = parent.defs
|
||||
})
|
||||
setmetatable(self.defs["#vars"], {
|
||||
__index = parent["#vars"]
|
||||
})
|
||||
setmetatable(self.defs["#loaded_files"], {
|
||||
__index = parent["#loaded_files"]
|
||||
})
|
||||
end
|
||||
self.callstack = { }
|
||||
self.debug = false
|
||||
self.utils = utils
|
||||
@ -1102,9 +1318,6 @@ end)]]):format(concat(lua_bits, "\n"))
|
||||
self.stringify = function(self, ...)
|
||||
return utils.stringify(...)
|
||||
end
|
||||
self.loaded_files = setmetatable({ }, {
|
||||
__index = parent and parent.loaded_files
|
||||
})
|
||||
if not parent then
|
||||
return self:initialize_core()
|
||||
end
|
||||
@ -1121,6 +1334,7 @@ end)]]):format(concat(lua_bits, "\n"))
|
||||
})
|
||||
_base_0.__class = _class_0
|
||||
local self = _class_0
|
||||
self.def_number = 0
|
||||
self.unescape_string = function(self, str)
|
||||
return str:gsub("\\(.)", (function(c)
|
||||
return STRING_ESCAPES[c] or c
|
||||
|
222
nomsu.moon
222
nomsu.moon
@ -19,6 +19,12 @@ colors = setmetatable({}, {__index:->""})
|
||||
colored = setmetatable({}, {__index:(_,color)-> ((msg)-> colors[color]..msg..colors.reset)})
|
||||
{:insert, :remove, :concat} = table
|
||||
--pcall = (fn,...)-> true, fn(...)
|
||||
if _VERSION == "Lua 5.1"
|
||||
xp = xpcall
|
||||
xpcall = (f, errhandler, ...)->
|
||||
args = {n:select("#", ...), ...}
|
||||
return xp((...)-> f(unpack(args,1,args.n))), errhandler
|
||||
--pcall = (fn, ...) -> xpcall(fn, debug.traceback, ...)
|
||||
|
||||
-- TODO:
|
||||
-- Maybe get GOTOs working at file scope.
|
||||
@ -28,7 +34,6 @@ colored = setmetatable({}, {__index:(_,color)-> ((msg)-> colors[color]..msg..col
|
||||
-- better scoping?
|
||||
-- better error reporting
|
||||
-- fix propagation of filename for error reporting
|
||||
-- add line numbers of function calls
|
||||
-- type checking?
|
||||
-- Fix compiler bug that breaks when file ends with a block comment
|
||||
-- Add compiler options for optimization level (compile-fast vs. run-fast, etc.)
|
||||
@ -162,7 +167,8 @@ defs =
|
||||
for _ in src\sub(1,pos)\gmatch("\n") do line_no += 1
|
||||
return pos, "#{CURRENT_FILE}:#{line_no}"
|
||||
FunctionCall: (src, line_no, value, errors)->
|
||||
{type: "FunctionCall", :src, :line_no, :value, :errors}
|
||||
stub = concat([(t.type == "Word" and t.value or "%") for t in *value], " ")
|
||||
{type: "FunctionCall", :src, :line_no, :value, :errors, :stub}
|
||||
error: (src,pos,errors,err_msg)->
|
||||
line_no = 1
|
||||
for _ in src\sub(1,-#errors)\gmatch("\n") do line_no += 1
|
||||
@ -190,17 +196,21 @@ setmetatable(defs, {
|
||||
nomsu = re.compile(nomsu, defs)
|
||||
|
||||
class NomsuCompiler
|
||||
@def_number: 0
|
||||
new:(parent)=>
|
||||
@write = (...)=> io.write(...)
|
||||
@write_err = (...)=> io.stderr\write(...)
|
||||
@defs = setmetatable({}, {__index:parent and parent.defs})
|
||||
@def_number = 0
|
||||
-- Use # to prevent someone from defining a function that has a namespace collision.
|
||||
@defs = {["#vars"]:{}, ["#loaded_files"]:{}}
|
||||
if parent
|
||||
setmetatable(@defs, {__index:parent.defs})
|
||||
setmetatable(@defs["#vars"], {__index:parent["#vars"]})
|
||||
setmetatable(@defs["#loaded_files"], {__index:parent["#loaded_files"]})
|
||||
@callstack = {}
|
||||
@debug = false
|
||||
@utils = utils
|
||||
@repr = (...)=> repr(...)
|
||||
@stringify = (...)=> utils.stringify(...)
|
||||
@loaded_files = setmetatable({}, {__index:parent and parent.loaded_files})
|
||||
if not parent
|
||||
@initialize_core!
|
||||
|
||||
@ -219,9 +229,12 @@ class NomsuCompiler
|
||||
signature = @get_stubs signature
|
||||
assert type(thunk) == 'function', "Bad thunk: #{repr thunk}"
|
||||
canonical_args = nil
|
||||
canonical_escaped_args = nil
|
||||
aliases = {}
|
||||
@def_number += 1
|
||||
for {stub, arg_names} in *signature
|
||||
@@def_number += 1
|
||||
def = {:thunk, :src, :is_macro, aliases:{}, def_number:@@def_number, defs:@defs}
|
||||
where_defs_go = ((getmetatable(@defs) or {}).__newindex) or @defs
|
||||
for {stub, arg_names, escaped_args} in *signature
|
||||
assert stub, "NO STUB FOUND: #{repr signature}"
|
||||
if @debug then @writeln "#{colored.bright "DEFINING RULE:"} #{colored.underscore colored.magenta repr(stub)} #{colored.bright "WITH ARGS"} #{colored.dim repr(arg_names)}"
|
||||
for i=1,#arg_names-1 do for j=i+1,#arg_names
|
||||
@ -229,22 +242,58 @@ class NomsuCompiler
|
||||
if canonical_args
|
||||
assert utils.equivalent(utils.set(arg_names), canonical_args), "Mismatched args"
|
||||
else canonical_args = utils.set(arg_names)
|
||||
insert aliases, stub
|
||||
@defs[stub] = {:thunk, :stub, :arg_names, :src, :is_macro, :aliases, def_number:@def_number}
|
||||
if canonical_escaped_args
|
||||
assert utils.equivalent(escaped_args, canonical_escaped_args), "Mismatched escaped args"
|
||||
else
|
||||
canonical_escaped_args = escaped_args
|
||||
def.escaped_args = escaped_args
|
||||
insert def.aliases, stub
|
||||
stub_def = setmetatable({:stub, :arg_names, :escaped_args}, {__index:def})
|
||||
rawset(where_defs_go, stub, stub_def)
|
||||
|
||||
defmacro: (signature, thunk, src)=>
|
||||
@def(signature, thunk, src, true)
|
||||
|
||||
serialize_defs: (after=0)=>
|
||||
defs = {}
|
||||
for _, def in pairs(@defs)
|
||||
defs[def.def_number] = def.src or ""
|
||||
keys = utils.keys(defs)
|
||||
scoped: (thunk)=>
|
||||
old_defs = @defs
|
||||
@defs = setmetatable({}, {__index:old_defs})
|
||||
ok, ret1, ret2 = pcall thunk, @
|
||||
@defs = old_defs
|
||||
if not ok then @error(ret1)
|
||||
return ret1, ret2
|
||||
|
||||
serialize_defs: (scope=nil, after=0)=>
|
||||
scope or= @defs
|
||||
defs_by_num = {}
|
||||
for stub, def in pairs(scope)
|
||||
if def and stub\sub(1,1) != "#"
|
||||
defs_by_num[def.def_number] = def
|
||||
keys = [k for k,v in pairs(defs_by_num)]
|
||||
table.sort(keys)
|
||||
|
||||
buff = {}
|
||||
for i in *keys
|
||||
if i > after and #defs[i] > 0
|
||||
insert buff, defs[i]
|
||||
k_i = 1
|
||||
_using = nil
|
||||
_using_do = {}
|
||||
for k_i,i in ipairs(keys)
|
||||
if i <= after then continue
|
||||
def = defs_by_num[i]
|
||||
if def.defs == scope
|
||||
if def.src
|
||||
insert buff, def.src
|
||||
continue
|
||||
if _using == def.defs
|
||||
if def.src
|
||||
insert _using_do, def.src
|
||||
else
|
||||
_using = def.defs
|
||||
_using_do = {def.src}
|
||||
if k_i == #keys or defs_by_num[keys[k_i+1]].defs != _using
|
||||
insert buff, "using:\n #{@indent @serialize_defs(_using)}\n..do:\n #{@indent concat(_using_do, "\n")}"
|
||||
|
||||
for k,v in pairs(scope["#vars"] or {})
|
||||
insert buff, "<@#{k}> = #{@value_to_nomsu v}"
|
||||
|
||||
return concat buff, "\n"
|
||||
|
||||
call: (stub,line_no,...)=>
|
||||
@ -254,7 +303,7 @@ class NomsuCompiler
|
||||
if def and def.is_macro and @callstack[#@callstack] != "#macro"
|
||||
@error "Attempt to call macro at runtime: #{stub}\nThis can be caused by using a macro in a function that is defined before the macro."
|
||||
insert @callstack, {stub, line_no}
|
||||
if def == nil
|
||||
unless def
|
||||
@error "Attempt to call undefined function: #{stub}"
|
||||
unless def.is_macro
|
||||
@assert_permission(stub)
|
||||
@ -263,22 +312,41 @@ class NomsuCompiler
|
||||
if @debug
|
||||
@write "#{colored.bright "CALLING"} #{colored.magenta(colored.underscore stub)} "
|
||||
@writeln "#{colored.bright "WITH ARGS:"} #{colored.dim repr(args)}"
|
||||
-- TODO: optimize, but still allow multiple return values?
|
||||
old_defs, @defs = @defs, def.defs
|
||||
rets = {thunk(self,args)}
|
||||
@defs = old_defs
|
||||
remove @callstack
|
||||
return unpack(rets)
|
||||
|
||||
run_macro: (tree)=>
|
||||
stub = @get_stub tree
|
||||
args = [arg for arg in *tree.value when arg.type != "Word"]
|
||||
if @debug
|
||||
@write "#{colored.bright "RUNNING MACRO"} #{colored.underscore colored.magenta(stub)} "
|
||||
@write "#{colored.bright "RUNNING MACRO"} #{colored.underscore colored.magenta(tree.stub)} "
|
||||
@writeln "#{colored.bright "WITH ARGS:"} #{colored.dim repr args}"
|
||||
insert @callstack, "#macro"
|
||||
expr, statement = @call(stub, tree.line_no, unpack(args))
|
||||
expr, statement = @call(tree.stub, tree.line_no, unpack(args))
|
||||
remove @callstack
|
||||
return expr, statement
|
||||
|
||||
dedent: (code)=>
|
||||
unless code\find("\n")
|
||||
return code
|
||||
spaces, indent_spaces = math.huge, math.huge
|
||||
for line in code\gmatch("\n([^\n]*)")
|
||||
if line\match("^%s*#.*")
|
||||
continue
|
||||
elseif s = line\match("^(%s*)%.%..*")
|
||||
spaces = math.min(spaces, #s)
|
||||
elseif s = line\match("^(%s*)%S.*")
|
||||
indent_spaces = math.min(indent_spaces, #s)
|
||||
if spaces != math.huge and spaces < indent_spaces
|
||||
return (code\gsub("\n"..(" ")\rep(spaces), "\n"))
|
||||
else
|
||||
return (code\gsub("\n"..(" ")\rep(indent_spaces), "\n "))
|
||||
|
||||
indent: (code)=>
|
||||
(code\gsub("\n","\n "))
|
||||
|
||||
assert_permission: (stub)=>
|
||||
fn_def = @defs[stub]
|
||||
unless fn_def
|
||||
@ -343,7 +411,7 @@ class NomsuCompiler
|
||||
ok,expr,statements = pcall(@tree_to_lua, self, statement)
|
||||
if not ok
|
||||
@errorln "#{colored.red "Error occurred in statement:"}\n#{colored.bright colored.yellow statement.src}"
|
||||
@error(expr)
|
||||
error(expr)
|
||||
code_for_statement = ([[
|
||||
return (function(nomsu, vars)
|
||||
%s
|
||||
@ -365,7 +433,7 @@ end);]])\format(statements or "", expr or "ret")
|
||||
if not ok
|
||||
@errorln "#{colored.red "Error occurred in statement:"}\n#{colored.yellow statement.src}"
|
||||
@errorln debug.traceback!
|
||||
@error(ret)
|
||||
error(ret)
|
||||
if statements
|
||||
insert buffer, statements
|
||||
if expr
|
||||
@ -392,8 +460,6 @@ end);]])\format(concat(buffer, "\n"))
|
||||
|
||||
tree_to_nomsu: (tree, force_inline=false)=>
|
||||
-- Return <nomsu code>, <is safe for inline use>
|
||||
indent = (s)->
|
||||
s\gsub("\n","\n ")
|
||||
assert tree, "No tree provided."
|
||||
if not tree.type
|
||||
@errorln debug.traceback()
|
||||
@ -410,27 +476,31 @@ end);]])\format(concat(buffer, "\n"))
|
||||
if force_inline
|
||||
return "{#{concat([@tree_to_nomsu(v, true) for v in *tree.value], "; ")}", true
|
||||
else
|
||||
return ":"..indent("\n"..concat([@tree_to_nomsu v for v in *tree.value], "\n")), false
|
||||
return ":"..@indent("\n"..concat([@tree_to_nomsu v for v in *tree.value], "\n")), false
|
||||
|
||||
when "FunctionCall"
|
||||
buff = ""
|
||||
sep = ""
|
||||
inline = true
|
||||
do_arg = (arg)->
|
||||
|
||||
line_len = 0
|
||||
for arg in *tree.value
|
||||
nomsu, arg_inline = @tree_to_nomsu(arg, force_inline)
|
||||
buff ..= sep
|
||||
if sep == " " and line_len + #nomsu > 80
|
||||
sep = "\n.."
|
||||
unless sep == " " and not arg_inline and nomsu\sub(1,1) == ":"
|
||||
buff ..= sep
|
||||
if arg_inline
|
||||
sep = " "
|
||||
line_len += 1 + #nomsu
|
||||
else
|
||||
line_len = 0
|
||||
inline = false
|
||||
sep = "\n.."
|
||||
if arg.type == 'FunctionCall'
|
||||
if arg_inline
|
||||
buff ..= "(#{nomsu})"
|
||||
else
|
||||
buff ..= "(..)\n #{indent nomsu}"
|
||||
buff ..= "(..)\n #{@indent nomsu}"
|
||||
else
|
||||
buff ..= nomsu
|
||||
return buff, inline
|
||||
@ -491,6 +561,22 @@ end);]])\format(concat(buffer, "\n"))
|
||||
else
|
||||
@error("Unknown/unimplemented thingy: #{tree.type}")
|
||||
|
||||
value_to_nomsu: (value)=>
|
||||
switch type(value)
|
||||
when "nil"
|
||||
return "(nil)"
|
||||
when "bool"
|
||||
return value and "(yes)" or "(no)"
|
||||
when "number"
|
||||
return repr(value)
|
||||
when "table"
|
||||
if utils.is_list(value)
|
||||
return "[#{concat [@value_to_nomsu(v) for v in *value], ", "}]"
|
||||
else
|
||||
return "(d{#{concat ["#{@value_to_nomsu(k)}=#{@value_to_nomsu(v)}" for k,v in pairs(value)], "; "}})"
|
||||
else
|
||||
error("Unsupported value_to_nomsu type: #{type(value)}")
|
||||
|
||||
tree_to_lua: (tree)=>
|
||||
-- Return <lua code for value>, <additional lua code>
|
||||
assert tree, "No tree provided."
|
||||
@ -502,7 +588,7 @@ end);]])\format(concat(buffer, "\n"))
|
||||
error("Should not be converting File to lua through this function.")
|
||||
|
||||
when "Nomsu"
|
||||
return "nomsu:parse(#{repr tree.value.src}).value[1]", nil
|
||||
return "nomsu:parse(#{repr tree.value.src}, #{repr CURRENT_FILE}).value[1]", nil
|
||||
|
||||
when "Thunk"
|
||||
lua_bits = {}
|
||||
@ -518,23 +604,31 @@ return ret;
|
||||
end)]])\format(concat(lua_bits, "\n"))
|
||||
|
||||
when "FunctionCall"
|
||||
stub = @get_stub(tree)
|
||||
def = @defs[stub]
|
||||
def = @defs[tree.stub]
|
||||
if def and def.is_macro
|
||||
expr, statement = @run_macro(tree)
|
||||
if def.whiteset
|
||||
if expr
|
||||
expr = "(nomsu:assert_permission(#{repr stub}) and #{expr})"
|
||||
expr = "(nomsu:assert_permission(#{repr tree.stub}) and #{expr})"
|
||||
if statement
|
||||
statement = "nomsu:assert_permission(#{repr stub});\n"..statement
|
||||
statement = "nomsu:assert_permission(#{repr tree.stub});\n"..statement
|
||||
return expr, statement
|
||||
args = {repr(stub), repr(tree.line_no)}
|
||||
args = {repr(tree.stub), repr(tree.line_no)}
|
||||
local arg_names, escaped_args
|
||||
if def
|
||||
arg_names, escaped_args = def.arg_names, def.escaped_args
|
||||
else
|
||||
arg_names, escaped_args = [w.value for w in *tree.value when w.type == "Word"], {}
|
||||
arg_num = 1
|
||||
for arg in *tree.value
|
||||
if arg.type == 'Word' then continue
|
||||
if escaped_args[arg_names[arg_num]]
|
||||
arg = {type:"Nomsu", value:arg}
|
||||
expr,statement = @tree_to_lua arg
|
||||
if statement
|
||||
@error "Cannot use [[#{arg.src}]] as a function argument, since it's not an expression."
|
||||
insert args, expr
|
||||
arg_num += 1
|
||||
return @@comma_separated_items("nomsu:call(", args, ")"), nil
|
||||
|
||||
when "String"
|
||||
@ -658,27 +752,22 @@ end)]])\format(concat(lua_bits, "\n"))
|
||||
get_stub: (x)=>
|
||||
if not x
|
||||
@error "Nothing to get stub from"
|
||||
-- Returns a single stub ("say %"), and list of arg names ({"msg"}) from a single rule def
|
||||
-- Returns a single stub ("say %"), list of arg names ({"msg"}), and set of arg
|
||||
-- names that should not be evaluated from a single rule def
|
||||
-- (e.g. "say %msg") or function call (e.g. FunctionCall({Word("say"), Var("msg")))
|
||||
if type(x) == 'string'
|
||||
stub = x\gsub("([#{wordbreaker}]+)"," %1 ")\gsub("%%%S+","%%")\gsub("%s+"," ")\gsub("^%s*","")\gsub("%s*$","")
|
||||
arg_names = [arg for arg in x\gmatch("%%([^%s']*)")]
|
||||
return stub, arg_names
|
||||
-- Standardize format to stuff separated by spaces
|
||||
x = x\gsub("\n%s*%.%.", " ")\gsub("([#{wordbreaker}]+)", " %1 ")\gsub("%s+"," ")
|
||||
x = x\gsub("^%s*","")\gsub("%s*$","")
|
||||
stub = x\gsub("%%%S+","%%")\gsub("\\","")
|
||||
arg_names = [arg for arg in x\gmatch("%%([^%s]*)")]
|
||||
escaped_args = utils.set [arg for arg in x\gmatch("\\%%([^%s]*)")]
|
||||
return stub, arg_names, escaped_args
|
||||
if type(x) != 'table'
|
||||
@error "Invalid type for getting stub: #{type(x)} for:\n#{repr x}"
|
||||
switch x.type
|
||||
when "String" then return @get_stub(x.value)
|
||||
when "FunctionCall"
|
||||
stub, arg_names = {}, {}, {}
|
||||
for token in *x.value
|
||||
switch token.type
|
||||
when "Word"
|
||||
insert stub, token.value
|
||||
when "Var"
|
||||
insert stub, "%"
|
||||
if arg_names then insert arg_names, token.value
|
||||
else
|
||||
insert stub, "%"
|
||||
arg_names = nil
|
||||
return concat(stub," "), arg_names
|
||||
when "FunctionCall" then return @get_stub(x.src)
|
||||
else @error "Unsupported get stub type: #{x.type} for #{repr x}"
|
||||
|
||||
get_stubs: (x)=>
|
||||
@ -699,31 +788,33 @@ end)]])\format(concat(lua_bits, "\n"))
|
||||
if verboten == "_" then "__" else ("_%x")\format(verboten\byte!))
|
||||
|
||||
error: (msg)=>
|
||||
@errorln (colored.red "ERROR!")
|
||||
error_msg = colored.red "ERROR!"
|
||||
if msg
|
||||
@errorln(colored.bright colored.yellow colored.onred msg)
|
||||
@errorln("Callstack:")
|
||||
error_msg ..= "\n" .. (colored.bright colored.yellow colored.onred msg)
|
||||
error_msg ..= "\nCallstack:"
|
||||
maxlen = utils.max([#c[2] for c in *@callstack when c != "#macro"])
|
||||
for i=#@callstack,1,-1
|
||||
if @callstack[i] != "#macro"
|
||||
@errorln " #{"%-#{maxlen}s"\format @callstack[i][2]}| #{@callstack[i][1]}"
|
||||
@errorln " <top level>"
|
||||
error_msg ..= "\n #{"%-#{maxlen}s"\format @callstack[i][2]}| #{@callstack[i][1]}"
|
||||
error_msg ..= "\n <top level>"
|
||||
@callstack = {}
|
||||
error!
|
||||
error error_msg, 3
|
||||
|
||||
typecheck: (vars, varname, desired_type)=>
|
||||
x = vars[varname]
|
||||
if type(x) == desired_type then return x
|
||||
if type(x) == 'table' and x.type == desired_type then return x
|
||||
@error "Invalid type for %#{varname}. Expected #{desired_type}, but got #{x.type}."
|
||||
@error "Invalid type for %#{varname}. Expected #{desired_type}, but got #{repr x}."
|
||||
|
||||
initialize_core: =>
|
||||
-- Sets up some core functionality
|
||||
nomsu_string_as_lua = (code)=>
|
||||
nomsu_string_as_lua = (code, tree)=>
|
||||
concat_parts = {}
|
||||
for bit in *code.value
|
||||
if type(bit) == "string"
|
||||
insert concat_parts, bit
|
||||
elseif type(bit) == "table" and bit.type == "FunctionCall" and bit.src == "__src__"
|
||||
insert concat_parts, repr(tree.src)
|
||||
else
|
||||
expr, statement = @tree_to_lua bit
|
||||
if statement
|
||||
@ -763,9 +854,10 @@ end)]])\format(concat(lua_bits, "\n"))
|
||||
@def "run file %filename", run_file
|
||||
|
||||
_require = (vars)=>
|
||||
if not @loaded_files[vars.filename]
|
||||
@loaded_files[vars.filename] = run_file(self, {filename:vars.filename}) or true
|
||||
return @loaded_files[vars.filename]
|
||||
loaded = @defs["#loaded_files"]
|
||||
if not loaded[vars.filename]
|
||||
loaded[vars.filename] = run_file(self, {filename:vars.filename}) or true
|
||||
return loaded[vars.filename]
|
||||
@def "require %filename", _require
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user