# Program Listing for File item.hpp

↰ Return to documentation for file (`cif++/item.hpp`)

    /*-
     * 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++/exports.hpp"
    #include "cif++/forward_decl.hpp"
    #include "cif++/text.hpp"
    #include "cif++/utilities.hpp"

    #include <cassert>
    #include <charconv>
    #include <cstring>
    #include <iomanip>
    #include <iostream>
    #include <limits>
    #include <memory>
    #include <optional>
    #include <utility>

    namespace cif
    {

    // --------------------------------------------------------------------
    class item
    {
      public:
        item() = default;

        item(std::string_view name)
            : m_name(name)
            , m_value({ '.' })
        {
        }

        item(std::string_view name, char value)
            : m_name(name)
            , m_value({ value })
        {
        }

        template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
        item(std::string_view name, const T &value, int precision)
            : m_name(name)
        {
            using namespace std;
            using namespace cif;

            char buffer[32];

            auto r = to_chars(buffer, buffer + sizeof(buffer) - 1, value, chars_format::fixed, precision);
            if ((bool)r.ec)
                throw std::runtime_error("Could not format number");

            m_value.assign(buffer, r.ptr - buffer);
        }

        template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
        item(const std::string_view name, const T &value)
            : m_name(name)
        {
            using namespace std;
            using namespace cif;

            char buffer[32];

            auto r = to_chars(buffer, buffer + sizeof(buffer) - 1, value, chars_format::general);
            if ((bool)r.ec)
                throw std::runtime_error("Could not format number");

            m_value.assign(buffer, r.ptr - buffer);
        }

        template <typename T, std::enable_if_t<std::is_integral_v<T> and not std::is_same_v<T, bool>, int> = 0>
        item(const std::string_view name, const T &value)
            : m_name(name)
        {
            char buffer[32];

            auto r = std::to_chars(buffer, buffer + sizeof(buffer) - 1, value);
            if ((bool)r.ec)
                throw std::runtime_error("Could not format number");

            m_value.assign(buffer, r.ptr - buffer);
        }

        template <typename T, std::enable_if_t<std::is_same_v<T, bool>, int> = 0>
        item(const std::string_view name, const T &value)
            : m_name(name)
        {
            m_value.assign(value ? "y" : "n");
        }

        item(const std::string_view name, std::string_view value)
            : m_name(name)
            , m_value(value)
        {
        }

        template<typename T, std::enable_if_t<std::is_same_v<T, std::string>, int> = 0>
        item(const std::string_view name, T &&value)
            : m_name(name)
            , m_value(std::move(value))
        {
        }

        template <typename T>
        item(const std::string_view name, const std::optional<T> &value)
            : m_name(name)
        {
            if (value.has_value())
            {
                item tmp(name, *value);
                std::swap(tmp.m_value, m_value);
            }
            else
                m_value.assign("?");
        }

        template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
        item(std::string_view name, const std::optional<T> &value, int precision)
            : m_name(name)
        {
            if (value.has_value())
            {
                item tmp(name, *value, precision);
                std::swap(tmp.m_value, m_value);
            }
            else
                m_value.assign("?");
        }

        item(const item &rhs) = default;
        item(item &&rhs) noexcept = default;
        item &operator=(const item &rhs) = default;
        item &operator=(item &&rhs) noexcept = default;
        std::string_view name() const { return m_name; }
        std::string_view value() const & { return m_value; }
        std::string value() const && { return std::move(m_value); }

        void value(std::string_view v) { m_value = v; }

        bool empty() const { return m_value.empty(); }

        bool is_null() const { return m_value == "."; }

        bool is_unknown() const { return m_value == "?"; }

        std::size_t length() const { return m_value.length(); }

        template <std::size_t N>
        decltype(auto) get() const
        {
            if constexpr (N == 0)
                return name();
            else if constexpr (N == 1)
                return value();
        }

      private:
        std::string_view m_name;
        std::string m_value;
    };

    // --------------------------------------------------------------------

    struct item_value
    {
        item_value() = default;
        item_value(std::string_view text)
            : m_length(text.length())
            , m_storage(0)
        {
            if (m_length >= kBufferSize)
            {
                m_data = new char[m_length + 1];
                std::copy(text.begin(), text.end(), m_data);
                m_data[m_length] = 0;
            }
            else
            {
                std::copy(text.begin(), text.end(), m_local_data);
                m_local_data[m_length] = 0;
            }
        }

        item_value(item_value &&rhs) noexcept
            : m_length(std::exchange(rhs.m_length, 0))
            , m_storage(std::exchange(rhs.m_storage, 0))
        {
        }

        item_value &operator=(item_value &&rhs) noexcept
        {
            std::swap(m_length, rhs.m_length);
            std::swap(m_storage, rhs.m_storage);
            return *this;
        }

        ~item_value()
        {
            if (m_length >= kBufferSize)
                delete[] m_data;
            m_storage = 0;
            m_length = 0;
        }

        item_value(const item_value &) = delete;
        item_value &operator=(const item_value &) = delete;
        explicit operator bool() const
        {
            return m_length != 0;
        }

        std::size_t m_length = 0;
        union
        {
            char m_local_data[8];
            char *m_data;
            uint64_t m_storage;
        };

        static constexpr std::size_t kBufferSize = sizeof(m_local_data);

        // By using std::string_view instead of c_str we obain a
        // nice performance gain since we avoid many calls to strlen.

        constexpr inline std::string_view text() const
        {
            return { m_length >= kBufferSize ? m_data : m_local_data, m_length };
        }
    };

    // --------------------------------------------------------------------
    // Transient object to access stored data

    struct item_handle
    {
      public:
        // conversion helper class
        template <typename T, typename = void>
        struct item_value_as;
        template <typename T>
        item_handle &operator=(const T &value)
        {
            assign_value(item{ "", value }.value());
            return *this;
        }

        template <typename T>
        item_handle &operator=(T &&value)
        {
            assign_value(item{ "", std::forward<T>(value) }.value());
            return *this;
        }

        template <std::size_t N>
        item_handle &operator=(const char (&value)[N])
        {
            assign_value(item{ "", std::move(value) }.value());
            return *this;
        }

        template <typename... Ts>
        void os(const Ts &...v)
        {
            std::ostringstream ss;
            ((ss << v), ...);
            this->operator=(ss.str());
        }

        void swap(item_handle &b);

        template <typename T = std::string>
        auto as() const -> T
        {
            using value_type = std::remove_cv_t<std::remove_reference_t<T>>;
            return item_value_as<value_type>::convert(*this);
        }

        template <typename T>
        auto value_or(const T &dv) const
        {
            return empty() ? dv : this->as<T>();
        }

        template <typename T>
        int compare(const T &value, bool icase = true) const
        {
            return item_value_as<T>::compare(*this, value, icase);
        }

        template <typename T>
        bool operator==(const T &value) const
        {
            // TODO: icase or not icase?
            return item_value_as<T>::compare(*this, value, true) == 0;
        }

        // We may not have C++20 yet...

        template <typename T>
        bool operator!=(const T &value) const
        {
            return not operator==(value);
        }

        bool empty() const
        {
            auto txt = text();
            return txt.empty() or (txt.length() == 1 and (txt.front() == '.' or txt.front() == '?'));
        }

        explicit operator bool() const { return not empty(); }

        bool is_null() const
        {
            auto txt = text();
            return txt.length() == 1 and txt.front() == '.';
        }

        bool is_unknown() const
        {
            auto txt = text();
            return txt.length() == 1 and txt.front() == '?';
        }

        std::string_view text() const;

        item_handle(uint16_t item, row_handle &row)
            : m_item_ix(item)
            , m_row_handle(row)
        {
        }

        CIFPP_EXPORT static const item_handle s_null_item;

        friend void swap(item_handle a, item_handle b)
        {
            a.swap(b);
        }

      private:
        item_handle();

        uint16_t m_item_ix;
        row_handle &m_row_handle;

        void assign_value(std::string_view value);
    };

    // So sad that older gcc implementations of from_chars did not support floats yet...

    template <typename T>
    struct item_handle::item_value_as<T, std::enable_if_t<std::is_arithmetic_v<T> and not std::is_same_v<T, bool>>>
    {
        using value_type = std::remove_reference_t<std::remove_cv_t<T>>;

        static value_type convert(const item_handle &ref)
        {
            value_type result = {};

            if (not ref.empty())
            {
                auto txt = ref.text();

                auto b = txt.data();
                auto e = txt.data() + txt.size();

                std::from_chars_result r = (b + 1 < e and *b == '+' and std::isdigit(b[1])) ? selected_charconv<value_type>::from_chars(b + 1, e, result) : selected_charconv<value_type>::from_chars(b, e, result);

                if ((bool)r.ec or r.ptr != e)
                {
                    result = {};
                    if (cif::VERBOSE)
                    {
                        if (r.ec == std::errc::invalid_argument)
                            std::cerr << "Attempt to convert " << std::quoted(txt) << " into a number\n";
                        else if (r.ec == std::errc::result_out_of_range)
                            std::cerr << "Conversion of " << std::quoted(txt) << " into a type that is too small\n";
                        else
                            std::cerr << "Not a valid number " << std::quoted(txt) << '\n';
                    }
                }
            }

            return result;
        }

        static int compare(const item_handle &ref, const T &value, bool icase)
        {
            int result = 0;

            auto txt = ref.text();

            if (ref.empty())
                result = 1;
            else
            {
                value_type v = {};

                auto b = txt.data();
                auto e = txt.data() + txt.size();

                std::from_chars_result r = (b + 1 < e and *b == '+' and std::isdigit(b[1])) ? selected_charconv<value_type>::from_chars(b + 1, e, v) : selected_charconv<value_type>::from_chars(b, e, v);

                if ((bool)r.ec or r.ptr != e)
                {
                    if (cif::VERBOSE)
                    {
                        if (r.ec == std::errc::invalid_argument)
                            std::cerr << "Attempt to convert " << std::quoted(txt) << " into a number\n";
                        else if (r.ec == std::errc::result_out_of_range)
                            std::cerr << "Conversion of " << std::quoted(txt) << " into a type that is too small\n";
                        else
                            std::cerr << "Not a valid number " << std::quoted(txt) << '\n';
                    }
                    result = 1;
                }
                else if (std::abs(v - value) <= std::numeric_limits<value_type>::epsilon())
                    result = 0;
                else if (v < value)
                    result = -1;
                else if (v > value)
                    result = 1;
            }

            return result;
        }
    };

    template <typename T>
    struct item_handle::item_value_as<std::optional<T>>
    {
        static std::optional<T> convert(const item_handle &ref)
        {
            std::optional<T> result;
            if (ref)
                result = ref.as<T>();
            return result;
        }

        static int compare(const item_handle &ref, std::optional<T> value, bool icase)
        {
            if (ref.empty() and not value)
                return 0;

            if (ref.empty())
                return -1;
            else if (not value)
                return 1;
            else
                return ref.compare(*value, icase);
        }
    };

    template <typename T>
    struct item_handle::item_value_as<T, std::enable_if_t<std::is_same_v<T, bool>>>
    {
        static bool convert(const item_handle &ref)
        {
            bool result = false;
            if (not ref.empty())
                result = iequals(ref.text(), "y");
            return result;
        }

        static int compare(const item_handle &ref, bool value, bool icase)
        {
            bool rv = convert(ref);
            return value && rv ? 0
                               : (rv < value ? -1 : 1);
        }
    };

    template <std::size_t N>
    struct item_handle::item_value_as<char[N]>
    {
        static std::string convert(const item_handle &ref)
        {
            if (ref.empty())
                return {};
            return { ref.text().data(), ref.text().size() };
        }

        static int compare(const item_handle &ref, const char (&value)[N], bool icase)
        {
            return icase ? cif::icompare(ref.text(), value) : ref.text().compare(value);
        }
    };

    template <typename T>
    struct item_handle::item_value_as<T, std::enable_if_t<std::is_same_v<T, const char *>>>
    {
        static std::string convert(const item_handle &ref)
        {
            if (ref.empty())
                return {};
            return { ref.text().data(), ref.text().size() };
        }

        static int compare(const item_handle &ref, const char *value, bool icase)
        {
            return icase ? cif::icompare(ref.text(), value) : ref.text().compare(value);
        }
    };

    template <typename T>
    struct item_handle::item_value_as<T, std::enable_if_t<std::is_same_v<T, std::string_view>>>
    {
        static std::string convert(const item_handle &ref)
        {
            if (ref.empty())
                return {};
            return { ref.text().data(), ref.text().size() };
        }

        static int compare(const item_handle &ref, const std::string_view &value, bool icase)
        {
            return icase ? cif::icompare(ref.text(), value) : ref.text().compare(value);
        }
    };

    template <typename T>
    struct item_handle::item_value_as<T, std::enable_if_t<std::is_same_v<T, std::string>>>
    {
        static std::string convert(const item_handle &ref)
        {
            if (ref.empty())
                return {};
            return { ref.text().data(), ref.text().size() };
        }

        static int compare(const item_handle &ref, const std::string &value, bool icase)
        {
            return icase ? cif::icompare(ref.text(), value) : ref.text().compare(value);
        }
    };

    } // namespace cif

    namespace std
    {

    template <>
    struct tuple_size<::cif::item>
        : public std::integral_constant<std::size_t, 2>
    {
    };

    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