.. _program_listing_file_cif++_item.hpp: Program Listing for File item.hpp ================================= |exhale_lsh| :ref:`Return to documentation for file ` (``cif++/item.hpp``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022 NKI/AVL, Netherlands Cancer Institute * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include "cif++/text.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace cif { class category; class row; // -------------------------------------------------------------------- enum class item_value_type { INT, FLOAT, TEXT, INAPPLICABLE, MISSING }; // -------------------------------------------------------------------- template concept IntegralType = (std::is_integral_v>); template concept FloatType = std::is_floating_point_v>; template concept StringType = (std::is_assignable_v and not std::is_integral_v and not std::is_floating_point_v); template inline constexpr bool is_optional_v = false; template inline constexpr bool is_optional_v> = true; class item_value { public: item_value() noexcept { m_data.m_type = item_value_type::INAPPLICABLE; } item_value(std::nullptr_t) { m_data.m_type = item_value_type::MISSING; } item_value(item_value_type type) noexcept : m_data(type) { } item_value(const item_value &rhs) { m_data.m_type = rhs.m_data.m_type; switch (m_data.m_type) { using enum item_value_type; case INT: m_data.m_value = rhs.m_data.m_value.m_integer; break; case FLOAT: m_data.m_len = rhs.m_data.m_len; m_data.m_value = rhs.m_data.m_value.m_float; break; case TEXT: m_data.m_len = rhs.m_data.m_len; m_data.m_value = rhs.m_data.sv(); break; default: break; } } item_value(std::string_view s) { if (s == ".") m_data.m_type = item_value_type::INAPPLICABLE; else if (s == "?") m_data.m_type = item_value_type::MISSING; else { m_data.m_type = item_value_type::TEXT; m_data.m_len = s.length(); m_data.m_value = s; } } template item_value(const char(s)[N]) : item_value(std::string_view{ s, N }) { } item_value(const char *s) : item_value(std::string_view{ s }) { } item_value(const std::string &s) : item_value(std::string_view{ s }) { } template item_value(T v) { m_data.m_type = item_value_type::INT; m_data.m_value = static_cast(v); } template item_value(T v) { m_data.m_type = item_value_type::FLOAT; m_data.m_value = static_cast(v); m_data.m_len = 0; } template item_value(T v, int precision) { m_data.m_type = item_value_type::FLOAT; m_data.m_value = static_cast(v); m_data.m_len = precision; } template item_value(std::optional v) { if (v.has_value()) { item_value iv{ *v }; swap(*this, iv); } else m_data.m_type = item_value_type::MISSING; } template item_value(std::optional v, int precision) { if (v.has_value()) { item_value iv{ *v }; swap(*this, iv); } else m_data.m_type = item_value_type::MISSING; m_data.m_len = precision; } item_value(item_value &&rhs) noexcept { swap(*this, rhs); } item_value &operator=(item_value rhs) noexcept { swap(*this, rhs); return *this; } // -------------------------------------------------------------------- [[nodiscard]] constexpr bool is_inapplicable() const noexcept { return m_data.m_type == item_value_type::INAPPLICABLE; } [[nodiscard]] constexpr bool is_missing() const noexcept { return m_data.m_type == item_value_type::MISSING; } [[nodiscard]] constexpr bool is_null() const noexcept { return is_inapplicable() or is_missing(); } [[nodiscard]] constexpr bool is_string() const noexcept { return m_data.m_type == item_value_type::TEXT; } [[nodiscard]] constexpr bool is_number_int() const noexcept { return m_data.m_type == item_value_type::INT; } [[nodiscard]] constexpr bool is_number_float() const noexcept { return m_data.m_type == item_value_type::FLOAT; } [[nodiscard]] constexpr bool is_number() const noexcept { return is_number_int() or is_number_float(); } [[nodiscard]] constexpr item_value_type type() const { return m_data.m_type; } [[nodiscard]] bool empty() const noexcept { switch (m_data.m_type) { using enum item_value_type; case INAPPLICABLE: case MISSING: return true; case TEXT: return m_data.sv().empty(); default: return false; } } // -------------------------------------------------------------------- template [[nodiscard]] inline std::string get() const { return str(); } template [[nodiscard]] std::remove_cvref_t get() const { static_assert(not std::is_same_v, bool>, "bool is no longer supported"); switch (m_data.m_type) { using enum item_value_type; case INT: return m_data.m_value.m_integer; case FLOAT: return m_data.m_value.m_float; case TEXT: { auto sv = m_data.sv(); auto sp = sv.data(); if (*sp == '+') ++sp; int64_t v; auto &&[ptr, ec] = from_chars(sp, sv.data() + sv.length(), v); if (ec != std::errc{}) throw std::system_error(std::make_error_code(ec)); if (ptr != sv.data() + sv.length()) throw std::invalid_argument("String value does not contain only an integer"); return v; } default: return not empty(); } } template [[nodiscard]] std::remove_cvref_t get() const { switch (m_data.m_type) { using enum item_value_type; case INT: return m_data.m_value.m_integer; case FLOAT: return m_data.m_value.m_float; case TEXT: { auto sv = m_data.sv(); auto sp = sv.data(); if (*sp == '+') ++sp; double v; auto &&[ptr, ec] = from_chars(sp, sv.data() + sv.length(), v); if (ec != std::errc{}) throw std::system_error(std::make_error_code(ec)); if (ptr != sv.data() + sv.length()) throw std::invalid_argument("String value does not contain only a floating point number"); return v; } default: return not empty(); } } template requires is_optional_v [[nodiscard]] auto get() const { switch (m_data.m_type) { using enum item_value_type; case INAPPLICABLE: case MISSING: return T{}; default: { auto v = get(); return T{ v }; } } } [[nodiscard]] std::string str() const; [[nodiscard]] const std::string_view sv() const { assert(m_data.m_type == cif::item_value_type::TEXT); return m_data.sv(); } // -------------------------------------------------------------------- friend void swap(item_value &a, item_value &b) noexcept { std::swap(a.m_data.m_type, b.m_data.m_type); std::swap(a.m_data.m_len, b.m_data.m_len); std::swap(a.m_data.m_value, b.m_data.m_value); } // -------------------------------------------------------------------- auto operator<=>(const item_value &rhs) const noexcept { std::partial_ordering result = std::partial_ordering::unordered; if (m_data.m_type == rhs.m_data.m_type) { switch (m_data.m_type) { using enum item_value_type; case INT: result = m_data.m_value.m_integer <=> rhs.m_data.m_value.m_integer; case FLOAT: result = m_data.m_value.m_float <=> rhs.m_data.m_value.m_float; case TEXT: result = m_data.sv() <=> rhs.m_data.sv(); default: result = std::partial_ordering::equivalent; } } else result = m_data.m_type <=> rhs.m_data.m_type; return result; } bool operator==(const item_value &rhs) const { if (m_data.m_type == rhs.m_data.m_type) { switch (m_data.m_type) { using enum item_value_type; case INT: return m_data.m_value.m_integer == rhs.m_data.m_value.m_integer; case FLOAT: return m_data.m_value.m_float == rhs.m_data.m_value.m_float; case TEXT: return m_data.sv() == rhs.m_data.sv(); case INAPPLICABLE: case MISSING: return true; } } return false; } [[nodiscard]] int compare(const item_value &b, bool ignore_case = false) const noexcept; friend std::ostream &operator<<(std::ostream &os, const item_value &v); void cast_to_int(); void cast_to_float(); void cast_to_string(); private: union value { int64_t m_integer{}; double m_float; char m_local_str[8]; char *m_str; value() = default; value(int64_t v) : m_integer(v) { } value(double v) : m_float(v) { } value(std::string_view s) { if (s.length() >= sizeof(m_local_str)) { m_str = new char[s.length() + 1]; std::copy(s.data(), s.data() + s.length(), m_str); m_str[s.length()] = 0; } else std::memcpy(m_local_str, s.data(), s.length() + 1); } value(item_value_type t) { m_integer = 0; } void destroy(item_value_type t, size_t len) { if (t == item_value_type::TEXT and len >= sizeof(m_local_str)) delete[] m_str; } }; struct data { item_value_type m_type = item_value_type::MISSING; uint32_t m_len{}; value m_value{}; data(item_value_type t) : m_type(t) , m_value(t) { } data() noexcept = default; data(data &&rhs) noexcept { std::swap(m_type, rhs.m_type); std::swap(m_len, rhs.m_len); std::swap(m_value, rhs.m_value); } data(const data &) noexcept = delete; data &operator=(data &&) noexcept = delete; data &operator=(const data &) noexcept = delete; ~data() { m_value.destroy(m_type, m_len); } [[nodiscard]] std::string_view sv() const noexcept { assert(m_type == item_value_type::TEXT); return m_type == item_value_type::TEXT ? std::string_view(m_len >= sizeof(m_value.m_local_str) ? m_value.m_str : m_value.m_local_str, m_len) : std::string_view{}; } [[nodiscard]] const char *c_str() const noexcept { assert(m_type == item_value_type::TEXT); return m_type == item_value_type::TEXT ? (m_len >= sizeof(m_value.m_local_str) ? m_value.m_str : m_value.m_local_str) : nullptr; } } m_data{}; }; static_assert(sizeof(item_value) == 16, "item_value should be 16 bytes"); class item { public: item() = default; item(std::string name) : m_name(std::move(name)) , m_value(item_value_type::MISSING) { } item(std::string name, item_value value) : m_name(std::move(name)) , m_value(std::move(value)) { } item(const item &rhs) = default; item(item &&rhs) { swap(*this, rhs); } item &operator=(item rhs) noexcept { swap(*this, rhs); return *this; } friend void swap(item &a, item &b) noexcept { std::swap(a.m_name, b.m_name); std::swap(a.m_value, b.m_value); } [[nodiscard]] const std::string &name() const { return m_name; } [[nodiscard]] const item_value &value() const & { return m_value; } item_value &value() & { return m_value; } void value(item_value v) { m_value = std::move(v); } [[nodiscard]] bool empty() const { return m_value.empty(); } [[nodiscard]] bool is_null() const { return m_value.is_null(); } [[nodiscard]] bool is_unknown() const { return m_value.is_missing(); } // /// \brief the length of the value string // std::size_t length() const { return m_value.length(); } template decltype(auto) get() const { if constexpr (N == 0) return name(); else if constexpr (N == 1) return value(); } // auto operator<=>(const item &rhs) const = default; private: std::string m_name; item_value m_value; }; // -------------------------------------------------------------------- struct item_handle { public: item_handle() = delete; item_handle &operator=(item_value value) { set(std::move(value), true); return *this; } [[nodiscard]] item_value &value(); [[nodiscard]] const item_value &value() const; [[nodiscard]] bool is_inapplicable() const noexcept { return not empty() and value().type() == item_value_type::INAPPLICABLE; } [[nodiscard]] bool is_missing() const noexcept { return empty() or value().type() == item_value_type::MISSING; } [[nodiscard]] bool is_null() const noexcept { return empty() or is_inapplicable() or is_missing(); } [[nodiscard]] bool is_string() const noexcept { return not empty() and value().type() == item_value_type::TEXT; } [[nodiscard]] bool is_number_int() const noexcept { return not empty() and value().type() == item_value_type::INT; } [[nodiscard]] bool is_number_float() const noexcept { return not empty() and value().type() == item_value_type::FLOAT; } [[nodiscard]] bool is_number() const noexcept { return not empty() and (is_number_int() or is_number_float()); } [[nodiscard]] auto type() const { return empty() ? item_value_type::MISSING : value().type(); } template [[nodiscard]] auto get() const { if (empty()) return T{}; else return value().template get(); } template [[deprecated("Use get instead")]] [[nodiscard]] auto as() const { if (empty()) return T{}; else return value().template get(); } [[nodiscard]] auto str() const { return value().str(); } [[nodiscard]] auto sv() const { return value().sv(); } friend void swap(item_handle a, item_handle b) noexcept; template [[nodiscard]] auto value_or(const T &dv) const { return empty() ? dv : this->get(); } [[nodiscard]] int compare(const item_value &value, bool icase = true) const noexcept { return this->value().compare(value, icase); } [[nodiscard]] int compare(const item_handle &value, bool icase = true) const noexcept { if (empty() and value.empty()) return 0; else if (empty()) return -1; else if (value.empty()) return 1; else return compare(value.value(), icase); } [[nodiscard]] bool operator==(const item_value &value) const noexcept { // TODO: icase or not icase? return this->value().compare(value) == 0; } // We may not have C++20 yet... template [[nodiscard]] bool operator!=(const T &value) const noexcept { return not operator==(value); } [[nodiscard]] bool empty() const; [[nodiscard]] std::string_view text_() const; item_handle(category &cat, row &row, uint16_t item_ix) : m_category(cat) , m_row(row) , m_item_ix(item_ix) { } item_handle(const category &cat, const row &r, uint16_t item_ix) : m_category(const_cast(cat)) , m_row(const_cast(r)) , m_item_ix(item_ix) , m_is_const(true) { } item_handle(const item_handle &) = delete; item_handle &operator=(const item_handle &) = delete; friend std::ostream &operator<<(std::ostream &os, const item_handle &h) { if (h.empty()) os << "NULL"; else os << h.value(); return os; } private: category &m_category; row &m_row; uint16_t m_item_ix; bool m_is_const = false; friend class parser; void set(item_value value, bool updateLinked); }; } // namespace cif namespace std { template <> struct tuple_size<::cif::item> : public std::integral_constant { }; template <> struct tuple_element<0, ::cif::item> { using type = decltype(std::declval<::cif::item>().name()); }; template <> struct tuple_element<1, ::cif::item> { using type = decltype(std::declval<::cif::item>().value()); }; } // namespace std