Merge pull request #2532 from EylonKrause/fix/preprocess-macro-call-eof
preprocessor: stop macro-argument scan on an unexpected/EOF token
diff --git a/.github/bin/smoke-test.sh b/.github/bin/smoke-test.sh
index d10251b..5532187 100755
--- a/.github/bin/smoke-test.sh
+++ b/.github/bin/smoke-test.sh
@@ -29,17 +29,13 @@
# crash Verible, we're good.
###
-# Suppress '... aborted' messages bash would print when a tool crashes.
-# Comment out to see syntax errors in bash while working on script.
-exec 2>/dev/null
-
set -u # Be strict: only allow using a variable after it is assigned
BAZEL_BUILD_OPTIONS="-c opt"
TMPDIR="${TMPDIR:-/tmp}"
readonly BASE_TEST_DIR="${TMPDIR}/test/verible-smoke-test"
-readonly DEFAULT_HASH_FILE="$(dirname $0)/smoke-projects.hashes"
+readonly PROJECT_HASHES_FILE="$(dirname $0)/smoke-projects.hashes"
# Write log files to this directory
readonly SMOKE_LOGGING_DIR="${SMOKE_LOGGING_DIR:-$BASE_TEST_DIR/error-logs}"
@@ -82,7 +78,6 @@
#
# There are some known issues which are all recorded in the associative
# array below, mapping them to Verible issue tracker numbers.
-readonly PROJECT_HASHES_FILE="${1:-${DEFAULT_HASH_FILE}}"
if [ ! -f "${PROJECT_HASHES_FILE}" ]; then
echo "Project hashes file not found: ${PROJECT_HASHES_FILE}"
@@ -359,8 +354,55 @@
return ${result}
}
+# --- main
+
+KEEP_LOGS=0
+VERBOSE=0
+PROJECT_FILTER=""
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --keep-logs)
+ KEEP_LOGS=1
+ shift
+ ;;
+ --verbose|-v)
+ VERBOSE=1
+ shift
+ ;;
+ --filter=*)
+ PROJECT_FILTER="${1#*=}"
+ shift
+ ;;
+ --filter|-f)
+ if [[ $# -lt 2 ]]; then
+ echo "Error: $1 requires an argument." >&2
+ exit 1
+ fi
+ PROJECT_FILTER="$2"
+ shift 2
+ ;;
+ -h|--help)
+ echo "Usage: $0 [--keep-logs] [--verbose] [--filter=<name>]"
+ exit 0
+ ;;
+ *)
+ echo "Unknown option: $1" >&2
+ echo "Usage: $0 [--keep-logs] [--verbose] [--filter=<name>]" >&2
+ exit 1
+ ;;
+ esac
+done
+
+if [ ${VERBOSE} -eq 0 ]; then
+ # Suppress '... aborted' messages bash would print when a tool crashes.
+ exec 2>/dev/null
+fi
+
mkdir -p "${BASE_TEST_DIR}"
-trap 'rm -rf -- "${BASE_TEST_DIR}"' EXIT
+if [ ${KEEP_LOGS} -eq 0 ]; then
+ trap 'rm -rf -- "${BASE_TEST_DIR}"' EXIT
+fi
status_sum=0
@@ -378,6 +420,9 @@
while read -r git_hash git_project _; do
[[ -z "${git_hash}" || "${git_hash}" =~ ^# ]] && continue
PROJECT_NAME="$(basename "${git_project}")"
+ if [[ -n "${PROJECT_FILTER}" && "${PROJECT_NAME}" != *"${PROJECT_FILTER}"* ]]; then
+ continue
+ fi
PROJECT_DIR="${BASE_TEST_DIR}/${PROJECT_NAME}"
( git clone "${git_project}" "${PROJECT_DIR}" && git -C "${PROJECT_DIR}" checkout -q "${git_hash}" ) 2>/dev/null &
done < "${PROJECT_HASHES_FILE}"
@@ -389,6 +434,9 @@
while read -r git_hash git_project _; do
[[ -z "${git_hash}" || "${git_hash}" =~ ^# ]] && continue
PROJECT_NAME="$(basename "${git_project}")"
+ if [[ -n "${PROJECT_FILTER}" && "${PROJECT_NAME}" != *"${PROJECT_FILTER}"* ]]; then
+ continue
+ fi
PROJECT_DIR="${BASE_TEST_DIR}/${PROJECT_NAME}"
# Already cloned above
@@ -411,7 +459,7 @@
echo "There were a total of ${status_sum} mismatches"
# Let's see if there are any issues that are fixed in the meantime.
-if [ "${#KnownIssue[@]}" -ne 0 ]; then
+if [ -z "${PROJECT_FILTER}" ] && [ "${#KnownIssue[@]}" -ne 0 ]; then
echo "::warning ::There are ${#KnownIssue[@]} tool/file combinations, that no longer fail"
declare -A DistinctIssues
for key in "${!KnownIssue[@]}"; do
@@ -423,7 +471,10 @@
for issue_id in "${!DistinctIssues[@]}"; do
echo " 🐞 ${ISSUE_PREFIX}/${issue_id}"
done
- echo
+fi
+
+if [ ${KEEP_LOGS} -ne 0 ]; then
+ echo "Logs and project files kept in ${BASE_TEST_DIR} (error logs: ${SMOKE_LOGGING_DIR})"
fi
exit ${status_sum}
diff --git a/README.md b/README.md
index b813b76..047fb20 100644
--- a/README.md
+++ b/README.md
@@ -172,8 +172,9 @@
Verible's code base is written in C++.
-To build, you need the [bazel] build system (Min version 7) and a C++20
-compatible compiler.
+To build, you need the [bazel] build system (get it from
+[bazel install][bazel-install] if not already on your system) and a
+C++20 compatible compiler.
Use your package manager to install the dependencies; on a system with
the nix package manager simply run `nix-shell` to get a build environment.
@@ -199,6 +200,8 @@
bazel build -c opt --config=create_static_linked_executables //...
```
+See [Installation](#installation-1) for install.
+
### Optionally using local flex/bison for build
Flex and Bison, that are needed for the parser generation, are compiled as part
@@ -283,6 +286,7 @@
[UHDM] format. If you are interested in collaborating, contact us.
[bazel]: https://bazel.build/
+[bazel-install]: https://bazel.build/install
[SV-LRM]: https://ieeexplore.ieee.org/document/8299595
[lint-rule-list]: https://chipsalliance.github.io/verible/lint.html
[github-lint-action]: https://github.com/chipsalliance/verible-linter-action
diff --git a/shell.nix b/shell.nix
index 85163eb..408340b 100644
--- a/shell.nix
+++ b/shell.nix
@@ -8,6 +8,17 @@
#verible_used_stdenv = pkgs.gcc15Stdenv;
#verible_used_stdenv = pkgs.clang19Stdenv;
bazel = pkgs.bazel_8;
+
+
+ userNixPath = ./user.nix; # optional user config
+ userPackages =
+ if builtins.pathExists userNixPath
+ then
+ let loaded = import userNixPath;
+ in if builtins.isFunction loaded
+ then loaded { inherit pkgs; }
+ else loaded
+ else [];
in
verible_used_stdenv.mkDerivation {
name = "verible-build-environment";
@@ -40,7 +51,7 @@
llvmPackages_22.clang-tools # for clang-tidy
llvmPackages_19.clang-tools # for clang-format
- ];
+ ] ++ userPackages;
shellHook = ''
# clang tidy: use latest.
export CLANG_TIDY=${pkgs.llvmPackages_22.clang-tools}/bin/clang-tidy
diff --git a/verible/common/util/container-proxy_test.cc b/verible/common/util/container-proxy_test.cc
index 5e8220f..4e4cd75 100644
--- a/verible/common/util/container-proxy_test.cc
+++ b/verible/common/util/container-proxy_test.cc
@@ -874,13 +874,14 @@
const int new_capacity = initial_capacity + 42;
this->proxy.reserve(new_capacity);
- EXPECT_EQ(this->proxy.capacity(), new_capacity);
- EXPECT_EQ(this->container.capacity(), new_capacity);
+ EXPECT_GE(this->proxy.capacity(), new_capacity);
+ EXPECT_EQ(this->proxy.capacity(), this->container.capacity());
+ const int capacity_after_first_reserve = this->proxy.capacity();
const int lower_capacity = 1;
this->proxy.reserve(lower_capacity);
- EXPECT_EQ(this->proxy.capacity(), new_capacity);
- EXPECT_EQ(this->container.capacity(), new_capacity);
+ EXPECT_EQ(this->proxy.capacity(), capacity_after_first_reserve);
+ EXPECT_EQ(this->container.capacity(), capacity_after_first_reserve);
}
} // namespace
diff --git a/verible/verilog/analysis/checkers/BUILD b/verible/verilog/analysis/checkers/BUILD
index bd3a98b..4681607 100644
--- a/verible/verilog/analysis/checkers/BUILD
+++ b/verible/verilog/analysis/checkers/BUILD
@@ -817,6 +817,7 @@
"//verible/common/analysis:syntax-tree-lint-rule",
"//verible/common/analysis/matcher",
"//verible/common/analysis/matcher:bound-symbol-manager",
+ "//verible/common/text:config-utils",
"//verible/common/text:symbol",
"//verible/common/text:syntax-tree-context",
"//verible/common/text:token-info",
@@ -826,7 +827,9 @@
"//verible/verilog/CST:verilog-nonterminals",
"//verible/verilog/analysis:descriptions",
"//verible/verilog/analysis:lint-rule-registry",
+ "@abseil-cpp//absl/status",
"@abseil-cpp//absl/strings",
+ "@re2",
],
alwayslink = 1,
)
@@ -1821,6 +1824,7 @@
"//verible/common/text:symbol",
"//verible/common/text:syntax-tree-context",
"//verible/common/text:token-info",
+ "//verible/common/util:container-util",
"//verible/verilog/CST:port",
"//verible/verilog/CST:verilog-matchers",
"//verible/verilog/analysis:descriptions",
diff --git a/verible/verilog/analysis/checkers/generate-label-prefix-rule.cc b/verible/verilog/analysis/checkers/generate-label-prefix-rule.cc
index c8822ce..e678ad5 100644
--- a/verible/verilog/analysis/checkers/generate-label-prefix-rule.cc
+++ b/verible/verilog/analysis/checkers/generate-label-prefix-rule.cc
@@ -14,12 +14,17 @@
#include "verible/verilog/analysis/checkers/generate-label-prefix-rule.h"
+#include <memory>
+#include <string>
#include <string_view>
-#include "absl/strings/match.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "re2/re2.h"
#include "verible/common/analysis/lint-rule-status.h"
#include "verible/common/analysis/matcher/bound-symbol-manager.h"
#include "verible/common/analysis/matcher/matcher.h"
+#include "verible/common/text/config-utils.h"
#include "verible/common/text/symbol.h"
#include "verible/common/text/syntax-tree-context.h"
#include "verible/common/text/token-info.h"
@@ -38,22 +43,36 @@
// Register the lint rule
VERILOG_REGISTER_LINT_RULE(GenerateLabelPrefixRule);
-static constexpr std::string_view kMessage =
- "All generate block labels must start with g_ or gen_";
+static constexpr std::string_view kDefaultStyleRegex = "(g_|gen_).*";
-// TODO(fangism): and be lower_snake_case?
-// TODO(fangism): generalize to a configurable pattern and
-// rename this class/rule to GenerateLabelNamingStyle?
+GenerateLabelPrefixRule::GenerateLabelPrefixRule()
+ : style_regex_(
+ std::make_unique<re2::RE2>(kDefaultStyleRegex, re2::RE2::Quiet)) {}
const LintRuleDescriptor &GenerateLabelPrefixRule::GetDescriptor() {
static const LintRuleDescriptor d{
.name = "generate-label-prefix",
.topic = "generate-constructs",
- .desc = "Checks that every generate block label starts with g_ or gen_.",
+ .desc =
+ "Checks that every generate block label matches the regex defined by "
+ "style_regex. The default regex requires labels to start with g_ or "
+ "gen_. Refer to https://github.com/chipsalliance/verible/tree/master/"
+ "verilog/tools/lint#readme for more detail on verible regex "
+ "patterns.",
+ // NOLINTNEXTLINE(misc-include-cleaner)
+ .param = {{"style_regex", std::string(kDefaultStyleRegex),
+ "A regex used to check generate label style."}},
};
return d;
}
+std::string GenerateLabelPrefixRule::CreateViolationMessage() const {
+ return absl::StrCat(
+ "Generate block label does not match the naming convention defined by "
+ "regex pattern: ",
+ style_regex_->pattern());
+}
+
// Matches begin statements
static const Matcher &BlockMatcher() {
static const Matcher matcher(NodekGenerateBlock());
@@ -84,15 +103,24 @@
}
if (label != nullptr) {
- if (!(absl::StartsWith(label->text(), "g_") ||
- absl::StartsWith(label->text(), "gen_"))) {
- violations_.insert(verible::LintViolation(*label, kMessage, context));
+ if (!RE2::FullMatch(label->text(), *style_regex_)) {
+ violations_.insert(verible::LintViolation(
+ *label, CreateViolationMessage(), context));
}
}
}
}
}
+// NOLINTNEXTLINE(misc-include-cleaner)
+absl::Status GenerateLabelPrefixRule::Configure(
+ std::string_view configuration) {
+ using verible::config::SetRegex;
+ absl::Status s = verible::ParseNameValues(
+ configuration, {{"style_regex", SetRegex(&style_regex_)}});
+ return s;
+}
+
verible::LintRuleStatus GenerateLabelPrefixRule::Report() const {
return verible::LintRuleStatus(violations_, GetDescriptor());
}
diff --git a/verible/verilog/analysis/checkers/generate-label-prefix-rule.h b/verible/verilog/analysis/checkers/generate-label-prefix-rule.h
index 9090cf3..e18df9c 100644
--- a/verible/verilog/analysis/checkers/generate-label-prefix-rule.h
+++ b/verible/verilog/analysis/checkers/generate-label-prefix-rule.h
@@ -15,8 +15,13 @@
#ifndef VERIBLE_VERILOG_ANALYSIS_CHECKERS_GENERATE_LABEL_PREFIX_RULE_H_
#define VERIBLE_VERILOG_ANALYSIS_CHECKERS_GENERATE_LABEL_PREFIX_RULE_H_
+#include <memory>
#include <set>
+#include <string>
+#include <string_view>
+#include "absl/status/status.h"
+#include "re2/re2.h"
#include "verible/common/analysis/lint-rule-status.h"
#include "verible/common/analysis/syntax-tree-lint-rule.h"
#include "verible/common/text/symbol.h"
@@ -27,20 +32,28 @@
namespace analysis {
// GenerateLabelPrefixRule checks that all generate block labels start
-// with g_ or gen_
+// with g_ or gen_ (configurable via style_regex)
class GenerateLabelPrefixRule : public verible::SyntaxTreeLintRule {
public:
using rule_type = verible::SyntaxTreeLintRule;
+ GenerateLabelPrefixRule();
+
static const LintRuleDescriptor &GetDescriptor();
+ std::string CreateViolationMessage() const;
+
void HandleSymbol(const verible::Symbol &symbol,
const verible::SyntaxTreeContext &context) final;
verible::LintRuleStatus Report() const final;
+ absl::Status Configure(std::string_view configuration) final;
+
private:
std::set<verible::LintViolation> violations_;
+
+ std::unique_ptr<re2::RE2> style_regex_;
};
} // namespace analysis
diff --git a/verible/verilog/analysis/checkers/generate-label-prefix-rule_test.cc b/verible/verilog/analysis/checkers/generate-label-prefix-rule_test.cc
index b46bfe4..cb43501 100644
--- a/verible/verilog/analysis/checkers/generate-label-prefix-rule_test.cc
+++ b/verible/verilog/analysis/checkers/generate-label-prefix-rule_test.cc
@@ -27,6 +27,7 @@
namespace {
using verible::LintTestCase;
+using verible::RunConfiguredLintTestCases;
using verible::RunLintTestCases;
TEST(GenerateLabelPrefixRuleTest, Various) {
@@ -229,6 +230,40 @@
RunLintTestCases<VerilogAnalyzer, GenerateLabelPrefixRule>(kTestCases);
}
+TEST(GenerateLabelPrefixRuleTest, CustomRegex) {
+ const std::initializer_list<LintTestCase> kTestCases = {
+ {"module m;\n"
+ "generate\n"
+ "if (1) begin : my_custom_label\n"
+ "end\n"
+ "endgenerate\nendmodule\n"},
+ {"module m;\n"
+ "generate\n"
+ "for (genvar i=0; i<5; i++) begin : my_custom_label\n"
+ "end\n"
+ "endgenerate\nendmodule\n"},
+ };
+ RunConfiguredLintTestCases<VerilogAnalyzer, GenerateLabelPrefixRule>(
+ kTestCases, "style_regex:my_.*");
+
+ const std::initializer_list<LintTestCase> kFailCases = {
+ {"module m;\n"
+ "generate\n"
+ "if (1) begin : ",
+ {SymbolIdentifier, "g_label"},
+ "\nend\n"
+ "endgenerate\nendmodule\n"},
+ {"module m;\n"
+ "generate\n"
+ "if (1) begin : ",
+ {SymbolIdentifier, "gen_label"},
+ "\nend\n"
+ "endgenerate\nendmodule\n"},
+ };
+ RunConfiguredLintTestCases<VerilogAnalyzer, GenerateLabelPrefixRule>(
+ kFailCases, "style_regex:my_.*");
+}
+
} // namespace
} // namespace analysis
} // namespace verilog
diff --git a/verible/verilog/analysis/checkers/port-name-suffix-rule.cc b/verible/verilog/analysis/checkers/port-name-suffix-rule.cc
index 85a0db3..ffecef3 100644
--- a/verible/verilog/analysis/checkers/port-name-suffix-rule.cc
+++ b/verible/verilog/analysis/checkers/port-name-suffix-rule.cc
@@ -28,6 +28,7 @@
#include "verible/common/text/symbol.h"
#include "verible/common/text/syntax-tree-context.h"
#include "verible/common/text/token-info.h"
+#include "verible/common/util/container-util.h"
#include "verible/verilog/CST/port.h"
#include "verible/verilog/CST/verilog-matchers.h"
#include "verible/verilog/analysis/descriptions.h"
@@ -90,10 +91,16 @@
{"output", {"o", "no", "po"}},
{"inout", {"io", "nio", "pio"}}};
- // At this point it is guaranteed that the direction will be set to
- // one of the expected values (used as keys in the map above).
- // Therefore checking the suffix like this is safe
- return suffixes.at(direction).count(suffix) == 1;
+ // `direction` is usually one of the map keys, but the grammar also permits a
+ // `ref` port direction, which has no suffix convention. FindWithDefault looks
+ // the direction up with an empty-set fallback, so an unknown direction (e.g.
+ // `ref`) has no required suffixes and is treated as "correct" (no violation),
+ // consistent with Violation() which also ignores non-input/output/inout
+ // directions.
+ static const std::set<std::string_view> kNoConvention;
+ const std::set<std::string_view> &valid =
+ verible::container::FindWithDefault(suffixes, direction, kNoConvention);
+ return valid.empty() || valid.count(suffix) == 1;
}
void PortNameSuffixRule::HandleSymbol(const Symbol &symbol,
@@ -113,8 +120,11 @@
absl::StrSplit(name, '_', absl::SkipEmpty());
if (name_parts.size() < 2) {
- // No suffix at all
+ // No suffix at all. This also covers an all-underscore name (e.g. "_"),
+ // for which SkipEmpty leaves name_parts empty; return here so the
+ // name_parts.back() access below is not reached on an empty vector.
Violation(direction, token, context);
+ return;
}
if (!IsSuffixCorrect(name_parts.back(), direction)) {
diff --git a/verible/verilog/analysis/checkers/port-name-suffix-rule_test.cc b/verible/verilog/analysis/checkers/port-name-suffix-rule_test.cc
index 69f18f4..db69d71 100644
--- a/verible/verilog/analysis/checkers/port-name-suffix-rule_test.cc
+++ b/verible/verilog/analysis/checkers/port-name-suffix-rule_test.cc
@@ -57,6 +57,9 @@
{"module t (input bit name_i); endmodule;"},
{"module t (output bit abc_o); endmodule;"},
{"module t (inout bit xyz_io); endmodule;"},
+ // A `ref` port has no suffix convention and must not be flagged (and must
+ // not crash the rule via std::map::at).
+ {"module t (ref logic data_x); endmodule;"},
{"module t (input logic name_i,\n"
"output logic abc_o,\n"
"inout logic xyz_io,\n"
@@ -90,6 +93,10 @@
{"module t (output logic ", {kToken, "_o"}, "); endmodule;"},
{"module t (inout logic ", {kToken, "_io"}, "); endmodule;"},
+ // An all-underscore name splits (SkipEmpty) to an empty parts list; it
+ // must report a suffix violation, not dereference an empty vector.
+ {"module t (input logic ", {kToken, "_"}, "); endmodule;"},
+
{"module t (input logic ", {kToken, "namei"}, "); endmodule;"},
{"module t (input logic ", {kToken, "nam_ei"}, "); endmodule;"},
{"module t (input logic ", {kToken, "name_o"}, "); endmodule;"},
diff --git a/verible/verilog/analysis/verilog-equivalence.cc b/verible/verilog/analysis/verilog-equivalence.cc
index f8a47d0..d5c12bea 100644
--- a/verible/verilog/analysis/verilog-equivalence.cc
+++ b/verible/verilog/analysis/verilog-equivalence.cc
@@ -89,6 +89,26 @@
return IsUnlexed(verilog_tokentype(token.token_enum()));
}
+// True when left/right differ only because surrounding whitespace changed
+// token classification (e.g. MacroCallCloseToEndLine vs ')', or
+// MacroIdentifier vs MacroIdItem with unchanged spelling).
+static bool TokensAreWhitespaceDependentFormatEquivalent(
+ const TokenInfo &left, const TokenInfo &right) {
+ if ((left.token_enum() == verilog_tokentype::MacroCallCloseToEndLine &&
+ right.text() == ")") ||
+ (right.token_enum() == verilog_tokentype::MacroCallCloseToEndLine &&
+ left.text() == ")")) {
+ return true;
+ }
+ const auto is_line_ending_macro = [](int token_enum) {
+ return token_enum == verilog_tokentype::MacroIdentifier ||
+ token_enum == verilog_tokentype::MacroIdItem;
+ };
+ return is_line_ending_macro(left.token_enum()) &&
+ is_line_ending_macro(right.token_enum()) &&
+ left.text() == right.text();
+}
+
DiffStatus VerilogLexicallyEquivalent(
std::string_view left, std::string_view right,
const std::function<bool(const verible::TokenInfo &)> &remove_predicate,
@@ -167,11 +187,11 @@
DiffStatus diff_status = DiffStatus::kEquivalent;
auto recursive_comparator = [&](const TokenSequence::const_iterator l,
const TokenSequence::const_iterator r) {
+ // Some token enums differ only by surrounding whitespace (e.g. whether a
+ // macro or ')' ends a line). Treat those pairs as matching enums when the
+ // spelling is unchanged so FormatEquivalent tolerates re-wrapping.
if (l->token_enum() != r->token_enum() &&
- !((l->token_enum() == verilog_tokentype::MacroCallCloseToEndLine &&
- r->text() == ")") ||
- (r->token_enum() == verilog_tokentype::MacroCallCloseToEndLine &&
- l->text() == ")"))) {
+ !TokensAreWhitespaceDependentFormatEquivalent(*l, *r)) {
if (errstream != nullptr) {
*errstream << "Mismatched token enums. got: ";
token_printer(*l, *errstream);
@@ -250,12 +270,7 @@
return IsWhitespace(verilog_tokentype(t.token_enum()));
},
[=](const TokenInfo &l, const TokenInfo &r) {
- // MacroCallCloseToEndLine should be considered equivalent to ')', as
- // they are whitespace dependant
- if (((r.token_enum() == verilog_tokentype::MacroCallCloseToEndLine) &&
- (l.text() == ")")) ||
- ((l.token_enum() == verilog_tokentype::MacroCallCloseToEndLine) &&
- (r.text() == ")"))) {
+ if (TokensAreWhitespaceDependentFormatEquivalent(l, r)) {
return true;
}
return l.EquivalentWithoutLocation(r);
diff --git a/verible/verilog/analysis/verilog-equivalence_test.cc b/verible/verilog/analysis/verilog-equivalence_test.cc
index 90b0ee1..1cb9436 100644
--- a/verible/verilog/analysis/verilog-equivalence_test.cc
+++ b/verible/verilog/analysis/verilog-equivalence_test.cc
@@ -255,6 +255,21 @@
}
}
+// MacroIdentifier vs MacroIdItem depends on whether the macro ends the line.
+TEST(FormatEquivalentTest, EquivalenceOfMacroIdentifierAndMacroIdItem) {
+ const char *kSameSpelling[] = {
+ "assign x = f(`TOKEN);\n",
+ "assign x = f(\n`TOKEN\n);\n",
+ };
+ ExpectCompareWithErrstream(FormatEquivalent, DiffStatus::kEquivalent,
+ kSameSpelling[0], kSameSpelling[1]);
+
+ // Different macro names remain different.
+ ExpectCompareWithErrstream(FormatEquivalent, DiffStatus::kDifferent,
+ "assign x = f(`TOKEN);\n",
+ "assign x = f(`OTHER);\n");
+}
+
TEST(FormatEquivalentTest, DiagnosticMismatch) {
const char *kTestCases[] = {
"module foo;\n",
diff --git a/verible/verilog/formatting/format-style-init.cc b/verible/verilog/formatting/format-style-init.cc
index 2dde818..a6d010b 100644
--- a/verible/verilog/formatting/format-style-init.cc
+++ b/verible/verilog/formatting/format-style-init.cc
@@ -140,6 +140,13 @@
"Use compact binary expressions inside indexing / bit selection "
"operators");
+ABSL_FLAG(bool, class_parameter_space, false,
+ "If true, keep/insert a space before '#' in a class"
+ "parameterized typedef, e.g. \"typedef my_class #(.P(P)) "
+ "my_class_t;\". If false (default), no space is inserted, "
+ "matching the convention used for IEEE parameterized class "
+ "instantiations, e.g. \"type#(params...)::method(...)\".");
+
ABSL_FLAG(bool, wrap_end_else_clauses, false,
"Split end and else keywords into separate lines");
@@ -197,6 +204,7 @@
STYLE_FROM_FLAG(try_wrap_long_lines);
STYLE_FROM_FLAG(expand_coverpoints);
STYLE_FROM_FLAG(compact_indexing_and_selections);
+ STYLE_FROM_FLAG(class_parameter_space);
STYLE_FROM_FLAG(wrap_end_else_clauses);
STYLE_FROM_FLAG(alignment_group_boundary);
diff --git a/verible/verilog/formatting/format-style.h b/verible/verilog/formatting/format-style.h
index 2fad329..55347c5 100644
--- a/verible/verilog/formatting/format-style.h
+++ b/verible/verilog/formatting/format-style.h
@@ -146,6 +146,9 @@
// Compact binary expressions inside indexing / bit selection operators
bool compact_indexing_and_selections = true;
+ // Keep/insert a space before '#' in a parameterized class typedef
+ bool class_parameter_space = false;
+
// Split with a \n end and else clauses
bool wrap_end_else_clauses = false;
diff --git a/verible/verilog/formatting/formatter_test.cc b/verible/verilog/formatting/formatter_test.cc
index 5205a01..16e0572 100644
--- a/verible/verilog/formatting/formatter_test.cc
+++ b/verible/verilog/formatting/formatter_test.cc
@@ -4478,19 +4478,42 @@
" .L(L),\n"
" .W(W)\n"
") bar_t;\n"},
- // unqualified parameterized type keeps a space before '#'
+ // By default (class_parameter_space == false), no space before '#'
{"typedef dv_base_env_cov #(.CFG_T(tl_agent_env_cfg)) tl_agent_env_cov;\n",
- "typedef dv_base_env_cov #(\n"
+ "typedef dv_base_env_cov#(\n"
" .CFG_T(tl_agent_env_cfg)\n"
") tl_agent_env_cov;\n"},
- // ... and is inserted when absent
{"typedef dv_base_env_cov#(.CFG_T(tl_agent_env_cfg)) tl_agent_env_cov;\n",
- "typedef dv_base_env_cov #(\n"
+ "typedef dv_base_env_cov#(\n"
" .CFG_T(tl_agent_env_cfg)\n"
") tl_agent_env_cov;\n"},
// single short parameter stays on one line
{"typedef my_class #(.P(P)) my_class_t;\n",
- "typedef my_class #(.P(P)) my_class_t;\n"},
+ "typedef my_class#(.P(P)) my_class_t;\n"},
+
+ // let declarations each stay on their own line
+ {"module t;\n"
+ "let OFF = 4;\n"
+ "let UNIQUE = 32;\n"
+ "let PP(a) = 30 + a;\n"
+ "endmodule\n",
+ "module t;\n"
+ " let OFF = 4;\n"
+ " let UNIQUE = 32;\n"
+ " let PP(a) = 30 + a;\n"
+ "endmodule\n"},
+
+ // let declarations each stay on their own line
+ {"module t;\n"
+ "let OFF = 4;\n"
+ "let UNIQUE = 32;\n"
+ "let PP(a) = 30 + a;\n"
+ "endmodule\n",
+ "module t;\n"
+ " let OFF = 4;\n"
+ " let UNIQUE = 32;\n"
+ " let PP(a) = 30 + a;\n"
+ "endmodule\n"},
// package test cases
{"package fedex;localparam int www=3 ;endpackage : fedex\n",
@@ -4707,6 +4730,15 @@
" for (int i = 0; i < f(m); i--) begin\n"
" end\n"
"endfunction\n"},
+ {// for loop with an attribute instance in the initializer.
+ // Regression: this used to abort with a CHECK failure while reshaping
+ // the kForSpec partitions when an attribute appears in the header.
+ "module m; initial for(int i=0(* a *);i<4;i++) x=i; endmodule",
+ "module m;\n"
+ " initial\n"
+ " for (int i = 0 (* a *); i < 4; i++)\n"
+ " x = i;\n"
+ "endmodule\n"},
{// forever loop
"function\nvoid\tforevah;forever begin "
"++k\n;end endfunction\n",
@@ -18788,6 +18820,50 @@
}
}
+static constexpr FormatterTestCase
+ kSpaceBeforeHashInUnqualifiedTypedefTestCases[] = {
+ // unqualified parameterized type keeps a space before '#'
+ {"typedef dv_base_env_cov #(.CFG_T(tl_agent_env_cfg)) "
+ "tl_agent_env_cov;\n",
+ "typedef dv_base_env_cov #(\n"
+ " .CFG_T(tl_agent_env_cfg)\n"
+ ") tl_agent_env_cov;\n"},
+ // ... and is inserted when absent
+ {"typedef dv_base_env_cov#(.CFG_T(tl_agent_env_cfg)) "
+ "tl_agent_env_cov;\n",
+ "typedef dv_base_env_cov #(\n"
+ " .CFG_T(tl_agent_env_cfg)\n"
+ ") tl_agent_env_cov;\n"},
+ // single short parameter stays on one line
+ {"typedef my_class #(.P(P)) my_class_t;\n",
+ "typedef my_class #(.P(P)) my_class_t;\n"},
+ // package-qualified types are unaffected (no space before '#')
+ {"typedef foo_pkg::baz_t#(.L(L), .W(W)) bar_t;\n",
+ "typedef foo_pkg::baz_t#(\n"
+ " .L(L),\n"
+ " .W(W)\n"
+ ") bar_t;\n"},
+};
+
+TEST(FormatterEndToEndTest, SpaceBeforeHashInUnqualifiedTypedefTestCases) {
+ // Use a fixed style.
+ FormatStyle style;
+ style.column_limit = 40;
+ style.indentation_spaces = 2;
+ style.wrap_spaces = 4;
+ style.class_parameter_space = true;
+
+ for (const auto &test_case : kSpaceBeforeHashInUnqualifiedTypedefTestCases) {
+ VLOG(1) << "code-to-format:\n" << test_case.input << "<EOF>";
+ std::ostringstream stream;
+ const auto status =
+ FormatVerilog(test_case.input, "<filename>", style, stream);
+ // Require these test cases to be valid.
+ EXPECT_OK(status) << status.message();
+ EXPECT_EQ(stream.str(), test_case.expected) << "code:\n" << test_case.input;
+ }
+}
+
static constexpr FormatterTestCase kFunctionCallsWithComments[] = {
{// no comments
"module foo;\n"
@@ -19380,6 +19456,68 @@
}
}
+// Regression for https://github.com/chipsalliance/verible/issues/2547:
+// A localparam initialized to a sum of long macros must converge: infix `+`
+// stays with the following operand so re-format does not oscillate between
+// `+\n`MACRO` and `+ `MACRO`.
+TEST(FormatterEndToEndTest, LongMacroSumLocalparamConverges) {
+ static constexpr FormatterTestCase kTestCases[] = {
+ {"module m;\n"
+ " localparam N =\n"
+ " `MACRO_GEN3_SCRAMBLE_LFSR_REGOUT\n"
+ " + `MACRO_GEN3_SCRAMBLE_REGIN\n"
+ " + `MACRO_GEN3_SCRAMBLE_REGOUT\n"
+ " ;\n"
+ "endmodule\n",
+ "module m;\n"
+ " localparam N =\n"
+ " `MACRO_GEN3_SCRAMBLE_LFSR_REGOUT\n"
+ " + `MACRO_GEN3_SCRAMBLE_REGIN\n"
+ " + `MACRO_GEN3_SCRAMBLE_REGOUT;\n"
+ "endmodule\n"},
+ // Already in pass-1 form must stay stable.
+ {"module m;\n"
+ " localparam N =\n"
+ " `MACRO_GEN3_SCRAMBLE_LFSR_REGOUT\n"
+ " + `MACRO_GEN3_SCRAMBLE_REGIN\n"
+ " + `MACRO_GEN3_SCRAMBLE_REGOUT;\n"
+ "endmodule\n",
+ "module m;\n"
+ " localparam N =\n"
+ " `MACRO_GEN3_SCRAMBLE_LFSR_REGOUT\n"
+ " + `MACRO_GEN3_SCRAMBLE_REGIN\n"
+ " + `MACRO_GEN3_SCRAMBLE_REGOUT;\n"
+ "endmodule\n"},
+ };
+ FormatStyle style; // default column_limit (100)
+ for (const auto &test_case : kTestCases) {
+ VLOG(1) << "code-to-format:\n" << test_case.input << "<EOF>";
+ std::ostringstream stream;
+ const auto status =
+ FormatVerilog(test_case.input, "<filename>", style, stream);
+ EXPECT_OK(status) << status.message();
+ EXPECT_EQ(stream.str(), test_case.expected) << "code:\n" << test_case.input;
+ }
+}
+
+// Regression for https://github.com/chipsalliance/verible/issues/2544:
+// Wrapping a $bits(...)'(...) cast may leave `MACRO at EOL, reclassifying
+// MacroIdentifier as MacroIdItem. FormatEquivalent must accept that, and
+// formatting must still pass verification.
+TEST(FormatterEndToEndTest, MacroBeforeCloseParenFormatEquivalent) {
+ static constexpr std::string_view kInput =
+ "module m;\n"
+ " assign result_value = $bits(result_value)'( "
+ "compare_bytes(input_data[DATA_WIDTH_INT-1:0], "
+ "input_datak[STROBE_WIDTH_INT-1:0], `TOKEN_BYTE) );\n"
+ "endmodule\n";
+ FormatStyle style;
+ std::ostringstream stream;
+ const auto status = FormatVerilog(kInput, "<filename>", style, stream);
+ EXPECT_OK(status) << status.message();
+ EXPECT_THAT(stream.str(), testing::HasSubstr("`TOKEN_BYTE"));
+}
+
// Regression for https://github.com/chipsalliance/verible/issues/2542:
// Continuation EOL comments after a wrapped assign must keep a stable column
// across re-format (convergence).
diff --git a/verible/verilog/formatting/token-annotator.cc b/verible/verilog/formatting/token-annotator.cc
index def1349..ebaeb21 100644
--- a/verible/verilog/formatting/token-annotator.cc
+++ b/verible/verilog/formatting/token-annotator.cc
@@ -510,7 +510,8 @@
// This may be controversial or context-dependent, as parameterized
// classes often appear with method calls like:
// type#(params...)::method(...);
- // A parameterized type in a typedef keeps the space before '#':
+ // If style.class_parameter_space is enabled, a
+ // parameterized type in a typedef keeps the space before '#':
// typedef my_class #(.P(P)) my_class_t;
// but a package-qualified type does not, matching the existing
// "type#(params...)::method(...)" convention:
@@ -518,6 +519,7 @@
// Kept as separate IsInsideFirst() calls because MatchesTagAnyOf()
// only unrolls up to four tags.
const bool inside_unqualified_typedef =
+ style.class_parameter_space &&
left_context.IsInsideFirst({NodeEnum::kTypeDeclaration}, {}) &&
!left_context.IsInsideFirst({NodeEnum::kQualifiedId}, {});
diff --git a/verible/verilog/formatting/tree-unwrapper.cc b/verible/verilog/formatting/tree-unwrapper.cc
index a6601ac..09fa8c0 100644
--- a/verible/verilog/formatting/tree-unwrapper.cc
+++ b/verible/verilog/formatting/tree-unwrapper.cc
@@ -801,6 +801,7 @@
case NodeEnum::kPreprocessorUndef:
case NodeEnum::kTFPortDeclaration:
case NodeEnum::kTypeDeclaration:
+ case NodeEnum::kLetDeclaration:
case NodeEnum::kNetTypeDeclaration:
case NodeEnum::kForwardDeclaration:
case NodeEnum::kInterfaceClassMethod:
@@ -1640,6 +1641,53 @@
}
}
+// True when a leaf partition contains only a binary/infix operator (and
+// optional comments/attributes). Used to normalize wrapping of expressions
+// like `A + B + C` so operators always stay with the following operand.
+static bool PartitionIsInfixOperatorOnly(const TokenPartitionTree &partition) {
+ if (!is_leaf(partition)) return false;
+ const auto tokens = partition.Value().TokensRange();
+ if (tokens.empty()) return false;
+
+ const verible::PreFormatToken *op = nullptr;
+ for (const auto &token : tokens) {
+ switch (token.TokenEnum()) {
+ case verilog_tokentype::TK_COMMENT_BLOCK:
+ case verilog_tokentype::TK_EOL_COMMENT:
+ case verilog_tokentype::TK_ATTRIBUTE:
+ break;
+ default:
+ if (GetFormatTokenType(static_cast<verilog_tokentype>(
+ token.TokenEnum())) != FormatTokenType::binary_operator ||
+ op != nullptr) {
+ return false;
+ }
+ op = &token;
+ break;
+ }
+ }
+ return op != nullptr;
+}
+
+// Always attach infix-operator-only partitions to the following operand.
+// Attachment based on original newlines is unstable for macro sums:
+// `A\n+\n`B vs `A\n+ `B produce different partition shapes and oscillate
+// under re-format (GitHub issue 2547).
+static void AttachInfixOperatorsToFollowingOperands(
+ TokenPartitionTree *partition) {
+ // Iterate by index; merges invalidate sibling pointers.
+ for (int i = 0; i < static_cast<int>(partition->Children().size()); ++i) {
+ auto &child = partition->Children()[i];
+ if (!PartitionIsInfixOperatorOnly(child)) continue;
+ if (NextLeaf(child) == nullptr) continue;
+ VLOG(4) << "Attaching infix operator partition to following operand:\n"
+ << child;
+ verible::MergeLeafIntoNextLeaf(&child);
+ // Children shifted; re-check current index.
+ --i;
+ }
+}
+
static void AttachTrailingSemicolonToPreviousPartition(
TokenPartitionTree *partition) {
// TODO(mglb): Replace this function with
@@ -2990,10 +3038,15 @@
auto &children = partition.Children();
const auto iter1 = std::find_if(children.begin(), children.end(),
PartitionStartsWithSemicolon);
- CHECK(iter1 != children.end());
+ // An attribute instance ((* ... *)) in the for-loop header can change
+ // the partition structure so that the expected semicolon-leading
+ // partitions are not present. When they are missing, leave the
+ // partitions unreshaped rather than aborting (avoids a CHECK-failure
+ // crash on attributed/unusual for-headers).
+ if (iter1 == children.end()) break;
const auto iter2 =
std::find_if(iter1 + 1, children.end(), PartitionStartsWithSemicolon);
- CHECK(iter2 != children.end());
+ if (iter2 == children.end()) break;
const int dist1 = std::distance(children.begin(), iter1);
const int dist2 = std::distance(children.begin(), iter2);
VLOG(4) << "kForSpec got ';' at child " << dist1 << " and " << dist2;
@@ -3131,6 +3184,7 @@
case NodeEnum::kParamDeclaration: {
AttachTrailingSemicolonToPreviousPartition(&partition);
AttachOpeningBraceToDeclarationsAssignmentOperator(&partition);
+ AttachInfixOperatorsToFollowingOperands(&partition);
break;
}
diff --git a/verible/verilog/preprocessor/verilog-preprocess.cc b/verible/verilog/preprocessor/verilog-preprocess.cc
index 09189ed..39e816a 100644
--- a/verible/verilog/preprocessor/verilog-preprocess.cc
+++ b/verible/verilog/preprocessor/verilog-preprocess.cc
@@ -245,11 +245,15 @@
if ((*token_iter)->text() == "(") {
token_iter = GenerateBypassWhiteSpaces(generator); // skip the "("
} else {
+ preprocess_data_.errors.emplace_back(
+ **token_iter,
+ "Error it is illegal to call a callable macro without ().");
return absl::InvalidArgumentError(
"Error it is illegal to call a callable macro without ().");
}
while (parameters_size > 0) {
+ if ((*token_iter)->isEOF()) break; // truncated call; stop scanning args
if ((*token_iter)->token_enum() == MacroArg) {
macro_call->positional_arguments.emplace_back(**token_iter);
token_iter = GenerateBypassWhiteSpaces(generator);
@@ -359,6 +363,10 @@
lexer.DoNextToken()) {
lexed_sequence.push_back(lexer.GetLastToken());
}
+ // Retain the EOF token as an end sentinel so a truncated callable-macro
+ // invocation stops at EOF in GenerateBypassWhiteSpaces instead of
+ // dereferencing past the end of the stream view.
+ lexed_sequence.push_back(lexer.GetLastToken());
verible::TokenStreamView lexed_streamview;
// Initializing the lexed token stream view.
InitTokenStreamView(lexed_sequence, &lexed_streamview);
@@ -369,6 +377,7 @@
// Token-pulling loop.
for (auto iter = iter_generator(); iter != end; iter = iter_generator()) {
auto &last_token = **iter;
+ if (last_token.isEOF()) break; // end sentinel; nothing to forward
// TODO: handle lexical error
if (lexer.GetLastToken().token_enum() == TK_SPACE) {
continue; // don't forward spaces
@@ -413,6 +422,8 @@
lexer.DoNextToken()) {
lexed_sequence.push_back(lexer.GetLastToken());
}
+ // Retain EOF end sentinel (see ExpandText).
+ lexed_sequence.push_back(lexer.GetLastToken());
verible::TokenStreamView lexed_streamview;
// Initializing the lexed token stream view.
InitTokenStreamView(lexed_sequence, &lexed_streamview);
@@ -424,6 +435,7 @@
for (auto iter = iter_generator(); iter != end; iter = iter_generator()) {
// TODO: handle lexical error
auto &last_token = **iter;
+ if (last_token.isEOF()) break; // end sentinel; nothing to forward
if (last_token.token_enum() == TK_SPACE) continue; // don't forward spaces
// If the expanded token is another macro identifier that needs to be
// expanded.
@@ -652,6 +664,9 @@
lexer.DoNextToken()) {
included_sequence.push_back(lexer.GetLastToken());
}
+ // Retain EOF end sentinel; the child ScanStream expects an EOF-terminated
+ // stream.
+ included_sequence.push_back(lexer.GetLastToken());
// Preprocessing the included file tokens.
verible::TokenStreamView lexed_streamview;
@@ -674,8 +689,11 @@
preprocess_data_.included_text_structure.push_back(std::move(u));
}
- // Forwarding the included preprocessed view.
+ // Forwarding the included preprocessed view. The EOF end sentinel appended
+ // above is consumed by the child ScanStream and must not be spliced into the
+ // middle of the parent's token stream.
for (const auto &u : child_preprocessed_data.preprocessed_token_stream) {
+ if (u->isEOF()) continue;
preprocess_data_.preprocessed_token_stream.push_back(u);
}
diff --git a/verible/verilog/preprocessor/verilog-preprocess.h b/verible/verilog/preprocessor/verilog-preprocess.h
index 2d134ae..0d932f0 100644
--- a/verible/verilog/preprocessor/verilog-preprocess.h
+++ b/verible/verilog/preprocessor/verilog-preprocess.h
@@ -164,9 +164,11 @@
absl::Status HandleElse(TokenStreamView::const_iterator else_pos);
absl::Status HandleEndif(TokenStreamView::const_iterator endif_pos);
- static absl::Status ConsumeAndParseMacroCall(
- TokenStreamView::const_iterator, const StreamIteratorGenerator &,
- verible::MacroCall *, const verible::MacroDefinition &);
+ // Non-static so it can record diagnostics into preprocess_data_.errors.
+ absl::Status ConsumeAndParseMacroCall(TokenStreamView::const_iterator,
+ const StreamIteratorGenerator &,
+ verible::MacroCall *,
+ const verible::MacroDefinition &);
// The following functions return nullptr when there is no error:
absl::Status ConsumeMacroDefinition(const StreamIteratorGenerator &,
diff --git a/verible/verilog/preprocessor/verilog-preprocess_test.cc b/verible/verilog/preprocessor/verilog-preprocess_test.cc
index 64817c7..c5d8eaa 100644
--- a/verible/verilog/preprocessor/verilog-preprocess_test.cc
+++ b/verible/verilog/preprocessor/verilog-preprocess_test.cc
@@ -1044,5 +1044,30 @@
<< error.error_message;
}
+// Regression: a callable-macro invocation truncated at end-of-stream (no '(',
+// or '(' with no matching ')') must not crash or hang the preprocessor. Before
+// the fix these inputs dereferenced past the end of the token stream view
+// (SIGSEGV) or spun forever scanning arguments. With error-surfacing enabled
+// the no-'(' cases also report a preprocessor diagnostic.
+TEST(VerilogPreprocessTest, TruncatedCallableMacroDoesNotCrash) {
+ constexpr std::string_view kNoParenInputs[] = {
+ "`define A(x) hello `A\n`A(1)\n", // truncated callable ref in macro body
+ "`define A(x) x\n`A\n", // truncated callable ref at top level
+ };
+ for (std::string_view input : kNoParenInputs) {
+ PreprocessorTester tester(
+ input, VerilogPreprocess::Config({.expand_macros = true}));
+ EXPECT_FALSE(tester.Status().ok()) << input;
+ EXPECT_GE(tester.PreprocessorData().errors.size(), 1) << input;
+ }
+
+ // '(' with no matching ')': must terminate (was an infinite loop). The
+ // residue is rejected downstream, so only assert non-OK here.
+ PreprocessorTester open_paren(
+ "`define C(z) z\n`define A(x) hello `C(\n`A(1)\n",
+ VerilogPreprocess::Config({.expand_macros = true}));
+ EXPECT_FALSE(open_paren.Status().ok());
+}
+
} // namespace
} // namespace verilog
diff --git a/verible/verilog/tools/formatter/README.md b/verible/verilog/tools/formatter/README.md
index 01b2869..a3ffb45 100644
--- a/verible/verilog/tools/formatter/README.md
+++ b/verible/verilog/tools/formatter/README.md
@@ -42,6 +42,11 @@
{align,flush-left,preserve,infer}); default: infer;
--class_member_variable_alignment (Format class member variables:
{align,flush-left,preserve,infer}); default: infer;
+ --class_parameter_space (If true, keep/insert a space
+ before '#' in a class parameterized typedef, e.g. "typedef
+ my_class #(.P(P)) my_class_t;". If false (default), no space is
+ inserted, matching the IEEE convention used for parameterized class
+ instantiations, e.g. "type#(params...)::method(...)".); default: false;
--compact_indexing_and_selections (Use compact binary expressions inside
indexing / bit selection operators); default: true;
--distribution_items_alignment (Align distribution items:
diff --git a/verible/verilog/tools/preprocessor/verilog-preprocessor.cc b/verible/verilog/tools/preprocessor/verilog-preprocessor.cc
index c23fdf7..26907dc 100644
--- a/verible/verilog/tools/preprocessor/verilog-preprocessor.cc
+++ b/verible/verilog/tools/preprocessor/verilog-preprocessor.cc
@@ -122,13 +122,17 @@
// source code just like it was, but with conditionals filtered.
lexed_sequence.push_back(lexer.GetLastToken());
}
+ lexed_sequence.push_back(lexer.GetLastToken()); // EOF end sentinel
verible::TokenStreamView lexed_streamview;
// Initializing the lexed token stream view.
InitTokenStreamView(lexed_sequence, &lexed_streamview);
verilog::VerilogPreprocessData preprocessed_data =
preprocessor.ScanStream(lexed_streamview);
auto &preprocessed_stream = preprocessed_data.preprocessed_token_stream;
- for (auto u : preprocessed_stream) outs << u->text();
+ for (auto u : preprocessed_stream) {
+ if (u->isEOF()) continue; // end sentinel, not part of the source
+ outs << u->text();
+ }
for (auto &u : preprocessed_data.errors) outs << u.error_message << '\n';
if (!preprocessed_data.errors.empty()) {
return absl::InvalidArgumentError("Error: The preprocessing has failed.");