<<編譯原理>>
詞法分析Lexical analysis或Scanning:根據代碼文本區分單詞類型 保留字(while if else ...),數據類型(整型,字符,浮點..),操作符(+ - * | & ...) ,若有對應單詞類型繼續語法分析,否 則 詞法異常
語法分析Syntax analysis或Parsin:根據給定的語法,在詞法分析器給的結果分析是否符合語法規則。LL分析法和LR分析法。符合繼續語義分析,否則 語法異常
四個動作 shift移入 reduce規約 accept(增廣文法的接收$) error 報錯
編譯原理lecture 4d :hezhenfeng
語義分析semantic analysis:在語法分析的基礎上,給出對應語法語句的語義規則,根據語義規則得到中間代碼(eg.三地址碼(類似彙編))
編譯原理。自底向上分析,最優推導
----------------------------------------------------
LR(0)沒FOLLOW集,
SLR(1)歸約時FOLLOW集所在的全部可歸約
LR(1)在產生式的歸約中(即增加搜索符【新擴產生式時加入原來的終結符或結束標誌】
--用於follow集無法解決的衝突--只遇到搜索符的時候纔可以歸約)
LALR(1)在LR(1)下增加同心合併(合併時只考慮產生式相同,搜索符合並)
----------------------------------------------------
SLR(1)有FOLLOW集計算(歸約時考慮,沒有搜索符)
1增廣文法(的歸約acept)
2消除左遞歸
3產生是規則 A->B 排序編號
3計算FOLLOW集,部分依賴於FIRST集。
4構造預測分析表
狀態|項目集|後繼符號(將移入/規約產生式)|後繼狀態(S移入/規約)
語義規則 A->B A.NODE=B.NODE
代碼段
移入/規約reduce
==========================================
Lex 詞法分析.l Yacc語法分析.y
lex 文件.l :正則表達式 保留字 數據類型 操作符
Yacc 文件.y :上下文無關的文法-產生式。LALR(1)
extern char *yytext; /* Correct for Flex */
extern char yytext[]; /* Correct for traditional Lex */
===========================================
http://dinosaur.compilertools.net/lex/
Lex詞法分析器(先翻譯的Yacc,Lex部分翻譯)
把用戶的表達式和actions(即源資源source)轉換爲目標語言。生成程序叫做yylex。
%%
[ \t]+$
開始部分用%%分界,這個規則部分包含一個正則表達式,用於匹配一個或多個的空格和退格鍵。
=========
2.Lex Source
Lex的原文件基本格式
{definitions}
%%
{rules}
%%
{user subroutines}
===========
3.Lex Regular Expressions 詞法分析器的正則表達式
正則表的是的操作符有
" \ [ ] ^ - ? . * + | ( ) $ / { } % < >
雙引號 |反斜槓|左方括號|右方括號|上尖號|減號|問號|句點|星號|加號|豎槓|左括號|右括號|美元符號|斜槓|左花括號|右花括號|百分號|左尖號|右尖號
匹配這些符號的串要進行轉義。採用雙引號進行轉義".
比如xyz“++”匹配的是串 xyz++
操作符也可以用反斜槓進行轉義即 xyz\+\+
==========
動作 REJECT 意爲"跳到下一個可選規則"
==========
規則可以縮寫
D [0-9]
E [DEde][-+]?{D}+
%%
{D}+ printf("integer");
{D}+"."{D}*({E})? |
{D}*"."{D}+({E})? |
{D}+{E}
==========
12.Summary of Source Format 格式總結
{definitions}
%%
{rules}
%%
{user subroutines}
1--Definitions
4--開始條件,使用下列格式
%S name1 name2 ...
5--字符集表,在格式中
%T
number space character-string
...
%T
6--更改內部數組大小,使用格式爲
%x nnn
一個十進制nnn表示數組大小,x的可選參數爲
字母 參數
p 位置
n states
e 樹節點
a 轉換數
k 字符包類數目
o 輸出數組大小
Lex中使用的正則表達式操作符和釋譯:
x the character "x"
"x" an "x", even if x is an operator.
\x an "x", even if x is an operator.
[xy] the character x or y.
[x-z] the characters x, y or z.
[^x] any character but x.
. any character but newline.
^x an x at the beginning of a line.
<y>x an x when Lex is in start condition y.
x$ an x at the end of a line.
x? an optional x.
x* 0,1,2, ... instances of x.
x+ 1,2,3, ... instances of x.
x|y an x or a y.
(x) an x.
x/y an x but only if followed by y.
{xx} the translation of xx from the
definitions section.
x{m,n} m through n occurrences of x
**********************************************************************
編譯鏈接庫-lfl
http://dinosaur.compilertools.net/flex/manpage.html
Flex(和lex有些部分不兼容,特有的比如%option 更多見
%s 和%x作爲開始在definitions聲明。
開始符用BEGIN動作
默認開始條件堆棧是使用動態增漲的,因此構建沒有大小限制。如果內存疲乏,程序會執行aborts中止。
如果要使用條件堆棧。你的掃描要包含%option stack 指令
http://dinosaur.compilertools.net/flex/flex_17.html#SEC17
一些%options的特定指令設置了纔可以獲得
http://dinosaur.compilertools.net/flex/flex_20.html#SEC20)
`always-interactive' 指示flex生成一個和輸入交互的掃描器。通常,掃描每個新
輸入文件時調用isatty(),以試圖確定掃描的輸入原文件是交互的,因此應該每次讀取一個字符。當這個option使用時,不會進行這個調用。
`main'
引導flex提供一個main()作爲yylex()的簡單調用。該option意味着noyywrap
`never-interactive'
flex生成一個不考慮交互的掃描器。[也不調用isatyy().]和always-相反。
`stack'
啓動是要用開始條件stack
`stdinit'
沒有設置時,初始yyin和yyout指向空文件指針而不是標準輸入輸出.
`yylineno'
引導flex生成的掃描器採用當前輸入文件的行數作爲全局變量那個yylineno。這個歌option意味着 %option lex-compat
`yywrap'
沒有設置時(即%option noyywrap),掃描器在文件結尾前不會調用yywrap(),除非已經沒有文件要掃描(直到用戶的yyin指向的新文件並在此調用yylex())
flex可以掃描你的actions決定使用REJECT或者yymore()特性。reject和yymore的options可以通過重載是否適用通過%option reject確認使用或%option noyymore取消設置。
====================
***********************************************************************
https://luv.asn.au/overheads/lex_yacc/yacc.html
YACC的語言規範
%{
/* C declarations and includes */
%}
/* Yacc token and type declarations */
%%
/* Yacc Specification
in the form of grammer rules like this:
*/
symbol : symbols tokens
{ $$ = my_c_code($1); }
;
%%
/* C language program (the rest) */
Options
最新options規則版本,這些items不是真正的menu項,只是設置一些options,例如標題title和列columns。這些被定義作爲struct menu的成員。當我們解析這些options時,我們不能獲得正確的struct menu。我們可以用一個全局變量存儲當前的menu。但是當我們達到子菜單尾部時,恢復到正確的菜單就會棘手。
解析器根據我們的使用習慣傳遞值更簡單。
我們使用menu項作爲傳輸title和columns的options。而後將會好似用add_item的函數來處理這些特殊的項,並釋放這些結構。
***********************************************************
YACC
http://dinosaur.compilertools.net/yacc/
目錄
1.規範 產生式
2.動作 action 引用變量$ 全局變量位置和命名避免
3.詞法分析 Token數257宏定義 Lex
4:解析器是如何工作的[編譯原理預測分析表、GOTO表]
5:歧義和矛盾
6:優先級
7:錯誤處理
8:Yacc環境量
9:提示準備規範
10:高級主題 類型
11:致謝
YACC是用便攜C編寫的。接受的規範類是一個非常普遍的規則:LALR(1)語法,具有消除歧義的規則(上下無關文法)。
==================================
1基本規範--結構 開始符 結束符
Yacc每個規範文件由三部分組成,由兩個百分號%%分隔。
(百分號%作爲yacc規範的轉義字符)
declarations
%%
(grammar)rules
%%
programs
聲明declarations部分可以爲空。更甚,如果programs部分省略,%%可以省略則
%%
rules
空格 、退格、換行被忽略,除此之外,不能出現在names和大量字符的保留字中。
註釋/* */,出現在name的任何位置均合法。如C和PL/I一樣。
rules有一個或多個語法規則組成,形如
A:BODY;
名稱可以是任意長度的,可以由字母[a-zA-Z]點[.] 頓號[、]下劃線[_]和非初始數字[0-9]組成。
大寫字母和小寫字母是不同的。語法規則體中使用的名稱可以表示記號token或非終結符nonterminal symbols。
由字符構成的錯排包含在單引號中。和C語言一樣,斜槓\作爲錯排內的轉義字符,左右的C的轉義字符均被識別.因此
'\n' newline換行
'\r' return回車
'\'' single quote單引號 ``'''
'\\' backslash斜槓 ``\''
'\t' tab退格
'\b' backspace空格
'\f' form feed 走紙換頁
'\xxx' ‘’xxx''八進制
由於技術原因。NUL('\0' 或0)在rules中不被使用。若有非終結符(左邊A)相同,可以用豎槓|避免左邊的重複改寫。
此外,在豎槓左邊rule結束的分號可以被丟棄。
A : B C D ;
A : E F ;
A : G ;
YACC中可給定爲
A : B C D
| E F
| G
;
合並不是必要的,她只是讓語法可讀性更強、更容易更改。
非終結符和空格串匹配,可以顯示方法
empty: ;
NAME代表記號token必須聲明,簡記爲
%token name1 name2 . . .
decalretions聲明部分在(3.5.6)
Name在聲明部分沒有的,代表着非終結符。
每一個非終結符必須有一個規則。即在rules左邊出現。
所有的非終結符,叫開始符。解析器用來識別的開始象徵。
因此,開始符代表在(grammar)ruler中佔據最大且最普遍的。
開始符默認爲ruler部分的第一條語法規則的左值。
在declaretion部分顯示的聲明開始符用%start作爲關鍵字。
%start symbol
解析器的輸入結束標誌token成爲endmark。達到這個token但是不包含它,
這個結束標誌建立一個匹配開始符號的結構,解析函數在遇到結束標誌時調用
函數返回;接收輸入。如果結束標誌出現在其他的上下文中,是一個錯誤。
提供詞法分析器,並在適當的時候返回結束標誌。詳見3.結束符通常比較明顯
的I/O狀態或文件結束符EOF
=============================================================
2動作
每條語法規則後可能會有一個動作
執行每條規則的識別輸入時,這個動作可能會返回一個值,
可以獲得之前的動作的返回值。必要的話,詞法分析器lexical analyzer可以返回tokens的值。
動作可以是任意的C語句。可以做輸入輸出和調用子程序,改變外部變量和變量。一個動作可以
是很多語句,包含在花括號中。
A: '(' B ')'
{ hello( 1, "abc" ); }
和
XXX : YYY ZZZ
{ printf("a message\n");
flag = 25; }
帶有動作的rulers。
爲了方便action和解析的溝通,action語句略有修改。
$作爲Yacc的信號.
爲了返回一個值,action通常設置僞變量$$爲一些值。例如,action沒有做任何事情只是返回值1
{$$=1;}
爲了獲得之前的action的值以及詞法解析,動作可以使用僞變量$1 ,$2 ,...。涉及到的值根據組成由
規則的右邊根據左到右。例如規則
A:B C D;
$2是C返回,$3是D返回。
更復雜的
expr: '('expr')';
這個規則的返回值通常爲括號內的,表明
expr: '('expr')';{$$=$2;}
默認的,規則的值爲第一個元素$1。因此形如
A:B;
不需要明確的動作。
在上面的樣例中,所有的動作都是他們的最後的規則。
在一個ruler被完全解析獲得控制是令人滿意的。Yacc允許action在規則的中間和結尾,
規則假定返回一個值,通過右邊的動作容易接近這種機制。
可以獲得他左邊的符號,因此
A : B
{ $$ = 1; }
C
{ x = $2; y = $3; }
;
設置x=1,y爲C的返回值
行動不是終止規則實際上是由 Yacc通過製造一個新的非終結符號名稱,和一個新的規則匹配這個名字到空字符串。
內部動作是通過識別這一附加規則觸發的動作。YACC實際上把上面的例子當作是已經寫的:
$ACT : /* empty */
{ $$ = 1; }
;
A : B $ACT C
{ x = $2; y = $3; }
;
在許多應用程序中,輸出是沒有直接的action; 相反,一個數據結構,如一個解析樹,構造在內存中,在輸出之前移入被應用。 解析樹尤其容易構造,
例程來構建和維護想要的樹結構 。 例如,假設有一個C函數節點,叫
node( L, n1, n2 )
創建一個節點標籤L,後繼節點n1和n2, 返回新創建的索引節點。 然後解析樹可以 由提供如下action:
expr : expr '+' expr
{ $$ = node( '+', $1, $3 ); }
在這規範
用戶可以定義其他變量來被action使用。 聲明和定義可以出現在聲明declaretions部分,包含在標記%{和%}之間。
這些聲明和定義有全局作用域,所以他們被action的語句和詞法分析器Lex識別。 例如,
%{ int variable = 0; %}
可以放置在聲明declaretions部分,變量可以被所有的action訪問。 Yacc解析器只使用Name以yy開始的; 用戶應避免這樣的名字。
在這些例子中,都是整數的值:討論 其他類型的值將會在10節。
==========================================================
3:詞法分析
用戶必須提供一個詞法分析器讀取輸入流和溝通tokens(帶值,如果需要的話) 給解析器。 詞法分析器是一個整數值函數 叫yylex。
函數返回一個整數,token的數目, 代表讀取這種token。 如果有一個相關值token,它應該分配給外部變量yylval。
解析器和詞法分析器必須同意這些token號碼爲了他們之間的通信。這些數字可能選擇的Yacc,或由用戶選定。在這兩種情況下,C的
#define機制定義允許用於詞法分析器返回這些數字象徵意義(象徵性地)。例如,假設標記名數字已經Yacc的規範文件中的
declaretinos部分中定義已經定義
詞法分析程序的相關部分的樣子
yylex(){
extern int yylval;
int c;
. . .
c = getchar();
. . .
switch( c ) {
. . .
case '0':
case '1':
. . .
case '9':
yylval = c-'0';
return( DIGIT );
. . .
}
. . .
目的是返回一個token數量的數字,和一個值 數字的數值。 提供的詞法分析程序代碼的在
規範文件的program部分,DIGIT將被定義爲的token數,與token相關數字。
這種機制導致清晰、容易修改詞彙 分析程序; 唯一的缺陷是需要避免使用任何token NAME
的語法或C的保留字或解析器的保留字; 例如,如果用戶使用token name或許
會造詞法分析器的變異困難。token名錯誤句柄預留給錯誤句柄, 不應想當然的使用(見第7節)。
正如上面提到的,token數可能選擇的Yacc 或由用戶。 在默認情況下,數目由 Yacc選擇。
默認的字面量的token數是這個本地字符集的數值。 其他名稱分配token數字從257開始。
分配token數量給一個token(包括文字), 第一次出現的token name名稱或字面量在declaretions
部分可以立即緊隨其後的是負的整數。 這個整數是的token或者字面的數量。名稱和字面量沒有保持默認的定義的機制。
所有的token的數不一樣是很重要的。。
由於歷史原因,endmarker一定token數0或負數的。 這個token數不能用戶被重新定義的 ;
因此,所有的詞法分析器應該準備返回0 或負數作爲作爲令token數達到輸入尾。
一個非常有用的構造詞法分析器工具 是邁克Lesk Lex程序。 [8]這些詞法分析器被設計爲何Yacc解析器緊密工作。
Lex規範中使用正則表達式代替語法規則grammar rules;
Yacc A:B{action};
Lex 正則{action}
Lex可以很容易地用於產生相當複雜詞法分析,
但仍有一些 語言(如FORTRAN)不適應理論框架, 詞法分析器必須手工製作的。
===============================================
4:解析器是如何工作的(可聯繫編譯原理的預測分析表理解)
Yacc規範文件轉化爲一個C程序, 解析輸入根據給定的規範。 從規範的解析算法是複雜的, 在這裏不討論(請參閱參考更多信息)。
然而,解析器本身是相對簡單的, 和不是非常嚴格的理解它是如何工作的,會更易於理解錯誤處理和恢復。
解析器產生的Yacc堆棧由一個有限狀態機。解析器也能夠閱讀和記憶的下一個輸入token(稱爲超前token)。
當前狀態總是一個堆棧的頂部。給出了有限狀態機的狀態小整數標籤;最初,狀態是0,堆棧只包含狀態0,沒有先行token被讀取。
這種機制只有4中action 。移入shift,規約reduce、接受accept、和錯誤error。解析器的移動完成如下:
1。在其當前狀態的基礎上,解析器決定是否它 需要一個先行token來決定應該採取什麼action;
如果它需要一個token,但沒有,它會調用yylex 獲得下一個token。
2。在當前狀態,如果需要先行token,解析器決定它的下一個action,並攜帶。這可能會導致狀態被推壓入堆棧,
或被取出的堆棧,先行token被處理或放開。
解析器最常見的動作的是移入shift。每當採取移入時,總有一個先行token。例如,在狀態56可能有一個動作:
IF shift 34
在狀態56,先行token是IF,當前狀態56壓入堆棧。狀態56壓入堆棧,狀態變爲34(棧頂),先行token被清空。
reduce規約動作在阻止堆棧沒有邊界的增長 。reduce,當解析器看到語法規則rule的右邊,規約動作是恰當的,
zuo右邊部分被左邊的部分代替,他需要查閱先行token來決定是否規約,但通常不需要; 事實上,默認action(由一個表示 “.”)
通常是一個規約action。
規約action與個人的語法規則rule相關。 語法規則給出小整數數字,導致一些混亂。 這個動作
. reduce 18
是指語法規則rule18,而行動
IF shift 34
涉及狀態34。
假設規則被reduce規約的是 A:x y z;
歸約action取決於左手的符號(當前情況下A),符號右邊的符號數量(當前情況下3)。規約時,棧頂的三個狀態出棧。
通常,出棧的狀態的數目等於rule右邊的符號數目。 實際上, 這些狀態堆棧上識別爲,x, y和z,沒有其他任何目的。
這些狀態出棧後,一個解析器執行之前的規則的狀態被發現。使用這個發現的狀態 ,
二這個狀態在ruler的左邊,執行移入A 。新狀態A獲得並壓棧,解析繼續。但執行左邊的符號和普通的token的移入
有很大的不同,左邊符號的移入action 叫做goto action。在移入時先行token被清除,並且不被goto影響。
任何情況下,該發現的狀態獲得一個入口即
A goto 20
使得狀態20被壓倒到堆棧,併成爲了當前狀態。
實際上,reduce歸約行動“時光倒流” 解析,從堆棧中取出狀態,ruler右手的是先被看到。 如果ruler的右手是空的,
按規律解析器會看到左邊。 ,沒有狀態從堆棧的出棧:發現的狀態爲當前狀態。
reduce歸約動作對於用戶應用動作和值也很重要。當一個規則reduce歸約時, 代碼提供的規則執行堆棧調整。
堆棧出來保持狀態,另有堆棧保證詞法分析的值和actions與之平行。當移入操作,外部變量yylval被複制到
值得堆棧中。在返回用戶代碼之後,規約時會攜帶回。當執行goto動作時,外部變量yyval被複制到值堆棧中。
爲變量$1,$2等,會指向堆棧的值。
其他兩個解析器操作在概念上更加簡單。 接受的行動表明整個掃描過並它匹配的規範了。 這個動作只出現
在先行token已經是結束符,並且表明解析器工作已經完全成功。而error動作,表明根據當前規範LALR(1)解析器
無法繼續工作。這個輸入tokens已被讀取,不能被任何其他的合法的後繼符號輸入。解析器報告一個錯誤,並嘗試
恢復,而重新解析:錯誤恢復將在7部分(而不是檢測偵查錯誤)
這是一個例子! 根據規範
%token DING DONG DELL
%%
rhyme : sound place
;
sound : DING DONG
;
place : DELL
;
當Yacc -v 選項調用,解析器根據人類可讀的描述,產生一個輸出文件y.output,。對應上面的語法y.output理解
(一些統計剝去結尾)【預測分析表】:
state 0
$accept : _rhyme $end
DING shift 3
. error
rhyme goto 1
sound goto 2
state 1
$accept : rhyme_$end
$end accept
. error
state 2
rhyme : sound_place
DELL shift 5
. error
place goto 4
state 3
sound : DING_DONG
DONG shift 6
. error
state 4
rhyme : sound place_ (1)
. reduce 1
state 5
place : DELL_ (3)
. reduce 3
state 6
sound : DING DONG_ (2)
. reduce 2
注意到,出來每個狀態的actions,在每個狀態裏都有解析rule執行過程的描述。在每個rule中,下劃線 _字符
用於表明什麼被掃描,以及什麼將到來。輸入是
DING DONG DELL
執行過程根據解析步驟是有用的。
最初,當前狀態是狀態0。 解析器需要根據輸入的順序決定action。第一個token,DING,被讀取,成爲先行token。
狀態0在DING行動是“移入 3”,所以狀態3壓到堆棧,和先行token清除。 狀態3成爲當前狀態。 下一個記號, DONG,被讀取,
成爲先行token。 在狀態3,先行DONG的動作是“移入6“,因此狀態6被壓入堆棧,先行被清空。狀態堆棧現在包含0, 3和6,
沒有可以讀取的的先行token, 解析器由規則2可以歸約。
sound : DING DONG
這個規則有在右邊兩個符號,所以狀態6和3,從棧中彈出,發現狀態0。查詢狀態0時GOTO表的sound位置,
sound goto 2
獲得; 因此狀態2被壓入堆棧,成爲當前狀態。
在狀態2下一個token,DELL,必須讀取。 這個動作是“移入5”,所以狀態5壓倒堆棧,現在有0、2和5,並且先行token將被清除。
在狀態2的place,唯一的動作歸約規則3。有一個符號在右邊,所以狀態5彈出堆棧,顯現狀態2。在狀態2的goto中,左邊規則3是狀態4。
現在,堆棧包含0、2和4。 在狀態4中,唯一的動作就是規約規則1。有兩個符號在右邊,所以右邊兩個符號將被彈出,棧頂兩個狀態被彈出,
顯現狀態0。在狀態0,goto的rhyme符號解析進入狀態1。 在狀態1,輸入被讀取,獲得結束符,在y.output文件中由“$end”表示。
狀態1的讀取結束符時動作是接收,成功地結束瞭解析。
面對這種不和規矩的字符串如DING DONG DONG, DING DONG, DING DONG DELL DELL,等,讀者被要求瞭解解析器是如何工作的。
花費幾分鐘瞭解這個例子和其他簡單的例子,在面臨更復雜的上下文問題時會有所收穫。
=========================================
5:歧義和矛盾[二義性]
如果有一些輸入字符串可以用兩種或更多種不同的方式來構造(一種語法),那麼一組語法規則是模棱兩可的。例如,語法規則
expr : expr '-' expr
表示自然表達式是一個算術表達式,由兩個表達式通過減號合起來。
遺憾的是,這種語法規則並不能完全指定(所有)複雜的輸入被構造的方式。例如,如果輸入
expr - expr - expr
規則允許這個輸入結構
( expr - expr ) - expr
或者
expr - ( expr - expr )
(第一個叫左關聯[最左推導],第二種右關聯[最右推導]).
Yacc 在嘗試構建解析器時會檢測這種模糊性。當遇到下列給出的輸入時
解析器考慮這種問題是有意義的。
expr - expr - expr
當潔希提讀取到第二個給expr時,輸入就已經讀取了:
expr - expr
匹配了語法rule的右邊,解析器能夠應用這條規則進行輸入歸約。 應用以後,
輸入歸約爲expr(即語法rule的左邊)。解析器會讀取剩下的輸入部分:
- expr
並且再次歸約,這樣是採取左關聯解析的效果。
或者,當解析器讀取
expr - expr
它可以推遲的直接應用規則,並繼續 讀取輸入,直到看到了
expr - expr - expr
它可以將規則應用到右邊的三個符號, 規約他們到expr和留下
expr - expr
現在規則可以再次被規約; 這是採用有關聯解析的效果。因此,讀取
expr - expr
解析器可以做兩個合法的事情,移入或歸約, 他們之間無法決定,這叫做移入規約衝突。
也可能發生兩個合法的規約,這叫做歸約歸約衝突。一定不會發生移入移入衝突。
當存在移入歸約或歸約規約衝突,Yacc仍然產生解析。無論哪裏都只選擇其中一個有效的。
在給定的情況下選擇的描述的rule稱爲消除歧義規則。
Yacc默認調用兩個消除歧義規則:
1。 在移位/歸約衝突中,默認的移位。
2。 歸約/歸約衝突,默認通過早期的語法規則(在輸入的序列)歸約
規則1意味着無任何是的選擇是歸約推遲。
規則2給用戶對解析器的行爲的粗糙控制,但要儘可能地避免歸約歸約衝突。
由於輸入或邏輯錯誤,可能出現衝突, 或者因爲語法規則始終一致,
需要一個比Yacc構造更復雜的解析器。如果執行action必須在解析器確認識別rule之前,
使用rules的actions也會引起衝突。 在這種情況下,不恰當的二義性消除規則的應用, 將導致錯誤的語法分析器。
由於這個原因, Yacc總是報道通過規則1和規則2解決移入歸約和歸約歸約衝突的數目。
一般來說,是可以通過二義性消除規則產生正確的解析器。也可以重寫語法規則rule,以便讀取有相同的輸入而沒有衝突。
出於這個原因,大多數以前的解析器生成器 一直認爲衝突是致命的錯誤。我們的經驗建議重寫有點不自然,產生慢的解析器;
因此,Yacc將生成解析器即使在存在衝突。
作爲一個二義性消除規則的例子,考慮一個編程語言涉及的一個片段從“if - then - else” 構造:
stat : IF '(' cond ')' stat
| IF '(' cond ')' stat ELSE stat
;
在這些規則中,IF和ELSE是tokens,cond描述條件(邏輯)是一個非終結符,expressions和stat是描述聲明的非終結符。
第一個規則將被稱爲simple-if,第二個if - else法則。
這兩個規則形成一個二義性的結構,因爲輸入的形式
IF ( C1 ) IF ( C2 ) S1 ELSE S2
可以按照這些規則構造兩個方法:
IF ( C1 ) {
IF ( C2 ) S1
}
ELSE S2
或
IF ( C1 ) {
IF ( C2 ) S1
ELSE S2
}
第二個解釋是大多數編程語言的一種構造。 每個ELSE和最近的IF相關聯。在這個歌例子裏面,
這個情況解析器應該讀取爲
IF ( C1 ) IF ( C2 ) S1
並且讀取到ELSE時,應該根據simple-if立即歸約爲
IF ( C1 ) stat
然後讀剩下的輸入,
ELSE S2
並規約爲
IF ( C1 ) stat ELSE S2
根據if - else規則。 上述即爲輸入的第一分組。
另一方面,ELSE會被移入,並讀取狀態S2, 然後右邊的部分
IF ( C1 ) IF ( C2 ) S1 ELSE S2
可以根據if - else規則歸約爲
IF ( C1 ) stat
這時可以根據simple-if規則歸約。 上述便是輸入第二分組。即,所期望的。
一旦解析器再次可以做兩個有效的事情——就會有移位歸約衝突。 此時消除二義性規則1就會告訴解釋器進行移入,
即所期望的分組。
這種移位/歸約衝突發生只有當有一個特定的輸入符號,ELSE,否則,並且特性的輸入已經被讀取,如
IF ( C1 ) IF ( C2 ) S1
一般來說,可能會有許多衝突,每一個與一個輸入符號和之前讀取輸入集合相關。 之前讀取的輸入根據解析器的狀態來表徵。
對Yacc的衝突消息最好的理解是通過輸出的測試詳細文件(- v)選項。 例如,輸出對應於上述衝突狀態可能是:
23: shift/reduce conflict (shift 45, reduce 18) on ELSE
state 23
stat : IF ( cond ) stat_ (18) stat : IF ( cond ) stat_ELSE stat
ELSE shift 45 . reduce 18
第一行描述了衝突,給出了狀態和輸入符號。 緊跟普通狀態的描述,當前狀態語法規則rule語態,
以及解析器actions。下劃線_標記語法規則rule被讀取的部分 。 因此,在這個例子中,在狀態23時解析器已讀取可以理解爲
IF ( cond ) stat
此時展示的兩個語法規則是活躍的。解析器可以做兩種可能的事情。 如果輸入符號ELSE, 有可能移入狀態45。 狀態45作爲描述的一部分,
stat : IF ( cond ) stat ELSE_stat
因爲這個狀態下ELSE會移入。 在狀態23歲的可供選擇的action,由描述爲". ",是 如果輸入符號沒有明確的提及上面的action;
在這種情況下,如果輸入符號不是ELSE, 解析器可以根據語法規則18進行歸約:
stat : IF '(' cond ')' stat
再次,注意“移入”命令後面的數字涉及的是狀態,而“規約”後面的數字是語法規則數。
在文件y.output中,rules被歸約之後的規則數會被打印。大多數狀態中,儘可能多的狀態是歸約action,這是默認的命令。
用戶在遇到不可預料的移入歸約衝突時會儘可能地查看詳細的輸出文件決定默認的actions是否合適。
在非常困難的情況下,用戶可能需要知道比這裏更多語法解析器的相關行爲和構造。
這時,理論參考文獻之一[2、3、4]可能諮詢;本地的GURU服務業是適合的。
========================================
6:優先級
在相同的情況下,上面給出的解決衝突是不夠的。這裏有一個算是表達式的解析。算術表達式最常用的是根據操作符的優先級,以及左結合或右結合信息。 事實證明,二義性語法與帶有歧義的規則的解析器相較於消除二義性的解析器更快和且更容易編寫 。 基於這個概念寫出的語法規則形式
expr : expr OP expr
和
expr : UNARY expr
所有二元和一元運算符。 這將創建一個二義性的語法,存在很多解析衝突。 作爲二義性規則,用戶對左右的操作符以及二元操作符的結合性指定優先級,或強約束。 實現期待的優先級和結合性這些信息對於Yacc 解決器依照這些解決規則衝突, 和構造一個解析器是很重要的 。
優先級和結合性依附於在聲明declarations部分的tokens。這些通過一系列的YACC的關鍵字開始:
%left, %right, or %nonassoc 後面緊跟着一系列的tokens.左右的tokens在同一行。
假定有同樣的優先級和結合性,優先級和捆綁隨着行數的增加而增強。 因此,
%left '+' '-'
%left '*' '/'
描述了四種運算的優先級和結合性 操作符。 +-左結合,並且比也是左結合的* \較低優先級。 關鍵字%right用於描述右結合, 而關鍵字% nonassoc用於描述操作,例如在Fortran的.LT的操作 ,可能不會互相關聯; 因此,
A .LT. B .LT. C
在Fortran是非法的,和這樣的操作在YACC將被以關鍵字% nonassoc 描述 。 作爲一個例子declaration的行爲 ,描述爲
%right '='
%left '+' '-'
%left '*' '/'
%%
expr : expr '=' expr
| expr '+' expr
| expr '-' expr
| expr '*' expr
| expr '/' expr
| NAME
;
如果輸入爲
a = b = c*d - e - f*g
則等價:
a = ( b = ( ((c*d)-e) - (f*g) ) )
在一般情況下, 給定一個優先級中,一元操作符必須使用這種機制,。 有時一個一元運算符和一個元操作符有相同的符號表徵,但是不同的優先級。 一個例子; 一元操作符和二元操作符負號-;一元操作符-和乘法有相同的等級而二元操作符負號較低。 關鍵字% prec改變特定的語法規則優先級別 。 % prec在 的語法規則(grammar)rules的體部分之後,action和分號之前直接出現,並被一個token name或字面量緊隨其後。 它導致語法規則優先級成爲下列的token name 或字面量。 例如, 一元負號和乘法規則相同的優先級類似
%left '+' '-'
%left '*' '/'
%%
expr : expr '+' expr
| expr '-' expr
| expr '*' expr
| expr '/' expr
| '-' expr %prec '*'
| NAME
;
一個token不一定需要聲明%left, %right, and %nonassoc,但可能,也會被%token聲明
優先級和結合性被YACC用於解決解析衝突;是消除二義性規則的提升。正式的,規則工作如下:
1 tokens和字面量具有優先級和結合性被記錄
2 優先級和結合性和每條語法規則關聯時,是在規則rule體body的最後的token或字面量。如果使用了%prec構造,將覆蓋默認值。一些語法規則rule可以沒有使用優先級和結合性。
3 當出現歸約歸約或移入歸約衝突時,輸入的符號或者沒有優先級和結合性的語法規則,消除二義性規則將會被使用,並且會報告衝突。
4 如果有移位/歸約衝突,和語 規則和輸入字符有與之相關優先級和結合性 ,那麼衝突解決的action(移入或規約)採用優先級更高的那方。 如果優先級是相同的,那麼使用結合性左結合意味着歸約reduce, 兒右結合意味着移入shift,nonassociating暗示錯誤error。
YACC採用優先級的解決移入歸約和歸約歸約衝突的報告不計數。意味着優先級規範中的問題可能會掩蓋輸入語法中的錯誤。這是一個很好的 節約用的判例,並在使用它們
在基本的'COOKBOOK'中使用簡潔的優先級,除非已經很有經驗。 y.output用於判斷解析器實際的操作是非常有用的。
======================================
7:錯誤處理
錯誤處理是一個非常困難的領域,語義中存在許多問題。 當發現一個錯誤, 例子中,可能需要重新解析樹存儲, 刪除或修改符號表實體,代表性的設置開關避免避免深層輸出。
發生錯誤時停止所有的執行是不可接受的。更有用的繼續掃描輸入 找到更多的語法錯誤。 這就導致一個錯誤後的 解析器“重啓”問題。 一個一般類的算法包括從輸入字符串丟棄一個數量的tokens,並試圖解析器,以便調整解析器以便可以繼續輸入。
允許用戶控制這個過程,Yacc提供 一個簡單的,相當一般功能。 token name “error”是用於錯誤處理預留。 可以 在語法規則使用這個name; 實際上,它表明錯誤的地方是可預期,而且可以恢復。 解析器彈出它的堆棧,直到它進入token的“error”是合法的狀態。 然後它表現爲當前的先行token爲“error”,執行遇到的action。先行token會重置這個token造成的錯誤。 如果沒有 特殊的錯誤規則指定,當檢測到一個錯誤處理中斷 。
爲了防止嵌套的錯誤消息,解析器, 檢測到一個錯誤,之後依然在錯誤的狀態,直到三個 token已經成功地讀取和移入。 如果解析器已經發現錯誤狀態,沒有給出消息,輸入token將被默默刪除。
作爲一個例子,一個規則的形式
stat : error
實際上,意味着語法錯誤解析器會 試圖跳過被視爲錯誤的語句。 更準確地說,今後的解析器將掃描,尋找三個token可能合法地語句,並開始處理第一個; 如果語句的開端不夠充分詳細,這可能造成一箇中間語句錯誤的開始,結束後報告實際沒有錯誤第二個錯誤。
這些特殊錯誤規則可能會使用action。 這些action可能會嘗試重新啓動表,回收表空間等。
上面是非常一般錯誤規則,但控制困難。 有些簡單規則如
stat : error ';'
在這裏,當有一個錯誤,解析器試圖略過語句,會通過跳過下一個';'。 在錯誤之後且在 “;”之前,的所有的token不能被移入而是被丟棄。 當“;”被讀取,這條規則被規約,與之相關的與之相關的任何“清理”被執行。
另一種形式的錯誤規則出現在交互式應用程序中, 它允許在錯誤之後重新加入令人滿意的一行, 一個可能的錯誤規則
input : error '\n' { printf( "Reenter last line: " ); } input
{ $$ = $4; }
這種方法有一個潛在的困難; 必須確認解析器在允許正確的同步錯誤之後能正確執行三個token的輸入(error ‘\n’ input)。 在前兩個tokens(error ‘\n’ )中,如果重入行包含一個error,解析器刪除厭棄的token,
不給任何消息; 這是顯然不能接受的。 由於這個原因,有一個機制 可以強制使解析器相信錯誤已經完全恢復。 該語句是
yyerrok ;
在一個動作重置解析器到正常模式。 最後一個例子,更好的形式
input : error '\n'
{ yyerrok;
printf( "Reenter last line: " ); }
input
{ $$ = $4; }
;
正如上面提到的,在input token中發現error之後立即讀取token,有時是不適當。例如,錯誤恢復action可能需要承擔找到恢復輸入的正確位置。此時,之前的先行token必須被清除。語句
yyclearin ;
在一個行動會有這種效果。 例如,假設操作錯誤後調用一些複雜的用戶提供的再同步例程,試圖在下一個有效語句開始時預先輸入 在這例程之後 ,下一個被yylex返回的token將是一個合法語句的第一個token。舊的非法的touken會被丟棄,錯誤狀態被重置,規則rule可以如:
stat : error
{ resynch();
yyerrok ;
yyclearin ; }
;
無可否認,這些機制公認粗糙的,但是對於解析器從許多錯誤恢復時簡單的、相當有效的; 此外,對用戶在program部分根據需求處理錯誤的行爲 。
===================================
8:Yacc環境量
當用戶輸入一個規範Yacc,輸出 一個文件的C程序,稱爲y.tab.c(基於大多數系統 本地文件系統約定,名稱可能根據安裝 不同而不同)。 Yacc的產生函數是 yyparse; 這是一個返回值爲一個整型的函數。 當它被調用時,它會輪詢的反覆調用yylex,即用戶提供詞法分析器 (見第3節)來獲取輸入輸入tokens。 最終, 要麼檢測到一個錯誤,在這種情況下(可能沒有錯誤恢復 )yyparse返回值1,要麼詞法分析器返回結束符tendmarker token並且解析器接受,這時,yyparse返回值0。
用戶必須提供一定數量的環境量,以便這個解析器嫩能夠獲得一個工作程序 例如, 作爲一個C程序,必須定義一個主程序, 用於yyparse的調用。 此外,一個例程用於發現語法錯誤時調用 yyerror輸出一條消息。
這兩個例程必須在被用戶提供的一種或另一種形式中。 爲了簡單使用Yacc初始化,提供一個庫用於main和yyerror的默認版本。這個庫名稱是依賴於系統的;多數系統可以通過參數 -ly 加載獲得 。簡略的默認程序的源代碼如下所示:
main(){
return( yyparse() );
}
和
# include <stdio.h>
yyerror(s) char *s; {
fprintf( stderr, "%s\n", s );
}
yyerror的參數是一個字符串包含一個錯誤消息, 通常字符串“語法錯誤”。 一般的應用程序 想要做得更好。 通常,當檢測到語法錯誤時,程序應該跟蹤輸入的行號並打印消息出來 。在 錯誤被偵測到時, external外部的整數變量yychar包含先行token數; 這在提供更好的診斷中式一些感興趣的。 從用戶提供的主程序(讀參數等)Yacc庫library在小項目或大項目最初階段中是非常有用的 。
external外部整數變量yydebug正常時設置爲0。 如果它被設置爲非零值,解析器將輸出其行爲actions一個冗長描述,包括討論的被讀取的輸入符號,以及解析器的行動是什麼。 根據操作環境,它可能會設置被調試系統使用的變量。
============================================
9:提示準備規範
本節包含各種各樣的暗示準備效率, 更易變更,清理規範。 這個是特色部分是獨立的。
輸入風格
提供帶有大量的actions的規則但要仍舊保持可讀性的規範文件時非常難的。
下列額風格提示來自Brian Kernighan
a使用大寫字母表徵token name,所有的小寫字母作爲非終結names。這個規則使得有出錯時可以
通過下劃線知道歸咎方。
b把語法規則和行爲放在不同行。 這使得改變一個而不會對另一個造成無意識的改變。
c把規則相同的左邊的合起來。左邊只出現一次,用豎線|作爲規則分隔 。
d分號;僅出現在給定最後一個規則之後,且分號單獨一行。這使得新的規則的增加更簡單。
e規則體rule bodies定位由兩個退格鍵,而動作體action bodies 由三個退格鍵。
附錄A中的例子依據這種風格寫的, 也是本文的例子(空間允許)。 用戶必須自己注意這些體裁上的問題。更核心的是,這樣使得纏在一起的action代碼中的規則更明顯。
左遞歸
Yacc解析器使用的算法激勵叫做‘左遞歸’語法規則:規則的形式
name : name rest_of_rule ;
這些規則在編寫序列和列表的規範時經常出現:
list : item
| list ',' item
;
和
seq : item
| seq item
;
在這些情況下,僅有第一個item將根據第一個規則歸約 ,第二個item或者所有後繼item根據第二條規則歸約。
右遞歸規則如
seq : item
| item seq
;
解析器將會很大,items從右開始讀取,直到歸約。 更嚴重的是, 如果有很長串被讀取,解析器內部堆棧將溢出。 因此,這就是用戶應該使用左遞歸的原因。
值得考慮的是一個序列帶有0個元素是否有意義,如果有,考慮寫一個帶有空規則的序列規範:
seq : /* empty */
| seq item
;
開頭,在讀取第一個item時第一個規則總是會歸約一次,後序每次讀取到一個item第二個規則歸約一次。 允許空序列常常會導致增加通用性。 然而,如果Yacc還沒有讀取足量時被要求讀取空序列而做裁決可能產生衝突!
詞彙聯繫
一些詞彙的決定取決於上下文。 例如,
詞法分析器可能想要正常地刪除空格,但不是在引用的字符串裏面。 或名字可能會進入declarations中的一個符號表,但不是在表達式。
處理這種情況的一種方法是創建一個全局性的標誌用於此法分析器的測試,並在actions的設置。 例如,假設一個程序由0或多個聲明組成,緊隨其後的是0或多個語句。 考慮:
%{
int dflag;
%}
... other declarations ...
%%
prog : decls stats
;
decls : /* empty */
{ dflag = 1; }
| decls declaration
;
stats : /* empty */
{ dflag = 0; }
| stats statement
;
... other rules ...
除了在第一個語句中的第一個token,當標誌dflag是0是讀取statements,當1時讀取declarations。 這個token可以知道declaration部分被解析器讀取的開始和結束。 在許多情況下,這一個token不影響詞法掃描。
這種“後門”的方法可以被精心製作成有害的。 然而,它展現了一些難以理解的,如果不是必要的,不這樣做(if not impossible, to do otherwise)。
保留字
一些編程語言允許用戶使用這樣的詞 “if”,通常保留,如標籤或變量名, 在程序語言中提供這樣的names是合法的。Yacc的框架是相當難的。告訴詞法分析器’if這個關鍵字實際是個變量‘是困難的。用戶可以 是用最後的最後一個小節描述的分段機制製造一個透傳, 但它很困難。
使這些更簡單的多種方式的正在研討中。 在那之前,最好的是保留關鍵字; 也就是說, 禁止用作變量名。 這個涉及到很強的文本因素
=================================
10:高級主題
本節討論的Yacc的一些 高級特性。
模擬誤差和接受行爲
通過使用宏YYACCEPT和YYERROR一個動作,解析可以模擬操作錯誤和接受 。 YYACCEPT原因 yyparse返回值0; YYERROR使解析器 像如果當前輸入符號語法錯誤; yyerror叫做,錯誤恢復。 這些機制 可用於模擬和多個endmarkers或解析器嗎 上下文敏感的語法檢查。
在封閉的規則獲取值。
一個動作涉及的返回值可以通過action引用當前規則的左邊。機制與普通的actions是一樣的 ,一個美元符號$,後跟一個數字,但在 這種情況下,數字可能是0或負的。 考慮
sent : adj noun verb adj noun
{ look at the sentence . . . }
;
adj : THE { $$ = THE; }
| YOUNG { $$ = YOUNG; }
. . .
;
noun : DOG
{ $$ = DOG; }
| CRONE
{ if( $0 == YOUNG ){
printf( "what?\n" );
}
$$ = CRONE;
}
;
. . .
在CRONE這個詞後的行動,是檢查之前的移入的token不是YOUNG.。 顯然,唯一的可能是大量了解noun符號之前的輸入。 以及明顯的非結構化味道。 然而,有時這種機制,將會節省大量的麻煩,尤其是當幾個組合用於被一個regular的結構排除。
支持任意值類型
默認情況下,action和詞法分析器Lex的返回的值是整數。 Yacc還可以支持其他的類型值,包括結構。 此外,Yacc跟蹤類型,並插入適當的union成員的名字,以便解析器結果將進行嚴格的類型檢查。 Yacc值 堆棧(見第4節)被聲明在一個有各種期望出現的類型值得union中。 用戶聲明瞭union,並且union的成員名聯繫各個token和非終結符的值。當值是引用中的$$或$n結構 ,Yacc會自動地插入適當的union名,避免了不必要的轉換。 另外,類型檢查命令如Lint[5]將會更沉默。
有三個機制用於提供類型。
第一,定義union的方式; 這必須來自用戶的其他程序,詞法分析器必須的瞭解union成員的名字。
第二,一個union和tokens和非終結符關聯。
第三,當Yacc不能簡單的決定值的類型時使用描述機制(引用關聯)。
【Yacc語法的聲明部分】
在declarations部分的用戶包含include聲明:
%union {
body of union ...
}
這個聲明Yacc值棧,外部變量 yylval 和yyval的類型,等於這個union類型。 如果Yacc是 使用- d選項調用,union會複製到 y.tab. h文件。 另外,union可以被聲明在頭文件,並且一個typedef使用變量
【yacc的使用C語言聲明部分】
YYSTYPE表示這個union。 因此,頭文件可以爲:
typedef union {
body of union ...
} YYSTYPE;
頭文件必須包含在聲明部分, 使用C語言聲明部分的分塊符% {和% }間。
一旦定義YYSTYPE,union成員名稱必須是 與各種終結符和非終結符相關。結構
< name >
是用來表示一個union成員的名字。 如果後面跟着關鍵字%token,%left,%right%nonassoc, 這個union的名字是與token相關列出。 因此,如
%left <optype> '+' '-'
將會導致的任何引用返回值這兩個token 貼上的union成員的名字optype。 另一個關鍵字, %type,關聯union的成員名和非終結符。 因此,這個會是
%type <nodetype> expr stat
仍有一些情況下,這些機制不夠的。 如果一個規則rule中有一個action,這個action返回的值沒有先驗類型。 同樣,上下文中剩餘的引用值(比如$0 -見前面的小節) Yacc不能用簡單的方法知道類型。 這種情況下,可以在使用<type>在引用中插入union的成員名應用類型,並在緊跟$之後,使用樣例
rule : aaa { $<intval>$ = 3; } bbb
{ fun( $<intval>2, $<other>0 ); }
;
這種情況很少出現,不推薦這個語法。
附錄中C給出了一個示例規範的。 在示例中不觸發本章節,除非他們被使用: %type的使用將打開這些機制。 當它們被使用時,會進行一個相當嚴格的檢查。 例如, $n或$$引用使用沒有定義類型的診斷。 如果不觸發,Yacc值棧是保持int的。
--------翻譯結束
11:致謝---機器翻譯
Yacc很大程度上要歸功於一個最刺激用戶的集合, 那些驅使我超出我的傾向,經常超越 我的能力,在他們沒完沒了的尋找“一個特性”。 他們的刺激性不願意學習如何照我的方法做事 通常導致我的做事方式; 大多數時候, 他們是對的。 b·w·克尼漢,p . j . Plauger s I。 費爾德曼,c . Imagna m . e . Lesk和斯奈德將認識一些 當前版本的Yacc的他們的想法。 c·b·哈雷的貢獻 錯誤恢復算法。 b d·m·裏奇。W。 克尼漢,和m·o·哈里斯幫助翻譯這個文件 英語。 艾爾·霍也爲將值得特別信貸 山穆罕默德和其他好處。
1。 b·w·克尼漢和d·m·裏奇,C編程 語言,新世紀,恩格爾伍德懸崖,新澤西,1978。
2。 a . v .阿霍和s c·約翰遜,LR解析、Comp。調查, 6卷,沒有。 2,頁99 - 124,99年6月。
3所示。 a . v .阿霍s c·約翰遜和j . d . Ullman”確定的 模棱兩可的語法解析,“通訊Assoc。。 Comp。馬赫。 18卷,沒有。 8日,第452 - 441頁,441年8月。
4所示。 a . v .霍和j . d . Ullman編譯器設計的原則, addison - wesley、閱讀質量。 ,1977年。
5。 美國C·約翰遜,線頭,C程序檢查器,“Comp。科學。 技術。 衆議員65號,1978。]。 更新版本TM 78-1273-3
6。 s . c·約翰遜”便攜式編譯器:理論與實踐》 Proc,5日美國電腦。 在編程語言的原則, 第104 - 97頁,97年1月。
7所示。 b·w·克尼漢和l . l .櫻桃”排版系統 數學,“通訊Assoc。。 廣告樣稿,馬赫。 18卷,第157 - 151頁, 新澤西州貝爾實驗室,默裏希爾1975年3月 。]。
8。 m . e . Lesk“Lex -一個詞法分析器生成器”Comp。科學。 貝爾實驗室技術。衆議員39號,新澤西州默裏希爾 1975年10月)。
附錄A:一個簡單的例子
這個例子給出了完整的Yacc規範 小型臺式計算器; 臺式計算器有26個寄存器, 標記爲“一個”通過“z”,並接受算術表達式 由運算符+、-、*、/、%(mod操作符),&(逐位 和)|(按位或)和任務。 如果一個表達式 頂層是一個賦值,這個值是不打印; 否則 它是。 在C語言中,一個整數始於0(零) 八進制; 否則,它被認爲是小數。
作爲一個Yacc規範的例子,臺式計算器 一個合理的工作展示的判例和歧義 使用,證明簡單的錯誤恢復。 主要的 簡單化是詞法分析階段 比對於大多數應用程序簡單,輸出 立即,逐行。 注意十進制、八進制的方式 讀取整數的語法規則; 這個工作可能是 更好的通過詞法分析器。
%{
# include <stdio.h>
# include <ctype.h>
int regs[26];
int base;
%}
%start list
%token DIGIT LETTER
%left '|'
%left '&'
%left '+' '-'
%left '*' '/' '%'
%left UMINUS /* supplies precedence for unary minus */
%% /* beginning of rules section */
list : /* empty */
| list stat '\n'
| list error '\n'
{ yyerrok; }
;
stat : expr
{ printf( "%d\n", $1 ); }
| LETTER '=' expr
{ regs[$1] = $3; }
;
expr : '(' expr ')'
{ $$ = $2; }
| expr '+' expr
{ $$ = $1 + $3; }
| expr '-' expr
{ $$ = $1 - $3; }
| expr '*' expr
{ $$ = $1 * $3; }
| expr '/' expr
{ $$ = $1 / $3; }
| expr '%' expr
{ $$ = $1 % $3; }
| expr '&' expr
{ $$ = $1 & $3; }
| expr '|' expr
{ $$ = $1 | $3; }
| '-' expr %prec UMINUS
{ $$ = - $2; }
| LETTER
{ $$ = regs[$1]; }
| number
;
number : DIGIT
{ $$ = $1; base = ($1==0) ? 8 : 10; }
| number DIGIT
{ $$ = base * $1 + $2; }
;
%% /* start of programs */
yylex() { /* lexical analysis routine */
/* returns LETTER for a lower case letter, yylval = 0 through 25 */
/* return DIGIT for a digit, yylval = 0 through 9 */
/* all other characters are returned immediately */
int c;
while( (c=getchar()) == ' ' ) {/* skip blanks */ }
/* c is now nonblank */
if( islower( c ) ) {
yylval = c - 'a';
return ( LETTER );
}
if( isdigit( c ) ) {
yylval = c - '0';
return( DIGIT );
}
return( c );
}
附錄B:Yacc輸入語法
這個附錄Yacc輸入的描述語法, Yacc規範。 不考慮上下文依賴性等。 具有諷刺意味的是,Yacc輸入規範語言 最自然指定爲LR(1)文法; 棘手的部分 當一個標識符在一個規則,之後 一個動作。 如果該標識符後跟一個冒號,它是 開始下一條規則; 否則它的延續 當前的規則,這恰好嵌入了一個動作 它。 實現,看到後向前看的詞法分析器 一個標識符,並決定是否下一個記號(跳過 空格、換行、註釋等)是一個冒號。 如果是這樣,它返回 令牌C_IDENTIFIER。 否則,它將返回標識符。 文字(引用字符串)也作爲標識符返回,但是 從不C_IDENTIFIERs的一部分。
/* grammar for the input to Yacc */
/* basic entities */
%token IDENTIFIER /* includes identifiers and literals */
%token C_IDENTIFIER /* identifier (but not literal) followed by colon */
%token NUMBER /* [0-9]+ */
/* reserved words: %type => TYPE, %left => LEFT, etc. */
%token LEFT RIGHT NONASSOC TOKEN PREC TYPE START UNION
%token MARK /* the %% mark */
%token LCURL /* the %{ mark */
%token RCURL /* the %} mark */
/* ascii character literals stand for themselves */
%start spec
%%
spec : defs MARK rules tail
;
tail : MARK { In this action, eat up the rest of the file }
| /* empty: the second MARK is optional */
;
defs : /* empty */
| defs def
;
def : START IDENTIFIER
| UNION { Copy union definition to output }
| LCURL { Copy C code to output file } RCURL
| ndefs rword tag nlist
;
rword : TOKEN
| LEFT
| RIGHT
| NONASSOC
| TYPE
;
tag : /* empty: union tag is optional */
| '<' IDENTIFIER '>'
;
nlist : nmno
| nlist nmno
| nlist ',' nmno
;
nmno : IDENTIFIER /* NOTE: literal illegal with %type */
| IDENTIFIER NUMBER /* NOTE: illegal with %type */
;
/* rules section */
rules : C_IDENTIFIER rbody prec
| rules rule
;
rule : C_IDENTIFIER rbody prec
| '|' rbody prec
;
rbody : /* empty */
| rbody IDENTIFIER
| rbody act
;
act : '{' { Copy action, translate $$, etc. } '}'
;
prec : /* empty */
| PREC IDENTIFIER
| PREC IDENTIFIER act
| prec ';'
;
附錄C:一個先進的例子
這個附錄給出了一個使用一些語法 10節討論的高級特性。 桌上的計算器 附錄A中的示例修改提供臺式計算器 浮點區間算術。 計算器 理解浮點常量,算術運算 +、-、*、/、一元-,=(賦值),26日浮動 點變量”、“一個”通過“z”。 此外,它也能理解 間隔,寫
( x , y )
其中x是小於或等於y。有26個區間值 變量的一個“通過”“Z”,也可以使用。 使用 是類似於附錄A; 作業沒有返回值, 和打印,同時打印(浮動或表達 間隔)的價值。
這個例子中探索一些有趣的特性 Yacc和c .間隔由結構組成 左和右端點值,存儲爲的兩倍。 這 結構類型名稱,間隔,通過使用類型定義。 的 Yacc值棧也可以包含浮點標量、和 整數(用於索引數組變量 值)。 注意,整個戰略強烈依賴 能夠分配結構和工會在c。事實上,很多 調用函數返回結構的操作。
同樣值得注意的是使用YYERROR處理錯誤 條件:包含0部門由一個間隔,間隔 提出了錯誤的訂單。 事實上,錯誤恢復 Yacc機制用於扔掉剩下的冒犯 null
除了棧上的值類型的混合 語法還演示了一個有趣的使用語法 跟蹤的類型(如標量或間隔)的中間體 表達式。 注意,可以自動晉升爲一個標量 一個區間,如果上下文要求一個區間值。 這 導致大量的語法運行時的衝突 Yacc:18移位/歸約和26減少/減少。 這個問題 可以看到通過查看兩個輸入行:
2.5 + ( 3.5 - 4. )
和
2.5 + ( 3.5 , 4. )
請注意,2.5是被用在一個區間值表達式 在第二個例子中,但這一事實,直到不知道 “,”閱讀; 此時,2.5完成,解析器不能 回去改變主意。 更一般的,它可能是
需要向前看任意數量的令牌來決定 是否一個標量轉換爲一個區間。 這個問題是 逃避有兩個規則爲每個二進制區間值算子: 當左操作數是一個標量,和一個在左邊 操作數是一個區間。 在第二種情況下,正確的操作數 必須是一個區間,所以自動轉換將被應用。 儘管逃稅,仍有許多情況 轉換可以適用與否,導致上面的衝突。 他們是通過清單產生的規則來解決 標量首先在規範文件; 通過這種方式,衝突 將解決的方向保持標量值嗎 表達式的標量值,直到他們被迫成爲間隔。
這種方式處理多種類型是非常有益的,但是 不是很一般。 如果有多種表達式類型, 而不是兩個,所需的規則數量會增加 明顯,衝突更加顯著。 因此, 雖然這個例子很有啓發性,它是更好地實踐 正常編程語言環境類型 信息的價值,而不是語法的一部分。
最後,對詞法分析一個詞。 唯一的 不尋常的特性是浮點常量的治療。 C庫例程atof用來做實際的轉換 從字符串到雙精度值。 如果詞彙 分析儀檢測到一個錯誤,它會返回一個令牌 是非法的在語法、引發的語法錯誤 解析器,和那裏的錯誤恢復。
%{
# include <stdio.h>
# include <ctype.h>
typedef struct interval {
double lo, hi;
} INTERVAL;
INTERVAL vmul(), vdiv();
double atof();
double dreg[ 26 ];
INTERVAL vreg[ 26 ];
%}
%start lines
%union {
int ival;
double dval;
INTERVAL vval;
}
%token <ival> DREG VREG /* indices into dreg, vreg arrays */
%token <dval> CONST /* floating point constant */
%type <dval> dexp /* expression */
%type <vval> vexp /* interval expression */
/* precedence information about the operators */
%left '+' '-'
%left '*' '/'
%left UMINUS /* precedence for unary minus */
%%
lines : /* empty */
| lines line
;
line : dexp '\n'
{ printf( "%15.8f\n", $1 ); }
| vexp '\n'
{ printf( "(%15.8f , %15.8f )\n", $1.lo, $1.hi ); }
| DREG '=' dexp '\n'
{ dreg[$1] = $3; }
| VREG '=' vexp '\n'
{ vreg[$1] = $3; }
| error '\n'
{ yyerrok; }
;
dexp : CONST
| DREG
{ $$ = dreg[$1]; }
| dexp '+' dexp
{ $$ = $1 + $3; }
| dexp '-' dexp
{ $$ = $1 - $3; }
| dexp '*' dexp
{ $$ = $1 * $3; }
| dexp '/' dexp
{ $$ = $1 / $3; }
| '-' dexp %prec UMINUS
{ $$ = - $2; }
| '(' dexp ')'
{ $$ = $2; }
;
vexp : dexp
{ $$.hi = $$.lo = $1; }
| '(' dexp ',' dexp ')'
{
$$.lo = $2;
$$.hi = $4;
if( $$.lo > $$.hi ){
printf( "interval out of order\n" );
YYERROR;
}
}
| VREG
{ $$ = vreg[$1]; }
| vexp '+' vexp
{ $$.hi = $1.hi + $3.hi;
$$.lo = $1.lo + $3.lo; }
| dexp '+' vexp
{ $$.hi = $1 + $3.hi;
$$.lo = $1 + $3.lo; }
| vexp '-' vexp
{ $$.hi = $1.hi - $3.lo;
$$.lo = $1.lo - $3.hi; }
| dexp '-' vexp
{ $$.hi = $1 - $3.lo;
$$.lo = $1 - $3.hi; }
| vexp '*' vexp
{ $$ = vmul( $1.lo, $1.hi, $3 ); }
| dexp '*' vexp
{ $$ = vmul( $1, $1, $3 ); }
| vexp '/' vexp
{ if( dcheck( $3 ) ) YYERROR;
$$ = vdiv( $1.lo, $1.hi, $3 ); }
| dexp '/' vexp
{ if( dcheck( $3 ) ) YYERROR;
$$ = vdiv( $1, $1, $3 ); }
| '-' vexp %prec UMINUS
{ $$.hi = -$2.lo; $$.lo = -$2.hi; }
| '(' vexp ')'
{ $$ = $2; }
;
%%
# define BSZ 50 /* buffer size for floating point numbers */
/* lexical analysis */
yylex(){
register c;
while( (c=getchar()) == ' ' ){ /* skip over blanks */ }
if( isupper( c ) ){
yylval.ival = c - 'A';
return( VREG );
}
if( islower( c ) ){
yylval.ival = c - 'a';
return( DREG );
}
if( isdigit( c ) || c=='.' ){
/* gobble up digits, points, exponents */
char buf[BSZ+1], *cp = buf;
int dot = 0, exp = 0;
for( ; (cp-buf)<BSZ ; ++cp,c=getchar() ){
*cp = c;
if( isdigit( c ) ) continue;
if( c == '.' ){
if( dot++ || exp ) return( '.' ); /* will cause syntax error */
continue;
}
if( c == 'e' ){
if( exp++ ) return( 'e' ); /* will cause syntax error */
continue;
}
/* end of number */
break;
}
*cp = '\0';
if( (cp-buf) >= BSZ ) printf( "constant too long: truncated\n" );
else ungetc( c, stdin ); /* push back last char read */
yylval.dval = atof( buf );
return( CONST );
}
return( c );
}
INTERVAL hilo( a, b, c, d ) double a, b, c, d; {
/* returns the smallest interval containing a, b, c, and d */
/* used by *, / routines */
INTERVAL v;
if( a>b ) { v.hi = a; v.lo = b; }
else { v.hi = b; v.lo = a; }
if( c>d ) {
if( c>v.hi ) v.hi = c;
if( d<v.lo ) v.lo = d;
}
else {
if( d>v.hi ) v.hi = d;
if( c<v.lo ) v.lo = c;
}
return( v );
}
INTERVAL vmul( a, b, v ) double a, b; INTERVAL v; {
return( hilo( a*v.hi, a*v.lo, b*v.hi, b*v.lo ) );
}
dcheck( v ) INTERVAL v; {
if( v.hi >= 0. && v.lo <= 0. ){
printf( "divisor interval contains 0.\n" );
return( 1 );
}
return( 0 );
}
INTERVAL vdiv( a, b, v ) double a, b; INTERVAL v; {
return( hilo( a/v.hi, a/v.lo, b/v.hi, b/v.lo ) );
}
附錄D:舊功能支持但不鼓勵
這個附錄提到同義詞和功能支持 由於歷史的連續性,但由於種種原因, 不鼓勵。
1。 文字也可以使用雙引號“”。
2。 文字可能不止一個字符長。 如果所有的 字符是字母、數字或_,類型的數量 定義了文字,就像文字沒有 引號。 否則,很難找到 這類文字值。
多字的使用文字可能會誤導 那些不熟悉Yacc,因爲它表明,Yacc 做一份工作必須做的詞彙 分析儀。
3所示。 大多數地方%是合法的,可以使用反斜槓“\”。 特別是,\ \ % %是一樣的,\離開了一樣 %,等等。
4所示。 有許多其他同義詞:
%< is the same as %left
%> is the same as %right
%binary and %2 are the same as %nonassoc
%0 and %term are the same as %token
%= is the same as %prec
5。 行動也可能形式
={ . . . }
花括號可以刪除,如果是一個單一的動作 C語句。
6。 C代碼% {和% }之間曾經是允許的 規則的部分,以及在聲明部分