我一直试图绕过Scala的解析器组合器。看起来它们非常强大,但我似乎找到的唯一教程示例是数学表达式,并且很少有适当的现实解析示例,需要解析并映射到不同的实体等DSL。
为了这个例子,假设我有这个BNF,我有这个名为Model的实体,它由一个像这样的字符串组成:[model [name <name> ]]
。这是我所拥有的更大BNF的简单例子,现实中有更多实体。
所以我定义了自己的类Model
,它以name
作为构造函数,然后定义了我自己的ModelParser
对象,它扩展了JavaTokenParsers
。然后我在BNF之后定义了以下解析器(我知道有些可能有一个更简单的正则表达式匹配器,但我更倾向于因其他原因而完全遵循BNF。)
def model : Parser[Model] = "[model" ~> "[name" ~> name <~ "]]" ^^ ( Model(_) )
def name : Parser[String] = (letter ~ (anyChar*)) ^^ {case text => text.toString())
def anyChar = letter | digit | "_".r | "-".r
def letter = """[a-zA-Z]""".r
def digit = """\d""".r
toString
Model
看起来像这样:
override def toString : String = "[model " + name + "]"
当我尝试使用像[model [name helloWorld]]
之类的字符串运行它时,我得到了这个
[model [h~List(e, l, l, o, W, o, r, l, d)]]
而不是我期待的[model helloWorld]
如何让这些单个字符加入到原来的字符串中?
我也对单独的解析器和.r
的使用感到困惑。有时我看到他们只有以下作为解析器的示例(解析“hello”):
def hello = "hello"
这不仅仅是一个字符串吗?它是如何在地球上突然变成一个可以与其他解析器组合的解析器? .r
实际上在做什么?我已经阅读了至少3个教程,但仍然完全失去了实际发生的事情。
答案 0 :(得分:3)
问题是anyChar*
解析List[String]
(在这种情况下,每个字符串都是单个字符),并且在字符串列表上调用toString
的结果是{{ 1}},而不是通过连接内容得到的字符串。此外,"List(...)"
模式与整个case text =>
匹配,而不仅仅是letter ~ (anyChar*)
部分。
可以非常直接地解决这两个问题:
anyChar*
我们只是将第一个字符串附加到其余列表中,然后在整个列表中调用case class Model(name: String) {
override def toString : String = "[model " + name + "]"
}
import scala.util.parsing.combinator._
object ModelParser extends RegexParsers {
def model: Parser[Model] = "[model" ~> "[name" ~> name <~ "]]" ^^ (Model(_))
def name: Parser[String] = letter ~ (anyChar*) ^^ {
case first ~ rest => (first :: rest).mkString
}
def anyChar = letter | digit | "_".r | "-".r
def letter = """[a-zA-Z]""".r
def digit = """\d""".r
}
,这将连接内容。这按预期工作:
mkString
正如您所指出的那样,让正则表达式完成更多工作是可能的(并且可能更清晰,更高效):
scala> ModelParser.parseAll(ModelParser.model, "[model [name helloWorld]]")
res0: ModelParser.ParseResult[Model] = [1.26] parsed: [model helloWorld]
此示例还说明了解析组合器库使用隐式转换来减少编写解析器的一些冗余的方式。正如您所说,object ModelParser extends RegexParsers {
def model: Parser[Model] = "[model" ~> "[name" ~> name <~ "]]" ^^ (Model(_))
def name: Parser[String] = """[a-zA-Z\d_-]+""".r
}
定义了一个字符串,def hello = "hello"
定义了Regex
(通过the r
method on StringOps
),但两者都可以用作解析器,因为RegexParsers
定义了从"[a-zA-Z]+".r
(此名为String
}和literal
(Regex
)到regex
的隐式转化。