Lex & Yacc 學(xué)習(xí)筆記(4)- Yacc語法結(jié)構(gòu)

一、背景

  • 概念
    yacc(Yet Another Compiler Compiler),是Unix/Linux上一個(gè)用來生成編譯器的編譯器(編譯器代碼生成器).
    使用巴克斯范式(BNF)定義語法,能處理上下文無關(guān)文法(context-free)。出現(xiàn)在每個(gè)產(chǎn)生式左邊(left-hand side:lhs)的符號(hào)是非終端符號(hào),出現(xiàn)在產(chǎn)生式右邊(right-hand side:rhs)的符號(hào)有非終端符號(hào)和終端符號(hào),但終端符號(hào)只出現(xiàn)在右端。
    yacc是開發(fā)編譯器的一個(gè)有用的工具,采用LR(1)(實(shí)際上是LALR(1))語法分析方法。
    LR(k)分析方法是1965年Knuth提出的,括號(hào)中的k(k >=0)表示向右查看輸入串符號(hào)的個(gè)數(shù)。LR分析法正視給出一種能根據(jù)當(dāng)前分析棧中的符號(hào)串和向右順序查看輸入串的k個(gè)符號(hào)就可唯一確定分析器的動(dòng)作是移進(jìn)還是規(guī)約和用哪個(gè)產(chǎn)生式規(guī)約。
    這種方法具有分析速度快,能準(zhǔn)確,即使地指出出錯(cuò)的位置,它的主要缺點(diǎn)是對(duì)于一個(gè)使用語言文法的分析器的構(gòu)造工作量相當(dāng)大,k愈大構(gòu)造愈復(fù)雜,實(shí)現(xiàn)比較困難。

一個(gè)LR分析器有3個(gè)部分組成:

  • 總控程序,也可以稱為驅(qū)動(dòng)程序。
    對(duì)所有的LR分析器總控程序都是相同的。
  • 分析表或分析函數(shù)。
    不同的文法分析表將不同,同一個(gè)文法采用的LR分析器不同時(shí),分析表也不同,分析表又可分為動(dòng)作(ACTION)表和狀態(tài)轉(zhuǎn)換(GOTO)表兩個(gè)部分,它們都可用二維數(shù)組表示。
  • 分析棧,包括文法符號(hào)棧和相應(yīng)的狀態(tài)棧。
    它們均是先進(jìn)后出棧。 分析器的動(dòng)作由棧頂狀態(tài)和當(dāng)前輸入符號(hào)所決定(LR(0)分析器不需要向前查看輸入符號(hào))。
    LR分析器工作過程如下 :
    其中SP為棧指針,S[i]為狀態(tài)棧,X[i]為文法符號(hào)棧。狀態(tài)轉(zhuǎn)換表內(nèi)容按關(guān)系GOTO[Si,X] = Sj確定,該關(guān)系式是指當(dāng)棧頂狀態(tài)為Si遇到當(dāng)前文法符號(hào)為X時(shí)應(yīng)轉(zhuǎn)向狀態(tài)Sj。X為終結(jié)符或非終結(jié)符。 ACTION[Si,a]規(guī)定了棧頂狀態(tài)為Si是遇到輸入符號(hào)a應(yīng)執(zhí)行的動(dòng)作。

本文討論yacc語法的格式并描述可用的各種特征和選項(xiàng)

二、yacc語法結(jié)構(gòu)

yacc語法包括三部分:定義段、規(guī)則段和用戶子例程段

...定義段...

%%

...規(guī)則段...

%%

...用戶子例程段...

各部分由以兩個(gè)百分號(hào)開頭的行分開,盡管某一個(gè)部分可以為空,但是前兩部分是必須的,第三部分和前面的百分號(hào)可以省略。

符號(hào)

yacc 語法由符號(hào)組成,即語法的“詞”。符號(hào)是一串不以數(shù)字開頭的字母、數(shù)字、句點(diǎn)和下劃線。符號(hào)error專用于錯(cuò)誤恢復(fù),另外,yacc對(duì)任何符號(hào)都不會(huì)附加“先驗(yàn)”的意義。

由詞法分析程序產(chǎn)生的符號(hào)叫做終結(jié)符號(hào)或者標(biāo)記。定義在規(guī)則左側(cè)的叫做非終結(jié)符號(hào)或者非終結(jié)。標(biāo)記也可能是字面上引用的字符,通常遵循約定:標(biāo)記大寫,非終結(jié)符號(hào)小寫。

定義段

