原题:
Program 4.4:
本题有两点要注意,题目要求写出递归下降分析器,而给出的文法显然是有左递归的,因此要消除左递归;
另外就是本题的Yacc程序使用了优先级策略,在写出需要处理的文法时,我们要先通过引入额外的非终结符的方式解决优先级的问题,避免歧义。
消除直接左递归有标准的解决方案:
之后就是ll文法的老套路,找first集合、follow集合,找select集合写出文法分析表。
找select集合的方法:
相关知识已经足够,接下来就是一步一步写了。
原yacc程序形式化,并去除左递归以及添加优先级处理之后的语法:
1.prog->stm
2.stm->ID ASSIGN exp stm'
3.stm->PRINT LPAREN exps RPAREN stm'
4.stm'->semi_stm stm'
5.stm'->
6.semi_stm->SEMICOLON stm
7.exps->exp exps'
8.exps'->COMMA exp exps'
9.exps'->
10.exp->term exp'
11.exp->stm COMMA exp
12.exp'->PLUS term exp'
13.exp'->MINUS term exp'
14.exp'->
15.term->factor term'
16.term'->TIMES factor term'
17.term'->DIV factor term'
18.term'->
19.factor->INT
20.factor->ID
21.factor->LPAREN exp RPAREN
nullable first follow
prog n PRINT ID \
stm n PRINT ID SEMICOLON COMMA
stm' y SEMICOLON SEMICOLON COMMA
semi_stm n SEMICOLON SEMICOLON
exps n LPAREN PRINT ID INT RPAREN
exps' y COMMA RPAREN
exp n LPAREN PRINT ID INT SEMICOLON COMMA RPAREN
exp' y PLUS MINUS SEMICOLON COMMA RPAREN
term n INT ID LPAREN SEMICOLON COMMA RPAREN PLUS MINUS
term' y TIMES DIV SEMICOLON COMMA RPAREN PLUS MINUS
factor n INT ID LPAREN SEMICOLON COMMA RPAREN PLUS MINUS TIMES DIV
ll(1)分析表:
最后给出我的代码:
enum token {
ID,
INT,
ASSIGN,
SEMICOLON,
PRINT,
LPAREN,
RPAREN,
COMMA,
PLUS,
MINUS,
TIMES,
DIV
};
union tokenval {
string id;
int num;
};
enum token tok;
union tokenval tokval;
typedef struct table *Table_;
Table_ {
string id;
int value;
Table_ tail
};
Table_ Table(string id, int value, struct table *tail);
Table_ table = NULL;
void advance() { tok = getToken(); }
void eat(enum token t) {
if (tok == t)
advance();
else
error();
}
int lookup(Table_ table, string id) {
assert(table != NULL);
if (id == table.id)
return table.value;
else
return lookup(table.tail, id);
}
void update(Table_ *tabptr, string id, int value) {
*tabptr = Table(id, value, *tabptr);
}
// Assum that the tok's initial value is the first input token. If not, in
// Prog(), we can set it.
int Prog_follow[] = {};
int Prog() {
switch (tok) {
case ID:
case PRINT: {
return Stm();
}
default: {
printf("Error: expect ID or PRINT!\n");
return 0;
}
}
}
int Stm_follow[] = {SEMICOLON, COMMA};
int Stm() {
switch (tok) {
case ID: { // tok is ID
string id = tokval.id;
if (lookahead() == ASSIGN) {
advance(); // tok is ASSIGN
advance(); // tok is exp's first token
update(&table, id, Exp()); // update; semantic action
} else {
printf("Error: expect ASSIGN\n");
}
return StmPrime();
}
case PRINT: { // tok is PRINT
string id = tokval.id;
if (lookahead() == LPAREN) {
advance(); // tok is LPAREN
advance(); // tok is exps's first token
Exps(); // do exps
printf("\n"); // semantic action
} else {
printf("expected ASSIGN");
}
eatOrSkipTo(RPAREN, Stm_follow); // eat RPAREN
return StmPrime();
}
default: {
printf("Error: expect ID or PRINT!\n");
skipto(Stm_follow);
return 0;
}
}
}
int StmPrime_follow[] = {SEMICOLON, COMMA};
int StmPrime() {
switch (tok) {
case SEMICOLON: { // tok is SEMICOLON
SemiStm(); // do semi_stm which will change tok
StmPrime(); // do stm'
return 0;
}
case COMMA:
default: {
skipto(StmPrime_follow);
return 0;
}
}
}
int SemiStm_follow = {SEMICOLON};
int SemiStm() {
switch (tok) {
case SEMICOLON: {
advance();
Stm();
return 0;
}
default: {
printf("Error: expect semicolon!\n");
skipto(SemiStm_follow);
return 0;
}
}
}
int Exps_follow = {RPAREN};
int Exps() {
switch (tok) {
case LPAREN:
case PRINT:
case ID:
case INT: {
printf("%d ", Exp());
ExpsPrime();
}
default: {
printf("Error: expect LPAREN,PRINT,ID or INT!\n");
skipto(Exps_follow);
return 0;
}
}
return 0;
}
int ExpsPrime_follow[] = {RPAREN};
int ExpsPrime() {
switch (tok) {
case COMMA: {
advance(); // tok is exp first token
printf("%d ", Exp());
ExpsPrime();
}
default: {
skipto(ExpsPrime_follow);
return 0;
}
}
return 0;
}
int Exp_follow[] = {SEMICOLON, COMMA, RPAREN};
int Exp() {
switch (tok) {
case ID: {
if (lookahead() == ASSIGN) { // next token is ASSIGN, use stm production
Stm();
advance(); // comma
int return_value =
ExpPrime(Exp()); // pass this value to exp' as a tricky iteration
return return_value; // semantic action
} else {
return ExpPrime(
Term()); // use term production and pass this value to exp'
}
}
case INT: {
return ExpPrime(
Term()); // use term production and pass this value to exp'
}
case PRINT: {
Stm();
advance(); // comma
int return_value = Exp();
ExpPrime(); // do exp'
return return_value; // semantic action
}
case LPAREN: {
return Term();
}
default: {
printf("Error: expect LPAREN,PRINT,ID or INT!\n");
skipto(Exp_follow);
return 0;
}
}
}
int ExpPrime_follow[] = {SEMICOLON, COMMA, RPAREN};
int ExpPrime(int a) {
switch (tok) {
case PLUS: {
advance(); // tok is term first token
return ExpPrime(a + Term());
}
case MINUS: {
advance(); // tok is term first token
return ExpPrime(a - Term());
}
default: {
skipto(ExpPrime_follow);
return a;
}
}
return 0;
}
int Term_follow[] = {SEMICOLON, COMMA, RPAREN, PLUS, MINUS};
int Term() {
switch (tok) {
case ID:
case INT:
case LPAREN: {
return TermPrime(
Factor()); // use factor production and pass this value to term'
}
default: {
printf("Error: expect LPAREN,ID or INT!\n");
skipto(Term_follow);
return 0;
}
}
}
int TermPrime_follow[] = {SEMICOLON, COMMA, RPAREN, PLUS, MINUS};
int TermPrime(int a) {
switch (tok) {
case TIMES: {
advance(); // tok is factor first token
return TermPrime(a * Factor());
}
case DIV: {
advance(); // tok is factor first token
return ExpPrime(a / Factor());
}
default: {
skipto(TermPrime_follow);
return a;
}
}
return 0;
}
int factor_follow[] = {SEMICOLON, COMMA, RPAREN, PLUS, MINUS, TIMES, DIV};
int Factor() {
switch (tok) {
case ID: {
return lookup(table, tokval.id); // semantic action
}
case INT: {
return tokval.num; // semantic action
}
case LPAREN: {
advance(); // tok is exp first token now
int return_value = Exp();
eatOrSkipTo(RPAREN, factor_follow); // eat RPAREN
return return_value;
}
default: {
printf("Error: expect LPAREN,ID or INT!\n");
skipto(Term_follow);
return 0;
}
}
}
总结:这道作业题花费了我非常非常长的时间,原因在于我刚开始没考虑优先级而且在写出文法分析表后,发现有duplicate item,然后就开始纠结,咨询了其他同学后,发现duplicate的item中有空产生式,而空产生式是可以处理的(至少我没看出问题QAQ),接着兴冲冲地开写,结果发现自己优先级还是没处理好……最后又折腾了两个小时才写好。加起来的时间都要有一整天了sigh.