Action Variables

Peter Verhas edited this page Jan 23, 2014 · 5 revisions
Clone this wiki locally

As discussed in the section Working with the Value Stack value stack operations require some design discipline. Additionally, in order to maintain type safety, the value stack should ideally only contain objects of a common type. Otherwise you might have to add casts to your actions, which would reduce parser robustness and long-term maintainability.
In order to give you more flexibility in action design parboiled for Java offers you a second tool that can prove valuable whenever the value stack somehow doesn’t seem to fit.

Often times a rule (method) contains several action expressions in various places of the rules substructure that all work together to create the rules end product, like for example a properly initialized AST node. In many cases it would thereby be helpful to have access to common temporary helper variables accessible by all actions of the rule.
Consider the following rule example, which is part of the pegdown Markdown parser:

Rule Verbatim() {
    StringVar text = new StringVar("");
    StringVar temp = new StringVar("");
        return Sequence(
            OneOrMore(
                ZeroOrMore(BlankLine(), temp.append("\n")),
                NonblankIndentedLine(), text.append(temp.getAndSet(""), pop().getText())
            ),
            push(new VerbatimNode(text.get()))
        );
}

This rule parses a “verbatim” Markdown construct, which consists of one or more indented lines of text. The indented lines can be separated by completely blank lines, which should also be matched by the rule if they are followed by at least one more indented line. The job of the rule is to create an AST node object and initialize it with the matched text (without the line indentations).

In order to be able to build this text argument to the AST node it is helpful to have access to a string variable that serves as a temporary container for the built string. In a normal Java method you would simply use a local variable for the job, however, since the rule method only contains the construction code of the rule and not the code actually run during rule execution a normal local variable wouldn’t help. It would only be available during rule construction and not during rule execution.
This is why parboiled for Java comes with a class called Var, which serves as a kind of “local variable” for the rules execution phase. Var objects wrap an internal value of an arbitrary (reference) type, can have an initial value, allow read/write access to their values and can be passed around as parameters to nested rule methods. Each rule invocation (i.e. rule matching attempt) receives its own Var scope (which is automatically initialized by executing the initialization code creating the Vars constructor argument), so actions in recursive rules work just like expected. Additionally the Var class defines a couple of convenient helper methods that are optimized for the use of Vars in action expressions (which, as you know, always have to return a boolean value). (The StringVar class from the example above is nothing but a simple specialization of a Var that defines even more helper methods optimized for Strings.)

Var objects generally behave just like local variables with one exception, outlined in the following example:

Rule A() {
    Var<Integer> i = new Var<Integer>();
    return Sequence(
        ...,
        i.set(42),
        B(i),
        action(i.get())
    );
}

Rule B(Var<Integer> i) {
    return Sequence(
        ...,
        i.set(26)
    );
}

When rule method A passes a Var defined in its scope to another rule method B as a parameter and an action in rule method B writes to this Var all actions in rule method A running after B will “see” this newly written value. In the preceding example the action running last in rule A will receive the argument 26 and not 42 as would be the case with local Integer variable.

In general these action variables allow for easy and readable rule and action code and offer a good way for keeping and working with temporary state independently of the parsers value stack.