在Python 2.7中解析布尔表达式中的复杂操作数

时间:2014-10-14 05:27:58

标签: parsing python-2.7 pyparsing boolean-expression

我正在尝试修改pyparsing中的示例代码来处理作为键值对的操作数,例如:

(Region:US and Region:EU) or (Region:Asia)

这是一个包含三个操作数的布尔表达式 - Region:US,Region:EU和Region:Asia。如果它们是简单的操作数,如x,y和z,我会很高兴。我不需要对它们进行任何特殊处理来分解键值对。我需要完整地处理操作数,就像它可能只是x一样,需要为它分配真值并评估完整的表达式。

我如何修改以下代码来处理这个问题:

#
# simpleBool.py
#
# Example of defining a boolean logic parser using
# the operatorGrammar helper method in pyparsing.
#
# In this example, parse actions associated with each
# operator expression will "compile" the expression
# into BoolXXX class instances, which can then
# later be evaluated for their boolean value.
#
# Copyright 2006, by Paul McGuire
# Updated 2013-Sep-14 - improved Python 2/3 cross-compatibility
#
from pyparsing import infixNotation, opAssoc, Keyword, Word, alphas

# define classes to be built at parse time, as each matching
# expression type is parsed
class BoolOperand(object):
    def __init__(self,t):
        self.label = t[0]
        self.value = eval(t[0])
    def __bool__(self):
        return self.value
    def __str__(self):
        return self.label
    __repr__ = __str__
    __nonzero__ = __bool__

class BoolBinOp(object):
    def __init__(self,t):
        self.args = t[0][0::2]
    def __str__(self):
        sep = " %s " % self.reprsymbol
        return "(" + sep.join(map(str,self.args)) + ")"
    def __bool__(self):
        return self.evalop(bool(a) for a in self.args)
    __nonzero__ = __bool__
    __repr__ = __str__

class BoolAnd(BoolBinOp):
    reprsymbol = '&'
    evalop = all

class BoolOr(BoolBinOp):
    reprsymbol = '|'
    evalop = any

class BoolNot(object):
    def __init__(self,t):
        self.arg = t[0][1]
    def __bool__(self):
        v = bool(self.arg)
        return not v
    def __str__(self):
        return "~" + str(self.arg)
    __repr__ = __str__
    __nonzero__ = __bool__

TRUE = Keyword("True")
FALSE = Keyword("False")
boolOperand = TRUE | FALSE | Word(alphas,max=1)
boolOperand.setParseAction(BoolOperand)

# define expression, based on expression operand and
# list of operations in precedence order
boolExpr = infixNotation( boolOperand,
    [
    ("not", 1, opAssoc.RIGHT, BoolNot),
    ("and", 2, opAssoc.LEFT,  BoolAnd),
    ("or",  2, opAssoc.LEFT,  BoolOr),
    ])


if __name__ == "__main__":
    p = True
    q = False
    r = True
    tests = [("p", True),
             ("q", False),
             ("p and q", False),
             ("p and not q", True),
             ("not not p", True),
             ("not(p and q)", True),
             ("q or not p and r", False),
             ("q or not p or not r", False),
             ("q or not (p and r)", False),
             ("p or q or r", True),
             ("p or q or r and False", True),
             ("(p or q or r) and False", False),
            ]

    print("p =", p)
    print("q =", q)
    print("r =", r)
    print()
    for t,expected in tests:
        res = boolExpr.parseString(t)[0]
        success = "PASS" if bool(res) == expected else "FAIL"
        print (t,'\n', res, '=', bool(res),'\n', success, '\n')

而不是p,q,r,我想使用“地区:美国”,“地区:欧盟”和“地区:亚洲”。有什么想法吗?

编辑:使用Paul McGuire的建议,我尝试编写以下代码,该代码打破了解析:

#
# simpleBool.py
#
# Example of defining a boolean logic parser using
# the operatorGrammar helper method in pyparsing.
#
# In this example, parse actions associated with each
# operator expression will "compile" the expression
# into BoolXXX class instances, which can then
# later be evaluated for their boolean value.
#
# Copyright 2006, by Paul McGuire
# Updated 2013-Sep-14 - improved Python 2/3 cross-compatibility
#
from pyparsing import infixNotation, opAssoc, Keyword, Word, alphas

