diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2021-11-18 15:58:12 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-11-18 15:58:12 -0500 |
| commit | 1d5f815b3964edee8a2d701e1a6cc078c89d677f (patch) | |
| tree | aa5b4b1473344e635d7ce1d2159fc57eeb40b841 /source/compiler-core | |
| parent | b482844b689eb109ee1d70c527e098400ac6d409 (diff) | |
RTTI/JSON (#2021)
* #include an absolute path didn't work - because paths were taken to always be relative.
* Use 'Process' to communicate with an command line tool.
* Remove slang-win-stream
* Tidy up windows ProcessUtil.
* First version of BufferedReadStream.
* Windows working IPC for steams.
* Test proxy count option.
* Split Process/ProcessUtil. Process is platform dependant. ProcessUtil are functions that are platform independent.
* First implementation of Unix Process interface.
* Unix process compiles on cygwin.
* Fix typo in unix process.
* Separate unix pipe stream error of invalid access, from pipe availability.
* Fix in standard line extraction.
* Make fd non blocking.
* Fix issues with Windows Process streams.
* Added UnixPipe.
* Some fixes around UnixPipeStream.
* Make a unix stream closed explicit.
* Hack to debug linux process/stream.
* Revert to old linux pipe handling.
* Pass executable path for unit tests.
Split out CommandLine into own source.
* Small improvements in process/command line.
* Check process behavior with crash.
* Make stderr and stdout unbuffered for crash testing.
* Only turn disable buffering in crash test.
* Disable crash test on CI.
* Fix crash on clang/linux.
* Enable crash test.
Remove _appendBuffer as can use StreamUtil functionality.
* Added inital processing for http headers.
* Small improvements to HttpHeader.
* First pass HTTPPacketConnection working on windows.
* Enable other Process communication tests.
* Update comments.
* WIP JSON RPC.
* Add terminate to Process.
Made JSONRPC a Util.
* Small tidy up around HTTPPacketConnection.
* Improve process termination options.
* WIP for test-server.
* Add diagnostics error handling to test-server.
* Improved JSON support.
Parsing/creating JSON-RPC messages.
* WIP JSONRPC parsing.
* First pass RttiInfo support.
* WIP converting between JSON/native types.
* Project files.
* Split out RttiUtil.
Made RttiInfo constuction thread safe.
* WIP RTTI<->JSON.
* Add diagnostics to JSON<->native conversions.
* Make RttiInfo for structs globals. Avoids problem around derived types (like pointers), being able to cause an abort.
* Add pointer support to RTTI.
Fixed some compilation issues on linux.
* Add fixed array support.
* Added Rtti unit test.
* Add rtti unit test.
* Split out quoted/unquoted key handling.
Fix bugs in JSON value/container.
Added JSON native test.
* Make default array allocator use malloc/free.
Remove the new[] handler (doesn't work on visuals studio).
* Fix for linux warning.
* Remove some test code.
* Fix issues on x86 win.
* Fix warning on aarch64.
Diffstat (limited to 'source/compiler-core')
| -rw-r--r-- | source/compiler-core/slang-json-diagnostic-defs.h | 5 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-native.cpp | 377 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-native.h | 50 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-parser.cpp | 27 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-parser.h | 6 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-rpc.cpp | 402 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-rpc.h | 101 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-value.cpp | 83 | ||||
| -rw-r--r-- | source/compiler-core/slang-json-value.h | 34 |
9 files changed, 1071 insertions, 14 deletions
diff --git a/source/compiler-core/slang-json-diagnostic-defs.h b/source/compiler-core/slang-json-diagnostic-defs.h index da3b2a28c..23bea9b79 100644 --- a/source/compiler-core/slang-json-diagnostic-defs.h +++ b/source/compiler-core/slang-json-diagnostic-defs.h @@ -36,4 +36,9 @@ DIAGNOSTIC(20006, Error, expectingValueName, "expecting value name [null, true, DIAGNOSTIC(20007, Error, unexpectedTokenExpectedTokenType, "unexpected '$0', expected '$1'") DIAGNOSTIC(20008, Error, unexpectedToken, "unexpected '$0'") +DIAGNOSTIC(20009, Error, unableToConvertField, "unable to convert field '$0' in type '$1'") +DIAGNOSTIC(20010, Error, fieldNotFound, "field '$0' not found in type '$1'") +DIAGNOSTIC(20011, Error, fieldNotDefinedOnType, "field '$0' not defined on type '$1'") +DIAGNOSTIC(20011, Error, fieldRequiredOnType, "field '$0' required on '$1'") + #undef DIAGNOSTIC diff --git a/source/compiler-core/slang-json-native.cpp b/source/compiler-core/slang-json-native.cpp new file mode 100644 index 000000000..5b2fb5db4 --- /dev/null +++ b/source/compiler-core/slang-json-native.cpp @@ -0,0 +1,377 @@ +#include "slang-json-native.h" + +#include "../../slang-com-helper.h" + +#include "../core/slang-rtti-util.h" + +#include "slang-json-diagnostics.h" + +namespace Slang { + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! JSONToNativeConverter !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +/* static */Index JSONToNativeConverter::_getFieldCount(const StructRttiInfo* structRttiInfo) +{ + if (structRttiInfo->m_super) + { + return _getFieldCount(structRttiInfo->m_super) + structRttiInfo->m_fieldCount; + } + else + { + return structRttiInfo->m_fieldCount; + } +} + +/* static */Index JSONToNativeConverter::_findFieldIndex(const StructRttiInfo* structRttiInfo, const UnownedStringSlice& fieldName) +{ + if (structRttiInfo->m_super) + { + const Index index = _findFieldIndex(structRttiInfo->m_super, fieldName); + if (index >= 0) + { + return index + _getFieldCount(structRttiInfo->m_super); + } + } + + ConstArrayView<StructRttiInfo::Field> fields(structRttiInfo->m_fields, structRttiInfo->m_fieldCount); + + Index index = fields.findFirstIndex([fieldName](const StructRttiInfo::Field& field) ->bool { return fieldName == field.m_name; }); + if (index >= 0 && structRttiInfo->m_super) + { + index += _getFieldCount(structRttiInfo->m_super); + } + + return index; +} + +SlangResult JSONToNativeConverter::_structToNative(const ConstArrayView<JSONKeyValue>& pairs, const StructRttiInfo* structRttiInfo, void* out, Index& outFieldCount) +{ + Index fieldCount = 0; + + if (structRttiInfo->m_super) + { + SLANG_RETURN_ON_FAIL(_structToNative(pairs, structRttiInfo->m_super, out, fieldCount)); + } + + Byte* dst = (Byte*)out; + + const Index count = structRttiInfo->m_fieldCount; + + for (Index i = 0; i < count; ++i) + { + const auto& field = structRttiInfo->m_fields[i]; + + auto key = m_container->findKey(UnownedStringSlice(field.m_name)); + + if (key == 0) + { + if (field.m_flags & StructRttiInfo::Flag::Optional) + { + continue; + } + + m_sink->diagnose(SourceLoc(), JSONDiagnostics::fieldRequiredOnType, field.m_name, structRttiInfo->m_name); + + // Unable to find this key + return SLANG_FAIL; + } + + // If there are any of the pairs, that are not in the type.. it's an error + const Index index = pairs.findFirstIndex([key](const JSONKeyValue& pair) -> bool { return pair.key == key; }); + if (index < 0) + { + if (field.m_flags & StructRttiInfo::Flag::Optional) + { + continue; + } + + m_sink->diagnose(SourceLoc(), JSONDiagnostics::fieldRequiredOnType, field.m_name, structRttiInfo->m_name); + + // Unable to find this key + return SLANG_FAIL; + } + + auto& pair = pairs[index]; + + // Copy the field over + SLANG_RETURN_ON_FAIL(convert(pair.value, field.m_type, dst + field.m_offset)); + + // Field was handled + ++fieldCount; + } + + // Write off the amount of fields converted/handled. + outFieldCount = fieldCount; + return SLANG_OK; +} + +SlangResult JSONToNativeConverter::convert(const JSONValue& in, const RttiInfo* rttiInfo, void* out) +{ + if (rttiInfo->isIntegral()) + { + return RttiUtil::setInt(m_container->asInteger(in), rttiInfo, out); + } + else if (rttiInfo->isFloat()) + { + return RttiUtil::setFromDouble(m_container->asFloat(in), rttiInfo, out); + } + + switch (rttiInfo->m_kind) + { + case RttiInfo::Kind::Bool: + { + *(bool*)out = m_container->asBool(in); + return SLANG_OK; + } + case RttiInfo::Kind::Struct: + { + if (in.getKind() != JSONValue::Kind::Object) + { + return SLANG_FAIL; + } + + auto pairs = m_container->getObject(in); + const StructRttiInfo* structRttiInfo = static_cast<const StructRttiInfo*>(rttiInfo); + + Index fieldCount = 0; + SLANG_RETURN_ON_FAIL(_structToNative(pairs, structRttiInfo, out, fieldCount)); + + if (fieldCount != pairs.getCount()) + { + // We want to find the fields not found in the type + + for (auto& pair : pairs) + { + UnownedStringSlice fieldName = m_container->getStringFromKey(pair.key); + const Index index = _findFieldIndex(structRttiInfo, UnownedStringSlice(fieldName)); + + if (index < 0) + { + m_sink->diagnose(pair.keyLoc, JSONDiagnostics::fieldNotDefinedOnType, fieldName, structRttiInfo->m_name); + } + } + + // If these are different then there are fields defined in the object that are *not* defined in class definition + return SLANG_FAIL; + } + + return SLANG_OK; + } + case RttiInfo::Kind::Enum: + { + return SLANG_E_NOT_IMPLEMENTED; + } + case RttiInfo::Kind::String: + { + *(String*)out = m_container->getTransientString(in); + return SLANG_OK; + } + case RttiInfo::Kind::UnownedStringSlice: + { + // Problem -> if the slice is a lexeme, then when we decode with getString, it will lose scope. + // So we do something a bit odd and place the decoding string + + *(UnownedStringSlice*)out = m_container->getString(in); + return SLANG_OK; + } + case RttiInfo::Kind::List: + { + if (in.getKind() != JSONValue::Kind::Array) + { + return SLANG_FAIL; + } + + typedef List<Byte> Type; + Type& list = *(Type*)out; + + auto arr = m_container->getArray(in); + + const Index count = arr.getCount(); + + const ListRttiInfo* listRttiInfo = static_cast<const ListRttiInfo*>(rttiInfo); + auto elementType = listRttiInfo->m_elementType; + + SLANG_RETURN_ON_FAIL(RttiUtil::setListCount(elementType, out, arr.getCount())); + + // Okay, we need to copy over one by one + + Byte* dstEles = list.getBuffer(); + for (Index i = 0; i < count; ++i, dstEles += elementType->m_size) + { + SLANG_RETURN_ON_FAIL(convert(arr[i], elementType, dstEles)); + } + + return SLANG_OK; + } + case RttiInfo::Kind::Dictionary: + { + // We can *only* serialize this into a straight JSON object iff the key is a string-like type + // We could turn into (say) an array of keys and values + break; + } + case RttiInfo::Kind::Other: + { + if (rttiInfo == GetRttiInfo<JSONValue>::get()) + { + // Do we need to copy into the container? + // As it stands we have to assume src is stored in container. + *(JSONValue*)out = in; + return SLANG_OK; + } + return SLANG_FAIL; + } + default: break; + } + return SLANG_FAIL; +} + + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! NativeToJSONConverter !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +SlangResult NativeToJSONConverter::_structToJSON(const StructRttiInfo* structRttiInfo, const void* src, List<JSONKeyValue>& outPairs) +{ + // Do the super class first + if (structRttiInfo->m_super) + { + SLANG_RETURN_ON_FAIL(_structToJSON(structRttiInfo, src, outPairs)); + } + + const Byte* base = (const Byte*)src; + const Index count = structRttiInfo->m_fieldCount; + + for (Index i = 0; i < count; ++i) + { + const auto& field = structRttiInfo->m_fields[i]; + + if (field.m_flags & StructRttiInfo::Flag::Optional) + { + const RttiDefaultValue defaultValue = RttiDefaultValue(field.m_flags & uint8_t(RttiDefaultValue::Mask)); + if (RttiUtil::isDefault(defaultValue, field.m_type, base + field.m_offset)) + { + // If it's a default, we don't bother writing it + continue; + } + } + + JSONKeyValue pair; + pair.key = m_container->getKey(UnownedStringSlice(field.m_name)); + auto res = convert(field.m_type, base + field.m_offset, pair.value); + + if (SLANG_FAILED(res)) + { + m_sink->diagnose(SourceLoc(), JSONDiagnostics::unableToConvertField, field.m_name, structRttiInfo->m_name); + return res; + } + + outPairs.add(pair); + } + + return SLANG_OK; +} + + +SlangResult NativeToJSONConverter::convert(const RttiInfo* rttiInfo, const void* in, JSONValue& out) +{ + if (rttiInfo->isIntegral()) + { + out = JSONValue::makeInt(RttiUtil::getInt64(rttiInfo, in)); + return SLANG_OK; + } + else if (rttiInfo->isFloat()) + { + out = JSONValue::makeFloat(RttiUtil::asDouble(rttiInfo, in)); + return SLANG_OK; + } + + switch (rttiInfo->m_kind) + { + case RttiInfo::Kind::Invalid: return SLANG_FAIL; + case RttiInfo::Kind::Bool: + { + out = JSONValue::makeBool(RttiUtil::asBool(rttiInfo, in)); + return SLANG_OK; + } + case RttiInfo::Kind::String: + { + const String& str = *(const String*)in; + out = m_container->createString(str.getUnownedSlice()); + return SLANG_OK; + } + case RttiInfo::Kind::UnownedStringSlice: + { + const UnownedStringSlice& slice = *(const UnownedStringSlice*)in; + out = m_container->createString(slice); + return SLANG_OK; + } + case RttiInfo::Kind::Struct: + { + const StructRttiInfo* structRttiInfo = static_cast<const StructRttiInfo*>(rttiInfo); + + List<JSONKeyValue> pairs; + SLANG_RETURN_ON_FAIL(_structToJSON(structRttiInfo, in, pairs)); + out = m_container->createObject(pairs.getBuffer(), pairs.getCount()); + return SLANG_OK; + } + case RttiInfo::Kind::Enum: + { + return SLANG_E_NOT_IMPLEMENTED; + } + case RttiInfo::Kind::List: + { + const ListRttiInfo* listRttiInfo = static_cast<const ListRttiInfo*>(rttiInfo); + const auto elementRttiInfo = listRttiInfo->m_elementType; + + // The src probably *doesn't* contain bytes, but can cast like this because + // we only need the count (which doesn't depend on <T>), and the backing buffer + const List<Byte>& srcValuesList = *(const List<Byte>*)in; + + const Index count = srcValuesList.getCount(); + const Byte* srcValues = srcValuesList.getBuffer(); + + List<JSONValue> dstValues; + dstValues.setCount(count); + + const size_t elementStride = elementRttiInfo->m_size; + + for (Index i = 0; i < count; ++i, srcValues += elementStride) + { + SLANG_RETURN_ON_FAIL(convert(elementRttiInfo, srcValues, dstValues[i])); + } + + out = m_container->createArray(dstValues.getBuffer(), count); + return SLANG_OK; + } + case RttiInfo::Kind::Dictionary: + { + const DictionaryRttiInfo* listRttiInfo = static_cast<const DictionaryRttiInfo*>(rttiInfo); + const auto keyRttiInfo = listRttiInfo->m_keyType; + const auto valueRttiInfo = listRttiInfo->m_valueType; + + SLANG_UNUSED(keyRttiInfo); + SLANG_UNUSED(valueRttiInfo); + + // We can *only* serialize this into a straight JSON object iff the key is a string-like type + // We could turn into (say) an array of keys and values + + break; + } + case RttiInfo::Kind::Other: + { + if (rttiInfo == GetRttiInfo<JSONValue>::get()) + { + // Do we need to copy into the container? + // As it stands we have to assume src is stored in container. + const JSONValue& src = *(const JSONValue*)in; + + out = src; + return SLANG_OK; + } + break; + } + default: break; + } + + return SLANG_E_NOT_IMPLEMENTED; +} + +} // namespace Slang diff --git a/source/compiler-core/slang-json-native.h b/source/compiler-core/slang-json-native.h new file mode 100644 index 000000000..66f20aafb --- /dev/null +++ b/source/compiler-core/slang-json-native.h @@ -0,0 +1,50 @@ +#ifndef SLANG_COMPILER_CORE_JSON_NATIVE_H +#define SLANG_COMPILER_CORE_JSON_NATIVE_H + +#include "../../slang.h" +#include "../../slang-com-helper.h" +#include "../../slang-com-ptr.h" + +#include "slang-json-value.h" + +namespace Slang { + +struct JSONToNativeConverter +{ + SlangResult convert(const JSONValue& value, const RttiInfo* rttiInfo, void* out); + + JSONToNativeConverter(JSONContainer* container, DiagnosticSink* sink): + m_container(container), + m_sink(sink) + {} + +protected: + static Index _getFieldCount(const StructRttiInfo* structRttiInfo); + static Index _findFieldIndex(const StructRttiInfo* structRttiInfo, const UnownedStringSlice& fieldName); + + SlangResult _structToNative(const ConstArrayView<JSONKeyValue>& pairs, const StructRttiInfo* structRttiInfo, void* out, Index& outFieldCount); + + DiagnosticSink* m_sink; + JSONContainer* m_container; +}; + +struct NativeToJSONConverter +{ + SlangResult convert(const RttiInfo* rttiInfo, const void* in, JSONValue& out); + + NativeToJSONConverter(JSONContainer* container, DiagnosticSink* sink) : + m_container(container), + m_sink(sink) + {} + +protected: + SlangResult _structToJSON(const StructRttiInfo* structRttiInfo, const void* src, List<JSONKeyValue>& outPairs); + + DiagnosticSink* m_sink; + JSONContainer* m_container; +}; + + +} // namespace Slang + +#endif // SLANG_COMPILER_CORE_JSON_NATIVE_H diff --git a/source/compiler-core/slang-json-parser.cpp b/source/compiler-core/slang-json-parser.cpp index a38afc418..fe9a0f580 100644 --- a/source/compiler-core/slang-json-parser.cpp +++ b/source/compiler-core/slang-json-parser.cpp @@ -32,7 +32,7 @@ SlangResult JSONParser::_parseObject() { JSONToken keyToken; SLANG_RETURN_ON_FAIL(m_lexer->expect(JSONTokenType::StringLiteral, keyToken)); - m_listener->addKey(m_lexer->getLexeme(keyToken), keyToken.loc); + m_listener->addQuotedKey(m_lexer->getLexeme(keyToken), keyToken.loc); SLANG_RETURN_ON_FAIL(m_lexer->expect(JSONTokenType::Colon)); @@ -345,18 +345,39 @@ void JSONWriter::endArray(SourceLoc loc) m_stack.removeLast(); } -void JSONWriter::addKey(const UnownedStringSlice& key, SourceLoc loc) +void JSONWriter::addUnquotedKey(const UnownedStringSlice& key, SourceLoc loc) { SLANG_UNUSED(loc); SLANG_ASSERT(m_state.m_kind == State::Kind::Object && (m_state.m_flags & State::Flag::HasKey) == 0); _maybeEmitFieldComma(); + _maybeEmitIndent(); + + // Output the key quoted + StringEscapeHandler* handler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::JSON); + StringEscapeUtil::appendQuoted(handler, key, m_builder); + + m_builder << " : "; + + m_state.m_flags |= State::Flag::HasKey; + // We don't want it to emit a , after the : + m_state.m_flags &= ~State::Flag::HasPrevious; +} + +void JSONWriter::addQuotedKey(const UnownedStringSlice& key, SourceLoc loc) +{ + SLANG_UNUSED(loc); + SLANG_ASSERT(m_state.m_kind == State::Kind::Object && (m_state.m_flags & State::Flag::HasKey) == 0); // It should be quoted SLANG_ASSERT(key.getLength() >= 2 && key[0] == '"' && key[key.getLength() - 1] == '"'); + _maybeEmitFieldComma(); _maybeEmitIndent(); - m_builder << key << " : "; + + m_builder << key; + + m_builder << " : "; m_state.m_flags |= State::Flag::HasKey; // We don't want it to emit a , after the : diff --git a/source/compiler-core/slang-json-parser.h b/source/compiler-core/slang-json-parser.h index 96531aee9..2391ea0d2 100644 --- a/source/compiler-core/slang-json-parser.h +++ b/source/compiler-core/slang-json-parser.h @@ -21,7 +21,8 @@ public: /// Add the key. Must be followed by addXXXValue. - virtual void addKey(const UnownedStringSlice& key, SourceLoc loc) = 0; + virtual void addQuotedKey(const UnownedStringSlice& key, SourceLoc loc) = 0; + virtual void addUnquotedKey(const UnownedStringSlice& key, SourceLoc loc) = 0; /// Can be performed in an array or after an addLexemeKey in an object virtual void addLexemeValue(JSONTokenType type, const UnownedStringSlice& value, SourceLoc loc) = 0; @@ -93,7 +94,8 @@ public: virtual void endObject(SourceLoc loc) SLANG_OVERRIDE; virtual void startArray(SourceLoc loc) SLANG_OVERRIDE; virtual void endArray(SourceLoc loc) SLANG_OVERRIDE; - virtual void addKey(const UnownedStringSlice& key, SourceLoc loc) SLANG_OVERRIDE; + virtual void addQuotedKey(const UnownedStringSlice& key, SourceLoc loc) SLANG_OVERRIDE; + virtual void addUnquotedKey(const UnownedStringSlice& key, SourceLoc loc) SLANG_OVERRIDE; virtual void addLexemeValue(JSONTokenType type, const UnownedStringSlice& value, SourceLoc loc) SLANG_OVERRIDE; virtual void addIntegerValue(int64_t value, SourceLoc loc) SLANG_OVERRIDE; virtual void addFloatValue(double value, SourceLoc loc) SLANG_OVERRIDE; diff --git a/source/compiler-core/slang-json-rpc.cpp b/source/compiler-core/slang-json-rpc.cpp index 15da92ff0..1ca55e9eb 100644 --- a/source/compiler-core/slang-json-rpc.cpp +++ b/source/compiler-core/slang-json-rpc.cpp @@ -1,5 +1,7 @@ #include "slang-json-rpc.h" +#include "../../slang-com-helper.h" + namespace Slang { // https://www.jsonrpc.org/specification @@ -7,6 +9,406 @@ namespace Slang { // m_sourceManager.initialize(nullptr, nullptr); // m_diagnosticSink.init(&m_sourceManager, &JSONLexer::calcLexemeLocation); +static const auto g_jsonRpc = UnownedStringSlice::fromLiteral("jsonrpc"); +static const auto g_jsonRpcVersion = UnownedStringSlice::fromLiteral("2.0"); +static const auto g_method = UnownedStringSlice::fromLiteral("method"); +static const auto g_id = UnownedStringSlice::fromLiteral("id"); +static const auto g_params = UnownedStringSlice::fromLiteral("params"); +static const auto g_code = UnownedStringSlice::fromLiteral("code"); +static const auto g_error = UnownedStringSlice::fromLiteral("error"); +static const auto g_message = UnownedStringSlice::fromLiteral("message"); +static const auto g_result = UnownedStringSlice::fromLiteral("result"); +static const auto g_data = UnownedStringSlice::fromLiteral("data"); + +// Add the fields. +// TODO(JS): This is a little verbose, and could be improved on with something like +// * Tool that automatically generated from C++ (say via the C++ extractor) +// * Macro magic to simplify the construction +static const StructRttiInfo _makeJSONRPCErrorResponse_ErrorRtti() +{ + JSONRPCErrorResponse::Error obj; + StructRttiBuilder builder(&obj, "JSONRPCErrorResponse::Error", nullptr); + builder.addField("code", &obj.code); + builder.addField("message", &obj.message); + return builder.make(); +} +/* static */const StructRttiInfo JSONRPCErrorResponse::Error::g_rttiInfo = _makeJSONRPCErrorResponse_ErrorRtti(); + +static const StructRttiInfo _makeJSONRPCErrorResponseRtti() +{ + JSONRPCErrorResponse obj; + StructRttiBuilder builder(&obj, "JSONRPCErrorResponse", nullptr); + + builder.addField("error", &obj.error); + builder.addField("data", &obj.data, StructRttiInfo::Flag::Optional); + builder.addField("id", &obj.id, combine(StructRttiInfo::Flag::Optional, RttiDefaultValue::MinusOne)); + + return builder.make(); +} +/* static */const StructRttiInfo JSONRPCErrorResponse::g_rttiInfo = _makeJSONRPCErrorResponseRtti(); + +static const StructRttiInfo _makeJSONRPCCallResponseRtti() +{ + JSONRPCCall obj; + StructRttiBuilder builder(&obj, "JSONRPCCall", nullptr); + + builder.addField("method", &obj.method); + builder.addField("params", &obj.params, StructRttiInfo::Flag::Optional); + builder.addField("id", &obj.id, combine(StructRttiInfo::Flag::Optional, RttiDefaultValue::MinusOne)); + + return builder.make(); +} +/* static */const StructRttiInfo JSONRPCCall::g_rttiInfo = _makeJSONRPCCallResponseRtti(); + +static const StructRttiInfo _makeJSONResultResponseResponseRtti() +{ + JSONResultResponse obj; + StructRttiBuilder builder(&obj, "JSONResultResponse", nullptr); + + builder.addField("result", &obj.result); + builder.addField("id", &obj.id, combine(StructRttiInfo::Flag::Optional, RttiDefaultValue::MinusOne)); + + return builder.make(); +} +/* static */const StructRttiInfo JSONResultResponse::g_rttiInfo = _makeJSONResultResponseResponseRtti(); + + +/* static */JSONValue JSONRPCUtil::createCall(JSONContainer* container, const UnownedStringSlice& method, JSONValue params, Int id) +{ + const Index maxPairs = 4; + JSONKeyValue pairs[maxPairs]; + + Index i = 0; + + // Version number is a string + pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion)); + pairs[i++] = JSONKeyValue::make(container->getKey(g_method), container->createString(method)); + pairs[i++] = JSONKeyValue::make(container->getKey(g_params), params); + + if (id >= 0) + { + pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id)); + } + + return container->createObject(pairs, i); +} + +/* static */JSONValue JSONRPCUtil::createCall(JSONContainer* container, const UnownedStringSlice& method, Int id) +{ + const Index maxPairs = 3; + JSONKeyValue pairs[maxPairs]; + Index i = 0; + // Version number is a string + pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion)); + pairs[i++] = JSONKeyValue::make(container->getKey(g_method), container->createString(method)); + + if (id >= 0) + { + pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id)); + } + + return container->createObject(pairs, i); +} + +/* static */JSONValue JSONRPCUtil::createErrorResponse(JSONContainer* container, Index code, const UnownedStringSlice& message, const JSONValue& data, Int id) +{ + // Set up the error value + JSONValue errorValue; + { + const Index maxPairs = 2; + JSONKeyValue pairs[maxPairs]; + Index i = 0; + + if (code != 0) + { + pairs[i++] = JSONKeyValue::make(container->getKey(g_code), JSONValue::makeInt(code)); + } + if (message.getLength() > 0) + { + pairs[i++] = JSONKeyValue::make(container->getKey(g_message), container->createString(message)); + } + errorValue = container->createObject(pairs, i); + } + + const Index maxPairs = 4; + JSONKeyValue pairs[maxPairs]; + Index i = 0; + + pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion)); + pairs[i++] = JSONKeyValue::make(container->getKey(g_error), errorValue); + + if (data.isValid()) + { + pairs[i++] = JSONKeyValue::make(container->getKey(g_data), data); + } + + if (id >= 0) + { + pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id)); + } + + return container->createObject(pairs, i); +} + + +/* static */JSONValue JSONRPCUtil::createErrorResponse(JSONContainer* container, ErrorCode code, const UnownedStringSlice& message, const JSONValue& data, Int id) +{ + return createErrorResponse(container, Index(code), message, data, id); +} + +/* static */JSONValue JSONRPCUtil::createResultResponse(JSONContainer* container, const JSONValue& resultValue, Int id) +{ + const Index maxPairs = 3; + JSONKeyValue pairs[maxPairs]; + Index i = 0; + + pairs[i++] = JSONKeyValue::make(container->getKey(g_jsonRpc), container->createString(g_jsonRpcVersion)); + pairs[i++] = JSONKeyValue::make(container->getKey(g_result), resultValue); + + if (id >= 0) + { + pairs[i++] = JSONKeyValue::make(container->getKey(g_id), JSONValue::makeInt(id)); + } + + return container->createObject(pairs, i); +} + +/* static */ JSONRPCUtil::ResponseType JSONRPCUtil::getResponseType(JSONContainer* container, const JSONValue& response) +{ + if (response.getKind() == JSONValue::Kind::Object) + { + const JSONKey resultKey = container->findKey(g_result); + const JSONKey errorKey = container->findKey(g_error); + + auto pairs = container->getObject(response); + + for (const auto& pair : pairs) + { + if (pair.key == resultKey) + { + return ResponseType::Result; + } + else if (pair.key == errorKey) + { + return ResponseType::Error; + } + } + } + + return ResponseType::Error; +} + +static SlangResult _parseError(JSONContainer* container, const JSONValue& error, JSONRPCUtil::ErrorResponse& out) +{ + if (error.getKind() != JSONValue::Kind::Object) + { + return SLANG_FAIL; + } + const auto pairs = container->getObject(error); + + const JSONKey messageKey = container->findKey(g_message); + const JSONKey codeKey = container->findKey(g_code); + const JSONKey dataKey = container->findKey(g_data); + + Int fieldBits = 0; + + for (auto const& pair : pairs) + { + if (pair.key == messageKey) + { + if (pair.value.getKind() != JSONValue::Kind::String) + { + return SLANG_FAIL; + } + out.message = container->getString(pair.value); + fieldBits |= 0x1; + } + else if (pair.key == codeKey) + { + if (pair.value.getKind() != JSONValue::Kind::Integer) + { + return SLANG_FAIL; + } + out.code = Index(pair.value.asInteger()); + fieldBits |= 0x2; + } + else if (pair.key == dataKey) + { + out.data = pair.value; + fieldBits |= 0x4; + } + else + { + return SLANG_FAIL; + } + } + + // Check all required fields are set + return (fieldBits & 0x3) == 0x3 ? SLANG_OK : SLANG_FAIL; +} + +/* static */SlangResult JSONRPCUtil::parseError(JSONContainer* container, const JSONValue& response, ErrorResponse& out) +{ + if (response.getKind() != JSONValue::Kind::Object) + { + return SLANG_FAIL; + } + + const auto pairs = container->getObject(response); + + const JSONKey jsonRpcKey = container->findKey(g_jsonRpc); + const JSONKey errorKey = container->findKey(g_error); + const JSONKey idKey = container->findKey(g_id); + + Int fieldBits = 0; + + for (auto const& pair : pairs) + { + if (pair.key == jsonRpcKey) + { + if (!container->areEqual(pair.value, g_jsonRpcVersion)) + { + return SLANG_FAIL; + } + fieldBits |= 0x1; + } + else if (pair.key == errorKey) + { + // We need to decode the error + SLANG_RETURN_ON_FAIL(_parseError(container, pair.value, out)); + fieldBits |= 0x2; + } + else if (pair.key == idKey) + { + if (pair.value.getKind() != JSONValue::Kind::Integer) + { + return SLANG_FAIL; + } + out.id = Int(pair.value.asInteger()); + fieldBits |= 0x4; + } + else + { + // Unknown key + return SLANG_FAIL; + } + } + + // Check all the required bits are set + return ((fieldBits & 0x3) == 0x3) ? SLANG_OK : SLANG_FAIL; +} + +/* static */SlangResult JSONRPCUtil::parseResult(JSONContainer* container, const JSONValue& response, ResultResponse& out) +{ + if (response.getKind() != JSONValue::Kind::Object) + { + return SLANG_FAIL; + } + + const auto pairs = container->getObject(response); + + const JSONKey jsonRpcKey = container->findKey(g_jsonRpc); + const JSONKey resultKey = container->findKey(g_result); + const JSONKey idKey = container->findKey(g_id); + + Int fieldBits = 0; + + for (auto const& pair : pairs) + { + if (pair.key == jsonRpcKey) + { + if (!container->areEqual(pair.value, g_jsonRpcVersion)) + { + return SLANG_FAIL; + } + fieldBits |= 0x1; + } + else if (pair.key == resultKey) + { + out.result = pair.value; + fieldBits |= 0x2; + } + else if (pair.key == idKey) + { + if (pair.value.getKind() != JSONValue::Kind::Integer) + { + return SLANG_FAIL; + } + out.id = Int(pair.value.asInteger()); + fieldBits |= 0x4; + } + else + { + // Unknown key + return SLANG_FAIL; + } + } + + // Check all the required bits are set + return ((fieldBits & 0x3) == 0x3) ? SLANG_OK : SLANG_FAIL; +} + +/* static */SlangResult JSONRPCUtil::parseCall(JSONContainer* container, const JSONValue& value, Call& out) +{ + if (value.getKind() != JSONValue::Kind::Object) + { + return SLANG_FAIL; + } + + const auto pairs = container->getObject(value); + + const JSONKey jsonRpcKey = container->findKey(g_jsonRpc); + const JSONKey methodKey = container->findKey(g_method); + const JSONKey paramsKey = container->findKey(g_params); + const JSONKey idKey = container->findKey(g_id); + + Int fieldBits = 0; + + for (auto const& pair : pairs) + { + if (pair.key == jsonRpcKey) + { + if (!container->areEqual(pair.value, g_jsonRpcVersion)) + { + return SLANG_FAIL; + } + fieldBits |= 0x1; + } + else if (pair.key == methodKey) + { + if (pair.value.getKind() != JSONValue::Kind::String) + { + return SLANG_FAIL; + } + out.method = container->getString(pair.value); + fieldBits |= 0x2; + } + else if (pair.key == idKey) + { + if (pair.value.getKind() != JSONValue::Kind::Integer) + { + return SLANG_FAIL; + } + out.id = Int(pair.value.asInteger()); + fieldBits |= 0x4; + } + else if (pair.key == paramsKey) + { + out.params = pair.value; + fieldBits |= 0x8; + } + else + { + // Unknown key + return SLANG_FAIL; + } + } + + // Check all the required bits are set + return ((fieldBits & 0x3) == 0x3) ? SLANG_OK : SLANG_FAIL; +} + + /* static */SlangResult JSONRPCUtil::parseJSON(const UnownedStringSlice& slice, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue) { SourceManager* sourceManager = sink->getSourceManager(); diff --git a/source/compiler-core/slang-json-rpc.h b/source/compiler-core/slang-json-rpc.h index 3ed3e5fee..e85664ceb 100644 --- a/source/compiler-core/slang-json-rpc.h +++ b/source/compiler-core/slang-json-rpc.h @@ -12,11 +12,110 @@ namespace Slang { +struct JSONRPCErrorResponse +{ + struct Error + { + Index code = 0; ///< Value from ErrorCode + UnownedStringSlice message; ///< Error message + + static const StructRttiInfo g_rttiInfo; + }; + + Error error; + JSONValue data; + Int id = -1; ///< Id of initiating method or -1 if not set + + static const StructRttiInfo g_rttiInfo; +}; + +struct JSONRPCCall +{ + UnownedStringSlice method; ///< The name of the method + JSONValue params; ///< Can be invalid/array/object + Int id = -1; ///< Id associated with this request, or -1 if not set + + static const StructRttiInfo g_rttiInfo; +}; + +struct JSONResultResponse +{ + JSONValue result; ///< The result value + Int id = -1; ///< Id of initiating method or -1 if not set + + static const StructRttiInfo g_rttiInfo; +}; + /// Send and receive messages as JSON +/// +/// Strictly speaking should support ids, as strings or ids. Currently just supports with integer ids. +/// One way of dealing with this would be to just use JSONValue for ids, would allow invalid/string/integer and +/// a mechanism to compare/display etc. class JSONRPCUtil { public: - + + enum class ErrorCode + { + ParseError = -32700, ///< Invalid JSON was received by the server. + InvalidRequest = -32600, ///< The JSON sent is not a valid Request object. + MethodNotFound = -32601, ///< The method does not exist / is not available. + InvalidParams = -32602, ///< Invalid method parameter(s). + InternalError = -32603, ///< Internal JSON - RPC error. + + ServerImplStart = -32000, ///< Server implementation defined error range + ServerImplEnd = -32099, + }; + + enum class ResponseType + { + Invalid, + Error, + Result + }; + + struct ErrorResponse + { + Index code = 0; ///< Value from ErrorCode + UnownedStringSlice message; ///< Error message + JSONValue data; + Int id = -1; ///< Id of initiating method or -1 if not set + }; + + struct ResultResponse + { + JSONValue result; ///< The result value + Int id = -1; ///< Id of initiating method or -1 if not set + }; + + struct Call + { + UnownedStringSlice method; ///< The name of the method + JSONValue params; ///< Can be invalid/array/object + Int id = -1; ///< Id associated with this request, or -1 if not set + }; + + /// Parameters can be either named or via index. + static JSONValue createCall(JSONContainer* container, const UnownedStringSlice& method, JSONValue params, Int id = -1); + /// Parameters can be either named or via index. + static JSONValue createCall(JSONContainer* container, const UnownedStringSlice& method, Int id = -1); + + /// Create an error response + /// Code should typically be something in the ErrorCode range + static JSONValue createErrorResponse(JSONContainer* container, Index code, const UnownedStringSlice& message, const JSONValue& data = JSONValue(), Int id = -1); + static JSONValue createErrorResponse(JSONContainer* container, ErrorCode code, const UnownedStringSlice& message, const JSONValue& data = JSONValue(), Int id = -1); + /// Create a result response + static JSONValue createResultResponse(JSONContainer* container, const JSONValue& resultValue, Int id = -1); + + /// Determine the response type + static ResponseType getResponseType(JSONContainer* container, const JSONValue& response); + + static SlangResult parseError(JSONContainer* container, const JSONValue& response, ErrorResponse& out); + + static SlangResult parseResult(JSONContainer* container, const JSONValue& response, ResultResponse& out); + + static SlangResult parseCall(JSONContainer* container, const JSONValue& value, Call& out); + /// Parse slice into JSONContainer. outValue is the root of the hierarchy. static SlangResult parseJSON(const UnownedStringSlice& slice, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue); diff --git a/source/compiler-core/slang-json-value.cpp b/source/compiler-core/slang-json-value.cpp index 9a2bb37f4..1e18bee39 100644 --- a/source/compiler-core/slang-json-value.cpp +++ b/source/compiler-core/slang-json-value.cpp @@ -26,6 +26,24 @@ namespace Slang { JSONValue::Kind::Object, // Object, }; +static bool _isDefault(const RttiInfo* type, const void* in) +{ + SLANG_UNUSED(type) + const JSONValue& value = *(const JSONValue*)in; + return value.getKind() == JSONValue::Kind::Invalid; +} + +static OtherRttiInfo _getJSONValueRttiInfo() +{ + OtherRttiInfo info; + info.init<JSONValue>(RttiInfo::Kind::Other); + info.m_name = "JSONValue"; + info.m_isDefaultFunc = _isDefault; + info.m_typeFuncs = GetRttiTypeFuncs<JSONValue>::getFuncs(); + return info; +} +/* static */const OtherRttiInfo JSONValue::g_rttiInfo = _getJSONValueRttiInfo(); + static JSONKeyValue _makeInvalidKeyValue() { JSONKeyValue keyValue; @@ -204,7 +222,7 @@ JSONValue JSONContainer::createArray(const JSONValue* values, Index valuesCount, JSONValue value; value.type = JSONValue::Type::Array; value.loc = loc; - value.rangeIndex = _addRange(Range::Type::Array, m_objectValues.getCount(), valuesCount); + value.rangeIndex = _addRange(Range::Type::Array, m_arrayValues.getCount(), valuesCount); m_arrayValues.addRange(values, valuesCount); return value; @@ -240,6 +258,12 @@ JSONKey JSONContainer::getKey(const UnownedStringSlice& slice) return JSONKey(m_slicePool.add(slice)); } +JSONKey JSONContainer::findKey(const UnownedStringSlice& slice) const +{ + const Index index = m_slicePool.findIndex(slice); + return (index < 0) ? JSONKey(0) : JSONKey(index); +} + ConstArrayView<JSONValue> JSONContainer::getArray(const JSONValue& in) const { SLANG_ASSERT(in.type == JSONValue::Type::Array); @@ -271,6 +295,8 @@ ArrayView<JSONValue> JSONContainer::getArray(const JSONValue& in) return ArrayView<JSONValue>((JSONValue*)nullptr, 0); } const Range& range = m_ranges[in.rangeIndex]; + SLANG_ASSERT(range.startIndex <= m_arrayValues.getCount() && range.startIndex + range.count <= m_arrayValues.getCount()); + return ArrayView<JSONValue>(m_arrayValues.getBuffer() + range.startIndex, range.count); } @@ -311,9 +337,29 @@ UnownedStringSlice JSONContainer::getLexeme(const JSONValue& in) UnownedStringSlice JSONContainer::getString(const JSONValue& in) { + if (in.type == JSONValue::Type::StringValue) + { + return getStringFromKey(in.stringKey); + } + else if (in.type == JSONValue::Type::StringLexeme) + { + auto slice = getTransientString(in); + auto handle = m_slicePool.add(slice); + return m_slicePool.getSlice(handle); + } + + SLANG_ASSERT(!"Not a string type"); + return UnownedStringSlice(); +} + +UnownedStringSlice JSONContainer::getTransientString(const JSONValue& in) +{ switch (in.type) { - case JSONValue::Type::StringValue: return getStringFromKey(in.stringKey); + case JSONValue::Type::StringValue: + { + return getStringFromKey(in.stringKey); + } case JSONValue::Type::StringLexeme: { StringEscapeHandler* handler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::JSON); @@ -340,7 +386,7 @@ UnownedStringSlice JSONContainer::getString(const JSONValue& in) JSONKey JSONContainer::getStringKey(const JSONValue& in) { - return (in.type == JSONValue::Type::StringValue) ? in.stringKey : getKey(getString(in)); + return (in.type == JSONValue::Type::StringValue) ? in.stringKey : getKey(getTransientString(in)); } bool JSONContainer::asBool(const JSONValue& value) @@ -393,6 +439,19 @@ double JSONContainer::asFloat(const JSONValue& value) } } +Index JSONContainer::findObjectIndex(const JSONValue& obj, JSONKey key) const +{ + auto pairs = getObject(obj); + return pairs.findFirstIndex([key](const JSONKeyValue& pair) -> bool { return pair.key == key; }); +} + +JSONValue JSONContainer::findObjectValue(const JSONValue& obj, JSONKey key) const +{ + auto pairs = getObject(obj); + const Index index = pairs.findFirstIndex([key](const JSONKeyValue& pair) -> bool { return pair.key == key; }); + return (index >= 0) ? pairs[index].value : JSONValue::makeInvalid(); +} + JSONValue& JSONContainer::getAt(const JSONValue& array, Index index) { SLANG_ASSERT(array.type == JSONValue::Type::Array); @@ -742,6 +801,11 @@ bool JSONContainer::areEqual(const JSONKeyValue* a, const JSONKeyValue* b, Index } } +bool JSONContainer::areEqual(const JSONValue& a, const UnownedStringSlice& slice) +{ + return a.getKind() == JSONValue::Kind::String && getTransientString(a) == slice; +} + bool JSONContainer::areEqual(const JSONValue& a, const JSONValue& b) { if (&a == &b) @@ -864,7 +928,7 @@ void JSONContainer::traverseRecursively(const JSONValue& value, JSONListener* li { // Emit the key const auto keyString = getStringFromKey(objKeyValue.key); - listener->addKey(keyString, objKeyValue.keyLoc); + listener->addUnquotedKey(keyString, objKeyValue.keyLoc); // Emit the value associated with the key traverseRecursively(objKeyValue.value, listener); @@ -1035,7 +1099,16 @@ void JSONBuilder::endArray(SourceLoc loc) _add(value); } -void JSONBuilder::addKey(const UnownedStringSlice& key, SourceLoc loc) +void JSONBuilder::addQuotedKey(const UnownedStringSlice& key, SourceLoc loc) +{ + // We need to decode + m_work.Clear(); + StringEscapeHandler* handler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::JSON); + StringEscapeUtil::appendUnquoted(handler, key, m_work); + addUnquotedKey(m_work.getUnownedSlice(), loc); +} + +void JSONBuilder::addUnquotedKey(const UnownedStringSlice& key, SourceLoc loc) { SLANG_ASSERT(m_keyValue.key == JSONKey(0)); m_keyValue.key = m_container->getKey(key); diff --git a/source/compiler-core/slang-json-value.h b/source/compiler-core/slang-json-value.h index d008d5f18..2eae4ae08 100644 --- a/source/compiler-core/slang-json-value.h +++ b/source/compiler-core/slang-json-value.h @@ -9,6 +9,8 @@ #include "slang-json-parser.h" +#include "../core/slang-rtti-info.h" + namespace Slang { typedef uint32_t JSONKey; @@ -111,8 +113,13 @@ struct JSONValue }; static const Kind g_typeToKind[Index(Type::CountOf)]; + + static const OtherRttiInfo g_rttiInfo; }; +template <> +struct GetRttiInfo<JSONValue> { static const RttiInfo* get() { return &JSONValue::g_rttiInfo; } }; + struct JSONKeyValue { /// True if it's valid @@ -129,6 +136,11 @@ struct JSONKeyValue SourceLoc keyLoc; JSONValue value; + static JSONKeyValue make(JSONKey inKey, JSONValue inValue, SourceLoc inKeyLoc = SourceLoc()) + { + return JSONKeyValue{ inKey, inKeyLoc, inValue }; + } + static JSONKeyValue g_invalid; }; @@ -155,6 +167,11 @@ public: /// Get the value at the index in the array JSONValue& getAt(const JSONValue& array, Index index); + /// Returns the index of key in obj, or -1 if not found + Index findObjectIndex(const JSONValue& obj, JSONKey key) const; + /// Get the value in the object at key. REturns invalid if not found. + JSONValue findObjectValue(const JSONValue& obj, JSONKey key) const; + /// Returns the index Index findKeyGlobalIndex(const JSONValue& obj, JSONKey key); Index findKeyGlobalIndex(const JSONValue& obj, const UnownedStringSlice& slice); @@ -176,14 +193,20 @@ public: /// Returns string as a key JSONKey getStringKey(const JSONValue& in); - /// Get as a string. + /// Get as a string. The slice may used backing lexeme (ie will only last + /// as long as the backing JSON text, or be decoded and be transitory). + UnownedStringSlice getTransientString(const JSONValue& in); + + /// Get as a string. The contents will stay in scope as long as the container UnownedStringSlice getString(const JSONValue& in); - /// Gets the lexeme + /// Gets the lexeme UnownedStringSlice getLexeme(const JSONValue& in); /// Get a key for a name JSONKey getKey(const UnownedStringSlice& slice); + /// Returns JSONKey(0) if not found + JSONKey findKey(const UnownedStringSlice& slice) const; /// Get the string from the key UnownedStringSlice getStringFromKey(JSONKey key) const { return m_slicePool.getSlice(StringSlicePool::Handle(key)); } @@ -194,6 +217,8 @@ public: bool areEqual(const JSONValue* a, const JSONValue* b, Index count); bool areEqual(const JSONKeyValue* a, const JSONKeyValue* b, Index count); + bool areEqual(const JSONValue& a, const UnownedStringSlice& slice); + /// Destroy value void destroy(JSONValue& value); /// Destroy recursively from value @@ -282,7 +307,8 @@ public: virtual void endObject(SourceLoc loc) SLANG_OVERRIDE; virtual void startArray(SourceLoc loc) SLANG_OVERRIDE; virtual void endArray(SourceLoc loc) SLANG_OVERRIDE; - virtual void addKey(const UnownedStringSlice& key, SourceLoc loc) SLANG_OVERRIDE; + virtual void addQuotedKey(const UnownedStringSlice& key, SourceLoc loc) SLANG_OVERRIDE; + virtual void addUnquotedKey(const UnownedStringSlice& key, SourceLoc loc) SLANG_OVERRIDE; virtual void addLexemeValue(JSONTokenType type, const UnownedStringSlice& value, SourceLoc loc) SLANG_OVERRIDE; virtual void addIntegerValue(int64_t value, SourceLoc loc) SLANG_OVERRIDE; virtual void addFloatValue(double value, SourceLoc loc) SLANG_OVERRIDE; @@ -330,6 +356,8 @@ protected: JSONKeyValue m_keyValue; JSONValue m_rootValue; + + StringBuilder m_work; }; } // namespace Slang |
