bison操作符结合性有bug

时间:2022-06-01 20:59:13
好像bison的操作符结合性不起作用啊,
我按照bison的手册上抄过来的,程序如下(在exp规则中加了一些printf的debug信息):
     /* Reverse polish notation calculator.  */
     
     %{
       #define YYSTYPE double
       #include <math.h>
       #include <ctype.h>
       #include <stdio.h>
       int yylex (void);
       void yyerror (char const *);
     %}
     
     %token NUM
     
     /* Bison declarations.  */
     %token NUM
     %left '-' '+'
     %left '*' '/'
     %left NEG     /* negation--unary minus */
     %right '^'    /* exponentiation */
     
     %% /* The grammar follows.  */
     input:    /* empty */
             | input line
     ;
     
     line:     '\n'
             | exp '\n'  { printf ("\t%.10g\n", $1); }
     ;
     
     exp:      NUM                { $$ = $1;printf("debug 1\n");         }
             | exp '+' exp        { $$ = $1 + $3;printf("debug 2\n");    }
             | exp '-' exp        { $$ = $1 - $3;printf("debug 3\n");    }
             | exp '*' exp        { $$ = $1 * $3;printf("debug 4\n");    }
             | exp '/' exp        { $$ = $1 / $3;printf("debug 5\n");    }
             | '-' exp  %prec NEG { $$ = -$2;printf("debug 6\n");        }
             | exp '^' exp        { $$ = pow ($1, $3);printf("debug 7\n"); }
             | '(' exp ')'        { $$ = $2;printf("debug 8\n");         }
     ;
     %%

     /* The lexical analyzer returns a double floating point
        number on the stack and the token NUM, or the numeric code
        of the character read if not a number.  It skips all blanks
        and tabs, and returns 0 for end-of-input.  */
     
     int
     yylex (void)
     {
       int c;
     
       /* Skip white space.  */
       while ((c = getchar ()) == ' ' || c == '\t')
         ;
       /* Process numbers.  */
       if (c == '.' || isdigit (c))
         {
           ungetc (c, stdin);
           scanf ("%lf", &yylval);
           return NUM;
         }
       /* Return end-of-input.  */
       if (c == EOF)
         return 0;
       /* Return a single char.  */
       return c;
     }

     int
     main (void)
     {
       return yyparse ();
     }

     /* Called by yyparse on error.  */
     void
     yyerror (char const *s)
     {
       fprintf (stderr, "%s\n", s);
     }
运行之后输入(1+2)^(4-3)
然后这是结果:
(1+2)^(4-3)
debug 1
debug 1
debug 2
debug 8
debug 1
debug 1
debug 3
debug 8
debug 7
        3
为什么不是预想的:
(1+2)^(4-3)
debug 1
debug 1
debug 3
debug 8
debug 1
debug 1
debug 2
debug 8
debug 7
        3

(1+2)^(4-3)
debug 1
debug 1
debug 1
debug 1
debug 3
debug 8
debug 2
debug 8
debug 7
        3
?把right改成left之后执行结果也是一样,那么操作符结合性还有什么意义?
Solaris和Windows下的bison都试过了,结果都是一样。
不知如何才能使结果是预想的这样?

25 个解决方案

#1


那是什么东东?

#2


你试试看 1^2^3 在想想是咋回事 ....

#3


1^2^3
debug 1
debug 1
debug 1
debug 7
debug 7
        1
还不是很明白,难道只有都是right的操作才会先执行右面的?
但是c++的编译器执行p()=p1()+p2();(函数p返回一个引用,这样就可以赋值了)的时候,会先打印出p1和p2的执行结果,然后再执行函数p打印p中的printf然后返回引用的

#4


你再在有疑问的几个地方下个断点看看 yyvs 里的内容吧 ...

#5


稍微有点明白了,我再看看……

#6


yyvs保存的是语意值,每次把规则执行结果放到里面,1^2^3的时候yyvs显示是先执行2^3然后执行1^8的,不过还是不清楚如何修改程序才能达到执行(1+2)^(4-3)的时候先执行4-3然后执行1+2最后执行3^1?

#7


这个顺序有关系么 ...
如果你的程序有代码生成的过程, 可以在这个时候做这件事情 ...

#8


