提升:精神:业力:如何获得当前的产出位置?

时间:2013-08-06 15:59:17

标签: c++ boost boost-spirit boost-spirit-karma

我想生成一些格式化的输出。为此,需要一些缩进。因此,在生成期间的某个时刻,我希望获得当前位置,以便使用该数量缩进以下行。

这是一个最小的例子。请假设我们不知道编译期间karma::lit("Some text: ")的输出有多长。事实上,这个主要文本可能由几个规则生成。

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

这会产生

Some text: 0
1
2
3

我想结果:

Some text: 0
           1
           2
           3

要实现这一点,需要在生成向量之前知道当前位置。那么,就像qi::raw[]的等价物一样?

更新:指向此点生成输出的指针也可以。

2 个答案:

答案 0 :(得分:6)

我认为这种方法类似于您在评论中描述的方法。它假定您可以从迭代器获得的唯一信息是写入的字符总数。如果您可以通过修改其他答案中提到的头文件来访问当前列,则可以进一步简化。

编辑:使用Mike M在评论中建议的方法修改了代码。现在它有一个更好的界面。使用boost 1.54.0测试g ++ 4.8.1和clang 3.2。

为了使用,您需要先定义两个position_getter类型的终端:

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

然后,您只需将start_放在一行的开头,然后将end_放在您想知道哪一列的位置。之后,您可以使用end - start来计算该列。由于此计算需要在分析时(不是编译时)完成,因此您需要使用phx::ref(end) - phx::ref(start)

通过另一个答案中提到的修改,您可以简单地定义一个终端:

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

然后在规则中使用它:

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

答案 1 :(得分:3)

这是一个很大程度上基于解释here的自定义指令 不幸的是,由于您需要的信息包含在迭代器的私有成员中,这只适用于您发布的非常简单的示例。如果在每个事件都未对齐之前输出任何其他内容。如果您愿意稍微修改detail/output_iterator.hpp中的代码,则可以解决此问题。您可以在position_policy中对“private:”发表评论,也可以像get_out_column一样添加成员函数get_out_count

要使用它,您需要更改:

karma::int_ % karma::eol;

为:

custom_generator::align_list_to_current_position[karma::int_];

正如您所看到的,自定义指令需要大量的样板,但是这些代码的很大一部分对于每个指令都是通用的。事实上,除了改变名称之外,我只需要改变三件事:

确保tracking在所需属性集中:

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

使指令的属性与列表(%)的属性相同(通过查看here):

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

最后更改generate功能。在这个函数中,我简单地构建一个列表,其中包含作为其左侧成员的任何内容,无论您传递给指令,还是正确的列表,karma :: eol的串联以及需要对齐的多个空格。


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