MimIR 0.1
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
print.h
Go to the documentation of this file.
1#pragma once
2
3#include <functional>
4#include <iostream>
5#include <ostream>
6#include <ranges>
7#include <sstream>
8#include <string>
9
10#include <fe/assert.h>
11
12namespace mim {
13namespace detail {
14
15/// Does @p T provide an ostream operator?
16template<class T>
17concept Streamable = requires(std::ostream& os, T a) { os << a; };
18
19/// Does @p has a @p range and a function @p f to invoke?
20template<class T>
21concept Elemable = requires(T elem) {
22 elem.range;
23 elem.f;
24};
25
26template<class R, class F>
27std::ostream& range(std::ostream& os, const R& r, F f, const char* sep = ", ") {
28 for (const char* curr_sep = ""; const auto& elem : r) {
29 for (auto i = curr_sep; *i != '\0'; ++i)
30 os << *i;
31
32 if constexpr (std::is_invocable_v<F, std::ostream&, decltype(elem)>)
33 std::invoke(f, os, elem);
34 else
35 std::invoke(f, elem);
36 curr_sep = sep;
37 }
38 return os;
39}
40
41bool match2nd(std::ostream& os, const char* next, const char*& s, const char c);
42} // namespace detail
43
44/// @name Formatted Output
45/// @anchor fmt
46/// Provides a `printf`-like interface to format @p s with @p args and puts it into @p os.
47/// Use `{}` as a placeholder within your format string @p s.
48/// * By default, `os << t` will be used to stream the appropriate argument:
49/// ```
50/// print(os, "int: {}, float: {}", 23, 42.f);
51/// ```
52/// * You can also place a function as argument to inject arbitrary code:
53/// ```
54/// print(os, "int: {}, fun: {}, float: {}", 23, [&]() { os << "hey :)"}, 42.f);
55/// ```
56/// * There is also a variant to pass @p os to the function, if you don't have easy access to it:
57/// ```
58/// print(os, "int: {}, fun: {}, float: {}", 23, [&](auto& os) { os << "hey :)"}, 42.f);
59/// ```
60/// * You can put a `std::ranges::range` as argument.
61/// This will output a list - using the given specifier as separator.
62/// Each element `elem` of the range will be output via `os << elem`:
63/// ```
64/// std::vector<int> v = {0, 1, 2, 3};
65/// print(os, "v: {, }", v);
66/// ```
67/// * If you have a `std::ranges::range` that needs to be formatted in a more complicated way, bundle it with an Elem:
68/// ```
69/// std::vector<int> v = {3, 2, 1, 0};
70/// size_t i = 0;
71/// print(os, "v: {, }", Elem(v, [&](auto elem) { print(os, "{}: {}", i++, elem); }));
72/// ```
73/// * You again have the option to pass @p os to the bundled function:
74/// ```
75/// std::vector<int> v = {3, 2, 1, 0};
76/// size_t i = 0;
77/// print(os, "v: {, }", Elem(v, [&](auto& os, auto elem) { print(os, "{}: {}", i++, elem); }));
78/// * Use `{{` or `}}` to literally output `{` or `{`:
79/// print(os, "{{{}}}", 23); // "{23}"
80/// ```
81/// @see Tab
82///@{
83
84/// Use with print to output complicated `std::ranges::range`s.
85template<class R, class F>
86struct Elem {
87 Elem(const R& range, const F& f)
88 : range(range)
89 , f(f) {}
90
91 const R range;
92 const F f;
93};
94
95/// Wrap all elements in @p range through `os << T(elem)`.
96template<class T>
97auto elems(std::ostream& os, const auto& range) {
98 return Elem(range, [&os](const auto& elem) { os << T(elem); });
99}
100
101/// Create function-wrapper objects amenable for `opeartor<<`.
102template<class F>
103struct StreamFn {
104 F f;
105 friend std::ostream& operator<<(std::ostream& os, const StreamFn& s) { return s.f(os); }
106};
107
108template<class F>
110
111std::ostream& print(std::ostream& os, const char* s); ///< Base case.
112
113template<class T, class... Args>
114std::ostream& print(std::ostream& os, const char* s, T&& t, Args&&... args) {
115 while (*s != '\0') {
116 auto next = s + 1;
117
118 switch (*s) {
119 case '{': {
120 if (detail::match2nd(os, next, s, '{')) continue;
121 s++; // skip opening brace '{'
122
123 std::string spec;
124 while (*s != '\0' && *s != '}')
125 spec.push_back(*s++);
126 assert(*s == '}' && "unmatched closing brace '}' in format string");
127
128 if constexpr (std::is_invocable_v<decltype(t)>) {
129 std::invoke(t);
130 } else if constexpr (std::is_invocable_v<decltype(t), std::ostream&>) {
131 std::invoke(t, os);
132 } else if constexpr (detail::Streamable<decltype(t)>) {
133 auto flags = std::ios_base::fmtflags(os.flags());
134
135 if (spec == "b")
136 assert(false && "TODO");
137 else if (spec == "o")
138 os << std::oct;
139 else if (spec == "d")
140 os << std::dec;
141 else if (spec == "x")
142 os << std::hex;
143
144 os << t;
145 os.flags(flags);
146 } else if constexpr (detail::Elemable<decltype(t)>) {
147 detail::range(os, t.range, t.f, spec.c_str());
148 } else if constexpr (std::ranges::range<decltype(t)>) {
149 detail::range(os, t, [&](const auto& x) { os << x; }, spec.c_str());
150 } else {
151 []<bool flag = false>() { static_assert(flag, "cannot print T t"); }();
152 }
153
154 ++s; // skip closing brace '}'
155 // call even when *s == '\0' to detect extra arguments
156 return print(os, s, std::forward<Args>(args)...);
157 }
158 case '}':
159 if (detail::match2nd(os, next, s, '}')) continue;
160 assert(false && "unmatched/unescaped closing brace '}' in format string");
161 fe::unreachable();
162 default: os << *s++;
163 }
164 }
165
166 assert(false && "invalid format string for 's'");
167 fe::unreachable();
168}
169
170/// As above but end with `std::endl`.
171template<class... Args>
172std::ostream& println(std::ostream& os, const char* fmt, Args&&... args) {
173 return print(os, fmt, std::forward<Args>(args)...) << std::endl;
174}
175
176/// Wraps mim::print to output a formatted `std:string`.
177template<class... Args>
178std::string fmt(const char* s, Args&&... args) {
179 std::ostringstream os;
180 print(os, s, std::forward<Args>(args)...);
181 return os.str();
182}
183
184/// Wraps mim::print to throw `T` with a formatted message.
185template<class T = std::logic_error, class... Args>
186[[noreturn]] void error(const char* fmt, Args&&... args) {
187 std::ostringstream oss;
188 print(oss << "error: ", fmt, std::forward<Args>(args)...);
189 throw T(oss.str());
190}
191
192#ifdef NDEBUG
193# define assertf(condition, ...) \
194 do { \
195 (void)sizeof(condition); \
196 } while (false)
197#else
198# define assertf(condition, ...) \
199 do { \
200 if (!(condition)) { \
201 mim::errf("{}:{}: assertion: ", __FILE__, __LINE__); \
202 mim::errln(__VA_ARGS__); \
203 fe::breakpoint(); \
204 } \
205 } while (false)
206#endif
207///@}
208
209/// @name out/err
210/// mim::print%s to `std::cout`/`std::cerr`; the *`ln` variants emit an additional `std::endl`.
211///@{
212// clang-format off
213template<class... Args> std::ostream& outf (const char* fmt, Args&&... args) { return print(std::cout, fmt, std::forward<Args>(args)...); }
214template<class... Args> std::ostream& errf (const char* fmt, Args&&... args) { return print(std::cerr, fmt, std::forward<Args>(args)...); }
215template<class... Args> std::ostream& outln(const char* fmt, Args&&... args) { return outf(fmt, std::forward<Args>(args)...) << std::endl; }
216template<class... Args> std::ostream& errln(const char* fmt, Args&&... args) { return errf(fmt, std::forward<Args>(args)...) << std::endl; }
217// clang-format on
218///@}
219
220/// Keeps track of indentation level.
221class Tab {
222public:
223 Tab(std::string_view tab = {" "}, size_t indent = 0)
224 : tab_(tab)
225 , indent_(indent) {}
226
227 /// @name Getters
228 ///@{
229 size_t indent() const { return indent_; }
230 std::string_view tab() const { return tab_; }
231 ///@}
232
233 /// @name print
234 /// Wraps mim::print to prefix it with indentation.
235 /// @see @ref fmt "Formatted Output"
236 ///@{
237 template<class... Args>
238 std::ostream& print(std::ostream& os, const char* s, Args&&... args) {
239 for (size_t i = 0; i < indent_; ++i)
240 os << tab_;
241 return mim::print(os, s, std::forward<Args>(args)...);
242 }
243 /// Same as Tab::print but **prepends** a `std::endl` to @p os.
244 template<class... Args>
245 std::ostream& lnprint(std::ostream& os, const char* s, Args&&... args) {
246 return print(os << std::endl, s, std::forward<Args>(args)...);
247 }
248 /// Same as Tab::print but **appends** a `std::endl` to @p os.
249 template<class... Args>
250 std::ostream& println(std::ostream& os, const char* s, Args&&... args) {
251 return print(os, s, std::forward<Args>(args)...) << std::endl;
252 }
253 ///@}
254
255 // clang-format off
256 /// @name Creates a new Tab
257 ///@{
258 [[nodiscard]] Tab operator++(int) { return {tab_, indent_++}; }
259 [[nodiscard]] Tab operator--(int) { assert(indent_ > 0); return {tab_, indent_--}; }
260 [[nodiscard]] Tab operator+(size_t indent) const { return {tab_, indent_ + indent}; }
261 [[nodiscard]] Tab operator-(size_t indent) const { assert(indent_ > 0); return {tab_, indent_ - indent}; }
262 ///@}
263
264 /// @name Modifies this Tab
265 ///@{
266 Tab& operator++() { ++indent_; return *this; }
267 Tab& operator--() { assert(indent_ > 0); --indent_; return *this; }
268 Tab& operator+=(size_t indent) { indent_ += indent; return *this; }
269 Tab& operator-=(size_t indent) { assert(indent_ > 0); indent_ -= indent; return *this; }
270 Tab& operator=(size_t indent) { indent_ = indent; return *this; }
271 Tab& operator=(std::string_view tab) { tab_ = tab; return *this; }
272 ///@}
273 // clang-format on
274
275private:
276 std::string_view tab_;
277 size_t indent_ = 0;
278};
279
280} // namespace mim
Tab operator-(size_t indent) const
Definition print.h:261
Tab operator--(int)
Definition print.h:259
Tab & operator--()
Definition print.h:267
Tab & operator=(size_t indent)
Definition print.h:270
std::string_view tab() const
Definition print.h:230
std::ostream & println(std::ostream &os, const char *s, Args &&... args)
Same as Tab::print but appends a std::endl to os.
Definition print.h:250
Tab & operator=(std::string_view tab)
Definition print.h:271
Tab & operator-=(size_t indent)
Definition print.h:269
std::ostream & lnprint(std::ostream &os, const char *s, Args &&... args)
Same as Tab::print but prepends a std::endl to os.
Definition print.h:245
Tab & operator+=(size_t indent)
Definition print.h:268
Tab(std::string_view tab={" "}, size_t indent=0)
Definition print.h:223
Tab & operator++()
Definition print.h:266
size_t indent() const
Definition print.h:229
Tab operator++(int)
Definition print.h:258
Tab operator+(size_t indent) const
Definition print.h:260
std::ostream & print(std::ostream &os, const char *s, Args &&... args)
Definition print.h:238
Definition ast.h:14
std::ostream & print(std::ostream &os, const char *s)
Base case.
Definition print.cpp:5
std::string fmt(const char *s, Args &&... args)
Wraps mim::print to output a formatted std:string.
Definition print.h:178
void error(Loc loc, const char *f, Args &&... args)
Definition dbg.h:125
std::ostream & errf(const char *fmt, Args &&... args)
Definition print.h:214
auto elems(std::ostream &os, const auto &range)
Wrap all elements in range through os << T(elem).
Definition print.h:97
StreamFn(F) -> StreamFn< F >
std::ostream & outf(const char *fmt, Args &&... args)
Definition print.h:213
std::ostream & outln(const char *fmt, Args &&... args)
Definition print.h:215
std::ostream & errln(const char *fmt, Args &&... args)
Definition print.h:216
std::ostream & println(std::ostream &os, const char *fmt, Args &&... args)
As above but end with std::endl.
Definition print.h:172
Use with print to output complicated std::ranges::ranges.
Definition print.h:86
const F f
Definition print.h:92
const R range
Definition print.h:91
Elem(const R &range, const F &f)
Definition print.h:87
Create function-wrapper objects amenable for opeartor<<.
Definition print.h:103
friend std::ostream & operator<<(std::ostream &os, const StreamFn &s)
Definition print.h:105