blob: a0d77626d3706922ebba95c50458c9908935b742 [file] [log] [blame]
/*
* 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 "include/core/SkTypes.h"
#include "include/private/SkNoncopyable.h"
#include "include/private/SkTo.h"
#include "src/core/SkArenaAlloc.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
-- lowest 3 bits reserved for tag storage
*/
enum class Tag : uint8_t {
// n.b.: we picked kShortString == 0 on purpose,
// to enable certain short-string optimizations.
kShortString = 0b00000000, // inline payload
kNull = 0b00000001, // no payload
kBool = 0b00000010, // inline payload
kInt = 0b00000011, // inline payload
kFloat = 0b00000100, // inline payload
kString = 0b00000101, // ptr to external storage
kArray = 0b00000110, // ptr to external storage
kObject = 0b00000111, // ptr to external storage
};
inline static constexpr uint8_t kTagMask = 0b00000111;
void init_tagged(Tag);
void init_tagged_pointer(Tag, void*);
Tag getTag() const {
return static_cast<Tag>(fData8[0] & kTagMask);
}
// Access the record payload as T.
//
// Since the tag is stored in the lower bits, we skip the first word whenever feasible.
//
// E.g. (U == unused)
//
// uint8_t
// -----------------------------------------------------------------------
// |TAG| U | val8 | U | U | U | U | U | U |
// -----------------------------------------------------------------------
//
// uint16_t
// -----------------------------------------------------------------------
// |TAG| U | val16 | U | U |
// -----------------------------------------------------------------------
//
// uint32_t
// -----------------------------------------------------------------------
// |TAG| U | val32 |
// -----------------------------------------------------------------------
//
// T* (32b)
// -----------------------------------------------------------------------
// |TAG| U | T* (32bits) |
// -----------------------------------------------------------------------
//
// T* (64b)
// -----------------------------------------------------------------------
// |TAG| T* (61bits) |
// -----------------------------------------------------------------------
//
template <typename T>
const T* cast() const {
static_assert(sizeof (T) <= sizeof(Value), "");
static_assert(alignof(T) <= alignof(Value), "");
return (sizeof(T) > sizeof(*this) / 2)
? reinterpret_cast<const T*>(this) + 0 // need all the bits
: reinterpret_cast<const T*>(this) + 1; // skip the first word (where the tag lives)
}
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 lower bits of the pointer as tag storage.
: reinterpret_cast<T*>(*this->cast<uintptr_t>() & ~static_cast<uintptr_t>(kTagMask));
}
private:
inline static constexpr size_t kValueSize = 8;
uint8_t fData8[kValueSize];
#if !defined(SK_CPU_LENDIAN)
// 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:
inline static constexpr Type kType = Type::kNull;
NullValue();
};
class BoolValue final : public Value {
public:
inline 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:
inline 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;
inline 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:
inline 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;
const Member& operator[](size_t i) const {
return this->VectorValue::operator[](i);
}
};
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