diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2021-06-01 16:58:07 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-06-01 16:58:07 -0400 |
| commit | 7a3c87b58de2683c077bd5341052c2e3cebeb048 (patch) | |
| tree | 8641667ebcfecd728bfe8b572822751fae1c55bd /source/compiler-core | |
| parent | 67486ee516ddc33806003727682cbfc68ab1f726 (diff) | |
JSONValue / Container (#1864)
* #include an absolute path didn't work - because paths were taken to always be relative.
* WIP JSONWriter/JSONParser.
* Checking different Layout styles for JSON.
* Add slang-json-parser.h/.cpp
* WIP JSONValue.
* Added JSONValue::destroy/Recursive.
* Improvement to JSONValue.
* Improve text double conversion precision. Testing.
* Simplify double parsing (just use atof).
JSON comparison
More testing of conversions and start of JSONValue.
* Add <math.h> for isnan, isinf etc.
* Small improvement with object comparison.
* Fix typo in getArgsByName.
* Removed use of isnan and isinf as includes don't work on linux.
* Improve JSON unit test.
* Added asInteger/asFloat/asBool to JSONValue.
* Change comment to trigger CI build.
Diffstat (limited to 'source/compiler-core')
| -rw-r--r-- | source/compiler-core/slang-command-line-args.cpp | 4 | ||||
| -rw-r--r-- | source/compiler-core/slang-command-line-args.h | 4 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-value.cpp | 811 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-value.h | 243 |
4 files changed, 1058 insertions, 4 deletions
diff --git a/source/compiler-core/slang-command-line-args.cpp b/source/compiler-core/slang-command-line-args.cpp index d7a053b00..ec03e3116 100644 --- a/source/compiler-core/slang-command-line-args.cpp +++ b/source/compiler-core/slang-command-line-args.cpp @@ -171,14 +171,14 @@ Index DownstreamArgs::_findOrAddName(SourceLoc loc, const UnownedStringSlice& na return -1; } -CommandLineArgs& DownstreamArgs::getArgsByName(char* name) +CommandLineArgs& DownstreamArgs::getArgsByName(const char* name) { const Index index = findName(name); SLANG_ASSERT(index >= 0); return m_entries[index].args; } -const CommandLineArgs& DownstreamArgs::getArgsByName(char* name) const +const CommandLineArgs& DownstreamArgs::getArgsByName(const char* name) const { const Index index = findName(name); SLANG_ASSERT(index >= 0); diff --git a/source/compiler-core/slang-command-line-args.h b/source/compiler-core/slang-command-line-args.h index c18996005..31807cd48 100644 --- a/source/compiler-core/slang-command-line-args.h +++ b/source/compiler-core/slang-command-line-args.h @@ -147,8 +147,8 @@ struct DownstreamArgs /// Get the args at the nameIndex CommandLineArgs& getArgsAt(Index nameIndex) { return m_entries[nameIndex].args; } /// Get args by name - will assert if name isn't found - CommandLineArgs& getArgsByName(char* name); - const CommandLineArgs& getArgsByName(char* name) const; + CommandLineArgs& getArgsByName(const char* name); + const CommandLineArgs& getArgsByName(const char* name) const; /// Looks for '-X' expressions, removing them from ioArgs and putting in appropriate args SlangResult stripDownstreamArgs(CommandLineArgs& ioArgs, Flags flags, DiagnosticSink* sink); 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 diff --git a/source/compiler-core/slang-json-value.h b/source/compiler-core/slang-json-value.h new file mode 100644 index 000000000..48cde5750 --- /dev/null +++ b/source/compiler-core/slang-json-value.h @@ -0,0 +1,243 @@ +// slang-json-value.h +#ifndef SLANG_JSON_VALUE_H +#define SLANG_JSON_VALUE_H + +#include "../core/slang-basic.h" + +#include "slang-source-loc.h" +#include "slang-diagnostic-sink.h" + +namespace Slang { + +typedef uint32_t JSONKey; + +struct JSONValue +{ + enum class Kind + { + Invalid, + + Null, + + Bool, + String, + Integer, + Float, + + Array, + Object, + + CountOf, + }; + + enum class Type + { + Invalid, + + True, + False, + Null, + + StringLexeme, + IntegerLexeme, + FloatLexeme, + + IntegerValue, + FloatValue, + StringValue, + + Array, + Object, + + CountOf, + }; + + static bool isLexeme(Type type) { return Index(type) >= Index(Type::StringLexeme) && Index(type) <= Index(Type::FloatLexeme); } + + static JSONValue makeInt(int64_t inValue, SourceLoc loc = SourceLoc()) { JSONValue value; value.type = Type::IntegerValue; value.loc = loc; value.intValue = inValue; return value; } + static JSONValue makeFloat(double inValue, SourceLoc loc = SourceLoc()) { JSONValue value; value.type = Type::FloatValue; value.loc = loc; value.floatValue = inValue; return value; } + static JSONValue makeNull(SourceLoc loc = SourceLoc()) { JSONValue value; value.type = Type::Null; value.loc = loc; return value; } + static JSONValue makeBool(bool inValue, SourceLoc loc = SourceLoc()) { JSONValue value; value.type = (inValue ? Type::True : Type::False); value.loc = loc; return value; } + + static JSONValue makeLexeme(Type type, SourceLoc loc, Index length) { SLANG_ASSERT(isLexeme(type)); JSONValue value; value.type = type; value.loc = loc; value.length = length; return value; } + + static JSONValue makeEmptyArray(SourceLoc loc = SourceLoc()) { JSONValue value; value.type = Type::Array; value.loc = loc; value.rangeIndex = 0; return value; } + static JSONValue makeEmptyObject(SourceLoc loc = SourceLoc()) { JSONValue value; value.type = Type::Object; value.loc = loc; value.rangeIndex = 0; return value; } + + // The following functions only work if the value is stored directly NOT as a lexeme. Use the methods on the container + // to access values if it is potentially stored as a lexeme + + /// As a boolean value + bool asBool() const; + /// As an integer value + int64_t asInteger() const; + /// As a float value + double asFloat() const; + + /// True if this is a object like (array or object) + bool isObjectLike() const { return Index(type) >= Index(Type::Array); } + + /// True if this appears to be a valid value + bool isValid() const { return type != JSONValue::Type::Invalid; } + + /// True if needs destroy + bool needsDestroy() const { return isObjectLike() && rangeIndex != 0; } + + /// Get the kind + SLANG_FORCE_INLINE Kind getKind() const { return getKindForType(type); } + + /// Given a type return the associated kind + static Kind getKindForType(Type type) { return g_typeToKind[Index(type)]; } + + Type type; ///< The type of value + SourceLoc loc; ///< The (optional) location in source of this value. + + union + { + Index rangeIndex; ///< Used for Array/Object + Index length; ///< Length in bytes if it is a 'Lexeme' + double floatValue; ///< Float value + int64_t intValue; ///< Integer value + JSONKey stringKey; ///< The pool key if it's a string + }; + + static const Kind g_typeToKind[Index(Type::CountOf)]; +}; + +struct JSONKeyValue +{ + /// True if it's valid + bool isValid() const { return value.type != JSONValue::Type::Invalid; } + + JSONKey key; + SourceLoc keyLoc; + JSONValue value; + + static JSONKeyValue g_invalid; +}; + +class JSONContainer : public RefObject +{ +public: + + /// Make a new array + JSONValue createArray(const JSONValue* values, Index valuesCount, SourceLoc loc = SourceLoc()); + /// Make a new object + JSONValue createObject(const JSONKeyValue* keyValues, Index keyValueCount, SourceLoc loc = SourceLoc()); + /// Make a string + JSONValue createString(const UnownedStringSlice& slice, SourceLoc loc = SourceLoc()); + + ConstArrayView<JSONValue> getArray(const JSONValue& in) const; + ConstArrayView<JSONKeyValue> getObject(const JSONValue& in) const; + + ArrayView<JSONValue> getArray(const JSONValue& in); + ArrayView<JSONKeyValue> getObject(const JSONValue& in); + + /// Add value to array. + void addToArray(JSONValue& array, const JSONValue& value); + + /// Get the value at the index in the array + JSONValue& getAt(const JSONValue& array, Index index); + + /// Returns the index + Index findKeyGlobalIndex(const JSONValue& obj, JSONKey key); + Index findKeyGlobalIndex(const JSONValue& obj, const UnownedStringSlice& slice); + + /// Set a key value for the obj + void setKeyValue(JSONValue& obj, JSONKey key, const JSONValue& value, SourceLoc loc = SourceLoc()); + + /// Returns true if found + bool removeKey(JSONValue& obj, JSONKey key); + bool removeKey(JSONValue& obj, const UnownedStringSlice& slice); + + /// As a boolean value + bool asBool(const JSONValue& value); + /// As an integer value + int64_t asInteger(const JSONValue& value); + /// As a float value + double asFloat(const JSONValue& value); + + /// Returns string as a key + JSONKey getStringKey(const JSONValue& in); + + /// Get as a string. + UnownedStringSlice getString(const JSONValue& in); + + /// Gets the lexeme + UnownedStringSlice getLexeme(const JSONValue& in); + + /// Get a key for a name + JSONKey getKey(const UnownedStringSlice& slice); + /// Get the string from the key + UnownedStringSlice getStringFromKey(JSONKey key) const { return m_slicePool.getSlice(StringSlicePool::Handle(key)); } + + /// True if they are the same value + /// If object like type comparison is performed recursively. + /// NOTE! That Float and Integer values do not compare & source locations are ignored. + bool areEqual(const JSONValue& a, const JSONValue& b); + bool areEqual(const JSONValue* a, const JSONValue* b, Index count); + bool areEqual(const JSONKeyValue* a, const JSONKeyValue* b, Index count); + + /// Destroy value + void destroy(JSONValue& value); + /// Destroy recursively from value + void destroyRecursively(JSONValue& value); + + // + JSONContainer(SourceManager* sourceManger); + + /// Returns true if all the keys are unique + static bool areKeysUnique(const JSONKeyValue* keyValues, Index keyValueCount); + +protected: + struct Range + { + // We want to record the underlying range, because we don't track JSONValue, and so we need to know what the range + // applies to if we want to reorder, flatten etc. + enum class Type + { + None, + Destroyed, + Object, + Array, + }; + + /// Is active if it consuming some part of a value list (even if zero count) + SLANG_FORCE_INLINE bool isActive() const { return Index(type) >= Index(Type::Object); } + + Type type; + Index startIndex; + Index count; + Index capacity; + }; + + template <typename T> + static void _add(Range& range, List<T>& list, const T& value); + + Index _addRange(Range::Type type, Index startIndex, Index count); + void _removeKey(JSONValue& obj, Index globalIndex); + /// Note does not destroy values in range. + void _destroyRange(Index rangeIndex); + + static bool _sameKeyOrder(const JSONKeyValue* a, const JSONKeyValue* b, Index count); + /// True if the values are equal + bool _areEqualValues(const JSONKeyValue* a, const JSONKeyValue* b, Index count); + /// True if the key and value are equal + bool _areEqualOrderedKeys(const JSONKeyValue* a, const JSONKeyValue* b, Index count); + + StringBuilder m_buf; ///< A temporary buffer used to hold unescaped strings + + SourceView* m_currentView = nullptr; + SourceManager* m_sourceManager; + + StringSlicePool m_slicePool; + List<Range> m_ranges; + List<Index> m_freeRangeIndices; + List<JSONValue> m_arrayValues; + List<JSONKeyValue> m_objectValues; + +}; + +} // namespace Slang + +#endif |
