Search in sources :

Example 1 with ExpressionContext

use of org.apache.lucene.expressions.js.JavascriptParser.ExpressionContext in project lucene-solr by apache.

the class JavascriptCompiler method generateClass.

/**
   * Sends the bytecode of class file to {@link ClassWriter}.
   */
private void generateClass(final ParseTree parseTree, final ClassWriter classWriter, final Map<String, Integer> externalsMap) throws ParseException {
    classWriter.visit(CLASSFILE_VERSION, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL, COMPILED_EXPRESSION_INTERNAL, null, EXPRESSION_TYPE.getInternalName(), null);
    final String clippedSourceText = (sourceText.length() <= MAX_SOURCE_LENGTH) ? sourceText : (sourceText.substring(0, MAX_SOURCE_LENGTH - 3) + "...");
    classWriter.visitSource(clippedSourceText, null);
    final GeneratorAdapter constructor = new GeneratorAdapter(Opcodes.ACC_PUBLIC, EXPRESSION_CTOR, null, null, classWriter);
    constructor.loadThis();
    constructor.loadArgs();
    constructor.invokeConstructor(EXPRESSION_TYPE, EXPRESSION_CTOR);
    constructor.returnValue();
    constructor.endMethod();
    final GeneratorAdapter gen = new GeneratorAdapter(Opcodes.ACC_PUBLIC, EVALUATE_METHOD, null, null, classWriter);
    // to completely hide the ANTLR visitor we use an anonymous impl:
    new JavascriptBaseVisitor<Void>() {

        private final Deque<Type> typeStack = new ArrayDeque<>();

        @Override
        public Void visitCompile(JavascriptParser.CompileContext ctx) {
            typeStack.push(Type.DOUBLE_TYPE);
            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.push(Double.parseDouble(ctx.DECIMAL().getText()));
                gen.cast(Type.DOUBLE_TYPE, 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;
            Method method = parens ? functions.get(text) : null;
            try {
                if (method != null) {
                    int arity = method.getParameterTypes().length;
                    if (arguments != arity) {
                        throw new ParseException("Invalid expression '" + sourceText + "': Expected (" + arity + ") arguments for function call (" + text + "), but found (" + arguments + ").", ctx.start.getStartIndex());
                    }
                    typeStack.push(Type.DOUBLE_TYPE);
                    for (int argument = 0; argument < arguments; ++argument) {
                        visit(ctx.expression(argument));
                    }
                    typeStack.pop();
                    gen.invokeStatic(Type.getType(method.getDeclaringClass()), org.objectweb.asm.commons.Method.getMethod(method));
                    gen.cast(Type.DOUBLE_TYPE, typeStack.peek());
                } else if (!parens || arguments == 0 && text.contains(".")) {
                    int index;
                    text = normalizeQuotes(ctx.getText());
                    if (externalsMap.containsKey(text)) {
                        index = externalsMap.get(text);
                    } else {
                        index = externalsMap.size();
                        externalsMap.put(text, index);
                    }
                    gen.loadArg(0);
                    gen.push(index);
                    gen.arrayLoad(FUNCTION_VALUES_TYPE);
                    gen.invokeVirtual(FUNCTION_VALUES_TYPE, DOUBLE_VAL_METHOD);
                    gen.cast(Type.DOUBLE_TYPE, typeStack.peek());
                } else {
                    throw new ParseException("Invalid expression '" + sourceText + "': Unrecognized function call (" + text + ").", ctx.start.getStartIndex());
                }
                return null;
            } catch (ParseException e) {
                // in getAntlrParseTree. 
                throw new RuntimeException(e);
            }
        }

        @Override
        public Void visitUnary(JavascriptParser.UnaryContext ctx) {
            if (ctx.BOOLNOT() != null) {
                Label labelNotTrue = new Label();
                Label labelNotReturn = new Label();
                typeStack.push(Type.INT_TYPE);
                visit(ctx.expression());
                typeStack.pop();
                gen.visitJumpInsn(Opcodes.IFEQ, labelNotTrue);
                pushBoolean(false);
                gen.goTo(labelNotReturn);
                gen.visitLabel(labelNotTrue);
                pushBoolean(true);
                gen.visitLabel(labelNotReturn);
            } else if (ctx.BWNOT() != null) {
                typeStack.push(Type.LONG_TYPE);
                visit(ctx.expression());
                typeStack.pop();
                gen.push(-1L);
                gen.visitInsn(Opcodes.LXOR);
                gen.cast(Type.LONG_TYPE, typeStack.peek());
            } else if (ctx.ADD() != null) {
                visit(ctx.expression());
            } else if (ctx.SUB() != null) {
                typeStack.push(Type.DOUBLE_TYPE);
                visit(ctx.expression());
                typeStack.pop();
                gen.visitInsn(Opcodes.DNEG);
                gen.cast(Type.DOUBLE_TYPE, typeStack.peek());
            } else {
                throw new IllegalStateException("Unknown operation specified: " + ctx.getText());
            }
            return null;
        }

        @Override
        public Void visitMuldiv(JavascriptParser.MuldivContext ctx) {
            int opcode;
            if (ctx.MUL() != null) {
                opcode = Opcodes.DMUL;
            } else if (ctx.DIV() != null) {
                opcode = Opcodes.DDIV;
            } else if (ctx.REM() != null) {
                opcode = Opcodes.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) {
            int opcode;
            if (ctx.ADD() != null) {
                opcode = Opcodes.DADD;
            } else if (ctx.SUB() != null) {
                opcode = Opcodes.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) {
            int opcode;
            if (ctx.LSH() != null) {
                opcode = Opcodes.LSHL;
            } else if (ctx.RSH() != null) {
                opcode = Opcodes.LSHR;
            } else if (ctx.USH() != null) {
                opcode = Opcodes.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) {
            int opcode;
            if (ctx.LT() != null) {
                opcode = GeneratorAdapter.LT;
            } else if (ctx.LTE() != null) {
                opcode = GeneratorAdapter.LE;
            } else if (ctx.GT() != null) {
                opcode = GeneratorAdapter.GT;
            } else if (ctx.GTE() != null) {
                opcode = GeneratorAdapter.GE;
            } 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) {
            int opcode;
            if (ctx.EQ() != null) {
                opcode = GeneratorAdapter.EQ;
            } else if (ctx.NE() != null) {
                opcode = GeneratorAdapter.NE;
            } else {
                throw new IllegalStateException("Unknown operation specified: " + ctx.getText());
            }
            pushCond(opcode, ctx.expression(0), ctx.expression(1));
            return null;
        }

        @Override
        public Void visitBwand(JavascriptParser.BwandContext ctx) {
            pushBitwise(Opcodes.LAND, ctx.expression(0), ctx.expression(1));
            return null;
        }

        @Override
        public Void visitBwxor(JavascriptParser.BwxorContext ctx) {
            pushBitwise(Opcodes.LXOR, ctx.expression(0), ctx.expression(1));
            return null;
        }

        @Override
        public Void visitBwor(JavascriptParser.BworContext ctx) {
            pushBitwise(Opcodes.LOR, ctx.expression(0), ctx.expression(1));
            return null;
        }

        @Override
        public Void visitBooland(JavascriptParser.BoolandContext ctx) {
            Label andFalse = new Label();
            Label andEnd = new Label();
            typeStack.push(Type.INT_TYPE);
            visit(ctx.expression(0));
            gen.visitJumpInsn(Opcodes.IFEQ, andFalse);
            visit(ctx.expression(1));
            gen.visitJumpInsn(Opcodes.IFEQ, andFalse);
            typeStack.pop();
            pushBoolean(true);
            gen.goTo(andEnd);
            gen.visitLabel(andFalse);
            pushBoolean(false);
            gen.visitLabel(andEnd);
            return null;
        }

        @Override
        public Void visitBoolor(JavascriptParser.BoolorContext ctx) {
            Label orTrue = new Label();
            Label orEnd = new Label();
            typeStack.push(Type.INT_TYPE);
            visit(ctx.expression(0));
            gen.visitJumpInsn(Opcodes.IFNE, orTrue);
            visit(ctx.expression(1));
            gen.visitJumpInsn(Opcodes.IFNE, orTrue);
            typeStack.pop();
            pushBoolean(false);
            gen.goTo(orEnd);
            gen.visitLabel(orTrue);
            pushBoolean(true);
            gen.visitLabel(orEnd);
            return null;
        }

        @Override
        public Void visitConditional(JavascriptParser.ConditionalContext ctx) {
            Label condFalse = new Label();
            Label condEnd = new Label();
            typeStack.push(Type.INT_TYPE);
            visit(ctx.expression(0));
            typeStack.pop();
            gen.visitJumpInsn(Opcodes.IFEQ, condFalse);
            visit(ctx.expression(1));
            gen.goTo(condEnd);
            gen.visitLabel(condFalse);
            visit(ctx.expression(2));
            gen.visitLabel(condEnd);
            return null;
        }

        private void pushArith(int operator, ExpressionContext left, ExpressionContext right) {
            pushBinaryOp(operator, left, right, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE);
        }

        private void pushShift(int operator, ExpressionContext left, ExpressionContext right) {
            pushBinaryOp(operator, left, right, Type.LONG_TYPE, Type.INT_TYPE, Type.LONG_TYPE);
        }

        private void pushBitwise(int operator, ExpressionContext left, ExpressionContext right) {
            pushBinaryOp(operator, left, right, Type.LONG_TYPE, Type.LONG_TYPE, Type.LONG_TYPE);
        }

        private void pushBinaryOp(int operator, ExpressionContext left, ExpressionContext right, Type leftType, Type rightType, Type returnType) {
            typeStack.push(leftType);
            visit(left);
            typeStack.pop();
            typeStack.push(rightType);
            visit(right);
            typeStack.pop();
            gen.visitInsn(operator);
            gen.cast(returnType, typeStack.peek());
        }

        private void pushCond(int operator, ExpressionContext left, ExpressionContext right) {
            Label labelTrue = new Label();
            Label labelReturn = new Label();
            typeStack.push(Type.DOUBLE_TYPE);
            visit(left);
            visit(right);
            typeStack.pop();
            gen.ifCmp(Type.DOUBLE_TYPE, operator, labelTrue);
            pushBoolean(false);
            gen.goTo(labelReturn);
            gen.visitLabel(labelTrue);
            pushBoolean(true);
            gen.visitLabel(labelReturn);
        }

        private void pushBoolean(boolean truth) {
            switch(typeStack.peek().getSort()) {
                case Type.INT:
                    gen.push(truth);
                    break;
                case Type.LONG:
                    gen.push(truth ? 1L : 0L);
                    break;
                case Type.DOUBLE:
                    gen.push(truth ? 1. : 0.);
                    break;
                default:
                    throw new IllegalStateException("Invalid expected type: " + typeStack.peek());
            }
        }

        private void pushLong(long i) {
            switch(typeStack.peek().getSort()) {
                case Type.INT:
                    gen.push((int) i);
                    break;
                case Type.LONG:
                    gen.push(i);
                    break;
                case Type.DOUBLE:
                    gen.push((double) i);
                    break;
                default:
                    throw new IllegalStateException("Invalid expected type: " + typeStack.peek());
            }
        }
    }.visit(parseTree);
    gen.returnValue();
    gen.endMethod();
    classWriter.visitEnd();
}
Also used : Label(org.objectweb.asm.Label) Method(java.lang.reflect.Method) ArrayDeque(java.util.ArrayDeque) Type(org.objectweb.asm.Type) MethodType(java.lang.invoke.MethodType) ExpressionContext(org.apache.lucene.expressions.js.JavascriptParser.ExpressionContext) GeneratorAdapter(org.objectweb.asm.commons.GeneratorAdapter) ParseException(java.text.ParseException)

Aggregations

MethodType (java.lang.invoke.MethodType)1 Method (java.lang.reflect.Method)1 ParseException (java.text.ParseException)1 ArrayDeque (java.util.ArrayDeque)1 ExpressionContext (org.apache.lucene.expressions.js.JavascriptParser.ExpressionContext)1 Label (org.objectweb.asm.Label)1 Type (org.objectweb.asm.Type)1 GeneratorAdapter (org.objectweb.asm.commons.GeneratorAdapter)1