How to Make a Recursive Rule in Boost Spirit X3 in VS2017

How to make a recursive rule in boost spirit x3 in VS2017

I checked out the Boost.Spirit repository on GitHub because my local version was too old and noticed that your example compiles fine with the latest develop branch but not with the 1.66.0 release (also on Clang and GCC). Bisecting the commit history revealed that this bug was fixed in

ee4943d5891bdae0706fb616b908e3bf528e0dfa

You could either apply the patch from this commit to your installation or wait for the next release.

Boost Spirit X3: “Attribute does not have the expected size” redux

Like in the other linked answers, the attribute propagation/compatibility rules aren't the same as in Qi.

Sometimes this is surprising, sometimes it might be a bug.

YOu can always make the attribute type explicit by wrapping in a rule. Almost all of the time this fixes the bug.

Note: in Boost 1.66.0 there is a regression that was fixed for later versions: How to make a recursive rule in boost spirit x3 in VS2017

In your case, the id rule don't magically assign to a string. Help it a bit:

auto const typ_id          = as<std::string>(x3::lexeme[ascii::lower >> *alpha_num]);
auto const con_id = as<std::string>(x3::lexeme[ascii::upper >> *alpha_num]);
auto const id = typ_id | con_id;

Where as is a simple helper:

template <typename T> static auto as = [](auto p) { return x3::rule<struct tag, T> {"as"} = p; };

Demo

Note that the code can be a bit simpler:

Live On Coliru

#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

namespace asdl { namespace ast {
struct Field {
std::string type;
boost::optional<char> flag;
boost::optional<std::string> name;
};

typedef std::vector<asdl::ast::Field> field_vec;

struct Constructor {
std::string name;
boost::optional<field_vec> fields;
};

struct Sum {
std::vector<Constructor> types;
boost::optional<field_vec> attributes;
};

struct Product {
field_vec fields;
};

typedef boost::variant<Product, Sum> type_variant;

struct Type {
std::string name;
type_variant value;
};

struct Module {
std::string id;
std::vector<Type> dfns;
};
} }

BOOST_FUSION_ADAPT_STRUCT(asdl::ast::Module, id, dfns)
BOOST_FUSION_ADAPT_STRUCT(asdl::ast::Type, name, value)
BOOST_FUSION_ADAPT_STRUCT(asdl::ast::Field, type, flag, name)
BOOST_FUSION_ADAPT_STRUCT(asdl::ast::Constructor, name, fields)
BOOST_FUSION_ADAPT_STRUCT(asdl::ast::Sum, types, attributes)
BOOST_FUSION_ADAPT_STRUCT(asdl::ast::Product, fields)

namespace asdl
{
namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;

using ast::type_variant;
using ast::field_vec;

x3::rule<class module, ast::Module > const module = "module";
x3::rule<class definitions, ast::Type > const definitions = "definitions";
x3::rule<class type, type_variant > const type = "type";
x3::rule<class fields, field_vec > const fields = "fields";
x3::rule<class field, ast::Field > const field = "field";
x3::rule<class product, ast::Product > const product = "product";
x3::rule<class sum, ast::Sum > const sum = "sum";
x3::rule<class constructor, ast::Constructor> const constructor = "constructor";

template <typename T> static auto as = [](auto p) { return x3::rule<struct tag, T> {"as"} = p; };

auto const alpha = '_' | ascii::upper | ascii::lower;
auto const alpha_num = alpha | ascii::digit;
auto const typ_id = as<std::string>(x3::lexeme[ascii::lower >> *alpha_num]);
auto const con_id = as<std::string>(x3::lexeme[ascii::upper >> *alpha_num]);
auto const id = typ_id | con_id;

auto const module_def = "module" >> id >> '{' >> *definitions >> '}';
auto const definitions_def = typ_id >> '=' >> type;
auto const type_def = product | sum;
auto const product_def = fields;
auto const sum_def = constructor % '|' >> -("attributes" >> fields);
auto const constructor_def = con_id >> -fields;
auto const fields_def = '(' >> field % ',' >> ')';
auto const field_def = typ_id >> -(ascii::char_('?') | ascii::char_('*')) >> -id;

BOOST_SPIRIT_DEFINE(module, definitions, type, product, sum, constructor, fields, field)

bool parse(const std::string &input, ast::Module &mod) {
std::string::const_iterator iter = input.begin();
std::string::const_iterator end = input.end();

return x3::phrase_parse(iter, end, module >> x3::eoi, ascii::space, mod);
}
} //namespace asdl

