Question

I want to generate some formatted output. For this some indention is needed. So at some point during generation I would like to get the current position, to have the following lines indented with that amount.

Here is a minimal example. Please assume, that we don't know how long the output of karma::lit("Some text: ") is during compile time. In fact, this leading text may be generated by several rules.

#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include <boost/spirit/include/karma.hpp>
using namespace std;

int main() {
  vector<int> v { 0, 1, 2, 3 };
  {
    namespace karma = boost::spirit::karma;
    karma::rule<ostream_iterator<char>, std::vector<int>() > myRule =
        karma::lit("Some text: ") << (karma::int_ % karma::eol);
    karma::generate(ostream_iterator<char>(cout), myRule, v);
  }

  return 0;
}

This produces

Some text: 0
1
2
3

I would like the result:

Some text: 0
           1
           2
           3

To achieve this, one needs to know the current position, right before the vector gets generated. So, something like an equivalent for qi::raw[]?

Update: A pointer to the up to this point generated output, would also do.

Was it helpful?

Solution

I believe this approach is similar to the one you described in the comments. It assumes that the only information you can get from the iterator is the total count of characters written. It could be simplified further if you had access to the current column by modifying the header files as mentioned in the other answer.

Edit: Modified the code with the approach Mike M suggested in the comments. Now it has a better interface. Tested with g++ 4.8.1 and clang 3.2 using boost 1.54.0.

In order to use you need to first define two terminals of type position_getter:

std::size_t start=0, end=0;
position_getter start_(start), end_(end);

Then you simply put start_ at the start of a line, and end_ at the point where you want to know in which column you are. After that you can use end - start to calculate that column. Since this calculation needs to be done at parse time (not compile time) you need to use phx::ref(end) - phx::ref(start).

With the modifications mentioned in the other answer, you could simply define one terminal:

std::size_t column=0;
position_getter column_(column);

And then use it in rule like this:

myRule = karma::lit("Some text: ")
            << column_
            << karma::int_ % 
            (karma::eol << karma::repeat(phx::ref(column))[karma::char_(" ")]);

#include <iostream>
#include <string>
#include <vector>

#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/phoenix.hpp>

//START OF CURRENT_POS.HPP
#include <boost/spirit/include/karma_generate.hpp>

///////////////////////////////////////////////////////////////////////////////
// definition the place holder
namespace custom_generator {
  BOOST_SPIRIT_TERMINAL_EX(current_pos);

  struct position_getter: boost::spirit::terminal<
      boost::spirit::tag::stateful_tag<std::size_t&, tag::current_pos> > {
    typedef boost::spirit::tag::stateful_tag<std::size_t&, tag::current_pos> tag_type;

    position_getter(std::size_t& p)
        : boost::spirit::terminal<tag_type>(p) {
    }
  };
}

///////////////////////////////////////////////////////////////////////////////
// implementation the enabler
namespace boost {
  namespace spirit {

    // enables a terminal of type position_getter
    template<>
    struct use_terminal<karma::domain,
        tag::stateful_tag<std::size_t&, custom_generator::tag::current_pos> > : mpl::true_ {
    };
  }
}

///////////////////////////////////////////////////////////////////////////////
// implementation of the generator
namespace custom_generator {
  struct current_pos_generator: boost::spirit::karma::primitive_generator<
      current_pos_generator> {
    current_pos_generator(std::size_t& pos_)
        : pos(pos_) {
    }

    // Define required output iterator properties
    typedef typename boost::mpl::int_<
        boost::spirit::karma::generator_properties::tracking> properties;

    // Define the attribute type exposed by this parser component
    template<typename Context, typename Unused>
    struct attribute {
      typedef boost::spirit::unused_type type;
    };

    // This function is called during the actual output generation process.
    // It stores information about the position in the output stream in
    // the variable you used to construct position_getter
    template<typename OutputIterator, typename Context, typename Delimiter,
        typename Attribute>
    bool generate(OutputIterator& sink, Context& ctx,
                  Delimiter const& delimiter, Attribute const& attr) const {

      std::size_t column = sink.get_out_count();

      // This would only work if you comment "private:" in line 82 of
      // boost/spirit/home/karma/detail/output_iterator.hpp
      // std::size_t column = sink.track_position_data.get_column()-1;

      pos = column;

      return true;
    }

    // This function is called during error handling to create
    // a human readable string for the error context.
    template<typename Context>
    boost::spirit::info what(Context& ctx) const {
      return boost::spirit::info("current_pos");
    }

    std::size_t& pos;
  };
}

