lex yacc 學習
寫在前面的幾句廢話
最近在專案的過程中接觸了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能幫我們做什麼?
一句話:解釋執行自定義語言。有幾點要注意:
- 自定義語言的要做的事情必須可以能通過C語言來實現。其實任何計算機能做的事情都可以用C語言來實現,lex和yacc存在的意義在於簡化語言,讓使用者能夠以一種用比較簡單的語言來實現複雜的操作。比如:對於資料庫的查詢肯定有現成的庫可以來完成,但是使用起來比較麻煩,要自己寫成語呼叫API,編譯才行。如果我們想實自定義一個簡單的語言(比如SQL)來實現操作,這個時候就可以用lex和yacc。
- lex和yacc 做的事情只是:用C語言來實現另外一種語言。所以,他沒辦法實現C語言自己,但是可以實現java、python等。當然你可以通過Antlr來實現C語言的解析和執行,如果你這麼做的話,C語言程式首先是通過java來執行,然後java又變成了本地語言(C語言)來執行,誰叫我們的作業系統都是C語言實現的呢。
使用lex和yacc我們要做那幾件事情?
- 定義各種token型別。他們在.y中定義,這些token既會被lex使用到,也會被.y檔案中的BNF使用到。
- 寫詞彙分析程式碼。這部分程式碼在.l檔案(就是lex的輸入檔案)中。這塊的定義方式是:正規表示式-->對應操作。如果和yacc一起來使用的話,對應的操作通常是返回一個token型別,這個token的型別要在yacc中提前定義好。
- 寫BNF。這些東西定義了語言的規約方式。
關於BNF
是一種context-free grammars,請參考:http://en.wikipedia.org/wiki/Backus%E2%80%93Naur_Form 摘錄:
<symbol> ::= __expression__
- <symbol> is a nonterminal
- __expression__ consists of one or more sequences of symbols
- more sequences are separated by the vertical bar, '|'
- Symbols that never appear on a left side are terminals. On the other hand
- 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 - expr2 expr3都是BNF中定義的non-terminal
- PLUS和MINUS都是.y中定義的token類
- plus和minus 是事先定義好的C語言函式
關於yacc中BNF的推導過程引用後面的《lex和yacc簡明教程》做一下說明:
- yacc 在內部維護著兩個堆疊;一個分析棧和一個內容棧。分析棧中儲存著終結符和非終結符,並且代表當前剖析狀態。內容棧是一個YYSTYPE 元素的陣列,對於分析棧中的每一個元素都儲存著一個對應的值。例如,當yylex 返回一個INTEGER標記時,y acc 把這個標記移入分析棧。同時,相應的yylval 值將會被移入內容棧中。分析棧和內容棧的內容總是同步的,因此從棧中找到對應於一個標記的值是很容易實現的。
- 對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中一些預定義的東西
yyin | FILE* 型別。 它指向 lexer 正在解析的當前檔案。 |
yyout | FILE* 型別。 它指向記錄 lexer 輸出的位置。 預設情況下,yyin 和 yyout 都指向標準輸入和輸出。 |
yytext | 匹配模式的文字儲存在這一變數中(char*)。 |
yyleng | 給出匹配模式的長度。 |
yylineno | 提供當前的行數資訊。 (lexer不一定支援。) |
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/
http://www.ibm.com/developerworks/cn/linux/game/sdl/pirates-4/index.html
http://www.cnblogs.com/welkinwalker/archive/2012/04/09/2439065.html
相關文章
- LEX與YACC學習資料連結
- lex yacc 入門教程
- Pisa-Proxy SQL 解析之 Lex & YaccSQL
- C編譯器LEX 和 YACC輸入原始檔。 (轉)編譯
- 從lex&yacc說到編譯器(2.flex的使用) (轉)編譯Flex
- 麻省理工學院Lex Fridman:強化學習簡介強化學習
- YACC 例項分析
- Lex詞法分析器詞法分析
- PHP Lex Engine Sourcecode Analysis(undone)PHP
- Making YACC output an AST (token tree)AST
- GBase8s 的yacc語法分析語法分析
- Antlr的+ * ?在yacc中都是什麼樣的模式?模式
- 學習學習再學習
- ubuntu進行make時報錯error: Neither flex nor lex was found.UbuntuErrorFlex
- 深度學習——學習目錄——學習中……深度學習
- 深度學習(一)深度學習學習資料深度學習
- 深度學習學習框架深度學習框架
- 強化學習-學習筆記3 | 策略學習強化學習筆記
- 學習產品快報09 | “CSDN學習”:增加學習提醒,提示學習不忘記
- 【強化學習】強化學習/增強學習/再勵學習介紹強化學習
- 學習ThinkPHP,學習OneThinkPHP
- 前端學習之Bootstrap學習前端boot
- 學而習之,成就學習
- 前端週刊第62期:學習學習再學習前端
- 深度學習+深度強化學習+遷移學習【研修】深度學習強化學習遷移學習
- 強化學習-學習筆記2 | 價值學習強化學習筆記
- Golang 學習——interface 介面學習(一)Golang
- Golang 學習——interface 介面學習(二)Golang
- 深度學習學習7步驟深度學習
- 《JAVA學習指南》學習筆記Java筆記
- Go學習【二】學習資料Go
- java學習之道 --- 如何學習java?Java
- 免殺學習-基礎學習
- 強化學習10——迭代學習強化學習
- 程式設計學習MarkDown學習程式設計
- this學習
- 學習
- 【區塊鏈學習】《區塊鏈學習指南》學習筆記區塊鏈筆記