Search code examples
c++visual-c++boostboost-spiritboost-spirit-qi

Issue with spirit parser attribute propagation rules


I have a small parser in spirit. The problem is with the attribute rules. The rule should be able to parse both

@ATTRIBUTE num NUMERIC

@ATTRIBUTE test {hello, world}

typedef std::string::iterator string_iter_t;
qi::rule<string_iter_t, string()> ident_r= lexeme['"' >> +(char_("a-zA-Z0-9- ")) >> '"'] | lexeme[+char_("a-zA-Z0-9-")];
qi::rule<string_iter_t, string(), space_type> relation_rule = 
    no_case["@RELATION"] >> ident_r;
qi::rule<string_iter_t, optional<vector<string>>(), space_type> attr_val_rule = 
    no_case["NUMERIC"] |
    ('{' >> ident_r % ',' >> '}');

qi::rule<string_iter_t, tuple<string, optional<vector<string>>>(), space_type> attr_rule =
    no_case["@ATTRIBUTE"] >> ident_r >> attr_val_rule;

When I try to compile using the above code, I am getting a big compile error about a type mismatch with the last rule.

The error goes away when I change the last rule to read

qi::rule<string_iter_t, vector<string>(), space_type> attr_rule =
    no_case["@ATTRIBUTE"] >> ident_r >> attr_val_rule;

But there is a run time error when I try to parse

@ATTRIBUTE test NUMERIC

An assert in boost::optional is triggered because the parameter was not set. I get the following

Assertion failed: this->is_initialized(), file c:\Libs\boost_1_52_0\boost/option
al/optional.hpp, line 630

Can anyone help me understand what I am missing?

The error I get when I compile

c:\Libs\boost_1_52_0\boost/spirit/home/qi/detail/assign_to.hpp(152) : error C244
0: 'static_cast' : cannot convert from 'const std::basic_string<_Elem,_Traits,_A
lloc>' to 'std::tuple<<unnamed-symbol>,_V0_t>'
    with
    [
        _Elem=char,
        _Traits=std::char_traits<char>,
        _Alloc=std::allocator<char>
    ]
    and
    [
        <unnamed-symbol>=std::string,
        _V0_t=boost::optional<std::vector<std::string>>
    ]
    No constructor could take the source type, or constructor overload resol
ution was ambiguous
    c:\Libs\boost_1_52_0\boost/spirit/home/qi/detail/assign_to.hpp(170) : se
e reference to function template instantiation 'void boost::spirit::traits::assi
gn_to_attribute_from_value<Attribute,T>::call<T>(const T_ &,Attribute &,boost::m
pl::false_)' being compiled
    with
    [
        Attribute=std::tuple<std::string,boost::optional<std::vector<std::st
ring>>>,
        T=std::basic_string<char,std::char_traits<char>,std::allocator<char>
>,
        T_=std::basic_string<char,std::char_traits<char>,std::allocator<char
>>
    ] ..... and goes on and on

A copy of the program I am using

    int main(void)
    {
using boost::trim_left;
using boost::istarts_with;
using boost::optional;
namespace qi = boost::spirit::qi;
using qi::no_case;
using qi::ascii::space;
using qi::char_;
using qi::lexeme;
using qi::ascii::space_type;

typedef std::string::iterator string_iter_t;
qi::rule<string_iter_t, string()> ident_r= lexeme['"' >> +(char_("a-zA-Z0-9- ")) >> '"'] | lexeme[+char_("a-zA-Z0-9-")];
qi::rule<string_iter_t, string(), space_type> relation_rule = 
    no_case["@RELATION"] >> ident_r;
qi::rule<string_iter_t, optional<vector<string>>(), space_type> attr_val_rule = 
    no_case["NUMERIC"] |
    ('{' >> ident_r % ',' >> '}');
qi::rule<string_iter_t, vector<string>(), space_type> attr_rule =
    no_case["@ATTRIBUTE"] >> ident_r >> attr_val_rule;
string ss1 = "@ATTRIBUTE test NUMERIC";

vector<string> ans1;
bool ret1 = qi::phrase_parse(ss1.begin(), ss1.end(), attr_rule, space, ans1);
cout << boolalpha << ret1 << endl;

optional<vector<string>> ss;
string ss2 = "{hello, world}";
bool ret2 = qi::phrase_parse(ss2.begin(), ss2.end(), attr_val_rule, space, ss);
cout << ret2 << endl;

return 0;
    }

