Lex和Yacc應用方法(一).初識Lex
草木瓜 20070301
Lex(Lexical Analyzar 詞法分析產生器),Yacc(Yet Another Compiler Compiler
編譯器代碼產生器)是Unix下十分重要的詞法分析,文法分析的工具。經常用於語言分
析,公式編譯等廣泛領域。遺憾的是網上中文資料介紹不是過於簡單,就是跳躍太大,
入門參考意義並不大。本文通過循序漸進的例子,從0開始瞭解掌握Lex和Yacc的用法。
<本系列文章的地址:http://blog.csdn.net/liwei_cmg/category/207528.aspx>
一.Lex(Lexical Analyzar) 初步樣本
先看簡單的例子(註:本文所有執行個體皆在RetHat Linux下完成):
一個簡單的Lex檔案 exfirst.l 內容:
%{
#include "stdio.h"
%}
%%
[/n] ;
[0-9]+ printf("Int : %s/n",yytext);
[0-9]*/.[0-9]+ printf("Float : %s/n",yytext);
[a-zA-Z][a-zA-Z0-9]* printf("Var : %s/n",yytext);
[/+/-/*///%] printf("Op : %s/n",yytext);
. printf("Unknown : %c/n",yytext[0]);
%%
在命令列下執行命令flex解析,會自動產生lex.yy.c檔案:
[root@localhost liweitest]flex exfirst.l
進行編譯產生parser可執行程式:
[root@localhost liweitest]cc -o parser lex.yy.c -ll
[注意:如果不加-ll鏈結選項,cc編譯時間會出現以下錯誤,後面會進一步說明。]
/usr/lib/gcc-lib/i386-redhat-linux/3.2.2/../../../crt1.o(.text+0x18): In function `_start':
../sysdeps/i386/elf/start.S:77: undefined reference to `main'
/tmp/cciACkbX.o(.text+0x37b): In function `yylex':
: undefined reference to `yywrap'
/tmp/cciACkbX.o(.text+0xabd): In function `input':
: undefined reference to `yywrap'
collect2: ld returned 1 exit status
建立待解析的檔案 file.txt:
title
i=1+3.9;
a3=909/6
bcd=4%9-333
通過已產生的可執行程式,進行檔案解析。
[root@localhost liweitest]# ./parser < file.txt
Var : title
Var : i
Unknown : =
Int : 1
Op : +
Float : 3.9
Unknown : ;
Var : a3
Unknown : =
Int : 909
Op : /
Int : 6
Var : bcd
Unknown : =
Int : 4
Op : %
Int : 9
Op : -
Int : 333
到此Lex用法會有個直觀的瞭解:
1.定義Lex描述檔案
2.通過lex,flex工具解析成lex.yy.c檔案
3.使用cc編譯lex.yy.c產生可執行程式
再來看一個比較完整的Lex描述檔案 exsec.l :
%{
#include "stdio.h"
int linenum;
%}
%%
title showtitle();
[/n] linenum++;
[0-9]+ printf("Int : %s/n",yytext);
[0-9]*/.[0-9]+ printf("Float : %s/n",yytext);
[a-zA-Z][a-zA-Z0-9]* printf("Var : %s/n",yytext);
[/+/-/*///%] printf("Op : %s/n",yytext);
. printf("Unknown : %c/n",yytext[0]);
%%
showtitle()
{
printf("----- Lex Example -----/n");
}
int main()
{
linenum=0;
yylex(); /* 進行分析 */
printf("/nLine Count: %d/n",linenum);
return 0;
}
int yywrap()
{
return 1;
}
進行解析編譯:
[root@localhost liweitest]flex exsec.l
[root@localhost liweitest]cc -o parser lex.yy.c
[root@localhost liweitest]./parser < file.txt
----- Lex Example -----
Var : i
Unknown : =
Int : 1
Op : +
Float : 3.9
Unknown : ;
Var : a3
Unknown : =
Int : 909
Op : /
Int : 6
Var : bcd
Unknown : =
Int : 4
Op : %
Int : 9
Op : -
Int : 333
Line Count: 4
這裡就沒有加-ll選項,但是可以編譯通過。下面開始著重整理下Lex描述檔案.l。
二.Lex(Lexical Analyzar) 描述檔案的結構介紹
Lex工具是一種詞法剖析器產生器,它可以根據詞法規則說明書的要求來產生單詞識
別程式,由該程式識別出輸入文本中的各個單詞。 一般可以分為<定義部分><規則部
分><使用者子程式部分>。其中規則部分是必須的,定義和使用者子程式部分是任選的。
(1)定義部分
定義部分起始於 %{ 符號,終止於 %} 符號,其間可以是包括include語句、聲明語句
在內的C語句。這部分跟普通C程式開頭沒什麼區別。
%{
#include "stdio.h"
int linenum;
%}
(2) 規則部分
規則部分起始於"%%"符號,終止於"%%"符號,其間則是詞法規則。詞法規則由模式和
動作兩部分組成。模式部分可以由任意的Regex組成,動作部分是由C語言語句組
成,這些語句用來對所匹配的模式進行相應處理。需要注意的是,lex將識別出來的單
詞存放在yytext[]字元資料中,因此該數組的內容就代表了所識別出來的單詞的內容。
類似yytext這些預定義的變數函數會隨著後面內容展開一一介紹。動作部分如果有多
行執行語句,也可以用{}括起來。
%%
title showtitle();
[/n] linenum++;
[0-9]+ printf("Int : %s/n",yytext);
[0-9]*/.[0-9]+ printf("Float : %s/n",yytext);
[a-zA-Z][a-zA-Z0-9]* printf("Var : %s/n",yytext);
[/+/-/*///%] printf("Op : %s/n",yytext);
. printf("Unknown : %c/n",yytext[0]);
%%
A.規則部分的Regex
規則部分是Lex描述檔案中最為複雜的一部分,下面列出一些模式部分的Regex字
符含義:
A-Z, 0-9, a-z 構成模式部分的字元和數字。
- 指定範圍。例如:a-z 指從 a 到 z 之間的所有字元。
/ 轉義元字元。用來覆蓋字元在此運算式中定義的特殊意義,
只取字元的本身。
[] 表示一個字元集合。匹配括弧內的任一字元。如果第一個字
符是^那麼它表示否定模式。例如: [abC] 匹配 a, b, 和C
的任何一個。
^ 表示否定。
* 匹配0個或者多個上述模式。
+ 匹配1個或者多個上述模式。
? 匹配0個或1個上述模式。
$ 作為模式的最後一個字元時匹配一行的結尾。
{ } 表示一個模式可能出現的次數。 例如: A{1,3} 表示 A 可
能出現1次或3次。[a-z]{5} 表示長度為5的,由a-z組成的
字元。此外,還可以表示預定義的變數。
. 匹配任一字元,除了 /n。
( ) 將一系列常規運算式分組。如:{Letter}({Letter}|{Digit})*
| 運算式間的邏輯或。
"一些符號" 字元的字面含義。元字元具有。如:"*" 相當於 [/*]。
/ 向前匹配。如果在匹配的模式中的"/"後跟有後續運算式,
只匹配模版中"/"前面的部分。如:模式為 ABC/D 輸入 ABCD,
時ABC會匹配ABC/D,而D會匹配相應的模式。輸入ABCE的話,
ABCE就不會去匹配ABC/D。
B.規則部分的優先順序
規則部分具有優先順序的概念,先舉個簡單的例子:
%{
#include "stdio.h"
%}
%%
[/n] ;
A {printf("ONE/n");};
AA {printf("TWO/n");};
AAAA {printf("THREE/n");};
%%
此時,如果輸入內容:
[root@localhost liweitest]# cat file1.txt
AAAAAAA
[root@localhost liweitest]# ./parser < file1.txt
THREE
TWO
ONE
Lex分析詞法時,是逐個字元進行讀取,自上而下進行規則匹配的,讀取到第一個A字元
時,遍曆後發現三個規則皆匹配成功,Lex會繼續分析下去,讀至第五個字元時,發現
"AAAA"只有一個規則可用,即按行為進行處理,以此類推。可見Lex會選擇最長的字元
匹配規則。
如果將規則
AAAA {printf("THREE/n");};
改為
AAAAA {printf("THREE/n");};
./parser < file1.txt 輸出結果為:
THREE
TWO
再來一個特殊的例子:
%%
title showtitle();
[a-zA-Z][a-zA-Z0-9]* printf("Var : %s/n",yytext);
%%
並輸入title,Lex解析完後發現,仍然存在兩個規則,這時Lex只會選擇第一個規則,下面
的則被忽略的。這裡就體現了Lex的順序優先順序。把這個例子稍微改一下:
%%
[a-zA-Z][a-zA-Z0-9]* printf("Var : %s/n",yytext);
title showtitle();
%%
Lex編譯時間會提示:warning, rule cannot be matched.這時處理title字元時,匹配
到第一個規則後,第二個規則就無效了。
再把剛才第一個例子修改下,加深下印象!
%{
#include "stdio.h"
%}
%%
[/n] ;
A {printf("ONE/n");};
AA {printf("TWO/n");};
AAAA {printf("THREE/n");};
AAAA {printf("Cannot be executed!");};
./parser < file1.txt 顯示效果是一樣的,最後一項規則肯定是會忽略掉的。
C.規則部分的使用變數
且看下面樣本:
%{
#include "stdio.h"
int linenum;
%}
int [0-9]+
float [0-9]*/.[0-9]+
%%
{int} printf("Int : %s/n",yytext);
{float} printf("Float : %s/n",yytext);
. printf("Unknown : %c/n",yytext[0]);
%%
在%}和%%之間,加入了一些類似變數的東西,注意是沒有;的,這表示int,float分
別代指特定的含義,在兩個%%之間,可以通過{int}{float}進行直接引用,簡化模
式定義。
(3) 使用者子程式部分
最後一個%%後面的內容是使用者子程式部分,可以包含用C語言編寫的子程式,而這些子
程式可以用在前面的動作中,這樣就可以達到簡化編程的目的。這裡需要注意的是,
當編譯時間不帶-ll選項時,是必須加入main函數和yywrap(yywrap將下後面說明)。如:
...
%%
showtitle()
{
printf("----- Lex Example -----/n");
}
int main()
{
linenum=0;
yylex(); /* 進行Lex分析 */
printf("/nLine Count: %d/n",linenum);
return 0;
}
int yywrap()
{
return 1;
}
三.Lex(Lexical Analyzar) 一些的內部變數和函數
內部預定義變數:
yytext char * 當前匹配的字串
yyleng int 當前匹配的字串長度
yyin FILE * lex當前的解析檔案,預設為標準輸出
yyout FILE * lex解析後的輸出檔案,預設為標準輸入
yylineno int 當前的行數資訊
內部預定義宏:
ECHO #define ECHO fwrite(yytext, yyleng, 1, yyout) 也是未匹配字元的
預設動作
內部預定義的函數:
int yylex(void) 調用Lex進行詞法分析
int yywrap(void) 在檔案(或輸入)的末尾調用。如果函數的傳回值是1,就停止解
析。 因此它可以用來解析多個檔案。代碼可以寫在第三段,這
樣可以解析多個檔案。 方法是使用 yyin 檔案指標指向不同的
檔案,直到所有的檔案都被解析。最後,yywrap() 可以返回1
來表示解析的結束。
lex和flex都是解析Lex檔案的工具,用法相近,flex意為fast lexical analyzer generator。
可以看成lex的升級版本。
相關更多內容就需要參考flex的man手冊了,十分詳盡。
四.關於Lex的一些綜述
Lex其實就是詞法分析器,通過設定檔*.l,依據Regex逐字元去順序解析檔案,
並動態更新記憶體的資料解析狀態。不過Lex只有狀態和狀態轉換能力。因為它沒有堆棧,
它不適合用於剖析外殼結構。而yacc增加了一個堆棧,並且能夠輕易處理像括弧這樣的
結構。Lex善長於模式比對,如果有更多的運算要求就需要yacc了。