【问题标题】:Boost:Spirit:Karma: How to get current position of output?Boost:Spirit:Karma:如何获得当前的输出位置?
【发布时间】:2013-08-06 15:59:17
【问题描述】:

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

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

更新:指向当前生成的输出的指针也可以。

【问题讨论】:

  • 最近有一个35-post thread on the [spirit-general] mailing list 关于缩进工具。我相信有人提议将其包含在内,但您可能应该自己看看
  • @sehe:感谢您提供此线程的链接。据我所知,他们希望自动处理固定的无上下文数量的缩进。我有一个为此工作的手动解决方案;但我的问题是,固定缩进有时是不够的,人们希望它与之前的行对齐。所以,我需要得到当前位置,然后我会手动处理缩进...

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


【解决方案1】:

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

编辑: 使用 Mike M 在 cmets 中建议的方法修改了代码。现在它有一个更好的界面。使用 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;
}

【讨论】:

  • 非常感谢,这正是我所希望的。我可以再问一个小问题吗?为什么您将参数作为指针而不是通过引用传递?是有原因还是只是口味问题?
  • @MikeM 我也讨厌它,但显然精神机制导致传递给我的生成器的任何参数都是 const。使用指针我可以回避这个问题。有可能有更好的方法来做到这一点,或者让精神不适用于 consts,但不幸的是,这是我能够做到的唯一解决方案。这主要是反复试验。
  • 我试过了,这个pastebin.com/KKtfyHZW 对我有用。你觉得它有什么问题吗?
  • 你用的是什么编译器?我相信这正是我的第一种方法,它似乎不适用于 g++ 4.8.1。
  • Mingw g++ 4.8.1 on Windows with Boost 1.54
【解决方案2】:

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

为了使用它,您需要更改您的:

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

【讨论】:

  • 我不知道这是否是解决此问题的最佳方法,但如果我修改上述文件,在我所做的测试中似乎一切正常。强调“似乎”。我对自定义指令的经验很少,对生成器属性也没有。
  • 非常感谢您的努力。这对我来说是相当黑的魔法,但它适用于这个简单的案例:-)但是,我的情况并不是那么简单......是否有可能使用你的示例来获得一个不打印任何内容的规则,像karma::eps 一样,但获取一个引用变量来获取当前列? (例如karma::rule&lt;Iterator, karma::unused_type(RefVariable)&gt; curPos)对不起,如果这对当前示例中的某些人来说可能很明显;我只是不知道怎么去那里:-(
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-06
  • 1970-01-01
相关资源
最近更新 更多