0

I'm trying to use boost's spirit to reimplement the logos parsing perl script from iPhone jailbreaking development.

An example of input is:

%hook SBLockScreenView
-(void)setCustomSlideToUnlockText:(id)arg1
{
  arg1 = @"Changed the slider";
  %orig(arg1);
}
%end

I so far have:

namespace logos
{
  namespace qi = boost::spirit::qi;
  namespace ascii = boost::spirit::ascii;

  struct class_hook
  {
    std::string class_name;
    std::string method_signature;
    std::string method_body;
  };

  template <typename Iterator>
  struct class_hook_parser : qi::grammar<Iterator, class_hook(), ascii::space_type>
  {
    class_hook_parser() : class_hook_parser::base_type(start)
    {
      using qi::int_;
      using qi::lit;
      using qi::on_error;
      using qi::fail;
      using qi::double_;
      using qi::lexeme;
      using ascii::char_;

      hooked_class %= lexeme[+(char_("a-zA-Z") - '-')];
      method_sig %= lexeme[+(char_) - '{'];
      method_body %= lexeme[+(char_ - '}')];

      start %=
        lit("%hook")
        >> hooked_class
        >> method_sig
        >> method_body
        >> lit("%end")
        ;

      on_error<fail>
    (
     start,
     boost::phoenix::ref(std::cout) << "Something errored!" << std::endl);

    }
    qi::rule<Iterator, std::string(), ascii::space_type> hooked_class;
    qi::rule<Iterator, std::string(), ascii::space_type> method_sig;
    qi::rule<Iterator, std::string(), ascii::space_type> method_body;
    qi::rule<Iterator, class_hook(), ascii::space_type> start;

  };

}

BOOST_FUSION_ADAPT_STRUCT(logos::class_hook,
              (std::string, class_name)
              (std::string, method_signature)
              (std::string, method_body))

typedef std::string::const_iterator iterator_type;
typedef logos::class_hook_parser<iterator_type> class_hook_parser;

using boost::spirit::ascii::space;
std::string::const_iterator
  iter = std::begin(tweak_source_code),
  end = std::end(tweak_source_code);

class_hook_parser g;
logos::class_hook emp;
bool r = phrase_parse(iter, end, g, space, emp);
if (r) {
  std::cout << "Got: " << boost::fusion::as_vector(emp) << std::endl;
}
else std::cout << "Something isn't working" << std::endl;

But this oddly only prints out the Something isn't working message, not the on_fail callback. Where is my mistake in the parsing and how can I get actually working and informative parse error messages?

1 Answers1

3

Did you mean

+(char_ - '{')

instead of

+(char_) - '{'

And likely, you'd require the body to begin with that { that was rejected as part of the signature. Here's my fixed version:

    hooked_class = +char_("a-zA-Z");
    method_sig   = +(char_ - '{');
    method_body  = '{' >> +(char_ - '}') >> '}';

Notes:

  • Dropping the skipper allows you to drop the lexeme[] directive too.
  • Rejecting - from the "a-zA-Z" set is useless (it's not in it...).
  • method_sig now includes all whitespace (including the trailing newline)
  • Use BOOST_SPIRIT_DEBUG to get insight in why your grammar works in mysterious ways

See also: Boost spirit skipper issues

Live On Coliru

//#define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace logos
{
    namespace qi    = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;

    struct class_hook
    {
        std::string class_name;
        std::string method_signature;
        std::string method_body;
    };

    template <typename Iterator>
        struct class_hook_parser : qi::grammar<Iterator, class_hook(), ascii::space_type>
    {
        class_hook_parser() : class_hook_parser::base_type(start)
        {
            using qi::int_;
            using qi::lit;
            using qi::on_error;
            using qi::fail;
            using qi::double_;
            using qi::lexeme;
            using ascii::char_;

            hooked_class = +char_("a-zA-Z");
            method_sig   = +(char_ - '{');
            method_body  = '{' >> +(char_ - '}') >> '}';

            start        = "%hook"
                       >> hooked_class
                       >> method_sig
                       >> method_body
                       >> "%end"
                       ;

            on_error<fail> (start,
                    boost::phoenix::ref(std::cout) << "Something errored\n"
                );

            BOOST_SPIRIT_DEBUG_NODES((hooked_class)(method_sig)(method_body)(start))
        }
      private:
        qi::rule<Iterator, std::string()> hooked_class, method_sig, method_body;
        qi::rule<Iterator, class_hook(), ascii::space_type> start;
    };
}

BOOST_FUSION_ADAPT_STRUCT(logos::class_hook, class_name, method_signature, method_body)

int main() {
    typedef std::string::const_iterator iterator_type;
    typedef logos::class_hook_parser<iterator_type> class_hook_parser;

    std::string const tweak_source_code = R"(
%hook SBLockScreenView
-(void)setCustomSlideToUnlockText:(id)arg1
{
  arg1 = @"Changed the slider";
  %orig(arg1);
}
%end
        )";

    using boost::spirit::ascii::space;
    iterator_type iter = std::begin(tweak_source_code), end = std::end(tweak_source_code);

    class_hook_parser g;
    logos::class_hook emp;

    bool r = phrase_parse(iter, end, g, space, emp);

    if (r) {
        std::cout << "Got: " << boost::fusion::as_vector(emp) << "\n";
    } else {
        std::cout << "Something isn't working\n";
    }
}

Prints

Got: (SBLockScreenView -(void)setCustomSlideToUnlockText:(id)arg1

  arg1 = @"Changed the slider";
  %orig(arg1);
)
Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • At the risk of writing another question, what can I do to generalize this, i.e. say a list of these hook declarations? I imagine its like vector.push_back(parsed_result)? –  Feb 10 '16 at 00:04
  • Indeed, that's another question. Look here: http://ciere.com/cppnow15/x3_docs/spirit/quick_reference/operator.html – sehe Feb 10 '16 at 00:17