Skip to content

Commit

Permalink
#63. Negative Lookahead Error
Browse files Browse the repository at this point in the history
This kind of almost works
  • Loading branch information
curious-odd-man committed Aug 21, 2021
1 parent b6657c6 commit f367352
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 62 deletions.
58 changes: 37 additions & 21 deletions src/main/java/com/github/curiousoddman/rgxgen/RgxGen.java
Expand Up @@ -29,19 +29,21 @@
import com.github.curiousoddman.rgxgen.visitors.UniqueValuesCountingVisitor;

import java.math.BigInteger;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.stream.Stream;

import static com.github.curiousoddman.rgxgen.config.RgxGenOption.MAX_LOOKAROUND_MATCH_RETRIES;

/**
* String values generator based on regular expression pattern
*/
public class RgxGen {
private static RgxGenProperties aGlobalProperties;

private final Node aNode;
private final List<Validator> aValidators;
private final Node aNode;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private final Optional<Validator> aValidator;

private RgxGenProperties aLocalProperties = aGlobalProperties;

Expand Down Expand Up @@ -73,7 +75,7 @@ public RgxGen(CharSequence pattern) {
*/
public RgxGen(NodeTreeBuilder builder) {
aNode = builder.get();
aValidators = builder.getValidators();
aValidator = builder.getValidator();
}

/**
Expand Down Expand Up @@ -102,7 +104,7 @@ public void setProperties(RgxGenProperties properties) {
*/
@Deprecated
public BigInteger numUnique() {
if (!aValidators.isEmpty()) {
if (aValidator.isPresent()) {
return null;
}
UniqueValuesCountingVisitor v = new UniqueValuesCountingVisitor(aLocalProperties);
Expand All @@ -119,7 +121,7 @@ public BigInteger numUnique() {
* though actual count is only 5, because right and left part of group can yield same value
*/
public Optional<BigInteger> getUniqueEstimation() {
if (!aValidators.isEmpty()) {
if (aValidator.isPresent()) {
return Optional.empty();
}

Expand All @@ -146,10 +148,10 @@ public Stream<String> stream() {
public StringIterator iterateUnique() {
UniqueGenerationVisitor ugv = new UniqueGenerationVisitor(aLocalProperties);
aNode.visit(ugv);
if (aValidators.isEmpty()) {
return ugv.getUniqueStrings();
if (aValidator.isPresent()) {
return new ValidatedIterator(aValidator.get(), ugv.getUniqueStrings());
} else {
return new ValidatedIterator(aValidators, ugv.getUniqueStrings());
return ugv.getUniqueStrings();
}
}

Expand All @@ -170,18 +172,32 @@ public String generate() {
* @return generated string.
*/
public String generate(Random random) {
String[] value = new String[1];
do {
GenerationVisitor gv = GenerationVisitor.builder()
.withRandom(random)
.withProperties(aLocalProperties)
.get();
aNode.visit(gv);
value[0] = gv.getString();
} while (!aValidators.stream()
.allMatch(validator -> validator.validate(value[0])));

return value[0];
if (aValidator.isPresent()) {
int maxRetries = MAX_LOOKAROUND_MATCH_RETRIES.getIntFromProperties(aLocalProperties);
boolean limitRetries = maxRetries > 0;
int currentRetry = 0;
Validator validator = aValidator.get();
String value;
do {
if (limitRetries && ++currentRetry > maxRetries) {
throw new RuntimeException("Pattern generation takes too much tries.");
}
value = generateImpl(random);
} while (!validator.isValid(value));

return value;
} else {
return generateImpl(random);
}
}

private String generateImpl(Random random) {
GenerationVisitor gv = GenerationVisitor.builder()
.withRandom(random)
.withProperties(aLocalProperties)
.get();
aNode.visit(gv);
return gv.getString();
}

/**
Expand Down
Expand Up @@ -35,7 +35,16 @@ public enum RgxGenOption {
*
* @defaultValue false
*/
CASE_INSENSITIVE("matching.case.insensitive", "false");
CASE_INSENSITIVE("matching.case.insensitive", "false"),

/**
* Maximum number to re-try generate value when lookaround patterns are used.
* When this number of retries is exceeded - the exception is thrown.
* Negative value means infinite retries. (WARNING! This might lead to infinite loop)
*
* @defaultValue 1000
*/
MAX_LOOKAROUND_MATCH_RETRIES("max.lookaround.retries", "1000");

private final String aKey;
private final String aDefault;
Expand Down
@@ -1,20 +1,35 @@
package com.github.curiousoddman.rgxgen.iterators;

/* **************************************************************************
Copyright 2019 Vladislavs Varslavans
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
/* **************************************************************************/

import com.github.curiousoddman.rgxgen.validation.Validator;

import java.util.List;
import java.util.NoSuchElementException;

public class ValidatedIterator implements StringIterator {
private final List<Validator> aValidatorList;
private final StringIterator aStringIterator;
private final Validator aValidator;
private final StringIterator aStringIterator;

private boolean isInitialized = false;
private String aCurrentValue = null;
private boolean hasNext = true;

public ValidatedIterator(List<Validator> validatorList, StringIterator stringIterator) {
aValidatorList = validatorList;
public ValidatedIterator(Validator validator, StringIterator stringIterator) {
aValidator = validator;
aStringIterator = stringIterator;
}

Expand All @@ -34,8 +49,7 @@ public String current() {
private boolean findNextValid() {
while (aStringIterator.hasNext()) {
String next = aStringIterator.next();
if (aValidatorList.stream()
.allMatch(v -> v.validate(next))) {
if (aValidator.isValid(next)) {
return true;
}
}
Expand Down
Expand Up @@ -19,7 +19,7 @@
import com.github.curiousoddman.rgxgen.nodes.Node;
import com.github.curiousoddman.rgxgen.validation.Validator;

import java.util.List;
import java.util.Optional;

/**
* Interface for the parser/nodes builder.
Expand All @@ -32,8 +32,7 @@ public interface NodeTreeBuilder {
Node get();

/**
*
* @return list of validators that should be applied to determine if the generated text satisfies them all.
*/
List<Validator> getValidators();
Optional<Validator> getValidator();
}
Expand Up @@ -19,6 +19,7 @@
import com.github.curiousoddman.rgxgen.nodes.*;
import com.github.curiousoddman.rgxgen.parsing.NodeTreeBuilder;
import com.github.curiousoddman.rgxgen.util.Util;
import com.github.curiousoddman.rgxgen.validation.FullPatternValidator;
import com.github.curiousoddman.rgxgen.validation.Validator;

import java.util.*;
Expand All @@ -36,20 +37,22 @@ public class DefaultTreeBuilder implements NodeTreeBuilder {
private static final ConstantsProvider CONST_PROVIDER = new ConstantsProvider();

private final CharIterator aCharIterator;
private String aPattern;
private final Map<Node, Integer> aNodesStartPos = new IdentityHashMap<>();
private final List<Validator> aValidators = new ArrayList<>();

private Node aNode;
private int aNextGroupIndex = 1;
private boolean aNeedValidation = false;
private Node aNode;
private int aNextGroupIndex = 1;

/**
* Default implementation of parser and NodeTreeBuilder.
* It reads expression and creates a hierarchy of {@code com.github.curiousoddman.rgxgen.generator.nodes.Node}.
*
* @param expr expression to parse
* @param pattern expression to parse
*/
public DefaultTreeBuilder(String expr) {
aCharIterator = new CharIterator(expr);
public DefaultTreeBuilder(String pattern) {
aCharIterator = new CharIterator(pattern);
aPattern = pattern;
}

/**
Expand Down Expand Up @@ -233,7 +236,7 @@ private Node parseGroup(int groupStartPos, GroupType currentGroupType) {
sbToFinal(sb, nodes);
int intGroupStartPos = aCharIterator.prevPos();
GroupType groupType = processGroupType();
nodes.add(parseGroup(intGroupStartPos, groupType));
handleGroupType(nodes, intGroupStartPos, groupType);
break;

case '|':
Expand Down Expand Up @@ -268,6 +271,39 @@ private Node parseGroup(int groupStartPos, GroupType currentGroupType) {
return handleGroupEndCharacter(groupStartPos, sb, nodes, isChoice, choices, captureGroupIndex, currentGroupType);
}

private void handleGroupType(List<Node> nodes, int intGroupStartPos, GroupType groupType) {
Node newNode;
switch (groupType) {
case NEGATIVE_LOOKAHEAD:
newNode = parseGroup(intGroupStartPos, groupType);
if (aCharIterator.hasNext()) {
// If we have more nodes - we need to validate all generated texts and do not include lookahead node
aNeedValidation = true;
} else {
// If there are no more nodes - means we can just generate something that does not match the pattern.
nodes.add(newNode);
}
break;

case NEGATIVE_LOOKBEHIND:
newNode = parseGroup(intGroupStartPos, groupType);
if (aCharIterator.prevPos() == 3) {
// There is nothing to validate before this node. So we can just generate not matching text.
nodes.add(newNode);
} else {
aNeedValidation = true;
}
break;

case POSITIVE_LOOKBEHIND:
case POSITIVE_LOOKAHEAD:
case CAPTURE_GROUP:
case NON_CAPTURE_GROUP:
nodes.add(parseGroup(intGroupStartPos, groupType));
break;
}
}

private void handleAnySymbolCharacter(Collection<Node> nodes, StringBuilder sb) {
sbToFinal(sb, nodes);
SymbolSet symbolSet = new SymbolSet();
Expand Down Expand Up @@ -678,7 +714,7 @@ public Node get() {
}

@Override
public List<Validator> getValidators() {
return aValidators;
public Optional<Validator> getValidator() {
return aNeedValidation ? Optional.of(new FullPatternValidator(aPattern)) : Optional.empty();
}
}
@@ -0,0 +1,33 @@
package com.github.curiousoddman.rgxgen.validation;

/* **************************************************************************
Copyright 2019 Vladislavs Varslavans
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
/* **************************************************************************/

import java.util.regex.Pattern;

public class FullPatternValidator implements Validator {
private final Pattern aPattern;

public FullPatternValidator(String pattern) {
aPattern = Pattern.compile(pattern);
}

@Override
public boolean isValid(String text) {
return aPattern.matcher(text)
.find();
}
}
Expand Up @@ -28,5 +28,5 @@ public interface Validator {
* @param text text to check
* @return true if text is valid, false otherwise
*/
boolean validate(String text);
boolean isValid(String text);
}
Expand Up @@ -239,7 +239,7 @@ public enum TestPattern implements DataInterface {
}},
//-----------------------------------------------------------------------------------------------------------------------------------------
NEGATIVE_LOOKAHEAD_BEFORE_SPANS_TWO_NODES("(?!BB)[AB][AB]",
new FinalSymbol("NOT IMPLEMENTED")) {{
new FinalSymbol("NOT IMPLEMENTED")) {{
setUseFindForMatching();
}},
//-----------------------------------------------------------------------------------------------------------------------------------------
Expand Down

0 comments on commit f367352

Please sign in to comment.