2

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.

Mike M
  • 2,263
  • 3
  • 17
  • 31
  • There was recently a [35-post thread on the \[spirit-general\] mailing list](http://boost.2283326.n4.nabble.com/Indentation-on-Karma-td2677502.html) about an indentation facility. I believe something was proposed for inclusion, but you should probably have a look for yourself – sehe Aug 06 '13 at 17:21
  • @sehe: Thank you for the link to this thread. As far as I get it, they want to handle indention by fixed context-free amounts automatically. I have a manual solution working for this; but my problem here is, that fixed indention is sometimes not enough, and one wants to have it aligned by the line before. So, I need to get the current position, then I would handle indention manually... – Mike M Aug 06 '13 at 17:53

2 Answers2

6

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;
}
llonesmiz
  • 155
  • 2
  • 11
  • 20
  • Thank you very much, that is what I hoped for. May I ask one further little question? Why did you pass the argument as pointer and not by reference? Is there a reason or is it just a matter of taste? – Mike M Aug 07 '13 at 06:20
  • @MikeM I hate it too, but apparently spirit machinery causes that whatever argument is passed to my generator be const. Using a pointer I can sidestep that problem. It is possible that there is a better way to do this, or a way to make spirit not work with consts, but unfortunately this is the only solution I was able to make work. This was mostly trial and error. – llonesmiz Aug 07 '13 at 06:24
  • I tried it and this http://pastebin.com/KKtfyHZW works for me. Do you see any problems with it? – Mike M Aug 07 '13 at 06:30
  • What compiler are you using? I believe this is exactly my first approach and it didn't seem to work with g++ 4.8.1. – llonesmiz Aug 07 '13 at 06:33
  • Mingw g++ 4.8.1 on Windows with Boost 1.54 – Mike M Aug 07 '13 at 06:34
  • It works here too. Great, it is a much better interface. I'll try to find what I did wrong. – llonesmiz Aug 07 '13 at 06:35
  • Actually, I think this would make a nice addition to official spirit, at least with that changed output_iterator.hpp. – Mike M Aug 07 '13 at 06:38
  • I was using `stateful_tag` instead of `stateful_tag`. Thanks a lot for this question, and also for your solution, I think I've learnt a lot. – llonesmiz Aug 07 '13 at 06:47
3

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;
}
llonesmiz
  • 155
  • 2
  • 11
  • 20
  • I don't know if this is the best way to solve this problem, but if I modify the file mentioned above, everything seems to work fine in the tests I've made. Emphasis on "seems". I have very little experience with custom directives and none with generator properties. – llonesmiz Aug 06 '13 at 18:32
  • Thank you very much for your efforts. This is rather black magic for me, but it works for this simple case :-) However, my situation is not really that simple... Would it be possible, to use your example to get a rule which doesn't print anything, like `karma::eps` but gets a reference variable to get the current column out of it? (e.g. `karma::rule curPos`) Sorry, if that may be obvious for some from the current example; I just don't know how to get there :-( – Mike M Aug 06 '13 at 21:28