正则表达式,如 w(o|a)ke
其中的字符大致分两类,元字符和结构性字符
其中包含的结构有:
小括号(),小括号将其内部作为一个整体对待
中括号[],中括号整体上可作为一个特殊的元字符,如元字符.本质上等价于[^\n],元字符\d本质上等价于[0-9]等等
量词(含大括号中的内容如{1,3},,+,?),其中等价于{,},+等价于{1,},?等价于{0,1}(或{,1})
或字符|,将多条正则表达式集合成一条正则表达式
类RegexReader
读取token和正则表达式的对应关系,并依序创建出两个数组
tokens 和 rules
同时将里面出现的元字符如\d(目前不支持),\n,\r,0-9,a-z,等进行编码,存在映射表metaCharacterMap中
类Graph,结构体GraphNode,结构体GraphEdge
类Graph用于存储整个正则表达式或小括号内部的正则表达式的自动机转换图
主要提供了三个函数,一个void clearGraph(GraphNode* node)用于管理Graph所占用的内存空间,一般由析构函数自动调用;一个void printGraph()用于打印Graph的内容,用于调试;一个Graph(const Graph &g)即拷贝构造函数用于对Graph进行深拷贝(深拷贝的原因:虽然当Graph作为子图时进行重用可以节约内存,但由于从不同节点进入子图之后出节点一般也不同,在匹配时,只有当前节点信息,无法知道是从哪个节点进入子图的,也就无法知道该从哪个出口出去,因此,子图无法重用)
结构体GraphNode是最直接管理的结构,含两个成员,state(状态值) edges(转换边数组)
结构体GraphEdge是最核心的结构体,用于在节点之间流动 成员如下
endNode 该边匹配之后应转到的节点
isReverse 为true则将匹配结果翻转过来
childGraph 子图
transferChar 元字符数组
图的基本结构
起始节点和终止节点
单个元字符的处理
或结构的处理
中括号的处理(本质上与单个元字符一致)
小括号的处理
子图与量词的处理
综合应用
类NFA
用于从RegexReader读入的多个正则表达式构建并管理图,并提供正则表达式匹配功能
其中构造图的核心函数为Graph buildGraph(const std::string& rule, int &startPosition),用于从一个字符串中构造一个Graph
GraphNode addGraphNode(int charCode, GraphNode* currentNode, bool isReverse = false, int* transferChar = nullptr) 可重用函数,在构建Graph时处理单个元字符
int* handleMiddleBracket(const std::string& rule, int& j) 可重用函数,在构建Graph时用于将中括号中的内容集合为单个元字符
static int* handleBrace(const std::string& rule, int& j) 可重用函数,在构建Graph时用于确认量词的上下限
GraphNode* handleQuantifier(GraphNode* &lastNode, GraphNode* currentNode, int min, int max) 可重用函数,在构建Graph时用于处理量词
int* resizeArray(int* transferChar, int originSize, int newSize) 功能函数,用于将原有数组扩容,可用realloc函数替代
char* matchString(const std::string& str, int &startPosition) 匹配字符串的核心函数,根据构建好的NFA,对给予的字符串进行匹配,给出匹配到的正则表达式的token和实现匹配的那部分字符串
void getNextNodes(char c, GraphNode* currentNode, std::set<GraphNode*> nextNodeSet)
匹配字符串核心函数的主要功能部分,用于在当前节点之后路径中查找能匹配字符c的所有路径,从而得到当前节点获得字符c后可以转换到的节点,其中应用了set防止重复(这里得匹配使用最长匹配),能匹配到最长字符的token胜出
void getEpsilonNodes(GraphNode currentNode, std::set<GraphNode*>* nextNodeSet);
由于getNextNodes并不会得到epsilon转换后得节点,但所有得终止节点前都是epsilon转换,在查找匹配到得token时,需要将节点和图得终止节点进行匹配,故需要此函数获得某个节点epsilon转换后得所有节点
void metaCharacterMapToVector() 用于将RegexReader构造的映射表metaCharacterMap转换为数组
std::string* getToken(GraphNode* n)根据节点,在所有图的终止节点进行匹配,找到节点匹配的Graph从而获得对应的token
源代码如下,有兴趣可使用,有BUG可留言,待将其转换为DFA
RegexReader.h
#pragma once
#include <vector>
#include <map>
#include <string>
#include <iostream>
//读取并管理正则表达式
class RegexReader
{
public:
std::vector<std::string> tokens;
std::vector<std::string> rules;
//正则表达式常见元字符
//\t tab;\r 回车;\n 换行;\b 退格
//只能在 "[]" (中括号外使用转义,为了在中括号可以含[],需要转义,因此\字符也需要转义,不可见字符也要转义) 中括号特殊用法 0-9 '[' + '^'表示匹配不在中括号中的字符
//"()" (小括号内外符号表示无区别) 转义元字符 \( \) \[ \] \\
//\s:用于匹配单个空格符,包括tab键和换行符; [ \t\n]
//\S:用于匹配除单个空格符之外的所有字符 等价于[^ \t\n]
//\d:用于匹配从0到9的数字 [0-9]
//\w:用于匹配字母,数字或下划线字符 [A-Za-z0-9_]
//\W:用于匹配所有与\w不匹配的字符 [^A-Za-z0-9_]
//. :用于匹配除换行符之外的所有字符 . + * ? 在中括号失去特殊意义
std::map<std::pair<char, char>, int> metaCharacterMap;
int mapSize = 0;
void addMetaCharacter(char data, char& lastChar, int& nextCharState);//维护元字符映射表
void printMetaCharacterMap();//打印元字符映射表
RegexReader(std::istream& in);
};
RegexReader.cpp
#include "RegexReader.h"
#include <stdlib.h>
//ASCII码,7位二进制数组成,可表示27=128个字符。大写字母排在前,小写字母排在后。32(即20H)代表空格,大于32的ASCII码代表可显示字符,小于32的ASCII码代表不可显示字符(ASCII码表见第37页表2-7)。大小写字母的ASCII码值相差32。
//CONST const
//规则格式,每行一条规则,前面为正则表达式分类的字符串 后面为正则表达式本身 中间用空格隔开
RegexReader::RegexReader(std::istream& in)
{
std::string buf;
std::string rule;
for (;;) {
buf.clear();
std::getline(in, buf);
auto i = buf.begin();
// 跳过行首空白
for (; i != buf.end() && *i <= ' '; ++i);
// 读到空行停止
if (i == buf.end())
break;
auto j = i;
for (;j != buf.end() && *j > ' '; ++j);
tokens.emplace_back(i, j);
// 忽略中间空白
for (; j != buf.end() && *j <= ' '; ++j);
// 开始读规则
rule.clear();
//移动i指针
i = j;
bool inMiddleBracket = false;
//理论上来说读取到一个元字符就要执行下一次循环
char lastChar = 0;
int nextCharState = 0;//状态值解读 0状态为初始状态,1状态代表当前读入了有效的范围值,2状态代表刚处理完一个范围
for (; j != buf.end() && *j >= ' ';j++) {
//首先判断是不是转义字符
if (*j == '\\') {
j++;//无论何时都必须转义的字符
if (*j == '[' || *j == ']' || *j == '\\') {
addMetaCharacter(*j, lastChar, nextCharState);
}
else if (*j == 'r') {
addMetaCharacter('\r', lastChar, nextCharState);
}
else if (*j == 't') {
addMetaCharacter('\t', lastChar, nextCharState);
}
else if (*j == 'n') {
addMetaCharacter('\n', lastChar, nextCharState);
}
else if (*j == 'b') {
addMetaCharacter('\b', lastChar, nextCharState);
}else if (!inMiddleBracket && (*j == '(' || *j == ')' || *j == '{' || *j == '}' || *j == '|')) {//不在中括号里的额外转义
addMetaCharacter(*j, lastChar, nextCharState);
}
}//其次判断是不是'['
else if (*j == '[') {
inMiddleBracket = true;
}//其次判断是不是']'
else if (*j == ']') {
inMiddleBracket = false;
}//其次判断是不是'(',')' 忽略非转义小括号
else if (*j == '(' || *j == ')') {
//在中括号中不能忽略
if(inMiddleBracket)addMetaCharacter(*j, lastChar, nextCharState);
}
else if (*j == '{') {//忽略大括号中的内容
if(inMiddleBracket)addMetaCharacter(*j, lastChar, nextCharState); //在中括号中不能忽略
else while (*j != '}')j++;
}//处理特殊字符'^' 条件1 中括号内;条件2 当前字符为'^';条件3 前一字符不是'['
else if (inMiddleBracket && *j == '^' && *(j - 1) != '[') {//当前字符必然非'\\'和'['
addMetaCharacter('^', lastChar, nextCharState);
}//处理特殊字符'-' 条件1 中括号内;条件2 当前字符为'-';条件3 前一字符不是'['同时前两字符不能是'[^'
else if (inMiddleBracket && *j == '-' && (*(j - 1) != '[' && (*(j - 1) != '^' || *(j - 2) != '['))) {
if (nextCharState == 1 || nextCharState == 2)exit(-1);
else nextCharState = 1;
}//在中括号中 失去效力的特殊字符
else if (inMiddleBracket && (*j == '.' || *j == '?' || *j == '*' || *j == '+')) {
addMetaCharacter(*j, lastChar, nextCharState);
}
else if (!inMiddleBracket && *j == '.') {
addMetaCharacter('\n', lastChar, nextCharState);
}
//处理普通字符
else {
addMetaCharacter(*j, lastChar, nextCharState);
}
}
if (nextCharState == 1)exit(-1);
else addMetaCharacter(0, lastChar, nextCharState);
rules.emplace_back(i, j);
}
}
void RegexReader::addMetaCharacter(char data, char &lastChar, int &nextCharState) {
//默认只加单个字符
if (lastChar == 0) {
lastChar = data;
return;//初始的处理
}
std::pair<char, char> p(lastChar, lastChar);
//状态1插入范围
if (nextCharState == 1){
p.second = data;
if (data < lastChar)exit(-1);
nextCharState = 2;
}//状态2不进行插入
else if (nextCharState == 2) {
lastChar = data;
nextCharState = 0;
return;
}
lastChar = data;
auto result = metaCharacterMap.find(p);
if (result == metaCharacterMap.end()) {
mapSize++;
metaCharacterMap.emplace(p, mapSize);
}
}
void RegexReader::printMetaCharacterMap() {//打印元字符映射表
for (auto it = metaCharacterMap.begin(); it != metaCharacterMap.end(); it++) {
std::cout << "字符范围: " << it->first.first << " - " << it->first.second << " , 编码值为 " << it->second << std::endl;
}
}
Graph.h
#pragma once
#include <vector>
#include <string>
#include <iostream>
struct GraphEdge;
struct GraphNode;
class Graph;
typedef unsigned int StateNum;
struct GraphNode {
StateNum state;//状态值
std::vector<GraphEdge> edges;
GraphNode(StateNum state):state(state){}
};
class Graph {
public:
GraphNode* startNode = nullptr;
GraphNode* endNode = nullptr;
std::string* token = nullptr;//图所对应的token 由于匹配时只看到节点,无法看到图,如果将token放在图中,还需确认节点所在的图
//打印图
void printGraph();
//清空图占用的内存
void clearGraph(GraphNode* node);
Graph(StateNum state) { startNode = new GraphNode(state); }
//深拷贝,取消子图的重用
Graph(const Graph &g);
//复制一个同样的节点
GraphNode* copyNode(const GraphNode& n);
~Graph() { clearGraph(startNode); }
};
//图的边 图
//无条件转移边要求 isReverse为true,childGraph为nullptr,edges的size为0 后两个为默认值 在Edge里删除最大匹配次数的字段
struct GraphEdge {
GraphNode* endNode;
bool isReverse;
std::vector <int> transferChar;
Graph* childGraph;
GraphEdge(GraphNode* endNode, bool isReverse = false, Graph* childGraph = nullptr) : endNode(endNode), isReverse(isReverse), childGraph(childGraph) {}
};
Graph.cpp
#include "Graph.h"
#include <stack>
#include <iostream>
#include <map>
#include <unordered_set>
//清空图占用的内存
void Graph::clearGraph(GraphNode* node) {
//存放已被遍历到的节点
std::unordered_set<GraphNode* > deletedNodeSet;
std::unordered_set<Graph*> deletedChildGraph;
//遍历图
std::stack<GraphNode* > *nodeStack = new std::stack<GraphNode* >();//存放当前遍历节点
std::stack<GraphNode* > *nextNodeStack = new std::stack<GraphNode* >();//存放接下来遍历的节点
std::stack<GraphNode* >* temp = nullptr;
nodeStack->push(node);
while (true) {
//遍历所有当前需要遍历的节点
while (!nodeStack->empty()) {
//获取并弹出栈顶
GraphNode *n = nodeStack->top();
nodeStack->pop();
//将每个节点连接的未遍历节点加入接下来遍历的节点栈 在子图中的endNode存在转向母图的边,则endNode不可被遍历其边
if (n != endNode) {
for (auto it = n->edges.begin(); it != n->edges.end(); it++) {
GraphNode* nextNode = (*it).endNode;//管理节点内存
Graph* childGraph = (*it).childGraph;//管理子图内存
//这里为了遍历所有节点,在未取出节点的数据前不能删除
if (deletedNodeSet.find(nextNode) == deletedNodeSet.end()) {
deletedNodeSet.insert(nextNode);
nextNodeStack->push(nextNode);
}
//由于子图附属于节点 只要所有节点遍历到 所有子图即可遍历到 可直接删除
if (childGraph != nullptr) {//子图不能为空
if (deletedChildGraph.find(childGraph) == deletedChildGraph.end()) {
delete childGraph;
deletedChildGraph.insert(childGraph);
}
}//由于子图不进行重用,故每个节点的子图占用的内存空间都不同,无需在Graph进行管理,只要在GraphEdge进行管理即可
//由于GraphEdge的内存由vector管理,vector如果对空间进行重分配,会执行GraphEdge的析构函数,导致子图内存被回收,此时即使保存了GraphEdge的所有数据也不行,故子图的内存不由GraphEdge管理,改由Graph管理
}
}
delete n;
}
//交换当前需要遍历的节点栈和接下来需要遍历的节点栈
if (!nextNodeStack->empty()) {
temp = nodeStack;
nodeStack = nextNodeStack;
nextNodeStack = temp;
}//如果接下来需要遍历的节点栈为空,跳出循环
else break;
}
delete nodeStack;
delete nextNodeStack;
}
//打印图
void Graph::printGraph() {
//存放已被遍历到的节点
std::unordered_set<GraphNode* > visitedNodeSet;
//遍历图
std::stack<GraphNode* >* nodeStack = new std::stack<GraphNode* >();//存放当前遍历节点
std::stack<GraphNode* >* nextNodeStack = new std::stack<GraphNode* >();//存放接下来遍历的节点
std::stack<GraphNode* >* temp = nullptr;
nodeStack->push(startNode);
while (true) {
//遍历所有当前需要遍历的节点
while (!nodeStack->empty()) {
//获取并弹出栈顶
GraphNode* n = nodeStack->top();
nodeStack->pop();
//操作当前遍历的节点
std::cout<<"当前遍历节点的state值为" << n->state<<" ";
//将每个节点连接的未遍历节点加入接下来遍历的节点栈 在子图中的endNode存在转向母图的边,则endNode不可被遍历其边
if (n != endNode) {
for (auto it = n->edges.begin(); it != n->edges.end(); it++) {
GraphNode* nextNode = (*it).endNode;//管理节点内存
//这里为了遍历所有节点,在未取出节点的数据前不能删除
if (visitedNodeSet.find(nextNode) == visitedNodeSet.end()) {
visitedNodeSet.insert(nextNode);
nextNodeStack->push(nextNode);
}
if ((*it).isReverse)std::cout << " 为补集 ";
else std::cout << " 为正集 ";
std::cout<<",下一节点的state值为 " << nextNode->state << " ";
if ((*it).transferChar.size() != 0) {
std::cout << "元字符编码含 ";
for (auto i = (*it).transferChar.begin(); i != (*it).transferChar.end(); i++) {
std::cout << *i << " ";
}
}
std::cout << std::endl;
if ((*it).childGraph != nullptr) {
std::cout << "开始打印子图" << std::endl;
(*it).childGraph->printGraph();
}
}
}
else {
std::cout << " 遍历到子图终点 ";
for (auto it = n->edges.begin(); it != n->edges.end(); it++) {
GraphNode* nextNode = (*it).endNode;//管理节点内存
if ((*it).isReverse)std::cout << " 为补集 ";
else std::cout << " 为正集 ";
std::cout << ",下一节点的state值为 " << nextNode->state << " ";
if ((*it).transferChar.size() != 0) {
std::cout << "元字符编码含 ";
for (auto i = (*it).transferChar.begin(); i != (*it).transferChar.end(); i++) {
std::cout << *i << " ";
}
}
std::cout << std::endl;
if ((*it).childGraph != nullptr) {
std::cout << "开始打印子图" << std::endl;
(*it).childGraph->printGraph();
}
}
std::cout << "子图的最后一个节点遍历完成" << std::endl;
}
}
//交换当前需要遍历的节点栈和接下来需要遍历的节点栈
if (!nextNodeStack->empty()) {
temp = nodeStack;
nodeStack = nextNodeStack;
nextNodeStack = temp;
}//如果接下来需要遍历的节点栈为空,跳出循环
else break;
}
delete nodeStack;
delete nextNodeStack;
}
//深拷贝,取消子图的重用
Graph::Graph(const Graph& g) {
//存放已被遍历到的节点
std::unordered_set<GraphNode* > visitedNodeSet;
//存放新增的节点
std::unordered_set<GraphNode* > newNodeSet;
//存放旧新节点地址的转换
std::map<GraphNode*, GraphNode*> newAndOldNodeMap;
//遍历图
std::stack<GraphNode* >* nodeStack = new std::stack<GraphNode* >();//存放当前遍历节点
std::stack<GraphNode* >* nextNodeStack = new std::stack<GraphNode* >();//存放接下来遍历的节点
std::stack<GraphNode* >* temp = nullptr;
nodeStack->push(g.startNode);
while (true) {
//遍历所有当前需要遍历的节点
while (!nodeStack->empty()) {
//获取并弹出栈顶
GraphNode* n = nodeStack->top();
nodeStack->pop();
//对节点进行处理
GraphNode* newNode = copyNode(*n);
//存放旧新节点地址的转换
newAndOldNodeMap.emplace(n, newNode);
//将每个节点连接的未遍历节点加入接下来遍历的节点栈 在子图中的endNode存在转向母图的边,则endNode不可被遍历其边
if (n != g.endNode) {
for (auto it = n->edges.begin(); it != n->edges.end(); it++) {
GraphNode* nextNode = (*it).endNode;//管理节点内存
//这里为了遍历所有节点,在未取出节点的数据前不能删除
if (visitedNodeSet.find(nextNode) == visitedNodeSet.end()) {
visitedNodeSet.insert(nextNode);
nextNodeStack->push(nextNode);
}
}
}
else continue;//endNode的指针不需要替换
//存放新增的节点
newNodeSet.insert(newNode);
}
//交换当前需要遍历的节点栈和接下来需要遍历的节点栈
if (!nextNodeStack->empty()) {
temp = nodeStack;
nodeStack = nextNodeStack;
nextNodeStack = temp;
}//如果接下来需要遍历的节点栈为空,跳出循环
else break;
}
delete nodeStack;
delete nextNodeStack;
//修改startNode
startNode = newAndOldNodeMap[g.startNode];
endNode = newAndOldNodeMap[g.endNode];
//遍历所有newNode,修改指针值
for (auto it = newNodeSet.begin(); it != newNodeSet.end(); it++) {
for (auto i = (*it)->edges.begin(); i != (*it)->edges.end(); i++) {
//将就指针值替换为新指针值
(*i).endNode = newAndOldNodeMap[(*i).endNode];
}
}
}
//复制一个同样的节点
GraphNode* Graph::copyNode(const GraphNode& n) {
//新建GraphNode
GraphNode* newNode = new GraphNode(n.state);
for (auto it = n.edges.begin(); it != n.edges.end(); it ++) {
//复制原GraphNode的值
if((*it).childGraph != nullptr)newNode->edges.emplace_back((*it).endNode, (*it).isReverse, new Graph(*((*it).childGraph)));//子图调用拷贝构造函数 需要子图不为空
else newNode->edges.emplace_back((*it).endNode, (*it).isReverse, nullptr);
//获取刚刚新增的edge
GraphEdge& edge = newNode->edges.back();
//拷贝原EDGE的元字符数组
edge.transferChar.assign((*it).transferChar.begin(), (*it).transferChar.end());
}
return newNode;
}
NFA.h
#pragma once
#include "Graph.h"
#include <set>
#include <map>
#include "RegexReader.h"
class NFA
{
public:
std::vector<Graph*> graphVector;
std::map<std::pair<char, char>, int> metaCharacterMap;
int metaCharacterMapSize = 0;
const std::pair<char, char>** pVector = nullptr;
std::vector<std::string> tokens;
StateNum state = 0;
//打印NFA
void printNFA();
//处理量词
GraphNode* handleQuantifier(GraphNode* &lastNode, GraphNode* currentNode, int min, int max);
//增加图节点
GraphNode* addGraphNode(int charCode, GraphNode* currentNode, bool isReverse = false, int* transferChar = nullptr);
//构造图
Graph *buildGraph(const std::string& rule, int &startPosition);
//处理中括号
int* handleMiddleBracket(const std::string& rule, int& j);
//处理大括号
static int* handleBrace(const std::string& rule, int& j);
//重新分配数组大小
int* resizeArray(int* transferChar, int originSize, int newSize);
//匹配字符串
char* matchString(const std::string& str, int &startPosition);
//找出下一组节点 由于下一组节点需要去重,故使用set map m用于记录边的匹配次数 如果找到的节点数不为0,返回true,否则返回false
void getNextNodes(char c, GraphNode* currentNode, std::set<GraphNode*> *nextNodeSet);
//找出空转换的下一节点
void getEpsilonNodes(GraphNode* currentNode, std::set<GraphNode*>* nextNodeSet);
//将metaCharacterMap转换为数组
void metaCharacterMapToVector();
//判断获取node对应的token
std::string* getToken(GraphNode* n);
NFA(const RegexReader &rReader);
~NFA(){
for (auto it = graphVector.begin(); it != graphVector.end(); it++)delete (*it);
if (pVector != nullptr)delete[]pVector;
}
};
NFA.cpp
#include "NFA.h"
#include <stack>
NFA::NFA(const RegexReader& rReader) {
metaCharacterMap = rReader.metaCharacterMap;
tokens = rReader.tokens;
auto itRules = rReader.rules.begin();
auto itToken = tokens.begin();
while (itRules != rReader.rules.end() && itToken != tokens.end()) {
int statrtPosition = 0;
Graph* g = buildGraph(*itRules, statrtPosition);
g->token = &(*itToken);
graphVector.push_back(g);
itRules++;
itToken++;
}
}
//增加图节点
GraphNode* NFA::addGraphNode(int charCode, GraphNode* currentNode, bool isReverse, int* transferChar) {
//新建下一个节点
GraphNode* nextNode = new GraphNode(state);
state++;
//新建下一条边
currentNode->edges.emplace_back(nextNode, isReverse);
//获取新建的边的引用
GraphEdge& lastEdge = currentNode->edges.back();
//设置新建的边的元字符码
if (transferChar == nullptr) {
lastEdge.transferChar.push_back(charCode);
}
else {
for (int i = 0; transferChar[i] != 0; i++)lastEdge.transferChar.push_back(transferChar[i]);
}
return nextNode;
}
//处理量词 因为子图出口不一致,无法重用,可重用版本待研发
//只有在量词中会修改最大匹配次数 max为-1代表匹配次数无上限
GraphNode* NFA::handleQuantifier(GraphNode* &lastNode, GraphNode* currentNode, int min, int max) {
//错误处理
if (lastNode == nullptr) {
std::cout << "量词前无元字符" << std::endl;
exit(-1);
}
else if (max < min) {
std::cout << "量词错误" << std::endl;
exit(-1);
}
//获取上一节点的最后一条edge
GraphEdge& lastEdgeForLastNode = lastNode->edges.back();
if (min == 0 && max == -1) {
//这条edge改为指向自己
lastEdgeForLastNode.endNode = lastNode;
//如果这条Edge的子图不为空,需要转换子图的endNode的edge的endNode,指向lastNode
if (lastEdgeForLastNode.childGraph != nullptr) {
GraphEdge& edge = lastEdgeForLastNode.childGraph->endNode->edges.back();
edge.endNode = lastNode;
}
//删除之前新增的节点
delete currentNode;
state--;//节点被删除,回收他的state值
//当前节点变为上一节点
return lastNode;
}
int num = 0;
if (max == -1)num = min;
else num = max;
// 保存量词子图的每个新增节点
std::stack<GraphNode*> nodeStack;
//由于 min<=max 如max为0,则min必为0,无意义,不考虑 故max>0
//currentNode为量词所修饰的子图的第一个转换节点 故入栈
nodeStack.push(lastNode);
nodeStack.push(currentNode);
//构建量词子图
while (num > 1) {
//新建新的转换节点
GraphNode* nextNode = new GraphNode(state);
state++;
//新建下一条边
if (lastEdgeForLastNode.childGraph != nullptr)
//调用复制构造函数创建新子图
currentNode->edges.emplace_back(nextNode, lastEdgeForLastNode.isReverse, new Graph(*(lastEdgeForLastNode.childGraph)));
else currentNode->edges.emplace_back(nextNode, lastEdgeForLastNode.isReverse, nullptr);
//获取新建的边的引用
GraphEdge& lastEdgeForCurrentNode = currentNode->edges.back();
//如果这条Edge的子图不为空,需要转换子图的endNode的edge的endNode 变为nextNode
if (lastEdgeForLastNode.childGraph != nullptr) {
//由于这里子图copy了之前的子图 则该子图的endNode存在一条无条件转换边
GraphEdge& edge = lastEdgeForCurrentNode.childGraph->endNode->edges.back();
edge.endNode = nextNode;
}
//复制上一节点的最后一条edge的元字符码数据
lastEdgeForCurrentNode.transferChar.assign(lastEdgeForLastNode.transferChar.begin(), lastEdgeForLastNode.transferChar.end());
//新增节点入栈
nodeStack.push(nextNode);
//更新lastNode
lastNode = currentNode;
//更新currentNode
currentNode = nextNode;
//更新max
num--;
}
//子图构建完成,补充epsilon边
if (max == -1) {//如果匹配无上限 则只需在最后一个节点加一条指向自己的转换边
//新建一条指向自己的边
if (lastEdgeForLastNode.childGraph != nullptr)
//调用复制构造函数创建新子图
currentNode->edges.emplace_back(currentNode, lastEdgeForLastNode.isReverse, new Graph(*(lastEdgeForLastNode.childGraph)));
else
currentNode->edges.emplace_back(currentNode, lastEdgeForLastNode.isReverse, nullptr);
//获取新建的边的引用
GraphEdge& lastEdgeForCurrentNode = currentNode->edges.back();
//如果这条Edge的子图不为空,需要转换子图的endNode的edge的endNode 变为currentNode
if (lastEdgeForLastNode.childGraph != nullptr) {
//由于这里子图copy了之前的子图 则该子图的endNode存在一条无条件转换边
GraphEdge& edge = lastEdgeForCurrentNode.childGraph->endNode->edges.back();
edge.endNode = currentNode;
}
//复制上一节点的最后一条edge的元字符码数据
lastEdgeForCurrentNode.transferChar.assign(lastEdgeForLastNode.transferChar.begin(), lastEdgeForLastNode.transferChar.end());
}
else {
while (max > min) {
nodeStack.pop();
GraphNode* n = nodeStack.top();
//在所有位于min和max之间的节点增加通向最后节点的无条件转换边
n->edges.emplace_back(currentNode, true);
max--;
}
}
return currentNode;
}
//构造图
//中括号和大括号的语义无法嵌套 如果表达式为 a*|b* 必须在两边额外添加一个公共起始节点和公共终止节点
//难点 由于不同子图的endNode节点的出口不同,如果进行子图重用,会导致子图匹配后多出口问题
Graph* NFA::buildGraph(const std::string &rule, int &startPosition) {//startPosition 指示应该从什么位置开始读取字符串
//遍历rule;
Graph* g = new Graph(state);
state++;
GraphNode* lastNode = g->startNode;
GraphNode* currentNode = new GraphNode(state);
state++;
//在startNode和currentNode之间添加一条无条件转移边 无条件转移边要求 isReverse为true,childGraph为nullptr,edges的size为0 后两个为默认值
lastNode->edges.emplace_back(currentNode, true);
std::pair<char, char> p;
for (; startPosition < rule.length(); startPosition++) {
//首先判断是不是转义字符
if (rule[startPosition] == '\\') {
startPosition++;//不在中括号内需要转义的字符 [ ] \ \r \n \b \t ( ) { } |
if (rule[startPosition] == '[' || rule[startPosition] == ']' || rule[startPosition] == '(' || rule[startPosition] == ')' || rule[startPosition] == '{' ||
rule[startPosition] == '}' || rule[startPosition] == '\\' || rule[startPosition] == '|') {
p.first = p.second = rule[startPosition];
}
else if (rule[startPosition] == 'r') {
p.first = p.second = '\r';
}
else if (rule[startPosition] == 't') {
p.first = p.second = '\t';
}
else if (rule[startPosition] == 'n') {
p.first = p.second = '\n';
}
else if (rule[startPosition] == 'b') {
p.first = p.second = '\b';
}
//新增下一节点 处理单个元字符
lastNode = currentNode;
currentNode = addGraphNode(metaCharacterMap[p], currentNode);//替换当前节点
}//其次判断是不是'[' 这里保证在这里的字符都不在中括号中
else if (rule[startPosition] == '[') {
bool isReverse = false;
if (rule[startPosition + 1] == '^') {
isReverse = true;
startPosition++;
}
int *transferChar = handleMiddleBracket(rule, startPosition);
//新增下一节点 处理单个元字符
lastNode = currentNode;
currentNode = addGraphNode(0, currentNode, isReverse, transferChar);//替换当前节点
delete []transferChar;
}//处理'(' 忽略非转义小括号
else if (rule[startPosition] == '(') {
startPosition++;
Graph* childGraph = buildGraph(rule, startPosition);
//新建下一个节点
GraphNode* nextNode = new GraphNode(state);
state++;
//新建下一条边 将childGraph作为转移条件 transferChar为空,即保持默认
currentNode->edges.emplace_back(nextNode, false, childGraph);
//在childGraph->endNode和nextNode之间新建一条无条件转移边
childGraph->endNode->edges.emplace_back(nextNode, true);
//当前节点往前推移
lastNode = currentNode;
currentNode = nextNode;
}
//处理')' 子图的终结位置
else if (rule[startPosition] == ')') {
//如果endNode为空 说明是第一次遇到| 由于currentNode
if (g->endNode == nullptr) {
g->endNode = new GraphNode(state);
state++;
}
//在currentNode和g->endNode之间添加一条无条件转移边 无条件转移边要求 isReverse为true,childGraph为nullptr,edges的size为0 后两个为默认值
currentNode->edges.emplace_back(g->endNode, true);
//返回子图
return g;
}
else if (rule[startPosition] == '{') {//忽略大括号中的内容 这里保证在这里的字符都不在大括号中 量词处理
int* range = handleBrace(rule, startPosition);
currentNode = handleQuantifier(lastNode, currentNode, range[0], range[1]);
delete []range;
}//处理特殊字符'.'
else if (rule[startPosition] == '.') {//等价于[^\n]
p.first = p.second = '\n';
//新增下一节点 处理单个元字符
lastNode = currentNode;
currentNode = addGraphNode(metaCharacterMap[p], currentNode, true);//替换当前节点
}//处理特殊字符'?'
else if (rule[startPosition] == '?') {//等价于{0,1} 量词处理 最低值为0
currentNode = handleQuantifier(lastNode, currentNode, 0, 1);
}//处理特殊字符'*'
else if (rule[startPosition] == '*') {//等价于{0,-1} 量词处理 最低值为0
currentNode = handleQuantifier(lastNode, currentNode, 0, -1);
}//处理特殊字符'+'
else if (rule[startPosition] == '+') {//等价于{1,-1} 量词处理
currentNode = handleQuantifier(lastNode, currentNode, 1, -1);
}
//处理特殊字符'|'
else if (rule[startPosition] == '|') {
//如果endNode为空 说明是第一次遇到| 由于currentNode
if (g->endNode == nullptr) {
g->endNode = new GraphNode(state);
state++;
}
//在currentNode和g->endNode之间添加一条无条件转移边 无条件转移边要求 isReverse为true,childGraph为nullptr,edges的size为0 后两个为默认值
currentNode->edges.emplace_back(g->endNode, true);
//重起另一条链路
lastNode = g->startNode;
currentNode = new GraphNode(state);
state++;
//在startNode和currentNode之间添加一条无条件转移边 无条件转移边要求 isReverse为true,childGraph为nullptr,edges的size为0 后两个为默认值
lastNode->edges.emplace_back(currentNode, true);
}//处理普通字符 处理单个元字符
else {
p.first = p.second = rule[startPosition];
//新增下一节点
lastNode = currentNode;
currentNode = addGraphNode(metaCharacterMap[p], currentNode);//替换当前节点
}
}
//如果endNode为空 说明是第一次遇到| 由于currentNode
if (g->endNode == nullptr) {
g->endNode = new GraphNode(state);
state++;
}
//非子图的终结位置
//如果读取完了还没结束 在currentNode和g->endNode之间添加一条无条件转移边 无条件转移边要求 isReverse为true,childGraph为nullptr,edges的size为0 后两个为默认值
currentNode->edges.emplace_back(g->endNode, true);
return g;
}
int* NFA::resizeArray(int* transferChar, int originSize, int newSize) {
int* newArray = new int[newSize];
memset(newArray, 0, sizeof(int) * newSize);
memcpy(newArray, transferChar, sizeof(int) * originSize);
delete []transferChar;
return newArray;
}
//处理中括号 初始指向中括号表达式的开头,可能为'['或'^'
int* NFA::handleMiddleBracket(const std::string& rule, int &j) {
const int CHARNUM = 10;
int* transferChar = new int[CHARNUM];
int length = CHARNUM;
memset(transferChar, 0, sizeof(int) * CHARNUM);
//指示待读取的元字符数组位置
int i = 0;
j++;
std::pair<char, char> p;
//处理掉只能出现在开头的'-'
if (rule[j] == '-') {
p.first = p.second = '-';
transferChar[i] = metaCharacterMap[p];
//元字符数组指向下一个
i++;
//读取下一个字符
j++;
}
while (rule[j] != ']') {
//首先判断是不是转义字符 中括号之内需要转义的有 [ ] \ \r \n \b \t
if (rule[j] == '\\') {
j++;
if (rule[j] == '[' || rule[j] == ']' || rule[j] == '\\') {
p.first = p.second = rule[j];
}
else if (rule[j] == 'r') {
p.first = p.second = '\r';
}
else if (rule[j] == 't') {
p.first = p.second = '\t';
}
else if (rule[j] == 'n') {
p.first = p.second = '\n';
}
else if (rule[j] == 'b') {
p.first = p.second = '\b';
}
}
else {
p.first = p.second = rule[j];
}
transferChar[i] = metaCharacterMap[p];
//元字符数组指向下一个
i++;
//为了保证最后能补0,长度必须保证最后一个是空位
if (i >= (length - 1)) {
transferChar = resizeArray(transferChar, length, length * 2);
length = length * 2;
}
//读取下一个字符
j++;
}
transferChar[i] = 0;
return transferChar;
}
//处理大括号 初始指向 '{' -1代表次数无上限(为了不占用正数,-1本质上为0xFFFFFFFF)
int* NFA::handleBrace(const std::string& rule, int &j) {
int* range = new int[2];
//保存j的值
int i = j;
while (rule[j] != ',')j++;
if (j == i + 1)range[0] = 0;
else {
char* str = new char[j - i];
for (int k = 0; k < j - i - 1; k++)str[k] = rule[i + 1 + k];
str[j - i - 1] = 0;
range[0] = atoi(str);
delete []str;
}
//此时j指向 ','
i = j;
while (rule[j] != '}')j++;
if (j == i + 1)range[1] = -1;
else {
char* str = new char[j - i];
for (int k = 0; k < j - i - 1; k++)str[k] = rule[i + 1 + k];
str[j - i - 1] = 0;
range[1] = atoi(str);
delete []str;
}
return range;
}
//打印NFA
void NFA::printNFA() {
for (auto it = graphVector.begin(); it != graphVector.end(); it++) {
auto token = *((*it)->token);
std::cout << "开始打印token " << token << std::endl;
(*it)->printGraph();
}
}
//将metaCharacterMap转换为数组
void NFA::metaCharacterMapToVector() {
metaCharacterMapSize = metaCharacterMap.size();
//因为编码从1开始分配,必须+1
pVector = new const std::pair<char, char>*[metaCharacterMapSize + 1];
for (auto it = metaCharacterMap.begin(); it != metaCharacterMap.end(); it++) {
pVector[(*it).second] = &((*it).first);
}
}
//判断获取node对应的token
std::string* NFA::getToken(GraphNode* n) {
//遍历所有顶级图
for (auto it = graphVector.begin(); it != graphVector.end(); it++) {
if (n == (*it)->endNode)return (*it)->token;
}
return nullptr;
}
//匹配字符串
char* NFA::matchString(const std::string& str, int& startPosition) {
//如果未将MAP转换为vector,先进行转换
if (pVector == nullptr)metaCharacterMapToVector();
//保存开始遍历位置
int start = startPosition;
//存放当前的node
std::set<GraphNode*>* nodeSet = new std::set<GraphNode*>();
//存放接下来的node
std::set<GraphNode*>* nextNodeSet = new std::set<GraphNode*>();
std::set<GraphNode*>* temp = nullptr;
while (start < str.length()) {
//在start位置开始进行的单次匹配
//初始化nodeSet
//遍历所有顶级图 将所有startNode压入
for (auto it = graphVector.begin(); it != graphVector.end(); it++) {
nodeSet->insert((*it)->startNode);
}
//遍历string
for (; startPosition < str.length(); startPosition++) {
//遍历set
for (auto it = nodeSet->begin(); it != nodeSet->end(); it++) {
//对字符str[startPosition]进行匹配
getNextNodes(str[startPosition], *it, nextNodeSet);
}
//如果接下来的节点集非空 交换两个节点集
if (!nextNodeSet->empty()) {
//清空set 必须在确认nextNodeSet非空才能清空nodeSet
nodeSet->clear();
//交换两个节点集
temp = nextNodeSet;
nextNodeSet = nodeSet;
nodeSet = temp;
}
else break;
}//从这里出来的时候nextNodeSet必为空
//因为终结点都不是endNode需要进一步进行epsilon转换
for (auto it = nodeSet->begin(); it != nodeSet->end(); it++) {
getEpsilonNodes(*it, nextNodeSet);
}
std::cout << "开始匹配位置 " << start << " , 结束匹配位置 " << startPosition << std::endl;
std::string* s = nullptr;
//遍历nextNodeSet找到匹配到的token
for (auto it = nextNodeSet->begin(); it != nextNodeSet->end(); it++) {
std::string *result = getToken(*it);
if (result != nullptr) {
std::cout << "匹配到的Token为 " << *result << std::endl;
s = result;
}
}
//s不为空指针说明找到了token
if (s != nullptr) {
char* c = new char[startPosition - start + 1];
for (int i = 0; i < startPosition - start; i++)c[i] = str[start + i];
c[startPosition - start] = 0;
return c;
}
//这里说明匹配失败,进行下一轮匹配
else {
start++;
startPosition = start;
//所有相关内容清空
nodeSet->clear();
nextNodeSet->clear();
}
}
return nullptr;
}
//找出下一组节点 由于下一组节点需要去重,故使用set 如果找到的节点数不为0,返回true,否则返回false
//当多次进入一个子图匹配时 由于匹配次数会叠加就会导致出问题 必须解决子图匹配问题 是否新增一个函数对子图进行尝试性匹配
void NFA::getNextNodes(char c, GraphNode* currentNode, std::set<GraphNode*>* nextNodeSet) {
//首先遍历所有Edge
for (auto it = currentNode->edges.begin(); it != currentNode->edges.end(); it++) {
//无条件转移边的处理 这里说明边匹配成功 无条件转移边的endNode不加入nextNodeSet 这里增加的是无条件转移边的匹配次数,应该正常增加
//无条件转移边的形式 isReverse为true 子图为空 元字符数组长度为0
if ((*it).isReverse && (*it).childGraph == nullptr && (*it).transferChar.size() == 0)getNextNodes(c, (*it).endNode, nextNodeSet);
else {
//子图的处理
if ((*it).childGraph != nullptr) {
//在有子图的情况下 该边不可能为isReverse
if ((*it).isReverse) {
std::cout << "子图边的isReverse为true\n";
exit(-1);
}
//由于不进行子图重用 故所有子图边均不相同
//对子图的处理 找出子图的起始点的匹配 这里说明边匹配成功 这里的匹配成功只是子图的一部分匹配成功,不能判断该EDGE是否匹配成功
getNextNodes(c, (*it).childGraph->startNode, nextNodeSet);//由于这里无法判断子图是否已匹配成功 这里不需要维护匹配次数,只需要按照边进行转移
}
if ((*it).transferChar.size() != 0) {
//记录元字符数组的匹配结果
bool isMatch = false;
//对元字符数组进行遍历 只要有一个满足即可
for (auto i = (*it).transferChar.begin(); i != (*it).transferChar.end(); i++) {
const std::pair<char, char> p = *(pVector[*i]);
if (c >= p.first && c <= p.second) {
isMatch = true;
break;
}
}
//如果是反的 将isMatch结果反过来
if ((*it).isReverse)isMatch = !isMatch;
//说明边匹配成功
if (isMatch)
nextNodeSet->emplace((*it).endNode);
}
}
}
}
//找出空转换的下一节点 该函数的调用在最后,所以不需要更新匹配次数
void NFA::getEpsilonNodes(GraphNode* currentNode, std::set<GraphNode*>* nextNodeSet) {
//节点本身也要加入
nextNodeSet->emplace(currentNode);
//首先遍历所有Edge
for (auto it = currentNode->edges.begin(); it != currentNode->edges.end(); it++) {
//加入所有epsilon转换之后的边
if ((*it).isReverse && (*it).childGraph == nullptr && (*it).transferChar.size() == 0)nextNodeSet->emplace((*it).endNode);
}
}
testfunction.cpp(用于测试的代码)
#include "testfunction.h"
#include "BitMap.h"
#include <iostream>
#include <string>
#include "NFA.h"
#include <sstream>
#include "RegexReader.h"
//测试BitMap
void testBitMap() {
BitMap map(128);
map.setSection(16, 40);
map.printBitMap();
map.setSection(28, 32, false);
map.printBitMap();
map.reverseSection(16, 40);
map.printBitMap();
}
//测试RegexReader
void testRegexReader() {
std::string str("FIRST w(o|a)ke\nSECOND t[a-o({]ke");
std::istringstream istrStream(str);
RegexReader rr(istrStream);
rr.printMetaCharacterMap();
for (unsigned int i = 0; i < rr.tokens.size(); i++)std::cout << rr.tokens[i] << std::endl;
for (unsigned int i = 0; i < rr.rules.size(); i++)std::cout << rr.rules[i] << std::endl;
}
//测试NFA
void testNFA() {
//定义NFA 字符串
std::string str(R"(FIRST w(o|a){2,5}ke)");
//构建输入流
std::istringstream istrStream(str);
//读取NFA字符串
RegexReader rr(istrStream);
//for (unsigned int i = 0; i < rr.tokens.size(); i++)std::cout << rr.tokens[i] << std::endl;
//for (unsigned int i = 0; i < rr.rules.size(); i++)std::cout << rr.rules[i] << std::endl;
rr.printMetaCharacterMap();
NFA nfa(rr);
//nfa.metaCharacterMapToVector();
/*for (int i = 1; i < nfa.metaCharacterMapSize + 1; i++) {
std::cout << "数组编号为 " << i << ", 第一个值为 " << (*nfa.pVector[i]).first << ", 第二个值为 "<< (*nfa.pVector[i]).second <<std::endl;
}*/
//nfa.printNFA();
std::string matchStr(R"(iwookes)");
int i = 0;
char *c = nfa.matchString(matchStr, i);
if (c != nullptr) {
std::cout << c << std::endl;
delete []c;
}
else std::cout << "匹配失败" << std::endl;
}