local U = require "alicorn-utils"
local gen = require "terms-generators"
local MatcherKind =
{
Symbol = "Symbol",
Pair = "Pair",
Nil = "Nil",
Value = "Value",
Reducible = "Reducible",
}
local function issymbol(handler)
return {
kind = MatcherKind.Symbol,
handler = handler,
}
end
local function ispair(handler)
return {
kind = MatcherKind.Pair,
handler = handler,
}
end
local function isnil(handler)
return {
kind = MatcherKind.Nil,
handler = handler,
}
end
local function isvalue(handler)
return {
kind = MatcherKind.Value,
handler = handler,
}
end
local function get_reducer(reducible)
return getmetatable(reducible.reducible).reducer
end
local function dispatch_reducer(handler_mapping, default, matcher)
if matcher.kind == MatcherKind.Reducible then
if handler_mapping[get_reducer(matcher)] then
return handler_mapping[get_reducer(matcher)](matcher)
else
return default(matcher)
end
else
default(matcher)
end
end
local function create_reducible(self, handler, ...)
local funcnew = {
...,
}
setmetatable(funcnew, self.mt)
local reducible = {
kind = MatcherKind.Reducible,
handler = handler,
reducible = funcnew,
}
return reducible
end
local reducer_mt = { __call = create_reducible }
local ExternalError = {}
local external_error_mt = {
__tostring = function(self)
local message = "Lua error raised inside reducer "
.. self.reducer_name
.. " "
.. (self.span and tostring(self.span) or "at unknown position")
.. ":\n"
local cause = tostring(self.cause)
if cause:find("table", 1, true) == 1 then
for k, v in
pairs(self.cause )
do
message = message .. tostring(k)
message = message .. " = "
message = message .. tostring(v)
message = message .. "\n"
end
else
message = message .. cause
end
return message
end,
__index = ExternalError,
}
function ExternalError.new(cause, span, reducer_name)
return setmetatable({
cause = cause,
span = span,
reducer_name = reducer_name,
}, external_error_mt)
end
local function augment_error(syntax, reducer_name, ok, err_msg, ...)
if not ok then
return false, U.notail(ExternalError.new(err_msg, syntax.span, reducer_name))
end
return err_msg, ...
end
local pdump = require "pretty-printer".s
local protect_reducer_func_calls = true
local function reducer(func, name)
local function funcwrapper(syntax, matcher)
if protect_reducer_func_calls then
return augment_error(
syntax,
name,
xpcall(func, U.custom_traceback, syntax, table.unpack(matcher.reducible))
)
else
return func(syntax, table.unpack(matcher.reducible))
end
end
local reducer = {
wrapper = funcwrapper,
create_reducible = create_reducible,
}
local funcnew_mt = {
name = name,
__index = {
reduce = funcwrapper,
},
reducer = reducer,
}
reducer.mt = funcnew_mt
setmetatable(reducer, reducer_mt)
return reducer
end
local function symbolexacthandler(expected, symbol)
if symbol.str == expected then
return true
else
return false, "symbol is expected to be exactly " .. expected .. " but was instead " .. symbol.str
end
end
local function accept_handler(data, ...)
return true, ...
end
local function failure_handler(data, exception)
return false, exception
end
local function SymbolExact(syntax, name)
return U.notail(syntax:match({
issymbol(symbolexacthandler),
}, failure_handler, name))
end
local symbol_exact = reducer(SymbolExact, "symbol exact")
local SyntaxError = {}
function SyntaxError:__tostring()
local message = "Syntax error at span "
.. (self.span and tostring(self.span) or "<unknown position>")
.. " must be acceptable for one of:\n"
local options = {}
for k, v in ipairs(self.matchers) do
if v.kind == MatcherKind.Reducible then
options[k] = v.kind .. ": " .. getmetatable(v.reducible).name
else
options[k] = v.kind
end
end
message = message .. "[ " .. table.concat(options, ", ") .. " ]"
message = message .. "\nbut was rejected"
if self.cause then
message = message .. " because:\n" .. tostring(self.cause)
end
return message
end
local syntax_error_mt = {
__tostring = SyntaxError.__tostring,
}
local function syntax_error(matchers, span, cause)
return setmetatable({
matchers = matchers,
span = span,
cause = cause,
}, syntax_error_mt)
end
local ConstructedSyntax = {}
function ConstructedSyntax:match(matchers, unmatched, extra)
if matchers.kind ~= nil then
error("matchers must be a list of matchers (not a matcher itself)")
end
local lasterr = nil
for _, matcher in ipairs(matchers) do
if self.accepters[matcher.kind] then
return self.accepters[matcher.kind](self, matcher, extra)
elseif matcher.kind == MatcherKind.Reducible then
local res = table.pack(matcher.reducible.reduce(self, matcher))
if res[1] then
if not matcher.handler then
print("missing handler for ", matcher.kind, debug.traceback())
end
return U.notail(matcher.handler(extra, table.unpack(res, 2, res.n)))
end
lasterr = res[2]
end
end
return U.notail(unmatched(extra, syntax_error(matchers, self.span, lasterr)))
end
local constructed_syntax_mt = {
__index = ConstructedSyntax,
}
local function cons_syntax(accepters, span, ...)
if getmetatable(span) ~= require("format").span_mt then
error(("metalanguage.cons_syntax called with non-span: %s"):format(span))
end
return setmetatable({ accepters = accepters, span = span, ... }, constructed_syntax_mt)
end
local pair_accepters = {
Pair = function(self, matcher, extra)
return U.notail(matcher.handler(extra, self[1], self[2]))
end,
}
local function pair(span, a, b)
return U.notail(cons_syntax(pair_accepters, span, a, b))
end
local symbol_accepters = {
Symbol = function(self, matcher, extra)
return U.notail(matcher.handler(extra, self[1]))
end,
}
local function symbol_syntax(span, symbol)
return U.notail(cons_syntax(symbol_accepters, span, symbol))
end
local value_accepters = {
Value = function(self, matcher, extra)
return U.notail(matcher.handler(extra, self[1]))
end,
}
local function value(span, val)
return U.notail(cons_syntax(value_accepters, span, val))
end
local nil_accepters = {
Nil = function(self, matcher, extra)
return U.notail(matcher.handler(extra))
end,
}
local function new_nilval(span)
return U.notail(cons_syntax(nil_accepters, span))
end
local function list(span, a, ...)
if a == nil then
return U.notail(new_nilval(span))
end
return pair(span, a, list(span, ...))
end
local any = reducer(
function(syntax)
return true, syntax
end,
"any"
)
local function list_match_pair_handler(rule, a, b)
local ok, val = a:match({ rule }, failure_handler, nil)
return ok, val, b
end
local function ListMatch(syntax, ...)
local args = {}
local ok, err, val, tail = true, nil, true, nil
for i, rule in ipairs({ ... }) do
ok, val, tail = syntax:match({
ispair(list_match_pair_handler),
}, failure_handler, rule)
if not ok then
return false, val
end
args[#args + 1] = val
syntax = tail
end
ok, err = syntax:match({
isnil(accept_handler),
}, failure_handler, nil)
if not ok then
return false, err
end
return true, table.unpack(args)
end
local listmatch = reducer(ListMatch, "list_match")
local function ListTail(syntax, ...)
local args = {}
local ok, err, val, tail = true, nil, true, nil
for i, rule in ipairs({ ... }) do
ok, val, tail = syntax:match({
ispair(list_match_pair_handler),
}, failure_handler, rule)
if not ok then
return false, val
end
args[#args + 1] = val
syntax = tail
end
args[#args + 1] = tail
return true, table.unpack(args)
end
local listtail = reducer(ListTail, "list+tail")
local function list_many_fold_pair_handler(rule, a, b)
local ok, val, thread
ok, val, thread = a:match({ rule[1] }, failure_handler, nil)
if not ok then
ok = a:match({ rule[2] }, failure_handler, nil)
if ok then
return ok, false, nil, nil, b
else
return ok, val
end
end
return ok, true, val, thread, b
end
local function list_many_nil_handler()
return true, false
end
local list_many_fold_until = reducer(
function(syntax, submatcher_fn, init_thread, termination)
local vals = {}
local ok, cont, val, thread, tail = true, true, nil, init_thread, syntax
local nextthread = init_thread
while ok and cont do
thread = nextthread
ok, cont, val, nextthread, tail = tail:match(
{
ispair(list_many_fold_pair_handler),
isnil(list_many_nil_handler),
},
failure_handler,
{
submatcher_fn(thread, tail.span),
termination,
}
)
vals[#vals + 1] = val
end
if not ok then
return ok, cont
end
return ok, vals, thread, tail
end,
"list_many_fold_until"
)
local list_many_fold = reducer(
function(syntax, submatcher_fn, init_thread)
local ok, vals, thread, tail = syntax:match(
{ list_many_fold_until(accept_handler, submatcher_fn, init_thread, nil) },
failure_handler,
nil
)
if not ok then
return ok, vals
end
return ok, vals, thread
end,
"list_many_fold"
)
local list_many = reducer(
function(syntax, submatcher)
local ok, vals, thread, tail = syntax:match(
{ list_many_fold(accept_handler, function()
return submatcher
end, {}) },
failure_handler,
nil
)
if not ok then
return ok, false
end
return true, vals
end,
"list_many"
)
local oneof = reducer(
function(syntax, ...)
return U.notail(syntax:match({ ... }, failure_handler, nil))
end,
"oneof"
)
local list_tail_ends = reducer(
function(syntax, rule)
local res = { syntax:match({ rule }, failure_handler, nil) }
local ok, err, tail = res[1], res[2], res[#res]
if not ok then
return false, err
end
ok, err = tail:match({ isnil(accept_handler) }, failure_handler, nil)
if not ok then
return false, err, "list tail does not end with nil"
end
res[#res] = nil
return table.unpack(res)
end,
"list_tail_ends"
)
local constructed_syntax_type = gen.declare_foreign(gen.metatable_equality(constructed_syntax_mt), "ConstructedSyntax")
local reducer_type = gen.declare_foreign(gen.metatable_equality(reducer_mt), "Reducer")
local matcher_type = gen.declare_foreign(function(val)
return MatcherKind[val.kind] ~= nil
end, "Matcher")
local metalanguage = {
accept_handler = accept_handler,
failure_handler = failure_handler,
ispair = ispair,
issymbol = issymbol,
isvalue = isvalue,
value = value,
any = any,
listmatch = listmatch,
oneof = oneof,
listtail = listtail,
list_many = list_many,
list_many_fold = list_many_fold,
list_many_fold_until = list_many_fold_until,
list_tail_ends = list_tail_ends,
reducer = reducer,
isnil = isnil,
new_nilval = new_nilval,
symbol_exact = symbol_exact,
pair = pair,
list = list,
symbol = symbol_syntax,
constructed_syntax_type = constructed_syntax_type,
reducer_type = reducer_type,
matcher_type = matcher_type,
}
local internals_interface = require "internals-interface"
internals_interface.metalanguage = metalanguage
return metalanguage