| // Copyright 2017-2020 The Verible Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "common/text/text_structure.h" | |
| #include <cstddef> | |
| #include <iterator> | |
| #include <map> | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/memory/memory.h" | |
| #include "absl/status/status.h" | |
| #include "absl/strings/string_view.h" | |
| #include "common/strings/line_column_map.h" | |
| #include "common/text/concrete_syntax_tree.h" | |
| #include "common/text/symbol.h" | |
| #include "common/text/text_structure_test_utils.h" | |
| #include "common/text/token_info.h" | |
| #include "common/text/token_stream_view.h" | |
| #include "common/text/tree_builder_test_util.h" | |
| #include "common/text/tree_compare.h" | |
| #include "common/util/iterator_range.h" | |
| #include "common/util/logging.h" | |
| #include "common/util/range.h" | |
| #include "common/util/value_saver.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #undef EXPECT_OK | |
| #define EXPECT_OK(value) EXPECT_TRUE((value).ok()) | |
| namespace verible { | |
| using ::testing::IsEmpty; | |
| using ::testing::IsNull; | |
| using ::testing::SizeIs; | |
| // Test constructor and initial state. | |
| TEST(TextStructureViewCtorTest, InitializeContents) { | |
| const char* inputs[] = {"", "<ANY>", "hello world", "foo\nbar\n"}; | |
| for (const auto* input : inputs) { | |
| TextStructureView test_view(input); | |
| EXPECT_EQ(test_view.Contents(), input); | |
| EXPECT_THAT(test_view.TokenStream(), IsEmpty()); | |
| EXPECT_THAT(test_view.GetTokenStreamView(), IsEmpty()); | |
| EXPECT_THAT(test_view.SyntaxTree(), IsNull()); | |
| EXPECT_OK(test_view.InternalConsistencyCheck()); | |
| } | |
| } | |
| // Test that filtering nothing works. | |
| TEST(FilterTokensTest, EmptyTokens) { | |
| TextStructureView test_view("blah"); | |
| EXPECT_THAT(test_view.GetTokenStreamView(), IsEmpty()); | |
| test_view.FilterTokens([](const TokenInfo& token) { return true; }); | |
| EXPECT_THAT(test_view.GetTokenStreamView(), IsEmpty()); | |
| } | |
| // Create a one-token token stream and syntax tree. | |
| void OneTokenTextStructureView(TextStructureView* view) { | |
| TokenInfo token(1, view->Contents()); | |
| view->MutableTokenStream().push_back(token); | |
| view->MutableTokenStreamView().push_back(view->TokenStream().begin()); | |
| view->MutableSyntaxTree() = Leaf(token); | |
| } | |
| // Create a two-token token stream, no syntax tree. | |
| void MultiTokenTextStructureViewNoTree(TextStructureView* view) { | |
| const auto contents = view->Contents(); | |
| CHECK_GE(contents.length(), 5); | |
| auto& stream = view->MutableTokenStream(); | |
| for (int i = 0; i < 5; ++i) { // Populate with 5 single-char tokens. | |
| stream.push_back(TokenInfo(i + 1, contents.substr(i, 1))); | |
| } | |
| auto& stream_view = view->MutableTokenStreamView(); | |
| // Populate view with 2 tokens. | |
| stream_view.push_back(stream.begin() + 1); | |
| stream_view.push_back(stream.begin() + 3); | |
| } | |
| // Test that filtering can keep tokens. | |
| TEST(FilterTokensTest, OneTokenKept) { | |
| const absl::string_view text = "blah"; | |
| TextStructureView test_view(text); | |
| // Pretend to lex and parse text. | |
| OneTokenTextStructureView(&test_view); | |
| EXPECT_THAT(test_view.GetTokenStreamView(), SizeIs(1)); | |
| test_view.FilterTokens([](const TokenInfo& token) { return true; }); | |
| EXPECT_THAT(test_view.GetTokenStreamView(), SizeIs(1)); | |
| } | |
| // Test that filtering can remove tokens. | |
| TEST(FilterTokensTest, OneTokenRemoved) { | |
| const absl::string_view text = "blah"; | |
| TextStructureView test_view(text); | |
| // Pretend to lex and parse text. | |
| OneTokenTextStructureView(&test_view); | |
| EXPECT_THAT(test_view.GetTokenStreamView(), SizeIs(1)); | |
| test_view.FilterTokens([](const TokenInfo& token) { return false; }); | |
| EXPECT_THAT(test_view.GetTokenStreamView(), IsEmpty()); | |
| } | |
| // Test that mutating nothing works. | |
| TEST(MutateTokensTest, EmptyTokensNoOp) { | |
| TextStructureView test_view(""); | |
| test_view.MutateTokens([](TokenInfo*) {}); | |
| EXPECT_THAT(test_view.TokenStream(), IsEmpty()); | |
| EXPECT_THAT(test_view.GetTokenStreamView(), IsEmpty()); | |
| EXPECT_THAT(test_view.SyntaxTree(), IsNull()); | |
| } | |
| // Test that a copy of writeable iterators to tokens matches const iterators. | |
| TEST(TokenStreamReferenceViewTest, ShiftRight) { | |
| TextStructureView test_view("hello"); | |
| MultiTokenTextStructureViewNoTree(&test_view); | |
| auto iterators = test_view.MakeTokenStreamReferenceView(); | |
| const auto& stream_view = test_view.GetTokenStreamView(); | |
| auto view_iter = stream_view.begin(); | |
| for (auto iter : iterators) { | |
| EXPECT_EQ(iter, *view_iter); // write-iterators same as read-iterators | |
| ++view_iter; | |
| } | |
| } | |
| // Test that EOFToken is properly constructed to the correct range. | |
| TEST(EOFTokenTest, TokenRange) { | |
| const absl::string_view kTestCases[] = { | |
| "", | |
| "\n", | |
| "foobar", | |
| "foobar\n", | |
| }; | |
| for (auto test : kTestCases) { | |
| TextStructureView test_view(test); | |
| TokenInfo token(test_view.EOFToken()); | |
| EXPECT_EQ(token.token_enum(), verible::TK_EOF); | |
| EXPECT_TRUE(token.text().empty()); | |
| EXPECT_EQ(token.text().begin(), test.end()); | |
| EXPECT_EQ(token.left(test_view.Contents()), test.length()); | |
| } | |
| } | |
| // Test that string_views can point to memory owned in new location, | |
| // where new location is a superstring of the original. | |
| TEST(RebaseTokensToSuperstringTest, NewOwner) { | |
| const absl::string_view superstring = "abcdefgh"; | |
| const absl::string_view substring = "cdef"; | |
| EXPECT_FALSE(IsSubRange(substring, superstring)); | |
| TextStructureView test_view(substring); | |
| OneTokenTextStructureView(&test_view); | |
| const TokenInfo expect_pre(1, substring); | |
| EXPECT_EQ(test_view.TokenStream().front(), expect_pre); | |
| EXPECT_TRUE(EqualTrees(test_view.SyntaxTree().get(), Leaf(expect_pre).get())); | |
| test_view.RebaseTokensToSuperstring(superstring, substring, 2); | |
| const TokenInfo expect_post(1, superstring.substr(2, 4)); | |
| EXPECT_EQ(test_view.TokenStream().front(), expect_post); | |
| EXPECT_TRUE( | |
| EqualTrees(test_view.SyntaxTree().get(), Leaf(expect_post).get())); | |
| EXPECT_TRUE(IsSubRange(test_view.TokenStream().front().text(), superstring)); | |
| } | |
| // Helper class for testing Token range methods. | |
| class TokenRangeTest : public ::testing::Test, public TextStructureTokenized { | |
| public: | |
| static constexpr int kSpace = 2; | |
| static constexpr int kNewline = 4; | |
| TokenRangeTest() | |
| : TextStructureTokenized( | |
| {{TokenInfo(3, "hello"), TokenInfo(1, ","), TokenInfo(kSpace, " "), | |
| TokenInfo(3, "world"), TokenInfo(kNewline, "\n")}, | |
| {TokenInfo(kNewline, "\n")}, | |
| {TokenInfo(3, "hello"), TokenInfo(1, ","), TokenInfo(kSpace, " "), | |
| TokenInfo(3, "world"), TokenInfo(kNewline, "\n")}}) {} | |
| }; | |
| // Checks for consistency between beginning-of-line offset map and the | |
| // beginning-of-line token iterator map. | |
| TEST_F(TokenRangeTest, CalculateFirstTokensPerLineTest) { | |
| const auto& line_token_map = data_.GetLineTokenMap(); | |
| const auto& line_column_map = data_.GetLineColumnMap(); | |
| // There is always one more entry in the line_token_map that points to end(). | |
| EXPECT_EQ(line_column_map.GetBeginningOfLineOffsets().size() + 1, | |
| line_token_map.size()); | |
| const auto& tokens = data_.TokenStream(); | |
| EXPECT_EQ(line_token_map.front(), tokens.begin()); | |
| EXPECT_EQ(line_token_map.back(), tokens.end()); | |
| EXPECT_EQ(line_token_map[1], tokens.begin() + 5); | |
| EXPECT_EQ(line_token_map[2], tokens.begin() + 6); | |
| EXPECT_EQ(line_token_map[3], tokens.begin() + 11); | |
| } | |
| TEST_F(TokenRangeTest, GetRangeOfTokenVerifyAllRangesExclusive) { | |
| // Bulk testing: let's see that we constantly progress in emitted ranges. | |
| LineColumnRange previous{{0, 0}, {0, 0}}; | |
| for (const TokenInfo& token : data_.TokenStream()) { | |
| LineColumnRange token_range = data_.GetRangeForToken(token); | |
| EXPECT_EQ(token_range.start, previous.end); | |
| EXPECT_LT(previous.end, token_range.end); | |
| EXPECT_GE(token_range.end, token_range.start); | |
| previous = token_range; | |
| } | |
| } | |
| TEST_F(TokenRangeTest, GetRangeOfTokenEofTokenAcceptedUniversally) { | |
| // For the EOF token, the returned range should automatically be relative | |
| // to the TextView no matter where it comes from. | |
| EXPECT_EQ(data_.GetRangeForToken(data_.EOFToken()), | |
| data_.GetRangeForToken(TokenInfo::EOFToken())); | |
| } | |
| TEST_F(TokenRangeTest, GetRangeForTokenOrText) { | |
| const TokenInfo& token = data_.FindTokenAt({0, 7}); | |
| EXPECT_EQ(token.text(), "world"); | |
| { // Extract from token | |
| const LineColumnRange range = data_.GetRangeForToken(token); | |
| EXPECT_EQ(range.start.line, 0); | |
| EXPECT_EQ(range.start.column, 7); | |
| } | |
| { // Extract from token text | |
| const LineColumnRange range = data_.GetRangeForText(token.text()); | |
| EXPECT_EQ(range.start.line, 0); | |
| EXPECT_EQ(range.start.column, 7); | |
| } | |
| { // Entire text range | |
| const LineColumnRange range = data_.GetRangeForText(data_.Contents()); | |
| EXPECT_EQ(range.start.line, 0); | |
| EXPECT_EQ(range.start.column, 0); | |
| EXPECT_EQ(range.end.line, data_.Lines().size() - 1); | |
| EXPECT_EQ(range.end.column, data_.Lines().back().length()); | |
| } | |
| } | |
| TEST_F(TokenRangeTest, FindTokenAtPosition) { | |
| EXPECT_EQ(data_.FindTokenAt({0, 0}).text(), "hello"); | |
| EXPECT_EQ(data_.FindTokenAt({0, 4}).text(), "hello"); | |
| EXPECT_EQ(data_.FindTokenAt({0, 5}).text(), ","); | |
| EXPECT_EQ(data_.FindTokenAt({0, 6}).text(), " "); | |
| EXPECT_EQ(data_.FindTokenAt({0, 7}).text(), "world"); | |
| // Out of range column: return last token in line. | |
| EXPECT_EQ(data_.FindTokenAt({0, 200}).text(), "\n"); | |
| // Graceful handling of values out of range: EOF | |
| EXPECT_TRUE(data_.FindTokenAt({-1, -1}).isEOF()); | |
| EXPECT_TRUE(data_.FindTokenAt({42, 7}).isEOF()); | |
| } | |
| // Checks that when lower == upper, returned range is empty. | |
| TEST_F(TokenRangeTest, TokenRangeSpanningOffsetsEmpty) { | |
| const size_t test_offsets[] = {0, 1, 4, 12, 18, 22, 26}; | |
| for (const auto offset : test_offsets) { | |
| const auto token_range = data_.TokenRangeSpanningOffsets(offset, offset); | |
| EXPECT_EQ(token_range.begin(), token_range.end()); | |
| } | |
| } | |
| struct TokenRangeTestCase { | |
| size_t left_offset, right_offset; | |
| size_t left_index, right_index; | |
| }; | |
| // Checks that token ranges span the given offsets. | |
| TEST_F(TokenRangeTest, TokenRangeSpanningOffsetsNonEmpty) { | |
| const TokenRangeTestCase test_cases[] = { | |
| {0, 1, 0, 1}, // noformat | |
| {0, 5, 0, 1}, // noformat | |
| {0, 6, 0, 2}, // noformat | |
| {0, 14, 0, 6}, // noformat | |
| {0, 15, 0, 7}, // noformat | |
| {0, 27, 0, 11}, // noformat | |
| {1, 27, 1, 11}, // noformat | |
| {5, 27, 1, 11}, // noformat | |
| {6, 27, 2, 11}, // noformat | |
| {21, 27, 9, 11}, // noformat | |
| {22, 27, 10, 11}, // noformat | |
| {26, 27, 10, 11}, // noformat | |
| {9, 12, 4, 4}, // empty, does not span a whole token | |
| {9, 19, 4, 7}, | |
| }; | |
| for (const auto& test_case : test_cases) { | |
| const auto token_range = data_.TokenRangeSpanningOffsets( | |
| test_case.left_offset, test_case.right_offset); | |
| EXPECT_EQ(std::distance(data_.TokenStream().cbegin(), token_range.begin()), | |
| test_case.left_index); | |
| EXPECT_EQ(std::distance(data_.TokenStream().cbegin(), token_range.end()), | |
| test_case.right_index); | |
| } | |
| } | |
| struct TokenLineTestCase { | |
| size_t lineno; | |
| size_t left_index, right_index; | |
| }; | |
| // Verify the ranges of tokens spanned per line, and that they end with '\n'. | |
| TEST_F(TokenRangeTest, TokenRangeOnLine) { | |
| const TokenLineTestCase test_cases[] = { | |
| {0, 0, 5}, // The first entry always points to the first token at [0]. | |
| {1, 5, 6}, // empty line that only contains newline | |
| {2, 6, 11}, | |
| {3, 11, 11}, // There is no line[3], this represents an empty range. | |
| }; | |
| for (const auto& test_case : test_cases) { | |
| const auto token_range = data_.TokenRangeOnLine(test_case.lineno); | |
| EXPECT_EQ(std::distance(data_.TokenStream().cbegin(), token_range.begin()), | |
| test_case.left_index); | |
| EXPECT_EQ(std::distance(data_.TokenStream().cbegin(), token_range.end()), | |
| test_case.right_index); | |
| // All lines end with newline in this example. | |
| EXPECT_EQ((token_range.end() - 1)->text(), "\n"); | |
| } | |
| } | |
| // Testing select public methods of TextStructureView. | |
| class TextStructureViewPublicTest : public ::testing::Test, | |
| public TextStructureView { | |
| public: | |
| TextStructureViewPublicTest() : TextStructureView("hello, world") { | |
| // Manually lex and parse into token stream and syntax tree. | |
| // Token enums must be nonzero to not be considered EOF. | |
| tokens_.push_back(TokenInfo(3, contents_.substr(0, 5))); // "hello" | |
| tokens_.push_back(TokenInfo(1, contents_.substr(5, 1))); // "," | |
| tokens_.push_back(TokenInfo(2, contents_.substr(6, 1))); // " " | |
| tokens_.push_back(TokenInfo(3, contents_.substr(7, 5))); // "world" | |
| // Stream view will omit the space token. | |
| tokens_view_.push_back(tokens_.begin()); | |
| tokens_view_.push_back(tokens_.begin() + 1); | |
| tokens_view_.push_back(tokens_.begin() + 3); | |
| // Make syntax tree from stream view. | |
| syntax_tree_ = Node(Leaf(tokens_[0]), Leaf(tokens_[1]), Leaf(tokens_[3])); | |
| // Calculate map of beginning-of-line tokens. | |
| CalculateFirstTokensPerLine(); | |
| } | |
| }; | |
| // Testing select protected methods of TextStructureView. | |
| class TextStructureViewInternalsTest : public TextStructureViewPublicTest { | |
| public: | |
| // Clean-up to prevent consistency checks from failing after this internal | |
| // modifications. This is only appropriate for tests on private or protected | |
| // methods; public methods should always leave the structure in a consistent | |
| // state. | |
| ~TextStructureViewInternalsTest() override { Clear(); } | |
| }; | |
| // Test that whole tree is returned with offset 0. | |
| TEST_F(TextStructureViewInternalsTest, TrimSyntaxTreeWholeTree) { | |
| TrimSyntaxTree(0, contents_.length()); | |
| const auto expect_tree = | |
| Node(Leaf(tokens_[0]), Leaf(tokens_[1]), Leaf(tokens_[3])); | |
| EXPECT_TRUE(EqualTrees(syntax_tree_.get(), expect_tree.get())); | |
| } | |
| // Test that partial tree is returned with nonzero offset. | |
| TEST_F(TextStructureViewInternalsTest, TrimSyntaxTreeOneLeaf) { | |
| TrimSyntaxTree(1, contents_.length()); | |
| const auto expect_tree = Leaf(tokens_[1]); | |
| EXPECT_TRUE(EqualTrees(syntax_tree_.get(), expect_tree.get())); | |
| } | |
| // Test that partial tree is returned with nonzero offset, last leaf. | |
| TEST_F(TextStructureViewInternalsTest, TrimSyntaxTreeLastLeaf) { | |
| TrimSyntaxTree(7, contents_.length()); | |
| const auto expect_tree = Leaf(tokens_[3]); | |
| EXPECT_TRUE(EqualTrees(syntax_tree_.get(), expect_tree.get())); | |
| } | |
| // Test that trimming tokens changes nothing when range spans whole contents. | |
| TEST_F(TextStructureViewInternalsTest, TrimTokensToSubstringKeepEverything) { | |
| TrimTokensToSubstring(0, contents_.length()); | |
| EXPECT_THAT(tokens_, SizeIs(5)); | |
| EXPECT_THAT(tokens_view_, SizeIs(3)); | |
| const TokenInfo& back(tokens_.back()); | |
| EXPECT_TRUE(back.isEOF()); | |
| EXPECT_TRUE( | |
| BoundsEqual(back.text(), make_range(contents_.end(), contents_.end()))); | |
| } | |
| // Test that trimming tokens changes nothing when range is empty. | |
| TEST_F(TextStructureViewInternalsTest, TrimTokensToSubstringKeepNothing) { | |
| TrimTokensToSubstring(5, 5); // an empty range | |
| EXPECT_THAT(tokens_, IsEmpty()); | |
| EXPECT_THAT(tokens_view_, IsEmpty()); | |
| } | |
| // Test that trimming tokens can reduce to a subset. | |
| TEST_F(TextStructureViewInternalsTest, TrimTokensToSubstringKeepSubset) { | |
| TrimTokensToSubstring(3, 12); | |
| EXPECT_THAT(tokens_, SizeIs(4)); | |
| EXPECT_THAT(tokens_view_, SizeIs(2)); | |
| const TokenInfo& back(tokens_.back()); | |
| EXPECT_TRUE(back.isEOF()); | |
| EXPECT_TRUE(BoundsEqual( | |
| back.text(), make_range(contents_.begin() + 12, contents_.begin() + 12))); | |
| } | |
| // Test that trimming tokens can reduce to one leaf. | |
| TEST_F(TextStructureViewInternalsTest, TrimTokensToSubstringKeepLeaf) { | |
| TrimTokensToSubstring(0, 6); | |
| EXPECT_THAT(tokens_, SizeIs(3)); | |
| EXPECT_THAT(tokens_view_, SizeIs(2)); | |
| const TokenInfo& back(tokens_.back()); | |
| EXPECT_TRUE(back.isEOF()); | |
| EXPECT_TRUE(BoundsEqual( | |
| back.text(), make_range(contents_.begin() + 6, contents_.begin() + 6))); | |
| } | |
| // Test trimming the contents to narrower range of text. | |
| TEST_F(TextStructureViewInternalsTest, TrimContents) { | |
| TrimContents(2, 9); | |
| EXPECT_EQ(contents_, "llo, worl"); | |
| TrimContents(1, 6); | |
| EXPECT_EQ(contents_, "lo, wo"); | |
| } | |
| // Test that a span of the whole contents preserves everything. | |
| TEST_F(TextStructureViewPublicTest, FocusOnSubtreeSpanningSubstringWholeTree) { | |
| const auto expect_tree = | |
| Node(Leaf(tokens_[0]), Leaf(tokens_[1]), Leaf(tokens_[3])); | |
| FocusOnSubtreeSpanningSubstring(0, contents_.length()); | |
| EXPECT_THAT(tokens_, SizeIs(5)); | |
| EXPECT_THAT(tokens_view_, SizeIs(3)); | |
| EXPECT_TRUE(EqualTrees(syntax_tree_.get(), expect_tree.get())); | |
| EXPECT_TRUE(tokens_.back().isEOF()); | |
| } | |
| // Test that a substring range yields a subtree. | |
| TEST_F(TextStructureViewPublicTest, FocusOnSubtreeSpanningSubstringFirstLeaf) { | |
| const auto expect_tree = Leaf(tokens_[0]); | |
| FocusOnSubtreeSpanningSubstring(0, tokens_[0].text().length()); | |
| EXPECT_THAT(tokens_, SizeIs(2)); | |
| EXPECT_THAT(tokens_view_, SizeIs(1)); | |
| EXPECT_TRUE(EqualTrees(syntax_tree_.get(), expect_tree.get())); | |
| EXPECT_TRUE(tokens_.back().isEOF()); | |
| } | |
| // Test that ExpandSubtrees on an empty map changes nothing. | |
| TEST_F(TextStructureViewPublicTest, ExpandSubtreesEmpty) { | |
| const auto expect_tree = | |
| Node(Leaf(tokens_[0]), Leaf(tokens_[1]), Leaf(tokens_[3])); | |
| TextStructureView::NodeExpansionMap expansion_map; | |
| ExpandSubtrees(&expansion_map); | |
| EXPECT_TRUE(EqualTrees(syntax_tree_.get(), expect_tree.get())); | |
| } | |
| // Splits a single token into a syntax tree node with two leaves. | |
| void FakeParseToken(TextStructureView* data, int offset, int node_tag) { | |
| TokenSequence& tokens = data->MutableTokenStream(); | |
| tokens.push_back(TokenInfo(11, data->Contents().substr(0, offset))); | |
| tokens.push_back(TokenInfo(12, data->Contents().substr(offset))); | |
| TokenStreamView& tokens_view = data->MutableTokenStreamView(); | |
| tokens_view.push_back(tokens.begin()); | |
| tokens_view.push_back(tokens.begin() + 1); | |
| data->MutableSyntaxTree() = TNode(node_tag, Leaf(tokens[0]), Leaf(tokens[1])); | |
| } | |
| // Test that ExpandSubtrees expands a single leaf into a subtree. | |
| TEST_F(TextStructureViewPublicTest, ExpandSubtreesOneLeaf) { | |
| // Expand the "hello" token into ("hel", "lo"). | |
| const int divide = 3; | |
| const int new_node_tag = 7; | |
| std::string subtext(tokens_[0].text().data(), tokens_[0].text().length()); | |
| std::unique_ptr<TextStructure> subanalysis(new TextStructure(subtext)); | |
| FakeParseToken(&subanalysis->MutableData(), divide, new_node_tag); | |
| auto& replacement_node = down_cast<SyntaxTreeNode*>(syntax_tree_.get()) | |
| ->mutable_children() | |
| .front(); | |
| TextStructureView::DeferredExpansion expansion{&replacement_node, | |
| std::move(subanalysis)}; | |
| // Expect tree must be built using substrings of contents_. | |
| // Build the expect tree first because it references text using | |
| // pre-mutation indices. | |
| const auto expect_tree = Node( // noformat | |
| TNode(new_node_tag, // noformat | |
| Leaf(11, tokens_[0].text().substr(0, divide)), // noformat | |
| Leaf(12, tokens_[0].text().substr(divide)) // noformat | |
| ), // noformat | |
| Leaf(tokens_[1]), // noformat | |
| Leaf(tokens_[3])); | |
| TextStructureView::NodeExpansionMap expansion_map; | |
| expansion_map[tokens_[0].left(contents_)] = std::move(expansion); | |
| ExpandSubtrees(&expansion_map); | |
| EXPECT_TRUE(EqualTrees(syntax_tree_.get(), expect_tree.get())); | |
| } | |
| // Test that ExpandSubtrees expands a single leaf into a subtree. | |
| TEST_F(TextStructureViewPublicTest, ExpandSubtreesMultipleLeaves) { | |
| const int divide1 = 3; | |
| const int new_node_tag1 = 7; | |
| const int divide2 = 2; | |
| const int new_node_tag2 = 9; | |
| const int offset2 = tokens_[3].left(contents_); | |
| TextStructureView::NodeExpansionMap expansion_map; | |
| { | |
| // Expand the "hello" token into ("hel", "lo"). | |
| std::string subtext(tokens_[0].text().data(), tokens_[0].text().length()); | |
| std::unique_ptr<TextStructure> subanalysis(new TextStructure(subtext)); | |
| FakeParseToken(&subanalysis->MutableData(), divide1, new_node_tag1); | |
| auto& replacement_node = down_cast<SyntaxTreeNode*>(syntax_tree_.get()) | |
| ->mutable_children() | |
| .front(); | |
| TextStructureView::DeferredExpansion expansion{&replacement_node, | |
| std::move(subanalysis)}; | |
| expansion_map[tokens_[0].left(contents_)] = std::move(expansion); | |
| } | |
| { | |
| // Expand the "world" token into ("wo", "rld"). | |
| std::string subtext(tokens_[3].text().data(), tokens_[3].text().length()); | |
| std::unique_ptr<TextStructure> subanalysis(new TextStructure(subtext)); | |
| FakeParseToken(&subanalysis->MutableData(), divide2, new_node_tag2); | |
| auto& replacement_node = down_cast<SyntaxTreeNode*>(syntax_tree_.get()) | |
| ->mutable_children() | |
| .back(); | |
| TextStructureView::DeferredExpansion expansion{&replacement_node, | |
| std::move(subanalysis)}; | |
| expansion_map[offset2] = std::move(expansion); | |
| } | |
| // Expect tree must be built using substrings of contents_. | |
| // Build the expect tree first because it references text using | |
| // pre-mutation indices. | |
| const auto expect_tree = Node( // noformat | |
| TNode(new_node_tag1, // noformat | |
| Leaf(11, tokens_[0].text().substr(0, divide1)), // noformat | |
| Leaf(12, tokens_[0].text().substr(divide1)) // noformat | |
| ), // noformat | |
| Leaf(tokens_[1]), // noformat | |
| TNode(new_node_tag2, // noformat | |
| Leaf(11, tokens_[3].text().substr(0, divide2)), // noformat | |
| Leaf(12, tokens_[3].text().substr(divide2)) // noformat | |
| ) // noformat | |
| ); | |
| ExpandSubtrees(&expansion_map); | |
| EXPECT_TRUE(EqualTrees(syntax_tree_.get(), expect_tree.get())); | |
| } | |
| // The following tests intentionally cause internal violations to | |
| // make sure the consistency checks work as intended. | |
| // The mutated fields are restored so that the consistency checks | |
| // pass at destruction time. | |
| // Test that FastLineRangeConsistencyCheck catches text mismatch at first line. | |
| TEST_F(TextStructureViewInternalsTest, LineConsistencyFailsBeginning) { | |
| const ValueSaver<absl::string_view> save_contents(&contents_); | |
| contents_ = contents_.substr(1); | |
| EXPECT_FALSE(FastLineRangeConsistencyCheck().ok()); | |
| EXPECT_FALSE(InternalConsistencyCheck().ok()); | |
| } | |
| // Test that FastLineRangeConsistencyCheck catches text mismatch at last line. | |
| TEST_F(TextStructureViewInternalsTest, LineConsistencyFailsEnd) { | |
| const ValueSaver<absl::string_view> save_contents(&contents_); | |
| contents_ = contents_.substr(0, contents_.length() - 1); | |
| EXPECT_FALSE(FastLineRangeConsistencyCheck().ok()); | |
| EXPECT_FALSE(InternalConsistencyCheck().ok()); | |
| } | |
| // Test that FastTokenRangeConsistencyCheck catches location past end. | |
| TEST_F(TextStructureViewInternalsTest, RangeConsistencyFailPastContentsEnd) { | |
| const ValueSaver<absl::string_view> save_contents(&contents_); | |
| contents_ = contents_.substr(0, contents_.length() - 1); | |
| EXPECT_FALSE(FastTokenRangeConsistencyCheck().ok()); | |
| EXPECT_FALSE(InternalConsistencyCheck().ok()); | |
| } | |
| // Test that FastTokenRangeConsistencyCheck catches location past begin. | |
| TEST_F(TextStructureViewInternalsTest, RangeConsistencyFailPastContentsBegin) { | |
| const ValueSaver<absl::string_view> save_contents(&contents_); | |
| contents_ = contents_.substr(1); | |
| EXPECT_FALSE(FastTokenRangeConsistencyCheck().ok()); | |
| EXPECT_FALSE(InternalConsistencyCheck().ok()); | |
| } | |
| // Test that FastTokenRangeConsistencyCheck catches first token iterator past | |
| // begin. | |
| TEST_F(TextStructureViewInternalsTest, | |
| RangeConsistencyFailViewFrontPastTokensBegin) { | |
| const ValueSaver<TokenSequence::const_iterator> save_iterator( | |
| &tokens_view_.front()); | |
| --tokens_view_.front(); | |
| EXPECT_FALSE(FastTokenRangeConsistencyCheck().ok()); | |
| EXPECT_FALSE(InternalConsistencyCheck().ok()); | |
| } | |
| // Test that FastTokenRangeConsistencyCheck catches first token iterator past | |
| // end. | |
| TEST_F(TextStructureViewInternalsTest, | |
| RangeConsistencyFailViewFrontPastTokensEnd) { | |
| const ValueSaver<TokenSequence::const_iterator> save_iterator( | |
| &tokens_view_.front()); | |
| tokens_view_.front() += tokens_.size(); | |
| EXPECT_FALSE(FastTokenRangeConsistencyCheck().ok()); | |
| EXPECT_FALSE(InternalConsistencyCheck().ok()); | |
| } | |
| // Test that FastTokenRangeConsistencyCheck catches last token iterator past | |
| // end. | |
| TEST_F(TextStructureViewInternalsTest, | |
| RangeConsistencyFailViewBackPastTokensEnd) { | |
| const ValueSaver<TokenSequence::const_iterator> save_iterator( | |
| &tokens_view_.back()); | |
| ++tokens_view_.back(); | |
| EXPECT_FALSE(FastTokenRangeConsistencyCheck().ok()); | |
| EXPECT_FALSE(InternalConsistencyCheck().ok()); | |
| } | |
| // Test that FastTokenRangeConsistencyCheck catches last token iterator past | |
| // begin. | |
| TEST_F(TextStructureViewInternalsTest, | |
| RangeConsistencyFailViewBackPastTokensBegin) { | |
| const ValueSaver<TokenSequence::const_iterator> save_iterator( | |
| &tokens_view_.back()); | |
| tokens_view_.back() -= tokens_.size(); | |
| EXPECT_FALSE(FastTokenRangeConsistencyCheck().ok()); | |
| EXPECT_FALSE(InternalConsistencyCheck().ok()); | |
| } | |
| // Test that FastTokenRangeConsistencyCheck catches last token in tree | |
| // located past the begin. | |
| TEST_F(TextStructureViewInternalsTest, | |
| SyntaxTreeConsistencyFailViewRightmostLeafPastBegin) { | |
| const ValueSaver<absl::string_view> save_contents(&contents_); | |
| contents_ = contents_.substr(1); | |
| EXPECT_FALSE(SyntaxTreeConsistencyCheck().ok()); | |
| EXPECT_FALSE(InternalConsistencyCheck().ok()); | |
| } | |
| // Test that FastTokenRangeConsistencyCheck catches last token in tree | |
| // located past the end. | |
| TEST_F(TextStructureViewInternalsTest, | |
| SyntaxTreeConsistencyFailViewRightmostLeafPastEnd) { | |
| const ValueSaver<absl::string_view> save_contents(&contents_); | |
| contents_ = contents_.substr(0, contents_.length() - 1); | |
| EXPECT_FALSE(SyntaxTreeConsistencyCheck().ok()); | |
| EXPECT_FALSE(InternalConsistencyCheck().ok()); | |
| } | |
| // Test that FastTokenRangeConsistencyCheck catches an incorrect beginning of | |
| // the per-line token map. | |
| TEST_F(TextStructureViewInternalsTest, LineTokenMapWrongBegin) { | |
| EXPECT_FALSE(tokens_.empty()); | |
| ASSERT_FALSE(GetLineTokenMap().empty()); | |
| ++lazy_line_token_map_.front(); | |
| EXPECT_FALSE(FastTokenRangeConsistencyCheck().ok()); | |
| EXPECT_FALSE(InternalConsistencyCheck().ok()); | |
| } | |
| // Test that FastTokenRangeConsistencyCheck catches an incorrect end of | |
| // the per-line token map. | |
| TEST_F(TextStructureViewInternalsTest, LineTokenMapWrongEnd) { | |
| EXPECT_FALSE(tokens_.empty()); | |
| ASSERT_FALSE(GetLineTokenMap().empty()); | |
| --lazy_line_token_map_.back(); | |
| EXPECT_FALSE(FastTokenRangeConsistencyCheck().ok()); | |
| EXPECT_FALSE(InternalConsistencyCheck().ok()); | |
| } | |
| } // namespace verible |