Compiling a Simple Parser with Boost.Spirit

Compiling a simple parser with Boost.Spirit

Mmm. I feel that we have discussed a few more details in chat than have been reflected in the question as it is.

Let me entertain you with my 'toy' implementation, complete with test cases, of a grammar that will recognize <<macros>> like this, including nested expansion of the same.

Notable features:

  1. Expansion is done using a callback (process()), giving you maximum flexibility (you could use a look up table, cause parsing to fail depending on the macro content, or even have sideeffects independent of the output
  2. the parser is optimized to favour streaming mode. Look at spirit::istream_iterator on how to parse input in streaming mode (Stream-based Parsing Made Easy). This has the obvious benefits if your input stream is 10 GB, and contains only 4 macros - it is the difference between crawling performance (or running out of memory) and just scaling.
    • note that the demo still writes to a string buffer (via oss). You could, however, easily, hook the output directly to std::cout or, say, an std::ofstream instance
  3. Expansion is done eagerly, so you can have nifty effects using indirect macros. See the testcases
  4. I even demoed a simplistic way to support escaping the << or >> delimiters (#define SUPPORT_ESCAPES)

Without further ado:

The Code

Note due to laziness, I require -std==c++0x, but only when SUPPORT_ESCAPES is defined

//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi = boost::spirit::qi;
namespace phx= boost::phoenix;
namespace fsn= boost::fusion;

namespace
{
#define SUPPORT_ESCAPES

static bool process(std::string& macro)
{
if (macro == "error") {
return false; // fail the parse
}

if (macro == "hello") {
macro = "bye";
} else if (macro == "bye") {
macro = "We meet again";
} else if (macro == "sideeffect") {
std::cerr << "this is a side effect while parsing\n";
macro = "(done)";
} else if (std::string::npos != macro.find('~')) {
std::reverse(macro.begin(), macro.end());
macro.erase(std::remove(macro.begin(), macro.end(), '~'));
} else {
macro = std::string("<<") + macro + ">>"; // this makes the unsupported macros appear unchanged
}

return true;
}

template<typename Iterator, typename OutIt>
struct skel_grammar : public qi::grammar<Iterator>
{
struct fastfwd {
template<typename,typename> struct result { typedef bool type; };

template<typename R, typename O>
bool operator()(const R&r,O& o) const
{
#ifndef SUPPORT_ESCAPES
o = std::copy(r.begin(),r.end(),o);
#else
auto f = std::begin(r), l = std::end(r);
while(f!=l)
{
if (('\\'==*f) && (l == ++f))
break;
*o++ = *f++;
}
#endif
return true; // false to fail the parse
}
} copy;

skel_grammar(OutIt& out) : skel_grammar::base_type(start)
{
using namespace qi;

#ifdef SUPPORT_ESCAPES
rawch = ('\\' >> char_) | char_;
#else
# define rawch qi::char_
#endif

macro = ("<<" >> (
(*(rawch - ">>" - "<<") [ _val += _1 ])
% macro [ _val += _1 ] // allow nests
) >>
">>")
[ _pass = phx::bind(process, _val) ];

start =
raw [ +(rawch - "<<") ] [ _pass = phx::bind(copy, _1, phx::ref(out)) ]
% macro [ _pass = phx::bind(copy, _1, phx::ref(out)) ]
;

BOOST_SPIRIT_DEBUG_NODE(start);
BOOST_SPIRIT_DEBUG_NODE(macro);

# undef rawch
}

private:
#ifdef SUPPORT_ESCAPES
qi::rule<Iterator, char()> rawch;
#endif
qi::rule<Iterator, std::string()> macro;
qi::rule<Iterator> start;
};
}

int main(int argc, char* argv[])
{
std::string input =
"Greeting is <<hello>> world!\n"
"Side effects are <<sideeffect>> and <<other>> vars are untouched\n"
"Empty <<>> macros are ok, as are stray '>>' pairs.\n"
"<<nested <<macros>> (<<hello>>?) work>>\n"
"The order of expansion (evaluation) is _eager_: '<<<<hello>>>>' will expand to the same as '<<bye>>'\n"
"Lastly you can do algorithmic stuff too: <<!esrever ~ni <<hello>>>>\n"
#ifdef SUPPORT_ESCAPES // bonus: escapes
"You can escape \\<<hello>> (not expanded to '<<hello>>')\n"
"Demonstrate how it <<avoids <\\<nesting\\>> macros>>.\n"
#endif
;

std::ostringstream oss;
std::ostream_iterator<char> out(oss);

skel_grammar<std::string::iterator, std::ostream_iterator<char> > grammar(out);

std::string::iterator f(input.begin()), l(input.end());
bool r = qi::parse(f, l, grammar);

std::cout << "parse result: " << (r?"success":"failure") << "\n";
if (f!=l)
std::cout << "unparsed remaining: '" << std::string(f,l) << "'\n";

std::cout << "Streamed output:\n\n" << oss.str() << '\n';

return 0;
}

The Test Output

this is a side effect while parsing
parse result: success
Streamed output:

Greeting is bye world!
Side effects are (done) and <<other>> vars are untouched
Empty <<>> macros are ok, as are stray '>>' pairs.
<<nested <<macros>> (bye?) work>>
The order of expansion (evaluation) is _eager_: 'We meet again' will expand to the same as 'We meet again'
Lastly you can do algorithmic stuff too: eyb in reverse!
You can escape <<hello>> (not expanded to 'bye')
Demonstrate how it <<avoids <<nesting>> macros>>.

There is quite a lot of functionality hidden there to grok. I suggest you look at the test cases and the process() callback alongside each other to see what is going on.

Cheers & HTH :)

Compile Boost Spirit Keyword Parser Example

I believe I have the answer I tried compiling with g++ -I boost-1.63.0/include -DBOOST_RESULT_OF_USE_TR1 ~/keywords.cpp and it compiles. This solution was found in a bug report here: https://svn.boost.org/trac/boost/ticket/11493

Parsing a command language using Boost Spirit

I'm going to side-step the majority of your code, for the simple reasons that experience tells me that Lex and utree are generally not what you want to use.

What you do want is define an AST to represent your command language and then come up with a grammar to build it.

AST

namespace Ast {
struct NoValue {
bool operator==(NoValue const &) const { return true; }
};
template <typename Tag> struct GenericCommand {};

namespace tag {
struct boot;
struct help;
struct load;
struct exit;
struct set;
struct show;
};

template <> struct GenericCommand<tag::load> { std::string name; };

template <> struct GenericCommand<tag::set> {
std::string property;
boost::variant<NoValue, std::string, bool> value; // optional
};

using BootCmd = GenericCommand<tag::boot>;
using HelpCmd = GenericCommand<tag::help>;
using ExitCmd = GenericCommand<tag::exit>;
using ShowCmd = GenericCommand<tag::show>;
using LoadCmd = GenericCommand<tag::load>;
using SetCmd = GenericCommand<tag::set>;

using Command = boost::variant<BootCmd, HelpCmd, ExitCmd, ShowCmd, LoadCmd, SetCmd>;
using Commands = std::list<Command>;
}

The full code only adds debug output helpers. And here's the full Fusion Adaption:

BOOST_FUSION_ADAPT_TPL_STRUCT((Tag), (Ast::GenericCommand) (Tag), )
BOOST_FUSION_ADAPT_STRUCT(Ast::LoadCmd, name)
BOOST_FUSION_ADAPT_STRUCT(Ast::SetCmd, property, value)

Grammar

Here I make some choices:

  • let's make things white-space and case insensitive, allowing line-separated commands: (see also Boost spirit skipper issues)

    start = skip(blank) [lazy_command % eol];
  • let's use Nabialek Trick to associate commands with prefixes. I used a very simple snippet of code to generate the unique prefixes:

    std::set<std::string> const verbs { "boot", "exit", "help", "-help", "/help", "load", "quit", "set", "show", };
    for (auto const full : verbs)
    for (auto partial=full; partial.length(); partial.resize(partial.size()-1)) {
    auto n = std::distance(verbs.lower_bound(partial), verbs.upper_bound(full));
    if (n < 2) std::cout << "(\"" << partial << "\", &" << full << "_command)\n";
    }
  • you could do the same for properties, but I thought the current setup is simpler:

template <typename Iterator>
struct command_grammar : qi::grammar<Iterator, Ast::Commands()> {
command_grammar() : command_grammar::base_type(start) {
using namespace qi;

start = skip(blank) [lazy_command % eol];

// nabialek trick
lazy_command = no_case [ commands [ _a = _1 ] > lazy(*_a) [ _val = _1 ] ];

on_off.add("on", true)("off", false);

commands.add
("-help", &help_command) ("-hel", &help_command) ("-he", &help_command) ("-h", &help_command)
("/help", &help_command) ("/hel", &help_command) ("/he", &help_command) ("/h", &help_command)
("help", &help_command) ("hel", &help_command) ("he", &help_command) ("h", &help_command)
("boot", &boot_command) ("boo", &boot_command) ("bo", &boot_command) ("b", &boot_command)
("exit", &exit_command) ("exi", &exit_command) ("ex", &exit_command) ("e", &exit_command)
("quit", &exit_command) ("qui", &exit_command) ("qu", &exit_command) ("q", &exit_command)
("load", &load_command) ("loa", &load_command) ("lo", &load_command) ("l", &load_command)
("set", &set_command) ("se", &set_command)
("show", &show_command) ("sho", &show_command) ("sh", &show_command);

quoted_string = '"' >> +~char_('"') >> '"';

// nullary commands
boot_command_ = eps;
exit_command_ = eps;
help_command_ = eps;
show_command_ = eps;

// non-nullary commands
load_command_ = quoted_string;
drive_ = char_("A-Z") >> ':';
set_command_ = no_case[lit("drive")|"driv"|"dri"|"dr"] >> attr("DRIVE") >> drive_
| no_case[ (lit("debug")|"debu"|"deb"|"de") >> attr("DEBUG") >> on_off ]
| no_case[ (lit("trace")|"trac"|"tra"|"tr"|"t") >> attr("TRACE") >> on_off ]
;

BOOST_SPIRIT_DEBUG_NODES(
(start)(lazy_command)
(boot_command) (exit_command) (help_command) (show_command) (set_command) (load_command)
(boot_command_)(exit_command_)(help_command_)(show_command_)(set_command_)(load_command_)
(quoted_string)(drive_)
)

on_error<fail>(start, error_handler_(_4, _3, _2));
on_error<fail>(lazy_command, error_handler_(_4, _3, _2));
boot_command = boot_command_;
exit_command = exit_command_;
help_command = help_command_;
load_command = load_command_;
exit_command = exit_command_;
set_command = set_command_;
show_command = show_command_;
}

private:
struct error_handler_t {
template <typename...> struct result { typedef void type; };

void operator()(qi::info const &What, Iterator Err_pos, Iterator Last) const {
std::cout << "Error! Expecting " << What << " here: \"" << std::string(Err_pos, Last) << "\"" << std::endl;
}
};

boost::phoenix::function<error_handler_t> const error_handler_ = error_handler_t {};

qi::rule<Iterator, Ast::Commands()> start;

using Skipper = qi::blank_type;
using CommandRule = qi::rule<Iterator, Ast::Command(), Skipper>;

qi::symbols<char, bool> on_off;
qi::symbols<char, CommandRule const*> commands;

qi::rule<Iterator, std::string()> drive_property, quoted_string, drive_;

qi::rule<Iterator, Ast::Command(), Skipper, qi::locals<CommandRule const*> > lazy_command;
CommandRule boot_command, exit_command, help_command, load_command, set_command, show_command;

qi::rule<Iterator, Ast::BootCmd(), Skipper> boot_command_;
qi::rule<Iterator, Ast::ExitCmd(), Skipper> exit_command_;
qi::rule<Iterator, Ast::HelpCmd(), Skipper> help_command_;
qi::rule<Iterator, Ast::LoadCmd(), Skipper> load_command_;
qi::rule<Iterator, Ast::SetCmd(), Skipper> set_command_;
qi::rule<Iterator, Ast::ShowCmd(), Skipper> show_command_;
};

Test Cases

Live On Coliru

int main() {
typedef std::string::const_iterator It;
command_grammar<It> const commands;

for (std::string const input : {
"help",
"set drive C:",
"SET DRIVE C:",
"loAD \"XYZ\"",
"load \"anything \nat all\"",
// multiline
"load \"ABC\"\nhelp\n-he\n/H\nsh\nse t off\nse debug ON\nb\nq"
})
{
std::cout << "----- '" << input << "' -----\n";
It f = input.begin(), l = input.end();

Ast::Commands parsed;
bool result = parse(f, l, commands, parsed);

if (result) {
for (auto& cmd : parsed) {
std::cout << "Parsed " << cmd << "\n";
}
} else {
std::cout << "Parse failed\n";
}

if (f != l) {
std::cout << "Remaining unparsed '" << std::string(f, l) << "'\n";
}
}
}

Prints:

----- 'help' -----
Parsed HELP ()
----- 'set drive C:' -----
Parsed SET (DRIVE C)
----- 'SET DRIVE C:' -----
Parsed SET (DRIVE C)
----- 'loAD "XYZ"' -----
Parsed LOAD (XYZ)
----- 'load "anything
at all"' -----
Parsed LOAD (anything
at all)
----- 'load "ABC"
help
-he
/H
sh
se t off
se debug ON
b
q' -----
Parsed LOAD (ABC)
Parsed HELP ()
Parsed HELP ()
Parsed HELP ()
Parsed SHOW ()
Parsed SET (TRACE 0)
Parsed SET (DEBUG 1)
Parsed BOOT ()
Parsed EXIT ()

Full Listing

Live On Coliru

//#define BOOST_SPIRIT_DEBUG
#include <boost/fusion/include/io.hpp>
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;

namespace Ast {
struct NoValue {
bool operator==(NoValue const &) const { return true; }
friend std::ostream& operator<<(std::ostream& os, NoValue) { return os; }
};
template <typename Tag> struct GenericCommand {};

namespace tag {
struct boot {};
struct help {};
struct load {};
struct exit {};
struct set {};
struct show {};

static std::ostream& operator<<(std::ostream& os, boot) { return os << "BOOT"; }
static std::ostream& operator<<(std::ostream& os, help) { return os << "HELP"; }
static std::ostream& operator<<(std::ostream& os, load) { return os << "LOAD"; }
static std::ostream& operator<<(std::ostream& os, exit) { return os << "EXIT"; }
static std::ostream& operator<<(std::ostream& os, set ) { return os << "SET"; }
static std::ostream& operator<<(std::ostream& os, show) { return os << "SHOW"; }
};

template <> struct GenericCommand<tag::load> { std::string name; };

template <> struct GenericCommand<tag::set> {
std::string property;
boost::variant<NoValue, std::string, bool> value; // optional
};

using BootCmd = GenericCommand<tag::boot>;
using HelpCmd = GenericCommand<tag::help>;
using ExitCmd = GenericCommand<tag::exit>;
using ShowCmd = GenericCommand<tag::show>;
using LoadCmd = GenericCommand<tag::load>;
using SetCmd = GenericCommand<tag::set>;

using Command = boost::variant<BootCmd, HelpCmd, ExitCmd, ShowCmd, LoadCmd, SetCmd>;
using Commands = std::list<Command>;

template <typename Tag>
static inline std::ostream& operator<<(std::ostream& os, Ast::GenericCommand<Tag> const& command) {
return os << Tag{} << " " << boost::fusion::as_vector(command);
}
}

BOOST_FUSION_ADAPT_TPL_STRUCT((Tag), (Ast::GenericCommand) (Tag), )
BOOST_FUSION_ADAPT_STRUCT(Ast::LoadCmd, name)
BOOST_FUSION_ADAPT_STRUCT(Ast::SetCmd, property, value)

template <typename Iterator>
struct command_grammar : qi::grammar<Iterator, Ast::Commands()> {
command_grammar() : command_grammar::base_type(start) {
using namespace qi;

start = skip(blank) [lazy_command % eol];

// nabialek trick
lazy_command = no_case [ commands [ _a = _1 ] > lazy(*_a) [ _val = _1 ] ];

on_off.add("on", true)("off", false);

commands.add
("-help", &help_command) ("-hel", &help_command) ("-he", &help_command) ("-h", &help_command)
("/help", &help_command) ("/hel", &help_command) ("/he", &help_command) ("/h", &help_command)
("help", &help_command) ("hel", &help_command) ("he", &help_command) ("h", &help_command)
("boot", &boot_command) ("boo", &boot_command) ("bo", &boot_command) ("b", &boot_command)
("exit", &exit_command) ("exi", &exit_command) ("ex", &exit_command) ("e", &exit_command)
("quit", &exit_command) ("qui", &exit_command) ("qu", &exit_command) ("q", &exit_command)
("load", &load_command) ("loa", &load_command) ("lo", &load_command) ("l", &load_command)
("set", &set_command) ("se", &set_command)
("show", &show_command) ("sho", &show_command) ("sh", &show_command);

quoted_string = '"' >> +~char_('"') >> '"';

// nullary commands
boot_command_ = eps;
exit_command_ = eps;
help_command_ = eps;
show_command_ = eps;

// non-nullary commands
load_command_ = quoted_string;
drive_ = char_("A-Z") >> ':';
set_command_ = no_case[lit("drive")|"driv"|"dri"|"dr"] >> attr("DRIVE") >> drive_
| no_case[ (lit("debug")|"debu"|"deb"|"de") >> attr("DEBUG") >> on_off ]
| no_case[ (lit("trace")|"trac"|"tra"|"tr"|"t") >> attr("TRACE") >> on_off ]
;

BOOST_SPIRIT_DEBUG_NODES(
(start)(lazy_command)
(boot_command) (exit_command) (help_command) (show_command) (set_command) (load_command)
(boot_command_)(exit_command_)(help_command_)(show_command_)(set_command_)(load_command_)
(quoted_string)(drive_)
)

on_error<fail>(start, error_handler_(_4, _3, _2));
on_error<fail>(lazy_command, error_handler_(_4, _3, _2));
boot_command = boot_command_;
exit_command = exit_command_;
help_command = help_command_;
load_command = load_command_;
exit_command = exit_command_;
set_command = set_command_;
show_command = show_command_;
}

private:
struct error_handler_t {
template <typename...> struct result { typedef void type; };

void operator()(qi::info const &What, Iterator Err_pos, Iterator Last) const {
std::cout << "Error! Expecting " << What << " here: \"" << std::string(Err_pos, Last) << "\"" << std::endl;
}
};

boost::phoenix::function<error_handler_t> const error_handler_ = error_handler_t {};

qi::rule<Iterator, Ast::Commands()> start;

using Skipper = qi::blank_type;
using CommandRule = qi::rule<Iterator, Ast::Command(), Skipper>;

qi::symbols<char, bool> on_off;
qi::symbols<char, CommandRule const*> commands;

qi::rule<Iterator, std::string()> drive_property, quoted_string, drive_;

qi::rule<Iterator, Ast::Command(), Skipper, qi::locals<CommandRule const*> > lazy_command;
CommandRule boot_command, exit_command, help_command, load_command, set_command, show_command;

qi::rule<Iterator, Ast::BootCmd(), Skipper> boot_command_;
qi::rule<Iterator, Ast::ExitCmd(), Skipper> exit_command_;
qi::rule<Iterator, Ast::HelpCmd(), Skipper> help_command_;
qi::rule<Iterator, Ast::LoadCmd(), Skipper> load_command_;
qi::rule<Iterator, Ast::SetCmd(), Skipper> set_command_;
qi::rule<Iterator, Ast::ShowCmd(), Skipper> show_command_;
};

int main() {
typedef std::string::const_iterator It;
command_grammar<It> const commands;

for (std::string const input : {
"help",
"set drive C:",
"SET DRIVE C:",
"loAD \"XYZ\"",
"load \"anything \nat all\"",
// multiline
"load \"ABC\"\nhelp\n-he\n/H\nsh\nse t off\nse debug ON\nb\nq"
})
{
std::cout << "----- '" << input << "' -----\n";
It f = input.begin(), l = input.end();

Ast::Commands parsed;
bool result = parse(f, l, commands, parsed);

if (result) {
for (auto& cmd : parsed) {
std::cout << "Parsed " << cmd << "\n";
}
} else {
std::cout << "Parse failed\n";
}

if (f != l) {
std::cout << "Remaining unparsed '" << std::string(f, l) << "'\n";
}
}
}

POST-SCRIPT

Q. How do I annotate the parser to create an AST using utree?

  • See above

Q. How do I walk the utree after it is built, to discover what was parsed?

  • See above, see also http://www.boost.org/doc/libs/1_64_0/doc/html/variant/tutorial.html

Q. I want to add a comment character, "!". So, how can I ignore everything after that - except when it occurs in a quoted string?

  • Simply make the Skipper type a rule that parses e.g.:

    qi::rule<Iterator> my_skipper;
    my_skipper = blank | '!' >> *(char_ - eol) >> (eol|eoi);

    Then use it instead of skip(blank) like skip(my_skipper)

Q. Why doesn't my error handler get called when I give it invalid input?

  • Because you didn't mark expectation points (operator> instead of operator>>). If you don't, a failure to match a sub-expression simply backtracks.

Q. How can I make the command tokens case insensitive, but not change the contents of a quoted string?

  • See above

Embedding a boost-spirit grammar parsing into a struct into another grammar gives compilation errors

Here is an even shorter example, demonstrating the same problem (compiler explorer):

#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/spirit/include/qi.hpp>

#include <vector>
#include <tuple>

namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;

void test()
{
using Iterator = std::string::const_iterator;

// OK
qi::rule<Iterator, std::vector<int>(), ascii::space_type> vecI_src;
qi::rule<Iterator, std::vector<int>(), ascii::space_type> vecI_dst = *vecI_src;

// error: no matching function for call to 'std::tuple<int, float>::tuple(const std::vector<std::tuple<int, float> >&)'
qi::rule<Iterator, std::vector<std::tuple<int, float>>(), ascii::space_type> vecT_src;
qi::rule<Iterator, std::vector<std::tuple<int, float>>(), ascii::space_type> vecT_dst = *vecT_src;
}

I think, the problem is, that vectors and tuples are handles quite similarly in the underlying boost::fusion library, so when it comes to flattening the vector, boost::fusion overshoots the goal and assignment fails. (Possibly by some kind of SFINAE mechanism.) Now, that flattening the vector does not work, the right-hand-side tuple parser's synthesized attribute is of type vector<vector<tuple<int, float>>>, as opposed to the expected vector<tuple<int, float>>.

Knowing this, the (not very pretty) solution I've found (for the original example) is to manually create assignment function overloads for both expected forms:

  static
void flattenAndAppend(std::vector<Element>& into,
std::vector<std::vector<Element>> const& vector)
{
for(auto const& subvector: vector)
{
into.insert(into.end(), subvector.begin(), subvector.end());
}
}

static
void flattenAndAppend(std::vector<Element>& into,
std::vector<Element> const& vector)
{
into.insert(into.end(), vector.begin(), vector.end());
}

and call these in a semantic action via a boost::phoenix function:

    ph::function append = [](auto& into,
auto const& a1)
{
flattenAndAppend(into, a1);
};

sequence = (simpleVector % ',')[append(qi::_val, ql::_1)];

Here is the whole working example (compiler explorer):

#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>

#include <vector>
#include <tuple>

namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;
namespace ql = qi::labels;
namespace ph = boost::phoenix;

struct Struct1
{
float f;
};
BOOST_FUSION_ADAPT_STRUCT(
Struct1,
(float, f))

struct Struct2
{
float f;
int i;
};
BOOST_FUSION_ADAPT_STRUCT(
Struct2,
(float, f)
(int, i))

template<typename Iterator,
typename Result>
class ElementParser : public qi::grammar<Iterator, Result(), ascii::space_type>
{
public:
using ValueType = Result;

ElementParser() : ElementParser::base_type(element) {}

private:
qi::rule<Iterator, Result(), ascii::space_type> element;
};

template<typename Iterator>
class Struct2Tuple : public qi::grammar<Iterator, std::tuple<float, int>(), ascii::space_type>
{
public:
using ValueType = std::tuple<float, int>;

Struct2Tuple() : Struct2Tuple::base_type(tupleElement)
{
ph::function convert = [](auto const& s,
auto& t)
{
t = std::make_tuple(s.f, s.i);
};

tupleElement = structElement[convert(ql::_1, qi::_val)];
}

private:
qi::rule<Iterator, ValueType(), ascii::space_type> tupleElement;
ElementParser<Iterator, Struct2> structElement;
};

template<typename Iterator,
typename ElementParser,
typename Element = typename ElementParser::ValueType>
class SP : public qi::grammar<Iterator, std::vector<Element>(), ascii::space_type>
{
private:
static
void flattenAndAppend(std::vector<Element>& into,
std::vector<std::vector<Element>> const& vector)
{
for(auto const& subvector: vector)
{
into.insert(into.end(), subvector.begin(), subvector.end());
}
}

static
void flattenAndAppend(std::vector<Element>& into,
std::vector<Element> const& vector)
{
into.insert(into.end(), vector.begin(), vector.end());
}

public:
SP()
: SP::base_type(sequence)
{
ph::function append = [](auto& into,
auto const& a1)
{
flattenAndAppend(into, a1);
};

sequence = (simpleVector % ',')[append(qi::_val, ql::_1)];
simpleVector = qi::repeat(1)[simple];
}

private:
using Rule = qi::rule<Iterator, std::vector<Element>(), ascii::space_type>;
Rule sequence;
Rule simpleVector;
ElementParser simple;
};


Related Topics



Leave a reply



Submit