c++boost-spiritboost-variant

boost spirint x3 phrase_parse confusion


I defined the output of literal_string as std:: string:

namespace parsesss {
namespace x3 = boost::spirit::x3;
auto const literal_string = x3::rule<class literal_string,std::string>{"literal_string"};
auto const literal_string_def = x3::lexeme['"' >> +(x3::char_ - '"') >> '"'];

BOOST_SPIRIT_DEFINE(literal_string);
}

Then I tested with the following code and the results also met expectations:

    std::string test = "\"asdfjalsdjflajsdlfjalsdf\" \"xxxxxxxxxxasdfjalsdjflajsdlfjalsdf\"";
    std::vector<std::string> out;
    bool r = parse::x3::phrase_parse(test.begin(), test.end(), *(parsesss::literal_string), parse::x3::ascii::space, out);
    if(r && (parse::g_iter == parse::g_end)){
        std::cout << "parse success!" << std::endl;
    }else{
        std::cerr << "parse fail!" << std::endl;
    }

But when I use variant data structures to store output, std:: string degenerates into char, why is this。

struct constant : x3::variant<long,char,double,float,int>{
    using base_type::base_type;
    using base_type::operator=;
};
struct last : x3::variant<std::string,char,constant>{
    using base_type::base_type;
    using base_type::operator=;
};


//
    std::string test = "\"asdfjalsdjflajsdlfjalsdf\" \"xxxxxxxxxxasdfjalsdjflajsdlfjalsdf\"";
    std::vector<last> out;
    bool r = parse::x3::phrase_parse(test.begin(), test.end(), *(parsesss::literal_string), parse::x3::ascii::space, out);
    if(r && (parse::g_iter == parse::g_end)){
        std::cout << "parse success!" << std::endl;
    }else{
        std::cerr << "parse fail!" << std::endl;
    }

Who can tell me why,

Or where can I find more detailed questions about this aspect on the official website。


