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