正则语言引擎:一个简单LEX和YACC结合运用的实例
来源:互联网 发布:知天命尽人事的网名 编辑:程序博客网 时间:2024/06/06 12:29
本文先描述了LEX
与YACC
的书写方法。然后利用LEX
与YACC
编写了一个简单正则语言的引擎(暂时不支持闭包与或运算),生成的中间语言为C语言
。
正则引擎应直接生成NFA或DFA模拟器的输入文件,但在SDT的设计上就走的是增加,修改状态节点。而本文则采用类似语言编译器,翻译生产对应C语言的语句。
熟悉LEX和YACC工具
lex工具简介
lex
格式
[第一部分:定义段]%%[第二部分:词法规则段]%%[第三部分:辅助函数段]
第一部分
Part1.1
- 第一部分以符号%{和%}包裹,里面为以C语法写的一些定义和声明
- 例如,文件包含,宏定义,常数定义,全局变量及外部变量定义,函数声明等。
- 这一部分被Lex翻译器处理后会全部拷贝到文件lex.yy.c中。
- 注意,特殊括号%{和%}都必须顶着行首写。
%{ #include <ctype.h> char tokenString[MAXTOKENLEN+1];%}
Part1.2
- 第二部分是一组正规定义和状态定义。
- 正规定义是为了简化后面的词法规则而给部分正规式定义了名字。
- 每条正规定义也都要顶着行首写。
digit [0-9]number {digit}+letter [a-zA-Z]identifier {letter}+newline \nwhitespace [ \t]+
- 状态定义也叫环境定义,它定义了匹配正规式时所处的状态的名字。
- 状态定义以%s开始,后跟所定义的状态的名字,注意%s也要顶行首写。
- 例如下面一行就定义了一个名为COMMENT的状态和一个名为BAD的状态,状态名之间用空白分隔:
%s COMMENT BAD
第二部分
- 词法规则段列出的是词法分析器需要匹配的正规式,以及匹配该正规式后需要进行的相关动作。
"if" {return IF;}"then" {return THEN;}"else" {return ELSE;}
- 每行都是一条规则,该规则的前一部分是正规式,需要顶行首写。
- 后一部分是匹配该正规式后需要进行的动作,这个动作是用C语法来写的,被包裹在{}之内,被Lex翻译器翻译后会被直接拷贝进lex.yy.c。
- 正规式和语义动作之间要有空白隔开。其中用{}扩住的正规式表示正规定义的名字。
- 也可以若干个正规式匹配同一条语义动作,此时正规式之间要用 | 分隔。
第三部分
- 辅助函数段用C语言语法来写,辅助函数一般是在词法规则段中用到的函数。这一部分一般会被直接拷贝到lex.yy.c中。
LEX中的变量
- yyin和yyout:这是Lex中本身已定义的输入和输出文件指针。这两个变量指明了lex生成的词法分析器从哪里获得输入和输出到哪里。默认:键盘输入,屏幕输出。
- yytext和yyleng:这也是lex中已定义的变量,直接用就可以了。
- yytext:指向当前识别的词法单元(词文)的指针
- yyleng:当前词法单元的长度。
- ECHO:Lex中预定义的宏,可以出现在动作中,相当于fprintf(yyout, “%s”,yytext),即输出当前匹配的词法单元。
- yylex():词法分析器驱动程序,用Lex翻译器生成的lex.yy.c内必然含有这个函数。
- yywrap():词法分析器遇到文件结尾时会调用yywrap()来决定下一步怎么做:
- 若yywrap()返回0,则继续扫描
- 若返回1,则返回报告文件结尾的0标记。
- 由于词法分析器总会调用yywrap,因此辅助函数中最好提供yywrap,如果不提供,则在用C编译器编译lex.yy.c时,需要链接相应的库,库中会给出标准的yywrap函数(标准函数返回1)
一个简易的词法分析器
保留字有
int float main return while do if else for
符号有
< <= = <> > >= + - * / ( ) { } ;
双引号内容当作MSG
整体。
书写lex
分析程序如下:
%{ #include <stdio.h> #include <stdlib.h> #define LT 1 #define LE 2 #define GT 3 #define GE 4 #define EQ 5 #define NE 6 #define WHILE 18 #define DO 19 #define ID 20 #define NUMBER 21 #define RELOP 22 #define NEWLINE 23 #define IF 24 #define ELSE 25 #define FOR 26 #define PLUS 27 #define MINUS 28 #define TIMES 29 #define OVER 30 #define LPAREN 31 #define RPAREN 32 #define SEMI 33 #define OP 34 #define LBRACE 35 #define RBRACE 36 #define INT 37 #define MAIN 38 #define RETURN 39 #define FLOAT 40 #define MSG 41 #define ERRORCHAR 42 int yylval; int installID (); int installNum ();%}delim [ \t \n]ws {delim}+letter [A-Za-z_]digit [0-9]id {letter}({letter}|{digit})*number {digit}+(\.{digit}+)?(E[+-]?{digit}+)?%%{ws} {;}int {return (INT);}float {return (FLOAT);}main {return (MAIN);}return {return (RETURN);}while {return (WHILE);}do {return (DO);}if {return (IF);}else {return (ELSE);}for {return (FOR);}{id} {yylval = installID (); return (ID);}{number} {yylval = installNum (); return (NUMBER);}"<" {yylval = LT; return (RELOP);}"<=" {yylval = LE; return (RELOP);}"=" {yylval = EQ; return (RELOP);}"<>" {yylval = NE; return (RELOP);}">" {yylval = GT; return (RELOP);}">=" {yylval = GE; return (RELOP);}"+" {yylval = PLUS; return (OP);}"-" {yylval = MINUS; return (OP);}"*" {yylval = TIMES; return (OP);}"/" {yylval = OVER; return (OP);}"(" {return LPAREN;}")" {return RPAREN;}";" {return SEMI;}"{" {return LBRACE;}"}" {return RBRACE;}"\"" { char c; int i = 0; do{ yytext[i++]=c; c = input(); } while (c != '\"'); yytext[i]='\0'; return MSG; }. {yylval = ERRORCHAR; return ERRORCHAR;}%%int installID () { return ID;}int installNum () { return NUMBER;}int yywrap (){ return 1;}void writeout(int c){ switch(c){ case ERRORCHAR: fprintf(yyout, "(ERRORCHAR, \"%s\") ", yytext);break; case RELOP: fprintf(yyout, "(RELOP, \"%s\") ", yytext);break; case WHILE: fprintf(yyout, "(WHILE, \"%s\") ", yytext);break; case DO: fprintf(yyout, "(DO, \"%s\") ", yytext);break; case NUMBER: fprintf(yyout, "(NUM, \"%s\") ", yytext);break; case ID: fprintf(yyout, "(ID, \"%s\") ", yytext);break; case NEWLINE: fprintf(yyout, "\n");break; case IF: fprintf(yyout, "(IF, \"%s\") ", yytext);break; case ELSE: fprintf(yyout, "(ELSE, \"%s\") ", yytext);break; case FOR: fprintf(yyout, "(FOR, \"%s\") ", yytext);break; case PLUS:fprintf(yyout, "(PLUS, \"%s\") ", yytext);break; case MINUS:fprintf(yyout, "(MINUS, \"%s\") ", yytext);break; case OVER:fprintf(yyout, "(OVER, \"%s\") ", yytext);break; case LPAREN:fprintf(yyout, "(LPAREN, \"%s\") ", yytext);break; case RPAREN:fprintf(yyout, "(RPAREN, \"%s\") ", yytext);break; case SEMI:fprintf(yyout, "(SEMI, \"%s\") ", yytext);break; case LBRACE:fprintf(yyout, "(LBRACE, \"%s\") ", yytext);break; case RBRACE:fprintf(yyout, "(RBRACE, \"%s\") ", yytext);break; case INT:fprintf(yyout, "(INT, \"%s\") ", yytext);break; case MAIN:fprintf(yyout, "(MAIN, \"%s\") ", yytext);break; case RETURN:fprintf(yyout, "(RETURN, \"%s\") ", yytext);break; case FLOAT:fprintf(yyout, "(FLOAT, \"%s\") ", yytext);break; case MSG:fprintf(yyout, "(MSG, \"%s\") ", yytext);break; case OP:{ switch(yylval){ case PLUS: fprintf(yyout, "(PLUS, \"%s\") ", yytext);break; case MINUS: fprintf(yyout, "(MINUS, \"%s\") ", yytext);break; case TIMES: fprintf(yyout, "(TIMES, \"%s\") ", yytext);break; case OVER: fprintf(yyout, "(OVER, \"%s\") ", yytext);break; } };break; default:fprintf(yyout, "(我不知道发生了什么,但是你大概是写错了. \"%s\") ", yytext);break; } return;}int main (int argc, char* argv[]){ int c,j=0; if (argc>=2){ if ((yyin = fopen(argv[1], "r")) == NULL){ printf("Can't open file %s\n", argv[1]); return 1; } if (argc>=3){ yyout=fopen(argv[2], "w"); } } else{ printf("Usage:./scanner [source file] [dst file]\n"); exit(0); } while ((c = yylex())!=0){ writeout(c); writeout(NEWLINE); } if(argc>=2){ fclose(yyin); if (argc>=3) fclose(yyout); } return 0;}
学习YACC工具
Yacc 代表 Yet Another Compiler Compiler
。 Yacc 的 GNU 版叫做 Bison。
- 它是一种工具,将任何一种编程语言的所有语法翻译成针对此种语言的 Yacc 语法解析器。
- 它用巴科斯范式(BNF, Backus Naur Form)来书写。
- 按照惯例,Yacc 文件有 .y 后缀。
引用网上的一个比喻
为了更加说清这一概念,让我们以英语为例。 这一套标记可能是:名词, 动词, 形容词等等。为了使用这些标记造一个语法正确的句子,你的结构必须符合一定的规则。 一个简单的句子可能是名词+动词或者名词+动词+名词。(如 I care. See spot run.)
创建有四个步骤
- 通过在语法文件上运行 Yacc 生成一个解析器。
- 说明语法:
- 编写一个 .y 的语法文件(同时说明 C 在这里要进行的动作)。
- 编写一个词法分析器来处理输入并将标记传递给解析器。 这可以使用 Lex 来完成。
- 编写一个函数,通过调用 yyparse() 来开始解析。
- 编写错误处理例程(如 yyerror())
- 编译 Yacc 生成的代码以及其他相关的源文件。
- 将目标文件链接到适当的可执行解析器库。
YACC 语法规则
Yacc 语法规则具有以下一般格式:
result: components { /* action to be taken in C */ };
在这个例子中,result 是规则描述的非终端符号。Components 是根据规则放在一起的不同的终端和非终端符号。 如果匹配特定序列的话 Components 后面可以跟随要执行的动作。
param : NAME EQ NAME { printf("\tName:%s\tValue(name):%s\n", $1,$3);} | NAME EQ VALUE{ printf("\tName:%s\tValue(value):%s\n",$1,$3);} ;
如果上例中序列 NAME EQ NAME 被匹配,将执行相应的 { } 括号中的动作。 这里另一个有用的就是 $1
和 $3
的使用, 它们引用了标记 NAME 和 NAME(或者第二行的 VALUE)的值。
lexer 通过 Yacc 的变量 yylval 返回这些值。标记 NAME 的 Lex 代码是这样的:
char [A-Za-z]name {char}+%%{name} { yylval = strdup(yytext); return NAME; }
文件解析例子的规则段是这样的:
文件解析的语法
file : record file | record ;record: NAME EQ AGE { printf("%s is now %s years old!!!", $1, $3);};%%
一般来说,Yacc 最好提供 yyerror(char msg) 函数的代码。 当解析器遇到错误时调用 yyerror(char msg)。错误消息作为参数来传递。 一个简单的 yyerror( char* ) 可能是这样的:
int yyerror(char* msg){ printf("Error: %s encountered at line number:%d\n", msg, yylineno);}
用LEX和YACC工具分析正则表达式
正则语言的词法定义
全集为ANCII码33-126的所有字符和空
元符号有
d_quotation "backslash /l_parentheses (r_parentheses )l_square_brack [r_square_brack ]l_curly_braces {r_curly_braces }star *plus +horizontal_line -dot .question_mark ?vertical_line |caret ^equal =empty εblank [\t\r\n]+
写出其词法分析器为
%{ #include <stdio.h> #include <string.h> #define LETTER 1 #define D_QUOTATION 2 #define BACKSLASH 3 #define L_PARENTHESES 4 #define R_PARENTHESES 5 #define L_SQUARE_BRACK 6 #define R_SQUARE_BRACK 7 #define L_CURLY_BRACES 8 #define R_CURLY_BRACES 9 #define STAR 10 #define PLUS 11 #define HORIZONTAL_LINE 12 #define DOT 13 #define QUESTION_MARK 14 #define VERTICAL_LINE 15 #define CARET 16 #define EQUAL 17 #define EMPTY 18 #define BLANK 19%}allset [!-~]blank [\t\r\n]+%%"\"" {return (D_QUOTATION);}"\\" {return (BACKSLASH);}"(" {return (L_PARENTHESES);}")" {return (R_PARENTHESES);}"[" {return (L_SQUARE_BRACK);}"]" {return (R_SQUARE_BRACK);}"{" {return (L_CURLY_BRACES);}"}" {return (R_CURLY_BRACES);}"*" {return (STAR);}"+" {return (PLUS);}"-" {return (HORIZONTAL_LINE);}"." {return (DOT);}"?" {return (QUESTION_MARK);}"|" {return (VERTICAL_LINE);}"^" {return (CARET);}"=" {return (EQUAL);}"ε" {return (EMPTY);}{blank} {return (BLANK);}{allset} {return (LETTER);}%%int yywrap(){ return 1;}int main (int argc, char* argv[]){ int c,j=0; if (argc>=2){ if ((yyin = fopen(argv[1], "r")) == NULL){ printf("Can't open file %s\n", argv[1]); return 1; } } else{ printf("Usage:./scanner [source file] [dst file]\n"); exit(0); } while ((c = yylex())!=0){ printf("%d\n",c); } fclose(yyin); return 0;}
简单测试RE
的词法分析器
正则表达式的语法规则
可以用字母开头的字母数字串
来作为标识符命名正则表达式。在使用名字的时候要加花括号,如
binary = (0|1)+real = {integer}.{nature}
正则表达式的上下文无关文法为
# List表示多个正则表达式的列表List → List Named_RE | Named_RE# Name_RE表示命名或未命名的正则表达式Named_RE → ID = RE | RE# RE 表示正则表达式RE → RE “|” No_Select | No_Select# No_Select表示不包含选择运算的正则表达式No_Select → No_Select No_Join | No_Join# No_Join表示不包含选择或者连接运算的正则表达式No_Join → Base | Base * | Base + | Base ?# Base表示最基本的正则表达式Base → ( RE ) | LETTER | [No_Select] | "No_select " | . | { ID } # ID表示标识符ID → ID <letter> | ID <number> | <letter>
根据这些规则写的CFG
:
%token D_QUOTATION BACKSLASH L_PARENTHESES R_PARENTHESES L_SQUARE_BRACK R_SQUARE_BRACK L_CURLY_BRACES R_CURLY_BRACES STAR PLUS HORIZONTAL_LINE DOT QUESTION_MARK VERTICAL_LINE CARET EQUAL EMPTY RE_LETTER LETTER NUMBER BLANK%%List : Named_RE {printf("Finish one line\n\n\n");} List | Named_RE {printf("Finish prog! End!\n\n");} ;Named_RE : ID EQUAL {printf("Find an ID\n\n");} RE BLANK | RE {printf("Find an RE\n");} BLANK ;RE : RE VERTICAL_LINE {printf("Finish VERTICAL_LINE\n");} No_Select | { printf("%d No_select start\n",debug_i); debug_i++;} No_Select { printf("Finish %d No_Select\n",debug_i);debug_i--;} ;No_Select : No_Join {printf("Find next join\n");} No_Join | No_Join {printf("End of No_Select\n");} ;No_Join : Base | Base STAR | Base PLUS | Base QUESTION_MARK ;Base : L_PARENTHESES RE R_PARENTHESES | L_SQUARE_BRACK RE R_SQUARE_BRACK | D_QUOTATION No_Select D_QUOTATION | L_CURLY_BRACES ID {printf("Confirm an ID\n");} R_CURLY_BRACES | DOT | LETTER | NUMBER | RE_LETTER ;ID : ID LETTER | ID NUMBER | LETTER ;%%
尝试出现很多conflict
,利用Yacc -v
生成查错log
。
Yacc
工具利用的是LALR文法,生成的查错报告也是将整个LALR
打印在文件中。
文件的前一部分为归约规则,如下:
0 $accept: List $end 1 @1: /* empty */ 2 List: Named_RE @1 List 3 | Named_RE ...
中间部分为每个状态的意义与GOTO
与ACTION
表
state 20 13 No_Select: No_Join . @6 No_Join 14 | No_Join . D_QUOTATION reduce using rule 12 (@6) D_QUOTATION [reduce using rule 14 (No_Select)] R_PARENTHESES reduce using rule 14 (No_Select) R_SQUARE_BRACK reduce using rule 14 (No_Select) VERTICAL_LINE reduce using rule 14 (No_Select) BLANK reduce using rule 14 (No_Select) $default reduce using rule 12 (@6) @6 go to state 32
可以看到,在状态20中,遇到D_QUOTATION
有冲突。
为了解决这个问题,这里简单处理,舍弃其中的一条规则。我的语言只ID = RE
的表达形式
但是对于引号的判断还是有问题,修改引号部分如下:
Base : L_PARENTHESES RE R_PARENTHESES | L_SQUARE_BRACK SQUARE_PART R_SQUARE_BRACK | D_QUOTATION QUOTA_PART D_QUOTATION | L_CURLY_BRACES ID R_CURLY_BRACES | DOT | LETTER | NUMBER | RE_LETTER ;QUOTA_PART : QUOTA_PART LETTER | LETTER ;
解决YACC
冲突有以下方法:
仔细选择书写的先后顺序,YACC默认移入/归约冲突时移入,归约/归约时选第一条。
一般我们的语言可以选择最长匹配。
如同上面引号,引入新的非终结符来解决冲突。
最终的RE.y
文件中的CFG
%token D_QUOTATION BACKSLASH L_PARENTHESES R_PARENTHESES L_SQUARE_BRACK R_SQUARE_BRACK L_CURLY_BRACES R_CURLY_BRACES STAR PLUS HORIZONTAL_LINE DOT QUESTION_MARK VERTICAL_LINE CARET EQUAL EMPTY RE_LETTER LETTER NUMBER BLANK%%List : Named_RE {printf("======Finish one line======\n\n\n");} List | Named_RE {printf("======Finish prog! End!======\n");} ;Named_RE : ID EQUAL {printf("==Find an ID==\n\n");} RE {printf("==Find a RE==\n\n");} BLANK ;RE : RE VERTICAL_LINE {printf("Finish VERTICAL_LINE\n");} No_Select | { printf("%d No_select start\n",debug_i); debug_i++;} No_Select { printf("Finish %d No_Select\n",debug_i); debug_i--;} ;No_Select : No_Select {printf("Find next join\n");} No_Join | No_Join {printf("End of No_Select\n");} ;No_Join : Base | Base STAR | Base PLUS | Base QUESTION_MARK ;Base : L_PARENTHESES RE R_PARENTHESES | L_SQUARE_BRACK SQUARE_PART R_SQUARE_BRACK | D_QUOTATION QUOTA_PART {printf("QUOTATION END\n");} | L_CURLY_BRACES ID {printf("Confirm an ID\n\n");} R_CURLY_BRACES | DOT | LETTER | NUMBER | RE_LETTER ;QUOTA_PART : LETTER QUOTA_PART | LETTER D_QUOTATION ;SQUARE_PART : LETTER HORIZONTAL_LINE LETTER | NUMBER HORIZONTAL_LINE NUMBER ;ID : ID LETTER | ID NUMBER | LETTER ;%%
输入测试文件
letter = [A-Z]number = [0-9]id = ({letter}_)+({number}|{letter})*
测试结果
TOKEN:LETTERTOKEN:LETTERTOKEN:LETTERTOKEN:LETTERTOKEN:LETTERTOKEN:LETTER TOKEN:EQUAL==Find an ID==0 No_select start TOKEN:L_SQUARE_BRACKTOKEN:LETTERTOKEN:HORIZONTAL_LINETOKEN:LETTERTOKEN:R_SQUARE_BRACKTOKEN:BLANKEnd of No_SelectFinish 1 No_Select==Find a RE==TOKEN:LETTER======Finish one line======...# 后面略去======Finish prog! End!======
生成中间语言(C语言)
考虑直接使用控制流的执行难度,这里用了正则语言的一个子集,去除了所有NFA
的不确定的东西。
由于YACC中生成的头文件中默认为自己定义的YYSTYPE
,这里需要在Makefile里面修改该关键字
Makefile如下:
CC = gcctest:parse example ./parse example gcc out.c -o testparse:lex.yy.c y.tab.c gcc lex.yy.c y.tab.c -o parsey.tab.c:RE.y yacc -d RE.y cp y.tab.h{,.tmp} sed 's/typedef int YYSTYPE/typedef char* YYSTYPE/' y.tab.h.tmp > y.tab.h rm y.tab.h.tmplex.yy.c:RE.l flex RE.lclean: -rm out.c -rm test -rm parse -rm lex.yy.c -rm y.tab.c
Re.y
中的语法定义部分如下:
%%List : Named_RE List | Named_RE ;Named_RE : ID { printf("== Find an ID ==> %s ==\n\n",$1); gen_id($1); } EQUAL RE {printf("== Find a RE== \n\n"); gen_id_trail();} BLANK ;RE : { printf("%d No_select start\n",debug_i); debug_i++;} No_Select { printf("Finish %d No_Select\n",debug_i); debug_i--;} ;No_Select : No_Select {printf("Find next join\n");} No_Join {base_i++;} | No_Join {base_i++;printf("End of No_Select\n");} ;No_Join : Base ;Base : L_PARENTHESES RE R_PARENTHESES | L_SQUARE_BRACK SQUARE_PART R_SQUARE_BRACK | D_QUOTATION QUOTA_PART {printf("= QUOTATION %s =\n",$2);gen_quote($2);} D_QUOTATION | L_CURLY_BRACES ID {printf("= Call an ID %s =\n",$2);gen_call($2);} R_CURLY_BRACES | DOT {gen_dot();} | LETTER {gen_next($1);} | NUMBER {gen_next($1);} | RE_LETTER {gen_next($1);} ;QUOTA_PART : QUOTA_PART LETTER {strcat($1,$2);} | LETTER | QUOTA_PART NUMBER {strcat($1,$2);} | NUMBER ;SQUARE_PART : LETTER HORIZONTAL_LINE LETTER {gen_square($1,$3);} | NUMBER HORIZONTAL_LINE NUMBER {gen_square($1,$3);} ;ID : ID LETTER {strcat($1,$2);} | ID NUMBER {strcat($1,$2);} | LETTER ;%%int main(int argc, char* argv[]){ if(argc == 2){ yyin = fopen(argv[1],"r"); if(yyin == NULL){ printf("Cannot open file!\n"); exit(0); } outfile = fopen("out.c","w"); gen_head(); yyparse(); gen_main(); fclose(outfile); } else{ printf("Usage:./test [filename]\n"); } return 0;}int yyerror(char *msg){ printf("Error encountered: %s \n", msg); return 0;}
其中用于生成C
语言中间代码的功能函数如下:
void gen_main(){ fprintf(outfile,"int main(int argc,char* argv[]){\n"); fprintf(outfile,"\tcur_pos = 0;\n"); fprintf(outfile,"\tif(argc == 2){\n"); fprintf(outfile,"\t\tstrcpy(in_str,argv[1]);\n"); fprintf(outfile,"\t\ttarget();\n"); fprintf(outfile,"\t\tprintf(\"ACCEPT\\n\");\n"); fprintf(outfile,"\t}\n"); fprintf(outfile,"\treturn 0;\n"); fprintf(outfile,"}\n");}
由于大部分类似,这里就不贴过多代码了。
测试文件:
letter = [A-Z]num = [0-9]target = "test"{letter} {num}.
生成C语言代码
#include <stdio.h>#include <stdlib.h>#include <string.h>char in_str[1000];int cur_pos;void letter(){ if(in_str[cur_pos]>=65&&in_str[cur_pos]<=90){ cur_pos++; } else{ printf("ERROR DOT\n");exit(0); }}void num(){ if(in_str[cur_pos]>=48&&in_str[cur_pos]<=57){ cur_pos++; } else{ printf("ERROR DOT\n");exit(0); }}void target(){ if(in_str[cur_pos]=='t'){ cur_pos++; } else{ printf("ERROR t\n");exit(0); } if(in_str[cur_pos]=='e'){ cur_pos++; } else{ printf("ERROR e\n");exit(0); } if(in_str[cur_pos]=='s'){ cur_pos++; } else{ printf("ERROR s\n");exit(0); } if(in_str[cur_pos]=='t'){ cur_pos++; } else{ printf("ERROR t\n");exit(0); } letter(); num(); if(in_str[cur_pos]>=33&&in_str[cur_pos]<=126){ cur_pos++; } else{ printf("ERROR DOT\n");exit(0); }}int main(int argc,char* argv[]){ cur_pos = 0; if(argc == 2){ strcpy(in_str,argv[1]); target(); printf("ACCEPT\n"); } return 0;}
测试:
./test testA9EACCEPT
- 正则语言引擎:一个简单LEX和YACC结合运用的实例
- Lex和Yacc从入门到精通(3)--一个极其简单的lex和yacc程序
- Lex和Yacc从入门到精通(3)--一个极其简单的lex和yacc程序
- lex yacc简单实例
- Lex与Yacc的结合
- Lex与Yacc的结合
- Lex/Yacc Lex结合Yacc
- 用Lex和Yacc做一个简单的SQL解释器
- LineCalc,一个基于Lex&Yacc的简单行计算工具
- yacc和lex的林林总总
- LEX和YACC的使用
- Lex和Yacc的入门
- C语言的lex和yacc工具说明
- 使用Lex和Yacc开发C语言的编译器
- C语言的lex和yacc工具说明
- C语言的lex和yacc工具说明
- 使用Lex和Yacc开发C语言的编译器
- C语言的lex和yacc工具说明
- 网络编程之开源项目和JSON解析技术的使用——简易新闻客户端
- socket系列(一)——socket实现推送
- android18
- css之元素的显示方式
- tensorflow实现对图片的读取
- 正则语言引擎:一个简单LEX和YACC结合运用的实例
- spark 2.1 TaskResult
- postfix
- android18
- 用c语言链表实现通讯录
- 列出联通集
- android18
- 数据结构基本概念
- Android-mvp&mvvm分别实现列表加载-demo