io.burt.jmespath.parser.ExpressionParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jmespath-core Show documentation
Show all versions of jmespath-core Show documentation
A JMESPath implementation for Java
package io.burt.jmespath.parser;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import io.burt.jmespath.Expression;
import io.burt.jmespath.Adapter;
import io.burt.jmespath.function.Function;
import io.burt.jmespath.function.ArgumentConstraint;
import io.burt.jmespath.function.ArityException;
import io.burt.jmespath.util.StringEscapeHelper;
import io.burt.jmespath.util.AntlrHelper;
import io.burt.jmespath.node.NodeFactory;
import io.burt.jmespath.node.Node;
import io.burt.jmespath.node.CreateObjectNode.Entry;
import io.burt.jmespath.node.Operator;
public class ExpressionParser extends JmesPathBaseVisitor> {
private static final StringEscapeHelper identifierEscapeHelper = new StringEscapeHelper(
true,
'"', '"',
'/', '/',
'\\', '\\',
'b', '\b',
'f', '\f',
'n', '\n',
'r', '\r',
't', '\t'
);
private static final StringEscapeHelper rawStringEscapeHelper = new StringEscapeHelper(
false,
'\'', '\'',
'\\', '\\'
);
private static final StringEscapeHelper jsonLiteralEscapeHelper = new StringEscapeHelper(
false,
'`', '`'
);
private final ParseTree tree;
private final Adapter runtime;
private final NodeFactory nodeFactory;
private final ParseErrorAccumulator errors;
private Node chainedNode;
public static Expression fromString(Adapter runtime, String rawExpression) {
ParseErrorAccumulator errors = new ParseErrorAccumulator();
JmesPathParser parser = AntlrHelper.createParser(rawExpression, errors);
ParseTree tree = parser.jmesPathExpression();
Expression expression = null;
if (errors.isEmpty()) {
ExpressionParser visitor = new ExpressionParser<>(runtime, tree, errors);
expression = visitor.expression();
}
if (!errors.isEmpty()) {
throw new ParseException(rawExpression, errors);
}
return expression;
}
private ExpressionParser(Adapter runtime, ParseTree tree, ParseErrorAccumulator errors) {
this.runtime = runtime;
this.nodeFactory = runtime.nodeFactory();
this.tree = tree;
this.errors = errors;
}
public Expression expression() {
return visit(tree);
}
private String identifierToString(JmesPathParser.IdentifierContext ctx) {
String id = ctx.getText();
if (ctx.STRING() != null) {
id = identifierEscapeHelper.unescape(id.substring(1, id.length() - 1));
}
return id;
}
private void checkForUnescapedBackticks(Token token) {
int unescapedBacktickIndex = indexOfUnescapedBacktick(token.getText());
if (unescapedBacktickIndex > -1) {
errors.parseError("syntax error unexpected `", token.getStartIndex() + unescapedBacktickIndex);
}
}
private int indexOfUnescapedBacktick(String str) {
int backtickIndex = str.indexOf('`');
while (backtickIndex > -1) {
if (backtickIndex == 0 || str.charAt(backtickIndex - 1) != '\\') {
return backtickIndex;
}
backtickIndex = str.indexOf('`', backtickIndex + 1);
}
return -1;
}
private Node createProjectionIfChained(Node node) {
if (chainedNode != null) {
node = nodeFactory.createSequence(Arrays.asList(node, nodeFactory.createProjection(chainedNode)));
chainedNode = null;
}
return node;
}
private Node createSequenceIfChained(Node node) {
if (chainedNode != null) {
node = nodeFactory.createSequence(Arrays.asList(node, chainedNode));
chainedNode = null;
}
return node;
}
private Node nonChainingVisit(ParseTree tree) {
Node stashedNextNode = chainedNode;
chainedNode = null;
Node result = createSequenceIfChained(visit(tree));
chainedNode = stashedNextNode;
return result;
}
@Override
public Node visitJmesPathExpression(JmesPathParser.JmesPathExpressionContext ctx) {
return createSequenceIfChained(visit(ctx.expression()));
}
@Override
public Node visitPipeExpression(JmesPathParser.PipeExpressionContext ctx) {
Node right = createSequenceIfChained(visit(ctx.expression(1)));
Node left = createSequenceIfChained(visit(ctx.expression(0)));
return nodeFactory.createSequence(Arrays.asList(left, right));
}
@Override
public Node visitIdentifierExpression(JmesPathParser.IdentifierExpressionContext ctx) {
return visit(ctx.identifier());
}
@Override
public Node visitNotExpression(JmesPathParser.NotExpressionContext ctx) {
return nodeFactory.createNegate(createSequenceIfChained(visit(ctx.expression())));
}
@Override
public Node visitRawStringExpression(JmesPathParser.RawStringExpressionContext ctx) {
String quotedString = ctx.RAW_STRING().getText();
String unquotedString = rawStringEscapeHelper.unescape(quotedString.substring(1, quotedString.length() - 1));
return nodeFactory.createString(unquotedString);
}
@Override
public Node visitComparisonExpression(JmesPathParser.ComparisonExpressionContext ctx) {
Operator operator = Operator.fromString(ctx.COMPARATOR().getText());
Node right = nonChainingVisit(ctx.expression(1));
Node left = nonChainingVisit(ctx.expression(0));
return createSequenceIfChained(nodeFactory.createComparison(operator, left, right));
}
@Override
public Node visitParenExpression(JmesPathParser.ParenExpressionContext ctx) {
return createSequenceIfChained(nonChainingVisit(ctx.expression()));
}
@Override
public Node visitBracketExpression(JmesPathParser.BracketExpressionContext ctx) {
Node result = visit(ctx.bracketSpecifier());
if (result == null) {
result = chainedNode;
chainedNode = null;
}
return result;
}
@Override
public Node visitOrExpression(JmesPathParser.OrExpressionContext ctx) {
Node left = nonChainingVisit(ctx.expression(0));
Node right = nonChainingVisit(ctx.expression(1));
return createSequenceIfChained(nodeFactory.createOr(left, right));
}
@Override
public Node visitChainExpression(JmesPathParser.ChainExpressionContext ctx) {
chainedNode = visit(ctx.chainedExpression());
return createSequenceIfChained(visit(ctx.expression()));
}
@Override
public Node visitAndExpression(JmesPathParser.AndExpressionContext ctx) {
Node left = nonChainingVisit(ctx.expression(0));
Node right = nonChainingVisit(ctx.expression(1));
return createSequenceIfChained(nodeFactory.createAnd(left, right));
}
@Override
public Node visitWildcardExpression(JmesPathParser.WildcardExpressionContext ctx) {
return visit(ctx.wildcard());
}
@Override
public Node visitBracketedExpression(JmesPathParser.BracketedExpressionContext ctx) {
Node chainAfterExpression = visit(ctx.bracketSpecifier());
Node expression = createSequenceIfChained(visit(ctx.expression()));
chainedNode = chainAfterExpression;
return createSequenceIfChained(expression);
}
@Override
public Node visitWildcard(JmesPathParser.WildcardContext ctx) {
return createProjectionIfChained(nodeFactory.createFlattenObject());
}
@Override
public Node visitMultiSelectList(JmesPathParser.MultiSelectListContext ctx) {
int n = ctx.expression().size();
List> entries = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
entries.add(nonChainingVisit(ctx.expression(i)));
}
return createSequenceIfChained(nodeFactory.createCreateArray(entries));
}
@Override
public Node visitMultiSelectHash(JmesPathParser.MultiSelectHashContext ctx) {
int n = ctx.keyvalExpr().size();
List> entries = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
JmesPathParser.KeyvalExprContext kvCtx = ctx.keyvalExpr(i);
String key = identifierToString(kvCtx.identifier());
Node value = nonChainingVisit(kvCtx.expression());
entries.add(new Entry<>(key, value));
}
return createSequenceIfChained(nodeFactory.createCreateObject(entries));
}
@Override
public Node visitBracketIndex(JmesPathParser.BracketIndexContext ctx) {
int index = Integer.parseInt(ctx.SIGNED_INT().getText());
chainedNode = createSequenceIfChained(nodeFactory.createIndex(index));
return null;
}
@Override
public Node visitBracketStar(JmesPathParser.BracketStarContext ctx) {
Node projection = (chainedNode == null) ? nodeFactory.createCurrent() : chainedNode;
chainedNode = nodeFactory.createProjection(projection);
return null;
}
@Override
public Node visitBracketSlice(JmesPathParser.BracketSliceContext ctx) {
Integer start = null;
Integer stop = null;
Integer step = null;
JmesPathParser.SliceContext sliceCtx = ctx.slice();
if (sliceCtx.start != null) {
start = Integer.parseInt(sliceCtx.start.getText());
}
if (sliceCtx.stop != null) {
stop = Integer.parseInt(sliceCtx.stop.getText());
}
if (sliceCtx.step != null) {
step = Integer.parseInt(sliceCtx.step.getText());
if (step == 0) {
errors.parseError(String.format("invalid value %d for step size", step), sliceCtx.step.getStartIndex());
}
}
chainedNode = createProjectionIfChained(nodeFactory.createSlice(start, stop, step));
return null;
}
@Override
public Node visitBracketFlatten(JmesPathParser.BracketFlattenContext ctx) {
return createProjectionIfChained(nodeFactory.createFlattenArray());
}
@Override
public Node visitSelect(JmesPathParser.SelectContext ctx) {
chainedNode = createProjectionIfChained(nodeFactory.createSelection(nonChainingVisit(ctx.expression())));
return null;
}
@Override
public Node visitFunctionExpression(JmesPathParser.FunctionExpressionContext ctx) {
String name = ctx.NAME().getText();
int n = ctx.functionArg().size();
List> args = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
args.add(nonChainingVisit(ctx.functionArg(i)));
}
Function implementation = runtime.functionRegistry().getFunction(name);
if (implementation == null) {
Token token = ctx.NAME().getSymbol();
errors.parseError(String.format("unknown function \"%s\"", name), token.getStartIndex());
} else {
ArgumentConstraint argumentConstraints = implementation.argumentConstraints();
if (n < argumentConstraints.minArity() || n > argumentConstraints.maxArity()) {
Token token = ctx.NAME().getSymbol();
String message = ArityException.createMessage(implementation, n, false);
errors.parseError(message, token.getStartIndex());
}
}
return createSequenceIfChained(nodeFactory.createFunctionCall(implementation, args));
}
@Override
public Node visitCurrentNode(JmesPathParser.CurrentNodeContext ctx) {
if (chainedNode == null) {
return nodeFactory.createCurrent();
} else {
Node result = chainedNode;
chainedNode = null;
return result;
}
}
@Override
public Node visitExpressionType(JmesPathParser.ExpressionTypeContext ctx) {
Node expression = createSequenceIfChained(visit(ctx.expression()));
return nodeFactory.createExpressionReference(expression);
}
@Override
public Node visitLiteral(JmesPathParser.LiteralContext ctx) {
visit(ctx.jsonValue());
String string = jsonLiteralEscapeHelper.unescape(ctx.jsonValue().getText());
return nodeFactory.createJsonLiteral(string);
}
@Override
public Node visitJsonStringValue(JmesPathParser.JsonStringValueContext ctx) {
checkForUnescapedBackticks(ctx.getStart());
return super.visitJsonStringValue(ctx);
}
@Override
public Node visitJsonObjectPair(JmesPathParser.JsonObjectPairContext ctx) {
checkForUnescapedBackticks(ctx.STRING().getSymbol());
return super.visitJsonObjectPair(ctx);
}
@Override
public Node visitIdentifier(JmesPathParser.IdentifierContext ctx) {
return createSequenceIfChained(nodeFactory.createProperty(identifierToString(ctx)));
}
}