日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

简易正则表达式引擎源码阅读

發布時間:2025/3/15 编程问答 11 豆豆
生活随笔 收集整理的這篇文章主要介紹了 简易正则表达式引擎源码阅读 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

  第一篇博客。分析一下一個簡單的正則表達式引擎的實現。這個引擎是Ozan S. Yigit(Dept. of Computer Science,?York University)根據4.nBSD UN*X中的regex routine編寫的,在他的個人主頁上可以找到源碼。引擎支持的特性不多,但源碼不到1000行,而且是典型的compile-execute模式,邏輯清晰,對理解正則表達式的工作原理很有幫助。

1?受支持的特性

  引擎支持的正則表達式特性如下。

字符解釋?
?常規字符

?除了元字符(. \ [ ] * + ^ $)以外的字符。

匹配字符本身

?.匹配任意字符?
?[set]

character class。 匹配所有set中的字符。

如果set的第一個字符是^,表示匹配所有不在set內的字符。

快捷寫法S-E表示匹配從字符S到字符E的所有字符。

例:

[a-z] 匹配 任意一個小寫字母

[^]-] 匹配 除了 "]" 和 "-" 以外的任意字符

[^A-Z] 匹配 除了大寫字母以外的任意字符

[a-zA-Z] 匹配 任意字母

*

closure。匹配零個或多個指定符號。

它只能緊跟在常規字符、"." 、character class或closure的后面。

它會盡可能多地匹配滿足條件的字符。

+

匹配一個或多個指定符號。其他規則與*相同。

\\(group\)

captured group。匹配group中的符號。

在其后的表達式中可以使用\1 ~ \9引用這個group

\1 ~ \9引用之前匹配的group
\<

匹配單詞開頭的邊界。

單詞是一個或多個由字母、數字和下劃線組成的序列。

\>匹配單詞末尾的邊界。
其他字符匹配字符本身。主要用于對元字符進行轉義
^

如果^是表達式的第一個字符,表示匹配字符串的開頭;

否則將匹配^字符本身。

$

如果$是表達式的最后一個字符,表示匹配字符串的末尾;

否則將匹配$字符本身。

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

2 工作原理

  引擎首先將表達式編譯為NFA,然后使用這個NFA匹配字符串。

2.1 編譯

  引擎掃描整個表達式,并生成一個NFA。NFA的結構是一個由opcode組成的序列。所有opcode如下。

?opcodeoperand?解釋
?CHR字符匹配一個字符
ANY?匹配任意一個字符
CCLbitset

character class。

操作數為16 byte的bitset,其中每個bit都

與一個ASCII字符匹配。

BOL??匹配字符串開頭
EOL?匹配字符串末尾
BOT1~9標識一個captured group的開頭
EOT1~9標識一個captured group的結尾
BOW?匹配單詞開頭的邊界
EOW?匹配單詞末尾的邊界
REF1~9引用group
CLO?

closure。

一個CLO ... END pair所包含的內容為closure的內容。

END?標識NFA結束或closure結束

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

  例:

  表達式:?foo*.*?

  NFA: ?CHR f CHR o CLO CHR o END CLO ANY END END?

  匹配: ?fo foo fooo foobar fobar foxx ...?

?

  表達式:?fo[ob]a[rz]?

  NFA: ?CHR f CHR o CCL bitset CHR a CCL bitset END?

  匹配: ?fobar fooar fobaz fooaz?

?

  表達式:?foo\\+?

  NFA: ?CHR f CHR o CHR o CHR \ CLO CHR \ END END ? ?--?x+ 被轉換成 xx*?

  匹配: ?foo\ foo\\ foo\\\ ...?

?

  表達式:?\(foo\)[1-3]\1?

  NFA: ?BOT 1 CHR f CHR o CHR o EOT 1 CCL bitset REF 1 END?

  匹配: ?foo1foo foo2foo foo3foo?

?

  表達式:?\(fo.*\)-\1?

  NFA: ?BOT 1 CHR f CHR o CLO ANY END EOT 1 CHR - REF 1 END?

  匹配: ?foo-foo fo-fo fob-fob foobar-foobar ...?

?

  編譯生成的NFA保存在一個全局char數組中:

#define MAXNFA 1024 static CHAR nfa[MAXNFA];

  re_comp()函數接受一個表達式字符串并編譯生成NFA。如果編譯失敗,則返回錯誤信息的字符串,否則返回0。

