Overhaul of invocations/specs. Much cleaner and more consistent now,

with less code duplication.
This commit is contained in:
Bruce Hill 2017-09-21 21:11:13 -07:00
parent e478b33d7a
commit 26d72ce56e
8 changed files with 526 additions and 435 deletions

View File

@ -112,6 +112,22 @@ rule [entries in %dict] =:
|end |end
|return items |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 # List Comprehension
macro [%expression for %var in %iterable] =: macro [%expression for %var in %iterable] =:
assert ((%var's "type") == "Var") ".." assert ((%var's "type") == "Var") ".."

View File

@ -1,4 +1,3 @@
#.. #..
This File contains rules for making rules and macros and some helper functions to make This File contains rules for making rules and macros and some helper functions to make
that easier. that easier.
@ -8,10 +7,9 @@
lua block ".." lua block ".."
|local function make_fn(wrap_in_block) |local function make_fn(wrap_in_block)
| return function(compiler, vars, kind) | return function(compiler, vars, kind)
# Do a minimal amount of pre-processing (get the spec and the source) # Do a minimal amount of pre-processing (get the aliases and the source)
| local spec = compiler:get_invocations_from_definition(vars.spec, vars) | local aliases = compiler:repr(compiler:get_aliases(vars.macro_def))
| spec = compiler.utils.repr(spec) | local src = compiler:repr(vars.user_macro.src)
| local src = compiler.utils.repr(vars.user_macro.src)
| local user_macro = compiler:tree_to_lua(vars.user_macro) | local user_macro = compiler:tree_to_lua(vars.user_macro)
# Then produce a block of code that creates the macro at runtime # Then produce a block of code that creates the macro at runtime
| local lua = [[ | local lua = [[
@ -25,57 +23,75 @@ lua block ".."
| return lua, true | return lua, true
| end), %s) | 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) | wrap_in_block and [[lua = "do\\n "..lua.."\\nend"]] or "", src)
| return lua, true | return lua, true
| end | end
|end |end
|compiler:defmacro("macro statement %spec = %user_macro", make_fn(false), "N/A") |compiler:defmacro("macro statement %macro_def = %user_macro", make_fn(false), "see:lib/metaprogramming.nom")
|compiler:defmacro("macro block %spec = %user_macro", make_fn(true), "N/A") |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( ".."|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: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] =: "compiler"
macro [compiler's %key] =: ".."|compiler[\%key as lua\] macro [compiler's %key] =: ".."|compiler[\%key as lua\]
macro [compiler %method %args] =: macro [compiler %method %args] =:
lua block ".." lua block ".."
|local args = {} |local args = {"compiler"}
|for i,arg in ipairs(vars.args.value) do |for _,arg in ipairs(vars.args.value) do
| args[i] = compiler:tree_to_lua(arg) | table.insert(args, compiler:tree_to_lua(arg))
|end |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] =: macro [compiler utils %method %args] =:
lua block ".." lua block ".."
|local args = {} |local args = {}
|for i,arg in ipairs(vars.args.value) do |for i,arg in ipairs(vars.args.value) do
| args[i] = compiler:tree_to_lua(arg) | args[i] = compiler:tree_to_lua(arg)
|end |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 # 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 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 is currently in the middle of being defined). Being a macro also allows us to snatch
the source code and store that the source code and store that
macro block [rule %spec = %body] =: ".." macro block [rule %rule_def = %body] =: ".."
|compiler:def( |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 "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 # Get the source code for a function
rule [help %invocation] =: rule [help %rule] =:
lua block ".." lua block ".."
|local fn_info = compiler.defs[vars.invocation] |local fn_def = compiler:get_fn_def(vars.rule)
|if not fn_info then |if not fn_def then
| compiler:writeln("Function not found: "..compiler.utils.repr(vars.invocation)) | compiler:writeln("Rule not found: "..compiler:repr(vars.rule))
|else |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 |end
@ -103,8 +119,8 @@ rule [source code from tree %tree] =:
|end |end
macro [source code %body] =: 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] =: 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"]]

View File

