What is the intent of Spirit X3 being so much 'stateless'?
Bad aspects of 'states' in Spirit V2
Looking back to Spirit V2, the "grammar" was, say, conceptually stateful - in many ways. This is because the grammar was a class instance.
Basically, there are lots of bad aspects for making your grammar -- or even any single rule -- to be stateful:
- It might make your grammar non-re-entrant;
- It might add thread-unsafety to your grammar instance;
- Self-managed 'flag' is a disaster.
Theoretically speaking, adding an external state makes your grammar non-trivial.
Really need no state?
In contrast, you can say any parser is stateful (because it parses the current context and context is the state). Below is a good case of additional 'context' added by a programmer:
quoted_string_ = as_string [omit [char_("'\"") [_a = _1]] >> *(char_ - lit(_a)) >> lit(_a)]
qi::locals
was a good sign of non-external states.
There were also 'external states' which a programmer could add to his grammar, and they were just doing something wrong in most cases:
func_call_ = func_name_ >> lit('(') >> eps [ref(is_inside_function_call) = true] >> ...
But still, there were some corner cases where external states being useful.
macro_type_1_ =
lit("{{{") [PUSH_STATE(macro_ctx, Macro::Type1)] >> (
((any_expr_ - end_of_macro_ctx_) >> lit("}}}") >> eps [POP_STATE(macro_ctx)]) |
(eps [POP_STATE(macro_ctx)] >> eps [_pass = false])
)
;
macro_type_2_ =
lit("[[[") [PUSH_STATE(macro_ctx, Macro::Type2)] >> (
((any_expr_ - end_of_macro_ctx_) >> lit("]]]") >> eps [POP_STATE(macro_ctx)]) |
(eps [POP_STATE(macro_ctx)] >> eps [_pass = false])
)
;
Above is an example of some arbitrary context-sensitive language. Here I am adding a 'context stack' by emulating a 'destructor' for the sub rule. This might be a good case of using a special variant of Nabialec Trick where end_of_macro_ctx_
being qi::symbols
instance.
(See Boost.Spirit.Qi: dynamically create "difference" parser at parse time for possible implementation detail)
You can't use qi::locals
here, because there are no guarantee for the lifetime of qi::locals
. So you should use a global variable (i.e. member variable for your grammar class instance).
Inherited attributes? Maybe. If you are willing to pass the same variable to every single rule.
External states for grammar itself
Speaking about external states, there are even more fundamental stuff which a programmer might want to add to his grammar.
on_error<fail>(root_, phx::bind(&my_logger, &MyLogger::error, _1, _2, _3, _4));
You can't do this anymore on X3.
Statelessness of X3
X3 is expecting an user to define his every single rule in namespace scope, with auto-consted instance.
Okay, now let's take a look at the implementation of BOOST_SPIRIT_DEFINE
. It is basically doing only one thing:
#define BOOST_SPIRIT_DEFINE(your_rule, <unspecified>) template <unspecified> <unspecified> parse_rule(decltype(your_rule), <unspecified>...) { <unspecified> }
The first argument of parse_rule()
is decltype-d to given rule's unique type.
This means two things:
- X3 is fully relying on ADL call to
parse_rule()
. parse_rule()
must be defined in namespace scope.
You can't specialize a template function for an instance. There's no way of telling X3 to use any of my instance variables.
I lied. You can do this if you want:
static inline MyLogger& use_my_logger_please() {
static MyLogger instance; return instance;
}
or
#define MY_BOOST_SPIRIT_DEFINE(my_rule, <unspecified>, my_logger_f) <unspecified>
MY_BOOST_SPIRIT_DEFINE(rule_1_, ..., std::bind([] (MyLogger& l, std::string const& msg) { l << msg; }, this->logger_instance_, std::placeholders::_1))
Really?