編譯原理---遞歸下降分析法


所謂遞歸下降法 (recursive descent method),是指對文法的每一非終結符號,都根據相應產生式各候選式的結構,為其編寫一個子程序 (或函數),用來識別該非終結符號所表示的語法范疇。例如,對於產生式E′→+TE′,可寫出相應的子程序如下:

exprprime( )
{
if (match (PLUS))
{
advance( );
term( );
exprprime( );
}
}

     其中: 函數match()的功能是,以其實參與當前正掃視的符號 (單詞)進行匹配,若成功則回送true,否則回送false;函數advance()是一個讀單詞子程序,其功能是從輸入單詞串中讀取下一個單詞,並將它賦給變量Lookahead;term則是與非終結符號T相對應的子程序。諸如上述這類子程序的全體,便組成了所需的自頂向下的語法分析程序。
     應當指出,由於一個語言的各個語法范疇 (非終結符號)常常是按某種遞歸方式來定義的,此種特點也就決定了這組子程序必然以相互遞歸的方式進行調用,因此,在實現遞歸下降分析法時,應使用支持遞歸調用的語言來編寫程序。所以,通常也將上述方法稱為遞歸子程序法。
    
例42對於如下的文法G[statements]:
statements→expression; statements |ε
expression→term expression′
expression′→+term expression′ |ε
term→factor term′
term′→*factor term′ |ε
factor→numorid | (expression)
通過對其中各非終結符號求出相應的FIRST集和FOLLOW集 (計算FIRST集和FOLLOW集的方法后面再做介紹),可以驗證,此文法為一LL(1)文法,故可寫出遞歸下降語法分析程序如程序41所示(其中,在文件lex.h里,將分號、加號、乘號、左括號、右括號、輸入結束符及運算對象分別命名為SEMI,PLUS,TIMES,LP,RP,EOI及NUMORID,並指定了它們的內部碼;此外,還對外部變量yytext,yyleng及yylineno進行了說明)。

