2017-03-29 165 views
5

我试图分析和评估表达式,从文件给我作为输入,形式为:Python的pyparsing:实现语法解析逻辑与表达

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,第二个表达式应该从0x9 = b1001开始评估为True
and=是我允许的唯一二元运算符,对于=运算符,左操作数始终为var[X],右操作数始终为数字。
我试着环顾了一下,发现这可能与使用Python的pyparsing可以实现,但我遇到了一些困难,努力实现它。
这是我到目前为止已经试过,在this example大致基于(这是提供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,并且这将导致expression.parseString()如果Y != 0/1)失败。
  2. 更重要的是:为什么总是打印SUCCESS?我究竟做错了什么?
    对于输入var[3] = 1 AND var[1] = 0应该是打印FAIL(你可以在我的例子,我硬编码var0xf看,所以var[3] = 1Truevar[1] = 0False)。
  3. 这将我带到我的第三个问题:var是不是BoolEqual的类成员也不是全球...有没有办法以某种方式将它发送到BoolEqual__init__函数?
+0

对#2的简单回答:p是一个非空的ParseResults,因此无论内容如何,​​它都将始终评估为True。试试'print('SUCCESS'if bool(p [0])else'FAIL')' – PaulMcG

回答

3

之前获得的解决问题,我建议一些小的改动你的语法,主要是列入结果的名字。添加这些名称将使您的结果代码更加干净和健壮。我还使用了一些在最近pyparsing版本中添加的表情,在pyparsing_common命名空间类:

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:“[:Y X] VAR”

这在执行有效值最好使用解析动作和条件来完成某种工作。分析操作是分析时回调,您可以附加到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.parseString("var[3:0]") 

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

现在你可以试试这个你会得到这个例外:

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
  • 如果多位表达变种[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), 
    ]) 

3部分:支持其他变种名称和值比0xf

应对各种名称的增值经销商,你可以添加类级别字典来BoolEqual:

class BoolEqual(): 
    var_names = {} 

,并设置这个时间提前:

BoolEqual.var_names['var'] = 0xf 

,然后实现你的__bool__方法,只是:

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

(这将需要进行扩展,以支持多位,但总的想法是一样的。)

+0

美丽。 正是我需要的,谢谢! –

0

如何变量转换为和0的1列表,并使用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