为什么我需要重写语法?

时间:2016-06-02 17:06:48

标签: parsing bison yacc

我试图自己研究编译器构建。我正在读一本书,这是其中一项练习(我想强调的是,这是不是家庭作业,我自己这样做。)

  

以下语法表示一个简单的算术表达式   类似LISP的前缀表示法

     

lexp - > 数字 | (op lexp-seq)
   op - > + | * | +
   lexp-seq - > lexp-seq lexp | lexp

     

例如,表达式(*( - 2)3 4)的值为-24。写   Yacc / Bison规范,用于计算和打印的程序   此语法中表达式的值。 (提示:这将需要   重写语法,以及使用传递机制   操作符为 lexp-seq

我已经解决了。解决方案如下。但是我对我的解决方案以及问题本身有疑问。他们在这里:

  1. 我没有在我的解决方案中修改语法,它似乎完美无缺。将Yacc / Bison规范转换为.c文件时没有冲突。那么为什么作者说我需要重写一个语法?

  2. 我的解决方案是使用堆栈作为将操作符传递给 lexp-seq 的机制。有人可以提出一个不同的方法,一个不使用堆栈的方法吗?

  3. 这是我对问题的解决方案(我没有发布堆栈操作的代码,因为假设读者熟悉堆栈的工作方式)

    %{
    #include <stdio.h>
    #include <stdlib.h>
    #include <ctype.h>
    #include <string.h>
    #include "linkedstack.h"
    
    int yylex();
    int yyerror();
    
    node *operatorStack;
    
    %}
    
    %token NUMBER
    
    %%
    command : lexp { printf("%d\n", $1); };
    
    lexp : NUMBER { $$ = $1; }
         | '(' op lexp_seq ')' 
           { 
             int operator;
             operatorStack = pop(operatorStack, &operator); 
             switch(operator) {
               default: 
                 yyerror("Unknown operator");
                 exit(1);
                 break;
    
               case '+':
               case '*':
                 $$ = $3;
                 break;
    
               case '-':
                 $$ = -$3;
                 break;
             }
           }
         ;
    
    op : '+' { operatorStack = push(operatorStack, '+'); } 
       | '-' { operatorStack = push(operatorStack, '-'); } 
       | '*' { operatorStack = push(operatorStack, '*'); }
       ; 
    
    lexp_seq : lexp_seq lexp 
               {
                 switch(operatorStack->data) {
                   default:
                     yyerror("Unrecognized operator");
                     exit(1);
                     break;
                   case '+':
                     $$ = $1 + $2;
                     break;
                   case '-':
                     $$ = $1 - $2;
                     break;
                   case '*':
                     $$ = $1 * $2;
                     break;
                 }
               }
    
             | lexp { $$ = $1; }
             ;
    %%
    
    int main(int argc, char** argv) {
      int retVal;
    
      init(operatorStack);
    
      if (2 == argc && (0 == strcmp("-g", argv[1])))
        yydebug = 1;
    
      retVal = yyparse();
    
      destroy(operatorStack);
    
      return retVal;
    }
    
    int yylex() {
      int c;
    
      /* eliminate blanks*/
      while((c = getchar()) == ' ');
    
      if (isdigit(c)) {
        ungetc(c, stdin);
        scanf("%d", &yylval);
        return (NUMBER);
      }
    
      /* makes the parse stop */
      if (c == '\n') return 0;
    
      return (c);
    }
    
    int yyerror(char * s) {
      fprintf(stderr, "%s\n", s);
      return 0;
    } /* allows for printing of an error message */ 
    

1 个答案:

答案 0 :(得分:2)

如果你重写语法,就不需要在这里使用堆栈。

一种方法是为每个运营商使用不同的非终端:

command : lexp '\n'      { printf("%d\n", $1); }
lexp    : NUMBER
        | '(' op_exp ')' { $$ = $2; }
op_exp  : plus_exp | times_exp | minus_exp
plus_exp: '+' lexp       { $$ = $2; }
        | plus_exp lexp  { $$ = $1 + $2; }
times_exp: '*' lexp      { $$ = $2; }
        | times_exp lexp { $$ = $1 * $2; }
minus_exp: '-' lexp      { $$ = -$2; }
        | minus_exp lexp { $$ = $1 - $2; }

我不知道这是你的书作者的想法。当然还有其他可能的实施方式。

在一个真正类似于lisp的语言中,你需要完全不同的方式,因为lexp中的第一个对象可能是一个高阶值(即一个函数),甚至可能是函数调用的结果,因此您无法将操作编码为语法(并且在解析新参数时也不一定能部分地计算表达式。)