193 lines
5.3 KiB
Lua
Executable File

--------------------------------------------------------------------------------
---------------------- ## ##### ##### ###### -----------------------
---------------------- ## ## ## ## ## ## ## -----------------------
---------------------- ## ## ## ## ## ###### -----------------------
---------------------- ## ## ## ## ## ## -----------------------
---------------------- ###### ##### ##### ## -----------------------
---------------------- -----------------------
----------------------- Lua Object-Oriented Programming ------------------------
--------------------------------------------------------------------------------
-- Project: LOOP Class Library --
-- Release: 2.3 beta --
-- Title : Matcher of Lua Values --
-- Author : Renato Maia <maia@inf.puc-rio.br> --
--------------------------------------------------------------------------------
local pairs = pairs
local type = type
local unpack = unpack
local select = select
local getfenv = getfenv
local tostring = tostring
local newproxy = newproxy
local getmetatable = getmetatable
local setmetatable = setmetatable
local getupvalue = debug and debug.getupvalue
local string = require "string"
local table = require "loop.table"
local oo = require "loop.base"
module("loop.debug.Matcher", oo.class)
__mode = "k"
isomorphic = true
environment = getfenv
upvalue = getupvalue
metakey = newproxy()
envkey = newproxy()
function error(self, message)
local path = { "value" }
for i = 2, #self do
local key = self[i]
if key == metakey then
table.insert(path, 1, "getmetatable(")
key = ")"
elseif key == envkey then
table.insert(path, 1, "getfenv(")
key = ")"
elseif type(key) == "string" then
if key:match("^[%a_][%w_]*$") then
key = "."..key
else
key = string.format("[%q]", key)
end
else
key = string.format("[%s]", tostring(key))
end
path[#path+1] = key
end
return string.format("%s: %s", table.concat(path), message)
end
function matchtable(self, value, other)
local matched, errmsg = true
local keysmatched = {}
self[value], self[other] = other, value
for key, field in pairs(value) do
local otherfield = other[key]
if otherfield == nil then
matched = false
for otherkey, otherfield in pairs(other) do
local matcher = setmetatable(table.copy(self), getmetatable(self))
matcher.error = nil
if
matcher:match(key, otherkey) and
matcher:match(field, otherfield)
then
table.copy(matcher, self)
keysmatched[otherkey] = true
matched = true
break
end
end
if not matched then
self[#self+1] = key
errmsg = self:error("no match found")
self[#self] = nil
break
end
else
self[#self+1] = key
matched, errmsg = self:match(field, otherfield)
self[#self] = nil
if matched then
keysmatched[key] = true
else
break
end
end
end
if matched and self.isomorphic then
for otherkey, otherfield in pairs(other) do
if not keysmatched[otherkey] then
self[#self+1] = otherkey
matched, errmsg = false, self:error("missing")
self[#self] = nil
break
end
end
end
if not matched then self[value], self[other] = nil, nil end
return matched, errmsg
end
local dump = string.dump
function matchfunction(self, func, other)
local matched, errmsg = (dump(func) == dump(other))
if matched then
self[func], self[other] = other, func
local upvalue = self.upvalue
if upvalue then
local name, value
local up = 1
repeat
name, value = upvalue(func, up)
if name then
self[#self+1] = name
matched, errmsg = self:match(value, select(2, upvalue(other, up)))
self[#self] = nil
if not matched then
self[func], self[other] = nil, nil
break
end
up = up + 1
end
until not name
end
local environment = self.environment
if matched and environment then
self[#self+1] = envkey
matched, errmsg = self:match(environment(func), environment(other))
self[#self] = nil
end
else
errmsg = self:error "bytecodes not matched"
end
return matched, errmsg
end
function match(self, value, other)
self[0] = self[0] or other
self[1] = self[1] or value
local matched, errmsg = false
local kind = type(value)
local matcher = self[kind]
if matcher then
local valuematch = self[value]
local othermatch = self[other]
matched = (valuematch == other and othermatch == value)
if not matched then
if valuematch == nil and othermatch == nil then
if value == other then
matched = true
elseif kind == type(other) then
matched, errmsg = matcher(self, value, other)
matcher = self.metatable
if matched and matcher then
self[#self+1] = metakey
matched, errmsg = matcher(self, getmetatable(value), getmetatable(other))
self[#self] = nil
end
else
errmsg = self:error "not matched"
end
else
errmsg = self:error "wrong match"
end
end
elseif value == other then
matched = true
else
errmsg = self:error "not matched"
end
return matched, errmsg
end
_M["table"] = matchtable
_M["function"] = matchfunction
_M["metatable"] = match