Solution

  • The workaround posted by Anand works, in a way. However, a much more convenient answer is at hand.

    #include <boost/fusion/adapted.hpp>
    // or specificly for std::tuple:
    #include <boost/fusion/adapted/std_tuple.hpp>
    

    Consider, for example, when you don't want to, or cannot, change the attribute data types.

    Here is a demonstration with a few style issues removed live on http://liveworkspace.org/code/Mwpah.

    Output:

    =======================================
    Trying to parse '@attribute ident1 numeric'
    parse success
    data: ident1 
    =======================================
    Trying to parse '@attribute ident2 { field1, field2, field3 }'
    parse success
    data: ident2 field1 field2 field3 
    =======================================
    Trying to parse '@attribute ident3 { }'
    parse failed: '@attribute ident3 { }'
    trailing unparsed: '@attribute ident3 { }'
    

    Full code:

    #include <boost/fusion/adapted.hpp>
    #include <boost/optional.hpp>
    #include <boost/variant.hpp>
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/karma.hpp>
    
    namespace qi    = boost::spirit::qi;
    namespace karma = boost::spirit::karma;
    
    typedef std::string rel_t;
    typedef boost::optional<std::vector<std::string>> fields_t;
    typedef std::tuple<std::string, fields_t> attr_t;
    typedef boost::variant<attr_t, rel_t> parsed_t;
    
    template <typename It, typename Skipper = qi::space_type>
        struct parser : qi::grammar<It, attr_t(), Skipper>
    {
        parser() : parser::base_type(attr_rule)
        {
            using namespace qi;
    
            ident_r       = lexeme // technically redundant since the rule is skipperless
                [
                      ('"' >> +char_("a-zA-Z0-9- ") >> '"') 
                    | +char_("a-zA-Z0-9-") 
                ];
    
            relation_rule = no_case["@RELATION"]      >> ident_r;
            attr_val_rule = no_case["NUMERIC"] | ('{' >> ident_r % ',' >> '}');
            attr_rule     = no_case["@ATTRIBUTE"]     >> ident_r >> attr_val_rule;
            start         = attr_rule | relation_rule;
    
            BOOST_SPIRIT_DEBUG_NODE(start);
            BOOST_SPIRIT_DEBUG_NODE(attr_rule);
            BOOST_SPIRIT_DEBUG_NODE(attr_val_rule);
            BOOST_SPIRIT_DEBUG_NODE(relation_rule);
            BOOST_SPIRIT_DEBUG_NODE(ident_r);
        }
    
      private:
        qi::rule<It, std::string()> ident_r;
        qi::rule<It, fields_t(), Skipper> attr_val_rule;
        qi::rule<It, rel_t()   , Skipper> relation_rule;
        qi::rule<It, attr_t()  , Skipper> attr_rule;
    
        qi::rule<It, parsed_t(), Skipper> start;
    };
    
    bool doParse(std::string const& input)
    {
        auto f(std::begin(input)), l(std::end(input));
    
        parser<decltype(f), qi::space_type> p;
    
        std::cout << "=======================================\n";
        std::cout << "Trying to parse '" << input << "'\n";
    
        try
        {
            attr_t data;
            bool ok = qi::phrase_parse(f,l,p,qi::space,data);
            if (ok)   
            {
                std::cout << "parse success\n";
                std::cout << "data: " << karma::format_delimited(karma::auto_, ' ', data) << "\n";
            }
            else      std::cerr << "parse failed: '" << std::string(f,l) << "'\n";
    
            if (f!=l) std::cerr << "trailing unparsed: '" << std::string(f,l) << "'\n";
            return ok;
        } catch(const qi::expectation_failure<decltype(f)>& e)
        {
            std::string frag(e.first, e.last);
            std::cerr << e.what() << "'" << frag << "'\n";
        }
    
        return false;
    }
    
    int main()
    {
        assert(true == doParse("@attribute ident1 numeric"));
        assert(true == doParse("@attribute ident2 { field1, field2, field3 }"));
        assert(false== doParse("@attribute ident3 { }"));
        //// enable relation_rule (requires tuple IO streaming for test output)
        // assert(true == doParse("@relation rel1"));
    }