由于这是我的第一篇文章,我想借此机会说:这是一个多么伟大的网站!
无论如何,对于这个问题:
我有点像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
应该做到这一点。
答案 0 :(得分:5)
问题在于你确实填满了堆栈。您的表达式包含500个左括号,“1 + 1”,然后是500个右括号。在语法方面,你有500个术语“因子”相互嵌套,然后在一个术语“expr”中。
对于(嵌套)术语的每次开始,解析器必须在堆栈上推送一些东西(在本例中是一个函数调用)。嵌套术语完成后,解析后会从堆栈中弹出这个东西(在这种情况下:函数返回)。如果在最后一个令牌之后,堆栈是空的,如果堆栈永远不会消极(弹出太多),那么你的术语就会很好(在你的情况下:括号是平衡的)。
简单来说:解析器使用堆栈来计算括号是否平衡。
您正在使用多种工具来加快解析速度。这些工具都不会有助于堆栈消耗。
packrat解析器缓存已解析的部分,因此不需要再次解析它们。当你的语法有许多常见部分的替代方案时,这可以带来很好的加速。它对你的情况没有帮助,因为你没有其他选择。它对堆栈消耗没有帮助。
您使用 <~
和 ~>
来省略已解析结果的某些部分。但这只会助长一个学期。当调用相应的规则时,解析器仍然必须在堆栈上推送一些内容。
在解析之前,您将分解标记中的输入流。这通常会加快解析速度,因为令牌化(非递归,通常是正则表达式)比解析(递归)便宜得多。但在你的情况下,问题在于嵌套术语的深度(递归问题)。所以你的压力完全在解析部分,因此耗尽了堆栈。令牌化对这个问题没有帮助。
我认为你不能轻易解决这个问题。解析器必须使用某种堆栈数据结构。通常,内置堆栈用于性能问题。您必须在堆上使用一些堆栈结构。这通常会慢很多,并且与Scala Parser Combinators不兼容。
您可以尝试在continuation-passing style(CPS)中包含“factor”。然后将在堆上跟踪调用,而不是在堆栈上。
开箱即用,没有简单的解决方案。