-
Notifications
You must be signed in to change notification settings - Fork 162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Non-throwing x3::expect #788
Conversation
@djowel Could I ask you to review this PR? |
Will do! Thanks so much for doing this! It seems to be very comprehensive and well thought! |
I think your English is very good for a non-native speaker. Please note that I too am not a native English speaker, and neither is the current maintainer. I'd say just go ahead, and I'll do the review and copy editing. This PR will not be complete with proper documentation, since this is a major change. |
@@ -32,7 +35,7 @@ namespace boost { namespace spirit { namespace x3 | |||
while (detail::parse_into_container( | |||
this->subject, first, last, context, rcontext, attr)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't parse_into_container
short circuit the loop on !has_expectation_failure(context);
? Or is it already done in this PR somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good question; it does short circuit. There are 3 patterns here:
- subject parser succeeded (-> returned
true
) - subject parser failed normally (-> returned
false
) - subject parser failed with expectation failure (-> returned
false
)
So the while
statement does break when expectation failure is raised. All we need to do in kleene
is to distinguish the difference between case 2 and 3, then return false if it was the latter.
0286301
to
83ff3ef
Compare
This comment was marked as resolved.
This comment was marked as resolved.
Which version? |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
@@ -91,9 +151,9 @@ namespace boost { namespace spirit { namespace x3 | |||
|
|||
template <typename Iterator, typename Context> | |||
inline void skip_over( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess throwing expectation failure inside a skipper and catching it in a rule does work but I don't think there any use for it. That's probably an omission that it's not catched here. Is it really need to propagate expectation failures outside from skippers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider the case like below:
TEST_FAILURE("a..b.z", lit('a') >> 'b', lit('.') > '.', {
BOOST_TEST(x.which() == "'.'"sv);
BOOST_TEST(x.where() == "z"sv);
});
Traditionally in throwing mode, this kind of grammar led to an expectation_failure being thrown. To get the same x.where()
and x.which()
values in non-throwing mode, we need to propagate the failures.
I don't know what that is. I'll go with Clang 18 and GCC 13. One less the latest.
Wonderful! |
Let's try GCC 13 and Clang 18 first. |
This comment was marked as resolved.
This comment was marked as resolved.
CWG2518 problem and some semantic issue has been resolved in the recent diff, and I see the tests are passing, Since the source of error is gone, this PR should compile on older GCC/Clang now. As far as I understand it, the only remaining to-dos regarding implementation is the ODR issue raised by Kojoley. I'm still not sure what exactly is the right code that I should write. |
Added documentation. I have configured my environment to build & preview quickbook locally, there are no syntax errors. (BTW, quickbook setup was so painful) |
@Kojoley @djowel I've fixed the ODR violation issue in the latest commit. What I've done is just wrapping throwing/non-throwing implementations in separate namespaces. I think inline namespace is the proper solution as described in Stack Overflow. I've also added a test case (previously failing) for mixing throwing/non-throwing modes in a single executable. Documentation is done in yesterday's commit. |
It's looking good as far as I can tell. @Kojoley do you have any further thoughts and review? |
Thank you for working on the docs too! It's hard to review the docs, out of context though, without generating the html, which I haven't done in a long time already. |
Another quick thought, while reading your doc: should we [[deprecate...]] the throwing code with a message pointing to the docs? |
Thanks for your review!
That's great. I was thinking of how to encourage people to use this feature, and that's going to be a good way to effectively informing the existing users. I think we can handle it in a separate PR. |
[snip] Wonderful! |
Merged! Thank you so much for contributing this code! It is well thought and well implemented. |
Thanks! |
This PR is a fresh implementation of the non-throwing
x3::expect
, which was originally abandoned in 2017.supersedes and closes #242
cc: @djowel @wanghan02
Summary
Expectations like
lit('a') > 'b'
will be parsed 90 times faster 🚀 than the current implementation.Non-throwing mode is only enabled when the macro is explicitly specified by user, so this PR has 100% backward compatibility.
Show benchmark C++ code
Stolen from #242 (comment), modified by saki7
cd test/x3
run your_file.cpp ;
toJamfile
../../../../b2 toolset=msvc cxxstd=17 cxxflags=/utf-8 release -j24
Features
#define BOOST_SPIRIT_X3_THROW_EXPECTATION_FAILURE
1
: Default value. This is the traditional behavior, throwsx3::expectation_failure
whenx3::expect[p]
fails.0
: Non-throwing mode. The error will be stored into aboost/std::optional
variable and can be retrieved byx3::get<x3::expectation_failure_tag>(context)
.Usage
Documented as per
doc/x3/tutorial/non_throwing_expectations.qbk
.Throwing mode (aka traditional behavior)
Non-throwing mode
That's all. I believe that potentially all X3 users can experience a performance boost by just changing few lines of code in their application.
Implementation notes (for X3 developers)
The idea of passing around
x3::expectation_failure
byoptional
comes from the original PR back in 2017. Therefore I have added the original author's name as well as mine to the license attribution, great thanks to the original author.That said, my new implementation is different than the reference, both in terms of semantics and code quality. My implementation is mimicking the internal behavior of throwing version, whereas the original PR was disposing
expectation_failure
context incorrectly in various locations. On my implementation, all iterators (i.e.e.where()
) and source info (i.e.e.which()
) should be identical to the values in throwing version.Quoting from original PR (#242 (comment)):
This should not be the expected behavior.
Usage of
x3::expect[p]
in skippers is legit X3 code and it should be supported. In such cases the library user must provide a context which can store thex3::expectation_failure
value so that X3 can store the error in it. So then I carefully investigated all possible combinations of parser primitives and skippers; I have included my discoveries in unit tests.Tests
All tests are passing, however, you should expect Internal Compiler Errors as usual. 🤓
It's not the test case's issue so I can do nothing about it. Just do rebuild several times and you should be getting proper results.
My environment is
I'd really appreciate it if you could test this PR on your environment.
I've added ~100 testcases to make sure that the throwing/non-throwing versions both yield same result when
x3::expect
is involved. For this purpose, I have redesigned the whole test suitetest/x3/expect.ipp
so that b2 automatically runs all expect-related tests for both versions.Documentation
Documentation was initially marked as TODO but was later added to this PR.
Bonus
Originally the runtime failures in
x3::expect[p]
were leading to tremendous amount of error logs on debuggers attached. It was sadly making my output window useless since it literally washes away every useful information including non-X3 errors. This PR solves this issue too.I have also confirmed that non-throwing mode performs 10% to 70% faster when debugger is attached.