Scala 解析器组合器
当需要解析器生成器时,我们想到的一些著名的解析器是:用 C 编写的解析器的Yacc和Bison和用Java编写的解析器的 ANTLR,但它们旨在为特定的编程语言运行。这缩短了解析器的使用范围。然而,Scala 提供了一个独特且有用的替代方案。代替使用解析器生成器的独立领域特定语言,可以使用内部领域特定语言(简称内部 DSL)。内部 DSL 将包含一个解析器组合器库——在 Scala 中定义的函数和运算符,它们将用作解析器的构建块。
为了理解这一内容,必须具备编译器的基本知识,并且必须理解常规和上下文无关的语言。
- 正则语法
- 上下文无关语法
第一步总是为要解析的语言写下语法。
表达式:每个表达式(由 expr 表示)都是一个术语,后面可以跟一系列 '+' 或 '-'运算符和其他术语。
术语:术语是一个因素,可能后跟一系列“*”或“/”运算符以及其他因素。
因子:因子是数字字面量或括号中的表达式。
算术表达式解析器示例:
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 对象定义了一个解析传递给它的第一个命令行参数的 main 方法。解析由表达式完成: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' 方法随后发出一条错误消息,说它期望在右括号处有一个运算符。