分享

ANTLR: 文法分析利器 (Ⅱ)

 快乐学习 2006-12-23

ANTLR: 文法分析利器 (Ⅱ)

(I)里,我已经简单介绍了ANTLR。现在,让我们继续ANTLR之旅,来真实感受一下它的强大。

和很多词法语法分析软件一样,我也以一个简易计算器作为example。

计算器需求

  1. 运算符:+,-,×,÷,(,)
  2. 支持整型和浮点型

测试样例

  1. 1 + 2 * 3 + 5 - 4/2
  2. (1 + 2) * 3 + 5 - 4 / (2+2)
  3. (1.2*2.5)+8/(4-3)*2.7

下面就让我们动手完成一个计算器,:)
先搭个框架。文件名是calc.g

options {
language = “Cpp”;
}

class CalcParser extends Parser;

class CalcLexer extends Lexer;

这些就是基本框架了。
options里设置language为”Cpp”,表示要生成c++代码。
CalcParser是我们的计算器的语法解析类,继承ANTLR里的Parser类。
同理,CalcLexer是词法分析类,继承ANTLR里的Lexer类。


接着定义计算器的词法规则。

首先是运算符。
PLUS : ‘+’;
SUB : ‘-’ ;
MUL : ‘*’ ;
DIV : ‘/’ ;
LPAREN : ‘(’ ;
RPAREN : ‘)’ ;

接着是操作数。
NUM : (’0′..’9′)+ { $setType(INT); } (’.’ (’0′..’9′)+ { $setType(REAL); } )? ;

:这里解释一下NUM这个规则。$setType是ANTLR内置函数,用来设置token类型。所以这个规则的意思就是当匹配到(’0′..’9′)*后,设置token类型为INT,当发现后面跟着小数点和数字后,重新设置token类型为REAL。相信细心的读者会有这种疑问,为什么不用两条规则,通过有无’.‘来匹配。关于这点,我们来回忆一下,ANTLR是采用LL(*),目前的release版本2.7.6只支持fixed-length lookahead,所以用两条规则会引起ambiguity。可以查看我在(I)里的回复有更详细的描述。)


当然,我们还希望忽略空格,制表符,回车换行这种无意义字符的。
WS : (’ ‘
| ‘\t’
| ‘\n’
| ‘\r’)+
{
$setType(ANTLR_USE_NAMESPACE(antlr)Token::SKIP);
}
;

到这里为止,我们的词法规则就完成了。下面就是语法规则。先来分析一下运算符。显然,(×/÷)>(+/−)。(,)用来组合式子。

至此,语法规则也呼之欲出了。

statement : mexpr ( (PLUS | SUB) mexpr )*
;
mexpr : expr ( (MUL | DIV) expr )*
;
expr : INT
| REAL
| LPAREN statement RPAREN
;

all done。一个计算器的语法程序就写好了。让我们来生成c++代码,实际测试一下.
$java -cp /usr/share/java/antlr.jar antlr.Tool calc.g
ANTLR Parser Generator Version 2.7.6 (20060528) 1989-2005
$ls
calc.g CalcParser.cpp CalcParserTokenTypes.txt
CalcLexer.cpp CalcParser.hpp
CalcLexer.hpp CalcParserTokenTypes.hpp
$
我们可以看到,生成了6个文件。有兴趣的可以自己看一下,每个文件的可读性是很强的,:)。
我们写个CalcTest.cpp:

#include “CalcLexer.hpp”
#include “CalcParser.hpp”
#include
using namespace std;
using namespace antlr;

int main()
{
try {
CalcLexer lexer(cin);
CalcParser parser(lexer);
parser.statement();
} catch (exception &e) {
cout << e.what() << endl;
}
}

编译之:
$g++ -o Calc CalcTest.cpp CalcParser.cpp CalcLexer.cpp -lantlr

让我们来跑一下测试用例吧,:p

$ ./Calc
1 + 2 * 3 + 5 - 4/2
$ ./Calc
(1 + 2) * 3 + 5 - 4 / (2+2)
$ ./Calc
(1.2*2.5)+8/(4-3)*2.7
$

顺利通过,一切都是那么的自然,lol~。ok, 来些错误的。

$ ./Calc
(1+(1+3.5*(3-2))
line 1:18: expecting RPAREN, found ‘’

可以从错误中看到,还有一个没有匹配的’(‘。 有兴趣的话你就自己测试吧,:)

至此,我们的计算器的有语法解析部分基本已经完成,剩下的就是如何得到计算结果,这个留在后续文章吧。

完整的程序可以从这里下载calc.tar.gz
enjoy yourself! Any advice prefer.

1 Comment so far

  1. sishen October 13th, 2006 10:17 am

    关于终结符.

    在statement的规则里, 我们并没有设置终结符, 同时由于我们忽略了空格,制表符,回车, 所以在stdin状态下, 规则是无法终结的, 需要我们自己输入”EOF”来终止. 也就是结束lexer和parser. 使程序中止, 如文章中的例子所示.

    ANTLR也自带了EOF特殊符. 最简单的是

    statement : mexpr ( (PLUS | SUB) mexpr )* EOF
    ;

    我之所以没用是我希望在后面的文章中用’=‘来表示终结. 这里就手动输入EOF好了.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多