日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

一个Lex/Yacc完整的示例(可使用C++)

發布時間:2024/4/18 c/c++ 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一个Lex/Yacc完整的示例(可使用C++) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者: 胡彥 2013-4-28
代碼下載地址:http://pan.baidu.com/share/link?shareid=579088&uk=253544182
本框架是一個lex/yacc完整的示例,包括詳細的注釋,用于學習lex/yacc程序基本的搭建方法,在linux/cygwin下敲入make就可以編譯和執行。大部分框架已經搭好了,你只要稍加擴展就可以成為一個計算器之類的程序,用于《編譯原理》的課程設計,或者對照理解其它lex/yacc項目的代碼。
本例子雖小卻演示了lex/yacc程序最重要和常用的特征:

* lex/yacc程序組成結構、文件格式。 * 如何在lex/yacc中使用C++和STL庫,用extern "C"聲明那些lex/yacc生成的、要鏈接的C函數,如yylex(), yywrap(), yyerror()。 * 重定義YYSTYPE/yylval為復雜類型。 * lex里多狀態的定義和使用,用BEGIN宏在初始態和其它狀態間切換。 * lex里正則表達式的定義、識別方式。 * lex里用yylval向yacc返回數據。 * yacc里用%token<>方式聲明yacc記號。 * yacc里用%type<>方式聲明非終結符的類型。 * 在yacc嵌入的C代碼動作里,對記號屬性($1, $2等)、和非終結符屬性($$)的正確引用方法。 * 對yyin/yyout重賦值,以改變yacc默認的輸入/輸出目標。

本例子功能是,對當前目錄下的file.txt文件,解析出其中的標識符、數字、其它符號,顯示在屏幕上。linux調試環境是Ubuntu 10.04。

文件列表:

lex.l: lex程序文件。 yacc.y: yacc程序文件。 main.h: lex.lyacc.y共同使用的頭文件。 Makefile: makefile文件。 lex.yy.c: 用lex編譯lex.l后生成的C文件。 yacc.tab.c: 用yacc編譯yacc.y后生成的C文件。 yacc.tab.h: 用yacc編譯yacc.y后生成的C頭文件,內含%tokenYYSTYPEyylval等定義,供lex.yy.cyacc.tab.c使用。 file.txt: 被解析的文本示例。 README.txt: 本說明。


下面列出主要的代碼文件:

