Search code examples
boostboost-spirit

Boost Spirit No type names 'value_type' in struct boost::fusion::extension::adt_attribute_proxy<Ast::Term, 0, false>


Following this code, I changed the struct to a class and added other copy constructors and overloaded the = operator. I also added functionality for the parser to spot newlines, tabs etc. However, the code does not run due some errors. I have a clue on how to fix it. Below is a snippet of the error message and the code.

***/main.cpp:130:24:   required from ‘Parser::BNF<Iterator>::BNF() [with Iterator = __gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char> >]’
****/main.cpp:156:52:   required from here
/usr/include/boost/spirit/home/qi/detail/assign_to.hpp:41:16: error: no type named ‘value_type’ in ‘struct boost::fusion::extension::adt_attribute_proxy<Ast::Term, 0, false>’
   41 |         struct is_container_of_ranges
      |                ^~~~~~~~~~~~~~~~~~~~~~
......

***/main.cpp:130:24:   required from ‘Parser::BNF<Iterator>::BNF() [with Iterator = __gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char> >]’
***/main.cpp:156:52:   required from here
/usr/include/boost/spirit/home/qi/detail/assign_to.hpp:72:17: error: no matching function for call to ‘boost::spirit::traits::assign_to_attribute_from_iterators<boost::fusion::extension::adt_attribute_proxy<Ast::Term, 0, false>, __gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char> >, void>::call(const __gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char> >&, const __gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char> >&, boost::fusion::extension::adt_attribute_proxy<Ast::Term, 0, false>&, boost::spirit::traits::detail::is_container_of_ranges<boost::fusion::extension::adt_attribute_proxy<Ast::Term, 0, false> >)’
   72 |             call(first, last, attr, detail::is_container_of_ranges<Attribute>());
//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted.hpp>
#include <iomanip>

namespace qi = boost::spirit::qi;

namespace Ast {
    enum class TermType {
        literal, rule_name
    };

    class Term {
    private:
        std::string data;
        TermType term_type;

    public:
        Term() = default;

        void set_data(std::string const &data) {
            this->data = data;
        }

        std::string const &get_data() const {
            return data;
        }

        void set_term_type(TermType term_type) {
            this->term_type = term_type;
        }

        TermType const &get_term_type() const {
            return term_type;
        }

        bool operator==(const Term &term) {
            if (!data.compare(term.data) && term_type == term.term_type)
                return true;
            return false;
        }

        Term &operator=(const Term &term) {
            if (this != &term) {
                data = term.data;
                term_type = term.term_type;
            }
            return *this;
        }

        Term(const Term &term) {
            data = term.data;
            term_type = term.term_type;
        }
    };

    class List : public std::vector<Term> {
        // attributes not required for parsing
        // constructor, copy constructor, = operator overloading
    };

    using Expression = std::vector<List>;

    class Rule {
    private:
        Term name; // lhs
        Expression rhs;

    public:
        void set_rule_name(const Term &rule_name) {
            this->name = rule_name;
        }

        Term const &get_rule_name() const {
            return name;
        }

        void set_expression(const Expression &expression) {
            rhs = expression;
        }

        Expression const &get_expression() const {
            return rhs;
        }

        // constructor, copy constructor, = operator overloading
    };

    using Syntax = std::list<Rule>;
}
BOOST_FUSION_ADAPT_ADT(Ast::Term,
                       (obj.get_data(), obj.set_data(val))
                               (obj.get_term_type(), obj.set_term_type(val)))

BOOST_FUSION_ADAPT_ADT(Ast::Rule,
                       (obj.get_rule_name(), obj.set_rule_name(val))
                               (obj.get_expression(), obj.set_expression(val)))

