项目地址:Regex in Python

在看一下之前正则的语法的 BNF 范式

group ::= ("(" expr ")")*
expr ::= factor_conn ("|" factor_conn)*
factor_conn ::= factor | factor factor*
factor ::= (term | term ("*" | "+" | "?"))*
term ::= char | "[" char "-" char "]" | .

上一篇构造了 term 的简单 NFA

构造复杂的 NFA

factor

根据上面的factor ::= (term | term ("*" | "+" | "?"))*,先进行 term 的 NFA 的生成,然后根据词法分析器来判断要进行哪个 factor 的 NFA 的构造

def factor(pair_out):
term(pair_out)
if lexer.match(Token.CLOSURE):
nfa_star_closure(pair_out)
elif lexer.match(Token.PLUS_CLOSE):
nfa_plus_closure(pair_out)
elif lexer.match(Token.OPTIONAL):
nfa_option_closure(pair_out)

nfa_star_closure

*操作就是对之前的 term 再生成两个节点进行连接

def nfa_star_closure(pair_out):
if not lexer.match(Token.CLOSURE):
return False
start = Nfa()
end = Nfa()
start.next_1 = pair_out.start_node
start.next_2 = end pair_out.end_node.next_1 = pair_out.start_node
pair_out.end_node.next_2 = end pair_out.start_node = start
pair_out.end_node = end lexer.advance()
return True

nfa_plus_closure

+和*的唯一区别就是必须至少匹配一个字符,所以不能从节点 2 直接跳转到节点 4

def nfa_plus_closure(pair_out):
if not lexer.match(Token.PLUS_CLOSE):
return False
start = Nfa()
end = Nfa()
start.next_1 = pair_out.start_node pair_out.end_node.next_1 = pair_out.start_node
pair_out.end_node.next_2 = end pair_out.start_node = start
pair_out.end_node = end lexer.advance()
return True

nfa_option_closure

?对应的则是只能输入 0 个或 1 个的匹配字符,所以相对于*就不能再次从节点 1 跳转会节点 0

def nfa_option_closure(pair_out):
if not lexer.match(Token.OPTIONAL):
return False
start = Nfa()
end = Nfa() start.next_1 = pair_out.start_node
start.next_2 = end
pair_out.end_node.next_1 = end pair_out.start_node = start
pair_out.end_node = end lexer.advance()
return True

factor_conn

factor_conn ::= factor | factor factor*

对于 factor_conn 就是一个或者多个 factor 相连接,也就是说如果有多个 factor,只要将它们的头尾节点相连接

def factor_conn(pair_out):
if is_conn(lexer.current_token):
factor(pair_out) while is_conn(lexer.current_token):
pair = NfaPair()
factor(pair)
pair_out.end_node.next_1 = pair.start_node
pair_out.end_node = pair.end_node return True

expr

expr ::= factor_conn ("|" factor_conn)*

对于 expr 就是一个 factor_conn 或者多个 factor_conn 用|相连接

构建|的 NFA 就是生成两个新节点,新生成的头节点有两条边分别连接到 factor_conn 的头节点,对于两个 factor_conn 的尾节点分别生成一条边连接到新生成的尾节点

def expr(pair_out):
factor_conn(pair_out)
pair = NfaPair() while lexer.match(Token.OR):
lexer.advance()
factor_conn(pair)
start = Nfa()
start.next_1 = pair.start_node
start.next_2 = pair_out.start_node
pair_out.start_node = start end = Nfa()
pair.end_node.next_1 = end
pair_out.end_node.next_2 = end
pair_out.end_node = end return True

group

group 其实就是在 expr 上加了两个括号,完全可以去掉

def group(pair_out):
if lexer.match(Token.OPEN_PAREN):
lexer.advance()
expr(pair_out)
if lexer.match(Token.CLOSE_PAREN):
lexer.advance()
elif lexer.match(Token.EOS):
return False
else:
expr(pair_out) while True:
pair = NfaPair()
if lexer.match(Token.OPEN_PAREN):
lexer.advance()
expr(pair)
pair_out.end_node.next_1 = pair.start_node
pair_out.end_node = pair.end_node
if lexer.match(Token.CLOSE_PAREN):
lexer.advance()
elif lexer.match(Token.EOS):
return False
else:
expr(pair)
pair_out.end_node.next_1 = pair.start_node
pair_out.end_node = pair.end_node

构造 NFA 总结

