Scala解析器组合器:使用packratparsers获取stackoverflow

时间:2014-02-05 12:41:33

标签: scala parsing stack-overflow text-parsing parser-combinators

由于这是我的第一篇文章,我想借此机会说:这是一个多么伟大的网站!

无论如何,对于这个问题:

我有点像Scala新手,我正在尝试使用Scala中的解析器组合器来解决数据提取和解析问题,并且我得到了java.lang.StackOverflowError异常。

我的真实世界示例太大而无法包含,所以我重复使用another SO question中的代码同样的问题。虽然代码略有修改。我尝试使用PackratParsers解决问题,但没有成功。

import scala.util.parsing.combinator.syntactical.StandardTokenParsers
import scala.util.parsing.combinator.PackratParsers

object ArithmeticParser1 extends StandardTokenParsers with PackratParsers {
  lexical.delimiters ++= List("(", ")", "+", "-", "*", "/")

  lazy val reduceList: Int ~ List[String ~ Int] => Int = {
    case i ~ ps => (i /: ps)(reduce)
  }

  def reduce(x: Int, r: String ~ Int) = (r: @unchecked) match {
    case "+" ~ y => x + y
    case "-" ~ y => x - y
    case "*" ~ y => x * y
    case "/" ~ y => x / y
  }

  lazy val expr  : PackratParser[Int] = term ~ rep ("+" ~ term | "-" ~ term) ^^ reduceList
  lazy val term  : PackratParser[Int] = factor ~ rep ("*" ~ factor | "/" ~ factor) ^^ reduceList
  lazy val factor: PackratParser[Int] = "(" ~> expr <~ ")" | numericLit ^^ (_.toInt)

  def main(args: Array[String]) {
    val times = 500
    val s = "(" * times + "1 + 1" + ")" * times
    val tokens = new PackratReader(new lexical.Scanner(s))
    println(phrase(expr)(tokens))
  }
}

我混合了PackratParsers特性,将def更改为lazy val s,我使用的是PackratReader。我在这里误解了什么?从阅读丹尼尔C. Sobrals回答评论到SO问题How can I ignore non-matching preceding text when using Scala's parser combinators?似乎PackratParsers应该做到这一点。

参考:PackratParsers paper

1 个答案:

答案 0 :(得分:5)

问题

问题在于你确实填满了堆栈。您的表达式包含500个左括号,“1 + 1”,然后是500个右括号。在语法方面,你有500个术语“因子”相互嵌套,然后在一个术语“expr”中。

对于(嵌套)术语的每次开始,解析器必须在堆栈上推送一些东西(在本例中是一个函数调用)。嵌套术语完成后,解析后会从堆栈中弹出这个东西(在这种情况下:函数返回)。如果在最后一个令牌之后,堆栈是空的,如果堆栈永远不会消极(弹出太多),那么你的术语就会很好(在你的情况下:括号是平衡的)。

简单来说:解析器使用堆栈来计算括号是否平衡。

您正在使用多种工具来加快解析速度。这些工具都不会有助于堆栈消耗。

你的助手:

使用packrat解析器

packrat解析器缓存已解析的部分,因此不需要再次解析它们。当你的语法有许多常见部分的替代方案时,这可以带来很好的加速。它对你的情况没有帮助,因为你没有其他选择。它对堆栈消耗没有帮助。

省略部分结果

您使用 <~ ~> 来省略已解析结果的某些部分。但这只会助长一个学期。当调用相应的规则时,解析器仍然必须在堆栈上推送一些内容。

使用令牌

在解析之前,您将分解标记中的输入流。这通常会加快解析速度,因为令牌化(非递归,通常是正则表达式)比解析(递归)便宜得多。但在你的情况下,问题在于嵌套术语的深度(递归问题)。所以你的压力完全在解析部分,因此耗尽了堆栈。令牌化对这个问题没有帮助。

解决方案

我认为你不能轻易解决这个问题。解析器必须使用某种堆栈数据结构。通常,内置堆栈用于性能问题。您必须在堆上使用一些堆栈结构。这通常会慢很多,并且与Scala Parser Combinators不兼容。

您可以尝试在continuation-passing style(CPS)中包含“factor”。然后将在堆上跟踪调用,而不是在堆栈上。

开箱即用,没有简单的解决方案。