blob: 6232ff925d7aaf073cb06396423bc79f7d9b091c [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/formatting/tree_unwrapper.h"
#include <algorithm>
#include <iterator>
#include <vector>
#include "absl/base/macros.h"
#include "absl/strings/match.h"
#include "common/formatting/basic_format_style.h"
#include "common/formatting/format_token.h"
#include "common/formatting/token_partition_tree.h"
#include "common/formatting/tree_unwrapper.h"
#include "common/formatting/unwrapped_line.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/syntax_tree_context.h"
#include "common/text/text_structure.h"
#include "common/text/token_info.h"
#include "common/text/token_stream_view.h"
#include "common/text/tree_utils.h"
#include "common/util/container_iterator_range.h"
#include "common/util/enum_flags.h"
#include "common/util/logging.h"
#include "common/util/value_saver.h"
#include "verilog/CST/declaration.h"
#include "verilog/CST/functions.h"
#include "verilog/CST/macro.h"
#include "verilog/CST/statement.h"
#include "verilog/CST/verilog_nonterminals.h"
#include "verilog/formatting/verilog_token.h"
#include "verilog/parser/verilog_parser.h"
#include "verilog/parser/verilog_token_classifications.h"
#include "verilog/parser/verilog_token_enum.h"
namespace verilog {
namespace formatter {
using ::verible::PartitionPolicyEnum;
using ::verible::PreFormatToken;
using ::verible::SyntaxTreeNode;
using ::verible::TokenInfo;
using ::verible::TokenPartitionTree;
using ::verible::TokenWithContext;
using ::verilog::IsComment;
// Used to filter the TokenStreamView by discarding space-only tokens.
static bool KeepNonWhitespace(const TokenInfo& t) {
if (t.token_enum() == verible::TK_EOF) return false; // omit the EOF token
return !IsWhitespace(verilog_tokentype(t.token_enum()));
}
static bool NodeIsBeginEndBlock(const SyntaxTreeNode& node) {
return node.MatchesTagAnyOf({NodeEnum::kSeqBlock, NodeEnum::kGenerateBlock});
}
static SyntaxTreeNode& GetBlockEnd(const SyntaxTreeNode& block) {
CHECK(NodeIsBeginEndBlock(block));
return verible::SymbolCastToNode(*block.children().back());
}
static const verible::Symbol* GetEndLabel(const SyntaxTreeNode& end_node) {
return GetSubtreeAsSymbol(end_node, NodeEnum::kEnd, 1);
}
static bool NodeIsConditionalConstruct(const SyntaxTreeNode& node) {
return node.MatchesTagAnyOf({NodeEnum::kConditionalStatement,
NodeEnum::kConditionalGenerateConstruct});
}
static bool NodeIsConditionalOrBlock(const SyntaxTreeNode& node) {
return NodeIsBeginEndBlock(node) || NodeIsConditionalConstruct(node);
}
// Creates a PreFormatToken given a TokenInfo and returns it
static PreFormatToken CreateFormatToken(const TokenInfo& token) {
PreFormatToken format_token(&token);
format_token.format_token_enum =
GetFormatTokenType(verilog_tokentype(format_token.TokenEnum()));
switch (format_token.format_token_enum) {
case FormatTokenType::open_group:
format_token.balancing = verible::GroupBalancing::Open;
break;
case FormatTokenType::close_group:
format_token.balancing = verible::GroupBalancing::Close;
break;
default:
format_token.balancing = verible::GroupBalancing::None;
break;
}
return format_token;
}
UnwrapperData::UnwrapperData(const verible::TokenSequence& tokens) {
// Create a TokenStreamView that removes spaces, but preserves comments.
{
verible::InitTokenStreamView(tokens, &tokens_view_no_whitespace);
verible::FilterTokenStreamViewInPlace(KeepNonWhitespace,
&tokens_view_no_whitespace);
}
// Create an array of PreFormatTokens.
{
preformatted_tokens.reserve(tokens_view_no_whitespace.size());
std::transform(tokens_view_no_whitespace.begin(),
tokens_view_no_whitespace.end(),
std::back_inserter(preformatted_tokens),
[=](verible::TokenSequence::const_iterator iter) {
return CreateFormatToken(*iter);
});
}
}
// Represents a state of scanning tokens between syntax tree leaves.
enum TokenScannerState {
// Initial state: immediately after a leaf token
kStart,
// While encountering any string of consecutive newlines
kHaveNewline,
// Transition from newline to non-newline
kNewPartition,
// Reached next leaf token, stop. Preserve existing newline.
kEndWithNewline,
// Reached next leaf token, stop.
kEndNoNewline,
};
static const std::initializer_list<
std::pair<const absl::string_view, TokenScannerState>>
kTokenScannerStateStringMap = {
{"kStart", TokenScannerState::kStart},
{"kHaveNewline", TokenScannerState::kHaveNewline},
{"kNewPartition", TokenScannerState::kNewPartition},
{"kEndWithNewline", TokenScannerState::kEndWithNewline},
{"kEndNoNewline", TokenScannerState::kEndNoNewline},
};
// Conventional stream printer (declared in header providing enum).
std::ostream& operator<<(std::ostream& stream, TokenScannerState p) {
static const auto* flag_map =
verible::MakeEnumToStringMap(kTokenScannerStateStringMap);
return stream << flag_map->find(p)->second;
}
// This finite state machine class is used to determine the placement of
// non-whitespace and non-syntax-tree-node tokens such as comments in
// UnwrappedLines. The input to this FSM is a Verilog-specific token enum.
// This class is an internal implementation detail of the TreeUnwrapper class.
// TODO(fangism): handle attributes.
class TreeUnwrapper::TokenScanner {
public:
TokenScanner() = default;
// Deleted standard interfaces:
TokenScanner(const TokenScanner&) = delete;
TokenScanner(TokenScanner&&) = delete;
TokenScanner& operator=(const TokenScanner&) = delete;
TokenScanner& operator=(TokenScanner&&) = delete;
// Re-initializes state.
void Reset() {
current_state_ = State::kStart;
seen_any_nonspace_ = false;
}
// Calls TransitionState using current_state_ and the tranition token_Type
void UpdateState(verilog_tokentype token_type) {
current_state_ = TransitionState(current_state_, token_type);
seen_any_nonspace_ |= IsComment(token_type);
}
// Returns true if this is a state that should start a new token partition.
bool ShouldStartNewPartition() const {
return current_state_ == State::kNewPartition ||
(current_state_ == State::kEndWithNewline && seen_any_nonspace_);
}
protected:
typedef TokenScannerState State;
// The current state of the TokenScanner.
State current_state_ = kStart;
bool seen_any_nonspace_ = false;
// Transitions the TokenScanner given a TokenState and a verilog_tokentype
// transition
static State TransitionState(const State& old_state,
verilog_tokentype token_type);
};
static bool IsNewlineOrEOF(verilog_tokentype token_type) {
return token_type == verilog_tokentype::TK_NEWLINE ||
token_type == verible::TK_EOF;
}
/*
* TransitionState computes the next state in the state machine given
* a current TokenScanner::State and token_type transition.
* This state machine is only expected to handle tokens that can appear
* between syntax tree leaves (whitespace, comments, attributes).
*
* Transitions are expected to take place across 3 phases of inter-leaf
* scanning:
* TreeUnwrapper::LookAheadBeyondCurrentLeaf()
* TreeUnwrapper::LookAheadBeyondCurrentNode()
* TreeUnwrapper::CatchUpToCurrentLeaf()
*/
TokenScannerState TreeUnwrapper::TokenScanner::TransitionState(
const State& old_state, verilog_tokentype token_type) {
VLOG(4) << "state transition on: " << old_state
<< ", token: " << verilog_symbol_name(token_type);
State new_state = old_state;
switch (old_state) {
case kStart: {
if (IsNewlineOrEOF(token_type)) {
new_state = kHaveNewline;
} else if (IsComment(token_type)) {
new_state = kStart; // same state
} else {
new_state = kEndNoNewline;
}
break;
}
case kHaveNewline: {
if (IsNewlineOrEOF(token_type)) {
new_state = kHaveNewline; // same state
} else if (IsComment(token_type)) {
new_state = kNewPartition;
} else {
new_state = kEndWithNewline;
}
break;
}
case kNewPartition: {
if (IsNewlineOrEOF(token_type)) {
new_state = kHaveNewline;
} else if (IsComment(token_type)) {
new_state = kStart;
} else {
new_state = kEndNoNewline;
}
break;
}
case kEndWithNewline:
case kEndNoNewline: {
// Terminal states. Must Reset() next.
break;
}
default:
break;
}
VLOG(4) << "new state: " << new_state;
return new_state;
}
void TreeUnwrapper::EatSpaces() {
// TODO(fangism): consider a NextNonSpaceToken() method that combines
// NextUnfilteredToken with EatSpaces.
const auto before = NextUnfilteredToken();
SkipUnfilteredTokens(
[](const TokenInfo& token) { return token.token_enum() == TK_SPACE; });
const auto after = NextUnfilteredToken();
VLOG(4) << __FUNCTION__ << " ate " << std::distance(before, after)
<< " space tokens";
}
TreeUnwrapper::TreeUnwrapper(const verible::TextStructureView& view,
const FormatStyle& style,
const preformatted_tokens_type& ftokens)
: verible::TreeUnwrapper(view, ftokens),
style_(style),
inter_leaf_scanner_(new TokenScanner),
token_context_(FullText(), [](std::ostream& stream, int e) {
stream << verilog_symbol_name(e);
}) {
// Verify that unfiltered token stream is properly EOF terminated,
// so that stream scanners (inter_leaf_scanner_) know when to stop.
const auto& tokens = view.TokenStream();
CHECK(!tokens.empty());
const auto& back(tokens.back());
CHECK(back.isEOF());
CHECK(back.text().empty());
}
TreeUnwrapper::~TreeUnwrapper() {}
static bool IsPreprocessorClause(NodeEnum e) {
switch (e) {
case NodeEnum::kPreprocessorIfdefClause:
case NodeEnum::kPreprocessorIfndefClause:
case NodeEnum::kPreprocessorElseClause:
case NodeEnum::kPreprocessorElsifClause:
return true;
default:
return false;
}
}
// These are constructs where it is permissible to fit on one line, but in the
// event that the statement body is split, we need to ensure it is properly
// indented, even if it is a single statement.
// Keep this list in sync below where the same function name appears in comment.
static bool ShouldIndentRelativeToDirectParent(
const verible::SyntaxTreeContext& context) {
// TODO(fangism): flip logic to check that item/statement is *not* a direct
// child of one of the exceptions: sequential/parallel/generate blocks.
return context.DirectParentIsOneOf({
//
NodeEnum::kLoopGenerateConstruct, //
NodeEnum::kCaseStatement, //
NodeEnum::kRandCaseStatement, //
NodeEnum::kForLoopStatement, //
NodeEnum::kForeverLoopStatement, //
NodeEnum::kRepeatLoopStatement, //
NodeEnum::kWhileLoopStatement, //
NodeEnum::kDoWhileLoopStatement, //
NodeEnum::kForeachLoopStatement, //
NodeEnum::kConditionalStatement, //
NodeEnum::kIfClause, //
NodeEnum::kGenerateIfClause, //
NodeEnum::kAssertionClause, //
NodeEnum::kAssumeClause, //
NodeEnum::kProceduralTimingControlStatement, //
NodeEnum::kCoverStatement, //
NodeEnum::kAssertPropertyClause, //
NodeEnum::kAssumePropertyClause, //
NodeEnum::kExpectPropertyClause, //
NodeEnum::kCoverPropertyStatement, //
NodeEnum::kCoverSequenceStatement, //
NodeEnum::kWaitStatement, //
NodeEnum::kInitialStatement, //
NodeEnum::kAlwaysStatement, //
NodeEnum::kFinalStatement, //
// Do not further indent under kElseClause and kElseGenerateClause,
// so that chained else-ifs remain flat.
});
}
void TreeUnwrapper::UpdateInterLeafScanner(verilog_tokentype token_type) {
VLOG(4) << __FUNCTION__ << ", token: " << verilog_symbol_name(token_type);
inter_leaf_scanner_->UpdateState(token_type);
if (inter_leaf_scanner_->ShouldStartNewPartition()) {
VLOG(4) << "new partition";
// interleaf-tokens like comments do not have corresponding syntax tree
// nodes, so pass nullptr.
StartNewUnwrappedLine(PartitionPolicyEnum::kFitOnLineElseExpand, nullptr);
}
VLOG(4) << "end of " << __FUNCTION__;
}
void TreeUnwrapper::AdvanceLastVisitedLeaf() {
VLOG(4) << __FUNCTION__;
EatSpaces();
const auto& next_token = *NextUnfilteredToken();
const verilog_tokentype token_enum =
verilog_tokentype(next_token.token_enum());
UpdateInterLeafScanner(token_enum);
AdvanceNextUnfilteredToken();
VLOG(4) << "end of " << __FUNCTION__;
}
verible::TokenWithContext TreeUnwrapper::VerboseToken(
const TokenInfo& token) const {
return TokenWithContext{token, token_context_};
}
void TreeUnwrapper::CatchUpToCurrentLeaf(const TokenInfo& leaf_token) {
VLOG(4) << __FUNCTION__ << " to " << VerboseToken(leaf_token);
// "Catch up" NextUnfilteredToken() to the current leaf.
// Assigns non-whitespace tokens such as comments into UnwrappedLines.
// Recall that SyntaxTreeLeaf has its own copy of TokenInfo, so we need to
// compare a unique property of TokenInfo instead of its address.
while (!NextUnfilteredToken()->isEOF()) {
EatSpaces();
// compare const char* addresses:
if (NextUnfilteredToken()->text().begin() != leaf_token.text().begin()) {
VLOG(4) << "token: " << VerboseToken(*NextUnfilteredToken());
AdvanceLastVisitedLeaf();
} else {
break;
}
}
VLOG(4) << "end of " << __FUNCTION__;
}
// Scan forward from the current leaf token, until some implementation-defined
// stopping condition. This collects comments up until the first newline,
// and appends them to the most recent partition.
void TreeUnwrapper::LookAheadBeyondCurrentLeaf() {
VLOG(4) << __FUNCTION__;
// Re-initialize internal token-scanning state machine.
inter_leaf_scanner_->Reset();
// Consume trailing comments after the last leaf token.
// Ignore spaces but stop at newline.
// Advance until hitting a newline or catching up to the current leaf node,
// whichever comes first.
//
// TODO(fangism): This might not adequately handle a block of //-style
// comments that span multiple lines. e.g.
//
// ... some token; // start of long comment ...
// // continuation of long comment
// next tokens...
//
while (!NextUnfilteredToken()->isEOF()) {
EatSpaces();
VLOG(4) << "lookahead token: " << VerboseToken(*NextUnfilteredToken());
if (IsComment(verilog_tokentype(NextUnfilteredToken()->token_enum()))) {
// TODO(fangism): or IsAttribute(). Basically, any token that is not
// in the syntax tree and not a space.
AdvanceLastVisitedLeaf();
} else { // including newline
// Don't advance NextUnfilteredToken() in this case.
VLOG(4) << "no advance";
break;
}
}
VLOG(4) << "end of " << __FUNCTION__;
}
// Scan forward up to a syntax tree leaf token, but return (possibly) the
// position of the last newline *before* that leaf token.
// This allows prefix comments and attributes to stick with their
// intended token that immediately follows.
static verible::TokenSequence::const_iterator StopAtLastNewlineBeforeTreeLeaf(
const verible::TokenSequence::const_iterator token_begin,
const TokenInfo::Context& context) {
VLOG(4) << __FUNCTION__;
auto token_iter = token_begin;
auto last_newline = token_begin;
bool have_last_newline = false;
// Find next syntax tree token or EOF.
bool break_while = false;
while (!token_iter->isEOF() && !break_while) {
VLOG(4) << "scan: " << TokenWithContext{*token_iter, context};
switch (token_iter->token_enum()) {
// TODO(b/144653479): this token-case logic is redundant with other
// places; plumb that through to here instead of replicating it.
case TK_NEWLINE:
have_last_newline = true;
last_newline = token_iter;
ABSL_FALLTHROUGH_INTENDED;
case TK_SPACE:
case TK_EOL_COMMENT:
case TK_COMMENT_BLOCK:
case TK_ATTRIBUTE:
++token_iter;
break;
default:
break_while = true;
break;
}
}
const auto result = have_last_newline ? last_newline : token_iter;
VLOG(4) << "end of " << __FUNCTION__ << ", advanced "
<< std::distance(token_begin, result) << " tokens";
return result;
}
// Scan forward for comments between leaf tokens, and append them to a partition
// with the correct amount of indentation.
void TreeUnwrapper::LookAheadBeyondCurrentNode() {
VLOG(4) << __FUNCTION__;
// Scan until token is reached, or the last newline before token is reached.
const auto token_begin = NextUnfilteredToken();
const auto token_end =
StopAtLastNewlineBeforeTreeLeaf(token_begin, token_context_);
VLOG(4) << "stop before: " << VerboseToken(*token_end);
while (NextUnfilteredToken() != token_end) {
// Almost like AdvanceLastVisitedLeaf(), except suppress the last
// advancement.
EatSpaces();
const auto next_token_iter = NextUnfilteredToken();
const auto& next_token = *next_token_iter;
VLOG(4) << "token: " << VerboseToken(next_token);
const verilog_tokentype token_enum =
verilog_tokentype(next_token.token_enum());
UpdateInterLeafScanner(token_enum);
if (next_token_iter != token_end) {
AdvanceNextUnfilteredToken();
} else {
break;
}
}
VLOG(4) << "end of " << __FUNCTION__;
}
// This hook is called between the children nodes of the handled node types.
// This is what allows partitions containing only comments to be properly
// indented to the same level that non-comment sub-partitions would.
void TreeUnwrapper::InterChildNodeHook(const SyntaxTreeNode& node) {
const auto tag = NodeEnum(node.Tag().tag);
VLOG(4) << __FUNCTION__ << " node type: " << tag;
switch (tag) {
// TODO(fangism): cover all other major lists
case NodeEnum::kFormalParameterList:
case NodeEnum::kPortDeclarationList:
case NodeEnum::kActualParameterByNameList:
case NodeEnum::kPortActualList:
// case NodeEnum::kPortList: // TODO(fangism): for task/function ports
case NodeEnum::kModuleItemList:
case NodeEnum::kGenerateItemList:
case NodeEnum::kClassItems:
case NodeEnum::kDescriptionList: // top-level item comments
case NodeEnum::kStatementList:
case NodeEnum::kPackageItemList:
case NodeEnum::kSpecifyItemList:
case NodeEnum::kBlockItemStatementList:
case NodeEnum::kCaseItemList:
case NodeEnum::kCaseInsideItemList:
case NodeEnum::kGenerateCaseItemList:
case NodeEnum::kConstraintExpressionList:
case NodeEnum::kConstraintBlockItemList:
LookAheadBeyondCurrentNode();
break;
default: {
if (Context().DirectParentIs(NodeEnum::kMacroArgList)) {
StartNewUnwrappedLine(PartitionPolicyEnum::kFitOnLineElseExpand, &node);
}
break;
}
}
VLOG(4) << "end of " << __FUNCTION__ << " node type: " << tag;
}
void TreeUnwrapper::CollectLeadingFilteredTokens() {
VLOG(4) << __FUNCTION__;
// inter_leaf_scanner_ is already initialized to kStart state
LookAheadBeyondCurrentNode();
VLOG(4) << "end of " << __FUNCTION__;
}
void TreeUnwrapper::CollectTrailingFilteredTokens() {
VLOG(4) << __FUNCTION__;
// This should emulate ::Visit(leaf == EOF token).
// A newline means there are no comments to add to this UnwrappedLine
// TODO(fangism): fold this logic into CatchUpToCurrentLeaf()
if (IsNewlineOrEOF(verilog_tokentype(NextUnfilteredToken()->token_enum()))) {
// Filtered tokens may include comments, which do not correspond to syntax
// tree nodes, so pass nullptr.
StartNewUnwrappedLine(PartitionPolicyEnum::kFitOnLineElseExpand, nullptr);
}
// "Catch up" to EOF.
// The very last unfiltered token scanned should be the EOF.
// The byte offset of the EOF token is used as the stop-condition.
CatchUpToCurrentLeaf(EOFToken());
VLOG(4) << "end of " << __FUNCTION__;
}
// Visitor to determine which node enum function to call
void TreeUnwrapper::Visit(const SyntaxTreeNode& node) {
const auto tag = static_cast<NodeEnum>(node.Tag().tag);
VLOG(3) << __FUNCTION__ << " node: " << tag;
// This phase is only concerned with creating token partitions (during tree
// recursive descent) and setting correct indentation values. It is ok to
// have excessive partitioning during this phase.
SetIndentationsAndCreatePartitions(node);
// This phase is only concerned with reshaping operations on token partitions,
// such as merging, flattening, hoisting. Reshaping should only occur on the
// return path of tree traversal (here).
auto* partition = CurrentTokenPartition()->PreviousSibling();
if (partition != nullptr) {
ReshapeTokenPartitions(node, style_, partition);
}
}
// CST-descending phase that creates partitions with correct indentation.
void TreeUnwrapper::SetIndentationsAndCreatePartitions(
const SyntaxTreeNode& node) {
const auto tag = static_cast<NodeEnum>(node.Tag().tag);
VLOG(3) << __FUNCTION__ << " node: " << tag;
// Tips:
// In addition to the handling based on the node enum,
// indentation decisions can be based on the following:
// * Context() -- lookup upwards
// * node.children() substructure -- look downwards
// + prefer to use more robust CST functions that have abstracted
// away positional and structural details.
// * Localize examination of the above to 1 or 2 levels (up/down)
// where possible.
//
// Do not:
// * try to manipulate the partition structures; reshaping is left to a
// different phase, ReshapeTokenPartitions().
// Suppress additional indentation when:
// - at the syntax tree root
// - direct parent is `ifdef/`ifndef/`else/`elsif clause
// - at the first level of macro argument expansion
const bool suppress_indentation =
Context().empty() ||
IsPreprocessorClause(NodeEnum(Context().top().Tag().tag)) ||
Context().DirectParentIs(NodeEnum::kMacroArgList);
// Traversal control section.
switch (tag) {
case NodeEnum::kDescriptionList: {
// top-level doesn't need to open a new partition level
// This is because the TreeUnwrapper base class created this partition
// already.
// TODO(fangism): make this consistent with style of other cases
// and call VisitIndentedSection(node, 0, kAlwaysExpand);
VisitNewUnwrappedLine(node);
break;
}
// Indent only when applying kAppendFittingSubPartitions in parents
case NodeEnum::kArgumentList:
case NodeEnum::kIdentifierList: {
if (Context().DirectParentsAre(
{NodeEnum::kParenGroup, NodeEnum::kRandomizeFunctionCall}) ||
Context().DirectParentsAre(
{NodeEnum::kParenGroup, NodeEnum::kFunctionCall}) ||
Context().DirectParentsAre(
{NodeEnum::kParenGroup,
NodeEnum::kRandomizeMethodCallExtension}) ||
Context().DirectParentsAre(
{NodeEnum::kParenGroup, NodeEnum::kSystemTFCall}) ||
Context().DirectParentsAre(
{NodeEnum::kParenGroup, NodeEnum::kMethodCallExtension})) {
// TODO(fangism): Using wrap_spaces because of poor support of
// function/system/method/random calls inside trailing assignments,
// if headers, ternary operators and so on
VisitIndentedSection(node, style_.wrap_spaces,
PartitionPolicyEnum::kFitOnLineElseExpand);
} else {
TraverseChildren(node);
}
break;
}
// The following constructs are flushed-left, not indented:
case NodeEnum::kPreprocessorIfdefClause:
case NodeEnum::kPreprocessorIfndefClause:
case NodeEnum::kPreprocessorElseClause:
case NodeEnum::kPreprocessorElsifClause: {
VisitNewUnindentedUnwrappedLine(node);
break;
}
// The following constructs start a new UnwrappedLine indented to the
// current level.
case NodeEnum::kGenerateRegion:
case NodeEnum::kCaseGenerateConstruct:
case NodeEnum::kLoopGenerateConstruct:
case NodeEnum::kClassConstructor:
case NodeEnum::kPackageImportDeclaration:
// TODO(fangism): case NodeEnum::kDPIExportItem:
case NodeEnum::kPreprocessorInclude:
case NodeEnum::kPreprocessorUndef:
case NodeEnum::kTFPortDeclaration:
case NodeEnum::kTypeDeclaration:
case NodeEnum::kForwardDeclaration:
case NodeEnum::kConstraintDeclaration:
case NodeEnum::kConstraintExpression:
case NodeEnum::kCovergroupDeclaration:
case NodeEnum::kCoverageOption:
case NodeEnum::kCoverageBin:
case NodeEnum::kCoverPoint:
case NodeEnum::kCoverCross:
case NodeEnum::kBinsSelection:
case NodeEnum::kDistributionItem:
case NodeEnum::kStructUnionMember:
case NodeEnum::kEnumName:
case NodeEnum::kNetDeclaration:
case NodeEnum::kModulePortDeclaration:
case NodeEnum::kAlwaysStatement:
case NodeEnum::kInitialStatement:
case NodeEnum::kFinalStatement:
case NodeEnum::kDisableStatement:
case NodeEnum::kSpecifyItem:
case NodeEnum::kForInitialization:
case NodeEnum::kForCondition:
case NodeEnum::kForStepList:
case NodeEnum::kParamByName:
case NodeEnum::kActualNamedPort:
case NodeEnum::kActualPositionalPort:
case NodeEnum::kAssertionVariableDeclaration:
case NodeEnum::kPortItem:
case NodeEnum::kMacroFormalArg:
case NodeEnum::kPropertyDeclaration:
case NodeEnum::kSequenceDeclaration:
case NodeEnum::kPort:
case NodeEnum::kPortDeclaration:
case NodeEnum::kParamDeclaration:
case NodeEnum::kClockingDeclaration:
case NodeEnum::kClockingItem:
case NodeEnum::kUdpPrimitive:
case NodeEnum::kGenvarDeclaration:
case NodeEnum::kConditionalGenerateConstruct:
case NodeEnum::kMacroGenericItem:
case NodeEnum::kModuleHeader:
case NodeEnum::kBindDirective:
case NodeEnum::kDataDeclaration:
case NodeEnum::kGateInstantiation:
case NodeEnum::kLoopHeader:
case NodeEnum::kCovergroupHeader:
case NodeEnum::kModportDeclaration:
case NodeEnum::kInstantiationType:
case NodeEnum::kRegisterVariable:
case NodeEnum::kVariableDeclarationAssignment:
case NodeEnum::kCaseItem:
case NodeEnum::kDefaultItem:
case NodeEnum::kCaseInsideItem:
case NodeEnum::kCasePatternItem:
case NodeEnum::kGenerateCaseItem:
case NodeEnum::kGateInstance:
case NodeEnum::kGenerateIfClause:
case NodeEnum::kGenerateElseClause:
case NodeEnum::kGenerateIfHeader:
case NodeEnum::kIfClause:
case NodeEnum::kElseClause:
case NodeEnum::kIfHeader:
// TODO(fangism): k{Assert,Assume,Expect}PropertyClause
case NodeEnum::kAssertionClause:
case NodeEnum::kAssumeClause:
case NodeEnum::kAssertPropertyClause:
case NodeEnum::kAssumePropertyClause:
case NodeEnum::kExpectPropertyClause: {
VisitIndentedSection(node, 0, PartitionPolicyEnum::kFitOnLineElseExpand);
break;
}
// The following cases will always expand into their constituent
// partitions:
case NodeEnum::kModuleDeclaration:
case NodeEnum::kProgramDeclaration:
case NodeEnum::kPackageDeclaration:
case NodeEnum::kInterfaceDeclaration:
case NodeEnum::kFunctionDeclaration:
case NodeEnum::kTaskDeclaration:
case NodeEnum::kClassDeclaration:
case NodeEnum::kClassHeader:
case NodeEnum::kBegin:
case NodeEnum::kEnd:
// case NodeEnum::kFork: // TODO(fangism): introduce this node enum
// case NodeEnum::kJoin: // TODO(fangism): introduce this node enum
case NodeEnum::kParBlock:
case NodeEnum::kSeqBlock:
case NodeEnum::kGenerateBlock: {
VisitIndentedSection(node, 0, PartitionPolicyEnum::kAlwaysExpand);
break;
}
// The following set of cases are related to flow-control (loops and
// conditionals) for statements and generate items:
case NodeEnum::kAssertionBody:
case NodeEnum::kAssumeBody:
case NodeEnum::kCoverBody:
case NodeEnum::kAssertPropertyBody:
case NodeEnum::kAssumePropertyBody:
case NodeEnum::kExpectPropertyBody:
case NodeEnum::kCoverPropertyBody:
case NodeEnum::kCoverSequenceBody:
case NodeEnum::kWaitBody:
case NodeEnum::kGenerateIfBody:
case NodeEnum::kIfBody: {
// In the case of if-begin, let the 'begin'-'end' block indent its own
// section. Othewise for single statements/items, indent here.
const auto* subnode =
verible::CheckOptionalSymbolAsNode(GetSubtreeAsSymbol(node, tag, 0));
const auto next_indent =
(subnode != nullptr && NodeIsBeginEndBlock(*subnode))
? 0
: style_.indentation_spaces;
VisitIndentedSection(node, next_indent,
PartitionPolicyEnum::kFitOnLineElseExpand);
break;
}
case NodeEnum::kGenerateElseBody:
case NodeEnum::kElseBody: {
// In the case of else-begin, let the 'begin'-'end' block indent its own
// section, otherwise, indent single statements here.
// In the case of else-if, suppress further indentation, deferring to
// the conditional construct.
const auto& subnode = GetSubtreeAsNode(node, tag, 0);
const auto next_indent =
NodeIsConditionalOrBlock(subnode) ? 0 : style_.indentation_spaces;
VisitIndentedSection(node, next_indent,
PartitionPolicyEnum::kFitOnLineElseExpand);
break;
}
// For the following items, start a new unwrapped line only if they are
// *direct* descendants of list elements. This effectively suppresses
// starting a new line when single statements are found to extend other
// statements, delayed assignments, single-statement if/for loops.
// search-anchor: STATEMENT_TYPES
case NodeEnum::kNullItem: // ;
case NodeEnum::kNullStatement: // ;
case NodeEnum::kStatement:
case NodeEnum::kLabeledStatement: // e.g. foo_label : do_something();
case NodeEnum::kJumpStatement:
case NodeEnum::kWaitStatement: // wait(expr) ...
case NodeEnum::kWaitForkStatement: // wait fork;
case NodeEnum::kAssertionStatement: // assert(expr);
case NodeEnum::kAssumeStatement: // assume(expr);
case NodeEnum::kCoverStatement: // cover(expr);
case NodeEnum::kAssertPropertyStatement:
case NodeEnum::kAssumePropertyStatement:
case NodeEnum::kExpectPropertyStatement:
case NodeEnum::kCoverPropertyStatement:
case NodeEnum::kCoverSequenceStatement:
// TODO(fangism): case NodeEnum::kRestrictPropertyStatement:
case NodeEnum::kContinuousAssignmentStatement: // e.g. assign x=y;
case NodeEnum::kProceduralContinuousAssignmentStatement: // e.g. assign
// x=y;
case NodeEnum::kProceduralContinuousDeassignmentStatement:
case NodeEnum::kProceduralContinuousForceStatement:
case NodeEnum::kProceduralContinuousReleaseStatement:
case NodeEnum::kNetVariableAssignment: // e.g. x=y
case NodeEnum::kBlockingAssignmentStatement: // id=expr
case NodeEnum::kNonblockingAssignmentStatement: // dest <= src;
case NodeEnum::kAssignModifyStatement: // id+=expr
case NodeEnum::kIncrementDecrementExpression: // --y
case NodeEnum::kProceduralTimingControlStatement:
// various flow control constructs
case NodeEnum::kCaseStatement:
case NodeEnum::kRandCaseStatement:
case NodeEnum::kForLoopStatement:
case NodeEnum::kForeverLoopStatement:
case NodeEnum::kRepeatLoopStatement:
case NodeEnum::kWhileLoopStatement:
case NodeEnum::kDoWhileLoopStatement:
case NodeEnum::kForeachLoopStatement:
case NodeEnum::kConditionalStatement: //
{
// Single statements directly inside a flow-control construct
// should be properly indented one level.
const int indent = ShouldIndentRelativeToDirectParent(Context())
? style_.indentation_spaces
: 0;
VisitIndentedSection(node, indent,
PartitionPolicyEnum::kFitOnLineElseExpand);
break;
}
case NodeEnum::kReferenceCallBase: {
// TODO(fangism): Create own section only for standalone calls
if (Context().DirectParentIs(NodeEnum::kStatement)) {
const auto& subnode = verible::SymbolCastToNode(
*ABSL_DIE_IF_NULL(node.children().back()));
if (subnode.MatchesTag(NodeEnum::kRandomizeMethodCallExtension) &&
subnode.children().back() != nullptr) {
// TODO(fangism): Handle constriants
VisitIndentedSection(node, 0, PartitionPolicyEnum::kAlwaysExpand);
} else if (subnode.MatchesTagAnyOf(
{NodeEnum::kMethodCallExtension,
NodeEnum::kRandomizeMethodCallExtension,
NodeEnum::kFunctionCall})) {
VisitIndentedSection(
node, 0, PartitionPolicyEnum::kAppendFittingSubPartitions);
} else {
TraverseChildren(node);
}
} else {
TraverseChildren(node);
}
break;
}
case NodeEnum::kRandomizeFunctionCall: {
// TODO(fangism): Create own section only for standalone calls
if (Context().DirectParentIs(NodeEnum::kStatement)) {
if (node.children().back() != nullptr) {
// TODO(fangism): Handle constriants
VisitIndentedSection(node, 0, PartitionPolicyEnum::kAlwaysExpand);
} else {
VisitIndentedSection(
node, 0, PartitionPolicyEnum::kAppendFittingSubPartitions);
}
} else {
TraverseChildren(node);
}
break;
}
case NodeEnum::kSystemTFCall: {
// TODO(fangism): Create own section only for standalone calls
if (Context().DirectParentIs(NodeEnum::kStatement)) {
VisitIndentedSection(node, 0,
PartitionPolicyEnum::kAppendFittingSubPartitions);
} else {
TraverseChildren(node);
}
break;
};
case NodeEnum::kMacroCall: {
// Single statements directly inside a controlled construct
// should be properly indented one level.
const int indent = ShouldIndentRelativeToDirectParent(Context())
? style_.indentation_spaces
: 0;
VisitIndentedSection(node, indent,
PartitionPolicyEnum::kAppendFittingSubPartitions);
break;
}
// The following constructs wish to use the partition policy of appending
// trailing subpartitions greedily as long as they fit, wrapping as
// needed.
case NodeEnum::kPreprocessorDefine:
case NodeEnum::kClassConstructorPrototype:
case NodeEnum::kTaskHeader:
case NodeEnum::kFunctionHeader:
case NodeEnum::kTaskPrototype:
case NodeEnum::kFunctionPrototype:
case NodeEnum::kDPIImportItem: {
VisitIndentedSection(node, 0,
PartitionPolicyEnum::kAppendFittingSubPartitions);
break;
}
// For the following constructs, always expand the view to subpartitions.
// Add a level of indentation.
case NodeEnum::kPackageItemList:
case NodeEnum::kInterfaceClassDeclaration:
case NodeEnum::kGenerateItemList:
case NodeEnum::kCasePatternItemList:
case NodeEnum::kStructUnionMemberList:
case NodeEnum::kEnumNameList:
case NodeEnum::kConstraintBlockItemList:
case NodeEnum::kConstraintExpressionList:
case NodeEnum::kDistributionItemList:
case NodeEnum::kBlockItemStatementList:
case NodeEnum::kFunctionItemList:
case NodeEnum::kAssertionVariableDeclarationList:
// The final sequence_expr of a sequence_declaration is same indentation
// level as the kAssertionVariableDeclarationList that precedes it.
case NodeEnum::kSequenceDeclarationFinalExpr:
case NodeEnum::kCoverageSpecOptionList:
case NodeEnum::kBinOptionList:
case NodeEnum::kCrossBodyItemList:
case NodeEnum::kUdpBody:
case NodeEnum::kUdpPortDeclaration:
case NodeEnum::kUdpSequenceEntry:
case NodeEnum::kUdpCombEntry:
case NodeEnum::kStatementList:
case NodeEnum::kSpecifyItemList:
case NodeEnum::kClockingItemList: {
// Do not further indent preprocessor clauses.
const int indent = suppress_indentation ? 0 : style_.indentation_spaces;
VisitIndentedSection(node, indent, PartitionPolicyEnum::kAlwaysExpand);
break;
}
// Add a level of grouping that is treated as wrapping.
case NodeEnum::kMacroFormalParameterList:
case NodeEnum::kMacroArgList:
case NodeEnum::kForSpec:
case NodeEnum::kModportSimplePortsDeclaration:
case NodeEnum::kModportTFPortsDeclaration:
case NodeEnum::kGateInstanceRegisterVariableList:
case NodeEnum::kVariableDeclarationAssignmentList:
case NodeEnum::kPortList: {
// Do not further indent preprocessor clauses.
const int indent = suppress_indentation ? 0 : style_.wrap_spaces;
VisitIndentedSection(node, indent,
PartitionPolicyEnum::kFitOnLineElseExpand);
break;
}
case NodeEnum::kOpenRangeList: {
if (Context().DirectParentIs(NodeEnum::kConcatenationExpression)) {
// Do not further indent preprocessor clauses.
const int indent = suppress_indentation ? 0 : style_.indentation_spaces;
VisitIndentedSection(node, indent,
PartitionPolicyEnum::kFitOnLineElseExpand);
break;
} else {
// Default handling
TraverseChildren(node);
break;
}
}
case NodeEnum::kCaseItemList:
case NodeEnum::kCaseInsideItemList:
case NodeEnum::kGenerateCaseItemList:
case NodeEnum::kClassItems:
case NodeEnum::kModuleItemList: {
const int indent = suppress_indentation ? 0 : style_.indentation_spaces;
VisitIndentedSection(node, indent,
PartitionPolicyEnum::kTabularAlignment);
break;
}
// module instantiations (which look like data declarations) want to
// expand one parameter/port per line.
case NodeEnum::kActualParameterByNameList: {
const int indent =
suppress_indentation ? 0 : style_.NamedParameterIndentation();
VisitIndentedSection(node, indent,
PartitionPolicyEnum::kTabularAlignment);
break;
}
case NodeEnum::kPortActualList: // covers named and positional ports
{
const int indent =
suppress_indentation ? 0 : style_.NamedPortIndentation();
const auto policy = Context().IsInside(NodeEnum::kDataDeclaration)
? PartitionPolicyEnum::kTabularAlignment
: PartitionPolicyEnum::kFitOnLineElseExpand;
VisitIndentedSection(node, indent, policy);
break;
}
case NodeEnum::kPortDeclarationList: {
// Do not further indent preprocessor clauses.
const int indent =
suppress_indentation ? 0 : style_.PortDeclarationsIndentation();
if (Context().IsInside(NodeEnum::kClassHeader) ||
// kModuleHeader covers interfaces and programs
Context().IsInside(NodeEnum::kModuleHeader)) {
VisitIndentedSection(node, indent,
PartitionPolicyEnum::kTabularAlignment);
} else {
VisitIndentedSection(node, indent,
PartitionPolicyEnum::kFitOnLineElseExpand);
}
break;
}
case NodeEnum::kFormalParameterList: {
// Do not further indent preprocessor clauses.
const int indent =
suppress_indentation ? 0 : style_.FormalParametersIndentation();
if (Context().IsInside(NodeEnum::kClassHeader) ||
// kModuleHeader covers interfaces and programs
Context().IsInside(NodeEnum::kModuleHeader)) {
VisitIndentedSection(node, indent,
PartitionPolicyEnum::kTabularAlignment);
} else {
VisitIndentedSection(node, indent,
PartitionPolicyEnum::kFitOnLineElseExpand);
}
break;
}
// Since NodeEnum::kActualParameterPositionalList consists of a
// comma-separated list of expressions, we let those default to appending
// to current token partition, and let the line-wrap-optimizer handle the
// enclosing construct, such as a parameterized type like "foo #(1, 2)".
// Special cases:
case NodeEnum::kPropertySpec: {
if (Context().IsInside(NodeEnum::kPropertyDeclaration)) {
// indent the same level as kAssertionVariableDeclarationList
// which (optionally) appears before the final property_spec.
VisitIndentedSection(node, style_.indentation_spaces,
PartitionPolicyEnum::kAlwaysExpand);
} else if (Context().DirectParentIs(NodeEnum::kMacroArgList)) {
VisitIndentedSection(node, 0,
PartitionPolicyEnum::kFitOnLineElseExpand);
} else {
TraverseChildren(node);
}
break;
}
// kReference can be found in kIdentifierList
// and kExpression can be found in kArgumentList & kMacroArgList
case NodeEnum::kReference:
case NodeEnum::kExpression: {
if (Context().DirectParentIsOneOf({NodeEnum::kMacroArgList,
NodeEnum::kArgumentList,
NodeEnum::kIdentifierList})) {
// original un-lexed macro argument was successfully expanded
VisitNewUnwrappedLine(node);
} else if (Context().DirectParentIs(NodeEnum::kOpenRangeList) &&
Context().IsInside(NodeEnum::kConcatenationExpression)) {
VisitIndentedSection(node, 0,
PartitionPolicyEnum::kFitOnLineElseExpand);
} else {
TraverseChildren(node);
}
break;
}
default: {
TraverseChildren(node);
// TODO(fangism): Eventually replace this case with:
// VisitIndentedSection(node, 0,
// PartitionPolicyEnum::kFitOnLineElseExpand);
}
}
}
static bool PartitionStartsWithSemicolon(const TokenPartitionTree& partition) {
const auto& uwline = partition.RightmostDescendant()->Value();
return !uwline.IsEmpty() && uwline.TokensRange().front().TokenEnum() == ';';
}
static bool PartitionIsCloseParenSemi(const TokenPartitionTree& partition) {
const auto ftokens = partition.Value().TokensRange();
if (ftokens.size() < 2) return false;
if (ftokens.front().TokenEnum() != ')') return false;
return ftokens.back().TokenEnum() == ';';
}
static bool PartitionStartsWithCloseParen(const TokenPartitionTree& partition) {
const auto ftokens = partition.Value().TokensRange();
if (ftokens.empty()) return false;
const auto token_enum = ftokens.front().TokenEnum();
return ((token_enum == ')') || (token_enum == MacroCallCloseToEndLine));
}
static bool PartitionEndsWithOpenParen(const TokenPartitionTree& partition) {
const auto ftokens = partition.Value().TokensRange();
if (ftokens.empty()) return false;
const auto token_enum = ftokens.back().TokenEnum();
return token_enum == '(';
}
static bool PartitionIsCloseBrace(const TokenPartitionTree& partition) {
const auto ftokens = partition.Value().TokensRange();
if (ftokens.size() != 1) return false;
const auto token_enum = ftokens.front().TokenEnum();
return token_enum == '}';
}
static void AttachTrailingSemicolonToPreviousPartition(
TokenPartitionTree* partition) {
// Attach the trailing ';' partition to the previous sibling leaf.
// VisitIndentedSection() finished by starting a new partition,
// so we need to back-track to the previous sibling partition.
// In some cases where macros are involved, there may not necessarily
// be a semicolon where one is grammatically expected.
// In those cases, do nothing.
if (PartitionStartsWithSemicolon(*partition)) {
verible::MergeLeafIntoPreviousLeaf(partition->RightmostDescendant());
VLOG(4) << "after moving semicolon:\n" << *partition;
}
}
static void ReshapeIfClause(const SyntaxTreeNode& node,
TokenPartitionTree* partition_ptr) {
auto& partition = *partition_ptr;
const SyntaxTreeNode* body = GetAnyControlStatementBody(node);
if (body == nullptr || !NodeIsBeginEndBlock(*body)) {
VLOG(4) << "if-body was not a begin-end block.";
// If body is a null statement, attach it to the previous partition.
AttachTrailingSemicolonToPreviousPartition(partition_ptr);
return;
}
// Then fuse the 'begin' partition with the preceding 'if (...)'
auto& if_body_partition = partition.Children().back();
auto& begin_partition = if_body_partition.Children().front();
verible::MergeLeafIntoPreviousLeaf(&begin_partition);
partition.Value().SetPartitionPolicy(
if_body_partition.Value().PartitionPolicy());
// if seq_block body was empty, that leaves only 'end', so hoist.
// if if-header was flat, hoist that too.
partition.FlattenOneChild(if_body_partition.BirthRank());
}
static void ReshapeElseClause(const SyntaxTreeNode& node,
TokenPartitionTree* partition_ptr) {
auto& partition = *partition_ptr;
const SyntaxTreeNode& else_body_subnode =
*ABSL_DIE_IF_NULL(GetAnyControlStatementBody(node));
if (!NodeIsConditionalOrBlock(else_body_subnode)) {
VLOG(4) << "else-body was neither a begin-end block nor if-conditional.";
// If body is a null statement, attach it to the previous partition.
AttachTrailingSemicolonToPreviousPartition(partition_ptr);
return;
}
// Then fuse 'else' and 'begin' partitions together
// or fuse the 'else' and 'if' (header) partitions together
auto& else_partition = partition.Children().front();
verible::MergeLeafIntoNextLeaf(&else_partition);
}
static void HoistOnlyChildPartition(TokenPartitionTree* partition) {
const auto* origin = partition->Value().Origin();
if (partition->HoistOnlyChild()) {
VLOG(4) << "reshape: hoisted, using child partition policy, parent origin";
// Preserve source origin
partition->Value().SetOrigin(origin);
}
}
static void PushEndIntoElsePartition(TokenPartitionTree* partition_ptr) {
// Then combine 'end' with the following 'else' ...
// Do not flatten, so that if- and else- clauses can make formatting
// decisions independently from each other.
auto& partition = *partition_ptr;
auto& if_clause_partition = partition.Children().front();
auto* end_partition = if_clause_partition.RightmostDescendant();
auto* end_parent = verible::MergeLeafIntoNextLeaf(end_partition);
// if moving leaf results in any singleton partitions, hoist.
if (end_parent != nullptr) {
HoistOnlyChildPartition(end_parent);
}
}
static void MergeEndElseWithoutLabel(const SyntaxTreeNode& conditional,
TokenPartitionTree* partition_ptr) {
auto& partition = *partition_ptr;
// Handle merging of 'else' partition with (possibly)
// a previous 'end' partition.
// Do not flatten, so that if- and else- clauses can make formatting
// decisions independently from each other.
const auto* if_body_subnode =
GetAnyControlStatementBody(*GetAnyConditionalIfClause(conditional));
if (if_body_subnode == nullptr || !NodeIsBeginEndBlock(*if_body_subnode)) {
VLOG(4) << "if-body was not begin-end block";
return;
}
VLOG(4) << "if body was a begin-end block";
const auto* end_label = GetEndLabel(GetBlockEnd(*if_body_subnode));
if (end_label != nullptr) {
VLOG(4) << "'end' came with label, no merge";
return;
}
VLOG(4) << "No 'end' label, merge 'end' and 'else...' partitions";
PushEndIntoElsePartition(&partition);
}
static void FlattenElseIfElse(const SyntaxTreeNode& else_clause,
TokenPartitionTree* partition_ptr) {
// Keep chained else-if-else conditionals in a flat structure.
auto& partition = *partition_ptr;
const auto& else_body_subnode = *GetAnyControlStatementBody(else_clause);
if (NodeIsConditionalConstruct(else_body_subnode) &&
GetAnyConditionalElseClause(else_body_subnode) != nullptr) {
partition.FlattenOneChild(partition.Children().size() - 1);
}
}
static void ReshapeConditionalConstruct(const SyntaxTreeNode& conditional,
TokenPartitionTree* partition_ptr) {
const auto* else_clause = GetAnyConditionalElseClause(conditional);
if (else_clause == nullptr) {
VLOG(4) << "there was no else clause";
return;
}
VLOG(4) << "there was an else clause";
MergeEndElseWithoutLabel(conditional, partition_ptr);
FlattenElseIfElse(*else_clause, partition_ptr);
}
static void IndentBetweenUVMBeginEndMacros(TokenPartitionTree* partition_ptr,
int indentation_spaces) {
auto& partition = *partition_ptr;
// Indent elements between UVM begin/end macro calls
unsigned int uvm_level = 1;
std::vector<TokenPartitionTree*> uvm_range;
// Search backwards for matching _begin.
for (auto* itr = partition.PreviousSibling(); itr;
itr = itr->PreviousSibling()) {
VLOG(4) << "Scanning previous sibling:\n" << *itr;
const auto macroId =
itr->LeftmostDescendant()->Value().TokensRange().front().Text();
VLOG(4) << "macro id: " << macroId;
// Indent only uvm macros
if (!absl::StartsWith(macroId, "`uvm_")) {
continue;
}
// Count uvm indentation level
if (absl::EndsWith(macroId, "_end")) {
uvm_level++;
} else if (absl::EndsWith(macroId, "_begin")) {
uvm_level--;
}
// Break before indenting matching _begin macro
if (uvm_level == 0) {
break;
}
uvm_range.push_back(itr);
}
// Found matching _begin-_end macros
if ((uvm_level == 0) && !uvm_range.empty()) {
VLOG(4) << "Found matching uvm-begin/end macros";
for (auto* itr : uvm_range) {
// Indent uvm macros inside `uvm.*begin - `uvm.*end
verible::AdjustIndentationRelative(itr, +indentation_spaces);
}
}
}
// This phase is strictly concerned with reshaping token partitions,
// and occurs on the return path of partition tree construction.
void TreeUnwrapper::ReshapeTokenPartitions(
const SyntaxTreeNode& node, const verible::BasicFormatStyle& style,
TokenPartitionTree* recent_partition) {
const auto tag = static_cast<NodeEnum>(node.Tag().tag);
VLOG(3) << __FUNCTION__ << " node: " << tag;
auto& partition = *recent_partition;
VLOG(4) << "before reshaping " << tag << ":\n" << partition;
// Tips, when making reshaping decisions based on subtrees:
// * Manipulate the partition *incrementally* and as close to the
// corresponding node enum case that produced it as possible.
// In otherwords, minimize the depth of examination needed to
// make a reshaping decision. If there is insufficient information to
// make a decision, that would be a good reason to defer to a parent node.
// * Prefer to examine the SyntaxTreeNode& node instead of the token
// partition tree because the token partition tree may include
// EOL-comments as nodes. Use/create CST functions to abstract away
// the precise structural details.
// * Altering the indentation and partition policy itself is allowed,
// even though they were set during the
// SetIndentationsAndCreatePartitions() phase.
// post-creation token partition adjustments
switch (tag) {
// Note: this is also being applied to variable declaration lists,
// which may want different handling than instantiations.
case NodeEnum::kDataDeclaration: {
AttachTrailingSemicolonToPreviousPartition(&partition);
auto& data_declaration_partition = partition;
auto& children = data_declaration_partition.Children();
CHECK(!children.empty());
// TODO(fangism): fuse qualifiers (if any) with type partition
// The instances/variable declaration list is always in last position.
auto& instance_list_partition = children.back();
if ( // TODO(fangism): get type and qualifiers from declaration node,
// and check whether type node is implicit, using CST functions.
instance_list_partition.BirthRank() == 0) {
VLOG(4) << "No qualifiers and type is implicit";
// This means there are no qualifiers and type was implicit,
// so no partition precedes this.
// Use the declaration (parent) level's indentation.
verible::AdjustIndentationAbsolute(
&instance_list_partition,
data_declaration_partition.Value().IndentationSpaces());
HoistOnlyChildPartition(&instance_list_partition);
} else if (GetInstanceListFromDataDeclaration(node).children().size() ==
1) {
VLOG(4) << "Instance list has only one child, singleton.";
// Undo the indentation of the only instance in the hoisted subtree.
verible::AdjustIndentationRelative(&instance_list_partition,
-style.wrap_spaces);
VLOG(4) << "After un-indenting, instance list:\n"
<< instance_list_partition;
// Reshape type-instance partitions.
auto* instance_type_partition =
ABSL_DIE_IF_NULL(children.back().PreviousSibling());
VLOG(4) << "instance type:\n" << *instance_type_partition;
if (instance_type_partition == &children.front()) {
// data_declaration_partition now consists of exactly:
// instance_type_partition, instance_list_partition (single
// instance)
// Flatten these (which will invalidate their references).
std::vector<size_t> offsets;
data_declaration_partition.FlattenOnlyChildrenWithChildren(&offsets);
// This position in the flattened result will be merged.
const size_t fuse_position = offsets.back() - 1;
// Join the rightmost of instance_type_partition with the leftmost of
// instance_list_partition. Keep the type's indentation.
// This can yield an intermediate partition that contains:
// ") instance_name (".
MergeConsecutiveSiblings(&data_declaration_partition, fuse_position);
} else {
// There is a qualifier before instance_type_partition.
data_declaration_partition.FlattenOnlyChildrenWithChildren();
}
} else {
VLOG(4) << "None of the special cases apply.";
}
break;
}
// For these cases, the leading sub-partition is merged into its next
// relative. This is useful for constructs that are repeatedly prefixed
// with attributes, but otherwise maintain the same subpartition shape.
case NodeEnum::kForwardDeclaration:
// partition consists of (example):
// [pure virtual]
// [task ... ] (task header/prototype)
// Push the qualifiers down.
case NodeEnum::kDPIImportItem:
// partition consists of (example):
// [import "DPI-C"]
// [function ... ] (task header/prototype)
// Push the "import..." down.
{
verible::MergeLeafIntoNextLeaf(&partition.Children().front());
break;
}
case NodeEnum::kBindDirective: {
AttachTrailingSemicolonToPreviousPartition(&partition);
// Take advantage here that preceding data declaration partition
// was already shaped.
auto& target_instance_partition = partition;
auto& children = target_instance_partition.Children();
// Attach ')' to the instance name
verible::MergeLeafIntoNextLeaf(children.back().PreviousSibling());
verible::AdjustIndentationRelative(&children.back(), -style.wrap_spaces);
break;
}
case NodeEnum::kStatement: {
// This handles cases like macro-calls followed by a semicolon.
AttachTrailingSemicolonToPreviousPartition(&partition);
break;
}
case NodeEnum::kModuleHeader: {
// Allow empty ports to appear as "();"
if (partition.Children().size() >= 2) {
auto& last = partition.Children().back();
auto& last_prev = *ABSL_DIE_IF_NULL(last.PreviousSibling());
if (PartitionStartsWithCloseParen(last) &&
PartitionEndsWithOpenParen(last_prev)) {
verible::MergeLeafIntoPreviousLeaf(&last);
}
}
// If there were any parameters or ports at all, expand.
// TODO(fangism): This should be done by inspecting the CST node,
// instead of the partition structure.
if (partition.Children().size() > 2) {
partition.Value().SetPartitionPolicy(
PartitionPolicyEnum::kAlwaysExpand);
}
break;
}
case NodeEnum::kClassHeader: {
// Allow empty parameters to appear as "#();"
if (partition.Children().size() >= 2) {
auto& last = partition.Children().back();
auto& last_prev = *ABSL_DIE_IF_NULL(last.PreviousSibling());
if (PartitionStartsWithCloseParen(last) &&
PartitionEndsWithOpenParen(last_prev)) {
verible::MergeLeafIntoPreviousLeaf(&last);
}
}
break;
}
// The following partitions may end up with a trailing ");" subpartition
// and want to attach it to the item that preceded it.
case NodeEnum::kClassConstructorPrototype:
case NodeEnum::kFunctionHeader:
case NodeEnum::kFunctionPrototype:
case NodeEnum::kTaskHeader:
case NodeEnum::kTaskPrototype: {
auto& last = *ABSL_DIE_IF_NULL(partition.RightmostDescendant());
if (PartitionIsCloseParenSemi(last)) {
verible::MergeLeafIntoPreviousLeaf(&last);
}
break;
}
case NodeEnum::kReferenceCallBase: {
const auto& subnode = verible::SymbolCastToNode(*node.children().back());
if (subnode.MatchesTagAnyOf({NodeEnum::kMethodCallExtension,
NodeEnum::kFunctionCall,
NodeEnum::kRandomizeMethodCallExtension})) {
if (partition.Value().PartitionPolicy() ==
PartitionPolicyEnum::kAppendFittingSubPartitions) {
auto& last = *ABSL_DIE_IF_NULL(partition.RightmostDescendant());
if (PartitionStartsWithCloseParen(last) ||
PartitionStartsWithSemicolon(last)) {
verible::MergeLeafIntoPreviousLeaf(&last);
}
}
}
break;
}
case NodeEnum::kRandomizeFunctionCall:
case NodeEnum::kSystemTFCall: {
if (partition.Value().PartitionPolicy() ==
PartitionPolicyEnum::kAppendFittingSubPartitions) {
auto& last = *ABSL_DIE_IF_NULL(partition.RightmostDescendant());
if (PartitionStartsWithCloseParen(last) ||
PartitionStartsWithSemicolon(last)) {
verible::MergeLeafIntoPreviousLeaf(&last);
}
}
break;
}
case NodeEnum::kGenerateIfHeader:
case NodeEnum::kIfHeader: {
// Fix indentation in case of e.g. function calls inside if headers
// TODO(fangism): This should be done smarter (using CST) or removed
// after better handling of function calls inside expressions
// e.g. kBinaryExpression, kUnaryPrefixExpression...
if (partition.Children().size() > 1) {
auto& if_header_partition = partition.Children()[0];
const auto original_indentation =
if_header_partition.Value().IndentationSpaces();
// Adjust indentation recursively
verible::AdjustIndentationRelative(&partition, style.wrap_spaces);
// Restore original indentation in first partition
partition.Value().SetIndentationSpaces(original_indentation);
if_header_partition.Value().SetIndentationSpaces(original_indentation);
}
break;
}
// The following cases handle reshaping around if/else/begin/end.
case NodeEnum::kAssertionClause:
case NodeEnum::kAssumeClause:
case NodeEnum::kCoverStatement:
case NodeEnum::kWaitStatement:
case NodeEnum::kAssertPropertyClause:
case NodeEnum::kAssumePropertyClause:
case NodeEnum::kExpectPropertyClause:
case NodeEnum::kCoverPropertyStatement:
case NodeEnum::kCoverSequenceStatement:
case NodeEnum::kIfClause:
case NodeEnum::kGenerateIfClause: {
ReshapeIfClause(node, &partition);
break;
}
case NodeEnum::kGenerateElseClause:
case NodeEnum::kElseClause: {
ReshapeElseClause(node, &partition);
break;
}
case NodeEnum::kAssertionStatement:
// Contains a kAssertionClause and possibly a kElseClause
case NodeEnum::kAssumeStatement:
// Contains a kAssumeClause and possibly a kElseClause
case NodeEnum::kAssertPropertyStatement:
// Contains a kAssertPropertyClause and possibly a kElseClause
case NodeEnum::kAssumePropertyStatement:
// Contains a kAssumePropertyClause and possibly a kElseClause
case NodeEnum::kExpectPropertyStatement:
// Contains a kExpectPropertyClause and possibly a kElseClause
case NodeEnum::kConditionalStatement:
// Contains a kIfClause and possibly a kElseClause
case NodeEnum::kConditionalGenerateConstruct:
// Contains a kGenerateIfClause and possibly a kGenerateElseClause
{
ReshapeConditionalConstruct(node, &partition);
break;
}
case NodeEnum::kPreprocessorDefine: {
auto& last = *ABSL_DIE_IF_NULL(partition.RightmostDescendant());
// TODO(fangism): why does test fail without this clause?
if (PartitionStartsWithCloseParen(last)) {
verible::MergeLeafIntoPreviousLeaf(&last);
}
break;
}
case NodeEnum::kMacroCall: {
// If there are no call args, join the '(' and ')' together.
if (MacroCallArgsIsEmpty(GetMacroCallArgs(node))) {
// FIXME HERE: flattening wrong place! Should merge instead.
partition.FlattenOnce();
VLOG(4) << "NODE: kMacroCall (flattened):\n" << partition;
} else {
// Merge closing parenthesis into last argument partition
// Test for ')' and MacroCallCloseToEndLine because macros
// use its own token 'MacroCallCloseToEndLine'
auto& last = *ABSL_DIE_IF_NULL(partition.RightmostDescendant());
if (PartitionStartsWithCloseParen(last) ||
PartitionIsCloseParenSemi(last)) {
verible::MergeLeafIntoPreviousLeaf(&last);
}
}
break;
}
case NodeEnum::kConstraintDeclaration: {
// TODO(fangism): kConstraintSet should be handled similarly with {}
if (partition.Children().size() == 2) {
auto& last = *ABSL_DIE_IF_NULL(partition.RightmostDescendant());
if (PartitionIsCloseBrace(last)) {
verible::MergeLeafIntoPreviousLeaf(&last);
}
}
break;
}
// This group of cases is temporary: simplify these during the
// rewrite/refactor of this function.
// See search-anchor: STATEMENT_TYPES
case NodeEnum::kNetVariableAssignment: // e.g. x=y
case NodeEnum::kBlockingAssignmentStatement: // id=expr
case NodeEnum::kNonblockingAssignmentStatement: // dest <= src;
case NodeEnum::kAssignModifyStatement: // id+=expr
{
VLOG(4) << "before moving semicolon:\n" << partition;
AttachTrailingSemicolonToPreviousPartition(&partition);
// RHS may have been further partitioned, e.g. a macro call.
auto& children = partition.Children();
if (children.size() == 2 &&
children.front().Children().empty() /* left side */) {
verible::MergeLeafIntoNextLeaf(&children.front());
VLOG(4) << "after merge leaf (left-into-right):\n" << partition;
}
break;
}
case NodeEnum::kProceduralTimingControlStatement: {
std::vector<size_t> offsets;
partition.FlattenOnlyChildrenWithChildren(&offsets);
VLOG(4) << "before moving semicolon:\n" << partition;
AttachTrailingSemicolonToPreviousPartition(&partition);
// Check body, for kSeqBlock, merge 'begin' with previous sibling
if (NodeIsBeginEndBlock(GetProceduralTimingControlStatementBody(node))) {
verible::MergeConsecutiveSiblings(&partition, offsets[1] - 1);
VLOG(4) << "after merge siblings:\n" << partition;
}
break;
}
case NodeEnum::kProceduralContinuousAssignmentStatement:
case NodeEnum::kProceduralContinuousForceStatement:
case NodeEnum::kContinuousAssignmentStatement: { // e.g. assign a=0, b=2;
// TODO(fangism): group into above similar assignment statement cases?
// Cannot easily move now due to multiple-assignments.
partition.FlattenOnlyChildrenWithChildren();
VLOG(4) << "after flatten:\n" << partition;
AttachTrailingSemicolonToPreviousPartition(&partition);
// Merge the 'assign' keyword with the (first) x=y assignment.
// TODO(fangism): reshape for multiple assignments.
verible::MergeConsecutiveSiblings(&partition, 0);
VLOG(4) << "after merging 'assign':\n" << partition;
break;
}
case NodeEnum::kForSpec: {
// This applies to loop statements and loop generate constructs.
// There are two 'partitions' with ';'.
// Merge those with their predecessor sibling partitions.
const auto& children = partition.Children();
const auto iter1 = std::find_if(children.begin(), children.end(),
PartitionStartsWithSemicolon);
CHECK(iter1 != children.end());
const auto iter2 =
std::find_if(iter1 + 1, children.end(), PartitionStartsWithSemicolon);
CHECK(iter2 != children.end());
const int dist1 = std::distance(children.begin(), iter1);
const int dist2 = std::distance(children.begin(), iter2);
VLOG(4) << "kForSpec got ';' at " << dist1 << " and " << dist2;
// Merge from back-to-front to keep indices valid.
if (dist2 > 0 && (dist2 - dist1 > 1)) {
verible::MergeConsecutiveSiblings(&partition, dist2 - 1);
}
if (dist1 > 0) {
verible::MergeConsecutiveSiblings(&partition, dist1 - 1);
}
break;
}
case NodeEnum::kLoopGenerateConstruct:
case NodeEnum::kForLoopStatement:
case NodeEnum::kForeverLoopStatement:
case NodeEnum::kRepeatLoopStatement:
case NodeEnum::kWhileLoopStatement:
case NodeEnum::kForeachLoopStatement:
case NodeEnum::kLabeledStatement:
case NodeEnum::kCaseItem:
case NodeEnum::kDefaultItem:
case NodeEnum::kCaseInsideItem:
case NodeEnum::kCasePatternItem:
case NodeEnum::kGenerateCaseItem:
// case NodeEnum::kAlwaysStatement: // handled differently below
// TODO(fangism): always,initial,final should be handled the same way
case NodeEnum::kInitialStatement:
case NodeEnum::kFinalStatement: {
// In these cases, merge the 'begin' partition of the statement block
// with the preceding keyword or header partition.
if (NodeIsBeginEndBlock(
verible::SymbolCastToNode(*node.children().back()))) {
auto& seq_block_partition = partition.Children().back();
VLOG(4) << "block partition: " << seq_block_partition;
auto& begin_partition = *seq_block_partition.LeftmostDescendant();
VLOG(4) << "begin partition: " << begin_partition;
CHECK(begin_partition.Children().empty());
verible::MergeLeafIntoPreviousLeaf(&begin_partition);
VLOG(4) << "after merging 'begin' to predecessor:\n" << partition;
// Flatten only the statement block so that the control partition
// can retain its own partition policy.
partition.FlattenOneChild(seq_block_partition.BirthRank());
}
break;
}
case NodeEnum::kDoWhileLoopStatement: {
if (NodeIsBeginEndBlock(GetDoWhileStatementBody(node))) {
// between do... and while (...);
auto& seq_block_partition = partition.Children()[1];
// merge "do" <- "begin"
auto& begin_partition = *seq_block_partition.LeftmostDescendant();
verible::MergeLeafIntoPreviousLeaf(&begin_partition);
// merge "end" -> "while"
auto& end_partition = *seq_block_partition.RightmostDescendant();
verible::MergeLeafIntoNextLeaf(&end_partition);
// Flatten only the statement block so that the control partition
// can retain its own partition policy.
partition.FlattenOneChild(seq_block_partition.BirthRank());
}
break;
}
case NodeEnum::kAlwaysStatement: {
if (GetSubtreeAsNode(node, tag, node.children().size() - 1)
.MatchesTagAnyOf({NodeEnum::kProceduralTimingControlStatement,
NodeEnum::kSeqBlock})) {
// Merge 'always' keyword with next sibling, and adjust subtree indent.
verible::MergeLeafIntoNextLeaf(&partition.Children().front());
verible::AdjustIndentationAbsolute(
&partition.Children().front(),
partition.Value().IndentationSpaces());
VLOG(4) << "after merging 'always':\n" << partition;
}
break;
}
case NodeEnum::kMacroGenericItem: {
const auto& token = GetMacroGenericItemId(node);
if (absl::StartsWith(token.text(), "`uvm_") &&
absl::EndsWith(token.text(), "_end")) {
VLOG(4) << "Found `uvm_*_end macro call";
IndentBetweenUVMBeginEndMacros(&partition, style.indentation_spaces);
}
break;
}
default:
break;
}
// In the majority of cases, automatically hoist singletons.
// A few node types, however, will wish to delay the hoisting change,
// so that the parent node can make reshaping decisions based on the
// child node's original unhoisted form.
// Exceptions:
if (!node.MatchesTagAnyOf({
//
NodeEnum::kGateInstanceRegisterVariableList // parent:
// kDataDeclaration)
})) {
HoistOnlyChildPartition(&partition);
}
VLOG(4) << "after reshaping " << tag << ":\n" << partition;
VLOG(3) << "end of " << __FUNCTION__ << " node: " << tag;
}
// Visitor to determine which leaf enum function to call
void TreeUnwrapper::Visit(const verible::SyntaxTreeLeaf& leaf) {
VLOG(3) << __FUNCTION__ << " leaf: " << VerboseToken(leaf.get());
const verilog_tokentype tag = verilog_tokentype(leaf.Tag().tag);
// Catch up on any non-whitespace tokens that fall between the syntax tree
// leaves, such as comments and attributes, stopping at the current leaf.
CatchUpToCurrentLeaf(leaf.get());
VLOG(4) << "Visit leaf: after CatchUp";
// Possibly start new partition (without advancing token iterator).
UpdateInterLeafScanner(tag);
// Sanity check that NextUnfilteredToken() is aligned to the current leaf.
CHECK_EQ(NextUnfilteredToken()->text().begin(), leaf.get().text().begin());
// Start a new partition in the following cases.
// In most other cases, do nothing.
if (IsPreprocessorControlFlow(tag)) {
VLOG(4) << "handling preprocessor control flow token";
StartNewUnwrappedLine(PartitionPolicyEnum::kFitOnLineElseExpand, &leaf);
CurrentUnwrappedLine().SetIndentationSpaces(0);
} else if (IsEndKeyword(tag)) {
VLOG(4) << "handling end* keyword";
StartNewUnwrappedLine(PartitionPolicyEnum::kAlwaysExpand, &leaf);
}
auto& partition = *ABSL_DIE_IF_NULL(CurrentTokenPartition());
VLOG(4) << "before adding token " << VerboseToken(leaf.get()) << ":\n"
<< partition;
// Advances NextUnfilteredToken(), and extends CurrentUnwrappedLine().
// Should be equivalent to AdvanceNextUnfilteredToken().
AddTokenToCurrentUnwrappedLine();
// Look-ahead to any trailing comments that are associated with this leaf,
// up to a newline.
LookAheadBeyondCurrentLeaf();
VLOG(4) << "before reshaping " << VerboseToken(leaf.get()) << ":\n"
<< partition;
// Post-token-handling token partition adjustments:
switch (leaf.Tag().tag) {
case ',': {
// In many cases (in particular lists), we want to attach delimiters like
// ',' to the item that preceded it (on its rightmost leaf).
// This adjustment is necessary for lists of element types that beget
// their own indented sections.
//
// TODO(fangism): See also AttachTrailingSemicolonToPreviousPartition().
// There should be one consistent way of attaching trailing delimiters.
// Pick one, even if both work.
if (current_context_.DirectParentIsOneOf({
// NodeEnum:xxxx // due to element:
NodeEnum::kMacroArgList, // MacroArg
NodeEnum::kFormalParameterList, // kParamDeclaration
NodeEnum::kEnumNameList, // kEnumName
NodeEnum::kActualParameterByNameList, // kParamByName
NodeEnum::kPortDeclarationList, // kPort, kPortDeclaration
NodeEnum::kPortActualList, // kActualNamedPort,
// kActualPositionalPort
NodeEnum::kGateInstanceRegisterVariableList, // kGateInstance,
// kRegisterVariable
NodeEnum::kVariableDeclarationAssignmentList // due to element:
// kVariableDeclarationAssignment
}) ||
(current_context_.DirectParentIs(NodeEnum::kOpenRangeList) &&
current_context_.IsInside(NodeEnum::kConcatenationExpression))) {
MergeLastTwoPartitions();
} else if (CurrentUnwrappedLine().Size() == 1) {
// Partition would begin with a comma,
// instead add this token to previous partition
MergeLastTwoPartitions();
}
break;
}
case verilog_tokentype::SemicolonEndOfAssertionVariableDeclarations: {
// is a ';'
if (current_context_.DirectParentIs(
NodeEnum::kAssertionVariableDeclarationList)) {
MergeLastTwoPartitions();
}
break;
}
case PP_else: {
// Do not allow non-comment tokens on the same line as `else
// (comments were handled above)
StartNewUnwrappedLine(PartitionPolicyEnum::kFitOnLineElseExpand, &leaf);
break;
}
default:
break;
}
VLOG(3) << "end of " << __FUNCTION__ << " leaf: " << VerboseToken(leaf.get());
}
// Specialized node visitors
void TreeUnwrapper::VisitNewUnwrappedLine(const SyntaxTreeNode& node) {
StartNewUnwrappedLine(PartitionPolicyEnum::kFitOnLineElseExpand, &node);
TraverseChildren(node);
}
void TreeUnwrapper::VisitNewUnindentedUnwrappedLine(
const SyntaxTreeNode& node) {
StartNewUnwrappedLine(PartitionPolicyEnum::kFitOnLineElseExpand, &node);
// Force the current line to be unindented without losing track of where
// the current indentation level is for children.
CurrentUnwrappedLine().SetIndentationSpaces(0);
TraverseChildren(node);
}
} // namespace formatter
} // namespace verilog