| // 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. |
| |
| // VerilogPreprocess is a *pseudo*-preprocessor for Verilog. |
| // Unlike a conventional preprocessor, this pseudo-preprocessor does not open |
| // included files, nor does it evaluate preprocessor expressions. |
| // Instead, it does a best-effort handling of preprocessor directives |
| // locally within one file, and no additional context. |
| // For example, it may expand a macro call if its definition happens to be |
| // available, but it is not required to do so. |
| // The pseudo-preprocessor is free to evaluate any/all/no conditional |
| // branches. |
| // Each analysis tool may configure the pseudo-preprocessor differently. |
| |
| // TODO(fangism): expand macros if locally defined, and feed un-lexed |
| // body text to lexer. This approach works if the definition text |
| // does not depend on the start-condition state at the macro call site. |
| // TODO(fangism): implement conditional evaluation policy (`ifdef, `else, ...) |
| // TODO(fangism): token concatenation, e.g. a``b |
| // This will produce tokens that are not in the original source text. |
| // TODO(fangism): token string-ification (turning symbol names into strings) |
| // TODO(fangism): evaluate `defines inside `defines at expansion time. |
| |
| #ifndef VERIBLE_VERILOG_PREPROCESSOR_VERILOG_PREPROCESS_H_ |
| #define VERIBLE_VERILOG_PREPROCESSOR_VERILOG_PREPROCESS_H_ |
| |
| #include <functional> |
| #include <map> |
| #include <memory> |
| #include <stack> |
| #include <string> |
| #include <vector> |
| |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/string_view.h" |
| #include "common/text/macro_definition.h" |
| #include "common/text/text_structure.h" |
| #include "common/text/token_info.h" |
| #include "common/text/token_stream_view.h" |
| #include "verilog/analysis/verilog_filelist.h" |
| |
| namespace verilog { |
| |
| // TODO(fangism): configuration policy enums. |
| |
| // VerilogPreprocessError contains preprocessor error information. |
| // TODO(hzeller): should we just use verible::RejectedToken here ? |
| struct VerilogPreprocessError { |
| verible::TokenInfo token_info; // offending token |
| std::string error_message; |
| |
| VerilogPreprocessError(const verible::TokenInfo& token, |
| const std::string& message) |
| : token_info(token), error_message(message) {} |
| }; |
| |
| // Information that results from preprocessing. |
| struct VerilogPreprocessData { |
| using MacroDefinition = verible::MacroDefinition; |
| using MacroDefinitionRegistry = std::map<absl::string_view, MacroDefinition>; |
| using TokenSequence = std::vector<verible::TokenInfo>; |
| |
| // Resulting token stream after preprocessing |
| verible::TokenStreamView preprocessed_token_stream; |
| std::vector<TokenSequence> lexed_macros_backup; |
| |
| // A backup memory that owns the content of the included files. |
| std::vector<std::unique_ptr<verible::TextStructure>> included_text_structure; |
| |
| // Map of defined macros. |
| MacroDefinitionRegistry macro_definitions; |
| |
| // Sequence of tokens rejected by preprocessing. |
| // TODO(hzeller): Until we have a severity in VerilogPreprocessError, these |
| // are two separate vectors. |
| std::vector<VerilogPreprocessError> errors; |
| std::vector<VerilogPreprocessError> warnings; |
| }; |
| |
| // VerilogPreprocess transforms a TokenStreamView. |
| // The input stream view is expected to have been stripped of whitespace. |
| class VerilogPreprocess { |
| using TokenStreamView = verible::TokenStreamView; |
| using MacroDefinition = verible::MacroDefinition; |
| using MacroParameterInfo = verible::MacroParameterInfo; |
| |
| public: |
| using FileOpener = |
| std::function<absl::StatusOr<absl::string_view>(absl::string_view)>; |
| struct Config { |
| // Filter out non-matching `ifdef and `ifndef branches depending on |
| // which defines are set. |
| // |
| // This option is useful for syntax check and possibly linting in |
| // case parsing fails with all consecutive branches emitted. |
| // |
| // Note, for formatting, we do _not_ want to filter the output as we |
| // want to emit all tokens. |
| bool filter_branches = false; |
| |
| // Inlude files with `include. |
| bool include_files = false; |
| |
| // Expand macro definition bodies, this will relexes the macro body. |
| bool expand_macros = false; |
| // TODO(hzeller): Provide a map of command-line provided +define+'s |
| }; |
| |
| explicit VerilogPreprocess(const Config& config); |
| VerilogPreprocess(const Config& config, FileOpener opener); |
| |
| // Initialize preprocessing with safe default options |
| // TODO(hzeller): remove this constructor once all places using the |
| // preprocessor have been updated to pass a config. |
| VerilogPreprocess() : VerilogPreprocess(Config()){}; |
| |
| // ScanStream reads in a stream of tokens returns the result as a move |
| // of preprocessor_data_. preprocessor_data_ should not be accessed |
| // after this returns. |
| VerilogPreprocessData ScanStream(const TokenStreamView& token_stream); |
| |
| // TODO(fangism): ExpandMacro, ExpandMacroCall |
| // TODO(b/111544845): ExpandEvalStringLiteral |
| |
| // Sets the preprocessing information containing defines and incdirs. |
| void setPreprocessingInfo( |
| const verilog::FileList::PreprocessingInfo& preprocess_info); |
| |
| private: |
| using StreamIteratorGenerator = |
| std::function<TokenStreamView::const_iterator()>; |
| |
| // Extract macro name after `define, `ifdef, `elsif ... and returns |
| // iterator of macro name or a failure status. |
| // Updates error messages on failure. |
| absl::StatusOr<TokenStreamView::const_iterator> ExtractMacroName( |
| const StreamIteratorGenerator&); |
| |
| absl::Status HandleTokenIterator(TokenStreamView::const_iterator, |
| const StreamIteratorGenerator&); |
| absl::Status HandleMacroIdentifier(TokenStreamView::const_iterator, |
| const StreamIteratorGenerator&, |
| bool forward); |
| |
| absl::Status HandleDefine(TokenStreamView::const_iterator, |
| const StreamIteratorGenerator&); |
| absl::Status HandleUndef(TokenStreamView::const_iterator, |
| const StreamIteratorGenerator&); |
| |
| absl::Status HandleIf(TokenStreamView::const_iterator ifpos, |
| const StreamIteratorGenerator&); |
| absl::Status HandleElse(TokenStreamView::const_iterator else_pos); |
| absl::Status HandleEndif(TokenStreamView::const_iterator endif_pos); |
| |
| static absl::Status ConsumeAndParseMacroCall(TokenStreamView::const_iterator, |
| const StreamIteratorGenerator&, |
| verible::MacroCall*, |
| const verible::MacroDefinition&); |
| |
| // The following functions return nullptr when there is no error: |
| absl::Status ConsumeMacroDefinition(const StreamIteratorGenerator&, |
| TokenStreamView*); |
| |
| static std::unique_ptr<VerilogPreprocessError> ParseMacroDefinition( |
| const TokenStreamView&, MacroDefinition*); |
| |
| static std::unique_ptr<VerilogPreprocessError> ParseMacroParameter( |
| TokenStreamView::const_iterator*, MacroParameterInfo*); |
| |
| void RegisterMacroDefinition(const MacroDefinition&); |
| absl::Status ExpandText(const absl::string_view&); |
| absl::Status ExpandMacro(const verible::MacroCall&, |
| const verible::MacroDefinition*); |
| absl::Status HandleInclude(TokenStreamView::const_iterator, |
| const StreamIteratorGenerator&); |
| |
| // Generate a const_iterator to a non-whitespace token. |
| static TokenStreamView::const_iterator GenerateBypassWhiteSpaces( |
| const StreamIteratorGenerator&); |
| |
| const Config config_; |
| |
| // State of a block that can have a sequence of sub-blocks with conditions |
| // (ifdef/elsif/else/endif). Only the first of the subblock whose condition |
| // matches will be selected - or the else block if none before matched. |
| class BranchBlock { |
| public: |
| BranchBlock(bool is_enabled, bool condition, |
| const verible::TokenInfo& token) |
| : outer_scope_enabled_(is_enabled), branch_token_(token) { |
| UpdateCondition(token, condition); |
| } |
| |
| // Is the current block selected, i.e. should tokens pass through. |
| bool InSelectedBranch() const { |
| return outer_scope_enabled_ && current_branch_condition_met_; |
| } |
| |
| // Update condition e.g. in `elsif. Return 'false' if already in an |
| // else clause. |
| bool UpdateCondition(const verible::TokenInfo& token, bool condition) { |
| if (in_else_) return false; |
| branch_token_ = token; |
| // Only the first in a row of matching conditions will select block. |
| current_branch_condition_met_ = !any_branch_matched_ && condition; |
| any_branch_matched_ |= condition; |
| return true; |
| } |
| |
| // Start an `else block. Uses its internal state to determine if |
| // this will put is InSelectedBranch(). |
| // Returns 'false' if already in an else block. |
| bool StartElse(const verible::TokenInfo& token) { |
| if (in_else_) return false; |
| in_else_ = true; |
| branch_token_ = token; |
| current_branch_condition_met_ = !any_branch_matched_; |
| return true; |
| } |
| |
| const verible::TokenInfo& token() const { return branch_token_; } |
| |
| private: |
| const bool outer_scope_enabled_; |
| verible::TokenInfo branch_token_; // FYI for error reporting. |
| bool any_branch_matched_ = false; // only if no branch, `else will |
| bool in_else_ = false; |
| bool current_branch_condition_met_; |
| }; |
| |
| // State of nested conditional blocks. For code simplicity, this always has |
| // a toplevel branch that is selected. |
| std::stack<BranchBlock> conditional_block_; |
| |
| // Results of preprocessing |
| VerilogPreprocessData preprocess_data_; |
| |
| // Defines and incdirs Information passed externally. |
| FileList::PreprocessingInfo preprocess_info_; |
| |
| // A pointer to a file opener function. |
| // This is needed for opening new files while handling includes. |
| const FileOpener file_opener_ = nullptr; |
| }; |
| |
| } // namespace verilog |
| |
| #endif // VERIBLE_VERILOG_PREPROCESSOR_VERILOG_PREPROCESS_H_ |