blob: 99c00ceeb6e38659862f856ae319fe07a1f8634a [file] [log] [blame]
// 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/preprocessor/verilog_preprocess.h"
#include <algorithm>
#include <map>
#include <vector>
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "common/text/macro_definition.h"
#include "common/text/token_info.h"
#include "common/util/container_util.h"
#include "common/util/file_util.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "verilog/analysis/verilog_analyzer.h"
#include "verilog/analysis/verilog_project.h"
#include "verilog/parser/verilog_lexer.h"
#include "verilog/parser/verilog_token_enum.h"
namespace verilog {
namespace {
using testing::ElementsAre;
using testing::Pair;
using testing::StartsWith;
using verible::container::FindOrNull;
using verible::file::CreateDir;
using verible::file::JoinPath;
using verible::file::testing::ScopedTestFile;
using FileOpener = VerilogPreprocess::FileOpener;
class LexerTester {
public:
LexerTester(absl::string_view text) : lexer_(text) {
for (lexer_.DoNextToken(); !lexer_.GetLastToken().isEOF();
lexer_.DoNextToken()) {
lexed_sequence_.push_back(lexer_.GetLastToken());
}
InitTokenStreamView(lexed_sequence_, &stream_view_);
}
verible::TokenStreamView GetTokenStreamView() const { return stream_view_; }
private:
VerilogLexer lexer_;
verible::TokenSequence lexed_sequence_;
verible::TokenStreamView stream_view_;
};
class PreprocessorTester {
public:
PreprocessorTester(absl::string_view text,
const VerilogPreprocess::Config& config)
: analyzer_(text, "<<inline-file>>", config),
status_(analyzer_.Analyze()) {}
explicit PreprocessorTester(absl::string_view text)
: PreprocessorTester(text, VerilogPreprocess::Config()) {}
const VerilogPreprocessData& PreprocessorData() const {
return analyzer_.PreprocessorData();
}
const verible::TextStructureView& Data() const { return analyzer_.Data(); }
const absl::Status& Status() const { return status_; }
const VerilogAnalyzer& Analyzer() const { return analyzer_; }
private:
VerilogAnalyzer analyzer_;
const absl::Status status_;
};
struct FailTest {
absl::string_view input;
int offset;
};
TEST(VerilogPreprocessTest, InvalidPreprocessorInputs) {
const FailTest test_cases[] = {
{"`define\n", 8}, // unterminated macro definition
{"\n\n`define\n", 10}, // unterminated macro definition
{"`define 789\n", 8}, // expect identifier for macro name
{"`define 789 non-sense\n", 8}, // expect identifier for macro name
{"`define 789 \\\nnon-sense\n", 8}, // expect identifier for macro name
{"`define FOO(\n", 13}, // unterminated parameter list
{"`define FOO(234\n", 12}, // invalid parameter name
{"`define FOO(234)\n", 12}, // invalid parameter name
{"`define FOO(aaa\n", 16}, // unterminated parameter list
{"`define FOO(aaa;\n", 15}, // bad parameter separator
{"`define FOO(aaa bbb\n", 16}, // bad parameter separator
{"`define FOO(aaa bbb)\n", 16}, // bad parameter separator
{"`define FOO(aaa+bbb)\n", 15}, // bad parameter separator
{"`define FOO(aaa.zzz\n", 15}, // bad parameter separator
{"`define FOO(aaa.zzz)\n", 15}, // bad parameter separator
{"`define FOO(aaa,\n", 17}, // unterminated parameter list
{"`define FOO(aaa,)\n", 16}, // missing parameter name
{"`define FOO(,,)\n", 12}, // missing parameter name
{"`define FOO(aaa, 345)\n", 17}, // invalid parameter name
{"`define FOO(aaa=\n", 17}, // unterminated default parameter
{"`define FOO(aaa =\n", 18}, // unterminated default parameter
{"`define FOO(aaa = 9\n", 20}, // expecting ',' or ')'
{"`define FOO(aaa = 9, bbb =\n", 27}, // unterminated parameter list
{"`define FOO(aa = 9, bb = 2\n", 27}, // expecting ',' or ')'
};
for (const auto& test_case : test_cases) {
PreprocessorTester tester(test_case.input);
EXPECT_FALSE(tester.Status().ok())
<< "Expected preprocess to fail on invalid input: \"" << test_case.input
<< "\"";
const auto& rejected_tokens = tester.Analyzer().GetRejectedTokens();
ASSERT_FALSE(rejected_tokens.empty())
<< "on invalid input: \"" << test_case.input << "\"";
const int rejected_token_offset =
rejected_tokens[0].token_info.left(tester.Analyzer().Data().Contents());
EXPECT_EQ(rejected_token_offset, test_case.offset)
<< "on invalid input: \"" << test_case.input << "\"";
}
}
#define EXPECT_PARSE_OK() \
do { \
EXPECT_TRUE(tester.Status().ok()) << "Unexpected analyzer failure."; \
EXPECT_TRUE(tester.PreprocessorData().errors.empty()); \
EXPECT_TRUE(tester.Analyzer().GetRejectedTokens().empty()); \
} while (false)
// Verify that VerilogPreprocess works without any directives.
TEST(VerilogPreprocessTest, WorksWithoutDefinitions) {
absl::string_view test_cases[] = {
"",
"\n",
"module foo;\nendmodule\n",
"module foo(input x, output y);\nendmodule\n",
};
for (const auto& test_case : test_cases) {
PreprocessorTester tester(test_case);
EXPECT_PARSE_OK();
const auto& definitions = tester.PreprocessorData().macro_definitions;
EXPECT_TRUE(definitions.empty());
}
}
TEST(VerilogPreprocessTest, OneMacroDefinitionNoParamsNoValue) {
absl::string_view test_cases[] = {
"`define FOOOO\n",
"`define FOOOO\n",
"module foo;\nendmodule\n"
"`define FOOOO\n",
"`define FOOOO\n"
"module foo;\nendmodule\n",
};
for (const auto& test_case : test_cases) {
PreprocessorTester tester(test_case);
EXPECT_PARSE_OK();
const auto& definitions = tester.PreprocessorData().macro_definitions;
EXPECT_THAT(definitions, ElementsAre(Pair("FOOOO", testing::_)));
auto macro = FindOrNull(definitions, "FOOOO");
ASSERT_NE(macro, nullptr);
EXPECT_EQ(macro->DefinitionText().text(), "");
EXPECT_FALSE(macro->IsCallable());
EXPECT_TRUE(macro->Parameters().empty());
}
}
TEST(VerilogPreprocessTest, OneMacroDefinitionNoParamsSimpleValue) {
PreprocessorTester tester(
"module foo;\nendmodule\n"
"`define FOOOO \"bar\"\n");
EXPECT_PARSE_OK();
const auto& definitions = tester.PreprocessorData().macro_definitions;
EXPECT_THAT(definitions, ElementsAre(Pair("FOOOO", testing::_)));
auto macro = FindOrNull(definitions, "FOOOO");
ASSERT_NE(macro, nullptr);
EXPECT_EQ(macro->DefinitionText().text(), "\"bar\"");
EXPECT_FALSE(macro->IsCallable());
EXPECT_TRUE(macro->Parameters().empty());
}
TEST(VerilogPreprocessTest, OneMacroDefinitionOneParamWithValue) {
PreprocessorTester tester(
"module foo;\nendmodule\n"
"`define FOOOO(x) (x+1)\n");
EXPECT_PARSE_OK();
const auto& definitions = tester.PreprocessorData().macro_definitions;
EXPECT_THAT(definitions, ElementsAre(Pair("FOOOO", testing::_)));
auto macro = FindOrNull(definitions, "FOOOO");
ASSERT_NE(macro, nullptr);
EXPECT_EQ(macro->DefinitionText().text(), "(x+1)");
EXPECT_TRUE(macro->IsCallable());
const auto& params = macro->Parameters();
EXPECT_EQ(params.size(), 1);
const auto& param(params[0]);
EXPECT_EQ(param.name.text(), "x");
EXPECT_FALSE(param.HasDefaultText());
}
TEST(VerilogPreprocessTest, OneMacroDefinitionOneParamDefaultWithValue) {
PreprocessorTester tester(
"module foo;\nendmodule\n"
"`define FOOOO(x=22) (x+3)\n");
EXPECT_PARSE_OK();
const auto& definitions = tester.PreprocessorData().macro_definitions;
EXPECT_THAT(definitions, ElementsAre(Pair("FOOOO", testing::_)));
auto macro = FindOrNull(definitions, "FOOOO");
ASSERT_NE(macro, nullptr);
EXPECT_EQ(macro->DefinitionText().text(), "(x+3)");
EXPECT_TRUE(macro->IsCallable());
const auto& params = macro->Parameters();
EXPECT_EQ(params.size(), 1);
const auto& param(params[0]);
EXPECT_EQ(param.name.text(), "x");
EXPECT_TRUE(param.HasDefaultText());
EXPECT_EQ(param.default_value.text(), "22");
}
TEST(VerilogPreprocessTest, TwoMacroDefinitions) {
PreprocessorTester tester(
"`define BAAAAR(y, z) (y*z)\n"
"`define FOOOO(x=22) (x+3)\n");
EXPECT_PARSE_OK();
const auto& definitions = tester.PreprocessorData().macro_definitions;
EXPECT_THAT(definitions, ElementsAre(Pair("BAAAAR", testing::_),
Pair("FOOOO", testing::_)));
{
auto macro = FindOrNull(definitions, "BAAAAR");
ASSERT_NE(macro, nullptr);
EXPECT_TRUE(macro->IsCallable());
const auto& params = macro->Parameters();
EXPECT_EQ(params.size(), 2);
}
{
auto macro = FindOrNull(definitions, "FOOOO");
ASSERT_NE(macro, nullptr);
EXPECT_TRUE(macro->IsCallable());
const auto& params = macro->Parameters();
EXPECT_EQ(params.size(), 1);
}
}
TEST(VerilogPreprocessTest, UndefMacro) {
PreprocessorTester tester(
"`define FOO 42\n"
"`undef FOO");
EXPECT_PARSE_OK();
const auto& definitions = tester.PreprocessorData().macro_definitions;
EXPECT_EQ(definitions.size(), 0);
}
TEST(VerilogPreprocessTest, UndefNonexistentMacro) {
PreprocessorTester tester("`undef FOO");
EXPECT_PARSE_OK();
const auto& definitions = tester.PreprocessorData().macro_definitions;
EXPECT_EQ(definitions.size(), 0);
EXPECT_TRUE(tester.PreprocessorData().warnings.empty()); // not a problem.
}
TEST(VerilogPreprocessTest, RedefineMacroWarning) {
PreprocessorTester tester(
"`define FOO 1\n"
"`define FOO 2\n");
EXPECT_PARSE_OK();
const auto& definitions = tester.PreprocessorData().macro_definitions;
EXPECT_EQ(definitions.size(), 1);
const auto& warnings = tester.PreprocessorData().warnings;
EXPECT_EQ(warnings.size(), 1);
EXPECT_EQ(warnings.front().error_message, "Re-defining macro");
}
// We might have different modes later, in which we remove the define tokens
// from the stream. Document the current default which registeres all the
// defines, but also does not filter out the define calls.
TEST(VerilogPreprocessTest, DefaultPreprocessorKeepsDefineInStream) {
PreprocessorTester tester(
"`define FOO\n"
"`define BAR(x) (x)\n"
"module x(); endmodule\n");
EXPECT_PARSE_OK();
const auto& definitions = tester.PreprocessorData().macro_definitions;
EXPECT_EQ(definitions.size(), 2);
// The original `define tokens are still in the stream
const auto& token_stream = tester.Data().GetTokenStreamView();
const int count_defines =
std::count_if(token_stream.begin(), token_stream.end(),
[](verible::TokenSequence::const_iterator t) {
return t->token_enum() == verilog_tokentype::PP_define;
});
EXPECT_EQ(count_defines, 2);
}
struct BranchFailTest {
absl::string_view input;
int offset;
absl::string_view expected_error;
};
TEST(VerilogPreprocessTest, IncompleteOrUnbalancedIfdef) {
const BranchFailTest test_cases[] = {
{"`endif", 0, "Unmatched `endif"},
{"`else", 0, "Unmatched `else"},
{"`elsif FOO", 0, "Unmatched `elsif"},
{"`ifdef", 6, "unexpected EOF where expecting macro name"},
{"`ifdef FOO\n`endif\n`endif", 18, "Unmatched `endif"},
{"`ifdef FOO\n`endif\n`else", 18, "Unmatched `else"},
{"`ifdef FOO\n`endif\n`elsif BAR", 18, "Unmatched `elsif"},
{"`ifdef FOO\n`else\n`else", 17, "Duplicate `else"},
{"`ifdef FOO\n`else\n`elsif BAR", 17, "`elsif after `else"},
{"`ifdef FOO\n`ifdef BAR`else\n`else", 27, "Duplicate `else"},
{"`ifdef FOO\n`else\n`ifdef BAR\n`endif", 11, "Unterminated preprocess"},
{"`ifdef FOO\n`elsif BAR\n", 11, "Unterminated preprocessing"},
{"`ifdef FOO\n`elsif BAR\n`else\n", 22, "Unterminated preprocessing"},
};
for (const BranchFailTest& test : test_cases) {
PreprocessorTester tester(
test.input, VerilogPreprocess::Config({.filter_branches = true}));
EXPECT_FALSE(tester.Status().ok());
ASSERT_GE(tester.PreprocessorData().errors.size(), 1);
const auto& error = tester.PreprocessorData().errors.front();
EXPECT_THAT(error.error_message, StartsWith(test.expected_error));
const int error_token_offset =
error.token_info.left(tester.Analyzer().Data().Contents());
EXPECT_EQ(error_token_offset, test.offset) << "Input: " << test.input;
}
}
struct RawAndFiltered {
absl::string_view description;
absl::string_view pp_input;
absl::string_view equivalent;
};
TEST(VerilogPreprocess, FilterPPBranches) {
const RawAndFiltered test_cases[] = {
{"[** Defined macro taking ifdef branch **]",
R"(
`define FOO 1
`ifdef FOO
module bar();
`else
module quux();
`endif
endmodule)",
// ...equivalent to
R"(
`define FOO 1
module bar();
endmodule)"},
{"[** Undefined macro taking else branch **]",
R"(
`ifdef FOO
module bar();
`else
module quux();
`endif
endmodule)",
// ...equivalent to
R"(
module quux();
endmodule)"},
{"[** Undefined macro taking else branch. defined value `undef-ed **]",
R"(
`define FOO
`undef FOO
`ifdef FOO
module bar();
`else
module quux();
`endif
endmodule)",
// ...equivalent to
R"(
`define FOO
`undef FOO
module quux();
endmodule)"},
{"[** Negative logic: Defined macro taking ifndef-else branch **]",
R"(
`define FOO 1
`ifndef FOO
module bar();
`else
module quux();
`endif
endmodule)",
// ...equivalent to
R"(
`define FOO 1
module quux();
endmodule)"},
{"[** Negative logic: Undefined macro taking ifndef branch **]",
R"(
`ifndef FOO
module bar();
`else
module quux();
`endif
endmodule)",
// ...equivalent to
R"(
module bar();
endmodule)"},
{"[** Elsif: choice of first branch **]",
R"(
`define FOO 1
`ifdef FOO
module foo(); endmodule
`elsif BAR
module bar(); endmodule
`endif)",
// ... equivalent to
R"(
`define FOO 1
module foo(); endmodule)"},
{"[** Elsif: choice of elsif branch **]",
R"(
`define BAR 1
`ifdef FOO
module foo(); endmodule
`elsif BAR
module bar(); endmodule
`endif)",
// ... equivalent to
R"(
`define BAR 1
module bar(); endmodule)"},
{"[** Elsif: no branch chosen **]",
R"(
`define BAZ 1
`ifdef FOO
module foo(); endmodule
`elsif BAR
module bar(); endmodule
`endif)",
// ... equivalent to
R"(
`define BAZ 1
)"},
{"[** Elsif: only first (`ifdef) matching branch chosen **]",
R"(
`define FOO 1
`define BAR 1
`define BAZ 1
`ifdef FOO
module foo(); endmodule
`elsif BAR
module bar(); endmodule
`elsif BAZ
module baz(); endmodule
`endif)",
// ... equivalent to
R"(
`define FOO 1
`define BAR 1
`define BAZ 1
module foo(); endmodule
)"},
{"[** Elsif: only first (`elsif) matching branch chosen **]",
R"(
`define BAR 1
`define BAZ 1
`define QUUX 1
`ifdef FOO
module foo(); endmodule
`elsif BAR
module bar(); endmodule
`elsif BAZ
module baz(); endmodule
`elsif QUUX
module quux(); endmodule
`endif)",
// ... equivalent to
R"(
`define BAR 1
`define BAZ 1
`define QUUX 1
module bar(); endmodule
)"},
{"[** Nested conditions **]",
R"(
`define BAR 1
`ifdef FOO
module foo(); endmodule
`ifdef BAR
module foo_bar(); endmodule
`else
module foo_nonbar(); endmodule
`endif
module post_foo(); endmodule
`else
module nonfoo(); endmodule
`ifdef BAR
module nonfoo_bar(); endmodule
`else
module nonfoo_nonbar(); endmodule;
`endif
module post_nonfoo(); endmodule
`endif)",
// ... equivalent to
R"(
`define BAR 1
module nonfoo(); endmodule
module nonfoo_bar(); endmodule
module post_nonfoo(); endmodule)"},
{"[** Meta-def: Macro defined in branch controls another branch **]",
R"(
`ifdef FOO
`define BAR 1
`undef FOOBAR
`else
`define BAZ 1
`undef FOOQUX
`endif
`ifdef BAR
module bar(); endmodule
`endif
`ifdef BAZ
module baz(); endmodule
`endif)",
// ...equivalent to
R"(
`define BAZ 1
`undef FOOQUX
module baz(); endmodule
)"}};
for (const RawAndFiltered& test : test_cases) {
PreprocessorTester with_filter(
test.pp_input, VerilogPreprocess::Config({.filter_branches = true}));
EXPECT_TRUE(with_filter.Status().ok())
<< with_filter.Status() << " " << test.description;
PreprocessorTester equivalent(
test.equivalent, VerilogPreprocess::Config({.filter_branches = false}));
EXPECT_TRUE(equivalent.Status().ok())
<< equivalent.Status() << " " << test.description;
const auto& filtered_stream = with_filter.Data().GetTokenStreamView();
const auto& equivalent_stream = equivalent.Data().GetTokenStreamView();
EXPECT_GT(filtered_stream.size(), 0) << test.description;
EXPECT_EQ(filtered_stream.size(), equivalent_stream.size())
<< test.description;
auto filtered_it = filtered_stream.begin();
auto equivalent_it = equivalent_stream.begin();
while (filtered_it != filtered_stream.end() &&
equivalent_it != equivalent_stream.end()) {
EXPECT_EQ((*filtered_it)->text(), (*equivalent_it)->text())
<< test.description;
EXPECT_EQ((*filtered_it)->token_enum(), (*equivalent_it)->token_enum())
<< test.description;
++filtered_it;
++equivalent_it;
}
}
}
TEST(VerilogPreprocessTest, MacroExpansion) {
const RawAndFiltered test_cases[] = {
{"[** Multi-tokens macros being correctly parsed **]",
R"(
`define ASSIGN1 =1
`define ASSIGN0 =0
module foo;
wire x`ASSIGN1;
wire y `ASSIGN0;
endmodule)",
// ...equivalent to
R"(
`define ASSIGN1 =1
`define ASSIGN0 =0
module foo;
wire x =1;
wire y =0;
endmodule)"},
{"[** Multi-tokens macros not empty after undefing **]",
R"(
`define XWIRE wire x
`define YWIRE wire y
module foo;
`XWIRE = 1;
`YWIRE = 0;
endmodule
`undef XWIRE
`undef YWIRE)",
// ...equivalent to
R"(
`define XWIRE wire x
`define YWIRE wire y
module foo;
wire x = 1;
wire y = 0;
endmodule
`undef XWIRE
`undef YWIRE)"},
{"[** Macros that contain other macro calls, redefining the inner macro "
"**]",
R"(
`define XWIRE wire x
`define YWIRE wire y
`define ASSIGN1XWIRE `XWIRE = 1;
`define ASSIGN0YWIRE `YWIRE = 0;
module foo;
`ASSIGN1XWIRE
`ASSIGN0YWIRE
`define XWIRE wire new_x_wire
`ASSIGN1XWIRE
endmodule)",
// ...equivalent to
R"(
`define XWIRE wire x
`define YWIRE wire y
`define ASSIGN1XWIRE `XWIRE = 1;
`define ASSIGN0YWIRE `YWIRE = 0;
module foo;
wire x = 1;
wire y = 0;
`define XWIRE wire new_x_wire
wire new_x_wire = 1;
endmodule)"},
{"[** Macros contatining back to back macro calls **]",
R"(
`define XWIRE wire x
`define YWIRE wire y
`define ASSIGN1 = 1
`define ASSIGN0 = 0
`define ASSIGN1XWIRE `XWIRE `ASSIGN1;
`define ASSIGN0YWIRE `YWIRE `ASSIGN0;
module foo;
`ASSIGN1XWIRE
`ASSIGN0YWIRE
`define XWIRE wire new_x_wire
`ASSIGN1XWIRE
endmodule)",
// ...equivalent to
R"(
`define XWIRE wire x
`define YWIRE wire y
`define ASSIGN1 = 1
`define ASSIGN0 = 0
`define ASSIGN1XWIRE `XWIRE `ASSIGN1;
`define ASSIGN0YWIRE `YWIRE `ASSIGN0;
module foo;
wire x = 1;
wire y = 0;
`define XWIRE wire new_x_wire
wire new_x_wire = 1;
endmodule)"},
{"[** Macros with formal parameters, expanded with both default value, "
"and actual passed value **]",
R"(
`define LSb(n=2) [n-1:0]
module testcase_ppMacro;
localparam int A = 123;
wire a = A`LSb();
wire b = A`LSb(5);
wire c = A[5-1:0];
endmodule)",
// ...equivalent to
R"(
`define LSb(n=2) [n-1:0]
module testcase_ppMacro;
localparam int A = 123;
wire a = A[2-1:0];
wire b = A[5-1:0];
wire c = A[5-1:0];
endmodule)"},
{"[** Actual parameter is another macro call **]",
R"(
`define FOO a
`define A(n) n
`define B(n=x) n ,y
module m;
wire `A(xyz);
wire `B(`FOO);
endmodule
`undef B
`undef A
`undef FOO)",
// ...equivalent to
R"(
`define FOO a
`define A(n) n
`define B(n=x) n ,y
module m;
wire xyz;
wire a ,y;
endmodule
`undef B
`undef A
`undef FOO)"},
{"[** Multiple parameter macros (From 2017 SV-LRM) **]",
R"(
`define MACRO1(a=5,b="B",c) $display(a,,b,,c);
`define MACRO2(a=5, b, c="C") $display(a,,b,,c);
`define MACRO3(a=5, b=0, c="C") $display(a,,b,,c);
module m;
`MACRO1 ( , 2, 3 )
`MACRO1 ( 1 , , 3 )
`MACRO1 ( , 2, )
`MACRO2 (1, , 3)
`MACRO2 (, 2, )
`MACRO2 (, 2)
`MACRO3 ( 1 )
`MACRO3 ()
endmodule
`undef MACRO)",
// ...equivalent to
R"(
`define MACRO1(a=5,b="B",c) $display(a,,b,,c);
`define MACRO2(a=5, b, c="C") $display(a,,b,,c);
`define MACRO3(a=5, b=0, c="C") $display(a,,b,,c);
module m;
$display(5,,2,,3);
$display(1,,"B",,3);
$display(5,,2,,);
$display(1,,,,3);
$display(5,,2,,"C");
$display(5,,2,,"C");
$display(1,,0,,"C");
$display(5,,0,,"C");
endmodule
`undef MACRO)"},
{"[** Nested callable macros **]",
R"(
`define MACRO1(n) real x=n;
`define MACRO2(m) real y=m; `MACRO1(1)
module foo;
`MACRO1(2)
`MACRO2(3)
endmodule
`undef MACRO1
`undef MACRO2)",
// ...equivalent to
R"(
`define MACRO1(n) real x=n;
`define MACRO2(m) real y=m; `MACRO1(1)
module foo;
real x=2;
real y=3; real x=1;
endmodule
`undef MACRO1
`undef MACRO2)"}
};
for (const auto& test_case : test_cases) {
PreprocessorTester expanded(
test_case.pp_input, VerilogPreprocess::Config({.expand_macros = true}));
EXPECT_TRUE(expanded.Status().ok())
<< expanded.Status() << " " << test_case.description;
PreprocessorTester equivalent(
test_case.equivalent,
VerilogPreprocess::Config({.expand_macros = false}));
EXPECT_TRUE(equivalent.Status().ok())
<< equivalent.Status() << " " << test_case.description;
const auto& expanded_stream = expanded.Data().GetTokenStreamView();
const auto& equivalent_stream = equivalent.Data().GetTokenStreamView();
EXPECT_GT(expanded_stream.size(), 0) << test_case.description;
EXPECT_EQ(expanded_stream.size(), equivalent_stream.size())
<< test_case.description;
auto expanded_it = expanded_stream.begin();
auto equivalent_it = equivalent_stream.begin();
while (expanded_it != expanded_stream.end() &&
equivalent_it != equivalent_stream.end()) {
EXPECT_EQ((*expanded_it)->text(), (*equivalent_it)->text())
<< test_case.description;
EXPECT_EQ((*expanded_it)->token_enum(), (*equivalent_it)->token_enum())
<< test_case.description;
++expanded_it;
++equivalent_it;
}
}
}
// TODO(karimtera): This test doesn't use "PreprocessorTester",
// as there isn't a way to tell "VerilogAnalyzer" about external preprocessing
// info. Typically, all tests should use "PreprocessorTester".
TEST(VerilogPreprocessTest, SetExternalDefines) {
// Test case input tokens.
const verible::TokenSequence test_case_tokens = {
{verible::TokenInfo(MacroIdentifier, "`MACRO1")},
{MacroIdentifier, "`MACRO2"}};
verible::TokenStreamView test_case_stream_view;
verible::InitTokenStreamView(test_case_tokens, &test_case_stream_view);
VerilogPreprocess::Config test_config({.expand_macros = true});
VerilogPreprocess preprocessor(test_config);
verilog::TextMacroDefinition macro1("MACRO1", "VALUE1");
verilog::TextMacroDefinition macro2("MACRO2", "VALUE2");
verilog::FileList::PreprocessingInfo preprocessing_info;
preprocessing_info.defines.push_back(macro1);
preprocessing_info.defines.push_back(macro2);
preprocessor.setPreprocessingInfo(preprocessing_info);
const auto& pp_data = preprocessor.ScanStream(test_case_stream_view);
EXPECT_THAT(pp_data.preprocessed_token_stream[0]->text(), "VALUE1");
EXPECT_THAT(pp_data.preprocessed_token_stream[1]->text(), "VALUE2");
}
// TODO(karimtera): This test doesn't use "PreprocessorTester",
// as there isn't a way to tell "VerilogAnalyzer" about external preprocessing
// info. Typically, all tests should use "PreprocessorTester".
TEST(VerilogPreprocessTest, ExternalDefinesWithUndef) {
// Test case input tokens.
const verible::TokenSequence test_case_tokens = {
{verible::TokenInfo(PP_undef, "`undef")},
{verible::TokenInfo(PP_Identifier, "MACRO1")},
{verible::TokenInfo(MacroIdentifier, "`MACRO1")},
{verible::TokenInfo(MacroIdentifier, "`MACRO2")}};
verible::TokenStreamView test_case_stream_view;
verible::InitTokenStreamView(test_case_tokens, &test_case_stream_view);
VerilogPreprocess::Config test_config({.expand_macros = true});
VerilogPreprocess preprocessor(test_config);
verilog::TextMacroDefinition macro1("MACRO1", "VALUE1");
verilog::TextMacroDefinition macro2("MACRO2", "VALUE2");
verilog::FileList::PreprocessingInfo preprocessing_info;
preprocessing_info.defines.push_back(macro1);
preprocessing_info.defines.push_back(macro2);
preprocessor.setPreprocessingInfo(preprocessing_info);
const auto& pp_data = preprocessor.ScanStream(test_case_stream_view);
const auto& errors = pp_data.errors;
EXPECT_THAT(errors.size(), 1);
EXPECT_THAT(errors.front().error_message,
StartsWith("Error expanding macro identifier"));
}
TEST(VerilogPreprocessTest, IncludingFileWithAbsolutePath) {
const auto tempdir = testing::TempDir();
const std::string includes_dir = JoinPath(tempdir, "includes");
constexpr absl::string_view included_content(
"module included_file(); endmodule");
const absl::string_view included_filename = "included_file.sv";
const std::string included_absolute_path =
JoinPath(includes_dir, included_filename);
const std::string src_content = absl::StrCat(
"`include \"", included_absolute_path, "\"\nmodule src(); endmodule\n");
const std::string equivalent_content =
"module included_file(); endmodule\nmodule src(); endmodule\n";
FileOpener file_opener =
[included_absolute_path, included_content](
absl::string_view filename) -> absl::StatusOr<absl::string_view> {
if (filename == included_absolute_path) return included_content;
return absl::NotFoundError(absl::StrCat(filename, " is not found"));
};
VerilogPreprocess tester(VerilogPreprocess::Config({.include_files = true}),
file_opener);
VerilogPreprocess equivalent(
VerilogPreprocess::Config({.include_files = true}));
LexerTester src_lexer(src_content);
LexerTester equivalent_lexer(equivalent_content);
const auto& tester_pp_data =
tester.ScanStream(src_lexer.GetTokenStreamView());
const auto& equivalent_pp_data =
equivalent.ScanStream(equivalent_lexer.GetTokenStreamView());
EXPECT_TRUE(tester_pp_data.errors.empty());
EXPECT_TRUE(equivalent_pp_data.errors.empty());
const auto& tester_stream = tester_pp_data.preprocessed_token_stream;
const auto& equivalent_stream = equivalent_pp_data.preprocessed_token_stream;
EXPECT_FALSE(tester_stream.empty());
EXPECT_EQ(tester_stream.size(), equivalent_stream.size());
auto tester_it = tester_stream.begin();
auto equivalent_it = equivalent_stream.begin();
while (tester_it != tester_stream.end() &&
equivalent_it != equivalent_stream.end()) {
EXPECT_EQ((*tester_it)->text(), (*equivalent_it)->text());
EXPECT_EQ((*tester_it)->token_enum(), (*equivalent_it)->token_enum());
++tester_it;
++equivalent_it;
}
}
TEST(VerilogPreprocessTest, IncludingFileWithRelativePath) {
const auto tempdir = testing::TempDir();
const std::string includes_dir = JoinPath(tempdir, "includes");
EXPECT_TRUE(CreateDir(includes_dir).ok());
constexpr absl::string_view included_content(
"module included_file(); endmodule");
const absl::string_view included_filename = "included_file.sv";
const std::string included_absolute_path =
JoinPath(includes_dir, included_filename);
const ScopedTestFile tf(includes_dir, included_content, included_filename);
const std::string src_content = absl::StrCat("`include \"", included_filename,
"\"\nmodule src(); endmodule\n");
const std::string equivalent_content =
"module included_file(); endmodule\nmodule src(); endmodule\n";
// TODO(karimtera): allow including files with absolute paths.
// This is a hacky solution for now.
verilog::VerilogProject project(".", {"/", includes_dir});
FileOpener file_opener =
[&project](
absl::string_view filename) -> absl::StatusOr<absl::string_view> {
auto result = project.OpenIncludedFile(filename);
if (!result.status().ok()) return result.status();
return (*result)->GetContent();
};
VerilogPreprocess tester(VerilogPreprocess::Config({.include_files = true}),
file_opener);
VerilogPreprocess equivalent(
VerilogPreprocess::Config({.include_files = true}));
LexerTester src_lexer(src_content);
LexerTester equivalent_lexer(equivalent_content);
const auto& tester_pp_data =
tester.ScanStream(src_lexer.GetTokenStreamView());
const auto& equivalent_pp_data =
equivalent.ScanStream(equivalent_lexer.GetTokenStreamView());
EXPECT_TRUE(tester_pp_data.errors.empty());
EXPECT_TRUE(equivalent_pp_data.errors.empty());
const auto& tester_stream = tester_pp_data.preprocessed_token_stream;
const auto& equivalent_stream = equivalent_pp_data.preprocessed_token_stream;
EXPECT_FALSE(tester_stream.empty());
EXPECT_EQ(tester_stream.size(), equivalent_stream.size());
auto tester_it = tester_stream.begin();
auto equivalent_it = equivalent_stream.begin();
while (tester_it != tester_stream.end() &&
equivalent_it != equivalent_stream.end()) {
EXPECT_EQ((*tester_it)->text(), (*equivalent_it)->text());
EXPECT_EQ((*tester_it)->token_enum(), (*equivalent_it)->token_enum());
++tester_it;
++equivalent_it;
}
}
TEST(VerilogPreprocessTest,
IncludingFileWithRelativePathWithoutPreprocessingInfo) {
const auto tempdir = testing::TempDir();
const std::string includes_dir = JoinPath(tempdir, "includes");
EXPECT_TRUE(CreateDir(includes_dir).ok());
constexpr absl::string_view included_content(
"module included_file(); endmodule\n");
const absl::string_view included_filename = "included_file.sv";
const std::string included_absolute_path =
JoinPath(includes_dir, included_filename);
const ScopedTestFile tf(includes_dir, included_content, included_filename);
const std::string src_content = absl::StrCat("`include \"", included_filename,
"\"\nmodule src(); endmodule\n");
// TODO(karimtera): allow including files with absolute paths.
// This is a hacky solution for now.
verilog::VerilogProject project(".", {"/"});
FileOpener file_opener =
[&project](
absl::string_view filename) -> absl::StatusOr<absl::string_view> {
auto result = project.OpenIncludedFile(filename);
if (!result.status().ok()) return result.status();
return (*result)->GetContent();
};
VerilogPreprocess tester(VerilogPreprocess::Config({.include_files = true}),
file_opener);
LexerTester src_lexer(src_content);
const auto& tester_pp_data =
tester.ScanStream(src_lexer.GetTokenStreamView());
EXPECT_EQ(tester_pp_data.errors.size(), 1);
const auto& error = tester_pp_data.errors.front();
EXPECT_THAT(error.error_message, StartsWith("Unable to find"));
}
} // namespace
} // namespace verilog