6

パーサーを使用して、複数行にまたがるレコードを解析するにはどうすればよいですか?ツリーデータを解析する必要があります(そして最終的にはツリーデータ構造に変換します)。以下のコードで追跡が困難な解析エラーが発生していますが、これがScalaパーサーを使用した最善のアプローチであるかどうかは明確ではありません。問題は、既存のコードをデバッグすることではなく、問題解決のアプローチに関するものです。

EBNFっぽい文法は次のとおりです。

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)
4

2 に答える 2

3

ダニエルが言ったように、コードを最小化するために、パーサーに空白のスキップを処理させる方がよいでしょう。ただし、行の終わりを明示的に一致whitespaceさせることができるように、値を微調整することもできます。レコードの値が定義されていない場合にパーサーが次の行に移動しないようにするために、以下でこれを行いました。

可能な限り、アルファベットの単語と一致させたい場合は、 JavaTokenParserslikeで定義されたパーサーを使用するようにしてください。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)
于 2011-05-31T19:02:14.967 に答える
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スペースから始めるべきだった。

于 2011-05-31T17:50:41.930 に答える