Scala 解析器组合器
当需要一个解析器生成器时,我们想到的一些著名的解析器是。Yacc和Bison用于用C语言编写的解析器,ANTLR用于用Java编写的解析器,但它们都是为特定的编程语言设计的。这就缩短了解析器的使用范围。然而,Scala提供了一个独特而有用的替代方案。人们可以不使用解析器生成器的独立领域特定语言,而是使用内部领域特定语言(简称内部DSL)。内部DSL将由一个解析器组合库组成–在Scala中定义的函数和操作符,它们将作为解析器的构建块。
为了理解这些内容,人们必须具备编译器的基本知识,并且必须理解正则语言和无语境语言。
- 正则语法
- 无语境语法
第1步总是为要解析的语言写下一个语法。
Expression : 每个表达式(用expr表示)都是一个术语,后面可能有一连串的’+’或’-‘运算符和进一步的术语。
Term : 一个术语是一个因子,后面可能有一连串的'*'或'/'运算符和进一步的因子。
Factor : 一个因子是一个数字字面或一个括号内的表达式。
算术表达式解析器的例子。
expr ::= term {"+" term | "-" term}.
term ::= factor {"*" factor | "/" factor}.
factor ::= ?FloatingPointNumber | "(" expr ")".
| 表示替代性生产
{ … } 表示重复(零次或多次)。
以上例子的Scala代码。
import scala.util.parsing.combinator._
class Arith extends JavaTokenParsers
{
def expr: Parser[Any] = term~rep("+"~term | "-"~term)
def term: Parser[Any] = factor~rep("*"~factor | "/"~factor)
def factor: Parser[Any] = floatingPointNumber | "("~expr~")"
}
算术表达式的解析器包含在一个继承了JavaTokenParsers特性的类中。
将无上下文语法转换为代码的步骤。
- 每个生产都成为一个方法,因此要加一个前缀 “def”。
- 每个方法的结果类型是Parser[Any],因此我们需要将::=符号改为”: Parser[Any] =”。
- 在语法中,顺序组合是隐含的,但在程序中,它由一个显式运算符来表达。~.所以我们需要在一个生产的每两个连续符号之间插入一个’~’。
- 重复的表达是rep( … ) 而不是{ … }。
- 在每个产品的结尾处的句号(.)被省略,但是也可以使用分号(;)。
用下面的代码测试一下你的分析器是否工作
object ParseExpr extends Arith
{
def main(args: Array[String])
{
println("input : "+ args(0))
println(parseAll(expr, args(0)))
}
}
ParseExpr 对象定义了一个主方法,用来解析传递给它的第一个命令行参数。解析是由表达式完成的:parseAll(expr, input)
我们可以用下面的命令来运行算术分析器。
$ scala ParseExpr "4 * (5 + 7)"
input: 4 * (5 + 7)
[1.12] parsed: ((4~List((*~(((~((5~List())~List((+ ~(7~List())))))~)))))~List())
输出告诉我们,解析器成功地分析了输入字符串,直到位置[1.12]。这意味着第一行和第十二列,或者我们可以说整个输入字符串被解析了。
我们还可以检查解析器是否对错误的输入起作用,是否给出了一个错误。
例子
$ scala ParseExpr "2 * (3 + 7))"
input: 2 * (3 + 7))
[1.12] failure: `-' expected but `)' found
2 * (3 + 7)) ˆ
expr解析器解析了所有的内容,除了最后的闭合小括号,它并不构成算术表达式的一部分。然后’parseAll’方法发出了一条错误信息,说它希望在结尾小括号的位置有一个运算符。
极客教程