Skip to content

Commit 5679e10

Browse files
authored
QL: Introduce ParserUtils to consolidate code (elastic#76399)
Move common ANTLR utility methods, such as extracting code or case insensitive streams into QL. Replace CaseInsensitiveStream (which relies on ANTLRInputStream which has been deprecated) with a CharStream variant.
1 parent adfa977 commit 5679e10

File tree

13 files changed

+252
-227
lines changed

13 files changed

+252
-227
lines changed

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/AbstractBuilder.java

Lines changed: 4 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,11 @@
77
package org.elasticsearch.xpack.eql.parser;
88

99
import org.antlr.v4.runtime.ParserRuleContext;
10-
import org.antlr.v4.runtime.Token;
11-
import org.antlr.v4.runtime.misc.Interval;
1210
import org.antlr.v4.runtime.tree.ParseTree;
13-
import org.antlr.v4.runtime.tree.TerminalNode;
11+
import org.elasticsearch.xpack.ql.parser.ParserUtils;
1412
import org.elasticsearch.xpack.ql.plan.logical.LogicalPlan;
15-
import org.elasticsearch.xpack.ql.tree.Location;
1613
import org.elasticsearch.xpack.ql.tree.Source;
17-
import org.elasticsearch.xpack.ql.util.Check;
1814

19-
import java.util.ArrayList;
2015
import java.util.List;
2116

2217
/**
@@ -26,90 +21,19 @@ abstract class AbstractBuilder extends EqlBaseBaseVisitor<Object> {
2621

2722
@Override
2823
public Object visit(ParseTree tree) {
29-
Object result = super.visit(tree);
30-
Check.notNull(result, "Don't know how to handle context [{}] with value [{}]", tree.getClass(), tree.getText());
31-
return result;
24+
return ParserUtils.visit(super::visit, tree);
3225
}
3326

34-
@SuppressWarnings("unchecked")
3527
protected <T> T typedParsing(ParseTree ctx, Class<T> type) {
36-
Object result = ctx.accept(this);
37-
if (type.isInstance(result)) {
38-
return (T) result;
39-
}
40-
41-
throw new ParsingException(source(ctx), "Invalid query '{}'[{}] given; expected {} but found {}",
42-
ctx.getText(), ctx.getClass().getSimpleName(),
43-
type.getSimpleName(), (result != null ? result.getClass().getSimpleName() : "null"));
28+
return ParserUtils.typedParsing(this, ctx, type);
4429
}
4530

4631
protected LogicalPlan plan(ParseTree ctx) {
4732
return typedParsing(ctx, LogicalPlan.class);
4833
}
4934

5035
protected List<LogicalPlan> plans(List<? extends ParserRuleContext> ctxs) {
51-
return visitList(ctxs, LogicalPlan.class);
52-
}
53-
54-
protected <T> List<T> visitList(List<? extends ParserRuleContext> contexts, Class<T> clazz) {
55-
List<T> results = new ArrayList<>(contexts.size());
56-
for (ParserRuleContext context : contexts) {
57-
results.add(clazz.cast(visit(context)));
58-
}
59-
return results;
60-
}
61-
62-
static Source source(ParseTree ctx) {
63-
if (ctx instanceof ParserRuleContext) {
64-
return source((ParserRuleContext) ctx);
65-
}
66-
return Source.EMPTY;
67-
}
68-
69-
static Source source(TerminalNode terminalNode) {
70-
Check.notNull(terminalNode, "terminalNode is null");
71-
return source(terminalNode.getSymbol());
72-
}
73-
74-
static Source source(ParserRuleContext parserRuleContext) {
75-
Check.notNull(parserRuleContext, "parserRuleContext is null");
76-
Token start = parserRuleContext.start;
77-
Token stop = parserRuleContext.stop != null ? parserRuleContext.stop : start;
78-
Interval interval = new Interval(start.getStartIndex(), stop.getStopIndex());
79-
String text = start.getInputStream().getText(interval);
80-
return new Source(new Location(start.getLine(), start.getCharPositionInLine()), text);
81-
}
82-
83-
static Source source(Token token) {
84-
Check.notNull(token, "token is null");
85-
String text = token.getInputStream().getText(new Interval(token.getStartIndex(), token.getStopIndex()));
86-
return new Source(new Location(token.getLine(), token.getCharPositionInLine()), text);
87-
}
88-
89-
Source source(ParserRuleContext begin, ParserRuleContext end) {
90-
Check.notNull(begin, "begin is null");
91-
Check.notNull(end, "end is null");
92-
Token start = begin.start;
93-
Token stop = end.stop != null ? end.stop : begin.stop;
94-
Interval interval = new Interval(start.getStartIndex(), stop.getStopIndex());
95-
String text = start.getInputStream().getText(interval);
96-
return new Source(new Location(start.getLine(), start.getCharPositionInLine()), text);
97-
}
98-
99-
static Source source(TerminalNode begin, ParserRuleContext end) {
100-
Check.notNull(begin, "begin is null");
101-
Check.notNull(end, "end is null");
102-
Token start = begin.getSymbol();
103-
Token stop = end.stop != null ? end.stop : start;
104-
String text = start.getInputStream().getText(new Interval(start.getStartIndex(), stop.getStopIndex()));
105-
return new Source(new Location(start.getLine(), start.getCharPositionInLine()), text);
106-
}
107-
108-
/**
109-
* Retrieves the raw text of the node (without interpreting it as a string literal).
110-
*/
111-
static String text(ParseTree node) {
112-
return node == null ? null : node.getText();
36+
return ParserUtils.visitList(this, ctxs, LogicalPlan.class);
11337
}
11438

11539
public static String unquoteString(Source source) {
@@ -212,11 +136,4 @@ private static void checkForSingleQuotedString(Source source, String text, int i
212136
"Use double quotes [\"] to define string literals, not single quotes [']");
213137
}
214138
}
215-
216-
@Override
217-
public Object visitTerminal(TerminalNode node) {
218-
Source source = source(node);
219-
throw new ParsingException(source, "Does not know how to handle {}", source.text());
220-
}
221-
222139
}

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/ExpressionBuilder.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858

