Dynamic Rule Semantics

Stratego -- Strategies for Program Transformation
Dynamic rules allow the run-time addition of rewrite rules. This can be used to model context-sensitive rewriting and has a host of applications in program transformation.

This page discusses issues in the semantics of dynamic rules



Bug: Overlapping left-hand sides

Dynamic rules with the same label share the same mapping from context-variables in the left-hand side to context-variables in the right-hand side. That is, the dynamic data in a dynamic rule is determined by the context-variables in the rule.

For example, consider the following generation of a dynamic rule

  generate =
    ?Context(x,y,z);
    rules( Lab : Pat1(x, es) -> Pat2(y, <op>(z, es)) )
When applying generate a mapping from Keys(x) to Defined(y, z) is generated. When applying generate twice in a context with the same value for x, the second rule overrides the first, i.e., the first will be invisible.

Two dynamic rules with the same label share the same mapping from values to keys. If the number of context-variables in the left-hand side of the rule is the same, subsequent rule generations will shadow earlier ones, even when the static part of the dynamic rule is different.

MarkusLauer? reports an example where this causes different behaviour than expected. The strategy gendyn generates two rules with label Dyn. The idea of the rules is that each time a section is encountered the section counter is incremented, and that titles are transformed using the current section counter:

 gendyn = ?n; 
   where( <add>(n,1) => n1 );
   where( 
     rules(
       Dyn : tag("section", _, l) -> l where<gendyn> n1
       Dyn : tag("title", _, l)   -> para(sect(n),l)
     ))
These rules are then used in the following strategy to number titles:
 transform = 
   {| Dyn : 
      where(<gendyn>0);
      rec x({| Dyn : try(Dyn <+ A); all(try(x)) |})
   |}
However, this approach does not work because the mappings of the two Dyn rules overlap; since the rules do not use any context variables in the left-hand side, the mapping generated for the rules is Keys() to Defined(n1) (first rule) or Defined(n) (second rule). The last rule to be generated will thus determine the values that both rules get.

A workaround in these situations is to use two different rule labels. In the example above the problem is solved by naming the rules Dyn1 and Dyn2.

 gendyn = ?n; 
   where( <add>(n,1) => n1 );
   where( 
     rules(
       Dyn1 : tag("section", _, l) -> l where <gendyn> n1
       Dyn2 : tag("title", _, l)   -> para(sect(n),l)
     ))

 transform = 
   {| Dyn1, Dyn2 : 
      where(<gendyn>0);
      rec x({| Dyn1, Dyn2 : try(Dyn1 + Dyn2 <+ A); all(try(x)) |})
   |}

Now the question is whether this is a bug or a feature. A solution would be to use the entire static part of the left-hand side to determine which dynamic rule should be applied. However, that would also make it more difficult to use the shadowing effect of overlapping context variables. Any opinions on the matter?

-- EelcoVisser - 22 Nov 2001

I believe this is a bug and not a feature. I would intuitively expect several dynamic rules with the same name to behave just like normal rules with the same name. If anyone needs special shadowing effects, couldn't the syntax be extended so that the programmer can specify that?

Another thing; why do we only have dynamic rules, and not dynamic strategies?

-- OttoSkroveBagge - 18 Jan 2002

Implementing User-Defined Rules with Dynamic Rules?

I've made an implementation of user-defined rules for CodeBoost, e.g. the programmer can say,

void rules()
{
   X<int> x;

  simplify: f(g(x)) = fg(x);
}

int main(int argc, char**argv)
{
   X<int> x;

   x = f(g(x));
}
and this is transformed into
int main(int argc, char**argv)
{
   X<int> x;

   x = fg(x);
}
(More or less similar to the "Transform.Playing by the rules..." paper where this is done for Haskell)

This was surprisingly easy to do, but I've run into the "Overlapping Left-Hand Sides"-bug in dynamic rules. I'm still using Stratego 0.6.2 -- is this fixed in newer versions? If not, is there a smart way of avoiding the problem?

