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'
我有三个问题需要帮助。
- 对于
var[X:Y] = Z
形式的多位访问,我该如何强制执行:
一种。 X > Y
b. Z < 2^{X - Y + 1}
我假设这不能由语法本身强制执行(例如,对于形式 var[X] = Y
的单位访问,我可以通过语法强制执行 Y
将是 0
或 1
,如果 Y != 0/1
). ,这将导致 expression.parseString()
失败并出现异常
- 最重要的是:为什么它总是打印
SUCCESS
?我做错了什么?
对于输入 var[3] = 1 AND var[1] = 0
它应该是 print FAIL
(你可以在我的示例中看到我将 var
硬编码为 0xf
,所以 var[3] = 1
是 True
但 var[1] = 0
是 False
)。
- 这让我想到了第三个问题:
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"
中强制执行有效值
您的第二条验证规则不仅涉及 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):
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
(这需要扩展以支持多位,但总体思路是相同的。)
我正在尝试解析和评估作为文件输入提供给我的表达式,其形式为:
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'
我有三个问题需要帮助。
- 对于
var[X:Y] = Z
形式的多位访问,我该如何强制执行:
一种。X > Y
b.Z < 2^{X - Y + 1}
我假设这不能由语法本身强制执行(例如,对于形式var[X] = Y
的单位访问,我可以通过语法强制执行Y
将是0
或1
,如果Y != 0/1
). ,这将导致 - 最重要的是:为什么它总是打印
SUCCESS
?我做错了什么?
对于输入var[3] = 1 AND var[1] = 0
它应该是 printFAIL
(你可以在我的示例中看到我将var
硬编码为0xf
,所以var[3] = 1
是True
但var[1] = 0
是False
)。 - 这让我想到了第三个问题:
var
不是BoolEqual
的 class 成员,也不是全局的...有没有办法以某种方式将它发送到BoolEqual
的__init__
函数?
expression.parseString()
失败并出现异常
如何将变量转换为 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"
中强制执行有效值您的第二条验证规则不仅涉及 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): 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
(这需要扩展以支持多位,但总体思路是相同的。)