确实有代码生成的过程,需要先执行后面的生成先决条件,然后执行前面的。
不过c++在执行p1()+p2()=p3()-p4();的时候执行的顺序是p3()、p4()、'-'、p1()、p2()、'+'、'=',是重载了+-=操作符在里面打印出执行顺序之后得出的结论。
我认为操作符结合性不仅表现为结合性的计算顺序,还要表现为结合性的左右子表达式的执行顺序,bison只实现了部分操作符结合性的特性,如果能和c++编译器那样执行就好了。
现在碰到这种情况,唯一的办法就是先对语句进行预分析,生成先决条件,然后对语句再进行分析了,其中设个标志位就可以。

#9


c/cpp 里表达式求值本来就大部分是从右到左, 偶觉得这暴不自然, 竟然有人会稀饭这种顺序, 狂晕 ...
不需要对语句进行预分析, yacc 直接可以生成你需要的东东.. 一会有空的时候写个 ..

#10


但是执行p1()+p2()和p3()+p4()的时候却是从左到右的,这应该是按照结合性执行的,只有right结合的话才会先执行右面的,并不是大部分是从右到左的。
yacc不是和bison一样的吗?bison是yacc的GNU版?

#11


yacc..
不太明白..

#12


什么跟什么哦, 左结合只是表示 a+b+c+d == ((a+b)+c)+d ,右结合只是表示 a^b^c^d == a^(b^(c^d))), abcd 的计算顺序本来就没有保证, 你想怎么算就怎么算 ...

#13


这是知道的,不过好像c++编译器的结合性的执行顺序也有区别的。

#14


c++有右结合的东东么, 赋值不算 ..

#15


除了赋值,其它好像都是左结合的了。
不过赋值操作可以说明问题吧

#16


对了,好像三元“?:”和逗号“,”有一个也是右结合,忘记是哪个了

#17


C++操作符的优先级&结合性: http://hi.baidu.com/xyh2007/blog/item/b2cd4b60c5dfa145eaf8f8b3.html

#18


bison中的left和right只是用来解决生成语法树的二义性,而左右部分哪个先运算出来是由语法树的遍历方式决定的吧。

#19


从上面和mLee79的讨论来看也的确是这个样子了,所以如果需要后面的子表达式来初始化一些信息的话,只能先对语句进行预分析,生成先决条件,然后对语句再进行分析了,其中设个标志位就可以。
不知道还有没有更好的办法?

#20


偶可木有说过要预处理才能生成结果, 代码生成的时候你想先给右边的表达式求值就先求右边, 想先求左边就先左边, 跟其他东东有啥关系 ...

#21


怎么做到?按照一开始的程序,输入(1+2)^(4-3)的时候如何才能先计算4-3然后计算1+2?从而debug信息中的printf先输出"debug 3"然后是"debug 2"?

#22


bison生成语法树的顺序是没法改变的。bison用的是LALR分析法。(1+2)^(4-3)中1+2会被先规约,也就是:(1+2)^(4-3)中
1.规约exp -> exp + exp 即 (exp)^(4-3)
2.规约exp -> ( exp ) 即 exp ^ ( 4-3 ),这时^是右结合的,所以会先规约右侧
3.规约exp -> exp - exp 即 exp ^ ( exp ), 现作这个规约是因为在遇到')'前,遇到了'+'。
4.规约exp -> ( exp )即 exp ^ exp
5.规约exp -> exp ^ exp 即 exp, 规约完成。
所以执行顺序是无法改变的。但是,如果不是解释执行,而是编译执行,就像C++这样的。bison是可以自定义语意记录的。所以实现起来还是比较简单的。
例如,基本块使用链表保存的。
...
%{
typedef struct block{
 struct block *next;
 ...//存放生成代码的中间结果。
}block_t;
%}

%union{
 block_t *pblock;/*语意记录*/
};
...
type <pblock> exp
%%
...
exp:
...
   | exp '+' exp {//左结合
     $$ = $1;$1->next=$3;...
   }
   | exp '^' exp{//右结合
     $$ = $3;$3->next=$1;...
   }
...

#23


多谢WizardLucien了,不过现在是解释执行的程序,就如上面的算术表达式运算这样,看来改起来比较麻烦,还要想其他的办法,当然也可以改成编译执行的方式,只是改动比较大了。

