Skip to content

Commit 8dbf758

Browse files
committed
Common: allow for dynamic types
Signed-off-by: Felix Schlepper <felix.schlepper@cern.ch>
1 parent 323df58 commit 8dbf758

6 files changed

Lines changed: 865 additions & 33 deletions

File tree

Common/Utils/include/CommonUtils/ConfigurableParam.h

Lines changed: 246 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,24 @@
99
// granted to it by virtue of its status as an Intergovernmental Organization
1010
// or submit itself to any jurisdiction.
1111

12-
//first version 8/2018, Sandro Wenzel
12+
// first version 8/2018, Sandro Wenzel
1313

1414
#ifndef COMMON_SIMCONFIG_INCLUDE_SIMCONFIG_CONFIGURABLEPARAM_H_
1515
#define COMMON_SIMCONFIG_INCLUDE_SIMCONFIG_CONFIGURABLEPARAM_H_
1616

17-
#include <vector>
17+
#include <algorithm>
1818
#include <cassert>
19+
#include <cctype>
20+
#include <concepts>
21+
#include <cstdint>
22+
#include <limits>
1923
#include <map>
24+
#include <sstream>
25+
#include <stdexcept>
26+
#include <string>
27+
#include <type_traits>
2028
#include <unordered_map>
29+
#include <vector>
2130
#include <boost/property_tree/ptree_fwd.hpp>
2231
#include <typeinfo>
2332
#include <iostream>
@@ -136,6 +145,233 @@ class EnumRegistry
136145
std::unordered_map<std::string, EnumLegalValues> entries;
137146
};
138147

