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
81/// Use with print to output complicated `std::ranges::range`s.
82template<class R, class F> struct Elem {
83 Elem(const R& range, const F& f)
84 : range(range)
85 , f(f) {}
86
87 const R range;
88 const F f;
89};
90
91std::ostream& print(std::ostream& os, const char* s); ///< Base case.
92
93template<class T, class... Args> std::ostream& print(std::ostream& os, const char* s, T&& t, Args&&... args) {
94 while (*s != '\0') {
95 auto next = s + 1;
96
97 switch (*s) {
98 case '{': {
99 if (detail::match2nd(os, next, s, '{')) continue;
100 s++; // skip opening brace '{'
101
102 std::string spec;
103 while (*s != '\0' && *s != '}') spec.push_back(*s++);
104 assert(*s == '}' && "unmatched closing brace '}' in format string");
105
106 if constexpr (std::is_invocable_v<decltype(t)>) {
107 std::invoke(t);
108 } else if constexpr (std::is_invocable_v<decltype(t), std::ostream&>) {
109 std::invoke(t, os);
110 } else if constexpr (detail::Printable<decltype(t)>) {
111 auto flags = std::ios_base::fmtflags(os.flags());
112
113 if (spec == "b")
114 assert(false && "TODO");
115 else if (spec == "o")
116 os << std::oct;
117 else if (spec == "d")
118 os << std::dec;
119 else if (spec == "x")
120 os << std::hex;
121
122 os << t;
123 os.flags(flags);
124 } else if constexpr (detail::Elemable<decltype(t)>) {
125 detail::range(os, t.range, t.f, spec.c_str());
126 } else if constexpr (std::ranges::range<decltype(t)>) {
127 detail::range(
128 os, t, [&](const auto& x) { os << x; }, spec.c_str());
129 } else {
130 []<bool flag = false>() { static_assert(flag, "cannot print T t"); }
131 ();
132 }
133
134 ++s; // skip closing brace '}'
135 // call even when *s == '\0' to detect extra arguments
136 return print(os, s, std::forward<Args&&>(args)...);
137 }
138 case '}':
139 if (detail::match2nd(os, next, s, '}')) continue;
140 assert(false && "unmatched/unescaped closing brace '}' in format string");
141 fe::unreachable();
142 default: os << *s++;
143 }
144 }
145
146 assert(false && "invalid format string for 's'");
147 fe::unreachable();
148}
149
150/// As above but end with `std::endl`.
151template<class... Args> std::ostream& println(std::ostream& os, const char* fmt, Args&&... args) {
152 return print(os, fmt, std::forward<Args&&>(args)...) << std::endl;
153}
154
155/// Wraps mim::print to output a formatted `std:string`.
156template<class... Args> std::string fmt(const char* s, Args&&... args) {
157 std::ostringstream os;
158 print(os, s, std::forward<Args&&>(args)...);
159 return os.str();
160}
161
162/// Wraps mim::print to throw `T` with a formatted message.
163template<class T = std::logic_error, class... Args> [[noreturn]] void error(const char* fmt, Args&&... args) {
164 std::ostringstream oss;
165 print(oss << "error: ", fmt, std::forward<Args&&>(args)...);
166 throw T(oss.str());
167}
168
169#ifdef NDEBUG
170# define assertf(condition, ...) \
171 do { (void)sizeof(condition); } while (false)
172#else
173# define assertf(condition, ...) \
174 do { \
175 if (!(condition)) { \
176 mim::errf("{}:{}: assertion: ", __FILE__, __LINE__); \
177 mim::errln(__VA_ARGS__); \
178 fe::breakpoint(); \
179 } \
180 } while (false)
181#endif
182///@}
183
184/// @name out/err
185/// mim::print%s to `std::cout`/`std::cerr`; the *`ln` variants emit an additional `std::endl`.
186///@{
187// clang-format off
188template<class... Args> std::ostream& outf (const char* fmt, Args&&... args) { return print(std::cout, fmt, std::forward<Args&&>(args)...); }
189template<class... Args> std::ostream& errf (const char* fmt, Args&&... args) { return print(std::cerr, fmt, std::forward<Args&&>(args)...); }
190template<class... Args> std::ostream& outln(const char* fmt, Args&&... args) { return outf(fmt, std::forward<Args&&>(args)...) << std::endl; }
191template<class... Args> std::ostream& errln(const char* fmt, Args&&... args) { return errf(fmt, std::forward<Args&&>(args)...) << std::endl; }
192// clang-format on
193///@}
194
195/// Keeps track of indentation level.
196class Tab {
197public:
198 Tab(std::string_view tab = {" "}, size_t indent = 0)
199 : tab_(tab)
200 , indent_(indent) {}
201
202 /// @name Getters
203 ///@{
204 size_t indent() const { return indent_; }
205 std::string_view tab() const { return tab_; }
206 ///@}
207
208 /// @name print
209 /// Wraps mim::print to prefix it with indentation.
210 /// @see @ref fmt "Formatted Output"
211 ///@{
212 template<class... Args> std::ostream& print(std::ostream& os, const char* s, Args&&... args) {
213 for (size_t i = 0; i < indent_; ++i) os << tab_;
214 return mim::print(os, s, std::forward<Args>(args)...);
215 }
216 /// Same as Tab::print but **prepends** a `std::endl` to @p os.
217 template<class... Args> std::ostream& lnprint(std::ostream& os, const char* s, Args&&... args) {
218 return print(os << std::endl, s, std::forward<Args>(args)...);
219 }
220 /// Same as Tab::print but **appends** a `std::endl` to @p os.
221 template<class... Args> std::ostream& println(std::ostream& os, const char* s, Args&&... args) {
222 return print(os, s, std::forward<Args>(args)...) << std::endl;
223 }
224 ///@}
225
226 // clang-format off
227 /// @name Creates a new Tab
228 ///@{
229 [[nodiscard]] Tab operator++(int) { return {tab_, indent_++}; }
230 [[nodiscard]] Tab operator--(int) { assert(indent_ > 0); return {tab_, indent_--}; }
231 [[nodiscard]] Tab operator+(size_t indent) const { return {tab_, indent_ + indent}; }
232 [[nodiscard]] Tab operator-(size_t indent) const { assert(indent_ > 0); return {tab_, indent_ - indent}; }
233 ///@}
234
235 /// @name Modifies this Tab
236 ///@{
237 Tab& operator++() { ++indent_; return *this; }
238 Tab& operator--() { assert(indent_ > 0); --indent_; return *this; }
239 Tab& operator+=(size_t indent) { indent_ += indent; return *this; }
240 Tab& operator-=(size_t indent) { assert(indent_ > 0); indent_ -= indent; return *this; }
241 Tab& operator=(size_t indent) { indent_ = indent; return *this; }
242 Tab& operator=(std::string_view tab) { tab_ = tab; return *this; }
243 ///@}
244 // clang-format on
245
246private:
247 std::string_view tab_;
248 size_t indent_ = 0;
249};
250
251} // namespace mim
Tab operator-(size_t indent) const
Definition print.h:232
Tab operator--(int)
Definition print.h:230
Tab & operator--()
Definition print.h:238
Tab & operator=(size_t indent)
Definition print.h:241
std::string_view tab() const
Definition print.h:205
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:221
Tab & operator=(std::string_view tab)
Definition print.h:242
Tab & operator-=(size_t indent)
Definition print.h:240
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:217
Tab & operator+=(size_t indent)
Definition print.h:239
Tab(std::string_view tab={" "}, size_t indent=0)
Definition print.h:198
Tab & operator++()
Definition print.h:237
size_t indent() const
Definition print.h:204
Tab operator++(int)
Definition print.h:229
Tab operator+(size_t indent) const
Definition print.h:231
std::ostream & print(std::ostream &os, const char *s, Args &&... args)
Definition print.h:212
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:156
void error(Loc loc, const char *f, Args &&... args)
Definition dbg.h:122
std::ostream & errf(const char *fmt, Args &&... args)
Definition print.h:189
std::ostream & outf(const char *fmt, Args &&... args)
Definition print.h:188
std::ostream & outln(const char *fmt, Args &&... args)
Definition print.h:190
std::ostream & errln(const char *fmt, Args &&... args)
Definition print.h:191
std::ostream & println(std::ostream &os, const char *fmt, Args &&... args)
As above but end with std::endl.
Definition print.h:151
const F f
Definition print.h:88
const R range
Definition print.h:87
Elem(const R &range, const F &f)
Definition print.h:83