Scala 提供了一种非常简单的方法来设计您自己的编程语言,使用它的解析器库。这使得创建您自己的领域特定语言(即 DSL)或解释语言比您想象的更容易。作为入门,让我们编写一个解析器来解析简单的数学表达式,例如“1+9*8”和“4*6/2-5”。
对于那些熟悉语言设计的人来说,这种语言的 EBNF 语法看起来像这样:
digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
number ::= digit | digit number
operator ::= "+" | "-" | "*" | "/"
expr ::= number (operator expr)?
为了开始使用 Scala 解析库编写解析器,我们编写了一个扩展 Parsers 特性的类。这是一个扩展 RegexParsers 的类的示例, 它是 Parsers 的子特征。
digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
number ::= digit | digit number
operator ::= "+" | "-" | "*" | "/"
expr ::= number (operator expr)?
有效标记的 Scala 定义与 EBNF 语法中的定义之间的唯一区别如下:
- Scala 在每个标记之间使用“~”
- 而不是使用“?”就像您在 EBNF 语法中一样,Scala 使用关键字“opt”
要执行我们的解析器,我们只需调用作为 Parsers 特性一部分的继承的 parse 方法。
digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
number ::= digit | digit number
operator ::= "+" | "-" | "*" | "/"
expr ::= number (operator expr)?
此 println 的结果将是:
(9~一些((*~(8~一些((+~(21~一些((/~(7~无))))))))))
我们完成了!好吧,不是真的。现在的输出是 Scala 看到解析器操作结果的方式。为了使我们的语言更有意义,让我们添加一些 Scala 代码来计算算术运算并将结果打印到输出。
让我们开始通过检查什么来计算结果 "(9~Some((*~(8~Some((+~(21~Some((/~(7~None)))))))))) " 真正意味着在 Scala 的世界里。让我们看一下这个字符串的子集,“(9~Some((*~(8~None))))”。这是解析“9*8”的结果。看起来有趣的第一部分是“9~Some(...)”。在我们的解析器中,我们定义了以下规则:
digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
number ::= digit | digit number
operator ::= "+" | "-" | "*" | "/"
expr ::= number (operator expr)?
很明显,“number”的计算结果为“9”,并且“~”被逐字打印出来,您应该记得它在 Scala 解析器中用于连接部分语法。然而,“Some(...)”是怎么回事?好吧,每当 Scala 解析 opt(x) 语句时,它都会将其评估为 Some(...) 或 None,这两者都是 Option 的子类。这是有道理的...... opt(x) 语句评估为一个选项。
让我们看一下将解析器结果转换成更有用的东西,而不是让我们的解析器返回一堆 ~ 和选项。再次查看我们当前的解析器规则:
digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
number ::= digit | digit number
operator ::= "+" | "-" | "*" | "/"
expr ::= number (operator expr)?
我们需要修改这个解析器定义,让它返回一个 Int 而不是 Any。我们还需要计算算术运算的结果。我们的语法规则允许单个数字或数字后跟算术运算符和另一个数字。如果我们正在处理单个数字,我们需要告诉解析器将结果转换为 Int。为此,我们对解析器规则进行以下修改:
digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
number ::= digit | digit number
operator ::= "+" | "-" | "*" | "/"
expr ::= number (operator expr)?
^^ 只是告诉解析器执行它后面的代码,包含在 {...} 中。我们所做的只是将其转换为 Int。
接下来,我们需要告诉解析器当它遇到一个数字时,或者当它遇到一个数字后跟一个运算符和另一个数字时该怎么做。为此,我们需要为每种情况定义整数运算(单个整数值、两个值的加法、两个值的减法、两个值的除法和两个值的乘法)。
digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
number ::= digit | digit number
operator ::= "+" | "-" | "*" | "/"
expr ::= number (operator expr)?
我们正在处理五个案例。第一种情况是我们只有一个整数 (a ~ None)。当我们有一个 Int 后面有 None 时,我们只需按原样评估整数值。第二种情况是当我们有一个整数乘以另一个整数时 (a ~ Some("*" ~ b))。在这种情况下,我们只需执行 a * b。然后我们继续定义除法、加法和减法的规则。
本教程的主要内容是:
- 您在 Parser[ ] 定义的括号内定义解析器规则返回的类型。在这个例子中,它是一个 Int。
- 您可以添加自定义 Scala 代码以使用 ^^ { ... } 对解析器结果进行操作
现在我们已经为 Scala 解析器组合器奠定了基础,我们可以在这些特性的基础上构建一个功能齐全的解释型语言,其中包含 if-else 条件、循环,甚至函数调用。
这是一篇关于如何使用这种方法创建功能齐全的解释型语言的文章: https://dzone.com/articles/create-a-programming-language-with-scala-parser-co