@ -6,8 +6,8 @@ require "lib/collections.nom"
# Permission functions # Permission functions
rule [restrict %rules to within %elite-rules] =: rule [restrict %rules to within %elite-rules] =:
say ".."|Restricting \%rules\ to within \%elite-rules\ say ".."|Restricting \%rules\ to within \%elite-rules\
%rules =: compiler "get_invocations" [%rules] %rules =: keys in (compiler "get_aliases" [%rules])
%elite-rules =: compiler "get_invocations" [%elite-rules] %elite-rules =: keys in (compiler "get_aliases" [%elite-rules])
for all (flatten [%elite-rules, %rules]): for all (flatten [%elite-rules, %rules]):
assert ((compiler's "defs") has key %it) ".."|Undefined function: \%it\ assert ((compiler's "defs") has key %it) ".."|Undefined function: \%it\
for all %rules: for all %rules:
@ -20,8 +20,8 @@ rule [restrict %rules to within %elite-rules] =:
rule [allow %elite-rules to use %rules] =: rule [allow %elite-rules to use %rules] =:
say ".."|Allowing \%elite-rules\ to use \%rules\ say ".."|Allowing \%elite-rules\ to use \%rules\
%rules =: compiler "get_invocations" [%rules] %rules =: keys in (compiler "get_aliases" [%rules])
%elite-rules =: compiler "get_invocations" [%elite-rules] %elite-rules =: keys in (compiler "get_aliases" [%elite-rules])
for all (flatten [%elite-rules, %rules]): for all (flatten [%elite-rules, %rules]):
assert ((compiler's "defs") has key %it) ".."|Undefined function: \%it\ assert ((compiler's "defs") has key %it) ".."|Undefined function: \%it\
for %fn in %rules: for %fn in %rules:
@ -33,8 +33,8 @@ rule [allow %elite-rules to use %rules] =:
rule [forbid %pleb-rules to use %rules] =: rule [forbid %pleb-rules to use %rules] =:
say ".."|Forbidding \%pleb-rules\ to use \%rules\ say ".."|Forbidding \%pleb-rules\ to use \%rules\
%rules =: compiler "get_invocations" [%rules] %rules =: keys in (compiler "get_aliases" [%rules])
%pleb-rules =: compiler "get_invocations" [%pleb-rules] %pleb-rules =: keys in (compiler "get_aliases" [%pleb-rules])
for all (flatten [%pleb-rules, %used]): for all (flatten [%pleb-rules, %used]):
assert ((compiler's "defs") has key %it) ".."|Undefined function: \%it\ assert ((compiler's "defs") has key %it) ".."|Undefined function: \%it\
for all %rules: for all %rules:

View File

@ -15,7 +15,7 @@ macro block [assert %condition %msg] =: ".."
|end |end
macro block [show generated lua %block] =: ".." 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 # String functions
@ -35,7 +35,7 @@ macro [capitalize %str, %str capitalized] =: ".."
|(\%str as lua\):gsub("%l", string.upper, 1) |(\%str as lua\):gsub("%l", string.upper, 1)
macro [repr %obj] =: macro [repr %obj] =:
".."|compiler.utils.repr(\%obj as lua\) ".."|compiler:repr(\%obj as lua\)
macro [%obj as string] =: macro [%obj as string] =:
".."|compiler.utils.repr_if_not_string(\%obj as lua\) ".."|compiler.utils.repr_if_not_string(\%obj as lua\)

502
nomsu.lua
View File

