How do you create a generic parser using qi? - c++

I am attempting to create generic parser-elements using qi as I unfortunately (MSVC must be supported) can not use X3.
The idea is to have a templated struct:
template<class T> struct parse_type;
Which I could use like this:
template<class T> T from_string(std::string const& s)
{
T res;
parse_type<T> t;
...
if (phrase_parse(...,parse_type<T>(),...,t))
}
or specialise like this
template<class T,class Alloc>
struct parse_type<std::vector<T,Alloc>>
{
// Parse a vector using rule '[' >> parse_type<T> % ',' > ']';
}
The primary purpose is to allow for easy parsing of e.g. std::tuple, boost::optional and boost::variant (The last one can not be automatic due to the greedy nature of qi).
I would appreciate feedback as to how approach this. Currently I base my struct on qi::grammar, but grammar is not supported in X3 and I would like to use X3 when MSVC compiles this, and I am also a little bit uncomfortable with having to provide the skipper.
An alternative would be to have a static function in parse_type that returns the appropriate rule. I am considering if this is a cleaner approach?
Any feedback will be appreciated.
Update2: Replaced code-snippet with compilable example that fails at runtime. Here is the code:
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <string>
#include <string>
#include <iostream>
#include <iostream>
// Support to simplify
using iter = std::string::const_iterator;
void print(std::vector<int> const& v)
{
std::cout << '[';
for (auto i: v) std::cout << i << ',';
std::cout << "]";
}
namespace qi = boost::spirit::qi;
// My rule factory - quite useless if you do not specialise
template<class T> struct ps_rule;
// An example of using the factory
template<class T>
T from_string(std::string const& s)
{
T result;
iter first { std::begin(s) };
auto rule = ps_rule<T>::get();
phrase_parse(first,std::end(s),rule,qi::space,result);
return result;
}
// Specialising rule for int
template<>
struct ps_rule<int>
{
static qi::rule<iter,int()> get() { return qi::int_; }
};
// ... and for std::vector (where the elements must have rules)
template<class T,class Alloc>
struct ps_rule<std::vector<T,Alloc>>
{
static qi::rule<iter,std::vector<T,Alloc>()> get()
{
qi::rule<iter,std::vector<T,Alloc>()> res;
res.name("Vector");
res =
qi::lit('{')
>> ps_rule<T>::get() % ','
>> '}';
return res;
}
};
int main()
{
// This one works like a charm.
std::cout << ((from_string<int>("100") == 100) ? "OK\n":"Failed\n");
std::vector<int> v {1,2,3,4,5,6};
// This one fails
std::cout << ((from_string<std::vector<int>>("{1,2,3,4,5,6}") == v) ? "OK\n":"Failed\n");
}
The code fails in boost/function_template.hpp line 766:
result_type operator()(BOOST_FUNCTION_PARMS) const
{
if (this->empty())
boost::throw_exception(bad_function_call());
return get_vtable()->invoker
(this->functor BOOST_FUNCTION_COMMA BOOST_FUNCTION_ARGS);
}
This code is a member function in boost::function4
,boost::fusion::vector0 > &
,boost::spirit::unused_type const&>
and the problem is that get_vtable returns an invalid pointer.

