I am trying to add a greater than operator > to a ast: the code is 95% identical to the code in the docs.
Two points of interest below
- A block of code where I’m trying to write support for greater than: commented in the code below.
- A single line in the parse for
termwhich fails to compile because I don’t yet understand semantic actions yet: not sure how to bind thelhsoflhs > rhsthrough phoenix and semantic actions.
The solution should be trivial for regular users of Spirit, but I am still learning, and only getting by through examples so far.
Any help would be appreciated. TIA.
Code
#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <boost/spirit/include/classic_symbols.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/regex.hpp> // std::regex not fully implemented in stdc++ yet
#include <string>
#include <map>
#include <utility>
#include <functional>
#include <iostream>
#include <string>
#include <vector>
namespace client
{
namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
struct binary_op;
struct unary_op;
struct nil {};
struct expression_ast
{
typedef
boost::variant<
nil // can't happen!
, double
, std::string
, boost::recursive_wrapper<expression_ast>
, boost::recursive_wrapper<binary_op>
, boost::recursive_wrapper<unary_op>
>
type;
expression_ast()
: m_expr(nil()) {}
template <typename Expr>
expression_ast(Expr const& expr)
: m_expr(expr) {}
expression_ast& operator+=(expression_ast const& rhs);
expression_ast& operator-=(expression_ast const& rhs);
expression_ast& operator*=(expression_ast const& rhs);
expression_ast& operator/=(expression_ast const& rhs);
type m_expr;
};
struct binary_op
{
binary_op(
char op
, expression_ast const& left
, expression_ast const& right)
: m_op(op), m_left(left), m_right(right) {}
char m_op;
expression_ast m_left;
expression_ast m_right;
};
struct unary_op
{
unary_op(
char op
, expression_ast const& subject)
: m_op(op), m_subject(subject) {}
char m_op;
expression_ast m_subject;
};
expression_ast& expression_ast::operator+=(expression_ast const& rhs)
{
m_expr = binary_op('+', m_expr, rhs);
return *this;
}
expression_ast& expression_ast::operator-=(expression_ast const& rhs)
{
m_expr = binary_op('-', m_expr, rhs);
return *this;
}
expression_ast& expression_ast::operator*=(expression_ast const& rhs)
{
m_expr = binary_op('*', m_expr, rhs);
return *this;
}
expression_ast& expression_ast::operator/=(expression_ast const& rhs)
{
m_expr = binary_op('/', m_expr, rhs);
return *this;
}
// We should be using expression_ast::operator-. There's a bug
// in phoenix type deduction mechanism that prevents us from
// doing so. Phoenix will be switching to BOOST_TYPEOF. In the
// meantime, we will use a phoenix::function below:
struct negate_expr
{
template <typename T>
struct result
{
typedef T type;
};
expression_ast operator()(expression_ast const& expr) const
{
return expression_ast(unary_op('-', expr));
}
};
static boost::phoenix::function<negate_expr> neg;
struct ast_print
{
typedef std::string result_type;
std::string operator()(qi::info::nil) const
{
return "";
}
std::string operator()(std::string const& str) const
{
return str;
}
std::string operator()(double d) const
{
std::ostringstream oss;
oss << d;
return oss.str();
}
std::string operator()(expression_ast const& ast) const
{
return boost::apply_visitor(*this, ast.m_expr);
}
std::string operator()(binary_op const& expr) const
{
std::ostringstream oss;
oss << "op:" << expr.m_op << "(";
oss << boost::apply_visitor(*this, expr.m_left.m_expr);
oss << ", ";
oss << boost::apply_visitor(*this, expr.m_right.m_expr);
oss << ')';
return oss.str();
}
std::string operator()(unary_op const& expr) const
{
std::ostringstream oss;
oss << "op:" << expr.m_op << "(";
oss << boost::apply_visitor(*this, expr.m_subject.m_expr);
oss << ')';
return oss.str();
}
};
std::ostream& operator << (std::ostream& stream, const expression_ast& expr)
{
ast_print printer;
stream << printer(expr) << std::endl;
return stream;
}
// CODE ADDED HERE ------------------------------------------------------------
template< char OP >
struct binary_expr
{
template <typename T>
struct result
{
typedef T type;
};
expression_ast operator()(expression_ast const& lhs,expression_ast const& rhs) const
{
return expression_ast(binary_op( OP, lhs, rhs ));
}
};
static boost::phoenix::function<binary_expr<'>'>> gt;
// CODE ADDED END HERE -------------------------------------------------------
template <typename Iterator>
struct ParserGenerator : qi::grammar<Iterator, expression_ast(), ascii::space_type>
{
ParserGenerator() : ParserGenerator::base_type(expression)
{
using qi::_val;
using qi::_1;
using qi::double_;
using qi::iso8859_1::char_;
using qi::iso8859_1::space;
using qi::eol;
using boost::spirit::ascii::string;
comment =
space >> ("//" >> *(char_ - eol) >> eol)
;
expression =
term [_val = _1]
>> *( ('+' >> term [_val += _1])
| ('-' >> term [_val -= _1])
)
;
term =
factor [_val = _1]
>> *( ('*' >> factor [_val *= _1])
| ('/' >> factor [_val /= _1])
// | ('>' >> factor [_val = gt(qi::_val,_1)]) // PROBLEM HERE!
)
;
factor =
symbol [_val = _1]
| double_ [_val = _1]
| '(' >> expression [_val = _1] >> ')'
| ('-' >> factor [_val = neg(_1)])
| ('+' >> factor [_val = _1])
;
symbol %=
(symbol_raw
>> *( string("[") >> +qi::digit >> string("]"))
>> *( string(".") >> symbol ))
;
symbol_raw %=
+(qi::alpha | qi::char_( "_" ))
;
BOOST_SPIRIT_DEBUG_NODE(expression);
BOOST_SPIRIT_DEBUG_NODE(term);
BOOST_SPIRIT_DEBUG_NODE(factor);
BOOST_SPIRIT_DEBUG_NODE(comment);
BOOST_SPIRIT_DEBUG_NODE(symbol);
BOOST_SPIRIT_DEBUG_NODE(symbol_raw);
}
qi::rule<Iterator, expression_ast(), ascii::space_type>
expression, term, factor, comment;
qi::rule<Iterator, std::string(), ascii::space_type>
symbol, symbol_raw;
};
}
int main(int argc, char* argv[])
{
using boost::spirit::ascii::space;
using client::expression_ast;
using client::ast_print;
typedef std::string::const_iterator iterator_type;
typedef client::ParserGenerator<iterator_type> ParserGenerator;
ParserGenerator pg; // our grammar
std::string predicate( "i_.c>x[0]" );
expression_ast ast;
ast_print printer;
iterator_type iter = predicate.begin(), end = predicate.end();
if ( phrase_parse( iter, end, pg, space, ast ))
{
std::cerr << printer( ast ) << std::endl;
}
return 0;
}
TL;DR use
inside
binary_exprstruct. Here’s why:You declare a functor object, to be used as a Phoenix lazy actor.
The functor is what’s known as a Deferred/Polymorphic Calleable Object (PCE) in the Boost documentation. This means that the expression templates building the Phoenix actors will actually only do actual overload resolution/type deduction for the function arguments at the actual time of application.
The function return type cannot be deduced (just like it cannot in regular (non-lazy) C++). This is where boost libraries employ the BOOST_RESULT_OF protocol.
What this protocol entails, is this:
nparameters, boost will look for a nestedtypetypedef inside a nested class templateresult, parameterized with thenactual argument types. This will be the return typeoperator()with those arguments. Here, C++ will be left to do the overload resolution.In general, you are expected to write your functor in fully parameterized style:
This works. However, because the actual overload resolution on argument types is done by the compiler, you can change the
operator()signature to use fixed types:If you wish, you can do away with (some) template parameters:
Or even not a function template at all:
Everything is fine, as long as one overload matches the actual argument types passed. The important bit, though, is that the
result<...>::typeis getting evaluated regardless of the signature ofoperator(), and as such it will need precisely the number of arguments expected.Note also that you can, in this fashion, combine functors of different arity:
This works, because both
result<T>::typeandresult<T,U>::typeare valid type expressions with this definition.Bonus notes:
You had a strangeness there where you said
instead of
This is because the return type shouls, in fact, not vary depending on the actual argument type. In your sample, the argument type would normally be the same, but it didn’t make sense technically.
You can do things without the BOOST_RESULT_OF protocol if you use decltype. This means you can just drop the nested
resultstructYou can also adapt normal functions as Phoenix actors:
This will basically write the functor objects for you, including the RESULT_OF protocol bits.
Finally, you can use standard Phoenix actors instead of defining custom ones. In that case, there is no need for any of the above:
Wrap-up
The complete code is here: http://ideone.com/Xv9IH1 and was tested on