| // 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 <filesystem> | |
| #include <functional> | |
| #include <iterator> | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/status/status.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "common/lexer/token_generator.h" | |
| #include "common/lexer/token_stream_adapter.h" | |
| #include "common/text/macro_definition.h" | |
| #include "common/text/token_info.h" | |
| #include "common/text/token_stream_view.h" | |
| #include "common/util/container_util.h" | |
| #include "common/util/file_util.h" | |
| #include "common/util/logging.h" | |
| #include "verilog/parser/verilog_lexer.h" | |
| #include "verilog/parser/verilog_parser.h" // for verilog_symbol_name() | |
| #include "verilog/parser/verilog_token_enum.h" | |
| namespace verilog { | |
| using verible::TokenGenerator; | |
| using verible::TokenStreamView; | |
| using verible::container::FindOrNull; | |
| using verible::container::InsertOrUpdate; | |
| VerilogPreprocess::VerilogPreprocess(const Config& config) | |
| : VerilogPreprocess(config, nullptr) {} | |
| VerilogPreprocess::VerilogPreprocess(const Config& config, FileOpener opener) | |
| : config_(config), file_opener_(std::move(opener)) { | |
| // To avoid having to check at every place if the stack is empty, we always | |
| // place a toplevel 'conditional' that is always selected. | |
| // Thus we only need to test in `else and `endif to see if we underrun due | |
| // to unbalanced statements. | |
| conditional_block_.push( | |
| BranchBlock(true, true, verible::TokenInfo::EOFToken())); | |
| } | |
| TokenStreamView::const_iterator VerilogPreprocess::GenerateBypassWhiteSpaces( | |
| const StreamIteratorGenerator& generator) { | |
| auto iterator = | |
| generator(); // iterator should be pointing to a non-whitespace token; | |
| while (verilog::VerilogLexer::KeepSyntaxTreeTokens(**iterator) == 0) { | |
| iterator = generator(); | |
| } | |
| return iterator; | |
| } | |
| absl::StatusOr<TokenStreamView::const_iterator> | |
| VerilogPreprocess::ExtractMacroName(const StreamIteratorGenerator& generator) { | |
| // Next token to expect is macro definition name. | |
| TokenStreamView::const_iterator token_iter = | |
| GenerateBypassWhiteSpaces(generator); | |
| if ((*token_iter)->isEOF()) { | |
| preprocess_data_.errors.push_back( | |
| {**token_iter, "unexpected EOF where expecting macro name"}); | |
| return absl::InvalidArgumentError("Unexpected EOF"); | |
| } | |
| const auto& macro_name = *token_iter; | |
| if (macro_name->token_enum() != PP_Identifier) { | |
| preprocess_data_.errors.push_back( | |
| {**token_iter, | |
| absl::StrCat("Expected identifier for macro name, but got \"", | |
| macro_name->text(), "...\"")}); | |
| return absl::InvalidArgumentError("macro name expected"); | |
| } | |
| return token_iter; | |
| } | |
| // Copies `define token iterators into a temporary buffer. | |
| // Assumes that the last token of a definition is the un-lexed definition body. | |
| // Tokens are copied from the 'generator' into 'define_tokens'. | |
| absl::Status VerilogPreprocess::ConsumeMacroDefinition( | |
| const StreamIteratorGenerator& generator, TokenStreamView* define_tokens) { | |
| auto macro_name_extract = ExtractMacroName(generator); | |
| if (!macro_name_extract.ok()) { | |
| return macro_name_extract.status(); | |
| } | |
| define_tokens->push_back(**macro_name_extract); | |
| // Everything else covers macro parameters and the definition body. | |
| TokenStreamView::const_iterator token_iter; | |
| do { | |
| token_iter = GenerateBypassWhiteSpaces(generator); | |
| if ((*token_iter)->isEOF()) { | |
| // Diagnose unexpected EOF downstream instead of erroring here. | |
| // Other subroutines can give better context about the parsing state. | |
| define_tokens->push_back(*token_iter); | |
| return absl::OkStatus(); | |
| } | |
| define_tokens->push_back(*token_iter); | |
| } while ((*token_iter)->token_enum() != PP_define_body); | |
| return absl::OkStatus(); | |
| } | |
| // TODO(hzeller): instead of returning a unique ptr to a | |
| // VerilogPreprocessError, these functions should just be non-static, | |
| // fill in the error directly into preprocess_data.errors and | |
| // return an absl::Status, | |
| // Interprets a single macro definition parameter. | |
| // Tokens are scanned by advancing the token_scan iterator (by-reference). | |
| std::unique_ptr<VerilogPreprocessError> VerilogPreprocess::ParseMacroParameter( | |
| TokenStreamView::const_iterator* token_scan, | |
| MacroParameterInfo* macro_parameter) { | |
| auto advance = [](TokenStreamView::const_iterator* scan) { return *++*scan; }; | |
| auto token_iter = **token_scan; | |
| // Extract macro name. | |
| if (token_iter->token_enum() != PP_Identifier) { | |
| return std::make_unique<VerilogPreprocessError>( | |
| *token_iter, | |
| absl::StrCat("expected identifier for macro parameter, but got: ", | |
| token_iter->ToString())); | |
| } | |
| macro_parameter->name = *token_iter; | |
| // Check for separator or default text. | |
| token_iter = advance(token_scan); | |
| if (token_iter->isEOF()) { | |
| return std::make_unique<VerilogPreprocessError>( | |
| *token_iter, "unexpected EOF while parsing macro parameter"); | |
| } | |
| if (token_iter->token_enum() == '=') { | |
| token_iter = advance(token_scan); | |
| if (token_iter->isEOF()) { | |
| return std::make_unique<VerilogPreprocessError>( | |
| *token_iter, | |
| "unexpected EOF where macro parameter default text is expected"); | |
| } | |
| if (token_iter->token_enum() != PP_default_text) { | |
| return std::make_unique<VerilogPreprocessError>( | |
| *token_iter, | |
| absl::StrCat("expected macro parameter default text, but got: ", | |
| token_iter->ToString())); | |
| } | |
| // Note: the default parameter text is allowed to be empty. | |
| macro_parameter->default_value = *token_iter; | |
| token_iter = advance(token_scan); | |
| } | |
| if (token_iter->isEOF()) { | |
| return std::make_unique<VerilogPreprocessError>( | |
| *token_iter, | |
| "unexpected EOF where expecting macro parameter separator"); | |
| } | |
| if (token_iter->token_enum() == ',') { | |
| advance(token_scan); // Advance to next parameter identifier. | |
| } else if (token_iter->token_enum() == ')') { | |
| // Do not advance. | |
| } else { | |
| // This case covers an unexpected EOF token. | |
| return std::make_unique<VerilogPreprocessError>( | |
| *token_iter, | |
| absl::StrCat( | |
| "expecting macro parameter separator ',', or terminator ')', " | |
| "but got: ", | |
| verilog_symbol_name(token_iter->token_enum()))); | |
| } | |
| return nullptr; | |
| } | |
| // Parses an entire macro definition from header through body text. | |
| // The span of tokens that covers a macro definition is expected to | |
| // be in define_tokens. | |
| std::unique_ptr<VerilogPreprocessError> VerilogPreprocess::ParseMacroDefinition( | |
| const TokenStreamView& define_tokens, MacroDefinition* macro_definition) { | |
| auto token_scan = define_tokens.begin() + 2; // skip `define and the name | |
| auto token_iter = *token_scan; | |
| if (token_iter->token_enum() == '(') { | |
| token_iter = *++token_scan; | |
| // Scan for macro parameters. | |
| while (token_iter->token_enum() != ')') { | |
| MacroParameterInfo macro_parameter; | |
| auto error_ptr = ParseMacroParameter(&token_scan, ¯o_parameter); | |
| if (error_ptr) return error_ptr; | |
| macro_definition->AppendParameter(macro_parameter); | |
| token_iter = *token_scan; | |
| } // while there are macro parameters | |
| // Advance past final ')'. | |
| token_iter = *++token_scan; | |
| } | |
| // The macro definition body follows. | |
| if (token_iter->token_enum() != PP_define_body) { | |
| return std::make_unique<VerilogPreprocessError>( | |
| *token_iter, | |
| absl::StrCat("expected macro definition body text, but got: ", | |
| token_iter->ToString())); | |
| } | |
| macro_definition->SetDefinitionText(*token_iter); | |
| ++token_scan; | |
| if (token_scan != define_tokens.end()) { | |
| token_iter = *token_scan; | |
| return std::make_unique<VerilogPreprocessError>( | |
| *token_iter, | |
| absl::StrCat("expected no more tokens from macro definition, but got: ", | |
| token_iter->ToString())); | |
| } | |
| return nullptr; | |
| } | |
| // Parses a callable macro actual parameters, and saves it into a MacroCall | |
| absl::Status VerilogPreprocess::ConsumeAndParseMacroCall( | |
| TokenStreamView::const_iterator iter, | |
| const StreamIteratorGenerator& generator, verible::MacroCall* macro_call, | |
| const verible::MacroDefinition& macro_definition) { | |
| // Parsing the macro . | |
| const absl::string_view macro_name_str = (*iter)->text().substr(1); | |
| verible::TokenInfo macro_name_token(MacroCallId, macro_name_str); | |
| macro_call->macro_name = macro_name_token; | |
| // Checking if the macro has formal parameters. | |
| if (!macro_definition.IsCallable()) { | |
| macro_call->has_parameters = 0; | |
| return absl::OkStatus(); | |
| } | |
| macro_call->has_parameters = 1; | |
| // Parsing parameters. | |
| TokenStreamView::const_iterator token_iter = | |
| GenerateBypassWhiteSpaces(generator); | |
| int parameters_size = macro_definition.Parameters().size(); | |
| if ((*token_iter)->text() == "(") { | |
| token_iter = GenerateBypassWhiteSpaces(generator); // skip the "(" | |
| } else { | |
| return absl::InvalidArgumentError( | |
| "Error it is illegal to call a callable macro without ()."); | |
| } | |
| while (parameters_size > 0) { | |
| if ((*token_iter)->token_enum() == MacroArg) { | |
| macro_call->positional_arguments.emplace_back(**token_iter); | |
| token_iter = GenerateBypassWhiteSpaces(generator); | |
| if ((*token_iter)->text() == ",") | |
| token_iter = GenerateBypassWhiteSpaces(generator); | |
| parameters_size--; | |
| continue; | |
| } else if ((*token_iter)->text() == ",") { | |
| macro_call->positional_arguments.emplace_back( | |
| verible::DefaultTokenInfo()); | |
| token_iter = GenerateBypassWhiteSpaces(generator); | |
| parameters_size--; | |
| continue; | |
| } else if ((*token_iter)->text() == ")") | |
| break; | |
| } | |
| if (parameters_size > 0) { | |
| while (parameters_size--) | |
| macro_call->positional_arguments.emplace_back( | |
| verible::DefaultTokenInfo()); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| // Responds to `define directives. Macro definitions are parsed and saved | |
| // for use within the same file. | |
| absl::Status VerilogPreprocess::HandleMacroIdentifier( | |
| const TokenStreamView::const_iterator | |
| iter // points to `MACROIDENTIFIER token | |
| , | |
| const StreamIteratorGenerator& generator, bool forward = 1) { | |
| // Note: since this function is called we know that config_.expand_macros is | |
| // true. | |
| // Finding the macro definition. | |
| const absl::string_view sv = (*iter)->text(); | |
| const auto* found = | |
| FindOrNull(preprocess_data_.macro_definitions, sv.substr(1)); | |
| if (!found) { | |
| preprocess_data_.errors.push_back(VerilogPreprocessError( | |
| **iter, | |
| "Error expanding macro identifier, might not be defined before.")); | |
| return absl::InvalidArgumentError( | |
| "Error expanding macro identifier, might not be defined before."); | |
| } | |
| if (config_.expand_macros) { | |
| verible::MacroCall macro_call; | |
| if (auto status = | |
| ConsumeAndParseMacroCall(iter, generator, ¯o_call, *found); | |
| !status.ok()) | |
| return status; | |
| if (auto status = ExpandMacro(macro_call, found); !status.ok()) | |
| return status; | |
| } | |
| auto& lexed = preprocess_data_.lexed_macros_backup.back(); | |
| if (!forward) return absl::OkStatus(); | |
| auto iter_generator = verible::MakeConstIteratorStreamer(lexed); | |
| const auto it_end = lexed.end(); | |
| for (auto it = iter_generator(); it != it_end; it++) { | |
| preprocess_data_.preprocessed_token_stream.push_back(it); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| // Stores a macro definition for later use. | |
| void VerilogPreprocess::RegisterMacroDefinition( | |
| const MacroDefinition& definition) { | |
| // For now, unconditionally register the macro definition, keeping the last | |
| // definition if macro is re-defined. | |
| const bool inserted = InsertOrUpdate(&preprocess_data_.macro_definitions, | |
| definition.Name(), definition); | |
| if (inserted) return; | |
| preprocess_data_.warnings.emplace_back( | |
| VerilogPreprocessError(definition.NameToken(), "Re-defining macro")); | |
| // TODO(hzeller): multiline warning with 'previously defined here' location | |
| } | |
| // This function expands a text. | |
| // The expanded tokens are saved as a TokenSequence, stored at | |
| // preprocess_data_.lexed_macros_backup Can be accessed directly after expansion | |
| // as: preprocess_data_.lexed_macros_backup.back() | |
| absl::Status VerilogPreprocess::ExpandText( | |
| const absl::string_view& definition_text) { | |
| VerilogLexer lexer(definition_text); | |
| verible::TokenSequence lexed_sequence; | |
| verible::TokenSequence expanded_lexed_sequence; | |
| // Populating the lexed token sequence. | |
| for (lexer.DoNextToken(); !lexer.GetLastToken().isEOF(); | |
| lexer.DoNextToken()) { | |
| lexed_sequence.push_back(lexer.GetLastToken()); | |
| } | |
| verible::TokenStreamView lexed_streamview; | |
| // Initializing the lexed token stream view. | |
| InitTokenStreamView(lexed_sequence, &lexed_streamview); | |
| auto iter_generator = verible::MakeConstIteratorStreamer(lexed_streamview); | |
| const auto end = lexed_streamview.end(); | |
| // Token-pulling loop. | |
| for (auto iter = iter_generator(); iter != end; iter = iter_generator()) { | |
| auto& last_token = **iter; | |
| // TODO: handle lexical error | |
| if (lexer.GetLastToken().token_enum() == TK_SPACE) | |
| continue; // don't forward spaces | |
| // If the expanded token is another macro identifier that needs to be | |
| // expanded. | |
| // TODO: this needs to be something like HandleTokenIterator, to claim that | |
| // it fully covers all cases. | |
| if (last_token.token_enum() == MacroIdentifier || | |
| last_token.token_enum() == MacroIdItem || | |
| last_token.token_enum() == MacroCallId) { | |
| if (auto status = HandleMacroIdentifier(iter, iter_generator, 0); | |
| !status.ok()) | |
| return status; | |
| // merge the expanded macro tokens into 'expanded_lexed_sequence' | |
| auto& expanded_child = preprocess_data_.lexed_macros_backup.back(); | |
| for (auto& u : expanded_child) expanded_lexed_sequence.push_back(u); | |
| continue; | |
| } | |
| expanded_lexed_sequence.push_back(last_token); | |
| } | |
| preprocess_data_.lexed_macros_backup.emplace_back(expanded_lexed_sequence); | |
| return absl::OkStatus(); | |
| } | |
| // This method expands a callable macro call, that follows this form: | |
| // `MACRO([param1],[param2],...) | |
| absl::Status VerilogPreprocess::ExpandMacro( | |
| const verible::MacroCall& macro_call, | |
| const verible::MacroDefinition* macro_definition) { | |
| const auto& actual_parameters = macro_call.positional_arguments; | |
| std::map<absl::string_view, verible::DefaultTokenInfo> subs_map; | |
| if (macro_definition->IsCallable()) { | |
| if (auto status = macro_definition->PopulateSubstitutionMap( | |
| actual_parameters, &subs_map); | |
| !status.ok()) | |
| return status; | |
| } | |
| VerilogLexer lexer(macro_definition->DefinitionText().text()); | |
| verible::TokenSequence lexed_sequence; | |
| verible::TokenSequence expanded_lexed_sequence; | |
| // Populating the lexed token sequence. | |
| for (lexer.DoNextToken(); !lexer.GetLastToken().isEOF(); | |
| lexer.DoNextToken()) { | |
| lexed_sequence.push_back(lexer.GetLastToken()); | |
| } | |
| verible::TokenStreamView lexed_streamview; | |
| // Initializing the lexed token stream view. | |
| InitTokenStreamView(lexed_sequence, &lexed_streamview); | |
| auto iter_generator = verible::MakeConstIteratorStreamer(lexed_streamview); | |
| const auto end = lexed_streamview.end(); | |
| // Token-pulling loop. | |
| for (auto iter = iter_generator(); iter != end; iter = iter_generator()) { | |
| // TODO: handle lexical error | |
| auto& last_token = **iter; | |
| if (last_token.token_enum() == TK_SPACE) continue; // don't forward spaces | |
| // If the expanded token is another macro identifier that needs to be | |
| // expanded. | |
| // TODO: this needs to be something like HandleTokenIterator, to claim that | |
| // it fully covers all cases. | |
| if (last_token.token_enum() == MacroIdentifier || | |
| last_token.token_enum() == MacroIdItem || | |
| last_token.token_enum() == MacroCallId) { | |
| if (auto status = HandleMacroIdentifier(iter, iter_generator, 0); | |
| !status.ok()) | |
| return status; | |
| // merge the expanded macro tokens into 'expanded_lexed_sequence' | |
| auto& expanded_child = preprocess_data_.lexed_macros_backup.back(); | |
| for (auto& u : expanded_child) expanded_lexed_sequence.push_back(u); | |
| continue; | |
| } | |
| if (macro_definition->IsCallable()) { | |
| // Check if the last token is a formal parameter | |
| const auto* replacement = FindOrNull(subs_map, last_token.text()); | |
| if (replacement) { | |
| if (auto status = ExpandText(replacement->text()); !status.ok()) | |
| return status; | |
| // merge the expanded macro tokens into 'expanded_lexed_sequence' | |
| auto& expanded_child = preprocess_data_.lexed_macros_backup.back(); | |
| for (auto& u : expanded_child) expanded_lexed_sequence.push_back(u); | |
| continue; | |
| } | |
| } | |
| expanded_lexed_sequence.push_back(last_token); | |
| } | |
| preprocess_data_.lexed_macros_backup.emplace_back(expanded_lexed_sequence); | |
| return absl::OkStatus(); | |
| } | |
| // Responds to `define directives. Macro definitions are parsed and saved | |
| // for use within the same file. | |
| absl::Status VerilogPreprocess::HandleDefine( | |
| const TokenStreamView::const_iterator iter, // points to `define token | |
| const StreamIteratorGenerator& generator) { | |
| TokenStreamView define_tokens; | |
| define_tokens.push_back(*iter); | |
| if (auto status = ConsumeMacroDefinition(generator, &define_tokens); | |
| !status.ok()) | |
| return status; | |
| CHECK_GE(define_tokens.size(), 3) | |
| << "Macro definition should span at least 3 tokens, but only got " | |
| << define_tokens.size(); | |
| const verible::TokenSequence::const_iterator macro_name = define_tokens[1]; | |
| verible::MacroDefinition macro_definition(*define_tokens[0], *macro_name); | |
| const auto parse_error_ptr = | |
| ParseMacroDefinition(define_tokens, ¯o_definition); | |
| if (parse_error_ptr) { | |
| preprocess_data_.errors.push_back(*parse_error_ptr); | |
| return absl::InvalidArgumentError("Error parsing macro definition."); | |
| } | |
| // Parsing showed that things are syntatically correct. | |
| // But let's only emit things if we're in an active preprocessing branch. | |
| if (conditional_block_.top().InSelectedBranch()) { | |
| RegisterMacroDefinition(macro_definition); | |
| // For now, forward all definition tokens. | |
| for (const auto& token : define_tokens) { | |
| preprocess_data_.preprocessed_token_stream.push_back(token); | |
| } | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status VerilogPreprocess::HandleUndef( | |
| TokenStreamView::const_iterator undef_it, | |
| const StreamIteratorGenerator& generator) { | |
| auto macro_name_extract = ExtractMacroName(generator); | |
| if (!macro_name_extract.ok()) { | |
| return macro_name_extract.status(); | |
| } | |
| const auto& macro_name = *macro_name_extract.value(); | |
| preprocess_data_.macro_definitions.erase(macro_name->text()); | |
| // For now, forward all `undef tokens. | |
| if (conditional_block_.top().InSelectedBranch()) { | |
| preprocess_data_.preprocessed_token_stream.push_back(*undef_it); | |
| preprocess_data_.preprocessed_token_stream.push_back(macro_name); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status VerilogPreprocess::HandleIf( | |
| const TokenStreamView::const_iterator ifpos, // `ifdef, `ifndef, `elseif | |
| const StreamIteratorGenerator& generator) { | |
| if (!config_.filter_branches) { // nothing to do. | |
| preprocess_data_.preprocessed_token_stream.push_back(*ifpos); | |
| return absl::OkStatus(); | |
| } | |
| auto macro_name_extract = ExtractMacroName(generator); | |
| if (!macro_name_extract.ok()) { | |
| return macro_name_extract.status(); | |
| } | |
| const auto& macro_name = *macro_name_extract.value(); | |
| const bool negative_if = (*ifpos)->token_enum() == PP_ifndef; | |
| const auto& defs = preprocess_data_.macro_definitions; | |
| const bool name_is_defined = defs.find(macro_name->text()) != defs.end(); | |
| const bool condition_met = (name_is_defined ^ negative_if); | |
| if ((*ifpos)->token_enum() == PP_elsif) { | |
| if (conditional_block_.size() <= 1) { | |
| preprocess_data_.errors.push_back({**ifpos, "Unmatched `elsif"}); | |
| return absl::InvalidArgumentError("Unmatched `else"); | |
| } | |
| if (!conditional_block_.top().UpdateCondition(**ifpos, condition_met)) { | |
| preprocess_data_.errors.push_back({**ifpos, "`elsif after `else"}); | |
| preprocess_data_.errors.push_back( | |
| {conditional_block_.top().token(), "Previous `else started here."}); | |
| return absl::InvalidArgumentError("Duplicate `else"); | |
| } | |
| } else { | |
| // A new, nested if-branch. | |
| const bool scope_enabled = conditional_block_.top().InSelectedBranch(); | |
| conditional_block_.push(BranchBlock(scope_enabled, condition_met, **ifpos)); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status VerilogPreprocess::HandleElse( | |
| TokenStreamView::const_iterator else_pos) { | |
| if (!config_.filter_branches) { // nothing to do. | |
| preprocess_data_.preprocessed_token_stream.push_back(*else_pos); | |
| return absl::OkStatus(); | |
| } | |
| if (conditional_block_.size() <= 1) { | |
| preprocess_data_.errors.push_back({**else_pos, "Unmatched `else"}); | |
| return absl::InvalidArgumentError("Unmatched `else"); | |
| } | |
| if (!conditional_block_.top().StartElse(**else_pos)) { | |
| preprocess_data_.errors.push_back({**else_pos, "Duplicate `else"}); | |
| preprocess_data_.errors.push_back( | |
| {conditional_block_.top().token(), "Previous `else started here."}); | |
| return absl::InvalidArgumentError("Duplicate `else"); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status VerilogPreprocess::HandleEndif( | |
| TokenStreamView::const_iterator endif_pos) { | |
| if (!config_.filter_branches) { // nothing to do. | |
| preprocess_data_.preprocessed_token_stream.push_back(*endif_pos); | |
| return absl::OkStatus(); | |
| } | |
| if (conditional_block_.size() <= 1) { | |
| preprocess_data_.errors.push_back({**endif_pos, "Unmatched `endif"}); | |
| return absl::InvalidArgumentError("Unmatched `endif"); | |
| } | |
| conditional_block_.pop(); | |
| return absl::OkStatus(); | |
| } | |
| // Handle `include directives. | |
| // TODO(karimtera): An important future work would be to utilize | |
| // "VerilogProject::OpenIncludedFile()", which has more advantages over the way | |
| // we open included files in "VerilogPreprocess::HandleInclude()", such as | |
| // avoiding to open the same file multiple times, and have a more clear | |
| // definition of a compilation unit. It could be done, but here are some changes | |
| // that I think need to be done first: | |
| // 1- Add a member "VerilogProject project_" to "VerilogPreprocess". | |
| // 2- Add a constructor to "VerilogPreprocess" to construct "project_" | |
| // correctly (as a VerilogProject can't be assigned, copied, or moved). | |
| // 3- Modify "VerilogPreprocess::ScanStream()" or replace it with | |
| // "VerilogPreprocess::ScanProject()", which should scan all | |
| // "project_.files_" files. | |
| absl::Status VerilogPreprocess::HandleInclude( | |
| TokenStreamView::const_iterator iter, | |
| const StreamIteratorGenerator& generator) { | |
| if (!file_opener_) | |
| return absl::FailedPreconditionError("file_opener_ is not defined"); | |
| // TODO(karimtera): Support inclduing <file>, | |
| // which should look for files defined by language standard in a compiler | |
| // dependent path. | |
| TokenStreamView::const_iterator token_iter = | |
| GenerateBypassWhiteSpaces(generator); | |
| auto file_token_iter = *token_iter; | |
| if (file_token_iter->token_enum() != TK_StringLiteral) { | |
| preprocess_data_.errors.push_back( | |
| {**token_iter, "Expected a path to a SV file."}); | |
| return absl::InvalidArgumentError("Expected a path to a SV file."); | |
| } | |
| // Currently the file path looks like "path", we need to remove "". | |
| const auto& token_text = file_token_iter->text(); | |
| std::filesystem::path file_path = | |
| std::string(token_text.substr(1, token_text.size() - 2)); | |
| // Use the provided FileOpener to open the included file. | |
| const auto status_or_file = file_opener_(file_path.string()); | |
| if (!status_or_file.ok()) { | |
| preprocess_data_.errors.push_back( | |
| {**token_iter, std::string(status_or_file.status().message())}); | |
| return status_or_file.status(); | |
| } | |
| const absl::string_view source_contents = *status_or_file; | |
| // Creating a new "VerilogPreprocess" object for the included file, | |
| // With the same configuration and preprocessing info (defines, incdirs) as | |
| // the main one. | |
| // TODO(karimtera): Ideally modify the FileOpener to return | |
| // absl::StatusOr<MemBlock> to avoid doing a second copy inside TextStructure. | |
| verilog::VerilogPreprocess child_preprocessor(config_, file_opener_); | |
| child_preprocessor.setPreprocessingInfo(preprocess_info_); | |
| // TODO(karimtera): limit number of nested includes, detect cycles? maybe. | |
| preprocess_data_.included_text_structure.emplace_back( | |
| new verible::TextStructure(source_contents)); | |
| verible::TextStructure& included_structure = | |
| *preprocess_data_.included_text_structure.back(); | |
| // "included_sequence" should contain the lexed token sequence. | |
| verible::TokenSequence& included_sequence = | |
| included_structure.MutableData().MutableTokenStream(); | |
| // Lexing the included file content, and storing it in "included_sequence". | |
| verilog::VerilogLexer lexer(included_structure.Data().Contents()); | |
| for (lexer.DoNextToken(); !lexer.GetLastToken().isEOF(); | |
| lexer.DoNextToken()) { | |
| included_sequence.push_back(lexer.GetLastToken()); | |
| } | |
| // Preprocessing the included file tokens. | |
| verible::TokenStreamView lexed_streamview; | |
| InitTokenStreamView(included_sequence, &lexed_streamview); | |
| verilog::VerilogPreprocessData child_preprocessed_data = | |
| child_preprocessor.ScanStream(lexed_streamview); | |
| // Check for errors while preprocessing the included file. | |
| if (!child_preprocessed_data.errors.empty()) { | |
| preprocess_data_.errors.insert(preprocess_data_.errors.end(), | |
| child_preprocessed_data.errors.begin(), | |
| child_preprocessed_data.errors.end()); | |
| return absl::InvalidArgumentError( | |
| "Error: the included file preprocessing has failed."); | |
| } | |
| // Need to move the text structures of the child preprocessor to avoid | |
| // destruction. | |
| for (auto& u : child_preprocessed_data.included_text_structure) { | |
| preprocess_data_.included_text_structure.push_back(std::move(u)); | |
| } | |
| // Forwarding the included preprocessed view. | |
| for (const auto& u : child_preprocessed_data.preprocessed_token_stream) { | |
| preprocess_data_.preprocessed_token_stream.push_back(u); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| // Interprets preprocessor tokens as directives that act on this preprocessor | |
| // object and possibly transform the input token stream. | |
| absl::Status VerilogPreprocess::HandleTokenIterator( | |
| TokenStreamView::const_iterator iter, | |
| const StreamIteratorGenerator& generator) { | |
| switch ((*iter)->token_enum()) { | |
| case PP_define: | |
| return HandleDefine(iter, generator); | |
| case PP_undef: | |
| return HandleUndef(iter, generator); | |
| case PP_ifdef: | |
| case PP_ifndef: | |
| case PP_elsif: | |
| return HandleIf(iter, generator); | |
| case PP_else: | |
| return HandleElse(iter); | |
| case PP_endif: | |
| return HandleEndif(iter); | |
| } | |
| if (config_.expand_macros && ((*iter)->token_enum() == MacroIdentifier || | |
| (*iter)->token_enum() == MacroIdItem || | |
| (*iter)->token_enum() == MacroCallId)) { | |
| return HandleMacroIdentifier(iter, generator); | |
| } | |
| if (config_.include_files && (*iter)->token_enum() == PP_include) { | |
| return HandleInclude(iter, generator); | |
| } | |
| // If not return'ed above, any other tokens are passed through unmodified | |
| // unless filtered by a branch. | |
| if (conditional_block_.top().InSelectedBranch()) { | |
| preprocess_data_.preprocessed_token_stream.push_back(*iter); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| void VerilogPreprocess::setPreprocessingInfo( | |
| const verilog::FileList::PreprocessingInfo& preprocess_info) { | |
| preprocess_info_ = preprocess_info; | |
| // Adding defines. | |
| for (const auto& define : preprocess_info_.defines) { | |
| // manually create the tokens to save them into a MacroDefinition. | |
| verible::TokenInfo macro_directive(PP_define, "`define"); | |
| verible::TokenInfo macro_name(PP_Identifier, define.name); | |
| verible::TokenInfo macro_body(PP_define_body, define.value); | |
| verible::MacroDefinition macro_definition(macro_directive, macro_name); | |
| macro_definition.SetDefinitionText(macro_body); | |
| // Registers the macro definition to memeory. | |
| RegisterMacroDefinition(macro_definition); | |
| } | |
| // We can directly access "preprocess_info_.include_dirs" whenever needed. | |
| } | |
| VerilogPreprocessData VerilogPreprocess::ScanStream( | |
| const TokenStreamView& token_stream) { | |
| preprocess_data_.preprocessed_token_stream.reserve(token_stream.size()); | |
| auto iter_generator = verible::MakeConstIteratorStreamer(token_stream); | |
| const auto end = token_stream.end(); | |
| // Token-pulling loop. | |
| for (auto iter = iter_generator(); iter != end; iter = iter_generator()) { | |
| const auto status = HandleTokenIterator(iter, iter_generator); | |
| if (!status.ok()) { | |
| // Detailed errors are already in preprocessor_data_.errors. | |
| break; // For now, stop after first error. | |
| } | |
| } | |
| if (conditional_block_.size() > 1 && | |
| preprocess_data_.errors.empty()) { // Only report if not followup-error | |
| preprocess_data_.errors.push_back( | |
| {conditional_block_.top().token(), | |
| "Unterminated preprocessing conditional here, but never completed at " | |
| "end of file."}); | |
| } | |
| return std::move(preprocess_data_); | |
| } | |
| } // namespace verilog |