Your main problem is that the copy constructor for qi::rule takes a reference to the original rule, which in your case is a local variable. One way you can avoid this problem is by using qi::rule's copy member function but this requires changing slightly the return type of your specialization of ps_rule.
static typename boost::proto::terminal<qi::rule<iter,std::vector<T,Alloc>()>>::type get()
{
//[...] (same as before)
return res.copy();
}
Once you do that, the same problem arises with your ps_rule<int> even though it seemed to work in isolation. You could do something analogous but in this case the rule is not required, it would be better (even from a performance point of view) to just use something like:
static qi::int_type get() { return qi::int_; }
Full Sample (Running on WandBox)
#include <boost/spirit/include/qi.hpp>
#include <string>
#include <iostream>
// Support to simplify
using iter = std::string::const_iterator;
void print(std::vector<int> const& v)
{
std::cout << '[';
for (auto i: v) std::cout << i << ',';
std::cout << "]";
}
namespace qi = boost::spirit::qi;
// My rule factory - quite useless if you do not specialise
template<class T> struct ps_rule;
// An example of using the factory
template<class T>
T from_string(std::string const& s)
{
T result;
iter first { std::begin(s) };
auto rule = ps_rule<T>::get();
qi::phrase_parse(first,std::end(s),rule,qi::space,result);
return result;
}
// Specialising rule for int
template<>
struct ps_rule<int>
{
static qi::int_type get() { return qi::int_; }
};
// ... and for std::vector (where the elements must have rules)
template<class T,class Alloc>
struct ps_rule<std::vector<T,Alloc>>
{
static typename boost::proto::terminal<qi::rule<iter,std::vector<T,Alloc>()>>::type get()
{
qi::rule<iter,std::vector<T,Alloc>()> res;
res.name("Vector");
res =
qi::lit('{')
>> ps_rule<T>::get() % ','
>> '}';
return res.copy();
}
};
int main()
{
// This one works like a charm.
std::cout << ((from_string<int>("100") == 100) ? "OK\n":"Failed\n");
std::vector<int> v {1,2,3,4,5,6};
std::cout << ((from_string<std::vector<int>>("{1,2,3,4,5,6}") == v) ? "OK\n":"Failed\n");
std::vector<std::vector<int> > vv {{1,2,3},{4,5,6}};
std::cout << ((from_string<std::vector<std::vector<int>>>("{{1,2,3},{4,5,6}}") == vv) ? "OK\n":"Failed\n");
}
PS: You can save lots of specializations if you use Spirit's own machinery to create parsers automatically in your primary template. Here is an example.

Related

Boost zip-iterator and structured bindings [duplicate]

