Initiating Short circuit rule in Bison for && and

2019-06-26 07:25发布

问题:

I'm programming a simple calculator in Bison & Flex , using C/C++ (The logic is done in Bison , and the C/C++ part is responsible for the data structures , e.g. STL and more) .

I have the following problem :

In my calculator the dollar sign $ means i++ and ++i (both prefix and postfix) , e.g. :

int y = 3;
-> $y = 4
-> y$ = 4

When the user hits : int_expression1 && int_expression2 , if int_expression1 is evaluated to 0 (i.e. false) , then I don't wan't bison to evaluate int_expression2 !

For example :

int a = 0 ; 
int x = 2 ;

and the user hits : int z = a&&x$ ...

So , the variable a is evaluated to 0 , hence , I don't want to evaluate x , however it still grows by 1 ... here is the code of the bison/c++ :

%union
{
    int int_value;
    double double_value;
    char* string_value;
}

%type <int_value> int_expr
%type <double_value> double_expr
%type <double_value> cmp_expr

int_expr:

    | int_expr '&&' int_expr    { /* And operation between two integers */
                      if ($1 == 0) 
                        $$ = 0;
                      else  // calc
                        $$ = $1 && $3;          
                    }

How can I tell bison to not evaluate the second expression , if the first one was already evaluated to false (i.e. 0) ?

回答1:

Converting extensive commentary into an answer:

How can I tell Bison to not evaluate the second expression if the first one was already evaluated to false?

It's your code that does the evaluation, not Bison; put the 'blame' where it belongs.

You need to detect that you're dealing with an && rule before the RHS is evaluated. The chances are that you need to insert some code after the && and before the second int_expr that suspends evaluation if the first int_expr evaluates to 0. You'll also need to modify all the other evaluation code to check for and obey a 'do not evaluate' flag.

Alternatively, you have the Bison do the parsing and create a program that you execute when the parse is complete, rather than evaluating as you parse. That is a much bigger set of changes.

Are you sure regarding putting some code before the second int_expr ? I can't seem to find a plausible way to do that. It's a nice trick, but I can't find a way to actually tell Bison not to evaluate the second int_expr, without ruining the entire evaluation.

You have to write your code so that it does not evaluate when it is not supposed to evaluate. The Bison syntax is:

| int_expr '&&' {...code 1...} int_expr {...code 2...}

'Code 1' will check on $1 and arrange to stop evaluating (set a global variable or something similar). 'Code 2' will conditionally evaluate $4 (4 because 'code 1' is now $3). All evaluation code must obey the dictates of 'code 1' — it must not evaluate if 'code 1' says 'do not evaluate'. Or you can do what I suggested and aselle suggested; parse and evaluate separately.

I second aselle's suggestion about The UNIX Programming Environment. There's a whole chapter in there about developing a calculator (they call it hoc for higher-order calculator) which is worth reading. Be aware, though, that the book was published in 1984, and pre-dates the C standard by a good margin. There are no prototypes in the C code, and (by modern standards) it takes a few liberties. I do have hoc6 (the last version of hoc they describe; also versions 1-3) in modern C — contact me if you want it (see my profile).

That's the problem: I can't stop evaluating in the middle of the rule, since I cannot use return (I can, but of no use; it causes the program to exit). | intExpr '&&' { if ($1 == 0) {/* turn off a flag */ } } intExpr { /* code */} After I exit $3 the $4 is being evaluated automatically.

You can stop evaluating in the middle of a rule, but you have to code your expression evaluation code block to take the possibility into account. And when I said 'stop evaluating', I meant 'stop doing the calculations', not 'stop the parser in its tracks'. The parsing must continue; your code that calculates values must only evaluate when evaluation is required, not when no evaluation is required. This might be an (ugh!) global flag, or you may have some other mechanism.

It's probably best to convert your parser into a code generator and execute the code after you've parsed it. This sort of complication is why that is a good strategy.

@JonathanLeffler: You're indeed the king ! This should be an answer !!!

Now it is an answer.



回答2:

You almost assuredly want to generate some other representation before evaluating in your calculator. A parse tree or ast are classic methods, but a simple stack machine is also popular. There are many great examples of how to do this, but my favorite is http://www.amazon.com/Unix-Programming-Environment-Prentice-Hall-Software/dp/013937681X That shows how to take a simple direct evaluation tool like you have made in yacc (old bison) and take it all the way to a programming language that is almost as powerful as BASIC. All in very few pages. It's a very old book but well worth the read.

You can also look at SeExpr http://www.disneyanimation.com/technology/seexpr.html which is a simple expression language calculator for scalars and 3 vectors. If you look at https://github.com/wdas/SeExpr/blob/master/src/SeExpr/SeExprNode.cpp on line 313 you will see the && implementation of he eval() function:

void
SeExprAndNode::eval(SeVec3d& result) const
{
    // operands and result must be scalar
    SeVec3d a, b;
    child(0)->eval(a);
    if (!a[0]) {
    result[0] = 0;
    } else { 
    child(1)->eval(b);
    result[0] = (b[0] != 0.0); 
    }
}

That file contains all objects that represent operations in the parse tree. These objects are generated as the code is parsed (these are the actions in yacc). Hope this helps.