lex yacc 學習

Augusdi發表於2017-03-20


寫在前面的幾句廢話 

      最近在專案的過程中接觸了lex 和 yacc,他們可以幫助我們來實現自己的領域語言。最典型的應用就是可以幫助我們來實現自定義測試指令碼的執行器。但是,這裡也有一個限制,就是測試指令碼要做的基本事情必須有現成的C語言庫來實現,否則就做不到了;如果基本的操作是用java來做的,那麼還可以用Antlr,這裡不對Antlr做詳細介紹。


lex是什麼?

      教科書上把lex的作用的作用叫做“詞法分析 lexical analysis ”,這個中文叫法非常讓人看不明白(叫做“符號提取”更合適),其實從它的英文單詞lexical上來看他的意思其實是非常清楚的。

     lexical,在webster上的解釋是:of or relating to words or the vocabulary of a language as distinguished from its grammar and construction。

指的是: 一種語言中關於詞彙、單詞的,與之相對的是這種語言的語法和組織

     這麼來看的話 lexical analysis 的作用就應該是語言中的詞彙和單詞分析。事實上他的作用就是從語言中提取單詞。放到程式語言中來說,他要做的事情其實就是提取程式語言佔用的各種保留字、操作符等等語言的元素

      所以他的另外一個名字scanner其實更形象一些,就是掃描一個文字中的單詞。

      lex把每個掃面出來的單詞叫統統叫做token,token可以有很多類。對比自然語言的話,英語中的每個單詞都是token,token有很多類,比如non(名詞)就是一個類token,apple就是屬於這個型別的一個具體token。對於某個程式語言來說,token的個數是很有限的,不像英語這種自然語言中有幾十萬個單詞。

     lex工具會幫我們生成一個yylex函式,yacc通過呼叫這個函式來得知拿到的token是什麼型別的,但是token的型別是在yacc中定義的。

     lex的輸入檔案一般會被命名成 .l檔案,通過lex XX.l 我們得到輸出的檔案是lex.yy.c


yacc是什麼呢?

    剛才說完lex了,那麼yacc呢,教科書上把yacc做的工作叫做syntactic analysis。 這次我們翻譯沒有直譯做句法分析,而是叫語法分析,這個翻譯能好一點,意思也基本上比較清楚。
     其實我們最開始學習英語的時候老師都會告訴我們英語其實就是“單詞+語法”,這個觀點放到程式語言中很合適,lex提取了單詞,那麼是剩下的部分就是如何表達語法。那麼yacc做的事情就是這一部分(實際應該說是BNF來做的)。

     yacc會幫我們生成一個yyparse函式,這個函式會不斷呼叫上面的yylex函式來得到token的型別。

     yacc的輸入檔案一般會被命名成 .y檔案,通過yacc -d XX.y我們得到的輸出檔案是y.tab.h y.tab.c,前者包含了lex需要的token型別定義,需要被include進 .l檔案中 

 

lex和yacc的輸入檔案格式


Definition section
%%
Rules section

%%
C code section

.l和.y的檔案格式都是分成三段,用%%來分割,三個section的含義是: 

  • Definition Section 

這塊可以放C語言的各種各種include,define等宣告語句,但是要用%{ %}括起來。 

如果是.l檔案,可以放預定義的正規表示式:minus "-" 還要放token的定義,方法是:代號 正規表示式。然後到了,Rules Section就可以通過{符號} 來引用正規表示式

如果是.y檔案,可以放token的定義,如:%token INTEGER PLUS ,這裡的定一個的每個token都可以在y.tab.h中看到 

  •  Rules section

.l檔案在這裡放置的rules就是每個正規表示式要對應的動作,一般是返回一個token

.y檔案在這裡放置的rules就是滿足一個語法描述時要執行的動作

不論是.l檔案還是.y檔案這裡的動作都是用{}擴起來的,用C語言來描述,這些程式碼可以做你任何想要做的事情 

  •  C code Section

main函式,yyerror函式等的定義   

