#ifndef SENSORDEVICESPEC_H #define SENSORDEVICESPEC_H #include #include #include #include #include #include #include #include #include #include #include #include namespace smo { namespace device { /* Carrier used by the DAP spec parser to pass one parenthesized segment * (e.g. postrin(interest-pc=85) or pcloudLightAmbience(...)) up the reduction * 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> 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 && postrin == other.postrin && negtrin == other.negtrin && qualeIfaceApi == other.qualeIfaceApi && stimBuffApi == other.stimBuffApi && provider == other.provider && deviceSelector == other.deviceSelector; } public: std::string deviceIdentifier; char sensorType; /* 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> postrinParams; std::string negtrin; std::vector> negtrinParams; std::string qualeIfaceApi; std::vector> qualeIfaceApiParams; std::string stimBuffApi; std::vector> stimBuffApiParams; std::string provider; std::vector> providerParams; std::string deviceSelector; static void stringifyParams( std::ostream& os, const std::vector>& 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 << ", 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: ("; 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 << " "; } 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>& params, const std::string& paramName ) { return parseRequiredParamAsIntWithSynonyms( params, std::array{{paramName}}, "No " + paramName + " specified in params"); } /** * @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>& params, const std::string& paramName, int defaultValue ) { return parseOptionalParamAsIntWithSynonyms( params, std::array{{paramName}}, defaultValue); } /** * @brief Test whether a parameter name appears in a synonym collection */ template 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>& 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 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(token.back()))) { token.pop_back(); } while (!token.empty() && std::isspace(static_cast(token.front()))) { token.erase(token.begin()); } for (char& character : token) { character = static_cast( std::tolower(static_cast(character))); } return token; } /** * @brief Find the lattermost param matching any synonym (name + value pair) */ template static std::optional> findOptionalParamWithSynonyms( const std::vector>& 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> findOptionalParam( const std::vector>& params, std::string_view paramName) { return findOptionalParamWithSynonyms( params, std::array{{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 static int parseRequiredParamAsIntWithSynonyms( const std::vector>& params, const SynonymCollectionT& synonymNames, const std::string& missingParamMessage) { const std::optional> matchedParam = findOptionalParamWithSynonyms(params, synonymNames); if (!matchedParam) { throw std::runtime_error(missingParamMessage); } return parseParamValueAsInt(matchedParam->first, matchedParam->second); } /** * @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 */ template static int parseOptionalParamAsIntWithSynonyms( const std::vector>& params, const SynonymCollectionT& synonymNames, int defaultValue ) { const std::optional> 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 static std::string parseRequiredParamValueWithSynonyms( const std::vector>& params, const SynonymCollectionT& synonymNames, const std::string& missingOrEmptyValueMessage) { const std::optional> matchedParam = findOptionalParamWithSynonyms(params, synonymNames); if (!matchedParam || matchedParam->second.empty()) { 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 static std::optional parseOptionalParamValueWithSynonyms( const std::vector>& params, const SynonymCollectionT& synonymNames, const std::string& emptyValueMessage) { const std::optional> 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 static bool parseOptionalParamAsBoolWithSynonyms( const std::vector>& params, const SynonymCollectionT& synonymNames, bool defaultValue = false, bool emptyMeansTrue = true) { const std::optional> 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 static void rejectUnknownParams( const std::vector>& 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 + "'"); } } } }; class InteroceptorDevAttachmentSpec : public DeviceAttachmentSpec { }; class ExtrospectorDevAttachmentSpec : public DeviceAttachmentSpec { }; } // namespace device } // namespace smo #endif // SENSORDEVICESPEC_H