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:
- single-element fusion sequences
- ADT adaptation in general
- 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
- compile against ae78e1ec243151 or later (
develop
) - 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 ClangBoost 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 nowone=two three four
would parse the "qualifier"one
with a "keyword value" ofonethreefour
, notone three four
¹x3::space
skips embedded newlines, if that's not the intent, usex3::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 tolexeme[]
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 (likeid /a /b
; currently they can).Perhaps
verb
needed somelexemes[]
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 grammarThe ordering of
int_
anddouble_
makes it so that most doubles are mis-parsed asint
before they could ever be recognized. Consider something more explicit likex3::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 astd::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
Stl Random Distributions and Portability
C++ Access Violation Reading Location 0Xcdcdcdcd Error on Calling a Function
Line by Line C - C++ Code Debugging in Linux Ubuntu
Is There a Proper 'Ownership-In-A-Package' for 'Handles' Available
How to Print the Value of Nullptr on Screen
Vector Memory Allocation Strategy
Symbol Not Found When Using Template Defined in a Library
Boost::Asio Synchronous Client with Timeout
Is There a Case Where Including the Same Header Twice Is Actually Helpful
Cin >> Fails with Bigger Numbers But Works with Smaller Ones
How to Write and Read To/From a Qresource File in Qt 5
How Are Exceptions Implemented Under the Hood
Overriding Return Type in Function Template Specialization
How to Compare Two Standard Conversion Sequences Use the Rank of Contained Conversions
Reversing Order of Words in a Sentence
To Stl or !Stl, That Is the Question