Scala 解析器组合器入门

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 63w+ 字,讲解图 2808+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2200+ 小伙伴加入学习 ,欢迎点击围观

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

相关文章