aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/collections.nom2
-rw-r--r--lib/control_flow.nom49
-rw-r--r--lib/permissions.nom56
-rw-r--r--lib/utils.nom21
-rw-r--r--lib/utils2.nom42
-rw-r--r--nomsu.lua376
-rwxr-xr-xnomsu.moon224
7 files changed, 549 insertions, 221 deletions
diff --git a/lib/collections.nom b/lib/collections.nom
index c58d2f7..02729c5 100644
--- a/lib/collections.nom
+++ b/lib/collections.nom
@@ -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"):
diff --git a/lib/control_flow.nom b/lib/control_flow.nom
index aeb63ef..2d1a6a4 100644
--- a/lib/control_flow.nom
+++ b/lib/control_flow.nom
@@ -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
+
diff --git a/lib/permissions.nom b/lib/permissions.nom
index 887f4fa..1811ee8 100644
--- a/lib/permissions.nom
+++ b/lib/permissions.nom
@@ -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)
diff --git a/lib/utils.nom b/lib/utils.nom
index 965d338..ef0e61e 100644
--- a/lib/utils.nom
+++ b/lib/utils.nom
@@ -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
-
diff --git a/lib/utils2.nom b/lib/utils2.nom
index 2562471..415f8c3 100644
--- a/lib/utils2.nom
+++ b/lib/utils2.nom
@@ -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
+
+
diff --git a/nomsu.lua b/nomsu.lua
index fa77c1c..23522f6 100644
--- a/nomsu.lua
+++ b/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
diff --git a/nomsu.moon b/nomsu.moon
index 899b1fc..676f9b8 100755
--- a/nomsu.moon
+++ b/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