FE 0.16.1
Fast, Effecient FrontEnds
Loading...
Searching...
No Matches
format.h
Go to the documentation of this file.
1#pragma once
2
3#include <concepts>
4
5#include <format>
6#include <functional>
7#include <iostream>
8#include <ostream>
9#include <ranges>
10#include <sstream>
11#include <string_view>
12#include <utility>
13
14#include "fe/bitset.h"
15#include "fe/loc.h"
16#include "fe/sym.h"
17#include "fe/utf8.h"
18
19namespace fe {
20
21/// Wrap a callable `f(std::ostream&) -> std::ostream&` so it streams via `operator<<` and `std::format`.
22/// Useful for inline ad-hoc formatting:
23/// ```
24/// auto greet = fe::StreamFn{[](std::ostream& os) { os << "hi"; }};
25/// std::cout << greet;
26/// std::format("{}", greet);
27/// ```
28template<class F>
29requires std::invocable<const F&, std::ostream&> class StreamFn {
30public:
31 constexpr StreamFn(F f)
32 : f_(std::move(f)) {}
33
34 friend std::ostream& operator<<(std::ostream& os, StreamFn const& s) {
35 std::invoke(s.f_, os);
36 return os;
37 }
38
39private:
40 F f_;
41};
42
43template<class F>
45
46/// Make types that support ostream operators available for `std::format`.
47/// Use like this:
48/// ```
49/// template<> struct std::formatter<T> : fe::ostream_formatter {};
50/// ```
51/// @sa [Stack Overflow](https://stackoverflow.com/a/75738462).
52template<class Char>
53struct basic_ostream_formatter : std::formatter<std::basic_string_view<Char>, Char> {
54 auto format(const auto& value, auto& ctx) const {
55 std::basic_stringstream<Char> ss;
56 ss << value;
57 return std::formatter<std::basic_string_view<Char>, Char>::format(ss.view(), ctx);
58 }
59};
60
62
63/// Keeps track of indentation level during output
64class Tab {
65public:
66 /// @name Construction
67 ///@{
68 constexpr Tab(const Tab&) = default;
69 constexpr Tab(std::string_view tab = {"\t"}, int indent = 0) noexcept
70 : tab_(tab)
71 , indent_(indent) {
72 assert(indent >= 0);
73 }
74
75 static constexpr Tab spaces() noexcept { return Tab(std::string_view(" ")); }
76 ///@}
77
78 /// @name Getters
79 ///@{
80 constexpr int indent() const noexcept { return indent_; }
81 constexpr std::string_view tab() const noexcept { return tab_; }
82 ///@}
83
84 // clang-format off
85 /// @name Creates a new Tab
86 ///@{
87 ///
88 [[nodiscard]] constexpr Tab operator+(int indent) const noexcept { assert(indent >= 0); return {tab_, indent_ + indent}; }
89 [[nodiscard]] constexpr Tab operator-(int indent) const noexcept { assert(indent >= 0 && indent_ >= indent); return {tab_, indent_ - indent}; }
90 ///@}
91
92 /// @name Modifies this Tab
93 ///@{
94 constexpr Tab& operator++() noexcept { ++indent_; return *this; }
95 constexpr Tab& operator--() noexcept { assert(indent_ > 0); --indent_; return *this; }
96 constexpr Tab& operator+=(int indent) noexcept { assert(indent >= 0); indent_ += indent; return *this; }
97 constexpr Tab& operator-=(int indent) noexcept { assert(indent >= 0 && indent_ >= indent); indent_ -= indent; return *this; }
98 ///@}
99 // clang-format on
100
101 friend std::ostream& operator<<(std::ostream& os, Tab tab) {
102 for (int i = 0; i != tab.indent_; ++i)
103 os << tab.tab_;
104 return os;
105 }
106
107private:
108 std::string_view tab_;
109 int indent_ = 0;
110};
111
112template<class T>
114 = requires(std::format_context& ctx, const T& v) { std::formatter<std::remove_cvref_t<T>>{}.format(v, ctx); };
115
116/// Join elements of @p range with @p sep.
117/// Use as a `std::format` or `operator<<` argument: `std::format("{}", fe::Join(v, ", "))`.
118template<std::ranges::input_range R>
120public:
121 using View = std::views::all_t<R>;
122
123 Join(R&& range, std::string_view sep = ", ")
124 : range_(std::views::all(std::forward<R>(range)))
125 , sep_(sep) {}
126
127 const auto& range() const { return range_; }
128 std::string_view sep() const { return sep_; }
129
130 friend std::ostream& operator<<(std::ostream& os, const Join& j) {
131 for (std::string_view sep{}; const auto& elem : j.range_) {
132 os << sep << elem;
133 sep = j.sep_;
134 }
135 return os;
136 }
137
138private:
139 View range_;
140 std::string_view sep_;
141};
142
143template<class R>
144Join(R&&, std::string_view = ", ") -> Join<R>;
145
146} // namespace fe
147
148#ifndef DOXYGEN
149template<class F>
150struct std::formatter<fe::StreamFn<F>> : fe::ostream_formatter {};
151
152template<class R>
153struct std::formatter<fe::Join<R>> {
154 using elem_t = std::remove_cvref_t<std::ranges::range_reference_t<std::views::all_t<R>>>;
155 std::formatter<elem_t> elem_fmt;
156
157 constexpr auto parse(std::format_parse_context& ctx) { return elem_fmt.parse(ctx); }
158
159 auto format(const fe::Join<R>& j, auto& ctx) const {
160 auto out = ctx.out();
161 for (std::string_view sep = {}; const auto& elem : j.range()) {
162 out = std::ranges::copy(sep, out).out;
163 ctx.advance_to(out);
164 out = elem_fmt.format(elem, ctx);
165 ctx.advance_to(out);
166 sep = j.sep();
167 }
168 return out;
169 }
170};
171
172// clang-format off
173template<> struct std::formatter<fe::Bitset> : fe::ostream_formatter {};
174template<> struct std::formatter<fe::Pos> : fe::ostream_formatter {};
175template<> struct std::formatter<fe::Loc> : fe::ostream_formatter {};
176template<> struct std::formatter<fe::Sym> : fe::ostream_formatter {};
177template<> struct std::formatter<fe::Tab> : fe::ostream_formatter {};
178template<> struct std::formatter<fe::utf8::Char32> : fe::ostream_formatter {};
179// clang-format on
180#endif
181
182/// Like `assert`, but with a `std::format`-style message: `assertf(p != nullptr, "bad ptr {}", id)`.
183/// @note A message is *required* - at least a format-string argument must follow @p condition.
184/// For a bare assertion without a message just use vanilla `assert`. (Supporting zero message args here would
185/// need a surprising amount of preprocessor hackery, especially on MSVC, and isn't worth it.)
186#ifdef NDEBUG
187# define assertf(condition, ...) \
188 do { \
189 (void)sizeof(condition); \
190 } while (false)
191#else
192# define assertf(condition, ...) \
193 do { \
194 if (!(condition)) { \
195 std::println(std::cerr, "{}:{}: assertion `{}` failed", __FILE__, __LINE__, #condition); \
196 std::println(std::cerr, __VA_ARGS__); \
197 fe::breakpoint(); \
198 } \
199 } while (false)
200#endif
Join elements of range with sep.
Definition format.h:119
const auto & range() const
Definition format.h:127
std::string_view sep() const
Definition format.h:128
friend std::ostream & operator<<(std::ostream &os, const Join &j)
Definition format.h:130
Join(R &&range, std::string_view sep=", ")
Definition format.h:123
std::views::all_t< R > View
Definition format.h:121
Wrap a callable f(std::ostream&) -> std::ostream& so it streams via operator<< and std::format.
Definition format.h:29
friend std::ostream & operator<<(std::ostream &os, StreamFn const &s)
Definition format.h:34
constexpr StreamFn(F f)
Definition format.h:31
constexpr Tab & operator+=(int indent) noexcept
Definition format.h:96
constexpr std::string_view tab() const noexcept
Definition format.h:81
constexpr Tab operator-(int indent) const noexcept
Definition format.h:89
constexpr Tab & operator++() noexcept
Definition format.h:94
friend std::ostream & operator<<(std::ostream &os, Tab tab)
Definition format.h:101
constexpr Tab & operator-=(int indent) noexcept
Definition format.h:97
constexpr int indent() const noexcept
Definition format.h:80
constexpr Tab(const Tab &)=default
static constexpr Tab spaces() noexcept
Definition format.h:75
constexpr Tab & operator--() noexcept
Definition format.h:95
constexpr Tab operator+(int indent) const noexcept
Definition format.h:88
constexpr Tab(std::string_view tab={"\t"}, int indent=0) noexcept
Definition format.h:69
Definition algo.h:17
Span< const T, N > View
Read-only Span; use Span itself, if you want to write through its elements.
Definition span.h:107
basic_ostream_formatter< char > ostream_formatter
Definition format.h:61
Join(R &&, std::string_view=", ") -> Join< R >
Definition span.h:150
Make types that support ostream operators available for std::format.
Definition format.h:53
auto format(const auto &value, auto &ctx) const
Definition format.h:54