private JavascriptBaseVisitor newClassFileGeneratorVisitor()

in lucene/expressions/src/java/org/apache/lucene/expressions/js/JavascriptCompiler.java [363:766]


  private JavascriptBaseVisitor<Void> newClassFileGeneratorVisitor(
      CodeBuilder gen,
      final Map<String, Integer> externalsMap,
      final Map<String, Integer> constantsMap) {
    // to completely hide the ANTLR visitor we use an anonymous impl:
    return new JavascriptBaseVisitor<Void>() {
      private final Deque<TypeKind> typeStack = new ArrayDeque<>();

      @Override
      public Void visitCompile(JavascriptParser.CompileContext ctx) {
        typeStack.push(TypeKind.DOUBLE);
        visit(ctx.expression());
        typeStack.pop();

        return null;
      }

      @Override
      public Void visitPrecedence(JavascriptParser.PrecedenceContext ctx) {
        visit(ctx.expression());

        return null;
      }

      @Override
      public Void visitNumeric(JavascriptParser.NumericContext ctx) {
        if (ctx.HEX() != null) {
          pushLong(Long.parseLong(ctx.HEX().getText().substring(2), 16));
        } else if (ctx.OCTAL() != null) {
          pushLong(Long.parseLong(ctx.OCTAL().getText().substring(1), 8));
        } else if (ctx.DECIMAL() != null) {
          gen.loadConstant(Double.parseDouble(ctx.DECIMAL().getText()));
          gen.conversion(TypeKind.DOUBLE, typeStack.peek());
        } else {
          throw new IllegalStateException("Unknown operation specified: " + ctx.getText());
        }

        return null;
      }

      @Override
      public Void visitExternal(JavascriptParser.ExternalContext ctx) {
        String text = ctx.VARIABLE().getText();
        int arguments = ctx.expression().size();
        boolean parens = ctx.LP() != null && ctx.RP() != null;
        MethodHandle mh = parens ? functions.get(text) : null;

        if (mh != null) {
          final int arity = mh.type().parameterCount();

          if (arguments != arity) {
            throw newWrappedParseException(
                "Invalid expression '"
                    + sourceText
                    + "': Expected ("
                    + arity
                    + ") arguments for function call ("
                    + text
                    + "), but found ("
                    + arguments
                    + ").",
                ctx.start.getStartIndex());
          }

          // place dynamic constant with MethodHandle on top of stack
          final int index = constantsMap.computeIfAbsent(text, _ -> constantsMap.size());
          final var constantDesc =
              DynamicConstantDesc.ofNamed(
                  ConstantDescs.BSM_CLASS_DATA_AT,
                  ConstantDescs.DEFAULT_NAME,
                  ConstantDescs.CD_MethodHandle,
                  index);
          gen.loadConstant(constantDesc);

          // add arguments:
          typeStack.push(TypeKind.DOUBLE);
          for (int argument = 0; argument < arguments; ++argument) {
            visit(ctx.expression(argument));
          }
          typeStack.pop();

          // invoke MethodHandle of function:
          gen.invokevirtual(
              ConstantDescs.CD_MethodHandle,
              "invokeExact",
              mh.type().describeConstable().orElseThrow());

          gen.conversion(TypeKind.DOUBLE, typeStack.peek());
        } else if (!parens || arguments == 0 && text.contains(".")) {
          text = normalizeQuotes(ctx.getText());
          final int index = externalsMap.computeIfAbsent(text, _ -> externalsMap.size());

          gen.aload(gen.parameterSlot(0));
          gen.loadConstant(index);
          gen.arrayLoad(TypeKind.REFERENCE);
          gen.invokevirtual(CD_DoubleValues, "doubleValue", MTD_DOUBLE_VAL);
          gen.conversion(TypeKind.DOUBLE, typeStack.peek());
        } else {
          throw newWrappedParseException(
              "Invalid expression '" + sourceText + "': Unrecognized function call (" + text + ").",
              ctx.start.getStartIndex());
        }
        return null;
      }

      @Override
      public Void visitUnary(JavascriptParser.UnaryContext ctx) {
        if (ctx.BOOLNOT() != null) {
          Label labelNotTrue = gen.newLabel();
          Label labelNotReturn = gen.newLabel();

          typeStack.push(TypeKind.INT);
          visit(ctx.expression());
          typeStack.pop();
          gen.ifeq(labelNotTrue);
          pushBoolean(false);
          gen.goto_(labelNotReturn);
          gen.labelBinding(labelNotTrue);
          pushBoolean(true);
          gen.labelBinding(labelNotReturn);

        } else if (ctx.BWNOT() != null) {
          typeStack.push(TypeKind.LONG);
          visit(ctx.expression());
          typeStack.pop();
          gen.loadConstant(-1L);
          gen.lxor();
          gen.conversion(TypeKind.LONG, typeStack.peek());

        } else if (ctx.ADD() != null) {
          visit(ctx.expression());

        } else if (ctx.SUB() != null) {
          typeStack.push(TypeKind.DOUBLE);
          visit(ctx.expression());
          typeStack.pop();
          gen.dneg();
          gen.conversion(TypeKind.DOUBLE, typeStack.peek());

        } else {
          throw new IllegalStateException("Unknown operation specified: " + ctx.getText());
        }

        return null;
      }

      @Override
      public Void visitMuldiv(JavascriptParser.MuldivContext ctx) {
        Opcode opcode;

        if (ctx.MUL() != null) {
          opcode = Opcode.DMUL;
        } else if (ctx.DIV() != null) {
          opcode = Opcode.DDIV;
        } else if (ctx.REM() != null) {
          opcode = Opcode.DREM;
        } else {
          throw new IllegalStateException("Unknown operation specified: " + ctx.getText());
        }

        pushArith(opcode, ctx.expression(0), ctx.expression(1));

        return null;
      }

      @Override
      public Void visitAddsub(JavascriptParser.AddsubContext ctx) {
        Opcode opcode;

        if (ctx.ADD() != null) {
          opcode = Opcode.DADD;
        } else if (ctx.SUB() != null) {
          opcode = Opcode.DSUB;
        } else {
          throw new IllegalStateException("Unknown operation specified: " + ctx.getText());
        }

        pushArith(opcode, ctx.expression(0), ctx.expression(1));

        return null;
      }

      @Override
      public Void visitBwshift(JavascriptParser.BwshiftContext ctx) {
        Opcode opcode;

        if (ctx.LSH() != null) {
          opcode = Opcode.LSHL;
        } else if (ctx.RSH() != null) {
          opcode = Opcode.LSHR;
        } else if (ctx.USH() != null) {
          opcode = Opcode.LUSHR;
        } else {
          throw new IllegalStateException("Unknown operation specified: " + ctx.getText());
        }

        pushShift(opcode, ctx.expression(0), ctx.expression(1));

        return null;
      }

      @Override
      public Void visitBoolcomp(JavascriptParser.BoolcompContext ctx) {
        Opcode opcode;

        if (ctx.LT() != null) {
          opcode = Opcode.IFLT;
        } else if (ctx.LTE() != null) {
          opcode = Opcode.IFLE;
        } else if (ctx.GT() != null) {
          opcode = Opcode.IFGT;
        } else if (ctx.GTE() != null) {
          opcode = Opcode.IFGE;
        } else {
          throw new IllegalStateException("Unknown operation specified: " + ctx.getText());
        }

        pushCond(opcode, ctx.expression(0), ctx.expression(1));

        return null;
      }

      @Override
      public Void visitBooleqne(JavascriptParser.BooleqneContext ctx) {
        Opcode opcode;

        if (ctx.EQ() != null) {
          opcode = Opcode.IFEQ;
        } else if (ctx.NE() != null) {
          opcode = Opcode.IFNE;
        } else {
          throw new IllegalStateException("Unknown operation specified: " + ctx.getText());
        }

        pushCond(opcode, ctx.expression(0), ctx.expression(1));

        return null;
      }

      private void pushCond(Opcode operator, ExpressionContext left, ExpressionContext right) {
        Label labelTrue = gen.newLabel();
        Label labelReturn = gen.newLabel();

        typeStack.push(TypeKind.DOUBLE);
        visit(left);
        visit(right);
        typeStack.pop();

        if (operator == Opcode.IFGE || operator == Opcode.IFGT) {
          gen.dcmpl();
        } else {
          gen.dcmpg();
        }
        gen.with(BranchInstruction.of(operator, labelTrue));
        pushBoolean(false);
        gen.goto_(labelReturn);
        gen.labelBinding(labelTrue);
        pushBoolean(true);
        gen.labelBinding(labelReturn);
      }

      @Override
      public Void visitBwand(JavascriptParser.BwandContext ctx) {
        pushBitwise(Opcode.LAND, ctx.expression(0), ctx.expression(1));

        return null;
      }

      @Override
      public Void visitBwxor(JavascriptParser.BwxorContext ctx) {
        pushBitwise(Opcode.LXOR, ctx.expression(0), ctx.expression(1));

        return null;
      }

      @Override
      public Void visitBwor(JavascriptParser.BworContext ctx) {
        pushBitwise(Opcode.LOR, ctx.expression(0), ctx.expression(1));

        return null;
      }

      @Override
      public Void visitBooland(JavascriptParser.BoolandContext ctx) {
        Label andFalse = gen.newLabel();
        Label andEnd = gen.newLabel();

        typeStack.push(TypeKind.INT);
        visit(ctx.expression(0));
        gen.ifeq(andFalse);
        visit(ctx.expression(1));
        gen.ifeq(andFalse);
        typeStack.pop();
        pushBoolean(true);
        gen.goto_(andEnd);
        gen.labelBinding(andFalse);
        pushBoolean(false);
        gen.labelBinding(andEnd);

        return null;
      }

      @Override
      public Void visitBoolor(JavascriptParser.BoolorContext ctx) {
        Label orTrue = gen.newLabel();
        Label orEnd = gen.newLabel();

        typeStack.push(TypeKind.INT);
        visit(ctx.expression(0));
        gen.ifne(orTrue);
        visit(ctx.expression(1));
        gen.ifne(orTrue);
        typeStack.pop();
        pushBoolean(false);
        gen.goto_(orEnd);
        gen.labelBinding(orTrue);
        pushBoolean(true);
        gen.labelBinding(orEnd);

        return null;
      }

      @Override
      public Void visitConditional(JavascriptParser.ConditionalContext ctx) {
        Label condFalse = gen.newLabel();
        Label condEnd = gen.newLabel();

        typeStack.push(TypeKind.INT);
        visit(ctx.expression(0));
        typeStack.pop();
        gen.ifeq(condFalse);
        visit(ctx.expression(1));
        gen.goto_(condEnd);
        gen.labelBinding(condFalse);
        visit(ctx.expression(2));
        gen.labelBinding(condEnd);

        return null;
      }

      private void pushArith(Opcode operator, ExpressionContext left, ExpressionContext right) {
        pushBinaryOp(operator, left, right, TypeKind.DOUBLE, TypeKind.DOUBLE, TypeKind.DOUBLE);
      }

      private void pushShift(Opcode operator, ExpressionContext left, ExpressionContext right) {
        pushBinaryOp(operator, left, right, TypeKind.LONG, TypeKind.INT, TypeKind.LONG);
      }

      private void pushBitwise(Opcode operator, ExpressionContext left, ExpressionContext right) {
        pushBinaryOp(operator, left, right, TypeKind.LONG, TypeKind.LONG, TypeKind.LONG);
      }

      private void pushBinaryOp(
          Opcode operator,
          ExpressionContext left,
          ExpressionContext right,
          TypeKind leftType,
          TypeKind rightType,
          TypeKind returnType) {
        typeStack.push(leftType);
        visit(left);
        typeStack.pop();
        typeStack.push(rightType);
        visit(right);
        typeStack.pop();
        gen.with(OperatorInstruction.of(operator));
        gen.conversion(returnType, typeStack.peek());
      }

      private void pushBoolean(boolean truth) {
        switch (typeStack.peek()) {
          case TypeKind.INT:
            gen.loadConstant(truth ? 1 : 0);
            break;
          case TypeKind.LONG:
            gen.loadConstant(truth ? 1L : 0L);
            break;
          case TypeKind.DOUBLE:
            gen.loadConstant(truth ? 1. : 0.);
            break;
          // $CASES-OMITTED$
          default:
            throw new IllegalStateException("Invalid expected type: " + typeStack.peek());
        }
      }

      private void pushLong(long i) {
        switch (typeStack.peek()) {
          case TypeKind.INT:
            gen.loadConstant((int) i);
            break;
          case TypeKind.LONG:
            gen.loadConstant(i);
            break;
          case TypeKind.DOUBLE:
            gen.loadConstant((double) i);
            break;
          // $CASES-OMITTED$
          default:
            throw new IllegalStateException("Invalid expected type: " + typeStack.peek());
        }
      }
    };
  }