PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
na.hpp
Go to the documentation of this file.
1#pragma once
2#include <cmath>
3#include <cstdint>
4#include <limits>
5#include <string>
6#include <type_traits>
7#include <utility>
8
9namespace pineforge {
10
11namespace na_detail {
12
13template <typename T, typename = void>
14struct HasNaMember : std::false_type {};
15
16template <typename T>
17struct HasNaMember<T, std::void_t<
18 decltype(static_cast<bool>(std::declval<const T&>().is_na()))>>
19 : std::true_type {};
20
21template <typename T, typename = void>
22struct HasNaFactory : std::false_type {};
23
24template <typename T>
25struct HasNaFactory<T, std::void_t<decltype(T::na())>>
26 : std::is_same<decltype(T::na()), T> {};
27
28// Only a more specific ADL overload may supply a free missingness protocol.
29// This deleted candidate prevents redispatch to the generic engine overload
30// for types whose associated namespaces include pineforge.
31template <typename T>
32void is_na(const T&) = delete;
33
34template <typename T>
35inline auto call_adl_is_na(const T& value)
36 -> decltype(static_cast<bool>(is_na(value))) {
37 return static_cast<bool>(is_na(value));
38}
39
40template <typename T, typename = void>
41struct HasAdlIsNa : std::false_type {};
42
43template <typename T>
44struct HasAdlIsNa<T, std::void_t<
45 decltype(call_adl_is_na(std::declval<const T&>()))>> : std::true_type {};
46
47} // namespace na_detail
48
49// Nullable handles use T::na() when supplied alongside an is_na() member.
50// Otherwise T{} is the default na handle for engine maps/matrices and
51// generated array-history values, or the ordinary default for value storage.
52// Explicit specializations below still take precedence for primitive types.
53template<typename T>
54inline T na() {
57 return T::na();
58 } else {
59 static_assert(std::is_default_constructible_v<T>,
60 "na<T>() requires a default constructor or an is_na() member and T::na()");
61 return T{};
62 }
63}
64template<> inline double na<double>() { return std::numeric_limits<double>::quiet_NaN(); }
65template<> inline int na<int>() { return std::numeric_limits<int>::min(); }
66template<> inline int64_t na<int64_t>() { return std::numeric_limits<int64_t>::min(); }
67template<> inline bool na<bool>() { return false; }
68
69inline bool is_na(double v) { return std::isnan(v); }
70
71// Generic integer is_na: covers int, long, long long, int64_t, etc.
72template<typename T, typename = std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>>>
73inline bool is_na(T v) { return v == std::numeric_limits<T>::min(); }
74
75// Store arrays have an explicit unbound state (no store behind them: default
76// constructed or moved from), unlike std::vector values. Forward declaration
77// avoids an include cycle with array.hpp.
78template <class T> class ScriptArray;
79
80template <class T>
81inline bool is_na(const ScriptArray<T>& value) {
82 return !value.store_backed();
83}
84
85// A string na is stored as the empty string: na<std::string>() is "" (the
86// generic na<T>() above), and generated code has always read "" as na (the
87// codegen warns that TradingView tells an authored "" from a missing string,
88// the engine cannot). So the one string observer is "is it empty". It is a
89// non-template overload, which wins over the generic template below. A type
90// that converts implicitly to `const std::string&` is read through that
91// conversion (the generic template is constrained away from it). String views
92// and other engine string handles (std::string_view, ScriptString) are NOT
93// std::string: they are never na.
94inline bool is_na(const std::string& value) { return value.empty(); }
95
96// Preserve the existing scalar overloads, including bool/float conversions
97// and user-defined numeric conversions, rather than intercepting them here.
98// The generic template below is the answer for everything else. LIMIT, stated
99// here on the overload itself: a plain value container (std::vector<T, A>,
100// std::map, std::unordered_map) cannot hold an na reference, so is_na of one is
101// ALWAYS false, even for an empty container, and std::vector cannot tell an na
102// array from an empty one (a nullable-array representation is a separate,
103// later change).
104template <typename T, std::enable_if_t<
105 !std::is_arithmetic_v<T> &&
106 !std::is_convertible_v<T, double> &&
107 !std::is_convertible_v<T&, double> &&
108 !std::is_convertible_v<const T&, double> &&
109 !std::is_convertible_v<const T&, const std::string&>, int> = 0>
110inline bool is_na(const T& value) {
111 if constexpr (na_detail::HasNaMember<T>::value) {
112 return static_cast<bool>(value.is_na());
113 } else if constexpr (na_detail::HasAdlIsNa<T>::value) {
114 return na_detail::call_adl_is_na(value);
115 } else {
116 // Value storage without a nullable ID is always present. In particular,
117 // std::vector<T, A>, std::map and std::unordered_map cannot hold an na
118 // reference, so std::vector<T, A> cannot tell an na array from an empty
119 // one: even an empty container answers false (the nullable-array
120 // representation is a separate, later change). The string rule is the
121 // overload above. A plain
122 // UDT without a missingness protocol follows the same rule; fields and
123 // elements are never inspected recursively. Its na<T>() remains T{}.
124 return false;
125 }
126}
127
128} // namespace pineforge
void is_na(const T &)=delete
auto call_adl_is_na(const T &value) -> decltype(static_cast< bool >(is_na(value)))
Definition na.hpp:35
int64_t na< int64_t >()
Definition na.hpp:66
double na< double >()
Definition na.hpp:64
bool is_na(const Line &h)
Definition drawing.hpp:58
T na()
Definition na.hpp:54
bool na< bool >()
Definition na.hpp:67
int na< int >()
Definition na.hpp:65