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
|
|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") ".."
|
||||||
|
@ -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"]]
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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
502
nomsu.lua
@ -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
|
||||||
|
323
nomsu.moon
323
nomsu.moon
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 = {}
|
||||||
|
Loading…
Reference in New Issue
Block a user