Skip to content

Commit 211a355

Browse files
authored
Decouple Painless AST Lambda generation from the grammar (#45111)
This is the first step in decoupling the Painless AST from the grammar. The Painless AST should be able to generate classes independently of how the AST is generated from a grammar. (If I were to build a Painless AST by hand in code this should be all that's necessary.) This change removes Lambda name generation from the ANTLR grammar tree walker. It also removes unnecessary node generation of new array function references from the tree walker as well.
1 parent 5fee557 commit 211a355

File tree

7 files changed

+129
-43
lines changed

7 files changed

+129
-43
lines changed

modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@
3838
* Tracks user defined methods and variables across compilation phases.
3939
*/
4040
public final class Locals {
41+
private int syntheticCounter = 0;
42+
43+
/**
44+
* Returns a unique identifier for generating the name of a synthetic method.
45+
*/
46+
public String getNextSyntheticName() {
47+
Locals locals = this;
48+
while (locals.getParent() != null) {
49+
locals = locals.getParent();
50+
}
51+
52+
return "lambda$" + locals.syntheticCounter++;
53+
}
4154

4255
/**
4356
* Constructs a local method key used to lookup local methods from a painless class.

modules/lang-painless/src/main/java/org/elasticsearch/painless/action/PainlessContextAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919

2020
package org.elasticsearch.painless.action;
2121

22-
import org.elasticsearch.action.ActionType;
2322
import org.elasticsearch.action.ActionListener;
2423
import org.elasticsearch.action.ActionRequest;
2524
import org.elasticsearch.action.ActionRequestValidationException;
2625
import org.elasticsearch.action.ActionResponse;
26+
import org.elasticsearch.action.ActionType;
2727
import org.elasticsearch.action.support.ActionFilters;
2828
import org.elasticsearch.action.support.HandledTransportAction;
2929
import org.elasticsearch.client.node.NodeClient;

modules/lang-painless/src/main/java/org/elasticsearch/painless/action/PainlessExecuteAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
import org.apache.lucene.search.Scorer;
3131
import org.apache.lucene.search.Weight;
3232
import org.apache.lucene.store.RAMDirectory;
33-
import org.elasticsearch.action.ActionType;
3433
import org.elasticsearch.action.ActionRequestValidationException;
3534
import org.elasticsearch.action.ActionResponse;
35+
import org.elasticsearch.action.ActionType;
3636
import org.elasticsearch.action.support.ActionFilters;
3737
import org.elasticsearch.action.support.IndicesOptions;
3838
import org.elasticsearch.action.support.single.shard.SingleShardRequest;

modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import org.antlr.v4.runtime.atn.PredictionMode;
3030
import org.antlr.v4.runtime.tree.TerminalNode;
3131
import org.elasticsearch.painless.CompilerSettings;
32-
import org.elasticsearch.painless.Globals;
3332
import org.elasticsearch.painless.Location;
3433
import org.elasticsearch.painless.Operation;
3534
import org.elasticsearch.painless.ScriptClassInfo;
@@ -127,6 +126,7 @@
127126
import org.elasticsearch.painless.node.EListInit;
128127
import org.elasticsearch.painless.node.EMapInit;
129128
import org.elasticsearch.painless.node.ENewArray;
129+
import org.elasticsearch.painless.node.ENewArrayFunctionRef;
130130
import org.elasticsearch.painless.node.ENewObj;
131131
import org.elasticsearch.painless.node.ENull;
132132
import org.elasticsearch.painless.node.ENumeric;
@@ -163,8 +163,6 @@
163163

164164
import java.util.ArrayDeque;
165165
import java.util.ArrayList;
166-
import java.util.Arrays;
167-
import java.util.BitSet;
168166
import java.util.Deque;
169167
import java.util.List;
170168

@@ -184,11 +182,10 @@ public static SSource buildPainlessTree(ScriptClassInfo mainMethod, MainMethodRe
184182
private final CompilerSettings settings;
185183
private final Printer debugStream;
186184
private final String sourceName;
185+
private final String sourceText;
187186
private final PainlessLookup painlessLookup;
188187

189188
private final Deque<Reserved> reserved = new ArrayDeque<>();
190-
private final Globals globals;
191-
private int syntheticCounter = 0;
192189

193190
private Walker(ScriptClassInfo scriptClassInfo, MainMethodReserved reserved, String sourceName, String sourceText,
194191
CompilerSettings settings, PainlessLookup painlessLookup, Printer debugStream) {
@@ -197,7 +194,7 @@ private Walker(ScriptClassInfo scriptClassInfo, MainMethodReserved reserved, Str
197194
this.debugStream = debugStream;
198195
this.settings = settings;
199196
this.sourceName = Location.computeSourceName(sourceName);
200-
this.globals = new Globals(new BitSet(sourceText.length()));
197+
this.sourceText = sourceText;
201198
this.painlessLookup = painlessLookup;
202199
this.source = (SSource)visit(buildAntlrTree(sourceText));
203200
}
@@ -242,11 +239,6 @@ private Location location(ParserRuleContext ctx) {
242239
return new Location(sourceName, ctx.getStart().getStartIndex());
243240
}
244241

245-
/** Returns name of next lambda */
246-
private String nextLambda() {
247-
return "lambda$" + syntheticCounter++;
248-
}
249-
250242
@Override
251243
public ANode visitSource(SourceContext ctx) {
252244
List<SFunction> functions = new ArrayList<>();
@@ -261,8 +253,8 @@ public ANode visitSource(SourceContext ctx) {
261253
statements.add((AStatement)visit(statement));
262254
}
263255

264-
return new SSource(scriptClassInfo, settings, sourceName, debugStream, (MainMethodReserved)reserved.pop(),
265-
location(ctx), functions, globals, statements);
256+
return new SSource(scriptClassInfo, settings, sourceName, sourceText, debugStream,
257+
(MainMethodReserved)reserved.pop(), location(ctx), functions, statements);
266258
}
267259

268260
@Override
@@ -1099,8 +1091,7 @@ public ANode visitLambda(LambdaContext ctx) {
10991091
FunctionReserved lambdaReserved = (FunctionReserved)reserved.pop();
11001092
reserved.peek().addUsedVariables(lambdaReserved);
11011093

1102-
String name = nextLambda();
1103-
return new ELambda(name, lambdaReserved, location(ctx), paramTypes, paramNames, statements);
1094+
return new ELambda(lambdaReserved, location(ctx), paramTypes, paramNames, statements);
11041095
}
11051096

11061097
@Override
@@ -1115,22 +1106,9 @@ public ANode visitClassfuncref(ClassfuncrefContext ctx) {
11151106

11161107
@Override
11171108
public ANode visitConstructorfuncref(ConstructorfuncrefContext ctx) {
1118-
if (!ctx.decltype().LBRACE().isEmpty()) {
1119-
// array constructors are special: we need to make a synthetic method
1120-
// taking integer as argument and returning a new instance, and return a ref to that.
1121-
Location location = location(ctx);
1122-
String arrayType = ctx.decltype().getText();
1123-
SReturn code = new SReturn(location,
1124-
new ENewArray(location, arrayType, Arrays.asList(
1125-
new EVariable(location, "size")), false));
1126-
String name = nextLambda();
1127-
globals.addSyntheticMethod(new SFunction(new FunctionReserved(), location, arrayType, name,
1128-
Arrays.asList("int"), Arrays.asList("size"), Arrays.asList(code), true));
1129-
1130-
return new EFunctionRef(location(ctx), "this", name);
1131-
}
1132-
1133-
return new EFunctionRef(location(ctx), ctx.decltype().getText(), ctx.NEW().getText());
1109+
return ctx.decltype().LBRACE().isEmpty() ?
1110+
new EFunctionRef(location(ctx), ctx.decltype().getText(), ctx.NEW().getText()) :
1111+
new ENewArrayFunctionRef(location(ctx), ctx.decltype().getText());
11341112
}
11351113

11361114
@Override

modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
*/
6464
public final class ELambda extends AExpression implements ILambda {
6565

66-
private final String name;
6766
private final FunctionReserved reserved;
6867
private final List<String> paramTypeStrs;
6968
private final List<String> paramNameStrs;
@@ -78,11 +77,10 @@ public final class ELambda extends AExpression implements ILambda {
7877
// dynamic parent, deferred until link time
7978
private String defPointer;
8079

81-
public ELambda(String name, FunctionReserved reserved,
82-
Location location, List<String> paramTypes, List<String> paramNames,
80+
public ELambda(FunctionReserved reserved, Location location,
81+
List<String> paramTypes, List<String> paramNames,
8382
List<AStatement> statements) {
8483
super(location);
85-
this.name = Objects.requireNonNull(name);
8684
this.reserved = Objects.requireNonNull(reserved);
8785
this.paramTypeStrs = Collections.unmodifiableList(paramTypes);
8886
this.paramNameStrs = Collections.unmodifiableList(paramNames);
@@ -167,6 +165,7 @@ void analyze(Locals locals) {
167165
paramNames.addAll(paramNameStrs);
168166

169167
// desugar lambda body into a synthetic method
168+
String name = locals.getNextSyntheticName();
170169
desugared = new SFunction(reserved, location, PainlessLookupUtility.typeToCanonicalTypeName(returnType), name,
171170
paramTypes, paramNames, statements, true);
172171
desugared.generateSignature(locals.getPainlessLookup());
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.painless.node;
21+
22+
import org.elasticsearch.painless.FunctionRef;
23+
import org.elasticsearch.painless.Globals;
24+
import org.elasticsearch.painless.Locals;
25+
import org.elasticsearch.painless.Location;
26+
import org.elasticsearch.painless.MethodWriter;
27+
import org.objectweb.asm.Type;
28+
29+
import java.util.Arrays;
30+
import java.util.Objects;
31+
import java.util.Set;
32+
33+
/**
34+
* Represents a function reference.
35+
*/
36+
public final class ENewArrayFunctionRef extends AExpression implements ILambda {
37+
private final String type;
38+
39+
private SFunction function;
40+
private FunctionRef ref;
41+
private String defPointer;
42+
43+
public ENewArrayFunctionRef(Location location, String type) {
44+
super(location);
45+
46+
this.type = Objects.requireNonNull(type);
47+
}
48+
49+
@Override
50+
void extractVariables(Set<String> variables) {}
51+
52+
@Override
53+
void analyze(Locals locals) {
54+
SReturn code = new SReturn(location, new ENewArray(location, type, Arrays.asList(new EVariable(location, "size")), false));
55+
function = new SFunction(new SFunction.FunctionReserved(), location, type, locals.getNextSyntheticName(),
56+
Arrays.asList("int"), Arrays.asList("size"), Arrays.asList(code), true);
57+
function.generateSignature(locals.getPainlessLookup());
58+
function.analyze(Locals.newLambdaScope(locals.getProgramScope(), function.name, function.returnType,
59+
function.parameters, 0, 0));
60+
61+
if (expected == null) {
62+
ref = null;
63+
actual = String.class;
64+
defPointer = "Sthis." + function.name + ",0";
65+
} else {
66+
defPointer = null;
67+
ref = FunctionRef.create(locals.getPainlessLookup(), locals.getMethods(), location, expected, "this", function.name, 0);
68+
actual = expected;
69+
}
70+
}
71+
72+
@Override
73+
void write(MethodWriter writer, Globals globals) {
74+
if (ref != null) {
75+
writer.writeDebugInfo(location);
76+
writer.invokeLambdaCall(ref);
77+
} else {
78+
// push a null instruction as a placeholder for future lambda instructions
79+
writer.push((String)null);
80+
}
81+
82+
globals.addSyntheticMethod(function);
83+
}
84+
85+
@Override
86+
public String getPointer() {
87+
return defPointer;
88+
}
89+
90+
@Override
91+
public Type[] getCaptures() {
92+
return new Type[0]; // no captures
93+
}
94+
95+
@Override
96+
public String toString() {
97+
return singleLineToString(type + "[]", "new");
98+
}
99+
}

modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,20 +140,17 @@ public int getMaxLoopCounter() {
140140
private final List<org.objectweb.asm.commons.Method> getMethods;
141141
private byte[] bytes;
142142

143-
public SSource(ScriptClassInfo scriptClassInfo, CompilerSettings settings, String name, Printer debugStream,
144-
MainMethodReserved reserved, Location location, List<SFunction> functions, Globals globals, List<AStatement> statements) {
143+
public SSource(ScriptClassInfo scriptClassInfo, CompilerSettings settings, String name, String sourceText, Printer debugStream,
144+
MainMethodReserved reserved, Location location, List<SFunction> functions, List<AStatement> statements) {
145145
super(location);
146146
this.scriptClassInfo = Objects.requireNonNull(scriptClassInfo);
147147
this.settings = Objects.requireNonNull(settings);
148148
this.name = Objects.requireNonNull(name);
149149
this.debugStream = debugStream;
150150
this.reserved = Objects.requireNonNull(reserved);
151-
// process any synthetic functions generated by walker (because right now, thats still easy)
152-
functions.addAll(globals.getSyntheticMethods().values());
153-
globals.getSyntheticMethods().clear();
154151
this.functions = Collections.unmodifiableList(functions);
155152
this.statements = Collections.unmodifiableList(statements);
156-
this.globals = globals;
153+
this.globals = new Globals(new BitSet(sourceText.length()));
157154

158155
this.getMethods = new ArrayList<>();
159156
}

0 commit comments

Comments
 (0)