Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Added
- [Java] Make CucumberExpressionParser.parse public ([#340](https://github.com/cucumber/cucumber-expressions/pull/340))
### Changed
- [Ruby] Minor cosmetic / CI changes for development (Nothing front-facing)
- [Python] PEP 639 licence metadata specification ([#361](https://github.com/cucumber/cucumber-expressions/pull/361))
Expand Down
146 changes: 12 additions & 134 deletions java/src/main/java/io/cucumber/cucumberexpressions/Ast.java
Original file line number Diff line number Diff line change
@@ -1,156 +1,34 @@
package io.cucumber.cucumberexpressions;

import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;

import static java.util.Arrays.asList;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;

final class Ast {

private static final char escapeCharacter = '\\';
private static final char alternationCharacter = '/';
private static final char beginParameterCharacter = '{';
private static final char endParameterCharacter = '}';
private static final char beginOptionalCharacter = '(';
private static final char endOptionalCharacter = ')';

interface Located {
int start();

int end();

}

static final class Node implements Located {

private final Type type;
private final List<Node> nodes;
private final String token;
private final int start;
private final int end;

Node(Type type, int start, int end, String token) {
this(type, start, end, null, token);
}

Node(Type type, int start, int end, List<Node> nodes) {
this(type, start, end, nodes, null);
}

private Node(Type type, int start, int end, List<Node> nodes, String token) {
this.type = requireNonNull(type);
this.nodes = nodes;
this.token = token;
this.start = start;
this.end = end;
}

enum Type {
TEXT_NODE,
OPTIONAL_NODE,
ALTERNATION_NODE,
ALTERNATIVE_NODE,
PARAMETER_NODE,
EXPRESSION_NODE
}

public int start() {
return start;
}

public int end() {
return end;
}

List<Node> nodes() {
return nodes;
}

Type type() {
return type;
}

String text() {
if (nodes == null)
return token;

return nodes().stream()
.map(Node::text)
.collect(joining());
}

@Override
public String toString() {
return toString(0).toString();
}

private StringBuilder toString(int depth) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < depth; i++) {
sb.append(" ");
}
sb.append("{")
.append("\"type\": \"").append(type)
.append("\", \"start\": ")
.append(start)
.append(", \"end\": ")
.append(end);

if (token != null) {
sb.append(", \"token\": \"").append(token.replaceAll("\\\\", "\\\\\\\\")).append("\"");
}

if (nodes != null) {
sb.append(", \"nodes\": ");
if (!nodes.isEmpty()) {
StringBuilder padding = new StringBuilder();
for (int i = 0; i < depth; i++) {
padding.append(" ");
}
sb.append(nodes.stream()
.map(node -> node.toString(depth + 1))
.collect(joining(",\n", "[\n", "\n" +padding + "]")));

} else {
sb.append("[]");
}
}
sb.append("}");
return sb;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Node node = (Node) o;
return start == node.start &&
end == node.end &&
type == node.type &&
Objects.equals(nodes, node.nodes) &&
Objects.equals(token, node.token);
}

@Override
public int hashCode() {
return Objects.hash(type, nodes, token, start, end);
}

}

static final class Token implements Located {

private static final char escapeCharacter = '\\';
private static final char alternationCharacter = '/';
private static final char beginParameterCharacter = '{';
private static final char endParameterCharacter = '}';
private static final char beginOptionalCharacter = '(';
private static final char endOptionalCharacter = ')';

final String text;
final Token.Type type;
final Type type;
final int start;
final int end;

Token(String text, Token.Type type, int start, int end) {
Token(String text, Type type, int start, int end) {
this.text = requireNonNull(text);
this.type = requireNonNull(type);
this.start = start;
Expand Down Expand Up @@ -224,10 +102,10 @@ public int hashCode() {

@Override
public String toString() {
return new StringJoiner(", ", "" + "{", "}")
return new StringJoiner(", ", "{", "}")
.add("\"type\": \"" + type + "\"")
.add("\"start\": " + start + "")
.add("\"end\": " + end + "")
.add("\"start\": " + start)
.add("\"end\": " + end)
.add("\"text\": \"" + text + "\"")
.toString();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
package io.cucumber.cucumberexpressions;

import io.cucumber.cucumberexpressions.Ast.Node;
import io.cucumber.cucumberexpressions.Node.Type;
import org.apiguardian.api.API;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.regex.Pattern;

import static io.cucumber.cucumberexpressions.Ast.Node.Type.OPTIONAL_NODE;
import static io.cucumber.cucumberexpressions.Ast.Node.Type.PARAMETER_NODE;
import static io.cucumber.cucumberexpressions.Ast.Node.Type.TEXT_NODE;
import static io.cucumber.cucumberexpressions.Node.Type.OPTIONAL_NODE;
import static io.cucumber.cucumberexpressions.Node.Type.PARAMETER_NODE;
import static io.cucumber.cucumberexpressions.Node.Type.TEXT_NODE;
import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeMayNotBeEmpty;
import static io.cucumber.cucumberexpressions.CucumberExpressionException.createAlternativeMayNotExclusivelyContainOptionals;
import static io.cucumber.cucumberexpressions.CucumberExpressionException.createInvalidParameterTypeName;
import static io.cucumber.cucumberexpressions.CucumberExpressionException.createOptionalIsNotAllowedInOptional;
import static io.cucumber.cucumberexpressions.CucumberExpressionException.createOptionalMayNotBeEmpty;
import static io.cucumber.cucumberexpressions.CucumberExpressionException.createParameterIsNotAllowedInOptional;
import static io.cucumber.cucumberexpressions.ParameterType.isValidParameterTypeName;
import static io.cucumber.cucumberexpressions.RegexpUtils.escapeRegex;
import static io.cucumber.cucumberexpressions.UndefinedParameterTypeException.createUndefinedParameterType;
import static java.util.stream.Collectors.joining;
Expand Down Expand Up @@ -130,8 +127,8 @@ private void assertNoOptionals(Node node,
assertNoNodeOfType(OPTIONAL_NODE, node, createNodeContainedAnOptionalException);
}

private void assertNoNodeOfType(Node.Type nodeType, Node node,
Function<Node, CucumberExpressionException> createException) {
private void assertNoNodeOfType(Type nodeType, Node node,
Function<Node, CucumberExpressionException> createException) {
node.nodes()
.stream()
.filter(astNode -> nodeType.equals(astNode.type()))
Expand All @@ -144,7 +141,7 @@ private void assertNoNodeOfType(Node.Type nodeType, Node node,


@Override
public List<Argument<?>> match(String text, Type... typeHints) {
public List<Argument<?>> match(String text, java.lang.reflect.Type... typeHints) {
final Group group = treeRegexp.match(text);
if (group == null) {
return null;
Expand All @@ -153,7 +150,7 @@ public List<Argument<?>> match(String text, Type... typeHints) {
List<ParameterType<?>> parameterTypes = new ArrayList<>(this.parameterTypes);
for (int i = 0; i < parameterTypes.size(); i++) {
ParameterType<?> parameterType = parameterTypes.get(i);
Type type = i < typeHints.length ? typeHints[i] : String.class;
java.lang.reflect.Type type = i < typeHints.length ? typeHints[i] : String.class;
if (parameterType.isAnonymous()) {
ParameterByTypeTransformer defaultTransformer = parameterTypeRegistry.getDefaultParameterTransformer();
parameterTypes.set(i, parameterType.deAnonymize(type, arg -> defaultTransformer.transform(arg, type)));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.cucumber.cucumberexpressions;

import io.cucumber.cucumberexpressions.Ast.Located;
import io.cucumber.cucumberexpressions.Ast.Node;
import io.cucumber.cucumberexpressions.Ast.Token;
import io.cucumber.cucumberexpressions.Ast.Token.Type;
import org.apiguardian.api.API;
Expand All @@ -18,7 +17,7 @@ public class CucumberExpressionException extends RuntimeException {
}

static CucumberExpressionException createMissingEndToken(String expression, Type beginToken, Type endToken,
Token current) {
Token current) {
return new CucumberExpressionException(message(
current.start(),
expression,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package io.cucumber.cucumberexpressions;

import io.cucumber.cucumberexpressions.Ast.Node;
import io.cucumber.cucumberexpressions.Ast.Token;
import io.cucumber.cucumberexpressions.Ast.Token.Type;
import org.apiguardian.api.API;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static io.cucumber.cucumberexpressions.Ast.Node.Type.ALTERNATION_NODE;
import static io.cucumber.cucumberexpressions.Ast.Node.Type.ALTERNATIVE_NODE;
import static io.cucumber.cucumberexpressions.Ast.Node.Type.EXPRESSION_NODE;
import static io.cucumber.cucumberexpressions.Ast.Node.Type.OPTIONAL_NODE;
import static io.cucumber.cucumberexpressions.Ast.Node.Type.PARAMETER_NODE;
import static io.cucumber.cucumberexpressions.Ast.Node.Type.TEXT_NODE;
import static io.cucumber.cucumberexpressions.Node.Type.ALTERNATION_NODE;
import static io.cucumber.cucumberexpressions.Node.Type.ALTERNATIVE_NODE;
import static io.cucumber.cucumberexpressions.Node.Type.EXPRESSION_NODE;
import static io.cucumber.cucumberexpressions.Node.Type.OPTIONAL_NODE;
import static io.cucumber.cucumberexpressions.Node.Type.PARAMETER_NODE;
import static io.cucumber.cucumberexpressions.Node.Type.TEXT_NODE;
import static io.cucumber.cucumberexpressions.Ast.Token.Type.ALTERNATION;
import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_OPTIONAL;
import static io.cucumber.cucumberexpressions.Ast.Token.Type.BEGIN_PARAMETER;
Expand All @@ -27,8 +27,13 @@
import static io.cucumber.cucumberexpressions.CucumberExpressionException.createMissingEndToken;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.apiguardian.api.API.Status.EXPERIMENTAL;

final class CucumberExpressionParser {
/**
* A parser for Cucumber expressions
*/
@API(since = "18.1", status = EXPERIMENTAL)
public final class CucumberExpressionParser {

/*
* text := whitespace | ')' | '}' | .
Expand Down Expand Up @@ -160,7 +165,13 @@ final class CucumberExpressionParser {
)
);

Node parse(String expression) {
/**
* Parses as Cucumber expression into an AST of {@link Node nodes}.
* @param expression the expression to parse
* @return an AST of nodes
* @throws CucumberExpressionException if the expression could not be parsed
*/
public Node parse(String expression) {
CucumberExpressionTokenizer tokenizer = new CucumberExpressionTokenizer();
List<Token> tokens = tokenizer.tokenize(expression);
Result result = cucumberExpressionParser.parse(expression, tokens, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ private Type tokenTypeOf(Integer token, boolean treatAsText) {
}

private boolean shouldContinueTokenType(Type previousTokenType,
Type currentTokenType) {
Type currentTokenType) {
return currentTokenType == previousTokenType
&& (currentTokenType == Type.WHITE_SPACE || currentTokenType == Type.TEXT);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ final class GroupBuilder {
private final List<GroupBuilder> groupBuilders = new ArrayList<>();
private boolean capturing = true;
private String source;
private int startIndex;
private final int startIndex;
private int endIndex;

GroupBuilder(int startIndex) {
Expand Down
Loading