I'm using Visual Studio 2019 with std/c++14 setting. With the help from Sehe, this code compiled fine without any issue: Working Code
(Note that I switched to boost::optional
and added ':'
to the identifier charset)
Now I want to extend the grammar so that I can parse this syntax
Class Complex Complex_Name (
Inherit Class1:Variable1
Inherit Class1:Variable2
);
and output the following:
<class>
<complex>
<identifier>Complex_Name</identifier>
<literal>" "</literal>
<inherit>
<identifier>Class1:Variable1</identifier>
</inherit>
<inherit>
<identifier>Class1:Variable2</identifier>
</inherit>
</complex>
</class>
Below is what I have done
struct Inherit {
Id id;
};
using Class = boost::variant<
Simple,
Inherit,
recursive_wrapper<Complex>,
recursive_wrapper<Container>
>;
id_ = raw[alpha >> *(alnum | '_' | ':')]; // add ':' to identifier rule
...
type_ = simple_ | inherit_ | complex_ | container_;
...
inherit_ = lit("Inherit") >> id_
...
qi::rule<It, Ast::Inherit(), Skipper> inherit_;
void apply(Node parent, Inherit const& inh) const {
auto inherit_ = named_child(parent, "inherit");
apply(inherit_, inh.id);
}
FULL CODE
// #define BOOST_SPIRIT_DEBUG 1
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
namespace qi = boost::spirit::qi;
namespace Ast {
using boost::recursive_wrapper;
template <typename> struct flavour_hack : std::char_traits<char> {};
template <typename Tag>
using String = std::basic_string<char, flavour_hack<Tag> >;
using Id = String<struct TagId>;
using Literal = String<struct TagLiteral>;
using Datatype = String<struct TagDatatype>;
struct Base {
Id id;
Literal literal;
};
using Enum = std::vector<Id>;
using Number = double;
using Value = boost::variant<Literal, Number, Id>;
struct Simple : Base {
boost::optional<Enum> enumeration;
boost::optional<Datatype> datatype;
boost::optional<Value> default_;
};
struct Complex;
struct Container;
struct Inherit {
Id id;
};
using Class = boost::variant<
Simple,
Inherit,
recursive_wrapper<Complex>,
recursive_wrapper<Container>
>;
using Classes = std::vector<Class>;
struct Container : Base { Class element; };
struct Complex : Base { Classes members; };
using Task = std::vector<Class>;
} // namespace Ast
BOOST_FUSION_ADAPT_STRUCT(Ast::Simple, id, literal, enumeration, datatype, default_);
BOOST_FUSION_ADAPT_STRUCT(Ast::Complex, id, literal, members)
BOOST_FUSION_ADAPT_STRUCT(Ast::Container, id, literal, element)
namespace Parser {
template <typename It> struct Task : qi::grammar<It, Ast::Task()> {
Task() : Task::base_type(start) {
using namespace qi;
start = skip(space)[task_];
// lexemes:
id_ = raw[alpha >> *(alnum | '_' | ':')];
literal_ = '"' > *('\\' >> char_ | ~char_('"')) > '"';
auto optlit = copy(literal_ | attr(std::string(" "))); // weird, but okay
task_ = *class_ > eoi;
type_ = simple_ | inherit_ | complex_ | container_;
class_ = lit("Class") > type_ > ';';
simple_ = lit("Simple") >> id_ >> optlit >> -enum_ >> -datatype_ >> -default_;
inherit_ = lit("Inherit") >> id_;
complex_ = lit("Complex") >> id_ >> optlit >> '(' >> *type_ >> ')';
container_ = lit("Container") >> id_ >> optlit >> '(' >> type_ > ')';
enum_ = lit("enumeration") >> '(' >> -(id_ % ',') > ')';
datatype_ = lit("datatype") >> id_;
value_ = literal_ | number_ | id_;
number_ = double_;
default_ = lit("Default") >> value_;
BOOST_SPIRIT_DEBUG_NODES(
(task_)(class_)(type_)(simple_)(complex_)(container_)(enum_)(datatype_)
(default_)(id_)(literal_)(value_)(number_)
)
}
private:
qi::rule<It, Ast::Task()> start;
using Skipper = qi::space_type;
qi::rule<It, Ast::Task(), Skipper> task_;
qi::rule<It, Ast::Class(), Skipper> class_, type_;
qi::rule<It, Ast::Simple(), Skipper> simple_;
qi::rule<It, Ast::Complex(), Skipper> complex_;
qi::rule<It, Ast::Container(), Skipper> container_;
qi::rule<It, Ast::Enum(), Skipper> enum_;
qi::rule<It, Ast::Datatype(), Skipper> datatype_;
qi::rule<It, Ast::Value(), Skipper> default_;
qi::rule<It, Ast::Inherit(), Skipper> inherit_;
// lexemes:
qi::rule<It, Ast::Id()> id_;
qi::rule<It, Ast::Literal()> literal_;
qi::rule<It, Ast::Value()> value_;
qi::rule<It, Ast::Number()> number_;
};
}
#include <pugixml.hpp>
namespace Generate {
using namespace Ast;
struct XML {
using Node = pugi::xml_node;
// callable for variant visiting:
template <typename T> void operator()(Node parent, T const& node) const { apply(parent, node); }
private:
template <typename... Ts>
void apply(Node parent, boost::variant<Ts...> const& v) const {
using std::placeholders::_1;
boost::apply_visitor(std::bind(*this, parent, _1), v);
}
void apply(Node parent, Ast::Number const& num) const {
named_child(parent, "num").text().set(num);
}
void apply(Node parent, Ast::Id const& id) const {
named_child(parent, "identifier").text().set(id.c_str());
}
void apply(Node parent, Ast::Literal const& literal) const {
named_child(parent, "literal").text().set(literal.c_str());
}
void apply(Node parent, Ast::Datatype const& datatype) const {
named_child(parent, "datatype").text().set(datatype.c_str());
}
template <typename T> void apply(Node parent, boost::optional<T> const& opt) const {
if (opt)
apply(parent, *opt);
}
void apply(Node parent, Simple const& s) const {
auto simple = named_child(parent, "simple");
apply(simple, s.id);
apply(simple, s.literal);
apply(simple, s.enumeration);
apply(simple, s.datatype);
if (s.default_.has_value()) {
apply(named_child(simple, "default"), *s.default_);
}
}
void apply(Node parent, Enum const& e) const {
auto enum_ = named_child(parent, "enumeration");
for (auto& v : e)
named_child(enum_, "word").text().set(v.c_str());
}
void apply(Node parent, Inherit const& inh) const {
auto inherit = named_child(parent, "inherit");
apply(inherit, inh.id);
}
void apply(Node parent, Complex const& c) const {
auto complex_ = named_child(parent, "complex");
apply(complex_, c.id);
apply(complex_, c.literal);
for (auto& m : c.members)
apply(complex_, m);
}
void apply(Node parent, Container const& c) const {
auto cont = named_child(parent, "container");
apply(cont, c.id);
apply(cont, c.literal);
apply(cont, c.element);
}
void apply(Node parent, Task const& t) const {
auto task = named_child(parent, "task");
for (auto& c : t)
apply(task.append_child("class"), c);
}
private:
Node named_child(Node parent, std::string const& name) const {
auto child = parent.append_child();
child.set_name(name.c_str());
return child;
}
};
} // namespace Generate
int main() {
using It = std::string_view::const_iterator;
static const Parser::Task<It> p;
static const Generate::XML to_xml;
for (std::string_view input : {
R"(
Class Simple caption;
Class Simple test enumeration(opt1, opt2, opt3, opt4);
Class Simple my_var datatype restriction;
Class Simple var2 Default 0;
Class Complex complexType (
Inherit Class1:Variable2
Inherit Class1:Variable2
Inherit Class2:Variable1
);
)"
}) {
try {
Ast::Task t;
if (qi::parse(begin(input), end(input), p, t)) {
pugi::xml_document doc;
to_xml(doc.root(), t);
doc.print(std::cout, " ", pugi::format_default);
std::cout << std::endl;
} else {
std::cout << " -> INVALID" << std::endl;
}
} catch (qi::expectation_failure<It> const& ef) {
auto f = begin(input);
auto p = ef.first - input.begin();
auto bol = input.find_last_of("\r\n", p) + 1;
auto line = std::count(f, f + bol, '\n') + 1;
auto eol = input.find_first_of("\r\n", p);
std::cerr << " -> EXPECTED " << ef.what_ << " in line:" << line << "\n"
<< input.substr(bol, eol - bol) << "\n"
<< std::setw(p - bol) << ""
<< "^--- here" << std::endl;
}
}
}
When I compile this new update with current std/c++14 settings, it throw this error
Error C2440 'static_cast': cannot convert from 'const T_' to 'Attribute'
Even though I'm not suppose to, I still tried to compiled this with std/c++17 options and get the same error. But if I compile this with std/c++20 option using Visual Studio 2022. Then it compiled and work as expected.
Same thing happen with Compiler Explorer. This will throw the same error when I'm using anything below x86-64 clang 16.0.0 option.
DEMO NEW CODE that fail using old compiler
If I switch to use x86-64 clang 16.0.0 option, it will compile and work just fine.
DEMO NEW CODE that work with latest compiler
What I added is fairly simple but not sure why it would not supported. Can somebody tell me what's wrong and why I need to use the latest compiler for this small change.
Thanks Dylan
it would not supported
That's framing. It's supported. You just don't write enough code.
C++20 adds () constructability of aggregates. Since you don't have it, either add the constructor to Inherit
:
Inherit(Id id = {}) : id(std::move(id)) {}
OR adapt it like the other AST nodes:
BOOST_FUSION_ADAPT_STRUCT(Ast::Inherit, id)
Both work. Other notes:
ironically you were using std::string_view
which is c++17 only ¯\(ツ)/¯
you also forgot to add inherit_
to the debug node list
it looks as though you're abusing the type_
rule to express something semantically independent: inheritance. Now your grammar will accept Class Inherit bogus;
as well 🤷
In fact, assuming OO-like inheritance I'd expect something more like
// #define BOOST_SPIRIT_DEBUG 1
#include <boost/fusion/adapted.hpp>
#include <boost/spirit/include/qi.hpp>
#include <iomanip>
namespace qi = boost::spirit::qi;
namespace Ast {
using boost::recursive_wrapper;
template <typename> struct flavour_hack : std::char_traits<char> {};
template <typename Tag>
using String = std::basic_string<char, flavour_hack<Tag> >;
using Id = String<struct TagId>;
using Literal = String<struct TagLiteral>;
using Datatype = String<struct TagDatatype>;
struct Base {
Id id;
Literal literal;
};
using Ids = std::vector<Id>;
using Enum = Ids;
using Number = double;
using Value = boost::variant<Literal, Number, Id>;
struct Simple : Base {
boost::optional<Enum> enumeration;
boost::optional<Datatype> datatype;
boost::optional<Value> default_;
};
struct Complex;
struct Container;
;
using Class = boost::variant<
Simple,
recursive_wrapper<Complex>,
recursive_wrapper<Container>
>;
using Classes = std::vector<Class>;
struct Container : Base { Class element; };
struct Complex : Base { Ids bases; Classes members; };
using Task = std::vector<Class>;
} // namespace Ast
BOOST_FUSION_ADAPT_STRUCT(Ast::Simple, id, literal, enumeration, datatype, default_);
BOOST_FUSION_ADAPT_STRUCT(Ast::Complex, id, literal, bases, members)
BOOST_FUSION_ADAPT_STRUCT(Ast::Container, id, literal, element)
namespace Parser {
template <typename It> struct Task : qi::grammar<It, Ast::Task()> {
Task() : Task::base_type(start) {
using namespace qi;
start = skip(space)[task_];
// lexemes:
id_ = raw[alpha >> *(alnum | '_' | ':')];
literal_ = '"' > *('\\' >> char_ | ~char_('"')) > '"';
auto optlit = copy(literal_ | attr(std::string(" "))); // weird, but okay
task_ = *class_ > eoi;
type_ = simple_ | complex_ | container_;
class_ = lit("Class") > type_ > ';';
simple_ = lit("Simple") >> id_ >> optlit >> -enum_ >> -datatype_ >> -default_;
inherit_ = lit("Inherit") >> id_;
complex_ = lit("Complex") >> id_ >> optlit >> '(' >> *inherit_ >> *type_ >> ')';
container_ = lit("Container") >> id_ >> optlit >> '(' >> type_ > ')';
enum_ = lit("enumeration") >> '(' >> -(id_ % ',') > ')';
datatype_ = lit("datatype") >> id_;
value_ = literal_ | number_ | id_;
number_ = double_;
default_ = lit("Default") >> value_;
BOOST_SPIRIT_DEBUG_NODES(
(task_)(class_)(type_)(simple_)(complex_)(container_)(enum_)(datatype_)
(default_)(id_)(literal_)(value_)(number_)(inherit_)
)
}
private:
qi::rule<It, Ast::Task()> start;
using Skipper = qi::space_type;
qi::rule<It, Ast::Task(), Skipper> task_;
qi::rule<It, Ast::Class(), Skipper> class_, type_;
qi::rule<It, Ast::Simple(), Skipper> simple_;
qi::rule<It, Ast::Complex(), Skipper> complex_;
qi::rule<It, Ast::Container(), Skipper> container_;
qi::rule<It, Ast::Enum(), Skipper> enum_;
qi::rule<It, Ast::Datatype(), Skipper> datatype_;
qi::rule<It, Ast::Value(), Skipper> default_;
qi::rule<It, Ast::Id(), Skipper> inherit_;
// lexemes:
qi::rule<It, Ast::Id()> id_;
qi::rule<It, Ast::Literal()> literal_;
qi::rule<It, Ast::Value()> value_;
qi::rule<It, Ast::Number()> number_;
};
}
#include <pugixml.hpp>
namespace Generate {
using namespace Ast;
struct XML {
using Node = pugi::xml_node;
// callable for variant visiting:
template <typename T> void operator()(Node parent, T const& node) const { apply(parent, node); }
private:
template <typename... Ts>
void apply(Node parent, boost::variant<Ts...> const& v) const {
using std::placeholders::_1;
boost::apply_visitor(std::bind(*this, parent, _1), v);
}
void apply(Node parent, Ast::Number const& num) const {
named_child(parent, "num").text().set(num);
}
void apply(Node parent, Ast::Id const& id) const {
named_child(parent, "identifier").text().set(id.c_str());
}
void apply(Node parent, Ast::Literal const& literal) const {
named_child(parent, "literal").text().set(literal.c_str());
}
void apply(Node parent, Ast::Datatype const& datatype) const {
named_child(parent, "datatype").text().set(datatype.c_str());
}
template <typename T> void apply(Node parent, boost::optional<T> const& opt) const {
if (opt)
apply(parent, *opt);
}
void apply(Node parent, Simple const& s) const {
auto simple = named_child(parent, "simple");
apply(simple, s.id);
apply(simple, s.literal);
apply(simple, s.enumeration);
apply(simple, s.datatype);
if (s.default_.has_value()) {
apply(named_child(simple, "default"), *s.default_);
}
}
void apply(Node parent, Enum const& e) const {
auto enum_ = named_child(parent, "enumeration");
for (auto& v : e)
named_child(enum_, "word").text().set(v.c_str());
}
void apply(Node parent, Complex const& c) const {
auto complex_ = named_child(parent, "complex");
apply(complex_, c.id);
for (auto& base : c.bases)
apply(named_child(complex_, "inherit"), base);
apply(complex_, c.literal);
for (auto& m : c.members)
apply(complex_, m);
}
void apply(Node parent, Container const& c) const {
auto cont = named_child(parent, "container");
apply(cont, c.id);
apply(cont, c.literal);
apply(cont, c.element);
}
void apply(Node parent, Task const& t) const {
auto task = named_child(parent, "task");
for (auto& c : t)
apply(task.append_child("class"), c);
}
private:
Node named_child(Node parent, std::string const& name) const {
auto child = parent.append_child();
child.set_name(name.c_str());
return child;
}
};
} // namespace Generate
int main() {
using It = std::string::const_iterator;
static const Parser::Task<It> p;
static const Generate::XML to_xml;
for (std::string const input : {
R"(
Class Simple caption;
Class Simple test enumeration(opt1, opt2, opt3, opt4);
Class Simple my_var datatype restriction;
Class Simple var2 Default 0;
Class Complex complexType (
Inherit Class1:Variable2
Inherit Class1:Variable2
Inherit Class2:Variable1
);
)"
}) {
try {
Ast::Task t;
if (qi::parse(begin(input), end(input), p, t)) {
pugi::xml_document doc;
to_xml(doc.root(), t);
doc.print(std::cout, " ", pugi::format_default);
std::cout << std::endl;
} else {
std::cout << " -> INVALID" << std::endl;
}
} catch (qi::expectation_failure<It> const& ef) {
auto f = begin(input);
auto p = ef.first - input.begin();
auto bol = input.find_last_of("\r\n", p) + 1;
auto line = std::count(f, f + bol, '\n') + 1;
auto eol = input.find_first_of("\r\n", p);
std::cerr << " -> EXPECTED " << ef.what_ << " in line:" << line << "\n"
<< input.substr(bol, eol - bol) << "\n"
<< std::setw(p - bol) << ""
<< "^--- here" << std::endl;
}
}
}
Prints
<task>
<class>
<simple>
<identifier>caption</identifier>
<literal> </literal>
</simple>
</class>
<class>
<simple>
<identifier>test</identifier>
<literal> </literal>
<enumeration>
<word>opt1</word>
<word>opt2</word>
<word>opt3</word>
<word>opt4</word>
</enumeration>
</simple>
</class>
<class>
<simple>
<identifier>my_var</identifier>
<literal> </literal>
<datatype>restriction</datatype>
</simple>
</class>
<class>
<simple>
<identifier>var2</identifier>
<literal> </literal>
<default>
<num>0</num>
</default>
</simple>
</class>
<class>
<complex>
<identifier>complexType</identifier>
<inherit>
<identifier>Class1:Variable2</identifier>
</inherit>
<inherit>
<identifier>Class1:Variable2</identifier>
</inherit>
<inherit>
<identifier>Class2:Variable1</identifier>
</inherit>
<literal> </literal>
</complex>
</class>
</task>
Note that
Class x Inherit b;