namespace Parser {
    template<typename Iterator>
    struct BNF : qi::grammar<Iterator, Ast::Syntax()> {
        BNF()
                : BNF::base_type(start) {
            using namespace qi;
            _blank = blank;
            unesc_char.add
                    ("\\a", '\a')
                    ("\\b", '\b')
                    ("\\f", '\f')
                    ("\\n", '\n')
                    ("\\r", '\r')
                    ("\\t", '\t')
                    ("\\v", '\v')
                    ("\\\\", '\\')
                    ("\\'", '\'')
                    ("\\\"", '\"');
            _skipper = blank | (eol >> !skip(_blank.alias())[_rule]);
            start = skip(_skipper.alias())[_rule % +eol];

            _rule = _rule_name >> "::=" >> _expression;
            _expression = _list % '|';
            _list = +_term;
            _term = _literal >> attr(Ast::TermType::literal)
                    | _rule_name;
            _literal = unesc_char | '"' >> *(_character - '"') >> '"'
                       | "'" >> *(_character - "'") >> "'";

            _character = alnum | char_("\"'| !#$%&()*+,./:;>=<?@]\\^_`{}~[-");
            _rule_name = '<' >> qi::raw[(alpha >> *(alnum | char_('-')))] >> '>'
                             >> attr(Ast::TermType::rule_name);

            // clang-format on
            BOOST_SPIRIT_DEBUG_NODES(
                    (_rule)(_expression)(_list)(_literal)(_character)(_rule_name))
        }

    private:
        using Skipper = qi::rule<Iterator>;
        Skipper _skipper, _blank;

        qi::rule<Iterator, Ast::Syntax()> start;
        qi::rule<Iterator, Ast::Rule(), Skipper> _rule;
        qi::rule<Iterator, Ast::Expression(), Skipper> _expression;
        qi::rule<Iterator, Ast::List(), Skipper> _list;
        // lexemes
        qi::rule<Iterator, Ast::Term()> _term;
        qi::rule<Iterator, std::string()> _literal;
        qi::rule<Iterator, Ast::Term()> _rule_name;
        qi::rule<Iterator, char()> _character;
        qi::symbols<char const, char const> unesc_char;
    };
}

int main() {
    Parser::BNF<std::string::const_iterator> const parser;

    std::string const input = R"(<code>   ::=  <letter><digit> | <letter><digit><code>
<letter> ::= "a" | "b" | "c" | "d" | "e"
           | "f" | "g" | "h" | "i"
<digit>  ::= "0" | "1 \n 3" | "2" | "3 \t yy" |
             "4"
    )";

    auto it = input.begin(), itEnd = input.end();

    Ast::Syntax syntax;
    if (parse(it, itEnd, parser, syntax)) {
        for (auto &rule : syntax) {
            std::cout << rule.get_rule_name().get_data() << " ::= ";
            std::string sep;
            for (auto &list : rule.get_expression()) {
                std::cout << sep;
                for (auto &term: list) { std::cout << term.get_data(); }
                sep = " | ";
            };
            std::cout << "\n";
        }
    } else {
        std::cout << "Failed\n";
    }

    if (it != itEnd)
        std::cout << "Remaining: " << std::quoted(std::string(it, itEnd)) << "\n";
}

Solution

  • For some reason the attribute compatibility rules don't apply when invoking the setter methods. You can force the issue with as_string on top:

    _rule_name = '<' >> qi::as_string [ qi::raw[ (alpha >> *(alnum | char_('-'))) ] ] >>
                 '>' >> attr(Ast::TermType::rule_name);
    

    Side Observations

    On Feb 27 at 23:35 you commented:

    My other concern is the inheritance from string class. I thought was bad to do that.

    Apparently, you had a change of heart there. I really don't know why. In this case, you probably should not have. Shortcuts are "fine" unless they're not. See e.g. More silent behaviour changes with C++20 three-way comparison and https://quuxplusone.github.io/blog/2018/12/11/dont-inherit-from-std-types/

    Also, beware of quasi-classes (PDF). Can you tell me why the struct wasn't better? You're merely redefining a lot of standard behaviour (copy ctor/assignment should have been defaulted, just as operator<=> on C++20).