简单的Thompson
设计一个节点数据结构
实现目的为正则表达式
假设:
- 状态机一定有一个初始状态节点和一个结束状态节点;
- 任何一个状态节点,至多只有两个指向外面的转换边;
- 每个状态节点,所拥有的边,最多只有三种可能,as follow:
- 有一条非epsilon的边
- 有一条epsilon的边
- 有两条epsion的边
根据这三个假设构造一个NFA的节点
//这里仍旧使用java代码public class NFA{public enum anchor{NONE,START,END,BOTH}//定义几个路径状态public static final int EPSILON = -1;//边对应的是εpublic static final int CCL = -2;//边对应的是字符集public static final int EMPTY = -3;//没有指向外面的边private static final int ASCII_COUNT=127;//?????//记录转换边对应的输入,输入可以是空,CCL,epsilonprivate int edge;public int getEdge(){ return edge; }public void setEdge(int type){ edge = type; }//一个状态节点的重要属性public Set<Byte> inputSet;public NFA next; //通过边转跳到下一个节点,可以为空public NFA next2; //根据之前的假设,一个节点至多拥有两个指向外侧的边public ANCHOR anchor; //对应的正则表达式是否开头含有^,或者结尾含有$private int stateNum; //节点编号private boolean visited = false;//节点是否被访问过public void setVisited(){visited = true;}public boolean isVisited(){return visited;}public NFA(){inputSet = new HashSet<Byte>();//使用一个hashset存储输入?clearState();//在创建NFA的时候清除一次状态}public void setStateNum(int num){stateNum = num;}}
词法分析器
public class Lexer{public enum Token{EOS, //end of stringANY, //.通配符AT_BOL, //开头匹配符^AT_EOL, //结尾匹配夫$CCL_END, //字符集类结尾括号]CCL_START, //字符集类开始括号[//...etc...}//lexer的各种属性private final int ASCII_COUNT = 128;private Token[] tokenMap = new Token[ASCII_COUNT]; //token数组private Token currentToken = Token.EOS;RegularExpressionHandler exprHandler = null; //句柄//...etcpublic Lexer(RegularExpressionHandler exprHandler){initTokenMap();this.exprHandler = exprHandler;}private void initTokenMap(){//首先把全部的for(int i=0;i<ASCII_COUNT;i++)tokenMap[i] = Token.L;//将特殊字符修改为特殊含义tokenmap['.'] = Token.ANY;tokenMap['['] = Token.CCL_START;//etc...}public Token advance(){if(currentToken == Token.EOS){//一个正则表达式结束了,进入下一个正则表达式if(exprCount >= exprHandler.getRegularExpressionCount()){currentToken = Token.END_OF_INPUT;return currentToken;}else{//当前没有解析完成全部的内容curExpr = exprhandler.getRegularExpressionCount(exprCount);exprCount++;}}if(charIndex >= curExpr.length()){currentToken = Token.EOS;charIndex = 0;return currentToken;}}}
NFA节点的情况
NFA节点对应于情况3.a(一条非epsilon的边)
- edge = CCL;如果
- next->下一个节点
-
NFA节点对应于情况3.b(一条epsilon的边)
edge = epsilon;
- next->下一节点
-
NFA节点对应于情况3.c(两条epsilon的边)
edge = epsilon
- next->下一节点
-
NFA节点没有指向外侧的边
edge = empty
- next->null
-
关于inputSet属性
inputSet是专门用来存放字符集的,在这个题目中,仅仅存在ascii输入,因此inputSet至多包含127个元素(因为是HashSet集合….因此不会重复)
节点的内存管理
当程序要生成一个NFA节点的时候,不直接使用new,采用了类似工厂模式的方法,NFAManager用于构造&回收节点(这部分其实使用new也可以….这里使用了工厂模式和池模型,能够更好的保证对性能的利用)
有一个起始状态和一个结束状态
使用一个类….内包含两个NFA对象,一个指向起点,一个指向终点即可;
NFA状态机构造实现
简单状态1:单个字符匹配
判断输入的正则表达式是否为单字匹配的模式?
- 如果是,则分配两个NFA节点,起始状态指向节点A,结束状态指向节点B
- 让节点A的next->节点B
- 将节点A的edge设置为目标匹配字符
例如,单字匹配a,起始节点的edge=’a’,起始节点.next->结束节点
简单状态2:任意单个字符匹配
- 分配起始节点和结束节点
- 起始节点.next->结束节点
- 起始节点的edge赋值为CCL
起始节点的inputSet中添加除了回车换行之外的所有ascii字符
简单状态3:范围匹配(构建NFA状态机[abcd]、[0-9])
分析:[abcd]可以匹配任何abcd中的任何一个字符
分配两个NFA节点作为开始&结束
- [abcd]仍旧属于字符集识别,起始节点的edge=CCL
调用一个方法检查正则表达式是否存在只包含”[“或者”]”的情况,如果有这种情况,正则表达式是不能成立的,直接报错;
分配四个节点
节点1-(epsilon)->节点2-(9)->节点3-(epsilon)->节点4
| |——(e)———| |
|———————————-(epsilon)————————-|
该模型可以完成重复0次->重复n次的效果
关于NFA节点的模型
NFA节点最多只会存在两个向外的边
