Search code examples
attributesrascal

Accessing _all_ downstream annotations of a node in Rascal


I read this question while trying to do something similar. The answer given there does not solve my problem.

I want to use a visit statement to determine the 'mass' of each subtree, so for each node I want to sum the masses of all descendants. For example, a visiting step which encounters this node expression with a list in it:

\anode([\bnode()[@mass=1], \bnode()[@mass=2]], \cnode()[@mass=5])

should yield this:

\anode([\bnode()[@mass=1], \bnode()[@mass=2]], \cnode()[@mass=5])[@mass=8]

So I do not just want to filter out the non-nodes, but actually traverse them. Including a case for lists in my visit statement does not work (at least not obviously) because lists cannot have annotations.

Is there a natural way to propagate the complete annotation information up a tree?


Solution

  • This first solution is precise and useful if the number of nodes with container children is not so large:

    x = \anode([\bnode()[@mass=1], \bnode()[@mass=2]], \cnode()[@mass=5]);
    
    visit(x) {
      case n:\anode(l) => n[@mass=(0 | it + e@mass | e <- l)]
    }
    

    (You'd probably also abstract the reducer expression in a local function)

    The second solution would be to abstract from the node type, in case you have many of these:

    visit(x) {
      case n:str _(l) => n[@mass=(0 | it + e@mass | e <- l)]
    }
    

    The third solution would work best in case the annotations are nested even deeper, as in lists of lists etc, and you have many different types of nodes:

    import Node;
    int computeMass(list[value] x) {
      mass = 0;
      top-down-break visit(x) {
        case node x : mass += (x@mass?) ? x@mass : 0;
      }
      return mass;
    }
    
    visit(x) {
      case node n => n[@mass=computeMass(getChildren(n))]
    }
    

    I prefer the very first solution because it is most precise.

    Note that we are replacing the annotations feature by "keyword parameters" in the near future; with almost the same semantics, different syntax, like \cnode(mass=2) for \code()[@mass=2]. With the new keyword parameters you would also have a lazily computed default value for the mass field, like so:

    data N() 
      = anode(list[N] children, int mass=(0 | it + c.mass | c <- children)) 
      | cnode(int mass=0)
      ;
    
    anode([cnode(mass=1),cnode(mass=2)]).mass == 3