PrevUpHomeNext

The Parse Context

Now would be a good time to describe the parse context in some detail. Any semantic action that you write will need to use state in the parse context, so you need to know what's available.

The parse context is an object that stores the current state of the parse — the current- and end-iterators, the error handler, etc. Data may seem to be "added" to or "removed" from it at different times during the parse. For instance, when a parser p with a semantic action a succeeds, the context adds the attribute that p produces to the parse context, then calls a, passing it the context.

Though the context object appears to have things added to or removed from it, it does not. In reality, there is no one context object. Contexts are formed at various times during the parse, usually when starting a subparser. Each context is formed by taking the previous context and adding or changing members as needed to form a new context object. When the function containing the new context object returns, its context object (if any) is destructed. This is efficient to do, because the parse context has only about a dozen data members, and each data member is less than or equal to the size of a pointer. Copying the entire context when mutating the context is therefore fast. The context does no memory allocation.

[Tip] Tip

All these functions that take the parse context as their first parameter will find by found by Argument-Dependent Lookup. You will probably never need to qualify them with boost::parser::.

Accessors for data that are always available

By convention, the names of all Boost.Parser functions that take a parse context, and are therefore intended for use inside semantic actions, contain a leading underscore.

_pass()

_pass() returns a reference to a bool indicating the success of failure of the current parse. This can be used to force the current parse to pass or fail:

[](auto & ctx) {
    // If the attribute fails to meet this predicate, fail the parse.
    if (!necessary_condition(_attr(ctx)))
        _pass(ctx) = false;
}

Note that for a semantic action to be executed, its associated parser must already have succeeded. So unless you previously wrote _pass(ctx) = false within your action, _pass(ctx) = true does nothing; it's redundant.

_begin(), _end() and _where()

_begin() and _end() return the beginning and end of the range that you passed to parse(), respectively. _where() returns a subrange indicating the bounds of the input matched by the current parse. _where() can be useful if you just want to parse some text and return a result consisting of where certain elements are located, without producing any other attributes. _where() can also be essential in tracking where things are located, to provide good diagnostics at a later point in the parse. Think mismatched tags in XML; if you parse a close-tag at the end of an element, and it does not match the open-tag, you want to produce an error message that mentions or shows both tags. Stashing _where(ctx).begin() somewhere that is available to the close-tag parser will enable that. See Error Handling and Debugging for an example of this.

_error_handler()

_error_handler() returns a reference to the error handler associated with the parser passed to parse(). Using _error_handler(), you can generate errors and warnings from within your semantic actions. See Error Handling and Debugging for concrete examples.

Accessors for data that are only sometimes available
_attr()

_attr() returns a reference to the value of the current parser's attribute. It is available only when the current parser's parse is successful. If the parser has no semantic action, no attribute gets added to the parse context. It can be used to read and write the current parser's attribute:

[](auto & ctx) { _attr(ctx) = 3; }

If the current parser has no attribute, a none is returned.

_val()

_val() returns a reference to the value of the attribute of the current rule being used to parse (if any), and is available even before the rule's parse is successful. It can be used to set the current rule's attribute, even from a parser that is a subparser inside the rule. Let's say we're writing a parser with a semantic action that is within a rule. If we want to set the current rule's value to some function of subparser's attribute, we would write this semantic action:

[](auto & ctx) { _val(ctx) = some_function(_attr(ctx)); }

If there is no current rule, or the current rule has no attribute, a none is returned.

You need to use _val() in cases where the default attribute for a rule's parser is not directly compatible with the attribute type of the rule. In these cases, you'll need to write some code like the example above to compute the rule's attribute from the rule's parser's generated attribute. For more info on rules, see the next page, and More About Rules.

_globals()

_globals() returns a reference to a user-supplied object that contains whatever data you want to use during the parse. The "globals" for a parse is an object — typically a struct — that you give to the top-level parser. Then you can use _globals() to access it at any time during the parse. We'll see how globals get associated with the top-level parser in The parse() API later. As an example, say that you have an early part of the parse that needs to record some black-listed values, and that later parts of the parse might need to parse values, failing the parse if they see the black-listed values. In the early part of the parse, you could write something like this.

[](auto & ctx) {
    // black_list is a std::unordered_set.
    _globals(ctx).black_list.insert(_attr(ctx));
}

Later in the parse, you could then use black_list to check values as they are parsed.

[](auto & ctx) {
    if (_globals(ctx).black_list.contains(_attr(ctx)))
        _pass(ctx) = false;
}
_locals()

_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 boost::parser::tuple. Rules with locals are something 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 rule, 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 '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];
}

If there is no current rule, or the current rule has no locals, a none is returned.

_params()

_params(), like _locals(), applies to the current rule being used to parse, if any (see More About Rules). It also returns a reference to a single value, if the current rule has only one parameter, or a boost::parser::tuple of multiple values if the current rule has multiple parameters. If there is no current rule, or the current rule has no parameters, a none is returned.

Unlike with _locals(), you do not provide a template parameter to rule. Instead you call the rule's with() member function (again, see More About Rules).

[Note] Note

none is a type that is used as a return value in Boost.Parser for parse context accessors. none is convertible to anything that has a default constructor, convertible from anything, assignable form anything, and has templated overloads for all the overloadable operators. The intention is that a misuse of _val(), _globals(), etc. should compile, and produce an assertion at runtime. Experience has shown that using a debugger for investigating the stack that leads to your mistake is a far better user experience than sifting through compiler diagnostics. See the Rationale section for a more detailed explanation.

_no_case()

_no_case() returns true if the current parse context is inside one or more (possibly nested) no_case[] directives. I don't have a use case for this, but if I didn't expose it, it would be the only thing in the context that you could not examine from inside a semantic action. It was easy to add, so I did.


PrevUpHomeNext