Search code examples
c++boostboost-spiritboost-spirit-x3

Spirit X3: Rule that parses a single character and generates a string


Is it possible to create a rule in Spirit X3 that parses a single character and generates a string?

I'd like to use this in the context of a parser for version numbers, where each numeric identifier can be either a single digit, or a non-zero digit followed by one or more digits:

auto const positive_digit = char_(L"123456789");
auto const digit = char_(L"0123456789");
auto const digits = x3::rule<class digits, std::wstring>{"digits"} = +digit;
auto const numeric_identifier = (positive_digit >> digits) | digit;

The problem I see is that the type numeric_identifier synthesizes is not compatible with a string (see full example here).

To solve this, I would need to create a rule that matches a digit and synthesizes a string. The only solution that I can think of is to use semantic actions, but this causes errors when the rule is used in a situation where backtracking is necessary (see full example here).


Solution

  • It's not completely clear to me what you're trying to do. If the goal is to valid the format of a string but parse match the input string exactly, why not use x3::raw?

    E.g.

    auto num_id  = x3::uint_;
    auto version = x3::raw[num_id % '.'];
    

    Now you can directly parse typical version strings into a string:

    Live On Coliru

    int main() {
        for (sv input : {"0", "1", "1.4", "1.6.77.0.1234",}) {
            std::string parsed;
    
            std::cout << "Parsing " << std::quoted(input);
             
            auto f = begin(input), l = end(input);
    
            if (parse(f, l, version, parsed)) {
                std::cout << " -> " << std::quoted(parsed) << "\n";
            } else {
                std::cout << " -- FAILED\n";
            }
    
            if (f != l) {
                std::cout << "Remaining unparsed: " << std::quoted(sv{f, l}) << "\n";
            }
        }
    }
    

    Prints

    Parsing "0" -> "0"
    Parsing "1" -> "1"
    Parsing "1.4" -> "1.4"
    Parsing "1.6.77.0.1234" -> "1.6.77.0.1234"
    

    To add the restriction that id numbers not begin with 0 unless they're literally zero:

    auto num_id  = x3::char_('0') | x3::uint_;
    

    Of course you can be less clever or more blunt:

    auto num_id
        = !x3::lit('0') >> x3::uint_
        | x3::uint_parser<unsigned, 10, 1, 1>{};
    

    The effect would be equivalent. I like the first one a bit better.