[clang-tools-extra] 7d8e274 - [pseudo] Define recovery strategy as grammar extension.

Sam McCall via cfe-commits cfe-commits at lists.llvm.org
Wed Jul 6 06:04:21 PDT 2022


Author: Sam McCall
Date: 2022-07-06T15:03:38+02:00
New Revision: 7d8e2742d958468b900532f1e44610b9b0b29f5e

URL: https://github.com/llvm/llvm-project/commit/7d8e2742d958468b900532f1e44610b9b0b29f5e
DIFF: https://github.com/llvm/llvm-project/commit/7d8e2742d958468b900532f1e44610b9b0b29f5e.diff

LOG: [pseudo] Define recovery strategy as grammar extension.

Differential Revision: https://reviews.llvm.org/D129158

Added: 
    

Modified: 
    clang-tools-extra/pseudo/include/clang-pseudo/Language.h
    clang-tools-extra/pseudo/include/clang-pseudo/grammar/Grammar.h
    clang-tools-extra/pseudo/include/clang-pseudo/grammar/LRGraph.h
    clang-tools-extra/pseudo/include/clang-pseudo/grammar/LRTable.h
    clang-tools-extra/pseudo/lib/GLR.cpp
    clang-tools-extra/pseudo/lib/cli/CLI.cpp
    clang-tools-extra/pseudo/lib/cxx/CXX.cpp
    clang-tools-extra/pseudo/lib/cxx/cxx.bnf
    clang-tools-extra/pseudo/lib/grammar/Grammar.cpp
    clang-tools-extra/pseudo/lib/grammar/GrammarBNF.cpp
    clang-tools-extra/pseudo/lib/grammar/LRGraph.cpp
    clang-tools-extra/pseudo/test/cxx/empty-member-spec.cpp
    clang-tools-extra/pseudo/test/cxx/recovery-init-list.cpp
    clang-tools-extra/pseudo/unittests/GLRTest.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/pseudo/include/clang-pseudo/Language.h b/clang-tools-extra/pseudo/include/clang-pseudo/Language.h
index 8c2fdfaa2852c..24267c429a965 100644
--- a/clang-tools-extra/pseudo/include/clang-pseudo/Language.h
+++ b/clang-tools-extra/pseudo/include/clang-pseudo/Language.h
@@ -9,6 +9,7 @@
 #ifndef CLANG_PSEUDO_GRAMMAR_LANGUAGE_H
 #define CLANG_PSEUDO_GRAMMAR_LANGUAGE_H
 
+#include "clang-pseudo/Token.h"
 #include "clang-pseudo/grammar/Grammar.h"
 #include "clang-pseudo/grammar/LRTable.h"
 
@@ -28,13 +29,25 @@ class LRTable;
 using RuleGuard = llvm::function_ref<bool(
     llvm::ArrayRef<const ForestNode *> RHS, const TokenStream &)>;
 
