[0123456789]+
為了簡(jiǎn)化書(shū)寫(xiě)起見(jiàn),也可以寫(xiě)成如下的格式:
[0-9]+
對(duì)于任意單詞,其中只能包含字母,所以單詞的正則表達(dá)式為:
[a-zA-Z]+
Flex支持如下類(lèi)型的正則表達(dá)式:
x 符合字符串"x"
. 除了換行以外的任何字符
[xyz] 一個(gè)字符類(lèi),在這個(gè)例子中,輸入必須符合要么是'x’要么是'y'要么是'z'
[abj-oZ] 一個(gè)帶范圍的字符類(lèi),符合任何滿(mǎn)足'a', 'b', 從'j'到'o'還有'Z'
[^A-Z] 一個(gè)取反的字符類(lèi),比如任何字母除了大寫(xiě)字母。
[^A-Z\n] 任何字符除了大寫(xiě)字母和換行
r* 零個(gè)或更多r,r可以是任意正則表達(dá)式
r+ 一個(gè)或更多r
r? 零個(gè)或最多一個(gè)r
r{2,5} 任何2到5個(gè)r
r{2,} 2個(gè)或更多r
r{4} 正好4個(gè)r
{name} 對(duì)name的擴(kuò)展
"[xyz]\"foo"
符合正則表達(dá)式 [xyz]"foo 的字符串
\X 如果X是一個(gè)'a', 'b', 'f', 'n', 'r', 't', 或者'v',
則按照ASCII碼\x轉(zhuǎn)義符進(jìn)行處理,否則,其他的'X'將用來(lái)做取消處理符處理
\0 一個(gè)ASCII碼空字符
\123 內(nèi)容為八進(jìn)制123的char型
\x2a 內(nèi)容為十六進(jìn)制0x2a的char型
(r) 符合一個(gè)r,括號(hào)是用來(lái)越過(guò)優(yōu)先級(jí)的
rs 正則表達(dá)式r,緊跟著一個(gè)
r|s 要么是r要么是s
^r 一個(gè)r,但是必須是在一行的開(kāi)始
r$ 一個(gè)r,但是必須是在行末
<s>r 一個(gè)r,但是之前字符串必須符合條件s
<s1,s2,s3>r
同上,但是必須之前字符串符合s1或者s2或者s3
<*>r 不考慮開(kāi)始字符串類(lèi)型,只符合r
<<EOF>> 文件末尾
<s1,s2><<EOF>>
前面符合s1或者s2的文件末尾
Flex甚至Bison代碼都有如下的編寫(xiě)格式:
/* 定義段 */
%%
/* Flex、Bison代碼段(規(guī)則) */
%%
/* 輔助代碼段,C語(yǔ)言 */
首先,使用vi編譯器,輸入以下代碼(test.l):
// 定義段代碼
%{ // 這種括號(hào)說(shuō)明內(nèi)部的代碼不許flex處理,直接進(jìn)入.C文件
#include <stdio.h>
%}
%%
// 詞法規(guī)則段代碼
[0123456789]+ printf("NUMBER"); // 數(shù)字類(lèi)型字符串
[a-zA-Z]+ {
printf("WO"); // 單詞類(lèi)型字符串(WORD)
printf("RD");
}
%%
// 輔助C語(yǔ)言函數(shù)代碼(直接寫(xiě)C語(yǔ)言,無(wú)須括號(hào),我們這里無(wú)內(nèi)容)
下面我們首先使用lex程序生成所需的狀態(tài)機(jī)代碼:
flex -otest.c test.l # 從正則表達(dá)式聲稱(chēng)對(duì)應(yīng)的C語(yǔ)言代碼,注意-o后不要有空格(flex bug?)
gcc test.c -o test -lfl # 從C語(yǔ)言代碼生成二進(jìn)制,從而運(yùn)行,-lfl說(shuō)明鏈接libfl.a庫(kù)文件
./test # 運(yùn)行剛剛生成的二進(jìn)制
下面我們來(lái)對(duì)剛生成的二進(jìn)制進(jìn)行試驗(yàn),以弄清楚flex到底是做什么的,在test程序中輸入
以下內(nèi)容,按下Ctrl-D可以退出test程序:
3505
hello
what is 3505
通過(guò)這些試驗(yàn),相信您已經(jīng)明白了Flex的用途,每當(dāng)一個(gè)正則表達(dá)式符合時(shí),flex生成的代
碼就會(huì)自動(dòng)調(diào)用對(duì)應(yīng)的函數(shù),或者運(yùn)行對(duì)應(yīng)的程序。下面我們對(duì)這個(gè)例子進(jìn)行一些深入研究,處理一個(gè)類(lèi)似C語(yǔ)言的程序配置腳本代碼。首先,一個(gè)演示腳本如下:
logging {
category lame-servers { null; };
category cname { null; };
};
zone "." {
type hint;
file "/etc/bind/db.root";
}
在這個(gè)腳本中,我們可以看到一系列的詞匯類(lèi)型:
- 單詞(WORD),比如'zone', 'type'
- 文件名(FILENAME),比如'/etc/bind/db.root'
- 引號(hào)(QUOTE),比如文件名兩邊的
- 左花括號(hào)(OBRACE),'{'
- 右花括號(hào)(EBRACE),'}'
- 分號(hào)(SEMICOLON),';'
我們修改后的Lex代碼如下:
%{
#include <stdio.h>
%}
%%
[a-zA-Z][a-zA-Z0-9]* printf("WORD "); /* 字符串 */
[a-zA-Z0-9\/.-]+ printf("FILENAME "); /* 文件名 */
\" printf("QUOTE "); /* 引號(hào)" */
\{ printf("OBRACE "); /* 左花括號(hào) */
\} printf("EBRACE "); /* 右花括號(hào) */
; printf("SEMICOLON "); /* 分號(hào) */
\n printf("\n"); /* 換行 */
[ \t]+ /* 忽略空白字符 */
%%
int yywrap(void) /* 當(dāng)詞法分析器到了文件末尾做什么 */
{
return 1; /* 返回1,說(shuō)明停止前進(jìn),0則繼續(xù) */
}
int yyerror(char *s) /* 錯(cuò)誤信息打印函數(shù) */
{
fprintf(stderr, "%s\n", s);
return 0;
}
int main(int argc, char *argv[])
{
FILE *fp;
fp = fopen(argv[1], "r"); /* 首先打開(kāi)要被處理的文件(參數(shù)1) */
yyin = fp; /* yyin是lex默認(rèn)的文件輸入指針,則不處理控制臺(tái)輸入 */
yylex(); /* 調(diào)用lex的工作函數(shù),從這里開(kāi)始,lex的詞法分析器開(kāi)始工作 */
fclose(fp);
return 0;
}
到這里,我們已經(jīng)完全可以對(duì)一個(gè)包含各種類(lèi)型詞組的源代碼文件進(jìn)行分析,得出其中各類(lèi)型詞組的
排列順序。在一個(gè)規(guī)范的基于語(yǔ)法的源代碼中,詞組的順序從一定意義上來(lái)說(shuō),就是語(yǔ)法。對(duì)于源代碼,lex的處理能力也就到這里為止,但是我們并沒(méi)有完全展
示lex的所有功能,讀者如果有興趣,可以繼續(xù)深入閱讀本文提供的參考文獻(xiàn)。如何進(jìn)行語(yǔ)法分析?我們?cè)谙旅娴恼鹿?jié)中講開(kāi)始講述Bison的基本使用方法,
以及如何使用Bison進(jìn)行語(yǔ)法分析。
x = 0;
while(x < 3) {
print(x);
x = x + 1;
}
例子中得到的輸出結(jié)果如下:
0
1
2
首先是我們的全局頭文件test3.h:
typedef enum {
typeCon,
typeId,
typeOpr
} nodeEnum;
/* constants */
typedef struct {
int value; /* value of constant */
} conNodeType;
/* identifiers */
typedef struct {
int i; /* subscript to sym array */
} idNodeType;
/* operators */
typedef struct {
int oper; /* operator */
int nops; /* number of operands */
struct nodeTypeTag *op[1]; /* operands (expandable) */
} oprNodeType;
typedef struct nodeTypeTag {
nodeEnum type; /* type of node */
/* union must be last entry in nodeType */
/* because operNodeType may dynamically increase */
union {
conNodeType con; /* constants */
idNodeType id; /* identifiers */
oprNodeType opr; /* operators */
};
} nodeType;
extern int sym[26];
下面是Flex語(yǔ)言文件test3ll.l:
%{
#include <stdlib.h>
#include "test3.h"
#include "test3yy.h"
int yyerror(char *);
%}
%%
[a-z] {
yylval.sIndex = *yytext - 'a';
return VARIABLE;
}
[0-9]+ {
yylval.iValue = atoi(yytext);
return INTEGER;
}
[-()<>=+*/;{}.] {
return *yytext;
}
">=" return GE;
"<=" return LE;
"==" return EQ;
"!=" return NE;
"while" return WHILE;
"if" return IF;
"else" return ELSE;
"print" return PRINT;
[ \t\n]+ ; /* ignore whitespace */
. yyerror("Unknown character");
%%
int yywrap(void)
{
return 1;
}
然后是我們的Bison文件test3yy.y:
%{
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include "test3.h"
/* prototypes */
nodeType *opr(int oper, int nops, ...);
nodeType *id(int i);
nodeType *con(int value);
void freeNode(nodeType *p);
int yylex(void);
int yyerror(char *s);
int sym[26];
%}
%union {
int iValue; /* integer value */
char sIndex; /* symbol table index */
nodeType *nPtr; /* node pointer */
};
%token <iValue> INTEGER
%token <sIndex> VARIABLE
%token WHILE IF PRINT
%nonassoc IFX
%nonassoc ELSE
%left GE LE EQ NE '>' '<'
%left '+' '-'
%left '*' '/'
%nonassoc UMINUS
%type <nPtr> stmt expr stmt_list
%%
program:
function { exit(0); }
;
function:
function stmt { ex($2); freeNode($2); }
|
;
stmt:
';' { $$ = opr(';', 2, NULL, NULL); }
| expr ';' { $$ = $1; }
| PRINT expr ';' { $$ = opr(PRINT, 1, $2); }
| VARIABLE '=' expr ';' { $$ = opr('=', 2, id($1), $3); }
| WHILE '(' expr ')' stmt { $$ = opr(WHILE, 2, $3, $5); }
| IF '(' expr ')' stmt %prec IFX{ $$ = opr(IF, 2, $3, $5); }
| IF '(' expr ')' stmt ELSE stmt{ $$ = opr(IF, 3, $3, $5, $7); }
| '{' stmt_list '}' { $$ = $2; }
;
stmt_list:
stmt { $$ = $1; }
| stmt_list stmt { $$ = opr(';', 2, $1, $2); }
;
expr:
INTEGER { $$ = con($1); }
| VARIABLE { $$ = id($1); }
| '-' expr %prec UMINUS { $$ = opr(UMINUS, 1, $2); }
| expr '+' expr { $$ = opr('+', 2, $1, $3); }
| expr '-' expr { $$ = opr('-', 2, $1, $3); }
| expr '*' expr { $$ = opr('*', 2, $1, $3); }
| expr '/' expr { $$ = opr('/', 2, $1, $3); }
| expr '<' expr { $$ = opr('<', 2, $1, $3); }
| expr '>' expr { $$ = opr('>', 2, $1, $3); }
| expr GE expr { $$ = opr(GE , 2, $1, $3); }
| expr LE expr { $$ = opr(LE , 2, $1, $3); }
| expr NE expr { $$ = opr(NE , 2, $1, $3); }
| expr EQ expr { $$ = opr(EQ , 2, $1, $3); }
| '(' expr ')' { $$ = $2; }
;
%%
#define SIZEOF_NODETYPE ((char*)&p->con - (char*)p)
nodeType *con(int value) {
nodeType *p;
size_t nodeSize;
/* allocate node */
nodeSize = SIZEOF_NODETYPE + sizeof(conNodeType);
if((p = malloc(nodeSize)) == NULL)
yyerror("out of memory");
/* copy information */
p->type = typeCon;
p->con.value = value;
return p;
}
nodeType *id(int i) {
nodeType *p;
size_t nodeSize;
/* allocate node */
nodeSize = SIZEOF_NODETYPE + sizeof(idNodeType);
if((p = malloc(nodeSize)) == NULL)
yyerror("out of memory");
/* copy information */
p->type = typeId;
p->id.i = i;
return p;
}
nodeType *opr(int oper, int nops, ...) {
va_list ap;
nodeType *p;
size_t nodeSize;
int i;
/* allocate node */
nodeSize = SIZEOF_NODETYPE + sizeof(oprNodeType) +
(nops - 1) * sizeof(nodeType*);
if((p = malloc(nodeSize)) == NULL)
yyerror("out of memory");
/* copy information */
p->type = typeOpr;
p->opr.oper = oper;
p->opr.nops = nops;
va_start(ap, nops);
for(i = 0; i < nops; i++)
p->opr.op[i] = va_arg(ap, nodeType*);
va_end(ap);
return p;
}
void freeNode(nodeType *p) {
int i;
if(!p) return;
if(p->type == typeOpr) {
for(i=0; i<p->opr.nops; i++)
freeNode(p->opr.op[i]);
}
free(p);
}
int yyerror(char *s) {
fprintf(stdout, "%s\n", s);
}
int main(void) {
yyparse();
return 0;
}
上面的Flex和Bison代碼所作的工作是根據(jù)語(yǔ)法建立語(yǔ)意描述樹(shù)結(jié)構(gòu)。延續(xù)我們上面計(jì)算器
的例子,我們寫(xiě)出如何翻譯這些語(yǔ)言的實(shí)現(xiàn)部分(對(duì)生成的樹(shù)進(jìn)行第歸分析):
#include <stdio.h>
#include "test3.h"
#include "test3yy.h"
int ex(nodeType *p) {
if(!p) return 0;
switch(p->type) {
case typeCon:
return p->con.value;
case typeId:
return sym[p->id.i];
case typeOpr:
switch(p->opr.oper) {
case WHILE:
while(ex(p->opr.op[0]))
ex(p->opr.op[1]);
return 0;
case IF:
if(ex(p->opr.op[0]))
ex(p->opr.op[1]);
else if(p->opr.nops > 2)
ex(p->opr.op[2]);
return 0;
case PRINT:
printf("%d\n", ex(p->opr.op[0]));
return 0;
case ';':
ex(p->opr.op[0]);
return ex(p->opr.op[1]);
case '=':
return sym[p->opr.op[0]->id.i] = ex(p->opr.op[1]);
case UMINUS:
return -ex(p->opr.op[0]);
case '+':
return ex(p->opr.op[0]) + ex(p->opr.op[1]);
case '-':
return ex(p->opr.op[0]) - ex(p->opr.op[1]);
case '*':
return ex(p->opr.op[0]) * ex(p->opr.op[1]);
case '/':
return ex(p->opr.op[0]) / ex(p->opr.op[1]);
case '<':
return ex(p->opr.op[0]) < ex(p->opr.op[1]);
case '>':
return ex(p->opr.op[0]) > ex(p->opr.op[1]);
case GE:
return ex(p->opr.op[0]) >= ex(p->opr.op[1]);
case LE:
return ex(p->opr.op[0]) <= ex(p->opr.op[1]);
case NE:
return ex(p->opr.op[0]) != ex(p->opr.op[1]);
case EQ:
return ex(p->opr.op[0]) == ex(p->opr.op[1]);
}
}
}
一般實(shí)際的編譯器都是以匯編代碼輸出的,所以我們?cè)谶@里進(jìn)行一些深入研究,得出了另一個(gè)版本的
ex函數(shù)實(shí)現(xiàn),能夠?qū)崿F(xiàn)匯編代碼的輸出(compiler.c):
#include <stdio.h>
#include "test3.h"
#include "test3yy.h"
static int lbl;
int ex(nodeType *p) {
int lbl1, lbl2;
if(!p) return 0;
switch(p->type) {
case typeCon:
printf("\tpush\t%d\n", p->con.value);
break;
case typeId:
printf("\tpush\t%c\n", p->id.i + 'a');
break;
case typeOpr:
switch(p->opr.oper) {
case WHILE:
printf("L%03d:\n", lbl1 = lbl++);
ex(p->opr.op[0]);
printf("\tjs\tL%03d\n", lbl2 = lbl++);
ex(p->opr.op[1]);
printf("\tjz\tL%03d\n", lbl1);
printf("L%03d:\n", lbl2);
break;
case IF:
ex(p->opr.op[0]);
if(p->opr.nops > 2) {
/* if else */
printf("\tjs\tL%03d\n", lbl1 = lbl++);
ex(p->opr.op[1]);
printf("\tjmp\tL%03d\n", lbl2 = lbl++);
printf("L%03d:\n", lbl1);
ex(p->opr.op[2]);
printf("L%03d:\n", lbl2);
} else {
/* if */
printf("\tjs\tL%03d\n", lbl1 = lbl++);
ex(p->opr.op[1]);
printf("L%03d:\n", lbl1);
}
break;
case PRINT:
ex(p->opr.op[0]);
printf("\tprint\n");
break;
case '=':
ex(p->opr.op[1]);
printf("\tpop\t%c\n", p->opr.op[0]->id.i + 'a');
break;
case UMINUS:
ex(p->opr.op[0]);
printf("\tneg\n");
break;
default:
ex(p->opr.op[0]);
ex(p->opr.op[1]);
switch(p->opr.oper) {
case '+': printf("\tadd\n"); break;
case '-': printf("\tsub\n"); break;
case '*': printf("\tmul\n"); break;
case '/': printf("\tdiv\n"); break;
case '<': printf("\tcompLT\n"); break;
case '>': printf("\tcompGT\n"); break;
case GE: printf("\tcompGE\n"); break;
case LE: printf("\tcompLE\n"); break;
case NE: printf("\tcompNE\n"); break;
case EQ: printf("\tcompEQ\n"); break;
}
}
}
return 0;
}
使用方法:取消interpreter.c的編譯,取而代之用compiler.c即可。關(guān)于
這個(gè)計(jì)算器的代碼分析,請(qǐng)等待后續(xù),(未完待續(xù))