lex和yacc能幫我們做什麼?

一句話:解釋執行自定義語言。有幾點要注意: 

  1. 自定義語言的要做的事情必須可以能通過C語言來實現。其實任何計算機能做的事情都可以用C語言來實現,lex和yacc存在的意義在於簡化語言,讓使用者能夠以一種用比較簡單的語言來實現複雜的操作。比如:對於資料庫的查詢肯定有現成的庫可以來完成,但是使用起來比較麻煩,要自己寫成語呼叫API,編譯才行。如果我們想實自定義一個簡單的語言(比如SQL)來實現操作,這個時候就可以用lex和yacc。
  2. lex和yacc 做的事情只是:用C語言來實現另外一種語言。所以,他沒辦法實現C語言自己,但是可以實現java、python等。當然你可以通過Antlr來實現C語言的解析和執行,如果你這麼做的話,C語言程式首先是通過java來執行,然後java又變成了本地語言(C語言)來執行,誰叫我們的作業系統都是C語言實現的呢。 

使用lex和yacc我們要做那幾件事情? 

  1. 定義各種token型別。他們在.y中定義,這些token既會被lex使用到,也會被.y檔案中的BNF使用到。
  2. 寫詞彙分析程式碼。這部分程式碼在.l檔案(就是lex的輸入檔案)中。這塊的定義方式是:正規表示式-->對應操作。如果和yacc一起來使用的話,對應的操作通常是返回一個token型別,這個token的型別要在yacc中提前定義好。
  3. 寫BNF。這些東西定義了語言的規約方式。

關於BNF

是一種context-free grammars,請參考:http://en.wikipedia.org/wiki/Backus%E2%80%93Naur_Form 摘錄:

 <symbol> ::= __expression__ 

  1. <symbol> is a nonterminal 
  2.  __expression__ consists of one or more sequences of symbols 
  3. more sequences are separated by the vertical bar, '|' 
  4. Symbols that never appear on a left side are terminals. On the other hand 
  5. symbols that appear on a left side are non-terminals and are always enclosed between the pair <>.  

在yacc中定義的方式其實是:

 <symbol> : __expression__  {operation}

 __expression__  {operation}

operation 是 滿足語法時要執行的C語言程式碼,這裡的C語言程式碼可以使用一些變數,他們是:$$ $1 $2等等。$$代表規約的結果,就是表示式__expression__的值,$1代表的是前面 __expression__ 中出現的各個word。舉個例子: 

expr2:
  expr3 { $$ == $1; }
| expr2 PLUS expr3 { $$ = plus($1, $3); }
| expr2 MINUS expr3 { $$ = minus($1, $3); }
;
來自:http://memphis.compilertools.net/interpreter.html 
  1. expr2 expr3都是BNF中定義的non-terminal
  2. PLUS和MINUS都是.y中定義的token類
  3. plus和minus 是事先定義好的C語言函式

關於yacc中BNF的推導過程引用後面的《lex和yacc簡明教程》做一下說明:

  1. yacc 在內部維護著兩個堆疊;一個分析棧和一個內容棧。分析棧中儲存著終結符和非終結符,並且代表當前剖析狀態。內容棧是一個YYSTYPE 元素的陣列,對於分析棧中的每一個元素都儲存著一個對應的值。例如,當yylex 返回一個INTEGER標記時,y acc 把這個標記移入分析棧。同時,相應的yylval 值將會被移入內容棧中。分析棧和內容棧的內容總是同步的,因此從棧中找到對應於一個標記的值是很容易實現的。
  2. 對expr: expr '+' expr { $$ = $1 + $3; }來說,在分析棧中我們其實用左式替代了右式。在本例中,我們彈出“expr '+' expr” 然後壓入“expr”。 我們通過彈出三個成員,壓入一個成員縮小的堆疊。在我們的C 程式碼中可以用通過相對地址訪問內容棧中的值,“ $1”代表右式中的第一個成員,“ $2”代表第二個,後面的以此類推。“ $$ ”表示縮小後的堆疊的頂部。在上面的動作中,把對應兩個表示式的值相加,彈出內容棧中的三個成員,然後把造得到的和壓入堆疊中。這樣,分析棧和內容棧中的內容依然是同步的。

