Search code examples
rascal

Nested descendant pattern matches


I'm trying to find all method calls and the classes that contain them. If I understand correctly, pattern matches perform backtracking to match in all possible ways.

Take the following java code.

package main;
public class Main {
    public static void main(String[] args) {
        System.out.println("hello world");
        System.out.println("hello again");
    }
}

I'm loading the code with createAstsFromDirectory.

rascal>ast = createAstsFromDirectory(|home:///multiple-method-calls|, false);

I'm trying to find both calls to println. The following code matches once:

void findCalls(set[Declaration] ast)
{
  visit(ast)
  {
    case \class(_,_,_,/\methodCall(_,_,str methodName,_)):
      println("<methodName>");
  }
}
rascal>findCalls(ast);
println
ok

This code matches four times:

void findCalls(set[Declaration] ast)
{
  visit(ast)
  {
    case /\class(_,_,_,/\methodCall(_,_,str methodName,_)):
      println("<methodName>");
  }
}
rascal>findCalls(ast);
println
println
println
println
ok

How must the pattern look like to match exactly twice?

Related question, how to access the class name? When trying to access the class name I get an error message.

void findCalls(set[Declaration] ast)
{
  visit(ast)
  {
    case /\class(str className,_,_,/\methodCall(_,_,str methodName,_)):
      println("<className> <methodName>");
  }
}
findCalls(ast);
Main println
|project://personal-prof/src/Assignment13Rules.rsc|(3177,9,<141,16>,<141,25>): Undeclared variable: className

It looks like the first match has className bound correctly to "Main", but the second one does not.


Solution

  • I think I would write this:

    void findCalls(set[Declaration] ast) {
      for(/class(_, _, _, /methodCall(_,_,str methodName,_)) <- ast) {
          println("<methodName>");
      }
    }
    

    The for loop goes for every class it can find, through every methodCall it can find inside and thus twice for the example you gave.

    Your second try goes wrong and matches too often: if you nest a / at the top of the case of a visit, you visit every position in the tree once, and the traverse the entire sub-tree again including the root node again. So you would get each call twice.

    Your first try goes wrong because the top-level of a case pattern of a visit does not back-track on itself, it finds the first match for the entire pattern and then stops. So the nested / is only matched once and then the body is executed.

    In the for loop solution, the for-loop (unlike the visit) will try all possible matches until it stops, so that's the way to go. There is yet another solution closer to your original plan:

    void findCalls(set[Declaration] ast) {
          visit (ast) {
            case class(_, _, _, body) : 
              for (/methodCall(_,_,str methodName,_) <- body) {
                println("<methodName>");
              }
          }
    }   
    

    This also works just like the for loop, it first finds all the classes one-by-one via the visit and then goes through all the nested matches via the for loop.

    Finally you could also nest the visit itself to get the right answer:

    void findCalls(set[Declaration] ast) {
          visit (ast) {
            case class(_, _, _, body) : 
              visit(body) {
                case methodCall(_,_,str methodName,_): {
                println("<methodName>");
              }
            }
          }
    }
    

    WRT to the className thing, it appears that the combination of visit and top-level deep match on a case like so: case /<pattern> there is a bug in the Rasca interpreter with a loss of variable bindings in the deep pattern. So pls avoid that pattern (I don't think you need it), and if you feel like it, submit an issue report on github?

    In the for-loop case, this will simply work as expected:

    for(/class(str className, _, _, /methodCall(_,_,str methodName,_)) <- ast) {
       println("<className>::<methodName>");
    }