blob: bc573529df82ed4a6e3c02e4c7decaba2fc8c90b [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 <cstddef>
#include <functional>
#include <initializer_list>
#include <iterator>
#include <ostream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/base/attributes.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.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/strings/display_utils.h"
#include "common/strings/range.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/iterator_range.h"
#include "common/util/logging.h"
#include "common/util/tree_operations.h"
#include "verilog/CST/declaration.h"
#include "verilog/CST/macro.h"
#include "verilog/CST/statement.h"
#include "verilog/CST/verilog_nonterminals.h"
#include "verilog/formatting/format_style.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::iterator_range;
using ::verible::NodeTag;
using ::verible::PartitionPolicyEnum;
using ::verible::PreFormatToken;
using ::verible::SpacingOptions;
using ::verible::SymbolTag;
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 const SyntaxTreeNode& GetBlockEnd(const SyntaxTreeNode& block) {
CHECK(NodeIsBeginEndBlock(block));
return verible::SymbolCastToNode(*block.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::kOpen;
break;
case FormatTokenType::close_group:
format_token.balancing = verible::GroupBalancing::kClose;
break;
default:
format_token.balancing = verible::GroupBalancing::kNone;
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 verible::EnumNameMap<TokenScannerState>&
TokenScannerStateStrings() {
static const verible::EnumNameMap<TokenScannerState>
kTokenScannerStateStringMap({
{"kStart", TokenScannerState::kStart},
{"kHaveNewline", TokenScannerState::kHaveNewline},
{"kNewPartition", TokenScannerState::kNewPartition},
{"kEndWithNewline", TokenScannerState::kEndWithNewline},
{"kEndNoNewline", TokenScannerState::kEndNoNewline},
});
return kTokenScannerStateStringMap;
}
// Conventional stream printer (declared in header providing enum).
std::ostream& operator<<(std::ostream& stream, TokenScannerState p) {
return TokenScannerStateStrings().Unparse(p, stream);
}
// 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:
using State = TokenScannerState;
// 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() = default;
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_};
}
static void VerilogOriginPrinter(
std::ostream& stream, const verible::TokenInfo::Context& token_context,
const verible::Symbol* symbol) {
CHECK_NOTNULL(symbol);
if (symbol->Kind() == verible::SymbolKind::kNode) {
stream << NodeEnum(symbol->Tag().tag);
} else {
stream << "#" << verilog_symbol_name(verilog_tokentype(symbol->Tag().tag));
}
const auto* left = verible::GetLeftmostLeaf(*symbol);
const auto* right = verible::GetRightmostLeaf(*symbol);
if (left && right) {
const int start = left->get().left(token_context.base);
const int end = right->get().right(token_context.base);
stream << "@" << start << "-" << end;
}
stream << " ";
verible::UnwrappedLine::DefaultOriginPrinter(stream, symbol);
}
verible::TokenPartitionTreePrinter TreeUnwrapper::VerilogPartitionPrinter(
const verible::TokenPartitionTree& partition) const {
return verible::TokenPartitionTreePrinter(
partition, false,
[this](std::ostream& stream, const verible::Symbol* symbol) {
VerilogOriginPrinter(stream, this->token_context_, symbol);
});
}
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 (auto next_tok = verilog_tokentype(NextUnfilteredToken()->token_enum());
IsComment(next_tok) || next_tok == verilog_tokentype::TK_LINE_CONT) {
// 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:
case NodeEnum::kModuleItemList:
case NodeEnum::kGenerateItemList:
case NodeEnum::kClassItems:
case NodeEnum::kEnumNameList:
case NodeEnum::kDescriptionList: // top-level item comments
case NodeEnum::kStatementList:
case NodeEnum::kPackageItemList:
case NodeEnum::kSpecifyItemList:
case NodeEnum::kBlockItemStatementList:
case NodeEnum::kFunctionItemList:
case NodeEnum::kCaseItemList:
case NodeEnum::kCaseInsideItemList:
case NodeEnum::kGenerateCaseItemList:
case NodeEnum::kConstraintExpressionList:
case NodeEnum::kConstraintBlockItemList:
case NodeEnum::kDistributionItemList:
case NodeEnum::kStructUnionMemberList:
LookAheadBeyondCurrentNode();
break;
case NodeEnum::kArgumentList:
case NodeEnum::kMacroArgList:
// Put each argument, separator, and comment in a new unwrapped line
StartNewUnwrappedLine(PartitionPolicyEnum::kFitOnLineElseExpand, &node);
// Catch comments after last argument
LookAheadBeyondCurrentNode();
break;
default:
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__;
}
enum class ContextHint {
kInsideStandaloneMacroCall,
};
static const verible::EnumNameMap<ContextHint>& ContextHintStrings() {
static const verible::EnumNameMap<ContextHint> kContextHintStringMap({
{"kInsideStandaloneMacroCall", ContextHint::kInsideStandaloneMacroCall},
});
return kContextHintStringMap;
}
std::ostream& operator<<(std::ostream& stream, ContextHint p) {
return ContextHintStrings().Unparse(p, stream);
}
std::ostream& operator<<(std::ostream& stream,
const std::vector<ContextHint>& f) {
return stream << verible::SequenceFormatter(f);
}
// 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;
const auto context_hints_size = context_hints_.size();
// 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 = PreviousSibling(*CurrentTokenPartition());
if (partition != nullptr) {
ReshapeTokenPartitions(node, style_, partition);
}
CHECK_GE(context_hints_.size(), context_hints_size);
// Remove hints from traversed context
context_hints_.erase(context_hints_.begin() + context_hints_size,
context_hints_.end());
}
// CST-descending phase that creates partitions with correct indentation.
// TODO(mglb): This function is > 600 lines and should probably be split
// up into multiple.
void TreeUnwrapper::SetIndentationsAndCreatePartitions(
const SyntaxTreeNode& node) {
const auto tag = static_cast<NodeEnum>(node.Tag().tag);
VLOG(3) << __FUNCTION__ << " node: " << tag;
VLOG(4) << "Context hints: " << ContextHints();
if (!Context().empty()) {
DVLOG(4) << "Context: "
<< absl::StrJoin(Context().begin(), Context().end(), " / ",
[](std::string* out, const SyntaxTreeNode* n) {
const auto tag =
static_cast<NodeEnum>(n->Tag().tag);
absl::StrAppend(out, NodeEnumToString(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::kReferenceCallBase,
NodeEnum::kFunctionCall}) &&
Context().IsInside(NodeEnum::kAlwaysStatement) &&
Context().IsInside(NodeEnum::kNetVariableAssignment)) {
if (any_of(node.children().begin(), node.children().end(),
[](const verible::SymbolPtr& p) {
return p->Tag().tag ==
static_cast<int>(NodeEnum::kParamByName);
})) {
// Indentation of named arguments of functions
VisitIndentedSection(node, style_.indentation_spaces,
PartitionPolicyEnum::kTabularAlignment);
} else {
VisitIndentedSection(node, style_.indentation_spaces,
PartitionPolicyEnum::kFitOnLineElseExpand);
}
} else if (HasContextHint(ContextHint::kInsideStandaloneMacroCall) &&
Context().DirectParentsAre(
{NodeEnum::kParenGroup, NodeEnum::kSystemTFCall})) {
VisitIndentedSection(node, style_.wrap_spaces,
PartitionPolicyEnum::kWrap);
} else if (Context().DirectParentsAre(
{NodeEnum::kParenGroup,
NodeEnum::kRandomizeFunctionCall}) ||
(Context().DirectParentsAre({NodeEnum::kParenGroup,
NodeEnum::kReferenceCallBase,
NodeEnum::kFunctionCall})) ||
(Context().DirectParentsAre(
{NodeEnum::kParenGroup, NodeEnum::kClassNew})) ||
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:
case NodeEnum::kPreprocessorInclude:
case NodeEnum::kPreprocessorUndef:
case NodeEnum::kTFPortDeclaration:
case NodeEnum::kTypeDeclaration:
case NodeEnum::kNetTypeDeclaration:
case NodeEnum::kForwardDeclaration:
case NodeEnum::kConstraintDeclaration:
case NodeEnum::kConstraintExpression:
case NodeEnum::kCovergroupDeclaration:
case NodeEnum::kCoverageOption:
case NodeEnum::kCoverageBin:
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::kVariableDeclarationAssignment:
case NodeEnum::kCaseItem:
case NodeEnum::kDefaultItem:
case NodeEnum::kCaseInsideItem:
case NodeEnum::kCasePatternItem:
case NodeEnum::kGenerateCaseItem:
case NodeEnum::kGateInstance:
case NodeEnum::kRegisterVariable:
case NodeEnum::kGenerateIfClause:
case NodeEnum::kGenerateElseClause:
case NodeEnum::kGenerateIfHeader:
case NodeEnum::kIfClause:
case NodeEnum::kElseClause:
case NodeEnum::kIfHeader:
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 = subnode && 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 = Context().IsInside(NodeEnum::kBlockItemStatementList)
? 0
: ShouldIndentRelativeToDirectParent(Context())
? style_.indentation_spaces
: 0;
VisitIndentedSection(node, indent,
PartitionPolicyEnum::kFitOnLineElseExpand);
break;
}
case NodeEnum::kFunctionCall: {
if (Context().DirectParentIsOneOf(
{NodeEnum::kStatement, NodeEnum::kBlockItemStatementList})) {
VisitIndentedSection(node, 0,
PartitionPolicyEnum::kAppendFittingSubPartitions);
} else {
TraverseChildren(node);
}
break;
}
case NodeEnum::kReferenceCallBase: {
// TODO(fangism): Create own section only for standalone calls
if (Context().DirectParentsAre(
{NodeEnum::kFunctionCall, NodeEnum::kStatement})) {
const auto& subnode =
verible::SymbolCastToNode(*ABSL_DIE_IF_NULL(node.back()));
if (subnode.MatchesTag(NodeEnum::kRandomizeMethodCallExtension) &&
subnode.back() != nullptr) {
// TODO(fangism): Handle constriants
VisitIndentedSection(node, 0, PartitionPolicyEnum::kAlwaysExpand);
} else if (subnode.MatchesTagAnyOf(
{NodeEnum::kMethodCallExtension,
NodeEnum::kRandomizeMethodCallExtension,
NodeEnum::kParenGroup, NodeEnum::kReferenceCallBase})) {
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.back() != nullptr) {
// TODO(fangism): Handle constriants
VisitIndentedSection(node, 0, PartitionPolicyEnum::kAlwaysExpand);
} else {
VisitIndentedSection(
node, 0, PartitionPolicyEnum::kAppendFittingSubPartitions);
}
} else {
TraverseChildren(node);
}
break;
}
case NodeEnum::kSystemTFCall: {
const auto is_nested_call = [&] {
return Context().DirectParentsAre(
{NodeEnum::kExpression, NodeEnum::kArgumentList}) ||
Context().DirectParentsAre(
{NodeEnum::kExpression, NodeEnum::kMacroArgList});
};
const auto is_standalone_call = [&] {
return Context().DirectParentsAre(
{NodeEnum::kStatement, NodeEnum::kStatementList}) ||
Context().DirectParentsAre(
{NodeEnum::kStatement, NodeEnum::kBlockItemStatementList}) ||
Context().DirectParentIs(NodeEnum::kModuleItemList);
};
// TODO(mglb): Format non-standalone calls with layout optimizer.
// This requires using layout optimizer for structures wrapping the
// calls.
if (is_nested_call() &&
HasContextHint(ContextHint::kInsideStandaloneMacroCall)) {
VisitIndentedSection(node, 0, PartitionPolicyEnum::kStack);
} else if (is_standalone_call()) {
PushContextHint(ContextHint::kInsideStandaloneMacroCall);
VisitIndentedSection(node, 0, PartitionPolicyEnum::kStack);
} else if (Context().DirectParentIs(NodeEnum::kGenerateItemList)) {
VisitIndentedSection(node, 0,
PartitionPolicyEnum::kFitOnLineElseExpand);
} 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;
const auto is_nested_call = [&] {
return Context().DirectParentsAre({NodeEnum::kFunctionCall,
NodeEnum::kExpression,
NodeEnum::kArgumentList}) ||
Context().DirectParentsAre({NodeEnum::kFunctionCall,
NodeEnum::kExpression,
NodeEnum::kMacroArgList}) ||
Context().DirectParentsAre(
{NodeEnum::kExpression, NodeEnum::kArgumentList}) ||
Context().DirectParentsAre(
{NodeEnum::kExpression, NodeEnum::kMacroArgList});
};
const auto is_standalone_call = [&] {
return ((Context().DirectParentIs(NodeEnum::kModuleItemList) ||
Context().DirectParentIs(NodeEnum::kClassItems) ||
Context().DirectParentIs(NodeEnum::kDescriptionList) ||
Context().DirectParentsAre({
NodeEnum::kStatementList,
NodeEnum::kTaskDeclaration,
}) ||
Context().DirectParentsAre({
// TODO(jbylicki): Check if LocalRoot and Reference
// should be here based on other tests
NodeEnum::kLocalRoot,
NodeEnum::kReference,
NodeEnum::kReferenceCallBase,
}) ||
Context().DirectParentsAre({
NodeEnum::kBlockItemStatementList,
NodeEnum::kSeqBlock,
NodeEnum::kIfBody,
NodeEnum::kIfClause,
})) &&
!(Context().IsInside(NodeEnum::kNetVariableAssignment)) &&
!(Context().IsInside(NodeEnum::kUnpackedDimensions)));
};
// TODO(mglb): Format non-standalone calls with layout optimizer.
// This would require using layout optimizer for structures wrapping the
// calls.
if (is_nested_call() &&
HasContextHint(ContextHint::kInsideStandaloneMacroCall)) {
VLOG(4) << "kMacroCall: nested";
VisitIndentedSection(node, indent, PartitionPolicyEnum::kStack);
} else if (is_standalone_call()) {
VLOG(4) << "kMacroCall: standalone";
PushContextHint(ContextHint::kInsideStandaloneMacroCall);
VisitIndentedSection(node, indent, PartitionPolicyEnum::kStack);
} else {
// TODO(jbylicki): Check if there is a possibility of
// using kFitOnLineElseExpand - seems to have no effect
VisitIndentedSection(node, indent,
PartitionPolicyEnum::kAppendFittingSubPartitions);
}
break;
}
case NodeEnum::kParenGroup: {
const bool is_directly_inside_macro_call = Context().DirectParentIsOneOf({
NodeEnum::kMacroCall,
NodeEnum::kSystemTFCall,
});
if (HasContextHint(ContextHint::kInsideStandaloneMacroCall) &&
is_directly_inside_macro_call) {
const bool is_nested_call = Context().IsInsideFirst(
{
NodeEnum::kArgumentList,
NodeEnum::kMacroArgList,
NodeEnum::kParenGroup,
},
{
NodeEnum::kStatementList,
NodeEnum::kStatement,
});
if (is_nested_call) {
VisitIndentedSection(node, 0, PartitionPolicyEnum::kWrap);
} else {
VisitIndentedSection(node, style_.wrap_spaces,
PartitionPolicyEnum::kWrap);
}
} else {
TraverseChildren(node);
}
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: {
if (Context().IsInside(NodeEnum::kDPIExportItem) ||
Context().IsInside(NodeEnum::kDPIImportItem)) {
VisitIndentedSection(node, 0,
PartitionPolicyEnum::kFitOnLineElseExpand);
} else {
VisitIndentedSection(node, 0,
PartitionPolicyEnum::kAppendFittingSubPartitions);
}
break;
}
case NodeEnum::kDPIExportItem:
case NodeEnum::kDPIImportItem: {
VisitIndentedSection(node, 0, PartitionPolicyEnum::kAlwaysExpand);
break;
}
// For the following constructs, always expand the view to subpartitions.
// Add a level of indentation.
case NodeEnum::kPackageImportList:
case NodeEnum::kPackageItemList:
case NodeEnum::kInterfaceClassDeclaration:
case NodeEnum::kCasePatternItemList:
case NodeEnum::kConstraintBlockItemList:
case NodeEnum::kConstraintExpressionList:
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::kCrossBodyItemList:
case NodeEnum::kUdpBody:
case NodeEnum::kUdpPortDeclaration:
case NodeEnum::kUdpSequenceEntry:
case NodeEnum::kUdpCombEntry:
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;
}
case NodeEnum::kBinOptionList: {
PartitionPolicyEnum shouldExpand =
style_.expand_coverpoints ? PartitionPolicyEnum::kAlwaysExpand
: PartitionPolicyEnum::kFitOnLineElseExpand;
// Do not further indent preprocessor clauses.
const int indent = suppress_indentation ? 0 : style_.indentation_spaces;
VisitIndentedSection(node, indent, shouldExpand);
break;
}
case NodeEnum::kCoverPoint: {
if (style_.expand_coverpoints) {
VisitIndentedSection(node, 0, PartitionPolicyEnum::kAlwaysExpand);
} else {
VisitIndentedSection(node, 0,
PartitionPolicyEnum::kFitOnLineElseExpand);
}
break;
}
case NodeEnum::kMacroArgList: {
if (node.front() == nullptr ||
node.front()->Tag().tag == verible::kUntagged) {
// Empty arguments list.
TraverseChildren(node);
break;
}
if (HasContextHint(ContextHint::kInsideStandaloneMacroCall) &&
Context().DirectParentsAre(
{NodeEnum::kParenGroup, NodeEnum::kMacroCall})) {
VisitIndentedSection(node, style_.wrap_spaces,
PartitionPolicyEnum::kWrap);
break;
}
}
[[fallthrough]];
// Add a level of grouping that is treated as wrapping.
case NodeEnum::kMacroFormalParameterList:
case NodeEnum::kForSpec:
case NodeEnum::kModportSimplePortsDeclaration:
case NodeEnum::kModportTFPortsDeclaration:
case NodeEnum::kGateInstanceRegisterVariableList:
case NodeEnum::kVariableDeclarationAssignmentList: {
// 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().DirectParentsAre(
{NodeEnum::kConcatenationExpression, NodeEnum::kExpression}) &&
!Context().IsInside(NodeEnum::kCoverageBin) &&
!Context().IsInside(NodeEnum::kConditionExpression) &&
(!Context().IsInside(NodeEnum::kBinaryExpression) ||
Context().IsInside(NodeEnum::kPropertyImplicationList))) {
// Do not further indent preprocessor clauses.
const int indent = suppress_indentation ? 0 : style_.indentation_spaces;
VisitIndentedSection(node, indent,
PartitionPolicyEnum::kFitOnLineElseExpand);
break;
}
// Default handling
TraverseChildren(node);
break;
}
case NodeEnum::kPortList: {
if (Context().IsInside(NodeEnum::kDPIExportItem) ||
Context().IsInside(NodeEnum::kDPIImportItem)) {
const int indent = suppress_indentation ? 0 : style_.indentation_spaces;
VisitIndentedSection(node, indent,
PartitionPolicyEnum::kTabularAlignment);
} else {
const int indent = suppress_indentation ? 0 : style_.wrap_spaces;
VisitIndentedSection(node, indent,
PartitionPolicyEnum::kFitOnLineElseExpand);
}
break;
}
case NodeEnum::kBlockItemStatementList:
case NodeEnum::kStatementList:
case NodeEnum::kFunctionItemList:
case NodeEnum::kCaseItemList:
case NodeEnum::kCaseInsideItemList:
case NodeEnum::kGenerateCaseItemList:
case NodeEnum::kClassItems:
case NodeEnum::kModuleItemList:
case NodeEnum::kGenerateItemList:
case NodeEnum::kDistributionItemList:
case NodeEnum::kEnumNameList:
case NodeEnum::kStructUnionMemberList: {
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) ||
Context().IsInside(NodeEnum::kBindDirective)
? PartitionPolicyEnum::kTabularAlignment
: PartitionPolicyEnum::kFitOnLineElseExpand;
VisitIndentedSection(node, indent, policy);
break;
}
case NodeEnum::kPortDeclarationList: {
if (Context().IsInside(NodeEnum::kPortDeclarationList)) {
// This "recursion" can occur when there are preprocessing conditional
// port declaration lists.
// When this occurs, suppress creating another token partition level.
// This will essentially flatten all port declarations to the same token
// partition tree depth, and importantly, be alignable across
// preprocessing directives as sibling subpartitions.
// Alternatively, we could have done this flattening during
// ReshapeTokenPartitions(), but it would've taken more work to
// re-identify the subpartitions (children) to flatten.
TraverseChildren(node);
break;
}
// 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().DirectParentsAre({NodeEnum::kOpenRangeList,
NodeEnum::kConcatenationExpression,
NodeEnum::kExpression}) &&
!Context().IsInside(NodeEnum::kCoverageBin) &&
!Context().IsInside(NodeEnum::kConditionExpression) &&
(!Context().IsInside(NodeEnum::kBinaryExpression) ||
Context().IsInside(NodeEnum::kPropertyImplicationList))) {
VisitIndentedSection(node, 0,
PartitionPolicyEnum::kFitOnLineElseExpand);
} else if (Context().IsInside(NodeEnum::kAssignmentPattern)) {
VisitIndentedSection(node, 0,
PartitionPolicyEnum::kFitOnLineElseExpand);
} else {
TraverseChildren(node);
}
break;
}
case NodeEnum::kExpressionList:
case NodeEnum::kUntagged: {
if (Context().DirectParentIs(NodeEnum::kAssignmentPattern)) {
VisitIndentedSection(node, style_.wrap_spaces,
PartitionPolicyEnum::kFitOnLineElseExpand);
} else {
TraverseChildren(node);
}
break;
}
case NodeEnum::kAssignmentPattern:
case NodeEnum::kPatternExpression: {
VisitIndentedSection(node, 0, PartitionPolicyEnum::kFitOnLineElseExpand);
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.Value();
if (uwline.IsEmpty()) return false;
const auto first_token = uwline.TokensRange().front().TokenEnum();
return (first_token == ';' ||
first_token ==
verilog_tokentype::SemicolonEndOfAssertionVariableDeclarations);
}
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 bool PartitionEndsWithOpenBrace(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 PartitionStartsWithCloseBrace(const TokenPartitionTree& partition) {
const auto ftokens = partition.Value().TokensRange();
if (ftokens.empty()) return false;
const auto token_enum = ftokens.front().TokenEnum();
return token_enum == '}';
}
// Returns true if the partition is forced to start in a new line.
static bool PartitionIsForcedIntoNewLine(const TokenPartitionTree& partition) {
const auto policy = partition.Value().PartitionPolicy();
if (policy == PartitionPolicyEnum::kAlreadyFormatted) return true;
if (policy == PartitionPolicyEnum::kInline) return false;
if (!is_leaf(partition)) {
auto* first_leaf = &LeftmostDescendant(partition);
const auto leaf_policy = first_leaf->Value().PartitionPolicy();
if (leaf_policy == PartitionPolicyEnum::kAlreadyFormatted ||
leaf_policy == PartitionPolicyEnum::kInline) {
return true;
}
}
const auto ftokens = partition.Value().TokensRange();
if (ftokens.empty()) return false;
return ftokens.front().before.break_decision == SpacingOptions::kMustWrap;
}
// Joins partition containing only a ',' (and optionally comments) with
// a partition preceding or following it.
static void AttachSeparatorToPreviousOrNextPartition(
TokenPartitionTree* partition) {
CHECK_NOTNULL(partition);
VLOG(5) << __FUNCTION__ << ": subpartition:\n" << *partition;
if (!is_leaf(*partition)) {
VLOG(5) << " skip: not a leaf.";
return;
}
// Find a separator and make sure this is the only non-comment token
const verible::PreFormatToken* separator = nullptr;
for (const auto& token : partition->Value().TokensRange()) {
switch (token.TokenEnum()) {
case verilog_tokentype::TK_COMMENT_BLOCK:
case verilog_tokentype::TK_EOL_COMMENT:
case verilog_tokentype::TK_ATTRIBUTE:
break;
case ',':
case ':':
if (separator == nullptr) {
separator = &token;
break;
}
[[fallthrough]];
default:
VLOG(5) << " skip: contains tokens other than separator and comments.";
return;
}
}
if (separator == nullptr) {
VLOG(5) << " skip: separator token not found.";
return;
}
// Merge with previous partition if both partitions are in the same line in
// original text
const auto* previous_partition = PreviousLeaf(*partition);
if (previous_partition != nullptr) {
if (!previous_partition->Value().TokensRange().empty()) {
const auto& previous_token =
previous_partition->Value().TokensRange().back();
absl::string_view original_text_between = verible::make_string_view_range(
previous_token.Text().end(), separator->Text().begin());
if (!absl::StrContains(original_text_between, '\n')) {
VLOG(5) << " merge into previous partition.";
verible::MergeLeafIntoPreviousLeaf(partition);
return;
}
}
}
// Merge with next partition if both partitions are in the same line in
// original text
const auto* next_partition = NextLeaf(*partition);
if (next_partition != nullptr) {
if (!next_partition->Value().TokensRange().empty()) {
const auto& next_token = next_partition->Value().TokensRange().front();
absl::string_view original_text_between = verible::make_string_view_range(
separator->Text().end(), next_token.Text().begin());
if (!absl::StrContains(original_text_between, '\n')) {
VLOG(5) << " merge into next partition.";
verible::MergeLeafIntoNextLeaf(partition);
return;
}
}
}
// If there are no comments (separator is the only token)...
if (partition->Value().TokensRange().size() == 1) {
// Try merging with previous partition
if (!PartitionIsForcedIntoNewLine(*partition)) {
VLOG(5) << " merge into previous partition.";
verible::MergeLeafIntoPreviousLeaf(partition);
return;
}
// Try merging with next partition
if (next_partition != nullptr &&
!PartitionIsForcedIntoNewLine(*next_partition)) {
VLOG(5) << " merge into next partition.";
verible::MergeLeafIntoNextLeaf(partition);
return;
}
}
// Leave the separator in its own line and clear its Origin() to not confuse
// tabular alignment.
VLOG(5) << " keep in separate line, remove origin.";
partition->Value().SetOrigin(nullptr);
}
void AttachSeparatorsToListElementPartitions(TokenPartitionTree* partition) {
CHECK_NOTNULL(partition);
// Skip the first partition, it can't contain just a separator.
for (int i = 1; i < static_cast<int>(partition->Children().size()); ++i) {
auto& subpartition = partition->Children()[i];
// This can change children count
AttachSeparatorToPreviousOrNextPartition(&subpartition);
}
}
static void AttachTrailingSemicolonToPreviousPartition(
TokenPartitionTree* partition) {
// TODO(mglb): Replace this function with
// AttachSeparatorToPreviousOrNextPartition().
// 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.
auto* semicolon_partition = &RightmostDescendant(*partition);
if (PartitionStartsWithSemicolon(*semicolon_partition)) {
// When the semicolon is forced to wrap (e.g. when previous partition ends
// with EOL comment), wrap previous partition with a group and append the
// semicolon partition to it.
if (PartitionIsForcedIntoNewLine(*semicolon_partition)) {
auto* group = verible::GroupLeafWithPreviousLeaf(semicolon_partition);
// Update invalidated pointer
semicolon_partition = &RightmostDescendant(*group);
group->Value().SetPartitionPolicy(
verible::PartitionPolicyEnum::kAlwaysExpand);
// Set indentation of partition with semicolon to match indentation of the
// partition it has been grouped with
semicolon_partition->Value().SetIndentationSpaces(
group->Value().IndentationSpaces());
} else {
verible::MergeLeafIntoPreviousLeaf(semicolon_partition);
}
VLOG(4) << "after moving semicolon:\n" << *partition;
}
}
static void AdjustSubsequentPartitionsIndentation(TokenPartitionTree* partition,
int indentation) {
// Adjust indentation of subsequent partitions
const auto npartitions = partition->Children().size();
if (npartitions > 1) {
const auto& first_partition = partition->Children().front();
const auto& last_partition = partition->Children().back();
// Do not indent intentionally wrapped partitions, e.g.
// { (>>[assign foo = {] }
// { (>>>>[<auto>]
// { (>>>>[a ,] }
// { (>>>>[b ,] }
// { (>>>>[c ,] }
// { (>>>>[d] }
// }
// { (>>[} ;] }
if (!(PartitionEndsWithOpenBrace(first_partition) &&
PartitionStartsWithCloseBrace(last_partition)) &&
!(PartitionEndsWithOpenParen(first_partition) &&
PartitionIsCloseParenSemi(last_partition))) {
for (unsigned int idx = 1; idx < npartitions; ++idx) {
AdjustIndentationRelative(&partition->Children()[idx], indentation);
}
}
}
}
// Merges first found subpartition ending with "=" or ":" with "'{" or "{"
// from the subpartition following it.
static void AttachOpeningBraceToDeclarationsAssignmentOperator(
TokenPartitionTree* partition) {
const int children_count = partition->Children().size();
if (children_count <= 1) return;
for (int i = 0; i < children_count - 1; ++i) {
auto& current = RightmostDescendant(partition->Children()[i]);
const auto tokens = current.Value().TokensRange();
if (tokens.empty()) continue;
auto rbegin = std::make_reverse_iterator(tokens.end());
auto rend = std::make_reverse_iterator(tokens.begin());
auto last_non_comment_it =
std::find_if_not(rbegin, rend, [](const PreFormatToken& token) {
// Lines with EOL Comment are non-mergable, so don't skip it here.
return token.TokenEnum() == verilog_tokentype::TK_COMMENT_BLOCK;
});
if (last_non_comment_it == rend ||
!(last_non_comment_it->TokenEnum() == '=' ||
last_non_comment_it->TokenEnum() == ':')) {
continue;
}
const auto& next = partition->Children()[i + 1];
if (next.Value().IsEmpty()) continue;
const auto& next_token = next.Value().TokensRange().front();
if (!(next_token.TokenEnum() == verilog_tokentype::TK_LP ||
next_token.TokenEnum() == '{')) {
continue;
}
if (!PartitionIsForcedIntoNewLine(next)) {
verible::MergeLeafIntoNextLeaf(&current);
// There shouldn't be more matching partitions
return;
}
}
}
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.
FlattenOneChild(partition, verible::BirthRank(if_body_partition));
}
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& children = partition.Children();
auto else_partition_iter = std::find_if(
children.begin(), children.end(), [](const TokenPartitionTree& n) {
const auto* origin = n.Value().Origin();
return origin &&
origin->Tag() == verible::LeafTag(verilog_tokentype::TK_else);
});
if (else_partition_iter == children.end()) return;
auto& else_partition = *else_partition_iter;
auto* next_leaf = NextLeaf(else_partition);
if (!next_leaf || PartitionIsForcedIntoNewLine(*next_leaf)) return;
const auto* next_origin = next_leaf->Value().Origin();
if (!next_origin ||
!(next_origin->Tag() == verible::NodeTag(NodeEnum::kBegin) ||
next_origin->Tag() == verible::LeafTag(verilog_tokentype::TK_begin) ||
next_origin->Tag() == verible::NodeTag(NodeEnum::kIfHeader) ||
next_origin->Tag() == verible::NodeTag(NodeEnum::kGenerateIfHeader) ||
next_origin->Tag() == verible::LeafTag(verilog_tokentype::TK_if))) {
return;
}
verible::MergeLeafIntoNextLeaf(&else_partition);
}
static void HoistOnlyChildPartition(TokenPartitionTree* partition) {
// kWrap uses relative child indentation as a hanging
// if (partition->Value().PartitionPolicy() == PartitionPolicyEnum::kWrap) {
// TODO(mglb): implement or remove.
// }
const auto* origin = partition->Value().Origin();
if (HoistOnlyChild(*partition)) {
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 = &RightmostDescendant(if_clause_partition);
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) {
FlattenOneChild(partition, partition.Children().size() - 1);
}
}
static void ReshapeConditionalConstruct(const SyntaxTreeNode& conditional,
TokenPartitionTree* partition_ptr,
const FormatStyle& style) {
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";
if (!style.wrap_end_else_clauses) {
MergeEndElseWithoutLabel(conditional, partition_ptr);
}
FlattenElseIfElse(*else_clause, partition_ptr);
}
static bool TokenIsComment(const PreFormatToken& t) {
return IsComment(verilog_tokentype(t.TokenEnum()));
}
static void SetCommentLinePartitionAsAlreadyFormatted(
TokenPartitionTree* partition) {
for (auto& child : partition->Children()) {
if (!is_leaf(child)) continue;
const auto tokens = child.Value().TokensRange();
if (std::all_of(tokens.begin(), tokens.end(), TokenIsComment)) {
child.Value().SetPartitionPolicy(PartitionPolicyEnum::kAlreadyFormatted);
}
}
}
using TokenPartitionPredicate = std::function<bool(const TokenPartitionTree&)>;
// TokenPartitionPredicate returning true for partitions whose Origin's tag is
// equal to any tag from `tags` list.
struct OriginTagIs {
OriginTagIs(std::initializer_list<SymbolTag> tags) : tags(tags) {}
std::initializer_list<SymbolTag> tags;
bool operator()(const TokenPartitionTree& partition) const {
if (!partition.Value().Origin()) return false;
for (const auto& tag : tags) {
if (partition.Value().Origin()->Tag() == tag) return true;
}
return false;
}
};
// TokenPartitionPredicate returning true for partitions containing token with a
// type equal to any value from `token_types` list.
struct ContainsToken {
ContainsToken(std::initializer_list<int> token_types)
: token_types(token_types) {}
std::initializer_list<int> token_types;
bool operator()(const TokenPartitionTree& partition) const {
for (const auto& token : partition.Value().TokensRange()) {
for (const auto token_type : token_types) {
if (token.TokenEnum() == token_type) return true;
}
}
return false;
}
};
// Finds direct child of the `parent` node satisfying the `predicate`. Returns
// nullptr when child has not been found.
static const TokenPartitionTree* FindDirectChild(
const TokenPartitionTree* parent, TokenPartitionPredicate predicate) {
if (!parent) return nullptr;
auto iter = std::find_if(parent->Children().begin(), parent->Children().end(),
std::move(predicate));
if (iter == parent->Children().end()) return nullptr;
return &(*iter);
}
// Finds direct child of the `parent` node satisfying the `predicate`. Returns
// nullptr when child has not been found.
static TokenPartitionTree* FindDirectChild(TokenPartitionTree* parent,
TokenPartitionPredicate predicate) {
const TokenPartitionTree* const_parent = parent;
return const_cast<TokenPartitionTree*>(
FindDirectChild(const_parent, std::move(predicate)));
}
static bool LineBreaksInsidePartitionBeforeChild(
const TokenPartitionTree& parent, const TokenPartitionTree& child) {
for (auto& node : parent.Children()) {
if (PartitionIsForcedIntoNewLine(node)) return true;
if (&node == &child) break;
}
return false;
}
class MacroCallReshaper {
public:
explicit MacroCallReshaper(const FormatStyle& style,
TokenPartitionTree* main_node)
: style_(style),
main_node_(main_node),
is_nested_(IsNested(*main_node)) {}
void Reshape() {
const auto* const main_node_origin = main_node_->Value().Origin();
if (!FindInitialPartitions()) {
// Something went wrong, leave partitions intact. Log message has been
// printed.
return;
}
if (semicolon_) {
if (semicolon_ == NextSibling(*paren_group_)) {
if (!PartitionIsForcedIntoNewLine(*semicolon_)) {
// Merge with ')'
verible::MergeLeafIntoPreviousLeaf(semicolon_);
semicolon_ = nullptr;
}
}
}
if (ReshapeEmptyParenGroup()) {
auto* identifier_with_paren_group = main_node_;
if (!IsLastChild(*paren_group_)) {
// There is a semicolon preceded by comment line(s).
identifier_with_paren_group =
verible::GroupLeafWithPreviousLeaf(paren_group_);
identifier_with_paren_group->Value().SetPartitionPolicy(
PartitionPolicyEnum::kJuxtapositionOrIndentedStack);
main_node_->Value().SetPartitionPolicy(PartitionPolicyEnum::kStack);
}
if (main_node_origin &&
main_node_origin->Tag() == NodeTag(NodeEnum::kMacroCall)) {
// '(' must follow macro identifier in the same line
CHECK(!PartitionIsForcedIntoNewLine(*paren_group_));
identifier_with_paren_group->Children().clear();
identifier_with_paren_group->Value().SetPartitionPolicy(
PartitionPolicyEnum::kAlreadyFormatted);
}
return;
}
if ((!argument_list_ || is_leaf(*argument_list_)) &&
(BirthRank(*r_paren_) - BirthRank(*l_paren_) > 1)) {
// Group partitions between parentheses (comments and stuff)
CreateArgumentList();
}
// TODO(jbylicki): This part causes mismatches in the syntax tree of the
// formatted code.
if (argument_list_ &&
(is_nested_ || !PartitionIsForcedIntoNewLine(*r_paren_))) {
// Format like a part of argument list.
MoveRightParenToArgumentList();
}
if (argument_list_ && style_.try_wrap_long_lines) {
for (auto& arg : argument_list_->Children()) {
const auto tokens = arg.Value().TokensRange();
// Hack: in order to avoid wrapping just before comment, do not enable
// wrapping on partitions containing comments.
if (is_leaf(arg) &&
arg.Value().PartitionPolicy() !=
PartitionPolicyEnum::kAlreadyFormatted &&
std::none_of(tokens.begin(), tokens.end(),
[](const PreFormatToken& t) {
return IsComment(verilog_tokentype(t.TokenEnum()));
})) {
arg.Value().SetPartitionPolicy(PartitionPolicyEnum::kWrap);
}
}
}
if (main_node_origin &&
main_node_origin->Tag() == NodeTag(NodeEnum::kMacroCall)) {
// '(' must follow macro identifier in the same line
MergeIdentifierAndLeftParen();
} else if (!LineBreaksInsidePartitionBeforeChild(*paren_group_,
*l_paren_)) {
GroupIdentifierCommentsAndLeftParen();
} else if (l_paren_ != &paren_group_->Children().front()) {
GroupCommentsAndLeftParen();
}
if (argument_list_) {
HoistOnlyChildPartition(argument_list_);
if (paren_group_->Children().size() == 3) {
// Children: '(', argument_list, ')'
GroupLeftParenAndArgumentList();
}
if (!PartitionIsForcedIntoNewLine(paren_group_->Children().back())) {
paren_group_->Value().SetPartitionPolicy(
PartitionPolicyEnum::kJuxtapositionOrIndentedStack);
} else {
paren_group_->Value().SetPartitionPolicy(PartitionPolicyEnum::kStack);
}
}
}
private:
static bool IsLeftParenPartition(const TokenPartitionTree& node) {
const auto tokens = node.Value().TokensRange();
bool found = false;
auto token = tokens.begin();
for (; token != tokens.end(); ++token) {
if (token->TokenEnum() == '(') {
found = true;
++token;
break;
}
if (token->TokenEnum() == verilog_tokentype::TK_COMMENT_BLOCK) continue;
return false;
}
for (; token != tokens.end(); ++token) {
if (IsComment(verilog_tokentype(token->TokenEnum()))) continue;
if (found && token->TokenEnum() == ')') continue;
return false;
}
return found;
}
// Report a bug/untested situation, but don't abort.
#define LOG_PARTITION_BUG(reason) \
LOG(ERROR) << "formatting of macro call failed: " << (reason) \
<< "\n*** Please file a bug. ***";
bool FindInitialPartitions() {
// Note: identifier can contain tokens for macro name + EOL comment
identifier_ = FindDirectChild(
main_node_, ContainsToken{verilog_tokentype::SystemTFIdentifier,
verilog_tokentype::MacroCallId});
if (!identifier_) {
LOG_PARTITION_BUG("identifier not found.");
return false;
}
paren_group_ = FindDirectChild(main_node_,
OriginTagIs{NodeTag(NodeEnum::kParenGroup)});
// Macro can be used without parentheses
if (!paren_group_) {
return false;
}
// Make sure there's nothing between identifier and paren group partitions.
// Optional comment partitions are expected to be inside paren group.
if (NextSibling(*identifier_) != paren_group_) {
LOG_PARTITION_BUG(
"Unexpected partitions between identifier and paren_group.");
return false;
}
if (!IsLastChild(*paren_group_)) {
semicolon_ = &main_node_->Children().back();
if (!ContainsToken{';'}(*semicolon_)) {
LOG_PARTITION_BUG("Unexpected partition(s) after the call.");
LOG(ERROR) << "\n" << *main_node_;
return false;
}
}
if (is_leaf(*paren_group_)) {
l_paren_ = paren_group_;
r_paren_ = paren_group_;
} else {
l_paren_ = FindDirectChild(paren_group_, IsLeftParenPartition);
if (!l_paren_) {
LOG_PARTITION_BUG("'(' not found.");
return false;
}
r_paren_ = &paren_group_->Children().back();
if (!ContainsToken{
')', verilog_tokentype::MacroCallCloseToEndLine}(*r_paren_)) {
LOG_PARTITION_BUG("')' not found.");
return false;
}
const auto l_paren_index = BirthRank(*l_paren_);
const auto r_paren_index = BirthRank(*r_paren_);
CHECK_LE(l_paren_index, r_paren_index);
for (std::size_t i = l_paren_index + 1; i < r_paren_index; ++i) {
auto& child = paren_group_->Children()[i];
if (OriginTagIs{NodeTag(NodeEnum::kArgumentList),
NodeTag(NodeEnum::kMacroArgList)}(child)) {
argument_list_ = &child;
break;
}
}
}
return true;
}
#undef LOG_PARTITION_BUG
bool ReshapeEmptyParenGroup() {
if (paren_group_->Children().size() == 2 && l_paren_ != r_paren_ &&
l_paren_->Value().TokensRange().end() ==
r_paren_->Value().TokensRange().begin() &&
!PartitionIsForcedIntoNewLine(*r_paren_)) {
VLOG(6) << "Flatten paren group.";
paren_group_->Children().clear();
}
if (is_leaf(*paren_group_)) {
if (!PartitionIsForcedIntoNewLine(*paren_group_)) {
main_node_->Value().SetPartitionPolicy(
PartitionPolicyEnum::kJuxtapositionOrIndentedStack);
}
VLOG(6) << "Empty paren group.";
return true;
}
return false;
}
void CreateArgumentList() {
const int arguments_indentation =
paren_group_->Value().IndentationSpaces() + style_.wrap_spaces;
auto group = TokenPartitionTree(verible::UnwrappedLine(
arguments_indentation, l_paren_->Value().TokensRange().end(),
PartitionPolicyEnum::kWrap));
group.Value().SpanUpToToken(r_paren_->Value().TokensRange().begin());
const auto paren_group_begin = paren_group_->Children().begin();
const auto args_begin = paren_group_begin + BirthRank(*l_paren_) + 1;
const auto args_end = paren_group_begin + BirthRank(*r_paren_);
// Move partitions into the group.
group.Children().assign(std::make_move_iterator(args_begin),
std::make_move_iterator(args_end));
for (auto& node : group.Children()) {
verible::AdjustIndentationAbsolute(&node, arguments_indentation);
}
// Remove leftover entries of all grouped partitions except the first.
paren_group_->Children().erase(args_begin + 1, args_end);
// Move the group into first grouped partition's place.
*args_begin = std::move(group);
argument_list_ = &*args_begin;
r_paren_ = NextSibling(*argument_list_);
}
void MoveRightParenToArgumentList() {
{
r_paren_->Value().SetIndentationSpaces(
argument_list_->Value().IndentationSpaces());
const auto r_paren_iter =
paren_group_->Children().begin() + BirthRank(*r_paren_);
argument_list_->Value().SpanUpToToken(
r_paren_->Value().TokensRange().end());
argument_list_->Children().push_back(std::move(*r_paren_));
r_paren_ = &argument_list_->Children().back();
paren_group_->Children().erase(r_paren_iter);
}
if (!PartitionIsForcedIntoNewLine(*r_paren_)) {
// We want to avoid wrapping just before `)`. It would be best to use
// token's break_penalty in Layout Optimizer, but that's a task for
// future.
// So far, use hacky heuristic: merge short partitions (things
// like `);`) and group longer ones (anything with comments).
const auto r_paren_tokens = r_paren_->Value().TokensRange();
if (is_leaf(*r_paren_) &&
std::none_of(r_paren_tokens.begin(), r_paren_tokens.end(),
[](const PreFormatToken& t) {
return IsComment(verilog_tokentype(t.TokenEnum()));
})) {
// Merge
verible::MergeLeafIntoPreviousLeaf(r_paren_);
r_paren_ = &argument_list_->Children().back();
} else {
// Group
auto& last_argument = *PreviousSibling(*r_paren_);
auto group = TokenPartitionTree(
verible::UnwrappedLine(last_argument.Value().IndentationSpaces(),
last_argument.Value().TokensRange().begin(),
PartitionPolicyEnum::kJuxtaposition));
group.Value().SpanUpToToken(r_paren_->Value().TokensRange().end());
group.Children().reserve(2);
group.Children().push_back(std::move(last_argument));
group.Children().push_back(std::move(*r_paren_));
argument_list_->Children().erase(argument_list_->Children().end() - 1);
argument_list_->Children().back() = std::move(group);
r_paren_ = &argument_list_->Children().back();
}
}
}
void GroupIdentifierCommentsAndLeftParen() {
const bool r_paren_is_in_arg_list = (r_paren_->Parent() == argument_list_);
auto group = TokenPartitionTree(
verible::UnwrappedLine(main_node_->Value().IndentationSpaces(),
identifier_->Value().TokensRange().begin(),
PartitionPolicyEnum::kJuxtaposition));
group.Value().SpanUpToToken(l_paren_->Value().TokensRange().end());
verible::AdjustIndentationAbsolute(paren_group_,
main_node_->Value().IndentationSpaces());
paren_group_->Value().SpanBackToToken(group.Value().TokensRange().begin());
const auto old_identifier_iter =
identifier_->Parent()->Children().begin() + BirthRank(*identifier_);
const auto l_paren_index = BirthRank(*l_paren_);
auto nodes_to_group = verible::iterator_range(
paren_group_->Children().begin(),
paren_group_->Children().begin() + l_paren_index + 1);
const auto nodes_count = l_paren_index + 1 + 1; // + 1 for identifier
group.Children().reserve(nodes_count);
// Move partitions into the group.
group.Children().push_back(std::move(*identifier_));
group.Children().insert(group.Children().end(),
std::make_move_iterator(nodes_to_group.begin()),
std::make_move_iterator(nodes_to_group.end()));
// Remove leftover entries of all grouped partitions except:
// * First paren_group's child: will be reused as a group node.
// * The identifier: removing it will invalidate iterators; done later.
paren_group_->Children().erase(nodes_to_group.begin() + 1,
nodes_to_group.end());
// Move the group into first grouped partition's place.
*nodes_to_group.begin() = std::move(group);
// Remove identifier
main_node_->Children().erase(old_identifier_iter);
HoistOnlyChildPartition(main_node_);
paren_group_ = main_node_;
identifier_ = nullptr;
l_paren_ = &paren_group_->Children().front();
if (argument_list_) {
argument_list_ = NextSibling(*l_paren_);
r_paren_ = r_paren_is_in_arg_list && !argument_list_->Children().empty()
? &argument_list_->Children().back()
: NextSibling(*argument_list_);
} else {
r_paren_ = &paren_group_->Children().back();
}
}
void MergeIdentifierAndLeftParen() {
const bool r_paren_is_in_arg_list = (r_paren_->Parent() == argument_list_);
CHECK_EQ(NextLeaf(*identifier_), l_paren_);
CHECK(!PartitionIsForcedIntoNewLine(*l_paren_));
verible::AdjustIndentationAbsolute(paren_group_,
main_node_->Value().IndentationSpaces());
verible::MergeLeafIntoNextLeaf(identifier_);
HoistOnlyChildPartition(main_node_);
paren_group_ = main_node_;
identifier_ = nullptr;
l_paren_ = &paren_group_->Children().front();
if (argument_list_) {
argument_list_ = NextSibling(*l_paren_);
r_paren_ = r_paren_is_in_arg_list ? &argument_list_->Children().back()
: NextSibling(*argument_list_);
} else {
r_paren_ = &paren_group_->Children().back();
}
}
void GroupCommentsAndLeftParen() {
// Group comments + '('
auto group = TokenPartitionTree(
verible::UnwrappedLine(paren_group_->Value().IndentationSpaces(),
paren_group_->Value().TokensRange().begin(),
PartitionPolicyEnum::kWrap));
group.Value().SpanUpToToken(l_paren_->Value().TokensRange().end());
const auto l_paren_index = BirthRank(*l_paren_);
const auto paren_group_begin = paren_group_->Children().begin();
auto nodes_to_group = verible::iterator_range(
paren_group_begin, paren_group_begin + l_paren_index + 1);
// Move partitions into the group.
group.Children().assign(std::make_move_iterator(nodes_to_group.begin()),
std::make_move_iterator(nodes_to_group.end()));
// Remove leftover entries of all grouped partitions except:
// * First paren_group's child: will be reused as a group node.
paren_group_->Children().erase(nodes_to_group.begin() + 1,
nodes_to_group.end());
// Move the group into first grouped partition's place.
*nodes_to_group.begin() = std::move(group);
l_paren_ = &(*nodes_to_group.begin());
if (argument_list_) {
argument_list_ = NextSibling(*l_paren_);
r_paren_ = is_nested_ ? &argument_list_->Children().back()
: NextSibling(*argument_list_);
} else {
r_paren_ = &paren_group_->Children().back();
}
}
void GroupLeftParenAndArgumentList() {
auto old_argument_list_iter =
paren_group_->Children().begin() + BirthRank(*argument_list_);
const auto group_policy =
PartitionIsForcedIntoNewLine(*argument_list_)
? PartitionPolicyEnum::kStack
: PartitionPolicyEnum::kJuxtapositionOrIndentedStack;
auto group = TokenPartitionTree(verible::UnwrappedLine(
l_paren_->Value().IndentationSpaces(),
l_paren_->Value().TokensRange().begin(), group_policy));
group.Value().SpanUpToToken(argument_list_->Value().TokensRange().end());
AdoptSubtree(group, std::move(*l_paren_), std::move(*argument_list_));
*l_paren_ = std::move(group);
paren_group_->Children().erase(old_argument_list_iter);
l_paren_ = nullptr;
argument_list_ = nullptr;
}
const FormatStyle& style_;
TokenPartitionTree* const main_node_;
// Subnodes of main_node_. Initialized by FindInitialPartitions().
// Tree modifications invalidate pointers, so keeping pointers here instead
// of passing them to each method allows for more readable code.
TokenPartitionTree* identifier_ = nullptr;
TokenPartitionTree* paren_group_ = nullptr;
TokenPartitionTree* argument_list_ = nullptr;
TokenPartitionTree* l_paren_ = nullptr;
TokenPartitionTree* r_paren_ = nullptr;
TokenPartitionTree* semicolon_ = nullptr;
static bool IsNested(const TokenPartitionTree& macro_call_node) {
auto* paren_group = FindDirectChild(
&macro_call_node, OriginTagIs{NodeTag(NodeEnum::kParenGroup)});
if (!paren_group) return false;
return paren_group->Value().IndentationSpaces() ==
macro_call_node.Value().IndentationSpaces();
}
const bool is_nested_;
};
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 = PreviousSibling(partition); itr;
itr = PreviousSibling(*itr)) {
VLOG(4) << "Scanning previous sibling:\n" << *itr;
const auto macroId =
LeftmostDescendant(*itr).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);
}
}
}
static const SyntaxTreeNode* GetAssignedExpressionFromDataDeclaration(
const verible::Symbol& data_declaration) {
const auto* instance_list =
GetInstanceListFromDataDeclaration(data_declaration);
if (!instance_list || instance_list->empty()) return nullptr;
const auto& variable_or_gate = *instance_list->front();
if (variable_or_gate.Tag().kind != verible::SymbolKind::kNode) return nullptr;
const verible::Symbol* trailing_assign;
if (variable_or_gate.Tag() == verible::NodeTag(NodeEnum::kRegisterVariable)) {
trailing_assign =
GetTrailingExpressionFromRegisterVariable(variable_or_gate);
} else if (variable_or_gate.Tag() ==
verible::NodeTag(NodeEnum::kVariableDeclarationAssignment)) {
trailing_assign =
GetTrailingExpressionFromVariableDeclarationAssign(variable_or_gate);
} else {
return nullptr;
}
if (!trailing_assign) return nullptr;
const verible::Symbol* expression = verible::GetSubtreeAsSymbol(
*trailing_assign, NodeEnum::kTrailingAssign, 1);
if (!expression ||
expression->Tag() != verible::NodeTag(NodeEnum::kExpression)) {
return nullptr;
}
return &verible::SymbolCastToNode(*expression);
}
static void HandleDataDeclaration(const SyntaxTreeNode& node,
const FormatStyle& style,
TokenPartitionTree* partition) {
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.
verible::BirthRank(instance_list_partition) == 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)->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(PreviousSibling(children.back()));
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;
FlattenOnlyChildrenWithChildren(data_declaration_partition, &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.
FlattenOnlyChildrenWithChildren(data_declaration_partition);
}
} else {
VLOG(4) << "None of the special cases apply.";
}
// Handle variable declaration with an assignment
const auto* assigned_expr = GetAssignedExpressionFromDataDeclaration(node);
if (!assigned_expr || assigned_expr->empty()) return;
AttachOpeningBraceToDeclarationsAssignmentOperator(partition);
// Special handling for assignment of a string concatenation
const auto* concat_expr_symbol = assigned_expr->front().get();
if (concat_expr_symbol->Tag() !=
verible::NodeTag(NodeEnum::kConcatenationExpression)) {
return;
}
const auto* open_range_list_symbol =
verible::SymbolCastToNode(*concat_expr_symbol)[1].get();
if (open_range_list_symbol->Tag() !=
verible::NodeTag(NodeEnum::kOpenRangeList)) {
return;
}
const auto& open_range_list =
verible::SymbolCastToNode(*open_range_list_symbol);
if (std::all_of(
open_range_list.children().begin(), open_range_list.children().end(),
[](const verible::SymbolPtr& sym) {
if (sym->Tag() == verible::NodeTag(NodeEnum::kExpression)) {
const auto& expr = verible::SymbolCastToNode(*sym.get());
return !expr.empty() &&
expr.front()->Tag() ==
verible::LeafTag(verilog_tokentype::TK_StringLiteral);
}
return (sym->Kind() == verible::SymbolKind::kLeaf);
})) {
auto& assigned_value_partition = data_declaration_partition.Children()[1];
partition->Value().SetPartitionPolicy(
PartitionPolicyEnum::kAppendFittingSubPartitions);
assigned_value_partition.Value().SetPartitionPolicy(
PartitionPolicyEnum::kAlwaysExpand);
}
}
// This phase is strictly concerned with reshaping token partitions,
// and occurs on the return path of partition tree construction.
// TODO: this method is long; possibly break out functionality similar to
// HandleDataDeclaration().
void TreeUnwrapper::ReshapeTokenPartitions(
const SyntaxTreeNode& node, const FormatStyle& style,
TokenPartitionTree* recent_partition) {
const NodeEnum tag = static_cast<NodeEnum>(node.Tag().tag);
VLOG(3) << __FUNCTION__ << " node: " << tag;
TokenPartitionTree& partition = *recent_partition;
VLOG(4) << "before reshaping " << tag << ":\n"
<< VerilogPartitionPrinter(partition);
// Few node types that need to skip hoisting at the end of this function set
// this flag to true.
bool skip_singleton_hoisting = false;
// 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:
HandleDataDeclaration(node, style, &partition);
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::kDPIExportItem:
case NodeEnum::kDPIImportItem:
// partition consists of (example):
// [import "DPI-C"]
// [function ... ] (task header/prototype)
// Push the "import..." down.
{
if (partition.Children().size() > 1) {
verible::MergeLeafIntoNextLeaf(&partition.Children().front());
AttachTrailingSemicolonToPreviousPartition(&partition);
}
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(PreviousSibling(children.back()));
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(PreviousSibling(last));
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(PreviousSibling(last));
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 = RightmostDescendant(partition);
if (PartitionIsCloseParenSemi(last)) {
verible::MergeLeafIntoPreviousLeaf(&last);
}
break;
}
case NodeEnum::kFunctionCall: {
const auto& reference_call_base =
verible::SymbolCastToNode(*node.front());
const auto& subnode =
verible::SymbolCastToNode(*reference_call_base.back());
if (subnode.MatchesTagAnyOf({NodeEnum::kMethodCallExtension,
NodeEnum::kParenGroup,
NodeEnum::kRandomizeMethodCallExtension})) {
auto& last = RightmostDescendant(partition);
if (PartitionStartsWithCloseParen(last) ||
PartitionStartsWithSemicolon(last)) {
verible::MergeLeafIntoPreviousLeaf(&last);
}
}
break;
}
case NodeEnum::kMacroCall: {
const auto policy = partition.Value().PartitionPolicy();
if (policy == PartitionPolicyEnum::kStack) {
MacroCallReshaper(style, &partition).Reshape();
break;
}
if (MacroCallArgsIsEmpty(*GetMacroCallArgs(node))) {
// If there are no call args, join the name, '(' and ')' together.
partition.Children().clear();
} else {
// Merge closing parenthesis into last argument partition
// Test for ')' and MacroCallCloseToEndLine because macros
// use its own token 'MacroCallCloseToEndLine'
auto& last = RightmostDescendant(partition);
if (PartitionStartsWithCloseParen(last) ||
PartitionIsCloseParenSemi(last)) {
auto& token = last.Value().TokensRange().front();
if (token.before.break_decision ==
verible::SpacingOptions::kMustWrap) {
auto* group = verible::GroupLeafWithPreviousLeaf(&last);
group->Value().SetPartitionPolicy(PartitionPolicyEnum::kStack);
} else {
verible::MergeLeafIntoPreviousLeaf(&last);
}
}
}
break;
}
case NodeEnum::kRandomizeFunctionCall:
case NodeEnum::kSystemTFCall: {
// Function/system calls don't always have it's own section.
// So before we do any adjuments we check that.
// We're checking for partition policy because those two policies
// force own partition section.
const auto policy = partition.Value().PartitionPolicy();
if (policy == PartitionPolicyEnum::kAppendFittingSubPartitions) {
auto& last = RightmostDescendant(partition);
if (PartitionStartsWithCloseParen(last) ||
PartitionStartsWithSemicolon(last)) {
auto& token = last.Value().TokensRange().front();
if (token.before.break_decision ==
verible::SpacingOptions::kMustWrap) {
auto* group = verible::GroupLeafWithPreviousLeaf(&last);
group->Value().SetPartitionPolicy(PartitionPolicyEnum::kStack);
} else {
verible::MergeLeafIntoPreviousLeaf(&last);
}
}
} else if (policy == PartitionPolicyEnum::kStack) {
MacroCallReshaper(style, &partition).Reshape();
}
break;
}
case NodeEnum::kAssertionVariableDeclarationList: {
AttachTrailingSemicolonToPreviousPartition(&partition);
break;
}
case NodeEnum::kArgumentList: {
SetCommentLinePartitionAsAlreadyFormatted(&partition);
AttachSeparatorsToListElementPartitions(&partition);
break;
}
case NodeEnum::kMacroArgList: {
SetCommentLinePartitionAsAlreadyFormatted(&partition);
AttachSeparatorsToListElementPartitions(&partition);
break;
}
case NodeEnum::kParenGroup: {
SetCommentLinePartitionAsAlreadyFormatted(&partition);
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_iter = std::find_if(
partition.Children().begin(), partition.Children().end(),
[](const TokenPartitionTree& n) {
const auto* origin = n.Value().Origin();
return origin && origin->Tag() ==
verible::LeafTag(verilog_tokentype::TK_if);
});
if (if_header_partition_iter == partition.Children().end()) break;
// Adjust indentation of all partitions following if header recursively
for (auto& child : make_range(if_header_partition_iter + 1,
partition.Children().end())) {
verible::AdjustIndentationRelative(&child, style.wrap_spaces);
}
}
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, style_);
break;
}
case NodeEnum::kPreprocessorDefine: {
auto& last = RightmostDescendant(partition);
// TODO(fangism): why does test fail without this clause?
if (PartitionStartsWithCloseParen(last)) {
verible::MergeLeafIntoPreviousLeaf(&last);
}
break;
}
case NodeEnum::kConstraintDeclaration: {
// TODO(fangism): kConstraintSet should be handled similarly with {}
if (partition.Children().size() == 2) {
auto& last = RightmostDescendant(partition);
if (PartitionIsCloseBrace(last)) {
verible::MergeLeafIntoPreviousLeaf(&last);
}
}
break;
}
case NodeEnum::kConstraintBlockItemList: {
HoistOnlyChildPartition(&partition);
// Alwyas expand constraint(s) blocks with braces inside them
const auto& uwline = partition.Value();
const auto& ftokens = uwline.TokensRange();
auto found = std::find_if(ftokens.begin(), ftokens.end(),
[](const verible::PreFormatToken& token) {
return token.TokenEnum() == '{';
});
if (found != ftokens.end()) {
VLOG(4) << "Found brace group, forcing expansion";
partition.Value().SetPartitionPolicy(
PartitionPolicyEnum::kAlwaysExpand);
}
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 &&
verible::is_leaf(children.front()) /* left side */ &&
!PartitionIsForcedIntoNewLine(children.back())) {
verible::MergeLeafIntoNextLeaf(&children.front());
VLOG(4) << "after merge leaf (left-into-right):\n" << partition;
}
break;
}
case NodeEnum::kProceduralTimingControlStatement: {
std::vector<size_t> offsets;
FlattenOnlyChildrenWithChildren(partition, &offsets);
VLOG(4) << "before moving semicolon:\n" << partition;
AttachTrailingSemicolonToPreviousPartition(&partition);
// Check body, for kSeqBlock, merge 'begin' with previous sibling
if (const auto* tbody = GetProceduralTimingControlStatementBody(node);
tbody != nullptr && NodeIsBeginEndBlock(*tbody)) {
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.
FlattenOnlyChildrenWithChildren(partition);
VLOG(4) << "after flatten:\n" << partition;
AttachTrailingSemicolonToPreviousPartition(&partition);
AttachSeparatorsToListElementPartitions(&partition);
// Merge the 'assign' keyword with the (first) x=y assignment.
// TODO(fangism): reshape for multiple assignments.
verible::MergeLeafIntoNextLeaf(&partition.Children().front());
VLOG(4) << "after merging 'assign':\n" << partition;
AdjustSubsequentPartitionsIndentation(&partition, style.wrap_spaces);
VLOG(4) << "after adjusting partitions indentation:\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.
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 child " << dist1 << " and " << dist2;
// Merge from back-to-front to keep indices valid.
if (dist2 > 0 && (dist2 - dist1 > 1)) {
verible::MergeLeafIntoPreviousLeaf(&*iter2);
}
if (dist1 > 0) {
verible::MergeLeafIntoPreviousLeaf(&*iter1);
}
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: {
// Check if 'initial' is separated from 'begin' by EOL_COMMENT. If so,
// adjust indentation and do not merge leaf into previous leaf.
const verible::UnwrappedLine& unwrapped_line = partition.Value();
verible::FormatTokenRange tokenrange = unwrapped_line.TokensRange();
auto token_tmp = tokenrange.begin();
if (token_tmp->TokenEnum() == TK_initial &&
(++token_tmp)->TokenEnum() == TK_EOL_COMMENT &&
(++token_tmp)->TokenEnum() == TK_begin) {
AdjustIndentationRelative(
&partition.Children()[(partition.Children().size() - 1)],
style.indentation_spaces);
break;
}
// In these cases, merge the 'begin' partition of the statement block
// with the preceding keyword or header partition.
if (!verible::is_leaf(partition.Children().back()) &&
NodeIsBeginEndBlock(verible::SymbolCastToNode(*node.back()))) {
auto& seq_block_partition = partition.Children().back();
VLOG(4) << "block partition: " << seq_block_partition;
auto& begin_partition = LeftmostDescendant(seq_block_partition);
VLOG(4) << "begin partition: " << begin_partition;
CHECK(is_leaf(begin_partition));
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.
FlattenOneChild(partition, verible::BirthRank(seq_block_partition));
}
break;
}
case NodeEnum::kDoWhileLoopStatement: {
if (const auto* dw = GetDoWhileStatementBody(node);
dw != nullptr && NodeIsBeginEndBlock(*dw)) {
// between do... and while (...);
auto& seq_block_partition = partition.Children()[1];
// merge "do" <- "begin"
auto& begin_partition = LeftmostDescendant(seq_block_partition);
verible::MergeLeafIntoPreviousLeaf(&begin_partition);
// merge "end" -> "while"
auto& end_partition = RightmostDescendant(seq_block_partition);
verible::MergeLeafIntoNextLeaf(&end_partition);
// Flatten only the statement block so that the control partition
// can retain its own partition policy.
FlattenOneChild(partition, verible::BirthRank(seq_block_partition));
}
break;
}
case NodeEnum::kAlwaysStatement: {
if (GetSubtreeAsNode(node, tag, node.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 (token && absl::StartsWith(token->text(), "`uvm_") &&
absl::EndsWith(token->text(), "_end")) {
VLOG(4) << "Found `uvm_*_end macro call";
IndentBetweenUVMBeginEndMacros(&partition, style.indentation_spaces);
}
break;
}
case NodeEnum::kGateInstanceRegisterVariableList:
// parent: kDataDeclaration
skip_singleton_hoisting = true;
[[fallthrough]];
case NodeEnum::kActualParameterByNameList:
case NodeEnum::kDistributionItemList:
case NodeEnum::kEnumNameList:
case NodeEnum::kFormalParameterList:
case NodeEnum::kOpenRangeList:
case NodeEnum::kPortActualList:
case NodeEnum::kPortDeclarationList:
case NodeEnum::kPortList:
case NodeEnum::kVariableDeclarationAssignmentList:
case NodeEnum::kMacroFormalParameterList: {
AttachSeparatorsToListElementPartitions(&partition);
break;
}
case NodeEnum::kModportDeclaration: {
AttachSeparatorsToListElementPartitions(&partition);
break;
}
case NodeEnum::kExpressionList:
case NodeEnum::kUntagged: {
AttachSeparatorsToListElementPartitions(&partition);
skip_singleton_hoisting = true;
break;
}
case NodeEnum::kParamDeclaration: {
AttachTrailingSemicolonToPreviousPartition(&partition);
AttachOpeningBraceToDeclarationsAssignmentOperator(&partition);
break;
}
case NodeEnum::kAssignmentPattern: {
FlattenOnlyChildrenWithChildren(partition);
break;
}
case NodeEnum::kPatternExpression: {
if (partition.Children().size() >= 3) {
auto& colon = partition.Children()[1];
AttachSeparatorToPreviousOrNextPartition(&colon);
}
FlattenOnlyChildrenWithChildren(partition);
AttachOpeningBraceToDeclarationsAssignmentOperator(&partition);
break;
}
case NodeEnum::kRegisterVariable:
case NodeEnum::kInstantiationType: {
size_t partition_size = partition.Children().size();
// Partition with complex expressions inside dimensions have at least 4
// children, so they are filtered
if (partition_size < 4) break;
// Then either packed or unpacked dimensions are extracted from their
// positions
auto subnode = verible::GetSubtreeAsNode(node, tag, 1);
if (!subnode ||
NodeEnum(subnode->Tag().tag) != NodeEnum::kUnpackedDimensions) {
subnode = verible::GetSubtreeAsNode(node, tag, 0);
subnode = verible::GetSubtreeAsNode(*subnode, subnode->Tag().tag, 3);
if (!subnode ||
NodeEnum(subnode->Tag().tag) != NodeEnum::kPackedDimensions) {
break;
}
}
// Next the colon is located and attached to the first dimension
auto& colon = partition.Children()[partition_size - 3];
// And the colon is checked to be an actual colon
if (colon.Value().TokensRange().begin()->Text() != ":") break;
AttachSeparatorToPreviousOrNextPartition(&colon);
// And if everything went smoothly, the latter dimensions are attached to
// their predecessors
verible::MergeLeafIntoPreviousLeaf(
&partition.Children()[partition_size - 2]);
// And flattened so that the breaks contain the whole dimension range in
// one line
verible::FlattenOneChild(partition, partition_size - 3);
verible::MergeLeafIntoPreviousLeaf(
&partition.Children()[partition_size - 2]);
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.
if (!skip_singleton_hoisting) {
HoistOnlyChildPartition(&partition);
}
VLOG(4) << "after reshaping " << tag << ":\n"
<< VerilogPartitionPrinter(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"
<< VerilogPartitionPrinter(partition);
// Advances NextUnfilteredToken(), and extends CurrentUnwrappedLine().
// Should be equivalent to AdvanceNextUnfilteredToken().
AddTokenToCurrentUnwrappedLine();
// Make sure that newly extended unwrapped line has origin symbol
// FIXME: Pretty sure that this should be handled in an other way,
// e.g. in common/formatting/tree_unwrapper.cc similarly
// to StartNewUnwrappedLine()
if (CurrentUnwrappedLine().Origin() == nullptr &&
CurrentUnwrappedLine().TokensRange().size() == 1) {
CurrentUnwrappedLine().SetOrigin(&leaf);
}
// 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"
<< VerilogPartitionPrinter(partition);
// Post-token-handling token partition adjustments:
switch (leaf.Tag().tag) {
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(4) << "after reshaping " << VerboseToken(leaf.get()) << ":\n"
<< VerilogPartitionPrinter(partition);
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