Pyparsing 无法将关键字传递给解析器
Pyparsing trouble passing keywords to the parser
我用谷歌搜索了一段时间,从 (感谢保罗先生)我知道了如何将标识符传递给解析器。这是我目前所拥有的:
from pyparsing import *
class Expressions:
ParserElement.enablePackrat()
arith_expr = Forward()
num = Word(nums) + Optional("." + OneOrMore(Word(nums)))
opmd = Word("*/", max=1)
opss = Word("+-", max=1)
ident = Word(alphas + "_", alphanums + "_")
fn_call = Group(Optional(delimitedList(arith_expr)))
arith_operand = fn_call | num | ident
arith_expr <<= infixNotation(arith_operand, [
('-', 1, opAssoc.RIGHT),
(opmd, 2, opAssoc.LEFT,),
(opss, 2, opAssoc.LEFT,)
])
def __init__(self, vars):
if isinstance(vars, list) and vars:
ids = []
for x in vars:
ids.append(x)
self.ident = MatchFirst(map(Keyword, ids))
def check(text):
try:
result = self.arith_expr.parseString(texto, True)
print(result)
except ParseException as exc:
print(exc)
然后,如果我转到 python 控制台并执行此操作:
vars = ['v1', 'v2', 'v3,1']
e = Expressions(vars)
e.check('10+v1+v2+v3,1-whatever')
它打印 whatever
作为正确的标记,尽管它没有在 vars
中定义。我该如何解决这个问题?
Expressions
class 中定义的 ident
变量不是占位符,因此当您在 __init__
方法中分配它时,您并没有改变整体解析器,只是 self.ident
的定义(它在 Expressions 实例上创建一个新属性,并且不会更改 class-level 标识)。
为什么不直接在 __init__
中定义整个解析器?然后你可以使用给定的变量名定义 ident
,你可以绕过所有 class-vs-instance 属性问题,以及 updating-part-of-a-parser-after-the-fact 问题。
这段代码应该做什么?
ids = []
for x in vars:
ids.append(x)
将值从一个列表复制到另一个列表的方法要简单得多,但您为什么还要进行复制?只需使用变量名称的输入列表定义 ident
(您可以将其命名为 vars
之外的名称,因为这与有用的内置方法冲突 - 也许将其称为 var_names
?)。
编辑:更多注释
您需要修复 fn_call。实际上,您将拥有无限递归,因为它只是 arith_exprs 的 comma-delimited 列表。由于您使用 fn_call 定义 arith_expr,因此存在 left-recursion。我认为您未完全从另一个示例中复制它,您拥有的表达式对函数的参数列表中的参数列表有效,但您缺少函数名称和封闭的括号。添加这些,递归问题就消失了。
您的一个变量是 'v_3,1'。这是一个 odd-looking 标识符,但幸运的是它不会与解析器中的任何其他位冲突。但是,如果您发送标识符“3.1”或“42”,事情就会变得非常混乱。您可能想要定义一个 valid_identifier 表达式,然后使用类似以下内容验证传入的变量名称:
valid_expression = Word(alphas + '_', alphanums + '_,')
if not all(valid_expression.matches(varname) for varname in varnames):
raise WhatWereYouThinkingException("invalid identifier specified")
我用谷歌搜索了一段时间,从
from pyparsing import *
class Expressions:
ParserElement.enablePackrat()
arith_expr = Forward()
num = Word(nums) + Optional("." + OneOrMore(Word(nums)))
opmd = Word("*/", max=1)
opss = Word("+-", max=1)
ident = Word(alphas + "_", alphanums + "_")
fn_call = Group(Optional(delimitedList(arith_expr)))
arith_operand = fn_call | num | ident
arith_expr <<= infixNotation(arith_operand, [
('-', 1, opAssoc.RIGHT),
(opmd, 2, opAssoc.LEFT,),
(opss, 2, opAssoc.LEFT,)
])
def __init__(self, vars):
if isinstance(vars, list) and vars:
ids = []
for x in vars:
ids.append(x)
self.ident = MatchFirst(map(Keyword, ids))
def check(text):
try:
result = self.arith_expr.parseString(texto, True)
print(result)
except ParseException as exc:
print(exc)
然后,如果我转到 python 控制台并执行此操作:
vars = ['v1', 'v2', 'v3,1']
e = Expressions(vars)
e.check('10+v1+v2+v3,1-whatever')
它打印 whatever
作为正确的标记,尽管它没有在 vars
中定义。我该如何解决这个问题?
Expressions
class 中定义的 ident
变量不是占位符,因此当您在 __init__
方法中分配它时,您并没有改变整体解析器,只是 self.ident
的定义(它在 Expressions 实例上创建一个新属性,并且不会更改 class-level 标识)。
为什么不直接在 __init__
中定义整个解析器?然后你可以使用给定的变量名定义 ident
,你可以绕过所有 class-vs-instance 属性问题,以及 updating-part-of-a-parser-after-the-fact 问题。
这段代码应该做什么?
ids = []
for x in vars:
ids.append(x)
将值从一个列表复制到另一个列表的方法要简单得多,但您为什么还要进行复制?只需使用变量名称的输入列表定义 ident
(您可以将其命名为 vars
之外的名称,因为这与有用的内置方法冲突 - 也许将其称为 var_names
?)。
编辑:更多注释
您需要修复 fn_call。实际上,您将拥有无限递归,因为它只是 arith_exprs 的 comma-delimited 列表。由于您使用 fn_call 定义 arith_expr,因此存在 left-recursion。我认为您未完全从另一个示例中复制它,您拥有的表达式对函数的参数列表中的参数列表有效,但您缺少函数名称和封闭的括号。添加这些,递归问题就消失了。
您的一个变量是 'v_3,1'。这是一个 odd-looking 标识符,但幸运的是它不会与解析器中的任何其他位冲突。但是,如果您发送标识符“3.1”或“42”,事情就会变得非常混乱。您可能想要定义一个 valid_identifier 表达式,然后使用类似以下内容验证传入的变量名称:
valid_expression = Word(alphas + '_', alphanums + '_,')
if not all(valid_expression.matches(varname) for varname in varnames):
raise WhatWereYouThinkingException("invalid identifier specified")