Align case items (statements).
Initially, this only does two-column alignment (after the colon), nothing smarter.
e.g:
case (x)
kFooo: xx = y;
kBar: y = xx;
endcase
This also fixes handling of //comments in case item lists.
Next: related cases, like case-inside, randcase, generate case...
issues #413
PiperOrigin-RevId: 331254916
diff --git a/common/formatting/align.cc b/common/formatting/align.cc
index 49c06dd..70efa81 100644
--- a/common/formatting/align.cc
+++ b/common/formatting/align.cc
@@ -454,8 +454,11 @@
decision = SpacingOptions::MustAppend;
}
}
- partition.Value().SetPartitionPolicy(
- PartitionPolicyEnum::kSuccessfullyAligned);
+ // Tag every subtree as having already been committed to alignment.
+ partition.ApplyPostOrder([](TokenPartitionTree& node) {
+ node.Value().SetPartitionPolicy(
+ PartitionPolicyEnum::kSuccessfullyAligned);
+ });
}
}
diff --git a/verilog/formatting/align.cc b/verilog/formatting/align.cc
index e147a9f..2639aea 100644
--- a/verilog/formatting/align.cc
+++ b/verilog/formatting/align.cc
@@ -155,6 +155,30 @@
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.
+ if (std::any_of(token_range.begin(), token_range.end(),
+ &TokenForcesLineBreak)) {
+ return true;
+ }
+
+ return false;
+}
+
// This class marks up token-subranges in named parameter assignments for
// alignment. e.g. ".parameter_name(value_expression)"
class ActualNamedParameterColumnSchemaScanner : public ColumnSchemaScanner {
@@ -689,6 +713,66 @@
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);"
+class CaseItemColumnSchemaScanner : public ColumnSchemaScanner {
+ public:
+ CaseItemColumnSchemaScanner() = default;
+
+ bool ParentContextIsCaseItem() const {
+ return Context().DirectParentIsOneOf(
+ {NodeEnum::kCaseItem, NodeEnum::kDefaultItem});
+ }
+
+ void Visit(const SyntaxTreeNode& node) override {
+ 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::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) override {
+ 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;
+};
+
static const verible::AlignedFormattingHandler kPortDeclarationAligner{
.extract_alignment_groups = verible::ExtractAlignmentGroupsAdapter(
&verible::GetSubpartitionsBetweenBlankLines,
@@ -729,6 +813,14 @@
AlignmentCellScannerGenerator<ClassPropertyColumnSchemaScanner>(),
};
+static const verible::AlignedFormattingHandler kCaseItemAligner{
+ .extract_alignment_groups = verible::ExtractAlignmentGroupsAdapter(
+ &verible::GetSubpartitionsBetweenBlankLines,
+ &IgnoreMultilineCaseStatements),
+ .alignment_cell_scanner =
+ AlignmentCellScannerGenerator<CaseItemColumnSchemaScanner>(),
+};
+
struct AlignedFormattingConfiguration {
// Set of functions for driving specific code aligners.
verible::AlignedFormattingHandler handler;
@@ -794,6 +886,11 @@
[](const FormatStyle& vstyle) {
return vstyle.class_member_variable_alignment;
}}},
+ {NodeEnum::kCaseItemList,
+ {kCaseItemAligner,
+ [](const FormatStyle& vstyle) {
+ return vstyle.case_items_alignment;
+ }}},
};
const auto handler_iter = kAlignHandlers->find(NodeEnum(node->Tag().tag));
if (handler_iter == kAlignHandlers->end()) return;
diff --git a/verilog/formatting/format_style.h b/verilog/formatting/format_style.h
index f68821d..957c8b2 100644
--- a/verilog/formatting/format_style.h
+++ b/verilog/formatting/format_style.h
@@ -70,6 +70,10 @@
// Internal tests assume these are forced to kAlign.
AlignmentPolicy class_member_variable_alignment = AlignmentPolicy::kAlign;
+ // Control how case items are formatted.
+ // Internal tests assume these are forced to kAlign.
+ AlignmentPolicy case_items_alignment = AlignmentPolicy::kAlign;
+
// At this time line wrap optimization is problematic and risks ruining
// otherwise reasonable code. When set to false, this switch will make the
// formatter give-up and leave code as-is in cases where it would otherwise
@@ -117,6 +121,7 @@
module_net_variable_alignment = policy;
formal_parameters_alignment = policy;
class_member_variable_alignment = policy;
+ case_items_alignment = policy;
}
};
diff --git a/verilog/formatting/formatter_test.cc b/verilog/formatting/formatter_test.cc
index f4ecec9..bebb9bf 100644
--- a/verilog/formatting/formatter_test.cc
+++ b/verilog/formatting/formatter_test.cc
@@ -3836,6 +3836,19 @@
" end\n"
" endcase\n"
"endfunction\n"},
+ {// case statement, interleaved with comments
+ "function f; case (x) \n//c1\nState0 : a=b;//c2\n//c3\n State1 : "
+ "a=b;//c4\n//c5\n "
+ "endcase endfunction\n",
+ "function f;\n"
+ " case (x)\n"
+ " //c1\n"
+ " State0: a = b; //c2\n"
+ " //c3\n"
+ " State1: a = b; //c4\n"
+ " //c5\n"
+ " endcase\n"
+ "endfunction\n"},
{// case inside statement
"function f; case (x)inside k1 : return b; k2 : begin return b; end "
"endcase endfunction\n",
@@ -3884,7 +3897,7 @@
"endcase endfunction\n",
"function f;\n"
" case (x)\n"
- " k1: if (b) break;\n"
+ " k1: if (b) break;\n" // aligned
" default: return 2;\n"
" endcase\n"
"endfunction\n"},
@@ -3893,7 +3906,7 @@
"endcase endfunction\n",
"function f;\n"
" case (x)\n"
- " k1: break;\n"
+ " k1: break;\n" // aligned
" default: if (c) return 2;\n"
" endcase\n"
"endfunction\n"},
@@ -7152,6 +7165,106 @@
" int my_int;\n" // ... but still indent
" foo_pkg::bar_t my_bar;\n"
"endclass : cc\n"},
+
+ // case item test cases
+ {// small difference between flush-left and align, so align
+ "function f; case (x)kZZZZ :if( b )break; default :return 2;"
+ "endcase endfunction\n",
+ "function f;\n"
+ " case (x)\n"
+ " kZZZZ: if (b) break;\n" // aligned, only adds 2 spaces
+ " default: return 2;\n"
+ " endcase\n"
+ "endfunction\n"},
+ {// small error relative to flush-left, so flush-left
+ "function f; case (x)kZ :if( b )break; default :return 2;"
+ "endcase endfunction\n",
+ "function f;\n"
+ " case (x)\n"
+ " kZ: if (b) break;\n" // flush-left
+ " default: return 2;\n"
+ " endcase\n"
+ "endfunction\n"},
+ {// intentional spacing error (delta=4) induces alignment
+ "function f; case (x)kZ :if( b )break; default :return 2;"
+ "endcase endfunction\n",
+ "function f;\n"
+ " case (x)\n"
+ " kZ: if (b) break;\n"
+ " default: return 2;\n"
+ " endcase\n"
+ "endfunction\n"},
+ {// induced alignment, with ignored comments
+ "function f; case (x)kZ :if( b )break; \n//c1\n kXX: g = f; "
+ "\n//c2\ndefault :return 2;"
+ "endcase endfunction\n",
+ "function f;\n"
+ " case (x)\n"
+ " kZ: if (b) break;\n"
+ " //c1\n"
+ " kXX: g = f;\n"
+ " //c2\n"
+ " default: return 2;\n"
+ " endcase\n"
+ "endfunction\n"},
+ {// induced alignment, ignore multiline case item in the middle
+ "function f; case (x)"
+ "kZ :if( b )break; "
+ "kYY :return 2;" // excess spaces induce alignment
+ " kXXXXXXXXX: begin end" // multi-line, ignored
+ " kWWWWW: cc = 23;\n"
+ " kVVV: cd = 24;\n"
+ "endcase endfunction\n",
+ "function f;\n"
+ " case (x)\n"
+ " kZ: if (b) break;\n"
+ " kYY: return 2;\n"
+ " kXXXXXXXXX: begin\n" // separates above/below groups
+ " end\n"
+ " kWWWWW: cc = 23;\n"
+ " kVVV: cd = 24;\n" // aligned
+ " endcase\n"
+ "endfunction\n"},
+ {// induced alignment, ignore multiline case item in the middle
+ "function f; case (x)"
+ "kZ :if( b )break; "
+ "kYY :return 2;" // excess spaces induce alignment
+ " kXXXXXXXXX: if(w)begin end" // multi-line, ignored
+ " kWWWWW: cc = 23;\n"
+ " kVVV: cd = 24;\n"
+ "endcase endfunction\n",
+ "function f;\n"
+ " case (x)\n"
+ " kZ: if (b) break;\n"
+ " kYY: return 2;\n"
+ " kXXXXXXXXX:\n" // TODO(fangism): allow this to merge with if()
+ // else indent the following two lines
+ " if (w) begin\n" // separates above/below groups
+ " end\n"
+ " kWWWWW: cc = 23;\n"
+ " kVVV: cd = 24;\n" // aligned
+ " endcase\n"
+ "endfunction\n"},
+ {// induced alignment, ignore multiline case item in the middle
+ "task t; case (x)"
+ "kZ :if( b )break; "
+ "kYY :return 2;" // excess spaces induce alignment
+ " kXXXXXXXXX: fork join" // multi-line, ignored
+ " kWWWWW: cc = 23;\n"
+ " kVVV: cd = 24;\n"
+ "endcase endtask\n",
+ "task t;\n"
+ " case (x)\n"
+ " kZ: if (b) break;\n"
+ " kYY: return 2;\n"
+ " kXXXXXXXXX:\n" // TODO(fangism): allow this to merge with fork
+ // else indent the following two lines
+ " fork\n" // separates above/below groups
+ " join\n"
+ " kWWWWW: cc = 23;\n"
+ " kVVV: cd = 24;\n" // aligned
+ " endcase\n"
+ "endtask\n"},
};
// Use a fixed style.
FormatStyle style;
diff --git a/verilog/formatting/tree_unwrapper.cc b/verilog/formatting/tree_unwrapper.cc
index b6df5a0..e8feffb 100644
--- a/verilog/formatting/tree_unwrapper.cc
+++ b/verilog/formatting/tree_unwrapper.cc
@@ -528,6 +528,7 @@
case NodeEnum::kPackageItemList:
case NodeEnum::kSpecifyItemList:
case NodeEnum::kBlockItemStatementList:
+ case NodeEnum::kCaseItemList:
case NodeEnum::kConstraintExpressionList:
case NodeEnum::kConstraintBlockItemList:
LookAheadBeyondCurrentNode();
@@ -944,7 +945,6 @@
case NodeEnum::kPackageItemList:
case NodeEnum::kInterfaceClassDeclaration:
case NodeEnum::kGenerateItemList:
- case NodeEnum::kCaseItemList:
case NodeEnum::kCaseInsideItemList:
case NodeEnum::kCasePatternItemList:
case NodeEnum::kGenerateCaseItemList:
@@ -1004,6 +1004,7 @@
}
}
+ case NodeEnum::kCaseItemList:
case NodeEnum::kClassItems:
case NodeEnum::kModuleItemList: {
const int indent = suppress_indentation ? 0 : style_.indentation_spaces;
diff --git a/verilog/formatting/tree_unwrapper_test.cc b/verilog/formatting/tree_unwrapper_test.cc
index 709a719..9f33cc2 100644
--- a/verilog/formatting/tree_unwrapper_test.cc
+++ b/verilog/formatting/tree_unwrapper_test.cc
@@ -6891,6 +6891,35 @@
},
{
+ "function with case statement, with comments",
+ "function foo_case;"
+ "case (y) \n"
+ "//c1\n"
+ "k1: return 0;\n"
+ "//c2\n"
+ "k2: return 1;\n"
+ "//c3\n"
+ "endcase "
+ "endfunction",
+ FunctionDeclaration(
+ 0, FunctionHeader(0, {"function", "foo_case", ";"}),
+ FlowControl(1, L(1, {"case", "(", "y", ")"}),
+ CaseItemList(2, //
+ L(2, {"//c1"}), //
+ N(2, //
+ L(2, {"k1", ":"}), //
+ L(2, {"return", "0", ";"})),
+ L(2, {"//c2"}), //
+ N(2, //
+ L(2, {"k2", ":"}), //
+ L(2, {"return", "1", ";"})),
+ L(2, {"//c3"}) //
+ ), //
+ L(1, {"endcase"})), //
+ L(0, {"endfunction"})),
+ },
+
+ {
"function with case statements",
"function foo_case;"
"case (y) "
diff --git a/verilog/tools/formatter/verilog_format.cc b/verilog/tools/formatter/verilog_format.cc
index 188c4a4..c5572b7 100644
--- a/verilog/tools/formatter/verilog_format.cc
+++ b/verilog/tools/formatter/verilog_format.cc
@@ -157,6 +157,9 @@
ABSL_FLAG(AlignmentPolicy, class_member_variables_alignment,
AlignmentPolicy::kInferUserIntent,
"Format class member variables: {align,flush-left,preserve,infer}");
+ABSL_FLAG(AlignmentPolicy, case_items_alignment,
+ AlignmentPolicy::kInferUserIntent,
+ "Format case items: {align,flush-left,preserve,infer}");
ABSL_FLAG(bool, try_wrap_long_lines, false,
"If true, let the formatter attempt to optimize line wrapping "
@@ -236,6 +239,8 @@
absl::GetFlag(FLAGS_formal_parameters_alignment);
format_style.class_member_variable_alignment =
absl::GetFlag(FLAGS_class_member_variables_alignment);
+ format_style.case_items_alignment =
+ absl::GetFlag(FLAGS_case_items_alignment);
}
std::ostringstream stream;