int main() {
asdl::ast::Module mod;
asdl::parse("", mod);
}

Since you're not using recursive rules, there's no actual need for _def and BOOST_SPIRIT_DEFINE etc:

Live On Coliru

namespace x3    = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;

using ast::type_variant;
using ast::field_vec;

template <typename T> static auto as = [](auto p) { return x3::rule<struct tag, T> {"as"} = p; };

auto const alpha = '_' | ascii::upper | ascii::lower;
auto const alpha_num = alpha | ascii::digit;
auto const typ_id = as<std::string>(x3::lexeme[ascii::lower >> *alpha_num]);
auto const con_id = as<std::string>(x3::lexeme[ascii::upper >> *alpha_num]);
auto const id = typ_id | con_id;

auto const field
= x3::rule<class constructor, ast::Field> {"field"}
= as<ast::Field>(typ_id >> -(ascii::char_('?') | ascii::char_('*')) >> -id);
auto const fields
= x3::rule<class sum, field_vec>{ "fields" }
= '(' >> field % ',' >> ')';
auto const constructor
= x3::rule<class product, ast::Constructor>{ "constructor" }
= as<ast::Constructor>(con_id >> -fields);
auto const sum
= x3::rule<class field, ast::Sum>{ "sum" }
= as<ast::Sum>(constructor % '|' >> -("attributes" >> fields));
auto const product
= x3::rule<class fields, ast::Product>{ "product" }
= as<ast::Product>(fields);
auto const type
= x3::rule<class type, type_variant>{ "type" }
= product | sum;
auto const definitions
= x3::rule<class definitions, ast::Type>{ "definitions" }
= as<ast::Type>(typ_id >> '=' >> type);
auto const module
= x3::rule<class module, ast::Module>{ "module" }
= "module" >> id >> '{' >> *definitions >> '}';

bool parse(const std::string &input, ast::Module &mod) {
std::string::const_iterator iter = input.begin();
std::string::const_iterator end = input.end();

return x3::phrase_parse(iter, end, module >> x3::eoi, ascii::space, mod);
}

Can spirit X3 work with BOOST_FUSION_ADAPT_ADT?

As I've been warning people before¹ you're pushing limits right on the intersection of things that frequently break Spirit's gears:

  1. single-element fusion sequences
  2. ADT adaptation in general
  3. Persistent bugs with ADT fixed in develop (after 1.67.0 release)

1. The Single-Element Conundrum

I won't spend much time on this because it's a rather old, boring, well-documented² and not essential to your question.

Let's side-step it by adding a dummy field:

struct rexpr
{
rexpr_map i_entries;

const rexpr_map& entries() const { return i_entries; }
rexpr_map& entries() { return i_entries; }

void entries(const rexpr_map& ent) { i_entries = ent; }

int i_dummy;
int dummy() const { return i_dummy; }
void dummy(int i) { i_dummy = i; }
};

// ... later:
BOOST_FUSION_ADAPT_ADT(client::ast::rexpr,
(obj.entries(), obj.entries(val))
(obj.dummy(), obj.dummy(val))
)

// ... even later:
auto const rexpr_def =
'{' >> *rexpr_key_value >> '}' >> x3::attr(42);

2. The ADT Proxy

The attribute-category machinery of Spirit detects the entries property as a container attribute (is_container<...>{} evaluates to true).

However the requisite container traits are not in place.

What's more, because of the restrictive interface that ADT proxies grant, the property values can only replaced whole-sale, meaning that we can only implement a very suboptimal version of it:

namespace boost { namespace spirit { namespace x3 { namespace traits {

template <typename T, auto... Other>
struct container_value<boost::fusion::extension::adt_attribute_proxy<T, Other...> >
: container_value<typename boost::fusion::extension::adt_attribute_proxy<T, Other...>::type>
{ };

template <typename T, auto... Other>
struct push_back_container<boost::fusion::extension::adt_attribute_proxy<T, Other...> >
{
using underlying_type = typename boost::fusion::extension::adt_attribute_proxy<T, Other...>::type;

template <typename X, typename Y>
static bool call(X& proxy, Y&& v) {
auto u = proxy.get();
bool b = push_back_container<underlying_type>::call(u, std::forward<Y>(v));
proxy = u;
return b;
}
};

} } } }

3. Surprise: Old bugs fixed after 1.67.0

