正则表达式转NFA的一点思考和尝试(c++)


前言

这学期的编译原理课需要从正则表达式转为NFA,但是对Thompson算法如何实现很是费解,对于其他博客的代码理解的很困难。于是我开始尝试利用栈直接对正则表达式进行扫描检索,通过冗余解决括号的匹配,实现了一个比较简陋的正则表达式转NFA,于是写这个博客记录一下。


一、基本思路和默认条件

首先讲一下默认条件。我们假设输入的正则表达式是一串仅由大小写字母、数字、‘(’、‘)’、‘×’、‘|’组成的字符串,默认输入的字符串是左右括号匹配的,且符合正则表达式的构造规则。

然后是基本思路,主要是分治的思想,我们对括号分层处理(具体操作我们后续讲),而对于每一个基础运算符号(‘’、‘|’),我们都将其转换为双目运算(同上),同时我们要求运算的运算体必须要用()包起来。


对于每一层括号深度,我们都有一个Node栈存储中间节点。同时应该有一个OR栈存储或运算的节点(为什么这么做我们后面说)。

对于第N+1层和第N层的连接,我们在第N+1层的‘)’符号处进行处理。
在开始,我们在第0层的Node栈中压入开始符号‘S’。

二、对各类符号的处理

对正则表达式的当前字符我们记为temp_Char。

1.非运算符

对于非运算符,我们将当前括号深度的Node栈的栈顶取出放到Node0,并产生新Node1,产生边Node0->Node1的边,这条边的权为temp_Char。同时当当前深度的Node栈的大小大于2时POP掉中间节点,仅留下当前深度的头节点和尾节点。
示例:
正则表达式:abc
0.开始符号 S 压入Node[0]
Node[2]
Node[1]
Node[0] S
1.temp_Char=‘a’ 产生新符号A,产生新边S->A:a
Node[2]
Node[1]
Node[0] S A
2.temp_Char=‘b’ 弹出A 产生新符号B,产生新边S->B:b
Node[2]
Node[1]
Node[0] S B
3.temp_Char=‘c’ 弹出B 产生新符号C,产生新边B->C:c
Node[2]
Node[1]
Node[0] S C

2.运算符

*运算由于我们要求了其闭包体必须在()中,因此在)中处理

1.’(’

当检测到左括号,括号深度+1,并在+1后的括号深度的Node栈中压入一个新的符号。
示例:
正则表达式:a(b)
0.开始符号 S 压入Node[0]
Node[2]
Node[1]
Node[0] S
1.temp_Char=‘a’ 产生新符号A,产生新边S->A:a
Node[2]
Node[1]
Node[0] S A
2.temp_Char=’(’ 括号深度+1 产生新符号B
Node[2]
Node[1] B
Node[0] S A
3.temp_Char=‘b’ 产生新符号C,产生新边B->C:b
Node[2]
Node[1] B C
Node[0] S A
对‘)’的处理,我们最后再讲。

2.’|’

当检测到‘|’符号,我们首先将当前深度的Node栈的栈顶压入当前深度的OR栈中,然后将其从Node栈中pop出来,然后将栈顶放到Node0中,然后产生一个新符号放到Node1中,产生一个新边Node0->Node1,权为ε (如果是在第0层这样就结束了),(如果是括号内的)然后在对‘)’处理时将那时的当前深度的Node栈栈顶和每一个OR栈中的符号产生一条边,权为ε 。
示例(不包含‘)’):
正则表达式:a|b|c
0.开始符号 S 压入Node[0]
Node[2]
Node[1]
Node[0] S
1.temp_Char=‘a’ 产生新符号A,产生新边S->A:a
Node[2]
Node[1]
Node[0] S A
2.temp_Char=’|’ 产生新符号B,产生新边S->B:ε
Node[2] OR[2]
Node[1] OR[1]
Node[0] S B OR[0] A
3.temp_Char=‘b’ 产生新符号C,产生新边B->C:b
Node[2] OR[2]
Node[1] OR[1]
Node[0] S C OR[0] A
4.temp_Char=’|’ 产生新符号D,产生新边S->D:ε
Node[2] OR[2]
Node[1] OR[1]
Node[0] S D OR[0] A C
5.temp_Char=‘c’ 产生新符号E,产生新边D->E:c
Node[2] OR[2]
Node[1] OR[1]
Node[0] S E OR[0] A C

