從lex&yacc說到編譯器(2.flex的使用) (轉)

worldblog發表於2008-01-22
從lex&yacc說到編譯器(2.flex的使用) (轉)[@more@]

從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/,如需轉載,請註明出處,否則將追究法律責任。

相關文章