Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cucumber expressions tree regexp #237

Merged
merged 7 commits into from Jul 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion cucumber-expressions/CHANGELOG.md
Expand Up @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
----
## [Unreleased]

## [4.0.0] - 2017-06-27
## [4.0.0] - 2017-06-28

### Removed
* Remove support for `{name:type}` syntax which was deprecated in
Expand Down
4 changes: 2 additions & 2 deletions cucumber-expressions/java/.gitrepo
Expand Up @@ -6,6 +6,6 @@
[subrepo]
remote = https://github.com/cucumber/cucumber-expressions-java.git
branch = master
commit = 94bf04cc2c677540e2fe0873f35b97e89f6049db
parent = 470760fc9f0155666c8fbd0222aa74f45e153271
commit = f9ca1ec2c9bd291d5647accc6480ca5c40bac2b2
parent = 05d2490e398a8c09cbaa71e59032f4ab401334b4
cmdver = 0.4.0
2 changes: 1 addition & 1 deletion cucumber-expressions/java/pom.xml
Expand Up @@ -4,7 +4,7 @@

<groupId>io.cucumber</groupId>
<artifactId>cucumber-expressions</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cucumber-expressions</name>
<description>Cucumber Expressions</description>
Expand Down
Expand Up @@ -2,29 +2,34 @@

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class Argument<T> {
private final ParameterType<T> parameterType;
private final Group group;

static List<Argument<?>> build(Pattern pattern, String text, List<ParameterType<?>> parameterTypes) {
Matcher matcher = pattern.matcher(text);
if (!matcher.lookingAt()) return null;
static List<Argument<?>> build(TreeRegexp treeRegexp, List<ParameterType<?>> parameterTypes, String text) {
Group group = treeRegexp.match(text);
if (group == null) return null;

List<Group> argGroups = group.getChildren();

Group matchGroup = new Group(matcher);
List<Group> argGroups = matchGroup.getChildren();
if (argGroups.size() != parameterTypes.size()) {
throw new CucumberExpressionException(String.format("Expression has %s arguments, but there were %s parameter types", argGroups.size(), parameterTypes.size()));
throw new CucumberExpressionException(String.format("Expression /%s/ has %s capture groups (%s), but there were %s parameter types (%s)",
treeRegexp.pattern().pattern(),
argGroups.size(),
argGroups.stream().map(Group::getValue).collect(Collectors.toList()),
parameterTypes.size(),
parameterTypes.stream().map(ParameterType::getName).collect(Collectors.toList())
));
}

List<Argument<?>> args = new ArrayList<>(argGroups.size());
for (int i = 0; i < parameterTypes.size(); i++) {
Group argGroup = argGroups.get(i);
ParameterType<?> parameterType = parameterTypes.get(i);
args.add(new Argument<>(argGroup, parameterType));
}

return args;
}

Expand All @@ -33,6 +38,10 @@ public Argument(Group group, ParameterType<T> parameterType) {
this.parameterType = parameterType;
}

public Group getGroup() {
return group;
}

public T getValue() {
return parameterType.transform(group == null ? null : group.getValues());
}
Expand Down
Expand Up @@ -11,9 +11,9 @@ public class CucumberExpression implements Expression {
private static final Pattern OPTIONAL_PATTERN = Pattern.compile("\\(([^)]+)\\)");
private static final Pattern ALTERNATIVE_WORD_REGEXP = Pattern.compile("([\\p{IsAlphabetic}]+)((/[\\p{IsAlphabetic}]+)+)");

private final Pattern pattern;
private final List<ParameterType<?>> parameterTypes = new ArrayList<>();
private final String expression;
private final TreeRegexp treeRegexp;

public CucumberExpression(String expression, ParameterTypeRegistry parameterTypeRegistry) {
this.expression = expression;
Expand All @@ -39,15 +39,15 @@ public CucumberExpression(String expression, ParameterTypeRegistry parameterType
}
parameterTypes.add(parameterType);

matcher.appendReplacement(regexp, Matcher.quoteReplacement(getCaptureGroupRegexp(parameterType.getRegexps())));
matcher.appendReplacement(regexp, Matcher.quoteReplacement(buildCaptureRegexp(parameterType.getRegexps())));
}
matcher.appendTail(regexp);
regexp.append("$");

pattern = Pattern.compile(regexp.toString());
treeRegexp = new TreeRegexp(regexp.toString());
}

private String getCaptureGroupRegexp(List<String> regexps) {
private String buildCaptureRegexp(List<String> regexps) {
StringBuilder sb = new StringBuilder("(");

if (regexps.size() == 1) {
Expand All @@ -67,15 +67,16 @@ private String getCaptureGroupRegexp(List<String> regexps) {

@Override
public List<Argument<?>> match(String text) {
return Argument.build(pattern, text, parameterTypes);
return Argument.build(treeRegexp, parameterTypes, text);
}

@Override
public String getSource() {
return expression;
}

Pattern getPattern() {
return pattern;
@Override
public Pattern getRegexp() {
return treeRegexp.pattern();
}
}
Expand Up @@ -66,9 +66,9 @@ public List<GeneratedExpression> generateExpressions(String text) {
}

/**
* @deprecated use {@link #generateExpressions(String)}
* @param text the text (step) to generate an expression for
* @return the first of the generated expressions
* @deprecated use {@link #generateExpressions(String)}
*/
@Deprecated
public GeneratedExpression generateExpression(String text) {
Expand Down
@@ -1,9 +1,12 @@
package io.cucumber.cucumberexpressions;

import java.util.List;
import java.util.regex.Pattern;

public interface Expression {
List<Argument<?>> match(String text);

Pattern getRegexp();

String getSource();
}
@@ -1,77 +1,37 @@
package io.cucumber.cucumberexpressions;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.stream.Collectors;

import static java.util.Collections.singletonList;

/**
* Converts a {@link Matcher} into a tree of {@link Group}
* where each node has a 0-indexed list of children.
* <p>
* This is particularly useful for traversing nested capture groups
* and optional groups.
*/
class Group {
public class Group {
private final List<Group> children;
private final String value;
private final int start;
private final int end;
private final String value;
private final List<Group> children = new ArrayList<>();

public Group(Matcher matcher) {
if (matcher.groupCount() == 0) {
start = end = -1;
value = null;
return;
}
start = matcher.start(0);
end = matcher.end(0);
value = matcher.group(0);

List<Group> stack = new ArrayList<>();
stack.add(this);

for (int groupIndex = 1; groupIndex <= matcher.groupCount(); groupIndex++) {
Group group = new Group(
matcher.start(groupIndex),
matcher.end(groupIndex),
matcher.group(groupIndex)
);

while (!stack.get(stack.size() - 1).contains(group)) {
stack.remove(stack.size() - 1);
}
stack.get(stack.size() - 1).add(group);
stack.add(group);
}
}

public Group(int start, int end, String value) {
public Group(String value, int start, int end, List<Group> children) {
this.value = value;
this.start = start;
this.end = end;
this.value = value;
this.children = children;
}

public boolean contains(Group group) {
return group.isNull() || group.start >= start && group.end <= end;
}

public void add(Group group) {
children.add(group);
public String getValue() {
return value;
}

public List<Group> getChildren() {
return children;
public int getStart() {
return start;
}

public String getValue() {
return value;
public int getEnd() {
return end;
}

public boolean isNull() {
return value == null;
public List<Group> getChildren() {
return children;
}

public List<String> getValues() {
Expand Down
@@ -0,0 +1,38 @@
package io.cucumber.cucumberexpressions;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;

class GroupBuilder {
private List<GroupBuilder> groupBuilders = new ArrayList<>();
private boolean capturing = true;

void add(GroupBuilder groupBuilder) {
groupBuilders.add(groupBuilder);
}

Group build(Matcher matcher, Iterator<Integer> groupIndices) {
int groupIndex = groupIndices.next();
List<Group> children = new ArrayList<>(groupBuilders.size());
for (GroupBuilder childGroupBuilder : groupBuilders) {
children.add(childGroupBuilder.build(matcher, groupIndices));
}
return new Group(matcher.group(groupIndex), matcher.start(groupIndex), matcher.end(groupIndex), children);
}

void setNonCapturing() {
this.capturing = false;
}

boolean isCapturing() {
return capturing;
}

public void moveChildrenTo(GroupBuilder groupBuilder) {
for (GroupBuilder child : groupBuilders) {
groupBuilder.add(child);
}
}
}
Expand Up @@ -10,6 +10,7 @@ public class RegularExpression implements Expression {

private final Pattern expressionRegexp;
private final ParameterTypeRegistry parameterTypeRegistry;
private TreeRegexp treeRegexp;

/**
* Creates a new instance. Use this when the transform types are not known in advance,
Expand All @@ -22,6 +23,7 @@ public class RegularExpression implements Expression {
public RegularExpression(Pattern expressionRegexp, ParameterTypeRegistry parameterTypeRegistry) {
this.expressionRegexp = expressionRegexp;
this.parameterTypeRegistry = parameterTypeRegistry;
treeRegexp = new TreeRegexp(expressionRegexp);
}

@Override
Expand All @@ -35,16 +37,21 @@ public List<Argument<?>> match(String text) {
ParameterType<?> parameterType = parameterTypeRegistry.lookupByRegexp(parameterTypeRegexp, expressionRegexp, text);
if (parameterType == null) {
parameterType = new ParameterType<>(
"*",
parameterTypeRegexp,
parameterTypeRegexp,
String.class,
new SingleTransformer<>(String::new)
);
}
parameterTypes.add(parameterType);
}
return Argument.build(treeRegexp, parameterTypes, text);
}

return Argument.build(expressionRegexp, text, parameterTypes);

@Override
public Pattern getRegexp() {
return expressionRegexp;
}

@Override
Expand Down
Expand Up @@ -14,11 +14,13 @@ public T transform(String... groupValues) {
if (groupValues == null) return null;
String arg = null;
for (String groupValue : groupValues) {
if (groupValue != null && arg != null)
throw new CucumberExpressionException(String.format("Single transformer unexpectedly matched 2 values - \"%s\" and \"%s\"", arg, groupValue));
arg = groupValue;
if (groupValue != null) {
if (arg != null)
throw new CucumberExpressionException(String.format("Single transformer unexpectedly matched 2 values - \"%s\" and \"%s\"", arg, groupValue));
arg = groupValue;
}
}
if (arg == null) return null; // optional group
if (arg == null) return null;
return function.apply(arg);
}
}