繁体   English   中英

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
...

(实际上我也允许“多位访问”(即var[X:Y] )但是现在让我们忽略它...)
其中var是整数, []表示位访问。
例如,对于var = 0x9 ,上面的第一个表达式应该被计算为False ,而第二个表达式应该被计算为True因为0x9 = b1001
and =是我允许的唯一二元运算符,对于=运算符,左操作数总是var[X] ,右操作数总是一个数字。
我试着环顾四周,发现这可以通过Python的pyparsing来实现,但是我在尝试实现它时遇到了一些困难。
这是我到目前为止所尝试的内容,大致基于此示例 (这是此处提供的众多示例之一):

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

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

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

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

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

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”中强制执行有效值

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

以下是我们希望执行的验证:

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

      def validate_equality_args(tokens):\n     tokens = tokens [0]\n     z =代币[-1]\n     如果令牌中有'位':\n         如果z不在(0,1)中:\n             引发ParseFatalException(“无效的相等值 - 必须为0或1”)\n     其他:\n         x = tokens.start_bit\n         y = tokens.end_bit\n         如果不是z <2 **(y  -  x + 1):\n             引发ParseFatalException(“无效的相等值”) 

我们使用以下命令将此解析操作附加到infixNotation

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

第3部分:支持除0xf之外的其他var名称和值

要处理各种名称的变量,可以向BoolEqual添加类级别的dict:

class BoolEqual():
    var_names = {}

并提前设定:

BoolEqual.var_names['var'] = 0xf

然后将__bool__方法实现为:

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

(这需要扩展到支持多位,但总体思路是一样的。)

如何将变量转换为1和0的列表,并使用eval来计算布尔表达式(通过一个小的修改,更改= into ==):

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM