1

The parser

        namespace qi = boost::spirit::qi;

        template<typename T>
        class action
        {
        public:
            action(std::vector<std::shared_ptr<part>>& parts) : m_parts{ parts } {}

            void operator()(const std::vector<char>& cc, qi::unused_type, qi::unused_type) const
            {
                std::string s(cc.begin(), cc.end());
                if (s.length() > 0) {
                    auto p = new T(s);
                    m_parts.push_back(std::shared_ptr<part>(p));
                }
            }
        private:
            std::vector<std::shared_ptr<part>>& m_parts;
        };

        std::vector<std::shared_ptr<part>> parse(const std::string& source) {
            namespace ascii = boost::spirit::ascii;

            using ascii::char_;
            using qi::lit;

            std::vector<std::shared_ptr<part>> parts;

            auto prop_g = lit("{{=")
                >> *char_(' ')
                >> (*(char_ - char_("} ")))[action<property_part>(parts)]
                >> *char_(' ')
                >> "}}"
                ;
            auto text_g = (+(char_ - '{'))[action<text_part>(parts)];
            auto g = -text_g >> +(prop_g >> text_g);

            qi::parse(source.begin(), source.end(), g);
            return parts;
        }

causes a fault on the qi::parse call when testing on a Kitkat device. The fault happens before any semantic action is called. The same code works on Xcode 6/iOS 8.4 and VS 2015. We're using Boost 1.59.

We could replace Spirit with Bison implying an extra build step, or use Clang with the Android NDK, getting us off the beaten path.

Can this fault be fixed with build configuration or are there other options we could explore?

Hans Malherbe
  • 2,988
  • 24
  • 19

1 Answers1

2

Of course errors can be fixed. You do not show the grammar See Update You show a lot of parser expressions with unknowns, though, so there's no way we could even start to reason about your code.

One thing that does stand out is the (ab)use of auto, though.

A quick google should point you at ~6 questions on SO that warn about this.

You can not use auto with Spirit's expression templates. In all but the very very trivial cases (unparameterized stateless terminals) this leads directly to Undefined Behaviour

Try using

  • qi::rule<> to contain/group the expressions
  • boost::spirit::copy or boost::proto::deep_copy

UPDATE

To the edited question: indeed the grammar crashed due to undefined behaviour (if this ever "appeared to work", you were just (un)lucky!).

Here's a fixed version, as a bonus I removed the action<> thing replacing it with existing Phoenix mechanisms and attribute propagation.

You may want to study the changes side-by-side to find out exactly what I changed.

Live On Coliru

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

struct part {
    part(std::string s="") : _value(std::move(s)) {}
    virtual ~part() { }
    virtual void do_print(std::ostream& os) const = 0;
  protected:
    std::string _value;
};

struct property_part : part { 
    using part::part; 
    void do_print(std::ostream& os) const { os << "{{=" << _value << "}}"; }
};
struct text_part     : part { 
    using part::part; 
    void do_print(std::ostream& os) const { os << "'" << _value << "'"; }
};

std::vector<std::shared_ptr<part>> parse(const std::string& source) {
    namespace qi = boost::spirit::qi;
    namespace px = boost::phoenix;
    using boost::spirit::ascii::char_;
    using qi::lit;

    typedef std::shared_ptr<part> pptr;
    qi::rule<std::string::const_iterator, pptr()> prop_g, text_g;

    // this is ok: purely stateless expression template
    prop_g = lit("{{=")
        >> *char_(' ')
        >> qi::as_string [ +~char_("} ") ] [ qi::_val = px::construct<pptr>(px::new_<property_part>(qi::_1)) ]
        >> *char_(' ')
        >> "}}"
        ;

    text_g = qi::as_string [ +~char_('{') ] [ qi::_val = px::construct<pptr>(px::new_<text_part>(qi::_1)) ];

    std::vector<pptr> parts;
    qi::parse(source.begin(), source.end(), -text_g >> +(prop_g >> text_g), parts);

    return parts;
}

int main() {
    auto result = parse("My book is about {{= this-is-a-(bogus)-property-part }} else entirely {{=byebye}}");
    assert(result.size() == 4);

    for(auto item : result)
        item->do_print(std::cout);
}

Prints

'My book is about '{{=this-is-a-(bogus)-property-part}}' else entirely '{{=byebye}}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thanks, I'll try your recommendations. I've added the semantic action to the code snippet to complete the parser. – Hans Malherbe Sep 17 '15 at 07:27
  • I still had to add some stuff there :/ See the updated answer with a fixed grammar. Consider keeping the rules static/shared (they can be const). Also note, it looks like you're doing macro expansion but very (very!!) inefficiently. Look at [this very flexible sample](http://stackoverflow.com/questions/9404558/a/9405546?s=3|0.4015#9405546), this [simple one using spirit](http://stackoverflow.com/questions/17112494/a/17126962#17126962), the same [not using spirit](http://stackoverflow.com/questions/17112494/a/17128601#17128601). – sehe Sep 17 '15 at 08:37
  • In case you really wanted to reuse the template **and** be efficient, [this answer uses Spirit + ICL to parse templates](http://stackoverflow.com/a/28521682/85371) that can be very efficiently expanded repeatedly. – sehe Sep 17 '15 at 08:38