Search code examples
c++boost-spiritcontext-free-grammarboost-spirit-qiassociativity

Expression grammar with exponentiation operator using Boost Spirit


I would like to add the exponentiation operator to the expression grammar provided in the Boost spirit samples.

The BNF grammar is the following: (see this answer for example: "Unambiguous grammar for exponentiation operation" )

E -> E + T | E - T | T
T -> T * F | T / F | X
X -> X ^ Y | Y
Y -> i | (E)

which I translated to Boost spirit like this:

    template <typename Iterator>
struct calculator : qi::grammar<Iterator, ascii::space_type>
{
    calculator() : calculator::base_type(expression)
    {
        qi::uint_type uint_;

        expression =
        term
        >> *(   ('+' >> term            [&do_add])
             |   ('-' >> term            [&do_subt])
             )
        ;

        term =
        factor
        >> *(   ( '*' >> factor          [&do_mult])
             |  ('x' >> factor          [&do_mult])
             |   ('/' >> factor          [&do_div])
             );

        factor= expo >> *( '^' >> expo [&do_power]);

        expo =
           uint_                           [&do_int]
        |  '(' >> expression >> ')'
        |  ('-' >> expo[&do_neg])
        |  ('+' >> expo)

        ;
    }

    qi::rule<Iterator, ascii::space_type> expression, term, factor, expo;
};

The problem is that the ^ operator in this case is left associative, ie 2 ^ 3 ^ 4 is parsed incorrectly as (2 ^ 3) ^ 4 instead of 2^ (3 ^ 4).

How may I rewrite the grammar so that ^ becomes right associative? Obviously the Kleene star I used in the definition of factor is incorrect. What is the method to translate a grammar to Spirit code ? There seems to be a way to go from the left-factored grammar to the Spirit implementation but I can't see it immediately.

In a more formal way, the Spirit code looks like this (before I tried to add the exponent):

E = T ( +T | -T ) *
T = F ( xF | /F ) *
F = int | ( E ) | +F | -F

and the left-factored grammmar is

E  =  T E'
E' = +T E' | -T E' | epsilon
T  =  F T'
T' = *F T' | /F T' | epsilon
F  = ( E ) | int | +F | -F

Solution

  • I think you can employ right recursion to get what you want:

    factor= expo >> -('^' >> factor [&do_power]);
    

    I'm not sure about the desired order of evaluation; you could want something like

    factor= expo [&do_power] >> -('^' >> factor);
    

    instead.

    Here's a simple test program showing it how it handles 2^(6/2)^4+1:

    Edit See it LIVE on Coliru

    Type an expression...or [q or Q] to quit
    2^(6/2)^4+1
    
    push 2
    push 6
    push 2
    divide
    push 4
    exp
    exp
    push 1
    add
    -------------------------
    Parsing succeeded
    

    Full Code

    #define BOOST_SPIRIT_NO_PREDEFINED_TERMINALS
    #define BOOST_SPIRIT_DEBUG
    
    #include <boost/spirit/include/qi.hpp>
    
    namespace client
    {
        namespace qi = boost::spirit::qi;
        namespace ascii = boost::spirit::ascii;
    
        ///////////////////////////////////////////////////////////////////////////////
        //  Semantic actions
        ////////////////////////////////////////////////////////1///////////////////////
        namespace
        {
            void do_int(int n)  { std::cout << "push " << n << std::endl; }
            void do_add()       { std::cout << "add\n"; }
            void do_subt()      { std::cout << "subtract\n"; }
            void do_mult()      { std::cout << "mult\n"; }
            void do_div()       { std::cout << "divide\n"; }
            void do_power()     { std::cout << "exp\n"; }
            void do_neg()       { std::cout << "negate\n"; }
        }
    
        ///////////////////////////////////////////////////////////////////////////////
        //  Our calculator grammar
        ///////////////////////////////////////////////////////////////////////////////
        template <typename Iterator>
            struct calculator : qi::grammar<Iterator, ascii::space_type>
        {
            calculator() : calculator::base_type(expression)
            {
                qi::uint_type uint_;
    
                expression = term
                    >> *(   ('+' >> term            [&do_add])
                         |  ('-' >> term            [&do_subt])
                         )
                    ;
    
                term = factor
                    >> *(   ( '*' >> factor         [&do_mult])
                         |  ('x' >> factor          [&do_mult])
                         |  ('/' >> factor          [&do_div])
                         );
    
                factor= expo >> -('^' >> factor [&do_power]);
    
                expo = uint_                        [&do_int]
                    |  '(' >> expression >> ')'
                    |  ('-' >> expo[&do_neg])
                    |  ('+' >> expo)
                ;
    
                BOOST_SPIRIT_DEBUG_NODES((expression)(term)(factor)(expo));
            }
          private:
            qi::rule<Iterator, ascii::space_type> expression, term, factor, expo;
        };
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    //  Main program
    ///////////////////////////////////////////////////////////////////////////////
    int
    main()
    {
        std::cout << "/////////////////////////////////////////////////////////\n\n";
        std::cout << "Expression parser...\n\n";
        std::cout << "/////////////////////////////////////////////////////////\n\n";
        std::cout << "Type an expression...or [q or Q] to quit\n\n";
    
        typedef std::string::const_iterator iterator_type;
        typedef client::calculator<iterator_type> calculator;
    
        boost::spirit::ascii::space_type space; // Our skipper
        calculator calc; // Our grammar
    
        std::string str;
        while (std::getline(std::cin, str))
        {
            if (str.empty() || str[0] == 'q' || str[0] == 'Q')
                break;
    
            std::string::const_iterator iter = str.begin();
            std::string::const_iterator end = str.end();
            bool r = phrase_parse(iter, end, calc, space);
    
            if (r && iter == end)
            {
                std::cout << "-------------------------\n";
                std::cout << "Parsing succeeded\n";
                std::cout << "-------------------------\n";
            }
            else
            {
                std::string rest(iter, end);
                std::cout << "-------------------------\n";
                std::cout << "Parsing failed\n";
                std::cout << "stopped at: \" " << rest << "\"\n";
                std::cout << "-------------------------\n";
            }
        }
    
        std::cout << "Bye... :-) \n\n";
        return 0;
    }