正则表达式引擎

一、前言

1.1 正则表达式

正则表达式的概念最开始是由 Unix 中的工具软件 (如 sed 和 grep) 普及开的。正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的
组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。给定一个正则表达式和另一个字符串,我们可以达到如下的目的:

  • 给定的字符串是否符合正则表达式的过滤逻辑(称作“匹配”);
  • 可以通过正则表达式,从字符串中获取我们想要的特定部分。

正则表达式的特点是:

  1. 灵活性、逻辑性和功能性非常的强;
  2. 可以迅速地用极简单的方式达到字符串的复杂控制。

由于正则表达式主要应用对象是文本,因此它在各种文本编辑器场合都有应用,小到著名编辑器 EditPlus,大到 Microsoft Word、Visual Studio 等大型编辑器,都可以使用正则表达式
来处理文本内容。[1]

正则表达式是一种文本模式,包含普通字符和特殊字符,其中特殊字符又称为“元字符”,元字符完整列表参见 正则表达式列表。可以将元字符分为三类:

  • 普通字符集合:通过标记表示多种普通字符,例如‘.’代表除换行符以外的其他任意字符。
  • 定界字符:表示位置的一类字符,用于边界检测,例如‘%blatex%b’是用来匹配单词latex 的,是一种全词匹配。
  • 控制字符:控制重复、分支以及分组的一类字符,例如‘*’表示重复 0 至任意次。

  文本模式就是通过这些元字符、普通字符的组合来表示,使用这些字符可以以很简洁的形式表示某种字符串结构。正则表达式的作用不仅仅局限在文本的搜索匹配上,在网络安全 [2]、数据库查询 [3]、协议识别 [4] 等领域都有应用。

1.2 贪婪匹配

贪婪算法又称为贪心法,是一种比较简单的算法。与动态规划法类似,贪心法常用于求解问题的最优解。贪心法在解决问题的策略上是根据当前的已知信息做出决策,并且决策之后就不再改变。可见,贪心法并非是从全局最优考虑,而是局部最优,这种局部最优不能保证所求出的解是全局最优解,但是一般情况下是近似最优解。能用贪心法求得最优解的问题通常具有以下两种性质:

  • 最优子结构。当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构。
  • 贪心选择性质。全局最优解可以通过一系列的局部最优解的选择来得到。

使用贪心法解决的典型问题有排课问题 [5]、背包问题 [6] 和多机调度问题 [7]。

1.3 说明

  1. 仅仅要求匹配 ASCII 中的可显示字符。
  2. 不需要处理表意冲突的情况。
  3. 存在多个输出结果时, 仅仅匹配第一个输出结果。
  4. 不要求能识别错误的正则表达式。

二、算法设计

设计要求实现元字符和反义字符的匹配、重复匹配、分支匹配、分组匹配这几项基本功能。其中分组匹配只要求实现后项引用,可以使用字符串替换进行处理,此外,分支匹配也可以转换成多个标准模式字符串,那么实现标准模式字符串的匹配过程,就实现了整个模式字符串的匹配。其中标准模式字符串的定义如下:

定义 2.1 (标准模式字符串) 模式字符串中,不存在分支及分组,并且不存在多余的括号,括号只能在跟有重复项的情况下出现,这样的字符串称为标准模式字符串。

在对原始模式字符串的处理过程中,首先进行文本的预处理过程,预处理是为了得到标准模式字符串,然后进行标准模式字符串的匹配过程,其中预处理过程主要是字符串的处理过程。整体流程如下图所示:

字符串匹配以标准模式字符串为界,前面一部分是字符串的转换过程,后一部分是匹配过程。下面将按照处理流程,分别阐述分组处理、分支处理、标准格式匹配三个步骤:

2.1 分组处理

正则表达式的分组使用一对括号‘(’、‘)’表示一个子表达式,这个子表达式为一个分组。每个分组都会分配一个组号,分配的规则为:从左向右, 以分组的左括号为标志, 第一个出现的分组的组号为 1, 第二个为 2, 以此类推。后向引用用于重复搜索前面某个分组匹配的文本。要得到无分组引用的模式字符串,需要将引用的子表达式替换掉引用记号。实现这一功能,需要分两步进行:

  1. 提取分组。从原始字符串中,提取出个子表达式,并且按照组号进行排列。
  2. 替换。用子表达式替换所有的引用记号。

提取文本中的分组是一种括号匹配问题,对于这类问题通常使用栈实现。实现算法如下:

