【问题标题】:Dumb it down for me: What is parsing?对我来说哑巴:什么是解析?
【发布时间】:2014-09-15 03:37:27
【问题描述】:

昨天我问了语法,今天在 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() 需要一个前瞻令牌才能做出决定,这可以解释一下吗?可能是一个伪代码示例?

谢谢!

【问题讨论】:

  • 写一个正则表达式并通过string.matches(...)测试输入会更容易吗?
  • 当然可以,但是我正在为 Java 中的方案构建编译器。它需要配备错误检查。解析后的下一部分是构建解释器,这需要解析树等数据结构。
  • @Hannes Java 正则表达式不支持递归,即使他们支持,编写这样一个怪物正则表达式很可能不会比这更容易(而且绝对不会更易读或更易于维护)。通常不建议使用正则表达式解析上下文无关语言(在 Java 中甚至不可能)。当然,即使你有这样一个正则表达式,它也不会给你一个 AST,所以你所能做的就是检查有效的语法,这不是我们想要的。

标签: java parsing scheme


【解决方案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 循环将毫无意义,因为您每次都在第一次迭代中退出它。事实上,即使在非递归情况下,这也是一个问题:例如,您返回一个点节点,然后继续解析它之后的表达式。但是在 return 之后函数退出,所以 return 之后的任何内容都将被忽略。所以这行不通。

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

  • 对于“()”,我们需要一个 Nil 节点。这适用于您当前的代码。
  • 对于“(x)”,我们需要Cons(Ident("x"), Nil)
  • 对于“(x . y)”,我们需要Cons(Ident("x"), Ident("y"))
  • 对于“(x y)”,我们需要Cons(Ident("x"), Cons (Ident("y"), Nil))
  • 对于“(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(但当它是点时,您仍然需要删除令牌)。

【讨论】:

  • 哇,很有帮助的帖子!我将需要修改点在方案中的工作方式,但除此之外,这对我来说很清楚。谢谢!
  • 得回过头来说:这下真的很清楚了,我现在知道如何返回一个节点,就像返回新的Cons(汽车节点,cdr节点)!其中节点可以是进一步解析或终止符。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-25
  • 2012-12-23
  • 1970-01-01
  • 2012-04-07
  • 2021-02-06
  • 1970-01-01
相关资源
最近更新 更多