定義段包括文字塊,逐字拷貝到生成的C文件開頭部分的C代碼,通常包括聲明和#include行??赡苡?union %start %token %type %left %right 和 %nonassoc聲明。

也可以包含普通的C語言風(fēng)格的注釋,所有這些都是可選的,在簡(jiǎn)單的語法分析程序中,定義段可能完全是空的。

如在定義部分定義標(biāo)志:

%token INTEGER

當(dāng)運(yùn)行yacc后,會(huì)產(chǎn)生頭文件,里面包含該標(biāo)志的預(yù)定義,如:

#ifndef YYSTYPE 
#define YYSTYPE int 
#endif 
#define INTEGER 258 
extern YYSTYPE yylval;

lex使用該頭文件中的標(biāo)志定義。Yacc調(diào)用lex的yylex()來獲得標(biāo)志(token),與標(biāo)志對(duì)應(yīng)的值由lex放在變量yylval中。yylval的類型由YYSTYPE決定,YYSTYPE缺省類型是int。如:

[0-9]+ { 
yylval = atoi(yytext); 
return INTEGER; 
}

標(biāo)志0-255被保留作為字符值,一般產(chǎn)生的token標(biāo)志從258開始。如:

[-+] return *yytext; /* return operator */

返回加號(hào)或減號(hào)。注意要把減號(hào)放在前面,避免被認(rèn)作是范圍符號(hào)。
對(duì)于操作符,可以定義%left和%right:%left表示左相關(guān)(left-associative),%right表示右相關(guān)(right-associative)。可以定義多組%left或%right,在后面定義的組有更高的優(yōu)先級(jí)。如:

%left ‘+’ ‘-‘
%left ‘*’ ‘/’

上面定義的乘法和除法比加法和減法有更高的優(yōu)先級(jí)。
改變YYSTYPE的類型。如這樣定義TTSTYPE:

%union
{ 
     int iValue; /* integer value */ 
     char sIndex; /* symbol table index */ 
     nodeType *nPtr; /* node pointer */ 
};

則生成的頭文件中的內(nèi)容是:

typedef union
{ 
     int iValue;      /* integer value */ 
     char sIndex;    /* symbol table index */ 
     nodeType *nPtr; /* node pointer */ 
} YYSTYPE; 
extern YYSTYPE yylval;

可以把標(biāo)志(token)綁定到Y(jié)YSTYPE的某個(gè)域。如:

%token <iValue> INTEGER 
%type <nPtr> expr

把expr綁定到nPtr,把INTEGER綁定到iValue。yacc處理時(shí)會(huì)做轉(zhuǎn)換。如:

expr: INTEGER { $$ = con($1); }

轉(zhuǎn)換結(jié)果為:

yylval.nPtr = con(yyvsp[0].iValue);

其中yyvsp[0]是值棧(value stack)當(dāng)前的頭部。

定義一元減號(hào)符有更高的優(yōu)先級(jí)的方法:

%left GE LE EQ NE '>' '<' 
%left '+' '-' 
%left '*' 
%nonassoc UMINUS

%nonassoc的含義是沒有結(jié)合性。它一般與%prec結(jié)合使用表示該操作有同樣的優(yōu)先級(jí)。如:

expr: '-' expr %prec UMINUS { $$ = node(UMINUS, 1, $2); }

表示該操作的優(yōu)先級(jí)與UMINUS相同,在上面的定義中,UMINUS的優(yōu)先級(jí)高于其他操作符,所以該操作的優(yōu)先級(jí)也高于其他操作符計(jì)算。

規(guī)則段

規(guī)則段由語法規(guī)則和包括C代碼的動(dòng)作組成。

規(guī)則中目標(biāo)或非終端符放在左邊,后跟一個(gè)冒號(hào)(:),然后是產(chǎn)生式的右邊,之后是對(duì)應(yīng)的動(dòng)作(用{}包含)。如:

%token INTEGER
%%
program: program expr '\n' { printf("%d\n", $2); } 
              ;
expr: INTEGER { $$ = $1; }  
         | expr '+' expr { $$ = $1 + $3; } 
         | expr '-' expr { $$ = $1 - $3; } 
;

%%
int yyerror(char *s) 
{ 
     fprintf(stderr, "%s\n", s); 
     return 0; 
}

其中,1表示右邊的第一個(gè)標(biāo)記的值,2表示右邊的第二個(gè)標(biāo)記的值,依次類推。$$表示歸約后的值。

用戶子例程段

