summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build/visual-studio/compiler-core/compiler-core.vcxproj4
-rw-r--r--build/visual-studio/compiler-core/compiler-core.vcxproj.filters12
-rw-r--r--source/compiler-core/slang-json-diagnostic-defs.h7
-rw-r--r--source/compiler-core/slang-json-native.h5
-rw-r--r--source/compiler-core/slang-json-rpc-connection.cpp286
-rw-r--r--source/compiler-core/slang-json-rpc-connection.h161
-rw-r--r--source/compiler-core/slang-json-rpc.cpp404
-rw-r--r--source/compiler-core/slang-json-rpc.h151
-rw-r--r--source/compiler-core/slang-json-value.cpp2
-rw-r--r--source/compiler-core/slang-json-value.h3
-rw-r--r--source/compiler-core/slang-test-server-protocol.cpp43
-rw-r--r--source/compiler-core/slang-test-server-protocol.h51
-rw-r--r--source/core/slang-http.cpp24
-rw-r--r--source/core/slang-http.h5
-rw-r--r--source/core/slang-writer.h1
-rw-r--r--tools/slang-test/options.cpp5
-rw-r--r--tools/slang-test/options.h1
-rw-r--r--tools/slang-test/slang-test-main.cpp98
-rw-r--r--tools/slang-test/test-context.cpp47
-rw-r--r--tools/slang-test/test-context.h13
-rw-r--r--tools/test-server/test-server-diagnostic-defs.h1
-rw-r--r--tools/test-server/test-server-main.cpp337
22 files changed, 1101 insertions, 560 deletions
diff --git a/build/visual-studio/compiler-core/compiler-core.vcxproj b/build/visual-studio/compiler-core/compiler-core.vcxproj
index 5e70d59f7..4a35f982a 100644
--- a/build/visual-studio/compiler-core/compiler-core.vcxproj
+++ b/build/visual-studio/compiler-core/compiler-core.vcxproj
@@ -288,6 +288,7 @@
<ClInclude Include="..\..\..\source\compiler-core\slang-json-lexer.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-json-native.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-json-parser.h" />
+ <ClInclude Include="..\..\..\source\compiler-core\slang-json-rpc-connection.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-json-rpc.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-json-value.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-lexer-diagnostic-defs.h" />
@@ -298,6 +299,7 @@
<ClInclude Include="..\..\..\source\compiler-core\slang-name.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-nvrtc-compiler.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-source-loc.h" />
+ <ClInclude Include="..\..\..\source\compiler-core\slang-test-server-protocol.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-token-defs.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-token.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-visual-studio-compiler-util.h" />
@@ -317,6 +319,7 @@
<ClCompile Include="..\..\..\source\compiler-core\slang-json-lexer.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-json-native.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-json-parser.cpp" />
+ <ClCompile Include="..\..\..\source\compiler-core\slang-json-rpc-connection.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-json-rpc.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-json-value.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-lexer.cpp" />
@@ -325,6 +328,7 @@
<ClCompile Include="..\..\..\source\compiler-core\slang-name.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-nvrtc-compiler.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-source-loc.cpp" />
+ <ClCompile Include="..\..\..\source\compiler-core\slang-test-server-protocol.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-token.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-visual-studio-compiler-util.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\windows\slang-win-visual-studio-util.cpp" />
diff --git a/build/visual-studio/compiler-core/compiler-core.vcxproj.filters b/build/visual-studio/compiler-core/compiler-core.vcxproj.filters
index c392612f6..0bb53f1b8 100644
--- a/build/visual-studio/compiler-core/compiler-core.vcxproj.filters
+++ b/build/visual-studio/compiler-core/compiler-core.vcxproj.filters
@@ -51,6 +51,9 @@
<ClInclude Include="..\..\..\source\compiler-core\slang-json-parser.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\compiler-core\slang-json-rpc-connection.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\compiler-core\slang-json-rpc.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -81,6 +84,9 @@
<ClInclude Include="..\..\..\source\compiler-core\slang-source-loc.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\compiler-core\slang-test-server-protocol.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\compiler-core\slang-token-defs.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -134,6 +140,9 @@
<ClCompile Include="..\..\..\source\compiler-core\slang-json-parser.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\source\compiler-core\slang-json-rpc-connection.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\source\compiler-core\slang-json-rpc.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -158,6 +167,9 @@
<ClCompile Include="..\..\..\source\compiler-core\slang-source-loc.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\source\compiler-core\slang-test-server-protocol.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\source\compiler-core\slang-token.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/source/compiler-core/slang-json-diagnostic-defs.h b/source/compiler-core/slang-json-diagnostic-defs.h
index 4733a3291..576f14beb 100644
--- a/source/compiler-core/slang-json-diagnostic-defs.h
+++ b/source/compiler-core/slang-json-diagnostic-defs.h
@@ -42,4 +42,11 @@ DIAGNOSTIC(20011, Error, fieldNotDefinedOnType, "field '$0' not defined on type
DIAGNOSTIC(20011, Error, fieldRequiredOnType, "field '$0' required on '$1'")
DIAGNOSTIC(20012, Error, tooManyElementsForArray, "too many elements ($0) for array array. Max allowed is $1")
+//
+// 3xxxx JSON-RPC
+//
+
+DIAGNOSTIC(30000, Error, argsAreInvalid, "Args for '%0' are invalid")
+
+
#undef DIAGNOSTIC
diff --git a/source/compiler-core/slang-json-native.h b/source/compiler-core/slang-json-native.h
index 66f20aafb..f9ec93119 100644
--- a/source/compiler-core/slang-json-native.h
+++ b/source/compiler-core/slang-json-native.h
@@ -12,6 +12,8 @@ namespace Slang {
struct JSONToNativeConverter
{
SlangResult convert(const JSONValue& value, const RttiInfo* rttiInfo, void* out);
+ template <typename T>
+ SlangResult convert(const JSONValue& value, T* in) { return convert(value, GetRttiInfo<T>::get(), (void*)in); }
JSONToNativeConverter(JSONContainer* container, DiagnosticSink* sink):
m_container(container),
@@ -32,6 +34,9 @@ struct NativeToJSONConverter
{
SlangResult convert(const RttiInfo* rttiInfo, const void* in, JSONValue& out);
+ template <typename T>
+ SlangResult convert(T* in, JSONValue& out) { return convert(GetRttiInfo<T>::get(), (const void*)in, out); }
+
NativeToJSONConverter(JSONContainer* container, DiagnosticSink* sink) :
m_container(container),
m_sink(sink)
diff --git a/source/compiler-core/slang-json-rpc-connection.cpp b/source/compiler-core/slang-json-rpc-connection.cpp
new file mode 100644
index 000000000..20283070b
--- /dev/null
+++ b/source/compiler-core/slang-json-rpc-connection.cpp
@@ -0,0 +1,286 @@
+// slang-json-rpc-connection.cpp
+#include "slang-json-rpc-connection.h"
+
+#include "../core/slang-string-util.h"
+#include "../core/slang-process-util.h"
+
+#include "slang-json-rpc.h"
+#include "slang-json-native.h"
+
+namespace Slang {
+
+SlangResult JSONRPCConnection::init(HTTPPacketConnection* connection, Process* process)
+{
+ m_connection = connection;
+ m_process = process;
+
+ m_sourceManager.initialize(nullptr, nullptr);
+ m_diagnosticSink.init(&m_sourceManager, &JSONLexer::calcLexemeLocation);
+ m_container.setSourceManager(&m_sourceManager);
+
+ return SLANG_OK;
+}
+
+SlangResult JSONRPCConnection::initWithStdStreams(Process* process)
+{
+ RefPtr<Stream> stdinStream, stdoutStream;
+
+ Process::getStdStream(Process::StreamType::StdIn, stdinStream);
+ Process::getStdStream(Process::StreamType::StdOut, stdoutStream);
+
+ RefPtr<BufferedReadStream> readStream(new BufferedReadStream(stdinStream));
+
+ RefPtr<HTTPPacketConnection> connection = new HTTPPacketConnection(readStream, stdoutStream);
+ return init(connection, process);
+}
+
+void JSONRPCConnection::clearBuffers()
+{
+ m_sourceManager.reset();
+ m_diagnosticSink.reset();
+ m_container.reset();
+ m_jsonRoot.reset();
+}
+
+bool JSONRPCConnection::isActive()
+{
+ return m_connection->isActive() && (m_process == nullptr || !m_process->isTerminated());
+}
+
+JSONValue JSONRPCConnection::getMessageId()
+{
+ SLANG_ASSERT(hasMessage());
+ return JSONRPCUtil::getId(&m_container, m_jsonRoot);
+}
+
+void JSONRPCConnection::disconnect()
+{
+ if (m_process)
+ {
+ if (!m_process->isTerminated())
+ {
+ if (m_connection)
+ {
+ // Send. If succeeded, wait
+ if (SLANG_SUCCEEDED(sendCall(UnownedStringSlice::fromLiteral("quit"))))
+ {
+ // Wait for termination
+ m_process->waitForTermination(m_terminationTimeOutInMs);
+ }
+ }
+
+ if (!m_process->isTerminated())
+ {
+ // Okay, just try terminating
+ m_process->waitForTermination(m_terminationTimeOutInMs);
+ }
+
+ // Okay just kill it then
+ if (!m_process->isTerminated())
+ {
+ m_process->kill(-1);
+ }
+ }
+ m_process.setNull();
+ }
+
+ m_connection.setNull();
+}
+
+SlangResult JSONRPCConnection::sendRPC(const RttiInfo* rttiInfo, const void* data)
+{
+ // Convert to JSON
+ NativeToJSONConverter converter(&m_container, &m_diagnosticSink);
+ JSONValue value;
+
+ SLANG_RETURN_ON_FAIL(converter.convert(rttiInfo, data, value));
+
+ // Convert to text
+ JSONWriter writer(JSONWriter::IndentationStyle::Allman);
+
+ m_container.traverseRecursively(value, &writer);
+ const StringBuilder& builder = writer.getBuilder();
+ return m_connection->write(builder.getBuffer(), builder.getLength());
+}
+
+SlangResult JSONRPCConnection::sendError(JSONRPC::ErrorCode code)
+{
+ return sendError(code, m_diagnosticSink.outputBuffer.getUnownedSlice());
+}
+
+SlangResult JSONRPCConnection::sendError(JSONRPC::ErrorCode errorCode, const UnownedStringSlice& msg)
+{
+ JSONRPCErrorResponse errorResponse;
+ errorResponse.error.code = Int(errorCode);
+ errorResponse.error.message = msg;
+
+ // TODO(JS):
+ // This is only appropriate if the sendError is for the current input message.
+ // We might want to add function that the client uses, which take the id as a parameter.
+
+ if (m_jsonRoot.isValid())
+ {
+ errorResponse.id = JSONRPCUtil::getId(&m_container, m_jsonRoot);
+ }
+ else
+ {
+ // If we don't have valid json, we set the id to be null per the spec
+ errorResponse.id = JSONValue::makeNull();
+ }
+
+ return sendRPC(&errorResponse);
+}
+
+SlangResult JSONRPCConnection::toNativeOrSendError(const JSONValue& value, const RttiInfo* info, void* dst)
+{
+ m_diagnosticSink.outputBuffer.Clear();
+ if (SLANG_FAILED(JSONRPCUtil::convertToNative(&m_container, value, &m_diagnosticSink, info, dst)))
+ {
+ return sendError(JSONRPC::ErrorCode::InvalidRequest);
+ }
+ return SLANG_OK;
+}
+
+SlangResult JSONRPCConnection::sendCall(const UnownedStringSlice& method, const JSONValue& id)
+{
+ JSONRPCCall call;
+ call.id = id;
+ call.method = method;
+
+ SLANG_RETURN_ON_FAIL(sendRPC(&call));
+ return SLANG_OK;
+}
+
+SlangResult JSONRPCConnection::sendResult(const RttiInfo* rttiInfo, const void* result, const JSONValue& id)
+{
+ JSONResultResponse response;
+ response.id = id;
+
+ NativeToJSONConverter converter(&m_container, &m_diagnosticSink);
+ SLANG_RETURN_ON_FAIL(converter.convert(rttiInfo, result, response.result));
+
+ // Send the RPC
+ SLANG_RETURN_ON_FAIL(sendRPC(&response));
+ return SLANG_OK;
+}
+
+SlangResult JSONRPCConnection::sendCall(const UnownedStringSlice& method, const RttiInfo* argsRttiInfo, const void* args, const JSONValue& id)
+{
+ JSONRPCCall call;
+ call.id = id;
+ call.method = method;
+
+ // Convert the args/params
+ NativeToJSONConverter converter(&m_container, &m_diagnosticSink);
+ SLANG_RETURN_ON_FAIL(converter.convert(argsRttiInfo, args, call.params));
+
+ // Send the RPC
+ SLANG_RETURN_ON_FAIL(sendRPC(&call));
+ return SLANG_OK;
+}
+
+SlangResult JSONRPCConnection::waitForResult(Int timeOutInMs)
+{
+ SLANG_RETURN_ON_FAIL(m_connection->waitForResult(timeOutInMs));
+ return tryReadMessage();
+}
+
+SlangResult JSONRPCConnection::tryReadMessage()
+{
+ m_jsonRoot.reset();
+
+ SLANG_RETURN_ON_FAIL(m_connection->update());
+ if (!m_connection->hasContent())
+ {
+ return SLANG_OK;
+ }
+
+ auto content = m_connection->getContent();
+ UnownedStringSlice slice((const char*)content.begin(), content.getCount());
+
+ clearBuffers();
+
+ {
+ const SlangResult res = JSONRPCUtil::parseJSON(slice, &m_container, &m_diagnosticSink, m_jsonRoot);
+
+ // Consume that content/packet
+ m_connection->consumeContent();
+ if (SLANG_FAILED(res))
+ {
+ return sendError(JSONRPC::ErrorCode::ParseError);
+ }
+ }
+
+ return SLANG_OK;
+}
+
+JSONRPCMessageType JSONRPCConnection::getMessageType()
+{
+ return JSONRPCUtil::getMessageType(&m_container, m_jsonRoot);
+}
+
+SlangResult JSONRPCConnection::getMessage(const RttiInfo* rttiInfo, void* out)
+{
+ if (!hasMessage())
+ {
+ return SLANG_FAIL;
+ }
+
+ m_diagnosticSink.outputBuffer.Clear();
+ JSONToNativeConverter converter(&m_container, &m_diagnosticSink);
+
+ // Get the RPC response
+ JSONResultResponse resultResponse;
+ SLANG_RETURN_ON_FAIL(converter.convert(m_jsonRoot, &resultResponse));
+
+ // Convert the result in the response
+ SLANG_RETURN_ON_FAIL(converter.convert(resultResponse.result, rttiInfo, out));
+ return SLANG_OK;
+}
+
+SlangResult JSONRPCConnection::getMessageOrSendError(const RttiInfo* rttiInfo, void* out)
+{
+ if (!hasMessage())
+ {
+ return SLANG_FAIL;
+ }
+
+ const auto res = getMessage(rttiInfo, out);
+ if (SLANG_FAILED(res))
+ {
+ return sendError(JSONRPC::ErrorCode::ParseError);
+ }
+ return res;
+}
+
+SlangResult JSONRPCConnection::getRPC(const RttiInfo* rttiInfo, void* out)
+{
+ if (!hasMessage())
+ {
+ return SLANG_FAIL;
+ }
+
+ m_diagnosticSink.outputBuffer.Clear();
+ JSONToNativeConverter converter(&m_container, &m_diagnosticSink);
+
+ // Convert the result in the response
+ SLANG_RETURN_ON_FAIL(converter.convert(m_jsonRoot, rttiInfo, out));
+ return SLANG_OK;
+}
+
+SlangResult JSONRPCConnection::getRPCOrSendError(const RttiInfo* rttiInfo, void* out)
+{
+ if (!hasMessage())
+ {
+ return SLANG_FAIL;
+ }
+
+ const auto res = getRPC(rttiInfo, out);
+ if (SLANG_FAILED(res))
+ {
+ return sendError(JSONRPC::ErrorCode::ParseError);
+ }
+ return res;
+}
+
+} // namespcae Slang
diff --git a/source/compiler-core/slang-json-rpc-connection.h b/source/compiler-core/slang-json-rpc-connection.h
new file mode 100644
index 000000000..730635493
--- /dev/null
+++ b/source/compiler-core/slang-json-rpc-connection.h
@@ -0,0 +1,161 @@
+#ifndef SLANG_COMPILER_CORE_JSON_RPC_CONNECTION_H
+#define SLANG_COMPILER_CORE_JSON_RPC_CONNECTION_H
+
+#include "../../source/core/slang-http.h"
+#include "../../source/core/slang-process.h"
+
+#include "slang-diagnostic-sink.h"
+#include "slang-source-loc.h"
+#include "slang-json-value.h"
+#include "slang-json-rpc.h"
+
+#include "slang-json-diagnostics.h"
+
+#include "slang-test-server-protocol.h"
+
+namespace Slang {
+
+/* TODO(JS):
+
+It's not really clear here, how to handle id handling. If a server can receive multiple messages, and then later send responses we need
+a way to queue up ids. This is complicated by JSONValue is only valid whilst the backing JSONContainer holds it.
+
+We probably want to create some kind of Variant that can hold all of these values that can hold state independently.
+*/
+class JSONRPCConnection : public RefObject
+{
+public:
+
+ /// An init function must be called before use
+ /// If a process is implementing the server it should be passed in if the process needs to shut down if the connection does
+ SlangResult init(HTTPPacketConnection* connection, Process* process = nullptr);
+
+ /// Initialize using stdin/out streams for input/output.
+ SlangResult initWithStdStreams(Process* process = nullptr);
+
+ /// Disconnect. May block while server shuts down
+ void disconnect();
+
+ /// Convert value to dst. Will write response on fails
+ SlangResult toNativeOrSendError(const JSONValue& value, const RttiInfo* info, void* dst);
+ template <typename T>
+ SlangResult toNativeOrSendError(const JSONValue& value, T* data) { return toNativeOrSendError(value, GetRttiInfo<T>::get(), data); }
+
+ template <typename T>
+ SlangResult toValidNativeOrSendError(const JSONValue& value, T* data);
+
+ /// Send a RPC response (ie should only be one of the JSONRPC classes)
+ SlangResult sendRPC(const RttiInfo* info, const void* data);
+ template <typename T>
+ SlangResult sendRPC(const T* data) { return sendRPC(GetRttiInfo<T>::get(), (const void*)data); }
+
+ /// Send an error
+ SlangResult sendError(JSONRPC::ErrorCode code);
+ SlangResult sendError(JSONRPC::ErrorCode errorCode, const UnownedStringSlice& msg);
+
+ /// Send a call
+ /// If no id is needed, id can just be invalid
+ SlangResult sendCall(const UnownedStringSlice& method, const RttiInfo* argsRttiInfo, const void* args, const JSONValue& id = JSONValue());
+ template <typename T>
+ SlangResult sendCall(const UnownedStringSlice& method, const T* args, const JSONValue& id = JSONValue()) { return sendCall(method, GetRttiInfo<T>::get(), (const void*)args, id); }
+ ///
+ SlangResult sendCall(const UnownedStringSlice& method, const JSONValue& id = JSONValue());
+
+ template <typename T>
+ SlangResult sendResult(const T* result, const JSONValue& id = JSONValue()) { return sendResult(GetRttiInfo<T>::get(), (const void*)result, id); }
+ SlangResult sendResult(const RttiInfo* rttiInfo, const void* result, const JSONValue& id = JSONValue());
+
+ /// Try to read a message. Will return if message is not available.
+ SlangResult tryReadMessage();
+
+ /// Will block for message/result up to time
+ SlangResult waitForResult(Int timeOutInMs = -1);
+
+ /// If we have an JSON-RPC message m_jsonRoot the root.
+ bool hasMessage() const { return m_jsonRoot.isValid(); }
+
+ /// If there is a message returns kind of JSON RPC message
+ JSONRPCMessageType getMessageType();
+
+ /// Get JSON-RPC message (ie one of JSONRPC classes)
+ template <typename T>
+ SlangResult getRPC(T* out) { return getRPC(GetRttiInfo<T>::get(), (void*)out); }
+ SlangResult getRPC(const RttiInfo* rttiInfo, void* out);
+
+ /// Get JSON-RPC message (ie one of JSONRPC prefixed classes)
+ /// If there is a message and there is a failure, will send an error response
+ template <typename T>
+ SlangResult getRPCOrSendError(T* out) { return getRPCOrSendError(GetRttiInfo<T>::get(), (void*)out); }
+ SlangResult getRPCOrSendError(const RttiInfo* rttiInfo, void* out);
+
+ /// Get message (has to be part of JSONRPCResultResponse)
+ template <typename T>
+ SlangResult getMessage(T* out) { return getMessage(GetRttiInfo<T>::get(), (void*)out); }
+ SlangResult getMessage(const RttiInfo* rttiInfo, void* out);
+
+ /// If there is a message and there is a failure, will send an error response
+ template <typename T>
+ SlangResult getMessageOrSendError(T* out) { return getMessageOrSendError(GetRttiInfo<T>::get(), (void*)out); }
+ SlangResult getMessageOrSendError(const RttiInfo* rttiInfo, void* out);
+
+ /// Clears all the internal buffers (for JSON/Source/etc).
+ /// Happens automatically on tryReadMessage/readMessage
+ void clearBuffers();
+
+ /// True if this connection is active
+ bool isActive();
+
+ /// Get the id of the current message
+ JSONValue getMessageId();
+
+ /// Get the diagnostic sink. Can queue up errors before sending an error
+ DiagnosticSink* getSink() { return &m_diagnosticSink; }
+
+ /// Dtor
+ ~JSONRPCConnection() { disconnect(); }
+
+ /// Ctor
+ JSONRPCConnection():m_container(nullptr) {}
+
+protected:
+ RefPtr<Slang::Process> m_process; ///< Backing process (optional)
+ RefPtr<Slang::HTTPPacketConnection> m_connection;
+
+ DiagnosticSink m_diagnosticSink;
+
+ SourceManager m_sourceManager;
+ JSONContainer m_container;
+
+ JSONValue m_jsonRoot;
+
+ /// Default timeout is 10 seconds
+ Int m_timeOutInMs = 10 * 1000;
+ /// Termination timeout
+ Int m_terminationTimeOutInMs = 1 * 1000;
+};
+
+// ---------------------------------------------------------------------------
+template <typename T>
+SlangResult JSONRPCConnection::toValidNativeOrSendError(const JSONValue& value, T* data)
+{
+ const RttiInfo* rttiInfo = GetRttiInfo<T>::get();
+
+ SLANG_RETURN_ON_FAIL(toNativeOrSendError(value, rttiInfo, (void*)data));
+ if (!data->isValid())
+ {
+ // If it has a name add validation info
+ if (rttiInfo->isNamed())
+ {
+ const NamedRttiInfo* namedRttiInfo = static_cast<const NamedRttiInfo*>(rttiInfo);
+ m_diagnosticSink.diagnose(SourceLoc(), JSONDiagnostics::argsAreInvalid, namedRttiInfo->m_name);
+ }
+
+ return sendError(JSONRPC::ErrorCode::InvalidRequest);
+ }
+ return SLANG_OK;
+}
+
+} // namespace Slang
+
+#endif // SLANG_COMPILER_CORE_JSON_RPC_CONNECTION_H
+
diff --git a/source/compiler-core/slang-json-rpc.cpp b/source/compiler-core/slang-json-rpc.cpp
index 1ca55e9eb..660bbd4b7 100644
--- a/source/compiler-core/slang-json-rpc.cpp
+++ b/source/compiler-core/slang-json-rpc.cpp
@@ -2,23 +2,20 @@
#include "../../slang-com-helper.h"
+#include "slang-json-native.h"
+
namespace Slang {
// https://www.jsonrpc.org/specification
-// 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 UnownedStringSlice JSONRPC::jsonRpc = UnownedStringSlice::fromLiteral("jsonrpc");
+/* static */const UnownedStringSlice JSONRPC::jsonRpcVersion = UnownedStringSlice::fromLiteral("2.0");
+/* static */const UnownedStringSlice JSONRPC::id = UnownedStringSlice::fromLiteral("id");
+
static const auto g_result = UnownedStringSlice::fromLiteral("result");
-static const auto g_data = UnownedStringSlice::fromLiteral("data");
+static const auto g_error = UnownedStringSlice::fromLiteral("error");
+static const auto g_method = UnownedStringSlice::fromLiteral("method");
// Add the fields.
// TODO(JS): This is a little verbose, and could be improved on with something like
@@ -39,9 +36,10 @@ static const StructRttiInfo _makeJSONRPCErrorResponseRtti()
JSONRPCErrorResponse obj;
StructRttiBuilder builder(&obj, "JSONRPCErrorResponse", nullptr);
+ builder.addField("jsonrpc", &obj.jsonrpc);
builder.addField("error", &obj.error);
builder.addField("data", &obj.data, StructRttiInfo::Flag::Optional);
- builder.addField("id", &obj.id, combine(StructRttiInfo::Flag::Optional, RttiDefaultValue::MinusOne));
+ builder.addField("id", &obj.id, StructRttiInfo::Flag::Optional);
return builder.make();
}
@@ -52,9 +50,10 @@ static const StructRttiInfo _makeJSONRPCCallResponseRtti()
JSONRPCCall obj;
StructRttiBuilder builder(&obj, "JSONRPCCall", nullptr);
+ builder.addField("jsonrpc", &obj.jsonrpc);
builder.addField("method", &obj.method);
builder.addField("params", &obj.params, StructRttiInfo::Flag::Optional);
- builder.addField("id", &obj.id, combine(StructRttiInfo::Flag::Optional, RttiDefaultValue::MinusOne));
+ builder.addField("id", &obj.id, StructRttiInfo::Flag::Optional);
return builder.make();
}
@@ -65,350 +64,44 @@ static const StructRttiInfo _makeJSONResultResponseResponseRtti()
JSONResultResponse obj;
StructRttiBuilder builder(&obj, "JSONResultResponse", nullptr);
+ builder.addField("jsonrpc", &obj.jsonrpc);
builder.addField("result", &obj.result);
- builder.addField("id", &obj.id, combine(StructRttiInfo::Flag::Optional, RttiDefaultValue::MinusOne));
+ builder.addField("id", &obj.id, StructRttiInfo::Flag::Optional);
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)
+/* static */JSONRPCMessageType JSONRPCUtil::getMessageType(JSONContainer* container, const JSONValue& value)
{
- 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)
+ if (value.getKind() == JSONValue::Kind::Object)
{
const JSONKey resultKey = container->findKey(g_result);
const JSONKey errorKey = container->findKey(g_error);
+ const JSONKey methodKey = container->findKey(g_method);
- auto pairs = container->getObject(response);
+ auto pairs = container->getObject(value);
for (const auto& pair : pairs)
{
if (pair.key == resultKey)
{
- return ResponseType::Result;
+ return JSONRPCMessageType::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;
+ return JSONRPCMessageType::Error;
}
- 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)
+ else if (pair.key == methodKey)
{
- return SLANG_FAIL;
+ return JSONRPCMessageType::Call;
}
- 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;
+ return JSONRPCMessageType::Invalid;
}
-
/* static */SlangResult JSONRPCUtil::parseJSON(const UnownedStringSlice& slice, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue)
{
SourceManager* sourceManager = sink->getSourceManager();
@@ -430,21 +123,50 @@ static SlangResult _parseError(JSONContainer* container, const JSONValue& error,
return SLANG_OK;
}
-SlangResult JSONRPCUtil::parseJSONAndConsume(HTTPPacketConnection* connection, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue)
+/* static */SlangResult JSONRPCUtil::convertToNative(JSONContainer* container, const JSONValue& value, DiagnosticSink* sink, const RttiInfo* rttiInfo, void* out)
{
- if (!connection->hasContent())
- {
- return SLANG_FAIL;
- }
+ JSONToNativeConverter converter(container, sink);
+ SLANG_RETURN_ON_FAIL(converter.convert(value, rttiInfo, out));
+ return SLANG_OK;
+}
- auto content = connection->getContent();
+/* static */SlangResult JSONRPCUtil::convertToJSON(const RttiInfo* rttiInfo, const void* in, DiagnosticSink* sink, StringBuilder& out)
+{
+ SourceManager* sourceManager = sink->getSourceManager();
+ JSONContainer container(sourceManager);
- UnownedStringSlice text((const char*)content.begin(), content.getCount());
- SlangResult res = parseJSON(text, container, sink, outValue);
+ NativeToJSONConverter converter(&container, sink);
- // Consume the content
- connection->consumeContent();
- return res;
+ JSONValue value;
+ SLANG_RETURN_ON_FAIL(converter.convert(rttiInfo, in, value));
+
+ // Convert into a string
+ JSONWriter writer(JSONWriter::IndentationStyle::Allman);
+ container.traverseRecursively(value, &writer);
+
+ out = writer.getBuilder();
+ return SLANG_OK;
}
+/* static */JSONValue JSONRPCUtil::getId(JSONContainer* container, const JSONValue& root)
+{
+ if (root.getKind() == JSONValue::Kind::Object)
+ {
+ const JSONKey key = container->findKey(JSONRPC::id);
+
+ if (key != JSONKey(0))
+ {
+ auto obj = container->getObject(root);
+ Index index = obj.findFirstIndex([key](const JSONKeyValue& pair) -> bool { return pair.key == key; });
+
+ if (index >= 0)
+ {
+ return obj[index].value;
+ }
+ }
+ }
+ return JSONValue();
+}
+
+
} // namespace Slang
diff --git a/source/compiler-core/slang-json-rpc.h b/source/compiler-core/slang-json-rpc.h
index e85664ceb..f32513b02 100644
--- a/source/compiler-core/slang-json-rpc.h
+++ b/source/compiler-core/slang-json-rpc.h
@@ -12,115 +12,124 @@
namespace Slang {
+/// Struct to hold values associated with JSON-RPC
+struct JSONRPC
+{
+ 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,
+ };
+
+ static bool isIdOk(const JSONValue& value)
+ {
+ auto kind = value.getKind();
+ switch (kind)
+ {
+ case JSONValue::Kind::Integer:
+ case JSONValue::Kind::Invalid:
+ case JSONValue::Kind::String:
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static const UnownedStringSlice jsonRpc;
+ static const UnownedStringSlice jsonRpcVersion;
+ static const UnownedStringSlice id;
+};
+
struct JSONRPCErrorResponse
{
struct Error
{
- Index code = 0; ///< Value from ErrorCode
- UnownedStringSlice message; ///< Error message
+ bool isValid() const { return code != 0; }
+ Int code = 0; ///< Value from ErrorCode
+ UnownedStringSlice message; ///< Error message
+
static const StructRttiInfo g_rttiInfo;
};
+ bool isValid() const { return jsonrpc == JSONRPC::jsonRpcVersion && error.isValid() && JSONRPC::isIdOk(id); }
+
+ UnownedStringSlice jsonrpc = JSONRPC::jsonRpcVersion;
Error error;
- JSONValue data;
- Int id = -1; ///< Id of initiating method or -1 if not set
+ JSONValue data; ///< Optional data describing the errro
+ JSONValue id; ///< Id associated with this request
static const StructRttiInfo g_rttiInfo;
};
struct JSONRPCCall
{
+ bool isValid() const { return method.getLength() > 0 && jsonrpc == JSONRPC::jsonRpcVersion && JSONRPC::isIdOk(id); }
+
+ UnownedStringSlice jsonrpc = JSONRPC::jsonRpcVersion;
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
+ JSONValue id; ///< Id associated with this request
static const StructRttiInfo g_rttiInfo;
};
struct JSONResultResponse
{
- JSONValue result; ///< The result value
- Int id = -1; ///< Id of initiating method or -1 if not set
+ bool isValid() const { return jsonrpc == JSONRPC::jsonRpcVersion && JSONRPC::isIdOk(id); }
+
+ UnownedStringSlice jsonrpc = JSONRPC::jsonRpcVersion;
+ JSONValue result; ///< The result value
+ JSONValue id; ///< Id associated with this request
static const StructRttiInfo g_rttiInfo;
};
+enum class JSONRPCMessageType
+{
+ Invalid,
+ Result,
+ Call,
+ Error,
+ CountOf,
+};
+
/// 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 JSONRPCMessageType getMessageType(JSONContainer* container, const JSONValue& value);
- static SlangResult parseError(JSONContainer* container, const JSONValue& response, ErrorResponse& out);
+ /// Parse slice into JSONContainer. outValue is the root of the hierarchy.
+ /// NOTE! Uses and *assumes* there is a source manager on the sink. outValue is likely only usable whilst the sourceManger is in scope
+ /// The sourceLoc can only be interpretted with the sourceLoc anyway
+ static SlangResult parseJSON(const UnownedStringSlice& slice, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue);
- static SlangResult parseResult(JSONContainer* container, const JSONValue& response, ResultResponse& out);
+ /// Convert value into out
+ static SlangResult convertToNative(JSONContainer* container, const JSONValue& value, DiagnosticSink* sink, const RttiInfo* rttiInfo, void* out);
+ template <typename T>
+ static SlangResult convertToNative(JSONContainer* container, const JSONValue& value, DiagnosticSink* sink, T& out) { return convertToNative(container, value, sink, GetRttiInfo<T>::get(), (void*)&out); }
- static SlangResult parseCall(JSONContainer* container, const JSONValue& value, Call& out);
+ /// Convert to JSON
+ static SlangResult convertToJSON(const RttiInfo* rttiInfo, const void* in, DiagnosticSink* sink, StringBuilder& out);
- /// Parse slice into JSONContainer. outValue is the root of the hierarchy.
- static SlangResult parseJSON(const UnownedStringSlice& slice, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue);
+ template <typename T>
+ static SlangResult convertToJSON(const T* in, DiagnosticSink* sink, StringBuilder& out)
+ {
+ return convertToJSON(GetRttiInfo<T>::get(), (const void*)in, sink, out);
+ }
- /// Parse content from stream, and consume the packet
- static SlangResult parseJSONAndConsume(HTTPPacketConnection* connection, JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue);
+ /// Get an id directly from root (assumed id: is in root object definition).
+ static JSONValue getId(JSONContainer* container, const JSONValue& root);
};
} // namespace Slang
diff --git a/source/compiler-core/slang-json-value.cpp b/source/compiler-core/slang-json-value.cpp
index 4fad3764f..216512407 100644
--- a/source/compiler-core/slang-json-value.cpp
+++ b/source/compiler-core/slang-json-value.cpp
@@ -164,6 +164,8 @@ void JSONContainer::reset()
m_objectValues.clear();
_addRange(Range::Type::None, 0, 0);
+
+ m_currentView = nullptr;
}
/* static */bool JSONContainer::areKeysUnique(const JSONKeyValue* keyValues, Index keyValueCount)
diff --git a/source/compiler-core/slang-json-value.h b/source/compiler-core/slang-json-value.h
index 119479285..b94283d81 100644
--- a/source/compiler-core/slang-json-value.h
+++ b/source/compiler-core/slang-json-value.h
@@ -100,7 +100,7 @@ struct JSONValue
/// Given a type return the associated kind
static Kind getKindForType(Type type) { return g_typeToKind[Index(type)]; }
- Type type; ///< The type of value
+ Type type = Type::Invalid; ///< The type of value
SourceLoc loc; ///< The (optional) location in source of this value.
union
@@ -112,6 +112,7 @@ struct JSONValue
JSONKey stringKey; ///< The pool key if it's a string
};
+
static const Kind g_typeToKind[Index(Type::CountOf)];
static const OtherRttiInfo g_rttiInfo;
diff --git a/source/compiler-core/slang-test-server-protocol.cpp b/source/compiler-core/slang-test-server-protocol.cpp
new file mode 100644
index 000000000..2a3bb3a3b
--- /dev/null
+++ b/source/compiler-core/slang-test-server-protocol.cpp
@@ -0,0 +1,43 @@
+#include "slang-test-server-protocol.h"
+
+namespace TestServerProtocol {
+
+static const StructRttiInfo _makeExecuteUnitTestArgsRtti()
+{
+ ExecuteUnitTestArgs obj;
+ StructRttiBuilder builder(&obj, "TestServerProtocol::ExecuteUnitTestArgs", nullptr);
+
+ builder.addField("moduleName", &obj.moduleName);
+ builder.addField("testName", &obj.testName);
+ builder.addField("enabledApis", &obj.enabledApis);
+ return builder.make();
+}
+/* static */const UnownedStringSlice ExecuteUnitTestArgs::g_methodName = UnownedStringSlice::fromLiteral("unitTest");
+/* static */const StructRttiInfo ExecuteUnitTestArgs::g_rttiInfo = _makeExecuteUnitTestArgsRtti();
+
+static const StructRttiInfo _makeExecuteToolTestArgsRtti()
+{
+ ExecuteToolTestArgs obj;
+ StructRttiBuilder builder(&obj, "TestServerProtocol::ExecuteToolTestArgs", nullptr);
+ builder.addField("toolName", &obj.toolName);
+ builder.addField("args", &obj.args);
+ return builder.make();
+}
+/* static */const StructRttiInfo ExecuteToolTestArgs::g_rttiInfo = _makeExecuteToolTestArgsRtti();
+/* static */const UnownedStringSlice ExecuteToolTestArgs::g_methodName = UnownedStringSlice::fromLiteral("tool");
+
+static const StructRttiInfo _makeExecutionResultRtti()
+{
+ ExecutionResult obj;
+ StructRttiBuilder builder(&obj, "TestServerProtocol::ExecutionResult", nullptr);
+ builder.addField("stdOut", &obj.stdOut);
+ builder.addField("stdError", &obj.stdError);
+ builder.addField("result", &obj.result);
+ builder.addField("returnCode", &obj.returnCode);
+ return builder.make();
+}
+/* static */const StructRttiInfo ExecutionResult::g_rttiInfo = _makeExecutionResultRtti();
+
+/* static */const UnownedStringSlice QuitArgs::g_methodName = UnownedStringSlice::fromLiteral("quit");
+
+} // namespace TestServerProtocol
diff --git a/source/compiler-core/slang-test-server-protocol.h b/source/compiler-core/slang-test-server-protocol.h
new file mode 100644
index 000000000..8b61c4b39
--- /dev/null
+++ b/source/compiler-core/slang-test-server-protocol.h
@@ -0,0 +1,51 @@
+#ifndef SLANG_COMPILER_CORE_TEST_PROTOCOL_H
+#define SLANG_COMPILER_CORE_TEST_PROTOCOL_H
+
+#include "../../slang.h"
+#include "../../slang-com-helper.h"
+#include "../../slang-com-ptr.h"
+
+#include "../core/slang-rtti-info.h"
+#include "slang-json-value.h"
+
+namespace TestServerProtocol {
+
+using namespace Slang;
+
+struct ExecuteUnitTestArgs
+{
+ String moduleName;
+ String testName;
+ uint32_t enabledApis;
+
+ static const UnownedStringSlice g_methodName;
+ static const StructRttiInfo g_rttiInfo;
+};
+
+struct ExecuteToolTestArgs
+{
+ String toolName; ///< The name of the tool (will be a shared library typically - like render-test). Doesn't need -tool suffix.
+ List<String> args; ///< Arguments passed to the tool during exectution
+
+ static const UnownedStringSlice g_methodName;
+ static const StructRttiInfo g_rttiInfo;
+};
+
+struct QuitArgs
+{
+ static const UnownedStringSlice g_methodName;
+};
+
+struct ExecutionResult
+{
+ String stdOut;
+ String stdError;
+ int32_t result = SLANG_OK;
+ int32_t returnCode = 0; ///< As returned if invoked as command line
+
+ static const StructRttiInfo g_rttiInfo;
+};
+
+} // namespace Slang
+
+#endif // SLANG_COMPILER_CORE_TEST_PROTOCOL_H
diff --git a/source/core/slang-http.cpp b/source/core/slang-http.cpp
index 43be26c1c..6a4bb4f92 100644
--- a/source/core/slang-http.cpp
+++ b/source/core/slang-http.cpp
@@ -287,8 +287,19 @@ SlangResult HTTPPacketConnection::update()
return m_readResult;
}
-SlangResult HTTPPacketConnection::waitForResult()
+SlangResult HTTPPacketConnection::waitForResult(Int timeOutInMs)
{
+ m_readResult = SLANG_OK;
+
+ int64_t startTick = 0;
+ int64_t timeOutInTicks = -1;
+
+ if (timeOutInMs >= 0)
+ {
+ timeOutInTicks = timeOutInMs * (Process::getClockFrequency() / 1000);
+ startTick = Process::getClockTick();
+ }
+
while (m_readState == ReadState::Header ||
m_readState == ReadState::Content)
{
@@ -296,6 +307,17 @@ SlangResult HTTPPacketConnection::waitForResult()
SLANG_RETURN_ON_FAIL(update());
+ if (m_readState == ReadState::Done)
+ {
+ break;
+ }
+
+ // We timed out
+ if (timeOutInTicks >= 0 && int64_t(Process::getClockTick()) - startTick >= timeOutInTicks)
+ {
+ break;
+ }
+
if (prevCount == m_readStream->getCount())
{
// Yield if it appears nothing was read.
diff --git a/source/core/slang-http.h b/source/core/slang-http.h
index a298e9e27..784357273 100644
--- a/source/core/slang-http.h
+++ b/source/core/slang-http.h
@@ -123,8 +123,9 @@ public:
/// Write. Will potentially block if write stream is blocking.
SlangResult write(const void* content, size_t sizeInBytes);
- /// Blocks until some result - a packet, closure, or some kind of error
- SlangResult waitForResult();
+ /// Blocks until some result - a packet, closure, or some kind of error or timeout.
+ /// TimeOut of -1 means no timeout.
+ SlangResult waitForResult(Int timeOutInMs = -1);
/// Consume the content - so can read next content
void consumeContent();
diff --git a/source/core/slang-writer.h b/source/core/slang-writer.h
index 380823b39..7c768a22f 100644
--- a/source/core/slang-writer.h
+++ b/source/core/slang-writer.h
@@ -148,6 +148,7 @@ public:
Parent(flags),
m_builder(builder)
{}
+ ~StringWriter() {}
protected:
StringBuilder* m_builder;
diff --git a/tools/slang-test/options.cpp b/tools/slang-test/options.cpp
index d24ea83e2..4fe7ea6bd 100644
--- a/tools/slang-test/options.cpp
+++ b/tools/slang-test/options.cpp
@@ -81,6 +81,7 @@ static bool _isSubCommand(const char* arg)
while (argCursor != argEnd)
{
char const* arg = *argCursor++;
+
if (arg[0] != '-')
{
// We need to determine if this is a command, the confusion is that
@@ -129,6 +130,10 @@ static bool _isSubCommand(const char* arg)
{
optionsOut->defaultSpawnType = SpawnType::UseProxy;
}
+ else if (strcmp(arg, "-use-test-server") == 0)
+ {
+ optionsOut->defaultSpawnType = SpawnType::UseTestServer;
+ }
else if (strcmp(arg, "-v") == 0)
{
optionsOut->shouldBeVerbose = true;
diff --git a/tools/slang-test/options.h b/tools/slang-test/options.h
index 4a40cba9a..5dc3ca1fb 100644
--- a/tools/slang-test/options.h
+++ b/tools/slang-test/options.h
@@ -41,6 +41,7 @@ enum class SpawnType
UseExe,
UseSharedLibrary,
UseProxy,
+ UseTestServer,
};
struct Options
diff --git a/tools/slang-test/slang-test-main.cpp b/tools/slang-test/slang-test-main.cpp
index e007d1978..000ee462c 100644
--- a/tools/slang-test/slang-test-main.cpp
+++ b/tools/slang-test/slang-test-main.cpp
@@ -655,6 +655,62 @@ Result spawnAndWaitProxy(TestContext* context, const String& testPath, const Com
return res;
}
+static Result _executeRPC(TestContext* context, const UnownedStringSlice& method, const RttiInfo* rttiInfo, const void* args, ExecuteResult& outRes)
+{
+ JSONRPCConnection* rpcConnection = context->getOrCreateJSONRPCConnection();
+ if (!rpcConnection)
+ {
+ return SLANG_FAIL;
+ }
+
+ // Execute
+ SLANG_RETURN_ON_FAIL(rpcConnection->sendCall(method, rttiInfo, args));
+
+ // Wait for the result
+ rpcConnection->waitForResult(context->timeOutInMs);
+
+ if (!rpcConnection->hasMessage())
+ {
+ // We can assume somethings gone wrong. So lets kill the connection and fail.
+ context->destroyRPCConnection();
+ return SLANG_FAIL;
+ }
+
+ if (rpcConnection->getMessageType() != JSONRPCMessageType::Result)
+ {
+ return SLANG_FAIL;
+ }
+
+ // Get the result
+ TestServerProtocol::ExecutionResult exeRes;
+ SLANG_RETURN_ON_FAIL(rpcConnection->getMessage(&exeRes));
+
+ outRes.resultCode = exeRes.returnCode;
+ outRes.standardError = exeRes.stdError;
+ outRes.standardOutput = exeRes.stdOut;
+
+ return SLANG_OK;
+}
+
+template <typename T>
+static Result _executeRPC(TestContext* context, const UnownedStringSlice& method, const T* msg, ExecuteResult& outRes)
+{
+ return _executeRPC(context, method, GetRttiInfo<T>::get(), (const void*)msg, outRes);
+}
+
+Result spawnAndWaitTestServer(TestContext* context, const String& testPath, const CommandLine& inCmdLine, ExecuteResult& outRes)
+{
+ String exeName = Path::getFileNameWithoutExt(inCmdLine.m_executable);
+
+ // This is a test tool execution
+ TestServerProtocol::ExecuteToolTestArgs args;
+
+ args.toolName = exeName;
+ args.args = inCmdLine.m_args;
+
+ return _executeRPC(context, TestServerProtocol::ExecuteToolTestArgs::g_methodName, &args, outRes);
+}
+
static SlangResult _extractArg(const CommandLine& cmdLine, const String& argName, String& outValue)
{
SLANG_ASSERT(argName.getLength() > 0 && argName[0] == '-');
@@ -1012,6 +1068,11 @@ ToolReturnCode spawnAndWait(TestContext* context, const String& testPath, SpawnT
spawnResult = spawnAndWaitProxy(context, testPath, cmdLine, outExeRes);
break;
}
+ case SpawnType::UseTestServer:
+ {
+ spawnResult = spawnAndWaitTestServer(context, testPath, cmdLine, outExeRes);
+ break;
+ }
default: break;
}
@@ -1574,7 +1635,7 @@ TestResult runReflectionTest(TestContext* context, TestInput& input)
TestResult result = TestResult::Pass;
// Otherwise we compare to the expected output
- if (actualOutput != expectedOutput)
+ if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice()))
{
result = TestResult::Fail;
}
@@ -1981,7 +2042,7 @@ TestResult runCrossCompilerTest(TestContext* context, TestInput& input)
TestResult result = TestResult::Pass;
// Otherwise we compare to the expected output
- if (actualOutput != expectedOutput)
+ if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice()))
{
result = TestResult::Fail;
}
@@ -2135,7 +2196,7 @@ static TestResult _runHLSLComparisonTest(
if (standardOutput.getLength() != 0) result = TestResult::Fail;
}
// Otherwise we compare to the expected output
- else if (actualOutput != expectedOutput)
+ else if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice()))
{
result = TestResult::Fail;
}
@@ -2272,7 +2333,7 @@ TestResult runGLSLComparisonTest(TestContext* context, TestInput& input)
if( hlslResult == TestResult::Fail ) return TestResult::Fail;
if( slangResult == TestResult::Fail ) return TestResult::Fail;
- if (actualOutput != expectedOutput)
+ if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice()))
{
context->reporter->dumpOutputDifference(expectedOutput, actualOutput);
@@ -2511,7 +2572,8 @@ TestResult runComputeComparisonImpl(TestContext* context, TestInput& input, cons
auto actualOutput = getOutput(exeRes);
auto expectedOutput = getExpectedOutput(outputStem);
- if (actualOutput != expectedOutput)
+
+ if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice()))
{
context->reporter->dumpOutputDifference(expectedOutput, actualOutput);
@@ -2817,7 +2879,7 @@ TestResult runHLSLRenderComparisonTestImpl(
if( hlslResult == TestResult::Fail ) return TestResult::Fail;
if( slangResult == TestResult::Fail ) return TestResult::Fail;
- if (actualOutput != expectedOutput)
+ if (!StringUtil::areLinesEqual(actualOutput.getUnownedSlice(), expectedOutput.getUnownedSlice()))
{
context->reporter->dumpOutputDifference(expectedOutput, actualOutput);
@@ -3478,6 +3540,30 @@ static SlangResult runUnitTestModule(TestContext* context, TestOptions& testOpti
reporter->addResult(testResult);
}
}
+ else if (spawnType == SpawnType::UseTestServer)
+ {
+ TestServerProtocol::ExecuteUnitTestArgs args;
+ args.enabledApis = context->options.enabledApis;
+ args.moduleName = moduleName;
+ args.testName = testName;
+
+ {
+ TestReporter::TestScope scopeTest(reporter, testOptions.command);
+ ExecuteResult exeRes;
+
+ SlangResult rpcRes = _executeRPC(context, TestServerProtocol::ExecuteUnitTestArgs::g_methodName, &args, exeRes);
+ const auto testResult = _asTestResult(ToolReturnCode(exeRes.resultCode));
+
+ // If the test fails, output any output - which might give information about individual tests that have failed.
+ if (SLANG_FAILED(rpcRes) || testResult == TestResult::Fail)
+ {
+ String output = getOutput(exeRes);
+ reporter->message(TestMessageType::TestFailure, output.getBuffer());
+ }
+
+ reporter->addResult(testResult);
+ }
+ }
else
{
TestReporter::TestScope scopeTest(reporter, testOptions.command);
diff --git a/tools/slang-test/test-context.cpp b/tools/slang-test/test-context.cpp
index 2872ddb47..eeb23b695 100644
--- a/tools/slang-test/test-context.cpp
+++ b/tools/slang-test/test-context.cpp
@@ -106,6 +106,53 @@ DownstreamCompilerSet* TestContext::getCompilerSet()
return compilerSet;
}
+SlangResult TestContext::_createJSONRPCConnection(RefPtr<JSONRPCConnection>& out)
+{
+ RefPtr<Process> process;
+
+ {
+ CommandLine cmdLine;
+ cmdLine.setExecutable(exeDirectoryPath.getBuffer(), "test-server");
+ SLANG_RETURN_ON_FAIL(Process::create(cmdLine, Process::Flag::AttachDebugger, process));
+ }
+
+ Stream* writeStream = process->getStream(Process::StreamType::StdIn);
+ RefPtr<BufferedReadStream> readStream(new BufferedReadStream(process->getStream(Process::StreamType::StdOut)));
+
+ RefPtr<HTTPPacketConnection> connection = new HTTPPacketConnection(readStream, writeStream);
+ RefPtr<JSONRPCConnection> rpcConnection = new JSONRPCConnection;
+
+ SLANG_RETURN_ON_FAIL(rpcConnection->init(connection, process));
+
+ out = rpcConnection;
+
+ return SLANG_OK;
+}
+
+
+void TestContext::destroyRPCConnection()
+{
+ if (m_jsonRpcConnection)
+ {
+ m_jsonRpcConnection->disconnect();
+ m_jsonRpcConnection.setNull();
+ }
+}
+
+Slang::JSONRPCConnection* TestContext::getOrCreateJSONRPCConnection()
+{
+ if (!m_jsonRpcConnection)
+ {
+ if (SLANG_FAILED(_createJSONRPCConnection(m_jsonRpcConnection)))
+ {
+ return nullptr;
+ }
+ }
+
+ return m_jsonRpcConnection;
+}
+
+
Slang::DownstreamCompiler* TestContext::getDefaultCompiler(SlangSourceLanguage sourceLanguage)
{
DownstreamCompilerSet* set = getCompilerSet();
diff --git a/tools/slang-test/test-context.h b/tools/slang-test/test-context.h
index 67707028d..203005943 100644
--- a/tools/slang-test/test-context.h
+++ b/tools/slang-test/test-context.h
@@ -11,6 +11,7 @@
#include "../../source/core/slang-render-api-util.h"
#include "../../source/compiler-core/slang-downstream-compiler.h"
+#include "../../source/compiler-core/slang-json-rpc-connection.h"
#include "../../slang-com-ptr.h"
@@ -110,6 +111,9 @@ class TestContext
Slang::DownstreamCompilerSet* getCompilerSet();
Slang::DownstreamCompiler* getDefaultCompiler(SlangSourceLanguage sourceLanguage);
+ Slang::JSONRPCConnection* getOrCreateJSONRPCConnection();
+ void destroyRPCConnection();
+
/// Ctor
TestContext();
/// Dtor
@@ -129,14 +133,21 @@ class TestContext
Slang::RefPtr<Slang::DownstreamCompilerSet> compilerSet;
Slang::String exeDirectoryPath;
-
+
+ Slang::Int timeOutInMs = 100 * 1000;
+
+
protected:
+ SlangResult _createJSONRPCConnection(Slang::RefPtr<Slang::JSONRPCConnection>& out);
+
struct SharedLibraryTool
{
Slang::ComPtr<ISlangSharedLibrary> m_sharedLibrary;
InnerMainFunc m_func;
};
+ Slang::RefPtr<Slang::JSONRPCConnection> m_jsonRpcConnection;
+
SlangSession* m_session;
Slang::Dictionary<Slang::String, SharedLibraryTool> m_sharedLibTools;
diff --git a/tools/test-server/test-server-diagnostic-defs.h b/tools/test-server/test-server-diagnostic-defs.h
index ef0b280e8..8eedaba0d 100644
--- a/tools/test-server/test-server-diagnostic-defs.h
+++ b/tools/test-server/test-server-diagnostic-defs.h
@@ -22,5 +22,6 @@ DIAGNOSTIC(100000, Error, unableToLoadSharedLibrary, "Unable to load shared libr
DIAGNOSTIC(100001, Error, unableToFindFunctionInSharedLibrary, "Unable to find function '$0' in shared library")
DIAGNOSTIC(100002, Error, unableToGetUnitTestModule, "Unable to get unit test module")
DIAGNOSTIC(100003, Error, unableToFindTest, "Unable to find test '$0'")
+DIAGNOSTIC(100004, Error, unableToFindUnitTestModule, "Unable to find unit test module '$0'")
#undef DIAGNOSTIC
diff --git a/tools/test-server/test-server-main.cpp b/tools/test-server/test-server-main.cpp
index 7e4180441..2a2f65f31 100644
--- a/tools/test-server/test-server-main.cpp
+++ b/tools/test-server/test-server-main.cpp
@@ -17,14 +17,10 @@
#include "../../source/core/slang-shared-library.h"
#include "../../source/core/slang-test-tool-util.h"
-#include "../../source/core/slang-http.h"
-#include "../../source/compiler-core/slang-source-loc.h"
-#include "../../source/compiler-core/slang-diagnostic-sink.h"
+#include "../../source/compiler-core/slang-json-rpc-connection.h"
-#include "../../source/compiler-core/slang-json-parser.h"
-#include "../../source/compiler-core/slang-json-rpc.h"
-#include "../../source/compiler-core/slang-json-value.h"
+#include "../../source/compiler-core/slang-test-server-protocol.h"
#include "test-server-diagnostics.h"
@@ -59,7 +55,7 @@ public:
SlangResult init(int argc, const char* const* argv);
/// Can return nullptr if cannot create the session
- slang::IGlobalSession* getGlobalSession();
+ slang::IGlobalSession* getOrCreateGlobalSession();
/// Can return nullptr if cannot load the tool
ISlangSharedLibrary* loadSharedLibrary(const String& name, DiagnosticSink* sink = nullptr);
@@ -78,43 +74,111 @@ public:
protected:
SlangResult _executeSingle();
- SlangResult _executeUnitTest(JSONContainer* container, const JSONValue& root);
- SlangResult _executeTool(JSONContainer* container, const JSONValue& root);
- SlangResult _writeResponse(JSONContainer* containers, const JSONValue& root);
+ SlangResult _executeUnitTest(const JSONRPCCall& call);
+ SlangResult _executeTool(const JSONRPCCall& root);
bool m_quit = false;
- ComPtr<slang::IGlobalSession> m_session;
+ ComPtr<slang::IGlobalSession> m_session; /// The slang session. Is created on demand
- Dictionary<String, ComPtr<ISlangSharedLibrary>> m_sharedLibraryMap; ///< Maps tool names to the dll
- Dictionary<String, IUnitTestModule*> m_unitTestModules;
+ Dictionary<String, ComPtr<ISlangSharedLibrary>> m_sharedLibraryMap; ///< Maps tool names to the dll
+ Dictionary<String, IUnitTestModule*> m_unitTestModules; ///< All the unit test modules.
- String m_exePath; ///< Path to executable
+ String m_exePath; ///< Path to executable (including exe name)
+ String m_exeDirectory; ///< The directory that holds the exe
- DiagnosticSink m_diagnosticSink;
- SourceManager m_sourceManager;
-
- RefPtr<HTTPPacketConnection> m_connection;
+ RefPtr<JSONRPCConnection> m_connection; ///< RPC connection, recieves calls to execute and returns results via JSON-RPC
};
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! TestServer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
-SlangResult TestServer::init(int argc, const char* const* argv)
+namespace SlangCTool {
+
+static void _diagnosticCallback(char const* message, void* userData)
+{
+ ISlangWriter* writer = (ISlangWriter*)userData;
+ writer->write(message, strlen(message));
+}
+
+SlangResult innerMain(StdWriters* stdWriters, slang::IGlobalSession* sharedSession, int argc, const char* const* argv)
{
- m_exePath = Path::getParentDirectory(argv[0]);
+ // Assume we will used the shared session
+ ComPtr<slang::IGlobalSession> session(sharedSession);
+
+ // The sharedSession always has a pre-loaded stdlib.
+ // This differed test checks if the command line has an option to setup the stdlib.
+ // If so we *don't* use the sharedSession, and create a new stdlib-less session just for this compilation.
+ if (TestToolUtil::hasDeferredStdLib(Index(argc - 1), argv + 1))
+ {
+ SLANG_RETURN_ON_FAIL(slang_createGlobalSessionWithoutStdLib(SLANG_API_VERSION, session.writeRef()));
+ }
+
+ ComPtr<slang::ICompileRequest> compileRequest;
+ SLANG_RETURN_ON_FAIL(session->createCompileRequest(compileRequest.writeRef()));
+
+ // Do any app specific configuration
+ for (int i = 0; i < SLANG_WRITER_CHANNEL_COUNT_OF; ++i)
+ {
+ compileRequest->setWriter(SlangWriterChannel(i), stdWriters->getWriter(i));
+ }
+
+ compileRequest->setDiagnosticCallback(&_diagnosticCallback, stdWriters->getWriter(SLANG_WRITER_CHANNEL_STD_ERROR));
+ compileRequest->setCommandLineCompilerMode();
+
+ {
+ const SlangResult res = compileRequest->processCommandLineArguments(&argv[1], argc - 1);
+ if (SLANG_FAILED(res))
+ {
+ // TODO: print usage message
+ return res;
+ }
+ }
+
+ SlangResult compileRes = SLANG_OK;
+
+#ifndef _DEBUG
+ try
+#endif
+ {
+ // Run the compiler (this will produce any diagnostics through SLANG_WRITER_TARGET_TYPE_DIAGNOSTIC).
+ compileRes = compileRequest->compile();
+
+ // If the compilation failed, then get out of here...
+ // Turn into an internal Result -> such that return code can be used to vary result to match previous behavior
+ compileRes = SLANG_FAILED(compileRes) ? SLANG_E_INTERNAL_FAIL : compileRes;
+ }
+#ifndef _DEBUG
+ catch (const Exception& e)
+ {
+ WriterHelper writerHelper(stdWriters->getWriter(SLANG_WRITER_CHANNEL_STD_OUTPUT));
+ writerHelper.print("internal compiler error: %S\n", e.Message.toWString().begin());
+ compileRes = SLANG_FAIL;
+ }
+#endif
- RefPtr<Stream> stdinStream, stdoutStream;
+ return compileRes;
+}
- Process::getStdStream(Process::StreamType::StdIn, stdinStream);
- Process::getStdStream(Process::StreamType::StdOut, stdoutStream);
+} // namespace SlangCTool
- RefPtr<BufferedReadStream> readStream(new BufferedReadStream(stdinStream));
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! TestServer !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
- m_connection = new HTTPPacketConnection(readStream, stdoutStream);
+SlangResult TestServer::init(int argc, const char* const* argv)
+{
+ m_exePath = argv[0];
- m_sourceManager.initialize(nullptr, nullptr);
- m_diagnosticSink.init(&m_sourceManager, &JSONLexer::calcLexemeLocation);
+ String canonicalPath;
+ if (SLANG_SUCCEEDED(Path::getCanonical(m_exePath, canonicalPath)))
+ {
+ m_exeDirectory = Path::getParentDirectory(canonicalPath);
+ }
+ else
+ {
+ m_exeDirectory = Path::getParentDirectory(m_exePath);
+ }
+ m_connection = new JSONRPCConnection;
+ SLANG_RETURN_ON_FAIL(m_connection->initWithStdStreams());
return SLANG_OK;
}
@@ -126,7 +190,7 @@ TestServer::~TestServer()
}
}
-slang::IGlobalSession* TestServer::getGlobalSession()
+slang::IGlobalSession* TestServer::getOrCreateGlobalSession()
{
if (!m_session)
{
@@ -151,7 +215,7 @@ ISlangSharedLibrary* TestServer::loadSharedLibrary(const String& name, Diagnosti
auto loader = DefaultSharedLibraryLoader::getSingleton();
- auto toolPath = Path::combine(m_exePath, name);
+ auto toolPath = Path::combine(m_exeDirectory, name);
ComPtr<ISlangSharedLibrary> sharedLibrary;
if (SLANG_FAILED(loader->loadSharedLibrary(toolPath.getBuffer(), sharedLibrary.writeRef())))
@@ -182,10 +246,10 @@ IUnitTestModule* TestServer::getUnitTestModule(const String& name, DiagnosticSin
return nullptr;
}
- UnownedStringSlice funcName = UnownedStringSlice::fromLiteral("slangUnitTestGetModule");
+ const char funcName[] = "slangUnitTestGetModule";
// get the unit test export name
- UnitTestGetModuleFunc getModuleFunc = (UnitTestGetModuleFunc)sharedLibrary->findFuncByName(funcName.begin());
+ UnitTestGetModuleFunc getModuleFunc = (UnitTestGetModuleFunc)sharedLibrary->findFuncByName(funcName);
if (!getModuleFunc)
{
if (sink)
@@ -211,6 +275,11 @@ IUnitTestModule* TestServer::getUnitTestModule(const String& name, DiagnosticSin
TestServer::InnerMainFunc TestServer::getToolFunction(const String& name, DiagnosticSink* sink)
{
+ if (name == "slangc")
+ {
+ return &SlangCTool::innerMain;
+ }
+
StringBuilder sharedLibToolBuilder;
sharedLibToolBuilder.append(name);
sharedLibToolBuilder.append("-tool");
@@ -221,9 +290,9 @@ TestServer::InnerMainFunc TestServer::getToolFunction(const String& name, Diagno
return nullptr;
}
- UnownedStringSlice funcName = UnownedStringSlice::fromLiteral("innerMain");
+ const char funcName[] = "innerMain";
- auto func = (InnerMainFunc)sharedLibrary->findFuncByName(funcName.begin());
+ auto func = (InnerMainFunc)sharedLibrary->findFuncByName(funcName);
if (!func && sink)
{
sink->diagnose(SourceLoc(), ServerDiagnostics::unableToFindFunctionInSharedLibrary, funcName);
@@ -232,79 +301,54 @@ TestServer::InnerMainFunc TestServer::getToolFunction(const String& name, Diagno
return func;
}
-SlangResult TestServer::_writeResponse(JSONContainer* container, const JSONValue& root)
-{
- // TODO(JS): We may want a non indented style, to reduce size
- JSONWriter writer(JSONWriter::IndentationStyle::Allman);
- container->traverseRecursively(root, &writer);
- const StringBuilder& builder = writer.getBuilder();
- return m_connection->write(builder.getBuffer(), builder.getLength());
-}
-
SlangResult TestServer::_executeSingle()
{
// Block waiting for content (or error/closed)
SLANG_RETURN_ON_FAIL(m_connection->waitForResult());
- // If we don't have content, we can quit for now
- if (!m_connection->hasContent())
+ // If we don't have a message, we can quit for now
+ if (!m_connection->hasMessage())
{
return SLANG_OK;
}
- auto content = m_connection->getContent();
-
- UnownedStringSlice slice((const char*)content.begin(), content.getCount());
-
- // Reset for parse
- m_sourceManager.reset();
- m_diagnosticSink.reset();
-
- JSONContainer container(&m_sourceManager);
-
- // Parse as RPC JSON
- JSONValue root;
+ const JSONRPCMessageType msgType = m_connection->getMessageType();
+ switch (msgType)
{
- SlangResult res = JSONRPCUtil::parseJSON(slice, &container, &m_diagnosticSink, root);
- // Consume that content/packet
- m_connection->consumeContent();
-
- if (SLANG_FAILED(res))
+ case JSONRPCMessageType::Call:
{
- return _writeResponse(&container, JSONRPCUtil::createErrorResponse(&container, JSONRPCUtil::ErrorCode::InvalidRequest, UnownedStringSlice::fromLiteral("Unable to parse JSON")));
+ JSONRPCCall call;
+ SLANG_RETURN_ON_FAIL(m_connection->getRPCOrSendError(&call));
+
+ // Do different things
+ if (call.method == TestServerProtocol::QuitArgs::g_methodName)
+ {
+ m_quit = true;
+ return SLANG_OK;
+ }
+ else if (call.method == TestServerProtocol::ExecuteUnitTestArgs::g_methodName)
+ {
+ SLANG_RETURN_ON_FAIL(_executeUnitTest(call));
+ return SLANG_OK;
+ }
+ else if (call.method == TestServerProtocol::ExecuteToolTestArgs::g_methodName)
+ {
+ SLANG_RETURN_ON_FAIL(_executeTool(call));
+ break;
+ }
+ else
+ {
+ return m_connection->sendError(JSONRPC::ErrorCode::MethodNotFound);
+ }
}
- }
-
- JSONRPCUtil::Call call;
- {
- SlangResult res = JSONRPCUtil::parseCall(&container, root, call);
- if (SLANG_FAILED(res))
+ default:
{
- return _writeResponse(&container, JSONRPCUtil::createErrorResponse(&container, Index(JSONRPCUtil::ErrorCode::InvalidRequest), UnownedStringSlice::fromLiteral("Cannot parse call")));
+ return m_connection->sendError(JSONRPC::ErrorCode::ParseError);
}
}
- const auto& method = call.method;
-
- // Do different things
- if (method == "quit")
- {
- m_quit = true;
- return SLANG_OK;
- }
- else if (method == "unitTest")
- {
- SLANG_RETURN_ON_FAIL(_executeUnitTest(&container, root));
- return SLANG_OK;
- }
- else if (method == "tool")
- {
- SLANG_RETURN_ON_FAIL(_executeTool(&container, root));
- return SLANG_OK;
- }
-
- return SLANG_FAIL;
+ return SLANG_OK;
}
static Index _findTestIndex(IUnitTestModule* testModule, const String& name)
@@ -322,23 +366,25 @@ static Index _findTestIndex(IUnitTestModule* testModule, const String& name)
return -1;
}
-SlangResult TestServer::_executeUnitTest(JSONContainer* container, const JSONValue& root)
+SlangResult TestServer::_executeUnitTest(const JSONRPCCall& call)
{
- String moduleName;
- String testName;
- Int enabledApis = 0;
+ TestServerProtocol::ExecuteUnitTestArgs args;
+ SLANG_RETURN_ON_FAIL(m_connection->toNativeOrSendError(call.params, &args));
- IUnitTestModule* testModule = getUnitTestModule(moduleName, &m_diagnosticSink);
+ auto sink = m_connection->getSink();
+
+ IUnitTestModule* testModule = getUnitTestModule(args.moduleName, m_connection->getSink());
if (!testModule)
{
- return SLANG_FAIL;
+ sink->diagnose(SourceLoc(), ServerDiagnostics::unableToFindUnitTestModule, args.moduleName);
+ return m_connection->sendError(JSONRPC::ErrorCode::InvalidParams);
}
- Index testIndex = _findTestIndex(testModule, moduleName);
+ const Index testIndex = _findTestIndex(testModule, args.testName);
if (testIndex < 0)
{
- m_diagnosticSink.diagnose(SourceLoc(), ServerDiagnostics::unableToFindTest, testName);
- return SLANG_FAIL;
+ sink->diagnose(SourceLoc(), ServerDiagnostics::unableToFindTest, args.testName);
+ return m_connection->sendError(JSONRPC::ErrorCode::InvalidParams);
}
TestReporter testReporter;
@@ -346,7 +392,7 @@ SlangResult TestServer::_executeUnitTest(JSONContainer* container, const JSONVal
testModule->setTestReporter(&testReporter);
// Assume we will used the shared session
- slang::IGlobalSession* session = getGlobalSession();
+ slang::IGlobalSession* session = getOrCreateGlobalSession();
if (!session)
{
return SLANG_FAIL;
@@ -355,8 +401,8 @@ SlangResult TestServer::_executeUnitTest(JSONContainer* container, const JSONVal
UnitTestContext unitTestContext;
unitTestContext.slangGlobalSession = session;
unitTestContext.workDirectory = "";
- unitTestContext.enabledApis = RenderApiFlags(enabledApis);
- unitTestContext.executableDirectory = m_exePath.getBuffer();
+ unitTestContext.enabledApis = RenderApiFlags(args.enabledApis);
+ unitTestContext.executableDirectory = m_exeDirectory.getBuffer();
auto testCount = testModule->getTestCount();
SLANG_ASSERT(testIndex >= 0 && testIndex < testCount);
@@ -372,73 +418,90 @@ SlangResult TestServer::_executeUnitTest(JSONContainer* container, const JSONVal
testReporter.m_failCount++;
}
+ TestServerProtocol::ExecutionResult result;
+ result.result = SLANG_OK;
+
if (testReporter.m_failCount > 0)
{
- // Write out to stderr...
- auto writers = StdWriters::createDefault();
- writers->getError().put(testReporter.m_buf.getUnownedSlice());
- return SLANG_FAIL;
+ result.result = SLANG_FAIL;
+ result.stdError = testReporter.m_buf.getUnownedSlice();
}
-
- if (testReporter.m_testCount == 0)
+ else if (testReporter.m_testCount == 0)
{
- return SLANG_E_NOT_AVAILABLE;
+ result.result = SLANG_E_NOT_AVAILABLE;
}
- return SLANG_OK;
+ result.returnCode = int32_t(TestToolUtil::getReturnCode(result.result));
+ return m_connection->sendResult(&result, m_connection->getMessageId());
}
-SlangResult TestServer::_executeTool(JSONContainer* container, const JSONValue& root)
+SlangResult TestServer::_executeTool(const JSONRPCCall& call)
{
- String toolName;
+ TestServerProtocol::ExecuteToolTestArgs args;
+
+ SLANG_RETURN_ON_FAIL(m_connection->toNativeOrSendError(call.params, &args));
+
+ auto sink = m_connection->getSink();
- auto func = getToolFunction(toolName, &m_diagnosticSink);
+ auto func = getToolFunction(args.toolName, sink);
if (!func)
{
- // Write out to diagnostics
- return SLANG_FAIL;
+ return m_connection->sendError(JSONRPC::ErrorCode::InvalidParams);
}
// Assume we will used the shared session
- slang::IGlobalSession* session = getGlobalSession();
+ slang::IGlobalSession* session = getOrCreateGlobalSession();
if (!session)
{
return SLANG_FAIL;
}
- // Get the args list
-
// Work out the args sent to the shared library
- List<const char*> args;
+ List<const char*> toolArgs;
-
- RefPtr<StdWriters> stdWriters = StdWriters::createDefault();
+ // Add the 'exe' name
+ toolArgs.add(args.toolName.getBuffer());
- const SlangResult res = func(stdWriters, session, int(args.getCount()), args.begin());
- if (SLANG_FAILED(res))
+ // Add the args
+ for (const auto& arg : args.args)
{
- return res;
+ toolArgs.add(arg.getBuffer());
}
- return res;
-}
+ StdWriters stdWriters;
+ StringBuilder stdOut;
+ StringBuilder stdError;
+
+ // Make writer/s act as if they are the console.
+ RefPtr<StringWriter> stdOutWriter(new StringWriter(&stdOut, WriterFlag::IsConsole));
+ RefPtr<StringWriter> stdErrorWriter(new StringWriter(&stdError, WriterFlag::IsConsole));
+
+ stdWriters.setWriter(SLANG_WRITER_CHANNEL_STD_ERROR, stdErrorWriter);
+ stdWriters.setWriter(SLANG_WRITER_CHANNEL_STD_OUTPUT, stdOutWriter);
+
+ // HACK, to make behavior the same as previously
+ if (args.toolName== "slangc")
+ {
+ stdWriters.setWriter(SLANG_WRITER_CHANNEL_DIAGNOSTIC, stdErrorWriter);
+ }
+
+ const SlangResult funcRes = func(&stdWriters, session, int(toolArgs.getCount()), toolArgs.begin());
+
+ TestServerProtocol::ExecutionResult result;
+ result.result = funcRes;
+ result.stdError = stdError;
+ result.stdOut = stdOut;
+
+ result.returnCode = int32_t(TestToolUtil::getReturnCode(result.result));
+ return m_connection->sendResult(&result);
+}
SlangResult TestServer::execute()
{
- DiagnosticSink diagnosticSink;
-
while (m_connection->isActive() && !m_quit)
{
- SlangResult res = _executeSingle();
- if (m_quit)
- {
- break;
- }
-
- if (SLANG_FAILED(res))
- {
- // Return a result
- }
+ // Failure doesn't make the execution terminate
+ const SlangResult res = _executeSingle();
}
return SLANG_OK;