Python的pyparsing:实现解析逻辑AND表达式的语法

Python's pyparsing: Implementing grammar to parse logical AND expression

我正在尝试解析和评估作为文件输入提供给我的表达式,其形式为:

var[3] = 0 and var[2] = 1
var[0] = 1 and var[2] = 0 and var[3] = 1
...

(实际上我也允许"multibit access"(即var[X:Y])但我们暂时忽略它...)
其中var为整数,[]表示位访问。
例如,对于 var = 0x9,上面的第一个表达式应计算为 False,第二个应计算为 True,因为 0x9 = b1001.
and= 是我唯一允许的二元运算符,对于 = 运算符,左边的 ope运行d 总是 var[X] 而右边的 ope运行d 始终是一个数字。
我试着四处看看,发现这可以用 Python 的 pyparsing 来实现,但是我 运行 在尝试实现它时遇到了一些困难。
到目前为止,这是我尝试过的,大致基于 this example (which is one of many examples provided here):

#!/usr/bin/env python
from pyparsing import Word, alphas, nums, infixNotation, opAssoc

class BoolAnd():
    def __init__(self, pattern):
        self.args = pattern[0][0::2]

    def __bool__(self):
        return all(bool(a) for a in self.args)

    __nonzero__ = __bool__


class BoolEqual():
    def __init__(self, pattern):
        self.bit_offset = int(pattern[0][1])
        self.value = int(pattern[0][-1])

    def __bool__(self):
        return True if (0xf >> self.bit_offset) & 0x1 == self.value else False # for now, let's assume var == 0xf

    __nonzero__ = __bool__




variable_name   = 'var'
bit_access      = variable_name + '[' + Word(nums) + ']'
multibit_access = variable_name + '[' + Word(nums) + ':' + Word(nums) + ']'
value = Word(nums)

operand = bit_access | multibit_access | value

expression = infixNotation(operand,
    [
    ('=',   2, opAssoc.LEFT,  BoolEqual),
    ('AND', 2, opAssoc.LEFT,  BoolAnd),
    ])


p = expression.parseString('var[3] = 1 AND var[1] = 0', True)

print 'SUCCESS' if bool(p) else 'FAIL'

我有三个问题需要帮助。

  1. 对于 var[X:Y] = Z 形式的多位访问,我该如何强制执行:
    一种。 X > Y
    b. Z < 2^{X - Y + 1}
    我假设这不能由语法本身强制执行(例如,对于形式 var[X] = Y 的单位访问,我可以通过语法强制执行 Y 将是 01,如果 Y != 0/1).
  2. ,这将导致 expression.parseString() 失败并出现异常
  3. 最重要的是:为什么它总是打印SUCCESS?我做错了什么?
    对于输入 var[3] = 1 AND var[1] = 0 它应该是 print FAIL (你可以在我的示例中看到我将 var 硬编码为 0xf,所以 var[3] = 1Truevar[1] = 0False)。
  4. 这让我想到了第三个问题:var 不是 BoolEqual 的 class 成员,也不是全局的...有没有办法以某种方式将它发送到 BoolEqual__init__函数?

如何将变量转换为 1 和 0 的列表并使用 eval 计算布尔表达式(稍作修改,将 = 更改为 ==):

def parse(lines, v):
    var = map(int,list(bin(v)[2:]))
    result = []

    for l in lines:
        l = l.replace('=','==')
        result.append(eval(l))

    return result

inp = \
"""
var[3] = 0 and var[2] = 1
var[0] = 1 and var[2] = 0 and var[3] = 1
"""

lines = inp.split('\n')[1:-1]
v = 0x09

print parse(lines, v)

输出:

[False, True]

请注意,只有在您信任输入的情况下才应使用 eval

在开始解决问题之前,我建议对您的语法进行一些小的修改,主要是包含结果名称。添加这些名称将使您生成的代码更加清晰和健壮。我还使用了一些在最近的 pyparsing 版本中添加的表达式,在 pyparsing_common 命名空间 class:

