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 | |
| 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.
| -rw-r--r-- | build/visual-studio/compiler-core/compiler-core.vcxproj | 2 | ||||
| -rw-r--r-- | build/visual-studio/compiler-core/compiler-core.vcxproj.filters | 6 | ||||
| -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 | ||||
| -rw-r--r-- | source/core/slang-list.h | 2 | ||||
| -rw-r--r-- | source/core/slang-string-escape-util.cpp | 43 | ||||
| -rw-r--r-- | source/core/slang-string-escape-util.h | 16 | ||||
| -rw-r--r-- | source/core/slang-string-util.cpp | 100 | ||||
| -rw-r--r-- | source/core/slang-string-util.h | 6 | ||||
| -rw-r--r-- | tools/slang-test/slang-test-main.cpp | 4 | ||||
| -rw-r--r-- | tools/slang-test/unit-test-json.cpp | 45 | ||||
| -rw-r--r-- | tools/slang-test/unit-test-string.cpp | 137 |
14 files changed, 1412 insertions, 11 deletions
diff --git a/build/visual-studio/compiler-core/compiler-core.vcxproj b/build/visual-studio/compiler-core/compiler-core.vcxproj index f167e59d8..a5ad9a372 100644 --- a/build/visual-studio/compiler-core/compiler-core.vcxproj +++ b/build/visual-studio/compiler-core/compiler-core.vcxproj @@ -183,6 +183,7 @@ <ClInclude Include="..\..\..\source\compiler-core\slang-json-diagnostics.h" /> <ClInclude Include="..\..\..\source\compiler-core\slang-json-lexer.h" /> <ClInclude Include="..\..\..\source\compiler-core\slang-json-parser.h" /> + <ClInclude Include="..\..\..\source\compiler-core\slang-json-value.h" /> <ClInclude Include="..\..\..\source\compiler-core\slang-lexer-diagnostic-defs.h" /> <ClInclude Include="..\..\..\source\compiler-core\slang-lexer.h" /> <ClInclude Include="..\..\..\source\compiler-core\slang-misc-diagnostic-defs.h" /> @@ -208,6 +209,7 @@ <ClCompile Include="..\..\..\source\compiler-core\slang-json-diagnostics.cpp" /> <ClCompile Include="..\..\..\source\compiler-core\slang-json-lexer.cpp" /> <ClCompile Include="..\..\..\source\compiler-core\slang-json-parser.cpp" /> + <ClCompile Include="..\..\..\source\compiler-core\slang-json-value.cpp" /> <ClCompile Include="..\..\..\source\compiler-core\slang-lexer.cpp" /> <ClCompile Include="..\..\..\source\compiler-core\slang-name-convention-util.cpp" /> <ClCompile Include="..\..\..\source\compiler-core\slang-name.cpp" /> diff --git a/build/visual-studio/compiler-core/compiler-core.vcxproj.filters b/build/visual-studio/compiler-core/compiler-core.vcxproj.filters index fc72b5af8..da0ca5d63 100644 --- a/build/visual-studio/compiler-core/compiler-core.vcxproj.filters +++ b/build/visual-studio/compiler-core/compiler-core.vcxproj.filters @@ -48,6 +48,9 @@ <ClInclude Include="..\..\..\source\compiler-core\slang-json-parser.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="..\..\..\source\compiler-core\slang-json-value.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="..\..\..\source\compiler-core\slang-lexer-diagnostic-defs.h"> <Filter>Header Files</Filter> </ClInclude> @@ -119,6 +122,9 @@ <ClCompile Include="..\..\..\source\compiler-core\slang-json-parser.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="..\..\..\source\compiler-core\slang-json-value.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="..\..\..\source\compiler-core\slang-lexer.cpp"> <Filter>Source Files</Filter> </ClCompile> 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 diff --git a/source/core/slang-list.h b/source/core/slang-list.h index 08d9aa773..4420cc084 100644 --- a/source/core/slang-list.h +++ b/source/core/slang-list.h @@ -351,7 +351,7 @@ namespace Slang void growToCount(Index count) { - Index newBufferCount = Index(1) << Math::Log2Ceil(count); + Index newBufferCount = Index(1) << Math::Log2Ceil((unsigned int)count); if (m_capacity < newBufferCount) { reserve(newBufferCount); diff --git a/source/core/slang-string-escape-util.cpp b/source/core/slang-string-escape-util.cpp index a91d88e05..ffc43a7cb 100644 --- a/source/core/slang-string-escape-util.cpp +++ b/source/core/slang-string-escape-util.cpp @@ -18,6 +18,8 @@ public: virtual bool isQuotingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE { return isEscapingNeeded(slice); } virtual bool isEscapingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE; + virtual bool isUnescapingNeeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE; + virtual SlangResult appendEscaped(const UnownedStringSlice& slice, StringBuilder& out) SLANG_OVERRIDE; virtual SlangResult appendUnescaped(const UnownedStringSlice& slice, StringBuilder& out) SLANG_OVERRIDE; virtual SlangResult lexQuoted(const char* cursor, const char** outCursor) SLANG_OVERRIDE; @@ -30,6 +32,13 @@ bool SpaceStringEscapeHandler::isEscapingNeeded(const UnownedStringSlice& slice) return slice.indexOf(' ') >= 0; } +bool SpaceStringEscapeHandler::isUnescapingNeeeded(const UnownedStringSlice& slice) +{ + SLANG_UNUSED(slice); + // As it stands we never have to unescape + return false; +} + SlangResult SpaceStringEscapeHandler::appendUnescaped(const UnownedStringSlice& slice, StringBuilder& out) { if (slice.indexOf('"') >= 0) @@ -98,6 +107,7 @@ public: virtual bool isQuotingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE { SLANG_UNUSED(slice); return true; } virtual bool isEscapingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE; + virtual bool isUnescapingNeeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE; virtual SlangResult appendEscaped(const UnownedStringSlice& slice, StringBuilder& out) SLANG_OVERRIDE; virtual SlangResult appendUnescaped(const UnownedStringSlice& slice, StringBuilder& out) SLANG_OVERRIDE; virtual SlangResult lexQuoted(const char* cursor, const char** outCursor) SLANG_OVERRIDE; @@ -167,6 +177,12 @@ static char _getCppUnescapedChar(char c) } } + +bool CppStringEscapeHandler::isUnescapingNeeeded(const UnownedStringSlice& slice) +{ + return slice.indexOf('\\') >= 0; +} + /* static */bool CppStringEscapeHandler::isEscapingNeeded(const UnownedStringSlice& slice) { const char* cur = slice.begin(); @@ -456,6 +472,7 @@ public: virtual bool isQuotingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE { SLANG_UNUSED(slice); return true; } virtual bool isEscapingNeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE; + virtual bool isUnescapingNeeeded(const UnownedStringSlice& slice) SLANG_OVERRIDE; virtual SlangResult appendEscaped(const UnownedStringSlice& slice, StringBuilder& out) SLANG_OVERRIDE; virtual SlangResult appendUnescaped(const UnownedStringSlice& slice, StringBuilder& out) SLANG_OVERRIDE; virtual SlangResult lexQuoted(const char* cursor, const char** outCursor) SLANG_OVERRIDE; @@ -463,6 +480,11 @@ public: JSONStringEscapeHandler() : Super('"') {} }; +bool JSONStringEscapeHandler::isUnescapingNeeeded(const UnownedStringSlice& slice) +{ + return slice.indexOf('\\') >= 0; +} + bool JSONStringEscapeHandler::isEscapingNeeded(const UnownedStringSlice& slice) { const char* cur = slice.begin(); @@ -868,6 +890,23 @@ StringEscapeUtil::Handler* StringEscapeUtil::getHandler(Style style) } } +/* static */bool StringEscapeUtil::isQuoted(char quoteChar, UnownedStringSlice& slice) +{ + const Index len = slice.getLength(); + return len >= 2 && slice[0] == quoteChar && slice[len - 1] == quoteChar; +} + +/* static */UnownedStringSlice StringEscapeUtil::unquote(char quoteChar, const UnownedStringSlice& slice) +{ + const Index len = slice.getLength(); + if (len >= 2 && slice[0] == quoteChar && slice[len - 1] == quoteChar) + { + return UnownedStringSlice(slice.begin() + 1, len - 2); + } + SLANG_ASSERT(!"Not quoted!"); + return UnownedStringSlice(); +} + /* static */SlangResult StringEscapeUtil::appendMaybeUnquoted(Handler* handler, const UnownedStringSlice& slice, StringBuilder& out) { const char quoteChar = handler->getQuoteChar(); @@ -885,6 +924,10 @@ StringEscapeUtil::Handler* StringEscapeUtil::getHandler(Style style) } } +/* static */SlangResult StringEscapeUtil::isUnescapeShellLikeNeeded(Handler* handler, const UnownedStringSlice& slice) +{ + return slice.indexOf(handler->getQuoteChar()) >= 0; +} /* static */SlangResult StringEscapeUtil::unescapeShellLike(Handler* handler, const UnownedStringSlice& slice, StringBuilder& out) { diff --git a/source/core/slang-string-escape-util.h b/source/core/slang-string-escape-util.h index c3039eb47..5f749c423 100644 --- a/source/core/slang-string-escape-util.h +++ b/source/core/slang-string-escape-util.h @@ -14,6 +14,9 @@ public: virtual bool isQuotingNeeded(const UnownedStringSlice& slice) = 0; /// True if any escaping is needed. If not slice can be used (assuming appropriate quoting) as is virtual bool isEscapingNeeded(const UnownedStringSlice& slice) = 0; + /// True if we need to unescape + virtual bool isUnescapingNeeeded(const UnownedStringSlice& slice) = 0; + /// Takes slice and adds any appropriate escaping (for example C++/C type escaping for special characters like '\', '"' and if not ascii will write out as hex sequence) /// Does not append quotes virtual SlangResult appendEscaped(const UnownedStringSlice& slice, StringBuilder& out) = 0; @@ -57,6 +60,14 @@ struct StringEscapeUtil /// Given a style returns a handler static Handler* getHandler(Style style); + /// Get without quotes. Will assert if not correctly quoted + static UnownedStringSlice unquote(char quoteChar, const UnownedStringSlice& slice); + static UnownedStringSlice unquote(Handler* handler, const UnownedStringSlice& slice) { return unquote(handler->getQuoteChar(), slice); } + + /// True is slice is quoted + static bool isQuoted(char quoteChar, UnownedStringSlice& slice); + static bool isQuoted(Handler* handler, UnownedStringSlice& slice) { return isQuoted(handler->getQuoteChar(), slice); } + /// If quoting is needed appends to out quoted static SlangResult appendMaybeQuoted(Handler* handler, const UnownedStringSlice& slice, StringBuilder& out); @@ -69,6 +80,11 @@ struct StringEscapeUtil /// Append with quotes (even if not needed) static SlangResult appendQuoted(Handler* handler, const UnownedStringSlice& slice, StringBuilder& out); + + /// True if requires 'shell-like' unescape. With shell-like, quoting does *not* have to start at the start of the slice. + /// and there may be multiple quoted section + static SlangResult isUnescapeShellLikeNeeded(Handler* handler, const UnownedStringSlice& slice); + /// Shells can have multiple quoted sections. This function makes a string with out quoting static SlangResult unescapeShellLike(Handler* handler, const UnownedStringSlice& slice, StringBuilder& out); }; diff --git a/source/core/slang-string-util.cpp b/source/core/slang-string-util.cpp index 7a142f643..b2886c413 100644 --- a/source/core/slang-string-util.cpp +++ b/source/core/slang-string-util.cpp @@ -458,9 +458,31 @@ ComPtr<ISlangBlob> StringUtil::createStringBlob(const String& string) } } -SLANG_FORCE_INLINE static bool _isDigit(char c) +/* static */SlangResult StringUtil::parseDouble(const UnownedStringSlice& text, double& out) { - return (c >= '0' && c <= '9'); + const Index bufSize = 32; + + const auto len = text.getLength(); + + if (len > bufSize - 1) + { + List<char> work; + work.setCount(len + 1); + char* dst = work.getBuffer(); + + ::memcpy(dst, text.begin(), len * sizeof(char)); + dst[len] = 0; + + out = atof(dst); + } + else + { + char buf[bufSize]; + ::memcpy(buf, text.begin(), len * sizeof(char)); + buf[len] = 0; + out = atof(buf); + } + return SLANG_OK; } /* static */SlangResult StringUtil::parseInt(const UnownedStringSlice& in, Int& outValue) @@ -476,7 +498,7 @@ SLANG_FORCE_INLINE static bool _isDigit(char c) } // We need at least one digit - if (cur >= end || !_isDigit(*cur)) + if (cur >= end || !CharUtil::isDigit(*cur)) { return SLANG_FAIL; } @@ -486,7 +508,7 @@ SLANG_FORCE_INLINE static bool _isDigit(char c) for (; cur < end; ++cur) { const char c = *cur; - if (!_isDigit(c)) + if (!CharUtil::isDigit(c)) { return SLANG_FAIL; } @@ -499,4 +521,74 @@ SLANG_FORCE_INLINE static bool _isDigit(char c) return SLANG_OK; } +/* static */SlangResult StringUtil::parseInt64(const UnownedStringSlice& text, int64_t& out) +{ + bool negate = false; + + const char* cur = text.begin(); + const char* end = text.end(); + + if (cur < end) + { + if (*cur == '-') + { + negate = true; + cur++; + } + else if (*cur == '+') + { + cur++; + } + } + + // Must have at least one digit + if (cur >= end || !CharUtil::isDigit(*cur)) + { + return SLANG_FAIL; + } + + uint64_t value = 0; + // We can have 20 digits, but the last digit can cause overflow. + // Lets do the easy first digits first + Index numSimple = 19; + for (; cur < end && CharUtil::isDigit(*cur) && numSimple > 0; ++cur, --numSimple) + { + value = value * 10 + (*cur - '0'); + } + + if (cur < end && CharUtil::isDigit(*cur)) + { + const auto prevValue = value; + value = value * 10 + (*cur - '0'); + cur++; + + if (value < prevValue) + { + // We have overflow + return SLANG_FAIL; + } + } + + if (negate) + { + if (value > ~((~uint64_t(0)) >> 1)) + { + // Overflow + return SLANG_FAIL; + } + out = -int64_t(value); + } + else + { + if (value > ((~uint64_t(0)) >> 1)) + { + // Overflow + return SLANG_FAIL; + } + out = value; + } + + return (cur == end) ? SLANG_OK : SLANG_FAIL; +} + } // namespace Slang diff --git a/source/core/slang-string-util.h b/source/core/slang-string-util.h index 2b30120b7..6a0794082 100644 --- a/source/core/slang-string-util.h +++ b/source/core/slang-string-util.h @@ -100,6 +100,12 @@ struct StringUtil /// Convert in to int. Returns SLANG_FAIL on error static SlangResult parseInt(const UnownedStringSlice& in, Int& outValue); + + /// Convert ioText into double. Returns SLANG_OK on success. + static SlangResult parseDouble(const UnownedStringSlice& text, double& out); + + /// Convert into int64_t. Returns SLANG_OK on success. + static SlangResult parseInt64(const UnownedStringSlice& text, int64_t& out); }; /* A helper class that allows parsing of lines from text with iteration. Uses StringUtil::extractLine for the actual underlying implementation. */ diff --git a/tools/slang-test/slang-test-main.cpp b/tools/slang-test/slang-test-main.cpp index ded4c777c..5e0d1142d 100644 --- a/tools/slang-test/slang-test-main.cpp +++ b/tools/slang-test/slang-test-main.cpp @@ -1416,8 +1416,8 @@ TestResult runCompile(TestContext* context, TestInput& input) for (auto arg : input.testOptions->args) { - // If there is a quote in the string, assume it is 'escaped'. - if (arg.indexOf(escapeHandler->getQuoteChar()) >= 0) + // If unescaping is needed, do it + if (StringEscapeUtil::isUnescapeShellLikeNeeded(escapeHandler, arg.getUnownedSlice())) { StringBuilder buf; StringEscapeUtil::unescapeShellLike(escapeHandler, arg.getUnownedSlice(), buf); diff --git a/tools/slang-test/unit-test-json.cpp b/tools/slang-test/unit-test-json.cpp index 5a59cdcc3..bcd5bf1f0 100644 --- a/tools/slang-test/unit-test-json.cpp +++ b/tools/slang-test/unit-test-json.cpp @@ -2,6 +2,7 @@ #include "../../source/compiler-core/slang-json-lexer.h" #include "../../source/core/slang-string-escape-util.h" #include "../../source/compiler-core/slang-json-parser.h" +#include "../../source/compiler-core/slang-json-value.h" #include "test-context.h" @@ -221,6 +222,50 @@ static void jsonUnitTest() } } + + + { + JSONValue value; + + value = JSONValue::makeBool(true); + + // Only need a SourceManager if we are going to store lexemes + RefPtr<JSONContainer> container = new JSONContainer(nullptr); + + { + List<JSONValue> values; + + for (Int i = 0; i < 100; ++i) + { + + values.add(JSONValue::makeInt(i)); + values.add(JSONValue::makeFloat(-double(i))); + } + + JSONValue array = container->createArray(values.getBuffer(), values.getCount()); + + auto arrayView = container->getArray(array); + + SLANG_CHECK(arrayView.getCount() == values.getCount()); + + // Check the values are the same + SLANG_CHECK(container->areEqual(arrayView.getBuffer(), values.getBuffer(), arrayView.getCount())); + } + { + JSONValue obj = JSONValue::makeEmptyObject(); + + JSONKey key = container->getKey(UnownedStringSlice::fromLiteral("Hello")); + + container->setKeyValue(obj, key, JSONValue::makeNull()); + container->setKeyValue(obj, key, JSONValue::makeInt(10)); + + auto objView = container->getObject(obj); + + SLANG_CHECK(objView.getCount() == 1); + + SLANG_CHECK(objView[0].value.asInteger() == 10); + } + } } SLANG_UNIT_TEST("JSON", jsonUnitTest); diff --git a/tools/slang-test/unit-test-string.cpp b/tools/slang-test/unit-test-string.cpp index e27411b3e..e8a33fbbe 100644 --- a/tools/slang-test/unit-test-string.cpp +++ b/tools/slang-test/unit-test-string.cpp @@ -4,6 +4,10 @@ #include "test-context.h" +//#include <math.h> + +#include <sstream> + using namespace Slang; static bool _areEqual(const List<UnownedStringSlice>& lines, const UnownedStringSlice* checkLines, Int checkLinesCount) @@ -43,6 +47,71 @@ static bool _checkLineParser(const UnownedStringSlice& input) return StringUtil::extractLine(remaining, line) == false; } +static void _append(double v, StringBuilder& buf) +{ + std::ostringstream stream; + stream.imbue(std::locale::classic()); + stream.setf(std::ios::fixed, std::ios::floatfield); + stream.precision(20); + + stream << std::scientific << v; + + buf << stream.str().c_str(); +} + +// Unit of least precision +static int64_t _calcULPDistance(double a, double b) +{ + // Save work if the floats are equal. + // Also handles +0 == -0 + if (a == b) + { + return 0; + } + + const int64_t max = int64_t((~uint64_t(0)) >> 1); + +#if 0 + // Max distance for NaN + if (isnan(a) || isnan(b)) + { + return max; + } + + // If one's infinite and they're not equal, max distance. + if (isinf(a) || isinf(b)) + { + return max; + } +#endif + + int64_t ia, ib; + memcpy(&ia, &a, sizeof(a)); + memcpy(&ib, &b, sizeof(b)); + + // Don't compare differently-signed floats. + if ((ia < 0) != (ib < 0)) + { + return max; + } + + // Return the absolute value of the distance in ULPs. + int64_t distance = ia - ib; + return distance < 0 ? -distance : distance; +} + +static bool _areApproximatelyEqual(double a, double b, double fixedEpsilon = 1e-10, int ulpsEpsilon = 100) +{ + // Handle the near-zero case. + const double difference = abs(a - b); + if (difference <= fixedEpsilon) + { + return true; + } + + return _calcULPDistance(a, b) <= ulpsEpsilon; +} + static void stringUnitTest() { { @@ -122,6 +191,74 @@ static void stringUnitTest() SLANG_CHECK(checkValues.getArrayView() == ArrayView<UnownedStringSlice>(values, 3)); } } + { + + List<double> values; + values.add(0.0); + values.add(-0.0); + + for (Index i = -300; i < 300; ++i) + { + double value = pow(10, i); + + values.add(value); + values.add(-value); + + values.addRange(value / 3); + values.addRange(-value / 3); + } + + StringBuilder buf; + + for (auto value : values) + { + buf.Clear(); + _append(value, buf); + + UnownedStringSlice slice = buf.getUnownedSlice(); + + double parsedValue; + SlangResult res = StringUtil::parseDouble(slice, parsedValue); + + auto ulpsParsed = _calcULPDistance(value, parsedValue); + + SLANG_CHECK(SLANG_SUCCEEDED(res)); + + // Check that they are equal + SLANG_CHECK(_areApproximatelyEqual(value, parsedValue)); + } + } + { + List<int64_t> values; + values.add(0); + + for (Index i = 0; i < 63; ++i) + { + auto value = int64_t(1) << i; + + values.add(value); + values.add(-value); + } + + StringBuilder buf; + + for (auto value : values) + { + buf.Clear(); + buf << value; + + + int64_t parsedValue; + + UnownedStringSlice slice = buf.getUnownedSlice(); + SlangResult res = StringUtil::parseInt64(slice, parsedValue); + + SLANG_CHECK(SLANG_SUCCEEDED(res)); + + // Check that they are equal + SLANG_CHECK(value == parsedValue); + } + } } SLANG_UNIT_TEST("String", stringUnitTest); |