可以看到对于整个 NFA 的构造,其实就是从最顶部开始向下递归,整个过程大概是:

  • expr -> factor_conn -> factor -> term

  • 当递归过程回到factor_conn会根据factor_conn ::= factor | factor factor*判断可不可以继续构造下一个factor

  • 如果不可以就返回到expr,expr则根据expr ::= factor_conn ("|" factor_conn)*

    判断能不能继续构造下一个factor_conn

  • 重复上面的过程

匹配输入字符串

现在已经完成了NFA的构造,接下来就是通过这个NFA来对输入的字符串进行分析

一个例子

以刚刚的图作为演示,假设0-1节点的边是字符集0-9,4-5节点的边是字符集a-z,其它都是空

所以这个图表示的正则表达式[0-9]*[a-z]+

假设对于分析字符串123a

  • closure

从开始节点8进行分析,我们要做的第一个操作就是算出在节点8时不需要任何输入就可以到达的节点,这个操作称为closure,得到closure集合

  • move

之后我们就需要根据NFA和当前的输入字符来进行节点间的跳转,得到的自然也是一个集合

closure操作

我们利用一个栈来实现closure操作

  • 把传入集合里的所有节点压入栈中
  • 然后对这个栈的所有节点进行判断是否有可以直接跳转的节点
  • 如果有的话直接压入栈中
  • 直到栈为空则结束操作
def closure(input_set):
if len(input_set) <= 0:
return None nfa_stack = []
for i in input_set:
nfa_stack.append(i) while len(nfa_stack) > 0:
nfa = nfa_stack.pop()
next1 = nfa.next_1
next2 = nfa.next_2
if next1 is not None and nfa.edge == EPSILON:
if next1 not in input_set:
input_set.append(next1)
nfa_stack.append(next1) if next2 is not None and nfa.edge == EPSILON:
if next2 not in input_set:
input_set.append(next2)
nfa_stack.append(next2) return input_set

move操作

  • move操作就是遍历当前的状态节点集合,如果符合的edge的条件的话
  • 就加入到下一个状态集合中
def move(input_set, ch):
out_set = []
for nfa in input_set:
if nfa.edge == ch or (nfa.edge == CCL and ch in nfa.input_set):
out_set.append(nfa.next_1) return out_set

match

现在最后一步就是根据上面的两个操作进行字符串的分析了

  • 首先先计算出开始节点的closure集合
  • 开始遍历输入的字符串,从刚刚的closure集合开始做move操作
  • 然后判断当前的集合是不是可以作为接收状态,只要当前集合有某个状态节点没有连接到其它节点,它就是一个可接收的状态节点,能被当前NFA接收还需要一个条件就是当前字符已经全匹配完了
def match(input_string, nfa_machine):
start_node = nfa_machine current_nfa_set = [start_node]
next_nfa_set = closure(current_nfa_set) for i, ch in enumerate(input_string):
current_nfa_set = move(next_nfa_set, ch)
next_nfa_set = closure(current_nfa_set) if next_nfa_set is None:
return False if has_accepted_state(next_nfa_set) and i == len(input_string) - 1:
return True return False

小结

这篇主要讲了复杂一点的NFA节点的构建方法,和对利用构造的NFA来对输入自负床进行分析。到目前为止,其实一个完整的正则表达式引擎已经完成了,但是如果想更近一步的话,还需要将NFA转换成DFA,再进行DFA的最小化

