最初步的正则表达式引擎:增加了字符集表示和?运算符和+运算符
2013-06-23 15:33
399 查看
关于正则的语法方面,就做到这吧。下一步是生成nfa,进而dfa,以及dfa的最简化。等下一个版本出来的话,估计要好久了。
#include <stdio.h> #include <malloc.h> #include <string.h> //这个版本在上一个版本的基础上,添加了?\+这两个操作符,这两个操作符都是单目操作符,跟*操作符同一级别 //因此碰到这两个操作符的时候,跟*操作符一样的处理 //除了新加的操作符外,还添加了字符集,模式为a-z这种,但是字符集只能通过假名来引用 //因此如果想使用字符集,则必须先通过假名来定义这个字符集,然后在后续的正则表达式中通过假名 //来使用这个字符集 //因此这里需要修改reg_opera_type,以及输入处理函数。 //注意这里的字符集定义的识别是通过判断正则的第二个字符是否是-来作用的,所以如果在其他类型的正则 //表达式中想使用-符号的话,千万不要把它放在第二个字符里面,因为这样会产生错误 //虽然可以去判断正则的长度来消除这种错误,不过我就懒得管了 //想改的话,读者自己去改吧 int token[100]; int token_pointer; char reg_operator[100]; int reg_operator_pointer=0; int name_number=0; //注意这里name_number是全局的,而其他的几个栈及变量都是每个子表达式都复用的 //其实那些变量及栈可以每次申请一个,为了节省空间,我就懒得管了,直接复用。 int input_pointer=0; char reg_input[40];//由于命名表达式的存在,考虑加长字符,其实随便多大都可以处理。 enum reg_opera_type { parenthesis=1,//对应括号 closure,//对应闭包运算符 one_or_more,//对应+运算符 maybe_exist,//对应?运算符 cat,//对应连接运算符 or,//对应选择运算符 alias,//对应命名子表达式 set_of_char,//对应字符集 literal_char//单字符 };//正则节点类型 typedef struct _reg_pattern { enum reg_opera_type type; union { struct//对应二元操作符 { int left; int right; }; struct//对应假名 { int origin_number; int hash_table_number; }; struct//对应字符集 { char begin; char end; }; char value;//对应字符值 int sub;//对应闭包运算符和括号运算符以及问号运算符及+运算符 }; }reg_pattern;//差不多当作抽象语法树吧 reg_pattern reg_pattern_table[100]; //当前的hash只使用加法hash加上一个模97,因为考虑到97是质数 typedef struct _hash_table//hash表类型 { enum _state { empty=0, in_use, deleted }state; char* name_of_alias; int reg_pattern_number; }hash_table; hash_table simple_hash_table[100]; int look_up_hash_table(char* source)//查询一个名字是否在hash表中 { int string_length; int counter; int index; int result; string_length=strlen(source); result=0; for(index=0;index<string_length;index++) { result+=*(source+index); } result=result%97; counter=0; while(counter<97)//顶多查询97次,如果查询达到了97次,则说明当前hash表中不存在这个字符串 { if(simple_hash_table[result].state==empty)//如果当前位置为空,说明不存在这个节点,直接返回-1 { return -1; } else { if(simple_hash_table[result].state==deleted)//如果为删除状态,则继续向下寻找,注意索引溢出的处理 { result=(result+1)%97; counter++; } else { if(strcmp(source,simple_hash_table[result].name_of_alias)==0)//如果出于使用状态,则开始比较 { return result;//名字相同则返回索引 } else//不同则继续向下寻找 { result+=(result+1)%97; counter++; } } } } return -1;//如果找了达到了97次,则返回-1 } int insert_hash_table(char* source,int index_of_reg_pattern) { int string_length; int counter; int index; int result; string_length=strlen(source); result=0; for(index=0;index<string_length;index++) { result+=*(source+index); } result=result%97; counter=0; while(counter<97) { if(simple_hash_table[result].state==in_use)//如果在使用中,则继续下次寻找 { result=(result+1)%97; counter++; } else//如果可用,则使用 { simple_hash_table[result].state=in_use; simple_hash_table[result].name_of_alias=source; simple_hash_table[result].reg_pattern_number=index_of_reg_pattern; return result; } } return -1;//已经满了 ,插入失败 } int is_begin_of_token() //判断输入字符是否可以当作一个token的开始符号,这样是为了处理非显示的连接符 //由于转义字符也是可以当作token的开始字符,所以也是返回1 { switch(reg_input[input_pointer]) { case '*': case '?': case '+': case '|': case '.': case ']': case ')': case '\0': return 0; default: return 1; } //这里两个闭括号的存在都是为了处理非显示的连接符 //而把开括号移动到外部的switch之中 //主要是为了防止括号嵌套引起的错误 } tackle_invisible_cat()//如果后面跟的是开括号(包括普通括号和假名括号)或者字符,则这时候要去处理隐式连接符 { if(is_begin_of_token()) { tackle_cat(); } } void tackle_or()//处理并操作符,这个处理完之后,输入指针也会后移 { if(reg_operator_pointer!=0)//如果操作符栈中已经有操作符了 { if(reg_operator[reg_operator_pointer-1]!='(')//括号另外说 { name_number++; if(reg_operator[reg_operator_pointer-1]=='.') //如果前面的优先级比当前的高,则处理前面的优先级 { printf("name%d is concat of name%d and name%d\n",name_number,token[token_pointer-2],token[token_pointer-1]); } else //这里处理的是相同优先级的情况,其实这里可以与前面的合并的,只不过打印信息不同 { printf("name%d is name%d or name%d\n",name_number,token[token_pointer-2],token[token_pointer-1]); } token[token_pointer-2]=name_number; token_pointer--; reg_operator[reg_operator_pointer-1]='|'; input_pointer++; } else//对于括号,则直接入栈 { reg_operator[reg_operator_pointer++]='|'; input_pointer++; } } else//对于空操作符栈,也是直接入栈 { reg_operator[reg_operator_pointer++]='|'; input_pointer++; } } int tackle_cat()//处理连接符,处理完之后输入指针不会移动,因为指针本来就指向了后面的字符 { if(reg_operator_pointer!=0)//如果操作符栈不为空 { if(reg_operator[reg_operator_pointer-1]=='(')//如果前面有括号,则直接入栈 { reg_operator[reg_operator_pointer++]='.'; } else//对于前面不是括号的情况下 { if(reg_operator[reg_operator_pointer-1]=='.')//优先级相同则输出前面的那个 { name_number++; printf("name%d is the concat of name%d and name%d\n",name_number,token[token_pointer-2],token[token_pointer-1]); reg_pattern_table[name_number].type=cat; reg_pattern_table[name_number].left=token[token_pointer-2]; reg_pattern_table[name_number].right=token[token_pointer-1]; token[token_pointer-1]=0; token[token_pointer-2]=name_number; token_pointer--; } else//否则的话,前面的优先级比当前优先级低,操作符入栈 { reg_operator[reg_operator_pointer++]='.'; } } } else//如果操作符栈为空,则入栈 { reg_operator[reg_operator_pointer++]='.'; } } void tackle_parenthesis(void)//处理闭括号模式,这里有点复杂,这里匹配完之后,指针会向后移一位 { if(reg_operator[reg_operator_pointer-1]=='(')//如果前面那个操作符为开括号,则匹配输出,并把输入指针向后移 { name_number++; printf("name%d is (name%d)\n",name_number,token[token_pointer-1]); reg_pattern_table[name_number].type=parenthesis; reg_pattern_table[name_number].sub=token[token_pointer-1]; token[token_pointer-1]=name_number; input_pointer++; reg_operator[--reg_operator_pointer]='\0'; //这时候需要考虑后面的是否少了显示的连接符,如果判断缺少连接符,则需要加上去 tackle_invisible_cat(); } else//如果闭括号前面还有运算符,那么根据他们的优先级输出,这个时候输入指针是不变的,注意 { name_number++; if(reg_operator[reg_operator_pointer-1]=='.') { printf("name%d is the concat of name%d and name%d\n",name_number,token[token_pointer-2],token[token_pointer-1]); reg_pattern_table[name_number].type=cat; reg_pattern_table[name_number].left=token[token_pointer-2]; reg_pattern_table[name_number].right=token[token_pointer-1]; } else { printf("name%d is name%d or name%d\n",name_number,token[token_pointer-2],token[token_pointer-1]); reg_pattern_table[name_number].type=or; reg_pattern_table[name_number].left=token[token_pointer-2]; reg_pattern_table[name_number].right=token[token_pointer-1]; } token[token_pointer-1]=0; token[token_pointer-2]=name_number; token_pointer--; reg_operator_pointer--; } } int tackle_alias(void)//注意这里处理完中括号匹配之后,还需要处理可能存在的非显示连接符 { int length,index; char* new_alias; index=length=0; while(reg_input[input_pointer+length]!=']') { length++; } new_alias=malloc(sizeof(char)*(length));//总的长度为length-1,另外加上一个\0。 for(index=0;index<length-1;index++) { new_alias[index]=reg_input[input_pointer+index+1]; } new_alias[index]='\0'; input_pointer+=length+1; index=look_up_hash_table(new_alias);//寻找hash表 if(index!=-1) { length=simple_hash_table[index].reg_pattern_number;//找到别名所代表的正则模式 token[token_pointer++]=length;//别名的正则模式的名字入栈 printf("match a alias,%s is the alias of name%d\n",new_alias,length); tackle_invisible_cat(); return 1; } else//否则,返回错误信息 { printf("the alias can't be matched\n"); return -1; } } int tackle_inter_reg(void) { token_pointer=0; while(reg_input[input_pointer]!='\0')//还没处理到输入的末尾 { switch(reg_input[input_pointer]) { case '\\'://注意这里因为在c语言中这个字符也是转义字符,所以要双斜杠才能写出来 input_pointer++; name_number++; printf("transition literal is encounted\n"); printf("the literal is %c\n",reg_input[input_pointer]); printf("the literal's token name is name%d\n",name_number); token[token_pointer++]=name_number; reg_pattern_table[name_number].type=literal_char; reg_pattern_table[name_number].value=reg_input[input_pointer]; input_pointer++; tackle_invisible_cat(); break; case '('://对于括号,直接入栈 reg_operator[reg_operator_pointer++]='('; input_pointer++; break; case ')': tackle_parenthesis(); break; case '*'://由于这个运算符优先级很高,不需要入栈了,直接拿顶上的token输出就行 name_number++; printf("name%d is multiple of name%d\n",name_number,token[token_pointer-1]); reg_pattern_table[name_number].type=closure; reg_pattern_table[name_number].sub=token[token_pointer-1]; token[token_pointer-1]=name_number; input_pointer++; tackle_invisible_cat(); break; case '+'://同上 name_number++; printf("name%d is one or more instance of name%d\n",name_number,token[token_pointer-1]); reg_pattern_table[name_number].type=one_or_more; reg_pattern_table[name_number].sub=token[token_pointer-1]; token[token_pointer-1]=name_number; input_pointer++; tackle_invisible_cat(); break; case '?'://同上 name_number++; printf("name%d is one or zero instance of name%d\n",name_number,token[token_pointer-1]); reg_pattern_table[name_number].type=maybe_exist; reg_pattern_table[name_number].sub=token[token_pointer-1]; token[token_pointer-1]=name_number; input_pointer++; tackle_invisible_cat(); break; case '['://对于开的中括号,因为中括号中只能允许假名存在,所以直接调用函数处理, //处理完之后指针位于闭中括号的后面一个字符,所以switch中不需要考虑闭中括号了 //注意处理时后面可能接上一个非显示的连接符 //这个时候需要特殊处理 tackle_alias(); break; case '|'://对于这个符号, tackle_or(); break; default: name_number++; printf("name%d is %c\n",name_number,reg_input[input_pointer]); token[token_pointer++]=name_number; reg_pattern_table[name_number].type=literal_char; reg_pattern_table[name_number].value=reg_input[input_pointer]; input_pointer++; tackle_invisible_cat(); break; } } //处理完了输入,这个时候可能还有剩余的内容在栈中 while(reg_operator_pointer>=1)//如果全部的输入都弄完了,可是 操作符栈中还有数据,则输出 { name_number++; if(reg_operator[reg_operator_pointer-1]=='.') { printf("name%d is concat of name%d and name%d\n",name_number,token[token_pointer-2],token[token_pointer-1]); reg_pattern_table[name_number].type=cat; reg_pattern_table[name_number].left=token[token_pointer-2]; reg_pattern_table[name_number].right=token[token_pointer-1]; } else { printf("name%d is name%d or name%d\n",name_number,token[token_pointer-2],token[token_pointer-1]); reg_pattern_table[name_number].type=or; reg_pattern_table[name_number].left=token[token_pointer-2]; reg_pattern_table[name_number].right=token[token_pointer-1]; } token[token_pointer-2]=name_number; token_pointer--; reg_operator_pointer--; } return name_number;//返回最后一个生成的token,作为假名的正则代号 } int tackle_particle()//这里处理一个正则表达式名字的定义,每次处理完之后复用输入数组 { //首先处理假名部分 int index; char* name; input_pointer=0; while(reg_input[input_pointer++]!=':')//注意这里在停止迭代之后,指针指的是冒号后面的那个字符, { //一直向后找,直到找到冒号分隔符 } //字符串长度比指针的值小1 name=malloc(sizeof(char)*(input_pointer));//考虑到字符串的结尾\0,于是不需要减1了 for(index=0;index<input_pointer-1;index++)//把名字复制过去 { name[index]=reg_input[index]; } name[index]='\0'; if(reg_input[input_pointer+1]=='-')//如果后面接的是字符集模式,则直接处理,因为这个比较简单 { name_number++;//新建一个字符集的名字 printf("new set of literal is created\n"); printf("the set's name is name%d\n",name_number); printf("the set begins from %c, and end with %c\n",reg_input[input_pointer],reg_input[input_pointer+2]); index=name_number; } else { index=tackle_inter_reg();//处理当前的小正则表达式,并得到这个表达式所生成的名字 } insert_hash_table(name,index); printf("creat a alias: %s is name%d\n",name,index);//输出,建立了一个新的假名 } int main(void) { int index; printf("please type in your short regex definition\n"); printf("if there is no more definition, just enter a newline\n"); scanf("%s",reg_input); while(strlen(reg_input)>=3)//当输入的字符数小于三个的时候,认为结束 { tackle_particle(); printf("please type in your short regex definition\n"); printf("if there is no more definition, just enter a newline\n"); scanf("%s",reg_input); } for(index=0;index<97;index++)//这里将所有的假名输出,并释放空间,其实无所谓,反正系统会回收 { if(simple_hash_table[index].state==in_use) { printf("there is a alias whose name is %s\n",simple_hash_table[index].name_of_alias); free(simple_hash_table[index].name_of_alias); } } }
相关文章推荐
- 最初步的正则表达式引擎:在上个版本的基础上增加了转义字符
- 最初步的正则表达式引擎:nfa的转换规则。
- 最初步的正则表达式引擎
- 最初步的正则表达式引擎:将显示的连接符改为了非显示的连接符
- 最初步的正则表达式引擎:支持子表达式替换
- 最初步的正则表达式引擎:生成nfa
- awk 使用表示次数的正则表达式(--posix --re-interval)
- js正则表达式中/=\s*\".*?\"/g表示什么意思?
- Linux正则表达式--基础正则表示法
- D5_正则表达式初步
- 正则表达式与POSIX标识字符集
- awk 正则表达式、正则运算符详细介绍
- java正则表达式不写^和$,表示匹配目标串的部分(包括本身)
- 正则表达式初步了解(参考jb51)
- 正则表达式的字符表示
- puppet自动化运维之运算符及正则表达式
- 用于POSIX风格的正则表达式的字符集
- 正则表达式 附正则表达式表示空的方法
- 正则表达式有关引擎
- 正则表达式详解(一)正则表达式初步