2

Short version

What are contexts (boost::spirit::x3::context) used for, what do they represent, why are they needed and not hidden to the end user, and what requirements should they meet for smooth compiling ?

Especially when having parsers in different translation units, for instance for unit testing.

Long version

In X3 documentation, when one want to understand precisely what is expected when a context is needed, it is not that clear.

Documentation says:

  1. In the context of semantic actions :

from the context we can extract relevant information

  1. Contexts are mentioned with BOOST_SPIRIT_DEFINE but nothing is said about it

  2. Contexts are mentioned as well when explaining the expected program structures:

We'll also need to provide the initial context type. This is the context that X3 will use to initiate a parse. For calling phrase_parse, you will need the phrase_parse_context like we do below, passing in the skipper type

  1. In the annotation tutorial contexts are again mentioned and manipulated, without clear explanation of what they are, and how they are expected to be used.

If you read the previous Program Structure tutorial where we separated various logical modules of the parser into separate cpp and header files, and you are wondering how to provide the context configuration information (see Config Section), we need to supplement the context like this:

  1. And so on, the further we advance in the reading the more contexts are assumed to be known and the less they are explained.

So at the end, we pick one (the one provided in the documentation to start with)

x3::phrase_parse_context<x3::ascii::space_type>::type

and as long as it compiles...everything is fine.

Though quickly, compiler yells. That's not unusual: it's the compiler doing its job. But in this case it is hard to see how to get useful information from the error messages.

I see that the linker reports a missing symbol, but I need more information. It's hard to see where the symbol is used or how the types are inferred.

If only the docs told us what contexts are used for and what is really expected

I tried reading context.hpp which is not that long and quite easy to read. But it's so abstract that I still have no clue about what contexts model or represent.

And on SO, there are a few questions about contexts, mismatches, and excellent answers as well. I didn't really find out more about ask why contexts are needed, how they should be used, and when one needs to care about them. See e.g.

  1. Mixing non-terminal rules from separeted translation unit
  2. Embedding a parser from a separate translation unit into another parser
  3. Splitting Boost.Spirit.X3 parsers into several TUs.

After having climbed the first hill of learning spirit/X3, I thought I had done the toughest part. That was without grasping the essence of contexts, which turns out important when linking.

Question

What are contexts (boost::spirit::x3::context) used for, what do they represent, why are they needed for the end user, and what requirements should they meet for smooth compiling ?

Heyji
  • 1,113
  • 8
  • 26
  • "these words are to be taken as those of a man frustrated" - I appreciate that. However, on this site you are supposedly solliciting help with the task at hand, not searching for sympathy or consolation. I think you should reduce the rant level of the question to improve it (and not waste too much time). – sehe Feb 26 '21 at 23:34
  • 2
    The answer for your question might depend on a context. Jokes aside, you will not find a definition of context in [context-sensitive grammars](https://en.wikipedia.org/wiki/Context-sensitive_grammar) because it is a property of a particular grammar, and even if a [grammar is context-free](https://en.wikipedia.org/wiki/Context-free_grammar) a parser of that grammar might not be. In case of X3 when you omit whitespace skipping (by using phrase_parse) or rule definition (by using scoped rules) or ask for error handling -- it looks for that information in a context. C++20 modules should save you. – Nikita Kniazev Feb 27 '21 at 18:24
  • Thanks Nikita, this is well understood. I thought C++20 concepts as well. – Heyji Feb 27 '21 at 21:56

1 Answers1

2

Contexts contains every bit of runtime state required for the parser to run and the following generic instantiation parameters:

  • skipper type
  • iterator type

Indeed, when splitting parsers across translation units, this is a frequent source of linker errors. Not in the least because

The documentation does basically state all these. But they focus on a happy path, and don't let you easily appreciate how cumbersome maintaining rules across translation units can become.

Playing Your Coach

I suggest a simple guideline

I will not spread/share rules beyond a single translation unit

such that their definitions can exist with their declaration/instantiations.

There are some downsides to that:

  • the compilation time for that TU can be longer

  • it might lead to big source file if the grammar is complicated

  • lastly, due to effect observed above (*), there can be excessive template instantiations in simple scenarios because when local rules are recursively instantiating; they will lead to many "different" instantiations.

    This is exacerbated if parts of the grammar change e.g. skippers (e.g. using x3::skip/x3::no_skip).

Corollary:

If your grammar is really large, consider a standalone parser generator as opposed to Spirit

In spite of the limitations these all seem to imply, X3 is still a superb tool that enhances my productivity in a big way. I'm actively empowered to write small parsers properly and consistently.

PS/Asides

I think the linked answer should also help the broader questions about what contexts are. Beyond that, I could point you to a few [other] locations where I helped people debugging their linker errors in the context [sic] of X3.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • "I will not spread, share rule beyond a single translation unit". What if we want unit testing ? I think this is not too much given the difficulty to have the parsers working. Shall we mix the unit tests within the same translation unit ? – Heyji Feb 27 '21 at 15:35
  • "contexts can sometimes contain the full parser expression template". But why isn't this hidden to the end-user ? we don't want/need to manage this, do we ? – Heyji Feb 27 '21 at 15:38
  • "Shall we mix the unit tests within the same TU" - It depends. White-box, sure! Of course, like always. Black-box? No, of course not, because by definition you test the public interface, not the implementation. I don't see a problem there. It sure feels like inventing more problems just to be outraged. – sehe Feb 27 '21 at 15:55
  • "we don't want [...] do we?" - It only happens when you _opt out_ of the SPIRIT_DEFINE machinery. Which means you don't have any reason to care about the context details anyways. Again, not the biggest problem. Just pick your preference. Use DEFINE or not. Perhaps, don't use X3 at all. There are pros and cons. – sehe Feb 27 '21 at 15:57
  • "I will not spread/share rules in another TU". My experience is that if you use a rule "alone" outside the TU, that is fine. But what is not fine is embedding a rule inside another parser, be it a rule or not, outside the rule's TU. Because in that case you need to instantiate the rule with the context of this new parser. But to simplify the advise, you just say : don't spread/share a rule outside of its TU. Is that correct ? – Heyji Feb 27 '21 at 22:03
  • "It sure feels like inventing more problems just to be outraged". The fact is that it's the second time I am refactoring the source code of my parser because of these kind of issues. The first time because I had a parser split among several TU. The second time because I reused the parser in unit test TU. So it is not a problem I am inventing, I am not outraged, but rather frustrated AND eager to refactor my code a third time to make things right (not sure I ought to write that though, but since you ask ;-) ) according to your excellent advices. – Heyji Feb 27 '21 at 22:09
  • Ok, redeemed. I just love helping, but it's not as fun when it feels like fighting/defending. As you can see from the linked answers I have dealt with a lot more of this pain, but I don't have the false hope that "just documenting the contexts better" will magically solve things. (How? Also, it's experimental and the implementation details are a moving target; in fact I think there are pending branches there that take different approaches to exactly how the context works). To me it's about getting out far enough so you can intuitively see these coming. – sehe Feb 28 '21 at 15:00
  • I'd like for some "not-so-happy path" examples. But I guess they would serve to confuse/deter or simply get ignored (this is how it always goes, see e..g Asio, but also completely different popular frameworks like Angular or Ruby On Rails). It's hard to grasp these things when you're starting out. I sure didn't. – sehe Feb 28 '21 at 15:01