SkJson
For now this is only wired to a bench and a couple of tests. Local numbers, for a ~500KB BM "compressed" json: micros bench 2456.54 json_rapidjson nonrendering 1192.38 json_skjson nonrendering Change-Id: I7b3514f84c7c525d1787722c43ad6095e3692563 Reviewed-on: https://skia-review.googlesource.com/127622 Reviewed-by: Mike Klein <mtklein@google.com> Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
parent
61e66867ae
commit
7796f00dcf
5
BUILD.gn
5
BUILD.gn
@ -1391,6 +1391,7 @@ if (skia_enable_tools) {
|
||||
]
|
||||
public_deps = [
|
||||
":gpu_tool_utils", # Test.h #includes headers from this target.
|
||||
"modules/skjson:tests",
|
||||
]
|
||||
}
|
||||
|
||||
@ -1405,6 +1406,9 @@ if (skia_enable_tools) {
|
||||
":skia",
|
||||
":tool_utils",
|
||||
]
|
||||
public_deps = [
|
||||
"modules/skjson:bench",
|
||||
]
|
||||
}
|
||||
|
||||
test_lib("experimental_svg_model") {
|
||||
@ -1745,6 +1749,7 @@ if (skia_enable_tools) {
|
||||
":flags",
|
||||
":gpu_tool_utils",
|
||||
":skia",
|
||||
"modules/skjson:fuzz",
|
||||
"modules/skottie:fuzz",
|
||||
"//third_party/jsoncpp",
|
||||
"//third_party/libpng",
|
||||
|
@ -28,10 +28,6 @@
|
||||
#include "SkSLCompiler.h"
|
||||
#endif
|
||||
|
||||
#if defined(SK_ENABLE_SKOTTIE)
|
||||
#include "Skottie.h"
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <regex>
|
||||
@ -47,22 +43,31 @@ DEFINE_string2(name, n, "", "If --type is 'api', fuzz the API with this name.");
|
||||
DEFINE_string2(dump, d, "", "If not empty, dump 'image*' or 'skp' types as a "
|
||||
"PNG with this name.");
|
||||
DEFINE_bool2(verbose, v, false, "Print more information while fuzzing.");
|
||||
DEFINE_string2(type, t, "", "How to interpret --bytes, one of:\n"
|
||||
"animated_image_decode\n"
|
||||
"api\n"
|
||||
"color_deserialize\n"
|
||||
"filter_fuzz (equivalent to Chrome's filter_fuzz_stub)\n"
|
||||
"image_decode\n"
|
||||
"image_mode\n"
|
||||
"image_scale\n"
|
||||
"path_deserialize\n"
|
||||
"pipe\n"
|
||||
"region_deserialize\n"
|
||||
"region_set_path\n"
|
||||
"skp\n"
|
||||
"sksl2glsl\n"
|
||||
"skottie_json\n"
|
||||
"textblob");
|
||||
|
||||
// This cannot be inlined in DEFINE_string2 due to interleaved ifdefs
|
||||
static constexpr char g_type_message[] = "How to interpret --bytes, one of:\n"
|
||||
"animated_image_decode\n"
|
||||
"api\n"
|
||||
"color_deserialize\n"
|
||||
"filter_fuzz (equivalent to Chrome's filter_fuzz_stub)\n"
|
||||
"image_decode\n"
|
||||
"image_mode\n"
|
||||
"image_scale\n"
|
||||
"path_deserialize\n"
|
||||
"pipe\n"
|
||||
"region_deserialize\n"
|
||||
"region_set_path\n"
|
||||
#if defined(SK_ENABLE_SKJSON)
|
||||
"skjson\n"
|
||||
#endif
|
||||
"skp\n"
|
||||
"sksl2glsl\n"
|
||||
#if defined(SK_ENABLE_SKOTTIE)
|
||||
"skottie_json\n"
|
||||
#endif
|
||||
"textblob";
|
||||
|
||||
DEFINE_string2(type, t, "", g_type_message);
|
||||
|
||||
static int fuzz_file(SkString path, SkString type);
|
||||
static uint8_t calculate_option(SkData*);
|
||||
@ -87,6 +92,10 @@ static void print_api_names();
|
||||
static void fuzz_sksl2glsl(sk_sp<SkData>);
|
||||
#endif
|
||||
|
||||
#if defined(SK_ENABLE_SKJSON)
|
||||
static void fuzz_skjson(sk_sp<SkData>);
|
||||
#endif
|
||||
|
||||
#if defined(SK_ENABLE_SKOTTIE)
|
||||
static void fuzz_skottie_json(sk_sp<SkData>);
|
||||
#endif
|
||||
@ -181,6 +190,12 @@ static int fuzz_file(SkString path, SkString type) {
|
||||
fuzz_skpipe(bytes);
|
||||
return 0;
|
||||
}
|
||||
#if defined(SK_ENABLE_SKJSON)
|
||||
if (type.equals("skjson")) {
|
||||
fuzz_skjson(bytes);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#if defined(SK_ENABLE_SKOTTIE)
|
||||
if (type.equals("skottie_json")) {
|
||||
fuzz_skottie_json(bytes);
|
||||
@ -266,6 +281,15 @@ static SkString try_auto_detect(SkString path, SkString* name) {
|
||||
return SkString("");
|
||||
}
|
||||
|
||||
#if defined(SK_ENABLE_SKJSON)
|
||||
void FuzzSkJSON(sk_sp<SkData> bytes);
|
||||
|
||||
static void fuzz_skjson(sk_sp<SkData> bytes){
|
||||
FuzzSkJSON(bytes);
|
||||
SkDebugf("[terminated] Done parsing!\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(SK_ENABLE_SKOTTIE)
|
||||
void FuzzSkottieJSON(sk_sp<SkData> bytes);
|
||||
|
||||
|
96
modules/skjson/BUILD.gn
Normal file
96
modules/skjson/BUILD.gn
Normal file
@ -0,0 +1,96 @@
|
||||
# Copyright 2018 Google Inc.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
declare_args() {
|
||||
skia_enable_skjson = true
|
||||
}
|
||||
|
||||
config("public_config") {
|
||||
if (skia_enable_skjson) {
|
||||
defines = [ "SK_ENABLE_SKJSON" ]
|
||||
include_dirs = [ "include" ]
|
||||
}
|
||||
}
|
||||
|
||||
source_set("skjson") {
|
||||
if (skia_enable_skjson) {
|
||||
public_configs = [ ":public_config" ]
|
||||
public = [
|
||||
"include/SkJSON.h",
|
||||
]
|
||||
sources = [
|
||||
"src/SkJSON.cpp",
|
||||
]
|
||||
configs += [ "../../:skia_private" ]
|
||||
deps = [
|
||||
"../..:skia",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
source_set("tests") {
|
||||
if (skia_enable_skjson) {
|
||||
testonly = true
|
||||
|
||||
configs += [
|
||||
"../..:skia_private",
|
||||
"../..:tests_config",
|
||||
]
|
||||
sources = [
|
||||
"src/SkJSONTest.cpp",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"../..:gpu_tool_utils",
|
||||
"../..:skia",
|
||||
]
|
||||
|
||||
public_deps = [
|
||||
":skjson",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
source_set("bench") {
|
||||
if (skia_enable_skjson) {
|
||||
testonly = true
|
||||
|
||||
configs += [
|
||||
"../..:bench_config",
|
||||
"../..:skia_private",
|
||||
]
|
||||
sources = [
|
||||
"src/SkJSONBench.cpp",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"../..:skia",
|
||||
"../../third_party/rapidjson",
|
||||
]
|
||||
|
||||
public_deps = [
|
||||
":skjson",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
source_set("fuzz") {
|
||||
if (skia_enable_skjson) {
|
||||
testonly = true
|
||||
|
||||
configs += [ "../..:skia_private" ]
|
||||
sources = [
|
||||
"src/FuzzSkJSON.cpp",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"../..:skia",
|
||||
]
|
||||
|
||||
public_deps = [
|
||||
":skjson",
|
||||
]
|
||||
}
|
||||
}
|
139
modules/skjson/include/SkJSON.h
Normal file
139
modules/skjson/include/SkJSON.h
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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 "SkTypes.h"
|
||||
|
||||
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 freely converted to any of the facade types for type-specific functionality.
|
||||
*
|
||||
* Note: type checking is lazy/deferred, to facilitate chained property access - e.g.
|
||||
*
|
||||
* if (!v.as<ObjectValue>()["foo"].as<ObjectValue>()["bar"].is<NullValue>())
|
||||
* LOG("found v.foo.bar!");
|
||||
*/
|
||||
class alignas(8) Value {
|
||||
public:
|
||||
enum class Type {
|
||||
kNull,
|
||||
kBool,
|
||||
kNumber,
|
||||
kString,
|
||||
kArray,
|
||||
kObject,
|
||||
};
|
||||
|
||||
/**
|
||||
* @return The public type of this record.
|
||||
*/
|
||||
Type getType() const;
|
||||
|
||||
/**
|
||||
* @return True if the record matches the facade type T.
|
||||
*/
|
||||
template <typename T>
|
||||
bool is() const { return T::IsType(this->getType()); }
|
||||
|
||||
/**
|
||||
* @return The record cast as facade type T.
|
||||
*
|
||||
* Note: this is always safe, as proper typing is enforced in the facade methods.
|
||||
*/
|
||||
template <typename T>
|
||||
const T& as() const {
|
||||
return *reinterpret_cast<const T*>(this->is<T>() ? this : &Value::Null());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Null value singleton.
|
||||
*/
|
||||
static const Value& Null();
|
||||
|
||||
protected:
|
||||
uint8_t fData8[8];
|
||||
};
|
||||
|
||||
class NullValue final : public Value {
|
||||
public:
|
||||
static bool IsType(Value::Type t) { return t == Type::kNull; }
|
||||
};
|
||||
|
||||
template <typename T, Value::Type vtype>
|
||||
class PrimitiveValue final : public Value {
|
||||
public:
|
||||
static bool IsType(Value::Type t) { return t == vtype; }
|
||||
|
||||
T operator *() const;
|
||||
};
|
||||
|
||||
template <typename T, Value::Type vtype>
|
||||
class VectorValue : public Value {
|
||||
public:
|
||||
static bool IsType(Value::Type t) { return t == vtype; }
|
||||
|
||||
size_t size() const;
|
||||
|
||||
const T* begin() const;
|
||||
const T* end() const;
|
||||
|
||||
const T& operator[](size_t i) const {
|
||||
return (i < this->size()) ? *(this->begin() + i) : T::Null();
|
||||
}
|
||||
};
|
||||
|
||||
using BoolValue = PrimitiveValue<bool , Value::Type::kBool >;
|
||||
using NumberValue = PrimitiveValue<double, Value::Type::kNumber>;
|
||||
using StringValue = VectorValue<char , Value::Type::kString>;
|
||||
using ArrayValue = VectorValue<Value , Value::Type::kArray >;
|
||||
|
||||
struct Member {
|
||||
StringValue fKey;
|
||||
Value fValue;
|
||||
|
||||
static const Member& Null();
|
||||
};
|
||||
|
||||
class ObjectValue final : public VectorValue<Member, Value::Type::kObject> {
|
||||
public:
|
||||
const Value& operator[](const char*) const;
|
||||
};
|
||||
|
||||
class DOM final : public SkNoncopyable {
|
||||
public:
|
||||
explicit DOM(const char*);
|
||||
|
||||
const Value& root() const { return *fRoot; }
|
||||
|
||||
void write(SkWStream*) const;
|
||||
|
||||
private:
|
||||
SkArenaAlloc fAlloc;
|
||||
const Value* fRoot;
|
||||
};
|
||||
|
||||
} // namespace skjson
|
||||
|
||||
#endif // SkJSON_DEFINED
|
||||
|
32
modules/skjson/src/FuzzSkJSON.cpp
Normal file
32
modules/skjson/src/FuzzSkJSON.cpp
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2018 Google, LLC
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "SkAutoMalloc.h"
|
||||
#include "SkData.h"
|
||||
#include "SkJSON.h"
|
||||
#include "SkStream.h"
|
||||
|
||||
void FuzzSkJSON(sk_sp<SkData> bytes) {
|
||||
// TODO: add a size + len skjson::DOM factory?
|
||||
SkAutoMalloc data(bytes->size() + 1);
|
||||
auto* c_str = static_cast<char*>(data.get());
|
||||
|
||||
memcpy(c_str, bytes->data(), bytes->size());
|
||||
c_str[bytes->size()] = '\0';
|
||||
|
||||
skjson::DOM dom(c_str);
|
||||
SkDynamicMemoryWStream wstream;
|
||||
dom.write(&wstream);
|
||||
}
|
||||
|
||||
#if defined(IS_FUZZING_WITH_LIBFUZZER)
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
auto bytes = SkData::MakeWithoutCopy(data, size);
|
||||
FuzzSkJSON(bytes);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
945
modules/skjson/src/SkJSON.cpp
Normal file
945
modules/skjson/src/SkJSON.cpp
Normal file
@ -0,0 +1,945 @@
|
||||
/*
|
||||
* Copyright 2018 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "SkJSON.h"
|
||||
|
||||
#include "SkStream.h"
|
||||
#include "SkString.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
const char* stringEnd() const {
|
||||
if (this->getRecType() == ValueRec::kShortString) {
|
||||
const auto* payload = this->cast<char>();
|
||||
return payload + kMaxInlineStringSize - SkToSizeT(payload[kMaxInlineStringSize]);
|
||||
}
|
||||
|
||||
return this->vectorEnd<char>(ValueRec::kString);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
// Boring public Value glue.
|
||||
|
||||
const Value& Value::Null() {
|
||||
static const Value g_null = ValueRec::MakeTypeBound(ValueRec::kNull);
|
||||
return g_null;
|
||||
}
|
||||
|
||||
const Member& Member::Null() {
|
||||
static const Member g_null = { Value::Null().as<StringValue>(), Value::Null() };
|
||||
return g_null;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
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>();
|
||||
}
|
||||
|
||||
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>());
|
||||
}
|
||||
|
||||
template <>
|
||||
size_t VectorValue<Value, Value::Type::kArray>::size() const {
|
||||
return reinterpret_cast<const ValueRec*>(this)->vectorSize(ValueRec::kArray);
|
||||
}
|
||||
|
||||
template <>
|
||||
const Value* VectorValue<Value, Value::Type::kArray>::begin() const {
|
||||
return reinterpret_cast<const ValueRec*>(this)->vectorBegin<Value>(ValueRec::kArray);
|
||||
}
|
||||
|
||||
template <>
|
||||
const Value* VectorValue<Value, Value::Type::kArray>::end() const {
|
||||
return reinterpret_cast<const ValueRec*>(this)->vectorEnd<Value>(ValueRec::kArray);
|
||||
}
|
||||
|
||||
template <>
|
||||
size_t VectorValue<Member, Value::Type::kObject>::size() const {
|
||||
return reinterpret_cast<const ValueRec*>(this)->vectorSize(ValueRec::kObject);
|
||||
}
|
||||
|
||||
template <>
|
||||
const Member* VectorValue<Member, Value::Type::kObject>::begin() const {
|
||||
return reinterpret_cast<const ValueRec*>(this)->vectorBegin<Member>(ValueRec::kObject);
|
||||
}
|
||||
|
||||
template <>
|
||||
const Member* VectorValue<Member, Value::Type::kObject>::end() const {
|
||||
return reinterpret_cast<const ValueRec*>(this)->vectorEnd<Member>(ValueRec::kObject);
|
||||
}
|
||||
|
||||
template <>
|
||||
size_t VectorValue<char, Value::Type::kString>::size() const {
|
||||
return reinterpret_cast<const ValueRec*>(this)->stringSize();
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* VectorValue<char, Value::Type::kString>::begin() const {
|
||||
return reinterpret_cast<const ValueRec*>(this)->stringBegin();
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* VectorValue<char, Value::Type::kString>::end() const {
|
||||
return reinterpret_cast<const ValueRec*>(this)->stringEnd();
|
||||
}
|
||||
|
||||
const Value& ObjectValue::operator[](const char* key) const {
|
||||
// Reverse search for duplicates resolution (policy: return last).
|
||||
const auto* begin = this->begin();
|
||||
const auto* member = this->end();
|
||||
|
||||
while (member > begin) {
|
||||
--member;
|
||||
if (0 == strcmp(key, member->fKey.as<StringValue>().begin())) {
|
||||
return member->fValue;
|
||||
}
|
||||
}
|
||||
|
||||
return Value::Null();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Lexer/parser inspired by rapidjson [1], sajson [2] and pjson [3].
|
||||
//
|
||||
// [1] https://github.com/Tencent/rapidjson/
|
||||
// [2] https://github.com/chadaustin/sajson
|
||||
// [3] https://pastebin.com/hnhSTL3h
|
||||
|
||||
|
||||
// bit 0 (0x01) - plain ASCII string character
|
||||
// bit 1 (0x02) - whitespace
|
||||
// bit 2 (0x04) - string terminator (" \0 [control chars])
|
||||
// bit 3 (0x08) - 0-9
|
||||
// bit 4 (0x10) - 0-9 e E .
|
||||
static constexpr uint8_t g_token_flags[256] = {
|
||||
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 4, 4, 6, 4, 4, // 0
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 1
|
||||
3, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0x11,1, // 2
|
||||
0x19,0x19,0x19,0x19,0x19,0x19,0x19,0x19, 0x19,0x19, 1, 1, 1, 1, 1, 1, // 3
|
||||
1, 1, 1, 1, 1, 0x11,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 5
|
||||
1, 1, 1, 1, 1, 0x11,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
|
||||
|
||||
// 128-255
|
||||
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0
|
||||
};
|
||||
|
||||
static inline bool is_ws(char c) { return g_token_flags[static_cast<uint8_t>(c)] & 0x02; }
|
||||
static inline bool is_sterminator(char c) { return g_token_flags[static_cast<uint8_t>(c)] & 0x04; }
|
||||
static inline bool is_digit(char c) { return g_token_flags[static_cast<uint8_t>(c)] & 0x08; }
|
||||
static inline bool is_numeric(char c) { return g_token_flags[static_cast<uint8_t>(c)] & 0x10; }
|
||||
|
||||
static inline const char* skip_ws(const char* p) {
|
||||
while (is_ws(*p)) ++p;
|
||||
return p;
|
||||
}
|
||||
|
||||
static inline float pow10(int32_t exp) {
|
||||
static constexpr float g_pow10_table[63] =
|
||||
{
|
||||
1.e-031f, 1.e-030f, 1.e-029f, 1.e-028f, 1.e-027f, 1.e-026f, 1.e-025f, 1.e-024f,
|
||||
1.e-023f, 1.e-022f, 1.e-021f, 1.e-020f, 1.e-019f, 1.e-018f, 1.e-017f, 1.e-016f,
|
||||
1.e-015f, 1.e-014f, 1.e-013f, 1.e-012f, 1.e-011f, 1.e-010f, 1.e-009f, 1.e-008f,
|
||||
1.e-007f, 1.e-006f, 1.e-005f, 1.e-004f, 1.e-003f, 1.e-002f, 1.e-001f, 1.e+000f,
|
||||
1.e+001f, 1.e+002f, 1.e+003f, 1.e+004f, 1.e+005f, 1.e+006f, 1.e+007f, 1.e+008f,
|
||||
1.e+009f, 1.e+010f, 1.e+011f, 1.e+012f, 1.e+013f, 1.e+014f, 1.e+015f, 1.e+016f,
|
||||
1.e+017f, 1.e+018f, 1.e+019f, 1.e+020f, 1.e+021f, 1.e+022f, 1.e+023f, 1.e+024f,
|
||||
1.e+025f, 1.e+026f, 1.e+027f, 1.e+028f, 1.e+029f, 1.e+030f, 1.e+031f
|
||||
};
|
||||
|
||||
static constexpr int32_t k_exp_offset = SK_ARRAY_COUNT(g_pow10_table) / 2;
|
||||
|
||||
// We only support negative exponents for now.
|
||||
SkASSERT(exp <= 0);
|
||||
|
||||
return (exp >= -k_exp_offset) ? g_pow10_table[exp + k_exp_offset]
|
||||
: std::pow(10.0f, static_cast<float>(exp));
|
||||
}
|
||||
|
||||
class DOMParser {
|
||||
public:
|
||||
explicit DOMParser(SkArenaAlloc& alloc)
|
||||
: fAlloc(alloc) {
|
||||
|
||||
fValueStack.reserve(kValueStackReserve);
|
||||
fScopeStack.reserve(kScopeStackReserve);
|
||||
}
|
||||
|
||||
const Value& parse(const char* p) {
|
||||
p = skip_ws(p);
|
||||
|
||||
switch (*p) {
|
||||
case '{':
|
||||
goto match_object;
|
||||
case '[':
|
||||
goto match_array;
|
||||
default:
|
||||
return this->error(Value::Null(), p, "invalid top-level value");
|
||||
}
|
||||
|
||||
match_object:
|
||||
SkASSERT(*p == '{');
|
||||
p = skip_ws(p + 1);
|
||||
|
||||
this->pushObjectScope();
|
||||
|
||||
if (*p == '}') goto pop_object;
|
||||
|
||||
// goto match_object_key;
|
||||
match_object_key:
|
||||
p = skip_ws(p);
|
||||
if (*p != '"') return this->error(Value::Null(), p, "expected object key");
|
||||
|
||||
p = this->matchString(p, [this](const char* key, size_t size) {
|
||||
this->pushObjectKey(key, size);
|
||||
});
|
||||
if (!p) return Value::Null();
|
||||
|
||||
p = skip_ws(p);
|
||||
if (*p != ':') return this->error(Value::Null(), p, "expected ':' separator");
|
||||
|
||||
++p;
|
||||
|
||||
// goto match_value;
|
||||
match_value:
|
||||
p = skip_ws(p);
|
||||
|
||||
switch (*p) {
|
||||
case '\0':
|
||||
return this->error(Value::Null(), p, "unexpected input end");
|
||||
case '"':
|
||||
p = this->matchString(p, [this](const char* str, size_t size) {
|
||||
this->pushString(str, size);
|
||||
});
|
||||
break;
|
||||
case '[':
|
||||
goto match_array;
|
||||
case 'f':
|
||||
p = this->matchFalse(p);
|
||||
break;
|
||||
case 'n':
|
||||
p = this->matchNull(p);
|
||||
break;
|
||||
case 't':
|
||||
p = this->matchTrue(p);
|
||||
break;
|
||||
case '{':
|
||||
goto match_object;
|
||||
default:
|
||||
p = this->matchNumber(p);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!p) return Value::Null();
|
||||
|
||||
// goto match_post_value;
|
||||
match_post_value:
|
||||
SkASSERT(!fScopeStack.empty());
|
||||
|
||||
p = skip_ws(p);
|
||||
switch (*p) {
|
||||
case ',':
|
||||
++p;
|
||||
if (fScopeStack.back() >= 0) {
|
||||
goto match_object_key;
|
||||
} else {
|
||||
goto match_value;
|
||||
}
|
||||
case ']':
|
||||
goto pop_array;
|
||||
case '}':
|
||||
goto pop_object;
|
||||
default:
|
||||
return this->error(Value::Null(), p - 1, "unexpected value-trailing token");
|
||||
}
|
||||
|
||||
// unreachable
|
||||
SkASSERT(false);
|
||||
|
||||
pop_object:
|
||||
SkASSERT(*p == '}');
|
||||
|
||||
if (fScopeStack.back() < 0) {
|
||||
return this->error(Value::Null(), p, "unexpected object terminator");
|
||||
}
|
||||
|
||||
this->popObjectScope();
|
||||
|
||||
// goto pop_common
|
||||
pop_common:
|
||||
SkASSERT(*p == '}' || *p == ']');
|
||||
|
||||
++p;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
goto match_post_value;
|
||||
|
||||
match_array:
|
||||
SkASSERT(*p == '[');
|
||||
p = skip_ws(p + 1);
|
||||
|
||||
this->pushArrayScope();
|
||||
|
||||
if (*p != ']') goto match_value;
|
||||
|
||||
// goto pop_array;
|
||||
pop_array:
|
||||
SkASSERT(*p == ']');
|
||||
|
||||
if (fScopeStack.back() >= 0) {
|
||||
return this->error(Value::Null(), p, "unexpected array terminator");
|
||||
}
|
||||
|
||||
this->popArrayScope();
|
||||
|
||||
goto pop_common;
|
||||
|
||||
SkASSERT(false);
|
||||
return Value::Null();
|
||||
}
|
||||
|
||||
const SkString& getError() const {
|
||||
return fError;
|
||||
}
|
||||
|
||||
private:
|
||||
SkArenaAlloc& fAlloc;
|
||||
|
||||
static constexpr size_t kValueStackReserve = 256;
|
||||
static constexpr size_t kScopeStackReserve = 128;
|
||||
std::vector<Value > fValueStack;
|
||||
std::vector<intptr_t> fScopeStack;
|
||||
|
||||
SkString fError;
|
||||
|
||||
template <typename T>
|
||||
void popScopeAsVec(ValueRec::RecType type, size_t scope_start) {
|
||||
SkASSERT(scope_start > 0);
|
||||
SkASSERT(scope_start <= fValueStack.size());
|
||||
|
||||
static_assert( sizeof(T) >= sizeof(Value), "");
|
||||
static_assert( sizeof(T) % sizeof(Value) == 0, "");
|
||||
static_assert(alignof(T) == alignof(Value), "");
|
||||
|
||||
const auto scope_count = fValueStack.size() - scope_start,
|
||||
count = scope_count / (sizeof(T) / sizeof(Value));
|
||||
SkASSERT(scope_count % (sizeof(T) / sizeof(Value)) == 0);
|
||||
|
||||
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);
|
||||
|
||||
// Drop the current scope.
|
||||
fScopeStack.pop_back();
|
||||
fValueStack.resize(scope_start);
|
||||
}
|
||||
|
||||
void pushObjectScope() {
|
||||
// Object placeholder.
|
||||
fValueStack.emplace_back();
|
||||
|
||||
// Object scope marker (size).
|
||||
fScopeStack.push_back(SkTo<intptr_t>(fValueStack.size()));
|
||||
}
|
||||
|
||||
void popObjectScope() {
|
||||
const auto scope_start = fScopeStack.back();
|
||||
SkASSERT(scope_start > 0);
|
||||
this->popScopeAsVec<Member>(ValueRec::kObject, SkTo<size_t>(scope_start));
|
||||
|
||||
SkDEBUGCODE(
|
||||
const auto& obj = fValueStack.back().as<ObjectValue>();
|
||||
SkASSERT(obj.is<ObjectValue>());
|
||||
for (const auto& member : obj) {
|
||||
SkASSERT(member.fKey.is<StringValue>());
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
void pushArrayScope() {
|
||||
// Array placeholder.
|
||||
fValueStack.emplace_back();
|
||||
|
||||
// Array scope marker (-size).
|
||||
fScopeStack.push_back(-SkTo<intptr_t>(fValueStack.size()));
|
||||
}
|
||||
|
||||
void popArrayScope() {
|
||||
const auto scope_start = -fScopeStack.back();
|
||||
SkASSERT(scope_start > 0);
|
||||
this->popScopeAsVec<Value>(ValueRec::kArray, SkTo<size_t>(scope_start));
|
||||
|
||||
SkDEBUGCODE(
|
||||
const auto& arr = fValueStack.back().as<ArrayValue>();
|
||||
SkASSERT(arr.is<ArrayValue>());
|
||||
)
|
||||
}
|
||||
|
||||
void pushObjectKey(const char* key, size_t size) {
|
||||
SkASSERT(fScopeStack.back() >= 0);
|
||||
SkASSERT(fValueStack.size() >= SkTo<size_t>(fScopeStack.back()));
|
||||
SkASSERT(!((fValueStack.size() - SkTo<size_t>(fScopeStack.back())) & 1));
|
||||
this->pushString(key, size);
|
||||
}
|
||||
|
||||
void pushTrue() {
|
||||
fValueStack.push_back(ValueRec::MakePrimitive<bool>(ValueRec::kBool, true));
|
||||
}
|
||||
|
||||
void pushFalse() {
|
||||
fValueStack.push_back(ValueRec::MakePrimitive<bool>(ValueRec::kBool, false));
|
||||
}
|
||||
|
||||
void pushNull() {
|
||||
fValueStack.push_back(ValueRec::MakeTypeBound(ValueRec::kNull));
|
||||
}
|
||||
|
||||
void pushString(const char* s, size_t size) {
|
||||
fValueStack.push_back(ValueRec::MakeString(s, size, fAlloc));
|
||||
}
|
||||
|
||||
void pushInt32(int32_t i) {
|
||||
fValueStack.push_back(ValueRec::MakePrimitive<int32_t>(ValueRec::kInt, i));
|
||||
}
|
||||
|
||||
void pushFloat(float f) {
|
||||
fValueStack.push_back(ValueRec::MakePrimitive<float>(ValueRec::kFloat, f));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T& error(const 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);
|
||||
fError.append(p, std::min(strlen(p), kMaxContext));
|
||||
#endif
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
const char* matchTrue(const char* p) {
|
||||
SkASSERT(p[0] == 't');
|
||||
|
||||
if (p[1] == 'r' && p[2] == 'u' && p[3] == 'e') {
|
||||
this->pushTrue();
|
||||
return p + 4;
|
||||
}
|
||||
|
||||
return this->error(nullptr, p, "invalid token");
|
||||
}
|
||||
|
||||
const char* matchFalse(const char* p) {
|
||||
SkASSERT(p[0] == 'f');
|
||||
|
||||
if (p[1] == 'a' && p[2] == 'l' && p[3] == 's' && p[4] == 'e') {
|
||||
this->pushFalse();
|
||||
return p + 5;
|
||||
}
|
||||
|
||||
return this->error(nullptr, p, "invalid token");
|
||||
}
|
||||
|
||||
const char* matchNull(const char* p) {
|
||||
SkASSERT(p[0] == 'n');
|
||||
|
||||
if (p[1] == 'u' && p[2] == 'l' && p[3] == 'l') {
|
||||
this->pushNull();
|
||||
return p + 4;
|
||||
}
|
||||
|
||||
return this->error(nullptr, p, "invalid token");
|
||||
}
|
||||
|
||||
template <typename MatchFunc>
|
||||
const char* matchString(const char* p, MatchFunc&& func) {
|
||||
SkASSERT(*p == '"');
|
||||
const auto* s_begin = p + 1;
|
||||
|
||||
// TODO: unescape
|
||||
for (p = s_begin; !is_sterminator(*p); ++p) {}
|
||||
|
||||
if (*p == '"') {
|
||||
func(s_begin, p - s_begin);
|
||||
return p + 1;
|
||||
}
|
||||
|
||||
return this->error(nullptr, s_begin - 1, "invalid string");
|
||||
}
|
||||
|
||||
const char* matchFastFloatDecimalPart(const char* p, int sign, float f, int exp) {
|
||||
SkASSERT(exp <= 0);
|
||||
|
||||
for (;;) {
|
||||
if (!is_digit(*p)) break;
|
||||
f = f * 10.f + (*p++ - '0'); --exp;
|
||||
if (!is_digit(*p)) break;
|
||||
f = f * 10.f + (*p++ - '0'); --exp;
|
||||
}
|
||||
|
||||
if (is_numeric(*p)) {
|
||||
SkASSERT(*p == '.' || *p == 'e' || *p == 'E');
|
||||
// We either have malformed input, or an (unsupported) exponent.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
this->pushFloat(sign * f * pow10(exp));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
const char* matchFastFloatPart(const char* p, int sign, float f) {
|
||||
for (;;) {
|
||||
if (!is_digit(*p)) break;
|
||||
f = f * 10.f + (*p++ - '0');
|
||||
if (!is_digit(*p)) break;
|
||||
f = f * 10.f + (*p++ - '0');
|
||||
}
|
||||
|
||||
if (!is_numeric(*p)) {
|
||||
// Matched (integral) float.
|
||||
this->pushFloat(sign * f);
|
||||
return p;
|
||||
}
|
||||
|
||||
return (*p == '.') ? this->matchFastFloatDecimalPart(p + 1, sign, f, 0)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
const char* matchFast32OrFloat(const char* p) {
|
||||
int sign = 1;
|
||||
if (*p == '-') {
|
||||
sign = -1;
|
||||
++p;
|
||||
}
|
||||
|
||||
const auto* digits_start = p;
|
||||
|
||||
int32_t n32 = 0;
|
||||
|
||||
// This is the largest absolute int32 value we can handle before
|
||||
// risking overflow *on the next digit* (214748363).
|
||||
static constexpr int32_t kMaxInt32 = (std::numeric_limits<int32_t>::max() - 9) / 10;
|
||||
|
||||
if (is_digit(*p)) {
|
||||
n32 = (*p++ - '0');
|
||||
for (;;) {
|
||||
if (!is_digit(*p) || n32 > kMaxInt32) break;
|
||||
n32 = n32 * 10 + (*p++ - '0');
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_numeric(*p)) {
|
||||
// Did we actually match any digits?
|
||||
if (p > digits_start) {
|
||||
this->pushInt32(sign * n32);
|
||||
return p;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (*p == '.') {
|
||||
const auto* decimals_start = ++p;
|
||||
|
||||
int exp = 0;
|
||||
|
||||
for (;;) {
|
||||
if (!is_digit(*p) || n32 > kMaxInt32) break;
|
||||
n32 = n32 * 10 + (*p++ - '0'); --exp;
|
||||
if (!is_digit(*p) || n32 > kMaxInt32) break;
|
||||
n32 = n32 * 10 + (*p++ - '0'); --exp;
|
||||
}
|
||||
|
||||
if (!is_numeric(*p)) {
|
||||
// Did we actually match any digits?
|
||||
if (p > decimals_start) {
|
||||
this->pushFloat(sign * n32 * pow10(exp));
|
||||
return p;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (n32 > kMaxInt32) {
|
||||
// we ran out on n32 bits
|
||||
return this->matchFastFloatDecimalPart(p, sign, n32, exp);
|
||||
}
|
||||
}
|
||||
|
||||
return this->matchFastFloatPart(p, sign, n32);
|
||||
}
|
||||
|
||||
const char* matchNumber(const char* p) {
|
||||
if (const auto* fast = this->matchFast32OrFloat(p)) return fast;
|
||||
|
||||
// slow fallback
|
||||
char* matched;
|
||||
float f = strtof(p, &matched);
|
||||
if (matched > p) {
|
||||
this->pushFloat(f);
|
||||
return matched;
|
||||
}
|
||||
return this->error(nullptr, p, "invalid numeric token");
|
||||
}
|
||||
};
|
||||
|
||||
void Write(const Value& v, SkWStream* stream) {
|
||||
switch (v.getType()) {
|
||||
case Value::Type::kNull:
|
||||
stream->writeText("null");
|
||||
break;
|
||||
case Value::Type::kBool:
|
||||
stream->writeText(*v.as<BoolValue>() ? "true" : "false");
|
||||
break;
|
||||
case Value::Type::kNumber:
|
||||
stream->writeScalarAsText(*v.as<NumberValue>());
|
||||
break;
|
||||
case Value::Type::kString:
|
||||
stream->writeText("\"");
|
||||
stream->writeText(v.as<StringValue>().begin());
|
||||
stream->writeText("\"");
|
||||
break;
|
||||
case Value::Type::kArray: {
|
||||
const auto& array = v.as<ArrayValue>();
|
||||
stream->writeText("[");
|
||||
bool first_value = true;
|
||||
for (const auto& v : array) {
|
||||
if (!first_value) stream->writeText(",");
|
||||
Write(v, stream);
|
||||
first_value = false;
|
||||
}
|
||||
stream->writeText("]");
|
||||
break;
|
||||
}
|
||||
case Value::Type::kObject:
|
||||
const auto& object = v.as<ObjectValue>();
|
||||
stream->writeText("{");
|
||||
bool first_member = true;
|
||||
for (const auto& member : object) {
|
||||
SkASSERT(member.fKey.getType() == Value::Type::kString);
|
||||
if (!first_member) stream->writeText(",");
|
||||
Write(member.fKey, stream);
|
||||
stream->writeText(":");
|
||||
Write(member.fValue, stream);
|
||||
first_member = false;
|
||||
}
|
||||
stream->writeText("}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static constexpr size_t kMinChunkSize = 4096;
|
||||
|
||||
DOM::DOM(const char* cstr)
|
||||
: fAlloc(kMinChunkSize) {
|
||||
DOMParser parser(fAlloc);
|
||||
|
||||
fRoot = &parser.parse(cstr);
|
||||
}
|
||||
|
||||
void DOM::write(SkWStream* stream) const {
|
||||
Write(*fRoot, stream);
|
||||
}
|
||||
|
||||
} // namespace skjson
|
167
modules/skjson/src/SkJSONBench.cpp
Normal file
167
modules/skjson/src/SkJSONBench.cpp
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2018 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "Benchmark.h"
|
||||
#include "SkData.h"
|
||||
#include "SkJSON.h"
|
||||
#include "SkStream.h"
|
||||
|
||||
#if defined(SK_BUILD_FOR_ANDROID)
|
||||
static constexpr const char* kBenchFile = "/data/local/tmp/bench.json";
|
||||
#else
|
||||
static constexpr const char* kBenchFile = "/tmp/bench.json";
|
||||
#endif
|
||||
|
||||
class JsonBench : public Benchmark {
|
||||
public:
|
||||
|
||||
protected:
|
||||
const char* onGetName() override { return "json_skjson"; }
|
||||
|
||||
bool isSuitableFor(Backend backend) override { return backend == kNonRendering_Backend; }
|
||||
|
||||
void onPerCanvasPreDraw(SkCanvas*) override {
|
||||
if (auto stream = SkStream::MakeFromFile(kBenchFile)) {
|
||||
SkASSERT(stream->hasLength());
|
||||
fCStringData = SkData::MakeUninitialized(stream->getLength() + 1);
|
||||
auto* data8 = reinterpret_cast<uint8_t*>(fCStringData->writable_data());
|
||||
SkAssertResult(stream->read(data8, stream->getLength()) == stream->getLength());
|
||||
data8[stream->getLength()] = '\0';
|
||||
|
||||
} else {
|
||||
SkDebugf("!! Could not open bench file: %s\n", kBenchFile);
|
||||
}
|
||||
}
|
||||
|
||||
void onPerCanvasPostDraw(SkCanvas*) override {
|
||||
fCStringData = nullptr;
|
||||
}
|
||||
|
||||
void onDraw(int loops, SkCanvas*) override {
|
||||
if (!fCStringData) return;
|
||||
|
||||
for (int i = 0; i < loops; i++) {
|
||||
skjson::DOM dom(static_cast<const char*>(fCStringData->data()));
|
||||
if (dom.root().is<skjson::NullValue>()) {
|
||||
SkDebugf("!! Parsing failed.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
sk_sp<SkData> fCStringData;
|
||||
|
||||
using INHERITED = Benchmark;
|
||||
};
|
||||
|
||||
DEF_BENCH( return new JsonBench; )
|
||||
|
||||
#if !defined(SK_BUILD_FOR_ANDROID_FRAMEWORK)
|
||||
|
||||
#include "rapidjson/document.h"
|
||||
|
||||
class RapidJsonBench : public Benchmark {
|
||||
public:
|
||||
|
||||
protected:
|
||||
const char* onGetName() override { return "json_rapidjson"; }
|
||||
|
||||
bool isSuitableFor(Backend backend) override { return backend == kNonRendering_Backend; }
|
||||
|
||||
void onPerCanvasPreDraw(SkCanvas*) override {
|
||||
if (auto stream = SkStream::MakeFromFile(kBenchFile)) {
|
||||
SkASSERT(stream->hasLength());
|
||||
fCStringData = SkData::MakeUninitialized(stream->getLength() + 1);
|
||||
auto* data8 = reinterpret_cast<uint8_t*>(fCStringData->writable_data());
|
||||
SkAssertResult(stream->read(data8, stream->getLength()) == stream->getLength());
|
||||
data8[stream->getLength()] = '\0';
|
||||
|
||||
} else {
|
||||
SkDebugf("!! Could not open bench file: %s\n", kBenchFile);
|
||||
}
|
||||
}
|
||||
|
||||
void onPerCanvasPostDraw(SkCanvas*) override {
|
||||
fCStringData = nullptr;
|
||||
}
|
||||
|
||||
void onDraw(int loops, SkCanvas*) override {
|
||||
if (!fCStringData) return;
|
||||
|
||||
for (int i = 0; i < loops; i++) {
|
||||
rapidjson::Document doc;
|
||||
doc.Parse(static_cast<const char*>(fCStringData->data()));
|
||||
if (doc.HasParseError()) {
|
||||
SkDebugf("!! Parsing failed.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
sk_sp<SkData> fCStringData;
|
||||
|
||||
using INHERITED = Benchmark;
|
||||
};
|
||||
|
||||
DEF_BENCH( return new RapidJsonBench; )
|
||||
|
||||
#endif
|
||||
|
||||
#if (0)
|
||||
|
||||
#include "pjson.h"
|
||||
|
||||
class PJsonBench : public Benchmark {
|
||||
public:
|
||||
|
||||
protected:
|
||||
const char* onGetName() override { return "json_pjson"; }
|
||||
|
||||
bool isSuitableFor(Backend backend) override { return backend == kNonRendering_Backend; }
|
||||
|
||||
void onPerCanvasPreDraw(SkCanvas*) override {
|
||||
if (auto stream = SkStream::MakeFromFile(kBenchFile)) {
|
||||
SkASSERT(stream->hasLength());
|
||||
fCStringData = SkData::MakeUninitialized(stream->getLength() + 1);
|
||||
auto* data8 = reinterpret_cast<uint8_t*>(fCStringData->writable_data());
|
||||
SkAssertResult(stream->read(data8, stream->getLength()) == stream->getLength());
|
||||
data8[stream->getLength()] = '\0';
|
||||
|
||||
} else {
|
||||
SkDebugf("!! Could not open bench file: %s\n", kBenchFile);
|
||||
}
|
||||
}
|
||||
|
||||
void onPerCanvasPostDraw(SkCanvas*) override {
|
||||
fCStringData = nullptr;
|
||||
}
|
||||
|
||||
void onDraw(int loops, SkCanvas*) override {
|
||||
if (!fCStringData) return;
|
||||
|
||||
for (int i = 0; i < loops; i++) {
|
||||
// Copy needed for in-place operation.
|
||||
auto data = SkData::MakeWithCopy(fCStringData->data(), fCStringData->size());
|
||||
pjson::document doc;
|
||||
if (!doc.deserialize_in_place(static_cast<char*>(data->writable_data()))) {
|
||||
SkDebugf("!! Parsing failed.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
sk_sp<SkData> fCStringData;
|
||||
|
||||
using INHERITED = Benchmark;
|
||||
};
|
||||
|
||||
DEF_BENCH( return new PJsonBench; )
|
||||
|
||||
#endif
|
272
modules/skjson/src/SkJSONTest.cpp
Normal file
272
modules/skjson/src/SkJSONTest.cpp
Normal file
@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright 2018 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "Test.h"
|
||||
|
||||
#include "SkJSON.h"
|
||||
#include "SkStream.h"
|
||||
|
||||
using namespace skjson;
|
||||
|
||||
DEF_TEST(SkJSON_Parse, reporter) {
|
||||
static constexpr struct {
|
||||
const char* in;
|
||||
const char* out;
|
||||
} g_tests[] = {
|
||||
{ "" , nullptr },
|
||||
{ "[" , nullptr },
|
||||
{ "]" , nullptr },
|
||||
{ "{" , nullptr },
|
||||
{ "}" , nullptr },
|
||||
{ "{]" , nullptr },
|
||||
{ "[}" , nullptr },
|
||||
{ "1" , nullptr },
|
||||
{ "true" , nullptr },
|
||||
{ "false", nullptr },
|
||||
{ "null" , nullptr },
|
||||
|
||||
{ "[nulll]" , nullptr },
|
||||
{ "[false2]", nullptr },
|
||||
{ "[true:]" , nullptr },
|
||||
|
||||
{ "[1 2]" , nullptr },
|
||||
{ "[1,,2]" , nullptr },
|
||||
{ "[1,2,]" , nullptr },
|
||||
{ "[,1,2]" , nullptr },
|
||||
|
||||
{ "[ \"foo" , nullptr },
|
||||
{ "[ \"fo\0o\" ]" , nullptr },
|
||||
|
||||
{ "{ null }" , nullptr },
|
||||
{ "{ \"k\" : }" , nullptr },
|
||||
{ "{ : null }" , nullptr },
|
||||
{ "{ \"k\" : : null }" , nullptr },
|
||||
{ "{ \"k\" : null , }" , nullptr },
|
||||
{ "{ \"k\" : null \"k\" : 1 }", nullptr },
|
||||
|
||||
|
||||
{ " \n\r\t [ \n\r\t ] \n\r\t " , "[]" },
|
||||
{ "[ null ]" , "[null]" },
|
||||
{ "[ true ]" , "[true]" },
|
||||
{ "[ false ]" , "[false]" },
|
||||
{ "[ 0 ]" , "[0]" },
|
||||
{ "[ 1 ]" , "[1]" },
|
||||
{ "[ 1.248 ]" , "[1.248]" },
|
||||
{ "[ \"\" ]" , "[\"\"]" },
|
||||
{ "[ \" f o o \" ]" , "[\" f o o \"]" },
|
||||
{ "[ \"123456\" ]" , "[\"123456\"]" },
|
||||
{ "[ \"1234567\" ]" , "[\"1234567\"]" },
|
||||
{ "[ \"12345678\" ]" , "[\"12345678\"]" },
|
||||
{ "[ \"123456789\" ]" , "[\"123456789\"]" },
|
||||
{ "[ null , true, false,0,12.8 ]", "[null,true,false,0,12.8]" },
|
||||
|
||||
{ " \n\r\t { \n\r\t } \n\r\t " , "{}" },
|
||||
{ "{ \"k\" : null }" , "{\"k\":null}" },
|
||||
{ "{ \"k1\" : null, \"k2 \":0 }", "{\"k1\":null,\"k2 \":0}" },
|
||||
{ "{ \"k1\" : null, \"k1\":0 }" , "{\"k1\":null,\"k1\":0}" },
|
||||
|
||||
{ "{ \"k1\" : null, \n\
|
||||
\"k2\" : 0, \n\
|
||||
\"k3\" : [ \n\
|
||||
true, \r\n\
|
||||
{ \"kk1\" : \"foo\" , \n\
|
||||
\"kk2\" : \"bar\" , \n\
|
||||
\"kk3\" : 1.28 , \n\
|
||||
\"kk4\" : [ 42 ] \n\
|
||||
} , \n\
|
||||
\"boo\" , \n\
|
||||
null \n\
|
||||
] \n\
|
||||
}",
|
||||
"{\"k1\":null,\"k2\":0,\"k3\":[true,"
|
||||
"{\"kk1\":\"foo\",\"kk2\":\"bar\",\"kk3\":1.28,\"kk4\":[42]},\"boo\",null]}" },
|
||||
};
|
||||
|
||||
for (const auto& tst : g_tests) {
|
||||
DOM dom(tst.in);
|
||||
const auto success = !dom.root().is<NullValue>();
|
||||
REPORTER_ASSERT(reporter, success == (tst.out != nullptr));
|
||||
if (!success) continue;
|
||||
|
||||
SkDynamicMemoryWStream str;
|
||||
dom.write(&str);
|
||||
str.write8('\0');
|
||||
|
||||
auto data = str.detachAsData();
|
||||
REPORTER_ASSERT(reporter, !strcmp(tst.out, static_cast<const char*>(data->data())));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template <typename T, typename VT>
|
||||
static void check_primitive(skiatest::Reporter* reporter, const Value& v, T pv,
|
||||
bool is_type) {
|
||||
|
||||
REPORTER_ASSERT(reporter, v.is<VT>() == is_type);
|
||||
REPORTER_ASSERT(reporter, *v.as<VT>() == pv);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void check_vector(skiatest::Reporter* reporter, const Value& v, size_t expected_size,
|
||||
bool is_vector) {
|
||||
REPORTER_ASSERT(reporter, v.is<T>() == is_vector);
|
||||
|
||||
const auto& vec = v.as<T>();
|
||||
REPORTER_ASSERT(reporter, vec.size() == expected_size);
|
||||
REPORTER_ASSERT(reporter, (vec.begin() != nullptr) == is_vector);
|
||||
REPORTER_ASSERT(reporter, vec.end() == vec.begin() + expected_size);
|
||||
}
|
||||
|
||||
static void check_string(skiatest::Reporter* reporter, const Value& v, const char* s) {
|
||||
check_vector<StringValue>(reporter, v, s ? strlen(s) : 0, !!s);
|
||||
if (s) {
|
||||
REPORTER_ASSERT(reporter, !strcmp(v.as<StringValue>().begin(), s));
|
||||
}
|
||||
}
|
||||
|
||||
DEF_TEST(SkJSON_DOM, reporter) {
|
||||
static constexpr char json[] = "{ \n\
|
||||
\"k1\": null, \n\
|
||||
\"k2\": false, \n\
|
||||
\"k3\": true, \n\
|
||||
\"k4\": 42, \n\
|
||||
\"k5\": .75, \n\
|
||||
\"k6\": \"foo\", \n\
|
||||
\"k7\": [ 1, true, \"bar\" ], \n\
|
||||
\"k8\": { \"kk1\": 2, \"kk2\": false, \"kk1\": \"baz\" } \n\
|
||||
}";
|
||||
|
||||
DOM dom(json);
|
||||
|
||||
const auto& jroot = dom.root().as<ObjectValue>();
|
||||
REPORTER_ASSERT(reporter, jroot.is<ObjectValue>());
|
||||
|
||||
{
|
||||
const auto& v = jroot["k1"];
|
||||
REPORTER_ASSERT(reporter, v.is<NullValue>());
|
||||
|
||||
check_primitive<bool, BoolValue>(reporter, v, false, false);
|
||||
check_primitive<float, NumberValue>(reporter, v, 0, false);
|
||||
|
||||
check_string(reporter, v, nullptr);
|
||||
check_vector<ArrayValue >(reporter, v, 0, false);
|
||||
check_vector<ObjectValue>(reporter, v, 0, false);
|
||||
}
|
||||
|
||||
{
|
||||
const auto& v = jroot["k2"];
|
||||
REPORTER_ASSERT(reporter, !v.is<NullValue>());
|
||||
|
||||
check_primitive<bool, BoolValue>(reporter, v, false, true);
|
||||
check_primitive<float, NumberValue>(reporter, v, 0, false);
|
||||
|
||||
check_string(reporter, v, nullptr);
|
||||
check_vector<ArrayValue >(reporter, v, 0, false);
|
||||
check_vector<ObjectValue>(reporter, v, 0, false);
|
||||
}
|
||||
|
||||
{
|
||||
const auto& v = jroot["k3"];
|
||||
REPORTER_ASSERT(reporter, !v.is<NullValue>());
|
||||
|
||||
check_primitive<bool, BoolValue>(reporter, v, true, true);
|
||||
check_primitive<float, NumberValue>(reporter, v, 0, false);
|
||||
|
||||
check_string(reporter, v, nullptr);
|
||||
check_vector<ArrayValue >(reporter, v, 0, false);
|
||||
check_vector<ObjectValue>(reporter, v, 0, false);
|
||||
}
|
||||
|
||||
{
|
||||
const auto& v = jroot["k4"];
|
||||
REPORTER_ASSERT(reporter, !v.is<NullValue>());
|
||||
|
||||
check_primitive<bool, BoolValue>(reporter, v, false, false);
|
||||
check_primitive<float, NumberValue>(reporter, v, 42, true);
|
||||
|
||||
check_string(reporter, v, nullptr);
|
||||
check_vector<ArrayValue >(reporter, v, 0, false);
|
||||
check_vector<ObjectValue>(reporter, v, 0, false);
|
||||
}
|
||||
|
||||
{
|
||||
const auto& v = jroot["k5"];
|
||||
REPORTER_ASSERT(reporter, !v.is<NullValue>());
|
||||
|
||||
check_primitive<bool, BoolValue>(reporter, v, false, false);
|
||||
check_primitive<float, NumberValue>(reporter, v, .75f, true);
|
||||
|
||||
check_string(reporter, v, nullptr);
|
||||
check_vector<ArrayValue >(reporter, v, 0, false);
|
||||
check_vector<ObjectValue>(reporter, v, 0, false);
|
||||
}
|
||||
|
||||
{
|
||||
const auto& v = jroot["k6"];
|
||||
REPORTER_ASSERT(reporter, !v.is<NullValue>());
|
||||
|
||||
check_primitive<bool, BoolValue>(reporter, v, false, false);
|
||||
check_primitive<float, NumberValue>(reporter, v, 0, false);
|
||||
|
||||
check_string(reporter, v, "foo");
|
||||
check_vector<ArrayValue >(reporter, v, 0, false);
|
||||
check_vector<ObjectValue>(reporter, v, 0, false);
|
||||
}
|
||||
|
||||
{
|
||||
const auto& v = jroot["k7"];
|
||||
REPORTER_ASSERT(reporter, !v.is<NullValue>());
|
||||
|
||||
check_primitive<bool, BoolValue>(reporter, v, false, false);
|
||||
check_primitive<float, NumberValue>(reporter, v, 0, false);
|
||||
|
||||
check_string(reporter, v, nullptr);
|
||||
check_vector<ObjectValue>(reporter, v, 0, false);
|
||||
|
||||
check_vector<ArrayValue >(reporter, v, 3, true);
|
||||
check_primitive<float, NumberValue>(reporter, v.as<ArrayValue>()[0], 1, true);
|
||||
check_primitive<bool, BoolValue>(reporter, v.as<ArrayValue>()[1], true, true);
|
||||
check_vector<StringValue>(reporter, v.as<ArrayValue>()[2], 3, true);
|
||||
REPORTER_ASSERT(reporter, v.as<ArrayValue>()[3].is<NullValue>());
|
||||
}
|
||||
|
||||
{
|
||||
const auto& v = jroot["k8"];
|
||||
REPORTER_ASSERT(reporter, !v.is<NullValue>());
|
||||
|
||||
check_primitive<bool, BoolValue>(reporter, v, false, false);
|
||||
check_primitive<float, NumberValue>(reporter, v, 0, false);
|
||||
|
||||
check_string(reporter, v, nullptr);
|
||||
check_vector<ArrayValue >(reporter, v, 0, false);
|
||||
|
||||
check_vector<ObjectValue>(reporter, v, 3, true);
|
||||
|
||||
const auto& m0 = v.as<ObjectValue>().begin()[0];
|
||||
check_string(reporter, m0.fKey, "kk1");
|
||||
check_primitive<float, NumberValue>(reporter, m0.fValue, 2, true);
|
||||
|
||||
const auto& m1 = v.as<ObjectValue>().begin()[1];
|
||||
check_string(reporter, m1.fKey, "kk2");
|
||||
check_primitive<bool, BoolValue>(reporter, m1.fValue, false, true);
|
||||
|
||||
const auto& m2 = v.as<ObjectValue>().begin()[2];
|
||||
check_string(reporter, m2.fKey, "kk1");
|
||||
check_string(reporter, m2.fValue, "baz");
|
||||
|
||||
REPORTER_ASSERT(reporter, v.as<ObjectValue>()[""].is<NullValue>());
|
||||
REPORTER_ASSERT(reporter, v.as<ObjectValue>()["nosuchkey"].is<NullValue>());
|
||||
check_string(reporter, v.as<ObjectValue>()["kk1"], "baz");
|
||||
check_primitive<bool, BoolValue>(reporter, v.as<ObjectValue>()["kk2"], false, true);
|
||||
}
|
||||
|
||||
{
|
||||
const auto& v =
|
||||
jroot["foo"].as<ObjectValue>()["bar"].as<ObjectValue>()["baz"];
|
||||
REPORTER_ASSERT(reporter, v.is<NullValue>());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user