1 char *re_comp(char *pat) { 2 char *p; /* pattern pointer */ 3 CHAR *mp = nfa; /* nfa pointer */ 4 CHAR *lp; /* saved pointer.. */ 5 CHAR *sp = nfa; /* another one.. */ 6 7 int tagi = 0; /* tag stack index */ 8 int tagc = 1; /* actual tag count */ 9 10 int n; 11 CHAR mask; /* xor mask CCL */ 12 int c1, c2;

  p作為遍歷字符串pat的指針使用。mp作為寫入nfa數組的指針使用,它始終指向最近寫入的數據的下一個位置。lp和sp用于保存mp的位置。

  tagi是一個stack的top指針,這個stack保存了當前所處的group編號:

#define MAXTAG 10 static int tagstk[MAXTAG];

  tagc是group編號的counter。

  剩下的局部變量都作為臨時變量。

13 for (p = pat; *p; p++) { 14 lp = mp; 15 switch (*p) { 16 case '.': /* match any char.. */ 17 store(ANY); 18 break;

  生成NFA的主循環。

  首先使用lp保存當前mp,在for循環末尾再將lp賦值給sp。sp保存了上一個opcode位置的指針。

  15行的switch根據不同的字符模式生成opcode和operand。

  17行從"."字符生成ANY opcode,store()是一個寫入mp的宏:

#define store(x) *mp++ = x 19 case '^': /* match beginning.. */ 20 if (p == pat) 21 store(BOL); 22 else { 23 store(CHR); 24 store(*p); 25 } 26 break; 27 case '$': /* match endofline.. */ 28 if (!p[1]) 29 store(EOL); 30 else { 31 store(CHR); 32 store(*p); 33 } 34 break;

  如果^字符是字符串的第一個字符,那么生成?BOL?,否則當作常規字符處理,生成?CHR ^?。對$字符的處理也類似。

35 case '[': /* match char class..*/ 36 store(CCL); 37 if (*++p == '^') { 38 mask = 0377; 39 p++; 40 } 41 else 42 mask = 0; 43 44 if (*p == '-') /* real dash */ 45 chset(*p++); 46 if (*p == ']') /* real brac */ 47 chset(*p++); 48 while (*p && *p != ']') { 49 if (*p == '-' && p[1] && p[1] != ']') { 50 p++; 51 c1 = p[-2] + 1; 52 c2 = *p++; 53 while (c1 <= c2) 54 chset((CHAR)c1++); 55 } else 56 chset(*p++); 57 } 58 if (!*p) 59 return badpat("Missing ]"); 60 61 for (n = 0; n < BITBLK; bittab[n++] = (char) 0) 62 store(mask ^ bittab[n]); 63 64 break;

  35~64行處理character class。

  37行判斷"["字符后的第一個字符是否是"^",如果是,那么需要在最后bitwise not整個bitset,這里用了一個mask,在最后會將bitset的每個byte和mask做xor操作(62行)。

  44行和46行的兩個if對出現在character class開頭的"-"和"]"字符當作常規字符處理。chset()函數傳入一個ASCII字符,將一個臨時bitset的對應bit置為1:

static void chset(CHAR c) {bittab[(CHAR) ((c) & BLKIND) >> 3] |= bitarr[(c) & BITIND]; }

  chset()函數中涉及的全局變量和宏定義如下:

#define MAXCHR 128 #define CHRBIT 8 #define BITBLK MAXCHR/CHRBIT #define BLKIND 0170 #define BITIND 07static CHAR bittab[BITBLK]; static CHAR bitarr[] = {1,2,4,8,16,32,64,128};

  bittab是一個臨時bitset,程序在處理character class時首先將bit信息寫入bittab,最后再將bittab寫入nfa作為?CCL?的operand。

  MAXCHAR是一個ASCII字符所需的bitset空間,BITBLK是一個bitset的字節大小,即MAXCHR/CHRBIT = 128 / 8 = 16字節。

  回到case ']'的代碼。48行的while循環處理方括號內的字符。49行判斷S-E形式,51行獲取S的ASCII,之所以要加1(c1 = p[-2] + 1)是因為在上一次循環中已經將S字符的bit置為1了,不再需要置1。52行獲取E字符的ASCII,53行的while循環將S~E字符區間的bit全部置為1。

  56行處理除了S-E形式以外的常規字符。

  61行將bittab的每個字節和mask做xor操作后寫入nfa。

65 case '*': /* match 0 or more.. */ 66 case '+': /* match 1 or more.. */ 67 if (p == pat) 68 return badpat("Empty closure"); 69 lp = sp; /* previous opcode */ 70 if (*lp == CLO) /* equivalence.. */ 71 break; 72 switch(*lp) { 73 case BOL: 74 case BOT: 75 case EOT: 76 case BOW: 77 case EOW: 78 case REF: 79 return badpat("Illegal closure"); 80 default: 81 break; 82 } 83 84 if (*p == '+') 85 for (sp = mp; lp < sp; lp++) 86 store(*lp); 87 88 store(END); 89 store(END); 90 sp = mp; 91 while (--mp > lp) 92 *mp = mp[-1]; 93 store(CLO); 94 mp = sp; 95 break;

  65行和66行的兩個case處理closure。69行將上一個opcode的指針賦值給lp。

  70行判斷如果出現了兩個連續的closure(x**的形式),那么會忽略當前closure。

  72行的switch限制closure所包含的內容,必須為常規字符、"."、character class或closure。

  84行將x+形式轉換為xx*形式,即將上一個opcode和operand復制一遍(lp ~ mp - 1)。

  88行和89行添加兩個?END?,其中一個是為了后面插入?CLO?預留空間用的。90行~94行將上一個opcode后移一字節,并在空出的位置插入?CLO?

96 case '\\': /* tags, backrefs .. */ 97 switch(*++p) { 98 case '(': 99 if (tagc < MAXTAG) { 100 tagstk[++tagi] = tagc; 101 store(BOT); 102 store(tagc++); 103 } 104 else 105 return badpat("Too many \\(\\) pairs"); 106 break; 107 case ')': 108 if (*sp == BOT) 109 return badpat("Null pattern inside \\(\\)"); 110 if (tagi > 0) { 111 store(EOT); 112 store(tagstk[tagi--]); 113 } 114 else 115 return badpat("Unmatched \\)"); 116 break;

  98行的case處理"\("。100行將當前group counter值壓入tagstk,然后生成?BOT tagc?。

  107行處理"\)"。108行的if防止出現空的group。111行和112行生成?EOT x?,并從tagstk中彈出原group counter值。

117 case '<': 118 store(BOW); 119 break; 120 case '>': 121 if (*sp == BOW) 122 return badpat("Null pattern inside \\<\\>"); 123 store(EOW); 124 break;

  上面的兩個case處理"\<"和"\>"。121行的if防止出現空的單詞(\<\>)。

125 case '1': case '2': case '3': case '4': case '5': 126 case '6': case '7': case '8': case '9': 127 n = *p-'0'; 128 if (tagi > 0 && tagstk[tagi] == n) 129 return badpat("Cyclical reference"); 130 if (tagc > n) { 131 store(REF); 132 store(n); 133 } 134 else 135 return badpat("Undetermined reference"); 136 break;

  處理group引用。128行防止出現循環引用(當前正位于某個group中時對這個group進行引用)。

  130行的if防止引用尚未存在的group。

137 default: 138 store(CHR); 139 store(*p); 140 } 141 break;

  對于"\x"中x的其他情況,直接生成?CHR x?。

142 default : /* an ordinary char */ 143 store(CHR); 144 store(*p); 145 break; 146 }

  如果*p是常規字符,生成?CHR x?。

147 if (tagi > 0) 148 return badpat("Unmatched \\("); 149 store(END);150 return 0; 151 }

  在主循環結束后,判斷表達式中的\(和\)是否匹配(tagi == 0)。最后向nfa寫入一個?END?

?

2.2 匹配

  在生成NFA后,就可以用這個NFA對目標字符串進行匹配。

  

  這里要對NFA分三種情況:

  1) 開頭是?BOL?。此時僅在字符串開頭使用整個NFA進行一次匹配;

  

  2) 開頭是?CHR x?。此時需要在字符串中找到字符x第一次出現的位置,然后從這個位置開始使用NFA進行匹配,如果匹配失敗則從下一個位置開始使用NFA匹配;

  

  3) 其他情況。從字符串開頭開始使用NFA匹配,若匹配失敗則從字符串的第二個字符開始使用NFA匹配,以此類推。

  closure的處理

  closure要盡可能多地匹配符合條件的字符,因此要先跳過所有匹配的字符,從第一個不匹配的字符開始用剩余的NFA進行匹配,若匹配失敗則向前移動一個字符,繼續使用NFA匹配。

  

  函數re_exec()接受一個字符串,使用全局nfa進行匹配。若匹配成功則返回非0,并將所有匹配的group的start offset和end offset放入全局變量:

