removed loop progress exceptions and endless loop shortcut

there is no need to throw an exception if a loop has no progress -> it
can be skipped

fixed bug with merge() in TokenAnalysisPaths: merging with empty path is
an identity operation but further merged with the same path cause
problems because of concurrent modifications in the LinkedHashSet
This commit is contained in:
overflowerror 2022-02-01 17:06:25 +01:00
parent 3af9fd0c4a
commit e00d86eb1c
2 changed files with 37 additions and 69 deletions

View file

@ -196,7 +196,7 @@ public class TokenAnalysis {
path.resetProgress();
// shortcut endless loops instead of throwing exception
path = getTokenPaths(element, path, false, false, true);
path = getTokenPaths(element, path, false, false);
if (!path.isDone() && element != null) {
boolean _considerCardinalities = considerCardinalities;
@ -238,11 +238,11 @@ public class TokenAnalysis {
return result;
}
private TokenAnalysisPaths getTokenPathsTrivial(Group path, TokenAnalysisPaths prefix, boolean shortcutEndlessLoops) {
private TokenAnalysisPaths getTokenPathsTrivial(Group path, TokenAnalysisPaths prefix) {
TokenAnalysisPaths result = new TokenAnalysisPaths(prefix);
for(AbstractElement element : path.getElements()) {
result = getTokenPaths(element, result, false, false, shortcutEndlessLoops);
result = getTokenPaths(element, result, false, false);
if (result.isDone()) {
break;
}
@ -250,16 +250,16 @@ public class TokenAnalysis {
return result;
}
private TokenAnalysisPaths getTokenPathsTrivial(Alternatives path, TokenAnalysisPaths prefix, boolean shortcutEndlessLoops) {
private TokenAnalysisPaths getTokenPathsTrivial(Alternatives path, TokenAnalysisPaths prefix) {
TokenAnalysisPaths result = TokenAnalysisPaths.empty(prefix);
for(AbstractElement element : path.getElements()) {
result = result.merge(getTokenPaths(element, prefix, false, false, shortcutEndlessLoops));
result = result.merge(getTokenPaths(element, prefix, false, false));
}
return result;
}
private TokenAnalysisPaths getTokenPathsTrivial(UnorderedGroup path, TokenAnalysisPaths prefix, boolean shortcutEndlessLoops) {
private TokenAnalysisPaths getTokenPathsTrivial(UnorderedGroup path, TokenAnalysisPaths prefix) {
TokenAnalysisPaths result;
TokenAnalysisPaths current;
@ -272,53 +272,36 @@ public class TokenAnalysis {
do {
current = TokenAnalysisPaths.empty(result);
current.resetProgress();
int currentPosition = current.getMinPosition();
for (AbstractElement element : path.getElements()) {
current = current.merge(getTokenPaths(element, result, false, false, shortcutEndlessLoops));
current = current.merge(getTokenPaths(element, result, false, false));
}
result.resetProgress();
result = result.merge(current);
if (current.getMinPosition() == currentPosition) {
// endless loop
// current will never be done since there is no progress to the shortest path
if (shortcutEndlessLoops) {
if (!result.hasProgress()) {
// no progress
// abort endless loop
break;
} else {
// there is still some progress done
continue;
}
} else {
throw new TokenAnalysisAbortedException("no progress in loop");
}
}
} while(!current.isDone());
} while(result.hasProgress());
return result;
}
private TokenAnalysisPaths getTokenPathsTrivial(AbstractElement path, TokenAnalysisPaths prefix, boolean shortcutEndlessLoops) {
private TokenAnalysisPaths getTokenPathsTrivial(AbstractElement path, TokenAnalysisPaths prefix) {
return new XtextSwitch<TokenAnalysisPaths>() {
@Override
public TokenAnalysisPaths caseGroup(Group group) {
return getTokenPathsTrivial(group, prefix, shortcutEndlessLoops);
return getTokenPathsTrivial(group, prefix);
};
@Override
public TokenAnalysisPaths caseAlternatives(Alternatives alternatives) {
return getTokenPathsTrivial(alternatives, prefix, shortcutEndlessLoops);
return getTokenPathsTrivial(alternatives, prefix);
};
@Override
public TokenAnalysisPaths caseUnorderedGroup(UnorderedGroup unorderedGroup) {
return getTokenPathsTrivial(unorderedGroup, prefix, shortcutEndlessLoops);
return getTokenPathsTrivial(unorderedGroup, prefix);
};
@Override
public TokenAnalysisPaths caseAssignment(Assignment assignment) {
return getTokenPaths(assignment.getTerminal(), prefix, false, false, shortcutEndlessLoops);
return getTokenPaths(assignment.getTerminal(), prefix, false, false);
};
@Override
public TokenAnalysisPaths caseRuleCall(RuleCall call) {
@ -326,7 +309,7 @@ public class TokenAnalysis {
isParserRuleCall(call) ||
isEnumRuleCall(call)
) {
return getTokenPaths(call.getRule().getAlternatives(), prefix, false, false, shortcutEndlessLoops);
return getTokenPaths(call.getRule().getAlternatives(), prefix, false, false);
} else {
// probably terminal rule call -> go to default case
return null;
@ -358,7 +341,7 @@ public class TokenAnalysis {
}
private TokenAnalysisPaths getTokenPaths(
AbstractElement path, TokenAnalysisPaths prefix, boolean analyseContext, boolean needsLength, boolean shortcutEndlessLoops
AbstractElement path, TokenAnalysisPaths prefix, boolean analyseContext, boolean needsLength
) {
if (prefix.isDone()) {
return prefix;
@ -373,11 +356,11 @@ public class TokenAnalysis {
}
// use actual cardinality from path
return getTokenPaths(path, path.getCardinality(), prefix, analyseContext, needsLength, shortcutEndlessLoops);
return getTokenPaths(path, path.getCardinality(), prefix, analyseContext, needsLength);
}
private TokenAnalysisPaths getTokenPaths(
AbstractElement path, String cardinality, TokenAnalysisPaths prefix, boolean analyseContext, boolean needsLength, boolean shortcutEndlessLoops
AbstractElement path, String cardinality, TokenAnalysisPaths prefix, boolean analyseContext, boolean needsLength
) {
// analyseContext implies needsLength
@ -409,10 +392,9 @@ public class TokenAnalysis {
boolean loop = isVirtualMultipleCardinality(cardinality);
do {
int currentMinPosition = result.getMinPosition();
result.resetProgress();
TokenAnalysisPaths tokenPaths = getTokenPathsTrivial(path, result, shortcutEndlessLoops);
TokenAnalysisPaths tokenPaths = getTokenPathsTrivial(path, result);
if (tokenPaths.isDone()) {
result = result.merge(tokenPaths);
@ -426,45 +408,21 @@ public class TokenAnalysis {
result = result.merge(tokenPaths);
}
if (loop) {
if (result.getMinPosition() == currentMinPosition) {
// endless loop
// result will never be done since there is no progress to the shortest path
if (shortcutEndlessLoops) {
if (!result.hasProgress()) {
// no progress
// abort endless loop
break;
} else {
// there is still some progress done
continue;
}
} else {
throw new TokenAnalysisAbortedException("no progress in loop");
}
} else {
currentMinPosition = result.getMinPosition();
}
}
} while (loop);
} while (loop && result.hasProgress());
return result;
}
private TokenAnalysisPaths getTokenPaths(AbstractElement path, List<Integer> indexes, boolean analyseContext, boolean needsLength, boolean shortcutEndlessLoops) {
return getTokenPaths(path, new TokenAnalysisPaths(indexes), analyseContext, needsLength, shortcutEndlessLoops);
}
private TokenAnalysisPaths getTokenPathsNoLength(AbstractElement path, List<Integer> indexes) {
return getTokenPaths(path, new TokenAnalysisPaths(indexes), false, false, false);
return getTokenPaths(path, new TokenAnalysisPaths(indexes), false, false);
}
private TokenAnalysisPaths getTokenPathsContext(AbstractElement path, List<Integer> indexes) {
return getTokenPaths(path, new TokenAnalysisPaths(indexes), true, true, false);
return getTokenPaths(path, new TokenAnalysisPaths(indexes), true, true);
}
private TokenAnalysisPaths getTokenPaths(AbstractElement path, String virtualCardinality, List<Integer> indexes, boolean analyseContext, boolean needsLength, boolean shortcutEndlessLoops) {
return getTokenPaths(path, virtualCardinality, new TokenAnalysisPaths(indexes), analyseContext, needsLength, shortcutEndlessLoops);
private TokenAnalysisPaths getTokenPaths(AbstractElement path, String virtualCardinality, List<Integer> indexes, boolean analyseContext, boolean needsLength) {
return getTokenPaths(path, virtualCardinality, new TokenAnalysisPaths(indexes), analyseContext, needsLength);
}
private TokenAnalysisPaths getTokenPathsContextOnly(AbstractElement path, List<Integer> indexes) throws TokenAnalysisAbortedException {
@ -583,7 +541,7 @@ public class TokenAnalysis {
tokenCombinations(indexList -> {
// no context analysis // TODO why?
List<List<Token>> tokenListsForPath = getTokenPaths(element, virtualCardinality, indexList, false, true, false)
List<List<Token>> tokenListsForPath = getTokenPaths(element, virtualCardinality, indexList, false, true)
.getTokenPaths();
List<List<Token>> tokenListForContext = getTokenPathsContextOnly(element, indexList)
.getTokenPaths();
@ -658,7 +616,7 @@ public class TokenAnalysis {
public List<List<AbstractElement>> getAllPossiblePaths(AbstractElement path) {
// token limit + 2 so identity analysis will recognize paths that are identical up to the token limit on the flattened tree
return getTokenPaths(path, range(0, config.getTokenLimit() + 2), false, false, true)
return getTokenPathsNoLength(path, range(0, config.getTokenLimit() + 2))
.getTokenPaths()
.stream()
.map(l -> l.stream()

View file

@ -10,6 +10,7 @@ package org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.pathAnalysis;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.xtext.AbstractElement;
@ -20,7 +21,7 @@ import org.eclipse.xtext.xtext.generator.parser.antlr.hoisting.utils.StreamUtils
* @author overflow - Initial contribution and APILinkedHashSet
*/
public class TokenAnalysisPaths {
private LinkedHashSet<TokenAnalysisPath> tokenPaths = new LinkedHashSet<>();
private Set<TokenAnalysisPath> tokenPaths = new LinkedHashSet<>();
private boolean isEmpty = false;
private boolean hasProgress = false;
@ -60,7 +61,9 @@ public class TokenAnalysisPaths {
public TokenAnalysisPaths merge(TokenAnalysisPaths other) {
if (isEmpty) {
return other;
return other.clone();
} else if (other.isEmpty) {
return this.clone();
} else {
// set hasProgress if other has progress and progress is merged
if (this.tokenPaths.addAll(other.tokenPaths)) {
@ -98,4 +101,11 @@ public class TokenAnalysisPaths {
+ "\n)";
}
}
@Override
public TokenAnalysisPaths clone() {
TokenAnalysisPaths clone = new TokenAnalysisPaths(this);
clone.isEmpty = this.isEmpty;
return clone;
}
}