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")