Search code examples
rascal

Replacement + Side effect


While visiting a compilation unit--- and given a certain condition- I would like to apply a transformation (using the => operator) and count the number of times the same transformation was applied for a given compilation unit.

I was able to perform that using a kind of "global module variable", but I am quite sure that it is possible to combine both replacements and actions within a single visit expression. Is that possible?

module MultiCatch

import lang::java::\syntax::Java18;
import ParseTree; 
import IO;
import Map;
import Type; 
import List;

// sure, I don't like global variables. 
//
// However I could not find a way to perform both 
// a replacement and count the number of times 
// it was applied in the same compilation unit. 
int numberOfOccurences = 0; 

/**
 * Refactor a try-catch statement to use the 
 * MultiCatch construct of Java 7. 
 */
public tuple[int, CompilationUnit]  refactorMultiCatch(CompilationUnit unit) { 
  numberOfOccurences = 0;  
  CompilationUnit cu =  visit(unit) {
   case (TryStatement)`try <Block b1> <Catches c1>` => (TryStatement)`try <Block b1> <Catches mc>`
     when mc := computeMultiCatches(c1)
  };
  return <numberOfOccurences, cu>;
}

/*
 * Based on a simple notion of similarity, 
 * this function calculates the possible 
 * occurences of MultiCatch. 
 */ 
private Catches computeMultiCatches(cs){
   map [Block, tuple[list[CatchType], VariableDeclaratorId, Block] ] mCatches =();
   visit(cs){
      case(CatchClause)`catch (<CatchType t> <VariableDeclaratorId vId>) <Block b>` :{
         if (b in mCatches){
            <ts, vId, blk> = mCatches[b];
            ts += t;
            mCatches[b] = <ts, vId, blk>;
            numberOfOccurences += 1;
         }
         else{
            mCatches[b] = <[t], vId, b>;
         }
      }
   }
   return generateMultiCatches([mCatches[b] | b <- mCatches]); 
}

/*
 * Creates a syntactic catch clause (either a simple one or 
 * a multicatch). 
 * 
 * This is a recursive definition. The base case expects only 
 * one tuple, and than it returns a single catch clause. In the 
 * recursive definition, at least two tuples must be passed as 
 * arguments, and thus it returns at least two catches clauses 
 * (actually, one catch clause for each element in the list)
 */
private Catches generateMultiCatches([<ts, vId, b>]) = {
  types = parse(#CatchType, intercalate("| ", ts));
  return (Catches)`catch(<CatchType types>  <VariableDeclaratorId vId>) <Block b>`; 
};
private Catches generateMultiCatches([<ts, vId, b>, C*]) = {
  catches = generateMultiCatches(C);
  types = parse(#CatchType, intercalate("| ", ts));
  return (Catches)`catch(<CatchType types> <VariableDeclaratorId vId>) <Block b> <CatchClause+ catches>`;
};

Solution

  • One way to do it is using a local variable and a block with an insert:

    module MultiCatch
    
    import lang::java::\syntax::Java18;
    import ParseTree; 
    import IO;
    import Map;
    import Type; 
    import List;
    
    /**
     * Refactor a try-catch statement to use the 
     * MultiCatch construct of Java 7. 
     */
    public tuple[int, CompilationUnit]  refactorMultiCatch(CompilationUnit unit) { 
      int numberOfOccurences = 0;  /* the type is superfluous */
      CompilationUnit cu =  visit(unit) {
       case (TryStatement)`try <Block b1> <Catches c1>` : {
          numberOfOccurences += 1;
          mc = computeMultiCatches(c1)
          insert (TryStatement)`try <Block b1> <Catches mc>`;
       }
      };
    
      return <numberOfOccurences, cu>;
    }
    
    • The {...} block allows multiple statements to be executed after the match;
    • The local variable is now only present in the frame of refactorMultiCatch;
    • The insert statement has the same effect as the previous => arrow did;
    • Since the match := always succeeds, I changed the when clause into a simple assigment

    There is also other more complex ways to share state in Rascal, but I personally prefer to have state not escape the lexical scope of a function.