编程实现算术表达式括号是否匹配_快速实现词法分析器-Parser Combinator

相信每个Scala的爱好者都有读过《Programming in Scala》这本书,书中有一个章节「Combinator Parsing」介绍了一个很有趣的Scala库 – Parser Combinator(https://github.com/scala/scala-parser-combinators)。单从「Parser Combinator」字面并不太好理解它是什么,可以用来做什么。这篇文章将带着大家一起通过实例来了解Parser Combinator。

通常我们会有这样一种需求,将输入语言转换成计算机程序能够识别和处理的数据结构,也就是实现一个词法分析器。通过Parser Combinator,我们可以比较方便且快速地实现一个词法分析器。首先,需要把Parser Combinator加入到我们的项目当中。以SBT项目为例,只需要在build.sbt里加入如下一行便可以将这个库引入到项目中:

7e46c6000e9978a5ae9a13fdbdbf1a7f.png

词法分析的主要目的是把一个长字符串转换为单词的序列,而每个单词就是被解析出来的可以由计算机去理解的处理单元,一般被称为token。假设现在我们想对“1+2*3-4/5”这个的算术表达式进行解析。直观上,我们可以轻松看出每个数是一个token,由加、减、乘、除的运算符连接起来,还知道要先计算乘除运算,再计算加减运算。但是计算机应该如何理解它呢?下面开始实现我们的词法分析器。

定义一下可能解析出来的token:加、减、乘、除、和十进制数。

3960bc479a06ecac6cade9d70bf03212.png

然后进行解析。把这些token从输入字符串中识别出来,需要使用到Parser(解析器)。Parser Combinator提供了很多预定义的Parser,比如RegexParsers提供了很多使用正则表达式来识别和解析的Parser,而JavaTokenParsers继承自RegexParsers,并且提供了一些Java语法的Parser,等等。我们先定义解析十进制数的Parser "factor":

3066b0ac14c431bf9954f9cfd7e3452e.png

为了使用JavaTokenParsers提供的Parser,我们定义一个ArithParsers并继承了JavaTokenParsers。decimalNumber是JavaTokenParsers提供的一个Parser,定义如下:

9f201b82eb42d37188fb5b98cc20635f.png

decimalNumber使用正则表达式识别十进制数,如果解析成功,则返回一个基于String的Parser。在factor的定义当中,decimalNumbert调用了"^^"函数,而“{ v => DecimalVal(v.toDouble) }”是这个函数的参数。可以理解factor是这么一个Parser,在decimalNumber匹配成功的情况下,将返回的Parser[String]转换为Parser[Token]。

接下来进行运算符的解析。众所周知,乘除的优先级高于加减,因此在十进制数被解析出来以后,我们优先定义解析乘除运算的Parser “term":

d5f9f52dde4098bd06900f1ddfde3f2f.png

这里调用了chainl1这个预定义的函数(定义如下):Parser p是用来解析元素,Parser q是用来解析分割元素的token,然后产生一个左结合的函数将两个元素合并成一个。

dc21e0f507c0d7e58d6a2bb9f9426020.png

所以,factor就是这里的p,而q是运算符解析,定义如下:

4df9a4af1693e9a5223eea5a0b3e6fbd.png

可以理解为,如果“*”匹配成功,就将它分割两边的token合成一个“Mul" token;如果“/”匹配成功,就将它分割两边的token合成一个“Div” token。“|”也是一个函数调用,表示“或”,也就是匹配“*”或者“/”。

我们可以用同样的方法来定义解析加减运算符的Parser “expr”:

673a84cf46bedaf81a27133d59b97a19.png

此外,算术表达式可以通过括号来改变运算的优先顺序。我们只需要将Parser “factor”改进一下,做一个“递归”:

490205451a4a7fb5f3998248e22f9597.png

factor中新加的部分"(" ~> expr <~ ")"用来匹配左右括号,“expr”是上面定义的加减运算符Parser。也就是说括号里可以是另一个复杂的算术表达式。

到这里,我们词法分析器就定义好了,汇总一下:

e48d59e8317afae09a341a4d451fb7b0.png

写一个测试程序:

9e11ba329c060cdb8c6f9f4c1753d32f.png

输出结果为:

a5eac992d5e6afd89a29b71084eda7e0.png

这样几行代码就实现了一个针对算术表达式的词法分析器。我们还能进行很多的扩展,大家有兴趣可以尝试一下:

  • 对比较运算符AND,OR,NOT进行解析
  • 对函数调用进行解析,比如1 + SUM(1, 2) + 3
  • 对对象进行识别,比如[column 1],[column 2]

另外,对于词法分析的结果,我们还可以继续用Parser Combinator进行语法分析,只是分析的不是最初的输入字符串,而是词法分析产生的token。

最后,我们理解一下文章开头留下的问题,Parser Combinator是什么,用来做什么?它是利用原始简单的Parser配合组合操作运算符,从而构建出复杂的Parser来满足解析的需求。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值