summaryrefslogtreecommitdiffstats
path: root/source/compiler-core/slang-json-value.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/compiler-core/slang-json-value.cpp')
-rw-r--r--source/compiler-core/slang-json-value.cpp811
1 files changed, 811 insertions, 0 deletions
diff --git a/source/compiler-core/slang-json-value.cpp b/source/compiler-core/slang-json-value.cpp
new file mode 100644
index 000000000..3b74c00dd
--- /dev/null
+++ b/source/compiler-core/slang-json-value.cpp
@@ -0,0 +1,811 @@
+// slang-json-value.cpp
+#include "slang-json-value.h"
+
+#include "../core/slang-string-escape-util.h"
+#include "../core/slang-string-util.h"
+
+namespace Slang {
+
+/* static */const JSONValue::Kind JSONValue::g_typeToKind[] =
+{
+ JSONValue::Kind::Invalid, // Invalid
+
+ JSONValue::Kind::Bool, // True,
+ JSONValue::Kind::Bool, // False
+ JSONValue::Kind::Null, // Null,
+
+ JSONValue::Kind::String, // StringLexeme,
+ JSONValue::Kind::Integer, // IntegerLexeme,
+ JSONValue::Kind::Float, // FloatLexeme,
+
+ JSONValue::Kind::Integer, // IntegerValue,
+ JSONValue::Kind::Float, // FloatValue,
+ JSONValue::Kind::String, // StringValue,
+
+ JSONValue::Kind::Array, // Array,
+ JSONValue::Kind::Object, // Object,
+};
+
+static JSONKeyValue _makeInvalidKeyValue()
+{
+ JSONKeyValue keyValue;
+ keyValue.key = JSONKey(0);
+ keyValue.value.type = JSONValue::Type::Invalid;
+ return keyValue;
+}
+
+/* static */JSONKeyValue g_invalid = _makeInvalidKeyValue();
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+ JSONValue
+
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+bool JSONValue::asBool() const
+{
+ switch (type)
+ {
+ case JSONValue::Type::True: return true;
+ case JSONValue::Type::False:
+ case JSONValue::Type::Null:
+ {
+ return false;
+ }
+ case JSONValue::Type::IntegerValue: return intValue != 0;
+ case JSONValue::Type::FloatValue: return floatValue != 0;
+ default: break;
+ }
+
+ if (isLexeme(type))
+ {
+ SLANG_ASSERT(!"Lexeme values can only be accessed through container");
+ }
+ else
+ {
+ SLANG_ASSERT(!"Not bool convertable");
+ }
+
+ return false;
+}
+
+int64_t JSONValue::asInteger() const
+{
+ switch (type)
+ {
+ case JSONValue::Type::True: return 1;
+ case JSONValue::Type::False:
+ case JSONValue::Type::Null:
+ {
+ return 0;
+ }
+ case JSONValue::Type::IntegerValue: return intValue;
+ case JSONValue::Type::FloatValue: return int64_t(floatValue);
+ break;
+ }
+
+ if (isLexeme(type))
+ {
+ SLANG_ASSERT(!"Lexeme values can only be accessed through container");
+ }
+ else
+ {
+ SLANG_ASSERT(!"Not int convertable");
+ }
+
+ return 0;
+}
+
+double JSONValue::asFloat() const
+{
+ switch (type)
+ {
+ case JSONValue::Type::True: return 1.0;
+ case JSONValue::Type::False:
+ case JSONValue::Type::Null:
+ {
+ return 0.0;
+ }
+ case JSONValue::Type::IntegerValue: return double(intValue);
+ case JSONValue::Type::FloatValue: return floatValue;
+ default: break;
+ }
+
+ if (isLexeme(type))
+ {
+ SLANG_ASSERT(!"Lexeme values can only be accessed through container");
+ }
+ else
+ {
+ SLANG_ASSERT(!"Not float convertable");
+ }
+
+ return 0;
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+ JSONContainer
+
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+JSONContainer::JSONContainer(SourceManager* sourceManager):
+ m_slicePool(StringSlicePool::Style::Default),
+ m_sourceManager(sourceManager)
+{
+ // Index 0 is the empty array or object
+ _addRange(Range::Type::None, 0, 0);
+}
+
+/* static */bool JSONContainer::areKeysUnique(const JSONKeyValue* keyValues, Index keyValueCount)
+{
+ for (Index i = 1; i < keyValueCount; ++i)
+ {
+ const JSONKey key = keyValues[i].key;
+
+ for (Int j = 0; j < i - 1; j++)
+ {
+ if (keyValues[j].key == key)
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+Index JSONContainer::_addRange(Range::Type type, Index startIndex, Index count)
+{
+ if (m_freeRangeIndices.getCount() > 0)
+ {
+ const Index rangeIndex = m_freeRangeIndices.getLast();
+ m_freeRangeIndices.removeLast();
+
+ auto& range = m_ranges[rangeIndex];
+ range.type = type;
+ range.startIndex = startIndex;
+ range.count = count;
+ range.capacity = count;
+
+ return rangeIndex;
+ }
+ else
+ {
+ Range range;
+ range.type = type;
+ range.startIndex = startIndex;
+ range.count = count;
+ range.capacity = count;
+
+ m_ranges.add(range);
+ return m_ranges.getCount() - 1;
+ }
+}
+
+JSONValue JSONContainer::createArray(const JSONValue* values, Index valuesCount, SourceLoc loc)
+{
+ if (valuesCount <= 0)
+ {
+ return JSONValue::makeEmptyArray(loc);
+ }
+
+ JSONValue value;
+ value.type = JSONValue::Type::Array;
+ value.loc = loc;
+ value.rangeIndex = _addRange(Range::Type::Array, m_objectValues.getCount(), valuesCount);
+
+ m_arrayValues.addRange(values, valuesCount);
+ return value;
+}
+
+JSONValue JSONContainer::createObject(const JSONKeyValue* keyValues, Index keyValueCount, SourceLoc loc)
+{
+ if (keyValueCount <= 0)
+ {
+ return JSONValue::makeEmptyObject(loc);
+ }
+
+ JSONValue value;
+ value.type = JSONValue::Type::Object;
+ value.loc = loc;
+ value.rangeIndex = _addRange(Range::Type::Object, m_objectValues.getCount(), keyValueCount);
+
+ m_objectValues.addRange(keyValues, keyValueCount);
+ return value;
+}
+
+JSONValue JSONContainer::createString(const UnownedStringSlice& slice, SourceLoc loc)
+{
+ JSONValue value;
+ value.type = JSONValue::Type::StringValue;
+ value.loc = loc;
+ value.stringKey = getKey(slice);
+ return value;
+}
+
+JSONKey JSONContainer::getKey(const UnownedStringSlice& slice)
+{
+ return JSONKey(m_slicePool.add(slice));
+}
+
+ConstArrayView<JSONValue> JSONContainer::getArray(const JSONValue& in) const
+{
+ SLANG_ASSERT(in.type == JSONValue::Type::Array);
+ if (in.type != JSONValue::Type::Array || in.rangeIndex == 0)
+ {
+ return ConstArrayView<JSONValue>((const JSONValue*)nullptr, 0);
+ }
+ const Range& range = m_ranges[in.rangeIndex];
+ return ConstArrayView<JSONValue>(m_arrayValues.getBuffer() + range.startIndex, range.count);
+}
+
+ConstArrayView<JSONKeyValue> JSONContainer::getObject(const JSONValue& in) const
+{
+ SLANG_ASSERT(in.type == JSONValue::Type::Array);
+ if (in.type != JSONValue::Type::Array || in.rangeIndex == 0)
+ {
+ return ConstArrayView<JSONKeyValue>((const JSONKeyValue*)nullptr, 0);
+ }
+
+ const Range& range = m_ranges[in.rangeIndex];
+ return ConstArrayView<JSONKeyValue>(m_objectValues.getBuffer() + range.startIndex, range.count);
+}
+
+ArrayView<JSONValue> JSONContainer::getArray(const JSONValue& in)
+{
+ SLANG_ASSERT(in.type == JSONValue::Type::Array);
+ if (in.type != JSONValue::Type::Array || in.rangeIndex == 0)
+ {
+ return ArrayView<JSONValue>((JSONValue*)nullptr, 0);
+ }
+ const Range& range = m_ranges[in.rangeIndex];
+ return ArrayView<JSONValue>(m_arrayValues.getBuffer() + range.startIndex, range.count);
+}
+
+ArrayView<JSONKeyValue> JSONContainer::getObject(const JSONValue& in)
+{
+ SLANG_ASSERT(in.type == JSONValue::Type::Object);
+ if (in.type != JSONValue::Type::Object || in.rangeIndex == 0)
+ {
+ return ArrayView<JSONKeyValue>((JSONKeyValue*)nullptr, 0);
+ }
+
+ const Range& range = m_ranges[in.rangeIndex];
+ return ArrayView<JSONKeyValue>(m_objectValues.getBuffer() + range.startIndex, range.count);
+}
+
+UnownedStringSlice JSONContainer::getLexeme(const JSONValue& in)
+{
+ SLANG_ASSERT(JSONValue::isLexeme(in.type));
+ if (!JSONValue::isLexeme(in.type))
+ {
+ return UnownedStringSlice();
+ }
+
+ if (!(m_currentView && m_currentView->getRange().contains(in.loc)))
+ {
+ m_currentView = m_sourceManager->findSourceView(in.loc);
+ if (!m_currentView)
+ {
+ return UnownedStringSlice();
+ }
+ }
+
+ const auto offset = m_currentView->getRange().getOffset(in.loc);
+ SourceFile* sourceFile = m_currentView->getSourceFile();
+
+ return UnownedStringSlice(sourceFile->getContent().begin() + offset, in.length);
+}
+
+UnownedStringSlice JSONContainer::getString(const JSONValue& in)
+{
+ switch (in.type)
+ {
+ case JSONValue::Type::StringValue: return getStringFromKey(in.stringKey);
+ case JSONValue::Type::StringLexeme:
+ {
+ StringEscapeHandler* handler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::JSON);
+
+ UnownedStringSlice lexeme = getLexeme(in);
+ UnownedStringSlice unquoted = StringEscapeUtil::unquote(handler, lexeme);
+
+ if (handler->isUnescapingNeeeded(unquoted))
+ {
+ m_buf.Clear();
+ handler->appendUnescaped(unquoted, m_buf);
+ return m_buf.getUnownedSlice();
+ }
+ else
+ {
+ return unquoted;
+ }
+ }
+ }
+
+ SLANG_ASSERT(!"Not a string type");
+ return UnownedStringSlice();
+}
+
+JSONKey JSONContainer::getStringKey(const JSONValue& in)
+{
+ return (in.type == JSONValue::Type::StringValue) ? in.stringKey : getKey(getString(in));
+}
+
+bool JSONContainer::asBool(const JSONValue& value)
+{
+ switch (value.type)
+ {
+ case JSONValue::Type::IntegerLexeme: return asInteger(value) != 0;
+ case JSONValue::Type::FloatLexeme: return asFloat(value) != 0.0;
+ default: return value.asBool();
+ }
+}
+
+int64_t JSONContainer::asInteger(const JSONValue& value)
+{
+ switch (value.type)
+ {
+ case JSONValue::Type::IntegerLexeme:
+ {
+ UnownedStringSlice slice = getLexeme(value);
+ int64_t intValue;
+ if (SLANG_SUCCEEDED(StringUtil::parseInt64(slice, intValue)) && slice.getLength() == 0)
+ {
+ return intValue;
+ }
+ SLANG_ASSERT(!"Couldn't convert int");
+ return 0;
+ }
+ case JSONValue::Type::FloatLexeme: return int64_t(asFloat(value));
+ default: return value.asInteger();
+ }
+}
+
+double JSONContainer::asFloat(const JSONValue& value)
+{
+ switch (value.type)
+ {
+ case JSONValue::Type::IntegerLexeme: return double(asInteger(value));
+ case JSONValue::Type::FloatLexeme:
+ {
+ UnownedStringSlice slice = getLexeme(value);
+ double floatValue;
+ if (SLANG_SUCCEEDED(StringUtil::parseDouble(slice, floatValue)) && slice.getLength() == 0)
+ {
+ return floatValue;
+ }
+ SLANG_ASSERT(!"Couldn't convert double");
+ return 0.0;
+ }
+ default: return value.asFloat();
+ }
+}
+
+JSONValue& JSONContainer::getAt(const JSONValue& array, Index index)
+{
+ SLANG_ASSERT(array.type == JSONValue::Type::Array);
+ const Range& range = m_ranges[array.rangeIndex];
+
+ SLANG_ASSERT(index >= 0 && index < range.count);
+ return m_arrayValues[range.startIndex + index];
+}
+
+void JSONContainer::addToArray(JSONValue& array, const JSONValue& value)
+{
+ SLANG_ASSERT(array.type == JSONValue::Type::Array);
+ if (array.type == JSONValue::Type::Array)
+ {
+ // If it's empty
+ if (array.rangeIndex == 0)
+ {
+ // We can just add to the end
+ array.rangeIndex = _addRange(Range::Type::Array, m_arrayValues.getCount(), 1);
+ m_arrayValues.add(value);
+
+ }
+ else
+ {
+ _add(m_ranges[array.rangeIndex], m_arrayValues, value);
+ }
+ }
+}
+
+Index JSONContainer::findKeyGlobalIndex(const JSONValue& obj, JSONKey key)
+{
+ SLANG_ASSERT(obj.type == JSONValue::Type::Object);
+ if (obj.type != JSONValue::Type::Object)
+ {
+ return -1;
+ }
+
+ auto buf = m_objectValues.getBuffer();
+
+ const Range& range = m_ranges[obj.rangeIndex];
+ for (Index i = range.startIndex; i < range.startIndex + range.count; ++i)
+ {
+ if (buf[i].key == key)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+Index JSONContainer::findKeyGlobalIndex(const JSONValue& obj, const UnownedStringSlice& slice)
+{
+ Index keyIndex = m_slicePool.findIndex(slice);
+ if (keyIndex < 0)
+ {
+ return -1;
+ }
+
+ return findKeyGlobalIndex(obj, JSONKey(keyIndex));
+}
+
+void JSONContainer::_removeKey(JSONValue& obj, Index globalIndex)
+{
+ Range& range = m_ranges[obj.rangeIndex];
+ const auto localIndex = globalIndex + range.startIndex;
+
+ if (localIndex < range.count - 1)
+ {
+ auto localBuf = m_objectValues.getBuffer() + range.startIndex;
+ ::memmove(localBuf + localIndex, localBuf + localIndex + 1, sizeof(*localBuf) * (range.count - (localIndex + 1)));
+ }
+
+ --range.count;
+}
+
+bool JSONContainer::removeKey(JSONValue& obj, JSONKey key)
+{
+ const Index globalIndex = findKeyGlobalIndex(obj, key);
+ if (globalIndex >= 0)
+ {
+ _removeKey(obj, globalIndex);
+ return true;
+ }
+ return false;
+}
+
+bool JSONContainer::removeKey(JSONValue& obj, const UnownedStringSlice& slice)
+{
+ const Index globalIndex = findKeyGlobalIndex(obj, slice);
+ if (globalIndex >= 0)
+ {
+ _removeKey(obj, globalIndex);
+ return true;
+ }
+ return false;
+}
+
+template <typename T>
+/* static */void JSONContainer::_add(Range& ioRange, List<T>& ioList, const T& value)
+{
+ // If we have capacity, we can add to the end
+ if (ioRange.count < ioRange.capacity)
+ {
+ ioList[ioRange.startIndex + ioRange.count++] = value;
+ return;
+ }
+
+ // If we are at the end, we can just add
+ if (ioRange.startIndex + ioRange.capacity == ioList.getCount())
+ {
+ ioList.add(value);
+ ioRange.capacity++;
+ ioRange.count++;
+ return;
+ }
+
+ // Okay we have no choice but to make new space at the end
+ // So there's no place to add. We want to move to the end with an extra space.
+
+ const Index newStartIndex = ioList.getCount();
+ ioList.growToCount(newStartIndex + ioRange.count + 1);
+
+ auto buffer = ioList.getBuffer();
+ ::memmove(buffer + newStartIndex, buffer + ioRange.startIndex, sizeof(*buffer) * ioRange.count);
+
+ buffer[newStartIndex + ioRange.count] = value;
+
+ ioRange.startIndex = newStartIndex;
+ ioRange.count++;
+ ioRange.capacity++;
+}
+
+
+void JSONContainer::setKeyValue(JSONValue& obj, JSONKey key, const JSONValue& value, SourceLoc loc)
+{
+ SLANG_ASSERT(obj.type == JSONValue::Type::Object);
+ if (obj.type != JSONValue::Type::Object)
+ {
+ return;
+ }
+
+ const JSONKeyValue keyValue{key, loc, value};
+ if (obj.rangeIndex == 0)
+ {
+ // We need a new range and add to the end
+ obj.rangeIndex = _addRange(Range::Type::Object, m_objectValues.getCount(), 1);
+ m_objectValues.add(keyValue);
+ return;
+ }
+
+ const Index globalIndex = findKeyGlobalIndex(obj, key);
+ if (globalIndex >= 0)
+ {
+ auto& dst = m_objectValues[globalIndex];
+ SLANG_ASSERT(dst.key == key);
+ dst = keyValue;
+ return;
+ }
+
+ Range& range = m_ranges[obj.rangeIndex];
+ _add(range, m_objectValues, keyValue);
+}
+
+void JSONContainer::_destroyRange(Index rangeIndex)
+{
+ auto& range = m_ranges[rangeIndex];
+
+ // If the range is at the end, shrink it
+ switch (range.type)
+ {
+ case Range::Type::Array:
+ {
+ if (range.startIndex + range.capacity == m_arrayValues.getCount())
+ {
+ m_arrayValues.setCount(range.startIndex);
+ }
+ break;
+ }
+ case Range::Type::Object:
+ {
+ if (range.startIndex + range.capacity == m_objectValues.getCount())
+ {
+ m_objectValues.setCount(range.startIndex);
+ }
+ break;
+ }
+ default: break;
+ }
+
+ range.type = Range::Type::Destroyed;
+ m_freeRangeIndices.add(rangeIndex);
+}
+
+void JSONContainer::destroy(JSONValue& value)
+{
+ if (value.needsDestroy())
+ {
+ _destroyRange(value.rangeIndex);
+ }
+ value.type = JSONValue::Type::Invalid;
+}
+
+void JSONContainer::destroyRecursively(JSONValue& inValue)
+{
+ if (!(inValue.needsDestroy() && m_ranges[inValue.rangeIndex].isActive()))
+ {
+ inValue.type = JSONValue::Type::Invalid;
+ return;
+ }
+
+ inValue.type = JSONValue::Type::Invalid;
+
+ List<Range> activeRanges;
+
+ activeRanges.add(m_ranges[inValue.rangeIndex]);
+ _destroyRange(inValue.rangeIndex);
+
+ while (activeRanges.getCount())
+ {
+ const Range range = activeRanges.getLast();
+ activeRanges.removeLast();
+
+ auto type = range.type;
+ const Index count = range.count;
+
+ if (type == Range::Type::Array)
+ {
+ auto* buf = m_arrayValues.getBuffer() + range.startIndex;
+
+ for (Index i = 0; i < count; ++i)
+ {
+ auto& value = buf[i];
+ // If we have an active range, add to work list, and destroy
+ if (value.needsDestroy() && m_ranges[value.rangeIndex].isActive())
+ {
+ activeRanges.add(m_ranges[value.rangeIndex]);
+ _destroyRange(value.rangeIndex);
+ }
+ value.type = JSONValue::Type::Invalid;
+ }
+ }
+ else
+ {
+ SLANG_ASSERT(type == Range::Type::Object);
+
+ auto* buf = m_objectValues.getBuffer() + range.startIndex;
+
+ for (Index i = 0; i < count; ++i)
+ {
+ auto& keyValue = buf[i];
+ auto& value = keyValue.value;
+ // We want to mark that it's in the list so that if we have a badly formed tree we don't read
+ if (value.needsDestroy() && m_ranges[value.rangeIndex].isActive())
+ {
+ activeRanges.add(m_ranges[value.rangeIndex]);
+ _destroyRange(value.rangeIndex);
+ }
+ value.type = JSONValue::Type::Invalid;
+ }
+ }
+ }
+}
+
+bool JSONContainer::areEqual(const JSONValue* a, const JSONValue* b, Index count)
+{
+ for (Index i = 0; i < count; ++i)
+ {
+ if (!areEqual(a[i], b[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/* static */bool JSONContainer::_sameKeyOrder(const JSONKeyValue* a, const JSONKeyValue* b, Index count)
+{
+ for (Index i = 0; i < count; ++i)
+ {
+ if (a[i].key != b[i].key)
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool JSONContainer::_areEqualOrderedKeys(const JSONKeyValue* a, const JSONKeyValue* b, Index count)
+{
+ for (Index i = 0; i < count; ++i)
+ {
+ const auto& curA = a[i];
+ const auto& curB = b[i];
+
+ if (curA.key != curB.key ||
+ !areEqual(curA.value, curB.value))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool JSONContainer::_areEqualValues(const JSONKeyValue* a, const JSONKeyValue* b, Index count)
+{
+ for (Index i = 0; i < count; ++i)
+ {
+ if (!areEqual(a[i].value, b[i].value))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool JSONContainer::areEqual(const JSONKeyValue* a, const JSONKeyValue* b, Index count)
+{
+ if (count == 0)
+ {
+ return true;
+ }
+
+ if (count == 1)
+ {
+ return _areEqualOrderedKeys(a, b, count);
+ }
+ else if (_sameKeyOrder(a, b, count))
+ {
+ return _areEqualValues(a, b, count);
+ }
+ else
+ {
+ // We need to compare with keys in the same order
+ List<JSONKeyValue> sortedAs;
+ sortedAs.addRange(a, count);
+
+ List<JSONKeyValue> sortedBs;
+ sortedBs.addRange(b, count);
+
+ sortedAs.sort([](const JSONKeyValue&a, const JSONKeyValue& b) -> bool { return a.key < b.key; });
+ sortedBs.sort([](const JSONKeyValue&a, const JSONKeyValue& b) -> bool { return a.key < b.key; });
+
+ return _areEqualOrderedKeys(sortedAs.getBuffer(), sortedBs.getBuffer(), count);
+ }
+}
+
+bool JSONContainer::areEqual(const JSONValue& a, const JSONValue& b)
+{
+ if (&a == &b)
+ {
+ return true;
+ }
+
+ if (a.type == b.type)
+ {
+ switch (a.type)
+ {
+ default:
+ // Invalid are never equal
+ case JSONValue::Type::Invalid: return false;
+ case JSONValue::Type::True:
+ case JSONValue::Type::False:
+ case JSONValue::Type::Null:
+ {
+ return true;
+ }
+ case JSONValue::Type::IntegerLexeme:return asInteger(a) == asInteger(b);
+ case JSONValue::Type::FloatLexeme: return asFloat(a) == asFloat(b);
+ case JSONValue::Type::StringLexeme:
+ {
+ // If the lexemes are equal they are equal
+ UnownedStringSlice lexemeA = getLexeme(a);
+ UnownedStringSlice lexemeB = getLexeme(b);
+ // Else we want to decode the string to be sure if they are equal.
+ return lexemeA == lexemeB || getStringKey(a) == getStringKey(b);
+ }
+ case JSONValue::Type::IntegerValue: return a.intValue == b.intValue;
+ case JSONValue::Type::FloatValue: return a.floatValue == b.floatValue;
+ case JSONValue::Type::StringValue: return a.stringKey == b.stringKey;
+
+ case JSONValue::Type::Array:
+ {
+ if (a.rangeIndex == b.rangeIndex)
+ {
+ return true;
+ }
+ auto arrayA = getArray(a);
+ auto arrayB = getArray(b);
+
+ const Index count = arrayA.getCount();
+ return (count == arrayB.getCount()) && areEqual(arrayA.getBuffer(), arrayB.getBuffer(), count);
+ }
+ case JSONValue::Type::Object:
+ {
+ if (a.rangeIndex == b.rangeIndex)
+ {
+ return true;
+ }
+ const auto aValues = getObject(a);
+ const auto bValues = getObject(b);
+
+ const Index count = aValues.getCount();
+ return (count == bValues.getCount()) && areEqual(aValues.getBuffer(), bValues.getBuffer(), count);
+ }
+ }
+ }
+
+ // If they are the same kind, and float/int/string we can convert to compare
+ const JSONValue::Kind kind = a.getKind();
+ if (kind == b.getKind())
+ {
+ switch (kind)
+ {
+ case JSONValue::Kind::String: return getStringKey(a) == getStringKey(b);
+ case JSONValue::Kind::Integer: return asInteger(a) == asInteger(b);
+ case JSONValue::Kind::Float: return asFloat(a) == asFloat(b);
+ default: break;
+ }
+ }
+
+ return false;
+}
+
+} // namespace Slang