#ifndef SMO_INTRIN_THRESHOLD_PARAMS_H #define SMO_INTRIN_THRESHOLD_PARAMS_H #include #include #include #include #include #include #include #include #include namespace smo::intrin { enum class ThresholdUnit { Percentage, Absolute, }; struct ParsedThresholdParam { int value; ThresholdUnit unit; std::string_view matchedName; bool wasSpecified; }; /* Canonical unprefixed threshold-param names that live inside postrin(...) / * negtrin(...) segments attached to a nontrin DAP spec. The "-pc" and * "-percentage" variants are Percentage-unit; the "-thr", "-thresh" and * "-threshold" variants are Absolute-unit. */ inline constexpr std::array kIntrinInterestPcNames = { "interest-percentage", "interest-pc", }; inline constexpr std::array kIntrinInterestThrNames = { "interest-threshold", "interest-thresh", "interest-thr", }; inline constexpr std::array kIntrinDistractionPcNames = { "distraction-percentage", "distraction-pc", }; inline constexpr std::array kIntrinDistractionThrNames = { "distraction-threshold", "distraction-thresh", "distraction-thr", }; inline constexpr std::array kIntrinStupefactionPcNames = { "stupefaction-percentage", "stupefaction-pc", "stupefying-percentage", "stupefying-pc", }; inline constexpr std::array kIntrinStupefactionThrNames = { "stupefaction-threshold", "stupefaction-thresh", "stupefaction-thr", "stupefying-threshold", "stupefying-thresh", "stupefying-thr", }; inline constexpr std::array kIntrinIntolerablePcNames = { "intolerable-percentage", "intolerable-pc", }; inline constexpr std::array kIntrinIntolerableThrNames = { "intolerable-threshold", "intolerable-thresh", "intolerable-thr", }; /* Unitless stems are invalid — authors must name the unit via -pc/-percentage * or -thr/-thresh/-threshold. We reject bare "interest", "distraction", etc. */ inline constexpr std::array kForbiddenUnitlessIntrinStems = { "interest", "distraction", "stupefaction", "stupefying", "intolerable", }; template inline bool arrayContains( const std::array& names, std::string_view candidate) { for (const auto& name : names) { if (name == candidate) { return true; } } return false; } template inline bool namesContain( const NameCollectionT& names, std::string_view candidate) { for (const auto& name : names) { if (name == candidate) { return true; } } return false; } inline bool isKnownIntrinThresholdParamName(std::string_view name) { return namesContain(kIntrinInterestPcNames, name) || namesContain(kIntrinInterestThrNames, name) || namesContain(kIntrinDistractionPcNames, name) || namesContain(kIntrinDistractionThrNames, name) || namesContain(kIntrinStupefactionPcNames, name) || namesContain(kIntrinStupefactionThrNames, name) || namesContain(kIntrinIntolerablePcNames, name) || namesContain(kIntrinIntolerableThrNames, name); } /* Intrin threshold params (interest-*, distraction-*, stupefaction-*, * intolerable-*) must appear only inside postrin(...) / negtrin(...) segments * on a DAP spec, never on qualeIfaceApi params. The deprecated `from-stimbuff` * marker is also rejected — postrin/negtrin are now directly attached to the * nontrin stimbuff they trigger from. */ inline void validateNoIntrinParamsOnQualeIface( const std::string& qualeIfaceApi, const std::vector>& params) { for (const auto& [name, value] : params) { (void)value; if (isKnownIntrinThresholdParamName(name)) { throw std::runtime_error( "Intrinsic threshold param '" + name + "' is not valid on " "qualeIfaceApi '" + qualeIfaceApi + "'. Declare it inside a " "postrin(...) or negtrin(...) segment attached to this DAP " "spec."); } if (arrayContains(kForbiddenUnitlessIntrinStems, name)) { throw std::runtime_error( "Intrinsic threshold param '" + name + "' on qualeIfaceApi '" + qualeIfaceApi + "' is invalid without a unit suffix and " "does not belong on qualeIfaceApi params anyway. Use a " "postrin(...) or negtrin(...) segment with a '-pc' or " "'-thr' suffix."); } if (name == "from-stimbuff") { throw std::runtime_error( "'from-stimbuff' is no longer supported. postrin(...) and " "negtrin(...) are now attached directly to the nontrin DAP " "spec they trigger from; remove 'from-stimbuff' from " "qualeIfaceApi '" + qualeIfaceApi + "'."); } } } /* Accepts only unprefixed threshold names and the two passband modifiers that * are defined on the sensory side. Rejects unit-less stems so that mis-typed * params fail loudly. */ inline void validateIntrinSegmentParams( std::string_view intrinKind, // "postrin" or "negtrin" const std::vector>& params) { for (const auto& [name, value] : params) { (void)value; if (arrayContains(kForbiddenUnitlessIntrinStems, name)) { throw std::runtime_error( std::string(intrinKind) + "(...) param '" + name + "' is " "invalid without a unit suffix. Use '-pc'/'-percentage' or " "'-thr'/'-thresh'/'-threshold'."); } } } inline std::optional> findLastMatchingIntParam( const std::vector>& params, const auto& synonymNames) { for (auto paramIt = params.rbegin(); paramIt != params.rend(); ++paramIt) { const auto& [name, value] = *paramIt; if (!namesContain(synonymNames, name)) { continue; } try { return std::pair(name, std::stoi(value)); } catch (const std::exception& e) { throw std::runtime_error( "Failed to parse '" + name + "' param value '" + value + "' as integer: " + e.what()); } } return std::nullopt; } inline ParsedThresholdParam parseOptionalThresholdParam( const std::vector>& params, const auto& percentageNames, const auto& absoluteNames, int defaultValue, ThresholdUnit defaultUnit) { for (auto paramIt = params.rbegin(); paramIt != params.rend(); ++paramIt) { const auto& [name, value] = *paramIt; ThresholdUnit unit; if (namesContain(percentageNames, name)) { unit = ThresholdUnit::Percentage; } else if (namesContain(absoluteNames, name)) { unit = ThresholdUnit::Absolute; } else { continue; } try { return ParsedThresholdParam{ .value = std::stoi(value), .unit = unit, .matchedName = name, .wasSpecified = true, }; } catch (const std::exception& e) { throw std::runtime_error( "Failed to parse '" + name + "' param value '" + value + "' as integer: " + e.what()); } } return ParsedThresholdParam{ .value = defaultValue, .unit = defaultUnit, .matchedName = {}, .wasSpecified = false, }; } inline uint32_t resolveThresholdValue( const ParsedThresholdParam& param, size_t percentageBase) { if (param.unit == ThresholdUnit::Percentage) { return static_cast((percentageBase * param.value) / 100); } return static_cast(param.value); } } // namespace smo::intrin #endif // SMO_INTRIN_THRESHOLD_PARAMS_H