Skip to content

Commit e38838e

Browse files
committed
Add <format> format specifiers to string interpolation
Example: `(x.price(): <10.2f)$` is a space-padded left-aligned 10-width 2-precision value. When people complain about <iostream>, they're usually complaining about `<<` everywhere to concatenate elements, and the ugliness of the `<iomanip>` manipulators (and locales but that's another topic). Cpp2 loves streams, but I have to agree that this iomanip code today is pretty horrible: std::cout << std::left << std::setw(20) << x.name() << " color " << std::left << std::setw(10) << x.color() << " price " << std::setw(10) << std::setprecision(3) << x.price() << " in stock = " << std::boolalpha << (x.count() > 0) << "\n"; With this commit, you get the same formatted output with string interpolation and a single `<<`: std::cout << "(x.name():20)$ color (x.color():10)$ price (x.price(): <10.2f)$ in stock = (x.count() > 0)$\n"; Both versions print the same thing (see `pure2-interpolation.cpp2`): Dog kennel color mauve price 3.14 in stock = true
1 parent 874fa8d commit e38838e

12 files changed

+158
-40
lines changed

Diff for: include/cpp2util.h

+21-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115
#endif
116116
#include <cwchar>
117117
#include <cwctype>
118-
#ifdef __cpp_lib_format
118+
#if defined(__cpp_lib_format) || (defined(_MSC_VER) && _MSC_VER >= 1929)
119119
#include <format>
120120
#endif
121121
#include <string>
@@ -221,6 +221,9 @@
221221
#include <concepts>
222222
#include <system_error>
223223
#include <limits>
224+
#if defined(__cpp_lib_format) || (defined(_MSC_VER) && _MSC_VER >= 1929)
225+
#include <format>
226+
#endif
224227

225228
#ifndef CPP2_NO_EXCEPTIONS
226229
#include <exception>
@@ -885,6 +888,23 @@ inline auto to_string(std::tuple<Ts...> const& t) -> std::string
885888
}
886889
}
887890

891+
// MSVC supports it but doesn't define __cpp_lib_format until the ABI stablizes, but here
892+
// don't care about that, so consider it as supported since VS 2019 16.10 (_MSC_VER 1929)
893+
#if defined(__cpp_lib_format) || (defined(_MSC_VER) && _MSC_VER >= 1929)
894+
inline auto to_string(auto&& value, std::string_view fmt) -> std::string
895+
{
896+
return std::vformat(fmt, std::make_format_args(CPP2_FORWARD(value)));
897+
}
898+
#else
899+
inline auto to_string(auto&& value, std::string_view) -> std::string
900+
{
901+
// This Cpp1 implementation does not support <format>-ted string interpolation
902+
// so the best we can do is ignore the formatting request (degraded operation
903+
// seems better than a dynamic error message string or a hard error)
904+
return to_string(CPP2_FORWARD(value));
905+
}
906+
#endif
907+
888908

889909
//-----------------------------------------------------------------------
890910
//

Diff for: regression-tests/pure2-interpolation.cpp2

+28-11
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,31 @@
11

2+
item: @struct type = {
3+
name : (this) -> std::string = "Dog kennel";
4+
color: (this) -> std::string = "mauve";
5+
price: (this) -> double = 3.14;
6+
count: (this) -> int = 42;
7+
}
8+
29
main: ()->int = {
3-
x := 0;
4-
std::cout << "g(x)$g(x)$g" << "\n";
5-
std::cout << "(x)$g(x)$g" << "\n";
6-
std::cout << "(x)$g(x)$" << "\n";
7-
std::cout << "(x)$(x)$" << "\n";
8-
std::cout << "\"(x)$\"" << "\n";
9-
std::cout << "\"(x)$" << "\n";
10-
std::cout << "\"" << "\n";
11-
std::cout << "" << "\n";
12-
std::cout << "pl(ug$h" << "\n";
13-
std::cout << "(x)$pl(ug$h" << "\n";
10+
11+
(x := 0) {
12+
std::cout << "g(x)$g(x)$g" << "\n";
13+
std::cout << "(x)$g(x)$g" << "\n";
14+
std::cout << "(x)$g(x)$" << "\n";
15+
std::cout << "(x)$(x)$" << "\n";
16+
std::cout << "\"(x)$\"" << "\n";
17+
std::cout << "\"(x)$" << "\n";
18+
std::cout << "\"" << "\n";
19+
std::cout << "" << "\n";
20+
std::cout << "pl(ug$h" << "\n";
21+
std::cout << "(x)$pl(ug$h" << "\n";
22+
23+
}
24+
25+
(x := item()) {
26+
std::cout << std::left << std::setw(20) << x.name() << " color " << std::left << std::setw(10) << x.color() << " price " << std::setw(10) << std::setprecision(3) << x.price() << " in stock = " << std::boolalpha << (x.count() > 0) << "\n";
27+
28+
std::cout << "(x.name():20)$ color (x.color():10)$ price (x.price(): <10.2f)$ in stock = (x.count() > 0)$\n";
29+
}
30+
1431
}

