/* * Copyright 2018 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef SkJSON_DEFINED #define SkJSON_DEFINED #include "SkArenaAlloc.h" #include "SkNoncopyable.h" #include "SkTo.h" #include "SkTypes.h" #include class SkString; class SkWStream; namespace skjson { /** * A fast and likely non-conforming JSON parser. * * Some known limitations/compromises: * * -- single-precision FP numbers * * -- missing string unescaping (no current users, could be easily added) * * * Values are opaque, fixed-size (64 bits), immutable records. * * They can be converted to facade types for type-specific functionality. * * E.g.: * * if (v.is()) { * for (const auto& item : v.as()) { * if (const NumberValue* n = item) { * printf("Found number: %f", **n); * } * } * } * * if (v.is()) { * const StringValue* id = v.as()["id"]; * if (id) { * printf("Found object ID: %s", id->begin()); * } else { * printf("Missing object ID"); * } * } */ class alignas(8) Value { public: enum class Type { kNull, kBool, kNumber, kString, kArray, kObject, }; /** * @return The type of this value. */ Type getType() const; /** * @return True if the record matches the facade type T. */ template bool is() const { return this->getType() == T::kType; } /** * Unguarded conversion to facade types. * * @return The record cast as facade type T&. */ template const T& as() const { SkASSERT(this->is()); return *reinterpret_cast(this); } /** * Guarded conversion to facade types. * * @return The record cast as facade type T*. */ template operator const T*() const { return this->is() ? &this->as() : nullptr; } /** * @return The string representation of this value. */ SkString toString() const; 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(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 const T* cast() const { static_assert(sizeof (T) <= sizeof(Value), ""); static_assert(alignof(T) <= alignof(Value), ""); return reinterpret_cast(this); } template T* cast() { return const_cast(const_cast(this)->cast()); } // Access the pointer payload. template 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() // For 64-bit, we use the high bits of the pointer as tag storage. : reinterpret_cast(*this->cast() & 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(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 }; class NullValue final : public Value { public: static constexpr Type kType = Type::kNull; NullValue(); }; 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(); } }; class NumberValue final : public Value { public: static constexpr Type kType = Type::kNumber; 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(*this->cast()) : static_cast(*this->cast()); } }; template class VectorValue : public Value { public: using ValueT = T; static constexpr Type kType = vtype; size_t size() const { SkASSERT(this->getType() == kType); return *this->ptr(); } const T* begin() const { SkASSERT(this->getType() == kType); const auto* size_ptr = this->ptr(); return reinterpret_cast(size_ptr + 1); } const T* end() const { SkASSERT(this->getType() == kType); const auto* size_ptr = this->ptr(); return reinterpret_cast(size_ptr + 1) + *size_ptr; } const T& operator[](size_t i) const { SkASSERT(this->getType() == kType); SkASSERT(i < this->size()); return *(this->begin() + i); } }; class ArrayValue final : public VectorValue { 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: // We don't bother storing a length for short strings on the assumption // that strlen is fast in this case. If this becomes problematic, we // can either go back to storing (7-len) in the tag byte or write a fast // short_strlen. return strlen(this->cast()); case Tag::kString: return this->cast>()->size(); default: return 0; } } const char* begin() const { return this->getTag() == Tag::kShortString ? this->cast() : this->cast>()->begin(); } const char* end() const { return this->getTag() == Tag::kShortString ? strchr(this->cast(), '\0') : this->cast>()->end(); } }; struct Member { StringValue fKey; Value fValue; }; class ObjectValue final : public VectorValue { 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: DOM(const char*, size_t); const Value& root() const { return fRoot; } void write(SkWStream*) const; private: SkArenaAlloc fAlloc; 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