You require the commits:

commit ae78e1ec2431517a8b0580099aeba8f9122d8abb
Author: Nikita Kniazev <nok.raven@gmail.com>
Date: Thu Mar 15 17:33:36 2018 +0300

X3: sequence: Fixed reference to temporary bug

commit e7f31017ec7c0b5584d12ec1b718d8c415b26fa1
Author: Nikita Kniazev <nok.raven@gmail.com>
Date: Wed Mar 14 18:54:35 2018 +0300

Qi: Fixed ADT support by permutation and sequence_or operator

This is follow-up to 0f2b3c49ce55a41a7d22cc5533e0f4ba59e491ae

These are more recent than 1.67.0 and currently in the develop branch. They (in part) fix an old issue: https://github.com/boostorg/spirit/pull/153#issuecomment-152879056. The current behaviour may also be impacted by commit

commit a0df3c098ff4e42c0958796c4f47d4d72a20c164
Merge: f73b121 fac9dfa
Author: Nikita Kniazev <nok.raven@gmail.com>
Date: Thu Mar 1 13:44:27 2018 +0300

Merge pull request #370 from Kojoley/x3-pass-container-attribute-through-sequence

X3: Pass container attribute through sequence

It's hard to gauge whether the impact is positive or negative in this ... turbulent landscape.

Demo

Suffice it to say that iff you

  1. compile against ae78e1ec243151 or later (develop)
  2. Apply both the workarounds described above

then I see the expected output:

-------------------------
Parsing succeeded
-------------------------
{
"color" = "blue"
"position" = {
"x" = "123"
"y" = "456"
}
"size" = "29 cm."
}

(based on libs/spirit/example/x3/rexpr/rexpr_examples/a.rexpr input).

Summarizing

I hope you don't think this is "fine". Please consider filing an issue at the mailing list/github. Also take these into account:

  • Is Boost Spirit X3 production ready?
  • How future-safe is it to write a parser with Boost Spirit X3?
  • In the light of recent recurring issues revolving arround container-attributes, I think a change around boost 1.65.1 caused container attributes to regress across the board:

    • How to make a recursive rule in boost spirit x3 in VS2017
    • Boost.Spirit X3 parser "no type named type in(...)"

¹ not to mention my dislike for it in most cases: Using spirit to parse into classes?

² Spirit Qi attribute propagation issue with single-member struct, X3, what is attr_gen?, boost::spirit::x3 attribute compatibility rules, intuition or code?

Boost Spirit X3 no matching reference to call, when stuffing strings to vector

Your rule definition does not expose an attribute.

Like @llonesmiz pointed out, fix that:

auto name = x3::rule<class name, std::string>{}
= char_("a-zA-Z") >> *char_("a-z_A-Z0-9");

And see it Live On Wandbox (boost-1.67)

Note: Bugs


