本文来自: http://fair-jm.iteye.com 转截请注明出处
最近几天看了些文章(Learn by Example: Scala Parser Combinators,An Introduction To Scala Parser Combinators )学习了scala中的Parser的一些使用 这边以一个解析JSON的例子来记录一些笔记
首先是一些连接符(翻译自第一篇文章):
- | 选择连接符,当左边或右边的Parser成功时成功。
- ~ 序列连接符,当左右的Parser都成功时成功
- ~> 和~类似 不同的是左边的结果不会包含在最后的结果中
- <~ 和~类似 右边的结果不会包含在最后的结果中
- ^^ 转换连接符,当左边的操作成功时可以通过这个连接符操作结果
- rep repeat的意思 重复
如果说看起来比较难懂的话 在使用之后就会觉得容易理解得多
我没有学过编译原理(选修课没选惭愧...听到别人说一星期两节课要上两章就退缩了...) 对一些专用术语不是很理解 这边的Parser使用更加像是使用正则表达式(这主要也有继承了RegexParser的因素在)
首先看JSON的组成 从ECMA-404的文档中可以看到:
主要有 object array number string bool(true|false) null 这几个元素构成
为了编码方便 我让类继承自JavaTokenParsers:
class JsonParser extends JavaTokenParsers
对以上一些元素进行了定义(和ECMA-404的规范有些许差别 其实很多地方需要重写正则表达式的...)
//来自JavaTokenParser的floatingPointNumber parser
def jNum: Parser[Double] = floatingPointNumber ^^ (_.toDouble)
//stringLiteral 也来自JavaTokenParser 这边解析出来的结果会是例如"string"的形式 我们需要把双引号给去掉
def jStr: Parser[String] = stringLiteral ^^ (s => s.substring(1, s.length() - 1))
//正则表达式会被隐式转换为Parser
def jBool: Parser[Boolean] = "(true|false)".r ^^ (_.toBoolean)
def jNull: Parser[Null] = "null".r ^^ (t => null)
接下去还差对object和array的定义
在ECMA-404中他们的定义如下:
其中他们内的value是可以包括所有类型的(包括object和array)
可能有多个 这样需要用到rep连接符
具体的编码不是很困难 scala的模式匹配用起来比较舒服:
这边将JsonArray转化为List的形式 将JsonObject转换为Map的形式:
def term = jsonArray | jsonObject | jNum | jBool | jNull | jStr
def jsonArray: Parser[List[Any]] = "[" ~> rep(term <~ ",?".r) <~ "]" ^^ (l => l)
//String也会被隐式转化为Parser的形式
def jsonObject: Parser[Map[String, Any]] = "{" ~> rep((ident ~ ":" ~ jNum |
ident ~ ":" ~ jBool | ident ~ ":" ~ jNull | ident ~ ":" ~ jsonObject | ident ~ ":" ~ jsonArray | ident ~ ":" ~ jStr) <~ ",?".r) <~ "}" ^^ {
os =>
var map = Map[String, Any]()
os.foreach(o =>
o match {
case k ~ ":" ~ v => map = map ++ Map(k -> v)
})
map
}
最后来实验一下:
虽然number的形式有点问题(我用了JavaTokenParser中的一个Parser) 但总体结果还是正确的 使用起来也是相当简单
对于解析JSON来说 用scala提供的Parser并不困难 本文并未对其中的原理有任何描述 可以看上面参考的第二篇文章 里面有比较详尽的分析
希望对初学scala的Parser的人有所帮助^_^