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
Show 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 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.
#include "caf/detail/format.hpp"
#include "caf/config.hpp"
#include "caf/detail/consumer.hpp"
#include "caf/detail/parser/chars.hpp"
#include "caf/detail/parser/is_char.hpp"
#include "caf/detail/parser/read_unsigned_integer.hpp"
#include "caf/detail/print.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/parser_state.hpp"
#include "caf/pec.hpp"
#include "caf/raise_error.hpp"
#include "caf/span.hpp"
#include <type_traits>
CAF_PUSH_UNUSED_LABEL_WARNING
#include "caf/detail/parser/fsm.hpp"
namespace
caf
::
detail
::
parser
{
struct
copy_state
{
using
fn_t
=
void
(
*
)(
string_parser_state
&
,
copy_state
&
);
using
maybe_uint
=
std
::
optional
<
size_t
>
;
explicit
copy_state
(
span
<
format_arg
>
arg_list
);
size_t
next_arg_index
=
0
;
// The next argument index for "auto"-mode.
maybe_uint
arg_index
;
// The current argument index.
maybe_uint
width
;
// The current width for formatting.
bool
is_number
=
false
;
// Whether we are currently parsing a number.
char
fill
=
' '
;
// The fill character when setting an alignment.
char
align
=
0
;
// Alignment; '<': left, '>': right, '^': center.
char
type
=
0
;
// Selects an integer/float representation.
span
<
format_arg
>
args
;
// The list of arguments to use for formatting.
fn_t
fn
;
// The current state function.
std
::
vector
<
char
>
buf
;
// Stores the formatted string.
std
::
vector
<
char
>
fstr
;
// Assembles the format string for snprintf.
// Restores the default values.
void
reset
()
{
arg_index
=
std
::
nullopt
;
width
=
std
::
nullopt
;
fill
=
' '
;
align
=
0
;
type
=
0
;
is_number
=
false
;
fstr
.
clear
();
fstr
.
push_back
(
'%'
);
}
// Callback for the format string parser.
void
after_width
()
{
CAF_ASSERT
(
width
.
has_value
());
if
(
align
==
0
)
// if alignment is set, we manage the width ourselves
print
(
fstr
,
*
width
);
}
// Callback for the format string parser.
void
after_width_index
(
size_t
index
)
{
if
(
index
>=
args
.
size
())
CAF_RAISE_ERROR
(
std
::
logic_error
,
"invalid format string: width index out of range"
);
const
auto
&
arg
=
args
[
index
];
if
(
const
auto
*
i64
=
std
::
get_if
<
int64_t
>
(
&
arg
))
{
if
(
*
i64
<
0
)
CAF_RAISE_ERROR
(
std
::
logic_error
,
"invalid format string: negative width"
);
width
=
static_cast
<
size_t
>
(
*
i64
);
after_width
();
return
;
}
if
(
const
auto
*
u64
=
std
::
get_if
<
uint64_t
>
(
&
arg
))
{
width
=
static_cast
<
size_t
>
(
*
u64
);
after_width
();
return
;
}
CAF_RAISE_ERROR
(
std
::
logic_error
,
"invalid format string: expected an integer for width"
);
}
// Callback for the format string parser.
void
after_precision
(
size_t
value
)
{
print
(
fstr
,
value
);
}
// Callback for the format string parser.
void
after_precision_index
(
size_t
index
)
{
if
(
index
>=
args
.
size
())
CAF_RAISE_ERROR
(
std
::
logic_error
,
"invalid format string: precision index out of range"
);
const
auto
&
arg
=
args
[
index
];
if
(
const
auto
*
i64
=
std
::
get_if
<
int64_t
>
(
&
arg
))
{
if
(
*
i64
<
0
)
CAF_RAISE_ERROR
(
std
::
logic_error
,
"invalid format string: negative precision"
);
print
(
fstr
,
*
i64
);
return
;
}
if
(
const
auto
*
u64
=
std
::
get_if
<
uint64_t
>
(
&
arg
))
{
print
(
fstr
,
*
u64
);
return
;
}
CAF_RAISE_ERROR
(
std
::
logic_error
,
"invalid format string: expected an integer for precision"
);
}
// Callback for the format string parser.
void
set_type
(
char
c
)
{
type
=
c
;
fstr
.
push_back
(
c
);
}
// Callback for the format string parser.
void
set_padding
(
char
c
)
{
fstr
.
push_back
(
c
);
if
(
align
!=
0
)
CAF_RAISE_ERROR
(
std
::
logic_error
,
"invalid format string: padding and "
"alignment are mutually exclusive"
);
}
// Callback for the format string parser.
void
set_align
(
char
fill_char
,
char
align_char
)
{
fill
=
fill_char
;
align
=
align_char
;
}
// Adds padding to the buffer and aligns the content.
void
apply_alignment
(
size_t
fixed_width
)
{
if
(
buf
.
size
()
<
fixed_width
)
{
if
(
align
==
0
)
align
=
is_number
?
'>'
:
'<'
;
switch
(
align
)
{
case
'<'
:
buf
.
insert
(
buf
.
end
(),
fixed_width
-
buf
.
size
(),
fill
);
break
;
case
'>'
:
buf
.
insert
(
buf
.
begin
(),
fixed_width
-
buf
.
size
(),
fill
);
break
;
default:
// '^'
buf
.
insert
(
buf
.
begin
(),
(
fixed_width
-
buf
.
size
())
/
2
,
fill
);
buf
.
resize
(
fixed_width
,
fill
);
}
}
}
// Renders the next argument.
void
render
()
{
if
(
arg_index
)
{
render_arg
(
*
arg_index
);
arg_index
=
std
::
nullopt
;
}
else
{
render_arg
(
next_arg_index
++
);
}
}
// Renders the argument at `index`.
void
render_arg
(
size_t
index
)
{
if
(
index
>=
args
.
size
())
CAF_RAISE_ERROR
(
std
::
logic_error
,
"index out of range"
);
std
::
visit
([
&
](
auto
val
)
{
render_val
(
val
);
},
args
[
index
]);
apply_alignment
(
width
.
value_or
(
0
));
}
// Writes "true" or "false" to the buffer.
void
render_val
(
bool
val
)
{
print
(
buf
,
val
);
}
// Writes a single character to the buffer.
void
render_val
(
char
val
)
{
buf
.
push_back
(
val
);
}
// Calls snprintf to render `val`.
template
<
class
T
>
void
do_sprintf
(
T
val
)
{
fstr
.
push_back
(
'\0'
);
auto
buf_size
=
std
::
snprintf
(
nullptr
,
0
,
fstr
.
data
(),
val
);
if
(
buf_size
<=
0
)
CAF_RAISE_ERROR
(
std
::
logic_error
,
"invalid format string"
);
buf
.
resize
(
static_cast
<
size_t
>
(
buf_size
)
+
1
);
buf_size
=
std
::
snprintf
(
buf
.
data
(),
buf_size
+
1
,
fstr
.
data
(),
val
);
if
(
buf_size
<
0
||
static_cast
<
size_t
>
(
buf_size
)
>=
buf
.
size
())
CAF_RAISE_ERROR
(
std
::
logic_error
,
"snprintf failed"
);
buf
.
resize
(
static_cast
<
size_t
>
(
buf_size
));
}
// Calls snprintf to render `val`.
void
render_val
(
int64_t
val
)
{
is_number
=
true
;
switch
(
type
)
{
case
0
:
type
=
'd'
;
fstr
.
push_back
(
'd'
);
[[
fallthrough
]];
case
'd'
:
case
'i'
:
fstr
.
pop_back
();
fstr
.
push_back
(
'l'
);
fstr
.
push_back
(
'l'
);
fstr
.
push_back
(
type
);
do_sprintf
(
static_cast
<
long
long
int
>
(
val
));
break
;
case
'c'
:
if
(
val
>=
-
128
&&
val
<=
127
)
{
buf
.
push_back
(
static_cast
<
char
>
(
val
));
return
;
}
CAF_RAISE_ERROR
(
std
::
logic_error
,
"cannot convert integer to character: too large"
);
case
'o'
:
case
'x'
:
case
'X'
:
if
(
val
<
0
)
CAF_RAISE_ERROR
(
std
::
logic_error
,
"cannot render negative number as unsigned"
);
render_val
(
static_cast
<
uint64_t
>
(
val
));
break
;
default:
CAF_RAISE_ERROR
(
std
::
logic_error
,
"invalid format string for unsigned integer"
);
}
}
// Calls snprintf to render `val`.
void
render_val
(
uint64_t
val
)
{
is_number
=
true
;
switch
(
type
)
{
case
'd'
:
fstr
.
pop_back
();
[[
fallthrough
]];
case
0
:
type
=
'u'
;
fstr
.
push_back
(
'u'
);
[[
fallthrough
]];
case
'u'
:
case
'o'
:
case
'x'
:
case
'X'
:
fstr
.
pop_back
();
fstr
.
push_back
(
'l'
);
fstr
.
push_back
(
'l'
);
fstr
.
push_back
(
type
);
do_sprintf
(
static_cast
<
long
long
unsigned
int
>
(
val
));
break
;
case
'c'
:
if
(
val
<=
127
)
{
buf
.
push_back
(
static_cast
<
char
>
(
val
));
return
;
}
CAF_RAISE_ERROR
(
std
::
logic_error
,
"cannot convert integer to character: too large"
);
default:
CAF_RAISE_ERROR
(
std
::
logic_error
,
"invalid format string for unsigned integer"
);
}
}
// Calls snprintf to render `val`.
void
render_val
(
double
val
)
{
is_number
=
true
;
switch
(
type
)
{
case
0
:
type
=
'g'
;
fstr
.
push_back
(
'g'
);
[[
fallthrough
]];
case
'a'
:
case
'A'
:
case
'e'
:
case
'E'
:
case
'f'
:
case
'F'
:
case
'g'
:
case
'G'
:
do_sprintf
(
val
);
break
;
default:
CAF_RAISE_ERROR
(
std
::
logic_error
,
"invalid type for floating point"
);
}
}
// Writes a string to the buffer.
void
render_val
(
const
char
*
val
)
{
for
(
auto
ch
=
*
val
++
;
ch
!=
'\0'
;
ch
=
*
val
++
)
buf
.
push_back
(
ch
);
}
// Writes a string to the buffer.
void
render_val
(
std
::
string_view
val
)
{
buf
.
insert
(
buf
.
end
(),
val
.
begin
(),
val
.
end
());
}
// Writes a pointer (address) to the buffer.
void
render_val
(
const
void
*
val
)
{
print
(
buf
,
val
);
}
};
template
<
class
ParserState
>
void
copy_verbatim
(
ParserState
&
ps
,
copy_state
&
cs
);
// Parses a format string and writes the result to `cs`.
template
<
class
ParserState
>
void
copy_formatted
(
ParserState
&
ps
,
copy_state
&
cs
)
{
cs
.
reset
();
auto
guard
=
make_scope_guard
([
&
]
{
if
(
ps
.
code
<=
pec
::
trailing_character
)
cs
.
fn
=
copy_verbatim
<
string_parser_state
>
;
});
size_t
tmp
=
0
;
auto
make_tmp_consumer
=
[
&
tmp
]
{
tmp
=
0
;
return
make_consumer
(
tmp
);
};
typename
ParserState
::
iterator_type
maybe_fill
;
// spec = [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
// fill = <a character other than '{' or '}'>
// align = "<" | ">" | "^"
// sign = "+" | "-" | " "
// width = integer | "{" [arg_id] "}"
// precision = integer | "{" [arg_id] "}"
// type = "a" | "A" | "b" | "B" | "c" | "d" | "e" | "E" | "f" | "F" |
// "g" | "G" | "o" | "p" | "s" | "x" | "X"
// clang-format off
start
();
state
(
init
)
{
fsm_epsilon
(
read_unsigned_integer
(
ps
,
make_consumer
(
cs
.
arg_index
)),
after_arg_index
,
decimal_chars
)
epsilon
(
after_arg_index
)
}
state
(
after_arg_index
)
{
transition
(
has_close_brace
,
'}'
,
cs
.
render
())
transition
(
read_format_spec
,
':'
)
}
state
(
read_format_spec
)
{
transition
(
disambiguate_fill
,
any_char
,
maybe_fill
=
ps
.
i
)
}
state
(
disambiguate_fill
)
{
transition
(
after_align
,
"<>^"
,
cs
.
set_align
(
*
maybe_fill
,
ch
))
epsilon
(
after_fill
,
any_char
,
ch
=
*
(
ps
.
i
=
maybe_fill
))
// go back one char
}
state
(
after_fill
)
{
transition
(
after_align
,
"<>^"
,
cs
.
set_align
(
' '
,
ch
))
epsilon
(
after_align
,
any_char
)
}
state
(
after_align
)
{
transition
(
after_sign
,
"+- "
,
cs
.
fstr
.
push_back
(
ch
))
epsilon
(
after_sign
,
any_char
)
}
state
(
after_sign
)
{
transition
(
after_alt
,
'#'
,
cs
.
fstr
.
push_back
(
ch
))
epsilon
(
after_alt
,
any_char
)
}
state
(
after_alt
)
{
transition
(
after_padding
,
'0'
,
cs
.
set_padding
(
ch
))
epsilon
(
after_padding
,
any_char
)
}
state
(
after_padding
)
{
fsm_epsilon
(
read_unsigned_integer
(
ps
,
make_consumer
(
cs
.
width
)),
after_width_ret
,
decimal_chars
)
fsm_transition
(
read_unsigned_integer
(
ps
,
make_tmp_consumer
()),
after_width_index
,
'{'
)
epsilon
(
after_width
,
any_char
)
}
state
(
after_width_index
)
{
transition
(
after_width
,
'}'
,
cs
.
after_width_index
(
tmp
))
}
state
(
after_width_ret
)
{
epsilon
(
after_width
,
any_char
,
cs
.
after_width
())
}
state
(
after_width
)
{
transition
(
read_precision
,
'.'
,
cs
.
fstr
.
push_back
(
ch
))
epsilon
(
after_precision
,
any_char
)
}
state
(
read_precision
)
{
fsm_epsilon
(
read_unsigned_integer
(
ps
,
make_tmp_consumer
()),
after_precision_ret
,
decimal_chars
)
fsm_transition
(
read_unsigned_integer
(
ps
,
make_tmp_consumer
()),
after_precision_index
,
'{'
)
epsilon
(
after_precision
,
any_char
)
}
state
(
after_precision_ret
)
{
epsilon
(
after_width
,
any_char
,
cs
.
after_precision
(
tmp
))
}
state
(
after_precision_index
)
{
transition
(
after_precision
,
'}'
,
cs
.
after_precision_index
(
tmp
))
}
state
(
after_precision
)
{
transition
(
after_locale
,
'L'
)
// not supported by snprintf
epsilon
(
after_locale
,
any_char
)
}
state
(
after_locale
)
{
transition
(
after_type
,
"aAbBcdeEfFgGopsxX"
,
cs
.
set_type
(
ch
))
transition
(
has_close_brace
,
'}'
,
cs
.
render
())
}
state
(
after_type
)
{
transition
(
has_close_brace
,
'}'
,
cs
.
render
())
}
term_state
(
has_close_brace
)
{
// nop
}
fin
();
// clang-format on
}
// Copies the input verbatim to the output buffer until finding a format string.
template
<
class
ParserState
>
void
copy_verbatim
(
ParserState
&
ps
,
copy_state
&
cs
)
{
// clang-format off
start
();
term_state
(
init
)
{
transition
(
has_open_brace
,
'{'
)
transition
(
has_close_brace
,
'}'
)
transition
(
init
,
any_char
,
cs
.
buf
.
push_back
(
ch
))
}
state
(
has_open_brace
)
{
transition
(
init
,
'{'
,
cs
.
buf
.
push_back
(
'{'
))
epsilon
(
at_fmt_start
,
any_char
,
cs
.
fn
=
copy_formatted
<
string_parser_state
>
)
}
state
(
has_close_brace
)
{
transition
(
init
,
'}'
,
cs
.
buf
.
push_back
(
'}'
))
}
term_state
(
at_fmt_start
)
{
// nop
}
fin
();
// clang-format on
}
copy_state
::
copy_state
(
span
<
format_arg
>
arg_list
)
:
args
(
arg_list
)
{
fn
=
copy_verbatim
<
string_parser_state
>
;
buf
.
reserve
(
64
);
}
}
// namespace caf::detail::parser
#include "caf/detail/parser/fsm_undef.hpp"
CAF_POP_WARNINGS
namespace
caf
::
detail
{
class
compiled_format_string_impl
:
public
compiled_format_string
{
public:
explicit
compiled_format_string_impl
(
std
::
string_view
fstr
,
span
<
format_arg
>
args
)
:
begin_
(
fstr
.
begin
()),
state_
(
begin_
,
fstr
.
end
()),
copy_
(
args
)
{
// nop
}
bool
at_end
()
const
noexcept
override
{
return
state_
.
at_end
();
}
std
::
string_view
next
()
override
{
copy_
.
buf
.
clear
();
copy_
.
fn
(
state_
,
copy_
);
if
(
state_
.
code
>
pec
::
trailing_character
)
{
auto
pos
=
std
::
distance
(
begin_
,
state_
.
i
);
std
::
string
err
=
"format error at offset "
;
err
+=
std
::
to_string
(
pos
);
err
+=
": "
;
err
+=
to_string
(
state_
.
code
);
CAF_RAISE_ERROR
(
std
::
logic_error
,
err
.
c_str
());
}
return
std
::
string_view
{
copy_
.
buf
.
data
(),
copy_
.
buf
.
size
()};
}
private:
std
::
string_view
::
iterator
begin_
;
string_parser_state
state_
;
parser
::
copy_state
copy_
;
};
compiled_format_string
::~
compiled_format_string
()
{
// nop
}
std
::
unique_ptr
<
compiled_format_string
>
compile_format_string
(
std
::
string_view
fstr
,
span
<
format_arg
>
args
)
{
return
std
::
make_unique
<
compiled_format_string_impl
>
(
fstr
,
args
);
}
}
// namespace caf::detail
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