程序 G\[statements\]的遞歸下降語法分析程序
1/* Basic parser, shows the structure but there's no code generation */
2
3#include <stdio.h>
4#include "lex.h"
5
6statements ( )
7{
8/* statements → expression SEMI
9 *| expression SEMI statements
10*/
11
12expression( );
13
14if (match (SEMI))
15advance( );
16else
17fprintf (stderr, "%d: Inserting missing semicolon\n", yylineno);
18
19if (!match (EOI))
20statements ( );/* Do another statement. */
21}
22
23expression( )
24{
25/* expression → term expression′ */
26
27term( );
28exprprime( );
29}
30
31exprprime( )
32{
33/* expression′ → PLUS term expression′
34 *| epsilon
35 */
36
37if (match (PLUS))
38{
39advance ( );
40term( );
41exprprime( );
42}
43}
44
45term( )
46{
47/* term → factor term′ */
48
49factor( );
50termprime( );
51}
52
53termprime( )
54{
55/* term′→TIMES factor term′
56 *| epsilon
57 */
58
59if (match (TIMES))
60{
61advance( );
62factor( );
63termprime( );
64}
65}
66
67factor( )
68{
69/* factor → NUMORID
70 *| LP expression RP
71 */
72
73if (match (NUMORID))
74advance( );
75
76else if (match (LP))
77{
78advance( );
79expression( );
80if (match(RP))
81advance( );
82else
83fprintf (stderr, "%d: Mismatched parenthesis\n", yylineno);
84}
85else
86fprintf (stderr, "%d: Number or identifier expected\n", yylineno);
87}
利用程序41進行語法分析時,我們約定,如果當前的輸入符號不是應出現的符號 (例如,對於第37行,當前的輸入符號不是加號),則用相應的ε產生式進行推導。另外,上述程序有兩個比較明顯的缺點: 一是頻繁的遞歸調用將使工作效率大為降低;二是缺乏較完善的語法檢查和出錯處理。這可通過適當改寫文法和擴充程序的功能來改善。例如,可將原文法改寫為如下的文法G′[statements]:
statements→{expression;}
expression→term{+term}
term→factor{*factor}
factor→numorid | (expression)
此時,對一些子程序的遞歸調用就可用一段代碼的重復執行來代替。於是,對於文法G′[statements],可寫出改進的遞歸下降分析程序如程序42所示。
程序 42改進的遞歸下降語法分析程序
1/* Revised parser */
2
3#include <stdio.h>
4#include "lex.h"
5
6voidfactor(void);
7voidterm(void);
8voidexpression(void);
9
10statements( )
11{
12/* statements → expression SEMI | expression SEMI statements */
13
14while (!match (EOI))
15{
16expression( );
17
18if (match (SEMI))
19advance( );
20else
21fprintf (stderr, "%d: Inserting missing semicolon\n", yylineno);
22}
23}
24
25voidexpression( )
26{
27/* expression → term expression′
28 * expression′ → PLUS term expression′ | epsilon
29 */
30
31if (!legallookahead (NUMORID, LP,0))
32return;
33
34term( );
35while (match (PLUS))
36{
37advance( );
38term( );
39}
40}
41
42voidterm( )
43{
44if (!legallookahead (NUMORID, LP,0))
45return;
46
47factor( );
48while (match (TIMES))
49{
50advance( );
51factor( );
52}
53}
54
55voidfactor( )
56{
57if (!legallookahead (NUMORID, LP,0))
58return;
59
60if (match (NUMORID))
61advance( );
62
63else if (match (LP))
64{
65advance( );
66expression( );
67if (match (RP))
68advance( );
69else
70fprintf (stderr, "%d: Mismatched parenthesis\n", yylineno);
71}
72else
73fprintf (stderr, "%d: Number or identifier expected\n", yylineno);
74}
在程序42的expression( )、term( )及factor( )等三個函數中,都調用了一個名為legallookahead的函數。此函數的實參是相應非終結符FIRST集合中的各個元素,並且用一個0作為最后一個實參,其功能是: 檢查當前的輸入符號是否屬於相應非終結符的FIRST集,若是其中的元素,則繼續進行語法分析;否則,除報錯外,還逐個刪除輸入串中的符號,直到出現屬於該FIRST集中的某個符號為止。
     下面,我們列出函數legallookahead的代碼如程序43所示。
     程序 43函數legallookahead
75#include <stdarg.h>
76
77#defineMAXFIRST16
78#defineSYNCHSEMI
79
80intlegallookahead (firstarg)
81intfirstarg;
82{
83/* Simple error detection and recovery. Arguments are a 0terminated list of
84 * those tokens that can legitimately come next in the input. If the list is
85 * empty, the end of file must come next. Print an error message if
86 * necessary. Error recovery is performed by discarding all input symbols
87 * until one that's in the input list is found
88 *
89 * Return true if there's no error or if we recovered from the error,
90 * false if we can't recover.
91 */
92
93valistargs;
94inttok;
95intlookaheads [MAXFIRST], *p=lookaheads, *current;
96interrorprinted=0;
97intrval=0;
98
99vastart (args, firstarg);
100
101if (!firstarg)
102{
103if(match (EOI))
104rval =1;
105}
106else
107{
108*p++=firstarg;
109while ((tok=vaarg(args, int)) && p<&lookaheads[MAXFIRST])
110*++p=tok;
111
112while (!match (SYNCH))
113{
114for (current = lookaheads; current < p; ++currrent)
115if (match (*current))
116{
117rval = 1;
118goto exit;
119}
120
121if (!errorprinted)
122{
123fprintf (stderr, "Line %d: Syntax error\n", yylineno);
124errorprinted = 1;
125}
126
127advance( );
128}
129}
130
131exit;
132vaend (args)
133return rval;
134}

---恢復內容結束---


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM