Я хочу создать какой-либо форматированный вывод. Для этого нужен отступ. Так что в какой-то момент во время генерации я хотел бы получить текущую позицию, чтобы следующие строки имели отступ с этой суммой.
Вот минимальный пример. Пожалуйста, предположим, что мы не знаем, как долго выход 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[]
?
Обновить: Указатель на сгенерированный до этого момента вывод также подойдет.
Я считаю, что этот подход похож на тот, который вы описали в комментариях. Предполагается, что единственная информация, которую вы можете получить от итератора, — это общее количество написанных символов. Это можно упростить, если у вас есть доступ к текущему столбцу, изменив заголовочные файлы, как указано в другом ответе.
Редактировать: Модифицированный код с подходом, предложенным Майком М в комментариях. Теперь у него есть лучший интерфейс. Протестировано с 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;
}
Вот пользовательская директива, основанная на объяснениях Вот.
К сожалению, из-за того, что необходимая информация содержится в закрытом члене итератора, это работает только с очень простым примером, который вы опубликовали. Если вы выводите что-то еще до того, как все сместится. Вы можете обойти это, если вы хотите немного изменить код в деталь / output_iterator.hpp. Вы можете оставить комментарий «private:» в position_policy
или просто добавьте функцию-член 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;
Сделайте атрибут директивы таким же, как атрибут списка (%) Вот):
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.HPPint 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;
}