struct Slice{
    int index;
    string value;
    int ter;
    int start;
};
typedef vector<Slice> VecSlice;

VecSlice getSliceOfPattern(string s, VecInt & vertex) {
    VecSlice vSlice;
    string value;
    value = "";
    Slice slice;
    stack<int> bra;
    int index = 0;
    vertex.push_back(-1);
    while (index < s.size()) {
        if (isSeparator(s[index])) {
            slice.value = value;
            value = "";
            slice.ter = index;
            slice.start = index - slice.value.size() - 1;
            vertex.push_back(index);
            switch (s[index]) {
            case ‘(‘:
                vSlice.push_back(slice);
                slice.index = index;
                bra.push(index);
                break;
            case ‘)‘:
                vSlice.push_back(slice);
                slice.index = index;
                bra.pop();
                break;
            case ‘|‘:
                vSlice.push_back(slice);
                if (bra.size() == 0)
                    slice.index = -1;
                else
                    slice.index = bra.top();
                break;
            }

        } else
            value = value + s[index];
        index++;
    }
    slice.value = value;
    slice.ter = index;
    slice.start = slice.ter - value.size() - 1;
    vSlice.push_back(slice);
    vertex.push_back(index);
    for (uint i = 0; i < vSlice.size(); ++i) {
        vSlice[i].index = findInVertex(vSlice[i].index, vertex);
        vSlice[i].start = findInVertex(vSlice[i].start, vertex);
        vSlice[i].ter = findInVertex(vSlice[i].ter, vertex);
    }

    return vSlice;
}

取得模式字符串中的分组后,下面就是引用的替换了,替换过程比较简单,首先查找引用标记,然后用相应的子表达式做替换,如此循环替换下去,直到模式字符串中不再有引用标记为止。

2.2 分支处理

前面一节已经完成了分组处理的过程,下面将需要进行分支处理。对于一个如下形式的模式字符串:

ab((c|d)e|f )g                   (2.1)

将其转换成状态图形式如下(此处不同考虑重复情况,括号后面跟有重复定义时,不会改变其重复意义):

为了便于描述,定义位置的索引如下图所示:

将模式字符串分成几段,其分段列表如下:

注意:分支中的各分支的起始位置与终止位置相同。

可以看到,将各段按起始位置有序连接,就是图 2.2 的简化版。以图论的观点看,这是有向图无环图(DAG,) 找路径的问题 [8]。但是相对于一般的有向无环图来说,它具有一些特性:

  1. 只有两个顶点没有出度或者入度,其他的顶点都有出度和入度,并且只有出度的顶点是起始点,只有入度的顶点是终点。
  2. 此图不可分割。图中各顶点都连接在一起,是一个连通图。
  3. 此图中的每一个顶点都可以通过特定的路径到达终点。

上面一个例子的 DAG 图如下:

使用邻接表存储,则此 DAG 图的邻接表的结构如下图所示:

以这个图为例,算法处理过程如下表所示:

上述的处理过程,需要对访问的节点进行标记,对于已经回退的节点需要及时清除标记。

2.3 标准模式匹配

前面两节已经进行了预处理过程,已经得到了标准模式字符串的列表。这部分将对标准模式匹配过程进行阐述,标准模式匹配分模式字符串解析和字符串检测两部分,下面将分这两
部分描述。

2.3.1 模式字符串解析

在解析模式字符串之前先剔除不必要的括号,这个过程较为简单,在此略过。分析元字符,将其分成几大类,在头文件中定义其类型码:

1 //模式类型码
2 #define     PATTERN_NORMAL        11    //\w  \s  \d  [x]
3 #define    PATTERN_NOT        12    //\W \S \D [^x] ‘.‘
4 #define    PATTERN_REPEAT        13    //重复类型
5 #define     PATTERN_POS_WORD    14    //\b
6 #define     PATTERN_POS_ENDLINE    15    //$
7 #define     PATTERN_POS_BENGINLINE    16    //^
8 #define     PATTERN_POS_NOTWORD    17    //\B

对于普通字字符、普通类型、取反类型的字符,表示的是一类字符,将使用字符区间表示可取的字符,其形式如 [[a-z][A-Z]......]。定义字符区间如下:

1 //可行域,即字符区间 [c_s,c_e]
2 struct CharInterval {
3 public:
4     char c_s;//起始
5     char c_e;//终止
6 };
7 typedef list<CharInterval> CInterval;

