Search code examples
c++boostboost-spiritboost-spirit-karma

Boost:Spirit:Karma: How to get current position of output?


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.


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;
    }