Next: , Up: Mid-Rule Actions


3.4.8.1 Using Mid-Rule Actions

A mid-rule action may refer to the components preceding it using $n, but it may not refer to subsequent components because it is run before they are parsed.

The mid-rule action itself counts as one of the components of the rule. This makes a difference when there is another action later in the same rule (and usually there is another at the end): you have to count the actions along with the symbols when working out which number n to use in $n.

The mid-rule action can also have a semantic value. The action can set its value with an assignment to $$, and actions later in the rule can refer to the value using $n. Since there is no symbol to name the action, there is no way to declare a data type for the value in advance, so you must use the ‘$<...>n’ construct to specify a data type each time you refer to this value.

There is no way to set the value of the entire rule with a mid-rule action, because assignments to $$ do not have that effect. The only way to set the value for the entire rule is with an ordinary action at the end of the rule.

Here is an example from a hypothetical compiler, handling a let statement that looks like ‘let (variable) statement’ and serves to create a variable named variable temporarily for the duration of statement. To parse this construct, we must put variable into the symbol table while statement is parsed, then remove it afterward. Here is how it is done:

     stmt:
       "let" '(' var ')'
         {
           $<context>$ = push_context ();
           declare_variable ($3);
         }
       stmt
         {
           $$ = $6;
           pop_context ($<context>5);
         }

As soon as ‘let (variable)’ has been recognized, the first action is run. It saves a copy of the current semantic context (the list of accessible variables) as its semantic value, using alternative context in the data-type union. Then it calls declare_variable to add the new variable to that list. Once the first action is finished, the embedded statement stmt can be parsed.

Note that the mid-rule action is component number 5, so the ‘stmt’ is component number 6. Named references can be used to improve the readability and maintainability (see Named References):

     stmt:
       "let" '(' var ')'
         {
           $<context>let = push_context ();
           declare_variable ($3);
         }[let]
       stmt
         {
           $$ = $6;
           pop_context ($<context>let);
         }

After the embedded statement is parsed, its semantic value becomes the value of the entire let-statement. Then the semantic value from the earlier action is used to restore the prior list of variables. This removes the temporary let-variable from the list so that it won't appear to exist while the rest of the program is parsed.

In the above example, if the parser initiates error recovery (see Error Recovery) while parsing the tokens in the embedded statement stmt, it might discard the previous semantic context $<context>5 without restoring it. Thus, $<context>5 needs a destructor (see Freeing Discarded Symbols). However, Bison currently provides no means to declare a destructor specific to a particular mid-rule action's semantic value.

One solution is to bury the mid-rule action inside a nonterminal symbol and to declare a destructor for that symbol:

     %type <context> let
     %destructor { pop_context ($$); } let
     
     %%
     
     stmt:
       let stmt
         {
           $$ = $2;
           pop_context ($let);
         };
     
     let:
       "let" '(' var ')'
         {
           $let = push_context ();
           declare_variable ($3);
         };

Note that the action is now at the end of its rule. Any mid-rule action can be converted to an end-of-rule action in this way, and this is what Bison actually does to implement mid-rule actions.