diff --git a/doc/parser.qbk b/doc/parser.qbk index 6e7db11a..8b800285 100644 --- a/doc/parser.qbk +++ b/doc/parser.qbk @@ -39,6 +39,7 @@ [import ../example/parsing_into_a_class.cpp] [import ../example/struct_rule.cpp] [import ../example/user_error_handler.cpp] +[import ../test/parser_rule.cpp] [import ../include/boost/parser/concepts.hpp] [import ../include/boost/parser/error_handling_fwd.hpp] @@ -209,6 +210,7 @@ [def _ATTR_np_ ['[^ATTR]]] [def _p_api_ [link boost_parser__proposed_.tutorial.the__parse____api The `parse()` API]] +[def _parsers_uses_ [link boost_parser__proposed_.tutorial.the_parsers_and_their_uses The Parsers And Their Uses]] [def _parse_ctx_ [link boost_parser__proposed_.tutorial.the_parse_context The Parse Context]] [def _rule_parsers_ [link boost_parser__proposed_.tutorial.rule_parsers Rule Parsers]] [def _parsing_structs_ [link boost_parser__proposed_.tutorial.parsing_into__struct_s_and__class_es Parsing into `struct`s and `class`es]] diff --git a/doc/tutorial.qbk b/doc/tutorial.qbk index e88b9552..7bcd8b8a 100644 --- a/doc/tutorial.qbk +++ b/doc/tutorial.qbk @@ -478,12 +478,14 @@ for now, here's how you might use it: _locals_ returns a reference to one or more values that are local to the current rule being parsed, if any. If there are two or more local values, _locals_ returns a reference to a _bp_tup_. Rules with locals are something -we haven't gotten to yet, but here is how you use _locals_: +we haven't gotten to yet (see _more_about_rules_), but for now all you need to +know is that you can provide a template parameter (`LocalState`) to _r_, and +the rule will default construct an object of that type for use within the +rule. You access it via _locals_: [](auto & ctx) { auto & local = _locals(ctx); - // Use local here. If boost::parser::tuple aliases to hana::tuple, access - // its members like this: + // Use local here. If 'local' is a hana::tuple, access its members like this: using namespace hana::literals; auto & first_element = local[0_c]; auto & second_element = local[1_c]; @@ -495,12 +497,14 @@ returned. [heading _params_] _params_, like _locals_, applies to the current rule being used to parse, if -any. It also returns a reference to a single value, if the current rule has -only one parameter, or a _bp_tup_ of multiple values if the current rule has -multiple parameters. +any (see _more_about_rules_). It also returns a reference to a single value, +if the current rule has only one parameter, or a _bp_tup_ of multiple values +if the current rule has multiple parameters. If there is no current rule, or +the current rule has no parameters, a _n_ is returned. -If there is no current rule, or the current rule has no parameters, a _n_ is -returned. +Unlike with _locals_, you *do not* provide a template parameter to _r_. +Instead you call the _r_'s `with()` member function (again, see +_more_about_rules_). [note _n_ is a type that is used as a return value in _Parser_ for parse context accessors. _n_ is convertible to anything that has a default @@ -2383,9 +2387,11 @@ semantics, is a lot easier to read, and is a lot less code.] [heading Locals] The _r_ template takes another template parameter we have not discussed yet. -You can pass a third parameter to _r_, which will be available within semantic -actions used in the rule as `_locals_np_(ctx)`. This gives your rule some -local state, if it needs it: +You can pass a third parameter `LocalState` to _r_, which will be defaulted +csontructed by the _r_, and made available within semantic actions used in the +rule as `_locals_np_(ctx)`. This gives your rule some local state, if it +needs it. The type of `LocalState` can be anything regular. It could be a +single value, a struct containing multiple values, or a tuple, among others. struct foo_locals { @@ -2414,49 +2420,75 @@ write parsers that have to track state as they parse. [heading Parameters] -Sometimes, it is convenient to parameterize parsers. Consider this parsing -rule from the _yaml_ spec: +Sometimes, it is convenient to parameterize parsers. Consider these parsing +rules from the _yaml_ spec: [pre -\[137\] c-flow-sequence(n,c) ::= “\[” s-separate(n,c)? - ns-s-flow-seq-entries(n,in-flow(c))? “\]” +\[80\] +s-separate(n,BLOCK-OUT) ::= s-separate-lines(n) +s-separate(n,BLOCK-IN) ::= s-separate-lines(n) +s-separate(n,FLOW-OUT) ::= s-separate-lines(n) +s-separate(n,FLOW-IN) ::= s-separate-lines(n) +s-separate(n,BLOCK-KEY) ::= s-separate-in-line +s-separate(n,FLOW-KEY) ::= s-separate-in-line + +\[136\] +in-flow(n,FLOW-OUT) ::= ns-s-flow-seq-entries(n,FLOW-IN) +in-flow(n,FLOW-IN) ::= ns-s-flow-seq-entries(n,FLOW-IN) +in-flow(n,BLOCK-KEY) ::= ns-s-flow-seq-entries(n,FLOW-KEY) +in-flow(n,FLOW-KEY) ::= ns-s-flow-seq-entries(n,FLOW-KEY) + +\[137\] +c-flow-sequence(n,c) ::= “\[” s-separate(n,c)? in-flow(c)? “\]” + ] -This YAML rule says that the parsing should proceed into two YAML subrules, -both of which have these `n` and `c` parameters. It is certainly possible to +YAML [137] says that the parsing should proceed into two YAML subrules, both +of which have these `n` and `c` parameters. It is certainly possible to transliterate these YAML parsing rules to something that uses unparameterized -_Parser_ _rs_, but it is quite painful to do so. +_Parser_ _rs_, but it is quite painful to do so. It is better to use a +parameterized rule. You give parameters to a _r_ by calling its `with()` member. The values you pass to `with()` are used to create a _bp_tup_ that is available in semantic actions attached to the rule, using `_params_np_(ctx)`. - namespace bp = boost::parser; - - // Declare our rules. - bp::rule foo = "foo"; - bp::rule bar = "bar"; - - // Get the first parameter for this rule. - auto first_param = [](auto & ctx) { - using namespace boost::hana::literals; - return _params(ctx)[0_c]; - }; - auto const foo_def = bp::repeat(first_param)[' '_l]; // Match ' ' the number of times indicated by the first parameter to foo. - - // Assume that bar has a locals struct with a local_indent member, and - // that set_local_indent and local_indent are lambdas that respectively write - // and read _locals(ctx).local_indent. - - // Parse an integer, and then pass that as a parameter to foo. - auto const bar_def = bp::int_[set_local_indent] >> foo.with(local_indent); - - BOOST_PARSER_DEFINE_RULES(foo, bar); - Passing parameters to _rs_ like this allows you to easily write parsers that change the way they parse depending on contextual data that they have already parsed. +Here is an implementation of YAML [137]. It also implements the two YAML +rules used directly by [137], rules [136] and [80]. The rules that *those* +rules use are also represented below, but are implemented using only _e_, so +that I don't have to repeat too much of the (very large) YAML spec. + +[extended_param_yaml_example_rules] + +YAML [137] (`c_flow_sequence`) parses a list. The list may be empty, and must +be surrounded by brackets, as you see here. But, depending on the current +YAML context (the `c` parameter to [137]), we may require certain spacing to +be matched by `s-separate`, and how sub-parser `in-flow` behaves also depends +on the current context. + +In `s_separate` above, we parse differently based on the value of `c`. This +is done above by using the value of the second parameter to `s_separate` in a +switch-parser. The second parameter is looked up by using __p_ as a parse +argument. + +`in_flow` does something similar. Note that `in_flow` calls its subrule by +passing its first parameter, but using a fixed value for the second value. +`s_separate` only passes its `n` parameter conditionally. The point is that a +rule can be used with and without `.with()`, and that you can pass constants +or parse arguments to `.with()`. + +With those rules defined, we could write a unit test for YAML [137] like this: + +[extended_param_yaml_example_use] + +You could extend this with tests for different values of `n` and `c`. +Obviously, in real tests, you parse actual contents inside the `"[]"`, if the +other rules were implemented, like [138]. + [heading The __p_ variable template] Getting at one of a rule's arguments and passing it as an argument to another @@ -2471,6 +2503,10 @@ Using __p_ can prevent you from having to write a bunch of lambdas that get each get an argument out of the parse context using `_params_np_(ctx)[0_c]` or similar. +Note that __p_ is a parse argument (see _parsers_uses_), meaning that it is an +invocable that takes the context as its only parameter. If you want to use it +inside a semantic action, you have to call it. + [heading Special forms of semantic actions usable within a rule] Semantic actions in this tutorial are usually of the signature `void (auto & diff --git a/test/parser_rule.cpp b/test/parser_rule.cpp index 776a0ac7..23283c93 100644 --- a/test/parser_rule.cpp +++ b/test/parser_rule.cpp @@ -557,3 +557,85 @@ namespace more_about_rules_4 { return bp::parse(str, bp::omit[parens], bp::ws); } } + +// clang-format off +namespace param_example { + //[ extended_param_yaml_example_rules + namespace bp = boost::parser; + + // A type to represent the YAML parse context. + enum class context { + block_in, + block_out, + block_key, + flow_in, + flow_out, + flow_key + }; + + // A YAML value; no need to fill it in for this example. + struct value + { + // ... + }; + + // YAML [66], just stubbed in here. + auto const s_separate_in_line = bp::eps; + + // YAML [137]. + bp::rule c_flow_sequence = "c-flow-sequence"; + // YAML [80]. + bp::rule s_separate = "s-separate"; + // YAML [136]. + bp::rule in_flow = "in-flow"; + // YAML [138]; just eps below. + bp::rule ns_s_flow_seq_entries = + "ns-s-flow-seq-entries"; + // YAML [81]; just eps below. + bp::rule s_separate_lines = "s-separate-lines"; + + // Parser for YAML [137]. + auto const c_flow_sequence_def = + '[' >> + -s_separate.with(bp::_p<0>, bp::_p<1>) >> + -in_flow.with(bp::_p<0>, bp::_p<1>) >> + ']'; + // Parser for YAML [80]. + auto const s_separate_def = bp::switch_(bp::_p<1>) + (context::block_out, s_separate_lines.with(bp::_p<0>)) + (context::block_in, s_separate_lines.with(bp::_p<0>)) + (context::flow_out, s_separate_lines.with(bp::_p<0>)) + (context::flow_in, s_separate_lines.with(bp::_p<0>)) + (context::block_key, s_separate_in_line) + (context::flow_key, s_separate_in_line); + // Parser for YAML [136]. + auto const in_flow_def = bp::switch_(bp::_p<1>) + (context::flow_out, ns_s_flow_seq_entries.with(bp::_p<0>, context::flow_in)) + (context::flow_in, ns_s_flow_seq_entries.with(bp::_p<0>, context::flow_in)) + (context::block_out, ns_s_flow_seq_entries.with(bp::_p<0>, context::flow_key)) + (context::flow_key, ns_s_flow_seq_entries.with(bp::_p<0>, context::flow_key)); + + auto const ns_s_flow_seq_entries_def = bp::eps; + auto const s_separate_lines_def = bp::eps; + + BOOST_PARSER_DEFINE_RULES( + c_flow_sequence, + s_separate, + in_flow, + ns_s_flow_seq_entries, + s_separate_lines); +//] +} +// clang-format on + +TEST(parser, extended_param_example) +{ + using namespace param_example; + + //[ extended_param_yaml_example_use + auto const test_parser = c_flow_sequence.with(4, context::block_out); + auto result = bp::parse("[]", test_parser); + assert(result); + //] + (void)result; +}