Overhaul of invocations/specs. Much cleaner and more consistent now,
with less code duplication.
This commit is contained in:
parent
e478b33d7a
commit
26d72ce56e
@ -112,6 +112,22 @@ rule [entries in %dict] =:
|
||||
|end
|
||||
|return items
|
||||
|
||||
rule [keys in %dict] =:
|
||||
lua block ".."
|
||||
|local items = {}
|
||||
|for k,v in pairs(vars.dict) do
|
||||
| table.insert(items, k)
|
||||
|end
|
||||
|return items
|
||||
|
||||
rule [values in %dict] =:
|
||||
lua block ".."
|
||||
|local items = {}
|
||||
|for k,v in pairs(vars.dict) do
|
||||
| table.insert(items, v)
|
||||
|end
|
||||
|return items
|
||||
|
||||
# List Comprehension
|
||||
macro [%expression for %var in %iterable] =:
|
||||
assert ((%var's "type") == "Var") ".."
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
#..
|
||||
This File contains rules for making rules and macros and some helper functions to make
|
||||
that easier.
|
||||
@ -8,10 +7,9 @@
|
||||
lua block ".."
|
||||
|local function make_fn(wrap_in_block)
|
||||
| return function(compiler, vars, kind)
|
||||
# Do a minimal amount of pre-processing (get the spec and the source)
|
||||
| local spec = compiler:get_invocations_from_definition(vars.spec, vars)
|
||||
| spec = compiler.utils.repr(spec)
|
||||
| local src = compiler.utils.repr(vars.user_macro.src)
|
||||
# Do a minimal amount of pre-processing (get the aliases and the source)
|
||||
| local aliases = compiler:repr(compiler:get_aliases(vars.macro_def))
|
||||
| local src = compiler:repr(vars.user_macro.src)
|
||||
| local user_macro = compiler:tree_to_lua(vars.user_macro)
|
||||
# Then produce a block of code that creates the macro at runtime
|
||||
| local lua = [[
|
||||
@ -25,57 +23,75 @@ lua block ".."
|
||||
| return lua, true
|
||||
| end), %s)
|
||||
| ]]
|
||||
| lua = lua:format(spec, compiler.utils.repr(spec), user_macro,
|
||||
| lua = lua:format(aliases, compiler:repr(aliases), user_macro,
|
||||
| wrap_in_block and [[lua = "do\\n "..lua.."\\nend"]] or "", src)
|
||||
| return lua, true
|
||||
| end
|
||||
|end
|
||||
|compiler:defmacro("macro statement %spec = %user_macro", make_fn(false), "N/A")
|
||||
|compiler:defmacro("macro block %spec = %user_macro", make_fn(true), "N/A")
|
||||
|compiler:defmacro("macro statement %macro_def = %user_macro", make_fn(false), "see:lib/metaprogramming.nom")
|
||||
|compiler:defmacro("macro block %macro_def = %user_macro", make_fn(true), "see:lib/metaprogramming.nom")
|
||||
|
||||
macro block [macro %spec = %user_macro] =:
|
||||
macro block [macro %macro_def = %user_macro] =:
|
||||
".."|compiler:defmacro(
|
||||
| \lua expr "compiler:get_invocations_from_definition(vars.spec, vars)"\,
|
||||
| \lua expr "compiler:get_aliases(vars.macro_def)"\,
|
||||
| \lua expr "compiler:tree_to_lua(vars.user_macro)"\,
|
||||
| \lua expr "compiler.utils.repr(vars.user_macro.src)"\)
|
||||
| \lua expr "compiler:repr(vars.user_macro.src)"\)
|
||||
|
||||
macro [compiler] =: "compiler"
|
||||
macro [compiler's %key] =: ".."|compiler[\%key as lua\]
|
||||
macro [compiler %method %args] =:
|
||||
lua block ".."
|
||||
|local args = {}
|
||||
|for i,arg in ipairs(vars.args.value) do
|
||||
| args[i] = compiler:tree_to_lua(arg)
|
||||
|local args = {"compiler"}
|
||||
|for _,arg in ipairs(vars.args.value) do
|
||||
| table.insert(args, compiler:tree_to_lua(arg))
|
||||
|end
|
||||
|return "compiler:"..compiler:tree_to_value(vars.method, vars).."("..table.concat(args, ", ")..")"
|
||||
|return "compiler["..compiler:repr(compiler:tree_to_value(vars.method, vars)).."]("..table.concat(args, ", ")..")"
|
||||
macro [compiler utils %method %args] =:
|
||||
lua block ".."
|
||||
|local args = {}
|
||||
|for i,arg in ipairs(vars.args.value) do
|
||||
| args[i] = compiler:tree_to_lua(arg)
|
||||
|end
|
||||
|return "compiler.utils."..compiler:tree_to_value(vars.method, vars).."("..table.concat(args, ", ")..")"
|
||||
|return "compiler.utils["..compiler:repr(compiler:tree_to_value(vars.method, vars)).."]("..table.concat(args, ", ")..")"
|
||||
|
||||
# Macro that lets you make new rules
|
||||
#..
|
||||
This is a macro instead of a rule because it needs to pre-empt processing the list of
|
||||
invocations and convert it into a list of strings (rather than call a function that
|
||||
function calls and convert it into a list of strings (rather than call a function that
|
||||
is currently in the middle of being defined). Being a macro also allows us to snatch
|
||||
the source code and store that
|
||||
macro block [rule %spec = %body] =: ".."
|
||||
macro block [rule %rule_def = %body] =: ".."
|
||||
|compiler:def(
|
||||
| \compiler utils "repr" [compiler "get_invocations_from_definition" [%spec, lua expr "vars"]]\,
|
||||
| \compiler "repr" [compiler "get_aliases" [%rule_def]]\,
|
||||
| \compiler "tree_to_lua" [%body]\,
|
||||
| \compiler utils "repr" [lua expr "vars.body.src"]\)
|
||||
| \compiler "repr" [lua expr "vars.body.src"]\)
|
||||
|
||||
rule [fart]=: lua block "print('poot')"
|
||||
|
||||
macro block [alias %aliases = %aliased] =:
|
||||
lua block ".."
|
||||
|local aliases = compiler:get_aliases(vars.aliases)
|
||||
|aliases = compiler:repr(aliases)
|
||||
|if vars.aliased.type ~= "Thunk" then
|
||||
| compiler:error("Right hand side of alias % = % should be a Thunk, but got "..vars.aliased.type
|
||||
| ..". Maybe you used = instead of =: by mistake?")
|
||||
|end
|
||||
|local aliased = next(compiler:get_aliases(vars.aliased.value))
|
||||
|aliased = compiler:repr(aliased)
|
||||
|local lua = ([[
|
||||
|compiler:add_aliases(%s, compiler.defs[%s])
|
||||
|]]):format(aliases, aliased)
|
||||
|return lua
|
||||
|
||||
# Get the source code for a function
|
||||
rule [help %invocation] =:
|
||||
rule [help %rule] =:
|
||||
lua block ".."
|
||||
|local fn_info = compiler.defs[vars.invocation]
|
||||
|if not fn_info then
|
||||
| compiler:writeln("Function not found: "..compiler.utils.repr(vars.invocation))
|
||||
|local fn_def = compiler:get_fn_def(vars.rule)
|
||||
|if not fn_def then
|
||||
| compiler:writeln("Rule not found: "..compiler:repr(vars.rule))
|
||||
|else
|
||||
| compiler:writeln("rule "..compiler.utils.repr(fn_info.invocations).." ="..(fn_info.src or ":\\n <unknown source code>"))
|
||||
| compiler:writeln("rule "..compiler:repr(compiler.utils.keys(fn_def.aliases))
|
||||
| .." ="..(fn_def.src or ":\\n <unknown source code>"))
|
||||
|end
|
||||
|
||||
|
||||
@ -103,8 +119,8 @@ rule [source code from tree %tree] =:
|
||||
|end
|
||||
|
||||
macro [source code %body] =:
|
||||
compiler utils "repr" [compiler "call" ["source code from tree %", %body]]
|
||||
compiler "repr" [compiler "call" ["source code from tree %", %body]]
|
||||
|
||||
macro [parse tree %code] =:
|
||||
compiler utils "repr" [compiler "stringify_tree" [lua expr "vars.code.value"]]
|
||||
compiler "repr" [compiler "stringify_tree" [lua expr "vars.code.value"]]
|
||||
|
||||
|
@ -6,8 +6,8 @@ require "lib/collections.nom"
|
||||
# Permission functions
|
||||
rule [restrict %rules to within %elite-rules] =:
|
||||
say ".."|Restricting \%rules\ to within \%elite-rules\
|
||||
%rules =: compiler "get_invocations" [%rules]
|
||||
%elite-rules =: compiler "get_invocations" [%elite-rules]
|
||||
%rules =: keys in (compiler "get_aliases" [%rules])
|
||||
%elite-rules =: keys in (compiler "get_aliases" [%elite-rules])
|
||||
for all (flatten [%elite-rules, %rules]):
|
||||
assert ((compiler's "defs") has key %it) ".."|Undefined function: \%it\
|
||||
for all %rules:
|
||||
@ -20,8 +20,8 @@ rule [restrict %rules to within %elite-rules] =:
|
||||
|
||||
rule [allow %elite-rules to use %rules] =:
|
||||
say ".."|Allowing \%elite-rules\ to use \%rules\
|
||||
%rules =: compiler "get_invocations" [%rules]
|
||||
%elite-rules =: compiler "get_invocations" [%elite-rules]
|
||||
%rules =: keys in (compiler "get_aliases" [%rules])
|
||||
%elite-rules =: keys in (compiler "get_aliases" [%elite-rules])
|
||||
for all (flatten [%elite-rules, %rules]):
|
||||
assert ((compiler's "defs") has key %it) ".."|Undefined function: \%it\
|
||||
for %fn in %rules:
|
||||
@ -33,8 +33,8 @@ rule [allow %elite-rules to use %rules] =:
|
||||
|
||||
rule [forbid %pleb-rules to use %rules] =:
|
||||
say ".."|Forbidding \%pleb-rules\ to use \%rules\
|
||||
%rules =: compiler "get_invocations" [%rules]
|
||||
%pleb-rules =: compiler "get_invocations" [%pleb-rules]
|
||||
%rules =: keys in (compiler "get_aliases" [%rules])
|
||||
%pleb-rules =: keys in (compiler "get_aliases" [%pleb-rules])
|
||||
for all (flatten [%pleb-rules, %used]):
|
||||
assert ((compiler's "defs") has key %it) ".."|Undefined function: \%it\
|
||||
for all %rules:
|
||||
|
@ -15,7 +15,7 @@ macro block [assert %condition %msg] =: ".."
|
||||
|end
|
||||
|
||||
macro block [show generated lua %block] =: ".."
|
||||
|compiler:writeln(\lua expr "compiler.utils.repr(compiler:tree_to_lua(vars.block.value))"\)
|
||||
|compiler:writeln(\lua expr "compiler:repr(compiler:tree_to_lua(vars.block.value))"\)
|
||||
|
||||
|
||||
# String functions
|
||||
@ -35,7 +35,7 @@ macro [capitalize %str, %str capitalized] =: ".."
|
||||
|(\%str as lua\):gsub("%l", string.upper, 1)
|
||||
|
||||
macro [repr %obj] =:
|
||||
".."|compiler.utils.repr(\%obj as lua\)
|
||||
".."|compiler:repr(\%obj as lua\)
|
||||
|
||||
macro [%obj as string] =:
|
||||
".."|compiler.utils.repr_if_not_string(\%obj as lua\)
|
||||
|
518
nomsu.lua
518
nomsu.lua
@ -1,7 +1,16 @@
|
||||
local re = require('re')
|
||||
local lpeg = require('lpeg')
|
||||
local utils = require('utils')
|
||||
local insert = table.insert
|
||||
local repr = utils.repr
|
||||
local insert, remove, concat
|
||||
do
|
||||
local _obj_0 = table
|
||||
insert, remove, concat = _obj_0.insert, _obj_0.remove, _obj_0.concat
|
||||
end
|
||||
local pcall
|
||||
pcall = function(fn, ...)
|
||||
return true, fn(...)
|
||||
end
|
||||
local INDENT = " "
|
||||
lpeg.setmaxstack(10000)
|
||||
local P, V, S, Cg, C, Cp, B, Cmt
|
||||
@ -15,6 +24,34 @@ local STRING_ESCAPES = {
|
||||
f = "\f",
|
||||
r = "\r"
|
||||
}
|
||||
local parsetree_mt = {
|
||||
__tostring = function(self)
|
||||
return tostring(self.type) .. "(" .. tostring(repr(self.value)) .. ")"
|
||||
end
|
||||
}
|
||||
local ParseTree
|
||||
ParseTree = function(type, src, value, errors)
|
||||
return setmetatable({
|
||||
type = type,
|
||||
src = src,
|
||||
value = value,
|
||||
errors = errors
|
||||
}, parsetree_mt)
|
||||
end
|
||||
local functiondef_mt = {
|
||||
__tostring = function(self)
|
||||
return "FunctionDef(" .. tostring(repr(self.aliases))
|
||||
end
|
||||
}
|
||||
local FunctionDef
|
||||
FunctionDef = function(fn, aliases, src, is_macro)
|
||||
return setmetatable({
|
||||
fn = fn,
|
||||
aliases = aliases,
|
||||
src = src,
|
||||
is_macro = is_macro
|
||||
}, functiondef_mt)
|
||||
end
|
||||
local NomsuCompiler
|
||||
do
|
||||
local _class_0
|
||||
@ -23,169 +60,150 @@ do
|
||||
self:write(...)
|
||||
return self:write("\n")
|
||||
end,
|
||||
call = function(self, fn_name, ...)
|
||||
local fn_info = self.defs[fn_name]
|
||||
if fn_info == nil then
|
||||
self:error("Attempt to call undefined function: " .. tostring(fn_name))
|
||||
end
|
||||
if fn_info.is_macro then
|
||||
self:error("Attempt to call macro at runtime: " .. tostring(fn_name) .. "\nThis can be caused by using a macro in a function that is defined before the macro.")
|
||||
end
|
||||
if not (self:check_permission(fn_name)) then
|
||||
self:error("You do not have the authority to call: " .. tostring(fn_name))
|
||||
end
|
||||
insert(self.callstack, fn_name)
|
||||
local fn, arg_names
|
||||
fn, arg_names = fn_info.fn, fn_info.arg_names
|
||||
local args
|
||||
do
|
||||
local _tbl_0 = { }
|
||||
for i, name in ipairs(arg_names[fn_name]) do
|
||||
_tbl_0[name] = select(i, ...)
|
||||
end
|
||||
args = _tbl_0
|
||||
end
|
||||
if self.debug then
|
||||
self:writeln("Calling " .. tostring(fn_name) .. " with args: " .. tostring(utils.repr(args)))
|
||||
end
|
||||
local ret = fn(self, args)
|
||||
table.remove(self.callstack)
|
||||
return ret
|
||||
end,
|
||||
check_permission = function(self, fn_name)
|
||||
local fn_info = self.defs[fn_name]
|
||||
if fn_info == nil then
|
||||
self:error("Undefined function: " .. tostring(fn_name))
|
||||
end
|
||||
if fn_info.whiteset == nil then
|
||||
return true
|
||||
end
|
||||
local _list_0 = self.callstack
|
||||
for _index_0 = 1, #_list_0 do
|
||||
local caller = _list_0[_index_0]
|
||||
if fn_info.whiteset[caller] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end,
|
||||
def = function(self, spec, fn, src)
|
||||
if self.debug then
|
||||
self:writeln("Defining rule: " .. tostring(spec))
|
||||
end
|
||||
local invocations, arg_names = self:get_invocations(spec)
|
||||
for i = 2, #invocations do
|
||||
if not utils.equivalent(utils.set(arg_names[invocations[1]]), utils.set(arg_names[invocations[i]])) then
|
||||
self:error("Conflicting argument names " .. tostring(utils.repr(invocations[1])) .. " and " .. tostring(utils.repr(invocations[i])) .. " for " .. tostring(utils.repr(spec)))
|
||||
end
|
||||
end
|
||||
local fn_info = {
|
||||
fn = fn,
|
||||
arg_names = arg_names,
|
||||
invocations = invocations,
|
||||
src = src,
|
||||
def = function(self, aliases, fn, src, is_macro)
|
||||
if is_macro == nil then
|
||||
is_macro = false
|
||||
}
|
||||
for _index_0 = 1, #invocations do
|
||||
local invocation = invocations[_index_0]
|
||||
self.defs[invocation] = fn_info
|
||||
end
|
||||
if type(aliases) == 'string' then
|
||||
aliases = self:get_aliases(aliases)
|
||||
end
|
||||
if self.debug then
|
||||
self:writeln("Defining rule: " .. tostring(aliases))
|
||||
end
|
||||
local fn_def = FunctionDef(fn, { }, src, is_macro)
|
||||
return self:add_aliases(aliases, fn_def)
|
||||
end,
|
||||
get_invocations_from_definition = function(self, def, vars)
|
||||
if def.type == "String" then
|
||||
return self.__class:unescape_string(def.value)
|
||||
defmacro = function(self, aliases, fn, src)
|
||||
return self:def(aliases, fn, src, true)
|
||||
end,
|
||||
add_aliases = function(self, aliases, fn_def)
|
||||
local first_alias, first_args = next(fn_def.aliases)
|
||||
if not first_alias then
|
||||
first_alias, first_args = next(aliases)
|
||||
end
|
||||
if def.type ~= "List" then
|
||||
self:error("Trying to get invocations from " .. tostring(def.type) .. ", but expected List or String.")
|
||||
end
|
||||
local invocations = { }
|
||||
local _list_0 = def.value
|
||||
for _index_0 = 1, #_list_0 do
|
||||
for alias, args in pairs(aliases) do
|
||||
local _continue_0 = false
|
||||
repeat
|
||||
local item = _list_0[_index_0]
|
||||
if item.type == "String" then
|
||||
insert(invocations, item.value)
|
||||
if fn_def[alias] then
|
||||
_continue_0 = true
|
||||
break
|
||||
end
|
||||
if item.type ~= "FunctionCall" then
|
||||
self:error("Invalid list item: " .. tostring(item.type) .. ", expected FunctionCall or String")
|
||||
if self.defs[alias] then
|
||||
self:remove_alias(alias)
|
||||
end
|
||||
local name_bits = { }
|
||||
local _list_1 = item.value
|
||||
for _index_1 = 1, #_list_1 do
|
||||
local token = _list_1[_index_1]
|
||||
if token.type == "Word" then
|
||||
insert(name_bits, token.value)
|
||||
elseif token.type == "Var" then
|
||||
insert(name_bits, token.src)
|
||||
else
|
||||
self:error("Unexpected token type in definition: " .. tostring(token.type) .. " (expected Word or Var)")
|
||||
end
|
||||
if alias ~= first_alias and not utils.equivalent(utils.set(args), utils.set(first_args)) then
|
||||
self:error("Conflicting argument names between " .. tostring(first_alias) .. " and " .. tostring(alias))
|
||||
end
|
||||
insert(invocations, table.concat(name_bits, " "))
|
||||
fn_def.aliases[alias] = args
|
||||
self.defs[alias] = fn_def
|
||||
_continue_0 = true
|
||||
until true
|
||||
if not _continue_0 then
|
||||
break
|
||||
end
|
||||
end
|
||||
return invocations
|
||||
end,
|
||||
get_invocations = function(self, text)
|
||||
if not text then
|
||||
self:error("No text provided!")
|
||||
remove_alias = function(self, alias)
|
||||
local fn_def = self.defs[alias]
|
||||
if not fn_def then
|
||||
return
|
||||
end
|
||||
if type(text) == 'function' then
|
||||
error("Function passed to get_invocations")
|
||||
fn_def.aliases[alias] = nil
|
||||
self.defs[alias] = nil
|
||||
end,
|
||||
remove_aliases = function(self, aliases)
|
||||
for alias in pairs(aliases) do
|
||||
self:remove_alias(alias)
|
||||
end
|
||||
if type(text) == 'string' then
|
||||
text = {
|
||||
text
|
||||
}
|
||||
end,
|
||||
get_fn_def = function(self, x)
|
||||
if not x then
|
||||
self:error("Nothing to get function def from")
|
||||
end
|
||||
local invocations = { }
|
||||
local arg_names = { }
|
||||
for _index_0 = 1, #text do
|
||||
local _text = text[_index_0]
|
||||
local invocation = _text:gsub("'", " '"):gsub("%%%S+", "%%"):gsub("%s+", " ")
|
||||
local _arg_names
|
||||
do
|
||||
local _accum_0 = { }
|
||||
local _len_0 = 1
|
||||
for arg in _text:gmatch("%%(%S[^%s']*)") do
|
||||
_accum_0[_len_0] = arg
|
||||
local aliases = self:get_aliases(x)
|
||||
local alias, _ = next(aliases)
|
||||
return self.defs[alias]
|
||||
end,
|
||||
call = function(self, alias, ...)
|
||||
local fn_def = self.defs[alias]
|
||||
if fn_def == nil then
|
||||
self:error("Attempt to call undefined function: " .. tostring(alias))
|
||||
end
|
||||
if fn_def.is_macro and self.callstack[#self.callstack] ~= "__macro__" then
|
||||
self:error("Attempt to call macro at runtime: " .. tostring(alias) .. "\nThis can be caused by using a macro in a function that is defined before the macro.")
|
||||
end
|
||||
if not (self:check_permission(alias)) then
|
||||
self:error("You do not have the authority to call: " .. tostring(alias))
|
||||
end
|
||||
insert(self.callstack, alias)
|
||||
local fn, aliases
|
||||
fn, aliases = fn_def.fn, fn_def.aliases
|
||||
local args
|
||||
do
|
||||
local _tbl_0 = { }
|
||||
for i, name in ipairs(aliases[alias]) do
|
||||
_tbl_0[name] = select(i, ...)
|
||||
end
|
||||
args = _tbl_0
|
||||
end
|
||||
if self.debug then
|
||||
self:writeln("Calling " .. tostring(alias) .. " with args: " .. tostring(repr(args)))
|
||||
end
|
||||
local rets = {
|
||||
fn(self, args)
|
||||
}
|
||||
remove(self.callstack)
|
||||
return unpack(rets)
|
||||
end,
|
||||
run_macro = function(self, tree, kind)
|
||||
if kind == nil then
|
||||
kind = "Expression"
|
||||
end
|
||||
local args
|
||||
do
|
||||
local _accum_0 = { }
|
||||
local _len_0 = 1
|
||||
local _list_0 = tree.value
|
||||
for _index_0 = 1, #_list_0 do
|
||||
local a = _list_0[_index_0]
|
||||
if a.type ~= "Word" then
|
||||
_accum_0[_len_0] = a
|
||||
_len_0 = _len_0 + 1
|
||||
end
|
||||
_arg_names = _accum_0
|
||||
end
|
||||
insert(invocations, invocation)
|
||||
arg_names[invocation] = _arg_names
|
||||
args = _accum_0
|
||||
end
|
||||
return invocations, arg_names
|
||||
local alias, _ = self:get_alias(tree)
|
||||
insert(self.callstack, "__macro__")
|
||||
local ret, manual_mode = self:call(alias, unpack(args))
|
||||
remove(self.callstack)
|
||||
if not ret then
|
||||
self:error("No return value for macro: " .. tostring(name))
|
||||
end
|
||||
if kind == "Statement" and not manual_mode then
|
||||
if ret:match("^do\n") then
|
||||
error("Attempting to use macro return value as an expression, when it looks like a block:\n" .. tostring(ret))
|
||||
end
|
||||
ret = "ret = " .. ret
|
||||
end
|
||||
return ret
|
||||
end,
|
||||
defmacro = function(self, spec, lua_gen_fn, src)
|
||||
if self.debug then
|
||||
self:writeln("DEFINING MACRO: " .. tostring(spec) .. tostring(src or ""))
|
||||
check_permission = function(self, fn_name)
|
||||
local fn_def = self.defs[fn_name]
|
||||
if fn_def == nil then
|
||||
self:error("Undefined function: " .. tostring(fn_name))
|
||||
end
|
||||
local invocations, arg_names = self:get_invocations(spec)
|
||||
for i = 2, #invocations do
|
||||
if not utils.equivalent(utils.set(arg_names[invocations[1]]), utils.set(arg_names[invocations[i]])) then
|
||||
self:error("Conflicting argument names " .. tostring(utils.repr(invocations[1])) .. " and " .. tostring(utils.repr(invocations[i])) .. " for " .. tostring(utils.repr(spec)))
|
||||
if fn_def.whiteset == nil then
|
||||
return true
|
||||
end
|
||||
local _list_0 = self.callstack
|
||||
for _index_0 = 1, #_list_0 do
|
||||
local caller = _list_0[_index_0]
|
||||
if fn_def.whiteset[caller] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
local fn_info = {
|
||||
fn = lua_gen_fn,
|
||||
arg_names = arg_names,
|
||||
invocations = invocations,
|
||||
src = src,
|
||||
is_macro = true
|
||||
}
|
||||
for _index_0 = 1, #invocations do
|
||||
local invocation = invocations[_index_0]
|
||||
self.defs[invocation] = fn_info
|
||||
end
|
||||
return false
|
||||
end,
|
||||
parse = function(self, str, filename)
|
||||
if self.debug then
|
||||
@ -204,7 +222,7 @@ do
|
||||
local check_dedent
|
||||
check_dedent = function(subject, end_pos, spaces)
|
||||
if #spaces < indent_stack[#indent_stack] then
|
||||
table.remove(indent_stack)
|
||||
remove(indent_stack)
|
||||
return end_pos
|
||||
end
|
||||
end
|
||||
@ -319,17 +337,21 @@ do
|
||||
return error("\n" .. tostring(err_msg or "Parse error") .. " in " .. tostring(filename) .. " on line " .. tostring(line_no) .. ":\n\n" .. tostring(prev_line) .. "\n" .. tostring(err_line) .. "\n" .. tostring(pointer) .. "\n" .. tostring(next_line) .. "\n")
|
||||
end
|
||||
}
|
||||
local tree_mt = {
|
||||
__tostring = function(self)
|
||||
return tostring(self.type) .. "(" .. tostring(repr(self.value)) .. ")"
|
||||
end
|
||||
}
|
||||
setmetatable(defs, {
|
||||
__index = function(t, key)
|
||||
local fn
|
||||
fn = function(src, value, errors)
|
||||
local token = {
|
||||
return setmetatable({
|
||||
type = key,
|
||||
src = src,
|
||||
value = value,
|
||||
errors = errors
|
||||
}
|
||||
return token
|
||||
}, tree_mt)
|
||||
end
|
||||
t[key] = fn
|
||||
return fn
|
||||
@ -355,7 +377,7 @@ do
|
||||
tree_to_lua = function(self, tree)
|
||||
assert(tree, "No tree provided.")
|
||||
if not tree.type then
|
||||
self:error("Invalid tree: " .. tostring(utils.repr(tree)))
|
||||
self:error("Invalid tree: " .. tostring(repr(tree)))
|
||||
end
|
||||
local _exp_0 = tree.type
|
||||
if "File" == _exp_0 then
|
||||
@ -375,7 +397,7 @@ do
|
||||
local lua_code = "\n return (function(compiler, vars)\n" .. tostring(code) .. "\nend)"
|
||||
local lua_thunk, err = load(lua_code)
|
||||
if not lua_thunk then
|
||||
error("Failed to compile generated code:\n" .. tostring(code) .. "\n\n" .. tostring(err) .. "\n\nProduced by statement:\n" .. tostring(utils.repr(statement)))
|
||||
error("Failed to compile generated code:\n" .. tostring(code) .. "\n\n" .. tostring(err) .. "\n\nProduced by statement:\n" .. tostring(repr(statement)))
|
||||
end
|
||||
local value = lua_thunk()
|
||||
local return_value
|
||||
@ -389,7 +411,7 @@ do
|
||||
insert(buffer, [[ return ret
|
||||
end)
|
||||
]])
|
||||
return table.concat(buffer, "\n"), return_value
|
||||
return concat(buffer, "\n"), return_value
|
||||
elseif "Block" == _exp_0 then
|
||||
local buffer = { }
|
||||
local _list_0 = tree.value
|
||||
@ -397,7 +419,7 @@ do
|
||||
local statement = _list_0[_index_0]
|
||||
insert(buffer, self:tree_to_lua(statement))
|
||||
end
|
||||
return table.concat(buffer, "\n")
|
||||
return concat(buffer, "\n")
|
||||
elseif "Thunk" == _exp_0 then
|
||||
assert(tree.value.type == "Block", "Non-block value in Thunk")
|
||||
return [[ (function(compiler, vars)
|
||||
@ -407,15 +429,15 @@ do
|
||||
]]
|
||||
elseif "Statement" == _exp_0 then
|
||||
if tree.value.type == "FunctionCall" then
|
||||
local name = self:fn_name_from_tree(tree.value)
|
||||
if self.defs[name] and self.defs[name].is_macro then
|
||||
local alias = self:get_alias(tree.value)
|
||||
if self.defs[alias] and self.defs[alias].is_macro then
|
||||
return self:run_macro(tree.value, "Statement")
|
||||
end
|
||||
end
|
||||
return "ret = " .. (self:tree_to_lua(tree.value))
|
||||
elseif "FunctionCall" == _exp_0 then
|
||||
local name = self:fn_name_from_tree(tree)
|
||||
if self.defs[name] and self.defs[name].is_macro then
|
||||
local alias = self:get_alias(tree)
|
||||
if self.defs[alias] and self.defs[alias].is_macro then
|
||||
return self:run_macro(tree, "Expression")
|
||||
else
|
||||
local args
|
||||
@ -432,11 +454,11 @@ do
|
||||
end
|
||||
args = _accum_0
|
||||
end
|
||||
insert(args, 1, utils.repr(name))
|
||||
insert(args, 1, repr(alias))
|
||||
return self.__class:comma_separated_items("compiler:call(", args, ")")
|
||||
end
|
||||
elseif "String" == _exp_0 then
|
||||
return utils.repr(self.__class:unescape_string(tree.value))
|
||||
return repr(self.__class:unescape_string(tree.value))
|
||||
elseif "Longstring" == _exp_0 then
|
||||
local concat_parts = { }
|
||||
local string_buffer = ""
|
||||
@ -450,7 +472,7 @@ do
|
||||
string_buffer = string_buffer .. bit:gsub("\\\\", "\\")
|
||||
else
|
||||
if string_buffer ~= "" then
|
||||
insert(concat_parts, utils.repr(string_buffer))
|
||||
insert(concat_parts, repr(string_buffer))
|
||||
string_buffer = ""
|
||||
end
|
||||
insert(concat_parts, "compiler.utils.repr_if_not_string(" .. tostring(self:tree_to_lua(bit)) .. ")")
|
||||
@ -458,14 +480,14 @@ do
|
||||
end
|
||||
end
|
||||
if string_buffer ~= "" then
|
||||
insert(concat_parts, utils.repr(string_buffer))
|
||||
insert(concat_parts, repr(string_buffer))
|
||||
end
|
||||
if #concat_parts == 0 then
|
||||
return "''"
|
||||
elseif #concat_parts == 1 then
|
||||
return concat_parts[1]
|
||||
else
|
||||
return "(" .. tostring(table.concat(concat_parts, "..")) .. ")"
|
||||
return "(" .. tostring(concat(concat_parts, "..")) .. ")"
|
||||
end
|
||||
elseif "Number" == _exp_0 then
|
||||
return tree.value
|
||||
@ -488,26 +510,94 @@ do
|
||||
end)(), "}")
|
||||
end
|
||||
elseif "Var" == _exp_0 then
|
||||
return "vars[" .. tostring(utils.repr(tree.value)) .. "]"
|
||||
return "vars[" .. tostring(repr(tree.value)) .. "]"
|
||||
else
|
||||
return self:error("Unknown/unimplemented thingy: " .. tostring(tree.type))
|
||||
end
|
||||
end,
|
||||
fn_name_from_tree = function(self, tree)
|
||||
assert(tree.type == "FunctionCall", "Attempt to get fn name from non-functioncall tree: " .. tostring(tree.type))
|
||||
local name_bits = { }
|
||||
local _list_0 = tree.value
|
||||
for _index_0 = 1, #_list_0 do
|
||||
local token = _list_0[_index_0]
|
||||
insert(name_bits, (function()
|
||||
if token.type == "Word" then
|
||||
return token.value
|
||||
else
|
||||
return "%"
|
||||
end
|
||||
end)())
|
||||
get_alias = function(self, x)
|
||||
if not x then
|
||||
self:error("Nothing to get alias from")
|
||||
end
|
||||
if type(x) == 'string' then
|
||||
local alias = x:gsub("'", " '"):gsub("%%%S+", "%%"):gsub("%s+", " ")
|
||||
local args
|
||||
do
|
||||
local _accum_0 = { }
|
||||
local _len_0 = 1
|
||||
for arg in x:gmatch("%%(%S[^%s']*)") do
|
||||
_accum_0[_len_0] = arg
|
||||
_len_0 = _len_0 + 1
|
||||
end
|
||||
args = _accum_0
|
||||
end
|
||||
return alias, args
|
||||
end
|
||||
local _exp_0 = x.type
|
||||
if "String" == _exp_0 then
|
||||
return self:get_alias(x.value)
|
||||
elseif "Statement" == _exp_0 then
|
||||
return self:get_alias(x.value)
|
||||
elseif "FunctionCall" == _exp_0 then
|
||||
local alias, args = { }, { }, { }
|
||||
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(alias, token.value)
|
||||
elseif "Var" == _exp_1 then
|
||||
insert(alias, "%")
|
||||
insert(args, token.value)
|
||||
else
|
||||
insert(alias, "%")
|
||||
insert(args, token)
|
||||
end
|
||||
end
|
||||
return concat(alias, " "), args
|
||||
end
|
||||
end,
|
||||
get_aliases = function(self, x)
|
||||
if self.value then
|
||||
print(self)
|
||||
error("WTF")
|
||||
end
|
||||
if not x then
|
||||
self:error("Nothing to get aliases from")
|
||||
end
|
||||
if type(x) == 'string' then
|
||||
local alias, args = self:get_alias(x)
|
||||
return {
|
||||
[alias] = args
|
||||
}
|
||||
end
|
||||
local _exp_0 = x.type
|
||||
if "String" == _exp_0 then
|
||||
return self:get_aliases({
|
||||
x.value
|
||||
})
|
||||
elseif "Statement" == _exp_0 then
|
||||
return self:get_aliases({
|
||||
x.value
|
||||
})
|
||||
elseif "FunctionCall" == _exp_0 then
|
||||
return self:get_aliases({
|
||||
x
|
||||
})
|
||||
elseif "List" == _exp_0 then
|
||||
x = x.value
|
||||
elseif "Block" == _exp_0 then
|
||||
x = x.value
|
||||
end
|
||||
do
|
||||
local _with_0 = { }
|
||||
for _index_0 = 1, #x do
|
||||
local y = x[_index_0]
|
||||
local alias, args = self:get_alias(y)
|
||||
_with_0[alias] = args
|
||||
end
|
||||
return _with_0
|
||||
end
|
||||
return table.concat(name_bits, " ")
|
||||
end,
|
||||
var_to_lua_identifier = function(self, var)
|
||||
if var.type ~= "Var" then
|
||||
@ -521,54 +611,6 @@ do
|
||||
end
|
||||
end))
|
||||
end,
|
||||
run_macro = function(self, tree, kind)
|
||||
if kind == nil then
|
||||
kind = "Expression"
|
||||
end
|
||||
local name = self:fn_name_from_tree(tree)
|
||||
if not (self.defs[name] and self.defs[name].is_macro) then
|
||||
self:error("Macro not found: " .. tostring(name))
|
||||
end
|
||||
if not (self:check_permission(name)) then
|
||||
self:error("You do not have the authority to call: " .. tostring(name))
|
||||
end
|
||||
local fn, arg_names
|
||||
do
|
||||
local _obj_0 = self.defs[name]
|
||||
fn, arg_names = _obj_0.fn, _obj_0.arg_names
|
||||
end
|
||||
local args
|
||||
do
|
||||
local _accum_0 = { }
|
||||
local _len_0 = 1
|
||||
local _list_0 = tree.value
|
||||
for _index_0 = 1, #_list_0 do
|
||||
local a = _list_0[_index_0]
|
||||
if a.type ~= "Word" then
|
||||
_accum_0[_len_0] = a
|
||||
_len_0 = _len_0 + 1
|
||||
end
|
||||
end
|
||||
args = _accum_0
|
||||
end
|
||||
do
|
||||
local _tbl_0 = { }
|
||||
for i, name in ipairs(arg_names[name]) do
|
||||
_tbl_0[name] = args[i]
|
||||
end
|
||||
args = _tbl_0
|
||||
end
|
||||
insert(self.callstack, name)
|
||||
local ret, manual_mode = fn(self, args, kind)
|
||||
table.remove(self.callstack)
|
||||
if not ret then
|
||||
self:error("No return value for macro: " .. tostring(name))
|
||||
end
|
||||
if kind == "Statement" and not manual_mode then
|
||||
ret = "ret = " .. ret
|
||||
end
|
||||
return ret
|
||||
end,
|
||||
_yield_tree = function(self, tree, indent_level)
|
||||
if indent_level == nil then
|
||||
indent_level = 0
|
||||
@ -580,9 +622,9 @@ do
|
||||
local _exp_0 = tree.type
|
||||
if "File" == _exp_0 then
|
||||
coroutine.yield(ind("File:"))
|
||||
self:_yield_tree(tree.value.body, indent_level + 1)
|
||||
return self:_yield_tree(tree.value.body, indent_level + 1)
|
||||
elseif "Errors" == _exp_0 then
|
||||
coroutine.yield(ind("Error:\n" .. tostring(tree.value)))
|
||||
return coroutine.yield(ind("Error:\n" .. tostring(tree.value)))
|
||||
elseif "Block" == _exp_0 then
|
||||
local _list_0 = tree.value
|
||||
for _index_0 = 1, #_list_0 do
|
||||
@ -591,11 +633,11 @@ do
|
||||
end
|
||||
elseif "Thunk" == _exp_0 then
|
||||
coroutine.yield(ind("Thunk:"))
|
||||
self:_yield_tree(tree.value, indent_level + 1)
|
||||
return self:_yield_tree(tree.value, indent_level + 1)
|
||||
elseif "Statement" == _exp_0 then
|
||||
self:_yield_tree(tree.value, indent_level)
|
||||
return self:_yield_tree(tree.value, indent_level)
|
||||
elseif "FunctionCall" == _exp_0 then
|
||||
local name = self:fn_name_from_tree(tree)
|
||||
local alias = self:get_alias(tree)
|
||||
local args
|
||||
do
|
||||
local _accum_0 = { }
|
||||
@ -611,23 +653,25 @@ do
|
||||
args = _accum_0
|
||||
end
|
||||
if #args == 0 then
|
||||
coroutine.yield(ind("Call [" .. tostring(name) .. "]!"))
|
||||
return coroutine.yield(ind("Call [" .. tostring(alias) .. "]!"))
|
||||
else
|
||||
coroutine.yield(ind("Call [" .. tostring(name) .. "]:"))
|
||||
coroutine.yield(ind("Call [" .. tostring(alias) .. "]:"))
|
||||
for _index_0 = 1, #args do
|
||||
local a = args[_index_0]
|
||||
self:_yield_tree(a, indent_level + 1)
|
||||
end
|
||||
end
|
||||
elseif "String" == _exp_0 then
|
||||
coroutine.yield(ind(utils.repr(tree.value)))
|
||||
return coroutine.yield(ind(repr(tree.value)))
|
||||
elseif "Longstring" == _exp_0 then
|
||||
coroutine.yield(ind(utils.repr(tree.value)))
|
||||
return coroutine.yield(ind(repr(tree.value)))
|
||||
elseif "Number" == _exp_0 then
|
||||
coroutine.yield(ind(tree.value))
|
||||
return coroutine.yield(ind(tree.value))
|
||||
elseif "Var" == _exp_0 then
|
||||
return coroutine.yield(ind("Var[" .. tostring(repr(tree.value)) .. "]"))
|
||||
elseif "List" == _exp_0 then
|
||||
if #tree.value == 0 then
|
||||
coroutine.yield(ind("<Empty List>"))
|
||||
return coroutine.yield(ind("<Empty List>"))
|
||||
else
|
||||
coroutine.yield(ind("List:"))
|
||||
local _list_0 = tree.value
|
||||
@ -636,12 +680,9 @@ do
|
||||
self:_yield_tree(item, indent_level + 1)
|
||||
end
|
||||
end
|
||||
elseif "Var" == _exp_0 then
|
||||
coroutine.yield(ind("Var[" .. tostring(utils.repr(tree.value)) .. "]"))
|
||||
else
|
||||
error("Unknown/unimplemented thingy: " .. tostring(tree.type))
|
||||
return error("Unknown/unimplemented thingy: " .. tostring(tree.type))
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
print_tree = function(self, tree)
|
||||
for line in coroutine.wrap(function()
|
||||
@ -657,7 +698,7 @@ do
|
||||
end) do
|
||||
insert(result, line)
|
||||
end
|
||||
return table.concat(result, "\n")
|
||||
return concat(result, "\n")
|
||||
end,
|
||||
run = function(self, src, filename, output_file)
|
||||
if output_file == nil then
|
||||
@ -707,22 +748,22 @@ do
|
||||
end
|
||||
end,
|
||||
initialize_core = function(self)
|
||||
self:defmacro([[lua block %lua_code]], function(self, vars, kind)
|
||||
self:defmacro("lua block %lua_code", function(self, vars, kind)
|
||||
if kind == "Expression" then
|
||||
error("Expected to be in statement.")
|
||||
end
|
||||
local inner_vars = setmetatable({ }, {
|
||||
__index = function(_, key)
|
||||
return error("vars[" .. tostring(utils.repr(key)) .. "]")
|
||||
return error("vars[" .. tostring(repr(key)) .. "]")
|
||||
end
|
||||
})
|
||||
return "do\n" .. self:tree_to_value(vars.lua_code, inner_vars) .. "\nend", true
|
||||
end)
|
||||
self:defmacro([[lua expr %lua_code]], function(self, vars, kind)
|
||||
self:defmacro("lua expr %lua_code", function(self, vars, kind)
|
||||
local lua_code = vars.lua_code.value
|
||||
local inner_vars = setmetatable({ }, {
|
||||
__index = function(_, key)
|
||||
return error("vars[" .. tostring(utils.repr(key)) .. "]")
|
||||
return error("vars[" .. tostring(repr(key)) .. "]")
|
||||
end
|
||||
})
|
||||
return self:tree_to_value(vars.lua_code, inner_vars)
|
||||
@ -759,6 +800,9 @@ do
|
||||
self.debug = false
|
||||
self:initialize_core()
|
||||
self.utils = utils
|
||||
self.repr = function(self, ...)
|
||||
return repr(...)
|
||||
end
|
||||
self.loaded_files = { }
|
||||
end,
|
||||
__base = _base_0,
|
||||
@ -795,7 +839,7 @@ do
|
||||
end
|
||||
end
|
||||
insert(bits, close)
|
||||
return table.concat(bits)
|
||||
return concat(bits)
|
||||
end
|
||||
NomsuCompiler = _class_0
|
||||
end
|
||||
@ -845,7 +889,7 @@ elseif arg then
|
||||
return c:run(buff)
|
||||
end)
|
||||
if ok and ret ~= nil then
|
||||
print("= " .. utils.repr(ret))
|
||||
print("= " .. repr(ret))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
327
nomsu.moon
327
nomsu.moon
@ -2,7 +2,9 @@
|
||||
re = require 're'
|
||||
lpeg = require 'lpeg'
|
||||
utils = require 'utils'
|
||||
insert = table.insert
|
||||
repr = utils.repr
|
||||
{:insert, :remove, :concat} = table
|
||||
pcall = (fn,...)-> true, fn(...)
|
||||
|
||||
-- TODO:
|
||||
-- improve indentation of generated lua code
|
||||
@ -19,6 +21,14 @@ lpeg.setmaxstack 10000 -- whoa
|
||||
{:P,:V,:S,:Cg,:C,:Cp,:B,:Cmt} = lpeg
|
||||
STRING_ESCAPES = n:"\n", t:"\t", b:"\b", a:"\a", v:"\v", f:"\f", r:"\r"
|
||||
|
||||
-- Helper "classes"
|
||||
parsetree_mt = {__tostring:=> "#{@type}(#{repr(@value)})"}
|
||||
ParseTree = (type, src, value, errors)->
|
||||
setmetatable({:type, :src, :value, :errors}, parsetree_mt)
|
||||
|
||||
functiondef_mt = {__tostring:=> "FunctionDef(#{repr(@aliases)}"}
|
||||
FunctionDef = (fn, aliases, src, is_macro)->
|
||||
setmetatable({:fn, :aliases, :src, :is_macro}, functiondef_mt)
|
||||
|
||||
class NomsuCompiler
|
||||
new:(parent)=>
|
||||
@ -28,102 +38,93 @@ class NomsuCompiler
|
||||
@debug = false
|
||||
@initialize_core!
|
||||
@utils = utils
|
||||
@repr = (...)=> repr(...)
|
||||
@loaded_files = {}
|
||||
|
||||
writeln:(...)=>
|
||||
@write(...)
|
||||
@write("\n")
|
||||
|
||||
call: (fn_name,...)=>
|
||||
fn_info = @defs[fn_name]
|
||||
if fn_info == nil
|
||||
@error "Attempt to call undefined function: #{fn_name}"
|
||||
if fn_info.is_macro
|
||||
@error "Attempt to call macro at runtime: #{fn_name}\nThis can be caused by using a macro in a function that is defined before the macro."
|
||||
unless @check_permission(fn_name)
|
||||
@error "You do not have the authority to call: #{fn_name}"
|
||||
insert @callstack, fn_name
|
||||
{:fn, :arg_names} = fn_info
|
||||
args = {name, select(i,...) for i,name in ipairs(arg_names[fn_name])}
|
||||
|
||||
def: (aliases, fn, src, is_macro=false)=>
|
||||
if type(aliases) == 'string'
|
||||
aliases = @get_aliases aliases
|
||||
if @debug
|
||||
@writeln "Calling #{fn_name} with args: #{utils.repr(args)}"
|
||||
ret = fn(self, args)
|
||||
table.remove @callstack
|
||||
@writeln "Defining rule: #{aliases}"
|
||||
fn_def = FunctionDef(fn, {}, src, is_macro)
|
||||
@add_aliases aliases, fn_def
|
||||
|
||||
defmacro: (aliases, fn, src)=> @def(aliases, fn, src, true)
|
||||
|
||||
add_aliases: (aliases, fn_def)=>
|
||||
first_alias,first_args = next(fn_def.aliases)
|
||||
if not first_alias
|
||||
first_alias,first_args = next(aliases)
|
||||
for alias,args in pairs(aliases)
|
||||
if fn_def[alias] then continue
|
||||
if @defs[alias] then @remove_alias(alias)
|
||||
if alias != first_alias and not utils.equivalent(utils.set(args), utils.set(first_args))
|
||||
@error "Conflicting argument names between #{first_alias} and #{alias}"
|
||||
fn_def.aliases[alias] = args
|
||||
@defs[alias] = fn_def
|
||||
|
||||
remove_alias: (alias)=>
|
||||
fn_def = @defs[alias]
|
||||
if not fn_def then return
|
||||
fn_def.aliases[alias] = nil
|
||||
@defs[alias] = nil
|
||||
|
||||
remove_aliases: (aliases)=>
|
||||
for alias in pairs(aliases) do @remove_alias(alias)
|
||||
|
||||
get_fn_def: (x)=>
|
||||
if not x then @error "Nothing to get function def from"
|
||||
aliases = @get_aliases x
|
||||
alias,_ = next(aliases)
|
||||
return @defs[alias]
|
||||
|
||||
call: (alias,...)=>
|
||||
fn_def = @defs[alias]
|
||||
if fn_def == nil
|
||||
@error "Attempt to call undefined function: #{alias}"
|
||||
if fn_def.is_macro and @callstack[#@callstack] != "__macro__"
|
||||
@error "Attempt to call macro at runtime: #{alias}\nThis can be caused by using a macro in a function that is defined before the macro."
|
||||
unless @check_permission(alias)
|
||||
@error "You do not have the authority to call: #{alias}"
|
||||
insert @callstack, alias
|
||||
{:fn, :aliases} = fn_def
|
||||
args = {name, select(i,...) for i,name in ipairs(aliases[alias])}
|
||||
if @debug
|
||||
@writeln "Calling #{alias} with args: #{repr(args)}"
|
||||
-- TODO: optimize, but still allow multiple return values?
|
||||
rets = {fn(self,args)}
|
||||
remove @callstack
|
||||
return unpack(rets)
|
||||
|
||||
run_macro: (tree, kind="Expression")=>
|
||||
args = [a for a in *tree.value when a.type != "Word"]
|
||||
alias,_ = @get_alias tree
|
||||
insert @callstack, "__macro__"
|
||||
ret, manual_mode = @call(alias, unpack(args))
|
||||
remove @callstack
|
||||
if not ret
|
||||
@error("No return value for macro: #{name}")
|
||||
if kind == "Statement" and not manual_mode
|
||||
if ret\match("^do\n")
|
||||
error "Attempting to use macro return value as an expression, when it looks like a block:\n#{ret}"
|
||||
ret = "ret = "..ret
|
||||
return ret
|
||||
|
||||
check_permission: (fn_name)=>
|
||||
fn_info = @defs[fn_name]
|
||||
if fn_info == nil
|
||||
fn_def = @defs[fn_name]
|
||||
if fn_def == nil
|
||||
@error "Undefined function: #{fn_name}"
|
||||
if fn_info.whiteset == nil then return true
|
||||
if fn_def.whiteset == nil then return true
|
||||
-- TODO: optimize this, maybe by making the callstack a Counter and having a move-to-front optimization on the whitelist
|
||||
for caller in *@callstack
|
||||
if fn_info.whiteset[caller]
|
||||
if fn_def.whiteset[caller]
|
||||
return true
|
||||
return false
|
||||
|
||||
def: (spec, fn, src)=>
|
||||
if @debug
|
||||
@writeln "Defining rule: #{spec}"
|
||||
invocations,arg_names = @get_invocations spec
|
||||
for i=2,#invocations
|
||||
if not utils.equivalent(utils.set(arg_names[invocations[1]]), utils.set(arg_names[invocations[i]]))
|
||||
@error("Conflicting argument names #{utils.repr(invocations[1])} and #{utils.repr(invocations[i])} for #{utils.repr(spec)}")
|
||||
fn_info = {:fn, :arg_names, :invocations, :src, is_macro:false}
|
||||
for invocation in *invocations
|
||||
@defs[invocation] = fn_info
|
||||
|
||||
get_invocations_from_definition:(def, vars)=>
|
||||
if def.type == "String"
|
||||
return @@unescape_string(def.value)
|
||||
|
||||
if def.type != "List"
|
||||
@error "Trying to get invocations from #{def.type}, but expected List or String."
|
||||
|
||||
invocations = {}
|
||||
for item in *def.value
|
||||
if item.type == "String"
|
||||
insert invocations, item.value
|
||||
continue
|
||||
if item.type != "FunctionCall"
|
||||
@error "Invalid list item: #{item.type}, expected FunctionCall or String"
|
||||
name_bits = {}
|
||||
for token in *item.value
|
||||
if token.type == "Word"
|
||||
insert name_bits, token.value
|
||||
elseif token.type == "Var"
|
||||
insert name_bits, token.src
|
||||
else
|
||||
@error "Unexpected token type in definition: #{token.type} (expected Word or Var)"
|
||||
insert invocations, table.concat(name_bits, " ")
|
||||
return invocations
|
||||
|
||||
get_invocations:(text)=>
|
||||
if not text
|
||||
@error "No text provided!"
|
||||
if type(text) == 'function'
|
||||
error "Function passed to get_invocations"
|
||||
if type(text) == 'string' then text = {text}
|
||||
invocations = {}
|
||||
arg_names = {}
|
||||
for _text in *text
|
||||
invocation = _text\gsub("'"," '")\gsub("%%%S+","%%")\gsub("%s+"," ")
|
||||
_arg_names = [arg for arg in _text\gmatch("%%(%S[^%s']*)")]
|
||||
insert invocations, invocation
|
||||
arg_names[invocation] = _arg_names
|
||||
return invocations, arg_names
|
||||
|
||||
defmacro: (spec, lua_gen_fn, src)=>
|
||||
if @debug
|
||||
@writeln("DEFINING MACRO: #{spec}#{src or ""}")
|
||||
invocations,arg_names = @get_invocations spec
|
||||
for i=2,#invocations
|
||||
if not utils.equivalent(utils.set(arg_names[invocations[1]]), utils.set(arg_names[invocations[i]]))
|
||||
@error("Conflicting argument names #{utils.repr(invocations[1])} and #{utils.repr(invocations[i])} for #{utils.repr(spec)}")
|
||||
fn_info = {fn:lua_gen_fn, :arg_names, :invocations, :src, is_macro:true}
|
||||
for invocation in *invocations
|
||||
@defs[invocation] = fn_info
|
||||
|
||||
parse: (str, filename)=>
|
||||
if @debug
|
||||
@writeln("PARSING:\n#{str}")
|
||||
@ -136,7 +137,7 @@ class NomsuCompiler
|
||||
return end_pos
|
||||
check_dedent = (subject,end_pos,spaces)->
|
||||
if #spaces < indent_stack[#indent_stack]
|
||||
table.remove(indent_stack)
|
||||
remove(indent_stack)
|
||||
return end_pos
|
||||
check_nodent = (subject,end_pos,spaces)->
|
||||
if #spaces == indent_stack[#indent_stack]
|
||||
@ -245,11 +246,10 @@ class NomsuCompiler
|
||||
pointer = ("-")\rep(err_pos - start_of_err_line + 0) .. "^"
|
||||
error("\n#{err_msg or "Parse error"} in #{filename} on line #{line_no}:\n\n#{prev_line}\n#{err_line}\n#{pointer}\n#{next_line}\n")
|
||||
|
||||
tree_mt = {__tostring:=> "#{@type}(#{repr(@value)})"}
|
||||
setmetatable(defs, {
|
||||
__index: (t,key)->
|
||||
fn = (src, value, errors)->
|
||||
token = {type: key, :src, :value, :errors}
|
||||
return token
|
||||
fn = (src, value, errors)-> setmetatable({type: key, :src, :value, :errors}, tree_mt)
|
||||
t[key] = fn
|
||||
return fn
|
||||
})
|
||||
@ -272,7 +272,7 @@ class NomsuCompiler
|
||||
tree_to_lua: (tree)=>
|
||||
assert tree, "No tree provided."
|
||||
if not tree.type
|
||||
@error "Invalid tree: #{utils.repr(tree)}"
|
||||
@error "Invalid tree: #{repr(tree)}"
|
||||
switch tree.type
|
||||
when "File"
|
||||
buffer = {[[return (function(compiler, vars)
|
||||
@ -288,7 +288,7 @@ class NomsuCompiler
|
||||
return (function(compiler, vars)\n#{code}\nend)"
|
||||
lua_thunk, err = load(lua_code)
|
||||
if not lua_thunk
|
||||
error("Failed to compile generated code:\n#{code}\n\n#{err}\n\nProduced by statement:\n#{utils.repr(statement)}")
|
||||
error("Failed to compile generated code:\n#{code}\n\n#{err}\n\nProduced by statement:\n#{repr(statement)}")
|
||||
value = lua_thunk!
|
||||
ok,return_value = pcall(value, self, vars)
|
||||
if not ok
|
||||
@ -299,13 +299,13 @@ class NomsuCompiler
|
||||
return ret
|
||||
end)
|
||||
]]
|
||||
return table.concat(buffer, "\n"), return_value
|
||||
return concat(buffer, "\n"), return_value
|
||||
|
||||
when "Block"
|
||||
buffer = {}
|
||||
for statement in *tree.value
|
||||
insert buffer, @tree_to_lua(statement)
|
||||
return table.concat(buffer, "\n")
|
||||
return concat(buffer, "\n")
|
||||
|
||||
when "Thunk"
|
||||
assert tree.value.type == "Block", "Non-block value in Thunk"
|
||||
@ -320,22 +320,22 @@ class NomsuCompiler
|
||||
when "Statement"
|
||||
-- This case here is to prevent "ret =" from getting prepended when the macro might not want it
|
||||
if tree.value.type == "FunctionCall"
|
||||
name = @fn_name_from_tree(tree.value)
|
||||
if @defs[name] and @defs[name].is_macro
|
||||
alias = @get_alias(tree.value)
|
||||
if @defs[alias] and @defs[alias].is_macro
|
||||
return @run_macro(tree.value, "Statement")
|
||||
return "ret = "..(@tree_to_lua(tree.value))
|
||||
|
||||
when "FunctionCall"
|
||||
name = @fn_name_from_tree(tree)
|
||||
if @defs[name] and @defs[name].is_macro
|
||||
alias = @get_alias(tree)
|
||||
if @defs[alias] and @defs[alias].is_macro
|
||||
return @run_macro(tree, "Expression")
|
||||
else
|
||||
args = [@tree_to_lua(a) for a in *tree.value when a.type != "Word"]
|
||||
insert args, 1, utils.repr(name)
|
||||
insert args, 1, repr(alias)
|
||||
return @@comma_separated_items("compiler:call(", args, ")")
|
||||
|
||||
when "String"
|
||||
return utils.repr(@@unescape_string(tree.value))
|
||||
return repr(@@unescape_string(tree.value))
|
||||
|
||||
when "Longstring"
|
||||
concat_parts = {}
|
||||
@ -347,19 +347,19 @@ class NomsuCompiler
|
||||
string_buffer ..= bit\gsub("\\\\","\\")
|
||||
else
|
||||
if string_buffer ~= ""
|
||||
insert concat_parts, utils.repr(string_buffer)
|
||||
insert concat_parts, repr(string_buffer)
|
||||
string_buffer = ""
|
||||
insert concat_parts, "compiler.utils.repr_if_not_string(#{@tree_to_lua(bit)})"
|
||||
|
||||
if string_buffer ~= ""
|
||||
insert concat_parts, utils.repr(string_buffer)
|
||||
insert concat_parts, repr(string_buffer)
|
||||
|
||||
if #concat_parts == 0
|
||||
return "''"
|
||||
elseif #concat_parts == 1
|
||||
return concat_parts[1]
|
||||
else
|
||||
return "(#{table.concat(concat_parts, "..")})"
|
||||
return "(#{concat(concat_parts, "..")})"
|
||||
|
||||
when "Number"
|
||||
return tree.value
|
||||
@ -373,7 +373,7 @@ class NomsuCompiler
|
||||
return @@comma_separated_items("{", [@tree_to_lua(item) for item in *tree.value], "}")
|
||||
|
||||
when "Var"
|
||||
return "vars[#{utils.repr(tree.value)}]"
|
||||
return "vars[#{repr(tree.value)}]"
|
||||
|
||||
else
|
||||
@error("Unknown/unimplemented thingy: #{tree.type}")
|
||||
@ -392,38 +392,60 @@ class NomsuCompiler
|
||||
insert bits, "\n"
|
||||
so_far = 0
|
||||
insert bits, close
|
||||
return table.concat(bits)
|
||||
|
||||
fn_name_from_tree: (tree)=>
|
||||
assert(tree.type == "FunctionCall", "Attempt to get fn name from non-functioncall tree: #{tree.type}")
|
||||
name_bits = {}
|
||||
for token in *tree.value
|
||||
insert name_bits, if token.type == "Word" then token.value else "%"
|
||||
table.concat(name_bits, " ")
|
||||
return concat(bits)
|
||||
|
||||
get_alias: (x)=>
|
||||
if not x then @error "Nothing to get alias from"
|
||||
-- Returns a single alias ("say %"), and list of args ({msg}) from a single rule def
|
||||
-- (e.g. "say %msg") or function call (e.g. FunctionCall({Word("say"), Var("msg")))
|
||||
if type(x) == 'string'
|
||||
-- TODO
|
||||
alias = x\gsub("'"," '")\gsub("%%%S+","%%")\gsub("%s+"," ")
|
||||
args = [arg for arg in x\gmatch("%%(%S[^%s']*)")]
|
||||
return alias, args
|
||||
switch x.type
|
||||
when "String" then return @get_alias(x.value)
|
||||
when "Statement" then return @get_alias(x.value)
|
||||
when "FunctionCall"
|
||||
alias, args = {}, {}, {}
|
||||
for token in *x.value
|
||||
switch token.type
|
||||
when "Word"
|
||||
insert alias, token.value
|
||||
when "Var"
|
||||
insert alias, "%"
|
||||
insert args, token.value
|
||||
else
|
||||
insert alias, "%"
|
||||
insert args, token
|
||||
return concat(alias," "), args
|
||||
|
||||
get_aliases:(x)=>
|
||||
if self.value
|
||||
print self
|
||||
error "WTF"
|
||||
if not x then @error "Nothing to get aliases from"
|
||||
if type(x) == 'string'
|
||||
alias, args = @get_alias(x)
|
||||
return {[alias]: args}
|
||||
switch x.type
|
||||
when "String" then return @get_aliases({x.value})
|
||||
when "Statement" then return @get_aliases({x.value})
|
||||
when "FunctionCall" then return @get_aliases({x})
|
||||
when "List" then x = x.value
|
||||
when "Block" then x = x.value
|
||||
with {}
|
||||
for y in *x
|
||||
alias,args = @get_alias(y)
|
||||
[alias] = args
|
||||
|
||||
var_to_lua_identifier: (var)=>
|
||||
-- Converts arbitrary nomsu vars to valid lua identifiers by replacing illegal
|
||||
-- characters with escape sequences
|
||||
if var.type != "Var"
|
||||
@error("Tried to convert something that wasn't a Var into a lua identifier: it was not a Var, it was: "..label.type)
|
||||
"var"..(var.value\gsub "%W", (verboten)->
|
||||
if verboten == "_" then "__" else ("_%x")\format(verboten\byte!))
|
||||
|
||||
run_macro: (tree, kind="Expression")=>
|
||||
name = @fn_name_from_tree(tree)
|
||||
unless @defs[name] and @defs[name].is_macro
|
||||
@error("Macro not found: #{name}")
|
||||
unless @check_permission(name)
|
||||
@error "You do not have the authority to call: #{name}"
|
||||
{:fn, :arg_names} = @defs[name]
|
||||
args = [a for a in *tree.value when a.type != "Word"]
|
||||
args = {name,args[i] for i,name in ipairs(arg_names[name])}
|
||||
insert @callstack, name
|
||||
ret, manual_mode = fn(self, args, kind)
|
||||
table.remove @callstack
|
||||
if not ret
|
||||
@error("No return value for macro: #{name}")
|
||||
if kind == "Statement" and not manual_mode
|
||||
ret = "ret = "..ret
|
||||
return ret
|
||||
|
||||
_yield_tree: (tree, indent_level=0)=>
|
||||
ind = (s) -> INDENT\rep(indent_level)..s
|
||||
@ -431,42 +453,27 @@ class NomsuCompiler
|
||||
when "File"
|
||||
coroutine.yield(ind"File:")
|
||||
@_yield_tree(tree.value.body, indent_level+1)
|
||||
|
||||
when "Errors"
|
||||
coroutine.yield(ind"Error:\n#{tree.value}")
|
||||
|
||||
when "Errors" then coroutine.yield(ind"Error:\n#{tree.value}")
|
||||
when "Block"
|
||||
for chunk in *tree.value
|
||||
@_yield_tree(chunk, indent_level)
|
||||
|
||||
when "Thunk"
|
||||
coroutine.yield(ind"Thunk:")
|
||||
@_yield_tree(tree.value, indent_level+1)
|
||||
|
||||
when "Statement"
|
||||
@_yield_tree(tree.value, indent_level)
|
||||
|
||||
when "Statement" then @_yield_tree(tree.value, indent_level)
|
||||
when "FunctionCall"
|
||||
name = @fn_name_from_tree(tree)
|
||||
alias = @get_alias tree
|
||||
args = [a for a in *tree.value when a.type != "Word"]
|
||||
if #args == 0
|
||||
coroutine.yield(ind"Call [#{name}]!")
|
||||
coroutine.yield(ind"Call [#{alias}]!")
|
||||
else
|
||||
coroutine.yield(ind"Call [#{name}]:")
|
||||
coroutine.yield(ind"Call [#{alias}]:")
|
||||
for a in *args
|
||||
@_yield_tree(a, indent_level+1)
|
||||
|
||||
when "String"
|
||||
-- TODO: Better implement
|
||||
coroutine.yield(ind(utils.repr(tree.value)))
|
||||
|
||||
when "Longstring"
|
||||
-- TODO: Better implement
|
||||
coroutine.yield(ind(utils.repr(tree.value)))
|
||||
|
||||
when "Number"
|
||||
coroutine.yield(ind(tree.value))
|
||||
|
||||
when "String" then coroutine.yield(ind(repr(tree.value)))
|
||||
when "Longstring" then coroutine.yield(ind(repr(tree.value)))
|
||||
when "Number" then coroutine.yield(ind(tree.value))
|
||||
when "Var" then coroutine.yield ind"Var[#{repr(tree.value)}]"
|
||||
when "List"
|
||||
if #tree.value == 0
|
||||
coroutine.yield(ind("<Empty List>"))
|
||||
@ -474,13 +481,7 @@ class NomsuCompiler
|
||||
coroutine.yield(ind"List:")
|
||||
for item in *tree.value
|
||||
@_yield_tree(item, indent_level+1)
|
||||
|
||||
when "Var"
|
||||
coroutine.yield ind"Var[#{utils.repr(tree.value)}]"
|
||||
|
||||
else
|
||||
error("Unknown/unimplemented thingy: #{tree.type}")
|
||||
return nil -- to prevent tail calls
|
||||
else error("Unknown/unimplemented thingy: #{tree.type}")
|
||||
|
||||
print_tree:(tree)=>
|
||||
for line in coroutine.wrap(-> @_yield_tree(tree))
|
||||
@ -490,7 +491,7 @@ class NomsuCompiler
|
||||
result = {}
|
||||
for line in coroutine.wrap(-> @_yield_tree(tree))
|
||||
insert(result, line)
|
||||
return table.concat result, "\n"
|
||||
return concat result, "\n"
|
||||
|
||||
run: (src, filename, output_file=nil)=>
|
||||
if @debug
|
||||
@ -533,14 +534,14 @@ class NomsuCompiler
|
||||
|
||||
initialize_core: =>
|
||||
-- Sets up some core functionality
|
||||
@defmacro [[lua block %lua_code]], (vars, kind)=>
|
||||
@defmacro "lua block %lua_code", (vars, kind)=>
|
||||
if kind == "Expression" then error("Expected to be in statement.")
|
||||
inner_vars = setmetatable({}, {__index:(_,key)-> error"vars[#{utils.repr(key)}]"})
|
||||
inner_vars = setmetatable({}, {__index:(_,key)-> error"vars[#{repr(key)}]"})
|
||||
return "do\n"..@tree_to_value(vars.lua_code, inner_vars).."\nend", true
|
||||
|
||||
@defmacro [[lua expr %lua_code]], (vars, kind)=>
|
||||
@defmacro "lua expr %lua_code", (vars, kind)=>
|
||||
lua_code = vars.lua_code.value
|
||||
inner_vars = setmetatable({}, {__index:(_,key)-> error"vars[#{utils.repr(key)}]"})
|
||||
inner_vars = setmetatable({}, {__index:(_,key)-> error"vars[#{repr(key)}]"})
|
||||
return @tree_to_value(vars.lua_code, inner_vars)
|
||||
|
||||
@def "require %filename", (vars)=>
|
||||
@ -607,6 +608,6 @@ elseif arg
|
||||
break
|
||||
ok, ret = pcall(-> c\run(buff))
|
||||
if ok and ret != nil
|
||||
print "= "..utils.repr(ret)
|
||||
print "= "..repr(ret)
|
||||
|
||||
return NomsuCompiler
|
||||
|
@ -78,6 +78,14 @@ utils = {
|
||||
end
|
||||
return _accum_0
|
||||
end,
|
||||
remove_from_list = function(list, item)
|
||||
for i, list_item in ipairs(list) do
|
||||
if list_item == item then
|
||||
table.remove(list, i)
|
||||
return
|
||||
end
|
||||
end
|
||||
end,
|
||||
accumulate = function(glue, co)
|
||||
if co == nil then
|
||||
glue, co = "", glue
|
||||
|
@ -41,6 +41,12 @@ utils = {
|
||||
split: (str, sep="%s")->
|
||||
[chunk for chunk in str\gmatch("[^#{sep}]+")]
|
||||
|
||||
remove_from_list: (list, item)->
|
||||
for i,list_item in ipairs(list)
|
||||
if list_item == item
|
||||
table.remove list, i
|
||||
return
|
||||
|
||||
accumulate: (glue, co)->
|
||||
if co == nil then glue, co = "", glue
|
||||
bits = {}
|
||||
|
Loading…
Reference in New Issue
Block a user