Boost Spirit Skipper Issues

Boost spirit skipper issues

In general the following directives are helpful for inhibiting/switching skippers mid-grammar:

  • qi::lexeme [ p ]
    which inhibits a skipper, e.g. if you want to be sure you parse an identifier without internal skips) - see also no_skip for comparison

  • qi::raw [ p ]
    which parses like always, including skips, but returns the raw iterator range of the matched source sequence (including the skipped positions)

  • qi::no_skip [ p ]
    Inhibiting Skipping Without Pre-skip (I've created a minimal example to demonstrate the difference here: Boost Spirit lexeme vs no_skip)

  • qi::skip(s) [ p ]
    which replaces the skipper by another skipper s altogether (note that you need to use appropriately declared qi::rule<> instances inside such a skip[] clause)

where p is any parser expression.

Specific solution

Your problem, as you already know, might be that qi::space eats all whitespace. I can't possibly know what is wrong in your grammar (since you don't show either the full grammar, or relevant input).

Therefore, here's what I'd write. Note

  • the use of qi::eol to explicitely require linebreaks at specific locations
  • the use of qi::blank as a skipper (not including eol)
  • for brevity I combined the grammars

Code:

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

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

struct rowType {
unsigned int number;
std::list<unsigned int> list;
};

struct problemType {
unsigned int ROW;
std::vector<rowType> rows;
};

BOOST_FUSION_ADAPT_STRUCT(rowType, (unsigned int, number)(std::list<unsigned int>, list))
BOOST_FUSION_ADAPT_STRUCT(problemType, (unsigned int, ROW)(std::vector<rowType>, rows))

template<typename Iterator>
struct problem_parser : qi::grammar<Iterator,problemType(),qi::blank_type>
{
problem_parser() : problem_parser::base_type(problem)
{
using namespace qi;
list = '[' >> -(int_ % ',') >> ']';
row = int_ >> list >> eol;
problem = "ROW" >> int_ >> eol >> +row;

BOOST_SPIRIT_DEBUG_NODES((problem)(row)(list));
}

qi::rule<Iterator, problemType() , qi::blank_type> problem;
qi::rule<Iterator, rowType() , qi::blank_type> row;
qi::rule<Iterator, std::list<unsigned int>(), qi::blank_type> list;
};