?main.h: lex.l和yacc.y共同使用的頭文件?

  • #ifndef MAIN_HPP
  • #define MAIN_HPP
  • #include <iostream>//使用C++庫
  • #include <string>
  • #include <stdio.h>//printf和FILE要用的
  • using namespace std;
  • /*當lex每識別出一個記號后,是通過變量yylval向yacc傳遞數據的。默認情況下yylval是int類型,也就是只能傳遞整型數據。
  • yylval是用YYSTYPE宏定義的,只要重定義YYSTYPE宏,就能重新指定yylval的類型(可參見yacc自動生成的頭文件yacc.tab.h)。
  • 在我們的例子里,當識別出標識符后要向yacc傳遞這個標識符串,yylval定義成整型不太方便(要先強制轉換成整型,yacc里再轉換回char*)。
  • 這里把YYSTYPE重定義為struct Type,可存放多種信息*/
  • struct Type//通常這里面每個成員,每次只會使用其中一個,一般是定義成union以節省空間(但這里用了string等復雜類型造成不可以)
  • {
  • string m_sId;
  • int m_nInt;
  • char m_cOp;
  • };
  • #define YYSTYPE Type//把YYSTYPE(即yylval變量)重定義為struct Type類型,這樣lex就能向yacc返回更多的數據了
  • #endif

  • lex.l: lex程序文件

  • %{
  • /*本lex的生成文件是lex.yy.c
  • lex文件由3段組成,用2個%%行把這3段隔開。
  • 第1段是聲明段,包括:
  • 1-C代碼部分:include頭文件、函數、類型等聲明,這些聲明會原樣拷到生成的.c文件中。
  • 2-狀態聲明,如%x COMMENT。
  • 3-正則式定義,如digit ([0-9])。
  • 第2段是規則段,是lex文件的主體,包括每個規則(如identifier)是如何匹配的,以及匹配后要執行的C代碼動作。
  • 第3段是C函數定義段,如yywrap()的定義,這些C代碼會原樣拷到生成的.c文件中。該段內容可以為空*/
  • //第1段:聲明段
  • #include "main.h"//lex和yacc要共用的頭文件,里面包含了一些頭文件,重定義了YYSTYPE
  • #include "yacc.tab.h"//用yacc編譯yacc.y后生成的C頭文件,內含%token、YYSTYPE、yylval等定義(都是C宏),供lex.yy.c和yacc.tab.c使用
  • extern "C"//為了能夠在C++程序里面調用C函數,必須把每一個需要使用的C函數,其聲明都包括在extern "C"{}塊里面,這樣C++鏈接時才能成功鏈接它們。extern "C"用來在C++環境下設置C鏈接類型。
  • { //yacc.y中也有類似的這段extern "C",可以把它們合并成一段,放到共同的頭文件main.h中
  • int yywrap(void);
  • int yylex(void);//這個是lex生成的詞法分析函數,yacc的yyparse()里會調用它,如果這里不聲明,生成的yacc.tab.c在編譯時會找不到該函數
  • }
  • %}
  • /*lex的每個正則式前面可以帶有"<狀態>",例如下面的"<COMMENT>\n"。每個狀態要先用%x聲明才能使用。
  • 當lex開始運行時,默認狀態是INITIAL,以后可在C代碼里用"BEGIN 狀態名;"切換到其它狀態(BEGIN是lex/yacc內置的宏)。
  • 這時,只有當lex狀態切換到COMMENT后,才會去匹配以<COMMENT>開頭的正則式,而不匹配其它狀態開頭的。
  • 也就是說,lex當前處在什么狀態,就考慮以該狀態開頭的正則式,而忽略其它的正則式。
  • 其應用例如,在一段C代碼里,同樣是串"abc",如果它寫在代碼段里,會被識別為標識符,如果寫在注釋里則就不會。所以對串"abc"的識別結果,應根據不同的狀態加以區分。
  • 本例子需要忽略掉文本中的行末注釋,行末注釋的定義是:從某個"//"開始,直到行尾的內容都是注釋。其實現方法是:
  • 1-lex啟動時默認是INITIAL狀態,在這個狀態下,串"abc"會識別為標識符,串"123"會識別為整數等。
  • 2-一旦識別到"//",則用BEGIN宏切換到COMMENT狀態,在該狀態下,abc這樣的串、以及其它字符會被忽略。只有識別到換行符\n時,再用BEGIN宏切換到初始態,繼續識別其它記號。*/
  • %x COMMENT
  • /*非數字由大小寫字母、下劃線組成*/
  • nondigit ([_A-Za-z])
  • /*一位數字,可以是0到9*/
  • digit ([0-9])
  • /*整數由1至多位數字組成*/
  • integer ({digit}+)
  • /*標識符,以非數字開頭,后跟0至多個數字或非數字*/
  • identifier ({nondigit}({nondigit}|{digit})*)
  • /*一個或一段連續的空白符*/
  • blank_chars ([ \f\r\t\v]+)
  • /*下面%%后開始第2段:規則段*/
  • %%
  • {identifier} { //匹配標識符串,此時串值由yytext保存
  • yylval.m_sId=yytext;//通過yylval向yacc傳遞識別出的記號的值,由于yylval已定義為struct Type,這里就可以把yytext賦給其m_sId成員,到了yacc里就可以用$n的方式來引用了
  • return IDENTIFIER; //向yacc返回: 識別出的記號類型是IDENTIFIER
  • }
  • {integer} { //匹配整數串
  • yylval.m_nInt=atoi(yytext);//把識別出的整數串,轉換為整型值,存儲到yylval的整型成員里,到了yacc里用$n方式引用
  • return INTEGER;//向yacc返回: 識別出的記號類型是INTEGER
  • }
  • {blank_chars} { //遇空白符時,什么也不做,忽略它們
  • }
  • \n { //遇換行符時,忽略之
  • }
  • "//" { //遇到串"//",表明要開始一段注釋,直到行尾
  • cout<<"(comment)"<<endl;//提示遇到了注釋
  • BEGIN COMMENT;//用BEGIN宏切換到注釋狀態,去過濾這段注釋,下一次lex將只匹配前面帶有<COMMENT>的正則式
  • }
  • . { //.表示除\n以外的其它字符,注意這個規則要放在最后,因為一旦匹配了.就不會匹配后面的規則了(以其它狀態<>開頭的規則除外)
  • yylval.m_cOp=yytext[0];//由于只匹配一個字符,這時它對應yytext[0],把該字符存放到yylval的m_cOp成員里,到了yacc里用$n方式引用
  • return OPERATOR;//向yacc返回: 識別出的記號類型是OPERATOR
  • }
  • <COMMENT>\n { //注釋狀態下的規則,只有當前切換到COMMENT狀態才會去匹配
  • BEGIN INITIAL;//在注釋狀態下,當遇到換行符時,表明注釋結束了,返回初始態
  • }
  • <COMMENT>. { //在注釋狀態下,對其它字符都忽略,即:注釋在lex(詞法分析層)就過濾掉了,不返回給yacc了
  • }
  • %%
  • //第3段:C函數定義段
  • int yywrap(void)
  • {
  • puts("-----the file is end");
  • return 1;//返回1表示讀取全部結束。如果要接著讀其它文件,可以這里fopen該文件,文件指針賦給yyin,并返回0
  • }

  • yacc.y: yacc程序文件

  • %{
  • /*本yacc的生成文件是yacc.tab.c和yacc.tab.h
  • yacc文件由3段組成,用2個%%行把這3段隔開。
  • 第1段是聲明段,包括:
  • 1-C代碼部分:include頭文件、函數、類型等聲明,這些聲明會原樣拷到生成的.c文件中。
  • 2-記號聲明,如%token
  • 3-類型聲明,如%type
  • 第2段是規則段,是yacc文件的主體,包括每個產生式是如何匹配的,以及匹配后要執行的C代碼動作。
  • 第3段是C函數定義段,如yyerror()的定義,這些C代碼會原樣拷到生成的.c文件中。該段內容可以為空*/
  • //第1段:聲明段
  • #include "main.h"//lex和yacc要共用的頭文件,里面包含了一些頭文件,重定義了YYSTYPE
  • extern "C"//為了能夠在C++程序里面調用C函數,必須把每一個需要使用的C函數,其聲明都包括在extern "C"{}塊里面,這樣C++鏈接時才能成功鏈接它們。extern "C"用來在C++環境下設置C鏈接類型。
  • { //lex.l中也有類似的這段extern "C",可以把它們合并成一段,放到共同的頭文件main.h中
  • void yyerror(const char *s);
  • extern int yylex(void);//該函數是在lex.yy.c里定義的,yyparse()里要調用該函數,為了能編譯和鏈接,必須用extern加以聲明
  • }
  • %}
  • /*lex里要return的記號的聲明
  • 用token后加一對<member>來定義記號,旨在用于簡化書寫方式。
  • 假定某個產生式中第1個終結符是記號OPERATOR,則引用OPERATOR屬性的方式:
  • 1-如果記號OPERATOR是以普通方式定義的,如%token OPERATOR,則在動作中要寫$1.m_cOp,以指明使用YYSTYPE的哪個成員
  • 2-用%token<m_cOp>OPERATOR方式定義后,只需要寫$1,yacc會自動替換為$1.m_cOp
  • 另外用<>定義記號后,非終結符如file, tokenlist,必須用%type<member>來定義(否則會報錯),以指明它們的屬性對應YYSTYPE中哪個成員,這時對該非終結符的引用,如$$,會自動替換為$$.member*/
  • %token<m_nInt>INTEGER
  • %token<m_sId>IDENTIFIER
  • %token<m_cOp>OPERATOR
  • %type<m_sId>file
  • %type<m_sId>tokenlist
  • %%
  • file: //文件,由記號流組成
  • tokenlist //這里僅顯示記號流中的ID
  • {
  • cout<<"all id:"<<$1<<endl; //$1是非終結符tokenlist的屬性,由于該終結符是用%type<m_sId>定義的,即約定對其用YYSTYPE的m_sId屬性,$1相當于$1.m_sId,其值已經在下層產生式中賦值(tokenlist IDENTIFIER)
  • };
  • tokenlist://記號流,或者為空,或者由若干數字、標識符、及其它符號組成
  • {
  • }
  • | tokenlist INTEGER
  • {
  • cout<<"int: "<<$2<<endl;//$2是記號INTEGER的屬性,由于該記號是用%token<m_nInt>定義的,即約定對其用YYSTYPE的m_nInt屬性,$2會被替換為yylval.m_nInt,已在lex里賦值
  • }
  • | tokenlist IDENTIFIER
  • {
  • $$+=" " + $2;//$$是非終結符tokenlist的屬性,由于該終結符是用%type<m_sId>定義的,即約定對其用YYSTYPE的m_sId屬性,$$相當于$$.m_sId,這里把識別到的標識符串保存在tokenlist屬性中,到上層產生式里可以拿出為用
  • cout<<"id: "<<$2<<endl;//$2是記號IDENTIFIER的屬性,由于該記號是用%token<m_sId>定義的,即約定對其用YYSTYPE的m_sId屬性,$2會被替換為yylval.m_sId,已在lex里賦值
  • }
  • | tokenlist OPERATOR
  • {
  • cout<<"op: "<<$2<<endl;//$2是記號OPERATOR的屬性,由于該記號是用%token<m_cOp>定義的,即約定對其用YYSTYPE的m_cOp屬性,$2會被替換為yylval.m_cOp,已在lex里賦值
  • };
  • %%
  • void yyerror(const char *s) //當yacc遇到語法錯誤時,會回調yyerror函數,并且把錯誤信息放在參數s中
  • {
  • cerr<<s<<endl;//直接輸出錯誤信息
  • }
  • int main()//程序主函數,這個函數也可以放到其它.c, .cpp文件里
  • {
  • const char* sFile="file.txt";//打開要讀取的文本文件
  • FILE* fp=fopen(sFile, "r");
  • if(fp==NULL)
  • {
  • printf("cannot open %s\n", sFile);
  • return -1;
  • }
  • extern FILE* yyin; //yyin和yyout都是FILE*類型
  • yyin=fp;//yacc會從yyin讀取輸入,yyin默認是標準輸入,這里改為磁盤文件。yacc默認向yyout輸出,可修改yyout改變輸出目的
  • printf("-----begin parsing %s\n", sFile);
  • yyparse();//使yacc開始讀取輸入和解析,它會調用lex的yylex()讀取記號
  • puts("-----end parsing");
  • fclose(fp);
  • return 0;
  • }

  • Makefile: makefile文件

  • LEX=flex
  • YACC=bison
  • CC=g++
  • OBJECT=main #生成的目標文件
  • $(OBJECT): lex.yy.o yacc.tab.o
  • $(CC) lex.yy.o yacc.tab.o -o $(OBJECT)
  • @./$(OBJECT) #編譯后立刻運行
  • lex.yy.o: lex.yy.c yacc.tab.h main.h
  • $(CC) -c lex.yy.c
  • yacc.tab.o: yacc.tab.c main.h
  • $(CC) -c yacc.tab.c
  • yacc.tab.c yacc.tab.h: yacc.y
  • # bison使用-d參數編譯.y文件
  • $(YACC) -d yacc.y
  • lex.yy.c: lex.l
  • $(LEX) lex.l
  • clean:
  • @rm -f $(OBJECT) *.o

  • ?file.txt: 被解析的文本示例

    abc defghi //this line is comment, abc 123 !@#$ 123 45678 //comment until line end ! @ # $


    使用方法:
    1-把lex_yacc_example.rar解壓到linux/cygwin下。
    2-命令行進入lex_yacc_example目錄。
    3-敲入make,這時會自動執行以下操作:
    (1) 自動調用flex編譯.l文件,生成lex.yy.c文件。
    (2) 自動調用bison編譯.y文件,生成yacc.tab.c和yacc.tab.h文件。
    (3) 自動調用g++編譯、鏈接出可執行文件main。
    (4) 自動執行main。
    運行結果如下所示:

    bison -d yacc.y g++ -c lex.yy.c g++ -c yacc.tab.c g++ lex.yy.o yacc.tab.o -o main -----begin parsing file.txt id: abc id: defghi (comment) int: 123 int: 45678 (comment) op: ! op: @ op: # op: $ -----the file is end all id: abc defghi -----end parsing


    參考資料:《Lex和Yacc從入門到精通(6)-解析C-C++包含文件》http://blog.csdn.net/pandaxcl/article/details/1321552
    其它文章和代碼請留意我的blog: http://blog.csdn.net/huyansoft

    [END]

    總結

    以上是生活随笔為你收集整理的一个Lex/Yacc完整的示例(可使用C++)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。