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());
}
}
};
}