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(); path.resetProgress();
// shortcut endless loops instead of throwing exception // 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) { if (!path.isDone() && element != null) {
boolean _considerCardinalities = considerCardinalities; boolean _considerCardinalities = considerCardinalities;
@ -238,11 +238,11 @@ public class TokenAnalysis {
return result; return result;
} }
private TokenAnalysisPaths getTokenPathsTrivial(Group path, TokenAnalysisPaths prefix, boolean shortcutEndlessLoops) { private TokenAnalysisPaths getTokenPathsTrivial(Group path, TokenAnalysisPaths prefix) {
TokenAnalysisPaths result = new TokenAnalysisPaths(prefix); TokenAnalysisPaths result = new TokenAnalysisPaths(prefix);
for(AbstractElement element : path.getElements()) { for(AbstractElement element : path.getElements()) {
result = getTokenPaths(element, result, false, false, shortcutEndlessLoops); result = getTokenPaths(element, result, false, false);
if (result.isDone()) { if (result.isDone()) {
break; break;
} }
@ -250,16 +250,16 @@ public class TokenAnalysis {
return result; return result;
} }
private TokenAnalysisPaths getTokenPathsTrivial(Alternatives path, TokenAnalysisPaths prefix, boolean shortcutEndlessLoops) { private TokenAnalysisPaths getTokenPathsTrivial(Alternatives path, TokenAnalysisPaths prefix) {
TokenAnalysisPaths result = TokenAnalysisPaths.empty(prefix); TokenAnalysisPaths result = TokenAnalysisPaths.empty(prefix);
for(AbstractElement element : path.getElements()) { for(AbstractElement element : path.getElements()) {
result = result.merge(getTokenPaths(element, prefix, false, false, shortcutEndlessLoops)); result = result.merge(getTokenPaths(element, prefix, false, false));
} }
return result; return result;
} }
private TokenAnalysisPaths getTokenPathsTrivial(UnorderedGroup path, TokenAnalysisPaths prefix, boolean shortcutEndlessLoops) { private TokenAnalysisPaths getTokenPathsTrivial(UnorderedGroup path, TokenAnalysisPaths prefix) {
TokenAnalysisPaths result; TokenAnalysisPaths result;
TokenAnalysisPaths current; TokenAnalysisPaths current;
@ -272,53 +272,36 @@ public class TokenAnalysis {
do { do {
current = TokenAnalysisPaths.empty(result); current = TokenAnalysisPaths.empty(result);
current.resetProgress(); current.resetProgress();
int currentPosition = current.getMinPosition();
for (AbstractElement element : path.getElements()) { 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.resetProgress();
result = result.merge(current); result = result.merge(current);
if (current.getMinPosition() == currentPosition) { } while(result.hasProgress());
// 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());
return result; return result;
} }
private TokenAnalysisPaths getTokenPathsTrivial(AbstractElement path, TokenAnalysisPaths prefix, boolean shortcutEndlessLoops) { private TokenAnalysisPaths getTokenPathsTrivial(AbstractElement path, TokenAnalysisPaths prefix) {
return new XtextSwitch<TokenAnalysisPaths>() { return new XtextSwitch<TokenAnalysisPaths>() {
@Override @Override
public TokenAnalysisPaths caseGroup(Group group) { public TokenAnalysisPaths caseGroup(Group group) {
return getTokenPathsTrivial(group, prefix, shortcutEndlessLoops); return getTokenPathsTrivial(group, prefix);
}; };
@Override @Override
public TokenAnalysisPaths caseAlternatives(Alternatives alternatives) { public TokenAnalysisPaths caseAlternatives(Alternatives alternatives) {
return getTokenPathsTrivial(alternatives, prefix, shortcutEndlessLoops); return getTokenPathsTrivial(alternatives, prefix);
}; };
@Override @Override
public TokenAnalysisPaths caseUnorderedGroup(UnorderedGroup unorderedGroup) { public TokenAnalysisPaths caseUnorderedGroup(UnorderedGroup unorderedGroup) {
return getTokenPathsTrivial(unorderedGroup, prefix, shortcutEndlessLoops); return getTokenPathsTrivial(unorderedGroup, prefix);
}; };
@Override @Override
public TokenAnalysisPaths caseAssignment(Assignment assignment) { public TokenAnalysisPaths caseAssignment(Assignment assignment) {
return getTokenPaths(assignment.getTerminal(), prefix, false, false, shortcutEndlessLoops); return getTokenPaths(assignment.getTerminal(), prefix, false, false);
}; };
@Override @Override
public TokenAnalysisPaths caseRuleCall(RuleCall call) { public TokenAnalysisPaths caseRuleCall(RuleCall call) {
@ -326,7 +309,7 @@ public class TokenAnalysis {
isParserRuleCall(call) || isParserRuleCall(call) ||
isEnumRuleCall(call) isEnumRuleCall(call)
) { ) {
return getTokenPaths(call.getRule().getAlternatives(), prefix, false, false, shortcutEndlessLoops); return getTokenPaths(call.getRule().getAlternatives(), prefix, false, false);
} else { } else {
// probably terminal rule call -> go to default case // probably terminal rule call -> go to default case
return null; return null;
@ -358,7 +341,7 @@ public class TokenAnalysis {
} }
private TokenAnalysisPaths getTokenPaths( private TokenAnalysisPaths getTokenPaths(
AbstractElement path, TokenAnalysisPaths prefix, boolean analyseContext, boolean needsLength, boolean shortcutEndlessLoops AbstractElement path, TokenAnalysisPaths prefix, boolean analyseContext, boolean needsLength
) { ) {
if (prefix.isDone()) { if (prefix.isDone()) {
return prefix; return prefix;
@ -373,11 +356,11 @@ public class TokenAnalysis {
} }
// use actual cardinality from path // 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( 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 // analyseContext implies needsLength
@ -409,10 +392,9 @@ public class TokenAnalysis {
boolean loop = isVirtualMultipleCardinality(cardinality); boolean loop = isVirtualMultipleCardinality(cardinality);
do { do {
int currentMinPosition = result.getMinPosition();
result.resetProgress(); result.resetProgress();
TokenAnalysisPaths tokenPaths = getTokenPathsTrivial(path, result, shortcutEndlessLoops); TokenAnalysisPaths tokenPaths = getTokenPathsTrivial(path, result);
if (tokenPaths.isDone()) { if (tokenPaths.isDone()) {
result = result.merge(tokenPaths); result = result.merge(tokenPaths);
@ -426,45 +408,21 @@ public class TokenAnalysis {
result = result.merge(tokenPaths); result = result.merge(tokenPaths);
} }
if (loop) { } while (loop && result.hasProgress());
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);
return result; 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) { 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) { 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) { private TokenAnalysisPaths getTokenPaths(AbstractElement path, String virtualCardinality, List<Integer> indexes, boolean analyseContext, boolean needsLength) {
return getTokenPaths(path, virtualCardinality, new TokenAnalysisPaths(indexes), analyseContext, needsLength, shortcutEndlessLoops); return getTokenPaths(path, virtualCardinality, new TokenAnalysisPaths(indexes), analyseContext, needsLength);
} }
private TokenAnalysisPaths getTokenPathsContextOnly(AbstractElement path, List<Integer> indexes) throws TokenAnalysisAbortedException { private TokenAnalysisPaths getTokenPathsContextOnly(AbstractElement path, List<Integer> indexes) throws TokenAnalysisAbortedException {
@ -583,7 +541,7 @@ public class TokenAnalysis {
tokenCombinations(indexList -> { tokenCombinations(indexList -> {
// no context analysis // TODO why? // 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(); .getTokenPaths();
List<List<Token>> tokenListForContext = getTokenPathsContextOnly(element, indexList) List<List<Token>> tokenListForContext = getTokenPathsContextOnly(element, indexList)
.getTokenPaths(); .getTokenPaths();
@ -658,7 +616,7 @@ public class TokenAnalysis {
public List<List<AbstractElement>> getAllPossiblePaths(AbstractElement path) { 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 // 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() .getTokenPaths()
.stream() .stream()
.map(l -> l.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.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.eclipse.xtext.AbstractElement; 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 * @author overflow - Initial contribution and APILinkedHashSet
*/ */
public class TokenAnalysisPaths { public class TokenAnalysisPaths {
private LinkedHashSet<TokenAnalysisPath> tokenPaths = new LinkedHashSet<>(); private Set<TokenAnalysisPath> tokenPaths = new LinkedHashSet<>();
private boolean isEmpty = false; private boolean isEmpty = false;
private boolean hasProgress = false; private boolean hasProgress = false;
@ -60,7 +61,9 @@ public class TokenAnalysisPaths {
public TokenAnalysisPaths merge(TokenAnalysisPaths other) { public TokenAnalysisPaths merge(TokenAnalysisPaths other) {
if (isEmpty) { if (isEmpty) {
return other; return other.clone();
} else if (other.isEmpty) {
return this.clone();
} else { } else {
// set hasProgress if other has progress and progress is merged // set hasProgress if other has progress and progress is merged
if (this.tokenPaths.addAll(other.tokenPaths)) { if (this.tokenPaths.addAll(other.tokenPaths)) {
@ -98,4 +101,11 @@ public class TokenAnalysisPaths {
+ "\n)"; + "\n)";
} }
} }
@Override
public TokenAnalysisPaths clone() {
TokenAnalysisPaths clone = new TokenAnalysisPaths(this);
clone.isEmpty = this.isEmpty;
return clone;
}
} }