Skip to content

Instantly share code, notes, and snippets.

@xiaobin83
Last active April 13, 2018 10:58
Show Gist options
  • Save xiaobin83/5f07deffe9b9f9071c2a9a79512ce58e to your computer and use it in GitHub Desktop.
Save xiaobin83/5f07deffe9b9f9071c2a9a79512ce58e to your computer and use it in GitHub Desktop.
evaluate mongo-like expression
local CondExpr = {}
local _M = {}
function CondExpr.New(expr)
local p = {
expr = expr,
}
return setmetatable(p, { __index = _M })
end
local _strsub = string.sub
local errShouldBeOnlyCondExprOrEqExpr = "should be only cond expr or eq expr"
local errEmptyCondExpr = "empty cond expr"
local errRequireTwoOperand = 'require two operand'
local errNotCompiled = 'not compiled'
local errInvalidOperand = 'invalid operand'
local _buildObject
local _buildCondExpr
local _buildEqExpr
local _buildSubItemsExpr
local _buildBinaryExpr
local _buildUnaryExpr
local _buildOperand
local binaryOp = {
['$gt'] = {
Value = function(self, h)
return self.opr1:Value(h) > self.opr2:Value(h)
end
},
['$lt'] = {
Value = function(self, h)
return self.opr1:Value(h) < self.opr2:Value(h)
end
},
['$ge'] = {
Value = function(self, h)
return self.opr1:Value(h) >= self.opr2:Value(h)
end
},
['$le'] = {
Value = function(self, h)
return self.opr1:Value(h) <= self.opr2:Value(h)
end
},
['$eq'] = {
Value = function(self, h)
return self.opr1:Value(h) == self.opr2:Value(h)
end
},
['$add'] = {
Value = function(self, h)
return self.opr1:Value(h) + self.opr2:Value(h)
end
},
['$sub'] = {
Value = function(self, h)
return self.opr1:Value(h) - self.opr2:Value(h)
end
}
}
local _HashedValueMeta = {
Value = function(self, h)
return h(self.hash)
end
}
local _HashedValue = function(hash)
return setmetatable({hash = hash}, {__index = _HashedValueMeta})
end
local _ValueMeta = {
Value = function(self)
return self.value
end
}
local _Value = function(value)
return setmetatable({value = value}, {__index = _ValueMeta})
end
_buildOperand = function(self, opr)
local typeOpr = type(opr)
if typeOpr == 'string'
and _strsub(opr, 1, 1) == '#' then
return _HashedValue(opr)
elseif typeOpr == 'table' then
return _buildObject(self, opr)
else
return _Value(opr)
end
end
_buildBinaryExpr = function(self, exprObject, meta)
if exprObject then
if #exprObject ~= 2 then
return nil, errRequireTwoOperand
end
local opr1 = _buildOperand(self, exprObject[1])
local opr2 = _buildOperand(self, exprObject[2])
if opr1 and opr2 then
return setmetatable(
{opr1 = opr1, opr2 = opr2},
{ __index = meta}
)
else
return nil, errInvalidOperand
end
end
end
-- $and
local _SubItemsBoolValueMeta = {}
function _SubItemsBoolValueMeta:Value(h)
local items = assert(self.subItems)
for _, item in pairs(items) do
if not item:Value(h) then
return false
end
end
return true
end
-- $or
local _SubItemsAnyBoolValueMeta = {}
function _SubItemsAnyBoolValueMeta:Value(h)
for index, item in pairs(self.subItems) do
if item:Value(h) then
return index
end
end
end
local _SubItemsAllValueMeta = {}
function _SubItemsAllValueMeta:Value(h)
local values = {}
for _, item in pairs(self.subItems) do
values[#values + 1] = item:Value(h)
end
return table.unpack(values)
end
local _SubItemsSumValueMeta = {}
function _SubItemsSumValueMeta:Value(h)
local totalValue = 0
for _, item in pairs(self.subItems) do
totalValue = totalValue + item:Value(h)
end
return totalValue
end
local subItemsOp = {
['$and'] = _SubItemsBoolValueMeta,
['$or'] = _SubItemsAnyBoolValueMeta,
['$all'] = _SubItemsAllValueMeta,
['$sum'] = _SubItemsSumValueMeta,
}
_buildSubItemsExpr = function(self, exprObject, meta)
local items = {}
if exprObject then
for _, expr in ipairs(exprObject) do
local item, err = _buildObject(self, expr)
if err then
return nil, err
end
items[#items + 1] = item
end
return setmetatable({subItems = items}, {__index = meta})
end
end
local unaryOp = {
['$not'] = {
Value = function(self, h)
return not self.opr:Value(h)
end
},
['$minus'] = {
Value = function(self, h)
return -self.opr:Value(h)
end
},
['$value'] = {
Value = function(self, h)
return self.opr:Value(h)
end
}
}
_buildUnaryExpr = function(self, exprObject, meta)
if exprObject then
local opr = _buildOperand(self, exprObject)
if opr then
return setmetatable( { opr = opr}, { __index = meta } )
end
return nil, errInvalidOperand
end
end
_buildCondExpr = function(self, exprObject)
for op, meta in pairs(subItemsOp) do
local item, err = _buildSubItemsExpr(self, exprObject[op], meta)
if err then return nil, err end
if item then return item end
end
for op, meta in pairs(binaryOp) do
local item, err = _buildBinaryExpr(self, exprObject[op], meta)
if err then return nil, err end
if item then return item end
end
for op, meta in pairs(unaryOp) do
local item, err = _buildUnaryExpr(self, exprObject[op], meta)
if err then return nil, err end
if item then return item end
end
return nil, errEmptyCondExpr
end
_buildEqExpr = function(self, exprObject)
local items = {}
for opr1, opr2 in pairs(exprObject) do
local item, err = _buildBinaryExpr(self, {opr1, opr2}, binaryOp['$eq'])
if err then
return nil, err
end
items[#items + 1] = item
end
return setmetatable({subItems = items}, {__index = _SubItemsBoolValueMeta})
end
_buildObject = function(self, exprObject)
local eqExpr
local condExpr
for key, obj in pairs(exprObject) do
if type(key) == 'string' then
if _strsub(key, 1, 1) == '$' then
condExpr = true
if eqExpr then
return nil, errShouldBeOnlyCondExprOrEqExpr
end
else
eqExpr = true
if condExpr then
return nil, errShouldBeOnlyCondExprOrEqExpr
end
end
end
end
if condExpr then
return _buildCondExpr(self, exprObject)
else
return _buildEqExpr(self, exprObject)
end
end
function _M:Compile()
if not self.compiledExpr then
local item, err = _buildObject(self, self.expr)
if err then
return false, err
end
self.compiledExpr = item
end
return true
end
function _M:Value(hashedVarFunc)
self:Compile()
if self.compiledExpr then
local h = function(hash)
if hashedVarFunc then
return hashedVarFunc(hash)
end
return hash
end
return self.compiledExpr:Value(h)
end
return false, errNotCompiled
end
return CondExpr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment