Scala 解析器组合器

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特性的类中。

将无上下文语法转换为代码的步骤。

  1. 每个生产都成为一个方法,因此要加一个前缀 “def”。
  2. 每个方法的结果类型是Parser[Any],因此我们需要将::=符号改为”: Parser[Any] =”。
  3. 在语法中,顺序组合是隐含的,但在程序中,它由一个显式运算符来表达。~.所以我们需要在一个生产的每两个连续符号之间插入一个’~’。
  4. 重复的表达是rep( … ) 而不是{ … }。
  5. 在每个产品的结尾处的句号(.)被省略,但是也可以使用分号(;)。

用下面的代码测试一下你的分析器是否工作

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’方法发出了一条错误信息,说它希望在结尾小括号的位置有一个运算符。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程