@ -1,7 +1,16 @@
local re = require('re') local re = require('re')
local lpeg = require('lpeg') local lpeg = require('lpeg')
local utils = require('utils') 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 = " " local INDENT = " "
lpeg.setmaxstack(10000) lpeg.setmaxstack(10000)
local P, V, S, Cg, C, Cp, B, Cmt local P, V, S, Cg, C, Cp, B, Cmt
@ -15,6 +24,34 @@ local STRING_ESCAPES = {
f = "\f", f = "\f",
r = "\r" 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 local NomsuCompiler
do do
local _class_0 local _class_0
@ -23,169 +60,150 @@ do
self:write(...) self:write(...)
return self:write("\n") return self:write("\n")
end, end,
call = function(self, fn_name, ...) def = function(self, aliases, fn, src, is_macro)
local fn_info = self.defs[fn_name] if is_macro == nil then
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,
is_macro = false is_macro = false
}
for _index_0 = 1, #invocations do
local invocation = invocations[_index_0]
self.defs[invocation] = fn_info
end 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, end,
get_invocations_from_definition = function(self, def, vars) defmacro = function(self, aliases, fn, src)
if def.type == "String" then return self:def(aliases, fn, src, true)
return self.__class:unescape_string(def.value) 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 end
if def.type ~= "List" then for alias, args in pairs(aliases) do
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
local _continue_0 = false local _continue_0 = false
repeat repeat
local item = _list_0[_index_0] if fn_def[alias] then
if item.type == "String" then
insert(invocations, item.value)
_continue_0 = true _continue_0 = true
break break
end end
if item.type ~= "FunctionCall" then if self.defs[alias] then
self:error("Invalid list item: " .. tostring(item.type) .. ", expected FunctionCall or String") self:remove_alias(alias)
end end
local name_bits = { } if alias ~= first_alias and not utils.equivalent(utils.set(args), utils.set(first_args)) then
local _list_1 = item.value self:error("Conflicting argument names between " .. tostring(first_alias) .. " and " .. tostring(alias))
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 end
end fn_def.aliases[alias] = args
insert(invocations, table.concat(name_bits, " ")) self.defs[alias] = fn_def
_continue_0 = true _continue_0 = true
until true until true
if not _continue_0 then if not _continue_0 then
break break
end end
end end
return invocations
end, end,
get_invocations = function(self, text) remove_alias = function(self, alias)
if not text then local fn_def = self.defs[alias]
self:error("No text provided!") if not fn_def then
return
end end
if type(text) == 'function' then fn_def.aliases[alias] = nil
error("Function passed to get_invocations") self.defs[alias] = nil
end,
remove_aliases = function(self, aliases)
for alias in pairs(aliases) do
self:remove_alias(alias)
end end
if type(text) == 'string' then end,
text = { get_fn_def = function(self, x)
text if not x then
self:error("Nothing to get function def from")
end
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 end
local invocations = { } local args
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 do
local _accum_0 = { } local _accum_0 = { }
local _len_0 = 1 local _len_0 = 1
for arg in _text:gmatch("%%(%S[^%s']*)") do local _list_0 = tree.value
_accum_0[_len_0] = arg 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 _len_0 = _len_0 + 1
end end
_arg_names = _accum_0
end end
insert(invocations, invocation) args = _accum_0
arg_names[invocation] = _arg_names
end 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, end,
defmacro = function(self, spec, lua_gen_fn, src) check_permission = function(self, fn_name)
if self.debug then local fn_def = self.defs[fn_name]
self:writeln("DEFINING MACRO: " .. tostring(spec) .. tostring(src or "")) if fn_def == nil then
self:error("Undefined function: " .. tostring(fn_name))
end end
local invocations, arg_names = self:get_invocations(spec) if fn_def.whiteset == nil then
for i = 2, #invocations do return true
if not utils.equivalent(utils.set(arg_names[invocations[1]]), utils.set(arg_names[invocations[i]])) then end
self:error("Conflicting argument names " .. tostring(utils.repr(invocations[1])) .. " and " .. tostring(utils.repr(invocations[i])) .. " for " .. tostring(utils.repr(spec))) 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
end end
local fn_info = { return false
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
end, end,
parse = function(self, str, filename) parse = function(self, str, filename)
if self.debug then if self.debug then
@ -204,7 +222,7 @@ do
local check_dedent local check_dedent
check_dedent = function(subject, end_pos, spaces) check_dedent = function(subject, end_pos, spaces)
if #spaces < indent_stack[#indent_stack] then if #spaces < indent_stack[#indent_stack] then
table.remove(indent_stack) remove(indent_stack)
return end_pos return end_pos
end end
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") 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 end
} }
local tree_mt = {
__tostring = function(self)
return tostring(self.type) .. "(" .. tostring(repr(self.value)) .. ")"
end
}
setmetatable(defs, { setmetatable(defs, {
__index = function(t, key) __index = function(t, key)
local fn local fn
fn = function(src, value, errors) fn = function(src, value, errors)
local token = { return setmetatable({
type = key, type = key,
src = src, src = src,
value = value, value = value,
errors = errors errors = errors
} }, tree_mt)
return token
end end
t[key] = fn t[key] = fn
return fn return fn
@ -355,7 +377,7 @@ do
tree_to_lua = function(self, tree) tree_to_lua = function(self, tree)
assert(tree, "No tree provided.") assert(tree, "No tree provided.")
if not tree.type then if not tree.type then
self:error("Invalid tree: " .. tostring(utils.repr(tree))) self:error("Invalid tree: " .. tostring(repr(tree)))
end end
local _exp_0 = tree.type local _exp_0 = tree.type
if "File" == _exp_0 then if "File" == _exp_0 then
@ -375,7 +397,7 @@ do
local lua_code = "\n return (function(compiler, vars)\n" .. tostring(code) .. "\nend)" local lua_code = "\n return (function(compiler, vars)\n" .. tostring(code) .. "\nend)"
local lua_thunk, err = load(lua_code) local lua_thunk, err = load(lua_code)
if not lua_thunk then 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 end
local value = lua_thunk() local value = lua_thunk()
local return_value local return_value
@ -389,7 +411,7 @@ do
insert(buffer, [[ return ret insert(buffer, [[ return ret
end) end)
]]) ]])
return table.concat(buffer, "\n"), return_value return concat(buffer, "\n"), return_value
elseif "Block" == _exp_0 then elseif "Block" == _exp_0 then
local buffer = { } local buffer = { }
local _list_0 = tree.value local _list_0 = tree.value
@ -397,7 +419,7 @@ do
local statement = _list_0[_index_0] local statement = _list_0[_index_0]
insert(buffer, self:tree_to_lua(statement)) insert(buffer, self:tree_to_lua(statement))
end end
return table.concat(buffer, "\n") return concat(buffer, "\n")
elseif "Thunk" == _exp_0 then elseif "Thunk" == _exp_0 then
assert(tree.value.type == "Block", "Non-block value in Thunk") assert(tree.value.type == "Block", "Non-block value in Thunk")
return [[ (function(compiler, vars) return [[ (function(compiler, vars)
@ -407,15 +429,15 @@ do
]] ]]
elseif "Statement" == _exp_0 then elseif "Statement" == _exp_0 then
if tree.value.type == "FunctionCall" then if tree.value.type == "FunctionCall" then
local name = self:fn_name_from_tree(tree.value) local alias = self:get_alias(tree.value)
if self.defs[name] and self.defs[name].is_macro then if self.defs[alias] and self.defs[alias].is_macro then
return self:run_macro(tree.value, "Statement") return self:run_macro(tree.value, "Statement")
end end
end end
return "ret = " .. (self:tree_to_lua(tree.value)) return "ret = " .. (self:tree_to_lua(tree.value))
elseif "FunctionCall" == _exp_0 then elseif "FunctionCall" == _exp_0 then
local name = self:fn_name_from_tree(tree) local alias = self:get_alias(tree)
if self.defs[name] and self.defs[name].is_macro then if self.defs[alias] and self.defs[alias].is_macro then
return self:run_macro(tree, "Expression") return self:run_macro(tree, "Expression")
else else
local args local args
@ -432,11 +454,11 @@ do
end end
args = _accum_0 args = _accum_0
end end
insert(args, 1, utils.repr(name)) insert(args, 1, repr(alias))
return self.__class:comma_separated_items("compiler:call(", args, ")") return self.__class:comma_separated_items("compiler:call(", args, ")")
end end
elseif "String" == _exp_0 then 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 elseif "Longstring" == _exp_0 then
local concat_parts = { } local concat_parts = { }
local string_buffer = "" local string_buffer = ""
@ -450,7 +472,7 @@ do
string_buffer = string_buffer .. bit:gsub("\\\\", "\\") string_buffer = string_buffer .. bit:gsub("\\\\", "\\")
else else
if string_buffer ~= "" then if string_buffer ~= "" then
insert(concat_parts, utils.repr(string_buffer)) insert(concat_parts, repr(string_buffer))
string_buffer = "" string_buffer = ""
end end
insert(concat_parts, "compiler.utils.repr_if_not_string(" .. tostring(self:tree_to_lua(bit)) .. ")") insert(concat_parts, "compiler.utils.repr_if_not_string(" .. tostring(self:tree_to_lua(bit)) .. ")")
@ -458,14 +480,14 @@ do
end end
end end
if string_buffer ~= "" then if string_buffer ~= "" then
insert(concat_parts, utils.repr(string_buffer)) insert(concat_parts, repr(string_buffer))
end end
if #concat_parts == 0 then if #concat_parts == 0 then
return "''" return "''"
elseif #concat_parts == 1 then elseif #concat_parts == 1 then
return concat_parts[1] return concat_parts[1]
else else
return "(" .. tostring(table.concat(concat_parts, "..")) .. ")" return "(" .. tostring(concat(concat_parts, "..")) .. ")"
end end
elseif "Number" == _exp_0 then elseif "Number" == _exp_0 then
return tree.value return tree.value
@ -488,26 +510,94 @@ do
end)(), "}") end)(), "}")
end end
elseif "Var" == _exp_0 then elseif "Var" == _exp_0 then
return "vars[" .. tostring(utils.repr(tree.value)) .. "]" return "vars[" .. tostring(repr(tree.value)) .. "]"
else else
return self:error("Unknown/unimplemented thingy: " .. tostring(tree.type)) return self:error("Unknown/unimplemented thingy: " .. tostring(tree.type))
end end
end, end,
fn_name_from_tree = function(self, tree) get_alias = function(self, x)
assert(tree.type == "FunctionCall", "Attempt to get fn name from non-functioncall tree: " .. tostring(tree.type)) if not x then
local name_bits = { } self:error("Nothing to get alias from")
local _list_0 = tree.value 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 for _index_0 = 1, #_list_0 do
local token = _list_0[_index_0] local token = _list_0[_index_0]
insert(name_bits, (function() local _exp_1 = token.type
if token.type == "Word" then if "Word" == _exp_1 then
return token.value insert(alias, token.value)
elseif "Var" == _exp_1 then
insert(alias, "%")
insert(args, token.value)
else else
return "%" insert(alias, "%")
insert(args, token)
end end
end)())
end end
return table.concat(name_bits, " ") 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
end, end,
var_to_lua_identifier = function(self, var) var_to_lua_identifier = function(self, var)
if var.type ~= "Var" then if var.type ~= "Var" then
@ -521,54 +611,6 @@ do
end end
end)) 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) _yield_tree = function(self, tree, indent_level)
if indent_level == nil then if indent_level == nil then
indent_level = 0 indent_level = 0
@ -580,9 +622,9 @@ do
local _exp_0 = tree.type local _exp_0 = tree.type
if "File" == _exp_0 then if "File" == _exp_0 then
coroutine.yield(ind("File:")) 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 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 elseif "Block" == _exp_0 then
local _list_0 = tree.value local _list_0 = tree.value
for _index_0 = 1, #_list_0 do for _index_0 = 1, #_list_0 do
@ -591,11 +633,11 @@ do
end end
elseif "Thunk" == _exp_0 then elseif "Thunk" == _exp_0 then
coroutine.yield(ind("Thunk:")) 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 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 elseif "FunctionCall" == _exp_0 then
local name = self:fn_name_from_tree(tree) local alias = self:get_alias(tree)
local args local args
do do
local _accum_0 = { } local _accum_0 = { }
@ -611,23 +653,25 @@ do
args = _accum_0 args = _accum_0
end end
if #args == 0 then if #args == 0 then
coroutine.yield(ind("Call [" .. tostring(name) .. "]!")) return coroutine.yield(ind("Call [" .. tostring(alias) .. "]!"))
else else
coroutine.yield(ind("Call [" .. tostring(name) .. "]:")) coroutine.yield(ind("Call [" .. tostring(alias) .. "]:"))
for _index_0 = 1, #args do for _index_0 = 1, #args do
local a = args[_index_0] local a = args[_index_0]
self:_yield_tree(a, indent_level + 1) self:_yield_tree(a, indent_level + 1)
end end
end end
elseif "String" == _exp_0 then elseif "String" == _exp_0 then
coroutine.yield(ind(utils.repr(tree.value))) return coroutine.yield(ind(repr(tree.value)))
elseif "Longstring" == _exp_0 then elseif "Longstring" == _exp_0 then
coroutine.yield(ind(utils.repr(tree.value))) return coroutine.yield(ind(repr(tree.value)))
elseif "Number" == _exp_0 then 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 elseif "List" == _exp_0 then
if #tree.value == 0 then if #tree.value == 0 then
coroutine.yield(ind("<Empty List>")) return coroutine.yield(ind("<Empty List>"))
else else
coroutine.yield(ind("List:")) coroutine.yield(ind("List:"))
local _list_0 = tree.value local _list_0 = tree.value
@ -636,12 +680,9 @@ do
self:_yield_tree(item, indent_level + 1) self:_yield_tree(item, indent_level + 1)
end end
end end
elseif "Var" == _exp_0 then
coroutine.yield(ind("Var[" .. tostring(utils.repr(tree.value)) .. "]"))
else else
error("Unknown/unimplemented thingy: " .. tostring(tree.type)) return error("Unknown/unimplemented thingy: " .. tostring(tree.type))
end end
return nil
end, end,
print_tree = function(self, tree) print_tree = function(self, tree)
for line in coroutine.wrap(function() for line in coroutine.wrap(function()
@ -657,7 +698,7 @@ do
end) do end) do
insert(result, line) insert(result, line)
end end
return table.concat(result, "\n") return concat(result, "\n")
end, end,
run = function(self, src, filename, output_file) run = function(self, src, filename, output_file)
if output_file == nil then if output_file == nil then
@ -707,22 +748,22 @@ do
end end
end, end,
initialize_core = function(self) 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 if kind == "Expression" then
error("Expected to be in statement.") error("Expected to be in statement.")
end end
local inner_vars = setmetatable({ }, { local inner_vars = setmetatable({ }, {
__index = function(_, key) __index = function(_, key)
return error("vars[" .. tostring(utils.repr(key)) .. "]") return error("vars[" .. tostring(repr(key)) .. "]")
end end
}) })
return "do\n" .. self:tree_to_value(vars.lua_code, inner_vars) .. "\nend", true return "do\n" .. self:tree_to_value(vars.lua_code, inner_vars) .. "\nend", true
end) 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 lua_code = vars.lua_code.value
local inner_vars = setmetatable({ }, { local inner_vars = setmetatable({ }, {
__index = function(_, key) __index = function(_, key)
return error("vars[" .. tostring(utils.repr(key)) .. "]") return error("vars[" .. tostring(repr(key)) .. "]")
end end
}) })
return self:tree_to_value(vars.lua_code, inner_vars) return self:tree_to_value(vars.lua_code, inner_vars)
@ -759,6 +800,9 @@ do
self.debug = false self.debug = false
self:initialize_core() self:initialize_core()
self.utils = utils self.utils = utils
self.repr = function(self, ...)
return repr(...)
end
self.loaded_files = { } self.loaded_files = { }
end, end,
__base = _base_0, __base = _base_0,
@ -795,7 +839,7 @@ do
end end
end end
insert(bits, close) insert(bits, close)
return table.concat(bits) return concat(bits)
end end
NomsuCompiler = _class_0 NomsuCompiler = _class_0
end end
@ -845,7 +889,7 @@ elseif arg then
return c:run(buff) return c:run(buff)
end) end)
if ok and ret ~= nil then if ok and ret ~= nil then
print("= " .. utils.repr(ret)) print("= " .. repr(ret))
end end
end end
end end

