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