2

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

这是一个最小的例子。请假设,我们不知道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[]?

更新:指向到目前为止生成的输出的指针也可以。

4

2 回答 2

6

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

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

为了使用,您需要首先定义两个类型的终端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;
}
于 2013-08-07T00:49:29.467 回答
3

这是一个基于此处解释的自定义指令。
不幸的是,由于您需要的信息包含在迭代器的私有成员中,这仅适用于您发布的非常简单的示例。如果您在所有内容未对齐之前输出任何其他内容。如果您愿意稍微修改detail/output_iterator.hpp中的代码,您可以解决此问题。您可以在中注释“private:”,position_policy也可以简单地添加一个get_out_columnget_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;  

使指令的属性与 list(%) 的属性相同(通过查看此处):

    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;
}
于 2013-08-06T18:01:08.517 回答