Program Listing for File Literal.hpp

Return to documentation for file (src/rdf4cpp/Literal.hpp)

#ifndef RDF4CPP_LITERAL_HPP
#define RDF4CPP_LITERAL_HPP


#include <any>
#include <optional>
#include <ostream>
#include <random>
#include <rdf4cpp/Node.hpp>
#include <rdf4cpp/datatypes/LiteralDatatype.hpp>
#include <rdf4cpp/datatypes/owl.hpp>
#include <rdf4cpp/datatypes/rdf.hpp>
#include <rdf4cpp/datatypes/xsd.hpp>
#include <rdf4cpp/regex/Regex.hpp>
#include <rdf4cpp/CowString.hpp>
#include <rdf4cpp/TriBool.hpp>
#include <rdf4cpp/Assert.hpp>
#include <type_traits>

namespace rdf4cpp {

struct Literal : Node {
private:
    [[nodiscard]] static bool lexical_form_needs_escape(std::string_view lexical_form) noexcept;

    template<typename OpSelect>
        requires std::is_nothrow_invocable_r_v<datatypes::registry::DatatypeRegistry::binop_fptr_t, OpSelect, datatypes::registry::DatatypeRegistry::NumericOpsImpl const &>
    [[nodiscard]] Literal numeric_binop_impl(OpSelect op_select, Literal const &other, storage::DynNodeStoragePtr node_storage) const;

    template<typename OpSelect>
        requires std::is_nothrow_invocable_r_v<datatypes::registry::DatatypeRegistry::unop_fptr_t, OpSelect, datatypes::registry::DatatypeRegistry::NumericOpsImpl const &>
    [[nodiscard]] Literal numeric_unop_impl(OpSelect op_select, storage::DynNodeStoragePtr node_storage) const;

    template<typename Op>
    [[nodiscard]] std::optional<Literal> run_binop(Literal const &other,
                                                   datatypes::registry::DatatypeIDView const &this_datatype,
                                                   datatypes::registry::DatatypeRegistry::DatatypeEntry const &this_entry,
                                                   datatypes::registry::DatatypeIDView const &other_datatype,
                                                   datatypes::registry::DatatypeRegistry::DatatypeEntry const &other_entry,
                                                   storage::DynNodeStoragePtr node_storage,
                                                   Op &&op) const;

    template<typename Op>
    [[nodiscard]] std::optional<Literal> run_binop_cast_rhs(Literal const &other,
                                                            datatypes::registry::DatatypeRegistry::DatatypeEntry const &other_entry,
                                                            datatypes::registry::DatatypeIDView const &other_target,
                                                            storage::DynNodeStoragePtr node_storage,
                                                            Op &&op) const;

    [[nodiscard]] std::optional<Literal> chrono_add_impl(Literal const &other, storage::DynNodeStoragePtr node_storage) const;

    [[nodiscard]] std::optional<Literal> chrono_sub_impl(Literal const &other, storage::DynNodeStoragePtr node_storage) const;

    [[nodiscard]] std::optional<Literal> chrono_mul_impl(Literal const &other, storage::DynNodeStoragePtr node_storage) const;

    [[nodiscard]] std::optional<Literal> chrono_div_impl(Literal const &other, storage::DynNodeStoragePtr node_storage) const;

    std::partial_ordering compare_impl(Literal const &other, std::strong_ordering *out_alternative_ordering = nullptr) const noexcept;

    [[nodiscard]] datatypes::registry::DatatypeIDView datatype_id() const noexcept;

    [[nodiscard]] bool is_fixed() const noexcept;

    [[nodiscard]] bool is_fixed_not_numeric() const noexcept;

    [[nodiscard]] bool is_fixed_not_timepoint() const noexcept;

    [[nodiscard]] bool is_fixed_not_duration() const noexcept;

    [[nodiscard]] bool is_string_like() const noexcept;

    [[nodiscard]] static Literal make_simple_unchecked(std::string_view lexical_form, bool needs_escape, storage::DynNodeStoragePtr node_storage);

    [[nodiscard]] static Literal make_noninlined_typed_unchecked(std::string_view lexical_form, bool needs_escape, IRI const &datatype, storage::DynNodeStoragePtr node_storage);

