1. 程式人生 > >建立一門新的程式語言-Flex&Bison教程-(1)-初探

建立一門新的程式語言-Flex&Bison教程-(1)-初探

之前一直想鑽研一下這方面的東西,於是便花了一些精力研究這些相關的工具和技術,現在我把我總結的一些經驗分享給大家
Flex & Bison 是比較有名而且易用的parser組合,今後的工作大體都用到了這兩個工具。他們可以在gnu官網下載,windows使用者要麼去下載winflexbison,要麼自己編譯一份(推薦),這裡不詳細介紹獲取方法
本教程所有程式碼都在Android平臺和windows平臺的g++編譯通過(可能不支援vs,untested)

初探 1

什麼是Flex和Bison?

Flex是一個詞法分析器,是unix lex的gnu克隆,作用是把"詞"抽象成符號,供程式識別
Bison則是一個文法分析器(也是unix yacc的gnu克隆),語法就是在這裡定義,是語言設計的核心
這兩巨頭不但可以應付複雜的語法處理,也可以拿來製作簡單的分析器,如配置檔案等

初探Flex
習慣上,一般把flex input檔案命名為xxxll.l
以下是一個簡單的例子test1ll.l:


%{
  #include <iostream>
  using namespace std;
%}
%%
[0-9]+        { cout << "Number "; }
[a-zA-Z]+     { cout << "Word ";   }
[ \t]         ; //忽略空格
%%


我們來看一看這些東西
開頭的{% %}包著的東西是輔助程式碼段
兩個%%之間的則是一些flex所認識的定義:左邊為匹配正則(這裡假設你已經學習過正則),右邊也是當匹配後應該執行的程式碼
最後的%%後面還可以寫一些輔助程式碼,現在我們還不需要
我們這樣用這個檔案:

flex -otest1ll.c test1ll.l
g++ test1ll.c -otest1 -lfl
./test1


(加上-lfl引數是因為我們沒有main等函式)
現在輸入幾個數字或者單詞試試?是不是能準確處理了?
我們來考慮輸入的這串內容:123 abc jkl 134
程式的輸出如下:Number Word Word Number
看出什麼了嗎?假設我們有一種語言的語法是兩個數字中間夾兩個單詞?實際上語法就是一些符號按順序的組合!但flex能幫我們的只到這裡了,現在你更明白flex的作用了嗎?
flex還有很多功能,今後會慢慢補充


Bison初探
對比flex,我們一般命名bison input檔案為xxxyy.y
例子test1yy.y:

%{
  #include <iostream>
  using namespace std;
  int yylex();  //只是一個宣告
  int yyerror(const char *); //必須要有
%}
%token <iv> Number
%token <sv> Word
%union {
  int iv;
  char *sv;
}
%%


main : main Number Word Word Number '\n'   { cout << $2 + $5 << $3 << $4 << endl ; free($3) ; free($4) ; //因為我們使用strdup }
       |    //空
       ;
  
%%


int yyerror(const char *emseg)
{
  cout << "Error: " << emseg << endl;
}


int main()
{
  yyparse();
}


格式和flex的差不多就不再囉嗦了
在%union裡我們定義了兩個型別,數字和字串
在%token處,我們定義兩個符號,並且用我們定義的兩個型別來宣告他們,這裡與flex有關聯,等下再提
重點在兩個%%之間的內容,如果看過 編譯原理 的讀者可能很快知道這是什麼,沒看過也沒關係在這裡我們定義了一條BNF正規化,什麼是BNF正規化呢?
舉個例子:
exp : exp + exp
    | exp - exp
    | int       //一個數字
它的意思是,表示式exp有三種可能可以是兩個exp表示式相加或者相減,也可能就是一個數字,是一個遞迴,這樣,我們就很清晰地給 5 5+3 5+3-6+1 這樣的算式提供了規則

上面,我們給兩個數字夾兩個單詞的語法定義了規則,輸出則是兩個數字相加然後再連線兩個單詞

在bison,BNF正規化的寫法是

表示式 : 表示式1 表示式2 ... {當匹配後執行的程式碼}
          | 表示式 .... { ... }
          | ....           { ... }
          ....
          ;


變數名$N代表了在這行的第N個符號的值
為了讓它工作,我們還需要稍微改一下我們的text1ll.l檔案:

%{
  #include <iostream>
  #include "test1yy.h"  //由bison生成
  extern int yyerror(const char *);
  using namespace std;
%}
%%
[0-9]+        { yylval.iv = strtol(yytext,0,10); return Number; }
[a-zA-Z]+     { yylval.sv = strdup(yytext); return Word;   }
[ \t]         ; //忽略空格  
\n            { return *yytext; } //直接返回換行符作為符號給bison
%%
int yywrap()
{
  return 1;
}



yylval用來傳遞當前符號的值到bison,yytext則是flex讀取到的東西


好,讓我們生成成品

bison -d -otest1yy.c test1yy.y
flex -otest1ll.c test1ll.l
g++ test1*.c -otest1
./test1

因為我們已經自己實現了yywrap和main函式,我們就不需要-lfl引數了
嘗試輸入一些東西
輸入 12 ty uy 45
輸出 57tyuy
輸入 16 45 2 3
輸出 Error: synax error
是不是能按預期進行了?現在你知道flex和bison配合的意義了嗎?


歸納:flex提供符號和值,bison進行相應文法處理,一般使用順序為先寫好bison規則,再寫flex規則


計算器的誕生
我們先從簡單的例子入手,之後本教程主要圍繞一個計算器的開發的例子,從一個簡易的加減乘除計算器到一個能定義函式或甚至迴圈表示式的計算器


(1)初探 結束