1

There are many examples online how to implement a calculator with Boost Spirit. There are also answered questions on how to add an exponentiation operator to it, for example this one.

But, when it comes to a combination of exponentiation operator AND unary negation operator, these examples fail.

Consider the following expressions:

-4^2

-(12/3)^2

What do you think the answers should be? Any sane mathematician will answer -16. Because exponentiation operation has a precedence over unary negation operation.

But the calculators implementing grammatical rules from the examples shared on the web will answer 16.

But, even more remarkable, try to enter these formula in Microsoft Excel. What you will get is 16! (exclamation mark, not a factorial). There is even a note in Wikipedia about this Microsoft's "exception" of the rule.

But to us, developers building engineering applications, it is unacceptable of course. It has to be -16, or planes will start falling apart. So, the question is how to tweak the example grammar rules to follow it.

Here is our version of the calculator using Boost Spirit "Classic":

     definition(calculator const & self ) {
        expression
           =  factor
           >> *( ('+' >> factor)[self.add_op]
               | ('-' >> factor)[self.sub_op]
               )
           ;

        factor
           =  powerterm
           >> *( ('*' >> powerterm)[self.mult_op]
               | ('/' >> powerterm)[self.div_op]
               )
           ;

           powerterm
           =  term
           >> *( ('^' >> powerterm)[self.exp_op]
               | ("**" >> powerterm)[self.exp_op]
               )
           ;

        term
           =  boost::spirit::classic::real_p[self.real_op]
           |  '(' >> expression >> ')'
           |  ('-' >> term)[self.neg_op]
           |  ('+' >> term)
           ;
     }

The output it generates, while matches Excel, is unacceptable:

info = parse("-4^2", calc, scspirit::space_p); 
// generates PUSH(-4);PUSH(2);EXPONENTIATE;  which is 16

info = parse("-(12/3)^2", calc, scspirit::space_p); 
// generates PUSH(12);PUSH(3);DIVIDE;NEGATE;PUSH(2);EXPONENTIATE; which is 16
Community
  • 1
  • 1
Vladimir Shutow
  • 1,028
  • 1
  • 14
  • 22

1 Answers1

1

Couple things must be done to the original code.

First, when expressing "term", the negative (and positive) operation should apply not to "term", but to "factor".

Second, instead of using boost::spirit::classic::real_p, use boost::spirit::classic::ureal_p, the unsigned variant.

        term
           =  boost::spirit::classic::ureal_p[self.real_op]
           |  '(' >> expression >> ')'
           |  ('-' >> factor)[self.neg_op]
           |  ('+' >> factor)
           ;

Now, the output is proper:

info = parse("-4^2", calc, scspirit::space_p); 
// generates PUSH(4);PUSH(2);EXPONENTIATE;NEGATE;  which is -16

info = parse("-(12/3)^2", calc, scspirit::space_p); 
// generates PUSH(12);PUSH(3);DIVIDE;PUSH(2);EXPONENTIATE;NEGATE; which is -16

Sanity is restored.

Here is a formal grammar:

expression  ::= expression [{ ('+'|'-') factor}];
factor  ::= powerterm [{ ('*'|'/') factor}];
powerterm   ::= powerterm [{('^'|'**') powerterm}];
term    ::= number | '(' expression ')' | '-' factor | '+' factor;

where 'number' is a positive number

Of course, Excel is still a problem, and since it is widely used in the financial sector, our economy is a mess.

sehe
  • 374,641
  • 47
  • 450
  • 633
Vladimir Shutow
  • 1,028
  • 1
  • 14
  • 22
  • I would very much vouch _against_ "restoring" sanity, by using deprecated parts of a library (that have been **[deprecated since August 14th, 2008](http://www.boost.org/users/history/version_1_36_0.html)**). – sehe Oct 02 '14 at 21:14
  • sehe: this is true, but the problem here is not in the boost::spirit framework but in the flawed grammatical rules. It can be applied to any parser. – Vladimir Shutow Oct 02 '14 at 21:50
  • Okay. This looks more like a rant or an open letter, but, okay. Removing downvote. – sehe Oct 02 '14 at 21:54