yacc 將用戶子例程段的內(nèi)容完全拷貝到C文件中,通常這部分包括從動(dòng)作調(diào)用的例程。
該部分是函數(shù)部分。當(dāng)yacc解析出錯(cuò)時(shí),會(huì)調(diào)用函數(shù)yyerror(),用戶可自定義函數(shù)的實(shí)現(xiàn)。
main函數(shù)是調(diào)用yacc解析入口函數(shù)yyparse()。如:

int main(void) 
{ 
     yyparse(); 
     return 0; 
}

動(dòng)作

動(dòng)作 是yacc與在語法中規(guī)則相符時(shí)執(zhí)行的C代碼,動(dòng)作一定是C復(fù)合語句。

動(dòng)作有4種可能:

  • 移進(jìn):
    當(dāng)Sj = GOTO[Si,a]成立,則把Sj移入到狀態(tài)棧,把a(bǔ)移入到文法符號(hào)棧。其中i,j表示狀態(tài)號(hào)。
  • 規(guī)約:
    當(dāng)在棧頂形成句柄為β時(shí),則用β歸約為相應(yīng)的非終結(jié)符A,即當(dāng)文法中有 A-->β的產(chǎn)生式,而β的長(zhǎng)度為r(即|β| = r),則從狀態(tài)棧和文法符號(hào)棧中自棧頂向下去掉r個(gè)符號(hào),即棧指針SP減去r。并把A移入文法符號(hào)棧內(nèi),再把滿足Sj = GOTO[Si,A]的狀態(tài)移進(jìn)狀態(tài)棧,其中Si為修改指針后的棧頂狀態(tài)。
  • 接受acc:
    當(dāng)規(guī)約到文法符號(hào)棧只剩文法的開始符號(hào)S時(shí),并且輸入符號(hào)串已結(jié)束即當(dāng)前輸入符是‘#’,則為分析成功。
  • 報(bào)錯(cuò):
    當(dāng)遇到狀態(tài)棧頂為某一狀態(tài)下出現(xiàn)不該遇到的文法符號(hào)時(shí),則報(bào)錯(cuò),說明輸入串不是該文法能接受的句子。

通過使用后面跟有數(shù)字的美元符號(hào),動(dòng)作可以查閱在規(guī)則中與符號(hào)有關(guān)的值,冒號(hào)后面跟的第一個(gè)符號(hào)是數(shù)字1,例如:

date:month  '/'  day   '/'  year  
           { printf ("date %d-%d-%d  found",$1,$3,$5);}  

而名字,$$是指冒號(hào)左邊符號(hào)的值,符號(hào)值可以有不同的C類型。

三、遞歸處理

遞歸處理有左遞歸和右遞歸。
左遞歸形式:

list: item 
    | list ',' item;

右遞歸形式:

list: item 
     | item ',' list

使用右遞歸時(shí),所有的項(xiàng)都?jí)喝攵褩@?,才開始規(guī)約;而使用左遞歸的話,同一時(shí)刻不會(huì)有超過三個(gè)項(xiàng)在堆棧里。

歧義和沖突

由于語法有歧義或者包含沖突,yacc對(duì)于語法規(guī)范的翻譯可能會(huì)失敗。一些情況下,語法確實(shí)有歧義,也就是說對(duì)于一個(gè)單獨(dú)的輸入字符串有兩種可能的分析而且yacc處理不了。

另外一些情況,語法并無歧義,但yacc使用的語法分析技術(shù)不足以分析這個(gè)語法。

  • 移進(jìn)/歸約沖突
    當(dāng)一個(gè)輸入字符串有兩種可能的分析時(shí),而且其中一個(gè)分析完成一個(gè)規(guī)則(歸約選項(xiàng)),而另一個(gè)卻沒有(移進(jìn)選項(xiàng))時(shí),移進(jìn)/歸約沖突便發(fā)生了。
    例如:
%%  
e: ‘X’  
     |e  '+'   e  
         ;  

對(duì)于輸入字符串“X+X+X” ,有兩種可能的分析: “(X+X)+X”或者“X+(X+X)”,采用歸約選項(xiàng)使得語法分析程序使用第一個(gè)分析,而采用移進(jìn)選項(xiàng)則使用另一個(gè)。

  • 歸約/歸約沖突
    當(dāng)同樣的標(biāo)記可以完成兩個(gè)不同的規(guī)則時(shí),就會(huì)發(fā)生歸約/歸約沖突。
    例如:
%%  
prog:    proga | progb  
proga:       'X' ;  
progb:       'Y' ;  

一個(gè)“X”可能是proga,也可能是progb。

