| // 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 "verilog/analysis/verilog_analyzer.h" |
| |
| #include <iostream> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "absl/base/casts.h" |
| #include "absl/memory/memory.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/match.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/span.h" |
| #include "common/analysis/file_analyzer.h" |
| #include "common/text/concrete_syntax_leaf.h" |
| #include "common/text/concrete_syntax_tree.h" |
| #include "common/text/constants.h" |
| #include "common/text/symbol.h" |
| #include "common/text/text_structure.h" |
| #include "common/text/token_info.h" |
| #include "common/text/token_info_test_util.h" |
| #include "common/text/token_stream_view.h" |
| #include "common/text/tree_utils.h" |
| #include "common/util/casts.h" |
| #include "common/util/logging.h" |
| #include "verilog/analysis/verilog_excerpt_parse.h" |
| #include "verilog/parser/verilog_token_enum.h" |
| |
| #undef EXPECT_OK |
| #define EXPECT_OK(value) EXPECT_TRUE((value).ok()) |
| #undef ASSERT_OK |
| #define ASSERT_OK(value) ASSERT_TRUE((value).ok()) |
| |
| namespace verilog { |
| namespace { |
| |
| using testing::SizeIs; |
| using verible::AnalysisPhase; |
| using verible::ConcreteSyntaxTree; |
| using verible::down_cast; |
| using verible::FindFirstSubtree; |
| using verible::Symbol; |
| using verible::SymbolPtr; |
| using verible::SyntaxTreeLeaf; |
| using verible::TokenInfo; |
| using verible::TokenInfoTestData; |
| |
| bool TreeContainsToken(const ConcreteSyntaxTree& tree, const TokenInfo& token) { |
| const auto* matching_leaf = |
| FindFirstSubtree(tree.get(), [&](const Symbol& symbol) { |
| if (symbol.Kind() != verible::SymbolKind::kLeaf) return false; |
| const auto* leaf_ptr = down_cast<const SyntaxTreeLeaf*>(&symbol); |
| return leaf_ptr->get() == token; |
| }); |
| return matching_leaf != nullptr; |
| } |
| |
| void DiagnosticMessagesContainFilename(const VerilogAnalyzer& analyzer, |
| absl::string_view filename) { |
| const std::vector<std::string> syntax_error_messages( |
| analyzer.LinterTokenErrorMessages()); |
| for (const auto& message : syntax_error_messages) { |
| EXPECT_TRUE(absl::StrContains(message, filename)); |
| } |
| } |
| |
| // AnalyzeVerilog tests: |
| // More extensive tests are in verilog_parser_unittest.cc. |
| |
| TEST(AnalyzeVerilogTest, EmptyText) { |
| const auto analyzer_ptr = absl::make_unique<VerilogAnalyzer>("", "<noname>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->Analyze()); |
| } |
| |
| // The following tests check Verilog lexer returns proper diagnostics: |
| |
| // Tests that invalid symbol identifier is rejected. |
| TEST(AnalyzeVerilogLexerTest, RejectsBadId) { |
| const auto analyzer_ptr = absl::make_unique<VerilogAnalyzer>( |
| "module 321foo;\nendmodule\n", "<noname>"); |
| const auto status = ABSL_DIE_IF_NULL(analyzer_ptr)->Tokenize(); |
| EXPECT_FALSE(status.ok()); |
| EXPECT_FALSE(analyzer_ptr->LexStatus().ok()); |
| const auto& rejects = analyzer_ptr->GetRejectedTokens(); |
| EXPECT_THAT(rejects, SizeIs(1)); |
| EXPECT_EQ(rejects.front().phase, AnalysisPhase::kLexPhase); |
| DiagnosticMessagesContainFilename(*analyzer_ptr, "<noname>"); |
| } |
| |
| // Tests that invalid macro identifier is rejected. |
| TEST(AnalyzeVerilogLexerTest, RejectsMacroBadId) { |
| const auto analyzer_ptr = |
| absl::make_unique<VerilogAnalyzer>("`321foo(a, b, c)\n", "<noname>"); |
| const auto status = ABSL_DIE_IF_NULL(analyzer_ptr)->Tokenize(); |
| EXPECT_FALSE(status.ok()); |
| EXPECT_FALSE(analyzer_ptr->LexStatus().ok()); |
| const auto& rejects = analyzer_ptr->GetRejectedTokens(); |
| EXPECT_THAT(rejects, SizeIs(1)); |
| EXPECT_EQ(rejects.front().phase, AnalysisPhase::kLexPhase); |
| DiagnosticMessagesContainFilename(*analyzer_ptr, "<noname>"); |
| } |
| |
| // The following tests check that standalone Verilog expression parsing work. |
| // More extensive tests are in verilog_parser_unittest.cc. |
| |
| TEST(AnalyzeVerilogExpressionTest, ParsesZero) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogExpression("0", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogExpressionTest, ParsesEmptyString) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogExpression("\"\"", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogExpressionTest, ParsesNonEmptyString) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogExpression("\"nevermore.\"", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogExpressionTest, ParsesBinaryOp) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogExpression("a+b", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogExpressionTest, ParsesParenBinaryOp) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogExpression("(a+b)", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogExpressionTest, ParsesUnfinishedOp) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogExpression("a+", "<file>"); |
| auto status = ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus(); |
| EXPECT_FALSE(status.ok()); |
| const auto& rejects = analyzer_ptr->GetRejectedTokens(); |
| EXPECT_THAT(rejects, SizeIs(1)); |
| EXPECT_EQ(rejects.front().phase, AnalysisPhase::kParsePhase); |
| DiagnosticMessagesContainFilename(*analyzer_ptr, "<file>"); |
| } |
| |
| TEST(AnalyzeVerilogExpressionTest, Unbalanced) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogExpression("(a+c", "<file>"); |
| auto status = ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus(); |
| EXPECT_FALSE(status.ok()); |
| const auto& rejects = analyzer_ptr->GetRejectedTokens(); |
| EXPECT_THAT(rejects, SizeIs(1)); |
| EXPECT_EQ(rejects.front().phase, AnalysisPhase::kParsePhase); |
| DiagnosticMessagesContainFilename(*analyzer_ptr, "<file>"); |
| } |
| |
| TEST(AnalyzeVerilogExpressionTest, ParsesConcat) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogExpression("{cde, fgh, ijk}", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogExpressionTest, ParsesFunctionCall) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogExpression("average(1, 2, \"five\")", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogExpressionTest, ParsesMacroCall) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogExpression("`MACRO(a+b, 1)", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogExpressionTest, ParsesMacroCallWithBadId) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogExpression("`MACRO(a+b, 1bad_id)", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogExpressionTest, RejectsModuleItemAttack) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogExpression("a; wire foo", "<file>"); |
| auto status = ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus(); |
| EXPECT_FALSE(status.ok()); |
| const auto& rejects = analyzer_ptr->GetRejectedTokens(); |
| EXPECT_THAT(rejects, SizeIs(2)); |
| EXPECT_EQ(rejects.front().phase, AnalysisPhase::kParsePhase); |
| DiagnosticMessagesContainFilename(*analyzer_ptr, "<file>"); |
| } |
| |
| // The following tests check that standalone Verilog module-body parsing works. |
| // More extensive tests are in verilog_parser_unittest.cc. |
| TEST(AnalyzeVerilogModuleBodyTest, ParsesEmptyString) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogModuleBody("", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogModuleBodyTest, ParsesWireDeclarations) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogModuleBody("wire fire;", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogModuleBodyTest, ParsesInstance) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogModuleBody("type_of_thing #(16) foo(a, b);", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogModuleBodyTest, ParsesInitialBlock) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = AnalyzeVerilogModuleBody( |
| "initial begin\n" |
| " a = 1;\n" |
| "end", |
| "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogModuleBodyTest, ParsesMultipleItems) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = AnalyzeVerilogModuleBody( |
| "wire [7:0] bar;\n" |
| "initial begin\n" |
| " a = 1;\n" |
| "end", |
| "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| // The following tests check for class-body parsing. |
| TEST(AnalyzeVerilogClassBodyTest, ParsesEmptyString) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogClassBody("", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogClassBodyTest, ParsesIntegerField) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogClassBody("integer foo;", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogClassBodyTest, ParsesMethod) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = AnalyzeVerilogClassBody( |
| "virtual function bar();\nendfunction\n", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogClassBodyTest, ParsesConstructor) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = AnalyzeVerilogClassBody( |
| "function new();\nx = 1;\nendfunction : new\n", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogClassBodyTest, RejectsModuleItem) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogClassBody("initial begin\nend\n", "<file>"); |
| const auto status = ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus(); |
| EXPECT_FALSE(status.ok()); |
| const auto& rejects = analyzer_ptr->GetRejectedTokens(); |
| EXPECT_THAT(rejects, SizeIs(1)); |
| const auto& first_reject = rejects.front(); |
| EXPECT_EQ(first_reject.phase, AnalysisPhase::kParsePhase); |
| EXPECT_EQ(first_reject.token_info.text(), "initial"); |
| DiagnosticMessagesContainFilename(*analyzer_ptr, "<file>"); |
| } |
| |
| // The following tests check for package-body parsing. |
| TEST(AnalyzeVerilogPackageBodyTest, ParsesEmptyString) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogPackageBody("", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogPackageBodyTest, ParsesExportDeclaration) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogPackageBody("export foo::bar;\n", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogPackageBodyTest, ParsesParameter) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogPackageBody("parameter int kFoo = 42;\n", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogClassBodyTest, RejectsWireDeclaration) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| AnalyzeVerilogClassBody("wire [3:0] bar;\n", "<file>"); |
| const auto status = ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus(); |
| EXPECT_FALSE(status.ok()); |
| const auto& rejects = analyzer_ptr->GetRejectedTokens(); |
| EXPECT_THAT(rejects, SizeIs(1)); |
| EXPECT_EQ(rejects.front().phase, AnalysisPhase::kParsePhase); |
| DiagnosticMessagesContainFilename(*analyzer_ptr, "<file>"); |
| } |
| |
| // The following tests verify that parser mode selection works. |
| TEST(AnalyzeVerilogAutomaticMode, NormalModeEmptyText) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| VerilogAnalyzer::AnalyzeAutomaticMode("", "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogAutomaticMode, NormalModeModule) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| VerilogAnalyzer::AnalyzeAutomaticMode("module rrr;\nendmodule\n", |
| "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogAutomaticMode, NormalModeModuleInvalidSelection) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| VerilogAnalyzer::AnalyzeAutomaticMode( |
| "// verilog_syntax: does-not-exist-mode\n" |
| "module rrr;\nendmodule\n", |
| "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogAutomaticMode, StatementsMode) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| VerilogAnalyzer::AnalyzeAutomaticMode( |
| "// verilog_syntax: parse-as-statements\n" |
| "foo_bar();\n" |
| "if (1) begin\n" |
| " x = 0;\n" |
| "end\n", |
| "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogAutomaticMode, ModuleBodyMode) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| VerilogAnalyzer::AnalyzeAutomaticMode( |
| "// verilog_syntax: parse-as-module-body\n" |
| "wire x;\n" |
| "initial begin\n" |
| " x = 0;\n" |
| "end\n", |
| "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogAutomaticMode, ModuleBodyModeSyntaxError) { |
| const absl::string_view filename = "wirefile.sv"; |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| VerilogAnalyzer::AnalyzeAutomaticMode( |
| "// verilog_syntax: parse-as-module-body\n" |
| "wire wire;\n", |
| filename); |
| const auto status = ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus(); |
| EXPECT_FALSE(status.ok()); |
| DiagnosticMessagesContainFilename(*analyzer_ptr, filename); |
| } |
| |
| TEST(AnalyzeVerilogAutomaticMode, ClassBodyMode) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| VerilogAnalyzer::AnalyzeAutomaticMode( |
| "// verilog_syntax: parse-as-class-body\n" |
| "function new();\n" |
| " x = 0;\n" |
| "endfunction\n", |
| "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogAutomaticMode, PackageBodyMode) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| VerilogAnalyzer::AnalyzeAutomaticMode( |
| "// verilog_syntax: parse-as-package-body\n" |
| "export xx::*;\n", |
| "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| TEST(AnalyzeVerilogAutomaticMode, PropertySpecMode) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| VerilogAnalyzer::AnalyzeAutomaticMode( |
| "// verilog_syntax: parse-as-property-spec\n" |
| "bb|=>cc\n", |
| "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()); |
| } |
| |
| // Tests that automatic mode parsing can detect that some first failing |
| // keywords will trigger (successful) re-parsing as a module-body. |
| TEST(AnalyzeVerilogAutomaticMode, InferredModuleBodyMode) { |
| constexpr const char* test_cases[] = { |
| "always @(posedge clk) begin x<=y; end\n", |
| "initial begin x = 0; end;\n", |
| }; |
| for (const char* code : test_cases) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| VerilogAnalyzer::AnalyzeAutomaticMode(code, "<file>"); |
| EXPECT_OK(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus()) << "code was:\n" |
| << code; |
| } |
| } |
| |
| struct TestCase { |
| const char* code; |
| bool valid; |
| }; |
| |
| // Tests that various invalid input does not crash. |
| TEST(AnalyzeVerilogAutomaticMode, InvalidInputs) { |
| constexpr TestCase test_cases[] = { |
| {"`s(\n", false}, |
| {"`s(}\n", false}, |
| {"`s(};\n", false}, |
| {"`s(};if\n", false}, |
| {"`s(};if(\n", false}, |
| {"`s(};if(k\n", false}, |
| {"`s(};if(k)\n", |
| // valid because it is macro call is un-expanded, closed at ')' |
| true}, |
| {"`s(};if(k);\n", true}, |
| }; |
| for (const auto& test : test_cases) { |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| VerilogAnalyzer::AnalyzeAutomaticMode(test.code, "<file>"); |
| EXPECT_EQ(ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus().ok(), test.valid) |
| << "code was:\n" |
| << test.code; |
| } |
| } |
| |
| // Tests that when retrying parsing in a different mode fails, we get the result |
| // of the analyzer that got further before the first syntax error. |
| TEST(AnalyzeVerilogAutomaticMode, InferredModuleBodyModeFarthestFirstError) { |
| const TokenInfoTestData test{ |
| "always @(posedge clk) begin ", {TK_module, "module"}, " x<=y; end\n"}; |
| std::unique_ptr<VerilogAnalyzer> analyzer_ptr = |
| VerilogAnalyzer::AnalyzeAutomaticMode(test.code, "<file>"); |
| auto status = ABSL_DIE_IF_NULL(analyzer_ptr)->ParseStatus(); |
| ASSERT_FALSE(status.ok()); |
| const auto& rejects = analyzer_ptr->GetRejectedTokens(); |
| ASSERT_FALSE(rejects.empty()); |
| const auto& token_info = rejects.front().token_info; |
| // Expect the first syntax error of the retried parsing: |
| const auto expected_tokens = |
| test.FindImportantTokens(analyzer_ptr->Data().Contents()); |
| ASSERT_EQ(expected_tokens.size(), 1); |
| EXPECT_EQ(token_info, expected_tokens.front()); |
| } |
| |
| // The following tests cover integration between parsing Verilog |
| // and verible::FileAnalyzer::FocusOnSubtreeSpanningSubstring |
| // and verible::FileAnalyzer::ExpandSubtrees. |
| // This is done in lieu of hand-crafting fake FileAnalyzer objects |
| // which would be very tedious without using a real parser. |
| // TODO(b/69043298): implement test utilites for building fake FileAnalyzer |
| // objects (with coherent token stream and syntax tree) *without* relying |
| // on a real language parser. |
| |
| // Test that an empty macro arg doesn't expand. |
| TEST(VerilogAnalyzerExpandsMacroArgsTest, NoArg) { |
| const TokenInfoTestData test = {{MacroCallId, "`FOOBAR"}, "()\n"}; |
| const auto analyzer = |
| absl::make_unique<VerilogAnalyzer>(test.code, "<<inline>>"); |
| EXPECT_OK(analyzer->Analyze()); |
| const ConcreteSyntaxTree& tree = analyzer->SyntaxTree(); |
| const auto search_tokens = |
| test.FindImportantTokens(analyzer->Data().Contents()); |
| ASSERT_EQ(search_tokens.size(), 1); |
| EXPECT_TRUE(TreeContainsToken(tree, search_tokens[0])); |
| } |
| |
| // Test that a space macro arg doesn't expand. |
| TEST(VerilogAnalyzerExpandsMacroArgsTest, SpaceArg) { |
| const TokenInfoTestData test = {" ", {MacroCallId, "`FOOBAR"}, "( )\n"}; |
| const auto analyzer = |
| absl::make_unique<VerilogAnalyzer>(test.code, "<<inline>>"); |
| EXPECT_OK(analyzer->Analyze()); |
| const ConcreteSyntaxTree& tree = analyzer->SyntaxTree(); |
| const auto search_tokens = |
| test.FindImportantTokens(analyzer->Data().Contents()); |
| ASSERT_EQ(search_tokens.size(), 1); |
| EXPECT_TRUE(TreeContainsToken(tree, search_tokens[0])); |
| } |
| |
| // Test that comma-separated blanks don't expand. |
| TEST(VerilogAnalyzerExpandsMacroArgsTest, CommaSeparatedBlankArg) { |
| const TokenInfoTestData test = {"`FOOBAR( ", ',', " )\n"}; |
| const auto analyzer = |
| absl::make_unique<VerilogAnalyzer>(test.code, "<<inline>>"); |
| EXPECT_OK(analyzer->Analyze()); |
| const ConcreteSyntaxTree& tree = analyzer->SyntaxTree(); |
| const auto search_tokens = |
| test.FindImportantTokens(analyzer->Data().Contents()); |
| ASSERT_EQ(search_tokens.size(), 1); |
| EXPECT_TRUE(TreeContainsToken(tree, search_tokens[0])); |
| } |
| |
| // Test that a non-expression macro arg doesn't expand. |
| TEST(VerilogAnalyzerExpandsMacroArgsTest, NonExprArg) { |
| // 'module' is a Verilog keyword, but macro argument text remains unlexed |
| // when it does not parse as an expression. |
| const TokenInfoTestData test = {"`FOOBAR(", {MacroArg, "module"}, ")\n"}; |
| const auto analyzer = |
| absl::make_unique<VerilogAnalyzer>(test.code, "<<inline>>"); |
| EXPECT_OK(analyzer->Analyze()); |
| const ConcreteSyntaxTree& tree = analyzer->SyntaxTree(); |
| const auto search_tokens = |
| test.FindImportantTokens(analyzer->Data().Contents()); |
| ASSERT_EQ(search_tokens.size(), 1); |
| EXPECT_TRUE(TreeContainsToken(tree, search_tokens[0])); |
| } |
| |
| // Test that an integer expression macro arg expands properly. |
| TEST(VerilogAnalyzerExpandsMacroArgsTest, IntegerArg) { |
| const TokenInfoTestData test = {"`FOO(", {TK_DecNumber, "123"}, ")\n"}; |
| const auto analyzer = |
| absl::make_unique<VerilogAnalyzer>(test.code, "<<inline>>"); |
| EXPECT_OK(analyzer->Analyze()); |
| const ConcreteSyntaxTree& tree = analyzer->SyntaxTree(); |
| const auto search_tokens = |
| test.FindImportantTokens(analyzer->Data().Contents()); |
| ASSERT_EQ(search_tokens.size(), 1); |
| EXPECT_TRUE(TreeContainsToken(tree, search_tokens[0])); |
| } |
| |
| // Test that an identifier expression macro arg expands properly. |
| TEST(VerilogAnalyzerExpandsMacroArgsTest, IdentifierArg) { |
| const TokenInfoTestData test = {"`FOO(", {SymbolIdentifier, "bar"}, ")\n"}; |
| const auto analyzer = |
| absl::make_unique<VerilogAnalyzer>(test.code, "<<inline>>"); |
| EXPECT_OK(analyzer->Analyze()); |
| const ConcreteSyntaxTree& tree = analyzer->SyntaxTree(); |
| const auto search_tokens = |
| test.FindImportantTokens(analyzer->Data().Contents()); |
| ASSERT_EQ(search_tokens.size(), 1); |
| EXPECT_TRUE(TreeContainsToken(tree, search_tokens[0])); |
| } |
| |
| // Test that an macro id macro arg expands properly. |
| TEST(VerilogAnalyzerExpandsMacroArgsTest, MacroIdentifierArg) { |
| const TokenInfoTestData test = {"`FOO(", {MacroIdentifier, "`bar"}, ")\n"}; |
| const auto analyzer = |
| absl::make_unique<VerilogAnalyzer>(test.code, "<<inline>>"); |
| EXPECT_OK(analyzer->Analyze()); |
| const ConcreteSyntaxTree& tree = analyzer->SyntaxTree(); |
| const auto search_tokens = |
| test.FindImportantTokens(analyzer->Data().Contents()); |
| ASSERT_EQ(search_tokens.size(), 1); |
| EXPECT_TRUE(TreeContainsToken(tree, search_tokens[0])); |
| } |
| |
| // Test that a string expression macro arg expands properly. |
| TEST(VerilogAnalyzerExpandsMacroArgsTest, StringArg) { |
| const TokenInfoTestData test = { |
| "`FOO(", {TK_StringLiteral, "\"hello\""}, ")\n"}; |
| const auto analyzer = |
| absl::make_unique<VerilogAnalyzer>(test.code, "<<inline>>"); |
| EXPECT_OK(analyzer->Analyze()); |
| const ConcreteSyntaxTree& tree = analyzer->SyntaxTree(); |
| const auto search_tokens = |
| test.FindImportantTokens(analyzer->Data().Contents()); |
| ASSERT_EQ(search_tokens.size(), 1); |
| EXPECT_TRUE(TreeContainsToken(tree, search_tokens[0])); |
| } |
| |
| // Test that an eval string expression macro arg expands properly. |
| TEST(VerilogAnalyzerExpandsMacroArgsTest, EvalStringArg) { |
| const TokenInfoTestData test = { |
| "`FOO(", {TK_EvalStringLiteral, "`\"`hello(world)`\""}, ")\n"}; |
| const auto analyzer = |
| absl::make_unique<VerilogAnalyzer>(test.code, "<<inline>>"); |
| EXPECT_OK(analyzer->Analyze()); |
| const ConcreteSyntaxTree& tree = analyzer->SyntaxTree(); |
| const auto search_tokens = |
| test.FindImportantTokens(analyzer->Data().Contents()); |
| ASSERT_EQ(search_tokens.size(), 1); |
| EXPECT_TRUE(TreeContainsToken(tree, search_tokens[0])); |
| } |
| |
| // Test that an binary expression macro arg expands properly. |
| TEST(VerilogAnalyzerExpandsMacroArgsTest, BinaryExprArg) { |
| const TokenInfoTestData test = { |
| "`FOO(", {TK_DecNumber, "1"}, '+', {TK_DecNumber, "3"}, ")\n"}; |
| const auto analyzer = |
| absl::make_unique<VerilogAnalyzer>(test.code, "<<inline>>"); |
| EXPECT_OK(analyzer->Analyze()); |
| const ConcreteSyntaxTree& tree = analyzer->SyntaxTree(); |
| const auto search_tokens = |
| test.FindImportantTokens(analyzer->Data().Contents()); |
| ASSERT_EQ(search_tokens.size(), 3); |
| for (const auto search_token : search_tokens) { |
| EXPECT_TRUE(TreeContainsToken(tree, search_token)); |
| } |
| } |
| |
| // Test that a function call expression macro arg expands properly. |
| TEST(VerilogAnalyzerExpandsMacroArgsTest, FunctionCallArg) { |
| const TokenInfoTestData test = {"`FOO(", {SymbolIdentifier, "square_root"}, |
| '(', {TK_DecNumber, "0"}, |
| ')', ")\n"}; |
| const auto analyzer = |
| absl::make_unique<VerilogAnalyzer>(test.code, "<<inline>>"); |
| EXPECT_OK(analyzer->Analyze()); |
| const ConcreteSyntaxTree& tree = analyzer->SyntaxTree(); |
| const auto search_tokens = |
| test.FindImportantTokens(analyzer->Data().Contents()); |
| ASSERT_EQ(search_tokens.size(), 4); |
| for (const auto search_token : search_tokens) { |
| EXPECT_TRUE(TreeContainsToken(tree, search_token)); |
| } |
| } |
| |
| // Test that a list of expression macro args expands properly. |
| TEST(VerilogAnalyzerExpandsMacroArgsTest, MultipleExpressions) { |
| const TokenInfoTestData test = { |
| "`FOO(", {TK_DecNumber, "9"}, ",", {SymbolIdentifier, "aa"}, ")\n"}; |
| const auto analyzer = |
| absl::make_unique<VerilogAnalyzer>(test.code, "<<inline>>"); |
| EXPECT_OK(analyzer->Analyze()); |
| const ConcreteSyntaxTree& tree = analyzer->SyntaxTree(); |
| const auto search_tokens = |
| test.FindImportantTokens(analyzer->Data().Contents()); |
| ASSERT_EQ(search_tokens.size(), 2); |
| for (const auto search_token : search_tokens) { |
| EXPECT_TRUE(TreeContainsToken(tree, search_token)); |
| } |
| } |
| |
| // Test that nested macro calls expand properly. |
| TEST(VerilogAnalyzerExpandsMacroArgsTest, MacroCall) { |
| const TokenInfoTestData test = { |
| "`FOO(", {MacroCallId, "`BAR"}, "(", {SymbolIdentifier, "abc"}, "))\n"}; |
| const auto analyzer = |
| absl::make_unique<VerilogAnalyzer>(test.code, "<<inline>>"); |
| EXPECT_OK(analyzer->Analyze()); |
| const ConcreteSyntaxTree& tree = analyzer->SyntaxTree(); |
| const auto search_tokens = |
| test.FindImportantTokens(analyzer->Data().Contents()); |
| ASSERT_EQ(search_tokens.size(), 2); |
| for (const auto search_token : search_tokens) { |
| EXPECT_TRUE(TreeContainsToken(tree, search_token)); |
| } |
| } |
| |
| // Test that deeply nested macro calls expand properly. |
| TEST(VerilogAnalyzerExpandsMacroArgsTest, MacroCallNested) { |
| const TokenInfoTestData test = { |
| "`FOO(", {MacroCallId, "`BAR"}, "(", {MacroCallId, "`BAZ"}, |
| "(", {SymbolIdentifier, "abc"}, ")))\n"}; |
| const auto analyzer = |
| absl::make_unique<VerilogAnalyzer>(test.code, "<<inline>>"); |
| EXPECT_OK(analyzer->Analyze()); |
| const ConcreteSyntaxTree& tree = analyzer->SyntaxTree(); |
| const auto search_tokens = |
| test.FindImportantTokens(analyzer->Data().Contents()); |
| ASSERT_EQ(search_tokens.size(), 3); |
| for (const auto search_token : search_tokens) { |
| EXPECT_TRUE(TreeContainsToken(tree, search_token)); |
| } |
| } |
| |
| // Test that deeply nested macro calls expand properly. |
| TEST(VerilogAnalyzerExpandsMacroArgsTest, MultipleMacroCalls) { |
| const TokenInfoTestData test = {"`FOO(", {MacroCallId, "`BAR"}, |
| "(", {TK_DecNumber, "33"}, |
| ")", ',', |
| " ", {MacroCallId, "`BAZ"}, |
| "(", {SymbolIdentifier, "abc"}, |
| "))\n"}; |
| const auto analyzer = |
| absl::make_unique<VerilogAnalyzer>(test.code, "<<inline>>"); |
| EXPECT_OK(analyzer->Analyze()); |
| const ConcreteSyntaxTree& tree = analyzer->SyntaxTree(); |
| const auto search_tokens = |
| test.FindImportantTokens(analyzer->Data().Contents()); |
| ASSERT_EQ(search_tokens.size(), 5); |
| for (const auto search_token : search_tokens) { |
| EXPECT_TRUE(TreeContainsToken(tree, search_token)); |
| } |
| } |
| |
| // Helper class for testing internals. |
| class VerilogAnalyzerInternalsTest : public testing::Test, |
| public VerilogAnalyzer { |
| public: |
| VerilogAnalyzerInternalsTest() : VerilogAnalyzer("", "") {} |
| }; |
| |
| // Tests that parser-selection directive is properly detected. |
| TEST_F(VerilogAnalyzerInternalsTest, ScanParsingModeDirective) { |
| const std::pair<std::string, absl::string_view> test_cases[] = { |
| // code, expected parsing mode |
| {"", ""}, |
| {"\n", ""}, |
| {"// nothing here\n", ""}, |
| {"/* or here */\n", ""}, |
| {"// verilog_syntax: super-mode\n", "super-mode"}, |
| {" // verilog_syntax: sub-mode \n", "sub-mode"}, |
| {"//verilog_syntax: sub-mode // blah\n", "sub-mode"}, |
| {"// verilogsyntax: foo-mode\n", ""}, // not spelled right |
| {"// VerilogSyntax: bar-mode\n", ""}, // not spelled right |
| {"/*verilog_syntax: foo-mode*/\n", "foo-mode"}, |
| {"/* verilog_syntax: foo-mode */\n", "foo-mode"}, |
| {"\n\n\n// verilog_syntax: super-mode\n", "super-mode"}, |
| {"// verilog_syntax: alpha-mode\n" // first wins |
| "// verilog_syntax: beta-mode\n", |
| "alpha-mode"}, |
| {"module foo;\n" // stops scanning after real tokens |
| "endmodule\n" |
| "// verilog_syntax: beta-mode\n", |
| ""}, |
| {"package foo;\n" // stops scanning after real tokens |
| "// verilog_syntax: delta-mode\n" |
| "endpackage\n", |
| ""}, |
| {"// regular comment\n" |
| "// verilog_syntax: beta-mode\n", |
| "beta-mode"}, |
| {"`ifndef FOO\n" // typical include guard |
| "`define FOO // this is FOO\n" |
| "// verilog_syntax: gamma-mode\n" // still scans up to here |
| "`endif // FOO\n", |
| "gamma-mode"}, |
| {"`ifdef FOO\n" // typical include guard |
| "`undef FOO\n" |
| "`elsif BAR\n" |
| "`else\n" |
| "// verilog_syntax: turbo-mode\n" // still scans up to here |
| "`endif\n", |
| "turbo-mode"}, |
| {"`MACRO\n" |
| "// verilog_syntax: moody-mode\n", |
| ""}, |
| {"`MACRO_CALL(arg1, arg2) // macro me\n" |
| "// verilog_syntax: evil-mode\n", |
| ""}, |
| }; |
| for (const auto& test : test_cases) { |
| VerilogAnalyzer analyzer(test.first, "<file>"); |
| const auto lexer_status = analyzer.Tokenize(); |
| EXPECT_OK(lexer_status); |
| absl::string_view mode = |
| ScanParsingModeDirective(analyzer.Data().TokenStream()); |
| EXPECT_EQ(mode, test.second) << " mismatched mode with input:\n" |
| << test.first; |
| } |
| } |
| |
| } // namespace |
| } // namespace verilog |