static char *bol; char *bopat[MAXTAG]; char *eopat[MAXTAG];

  bopat和eopat分別保存group的start offset和end offset。其中group 0是整個匹配的字符串。bol在匹配的過程中保存字符串地址。

1 int re_exec(char *lp) { 2 CHAR c; 3 char *ep = 0; 4 CHAR *ap = nfa; 5 6 bol = lp; 7 8 memset(bopat, 0, sizeof (char *) * MAXTAG); 9 10 switch(*ap) { 11 case BOL: /* anchored: match from BOL only */ 12 ep = pmatch(lp,ap); 13 break; 14 case CHR: /* ordinary char: locate it fast */ 15 c = *(ap+1); 16 while (*lp && *lp != c) 17 lp++; 18 if (!*lp) /* if EOS, fail, else fall thru. */ 19 return 0; 20 default: /* regular matching all the way. */ 21 do { 22 if ((ep = pmatch(lp,ap))) 23 break; 24 lp++; 25 } while (*lp); 26 break; 27 case END: /* munged automaton. fail always */ 28 return 0; 29 } 30 if (!ep) 31 return 0; 32 33 bopat[0] = lp; 34 eopat[0] = ep; 35 return 1; 36 }

  10行的switch處理nfa的3種情況。如果nfa第一個opcode是?BOL?,從字符串開頭進行一次匹配。pmatch()函數是使用NFA匹配字符串的核心函數,它返回匹配的字符串的end offset。如果第一個opcode是?CHR x?,16行的while將找到x字符第一次出現的位置,之后和第三種情況一樣處理。其他情況下,21行的do-while循環將逐個以字符串的每個字符開始使用NFA匹配。在匹配完后,將start offset和end offset分別保存到bopat[0]和eopat[0]。

