diff --git a/clang/include/clang/Tooling/Syntax/Nodes.h b/clang/include/clang/Tooling/Syntax/Nodes.h --- a/clang/include/clang/Tooling/Syntax/Nodes.h +++ b/clang/include/clang/Tooling/Syntax/Nodes.h @@ -142,6 +142,8 @@ /// statement, e.g. loop body for while, for, etc; inner statement for case, /// default, etc. BodyStatement, + List_element, + List_delimiter, // Roles specific to particular node kinds. OperatorExpression_operatorToken, diff --git a/clang/include/clang/Tooling/Syntax/Tree.h b/clang/include/clang/Tooling/Syntax/Tree.h --- a/clang/include/clang/Tooling/Syntax/Tree.h +++ b/clang/include/clang/Tooling/Syntax/Tree.h @@ -191,6 +191,68 @@ Node *FirstChild = nullptr; }; +/// A Tree that represents a syntactic list of elements. +/// +/// We try to model with this type the artificial grammar-construct: +/// delimited-list(element, delimiter, termination, canBeEmpty) +/// +/// For example, from C++ [dcl.decl]: +/// init-declarator-list: +/// init-declarator +/// init-declarator-list , init-declarator +/// May be mapped to: +/// delimited-list(init-declarator, ',', separated, !canBeEmpty) +/// +/// Thus the `InitDeclaratorList` syntax node would inherit from `List`, with: +/// getTerminationKind() returning `Separated` +/// canBeEmpty() returning `true` +/// getDelimiterTokenKind() returning `,` +class List : public Tree { + template struct ElementAndDelimiter { + Element *element; + Leaf *delimiter; + }; + + enum class TerminationKind { + Terminated, + MaybeTerminated, + Separated, + }; + + /// Returns the elements and corresponding delimiters. Missing elements + /// and delimiters are represented as null pointers. + /// + /// For example, in a separated list: + /// "a, b, c" <=> [("a", ","), ("b", ","), ("c", null)] + /// "a, , c" <=> [("a", ","), (null, ","), ("c", ",)] + /// "a, b," <=> [("a", ","), ("b", ","), (null, null)] + /// + /// In a terminated or maybe-terminated list: + /// "a, b," <=> [("a", ","), ("b", ",")] + std::vector> getElementsAsNodesAndDelimiters(); + + /// Returns the elements of the list. Missing elements are represented + /// as null pointers in the same way as in the return value of + /// `getElementsAsNodesAndDelimiters()`. + std::vector getElementsAsNodes(); + + // These can't be implemented with the information we have! + + /// Returns the appropriate delimiter for this list. + /// + /// Useful for discovering the correct delimiter to use when adding + /// elements to empty or one-element lists. + clang::tok::TokenKind getDelimiterTokenKind(); + + TerminationKind getTerminationKind(); + + /// Return whether *under valid code* the list can be empty. + /// + /// If the underlying source code is not expected to be valid, then the list + /// may be empty even if canBeEmpty() returns false. + bool canBeEmpty(); +}; + } // namespace syntax } // namespace clang diff --git a/clang/lib/Tooling/Syntax/Nodes.cpp b/clang/lib/Tooling/Syntax/Nodes.cpp --- a/clang/lib/Tooling/Syntax/Nodes.cpp +++ b/clang/lib/Tooling/Syntax/Nodes.cpp @@ -144,6 +144,10 @@ return OS << "ExternKeyword"; case syntax::NodeRole::BodyStatement: return OS << "BodyStatement"; + case syntax::NodeRole::List_element: + return OS << "List_element"; + case syntax::NodeRole::List_delimiter: + return OS << "List_delimiter"; case syntax::NodeRole::CaseStatement_value: return OS << "CaseStatement_value"; case syntax::NodeRole::IfStatement_thenStatement: diff --git a/clang/lib/Tooling/Syntax/Tree.cpp b/clang/lib/Tooling/Syntax/Tree.cpp --- a/clang/lib/Tooling/Syntax/Tree.cpp +++ b/clang/lib/Tooling/Syntax/Tree.cpp @@ -270,3 +270,107 @@ } return nullptr; } + +std::vector> +syntax::List::getElementsAsNodesAndDelimiters() { + if (!firstChild()) + return {}; + + auto children = std::vector>(); + syntax::Node *elementWithoutDelimiter = nullptr; + for (auto *C = firstChild(); C; C = C->nextSibling()) { + switch (C->role()) { + case syntax::NodeRole::List_element: { + if (elementWithoutDelimiter) { + children.push_back({elementWithoutDelimiter, nullptr}); + } + elementWithoutDelimiter = C; + break; + } + case syntax::NodeRole::List_delimiter: { + children.push_back({elementWithoutDelimiter, cast(C)}); + elementWithoutDelimiter = nullptr; + break; + } + default: + llvm_unreachable( + "A list can have only elements and delimiters as children."); + } + } + + switch (getTerminationKind()) { + case syntax::List::TerminationKind::Separated: { + children.push_back({elementWithoutDelimiter, nullptr}); + break; + } + case syntax::List::TerminationKind::Terminated: + case syntax::List::TerminationKind::MaybeTerminated: { + if (elementWithoutDelimiter) { + children.push_back({elementWithoutDelimiter, nullptr}); + } + break; + } + } + + return children; +} + +// Almost the same implementation of `getElementsAsNodesAndDelimiters` but +// ignoring delimiters +std::vector syntax::List::getElementsAsNodes() { + if (!firstChild()) + return {}; + + auto children = std::vector(); + syntax::Node *elementWithoutDelimiter = nullptr; + for (auto *C = firstChild(); C; C = C->nextSibling()) { + switch (C->role()) { + case syntax::NodeRole::List_element: { + if (elementWithoutDelimiter) { + children.push_back(elementWithoutDelimiter); + } + elementWithoutDelimiter = C; + break; + } + case syntax::NodeRole::List_delimiter: { + children.push_back(elementWithoutDelimiter); + elementWithoutDelimiter = nullptr; + break; + } + default: + llvm_unreachable("A list has only elements or delimiters."); + } + } + + switch (getTerminationKind()) { + case syntax::List::TerminationKind::Separated: { + children.push_back(elementWithoutDelimiter); + break; + } + case syntax::List::TerminationKind::Terminated: + case syntax::List::TerminationKind::MaybeTerminated: { + if (elementWithoutDelimiter) { + children.push_back(elementWithoutDelimiter); + } + break; + } + } + + return children; +} + +// The methods below can't be implemented without information about the derived +// list. These methods will be implemented by switching on the derived list's +// `NodeKind` + +clang::tok::TokenKind syntax::List::getDelimiterTokenKind() { + llvm_unreachable("A list can have only elements and delimiters as children."); +} + +syntax::List::TerminationKind syntax::List::getTerminationKind() { + llvm_unreachable("A list can have only elements and delimiters as children."); +} + +bool syntax::List::canBeEmpty() { + llvm_unreachable("A list can have only elements and delimiters as children."); +}