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