nk 1.0.0
C++ Numerical Computing Runtime
Loading...
Searching...
No Matches
value.hpp
1#pragma once
2
3#include <nk/value/heap_object.hpp>
4#include <nk/value/span.hpp>
5
6#include <array>
7#include <atomic>
8#include <complex>
9#include <cstddef>
10#include <cstdint>
11#include <cstring>
12#include <initializer_list>
13#include <map>
14#include <memory_resource>
15#include <string>
16#include <vector>
17
18namespace nk {
19
20using Complex = std::complex<double>;
21
22namespace detail {
26template<class T> struct value_type_for;
27
28template<> struct value_type_for<double> { static constexpr ValueType type = ValueType::DOUBLE; };
29template<> struct value_type_for<float> { static constexpr ValueType type = ValueType::SINGLE; };
30template<> struct value_type_for<std::int8_t> { static constexpr ValueType type = ValueType::INT8; };
31template<> struct value_type_for<std::int16_t> { static constexpr ValueType type = ValueType::INT16; };
32template<> struct value_type_for<std::int32_t> { static constexpr ValueType type = ValueType::INT32; };
33template<> struct value_type_for<std::int64_t> { static constexpr ValueType type = ValueType::INT64; };
34template<> struct value_type_for<std::uint8_t> { static constexpr ValueType type = ValueType::UINT8; };
35template<> struct value_type_for<std::uint16_t> { static constexpr ValueType type = ValueType::UINT16; };
36template<> struct value_type_for<std::uint32_t> { static constexpr ValueType type = ValueType::UINT32; };
37template<> struct value_type_for<std::uint64_t> { static constexpr ValueType type = ValueType::UINT64; };
38template<> struct value_type_for<std::complex<double>> { static constexpr ValueType type = ValueType::COMPLEX; };
39} // namespace detail
40
41// ============================================================
42// Value — 16-byte tagged pointer value
43//
44// Layout:
45// double scalar_ (8 bytes) — inline scalar value
46// HeapObject *heap_ (8 bytes) — tag / heap pointer
47//
48// Encoding:
49// heap_ == nullptr → inline double scalar (value in scalar_)
50// heap_ == emptyTag() → empty matrix / uninitialised slot
51// heap_ == logicalTrueTag() → logical scalar true
52// heap_ == logicalFalseTag() → logical scalar false
53// heap_ == deletedTag() → tombstone for indexed-delete
54// otherwise → heap-allocated object
55// ============================================================
60class Value
61{
62public:
65 ~Value();
66
67 Value(const Value &other);
68 Value &operator=(const Value &other);
69 Value(Value &&other) noexcept;
70 Value &operator=(Value &&other) noexcept;
71
72 void swap(Value &other) noexcept;
73
74 // ── Ergonomic ctors — owning, COPY ────────────────────────
75 //
76 // Build a 1×N row vector from common C++ sources. All four COPY
77 // the input bytes into a fresh buffer on `mr` (nullptr → process
78 // default resource). Reshape afterwards if you need a matrix / ND.
79 //
80 // T must be one of: double, float, int8..int64, uint8..uint64,
81 // std::complex<double>. Other types fail to compile via
82 // detail::value_type_for.
83 //
84 // The initializer_list overload is non-template (always double) so
85 // that `Value v = {1, 2, 3};` works without int → DOUBLE surprises
86 // from template type deduction. For other types use the vector /
87 // array / Span overloads.
88
91 Value(std::initializer_list<double> il,
92 std::pmr::memory_resource *mr = nullptr);
93
95 template<class T>
97 std::pmr::memory_resource *mr = nullptr);
98
100 template<class T>
101 Value(const std::vector<T> &v,
102 std::pmr::memory_resource *mr = nullptr);
103
105 template<class T, std::size_t N>
106 Value(const std::array<T, N> &a,
107 std::pmr::memory_resource *mr = nullptr);
108
120 static Value view(const void *data,
121 ValueType type,
122 Dims dims);
123
136 static const Value Empty;
137
138 // ── Factories — real ─────────────────────────────────────
139 static Value scalar(double v, std::pmr::memory_resource *mr = nullptr);
140 static Value logicalScalar(bool v, std::pmr::memory_resource *mr = nullptr);
141 static Value matrix(size_t rows,
142 size_t cols,
143 ValueType t = ValueType::DOUBLE,
144 std::pmr::memory_resource *mr = nullptr);
145 static Value matrix3d(size_t rows,
146 size_t cols,
147 size_t pages,
148 ValueType t = ValueType::DOUBLE,
149 std::pmr::memory_resource *mr = nullptr);
150 // ND factory. dims[0..nd) is the shape, column-major. For nd <= 3 the
151 // result is observably equivalent to matrix() / matrix3d(); for nd > 3
152 // the heap object's Dims uses SBO storage (inline up to 4D, heap for
153 // 5D+). Allocates dims[0]*dims[1]*...*dims[nd-1] * elementSize(t)
154 // bytes and zero-fills.
155 static Value matrixND(const size_t *dims,
156 int nd,
157 ValueType t = ValueType::DOUBLE,
158 std::pmr::memory_resource *mr = nullptr);
159 static Value fromString(const std::string &s, std::pmr::memory_resource *mr = nullptr);
160 static Value cell(size_t rows, size_t cols, std::pmr::memory_resource *mr = nullptr);
161 static Value cell3D(size_t rows, size_t cols, size_t pages, std::pmr::memory_resource *mr = nullptr);
162 // ND CELL constructor — picks 2D / 3D / true-ND backing as needed.
163 static Value cellND(const size_t *dims, int nd, std::pmr::memory_resource *mr = nullptr);
164 // CSL (comma-separated list) — a transient n-element value-list (see ValueType::CSL).
165 // Storage mirrors a 1xn CELL (HeapObject::cellData); only the type tag differs.
166 static Value csl(size_t n, std::pmr::memory_resource *mr = nullptr);
167 static Value structure(std::pmr::memory_resource *mr = nullptr);
168 // N×M struct array — every element is an independent map<string, Value>.
169 // numel() == rows*cols == size of the underlying structArray vector.
170 // Field set is allowed to differ across elements; MATLAB-compatible
171 // builtins (struct, fieldnames, ...) maintain the uniform-field
172 // invariant on creation.
173 static Value structArray(size_t rows, size_t cols,
174 std::pmr::memory_resource *mr = nullptr);
175 static Value funcHandle(const std::string &name, std::pmr::memory_resource *mr = nullptr);
176 // ── OBJECT (class instance) — see object.hpp / object_model.md ──
177 // Wrap pre-built instance state as an instance of `className`.
178 // `isHandle` is the class's handle flag and drives COW (clone shares
179 // state for handle classes, deep-copies for value classes).
180 static Value object(const std::string &className,
181 std::shared_ptr<ObjectState> state, bool isHandle,
182 std::pmr::memory_resource *mr = nullptr);
183 static Value objectArray(const std::string &className, const Dims &dims,
184 std::vector<std::shared_ptr<ObjectState>> states, bool isHandle,
185 std::pmr::memory_resource *mr = nullptr);
190 [[deprecated("Use Value::Empty (MATLAB 0x0 DOUBLE) or Value() (unset sentinel)")]]
191 static Value empty();
192 static Value deleted();
193
194 // ── Factories — compound operations ────────────────────────
195 // Colon range: start:stop (step=1) or start:step:stop
196 static Value colonRange(double start, double stop, std::pmr::memory_resource *mr = nullptr);
197 static Value colonRange(double start, double step, double stop, std::pmr::memory_resource *mr = nullptr);
198 // Typed colon range: same as above but output is class `t`
199 // (single / int8..uint64 / logical). Values are computed in double
200 // then cast to `t`. DOUBLE → identical to the untyped overload.
201 static Value colonRangeTyped(double start, double stop, ValueType t,
202 std::pmr::memory_resource *mr = nullptr);
203 static Value colonRangeTyped(double start, double step, double stop,
204 ValueType t, std::pmr::memory_resource *mr = nullptr);
205 // Number of elements in the colon range (no allocation). Used by the
206 // VM's lazy `for v = a:b` loop to size the iteration without
207 // materialising the row vector. Throws on infinite/zero step.
208 static size_t colonCount(double start, double step, double stop);
209
210 // Concatenation: [a, b, c] and [a; b; c]
211 static Value horzcat(const Value *elems, size_t count, std::pmr::memory_resource *mr = nullptr);
212 static Value vertcat(const Value *elems, size_t count, std::pmr::memory_resource *mr = nullptr);
213
214 // ── Type-preserving indexing ─────────────────────────────
215 // All methods preserve the element type (DOUBLE, COMPLEX, LOGICAL, CHAR).
216 Value elemAt(size_t linearIdx, std::pmr::memory_resource *mr = nullptr) const;
217 // Read element idx as double — covers DOUBLE / SINGLE / LOGICAL /
218 // CHAR / COMPLEX (real part) / INT8..INT64 / UINT8..UINT64.
219 double elemAsDouble(size_t idx) const;
220 Value indexGet(const size_t *indices, size_t count, std::pmr::memory_resource *mr = nullptr) const;
221 // Reshape to rows x cols (numel must match), column-major-preserving. The
222 // L0 primitive behind the reshape builtin and the `x(:)` linear-index
223 // column form: OBJECT -> objectReshape, CELL/STRING copy element-wise,
224 // contiguous types memcpy the buffer.
225 Value reshape(size_t rows, size_t cols, std::pmr::memory_resource *mr = nullptr) const;
226 Value indexGet2D(const size_t *rowIdx, size_t nrows,
227 const size_t *colIdx, size_t ncols, std::pmr::memory_resource *mr = nullptr) const;
228 Value indexGet3D(const size_t *rowIdx, size_t nrows,
229 const size_t *colIdx, size_t ncols,
230 const size_t *pageIdx, size_t npages, std::pmr::memory_resource *mr = nullptr) const;
235 Value indexGetND(const size_t *const *perDimIdx,
236 const size_t *perDimCount,
237 int nd,
238 std::pmr::memory_resource *mr = nullptr) const;
239 Value logicalIndex(const uint8_t *mask, size_t maskLen, std::pmr::memory_resource *mr = nullptr) const;
240
241 // ── Index resolution ────────────────────────────────────
242 // Convert an index Value (scalar, vector, logical mask, colon ':')
243 // into a vector of 0-based indices.
244 // resolveIndices: bounds-checked (for GET)
245 // resolveIndicesUnchecked: no bounds check, colon requires dimSize (for SET/auto-expand)
246 static std::vector<size_t> resolveIndices(const Value &idx, size_t dimSize);
247 static std::vector<size_t> resolveIndicesUnchecked(const Value &idx);
248
249 // Validate a single 1-based scalar subscript (paren OR brace) and return
250 // it 0-based. Throws the MATLAB-exact "Array indices must be positive
251 // integers or logical values." on fractional, non-positive, NaN or Inf
252 // input. Does NOT bounds-check — callers that auto-grow accept any
253 // in-range positive integer. Single source of truth for scalar-subscript
254 // validation across both engines.
255 static size_t checkedScalarIndex(double oneBased);
256
257 // ── Type-preserving indexed assignment ──────────────────
258 // All methods dispatch on the array's element type.
259 // val must be a scalar (broadcast) or have matching numel.
260 void elemSet(size_t linearIdx, const Value &val);
261 void indexSet(const size_t *indices, size_t count, const Value &val);
262 void indexSet2D(const size_t *rowIdx, size_t nrows,
263 const size_t *colIdx, size_t ncols,
264 const Value &val);
265 void indexSet3D(const size_t *rowIdx, size_t nrows,
266 const size_t *colIdx, size_t ncols,
267 const size_t *pageIdx, size_t npages,
268 const Value &val);
272 void indexSetND(const size_t *const *perDimIdx,
273 const size_t *perDimCount,
274 int nd,
275 const Value &val);
276
277 // ── Type-preserving deletion (v(idx) = []) ─────────────
278 void indexDelete(const size_t *indices, size_t count, std::pmr::memory_resource *mr = nullptr);
279 void indexDelete2D(const size_t *rowIdx, size_t nrows,
280 const size_t *colIdx, size_t ncols,
281 std::pmr::memory_resource *mr = nullptr);
282 void indexDelete3D(const size_t *rowIdx, size_t nrows,
283 const size_t *colIdx, size_t ncols,
284 const size_t *pageIdx, size_t npages,
285 std::pmr::memory_resource *mr = nullptr);
286 // ND delete: A(i_1, ..., i_n) = []. Exactly one axis must be a
287 // strict subset; all others must be the full range. Result has
288 // that axis shrunk by the count of deleted indices.
289 void indexDeleteND(const size_t *const *perDimIdx,
290 const size_t *perDimCount,
291 int nd,
292 std::pmr::memory_resource *mr = nullptr);
293
294 // ── Factories — complex ──────────────────────────────────
295 static Value complexScalar(Complex v, std::pmr::memory_resource *mr = nullptr);
296 static Value complexScalar(double re, double im, std::pmr::memory_resource *mr = nullptr);
297 static Value complexMatrix(size_t rows, size_t cols, std::pmr::memory_resource *mr = nullptr);
298
299 // ── Factories — string (MATLAB "..." double-quoted) ─────
300 static Value stringScalar(const std::string &s, std::pmr::memory_resource *mr = nullptr);
301 static Value stringArray(size_t rows, size_t cols, std::pmr::memory_resource *mr = nullptr);
302 static Value stringArray3D(size_t rows, size_t cols, size_t pages, std::pmr::memory_resource *mr = nullptr);
303
304 // ���─ String accessors ────────────────────────────────────
305 const std::string &stringElem(size_t i) const;
306 void stringElemSet(size_t i, const std::string &s);
307
308 // ── Type queries ─────────────────────────────────────────
309 ValueType type() const;
310 const Dims &dims() const;
311 size_t numel() const;
312 bool isScalar() const;
313 bool isEmpty() const;
314 bool isNumeric() const;
315
316 // True only for default-constructed Value (no value assigned).
317 // Unlike isEmpty(), returns false for empty matrices (A=[]) and empty strings ('').
318 bool isUnset() const { return heap_ == emptyTag(); }
319 // True only for variables explicitly removed via 'clear'.
320 bool isDeleted() const { return heap_ == deletedTag(); }
321 bool isComplex() const;
322 bool isLogical() const;
323 bool isChar() const;
324 bool isCell() const;
325 bool isStruct() const;
326 bool isFuncHandle() const;
327 bool isString() const;
328 bool isObject() const;
329 bool isCsl() const; // comma-separated list (transient value-list); see ValueType::CSL
330
331 // ── OBJECT accessors (see object.hpp) ────────────────────
332 // Class name of an OBJECT ("" otherwise). objectIsHandle: the
333 // class's reference-semantics flag.
334 std::string objectClassName() const;
335 bool objectIsHandle() const;
336 // Number of elements in an OBJECT array (0 if not an object; 1 for a
337 // scalar object). Shape lives in dims()/size() like any other array.
338 size_t objectCount() const;
339 // Read-only instance state of the FIRST element (null if not an
340 // object / empty). For scalar objects this is the only element.
341 const ObjectState *objectStateConst() const;
342 // Mutable instance state for writers. Detaches (COW) first so the
343 // value/handle clone rule applies, then returns the state to mutate
344 // (uniquely-owned for a value class; the shared one for a handle).
345 ObjectState *objectStateMut();
346 // Per-element accessors for object arrays (null if i out of range).
347 const ObjectState *objectStateAt(size_t i) const;
348 ObjectState *objectStateMutAt(size_t i);
349
350 // ── Builtin object-array indexing (no custom subsref/subsasgn) ──
351 // Select elements `idxs` (0-based linear) into a new object array of
352 // the same class. One index → a 1×1 scalar object; several → a 1×N
353 // row. Applies the value/handle rule per element (value classes get
354 // independent deep copies, handle classes alias), so a value-class
355 // element read is safe to mutate. Throws on out-of-range index.
356 Value objectSubArray(const std::vector<size_t> &idxs,
357 std::pmr::memory_resource *mr = nullptr) const;
358 // Reshape an object array to `newDims` (same numel) — element order is
359 // column-major-preserving, like reshape on any array. Applies the
360 // per-element value/handle rule. Used by the reshape builtin.
361 Value objectReshape(const Dims &newDims, std::pmr::memory_resource *mr = nullptr) const;
362 // 2-D transpose of an object array (R×C → C×R). Used by the builtin
363 // transpose/ctranspose for objects with no class overload.
364 Value objectTranspose(std::pmr::memory_resource *mr = nullptr) const;
365 // General element-rearrange primitive: build an object array of shape
366 // `resultDims` by gathering source states at the column-major linear
367 // indices `srcLinear` (length == resultDims.numel()), applying the
368 // per-element value/handle rule. Underlies object indexing, reshape and
369 // transpose; exposed for builtin rearrangers (repmat / flip / cat).
370 Value objectGather(const size_t *srcLinear, const Dims &resultDims,
371 std::pmr::memory_resource *mr = nullptr) const;
372 // Concatenate object `parts` (same class) by appending their states in
373 // column-major order into an array of shape `outDims` (numel must equal
374 // the total element count). value/handle rule per element. Used by
375 // cat(dim≥3, ...) for objects; empties are skipped.
376 static Value objectConcatN(const Value *parts, size_t count, const Dims &outDims,
377 std::pmr::memory_resource *mr = nullptr);
378 // Assign scalar object `elem` into linear slot `idx`, growing this
379 // (1-D row vector) and gap-filling new slots with independent copies
380 // of `fill` (typically a default-constructed object). An empty/unset/
381 // non-object receiver becomes a fresh object array of elem's class.
382 // Value class → store a deep copy of elem; handle class → alias elem.
383 // Detaches (COW) first. For the no-custom-subsasgn builtin path.
384 // Linear slice assignment: write `rhs` into the linear positions `pos`
385 // (0-based). `rhs` is either a scalar object (broadcast to every target)
386 // or an object array with numel == pos.size() (placed in order). Grows
387 // as a vector when a position is past the end (a proper matrix errors);
388 // an empty/unset receiver becomes a fresh object array of rhs's class.
389 // value class → deep copy per element, handle class → alias.
390 void objectAssignLinear(const std::vector<size_t> &pos, const Value &rhs,
391 const Value &fill, std::pmr::memory_resource *mr = nullptr);
392 // N-D slice assignment: `perDim[d]` is the 0-based index list for
393 // dimension d; targets are the column-major cartesian product. `rhs`
394 // broadcasts (scalar) or matches the target count. Grows any rank and
395 // re-lays-out column-major, default-filling new slots with `fill`.
396 void objectAssignND(const std::vector<std::vector<size_t>> &perDim, const Value &rhs,
397 const Value &fill, std::pmr::memory_resource *mr = nullptr);
398
399 // ── Const raw access ─────────────────────────────────────
400 const void *rawData() const;
401 size_t rawBytes() const;
402 // whos-correct deep size: a struct/cell reports the sum of all its
403 // contents (recursively) plus its own schema (field-name bytes), instead
404 // of 0. Leaf arrays return rawBytes(). Use this for display (whos /
405 // workspace / struct inspector); use rawBytes() for flat-buffer ops.
406 size_t deepBytes() const;
407
408 // ── Const typed access — double ──────────────────────────
409 const double *doubleData() const;
410 const uint8_t *logicalData() const;
411 const char *charData() const;
412 double toScalar() const;
413 bool toBool() const;
414 std::string toString() const;
415
416 // ── Const typed access — single ────────────────────────────
417 const float *singleData() const;
418
419 // ── Const typed access — integer types ───────────────────
420 const int8_t *int8Data() const;
421 const int16_t *int16Data() const;
422 const int32_t *int32Data() const;
423 const int64_t *int64Data() const;
424 const uint8_t *uint8Data() const;
425 const uint16_t *uint16Data() const;
426 const uint32_t *uint32Data() const;
427 const uint64_t *uint64Data() const;
428
429 // ── Const typed access — complex ─────────────────────────
430 const Complex *complexData() const;
431 Complex toComplex() const;
432 Complex complexElem(size_t i) const;
433 Complex complexElem(size_t r, size_t c) const;
434
435 // ── Mutable typed access (calls detach for COW) ──────────
436 double *doubleDataMut();
437 float *singleDataMut();
438 uint8_t *logicalDataMut();
439 char *charDataMut();
440 void *rawDataMut();
441 Complex *complexDataMut();
442 int8_t *int8DataMut();
443 int16_t *int16DataMut();
444 int32_t *int32DataMut();
445 int64_t *int64DataMut();
446 uint8_t *uint8DataMut();
447 uint16_t *uint16DataMut();
448 uint32_t *uint32DataMut();
449 uint64_t *uint64DataMut();
450
451 // ── Const indexing (column-major) ────────────────────────
452 double operator()(size_t i) const;
453 double operator()(size_t r, size_t c) const;
454 double operator()(size_t r, size_t c, size_t p) const;
455
456 // ── Mutable indexing (calls detach) ──────────────────────
457 double &elem(size_t i);
458 double &elem(size_t r, size_t c);
459 double &elem(size_t r, size_t c, size_t p);
460
461 // ── Char element access ──────────────────────────────────
462 char charElem(size_t i) const;
463 char &charElemMut(size_t i);
464 // Extract one row of a char matrix as a std::string. Column-major
465 // stride is applied, so `reshape('abcdef',2,3).charRow(0)` is "ace".
466 std::string charRow(size_t r) const;
467
468 // ── Resize ───────────────────────────────────────────────
469 void resize(size_t newRows, size_t newCols, std::pmr::memory_resource *mr = nullptr);
470 void resize3d(size_t newRows, size_t newCols, size_t newPages, std::pmr::memory_resource *mr = nullptr);
471 // ND resize: re-shape to `newDims` (length `nd`), preserving the
472 // intersection of old and new shapes (per-axis min). Pads with 0
473 // (or ' ' for CHAR). Delegates to resize/resize3d for nd ≤ 3.
474 void resizeND(const size_t *newDims, int nd, std::pmr::memory_resource *mr = nullptr);
475 void ensureSize(size_t linearIdx, std::pmr::memory_resource *mr = nullptr);
476 void appendScalar(double v, std::pmr::memory_resource *mr = nullptr);
477 // Column twin of appendScalar: grows a {n,1} column vector by one
478 // scalar in place (geometric capacity, amortised O(1)) — the
479 // VERTCAT_APPEND fast path for column literals `[1;2;3;…]`
480 // (bugs/closed/core/register-exhaustion-no-fallback).
481 void appendScalarCol(double v, std::pmr::memory_resource *mr = nullptr);
482
483 // ── Promote double → complex ─────────────────────────────
484 void promoteToComplex(std::pmr::memory_resource *mr = nullptr);
485
486 // ── Cell ─────────────────────────────────────────────────
487 Value &cellAt(size_t i);
488 const Value &cellAt(size_t i) const;
489 // ── CSL (comma-separated list) ───────────────────────────
490 // Element access + count for a transient value-list. Storage is the
491 // shared cellData backing, so cellAt() also works; these read the count
492 // without the isCell() type assumption and document CSL intent at call sites.
493 size_t cslCount() const;
494 Value &cslAt(size_t i) { return cellAt(i); }
495 const Value &cslAt(size_t i) const { return cellAt(i); }
496 // Grow a cell so subscript `coords` (length `nd`) is in bounds,
497 // preserving existing contents and lifting the rank if needed.
498 // Coerces an empty/unset receiver to a cell. Returns the column-major
499 // linear index of `coords` in the (grown) cell.
500 size_t growCellTo(const size_t *coords, int nd,
501 std::pmr::memory_resource *mr = nullptr);
502 std::pmr::vector<Value> &cellDataVec();
503 const std::pmr::vector<Value> &cellDataVec() const;
504
505 // ── Struct ───────────────────────────────────────────────
506 Value &field(const std::string &name);
507 const Value &field(const std::string &name) const;
508 bool hasField(const std::string &name) const;
509 std::pmr::map<std::string, Value> &structFields();
510 const std::pmr::map<std::string, Value> &structFields() const;
511
512 // BUG #15 fix: insertion-order tracking for fieldnames(). Use
513 // setField / setFieldAll for new field assignments so the order is
514 // preserved; the raw map[] also works (alphabetical via std::map)
515 // but won't update the order vector. removeField clears across
516 // every struct array element and the order tracker.
517 //
518 // setField(linear, name, value): assign to element `linear` only.
519 // setFieldAll(name, value): assign across the whole struct array.
520 // Both append `name` to fieldOrder if not already present.
521 void setField(size_t linearIdx, const std::string &name, const Value &v);
522 void setFieldAll(const std::string &name, const Value &v);
523 void removeField(const std::string &name);
524 // Returns the field names in MATLAB insertion order (preferred).
525 // Falls back to alphabetical (struct map iteration) if fieldOrder
526 // is missing — happens for legacy/cloned structs.
527 std::vector<std::string> fieldNamesInOrder() const;
528
529 // True for struct arrays (numel > 1). isStruct() && !isStructArray()
530 // means a single struct (the legacy code path).
531 bool isStructArray() const;
532 // Mutable / const access to the i-th element's field map. Caller must
533 // ensure i < numel(). Throws when called on a single struct (use
534 // structFields() there instead).
535 std::pmr::map<std::string, Value> &structArrayElem(size_t i);
536
537 // Grow a struct array so that linear index `idx` is in bounds.
538 // No-op if already large enough. Coerces the receiver to a fresh
539 // struct array if it's empty/unset. Row-vector / column-vector
540 // shape preserved; default to row vector for empty / 1×1 starts.
541 // Does not insert any field into the new slots — they're empty.
542 void growStructArrayTo(size_t idx, std::pmr::memory_resource *mr = nullptr);
543 // N-D analogue: grow the struct array so subscript `coords` (length
544 // `nd`) is in bounds, preserving existing elements at their
545 // column-major positions and lifting the rank if needed. Coerces an
546 // empty/unset receiver to a struct array. Returns the column-major
547 // linear index of `coords` in the (grown) array.
548 size_t growStructArrayND(const size_t *coords, int nd,
549 std::pmr::memory_resource *mr = nullptr);
550 const std::pmr::map<std::string, Value> &structArrayElem(size_t i) const;
551
552 // ── Func handle ──────────────────────────────────────────
553 std::string funcHandleName() const;
556 std::string funcHandleSource() const;
558 void setFuncHandleSource(const std::string &source);
559
560 // ── Debug ────────────────────────────────────────────────
561 std::string debugString() const;
562
563 // MATLAB-style display string: "name =\n value\n\n"
564 std::string formatDisplay(const std::string &name) const;
565
566 // ── Fast scalar access for VM ────────────────────────────
567 // Caller must ensure isDoubleScalar() is true.
568 bool isDoubleScalar() const { return heap_ == nullptr; }
569 double scalarVal() const { return scalar_; }
570 void setScalarVal(double v)
571 {
572 releaseHeap();
573 scalar_ = v;
574 heap_ = nullptr;
575 }
576 void setScalarFast(double v)
577 {
578 scalar_ = v;
579 heap_ = nullptr;
580 } // caller guarantees no heap to release
581
582 // Fast logical set for VM comparison fast-path — tag-based, zero allocation
583 void setLogicalFast(bool v)
584 {
585 scalar_ = 0.0;
586 heap_ = v ? logicalTrueTag() : logicalFalseTag();
587 } // caller guarantees no heap to release
588
589 // Fast check: is this a logical scalar (tag-based)?
590 bool isLogicalScalar() const { return heap_ == logicalTrueTag() || heap_ == logicalFalseTag(); }
591
592 // Fast scalar value for both double and logical scalars
593 // Caller must ensure isDoubleScalar() || isLogicalScalar()
594 double fastScalarVal() const
595 {
596 if (heap_ == nullptr)
597 return scalar_;
598 // logical tag
599 return heap_ == logicalTrueTag() ? 1.0 : 0.0;
600 }
601
602 // ── Ultra-fast VM hot-path accessors ─────────────────────
603 // Caller must ensure isHeap() && type == DOUBLE.
604 // Bypasses all safety checks and detach() for sole-owner arrays.
605 bool isHeapDouble() const { return heap_ != nullptr && !isTag() && heap_->type == ValueType::DOUBLE; }
606
607 const Dims &heapDims() const { return heap_->dims; }
608
609 // Refcount of the underlying heap buffer. Returns 1 for scalar /
610 // empty / tag values (no shared ownership possible). Used by fast
611 // paths that want to mutate in-place iff they're the sole owner.
612 int heapRefCount() const
613 {
614 if (heap_ == nullptr || isTag())
615 return 1;
616 return heap_->refCount.load(std::memory_order_relaxed);
617 }
618
619 // True iff heap_ points at a real (non-tag, non-null) heap object.
620 // Public-facing companion to the private isHeap() used by VM
621 // output-reuse fast paths.
622 bool hasHeap() const
623 {
624 return heap_ != nullptr && !isTag();
625 }
626
627 // Get mutable data pointer — skips detach when refcount == 1 (sole owner).
628 // Caller must guarantee this is a heap DOUBLE array.
629 double *doubleDataMutFast()
630 {
631 if (heap_->refCount.load(std::memory_order_relaxed) == 1)
632 return static_cast<double *>(heap_->buffer->data());
633 detach();
634 return static_cast<double *>(heap_->buffer->data());
635 }
636
637 const double *doubleDataFast() const
638 {
639 return static_cast<const double *>(heap_->buffer->data());
640 }
641
642 // Swap data buffers between this and `other`, keeping each Value's
643 // dims/type unchanged. Used by the slice-assign fast path
644 // (`z(:) = expr`) to absorb a uniquely-owned temporary's buffer
645 // without an O(N) memcpy. Caller must guarantee:
646 // * both MValues are heap (hasHeap() && !isTag())
647 // * both heaps are uniquely owned (heapRefCount() == 1)
648 // * both buffers are the same byte size
649 // After the swap, `other` holds this Value's prior buffer; on its
650 // next overwrite (typically the temp-register reuse a few bytecode
651 // ops later) that buffer is freed.
652 void swapHeapBufferUnchecked(Value &other) noexcept
653 {
654 DataBuffer *tmp = heap_->buffer;
655 heap_->buffer = other.heap_->buffer;
656 other.heap_->buffer = tmp;
657 }
658
659private:
660 // ── 16-byte layout ───────────────────────────────────────
661 double scalar_ = 0.0;
662 HeapObject *heap_;
663
664 // ── Sentinel tags ────────────────────────────────────────
665 static HeapObject sEmptyTag;
666 static HeapObject sLogicalTrue;
667 static HeapObject sLogicalFalse;
668 static HeapObject sDeletedTag;
669
670 // ── Tag constants ────────────────────────────────────────
671 static HeapObject *emptyTag() { return &sEmptyTag; }
672 static HeapObject *logicalTrueTag() { return &sLogicalTrue; }
673 static HeapObject *logicalFalseTag() { return &sLogicalFalse; }
674 static HeapObject *deletedTag() { return &sDeletedTag; }
675
676 // ── Internal helpers ─────────────────────────────────────
677 bool isTag() const
678 {
679 return heap_ == emptyTag() || heap_ == logicalTrueTag()
680 || heap_ == logicalFalseTag() || heap_ == deletedTag();
681 }
682 bool isHeap() const { return heap_ != nullptr && !isTag(); }
683
684 void releaseHeap();
685 void detach();
686 HeapObject *mutableHeap();
687 // Concatenate same-class object elements into a 1×N row / N×1 column
688 // object array (per-element value/handle rule). Used by horzcat/vertcat.
689 static Value concatObjects(const Value *elems, size_t count, bool vertical,
690 std::pmr::memory_resource *mr);
691 // Coerce a non-object receiver into a fresh 0×0 object array of `proto`'s
692 // class. No-op when already an object. Used by the slice-assign helpers.
693 void objectEnsureArrayLike(const Value &proto, std::pmr::memory_resource *mr);
694
695 // Static dims for scalar returns
696 static const Dims sScalarDims;
697 static const Dims sEmptyDims;
698
699 // Shared body of the ergonomic ctors. Allocates a 1×count row of
700 // `type` on `mr` and memcpy's `count * elemSize` bytes from `src`.
701 static Value makeContiguous(const void *src, size_t count,
702 ValueType type, size_t elemSize,
703 std::pmr::memory_resource *mr);
704};
705
706// MATLAB narrows a complex result whose imaginary part is all-zero back to a real
707// double — for the RESULTS of operations (arithmetic, indexing, reductions, …),
708// NOT the complex() constructor or a pure structural reshape/transpose/concat. A
709// NaN imaginary part keeps it complex. No-op unless COMPLEX with every imaginary
710// component exactly zero. bugs/math/complex-zero-imag-narrowing.md.
711Value narrowComplex(Value v, std::pmr::memory_resource *mr);
712
713// Collapse a comma-separated list reaching a SINGLE-VALUE context: a CSL with exactly
714// one element yields that element; 0 or >1 elements throw (MATLAB "not enough" / "too
715// many" values). A non-CSL value passes through unchanged, so this is safe to apply at
716// any single-value sink (assignment RHS, operator operand, condition, index value). The
717// VM COLLAPSE opcode is a thin wrapper. See dev-docs/memory/csl_first_class.md.
718Value collapseCsl(Value v);
719
720// ── Inline template bodies ──────────────────────────────────────
721// Kept in the header because they're tiny — each one is a single
722// thunk into Value::makeContiguous. The compile-time cost per TU is
723// negligible compared to dragging value.cpp into every includer.
724
725template<class T>
726inline Value::Value(Span<const T> data, std::pmr::memory_resource *mr)
727 : Value()
728{
729 constexpr ValueType vt = detail::value_type_for<T>::type;
730 *this = makeContiguous(data.data(), data.size(), vt, sizeof(T), mr);
731}
732
733template<class T>
734inline Value::Value(const std::vector<T> &v, std::pmr::memory_resource *mr)
735 : Value()
736{
737 constexpr ValueType vt = detail::value_type_for<T>::type;
738 *this = makeContiguous(v.data(), v.size(), vt, sizeof(T), mr);
739}
740
741template<class T, std::size_t N>
742inline Value::Value(const std::array<T, N> &a, std::pmr::memory_resource *mr)
743 : Value()
744{
745 constexpr ValueType vt = detail::value_type_for<T>::type;
746 *this = makeContiguous(a.data(), N, vt, sizeof(T), mr);
747}
748
749} // namespace nk
Core MATLAB-compatible multidimensional array and scalar value type.
Definition value.hpp:61
void setFuncHandleSource(const std::string &source)
Attach the anonymous-function source text to a freshly-created handle.
Value indexGetND(const size_t *const *perDimIdx, const size_t *perDimCount, int nd, std::pmr::memory_resource *mr=nullptr) const
ND subscript read for arbitrary rank.
void indexSetND(const size_t *const *perDimIdx, const size_t *perDimCount, int nd, const Value &val)
ND subscript write for arbitrary rank.
static const Value Empty
MATLAB-style empty matrix sentinel — a 0×0 DOUBLE value.
Definition value.hpp:136
static Value view(const void *data, ValueType type, Dims dims)
Non-owning view over an externally-owned buffer.
Value()
Constructs an empty 0 x 0 double matrix ([]).
Value(std::initializer_list< double > il, std::pmr::memory_resource *mr=nullptr)
Row vector from a brace-enclosed list of doubles.
static Value empty()
Returns a default-constructed (Unset) Value — NOT a MATLAB empty matrix despite the name.
std::string funcHandleSource() const
Reconstructed source text of an anonymous-function handle (e.g.
Compile-time map from C++ type → nk ValueType.
Definition value.hpp:26