一、cpptoml.h文件内容
/** * @file cpptoml.h * @author Chase Geigle * @date May 2013 */ #ifndef CPPTOML_H #define CPPTOML_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if __cplusplus > 201103L #define CPPTOML_DEPRECATED(reason) [[deprecated(reason)]] #elif defined(__clang__) #define CPPTOML_DEPRECATED(reason) __attribute__((deprecated(reason))) #elif defined(__GNUG__) #define CPPTOML_DEPRECATED(reason) __attribute__((deprecated)) #elif defined(_MSC_VER) #if _MSC_VER < 1910 #define CPPTOML_DEPRECATED(reason) __declspec(deprecated) #else #define CPPTOML_DEPRECATED(reason) [[deprecated(reason)]] #endif #endif namespace cpptoml { class writer; // forward declaration class base; // forward declaration #if defined(CPPTOML_USE_MAP) // a std::map will ensure that entries a sorted, albeit at a slight // performance penalty relative to the (default) unordered_map using string_to_base_map = std::map>; #else // by default an unordered_map is used for best performance as the // toml specification does not require entries to be sorted using string_to_base_map = std::unordered_map>; #endif // if defined, `base` will retain type information in form of an enum class // such that static_cast can be used instead of dynamic_cast // #define CPPTOML_NO_RTTI template class option { public: option() : empty_{true} { // nothing } option(T value) : empty_{false}, value_(std::move(value)) { // nothing } explicit operator bool() const { return !empty_; } const T& operator*() const { return value_; } const T* operator->() const { return &value_; } template T value_or(U&& alternative) const { if (!empty_) return value_; return static_cast(std::forward(alternative)); } private: bool empty_; T value_; }; struct local_date { int year = 0; int month = 0; int day = 0; }; struct local_time { int hour = 0; int minute = 0; int second = 0; int microsecond = 0; }; struct zone_offset { int hour_offset = 0; int minute_offset = 0; }; struct local_datetime : local_date, local_time { }; struct offset_datetime : local_datetime, zone_offset { static inline struct offset_datetime from_zoned(const struct tm& t) { offset_datetime dt; dt.year = t.tm_year + 1900; dt.month = t.tm_mon + 1; dt.day = t.tm_mday; dt.hour = t.tm_hour; dt.minute = t.tm_min; dt.second = t.tm_sec; char buf[16]; strftime(buf, 16, "%z", &t); int offset = std::stoi(buf); dt.hour_offset = offset / 100; dt.minute_offset = offset % 100; return dt; } CPPTOML_DEPRECATED("from_local has been renamed to from_zoned") static inline struct offset_datetime from_local(const struct tm& t) { return from_zoned(t); } static inline struct offset_datetime from_utc(const struct tm& t) { offset_datetime dt; dt.year = t.tm_year + 1900; dt.month = t.tm_mon + 1; dt.day = t.tm_mday; dt.hour = t.tm_hour; dt.minute = t.tm_min; dt.second = t.tm_sec; return dt; } }; CPPTOML_DEPRECATED("datetime has been renamed to offset_datetime") typedef offset_datetime datetime; class fill_guard { public: fill_guard(std::ostream& os) : os_(os), fill_{os.fill()} { // nothing } ~fill_guard() { os_.fill(fill_); } private: std::ostream& os_; std::ostream::char_type fill_; }; inline std::ostream& operator<<(std::ostream& os, const local_date& dt) { fill_guard g{os}; os.fill('0'); using std::setw; os << setw(4) << dt.year << "-" << setw(2) << dt.month << "-" << setw(2) << dt.day; return os; } inline std::ostream& operator<<(std::ostream& os, const local_time& ltime) { fill_guard g{os}; os.fill('0'); using std::setw; os << setw(2) << ltime.hour << ":" << setw(2) << ltime.minute << ":" << setw(2) << ltime.second; if (ltime.microsecond > 0) { os << "."; int power = 100000; for (int curr_us = ltime.microsecond; curr_us; power /= 10) { auto num = curr_us / power; os << num; curr_us -= num * power; } } return os; } inline std::ostream& operator<<(std::ostream& os, const zone_offset& zo) { fill_guard g{os}; os.fill('0'); using std::setw; if (zo.hour_offset != 0 || zo.minute_offset != 0) { if (zo.hour_offset > 0) { os << "+"; } else { os << "-"; } os << setw(2) << std::abs(zo.hour_offset) << ":" << setw(2) << std::abs(zo.minute_offset); } else { os << "Z"; } return os; } inline std::ostream& operator<<(std::ostream& os, const local_datetime& dt) { return os << static_cast(dt) << "T" << static_cast(dt); } inline std::ostream& operator<<(std::ostream& os, const offset_datetime& dt) { return os << static_cast(dt) << static_cast(dt); } template struct is_one_of; template struct is_one_of : std::is_same { }; template struct is_one_of { const static bool value = std::is_same::value || is_one_of::value; }; template class value; template struct valid_value : is_one_of { }; template struct value_traits; template struct valid_value_or_string_convertible { const static bool value = valid_value::type>::value || std::is_convertible::value; }; template struct value_traits::value>::type> { using value_type = typename std::conditional< valid_value::type>::value, typename std::decay::type, std::string>::type; using type = value; static value_type construct(T&& val) { return value_type(val); } }; template struct value_traits< T, typename std::enable_if< !valid_value_or_string_convertible::value && std::is_floating_point::type>::value>::type> { using value_type = typename std::decay::type; using type = value; static value_type construct(T&& val) { return value_type(val); } }; template struct value_traits< T, typename std::enable_if< !valid_value_or_string_convertible::value && !std::is_floating_point::type>::value && std::is_signed::type>::value>::type> { using value_type = int64_t; using type = value; static value_type construct(T&& val) { if (val < (std::numeric_limits::min)()) throw std::underflow_error{"constructed value cannot be " "represented by a 64-bit signed " "integer"}; if (val > (std::numeric_limits::max)()) throw std::overflow_error{"constructed value cannot be represented " "by a 64-bit signed integer"}; return static_cast(val); } }; template struct value_traits< T, typename std::enable_if< !valid_value_or_string_convertible::value && std::is_unsigned::type>::value>::type> { using value_type = int64_t; using type = value; static value_type construct(T&& val) { if (val > static_cast((std::numeric_limits::max)())) throw std::overflow_error{"constructed value cannot be represented " "by a 64-bit signed integer"}; return static_cast(val); } }; class array; class table; class table_array; template struct array_of_trait { using return_type = option>; }; template <> struct array_of_trait { using return_type = option>>; }; template inline std::shared_ptr::type> make_value(T&& val); inline std::shared_ptr make_array(); namespace detail { template inline std::shared_ptr make_element(); } inline std::shared_ptr make_table(); inline std::shared_ptr make_table_array(bool is_inline = false); #if defined(CPPTOML_NO_RTTI) /// Base type used to store underlying data type explicitly if RTTI is disabled enum class base_type { NONE, STRING, LOCAL_TIME, LOCAL_DATE, LOCAL_DATETIME, OFFSET_DATETIME, INT, FLOAT, BOOL, TABLE, ARRAY, TABLE_ARRAY }; /// Type traits class to convert C++ types to enum member template struct base_type_traits; template <> struct base_type_traits { static const base_type type = base_type::STRING; }; template <> struct base_type_traits { static const base_type type = base_type::LOCAL_TIME; }; template <> struct base_type_traits { static const base_type type = base_type::LOCAL_DATE; }; template <> struct base_type_traits { static const base_type type = base_type::LOCAL_DATETIME; }; template <> struct base_type_traits { static const base_type type = base_type::OFFSET_DATETIME; }; template <> struct base_type_traits { static const base_type type = base_type::INT; }; template <> struct base_type_traits { static const base_type type = base_type::FLOAT; }; template <> struct base_type_traits { static const base_type type = base_type::BOOL; }; template <> struct base_type_traits { static const base_type type = base_type::TABLE; }; template <> struct base_type_traits { static const base_type type = base_type::ARRAY; }; template <> struct base_type_traits { static const base_type type = base_type::TABLE_ARRAY; }; #endif /** * A generic base TOML value used for type erasure. */ class base : public std::enable_shared_from_this { public: virtual ~base() = default; virtual std::shared_ptr clone() const = 0; /** * Determines if the given TOML element is a value. */ virtual bool is_value() const { return false; } /** * Determines if the given TOML element is a table. */ virtual bool is_table() const { return false; } /** * Converts the TOML element into a table. */ std::shared_ptr as_table() { if (is_table()) return std::static_pointer_cast(shared_from_this()); return nullptr; } /** * Determines if the TOML element is an array of "leaf" elements. */ virtual bool is_array() const { return false; } /** * Converts the TOML element to an array. */ std::shared_ptr as_array() { if (is_array()) return std::static_pointer_cast(shared_from_this()); return nullptr; } /** * Determines if the given TOML element is an array of tables. */ virtual bool is_table_array() const { return false; } /** * Converts the TOML element into a table array. */ std::shared_ptr as_table_array() { if (is_table_array()) return std::static_pointer_cast(shared_from_this()); return nullptr; } /** * Attempts to coerce the TOML element into a concrete TOML value * of type T. */ template std::shared_ptr> as(); template std::shared_ptr> as() const; template void accept(Visitor&& visitor, Args&&... args) const; #if defined(CPPTOML_NO_RTTI) base_type type() const { return type_; } protected: base(const base_type t) : type_(t) { // nothing } private: const base_type type_ = base_type::NONE; #else protected: base() { // nothing } #endif }; /** * A concrete TOML value representing the "leaves" of the "tree". */ template class value : public base { struct make_shared_enabler { // nothing; this is a private key accessible only to friends }; template friend std::shared_ptr::type> cpptoml::make_value(U&& val); public: static_assert(valid_value::value, "invalid value type"); std::shared_ptr clone() const override; value(const make_shared_enabler&, const T& val) : value(val) { // nothing; note that users cannot actually invoke this function // because they lack access to the make_shared_enabler. } bool is_value() const override { return true; } /** * Gets the data associated with this value. */ T& get() { return data_; } /** * Gets the data associated with this value. Const version. */ const T& get() const { return data_; } private: T data_; /** * Constructs a value from the given data. */ #if defined(CPPTOML_NO_RTTI) value(const T& val) : base(base_type_traits::type), data_(val) { } #else value(const T& val) : data_(val) { } #endif value(const value& val) = delete; value& operator=(const value& val) = delete; }; template std::shared_ptr::type> make_value(T&& val) { using value_type = typename value_traits::type; using enabler = typename value_type::make_shared_enabler; return std::make_shared( enabler{}, value_traits::construct(std::forward(val))); } template inline std::shared_ptr> base::as() { #if defined(CPPTOML_NO_RTTI) if (type() == base_type_traits::type) return std::static_pointer_cast>(shared_from_this()); else return nullptr; #else return std::dynamic_pointer_cast>(shared_from_this()); #endif } // special case value to allow getting an integer parameter as a // double value template <> inline std::shared_ptr> base::as() { #if defined(CPPTOML_NO_RTTI) if (type() == base_type::FLOAT) return std::static_pointer_cast>(shared_from_this()); if (type() == base_type::INT) { auto v = std::static_pointer_cast>(shared_from_this()); return make_value(static_cast(v->get())); } #else if (auto v = std::dynamic_pointer_cast>(shared_from_this())) return v; if (auto v = std::dynamic_pointer_cast>(shared_from_this())) return make_value(static_cast(v->get())); #endif return nullptr; } template inline std::shared_ptr> base::as() const { #if defined(CPPTOML_NO_RTTI) if (type() == base_type_traits::type) return std::static_pointer_cast>(shared_from_this()); else return nullptr; #else return std::dynamic_pointer_cast>(shared_from_this()); #endif } // special case value to allow getting an integer parameter as a // double value template <> inline std::shared_ptr> base::as() const { #if defined(CPPTOML_NO_RTTI) if (type() == base_type::FLOAT) return std::static_pointer_cast>( shared_from_this()); if (type() == base_type::INT) { auto v = as(); // the below has to be a non-const value due to a bug in // libc++: https://llvm.org/bugs/show_bug.cgi?id=18843 return make_value(static_cast(v->get())); } #else if (auto v = std::dynamic_pointer_cast>(shared_from_this())) return v; if (auto v = as()) { // the below has to be a non-const value due to a bug in // libc++: https://llvm.org/bugs/show_bug.cgi?id=18843 return make_value(static_cast(v->get())); } #endif return nullptr; } /** * Exception class for array insertion errors. */ class array_exception : public std::runtime_error { public: array_exception(const std::string& err) : std::runtime_error{err} { } }; class array : public base { public: friend std::shared_ptr make_array(); std::shared_ptr clone() const override; virtual bool is_array() const override { return true; } using size_type = std::size_t; /** * arrays can be iterated over */ using iterator = std::vector>::iterator; /** * arrays can be iterated over. Const version. */ using const_iterator = std::vector>::const_iterator; iterator begin() { return values_.begin(); } const_iterator begin() const { return values_.begin(); } iterator end() { return values_.end(); } const_iterator end() const { return values_.end(); } /** * Obtains the array (vector) of base values. */ std::vector>& get() { return values_; } /** * Obtains the array (vector) of base values. Const version. */ const std::vector>& get() const { return values_; } std::shared_ptr at(size_t idx) const { return values_.at(idx); } /** * Obtains an array of values. Note that elements may be * nullptr if they cannot be converted to a value. */ template std::vector>> array_of() const { std::vector>> result(values_.size()); std::transform(values_.begin(), values_.end(), result.begin(), [&](std::shared_ptr v) { return v->as(); }); return result; } /** * Obtains a option>. The option will be empty if the array * contains values that are not of type T. */ template inline typename array_of_trait::return_type get_array_of() const { std::vector result; result.reserve(values_.size()); for (const auto& val : values_) { if (auto v = val->as()) result.push_back(v->get()); else return {}; } return {std::move(result)}; } /** * Obtains an array of arrays. Note that elements may be nullptr * if they cannot be converted to a array. */ std::vector> nested_array() const { std::vector> result(values_.size()); std::transform(values_.begin(), values_.end(), result.begin(), [&](std::shared_ptr v) -> std::shared_ptr { if (v->is_array()) return std::static_pointer_cast(v); return std::shared_ptr{}; }); return result; } /** * Add a value to the end of the array */ template void push_back(const std::shared_ptr>& val) { if (values_.empty() || values_[0]->as()) { values_.push_back(val); } else { throw array_exception{"Arrays must be homogenous."}; } } /** * Add an array to the end of the array */ void push_back(const std::shared_ptr& val) { if (values_.empty() || values_[0]->is_array()) { values_.push_back(val); } else { throw array_exception{"Arrays must be homogenous."}; } } /** * Convenience function for adding a simple element to the end * of the array. */ template void push_back(T&& val, typename value_traits::type* = 0) { push_back(make_value(std::forward(val))); } /** * Insert a value into the array */ template iterator insert(iterator position, const std::shared_ptr>& value) { if (values_.empty() || values_[0]->as()) { return values_.insert(position, value); } else { throw array_exception{"Arrays must be homogenous."}; } } /** * Insert an array into the array */ iterator insert(iterator position, const std::shared_ptr& value) { if (values_.empty() || values_[0]->is_array()) { return values_.insert(position, value); } else { throw array_exception{"Arrays must be homogenous."}; } } /** * Convenience function for inserting a simple element in the array */ template iterator insert(iterator position, T&& val, typename value_traits::type* = 0) { return insert(position, make_value(std::forward(val))); } /** * Erase an element from the array */ iterator erase(iterator position) { return values_.erase(position); } /** * Clear the array */ void clear() { values_.clear(); } /** * Reserve space for n values. */ void reserve(size_type n) { values_.reserve(n); } private: #if defined(CPPTOML_NO_RTTI) array() : base(base_type::ARRAY) { // empty } #else array() = default; #endif template array(InputIterator begin, InputIterator end) : values_{begin, end} { // nothing } array(const array& obj) = delete; array& operator=(const array& obj) = delete; std::vector> values_; }; inline std::shared_ptr make_array() { struct make_shared_enabler : public array { make_shared_enabler() { // nothing } }; return std::make_shared(); } namespace detail { template <> inline std::shared_ptr make_element() { return make_array(); } } // namespace detail /** * Obtains a option>. The option will be empty if the array * contains values that are not of type T. */ template <> inline typename array_of_trait::return_type array::get_array_of() const { std::vector> result; result.reserve(values_.size()); for (const auto& val : values_) { if (auto v = val->as_array()) result.push_back(v); else return {}; } return {std::move(result)}; } class table; class table_array : public base { friend class table; friend std::shared_ptr make_table_array(bool); public: std::shared_ptr clone() const override; using size_type = std::size_t; /** * arrays can be iterated over */ using iterator = std::vector>::iterator; /** * arrays can be iterated over. Const version. */ using const_iterator = std::vector>::const_iterator; iterator begin() { return array_.begin(); } const_iterator begin() const { return array_.begin(); } iterator end() { return array_.end(); } const_iterator end() const { return array_.end(); } virtual bool is_table_array() const override { return true; } std::vector>& get() { return array_; } const std::vector>& get() const { return array_; } /** * Add a table to the end of the array */ void push_back(const std::shared_ptr& val) { array_.push_back(val); } /** * Insert a table into the array */ iterator insert(iterator position, const std::shared_ptr& value) { return array_.insert(position, value); } /** * Erase an element from the array */ iterator erase(iterator position) { return array_.erase(position); } /** * Clear the array */ void clear() { array_.clear(); } /** * Reserve space for n tables. */ void reserve(size_type n) { array_.reserve(n); } /** * Whether or not the table array is declared inline. This mostly * matters for parsing, where statically defined arrays cannot be * appended to using the array-of-table syntax. */ bool is_inline() const { return is_inline_; } private: #if defined(CPPTOML_NO_RTTI) table_array(bool is_inline = false) : base(base_type::TABLE_ARRAY), is_inline_(is_inline) { // nothing } #else table_array(bool is_inline = false) : is_inline_(is_inline) { // nothing } #endif table_array(const table_array& obj) = delete; table_array& operator=(const table_array& rhs) = delete; std::vector> array_; const bool is_inline_ = false; }; inline std::shared_ptr make_table_array(bool is_inline) { struct make_shared_enabler : public table_array { make_shared_enabler(bool mse_is_inline) : table_array(mse_is_inline) { // nothing } }; return std::make_shared(is_inline); } namespace detail { template <> inline std::shared_ptr make_element() { return make_table_array(true); } } // namespace detail // The below are overloads for fetching specific value types out of a value // where special casting behavior (like bounds checking) is desired template typename std::enable_if::value && std::is_signed::value, option>::type get_impl(const std::shared_ptr& elem) { if (auto v = elem->as()) { if (v->get() < (std::numeric_limits::min)()) throw std::underflow_error{ "T cannot represent the value requested in get"}; if (v->get() > (std::numeric_limits::max)()) throw std::overflow_error{ "T cannot represent the value requested in get"}; return {static_cast(v->get())}; } else { return {}; } } template typename std::enable_if::value && std::is_unsigned::value, option>::type get_impl(const std::shared_ptr& elem) { if (auto v = elem->as()) { if (v->get() < 0) throw std::underflow_error{"T cannot store negative value in get"}; if (static_cast(v->get()) > (std::numeric_limits::max)()) throw std::overflow_error{ "T cannot represent the value requested in get"}; return {static_cast(v->get())}; } else { return {}; } } template typename std::enable_if::value || std::is_same::value, option>::type get_impl(const std::shared_ptr& elem) { if (auto v = elem->as()) { return {v->get()}; } else { return {}; } } /** * Represents a TOML keytable. */ class table : public base { public: friend class table_array; friend std::shared_ptr make_table(); std::shared_ptr clone() const override; /** * tables can be iterated over. */ using iterator = string_to_base_map::iterator; /** * tables can be iterated over. Const version. */ using const_iterator = string_to_base_map::const_iterator; iterator begin() { return map_.begin(); } const_iterator begin() const { return map_.begin(); } iterator end() { return map_.end(); } const_iterator end() const { return map_.end(); } bool is_table() const override { return true; } bool empty() const { return map_.empty(); } /** * Determines if this key table contains the given key. */ bool contains(const std::string& key) const { return map_.find(key) != map_.end(); } /** * Determines if this key table contains the given key. Will * resolve "qualified keys". Qualified keys are the full access * path separated with dots like "grandparent.parent.child". */ bool contains_qualified(const std::string& key) const { return resolve_qualified(key); } /** * Obtains the base for a given key. * @throw std::out_of_range if the key does not exist */ std::shared_ptr get(const std::string& key) const { return map_.at(key); } /** * Obtains the base for a given key. Will resolve "qualified * keys". Qualified keys are the full access path separated with * dots like "grandparent.parent.child". * * @throw std::out_of_range if the key does not exist */ std::shared_ptr get_qualified(const std::string& key) const { std::shared_ptr p; resolve_qualified(key, &p); return p; } /** * Obtains a table for a given key, if possible. */ std::shared_ptr get_table(const std::string& key) const { if (contains(key) && get(key)->is_table()) return std::static_pointer_cast(get(key)); return nullptr; } /** * Obtains a table for a given key, if possible. Will resolve * "qualified keys". */ std::shared_ptr get_table_qualified(const std::string& key) const { if (contains_qualified(key) && get_qualified(key)->is_table()) return std::static_pointer_cast(get_qualified(key)); return nullptr; } /** * Obtains an array for a given key. */ std::shared_ptr get_array(const std::string& key) const { if (!contains(key)) return nullptr; return get(key)->as_array(); } /** * Obtains an array for a given key. Will resolve "qualified keys". */ std::shared_ptr get_array_qualified(const std::string& key) const { if (!contains_qualified(key)) return nullptr; return get_qualified(key)->as_array(); } /** * Obtains a table_array for a given key, if possible. */ std::shared_ptr get_table_array(const std::string& key) const { if (!contains(key)) return nullptr; return get(key)->as_table_array(); } /** * Obtains a table_array for a given key, if possible. Will resolve * "qualified keys". */ std::shared_ptr get_table_array_qualified(const std::string& key) const { if (!contains_qualified(key)) return nullptr; return get_qualified(key)->as_table_array(); } /** * Helper function that attempts to get a value corresponding * to the template parameter from a given key. */ template option get_as(const std::string& key) const { try { return get_impl(get(key)); } catch (const std::out_of_range&) { return {}; } } /** * Helper function that attempts to get a value corresponding * to the template parameter from a given key. Will resolve "qualified * keys". */ template option get_qualified_as(const std::string& key) const { try { return get_impl(get_qualified(key)); } catch (const std::out_of_range&) { return {}; } } /** * Helper function that attempts to get an array of values of a given * type corresponding to the template parameter for a given key. * * If the key doesn't exist, doesn't exist as an array type, or one or * more keys inside the array type are not of type T, an empty option * is returned. Otherwise, an option containing a vector of the values * is returned. */ template inline typename array_of_trait::return_type get_array_of(const std::string& key) const { if (auto v = get_array(key)) { std::vector result; result.reserve(v->get().size()); for (const auto& b : v->get()) { if (auto val = b->as()) result.push_back(val->get()); else return {}; } return {std::move(result)}; } return {}; } /** * Helper function that attempts to get an array of values of a given * type corresponding to the template parameter for a given key. Will * resolve "qualified keys". * * If the key doesn't exist, doesn't exist as an array type, or one or * more keys inside the array type are not of type T, an empty option * is returned. Otherwise, an option containing a vector of the values * is returned. */ template inline typename array_of_trait::return_type get_qualified_array_of(const std::string& key) const { if (auto v = get_array_qualified(key)) { std::vector result; result.reserve(v->get().size()); for (const auto& b : v->get()) { if (auto val = b->as()) result.push_back(val->get()); else return {}; } return {std::move(result)}; } return {}; } /** * Adds an element to the keytable. */ void insert(const std::string& key, const std::shared_ptr& value) { map_[key] = value; } /** * Convenience shorthand for adding a simple element to the * keytable. */ template void insert(const std::string& key, T&& val, typename value_traits::type* = 0) { insert(key, make_value(std::forward(val))); } /** * Removes an element from the table. */ void erase(const std::string& key) { map_.erase(key); } private: #if defined(CPPTOML_NO_RTTI) table() : base(base_type::TABLE) { // nothing } #else table() { // nothing } #endif table(const table& obj) = delete; table& operator=(const table& rhs) = delete; std::vector split(const std::string& value, char separator) const { std::vector result; std::string::size_type p = 0; std::string::size_type q; while ((q = value.find(separator, p)) != std::string::npos) { result.emplace_back(value, p, q - p); p = q + 1; } result.emplace_back(value, p); return result; } // If output parameter p is specified, fill it with the pointer to the // specified entry and throw std::out_of_range if it couldn't be found. // // Otherwise, just return true if the entry could be found or false // otherwise and do not throw. bool resolve_qualified(const std::string& key, std::shared_ptr