+// A recovery strategy determines a region of code to skip when parsing fails.
+//
+// For example, given `class-def := CLASS IDENT { body [recover=Brackets] }`,
+// if parsing fails while attempting to parse `body`, we may skip up to the
+// matching `}` and assume everything between was a `body`.
+//
+// The provided index is the token where the skipped region begins.
+// Returns the (excluded) end of the range, or Token::Invalid for no recovery.
+using RecoveryStrategy =
+    llvm::function_ref<Token::Index(Token::Index Start, const TokenStream &)>;
+
 // Specify a language that can be parsed by the pseduoparser.
 struct Language {
   Grammar G;
   LRTable Table;
 
-  // Binding "guard" extension id to a piece of C++ code.
+  // Binding extension ids to corresponding implementations.
   llvm::DenseMap<ExtensionID, RuleGuard> Guards;
+  llvm::DenseMap<ExtensionID, RecoveryStrategy> RecoveryStrategies;
 
   // FIXME: add clang::LangOptions.
   // FIXME: add default start symbols.

diff  --git a/clang-tools-extra/pseudo/include/clang-pseudo/grammar/Grammar.h b/clang-tools-extra/pseudo/include/clang-pseudo/grammar/Grammar.h
index e2957cc2ec71e..ab11a84ebf295 100644
--- a/clang-tools-extra/pseudo/include/clang-pseudo/grammar/Grammar.h
+++ b/clang-tools-extra/pseudo/include/clang-pseudo/grammar/Grammar.h
@@ -85,9 +85,6 @@ inline tok::TokenKind symbolToToken(SymbolID SID) {
 inline constexpr SymbolID tokenSymbol(tok::TokenKind TK) {
   return TokenFlag | static_cast<SymbolID>(TK);
 }
-// Error recovery strategies.
-// FIXME: these should be provided as extensions instead.
-enum class RecoveryStrategy : uint8_t { None, Braces };
 
 // An extension is a piece of native code specific to a grammar that modifies
 // the behavior of annotated rules. One ExtensionID is assigned for each unique
@@ -133,7 +130,7 @@ struct Rule {
   // everything between braces.
   // For now, only a single strategy at a single point is possible.
   uint8_t RecoveryIndex = -1;
-  RecoveryStrategy Recovery = RecoveryStrategy::None;
+  ExtensionID Recovery = 0;
 
   llvm::ArrayRef<SymbolID> seq() const {
     return llvm::ArrayRef<SymbolID>(Sequence, Size);

diff  --git a/clang-tools-extra/pseudo/include/clang-pseudo/grammar/LRGraph.h b/clang-tools-extra/pseudo/include/clang-pseudo/grammar/LRGraph.h
index f5c2cc7a16232..dd9e87c2c172b 100644
--- a/clang-tools-extra/pseudo/include/clang-pseudo/grammar/LRGraph.h
+++ b/clang-tools-extra/pseudo/include/clang-pseudo/grammar/LRGraph.h
@@ -144,7 +144,7 @@ class LRGraph {
   // has a Recovery { Src = S, Strategy=braces, Result=stmt-seq }.
   struct Recovery {
     StateID Src; // The state we are in when encountering the error.
-    RecoveryStrategy Strategy; // Heuristic choosing the tokens to match.
+    ExtensionID Strategy;      // Heuristic choosing the tokens to match.
     SymbolID Result;           // The symbol that is produced.
   };
 

diff  --git a/clang-tools-extra/pseudo/include/clang-pseudo/grammar/LRTable.h b/clang-tools-extra/pseudo/include/clang-pseudo/grammar/LRTable.h
index aac80ad53ffd0..890a5125a1ef4 100644
--- a/clang-tools-extra/pseudo/include/clang-pseudo/grammar/LRTable.h
+++ b/clang-tools-extra/pseudo/include/clang-pseudo/grammar/LRTable.h
@@ -68,7 +68,7 @@ class LRTable {
   static constexpr unsigned StateBits = 13;
 
   struct Recovery {
-    RecoveryStrategy Strategy;
+    ExtensionID Strategy;
     SymbolID Result;
   };
 

diff  --git a/clang-tools-extra/pseudo/lib/GLR.cpp b/clang-tools-extra/pseudo/lib/GLR.cpp
index 2c1b1c09ce693..831271f1b1d4a 100644
--- a/clang-tools-extra/pseudo/lib/GLR.cpp
+++ b/clang-tools-extra/pseudo/lib/GLR.cpp
@@ -27,15 +27,13 @@ namespace clang {
 namespace pseudo {
 namespace {
 
-Token::Index findRecoveryEndpoint(RecoveryStrategy Strategy,
-                                  const GSS::Node *RecoveryNode,
-                                  const TokenStream &Tokens) {
-  assert(Strategy == RecoveryStrategy::Braces);
-  const ForestNode *LBrace = RecoveryNode->Payload;
-  assert(LBrace->kind() == ForestNode::Terminal &&
-         LBrace->symbol() == tokenSymbol(tok::l_brace));
-  if (const Token *RBrace = Tokens.tokens()[LBrace->startTokenIndex()].pair())
-    return Tokens.index(*RBrace);
+Token::Index findRecoveryEndpoint(ExtensionID Strategy, Token::Index Begin,
+                                  const TokenStream &Tokens,
+                                  const Language &Lang) {
+  assert(Strategy != 0);
+  assert(Begin > 0);
+  if (auto S = Lang.RecoveryStrategies.lookup(Strategy))
+    return S(Begin, Tokens);
   return Token::Invalid;
 }
 
@@ -55,7 +53,7 @@ void glrRecover(llvm::ArrayRef<const GSS::Node *> OldHeads,
     // The nonterminal which will be created in order to recover.
     SymbolID Symbol;
     // The heuristic used to choose the bounds of the nonterminal to recover.
-    RecoveryStrategy Strategy;
+    ExtensionID Strategy;
 
     // The GSS head where we are expecting the recovered nonterminal.
     const GSS::Node *RecoveryNode;
@@ -142,8 +140,8 @@ void glrRecover(llvm::ArrayRef<const GSS::Node *> OldHeads,
     if (RecoveryRange && Option.Position < RecoveryRange->Begin)
       break;
 
-    auto End =
-        findRecoveryEndpoint(Option.Strategy, Option.RecoveryNode, Params.Code);
+    auto End = findRecoveryEndpoint(Option.Strategy, Option.Position,
+                                    Params.Code, Lang);
     // Recovery may not take the parse backwards.
     if (End == Token::Invalid || End < TokenIndex)
       continue;

diff  --git a/clang-tools-extra/pseudo/lib/cli/CLI.cpp b/clang-tools-extra/pseudo/lib/cli/CLI.cpp
index cbc4b04545317..5c7c3b6c827ea 100644
--- a/clang-tools-extra/pseudo/lib/cli/CLI.cpp
+++ b/clang-tools-extra/pseudo/lib/cli/CLI.cpp
@@ -40,8 +40,12 @@ const Language &getLanguageFromFlags() {
     for (const auto &Diag : Diags)
       llvm::errs() << Diag << "\n";
     auto Table = LRTable::buildSLR(G);
-    return new Language{std::move(G), std::move(Table),
-                        llvm::DenseMap<ExtensionID, RuleGuard>()};
+    return new Language{
+        std::move(G),
+        std::move(Table),
+        llvm::DenseMap<ExtensionID, RuleGuard>(),
+        llvm::DenseMap<ExtensionID, RecoveryStrategy>(),
+    };
   }();
   return *Lang;
 }

diff  --git a/clang-tools-extra/pseudo/lib/cxx/CXX.cpp b/clang-tools-extra/pseudo/lib/cxx/CXX.cpp
index 7fb00f03752fc..e8de6821a05f5 100644
--- a/clang-tools-extra/pseudo/lib/cxx/CXX.cpp
+++ b/clang-tools-extra/pseudo/lib/cxx/CXX.cpp
@@ -35,9 +35,28 @@ bool guardFinal(llvm::ArrayRef<const ForestNode *> RHS,
 }
 
 llvm::DenseMap<ExtensionID, RuleGuard> buildGuards() {
-  return llvm::DenseMap<ExtensionID, RuleGuard>(
-      {{(ExtensionID)Extension::Override, guardOverride},
-       {(ExtensionID)Extension::Final, guardFinal}});
+  return {
+      {(ExtensionID)Extension::Override, guardOverride},
+      {(ExtensionID)Extension::Final, guardFinal},
+  };
+}
+
+Token::Index recoverBrackets(Token::Index Begin, const TokenStream &Tokens) {
+  assert(Begin > 0);
+  const Token &Left = Tokens.tokens()[Begin - 1];
+  assert(Left.Kind == tok::l_brace || Left.Kind == tok::l_paren ||
+         Left.Kind == tok::l_square);
+  if (const Token *Right = Left.pair()) {
+    assert(Tokens.index(*Right) > Begin);
+    return Tokens.index(*Right);
+  }
+  return Token::Invalid;
+}
+
+llvm::DenseMap<ExtensionID, RecoveryStrategy> buildRecoveryStrategies() {
+  return {
+      {(ExtensionID)Extension::Brackets, recoverBrackets},
+  };
 }
 
 } // namespace
@@ -48,8 +67,12 @@ const Language &getLanguage() {
     auto G = Grammar::parseBNF(CXXBNF, Diags);
     assert(Diags.empty());
     LRTable Table = LRTable::buildSLR(G);
-    const Language *PL =
-        new Language{std::move(G), std::move(Table), buildGuards()};
+    const Language *PL = new Language{
+        std::move(G),
+        std::move(Table),
+        buildGuards(),
+        buildRecoveryStrategies(),
+    };
     return *PL;
   }();
   return CXXLanguage;

diff  --git a/clang-tools-extra/pseudo/lib/cxx/cxx.bnf b/clang-tools-extra/pseudo/lib/cxx/cxx.bnf
index 3938fa9dcaf57..204b816632649 100644
--- a/clang-tools-extra/pseudo/lib/cxx/cxx.bnf
+++ b/clang-tools-extra/pseudo/lib/cxx/cxx.bnf
@@ -287,7 +287,7 @@ labeled-statement := IDENTIFIER : statement
 labeled-statement := CASE constant-expression : statement
 labeled-statement := DEFAULT : statement
 expression-statement := expression_opt ;
-compound-statement := { statement-seq_opt }
+compound-statement := { statement-seq_opt [recover=Brackets] }
 statement-seq := statement
 statement-seq := statement-seq statement
 selection-statement := IF CONSTEXPR_opt ( init-statement_opt condition ) statement
@@ -458,7 +458,9 @@ initializer-clause := assignment-expression
 initializer-clause := braced-init-list
 #! Allow mixed designated/non-designated init-list.
 # This is standard C, and accepted by clang and others as an extension.
-braced-init-list := { initializer-list ,_opt }
+# FIXME: Decouple recovery from is-there-a-trailing-comma!
+braced-init-list := { initializer-list [recover=Brackets] }
+braced-init-list := { initializer-list , }
 braced-init-list := { }
 initializer-list := initializer-list-item
 initializer-list := initializer-list , initializer-list-item
@@ -533,7 +535,7 @@ global-module-fragment := module-keyword ; declaration-seq_opt
 private-module-fragment := module-keyword : PRIVATE ; declaration-seq_opt
 
 # gram.class
-class-specifier := class-head { member-specification_opt }
+class-specifier := class-head { member-specification_opt [recover=Brackets] }
 class-head := class-key class-head-name class-virt-specifier_opt base-clause_opt
 class-head := class-key base-clause_opt
 class-head-name := nested-name-specifier_opt class-name

diff  --git a/clang-tools-extra/pseudo/lib/grammar/Grammar.cpp b/clang-tools-extra/pseudo/lib/grammar/Grammar.cpp
index dc4d95820264c..149d97f28a778 100644
--- a/clang-tools-extra/pseudo/lib/grammar/Grammar.cpp
+++ b/clang-tools-extra/pseudo/lib/grammar/Grammar.cpp
@@ -62,7 +62,7 @@ std::string Grammar::dumpRule(RuleID RID) const {
   for (unsigned I = 0; I < R.Size; ++I) {
     OS << " " << symbolName(R.Sequence[I]);
     if (R.RecoveryIndex == I)
-      OS << " [recover=" << static_cast<unsigned>(R.Recovery) << "]";
+      OS << " [recover=" << T->AttributeValues[R.Recovery] << "]";
   }
   if (R.Guard)
     OS << " [guard=" << T->AttributeValues[R.Guard] << "]";

diff  --git a/clang-tools-extra/pseudo/lib/grammar/GrammarBNF.cpp b/clang-tools-extra/pseudo/lib/grammar/GrammarBNF.cpp
index 281c08681c929..5e1fe9b6a0086 100644
--- a/clang-tools-extra/pseudo/lib/grammar/GrammarBNF.cpp
+++ b/clang-tools-extra/pseudo/lib/grammar/GrammarBNF.cpp
@@ -106,17 +106,6 @@ class GrammarBuilder {
 
     assert(T->Rules.size() < (1 << RuleBits) &&
            "Too many rules to fit in RuleID bits!");
-    // Wherever RHS contains { foo }, mark foo for brace-recovery.
-    // FIXME: this should be grammar annotations instead.
-    for (auto &Rule : T->Rules) {
-      for (unsigned I = 2; I < Rule.Size; ++I)
-        if (Rule.Sequence[I] == tokenSymbol(tok::r_brace) &&
-            Rule.Sequence[I - 2] == tokenSymbol(tok::l_brace) &&
-            !isToken(Rule.Sequence[I - 1])) {
-          Rule.Recovery = RecoveryStrategy::Braces;
-          Rule.RecoveryIndex = I - 1;
-        }
-    }
     const auto &SymbolOrder = getTopologicalOrder(T.get());
     llvm::stable_sort(
         T->Rules, [&SymbolOrder](const Rule &Left, const Rule &Right) {
@@ -266,13 +255,18 @@ class GrammarBuilder {
              "Didn't find the attribute in AttrValues!");
       return It - T.AttributeValues.begin();
     };
-    for (const auto &KV : Spec.Sequence.back().Attributes) {
-      if (KV.first == "guard") {
-        R.Guard = LookupExtensionID(KV.second);
-        continue;
+    for (unsigned I = 0; I < Spec.Sequence.size(); ++I) {
+      for (const auto &KV : Spec.Sequence[I].Attributes) {
+        if (KV.first == "guard") {
+          R.Guard = LookupExtensionID(KV.second);
+        } else if (KV.first == "recover") {
+          R.Recovery = LookupExtensionID(KV.second);
+          R.RecoveryIndex = I;
+        } else {
+          Diagnostics.push_back(
+              llvm::formatv("Unknown attribute '{0}'", KV.first).str());
+        }
       }
-      Diagnostics.push_back(
-          llvm::formatv("Unknown attribute '{0}'", KV.first).str());
     }
   }
 

diff  --git a/clang-tools-extra/pseudo/lib/grammar/LRGraph.cpp b/clang-tools-extra/pseudo/lib/grammar/LRGraph.cpp
index 290a4f32f576d..3aa665ef04d94 100644
--- a/clang-tools-extra/pseudo/lib/grammar/LRGraph.cpp
+++ b/clang-tools-extra/pseudo/lib/grammar/LRGraph.cpp
@@ -120,9 +120,9 @@ nextAvailableKernelItems(const State &S, const Grammar &G) {
   return Results;
 }
 
-std::vector<std::pair<RecoveryStrategy, SymbolID>>
+std::vector<std::pair<ExtensionID, SymbolID>>
 availableRecovery(const State &S, const Grammar &G) {
-  std::vector<std::pair<RecoveryStrategy, SymbolID>> Result;
+  std::vector<std::pair<ExtensionID, SymbolID>> Result;
   for (const Item &I : S.Items) {
     const auto &Rule = G.lookupRule(I.rule());
     if (I.dot() != Rule.RecoveryIndex)
@@ -196,8 +196,7 @@ LRGraph LRGraph::buildLR0(const Grammar &G) {
       Edges.push_back({Src, Dst, Label});
     }
 
-    void insertRecovery(StateID Src, RecoveryStrategy Strategy,
-                        SymbolID Result) {
+    void insertRecovery(StateID Src, ExtensionID Strategy, SymbolID Result) {
       Recoveries.push_back({Src, Strategy, Result});
     }
 

diff  --git a/clang-tools-extra/pseudo/test/cxx/empty-member-spec.cpp b/clang-tools-extra/pseudo/test/cxx/empty-member-spec.cpp
index 071790e8d0a20..4d15835565b7e 100644
--- a/clang-tools-extra/pseudo/test/cxx/empty-member-spec.cpp
+++ b/clang-tools-extra/pseudo/test/cxx/empty-member-spec.cpp
@@ -2,7 +2,7 @@
 class Foo {
 public:
 };
-// CHECK:      decl-specifier-seq~class-specifier := class-head { member-specification [recover=1] }
+// CHECK:      decl-specifier-seq~class-specifier := class-head { member-specification [recover=Brackets] }
 // CHECK-NEXT: ├─class-head := class-key class-head-name
 // CHECK-NEXT: │ ├─class-key~CLASS := tok[0]
 // CHECK-NEXT: │ └─class-head-name~IDENTIFIER := tok[1]

diff  --git a/clang-tools-extra/pseudo/test/cxx/recovery-init-list.cpp b/clang-tools-extra/pseudo/test/cxx/recovery-init-list.cpp
index a8bbe88dadd88..283c53d78128d 100644
--- a/clang-tools-extra/pseudo/test/cxx/recovery-init-list.cpp
+++ b/clang-tools-extra/pseudo/test/cxx/recovery-init-list.cpp
@@ -1,4 +1,4 @@
-// RUN: clang-pseudo -grammar=%cxx-bnf-file -source=%s --print-forest | FileCheck %s
+// RUN: clang-pseudo -grammar=cxx -source=%s --print-forest | FileCheck %s
 auto x = { complete garbage };
 // CHECK:      translation-unit~simple-declaration
 // CHECK-NEXT: ├─decl-specifier-seq~AUTO := tok[0]

diff  --git a/clang-tools-extra/pseudo/unittests/GLRTest.cpp b/clang-tools-extra/pseudo/unittests/GLRTest.cpp
index c0f3e3dd46a04..05419c1a35273 100644
--- a/clang-tools-extra/pseudo/unittests/GLRTest.cpp
+++ b/clang-tools-extra/pseudo/unittests/GLRTest.cpp
@@ -48,6 +48,17 @@ parents(llvm::ArrayRef<const GSS::Node *> Parents) {
                            testing::UnorderedElementsAreArray(Parents));
 }
 
+Token::Index recoverBraces(Token::Index Begin, const TokenStream &Code) {
+  EXPECT_GT(Begin, 0u);
+  const Token &Left = Code.tokens()[Begin - 1];
+  EXPECT_EQ(Left.Kind, tok::l_brace);
+  if (const auto* Right = Left.pair()) {
+    EXPECT_EQ(Right->Kind, tok::r_brace);
+    return Code.index(*Right);
+  }
+  return Token::Invalid;
+}
+
 class GLRTest : public ::testing::Test {
 public:
   void build(llvm::StringRef GrammarBNF) {
@@ -375,11 +386,12 @@ TEST_F(GLRTest, Recover) {
   //    0--1({)--2(?)
   //  After recovering a `word` at state 1:
   //    0--3(word)  // 3 is goto(1, word)
-  buildGrammar({"word"}, {});
+  buildGrammar({"word", "top"}, {"top := { word [recover=Braces] }"});
   LRTable::Builder B(TestLang.G);
   B.Transition[{StateID{1}, id("word")}] = StateID{3};
-  B.Recoveries.push_back({StateID{1}, {RecoveryStrategy::Braces, id("word")}});
+  B.Recoveries.push_back({StateID{1}, {extensionID("Braces"), id("word")}});
   TestLang.Table = std::move(B).build();
+  TestLang.RecoveryStrategies.try_emplace(extensionID("Braces"), recoverBraces);
 
   auto *LBrace = &Arena.createTerminal(tok::l_brace, 0);
   auto *Question1 = &Arena.createTerminal(tok::question, 1);
@@ -418,11 +430,12 @@ TEST_F(GLRTest, RecoverRightmost) {
   //    0--1({)--1({)--1({)
   //  After recovering a `block` at inside the second braces:
   //    0--1({)--2(body)  // 2 is goto(1, body)
-  buildGrammar({"body"}, {});
+  buildGrammar({"body", "top"}, {"top := { body [recover=Braces] }"});
   LRTable::Builder B(TestLang.G);
   B.Transition[{StateID{1}, id("body")}] = StateID{2};
-  B.Recoveries.push_back({StateID{1}, {RecoveryStrategy::Braces, id("body")}});
+  B.Recoveries.push_back({StateID{1}, {extensionID("Braces"), id("body")}});
   TestLang.Table = std::move(B).build();
+  TestLang.RecoveryStrategies.try_emplace(extensionID("Braces"), recoverBraces);
 
   clang::LangOptions LOptions;
   // Innermost brace is unmatched, to test fallback to next brace.
@@ -455,13 +468,17 @@ TEST_F(GLRTest, RecoverAlternatives) {
   //  After recovering either `word` or `number` inside the braces:
   //    0--1({)--2(word)   // 2 is goto(1, word)
   //          └--3(number) // 3 is goto(1, number)
-  buildGrammar({"number", "word"}, {});
+  buildGrammar({"number", "word", "top"},
+               {
+                   "top := { number [recover=Braces] }",
+                   "top := { word [recover=Braces] }",
+               });
   LRTable::Builder B(TestLang.G);
   B.Transition[{StateID{1}, id("number")}] = StateID{2};
   B.Transition[{StateID{1}, id("word")}] = StateID{3};
-  B.Recoveries.push_back(
-      {StateID{1}, {RecoveryStrategy::Braces, id("number")}});
-  B.Recoveries.push_back({StateID{1}, {RecoveryStrategy::Braces, id("word")}});
+  B.Recoveries.push_back({StateID{1}, {extensionID("Braces"), id("number")}});
+  B.Recoveries.push_back({StateID{1}, {extensionID("Braces"), id("word")}});
+  TestLang.RecoveryStrategies.try_emplace(extensionID("Braces"), recoverBraces);
   TestLang.Table = std::move(B).build();
   auto *LBrace = &Arena.createTerminal(tok::l_brace, 0);
   const auto *Root = GSStack.addNode(0, nullptr, {});
@@ -560,11 +577,12 @@ TEST_F(GLRTest, RecoveryEndToEnd) {
   build(R"bnf(
     _ := block
 
-    block := { block }
-    block := { numbers }
+    block := { block [recover=Braces] }
+    block := { numbers [recover=Braces] }
     numbers := NUMERIC_CONSTANT NUMERIC_CONSTANT
   )bnf");
   TestLang.Table = LRTable::buildSLR(TestLang.G);
+  TestLang.RecoveryStrategies.try_emplace(extensionID("Braces"), recoverBraces);
   clang::LangOptions LOptions;
   TokenStream Tokens = cook(lex("{ { 42 ? } }", LOptions), LOptions);
   pairBrackets(Tokens);
@@ -572,14 +590,14 @@ TEST_F(GLRTest, RecoveryEndToEnd) {
   const ForestNode &Parsed =
       glrParse({Tokens, Arena, GSStack}, id("block"), TestLang);
   EXPECT_EQ(Parsed.dumpRecursive(TestLang.G),
-            "[  0, end) block := { block [recover=1] }\n"
+            "[  0, end) block := { block [recover=Braces] }\n"
             "[  0,   1) ├─{ := tok[0]\n"
             "[  1,   5) ├─block := <ambiguous>\n"
-            "[  1,   5) │ ├─block := { block [recover=1] }\n"
+            "[  1,   5) │ ├─block := { block [recover=Braces] }\n"
             "[  1,   2) │ │ ├─{ := tok[1]\n"
             "[  2,   4) │ │ ├─block := <opaque>\n"
             "[  4,   5) │ │ └─} := tok[4]\n"
-            "[  1,   5) │ └─block := { numbers [recover=1] }\n"
+            "[  1,   5) │ └─block := { numbers [recover=Braces] }\n"
             "[  1,   2) │   ├─{ := tok[1]\n"
             "[  2,   4) │   ├─numbers := <opaque>\n"
             "[  4,   5) │   └─} := tok[4]\n"


        


More information about the cfe-commits mailing list