Search code examples
c++parsingboostboost-spiritboost-spirit-qi

Handling boost spirit expression on string


I wanted to extend the calc example in the boost spirit documentation to add a relational operator to check whether 2 strings are equal like "abc" = "abc" . Facing some issues while extending the calc to compare 2 strings.

Any help in fixing this would be appreciated..

The code I'm trying to implement:

/*=============================================================================
    Copyright (c) 2001-2011 Joel de Guzman

    Distributed under the Boost Software License, Version 1.0. (See accompanying
    file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
=============================================================================*/
///////////////////////////////////////////////////////////////////////////////
//
//  A Calculator example demonstrating generation of AST. The AST,
//  once created, is traversed, 1) To print its contents and
//  2) To evaluate the result.
//
//  [ JDG April 28, 2008 ]      For BoostCon 2008
//  [ JDG February 18, 2011 ]   Pure attributes. No semantic actions.
//
///////////////////////////////////////////////////////////////////////////////

// Spirit v2.5 allows you to suppress automatic generation
// of predefined terminals to speed up complation. With
// BOOST_SPIRIT_NO_PREDEFINED_TERMINALS defined, you are
// responsible in creating instances of the terminals that
// you need (e.g. see qi::uint_type uint_ below).
#define BOOST_SPIRIT_NO_PREDEFINED_TERMINALS

#if defined(_MSC_VER)
# pragma warning(disable: 4345)
#endif

#include <iostream>
#include <string>
#include <list>

#include <boost/config/warning_disable.hpp>
#include <boost/variant/recursive_variant.hpp>
#include <boost/variant/apply_visitor.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/foreach.hpp>
#include <boost/spirit/include/qi.hpp>

namespace client { namespace ast
{
    ///////////////////////////////////////////////////////////////////////////
    //  The AST
    ///////////////////////////////////////////////////////////////////////////
    struct nil {};
    struct signed_;
    struct program;

    typedef boost::variant<
            nil
          , unsigned int
          , bool
          , boost::recursive_wrapper<signed_>
          , boost::recursive_wrapper<program>
        >
    operand;

    struct signed_
    {
        char sign;
        operand operand_;
    };

    struct operation
    {
        char operator_;
        operand operand_;
    };

    struct program
    {
        operand first;
        std::list<operation> rest;
    };
}}

BOOST_FUSION_ADAPT_STRUCT(
    client::ast::signed_,
    (char, sign)
    (client::ast::operand, operand_)
)

BOOST_FUSION_ADAPT_STRUCT(
    client::ast::operation,
    (char, operator_)
    (client::ast::operand, operand_)
)

BOOST_FUSION_ADAPT_STRUCT(
    client::ast::program,
    (client::ast::operand, first)
    (std::list<client::ast::operation>, rest)
)

namespace client { namespace ast
{
    ///////////////////////////////////////////////////////////////////////////
    //  The AST Printer
    ///////////////////////////////////////////////////////////////////////////
    struct printer
    {
        typedef void result_type;

        void operator()(nil) const {}
        void operator()(unsigned int n) const { std::cout << n; }
        void operator()(const bool &n) const { std::cout << n; }

        void operator()(operation const& x) const
        {
            boost::apply_visitor(*this, x.operand_);
            switch (x.operator_)
            {
                case '+': std::cout << " add"; break;
                case '-': std::cout << " subt"; break;
                case '*': std::cout << " mult"; break;
                case '/': std::cout << " div"; break;
                case '|': std::cout << " or"; break;
                case '&': std::cout << " and"; break;
            }
        }

        void operator()(signed_ const& x) const
        {
            boost::apply_visitor(*this, x.operand_);
            switch (x.sign)
            {
                case '-': std::cout << " neg"; break;
                case '+': std::cout << " pos"; break;
                case '!': std::cout << " not"; break;
            }
        }

        void operator()(program const& x) const
        {
            boost::apply_visitor(*this, x.first);
            BOOST_FOREACH(operation const& oper, x.rest)
            {
                std::cout << ' ';
                (*this)(oper);
            }
        }
    };

