1

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

  1. Add New Inherit struct
struct Inherit {
  Id id;
};
  1. Add the new Inherit struct into Class variant
using Class = boost::variant<   
  Simple,                     
  Inherit,
  recursive_wrapper<Complex>, 
  recursive_wrapper<Container>
>;
  1. Now update the rule to pick this up
id_      = raw[alpha >> *(alnum | '_' | ':')];  // add ':' to identifier rule
...
type_      = simple_ | inherit_ | complex_ | container_;
...
inherit_ = lit("Inherit") >> id_
...
qi::rule<It, Ast::Inherit(), Skipper>     inherit_;
  1. Update XML rule to generate the content
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

sehe
  • 374,641
  • 47
  • 450
  • 633
Dylan
  • 121
  • 7

1 Answers1

1

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

Live On Compiler Explorer

// #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

  • this doesn't incur a new AST node (since bases are just identifiers)
  • it doesn't accidentally accept Class x Inherit b;
  • it assumes that bases be listed before members (which is true in every programming language I can think of)

sehe
  • 374,641
  • 47
  • 450
  • 633
  • 1
    "ironically you were using std::string_view which is c++17 only ¯\(ツ)/¯" <-- Yup. silly me. My local version doesn't use string_view... This is just me copy from your working code at **Compiler Explorer** and modified it to compose this question. – Dylan Jun 08 '23 at 20:25