blob: c9294c0f61205a800970b56862861678d3faf933 [file] [log] [blame]
// Copyright 2017-2020 The Verible Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "verilog/formatting/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(&current_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(&current_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(&current_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::kNetDeclarationAssignment:
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(&current_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::kDeclarationDimensions: {
if (current_path_ == SyntaxTreePath{1, 0, 0, 3, 0}) {
SyntaxTreePath new_path{1, 0, 0, 3};
const ValueSaver<SyntaxTreePath> path_saver(&current_path_, new_path);
TreeContextPathVisitor::Visit(node);
return;
}
break;
}
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(&current_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(&current_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(&current_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