    ///////////////////////////////////////////////////////////////////////////
    //  The AST evaluator
    ///////////////////////////////////////////////////////////////////////////
    struct eval
    {
        typedef int result_type;

        int operator()(nil) const { BOOST_ASSERT(0); return 0; }
        int operator()(unsigned int n) const { return n; }
        int operator()(const bool &n) const { return n; }
        int operator()(operation const& x, int lhs) const
        {
            int rhs = boost::apply_visitor(*this, x.operand_);
            switch (x.operator_)
            {
                case '+': return lhs + rhs;
                case '-': return lhs - rhs;
                case '*': return lhs * rhs;
                case '/': return lhs / rhs;
                case '|': return lhs | rhs;
                case '&': return lhs & rhs;
            }
            BOOST_ASSERT(0);
            return 0;
        }

        int operator()(signed_ const& x) const
        {
            int rhs = boost::apply_visitor(*this, x.operand_);
            switch (x.sign)
            {
                case '-': return -rhs;
                case '+': return +rhs;
                case '!': return !rhs;
            }
            BOOST_ASSERT(0);
            return 0;
        }

        int operator()(program const& x) const
        {
            int state = boost::apply_visitor(*this, x.first);
            BOOST_FOREACH(operation const& oper, x.rest)
            {
                state = (*this)(oper, state);
            }
            return state;
        }
    };
}}

namespace client
{
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;

    ///////////////////////////////////////////////////////////////////////////////
    //  The calculator grammar
    ///////////////////////////////////////////////////////////////////////////////
    template <typename Iterator>
    struct calculator : qi::grammar<Iterator, ast::program(), ascii::space_type>
    {
        calculator() : calculator::base_type(expression)
        {
            qi::uint_type uint_;
            qi::char_type char_;
            qi::bool_type bool_;
            qi::_val_type _val;
            qi::_1_type _1;
            qi::_2_type _2;
            qi::lexeme_type lexeme;
            qi::as_string_type as_string;
            // qi::string_type string;
            
            expression =
                comparison
                |
                term
                >> *(   (char_('+') >> term)
                    |   (char_('-') >> term)
                    |    (char_('|') >> term)
                    // |    (string("or") >> term)
                    )
                ;

            term =
                factor
                >> *(   (char_('*') >> factor)
                    |   (char_('/') >> factor)
                    |    (char_('&') >> term)
                    // |    (string("and") >> term)
                    )
                ;

            str = lexeme[+(char_ - '=')] [_val = _1];
            comparison = str >> '=' >> '=' >> str [_val = (as_string(_1) == as_string(_2))];

        
            factor =
                    uint_
                |   bool_
                |   '(' >> expression >> ')'
                |   (char_('-') >> factor)
                |   (char_('+') >> factor)
                |   (char_('!') >> factor)
                
                // |   (string("not") >> factor)
                ;
        }

        qi::rule<Iterator, ast::program(), ascii::space_type> expression;
        qi::rule<Iterator, ast::program(), ascii::space_type> term;
        qi::rule<Iterator, ast::operand(), ascii::space_type> factor;
        qi::rule<Iterator , bool() > comparison;
        qi::rule<Iterator , std::string()> str;
    };
}