int main()
{
const std::string input =
"ROW 1\n"
"2 [3, 4]\n"
"5 [6, 7]\n";

auto f = begin(input), l = end(input);

problem_parser<std::string::const_iterator> p;
problemType data;

bool ok = qi::phrase_parse(f, l, p, qi::blank, data);

if (ok) std::cout << "success\n";
else std::cout << "failed\n";

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

If you really didn't want to require line breaks:

template<typename Iterator>
struct problem_parser : qi::grammar<Iterator,problemType(),qi::space_type>
{
problem_parser() : problem_parser::base_type(problem)
{
using namespace qi;
list = '[' >> -(int_ % ',') >> ']';
row = int_ >> list;
problem = "ROW" >> int_ >> +row;

BOOST_SPIRIT_DEBUG_NODES((problem)(row)(list));
}

qi::rule<Iterator, problemType() , qi::space_type> problem;
qi::rule<Iterator, rowType() , qi::space_type> row;
qi::rule<Iterator, std::list<unsigned int>(), qi::space_type> list;
};

int main()
{
const std::string input =
"ROW 1 " // NOTE whitespace, obviously required!
"2 [3, 4]"
"5 [6, 7]";

auto f = begin(input), l = end(input);

problem_parser<std::string::const_iterator> p;
problemType data;

bool ok = qi::phrase_parse(f, l, p, qi::space, data);

if (ok) std::cout << "success\n";
else std::cout << "failed\n";

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

Update

In response to the comment: here is a snippet that shows how to read the input from a file. This was tested and works fine for me:

std::ifstream ifs("input.txt"/*, std::ios::binary*/);
ifs.unsetf(std::ios::skipws);

boost::spirit::istream_iterator f(ifs), l;

problem_parser<boost::spirit::istream_iterator> p;

Boost Spirit X3: skip parser that would do nothing

You can use either no_skip[] or lexeme[]. They're almost identical, except for pre-skip (Boost Spirit lexeme vs no_skip).

Are there no skip parsers that would simply do nothing? Am I missing something?

A wild guess, but you might be missing the parse API that doesn't accept a skipper in the first place

Live On Coliru

#include <iostream>
#include <iomanip>
#include <boost/spirit/home/x3.hpp>
namespace x3 = boost::spirit::x3;

int main() {
std::string const input{ "2,4,5" };
auto f = begin(input), l = end(input);

const auto parser = x3::int_ % ',';
std::vector<int> numbers;

auto r = parse(f, l, parser, numbers);

if (r) {
// success
for (const auto& item : numbers)
std::cout << item << std::endl;
} else {
std::cerr << "Input was not parsed successfully" << std::endl;
return 1;
}

if (f!=l) {
std::cout << "Remaining input " << std::quoted(std::string(f,l)) << "\n";
return 2;
}
}

Prints

2
4
5

Boost spirit skip parser with at least one whitespace

First off, the grammar specs I usually see this kind of requirement in are (always?) RFCs. In 99% of cases there is no issue, consider e.g.:

 myrule = skip(space) [ uint_ >> uint_ ];

This already implicitly requires at least 1 whitespace character between the numbers, for the simple reason that there would be 1 number, otherwise. The same simplification occurs in surprisingly many cases (see e.g. the simplifications made around the ubiquitous WSP productions in this answer last week Boost.Spirit qi value sequence vector).


With that out of the way, skippers apply zero or more times, by definition, so no there is not a way to get what you want with an existing stateful directive like skip(). See also http://stackoverflow.com/questions/17072987/boost-spirit-skipper-issues/17073965#17073965 or the docs - under lexeme, [no_]skip and skip_flag::dont_postskip).


Looking at your specific grammar, I'd do this:

bool r = qi::phrase_parse(iter, end, token >> token, qi::blank);

Here, you can add a negative lookahead assertion inside a lexeme to assert that "the end of the token was reached" - which in your parser would be mandated as !qi::graph:

    auto token = qi::copy(qi::lexeme [ qi::char_ >> !qi::graph ]);

See a demo:

Live On Coliru

#include <iostream>
#include <iomanip>
#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;

int main() {
for (std::string const str : { "ab", " ab ", " a b ", "a b" }) {
auto iter = str.begin(), end = str.end();

auto token = qi::copy(qi::lexeme [ qi::char_ >> !qi::graph ]);

bool r = qi::phrase_parse(iter, end, token >> token, qi::blank);

std::cout << " --- " << std::quoted(str) << " --- ";
if (r) {
std::cout << "parse succeeded.";
} else {
std::cout << "parse failed.";
}

if (iter != end) {
std::cout << " Remaining unparsed: " << std::string(iter, str.end());
}

std::cout << std::endl;
}
}

Prints

 --- "ab" --- parse failed. Remaining unparsed: ab
--- " ab " --- parse failed. Remaining unparsed: ab
--- " a b " --- parse succeeded.
--- "a b" --- parse succeeded.

BONUS Review notes

My guidelines would be:

  1. your skipper should be the grammar's responsibility. It's sad that all Qi samples lead people to believe you need to let the caller decide that
  2. end-iterator checking does not equal error-checking. It's very possible to parse things correctly without consuming all input. Which is why reporting the "remaining input" should not just happen in the case that parsing failed.
  3. If trailing unparsed input is an error, spell it out:

Live On Coliru

#include <iostream>
#include <iomanip>
#include <boost/spirit/include/qi.hpp>

namespace qi = boost::spirit::qi;

int main() {
for (std::string const str : { "ab", " ab ", " a b ", "a b happy trees are trailing" }) {
auto iter = str.begin(), end = str.end();

auto token = qi::copy(qi::lexeme [ qi::char_ >> !qi::graph ]);

bool r = qi::parse(iter, end, qi::skip(qi::space) [ token >> token >> qi::eoi ]);

std::cout << " --- " << std::quoted(str) << " --- ";
if (r) {
std::cout << "parse succeeded.";
} else {
std::cout << "parse failed.";
}

if (iter != end) {
std::cout << " Remaining unparsed: " << std::quoted(std::string(iter, str.end()));
}

std::cout << std::endl;
}
}

Prints

 --- "ab" --- parse failed. Remaining unparsed: "ab"
--- " ab " --- parse failed. Remaining unparsed: " ab "
--- " a b " --- parse succeeded.
--- "a b happy trees are trailing" --- parse failed. Remaining unparsed: "a b happy trees are trailing"

boost spirit parsing with no skipper

Indeed this should be a lot simpler.

First off, I fail to see why the absense of a skipper is at all relevant.

Second, exposing the raw input is best done using qi::raw[] instead of dancing with iter_pos and clumsy semantic actions¹.

Among the other observations I see:

  • negating a charset is done with ~, so e.g. ~char_(",()")
  • (p|eps) would be better spelled -p
  • (lit('(') >> lit(')')) could be just "()" (after all, there's no skipper, right)
  • p >> *(',' >> p) is equivalent to p % ','
  • With the above, resolve_para simplifies to this:

    resolve_para = '(' >> -(resolve_para_entry % ',') >> ')';
  • resolve_para_entry seems weird, to me. It appears that any nested parentheses are simply swallowed. Why not actually parse a recursive grammar so you detect syntax errors?


Here's my take on it:

Define An AST

I prefer to make this the first step because it helps me think about the parser productions:

namespace Ast {

using ArgList = std::list<std::string>;

struct Resolve {
std::string name;
ArgList arglist;
};

using Resolves = std::vector<Resolve>;
}

Creating The Grammar Rules

qi::rule<It, Ast::Resolves()> start;
qi::rule<It, Ast::Resolve()> resolve;
qi::rule<It, Ast::ArgList()> arglist;
qi::rule<It, std::string()> arg, identifier;

And their definitions:

identifier = char_("a-zA-Z_") >> *char_("a-zA-Z0-9_");

arg = raw [ +('(' >> -arg >> ')' | +~char_(",)(")) ];
arglist = '(' >> -(arg % ',') >> ')';
resolve = identifier >> arglist;

start = *qr::seek[hold[resolve]];

Notes:

  • No more semantic actions
  • No more eps
  • No more iter_pos
  • I've opted to make arglist not-optional. If you really wanted that, change it back:

    resolve    = identifier >> -arglist;

    But in our sample it will generate a lot of noisy output.

  • Of course your entry point (start) will be different. I just did the simplest thing that could possibly work, using another handy parser directive from the Spirit Repository (like iter_pos that you were already using): seek[]

  • The hold is there for this reason: boost::spirit::qi duplicate parsing on the output - You might not need it in your actual parser.

Live On Coliru

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/repository/include/qi_seek.hpp>

namespace Ast {

using ArgList = std::list<std::string>;

struct Resolve {
std::string name;
ArgList arglist;
};

using Resolves = std::vector<Resolve>;
}

BOOST_FUSION_ADAPT_STRUCT(Ast::Resolve, name, arglist)

namespace qi = boost::spirit::qi;
namespace qr = boost::spirit::repository::qi;

template <typename It>
struct Parser : qi::grammar<It, Ast::Resolves()>
{
Parser() : Parser::base_type(start) {
using namespace qi;

identifier = char_("a-zA-Z_") >> *char_("a-zA-Z0-9_");

arg = raw [ +('(' >> -arg >> ')' | +~char_(",)(")) ];
arglist = '(' >> -(arg % ',') >> ')';
resolve = identifier >> arglist;

start = *qr::seek[hold[resolve]];
}
private:
qi::rule<It, Ast::Resolves()> start;
qi::rule<It, Ast::Resolve()> resolve;
qi::rule<It, Ast::ArgList()> arglist;
qi::rule<It, std::string()> arg, identifier;
};

#include <iostream>

int main() {
using It = std::string::const_iterator;
std::string const samples = R"--(
Samples:

sometext(para) → expect para in the string list
sometext(para1,para2) → expect para1 and para2 in string list
sometext(call(a)) → expect call(a) in the string list
sometext(call(a,b)) ← here it fails; it seams that the "!lit(',')" wont make the parser step outside
)--";
It f = samples.begin(), l = samples.end();

Ast::Resolves data;
if (parse(f, l, Parser<It>{}, data)) {
std::cout << "Parsed " << data.size() << " resolves\n";

} else {
std::cout << "Parsing failed\n";
}

for (auto& resolve: data) {
std::cout << " - " << resolve.name << "\n (\n";
for (auto& arg : resolve.arglist) {
std::cout << " " << arg << "\n";
}
std::cout << " )\n";
}
}

Prints

Parsed 6 resolves
- sometext
(
para
)
- sometext
(
para1
para2
)
- sometext
(
call(a)
)
- call
(
a
)
- call
(
a
b
)
- lit
(
'
'
)

More Ideas

That last output shows you a problem with your current grammar: lit(',') should obviously not be seen as a call with two parameters.

I recently did an answer on extracting (nested) function calls with parameters which does things more neatly:

  • Boost spirit parse rule is not applied
  • or this one boost spirit reporting semantic error

BONUS

Bonus version that uses string_view and also shows exact line/column information of all extracted words.

Note that it still doesn't require any phoenix or semantic actions. Instead it simply defines the necesary trait to assign to boost::string_view from an iterator range.

Live On Coliru

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/repository/include/qi_seek.hpp>
#include <boost/utility/string_view.hpp>

namespace Ast {

using Source = boost::string_view;
using ArgList = std::list<Source>;

struct Resolve {
Source name;
ArgList arglist;
};

using Resolves = std::vector<Resolve>;
}

BOOST_FUSION_ADAPT_STRUCT(Ast::Resolve, name, arglist)

namespace boost { namespace spirit { namespace traits {
template <typename It>
struct assign_to_attribute_from_iterators<boost::string_view, It, void> {
static void call(It f, It l, boost::string_view& attr) {
attr = boost::string_view { f.base(), size_t(std::distance(f.base(),l.base())) };
}
};
} } }

namespace qi = boost::spirit::qi;
namespace qr = boost::spirit::repository::qi;

template <typename It>
struct Parser : qi::grammar<It, Ast::Resolves()>
{
Parser() : Parser::base_type(start) {
using namespace qi;

identifier = raw [ char_("a-zA-Z_") >> *char_("a-zA-Z0-9_") ];

arg = raw [ +('(' >> -arg >> ')' | +~char_(",)(")) ];
arglist = '(' >> -(arg % ',') >> ')';
resolve = identifier >> arglist;

start = *qr::seek[hold[resolve]];
}
private:
qi::rule<It, Ast::Resolves()> start;
qi::rule<It, Ast::Resolve()> resolve;
qi::rule<It, Ast::ArgList()> arglist;
qi::rule<It, Ast::Source()> arg, identifier;
};

#include <iostream>

struct Annotator {
using Ref = boost::string_view;

struct Manip {
Ref fragment, context;

friend std::ostream& operator<<(std::ostream& os, Manip const& m) {
return os << "[" << m.fragment << " at line:" << m.line() << " col:" << m.column() << "]";
}

size_t line() const {
return 1 + std::count(context.begin(), fragment.begin(), '\n');
}
size_t column() const {
return 1 + (fragment.begin() - start_of_line().begin());
}
Ref start_of_line() const {
return context.substr(context.substr(0, fragment.begin()-context.begin()).find_last_of('\n') + 1);
}
};

Ref context;
Manip operator()(Ref what) const { return {what, context}; }
};

int main() {
using It = std::string::const_iterator;
std::string const samples = R"--(Samples:

sometext(para) → expect para in the string list
sometext(para1,para2) → expect para1 and para2 in string list
sometext(call(a)) → expect call(a) in the string list
sometext(call(a,b)) ← here it fails; it seams that the "!lit(',')" wont make the parser step outside
)--";
It f = samples.begin(), l = samples.end();

Ast::Resolves data;
if (parse(f, l, Parser<It>{}, data)) {
std::cout << "Parsed " << data.size() << " resolves\n";

} else {
std::cout << "Parsing failed\n";
}

Annotator annotate{samples};

for (auto& resolve: data) {
std::cout << " - " << annotate(resolve.name) << "\n (\n";
for (auto& arg : resolve.arglist) {
std::cout << " " << annotate(arg) << "\n";
}
std::cout << " )\n";
}
}

Prints

Parsed 6 resolves
- [sometext at line:3 col:1]
(
[para at line:3 col:10]
)
- [sometext at line:4 col:1]
(
[para1 at line:4 col:10]
[para2 at line:4 col:16]
)
- [sometext at line:5 col:1]
(
[call(a) at line:5 col:10]
)
- [call at line:5 col:34]
(
[a at line:5 col:39]
)
- [call at line:6 col:10]
(
[a at line:6 col:15]
[b at line:6 col:17]
)
- [lit at line:6 col:62]
(
[' at line:6 col:66]
[' at line:6 col:68]
)

¹ Boost Spirit: "Semantic actions are evil"?

Skippers in Boost.Spirit.X3

You didn't actually show all declarations, so it's not completely clear how the setup is. So let me mock up something quick:

Live On Wandbox

#define BOOST_SPIRIT_X3_DEBUG
#include <iomanip>
#include <boost/spirit/home/x3.hpp>

namespace x3 = boost::spirit::x3;
namespace P {
using namespace x3;
static auto const comment = lexeme [
"/*" >> *(char_ - "*/") >> "*/"
| "//" >> *~char_("\r\n") >> eol
];

static auto const skipper = comment | blank;

static auto const property_type = lexeme["type"];
static auto const property_id = lexeme["id"];

auto const demo =
skip(skipper | eol) [
lit("#")
> property_type
> property_id
];
}

int main() {
for (std::string const input : {
"#type id",
"#type\nid",
})
{
std::cout << "==== " << std::quoted(input) << " ====" << std::endl;
auto f = begin(input), l = end(input);
if (parse(f, l, P::demo)) {
std::cout << "Parsed successfully" << std::endl;
} else {
std::cout << "Failed" << std::endl;
}

if (f!=l) {
std::cout << "Remaining input unparsed: " << std::quoted(std::string(f,l)) << std::endl;
}
}
}

As you can see there's not actually a problem unless the rule declarations get involved:

==== "#type id" ====
Parsed successfully
==== "#type
id" ====
Parsed successfully

Let's zoom in from here

static auto const demo_def =
skip(skipper | eol) [
lit("#")
> property_type
> property_id
];

static auto const demo = x3::rule<struct demo_> {"demo"} = demo_def;

Still OK: Live On Wandbox

<demo>
<try>#type id</try>
<success></success>
</demo>
<demo>
<try>#type\nid</try>
<success></success>
</demo>
Parsed successfully
==== "#type
id" ====
Parsed successfully

So, we know that x3::rule<> is not actually the issue. It's gonna be about the static dispatch based on the tag type (aka rule ID, I think, in this case struct demo_).

Doing the straight-forward:

static auto const demo_def =
skip(skipper | eol) [
lit("#")
> property_type
> property_id
];

static auto const demo = x3::rule<struct demo_> {"demo"};

BOOST_SPIRIT_DEFINE(demo)

Still OK: Live On Wandbox

Hmm what else could be wrong. Maybe if there are conflicing skipper contexts? Replacing

    if (parse(f, l, P::demo)) {

with

    if (phrase_parse(f, l, P::demo, P::skipper)) {

Still OK: Live On Wandbox

So, that's not it either. Ok, let's try the separate instantiation:

Separate Compilation

Live On Wandbox

  • rule.h

    #pragma once
    #define BOOST_SPIRIT_X3_DEBUG
    #include <boost/spirit/home/x3.hpp>
    #include <boost/spirit/home/x3/support/utility/error_reporting.hpp>

    namespace x3 = boost::spirit::x3;
    namespace P {
    using namespace x3;
    static auto const comment = lexeme [
    "/*" >> *(char_ - "*/") >> "*/"
    | "//" >> *~char_("\r\n") >> eol
    ];

    static auto const skipper = comment | blank;

    using demo_type = x3::rule<struct demo_>;
    extern demo_type const demo;

    BOOST_SPIRIT_DECLARE(demo_type)
    }
  • rule.cpp

    #include "rule.h"
    #include <iostream>
    #include <iomanip>

    namespace P {
    using namespace x3;

    static auto const property_type = lexeme["type"];
    static auto const property_id = lexeme["id"];

    static auto const demo_def =
    skip(skipper | eol) [
    lit("#")
    > property_type
    > property_id
    ];

    struct demo_ {
    template<typename It, typename Ctx>
    x3::error_handler_result on_error(It f, It l, expectation_failure<It> const& ef, Ctx const&) const {
    std::string s(f,l);
    auto pos = std::distance(f, ef.where());

    std::cout << "Expecting " << ef.which() << " at "
    << "\n\t" << s
    << "\n\t" << std::setw(pos) << std::setfill('-') << "" << "^\n";

    return error_handler_result::fail;
    }
    };

    demo_type const demo {"demo"};
    BOOST_SPIRIT_DEFINE(demo)

    // for non-skipper invocation (x3::parse)
    using iterator_type = std::string::const_iterator;
    BOOST_SPIRIT_INSTANTIATE(demo_type, iterator_type, x3::unused_type)

    // for skipper invocation (x3::phrase_parse)
    using skipper_type = decltype(skipper);
    using phrase_context_type = x3::phrase_parse_context<skipper_type>::type;
    BOOST_SPIRIT_INSTANTIATE(demo_type, iterator_type, phrase_context_type)
    }
  • test.cpp

    #include "rule.h"
    #include <iostream>
    #include <iomanip>

    int main() {
    std::cout << std::boolalpha;
    for (std::string const input : {
    "#type id",
    "#type\nid",
    })
    {
    std::cout << "\n==== " << std::quoted(input) << " ====" << std::endl;

    {
    auto f = begin(input), l = end(input);
    std::cout << "With top-level skipper: " << phrase_parse(f, l, P::demo, P::skipper) << std::endl;

    if (f!=l) {
    std::cout << "Remaining unparsed: " << std::quoted(std::string(f,l)) << std::endl;
    }
    }
    {
    auto f = begin(input), l = end(input);
    std::cout << "Without top-level skipper: " << parse(f, l, P::demo) << std::endl;

    if (f!=l) {
    std::cout << "Remaining unparsed: " << std::quoted(std::string(f,l)) << std::endl;
    }
    }
    }
    }

Prints the expected:

==== "#type id" ====
With top-level skipper: <demo>
<try>#type id</try>
<success></success>
</demo>
true
Without top-level skipper: <demo>
<try>#type id</try>
<success></success>
</demo>
true

==== "#type
id" ====
With top-level skipper: <demo>
<try>#type\nid</try>
<success></success>
</demo>
true
Without top-level skipper: <demo>
<try>#type\nid</try>
<success></success>
</demo>
true

Or, without debug enabled:

==== "#type id" ====
With top-level skipper: true
Without top-level skipper: true

==== "#type
id" ====
With top-level skipper: true
Without top-level skipper: true

FINAL THOUGHTS

Sadly, perhaps, I cannot reproduce the symptom you describe. However, I hope some of the steps above do clarify how separate linkage of rule-definition actually work with respect to the skipper/contexts.

If your situation is actually more complicated, I can only think of another situation where the X3 situation may be different from the QI situation. In Qi, a rule statically declared its skipper. In X3, the skipper is strictly from context (and the only way a rule can limit the number of supported skippers is by separating instantiation and hiding the definition in a separate TU).

This means that it is easy to accidentally inherit an overridden skipper. This can be counter-intuitive in e.g. nested rules. I'd suggest not relying on inherited skipper contexts at all if you have different skippers.

Skipper does not work in boost::spirit

I can't see what's wrong: I've taken the effort to reconstruct your SSCCE.

  • http://liveworkspace.org/code/1pDtmn$1

In the process, it seems I must have removed the problem. I suggest you do the same.

Oh, and this is how I'd write this:

  • no more phoenix
  • no more constructors
  • no more qi::locals
  • no more needless copying
  • using expectation points

In short: no more fuss.

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

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

typedef std::string::const_iterator StringIterator;

struct Color
{
float r,g,b,a;
};

BOOST_FUSION_ADAPT_STRUCT(Color, (float, r)(float, g)(float, b)(float, a))

template <typename ItType, typename Skipper>
struct ColorGrammar : public qi::grammar<StringIterator, Color(), Skipper>
{
ColorGrammar()
: ColorGrammar::base_type(rule_color, "color-grammar")
{
using namespace qi;

Related Topics



Leave a reply



Submit