來看一個用lex和yacc實現計算器的例子

參考了下面連結的lex和yacc檔案:http://blog.csdn.net/crond123/article/details/3932014 

cal.y 

%{
#include <stdio.h>
#include "lex.yy.c"
#define YYSTYPE int  
int yyparse(void);
%}
%token INTEGER PLUS MINUS TIMES DIVIDE LP RP
%%
command : exp {printf("%d/n",$1);}

exp: exp PLUS term {$$ = $1 + $3;}
    |exp MINUS term {$$ = $1 - $3;}
    |term {$$ = $1;}
    ;
term : term TIMES factor {$$ = $1 * $3;}
    |term DIVIDE factor {$$ = $1/$3;}
    |factor {$$ = $1;}
    ;
factor : INTEGER {$$ = $1;}
    | LP exp RP {$$ = $2;}
    ;
%%
int main()
{
    return yyparse();
}
void yyerror(char* s)
{
    fprintf(stderr,"%s",s);
}
int yywrap()
{
    return 1;
}

cal.l

%{
#include<string.h>  
#include "y.tab.h"  
extern int yylval;  
%}  
numbers ([0-9])+  
plus "+"  
minus "-"  
times "*"  
divide "/"  
lp "("  
rp ")"  
delim [ /n/t]  
ws {delim}*  
%%  
{numbers} {sscanf(yytext, "%d", &yylval); return INTEGER;}  
{plus} {return PLUS;}  
{minus} {return MINUS;}  
{times} {return TIMES;}  
{divide} {return DIVIDE;}  
{lp} {return LP;}  
{rp} {return RP;}  
{ws}       ;   
. {printf("Error");exit(1);}    
%%
使用方式: 

yacc -d cal.y 

lex cal.l

g++ -o cal y.tab.c

執行./cal 然後輸入3+4 ctrl+D就可以看到結果了 

  

關於lex和yacc中一些預定義的東西 

 

Lex 變數

yyinFILE* 型別。 它指向 lexer 正在解析的當前檔案。
yyoutFILE* 型別。 它指向記錄 lexer 輸出的位置。 預設情況下,yyin 和 yyout 都指向標準輸入和輸出。
yytext匹配模式的文字儲存在這一變數中(char*)。
yyleng給出匹配模式的長度。
yylineno提供當前的行數資訊。 (lexer不一定支援。)

 

 Lex 函式

yylex()這一函式開始分析。 它由 Lex 自動生成。
yywrap()這一函式在檔案(或輸入)的末尾呼叫。 如果函式的返回值是1,就停止解析。 因此它可以用來解析多個檔案。 程式碼可以寫在第三段,這就能夠解析多個檔案。 方法是使用 yyin 檔案指標(見上表)指向不同的檔案,直到所有的檔案都被解析。 最後,yywrap() 可以返回 1 來表示解析的結束。
yyless(int n)這一函式可以用來送回除了前�n? 個字元外的所有讀出標記。
yymore()這一函式告訴 Lexer 將下一個標記附加到當前標記後。

參考資料: 

首先推薦《lex and yacc tutorial》 http://epaperpress.com/lexandyacc/download/LexAndYaccTutorial.pdf 
上面pdf的中文版《lex和yacc簡明教程》在在:http://ishare.iask.sina.com.cn/f/22266803.html  

http://memphis.compilertools.net/interpreter.html 

http://www.ibm.com/developerworks/cn/linux/sdk/lex/ 

一個老外寫的上手教程
這兩個用 lex 和 yacc實現了 c語言直譯器

http://www.ibm.com/developerworks/cn/linux/game/sdl/pirates-4/index.html 


 http://www.cnblogs.com/welkinwalker/archive/2012/04/09/2439065.html


相關文章