解析右结合运算符(指数)

Parsing right-associative operator (exponents)

我一直在为自己的语言编写 lexer/parser/interpreter,到目前为止一切正常。我一直在关注 Ruslan Spivak's blog (Github link 中每篇文章的示例。

我想扩展我的语言语法,使其超越文章中所写的内容,以包含更多运算符,例如比较运算符(<>= 等)以及指数运算符(**或我的语言 ^)。我有这个语法:

expression        : exponent ((ADD | SUB) exponent)*
exponent          : term ((POWER) term)*
# this one is right-associative (powers **)

term              : comparison ((MUL | DIV) comparison)*
comparison        : factor ((EQUAl   | L_EQUAL | LESS
                             N_EQUAL | G_EQUAL | GREATER) factor)*
# these are all binary operations



factor            : NUM | STR        | variable
                        | ADD factor | SUB factor
                        | LPAREN expr RPAREN
# different types of 'base' types like integers
# also contains parenthesised expressions which are evalutaed first

在解析令牌方面,我使用的方法与Ruslan 的博客中使用的方法相同。这是一个将解析 exponent 行的代码,它处理加法和减法,尽管它的名称如此,因为语法表明表达式被解析为 exponent_expr (+ / -) exponent_expr

def exponent(self):
    node = self.term()
    while self.current_token.type in (ADD, SUB):
        token = self.current_token

        if token.type == ADD:
            self.consume_token(ADD)
        elif token.type == SUB:
            self.consume_token(SUB)

        node = BinaryOperation(left_node=node,
                               operator=token,
                               right_node=self.term())

    return node

现在这可以很好地解析左结合标记(因为标记流自然地从左到右),但我一直在研究如何解析右结合指数。看这个预期in/out,供参考:

>>> 2 ** 3 ** 2
# should be parsed as...
>>> 2 ** (3 ** 2)
# which is...
>>> 2 ** 9
# which returns...
512

# Mine, at the moment, parses it as...
>>> (2 ** 3) ** 2
# which is...
>>> 8 ** 2
# which returns...
64

为了解决这个问题,我尝试切换 BinaryOperation() 构造函数的左右节点,使当前节点在右,新节点在左,但这只会使 2**5 解析为 5**2 这给了我 25 而不是预期的 32.

有什么我可以尝试的方法吗?

您的 exponent 函数实际解析 expression 的事实应该是一个危险信号。事实上,您需要的是一个解析表达式的 expression 函数和一个解析求幂的 exponent 函数。

您还混淆了求幂和乘法(以及其他运算)的优先级,因为 2 * x ** 4 并不意味着 (2 * x) ** 4(即 16x⁴),而是2 * (x ** 4)。出于同样的原因,x * 3 < 17 并不意味着 x * (3 < 17),这是您的语法将如何解析它。

通常算术的优先级是这样的:

 comparison     <, <=, ==, ... ( lowest precedence)
 additive       +, -
 multiplicative *, /, %
 unary          +, -
 exponentiation **
 atoms          numbers, variables, parenthesized expressions, etc.

(如果你有像函数调用这样的后缀运算符,它们会介于求幂和原子之间。)

以这种形式修改语法后,指数解析器将如下所示:

def exponent(self):
    node = self.term()
    while self.current_token.type is POWER:
        self.consume_token(ADD)
        node = BinaryOperation(left_node=node,
                               operator=token,
                               right_node=self.exponent())
    return node

最后的递归调用产生了正确的结合性。在这种情况下,递归是可以接受的,因为左操作数和运算符已经被消耗掉了。因此递归调用不会产生无限循环。