");
+ }
+ return true;
+}
+
diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/MultiErrorPico.rsc b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/MultiErrorPico.rsc
new file mode 100644
index 00000000000..2a81b5f6af3
--- /dev/null
+++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/MultiErrorPico.rsc
@@ -0,0 +1,13 @@
+module lang::rascal::tests::concrete::recovery::bugs::MultiErrorPico
+
+import lang::pico::\syntax::Main;
+import lang::rascal::tests::concrete::recovery::RecoveryTestSupport;
+
+bool multiErrorPico() {
+ return checkRecovery(#start[Program], "begin
+ declare;
+ i := #1;
+ j := #2;
+ k := 3
+end" , ["#1", "#2"]);
+}
diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/MultiErrorPicoInput.pico b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/MultiErrorPicoInput.pico
new file mode 100644
index 00000000000..a7393e9f705
--- /dev/null
+++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/MultiErrorPicoInput.pico
@@ -0,0 +1,6 @@
+begin
+ declare;
+ i := #1;
+ j := #2;
+ k := 3
+end
\ No newline at end of file
diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/MultiProdRecovery.rsc b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/MultiProdRecovery.rsc
new file mode 100644
index 00000000000..ed6d77fe645
--- /dev/null
+++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/recovery/bugs/MultiProdRecovery.rsc
@@ -0,0 +1,20 @@
+module lang::rascal::tests::concrete::recovery::bugs::MultiProdRecovery
+
+import lang::rascal::tests::concrete::recovery::RecoveryTestSupport;
+
+layout Layout = [\ ]* !>> [\ ];
+
+syntax Prog = Stat*;
+
+syntax Stat
+ = Expr ";"
+ | "{" Stat* "}";
+
+syntax Expr
+ = Expr "+" Expr
+ | Expr "-" Expr
+ | "e";
+
+test bool multiProdOk() = checkRecovery(#Prog, "{ e + e; }", []);
+
+test bool multiProdOperatorError() = checkRecovery(#Prog, "{ e * e; }", ["* "]);
diff --git a/src/org/rascalmpl/parser/gtd/SGTDBF.java b/src/org/rascalmpl/parser/gtd/SGTDBF.java
index fea89a68099..2c249b0cc3f 100755
--- a/src/org/rascalmpl/parser/gtd/SGTDBF.java
+++ b/src/org/rascalmpl/parser/gtd/SGTDBF.java
@@ -32,6 +32,7 @@
import org.rascalmpl.parser.gtd.stack.AbstractStackNode;
import org.rascalmpl.parser.gtd.stack.EpsilonStackNode;
import org.rascalmpl.parser.gtd.stack.NonTerminalStackNode;
+import org.rascalmpl.parser.gtd.stack.SkippingStackNode;
import org.rascalmpl.parser.gtd.stack.edge.EdgesSet;
import org.rascalmpl.parser.gtd.stack.filter.ICompletionFilter;
import org.rascalmpl.parser.gtd.stack.filter.IEnterFilter;
@@ -1347,12 +1348,10 @@ protected AbstractNode parse(AbstractStackNode startNode, URI inputURI, int[]
initTime();
try {
-
if(invoked){
throw new RuntimeException("Can only invoke 'parse' once.");
}
-
invoked = true;
// Initialize.
@@ -1395,6 +1394,7 @@ protected AbstractNode parse(AbstractStackNode
startNode, URI inputURI, int[]
debugListener.shifting(location, input, positionStore);
}
+
// Reduce-expand loop.
do {
debugListener.iterating();
@@ -1409,11 +1409,15 @@ protected AbstractNode parse(AbstractStackNode
startNode, URI inputURI, int[]
shiftedLevel = true;
+ if (onlyRecoveredStacksLeft() && attemptRecovery()) {
+ continue;
+ }
+
if (!findStacksToReduce()) {
- if(location == input.length){
+ if(location == input.length) {
EdgesSet
startNodeEdgesSet = startNode.getIncomingEdges();
int resultStoreId = getResultStoreId(startNode.getId());
- if(startNodeEdgesSet != null && startNodeEdgesSet.getLastVisitedLevel(resultStoreId) == input.length){
+ if(startNodeEdgesSet != null && startNodeEdgesSet.getLastVisitedLevel(resultStoreId) == input.length) {
result = startNodeEdgesSet.getLastResult(resultStoreId); // Success.
break;
}
@@ -1464,6 +1468,28 @@ private void initTime() {
timestamp = System.nanoTime();
}
+ private boolean onlyRecoveredStacksLeft() {
+ int recoveredStacksFound = 0;
+
+ for (int i=0; i, AbstractNode> todoList = todoLists[i];
+ if (todoList != null) {
+ int size = todoList.getSize();
+ for (int j=0; j 50) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return recoveredStacksFound > 0;
+ }
+
private void checkTime(String msg) {
long newStamp = System.nanoTime();
long duration = newStamp - timestamp;
diff --git a/src/org/rascalmpl/parser/uptr/recovery/CaseInsensitiveLiteralMatcher.java b/src/org/rascalmpl/parser/uptr/recovery/CaseInsensitiveLiteralMatcher.java
index f1dda955e94..c72e1fa1387 100644
--- a/src/org/rascalmpl/parser/uptr/recovery/CaseInsensitiveLiteralMatcher.java
+++ b/src/org/rascalmpl/parser/uptr/recovery/CaseInsensitiveLiteralMatcher.java
@@ -46,10 +46,11 @@ public CaseInsensitiveLiteralMatcher(int[][] ciLiteral) {
}
@Override
- public MatchResult findMatch(int[] input, int startLocation) {
+ public MatchResult findMatch(int[] input, int startLocation, int maxLength) {
int length = chars.length;
- for (int start=startLocation; start < input.length - length; start++) {
+ int limit = Math.min(startLocation + maxLength - length, input.length - length + 1);
+ for (int start=startLocation; start < limit; start++) {
boolean matches = true;
for (int i=0; i, AbstractNode> reviveStac
// For now we ignore unmatchable leaf nodes and filtered nodes. At some point we might use those to
// improve error recovery.
-
+
ArrayList> failedNodes = new ArrayList<>();
collectUnexpandableNodes(unexpandableNodes, failedNodes);
collectUnmatchableMidProductionNodes(location, unmatchableMidProductionNodes, failedNodes);
@@ -81,13 +82,15 @@ private DoubleArrayList, AbstractNode> reviveNod
DoubleArrayList, ArrayList> recoveryNodes) {
DoubleArrayList, AbstractNode> recoveredNodes = new DoubleArrayList<>();
+ Set> skippedIds = new HashSet<>();
+
// Sort nodes by start location
- recoveryNodes
+ recoveryNodes
.sort((e1, e2) -> Integer.compare(e2.getLeft().getStartLocation(), e1.getLeft().getStartLocation()));
if (VISUALIZE_RECOVERY_NODES) {
- ParseStateVisualizer visualizer = new ParseStateVisualizer("Recovery");
- visualizer.visualizeRecoveryNodes(recoveryNodes);
+ ParseStateVisualizer visualizer = new ParseStateVisualizer("Recovery");
+ visualizer.visualizeRecoveryNodes(recoveryNodes);
}
for (int i = 0; i, AbstractNode> reviveNod
for (int j = prods.size() - 1; j >= 0; --j) {
IConstructor prod = prods.get(j);
+ IConstructor type = ProductionAdapter.getType(prod);
+
List> skippingNodes =
findSkippingNodes(input, location, recoveryNode, prod, startLocation);
for (SkippingStackNode skippingNode : skippingNodes) {
+ int skipLength = skippingNode.getLength();
+
+ if (!skippedIds.add(Triple.ofNonNull(startLocation, type, skipLength))) {
+ // Do not add this skipped node if a node with the same startLocation, type, and skipLength has been added already
+ continue;
+ }
+
AbstractStackNode continuer =
new RecoveryPointStackNode<>(stackNodeIdDispenser.dispenseId(), prod, recoveryNode);
@@ -142,19 +154,11 @@ private List> findSkippingNodes(int[] input, int
return nodes; // No other nodes would be useful
}
- // Try to find whitespace to skip to
- // This often creates hopeless recovery attempts, but it might help in some cases.
- // Further experimentation should quantify this statement.
- /*
- * result = SkippingStackNode.createResultUntilCharClass(WHITESPACE, input, startLocation, prod,
- * dot); if (result != null) { nodes.add(new SkippingStackNode<>(stackNodeIdDispenser.dispenseId(),
- * prod, result, startLocation)); }
- */
-
// Find the last token of this production and skip until after that
List endMatchers = findEndMatchers(recoveryNode);
for (InputMatcher endMatcher : endMatchers) {
- MatchResult endMatch = endMatcher.findMatch(input, startLocation);
+ // For now take a very large (basically unlimited) "max match length", experiment with smaller limit later
+ MatchResult endMatch = endMatcher.findMatch(input, startLocation, Integer.MAX_VALUE/2);
if (endMatch != null) {
result = SkippingStackNode.createResultUntilChar(uri, input, startLocation, endMatch.getEnd());
nodes.add(new SkippingStackNode<>(stackNodeIdDispenser.dispenseId(), prod, result, startLocation));
@@ -164,7 +168,8 @@ private List> findSkippingNodes(int[] input, int
// Find the first token of the next production and skip until before that
List nextMatchers = findNextMatchers(recoveryNode);
for (InputMatcher nextMatcher : nextMatchers) {
- MatchResult nextMatch = nextMatcher.findMatch(input, startLocation+1);
+ // For now take a very large (basically unlimited) "max match length", experiment with smaller limit later
+ MatchResult nextMatch = nextMatcher.findMatch(input, startLocation+1, Integer.MAX_VALUE/2);
if (nextMatch != null) {
result = SkippingStackNode.createResultUntilChar(uri, input, startLocation, nextMatch.getStart());
nodes.add(new SkippingStackNode<>(stackNodeIdDispenser.dispenseId(), prod, result, startLocation));
@@ -280,9 +285,7 @@ public Void visit(LiteralStackNode literal) {
return null;
}
-
@Override
-
public Void visit(CaseInsensitiveLiteralStackNode literal) {
matchers.add(new CaseInsensitiveLiteralMatcher(literal.getLiteral()));
return null;
@@ -396,7 +399,7 @@ private static void collectUnmatchableMidProductionNodes(int location,
AbstractStackNode failedNode =
unmatchableMidProductionNodes.getSecond(i).getCleanCopy(location); // Clone it to prevent by-reference
// updates of the static version
-
+
// Merge the information on the predecessors into the failed node.
for(int j = failedNodePredecessors.size() - 1; j >= 0; --j) {
AbstractStackNode predecessor = failedNodePredecessors.getFirst(j);