为我愚蠢:什么是解析?

时间:2014-09-14 17:37:17

标签: java parsing scheme

昨天我询问语法,今天用Java编写,我正在学习如何使用我完成的词法分析器中的标记来实现解析语法的算法。

对于这个问题,我需要一个人检查我的理解。

我们假设给出了Scheme语法:

exp -> ( rest
     | #f
     | #t
     | ' exp
     | integer_constant
     | string_constant
     | identifier

rest -> )
     | exp+ [ . exp ] )

以下伪代码是否正确?我研究了递归下降解析器,并且需要通过创建解析树的节点来为解释器创建一个解析树。

 Node parseExp() {
      check to see if the token is left parenthesis
           if true, return a node for Cons (which is a non-terminating node in Scheme 
           parse tree) and call parseRest()
      else check to see if the token is #f
           if true, return a node for Boolean with stored value #f
      else check to see if the token is #t
           if true, return a node for Boolean with stored value #t
      else check to see if the token is quote
           if true, return a node for Quote and recursively call parseExp()
      else check to see if the token is integer_constant
           if true, return a node for Integer with stored value int
      else check to see if the token is string_constant
           if true, return a node for String with stored string value
      else check to see if the token is identifier
           if true, return a node for identifier with stored string value
      else
           print error message saying a Syntax error occured
           return null
 }

 Node parseRest() {
      check to see if the token is right parenthesis
           if true, return a node for Nil (which is a terminating () node in scheme 
           parse tree)
      else  // I am having difficulty trying to put this into an algorithm here
           call parseExp() for the first expression
           while (token does not equal right parenthesis) {
                getNextToken()
                if (token equals right parenthesis)
                     return a node for right parenthesis
                else if (token equals dot)
                     return a node for dot
                     getNextToken()
                     if (token equals right parenthesis)
                          print error message saying a Syntax error occurred
                          return null
                     else
                        call parseExp()
                else
                     parseExp() 
           }    
 }

如果我对此有错误的想法,请纠正我。据说parseRest()需要一个先行令牌才能做出决定,是否可以解释并可能是伪代码示例?

谢谢!

1 个答案:

答案 0 :(得分:1)

您已走上正轨,但存在一些问题:

check to see if the token is left parenthesis
     if true, return a node for Cons (which is a non-terminating node in Scheme 
     parse tree) and call parseRest()

这有点模棱两可,因为你没有提到你打算用parseRest()的结果做什么,但我想你想把它存储在Cons节点中。问题是Cons节点应该有两个子节点(如果列表中的列表是列表的头部及其尾部 - 如果不清楚,则可能需要查看规则(尽管是Scheme语言),但是parseRest只给你一个节点,所以它不起作用。因此,当我们看到(时,让我们退一步思考一下我们想要的东西:

(是一对的开头(即点对或非空列表),或者是空列表()。在第一种情况下,我们需要Cons节点,但在第二种情况下,我们希望Nil节点作为空列表不是cons单元。所以我们有两种可能性,在我们查看列表的其余部分之前,我们不知道选择哪一个。因此,不应该在这里做出决定,而是在parseRest函数中做出决定。所以我们将代码更改为:

check to see if the token is left parenthesis
     if true, return the result of parseRest()

现在让我们看一下parseRest:

在这里,您有时会返回点和圆括号的节点,但这些节点根本不应该是AST中的节点 - 它们是令牌。另一个问题是,当你以递归方式调用parseRest时,你再也不清楚你想对结果做什么。有人可能认为你想要返回结果,但是你的while循环将毫无意义,因为你每次都在第一次迭代中返回它。事实上,即使在非递归情况下,这也是一个问题:例如,您返回一个点节点,然后继续解析它后面的表达式。但是在返回之后函数退出,因此返回之后的任何内容都将被忽略。所以这不起作用。

在我们讨论如何使其发挥作用之前,让我们首先更清楚地了解生成的AST应该是什么样子:

  • For"()"我们想要一个Nil节点。这适用于您当前的代码。
  • For"(x)"我们想要Cons(Ident("x"), Nil)
  • For"(x.y)"我们想要Cons(Ident("x"), Ident("y"))
  • For"(x y)"我们想要Cons(Ident("x"), Cons (Ident("y"), Nil))
  • For"(x y.z)"我们想要Cons(Ident("x"), Cons (Ident("y"), Ident("z")))

我希望现在模式清晰(否则你可能想要查看Scheme语言)。那么我们如何获得那种AST?

好吧,如果我们看到),我们会返回Nil。再次,这已经适用于您的代码。否则我们解析一个表达式(如果这里没有有效的表达式,我们就会出错)。那么之后会发生什么?好吧,如果我们找到一个表达式,那个表达式就是Cons单元格的第一个元素。所以我们想要返回Cons(theExpression, ...)。但是...部分是什么?那取决于下一个标记是否是一个点。如果它是一个点,我们有一个虚线表达式,因此需要在点后面有一个表达式,我们想要返回Cons(theExpressionBeforeTheDot, theExpressionAfterTheDot)。如果没有点,则表示我们在列表中,其后面是它的尾部。所以我们想要返回Cons(theExpression, parseRest())

  据说parseRest()需要一个先行令牌才能做出决定,可以解释一下这可能是一个伪代码示例吗?

Lookahead意味着您必须查看下一个令牌而不实际从流中删除它。就您的伪代码而言,这意味着您想要知道在致电nextToken()时将返回哪个令牌而不实际更改下一次nextToken()调用将返回的内容。所以你有另一个内置函数,如peekNext(),它返回下一个标记,而不实际推进标记流中的迭代器。

你在parseRest中需要这个的原因是点:当你检查下一个标记是否是一个点并且事实证明它不是,那么你不希望令牌实际上是不见了。也就是说,你将调用parseExpression,然后parseExpression将调用nextToken,对吧?当发生这种情况时,你希望它返回当前表达式之后的令牌 - 你不想跳过该令牌,因为你必须检查它是否是一个点。因此,在检查点时,您需要调用peekToken而不是nextToken(尽管如此,您仍然需要删除令牌。)