Lex工具
-------
Lex工具是一種詞法分析程序生成器,它可以根據詞法規(guī)則說明書的要求來生成單詞識別程序,由該程序識別出輸入文本中的各個單詞。
1、lex程序的結構
-定義部分
-規(guī)則部分
-用戶子程序部分
其中規(guī)則部分是必須的,定義和用戶子程序部分是任選的。
(1) 定義部分
定義部分起始于"%{"符號,終止于"%}"符號,其間可以是包括include語句、聲明語句在內的C語句。
%{
#include "stdio.h"
#include "y.tab.h"
extern int lineno;
%}
(2) 規(guī)則部分
規(guī)則部分起始于"%%"符號,終止于"%%"符號,其間則是詞法規(guī)則。詞法規(guī)則由模式和動作兩部分組成。模式部分可以由任意的正則表達式組成,動作部分是由C語言語句組成,這些語句用來對所匹配的模式進行相應處理。需要注意的是,lex將識別出來的單詞存放在yytext[]字符數據中,因此該數組的內容就代表了所識別出來的單詞的內容。
%%
[\t] {;}
[0-9]+\.?[0-9]*\.[0-9]+
{ sscanf(yytext,"%1f", &yylval.val);
return NUMBER; }
\n { lineno++;return ''\n''; }
. { return yytex+[0]; }
%%
(3) 用戶子程序部分
用戶子程序部分可以包含用C語言編寫的子程序,而這些子程序可以用在前面的動作中,這樣就可以達到簡化編程的目的。下面是帶有用戶子程序的lex程序片段。
"/*" skipcmnts();
. /* rest of rules */
%%
skipcmnts()
{
for ( ; ; )
{
while (input()!=''*'');
if(input()!=''/'')
unput(yytext[yylen-1]);
else return;
}
2、lex工具的使用方法
首先編寫一個lex程序
vi lex.l
%{
#include "stdio.h"
%}
%%
[\n] ;
[0-9]+ printf("Interger: %s \n",yytext);
[0-9]*\.[0-9]+ printf("Float: %s\n",yytext);
[a-zA-Z][a-zA-Z0-9]* printf("Word:%s\n",yytext);
. printf("Other symbol:%c\n",yytext[0]);
%%
然后使用lex將lex.l轉換成C語言程序
$lex lex.l
使用上述命令產生的C語言程序為lex.yy.c
然后使用C編譯程序將lex.yy.c編譯成可執(zhí)行程序regn
$cc -c lex.yy.c
$cc lex.yy.o -ll -o regn
下面可以使用regn來識別單詞
$vi testfile
x=355
y=113
p=x/y
# ./regn < testfile
Word:x
Other symbol:=
Interger: 355
Word:y
Other symbol:=
Interger: 113
Word:p
Other symbol:=
Word:x
Other symbol:/
Word:y
#
yacc工具
--------
yacc工具是一種語法分析程序生成器,它可以將有關某種語言的語法說明書轉換成相應的語法分析程序,由該程序完成對相應語言中語句的語法分析工作。
1、yacc程序結構
在使用yacc工具前,必須首先編寫yacc程序,因為有關語法分析程序是根據yacc程序生成的。yacc程序實際上是有關語法規(guī)則的說明書,它也是由定義部分、規(guī)則部分和子程序部分組成的。yacc程序的定義部分類似于lex程序的定義部分,只是在其后可帶有yacc聲明,其中包括詞法單詞、語法變量、優(yōu)先級和結合性信息。yacc程序的規(guī)則部分由語法規(guī)則和相應的動作組成,子程序部分可以包括在前面規(guī)則部分用到的子程序定義。接下來是main主程序,它調用yyparse子程序來對輸入進行語法分析,而yyparse反復地調用yylex子程序來獲得輸入單詞,在語法出錯時可通過yyerror子程序來處理。
2、yacc工具的使用方法
實例:我們將yacc程序分成片段,把這些片段組合在一起就是yacc程序。我們要使用的語法規(guī)則是一個有關四則運算的語法規(guī)則,可用BNF范式描述
list: expr \n
list expr \n
expr :NUMBER
expr + expr
expr - expr
expr * expr
expr / expr
(expr)
其含義是list是一個表達式序列,每個后面帶有一個新行。表達式是一個數值,或是由運算符連起來的兩個表達式,以及用圓括號括起來的表達式。
下面是有關上述語法規(guī)則的yacc程序片段。
$vi hoc.y
%{
#define YYSTYPE double
%}
%token NUMBER
%left ''+'' ''-''
%left ''*'' ''/''
%%
list:
list ''\n''
list expr ''\n'' { printf("\t%. 8g\n",$2);}
;
expr : NUMBER {$$=$1;}
expr ''+'' expr {$$ = $1 + $3; }
expr ''-'' expr {$$ = $1 - $3; }
expr ''*'' expr {$$ = $1 * $3; }
expr ''/'' expr {$$ = $1 / $3; }
''(''expr'')'' {$$ = $2; }
%%
上述yacc程序片段實際上是它的定義部分和規(guī)則部分。在yacc聲明部分,%token NUMBER表明了NUMBER是一個單詞符號,%left則表明了運算符號的左結合性,并且''*''和''/''和優(yōu)先級比''+''和''-''的優(yōu)先級高。在yacc程序的規(guī)則部分,備用規(guī)則是用''''隔開的,規(guī)則中的動作實際上是C語句序列,其中$n(即$1,$2等)是用來引用規(guī)則中的第幾個成份,而$$則代表了整個規(guī)則的返回值。
下面的yacc程序片段是main主程序
#include <stdio.h>
#include <ctype.h>
char *progname;
int lineno=1;
main(argc,argv)
int argc;
char *argv[];
{ progname = argv[0];
yyparse();
}
main主程序調用yyparse子程序來處理來處理輸入,而yyparse又是通過yylex子程序來獲得輸入單詞并通過yyerror子程序來報告出錯信息。下面是有關這兩個子程序的yacc程序片段
yylex()
{ int c;
while ((c=getchar()) == '' '' c==''\t'') ;
if (c==EOF)
return 0;
if (c==''.''isdigit(c)){
ungetc(c,stdin);
scanf("%lf", &yylval);
return NUMBER;
}
if(c==''\n'')
lineno++;
return c;
}
yyerror(s)
char *s;
{ warning (s,(char *)0);
}
warning(s,t)
char *s,*t;
{ fprintf(stderr,"%s:%s",progname,s);
if(t)
fprintf(stderr,"%s",t);
fprintf(stderr," near line %d\n",lineno);
}
這樣就完成了整個yacc程序
接下來就使用 yacc將hoc.y轉換成C語言程序
$yacc hoc.y
使用上述命令產生的C語言程序為y.tab.c,這時可以使用C編譯程序將它編譯成可執(zhí)行程序hoc.
$cc y.tab.c -o hoc
下面是使用hoc的例子
# ./hoc
4*3*2
24
(1+2)*(3+4)
21
1/2
0.5
355/133
2.6691729
-3-4
./hoc:Syntax error near line 5
上述結果顯示中,分別表明了計算結果,最后一次計算出錯的原因是由于在規(guī)則定義中未來定義單目減運算符號。