# define classes to be built at parse time, as each matching
# expression type is parsed
class BoolOperand(object):
    def __init__(self,t):
        self.label = t[0]
        self.value = validValues[t[0]]
    def __bool__(self):
        return self.value
    def __str__(self):
        return self.label
    __repr__ = __str__
    __nonzero__ = __bool__

class BoolBinOp(object):
    def __init__(self,t):
        self.args = t[0][0::2]
    def __str__(self):
        sep = " %s " % self.reprsymbol
        return "(" + sep.join(map(str,self.args)) + ")"
    def __bool__(self):
        return self.evalop(bool(a) for a in self.args)
    __nonzero__ = __bool__
    __repr__ = __str__

class BoolAnd(BoolBinOp):
    reprsymbol = '&'
    evalop = all

class BoolOr(BoolBinOp):
    reprsymbol = '|'
    evalop = any

class BoolNot(object):
    def __init__(self,t):
        self.arg = t[0][1]
    def __bool__(self):
        v = bool(self.arg)
        return not v
    def __str__(self):
        return "~" + str(self.arg)
    __repr__ = __str__
    __nonzero__ = __bool__

TRUE = Keyword("True")
FALSE = Keyword("False")
boolOperand = TRUE | FALSE | Word(alphas+":",max=1)
boolOperand.setParseAction(BoolOperand)

# define expression, based on expression operand and
# list of operations in precedence order
boolExpr = infixNotation( boolOperand,
    [
    ("not", 1, opAssoc.RIGHT, BoolNot),
    ("and", 2, opAssoc.LEFT,  BoolAnd),
    ("or",  2, opAssoc.LEFT,  BoolOr),
    ])


if __name__ == "__main__":
    validValues = {
        "Region:US": False,
        "Region:EU": True,
        "Type:Global Assets>24": True
    }
    tests = [("Region:US", True),
             ("Region:EU", False),
             ("Region:US and Region:EU", False),
             ("Region:US and not Region:EU", True),
             ("not not Region:US", True),
             ("not(Region:US and Region:EU)", True),
             ("Region:EU or not Region:US and Type:Global Assets>24", False),
             ("Region:EU or not Region:US or not Type:Global Assets>24", False),
             ("Region:EU or not (Region:US and Type:Global Assets>24)", False),
             ("Region:US or Region:EU or Type:Global Assets>24", True),
             ("Region:US or Region:EU or Type:Global Assets>24 and False", True),
             ("(Region:US or Region:EU or Type:Global Assets>24) and False", False),
            ]

    print("Region:US =", validValues["Region:US"])
    print("Region:EU =", validValues["Region:EU"])
    print("Type:Global Assets>24 =", validValues["Type:Global Assets>24"])
    print()
    for t,expected in tests:
        res = boolExpr.parseString(t)[0]
        success = "PASS" if bool(res) == expected else "FAIL"
        print (t,'\n', res, '=', bool(res),'\n', success, '\n')

感谢Paul McGuire的帮助,以下是解决方案:

boolOperand = TRUE | FALSE | Combine(Word(alphas)+":"+quotedString) | Word(alphas+":<>")

这是我想要的解析。

1 个答案:

答案 0 :(得分:1)

进行此更改有两个部分:更改解析器,然后更改解析后行为以适应这些新值。

要解析不仅仅是简单单字符名称的操作数,请在解析器中更改此行:

boolOperand = TRUE | FALSE | Word(alphas,max=1)

最简单的(但不是最严格的是将其改为:

boolOperand = TRUE | FALSE | Word(alphas+":")

但是除了你的“Region:US”或“TimeZone:UTC”的有效值之外,这可能会接受,大概是无效的值,例如“XouEWRL:sdlkfj”,“:sldjf:ljsdf:sdljf”,甚至“:” ::::::”。如果要加强解析器,可以将密钥条目强制执行:

valid_key = oneOf("Region Country City State ZIP")
valid_value = Word(alphas+"_")
valid_kv = Combine(valid_key + ":" + valid_value)
boolOperand = TRUE | FALSE | valid_kv

那应该照顾解析器。

其次,您需要在解析完成后更改此条目的评估方式。在我的例子中,我强调解析部分,而不是评估部分,所以我把它留给简单地调用内置的eval()。在您的情况下,您可能需要为每个可接受的键值对初始化有效值的dict,然后更改BoolOperand中的代码以执行dict查找而不是调用eval。 (这样做的另一个好处是使用用户输入的数据调用eval(),这会产生各种安全问题。)