【问题标题】:Handling extra operators in Shunting-yard在调车场处理额外的操作员
【发布时间】:2013-04-29 02:46:41
【问题描述】:

给定这样的输入:3+4+ 算法把它变成3 4 + +

我可以在执行后缀表达式时找到错误。 但是,是否有可能在转换过程中发现这一点?

(我读过的维基百科文章和互联网文章不处理这种情况)

谢谢

【问题讨论】:

  • 这个转换用什么算法?
  • 如果您使用3+4+ 作为转换为后缀的输入,您的代码应首先进行验证并返回invalid format
  • @Bill 验证中缀表达式与转换为后缀一样困难。特别是因为我有像 ^ 之类的单输入运算符! ~。就像有 2 个单独的解析器来处理表达式。

标签: parsing infix-notation postfix-notation shunting-yard


【解决方案1】:

除了括号不匹配之外,可以使用正则表达式验证有效表达式。 (不匹配的括号将被维基百科页面中指示的调车场算法捕获,所以我忽略了这些。)

正则表达式如下:

PRE* OP POST* (INF PRE* OP POST*)*

地点:

PRE  is a prefix operator or (
POST is a postfix operator or )
INF  is an infix operator
OP   is an operand (a literal or a variable name)

您链接的维基百科页面包括函数调用,但不包括数组下标;如果你想处理这些情况,那么:

PRE  is a prefix operator or (
POST is a postfix operator or ) or ]
INF  is an infix operator or ( or ,
OP   is an operand, including function names

上面要注意的一点是PREINF 在不相交的上下文中;也就是说,如果PRE 有效,则INF 无效,反之亦然。这意味着对前缀运算符和中缀运算符使用相同的符号是明确的。此外,PREPOST 处于不相交的上下文中,因此您可以对前缀和后缀运算符使用相同的符号。但是,后缀和中缀运算符不能使用相同的符号。例如,考虑 C/C++ 运算符:

-  prefix or infix
+  prefix or infix
-- prefix or postfix
++ prefix or postfix

我在上面隐含地使用了这个特性来允许(被用作表达式分组器(实际上是前缀)和函数调用(中缀,因为它位于函数名和参数列表之间;运算符是“打电话”。)

最常见的是将正则表达式实现为状态机,因为只有两种状态:

 +-----+            +-----+
 |State|-----OP---->|State|
 |  1  |<----INF----|  2  |
 |     |---+        |     |---+
 +-----+   |        +-----+   |
    ^     PRE          ^     POST
    |      |           |      |
    +------+           +------+

我们可以称状态 1“需要操作数”而状态 2“有操作数”。一个简单的实现就是将 wikipedia 中介绍的分流场算法分成两个循环,就像这样(如果你不喜欢 goto,它可以被消除,但它确实是呈现状态机的最清晰的方法) .

want_operand:
  read a token. If there are no more tokens, announce an error.
  if the token is an prefix operator or an '(':
    mark it as prefix and push it onto the operator stack
    goto want_operand
  if the token is an operand (identifier or variable):
    add it to the output queue
    goto have_operand
  if the token is anything else, announce an error and stop. (**)

have_operand:
  read a token
  if there are no more tokens:
    pop all operators off the stack, adding each one to the output queue.
    if a `(` is found on the stack, announce an error and stop.
  if the token is a postfix operator:
    mark it as postfix and add it to the output queue
    goto have_operand.
  if the token is a ')':
    while the top of the stack is not '(':
      pop an operator off the stack and add it to the output queue
    if the stack becomes empty, announce an error and stop.
    if the '(' is marked infix, add a "call" operator to the output queue (*)
    pop the '(' off the top of the stack
    goto have_operand
  if the token is a ',':
    while the top of the stack is not '(':
      pop an operator off the stack and add it to the output queue
    if the stack becomes empty, announce an error
    goto want_operand
  if the token is an infix operator:
    (see the wikipeda entry for precedence handling)
    (normally, all prefix operators are considered to have higher precedence
    than infix operators.)
    got to want_operand
  otherwise, token is an operand. Announce an error

(*) The procedure as described above does not deal gracefully with parameter lists;
    when the postfix expression is being evaluated and a "call" operator is found, it's
    not clear how many arguments need to be evaluated. It might be that function names
    are clearly identifiable, so that the evaluator can just attach arguments to the
    "call" until a function name is found. But a cleaner solution is for the "," handler
    to increment the argument count of the "call" operator (that is, the open
    parenthesis marked as "infix"). The ")" handler also needs to increment the
    argument count.

(**) The state machine as presented does not correctly handle function calls with 0
    arguments. This call will show up as a ")" read in the want_operand state and with
    a "call" operator on top of the stack. If the "call" operator is marked with an
    argument count, as above, then the argument count must be 0 when the ")" is read,
    and in this case, unlike the have_operand case, the argument count must not be
    incremented.

【讨论】:

  • 这么棒的帖子:)。正则表达式是一个有趣的想法。可以调用解析器的 not start,因为每个函数调用/变量/构造函数都可能包含单独的表达式。这使得任何状态机测试基本上都变成了 LR 表。作为初步的错误系统,我正在计算两个操作数 (+ * - / %) 并检查 if(#of2oper + 1 == #ofValue)。这是可能的,因为值是使用它们自己的解析器(函数调用/变量解析器/构造函数等)单独解析的。所以我将在这个上进行混合堆栈/递归:)。谢谢!
  • @mikbal:您可能可以对所有值使用运算符优先级语法。 Wikipedia 页面显示了如何进行函数调用,我对这个主题进行了一些扩展;这也应该适用于构造函数。运算符优先级确实是一种 LR 技术; LALR(1) 解析器被证明更强大,但许多语言也可以使用 op-prec/shunting-yard/Pratt 或其他变体进行解析。
  • 这个答案超出了问题中提到的具体错误。它提出了一种机制,用于通过状态机验证算法的输入是否是有效的中缀符号。
  • 感谢您提供如此美丽而清晰的解释。这应该链接到官方维基百科页面,以了解其详细信息和简单性。 7 年过去了,这仍然是信息量最大的帖子。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-24
  • 1970-01-01
相关资源
最近更新 更多