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