//! @file AnyMap.cpp // This file is part of Cantera. See License.txt in the top-level directory or // at http://www.cantera.org/license.txt for license and copyright information. #include "cantera/base/AnyMap.h" #include "cantera/base/yaml.h" #include "cantera/base/stringUtils.h" #ifdef CT_USE_DEMANGLE #include #endif #include namespace ba = boost::algorithm; namespace { // helper functions bool isFloat(const std::string& val) { // This function duplicates the logic of fpValueCheck, but doesn't throw // exceptions if the string isn't a float std::string str = ba::trim_copy(val); if (str.empty()) { return false; } int numDot = 0; int numExp = 0; int istart = 0; char ch = str[0]; if (ch == '+' || ch == '-') { istart = 1; if (str.size() == 1) { return false; } } for (size_t i = istart; i < str.size(); i++) { ch = str[i]; if (isdigit(ch)) { } else if (ch == '.') { numDot++; if (numDot > 1) { return false; } if (numExp > 0) { return false; } } else if (ch == 'e' || ch == 'E') { numExp++; if (numExp > 1 || i == str.size() - 1) { return false; } ch = str[i+1]; if (ch == '+' || ch == '-') { if (i + 1 == str.size() - 1) { return false; } i++; } } else { return false; } } return true; } bool isInt(const std::string& val) { std::string str = ba::trim_copy(val); if (str.empty()) { return false; } int istart = 0; char ch = str[0]; if (ch == '+' || ch == '-') { istart = 1; if (str.size() == 1) { return false; } } for (size_t i = istart; i < str.size(); i++) { if (!isdigit(str[i])) { return false; } } return true; } bool isBool(const std::string& val) { std::string str = ba::trim_copy(val); return (val == "true" || val == "True" || val == "false" || val == "False"); } enum class Type : char { Unknown = 0, Integer = 1, Double = 2, String = 4, Bool = 8, Map = 16, Sequence = 32 }; Type operator|(Type lhs, Type rhs) { return Type(static_cast(lhs) | static_cast(rhs)); } Type elementTypes(const YAML::Node& node) { // See what kinds of elements we have: Type types = Type::Unknown; for (const auto& el : node) { if (el.IsMap()) { types = types | Type::Map; } else if (el.IsSequence()) { types = types | Type::Sequence; } else if (el.IsScalar()) { std::string nodestr = el.as(); if (isInt(nodestr)) { types = types | Type::Integer; } else if (isFloat(nodestr)) { types = types | Type::Double; } else if (isBool(nodestr)) { types = types | Type::Bool; } else { types = types | Type::String; } } } return types; } } // end anonymous namespace namespace YAML { // YAML converters using namespace Cantera; template<> struct convert { static Node encode(const Cantera::AnyMap& rhs) { throw NotImplementedError("AnyMap::encode"); } static bool decode(const Node& node, Cantera::AnyMap& target) { if (!node.IsMap()) { std::string text = YAML::Dump(node); if (text.size() > 300) { text.resize(300); } throw CanteraError("YAML::convert", "YAML node is not a map. Node begins with:\n'''\n{}\n'''", text); } for (const auto& child : node) { std::string key = child.first.as(); if (child.second.IsMap()) { target[key] = child.second.as(); } else { target[key] = child.second.as(); target[key].setKey(key); } } return true; } }; template<> struct convert { static Node encode(const Cantera::AnyValue& rhs) { throw NotImplementedError("AnyValue::encode"); } static bool decode(const Node& node, Cantera::AnyValue& target) { if (node.IsScalar()) { // Scalar nodes are int, doubles, or strings std::string nodestr = node.as(); if (isInt(nodestr)) { target = intValue(nodestr); } else if (isFloat(nodestr)) { target = fpValue(nodestr); } else if (isBool(nodestr)) { target = node.as(); } else { target = nodestr; } return true; } else if (node.IsSequence()) { // Convert sequences of the same element type to vectors of that type Type types = elementTypes(node); if (types == Type::Integer) { target = node.as>(); } else if (types == (Type::Integer | Type::Double) || types == Type::Double) { target = node.as(); } else if (types == Type::String) { target = node.as>(); } else if (types == Type::Bool) { target = node.as>(); } else if (types == Type::Map) { target = node.as>(); } else if (types == Type::Sequence) { // Create nested vectors when data types are compatible Type subtypes = Type::Unknown; for (const auto& el : node) { subtypes = subtypes | elementTypes(el); } if (subtypes == Type::Integer) { target = node.as>>(); } else if (subtypes == (Type::Integer | Type::Double) || subtypes == Type::Double) { target = node.as>>(); } else if (types == Type::String) { target = node.as>>(); } else if (types == Type::Bool) { target = node.as>>(); } else { target = node.as>(); } } else { // If types are different, create a vector of generic values target = node.as>(); } return true; } return false; } }; } namespace Cantera { std::map AnyValue::s_typenames = { {typeid(double).name(), "double"}, {typeid(long int).name(), "long int"}, {typeid(std::string).name(), "string"}, {typeid(std::vector).name(), "vector"}, {typeid(AnyMap).name(), "AnyMap"}, }; // Methods of class AnyValue AnyValue::AnyValue() : m_key() , m_value(new boost::any{}) {} AnyValue::~AnyValue() = default; AnyValue::AnyValue(AnyValue const& other): m_key(other.m_key), m_value(new boost::any{*other.m_value}) { } AnyValue::AnyValue(AnyValue&& other): m_key(std::move(other.m_key)), m_value(std::move(other.m_value)) { } AnyValue& AnyValue::operator=(AnyValue const& other) { if (this == &other) return *this; m_key = other.m_key; m_value.reset(new boost::any{*other.m_value}); return *this; } AnyValue& AnyValue::operator=(AnyValue&& other) { if (this == &other) return *this; m_key = std::move(other.m_key); m_value = std::move(other.m_value); return *this; } AnyValue& AnyValue::operator[](const std::string& key) { return as()[key]; } bool AnyValue::hasKey(const std::string& key) const { return (is() && as().hasKey(key)); } void AnyValue::setKey(const std::string &key) { m_key = key; } const std::type_info &AnyValue::type() { return m_value->type(); } AnyValue::AnyValue(const std::string& value) : m_value(new boost::any{value}) {} AnyValue &AnyValue::operator=(const std::string &value) { *m_value = value; return *this; } AnyValue &AnyValue::operator=(const char *value) { *m_value = std::string(value); return *this; } const std::string &AnyValue::asString() const { return as(); } AnyValue::AnyValue(double value) : m_value(new boost::any{value}) {} AnyValue &AnyValue::operator=(double value) { *m_value = value; return *this; } double& AnyValue::asDouble() { return as(); } const double& AnyValue::asDouble() const { return as(); } AnyValue::AnyValue(bool value) : m_value(new boost::any{value}) {} AnyValue &AnyValue::operator=(bool value) { *m_value = value; return *this; } bool& AnyValue::asBool() { return as(); } const bool& AnyValue::asBool() const { return as(); } AnyValue::AnyValue(long int value) : m_value(new boost::any{value}) {} AnyValue &AnyValue::operator=(long int value) { *m_value = value; return *this; } AnyValue &AnyValue::operator=(int value) { *m_value = static_cast(value); return *this; } long int& AnyValue::asInt() { return as(); } const long int& AnyValue::asInt() const { return as(); } AnyValue& AnyValue::operator=(const AnyMap& value) { *m_value = value; return *this; } AnyValue& AnyValue::operator=(AnyMap&& value) { *m_value = std::move(value); return *this; } std::string AnyValue::demangle(const std::type_info& type) const { if (s_typenames.find(type.name()) != s_typenames.end()) { return s_typenames[type.name()]; } else { #ifdef CT_USE_DEMANGLE return boost::core::demangle(type.name()); #else return type.name(); #endif } } // Explicit template specializations to allow certain conversions template<> const std::vector& AnyValue::asVector(size_t nMin, size_t nMax) const { if (!is>()) { std::vector v; if (is>()) { for (const auto& el : asVector()) { v.push_back(AnyValue(el)); } *m_value = v; } else if (is>()) { for (const auto& el : asVector()) { v.push_back(AnyValue(el)); } *m_value = v; } else if (is>()) { for (const auto& el : asVector()) { v.push_back(AnyValue(el)); } *m_value = v; } // If none of these special cases match, the value won't be replaced, // and an exception will be thrown. } const auto& vv = as>(); checkSize(vv, nMin, nMax); return vv; } template<> std::vector& AnyValue::asVector(size_t nMin, size_t nMax) { auto& v = const_cast&>( const_cast(this)->asVector()); checkSize(v, nMin, nMax); return v; } template<> const std::vector& AnyValue::asVector(size_t nMin, size_t nMax) const { if (is>()) { std::vector v; for (const auto& el : asVector()) { v.push_back(el); } *m_value = v; } const auto& vv = as>(); checkSize(vv, nMin, nMax); return vv; } template<> std::vector& AnyValue::asVector(size_t nMin, size_t nMax) { if (is>()) { std::vector v; for (const auto& el : asVector()) { v.push_back(el); } *m_value = v; } auto& vv = as>(); checkSize(vv, nMin, nMax); return vv; } template<> const std::vector& AnyValue::asVector(size_t nMin, size_t nMax) const { if (is>>()) { std::vector v; for (const auto& outer : asVector>()) { v.push_back(vector_fp()); for (const auto& inner : outer) { v.back().push_back(inner); } } *m_value = v; } const auto& vv = as>(); checkSize(vv, nMin, nMax); return vv; } template<> std::vector& AnyValue::asVector(size_t nMin, size_t nMax) { if (is>>()) { std::vector v; for (const auto& outer : asVector>()) { v.push_back(vector_fp()); for (const auto& inner : outer) { v.back().push_back(inner); } } *m_value = v; } auto& vv = as>(); checkSize(vv, nMin, nMax); return vv; } // Methods of class AnyMap AnyValue& AnyMap::operator[](const std::string& key) { const auto& slash = boost::ifind_first(key, "/"); if (!slash) { // Simple key const auto& iter = m_data.find(key); if (iter == m_data.end()) { // Create a new key return it // NOTE: 'insert' can be replaced with 'emplace' after support for // G++ 4.7 is dropped. AnyValue& value = m_data.insert({key, AnyValue()}).first->second; value.setKey(key); return value; } else { // Return an already-existing item return iter->second; } } else { // Split the first slash-delimited part of key and recurse std::string head(key.begin(), slash.begin()); std::string tail(slash.end(), key.end()); const auto& iter = m_data.find(head); if (iter == m_data.end()) { // Create a new key AnyValue& value = m_data.insert({head, AnyValue()}).first->second; value = AnyMap(); value.setKey(head); return value.as()[tail]; } else { // Return an already existing key return iter->second.as()[tail]; } } } const AnyValue& AnyMap::at(const std::string& key) const { const auto& slash = boost::ifind_first(key, "/"); try { if (!slash) { return m_data.at(key); } else { std::string head(key.begin(), slash.begin()); std::string tail(slash.end(), key.end()); return m_data.at(head).as().at(tail); } } catch (std::out_of_range& err) { throw CanteraError("AnyMap::at", "Key '{}' not found", key); } } bool AnyMap::hasKey(const std::string& key) const { const auto& slash = boost::ifind_first(key, "/"); if (!slash) { return (m_data.find(key) != m_data.end()); } else { std::string head(key.begin(), slash.begin()); std::string tail(slash.end(), key.end()); if (m_data.find(head) == m_data.end() || !m_data.at(head).is()) { return false; } else { return m_data.at(head).as().hasKey(tail); } } } template const T& AnyMap::get(const std::string& key, const T& default_, std::function getter) const { const auto& slash = boost::ifind_first(key, "/"); if (!slash) { if (m_data.find(key) != m_data.end()) { return getter(&m_data.at(key)); } else { return default_; } } else { std::string head(key.begin(), slash.begin()); std::string tail(slash.end(), key.end()); if (m_data.find(head) == m_data.end() || !m_data.at(head).is()) { return default_; } else { return m_data.at(head).as().get(tail, default_, getter); } } } bool AnyMap::getBool(const std::string& key, bool default_) const { return get(key, default_, std::mem_fun(&AnyValue::asBool)); } double AnyMap::getDouble(const std::string& key, double default_) const { return get(key, default_, std::mem_fun(&AnyValue::asDouble)); } long int AnyMap::getInt(const std::string& key, long int default_) const { return get(key, default_, std::mem_fun(&AnyValue::asInt)); } const std::string& AnyMap::getString(const std::string& key, const std::string& default_) const { return get(key, default_, &AnyValue::asString); } AnyMap AnyMap::fromYamlString(const std::string& yaml) { YAML::Node node = YAML::Load(yaml); return node.as(); } AnyMap AnyMap::fromYamlFile(const std::string& name) { YAML::Node node = YAML::LoadFile(findInputFile(name)); return node.as(); } AnyMap::const_iterator begin(const AnyValue& v) { return v.as().begin(); } AnyMap::const_iterator end(const AnyValue& v) { return v.as().end(); } }