Search code examples
c++boost-spiritboost-spirit-qi

BNF (with custom modification) Parser using Spirit


Using the bnf parser from here, I am trying add a field to be read as an attribute of List. So what I have done is to change:

using List = std::list<Term>;

to

struct List : public std::list<Term>{
int number;
}

For example:

<code>   ::=  <letter><digit> 34 | <letter><digit><code> 23

So those numbers are read as attributes of List. The issue is I unable to read the number as an attribute of List.


Solution

  • I would favour composition over inheritance (for many reasons).

    So

    struct List {
        std::list<Term> terms;
        int number;
    };
    

    With

    BOOST_FUSION_ADAPT_STRUCT(Ast::List, terms, number)
    

    Then modifying the rule from

    _list       = +_term;
    

    to be

    _list       = +_term >> qi::uint_;
    

    Is enough to get what you described:

    Live On Compiler Explorer

    //#define BOOST_SPIRIT_DEBUG
    #include <boost/spirit/include/qi.hpp>
    #include <boost/fusion/adapted.hpp>
    #include <fmt/ranges.h>
    #include <fmt/ostream.h>
    #include <iomanip>
    namespace qi = boost::spirit::qi;
    
    namespace Ast {
        struct Name : std::string {
            using std::string::string;
            using std::string::operator=;
    
            friend std::ostream& operator<<(std::ostream& os, Name const& n) {
                return os << '<' << n.c_str() << '>';
            }
        };
    
        using Term = boost::variant<Name, std::string>;
    
        struct List {
            std::list<Term> terms;
            int number;
    
            friend std::ostream& operator<<(std::ostream& os, List const& l) {
                for (auto& t : l.terms)
                    os << t;
                return os << " " << l.number;
            }
        };
    
        using Expression = std::list<List>;
    
        struct Rule {
            Name name; // lhs
            Expression rhs;
        };
    
        using Syntax = std::list<Rule>;
    }
    
    BOOST_FUSION_ADAPT_STRUCT(Ast::List, terms, number)
    BOOST_FUSION_ADAPT_STRUCT(Ast::Rule, name, rhs)
    
    namespace Parser {
        template <typename Iterator>
        struct BNF: qi::grammar<Iterator, Ast::Syntax()> {
            BNF(): BNF::base_type(start) {
                using namespace qi;
                start = skip(blank) [ _rule % +eol ];
    
                _rule       = _rule_name >> "::=" >> _expression;
                _expression = _list % '|';
                _list       = +_term >> qi::uint_;
                _term       = _literal | _rule_name ;
                _literal    = '"' >> *(_character - '"') >> '"'
                            | "'" >> *(_character - "'") >> "'";
                _character  = alnum | char_("\"'| !#$%&()*+,./:;>=<?@]\\^_`{}~[-");
                _rule_name  = '<' >> (alpha >> *(alnum | char_('-'))) >> '>';
    
                BOOST_SPIRIT_DEBUG_NODES(
                    (_rule)(_expression)(_list)(_term)
                    (_literal)(_character)
                    (_rule_name))
            }
    
        private:
            qi::rule<Iterator, Ast::Syntax()>     start;
            qi::rule<Iterator, Ast::Rule(),       qi::blank_type> _rule;
            qi::rule<Iterator, Ast::Expression(), qi::blank_type> _expression;
            qi::rule<Iterator, Ast::List(),       qi::blank_type> _list;
            // lexemes
            qi::rule<Iterator, Ast::Term()>       _term;
            qi::rule<Iterator, Ast::Name()>       _rule_name;
            qi::rule<Iterator, std::string()>     _literal;
            qi::rule<Iterator, char()>            _character;
        };
    }
    
    int main() {
        Parser::BNF<std::string::const_iterator> const parser;
    
        std::string const input =
            R"(<code>   ::=  <letter><digit> 34 | <letter><digit><code> 23
    <letter> ::= "a" 1 | "b" 2 | "c" 3 | "d" 4 | "e" 5 | "f" 6 | "g" 7 | "h" 8 | "i" 9
    <digit>  ::= "9" 10 | "1" 11 | "2" 12 | "3" 13 | "4" 14
        )";
    
        auto it = input.begin(), itEnd = input.end();
    
        Ast::Syntax syntax;
        if (parse(it, itEnd, parser, syntax)) {
            for (auto& rule : syntax)
                fmt::print("{} ::= {}\n", rule.name, fmt::join(rule.rhs, " | "));
        } else {
            std::cout << "Failed\n";
        }
    
        if (it != itEnd)
            std::cout << "Remaining: " << std::quoted(std::string(it, itEnd)) << "\n";
    }
    

    Prints

    code ::= <letter><digit> 34 | <letter><digit><code> 23
    letter ::= a 1 | b 2 | c 3 | d 4 | e 5 | f 6 | g 7 | h 8 | i 9
    digit ::= 9 10 | 1 11 | 2 12 | 3 13 | 4 14
    Remaining: "
        "