Search code examples
boostboost-spiritboost-bindboost-phoenix

boost phoenix bind a semantic action with multiple parameter and a return value


I'm new to both C++ and Boost spirit.

I'm stuck on this for a day now. I want to parse two strings separated by a dot. basically, I need following strings to be parsed to an integer.

eg: [field] --> integer // working

eg2: [instance.field] --> integer // not working

For the 2nd one, I need to take two strings as parameters and evaluate them and return the relevant integer value.

I must have missed a basic thing, but I can't figure it out.

Please let me know the error in my code or a better way to do it. calling a method and getting the value is needed. I can't change that.

this is the code peace.

#define BOOST_SPIRIT_USE_PHOENIX_V3

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/support_utree.hpp>

#include <iostream>
#include <string>


namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace spirit = boost::spirit;
namespace phx = boost::phoenix;


int fieldVal(std::vector<char> fieldName) { 
    std::string fieldValue(fieldName.begin(), fieldName.end());
    std::cout << "Field Name Recieved.::: " << fieldValue << std::endl;
    int i = 50; //just for test 
    return i;
}

int instanceFieldVal(std::string instance, std::string fieldName) {

    std::cout << "Recieved ::: " << instance <<" : "<< fieldName << std::endl;
    int i = 60; //just for test
    return i;
}

namespace client
{
    template <typename Iterator>
    struct calculator : qi::grammar<Iterator, ascii::space_type, int()>
    {
        calculator() : calculator::base_type(instanceFieldValue)
      /*change base type to "field" and comment everything relevant to 
        "instanceFieldValue", then it's working */

        {
            using qi::int_;
            using qi::_val;
            using qi::_1;
            using qi::char_;
            using qi::lexeme;

            field = lexeme[
                '['
                    >> +(~char_(".]["))
                    >> ']'
            ][qi::_val = phx::bind(&fieldVal, qi::_1)]; // this is working


            instanceField = '['
                    >> +(~char_(".]["))
                    >> '.'
                    >> +(~char_(".]["))
                    >> ']';

            instanceFieldValue 
                = instanceField[qi::_val = phx::bind(&instanceFieldVal, qi::_1)]; 
            // how ^this line should be changed??

        }

        qi::rule<Iterator, ascii::space_type, int()> field, instanceFieldValue;

        qi::rule<Iterator, ascii::space_type, std::string(), std::string()>instanceField;
    };
}


int main()
{

    std::cout << "Type an expression...or [q or Q] to quit\n\n";

    using boost::spirit::ascii::space;
    using boost::spirit::utree;
    typedef std::string::const_iterator iterator_type;
    typedef client::calculator<iterator_type> calculator;

    calculator calc; // Our grammar

    std::string str;
    while (std::getline(std::cin, str))
    {
        if (str.empty() || str[0] == 'q' || str[0] == 'Q')
            break;

        std::string::const_iterator iter = str.begin();
        std::string::const_iterator end = str.end();
        int val;
        bool r = phrase_parse(iter, end, calc, space, val);

        if (r && iter == end)
        {
            std::cout << "-------------------------\n";
            std::cout << "Parsing succeeded: " << val << "\n";
            std::cout << "-------------------------\n";
        }
        else
        {
            std::string rest(iter, end);
            std::cout << "-------------------------\n";
            std::cout << "Parsing failed\n";
            std::cout << "stopped at: \": " << rest << "\"\n";
            std::cout << "-------------------------\n";
        }
    }

    std::cout << "Bye... :-) \n\n";
    return 0;
}