View File

@ -2,7 +2,9 @@
re = require 're' re = require 're'
lpeg = require 'lpeg' lpeg = require 'lpeg'
utils = require 'utils' utils = require 'utils'
insert = table.insert repr = utils.repr
{:insert, :remove, :concat} = table
pcall = (fn,...)-> true, fn(...)
-- TODO: -- TODO:
-- improve indentation of generated lua code -- improve indentation of generated lua code
@ -19,6 +21,14 @@ lpeg.setmaxstack 10000 -- whoa
{:P,:V,:S,:Cg,:C,:Cp,:B,:Cmt} = lpeg {: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" 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 class NomsuCompiler
new:(parent)=> new:(parent)=>
@ -28,102 +38,93 @@ class NomsuCompiler
@debug = false @debug = false
@initialize_core! @initialize_core!
@utils = utils @utils = utils
@repr = (...)=> repr(...)
@loaded_files = {} @loaded_files = {}
writeln:(...)=> writeln:(...)=>
@write(...) @write(...)
@write("\n") @write("\n")
call: (fn_name,...)=> def: (aliases, fn, src, is_macro=false)=>
fn_info = @defs[fn_name] if type(aliases) == 'string'
if fn_info == nil aliases = @get_aliases aliases
@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])}
if @debug if @debug
@writeln "Calling #{fn_name} with args: #{utils.repr(args)}" @writeln "Defining rule: #{aliases}"
ret = fn(self, args) fn_def = FunctionDef(fn, {}, src, is_macro)
table.remove @callstack @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 return ret
check_permission: (fn_name)=> check_permission: (fn_name)=>
fn_info = @defs[fn_name] fn_def = @defs[fn_name]
if fn_info == nil if fn_def == nil
@error "Undefined function: #{fn_name}" @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 -- TODO: optimize this, maybe by making the callstack a Counter and having a move-to-front optimization on the whitelist
for caller in *@callstack for caller in *@callstack
if fn_info.whiteset[caller] if fn_def.whiteset[caller]
return true return true
return false 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)=> parse: (str, filename)=>
if @debug if @debug
@writeln("PARSING:\n#{str}") @writeln("PARSING:\n#{str}")
@ -136,7 +137,7 @@ class NomsuCompiler
return end_pos return end_pos
check_dedent = (subject,end_pos,spaces)-> check_dedent = (subject,end_pos,spaces)->
if #spaces < indent_stack[#indent_stack] if #spaces < indent_stack[#indent_stack]
table.remove(indent_stack) remove(indent_stack)
return end_pos return end_pos
check_nodent = (subject,end_pos,spaces)-> check_nodent = (subject,end_pos,spaces)->
if #spaces == indent_stack[#indent_stack] if #spaces == indent_stack[#indent_stack]
@ -245,11 +246,10 @@ class NomsuCompiler
pointer = ("-")\rep(err_pos - start_of_err_line + 0) .. "^" 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") 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, { setmetatable(defs, {
__index: (t,key)-> __index: (t,key)->
fn = (src, value, errors)-> fn = (src, value, errors)-> setmetatable({type: key, :src, :value, :errors}, tree_mt)
token = {type: key, :src, :value, :errors}
return token
t[key] = fn t[key] = fn
return fn return fn
}) })
@ -272,7 +272,7 @@ class NomsuCompiler
tree_to_lua: (tree)=> tree_to_lua: (tree)=>
assert tree, "No tree provided." assert tree, "No tree provided."
if not tree.type if not tree.type
@error "Invalid tree: #{utils.repr(tree)}" @error "Invalid tree: #{repr(tree)}"
switch tree.type switch tree.type
when "File" when "File"
buffer = {[[return (function(compiler, vars) buffer = {[[return (function(compiler, vars)
@ -288,7 +288,7 @@ class NomsuCompiler
return (function(compiler, vars)\n#{code}\nend)" return (function(compiler, vars)\n#{code}\nend)"
lua_thunk, err = load(lua_code) lua_thunk, err = load(lua_code)
if not lua_thunk 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! value = lua_thunk!
ok,return_value = pcall(value, self, vars) ok,return_value = pcall(value, self, vars)
if not ok if not ok
@ -299,13 +299,13 @@ class NomsuCompiler
return ret return ret
end) end)
]] ]]
return table.concat(buffer, "\n"), return_value return concat(buffer, "\n"), return_value
when "Block" when "Block"
buffer = {} buffer = {}
for statement in *tree.value for statement in *tree.value
insert buffer, @tree_to_lua(statement) insert buffer, @tree_to_lua(statement)
return table.concat(buffer, "\n") return concat(buffer, "\n")
when "Thunk" when "Thunk"
assert tree.value.type == "Block", "Non-block value in Thunk" assert tree.value.type == "Block", "Non-block value in Thunk"
@ -320,22 +320,22 @@ class NomsuCompiler
when "Statement" when "Statement"
-- This case here is to prevent "ret =" from getting prepended when the macro might not want it -- This case here is to prevent "ret =" from getting prepended when the macro might not want it
if tree.value.type == "FunctionCall" if tree.value.type == "FunctionCall"
name = @fn_name_from_tree(tree.value) alias = @get_alias(tree.value)
if @defs[name] and @defs[name].is_macro if @defs[alias] and @defs[alias].is_macro
return @run_macro(tree.value, "Statement") return @run_macro(tree.value, "Statement")
return "ret = "..(@tree_to_lua(tree.value)) return "ret = "..(@tree_to_lua(tree.value))
when "FunctionCall" when "FunctionCall"
name = @fn_name_from_tree(tree) alias = @get_alias(tree)
if @defs[name] and @defs[name].is_macro if @defs[alias] and @defs[alias].is_macro
return @run_macro(tree, "Expression") return @run_macro(tree, "Expression")
else else
args = [@tree_to_lua(a) for a in *tree.value when a.type != "Word"] 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, ")") return @@comma_separated_items("compiler:call(", args, ")")
when "String" when "String"
return utils.repr(@@unescape_string(tree.value)) return repr(@@unescape_string(tree.value))
when "Longstring" when "Longstring"
concat_parts = {} concat_parts = {}
@ -347,19 +347,19 @@ class NomsuCompiler
string_buffer ..= bit\gsub("\\\\","\\") string_buffer ..= bit\gsub("\\\\","\\")
else else
if string_buffer ~= "" if string_buffer ~= ""
insert concat_parts, utils.repr(string_buffer) insert concat_parts, repr(string_buffer)
string_buffer = "" string_buffer = ""
insert concat_parts, "compiler.utils.repr_if_not_string(#{@tree_to_lua(bit)})" insert concat_parts, "compiler.utils.repr_if_not_string(#{@tree_to_lua(bit)})"
if string_buffer ~= "" if string_buffer ~= ""
insert concat_parts, utils.repr(string_buffer) insert concat_parts, repr(string_buffer)
if #concat_parts == 0 if #concat_parts == 0
return "''" return "''"
elseif #concat_parts == 1 elseif #concat_parts == 1
return concat_parts[1] return concat_parts[1]
else else
return "(#{table.concat(concat_parts, "..")})" return "(#{concat(concat_parts, "..")})"
when "Number" when "Number"
return tree.value return tree.value
@ -373,7 +373,7 @@ class NomsuCompiler
return @@comma_separated_items("{", [@tree_to_lua(item) for item in *tree.value], "}") return @@comma_separated_items("{", [@tree_to_lua(item) for item in *tree.value], "}")
when "Var" when "Var"
return "vars[#{utils.repr(tree.value)}]" return "vars[#{repr(tree.value)}]"
else else
@error("Unknown/unimplemented thingy: #{tree.type}") @error("Unknown/unimplemented thingy: #{tree.type}")
@ -392,81 +392,88 @@ class NomsuCompiler
insert bits, "\n" insert bits, "\n"
so_far = 0 so_far = 0
insert bits, close insert bits, close
return table.concat(bits) return concat(bits)
fn_name_from_tree: (tree)=> get_alias: (x)=>
assert(tree.type == "FunctionCall", "Attempt to get fn name from non-functioncall tree: #{tree.type}") if not x then @error "Nothing to get alias from"
name_bits = {} -- Returns a single alias ("say %"), and list of args ({msg}) from a single rule def
for token in *tree.value -- (e.g. "say %msg") or function call (e.g. FunctionCall({Word("say"), Var("msg")))
insert name_bits, if token.type == "Word" then token.value else "%" if type(x) == 'string'
table.concat(name_bits, " ") -- 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)=> var_to_lua_identifier: (var)=>
-- Converts arbitrary nomsu vars to valid lua identifiers by replacing illegal
-- characters with escape sequences
if var.type != "Var" 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) @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)-> "var"..(var.value\gsub "%W", (verboten)->
if verboten == "_" then "__" else ("_%x")\format(verboten\byte!)) 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)=> _yield_tree: (tree, indent_level=0)=>
ind = (s) -> INDENT\rep(indent_level)..s ind = (s) -> INDENT\rep(indent_level)..s
switch tree.type switch tree.type
when "File" when "File"
coroutine.yield(ind"File:") coroutine.yield(ind"File:")
@_yield_tree(tree.value.body, indent_level+1) @_yield_tree(tree.value.body, indent_level+1)
when "Errors" then coroutine.yield(ind"Error:\n#{tree.value}")
when "Errors"
coroutine.yield(ind"Error:\n#{tree.value}")
when "Block" when "Block"
for chunk in *tree.value for chunk in *tree.value
@_yield_tree(chunk, indent_level) @_yield_tree(chunk, indent_level)
when "Thunk" when "Thunk"
coroutine.yield(ind"Thunk:") coroutine.yield(ind"Thunk:")
@_yield_tree(tree.value, indent_level+1) @_yield_tree(tree.value, indent_level+1)
when "Statement" then @_yield_tree(tree.value, indent_level)
when "Statement"
@_yield_tree(tree.value, indent_level)
when "FunctionCall" when "FunctionCall"
name = @fn_name_from_tree(tree) alias = @get_alias tree
args = [a for a in *tree.value when a.type != "Word"] args = [a for a in *tree.value when a.type != "Word"]
if #args == 0 if #args == 0
coroutine.yield(ind"Call [#{name}]!") coroutine.yield(ind"Call [#{alias}]!")
else else
coroutine.yield(ind"Call [#{name}]:") coroutine.yield(ind"Call [#{alias}]:")
for a in *args for a in *args
@_yield_tree(a, indent_level+1) @_yield_tree(a, indent_level+1)
when "String" then coroutine.yield(ind(repr(tree.value)))
when "String" when "Longstring" then coroutine.yield(ind(repr(tree.value)))
-- TODO: Better implement when "Number" then coroutine.yield(ind(tree.value))
coroutine.yield(ind(utils.repr(tree.value))) when "Var" then coroutine.yield ind"Var[#{repr(tree.value)}]"
when "Longstring"
-- TODO: Better implement
coroutine.yield(ind(utils.repr(tree.value)))
when "Number"
coroutine.yield(ind(tree.value))
when "List" when "List"
if #tree.value == 0 if #tree.value == 0
coroutine.yield(ind("<Empty List>")) coroutine.yield(ind("<Empty List>"))
@ -474,13 +481,7 @@ class NomsuCompiler
coroutine.yield(ind"List:") coroutine.yield(ind"List:")
for item in *tree.value for item in *tree.value
@_yield_tree(item, indent_level+1) @_yield_tree(item, indent_level+1)
else error("Unknown/unimplemented thingy: #{tree.type}")
when "Var"
coroutine.yield ind"Var[#{utils.repr(tree.value)}]"
else
error("Unknown/unimplemented thingy: #{tree.type}")
return nil -- to prevent tail calls
print_tree:(tree)=> print_tree:(tree)=>
for line in coroutine.wrap(-> @_yield_tree(tree)) for line in coroutine.wrap(-> @_yield_tree(tree))
@ -490,7 +491,7 @@ class NomsuCompiler
result = {} result = {}
for line in coroutine.wrap(-> @_yield_tree(tree)) for line in coroutine.wrap(-> @_yield_tree(tree))
insert(result, line) insert(result, line)
return table.concat result, "\n" return concat result, "\n"
run: (src, filename, output_file=nil)=> run: (src, filename, output_file=nil)=>
if @debug if @debug
@ -533,14 +534,14 @@ class NomsuCompiler
initialize_core: => initialize_core: =>
-- Sets up some core functionality -- 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.") 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 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 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) return @tree_to_value(vars.lua_code, inner_vars)
@def "require %filename", (vars)=> @def "require %filename", (vars)=>
@ -607,6 +608,6 @@ elseif arg
break break
ok, ret = pcall(-> c\run(buff)) ok, ret = pcall(-> c\run(buff))
if ok and ret != nil if ok and ret != nil
print "= "..utils.repr(ret) print "= "..repr(ret)
return NomsuCompiler return NomsuCompiler

View File

@ -78,6 +78,14 @@ utils = {
end end
return _accum_0 return _accum_0
end, 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) accumulate = function(glue, co)
if co == nil then if co == nil then
glue, co = "", glue glue, co = "", glue

View File

@ -41,6 +41,12 @@ utils = {
split: (str, sep="%s")-> split: (str, sep="%s")->
[chunk for chunk in str\gmatch("[^#{sep}]+")] [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)-> accumulate: (glue, co)->
if co == nil then glue, co = "", glue if co == nil then glue, co = "", glue
bits = {} bits = {}