/** @{inheritDoc} */
    @Override
    public void visit(final Factor n) {
      if (n.getOperand() instanceof Identifier) {
        final Identifier id = (Identifier) n.getOperand();
        if (id.getToken().equals("visit")) {
          final Call c = (Call) n.getOp(0);
          if (c.getArgsSize() == 1) {
            c.addArg(ASTFactory.createFactorExpr(original.clone()));
            return;
          }
        }
      }

      super.visit(n);
    }
  /** @{inheritDoc} */
  @SuppressWarnings("unchecked")
  @Override
  public void visit(VisitorExpression n) {
    int wildcardStop = 0;
    final Map<BoaProtoTuple, Integer> beforeStopTypes = new HashMap<BoaProtoTuple, Integer>();
    final Map<VisitStatement, Integer> bodyMap = new HashMap<VisitStatement, Integer>();

    original = n.clone();

    //
    // 1) convert all self-visit() calls to pass in copy of the original visitor
    //
    transformSelfVisits.start(n.getBody());

    //
    // 2) add a long var declaration to the enclosing scope
    //
    n.insertStatementBefore(
        new VarDeclStatement(
            new Identifier(varCounterName),
            new Identifier("int"),
            ASTFactory.createFactorExpr(new IntegerLiteral("0"))));

    //
    // 3) find all visit statements containing stop statements, for each one:
    //
    stopFinder.start(n.getBody());
    for (final VisitStatement v : stopFinder.getVisits()) {
      //
      //    a) lift the body into a new function in the surrounding scope
      //
      final FunctionType funcType = new FunctionType();
      final String funcName;
      if (v.hasComponent()) {
        funcType.addArg(v.getComponent().clone());
        funcName = funcWithVisitorId + ++counter;
      } else {
        funcName = funcId + ++counter;
      }
      final Block funcBody = v.getBody().clone();
      final VarDeclStatement var =
          new VarDeclStatement(
              new Identifier(funcName),
              ASTFactory.createFactorExpr(new FunctionExpression(funcType, funcBody)));
      n.insertStatementBefore(var);

      //
      //       i) replace all stop statements with incrementing long var and a return statement
      //
      transformStops.start(funcBody);

      //
      //    b) replace the body with a call to the function
      //
      final Call c2 = new Call();
      if (funcType.getArgsSize() > 0)
        c2.addArg(ASTFactory.createFactorExpr(funcType.getArg(0).getIdentifier().clone()));
      final Factor f2 = new Factor(new Identifier(funcName));
      f2.addOp(c2);
      v.setBody(
          new Block()
              .addStatement(
                  new ExprStatement(
                      new Expression(
                          new Conjunction(new Comparison(new SimpleExpr(new Term(f2))))))));

      bodyMap.put(v, counter);

      if (v.hasWildcard()) wildcardStop = counter;
      else if (v.hasComponent())
        beforeStopTypes.put((BoaProtoTuple) v.getComponent().type, counter);
      else
        for (final Identifier id : v.getIdList())
          beforeStopTypes.put((BoaProtoTuple) id.type, counter);
    }

    final Set<BoaProtoTuple> keys = new HashSet<BoaProtoTuple>(beforeStopTypes.keySet());
    final Set<BoaProtoTuple> remainingAfters = new HashSet<BoaProtoTuple>(keys);

    //
    // 4) find all after visits matching the visited node types in step 1, for each one:
    //
    visitClassifier.start(n.getBody());
    for (final VisitStatement v : visitClassifier.getAfters()) {
      final Set<BoaProtoTuple> types = new HashSet<BoaProtoTuple>();
      if (v.hasComponent()) types.add((BoaProtoTuple) v.getComponent().getType().type);
      else for (final Identifier id : v.getIdList()) types.add((BoaProtoTuple) id.type);

      final Set<Integer> intersection = new HashSet<Integer>();
      for (final BoaProtoTuple t : keys)
        if (types.contains(t)) intersection.add(beforeStopTypes.get(t));
      if (wildcardStop > 0 && v.hasWildcard()) intersection.add(wildcardStop);

      if (intersection.isEmpty()) continue;

      remainingAfters.removeAll(types);

      //
      //    a) wrap body in an "if (stop == 0)" guard
      //
      addGuard(v);

      //
      //    b) add code "if (stop > 0) stop--;", after ALL if() blocks
      //
      createReset(v.getBody());
    }

    //
    // 5) for any node type matching the visited node types in step 1 not found in step 2:
    //
    for (final BoaProtoTuple t : remainingAfters) {
      //
      //    a) add an after visit for that type
      //
      final Block body = new Block();
      n.getBody()
          .addStatement(
              new VisitStatement(
                  false, new Component(new Identifier("n"), new Identifier(t.toString())), body));

      //
      //    b) add code "if (stop > 0) stop--;"
      //
      createReset(body);
    }

    if (wildcardStop > 0 && !visitClassifier.hasDefaultAfter()) {
      final Block body = new Block();
      n.getBody().addStatement(new VisitStatement(false, true, body));
      createReset(body);
    }

    //
    // 6) wrap default before visit body in an "if (stop == 0)" guard
    //
    if (visitClassifier.hasDefaultBefore()) addGuard(visitClassifier.getDefaultBefore());

    //
    // 7) find all before/after visits with a node type below a node type found in step 1, for each
    // one:
    //
    for (final BoaProtoTuple t : beforeStopTypes.keySet()) {
      final List<VisitStatement> statements =
          new ArrayList<VisitStatement>(visitClassifier.getBefores());
      statements.addAll(visitClassifier.getAfters());

      for (final VisitStatement v : statements) {
        final Set<Class<? extends BoaProtoTuple>> types =
            new HashSet<Class<? extends BoaProtoTuple>>();
        if (v.hasComponent())
          types.add((Class<? extends BoaProtoTuple>) v.getComponent().getType().type.getClass());
        else
          for (final Identifier id : v.getIdList())
            types.add((Class<? extends BoaProtoTuple>) id.type.getClass());

        types.retainAll(t.reachableTypes());
        if (types.isEmpty()) continue;

        //
        //    a) add code "if (stop > 0) stop--;", after ALL if() blocks
        //
        // FIXME the nesting makes for more than 1 guard
        addGuard(v);
      }
    }

    //    c) add code "if (stop > 0) stop++;" to the START of the new body
    for (final VisitStatement v : bodyMap.keySet())
      v.getBody()
          .getStatements()
          .add(
              0,
              new IfStatement(
                  ASTFactory.createComparison(
                      new Identifier(varCounterName), ">", new IntegerLiteral("0")),
                  new Block()
                      .addStatement(
                          new PostfixStatement(
                              ASTFactory.createFactorExpr(new Identifier(varCounterName)), "++"))));
  }
 public AssignmentStatement clone() {
   final AssignmentStatement s = new AssignmentStatement(lhs.clone(), rhs.clone());
   copyFieldsTo(s);
   return s;
 }
 public AssignmentStatement(final Factor lhs, final Expression rhs) {
   if (lhs != null) lhs.setParent(this);
   if (rhs != null) rhs.setParent(this);
   this.lhs = lhs;
   this.rhs = rhs;
 }