再次感谢大家的帮助,基本都明白了,受益非浅,准备结贴了……

#24


解释执行的话,可以利用延迟计算来完成,例如可以这样:
%{
...
typedef struct node{
 char op;//存放运算符
 struct node *left; //左子树
 struct node *right; //右子树
 double value; //叶子节点
}node_t;
double calculate( node_t *node );//实际运算的函数
...
%}

%union{
node_t *pnode;
double value;
};
type <pnode> exp
type <value> NUM
...
%% 
input:| input line;
line:     '\n'
 | exp '\n'  { printf("\t%.10g\n", calculate($1)); };

exp: NUM         {
$$ = (node_t*)malloc(node_t);$$->op='\0';$$->value = $1;
}
 | exp '+' exp   { $$ = (node_t*)malloc(node_t);$$->op = '+';$$->left=$1;$$->right=$3; }
 | exp '^' exp   { $$ = (node_t*)malloc(node_t);$$->op = '^';$$->left=$1;$$->right=$3; }
...
%%
double calculate( node_t *node )
{
    double ret = 0.0;
    if(node == NULL)return ret;
    switch( node->op )
    {
    case '\0':ret=node->value;break;
    case '+':ret=calculate(node->left)+calculate(node->right);break;
    case '^':{
double tmpr = calculate(node->right);//这里实现了先算右侧。
double tmpl = calculate(node->left);
ret = pow(tmpl,tmpr);
}break;
    ...
    }
    free( node );
    return ret;
}
...
还有,lz的产生式好像加减法和乘除法优先级相同。

#25


好,我再研究一下

#1


那是什么东东?

#2


你试试看 1^2^3 在想想是咋回事 ....

#3


1^2^3
debug 1
debug 1
debug 1
debug 7
debug 7
        1
还不是很明白,难道只有都是right的操作才会先执行右面的?
但是c++的编译器执行p()=p1()+p2();(函数p返回一个引用,这样就可以赋值了)的时候,会先打印出p1和p2的执行结果,然后再执行函数p打印p中的printf然后返回引用的

#4


你再在有疑问的几个地方下个断点看看 yyvs 里的内容吧 ...

#5


稍微有点明白了,我再看看……

#6


yyvs保存的是语意值,每次把规则执行结果放到里面,1^2^3的时候yyvs显示是先执行2^3然后执行1^8的,不过还是不清楚如何修改程序才能达到执行(1+2)^(4-3)的时候先执行4-3然后执行1+2最后执行3^1?

#7


这个顺序有关系么 ...
如果你的程序有代码生成的过程, 可以在这个时候做这件事情 ...

#8


确实有代码生成的过程,需要先执行后面的生成先决条件,然后执行前面的。
不过c++在执行p1()+p2()=p3()-p4();的时候执行的顺序是p3()、p4()、'-'、p1()、p2()、'+'、'=',是重载了+-=操作符在里面打印出执行顺序之后得出的结论。
我认为操作符结合性不仅表现为结合性的计算顺序,还要表现为结合性的左右子表达式的执行顺序,bison只实现了部分操作符结合性的特性,如果能和c++编译器那样执行就好了。
现在碰到这种情况,唯一的办法就是先对语句进行预分析,生成先决条件,然后对语句再进行分析了,其中设个标志位就可以。

#9


c/cpp 里表达式求值本来就大部分是从右到左, 偶觉得这暴不自然, 竟然有人会稀饭这种顺序, 狂晕 ...
不需要对语句进行预分析, yacc 直接可以生成你需要的东东.. 一会有空的时候写个 ..

#10


但是执行p1()+p2()和p3()+p4()的时候却是从左到右的,这应该是按照结合性执行的,只有right结合的话才会先执行右面的,并不是大部分是从右到左的。
yacc不是和bison一样的吗?bison是yacc的GNU版?

#11


yacc..
不太明白..

#12


什么跟什么哦, 左结合只是表示 a+b+c+d == ((a+b)+c)+d ,右结合只是表示 a^b^c^d == a^(b^(c^d))), abcd 的计算顺序本来就没有保证, 你想怎么算就怎么算 ...

#13


这是知道的,不过好像c++编译器的结合性的执行顺序也有区别的。

#14


c++有右结合的东东么, 赋值不算 ..