///////////////////////////////////////////////////////////////////////////////
// instantiation of the generator
namespace boost {
  namespace spirit {
    namespace karma {
      template<typename Modifiers>
      struct make_primitive<
          tag::stateful_tag<std::size_t&, custom_generator::tag::current_pos>,
          Modifiers> {
        typedef custom_generator::current_pos_generator result_type;

        template<typename Terminal>
        result_type operator()(Terminal& term, unused_type) const {
          typedef tag::stateful_tag<std::size_t&,
              custom_generator::tag::current_pos> tag_type;
          using spirit::detail::get_stateful_data;
          return result_type(get_stateful_data<tag_type>::call(term));
        }
      };
    }
  }
}
//END OF CURRENT_POS.HPP

int main() {
  std::vector<int> v { 0, 1, 2, 3 };
  {
    namespace karma = boost::spirit::karma;
    namespace phx = boost::phoenix;
    using custom_generator::position_getter;

    std::size_t start = 0, end = 0;
    position_getter start_(start), end_(end);

    karma::rule<std::ostream_iterator<char>, std::vector<int>()> myRule =
        start_
            << karma::lit("Some text: ")
            << end_
            << karma::int_ % (karma::eol
                << karma::repeat(phx::ref(end) - phx::ref(start))[karma::char_(
                    " ")]);
    karma::generate(std::ostream_iterator<char>(std::cout), myRule, v);

    std::cout << std::endl;

    karma::rule<std::ostream_iterator<char>, std::vector<int>()> myRuleThatAlsoWorks =
        karma::lit(":)")
            << karma::eol
            << start_
            << karma::lit("Some text: ")
            << end_
            << karma::int_ % (karma::eol
                << karma::repeat(phx::ref(end) - phx::ref(start))[karma::char_(
                    " ")]);
    karma::generate(std::ostream_iterator<char>(std::cout), myRuleThatAlsoWorks,
                    v);

  }

  return 0;
}

OTHER TIPS

Here is a custom directive heavily based on the explanations here.
Unfortunately due to the fact that the information you need is contained in a private member of the iterator, this only works with the really simple example that you posted. If you output anything else before everythings gets misaligned. You can work around this if you are willing to modify slightly the code in detail/output_iterator.hpp. You can either comment the "private:" in position_policy or simply add a member function get_out_column in the same vein as get_out_count.

In order to use it you need to change your:

karma::int_ % karma::eol;

to:

custom_generator::align_list_to_current_position[karma::int_];

As you can see the custom directive requires a lot of boilerplate but big part of this code is common to every directive. In fact, besides changing the names, I have only needed to change three things:

Make sure that tracking is in the set of required properties:

    typedef typename boost::mpl::int_<
          Subject::properties::value | karma::generator_properties::tracking
    > properties;  

Make the attribute of the directive be the same as the one a list(%) would have(by looking here):

    template <typename Context, typename Iterator>
    struct attribute 
    : boost::spirit::traits::build_std_vector<
        typename boost::spirit::traits::attribute_of<Subject, Context, Iterator>::type
      > 
    {}; 

And finally change the generate function. In this function I simply build a list that has as its left member whatever you passed to the directive and as its right one the concatenation of karma::eol and as many spaces are as needed to be aligned.


#include <iostream>
#include <string>
#include <vector>

#include <boost/spirit/include/karma.hpp>

//START OF ALIGN_LIST_TO_CURRENT_POSITION.HPP
#include <boost/spirit/include/karma_generate.hpp>

