Lex和Yacc是Unix下不错的词法分析器和语法分析器,在linux下,这两个工具被成为flex和bison,也是C++经常用来构建字符分析程序的工具。
本文不是一篇入门文章,我们假设您已经了解了Lex和Yacc的基本语法
入门文章请参考IBM的:【Yacc 与 Lex 快速入门】
我们这里讨论一些其有趣的用法和注意的事项
字符串的识别
常规的正则式和匹配问题都难不倒大家,那么下面来想一个问题,C语言中字符串如何识别?
我们知道,字符串一般是这样的
"some \"string\" problem.\n"
但我们会发现其中包含有转移符和引号,如何只是简单的如下书写正则式:
\"[^"]*\"
则会导致引号表达能力不全,不能满足C语言的要求。
所以我们考虑将里面的表达部分拆开,首先,虽然不能有引号,但可以让其有\"
,所以我们的正则式改完如下:
\"(\\"|[^"])*\"
好的,那么我们可以用这个\"
转义引号了,但如何你认为就这样就可以了,那未免有点心急,因为还有很重要的情况,那就是后一半中其实也可以含有\
,但其实我们的\
实际上是转义符,要成对配套使用,单独用\是不正确的,所以我们应该加上对其的限制,不让\
随意出现,那么我们的正则变成了这样:
\"(\\.|[^"\\])*\"
好,这就是我们的C语言字符串识别的正则式了。
注释的识别
恩,解决了棘手的字符串识别难题,那么,又发现了另外的情况,C语言有两种注释,如何正确的识别他们呢?
// hello world
/**
* hello world
*/
首先第一种较为容易实现,类似上面的方法,只要让注释中不存在换行符就好:
//[^\n]*
但下面一种较为复杂,当然也有简单实现的方式
"/*"([^\*]|(\*)*[^\*/])*(\*)*"*/"
这个正则式十分复杂,我们分解讲解一下
"/*" ( [^\*] | (\*)* [^\*/] )* (\*)* "*/"
( [^\*] | (\*)* [^\*/] )*
这一段是在找所以的非*
内容,或者是*
后面不是*
或/
的部分,这是被允许的,有人问,为何里面*
不能跟*
呢?
这是由于一旦可以跟*
,下一次匹配就限制不住匹配/
开头的了,为了避免这一情况,做出限制,但又由于可能存在末尾连续的*
的情况,所以在后面又补充了连续的*
这里,其实使用别的正则引擎,还有简单的解决方案,具体可以参考这篇英文博客:【Finding Comments in Source Code Using Regular Expressions】
另外在Lex的实际使用中,还有一种简便的方式,那就是利用固定的C代码,处理注释的丢弃,方法如下:
"/*" comment();
%%
comment()
{
char c, c1;
loop:
while ((c = input()) != ‘*‘ && c != 0)
putchar(c);
if ((c1 = input()) != ‘/‘ && c != 0)
{
unput(c1);
goto loop;
}
if (c != 0)
putchar(c1);
}