aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/utils/SkJSON.h
diff options
context:
space:
mode:
authorGravatar Florin Malita <fmalita@chromium.org>2018-06-19 11:27:20 -0400
committerGravatar Kevin Lubick <kjlubick@google.com>2018-06-19 18:23:30 +0000
commit80452bee11ebe6708ea459ea34e526a44c04bdb0 (patch)
tree2e74fe66a6554f81909188b423f7ce4055cdaea3 /src/utils/SkJSON.h
parent96aa535b782f31df0f063213c2958acba32a808d (diff)
Fold SkJSON into Skia/utils
It's a tiny, core-ish component -- might as well treat as such to simplify dependencies. Change-Id: I6f31ce2d151f9a629d88bfc7f15d64891d5150c0 Reviewed-on: https://skia-review.googlesource.com/135780 Reviewed-by: Mike Klein <mtklein@google.com> Reviewed-by: Kevin Lubick <kjlubick@google.com> Commit-Queue: Florin Malita <fmalita@chromium.org>
Diffstat (limited to 'src/utils/SkJSON.h')
-rw-r--r--src/utils/SkJSON.h360
1 files changed, 360 insertions, 0 deletions
diff --git a/src/utils/SkJSON.h b/src/utils/SkJSON.h
new file mode 100644
index 0000000000..8baf9fb8fc
--- /dev/null
+++ b/src/utils/SkJSON.h
@@ -0,0 +1,360 @@
+/*
+ * 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 "SkTo.h"
+#include "SkTypes.h"
+
+#include <cstring>
+
+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<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");
+ * }
+ * }
+ */
+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 <typename T>
+ bool is() const { return this->getType() == T::kType; }
+
+ /**
+ * Unguarded conversion to facade types.
+ *
+ * @return The record cast as facade type T&.
+ */
+ template <typename T>
+ const T& as() const {
+ SkASSERT(this->is<T>());
+ return *reinterpret_cast<const T*>(this);
+ }
+
+ /**
+ * Guarded conversion to facade types.
+ *
+ * @return The record cast as facade type T*.
+ */
+ 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;
+
+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
+};
+
+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<bool>();
+ }
+};
+
+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<double>(*this->cast<int32_t>())
+ : static_cast<double>(*this->cast<float>());
+ }
+};
+
+template <typename T, Value::Type vtype>
+class VectorValue : public Value {
+public:
+ using ValueT = T;
+ static constexpr Type kType = vtype;
+
+ size_t size() const {
+ SkASSERT(this->getType() == kType);
+ return *this->ptr<size_t>();
+ }
+
+ 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& operator[](size_t i) const {
+ SkASSERT(this->getType() == kType);
+ SkASSERT(i < this->size());
+
+ return *(this->begin() + i);
+ }
+};
+
+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:
+ // 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<char>());
+ 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 {
+ return this->getTag() == Tag::kShortString
+ ? strchr(this->cast<char>(), '\0')
+ : this->cast<VectorValue<char, Value::Type::kString>>()->end();
+ }
+};
+
+struct Member {
+ StringValue fKey;
+ Value fValue;
+};
+
+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:
+ 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
+