2017-03-29 40 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