Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
A
Actor Framework
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
cpp-libs
Actor Framework
Commits
ff9b5667
Unverified
Commit
ff9b5667
authored
Jul 10, 2023
by
Dominik Charousset
Committed by
GitHub
Jul 10, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1475
Add format API scaffolding
parents
6b6a5227
e51b17f4
Changes
7
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
743 additions
and
0 deletions
+743
-0
CMakeLists.txt
CMakeLists.txt
+19
-0
cmake/build_config.hpp.in
cmake/build_config.hpp.in
+2
-0
libcaf_core/CMakeLists.txt
libcaf_core/CMakeLists.txt
+5
-0
libcaf_core/caf/detail/format.hpp
libcaf_core/caf/detail/format.hpp
+115
-0
libcaf_core/caf/detail/is_one_of.hpp
libcaf_core/caf/detail/is_one_of.hpp
+3
-0
libcaf_core/src/detail/format.cpp
libcaf_core/src/detail/format.cpp
+490
-0
libcaf_core/test/detail/format.cpp
libcaf_core/test/detail/format.cpp
+109
-0
No files found.
CMakeLists.txt
View file @
ff9b5667
...
...
@@ -6,6 +6,7 @@ project(CAF CXX)
include
(
CMakeDependentOption
)
include
(
CMakePackageConfigHelpers
)
include
(
CheckCXXSourceCompiles
)
include
(
CheckCXXSourceRuns
)
include
(
GNUInstallDirs
)
include
(
GenerateExportHeader
)
...
...
@@ -158,6 +159,24 @@ if(NOT CMAKE_CROSSCOMPILING)
endif
()
endif
()
# -- check whether we can use std::format --------------------------------------
if
(
NOT DEFINED CAF_USE_STD_FORMAT
)
set
(
CAF_USE_STD_FORMAT OFF CACHE BOOL
"Enable std::format support"
FORCE
)
if
(
NOT CMAKE_CROSSCOMPILING
)
set
(
snippet
"#include <format>
#include <iostream>
int main() { std::cout << std::format(
\"
{}
\"
,
\"
ok
\"
); }"
)
check_cxx_source_runs
(
"
${
snippet
}
"
snippet_output
)
if
(
"
${
snippet_output
}
"
STREQUAL
"ok"
)
message
(
STATUS
"Enable std::format support"
)
set
(
CAF_USE_STD_FORMAT ON CACHE BOOL
"Enable std::format support"
FORCE
)
else
()
message
(
STATUS
"Disable std::format support: NOT available"
)
endif
()
endif
()
endif
()
# -- export internal target (may be useful for re-using compiler flags) --------
set_target_properties
(
caf_internal PROPERTIES EXPORT_NAME internal
)
...
...
cmake/build_config.hpp.in
View file @
ff9b5667
...
...
@@ -11,3 +11,5 @@
#cmakedefine CAF_ENABLE_EXCEPTIONS
#cmakedefine CAF_ENABLE_ACTOR_PROFILER
#cmakedefine CAF_USE_STD_FORMAT
libcaf_core/CMakeLists.txt
View file @
ff9b5667
...
...
@@ -255,6 +255,7 @@ caf_add_component(
detail.base64
detail.bounds_checker
detail.config_consumer
detail.format
detail.group_tunnel
detail.ieee_754
detail.json
...
...
@@ -385,6 +386,10 @@ caf_add_component(
uri
uuid
)
if
(
NOT CAF_USE_STD_FORMAT
)
target_sources
(
libcaf_core_obj PRIVATE src/detail/format.cpp
)
endif
()
if
(
CAF_ENABLE_TESTING AND CAF_ENABLE_EXCEPTIONS
)
caf_add_test_suites
(
caf-core-test custom_exception_handler
)
endif
()
libcaf_core/caf/detail/format.hpp
0 → 100644
View file @
ff9b5667
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
// Thin wrapper around std::format. If that is not available, we provide a
// minimal implementation that is "good enough" for our purposes.
//
// TODO: Currently, this only wraps the format_to and format functions as-is.
// The wrappers should also add support for types that provide an
// `inspect` overload.
#include "caf/detail/build_config.hpp"
#include <string>
#ifdef CAF_USE_STD_FORMAT
# include <format>
namespace
caf
::
detail
{
template
<
class
OutputIt
,
class
...
Args
>
auto
format_to
(
OutputIt
out
,
std
::
format_string
<
Args
...
>
fstr
,
Args
&&
...
args
)
{
return
std
::
format_to
(
out
,
fstr
,
std
::
forward
<
Args
>
(
args
)...);
}
template
<
class
...
Args
>
std
::
string
format
(
std
::
format_string
<
Args
...
>
fstr
,
Args
&&
...
args
)
{
return
std
::
format
(
fstr
,
std
::
forward
<
Args
>
(
args
)...);
}
}
// namespace caf::detail
#else // here comes the poor man's version
# include "caf/detail/core_export.hpp"
# include "caf/span.hpp"
# include <array>
# include <cstdint>
# include <iterator>
# include <memory>
# include <string_view>
# include <variant>
namespace
caf
::
detail
{
using
format_arg
=
std
::
variant
<
bool
,
char
,
int64_t
,
uint64_t
,
double
,
const
char
*
,
std
::
string_view
,
const
void
*>
;
template
<
class
T
>
format_arg
make_format_arg
(
const
T
&
arg
)
{
if
constexpr
(
is_one_of_v
<
T
,
bool
,
char
,
const
char
*
,
std
::
string_view
>
)
{
return
format_arg
{
arg
};
}
else
if
constexpr
(
std
::
is_integral_v
<
T
>
)
{
if
constexpr
(
std
::
is_signed_v
<
T
>
)
{
return
format_arg
{
static_cast
<
int64_t
>
(
arg
)};
}
else
{
return
format_arg
{
static_cast
<
uint64_t
>
(
arg
)};
}
}
else
if
constexpr
(
std
::
is_floating_point_v
<
T
>
)
{
return
format_arg
{
static_cast
<
double
>
(
arg
)};
}
else
if
constexpr
(
std
::
is_same_v
<
T
,
std
::
string
>
)
{
return
format_arg
{
std
::
string_view
{
arg
}};
}
else
{
static_assert
(
std
::
is_pointer_v
<
T
>
,
"unsupported argument type"
);
return
format_arg
{
static_cast
<
const
void
*>
(
arg
)};
}
}
template
<
size_t
N
>
format_arg
make_format_arg
(
const
char
(
&
arg
)[
N
])
{
return
format_arg
{
std
::
string_view
{
arg
,
N
-
1
}};
}
// Interface traversing the formatting output chunk by chunk.
class
CAF_CORE_EXPORT
compiled_format_string
{
public:
virtual
~
compiled_format_string
();
// Checks whether we reached the end of the format string.
virtual
bool
at_end
()
const
noexcept
=
0
;
// Returns the next chunk of the formatted output.
virtual
std
::
string_view
next
()
=
0
;
};
CAF_CORE_EXPORT
std
::
unique_ptr
<
compiled_format_string
>
compile_format_string
(
std
::
string_view
fstr
,
span
<
format_arg
>
args
);
template
<
class
OutputIt
,
class
...
Args
>
auto
format_to
(
OutputIt
out
,
std
::
string_view
fstr
,
Args
&&
...
raw_args
)
{
std
::
array
<
format_arg
,
sizeof
...(
Args
)
>
args
{
make_format_arg
(
raw_args
)...};
auto
compiled
=
compile_format_string
(
fstr
,
make_span
(
args
));
while
(
!
compiled
->
at_end
())
{
auto
chunk
=
compiled
->
next
();
out
=
std
::
copy
(
chunk
.
begin
(),
chunk
.
end
(),
out
);
}
return
out
;
}
template
<
class
...
Args
>
std
::
string
format
(
std
::
string_view
fstr
,
Args
&&
...
args
)
{
std
::
string
result
;
result
.
reserve
(
fstr
.
size
());
format_to
(
std
::
back_inserter
(
result
),
fstr
,
std
::
forward
<
Args
>
(
args
)...);
return
result
;
}
}
// namespace caf::detail
#endif
libcaf_core/caf/detail/is_one_of.hpp
View file @
ff9b5667
...
...
@@ -21,4 +21,7 @@ struct is_one_of<T, T, Ts...> : std::true_type {};
template
<
class
T
,
class
U
,
class
...
Ts
>
struct
is_one_of
<
T
,
U
,
Ts
...
>
:
is_one_of
<
T
,
Ts
...
>
{};
template
<
class
T
,
class
...
Ts
>
constexpr
bool
is_one_of_v
=
(
std
::
is_same_v
<
T
,
Ts
>
||
...);
}
// namespace caf::detail
libcaf_core/src/detail/format.cpp
0 → 100644
View file @
ff9b5667
This diff is collapsed.
Click to expand it.
libcaf_core/test/detail/format.cpp
0 → 100644
View file @
ff9b5667
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#define CAF_SUITE detail.format
#include "caf/detail/format.hpp"
#include "core-test.hpp"
#if !defined(CAF_USE_STD_FORMAT) && !defined(CAF_USE_SYSTEM_LIBFMT)
# define CAF_MINIMAL_FORMATTING
#endif
using
namespace
std
::
literals
;
using
caf
::
detail
::
format
;
using
caf
::
detail
::
format_to
;
TEST_CASE
(
"format strings without placeholders copies verbatim"
)
{
CHECK_EQ
(
format
(
"hello world"
),
"hello world"
);
CHECK_EQ
(
format
(
"foo {{bar}}"
),
"foo {bar}"
);
CHECK_EQ
(
format
(
"foo {{bar}} baz"
),
"foo {bar} baz"
);
}
TEST_CASE
(
"format strings without indexes iterate over their arguments"
)
{
CHECK_EQ
(
format
(
"foo: {}{}"
,
true
,
'!'
),
"foo: true!"
);
CHECK_EQ
(
format
(
"bar: {}{}"
,
false
,
'?'
),
"bar: false?"
);
CHECK_EQ
(
format
(
"{} {} {} {} {}"
,
1
,
2u
,
2.5
f
,
4.5
,
"5"
s
),
"1 2 2.5 4.5 5"
);
}
TEST_CASE
(
"format strings with indexes uses the specified arguments"
)
{
CHECK_EQ
(
format
(
"{1} {2} {0}"
,
3
,
1
,
2
),
"1 2 3"
);
CHECK_EQ
(
format
(
"{1} {0} {1}"
,
1
,
2
),
"2 1 2"
);
}
TEST_CASE
(
"format strings can specify rendering of floating point numbers"
)
{
CHECK_EQ
(
format
(
"{}"
,
2.5
),
"2.5"
);
CHECK_EQ
(
format
(
"{:.3f}"
,
2.5
),
"2.500"
);
CHECK_EQ
(
format
(
"{:.3F}"
,
2.5
),
"2.500"
);
CHECK_EQ
(
format
(
"{:g}"
,
2.5
),
"2.5"
);
CHECK_EQ
(
format
(
"{:G}"
,
2.5
),
"2.5"
);
CHECK_EQ
(
format
(
"{:.0e}"
,
10.0
),
"1e+01"
);
CHECK_EQ
(
format
(
"{:.0E}"
,
10.0
),
"1E+01"
);
}
TEST_CASE
(
"format strings can specify rendering of integers"
)
{
CHECK_EQ
(
format
(
"{}"
,
42
),
"42"
);
CHECK_EQ
(
format
(
"{:d}"
,
42
),
"42"
);
CHECK_EQ
(
format
(
"{:c}"
,
42
),
"*"
);
CHECK_EQ
(
format
(
"{:o}"
,
42
),
"52"
);
CHECK_EQ
(
format
(
"{:#o}"
,
42
),
"052"
);
CHECK_EQ
(
format
(
"{:x}"
,
42
),
"2a"
);
CHECK_EQ
(
format
(
"{:X}"
,
42
),
"2A"
);
CHECK_EQ
(
format
(
"{:#x}"
,
42
),
"0x2a"
);
CHECK_EQ
(
format
(
"{:#X}"
,
42
),
"0X2A"
);
CHECK_EQ
(
format
(
"{}"
,
42u
),
"42"
);
CHECK_EQ
(
format
(
"{:d}"
,
42u
),
"42"
);
CHECK_EQ
(
format
(
"{:c}"
,
42u
),
"*"
);
CHECK_EQ
(
format
(
"{:o}"
,
42u
),
"52"
);
CHECK_EQ
(
format
(
"{:#o}"
,
42u
),
"052"
);
CHECK_EQ
(
format
(
"{:x}"
,
42u
),
"2a"
);
CHECK_EQ
(
format
(
"{:X}"
,
42u
),
"2A"
);
CHECK_EQ
(
format
(
"{:#x}"
,
42u
),
"0x2a"
);
CHECK_EQ
(
format
(
"{:#X}"
,
42u
),
"0X2A"
);
CHECK_EQ
(
format
(
"'{:+}' '{:-}' '{: }'"
,
1
,
1
,
1
),
"'+1' '1' ' 1'"
);
CHECK_EQ
(
format
(
"'{:+}' '{:-}' '{: }'"
,
-
1
,
-
1
,
-
1
),
"'-1' '-1' '-1'"
);
}
TEST_CASE
(
"format strings may specify the width of the output"
)
{
CHECK_EQ
(
format
(
"{0:0{1}}"
,
1
,
2
),
"01"
);
CHECK_EQ
(
format
(
"{1:02} {0:02}"
,
1
,
2
),
"02 01"
);
CHECK_EQ
(
format
(
"{:!<3}?{:!>3}"
,
0
,
0
),
"0!!?!!0"
);
CHECK_EQ
(
format
(
"{:!^3}?{:!^3}"
,
'A'
,
'A'
),
"!A!?!A!"
);
CHECK_EQ
(
format
(
"{0:!^{1}}"
,
'A'
,
5
),
"!!A!!"
);
CHECK_EQ
(
format
(
"{:<3}?{:>3}"
,
0
,
0
),
"0 ? 0"
);
}
TEST_CASE
(
"format strings accept various string types as values"
)
{
using
namespace
std
::
literals
;
const
auto
*
cstr
=
"C-string"
;
CHECK_EQ
(
format
(
"{}"
,
cstr
),
"C-string"
);
CHECK_EQ
(
format
(
"{}"
,
"string literal"
),
"string literal"
);
CHECK_EQ
(
format
(
"{}"
,
"std::string"
s
),
"std::string"
);
CHECK_EQ
(
format
(
"{}"
,
"std::string_view"
sv
),
"std::string_view"
);
}
TEST_CASE
(
"format_to can incrementally build a string"
)
{
std
::
string
str
;
format_to
(
std
::
back_inserter
(
str
),
"foo"
);
CHECK_EQ
(
str
,
"foo"
);
format_to
(
std
::
back_inserter
(
str
),
"bar"
);
CHECK_EQ
(
str
,
"foobar"
);
format_to
(
std
::
back_inserter
(
str
),
"baz"
);
CHECK_EQ
(
str
,
"foobarbaz"
);
}
#if defined(CAF_ENABLE_EXCEPTIONS) && defined(CAF_MINIMAL_FORMATTING)
// Note: the standard version as well as libfmt (should) raise a compile-time
// error for these test cases. Only our minimal implementation throws.
TEST_CASE
(
"ill-formatted formatting strings throw"
)
{
CHECK_THROWS_AS
(
format
(
"foo {"
),
std
::
logic_error
);
CHECK_THROWS_AS
(
format
(
"foo } bar"
),
std
::
logic_error
);
CHECK_THROWS_AS
(
format
(
"{1}"
,
1
),
std
::
logic_error
);
}
#endif
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment