在经典的 Kernighan & Pike 书籍(UNIX 编程环境)的第 8 章中,将 hoc1 作为使用 yacc 的简单计算器的示例进行介绍。
本书建议改进源代码以涵盖酉增量运算符(++)。
我尝试在 yacc 源代码(包括 lex 函数并且不使用配套的 lex 程序)中实现 ++ 运算符,如下所示:
%{
#define YYSTYPE double /* DATA TYPE OF YACC STACK */
%}
%token NUMBER
%left '+' '-' /* LEFT ASSOCIATIVE, SAME PRECEDENCE */
%left '*' '/' /* LEFT ASSOC., HIGHER PRECEDENCE */
%left UNARYMINUS /* LEFT ASSOC., HIGHER PRECEDENCE */
%right INC
/* GRAMMAR */
%%
list: /* nothing */
| list '\n'
| list expr '\n' { printf("\t%.8g\n", $2); }
expr: NUMBER { $$ = $1; }
| expr INC { $$ = $1 + 1; }
| '-' expr %prec UNARYMINUS { $$ = -$2; }
| expr '+' expr { $$ = $1 + $3; }
| expr '-' expr { $$ = $1 - $3; }
| expr '*' expr { $$ = $1 * $3; }
| expr '/' expr { $$ = $1 / $3; }
| expr '%' expr { $$ = fmod($1, $3); }
| '(' expr ')' { $$ = $2; }
%%
/* GRAMMAR PROCESSING ROUTINES */
#include <stdio.h>
#include <ctype.h>
#include <math.h>
int yylex(void);
void yyerror(char *s);
void warning(char *s, char *t);
char *progname;
int lineno = 1;
int main(int argc, char *argv[]) {
progname = argv[0];
yyparse();
}
int yylex() {
int c;
while((c=getchar()) == ' ' || c == '\t')
;
if(c==EOF) {
return 0;
}
if(c=='.' || isdigit(c)) {
ungetc(c,stdin);
scanf("%lf", &yylval);
return NUMBER;
}
if(c=='+') {
if((c=getchar()) == '+') {
return INC;
} else {
ungetc(c,stdin);
return '+';
}
}
if(c=='\n')
lineno++;
return c;
}
void yyerror(char *s) {
warning(s, (char *)0);
}
void warning(char *s, char *t) {
fprintf(stderr, "%s: %s", progname, s);
if(t) {
fprintf(stderr, " %s", t);
}
fprintf(stderr, " near line %d\n", lineno);
}
首先,我使用 yacc 生成 C 源代码,并且得到了移位/归约冲突,为什么会出现这些移位/归约冲突?
$ make
yacc -d hoc1.y
yacc: 11 shift/reduce conflicts.
移位/归约冲突全部来自不明确的
'%'
运算符,因为您没有为其指定优先级或关联性。如果您将其添加到优先级规则中(与 '*'
和 '/
' 相同的优先级),它们将会消失:
%left '*' '/' '%' /* LEFT ASSOC., HIGHER PRECEDENCE */