对于重复类型,需要指定重复的起始位置、终止位置、最小重复次数、最大重复次数。其中重复类型 *、+ 的最大重复次数是任意次数,用 -1 指定。综合以上考虑,定义模式单元数
据结构如下:

 1 struct PNode {
 2 public:
 3     CInterval charInterval;//当type=PATTERN_POS/PATTERN_REPEAT时为空
 4     int type;//类型 PATTERN_NORMAL/PATTERN_NOT/PATTERN_POS/PATTERN_REPEAT
 5     int repeat_min;//最小次数
 6     int repeat_max;//最大次数
 7     int repeat_s;//重复起点
 8     int repeat_e;//重复终点
 9     int repeat_level;//记录重复次数,在匹配时使用
10     PNode() {
11         this->charInterval = CInterval();
12         this->type = PATTERN_NORMAL;
13         this->repeat_min = this->repeat_max = 0;
14         this->repeat_e=this->repeat_s=0;
15         this->repeat_level=-1;
16     }
17 };

定义好数据结构,接下来就需要进行模式字符串的解析。模式字符串的解析, 采用预测分析的方法进行解析:

  1 uint index = 0;
  2     stack<int> stBraket;
  3     int bra, ket;
  4     bra = -1;
  5     ket = -1;
  6     while (index < regtxt.size()) {
  7         PNode pNode;
  8         switch (regtxt[index]) {
  9         case ‘^‘:
 10                 .......
 11         case ‘$‘:
 12             .......
 13             //小括号
 14         case ‘(‘:
 15             .......
 16         case ‘)‘:
 17            .......
 18             //中括号
 19         case ‘[‘:
 20            .......
 21             //大括号
 22         case ‘{‘:
 23             pNode.type = PATTERN_REPEAT;
 24             if (regtxt[index - 1] == ‘)‘) {
 25                 pNode.repeat_s = bra;
 26                 pNode.repeat_e = ket;
 27             } else {
 28                 pNode.repeat_e = vPattern.size() - 1;
 29                 pNode.repeat_s = pNode.repeat_e;
 30             }
 31             bra = -1;
 32             ket = -1;
 33             index++;
 34             int num;
 35             num = 0;
 36             while (regtxt[index] <= ‘9‘ && regtxt[index] >= ‘0‘) {
 37                 num = num * 10 + (regtxt[index] - ‘0‘);
 38                 index++;
 39             }
 40             pNode.repeat_min = num;
 41             num = 0;
 42             switch (regtxt[index]) {
 43             case ‘,‘:
 44                 index++;
 45                 if (regtxt[index] == ‘}‘) {
 46                     pNode.repeat_max = -1;
 47                 } else {
 48                     while (regtxt[index] <= ‘9‘ && regtxt[index] >= ‘0‘) {
 49                         num = num * 10 + (regtxt[index] - ‘0‘);
 50                         index++;
 51                     }
 52                     pNode.repeat_max = num;
 53                 }
 54                 break;
 55             case ‘}‘:
 56                 pNode.repeat_max = pNode.repeat_min;
 57                 break;
 58             }
 59             vPattern.push_back(pNode);
 60             break;
 61         case ‘*‘:
 62             pNode.type = PATTERN_REPEAT;
 63             pNode.repeat_min = 0;
 64             pNode.repeat_max = -1;
 65
 66             if (regtxt[index - 1] == ‘)‘) {
 67                 pNode.repeat_s = bra;
 68                 pNode.repeat_e = ket;
 69             } else {
 70                 pNode.repeat_e = vPattern.size() - 1;
 71                 pNode.repeat_s = pNode.repeat_e;
 72             }
 73             bra = -1;
 74             ket = -1;
 75             vPattern.push_back(pNode);
 76             break;
 77         case ‘?‘:
 78             pNode.type = PATTERN_REPEAT;
 79             pNode.repeat_min = 0;
 80             pNode.repeat_max = 1;
 81
 82             if (regtxt[index - 1] == ‘)‘) {
 83                 pNode.repeat_s = bra;
 84                 pNode.repeat_e = ket;
 85             } else {
 86                 pNode.repeat_e = vPattern.size() - 1;
 87                 pNode.repeat_s = pNode.repeat_e;
 88             }
 89             bra = -1;
 90             ket = -1;
 91             vPattern.push_back(pNode);
 92             break;
 93         case ‘+‘:
 94             ......
 95         case ‘.‘:
 96             .......
 97         case ‘\\‘://元字符
 98             index++;
 99             switch (regtxt[index]) {
100             case ‘w‘:
101                 ........
102             case ‘W‘:
103                 ........
104             case ‘s‘:
105                .........
106             case ‘S‘:
107                 ........
108             case ‘d‘:
109                 .........
110             case ‘D‘:
111                 ........
112             case ‘b‘:
113                 .......
114             case ‘B‘:
115                .....
116             default:
117                 string s;
118                 s = "出现不能处理的元字符\\" + regtxt[index];
119                 throw SelfException(s);
120                 break;
121             }
122             break;
123         default://常规字符
124             pNode.type = PATTERN_NORMAL;
125             string s;
126             s = regtxt[index];
127             pNode.charInterval = getCharInterval(s);
128             vPattern.push_back(pNode);
129             break;
130         }
131         index++;
132     }
133     sort(this->vRepeat.begin(), this->vRepeat.end(), greaterRepeat);    