5959
import static java.util.Collections.emptyList;
6060
import static java.util.stream.Collectors.toList;
61+
import static org.elasticsearch.xpack.ql.parser.ParserUtils.source;
62+
import static org.elasticsearch.xpack.ql.parser.ParserUtils.visitList;
6163

6264

6365
public class ExpressionBuilder extends IdentifierBuilder {
@@ -73,7 +75,7 @@ protected Expression expression(ParseTree ctx) {
7375
}
7476

7577
protected List<Expression> expressions(List<? extends ParserRuleContext> contexts) {
76-
return visitList(contexts, Expression.class);
78+
return visitList(this, contexts, Expression.class);
7779
}
7880

7981
@Override
@@ -84,7 +86,7 @@ public Expression visitSingleExpression(EqlBaseParser.SingleExpressionContext ct
8486
@Override
8587
public List<Attribute> visitJoinKeys(JoinKeysContext ctx) {
8688
try {
87-
return ctx != null ? visitList(ctx.expression(), Attribute.class) : emptyList();
89+
return ctx != null ? visitList(this, ctx.expression(), Attribute.class) : emptyList();
8890
} catch (ClassCastException ex) {
8991
Source source = source(ctx);
9092
throw new ParsingException(source, "Unsupported join key ", source.text());
@@ -203,7 +205,7 @@ private Expression combineExpressions(
203205
List<? extends ParserRuleContext> expressions,
204206
java.util.function.Function<Expression, Expression> mapper
205207
) {
206-
return Predicates.combineOr(expressions(expressions).stream().map(mapper::apply).collect(toList()));
208+
return Predicates.combineOr(expressions(expressions).stream().map(mapper).collect(toList()));
207209
}
208210

209211
@Override

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/IdentifierBuilder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import org.elasticsearch.xpack.eql.parser.EqlBaseParser.IdentifierContext;
1111
import org.elasticsearch.xpack.eql.parser.EqlBaseParser.QualifiedNameContext;
1212

13+
import static org.elasticsearch.xpack.ql.parser.ParserUtils.visitList;
14+
1315
abstract class IdentifierBuilder extends AbstractBuilder {
1416

1517
@Override
@@ -24,7 +26,7 @@ public String visitQualifiedName(QualifiedNameContext ctx) {
2426
}
2527

2628
// this is fine, because we've already checked for array indexes [...]
27-
return Strings.collectionToDelimitedString(visitList(ctx.identifier(), String.class), ".");
29+
return Strings.collectionToDelimitedString(visitList(this, ctx.identifier(), String.class), ".");
2830
}
2931

3032
private static String unquoteIdentifier(String identifier) {

x-pack/plugin/eql/src/main/java/org/elasticsearch/xpack/eql/parser/LogicalPlanBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656

5757
import static java.util.Collections.emptyList;
5858
import static java.util.Collections.singletonList;
59+
import static org.elasticsearch.xpack.ql.parser.ParserUtils.source;
60+
import static org.elasticsearch.xpack.ql.parser.ParserUtils.text;
5961
import static org.elasticsearch.xpack.ql.tree.Source.synthetic;
6062

6163
public abstract class LogicalPlanBuilder extends ExpressionBuilder {
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.ql.parser;
9+
10+
import org.antlr.v4.runtime.CharStream;
11+
import org.antlr.v4.runtime.misc.Interval;
12+
13+
// Wrapping stream for handling case-insensitive grammars
14+
15+
// This approach is taken from the ANTLR documentation
16+
// https://github.com/antlr/antlr4/blob/master/doc/resources/CaseChangingCharStream.java
17+
// https://github.com/antlr/antlr4/blob/master/doc/case-insensitive-lexing.md
18+
19+
/**
20+
* This class supports case-insensitive lexing by wrapping an existing
21+
* {@link CharStream} and forcing the lexer to see either upper or
22+
* lowercase characters. Grammar literals should then be either upper or
23+
* lower case such as 'BEGIN' or 'begin'. The text of the character
24+
* stream is unaffected. Example: input 'BeGiN' would match lexer rule
25+
* 'BEGIN' if constructor parameter upper=true but getText() would return
26+
* 'BeGiN'.
27+
*/
28+
public class CaseChangingCharStream implements CharStream {
29+
30+
private final CharStream stream;
31+
private final boolean upper;
32+
33+
/**
34+
* Constructs a new CaseChangingCharStream wrapping the given {@link CharStream} forcing
35+
* all characters to upper case or lower case.
36+
* @param stream The stream to wrap.
37+
* @param upper If true force each symbol to upper case, otherwise force to lower.
38+
*/
39+
public CaseChangingCharStream(CharStream stream, boolean upper) {
40+
this.stream = stream;
41+
this.upper = upper;
42+
}
43+
44+
@Override
45+
public String getText(Interval interval) {
46+
return stream.getText(interval);
47+
}
48+
49+
@Override
50+
public void consume() {
51+
stream.consume();
52+
}
53+
54+
@Override
55+
public int LA(int i) {
56+
int c = stream.LA(i);
57+
if (c <= 0) {
58+
return c;
59+
}
60+
return upper ? Character.toUpperCase(c) : Character.toLowerCase(c);
61+
}
62+
63+
@Override
64+
public int mark() {
65+
return stream.mark();
66+
}
67+
68+
@Override
69+
public void release(int marker) {
70+
stream.release(marker);
71+
}
72+
73+
@Override
74+
public int index() {
75+
return stream.index();
76+
}
77+
78+
@Override
79+
public void seek(int index) {
80+
stream.seek(index);
81+
}
82+
83+
@Override
84+
public int size() {
85+
return stream.size();
86+
}
87+
88+
@Override
89+
public String getSourceName() {
90+
return stream.getSourceName();
91+
}
92+
}

0 commit comments

Comments
 (0)