大多數(shù)歸約/歸約沖突沒這么明顯,但是幾乎在任何情況下它們?cè)谡Z法中都表現(xiàn)為錯(cuò)誤。

  • If-Else 的沖突
    當(dāng)有兩個(gè)IF一個(gè)ELSE時(shí),該ELSE和哪個(gè)IF匹配是一個(gè)問題。有兩種匹配方法:與第一個(gè)匹配和與第二匹配。現(xiàn)代程序語言都讓ELSE與最近的IF匹配,這也是yacc的缺省行為。
    雖然yacc行為正確,但為避免警告,可以給IF-ELSE語句比IF語句更高的優(yōu)先級(jí):
%nonassoc IFX 
%nonassoc ELSE
stmt: IF expr stmt %prec IFX 
       | IF expr stmt ELSE stmt

四、特殊字符

由于yacc處理符號(hào)標(biāo)記而不是文本,它的輸入字符集比起lex來說就簡(jiǎn)單的多,下面列出了yacc所使用的特殊符號(hào)的列表:

%
具有兩個(gè)%標(biāo)記的行將yacc語法分成了幾部分;
定義段的所有聲明都是以%開始,包括%{ %} %union %start %token %type %left %right 和 %nonassoc聲明。

\
反斜線符號(hào)是廢棄的百分號(hào)同義詞,在動(dòng)作中,C語言字符串中有其通常作用。

$
在動(dòng)作中,美元符號(hào)引入一個(gè)值引用,舉例來說,$3表示規(guī)則右端第3個(gè)符號(hào)的值。

'
文字標(biāo)記由一個(gè)單引號(hào)結(jié)束,例如 'z' 。

<>
在一個(gè)動(dòng)作的值引用中,可以不考慮尖括號(hào)包圍起來的默認(rèn)類型。

"
有些yacc版本在文字標(biāo)記中將單引號(hào)和雙引號(hào)同等對(duì)待,這樣使用根本不方便。

{}
動(dòng)作中C代碼在大括號(hào)中。

;
除了后面緊接著是以豎線開頭的規(guī)則外,規(guī)則部分每個(gè)都是以分號(hào)結(jié)束。

|
當(dāng)連續(xù)兩個(gè)規(guī)則具有相同的左端,第二個(gè)規(guī)則可用一個(gè) | 代替符號(hào)和冒號(hào)。

:
在每一條規(guī)則里,左端的每個(gè)符號(hào)后面都跟著一個(gè)冒號(hào)。

_
符號(hào)可以包括和字母、數(shù)字以及句點(diǎn)在一起的下劃線。

.
符號(hào)可以包括與字母、數(shù)字、下劃線一起的句點(diǎn)。

=
早期版本使用,現(xiàn)已不推薦。

五、Yacc 源程序的風(fēng)格

建議按照如下風(fēng)格來寫:

  1. 終端符名全部用大寫字母,非終端符全部用小寫字母;
  2. 把語法規(guī)則和語義動(dòng)作放在不同的行;
  3. 把左部相同的規(guī)則寫在一起,左部只寫一次,而后面所有規(guī)則都寫在豎線“|”之后;
  4. 把分號(hào)“;”放在規(guī)則最后,獨(dú)占一行;
  5. 用制表符來對(duì)齊規(guī)則和動(dòng)作。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 編譯原理 第一章 引言 1.從面向機(jī)器的語言到面向人類的語言 匯編指令:用符號(hào)表示的指令被稱為匯編指令匯編語言:匯...
    SnorlaxSE閱讀 55,899評(píng)論 5 60
  • 簡(jiǎn)介 如果你有Unix環(huán)境的編程經(jīng)驗(yàn),想必你肯定遇到過神秘的Lex和YACC工具,在GUN/Linux中,又分別稱...
    upupSue閱讀 4,738評(píng)論 0 8
  • 官網(wǎng) 中文版本 好的網(wǎng)站 Content-type: text/htmlBASH Section: User ...
    不排版閱讀 4,701評(píng)論 0 5
  • 他們一路無所顧慮的飛奔著,已經(jīng)接近深夜,路上車輛行人都少了很多,若雪不顧方向的向前行進(jìn)著,只要不會(huì)撞樹,不會(huì)撞墻,...
    一心小記閱讀 347評(píng)論 0 3
  • 文/趙元波 宋仁宗寶元元年,西夏的黨項(xiàng)人圍攻延安城七天,禮部尚書范雍任統(tǒng)帥領(lǐng)軍防守。敵人不斷進(jìn)攻,延安城岌岌可危,...
    趙元波閱讀 538評(píng)論 0 0

友情鏈接更多精彩內(nèi)容