2.3.2 字符串的检测

字符串的检测首先要解决的问题是重复项的匹配问题,要求使用贪婪匹配,则从最长重复次数到最短重复次数进行遍历检测。以最外层重复子表达式为界,将表达式分为前中后三部分。字符串的前部则不存重复项,只需依次检测即可。中间部分是重复单元,对其进行遍历检测,在每一次循环中,都是一个标准模式字符串,同样将其分为前中后三部分,如此进行下去,最后分块的结果是只有前部,中间部分、及后部都为空。后部的处理跟中间部分类似。容易知道解决这种问题的最简单的方法是使用递归。

定义一个函数处理模式序列的分块,其定义如下:

 1 void Scanner::splitPattern(VecPattern vSource, VecPattern& pre,
 2         VecPattern& mid, VecPattern& post) {
 3     int len;
 4     len = vSource.size();
 5     VecInt flag;
 6     for (int i = 0; i < len; ++i) {
 7         flag.push_back(0);
 8     }
 9     for (int i = 0; i < len; ++i) {
10         if (vSource[i].type == PATTERN_REPEAT) {
11             for (int j = vSource[i].repeat_s; j <= vSource[i].repeat_e; ++j) {
12                 flag[j] = flag[j] + 1;
13             }
14         }
15     }
16     //前部
17     int i;
18     int k;
19     i = 0;
20     while (i < len) {
21         if (flag[i] == 0)
22             pre.push_back(vSource[i]);
23         else
24             break;
25         i++;
26     }
27     k = i;
28     while (i < len) {
29         if (flag[i] == 0)
30             break;
31         else
32             mid.push_back(vSource[i]);
33         i++;
34     }
35     if(mid.size()>0){
36         mid.push_back(vSource[i]);
37         i++;
38     }
39     for (uint i = 0; i < mid.size(); ++i) {
40         if (mid[i].type == PATTERN_REPEAT) {
41             mid[i].repeat_e = mid[i].repeat_e - k;
42             mid[i].repeat_s = mid[i].repeat_s - k;
43         }
44     }
45     k = i;
46     while (i < len) {
47         post.push_back(vSource[i]);
48         i++;
49     }
50     for (uint j = 0; j < post.size(); ++j) {
51         if (post[j].type == PATTERN_REPEAT) {
52             post[j].repeat_e = post[j].repeat_e - k;
53             post[j].repeat_s = post[j].repeat_s - k;
54         }
55     }
56 }

前部字符串检测函数定义如下:

 1 bool Scanner::pvsMatch(int & beg, VecPattern vPat, string s) {
 2     if (vPat.size() == 0)
 3         return true;
 4     if (beg >= s.size())
 5         return false;
 6     uint cache;
 7     cache = beg;
 8     uint flag;
 9     flag = 0;
10     while ( flag < vPat.size()) {
11         bool bBreak;
12         bBreak = false;
13         switch (vPat[flag].type) {
14         case PATTERN_NORMAL:
15             if(cache >= s.size())
16                 return false;
17             if (isInRange(s[cache], vPat[flag].charInterval)) {
18                 cache++;
19                 flag++;
20             } else {
21                 return false;
22             }
23             break;
24         case PATTERN_NOT:
25             if(cache >= s.size())
26                     return false;
27             if (isInRange(s[cache], vPat[flag].charInterval)) {
28                 return false;
29             } else {
30                 cache++;
31                 flag++;
32             }
33             break;
34         case PATTERN_POS_BENGINLINE:
35             if (isBeginOfLine(cache, s)) {
36                 flag++;
37             } else
38                 return false;
39             break;
40         case PATTERN_POS_ENDLINE:
41             if (cache==s.size())
42                 flag++;
43             else if(s[cache]==‘\n‘) {
44                 flag++;
45             } else
46                 return false;
47             break;
48         case PATTERN_POS_NOTWORD:
49             if (isWordBoder(cache, s)) {
50                 return false;
51             } else
52                 flag++;
53             ;
54             break;
55         case PATTERN_POS_WORD:
56             if (isWordBoder(cache, s)) {
57                 flag++;
58             } else
59                 return false;
60             break;
61         case PATTERN_REPEAT:
62             throw SelfException("此处不能处理重复项");
63             break;
64         default:
65             throw SelfException("未知类型项,检查type赋值语句");
66             break;
67         }
68     }
69     if(flag==vPat.size())
70         beg = cache;
71     return true;
72 }

