Ozan S. Yigit写的grep源码分析(不到1000行的正则表达式源码)
今天接着分析oz的grep源码。感觉他的写作风格我很喜欢,而且作者的代码也不长,写得非常流畅。佩服得不行,我原来也看过,但当时为了求快,有些地方就省过了,为了追求速度,只是为了看完,很多细致的地方就省略了,今天感觉,看源码,一定要看懂,如果没看懂,看再多的源码,对自己的帮助也不会太大。
昨天把gawk的源码大体框架弄懂了,今天就把grep的框架说一遍。
先读makefile文件,当时想调试,手工加debug,没弄出来,读grep.c时,看到注释中,说要用--DEBUG进行编译,于是打开makefile,发现有debug选项,于是
sudo make debug
这样,就编译出了,可以调试的版本。
再读grep.c文件
先调用re_comp进行编译,把正则表达式源码编译到nfa中。
再循环读文件,每读一行字串,就用re_exec进行匹配,如果匹配到了,就进行打印
再跳到regex.c中,分析re_comp
把"fo[1-3]"翻译成
chr f chr o clo 18bitset end
采用的方法是,逐字节扫描,用一个大的switch语句实现。
for(p=pat;*p;p++) {
lp=mp;
switch(*p){
如果是.
如果是^
如果是$
如果是[
先处理[-1-2]中的第一个-
再处理[]a]中的第一个]
再处理[^ab]中的^
处理[1-3]中的区间时,看程序时,要有动态的思想,自己在程序中执行,因为指向1,所以先执行else部分,指定增1后,指向-,所以再执行第一部分。这样就可以理解了。不然死活不理解,为什么c1要*(p-2)加1
c1 = *(p-2) + 1;
c2 = *p++;
while (c1 <= c2)
chset((CHAR)c1++);
如果是*
如果是+
其中把a+处理成aa*,因此,要先复制,再处理*
如果是\\
此处处理很有技巧,但难度不大。
}
}
再就是re_exec(char *lp)进行字串的匹配了。
分三种情况,
1)'^ab' 开头是'^'只进行一次pmatch
2) 'xab' 先查找到'x',再调用pmatch,加快匹配速度
3) 其它 调用pmatch,如果匹配不上,就把字串向后移一个位置,再调pmatch,所以用了循环。
再分析pmatch函数
和re_comp一样,也是一个大的switch语句,对nfa的每个字符,控制了字串的匹配,所以是表达式为主导的匹配。
static char *
pmatch(char *lp, CHAR *ap)
{
register int op, c, n;
register char *e; /* extra pointer for CLO */
register char *bp; /* beginning of subpat.. */
register char *ep; /* ending of subpat.. */
char *are; /* to save the line ptr. */
while ((op = *ap++) != END)
switch(op) {
case CHR:
if (*lp++ != *ap++)
return 0;
break;
case CLO://这个地方我看了许多次才看明白。
are = lp;
switch(*ap) {
case ANY:
while (*lp)
lp++;
n = ANYSKIP;
break;
case CHR:
c = *(ap+1);
while (*lp && c == *lp)
lp++;
n = CHRSKIP;
break;
case CCL:
while ((c = *lp) && isinset(ap+1,c))
lp++;
n = CCLSKIP;
break;
default:
re_fail("closure: bad nfa.", *ap);
return 0;
}
ap += n;
while (lp >= are) {
if (e = pmatch(lp, ap))
return e;
--lp;
} //循环中,是什么意思呢?我想啊想,实在没招了,就读
Plodsoft的博客,作者呢,写到关键的位置,就结束了。后来才想,噢,如果以
'fo[a-z]*1' 匹配'foa1'吧,匹配到[a-z]*后,剩下的部分,再开一个pmatch进行匹配。
因为是匹配优先,如果用'fo[a-z]*1' 匹配字串'fo1',此时,要回退,所谓--lp,也许我还要用手工执行一次。
纸上得来终须浅,还是要手工推导。
因此,读程序时,先建立一个宏观的印象,非常重要。这就是我今天想说的话。
return 0;
}
}