All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.burt.jmespath.parser.ExpressionParser Maven / Gradle / Ivy

There is a newer version: 0.6.0
Show newest version
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)));
  }
}