sapi
sapi

Reputation: 111

Parser rule dependent on parameter

I was trying to define a parser where the rules are not fully pre-defined, i.e. they contain a variable part. This was no problem with Spirit Qi, but I was not able to implement it due to the static nature of X3. I tried the with directive, which is unfortunately undocumented, but without luck so far. The only examples I found so far are inside a lambda expression.

I constructed a simple example to demonstrate the issue: parsing integers where the separator is given as a parameter.

#include <boost/spirit/home/x3.hpp>
#include <iostream>

namespace x3 = boost::spirit::x3;

namespace parsing {
    x3::rule<struct parser> parser {"parser"};

    //struct separator {};
    char separator(',');

    //auto parser_def = x3::int_ % x3::lit(x3::get<separator>(/* context */)); // candidate function template not viable: requires single argument 'context'
    auto parser_def = x3::int_ % x3::lit(separator);

    BOOST_SPIRIT_DEFINE(parser)
}

void parse(const std::string &data, const char separator) {
    using namespace std;

    //auto parser = x3::with<parsing::separator>(ref(separator)) [parsing::parser] >> x3::eoi;
    auto parser = parsing::parser >> x3::eoi;

    if (x3::parse(data.begin(), data.end(), parser))
        cout << "Parse succeeded\n";
    else
        cout << "Parse failed\n";
}

int main() {
    parse("1 2 3", ' ');
    parse("1,2,3", ',');
    parse("1;2;3", ';');
}

I commented out the parts where I tried to use the with directive.

Is this currently possible with X3? Has anyone done this before?

Upvotes: 3

Views: 393

Answers (2)

Llopeth
Llopeth

Reputation: 406

A simpler solution, you can have a function that gets a character and returns a parser value of the type x3::rule<struct parser>.

auto getParser(char sep)
{
     return x3::int_ % x3::lit(sep);
}

Complete code (Godbolt:https://godbolt.org/z/ENxCTF )

#include <boost/spirit/home/x3.hpp>
#include <iostream>

namespace x3 = boost::spirit::x3;

namespace parsing {
    x3::rule<struct parser> parser {"parser"};

    //struct separator {};
    char separator(',');

    //auto parser_def = x3::int_ % x3::lit(x3::get<separator>(/* context */)); // candidate function template not viable: requires single argument 'context'
    auto parser_def = x3::int_ % x3::lit(separator);


    BOOST_SPIRIT_DEFINE(parser)
    auto getParser(char sep)
    {
        return x3::int_ % x3::lit(sep);
    }
}

void parse(const std::string &data, const char separator) {
    using namespace std;

    //auto parser = x3::with<parsing::separator>(ref(separator)) [parsing::parser] >> x3::eoi;
    auto parser = parsing::getParser(separator) >> x3::eoi;

    if (x3::parse(data.begin(), data.end(), parser))
        cout << "Parse succeeded\n";
    else
        cout << "Parse failed\n";
}

int main() {
    parse("1 2 3", ' ');
    parse("1,2,3", ',');
    parse("1;2;3", ';');
}

Upvotes: 0

sapi
sapi

Reputation: 111

After looking at some more X3 posts, I was enlightened by this answer of sehe: https://stackoverflow.com/a/38303379/7110782

Knowing about x3::_pass led me to this solution:

#include <boost/spirit/home/x3.hpp>
#include <iostream>

namespace x3 = boost::spirit::x3;

namespace parsing {
    x3::rule<struct parser> parser {"parser"};

    struct separator {};

    // only the separator which is currently in the context is allowed (passes)
    auto isSeparator = [](auto& ctx){ x3::_pass(ctx) = x3::_attr(ctx) == x3::get<separator>(ctx); };

    // at first match any char and then check whether it is the separator
    auto parser_def = x3::int_ % x3::char_[isSeparator];

    BOOST_SPIRIT_DEFINE(parser)
}

void parse(const std::string &data, const char separator) {
    using namespace std;

    auto parser = x3::with<parsing::separator>(ref(separator)) [parsing::parser] >> x3::eoi;

    if (x3::parse(data.begin(), data.end(), parser))
        cout << "Parse succeeded\n";
    else
        cout << "Parse failed\n";
}

int main() {
    // succeed
    parse("1 2 3", ' ');
    parse("1,2,3", ',');
    parse("1;2;3", ';');

    // fail
    parse("1,2,3", ' ');
    parse("1;2;3", ',');
}

What remains to test in a further step is the possibility to set multiple parameters (probably via cascading x3::with<>).

Edit:

Yes, setting multiple parameters via cascading x3::with<> seems to work. For example:

auto parser = x3::with<parsing::separator>(ref(separator))[x3::with<parsing::separator2>(ref(separator2))[parsing::parser]] >> x3::eoi;

Upvotes: 1

Related Questions