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