從lex&yacc說到編譯器(2.flex的使用) (轉)
從lex&yacc說到(2.flex的使用):namespace prefix = o ns = "urn:schemas--com::office" />
作者:tangl_99
qq:8664220
:to:tangl_99@.com">tangl_99@hotmail.com
:
看了第一篇的關於正則的說明後,下面我們就來透過它,使用flex這個詞法分析工具來構造我們的編譯器的詞法分析器.
關於lex的教程應該是很多,這裡我就簡單地介紹一下,然後著重後面的lex和yacc的配合使用以及其技巧.所以,如果你不看了後還是不太明白lex或者yacc的使用,請你自己上網去查查,這方面的教程是很多的.我知道的一篇常見的就是
Yacc 與 Lex
Lex 與 Yacc 介紹
它的作者就是Ashish Bansal.
Flex就是fast lex的意思.而lex就是Lexical Analyzar的意思.flex可以在cygwin或者pro中找到.它是的一個工具,屬於GNU組織產品.網上也可以找到單獨可以在下用的版本.
我們一般把我們的詞法掃描要掃描的一些單詞(token)用正規表示式寫好,然後作為lex的輸入,輸入命令flex xxx.l(xxx.l就是輸入檔案),lex經過處理後,就能得到一個名字叫lex.yy.c的C.這個C原始碼檔案,就是我們的詞法掃描程式.通常lex為我們生成的詞法分析器的C原始碼都是十分複雜而且龐大的,我們一般根本不會去檢視裡面的程式碼(放心好了,flex這個東西不會出錯的)
下面讓我們看看幾個我已經使用過的幾個lex輸入檔案.
這是一個前段時間我為GBA上的一個RPG遊戲寫的指令碼引擎所使用的lex輸入檔案(部分)
例2.1
%{
/* need this for the call to atof() below */
#include
#include
#include
#include "globals.h"
%}
digit [0-9]
number ("-"|"+")?{digit}+
hexnumber "0x"({digit}|[a-fA-F])+
letter [a-zA-Z]
ntifier ({letter}|_)({number}|{letter}|_)*
newline [n]
whitespace [ t]+
string "[^"]*"
comment "#"[^#]*"#"
%%
{string} { return VM_STRING; }
"Logo" { return VMIN_LOGO; }
"FaceIn" { return VMIN_FACEIN; }
"FaceOut" { return VMIN_FACEOUT; }
"LoadTile" { return VMIN_LOAD_TILE; }
"CreateRole" { return VMIN_CREATE_ROLE; }
"ReleaseRole" { return VMIN_RELEASE_ROLE;}
"CreateMap" { return VMIN_CREATE_MAP; }
"ReleaseMAP" { return VMIN_RELEASE_MAP;}
"ShowBitmap" { return VMIN_SHOWBITMAP; }
"CreateDialog" { return VMIN_CREATE_DIALOG; }
"ReleaseDialog" { return VMIN_RELEASE_DIALOG;}
"Fight" { return VMIN_FIGHT; }
"Delay" { return VMIN_DELAY; }
"PressA" { return VMIN_PRESS_A; }
"PressB" { return VMIN_PRESS_B; }
"PressR" { return VMIN_PRESS_R; }
"Pre" { return VMIN_PRESS_L; }
"PressStart" { return VMIN_PRESS_START; }
"Press" { return VMIN_PRESS_SELECT;}
{number} { return VM_NUMBER; }
{whitespace} { /* skwhitespace */ }
{identifier} { return VM_ID; }
{newline} ;
. ;
%%
int yywrap()
{
return 1;
}
這裡的lex輸入檔案一共有三個部分,用%%分開.第一部分中的%{和}%中的內容就是直接放在lex輸出C程式碼中的頂部.我們透過它可以來定義一些所需要的宏,和include一些標頭檔案等等.我的這個lex輸入檔案中也沒什麼特別的東西,就是常規的C原始檔的include標頭檔案
%{
/* need this for the call to atof() below */
#include
#include
#include
#include "globals.h"
%}
第一部分中,除了前面的%{和}%包含的部分,下面的就是正規表示式的定義.
看了第一篇的正規表示式,這樣你就能夠在這裡派上用場了.
讓我們來看看我這裡定義的正規表示式:
digit [0-9]
number ("-"|"+")?{digit}+
hexnumber "0x"({digit}|[a-fA-F])+
letter [a-zA-Z]
identifier ({letter}|_)({number}|{letter}|_)*
newline [n]
whitespace [ t]+
string "[^"]*"
comment "#"[^#]*"#"
digit就不用說了,就是0-9的阿拉伯數字定義,第一篇文章中也舉了這個例子.number就是digit的1到無限次的重複,再在其前面加上”+”和”-“符號.
注意:
“a”: 即使a是元字元,它仍是字元a
a: 當a是元字元時候,為字元a
a?: 一個可選的a,也就是說可以是a,也可以沒有a
a|b: a或b
(a): a本身
[abc]: 字元a,b或c中的任一個
[a-d]: a,b,d或者d中的任一個
[^ab]: 除了a或b外的任何一個字元
.: 除了新行之外的任一個字元
{xxx}: 名字xxx表示的正規表示式
這裡需要特別說明的就是
newline [n]
newline就是新行,這裡我使用了[]把n換行號括起來.因為如果我直接用n表示的話,那麼按照上面的規則,那就會看成和n兩個字元,所以我使用了[n].有些時候newline也被寫成[n]|[rn].因為在文字檔案中,一般換行一次,那麼就是一個n(0xA),可是在二進位制檔案中,換行有時候又是rn(0xD,0xA)一共兩個字元號.
第二部分就是定義掃描到正規表示式的動作.
這些動作其實就是C程式碼,它們將會被鑲嵌在lex輸出的C檔案中的yylex()函式中.
上面的例子的動作其實十分平常,就是返回一個值.
我們在外部使用這個lex為我們生成C程式碼的時候,只需要使用它的int yylex()函式.當我們使用一次yylex(),那麼就會自動去掃描一個匹配的正規表示式,然後完成它相應的動作.這裡的動作都是返回一值,那麼yylex就會返回這個值.通常預設yylex返回0時候,表示檔案掃描結束,所以你的動作中最好不要返回0,以免發生衝突.當然,動作中也可以不返回一值,那麼yylex就會完成這個動作後自動掃描下一個可以被匹配的字串,一直到掃描到檔案結束.
當掃描到一個可以被匹配的字串,那麼這個時候,全域性變數yytext就等於這個字串
請大家一定記住這些正規表示式的順序.
如果出現一個字串,可以同時匹配多個正規表示式,那麼它將會被定義在前面的正規表示式匹配.所以我一般把字串string定義在最前面.
如果檔案中的字元沒有被lex輸入檔案中任何一個字元匹配,那麼它會自動地被標準輸出.所以大家一定要記住在每個正規表示式處理完畢後,一定要加上{newline}和.這兩個正規表示式的動作.
好,讓我們看看lex為我們輸出C檔案中提供一些常量
Lex 變數
yyin
FILE* 型別。 它指向 lexer 正在解析的當前檔案。
yyout
FILE* 型別。 它指向記錄 lexer 輸出的位置。 預設情況下,yyin 和 yyout 都指向標準輸入和輸出。
yytext
匹配的文字在這一變數中(char*)。
yyleng
給出匹配模式的長度。
yylineno
提供當前的行數資訊。(lexer不一定支援。)
例2.2
這是<>書中配套的原始碼的lex輸入檔案.大家可以參考一下,作者為它自己定義的一個Tiny C編譯所做的詞法掃描器.
/****************************************************/
/* File: tiny.l */
/* Lex specification for TINY */
/* Compiler Construction: Principles and Practice */
/* Keh C. Louden */
/****************************************************/
%{
#include "globals.h"
#include "util.h"
#include "scan.h"
/* lexeme of identifier or reserved */
char tokenString[MAXTOKENLEN+1];
%}
digit [0-9]
number {digit}+
letter [a-zA-Z]
identifier {letter}+
newline n
whitespace [ t]+
%%
"if" {return IF;}
"then" {return THEN;}
"else" {return ELSE;}
"end" {return END;}
"repeat" {return REPEAT;}
"until" {return UNTIL;}
"read" {return READ;}
"write" {return WRITE;}
":=" {return ASSIGN;}
"=" {return EQ;}
"
"+" {return PLUS;}
"-" {return MINUS;}
"*" {return TIMES;}
"/" {return OVER;}
"(" {return LPAREN;}
")" {return RPAREN;}
";" {return SEMI;}
{number} {return NUM;}
{identifier} {return ID;}
{newline} {lineno++;}
{whitespace} {/* skip whitespace */}
"{" { char c;
do
{ c = input();
if (c == EOF) break;
if (c == 'n') lineno++;
} while (c != '}');
}
. {return ERROR;}
%%
TokenType getToken(void)
{ static int firstTime = TRUE;
TokenType currentToken;
if (firstTime)
{ firstTime = FALSE;
lineno++;
yyin = ;
yyout = listing;
}
currentToken = yylex();
strncpy(tokenString,yytext,MAXTOKENLEN);
if (TraceScan) {
fprintf(listing,"t%d: ",lineno);
printToken(currentToken,tokenString);
}
return currentToken;
}
這裡有點不同的就是,作者用了另外一個getToken函式來代替yylex作為外部輸出函式.其中getToken裡面也使用了lex預設的輸出函式yylex(),同時還做了一些其它的事情.不過我建議大家不要像作者那樣另外寫自己的結果輸出函式,因為在後面,需要和yacc搭配工作的時候,yacc生成的語法分析程式只認名字叫yylex()的詞法結果輸出函式.
if (firstTime)
{ firstTime = FALSE;
lineno++;
yyin = source;
yyout = listing;
}
其中的yyin,yyout,source,listing都是FILE*型別.yyin就是要lex生成的詞法掃描程式要掃描的檔案,yyout就是基本輸出檔案(其實我們通常都不用yyout,即使要生成一些輸出資訊,我們都是自己透過fprintf來輸出).
"{" { char c;
do
{ c = input();
if (c == EOF) break;
if (c == 'n') lineno++;
} while (c != '}');
}
其中,作者的這個Tiny C是以{}來包括註釋資訊.作者並沒有寫出註釋資訊的正規表示式,但是它可以透過檢索“{”,然後用lex內部函式input()一一檢查 { 後面的字元是不是 } 來跳過註釋文字.(C語言的/* */註釋文字正規表示式十分難寫,所以很多時候我們都用這種方法直接把它的DFA(掃描自動機)寫出來).
本文就是透過簡單地舉出兩個比較實際的例子來講解flex輸入檔案的.再次說明,如果你是第一次接觸lex,那麼請看看前面我推薦的文章,你可以在IBM的開發者網上查到.下一篇關於yacc於BNF文法的說明也是如此.請大家先參考一下其它標準的教程.
-9-30
成都,四川大學
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-998171/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 說說 方舟編譯器編譯
- 從fdk_aac編碼器到自動靜態編譯FFmpeg編譯
- GCC編譯器的使用GC編譯
- FreeBSD中的GNU C編譯器--編譯器GCC(轉)編譯GC
- C程式從編譯到執行C程式編譯
- 關於支援OPenACC的編譯器說明編譯
- 【譯】說服Kotlin編譯器程式碼安全Kotlin編譯
- [譯] 理解編譯器 —— 從人類的角度(版本 2)編譯
- 從滴水到怒海:方舟編譯器如何影響技術世界?編譯
- 從編譯原理看一個直譯器的實現編譯原理
- 安裝 GCC 編譯器(轉)GC編譯
- 編譯器-Javac.exe(轉)編譯Java
- 使用C編譯器編寫shellcode編譯
- makefile教程---nmake命令編譯器的使用編譯
- 編譯器的編譯基本過程編譯
- Rust 編譯器探索使用 PGORust編譯Go
- 從AST編譯解析談到寫babel外掛AST編譯Babel
- 從【預編譯】到【宣告提升】到【作用域鏈】再到【閉包】編譯
- 使用Intel 向量化編譯器最佳化效能(1) (轉)Intel編譯
- gcc 編譯器與 clang 編譯器GC編譯
- 小白說編譯原理-9-最簡單minus-c語言編譯器編譯原理C語言
- JWebAssembly:Java 位元組碼到 WebAssembly 編譯器WebJava編譯
- 從放棄到入門-Yaf(從控制器說起)
- 編譯引數-ObjC的說明編譯OBJ
- 【譯】使用 Python 編寫虛擬機器直譯器Python虛擬機
- 富文字編譯器UEditor+SSM的使用編譯SSM
- 在命令列下使用vs的編譯器命令列編譯
- 使用ZendEncode編譯PHP程式(轉)編譯PHP
- 淺談彙編器、編譯器和直譯器編譯
- [譯]iOS編譯器iOS編譯
- 表示式編譯計算器(下) (轉)編譯
- gcc編譯器小知識FAQ(轉)GC編譯
- 編譯器的自展和自舉、交叉編譯編譯
- Ubuntu編譯Android整個系統以及編譯指定模組到模擬器Ubuntu編譯Android
- 為什麼C++編譯器不能支援對模板的分離式編譯 (轉)C++編譯
- 反編譯使用yield關鍵字的方法 轉編譯
- 面試官:Java從編譯到執行,發生了什麼?面試Java編譯
- 老話新說,RedHat 公司 推薦編譯核心方法(轉)Redhat編譯