    [[nodiscard]] static Literal make_noninlined_special_unchecked(std::any &&value, storage::identifier::LiteralType fixed_id, storage::DynNodeStoragePtr node_storage);

    [[nodiscard]] static Literal make_inlined_typed_unchecked(storage::identifier::LiteralID inlined_value, storage::identifier::LiteralType fixed_id, storage::DynNodeStoragePtr node_storage) noexcept;

    [[nodiscard]] static Literal make_typed_unchecked(std::any &&value, datatypes::registry::DatatypeIDView datatype, datatypes::registry::DatatypeRegistry::DatatypeEntry const &entry, storage::DynNodeStoragePtr node_storage);

    [[nodiscard]] static Literal make_lang_tagged_unchecked(std::string_view lexical_form, bool needs_escape, std::string_view lang, storage::DynNodeStoragePtr node_storage);

    [[nodiscard]] static Literal make_lang_tagged_unchecked_from_node_id(std::string_view lang, storage::DynNodeStoragePtr node_storage, storage::identifier::NodeBackendID node_id) noexcept;

     // TODO needs escape flag
    [[nodiscard]] static Literal make_string_like_copy_lang_tag(std::string_view str, Literal const &lang_tag_src, storage::DynNodeStoragePtr node_storage);

    [[nodiscard]] Literal lang_tagged_get_de_inlined() const noexcept;

    [[nodiscard]] bool dynamic_datatype_eq_impl(std::string_view datatype) const noexcept;

    template<bool short_form>
    bool serialize_impl(writer::BufWriterParts writer) const noexcept;

    template<bool simplified, typename C>
    auto serialize_lexical_form_impl(C &&consume) const noexcept;

    [[nodiscard]] Literal cast_impl(datatypes::registry::DatatypeIDView target_dtid, storage::DynNodeStoragePtr node_storage) const;

    explicit Literal(storage::identifier::NodeBackendHandle handle) noexcept;

public:

    Literal() noexcept;

    [[nodiscard]] static Literal make_null() noexcept;

    [[nodiscard]] static Literal make_simple(std::string_view lexical_form, storage::DynNodeStoragePtr node_storage = storage::default_node_storage);

    [[nodiscard]] static Literal make_simple_normalize(std::string_view lexical_form, storage::DynNodeStoragePtr node_storage = storage::default_node_storage);

    [[nodiscard]] static Literal make_lang_tagged(std::string_view lexical_form, std::string_view lang_tag,
                                                  storage::DynNodeStoragePtr node_storage = storage::default_node_storage);

    [[nodiscard]] static Literal make_lang_tagged_normalize(std::string_view lexical_form, std::string_view lang_tag,
                                                            storage::DynNodeStoragePtr node_storage = storage::default_node_storage);
    [[nodiscard]] static Literal make_typed(std::string_view lexical_form, IRI const &datatype,
                                            storage::DynNodeStoragePtr node_storage = storage::default_node_storage);

    template<datatypes::LiteralDatatype T>
    [[nodiscard]] static Literal make_typed(std::string_view lexical_form,
                                            storage::DynNodeStoragePtr node_storage = storage::default_node_storage) {

        if constexpr (std::is_same_v<T, datatypes::rdf::LangString>) {
            // see: https://www.w3.org/TR/rdf11-concepts/#section-Graph-Literal
            throw std::invalid_argument{"cannot construct rdf:langString without a language tag, please call one of the other factory functions"};
        } else if constexpr (std::is_same_v<T, datatypes::xsd::String>) {
            return Literal::make_simple(lexical_form, node_storage);
        }

        auto value = T::from_string(lexical_form);

        if constexpr (datatypes::IsInlineable<T>) {
            if (auto const maybe_inlined = T::try_into_inlined(value); maybe_inlined.has_value()) {
                return Literal::make_inlined_typed_unchecked(*maybe_inlined, T::fixed_id, node_storage);
            }
        }

        if constexpr (datatypes::HasFixedId<T>) {
            if (node_storage.has_specialized_storage_for(T::fixed_id)) {
                return Literal::make_noninlined_special_unchecked(std::any{std::move(value)}, T::fixed_id, node_storage);
            }
        }

        auto const lex = writer::StringWriter::oneshot([&value](writer::StringWriter &w) noexcept {
            return T::serialize_canonical_string(value, w);
        });

        auto const needs_escape = lexical_form_needs_escape(lex);
        return Literal::make_noninlined_typed_unchecked(lex,
                                                        needs_escape,
                                                        IRI{T::identifier, node_storage},
                                                        node_storage);
    }

