Files
salmanoff/include/user/deviceAttachmentSpec.h
T

492 lines
15 KiB
C++
Raw Normal View History

#ifndef SENSORDEVICESPEC_H
#define SENSORDEVICESPEC_H
#include <array>
#include <cctype>
#include <optional>
#include <string_view>
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <stdexcept>
#include <utility>
2025-07-22 06:48:04 -04:00
namespace smo {
namespace device {
2026-04-18 12:02:27 -04:00
/* Carrier used by the DAP spec parser to pass one parenthesized segment
* (e.g. postrin(interest-pc=85) or pcloudLightAmbience(...)) up the reduction
2026-04-18 12:02:27 -04:00
* stack; the spec_body reduction classifies segments and populates the
* DeviceAttachmentSpec. Defined here so both the parser header and consumers
* of the generated header see the type.
*/
struct DapSegment
{
std::string name;
std::vector<std::pair<std::string, std::string>> params;
};
class DeviceAttachmentSpec
{
public:
friend std::ostream& operator<<(
std::ostream& os, const DeviceAttachmentSpec& spec)
{
os << spec.stringify();
return os;
}
bool operator==(const DeviceAttachmentSpec& other) const
{
return deviceIdentifier == other.deviceIdentifier &&
sensorType == other.sensorType &&
2026-04-18 12:02:27 -04:00
postrin == other.postrin &&
negtrin == other.negtrin &&
qualeIfaceApi == other.qualeIfaceApi &&
stimBuffApi == other.stimBuffApi &&
provider == other.provider &&
deviceSelector == other.deviceSelector;
}
public:
std::string deviceIdentifier;
char sensorType;
2026-04-18 12:02:27 -04:00
/* postrin/negtrin hold the literal segment name ("postrin" /
* "negtrin") when present, empty string when the DAP spec omits the
* corresponding intrin specifier. Params vectors carry the params from
* within the postrin(...)/negtrin(...) segment.
*/
std::string postrin;
std::vector<std::pair<std::string,std::string>> postrinParams;
std::string negtrin;
std::vector<std::pair<std::string,std::string>> negtrinParams;
std::string qualeIfaceApi;
2025-11-01 00:57:04 -04:00
std::vector<std::pair<std::string,std::string>> qualeIfaceApiParams;
std::string stimBuffApi;
std::vector<std::pair<std::string,std::string>> stimBuffApiParams;
std::string provider;
std::vector<std::pair<std::string,std::string>> providerParams;
std::string deviceSelector;
2026-04-18 12:02:27 -04:00
static void stringifyParams(
std::ostream& os,
const std::vector<std::pair<std::string,std::string>>& params)
{
for (const auto& param : params)
{
os << param.first;
if (!param.second.empty()) {
os << "=" << param.second;
}
os << " ";
}
}
std::string stringify() const
{
std::ostringstream os;
os << "Device Identifier: " << deviceIdentifier
2026-04-18 12:02:27 -04:00
<< ", Sensor Type: " << sensorType;
if (!postrin.empty())
{
os << ", Postrin Params: (";
stringifyParams(os, postrinParams);
os << ")";
}
if (!negtrin.empty())
{
os << ", Negtrin Params: (";
stringifyParams(os, negtrinParams);
os << ")";
}
os << ", QualeIface API: " << qualeIfaceApi << ", QualeIface API Params: (";
2025-11-01 00:57:04 -04:00
for (const auto& param : qualeIfaceApiParams)
{
os << param.first;
if (!param.second.empty()) {
os << "=" << param.second;
}
os << " ";
}
os << "), StimBuff API: " << stimBuffApi
<< ", StimBuff API Params: (";
for (const auto& param : stimBuffApiParams)
{
os << param.first;
if (!param.second.empty()) {
os << "=" << param.second;
}
os << " ";
}
os << "), Provider: " << provider << ", Provider Params: (";
for (const auto& param : providerParams)
{
os << param.first;
if (!param.second.empty()) {
os << "=" << param.second;
}
os << " ";
}
2026-06-11 11:17:06 -04:00
os << "), Device Selector: " << deviceSelector;
return os.str();
}
/**
* @brief Parse a required integer parameter from a parameter list
* @param params The parameter vector to search in
* @param paramName The name of the parameter to parse
* @return The parsed integer value
* @throws std::runtime_error if parameter is not found or cannot be parsed
* @note The lattermost supplied matching param wins if multiple are present
*/
static int parseRequiredParamAsInt(
const std::vector<std::pair<std::string,std::string>>& params,
const std::string& paramName
)
{
return parseRequiredParamAsIntWithSynonyms(
params,
std::array<std::string_view, 1>{{paramName}},
"No " + paramName + " specified in params");
}
2025-11-16 04:46:42 -04:00
/**
* @brief Parse an optional integer parameter from a parameter list
* @param params The parameter vector to search in
* @param paramName The name of the parameter to parse
* @param defaultValue The default value to return if no parameter is found
* @return The parsed integer value, or defaultValue if none found
* @note The lattermost supplied matching param wins if multiple are present
*/
static int parseOptionalParamAsInt(
const std::vector<std::pair<std::string,std::string>>& params,
const std::string& paramName,
int defaultValue
)
{
return parseOptionalParamAsIntWithSynonyms(
params, std::array<std::string_view, 1>{{paramName}}, defaultValue);
}
/**
* @brief Test whether a parameter name appears in a synonym collection
*/
template <typename SynonymCollectionT>
static bool namesContain(
const SynonymCollectionT& synonymNames,
std::string_view paramName)
{
return std::find(
std::begin(synonymNames),
std::end(synonymNames),
paramName) != std::end(synonymNames);
}
/**
* @brief Test whether a parameter list contains an exact parameter name
*/
static bool paramsContain(
const std::vector<std::pair<std::string,std::string>>& params,
std::string_view paramName)
{
for (const auto& param : params)
{
if (param.first == paramName) {
return true;
}
}
return false;
}
/**
* @brief Test whether a parameter name matches any synonym collection
*/
template <typename... SynonymCollectionTs>
static bool paramNameMatchesAnySynonymGroup(
std::string_view paramName,
const SynonymCollectionTs&... synonymGroups)
{
return (namesContain(synonymGroups, paramName) || ...);
}
/**
* @brief Trim surrounding whitespace and lowercase a DAP param token
*/
static std::string normalizeParamToken(std::string token)
{
while (!token.empty()
&& std::isspace(static_cast<unsigned char>(token.back())))
{
token.pop_back();
}
while (!token.empty()
&& std::isspace(static_cast<unsigned char>(token.front())))
{
token.erase(token.begin());
}
for (char& character : token)
{
character = static_cast<char>(
std::tolower(static_cast<unsigned char>(character)));
}
return token;
}
/**
* @brief Find the lattermost param matching any synonym (name + value pair)
*/
template <typename SynonymCollectionT>
static std::optional<std::pair<std::string, std::string>>
findOptionalParamWithSynonyms(
const std::vector<std::pair<std::string,std::string>>& params,
const SynonymCollectionT& synonymNames)
{
for (auto paramIt = params.rbegin(); paramIt != params.rend(); ++paramIt)
{
if (namesContain(synonymNames, paramIt->first)) {
return *paramIt;
}
}
return std::nullopt;
}
/**
* @brief Find the lattermost param with an exact name (name + value pair)
*/
static std::optional<std::pair<std::string, std::string>> findOptionalParam(
const std::vector<std::pair<std::string,std::string>>& params,
std::string_view paramName)
{
return findOptionalParamWithSynonyms(
params, std::array<std::string_view, 1>{{paramName}});
}
/**
* @brief Parse a DAP integer param value
* @throws std::runtime_error if parsing fails
*/
static int parseParamValueAsInt(
const std::string& paramName,
const std::string& paramValue)
{
try {
return std::stoi(paramValue);
}
catch (const std::invalid_argument&)
{
throw std::runtime_error(
"Failed to parse '" + paramName + "' param value '"
+ paramValue + "' as integer");
}
catch (const std::out_of_range&)
{
throw std::runtime_error(
"'" + paramName + "' param value '" + paramValue
+ "' is out of range");
}
}
/**
* @brief Parse a DAP integer param value; result must be strictly positive
* @throws std::runtime_error if parsing fails or value is not positive
*/
static int parseParamValueAsPositiveInt(
const std::string& paramName,
const std::string& paramValue)
{
const int parsedValue = parseParamValueAsInt(paramName, paramValue);
if (parsedValue <= 0)
{
throw std::runtime_error(
"'" + paramName + "' must be positive");
}
return parsedValue;
}
/**
* @brief Parse a DAP boolean param value
* @param value Raw param value; empty string means @p emptyMeansTrue
* @throws std::runtime_error if the value is not recognized
*/
static bool parseParamValueAsBool(
const std::string& value,
bool emptyMeansTrue = true)
{
if (value.empty()) {
return emptyMeansTrue;
}
const std::string lowered = normalizeParamToken(value);
if (lowered == "true" || lowered == "1" || lowered == "yes") {
return true;
}
if (lowered == "false" || lowered == "0" || lowered == "no") {
return false;
}
throw std::runtime_error(
"Boolean param value '" + value
+ "' is not recognized (use true/false, 1/0, yes/no, or omit value)");
}
/**
* @brief Parse a required integer param using synonyms
* @note The lattermost supplied matching param wins if multiple are present
*/
template <typename SynonymCollectionT>
static int parseRequiredParamAsIntWithSynonyms(
const std::vector<std::pair<std::string,std::string>>& params,
const SynonymCollectionT& synonymNames,
const std::string& missingParamMessage)
{
const std::optional<std::pair<std::string, std::string>> matchedParam =
findOptionalParamWithSynonyms(params, synonymNames);
if (!matchedParam)
{
throw std::runtime_error(missingParamMessage);
}
return parseParamValueAsInt(matchedParam->first, matchedParam->second);
}
2025-11-16 04:46:42 -04:00
/**
* @brief Parse an optional integer parameter from a parameter list using synonyms
* @param params The parameter vector to search in
* @param synonymNames The collection of synonymous parameter names to try
* @param defaultValue The default value to return if no parameter is found
* @return The parsed integer value, or defaultValue if none found
* @note The lattermost supplied matching param wins if multiple are present
2025-11-16 04:46:42 -04:00
*/
template <typename SynonymCollectionT>
2025-11-16 04:46:42 -04:00
static int parseOptionalParamAsIntWithSynonyms(
const std::vector<std::pair<std::string,std::string>>& params,
const SynonymCollectionT& synonymNames,
2025-11-16 04:46:42 -04:00
int defaultValue
)
{
const std::optional<std::pair<std::string, std::string>> matchedParam =
findOptionalParamWithSynonyms(params, synonymNames);
if (!matchedParam) {
return defaultValue;
}
return parseParamValueAsInt(matchedParam->first, matchedParam->second);
}
/**
* @brief Parse a required non-empty param value using synonyms
* @note The lattermost supplied matching param wins if multiple are present
* @throws std::runtime_error if no matching param is found or value is empty
*/
template <typename SynonymCollectionT>
static std::string parseRequiredParamValueWithSynonyms(
const std::vector<std::pair<std::string,std::string>>& params,
const SynonymCollectionT& synonymNames,
const std::string& missingOrEmptyValueMessage)
{
const std::optional<std::pair<std::string, std::string>> matchedParam =
findOptionalParamWithSynonyms(params, synonymNames);
if (!matchedParam || matchedParam->second.empty())
2025-11-16 04:46:42 -04:00
{
throw std::runtime_error(missingOrEmptyValueMessage);
}
return matchedParam->second;
}
/**
* @brief Parse an optional non-empty param value using synonyms
* @return Parsed value, or std::nullopt when the param is absent
* @throws std::runtime_error if the param is present but its value is empty
*/
template <typename SynonymCollectionT>
static std::optional<std::string> parseOptionalParamValueWithSynonyms(
const std::vector<std::pair<std::string,std::string>>& params,
const SynonymCollectionT& synonymNames,
const std::string& emptyValueMessage)
{
const std::optional<std::pair<std::string, std::string>> matchedParam =
findOptionalParamWithSynonyms(params, synonymNames);
if (!matchedParam) {
return std::nullopt;
}
if (matchedParam->second.empty()) {
throw std::runtime_error(emptyValueMessage);
}
return matchedParam->second;
}
/**
* @brief Parse an optional boolean param using synonyms
* @note The lattermost supplied matching param wins if multiple are present
*/
template <typename SynonymCollectionT>
static bool parseOptionalParamAsBoolWithSynonyms(
const std::vector<std::pair<std::string,std::string>>& params,
const SynonymCollectionT& synonymNames,
bool defaultValue = false,
bool emptyMeansTrue = true)
{
const std::optional<std::pair<std::string, std::string>> matchedParam =
findOptionalParamWithSynonyms(params, synonymNames);
if (!matchedParam) {
return defaultValue;
}
return parseParamValueAsBool(matchedParam->second, emptyMeansTrue);
}
/**
* @brief Reject params whose names are not covered by any synonym group
*/
template <typename... SynonymCollectionTs>
static void rejectUnknownParams(
const std::vector<std::pair<std::string,std::string>>& params,
const std::string& unknownParamMessagePrefix,
const SynonymCollectionTs&... synonymGroups)
{
for (const auto& param : params)
{
if (!paramNameMatchesAnySynonymGroup(
param.first, synonymGroups...))
{
throw std::runtime_error(
unknownParamMessagePrefix + param.first + "'");
2025-11-16 04:46:42 -04:00
}
}
}
};
class InteroceptorDevAttachmentSpec : public DeviceAttachmentSpec
{
};
class ExtrospectorDevAttachmentSpec : public DeviceAttachmentSpec
{
};
} // namespace device
2025-07-22 06:48:04 -04:00
} // namespace smo
#endif // SENSORDEVICESPEC_H