Skip to content

Commit a07aa44

Browse files
authored
Merge pull request #597 from OpenVADL/wip/vadl-lsp
lsp: Basic implementation of language server
2 parents 9ce1430 + 196da41 commit a07aa44

File tree

9 files changed

+1112
-0
lines changed

9 files changed

+1112
-0
lines changed

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ rootProject.name = "open-vadl"
88
include("vadl")
99
include("java-annotations")
1010
include("vadl-cli")
11+
include("vadl-lsp")
1112
include("vadl-test")

vadl-lsp/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# OpenVADL Language Server
2+
3+
This is usually bundled into an IDE-specific extension.
4+
5+
## Running
6+
7+
The language server can be started manually using gradle:
8+
9+
`./gradlew :vadl-cli:run`
10+
11+
The server will wait for a connection on local TCP port 10999.
12+
Note that the server will serve one single client and shut down when the client disconnects.
13+
14+
For instructions on how to use our vscode extension with this language server, see [its wiki](https://github.com/OpenVADL/vscode-openvadl/wiki/Language-server-development-notes).
15+
16+
## Building
17+
18+
TBD

vadl-lsp/build.gradle.kts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
plugins {
2+
application
3+
}
4+
5+
group = "vadl"
6+
version = "unspecified"
7+
8+
repositories {
9+
mavenCentral()
10+
}
11+
12+
dependencies {
13+
implementation(project(":vadl"))
14+
implementation("org.eclipse.lsp4j:org.eclipse.lsp4j:0.24.0")
15+
implementation("ch.qos.logback:logback-classic:1.5.13")
16+
17+
testImplementation(platform("org.junit:junit-bom:5.10.0"))
18+
testImplementation("org.junit.jupiter:junit-jupiter")
19+
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
20+
}
21+
22+
application {
23+
applicationName = "openvadl-lsp"
24+
mainClass.set("vadl.lsp.Main")
25+
}
26+
27+
tasks.test {
28+
useJUnitPlatform()
29+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<configuration>
2+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
3+
<encoder>
4+
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %msg%n</pattern>
5+
</encoder>
6+
</appender>
7+
8+
<root level="DEBUG">
9+
<appender-ref ref="STDOUT"/>
10+
</root>
11+
</configuration>
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// SPDX-FileCopyrightText : © 2025 TU Wien <[email protected]>
2+
// SPDX-License-Identifier: GPL-3.0-or-later
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
package vadl.ast;
18+
19+
import java.io.ByteArrayInputStream;
20+
import java.lang.reflect.Field;
21+
import java.lang.reflect.Modifier;
22+
import java.nio.charset.StandardCharsets;
23+
import java.util.ArrayList;
24+
import java.util.List;
25+
import java.util.Map;
26+
import org.eclipse.lsp4j.SemanticTokenTypes;
27+
28+
/**
29+
* Tokenizer used by the language server. Provides LSP semantic tokens.
30+
* Must be in this package, otherwise it couldn't access {@code Token}.
31+
*/
32+
public final class LspTokenizer {
33+
34+
private final Map<String, Integer> tokenTypesMap;
35+
@SuppressWarnings("unused")
36+
private final Map<String, Integer> tokenModifiersMap;
37+
38+
/**
39+
* Maps VADL Scanner Token Kinds to LSP token types.
40+
*/
41+
private static final String[] tokenKindsMap;
42+
43+
static {
44+
// Generate tokenKindsMap
45+
tokenKindsMap = new String[Parser.maxT + 1];
46+
47+
// Hardcoded mappings:
48+
tokenKindsMap[Parser._hexLit] = SemanticTokenTypes.Number;
49+
tokenKindsMap[Parser._binLit] = SemanticTokenTypes.Number;
50+
tokenKindsMap[Parser._decLit] = SemanticTokenTypes.Number;
51+
tokenKindsMap[Parser._identifierToken] = SemanticTokenTypes.Variable;
52+
tokenKindsMap[Parser._string] = SemanticTokenTypes.String;
53+
54+
// Look through all known token kinds
55+
for (Field field : Parser.class.getDeclaredFields()) {
56+
var m = field.getModifiers();
57+
if (!Modifier.isPublic(m) || !Modifier.isStatic(m) || !Modifier.isFinal(m)
58+
|| !int.class.isAssignableFrom(field.getType())) {
59+
continue;
60+
}
61+
var name = field.getName();
62+
if (!name.startsWith("_")) {
63+
continue;
64+
}
65+
int kind;
66+
try {
67+
kind = field.getInt(null);
68+
} catch (IllegalAccessException e) {
69+
continue;
70+
}
71+
72+
if (tokenKindsMap[kind] != null) {
73+
// This mapping has already been set above
74+
continue;
75+
}
76+
// Operators according to ParserUtils / Token name "SYM_*"
77+
if (ParserUtils.BIN_OPS[kind] || ParserUtils.UN_OPS[kind] || name.startsWith("_SYM_")) {
78+
tokenKindsMap[kind] = SemanticTokenTypes.Operator;
79+
continue;
80+
}
81+
// Token name "T_*"
82+
if (name.startsWith("_T_")) {
83+
// Don't know what to map these to
84+
continue;
85+
}
86+
// Everything else should be Keyword
87+
tokenKindsMap[kind] = SemanticTokenTypes.Keyword;
88+
}
89+
}
90+
91+
92+
/**
93+
* Creates a new tokenizer.
94+
*
95+
* @param tokenTypesMap Maps Semantic token types to their integer index in the legend (which is
96+
* part of server capabilities). This is required for encoding
97+
* semanticTokens responses. Should only contain types that the client
98+
* supports.
99+
* @param tokenModifiersMap Maps Semantic token modifiers to their integer index in the legend
100+
* (which is part of server capabilities). This is required for encoding
101+
* semanticTokens responses. Should only contain modifiers that the
102+
* client supports.
103+
*/
104+
public LspTokenizer(Map<String, Integer> tokenTypesMap, Map<String, Integer> tokenModifiersMap) {
105+
this.tokenTypesMap = tokenTypesMap;
106+
this.tokenModifiersMap = tokenModifiersMap;
107+
}
108+
109+
/**
110+
* Returns LSP Tokens for the given source code.
111+
*
112+
* @param content of a VADL source code file
113+
* @return Token list encoded for a semanticTokens response. Note: deltaStart and length are
114+
* calculated for UTF-8 encoding.
115+
*/
116+
public List<Integer> getTokens(String content) {
117+
Scanner scanner = new Scanner(
118+
new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))
119+
);
120+
121+
// Note: We assume that all Tokens are single-line, i.e. no need to split them up at line
122+
// boundaries for LSP. So far, the only elements in OpenVADL that may span multiple lines
123+
// are comments, but the CocoR Scanner doesn't produce Tokens for those anyway.
124+
125+
List<Integer> lspTokens = new ArrayList<>();
126+
int previousLine = 1; // Token.line starts at 1
127+
int previousCol = 1; // Same for Token.col
128+
for (Token t = scanner.Scan(); t.kind != Parser._EOF; t = scanner.Scan()) {
129+
int tokenType = getTokenTypeFromScannerKind(t.kind);
130+
if (tokenType < 0) {
131+
continue;
132+
}
133+
134+
int deltaLine = t.line - previousLine;
135+
previousLine = t.line;
136+
137+
if (deltaLine != 0) {
138+
previousCol = 1;
139+
}
140+
int deltaStart = t.col - previousCol;
141+
previousCol = t.col;
142+
143+
// deltaLine, deltaStart, length, tokenType, tokenModifiers
144+
lspTokens.add(deltaLine);
145+
lspTokens.add(deltaStart);
146+
lspTokens.add(t.val.length());
147+
lspTokens.add(tokenType);
148+
lspTokens.add(0);
149+
}
150+
return lspTokens;
151+
}
152+
153+
private int getTokenTypeFromScannerKind(int kind) {
154+
return tokenTypesMap.getOrDefault(tokenKindsMap[kind], -1);
155+
}
156+
}

0 commit comments

Comments
 (0)