中部匹配函数定义如下:

 1 bool Scanner::midMatch(int & beg, int min,int max, VecPattern vP, string s) {
 2     if(vP.size()==0)
 3         return true;
 4     int l,i;
 5     int first;
 6     first=beg;
 7     if(max==-1)
 8         l=s.size()-beg;
 9     else
10         l=max;
11     while(l>=min){
12         for(i=0;i<l;++i)
13             if(!postMatch(first,vP,s))
14                 break;
15         if(i==l)
16         {
17             beg=first;
18             return true;
19         }
20         else
21             first=beg;
22         l--;
23     }
24     if(l<min)
25         return false;
26     return false;
27 }

后部匹配过程定义如下:

 1 bool Scanner::postMatch(int & beg, VecPattern vP, string s) {
 2     int first=beg;
 3     if(vP.size()==0)
 4         return true;
 5     VecPattern vPvs, vPost, vMid;
 6     //TODO 分块    vPvs,vPost,vMid
 7     splitPattern(vP,vPvs,vMid,vPost);
 8     if(vMid.size()==0){
 9         if(pvsMatch(first,vPvs,s)){
10             beg=first;
11             return true;
12         }
13     }
14     else
15     if(pvsMatch(first,vPvs,s)){
16         PNode p(vMid.back());
17         vMid.pop_back();
18         if(midMatch(first,p.repeat_min,p.repeat_max,vMid,s))
19         {
20             if(postMatch(first,vPost,s))
21             {
22                 beg=first;
23                 return true;
24             }
25             else
26                 return false;
27         }
28         else
29             return false;
30     }else
31         return false;;
32 }

上述三个程序段分别定义了三个部分的匹配过程,其中 postMatch 是函数调用的起点,然后进行递归调用。这样就实现了标准模式字符串的匹配过程。

时间: 2024-11-10 00:53:07

正则表达式引擎的相关文章

基于ε-NFA的正则表达式引擎

正则表达式几乎每个程序员都会用到,对于这么常见的一个语言,有没有想过怎么去实现一个呢?乍想一下,也许觉得困难,实际上实现一个正则表达式的引擎并没有想像中的复杂,<编译原理>一书中有一章专门讲解了怎么基于状态机来构建基本的正则表达式引擎,讲这个初衷是为词法分析服务,不过书里的东西相对偏理论了些,实现起来还是要费些功夫的,只是它到底指明了一条路,当然,书里只针对基本的语法进行了分析讲解,对于在实际中有些非常有用的很多扩展语法,它就基本没有涉及了,这些扩展的语法中有些是比较好实现的,有些则很难. 基

1000行代码徒手写正则表达式引擎【1】--JAVA中正则表达式的使用