Solution

  • The variant attribute can be constructed from a char, which is preferred.

    If you drop char from the variant element type list, it will work:

    Live On Coliru

    #include <boost/spirit/home/x3.hpp>
    #include <boost/spirit/home/x3/support/ast/variant.hpp>
    #include <iomanip>
    #include <iostream>
    namespace x3 = boost::spirit::x3;
    
    namespace AST {
        using x3::variant;
        struct constant : variant<long, double, float, int> {
            using base_type::base_type;
            using base_type::operator=;
        };
    
        struct last : variant<std::string, constant> {
            using base_type::base_type;
            using base_type::operator=;
        };
    
        template <typename... T> static std::ostream& operator<<(std::ostream& os, variant<T...> const& v) {
            return boost::apply_visitor([&os](auto&& v) -> auto& { return os << v; }, v);
        }
    }
    
    namespace parsesss {
        auto const literal_string     = x3::rule<class literal_string, std::string>{"literal_string"};
        auto const literal_string_def = x3::lexeme['"' >> *~x3::char_('"') >> '"'];
    
        BOOST_SPIRIT_DEFINE(literal_string);
    } // namespace parsesss
    
    int main() {
        std::string const test = R"("asdf" "xxx yyy")";
    
        if (std::vector<AST::last> out;
            phrase_parse(test.begin(), test.end(), *(parsesss::literal_string) >> x3::eoi, x3::space, out))
            for (auto& el : out) {
                std::cout << " - " << el << std::endl;
            }
        else
            std::cout << "fail (" << quoted(test) << std::endl;
    }
    

    Prints

     - asdf
     - xxx yyy
    

    Attempts

    I tried hinting the attribute system with traits:

    template <> struct x3::traits::is_substitute<AST::last, std::string> : std::true_type {};
    

    But, not unexpectedly, that didn't work.

    Working Workarounds

    What you can do instead is to break the compatibility of "char":

    struct Char {
        char value;
        friend std::ostream& operator<<(std::ostream& os, Char ch) { return os << '\'' << ch.value << '\''; }
    };
    

    This already works (live). You can do shorter here: (live as well)

    enum Char : char;
    

    However, I noticed that these will just give you other trouble when you do want to parse the char literals...

    Solution

    So instead, I took the flight forwards and made some rules to match all the AST nodes.

    As a rule of thumb, being explicit about attribute congruence helps. For example, the "naive" expression for constant will not work:

    constant = x3::double_ | x3::int_;
    

    For two reasons:

    constant 
        = x3::real_parser<double, x3::strict_real_policies<double>>{}
        | x3::int_parser<intmax_t>{}
        ;
    

    Live On Coliru

    #include <boost/spirit/home/x3.hpp>
    #include <boost/spirit/home/x3/support/ast/variant.hpp>
    #include <iomanip>
    #include <iostream>
    namespace x3 = boost::spirit::x3;
    
    namespace AST {
        using x3::variant;
    
        struct constant : variant<intmax_t, double> {
            using base_type::base_type;
            using base_type::operator=;
        };
    
        struct last : variant<std::string, char, constant> {
            using base_type::base_type;
            using base_type::operator=;
        };
    
        template <typename> constexpr std::string_view tname = "?";
    
        template <> constexpr std::string_view tname<std::string> = "string";
        template <> constexpr std::string_view tname<intmax_t>    = "integer";
        template <> constexpr std::string_view tname<double>      = "real";
        template <> constexpr std::string_view tname<char>        = "character";
        template <> constexpr std::string_view tname<constant>    = "constant";
    
        template <typename... T> static std::ostream& operator<<(std::ostream& os, variant<T...> const& v) {
            return boost::apply_visitor(
                [&os](auto const& v) -> auto& {
                    auto type = tname<std::decay_t<decltype(v)>>;
                    return os << type << '(' << v << ')';
                },
                v);
        }
    }
    
    namespace parsesss {
        auto string_lit = x3::rule<class string_lit, std::string>{"string_lit"} =
            x3::lexeme['"' >> *~x3::char_('"') >> '"'];
        auto char_lit = x3::rule<class char_lit, char>{"char_lit"} =
            x3::lexeme["'" >> ('\\' >> x3::char_ | ~x3::char_("'")) >> "'"];
        auto constant = x3::rule<class constant, AST::constant>{"constant"} =
            x3::real_parser<double, x3::strict_real_policies<double>>{} //
            | x3::int_parser<intmax_t>{};
    
        auto last = x3::rule<class last, AST::last>{"last"} =
            string_lit | char_lit | constant;
        
    } // namespace parsesss
    
    int main() {
        for (std::string const test :
             {
                 R"("asdf" "xxx yyy")",
                 R"("asdf" 'c' "xxx yyy")",
                 R"("asdf" '\'' "xxx yyy")",
                 R"("asdf" '\'' 3.14e4 9999 -9999 -inf +nan 3. "xxx yyy")",
             }) //
        {
            std::cout << "testcase " << quoted(test) << std::endl;
            std::vector<AST::last> out;
            if (phrase_parse(test.begin(), test.end(), *parsesss::last >> x3::eoi, x3::space, out))
                for (auto& el : out)
                    std::cout << " - " << el << std::endl;
            else
                std::cout << " - FAIL" << std::endl;
        }
    }
    

    Printing

    testcase "\"asdf\" \"xxx yyy\""
     - string(asdf)
     - string(xxx yyy)
    testcase "\"asdf\" 'c' \"xxx yyy\""
     - string(asdf)
     - character(c)
     - string(xxx yyy)
    testcase "\"asdf\" '\\'' \"xxx yyy\""
     - string(asdf)
     - character(')
     - string(xxx yyy)
    testcase "\"asdf\" '\\'' 3.14e4 9999 -9999 -inf +nan 3. \"xxx yyy\""
     - string(asdf)
     - character(')
     - constant(real(31400))
     - constant(integer(9999))
     - constant(integer(-9999))
     - constant(real(-inf))
     - constant(real(nan))
     - constant(real(3))
     - string(xxx yyy)