Home > Back-end >  Handling boost spirit expression on string
Handling boost spirit expression on string

Time:12-27

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

CodePudding user response:

        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:

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
  • Related