///////////////////////////////////////////////////////////////////////////////
// definition the place holder 
namespace custom_generator 
{ 
    BOOST_SPIRIT_TERMINAL(align_list_to_current_position);
} 

///////////////////////////////////////////////////////////////////////////////
// implementation the enabler
namespace boost { namespace spirit 
{ 
    // We want custom_generator::align_list_to_current_position to be usable as a directive only, 
    // and only for generator expressions (karma::domain).
    template <>
    struct use_directive<karma::domain, custom_generator::tag::align_list_to_current_position> 
      : mpl::true_ {}; 
}}

///////////////////////////////////////////////////////////////////////////////
// implementation of the generator
namespace custom_generator
{ 


    // That's the actual columns generator
    template <typename Subject>
    struct align_list_to_current_position_generator
      : boost::spirit::karma::unary_generator<
            align_list_to_current_position_generator<Subject> >
    {
        // Define required output iterator properties: take the properties needed by the subject and add tracking
        typedef typename boost::mpl::int_<Subject::properties::value | boost::spirit::karma::generator_properties::tracking> properties;

        // Define the attribute type exposed by this parser component
        template <typename Context, typename Iterator>
        struct attribute 
          : boost::spirit::traits::build_std_vector<
                typename boost::spirit::traits::attribute_of<Subject, Context, Iterator>::type> 
        {};

        align_list_to_current_position_generator(Subject const& s)
          : subject(s)
        {}

        // This function is called during the actual output generation process.
        // It dispatches to the embedded generator while supplying a new 
        // delimiter to use
        template <typename OutputIterator, typename Context
          , typename Delimiter, typename Attribute>
        bool generate(OutputIterator& sink, Context& ctx
          , Delimiter const& delimiter, Attribute const& attr) const
        {
            using boost::spirit::karma::repeat;
            using boost::spirit::karma::char_;
            using boost::spirit::karma::eol;
            using boost::spirit::karma::domain;

            std::size_t column = sink.get_out_count();

            //This would only work if you comment "private:" in line 82 of boost/spirit/home/karma/detail/output_iterator.hpp
            // std::size_t column = sink.track_position_data.get_column()-1;

            return boost::spirit::compile<domain>(subject%(eol << repeat(column)[char_(" ")])).generate(sink, ctx, delimiter, attr);
        }

        // This function is called during error handling to create
        // a human readable string for the error context.
        template <typename Context>
        boost::spirit::info what(Context& ctx) const
        {
            return boost::spirit::info("align_list_to_current_position", subject.what(ctx));
        }

        Subject subject;
    };
}

///////////////////////////////////////////////////////////////////////////////
// instantiation of the generator
namespace boost { namespace spirit { namespace karma
{
    // This is the factory function object invoked in order to create 
    // an instance of our align_list_to_current_position_generator.
    template <typename Subject, typename Modifiers>
    struct make_directive<custom_generator::tag::align_list_to_current_position, Subject, Modifiers>
    {
        typedef custom_generator::align_list_to_current_position_generator<Subject> result_type;

        result_type operator()(unused_type, Subject const& s, unused_type) const
        {
            return result_type(s);
        }
    };
}}}
//END OF ALIGN_LIST_TO_CURRENT_POSITION.HPP


int main() {
  std::vector<int> v { 0, 1, 2, 3 };
  {
    namespace karma = boost::spirit::karma;
    using custom_generator::align_list_to_current_position;
    karma::rule<std::ostream_iterator<char>, std::vector<int>() > myRule = 
        karma::lit("Some text: ") << align_list_to_current_position[karma::int_];
    karma::generate(std::ostream_iterator<char>(std::cout), myRule, v);

    std::cout << std::endl;

    //This rule would work if you make the changes mentioned in align_list_to_current_position_generator::generate
    karma::rule<std::ostream_iterator<char>, std::vector<int>() > myRuleThatFails = 
        karma::lit(":_(") << karma::eol << karma::lit("Some text: ") << align_list_to_current_position[karma::int_ << karma::int_];
    karma::generate(std::ostream_iterator<char>(std::cout), myRuleThatFails, v);
  }

  return 0;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top