如何使用解析器来解析跨越多行的记录?我需要解析树数据(并最终将其转换为树数据结构)。我在下面的代码中遇到了难以跟踪的解析错误,但不清楚这是否是Scala解析器的最佳方法。问题实际上更多的是解决问题的方法,而不是调试现有的代码。
EBNF-ish语法是:
SP = " "
CRLF = "\r\n"
level = "0" | "1" | "2" | "3"
varName = {alphanum}
varValue = {alphnum}
recordBegin = "0", varName
recordItem = level, varName, [varValue]
record = recordBegin, {recordItem}
file = {record}
尝试实现和测试语法:
import util.parsing.combinator._
val input = """0 fruit
1 id 2
1 name apple
2 type red
3 size large
3 origin Texas, US
2 date 2 aug 2011
0 fruit
1 id 3
1 name apple
2 type green
3 size small
3 origin Florida, US
2 date 3 Aug 2011"""
object TreeParser extends JavaTokenParsers {
override val skipWhitespace = false
def CRLF = "\r\n" | "\n"
def BOF = "\\A".r
def EOF = "\\Z".r
def TXT = "[^\r\n]*".r
def TXTNOSP = "[^ \r\n]*".r
def SP = "\\s".r
def level: Parser[Int] = "[0-3]{1}".r ^^ {v => v.toInt}
def varName: Parser[String] = SP ~> TXTNOSP
def varValue: Parser[String] = SP ~> TXT
def recordBegin: Parser[Any] = "0" ~ SP ~ varName ~ CRLF
def recordItem: Parser[(Int,String,String)] = level ~ varValue ~ opt(varValue) <~ CRLF ^^
{case l ~ f ~ v => (l,f,v.map(_+"").getOrElse(""))}
def record: Parser[List[(Int,String,String)]] = recordBegin ~> rep(recordItem)
def file: Parser[List[List[(Int,String,String)]]] = rep(record) <~ EOF
def parse(input: String) = parseAll(file, input)
}
val result = TreeParser.parse(input).get
result.foreach(println)
答案 0 :(得分:3)
明确处理空白并不是一个特别好的主意。当然,使用get
表示您丢失了错误消息。在这个特定的例子中:
[1.3] failure: string matching regex `\s' expected but `f' found
0 fruit
^
实际上非常清楚,但问题是为什么它需要一个空间。现在,这显然正在处理一个recordBegin
规则,这个规则是这样定义的:
"0" ~ SP ~ varName ~ CRLF
因此,它解析零,然后解析空间,然后必须针对fruit
解析varName
。现在,varName
的定义如下:
SP ~> TXTNOSP
另一个空间!因此,fruit
应该以空格开头。
答案 1 :(得分:3)
正如Daniel所说,你最好让解析器处理空格跳过以最小化你的代码。不过,您可能需要调整whitespace
值,以便match end of lines explicitly。如果没有定义记录的值,我在下面做了以防止解析器移动到下一行。
如果您想匹配字母词,请尝试使用JavaTokenParsers
中定义的解析器,例如ident
。
为了简化错误跟踪,请在NoSuccess
上执行parseAll
匹配,这样您就可以看到解析器失败的位置。
import util.parsing.combinator._
val input = """0 fruit
1 id 2
1 name apple
2 type red
3 size large
3 origin Texas, US
2 var_without_value
2 date 2 aug 2011
0 fruit
1 id 3
1 name apple
2 type green
3 size small
3 origin Florida, US
2 date 3 Aug 2011"""
object TreeParser extends JavaTokenParsers {
override val whiteSpace = """[ \t]+""".r
val level = """[1-3]{1}""".r
val value = """[a-zA-Z0-9_, ]*""".r
val eol = """[\r?\n]+""".r
def recordBegin = "0" ~ ident <~ eol
def recordItem = level ~ ident ~ opt(value) <~ opt(eol) ^^ {
case l ~ n ~ v => (l.toInt, n, v.getOrElse(""))
}
def record = recordBegin ~> rep1(recordItem)
def file = rep1(record)
def parse(input: String) = parseAll(file, input) match {
case Success(result, _) => result
case NoSuccess(msg, _) => throw new RuntimeException("Parsing Failed:" + msg)
}
}
val result = TreeParser.parse(input)
result.foreach(println)