简介: 本文是系列博客的第一篇,主要讲解和分析正则表达式规则以及JAVA中原生正则表达式引擎的使用.在后续的文章中会涉及基于NFA的正则表达式引擎内部的工作原理,并在此基础上用1000行左右的JAVA代码,实现一个支持常用功能的正则表达式引擎.它支持贪婪匹配和懒惰匹配:支持零宽度字符(如"\b", "\B"):支持常用字符集(如"\d", "\s"等):支持自定义字符集("[a-f]","[^b-

简易正则表达式引擎的实现

正则表达式基本每个程序员都会用到,实现正则表达式引擎却似乎是一个很难的任务.实际上,掌握<编译原理>前端的词法分析部分知识就能够实现一个简单的正则表达式引擎.这里推荐一下网易云课堂的课程.http://mooc.study.163.com/course/USTC-1000002001?tid=1000003000#/info 基本的正则表达式  正则表达式由字符与元字符组成,整个表达式用于描述符合某些特定特征的一类字符串,比如说表达式:abc,它表示 "abc" 这个字符串

Python的regex模块——更强大的正则表达式引擎(未完待写)

Python自带了正则表达式引擎(内置的re模块),不过支持的特性比较单薄,以下这些特性都不支持: 固化分组    Atomic grouping 占有优先量词    Possessive quantifiers 可变长逆序环视    Variable-length lookbehind 递归匹配    Recursive patterns Matthew Barnett为Python写了一个更强大正则表达式引擎——regex模块,可以替代内置的re模块.除了上面说到的几个特性,还有很多新东西.

实现一个 DFA 正则表达式引擎 - 4. DFA 的最小化

(正则引擎已完成,Github) 最小化 DFA 是引擎中另外一个略繁琐的点(第一个是构建语法树). 基本思路是,先对 DFA 进行重命名,然后引入一个拒绝态 0,定义所有状态经过非接受字符转到状态 0,0 接受所有字符转换为自身.也就是说我们先建立一个转换表,然后把第一行填写为: state a b c d e f g h ... 0 0 0 0 0 0 0 0 0 0 再之后,我们讲 DFA 的其余状态从 1 开始重命名,填入状态表.代码实现如下: // rename all states

DEELX 正则表达式引擎(v1.2)

DEELX 正则表达式引擎(v1.2) 简介见文末. 选择使用deelx的理由:全部代码位于一个头文件(.h)中, 比任何引擎都使用简单和方便. 利用分组从字符串当中提取出化学元素英文名.比如 Ag,Au,Fe3O4,Al2O3,K2O,CaO...... 1 #include "deelx.h" 2 #include <string> 3 //该正则表达式识别字符串当中是否有化学元素英文名. 4 //特征:首字母大写,由英文字符和数字组成. 5 //示例:数学变换_Ag_

(2015大作业)茹何优雅的手写正则表达式引擎(regular expression engine

貌似刚开学的时候装了个逼,和老师立了个flag说我要写个正则表达式引擎,然后学期末估计老师早就忘了这茬了,在历时3个月的懒癌发作下,终于在这学期末deadline的时候花了一个下午加晚上在没有网的房间写完了它,于是便有了这篇blog,本来想正儿八紧写篇论文,说不定毕业设计可以直接丢一篇这个走人,但第一觉得一个晚上写好的东西太low了,第二自己实在不适合写那种正经的论文,于是还是写从高中开始的一贯的乱七八糟体好了. 主要写自己写的时候遇到的一些瓶颈,例如茹何储存一个图,茹何遍历一个图,茹何表示一个

实现一个 DFA 正则表达式引擎 - 1. 语法树的构建

语法树的构建这里分为三步: 1. 补全正则表达式的省略部分(主要是省略的 concat 和 or 连接符)并翻译七个集合字 '\w', '\W', '\s', '\S', '\d', '\D' 和 '.': 2. 转换为逆波兰表达式: 3. 转换为语法树: 这里以正则表达式 (a*b|ab*) 为例,逐步解释构建语法树的过程. 1. 补全正则表达式的省略部分 符合我们要求的正则表达式只有三个正交的运算符,或运算,连接运算,重复量词.这里将正则表达式转换为以上三种运算加上两个括号运算符.转换规则比

实现一个 DFA 正则表达式引擎 - 0. 要求

决定把轮子造的飞起,试着用 JAVA 写个正则引擎. 要求: 1.  纯 DFA,无需支持 捕获组 和 断言: 2.  时间复杂度 O(n): 3. 支持 ASCII 字符集,支持基本语法:支持'\w', '\W', '\s', '\S', '\d', '\D' 和 '.'  七个集合及其他单字符转义,支持 ?, *, +, {x}, {x,}, {x,y} 六种量词,支持集合反转,支持括号. 4. 避免使用第三方工具,编译为无依赖库. (正则引擎已完成,Github)

实现一个 DFA 正则表达式引擎 - 3. NFA 的确定化

我们上一节已经将 NFA 构建出来了,我们的 NFAState 对象的结构实际上是这样的: NFAState { private Set<NFAState> directTable; private Map<Character, Set<NFAState>> transitionMap; private int id; } 其中 transitionMap 是该状态接受 Character 后可以转换的状态的映射表. 那么以初始状态 0 为例,我们是不是可以认为状态 0