///////////////////////////////////////////////////////////////////////////////
//  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;
    typedef client::ast::program ast_program;
    typedef client::ast::printer ast_print;
    typedef client::ast::eval ast_eval;

    std::string str;
    while (std::getline(std::cin, str))
    {
        if (str.empty() || str[0] == 'q' || str[0] == 'Q')
            break;

        calculator calc;        // Our grammar
        ast_program program;    // Our program (AST)
        ast_print print;        // Prints the program
        ast_eval eval;          // Evaluates the program

        std::string::const_iterator iter = str.begin();
        std::string::const_iterator end = str.end();
        boost::spirit::ascii::space_type space;
        bool r = phrase_parse(iter, end, calc, space, program);

        if (r && iter == end)
        {
            std::cout << "-------------------------\n";
            std::cout << "Parsing succeeded\n";
            print(program);
            std::cout << "\nResult: " << eval(program) << std::endl;
            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;
}



Error generated:

In file included from /usr/include/boost/phoenix/core/actor.hpp:20,
                 from /usr/include/boost/phoenix/core.hpp:12,
                 from /usr/include/boost/spirit/include/phoenix_core.hpp:11,
                 from /usr/include/boost/spirit/home/support/make_component.hpp:15,
                 from /usr/include/boost/spirit/home/support/meta_compiler.hpp:20,
                 from /usr/include/boost/spirit/home/qi/meta_compiler.hpp:14,
                 from /usr/include/boost/spirit/home/qi/action/action.hpp:14,
                 from /usr/include/boost/spirit/home/qi/action.hpp:14,
                 from /usr/include/boost/spirit/home/qi.hpp:14,
                 from /usr/include/boost/spirit/include/qi.hpp:16,
                 from integrate3.cpp:38:
/usr/include/boost/phoenix/core/is_nullary.hpp: In instantiation of ‘struct boost::phoenix::result_of::is_nullary<boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::assign, boost::proto::argsns_::list2<boost::phoenix::actor<boost::spirit::attribute<0> >, boost::phoenix::actor<boost::spirit::argument<0> > >, 2>, void>’:
/usr/include/boost/phoenix/core/actor.hpp:119:13:   required from ‘struct boost::phoenix::result_of::actor<boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::assign, boost::proto::argsns_::list2<boost::phoenix::actor<boost::spirit::attribute<0> >, boost::phoenix::actor<boost::spirit::argument<0> > >, 2> >’
/usr/include/boost/phoenix/core/actor.hpp:154:9:   required from ‘struct boost::phoenix::actor<boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::assign, boost::proto::argsns_::list2<boost::phoenix::actor<boost::spirit::attribute<0> >, boost::phoenix::actor<boost::spirit::argument<0> > >, 2> >’
integrate3.cpp:238:48:   required from ‘client::calculator<Iterator>::calculator() [with Iterator = __gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char> >]’
integrate3.cpp:285:20:   required from here
/usr/include/boost/phoenix/core/is_nullary.hpp:115:16: error: base type ‘boost::phoenix::evaluator::impl<const boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::assign, boost::proto::argsns_::list2<boost::phoenix::actor<boost::spirit::attribute<0> >, boost::phoenix::actor<boost::spirit::argument<0> > >, 2>&, boost::phoenix::vector2<mpl_::bool_<true>, boost::phoenix::is_nullary>, boost::proto::envns_::empty_env>::result_type’ {aka ‘const boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::assign, boost::phoenix::actor<boost::spirit::argument<0> > >, 2>&’} fails to be a struct or class type
  115 |         struct is_nullary
      |                ^~~~~~~~~~
In file included from /usr/include/boost/phoenix/core.hpp:12,
                 from /usr/include/boost/spirit/include/phoenix_core.hpp:11,
                 from /usr/include/boost/spirit/home/support/make_component.hpp:15,
                 from /usr/include/boost/spirit/home/support/meta_compiler.hpp:20,
                 from /usr/include/boost/spirit/home/qi/meta_compiler.hpp:14,
                 from /usr/include/boost/spirit/home/qi/action/action.hpp:14,
                 from /usr/include/boost/spirit/home/qi/action.hpp:14,
                 from /usr/include/boost/spirit/home/qi.hpp:14,
                 from /usr/include/boost/spirit/include/qi.hpp:16,
                 from integrate3.cpp:38:
/usr/include/boost/phoenix/core/actor.hpp: In instantiation of ‘struct boost::phoenix::result_of::actor<boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::assign, boost::proto::argsns_::list2<boost::phoenix::actor<boost::spirit::attribute<0> >, boost::phoenix::actor<boost::spirit::argument<0> > >, 2> >’:
/usr/include/boost/phoenix/core/actor.hpp:154:9:   required from ‘struct boost::phoenix::actor<boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::assign, boost::proto::argsns_::list2<boost::phoenix::actor<boost::spirit::attribute<0> >, boost::phoenix::actor<boost::spirit::argument<0> > >, 2> >’
integrate3.cpp:238:48:   required from ‘client::calculator<Iterator>::calculator() [with Iterator = __gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char> >]’
integrate3.cpp:285:20:   required from here
/usr/include/boost/phoenix/core/actor.hpp:119:13: error: ‘value’ is not a member of ‘boost::phoenix::result_of::is_nullary<boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::assign, boost::proto::argsns_::list2<boost::phoenix::actor<boost::spirit::attribute<0> >, boost::phoenix::actor<boost::spirit::argument<0> > >, 2>, void>’
  119 |             type;
      |             ^~~~
In file included from /usr/include/boost/phoenix/core/actor.hpp:20,
                 from /usr/include/boost/phoenix/core.hpp:12,
                 from /usr/include/boost/spirit/include/phoenix_core.hpp:11,
                 from /usr/include/boost/spirit/home/support/make_component.hpp:15,
                 from /usr/include/boost/spirit/home/support/meta_compiler.hpp:20,
                 from /usr/include/boost/spirit/home/qi/meta_compiler.hpp:14,
                 from /usr/include/boost/spirit/home/qi/action/action.hpp:14,
                 from /usr/include/boost/spirit/home/qi/action.hpp:14,
                 from /usr/include/boost/spirit/home/qi.hpp:14,
                 from /usr/include/boost/spirit/include/qi.hpp:16,
                 from integrate3.cpp:38:
/usr/include/boost/phoenix/core/is_nullary.hpp: In instantiation of ‘struct boost::phoenix::result_of::is_nullary<boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::assign, boost::proto::argsns_::list2<boost::phoenix::actor<boost::spirit::attribute<0> >, boost::proto::exprns_::expr<boost::proto::tagns_::tag::equal_to, boost::proto::argsns_::list2<const boost::proto::exprns_::expr<boost::proto::tagns_::tag::function, boost::proto::argsns_::list2<boost::proto::exprns_::expr<boost::proto::tagns_::tag::terminal, boost::proto::argsns_::term<boost::spirit::tag::as_string>, 0>&, const boost::phoenix::actor<boost::spirit::argument<0> >&>, 2>&, const boost::proto::exprns_::expr<boost::proto::tagns_::tag::function, boost::proto::argsns_::list2<boost::proto::exprns_::expr<boost::proto::tagns_::tag::terminal, boost::proto::argsns_::term<boost::spirit::tag::as_string>, 0>&, const boost::phoenix::actor<boost::spirit::argument<1> >&>, 2>&>, 2> >, 2>, void>’:
/usr/include/boost/phoenix/core/actor.hpp:119:13:   required from ‘struct boost::phoenix::result_of::actor<boost::proto::exprns_::basic_expr<boost::proto::tagns_::tag::assign, boost::proto::argsns_::list2<boost::phoenix::actor<boost::spirit::attribute<0> >, integrate3.cpp:218:24:   required from ‘client::calculator<Iterator>::calculator() [with Iterator = __gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char> >]’
integrate3.cpp:285:20:   required from here
/usr/include/boost/spirit/home/qi/detail/assign_to.hpp:153:20: error: no matching function for call to ‘client::ast::program::program(const bool&)’
  153 |             attr = static_cast<Attribute>(val);
      |                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~
integrate3.cpp:70:12: note: candidate: ‘client::ast::program::program()’
   70 |     struct program
      |            ^~~~~~~
integrate3.cpp:70:12: note:   candidate expects 0 arguments, 1 provided
integrate3.cpp:70:12: note: candidate: ‘client::ast::program::program(const client::ast::program&)’
integrate3.cpp:70:12: note:   no known conversion for argument 1 from ‘const bool’ to ‘const client::ast::program&’
integrate3.cpp:70:12: note: candidate: ‘client::ast::program::program(client::ast::program&&)’
integrate3.cpp:70:12: note:   no known conversion for argument 1 from ‘const bool’ to ‘client::ast::program&&’

I just want expressions like "abc" == "xyz" to be evaluated as bool and further try boolean logic on them. ("abc" == "xyz" or "ab" == "ab") .. Pls help


Solution

  • str = lexeme[+(char_ - '=')] [_val = _1];
    

    That's... not even close to your required grammar. Why not e.g.

    str = '"' >> *~char_('"') >> '"';
    

    Where (a) automatic attribute propagation is used and (b) the rule is already lexeme (since it has no skipper).

    You hard code a string comparison grammar like:

    str = lexeme[+(char_ - '=')] [_val = _1];
    comparison = str >> '=' >> '=' >> str [_val = (as_string(_1) == as_string(_2))];
    

    You probably don't want to special-case it like this:

    expression = comparison | ...
    

    Instead, consider a string a factor (like uint) and = a binary expression (like '/').

    Now on the evaluator side you have complicated things. Previously, all values were int. Now, you apparently try to return bools:

    int operator()(const bool &n) const { return n; }
    

    And, worse, you should be supporting strings, but your variant can't even hold them.

    Fixing the variant:

    using operand = boost::variant<
            nil
          , unsigned int
          , bool
          , std::string
          , boost::recursive_wrapper<signed_>
          , boost::recursive_wrapper<program>
        >;
    

    Add printing support:

        void operator()(bool n) const { std::cout << std::boolalpha << n; }
        void operator()(std::string const& s) const { std::cout << std::quoted(s);  }
    

    Evaluation becomes a large task because now you have to provide evaluations for all operands between bool/string/int. I won't get into that today.

    Simplest Direct Answer

    Instead, let's take your question literally (although it doesn't seem all that useful in its own right):

    expression = term >> *(qi::char_("-+|&") >> term);
    term       = factor >> *(qi::char_("*/!") >> factor);
    str        = '"' >> *~qi::char_('"') >> '"';
    comparison = (str >> '=' >> str)[_val = (_1 == _2)];
    factor     = comparison | qi::uint_ | qi::bool_ | qi::char_("-+!") >> factor |
        '(' >> expression >> ')';
    

    Live On Coliru

    #include <boost/fusion/include/adapt_struct.hpp>
    #include <boost/phoenix.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/variant.hpp>
    #include <iomanip>
    #include <list>
    
    namespace client { namespace ast {
        struct signed_;
        struct program;
    
        using operand = boost::variant<unsigned int, bool, //
                                       boost::recursive_wrapper<signed_>,
                                       boost::recursive_wrapper<program>>;
    
        struct signed_ { char sign; operand operand_; };
        struct operation { char operator_; operand operand_; };
        struct program { operand first; std::list<operation> rest; };
    }} // namespace client::ast
    
    BOOST_FUSION_ADAPT_STRUCT(client::ast::signed_, sign, operand_)
    BOOST_FUSION_ADAPT_STRUCT(client::ast::operation, operator_, operand_)
    BOOST_FUSION_ADAPT_STRUCT(client::ast::program, first, rest)
    
    namespace client { namespace ast
    {
        struct printer {
            using result_type = void;
    
            void operator()(auto const& v) const { std::cout << v; }
    
            void operator()(operation const& x) const {
                boost::apply_visitor(*this, x.operand_);
                switch (x.operator_) {
                case '+': std::cout << " add"; break;
                case '-': std::cout << " subt"; break;
                case '*': std::cout << " mult"; break;
                case '/': std::cout << " div"; break;
                case '|': std::cout << " or"; break;
                case '&': std::cout << " and"; break;
                }
            }
    
            void operator()(signed_ const& x) const {
                boost::apply_visitor(*this, x.operand_);
                switch (x.sign) {
                case '-': std::cout << " neg"; break;
                case '+': std::cout << " pos"; break;
                case '!': std::cout << " not"; break;
                }
            }
    
            void operator()(program const& x) const {
                boost::apply_visitor(*this, x.first);
                for (auto& oper : x.rest) {
                    std::cout << ' ';
                    (*this)(oper);
                }
            }
        };
    
        ///////////////////////////////////////////////////////////////////////////
        //  The AST evaluator
        ///////////////////////////////////////////////////////////////////////////
        struct eval {
            using result_type = int;
    
            int operator()(auto const& n) const { return n; }
            int operator()(operation const& x, int lhs) const {
                int rhs = boost::apply_visitor(*this, x.operand_);
                switch (x.operator_) {
                case '+': return lhs + rhs;
                case '-': return lhs - rhs;
                case '*': return lhs * rhs;
                case '/': return lhs / rhs;
                case '|': return lhs | rhs;
                case '&': return lhs & rhs;
                }
                BOOST_ASSERT(0);
                return 0;
            }
    
            int operator()(signed_ const& x) const {
                int rhs = boost::apply_visitor(*this, x.operand_);
                switch (x.sign) {
                case '-': return -rhs;
                case '+': return +rhs;
                case '!': return !rhs;
                }
                BOOST_ASSERT(0);
                return 0;
            }
    
            int operator()(program const& x) const {
                int state = boost::apply_visitor(*this, x.first);
                for (auto& oper : x.rest) {
                    state = (*this)(oper, state);
                }
                return state;
            }
        };
    }} // namespace client::ast
    
    namespace client
    {
        namespace qi = boost::spirit::qi;
    
        template <typename Iterator>
        struct calculator : qi::grammar<Iterator, ast::program(), qi::space_type> {
            calculator() : calculator::base_type(expression) {
                using namespace qi::labels;
    
                expression = term >> *(qi::char_("-+|&") >> term);
                term       = factor >> *(qi::char_("*/!") >> factor);
                str        = '"' >> *~qi::char_('"') >> '"';
                comparison = (str >> '=' >> str)[_val = (_1 == _2)];
                factor     = comparison | qi::uint_ | qi::bool_ | qi::char_("-+!") >> factor |
                    '(' >> expression >> ')';
            }
    
          private:
            qi::rule<Iterator, ast::program(), qi::space_type> expression, term;
            qi::rule<Iterator, ast::operand(), qi::space_type> factor, comparison;
            qi::rule<Iterator, std::string()> str;
        };
    }
    
    int main() {
        using iterator_type = std::string::const_iterator;
        using calculator    = client::calculator<iterator_type>;
        using ast_program   = client::ast::program;
        using ast_print     = client::ast::printer;
        using ast_eval      = client::ast::eval;
    
        calculator const calc;  // Our grammar
        ast_print const  print; // Prints the program
        ast_eval const   eval;  // Evaluates the program
    
        for (std::string const str : {
                 "42",
                 "3+(7*8)",
                 "7*8+3",
                 "7+8*3",
                 "\"abc\" = \"abc\"",
                 "\"abc\" = \"ABC\"",
             }) {
            ast_program program;
    
            auto f = str.begin(), l = str.end();
            bool r = phrase_parse(f, l, calc, client::qi::space, program);
    
            if (r && f == l) {
                std::cout << "\nResult: " << quoted(str) << " -> " << eval(program) << std::endl;
                print(program);
            }
            else
                std::cout << "\nFailed at: " << quoted(std::string (f, l)) << "\n";
        }
    }
    

    Prints

    Result: "42" -> 42
    42
    Result: "3+(7*8)" -> 59
    3 7 8 mult add
    Result: "7*8+3" -> 59
    7 8 mult 3 add
    Result: "7+8*3" -> 31
    7 8 3 mult add
    Result: "\"abc\" = \"abc\"" -> 1
    1
    Result: "\"abc\" = \"ABC\"" -> 0
    0