Bison筆記
2016/10/21
1.語法結構
%{
C/C++頭文件、全局文件、全局變量、類型定義
詞法分析器yylex(採用lex進行詞法分析)和錯誤打印函數
%}
Bison聲明區間。定義之後用到的終結符、非終結符、操作符優先級
%%
Bison語法規則定義
%%
C/C++代碼 需要定義prologue區域函數,或者其他代碼,生成的c/c++文件會完全拷貝這份代碼。
2.FAQ
終結符、非終結符定義
Token用於定義終結符 type定義非終結符 操作符也屬於終結符
Left表示左關聯運算符 right表示右關聯運算符
%token NUM
%nonassoc ‘<’ 表示該終結符無結合性 不能出現a<b<c
%left ‘+’ ‘-’ 左結合 後面接操作符 下方的操作符比上方的優先級高
%left ‘*’ ‘/’
%right NEG NEG表示非
%right ‘^’
Bison聲明區域
%union{
Expressions* expressions;/*表達式集合*/
Expression* expression;/*表達式*/
char name[32];
double num;
}
%token ASSIGN 258
%token<num> DOUBLE_CONST 259
%token<name> IDENTIFIER 260
%token IF 261 THEN 262 ELSE 263 FI 264
%token WHILE 265 LOOP 266 POOL 267
%type<expression> expr
%type<expressions> exprs
%type<expressions> exprs_no
%%
input:
/* empty */
| exprs
;
exprs:
error { $$ = 0;}
| exprs error
| expr ';'
{
$$ = t_single_exprs($1);
Execute($1);
}
| exprs expr ';'
{
$$ = t_append_exprs($1, $2);
Execute($2);
}
;
expr:
IDENTIFIER { $$ = t_id($1); }
| DOUBLE_CONST { $$ = t_num($1);}
| expr '+' expr { $$ = t_plus($1, $3); }
| expr '-' expr { $$ = t_sub($1, $3); }
| expr '*' expr { $$ = t_mul($1, $3); }
| expr '/' expr { $$ = t_div($1, $3); }
| '(' expr ')' { $$ = $2;}
| '{' exprs_no '}' { $$ = t_block($2);}
| expr '<' expr { $$ = t_less($1, $3); }
| expr '=' expr { $$ = t_eq($1, $3); }
| IDENTIFIER ASSIGN expr { $$ = t_assign($1, $3); }
| IF expr THEN expr ELSE expr FI { $$ = t_if($2, $4, $6); }
| WHILE expr LOOP expr POOL { $$ = t_while($2, $4); }
;
exprs_no:
expr ';'
{
$$ = t_single_exprs($1);
}
| exprs_no expr ';'
{
$$ = t_append_exprs($1, $2);
}
;
%%
終結符和非終結符
終結符的類型通過"%token<類型名>終結符"這樣的格式來確定
%type是指定非終結符的類型,用法和%token一樣,不過不需要指定編號。我們可以發expr是expression類型。這裏expr的意思是一個表達式,exprs和exprs_no是多個表達式集合。
非終結符也可以通過%type<類型名>非終結符這種格式來表示。
兩者區別是終結符相當於原子不可分,而非終結符相當於分子可分,可由終結符或非終結符歸約而成。
終結符使用詞法分析來識別,而非終結符使用的歸約方法。
在bison中詞法分析需要由自己指定,比如示例中的yylex(),也可以採用flex定義詞法分析規則,利用生成的詞法分析代碼來完成,比如在nessus使用的nasl語言使用的自己實現的詞法分析,詳見規則文件中的mylex,yara中則使用的flex實現的詞法分析。
Union結構
Union{
par_exp_t* exp;
int lt_integer;
}
Bison中默認將所有的語義值都定義爲int類型,可以通過定義宏YYSTYPE來改變值的類型。如果有多個值類型,則需要通過在Bison聲明中使用%union列舉出所有的類型。
Union中的每一個項,都是一個語法規則的每一個非終結符.
其中par_exp_t用來描述被識別出的exp的信息,對應到c/c++代碼中的類型。
可以這樣定義此非終結符
%type exp
%type lt_integer
如果類型名稱與非終結符名稱不一致可使用如下方法
Union{
par_exp_t* eee;
int iii;
}
%type<eee> exp
%type<iii> lt_integer
在一條歸約規則中,每個終結符或非終結符都對應一個c類型,如果沒有指定則爲int類型,具體的類型在union中定義,在每一條匹配規則中用$$或$n表示該類型。
聲明語法的開始符號
%start tiptop
這是告知bison, 這是語法最終需要規約的非終結符號。
示例
input:
/* empty */
| exprs
;
exprs:
error { $$ = 0;}
| exprs error
| expr ';'
{
$$ = t_single_exprs($1);
Execute($1);
}
| exprs expr ';'
{
$$ = t_append_exprs($1, $2);
Execute($2);
}
;
在一條規則中$$表示表達式的返回值,$1表示第一個終結符,依次類推。
從上面input可以看到輸入爲一個表達式集合,而exprs是由expr ';'或exprs expr ';'組成。也就是說一個表達式集合,是由一個或多個表達式後跟';'組成。
$$ = t_single_exprs($1);動作的意思是創建只有一個表達式expr的表達式集,賦值給exprs,此時只歸約到一個表達式。
$$ = t_append_exprs($1, $2);動作的意思是把表達式expr加入到exprs集合裏。Execute($1);的意思是執行這個表達式。這裏執行的意思是計算這個表達式的語義值,輸出結果。
歸約過程
expr:
IDENTIFIER { $$ = t_id($1); }
| DOUBLE_CONST { $$ = t_num($1);}
| expr '+' expr { $$ = t_plus($1, $3); }
| expr '-' expr { $$ = t_sub($1, $3); }
| expr '*' expr { $$ = t_mul($1, $3); }
| expr '/' expr { $$ = t_div($1, $3); }
| '(' expr ')' { $$ = $2;}
| '{' exprs_no '}' { $$ = t_block($2);}
| expr '<' expr { $$ = t_less($1, $3); }
| expr '=' expr { $$ = t_eq($1, $3); }
| IDENTIFIER ASSIGN expr { $$ = t_assign($1, $3); }
| IF expr THEN expr ELSE expr FI { $$ = t_if($2, $4, $6); }
| WHILE expr LOOP expr POOL { $$ = t_while($2, $4); }
;
'{' exprs_no '}' { $$ = t_block($2);}是類似cool語言的一個語法規則:一個表達式可以推出大括號包圍的表達式集合。這個集合類似之前的exprs,區別是exprs_no不需要立即執行表達式的值。因爲可能條件判斷不符合,所以這段代碼就不能執行。
3.語法規則分析
expr:
NUM { $$ = $1; }
| expr '+' expr { $$ = $1 + $3; }
| expr '-' expr { $$ = $1 - $3; }
;
例如這樣一個語句:1+3-2。根據規則expr->NUM先歸約expr+3-2,然後規約'+',繼續歸約終結符爲expr+expr-2。因爲左結合,根據expr->expr + expr,得expr-2。繼續歸約終結符的expr-expr,最後結果爲expr,歸約結束
移進歸約分析
語法分析有自頂向下(LL、遞歸下降分析)和自底向上(LR、移進歸約分析)兩種方法。
Bison採用LALR分析方法,適用於上下文無關文法。
參考鏈接http://www.cppblog.com/woaidongmao/archive/2008/11/23/67635.aspx
操作符優先級
例如1-2*3,考慮到文法二義性可能會產生2種方式進行分析。
假定分析器已經看到了終結符'1','-'和'2';那麼應該對它們歸約到減法運算規則嗎?這取決於下一個終結符。當然,若下一個終結符是')',就必須歸約;此時移進是非法的,因爲沒有任何規則可以對序列'- 2 )'進行歸約,也沒有以這個序列開始的什麼東西。但是如果下一個終結符是'*'或者'<',那麼就需要做一個選擇:移進或者歸約,都可以讓分析得以完成,但是卻有不同的結果。
爲了決定Bison應該怎麼做,必須考慮這兩個結果。若下一個終結符即操作符op被移進,那麼必然是op首先做歸約,然後纔有機會讓前面的減法操作符做歸約。其結果就是(有效的)'1–(2 op 3)'。另一方面,若在移進op之前先對減法做歸約,那結果就是'(1–2) op 3'。很顯然,這裏移進或者規約的選擇取決於減法操作符'-'與下一個操作符op之間的優先級:若op是乘法操作符'*',那麼就選擇移進;若是關係運算符'<'則應該選擇規約。
左關聯與右關聯操作符
那麼諸如'1 – 2 – 5'這樣的輸入又如何呢?是應該作爲'(1–2)–5'還是應該作爲'1–(2–5)'?對於大多數的操作符,我們傾向於前一種形式,稱作左關聯(left association)。後一種形式稱作右關聯(right association),對於賦值操作符來說是比較理想的。當堆棧中已經有'1–2'且預讀終結符是'-',此時分析器選擇移進還是歸約與選擇左關聯還是右關聯是一回事:移進將會進行右關聯。