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 @@ -147,6 +147,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,59 @@ Node *FirstChild = nullptr; }; +/// A list of Elements separated or terminated by a fixed token. +/// +/// This type models the following grammar construct: +/// delimited-list(element, delimiter, termination, canBeEmpty) +class List : public Tree { +public: + template struct ElementAndDelimiter { + Element *element; + Leaf *delimiter; + }; + + enum class TerminationKind { + Terminated, + MaybeTerminated, + Separated, + }; + + using Tree::Tree; + /// 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(); + + /// Whether this list can be empty in syntactically and semantically correct + /// code. + /// + /// This list may be empty when the source code has errors 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 @@ -152,6 +152,10 @@ return OS << "TemplateKeyword"; 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 @@ -268,3 +268,110 @@ } 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("There are no subclasses of List, thus " + "getDelimiterTokenKind() cannot be called"); +} + +syntax::List::TerminationKind syntax::List::getTerminationKind() { + llvm_unreachable("There are no subclasses of List, thus getTerminationKind() " + "cannot be called"); +} + +bool syntax::List::canBeEmpty() { + llvm_unreachable( + "There are no subclasses of List, thus canBeEmpty() cannot be called"); +}