实现一个正则表达式引擎in Python(二)的更多相关文章

  1. 实现一个正则表达式引擎in Python(一)

    前言 项目地址:Regex in Python 开学摸鱼了几个礼拜,最近几天用Python造了一个正则表达式引擎的轮子,在这里记录分享一下. 实现目标 实现了所有基本语法 st = 'AS342abc ...

  2. 实现一个正则表达式引擎in Python(三)

    项目地址:Regex in Python 前两篇已经完成的写了一个基于NFA的正则表达式引擎了,下面要做的就是更近一步,把NFA转换为DFA,并对DFA最小化 DFA的定义 对于NFA转换为DFA的算 ...

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

    简介: 本文是系列博客的第一篇,主要讲解和分析正则表达式规则以及JAVA中原生正则表达式引擎的使用.在后续的文章中会涉及基于NFA的正则表达式引擎内部的工作原理,并在此基础上用1000行左右的JAVA ...

  4. Python的regex模块——更强大的正则表达式引擎

    Python自带了正则表达式引擎(内置的re模块),但是不支持一些高级特性,比如下面这几个: 固化分组    Atomic grouping 占有优先量词    Possessive quantifi ...

  5. PHP正则表达式详解(二)

    前言: 在本文中讲述了正则表达式中的组与向后引用,先前向后查看,条件测试,单词边界,选择符等表达式及例子,并分析了正则引擎在执行匹配时的内部机理. 本文是Jan Goyvaerts为RegexBudd ...

  6. 正则表达式学习与python中的应用

    目录: 一.正则表达式的特殊符号 二.几种重要的正则表达式 三.python的re模块应用 四.参考文献 一.正则表达式的特殊符号 特殊符号可以说是正则表达式的关键,掌握并且可以灵活运用重要的pyth ...

  7. Python3 如何优雅地使用正则表达式(详解二)

    使用正则表达式 现在我们开始来写一些简单的正则表达式吧.Python 通过 re 模块为正则表达式引擎提供一个接口,同时允许你将正则表达式编译成模式对象,并用它们来进行匹配. 小甲鱼解释:re 模块是 ...

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

    正则表达式几乎每个程序员都会用到,对于这么常见的一个语言,有没有想过怎么去实现一个呢?乍一想,也许觉得困难,实际上实现一个正则表达式的引擎并没有想像中的复杂,<编译原理>一书中有一章专门讲 ...

  9. 【C++】正则表达式引擎学习心得

    最近参照一些资料实现了一个非常简易的正则表达式引擎,支持基本的正则语法 | + * ()等. 实现思路是最基本的:正则式->AST->NFA->DFA. 以下是具体步骤: 一. 正则 ...

随机推荐

  1. 由group by引发的sql_mode的学习

    前言 在一次使用group by查询数据库时,遇到了问题.下面先搭建环境,然后让问题复现,最后分析问题. 一 问题复现 mysql版本 建表插入数据 表的结构 现在问题来了:我想查询上面表中每个部门年 ...

  2. 盘一盘 NIO (一)—— Buffer源码解析

    Buffer是个啥? Buffer 即缓冲区,用来暂存输入输出数据的区域.Buffer对象是一份固定数量的数据的容器,实质上是一个数组.但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问, ...

  3. ip地址、域名、DNS、URL的区别与联系

    IP:每个连接到Internet上的主机都会分配一个IP地址,此ip是该计算机在互联网上的逻辑地址的唯一标识,计算机之间的访问就是通过IP地址来进行的.写法:十进制的形式,用“.”分开,叫做“点分十进 ...

  4. go 学习笔记之是否支持以及如何实现继承

    熟悉面向对象的小伙伴们可能会知道封装,继承和多态是最主要的特性,为什么前辈们会如此看重这三种特性,真的那么重要吗? 什么是封装 什么是封装,封装有什么好处以及怎么实现封装? 相信大多数小伙伴们都有自己 ...

  5. 浅析runtime包中的三个方法Gosched、Goexit、GOMAXPROCS

    Gosched 暂停当前goroutine,使其他goroutine先行运算.只是暂停,不是挂起,当时间片轮转到该协程时,Gosched()后面的操作将自动恢复 未使用Gosched的代码 packa ...

  6. HDU 4417

    题意略. 思路: 仔细思考这个题目会发现,它其实是要你查询两次,第一是要规定l,r的范围,第二是要在范围内查询小于等于H的个数.所以有的人说要用主席树. 现在,如果我们能省去范围内对h的查询呢?也就是 ...

  7. Go Home Trash!垃圾分类 风险与对策

    一.外部风险 政策风险 (1)税收风险 本项目同时牵涉教育以及公益领域,在国家大力支持垃圾分类.互联网.信息科技等高技术产业的发展的背景下,我们可以依照国家相关税收政策依法享受国家税收优惠与减免.本公 ...

  8. JavaScript算法模式——动态规划和贪心算法

    动态规划 动态规划(Dynamic Programming,DP)是一种将复杂问题分解成更小的子问题来解决的优化算法.下面有一些用动态规划来解决实际问题的算法: 最少硬币找零 给定一组硬币的面额,以及 ...

  9. hdu 1007 Quoit Design(分治)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1007 题意:给出n个点求最短的两点间距离除以2. 题解:简单的分治. 其实分治就和二分很像二分的写df ...

  10. 树形动态规划 fjutoj-2131 第四集,聚集城市

    第四集,聚集城市 TimeLimit:1000MS  MemoryLimit:128MB 64-bit integer IO format:%lld   Problem Description 在小A ...