blob: 69d97598358394cc34180af663558e3a12868db1 [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.
// 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_