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
ea4fb677
Commit
ea4fb677
authored
Oct 24, 2017
by
Dominik Charousset
Committed by
Dominik Charousset
May 29, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Support lists and durations in config_value
parent
2ac0ae32
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
450 additions
and
22 deletions
+450
-22
libcaf_core/CMakeLists.txt
libcaf_core/CMakeLists.txt
+1
-0
libcaf_core/caf/actor_system_config.hpp
libcaf_core/caf/actor_system_config.hpp
+6
-1
libcaf_core/caf/config_option.hpp
libcaf_core/caf/config_option.hpp
+21
-3
libcaf_core/caf/config_value.hpp
libcaf_core/caf/config_value.hpp
+209
-3
libcaf_core/src/actor_system_config.cpp
libcaf_core/src/actor_system_config.cpp
+2
-1
libcaf_core/src/config_option.cpp
libcaf_core/src/config_option.cpp
+2
-1
libcaf_core/src/config_value.cpp
libcaf_core/src/config_value.cpp
+89
-0
libcaf_core/src/parse_ini.cpp
libcaf_core/src/parse_ini.cpp
+30
-13
libcaf_core/test/config_value.cpp
libcaf_core/test/config_value.cpp
+90
-0
No files found.
libcaf_core/CMakeLists.txt
View file @
ea4fb677
...
...
@@ -37,6 +37,7 @@ set(LIBCAF_CORE_SRCS
src/blocking_behavior.cpp
src/concatenated_tuple.cpp
src/config_option.cpp
src/config_value.cpp
src/decorated_tuple.cpp
src/default_attachable.cpp
src/deserializer.cpp
...
...
libcaf_core/caf/actor_system_config.hpp
View file @
ea4fb677
...
...
@@ -238,7 +238,10 @@ public:
}
/// Sets a config by using its INI name `config_name` to `config_value`.
actor_system_config
&
set
(
const
char
*
cn
,
config_value
cv
);
template
<
class
T
>
actor_system_config
&
set
(
const
char
*
cn
,
T
&&
x
)
{
return
set_impl
(
cn
,
config_value
{
std
::
forward
<
T
>
(
x
)});
}
// -- parser and CLI state ---------------------------------------------------
...
...
@@ -413,6 +416,8 @@ private:
&
make_type_erased_value
<
T
>
);
}
actor_system_config
&
set_impl
(
const
char
*
cn
,
config_value
cv
);
static
std
::
string
render_sec
(
uint8_t
,
atom_value
,
const
message
&
);
static
std
::
string
render_exit_reason
(
uint8_t
,
atom_value
,
const
message
&
);
...
...
libcaf_core/caf/config_option.hpp
View file @
ea4fb677
...
...
@@ -48,7 +48,7 @@ public:
using
legal_types
=
detail
::
type_list
<
bool
,
float
,
double
,
std
::
string
,
atom_value
,
int8_t
,
uint8_t
,
int16_t
,
uint16_t
,
int32_t
,
uint32_t
,
int64_t
,
uint64_t
>
;
uint64_t
,
duration
>
;
config_option
(
const
char
*
cat
,
const
char
*
nm
,
const
char
*
expl
);
...
...
@@ -95,6 +95,18 @@ public:
return
type_name_visitor_tbl
[
static_cast
<
size_t
>
(
index
)];
}
const
char
*
operator
()(
const
std
::
vector
<
config_value
>&
)
{
return
"a list"
;
}
const
char
*
operator
()(
const
std
::
map
<
std
::
string
,
config_value
>&
)
{
return
"a map"
;
}
const
char
*
operator
()(
const
timespan
&
)
{
return
"a timespan"
;
}
private:
// Catches non-integer types.
template
<
class
T
>
...
...
@@ -147,6 +159,12 @@ protected:
return
true
;
}
template
<
class
T
>
static
bool
assign_config_value
(
std
::
vector
<
T
>&
,
std
::
vector
<
config_value
>&
)
{
// TODO: implement me
}
void
report_type_error
(
size_t
ln
,
config_value
&
x
,
const
char
*
expected
,
optional
<
std
::
ostream
&>
out
);
...
...
@@ -250,13 +268,13 @@ public:
// and all floating point numbers as doubles
using
cfg_type
=
typename
std
::
conditional
<
std
::
is_integral
<
value_type
>::
value
&&
std
::
is_integral
<
value_type
>::
value
&&
!
std
::
is_same
<
bool
,
value_type
>::
value
,
int64_t
,
typename
std
::
conditional
<
std
::
is_floating_point
<
value_type
>::
value
,
double
,
value_type
value_type
>::
type
>::
type
;
value_type
tmp
;
...
...
libcaf_core/caf/config_value.hpp
View file @
ea4fb677
...
...
@@ -18,16 +18,222 @@
#pragma once
#include <
string
>
#include <
chrono
>
#include <cstdint>
#include <map>
#include <string>
#include <vector>
#include "caf/atom.hpp"
#include "caf/deep_to_string.hpp"
#include "caf/timestamp.hpp"
#include "caf/variant.hpp"
#include "caf/detail/comparable.hpp"
namespace
caf
{
/// A variant type for config parameters.
using
config_value
=
variant
<
std
::
string
,
double
,
int64_t
,
bool
,
atom_value
>
;
/// A type for config parameters with similar interface to a `variant`. This
/// type is not implemented as a simple variant alias because variants cannot
/// contain lists of themselves.
class
config_value
{
public:
using
T0
=
int64_t
;
using
T1
=
bool
;
using
T2
=
double
;
using
T3
=
atom_value
;
using
T4
=
timespan
;
using
T5
=
std
::
string
;
using
T6
=
std
::
vector
<
config_value
>
;
using
T7
=
std
::
map
<
std
::
string
,
config_value
>
;
using
type0
=
T0
;
using
types
=
detail
::
type_list
<
T0
,
T1
,
T2
,
T3
,
T4
,
T5
,
T6
,
T7
>
;
using
variant_type
=
variant
<
T0
,
T1
,
T2
,
T3
,
T4
,
T5
,
T6
,
T7
>
;
config_value
()
=
default
;
config_value
(
config_value
&&
other
)
=
default
;
config_value
(
const
config_value
&
other
)
=
default
;
template
<
class
T
,
class
E
=
detail
::
enable_if_t
<
!
std
::
is_same
<
detail
::
decay_t
<
T
>,
config_value
>::
value
>>
explicit
config_value
(
T
&&
x
)
{
set
(
std
::
forward
<
T
>
(
x
));
}
config_value
&
operator
=
(
config_value
&&
other
)
=
default
;
config_value
&
operator
=
(
const
config_value
&
other
)
=
default
;
template
<
class
T
,
class
E
=
detail
::
enable_if_t
<
!
std
::
is_same
<
detail
::
decay_t
<
T
>,
config_value
>::
value
>>
config_value
&
operator
=
(
T
&&
x
)
{
set
(
std
::
forward
<
T
>
(
x
));
return
*
this
;
}
~
config_value
();
/// Converts the value to a list with one element. Does nothing if the value
/// already is a list.
void
convert_to_list
();
/// Appends `x` to a list. Converts this config value to a list first by
/// calling `convert_to_list` if needed.
void
append
(
config_value
x
);
inline
size_t
index
()
const
{
return
data_
.
index
();
}
inline
bool
valueless_by_exception
()
const
{
return
data_
.
valueless_by_exception
();
}
template
<
class
Visitor
>
auto
apply
(
Visitor
&&
visitor
)
->
decltype
(
visitor
(
std
::
declval
<
T0
&>
()))
{
return
data_
.
apply
(
visitor
);
}
template
<
class
Visitor
>
auto
apply
(
Visitor
&&
visitor
)
const
->
decltype
(
visitor
(
std
::
declval
<
const
T0
&>
()))
{
return
data_
.
apply
(
visitor
);
}
template
<
int
Pos
>
const
typename
detail
::
tl_at
<
types
,
Pos
>::
type
&
get
(
std
::
integral_constant
<
int
,
Pos
>
token
)
const
{
return
data_
.
get
(
token
);
}
template
<
int
Pos
>
typename
detail
::
tl_at
<
types
,
Pos
>::
type
&
get
(
std
::
integral_constant
<
int
,
Pos
>
token
)
{
return
data_
.
get
(
token
);
}
inline
variant_type
&
data
()
{
return
data_
;
}
inline
const
variant_type
&
data
()
const
{
return
data_
;
}
/// @endcond
private:
// -- auto conversion of related types ---------------------------------------
inline
void
set
(
bool
x
)
{
data_
=
x
;
}
inline
void
set
(
const
char
*
x
)
{
data_
=
std
::
string
{
x
};
}
template
<
class
T
>
detail
::
enable_if_t
<
detail
::
is_one_of
<
detail
::
decay_t
<
T
>
,
T2
,
T3
,
T4
,
T5
,
T6
,
T7
>::
value
>
set
(
T
&&
x
)
{
data_
=
std
::
forward
<
T
>
(
x
);
}
template
<
class
T
>
detail
::
enable_if_t
<
std
::
is_integral
<
T
>::
value
>
set
(
T
x
)
{
data_
=
static_cast
<
int64_t
>
(
x
);
}
inline
void
convert
(
timespan
x
)
{
data_
=
x
;
}
template
<
class
Rep
,
class
Ratio
>
void
convert
(
std
::
chrono
::
duration
<
Rep
,
Ratio
>
x
)
{
data_
=
std
::
chrono
::
duration_cast
<
timespan
>
(
x
);
}
// -- member variables -------------------------------------------------------
variant_type
data_
;
};
/// @relates config_value
bool
operator
<
(
const
config_value
&
x
,
const
config_value
&
y
);
/// @relates config_value
bool
operator
==
(
const
config_value
&
x
,
const
config_value
&
y
);
/// @relates config_value
inline
bool
operator
>=
(
const
config_value
&
x
,
const
config_value
&
y
)
{
return
!
(
x
<
y
);
}
/// @relates config_value
inline
bool
operator
!=
(
const
config_value
&
x
,
const
config_value
&
y
)
{
return
!
(
x
==
y
);
}
/// @relates config_value
template
<
class
Visitor
>
auto
visit
(
Visitor
&&
f
,
config_value
&
x
)
->
decltype
(
f
(
std
::
declval
<
int64_t
&>
()))
{
return
visit
(
std
::
forward
<
Visitor
>
(
f
),
x
.
data
());
}
/// @relates config_value
template
<
class
Visitor
>
auto
visit
(
Visitor
&&
f
,
const
config_value
&
x
)
->
decltype
(
f
(
std
::
declval
<
const
int64_t
&>
()))
{
return
visit
(
std
::
forward
<
Visitor
>
(
f
),
x
.
data
());
}
/// @relates config_value
template
<
class
T
>
bool
holds_alternative
(
const
config_value
&
x
)
{
return
holds_alternative
<
T
>
(
x
.
data
());
}
/// @relates config_value
template
<
class
T
>
T
&
get
(
config_value
&
x
)
{
return
get
<
T
>
(
x
.
data
());
}
/// @relates config_value
template
<
class
T
>
const
T
&
get
(
const
config_value
&
x
)
{
return
get
<
T
>
(
x
.
data
());
}
/// @relates config_value
template
<
class
T
>
T
*
get_if
(
config_value
*
x
)
{
return
x
!=
nullptr
?
get_if
<
T
>
(
&
(
x
->
data
()))
:
nullptr
;
}
/// @relates config_value
template
<
class
T
>
const
T
*
get_if
(
const
config_value
*
x
)
{
return
x
!=
nullptr
?
get_if
<
T
>
(
&
(
x
->
data
()))
:
nullptr
;
}
/// @relates config_value
inline
std
::
string
to_string
(
const
config_value
&
x
)
{
return
deep_to_string
(
x
.
data
());
}
/// @relates config_value
template
<
class
Inspector
>
typename
Inspector
::
result_type
inspect
(
Inspector
&
f
,
config_value
&
x
)
{
return
f
(
meta
::
type_name
(
"config_value"
),
x
.
data
());
}
}
// namespace caf
libcaf_core/src/actor_system_config.cpp
View file @
ea4fb677
...
...
@@ -419,7 +419,8 @@ actor_system_config::add_error_category(atom_value x, error_renderer y) {
return
*
this
;
}
actor_system_config
&
actor_system_config
::
set
(
const
char
*
cn
,
config_value
cv
)
{
actor_system_config
&
actor_system_config
::
set_impl
(
const
char
*
cn
,
config_value
cv
)
{
auto
e
=
options_
.
end
();
auto
i
=
std
::
find_if
(
options_
.
begin
(),
e
,
[
cn
](
const
option_ptr
&
ptr
)
{
return
ptr
->
full_name
()
==
cn
;
...
...
libcaf_core/src/config_option.cpp
View file @
ea4fb677
...
...
@@ -35,7 +35,8 @@ const char* type_name_visitor_tbl[] {
"a 32-bit integer"
,
"a 32-bit unsigned integer"
,
"a 64-bit integer"
,
"a 64-bit unsigned integer"
"a 64-bit unsigned integer"
,
"a duration"
};
config_option
::
config_option
(
const
char
*
cat
,
const
char
*
nm
,
const
char
*
expl
)
...
...
libcaf_core/src/config_value.cpp
0 → 100644
View file @
ea4fb677
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright (C) 2011 - 2017 *
* Dominik Charousset <dominik.charousset (at) haw-hamburg.de> *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/config_value.hpp"
#include "caf/detail/type_traits.hpp"
namespace
caf
{
namespace
{
struct
less_comparator
{
template
<
class
T
,
class
U
>
detail
::
enable_if_t
<
std
::
is_same
<
T
,
U
>::
value
,
bool
>
operator
()(
const
T
&
x
,
const
U
&
y
)
const
{
return
x
<
y
;
}
template
<
class
T
,
class
U
>
detail
::
enable_if_t
<!
std
::
is_same
<
T
,
U
>::
value
,
bool
>
operator
()(
const
T
&
,
const
U
&
)
const
{
// Sort by type index when comparing different types.
using
namespace
detail
;
using
types
=
config_value
::
types
;
return
tl_index_of
<
types
,
T
>::
value
<
tl_index_of
<
types
,
U
>::
value
;
}
};
struct
equal_comparator
{
template
<
class
T
,
class
U
>
detail
::
enable_if_t
<
std
::
is_same
<
T
,
U
>::
value
,
bool
>
operator
()(
const
T
&
x
,
const
U
&
y
)
const
{
return
x
==
y
;
}
template
<
class
T
,
class
U
>
detail
::
enable_if_t
<!
std
::
is_same
<
T
,
U
>::
value
,
bool
>
operator
()(
const
T
&
,
const
U
&
)
const
{
return
false
;
}
};
}
// namespace <anonymous>
config_value
::~
config_value
()
{
// nop
}
void
config_value
::
convert_to_list
()
{
if
(
holds_alternative
<
T6
>
(
data_
))
return
;
config_value
tmp
{
std
::
move
(
*
this
)};
set
(
T6
{
std
::
move
(
tmp
)});
}
void
config_value
::
append
(
config_value
x
)
{
convert_to_list
();
auto
&
xs
=
caf
::
get
<
T6
>
(
data_
);
xs
.
emplace_back
(
std
::
move
(
x
));
}
bool
operator
<
(
const
config_value
&
x
,
const
config_value
&
y
)
{
less_comparator
cmp
;
return
visit
(
cmp
,
x
.
data
(),
y
.
data
());
}
bool
operator
==
(
const
config_value
&
x
,
const
config_value
&
y
)
{
equal_comparator
cmp
;
return
visit
(
cmp
,
x
.
data
(),
y
.
data
());
}
}
// namespace caf
libcaf_core/src/parse_ini.cpp
View file @
ea4fb677
...
...
@@ -26,12 +26,27 @@
namespace
caf
{
namespace
detail
{
void
parse_ini_t
::
operator
()(
std
::
istream
&
input
,
const
config_consumer
&
consumer_fun
,
namespace
{
// TODO: replace with generic lambda when switching to C++14
// Wraps a temporary into an (lvalue) config_value and calls `consumer_fun`.
struct
consumer_t
{
const
parse_ini_t
::
config_consumer
&
consumer_fun
;
parse_ini_t
::
opt_err
&
errors
;
template
<
class
T
>
void
operator
()(
size_t
ln
,
std
::
string
name
,
T
&&
x
)
{
config_value
tmp
{
std
::
forward
<
T
>
(
x
)};
consumer_fun
(
ln
,
std
::
move
(
name
),
tmp
,
errors
);
}
};
}
// namespace <anonymous>
void
parse_ini_t
::
operator
()(
std
::
istream
&
input
,
const
config_consumer
&
consumer_fun
,
opt_err
errors
)
const
{
// wraps a temporary into an (lvalue) config_value and calls `consumer_fun`
auto
consumer
=
[
&
](
size_t
ln
,
std
::
string
name
,
config_value
x
)
{
consumer_fun
(
ln
,
std
::
move
(
name
),
x
,
errors
);
};
consumer_t
consumer
{
consumer_fun
,
errors
};
std
::
string
group
;
std
::
string
line
;
size_t
ln
=
0
;
// line number
...
...
@@ -90,14 +105,12 @@ void parse_ini_t::operator()(std::istream& input, const config_consumer& consume
// begin-of-value, ignoring whitespaces after '='
auto
bov
=
find_if_not
(
eqs
+
1
,
eol
,
::
isspace
);
// auto-detect what we are dealing with
constexpr
const
char
*
true_str
=
"true"
;
constexpr
const
char
*
false_str
=
"false"
;
auto
icase_eq
=
[](
char
x
,
char
y
)
{
return
tolower
(
x
)
==
tolower
(
y
);
};
if
(
std
::
equal
(
bov
,
eol
,
true_str
,
icase_eq
))
{
if
(
std
::
equal
(
bov
,
eol
,
"true"
,
icase_eq
))
{
consumer
(
ln
,
std
::
move
(
key
),
true
);
}
else
if
(
std
::
equal
(
bov
,
eol
,
false_str
,
icase_eq
))
{
}
else
if
(
std
::
equal
(
bov
,
eol
,
"false"
,
icase_eq
))
{
consumer
(
ln
,
std
::
move
(
key
),
false
);
}
else
if
(
*
bov
==
'\''
)
{
// end-of-atom iterator
...
...
@@ -189,6 +202,7 @@ void parse_ini_t::operator()(std::istream& input, const config_consumer& consume
else
consumer
(
ln
,
std
::
move
(
key
),
is_neg
?
-
res
:
res
);
};
// check for number prefixes (0 for octal, 0x for hex, 0b for binary)
if
(
*
bov
==
'0'
&&
std
::
distance
(
bov
,
eol
)
>
1
)
switch
(
*
(
bov
+
1
))
{
case
'x'
:
...
...
@@ -205,10 +219,13 @@ void parse_ini_t::operator()(std::istream& input, const config_consumer& consume
else
set_dval
();
}
else
if
(
all_of
(
bov
,
eol
,
::
isdigit
))
set_ival
(
10
,
0
,
"invalid decimal value"
);
else
set_dval
();
else
{
// no suffix, try parsing the line as integer or double
if
(
all_of
(
bov
,
eol
,
::
isdigit
))
set_ival
(
10
,
0
,
"invalid decimal value"
);
else
set_dval
();
}
}
}
}
...
...
libcaf_core/test/config_value.cpp
0 → 100644
View file @
ea4fb677
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/config.hpp"
#define CAF_SUITE config_value
#include "caf/test/unit_test.hpp"
#include <string>
#include "caf/actor_system.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/atom.hpp"
#include "caf/deep_to_string.hpp"
#include "caf/none.hpp"
#include "caf/variant.hpp"
using
namespace
std
;
using
namespace
caf
;
struct
tostring_visitor
:
static_visitor
<
string
>
{
template
<
class
T
>
inline
string
operator
()(
const
T
&
value
)
{
return
to_string
(
value
);
}
};
CAF_TEST
(
default_constructed
)
{
config_value
x
;
CAF_CHECK_EQUAL
(
holds_alternative
<
int64_t
>
(
x
),
true
);
CAF_CHECK_EQUAL
(
get
<
int64_t
>
(
x
),
0
);
}
CAF_TEST
(
list
)
{
std
::
vector
<
config_value
>
xs
;
xs
.
emplace_back
(
int64_t
{
1
});
xs
.
emplace_back
(
atom
(
"foo"
));
xs
.
emplace_back
(
string
(
"bar"
));
config_value
x
{
xs
};
CAF_CHECK_EQUAL
(
to_string
(
x
),
"[1, 'foo',
\"
bar
\"
]"
);
auto
ys
=
xs
;
xs
.
emplace_back
(
std
::
move
(
ys
));
x
=
xs
;
CAF_CHECK_EQUAL
(
to_string
(
x
),
"[1, 'foo',
\"
bar
\"
, [1, 'foo',
\"
bar
\"
]]"
);
}
CAF_TEST
(
convert_to_list
)
{
config_value
x
{
int64_t
{
42
}};
CAF_CHECK_EQUAL
(
to_string
(
x
),
"42"
);
x
.
convert_to_list
();
CAF_CHECK_EQUAL
(
to_string
(
x
),
"[42]"
);
x
.
convert_to_list
();
CAF_CHECK_EQUAL
(
to_string
(
x
),
"[42]"
);
}
CAF_TEST
(
append
)
{
config_value
x
{
int64_t
{
1
}};
CAF_CHECK_EQUAL
(
to_string
(
x
),
"1"
);
x
.
append
(
config_value
{
int64_t
{
2
}});
CAF_CHECK_EQUAL
(
to_string
(
x
),
"[1, 2]"
);
x
.
append
(
config_value
{
atom
(
"foo"
)});
CAF_CHECK_EQUAL
(
to_string
(
x
),
"[1, 2, 'foo']"
);
}
CAF_TEST
(
maps
)
{
std
::
map
<
std
::
string
,
config_value
>
xs
;
xs
[
"num"
]
=
int64_t
{
42
};
xs
[
"atm"
]
=
atom
(
"hello"
);
xs
[
"str"
]
=
string
{
"foobar"
};
xs
[
"dur"
]
=
timespan
{
100
};
config_value
x
{
xs
};
CAF_CHECK_EQUAL
(
to_string
(
x
),
R"([("atm", 'hello'), ("dur", 100ns), ("num", 42), ("str", "foobar")])"
);
}
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