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

Why can I not access the value in a semantic action?


I'm trying to write a parser to create an AST using boost::spirit. As a first step I'm trying to wrap numerical values in an AST node. This is the code I'm using:

AST_NodePtr make_AST_NodePtr(const int& i) {
    return std::make_shared<AST_Node>(i);
}

namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace l = qi::labels;

 template<typename Iterator>
    struct test_grammar : qi::grammar<Iterator, AST_NodePtr(), ascii::space_type> {
        test_grammar() : test_grammar::base_type(test) {

            test = qi::int_ [qi::_val = make_AST_NodePtr(qi::_1)];
        }
        qi::rule<Iterator, AST_NodePtr(), ascii::space_type> test;

    }; 

As far as I understood it from the documentation q::_1 should contain the value parsed by qi::int_, but the above code always gives me an error along the lines

invalid initialization of reference of type ‘const int&’ from expression of type ‘const _1_type {aka const boost::phoenix::actor<boost::spirit::argument<0> >}

Why does this not work even though qi::_1 is supposed to hold the parsed valued? How else would I parse the input into a custom AST?


Solution

  • You're using a regular function inside the semantic action.

    This means that in the contructor the compiler will try to invoke that make_AST_NodePtr function with the argument supplied: qi::_1.

    Q. Why does this not work even though qi::_1 is supposed to hold the parsed valued?

    A. qi::_1 does not hold the parsed value. It represents (is-a-placeholder-for) the first unbound argument in the function call

    This can /obviously/ never work. The function expects an integer.

    So what gives?


    You need to make a "lazy" or "deferred" function for use in the semantic action. Using only pre-supplied Boost Phoenix functors, you could spell it out:

    test  = qi::int_ [ qi::_val = px::construct<AST_NodePtr>(px::new_<AST_Node>(qi::_1)) ];
    

    You don't need the helper function this way. But the result is both ugly and suboptimal. So, let's do better!

    Using a Phoenix Function wrapper

    struct make_shared_f {
        std::shared_ptr<AST_Node> operator()(int v) const {
            return std::make_shared<AST_Node>(v);
        }
    };
    px::function<make_shared_f> make_shared_;
    

    With this defined, you can simplify the semantic action to:

    test  = qi::int_ [ qi::_val = make_shared_(qi::_1) ];
    

    Actually, if you make it generic you can reuse it for many types:

    template <typename T>
        struct make_shared_f {
            template <typename... Args>
                std::shared_ptr<T> operator()(Args&&... args) const {
                    return std::make_shared<T>(std::forward<Args>(args)...);
                }
        };
    px::function<make_shared_f<AST_Node> > make_shared_;
    

    DEMO

    Here's a self-contained example showing some style fixes in the process:

    Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <memory>
    
    struct AST_Node {
        AST_Node(int v) : _value(v) {}
        int value() const { return _value; }
      private:
        int _value;
    };
    
    using AST_NodePtr = std::shared_ptr<AST_Node>;
    
    AST_NodePtr make_AST_NodePtr(const int& i) {
        return std::make_shared<AST_Node>(i);
    }
    
    namespace qi    = boost::spirit::qi;
    namespace px    = boost::phoenix;
    
    template<typename Iterator>
    struct test_grammar : qi::grammar<Iterator, AST_NodePtr()> {
        test_grammar() : test_grammar::base_type(start) {
            using boost::spirit::ascii::space;
    
            start = qi::skip(space) [ test ];
    
            test  = qi::int_ [ qi::_val = make_shared_(qi::_1) ];
        }
      private:
        struct make_shared_f {
            std::shared_ptr<AST_Node> operator()(int v) const {
                return std::make_shared<AST_Node>(v);
            }
        };
        px::function<make_shared_f> make_shared_;
        //
        qi::rule<Iterator, AST_NodePtr()> start;
        qi::rule<Iterator, AST_NodePtr(), boost::spirit::ascii::space_type> test;
    }; 
    
    int main() {
    AST_NodePtr parsed;
    
        std::string const input ("42");
        auto f = input.begin(), l = input.end();
        test_grammar<std::string::const_iterator> g;
        bool ok = qi::parse(f, l, g, parsed);
    
        if (ok) {
            std::cout << "Parsed: " << (parsed? std::to_string(parsed->value()) : "nullptr") << "\n";
        } else {
            std::cout << "Failed\n";
        }
    
        if (f!=l)
        {
            std::cout << "Remaining input: '" << std::string(f, l) << "'\n";
        }
    }
    

    Prints

    Parsed: 42
    

    BONUS: Alternative using BOOST_PHOENIX_ADAPT_FUNCTION

    You can actually use your free function if you wish, and use it as follows:

    Live On Coliru

    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <memory>
    
    struct AST_Node {
        AST_Node(int v) : _value(v) {}
        int value() const { return _value; }
      private:
        int _value;
    };
    
    using AST_NodePtr = std::shared_ptr<AST_Node>;
    
    AST_NodePtr make_AST_NodePtr(int i) {
        return std::make_shared<AST_Node>(i);
    }
    
    BOOST_PHOENIX_ADAPT_FUNCTION(AST_NodePtr, make_AST_NodePtr_, make_AST_NodePtr, 1)
    
    namespace qi    = boost::spirit::qi;
    namespace px    = boost::phoenix;
    
    template<typename Iterator>
    struct test_grammar : qi::grammar<Iterator, AST_NodePtr()> {
        test_grammar() : test_grammar::base_type(start) {
            using boost::spirit::ascii::space;
    
            start = qi::skip(space) [ test                            ] ;
            test  = qi::int_        [ qi::_val = make_AST_NodePtr_(qi::_1) ] ;
        }
      private:
        qi::rule<Iterator, AST_NodePtr()> start;
        qi::rule<Iterator, AST_NodePtr(), boost::spirit::ascii::space_type> test;
    }; 
    
    int main() {
    AST_NodePtr parsed;
    
        std::string const input ("42");
        auto f = input.begin(), l = input.end();
        test_grammar<std::string::const_iterator> g;
        bool ok = qi::parse(f, l, g, parsed);
    
        if (ok) {
            std::cout << "Parsed: " << (parsed? std::to_string(parsed->value()) : "nullptr") << "\n";
        } else {
            std::cout << "Failed\n";
        }
    
        if (f!=l)
        {
            std::cout << "Remaining input: '" << std::string(f, l) << "'\n";
        }
    }