/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2020 Marco Trevisan #pragma once #include #include #include // for isnan #include #include #include #include #include #include #include #include #include // for UniqueChars #include "gi/gtype.h" #include "gi/value.h" #include "gjs/jsapi-util.h" #include "gjs/macros.h" namespace Gjs { template struct TypeWrapper { constexpr TypeWrapper() : m_value(0) {} explicit constexpr TypeWrapper(T v) : m_value(v) {} constexpr operator T() const { return m_value; } constexpr operator T() { return m_value; } private: T m_value; }; namespace JsValueHolder { template constexpr bool comparable_types() { return std::is_arithmetic_v == std::is_arithmetic_v && std::is_signed_v == std::is_signed_v; } template constexpr bool type_fits() { if constexpr (comparable_types()) { return (std::is_integral_v == std::is_integral_v && std::numeric_limits::max() <= std::numeric_limits::max() && std::numeric_limits::lowest() >= std::numeric_limits::lowest()); } return false; } /* The tag is needed to disambiguate types such as gboolean and GType * which are in fact typedef's of other generic types. * Setting a tag for a type allows to perform proper specialization. */ template constexpr auto get_strict() { if constexpr (TAG != GI_TYPE_TAG_VOID) { if constexpr (std::is_same_v && TAG == GI_TYPE_TAG_GTYPE) return GType{}; else if constexpr (std::is_same_v && TAG == GI_TYPE_TAG_BOOLEAN) return gboolean{}; else return; } else { if constexpr (std::is_same_v) return char32_t{}; else if constexpr (type_fits()) return int32_t{}; else if constexpr (type_fits()) return uint32_t{}; else if constexpr (type_fits()) return int64_t{}; else if constexpr (type_fits()) return uint64_t{}; else if constexpr (type_fits()) return double{}; else return T{}; } } template constexpr auto get_relaxed() { if constexpr (std::is_same_v || std::is_same_v) return TypeWrapper{}; else if constexpr (type_fits()) return int32_t{}; else if constexpr (type_fits()) return uint32_t{}; else if constexpr (std::is_arithmetic_v) return double{}; else return T{}; } template using Strict = decltype(JsValueHolder::get_strict()); template using Relaxed = decltype(JsValueHolder::get_relaxed()); } // namespace JsValueHolder template > constexpr bool type_has_js_getter() { return std::is_same_v; } /* Avoid implicit conversions */ template GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c(JSContext*, const JS::HandleValue&, T*) = delete; template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, int32_t* out) { return JS::ToInt32(cx, value, out); } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, uint32_t* out) { return JS::ToUint32(cx, value, out); } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, char32_t* out) { uint32_t tmp; bool retval = JS::ToUint32(cx, value, &tmp); *out = tmp; return retval; } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, int64_t* out) { if (value.isBigInt()) { *out = JS::ToBigInt64(value.toBigInt()); return true; } return JS::ToInt64(cx, value, out); } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, uint64_t* out) { if (value.isBigInt()) { *out = JS::ToBigUint64(value.toBigInt()); return true; } return JS::ToUint64(cx, value, out); } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, double* out) { return JS::ToNumber(cx, value, out); } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext*, const JS::HandleValue& value, gboolean* out) { *out = !!JS::ToBoolean(value); return true; } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, GType* out) { if (!value.isObject()) return false; JS::RootedObject elem_obj(cx); elem_obj = &value.toObject(); if (!gjs_gtype_get_actual_gtype(cx, elem_obj, out)) return false; if (*out == G_TYPE_INVALID) return false; return true; } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, GValue* out) { *out = G_VALUE_INIT; return gjs_value_to_g_value(cx, value, out); } template <> GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c( JSContext* cx, const JS::HandleValue& value, char** out) { JS::UniqueChars tmp_result = gjs_string_to_utf8(cx, value); if (!tmp_result) return false; *out = g_strdup(tmp_result.get()); return true; } template [[nodiscard]] inline constexpr BigT max_safe_big_number() { return (BigT(1) << std::numeric_limits::digits) - 1; } template [[nodiscard]] inline constexpr BigT min_safe_big_number() { if constexpr (std::is_signed_v) return -(max_safe_big_number()); return std::numeric_limits::lowest(); } template GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked( JSContext* cx, const JS::HandleValue& value, T* out, bool* out_of_range) { static_assert(std::numeric_limits::max() >= std::numeric_limits::max() && std::numeric_limits::lowest() <= std::numeric_limits::lowest(), "Container can't contain wanted type"); if constexpr (std::is_same_v || std::is_same_v) { if (out_of_range) { JS::BigInt* bi = nullptr; *out_of_range = false; if (value.isBigInt()) { bi = value.toBigInt(); } else if (value.isNumber()) { double number = value.toNumber(); if (!std::isfinite(number)) { *out = 0; return true; } number = std::trunc(number); bi = JS::NumberToBigInt(cx, number); if (!bi) return false; } if (bi) { *out_of_range = Gjs::bigint_is_out_of_range(bi, out); return true; } } } if constexpr (std::is_same_v) return js_value_to_c(cx, value, out); // JS::ToIntNN() converts undefined, NaN, infinity to 0 if constexpr (std::is_integral_v) { if (value.isUndefined() || (value.isDouble() && !std::isfinite(value.toDouble()))) { *out = 0; return true; } } if constexpr (std::is_arithmetic_v) { bool ret = js_value_to_c(cx, value, out); if (out_of_range) { // Infinity and NaN preserved between floating point types if constexpr (std::is_floating_point_v && std::is_floating_point_v) { if (!std::isfinite(*out)) { *out_of_range = false; return ret; } } *out_of_range = (*out > static_cast(std::numeric_limits::max()) || *out < static_cast(std::numeric_limits::lowest())); if constexpr (std::is_integral_v && std::is_floating_point_v) *out_of_range |= std::isnan(*out); } return ret; } } template GJS_JSAPI_RETURN_CONVENTION inline bool js_value_to_c_checked( JSContext* cx, const JS::HandleValue& value, TypeWrapper* out, bool* out_of_range) { static_assert(std::is_integral_v); WantedType wanted_out; if (!js_value_to_c_checked(cx, value, &wanted_out, out_of_range)) return false; *out = TypeWrapper{wanted_out}; return true; } } // namespace Gjs