Is there a way to make boost::combine work with structured bindings and range-based for (so that identifiers in the structure binding actually point to containers' elements instead of nested tuples of whatever boost::combine uses under the hood)? The following (live example) fails to compile:
#include <boost/range/combine.hpp>
#include <iostream>
int main()
{
std::vector<int> a{1,2,3};
std::vector<int> b{2,3,4};
for (auto [f, s] : boost::combine(a, b))
{
std::cout << f << ' ' << s << std::endl
}
}
The real answer is to use either boost::tie or grab the range-v3 zip() which actually yields a std::tuple.
The for educational purposes only answer is just to adapt the structured bindings machinery for boost::tuples::cons. That type already has a get() which works with ADL and does the right thing, so all we need to do is provide tuple_size and tuple_element (which ends up being really easy to do since these exact traits already exist in Boost):
namespace std {
template <typename T, typename U>
struct tuple_size<boost::tuples::cons<T, U>>
: boost::tuples::length<boost::tuples::cons<T, U>>
{ };
template <size_t I, typename T, typename U>
struct tuple_element<I, boost::tuples::cons<T, U>>
: boost::tuples::element<I, boost::tuples::cons<T, U>>
{ };
}
But don't actually do that in real code, since really only the type author should opt-in to this kind of thing.
That'll make the structured binding just work.
You can use boost::tie to accomplish this.
#include <boost/range/combine.hpp>
#include <iostream>
int main()
{
std::vector<int> a{1,2,3};
std::vector<int> b{2,3,4};
int f, s;
for (auto var : boost::combine(a, b))
{
boost::tie(f, s) = var;
std::cout << f << ' ' << s << std::endl;
}
}
Demo.

C++ if (false) condition evaluated?

I'm learning about template in C++, and my set of C++ terminology is somewhat limited, so I couldn't google this problem.I'm trying to implement a custom dict type based on std::unordered_map. My goal is to be able to instantiate the class dict in ways like the following:
dict<std::string, long> d; // OR
dict<std::string, std::set<std::string>> d; // OR
dict<std::string, std::map<char, float>> d; // OR
dict<std::string, std::vector<std::string>> d; // OR
dict<std::string, std::vector<double>> d;
So here's the code, I'm using:
utils.h
#include <fstream>
#include <unordered_map>
#include <set>
#include <vector>
#include <algorithm>
#include <type_traits>
// for bravity
using namespace std;
// to check for stl vector
// slightly modified version of: https://stackoverflow.com/a/31105859
namespace is_container {
template <typename T> struct stl_vector : false_type{};
template <typename T> struct stl_vector<std::vector<T>> : true_type{};
}
namespace StringOps {
// generaic function to split based on many delimiters:
// source: https://stackoverflow.com/a/9676623
vector<string> split(const string& str, const string& delimiters = " ,") {
vector<string> v;
unsigned start = 0;
auto pos = str.find_first_of(delimiters, start);
while(pos != string::npos) {
if(pos != start) // ignore empty tokens
v.emplace_back(str, start, pos - start);
start = pos + 1;
pos = str.find_first_of(delimiters, start);
}
if(start < str.length()) // ignore trailing delimiter
v.emplace_back(str, start, str.length() - start); // add what's left of the string
return v;
}
}
template<class Key, template <class...> class Value, typename T, class = void>
class dict {
public:
Value<T> t;
};
template<class Key, template <class...> class Value, typename T> // detect container types with ::iterator
class dict<Key, Value, T, void_t<typename Value<T>::iterator>> : true_type {
private:
unordered_map<Key, Value<T>> content;
bool is_vector = false;
string line;
unordered_map<Key, Value<T>> load(ifstream& file) {
while (getline(file, line)) {
if (!line.empty()) {
// remove trailling \n if exists
if (line[line.length()-1] == '\n')
line.erase(line.length() - 1);
vector<string> tokens = StringOps::split(line);
Value<T> result;
(tokens[i]));
if (is_vector) {
for (unsigned i = 1; i < tokens.size(); i++) {
result.emplace_back(static_cast<T>(tokens[i]));
}
}
if(false) { // should never be looked into
auto it = result.cend();
for (unsigned i = 1; i < tokens.size(); i++) {
result.emplace_hint(it, static_cast<T>(tokens[i]));
}
}
content[static_cast<Key>(tokens[0])] = result;
}
}
return content;
}
public:
constexpr Value<T>& operator[](Key k) {
return content[k];
}
dict(const string& path) {
// detect vector type
if(is_container::stl_vector<decay_t<Value<T>>>::value)
is_vector = true;
ifstream file(path);
content = load(file);
}
constexpr unsigned size() {
return content.size();
}
};
template<class Key, template <class...T> class Value, typename T> // detect arithmatic types
class dict<Key, Value, T, typename enable_if<is_arithmetic<Value<T>>::value>::type> {
public:
dict() {
// we'll come to you later..
}
};
main.cpp
#include <iostream>
#include "utils.h"
int main() {
dict<string, vector, string> d("/home/path/to/some/file");
cout << d.size();
}
results:
error: no member named 'emplace_hint' in 'std::vector<std::__cxx11::basic_string<char>, std::allocator<std::__cxx11::basic_string<char> > >'
result.emplace_hint(it, static_cast<T>(tokens[i]));
questions:
1 - why on earth if (false) condition is reached in the first place?
2 - how could this be tweaked to achieve the desired instantiation style?
if (false) doesn't mean the code isn't compiled; it simply means the code inside is not executed at runtime, but it still has to be valid.
There are (at least) three kinds of conditional constructs in C++:
Preprocessor conditions. This tells the preprocessor not to pass the code to the compiler if if the condition is not met. Therefore, the code can be completely gibberish as long as it consists of valid preprocessor tokens. For example, the following is a well-formed C++ program with defined behavior:
#include <iostream>
int main()
{
#if 0
YYMJBNvOldLdK8rC0PTXH8DHJ58FQpP0MisPZECDuYHDJ7xL9G
#else
std::cout << "Hello world!\n";
#endif
}
Runtime selection statements. Such code is still parsed by the compiler and must still be valid code regardless of whether the compiler is capable of proving unreachable code — the compiler cannot even find the terminating } if it doesn't parse the code. This is partly because compilers cannot evaluate arbitrary expressions at runtime — if you don't specify explicitly (see next bullet), then evaluation defaults to be runtime. Therefore, the code above becomes ill-formed if you replace #if 0 – #else – #endif with if (false) { – } else { – }. However, runtime errors (i.e., undefined behavior) within unreachable code are fine. Therefore, the following is a well-formed C++ program with defined behavior: (some compilers may generate warnings, but that's irrelevant)
#include <iostream>
#include <climits>
int main()
{
if (false) {
volatile int x = 42/0;
x = *static_cast<int*>(nullptr);
const int y = INT_MAX + 1;
} else {
std::cout << "Hello world!\n";
}
}
(Since C++17) if constexpr. The rule for this one is a bit complex. The condition has to be known at compile time, and the false branch is discarded. The discarded branch is still required to be valid, except that it is not instantiated. Therefore, the code above is still valid code if you change if to if constexpr. The following is also a well-formed C++ program with defined behavior:
#include <iostream>
#include <type_traits>
template <typename T>
void print(T x)
{
if constexpr (std::is_same_v<T, int>) {
std::cout << static_cast<typename T::template stack<T>::overflow>(x);
} else {
std::cout << x;
}
}
int main()
{
print("Hello world!\n");
}
The typename and template are still necessary to make the code syntactically valid, but the nonexistent type const char*::stack<const char*>::overflow is not formed.
In your case, you can write a trait class to determine whether a type is a specialization of the class template std::vector: (here I use the standard traits convention)
template <typename C>
struct is_std_vector :std::false_type {};
template <typename T, typename A>
struct is_std_vector<std::vector<T, A>> :std::true_type {};
template <typename C>
inline constexpr bool is_std_vector_v = is_std_vector<C>::value;
Then use it in if constexpr to dispatch: (don't forget to replace Container with the container type you are examining)
if constexpr (is_std_vector_v<Container>) {
// do std::vector specific things
} else {
// do other things
}

how to get rid of lambda syntax

I need to make those function in the "normal" syntax. how do i change it?
template <template <class, class> class Container>
typename const Container<Course*, std::allocator<Course*> > Schedule<Container>::getAllCourses( ) const
{
Container<Course*, std::allocator<Course*> > newone;
std::for_each(courses.begin(), courses.end(), [&newone](Course *c)
{Course* nc = new Course(c->getName(),c->getNumber(), c->getFaculty()); newone.push_back(nc);});
//make a container and push into it every course
return newone;
}
actually, I need to change the function "for_each" that it will use outside class.
I don't know how to do it. can you help?
A lambda function is a closure type, implemented as an unnamed functor.
You could look into those keywords, to understand how to perform a "conversion". Pretty much the rule would be that this lambda :
[capture_clause](args) -> return_type { /* lambda_body */ }
is practically (in a simplified view - generic lambdas or value/ref captures not explicitly shown here) this
struct no_name
{
no_name(capture_clause) : /* Initialize the closure */ { }
return_type operator()(args) { /* lambda_body */ }
};
In your case, you'd have to a make a class like the following :
template <template <class, class> class Container>
struct lamda_fun
{
Container<Course*, std::allocator<Course*> > &newone;
lamda_fun(Container<Course*, std::allocator<Course*> > &newone)
: newone(newone) {
}
void operator()(Course *c) {
Course* nc = new Course(c->getName(),c->getNumber(), c->getFaculty());
newone.push_back(nc);
}
};
If you still want to get rid of the lambda syntax call it like
std::for_each(courses.begin(), courses.end(), lamda_fun<Container>(newone));
Even though a copy of the functor will be passed to for_each, the functor wraps a reference so the correct thing will be done.
An easier way would be to just use a for loop though (yes those still exist)
// range based version
for (auto c : courses) {
Course* nc = new Course(c->getName(), c->getNumber(), c->getFaculty());
newone.push_back(nc);
}
// traditional version
for (auto it = courses.begin(), ite = courses.end(); it != ite; ++it)
{
auto c = *it;
Course* nc = new Course(c->getName(), c->getNumber(), c->getFaculty());
newone.push_back(nc);
}
You might replace the lambda with a local class:
#include <algorithm>
#include <iostream>
#include <vector>
template <class Container>
Container transform(const Container& container) {
struct Lambda {
Container& result;
Lambda(Container& result) : result(result) {}
void operator () (const typename Container::value_type& value) {
result.push_back(value + 1);
}
};
Container result;
std::for_each(container.begin(), container.end(), Lambda(result));
return result;
}
int main() {
std::vector<int> u;
u.push_back(0);
u.push_back(1);
u.push_back(2);
std::vector<int> v = transform(u);
std::cout << v[0] << v[1] << v[2] << '\n';
}

Is boost::lexical_cast redundant with c++11 stoi, stof and family?

Is boost::lexical_cast redundant now that C++11 introduces stoi, stof and family, or is there any reason to still use it? (apart from not having a C++11 compiler) Do they provide exactly the same functionality?
boost::lexical_cast
handles more kinds of conversion, including iterator pairs, arrays, C strings, etc.
offers the same generic interface (sto* have different names for different types)
is locale-sensitive (sto*/to_string are only in part, e.g. lexical_cast can process thousands separators, while stoul usually doesn't)
boost::lexical_cast gives you a uniform interface across types which is often very important in generic code.
In general, consistent interface across types for same functionality allows generic code better. For example, following can be used as generic parser from string tokens to std::tuple:
template<typename T>
void fill(T& item, const std::string& token){
item = boost::lexical_cast<T>(token)
}
template<int N, typename ...Ts>
void parse(std::integral_constant<int, N>, std::tuple<Ts...>& info, std::vector<std::string>& tokens) {
fill(std::get<N>(info), tokens[N]);
parse(std::integral_constant<int, N - 1>, info, tokens);
}
template<typename ...Ts>
void parse(std::integral_constant<int, 0>, std::tuple<Ts...>& info, std::vector<std::string>& tokens) {
fill(std::get<0>(info), tokens[0]);
}
Instead of tuple, I often use boost fusion struct to deserialize some tokenized strings directly into a struct in a generic way.
Performance wise, you could do the comparison using the following code (it's a variation of my post here)
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <chrono>
#include <random>
#include <exception>
#include <type_traits>
#include <boost/lexical_cast.hpp>
using namespace std;
// 1. A way to easily measure elapsed time -------------------
template<typename TimeT = std::chrono::milliseconds>
struct measure
{
template<typename F>
static typename TimeT::rep execution(F const &func)
{
auto start = std::chrono::system_clock::now();
func();
auto duration = std::chrono::duration_cast< TimeT>(
std::chrono::system_clock::now() - start);
return duration.count();
}
};
// -----------------------------------------------------------
// 2. Define the convertion functions ========================
// A. Using boost::lexical_cast ------------------------------
template<typename Ret>
Ret NumberFromString(string const &value) {
return boost::lexical_cast<Ret>(value);
}
// B. Using c++11 stoi() -------------------------------------
int IntFromString(string const &value) {
return std::stoi(value);
}
// C. Using c++11 stof() -------------------------------------
float FloatFromString(string const &value) {
return std::stof(value);
}
// ===========================================================
// 3. A wrapper to measure the different executions ----------
template<typename T, typename F> long long
MeasureExec(std::vector<string> const &v1, F const &func)
{
return measure<>::execution([&]() {
for (auto const &i : v1) {
if (func(i) != NumberFromString<T>(i)) {
throw std::runtime_error("FAIL");
}
}
});
}
// -----------------------------------------------------------
// 4. Machinery to generate random numbers into a vector -----
template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type
FillVec(vector<T> &v)
{
mt19937 e2(1);
uniform_int_distribution<> dist(3, 1440);
generate(v.begin(), v.end(), [&]() { return dist(e2); });
}
template<typename T>
typename std::enable_if<!std::is_integral<T>::value>::type
FillVec(vector<T> &v)
{
mt19937 e2(1);
uniform_real_distribution<> dist(-1440., 1440.);
generate(v.begin(), v.end(), [&]() { return dist(e2); });
}
template<typename T>
void FillVec(vector<T> const &vec, vector<string> *result)
{
result->resize(vec.size());
for (size_t i = 0; i < vec.size(); i++)
result->at(i) = boost::lexical_cast<string>(vec[i]);
}
// -----------------------------------------------------------
int main()
{
std::vector<int> vi(991908);
FillVec(vi);
std::vector<float> vf(991908);
FillVec(vf);
std::vector<string> vsi, vsf;
FillVec(vi, &vsi);
FillVec(vf, &vsf);
cout << "C++ 11 stof function .. " <<
MeasureExec<float>(vsf, FloatFromString) << endl;
cout << "Lexical cast method ... " <<
MeasureExec<float>(vsf, NumberFromString<float>) << endl;
cout << endl << endl;
cout << "C++ 11 stoi function .. " <<
MeasureExec<int>(vsi, IntFromString) << endl;
cout << "Lexical cast method ... " <<
MeasureExec<int>(vsi, NumberFromString<int>) << endl;
return 0;
}
When executed with
g++ -std=c++11 -Ofast -march=native -Wall -pedantic main.cpp && ./a.out
The results are
C++ 11 stof function .. 540
Lexical cast method ... 559
C++ 11 stoi function .. 117
Lexical cast method ... 156
The C++11 specialized functions certainly seem to perrform better. But they are exactly that, specialized, and as such make the construction of abstract interfaces harder than lexical_cast
boost::lexical_cast is more than converting to a distinct set of types:
struct A {};
std::ostream& operator << (std::ostream& stream, const A&) {
return stream;
}
struct B {};
std::istream& operator >> (std::istream& stream, B&) {
return stream;
}
int main(){
A a;
B b = boost::lexical_cast<B>(a);
}
Its strength and weakness is the acceptance of any pair of types for the conversion through an intermediate std::stringstream (where an optimized algorithm is or is not applied).

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.
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;
}
Here is a custom directive heavily based on the explanations here.
Unfortunately due to the fact that the information you need is contained in a private member of the iterator, this only works with the really simple example that you posted. If you output anything else before everythings gets misaligned. You can work around this if you are willing to modify slightly the code in detail/output_iterator.hpp. You can either comment the "private:" in position_policy or simply add a member function get_out_column in the same vein as get_out_count.
In order to use it you need to change your:
karma::int_ % karma::eol;
to:
custom_generator::align_list_to_current_position[karma::int_];
As you can see the custom directive requires a lot of boilerplate but big part of this code is common to every directive. In fact, besides changing the names, I have only needed to change three things:
Make sure that tracking is in the set of required properties:
typedef typename boost::mpl::int_<
Subject::properties::value | karma::generator_properties::tracking
> properties;
Make the attribute of the directive be the same as the one a list(%) would have(by looking here):
template <typename Context, typename Iterator>
struct attribute
: boost::spirit::traits::build_std_vector<
typename boost::spirit::traits::attribute_of<Subject, Context, Iterator>::type
>
{};
And finally change the generate function. In this function I simply build a list that has as its left member whatever you passed to the directive and as its right one the concatenation of karma::eol and as many spaces are as needed to be aligned.
#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;
}