from pyparsing import pyparsing_common

variable_name   = pyparsing_common.identifier.copy()
integer = pyparsing_common.integer.copy()
bit_access      = variable_name('name') + '[' + integer('bit') + ']'
multibit_access = variable_name('name') + '[' + integer('start_bit') + ':' + integer('end_bit') + ']'

第 1a 部分:在 "var[X:Y]"

中强制执行有效值

这种工作最好使用解析操作和条件来完成。解析操作是解析时回调,您可以将其附加到 pyparsing 表达式以修改、增强、过滤结果,或者在验证规则失败时引发异常。这些是使用以下方法附加的:

expr.addParseAction(parse_action_fn)

并且parse_action_fn可以有以下任何签名:

def parse_action_fn(parse_string, parse_location, matched_tokens):
def parse_action_fn(parse_location, matched_tokens):
def parse_action_fn(matched_tokens):
def parse_action_fn():

(更多信息见 https://pythonhosted.org/pyparsing/pyparsing.ParserElement-class.html#addParseActio)n

解析操作可以 return None、return 新标记、修改给定标记或引发异常。

如果所有解析操作都是根据输入标记评估某些条件,您可以将其编写为一个简单的函数,returns True 或 False,如果 False 是 return编辑。在您的情况下,您的第一个验证规则可以实现为:

def validate_multibit(tokens):
    return tokens.end_bit > tokens.start_bit
multibit_access.addCondition(validate_multibit,
                            message="start bit must be less than end bit", 
                            fatal=True)

或者甚至只是作为一个 Python lambda 函数:

multibit_access.addCondition(lambda t: t.end_bit > t.start_bit, 
                            message="start bit must be less than end bit", 
                            fatal=True)

现在你可以试试这个:

multibit_access.parseString("var[3:0]")

你会得到这个异常:

pyparsing.ParseFatalException: start bit must be less than end bit (at char 0), (line:1, col:1)

第 1b 部分:在 "var[X:Y] = Z"

中强制执行有效值

您的第二条验证规则不仅涉及 v​​ar 位范围,还涉及与之比较的值。这将需要附加到完整 BoolEqual 的解析操作。我们可以将它放在 BoolEqual 的 __init__ 方法中,但我更喜欢尽可能将独立函数分开。由于我们将通过附加到 infixNotation 级别来添加验证,并且 infixNotation 仅接受解析操作,因此我们需要将您的第二个验证规则编写为引发异常的解析操作。 (我们还将使用最近才在 pyparsing 2.2.0 中发布的新功能,在 infixNotation 的级别附加多个解析操作。)

这是我们希望执行的验证:

  • 如果是单个位表达式,值必须是0或1
  • 如果多位表达式 var[X:Y],值必须 < 2**(Y-X+1)

    def validate_equality_args(tokens):
        tokens = tokens[0]
        z = tokens[-1]
        if 'bit' in tokens:
            if z not in (0,1):
                raise ParseFatalException("invalid equality value - must be 0 or 1")
        else:
            x = tokens.start_bit
            y = tokens.end_bit
            if not z < 2**(y - x + 1):
                raise ParseFatalException("invalid equality value")

And we attach this parse action to infixNotation using:

expression = infixNotation(operand,
    [
    ('=',   2, opAssoc.LEFT,  (validate_equality_args, BoolEqual)),
    ('AND', 2, opAssoc.LEFT,  BoolAnd),
    ])

Part 3: Supporting other var names and values than 0xf

To deal with vars of various names, you can add a class-level dict to BoolEqual:

class BoolEqual():
    var_names = {}

and set this ahead of time:

BoolEqual.var_names['var'] = 0xf

And then implement your __bool__ method as just:

return (self.var_names[self.var_name] >> self.bit_offset) & 0x1 == self.value

(这需要扩展以支持多位,但总体思路是相同的。)