private Expr cleanupOpExpr(OpExpr opExp) {
    FunctionalRef ref = opExp.getOp();

    List<Expr> args = opExp.getArgs();

    if (args.size() <= 1) return opExp;
    IdOrOp name = ref.getNames().get(0);
    if (!(name instanceof Op)) return bug(name, "The name field of OpRef should be Op.");
    Op qop = (Op) name;

    if (OprUtil.isEnclosing(qop)) return opExp;
    if (OprUtil.isUnknownFixity(qop))
      return bug(opExp, "The operator fixity is unknown: " + ((Op) qop).getText());
    boolean prefix = OprUtil.hasPrefixColon(qop);
    boolean suffix = OprUtil.hasSuffixColon(qop);
    if (!prefix && !suffix) return opExp;
    qop = OprUtil.noColon(qop);
    Iterator<Expr> i = args.iterator();
    Expr res = i.next();
    Span sp = NodeUtil.getSpan(opExp);
    for (Expr arg : Iter.iter(i)) {
      if (prefix) {
        res = thunk(res);
      }
      if (suffix) {
        arg = thunk(arg);
      }
      res = ExprFactory.makeOpExpr(sp, qop, res, arg);
    }
    return res;
  }
  private Expr visitAccumulator(
      Span span,
      List<GeneratorClause> gens,
      Op op,
      Expr body,
      List<StaticArg> staticArgs,
      boolean isParen) {
    body = visitGenerators(span, gens, body);
    /**
     * * If the accumulation is a nested reduction like BIG OP [ys <- gg] BIG OT <| f y | y <- ys |>
     * , visitGenerators returns a tuple of ((BIG OT, f), gg) (this should be refactored, though)
     */
    Expr res;
    if (body instanceof FnExpr) {
      Expr opexp = ExprFactory.makeOpExpr(span, op, staticArgs);
      res =
          ExprFactory.make_RewriteFnApp(
              span, BIGOP_NAME, ExprFactory.makeTupleExpr(span, opexp, body));
    } else if (body instanceof TupleExpr) {
      /**
       * * For BIG OP [ys <- gg] BIG OT <| f y | y <- ys |> The nested reduction is replaced with
       * __bigOperator2(BIG OP, BIG OT, gg)
       *
       * <p>This is similar to forOpExpr(OpExpr that) .
       *
       * <p>by Kento
       */
      // a tuple of the inner Accumulator (op, body) and the gg
      TupleExpr tuple = (TupleExpr) body;
      TupleExpr innerAccumTuple = (TupleExpr) tuple.getExprs().get(0);
      Expr opexpI = (Expr) innerAccumTuple.getExprs().get(0);
      Expr innerBody = (Expr) innerAccumTuple.getExprs().get(1);
      Expr opexpO = ExprFactory.makeOpExpr(span, op, staticArgs);
      Expr gg = tuple.getExprs().get(1);

      res =
          ExprFactory.make_RewriteFnApp(
              span, BIGOP2_NAME, ExprFactory.makeTupleExpr(span, opexpO, opexpI, gg, innerBody));
    } else res = bug(body, "Function expressions or tuple expressions are expected.");
    if (isParen) res = ExprFactory.makeInParentheses(res);
    return (Expr) recur(res);
  }
  @Override
  public Node forAmbiguousMultifixOpExpr(AmbiguousMultifixOpExpr that) {
    // If there is a colon at all, the operator is no longer ambiguous:
    // It must be infix.
    IdOrOp name = that.getInfix_op().getNames().get(0);
    if (!(name instanceof Op)) return bug(name, "The name field of OpRef should be Op.");
    Op op_name = (Op) name;
    boolean prefix = OprUtil.hasPrefixColon(op_name);
    boolean suffix = OprUtil.hasSuffixColon(op_name);

    if (prefix || suffix) {
      OpExpr new_op =
          ExprFactory.makeOpExpr(
              NodeUtil.getSpan(that),
              NodeUtil.isParenthesized(that),
              NodeUtil.getExprType(that),
              that.getInfix_op(),
              that.getArgs());
      return recur(new_op);
    } else {
      return super.forAmbiguousMultifixOpExpr(that);
    }
  }
 @Override
 public Node forAssignment(Assignment that) {
   // System.out.println("PreDesugar entry");
   // Here there are three sorts of rewrite to consider:
   // (a) If this is a compound assignment, rewrite to use ordinary assignment.
   // (b) If the lhs is a tuple, rewrite into a set of individual assignments.
   // (c) If the lhs is a subscript expression, rewrite to a method call.
   List<Lhs> lhs = that.getLhs();
   Option<FunctionalRef> assignOp = that.getAssignOp();
   Expr rhs = that.getRhs();
   List<CompoundAssignmentInfo> assignmentInfos = that.getAssignmentInfos();
   if (!Shell.getAssignmentPreDesugaring()) {
     // System.out.println("Did not PreDesugar");
     return super.forAssignment(that);
   } else if (assignOp.isSome() || lhs.size() > 1) {
     // System.out.println("Compound/tuple PreDesugar");
     // Compound and/or tuple assignment
     // The basic idea is to transform `(a, b.field, c[sub1,sub2]) := e` into
     // `do (ta, tb, tc, tsub1, tsub2, (t1, t2, t3)) = (a, b, c, sub1, sub2, e)
     //     a := t1; tb.field := t2; tc[tsub1, tsub2] := t3 end`
     // (TODO) Unfortunately, currently we don't handle nested binding tuples.
     // For now, we'll just transform it into
     // `do (ta, tb, tc, tsub1, tsub2) = (a, b, c, sub1, sub2)
     //     (t1, t2, t3) = e
     //     a := t1; tb.field := t2; tc[tsub1, tsub2] := t3 end`
     // which merely loses a bit of potential parallelism.
     // We omit the first tuple binding if the tuple is empty.
     // If it is a compound assignment `(a, b.field, c[sub1,sub2]) OP= e`, it becomes
     // `do (ta, tb, tc, tsub1, tsub2) = (a, b, c, sub1, sub2)
     //     (t1, t2, t3) = (ta, tb, tc[tsub1, tsub2]) OP e
     //     a := t1; tb.field := t2; tc[tsub1, tsub2] := t3 end`
     List<LValue> exprLValues = Useful.list();
     List<LValue> otherLValues = Useful.list();
     List<Expr> otherExprs = Useful.list();
     List<Expr> assignments = Useful.list();
     boolean isCompound = assignOp.isSome();
     List<Expr> accesses = Useful.list();
     Span thatSpan = NodeUtil.getSpan(that);
     for (Lhs lh : lhs) {
       Span lhSpan = NodeUtil.getSpan((Expr) lh);
       Id tempId = DesugarerUtil.gensymId(lhSpan, "e");
       VarRef tempVar = ExprFactory.makeVarRef(lhSpan, tempId);
       exprLValues = Useful.snoc(exprLValues, NodeFactory.makeLValue(lhSpan, tempId));
       if (lh instanceof SubscriptExpr) {
         SubscriptExpr lhsub = (SubscriptExpr) lh;
         Expr obj = lhsub.getObj();
         Span objSpan = NodeUtil.getSpan(obj);
         List<Expr> subs = lhsub.getSubs();
         Id baseTempId = DesugarerUtil.gensymId(objSpan, "b");
         VarRef baseTempVar = ExprFactory.makeVarRef(objSpan, baseTempId);
         otherLValues = Useful.snoc(otherLValues, NodeFactory.makeLValue(objSpan, baseTempId));
         otherExprs = Useful.snoc(otherExprs, obj);
         List<Expr> subTempVars = Useful.list();
         for (Expr sub : lhsub.getSubs()) {
           Span subSpan = NodeUtil.getSpan(sub);
           Id subTempId = DesugarerUtil.gensymId(subSpan, "s");
           subTempVars = Useful.snoc(subTempVars, ExprFactory.makeVarRef(subSpan, subTempId));
           otherLValues = Useful.snoc(otherLValues, NodeFactory.makeLValue(subSpan, subTempId));
         }
         otherExprs = Useful.concat(otherExprs, subs);
         SubscriptExpr newLhs =
             ExprFactory.makeSubscriptExpr(
                 NodeUtil.getSpan(lhsub),
                 baseTempVar,
                 subTempVars,
                 lhsub.getOp(),
                 lhsub.getStaticArgs());
         if (isCompound) accesses = Useful.snoc(accesses, newLhs);
         assignments =
             Useful.snoc(assignments, ExprFactory.makeAssignment(thatSpan, newLhs, tempVar));
       } else if (lh instanceof FieldRef) {
         FieldRef lhref = (FieldRef) lh;
         Expr obj = lhref.getObj();
         Span objSpan = NodeUtil.getSpan(obj);
         Id objTempId = DesugarerUtil.gensymId(objSpan, "o");
         VarRef objTempVar = ExprFactory.makeVarRef(objSpan, objTempId);
         otherLValues = Useful.snoc(otherLValues, NodeFactory.makeLValue(objSpan, objTempId));
         otherExprs = Useful.snoc(otherExprs, obj);
         FieldRef newLhs =
             ExprFactory.makeFieldRef(NodeUtil.getSpan(lhref), objTempVar, lhref.getField());
         if (isCompound) accesses = Useful.snoc(accesses, newLhs);
         assignments =
             Useful.snoc(assignments, ExprFactory.makeAssignment(thatSpan, newLhs, tempVar));
       } else if (lh instanceof VarRef) {
         VarRef lhvar = (VarRef) lh;
         Span varSpan = NodeUtil.getSpan(lhvar);
         Id varTempId = DesugarerUtil.gensymId(varSpan, "v");
         VarRef varTempVar = ExprFactory.makeVarRef(varSpan, varTempId);
         otherLValues = Useful.snoc(otherLValues, NodeFactory.makeLValue(varSpan, varTempId));
         otherExprs = Useful.snoc(otherExprs, lhvar);
         if (isCompound) accesses = Useful.snoc(accesses, varTempVar);
         assignments =
             Useful.snoc(assignments, ExprFactory.makeAssignment(thatSpan, lhvar, tempVar));
       } else {
         bug(that, "Malformed assignment LHS");
       }
     }
     Expr result = ExprFactory.makeBlock(thatSpan, assignments);
     if (otherExprs.size() > 0) {
       Expr otherRhs = ExprFactory.makeMaybeTupleExpr(thatSpan, otherExprs);
       result = ExprFactory.makeLocalVarDecl(thatSpan, otherLValues, otherRhs, result);
     }
     Expr newRhs =
         isCompound
             ? ExprFactory.makeOpExpr(
                 NodeUtil.spanTwo(assignOp.unwrap(), rhs),
                 assignOp.unwrap(),
                 ExprFactory.makeMaybeTupleExpr(thatSpan, accesses),
                 rhs)
             : rhs;
     result = ExprFactory.makeLocalVarDecl(thatSpan, exprLValues, newRhs, result);
     return (Expr) recur(result);
   } else if (lhs.get(0) instanceof SubscriptExpr) {
     // System.out.println("PreDesugar single subscript expr");
     // Subscripted assignment
     SubscriptExpr lhExpr = (SubscriptExpr) lhs.get(0);
     Expr obj = lhExpr.getObj();
     List<Expr> subs = lhExpr.getSubs();
     Option<Op> op = lhExpr.getOp();
     List<StaticArg> staticArgs = lhExpr.getStaticArgs();
     if (!op.isSome()) bug(lhExpr, "Subscript operator expected");
     Op knownOp = op.unwrap();
     Expr result =
         ExprFactory.makeMethodInvocation(
             that,
             obj,
             NodeFactory.makeOp(knownOp, knownOp.getText() + ":="),
             staticArgs,
             ExprFactory.makeTupleExpr(NodeUtil.spanTwo(knownOp, rhs), Useful.cons(rhs, subs)));
     return (Expr) recur(result);
   } else {
     // System.out.println("PreDesugar single expr of class " + lhs.get(0).getClass().getName());
     return super.forAssignment(that);
   }
 }
  @Override
  public Node forOpExpr(OpExpr that) {
    FunctionalRef op_result = (FunctionalRef) recur(that.getOp());

    /**
     * * For BIG OP <| BIG OT <| f y | y <- ys |> | ys <- gg |> Is this case, BIG <||> is being
     * removed. The nested reduction is replaced with __bigOperator2(BIG OP, BIG OT, gg)
     *
     * <p>by Kento
     */
    String str = op_result.toString();
    String theListEnclosingOperatorName = "BIG <| BIG |>";
    String someBigOperatorName = "BIG";

    // make sure the body is of application of some big operator
    if ((str.length() >= someBigOperatorName.length()
        && str.substring(0, someBigOperatorName.length()).equals(someBigOperatorName))) {
      // make sure that BIG OP (Accumulator (BIG <||>, gs))
      if (that.getArgs().size() == 1
          && that.getArgs().get(0) instanceof Accumulator
          && ((Accumulator) that.getArgs().get(0))
              .getAccOp()
              .toString()
              .equals(theListEnclosingOperatorName)) {

        Accumulator acc = (Accumulator) that.getArgs().get(0);
        Expr body = visitGenerators(NodeUtil.getSpan(acc), acc.getGens(), acc.getBody());
        /**
         * * If the accumulation is a nested reduction like <| BIG OT <| f y | y <- ys |> | ys <- gg
         * |> , visitGenerators returns a tuple of ((BIG OT, f), gg) (this should be refactored,
         * though) In this case, the nested reduction is replaced with __bigOperator2
         */
        if (body instanceof TupleExpr) {
          // a tuple of the inner Accumulator (op, body) and the gg
          TupleExpr tuple = (TupleExpr) body;
          TupleExpr innerAccumTuple = (TupleExpr) tuple.getExprs().get(0);
          Expr opexpI = (Expr) innerAccumTuple.getExprs().get(0);
          Expr innerBody = (Expr) innerAccumTuple.getExprs().get(1);
          FunctionalRef ref = (FunctionalRef) op_result;
          IdOrOp name = ref.getNames().get(0);
          // make sure the operator is actually an operator
          if (!(name instanceof Op)) return null;
          Expr opexpO =
              ExprFactory.makeOpExpr(NodeUtil.getSpan(that), (Op) name, ref.getStaticArgs());
          Expr gg = tuple.getExprs().get(1);
          Expr res =
              ExprFactory.make_RewriteFnApp(
                  NodeUtil.getSpan(that),
                  BIGOP2_NAME,
                  ExprFactory.makeTupleExpr(NodeUtil.getSpan(body), opexpO, opexpI, gg, innerBody));
          return (Expr) recur(res);
        }
      }
    }

    List<Expr> args_result = recurOnListOfExpr(that.getArgs());

    OpExpr new_op;
    if (op_result == that.getOp() && args_result == that.getArgs()) {
      new_op = that;
    } else {
      new_op =
          ExprFactory.makeOpExpr(
              NodeUtil.getSpan(that),
              NodeUtil.isParenthesized(that),
              NodeUtil.getExprType(that),
              op_result,
              args_result);
    }
    return cleanupOpExpr(new_op);
  }