/*
|
* 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 <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
|