FE 0.15.0
A C++23 toolkit for writing compiler/interpreter 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, class CharT = char>
114 = requires(std::basic_format_context<std::back_insert_iterator<std::basic_string<CharT>>, CharT>& ctx, T const& v) {
115 std::formatter<std::remove_cvref_t<T>, CharT>{}.format(v, ctx);
116 };
117
118/// Join elements of @p range with @p sep.
119/// Use as a `std::format` or `operator<<` argument: `std::format("{}", fe::Join(v, ", "))`.
120template<std::ranges::input_range R>
122public:
123 using View = std::views::all_t<R>;
124
125 Join(R&& range, std::string_view sep = ", ")
126 : range_(std::views::all(std::forward<R>(range)))
127 , sep_(sep) {}
128
129 const auto& range() const { return range_; }
130 std::string_view sep() const { return sep_; }
131
132 friend std::ostream& operator<<(std::ostream& os, const Join& j) {
133 for (std::string_view sep{}; const auto& elem : j.range_) {
134 os << sep << elem;
135 sep = j.sep_;
136 }
137 return os;
138 }
139
140private:
141 View range_;
142 std::string_view sep_;
143};
144
145template<class R>
146Join(R&&, std::string_view = ", ") -> Join<R>;
147
148} // namespace fe
149
150#ifndef DOXYGEN
151template<class F>
152struct std::formatter<fe::StreamFn<F>> : fe::ostream_formatter {};
153
154template<class R>
155struct std::formatter<fe::Join<R>> {
156 using elem_t = std::remove_cvref_t<std::ranges::range_reference_t<std::views::all_t<R>>>;
157 std::formatter<elem_t> elem_fmt;
158
159 constexpr auto parse(std::format_parse_context& ctx) { return elem_fmt.parse(ctx); }
160
161 auto format(const fe::Join<R>& j, auto& ctx) const {
162 auto out = ctx.out();
163 for (std::string_view sep = {}; const auto& elem : j.range()) {
164 out = std::ranges::copy(sep, out).out;
165 ctx.advance_to(out);
166 out = elem_fmt.format(elem, ctx);
167 ctx.advance_to(out);
168 sep = j.sep();
169 }
170 return out;
171 }
172};
173
174// clang-format off
175template<> struct std::formatter<fe::Bitset> : fe::ostream_formatter {};
176template<> struct std::formatter<fe::Pos> : fe::ostream_formatter {};
177template<> struct std::formatter<fe::Loc> : fe::ostream_formatter {};
178template<> struct std::formatter<fe::Sym> : fe::ostream_formatter {};
179template<> struct std::formatter<fe::Tab> : fe::ostream_formatter {};
180template<> struct std::formatter<fe::utf8::Char32> : fe::ostream_formatter {};
181// clang-format on
182#endif
183
184/// Like `assert`, but with a `std::format`-style message: `assertf(p != nullptr, "bad ptr {}", id)`.
185/// @note A message is *required* - at least a format-string argument must follow @p condition.
186/// For a bare assertion without a message just use vanilla `assert`. (Supporting zero message args here would
187/// need a surprising amount of preprocessor hackery, especially on MSVC, and isn't worth it.)
188#ifdef NDEBUG
189# define assertf(condition, ...) \
190 do { \
191 (void)sizeof(condition); \
192 } while (false)
193#else
194# define assertf(condition, ...) \
195 do { \
196 if (!(condition)) { \
197 std::println(std::cerr, "{}:{}: assertion `{}` failed", __FILE__, __LINE__, #condition); \
198 std::println(std::cerr, __VA_ARGS__); \
199 fe::breakpoint(); \
200 } \
201 } while (false)
202#endif
Join elements of range with sep.
Definition format.h:121
const auto & range() const
Definition format.h:129
std::string_view sep() const
Definition format.h:130
friend std::ostream & operator<<(std::ostream &os, const Join &j)
Definition format.h:132
Join(R &&range, std::string_view sep=", ")
Definition format.h:125
std::views::all_t< R > View
Definition format.h:123
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