📜  Scala 解析器组合器

📅  最后修改于: 2022-05-13 01:55:17.013000             🧑  作者: Mango

Scala 解析器组合器

当需要解析器生成器时,我们想到的一些著名的解析器是:用 C 编写的解析器的YaccBison和用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 的类中。

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

  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 对象定义了一个解析传递给它的第一个命令行参数的 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' 方法随后发出一条错误消息,说它期望在右括号处有一个运算符。