【问题标题】:Basic questions regarding Shunting-yard关于调车场的基本问题
【发布时间】:2025-12-28 02:05:13
【问题描述】:

我已经完成了Shunting-yard算法的实现,但是有一些问题:

  1. 此算法如何验证输入是否合法,换句话说,它如何检测 a++b 是否合法(确定不合法)

  2. 我应该做的第二步是什么?调车场将1+2 转换为1 2 +


关于 1 的更新:

经过一些尝试,我认为确实如此,例如,取 a++b 它将是 a+b+ 然后当我评估它时,我会取一个 then + 但由于我手头只有一个变量,这是一个错误。

对于无效的表达式总是这样吗?

【问题讨论】:

  • 1.它没有;您需要自己添加,2. 如果您的目标是评估表达式,下一步就是评估它。
  • @molbdnilo 经过一番尝试,我认为确实如此,您确定我应该手动检查吗?例如取 a++b 它将是 a+b+ 然后当我评估它时我会取一个 then + 但由于我手头只有一个变量这是一个错误
  • 但是我不确定它是否会捕获所有这样的案例,请您给我一个无法捕获的案例示例
  • @danel,你搞错了……请看下面的答案
  • @daniel 如果您在评估期间检测到错误,您确实添加了该错误并且解析器没有验证它。

标签: c++ algorithm shunting-yard


【解决方案1】:

1。语法错误

这取决于您如何精确地实现该算法,但在通常通过互联网搜索找到的版本中,无法保证调车场算法会正确拒绝不合语法的表达式。许多不正确的表达式会产生不正确的后缀字符串(如您所见),甚至是正确的后缀字符串。特别是,如果您有一元运算符,算法(如通常所示)无法真正区分前缀使用(运算符在操作数之前)或后缀使用(运算符在操作数之后)。

如果您的目标语言具有可用作前缀或后缀运算符的运算符,具有不同的语义(例如 C 系列的 ++-- 运算符),这将是一个严重的问题。由于算法没有区分这两种情况,所以语义差异丢失了。

运算符有一个类似的、更常见的问题,它既可以用作二进制中缀运算符,也可以用作前缀运算符,例如- 运算符。除非区分这两种用途,否则后缀输出将无法解释,因为当达到- 时,评估器无法知道它适用于一个操作数还是两个操作数。 (此外,一元减号运算符可能会以不正确的优先级进行处理,因为所需的一元减号优先级高于乘法和除法。但是,对于大多数算术表达式,使用不正确的优先级不会改变数字结果的值,因为-(x * y)(-x) * y 具有完全相同的值。如果你实现一个模运算符,错误的结果会很明显。)

Shunting Yard 算法将检测不平衡的括号,因为不平衡的括号会导致解析堆栈溢出或在解析结束时具有过多的值。

使用一个非常小的状态机来扩充 Shutting Yard 算法相对容易,该状态机足以对具有多个句法意义的运算符的不同明确使用进行分类;该状态机也足以检测上面提到的其他语法错误:运算符放置不正确或完全丢失。

因为在实际使用中需要正确区分一元和二元否定;前缀和后缀运算符的不同含义;以及括号的不同使用(分组与函数调用),使用 Shunting Yard 的生产解析器将包括一些额外的句法机制,这些机制也将检测句法错误。这种算法的一个例子可以在in this answer找到。


2。 RPN 作为中间步骤

绝对没有必要使用RPN作为中间结果;调车场算法可用于

  • 直接计算算术表达式(如果表达式不包含条件或循环结构),

  • 为堆栈机器编译器输出可执行代码(或者,更加努力,为更真实的机器输出三地址代码),或更一般地

  • 生成表示解析表达式的语法树,可用于上述任何目的和其他语义分析任务。

要生成语法树,您需要将操作数推送到解析器堆栈,而不是直接将它们输出到输出流。此外,当您将运算符压入堆栈时,实际上是压入了表示该运算符应用程序的语法节点:对于二元运算符,它与顶部的两个堆栈槽结合。 (对于一元运算符,具有顶部堆栈槽。)如果您想使用 Shutting Yard 作为直接评估器,您可以使用相同的策略,但将运算符推入堆栈会导致该运算符及其操作数的评估,在同样的方式。

RPN 中间表示实际上没有提供任何价值。我不知道为什么它如此受欢迎。

【讨论】:

    【解决方案2】:

    让我们看看我是否可以为你分解它。调车码算法通过破坏中缀符号来执行以下操作之一:

    1. 要么产生一个后缀符号字符串(也称为逆波兰符号)
    2. 或抽象语法树。

    在你的情况下,它是后缀表示法。

    [注意:我希望你知道后缀符号,如果没有,请阅读this。]

    现在,后缀表示法使计算数学表达式变得非常容易。我将向您展示如何评估后缀符号:

    In a simple description:
        (1) Infix: A + B
            Postfix: A B +
        (2) Infix: A + B * C
            Postfix: A B C * +
    
    Let's observe the part A+B*C i.e. A+(B*C)
    
    If we represent it in Postfix, it would be like: A B C * +(by applying shunting-yard)
    
    Now, the algorithm to calculate it
        (1) Take a stack
        (2) When we see a number, we push it to stack
        (3) When we see a operator, we pop two numbers out of stack and calculate them with help of operator and push the result into stack again
        (4) We do it till the end
        (5) At last, only a number would be left in stack, that is our answer.
    
    Let's visualise it:
        (1) [A]
        (2) [A, B]
        (3) [A, B, C]
        (4) [A, R1] where R1 = B*C
        (5) [R2] where R2 = A+R1
    

    我希望,你已经明白,调车场将帮助你将中缀转换为后缀,然后,你可以轻松地评估后缀表示法。

    现在,问题是如何检测a++b 错误

    现在,观察 a、+、+、b 标记会发生什么(正如您在评论中所说:a++b 被标记化为 a、+、+、b 标记):

    我从*中获取了伪代码(懒惰,不想自己写):

    else if the token is an operator then:
            while ((there is a operator at the top of the operator stack)
                  and ((the operator at the top of the operator stack has greater precedence)
                   or (the operator at the top of the operator stack has equal precedence and the token is left associative))
                  and (the operator at the top of the operator stack is not a left parenthesis)):
                pop operators from the operator stack onto the output queue.
            push it onto the operator stack.
    

    据此:a、+、+、b 在输出队列中将采用以下形式:

    a, b, +, +
    

    a, b, +, + 完全是错误的,因为根据后缀评估规则会发生以下情况:

    1. [a] // output queue is now [b, +, +]
    2. [a, b] // output queue is now [+, +]
    3. [r1] // output queue is now [+]
    4. error // cause: notice there's still one '+' operator left in queue
    // but, only one number 'r1' left in the 'stack'
    // so, error
    

    我希望你现在清楚了......

    【讨论】:

    • 非常感谢,但我的第一个问题仍然没有得到答案,它会始终检测到错误吗?
    • "当我们看到一个运算符时,我们从堆栈中弹出两个数字,并在运算符的帮助下计算它们,然后再次将结果压入堆栈"如果没有两个数字怎么办?这是一个错误,但在给出无效表达式时总是会出现这种情况吗?
    • @daniel,当然是的,如果它是二进制运算符并且堆栈中还剩下一个数字,那显然是一个错误
    • 好的,当它没有检测到错误时,你能给我一个例子吗?
    • 好吧,当你正在实现分流场时,你必须执行错误检测,当你检测令牌时,如果你得到任何令牌错误,然后停止解析并抛出错误