If you have Boost 1.65-1.66, you'll run into
How to make a recursive rule in boost spirit x3 in VS2017,
(Live On Wandbox
which was fixed in Boost 1.67 (and earlier too, by the way, e.g. Live
on Wandbox/Boost 1.64)

#include <boost/spirit/home/x3.hpp>

namespace x3 = boost::spirit::x3;

namespace P {
using namespace x3;

auto name = x3::rule<class name, std::string>{}
= char_("a-zA-Z") >> *char_("a-z_A-Z0-9");

auto args_l = x3::rule<class l>{}
= " " >> (name % skip(space)[","]);

auto comment = x3::rule<class comment>{}
= "//" >> *char_;
}

#include <iostream>
#include <iomanip>

int main() {
std::string const line = "a90_b";
auto iter_start = line.begin();
auto iter_end = line.end();

std::vector<std::string> slt;

auto push_back = [&](auto& ctx){ slt.push_back(x3::_attr(ctx)); };

bool result = parse(
iter_start,
iter_end,
P::name[push_back] >> -P::args_l >> *x3::char_(" ")
);

for (auto& tok: slt) {
std::cout << std::quoted(tok) << "\n";
}

if (iter_start!=iter_end)
std::cout << "Remaining unparsed: " << std::quoted(std::string(iter_start, iter_end)) << "\n";

return result? 0 : 255;
}

Unhelpful compiler errors in x3 grammar

There is a regression since Boost 1.65 that causes problems with some rules that potentially propagate into container type attributes.

They dispatch to the wrong overload when instantiated without an actual bound attribute. When this happens there is a "mock" attribute type called unused_type. The errors you are seeing indicate that unused_type is being treated as if it were a concrete attribute type, and clearly that won't fly.

The regression was fixed in https://github.com/boostorg/spirit/commit/ee4943d5891bdae0706fb616b908e3bf528e0dfa

You can see that it's a regression by compiling with Boost 1.64:

  • Boost 1.64 compiles it fine GCC
    and Clang

  • Boost 1.65 breaks it GCC and Clang again

Now, latest develop is supposed to fix it, but you can simply copy the patched file, even just the 7-line patch.

All of the above was already available when I linked the duplicate question How to make a recursive rule in boost spirit x3 in VS2017, which highlights the same regression

Review

  • using namespace std; // Must be after Boost stuff!

    Actually, it probably needs to be nowhere unless very locally scoped, where you can see the impact of any potential name colisions.

  • Consider encapsulating the skipper, since it's likely logically part of your grammar spec, not something to be overridden by the caller.

  • This is a bug:

    auto quoted_string = '"' >> *(lexeme[~char_('"')]) >> '"';

    You probably meant to assert the whole literal is lexeme, not individual characters (that's... moot because whitespace would never hit the parser anyways, because of the skipper).

    auto quoted_string = lexeme['"' >> *~char_('"') >> '"'];
  • Likewise, you might have intended +keyword_value_chars to be lexeme, because right now one=two three four would parse the "qualifier" one with a "keyword value" of onethreefour, not one three four¹

  • x3::space skips embedded newlines, if that's not the intent, use x3::blank

  • Since PEG grammars are parsed left-to-right greedy, you can order the qualifier production and do without the !(no_case["no"]) lookahead assertion. That not only removes duplication but also makes the grammar simpler and more efficient:

    auto qual      = lexeme[+valid_identifier_chars] >>
    -('=' >> (quoted_string | int_ | double_ | +keyword_value_chars)); // TODO lexeme
    auto neg_qual = lexeme[no_case["no"] >> +valid_identifier_chars];
    auto qualifier = lexeme['/' >> (neg_qual | qual)];

    ¹ Note (Post-Scriptum) now that we notice qualifier is, itself, already a lexeme, there's no need to lexeme[] things inside (unless, of course they're reused in contexts with skippers).

    However, this also gives rise to the question whether whitespace around the = operator should be accepted (currently, it is not), or whether qualifiers can be separated with whitespace (like id /a /b; currently they can).

  • Perhaps verb needed some lexemes[] as well (unless you really did want to parse "one two three" as a verb)

  • If no prefix for negative qualifiers, then maybe the identifier itself is, too? This could simplify the grammar

  • The ordering of int_ and double_ makes it so that most doubles are mis-parsed as int before they could ever be recognized. Consider something more explicit like x3::strict_real_policies<double>>{} | int_

  • If you're parsing quoted constructs, perhaps you want to recognize escapes too ('\"' and '\\' for example):

    auto quoted_string = lexeme['"' >> *('\\' >> char_ | ~char_('"')) >> '"'];
  • If you have a need for "keyword values" consider listing known values in x3::symbols<>. This can also be used to parse directly into an enum type.

Here's a version that parses into AST types and prints it back for demonstration purposes:

Live On Coliru

#include <boost/config/warning_disable.hpp>

#include <string>
#include <vector>
#include <boost/variant.hpp>

namespace Ast {
struct Keyword : std::string { // needs to be strong-typed to distinguish from quoted values
using std::string::string;
using std::string::operator=;
};

struct Nil {};
using Value = boost::variant<Nil, std::string, int, double, Keyword>;

struct Qualifier {
enum Kind { positive, negative } kind;
std::string identifier;
Value value;
};

struct Param {
Keyword keyword;
std::vector<Qualifier> qualifiers;
};

struct Command {
std::string verb;
std::vector<Qualifier> qualifiers;
std::vector<Param> params;
};
}

#include <boost/fusion/adapted/struct.hpp>
BOOST_FUSION_ADAPT_STRUCT(Ast::Qualifier, kind, identifier, value)
BOOST_FUSION_ADAPT_STRUCT(Ast::Param, keyword, qualifiers)
BOOST_FUSION_ADAPT_STRUCT(Ast::Command, verb, qualifiers, params)

#include <boost/spirit/home/x3.hpp>

namespace x3 = boost::spirit::x3;
namespace scl {
//
// Grammar for simple command language
//
using x3::char_;
using x3::int_;
using x3::lexeme;
using x3::no_case;

// lexeme tokens
auto keyword = x3::rule<struct _keyword, Ast::Keyword> { "keyword" }
= lexeme [ +char_("a-zA-Z0-9$_.") ];
auto identifier = lexeme [ +char_("a-zA-Z_") ];
auto quoted_string = lexeme['"' >> *('\\' >> x3::char_ | ~x3::char_('"')) >> '"'];

auto value
= quoted_string
| x3::real_parser<double, x3::strict_real_policies<double>>{}
| x3::int_
| keyword;

auto qual
= x3::attr(Ast::Qualifier::positive) >> identifier >> -('=' >> value);
auto neg_qual
= x3::attr(Ast::Qualifier::negative) >> lexeme[no_case["no"] >> identifier] >> x3::attr(Ast::Nil{}); // never a value
auto qualifier
= lexeme['/' >> (neg_qual | qual)];

auto verb
= identifier;

auto parameter = x3::rule<struct _parameter, Ast::Param> {"parameter"}
= keyword >> *qualifier;

auto command = x3::rule<struct _command, Ast::Command> {"command"}
= x3::skip(x3::space) [ verb >> *qualifier >> *parameter ];

} // End namespace scl

// For Demo, Debug: printing the Ast types back
#include <iostream>
#include <iomanip>

namespace Ast {
static inline std::ostream& operator<<(std::ostream& os, Value const& v) {
struct {
std::ostream& _os;
void operator()(std::string const& s) const { _os << std::quoted(s); }
void operator()(int i) const { _os << i; }
void operator()(double d) const { _os << d; }
void operator()(Keyword const& kwv) const { _os << kwv; }
void operator()(Nil) const { }
} vis{os};

boost::apply_visitor(vis, v);
return os;
}

static inline std::ostream& operator<<(std::ostream& os, Qualifier const& q) {
os << "/" << (q.kind==Qualifier::negative?"no":"") << q.identifier;
if (q.value.which())
os << "=" << q.value;
return os;
}

static inline std::ostream& operator<<(std::ostream& os, std::vector<Qualifier> const& qualifiers) {
for (auto& qualifier : qualifiers)
os << qualifier;
return os;
}

static inline std::ostream& operator<<(std::ostream& os, Param const& p) {
return os << p.keyword << p.qualifiers;
}

static inline std::ostream& operator<<(std::ostream& os, Command const& cmd) {
os << cmd.verb << cmd.qualifiers;
for (auto& param : cmd.params) os << " " << param;
return os;
}
}

int main() {
for (std::string const str : {
"show/out=\"somefile.txt\" motors/all cameras/full",
"start/speed=5 motors arm1 arm2/speed=2.5/track arm3",
"rotate camera1/notrack/axis=y/angle=45",
})
{
auto b = str.begin(), e = str.end();

Ast::Command cmd;
bool ok = parse(b, e, scl::command, cmd);
std::cout << (ok?"OK":"FAIL") << '\t' << std::quoted(str) << '\n';

if (ok) {
std::cout << " -- Full AST: " << cmd << "\n";
std::cout << " -- Verb+Qualifiers: " << cmd.verb << cmd.qualifiers << "\n";
for (auto& param : cmd.params)
std::cout << " -- Param+Qualifiers: " << param << "\n";
}

if (b != e) {
std::cout << " -- Remaining unparsed: " << std::quoted(std::string(b,e)) << "\n";
}
}
}

Prints

OK  "show/out=\"somefile.txt\" motors/all cameras/full"
-- Full AST: show/out="somefile.txt" motors/all cameras/full
-- Verb+Qualifiers: show/out="somefile.txt"
-- Param+Qualifiers: motors/all
-- Param+Qualifiers: cameras/full
OK "start/speed=5 motors arm1 arm2/speed=2.5/track arm3"
-- Full AST: start/speed=5 motors arm1 arm2/speed=2.5/track arm3
-- Verb+Qualifiers: start/speed=5
-- Param+Qualifiers: motors
-- Param+Qualifiers: arm1
-- Param+Qualifiers: arm2/speed=2.5/track
-- Param+Qualifiers: arm3
OK "rotate camera1/notrack/axis=y/angle=45"
-- Full AST: rotate camera1/notrack/axis=y/angle=45
-- Verb+Qualifiers: rotate
-- Param+Qualifiers: camera1/notrack/axis=y/angle=45

For completeness

  • Demo also Live On MSVC (Rextester) - note that RexTester uses Boost 1.60
  • Coliru uses Boost 1.66 but the problem doesn't manifest itself because now, there are concrete attribute values bound to parsers

Boost Spirit x3: parse into structs

I love @llonesmiz's approach in the comment.

I "had to" try my favorite approach with X3 using functional composition too, though. Here's a sketch of the approach which does parse and propagate the values.

Missing are checks on property presence/uniqueness. (I think such a thing is doable using a x3::with<> context addition that basically contains a std::set<V T::*>. Of course such a thing needs (implementation dependent?) casts or an erasure wrapper).

For now, presented without comment:

Live On Coliru

#include <iostream>
//#define BOOST_SPIRIT_X3_DEBUG
#include <boost/spirit/home/x3.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>

struct LayerInfo
{
std::string layerName;
int layerNumber = 0;
std::string color;
bool visible = false;
};

namespace Parser {
namespace x3 = boost::spirit::x3;

// custom type parsers
auto quoted = rule<std::string>("quoted", x3::lexeme [ '"' >> *('\\' >> x3::char_ | ~x3::char_('"')) >> '"' ]);
struct colors_type : x3::symbols<char> {
colors_type() {
this->add("red")("blue")("green")("black");
}
} static const colors;

namespace detail {
template <typename T> auto propagate(T member) {
return [=](auto& ctx){ x3::traits::move_to(x3::_attr(ctx), x3::_val(ctx).*member); };
}

template <typename T> auto make_member_parser(int T::* const member) { return x3::int_ [propagate(member)]; }
template <typename T> auto make_member_parser(bool T::* const member) { return x3::bool_ [propagate(member)]; }
template <typename T> auto make_member_parser(std::string T::* const member) { return x3::raw[colors] [propagate(member)]; }

template <typename T = LayerInfo, typename P>
auto rule(const char* debug, P p) { return x3::rule<struct _, T> {debug} = x3::skip(x3::space)[p]; };

auto property = [](auto label, auto member) {
return rule(label, x3::as_parser(label) >> '=' >> make_member_parser(member));
};
}

using detail::rule;
using detail::propagate;
using detail::property;

auto name = rule("name", "Layer" >> quoted [propagate(&LayerInfo::layerName)]);

auto number = property("number", &LayerInfo::layerNumber);
auto color = property("color", &LayerInfo::color);
auto visible = property("visible", &LayerInfo::visible);

auto layer_info = name >> '{' >> +(number | color | visible) >> '}';

auto grammar = rule("layer_info", layer_info);
}

std::ostream& operator<<(std::ostream& os, LayerInfo const& li) {
return os << "LayerInfo \"" << li.layerName << "\"{"
<< "number=" << li.layerNumber << " "
<< "color=" << li.color << " "
<< "visible=" << std::boolalpha << li.visible
<< "}\n";
}

int main() {
std::string const sample = R"(Layer "L1" {
number = 23
color = green
visible = true
})";

