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

Boost.Spirit Qi: different entry points to the same grammar?


I have a recursive grammar and want to parse starting from the different rules from it. Is it possible without rewriting the same grammar several times?

Example: I have json parser:

template <typename It, typename Skipper = qi::space_type>
struct grammar : qi::grammar<It, value (), Skipper>
{
  grammar () : grammar::base_type (value_)
  {
    using namespace qi;

    static auto const null_ = proto::deep_copy ("null" >> qi::attr (null {}));

    static auto const bool_ = proto::deep_copy (
      "true" >> qi::attr (true) | "false" >> qi::attr (false));

    static auto const text_ = proto::deep_copy (
      '"' >> qi::raw [*('\\' >> qi::char_ | ~qi::char_('"'))] >> '"');

    value_  = null_ | bool_ | text_ | double_ | object_ | array_;
    member_ = text_ >> ':' >> value_;
    object_ = '{' >> -(member_ % ',') >> '}';
    array_  = '[' >> -(value_ % ',') >> ']';

    BOOST_SPIRIT_DEBUG_NODES((value_)(member_)(object_)(array_))
  }

private:
  qi::rule<It, json:: value (), Skipper> value_;
  qi::rule<It, json::member (), Skipper> member_;
  qi::rule<It, json::object (), Skipper> object_;
  qi::rule<It, json:: array (), Skipper> array_;
};

Usually I need to parse input as json value, but sometimes I need to parse it as json array or json object. Can I do it without rewriting the same grammar over and over where the only difference between these grammars is the entry point?


Solution

  • The closest solution I found was to split the grammar into base and derived classes, and use the different start rules in the derived classes. It does not duplicate the source code a lot, but still looks as overweight solution.

    template <typename It, typename Data, typename Skipper = qi::space_type>
    struct base : qi::grammar<It, Data (), Skipper>
    {
      using jbase_type = base;
    
      template <typename Member>
      base (Member& member) : base::base_type (member)
      {
        using namespace qi;
        using namespace Json;
    
        value_  = null_ | bool_ | text_ | double_ | object_ | array_;
        member_ = text_ >> ':' >> value_;
        object_ = '{' >> -(member_ % ',') >> '}';
        array_  = '[' >> -(value_ % ',') >> ']';
    
        BOOST_SPIRIT_DEBUG_NODES((value_)(member_)(object_)(array_))
      }
    
    protected:
      escaped_string_grammar<It> text_;
    
      qi::rule<It, Json:: Value (), Skipper> value_;
      qi::rule<It, Json::Member (), Skipper> member_;
      qi::rule<It, Json::Object (), Skipper> object_;
      qi::rule<It, Json:: Array (), Skipper> array_;
    };
    
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <typename It, typename Skipper = qi::space_type>
    struct value : base<It, Json::Value, Skipper>
    {
      value () : value::jbase_type (value::jbase_type::value_) {}
    };
    
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <typename It, typename Skipper = qi::space_type>
    struct array : base<It, Json::Array, Skipper>
    {
      array () : array::jbase_type (array::jbase_type::array_) {}
    };
    
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <typename It, typename Skipper = qi::space_type>
    struct object : base<It, Json::Object, Skipper>
    {
      object () : object::jbase_type (object::jbase_type::object_) {}
    };