Diff for: regression-tests/test-results/clang-12/pure2-interpolation.cpp.execution

+2
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ g0g0g
88

99
pl(ug$h
1010
0pl(ug$h
11+
Dog kennel color mauve price 3.14 in stock = true
12+
Dog kennel color mauve price 3.140000 in stock = true

Diff for: regression-tests/test-results/gcc-10/pure2-bugfix-for-requires-clause-in-forward-declaration.cpp.output

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,18 @@ pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:46: error: expect
1111
In file included from pure2-bugfix-for-requires-clause-in-forward-declaration.cpp:7:
1212
../../../include/cpp2util.h:10005:47: error: static assertion failed: GCC 11 or higher is required to support variables and type-scope functions that have a 'requires' clause. This includes a type-scope 'forward' parameter of non-wildcard type, such as 'func: (this, forward s: std::string)', which relies on being able to add a 'requires' clause - in that case, use 'forward s: _' instead if you need the result to compile with GCC 10.
1313
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:4:1: note: in expansion of macro ‘CPP2_REQUIRES_’
14-
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:3: error: no declaration matches ‘element::element(auto:81&&) requires is_same_v<typename std::remove_cv<typename std::remove_reference<decltype(element::__ct ::n)>::type>::type, std::__cxx11::string>’
14+
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:3: error: no declaration matches ‘element::element(auto:82&&) requires is_same_v<typename std::remove_cv<typename std::remove_reference<decltype(element::__ct ::n)>::type>::type, std::__cxx11::string>’
1515
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:5:11: note: candidates are: ‘element::element(const element&)’
16-
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp:16:20: note: ‘template<class auto:79> element::element(auto:79&&)’
16+
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp:16:20: note: ‘template<class auto:80> element::element(auto:80&&)’
1717
16 | public: explicit element(auto&& n)
1818
| ^~~~~~~
1919
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp:14:7: note: ‘class element’ defined here
2020
14 | class element {
2121
| ^~~~~~~
2222
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:3: error: expected unqualified-id before ‘{’ token
23-
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:8: error: no declaration matches ‘element& element::operator=(auto:82&&) requires is_same_v<typename std::remove_cv<typename std::remove_reference<decltype(element::operator=::n)>::type>::type, std::__cxx11::string>’
23+
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:8: error: no declaration matches ‘element& element::operator=(auto:83&&) requires is_same_v<typename std::remove_cv<typename std::remove_reference<decltype(element::operator=::n)>::type>::type, std::__cxx11::string>’
2424
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:6:16: note: candidates are: ‘void element::operator=(const element&)’
25-
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:16: note: ‘template<class auto:80> element& element::operator=(auto:80&&)’
25+
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp2:3:16: note: ‘template<class auto:81> element& element::operator=(auto:81&&)’
2626
pure2-bugfix-for-requires-clause-in-forward-declaration.cpp:14:7: note: ‘class element’ defined here
2727
14 | class element {
2828
| ^~~~~~~

Diff for: regression-tests/test-results/gcc-10/pure2-interpolation.cpp.execution

+2
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ g0g0g
88

99
pl(ug$h
1010
0pl(ug$h
11+
Dog kennel color mauve price 3.14 in stock = true
12+
Dog kennel color mauve price 3.140000 in stock = true

Diff for: regression-tests/test-results/gcc-13/pure2-interpolation.cpp.execution

+2
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ g0g0g
88

99
pl(ug$h
1010
0pl(ug$h
11+
Dog kennel color mauve price 3.14 in stock = true
12+
Dog kennel color mauve price 3.14 in stock = true

Diff for: regression-tests/test-results/msvc-2022/pure2-interpolation.cpp.execution

+2
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ g0g0g
88

99
pl(ug$h
1010
0pl(ug$h
11+
Dog kennel color mauve price 3.14 in stock = true
12+
Dog kennel color mauve price 3.14 in stock = true

Diff for: regression-tests/test-results/pure2-interpolation.cpp

+48-13
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,64 @@
77
#include "cpp2util.h"
88

99

10+
#line 2 "pure2-interpolation.cpp2"
11+
class item;
12+
1013

1114
//=== Cpp2 type definitions and function declarations ===========================
1215

1316

1417
#line 2 "pure2-interpolation.cpp2"
18+
class item {
19+
public: [[nodiscard]] auto name() const& -> std::string;
20+
public: [[nodiscard]] auto color() const& -> std::string;
21+
public: [[nodiscard]] auto price() const& -> double;
22+
public: [[nodiscard]] auto count() const& -> int;
23+
};
24+
1525
[[nodiscard]] auto main() -> int;
16-
26+
1727

1828
//=== Cpp2 function definitions =================================================
1929

2030

21-
#line 2 "pure2-interpolation.cpp2"
31+
#line 3 "pure2-interpolation.cpp2"
32+
[[nodiscard]] auto item::name() const& -> std::string { return "Dog kennel"; }
33+
[[nodiscard]] auto item::color() const& -> std::string { return "mauve"; }
34+
[[nodiscard]] auto item::price() const& -> double { return 3.14; }
35+
[[nodiscard]] auto item::count() const& -> int { return 42; }
36+
37+
#line 9 "pure2-interpolation.cpp2"
2238
[[nodiscard]] auto main() -> int{
23-
auto x {0};
24-
std::cout << "g" + cpp2::to_string(x) + "g" + cpp2::to_string(x) + "g" << "\n";
25-
std::cout << cpp2::to_string(x) + "g" + cpp2::to_string(x) + "g" << "\n";
26-
std::cout << cpp2::to_string(x) + "g" + cpp2::to_string(x) << "\n";
27-
std::cout << cpp2::to_string(x) + cpp2::to_string(x) << "\n";
28-
std::cout << "\"" + cpp2::to_string(x) + "\"" << "\n";
29-
std::cout << "\"" + cpp2::to_string(x) << "\n";
30-
std::cout << "\"" << "\n";
31-
std::cout << "" << "\n";
32-
std::cout << "pl(ug$h" << "\n";
33-
std::cout << cpp2::to_string(std::move(x)) + "pl(ug$h" << "\n";
39+
{
40+
auto x = 0;
41+
42+
#line 11 "pure2-interpolation.cpp2"
43+
{
44+
std::cout << "g" + cpp2::to_string(x) + "g" + cpp2::to_string(x) + "g" << "\n";
45+
std::cout << cpp2::to_string(x) + "g" + cpp2::to_string(x) + "g" << "\n";
46+
std::cout << cpp2::to_string(x) + "g" + cpp2::to_string(x) << "\n";
47+
std::cout << cpp2::to_string(x) + cpp2::to_string(x) << "\n";
48+
std::cout << "\"" + cpp2::to_string(x) + "\"" << "\n";
49+
std::cout << "\"" + cpp2::to_string(x) << "\n";
50+
std::cout << "\"" << "\n";
51+
std::cout << "" << "\n";
52+
std::cout << "pl(ug$h" << "\n";
53+
std::cout << cpp2::to_string(x) + "pl(ug$h" << "\n";
54+
55+
}
56+
}
57+
{
58+
auto x = item();
59+
60+
#line 25 "pure2-interpolation.cpp2"
61+
{
62+
std::cout << std::left << std::setw(20) << CPP2_UFCS_0(name, x) << " color " << std::left << std::setw(10) << CPP2_UFCS_0(color, x) << " price " << std::setw(10) << std::setprecision(3) << CPP2_UFCS_0(price, x) << " in stock = " << std::boolalpha << (cpp2::cmp_greater(CPP2_UFCS_0(count, x),0)) << "\n";
63+
64+
std::cout << cpp2::to_string(CPP2_UFCS_0(name, x), "{:20}") + " color " + cpp2::to_string(CPP2_UFCS_0(color, x), "{:10}") + " price " + cpp2::to_string(CPP2_UFCS_0(price, x), "{: <10.2f}") + " in stock = " + cpp2::to_string(cpp2::cmp_greater(CPP2_UFCS_0(count, std::move(x)),0)) + "\n";
65+
}
66+
}
67+
68+
#line 31 "pure2-interpolation.cpp2"
3469
}
3570

Diff for: regression-tests/test-results/version

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
cppfront compiler v0.2.1 Build 8922:0931
2+
cppfront compiler v0.2.1 Build 8923:1137
33
Copyright(c) Herb Sutter All rights reserved
44

55
SPDX-License-Identifier: CC-BY-NC-ND-4.0

Diff for: source/build.info

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"8922:0931"
1+
"8923:1137"

Diff for: source/lex.h

+31
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,37 @@ auto expand_string_literal(
434434
});
435435
chunk.erase(last_it, std::end(chunk));
436436
}
437+
438+
// This chunk string is now in the form "(some_capture_text)",
439+
// which might include a :formatter suffix like "(capture_text:formatter)"
440+
441+
if (std::ssize(chunk) < 1)
442+
{
443+
errors.emplace_back(
444+
source_position( src_pos.lineno, src_pos.colno + pos ),
445+
"string interpolation must not be empty"
446+
);
447+
return {};
448+
}
449+
if (chunk.ends_with(':'))
450+
{
451+
errors.emplace_back(
452+
source_position( src_pos.lineno, src_pos.colno + pos ),
453+
"string interpolation ':' must be followed by a std::formatter specifier"
454+
);
455+
return {};
456+
}
457+
458+
// If there's a :formatter suffix, decorate it as: ,"{:formatter}"
459+
if (auto colon = chunk.find_last_of(':');
460+
colon != chunk.npos
461+
&& chunk[colon-1] != ':' // ignore :: scope resolution
462+
)
463+
{
464+
chunk.insert(colon, ",\"{");
465+
chunk.insert(chunk.size()-1, "}\"");
466+
}
467+
437468
parts.add_code("cpp2::to_string" + chunk);
438469

439470
current_start = pos+1;

Diff for: source/parse.h

+16-9
Original file line numberDiff line numberDiff line change
@@ -1883,7 +1883,9 @@ struct statement_node
18831883
std::unique_ptr<jump_statement_node>
18841884
> statement;
18851885

1886-
bool emitted = false; // note field used during lowering
1886+
bool emitted = false; // a note field that's used during lowering to Cpp1
1887+
1888+
bool marked_for_removal = false; // for use during metafunctions which may replace members
18871889

18881890
// API
18891891
//
@@ -2516,6 +2518,7 @@ struct declaration_node
25162518
std::unique_ptr<statement_node> initializer;
25172519

25182520
declaration_node* parent_declaration = {};
2521+
statement_node* my_statement = {};
25192522

25202523
// Attributes currently configurable only via metafunction API,
25212524
// not directly in the base language grammar
@@ -7554,16 +7557,18 @@ class parser
75547557
bool is_template_parameter = false,
75557558
std::unique_ptr<unqualified_id_node> id = {},
75567559
accessibility access = {},
7557-
bool is_variadic = false
7560+
bool is_variadic = false,
7561+
statement_node const* my_stmt = {}
75587562
)
75597563
-> std::unique_ptr<declaration_node>
75607564
{
75617565
auto n = std::make_unique<declaration_node>( current_declarations.back() );
75627566
n->pos = start;
75637567

7564-
n->identifier = std::move(id);
7565-
n->access = access;
7566-
n->is_variadic = is_variadic;
7568+
n->identifier = std::move(id);
7569+
n->access = access;
7570+
n->is_variadic = is_variadic;
7571+
n->my_statement = my_stmt;
75677572

75687573
// If we're in a type scope and the next token is ';', treat this as if
75697574
// ': _;' without an initializer.
@@ -8217,9 +8222,10 @@ class parser
82178222
//G private
82188223
//G
82198224
auto declaration(
8220-
bool semicolon_required = true,
8221-
bool is_parameter = false,
8222-
bool is_template_parameter = false
8225+
bool semicolon_required = true,
8226+
bool is_parameter = false,
8227+
bool is_template_parameter = false,
8228+
statement_node const* my_stmt = {}
82238229
)
82248230
-> std::unique_ptr<declaration_node>
82258231
{
@@ -8367,7 +8373,8 @@ class parser
83678373
is_template_parameter,
83688374
std::move(id),
83698375
access,
8370-
is_variadic
8376+
is_variadic,
8377+
my_stmt
83718378
);
83728379
if (!n) {
83738380
pos = start_pos; // backtrack

0 commit comments

Comments
 (0)