    template<datatypes::LiteralDatatype T>
    [[nodiscard]] static Literal make_typed_from_value(typename T::cpp_type const &compatible_value,
                                                       storage::DynNodeStoragePtr node_storage = storage::default_node_storage) {

        if constexpr (std::is_same_v<T, datatypes::rdf::LangString>) {
            return Literal::make_lang_tagged(compatible_value.lexical_form,
                                             compatible_value.language_tag,
                                             node_storage);
        }

        if constexpr (std::is_same_v<T, datatypes::xsd::String>) {
            return Literal::make_simple(compatible_value, node_storage);
        }

        if constexpr (datatypes::IsInlineable<T>) {
            if (auto const maybe_inlined = T::try_into_inlined(compatible_value); maybe_inlined.has_value()) {
                return Literal::make_inlined_typed_unchecked(*maybe_inlined, T::fixed_id, node_storage);
            }
        }

        if constexpr (datatypes::HasFixedId<T>) {
            if (node_storage.has_specialized_storage_for(T::fixed_id)) {
                return Literal{storage::identifier::NodeBackendHandle{node_storage.find_or_make_id(storage::view::ValueLiteralBackendView{
                                                                                                        .datatype = T::fixed_id,
                                                                                                        .value = std::any{compatible_value}}),
                                                                      node_storage}};
            }
        }

        auto const lex = writer::StringWriter::oneshot([&compatible_value](writer::StringWriter &w) noexcept {
            return T::serialize_canonical_string(compatible_value, w);
        });

        auto const needs_escape = lexical_form_needs_escape(lex);

        return Literal::make_noninlined_typed_unchecked(lex,
                                                        needs_escape,
                                                        IRI{T::datatype_id, node_storage},
                                                        node_storage);
    }

    static Literal make_boolean(TriBool b, storage::DynNodeStoragePtr node_storage = storage::default_node_storage) noexcept;

    [[nodiscard]] static Literal make_string_uuid(storage::DynNodeStoragePtr node_storage = storage::default_node_storage);

    [[nodiscard]] static Literal generate_random_double(storage::DynNodeStoragePtr node_storage = storage::default_node_storage);

    template<typename Rng>
    [[nodiscard]] static Literal generate_random_double(Rng &rng, storage::DynNodeStoragePtr node_storage = storage::default_node_storage) {
        // uniform_real_distribution does not have any state, therefore we can construct a new one for each call
        std::uniform_real_distribution<typename datatypes::xsd::Double::cpp_type> dist{0.0, 1.0};
        return Literal::make_typed_from_value<datatypes::xsd::Double>(dist(rng), node_storage);
    }

    Literal to_node_storage(storage::DynNodeStoragePtr node_storage) const;
    [[nodiscard]] Literal try_get_in_node_storage(storage::DynNodeStoragePtr node_storage) const noexcept;

private:
    [[nodiscard]] static storage::identifier::NodeBackendID find_datatype_iri(datatypes::registry::DatatypeIDView id, storage::DynNodeStoragePtr node_storage) noexcept;

public:
    [[nodiscard]] static Literal find_simple(std::string_view lexical_form, storage::DynNodeStoragePtr node_storage = storage::default_node_storage) noexcept;

    [[nodiscard]] static Literal find_lang_tagged(std::string_view lexical_form, std::string_view lang_tag, storage::DynNodeStoragePtr node_storage = storage::default_node_storage) noexcept;

