diff options
Diffstat (limited to 'modules/skjson/include/SkJSON.h')
-rw-r--r-- | modules/skjson/include/SkJSON.h | 279 |
1 files changed, 30 insertions, 249 deletions
diff --git a/modules/skjson/include/SkJSON.h b/modules/skjson/include/SkJSON.h index 8e4231e7a7..4cb312c64f 100644 --- a/modules/skjson/include/SkJSON.h +++ b/modules/skjson/include/SkJSON.h @@ -11,7 +11,6 @@ #include "SkArenaAlloc.h" #include "SkTypes.h" -class SkString; class SkWStream; namespace skjson { @@ -28,26 +27,12 @@ namespace skjson { * * Values are opaque, fixed-size (64 bits), immutable records. * - * They can be converted to facade types for type-specific functionality. + * They can be freely converted to any of the facade types for type-specific functionality. * - * E.g.: + * Note: type checking is lazy/deferred, to facilitate chained property access - e.g. * - * if (v.is<ArrayValue>()) { - * for (const auto& item : v.as<ArrayValue>()) { - * if (const NumberValue* n = item) { - * printf("Found number: %f", **n); - * } - * } - * } - * - * if (v.is<ObjectValue>()) { - * const StringValue* id = v.as<ObjectValue>()["id"]; - * if (id) { - * printf("Found object ID: %s", id->begin()); - * } else { - * printf("Missing object ID"); - * } - * } + * if (!v.as<ObjectValue>()["foo"].as<ObjectValue>()["bar"].is<NullValue>()) + * LOG("found v.foo.bar!"); */ class alignas(8) Value { public: @@ -61,7 +46,7 @@ public: }; /** - * @return The type of this value. + * @return The public type of this record. */ Type getType() const; @@ -69,289 +54,85 @@ public: * @return True if the record matches the facade type T. */ template <typename T> - bool is() const { return this->getType() == T::kType; } + bool is() const { return T::IsType(this->getType()); } /** - * Unguarded conversion to facade types. + * @return The record cast as facade type T. * - * @return The record cast as facade type T&. + * Note: this is always safe, as proper typing is enforced in the facade methods. */ template <typename T> const T& as() const { - SkASSERT(this->is<T>()); - return *reinterpret_cast<const T*>(this); + return *reinterpret_cast<const T*>(this->is<T>() ? this : &Value::Null()); } /** - * Guarded conversion to facade types. - * - * @return The record cast as facade type T*. + * @return Null value singleton. */ - template <typename T> - operator const T*() const { - return this->is<T>() ? &this->as<T>() : nullptr; - } - - /** - * @return The string representation of this value. - */ - SkString toString() const; + static const Value& Null(); protected: - /* - Value implementation notes: - - -- fixed 64-bit size - - -- 8-byte aligned - - -- union of: - - bool - int32 - float - char[8] (short string storage) - external payload (tagged) pointer - - -- highest 3 bits reserved for type storage - - */ - enum class Tag : uint8_t { - // We picked kShortString == 0 so that tag 0x00 and stored max_size-size (7-7=0) - // conveniently overlap the '\0' terminator, allowing us to store a 7 character - // C string inline. - kShortString = 0b00000000, // inline payload - kNull = 0b00100000, // no payload - kBool = 0b01000000, // inline payload - kInt = 0b01100000, // inline payload - kFloat = 0b10000000, // inline payload - kString = 0b10100000, // ptr to external storage - kArray = 0b11000000, // ptr to external storage - kObject = 0b11100000, // ptr to external storage - }; - static constexpr uint8_t kTagMask = 0b11100000; - - void init_tagged(Tag); - void init_tagged_pointer(Tag, void*); - - Tag getTag() const { - return static_cast<Tag>(fData8[kTagOffset] & kTagMask); - } - - // Access the record data as T. - // - // This is also used to access the payload for inline records. Since the record type lives in - // the high bits, sizeof(T) must be less than sizeof(Value) when accessing inline payloads. - // - // E.g. - // - // uint8_t - // ----------------------------------------------------------------------- - // | val8 | val8 | val8 | val8 | val8 | val8 | val8 | TYPE| - // ----------------------------------------------------------------------- - // - // uint32_t - // ----------------------------------------------------------------------- - // | val32 | unused | TYPE| - // ----------------------------------------------------------------------- - // - // T* (64b) - // ----------------------------------------------------------------------- - // | T* (kTypeShift bits) |TYPE| - // ----------------------------------------------------------------------- - // - template <typename T> - const T* cast() const { - static_assert(sizeof (T) <= sizeof(Value), ""); - static_assert(alignof(T) <= alignof(Value), ""); - return reinterpret_cast<const T*>(this); - } - - template <typename T> - T* cast() { return const_cast<T*>(const_cast<const Value*>(this)->cast<T>()); } - - // Access the pointer payload. - template <typename T> - const T* ptr() const { - static_assert(sizeof(uintptr_t) == sizeof(Value) || - sizeof(uintptr_t) * 2 == sizeof(Value), ""); - - return (sizeof(uintptr_t) < sizeof(Value)) - // For 32-bit, pointers are stored unmodified. - ? *this->cast<const T*>() - // For 64-bit, we use the high bits of the pointer as tag storage. - : reinterpret_cast<T*>(*this->cast<uintptr_t>() & kTagPointerMask); - } - -private: - static constexpr size_t kValueSize = 8; - - uint8_t fData8[kValueSize]; - -#if defined(SK_CPU_LENDIAN) - static constexpr size_t kTagOffset = kValueSize - 1; - - static constexpr uintptr_t kTagPointerMask = - ~(static_cast<uintptr_t>(kTagMask) << ((sizeof(uintptr_t) - 1) * 8)); -#else - // The current value layout assumes LE and will take some tweaking for BE. - static_assert(false, "Big-endian builds are not supported at this time."); -#endif + uint8_t fData8[8]; }; class NullValue final : public Value { public: - static constexpr Type kType = Type::kNull; - - NullValue(); + static bool IsType(Value::Type t) { return t == Type::kNull; } }; -class BoolValue final : public Value { -public: - static constexpr Type kType = Type::kBool; - - explicit BoolValue(bool); - - bool operator *() const { - SkASSERT(this->getTag() == Tag::kBool); - return *this->cast<bool>(); - } -}; - -class NumberValue final : public Value { +template <typename T, Value::Type vtype> +class PrimitiveValue final : public Value { public: - static constexpr Type kType = Type::kNumber; + static bool IsType(Value::Type t) { return t == vtype; } - explicit NumberValue(int32_t); - explicit NumberValue(float); - - double operator *() const { - SkASSERT(this->getTag() == Tag::kInt || - this->getTag() == Tag::kFloat); - - return this->getTag() == Tag::kInt - ? static_cast<double>(*this->cast<int32_t>()) - : static_cast<double>(*this->cast<float>()); - } + T operator *() const; }; template <typename T, Value::Type vtype> class VectorValue : public Value { public: - using ValueT = T; - static constexpr Type kType = vtype; + static bool IsType(Value::Type t) { return t == vtype; } - size_t size() const { - SkASSERT(this->getType() == kType); - return *this->ptr<size_t>(); - } + size_t size() const; - const T* begin() const { - SkASSERT(this->getType() == kType); - const auto* size_ptr = this->ptr<size_t>(); - return reinterpret_cast<const T*>(size_ptr + 1); - } - - const T* end() const { - SkASSERT(this->getType() == kType); - const auto* size_ptr = this->ptr<size_t>(); - return reinterpret_cast<const T*>(size_ptr + 1) + *size_ptr; - } + const T* begin() const; + const T* end() const; const T& operator[](size_t i) const { - SkASSERT(this->getType() == kType); - SkASSERT(i < this->size()); - - return *(this->begin() + i); + return (i < this->size()) ? *(this->begin() + i) : T::Null(); } }; -class ArrayValue final : public VectorValue<Value, Value::Type::kArray> { -public: - ArrayValue(const Value* src, size_t size, SkArenaAlloc& alloc); -}; - -class StringValue final : public Value { -public: - static constexpr Type kType = Type::kString; - - StringValue(); - StringValue(const char* src, size_t size, SkArenaAlloc& alloc); - - size_t size() const { - switch (this->getTag()) { - case Tag::kShortString: - return kMaxInlineStringSize - SkToSizeT(this->cast<char>()[kMaxInlineStringSize]); - case Tag::kString: - return this->cast<VectorValue<char, Value::Type::kString>>()->size(); - default: - return 0; - } - } - - const char* begin() const { - return this->getTag() == Tag::kShortString - ? this->cast<char>() - : this->cast<VectorValue<char, Value::Type::kString>>()->begin(); - } - - const char* end() const { - if (this->getTag() == Tag::kShortString) { - const auto* payload = this->cast<char>(); - return payload + kMaxInlineStringSize - SkToSizeT(payload[kMaxInlineStringSize]); - } - return this->cast<VectorValue<char, Value::Type::kString>>()->end(); - } - -private: - static constexpr size_t kMaxInlineStringSize = sizeof(Value) - 1; -}; +using BoolValue = PrimitiveValue<bool , Value::Type::kBool >; +using NumberValue = PrimitiveValue<double, Value::Type::kNumber>; +using StringValue = VectorValue<char , Value::Type::kString>; +using ArrayValue = VectorValue<Value , Value::Type::kArray >; struct Member { StringValue fKey; Value fValue; + + static const Member& Null(); }; class ObjectValue final : public VectorValue<Member, Value::Type::kObject> { public: - ObjectValue(const Member* src, size_t size, SkArenaAlloc& alloc); - const Value& operator[](const char*) const; - -private: - // Not particularly interesting - hiding for disambiguation. - const Member& operator[](size_t i) const = delete; }; class DOM final : public SkNoncopyable { public: explicit DOM(const char*); - const Value& root() const { return fRoot; } + const Value& root() const { return *fRoot; } void write(SkWStream*) const; private: SkArenaAlloc fAlloc; - Value fRoot; + const Value* fRoot; }; -inline Value::Type Value::getType() const { - switch (this->getTag()) { - case Tag::kNull: return Type::kNull; - case Tag::kBool: return Type::kBool; - case Tag::kInt: return Type::kNumber; - case Tag::kFloat: return Type::kNumber; - case Tag::kShortString: return Type::kString; - case Tag::kString: return Type::kString; - case Tag::kArray: return Type::kArray; - case Tag::kObject: return Type::kObject; - } - - SkASSERT(false); // unreachable - return Type::kNull; -} - } // namespace skjson #endif // SkJSON_DEFINED |