FE 0.9.0
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/loc.h"
16#include "fe/utf8.h"
17
18namespace fe {
19
20/// Wrap a callable `f(std::ostream&) -> std::ostream&` so it streams via `operator<<` and `std::format`.
21/// Useful for inline ad-hoc formatting:
22/// ```
23/// auto greet = fe::StreamFn{[](std::ostream& os) { os << "hi"; }};
24/// std::cout << greet;
25/// std::format("{}", greet);
26/// ```
27template<class F>
28requires std::invocable<const F&, std::ostream&>
29class 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>
125class Join {
126public:
127 using View = std::views::all_t<R>;
128
129 Join(R&& range, std::string_view sep = ", ")
130 : range_(std::views::all(std::forward<R>(range)))
131 , sep_(sep) {}
132
133 const auto& range() const { return range_; }
134 std::string_view sep() const { return sep_; }
135
136 friend std::ostream& operator<<(std::ostream& os, const Join& j) {
137 for (std::string_view sep{}; const auto& elem : j.range_) {
138 os << sep << elem;
139 sep = j.sep_;
140 }
141 return os;
142 }
143
144private:
145 View range_;
146 std::string_view sep_;
147};
148
149template<class R>
150Join(R&&, std::string_view = ", ") -> Join<R>;
151
152} // namespace fe
153
154#ifndef DOXYGEN
155template<class F>
156struct std::formatter<fe::StreamFn<F>> : fe::ostream_formatter {};
157
158template<class R>
159struct std::formatter<fe::Join<R>> {
160 using elem_t = std::remove_cvref_t<std::ranges::range_reference_t<std::views::all_t<R>>>;
161 std::formatter<elem_t> elem_fmt;
162
163 constexpr auto parse(std::format_parse_context& ctx) { return elem_fmt.parse(ctx); }
164
165 template<class FormatContext>
166 auto format(const fe::Join<R>& j, FormatContext& ctx) const {
167 auto out = ctx.out();
168 for (std::string_view sep = {}; const auto& elem : j.range()) {
169 out = std::ranges::copy(sep, out).out;
170 ctx.advance_to(out);
171 out = elem_fmt.format(elem, ctx);
172 ctx.advance_to(out);
173 sep = j.sep();
174 }
175 return out;
176 }
177};
178
179// clang-format off
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#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:125
const auto & range() const
Definition format.h:133
std::string_view sep() const
Definition format.h:134
friend std::ostream & operator<<(std::ostream &os, const Join &j)
Definition format.h:136
Join(R &&range, std::string_view sep=", ")
Definition format.h:129
std::views::all_t< R > View
Definition format.h:127
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 arena.h:13
StreamFn(F) -> StreamFn< F >
basic_ostream_formatter< char > ostream_formatter
Definition format.h:64
Join(R &&, std::string_view=", ") -> Join< R >
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