aboutsummaryrefslogtreecommitdiffhomepage
path: root/modules/skjson/src/SkJSON.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/skjson/src/SkJSON.cpp')
-rw-r--r--modules/skjson/src/SkJSON.cpp481
1 files changed, 131 insertions, 350 deletions
diff --git a/modules/skjson/src/SkJSON.cpp b/modules/skjson/src/SkJSON.cpp
index 8af85ba695..b513c78536 100644
--- a/modules/skjson/src/SkJSON.cpp
+++ b/modules/skjson/src/SkJSON.cpp
@@ -9,7 +9,6 @@
#include "SkStream.h"
#include "SkString.h"
-#include "SkTo.h"
#include <cmath>
#include <vector>
@@ -18,357 +17,131 @@ namespace skjson {
//#define SK_JSON_REPORT_ERRORS
-namespace {
-
-/*
- Value's impl side:
-
- -- fixed 64-bit size
-
- -- 8-byte aligned
-
- -- union of:
-
- bool
- int32
- float
- char[8] (short string storage)
- external payload pointer
- -- highest 3 bits reserved for type storage
-
- */
static_assert( sizeof(Value) == 8, "");
static_assert(alignof(Value) == 8, "");
static constexpr size_t kRecAlign = alignof(Value);
-// The current record layout assumes LE and will take some tweaking for BE.
-#if defined(SK_CPU_BENDIAN)
-static_assert(false, "Big-endian builds are not supported.");
-#endif
-
-class ValueRec : public Value {
-public:
- static constexpr uint64_t kTypeBits = 3,
- kTypeShift = 64 - kTypeBits,
- kTypeMask = ((1ULL << kTypeBits) - 1) << kTypeShift;
-
- enum RecType : uint64_t {
- // We picked kShortString == 0 so that tag 0b000 and stored max_size-size (7-7=0)
- // conveniently overlap the '\0' terminator, allowing us to store a 7 character
- // C string inline.
- kShortString = 0b000ULL << kTypeShift, // inline payload
- kNull = 0b001ULL << kTypeShift, // no payload
- kBool = 0b010ULL << kTypeShift, // inline payload
- kInt = 0b011ULL << kTypeShift, // inline payload
- kFloat = 0b100ULL << kTypeShift, // inline payload
- kString = 0b101ULL << kTypeShift, // ptr to external storage
- kArray = 0b110ULL << kTypeShift, // ptr to external storage
- kObject = 0b111ULL << kTypeShift, // ptr to external storage
- };
-
- RecType getRecType() const {
- return static_cast<RecType>(*this->cast<uint64_t>() & kTypeMask);
- }
-
- // 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(ValueRec), "");
- static_assert(alignof(T) <= alignof(ValueRec), "");
- return reinterpret_cast<const T*>(this);
- }
-
- template <typename T>
- T* cast() { return const_cast<T*>(const_cast<const ValueRec*>(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 type storage.
- : reinterpret_cast<T*>(*this->cast<uintptr_t>() & ~kTypeMask);
- }
-
- // Type-bound recs only store their type.
- static ValueRec MakeTypeBound(RecType t) {
- ValueRec v;
- *v.cast<uint64_t>() = t;
- SkASSERT(v.getRecType() == t);
- return v;
- }
-
- // Primitive recs store a type and inline primitive payload.
- template <typename T>
- static ValueRec MakePrimitive(RecType t, T src) {
- ValueRec v = MakeTypeBound(t);
- *v.cast<T>() = src;
- SkASSERT(v.getRecType() == t);
- return v;
- }
-
- // Pointer recs store a type (in the upper kTypeBits bits) and a pointer.
- template <typename T>
- static ValueRec MakePtr(RecType t, const T* p) {
- SkASSERT((t & kTypeMask) == t);
- if (sizeof(uintptr_t) == sizeof(Value)) {
- // For 64-bit, we rely on the pointer hi bits being unused.
- SkASSERT(!(reinterpret_cast<uintptr_t>(p) & kTypeMask));
- }
-
- ValueRec v = MakeTypeBound(t);
- *v.cast<uintptr_t>() |= reinterpret_cast<uintptr_t>(p);
-
- SkASSERT(v.getRecType() == t);
- SkASSERT(v.ptr<T>() == p);
-
- return v;
- }
-
- // Vector recs point to externally allocated slabs with the following layout:
- //
- // [size_t n] [REC_0] ... [REC_n-1] [optional extra trailing storage]
- //
- // Long strings use extra_alloc_size == 1 to store the \0 terminator.
- template <typename T, size_t extra_alloc_size = 0>
- static ValueRec MakeVector(RecType t, const T* src, size_t size, SkArenaAlloc& alloc) {
- // For zero-size arrays, we just store a nullptr.
- size_t* size_ptr = nullptr;
-
- if (size) {
- // The Ts are already in memory, so their size should be safeish.
- const auto total_size = sizeof(size_t) + sizeof(T) * size + extra_alloc_size;
- size_ptr = reinterpret_cast<size_t*>(alloc.makeBytesAlignedTo(total_size, kRecAlign));
- auto* data_ptr = reinterpret_cast<T*>(size_ptr + 1);
- *size_ptr = size;
- memcpy(data_ptr, src, sizeof(T) * size);
- }
-
- return MakePtr(t, size_ptr);
- }
-
- size_t vectorSize(RecType t) const {
- if (this->is<NullValue>()) return 0;
- SkASSERT(this->getRecType() == t);
-
- const auto* size_ptr = this->ptr<const size_t>();
- return size_ptr ? *size_ptr : 0;
- }
-
- template <typename T>
- const T* vectorBegin(RecType t) const {
- if (this->is<NullValue>()) return nullptr;
- SkASSERT(this->getRecType() == t);
-
- const auto* size_ptr = this->ptr<const size_t>();
- return size_ptr ? reinterpret_cast<const T*>(size_ptr + 1) : nullptr;
- }
-
- template <typename T>
- const T* vectorEnd(RecType t) const {
- if (this->is<NullValue>()) return nullptr;
- SkASSERT(this->getRecType() == t);
-
- const auto* size_ptr = this->ptr<const size_t>();
- return size_ptr ? reinterpret_cast<const T*>(size_ptr + 1) + *size_ptr : nullptr;
- }
-
- // Strings have two flavors:
- //
- // -- short strings (len <= 7) -> these are stored inline, in the record
- // (one byte reserved for null terminator/type):
- //
- // [str] [\0]|[max_len - actual_len]
- //
- // Storing [max_len - actual_len] allows the 'len' field to double-up as a
- // null terminator when size == max_len (this works 'cause kShortString == 0).
- //
- // -- long strings (len > 7) -> these are externally allocated vectors (VectorRec<char>).
- //
- // The string data plus a null-char terminator are copied over.
- static constexpr size_t kMaxInlineStringSize = sizeof(Value) - 1;
-
- static ValueRec MakeString(const char* src, size_t size, SkArenaAlloc& alloc) {
- ValueRec v;
-
- if (size > kMaxInlineStringSize) {
- v = MakeVector<char, 1>(kString, src, size, alloc);
- const_cast<char *>(v.vectorBegin<char>(ValueRec::kString))[size] = '\0';
- } else {
- v = MakeTypeBound(kShortString);
- auto* payload = v.cast<char>();
- memcpy(payload, src, size);
- payload[size] = '\0';
-
- const auto len_tag = SkTo<char>(kMaxInlineStringSize - size);
- // This technically overwrites the type hi bits, but is safe because
- // 1) kShortString == 0
- // 2) 0 <= len_tag <= 7
- static_assert(kShortString == 0, "please don't break this");
- payload[kMaxInlineStringSize] = len_tag;
- SkASSERT(v.getRecType() == kShortString);
- }
- return v;
- }
-
- size_t stringSize() const {
- if (this->getRecType() == ValueRec::kShortString) {
- const auto* payload = this->cast<char>();
- return kMaxInlineStringSize - SkToSizeT(payload[kMaxInlineStringSize]);
- }
-
- return this->vectorSize(ValueRec::kString);
- }
-
- const char* stringBegin() const {
- if (this->getRecType() == ValueRec::kShortString) {
- return this->cast<char>();
- }
-
- return this->vectorBegin<char>(ValueRec::kString);
- }
+void Value::init_tagged(Tag t) {
+ memset(fData8, 0, sizeof(fData8));
+ fData8[Value::kTagOffset] = SkTo<uint8_t>(t);
+ SkASSERT(this->getTag() == t);
+}
- const char* stringEnd() const {
- if (this->getRecType() == ValueRec::kShortString) {
- const auto* payload = this->cast<char>();
- return payload + kMaxInlineStringSize - SkToSizeT(payload[kMaxInlineStringSize]);
- }
+// Pointer values store a type (in the upper kTagBits bits) and a pointer.
+void Value::init_tagged_pointer(Tag t, void* p) {
+ *this->cast<uintptr_t>() = reinterpret_cast<uintptr_t>(p);
- return this->vectorEnd<char>(ValueRec::kString);
+ if (sizeof(Value) == sizeof(uintptr_t)) {
+ // For 64-bit, we rely on the pointer upper bits being unused/zero.
+ SkASSERT(!(fData8[kTagOffset] & kTagMask));
+ fData8[kTagOffset] |= SkTo<uint8_t>(t);
+ } else {
+ // For 32-bit, we need to zero-initialize the upper 32 bits
+ SkASSERT(sizeof(Value) == sizeof(uintptr_t) * 2);
+ this->cast<uintptr_t>()[kTagOffset >> 2] = 0;
+ fData8[kTagOffset] = SkTo<uint8_t>(t);
}
-};
-} // namespace
-
-
-// Boring public Value glue.
-
-const Value& Value::Null() {
- static const Value g_null = ValueRec::MakeTypeBound(ValueRec::kNull);
- return g_null;
+ SkASSERT(this->getTag() == t);
+ SkASSERT(this->ptr<void>() == p);
}
-const Member& Member::Null() {
- static const Member g_null = { Value::Null().as<StringValue>(), Value::Null() };
- return g_null;
+NullValue::NullValue() {
+ this->init_tagged(Tag::kNull);
+ SkASSERT(this->getTag() == Tag::kNull);
}
-Value::Type Value::getType() const {
- static constexpr Value::Type kTypeMap[] = {
- Value::Type::kString, // kShortString
- Value::Type::kNull, // kNull
- Value::Type::kBool, // kBool
- Value::Type::kNumber, // kInt
- Value::Type::kNumber, // kFloat
- Value::Type::kString, // kString
- Value::Type::kArray, // kArray
- Value::Type::kObject, // kObject
- };
-
- const auto& rec = *reinterpret_cast<const ValueRec*>(this);
- const auto type_index = static_cast<size_t>(rec.getRecType() >> ValueRec::kTypeShift);
- SkASSERT(type_index < SK_ARRAY_COUNT(kTypeMap));
-
- return kTypeMap[type_index];
+BoolValue::BoolValue(bool b) {
+ this->init_tagged(Tag::kBool);
+ *this->cast<bool>() = b;
+ SkASSERT(this->getTag() == Tag::kBool);
}
-template <>
-bool PrimitiveValue<bool, Value::Type::kBool>::operator*() const {
- const auto& rec = *reinterpret_cast<const ValueRec*>(this);
-
- if (rec.is<NullValue>()) return false;
-
- SkASSERT(rec.getRecType() == ValueRec::kBool);
-
- return *rec.cast<bool>();
+NumberValue::NumberValue(int32_t i) {
+ this->init_tagged(Tag::kInt);
+ *this->cast<int32_t>() = i;
+ SkASSERT(this->getTag() == Tag::kInt);
}
-template <>
-double PrimitiveValue<double, Value::Type::kNumber>::operator*() const {
- const auto& rec = *reinterpret_cast<const ValueRec*>(this);
-
- if (rec.is<NullValue>()) return 0;
-
- SkASSERT(rec.getRecType() == ValueRec::kInt ||
- rec.getRecType() == ValueRec::kFloat);
-
- return rec.getRecType() == ValueRec::kInt
- ? static_cast<double>(*rec.cast<int32_t>())
- : static_cast<double>(*rec.cast<float>());
+NumberValue::NumberValue(float f) {
+ this->init_tagged(Tag::kFloat);
+ *this->cast<float>() = f;
+ SkASSERT(this->getTag() == Tag::kFloat);
}
-template <>
-size_t VectorValue<Value, Value::Type::kArray>::size() const {
- return reinterpret_cast<const ValueRec*>(this)->vectorSize(ValueRec::kArray);
+// Vector recs point to externally allocated slabs with the following layout:
+//
+// [size_t n] [REC_0] ... [REC_n-1] [optional extra trailing storage]
+//
+// Long strings use extra_alloc_size == 1 to store the \0 terminator.
+//
+template <typename T, size_t extra_alloc_size = 0>
+static void* MakeVector(const void* src, size_t size, SkArenaAlloc& alloc) {
+ // The Ts are already in memory, so their size should be safe.
+ const auto total_size = sizeof(size_t) + size * sizeof(T) + extra_alloc_size;
+ auto* size_ptr = reinterpret_cast<size_t*>(alloc.makeBytesAlignedTo(total_size, kRecAlign));
+ auto* data_ptr = reinterpret_cast<void*>(size_ptr + 1);
+ *size_ptr = size;
+ memcpy(data_ptr, src, size * sizeof(T));
+
+ return size_ptr;
}
-template <>
-const Value* VectorValue<Value, Value::Type::kArray>::begin() const {
- return reinterpret_cast<const ValueRec*>(this)->vectorBegin<Value>(ValueRec::kArray);
+ArrayValue::ArrayValue(const Value* src, size_t size, SkArenaAlloc& alloc) {
+ this->init_tagged_pointer(Tag::kArray, MakeVector<Value>(src, size, alloc));
+ SkASSERT(this->getTag() == Tag::kArray);
}
-template <>
-const Value* VectorValue<Value, Value::Type::kArray>::end() const {
- return reinterpret_cast<const ValueRec*>(this)->vectorEnd<Value>(ValueRec::kArray);
-}
+// Strings have two flavors:
+//
+// -- short strings (len <= 7) -> these are stored inline, in the record
+// (one byte reserved for null terminator/type):
+//
+// [str] [\0]|[max_len - actual_len]
+//
+// Storing [max_len - actual_len] allows the 'len' field to double-up as a
+// null terminator when size == max_len (this works 'cause kShortString == 0).
+//
+// -- long strings (len > 7) -> these are externally allocated vectors (VectorRec<char>).
+//
+// The string data plus a null-char terminator are copied over.
+//
+StringValue::StringValue(const char* src, size_t size, SkArenaAlloc& alloc) {
+ if (size > kMaxInlineStringSize) {
+ this->init_tagged_pointer(Tag::kString, MakeVector<char, 1>(src, size, alloc));
-template <>
-size_t VectorValue<Member, Value::Type::kObject>::size() const {
- return reinterpret_cast<const ValueRec*>(this)->vectorSize(ValueRec::kObject);
-}
+ auto* data = this->cast<VectorValue<char, Value::Type::kString>>()->begin();
+ const_cast<char*>(data)[size] = '\0';
+ SkASSERT(this->getTag() == Tag::kString);
+ return;
+ }
-template <>
-const Member* VectorValue<Member, Value::Type::kObject>::begin() const {
- return reinterpret_cast<const ValueRec*>(this)->vectorBegin<Member>(ValueRec::kObject);
-}
+ this->init_tagged(Tag::kShortString);
-template <>
-const Member* VectorValue<Member, Value::Type::kObject>::end() const {
- return reinterpret_cast<const ValueRec*>(this)->vectorEnd<Member>(ValueRec::kObject);
-}
+ auto* payload = this->cast<char>();
+ memcpy(payload, src, size);
+ payload[size] = '\0';
-template <>
-size_t VectorValue<char, Value::Type::kString>::size() const {
- return reinterpret_cast<const ValueRec*>(this)->stringSize();
-}
+ const auto len_tag = SkTo<char>(kMaxInlineStringSize - size);
+ // This technically overwrites the tag, but is safe because
+ // 1) kShortString == 0
+ // 2) 0 <= len_tag <= 7
+ static_assert(static_cast<uint8_t>(Tag::kShortString) == 0, "please don't break this");
+ payload[kMaxInlineStringSize] = len_tag;
-template <>
-const char* VectorValue<char, Value::Type::kString>::begin() const {
- return reinterpret_cast<const ValueRec*>(this)->stringBegin();
+ SkASSERT(this->getTag() == Tag::kShortString);
}
-template <>
-const char* VectorValue<char, Value::Type::kString>::end() const {
- return reinterpret_cast<const ValueRec*>(this)->stringEnd();
+ObjectValue::ObjectValue(const Member* src, size_t size, SkArenaAlloc& alloc) {
+ this->init_tagged_pointer(Tag::kObject, MakeVector<Member>(src, size, alloc));
+ SkASSERT(this->getTag() == Tag::kObject);
}
+
+// Boring public Value glue.
+
const Value& ObjectValue::operator[](const char* key) const {
// Reverse search for duplicates resolution (policy: return last).
const auto* begin = this->begin();
@@ -381,7 +154,8 @@ const Value& ObjectValue::operator[](const char* key) const {
}
}
- return Value::Null();
+ static const Value g_null = NullValue();
+ return g_null;
}
namespace {
@@ -457,7 +231,7 @@ public:
fScopeStack.reserve(kScopeStackReserve);
}
- const Value& parse(const char* p) {
+ const Value parse(const char* p) {
p = skip_ws(p);
switch (*p) {
@@ -466,7 +240,7 @@ public:
case '[':
goto match_array;
default:
- return this->error(Value::Null(), p, "invalid top-level value");
+ return this->error(NullValue(), p, "invalid top-level value");
}
match_object:
@@ -480,15 +254,15 @@ public:
// goto match_object_key;
match_object_key:
p = skip_ws(p);
- if (*p != '"') return this->error(Value::Null(), p, "expected object key");
+ if (*p != '"') return this->error(NullValue(), p, "expected object key");
p = this->matchString(p, [this](const char* key, size_t size) {
this->pushObjectKey(key, size);
});
- if (!p) return Value::Null();
+ if (!p) return NullValue();
p = skip_ws(p);
- if (*p != ':') return this->error(Value::Null(), p, "expected ':' separator");
+ if (*p != ':') return this->error(NullValue(), p, "expected ':' separator");
++p;
@@ -498,7 +272,7 @@ public:
switch (*p) {
case '\0':
- return this->error(Value::Null(), p, "unexpected input end");
+ return this->error(NullValue(), p, "unexpected input end");
case '"':
p = this->matchString(p, [this](const char* str, size_t size) {
this->pushString(str, size);
@@ -522,7 +296,7 @@ public:
break;
}
- if (!p) return Value::Null();
+ if (!p) return NullValue();
// goto match_post_value;
match_post_value:
@@ -542,7 +316,7 @@ public:
case '}':
goto pop_object;
default:
- return this->error(Value::Null(), p - 1, "unexpected value-trailing token");
+ return this->error(NullValue(), p - 1, "unexpected value-trailing token");
}
// unreachable
@@ -552,7 +326,7 @@ public:
SkASSERT(*p == '}');
if (fScopeStack.back() < 0) {
- return this->error(Value::Null(), p, "unexpected object terminator");
+ return this->error(NullValue(), p, "unexpected object terminator");
}
this->popObjectScope();
@@ -565,13 +339,11 @@ public:
if (fScopeStack.empty()) {
SkASSERT(fValueStack.size() == 1);
- auto* root = fAlloc.make<Value>();
- *root = fValueStack.front();
// Stop condition: parsed the top level element and there is no trailing garbage.
return *skip_ws(p) == '\0'
- ? *root
- : this->error(Value::Null(), p, "trailing root garbage");
+ ? fValueStack.front()
+ : this->error(NullValue(), p, "trailing root garbage");
}
goto match_post_value;
@@ -589,7 +361,7 @@ public:
SkASSERT(*p == ']');
if (fScopeStack.back() >= 0) {
- return this->error(Value::Null(), p, "unexpected array terminator");
+ return this->error(NullValue(), p, "unexpected array terminator");
}
this->popArrayScope();
@@ -597,7 +369,7 @@ public:
goto pop_common;
SkASSERT(false);
- return Value::Null();
+ return NullValue();
}
const SkString& getError() const {
@@ -614,11 +386,12 @@ private:
SkString fError;
- template <typename T>
- void popScopeAsVec(ValueRec::RecType type, size_t scope_start) {
+ template <typename VectorT>
+ void popScopeAsVec(size_t scope_start) {
SkASSERT(scope_start > 0);
SkASSERT(scope_start <= fValueStack.size());
+ using T = typename VectorT::ValueT;
static_assert( sizeof(T) >= sizeof(Value), "");
static_assert( sizeof(T) % sizeof(Value) == 0, "");
static_assert(alignof(T) == alignof(Value), "");
@@ -630,7 +403,7 @@ private:
const auto* begin = reinterpret_cast<const T*>(fValueStack.data() + scope_start);
// Instantiate the placeholder value added in onPush{Object/Array}.
- fValueStack[scope_start - 1] = ValueRec::MakeVector<T>(type, begin, count, fAlloc);
+ fValueStack[scope_start - 1] = VectorT(begin, count, fAlloc);
// Drop the current scope.
fScopeStack.pop_back();
@@ -648,7 +421,7 @@ private:
void popObjectScope() {
const auto scope_start = fScopeStack.back();
SkASSERT(scope_start > 0);
- this->popScopeAsVec<Member>(ValueRec::kObject, SkTo<size_t>(scope_start));
+ this->popScopeAsVec<ObjectValue>(SkTo<size_t>(scope_start));
SkDEBUGCODE(
const auto& obj = fValueStack.back().as<ObjectValue>();
@@ -670,7 +443,7 @@ private:
void popArrayScope() {
const auto scope_start = -fScopeStack.back();
SkASSERT(scope_start > 0);
- this->popScopeAsVec<Value>(ValueRec::kArray, SkTo<size_t>(scope_start));
+ this->popScopeAsVec<ArrayValue>(SkTo<size_t>(scope_start));
SkDEBUGCODE(
const auto& arr = fValueStack.back().as<ArrayValue>();
@@ -686,31 +459,31 @@ private:
}
void pushTrue() {
- fValueStack.push_back(ValueRec::MakePrimitive<bool>(ValueRec::kBool, true));
+ fValueStack.push_back(BoolValue(true));
}
void pushFalse() {
- fValueStack.push_back(ValueRec::MakePrimitive<bool>(ValueRec::kBool, false));
+ fValueStack.push_back(BoolValue(false));
}
void pushNull() {
- fValueStack.push_back(ValueRec::MakeTypeBound(ValueRec::kNull));
+ fValueStack.push_back(NullValue());
}
void pushString(const char* s, size_t size) {
- fValueStack.push_back(ValueRec::MakeString(s, size, fAlloc));
+ fValueStack.push_back(StringValue(s, size, fAlloc));
}
void pushInt32(int32_t i) {
- fValueStack.push_back(ValueRec::MakePrimitive<int32_t>(ValueRec::kInt, i));
+ fValueStack.push_back(NumberValue(i));
}
void pushFloat(float f) {
- fValueStack.push_back(ValueRec::MakePrimitive<float>(ValueRec::kFloat, f));
+ fValueStack.push_back(NumberValue(f));
}
template <typename T>
- const T& error(const T& ret_val, const char* p, const char* msg) {
+ T error(T&& ret_val, const char* p, const char* msg) {
#if defined(SK_JSON_REPORT_ERRORS)
static constexpr size_t kMaxContext = 128;
fError = SkStringPrintf("%s: >", msg);
@@ -930,17 +703,25 @@ void Write(const Value& v, SkWStream* stream) {
} // namespace
+SkString Value::toString() const {
+ SkDynamicMemoryWStream wstream;
+ Write(*this, &wstream);
+ const auto data = wstream.detachAsData();
+ // TODO: is there a better way to pass data around without copying?
+ return SkString(static_cast<const char*>(data->data()), data->size());
+}
+
static constexpr size_t kMinChunkSize = 4096;
-DOM::DOM(const char* cstr)
+DOM::DOM(const char* c_str)
: fAlloc(kMinChunkSize) {
DOMParser parser(fAlloc);
- fRoot = &parser.parse(cstr);
+ fRoot = parser.parse(c_str);
}
void DOM::write(SkWStream* stream) const {
- Write(*fRoot, stream);
+ Write(fRoot, stream);
}
} // namespace skjson