Congruences would be one way, but unfortunately in Kiama they require some boilerplate. If you want to go in that direction, see Kiama's lambda2 example. AST.scala defines congruences for the tree node types and files such as ParLazySubst.scala use them to define strategies. E.g., in App (s, id)
, the App
is a congruence and the App (s, id)
strategy succeeds on App
nodes if s succeeds on the first child of the node (id
is the identity strategy).
An alternative is to use child
which is sort of a generic congruence for a single child, where you say which child you want to operate on by giving its number. (Alternatively, if you don't know which child it is or you want to operate on more than one child, you can use all
, one
, or some
.)
E.g., I think the following is a clearer way to do what you are doing above:
def r1 =
rule {
case L (l) if l.s == "X" => L ("Did stuff")
}
def r2 =
rule {
case B (l, r) => B (l, "Avoided")
}
val r3 = (child (1, r1)) <* r2
and then use r3.
Notice that the child (...) strategy operates on the original input term, so we can use normal sequencing (<*) to decide whether or not to apply r2 to that term as well. This solution is more composable since r2 doesn't have to know anything about r1.