summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2021-06-01 16:58:07 -0400
committerGitHub <noreply@github.com>2021-06-01 16:58:07 -0400
commit7a3c87b58de2683c077bd5341052c2e3cebeb048 (patch)
tree8641667ebcfecd728bfe8b572822751fae1c55bd /source
parent67486ee516ddc33806003727682cbfc68ab1f726 (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')
-rw-r--r--source/compiler-core/slang-command-line-args.cpp4
-rw-r--r--source/compiler-core/slang-command-line-args.h4
-rw-r--r--source/compiler-core/slang-json-value.cpp811
-rw-r--r--source/compiler-core/slang-json-value.h243
-rw-r--r--source/core/slang-list.h2
-rw-r--r--source/core/slang-string-escape-util.cpp43
-rw-r--r--source/core/slang-string-escape-util.h16
-rw-r--r--source/core/slang-string-util.cpp100
-rw-r--r--source/core/slang-string-util.h6
9 files changed, 1220 insertions, 9 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
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. */