    template<datatypes::LiteralDatatype T>
    [[nodiscard]] static Literal find_typed_from_value(typename T::cpp_type const &compatible_value,
                                                       storage::DynNodeStoragePtr node_storage = storage::default_node_storage) noexcept {
        if constexpr (std::is_same_v<T, datatypes::rdf::LangString>) {
            return find_lang_tagged(compatible_value.lexical_form,
                                    compatible_value.language_tag,
                                    node_storage);
        }

        if constexpr (std::is_same_v<T, datatypes::xsd::String>) {
            return find_simple(compatible_value, node_storage);
        }

        if constexpr (datatypes::IsInlineable<T>) {
            if (auto const maybe_inlined = T::try_into_inlined(compatible_value); maybe_inlined.has_value()) {
                return Literal::make_inlined_typed_unchecked(*maybe_inlined, T::fixed_id, node_storage);
            }
        }

        if constexpr (datatypes::HasFixedId<T>) {
            if (node_storage.has_specialized_storage_for(T::fixed_id)) {
                auto nid = node_storage.find_id(storage::view::ValueLiteralBackendView{
                        .datatype = T::fixed_id,
                        .value = std::any{compatible_value}});
                if (nid.null())
                    return Literal{};
                return Literal{storage::identifier::NodeBackendHandle{nid, node_storage}};
            }
        }

        auto dty = find_datatype_iri(T::datatype_id, node_storage);
        if (dty.null())
            return Literal{};

        auto const lex = writer::StringWriter::oneshot([&compatible_value]<typename W>(W &w) noexcept {
            return T::serialize_canonical_string(compatible_value, w);
        });

        auto const needs_escape = lexical_form_needs_escape(lex);

        auto nid = node_storage.find_id(storage::view::LexicalFormLiteralBackendView{
                .datatype_id = dty,
                .lexical_form = lex,
                .language_tag = "",
                .needs_escape = needs_escape});

        if (nid.null())
            return Literal{};
        return Literal{storage::identifier::NodeBackendHandle{nid, node_storage}};
    }

    template<datatypes::LiteralDatatype T>
        requires(!std::same_as<T, datatypes::rdf::LangString>)
    [[nodiscard]] static Literal find_typed(std::string_view lexical_form,
                                            storage::DynNodeStoragePtr node_storage = storage::default_node_storage) noexcept {
        if constexpr (std::is_same_v<T, datatypes::xsd::String>) {
            return find_simple(lexical_form, node_storage);
        }

        if constexpr (datatypes::HasFixedId<T>) {
            auto value = T::from_string(lexical_form);

            if constexpr (datatypes::IsInlineable<T>) {
                if (auto const maybe_inlined = T::try_into_inlined(value); maybe_inlined.has_value()) {
                    return Literal::make_inlined_typed_unchecked(*maybe_inlined, T::fixed_id, node_storage);
                }
            }

            if (node_storage.has_specialized_storage_for(T::fixed_id)) {
                auto nid = node_storage.find_id(storage::view::ValueLiteralBackendView{
                        .datatype = T::fixed_id,
                        .value = std::any{value}});
                if (nid.null())
                    return Literal{};
                return Literal{storage::identifier::NodeBackendHandle{nid, node_storage}};
            }
        }

        auto dty = find_datatype_iri(T::datatype_id, node_storage);
        if (dty.null())
            return Literal{};

        auto const needs_escape = lexical_form_needs_escape(lexical_form);

        auto nid = node_storage.find_id(storage::view::LexicalFormLiteralBackendView{
                .datatype_id = dty,
                .lexical_form = lexical_form,
                .language_tag = "",
                .needs_escape = needs_escape});

        if (nid.null())
            return Literal{};
        return Literal{storage::identifier::NodeBackendHandle{nid, node_storage}};
    }

    template<datatypes::LiteralDatatype T>
    [[nodiscard]] bool datatype_eq() const noexcept {
        if constexpr (datatypes::HasFixedId<T>) {
            if (auto const type = this->handle_.node_id().literal_type(); type.is_fixed()) {
                return type == T::fixed_id;
            }

            return false;
        }

        return this->dynamic_datatype_eq_impl(T::identifier);
    }

    [[nodiscard]] bool datatype_eq(IRI const &datatype) const noexcept;

    [[nodiscard]] bool datatype_eq(Literal const &other) const noexcept;

