PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
script_string.hpp
Go to the documentation of this file.
1#pragma once
2
4#include <pineforge/na.hpp>
5
6#include <cstddef>
7#include <cstdint>
8#include <array>
9#include <functional>
10#include <iterator>
11#include <memory>
12#include <string>
13#include <string_view>
14#include <type_traits>
15#include <utility>
16
17namespace pineforge {
18
19// The generated strategy owns the Store. A scope binds literal and legacy
20// std::string construction during a callback; scopes nest and are thread-local.
21// Destroy all strings (including checkpoints and record pins) before the Store.
23public:
24 explicit ScriptStringScope(detail::o1::Store& store) noexcept;
29private:
30 detail::o1::Store* previous_;
31};
32
33// Byte strings, exactly like the former std::string script values. Payloads
34// and ownership metadata are paged by Store, not held in a heap-side directory.
35// A value is a slice of an append-only payload: appending at its high-water
36// mark can extend that payload even while old values/slices retain its prefix.
37// Appending to a different prefix forks through fixed-size scratch pages.
38// Store transactions cover payload, allocation and ownership mutations. Drop
39// tentative values/restore copied script handles before rolling the Store back.
41public:
42 using size_type = std::uint64_t;
43 static constexpr size_type npos = UINT64_MAX;
44 static constexpr std::size_t chunk_bytes = 4096;
45
46 // External record codecs pin a Handle, write these three integers, and
47 // unpin exactly once when the record/version is no longer reachable.
53
54 ScriptString() noexcept = default;
55 explicit ScriptString(detail::o1::Store&, std::string_view bytes = {},
56 size_type proven_byte_bound = 0);
57 ScriptString(const char*);
58 ScriptString(const std::string&);
59 ScriptString(std::string_view);
62 ScriptString& operator=(const ScriptString&);
65
66 void swap(ScriptString&) noexcept;
67 size_type size() const noexcept { return handle_.length; }
68 size_type length() const noexcept { return size(); }
69 bool empty() const noexcept { return size() == 0; }
70 detail::o1::Store* store() const noexcept { return store_; }
71 // A compatibility container with a hidden Store may lend its lifetime to
72 // an escaping value. Explicitly borrowed Stores still belong to their run
73 // owner. This token is physical ownership, never serialized or hashed.
74 void keep_store_alive(std::shared_ptr<void> owner) noexcept { owner_ = std::move(owner); }
75
76 // This reserves backing bytes, never a resident buffer. The bound is a
77 // hint, not a limit: append continues exactly if the proof was too narrow.
78 void reserve(size_type proven_byte_bound);
80 ScriptString& append(std::string_view);
81 ScriptString& append(const char* value) { return append(std::string_view(value)); }
82 ScriptString& append(const std::string& value) { return append(std::string_view(value)); }
83 ScriptString& operator+=(const ScriptString& value) { return append(value); }
84 ScriptString& operator+=(std::string_view value) { return append(value); }
85 ScriptString& operator+=(const char* value) { return append(std::string_view(value)); }
86 ScriptString& operator+=(const std::string& value) { return append(std::string_view(value)); }
87 ScriptString& operator+=(char value) { return append(std::string_view(&value, 1)); }
88 // Explicit ownership changes propagate backing/rollback-only failures.
89 // Destruction is best-effort during a failed transaction; its owner must
90 // roll back before continuing and restore the copied script handles.
91 void clear();
92
93 char operator[](size_type) const;
94 char at(size_type) const;
95 ScriptString substr(size_type pos = 0, size_type count = npos) const;
96 std::size_t read(size_type offset, void* destination, std::size_t bytes) const;
97 int compare(const ScriptString&) const;
98 int compare(std::string_view) const;
99 int compare(const char* value) const { return compare(std::string_view(value)); }
100 int compare(const std::string& value) const { return compare(std::string_view(value)); }
101 int compare(size_type pos, size_type count, const ScriptString&) const;
102 size_type find(const ScriptString&, size_type pos = 0) const;
103 size_type find(std::string_view, size_type pos = 0) const;
104 size_type find(const char* value, size_type pos = 0) const { return find(std::string_view(value), pos); }
105 size_type find(const std::string& value, size_type pos = 0) const { return find(std::string_view(value), pos); }
106 size_type find(char, size_type pos = 0) const;
107
108 // Streaming equivalent of this platform's std::hash<std::string>.
109 std::size_t hash() const;
110 // Canonical state hashing: the logical byte length followed by exact
111 // bytes. Store ids, capacity and reference counts are physical metadata.
112 template <class Sink>
113 void hash_into(Sink& sink) const {
114 sink.u(size());
115 std::array<char, chunk_bytes> buffer{};
116 for (size_type offset = 0; offset < size();) {
117 const auto n = read(offset, buffer.data(), buffer.size());
118 sink.bytes(buffer.data(), n);
119 offset += n;
120 }
121 }
122 Handle pin() const;
125
126 // Explicit compatibility boundary. Never use it in a string operation.
127 std::string to_std_string() const;
128 template <class Function>
129 void with_std_string(Function&& function) {
130 std::string temporary = to_std_string();
131 std::forward<Function>(function)(temporary);
132 ScriptString replacement(required_store(), std::string_view(temporary));
133 replacement.owner_ = owner_;
134 swap(replacement);
135 }
136
137private:
138 detail::o1::Store* store_ = nullptr;
139 Handle handle_{};
140 std::shared_ptr<void> owner_;
141 detail::o1::Store& required_store() const;
142 void retain() const;
143 void release();
144 void prepare_append(size_type extra);
145};
146
147inline void swap(ScriptString& a, ScriptString& b) noexcept { a.swap(b); }
148inline ScriptString operator+(ScriptString a, const ScriptString& b) { return a.append(b); }
149inline ScriptString operator+(ScriptString a, std::string_view b) { return a.append(b); }
150inline ScriptString operator+(ScriptString a, const char* b) { return a.append(std::string_view(b)); }
151inline ScriptString operator+(ScriptString a, const std::string& b) { return a.append(std::string_view(b)); }
152inline ScriptString operator+(ScriptString a, char b) { a += b; return a; }
153inline ScriptString operator+(std::string_view a, const ScriptString& b) {
154 ScriptString result(b);
155 result.clear();
156 result.append(a);
157 return result.append(b);
158}
159inline ScriptString operator+(const std::string& a, const ScriptString& b) {
160 return std::string_view(a) + b;
161}
162inline ScriptString operator+(const char* a, const ScriptString& b) {
163 return std::string_view(a) + b;
164}
165inline ScriptString operator+(char a, const ScriptString& b) {
166 return std::string_view(&a, 1) + b;
167}
168inline bool operator==(const ScriptString& a, const ScriptString& b) { return a.compare(b) == 0; }
169inline bool operator!=(const ScriptString& a, const ScriptString& b) { return !(a == b); }
170inline bool operator<(const ScriptString& a, const ScriptString& b) { return a.compare(b) < 0; }
171inline bool operator>(const ScriptString& a, const ScriptString& b) { return b < a; }
172inline bool operator<=(const ScriptString& a, const ScriptString& b) { return !(b < a); }
173inline bool operator>=(const ScriptString& a, const ScriptString& b) { return !(a < b); }
174inline bool operator==(const ScriptString& a, std::string_view b) { return a.compare(b) == 0; }
175inline bool operator==(std::string_view a, const ScriptString& b) { return b == a; }
176inline bool operator!=(const ScriptString& a, std::string_view b) { return !(a == b); }
177inline bool operator!=(std::string_view a, const ScriptString& b) { return !(b == a); }
178inline bool operator==(const ScriptString& a, const char* b) { return a == std::string_view(b); }
179inline bool operator==(const char* a, const ScriptString& b) { return b == std::string_view(a); }
180inline bool operator!=(const ScriptString& a, const char* b) { return !(a == b); }
181inline bool operator!=(const char* a, const ScriptString& b) { return !(a == b); }
182inline bool operator==(const ScriptString& a, const std::string& b) { return a == std::string_view(b); }
183inline bool operator==(const std::string& a, const ScriptString& b) { return b == std::string_view(a); }
184inline bool operator!=(const ScriptString& a, const std::string& b) { return !(a == b); }
185inline bool operator!=(const std::string& a, const ScriptString& b) { return !(a == b); }
186
187// A legacy/literal operand is a borrowed byte view, not a temporary stored
188// value. In particular an ordering test cannot allocate another text payload.
189template <class Text, std::enable_if_t<std::is_convertible_v<const Text&, std::string_view>, int> = 0>
190inline bool operator<(const ScriptString& a, const Text& b) { return a.compare(std::string_view(b)) < 0; }
191template <class Text, std::enable_if_t<std::is_convertible_v<const Text&, std::string_view>, int> = 0>
192inline bool operator<(const Text& a, const ScriptString& b) { return b.compare(std::string_view(a)) > 0; }
193template <class Text, std::enable_if_t<std::is_convertible_v<const Text&, std::string_view>, int> = 0>
194inline bool operator>(const ScriptString& a, const Text& b) { return a.compare(std::string_view(b)) > 0; }
195template <class Text, std::enable_if_t<std::is_convertible_v<const Text&, std::string_view>, int> = 0>
196inline bool operator>(const Text& a, const ScriptString& b) { return b.compare(std::string_view(a)) < 0; }
197template <class Text, std::enable_if_t<std::is_convertible_v<const Text&, std::string_view>, int> = 0>
198inline bool operator<=(const ScriptString& a, const Text& b) { return a.compare(std::string_view(b)) <= 0; }
199template <class Text, std::enable_if_t<std::is_convertible_v<const Text&, std::string_view>, int> = 0>
200inline bool operator<=(const Text& a, const ScriptString& b) { return b.compare(std::string_view(a)) >= 0; }
201template <class Text, std::enable_if_t<std::is_convertible_v<const Text&, std::string_view>, int> = 0>
202inline bool operator>=(const ScriptString& a, const Text& b) { return a.compare(std::string_view(b)) >= 0; }
203template <class Text, std::enable_if_t<std::is_convertible_v<const Text&, std::string_view>, int> = 0>
204inline bool operator>=(const Text& a, const ScriptString& b) { return b.compare(std::string_view(a)) <= 0; }
205
206// Lazy, repeatable split range. Both the payload and arbitrarily many result
207// boundaries stay out of the heap. An external array consumer can ingest this
208// range (or for_each) without ever creating a vector of parts. Each yielded
209// slice is an independent value and survives destruction/mutation of the source.
211public:
212 class iterator {
213 public:
214 using iterator_category = std::input_iterator_tag;
216 using difference_type = std::ptrdiff_t;
217 using pointer = void;
219 iterator() = default;
222 iterator operator++(int) { auto old = *this; ++*this; return old; }
223 friend bool operator==(const iterator& a, const iterator& b) {
224 return a.owner_ == b.owner_ && a.start_ == b.start_;
225 }
226 friend bool operator!=(const iterator& a, const iterator& b) { return !(a == b); }
227 private:
228 friend class ScriptStringParts;
230 const ScriptStringParts* owner_ = nullptr;
233 };
235 : source_(std::move(source)), separator_(std::move(separator)) {}
236 iterator begin() const { return iterator(this, 0); }
237 iterator end() const { return iterator(this, ScriptString::npos); }
240 template <class Function> void for_each(Function&& emit) const {
241 for (auto part : *this) emit(std::move(part));
242 }
243private:
244 ScriptString source_;
245 ScriptString separator_;
246};
247
248// Same prefix conversion/exception policy as generated std::stod followed by
249// invalid_argument/out_of_range -> na<double>(), using fixed numeric scratch.
251
252} // namespace pineforge
253
254namespace std {
255template <> struct hash<pineforge::ScriptString> {
256 size_t operator()(const pineforge::ScriptString& value) const { return value.hash(); }
257};
258} // namespace std
friend bool operator!=(const iterator &a, const iterator &b)
friend bool operator==(const iterator &a, const iterator &b)
std::input_iterator_tag iterator_category
void for_each(Function &&emit) const
ScriptStringParts(ScriptString source, ScriptString separator)
ScriptString operator[](ScriptString::size_type) const
ScriptString::size_type size() const
ScriptStringScope(const ScriptStringScope &)=delete
static detail::o1::Store & current()
ScriptStringScope(detail::o1::Store &store) noexcept
ScriptStringScope & operator=(const ScriptStringScope &)=delete
int compare(const char *value) const
ScriptString substr(size_type pos=0, size_type count=npos) const
size_type length() const noexcept
ScriptString & operator+=(std::string_view value)
ScriptString & append(const ScriptString &)
ScriptString & operator+=(char value)
static constexpr std::size_t chunk_bytes
size_type find(std::string_view, size_type pos=0) const
size_type find(const ScriptString &, size_type pos=0) const
int compare(std::string_view) const
ScriptString & append(const char *value)
static ScriptString from_handle(detail::o1::Store &, Handle)
std::string to_std_string() const
ScriptString & operator+=(const ScriptString &value)
size_type find(char, size_type pos=0) const
void hash_into(Sink &sink) const
ScriptString(const char *)
bool empty() const noexcept
void swap(ScriptString &) noexcept
ScriptString & operator+=(const std::string &value)
int compare(const ScriptString &) const
int compare(const std::string &value) const
size_type find(const char *value, size_type pos=0) const
ScriptString(const std::string &)
ScriptString(ScriptString &&) noexcept
size_type size() const noexcept
void with_std_string(Function &&function)
size_type find(const std::string &value, size_type pos=0) const
std::size_t hash() const
ScriptString & append(const std::string &value)
char operator[](size_type) const
detail::o1::Store * store() const noexcept
ScriptString(const ScriptString &)
static void unpin(detail::o1::Store &, Handle)
void keep_store_alive(std::shared_ptr< void > owner) noexcept
std::size_t read(size_type offset, void *destination, std::size_t bytes) const
void reserve(size_type proven_byte_bound)
char at(size_type) const
ScriptString(std::string_view)
ScriptString() noexcept=default
int compare(size_type pos, size_type count, const ScriptString &) const
ScriptString & operator+=(const char *value)
static constexpr size_type npos
ScriptString & append(std::string_view)
std::uint64_t Id
Definition o1_store.hpp:47
constexpr Id kNullId
Definition o1_store.hpp:48
ScriptString operator+(ScriptString a, const ScriptString &b)
double str_tonumber(const ScriptString &)
bool operator<=(const ScriptString &a, const ScriptString &b)
bool operator!=(const ScriptString &a, const ScriptString &b)
bool operator>(const ScriptString &a, const ScriptString &b)
bool operator<(const ScriptString &a, const ScriptString &b)
bool operator>=(const ScriptString &a, const ScriptString &b)
void swap(ScriptString &a, ScriptString &b) noexcept
bool operator==(const ScriptString &a, const ScriptString &b)
size_t operator()(const pineforge::ScriptString &value) const