Skip to content

Commit

Permalink
Merge pull request #2053 from usethesource/recovery/multi-errors
Browse files Browse the repository at this point in the history
Support for parsing input that forms a valid prefix
  • Loading branch information
PieterOlivier authored and toinehartman committed Oct 15, 2024
2 parents 01b8b48 + 1df5ed9 commit 40b9831
Show file tree
Hide file tree
Showing 13 changed files with 286 additions and 255 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ module lang::rascal::tests::concrete::recovery::BasicRecoveryTests

import ParseTree;
import util::ErrorRecovery;
import util::Maybe;

import lang::rascal::tests::concrete::recovery::RecoveryTestSupport;

layout Layout = [\ ]* !>> [\ ];

Expand All @@ -26,32 +27,17 @@ syntax T = ABC End;
syntax ABC = 'a' 'b' 'c';
syntax End = "$";

private Tree parseS(str input, bool visualize=false)
= parser(#S, allowRecovery=true, allowAmbiguity=true)(input, |unknown:///?visualize=<"<visualize>">|);

test bool basicOk() {
return !hasErrors(parseS("a b c $"));
}
test bool basicOk() = checkRecovery(#S, "a b c $", []);

test bool abx() {
Tree t = parseS("a b x $");
return getErrorText(findBestError(t).val) == "x ";
}
test bool abx() = checkRecovery(#S, "a b x $", ["x "]);

test bool axc() {
Tree t = parseS("a x c $");
return getErrorText(findBestError(t).val) == "x c";
}
test bool axc() = checkRecovery(#S, "a x c $", ["x c"]);

test bool ax() {
test bool autoDisambiguation() {
str input = "a x $";

Tree t = parseS(input);
assert size(findAllErrors(t)) == 3;
assert getErrorText(findBestError(t).val) == "x ";
assert checkRecovery(#S, input, ["x "]);

Tree autoDisambiguated = parser(#S, allowRecovery=true, allowAmbiguity=false)(input, |unknown:///|);
assert size(findAllErrors(autoDisambiguated)) == 1;

return getErrorText(findFirstError(autoDisambiguated)) == getErrorText(findBestError(t).val);
return size(findAllErrors(autoDisambiguated)) == 1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ module lang::rascal::tests::concrete::recovery::ListRecoveryTests
import ParseTree;
import util::ErrorRecovery;

import lang::rascal::tests::concrete::recovery::RecoveryTestSupport;

layout Layout = [\ ]* !>> [\ ];

syntax S = T End;
Expand All @@ -29,16 +31,8 @@ Tree parseList(str s, bool visualize=false) {
return parser(#S, allowRecovery=true, allowAmbiguity=true)(s, |unknown:///?visualize=<"<visualize>">|);
}

test bool listOk() {
return !hasErrors(parseList("a b , a b , a b $", visualize=true));
}
test bool listOk() = checkRecovery(#S, "a b , a b , a b $", []);

test bool listTypo() {
Tree t = parseList("a b, a x, ab $", visualize=true);
return hasErrors(t);
}
test bool listTypo() = checkRecovery(#S, "a b, a x, ab $", ["x"]);

test bool listTypoWs() {
Tree t = parseList("a b , a x , a b $", visualize=true);
return hasErrors(t);
}
test bool listTypoWs() = checkRecovery(#S, "a b , a x , a b $", ["x "]);
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@

module lang::rascal::tests::concrete::recovery::NestedRecoveryTests

import ParseTree;
import util::ErrorRecovery;
import util::Maybe;
import lang::rascal::tests::concrete::recovery::RecoveryTestSupport;

layout Layout = [\ ]* !>> [\ ];

Expand All @@ -28,14 +26,6 @@ syntax A = "a";
syntax B = "b" "b";
syntax C = "c";

private Tree parseS(str input, bool visualize=false)
= parser(#S, allowRecovery=true, allowAmbiguity=true)(input, |unknown:///?visualize=<"<visualize>">|);
test bool nestedOk() = checkRecovery(#S, "a b b c", []);

test bool nestedOk() {
return !hasErrors(parseS("a b b c"));
}

test bool nestedTypo() {
Tree t = parseS("a b x c");
return getErrorText(findBestError(t).val) == "x ";
}
test bool nestedTypo() = checkRecovery(#S, "a b x c", ["x "]);
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,12 @@ import lang::pico::\syntax::Main;

import ParseTree;
import util::ErrorRecovery;

import IO;
import String;
import util::Maybe;
import lang::rascal::tests::concrete::recovery::RecoveryTestSupport;

Tree parsePico(str input, bool visualize=false)
= parser(#Program, allowRecovery=true, allowAmbiguity=true)(input, |unknown:///?visualize=<"<visualize>">|);

bool checkError(Tree t, str expectedError) {
str bestError = getErrorText(findBestError(t));
//println("best error: <bestError>, expected: <expectedError>");
return size(bestError) == size(expectedError);
}

test bool picoOk() {
t = parsePico("begin declare input : natural,
test bool picoOk() = checkRecovery(#Program, "begin declare input : natural,
output : natural,
repnr : natural,
rep : natural;
Expand All @@ -48,12 +38,9 @@ test bool picoOk() {
od;
input := input - 1
od
end");
return !hasErrors(t);
}
end", []);

test bool picoTypo() {
t = parsePico("begin declare input : natural,
test bool picoTypo() = checkRecovery(#Program, "begin declare input : natural,
output : natural,
repnr : natural,
rep : natural;
Expand All @@ -68,13 +55,9 @@ test bool picoTypo() {
od;
input := input - 1
od
end");

return checkError(t, "output x rep");
}
end", ["output x rep"]);

test bool picoMissingSemi() {
t = parsePico("begin declare input : natural,
test bool picoMissingSemi() = checkRecovery(#Program, "begin declare input : natural,
output : natural,
repnr : natural,
rep : natural;
Expand All @@ -89,32 +72,24 @@ test bool picoMissingSemi() {
od
input := input - 1
od
end");
return checkError(t, "input := input - 1
od");
}
end", ["input := input - 1
od"]);

test bool picoTypoSmall() {
t = parsePico(
"begin declare;
test bool picoTypoSmall() = checkRecovery(#Program, "begin declare;
while input do
input x= 14;
output := 0
od
end");
end", ["x= 14"]);

return checkError(t, "x= 14");
}

test bool picoMissingSemiSmall() {
t = parsePico(
"begin declare;
test bool picoMissingSemiSmall() = checkRecovery(#Program, "begin declare;
while input do
input := 14
output := 0
od
end");
end", ["output := 0
od"]);

test bool picoEof() = checkRecovery(#Program, "begin declare; input := 0;", ["input := 0;"]);

return checkError(t, "output := 0
od");
}
test bool picoEofError() = checkRecovery(#Program, "begin declare x y; input := 0;", ["x y;", "input := 0;"]);
Original file line number Diff line number Diff line change
Expand Up @@ -21,115 +21,40 @@ import util::ErrorRecovery;
import IO;
import util::Maybe;

bool debugging = false;
import lang::rascal::tests::concrete::recovery::RecoveryTestSupport;

bool debugging = false;

Tree parseRascal(type[&T] t, str input, bool visualize=false) {
Tree result = parser(t, allowRecovery=true, allowAmbiguity=true)(input, |unknown:///?visualize=<"<visualize>">|);
if (debugging) {
list[Tree] errors = findAllErrors(result);
if (errors != []) {
println("Tree has <size(errors)> errors");
for (error <- errors) {
println("- <getErrorText(error)>");
}

println("Best error: <getErrorText(findBestError(result).val)>");
}
}

return result;
}

Tree parseRascal(str input, bool visualize=false) = parseRascal(#start[Module], input, visualize=visualize);

Tree parseFunctionDeclaration(str input, bool visualize=false) = parseRascal(#FunctionDeclaration, input, visualize=visualize);

Tree parseStatement(str input, bool visualize=false) = parseRascal(#Statement, input, visualize=visualize);

test bool rascalOk() {
Tree t = parseRascal("
test bool rascalOk() = checkRecovery(#start[Module], "
module A
int inc(int i) {
return i+1;
}
");
return !hasErrors(t);
}

test bool rascalFunctionDeclarationOk() {
Tree t = parseFunctionDeclaration("void f(){}");
return !hasErrors(t);
}
", []);

test bool rascalFunctionDeclarationOk() = checkRecovery(#FunctionDeclaration, "void f(){}", []);

test bool rascalModuleFollowedBySemi() {
Tree t = parseRascal("
test bool rascalModuleFollowedBySemi() = checkRecovery(#start[Module], "
module A
;
");

// There are a lot of productions in Rascal that have a ; as terminator.
// The parser assumes the user has only entered the ; on one of them,
// so the error list contains them all.
list[Tree] errors = findAllErrors(t);
assert size(errors) == 10;

return getErrorText(findFirstError(t)) == ";";
}
", [";"]);

test bool rascalOperatorTypo() {
Tree t = parseRascal("
test bool rascalOperatorTypo() = checkRecovery(#start[Module], "
module A
int f() = 1 x 1;
");
", ["x 1;"]);

return getErrorText(findFirstError(t)) == "x 1;";
}

test bool rascalIllegalStatement() {
Tree t = parseRascal("module A void f(){a}");
return getErrorText(findFirstError(t)) == "a}";
}

test bool rascalMissingCloseParen() {
Tree t = parseRascal("module A void f({} void g(){}");

assert getErrorText(findFirstError(t)) == "void g(";
assert getErrorText(findBestError(t).val) == "(";

return true;
}
test bool rascalIllegalStatement() = checkRecovery(#start[Module], "module A void f(){a}", ["a}"]);

test bool rascalFunctionDeclarationMissingCloseParen() {
Tree t = parseFunctionDeclaration("void f({} void g() {}");
test bool rascalMissingCloseParen() = checkRecovery(#start[Module], "module A void f({} void g(){}", ["("]);

assert getErrorText(findFirstError(t)) == "void g(";
test bool rascalFunctionDeclarationMissingCloseParen() = checkRecovery(#FunctionDeclaration, "void f({} void g() {}", ["("]);

Tree error = findBestError(t).val;
assert getErrorText(error) == "(";
loc location = getSkipped(error).src;
assert location.begin.column == 16 && location.length == 1;
test bool rascalIfMissingExpr() = checkRecovery(#FunctionDeclaration, "void f(){if(){1;}}", [")"]);

return true;
}

test bool rascalIfMissingExpr() {
Tree t = parseFunctionDeclaration("void f(){if(){1;}}", visualize=false);
return getErrorText(findBestError(t).val) == ")";
}

test bool rascalIfBodyEmpty() {
Tree t = parseRascal("module A void f(){1;} void g(){if(1){}} void h(){1;}");

println("error: <getErrorText(findFirstError(t))>");
assert getErrorText(findBestError(t).val) == "} void h(){1";

return true;
}
test bool rascalIfBodyEmpty() = checkRecovery(#start[Module], "module A void f(){1;} void g(){if(1){}} void h(){1;}", ["} void h(){1"]);

// Not working yet:
/*
Expand Down
Loading

0 comments on commit 40b9831

Please sign in to comment.