实现一个 DFA 正则表达式引擎 - 2. NFA 的构建

语法树如何实现对于之后步骤的繁琐程度有着举足轻重的影响。因为我们已经有了一棵简单优雅的语法树,所以我们的 NFA 很容易就可以构建出来。下面来回顾一下我们拥有的节点种类:

分支节点:Concat, Or, Many

叶子节点:Closure, Char

以下是转换的核心代码:

    public void visit(LChar lChar) {
        NFAState i = stateStack.pop();
        NFAState f = stateStack.pop();
        i.transitionRule(lChar.c, f);
    }

    public void visit(LNull lNull) {
        // do nothing
    }

    public void visit(BOr bOr) {
        NFAState i = stateStack.pop();
        NFAState f = stateStack.pop();
        stateStack.push(f);
        stateStack.push(i);
        stateStack.push(f);
        stateStack.push(i);
    }

    public void visit(BConcat bConcat) {
        NFAState i = stateStack.pop();
        NFAState f = stateStack.pop();
        NFAState n = newState();
        stateStack.push(f);
        stateStack.push(n);
        stateStack.push(n);
        stateStack.push(i);
    }

    public void visit(BMany bMany) {
        NFAState i = stateStack.pop();
        NFAState f = stateStack.pop();
        NFAState n = newState();
        i.directRule(n);
        n.directRule(f);
        stateStack.push(n);
        stateStack.push(n);
    }

    public void visit(LClosure lClosure) {
        NFAState i = stateStack.pop();
        NFAState f = stateStack.pop();
        i.directRule(f);
    }

遍历顺序为整个语法树的深搜。

这里用了一个栈来存储搜索中非第一优先级的节点。以我们上节中的语法树为例:

                |---------------[O]-------------|               

        |-------[C]-----|               |-------[C]-----|       

    |---[M]             b               a           |---[M]     

    a                                               b         

我们先把整个 NFA 的第一要素初态 0 和终态 1 压入栈,这时栈中有元素:

[1] [0]

自上而下开始搜索,首先我们遇到了 Or 节点,根据 Or 的规则:

于是我们先 pop 出栈顶的两个状态,再按顺序 push 两遍:

[1] [0] [1] [0]

继续深搜遇到根节点的左儿子 Concat,按照 Concat 的规则:

创建两个新状态 2 和 3,并插入栈顶的两个状态中:

[1] [0] [1] [3] [2] [0]

遇到一个 Many,按照 Many 的规则:

先弹出栈顶的两个状态,创建一个新状态 4, 用栈顶的状态 0 和状态 4 建立双向的 closure 连接 (状态 0 可以直接转换到状态 4,反之亦然),并把状态 4 push 两遍入栈。

[1] [0] [1] [3] [4] [4]

遇到一个字符 a,那么就弹出栈顶的两个状态,然后建立两个状态的字符连接 (就是先弹出的状态接受字符 a 转换为后弹出的状态):

[1] [0] [1] [3]

又遇到一个字符 b:

[1] [0]

至此,根节点的左子树已经遍历完了,而我们也介绍完了构建这个 NFA 所用到的所有规则。

下面直接给出遍历右子树时栈的状态:

Concat:

[1] [6] [5] [0]

a:

[1] [6]

Many:

[7] [7]

b:

Nil

因此,一棵结构正确的语法树经过这种 NFA 的构造后会留下一个空栈。

这样,我们就完成了 DFA 正则引擎的第二步: NFA 的构建。

时间: 2024-10-12 18:37:57

实现一个 DFA 正则表达式引擎 - 2. NFA 的构建的相关文章

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

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

实现一个 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

实现一个 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)

基于ε-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" 这个字符串

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

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

自己实现一个SQL解析引擎

自己实现一个SQL解析引擎 功能:将用户输入的SQL语句序列转换为一个可执行的操作序列,并返回查询的结果集. SQL的解析引擎包括查询编译与查询优化和查询的运行,主要包括3个步骤: 查询分析: 制定逻辑查询计划(优化相关) 制定物理查询计划(优化相关) 查询分析: 将SQL语句表示成某种有用的语法树. 制定逻辑查询计划: 把语法树转换成一个关系代数表达式或者类似的结构,这个结构通常称作逻辑计划. 制定物理查询计划:把逻辑计划转换成物理查询计划,要求指定操作执行的顺序,每一步使用的算法,操作之间的