| // 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. |
| |
| // Unit-tests for (Verilog) LexicalContext. |
| // |
| // Testing strategy: |
| // LexicalContext is just a means of disambiguation for overloaded tokens. |
| // What is most important is that the transformed tokens are correct. |
| // The vast majority of tokens pass through un-modified, so focus testing |
| // on those transfomations, and the state functions that directly support them. |
| // Testing exhaustively is counter-productive because many aspects of the |
| // class's internal details are subject to change. |
| |
| #include "verible/verilog/parser/verilog-lexical-context.h" |
| |
| #include <array> |
| #include <initializer_list> |
| #include <iterator> |
| #include <memory> |
| #include <stack> |
| #include <string> |
| #include <string_view> |
| #include <vector> |
| |
| #include "absl/strings/match.h" |
| #include "gtest/gtest.h" |
| #include "verible/common/text/text-structure.h" |
| #include "verible/common/text/token-info.h" |
| #include "verible/common/text/token-stream-view.h" |
| #include "verible/common/util/logging.h" |
| #include "verible/verilog/analysis/verilog-analyzer.h" // only used for lexing |
| #include "verible/verilog/parser/verilog-parser.h" // only used for diagnostics |
| #include "verible/verilog/parser/verilog-token-enum.h" |
| |
| #undef EXPECT_OK |
| #define EXPECT_OK(value) EXPECT_TRUE((value).ok()) |
| |
| namespace verilog { |
| namespace { |
| |
| using verible::TokenInfo; |
| using verible::TokenSequence; |
| using verible::TokenStreamReferenceView; |
| |
| // TODO(fangism): move this to a test-only library |
| // Compare value and reason. |
| // We define it as a macro so that when it fails, you get a meaningful line |
| // number. |
| // |
| // If this were a function, its signature would be: |
| // template <typename T> |
| // inline void EXPECT_EQ_REASON(const verible::WithReason<T>& r, const T& |
| // expected, std::string_view pattern); |
| #define EXPECT_EQ_REASON(expr, expected, pattern) \ |
| { \ |
| const auto &r = expr; /* evaluate expr once, auto-extend lifetime */ \ |
| EXPECT_EQ(r.value, expected) << r.reason; \ |
| /* value could be correct, but reason could be wrong. */ \ |
| EXPECT_TRUE(absl::StrContains(std::string_view(r.reason), pattern)) \ |
| << "value: " << r.value << "\nreason: " << r.reason; \ |
| } |
| |
| template <class SM> |
| void ExpectStateMachineTokenSequence( |
| SM &sm, verible::TokenStreamView::const_iterator *token_iter, |
| std::initializer_list<int> expect_token_enums) { |
| int i = 0; |
| for (int expect_token_enum : expect_token_enums) { |
| const TokenInfo &token(***token_iter); |
| const int token_enum = token.token_enum(); |
| const int interpreted_enum = sm.InterpretToken(token_enum); |
| const char *raw_symbol = verilog_symbol_name(token_enum); |
| const char *interpreted_symbol = verilog_symbol_name(interpreted_enum); |
| const char *expected_symbol = verilog_symbol_name(expect_token_enum); |
| VLOG(1) << "token[" << i << "] enum: " << raw_symbol << " -> " |
| << interpreted_symbol; |
| EXPECT_EQ(interpreted_enum, expect_token_enum) |
| << " (" << interpreted_symbol << " vs. " << expected_symbol << ')'; |
| sm.UpdateState(token_enum); |
| ++*token_iter; |
| ++i; |
| } |
| } |
| |
| // Tests for null state of state machine. |
| TEST(KeywordLabelStateMachineTest, NoKeywords) { |
| VerilogAnalyzer analyzer("1, 2; 3;", ""); |
| EXPECT_OK(analyzer.Tokenize()); |
| analyzer.FilterTokensForSyntaxTree(); |
| const auto &tokens_view = analyzer.Data().GetTokenStreamView(); |
| EXPECT_EQ(tokens_view.size(), 7); // including EOF |
| |
| internal::KeywordLabelStateMachine b; |
| EXPECT_TRUE(b.ItemMayStart()); |
| int i = 0; |
| for (auto iter : tokens_view) { |
| b.UpdateState(iter->token_enum()); |
| EXPECT_FALSE(b.ItemMayStart()) |
| << "Error at index " << i << ", after: " << *iter; |
| ++i; |
| } |
| } |
| |
| // Test for state transitions of state machine, no labels. |
| TEST(KeywordLabelStateMachineTest, KeywordsWithoutLabels) { |
| VerilogAnalyzer analyzer("1 2 begin end begin end 3 begin 4 5 end 6", ""); |
| const std::array<bool, 13> expect_item_may_start{ |
| {false, false, true, true, true, true, true, true, true, false, true, |
| true, false}}; |
| EXPECT_OK(analyzer.Tokenize()); |
| analyzer.FilterTokensForSyntaxTree(); |
| const auto &tokens_view = analyzer.Data().GetTokenStreamView(); |
| EXPECT_EQ(tokens_view.size(), expect_item_may_start.size()); |
| |
| internal::KeywordLabelStateMachine b; |
| EXPECT_TRUE(b.ItemMayStart()); |
| auto expect_iter = expect_item_may_start.begin(); |
| for (auto iter : tokens_view) { |
| b.UpdateState(iter->token_enum()); |
| EXPECT_EQ(b.ItemMayStart(), *expect_iter) |
| << "Error at index " |
| << std::distance(expect_item_may_start.begin(), expect_iter) |
| << ", after: " << *iter; |
| ++expect_iter; |
| } |
| } |
| |
| // Test for state transitions of state machine, with labels. |
| TEST(KeywordLabelStateMachineTest, KeywordsWithLabels) { |
| VerilogAnalyzer analyzer("1 begin:a end:a begin:b end:b 2", ""); |
| const std::array<bool, 15> expect_item_may_start{ |
| {false, true, false, true, true, false, true, true, false, true, true, |
| false, true, false, false}}; |
| EXPECT_OK(analyzer.Tokenize()); |
| analyzer.FilterTokensForSyntaxTree(); |
| const auto &tokens_view = analyzer.Data().GetTokenStreamView(); |
| EXPECT_EQ(tokens_view.size(), expect_item_may_start.size()); // including EOF |
| |
| internal::KeywordLabelStateMachine b; |
| EXPECT_TRUE(b.ItemMayStart()); |
| auto expect_iter = expect_item_may_start.cbegin(); |
| for (auto iter : tokens_view) { |
| b.UpdateState(iter->token_enum()); |
| EXPECT_EQ(b.ItemMayStart(), *expect_iter) |
| << "Error at index " |
| << std::distance(expect_item_may_start.begin(), expect_iter) |
| << ", after: " << *iter; |
| ++expect_iter; |
| } |
| } |
| |
| // Test for state transitions of state machine, with some labels, some items. |
| TEST(KeywordLabelStateMachineTest, ItemsInsideBlocks) { |
| VerilogAnalyzer analyzer("begin:a 1 end:a 2 begin 3 end", ""); |
| const std::array<bool, 12> expect_item_may_start{{true, false, true, false, |
| true, false, true, false, |
| true, true, true, true}}; |
| EXPECT_OK(analyzer.Tokenize()); |
| analyzer.FilterTokensForSyntaxTree(); |
| const auto &tokens_view = analyzer.Data().GetTokenStreamView(); |
| EXPECT_EQ(tokens_view.size(), expect_item_may_start.size()); // including EOF |
| |
| internal::KeywordLabelStateMachine b; |
| EXPECT_TRUE(b.ItemMayStart()); |
| auto expect_iter = expect_item_may_start.cbegin(); |
| for (auto iter : tokens_view) { |
| b.UpdateState(iter->token_enum()); |
| EXPECT_EQ(b.ItemMayStart(), *expect_iter) |
| << "Error at index " |
| << std::distance(expect_item_may_start.begin(), expect_iter) |
| << ", after: " << *iter; |
| ++expect_iter; |
| } |
| } |
| |
| class LastSemicolonStateMachineTest |
| : public ::testing::Test, |
| public internal::LastSemicolonStateMachine { |
| public: |
| LastSemicolonStateMachineTest() |
| : internal::LastSemicolonStateMachine( |
| TK_property, TK_endproperty, |
| SemicolonEndOfAssertionVariableDeclarations) {} |
| }; |
| |
| // Tests that the one and only semicolon in the range of interest is updated. |
| TEST_F(LastSemicolonStateMachineTest, LifeCycleOneSemicolon) { |
| EXPECT_EQ(state_, kNone); |
| EXPECT_TRUE(semicolons_.empty()); |
| |
| // Purely synthesized token sequence for testing: |
| // Only enums matter, not text. |
| constexpr std::string_view text("don't care"); |
| TokenInfo tokens[] = { |
| TokenInfo(TK_module, text), |
| TokenInfo(SymbolIdentifier, text), |
| TokenInfo(';', text), |
| TokenInfo(TK_property, text), |
| TokenInfo(SymbolIdentifier, text), |
| TokenInfo(';', text), // only this one should be modified |
| TokenInfo(SymbolIdentifier, text), |
| TokenInfo(TK_endproperty, text), |
| TokenInfo(';', text), |
| }; |
| |
| UpdateState(&tokens[0]); |
| EXPECT_EQ(state_, kNone); |
| |
| UpdateState(&tokens[1]); |
| UpdateState(&tokens[2]); |
| EXPECT_EQ(state_, kNone); |
| |
| UpdateState(&tokens[3]); // TK_property |
| EXPECT_EQ(state_, kActive); |
| |
| UpdateState(&tokens[4]); // SymbolIdentifier |
| EXPECT_EQ(state_, kActive); |
| |
| UpdateState(&tokens[5]); // ';' |
| EXPECT_EQ(state_, kActive); |
| EXPECT_EQ(semicolons_.top(), &tokens[5]); |
| |
| UpdateState(&tokens[6]); // SymbolIdentifier |
| EXPECT_EQ(state_, kActive); |
| |
| UpdateState(&tokens[7]); // TK_endproperty |
| EXPECT_EQ(state_, kNone); |
| EXPECT_TRUE(semicolons_.empty()); |
| EXPECT_EQ(tokens[2].token_enum(), ';'); // unmodified |
| EXPECT_EQ(tokens[5].token_enum(), |
| SemicolonEndOfAssertionVariableDeclarations); |
| |
| UpdateState(&tokens[8]); // ';' |
| EXPECT_EQ(state_, kNone); |
| EXPECT_TRUE(semicolons_.empty()); |
| EXPECT_EQ(tokens[8].token_enum(), ';'); // unmodified |
| } |
| |
| // Tests that only the last semicolon in the range of interest is updated. |
| TEST_F(LastSemicolonStateMachineTest, LifeCycleFinalSemicolon) { |
| EXPECT_EQ(state_, kNone); |
| EXPECT_TRUE(semicolons_.empty()); |
| |
| // Purely synthesized token sequence for testing: |
| // Only enums matter, not text. |
| constexpr std::string_view text("don't care"); |
| TokenInfo tokens[] = { |
| TokenInfo(TK_module, text), |
| TokenInfo(SymbolIdentifier, text), |
| TokenInfo(';', text), |
| TokenInfo(TK_property, text), |
| TokenInfo(SymbolIdentifier, text), |
| TokenInfo(';', text), |
| TokenInfo(SymbolIdentifier, text), |
| TokenInfo(';', text), // only this one should be modified |
| TokenInfo(SymbolIdentifier, text), |
| TokenInfo(TK_endproperty, text), |
| TokenInfo(';', text), |
| }; |
| |
| UpdateState(&tokens[0]); |
| EXPECT_EQ(state_, kNone); |
| |
| UpdateState(&tokens[1]); |
| UpdateState(&tokens[2]); |
| EXPECT_EQ(state_, kNone); |
| |
| UpdateState(&tokens[3]); // TK_property |
| EXPECT_EQ(state_, kActive); |
| |
| UpdateState(&tokens[4]); // SymbolIdentifier |
| EXPECT_EQ(state_, kActive); |
| |
| UpdateState(&tokens[5]); // ';' |
| EXPECT_EQ(state_, kActive); |
| EXPECT_EQ(semicolons_.top(), &tokens[5]); |
| |
| UpdateState(&tokens[6]); // SymbolIdentifier |
| EXPECT_EQ(state_, kActive); |
| |
| UpdateState(&tokens[7]); // ';' |
| EXPECT_EQ(state_, kActive); |
| EXPECT_EQ(semicolons_.top(), &tokens[7]); |
| |
| UpdateState(&tokens[8]); // SymbolIdentifier |
| EXPECT_EQ(state_, kActive); |
| |
| UpdateState(&tokens[9]); // TK_endproperty |
| EXPECT_EQ(state_, kNone); |
| EXPECT_TRUE(semicolons_.empty()); |
| EXPECT_EQ(tokens[2].token_enum(), ';'); // unmodified |
| EXPECT_EQ(tokens[5].token_enum(), ';'); // unmodified |
| EXPECT_EQ(tokens[7].token_enum(), |
| SemicolonEndOfAssertionVariableDeclarations); |
| |
| UpdateState(&tokens[10]); // ';' |
| EXPECT_EQ(state_, kNone); |
| EXPECT_TRUE(semicolons_.empty()); |
| EXPECT_EQ(tokens[10].token_enum(), ';'); // unmodified |
| } |
| |
| // TODO(fangism): move this into a test_util library |
| struct StateMachineTestBase : public ::testing::Test { |
| // Lexes code and initializes token_iter to point to the first token. |
| void Tokenize(const std::string &code) { |
| analyzer = std::make_unique<VerilogAnalyzer>(code, ""); |
| EXPECT_OK(analyzer->Tokenize()); |
| analyzer->FilterTokensForSyntaxTree(); |
| token_iter = analyzer->Data().GetTokenStreamView().cbegin(); |
| } |
| |
| template <class SM> |
| void ExpectTokenSequence(SM &sm, std::initializer_list<int> expect_enums) { |
| ExpectStateMachineTokenSequence(sm, &token_iter, expect_enums); |
| } |
| |
| // Parser, used only for lexing. |
| std::unique_ptr<VerilogAnalyzer> analyzer; |
| |
| // Iterator over the filtered token stream. |
| verible::TokenStreamView::const_iterator token_iter; |
| }; |
| |
| struct ConstraintBlockStateMachineTest : public StateMachineTestBase { |
| void ExpectTokenSequence(std::initializer_list<int> expect_enums) { |
| StateMachineTestBase::ExpectTokenSequence(sm, expect_enums); |
| } |
| |
| // Instance of the state machine under test. |
| internal::ConstraintBlockStateMachine sm; |
| }; |
| |
| // Test initial conditions of internal::ConstraintBlockStateMachine. |
| TEST_F(ConstraintBlockStateMachineTest, Initialization) { |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that empty constraint block is balanced. |
| TEST_F(ConstraintBlockStateMachineTest, EmptyBlock) { |
| Tokenize("{}"); |
| |
| ExpectTokenSequence({'{'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine parses a soft expression. |
| TEST_F(ConstraintBlockStateMachineTest, SoftExpression) { |
| Tokenize(R"( |
| { |
| soft a -> b; |
| } |
| )"); |
| |
| ExpectTokenSequence({'{', // |
| TK_soft, SymbolIdentifier /* a */, TK_LOGICAL_IMPLIES, |
| SymbolIdentifier, ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine parses a soft expression, with extra parens. |
| TEST_F(ConstraintBlockStateMachineTest, SoftExpressionExtraParens) { |
| Tokenize(R"( |
| { |
| soft (a -> b); |
| } |
| )"); |
| |
| ExpectTokenSequence({'{', // |
| TK_soft, '(', SymbolIdentifier /* a */, |
| TK_LOGICAL_IMPLIES, SymbolIdentifier, ')', ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine parses an unexpected '}'. |
| TEST_F(ConstraintBlockStateMachineTest, InvalidSoftUnexpectedCloseBrace) { |
| Tokenize(R"( |
| { |
| soft // missing expression and ';' |
| } |
| )"); |
| |
| ExpectTokenSequence({'{', TK_soft}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine parses a uniqueness constraint. |
| TEST_F(ConstraintBlockStateMachineTest, UniquenessConstraint) { |
| Tokenize(R"( |
| { |
| unique {[0:1],[3:4]}; |
| } |
| )"); |
| |
| ExpectTokenSequence( // |
| {'{', // |
| TK_unique, '{', // |
| '[', TK_DecNumber /* 0 */, ':', TK_DecNumber, ']', ',', // |
| '[', TK_DecNumber /* 3 */, ':', TK_DecNumber, ']', // |
| '}', ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine parses a solve-before item. |
| TEST_F(ConstraintBlockStateMachineTest, SolveItem) { |
| Tokenize(R"( |
| { |
| solve a before b; |
| } |
| )"); |
| |
| ExpectTokenSequence( |
| {'{', TK_solve, SymbolIdentifier, TK_before, SymbolIdentifier, ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine parses a solve-before, with multiple variables. |
| TEST_F(ConstraintBlockStateMachineTest, SolveItemMultiple) { |
| Tokenize(R"( |
| { |
| solve a, b before c, d; |
| } |
| )"); |
| |
| ExpectTokenSequence({'{', // |
| TK_solve, SymbolIdentifier, ',', SymbolIdentifier, // |
| TK_before, SymbolIdentifier, ',', SymbolIdentifier, |
| ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine parses a solve-before, with hierarchical variables. |
| TEST_F(ConstraintBlockStateMachineTest, SolveItemHierarchical) { |
| Tokenize(R"( |
| { |
| solve a.b before c.d; |
| } |
| )"); |
| |
| ExpectTokenSequence({'{', // |
| TK_solve, SymbolIdentifier, '.', SymbolIdentifier, // |
| TK_before, SymbolIdentifier, '.', SymbolIdentifier, |
| ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine parses a disable-soft. |
| TEST_F(ConstraintBlockStateMachineTest, DisableSoft) { |
| Tokenize(R"( |
| { |
| disable soft x.y; |
| } |
| )"); |
| |
| ExpectTokenSequence( |
| {'{', TK_disable, TK_soft, SymbolIdentifier, '.', SymbolIdentifier, ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly balances, even with a missing ';'. |
| TEST_F(ConstraintBlockStateMachineTest, BalanceConstraintSetMissingSemicolon) { |
| Tokenize(R"( |
| { |
| f -> { g -> h } // missing ';' after 'h' |
| } |
| )"); |
| |
| ExpectTokenSequence( // |
| {'{', SymbolIdentifier /* f */, TK_CONSTRAINT_IMPLIES, '{', |
| SymbolIdentifier, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, '}'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly balances a nested constraint set. |
| TEST_F(ConstraintBlockStateMachineTest, InterpretRightArrowConstraintSetRHS) { |
| Tokenize(R"( |
| { |
| f -> { g -> h; } |
| } |
| )"); |
| |
| ExpectTokenSequence( // |
| {'{', // |
| SymbolIdentifier /* f */, TK_CONSTRAINT_IMPLIES, // |
| '{', SymbolIdentifier, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, ';', |
| '}'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly balances a nested constraint set. |
| TEST_F(ConstraintBlockStateMachineTest, |
| InterpretRightArrowConstraintSetNested) { |
| Tokenize(R"( |
| { |
| f -> { |
| g -> {h;} |
| } |
| } |
| )"); |
| |
| ExpectTokenSequence( // |
| {'{', // |
| SymbolIdentifier /* f */, TK_CONSTRAINT_IMPLIES, // |
| '{', SymbolIdentifier, TK_CONSTRAINT_IMPLIES, '{', SymbolIdentifier, ';', |
| '}', '}'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly balances parentheses. |
| TEST_F(ConstraintBlockStateMachineTest, InterpretRightArrowDeepParens) { |
| Tokenize(R"( |
| { |
| (((f -> g))) -> ((j -> k)) -> ((p -> q)); |
| } |
| )"); |
| |
| // clang-format off |
| ExpectTokenSequence( |
| {'{', |
| '(', '(', '(', |
| SymbolIdentifier /* f */, TK_LOGICAL_IMPLIES, SymbolIdentifier, |
| ')', ')', ')', |
| TK_CONSTRAINT_IMPLIES, |
| '(', '(', |
| SymbolIdentifier /* j */, TK_LOGICAL_IMPLIES, SymbolIdentifier, |
| ')', ')', |
| TK_CONSTRAINT_IMPLIES, |
| '(', '(', |
| SymbolIdentifier /* p */, TK_LOGICAL_IMPLIES, SymbolIdentifier, |
| ')', ')', ';' |
| }); |
| // clang-format on |
| |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly balances braces. |
| TEST_F(ConstraintBlockStateMachineTest, InterpretRightArrowDeepBraces) { |
| Tokenize(R"( |
| { |
| {{a -> b, (g -> h)}} -> {{(j -> k), l -> m}}; // concatenation expressions |
| } |
| )"); |
| |
| // clang-format off |
| ExpectTokenSequence( |
| {'{', |
| '{', '{', |
| SymbolIdentifier /* a */, TK_LOGICAL_IMPLIES, SymbolIdentifier, |
| ',', |
| '(', SymbolIdentifier /* g */, TK_LOGICAL_IMPLIES, SymbolIdentifier, ')', |
| '}', '}', |
| TK_CONSTRAINT_IMPLIES, |
| '{', '{', |
| '(', SymbolIdentifier /* j */, TK_LOGICAL_IMPLIES, SymbolIdentifier, ')', |
| ',', |
| SymbolIdentifier /* l */, TK_LOGICAL_IMPLIES, SymbolIdentifier, |
| '}', '}', ';'}); |
| // clang-format on |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly interprets '->'. |
| TEST_F(ConstraintBlockStateMachineTest, InterpretRightArrow) { |
| Tokenize(R"( |
| { |
| a -> b; |
| (c -> d) -> e; |
| f -> { (g -> h) -> i } // missing ';' after 'i' |
| } |
| )"); |
| |
| ExpectTokenSequence({'{', SymbolIdentifier /* a */, TK_CONSTRAINT_IMPLIES, |
| SymbolIdentifier, ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({'(', SymbolIdentifier /* c */, TK_LOGICAL_IMPLIES, |
| SymbolIdentifier, ')', TK_CONSTRAINT_IMPLIES, |
| SymbolIdentifier, ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({SymbolIdentifier /* f */, TK_CONSTRAINT_IMPLIES, '{', |
| '(', SymbolIdentifier, TK_LOGICAL_IMPLIES, |
| SymbolIdentifier, ')', TK_CONSTRAINT_IMPLIES, |
| SymbolIdentifier, '}', '}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly interprets '->'. |
| TEST_F(ConstraintBlockStateMachineTest, InterpretRightArrow2) { |
| Tokenize(R"( |
| { |
| a -> (b -> c); |
| d -> { |
| e -> (f -> g); |
| (h -> i) -> j; |
| } |
| } |
| )"); |
| |
| ExpectTokenSequence({'{', SymbolIdentifier /* a */, TK_CONSTRAINT_IMPLIES, |
| '(', SymbolIdentifier, TK_LOGICAL_IMPLIES, |
| SymbolIdentifier, ')', ';'}); |
| ExpectTokenSequence({SymbolIdentifier /* d */, TK_CONSTRAINT_IMPLIES, '{', |
| SymbolIdentifier /* e */, TK_CONSTRAINT_IMPLIES, '(', |
| SymbolIdentifier, TK_LOGICAL_IMPLIES, SymbolIdentifier, |
| ')', ';'}); |
| ExpectTokenSequence({'(', SymbolIdentifier /* h */, TK_LOGICAL_IMPLIES, |
| SymbolIdentifier, ')', TK_CONSTRAINT_IMPLIES, |
| SymbolIdentifier, ';', '}', '}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly interprets '->' with balanced {} |
| // expressions. |
| TEST_F(ConstraintBlockStateMachineTest, InterpretRightArrowBracedExpressions) { |
| Tokenize(R"( |
| { |
| {2{4'h0}} -> {2{4'h1}}; |
| ({2{4'h2}} -> {2{4'h3}}) -> {2{4'h4}}; |
| {2{4'h5}} -> ({2{4'h6}} -> {2{4'h7}}); |
| } |
| )"); |
| |
| ExpectTokenSequence({'{'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| // Re-use the same expected token sequence for {N{M'hx}} |
| #define CONSTANT_CONCAT_SEQUENCE \ |
| '{', TK_DecNumber, '{', TK_DecNumber, TK_HexBase, TK_HexDigits, '}', '}' |
| |
| ExpectTokenSequence({CONSTANT_CONCAT_SEQUENCE, TK_CONSTRAINT_IMPLIES, |
| CONSTANT_CONCAT_SEQUENCE, ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'(', CONSTANT_CONCAT_SEQUENCE, TK_LOGICAL_IMPLIES, |
| CONSTANT_CONCAT_SEQUENCE, ')', TK_CONSTRAINT_IMPLIES, |
| CONSTANT_CONCAT_SEQUENCE, ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({CONSTANT_CONCAT_SEQUENCE, TK_CONSTRAINT_IMPLIES, '(', |
| CONSTANT_CONCAT_SEQUENCE, TK_LOGICAL_IMPLIES, |
| CONSTANT_CONCAT_SEQUENCE, ')', ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| #undef CONSTANT_CONCAT_SEQUENCE |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine recovers from a bad if construct. |
| TEST_F(ConstraintBlockStateMachineTest, InvalidIf) { |
| Tokenize(R"( |
| { |
| if |
| } |
| )"); |
| |
| ExpectTokenSequence({'{', TK_if}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly interprets '->' in if blocks. |
| TEST_F(ConstraintBlockStateMachineTest, IfConstraintSingle) { |
| Tokenize(R"( |
| { |
| if (a -> b) c -> d; |
| } |
| )"); |
| |
| ExpectTokenSequence({'{', TK_if, '(', SymbolIdentifier, TK_LOGICAL_IMPLIES, |
| SymbolIdentifier, ')', SymbolIdentifier, |
| TK_CONSTRAINT_IMPLIES, SymbolIdentifier, ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly interprets '->' in if clauses. |
| TEST_F(ConstraintBlockStateMachineTest, IfConstraintSingleParenExpressions) { |
| Tokenize(R"( |
| { |
| if (a -> b) |
| (c -> d) -> (e -> f); |
| } |
| )"); |
| |
| ExpectTokenSequence( // |
| {'{', TK_if, '(', SymbolIdentifier, TK_LOGICAL_IMPLIES, SymbolIdentifier, |
| ')'}); |
| ExpectTokenSequence( // |
| {'(', SymbolIdentifier /* c */, TK_LOGICAL_IMPLIES, SymbolIdentifier, ')', |
| TK_CONSTRAINT_IMPLIES, // |
| '(', SymbolIdentifier /* e */, TK_LOGICAL_IMPLIES, SymbolIdentifier, ')', |
| ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly interprets '->' in constraint set |
| // if-clause. |
| TEST_F(ConstraintBlockStateMachineTest, IfConstraintBlock) { |
| Tokenize(R"( |
| { |
| if (a -> b) { c -> d; } |
| } |
| )"); |
| |
| ExpectTokenSequence( // |
| {'{', TK_if, '(', // |
| SymbolIdentifier, TK_LOGICAL_IMPLIES, SymbolIdentifier, // |
| ')', // |
| '{', SymbolIdentifier, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, ';', |
| '}'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly interprets '->' in if-else clauses. |
| TEST_F(ConstraintBlockStateMachineTest, IfElseConstraintSingle) { |
| Tokenize(R"( |
| { |
| if (a -> b) c -> d; |
| else e -> f; |
| } |
| )"); |
| |
| ExpectTokenSequence( // |
| {'{', // |
| TK_if, '(', // |
| SymbolIdentifier, TK_LOGICAL_IMPLIES, SymbolIdentifier, |
| ')', // |
| SymbolIdentifier, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence( // |
| {TK_else, // |
| SymbolIdentifier, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly interprets '->' in if-else blocks. |
| TEST_F(ConstraintBlockStateMachineTest, IfElseConstraintBlocks) { |
| Tokenize(R"( |
| { |
| if (a -> b) { c -> d; } |
| else { e -> f; } |
| } |
| )"); |
| |
| ExpectTokenSequence( // |
| {'{', // |
| TK_if, '(', // |
| SymbolIdentifier, TK_LOGICAL_IMPLIES, SymbolIdentifier, // |
| ')', '{', // |
| SymbolIdentifier, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, // |
| ';', '}'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence( // |
| {TK_else, '{', // |
| SymbolIdentifier, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, // |
| ';', '}'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly interprets '->' in if-else nested blocks. |
| TEST_F(ConstraintBlockStateMachineTest, IfElseConstraintBlocksNested) { |
| Tokenize(R"( |
| { |
| if (a -> b) |
| if (p -> q) { c -> d; } |
| else { e -> f; } |
| else { r -> s; } |
| } |
| )"); |
| |
| ExpectTokenSequence( // |
| {'{', // |
| TK_if, '(', SymbolIdentifier /* a */, TK_LOGICAL_IMPLIES, |
| SymbolIdentifier, ')'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence( // |
| {TK_if, '(', // |
| SymbolIdentifier /* p */, TK_LOGICAL_IMPLIES, SymbolIdentifier, // |
| ')', '{', // |
| SymbolIdentifier /* c */, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, // |
| ';', '}'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({TK_else, '{', SymbolIdentifier /* e */, |
| TK_CONSTRAINT_IMPLIES, SymbolIdentifier, ';', '}'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({TK_else, '{', SymbolIdentifier /* r */, |
| TK_CONSTRAINT_IMPLIES, SymbolIdentifier, ';', '}'}); |
| EXPECT_TRUE(sm.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine recovers from a bad foreach construct. |
| TEST_F(ConstraintBlockStateMachineTest, InvalidForeach) { |
| Tokenize(R"( |
| { |
| foreach |
| } |
| )"); |
| |
| ExpectTokenSequence({'{', TK_foreach}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly interprets '->' in foreach blocks. |
| TEST_F(ConstraintBlockStateMachineTest, ForeachSingleSimple) { |
| Tokenize(R"( |
| { |
| foreach (a[i]) c -> d; |
| } |
| )"); |
| |
| ExpectTokenSequence( // |
| {'{', // |
| TK_foreach, '(', SymbolIdentifier, '[', SymbolIdentifier, ']', ')', // |
| SymbolIdentifier, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly interprets '->' in foreach blocks. |
| TEST_F(ConstraintBlockStateMachineTest, ForeachSingleHierarchical) { |
| Tokenize(R"( |
| { |
| foreach (a.b[i,j]) c -> d; |
| } |
| )"); |
| |
| ExpectTokenSequence( // |
| {'{', // |
| TK_foreach, '(', // |
| SymbolIdentifier /* a */, '.', SymbolIdentifier, // |
| '[', SymbolIdentifier /* i */, ',', SymbolIdentifier, ']', // |
| ')', // |
| SymbolIdentifier /* c */, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, ';'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly interprets '->' in foreach blocks. |
| TEST_F(ConstraintBlockStateMachineTest, ForeachBlock) { |
| Tokenize(R"( |
| { |
| foreach (a[i]) { c -> d; } |
| } |
| )"); |
| |
| ExpectTokenSequence( // |
| {'{', // |
| TK_foreach, '(', // |
| SymbolIdentifier, '[', SymbolIdentifier, ']', // |
| ')', '{', // |
| SymbolIdentifier, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, ';', // |
| '}'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly interprets '->' in foreach blocks. |
| TEST_F(ConstraintBlockStateMachineTest, ForeachBlockNested) { |
| Tokenize(R"( |
| { |
| foreach (a[i]) |
| foreach (b[j]) |
| { c -> d; } |
| } |
| )"); |
| |
| // clang-format off |
| ExpectTokenSequence( |
| {'{', |
| TK_foreach, '(', |
| SymbolIdentifier /* a */, '[', SymbolIdentifier, ']', |
| ')', |
| TK_foreach, '(', |
| SymbolIdentifier /* b */, '[', SymbolIdentifier, ']', |
| ')', |
| '{', |
| SymbolIdentifier, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, ';', |
| '}'}); |
| // clang-format on |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Tests that state machine correctly interprets '->' in foreach/if blocks. |
| TEST_F(ConstraintBlockStateMachineTest, ForeachIfMixed) { |
| Tokenize(R"( |
| { |
| foreach (a[i]) |
| if (b -> j) |
| { c -> d; } |
| if (e -> f) |
| foreach (g[i]) |
| { j -> k; } |
| } |
| )"); |
| |
| // clang-format off |
| ExpectTokenSequence( |
| {'{', TK_foreach, '(', |
| SymbolIdentifier /* a */, '[', SymbolIdentifier, ']', ')', |
| TK_if, '(', |
| SymbolIdentifier /* b */, TK_LOGICAL_IMPLIES, SymbolIdentifier, ')', |
| '{', SymbolIdentifier, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, ';', |
| '}'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence( |
| {TK_if, '(', |
| SymbolIdentifier /* e */, TK_LOGICAL_IMPLIES, SymbolIdentifier, ')', |
| TK_foreach, '(', |
| SymbolIdentifier /* g */, '[', SymbolIdentifier, ']', ')', |
| '{', SymbolIdentifier, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, ';', |
| '}'}); |
| // clang-format on |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({'}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| struct RandomizeCallStateMachineTest : public StateMachineTestBase { |
| void ExpectTokenSequence(std::initializer_list<int> expect_enums) { |
| StateMachineTestBase::ExpectTokenSequence(sm, expect_enums); |
| } |
| |
| // Instance of the state machine under test. |
| internal::RandomizeCallStateMachine sm; |
| }; |
| |
| // Test that internal::RandomizeCallStateMachine initializes in inactive state. |
| TEST_F(RandomizeCallStateMachineTest, Initialization) { |
| EXPECT_FALSE(sm.IsActive()); |
| sm.UpdateState(TK_randomize); |
| EXPECT_TRUE(sm.IsActive()); |
| } |
| |
| // Test that internal::RandomizeCallStateMachine updates correctly with plain |
| // call. |
| TEST_F(RandomizeCallStateMachineTest, ParseStdCall) { |
| Tokenize(R"( |
| x = std::randomize; |
| )"); |
| |
| ExpectTokenSequence({ |
| SymbolIdentifier /* x */, '=', TK_randomize /* std::randomize */ |
| }); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({';'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Test that internal::RandomizeCallStateMachine updates correctly with method |
| // call. |
| TEST_F(RandomizeCallStateMachineTest, ParseMethodCall) { |
| Tokenize(R"( |
| x = y.randomize; |
| )"); |
| ExpectTokenSequence( // |
| {SymbolIdentifier /* x */, '=', SymbolIdentifier, '.', TK_randomize}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({';'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Test that internal::RandomizeCallStateMachine updates correctly with empty |
| // variables. |
| TEST_F(RandomizeCallStateMachineTest, ParseMethodCallEmptyVariables) { |
| Tokenize(R"( |
| x = y.randomize(); |
| )"); |
| ExpectTokenSequence( // |
| {SymbolIdentifier /* x */, '=', // |
| SymbolIdentifier, '.', TK_randomize, '(', ')'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({';'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Test that internal::RandomizeCallStateMachine updates correctly with one |
| // variable. |
| TEST_F(RandomizeCallStateMachineTest, ParseMethodCallOneVariable) { |
| Tokenize(R"( |
| x = y.randomize(z); |
| )"); |
| ExpectTokenSequence({SymbolIdentifier /* x */, '=', SymbolIdentifier, '.', |
| TK_randomize, '(', SymbolIdentifier, ')'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({';'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Test that internal::RandomizeCallStateMachine updates correctly with multiple |
| // variables. |
| TEST_F(RandomizeCallStateMachineTest, ParseMethodCallMultiVariables) { |
| Tokenize(R"( |
| x = y.randomize(z, w); |
| )"); |
| ExpectTokenSequence( // |
| {SymbolIdentifier /* x */, '=', // |
| SymbolIdentifier, '.', TK_randomize, '(', // |
| SymbolIdentifier, ',', SymbolIdentifier, ')'}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({';'}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Test that internal::RandomizeCallStateMachine updates correctly as a |
| // predicate. |
| TEST_F(RandomizeCallStateMachineTest, ParseMethodCallPredicate) { |
| Tokenize(R"( |
| if (y.randomize) begin |
| )"); |
| ExpectTokenSequence( // |
| {TK_if, '(', // |
| SymbolIdentifier /* y */, '.', TK_randomize}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({')'}); |
| EXPECT_FALSE(sm.IsActive()); |
| ExpectTokenSequence({TK_begin}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Test that internal::RandomizeCallStateMachine updates correctly with |
| // constraint_block. |
| TEST_F(RandomizeCallStateMachineTest, ParseMethodCallWithConstraintBlock) { |
| Tokenize(R"( |
| if (y.randomize with {a -> b;}) begin |
| )"); |
| ExpectTokenSequence( // |
| {TK_if, '(', // |
| SymbolIdentifier /* y */, '.', TK_randomize}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence( // |
| {TK_with, '{', // |
| SymbolIdentifier /* a */, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, ';', |
| '}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| ExpectTokenSequence({')', TK_begin}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Test that internal::RandomizeCallStateMachine updates correctly with |
| // constraint_block with empty variable list. |
| TEST_F(RandomizeCallStateMachineTest, |
| ParseMethodCallWithConstraintBlockAndEmptyVariableList) { |
| Tokenize(R"( |
| if (y.randomize with () {a -> b;}) begin |
| )"); |
| ExpectTokenSequence( // |
| {TK_if, '(', // |
| SymbolIdentifier /* y */, '.', TK_randomize}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence( // |
| {TK_with, '(', ')', '{', // |
| SymbolIdentifier /* a */, TK_CONSTRAINT_IMPLIES, SymbolIdentifier, // |
| ';', '}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| ExpectTokenSequence({')', TK_begin}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Test that internal::RandomizeCallStateMachine updates correctly with |
| // constraint_block with non-empty variable list. |
| TEST_F(RandomizeCallStateMachineTest, |
| ParseMethodCallWithConstraintBlockAndVariableList) { |
| Tokenize(R"( |
| if (y.randomize with (j, k) {a -> b;}) begin |
| )"); |
| ExpectTokenSequence({TK_if, '(', // |
| SymbolIdentifier /* y */, '.', TK_randomize}); |
| EXPECT_TRUE(sm.IsActive()); |
| ExpectTokenSequence({TK_with, '(', SymbolIdentifier /* j */, ',', |
| SymbolIdentifier /* k */, ')', '{', |
| SymbolIdentifier /* a */, TK_CONSTRAINT_IMPLIES, |
| SymbolIdentifier /* b */, ';', '}'}); |
| EXPECT_FALSE(sm.IsActive()); |
| ExpectTokenSequence({')', TK_begin}); |
| EXPECT_FALSE(sm.IsActive()); |
| } |
| |
| // Class for testing some internal methods of LexicalContext. |
| class LexicalContextTest : public ::testing::Test, public LexicalContext { |
| protected: |
| LexicalContextTest() = default; |
| |
| void CheckInitialState() const { |
| EXPECT_EQ(previous_token_, nullptr); |
| EXPECT_FALSE(in_extern_declaration_); |
| EXPECT_FALSE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(in_task_declaration_); |
| EXPECT_FALSE(in_task_body_); |
| EXPECT_FALSE(in_module_declaration_); |
| EXPECT_FALSE(in_module_body_); |
| EXPECT_FALSE(randomize_call_tracker_.IsActive()); |
| EXPECT_FALSE(constraint_declaration_tracker_.IsActive()); |
| EXPECT_FALSE(InAnyDeclaration()); |
| EXPECT_FALSE(InAnyDeclarationHeader()); |
| EXPECT_TRUE(flow_control_stack_.empty()); |
| EXPECT_TRUE(keyword_label_tracker_.ItemMayStart()); |
| EXPECT_TRUE(balance_stack_.empty()); |
| EXPECT_TRUE(block_stack_.empty()); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "first token"); |
| } |
| |
| void AdvanceToken() { |
| TokenSequence::iterator iter(*token_iter_); |
| TokenInfo &token(*iter); |
| LexicalContext::AdvanceToken(&token); |
| ++token_iter_; |
| } |
| |
| static void ExpectCurrentTokenEnum(TokenStreamReferenceView::iterator iter, |
| int expect_token_enum) { |
| const int got_token_enum = (*iter)->token_enum(); |
| EXPECT_EQ(got_token_enum, expect_token_enum) |
| << " from token " << **iter << " (" |
| << verilog_symbol_name(got_token_enum) << " vs. " |
| << verilog_symbol_name(expect_token_enum) << ')'; |
| } |
| |
| // Advances the token iterator once for every element in token_enums, |
| // verifying token enumerations along the way. |
| // Use this helper method to quickly advance through a sequence of tokens |
| // without checking other interesting properties. This also verifies that |
| // the token was *not* transformed by the LexicalContext. |
| void ExpectTokenSequence(std::initializer_list<int> token_enums) { |
| for (const int token_enum : token_enums) { |
| ExpectCurrentTokenEnum(token_iter_, token_enum); |
| AdvanceToken(); |
| EXPECT_EQ(previous_token_->token_enum(), token_enum) |
| << " from token " << **token_iter_ << " (" |
| << verilog_symbol_name(previous_token_->token_enum()) << " vs. " |
| << verilog_symbol_name(token_enum) << ')'; |
| } |
| } |
| |
| // Advances the token iterator once, verifying the token enumeration before |
| // and after advancement. |
| void ExpectTransformedToken(int token_enum_before, int token_enum_after) { |
| ExpectCurrentTokenEnum(token_iter_, token_enum_before); |
| AdvanceToken(); |
| EXPECT_EQ(previous_token_->token_enum(), token_enum_after) |
| << " (" << verilog_symbol_name(previous_token_->token_enum()) << " vs. " |
| << verilog_symbol_name(token_enum_after) << ')'; |
| } |
| |
| // Lexes code and initializes token_iter to point to the first token. |
| void Tokenize(const std::string &code) { |
| analyzer_ = std::make_unique<VerilogAnalyzer>(code, ""); |
| EXPECT_OK(analyzer_->Tokenize()); |
| analyzer_->FilterTokensForSyntaxTree(); |
| token_refs_ = analyzer_->MutableData().MakeTokenStreamReferenceView(); |
| token_iter_ = token_refs_.begin(); |
| } |
| |
| // Parser, used only for lexing. |
| std::unique_ptr<VerilogAnalyzer> analyzer_; |
| |
| // Modifiable handles into token stream. |
| TokenStreamReferenceView token_refs_; |
| |
| // Iterator over the filtered token stream. |
| TokenStreamReferenceView::iterator token_iter_; |
| }; |
| |
| // Test that construction and initialization work. |
| TEST_F(LexicalContextTest, Initialization) { CheckInitialState(); } |
| |
| // Test that token stream initialization works. |
| TEST_F(LexicalContextTest, ScanEmptyTokens) { |
| Tokenize(""); |
| EXPECT_EQ(token_refs_.size(), 1); // only EOF token |
| AdvanceToken(); |
| // Don't really care what the state is after EOF, just don't crash. |
| } |
| |
| // Test that context of function declaration is correct. |
| TEST_F(LexicalContextTest, ScanEmptyFunctionDeclaration) { |
| const char code[] = "function void foo; endfunction"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 6); // including EOF token |
| |
| ExpectTokenSequence({TK_function}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), false, |
| "in other declaration header"); |
| EXPECT_FALSE(ExpectingStatement()); |
| |
| ExpectTokenSequence({TK_void}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), false, |
| "in other declaration header"); |
| EXPECT_FALSE(ExpectingStatement()); |
| |
| ExpectTokenSequence({SymbolIdentifier}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), false, |
| "in other declaration header"); |
| EXPECT_FALSE(ExpectingStatement()); |
| |
| ExpectTokenSequence({';'}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_TRUE(in_function_body_); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "end of header"); |
| EXPECT_TRUE(ExpectingStatement()); |
| |
| ExpectTokenSequence({TK_endfunction}); |
| EXPECT_FALSE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "item may start"); |
| EXPECT_FALSE(ExpectingStatement()); |
| } |
| |
| // Test for correct context in function declaration with empty ports. |
| TEST_F(LexicalContextTest, ScanFunctionDeclarationEmptyPorts) { |
| const char code[] = "function void foo(); endfunction"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 8); |
| |
| ExpectTokenSequence({TK_function}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingBodyItemStart().value); |
| EXPECT_FALSE(ExpectingStatement()); |
| |
| ExpectTokenSequence({TK_void}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingBodyItemStart().value); |
| EXPECT_FALSE(ExpectingStatement()); |
| |
| ExpectTokenSequence({SymbolIdentifier}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingBodyItemStart().value); |
| EXPECT_FALSE(ExpectingStatement()); |
| EXPECT_TRUE(balance_stack_.empty()); |
| |
| ExpectTokenSequence({'('}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingBodyItemStart().value); |
| EXPECT_FALSE(ExpectingStatement()); |
| EXPECT_EQ(balance_stack_.size(), 1); |
| |
| ExpectTokenSequence({')'}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingBodyItemStart().value); |
| EXPECT_FALSE(ExpectingStatement()); |
| EXPECT_TRUE(balance_stack_.empty()); |
| |
| ExpectTokenSequence({';'}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_TRUE(in_function_body_); |
| EXPECT_TRUE(ExpectingStatement()); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "end of header"); |
| |
| ExpectTokenSequence({TK_endfunction}); |
| EXPECT_FALSE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingStatement()); |
| } |
| |
| // Test for correct context in function declaration with ports. |
| TEST_F(LexicalContextTest, ScanFunctionDeclarationWithPorts) { |
| const char code[] = "function void foo(int a, int b); endfunction"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 13); |
| |
| ExpectTokenSequence({TK_function}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingBodyItemStart().value); |
| EXPECT_FALSE(ExpectingStatement()); |
| |
| ExpectTokenSequence({TK_void}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingBodyItemStart().value); |
| EXPECT_FALSE(ExpectingStatement()); |
| |
| ExpectTokenSequence({SymbolIdentifier}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingBodyItemStart().value); |
| EXPECT_FALSE(ExpectingStatement()); |
| EXPECT_TRUE(balance_stack_.empty()); |
| |
| ExpectTokenSequence({'('}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingBodyItemStart().value); |
| EXPECT_FALSE(ExpectingStatement()); |
| EXPECT_EQ(balance_stack_.size(), 1); |
| |
| ExpectTokenSequence({TK_int}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingBodyItemStart().value); |
| EXPECT_FALSE(ExpectingStatement()); |
| EXPECT_EQ(balance_stack_.size(), 1); |
| |
| ExpectTokenSequence({SymbolIdentifier}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingBodyItemStart().value); |
| EXPECT_FALSE(ExpectingStatement()); |
| EXPECT_EQ(balance_stack_.size(), 1); |
| |
| ExpectTokenSequence({','}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingBodyItemStart().value); |
| EXPECT_FALSE(ExpectingStatement()); |
| EXPECT_EQ(balance_stack_.size(), 1); |
| |
| ExpectTokenSequence({TK_int}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingBodyItemStart().value); |
| EXPECT_FALSE(ExpectingStatement()); |
| EXPECT_EQ(balance_stack_.size(), 1); |
| |
| ExpectTokenSequence({SymbolIdentifier}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingBodyItemStart().value); |
| EXPECT_FALSE(ExpectingStatement()); |
| EXPECT_EQ(balance_stack_.size(), 1); |
| |
| ExpectTokenSequence({')'}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(ExpectingBodyItemStart().value); |
| EXPECT_FALSE(ExpectingStatement()); |
| EXPECT_TRUE(balance_stack_.empty()); |
| |
| ExpectTokenSequence({';'}); |
| EXPECT_TRUE(in_function_declaration_); |
| EXPECT_TRUE(in_function_body_); |
| EXPECT_TRUE(ExpectingStatement()); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "end of header"); |
| |
| ExpectTokenSequence({TK_endfunction}); |
| EXPECT_FALSE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "item may start"); |
| EXPECT_FALSE(ExpectingStatement()); |
| } |
| |
| // Test that '->' is correctly disambiguated inside a function. |
| TEST_F(LexicalContextTest, ScanFunctionDeclarationWithRightArrows) { |
| const char code[] = R"( |
| function void foo; |
| -> z; // event-trigger |
| if (a -> b) -> y; // implies, event-trigger |
| -> w; // event-trigger |
| for (; c -> d; ) begin // implies |
| -> y; // event-trigger |
| end |
| endfunction |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 34); // including EOF token |
| |
| ExpectTokenSequence({TK_function, TK_void, SymbolIdentifier, ';'}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence({SymbolIdentifier, ';'}); |
| EXPECT_FALSE(InFlowControlHeader()); |
| |
| ExpectTokenSequence({TK_if}); |
| EXPECT_TRUE(InFlowControlHeader()); |
| |
| ExpectTokenSequence({'(', SymbolIdentifier}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_LOGICAL_IMPLIES); |
| |
| ExpectTokenSequence({SymbolIdentifier}); |
| EXPECT_TRUE(InFlowControlHeader()); |
| |
| ExpectTokenSequence({')'}); |
| EXPECT_FALSE(InFlowControlHeader()); |
| EXPECT_FALSE(InAnyDeclarationHeader()); |
| EXPECT_TRUE(in_function_body_); |
| EXPECT_TRUE(previous_token_finished_header_); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "end of header"); |
| EXPECT_TRUE(ExpectingStatement()); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence({SymbolIdentifier /* y */, ';'}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence({SymbolIdentifier /* w */, ';', TK_for, '(', ';', |
| SymbolIdentifier /* c */}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_LOGICAL_IMPLIES); |
| |
| ExpectTokenSequence({SymbolIdentifier /* d */, ';', ')'}); |
| EXPECT_FALSE(InFlowControlHeader()); |
| EXPECT_FALSE(InAnyDeclarationHeader()); |
| EXPECT_TRUE(in_function_body_); |
| EXPECT_TRUE(previous_token_finished_header_); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "end of header"); |
| EXPECT_TRUE(ExpectingStatement()); |
| |
| ExpectTokenSequence({TK_begin}); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "item may start"); |
| EXPECT_TRUE(ExpectingStatement()); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence({SymbolIdentifier /* y */, ';', TK_end, TK_endfunction}); |
| EXPECT_FALSE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| } |
| |
| // Test that '->' is correctly disambiguated, handling keyword labels. |
| TEST_F(LexicalContextTest, |
| ScanFunctionDeclarationWithRightArrowsControlLabels) { |
| const char code[] = R"( |
| function void foo; |
| if (a -> b) begin : bar |
| -> y; // implies, event-trigger |
| end : bar |
| -> z; // event-trigger |
| endfunction |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 24); // including EOF token |
| |
| ExpectTokenSequence({TK_function, TK_void, SymbolIdentifier, ';', TK_if}); |
| |
| EXPECT_TRUE(InFlowControlHeader()); |
| |
| ExpectTokenSequence({'(', SymbolIdentifier}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_LOGICAL_IMPLIES); |
| |
| ExpectTokenSequence({SymbolIdentifier}); |
| EXPECT_TRUE(InFlowControlHeader()); |
| |
| ExpectTokenSequence({')'}); |
| EXPECT_FALSE(InFlowControlHeader()); |
| EXPECT_FALSE(InAnyDeclarationHeader()); |
| EXPECT_TRUE(in_function_body_); |
| EXPECT_TRUE(previous_token_finished_header_); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "end of header"); |
| EXPECT_TRUE(ExpectingStatement()); |
| |
| ExpectTokenSequence({TK_begin, ':', SymbolIdentifier /* bar */}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence( |
| {SymbolIdentifier /* y */, ';', TK_end, ':', SymbolIdentifier /* bar */}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence({SymbolIdentifier /* w */, ';', TK_endfunction}); |
| EXPECT_FALSE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| } |
| |
| // Test that extern function declaration does not expect a declaration body. |
| TEST_F(LexicalContextTest, ScanExternMethodDeclaration) { |
| const char code[] = R"( |
| class n; |
| extern function foo; |
| endclass |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 9); // including EOF token |
| |
| ExpectTokenSequence({TK_class, SymbolIdentifier, ';', TK_extern}); |
| EXPECT_TRUE(in_extern_declaration_); |
| |
| ExpectTokenSequence({TK_function, SymbolIdentifier, ';'}); |
| // Make sure not in function body context here because of extern declaration. |
| EXPECT_FALSE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(in_extern_declaration_); |
| |
| ExpectTokenSequence({TK_endclass}); |
| } |
| |
| // Test that extern function declaration does not expect a declaration body. |
| TEST_F(LexicalContextTest, ScanExternMethodDeclarationWithEmptyPorts) { |
| const char code[] = R"( |
| class n; |
| extern function foo(); |
| endclass |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 11); // including EOF token |
| |
| ExpectTokenSequence({TK_class, SymbolIdentifier, ';', TK_extern}); |
| EXPECT_TRUE(in_extern_declaration_); |
| |
| ExpectTokenSequence({TK_function, SymbolIdentifier, '(', ')', ';'}); |
| |
| // Make sure not in function body context here because of extern declaration. |
| EXPECT_FALSE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(in_extern_declaration_); |
| |
| ExpectTokenSequence({TK_endclass}); |
| } |
| |
| // Test that extern function declaration does not expect a declaration body. |
| TEST_F(LexicalContextTest, ScanExternMethodDeclarationWithSomePorts) { |
| const char code[] = R"( |
| class n; |
| extern function foo(int bar); |
| endclass |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 13); // including EOF token |
| |
| ExpectTokenSequence({TK_class, SymbolIdentifier, ';', TK_extern}); |
| EXPECT_TRUE(in_extern_declaration_); |
| |
| ExpectTokenSequence( |
| {TK_function, SymbolIdentifier, '(', TK_int, SymbolIdentifier, ')', ';'}); |
| |
| // Make sure not in function body context here because of extern declaration. |
| EXPECT_FALSE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(in_extern_declaration_); |
| |
| ExpectTokenSequence({TK_endclass}); |
| } |
| |
| // Test that extern task declaration does not expect a declaration body. |
| TEST_F(LexicalContextTest, ScanExternTaskDeclaration) { |
| const char code[] = R"( |
| class n; |
| extern task foo; |
| endclass |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 9); // including EOF token |
| |
| ExpectTokenSequence({TK_class, SymbolIdentifier, ';', TK_extern}); |
| EXPECT_TRUE(in_extern_declaration_); |
| |
| ExpectTokenSequence({TK_task, SymbolIdentifier, ';'}); |
| // Make sure not in task body context here because of extern declaration. |
| EXPECT_FALSE(in_task_declaration_); |
| EXPECT_FALSE(in_task_body_); |
| EXPECT_FALSE(in_extern_declaration_); |
| |
| ExpectTokenSequence({TK_endclass}); |
| } |
| |
| // Test that extern constraint prototype does not expect a constraint_block. |
| TEST_F(LexicalContextTest, ScanExternConstraintPrototype) { |
| const char code[] = R"( |
| class n; |
| extern constraint foo; |
| endclass |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 9); // including EOF token |
| |
| ExpectTokenSequence({TK_class, SymbolIdentifier, ';', TK_extern}); |
| EXPECT_TRUE(in_extern_declaration_); |
| |
| ExpectTokenSequence({TK_constraint}); |
| // constraint_prototype should not activate declaration tracker. |
| EXPECT_FALSE(constraint_declaration_tracker_.IsActive()); |
| EXPECT_FALSE(in_extern_declaration_); // reset |
| |
| ExpectTokenSequence({SymbolIdentifier, ';'}); |
| EXPECT_FALSE(constraint_declaration_tracker_.IsActive()); |
| |
| ExpectTokenSequence({TK_endclass}); |
| } |
| |
| // Test that extern function declaration does not expect a declaration body, |
| // and that "->" is correctly interpreted as constraint-implication. |
| TEST_F(LexicalContextTest, |
| ScanExternMethodDeclarationFollowedByConstraintImplies) { |
| const char code[] = R"( |
| class n; |
| extern function foo; |
| endclass |
| |
| constraint v { |
| m -> { |
| x != y; |
| } |
| } |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 21); // including EOF token |
| |
| ExpectTokenSequence({TK_class, SymbolIdentifier, ';', TK_extern}); |
| EXPECT_TRUE(in_extern_declaration_); |
| |
| ExpectTokenSequence({TK_function, SymbolIdentifier, ';'}); |
| // Make sure not in function body context here because of extern declaration. |
| EXPECT_FALSE(in_function_declaration_); |
| EXPECT_FALSE(in_function_body_); |
| EXPECT_FALSE(in_extern_declaration_); |
| |
| ExpectTokenSequence({TK_endclass, TK_constraint}); |
| EXPECT_TRUE(constraint_declaration_tracker_.IsActive()); |
| ExpectTokenSequence({SymbolIdentifier, '{', SymbolIdentifier}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_CONSTRAINT_IMPLIES); |
| |
| ExpectTokenSequence( |
| {'{', SymbolIdentifier, TK_NE, SymbolIdentifier, ';', '}', '}'}); |
| EXPECT_FALSE(constraint_declaration_tracker_.IsActive()); |
| } |
| |
| // Test that '->' is correctly disambiguated, handling randomize-with. |
| TEST_F(LexicalContextTest, ScanRandomizeWithConstraintBlock) { |
| const char code[] = R"( |
| function void rat(seq_item item); |
| if (!item.randomize() with |
| { |
| (x -> y) -> { |
| a inside {[1 : 2]}; |
| } |
| }) begin |
| `uvm_fatal("rat", "failed") |
| end |
| endfunction : rat |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| |
| ExpectTokenSequence({TK_function, TK_void, SymbolIdentifier, '(', |
| SymbolIdentifier, SymbolIdentifier, ')', ';'}); |
| ExpectTokenSequence({TK_if, '(', '!', SymbolIdentifier, '.', TK_randomize}); |
| EXPECT_TRUE(randomize_call_tracker_.IsActive()); |
| |
| ExpectTokenSequence({'(', ')', TK_with, '{', '(', SymbolIdentifier}); |
| ExpectTransformedToken(_TK_RARROW, TK_LOGICAL_IMPLIES); |
| ExpectTokenSequence({SymbolIdentifier, ')'}); |
| ExpectTransformedToken(_TK_RARROW, TK_CONSTRAINT_IMPLIES); |
| |
| ExpectTokenSequence({'{', SymbolIdentifier, TK_inside, '{', '[', TK_DecNumber, |
| ':', TK_DecNumber, ']', '}', ';', '}', '}'}); |
| EXPECT_FALSE(randomize_call_tracker_.IsActive()); |
| ExpectTokenSequence({')', TK_begin}); |
| } |
| |
| // Test that '->' is correctly disambiguated inside a task. |
| TEST_F(LexicalContextTest, ScanTaskDeclarationWithRightArrows) { |
| const char code[] = R"( |
| task foo; |
| -> z; // event-trigger |
| if (a -> b) -> y; // implies, event-trigger |
| -> w; // event-trigger |
| for (; c -> d; ) begin // implies |
| -> y; // event-trigger |
| end |
| endtask |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 33); // including EOF token |
| |
| ExpectTokenSequence({TK_task}); |
| EXPECT_TRUE(in_task_declaration_); |
| EXPECT_FALSE(in_task_body_); |
| |
| ExpectTokenSequence({SymbolIdentifier, ';'}); |
| EXPECT_TRUE(in_task_declaration_); |
| EXPECT_TRUE(in_task_body_); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence({SymbolIdentifier, ';'}); |
| EXPECT_FALSE(InFlowControlHeader()); |
| |
| ExpectTokenSequence({TK_if}); |
| EXPECT_TRUE(InFlowControlHeader()); |
| |
| ExpectTokenSequence({'(', SymbolIdentifier}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_LOGICAL_IMPLIES); |
| |
| ExpectTokenSequence({SymbolIdentifier}); |
| EXPECT_TRUE(InFlowControlHeader()); |
| |
| ExpectTokenSequence({')'}); |
| EXPECT_FALSE(InFlowControlHeader()); |
| EXPECT_FALSE(InAnyDeclarationHeader()); |
| EXPECT_TRUE(in_task_body_); |
| EXPECT_TRUE(previous_token_finished_header_); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "end of header"); |
| EXPECT_TRUE(ExpectingStatement()); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence({SymbolIdentifier /* y */, ';'}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence({SymbolIdentifier /* w */, ';', TK_for, '(', ';', |
| SymbolIdentifier /* c */}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_LOGICAL_IMPLIES); |
| |
| ExpectTokenSequence({SymbolIdentifier /* d */, ';', ')'}); |
| EXPECT_FALSE(InFlowControlHeader()); |
| EXPECT_FALSE(InAnyDeclarationHeader()); |
| EXPECT_TRUE(in_task_body_); |
| EXPECT_TRUE(previous_token_finished_header_); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "end of header"); |
| EXPECT_TRUE(ExpectingStatement()); |
| |
| ExpectTokenSequence({TK_begin}); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "item may start"); |
| EXPECT_TRUE(ExpectingStatement()); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence({SymbolIdentifier /* y */, ';', TK_end, TK_endtask}); |
| EXPECT_FALSE(in_task_declaration_); |
| EXPECT_FALSE(in_task_body_); |
| } |
| |
| // Test that '->' is correctly disambiguated inside task, |
| // handling keyword labels. |
| TEST_F(LexicalContextTest, ScanTaskDeclarationWithRightArrowsControlLabels) { |
| const char code[] = R"( |
| task foo; |
| if (a -> b) begin : bar |
| -> y; // implies, event-trigger |
| end : bar |
| -> z; // event-trigger |
| endtask |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 23); // including EOF token |
| |
| ExpectTokenSequence({TK_task, SymbolIdentifier, ';', TK_if}); |
| EXPECT_TRUE(InFlowControlHeader()); |
| |
| ExpectTokenSequence({'(', SymbolIdentifier}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_LOGICAL_IMPLIES); |
| |
| ExpectTokenSequence({SymbolIdentifier}); |
| EXPECT_TRUE(InFlowControlHeader()); |
| |
| ExpectTokenSequence({')'}); |
| EXPECT_FALSE(InFlowControlHeader()); |
| EXPECT_FALSE(InAnyDeclarationHeader()); |
| EXPECT_TRUE(in_task_body_); |
| EXPECT_TRUE(previous_token_finished_header_); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "end of header"); |
| EXPECT_TRUE(ExpectingStatement()); |
| |
| ExpectTokenSequence({TK_begin, ':', SymbolIdentifier /* bar */}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence( |
| {SymbolIdentifier /* y */, ';', TK_end, ':', SymbolIdentifier /* bar */}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence({SymbolIdentifier /* w */, ';', TK_endtask}); |
| } |
| |
| // Test that '->' is correctly disambiguated inside initial blocks. |
| TEST_F(LexicalContextTest, ScanInitialStatementEventTrigger) { |
| const char code[] = R"( |
| module foo; |
| initial -> x; // -> should be event trigger |
| endmodule |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 9); // including EOF token |
| |
| ExpectTokenSequence({TK_module}); |
| EXPECT_TRUE(in_module_declaration_); |
| EXPECT_FALSE(in_module_body_); |
| |
| ExpectTokenSequence({SymbolIdentifier, ';'}); |
| EXPECT_TRUE(in_module_declaration_); |
| EXPECT_TRUE(in_module_body_); |
| |
| ExpectTokenSequence({TK_initial}); |
| EXPECT_TRUE(in_initial_always_final_construct_); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence({SymbolIdentifier, ';'}); |
| EXPECT_FALSE(in_initial_always_final_construct_); |
| |
| ExpectTokenSequence({TK_endmodule}); |
| EXPECT_FALSE(in_module_declaration_); |
| EXPECT_FALSE(in_module_body_); |
| } |
| |
| // Test that '->' is correctly interpreted as a logical implication. |
| TEST_F(LexicalContextTest, AssignmentToLogcalImplicationExpression) { |
| const char code[] = R"( |
| module foo; |
| assign a = b -> x; |
| endmodule |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 12); // including EOF token |
| |
| ExpectTokenSequence({TK_module}); |
| EXPECT_TRUE(in_module_declaration_); |
| EXPECT_FALSE(in_module_body_); |
| |
| ExpectTokenSequence({SymbolIdentifier, ';'}); |
| EXPECT_TRUE(in_module_declaration_); |
| EXPECT_TRUE(in_module_body_); |
| |
| ExpectTokenSequence({TK_assign}); |
| EXPECT_FALSE(in_initial_always_final_construct_); |
| |
| ExpectTokenSequence( |
| {SymbolIdentifier /* a */, '=', SymbolIdentifier /* b */}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_LOGICAL_IMPLIES); |
| |
| ExpectTokenSequence({SymbolIdentifier /* x */, ';'}); |
| EXPECT_FALSE(in_initial_always_final_construct_); |
| |
| ExpectTokenSequence({TK_endmodule}); |
| EXPECT_FALSE(in_module_declaration_); |
| EXPECT_FALSE(in_module_body_); |
| } |
| |
| // Test that '->' is correctly interpreted as a logical implication. |
| TEST_F(LexicalContextTest, AssignmentToLogcalImplicationExpressionInSeqBlock) { |
| const char code[] = R"( |
| module foo; |
| initial begin |
| a = b -> x; |
| end |
| endmodule |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 14); // including EOF token |
| |
| ExpectTokenSequence({TK_module}); |
| EXPECT_TRUE(in_module_declaration_); |
| EXPECT_FALSE(in_module_body_); |
| |
| ExpectTokenSequence({SymbolIdentifier, ';'}); |
| EXPECT_TRUE(in_module_declaration_); |
| EXPECT_TRUE(in_module_body_); |
| |
| ExpectTokenSequence({TK_initial}); |
| EXPECT_TRUE(ExpectingStatement()); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "initial"); |
| EXPECT_TRUE(in_initial_always_final_construct_); |
| |
| ExpectTokenSequence({TK_begin}); |
| EXPECT_TRUE(ExpectingStatement()); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "item may start"); |
| |
| ExpectTokenSequence({SymbolIdentifier /* a */}); |
| ExpectTokenSequence({'='}); |
| ExpectTokenSequence({SymbolIdentifier /* b */}); |
| |
| EXPECT_FALSE(ExpectingStatement()); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), false, "(default)"); |
| ExpectTransformedToken(_TK_RARROW, TK_LOGICAL_IMPLIES); |
| |
| ExpectTokenSequence({SymbolIdentifier /* x */, ';'}); |
| EXPECT_TRUE(in_initial_always_final_construct_); |
| |
| ExpectTokenSequence({TK_end}); |
| EXPECT_FALSE(in_initial_always_final_construct_); |
| |
| ExpectTokenSequence({TK_endmodule}); |
| EXPECT_FALSE(in_module_declaration_); |
| EXPECT_FALSE(in_module_body_); |
| } |
| |
| // Test that '->' is correctly disambiguated inside initial blocks. |
| TEST_F(LexicalContextTest, ScanInitialBlockWithRightArrows) { |
| const char code[] = R"( |
| module foo; |
| initial begin |
| -> x; |
| if (a -> b) begin : bar |
| -> y; // implies, event-trigger |
| end : bar |
| -> z; // event-trigger |
| end |
| endmodule |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 29); // including EOF token |
| |
| ExpectTokenSequence({TK_module, SymbolIdentifier, ';', TK_initial, TK_begin}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence({SymbolIdentifier /* x */, ';', TK_if}); |
| EXPECT_TRUE(InFlowControlHeader()); |
| |
| ExpectTokenSequence({'(', SymbolIdentifier}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_LOGICAL_IMPLIES); |
| |
| ExpectTokenSequence({SymbolIdentifier}); |
| EXPECT_TRUE(InFlowControlHeader()); |
| |
| ExpectTokenSequence({')'}); |
| EXPECT_FALSE(InFlowControlHeader()); |
| EXPECT_FALSE(InAnyDeclarationHeader()); |
| EXPECT_TRUE(in_module_body_); |
| EXPECT_TRUE(previous_token_finished_header_); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "end of header"); |
| EXPECT_TRUE(ExpectingStatement()); |
| |
| ExpectTokenSequence({TK_begin, ':', SymbolIdentifier /* bar */}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence( |
| {SymbolIdentifier /* y */, ';', TK_end, ':', SymbolIdentifier /* bar */}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence({SymbolIdentifier /* z */, ';', TK_end, TK_endmodule}); |
| } |
| |
| // Test that '->' is correctly disambiguated as a constraint implication. |
| TEST_F(LexicalContextTest, ConstraintDeclarationImplication) { |
| const char code[] = R"( |
| constraint c { |
| a -> b; |
| } |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 9); // including EOF token |
| |
| ExpectTokenSequence({TK_constraint}); |
| EXPECT_TRUE(constraint_declaration_tracker_.IsActive()); |
| |
| ExpectTokenSequence({SymbolIdentifier, '{'}); |
| EXPECT_EQ(balance_stack_.size(), 1); |
| |
| ExpectTokenSequence({SymbolIdentifier}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_CONSTRAINT_IMPLIES); |
| |
| ExpectTokenSequence({SymbolIdentifier, ';', '}'}); |
| EXPECT_TRUE(balance_stack_.empty()); |
| EXPECT_FALSE(constraint_declaration_tracker_.IsActive()); |
| } |
| |
| // Test that '->' is correctly disambiguated as logical implication. |
| TEST_F(LexicalContextTest, ConstraintDeclarationLogicalImplication) { |
| const char code[] = R"( |
| constraint c { |
| if (a -> b) { |
| c -> d; |
| } |
| } |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 17); // including EOF token |
| |
| ExpectTokenSequence({TK_constraint}); |
| EXPECT_TRUE(constraint_declaration_tracker_.IsActive()); |
| |
| ExpectTokenSequence({SymbolIdentifier, '{'}); |
| EXPECT_EQ(balance_stack_.size(), 1); |
| |
| ExpectTokenSequence({TK_if, '(', SymbolIdentifier}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_LOGICAL_IMPLIES); |
| |
| ExpectTokenSequence({SymbolIdentifier, ')', '{', SymbolIdentifier}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_CONSTRAINT_IMPLIES); |
| |
| ExpectTokenSequence({SymbolIdentifier, ';', '}'}); |
| EXPECT_TRUE(constraint_declaration_tracker_.IsActive()); |
| |
| ExpectTokenSequence({'}'}); |
| EXPECT_TRUE(balance_stack_.empty()); |
| EXPECT_FALSE(constraint_declaration_tracker_.IsActive()); |
| } |
| |
| TEST_F(LexicalContextTest, MacroCallBalance) { |
| const char code[] = R"( |
| `so_call_me_baby() |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 4); // including EOF token |
| |
| ExpectTokenSequence({MacroCallId}); |
| EXPECT_TRUE(balance_stack_.empty()); |
| ExpectTokenSequence({'('}); |
| EXPECT_EQ(balance_stack_.size(), 1); |
| ExpectTokenSequence({MacroCallCloseToEndLine}); // ')' |
| EXPECT_TRUE(balance_stack_.empty()); |
| } |
| |
| TEST_F(LexicalContextTest, MacroCallBalanceWithComment) { |
| const char code[] = R"( |
| `so_call_me_baby() // comment |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| |
| ExpectTokenSequence({MacroCallId}); |
| EXPECT_TRUE(balance_stack_.empty()); |
| ExpectTokenSequence({'('}); |
| EXPECT_EQ(balance_stack_.size(), 1); |
| ExpectTokenSequence({MacroCallCloseToEndLine}); // ')' |
| EXPECT_TRUE(balance_stack_.empty()); |
| // comment token is filtered out |
| } |
| |
| TEST_F(LexicalContextTest, MacroCallBalanceSemicolon) { |
| const char code[] = R"( |
| `macro1(foo+bar, `innermacro(11)); |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| |
| ExpectTokenSequence({MacroCallId}); |
| EXPECT_TRUE(balance_stack_.empty()); |
| ExpectTokenSequence({'('}); |
| EXPECT_EQ(balance_stack_.size(), 1); |
| |
| // "foo+bar" and "`innermacro(11)" are un-lexed |
| ExpectTokenSequence({MacroArg, ',', MacroArg}); |
| |
| EXPECT_EQ(balance_stack_.size(), 1); |
| ExpectTokenSequence({')'}); |
| |
| EXPECT_TRUE(balance_stack_.empty()); |
| ExpectTokenSequence({';'}); |
| } |
| |
| TEST_F(LexicalContextTest, TaskEventTrigger) { |
| const char code[] = R"( |
| module foo; |
| task bar; |
| begin |
| -> ack; // should be event-trigger |
| end |
| endtask |
| endmodule |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| EXPECT_EQ(token_refs_.size(), 14); // including EOF token |
| |
| ExpectTokenSequence({TK_module, SymbolIdentifier, ';'}); |
| EXPECT_TRUE(in_module_declaration_); |
| EXPECT_TRUE(in_module_body_); |
| ExpectTokenSequence({TK_task, SymbolIdentifier, ';'}); |
| EXPECT_TRUE(in_task_declaration_); |
| EXPECT_TRUE(in_task_body_); |
| ExpectTokenSequence({TK_begin}); |
| EXPECT_TRUE(in_task_body_); |
| EXPECT_EQ_REASON(ExpectingBodyItemStart(), true, "item may start"); |
| EXPECT_TRUE(ExpectingStatement()); |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| ExpectTokenSequence({SymbolIdentifier, ';'}); |
| ExpectTokenSequence({TK_end, TK_endtask, TK_endmodule}); |
| } |
| |
| TEST_F(LexicalContextTest, TaskIfEventTrigger) { |
| const char code[] = R"( |
| class reset_driver; |
| `macro() |
| endclass: reset_driver |
| task drv_interface; |
| if (m_kind) begin |
| -> m_event; |
| end |
| endtask: drv_interface |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| |
| ExpectTokenSequence({TK_class, SymbolIdentifier, ';'}); |
| ExpectTokenSequence({MacroCallId, '(', MacroCallCloseToEndLine}); |
| EXPECT_TRUE(balance_stack_.empty()); |
| ExpectTokenSequence({TK_endclass, ':', SymbolIdentifier}); |
| ExpectTokenSequence({TK_task, SymbolIdentifier, ';'}); |
| ExpectTokenSequence({TK_if, '(', SymbolIdentifier, ')', TK_begin}); |
| |
| ExpectTransformedToken(_TK_RARROW, TK_TRIGGER); |
| |
| ExpectTokenSequence({SymbolIdentifier, ';', TK_end}); |
| ExpectTokenSequence({TK_endtask, ':', SymbolIdentifier}); |
| } |
| |
| TEST_F(LexicalContextTest, RandomizeCallWithConstraintInsideTaskDeclaration) { |
| const char code[] = R"( |
| task wr(); |
| s = m.randomize() with { |
| a -> b; // should be a constraint-implication |
| }; |
| endtask |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| ExpectTokenSequence({TK_task, SymbolIdentifier, '(', ')', ';'}); |
| ExpectTokenSequence({SymbolIdentifier /* s */, '=', SymbolIdentifier, '.', |
| TK_randomize, '(', ')', TK_with, '{', |
| SymbolIdentifier /* a */}); |
| ExpectTransformedToken(_TK_RARROW, TK_CONSTRAINT_IMPLIES); |
| ExpectTokenSequence({SymbolIdentifier, ';', '}', ';', TK_endtask}); |
| } |
| |
| TEST_F(LexicalContextTest, RandomizeCallWithNestedConstraintImplication) { |
| const char code[] = R"( |
| function void rat(); |
| if (!item.randomize() with { |
| x -> { |
| d -> {a;} |
| } |
| }) begin |
| end |
| endfunction : rat |
| )"; |
| Tokenize(code); |
| CheckInitialState(); |
| ExpectTokenSequence({TK_function, TK_void, SymbolIdentifier, '(', ')', ';'}); |
| ExpectTokenSequence({TK_if, '(', '!', SymbolIdentifier, '.', TK_randomize, |
| '(', ')', TK_with, '{'}); |
| ExpectTokenSequence({SymbolIdentifier /* x */}); |
| ExpectTransformedToken(_TK_RARROW, TK_CONSTRAINT_IMPLIES); |
| ExpectTokenSequence({'{', SymbolIdentifier /* d */}); |
| ExpectTransformedToken(_TK_RARROW, TK_CONSTRAINT_IMPLIES); |
| ExpectTokenSequence({'{', SymbolIdentifier /* a */, ';', '}'}); |
| ExpectTokenSequence({'}', '}', ')', TK_begin, TK_end}); |
| ExpectTokenSequence({TK_endfunction, ':', SymbolIdentifier}); |
| } |
| |
| } // namespace |
| } // namespace verilog |