P4C
The P4 Compiler
Loading...
Searching...
No Matches
format.h
1// SPDX-FileCopyrightText: 2026 The P4 Language Consortium
2//
3// SPDX-License-Identifier: Apache-2.0
4
5#ifndef LIB_FORMAT_H_
6#define LIB_FORMAT_H_
7
8#include <array>
9#include <functional>
10#include <ostream>
11#include <sstream>
12#include <string>
13#include <string_view>
14#include <tuple>
15#include <type_traits>
16#include <utility>
17
18#include "absl/strings/str_cat.h"
19#include "absl/strings/str_format.h"
20#include "absl/types/span.h"
21#include "lib/source_file.h"
22#include "lib/stringify.h"
23
24namespace P4 {
25namespace FormatDetail {
26
27// Normalize supported placeholders to positional Abseil conversions. Arguments
28// have two representations: native values followed by their stream adapters.
29// %N% and %s use the stream adapters for an argument's text representation.
30std::string normalizeFormat(std::string_view format, size_t argumentCount);
31std::string formatMessage(std::string_view format, absl::Span<const absl::FormatArg> arguments);
32void setFormatFlags(std::ostream &out, const absl::FormatConversionSpec &spec);
33bool appendStreamArgument(std::string text, const absl::FormatConversionSpec &spec,
34 absl::FormatSink *sink, bool arithmetic);
35
36template <bool PreferDbprint, typename T>
37void streamArgument(std::ostream &out, const T &arg) {
38 // Keep arrays distinct from pointers: string literals cannot be null.
39 using Type = std::remove_cvref_t<T>;
40 if constexpr (std::is_same_v<Type, Util::SourceInfo>) {
41 // A source location consumes a placeholder but contributes no text.
42 } else if constexpr (std::is_same_v<Type, std::nullptr_t>) {
43 out << "<nullptr>";
44 } else if constexpr (std::is_pointer_v<Type>) {
45 using Pointee = std::remove_pointer_t<Type>;
46 if (arg == nullptr) {
47 out << "<nullptr>";
48 } else if constexpr (std::is_same_v<std::remove_cv_t<Pointee>, char> ||
49 std::is_void_v<Pointee> || std::is_function_v<Pointee>) {
50 out << arg;
51 } else if constexpr (std::is_same_v<std::remove_cv_t<Pointee>, Util::SourceInfo>) {
52 // SourceInfo pointers follow the same convention as references.
53 } else if constexpr (PreferDbprint) {
54 // Bug reports use pointer insertion unless dbprint exists.
55 if constexpr (has_dbprint_v<Pointee>) {
56 arg->dbprint(out);
57 } else {
58 out << arg;
59 }
60 } else {
61 streamArgument<PreferDbprint>(out, *arg);
62 }
63 } else if constexpr (PreferDbprint && has_dbprint_v<Type>) {
64 arg.dbprint(out);
65 } else if constexpr (!PreferDbprint && Util::has_toString_v<Type>) {
66 out << arg.toString();
67 } else {
68 static_assert(has_ostream_operator_v<T>,
69 "cannot format this type; implement toString, dbprint, or operator<<");
70 out << arg;
71 }
72}
73
74// Keep references: diagnostic arguments can be noncopyable IR nodes, and
75// FormatArg itself does not own the value to which it refers.
76template <bool PreferDbprint, typename T>
78 std::reference_wrapper<const T> value;
79
80 friend absl::FormatConvertResult<
81 absl::FormatConversionCharSet::kString | absl::FormatConversionCharSet::kNumeric |
82 absl::FormatConversionCharSet::c | absl::FormatConversionCharSet::v |
83 absl::FormatConversionCharSet::p>
84 // Abseil requires this customization-point name.
85 // NOLINTNEXTLINE(readability-identifier-naming)
86 AbslFormatConvert(const StreamArgument &arg, const absl::FormatConversionSpec &spec,
87 absl::FormatSink *sink) {
88 std::ostringstream out;
89 setFormatFlags(out, spec);
90 if constexpr (std::is_pointer_v<T> && !std::is_function_v<std::remove_pointer_t<T>>) {
91 if (spec.conversion_char() == absl::FormatConversionChar::p) {
92 out << static_cast<const void *>(arg.value.get());
93 } else {
94 streamArgument<PreferDbprint>(out, arg.value.get());
95 }
96 } else {
97 streamArgument<PreferDbprint>(out, arg.value.get());
98 }
99 return {appendStreamArgument(out.str(), spec, sink, std::is_arithmetic_v<T>)};
100 }
101};
102
103template <bool PreferDbprint, typename T>
104absl::FormatArg nativeArgument(const StreamArgument<PreferDbprint, T> &arg) {
105 if constexpr (std::is_arithmetic_v<T>) {
106 return absl::FormatArg(arg.value.get());
107 } else {
108 return absl::FormatArg(arg);
109 }
110}
111
112template <bool PreferDbprint, typename Tuple, size_t... Is>
113std::string formatTuple(std::string_view format, const Tuple &args,
114 [[maybe_unused]] std::index_sequence<Is...> indices) {
115 if constexpr (sizeof...(Is) == 0) {
116 // Still parse the format to handle %% and reject missing arguments.
117 return formatMessage(format, {});
118 } else {
119 auto adapters = std::tuple{
121 std::get<Is>(args)}...};
122 std::array<absl::FormatArg, 2 * sizeof...(Is)> arguments{
123 nativeArgument(std::get<Is>(adapters))..., absl::FormatArg(std::get<Is>(adapters))...};
124 return formatMessage(format, arguments);
125 }
126}
127
128// Use the same source-info traversal for errors and bugs, including pointers
129// to SourceInfo. Unrelated arguments must not erase previously found locations.
130template <typename T, typename Emit>
131void visitSourceInfo(const T &arg, const Emit &emit) {
132 using Type = std::remove_cvref_t<T>;
133 if constexpr (std::is_same_v<Type, Util::SourceInfo>) {
134 if (arg.isValid()) {
135 emit(arg);
136 }
137 } else if constexpr (std::is_pointer_v<Type>) {
138 if constexpr (!std::is_void_v<std::remove_pointer_t<Type>> &&
139 !std::is_function_v<std::remove_pointer_t<Type>>) {
140 if (arg != nullptr) {
141 visitSourceInfo(*arg, emit);
142 }
143 }
144 } else if constexpr (Util::has_SourceInfo_v<Type>) {
145 visitSourceInfo(arg.getSourceInfo(), emit);
146 }
147}
148
149} // namespace FormatDetail
150
157template <bool PreferDbprint = false, typename Tuple>
158std::string createFormattedMessageFromTuple(const char *format, const Tuple &args) {
159 return FormatDetail::formatTuple<PreferDbprint>(
160 format, args, std::make_index_sequence<std::tuple_size_v<Tuple>>{});
161}
162
163template <bool PreferDbprint = false, typename... Args>
164std::string createFormattedMessage(const char *format, const Args &...args) {
165 return createFormattedMessageFromTuple<PreferDbprint>(format, std::tie(args...));
166}
167
168template <typename... Args>
169std::string createBugMessage(const char *format, const Args &...args) {
170 std::string position;
171 std::string tail;
172 if constexpr (sizeof...(Args) != 0) {
173 auto collect = [&](const Util::SourceInfo &info) {
174 auto pos = info.toPositionString();
175 if (position.empty()) {
176 position = pos;
177 } else if (!pos.isNullOrEmpty()) {
178 absl::StrAppend(&tail, pos, "\n");
179 }
180 absl::StrAppend(&tail, info.toSourceFragment());
181 };
182 (FormatDetail::visitSourceInfo(args, collect), ...);
183 }
184 return absl::StrCat(position, position.empty() ? "" : ": ",
185 createFormattedMessage<true>(format, args...), "\n", tail);
186}
187
188} // namespace P4
189
190#endif /* LIB_FORMAT_H_ */
Definition source_file.h:123
TODO: this is not really specific to BMV2, it should reside somewhere else.
Definition applyOptionsPragmas.cpp:13
std::string createFormattedMessageFromTuple(const char *format, const Tuple &args)
Definition format.h:158
void info(const int kind, const char *format, const T *node, Args &&...args)
Report info messages of type kind. Requires that the node argument have source info.
Definition lib/error.h:157
Definition format.h:77