forked from Tracktion/choc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
choc_Value.h
3248 lines (2662 loc) · 132 KB
/
choc_Value.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//
// ██████ ██ ██ ██████ ██████
// ██ ██ ██ ██ ██ ██ ** Classy Header-Only Classes **
// ██ ███████ ██ ██ ██
// ██ ██ ██ ██ ██ ██ https://github.com/Tracktion/choc
// ██████ ██ ██ ██████ ██████
//
// CHOC is (C)2022 Tracktion Corporation, and is offered under the terms of the ISC license:
//
// Permission to use, copy, modify, and/or distribute this software for any purpose with or
// without fee is hereby granted, provided that the above copyright notice and this permission
// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#ifndef CHOC_VALUE_POOL_HEADER_INCLUDED
#define CHOC_VALUE_POOL_HEADER_INCLUDED
#include <vector>
#include <string>
#include <cstring>
#include <algorithm>
#include <memory>
#include <exception>
#include "../platform/choc_Assert.h"
namespace choc::value
{
class Value;
class ValueView;
class StringDictionary;
struct MemberNameAndType;
struct MemberNameAndValue;
struct ElementTypeAndOffset;
// This macro lets you override the primitive type that will be used for
// encoding bool elements in a choc::value::ValueView. This setting makes
// no difference to the way the serialisation is done, but affects the way
// elements are packed in memory in a live value object. By default this uses
// a uint32_t for bools, so that all data elements are 4-byte aligned, but you
// could change it to a uint8_t if you want to pack the data more tightly at
// the expense of aligned read/write access.
#ifdef CHOC_VALUE_BOOL_STORAGE_TYPE
using BoolStorageType = CHOC_VALUE_BOOL_STORAGE_TYPE;
#else
using BoolStorageType = uint32_t;
#endif
//==============================================================================
/// An exception object which is thrown by the Type, Value and ValueView classes when various
/// runtime checks fail.
/// @see Type, Value, ValueView
struct Error : public std::exception
{
Error (const char* desc) : description (desc) {}
const char* what() const noexcept override { return description; }
const char* description;
};
/// Throws an error exception.
/// Note that the message string is taken as a raw pointer and not copied, so must be a string literal.
/// This is used by the Type, Value and ValueView classes.
/// @see Type, Value, ValueView
[[noreturn]] static void throwError (const char* errorMessage) { throw Error (errorMessage); }
/// Throws an Error with the given message if the condition argument is false.
/// Note that the message string is taken as a raw pointer and not copied, so must be a string literal.
/// This is used by the Type, Value and ValueView classes.
static void check (bool condition, const char* errorMessage) { if (! condition) throwError (errorMessage); }
/// Used by some deserialisation methods in Type, Value and StringDictionary
struct InputData
{
const uint8_t* start;
const uint8_t* end;
};
/// This helper class holds a chunk of data that is a serialised Value or ValueView, and has
/// a handy method to turn it back into a Value object.
struct SerialisedData
{
std::vector<uint8_t> data;
Value deserialise() const;
InputData getInputData() const;
void write (const void*, size_t);
};
/** A custom allocator class which can be used to replace the normal heap allocator
for a Type object. This is mainly useful if you need to create and manipulate Type
and Value objects on a realtime thread and need a fast pool allocator.
If you pass a custom allocator to the Type class, you must make sure that its lifetime
is greater than that of the Types that are created (both directly and possibly indirectly
as nested sub-types).
*/
struct Allocator
{
virtual ~Allocator() = default;
virtual void* allocate (size_t size) = 0;
virtual void* resizeIfPossible (void*, size_t requestedSize) = 0;
virtual void free (void*) noexcept = 0;
};
//==============================================================================
/** */
template <size_t totalSize>
struct FixedPoolAllocator : public Allocator
{
FixedPoolAllocator() = default;
~FixedPoolAllocator() override = default;
void reset() noexcept { position = 0; }
void* allocate (size_t size) override;
void* resizeIfPossible (void* data, size_t requiredSize) override;
void free (void*) noexcept override {}
private:
size_t position = 0, lastAllocationPosition = 0;
char pool[totalSize];
};
//==============================================================================
/** A type class that can represent primitives, vectors, strings, arrays and objects.
A Type can represent:
- A primitive int32 or int64
- A primitive float or double
- A primitive bool
- A vector of primitives
- A string
- An array of other Values
- An object, which has a class name and a set of named members, each holding another Value.
The Type class attempts to be small and allocation-free for simple types like primitives, vectors and
arrays of vectors, but will use heap storage when given something more complex to represent.
A Type can also be serialised and deserialised to a packed format.
@see Value, ValueView
*/
class Type final
{
public:
Type() = default;
Type (Type&&);
Type (const Type&);
Type (Allocator*, const Type&); ///< Constructs a copy of another type, using a custom allocator (which may be nullptr).
Type& operator= (Type&&);
Type& operator= (const Type&);
~Type() noexcept;
bool isVoid() const noexcept { return isType (MainType::void_); }
bool isInt32() const noexcept { return isType (MainType::int32); }
bool isInt64() const noexcept { return isType (MainType::int64); }
bool isInt() const noexcept { return isType (MainType::int32, MainType::int64); }
bool isFloat32() const noexcept { return isType (MainType::float32); }
bool isFloat64() const noexcept { return isType (MainType::float64); }
bool isFloat() const noexcept { return isType (MainType::float32, MainType::float64); }
bool isBool() const noexcept { return isType (MainType::boolean); }
bool isPrimitive() const noexcept { return isType (MainType::int32, MainType::int64, MainType::float32, MainType::float64, MainType::boolean); }
bool isObject() const noexcept { return isType (MainType::object); }
bool isString() const noexcept { return isType (MainType::string); }
bool isVector() const noexcept { return isType (MainType::vector); }
bool isArray() const noexcept { return isType (MainType::primitiveArray, MainType::complexArray); }
bool isUniformArray() const; ///< A uniform array is one where every element has the same type.
bool isArrayOfVectors() const;
bool isVectorSize1() const;
/// Returns true if the type is the same as the provided template type (which must be a primitive)
template <typename PrimitiveType> bool isPrimitiveType() const noexcept;
/// Returns the number of elements in an array, vector or object. Throws an Error if the type is void.
uint32_t getNumElements() const;
/// If the type is an array or vector with a uniform element type, this returns it; if not, it throws an Error.
Type getElementType() const;
/// Returns the type of a given element in this type if it's an array. If the type isn't an array or the index is
/// out of bounds, it will throw an Error.
Type getArrayElementType (uint32_t index) const;
/// For a vector or uniform array type, this allows the number of elements to be directly mutated.
/// For any other type, this will throw an Error exception.
void modifyNumElements (uint32_t newNumElements);
/// Returns the name and type of one of the members if this type is an object; if not, or the index is out
/// of range, then this will throw an Error exception.
const MemberNameAndType& getObjectMember (uint32_t index) const;
/// If this is an object, this returns the index of the member with a given name. If the name isn't found, it
/// will return -1, and if the type isn't an object, it will throw an Error exception.
int getObjectMemberIndex (std::string_view name) const;
/// Returns the class-name of this type if it's an object, or throws an Error if it's not.
std::string_view getObjectClassName() const;
/// Returns true if this is an object with the given class-name.
bool isObjectWithClassName (std::string_view name) const;
bool operator== (const Type&) const;
bool operator!= (const Type&) const;
//==============================================================================
static Type createVoid() { return Type (MainType::void_); }
static Type createInt32() { return Type (MainType::int32); }
static Type createInt64() { return Type (MainType::int64); }
static Type createFloat32() { return Type (MainType::float32); }
static Type createFloat64() { return Type (MainType::float64); }
static Type createBool() { return Type (MainType::boolean); }
static Type createString() { return Type (MainType::string); }
/// Creates a type based on the given template type.
template <typename PrimitiveType>
static Type createPrimitive();
//==============================================================================
/// Creates a vector type based on the given template type and size.
template <typename PrimitiveType>
static Type createVector (uint32_t numElements);
static Type createVectorInt32 (uint32_t numElements) { return Type (MainType::int32, numElements); }
static Type createVectorInt64 (uint32_t numElements) { return Type (MainType::int64, numElements); }
static Type createVectorFloat32 (uint32_t numElements) { return Type (MainType::float32, numElements); }
static Type createVectorFloat64 (uint32_t numElements) { return Type (MainType::float64, numElements); }
static Type createVectorBool (uint32_t numElements) { return Type (MainType::boolean, numElements); }
//==============================================================================
/// Creates a type representing an empty array. Element types can be appended with addArrayElements().
static Type createEmptyArray();
/// Creates a type representing an array containing a set of elements of a fixed type.
static Type createArray (Type elementType, uint32_t numElements);
/// Creates a type representing an array of primitives based on the templated type.
template <typename PrimitiveType>
static Type createArray (uint32_t numArrayElements);
/// Creates a type representing an array of vectors based on the templated type.
template <typename PrimitiveType>
static Type createArrayOfVectors (uint32_t numArrayElements, uint32_t numVectorElements);
/// Appends a group of array elements with the given to this type's definition.
/// This will throw an Error if this isn't possible for various reasons.
void addArrayElements (Type elementType, uint32_t numElements);
//==============================================================================
/// Returns a type representing an empty object, with the given class name.
static Type createObject (std::string_view className, Allocator* allocator = nullptr);
/// Appends a member to an object type, with the given name and type. This will throw an Error if
/// this isn't possible for some reason.
void addObjectMember (std::string_view memberName, Type memberType);
//==============================================================================
/// Returns the size in bytes needed to store a value of this type.
size_t getValueDataSize() const;
/// Returns true if this type, or any of its sub-types are a string.
bool usesStrings() const;
/// Returns the type and packed-data position of one of this type's sub-elements.
ElementTypeAndOffset getElementTypeAndOffset (uint32_t index) const;
//==============================================================================
/** Stores a representation of this type in a packed data format.
It can later be reloaded with deserialise(). The OutputStream template can
be any object which has a method write (const void*, size_t)
The data format is simple:
Primitives: type (1 byte)
Vectors: type (1 byte), num elements (packed int), primitive type (1 byte)
Array: type (1 byte), num groups (packed int), [num repetitions (packed int), element type (type)]*
Object: type (1 byte), num members (packed int), name (null-term string), [member type (type), member name (null-term string)]*
Packed ints are stored as a sequence of bytes in little-endian order, where each byte contains
7 bits of data + the top bit is set if another byte follows it.
@see deserialise
*/
template <typename OutputStream>
void serialise (OutputStream&) const;
/// Recreates a type from a serialised version that was created by the serialise() method.
/// Any errors while reading the data will cause an Error exception to be thrown.
/// The InputData object will be left pointing to any remaining data after the type has been read.
/// @see serialise
static Type deserialise (InputData&, Allocator* allocator = nullptr);
/// Returns a representation of this type in the form of a Value. @see fromValue
Value toValue() const;
/// Parses a Value which was created by toValue(), converting it back into a Type object.
static Type fromValue (const ValueView&);
/// Returns a human-interpretable description of this type, useful for debugging.
std::string getDescription() const;
/// Returns a compact string to uniquely describe this type's layout.
/// The signature includes information about any sub-types, e.g object member types, array
/// element types and array sizes. If includeNames is true, it also embeds the names of objects
/// and members, but if false it will ingore names and represent the "duck" type.
std::string getSignature (bool includeNames) const;
private:
//==============================================================================
enum class MainType : uint8_t
{
void_ = 0,
int32 = 0x00 + sizeof (int32_t),
int64 = 0x00 + sizeof (int64_t),
float32 = 0x10 + sizeof (float),
float64 = 0x10 + sizeof (double),
boolean = 0x30 + sizeof (BoolStorageType),
string = 0x40 + sizeof (uint32_t),
vector = 0x50,
primitiveArray = 0x60,
object = 0x80, // these two must have the top bit set to make it quick
complexArray = 0x90 // to decide whether the content references a heap object
};
static constexpr uint32_t maxNumVectorElements = 256;
static constexpr uint32_t maxNumArrayElements = 1024 * 1024;
static constexpr uint32_t getPrimitiveSize (MainType t) { return static_cast<uint32_t> (t) & 15; }
friend class ValueView;
friend class Value;
struct SerialisationHelpers;
struct ComplexArray;
struct Object;
template <typename ObjectType> struct AllocatedVector;
struct Vector
{
MainType elementType;
uint32_t numElements;
size_t getElementSize() const;
size_t getValueDataSize() const;
ElementTypeAndOffset getElementInfo (uint32_t) const;
ElementTypeAndOffset getElementRangeInfo (uint32_t start, uint32_t length) const;
bool operator== (const Vector&) const;
};
struct PrimitiveArray
{
MainType elementType;
uint32_t numElements, numVectorElements;
Type getElementType() const;
size_t getElementSize() const;
size_t getValueDataSize() const;
ElementTypeAndOffset getElementInfo (uint32_t) const;
ElementTypeAndOffset getElementRangeInfo (uint32_t start, uint32_t length) const;
bool operator== (const PrimitiveArray&) const;
};
union Content
{
Object* object;
ComplexArray* complexArray;
Vector vector;
PrimitiveArray primitiveArray;
};
MainType mainType = MainType::void_;
Content content = {};
Allocator* allocator = nullptr;
template <typename... Types> bool isType (Types... types) const noexcept { return ((mainType == types) || ...); }
template <typename Type> static constexpr MainType selectMainType();
explicit Type (MainType);
Type (MainType, Content, Allocator*);
Type (MainType vectorElementType, uint32_t);
void allocateCopy (const Type&);
void deleteAllocatedObjects() noexcept;
ElementTypeAndOffset getElementRangeInfo (uint32_t start, uint32_t length) const;
template <typename Visitor> void visitStringHandles (size_t, const Visitor&) const;
static Type createArray (Type elementType, uint32_t numElements, Allocator*);
};
//==============================================================================
/** This holds the type and location of a sub-element of a Type.
@see Type::getElementTypeAndOffset()
*/
struct ElementTypeAndOffset
{
Type elementType;
size_t offset; ///< The byte position within its parent value of the data representing this element
};
//==============================================================================
/** A simple dictionary base-class for mapping strings onto integer handles.
This is needed by the Value and ValueView classes.
@see Value, ValueView
*/
class StringDictionary
{
public:
StringDictionary() = default;
virtual ~StringDictionary() = default;
struct Handle
{
uint32_t handle = 0;
bool operator== (Handle h) const { return handle == h.handle; }
bool operator!= (Handle h) const { return handle != h.handle; }
bool operator< (Handle h) const { return handle < h.handle; }
};
/// Finds or creates a handle for a string.
virtual Handle getHandleForString (std::string_view stringToAdd) = 0;
/// Fetches the string for a given handle. If the handle isn't found,
/// the implementation may throw an error.
virtual std::string_view getStringForHandle (Handle handle) const = 0;
};
//==============================================================================
/** A simple implementation of StringDictionary.
This should have good performance for typical-sized dictionaries.
Adding new strings will require O(n) time where n = dictionary size, but
retrieving the string for a handle is fast with O(1).
*/
struct SimpleStringDictionary : public StringDictionary
{
SimpleStringDictionary() = default;
SimpleStringDictionary (const SimpleStringDictionary& other) : strings (other.strings) {}
SimpleStringDictionary (SimpleStringDictionary&& other) : strings (std::move (other.strings)) {}
SimpleStringDictionary& operator= (const SimpleStringDictionary& other) { strings = other.strings; return *this; }
SimpleStringDictionary& operator= (SimpleStringDictionary&& other) { strings = std::move (other.strings); return *this; }
Handle getHandleForString (std::string_view) override;
std::string_view getStringForHandle (Handle handle) const override;
void clear();
/// The strings are stored in a single chunk, which can be saved and
/// reloaded if necessary.
std::vector<char> strings;
};
//==============================================================================
/**
Represents a view onto an object which can represent various types of primitive,
array and object types.
The ValueView and Value classes differ in that ValueView does not own the data that it
points to, but Value does. A ValueView should be used as a temporary wrapper around some
data whose lifetime can be trusted to outlive the ValueView object. As a rule-of-thumb, you
should treat Value and Valueview in the same way as std::string and std::string_view, so
a ValueView makes a great type for a function parameter, but probably shouldn't be used
as a function return type unless you really know what you're doing.
The purpose of these classes is to allow manipulation of complex, dynamically-typed objects
where the data holding a value is stored in a contiguous, packed, well-specified data
format, so that it can be manipulated directly as raw memory when necessary. The ValueView
is a lightweight wrapper around a type and a pointer to the raw data containing a value of that
type. The Value class provides the same interface, but also owns the storage needed, and can
return a ValueView of itself.
@see Type, Value, choc::json::toString()
*/
class ValueView final
{
public:
ValueView(); ///< Creates an empty value with a type of 'void'.
ValueView (Type&&, void* data, StringDictionary*); ///< Creates a value using the given type and raw block of data.
ValueView (const Type&, void* data, StringDictionary*); ///< Creates a value using the given type and raw block of data.
ValueView (const ValueView&) = default;
ValueView& operator= (const ValueView&) = default;
ValueView& operator= (ValueView&&) = default;
//==============================================================================
const Type& getType() const { return type; }
Type& getMutableType() { return type; }
bool isVoid() const noexcept { return type.isVoid(); }
bool isInt32() const noexcept { return type.isInt32(); }
bool isInt64() const noexcept { return type.isInt64(); }
bool isInt() const noexcept { return type.isInt(); }
bool isFloat32() const noexcept { return type.isFloat32(); }
bool isFloat64() const noexcept { return type.isFloat64(); }
bool isFloat() const noexcept { return type.isFloat(); }
bool isBool() const noexcept { return type.isBool(); }
bool isPrimitive() const noexcept { return type.isPrimitive(); }
bool isObject() const noexcept { return type.isObject(); }
bool isString() const noexcept { return type.isString(); }
bool isVector() const noexcept { return type.isVector(); }
bool isArray() const noexcept { return type.isArray(); }
//==============================================================================
int32_t getInt32() const; ///< Retrieves the value if this is an int32, otherwise throws an Error exception.
int64_t getInt64() const; ///< Retrieves the value if this is an int64, otherwise throws an Error exception.
float getFloat32() const; ///< Retrieves the value if this is a float, otherwise throws an Error exception.
double getFloat64() const; ///< Retrieves the value if this is a double, otherwise throws an Error exception.
bool getBool() const; ///< Retrieves the value if this is a bool, otherwise throws an Error exception.
std::string_view getString() const; ///< Retrieves the value if this is a string, otherwise throws an Error exception.
StringDictionary::Handle getStringHandle() const; ///< Retrieves the value if this is a string handle, otherwise throws an Error exception.
explicit operator int32_t() const { return getInt32(); } ///< If the object is not an int32, this will throw an Error.
explicit operator int64_t() const { return getInt64(); } ///< If the object is not an int64, this will throw an Error.
explicit operator float() const { return getFloat32(); } ///< If the object is not a float, this will throw an Error.
explicit operator double() const { return getFloat64(); } ///< If the object is not a double, this will throw an Error.
explicit operator bool() const { return getBool(); } ///< If the object is not a bool, this will throw an Error.
explicit operator std::string_view() const { return getString(); } ///< If the object is not a string, this will throw an Error.
/// Attempts to cast this value to the given primitive target type. If the type is void or something that
/// can't be cast, it will throw an exception. This will do some minor casting, such as ints to doubles,
/// but won't attempt do any kind of string to number conversions.
template <typename TargetType> TargetType get() const;
/// Attempts to get this value as the given target type, but if this isn't possible,
/// returns the default value provided instead of throwing an Error.
template <typename TargetType> TargetType getWithDefault (TargetType defaultValue) const;
/// A handy way to convert this value as a string where possible, or to return an empty
/// string (without throwing any errors) if not possible. The function is basically the
/// same as calling getWithDefault<std::string> ({})
std::string toString() const;
/// Attempts to write a new value to the memory pointed to by this view, as long as the type
/// provided exactly matches the value's type.
template <typename PrimitiveType> void set (PrimitiveType newValue);
/// Resets this value to a 'zero' state. Note that for arrays, this won't change the size
/// of the array, it just sets all the existing elements to zero.
void setToZero();
//==============================================================================
/// If this object is a vector, array or object, this returns the number of items it contains; otherwise
/// it will throw an Error exception.
uint32_t size() const;
/// If this object is an array or vector, and the index is valid, this returns one of its elements.
/// Throws an error exception if the object is not a vector or the index is out of range.
ValueView operator[] (int index) const;
/// If this object is an array or vector, and the index is valid, this returns one of its elements.
/// Throws an error exception if the object is not a vector or the index is out of range.
ValueView operator[] (uint32_t index) const;
/// If this object is an array or vector, and the index and length do not exceed its bounds, this
/// will return a view onto a range of its elements.
/// Throws an error exception if the object is not a vector or the range is invalid.
ValueView getElementRange (uint32_t startIndex, uint32_t length) const;
//==============================================================================
struct Iterator;
struct EndIterator {};
/// Iterating a Value is only valid for an array, vector or object.
Iterator begin() const;
EndIterator end() const { return {}; }
//==============================================================================
/// Returns the class name of this object.
/// This will throw an error if the value is not an object.
std::string_view getObjectClassName() const;
/// Returns true if this is an object with the given class-name.
bool isObjectWithClassName (std::string_view name) const;
/// Returns the name and value of a member by index.
/// This will throw an error if the value is not an object of if the index is out of range. (Use
/// size() to find out how many members there are). To get a named value from an object, you can
/// use operator[].
/// @see size
MemberNameAndValue getObjectMemberAt (uint32_t index) const;
/// Returns the value of a named member, or a void value if no such member exists.
/// This will throw an error if the value is not an object.
ValueView operator[] (std::string_view name) const;
/// Returns the value of a named member, or a void value if no such member exists.
/// This will throw an error if the value is not an object.
ValueView operator[] (const char* name) const;
/// Returns true if this is an object and contains the given member name.
bool hasObjectMember (std::string_view name) const;
/// Calls a functor on each member in an object.
/// The functor must take two parameters of type (string_view name, const ValueView& value).
template <typename Visitor>
void visitObjectMembers (Visitor&&) const;
//==============================================================================
/// Performs a comparison between two values, where only a bit-for-bit match is
/// considered to be true.
bool operator== (const ValueView&) const;
/// Performs a comparison between two values, where only a bit-for-bit match is
/// considered to be true.
bool operator!= (const ValueView&) const;
//==============================================================================
/// Gets a pointer to the string dictionary that the view is using, or nullptr
/// if it doesn't have one.
StringDictionary* getDictionary() const { return stringDictionary; }
/// Allows you to change the string dictionary which this view is using.
/// Changing the dictionary will visit all the strings inside the object,
/// remapping old handles into new ones from the new dictionary.
void setDictionary (StringDictionary* newDictionary);
/// Gets a pointer to the memory that this view is using for its content.
void* getRawData() { return data; }
/// Gets a pointer to the memory that this view is using for its content.
const void* getRawData() const { return data; }
/// Allows you to directly modify the internal pointer to the data that this view is
/// using. Obviously this should only be used if you really know what you're doing!
void setRawData (void* newAddress) { data = static_cast<uint8_t*> (newAddress); }
//==============================================================================
/// Stores a complete representation of this value and its type in a packed data format.
/// It can later be reloaded with Value::deserialise() or ValueView::deserialise().
/// The OutputStream object can be any class which has a method write (const void*, size_t).
/// The data format is:
/// - The serialised Type data, as written by Type::serialise()
/// - The block of value data, which is a copy of getRawData(), the size being Type::getValueDataSize()
/// - If any strings are in the dictionary, this is followed by a packed int for the total size of
/// the remaining string block, then a sequence of null-terminated strings. String handles are
/// encoded as a byte offset into this table, where the first character of the first string = 1.
/// @see Value::deserialise, ValueView::deserialise
template <typename OutputStream>
void serialise (OutputStream&) const;
/// Returns an object containing a serialised representation of this value. This is a helper
/// function to make it easier to call serialise() without needing to use your own output
/// stream class.
SerialisedData serialise() const;
/// Recreates a temporary ValueView from serialised data that was created by the
/// ValueView::serialise() method.
/// If a ValueView is successfully deserialised from the data, the handler functor will be
/// called with this (temporary!) ValueView as its argument.
/// Any errors while reading the data will cause an Error exception to be thrown.
/// The InputData object will be left pointing to any remaining data after the value has been read.
/// @see Value::serialise
template <typename Handler>
static void deserialise (InputData&, Handler&& handleResult,
Allocator* allocator = nullptr);
private:
//==============================================================================
friend class Value;
Type type;
uint8_t* data = nullptr;
StringDictionary* stringDictionary = nullptr;
ValueView (StringDictionary&);
template <typename TargetType> TargetType readContentAs() const;
template <typename TargetType> TargetType castToType (TargetType*) const;
template <typename PrimitiveType> void setUnchecked (PrimitiveType);
void updateStringHandles (StringDictionary&, StringDictionary&);
ValueView operator[] (const void*) const = delete;
ValueView operator[] (bool) const = delete;
};
//==============================================================================
/** Represents the name and type of a member in an object.
@see Type
*/
struct MemberNameAndType
{
std::string_view name;
Type type;
};
/** Represents the name and value of a member in an object.
@see Value, ValueView
*/
struct MemberNameAndValue
{
const char* name;
ValueView value;
};
//==============================================================================
/**
Stores a value of any type that the Type class can represent.
A Value class can be treated as a by-value class, and manages all the storage needed to
represent a ValueView object.
The ValueView and Value classes differ in that ValueView does not own the data that it
points to, but Value does. A ValueView should be used as a temporary wrapper around some
data whose lifetime can be trusted to outlive the ValueView object.
The purpose of these classes is to allow manipulation of complex, dynamically-typed objects
where the data holding a value is stored in a contiguous, packed, well-specified data
format, so that it can be manipulated directly as raw memory when necessary. The ValueView
is a lightweight wrapper around a type and a pointer to the raw data containing a value of that
type. The Value class provides the same interface, but also owns the storage needed, and can
return a ValueView of itself.
The Value class is versatile enough, and close enough to JSON's architecture that it can be
parsed and printed as JSON (though storing a Value as JSON will be a slightly lossy operation
as JSON has fewer types).
@see ValueView, Type, choc::json::parse(), choc::json::toString()
*/
class Value final
{
public:
/// Creates an empty value with a type of 'void'.
Value();
Value (Value&&);
Value (const Value&);
Value& operator= (Value&&);
Value& operator= (const Value&);
/// Creates a zero-initialised value with the given type.
explicit Value (const Type&);
/// Creates a zero-initialised value with the given type.
explicit Value (Type&&);
/// Creates a deep-copy of the given ValueView.
explicit Value (const ValueView&);
/// Creates a deep-copy of the given ValueView.
explicit Value (ValueView&&);
/// Creates a deep-copy of the given ValueView.
Value& operator= (const ValueView&);
explicit Value (int32_t);
explicit Value (int64_t);
explicit Value (float);
explicit Value (double);
explicit Value (bool);
explicit Value (std::string_view);
explicit Value (const char*);
//==============================================================================
/// Appends an element to this object, if it's an array. If not, then this will throw an Error exception.
template <typename ElementType>
void addArrayElement (ElementType);
/// Appends one or more members to an object, with the given names and values.
/// The value can be a supported primitive type, a string, or a Value or ValueView.
/// The function can take any number of name/value pairs.
/// This will throw an Error if this isn't possible for some reason (e.g. if the value isn't an object)
template <typename MemberType, typename... Others>
void addMember (std::string_view name, MemberType value, Others&&...);
/// Adds or changes an object member to a new value.
template <typename MemberType>
void setMember (std::string_view name, MemberType newValue);
//==============================================================================
bool isVoid() const { return value.isVoid(); }
bool isInt32() const { return value.isInt32(); }
bool isInt64() const { return value.isInt64(); }
bool isInt() const { return value.isInt(); }
bool isFloat32() const { return value.isFloat32(); }
bool isFloat64() const { return value.isFloat64(); }
bool isFloat() const { return value.isFloat(); }
bool isBool() const { return value.isBool(); }
bool isPrimitive() const { return value.isPrimitive(); }
bool isObject() const { return value.isObject(); }
bool isString() const { return value.isString(); }
bool isVector() const { return value.isVector(); }
bool isArray() const { return value.isArray(); }
//==============================================================================
int32_t getInt32() const { return value.getInt32(); } ///< Retrieves the value if this is an int32, otherwise throws an Error exception.
int64_t getInt64() const { return value.getInt64(); } ///< Retrieves the value if this is an int64, otherwise throws an Error exception.
float getFloat32() const { return value.getFloat32(); } ///< Retrieves the value if this is a float, otherwise throws an Error exception.
double getFloat64() const { return value.getFloat64(); } ///< Retrieves the value if this is a double, otherwise throws an Error exception.
bool getBool() const { return value.getBool(); } ///< Retrieves the value if this is a bool, otherwise throws an Error exception.
std::string_view getString() const { return value.getString(); } ///< Retrieves the value if this is a string, otherwise throws an Error exception.
StringDictionary::Handle getStringHandle() const { return value.getStringHandle(); } ///< Retrieves the value if this is a string handle, otherwise throws an Error exception.
explicit operator int32_t() const { return value.getInt32(); } ///< If the object is not an int32, this will throw an Error.
explicit operator int64_t() const { return value.getInt64(); } ///< If the object is not an int64, this will throw an Error.
explicit operator float() const { return value.getFloat32(); } ///< If the object is not a float, this will throw an Error.
explicit operator double() const { return value.getFloat64(); } ///< If the object is not a double, this will throw an Error.
explicit operator bool() const { return value.getBool(); } ///< If the object is not a bool, this will throw an Error.
explicit operator std::string_view() const { return value.getString(); } ///< If the object is not a string, this will throw an Error.
/// Attempts to cast this value to the given primitive target type. If the type is void or something that
/// can't be cast, it will throw an exception. This will do some minor casting, such as ints to doubles,
/// but won't attempt do any kind of string to number conversions.
template <typename TargetType> TargetType get() const;
/// Attempts to get this value as the given target type, but if this isn't possible,
/// returns the default value provided instead of throwing an Error.
template <typename TargetType> TargetType getWithDefault (TargetType defaultValue) const;
/// A handy way to convert this value as a string where possible, or to return an empty
/// string (without throwing any errors) if not possible. The function is basically the
/// same as calling getWithDefault<std::string> ({})
std::string toString() const;
/// If this object is a vector, array or object, this returns the number of items it contains; otherwise
/// it will throw an Error exception.
uint32_t size() const { return value.size(); }
/// If this object is an array or vector, and the index is valid, this returns one of its elements.
/// Note that this returns a view of the parent Value, which will become invalid as soon as any
/// change is made to the parent Value.
/// Throws an error exception if the object is not a vector or the index is out of range.
ValueView operator[] (int index) const { return value[index]; }
/// If this object is an array or vector, and the index is valid, this returns one of its elements.
/// Note that this returns a view of the parent Value, which will become invalid as soon as any
/// change is made to the parent Value.
/// Throws an error exception if the object is not a vector or the index is out of range.
ValueView operator[] (uint32_t index) const { return value[index]; }
/// If this object is an array or vector, and the index and length do not exceed its bounds, this
/// will return a view onto a range of its elements.
/// Throws an error exception if the object is not a vector or the range is invalid.
ValueView getElementRange (uint32_t startIndex, uint32_t length) const { return value.getElementRange (startIndex, length); }
//==============================================================================
/// Performs a comparison between two values, where only a bit-for-bit match is
/// considered to be true.
bool operator== (const ValueView& other) const { return value == other; }
/// Performs a comparison between two values, where only a bit-for-bit match is
/// considered to be true.
bool operator!= (const ValueView& other) const { return value != other; }
//==============================================================================
/// Iterating a Value is only valid for an array, vector or object.
ValueView::Iterator begin() const;
ValueView::EndIterator end() const;
//==============================================================================
/// Returns the class name of this object.
/// This will throw an error if the value is not an object.
std::string_view getObjectClassName() const { return value.getObjectClassName(); }
/// Returns true if this is an object with the given class-name.
bool isObjectWithClassName (std::string_view name) const { return value.isObjectWithClassName (name); }
/// Returns the name and value of a member by index.
/// This will throw an error if the value is not an object of if the index is out of range. (Use
/// size() to find out how many members there are). To get a named value from an object, you can
/// use operator[].
/// @see size
MemberNameAndValue getObjectMemberAt (uint32_t index) const { return value.getObjectMemberAt (index); }
/// Returns the value of a named member, or a void value if no such member exists.
/// Note that this returns a view of the parent Value, which will become invalid as soon as any
/// change is made to the parent Value.
/// This will throw an error if the value is not an object.
ValueView operator[] (std::string_view name) const { return value[name]; }
/// Returns the value of a named member, or a void value if no such member exists.
/// Note that this returns a view of the parent Value, which will become invalid as soon as any
/// change is made to the parent Value.
/// This will throw an error if the value is not an object.
ValueView operator[] (const char* name) const { return value[name]; }
/// Returns true if this is an object and contains the given member name.
bool hasObjectMember (std::string_view name) const { return value.hasObjectMember (name); }
/// Returns a ValueView of this Value. The ValueView will become invalid as soon as any change is made to this Value.
operator const ValueView&() const { return value; }
/// Returns a ValueView of this Value. The ValueView will become invalid as soon as any change is made to this Value.
const ValueView& getView() const { return value; }
/// Returns a mutable reference to the ValueView held inside this Value. This is only for use if you know what you're doing.
ValueView& getViewReference() { return value; }
/// Returns the type of this value.
const Type& getType() const { return value.getType(); }
/// Returns a pointer to the raw data that stores this value.
const void* getRawData() const { return packedData.data(); }
/// Returns a pointer to the raw data that stores this value.
void* getRawData() { return packedData.data(); }
/// Returns the size of the raw data that stores this value.
size_t getRawDataSize() const { return packedData.size(); }
/// Gets a pointer to the string dictionary that the view is using, or nullptr
/// if it doesn't have one.
StringDictionary* getDictionary() const { return value.getDictionary(); }
/// Stores a complete representation of this value and its type in a packed data format.
/// It can later be reloaded with Value::deserialise() or ValueView::deserialise().
/// The OutputStream object can be any class which has a method write (const void*, size_t).
/// The data format is:
/// - The serialised Type data, as written by Type::serialise()
/// - The block of value data, which is a copy of getRawData(), the size being Type::getValueDataSize()
/// - If any strings are in the dictionary, this is followed by a packed int for the total size of
/// the remaining string block, then a sequence of null-terminated strings. String handles are
/// encoded as a byte offset into this table, where the first character of the first string = 1.
/// @see Value::deserialise, ValueView::deserialise
template <typename OutputStream>
void serialise (OutputStream&) const;
/// Returns an object containing a serialised representation of this value. This is a helper
/// function to make it easier to call serialise() without needing to use your own output
/// stream class.
SerialisedData serialise() const;
/// Recreates a Value from serialised data that was created by the Value::serialise() method.
/// Any errors while reading the data will cause an Error exception to be thrown.
/// The InputData object will be left pointing to any remaining data after the value has been read.
/// @see Value::serialise
static Value deserialise (InputData&);
/// @internal
Value (Type&&, const void*, size_t);
/// @internal
Value (Type&&, const void*, size_t, StringDictionary*);
/// @internal
Value (const Type&, const void*, size_t, StringDictionary*);
private:
//==============================================================================
Value (const void*) = delete;
void appendData (const void*, size_t);
void appendValue (const ValueView&);
void appendMember (std::string_view, Type&&, const void*, size_t);
void changeMember (uint32_t, const Type&, void*, StringDictionary*);
std::vector<uint8_t> packedData;
SimpleStringDictionary dictionary;
ValueView value;
};
//==============================================================================
static Value createInt32 (int32_t);
static Value createInt64 (int64_t);
static Value createFloat32 (float);
static Value createFloat64 (double);
static Value createBool (bool);
static Value createPrimitive (int32_t);
static Value createPrimitive (int64_t);
static Value createPrimitive (float);
static Value createPrimitive (double);
static Value createPrimitive (bool);
static Value createString (std::string_view);
/// Allocates a vector, populating it from an array of primitive values.
template <typename ElementType>
static Value createVector (const ElementType* sourceElements, uint32_t numElements);
/// Allocates a vector, populating it using a functor to return the initial primitive values.
/// The functor must be a class or lambda which takes a uint32_t index parameter and returns
/// the primitive value for that index. The type of the returned primitive is used as the
/// vector's element type.
template <typename GetElementValue>
static Value createVector (uint32_t numElements, const GetElementValue& getValueForIndex);
/// Creates an empty array (to which elements can then be appended with addArrayElement)
static Value createEmptyArray();
/// Allocates an array, populating it using a functor to return the initial values.
/// The functor must be a class or lambda which takes a uint32_t index parameter and returns
/// either Value objects or primitive types to store at that index.
template <typename GetElementValue>
static Value createArray (uint32_t numElements, const GetElementValue& getValueForIndex);
/// Allocates an array which is a packed array of vector primitives, populating it using a
/// functor to return the initial values.
/// The functor must be a class or lambda which takes two uint32_t index parameters (the outer
/// and inner indices for the required element) and returns a primitive type to store at that
/// location.
template <typename GetElementValue>
static Value createArray (uint32_t numArrayElements, uint32_t numVectorElements, const GetElementValue& getValueAt);
/// Creates an array from an iterable container such as a std::vector. The container
/// must contain either Values, or primitive elements which can be turned into Values.
template <typename ContainerType>
static Value createArray (const ContainerType&);
/// Allocates a copy of a packed array of vector primitives.
template <typename ElementType>
static Value create2DArray (const ElementType* sourceElements, uint32_t numArrayElements, uint32_t numVectorElements);
/// Creates a view directly onto a packed array of primitives.
/// The ValueView that is returned will not take a copy of the data, so its lifetime must be managed by the caller.
template <typename ElementType>
static ValueView createArrayView (ElementType* targetData, uint32_t numElements);
/// Creates a view directly onto a packed array of vector primitives.