1 static char *pmatch(char *lp, CHAR *ap) { 2 int op, c, n; 3 char *e; /* extra pointer for CLO */ 4 char *bp; /* beginning of subpat.. */ 5 char *ep; /* ending of subpat.. */ 6 char *are; /* to save the line ptr. */ 7 8 while ((op = *ap++) != END) 9 switch(op) { 10 case CHR: 11 if (*lp++ != *ap++) 12 return 0; 13 break; 14 case ANY: 15 if (!*lp++) 16 return 0; 17 break; 18 case CCL: 19 c = *lp++; 20 if (!isinset(ap,c)) 21 return 0; 22 ap += BITBLK; 23 break;

  8行的循環遍歷整個nfa,并根據不同的opcode做不同處理。?CHR x?的處理是直接對*lp和x進行判斷。?ANY?匹配任意字符,因此只需要判斷字符串中是否有剩余字符。?CCL bitset?需要判斷字符在bitset中對應bit是否為1,使用isinset這個宏實現:

#define isinset(x,y) ((x)[((y)&BLKIND)>>3] & bitarr[(y)&BITIND])

  22行跳過bitset所占用的nfa空間。

24 case BOL: 25 if (lp != bol) 26 return 0; 27 break; 28 case EOL: 29 if (*lp) 30 return 0; 31 break;

  ?BOL?和?EOL?的處理很簡單,只要判斷lp是否是字符串首地址或末尾。

32 case BOT: 33 bopat[*ap++] = lp; 34 break; 35 case EOT: 36 eopat[*ap++] = lp; 37 break;

  ?BOT n?和?EOT n?分別將當前的字符串指針寫入bopat和eopat數組。

38 case BOW: 39 if (lp!=bol && iswordc(lp[-1]) || !iswordc(*lp)) 40 return 0; 41 break; 42 case EOW: 43 if (lp==bol || !iswordc(lp[-1]) || iswordc(*lp)) 44 return 0; 45 break;

  ?BOW?成功的條件是上一個字符是非單詞字符(或沒有上一個字符,即位于字符串開頭)并且當前字符是單詞字符。iswordc()宏判斷某個字符是否為單詞字符。

  ?EOW?成功的條件是前一個字符是單詞字符且當前字符是非單詞字符,如果當前位于字符串開頭那么判斷也將失敗。

  這兩個opcode都不匹配任何字符,他們只是匹配字符的邊界:

  

46 case REF: 47 n = *ap++; 48 bp = bopat[n]; 49 ep = eopat[n]; 50 while (bp < ep) 51 if (*bp++ != *lp++) 52 return 0; 53 break;

  ?REF n?先從bopat和eopat取出group n的start offset和end offset,對字符串中的每個字符逐個與start offset ~ end offset中的字符做比較。

54 case CLO: 55 are = lp; 56 switch(*ap) { 57 58 case ANY: 59 while (*lp) 60 lp++; 61 n = ANYSKIP; 62 break; 63 case CHR: 64 c = *(ap+1); 65 while (*lp && c == *lp) 66 lp++; 67 n = CHRSKIP; 68 break; 69 case CCL: 70 while ((c = *lp) && isinset(ap+1,c)) 71 lp++; 72 n = CCLSKIP; 73 break; 74 default: 75 re_fail("closure: bad nfa.", *ap); 76 return 0; 77 } 78 79 ap += n; 80 81 while (lp >= are) { 82 if (e = pmatch(lp, ap)) 83 return e; 84 --lp; 85 } 86 return 0;

  ?CLO?的處理比較復雜。首先使用臨時變量are保存當前字符串指針lp。接下來對closure包含的內容的三種不同情況分別處理。

  對于?ANY?,將直接把lp移動到字符串末尾。將ANYSKIP(值為2)賦給n,n是nfa指針ap將跳過的字節數(79行)。這種情況下NFA的opcode序列如下:

  ?CLO??ANY??END??...

  此時ap指向?ANY?,需要跳過2個字節才能移動到下一個opcode,因此ANYSKIP值為2。

  對于?CHR x?,跳過所有字符x。CHRSKIP值為3(?CLO??CHR??x??END?)。

  對于?CCL bitset?,跳過所有位于bitset中的字符。CCLSKIP值為18(?CLO??CCL??bitset (16 bytes)??END?)。

  81行從當前lp開始使用剩余的NFA遞歸調用pmatch()匹配,并不斷向前移動lp指針,直到lp小于are指針。

87 default: 88 re_fail("re_exec: bad nfa.", op); 89 return 0; 90 } 91 return lp; 92 }

  最后返回當前lp指針,即最后一個匹配的字符的下一個字符的位置。

