/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.elasticsearch.painless.antlr; import org.antlr.v4.runtime.ANTLRInputStream; import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.DiagnosticErrorListener; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; import org.antlr.v4.runtime.atn.PredictionMode; import org.antlr.v4.runtime.tree.TerminalNode; import org.elasticsearch.painless.CompilerSettings; import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Globals; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Operation; import org.elasticsearch.painless.ScriptInterface; import org.elasticsearch.painless.antlr.PainlessParser.AfterthoughtContext; import org.elasticsearch.painless.antlr.PainlessParser.ArgumentContext; import org.elasticsearch.painless.antlr.PainlessParser.ArgumentsContext; import org.elasticsearch.painless.antlr.PainlessParser.AssignmentContext; import org.elasticsearch.painless.antlr.PainlessParser.BinaryContext; import org.elasticsearch.painless.antlr.PainlessParser.BlockContext; import org.elasticsearch.painless.antlr.PainlessParser.BoolContext; import org.elasticsearch.painless.antlr.PainlessParser.BraceaccessContext; import org.elasticsearch.painless.antlr.PainlessParser.BreakContext; import org.elasticsearch.painless.antlr.PainlessParser.CallinvokeContext; import org.elasticsearch.painless.antlr.PainlessParser.CalllocalContext; import org.elasticsearch.painless.antlr.PainlessParser.CapturingfuncrefContext; import org.elasticsearch.painless.antlr.PainlessParser.CastContext; import org.elasticsearch.painless.antlr.PainlessParser.ClassfuncrefContext; import org.elasticsearch.painless.antlr.PainlessParser.CompContext; import org.elasticsearch.painless.antlr.PainlessParser.ConditionalContext; import org.elasticsearch.painless.antlr.PainlessParser.ConstructorfuncrefContext; import org.elasticsearch.painless.antlr.PainlessParser.ContinueContext; import org.elasticsearch.painless.antlr.PainlessParser.DeclContext; import org.elasticsearch.painless.antlr.PainlessParser.DeclarationContext; import org.elasticsearch.painless.antlr.PainlessParser.DecltypeContext; import org.elasticsearch.painless.antlr.PainlessParser.DeclvarContext; import org.elasticsearch.painless.antlr.PainlessParser.DelimiterContext; import org.elasticsearch.painless.antlr.PainlessParser.DoContext; import org.elasticsearch.painless.antlr.PainlessParser.DynamicContext; import org.elasticsearch.painless.antlr.PainlessParser.EachContext; import org.elasticsearch.painless.antlr.PainlessParser.ElvisContext; import org.elasticsearch.painless.antlr.PainlessParser.EmptyContext; import org.elasticsearch.painless.antlr.PainlessParser.ExprContext; import org.elasticsearch.painless.antlr.PainlessParser.ExpressionContext; import org.elasticsearch.painless.antlr.PainlessParser.FalseContext; import org.elasticsearch.painless.antlr.PainlessParser.FieldaccessContext; import org.elasticsearch.painless.antlr.PainlessParser.ForContext; import org.elasticsearch.painless.antlr.PainlessParser.FunctionContext; import org.elasticsearch.painless.antlr.PainlessParser.IfContext; import org.elasticsearch.painless.antlr.PainlessParser.IneachContext; import org.elasticsearch.painless.antlr.PainlessParser.InitializerContext; import org.elasticsearch.painless.antlr.PainlessParser.InstanceofContext; import org.elasticsearch.painless.antlr.PainlessParser.LambdaContext; import org.elasticsearch.painless.antlr.PainlessParser.LamtypeContext; import org.elasticsearch.painless.antlr.PainlessParser.ListinitContext; import org.elasticsearch.painless.antlr.PainlessParser.ListinitializerContext; import org.elasticsearch.painless.antlr.PainlessParser.LocalfuncrefContext; import org.elasticsearch.painless.antlr.PainlessParser.MapinitContext; import org.elasticsearch.painless.antlr.PainlessParser.MapinitializerContext; import org.elasticsearch.painless.antlr.PainlessParser.MaptokenContext; import org.elasticsearch.painless.antlr.PainlessParser.NewarrayContext; import org.elasticsearch.painless.antlr.PainlessParser.NewinitializedarrayContext; import org.elasticsearch.painless.antlr.PainlessParser.NewobjectContext; import org.elasticsearch.painless.antlr.PainlessParser.NewstandardarrayContext; import org.elasticsearch.painless.antlr.PainlessParser.NullContext; import org.elasticsearch.painless.antlr.PainlessParser.NumericContext; import org.elasticsearch.painless.antlr.PainlessParser.OperatorContext; import org.elasticsearch.painless.antlr.PainlessParser.ParametersContext; import org.elasticsearch.painless.antlr.PainlessParser.PostContext; import org.elasticsearch.painless.antlr.PainlessParser.PostdotContext; import org.elasticsearch.painless.antlr.PainlessParser.PostfixContext; import org.elasticsearch.painless.antlr.PainlessParser.PreContext; import org.elasticsearch.painless.antlr.PainlessParser.PrecedenceContext; import org.elasticsearch.painless.antlr.PainlessParser.ReadContext; import org.elasticsearch.painless.antlr.PainlessParser.RegexContext; import org.elasticsearch.painless.antlr.PainlessParser.ReturnContext; import org.elasticsearch.painless.antlr.PainlessParser.SingleContext; import org.elasticsearch.painless.antlr.PainlessParser.SourceContext; import org.elasticsearch.painless.antlr.PainlessParser.StatementContext; import org.elasticsearch.painless.antlr.PainlessParser.StaticContext; import org.elasticsearch.painless.antlr.PainlessParser.StringContext; import org.elasticsearch.painless.antlr.PainlessParser.ThrowContext; import org.elasticsearch.painless.antlr.PainlessParser.TrailerContext; import org.elasticsearch.painless.antlr.PainlessParser.TrapContext; import org.elasticsearch.painless.antlr.PainlessParser.TrueContext; import org.elasticsearch.painless.antlr.PainlessParser.TryContext; import org.elasticsearch.painless.antlr.PainlessParser.VariableContext; import org.elasticsearch.painless.antlr.PainlessParser.WhileContext; import org.elasticsearch.painless.node.AExpression; import org.elasticsearch.painless.node.ANode; import org.elasticsearch.painless.node.AStatement; import org.elasticsearch.painless.node.EAssignment; import org.elasticsearch.painless.node.EBinary; import org.elasticsearch.painless.node.EBool; import org.elasticsearch.painless.node.EBoolean; import org.elasticsearch.painless.node.ECallLocal; import org.elasticsearch.painless.node.ECapturingFunctionRef; import org.elasticsearch.painless.node.EComp; import org.elasticsearch.painless.node.EConditional; import org.elasticsearch.painless.node.EDecimal; import org.elasticsearch.painless.node.EElvis; import org.elasticsearch.painless.node.EExplicit; import org.elasticsearch.painless.node.EFunctionRef; import org.elasticsearch.painless.node.EInstanceof; import org.elasticsearch.painless.node.ELambda; import org.elasticsearch.painless.node.EListInit; import org.elasticsearch.painless.node.EMapInit; import org.elasticsearch.painless.node.ENewArray; import org.elasticsearch.painless.node.ENewObj; import org.elasticsearch.painless.node.ENull; import org.elasticsearch.painless.node.ENumeric; import org.elasticsearch.painless.node.ERegex; import org.elasticsearch.painless.node.EStatic; import org.elasticsearch.painless.node.EString; import org.elasticsearch.painless.node.EUnary; import org.elasticsearch.painless.node.EVariable; import org.elasticsearch.painless.node.PBrace; import org.elasticsearch.painless.node.PCallInvoke; import org.elasticsearch.painless.node.PField; import org.elasticsearch.painless.node.SBlock; import org.elasticsearch.painless.node.SBreak; import org.elasticsearch.painless.node.SCatch; import org.elasticsearch.painless.node.SContinue; import org.elasticsearch.painless.node.SDeclBlock; import org.elasticsearch.painless.node.SDeclaration; import org.elasticsearch.painless.node.SDo; import org.elasticsearch.painless.node.SEach; import org.elasticsearch.painless.node.SExpression; import org.elasticsearch.painless.node.SFor; import org.elasticsearch.painless.node.SFunction; import org.elasticsearch.painless.node.SFunction.FunctionReserved; import org.elasticsearch.painless.node.SIf; import org.elasticsearch.painless.node.SIfElse; import org.elasticsearch.painless.node.SReturn; import org.elasticsearch.painless.node.SSource; import org.elasticsearch.painless.node.SSource.MainMethodReserved; import org.elasticsearch.painless.node.SSource.Reserved; import org.elasticsearch.painless.node.SThrow; import org.elasticsearch.painless.node.STry; import org.elasticsearch.painless.node.SWhile; import org.objectweb.asm.util.Printer; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Deque; import java.util.List; /** * Converts the ANTLR tree to a Painless tree. */ public final class Walker extends PainlessParserBaseVisitor<ANode> { public static SSource buildPainlessTree(ScriptInterface mainMethod, String sourceName, String sourceText, CompilerSettings settings, Definition definition, Printer debugStream) { return new Walker(mainMethod, sourceName, sourceText, settings, definition, debugStream).source; } private final ScriptInterface scriptInterface; private final SSource source; private final CompilerSettings settings; private final Printer debugStream; private final String sourceName; private final String sourceText; private final Definition definition; private final Deque<Reserved> reserved = new ArrayDeque<>(); private final Globals globals; private int syntheticCounter = 0; private Walker(ScriptInterface scriptInterface, String sourceName, String sourceText, CompilerSettings settings, Definition definition, Printer debugStream) { this.scriptInterface = scriptInterface; this.debugStream = debugStream; this.settings = settings; this.sourceName = Location.computeSourceName(sourceName, sourceText); this.sourceText = sourceText; this.globals = new Globals(new BitSet(sourceText.length())); this.definition = definition; this.source = (SSource)visit(buildAntlrTree(sourceText)); } private SourceContext buildAntlrTree(String source) { ANTLRInputStream stream = new ANTLRInputStream(source); PainlessLexer lexer = new EnhancedPainlessLexer(stream, sourceName, definition); PainlessParser parser = new PainlessParser(new CommonTokenStream(lexer)); ParserErrorStrategy strategy = new ParserErrorStrategy(sourceName); lexer.removeErrorListeners(); parser.removeErrorListeners(); if (settings.isPicky()) { setupPicky(parser); } parser.setErrorHandler(strategy); return parser.source(); } private void setupPicky(PainlessParser parser) { // Diagnostic listener invokes syntaxError on other listeners for ambiguity issues, parser.addErrorListener(new DiagnosticErrorListener(true)); // a second listener to fail the test when the above happens. parser.addErrorListener(new BaseErrorListener() { @Override public void syntaxError(final Recognizer<?,?> recognizer, final Object offendingSymbol, final int line, final int charPositionInLine, final String msg, final RecognitionException e) { throw new AssertionError("line: " + line + ", offset: " + charPositionInLine + ", symbol:" + offendingSymbol + " " + msg); } }); // Enable exact ambiguity detection (costly). we enable exact since its the default for // DiagnosticErrorListener, life is too short to think about what 'inexact ambiguity' might mean. parser.getInterpreter().setPredictionMode(PredictionMode.LL_EXACT_AMBIG_DETECTION); } private Location location(ParserRuleContext ctx) { return new Location(sourceName, ctx.getStart().getStartIndex()); } /** Returns name of next lambda */ private String nextLambda() { return "lambda$" + syntheticCounter++; } @Override public ANode visitSource(SourceContext ctx) { reserved.push(new MainMethodReserved()); List<SFunction> functions = new ArrayList<>(); for (FunctionContext function : ctx.function()) { functions.add((SFunction)visit(function)); } List<AStatement> statements = new ArrayList<>(); for (StatementContext statement : ctx.statement()) { statements.add((AStatement)visit(statement)); } return new SSource(scriptInterface, settings, sourceName, sourceText, debugStream, (MainMethodReserved)reserved.pop(), location(ctx), functions, globals, statements); } @Override public ANode visitFunction(FunctionContext ctx) { reserved.push(new FunctionReserved()); String rtnType = ctx.decltype().getText(); String name = ctx.ID().getText(); List<String> paramTypes = new ArrayList<>(); List<String> paramNames = new ArrayList<>(); List<AStatement> statements = new ArrayList<>(); for (DecltypeContext decltype : ctx.parameters().decltype()) { paramTypes.add(decltype.getText()); } for (TerminalNode id : ctx.parameters().ID()) { paramNames.add(id.getText()); } for (StatementContext statement : ctx.block().statement()) { statements.add((AStatement)visit(statement)); } return new SFunction((FunctionReserved)reserved.pop(), location(ctx), rtnType, name, paramTypes, paramNames, statements, false); } @Override public ANode visitParameters(ParametersContext ctx) { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } @Override public ANode visitIf(IfContext ctx) { AExpression expression = (AExpression)visit(ctx.expression()); SBlock ifblock = (SBlock)visit(ctx.trailer(0)); if (ctx.trailer().size() > 1) { SBlock elseblock = (SBlock)visit(ctx.trailer(1)); return new SIfElse(location(ctx), expression, ifblock, elseblock); } else { return new SIf(location(ctx), expression, ifblock); } } @Override public ANode visitWhile(WhileContext ctx) { reserved.peek().setMaxLoopCounter(settings.getMaxLoopCounter()); AExpression expression = (AExpression)visit(ctx.expression()); if (ctx.trailer() != null) { SBlock block = (SBlock)visit(ctx.trailer()); return new SWhile(location(ctx), expression, block); } else if (ctx.empty() != null) { return new SWhile(location(ctx), expression, null); } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } } @Override public ANode visitDo(DoContext ctx) { reserved.peek().setMaxLoopCounter(settings.getMaxLoopCounter()); AExpression expression = (AExpression)visit(ctx.expression()); SBlock block = (SBlock)visit(ctx.block()); return new SDo(location(ctx), block, expression); } @Override public ANode visitFor(ForContext ctx) { reserved.peek().setMaxLoopCounter(settings.getMaxLoopCounter()); ANode initializer = ctx.initializer() == null ? null : visit(ctx.initializer()); AExpression expression = ctx.expression() == null ? null : (AExpression)visit(ctx.expression()); AExpression afterthought = ctx.afterthought() == null ? null : (AExpression)visit(ctx.afterthought()); if (ctx.trailer() != null) { SBlock block = (SBlock)visit(ctx.trailer()); return new SFor(location(ctx), initializer, expression, afterthought, block); } else if (ctx.empty() != null) { return new SFor(location(ctx), initializer, expression, afterthought, null); } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } } @Override public ANode visitEach(EachContext ctx) { reserved.peek().setMaxLoopCounter(settings.getMaxLoopCounter()); String type = ctx.decltype().getText(); String name = ctx.ID().getText(); AExpression expression = (AExpression)visit(ctx.expression()); SBlock block = (SBlock)visit(ctx.trailer()); return new SEach(location(ctx), type, name, expression, block); } @Override public ANode visitIneach(IneachContext ctx) { reserved.peek().setMaxLoopCounter(settings.getMaxLoopCounter()); String name = ctx.ID().getText(); AExpression expression = (AExpression)visit(ctx.expression()); SBlock block = (SBlock)visit(ctx.trailer()); return new SEach(location(ctx), "def", name, expression, block); } @Override public ANode visitDecl(DeclContext ctx) { return visit(ctx.declaration()); } @Override public ANode visitContinue(ContinueContext ctx) { return new SContinue(location(ctx)); } @Override public ANode visitBreak(BreakContext ctx) { return new SBreak(location(ctx)); } @Override public ANode visitReturn(ReturnContext ctx) { AExpression expression = (AExpression)visit(ctx.expression()); return new SReturn(location(ctx), expression); } @Override public ANode visitTry(TryContext ctx) { SBlock block = (SBlock)visit(ctx.block()); List<SCatch> catches = new ArrayList<>(); for (TrapContext trap : ctx.trap()) { catches.add((SCatch)visit(trap)); } return new STry(location(ctx), block, catches); } @Override public ANode visitThrow(ThrowContext ctx) { AExpression expression = (AExpression)visit(ctx.expression()); return new SThrow(location(ctx), expression); } @Override public ANode visitExpr(ExprContext ctx) { AExpression expression = (AExpression)visit(ctx.expression()); return new SExpression(location(ctx), expression); } @Override public ANode visitTrailer(TrailerContext ctx) { if (ctx.block() != null) { return visit(ctx.block()); } else if (ctx.statement() != null) { List<AStatement> statements = new ArrayList<>(); statements.add((AStatement)visit(ctx.statement())); return new SBlock(location(ctx), statements); } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } } @Override public ANode visitBlock(BlockContext ctx) { if (ctx.statement().isEmpty()) { return null; } else { List<AStatement> statements = new ArrayList<>(); for (StatementContext statement : ctx.statement()) { statements.add((AStatement)visit(statement)); } return new SBlock(location(ctx), statements); } } @Override public ANode visitEmpty(EmptyContext ctx) { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } @Override public ANode visitInitializer(InitializerContext ctx) { if (ctx.declaration() != null) { return visit(ctx.declaration()); } else if (ctx.expression() != null) { return visit(ctx.expression()); } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } } @Override public ANode visitAfterthought(AfterthoughtContext ctx) { return visit(ctx.expression()); } @Override public ANode visitDeclaration(DeclarationContext ctx) { String type = ctx.decltype().getText(); List<SDeclaration> declarations = new ArrayList<>(); for (DeclvarContext declvar : ctx.declvar()) { String name = declvar.ID().getText(); AExpression expression = declvar.expression() == null ? null : (AExpression)visit(declvar.expression()); declarations.add(new SDeclaration(location(declvar), type, name, expression)); } return new SDeclBlock(location(ctx), declarations); } @Override public ANode visitDecltype(DecltypeContext ctx) { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } @Override public ANode visitDeclvar(DeclvarContext ctx) { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } @Override public ANode visitTrap(TrapContext ctx) { String type = ctx.TYPE().getText(); String name = ctx.ID().getText(); SBlock block = (SBlock)visit(ctx.block()); return new SCatch(location(ctx), type, name, block); } @Override public ANode visitDelimiter(DelimiterContext ctx) { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } @Override public ANode visitSingle(SingleContext ctx) { return visit(ctx.unary()); } @Override public ANode visitBinary(BinaryContext ctx) { AExpression left = (AExpression)visit(ctx.expression(0)); AExpression right = (AExpression)visit(ctx.expression(1)); final Operation operation; if (ctx.MUL() != null) { operation = Operation.MUL; } else if (ctx.DIV() != null) { operation = Operation.DIV; } else if (ctx.REM() != null) { operation = Operation.REM; } else if (ctx.ADD() != null) { operation = Operation.ADD; } else if (ctx.SUB() != null) { operation = Operation.SUB; } else if (ctx.FIND() != null) { operation = Operation.FIND; } else if (ctx.MATCH() != null) { operation = Operation.MATCH; } else if (ctx.LSH() != null) { operation = Operation.LSH; } else if (ctx.RSH() != null) { operation = Operation.RSH; } else if (ctx.USH() != null) { operation = Operation.USH; } else if (ctx.BWAND() != null) { operation = Operation.BWAND; } else if (ctx.XOR() != null) { operation = Operation.XOR; } else if (ctx.BWOR() != null) { operation = Operation.BWOR; } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } return new EBinary(location(ctx), operation, left, right); } @Override public ANode visitComp(CompContext ctx) { AExpression left = (AExpression)visit(ctx.expression(0)); AExpression right = (AExpression)visit(ctx.expression(1)); final Operation operation; if (ctx.LT() != null) { operation = Operation.LT; } else if (ctx.LTE() != null) { operation = Operation.LTE; } else if (ctx.GT() != null) { operation = Operation.GT; } else if (ctx.GTE() != null) { operation = Operation.GTE; } else if (ctx.EQ() != null) { operation = Operation.EQ; } else if (ctx.EQR() != null) { operation = Operation.EQR; } else if (ctx.NE() != null) { operation = Operation.NE; } else if (ctx.NER() != null) { operation = Operation.NER; } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } return new EComp(location(ctx), operation, left, right); } @Override public ANode visitInstanceof(InstanceofContext ctx) { AExpression expr = (AExpression)visit(ctx.expression()); String type = ctx.decltype().getText(); return new EInstanceof(location(ctx), expr, type); } @Override public ANode visitBool(BoolContext ctx) { AExpression left = (AExpression)visit(ctx.expression(0)); AExpression right = (AExpression)visit(ctx.expression(1)); final Operation operation; if (ctx.BOOLAND() != null) { operation = Operation.AND; } else if (ctx.BOOLOR() != null) { operation = Operation.OR; } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } return new EBool(location(ctx), operation, left, right); } @Override public ANode visitConditional(ConditionalContext ctx) { AExpression condition = (AExpression)visit(ctx.expression(0)); AExpression left = (AExpression)visit(ctx.expression(1)); AExpression right = (AExpression)visit(ctx.expression(2)); return new EConditional(location(ctx), condition, left, right); } @Override public ANode visitElvis(ElvisContext ctx) { AExpression left = (AExpression)visit(ctx.expression(0)); AExpression right = (AExpression)visit(ctx.expression(1)); return new EElvis(location(ctx), left, right); } @Override public ANode visitAssignment(AssignmentContext ctx) { AExpression lhs = (AExpression)visit(ctx.expression(0)); AExpression rhs = (AExpression)visit(ctx.expression(1)); final Operation operation; if (ctx.ASSIGN() != null) { operation = null; } else if (ctx.AMUL() != null) { operation = Operation.MUL; } else if (ctx.ADIV() != null) { operation = Operation.DIV; } else if (ctx.AREM() != null) { operation = Operation.REM; } else if (ctx.AADD() != null) { operation = Operation.ADD; } else if (ctx.ASUB() != null) { operation = Operation.SUB; } else if (ctx.ALSH() != null) { operation = Operation.LSH; } else if (ctx.ARSH() != null) { operation = Operation.RSH; } else if (ctx.AUSH() != null) { operation = Operation.USH; } else if (ctx.AAND() != null) { operation = Operation.BWAND; } else if (ctx.AXOR() != null) { operation = Operation.XOR; } else if (ctx.AOR() != null) { operation = Operation.BWOR; } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } return new EAssignment(location(ctx), lhs, rhs, false, false, operation); } @Override public ANode visitPre(PreContext ctx) { AExpression expression = (AExpression)visit(ctx.chain()); final Operation operation; if (ctx.INCR() != null) { operation = Operation.INCR; } else if (ctx.DECR() != null) { operation = Operation.DECR; } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } return new EAssignment(location(ctx), expression, null, true, false, operation); } @Override public ANode visitPost(PostContext ctx) { AExpression expression = (AExpression)visit(ctx.chain()); final Operation operation; if (ctx.INCR() != null) { operation = Operation.INCR; } else if (ctx.DECR() != null) { operation = Operation.DECR; } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } return new EAssignment(location(ctx), expression, null, false, true, operation); } @Override public ANode visitRead(ReadContext ctx) { return visit(ctx.chain()); } @Override public ANode visitOperator(OperatorContext ctx) { AExpression expression = (AExpression)visit(ctx.unary()); final Operation operation; if (ctx.BOOLNOT() != null) { operation = Operation.NOT; } else if (ctx.BWNOT() != null) { operation = Operation.BWNOT; } else if (ctx.ADD() != null) { operation = Operation.ADD; } else if (ctx.SUB() != null) { if (ctx.unary() instanceof ReadContext && ((ReadContext)ctx.unary()).chain() instanceof DynamicContext && ((DynamicContext)((ReadContext)ctx.unary()).chain()).primary() instanceof NumericContext && ((DynamicContext)((ReadContext)ctx.unary()).chain()).postfix().isEmpty()) { return expression; } operation = Operation.SUB; } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } return new EUnary(location(ctx), operation, expression); } @Override public ANode visitCast(CastContext ctx) { String type = ctx.decltype().getText(); AExpression child = (AExpression)visit(ctx.unary()); return new EExplicit(location(ctx), type, child); } @Override public ANode visitDynamic(DynamicContext ctx) { AExpression primary = (AExpression)visit(ctx.primary()); return buildPostfixChain(primary, null, ctx.postfix()); } @Override public ANode visitStatic(StaticContext ctx) { String type = ctx.decltype().getText(); return buildPostfixChain(new EStatic(location(ctx), type), ctx.postdot(), ctx.postfix()); } @Override public ANode visitNewarray(NewarrayContext ctx) { return visit(ctx.arrayinitializer()); } @Override public ANode visitPrecedence(PrecedenceContext ctx) { return visit(ctx.expression()); } @Override public ANode visitNumeric(NumericContext ctx) { final boolean negate = ((DynamicContext)ctx.parent).postfix().isEmpty() && ctx.parent.parent.parent instanceof OperatorContext && ((OperatorContext)ctx.parent.parent.parent).SUB() != null; if (ctx.DECIMAL() != null) { return new EDecimal(location(ctx), (negate ? "-" : "") + ctx.DECIMAL().getText()); } else if (ctx.HEX() != null) { return new ENumeric(location(ctx), (negate ? "-" : "") + ctx.HEX().getText().substring(2), 16); } else if (ctx.INTEGER() != null) { return new ENumeric(location(ctx), (negate ? "-" : "") + ctx.INTEGER().getText(), 10); } else if (ctx.OCTAL() != null) { return new ENumeric(location(ctx), (negate ? "-" : "") + ctx.OCTAL().getText().substring(1), 8); } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } } @Override public ANode visitTrue(TrueContext ctx) { return new EBoolean(location(ctx), true); } @Override public ANode visitFalse(FalseContext ctx) { return new EBoolean(location(ctx), false); } @Override public ANode visitNull(NullContext ctx) { return new ENull(location(ctx)); } @Override public ANode visitString(StringContext ctx) { StringBuilder string = new StringBuilder(ctx.STRING().getText()); // Strip the leading and trailing quotes and replace the escape sequences with their literal equivalents int src = 1; int dest = 0; int end = string.length() - 1; assert string.charAt(0) == '"' || string.charAt(0) == '\'' : "expected string to start with a quote but was [" + string + "]"; assert string.charAt(end) == '"' || string.charAt(end) == '\'' : "expected string to end with a quote was [" + string + "]"; while (src < end) { char current = string.charAt(src); if (current == '\\') { src++; current = string.charAt(src); } string.setCharAt(dest, current); src++; dest++; } string.setLength(dest); return new EString(location(ctx), string.toString()); } @Override public ANode visitRegex(RegexContext ctx) { if (false == settings.areRegexesEnabled()) { throw location(ctx).createError(new IllegalStateException("Regexes are disabled. Set [script.painless.regex.enabled] to [true] " + "in elasticsearch.yaml to allow them. Be careful though, regexes break out of Painless's protection against deep " + "recursion and long loops.")); } String text = ctx.REGEX().getText(); int lastSlash = text.lastIndexOf('/'); String pattern = text.substring(1, lastSlash); String flags = text.substring(lastSlash + 1); return new ERegex(location(ctx), pattern, flags); } @Override public ANode visitListinit(ListinitContext ctx) { return visit(ctx.listinitializer()); } @Override public ANode visitMapinit(MapinitContext ctx) { return visit(ctx.mapinitializer()); } @Override public ANode visitVariable(VariableContext ctx) { String name = ctx.ID().getText(); reserved.peek().markUsedVariable(name); return new EVariable(location(ctx), name); } @Override public ANode visitCalllocal(CalllocalContext ctx) { String name = ctx.ID().getText(); List<AExpression> arguments = collectArguments(ctx.arguments()); return new ECallLocal(location(ctx), name, arguments); } @Override public ANode visitNewobject(NewobjectContext ctx) { String type = ctx.TYPE().getText(); List<AExpression> arguments = collectArguments(ctx.arguments()); return new ENewObj(location(ctx), type, arguments); } private AExpression buildPostfixChain(AExpression primary, PostdotContext postdot, List<PostfixContext> postfixes) { AExpression prefix = primary; if (postdot != null) { prefix = visitPostdot(postdot, prefix); } for (PostfixContext postfix : postfixes) { prefix = visitPostfix(postfix, prefix); } return prefix; } @Override public ANode visitPostfix(PostfixContext ctx) { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } public AExpression visitPostfix(PostfixContext ctx, AExpression prefix) { if (ctx.callinvoke() != null) { return visitCallinvoke(ctx.callinvoke(), prefix); } else if (ctx.fieldaccess() != null) { return visitFieldaccess(ctx.fieldaccess(), prefix); } else if (ctx.braceaccess() != null) { return visitBraceaccess(ctx.braceaccess(), prefix); } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } } @Override public ANode visitPostdot(PostdotContext ctx) { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } public AExpression visitPostdot(PostdotContext ctx, AExpression prefix) { if (ctx.callinvoke() != null) { return visitCallinvoke(ctx.callinvoke(), prefix); } else if (ctx.fieldaccess() != null) { return visitFieldaccess(ctx.fieldaccess(), prefix); } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } } @Override public ANode visitCallinvoke(CallinvokeContext ctx) { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } public AExpression visitCallinvoke(CallinvokeContext ctx, AExpression prefix) { String name = ctx.DOTID().getText(); List<AExpression> arguments = collectArguments(ctx.arguments()); return new PCallInvoke(location(ctx), prefix, name, ctx.NSDOT() != null, arguments); } @Override public ANode visitFieldaccess(FieldaccessContext ctx) { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } public AExpression visitFieldaccess(FieldaccessContext ctx, AExpression prefix) { final String value; if (ctx.DOTID() != null) { value = ctx.DOTID().getText(); } else if (ctx.DOTINTEGER() != null) { value = ctx.DOTINTEGER().getText(); } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } return new PField(location(ctx), prefix, ctx.NSDOT() != null, value); } @Override public ANode visitBraceaccess(BraceaccessContext ctx) { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } public AExpression visitBraceaccess(BraceaccessContext ctx, AExpression prefix) { AExpression expression = (AExpression)visit(ctx.expression()); return new PBrace(location(ctx), prefix, expression); } @Override public ANode visitNewstandardarray(NewstandardarrayContext ctx) { String type = ctx.TYPE().getText(); List<AExpression> expressions = new ArrayList<>(); for (ExpressionContext expression : ctx.expression()) { expressions.add((AExpression)visit(expression)); } return buildPostfixChain(new ENewArray(location(ctx), type, expressions, false), ctx.postdot(), ctx.postfix()); } @Override public ANode visitNewinitializedarray(NewinitializedarrayContext ctx) { String type = ctx.TYPE().getText(); List<AExpression> expressions = new ArrayList<>(); for (ExpressionContext expression : ctx.expression()) { expressions.add((AExpression)visit(expression)); } return buildPostfixChain(new ENewArray(location(ctx), type, expressions, true), null, ctx.postfix()); } @Override public ANode visitListinitializer(ListinitializerContext ctx) { List<AExpression> values = new ArrayList<>(); for (ExpressionContext expression : ctx.expression()) { values.add((AExpression)visit(expression)); } return new EListInit(location(ctx), values); } @Override public ANode visitMapinitializer(MapinitializerContext ctx) { List<AExpression> keys = new ArrayList<>(); List<AExpression> values = new ArrayList<>(); for (MaptokenContext maptoken : ctx.maptoken()) { keys.add((AExpression)visit(maptoken.expression(0))); values.add((AExpression)visit(maptoken.expression(1))); } return new EMapInit(location(ctx), keys, values); } @Override public ANode visitMaptoken(MaptokenContext ctx) { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } @Override public ANode visitArguments(ArgumentsContext ctx) { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } private List<AExpression> collectArguments(ArgumentsContext ctx) { List<AExpression> arguments = new ArrayList<>(); for (ArgumentContext argument : ctx.argument()) { arguments.add((AExpression)visit(argument)); } return arguments; } @Override public ANode visitArgument(ArgumentContext ctx) { if (ctx.expression() != null) { return visit(ctx.expression()); } else if (ctx.lambda() != null) { return visit(ctx.lambda()); } else if (ctx.funcref() != null) { return visit(ctx.funcref()); } else { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } } @Override public ANode visitLambda(LambdaContext ctx) { reserved.push(new FunctionReserved()); List<String> paramTypes = new ArrayList<>(); List<String> paramNames = new ArrayList<>(); List<AStatement> statements = new ArrayList<>(); for (LamtypeContext lamtype : ctx.lamtype()) { if (lamtype.decltype() == null) { paramTypes.add(null); } else { paramTypes.add(lamtype.decltype().getText()); } paramNames.add(lamtype.ID().getText()); } if (ctx.expression() != null) { // single expression AExpression expression = (AExpression)visit(ctx.expression()); statements.add(new SReturn(location(ctx), expression)); } else { for (StatementContext statement : ctx.block().statement()) { statements.add((AStatement)visit(statement)); } } String name = nextLambda(); return new ELambda(name, (FunctionReserved)reserved.pop(), location(ctx), paramTypes, paramNames, statements); } @Override public ANode visitLamtype(LamtypeContext ctx) { throw location(ctx).createError(new IllegalStateException("Illegal tree structure.")); } @Override public ANode visitClassfuncref(ClassfuncrefContext ctx) { return new EFunctionRef(location(ctx), ctx.TYPE().getText(), ctx.ID().getText()); } @Override public ANode visitConstructorfuncref(ConstructorfuncrefContext ctx) { if (!ctx.decltype().LBRACE().isEmpty()) { // array constructors are special: we need to make a synthetic method // taking integer as argument and returning a new instance, and return a ref to that. Location location = location(ctx); String arrayType = ctx.decltype().getText(); SReturn code = new SReturn(location, new ENewArray(location, arrayType, Arrays.asList( new EVariable(location, "size")), false)); String name = nextLambda(); globals.addSyntheticMethod(new SFunction(new FunctionReserved(), location, arrayType, name, Arrays.asList("int"), Arrays.asList("size"), Arrays.asList(code), true)); return new EFunctionRef(location(ctx), "this", name); } return new EFunctionRef(location(ctx), ctx.decltype().getText(), ctx.NEW().getText()); } @Override public ANode visitCapturingfuncref(CapturingfuncrefContext ctx) { return new ECapturingFunctionRef(location(ctx), ctx.ID(0).getText(), ctx.ID(1).getText()); } @Override public ANode visitLocalfuncref(LocalfuncrefContext ctx) { return new EFunctionRef(location(ctx), ctx.THIS().getText(), ctx.ID().getText()); } }