本文先描述了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 \n
whitespace [ \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{
y