LayerInfo v;
auto f = sample.begin(), l = sample.end();
bool ok = parse(f, l, Parser::grammar, v);

if (ok)
std::cout << "Parsed: " << v << "\n";
else
std::cout << "Parse failed\n";

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

Prints

Parsed: LayerInfo "L1"{number=23 color=green visible=true}

Wit debug info: Live On Coliru

<layer_info>
<try>Layer "L1" {\n num</try>
<name>
<try>Layer "L1" {\n num</try>
<quoted>
<try> "L1" {\n number =</try>
<success> {\n number = 23\n </success>
<attributes>[L, 1]</attributes>
</quoted>
<success> {\n number = 23\n </success>
<attributes>LayerInfo "L1"{number=0 color= visible=false}
</attributes>
</name>
<number>
<try>\n number = 23\n </try>
<success>\n color = green\n </success>
<attributes>LayerInfo "L1"{number=23 color= visible=false}
</attributes>
</number>
<number>
<try>\n color = green\n </try>
<fail/>
</number>
<color>
<try>\n color = green\n </try>
<success>\n visible = true\n</success>
<attributes>LayerInfo "L1"{number=23 color=green visible=false}
</attributes>
</color>
<number>
<try>\n visible = true\n</try>
<fail/>
</number>
<color>
<try>\n visible = true\n</try>
<fail/>
</color>
<visible>
<try>\n visible = true\n</try>
<success>\n}</success>


Related Topics



Leave a reply



Submit