轉載于:https://www.cnblogs.com/plodsoft/p/5853945.html

總結

以上是生活随笔為你收集整理的简易正则表达式引擎源码阅读的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 免费成人黄色网 | 日本三级2019| 亚洲无码精品在线播放 | 色噜噜日韩精品欧美一区二区 | 丝袜制服一区 | 丝瓜av| 火影忍者羞羞漫画 | 成年人免费网址 | 超碰免费av | 在线观看免费观看在线 | 欧美s码亚洲码精品m码 | 香蕉国产精品视频 | 日韩久久一区二区 | 久久传媒 | 小视频在线看 | 黑人狂躁日本妞hd | 国产成人综合自拍 | 91n在线观看 | 欧美无遮挡高潮床戏 | 精品九九九九 | 日韩视频国产 | 韩日精品视频 | 黄片毛片在线看 | 国产又爽又猛又粗的视频a片 | 女人裸体免费网站 | 小俊大肉大捧一进一出好爽 | 自拍偷拍电影 | 日韩福利视频导航 | 国产乱淫精品一区二区三区毛片 | 国产91丝袜在线播放九色 | 亚洲熟妇无码一区二区三区 | 男女黄床上色视频 | 天天爽一爽 | 乱色专区| 欧美色频| 色5566| 一级草逼片| 韩国美女av | 91免费福利 | 中文字幕乱码中文乱码b站 国产一区二区三区在线观看视频 | 国产男同gay网站 | 日本免费一区二区三区视频 | 日日爽夜夜| 奇米狠狠777 | 超碰成人网 | 国产精品sm | 奇米影视狠狠 | 先锋影音一区二区三区 | 欧日韩不卡在线视频 | 在线黄网站 | 无遮挡毛片 | 成人欧美一区二区三区黑人 | 国产精品视频一区在线观看 | 欧美黄色a级大片 | 国产专区在线 | 自拍偷拍精品视频 | 天天综合影院 | 日韩高清一区二区 | 精品av一区二区 | 日本亲与子乱ay中文 | 免费人成在线观看 | 黄色不卡| 先锋影音av资源站 | 黄色aa大片 | 91精品黄色| 亚洲一区二区三区久久 | 自拍偷拍欧美激情 | 青青草国产一区二区三区 | 在线视频a | 午夜在线一区二区三区 | 国产精品久久国产精麻豆96堂 | 久久五月天综合 | 麻豆av网址 | 亚洲一区二区国产精品 | 国产福利网 | 国产精品精品软件视频 | 午夜一区| 一道本在线观看视频 | 妹子干综合 | 97国产资源 | 一级美女大片 | 国产成人无码精品久久久电影 | 亚洲爆乳无码精品aaa片蜜桃 | 秋霞一级视频 | 国产国语老龄妇女a片 | 一级激情片 | 杂技xxx裸体xxxx欧美 | aa亚洲 | 日韩videos| 操女人视频网站 | 日韩理论片在线观看 | 婷婷亚洲视频 | 成人在线视频免费看 | www.狠狠艹 | 日韩色图在线观看 | 国产亚洲一区二区不卡 | 欧美a级成人淫片免费看 | 亚洲精品视频中文字幕 | 狠狠摸狠狠操 |