#15


除了赋值,其它好像都是左结合的了。
不过赋值操作可以说明问题吧

#16


对了,好像三元“?:”和逗号“,”有一个也是右结合,忘记是哪个了

#17


C++操作符的优先级&结合性: http://hi.baidu.com/xyh2007/blog/item/b2cd4b60c5dfa145eaf8f8b3.html

#18


bison中的left和right只是用来解决生成语法树的二义性,而左右部分哪个先运算出来是由语法树的遍历方式决定的吧。

#19


从上面和mLee79的讨论来看也的确是这个样子了,所以如果需要后面的子表达式来初始化一些信息的话,只能先对语句进行预分析,生成先决条件,然后对语句再进行分析了,其中设个标志位就可以。
不知道还有没有更好的办法?

#20


偶可木有说过要预处理才能生成结果, 代码生成的时候你想先给右边的表达式求值就先求右边, 想先求左边就先左边, 跟其他东东有啥关系 ...

#21


怎么做到?按照一开始的程序,输入(1+2)^(4-3)的时候如何才能先计算4-3然后计算1+2?从而debug信息中的printf先输出"debug 3"然后是"debug 2"?

#22


bison生成语法树的顺序是没法改变的。bison用的是LALR分析法。(1+2)^(4-3)中1+2会被先规约,也就是:(1+2)^(4-3)中
1.规约exp -> exp + exp 即 (exp)^(4-3)
2.规约exp -> ( exp ) 即 exp ^ ( 4-3 ),这时^是右结合的,所以会先规约右侧
3.规约exp -> exp - exp 即 exp ^ ( exp ), 现作这个规约是因为在遇到')'前,遇到了'+'。
4.规约exp -> ( exp )即 exp ^ exp
5.规约exp -> exp ^ exp 即 exp, 规约完成。
所以执行顺序是无法改变的。但是,如果不是解释执行,而是编译执行,就像C++这样的。bison是可以自定义语意记录的。所以实现起来还是比较简单的。
例如,基本块使用链表保存的。
...
%{
typedef struct block{
 struct block *next;
 ...//存放生成代码的中间结果。
}block_t;
%}

%union{
 block_t *pblock;/*语意记录*/
};
...
type <pblock> exp
%%
...
exp:
...
   | exp '+' exp {//左结合
     $$ = $1;$1->next=$3;...
   }
   | exp '^' exp{//右结合
     $$ = $3;$3->next=$1;...
   }
...

#23


多谢WizardLucien了,不过现在是解释执行的程序,就如上面的算术表达式运算这样,看来改起来比较麻烦,还要想其他的办法,当然也可以改成编译执行的方式,只是改动比较大了。

再次感谢大家的帮助,基本都明白了,受益非浅,准备结贴了……

#24


解释执行的话,可以利用延迟计算来完成,例如可以这样:
%{
...
typedef struct node{
 char op;//存放运算符
 struct node *left; //左子树
 struct node *right; //右子树
 double value; //叶子节点
}node_t;
double calculate( node_t *node );//实际运算的函数
...
%}

%union{
node_t *pnode;
double value;
};
type <pnode> exp
type <value> NUM
...
%% 
input:| input line;
line:     '\n'
 | exp '\n'  { printf("\t%.10g\n", calculate($1)); };

exp: NUM         {
$$ = (node_t*)malloc(node_t);$$->op='\0';$$->value = $1;
}
 | exp '+' exp   { $$ = (node_t*)malloc(node_t);$$->op = '+';$$->left=$1;$$->right=$3; }
 | exp '^' exp   { $$ = (node_t*)malloc(node_t);$$->op = '^';$$->left=$1;$$->right=$3; }
...
%%
double calculate( node_t *node )
{
    double ret = 0.0;
    if(node == NULL)return ret;
    switch( node->op )
    {
    case '\0':ret=node->value;break;
    case '+':ret=calculate(node->left)+calculate(node->right);break;
    case '^':{
double tmpr = calculate(node->right);//这里实现了先算右侧。
double tmpl = calculate(node->left);
ret = pow(tmpl,tmpr);
}break;
    ...
    }
    free( node );
    return ret;
}
...
还有,lz的产生式好像加减法和乘除法优先级相同。

#25


好,我再研究一下