| // 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/align.h" | |
| #include <algorithm> | |
| #include <functional> | |
| #include <iostream> | |
| #include <iterator> | |
| #include <map> | |
| #include <vector> | |
| #include "common/formatting/align.h" | |
| #include "common/formatting/format_token.h" | |
| #include "common/formatting/token_partition_tree.h" | |
| #include "common/formatting/unwrapped_line.h" | |
| #include "common/text/concrete_syntax_leaf.h" | |
| #include "common/text/concrete_syntax_tree.h" | |
| #include "common/text/symbol.h" | |
| #include "common/text/token_info.h" | |
| #include "common/text/tree_utils.h" | |
| #include "common/util/casts.h" | |
| #include "common/util/logging.h" | |
| #include "common/util/value_saver.h" | |
| #include "verilog/CST/context_functions.h" | |
| #include "verilog/CST/declaration.h" | |
| #include "verilog/CST/verilog_nonterminals.h" | |
| #include "verilog/parser/verilog_token_classifications.h" | |
| #include "verilog/parser/verilog_token_enum.h" | |
| namespace verilog { | |
| namespace formatter { | |
| using verible::AlignablePartitionGroup; | |
| using verible::AlignedPartitionClassification; | |
| using verible::AlignmentCellScannerGenerator; | |
| using verible::AlignmentColumnProperties; | |
| using verible::AlignmentGroupAction; | |
| using verible::ByteOffsetSet; | |
| using verible::ColumnSchemaScanner; | |
| using verible::down_cast; | |
| using verible::ExtractAlignmentGroupsFunction; | |
| using verible::FormatTokenRange; | |
| using verible::PreFormatToken; | |
| using verible::Symbol; | |
| using verible::SyntaxTreeLeaf; | |
| using verible::SyntaxTreeNode; | |
| using verible::SyntaxTreePath; | |
| using verible::TaggedTokenPartitionRange; | |
| using verible::TokenPartitionRange; | |
| using verible::TokenPartitionTree; | |
| using verible::TreePathFormatter; | |
| using verible::ValueSaver; | |
| static constexpr AlignmentColumnProperties FlushLeft(true); | |
| static constexpr AlignmentColumnProperties FlushRight(false); | |
| static const SyntaxTreePath::value_type kLeadingNonTreeTokenPathIndex = -1; | |
| static const SyntaxTreePath::value_type kTrailingNonTreeTokenPathIndex = | |
| std::numeric_limits<SyntaxTreePath::value_type>::max(); | |
| // Maximum SyntaxTreePath index available for tree tokens | |
| static const SyntaxTreePath::value_type kMaxPathIndex = | |
| std::numeric_limits<SyntaxTreePath::value_type>::max() - 1; | |
| template <class T> | |
| static bool TokensAreAllCommentsOrAttributes(const T& tokens) { | |
| return std::all_of( | |
| tokens.begin(), tokens.end(), [](const typename T::value_type& token) { | |
| const auto tag = static_cast<verilog_tokentype>(token.TokenEnum()); | |
| return IsComment(verilog_tokentype(tag)) || | |
| tag == verilog_tokentype::TK_ATTRIBUTE; | |
| }); | |
| } | |
| template <class T> | |
| static bool TokensHaveParenthesis(const T& tokens) { | |
| return std::any_of(tokens.begin(), tokens.end(), | |
| [](const typename T::value_type& token) { | |
| return token.TokenEnum() == '('; | |
| }); | |
| } | |
| static bool IgnoreCommentsAndPreprocessingDirectives( | |
| const TokenPartitionTree& partition) { | |
| const auto& uwline = partition.Value(); | |
| // ignore partitions with only non-tree tokens (comments, comma-only lines) | |
| if (!uwline.Origin()) return true; | |
| const auto token_range = uwline.TokensRange(); | |
| CHECK(!token_range.empty()); | |
| // ignore lines containing only comments | |
| if (TokensAreAllCommentsOrAttributes(token_range)) return true; | |
| // ignore partitions belonging to preprocessing directives | |
| if (IsPreprocessorKeyword(verilog_tokentype(token_range.front().TokenEnum()))) | |
| return true; | |
| return false; | |
| } | |
| static bool IgnoreWithinPortDeclarationPartitionGroup( | |
| const TokenPartitionTree& partition) { | |
| const auto& uwline = partition.Value(); | |
| const auto token_range = uwline.TokensRange(); | |
| CHECK(!token_range.empty()); | |
| if (IgnoreCommentsAndPreprocessingDirectives(partition)) return true; | |
| // Ignore .x or .x(x) port declarations. | |
| // These can appear in a list_of_port_or_port_declarations. | |
| CHECK_NOTNULL(uwline.Origin()); | |
| return uwline.Origin()->Kind() == verible::SymbolKind::kNode && | |
| verible::SymbolCastToNode(*uwline.Origin()) | |
| .MatchesTag(NodeEnum::kPort); | |
| } | |
| static bool IgnoreWithinStructUnionMemberPartitionGroup( | |
| const TokenPartitionTree& partition) { | |
| const auto& uwline = partition.Value(); | |
| const auto token_range = uwline.TokensRange(); | |
| CHECK(!token_range.empty()); | |
| // TODO(mglb): Verify whether `IgnoreCommentsAndPreprocessingDirectives` can | |
| // be used instead of a direct comments/preprocessor tests (like in other | |
| // IgnoreWithin* functions). If so, use it. | |
| // ignore lines containing only comments | |
| if (TokensAreAllCommentsOrAttributes(token_range)) { | |
| return true; | |
| } | |
| // ignore partitions belonging to preprocessing directives | |
| if (IsPreprocessorKeyword( | |
| verilog_tokentype(token_range.front().TokenEnum()))) { | |
| return true; | |
| } | |
| // ignore nested structs/unions | |
| if (verible::FindFirstSubtree( | |
| partition.Value().Origin(), [](const Symbol& symbol) { | |
| return symbol.Tag() == | |
| verible::NodeTag(NodeEnum::kStructUnionMemberList); | |
| }) != nullptr) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| static bool IgnoreWithinActualNamedParameterPartitionGroup( | |
| const TokenPartitionTree& partition) { | |
| if (IgnoreCommentsAndPreprocessingDirectives(partition)) return true; | |
| // ignore everything that isn't passing a parameter by name | |
| const auto& uwline = partition.Value(); | |
| CHECK_NOTNULL(uwline.Origin()); | |
| return !(uwline.Origin()->Kind() == verible::SymbolKind::kNode && | |
| verible::SymbolCastToNode(*uwline.Origin()) | |
| .MatchesTag(NodeEnum::kParamByName)); | |
| } | |
| static bool IgnoreWithinActualNamedPortPartitionGroup( | |
| const TokenPartitionTree& partition) { | |
| if (IgnoreCommentsAndPreprocessingDirectives(partition)) return true; | |
| const auto& uwline = partition.Value(); | |
| const auto token_range = uwline.TokensRange(); | |
| // ignore wildcard connections .* | |
| if (verilog_tokentype(token_range.front().TokenEnum()) == | |
| verilog_tokentype::TK_DOTSTAR) { | |
| return true; | |
| } | |
| CHECK_NOTNULL(uwline.Origin()); | |
| if (uwline.Origin()->Kind() != verible::SymbolKind::kNode) return true; | |
| // ignore implicit connections .aaa | |
| if (verible::SymbolCastToNode(*uwline.Origin()) | |
| .MatchesTag(NodeEnum::kActualNamedPort) && | |
| !TokensHaveParenthesis(token_range)) { | |
| return true; | |
| } | |
| // ignore positional port connections | |
| if (verible::SymbolCastToNode(*uwline.Origin()) | |
| .MatchesTag(NodeEnum::kActualPositionalPort)) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| static bool TokenForcesLineBreak(const PreFormatToken& ftoken) { | |
| switch (ftoken.TokenEnum()) { | |
| case verilog_tokentype::TK_begin: | |
| case verilog_tokentype::TK_fork: | |
| return true; | |
| } | |
| return false; | |
| } | |
| static bool IgnoreMultilineCaseStatements(const TokenPartitionTree& partition) { | |
| if (IgnoreCommentsAndPreprocessingDirectives(partition)) return true; | |
| const auto& uwline = partition.Value(); | |
| const auto token_range = uwline.TokensRange(); | |
| // Scan for any tokens that would force a line break. | |
| return std::any_of(token_range.begin(), token_range.end(), | |
| &TokenForcesLineBreak); | |
| } | |
| class VerilogColumnSchemaScanner : public ColumnSchemaScanner { | |
| public: | |
| explicit VerilogColumnSchemaScanner(const FormatStyle& style) | |
| : style_(style) {} | |
| protected: | |
| const FormatStyle& style_; | |
| }; | |
| template <class ScannerType> | |
| std::function<verible::AlignmentCellScannerFunction(const FormatStyle&)> | |
| UnstyledAlignmentCellScannerGenerator() { | |
| return [](const FormatStyle& vstyle) { | |
| return AlignmentCellScannerGenerator<ScannerType>( | |
| [vstyle] { return ScannerType(vstyle); }); | |
| }; | |
| } | |
| template <class ScannerType> | |
| std::function<verible::AlignmentCellScannerFunction(const FormatStyle&)> | |
| UnstyledAlignmentCellScannerGenerator( | |
| const verible::NonTreeTokensScannerFunction& non_tree_column_scanner) { | |
| return [non_tree_column_scanner](const FormatStyle& vstyle) { | |
| return AlignmentCellScannerGenerator<ScannerType>( | |
| [vstyle] { return ScannerType(vstyle); }, non_tree_column_scanner); | |
| }; | |
| } | |
| // This class marks up token-subranges in named parameter assignments for | |
| // alignment. e.g. ".parameter_name(value_expression)" | |
| class ActualNamedParameterColumnSchemaScanner | |
| : public VerilogColumnSchemaScanner { | |
| public: | |
| explicit ActualNamedParameterColumnSchemaScanner(const FormatStyle& style) | |
| : VerilogColumnSchemaScanner(style) {} | |
| void Visit(const SyntaxTreeNode& node) final { | |
| auto tag = NodeEnum(node.Tag().tag); | |
| VLOG(2) << __FUNCTION__ << ", node: " << tag << " at " | |
| << TreePathFormatter(Path()); | |
| switch (tag) { | |
| case NodeEnum::kParamByName: { | |
| // Always start first column right away | |
| ReserveNewColumn(node, FlushLeft); | |
| break; | |
| } | |
| case NodeEnum::kParenGroup: | |
| // Second column starts at the open parenthesis. | |
| if (Context().DirectParentIs(NodeEnum::kParamByName)) { | |
| ReserveNewColumn(node, FlushLeft); | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| TreeContextPathVisitor::Visit(node); | |
| VLOG(2) << __FUNCTION__ << ", leaving node: " << tag; | |
| } | |
| }; | |
| // This class marks up token-subranges in named port connections for alignment. | |
| // e.g. ".port_name(net_name)" | |
| class ActualNamedPortColumnSchemaScanner : public VerilogColumnSchemaScanner { | |
| public: | |
| explicit ActualNamedPortColumnSchemaScanner(const FormatStyle& style) | |
| : VerilogColumnSchemaScanner(style) {} | |
| void Visit(const SyntaxTreeNode& node) final { | |
| auto tag = NodeEnum(node.Tag().tag); | |
| VLOG(2) << __FUNCTION__ << ", node: " << tag << " at " | |
| << TreePathFormatter(Path()); | |
| switch (tag) { | |
| case NodeEnum::kActualNamedPort: { | |
| // Always start first column right away | |
| ReserveNewColumn(node, FlushLeft); | |
| break; | |
| } | |
| case NodeEnum::kParenGroup: | |
| // Second column starts at the open parenthesis. | |
| if (Context().DirectParentIs(NodeEnum::kActualNamedPort)) { | |
| ReserveNewColumn(node, FlushLeft); | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| TreeContextPathVisitor::Visit(node); | |
| VLOG(2) << __FUNCTION__ << ", leaving node: " << tag; | |
| } | |
| }; | |
| // This class marks up token-subranges in port declarations for alignment. | |
| // e.g. "input wire clk," | |
| class PortDeclarationColumnSchemaScanner : public VerilogColumnSchemaScanner { | |
| public: | |
| explicit PortDeclarationColumnSchemaScanner(const FormatStyle& style) | |
| : VerilogColumnSchemaScanner(style) {} | |
| void Visit(const SyntaxTreeNode& node) final { | |
| auto tag = NodeEnum(node.Tag().tag); | |
| VLOG(2) << __FUNCTION__ << ", node: " << tag << " at " | |
| << TreePathFormatter(Path()); | |
| switch (tag) { | |
| case NodeEnum::kPackedDimensions: { | |
| // Kludge: kPackedDimensions can appear in paths | |
| // [1,0,3] inside a kNetDeclaration and at | |
| // [1,0,0,3] inside a kDataDeclaration, | |
| // but we want them to line up in the same column. Make it so. | |
| // TODO(fangism): a swap-based saver would be more efficient | |
| // for vectors. | |
| SyntaxTreePath new_path; | |
| if (current_path_ == SyntaxTreePath{1, 0, 3}) | |
| new_path = {1, 0, 0, 3}; | |
| else | |
| new_path = Path(); | |
| const ValueSaver<SyntaxTreePath> path_saver(¤t_path_, new_path); | |
| // Left border is removed from each dimension subcolumn. | |
| // Adding it here creates one space before first column. | |
| static const verible::AlignmentColumnProperties single_left_border(true, | |
| 1); | |
| current_dimensions_group_ = ReserveNewColumn(node, single_left_border); | |
| TreeContextPathVisitor::Visit(node); | |
| current_dimensions_group_ = nullptr; | |
| return; | |
| } | |
| case NodeEnum::kUnpackedDimensions: { | |
| current_dimensions_group_ = ReserveNewColumn(node, FlushLeft); | |
| TreeContextPathVisitor::Visit(node); | |
| current_dimensions_group_ = nullptr; | |
| return; | |
| } | |
| case NodeEnum::kDimensionRange: | |
| case NodeEnum::kDimensionSlice: { | |
| CHECK_NOTNULL(current_dimensions_group_); | |
| CHECK_EQ(node.children().size(), 5); | |
| SyntaxTreePath dimension_path = Path(); | |
| const bool right_align = | |
| Context().IsInside(NodeEnum::kPackedDimensions) | |
| ? style_.port_declarations_right_align_packed_dimensions | |
| : style_.port_declarations_right_align_unpacked_dimensions; | |
| if (right_align) { | |
| dimension_path.back() += | |
| kMaxPathIndex - Context().top().children().size(); | |
| } | |
| const verible::AlignmentColumnProperties no_border(false, 0); | |
| auto* column = ABSL_DIE_IF_NULL(ReserveNewColumn( | |
| current_dimensions_group_, node, | |
| right_align ? no_border : FlushLeft, dimension_path)); | |
| ReserveNewColumn(column, *node[0], | |
| right_align ? no_border : FlushLeft); // '[' | |
| ReserveNewColumn(column, *node[1], FlushRight); // value | |
| ReserveNewColumn(column, *node[4], FlushLeft); // ']' | |
| return; | |
| } | |
| case NodeEnum::kDimensionScalar: | |
| case NodeEnum::kDimensionAssociativeType: { | |
| CHECK_NOTNULL(current_dimensions_group_); | |
| CHECK_EQ(node.children().size(), 3); | |
| SyntaxTreePath dimension_path = Path(); | |
| const bool right_align = | |
| Context().IsInside(NodeEnum::kPackedDimensions) | |
| ? style_.port_declarations_right_align_packed_dimensions | |
| : style_.port_declarations_right_align_unpacked_dimensions; | |
| if (right_align) { | |
| dimension_path.back() += | |
| kMaxPathIndex - Context().top().children().size(); | |
| } | |
| const verible::AlignmentColumnProperties no_border(false, 0); | |
| auto* column = ABSL_DIE_IF_NULL(ReserveNewColumn( | |
| current_dimensions_group_, node, | |
| right_align ? no_border : FlushLeft, dimension_path)); | |
| const auto& column_path = column->Value().path; | |
| // Value can be empty - set paths explicitly | |
| ReserveNewColumn(column, *node[0], right_align ? no_border : FlushLeft, | |
| GetSubpath(column_path, {0})); // '[' | |
| ReserveNewColumn(column, *node[1], FlushRight, | |
| GetSubpath(column_path, {1})); // value | |
| ReserveNewColumn(column, *node[2], FlushLeft, | |
| GetSubpath(column_path, {2})); // ']' | |
| return; | |
| } | |
| case NodeEnum::kDataType: | |
| // appears in path [2,0] | |
| ReserveNewColumn(node, FlushLeft); | |
| break; | |
| case NodeEnum::kUnqualifiedId: | |
| if (Context().DirectParentIs(NodeEnum::kPortDeclaration) || | |
| Context().DirectParentsAre( | |
| {NodeEnum::kDataTypeImplicitBasicIdDimensions, | |
| NodeEnum::kPortItem})) { | |
| ReserveNewColumn(node, FlushLeft); | |
| } | |
| break; | |
| case NodeEnum::kExpression: | |
| // optional: Early termination of tree traversal. | |
| // This also helps reduce noise during debugging of this visitor. | |
| return; | |
| // case NodeEnum::kConstRef: possible in CST, but should be | |
| // syntactically illegal in module ports context. | |
| default: | |
| break; | |
| } | |
| // recursive visitation | |
| TreeContextPathVisitor::Visit(node); | |
| VLOG(2) << __FUNCTION__ << ", leaving node: " << tag; | |
| } | |
| void Visit(const SyntaxTreeLeaf& leaf) final { | |
| VLOG(2) << __FUNCTION__ << ", leaf: " << leaf.get() << " at " | |
| << TreePathFormatter(Path()); | |
| const int tag = leaf.get().token_enum(); | |
| switch (tag) { | |
| // port directions | |
| case verilog_tokentype::TK_inout: | |
| case verilog_tokentype::TK_input: | |
| case verilog_tokentype::TK_output: | |
| case verilog_tokentype::TK_ref: { | |
| ReserveNewColumn(leaf, FlushLeft); | |
| break; | |
| } | |
| // net types | |
| case verilog_tokentype::TK_wire: | |
| case verilog_tokentype::TK_tri: | |
| case verilog_tokentype::TK_tri1: | |
| case verilog_tokentype::TK_supply0: | |
| case verilog_tokentype::TK_wand: | |
| case verilog_tokentype::TK_triand: | |
| case verilog_tokentype::TK_tri0: | |
| case verilog_tokentype::TK_supply1: | |
| case verilog_tokentype::TK_wor: | |
| case verilog_tokentype::TK_trior: | |
| case verilog_tokentype::TK_wone: | |
| case verilog_tokentype::TK_uwire: { | |
| // Effectively merge/re-map this into the next node slot, | |
| // which is kDataType of kPortDeclaration. | |
| // This works-around a quirk in the CST construction where net_types | |
| // like 'wire' appear positionally before kDataType variable types | |
| // like 'reg'. | |
| ReserveNewColumn(leaf, FlushLeft, verible::NextSiblingPath(Path())); | |
| break; | |
| } | |
| // TODO(b/70310743): Treat "[...:...]" as 5 columns. | |
| // Treat "[...]" (scalar) as 3 columns. | |
| // TODO(b/70310743): Treat the ... as a multi-column cell w.r.t. | |
| // the 5-column range format. | |
| default: | |
| break; | |
| } | |
| VLOG(2) << __FUNCTION__ << ", leaving leaf: " << leaf.get(); | |
| } | |
| private: | |
| verible::ColumnPositionTree* current_dimensions_group_ = nullptr; | |
| }; | |
| // This class marks up token-subranges in struct/union members for alignment. | |
| // e.g. bit [31:0] member_name; | |
| class StructUnionMemberColumnSchemaScanner : public VerilogColumnSchemaScanner { | |
| public: | |
| explicit StructUnionMemberColumnSchemaScanner(const FormatStyle& style) | |
| : VerilogColumnSchemaScanner(style) {} | |
| void Visit(const SyntaxTreeNode& node) final { | |
| auto tag = NodeEnum(node.Tag().tag); | |
| VLOG(2) << __FUNCTION__ << ", node: " << tag << " at " | |
| << TreePathFormatter(Path()); | |
| switch (tag) { | |
| case NodeEnum::kStructUnionMember: { | |
| ReserveNewColumn(node, FlushLeft); | |
| break; | |
| } | |
| case NodeEnum::kTrailingAssign: { | |
| ReserveNewColumn(node, FlushLeft); | |
| break; | |
| } | |
| case NodeEnum::kVariableDeclarationAssignmentList: | |
| case NodeEnum::kVariableDeclarationAssignment: | |
| case NodeEnum::kDataTypeImplicitIdDimensions: | |
| break; | |
| default: | |
| return; | |
| } | |
| // recursive visitation | |
| TreeContextPathVisitor::Visit(node); | |
| VLOG(2) << __FUNCTION__ << ", leaving node: " << tag; | |
| } | |
| void Visit(const SyntaxTreeLeaf& leaf) final { | |
| VLOG(2) << __FUNCTION__ << ", leaf: " << leaf.get() << " at " | |
| << TreePathFormatter(Path()); | |
| const int tag = leaf.get().token_enum(); | |
| switch (tag) { | |
| case verilog_tokentype::SymbolIdentifier: | |
| case verilog_tokentype::EscapedIdentifier: { | |
| // Member ID in kDataTypeImplicitIdDimensions can be at [1] or [2]. | |
| if (current_path_ == SyntaxTreePath{1, 1}) { | |
| SyntaxTreePath new_path{1, 2}; | |
| const ValueSaver<SyntaxTreePath> path_saver(¤t_path_, new_path); | |
| ReserveNewColumn(leaf, FlushLeft); | |
| } else { | |
| ReserveNewColumn(leaf, FlushLeft); | |
| } | |
| break; | |
| } | |
| } | |
| VLOG(2) << __FUNCTION__ << ", leaving leaf: " << leaf.get(); | |
| } | |
| }; | |
| static bool IsAlignableDeclaration(const SyntaxTreeNode& node) { | |
| // A data/net/variable declaration is alignable if: | |
| // * it is not a module instance | |
| // * it declares exactly one identifier | |
| switch (static_cast<NodeEnum>(node.Tag().tag)) { | |
| case NodeEnum::kDataDeclaration: { | |
| const SyntaxTreeNode* instances(GetInstanceListFromDataDeclaration(node)); | |
| if (!instances) return false; | |
| if (FindAllRegisterVariables(*instances).size() > 1) return false; | |
| return FindAllGateInstances(*instances).empty(); | |
| } | |
| case NodeEnum::kNetDeclaration: { | |
| return FindAllNetVariables(node).size() <= 1; | |
| } | |
| default: | |
| return false; | |
| } | |
| } | |
| // These enums classify alignable groups of token partitions by their syntax | |
| // structure, which then map to different alignment handler routines. | |
| // These need not have a 1:1 correspondence to verilog::NodeEnum syntax tree | |
| // enums, a single value here could apply to a group of syntax tree node types. | |
| enum class AlignableSyntaxSubtype { | |
| kDontCare = 0, | |
| kNamedActualParameters, | |
| kNamedActualPorts, | |
| kParameterDeclaration, | |
| kPortDeclaration, | |
| kStructUnionMember, | |
| kDataDeclaration, // net/variable declarations | |
| kClassMemberVariables, | |
| kCaseLikeItems, | |
| kContinuousAssignment, | |
| kEnumListAssignment, // Constants aligned in enums. | |
| kBlockingAssignment, | |
| kNonBlockingAssignment, | |
| kDistItem, // Distribution items. | |
| }; | |
| static AlignedPartitionClassification AlignClassify( | |
| AlignmentGroupAction match, | |
| AlignableSyntaxSubtype subtype = AlignableSyntaxSubtype::kDontCare) { | |
| if (match == AlignmentGroupAction::kMatch) { | |
| CHECK(subtype != AlignableSyntaxSubtype::kDontCare); | |
| } | |
| return {match, static_cast<int>(subtype)}; | |
| } | |
| static std::vector<TaggedTokenPartitionRange> GetConsecutiveModuleItemGroups( | |
| const TokenPartitionRange& partitions) { | |
| VLOG(2) << __FUNCTION__; | |
| return GetPartitionAlignmentSubranges( | |
| partitions, // | |
| [](const TokenPartitionTree& partition) | |
| -> AlignedPartitionClassification { | |
| const Symbol* origin = partition.Value().Origin(); | |
| if (origin == nullptr) return {AlignmentGroupAction::kIgnore}; | |
| const verible::SymbolTag symbol_tag = origin->Tag(); | |
| if (symbol_tag.kind != verible::SymbolKind::kNode) | |
| return AlignClassify(AlignmentGroupAction::kIgnore); | |
| const SyntaxTreeNode& node = verible::SymbolCastToNode(*origin); | |
| // Align net/variable declarations. | |
| if (IsAlignableDeclaration(node)) { | |
| return AlignClassify(AlignmentGroupAction::kMatch, | |
| AlignableSyntaxSubtype::kDataDeclaration); | |
| } | |
| // Align continuous assignment, like "assign foo = bar;" | |
| if (node.MatchesTag(NodeEnum::kContinuousAssignmentStatement)) { | |
| return AlignClassify(AlignmentGroupAction::kMatch, | |
| AlignableSyntaxSubtype::kContinuousAssignment); | |
| } | |
| return AlignClassify(AlignmentGroupAction::kNoMatch); | |
| }); | |
| } | |
| static std::vector<TaggedTokenPartitionRange> GetConsecutiveClassItemGroups( | |
| const TokenPartitionRange& partitions) { | |
| VLOG(2) << __FUNCTION__; | |
| return GetPartitionAlignmentSubranges( | |
| partitions, // | |
| [](const TokenPartitionTree& partition) | |
| -> AlignedPartitionClassification { | |
| const Symbol* origin = partition.Value().Origin(); | |
| if (origin == nullptr) return {AlignmentGroupAction::kIgnore}; | |
| const verible::SymbolTag symbol_tag = origin->Tag(); | |
| if (symbol_tag.kind != verible::SymbolKind::kNode) | |
| return {AlignmentGroupAction::kIgnore}; | |
| const SyntaxTreeNode& node = verible::SymbolCastToNode(*origin); | |
| // Align class member variables. | |
| return AlignClassify(IsAlignableDeclaration(node) | |
| ? AlignmentGroupAction::kMatch | |
| : AlignmentGroupAction::kNoMatch, | |
| AlignableSyntaxSubtype::kClassMemberVariables); | |
| }); | |
| } | |
| static std::vector<TaggedTokenPartitionRange> GetAlignableStatementGroups( | |
| const TokenPartitionRange& partitions) { | |
| VLOG(2) << __FUNCTION__; | |
| return GetPartitionAlignmentSubranges( | |
| partitions, // | |
| [](const TokenPartitionTree& partition) | |
| -> AlignedPartitionClassification { | |
| const Symbol* origin = partition.Value().Origin(); | |
| if (origin == nullptr) return {AlignmentGroupAction::kIgnore}; | |
| const verible::SymbolTag symbol_tag = origin->Tag(); | |
| if (symbol_tag.kind != verible::SymbolKind::kNode) | |
| return AlignClassify(AlignmentGroupAction::kIgnore); | |
| const SyntaxTreeNode& node = verible::SymbolCastToNode(*origin); | |
| // Align local variable declarations. | |
| if (IsAlignableDeclaration(node)) { | |
| return AlignClassify(AlignmentGroupAction::kMatch, | |
| AlignableSyntaxSubtype::kDataDeclaration); | |
| } | |
| // Align blocking assignments. | |
| if (node.MatchesTagAnyOf({NodeEnum::kBlockingAssignmentStatement, | |
| NodeEnum::kNetVariableAssignment})) { | |
| return AlignClassify(AlignmentGroupAction::kMatch, | |
| AlignableSyntaxSubtype::kBlockingAssignment); | |
| } | |
| // Align nonblocking assignments. | |
| if (node.MatchesTag(NodeEnum::kNonblockingAssignmentStatement)) { | |
| return AlignClassify(AlignmentGroupAction::kMatch, | |
| AlignableSyntaxSubtype::kNonBlockingAssignment); | |
| } | |
| return AlignClassify(AlignmentGroupAction::kNoMatch); | |
| }); | |
| } | |
| // This class marks up token-subranges in data declarations for alignment. | |
| // e.g. "foo_pkg::bar_t [3:0] some_values;" | |
| // Much of the implementation of this scanner was based on | |
| // PortDeclarationColumnSchemaScanner. | |
| // Differences: | |
| // * here, there are no port directions to worry about. | |
| // * need to handle both kDataDeclaration and kNetDeclaration. | |
| // TODO(fangism): refactor out common logic | |
| class DataDeclarationColumnSchemaScanner : public VerilogColumnSchemaScanner { | |
| public: | |
| explicit DataDeclarationColumnSchemaScanner(const FormatStyle& style) | |
| : VerilogColumnSchemaScanner(style) {} | |
| void Visit(const SyntaxTreeNode& node) final { | |
| auto tag = NodeEnum(node.Tag().tag); | |
| VLOG(2) << __FUNCTION__ << ", node: " << tag << " at " | |
| << TreePathFormatter(Path()); | |
| if (new_column_after_open_bracket_) { | |
| ReserveNewColumn(node, FlushRight); | |
| new_column_after_open_bracket_ = false; | |
| TreeContextPathVisitor::Visit(node); | |
| return; | |
| } | |
| switch (tag) { | |
| case NodeEnum::kDataDeclaration: | |
| case NodeEnum::kNetDeclaration: { | |
| // Don't wait for the type node, just start the first column right away. | |
| ReserveNewColumn(node, FlushLeft); | |
| break; | |
| } | |
| case NodeEnum::kPackedDimensions: { | |
| // Kludge: kPackedDimensions can appear in paths: | |
| // [1,0,3] inside a kNetDeclaration and at | |
| // [1,0,0,3] inside a kDataDeclaration, | |
| // but we want them to line up in the same column. Make it so. | |
| if (current_path_ == SyntaxTreePath{1, 0, 0, 3}) { | |
| SyntaxTreePath new_path{1, 0, 3}; | |
| const ValueSaver<SyntaxTreePath> path_saver(¤t_path_, new_path); | |
| TreeContextPathVisitor::Visit(node); | |
| return; | |
| } | |
| break; | |
| } | |
| case NodeEnum::kDimensionRange: | |
| case NodeEnum::kDimensionScalar: | |
| case NodeEnum::kDimensionSlice: | |
| case NodeEnum::kDimensionAssociativeType: { | |
| // all of these cases cover packed and unpacked dimensions | |
| ReserveNewColumn(node, FlushLeft); | |
| break; | |
| } | |
| case NodeEnum::kRegisterVariable: { | |
| // at path [1,1,0] in kDataDeclaration | |
| // contains the declared id | |
| ReserveNewColumn(node, FlushLeft); | |
| break; | |
| } | |
| case NodeEnum::kNetVariable: { | |
| // at path [2,0] in kNetDeclaration | |
| // contains the declared id | |
| // make this fit with kRegisterVariable | |
| if (current_path_ == SyntaxTreePath{2, 0}) { | |
| SyntaxTreePath new_path{1, 1, 0}; | |
| const ValueSaver<SyntaxTreePath> path_saver(¤t_path_, new_path); | |
| ReserveNewColumn(node, FlushLeft); | |
| TreeContextPathVisitor::Visit(node); | |
| return; | |
| } | |
| break; | |
| } | |
| case NodeEnum::kExpression: | |
| // optional: Early termination of tree traversal. | |
| // This also helps reduce noise during debugging of this visitor. | |
| return; | |
| // case NodeEnum::kConstRef: possible in CST, but should be | |
| // syntactically illegal in module ports context. | |
| default: | |
| break; | |
| } | |
| TreeContextPathVisitor::Visit(node); | |
| VLOG(2) << "end of " << __FUNCTION__ << ", node: " << tag; | |
| } | |
| void Visit(const SyntaxTreeLeaf& leaf) final { | |
| VLOG(2) << __FUNCTION__ << ", leaf: " << leaf.get() << " at " | |
| << TreePathFormatter(Path()); | |
| if (new_column_after_open_bracket_) { | |
| ReserveNewColumn(leaf, FlushRight); | |
| new_column_after_open_bracket_ = false; | |
| return; | |
| } | |
| const int tag = leaf.get().token_enum(); | |
| switch (tag) { | |
| // For now, treat [...] as a single column per dimension. | |
| case '[': { | |
| if (ContextAtDeclarationDimensions()) { | |
| // FlushLeft vs. Right doesn't matter, this is a single character. | |
| ReserveNewColumn(leaf, FlushLeft); | |
| new_column_after_open_bracket_ = true; | |
| } | |
| break; | |
| } | |
| case ']': { | |
| if (ContextAtDeclarationDimensions()) { | |
| // FlushLeft vs. Right doesn't matter, this is a single character. | |
| ReserveNewColumn(leaf, FlushLeft); | |
| } | |
| break; | |
| } | |
| // TODO(b/70310743): Treat "[...:...]" as 5 columns. | |
| // Treat "[...]" (scalar) as 3 columns. | |
| // TODO(b/70310743): Treat the ... as a multi-column cell w.r.t. | |
| // the 5-column range format. | |
| default: | |
| break; | |
| } | |
| VLOG(2) << __FUNCTION__ << ", leaving leaf: " << leaf.get(); | |
| } | |
| protected: | |
| bool ContextAtDeclarationDimensions() const { | |
| // Alternatively, could check that grandparent is | |
| // kDeclarationDimensions. | |
| return current_context_.DirectParentIsOneOf( | |
| {NodeEnum::kDimensionRange, NodeEnum::kDimensionScalar, | |
| NodeEnum::kDimensionSlice, NodeEnum::kDimensionAssociativeType}); | |
| } | |
| private: | |
| // Set this to force the next syntax tree node/leaf to start a new column. | |
| // This is useful for aligning after punctation marks. | |
| bool new_column_after_open_bracket_ = false; | |
| }; | |
| // This class marks up token-subranges in class member variable (data | |
| // declarations) for alignment. e.g. "const int [3:0] member_name;" For now, | |
| // re-use the same column scanner as data/variable/net declarations. | |
| class ClassPropertyColumnSchemaScanner : public VerilogColumnSchemaScanner { | |
| public: | |
| explicit ClassPropertyColumnSchemaScanner(const FormatStyle& style) | |
| : VerilogColumnSchemaScanner(style) {} | |
| void Visit(const SyntaxTreeNode& node) final { | |
| auto tag = NodeEnum(node.Tag().tag); | |
| VLOG(2) << __FUNCTION__ << ", node: " << tag << " at " | |
| << TreePathFormatter(Path()); | |
| switch (tag) { | |
| case NodeEnum::kDataDeclaration: | |
| case NodeEnum::kVariableDeclarationAssignment: { | |
| // Don't wait for the type node, just start the first column right away. | |
| ReserveNewColumn(node, FlushLeft); | |
| break; | |
| } | |
| case NodeEnum::kDimensionScalar: { | |
| CHECK_EQ(node.children().size(), 3); | |
| auto* column = ABSL_DIE_IF_NULL(ReserveNewColumn(node, FlushLeft)); | |
| ReserveNewColumn(column, *node[0], FlushLeft); // '[' | |
| ReserveNewColumn(column, *node[1], FlushRight); // value | |
| ReserveNewColumn(column, *node[2], FlushLeft); // ']' | |
| return; | |
| } | |
| case NodeEnum::kDimensionRange: { | |
| CHECK_EQ(node.children().size(), 5); | |
| auto* column = ABSL_DIE_IF_NULL(ReserveNewColumn(node, FlushLeft)); | |
| SyntaxTreePath np; | |
| ReserveNewColumn(column, *node[0], FlushLeft); // '[' | |
| auto* value_subcolumn = | |
| ABSL_DIE_IF_NULL(ReserveNewColumn(column, *node[1], FlushRight)); | |
| ReserveNewColumn(value_subcolumn, *node[1], FlushRight); // LHS value | |
| ReserveNewColumn(value_subcolumn, *node[2], FlushLeft); // ':' | |
| ReserveNewColumn(value_subcolumn, *node[3], FlushRight); // RHS value | |
| ReserveNewColumn(column, *node[4], FlushLeft); // ']' | |
| return; | |
| } | |
| default: | |
| break; | |
| } | |
| TreeContextPathVisitor::Visit(node); | |
| VLOG(2) << "end of " << __FUNCTION__ << ", node: " << tag; | |
| } | |
| void Visit(const SyntaxTreeLeaf& leaf) final { | |
| VLOG(2) << __FUNCTION__ << ", leaf: " << leaf.get() << " at " | |
| << TreePathFormatter(Path()); | |
| const int tag = leaf.get().token_enum(); | |
| switch (tag) { | |
| case '=': | |
| ReserveNewColumn(leaf, FlushLeft); | |
| break; | |
| default: | |
| break; | |
| } | |
| VLOG(2) << __FUNCTION__ << ", leaving leaf: " << leaf.get(); | |
| } | |
| }; | |
| // This class marks up token-subranges in formal parameter declarations for | |
| // alignment. | |
| // e.g. "localparam int Width = 5;" | |
| class ParameterDeclarationColumnSchemaScanner | |
| : public VerilogColumnSchemaScanner { | |
| public: | |
| explicit ParameterDeclarationColumnSchemaScanner(const FormatStyle& style) | |
| : VerilogColumnSchemaScanner(style) {} | |
| void Visit(const SyntaxTreeNode& node) final { | |
| auto tag = NodeEnum(node.Tag().tag); | |
| VLOG(2) << __FUNCTION__ << ", node: " << tag << " at " | |
| << TreePathFormatter(Path()); | |
| if (new_column_after_open_bracket_) { | |
| ReserveNewColumn(node, FlushRight); | |
| new_column_after_open_bracket_ = false; | |
| TreeContextPathVisitor::Visit(node); | |
| return; | |
| } | |
| switch (tag) { | |
| case NodeEnum::kTypeInfo: { | |
| SyntaxTreePath new_path{1}; | |
| const ValueSaver<SyntaxTreePath> path_saver(¤t_path_, new_path); | |
| ReserveNewColumn(node, FlushLeft); | |
| break; | |
| } | |
| case NodeEnum::kTrailingAssign: { | |
| ReserveNewColumn(node, FlushLeft); | |
| break; | |
| } | |
| case NodeEnum::kUnqualifiedId: { | |
| if (Context().DirectParentIs(NodeEnum::kParamType)) { | |
| ReserveNewColumn(node, FlushLeft); | |
| } | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| // recursive visitation | |
| TreeContextPathVisitor::Visit(node); | |
| VLOG(2) << __FUNCTION__ << ", leaving node: " << tag; | |
| } | |
| void Visit(const SyntaxTreeLeaf& leaf) final { | |
| VLOG(2) << __FUNCTION__ << ", leaf: " << leaf.get() << " at " | |
| << TreePathFormatter(Path()); | |
| if (new_column_after_open_bracket_) { | |
| ReserveNewColumn(leaf, FlushRight); | |
| new_column_after_open_bracket_ = false; | |
| return; | |
| } | |
| const int tag = leaf.get().token_enum(); | |
| switch (tag) { | |
| // Align keywords 'parameter', 'localparam' and 'type' under the same | |
| // column. | |
| case verilog_tokentype::TK_parameter: | |
| case verilog_tokentype::TK_localparam: { | |
| ReserveNewColumn(leaf, FlushLeft); | |
| break; | |
| } | |
| case verilog_tokentype::TK_type: { | |
| if (Context().DirectParentIs(NodeEnum::kParamDeclaration)) { | |
| ReserveNewColumn(leaf, FlushLeft); | |
| } | |
| break; | |
| } | |
| // Sometimes the parameter indentifier which is of token SymbolIdentifier | |
| // can appear at different paths depending on the parameter type. Make | |
| // them aligned so they fall under the same column. | |
| case verilog_tokentype::SymbolIdentifier: { | |
| if (current_path_ == SyntaxTreePath{2, 0}) { | |
| SyntaxTreePath new_path{1, 2}; | |
| const ValueSaver<SyntaxTreePath> path_saver(¤t_path_, new_path); | |
| ReserveNewColumn(leaf, FlushLeft); | |
| return; | |
| } | |
| if (Context().DirectParentIs(NodeEnum::kParamType)) | |
| ReserveNewColumn(leaf, FlushLeft); | |
| break; | |
| } | |
| // '=' is another column where things should be aligned. But type | |
| // declarations and localparam cause '=' to appear under two different | |
| // paths in CST. Align them. | |
| case '=': { | |
| if (current_path_ == SyntaxTreePath{2, 1}) { | |
| SyntaxTreePath new_path{2}; | |
| const ValueSaver<SyntaxTreePath> path_saver(¤t_path_, new_path); | |
| ReserveNewColumn(leaf, FlushLeft); | |
| } | |
| break; | |
| } | |
| // Align packed and unpacked dimenssions | |
| case '[': { | |
| if (verilog::analysis::ContextIsInsideDeclarationDimensions( | |
| Context()) && | |
| !Context().IsInside(NodeEnum::kActualParameterList)) { | |
| // FlushLeft vs. Right doesn't matter, this is a single character. | |
| ReserveNewColumn(leaf, FlushLeft); | |
| new_column_after_open_bracket_ = true; | |
| } | |
| break; | |
| } | |
| case ']': { | |
| if (verilog::analysis::ContextIsInsideDeclarationDimensions( | |
| Context()) && | |
| !Context().IsInside(NodeEnum::kActualParameterList)) { | |
| // FlushLeft vs. Right doesn't matter, this is a single character. | |
| ReserveNewColumn(leaf, FlushLeft); | |
| } | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| VLOG(2) << __FUNCTION__ << ", leaving leaf: " << leaf.get(); | |
| } | |
| private: | |
| bool new_column_after_open_bracket_ = false; | |
| }; | |
| // This class marks up token-subranges in case items for alignment. | |
| // e.g. "value1, value2: x = f(y);" | |
| // This is suitable for a variety of case-like items: statements, generate | |
| // items. | |
| class CaseItemColumnSchemaScanner : public VerilogColumnSchemaScanner { | |
| public: | |
| explicit CaseItemColumnSchemaScanner(const FormatStyle& style) | |
| : VerilogColumnSchemaScanner(style) {} | |
| bool ParentContextIsCaseItem() const { | |
| return Context().DirectParentIsOneOf( | |
| {NodeEnum::kCaseItem, NodeEnum::kCaseInsideItem, | |
| NodeEnum::kGenerateCaseItem, NodeEnum::kDefaultItem}); | |
| } | |
| void Visit(const SyntaxTreeNode& node) final { | |
| auto tag = NodeEnum(node.Tag().tag); | |
| VLOG(2) << __FUNCTION__ << ", node: " << tag << " at " | |
| << TreePathFormatter(Path()); | |
| if (previous_token_was_case_colon_) { | |
| if (ParentContextIsCaseItem()) { | |
| ReserveNewColumn(node, FlushLeft); | |
| previous_token_was_case_colon_ = false; | |
| } | |
| } else { | |
| switch (tag) { | |
| case NodeEnum::kCaseItem: | |
| case NodeEnum::kCaseInsideItem: | |
| case NodeEnum::kGenerateCaseItem: | |
| case NodeEnum::kDefaultItem: { | |
| // Start a new column right away. | |
| ReserveNewColumn(node, FlushLeft); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| // recursive visitation | |
| TreeContextPathVisitor::Visit(node); | |
| VLOG(2) << __FUNCTION__ << ", leaving node: " << tag; | |
| } | |
| void Visit(const SyntaxTreeLeaf& leaf) final { | |
| VLOG(2) << __FUNCTION__ << ", leaf: " << leaf.get() << " at " | |
| << TreePathFormatter(Path()); | |
| const int tag = leaf.get().token_enum(); | |
| switch (tag) { | |
| case ':': | |
| if (ParentContextIsCaseItem()) { | |
| // mark the next node as the start of a new column | |
| previous_token_was_case_colon_ = true; | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| VLOG(2) << __FUNCTION__ << ", leaving leaf: " << leaf.get(); | |
| } | |
| private: | |
| bool previous_token_was_case_colon_ = false; | |
| }; | |
| // This class marks up token-subranges in various assignment statements for | |
| // alignment. e.g. | |
| // * assign foo = bar; | |
| // * foo = bar; | |
| // * foo <= bar; | |
| class AssignmentColumnSchemaScanner : public VerilogColumnSchemaScanner { | |
| public: | |
| explicit AssignmentColumnSchemaScanner(const FormatStyle& style) | |
| : VerilogColumnSchemaScanner(style) {} | |
| void Visit(const SyntaxTreeNode& node) final { | |
| auto tag = NodeEnum(node.Tag().tag); | |
| VLOG(2) << __FUNCTION__ << ", node: " << tag << " at " | |
| << TreePathFormatter(Path()); | |
| switch (tag) { | |
| case NodeEnum::kNetVariableAssignment: | |
| case NodeEnum::kBlockingAssignmentStatement: | |
| case NodeEnum::kNonblockingAssignmentStatement: | |
| case NodeEnum::kContinuousAssignmentStatement: { | |
| // Start a new column right away. | |
| ReserveNewColumn(node, FlushLeft); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| // recursive visitation | |
| TreeContextPathVisitor::Visit(node); | |
| VLOG(2) << __FUNCTION__ << ", leaving node: " << tag; | |
| } | |
| void Visit(const SyntaxTreeLeaf& leaf) final { | |
| VLOG(2) << __FUNCTION__ << ", leaf: " << leaf.get() << " at " | |
| << TreePathFormatter(Path()); | |
| const int tag = leaf.get().token_enum(); | |
| switch (tag) { | |
| case '=': // align at '=' | |
| if (Context().DirectParentIsOneOf( | |
| {NodeEnum::kNetVariableAssignment, | |
| NodeEnum::kBlockingAssignmentStatement})) { | |
| ReserveNewColumn(leaf, FlushLeft); | |
| } | |
| break; | |
| case verilog_tokentype::TK_LE: // '<=' for nonblocking assignments | |
| if (Context().DirectParentIs( | |
| NodeEnum::kNonblockingAssignmentStatement)) { | |
| ReserveNewColumn(leaf, FlushLeft); | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| VLOG(2) << __FUNCTION__ << ", leaving leaf: " << leaf.get(); | |
| } | |
| }; | |
| // Aligns enums that have assignment. | |
| // enum { // cols: | |
| // foo = 42 // foo: flush left | =: left | ...: (default left) | |
| // } | |
| class EnumWithAssignmentsColumnSchemaScanner | |
| : public VerilogColumnSchemaScanner { | |
| public: | |
| explicit EnumWithAssignmentsColumnSchemaScanner(const FormatStyle& style) | |
| : VerilogColumnSchemaScanner(style) {} | |
| void Visit(const SyntaxTreeNode& node) final { | |
| auto tag = NodeEnum(node.Tag().tag); | |
| VLOG(2) << __FUNCTION__ << ", node: " << tag << " at " | |
| << TreePathFormatter(Path()); | |
| switch (tag) { | |
| case NodeEnum::kEnumName: | |
| ReserveNewColumn(node, FlushLeft); | |
| break; | |
| default: { | |
| // TODO(hzeller): Add third column for the assignment expression and | |
| // make (configurably?) align right if all of them are numbers ? | |
| // Need to keep track in little state machine, and across rows, as the | |
| // right side can also be any expression. If there is any expression, | |
| // we'd want to keep all left aligned. (nested TODO: keep state across | |
| // multiple rows ?) | |
| } | |
| } | |
| TreeContextPathVisitor::Visit(node); // Recurse down. | |
| VLOG(2) << __FUNCTION__ << ", leaving node: " << tag; | |
| } | |
| void Visit(const SyntaxTreeLeaf& leaf) final { | |
| VLOG(2) << __FUNCTION__ << ", leaf: " << leaf.get() << " at " | |
| << TreePathFormatter(Path()); | |
| // Make sure that we only catch an = at the expected point | |
| if (Context().DirectParentIs(NodeEnum::kTrailingAssign) && | |
| leaf.get().token_enum() == '=') { | |
| ReserveNewColumn(leaf, FlushLeft); | |
| } | |
| VLOG(2) << __FUNCTION__ << ", leaving leaf: " << leaf.get(); | |
| } | |
| }; | |
| // Distribution items should align on the :/ and := operators. | |
| class DistItemColumnSchemaScanner : public VerilogColumnSchemaScanner { | |
| public: | |
| explicit DistItemColumnSchemaScanner(const FormatStyle& style) | |
| : VerilogColumnSchemaScanner(style) {} | |
| void Visit(const SyntaxTreeNode& node) final { | |
| const auto tag = NodeEnum(node.Tag().tag); | |
| switch (tag) { | |
| case NodeEnum::kDistributionItem: | |
| // Start first column right away. | |
| item_column_ = ReserveNewColumn(node, FlushLeft); | |
| break; | |
| case NodeEnum::kValueRange: { | |
| if (!Context().DirectParentIs(NodeEnum::kDistributionItem)) { | |
| break; | |
| } | |
| CHECK_EQ(node.children().size(), 5); | |
| CHECK_NOTNULL(item_column_); | |
| ReserveNewColumn(item_column_, *node[0], FlushLeft, | |
| GetSubpath(Path(), {0})); // '[' | |
| ReserveNewColumn(item_column_, *node[1], FlushRight, | |
| GetSubpath(Path(), {1})); // LHS value | |
| ReserveNewColumn(item_column_, *node[2], FlushLeft, | |
| GetSubpath(Path(), {2})); // ':' | |
| ReserveNewColumn(item_column_, *node[3], FlushRight, | |
| GetSubpath(Path(), {3})); // RHS value | |
| ReserveNewColumn(item_column_, *node[4], FlushLeft, | |
| GetSubpath(Path(), {4})); // ']' | |
| item_column_ = nullptr; | |
| return; | |
| } | |
| default: | |
| break; | |
| } | |
| TreeContextPathVisitor::Visit(node); // Recurse down. | |
| } | |
| void Visit(const SyntaxTreeLeaf& leaf) final { | |
| switch (leaf.get().token_enum()) { | |
| case verilog_tokentype::TK_COLON_EQ: | |
| case verilog_tokentype::TK_COLON_DIV: { | |
| ReserveNewColumn(leaf, FlushLeft); | |
| break; | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| private: | |
| verible::ColumnPositionTree* item_column_ = nullptr; | |
| }; | |
| static std::function< | |
| std::vector<TaggedTokenPartitionRange>(const TokenPartitionRange&)> | |
| PartitionBetweenBlankLines(AlignableSyntaxSubtype subtype) { | |
| return [subtype](const TokenPartitionRange& range) { | |
| return verible::GetSubpartitionsBetweenBlankLinesSingleTag( | |
| range, static_cast<int>(subtype)); | |
| }; | |
| } | |
| // Each alignment group subtype maps to a set of functions. | |
| struct AlignmentGroupHandlers { | |
| std::function<verible::AlignmentCellScannerFunction( | |
| const FormatStyle& vstyle)> | |
| column_scanner_func; | |
| std::function<verible::AlignmentPolicy(const FormatStyle& vstyle)> | |
| policy_func; | |
| }; | |
| // Convert a pointer-to-member to a function/lambda that accesses that member. | |
| // Returns the referenced member by value. | |
| // TODO(fangism): move this to an STL-style util/functional library | |
| template <typename MemberType, typename StructType> | |
| std::function<MemberType(const StructType&)> function_from_pointer_to_member( | |
| MemberType StructType::*member) { | |
| return [member](const StructType& obj) { return obj.*member; }; | |
| } | |
| using AlignmentHandlerMapType = | |
| std::map<AlignableSyntaxSubtype, AlignmentGroupHandlers>; | |
| static void non_tree_column_scanner( | |
| verible::FormatTokenRange leading_tokens, | |
| verible::FormatTokenRange trailing_tokens, | |
| verible::ColumnPositionTree* column_entries) { | |
| static const SyntaxTreePath kLeadingTokensPath = { | |
| kLeadingNonTreeTokenPathIndex}; | |
| static const SyntaxTreePath kTrailingCommaPath = { | |
| kTrailingNonTreeTokenPathIndex, 0}; | |
| static const SyntaxTreePath kTrailingCommentPath = { | |
| kTrailingNonTreeTokenPathIndex, 1}; | |
| VLOG(4) << __FUNCTION__ << "\nleading tokens: " | |
| << verible::StringSpanOfTokenRange(leading_tokens) | |
| << "\ntrailing tokens: " | |
| << verible::StringSpanOfTokenRange(trailing_tokens); | |
| if (!leading_tokens.empty()) { | |
| column_entries->Children().emplace_back(verible::ColumnPositionEntry{ | |
| kLeadingTokensPath, *leading_tokens.front().token, FlushLeft}); | |
| } | |
| if (trailing_tokens.empty()) return; | |
| const auto separator_it = | |
| std::find_if(trailing_tokens.begin(), trailing_tokens.end(), | |
| [](const PreFormatToken& tok) { | |
| return tok.TokenEnum() == ',' || tok.TokenEnum() == ':'; | |
| }); | |
| auto comment_it = trailing_tokens.begin(); | |
| if (separator_it != trailing_tokens.end()) { | |
| AlignmentColumnProperties prop; | |
| prop.contains_delimiter = true; | |
| const verible::ColumnPositionEntry column{kTrailingCommaPath, | |
| *separator_it->token, prop}; | |
| column_entries->Children().emplace_back(column); | |
| comment_it = separator_it + 1; | |
| } | |
| if (comment_it != trailing_tokens.end() && | |
| (comment_it->token->token_enum() == TK_COMMENT_BLOCK || | |
| comment_it->token->token_enum() == TK_EOL_COMMENT)) { | |
| const verible::ColumnPositionEntry column{kTrailingCommentPath, | |
| *comment_it->token, FlushLeft}; | |
| column_entries->Children().emplace_back(column); | |
| } | |
| } | |
| // Global registry of all known alignment handlers for Verilog. | |
| // This organization lets the same handlers be re-used in multiple | |
| // syntactic contexts, e.g. data declarations can be module items and | |
| // generate items and block statement items. | |
| static const AlignmentHandlerMapType& AlignmentHandlerLibrary() { | |
| static const auto* handler_map = new AlignmentHandlerMapType{ | |
| {AlignableSyntaxSubtype::kDataDeclaration, | |
| {UnstyledAlignmentCellScannerGenerator< | |
| DataDeclarationColumnSchemaScanner>(), | |
| function_from_pointer_to_member( | |
| &FormatStyle::module_net_variable_alignment)}}, | |
| {AlignableSyntaxSubtype::kNamedActualParameters, | |
| {UnstyledAlignmentCellScannerGenerator< | |
| ActualNamedParameterColumnSchemaScanner>(non_tree_column_scanner), | |
| function_from_pointer_to_member( | |
| &FormatStyle::named_parameter_alignment)}}, | |
| {AlignableSyntaxSubtype::kNamedActualPorts, | |
| {UnstyledAlignmentCellScannerGenerator< | |
| ActualNamedPortColumnSchemaScanner>(non_tree_column_scanner), | |
| function_from_pointer_to_member(&FormatStyle::named_port_alignment)}}, | |
| {AlignableSyntaxSubtype::kParameterDeclaration, | |
| {UnstyledAlignmentCellScannerGenerator< | |
| ParameterDeclarationColumnSchemaScanner>(non_tree_column_scanner), | |
| function_from_pointer_to_member( | |
| &FormatStyle::formal_parameters_alignment)}}, | |
| {AlignableSyntaxSubtype::kPortDeclaration, | |
| {UnstyledAlignmentCellScannerGenerator< | |
| PortDeclarationColumnSchemaScanner>(non_tree_column_scanner), | |
| function_from_pointer_to_member( | |
| &FormatStyle::port_declarations_alignment)}}, | |
| {AlignableSyntaxSubtype::kStructUnionMember, | |
| {UnstyledAlignmentCellScannerGenerator< | |
| StructUnionMemberColumnSchemaScanner>(non_tree_column_scanner), | |
| function_from_pointer_to_member( | |
| &FormatStyle::struct_union_members_alignment)}}, | |
| {AlignableSyntaxSubtype::kClassMemberVariables, | |
| {UnstyledAlignmentCellScannerGenerator< | |
| ClassPropertyColumnSchemaScanner>(), | |
| function_from_pointer_to_member( | |
| &FormatStyle::class_member_variable_alignment)}}, | |
| {AlignableSyntaxSubtype::kCaseLikeItems, | |
| {UnstyledAlignmentCellScannerGenerator<CaseItemColumnSchemaScanner>(), | |
| function_from_pointer_to_member(&FormatStyle::case_items_alignment)}}, | |
| {AlignableSyntaxSubtype::kContinuousAssignment, | |
| {UnstyledAlignmentCellScannerGenerator<AssignmentColumnSchemaScanner>(), | |
| function_from_pointer_to_member( | |
| &FormatStyle::assignment_statement_alignment)}}, | |
| {AlignableSyntaxSubtype::kBlockingAssignment, | |
| {UnstyledAlignmentCellScannerGenerator<AssignmentColumnSchemaScanner>(), | |
| function_from_pointer_to_member( | |
| &FormatStyle::assignment_statement_alignment)}}, | |
| {AlignableSyntaxSubtype::kNonBlockingAssignment, | |
| {UnstyledAlignmentCellScannerGenerator<AssignmentColumnSchemaScanner>(), | |
| function_from_pointer_to_member( | |
| &FormatStyle::assignment_statement_alignment)}}, | |
| {AlignableSyntaxSubtype::kEnumListAssignment, | |
| {UnstyledAlignmentCellScannerGenerator< | |
| EnumWithAssignmentsColumnSchemaScanner>(non_tree_column_scanner), | |
| function_from_pointer_to_member( | |
| &FormatStyle::enum_assignment_statement_alignment)}}, | |
| {AlignableSyntaxSubtype::kDistItem, | |
| {UnstyledAlignmentCellScannerGenerator<DistItemColumnSchemaScanner>(), | |
| function_from_pointer_to_member( | |
| &FormatStyle::distribution_items_alignment)}}, | |
| }; | |
| return *handler_map; | |
| } | |
| static verible::AlignmentCellScannerFunction AlignmentColumnScannerSelector( | |
| const FormatStyle& vstyle, int subtype) { | |
| static const auto& handler_map = AlignmentHandlerLibrary(); | |
| const auto iter = handler_map.find(AlignableSyntaxSubtype(subtype)); | |
| CHECK(iter != handler_map.end()) << "subtype: " << subtype; | |
| return iter->second.column_scanner_func(vstyle); | |
| } | |
| static verible::AlignmentPolicy AlignmentPolicySelector( | |
| const FormatStyle& vstyle, int subtype) { | |
| static const auto& handler_map = AlignmentHandlerLibrary(); | |
| const auto iter = handler_map.find(AlignableSyntaxSubtype(subtype)); | |
| CHECK(iter != handler_map.end()) << "subtype: " << subtype; | |
| return iter->second.policy_func(vstyle); | |
| } | |
| static std::vector<AlignablePartitionGroup> ExtractAlignablePartitionGroups( | |
| const std::function<std::vector<TaggedTokenPartitionRange>( | |
| const TokenPartitionRange&)>& group_extractor, | |
| const verible::IgnoreAlignmentRowPredicate& ignore_group_predicate, | |
| const TokenPartitionRange& full_range, const FormatStyle& vstyle) { | |
| const std::vector<TaggedTokenPartitionRange> ranges( | |
| group_extractor(full_range)); | |
| std::vector<AlignablePartitionGroup> groups; | |
| groups.reserve(ranges.size()); | |
| for (const auto& range : ranges) { | |
| // Use the alignment scanner and policy that correspond to the | |
| // match_subtype. This supports aligning a heterogenous collection of | |
| // alignable partition groups from the same parent partition (full_range). | |
| groups.emplace_back(AlignablePartitionGroup{ | |
| FilterAlignablePartitions(range.range, ignore_group_predicate), | |
| AlignmentColumnScannerSelector(vstyle, range.match_subtype), | |
| AlignmentPolicySelector(vstyle, range.match_subtype)}); | |
| if (groups.back().IsEmpty()) groups.pop_back(); | |
| } | |
| return groups; | |
| } | |
| using AlignSyntaxGroupsFunction = | |
| std::function<std::vector<AlignablePartitionGroup>( | |
| const TokenPartitionRange& range, const FormatStyle& style)>; | |
| static std::vector<AlignablePartitionGroup> AlignPortDeclarations( | |
| const TokenPartitionRange& full_range, const FormatStyle& vstyle) { | |
| return ExtractAlignablePartitionGroups( | |
| PartitionBetweenBlankLines(AlignableSyntaxSubtype::kPortDeclaration), | |
| &IgnoreWithinPortDeclarationPartitionGroup, full_range, vstyle); | |
| } | |
| static std::vector<AlignablePartitionGroup> AlignStructUnionMembers( | |
| const TokenPartitionRange& full_range, const FormatStyle& vstyle) { | |
| return ExtractAlignablePartitionGroups( | |
| PartitionBetweenBlankLines(AlignableSyntaxSubtype::kStructUnionMember), | |
| &IgnoreWithinStructUnionMemberPartitionGroup, full_range, vstyle); | |
| } | |
| static std::vector<AlignablePartitionGroup> AlignActualNamedParameters( | |
| const TokenPartitionRange& full_range, const FormatStyle& vstyle) { | |
| return ExtractAlignablePartitionGroups( | |
| PartitionBetweenBlankLines( | |
| AlignableSyntaxSubtype::kNamedActualParameters), | |
| &IgnoreWithinActualNamedParameterPartitionGroup, full_range, vstyle); | |
| } | |
| static std::vector<AlignablePartitionGroup> AlignActualNamedPorts( | |
| const TokenPartitionRange& full_range, const FormatStyle& vstyle) { | |
| return ExtractAlignablePartitionGroups( | |
| PartitionBetweenBlankLines(AlignableSyntaxSubtype::kNamedActualPorts), | |
| &IgnoreWithinActualNamedPortPartitionGroup, full_range, vstyle); | |
| } | |
| static std::vector<AlignablePartitionGroup> AlignModuleItems( | |
| const TokenPartitionRange& full_range, const FormatStyle& vstyle) { | |
| // Currently, this only handles data/net/variable declarations. | |
| // TODO(b/161814377): align continuous assignments | |
| return ExtractAlignablePartitionGroups( | |
| &GetConsecutiveModuleItemGroups, | |
| &IgnoreCommentsAndPreprocessingDirectives, full_range, vstyle); | |
| } | |
| static std::vector<AlignablePartitionGroup> AlignClassItems( | |
| const TokenPartitionRange& full_range, const FormatStyle& vstyle) { | |
| // TODO(fangism): align other class items besides member variables. | |
| return ExtractAlignablePartitionGroups( | |
| &GetConsecutiveClassItemGroups, &IgnoreCommentsAndPreprocessingDirectives, | |
| full_range, vstyle); | |
| } | |
| static std::vector<AlignablePartitionGroup> AlignCaseItems( | |
| const TokenPartitionRange& full_range, const FormatStyle& vstyle) { | |
| return ExtractAlignablePartitionGroups( | |
| PartitionBetweenBlankLines(AlignableSyntaxSubtype::kCaseLikeItems), | |
| &IgnoreMultilineCaseStatements, full_range, vstyle); | |
| } | |
| static std::vector<AlignablePartitionGroup> AlignEnumItems( | |
| const TokenPartitionRange& full_range, const FormatStyle& vstyle) { | |
| return ExtractAlignablePartitionGroups( | |
| PartitionBetweenBlankLines(AlignableSyntaxSubtype::kEnumListAssignment), | |
| &IgnoreCommentsAndPreprocessingDirectives, full_range, vstyle); | |
| } | |
| static std::vector<AlignablePartitionGroup> AlignParameterDeclarations( | |
| const TokenPartitionRange& full_range, const FormatStyle& vstyle) { | |
| return ExtractAlignablePartitionGroups( | |
| PartitionBetweenBlankLines(AlignableSyntaxSubtype::kParameterDeclaration), | |
| &IgnoreWithinPortDeclarationPartitionGroup, full_range, vstyle); | |
| } | |
| static std::vector<AlignablePartitionGroup> AlignStatements( | |
| const TokenPartitionRange& full_range, const FormatStyle& vstyle) { | |
| return ExtractAlignablePartitionGroups( | |
| &GetAlignableStatementGroups, &IgnoreCommentsAndPreprocessingDirectives, | |
| full_range, vstyle); | |
| } | |
| static std::vector<AlignablePartitionGroup> AlignDistItems( | |
| const TokenPartitionRange& full_range, const FormatStyle& vstyle) { | |
| return ExtractAlignablePartitionGroups( | |
| PartitionBetweenBlankLines(AlignableSyntaxSubtype::kDistItem), | |
| &IgnoreCommentsAndPreprocessingDirectives, full_range, vstyle); | |
| } | |
| void TabularAlignTokenPartitions(const FormatStyle& style, | |
| absl::string_view full_text, | |
| const ByteOffsetSet& disabled_byte_ranges, | |
| TokenPartitionTree* partition_ptr) { | |
| VLOG(1) << __FUNCTION__; | |
| auto& partition = *partition_ptr; | |
| auto& uwline = partition.Value(); | |
| const auto* origin = uwline.Origin(); | |
| VLOG(1) << "origin is nullptr? " << (origin == nullptr); | |
| if (origin == nullptr) return; | |
| const auto* node = down_cast<const SyntaxTreeNode*>(origin); | |
| VLOG(1) << "origin is node? " << (node != nullptr); | |
| if (node == nullptr) return; | |
| // Dispatch aligning function based on syntax tree node type. | |
| static const auto* const kAlignHandlers = | |
| new std::map<NodeEnum, AlignSyntaxGroupsFunction>{ | |
| {NodeEnum::kPortDeclarationList, &AlignPortDeclarations}, | |
| {NodeEnum::kPortList, &AlignPortDeclarations}, | |
| {NodeEnum::kStructUnionMemberList, &AlignStructUnionMembers}, | |
| {NodeEnum::kActualParameterByNameList, &AlignActualNamedParameters}, | |
| {NodeEnum::kPortActualList, &AlignActualNamedPorts}, | |
| {NodeEnum::kModuleItemList, &AlignModuleItems}, | |
| {NodeEnum::kGenerateItemList, &AlignModuleItems}, | |
| {NodeEnum::kFormalParameterList, &AlignParameterDeclarations}, | |
| {NodeEnum::kClassItems, &AlignClassItems}, | |
| // various case-like constructs: | |
| {NodeEnum::kCaseItemList, &AlignCaseItems}, | |
| {NodeEnum::kCaseInsideItemList, &AlignCaseItems}, | |
| {NodeEnum::kGenerateCaseItemList, &AlignCaseItems}, | |
| {NodeEnum::kEnumNameList, &AlignEnumItems}, | |
| // align various statements, like assignments | |
| {NodeEnum::kStatementList, &AlignStatements}, | |
| {NodeEnum::kBlockItemStatementList, &AlignStatements}, | |
| {NodeEnum::kFunctionItemList, &AlignStatements}, | |
| {NodeEnum::kDistributionItemList, &AlignDistItems}, | |
| }; | |
| const auto handler_iter = kAlignHandlers->find(NodeEnum(node->Tag().tag)); | |
| if (handler_iter == kAlignHandlers->end()) return; | |
| const AlignSyntaxGroupsFunction& alignment_partitioner = handler_iter->second; | |
| const ExtractAlignmentGroupsFunction extract_alignment_groups = | |
| std::bind(alignment_partitioner, std::placeholders::_1, style); | |
| verible::TabularAlignTokens(style.column_limit, full_text, | |
| disabled_byte_ranges, extract_alignment_groups, | |
| &partition); | |
| VLOG(1) << "end of " << __FUNCTION__; | |
| } | |
| } // namespace formatter | |
| } // namespace verilog |