summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build/visual-studio/compiler-core/compiler-core.vcxproj2
-rw-r--r--build/visual-studio/compiler-core/compiler-core.vcxproj.filters6
-rw-r--r--build/visual-studio/slang/slang.vcxproj2
-rw-r--r--build/visual-studio/slang/slang.vcxproj.filters6
-rw-r--r--source/compiler-core/slang-json-native.cpp2
-rw-r--r--source/compiler-core/slang-language-server-protocol.cpp (renamed from source/slang/slang-language-server-protocol.cpp)38
-rw-r--r--source/compiler-core/slang-language-server-protocol.h (renamed from source/slang/slang-language-server-protocol.h)22
-rw-r--r--source/core/slang-io.cpp86
-rw-r--r--source/core/slang-io.h17
-rw-r--r--source/core/slang-string-util.cpp24
-rw-r--r--source/core/slang-string-util.h5
-rw-r--r--source/slang/slang-check-modifier.cpp2
-rw-r--r--source/slang/slang-language-server.cpp2
-rw-r--r--source/slang/slang-workspace-version.cpp96
-rw-r--r--source/slang/slang-workspace-version.h22
-rw-r--r--tests/language-server/robustness-1.slang11
-rw-r--r--tests/language-server/robustness-1.slang.expected.txt9
-rw-r--r--tests/language-server/robustness-2.slang16
-rw-r--r--tests/language-server/robustness-2.slang.expected.txt12
-rw-r--r--tests/language-server/robustness-3.slang12
-rw-r--r--tests/language-server/robustness-3.slang.expected.txt12
-rw-r--r--tests/language-server/robustness-4.slang5
-rw-r--r--tests/language-server/robustness-4.slang.expected.txt3
-rw-r--r--tests/language-server/smoke.slang33
-rw-r--r--tests/language-server/smoke.slang.expected.txt25
-rw-r--r--tests/language-server/vector-member.slang7
-rw-r--r--tests/language-server/vector-member.slang.expected.txt6
-rw-r--r--tools/slang-test/slang-test-main.cpp273
-rw-r--r--tools/slang-test/test-context.cpp24
-rw-r--r--tools/slang-test/test-context.h1
30 files changed, 625 insertions, 156 deletions
diff --git a/build/visual-studio/compiler-core/compiler-core.vcxproj b/build/visual-studio/compiler-core/compiler-core.vcxproj
index 48426f392..8c47086a7 100644
--- a/build/visual-studio/compiler-core/compiler-core.vcxproj
+++ b/build/visual-studio/compiler-core/compiler-core.vcxproj
@@ -294,6 +294,7 @@
<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-language-server-protocol.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-lexer-diagnostic-defs.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-lexer.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-llvm-compiler.h" />
@@ -328,6 +329,7 @@
<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-language-server-protocol.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-lexer.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-llvm-compiler.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-name-convention-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 2db3e73b1..1fc409048 100644
--- a/build/visual-studio/compiler-core/compiler-core.vcxproj.filters
+++ b/build/visual-studio/compiler-core/compiler-core.vcxproj.filters
@@ -69,6 +69,9 @@
<ClInclude Include="..\..\..\source\compiler-core\slang-json-value.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\compiler-core\slang-language-server-protocol.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\compiler-core\slang-lexer-diagnostic-defs.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -167,6 +170,9 @@
<ClCompile Include="..\..\..\source\compiler-core\slang-json-value.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\source\compiler-core\slang-language-server-protocol.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\source\compiler-core\slang-lexer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/build/visual-studio/slang/slang.vcxproj b/build/visual-studio/slang/slang.vcxproj
index dffc48700..a2ba8be7a 100644
--- a/build/visual-studio/slang/slang.vcxproj
+++ b/build/visual-studio/slang/slang.vcxproj
@@ -408,7 +408,6 @@ IF EXIST ..\..\..\external\slang-binaries\bin\windows-aarch64\slang-glslang.dll\
<ClInclude Include="..\..\..\source\slang\slang-ir.h" />
<ClInclude Include="..\..\..\source\slang\slang-language-server-ast-lookup.h" />
<ClInclude Include="..\..\..\source\slang\slang-language-server-collect-member.h" />
- <ClInclude Include="..\..\..\source\slang\slang-language-server-protocol.h" />
<ClInclude Include="..\..\..\source\slang\slang-language-server-semantic-tokens.h" />
<ClInclude Include="..\..\..\source\slang\slang-language-server.h" />
<ClInclude Include="..\..\..\source\slang\slang-legalize-types.h" />
@@ -558,7 +557,6 @@ IF EXIST ..\..\..\external\slang-binaries\bin\windows-aarch64\slang-glslang.dll\
<ClCompile Include="..\..\..\source\slang\slang-ir.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-language-server-ast-lookup.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-language-server-collect-member.cpp" />
- <ClCompile Include="..\..\..\source\slang\slang-language-server-protocol.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-language-server-semantic-tokens.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-language-server.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-legalize-types.cpp" />
diff --git a/build/visual-studio/slang/slang.vcxproj.filters b/build/visual-studio/slang/slang.vcxproj.filters
index 8721de464..9784bfef5 100644
--- a/build/visual-studio/slang/slang.vcxproj.filters
+++ b/build/visual-studio/slang/slang.vcxproj.filters
@@ -321,9 +321,6 @@
<ClInclude Include="..\..\..\source\slang\slang-language-server-collect-member.h">
<Filter>Header Files</Filter>
</ClInclude>
- <ClInclude Include="..\..\..\source\slang\slang-language-server-protocol.h">
- <Filter>Header Files</Filter>
- </ClInclude>
<ClInclude Include="..\..\..\source\slang\slang-language-server-semantic-tokens.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -767,9 +764,6 @@
<ClCompile Include="..\..\..\source\slang\slang-language-server-collect-member.cpp">
<Filter>Source Files</Filter>
</ClCompile>
- <ClCompile Include="..\..\..\source\slang\slang-language-server-protocol.cpp">
- <Filter>Source Files</Filter>
- </ClCompile>
<ClCompile Include="..\..\..\source\slang\slang-language-server-semantic-tokens.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/source/compiler-core/slang-json-native.cpp b/source/compiler-core/slang-json-native.cpp
index d268fffc2..c7a90df4c 100644
--- a/source/compiler-core/slang-json-native.cpp
+++ b/source/compiler-core/slang-json-native.cpp
@@ -308,7 +308,7 @@ SlangResult NativeToJSONConverter::_structToJSON(const StructRttiInfo* structRtt
// Do the super class first
if (structRttiInfo->m_super)
{
- SLANG_RETURN_ON_FAIL(_structToJSON(structRttiInfo, src, outPairs));
+ SLANG_RETURN_ON_FAIL(_structToJSON(structRttiInfo->m_super, src, outPairs));
}
const Byte* base = (const Byte*)src;
diff --git a/source/slang/slang-language-server-protocol.cpp b/source/compiler-core/slang-language-server-protocol.cpp
index c557be7ec..3e8907b9a 100644
--- a/source/slang/slang-language-server-protocol.cpp
+++ b/source/compiler-core/slang-language-server-protocol.cpp
@@ -15,14 +15,23 @@ static const StructRttiInfo _makeTextDocumentSyncOptionsRtti()
}
const StructRttiInfo TextDocumentSyncOptions::g_rttiInfo = _makeTextDocumentSyncOptionsRtti();
+static const StructRttiInfo _makeWorkDoneProgressParamsRtti()
+{
+ WorkDoneProgressParams obj;
+ StructRttiBuilder builder(&obj, "LanguageServerProtocol::WorkDoneProgressParams", nullptr);
+ builder.addField("workDoneToken", &obj.workDoneToken, StructRttiInfo::Flag::Optional);
+ builder.ignoreUnknownFields();
+ return builder.make();
+}
+const StructRttiInfo WorkDoneProgressParams::g_rttiInfo = _makeWorkDoneProgressParamsRtti();
+
static const StructRttiInfo _makeCompletionOptionsRtti()
{
CompletionOptions obj;
- StructRttiBuilder builder(&obj, "LanguageServerProtocol::CompletionOptions", nullptr);
+ StructRttiBuilder builder(&obj, "LanguageServerProtocol::CompletionOptions", &WorkDoneProgressParams::g_rttiInfo);
builder.addField("triggerCharacters", &obj.triggerCharacters);
builder.addField("resolveProvider", &obj.resolveProvider);
builder.addField("allCommitCharacters", &obj.allCommitCharacters);
- builder.addField("workDoneToken", &obj.workDoneToken);
builder.ignoreUnknownFields();
return builder.make();
}
@@ -314,23 +323,12 @@ static const StructRttiInfo _makeTextDocumentPositionParamsRtti()
}
const StructRttiInfo TextDocumentPositionParams::g_rttiInfo = _makeTextDocumentPositionParamsRtti();
-static const StructRttiInfo _makeWorkDoneProgressParamsRtti()
-{
- WorkDoneProgressParams obj;
- StructRttiBuilder builder(&obj, "LanguageServerProtocol::WorkDoneProgressParams", nullptr);
- builder.addField("workDoneToken", &obj.workDoneToken, StructRttiInfo::Flag::Optional);
- builder.ignoreUnknownFields();
- return builder.make();
-}
-const StructRttiInfo WorkDoneProgressParams::g_rttiInfo = _makeWorkDoneProgressParamsRtti();
-
static const StructRttiInfo _makeHoverParamsRtti()
{
HoverParams obj;
- StructRttiBuilder builder(&obj, "LanguageServerProtocol::HoverParams", nullptr);
+ StructRttiBuilder builder(&obj, "LanguageServerProtocol::HoverParams", &WorkDoneProgressParams::g_rttiInfo);
builder.addField("textDocument", &obj.textDocument);
builder.addField("position", &obj.position);
- builder.addField("workDoneToken", &obj, StructRttiInfo::Flag::Optional);
builder.ignoreUnknownFields();
return builder.make();
}
@@ -363,10 +361,9 @@ const StructRttiInfo Hover::g_rttiInfo = _makeHoverRtti();
static const StructRttiInfo _makeDefinitionParamsRtti()
{
DefinitionParams obj;
- StructRttiBuilder builder(&obj, "LanguageServerProtocol::DefinitionParams", nullptr);
+ StructRttiBuilder builder(&obj, "LanguageServerProtocol::DefinitionParams", &WorkDoneProgressParams::g_rttiInfo);
builder.addField("textDocument", &obj.textDocument);
builder.addField("position", &obj.position);
- builder.addField("workDoneToken", &obj, StructRttiInfo::Flag::Optional);
builder.ignoreUnknownFields();
return builder.make();
}
@@ -377,10 +374,9 @@ const UnownedStringSlice DefinitionParams::methodName =
static const StructRttiInfo _makeCompletionParamsRtti()
{
CompletionParams obj;
- StructRttiBuilder builder(&obj, "LanguageServerProtocol::CompletionParams", nullptr);
+ StructRttiBuilder builder(&obj, "LanguageServerProtocol::CompletionParams", &WorkDoneProgressParams::g_rttiInfo);
builder.addField("textDocument", &obj.textDocument);
builder.addField("position", &obj.position);
- builder.addField("workDoneToken", &obj, StructRttiInfo::Flag::Optional);
builder.ignoreUnknownFields();
return builder.make();
}
@@ -406,9 +402,8 @@ const StructRttiInfo CompletionItem::g_rttiInfo = _makeCompletionItemRtti();
static const StructRttiInfo _makeSemanticTokensParamsRtti()
{
SemanticTokensParams obj;
- StructRttiBuilder builder(&obj, "LanguageServerProtocol::SemanticTokensParams", nullptr);
+ StructRttiBuilder builder(&obj, "LanguageServerProtocol::SemanticTokensParams", &WorkDoneProgressParams::g_rttiInfo);
builder.addField("textDocument", &obj.textDocument);
- builder.addField("workDoneToken", &obj.workDoneToken, StructRttiInfo::Flag::Optional);
builder.ignoreUnknownFields();
return builder.make();
}
@@ -430,10 +425,9 @@ const StructRttiInfo SemanticTokens::g_rttiInfo = _makeSemanticTokensRtti();
static const StructRttiInfo _makeSignatureHelpParamsRtti()
{
SignatureHelpParams obj;
- StructRttiBuilder builder(&obj, "LanguageServerProtocol::SignatureHelpParams", nullptr);
+ StructRttiBuilder builder(&obj, "LanguageServerProtocol::SignatureHelpParams", &WorkDoneProgressParams::g_rttiInfo);
builder.addField("textDocument", &obj.textDocument);
builder.addField("position", &obj.position);
- builder.addField("workDoneToken", &obj.workDoneToken, StructRttiInfo::Flag::Optional);
builder.ignoreUnknownFields();
return builder.make();
}
diff --git a/source/slang/slang-language-server-protocol.h b/source/compiler-core/slang-language-server-protocol.h
index 29fbaa701..a23ba0130 100644
--- a/source/slang/slang-language-server-protocol.h
+++ b/source/compiler-core/slang-language-server-protocol.h
@@ -245,13 +245,11 @@ struct InitializeResult
static const StructRttiInfo g_rttiInfo;
};
-struct ShutdownParams
-{
+struct ShutdownParams {
static const UnownedStringSlice methodName;
};
-struct ExitParams
-{
+struct ExitParams {
static const UnownedStringSlice methodName;
};
@@ -377,16 +375,16 @@ struct TextDocumentPositionParams
};
struct HoverParams
- : TextDocumentPositionParams
- , WorkDoneProgressParams
+ : WorkDoneProgressParams
+ ,TextDocumentPositionParams
{
static const StructRttiInfo g_rttiInfo;
static const UnownedStringSlice methodName;
};
struct DefinitionParams
- : TextDocumentPositionParams
- , WorkDoneProgressParams
+ : WorkDoneProgressParams
+ , TextDocumentPositionParams
{
static const StructRttiInfo g_rttiInfo;
static const UnownedStringSlice methodName;
@@ -424,8 +422,8 @@ struct Hover
};
struct CompletionParams
- : TextDocumentPositionParams
- , WorkDoneProgressParams
+ : WorkDoneProgressParams
+ , TextDocumentPositionParams
{
static const StructRttiInfo g_rttiInfo;
static const UnownedStringSlice methodName;
@@ -532,8 +530,8 @@ struct SemanticTokens
};
struct SignatureHelpParams
- : TextDocumentPositionParams
- , WorkDoneProgressParams
+ : WorkDoneProgressParams
+ , TextDocumentPositionParams
{
static const UnownedStringSlice methodName;
diff --git a/source/core/slang-io.cpp b/source/core/slang-io.cpp
index 162e49980..c11ba85e0 100644
--- a/source/core/slang-io.cpp
+++ b/source/core/slang-io.cpp
@@ -4,6 +4,7 @@
#include "../../slang-com-helper.h"
#include "slang-string-util.h"
+#include "slang-char-util.h"
#ifndef __STDC__
# define __STDC__ 1
@@ -918,7 +919,90 @@ namespace Slang
return SLANG_OK;
}
+
+ String URI::getPath() const
+ {
+ Index startIndex = uri.indexOf("://");
+ if (startIndex == -1)
+ return String();
+ startIndex += 3;
+ Index endIndex = uri.indexOf('?');
+ if (endIndex == -1)
+ endIndex = uri.getLength();
+ StringBuilder sb;
+#if SLANG_WINDOWS_FAMILY
+ if (uri[startIndex] == '/')
+ startIndex++;
+#endif
+ for (Index i = startIndex; i < endIndex;)
+ {
+ auto ch = uri[i];
+ if (ch == '%')
+ {
+ Int charVal = CharUtil::getHexDigitValue(uri[i + 1]) * 16 +
+ CharUtil::getHexDigitValue(uri[i + 2]);
+ sb.appendChar((char)charVal);
+ i += 3;
+ }
+ else
+ {
+ sb.appendChar(uri[i]);
+ i++;
+ }
+ }
+ return sb.ProduceString();
+ }
+ StringSlice URI::getProtocol() const
+ {
+ Index separatorIndex = uri.indexOf("://");
+ if (separatorIndex != -1)
+ return uri.subString(0, separatorIndex);
+ return StringSlice();
+ }
-}
+ bool URI::isSafeURIChar(char ch)
+ {
+ return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
+ ch == '-' || ch == '_' || ch == '/' || ch == '.';
+ }
+ URI URI::fromLocalFilePath(UnownedStringSlice path)
+ {
+ URI uri;
+ StringBuilder sb;
+ sb << "file://";
+
+#if SLANG_WINDOWS_FAMILY
+ sb << "/";
+#endif
+
+ for (auto ch : path)
+ {
+ if (isSafeURIChar(ch))
+ {
+ sb.appendChar(ch);
+ }
+ else if (ch == '\\')
+ {
+ sb.appendChar('/');
+ }
+ else
+ {
+ char buffer[32];
+ int length = IntToAscii(buffer, (int)ch, 16);
+ ReverseInternalAscii(buffer, length);
+ sb << "%" << buffer;
+ }
+ }
+ return URI::fromString(sb.getUnownedSlice());
+ }
+
+ URI URI::fromString(UnownedStringSlice uriString)
+ {
+ URI uri;
+ uri.uri = uriString;
+ return uri;
+ }
+
+}
diff --git a/source/core/slang-io.h b/source/core/slang-io.h
index de761cf4b..51b1d45e5 100644
--- a/source/core/slang-io.h
+++ b/source/core/slang-io.h
@@ -157,6 +157,23 @@ namespace Slang
static SlangResult remove(const String& path);
};
+ struct URI
+ {
+ String uri;
+ bool operator==(const URI& other) const { return uri == other.uri; }
+ bool operator!=(const URI& other) const { return uri != other.uri; }
+
+ HashCode getHashCode() const { return uri.getHashCode(); }
+
+ bool isLocalFile() { return uri.startsWith("file://"); };
+ String getPath() const;
+ StringSlice getProtocol() const;
+
+ static URI fromLocalFilePath(UnownedStringSlice path);
+ static URI fromString(UnownedStringSlice uriString);
+ static bool isSafeURIChar(char ch);
+ };
+
// Helper class to clean up temporary files on dtor
class TemporaryFileSet: public RefObject
{
diff --git a/source/core/slang-string-util.cpp b/source/core/slang-string-util.cpp
index 5282f01a6..c4b654072 100644
--- a/source/core/slang-string-util.cpp
+++ b/source/core/slang-string-util.cpp
@@ -645,4 +645,28 @@ ComPtr<ISlangBlob> StringUtil::createStringBlob(const String& string)
return (cur == end) ? SLANG_OK : SLANG_FAIL;
}
+int StringUtil::parseIntAndAdvancePos(UnownedStringSlice text, Index& pos)
+{
+ int result = 0;
+ while (text[pos] == ' ' && pos < text.getLength())
+ {
+ pos++;
+ continue;
+ }
+ while (pos < text.getLength())
+ {
+ if (text[pos] >= '0' && text[pos] <= '9')
+ {
+ result *= 10;
+ result += text[pos] - '0';
+ pos++;
+ }
+ else
+ {
+ break;
+ }
+ }
+ return result;
+}
+
} // namespace Slang
diff --git a/source/core/slang-string-util.h b/source/core/slang-string-util.h
index 03f503ed2..b7576da1d 100644
--- a/source/core/slang-string-util.h
+++ b/source/core/slang-string-util.h
@@ -109,6 +109,11 @@ struct StringUtil
/// Convert into int64_t. Returns SLANG_OK on success.
static SlangResult parseInt64(const UnownedStringSlice& text, int64_t& out);
+
+ /// Parse an integer from text starting at pos until the end or the first non-digit char.
+ /// Modifies pos to the position where parsing ends.
+ /// Returns parsed integer.
+ static int parseIntAndAdvancePos(UnownedStringSlice text, Index& pos);
};
/* A helper class that allows parsing of lines from text with iteration. Uses StringUtil::extractLine for the actual underlying implementation. */
diff --git a/source/slang/slang-check-modifier.cpp b/source/slang/slang-check-modifier.cpp
index 1d9e30c95..176609106 100644
--- a/source/slang/slang-check-modifier.cpp
+++ b/source/slang/slang-check-modifier.cpp
@@ -80,6 +80,8 @@ namespace Slang
AttributeDecl* SemanticsVisitor::lookUpAttributeDecl(Name* attributeName, Scope* scope)
{
+ if (!attributeName)
+ return nullptr;
// We start by looking for an existing attribute matching
// the name `attributeName`.
//
diff --git a/source/slang/slang-language-server.cpp b/source/slang/slang-language-server.cpp
index dc3b4fe89..180e17ed9 100644
--- a/source/slang/slang-language-server.cpp
+++ b/source/slang/slang-language-server.cpp
@@ -13,7 +13,7 @@
#include "../core/slang-range.h"
#include "../../slang-com-helper.h"
#include "../compiler-core/slang-json-rpc-connection.h"
-#include "slang-language-server-protocol.h"
+#include "../compiler-core/slang-language-server-protocol.h"
#include "slang-language-server.h"
#include "slang-workspace-version.h"
#include "slang-language-server-ast-lookup.h"
diff --git a/source/slang/slang-workspace-version.cpp b/source/slang/slang-workspace-version.cpp
index 29404c2d0..1e92c31ca 100644
--- a/source/slang/slang-workspace-version.cpp
+++ b/source/slang/slang-workspace-version.cpp
@@ -1,7 +1,6 @@
#include "slang-workspace-version.h"
#include "../core/slang-io.h"
#include "../core/slang-file-system.h"
-#include "../core/slang-char-util.h"
#include "../compiler-core/slang-lexer.h"
namespace Slang
@@ -93,18 +92,18 @@ void parseDiagnostics(Dictionary<String, DocumentDiagnostics>& diagnostics, Stri
LanguageServerProtocol::Diagnostic diagnostic;
Index pos = lparentIndex + 1;
- int lineLoc = StringUtil::parseInt(line, pos);
+ int lineLoc = StringUtil::parseIntAndAdvancePos(line, pos);
if (lineLoc == 0)
lineLoc = 1;
diagnostic.range.end.line = diagnostic.range.start.line = lineLoc - 1;
pos++;
- int colLoc = StringUtil::parseInt(line, pos);
+ int colLoc = StringUtil::parseIntAndAdvancePos(line, pos);
if (colLoc == 0)
colLoc = 1;
diagnostic.range.end.character = diagnostic.range.start.character = colLoc - 1;
if (pos >= line.getLength())
continue;
- line = line.subString(colonIndex + 3, line.getLength());
+ line = line.tail(colonIndex + 3);
colonIndex = line.indexOf(':');
if (colonIndex == -1)
continue;
@@ -125,13 +124,13 @@ void parseDiagnostics(Dictionary<String, DocumentDiagnostics>& diagnostics, Stri
continue;
}
pos = line.indexOf(' ');
- diagnostic.code = StringUtil::parseInt(line, pos);
- diagnostic.message = line.subString(colonIndex + 2, line.getLength());
+ diagnostic.code = StringUtil::parseIntAndAdvancePos(line, pos);
+ diagnostic.message = line.tail(colonIndex + 2);
if (lineIndex + 1 < lines.getCount() && lines[lineIndex].startsWith("^+"))
{
lineIndex++;
pos = 2;
- auto tokenLength = StringUtil::parseInt(lines[lineIndex], pos);
+ auto tokenLength = StringUtil::parseIntAndAdvancePos(line, pos);
diagnostic.range.end.character += tokenLength;
}
diagnosticList.messages.Add(diagnostic);
@@ -192,89 +191,6 @@ void* Workspace::getInterface(const Guid& uuid)
return nullptr;
}
-String URI::getPath() const
-{
- Index startIndex = uri.indexOf("://");
- if (startIndex == -1) return String();
- startIndex += 3;
- Index endIndex = uri.indexOf('?');
- if (endIndex == -1)
- endIndex = uri.getLength();
- StringBuilder sb;
-#if SLANG_WINDOWS_FAMILY
- if (uri[startIndex] == '/')
- startIndex++;
-#endif
- for (Index i = startIndex; i < endIndex;)
- {
- auto ch = uri[i];
- if (ch == '%')
- {
- Int charVal = CharUtil::getHexDigitValue(uri[i + 1]) * 16 +
- CharUtil::getHexDigitValue(uri[i + 2]);
- sb.appendChar((char)charVal);
- i += 3;
- }
- else
- {
- sb.appendChar(uri[i]);
- i++;
- }
- }
- return sb.ProduceString();
-}
-
-StringSlice URI::getProtocol() const
-{
- Index separatorIndex = uri.indexOf("://");
- if (separatorIndex != -1)
- return uri.subString(0, separatorIndex);
- return StringSlice();
-}
-
-bool URI::isSafeURIChar(char ch)
-{
- return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
- ch == '-' || ch == '_' || ch == '/' || ch == '.';
-}
-
-URI URI::fromLocalFilePath(UnownedStringSlice path)
-{
- URI uri;
- StringBuilder sb;
- sb << "file://";
-
-#if SLANG_WINDOWS_FAMILY
- sb << "/";
-#endif
-
- for (auto ch : path)
- {
- if (isSafeURIChar(ch))
- {
- sb.appendChar(ch);
- }
- else if (ch == '\\')
- {
- sb.appendChar('/');
- }
- else
- {
- char buffer[32];
- int length = IntToAscii(buffer, (int)ch, 16);
- ReverseInternalAscii(buffer, length);
- sb << "%" << buffer;
- }
- }
- return URI::fromString(sb.getUnownedSlice());
-}
-
-URI URI::fromString(UnownedStringSlice uriString)
-{
- URI uri;
- uri.uri = uriString;
- return uri;
-}
void DocumentVersion::setText(const String& newText)
{
text = newText;
diff --git a/source/slang/slang-workspace-version.h b/source/slang/slang-workspace-version.h
index 58811ed7d..2aa2619f1 100644
--- a/source/slang/slang-workspace-version.h
+++ b/source/slang/slang-workspace-version.h
@@ -5,32 +5,12 @@
#include "../../slang.h"
#include "../core/slang-basic.h"
#include "../core/slang-com-object.h"
-#include "slang-language-server-protocol.h"
+#include "../compiler-core/slang-language-server-protocol.h"
#include "slang-compiler.h"
#include "slang-doc-ast.h"
namespace Slang
{
- struct URI
- {
- String uri;
- bool operator==(const URI& other) const
- {
- return uri == other.uri;
- }
- bool operator!=(const URI& other) const { return uri != other.uri; }
-
- HashCode getHashCode() const { return uri.getHashCode(); }
-
- bool isLocalFile() { return uri.startsWith("file://"); };
- String getPath() const;
- StringSlice getProtocol() const;
-
- static URI fromLocalFilePath(UnownedStringSlice path);
- static URI fromString(UnownedStringSlice uriString);
- static bool isSafeURIChar(char ch);
- };
-
class Workspace;
class DocumentVersion : public RefObject
diff --git a/tests/language-server/robustness-1.slang b/tests/language-server/robustness-1.slang
new file mode 100644
index 000000000..fa9036900
--- /dev/null
+++ b/tests/language-server/robustness-1.slang
@@ -0,0 +1,11 @@
+//TEST:LANG_SERVER:
+//
+// tests empty subscript argument.
+
+void main()
+{
+//HOVER:8,13
+ float3 arr[3];
+ arr[].;
+
+}
diff --git a/tests/language-server/robustness-1.slang.expected.txt b/tests/language-server/robustness-1.slang.expected.txt
new file mode 100644
index 000000000..4bfd5a31d
--- /dev/null
+++ b/tests/language-server/robustness-1.slang.expected.txt
@@ -0,0 +1,9 @@
+--------
+range: 7,11 - 7,14
+content:
+```
+vector<float,3>[3] arr
+```
+{REDACTED}.slang(8)
+
+
diff --git a/tests/language-server/robustness-2.slang b/tests/language-server/robustness-2.slang
new file mode 100644
index 000000000..bd73d25ed
--- /dev/null
+++ b/tests/language-server/robustness-2.slang
@@ -0,0 +1,16 @@
+//TEST:LANG_SERVER:
+//
+// tests broken generic parameter.
+
+void main()
+{
+ float3 x
+ vector<float,3> y;
+
+}
+
+enum Kind
+{
+//HOVER:15,6
+ Foo
+}
diff --git a/tests/language-server/robustness-2.slang.expected.txt b/tests/language-server/robustness-2.slang.expected.txt
new file mode 100644
index 000000000..2236aea91
--- /dev/null
+++ b/tests/language-server/robustness-2.slang.expected.txt
@@ -0,0 +1,12 @@
+--------
+range: 14,4 - 14,7
+content:
+```
+Kind Kind.Foo
+```
+
+
+
+{REDACTED}.slang(15)
+
+
diff --git a/tests/language-server/robustness-3.slang b/tests/language-server/robustness-3.slang
new file mode 100644
index 000000000..29835eb70
--- /dev/null
+++ b/tests/language-server/robustness-3.slang
@@ -0,0 +1,12 @@
+//TEST:LANG_SERVER:
+//
+// tests broken subscript decl.
+//HOVER:7,24
+struct MyType
+{
+ __subscript(int index)
+ {
+ get(){return index;}
+ se
+ }
+}
diff --git a/tests/language-server/robustness-3.slang.expected.txt b/tests/language-server/robustness-3.slang.expected.txt
new file mode 100644
index 000000000..000ad6add
--- /dev/null
+++ b/tests/language-server/robustness-3.slang.expected.txt
@@ -0,0 +1,12 @@
+--------
+range: 6,20 - 6,25
+content:
+```
+int index
+```
+
+
+
+{REDACTED}.slang(7)
+
+
diff --git a/tests/language-server/robustness-4.slang b/tests/language-server/robustness-4.slang
new file mode 100644
index 000000000..d1f110f5c
--- /dev/null
+++ b/tests/language-server/robustness-4.slang
@@ -0,0 +1,5 @@
+//TEST:LANG_SERVER:
+//
+//Broken syntax
+//HOVER:1,1
+[]
diff --git a/tests/language-server/robustness-4.slang.expected.txt b/tests/language-server/robustness-4.slang.expected.txt
new file mode 100644
index 000000000..dd2c29b31
--- /dev/null
+++ b/tests/language-server/robustness-4.slang.expected.txt
@@ -0,0 +1,3 @@
+--------
+null
+
diff --git a/tests/language-server/smoke.slang b/tests/language-server/smoke.slang
new file mode 100644
index 000000000..194902586
--- /dev/null
+++ b/tests/language-server/smoke.slang
@@ -0,0 +1,33 @@
+//TEST(smoke):LANG_SERVER:
+//COMPLETE:31,21
+//HOVER:25,30
+//SIGNATURE:25,40
+interface IFoo
+{
+ /**
+ Returns the sum of the contents.
+ */
+ int getSum();
+}
+
+struct MyType : IFoo
+{
+ int getSum() { return 0; }
+}
+
+struct Pair<T:IFoo, U: IFoo> : IFoo
+{
+ T first;
+ U second;
+ /**
+ Returns the sum of the contents.
+ */
+ int getSum() { return first.getSum() + second.getSum(); }
+}
+
+void m()
+{
+ Pair<MyType, Pair<MyType, MyType>> v;
+ v.first = v.second.first;
+
+}
diff --git a/tests/language-server/smoke.slang.expected.txt b/tests/language-server/smoke.slang.expected.txt
new file mode 100644
index 000000000..5f0bd3915
--- /dev/null
+++ b/tests/language-server/smoke.slang.expected.txt
@@ -0,0 +1,25 @@
+--------
+first: 6 ().;:,<>[]{}-*/%+=&|!
+second: 6 ().;:,<>[]{}-*/%+=&|!
+getSum: 2 ([
+--------
+range: 24,26 - 24,31
+content:
+```
+Pair.T Pair<Pair.T, Pair.U>.first
+```
+
+
+
+{REDACTED}.slang(20)
+
+--------
+activeParameter: 0
+activeSignature: 0
+func IFoo.getSum() -> int:
+
+Returns the sum of the contents.
+
+{REDACTED}.slang(10)
+
+
diff --git a/tests/language-server/vector-member.slang b/tests/language-server/vector-member.slang
new file mode 100644
index 000000000..5d624e56d
--- /dev/null
+++ b/tests/language-server/vector-member.slang
@@ -0,0 +1,7 @@
+//TEST:LANG_SERVER:
+void f()
+{
+ float4 v;
+//COMPLETE:6,7
+ v.
+}
diff --git a/tests/language-server/vector-member.slang.expected.txt b/tests/language-server/vector-member.slang.expected.txt
new file mode 100644
index 000000000..d93d9183f
--- /dev/null
+++ b/tests/language-server/vector-member.slang.expected.txt
@@ -0,0 +1,6 @@
+--------
+x: 6 float ().;:,<>[]{}-*/%+=&|!
+y: 6 float ().;:,<>[]{}-*/%+=&|!
+z: 6 float ().;:,<>[]{}-*/%+=&|!
+w: 6 float ().;:,<>[]{}-*/%+=&|!
+
diff --git a/tools/slang-test/slang-test-main.cpp b/tools/slang-test/slang-test-main.cpp
index f0d92b810..753ae5569 100644
--- a/tools/slang-test/slang-test-main.cpp
+++ b/tools/slang-test/slang-test-main.cpp
@@ -31,6 +31,7 @@
#include "../../source/compiler-core/slang-downstream-compiler.h"
#include "../../source/compiler-core/slang-nvrtc-compiler.h"
+#include "../../source/compiler-core/slang-language-server-protocol.h"
#define STB_IMAGE_IMPLEMENTATION
#include "external/stb/stb_image.h"
@@ -1329,6 +1330,276 @@ TestResult runDocTest(TestContext* context, TestInput& input)
return result;
}
+TestResult runLanguageServerTest(TestContext* context, TestInput& input)
+{
+ RefPtr<JSONRPCConnection> connection;
+ if (SLANG_FAILED(context->createLanguageServerJSONRPCConnection(connection)))
+ {
+ return TestResult::Fail;
+ }
+ if (context->isCollectingRequirements())
+ {
+ connection->sendCall(LanguageServerProtocol::ExitParams::methodName, JSONValue::makeInt(0));
+ return TestResult::Pass;
+ }
+ LanguageServerProtocol::InitializeParams initParams;
+ LanguageServerProtocol::WorkspaceFolder wsFolder;
+ wsFolder.name = "test";
+ String fullPath;
+ Path::getCanonical(input.filePath, fullPath);
+ wsFolder.uri = URI::fromLocalFilePath(Path::getParentDirectory(fullPath).getUnownedSlice()).uri;
+ initParams.workspaceFolders.add(wsFolder);
+ if (SLANG_FAILED(connection->sendCall(
+ LanguageServerProtocol::InitializeParams::methodName, &initParams, JSONValue::makeInt(0))))
+ {
+ return TestResult::Fail;
+ }
+ if (SLANG_FAILED(connection->waitForResult(-1)))
+ {
+ return TestResult::Fail;
+ }
+
+ LanguageServerProtocol::InitializeResult initResult;
+ if (SLANG_FAILED(connection->getMessage(&initResult)))
+ {
+ return TestResult::Fail;
+ }
+
+ // Send open document call.
+ String testFileContent;
+
+ if (SLANG_FAILED(File::readAllText(input.filePath, testFileContent)))
+ {
+ return TestResult::Fail;
+ }
+
+ LanguageServerProtocol::DidOpenTextDocumentParams openDocParams;
+ openDocParams.textDocument.version = 0;
+ openDocParams.textDocument.uri = URI::fromLocalFilePath(fullPath.getUnownedSlice()).uri;
+ openDocParams.textDocument.text = testFileContent;
+ connection->sendCall(
+ LanguageServerProtocol::DidOpenTextDocumentParams::methodName,
+ &openDocParams,
+ JSONValue::makeInt(1));
+ List<LanguageServerProtocol::PublishDiagnosticsParams> diagnostics;
+ bool diagnosticsReceived = false;
+ auto waitForNonDiagnosticResponse = [&]() -> SlangResult
+ {
+ repeat:
+ if (SLANG_FAILED(connection->waitForResult(10000)))
+ return SLANG_FAIL;
+ if (connection->getMessageType() == JSONRPCMessageType::Call)
+ {
+ JSONRPCCall call;
+ connection->getRPC(&call);
+ if (call.method == "textDocument/publishDiagnostics")
+ {
+ diagnosticsReceived = true;
+ LanguageServerProtocol::PublishDiagnosticsParams arg;
+ if (SLANG_FAILED(connection->getMessage(&arg)))
+ return SLANG_FAIL;
+ diagnostics.add(arg);
+ goto repeat;
+ }
+ }
+ return SLANG_OK;
+ };
+
+ List<UnownedStringSlice> lines;
+ StringUtil::calcLines(testFileContent.getUnownedSlice(), lines);
+
+ StringBuilder actualOutputSB;
+ auto parseLocation = [&](UnownedStringSlice text, Index startPos, Int& linePos, Int& colPos)
+ {
+ linePos = StringUtil::parseIntAndAdvancePos(text.trimStart(), startPos);
+ startPos++;
+ colPos = StringUtil::parseIntAndAdvancePos(text.trimStart(), startPos);
+ return startPos;
+ };
+ int callId = 2;
+ for (auto line : lines)
+ {
+ if (line.startsWith("//COMPLETE:"))
+ {
+ auto arg = line.tail(UnownedStringSlice("//COMPLETE:").getLength());
+ Int linePos, colPos;
+ parseLocation(arg, 0, linePos, colPos);
+
+ LanguageServerProtocol::CompletionParams params;
+ params.position.line = int(linePos - 1);
+ params.position.character = int(colPos - 1);
+ params.textDocument.uri = openDocParams.textDocument.uri;
+ if (SLANG_FAILED(connection->sendCall(
+ LanguageServerProtocol::CompletionParams::methodName,
+ &params,
+ JSONValue::makeInt(callId++))))
+ {
+ return TestResult::Fail;
+ }
+ if (SLANG_FAILED(waitForNonDiagnosticResponse()))
+ return TestResult::Fail;
+ actualOutputSB << "--------\n";
+ LanguageServerProtocol::NullResponse nullResponse;
+ List<LanguageServerProtocol::CompletionItem> completionItems;
+ if (SLANG_SUCCEEDED(connection->getMessage(&nullResponse)))
+ {
+ actualOutputSB << "null\n";
+ }
+ else if (SLANG_SUCCEEDED(connection->getMessage(&completionItems)))
+ {
+ for (auto item : completionItems)
+ {
+ actualOutputSB << item.label << ": " << item.kind << " " << item.detail << " ";
+ for (auto ch : item.commitCharacters)
+ actualOutputSB << ch;
+ actualOutputSB << "\n";
+ }
+ }
+ }
+ else if (line.startsWith("//SIGNATURE:"))
+ {
+ auto arg = line.tail(UnownedStringSlice("//SIGNATURE:").getLength());
+ Int linePos, colPos;
+ parseLocation(arg, 0, linePos, colPos);
+
+ LanguageServerProtocol::SignatureHelpParams params;
+ params.position.line = int(linePos - 1);
+ params.position.character = int(colPos - 1);
+ params.textDocument.uri = openDocParams.textDocument.uri;
+ if (SLANG_FAILED(connection->sendCall(
+ LanguageServerProtocol::SignatureHelpParams::methodName,
+ &params,
+ JSONValue::makeInt(callId++))))
+ {
+ return TestResult::Fail;
+ }
+ if (SLANG_FAILED(waitForNonDiagnosticResponse()))
+ return TestResult::Fail;
+ actualOutputSB << "--------\n";
+ LanguageServerProtocol::NullResponse nullResponse;
+ LanguageServerProtocol::SignatureHelp sigInfo;
+ if (SLANG_SUCCEEDED(connection->getMessage(&nullResponse)))
+ {
+ actualOutputSB << "null\n";
+ }
+ else if (SLANG_SUCCEEDED(connection->getMessage(&sigInfo)))
+ {
+ actualOutputSB << "activeParameter: " << sigInfo.activeParameter << "\n";
+ actualOutputSB << "activeSignature: " << sigInfo.activeSignature << "\n";
+ for (auto item : sigInfo.signatures)
+ {
+ actualOutputSB << item.label << ":";
+ for (auto param : item.parameters)
+ {
+ actualOutputSB << " (" << param.label[0] << "," << param.label[1] << ")";
+ }
+ actualOutputSB << "\n";
+ actualOutputSB << item.documentation.value << "\n";
+ }
+ }
+ }
+ else if (line.startsWith("//HOVER:"))
+ {
+ auto arg = line.tail(UnownedStringSlice("//HOVER:").getLength());
+ Int linePos, colPos;
+ parseLocation(arg, 0, linePos, colPos);
+
+ LanguageServerProtocol::HoverParams params;
+ params.position.line = int(linePos - 1);
+ params.position.character = int(colPos - 1);
+ params.textDocument.uri = openDocParams.textDocument.uri;
+ if (SLANG_FAILED(connection->sendCall(
+ LanguageServerProtocol::HoverParams::methodName,
+ &params,
+ JSONValue::makeInt(callId++))))
+ {
+ return TestResult::Fail;
+ }
+ if (SLANG_FAILED(waitForNonDiagnosticResponse()))
+ return TestResult::Fail;
+ actualOutputSB << "--------\n";
+ LanguageServerProtocol::NullResponse nullResponse;
+ LanguageServerProtocol::Hover hover;
+ if (SLANG_SUCCEEDED(connection->getMessage(&nullResponse)))
+ {
+ actualOutputSB << "null\n";
+ }
+ else if (SLANG_SUCCEEDED(connection->getMessage(&hover)))
+ {
+ actualOutputSB << "range: " << hover.range.start.line << ","
+ << hover.range.start.character << " - " << hover.range.end.line
+ << "," << hover.range.end.character;
+ actualOutputSB << "\ncontent:\n" << hover.contents.value << "\n";
+ }
+ }
+ else if (line.startsWith("//DIAGNOSTICS"))
+ {
+ if (!diagnosticsReceived)
+ {
+ waitForNonDiagnosticResponse();
+ }
+ actualOutputSB << "--------\n";
+ for (auto item : diagnostics)
+ {
+ actualOutputSB << item.uri << "\n";
+ for (auto msg : item.diagnostics)
+ {
+ actualOutputSB << msg.range.start.line << "," << msg.range.start.character
+ << "-" << msg.range.end.line << "," << msg.range.end.character
+ << " " << msg.message;
+ }
+ }
+ }
+ }
+ connection->sendCall(LanguageServerProtocol::ExitParams::methodName, JSONValue::makeInt(0));
+
+ auto outputStem = input.outputStem;
+ String expectedOutputPath = outputStem + ".expected.txt";
+ String expectedOutput;
+
+ Slang::File::readAllText(expectedOutputPath, expectedOutput);
+ expectedOutput = expectedOutput.trim();
+
+ TestResult result = TestResult::Pass;
+
+ auto actualOutput = actualOutputSB.ProduceString();
+
+ // Redact absolute file names from actualOutput
+ List<UnownedStringSlice> outputLines;
+ StringUtil::calcLines(actualOutput.getUnownedSlice(), outputLines);
+ StringBuilder redactedSB;
+ for (auto line : outputLines)
+ {
+ Index extIdx = line.indexOf(UnownedStringSlice(".slang"));
+ if (extIdx == -1)
+ {
+ redactedSB << line << "\n";
+ continue;
+ }
+ redactedSB << "{REDACTED}" << line.tail(extIdx) << "\n";
+ }
+
+ actualOutput = redactedSB.ProduceString().trim();
+
+ if (!_areResultsEqual(input.testOptions->type, expectedOutput, actualOutput))
+ {
+ context->getTestReporter()->dumpOutputDifference(expectedOutput, actualOutput);
+ result = TestResult::Fail;
+ }
+
+ // If the test failed, then we write the actual output to a file
+ // so that we can easily diff it from the command line and
+ // diagnose the problem.
+ if (result == TestResult::Fail)
+ {
+ String actualOutputPath = outputStem + ".actual";
+ Slang::File::writeAllText(actualOutputPath, actualOutput);
+
+ context->getTestReporter()->dumpOutputDifference(expectedOutput, actualOutput);
+ }
+ return result;
+}
+
TestResult runSimpleTest(TestContext* context, TestInput& input)
{
// need to execute the stand-alone Slang compiler on the file, and compare its output to what we expect
@@ -2985,6 +3256,8 @@ static const TestCommandInfo s_testCommandInfos[] =
{ "PERFORMANCE_PROFILE", &runPerformanceProfile, 0 },
{ "COMPILE", &runCompile, 0 },
{ "DOC", &runDocTest, 0 },
+ { "LANG_SERVER", &runLanguageServerTest, 0},
+
};
const TestCommandInfo* _findTestCommandInfoByCommand(const UnownedStringSlice& name)
diff --git a/tools/slang-test/test-context.cpp b/tools/slang-test/test-context.cpp
index cdafbc30d..6bf7406e6 100644
--- a/tools/slang-test/test-context.cpp
+++ b/tools/slang-test/test-context.cpp
@@ -171,6 +171,30 @@ SlangResult TestContext::_createJSONRPCConnection(RefPtr<JSONRPCConnection>& out
return SLANG_OK;
}
+SlangResult TestContext::createLanguageServerJSONRPCConnection(RefPtr<JSONRPCConnection>& out)
+{
+ RefPtr<Process> process;
+
+ {
+ CommandLine cmdLine;
+ cmdLine.setExecutableLocation(ExecutableLocation(exeDirectoryPath, "slangd"));
+ SLANG_RETURN_ON_FAIL(Process::create(cmdLine, Process::Flag::AttachDebugger, process));
+ }
+
+ Stream* writeStream = process->getStream(StdStreamType::In);
+ RefPtr<BufferedReadStream> readStream(
+ new BufferedReadStream(process->getStream(StdStreamType::Out)));
+
+ RefPtr<HTTPPacketConnection> connection = new HTTPPacketConnection(readStream, writeStream);
+ RefPtr<JSONRPCConnection> rpcConnection = new JSONRPCConnection;
+
+ SLANG_RETURN_ON_FAIL(
+ rpcConnection->init(connection, JSONRPCConnection::CallStyle::Object, process));
+
+ out = rpcConnection;
+
+ return SLANG_OK;
+}
void TestContext::destroyRPCConnection()
{
diff --git a/tools/slang-test/test-context.h b/tools/slang-test/test-context.h
index adb3e5ad2..284206efb 100644
--- a/tools/slang-test/test-context.h
+++ b/tools/slang-test/test-context.h
@@ -162,6 +162,7 @@ class TestContext
void setTestReporter(TestReporter* reporter);
TestReporter* getTestReporter();
+ SlangResult createLanguageServerJSONRPCConnection(Slang::RefPtr<Slang::JSONRPCConnection>& out);
std::mutex mutex;