You didn't actually show all declarations, so it's not completely clear how the setup is. So let me mock up something quick:
Live On Wandbox
#define BOOST_SPIRIT_X3_DEBUG
#include <iomanip>
#include <boost/spirit/home/x3.hpp>
namespace x3 = boost::spirit::x3;
namespace P {
using namespace x3;
static auto const comment = lexeme [
"/*" >> *(char_ - "*/") >> "*/"
| "//" >> *~char_("\r\n") >> eol
];
static auto const skipper = comment | blank;
static auto const property_type = lexeme["type"];
static auto const property_id = lexeme["id"];
auto const demo =
skip(skipper | eol) [
lit("#")
> property_type
> property_id
];
}
int main() {
for (std::string const input : {
"#type id",
"#type\nid",
})
{
std::cout << "==== " << std::quoted(input) << " ====" << std::endl;
auto f = begin(input), l = end(input);
if (parse(f, l, P::demo)) {
std::cout << "Parsed successfully" << std::endl;
} else {
std::cout << "Failed" << std::endl;
}
if (f!=l) {
std::cout << "Remaining input unparsed: " << std::quoted(std::string(f,l)) << std::endl;
}
}
}
As you can see there's not actually a problem unless the rule declarations get involved:
==== "#type id" ====
Parsed successfully
==== "#type
id" ====
Parsed successfully
Let's zoom in from here
static auto const demo_def =
skip(skipper | eol) [
lit("#")
> property_type
> property_id
];
static auto const demo = x3::rule<struct demo_> {"demo"} = demo_def;
Still OK: Live On Wandbox
<demo>
<try>#type id</try>
<success></success>
</demo>
<demo>
<try>#type\nid</try>
<success></success>
</demo>
Parsed successfully
==== "#type
id" ====
Parsed successfully
So, we know that x3::rule<>
is not actually the issue. It's gonna be about the static dispatch based on the tag type (aka rule ID, I think, in this case struct demo_
).
Doing the straight-forward:
static auto const demo_def =
skip(skipper | eol) [
lit("#")
> property_type
> property_id
];
static auto const demo = x3::rule<struct demo_> {"demo"};
BOOST_SPIRIT_DEFINE(demo)
Still OK: Live On Wandbox
Hmm what else could be wrong. Maybe if there are conflicing skipper contexts? Replacing
if (parse(f, l, P::demo)) {
with
if (phrase_parse(f, l, P::demo, P::skipper)) {
Still OK: Live On Wandbox
So, that's not it either. Ok, let's try the separate instantiation:
Separate Compilation
Live On Wandbox
rule.h
#pragma once
#define BOOST_SPIRIT_X3_DEBUG
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/utility/error_reporting.hpp>
namespace x3 = boost::spirit::x3;
namespace P {
using namespace x3;
static auto const comment = lexeme [
"/*" >> *(char_ - "*/") >> "*/"
| "//" >> *~char_("\r\n") >> eol
];
static auto const skipper = comment | blank;
using demo_type = x3::rule<struct demo_>;
extern demo_type const demo;
BOOST_SPIRIT_DECLARE(demo_type)
}
rule.cpp
#include "rule.h"
#include <iostream>
#include <iomanip>
namespace P {
using namespace x3;
static auto const property_type = lexeme["type"];
static auto const property_id = lexeme["id"];
static auto const demo_def =
skip(skipper | eol) [
lit("#")
> property_type
> property_id
];
struct demo_ {
template<typename It, typename Ctx>
x3::error_handler_result on_error(It f, It l, expectation_failure<It> const& ef, Ctx const&) const {
std::string s(f,l);
auto pos = std::distance(f, ef.where());
std::cout << "Expecting " << ef.which() << " at "
<< "\n\t" << s
<< "\n\t" << std::setw(pos) << std::setfill('-') << "" << "^\n";
return error_handler_result::fail;
}
};
demo_type const demo {"demo"};
BOOST_SPIRIT_DEFINE(demo)
// for non-skipper invocation (x3::parse)
using iterator_type = std::string::const_iterator;
BOOST_SPIRIT_INSTANTIATE(demo_type, iterator_type, x3::unused_type)
// for skipper invocation (x3::phrase_parse)
using skipper_type = decltype(skipper);
using phrase_context_type = x3::phrase_parse_context<skipper_type>::type;
BOOST_SPIRIT_INSTANTIATE(demo_type, iterator_type, phrase_context_type)
}
test.cpp
#include "rule.h"
#include <iostream>
#include <iomanip>
int main() {
std::cout << std::boolalpha;
for (std::string const input : {
"#type id",
"#type\nid",
})
{
std::cout << "\n==== " << std::quoted(input) << " ====" << std::endl;
{
auto f = begin(input), l = end(input);
std::cout << "With top-level skipper: " << phrase_parse(f, l, P::demo, P::skipper) << std::endl;
if (f!=l) {
std::cout << "Remaining unparsed: " << std::quoted(std::string(f,l)) << std::endl;
}
}
{
auto f = begin(input), l = end(input);
std::cout << "Without top-level skipper: " << parse(f, l, P::demo) << std::endl;
if (f!=l) {
std::cout << "Remaining unparsed: " << std::quoted(std::string(f,l)) << std::endl;
}
}
}
}
Prints the expected:
==== "#type id" ====
With top-level skipper: <demo>
<try>#type id</try>
<success></success>
</demo>
true
Without top-level skipper: <demo>
<try>#type id</try>
<success></success>
</demo>
true
==== "#type
id" ====
With top-level skipper: <demo>
<try>#type\nid</try>
<success></success>
</demo>
true
Without top-level skipper: <demo>
<try>#type\nid</try>
<success></success>
</demo>
true
Or, without debug enabled:
==== "#type id" ====
With top-level skipper: true
Without top-level skipper: true
==== "#type
id" ====
With top-level skipper: true
Without top-level skipper: true
FINAL THOUGHTS
Sadly, perhaps, I cannot reproduce the symptom you describe. However, I hope some of the steps above do clarify how separate linkage of rule-definition actually work with respect to the skipper/contexts.
If your situation is actually more complicated, I can only think of another situation where the X3 situation may be different from the QI situation. In Qi, a rule statically declared its skipper. In X3, the skipper is strictly from context (and the only way a rule can limit the number of supported skippers is by separating instantiation and hiding the definition in a separate TU).
This means that it is easy to accidentally inherit an overridden skipper. This can be counter-intuitive in e.g. nested rules. I'd suggest not relying on inherited skipper contexts at all if you have different skippers.