148+
template <typename T>
149+
concept Container = !std::is_same_v<std::remove_cvref_t<T>, std::string> && requires(T t) {
150+
typename T::value_type;
151+
typename T::iterator;
152+
{ t.begin() } -> std::same_as<typename T::iterator>;
153+
{ t.end() } -> std::same_as<typename T::iterator>;
154+
};
155+
156+
template <typename T>
157+
concept MapLike = Container<T> && requires {
158+
typename T::key_type;
159+
typename T::mapped_type;
160+
};
161+
162+
template <typename T>
163+
concept SequenceContainer = Container<T> && !MapLike<T>;
164+
165+
template <typename T>
166+
concept HasPushBack = requires(T a, typename T::value_type v) {
167+
{ a.push_back(v) } -> std::same_as<void>;
168+
};
169+
170+
template <typename>
171+
inline constexpr bool AlwaysFalse = false;
172+
173+
class ContainerParser
174+
{
175+
public:
176+
template <typename T>
177+
static T parse(const std::string& str)
178+
{
179+
if constexpr (MapLike<T>) {
180+
return parseMap<T>(str);
181+
} else if constexpr (SequenceContainer<T>) {
182+
return parseSet<T>(str);
183+
} else if constexpr (Container<T>) {
184+
return parseSequence<T>(str);
185+
} else {
186+
return parseScalar<T>(str);
187+
}
188+
}
189+
190+
static std::string trim(const std::string& str)
191+
{
192+
auto start = str.find_first_not_of(" \t\n\r\f\v");
193+
if (start == std::string::npos) {
194+
return "";
195+
}
196+
auto end = str.find_last_not_of(" \t\n\r\f\v");
197+
return str.substr(start, end - start + 1);
198+
}
199+
200+
private:
201+
// Parse vector, list, deque, array
202+
template <SequenceContainer SequenceT>
203+
static SequenceT parseSequence(const std::string& str)
204+
{
205+
SequenceT result;
206+
using ValueType = typename SequenceT::value_type;
207+
std::string cleaned = str;
208+
if (!cleaned.empty() && cleaned.front() == '[' && cleaned.back() == ']') { // removed brackets [1,2,3] -> 1,2,3
209+
cleaned = cleaned.substr(1, cleaned.length() - 2);
210+
}
211+
if (cleaned.empty() || cleaned == "{}") { // nothing to do
212+
return result;
213+
}
214+
if constexpr (Container<ValueType>) {
215+
static_assert(AlwaysFalse<ValueType>, "Nested containers are not supported as configurable parameters");
216+
}
217+
auto tokens = split(cleaned, ',');
218+
for (const auto& token : tokens) {
219+
std::string trimmed = trim(token);
220+
result.insert(result.end(), parseScalar<ValueType>(trimmed));
221+
}
222+
return result;
223+
}
224+
225+
// Parse map, unordered_map, multimap
226+
template <MapLike MapT>
227+
static MapT parseMap(const std::string& str)
228+
{
229+
MapT result;
230+
using KeyType = typename MapT::key_type;
231+
using ValueType = typename MapT::mapped_type;
232+
std::string cleaned = str;
233+
if (!cleaned.empty() && cleaned.front() == '{' && cleaned.back() == '}') { // stip braces {a:1,b:2} -> a:1,b:2
234+
cleaned = cleaned.substr(1, cleaned.length() - 2);
235+
}
236+
if (cleaned.empty()) { // nothing to do
237+
return result;
238+
}
239+
if constexpr (Container<KeyType> || Container<ValueType>) {
240+
static_assert(AlwaysFalse<MapT>, "Nested containers are not supported as configurable parameters");
241+
}
242+
auto pairs = split(cleaned, ',');
243+
for (const auto& pair_str : pairs) {
244+
auto kv = split(pair_str, ':');
245+
if (kv.size() != 2) {
246+
throw std::runtime_error("Invalid map syntax: " + pair_str + ". Expected 'key:value' format, got ");
247+
}
248+
KeyType key = parseScalar<KeyType>(trim(kv[0]));
249+
result[key] = parseScalar<ValueType>(trim(kv[1]));
250+
}
251+
return result;
252+
}
253+
254+
// Parse set containers
255+
template <SequenceContainer SetT>
256+
static SetT parseSet(const std::string& str)
257+
{
258+
SetT result;
259+
using ValueType = typename SetT::value_type;
260+
auto vec = parseSequence<std::vector<ValueType>>(str);
261+
for (const auto& val : vec) {
262+
if constexpr (HasPushBack<SetT>) {
263+
result.push_back(val);
264+
} else {
265+
result.insert(val);
266+
}
267+
}
268+
return result;
269+
}
270+
271+
// Parse scalar types
272+
template <typename T>
273+
static T parseScalar(const std::string& str)
274+
{
275+
if constexpr (std::is_same_v<T, std::string>) {
276+
return str;
277+
} else if constexpr (std::is_same_v<T, bool>) {
278+
std::string lower = str;
279+
std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
280+
if (lower == "true" || lower == "1") {
281+
return true;
282+
}
283+
if (lower == "false" || lower == "0") {
284+
return false;
285+
}
286+
throw std::runtime_error("Invalid boolean value: " + str);
287+
} else if constexpr (std::is_same_v<T, char> || std::is_same_v<T, signed char>) {
288+
size_t pos = 0;
289+
long long value = std::stoll(str, &pos);
290+
if (pos != str.size()) {
291+
throw std::runtime_error("Failed to parse '" + str + "' as char type");
292+
}
293+
if (value < std::numeric_limits<T>::min() || value > std::numeric_limits<T>::max()) {
294+
throw std::runtime_error("Value out of range for char type: " + str);
295+
}
296+
return static_cast<T>(value);
297+
} else if constexpr (std::is_same_v<T, unsigned char>) {
298+
size_t pos = 0;
299+
unsigned long long value = std::stoull(str, &pos);
300+
if (pos != str.size()) {
301+
throw std::runtime_error("Failed to parse '" + str + "' as unsigned char type");
302+
}
303+
if (value > std::numeric_limits<T>::max()) {
304+
throw std::runtime_error("Value out of range for unsigned char type: " + str);
305+
}
306+
return static_cast<T>(value);
307+
} else if constexpr (std::is_integral_v<T> && std::is_unsigned_v<T>) {
308+
if (!str.empty() && str.front() == '-') {
309+
throw std::runtime_error("Value out of range for unsigned integer type: " + str);
310+
}
311+
size_t pos = 0;
312+
unsigned long long value = std::stoull(str, &pos);
313+
if (pos != str.size() || value > std::numeric_limits<T>::max()) {
314+
throw std::runtime_error("Failed to parse '" + str + "' as unsigned integer type");
315+
}
316+
return static_cast<T>(value);
317+
} else if constexpr (std::is_integral_v<T>) {
318+
size_t pos = 0;
319+
long long value = std::stoll(str, &pos);
320+
if (pos != str.size() || value < std::numeric_limits<T>::min() || value > std::numeric_limits<T>::max()) {
321+
throw std::runtime_error("Failed to parse '" + str + "' as signed integer type");
322+
}
323+
return static_cast<T>(value);
324+
} else if constexpr (std::is_floating_point_v<T>) {
325+
size_t pos = 0;
326+
long double value = std::stold(str, &pos);
327+
if (pos != str.size()) {
328+
throw std::runtime_error("Failed to parse '" + str + "' as floating point type");
329+
}
330+
return static_cast<T>(value);
331+
} else {
332+
std::istringstream iss(str);
333+
T value;
334+
iss >> value;
335+
iss >> std::ws;
336+
if (iss.fail() || !iss.eof()) {
337+
throw std::runtime_error("Failed to parse '" + str + "' as " + typeid(T).name());
338+
}
339+
return value;
340+
}
341+
}
342+
343+
// Split respecting nested brackets and braces
344+
static std::vector<std::string> split(const std::string& str, char delimiter)
345+
{
346+
std::vector<std::string> tokens;
347+
std::string current;
348+
int bracket_depth = 0;
349+
int brace_depth = 0;
350+
for (char c : str) {
351+
if (c == '[') {
352+
bracket_depth++;
353+
} else if (c == ']') {
354+
bracket_depth--;
355+
} else if (c == '{') {
356+
brace_depth++;
357+
} else if (c == '}') {
358+
brace_depth--;
359+
} else if (c == delimiter && bracket_depth == 0 && brace_depth == 0) {
360+
if (!current.empty()) {
361+
tokens.push_back(current);
362+
current.clear();
363+
}
364+
continue;
365+
}
366+
current += c;
367+
}
368+
if (!current.empty()) {
369+
tokens.push_back(current);
370+
}
371+
return tokens;
372+
}
373+
};
374+
139375
class ConfigurableParam
140376
{
141377
public:
@@ -247,14 +483,21 @@ class ConfigurableParam
247483
static void setValue(std::string const& key, std::string const& valuestring);
248484
static void setEnumValue(const std::string&, const std::string&);
249485
static void setArrayValue(const std::string&, const std::string&);
486+
static void setContainerValue(const std::string&, const std::string&);
487+
static bool isRegisteredContainerType(const std::string& typeName);
488+
static void registerContainerType(const std::string& key, const std::string& typeName);
489+
static std::string getRegisteredContainerType(const std::string& key);
490+
static bool assignRegisteredContainer(const std::string& typeName, void* target, const void* source);
491+
static bool areRegisteredContainersEqual(const std::string& typeName, const void* lhs, const void* rhs);
492+
static std::string registeredContainerAsString(const std::string& typeName, const void* source);
250493

251494
// update the storagemap from a vector of key/value pairs, calling setValue for each pair
252495
static void setValues(std::vector<std::pair<std::string, std::string>> const& keyValues);
253496

254497
// initializes the parameter database
255498
static void initialize();
256499

257-
// create CCDB snapsnot
500+
// create CCDB snapshot
258501
static void toCCDB(std::string filename);
259502
// load from (CCDB) snapshot
260503
static void fromCCDB(std::string filename);

Common/Utils/include/CommonUtils/ConfigurableParamHelper.h

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,21 @@
99
// granted to it by virtue of its status as an Intergovernmental Organization
1010
// or submit itself to any jurisdiction.
1111

12-
//first version 8/2018, Sandro Wenzel
12+
// first version 8/2018, Sandro Wenzel
1313

1414
#ifndef COMMON_SIMCONFIG_INCLUDE_SIMCONFIG_CONFIGURABLEPARAMHELPER_H_
1515
#define COMMON_SIMCONFIG_INCLUDE_SIMCONFIG_CONFIGURABLEPARAMHELPER_H_
1616

1717
#include "CommonUtils/ConfigurableParam.h"
18-
#include "TClass.h"
18+
1919
#include <memory>
20+
#include <TClass.h>
21+
#include <TFile.h>
22+
#include <TDataMember.h>
2023
#include <type_traits>
2124
#include <typeinfo>
22-
#include "TFile.h"
2325

24-
namespace o2
25-
{
26-
namespace conf
26+
namespace o2::conf
2727
{
2828

2929
// ----------------------------------------------------------------
@@ -342,7 +342,19 @@ class ConfigurableParamPromoter : public Base, virtual public ConfigurableParam
342342
}
343343
};
344344

345-
} // namespace conf
346-
} // namespace o2
345+
inline bool isContainer(const std::string& typeName)
346+
{
347+
return ConfigurableParam::isRegisteredContainerType(typeName);
348+
}
349+
350+
inline bool isContainer(TDataMember const& dm)
351+
{
352+
if (auto* cl = dm.GetClass(); cl && isContainer(cl->GetName())) {
353+
return true;
354+
}
355+
return isContainer(dm.GetTrueTypeName()) || isContainer(dm.GetFullTypeName());
356+
}
357+
358+
} // namespace o2::conf
347359

348360
#endif /* COMMON_SIMCONFIG_INCLUDE_SIMCONFIG_CONFIGURABLEPARAMHELPER_H_ */

Common/Utils/include/CommonUtils/ConfigurableParamTest.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
#include "CommonUtils/ConfigurableParam.h"
1616
#include "CommonUtils/ConfigurableParamHelper.h"
1717

18+
#include <array>
19+
#include <cstdint>
20+
#include <map>
21+
#include <set>
22+
#include <vector>
23+
1824
namespace o2::conf::test
1925
{
2026
struct TestParam : public o2::conf::ConfigurableParamHelper<TestParam> {
@@ -37,6 +43,11 @@ struct TestParam : public o2::conf::ConfigurableParamHelper<TestParam> {
3743
int iValueProvenanceTest{0};
3844
TestEnum eValue = TestEnum::C;
3945
int caValue[3] = {0, 1, 2};
46+
std::vector<int> vec;
47+
std::vector<uint8_t> u8vec;
48+
std::map<int, uint32_t> map;
49+
std::map<std::string, uint32_t> smap;
50+
std::set<uint16_t> set;
4051

4152
O2ParamDef(TestParam, "TestParam");
4253
};

0 commit comments

Comments
 (0)