sofname
sofname

Reputation: 441

Boost Spirit Qi Symbols default value and NULL value

Boost Spirit qi::symbols implements a map of key-value pairs: give a key of a string, it can return a certain value. My questions are:

1) For a blank string, is it possible to return a default value? (Q1 in code)

2) For a string that is other than a blank string or the keys listed in the map of key-value pairs, is it possible to return a value indicating the key is invalid? (Q2 in code)

** The following code is based on the BOOST SPIRIT document. ** Thanks in advance for any suggestions.

#include <boost/spirit/include/support_utree.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/assert.hpp>
#include <iostream>
#include <string>
#include <cstdlib>

template <typename P, typename T>
void test_parser_attr(
    char const* input, P const& p, T& attr, bool full_match = true)
{
    using boost::spirit::qi::parse;

    char const* f(input);
    char const* l(f + strlen(f));
    if (parse(f, l, p, attr) && (!full_match || (f == l)))
        std::cout << "ok" << std::endl;
    else
        std::cout << "fail" << std::endl;
}

int main()
{
    using boost::spirit::qi::symbols;

    symbols<char, int> sym;
    sym.add
        ("Apple", 1)
        ("Banana", 2)
        ("Orange", 3)
    ;

    int i;
    test_parser_attr("Banana", sym, i);
    std::cout << i << std::endl;      // 2


    test_parser_attr("", sym, i);     // Q1: key is "",
    std::cout << i << std::endl;      // would like it to be 1 as default

    test_parser_attr("XXXX", sym, i); // Q2: key is other than "Apple"/"Banana"/"Orange",
    std::cout << i << std::endl;      // would like it to be 4

    return 0;
}

Upvotes: 4

Views: 442

Answers (1)

llonesmiz
llonesmiz

Reputation: 155

You can't accomplish what you want using just a qi::symbols. It should be possible to create a Spirit terminal/directive that achieves the desired result but doing so would be really complex and require knowledge of the inner workings of qi::symbols and related classes, so I don't think it's a worthwhile approach. Luckily there is a really simple alternative using qi::attr(val), a parser that doesn't consume any input, exposes val as its attribute and always succeeds.

Let's see all three cases:

  • if the string is in the symbol table return its associated value ->just use sym
  • if the string is empty return 1 -> just use attr(1)
  • if the string is not in the symbol table return 4 -> here you need to use attr(4) but that isn't enough since you also need to consume the string. If we assume the string is composed of only letters omit[+alpha] could work (omit discards the text, and + makes sure that there is at least one letter).

You need to put these three parsers in an alternative parser keeping in mind that the parser actually used in each case will be the first one to succeed:

sym | omit[+alpha] >> attr(4) | attr(1)

Possible problems:

  • If your not-in-symbol-table string can be more complex you'll need to change +alpha appropriately.
  • If you are using a skipper you'll probably need to use omit[lexeme[+alpha]] to stop the greedy +.

Full Sample (Running on WandBox)

#include <iostream>
#include <string>

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


template <typename P, typename T>
void test_parser_attr(
    char const* input, P const& p, T& attr, bool full_match = true)
{
    using boost::spirit::qi::parse;

    char const* f(input);
    char const* l(f + strlen(f));
    if (parse(f, l, p, attr) && (!full_match || (f == l)))
        std::cout << "ok" << std::endl;
    else
        std::cout << "fail" << std::endl;
}

int main()
{
    using boost::spirit::qi::symbols;

    symbols<char, int> sym;
    sym.add
        ("Apple", 1)
        ("Banana", 2)
        ("Orange", 3)
    ;

    using boost::spirit::qi::attr; 
    using boost::spirit::qi::alpha;
    using boost::spirit::qi::omit;
    using boost::spirit::qi::lexeme;

    //if the text is in the symbol table return the associated value
    //else if the text is formed by letters (you could change this using for example `alnum`) and contains at least one, discard the text and return 4
    //else return 1
    boost::spirit::qi::rule<char const*,int()> symbols_with_defaults = sym | omit[+alpha] >> attr (4) | attr(1);


    int i;
    test_parser_attr("Banana", symbols_with_defaults, i);
    std::cout << i << std::endl;      // 2

    test_parser_attr("", symbols_with_defaults, i);     // Q1: key is "",
    std::cout << i << std::endl;      // would like it to be 1 as default

    test_parser_attr("XXXX", symbols_with_defaults, i); // Q2: key is other than "Apple"/"Banana"/"Orange",
    std::cout << i << std::endl;      // would like it to be 4

    std::vector<int> values;
    test_parser_attr("<Banana>,<>,<xxxx>", ('<' >> symbols_with_defaults >> '>')%',', values);
    for(int val : values)
        std::cout << val << " ";  // should be '2 1 4'
    std::cout << std::endl;

    return 0;
}

Upvotes: 3

Related Questions