    template<datatypes::LiteralDatatype T>
    [[nodiscard]] Literal as_datatype_eq(storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept {
        if (this->null()) {
            return Literal{};
        }

        return Literal::make_boolean(this->datatype_eq<T>(), select_node_storage(node_storage));
    }

    [[nodiscard]] Literal as_datatype_eq(IRI const &datatype, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    [[nodiscard]] Literal as_datatype_eq(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    [[nodiscard]] Literal cast(IRI const &target, storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    template<datatypes::LiteralDatatype T>
    [[nodiscard]] Literal cast(storage::DynNodeStoragePtr node_storage = keep_node_storage) const {
        return this->cast_impl(T::datatype_id, node_storage);
    }

    template<datatypes::LiteralDatatype T>
    requires (!std::same_as<T, datatypes::xsd::String>)
    std::optional<typename T::cpp_type> cast_to_value() const noexcept {
        using namespace datatypes::registry;
        using namespace datatypes::xsd;

        if (this->null()) {
            return std::nullopt;
        }

        auto const this_dtid = this->datatype_id();
        DatatypeIDView const target_dtid = T::datatype_id;

        if (this_dtid == target_dtid) {
            return this->value<T>();
        }

        if (this_dtid == String::datatype_id) {
            // string -> any
            try {
                return T::from_string(this->lexical_form());
            } catch (...) {
                return std::nullopt;
            }
        }

        if constexpr (std::same_as<T, Boolean>) {
            // any -> bool
            TriBool const t = this->ebv();
            if (t == TriBool::Err)
                return std::nullopt;
            else if (t == TriBool::True)
                return true;
            else
                return false;
        }

        auto const *target_e = DatatypeRegistry::get_entry(target_dtid);
        if (target_e == nullptr) {
            // target not registered
            return std::nullopt;
        }

        if (this_dtid == Boolean::datatype_id && target_e->numeric_ops.has_value()) {
            // bool -> numeric
            if (target_e->numeric_ops->is_impl()) {
                auto value = this->template value<Boolean>() ? target_e->numeric_ops->get_impl().one_value_fptr()
                                                             : target_e->numeric_ops->get_impl().zero_value_fptr();

                return std::any_cast<typename T::cpp_type>(value);
            } else {
                auto const &impl_converter = DatatypeRegistry::get_numeric_op_impl_conversion(*target_e);
                auto const *target_num_impl = DatatypeRegistry::get_numerical_ops(impl_converter.target_type_id);
                RDF4CPP_ASSERT(target_num_impl != nullptr);

                // perform conversion as impl numeric type
                auto const value = this->template value<Boolean>() ? target_num_impl->get_impl().one_value_fptr()
                                                                   : target_num_impl->get_impl().zero_value_fptr();

                // downcast to target
                auto target_value = impl_converter.inverted_convert(value);

                if (!target_value.has_value()) {
                    // not representable as target type
                    return std::nullopt;
                }

                return std::any_cast<typename T::cpp_type>(*target_value);
            }
        }

        auto const *this_e = DatatypeRegistry::get_entry(this_dtid);
        if (this_e == nullptr) {
            // this datatype not registered
            return std::nullopt;
        }

        if (auto const common_conversion = DatatypeRegistry::get_common_type_conversion(this_e->conversion_table, target_e->conversion_table); common_conversion.has_value()) {
            // general cast
            // TODO: if performance is bad split into separate cases for up-, down- and cross-casting to avoid one set of std::any wrapping and unwrapping for the former 2

            auto const common_type_value = common_conversion->convert_lhs(this->value()); // upcast to common
            auto target_value = common_conversion->inverted_convert_rhs(common_type_value); // downcast to target
            if (!target_value.has_value()) {
                // downcast failed
                return std::nullopt;
            }
            return std::any_cast<typename T::cpp_type>(*target_value);
        }

        // no conversion found
        return std::nullopt;
    }

    [[nodiscard]] CowString lexical_form() const noexcept;

    [[nodiscard]] FetchOrSerializeResult fetch_or_serialize_lexical_form(std::string_view &out_lex_form, writer::BufWriterParts writer) const noexcept;

    [[nodiscard]] Literal as_lexical_form(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] CowString simplified_lexical_form() const noexcept;

    [[nodiscard]] FetchOrSerializeResult fetch_or_serialize_simplified_lexical_form(std::string_view &out_lex_form, writer::BufWriterParts writer) const noexcept;

    [[nodiscard]] Literal as_simplified_lexical_form(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] IRI datatype() const noexcept;

    [[nodiscard]] std::string_view language_tag() const noexcept;

    [[nodiscard]] Literal as_language_tag(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] TriBool language_tag_eq(std::string_view lang_tag) const noexcept;

    [[nodiscard]] TriBool language_tag_eq(Literal const &other) const noexcept;

    [[nodiscard]] Literal as_language_tag_eq(std::string_view lang_tag, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    [[nodiscard]] Literal as_language_tag_eq(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    bool serialize(writer::BufWriterParts writer, NodeSerializationOpts opts = NodeSerializationOpts::long_form()) const noexcept;

    bool serialize_lexical_form(writer::BufWriterParts writer) const noexcept;

    bool serialize_simplified_lexical_form(writer::BufWriterParts writer) const noexcept;

    [[nodiscard]] explicit operator std::string() const noexcept;
    friend std::ostream &operator<<(std::ostream &os, const Literal &literal);

    [[nodiscard]] std::any value() const noexcept;

    template<datatypes::LiteralDatatype T>
    typename T::cpp_type value() const {
        if (!this->datatype_eq<T>()) [[unlikely]] {
            throw std::runtime_error{"Literal::value error: incompatible type"};
        }

        if constexpr (std::is_same_v<T, datatypes::xsd::String>) {
            auto const view = this->handle_.literal_backend();
            auto const &lit = view.get_lexical();

            return lit.lexical_form;
        }

        if constexpr (std::is_same_v<T, datatypes::rdf::LangString>) {
            auto const handle = this->is_inlined()
                    ? this->lang_tagged_get_de_inlined().backend_handle()
                    : this->backend_handle();

            auto const view = handle.literal_backend();
            auto const &lit = view.get_lexical();

            return datatypes::registry::LangStringRepr{.lexical_form = lit.lexical_form,
                                                       .language_tag = lit.language_tag};
        }

        if constexpr (datatypes::IsInlineable<T>) {
            if (this->is_inlined()) {
                auto const inlined_value = this->handle_.node_id().literal_id();
                return T::from_inlined(inlined_value);
            }
        }

        auto const backend = handle_.literal_backend();
        return backend.visit(
                [](storage::view::LexicalFormLiteralBackendView const &lexical) noexcept {
                    return T::from_string(lexical.lexical_form);
                },
                [](storage::view::ValueLiteralBackendView const &any) noexcept {
                    RDF4CPP_ASSERT(any.datatype == T::datatype_id);
                    return std::any_cast<typename T::cpp_type>(any.value);
                });
    }

    bool is_literal() const noexcept = delete;
    bool is_variable() const noexcept = delete;
    bool is_blank_node() const noexcept = delete;
    bool is_iri() const noexcept = delete;

    [[nodiscard]] bool is_numeric() const noexcept;
    [[nodiscard]] bool is_timepoint() const noexcept;
    [[nodiscard]] bool is_duration() const noexcept;

    [[nodiscard]] std::partial_ordering compare(Literal const &other) const noexcept;

    [[nodiscard]] std::strong_ordering order(Literal const &other) const noexcept;

    [[nodiscard]] TriBool eq(Literal const &other) const noexcept;
    [[nodiscard]] bool order_eq(Literal const &other) const noexcept;
    [[nodiscard]] TriBool ne(Literal const &other) const noexcept;
    [[nodiscard]] bool order_ne(Literal const &other) const noexcept;
    [[nodiscard]] TriBool lt(Literal const &other) const noexcept;
    [[nodiscard]] bool order_lt(Literal const &other) const noexcept;
    [[nodiscard]] TriBool le(Literal const &other) const noexcept;
    [[nodiscard]] bool order_le(Literal const &other) const noexcept;
    [[nodiscard]] TriBool gt(Literal const &other) const noexcept;
    [[nodiscard]] bool order_gt(Literal const &other) const noexcept;
    [[nodiscard]] TriBool ge(Literal const &other) const noexcept;
    [[nodiscard]] bool order_ge(Literal const &other) const noexcept;

    [[nodiscard]] Literal as_eq(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;
    [[nodiscard]] Literal as_order_eq(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;
    [[nodiscard]] Literal as_ne(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;
    [[nodiscard]] Literal as_order_ne(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;
    [[nodiscard]] Literal as_lt(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;
    [[nodiscard]] Literal as_order_lt(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;
    [[nodiscard]] Literal as_le(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;
    [[nodiscard]] Literal as_order_le(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;
    [[nodiscard]] Literal as_gt(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;
    [[nodiscard]] Literal as_order_gt(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;
    [[nodiscard]] Literal as_ge(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;
    [[nodiscard]] Literal as_order_ge(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    std::partial_ordering operator<=>(Literal const &other) const noexcept;

    bool operator==(Literal const &other) const noexcept;

    [[nodiscard]] Literal add(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    Literal operator+(Literal const &other) const;
    Literal &add_assign(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage);
    Literal &operator+=(Literal const &other);

    [[nodiscard]] Literal sub(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    Literal operator-(Literal const &other) const;
    Literal &sub_assign(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage);
    Literal &operator-=(Literal const &other);

    [[nodiscard]] Literal mul(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    Literal operator*(Literal const &other) const;
    Literal &mul_assign(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage);
    Literal &operator*=(Literal const &other);

    [[nodiscard]] Literal div(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    Literal operator/(Literal const &other) const;
    Literal &div_assign(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage);
    Literal &operator/=(Literal const &other);

    [[nodiscard]] Literal pos(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    Literal operator+() const;

    [[nodiscard]] Literal neg(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    Literal operator-() const;

    [[nodiscard]] Literal abs(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] Literal round(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] Literal floor(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] Literal ceil(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] std::optional<size_t> strlen() const noexcept;

    [[nodiscard]] Literal as_strlen(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] TriBool language_tag_matches_range(std::string_view lang_range) const noexcept;

    [[nodiscard]] Literal as_language_tag_matches_range(std::string_view lang_range, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    [[nodiscard]] Literal as_language_tag_matches_range(Literal const &lang_range, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    [[nodiscard]] TriBool regex_matches(regex::Regex const &pattern) const noexcept;

    [[nodiscard]] Literal as_regex_matches(regex::Regex const &pattern, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    [[nodiscard]] Literal as_regex_matches(Literal const &pattern, Literal const &flags = Literal::make_simple(""), storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    [[nodiscard]] regex::Regex make_regex(Literal const &flags = Literal::make_simple("")) const;

    [[nodiscard]] Literal regex_replace(regex::RegexReplacer const &replacer, storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] Literal regex_replace(Literal const &pattern, Literal const &replacement,
                                        Literal const &flags = Literal::make_simple(""),
                                        storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] TriBool contains(std::string_view needle) const noexcept;

    [[nodiscard]] Literal as_contains(std::string_view needle, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    [[nodiscard]] Literal as_contains(Literal const &needle, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    [[nodiscard]] Literal substr_before(std::string_view needle, storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] Literal substr_before(Literal const &needle, storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] Literal substr_after(std::string_view needle, storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] Literal substr_after(Literal const &needle, storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] TriBool str_starts_with(std::string_view needle) const noexcept;

    [[nodiscard]] Literal as_str_starts_with(std::string_view needle, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    [[nodiscard]] Literal as_str_starts_with(Literal const &needle, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    [[nodiscard]] TriBool str_ends_with(std::string_view needle) const noexcept;

    [[nodiscard]] Literal as_str_ends_with(std::string_view needle, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    [[nodiscard]] Literal as_str_ends_with(Literal const &needle, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    [[nodiscard]] Literal uppercase(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] Literal lowercase(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] Literal concat(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] static Literal encode_for_uri(std::string_view string, storage::DynNodeStoragePtr node_storage = storage::default_node_storage);
    [[nodiscard]] Literal encode_for_uri(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] Literal substr(size_t start,
                                 size_t len = std::string_view::npos,
                                 storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] Literal substr(Literal const &start,
                                 Literal const &len = Literal::make_typed_from_value<datatypes::xsd::Double>(std::numeric_limits<datatypes::xsd::Double::cpp_type>::infinity()),
                                 storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

private:
    [[nodiscard]] Literal hash_with(const char *alg, storage::DynNodeStoragePtr node_storage) const;

public:
    [[nodiscard]] Literal md5(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    [[nodiscard]] Literal sha1(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    [[nodiscard]] Literal sha256(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    [[nodiscard]] Literal sha384(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] Literal sha512(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] static Literal now(storage::DynNodeStoragePtr node_storage = storage::default_node_storage);

    [[nodiscard]] std::optional<Year> year() const noexcept;

    [[nodiscard]] Literal as_year(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] std::optional<std::chrono::month> month() const noexcept;

    [[nodiscard]] Literal as_month(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] std::optional<std::chrono::day> day() const noexcept;

    [[nodiscard]] Literal as_day(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] std::optional<std::chrono::hours> hours() const noexcept;

    [[nodiscard]] Literal as_hours(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] std::optional<std::chrono::minutes> minutes() const noexcept;

    [[nodiscard]] Literal as_minutes(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] std::optional<std::chrono::nanoseconds> seconds() const noexcept;

    [[nodiscard]] Literal as_seconds(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] std::optional<Timezone> timezone() const noexcept;

    [[nodiscard]] Literal as_timezone(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] std::optional<std::string> tz() const noexcept;

    [[nodiscard]] Literal as_tz(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] TriBool ebv() const noexcept;

    [[nodiscard]] Literal as_ebv(storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;

    [[nodiscard]] Literal logical_and(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;
    Literal operator&&(Literal const &other) const noexcept;

    [[nodiscard]] Literal logical_or(Literal const &other, storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;
    Literal operator||(Literal const &other) const noexcept;

    [[nodiscard]] Literal logical_not(storage::DynNodeStoragePtr node_storage = keep_node_storage) const noexcept;
    Literal operator!() const noexcept;

    friend struct Node;
    friend Literal lang_matches(Literal const &lang_tag, Literal const &lang_range, storage::DynNodeStoragePtr node_storage) noexcept;

    [[nodiscard]] static Literal math_pi(storage::DynNodeStoragePtr node_storage = storage::default_node_storage);
    [[nodiscard]] Literal math_exp(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    [[nodiscard]] Literal math_exp10(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    [[nodiscard]] Literal math_log(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    [[nodiscard]] Literal math_log10(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    [[nodiscard]] Literal math_pow(Literal exp, storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    [[nodiscard]] Literal math_sqrt(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;

    [[nodiscard]] Literal math_sin(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    [[nodiscard]] Literal math_cos(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    [[nodiscard]] Literal math_tan(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    [[nodiscard]] Literal math_asin(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    [[nodiscard]] Literal math_acos(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    [[nodiscard]] Literal math_atan(storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
    [[nodiscard]] Literal math_atan2(Literal y, storage::DynNodeStoragePtr node_storage = keep_node_storage) const;
};

[[nodiscard]] std::string normalize_unicode(std::string_view utf8);

[[nodiscard]] bool lang_matches(std::string_view lang_tag, std::string_view lang_range) noexcept;

[[nodiscard]] Literal lang_matches(Literal const &lang_tag, Literal const &lang_range, storage::DynNodeStoragePtr node_storage = keep_node_storage) noexcept;

inline namespace shorthands {

Literal operator""_xsd_string(char const *str, size_t len);

Literal operator""_xsd_double(long double d);
Literal operator""_xsd_float(long double d) noexcept;

Literal operator""_xsd_decimal(char const *str, size_t len);

Literal operator""_xsd_integer(unsigned long long int i);

Literal operator""_xsd_byte(unsigned long long int i) noexcept;
Literal operator""_xsd_ubyte(unsigned long long int i) noexcept;

Literal operator""_xsd_short(unsigned long long int i) noexcept;
Literal operator""_xsd_ushort(unsigned long long int i) noexcept;

Literal operator""_xsd_int(unsigned long long int i) noexcept;
Literal operator""_xsd_uint(unsigned long long int i) noexcept;

Literal operator""_xsd_long(unsigned long long int i);
Literal operator""_xsd_ulong(unsigned long long int i);

}  // namespace shorthands

struct LiteralOrderByLess {
    bool operator()(Literal lhs, Literal rhs) const noexcept {
        return lhs.order_lt(rhs);
    }
};

struct LiteralOrderByGreater {
    bool operator()(Literal lhs, Literal rhs) const noexcept {
        return lhs.order_gt(rhs);
    }
};

}  // namespace rdf4cpp

template<>
struct std::hash<rdf4cpp::Literal> {
    inline size_t operator()(rdf4cpp::Literal const &v) const noexcept {
        return std::hash<rdf4cpp::Node>()(v);
    }
};

template<>
struct std::formatter<rdf4cpp::Literal> : std::formatter<rdf4cpp::Node> {
    auto format(rdf4cpp::Literal n, format_context &ctx) const -> decltype(ctx.out());
};

#endif  //RDF4CPP_LITERAL_HPP