Basically, what I'm doing is: 1. extract the user-defined rule specifications and store them as a list in the AST; 2. when the time comes to use the user-defined rules for something, the list is traversed, and dynamic rules are created:

  make-rule' =
   ?Rule("simplify", lhs, rhs, whr);
   rules(simplify: x -> y where <apply-user-rule> (lhs, x, rhs) => y)
Of course, this only works for a predefined set of rule names (and, with the dynamic rule bug, only for the last rule), and I haven't come up with a meaningful way of specifying "where"-clauses yet. apply-user-rule uses

special match/build strategies with support for user-defined variables (represented as RuleVar?("name") in the AST) -- I'll send you the code when I've tested it more.

-- OttoSkroveBagge - 18 Jan 2002

No, this is not an instance of the overlapping left-hand sides problem.

Dynamic rules trigger on the left-hand side. This means that such a rule is only meaningful if has at least one variable in the left-hand side that is bound in the context of the rule. This is not the case in your make-rule'.

The problem is that you cannot directly employ dynamic rules for this application. The paper Scoped Dynamic Rewrite Rules has the following to say about this issue:

Wadler's deforestation algorithm [18] can be expressed using rewrite rules and a simple strategy. Dynamic rules can be used to implement the folding of recursive occurrences of the function composition being deforested. However, this requires abstracting over object variables, which is not supported by the dynamic rules discussed in this papers. Currently, dynamic rules can only inherit ground terms from their de nition context. Another application that would need abstraction over object variables are the rule pragmas of the Glas- gow Haskell Compiler [14] that allow the user to state rewrite rules that should be applied during compilation in addition to normal optimizations.

What you would like to do is to generate a rule

  rules( simplify : ~lhs -> ~rhs )
where ~t expresses the replacement of object variables with meta-variables in the term. I don't currently know how to do this, but is on my mind.

Workaround: Store the (lhs, rhs) pairs in a list and trie to match each in turn.

-- EelcoVisser - 18 Jan 2002

Fix: Taking entire left-hand side into account

The desugaring of dynamic rules has been changed to repair the problems arising when two dynamic rules with the same name have

different left-hand sides, but the same number of context variables in the left-hand side (see discussion below). The key that is used for a dynamic rule is now the entire left-hand side, modulo non-context variables. The new implementation will also make it easier to define (or even generate) operations for saving, restoring, and intersecting sets of dynamic rules, as used in data-flow optimizations.

The bugfix will be available in StrategoRelease09 (beta8).

-- EelcoVisser - 22 Dec 2002

Bug: Rewriting terms with annotations

After some hours of debugging my Tiger to C compiler, I've found a problem in the application of dynamic rules to terms with annotations in StrategoXT 0.9.x . A dynamic rule without annotations in the lhs cannot be applied to a term with annotations.

Of course this is not the behaviour of static rules. This problem breaks all components that use dynamic rules and annotations extensively (like my Tiger to C compiler). In the 0.8 releases of Stratego the behaviour was correct (like static rules).

This works:

-------------------
module dyn-test
imports option dynamic-rules

strategies

   main =
     rules(DynRule : None() -> Some("Blaat"))
     ; <DynRule> None()
-------------------

But this results in a rewriting failed:

-------------------
module dyn-test
imports option dynamic-rules

strategies

   main =
     rules(DynRule : None() -> Some("Blaat"))
     ; <DynRule> None(){"whatever"}
-------------------

-- MartinBravenboer - 29 Jul 2003

The change in behaviour from 0.8 to 0.9.x is due to the change in the semantics of dynamic rules. (See above) According to this semantics the entire term is relevant for the mapping from left-hand side to right-hand side. Repairing this requires stripping off the annotations from a term before doing the lookup in the rule table. (Unless the lhs contains annotations,of course.) Repair is in the release plan for StrategoRelease093.

Workaround: explicitly match on the list of annotations:

-------------------
   test2 =
     rules(DynRule2 : None(){t*} -> Some("Blaat"))
     ; <DynRule2> None(){"whatever"}
     ; <DynRule2> None()
-------------------

-- EelcoVisser - 29 Jul 2003


CategoryBugs? | CategoryToDo?

Stratego.DynamicRuleSemantics moved from Stratego.DynamicRuleBugs on 03 Feb 2002 - 00:10 by EelcoVisser - put it back