簡體   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