Solution

  • Well, you say it's a function with multiple arguments, but then you only pass one arg: _1.

    How can that work? It can't. It's also completely what you even want to pass, because the second parameter is likely from the instanceField expression, but the first is ... magical context.

    Like always, I'd try to minimize state and semantic actions. In fact, I'd suggest separation of concerns:

    1. parse to std:vector<std::string> first
    2. on successful parse, evaluate it

    That would lead to a grammar like

    template <typename Iterator>
    struct path : qi::grammar<Iterator, std::deque<std::string>()> {
        path() : path::base_type(start) {
            using namespace qi;
    
            name          = +(graph - char_(".][")); // not matching spaces please
            qualifiedName = name % '.';
    
            start = skip(ascii::space) ['[' >> qualifiedName >> ']'];
    
            BOOST_SPIRIT_DEBUG_NODES((start)(qualifiedName)(name))
        }
    
      private:
        qi::rule<Iterator, std::deque<std::string>(), qi::ascii::space_type> qualifiedName;
        qi::rule<Iterator, std::string()> name;
        qi::rule<Iterator, std::deque<std::string>()> start;
    };
    

    You can of course combine it using a semantic action if you insist:

    template <typename Iterator>
    struct property : qi::grammar<Iterator, int()> {
        property() : property::base_type(start) {
            using namespace qi;
    
            name          = +(graph - char_(".][")); // not matching spaces please
            qualifiedName = name % '.';
    
            start = skip(ascii::space) ['[' >> qualifiedName >> ']']
                            [_val = phx::bind(lookup, phx::cref(sample), _1) ]
                    ;
    
            BOOST_SPIRIT_DEBUG_NODES((start)(qualifiedName)(name))
        }
    
      private:
        qi::rule<Iterator, std::deque<std::string>(), qi::ascii::space_type> qualifiedName;
        qi::rule<Iterator, std::string()> name;
        qi::rule<Iterator, int()> start;
    };
    

    Now, just implement a function

    int lookup(Node const& context, std::deque<std::string> path);
    

    As an example, let's imagine a sample context:

    using Leaf    = int;
    using Node    = boost::make_recursive_variant< Leaf, std::map<std::string, boost::recursive_variant_> >::type;
    using SubTree = std::map<std::string, Node>;
    
    static Node sample = SubTree {
        { "simple", 100 },
        { "compound", SubTree { 
              { "first", 200 },
              { "second", 300 },
          }, },
        { "deep", SubTree {  
            { "nested", SubTree { 
                { "compound", SubTree { 
                      { "buried", 400 },
                      { "secrets", 500 },
        }, }, }, }, }, }
    };
    

    Now, an implementation of lookup could be as simple as:

    int lookup(Node const& context, std::deque<std::string> path) {
        if (path.empty())
            return boost::get<Leaf>(context);
    
        auto& sub = boost::get<SubTree>(context);
    
        auto element = path.front();
        path.pop_front();
    
        try {
            return lookup(sub.at(element), std::move(path));
        } catch(std::out_of_range const& e) {
            throw std::runtime_error("'" + element + "' not found");
        }
    }
    

    Full Demo

    Live On Coliru

    //#define BOOST_SPIRIT_DEBUG
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    #include <iostream>
    #include <map>
    
    namespace qi  = boost::spirit::qi;
    namespace phx = boost::phoenix;
    
    using Leaf    = int;
    using Node    = boost::make_recursive_variant< Leaf, std::map<std::string, boost::recursive_variant_> >::type;
    using SubTree = std::map<std::string, Node>;
    
    static Node sample = SubTree {
        { "simple", 100 },
        { "compound", SubTree { 
                  { "first", 200 },
                  { "second", 300 },
              }, },
        { "deep", SubTree {  
                { "nested", SubTree { 
                        { "compound", SubTree { 
                                  { "buried", 400 },
                                  { "secrets", 500 },
                              },
                        },
                    },
                },
            },
        }
    };
    
    int lookup(Node const& context, std::deque<std::string> path) {
        if (path.empty())
            return boost::get<Leaf>(context);
    
        auto& sub = boost::get<SubTree>(context);
    
        auto element = path.front();
        path.pop_front();
    
        try {
            return lookup(sub.at(element), std::move(path));
        } catch(std::out_of_range const& e) {
            throw std::runtime_error("'" + element + "' not found");
        }
    }
    
    namespace parser {
    
        template <typename Iterator>
        struct property : qi::grammar<Iterator, int()> {
            property() : property::base_type(start) {
                using namespace qi;
    
                name          = +(graph - char_(".][")); // not matching spaces please
                qualifiedName = name % '.';
    
                start = skip(ascii::space) ['[' >> qualifiedName >> ']']
                                [_val = phx::bind(lookup, phx::cref(sample), _1) ]
                        ;
    
                BOOST_SPIRIT_DEBUG_NODES((start)(qualifiedName)(name))
            }
    
          private:
            qi::rule<Iterator, std::deque<std::string>(), qi::ascii::space_type> qualifiedName;
            qi::rule<Iterator, std::string()> name;
            qi::rule<Iterator, int()> start;
        };
    }
    
    int main() {
        using It =  std::string::const_iterator;
        parser::property<It> calc;
    
        for (std::string const str : {
                    "[simple]",
                    "[compound.first]",
                    "[compound.second]",
                    "[deep.nested.compound.buried]",
                    "[deep.nested.compound.secrets]",
                    // whitespace is ok
                    "  [ compound.\tfirst ]",
                    // failing:
                    "[]",
                    "[missing]",
                    "[deep.missing.compound.buried]",
                    // whitespace not ok inside names
                    "  [ compound.\tfi rst ]",
                })
        try {
            std::cout << " ===== Input: '" << str << "'\n";
            It iter = str.begin(), end = str.end();
    
            int val;
            bool r = parse(iter, end, calc, val);
    
            if (r) {
                std::cout << "Parsing succeeded: " << val << "\n";
            } else {
                std::cout << "Parsing failed\n";
            }
    
            if (iter != end) {
                std::cout << " - Remaining unparsed input: '" << std::string(iter, end) << "'\n";
            }
        } catch(std::exception const& e) {
            std::cout << "Exception: " << e.what() << "\n";
        }
    }
    

    Prints:

     ===== Input: '[simple]'
    Parsing succeeded: 100
     ===== Input: '[compound.first]'
    Parsing succeeded: 200
     ===== Input: '[compound.second]'
    Parsing succeeded: 300
     ===== Input: '[deep.nested.compound.buried]'
    Parsing succeeded: 400
     ===== Input: '[deep.nested.compound.secrets]'
    Parsing succeeded: 500
     ===== Input: '  [ compound.    first ]'
    Parsing succeeded: 200
     ===== Input: '[]'
    Parsing failed
     - Remaining unparsed input: '[]'
     ===== Input: '[missing]'
    Exception: 'missing' not found
     ===== Input: '[deep.missing.compound.buried]'
    Exception: 'missing' not found
     ===== Input: '  [ compound.    fi rst ]'
    Parsing failed
     - Remaining unparsed input: '  [ compound. fi rst ]'