3.’)’

首先将当前深度的Node栈的栈顶pop到Node0中,然后如果OR栈不为空,则产生OR栈中的每一个Node->Node0的边,权为ε。然后我们再取当前深度Node栈的栈顶到Node1中。
然后超前检测下一个符号是否是‘×’,如果是,就产生一条Node0到Node1的边,权为ε,然后令Node0等于Node1(保证*运算为双目运算符),并且跳过下一个字符的处理。
然后开始当前括号深度和上一层的连接,首先从上一层的Node栈中pop栈顶到Node2中,产生一条从Node2到Node1的边,权为ε。
然后将上一层的Node栈pop到仅剩一个起始符号,最后将Node0压入上一层的Node栈中。
示例:
正则表达式:ab((bc)×|d)
0.开始符号 S 压入Node[0]
Node[2]
Node[1]
Node[0] S
1.temp_Char=‘a’ 产生新符号A,产生新边S->A:a
Node[2]
Node[1]
Node[0] S A
2.temp_Char=‘b’ 产生新符号B,产生新边A->B:b
Node[2] OR[2]
Node[1] OR[1]
Node[0] S B OR[0]
3.temp_Char=’(’ 产生新符号C
Node[2] OR[2]
Node[1] C OR[1]
Node[0] S B OR[0]
4.temp_Char=’(’ 产生新符号D
Node[2] D OR[2]
Node[1] C OR[1]
Node[0] S B OR[0]
5.temp_Char=‘b’ 产生新符号E 产生新边D->E:b
Node[2] D E OR[2]
Node[1] C OR[1]
Node[0] S B OR[0]
6.temp_Char=‘c’ 产生新符号F 产生新边E->F:c
Node[2] D F OR[2]
Node[1] C OR[1]
Node[0] S B OR[0]
7.temp_Char=’)’ 产生新边F->D:ε,新边C->D:ε
Node[2] OR[2]
Node[1] C F OR[1]
Node[0] S B OR[0]
8.temp_Char=’|’ 产生新符号G,新边C->G:ε
Node[2] OR[2]
Node[1] C G OR[1] F
Node[0] S B OR[0]
9.temp_Char=‘d’ 产生新符号H 产生新边G->H:d
Node[2] OR[2]
Node[1] C H OR[1] F
Node[0] S B OR[0]
10.temp_Char=’)’ 产生新边F->H:ε,新边B->C:ε
Node[2] OR[2]
Node[1] OR[1]
Node[0] S H OR[0]

2.代码基础

#define max_Depth 10 //括号最大深度
#define max_Node_Count 1000 //最大节点数量
Node nodes[max_Node_Count]; //Node数组
#define max_Line_Count 5000 //最多边数
Line lines[max_Line_Count]; //边数组
Node结构体:
struct Node{
int Node_identification;//唯一标识符
string node_Name;//节点名
};
stack node_Stack[max_Depth];//Node栈
stack or_Node_Stack[max_Depth];//OR栈
int brackets=0;//当前括号深度
void creat_New_Node(int index);//在index深度的Node栈新建一个符号
//新建一个从index1指向index2,权为value的边
void creat_New_Line(int index1,int index2,char value);

源代码已上传到码云
传送门:https://gitee.com/haixiujun/codes/ek4vw6gzsu5tprjmodhbc50

总结

我自己测试了大约十几个测试数据,基本都可以通过产生冗余来完成NFA的构造,但是无法完成不带括号的闭包运算,算是一个小小的遗憾吧。

联系

如果有任何问题可以联系qq:94523105

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值