jaclang 0.6.5__py3-none-any.whl → 0.7.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jaclang might be problematic. Click here for more details.
- jaclang/compiler/absyntree.py +4 -36
- jaclang/compiler/compile.py +21 -0
- jaclang/compiler/parser.py +13 -4
- jaclang/compiler/passes/main/__init__.py +2 -2
- jaclang/compiler/passes/main/def_impl_match_pass.py +1 -5
- jaclang/compiler/passes/main/def_use_pass.py +14 -7
- jaclang/compiler/passes/main/import_pass.py +56 -10
- jaclang/compiler/passes/main/pyast_gen_pass.py +55 -2
- jaclang/compiler/passes/main/schedules.py +4 -3
- jaclang/compiler/passes/main/tests/fixtures/incautoimpl.jac +7 -0
- jaclang/compiler/passes/main/tests/test_decl_def_match_pass.py +4 -4
- jaclang/compiler/passes/main/tests/test_import_pass.py +13 -0
- jaclang/compiler/passes/tool/jac_formatter_pass.py +2 -2
- jaclang/compiler/passes/transform.py +13 -4
- jaclang/compiler/tests/fixtures/mod_doc_test.jac +3 -1
- jaclang/langserve/engine.py +375 -0
- jaclang/langserve/server.py +112 -74
- jaclang/langserve/tests/fixtures/circle.jac +73 -0
- jaclang/langserve/tests/fixtures/circle_err.jac +73 -0
- jaclang/langserve/tests/fixtures/circle_pure.impl.jac +28 -0
- jaclang/langserve/tests/fixtures/circle_pure.jac +34 -0
- jaclang/langserve/tests/fixtures/circle_pure_err.impl.jac +32 -0
- jaclang/langserve/tests/fixtures/circle_pure_err.jac +34 -0
- jaclang/langserve/tests/test_server.py +33 -1
- jaclang/langserve/utils.py +53 -16
- jaclang/plugin/default.py +1 -1
- jaclang/tests/fixtures/type_info.jac +1 -1
- jaclang/tests/test_cli.py +1 -1
- jaclang/utils/helpers.py +3 -5
- jaclang/utils/test.py +1 -1
- {jaclang-0.6.5.dist-info → jaclang-0.7.1.dist-info}/METADATA +8 -16
- {jaclang-0.6.5.dist-info → jaclang-0.7.1.dist-info}/RECORD +34 -28
- jaclang/compiler/tests/test_workspace.py +0 -93
- jaclang/compiler/workspace.py +0 -234
- {jaclang-0.6.5.dist-info → jaclang-0.7.1.dist-info}/WHEEL +0 -0
- {jaclang-0.6.5.dist-info → jaclang-0.7.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
"""Living Workspace of Jac project."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import IntEnum
|
|
6
|
+
from hashlib import md5
|
|
7
|
+
from typing import Optional, Sequence
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
import jaclang.compiler.absyntree as ast
|
|
11
|
+
from jaclang.compiler.compile import jac_ir_to_pass, jac_str_to_pass
|
|
12
|
+
from jaclang.compiler.parser import JacParser
|
|
13
|
+
from jaclang.compiler.passes import Pass
|
|
14
|
+
from jaclang.compiler.passes.main.schedules import type_checker_sched
|
|
15
|
+
from jaclang.compiler.passes.tool import FuseCommentsPass, JacFormatPass
|
|
16
|
+
from jaclang.compiler.passes.transform import Alert
|
|
17
|
+
from jaclang.langserve.utils import find_deepest_node_at_pos
|
|
18
|
+
from jaclang.vendor.pygls import uris
|
|
19
|
+
from jaclang.vendor.pygls.server import LanguageServer
|
|
20
|
+
|
|
21
|
+
import lsprotocol.types as lspt
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ALev(IntEnum):
|
|
25
|
+
"""Analysis Level."""
|
|
26
|
+
|
|
27
|
+
QUICK = 1
|
|
28
|
+
DEEP = 2
|
|
29
|
+
TYPE = 3
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ModuleInfo:
|
|
33
|
+
"""Module IR and Stats."""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
ir: ast.Module,
|
|
38
|
+
errors: Sequence[Alert],
|
|
39
|
+
warnings: Sequence[Alert],
|
|
40
|
+
alev: ALev,
|
|
41
|
+
parent: Optional[ModuleInfo] = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Initialize module info."""
|
|
44
|
+
self.ir = ir
|
|
45
|
+
self.errors = errors
|
|
46
|
+
self.warnings = warnings
|
|
47
|
+
self.alev = alev
|
|
48
|
+
self.parent: Optional[ModuleInfo] = parent
|
|
49
|
+
self.diagnostics = self.gen_diagnostics()
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def uri(self) -> str:
|
|
53
|
+
"""Return uri."""
|
|
54
|
+
return uris.from_fs_path(self.ir.loc.mod_path)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def has_syntax_error(self) -> bool:
|
|
58
|
+
"""Return if there are syntax errors."""
|
|
59
|
+
return len(self.errors) > 0 and self.alev == ALev.QUICK
|
|
60
|
+
|
|
61
|
+
def gen_diagnostics(self) -> list[lspt.Diagnostic]:
|
|
62
|
+
"""Return diagnostics."""
|
|
63
|
+
return [
|
|
64
|
+
lspt.Diagnostic(
|
|
65
|
+
range=lspt.Range(
|
|
66
|
+
start=lspt.Position(
|
|
67
|
+
line=error.loc.first_line - 1, character=error.loc.col_start - 1
|
|
68
|
+
),
|
|
69
|
+
end=lspt.Position(
|
|
70
|
+
line=error.loc.last_line - 1,
|
|
71
|
+
character=error.loc.col_end - 1,
|
|
72
|
+
),
|
|
73
|
+
),
|
|
74
|
+
message=error.msg,
|
|
75
|
+
severity=lspt.DiagnosticSeverity.Error,
|
|
76
|
+
)
|
|
77
|
+
for error in self.errors
|
|
78
|
+
] + [
|
|
79
|
+
lspt.Diagnostic(
|
|
80
|
+
range=lspt.Range(
|
|
81
|
+
start=lspt.Position(
|
|
82
|
+
line=warning.loc.first_line - 1,
|
|
83
|
+
character=warning.loc.col_start - 1,
|
|
84
|
+
),
|
|
85
|
+
end=lspt.Position(
|
|
86
|
+
line=warning.loc.last_line - 1,
|
|
87
|
+
character=warning.loc.col_end - 1,
|
|
88
|
+
),
|
|
89
|
+
),
|
|
90
|
+
message=warning.msg,
|
|
91
|
+
severity=lspt.DiagnosticSeverity.Warning,
|
|
92
|
+
)
|
|
93
|
+
for warning in self.warnings
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class JacLangServer(LanguageServer):
|
|
98
|
+
"""Class for managing workspace."""
|
|
99
|
+
|
|
100
|
+
def __init__(self) -> None:
|
|
101
|
+
"""Initialize workspace."""
|
|
102
|
+
super().__init__("jac-lsp", "v0.1")
|
|
103
|
+
self.modules: dict[str, ModuleInfo] = {}
|
|
104
|
+
|
|
105
|
+
def module_not_diff(self, uri: str, alev: ALev) -> bool:
|
|
106
|
+
"""Check if module was changed."""
|
|
107
|
+
doc = self.workspace.get_document(uri)
|
|
108
|
+
return (
|
|
109
|
+
doc.uri in self.modules
|
|
110
|
+
and self.modules[doc.uri].ir.source.hash
|
|
111
|
+
== md5(doc.source.encode()).hexdigest()
|
|
112
|
+
and (
|
|
113
|
+
self.modules[doc.uri].alev >= alev
|
|
114
|
+
or self.modules[doc.uri].has_syntax_error
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def push_diagnostics(self, file_path: str) -> None:
|
|
119
|
+
"""Push diagnostics for a file."""
|
|
120
|
+
if file_path in self.modules:
|
|
121
|
+
self.publish_diagnostics(
|
|
122
|
+
file_path,
|
|
123
|
+
self.modules[file_path].diagnostics,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def unwind_to_parent(self, file_path: str) -> str:
|
|
127
|
+
"""Unwind to parent."""
|
|
128
|
+
if file_path in self.modules:
|
|
129
|
+
while cur := self.modules[file_path].parent:
|
|
130
|
+
file_path = cur.uri
|
|
131
|
+
return file_path
|
|
132
|
+
|
|
133
|
+
def update_modules(self, file_path: str, build: Pass, alev: ALev) -> None:
|
|
134
|
+
"""Update modules."""
|
|
135
|
+
if not isinstance(build.ir, ast.Module):
|
|
136
|
+
self.log_error("Error with module build.")
|
|
137
|
+
return
|
|
138
|
+
self.modules[file_path] = ModuleInfo(
|
|
139
|
+
ir=build.ir,
|
|
140
|
+
errors=[
|
|
141
|
+
i
|
|
142
|
+
for i in build.errors_had
|
|
143
|
+
if i.loc.mod_path == uris.to_fs_path(file_path)
|
|
144
|
+
],
|
|
145
|
+
warnings=[
|
|
146
|
+
i
|
|
147
|
+
for i in build.warnings_had
|
|
148
|
+
if i.loc.mod_path == uris.to_fs_path(file_path)
|
|
149
|
+
],
|
|
150
|
+
alev=alev,
|
|
151
|
+
)
|
|
152
|
+
for p in build.ir.mod_deps.keys():
|
|
153
|
+
uri = uris.from_fs_path(p)
|
|
154
|
+
self.modules[uri] = ModuleInfo(
|
|
155
|
+
ir=build.ir.mod_deps[p],
|
|
156
|
+
errors=[i for i in build.errors_had if i.loc.mod_path == p],
|
|
157
|
+
warnings=[i for i in build.warnings_had if i.loc.mod_path == p],
|
|
158
|
+
alev=alev,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def quick_check(self, file_path: str) -> None:
|
|
162
|
+
"""Rebuild a file."""
|
|
163
|
+
if self.module_not_diff(file_path, ALev.QUICK):
|
|
164
|
+
return
|
|
165
|
+
try:
|
|
166
|
+
document = self.workspace.get_document(file_path)
|
|
167
|
+
build = jac_str_to_pass(
|
|
168
|
+
jac_str=document.source, file_path=document.path, schedule=[]
|
|
169
|
+
)
|
|
170
|
+
except Exception as e:
|
|
171
|
+
self.log_error(f"Error during syntax check: {e}")
|
|
172
|
+
self.update_modules(file_path, build, ALev.QUICK)
|
|
173
|
+
|
|
174
|
+
def deep_check(self, file_path: str) -> None:
|
|
175
|
+
"""Rebuild a file and its dependencies."""
|
|
176
|
+
if file_path in self.modules:
|
|
177
|
+
self.quick_check(file_path)
|
|
178
|
+
if self.module_not_diff(file_path, ALev.DEEP):
|
|
179
|
+
return
|
|
180
|
+
try:
|
|
181
|
+
file_path = self.unwind_to_parent(file_path)
|
|
182
|
+
build = jac_ir_to_pass(ir=self.modules[file_path].ir)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
self.log_error(f"Error during syntax check: {e}")
|
|
185
|
+
self.update_modules(file_path, build, ALev.DEEP)
|
|
186
|
+
|
|
187
|
+
def type_check(self, file_path: str) -> None:
|
|
188
|
+
"""Rebuild a file and its dependencies."""
|
|
189
|
+
if file_path not in self.modules:
|
|
190
|
+
self.deep_check(file_path)
|
|
191
|
+
if self.module_not_diff(file_path, ALev.TYPE):
|
|
192
|
+
return
|
|
193
|
+
try:
|
|
194
|
+
file_path = self.unwind_to_parent(file_path)
|
|
195
|
+
build = jac_ir_to_pass(
|
|
196
|
+
ir=self.modules[file_path].ir, schedule=type_checker_sched
|
|
197
|
+
)
|
|
198
|
+
except Exception as e:
|
|
199
|
+
self.log_error(f"Error during type check: {e}")
|
|
200
|
+
self.update_modules(file_path, build, ALev.TYPE)
|
|
201
|
+
|
|
202
|
+
def get_completion(
|
|
203
|
+
self, file_path: str, position: lspt.Position
|
|
204
|
+
) -> lspt.CompletionList:
|
|
205
|
+
"""Return completion for a file."""
|
|
206
|
+
items = []
|
|
207
|
+
document = self.workspace.get_document(file_path)
|
|
208
|
+
current_line = document.lines[position.line].strip()
|
|
209
|
+
if current_line.endswith("hello."):
|
|
210
|
+
|
|
211
|
+
items = [
|
|
212
|
+
lspt.CompletionItem(label="world"),
|
|
213
|
+
lspt.CompletionItem(label="friend"),
|
|
214
|
+
]
|
|
215
|
+
return lspt.CompletionList(is_incomplete=False, items=items)
|
|
216
|
+
|
|
217
|
+
def rename_module(self, old_path: str, new_path: str) -> None:
|
|
218
|
+
"""Rename module."""
|
|
219
|
+
if old_path in self.modules and new_path != old_path:
|
|
220
|
+
self.modules[new_path] = self.modules[old_path]
|
|
221
|
+
del self.modules[old_path]
|
|
222
|
+
|
|
223
|
+
def delete_module(self, uri: str) -> None:
|
|
224
|
+
"""Delete module."""
|
|
225
|
+
if uri in self.modules:
|
|
226
|
+
del self.modules[uri]
|
|
227
|
+
|
|
228
|
+
def formatted_jac(self, file_path: str) -> list[lspt.TextEdit]:
|
|
229
|
+
"""Return formatted jac."""
|
|
230
|
+
try:
|
|
231
|
+
document = self.workspace.get_document(file_path)
|
|
232
|
+
format = jac_str_to_pass(
|
|
233
|
+
jac_str=document.source,
|
|
234
|
+
file_path=document.path,
|
|
235
|
+
target=JacFormatPass,
|
|
236
|
+
schedule=[FuseCommentsPass, JacFormatPass],
|
|
237
|
+
)
|
|
238
|
+
formatted_text = (
|
|
239
|
+
format.ir.gen.jac
|
|
240
|
+
if JacParser not in [e.from_pass for e in format.errors_had]
|
|
241
|
+
else document.source
|
|
242
|
+
)
|
|
243
|
+
except Exception as e:
|
|
244
|
+
self.log_error(f"Error during formatting: {e}")
|
|
245
|
+
formatted_text = document.source
|
|
246
|
+
return [
|
|
247
|
+
lspt.TextEdit(
|
|
248
|
+
range=lspt.Range(
|
|
249
|
+
start=lspt.Position(line=0, character=0),
|
|
250
|
+
end=lspt.Position(
|
|
251
|
+
line=len(document.source.splitlines()) + 1, character=0
|
|
252
|
+
),
|
|
253
|
+
),
|
|
254
|
+
new_text=(formatted_text),
|
|
255
|
+
)
|
|
256
|
+
]
|
|
257
|
+
|
|
258
|
+
def get_hover_info(
|
|
259
|
+
self, file_path: str, position: lspt.Position
|
|
260
|
+
) -> Optional[lspt.Hover]:
|
|
261
|
+
"""Return hover information for a file."""
|
|
262
|
+
node_selected = find_deepest_node_at_pos(
|
|
263
|
+
self.modules[file_path].ir, position.line, position.character
|
|
264
|
+
)
|
|
265
|
+
value = self.get_node_info(node_selected) if node_selected else None
|
|
266
|
+
if value:
|
|
267
|
+
return lspt.Hover(
|
|
268
|
+
contents=lspt.MarkupContent(
|
|
269
|
+
kind=lspt.MarkupKind.PlainText, value=f"{value}"
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
def get_node_info(self, node: ast.AstNode) -> Optional[str]:
|
|
275
|
+
"""Extract meaningful information from the AST node."""
|
|
276
|
+
try:
|
|
277
|
+
if isinstance(node, ast.Token):
|
|
278
|
+
if isinstance(node, ast.AstSymbolNode):
|
|
279
|
+
if isinstance(node, ast.String):
|
|
280
|
+
return None
|
|
281
|
+
if node.sym_link and node.sym_link.decl:
|
|
282
|
+
decl_node = node.sym_link.decl
|
|
283
|
+
if isinstance(decl_node, ast.Architype):
|
|
284
|
+
if decl_node.doc:
|
|
285
|
+
node_info = f"({decl_node.arch_type.value}) {node.value} \n{decl_node.doc.lit_value}"
|
|
286
|
+
else:
|
|
287
|
+
node_info = (
|
|
288
|
+
f"({decl_node.arch_type.value}) {node.value}"
|
|
289
|
+
)
|
|
290
|
+
if decl_node.semstr:
|
|
291
|
+
node_info += f"\n{decl_node.semstr.lit_value}"
|
|
292
|
+
elif isinstance(decl_node, ast.Ability):
|
|
293
|
+
node_info = f"(ability) can {node.value}"
|
|
294
|
+
if decl_node.signature:
|
|
295
|
+
node_info += f" {decl_node.signature.unparse()}"
|
|
296
|
+
if decl_node.doc:
|
|
297
|
+
node_info += f"\n{decl_node.doc.lit_value}"
|
|
298
|
+
if decl_node.semstr:
|
|
299
|
+
node_info += f"\n{decl_node.semstr.lit_value}"
|
|
300
|
+
elif isinstance(decl_node, ast.Name):
|
|
301
|
+
if (
|
|
302
|
+
decl_node.parent
|
|
303
|
+
and isinstance(decl_node.parent, ast.SubNodeList)
|
|
304
|
+
and decl_node.parent.parent
|
|
305
|
+
and isinstance(decl_node.parent.parent, ast.Assignment)
|
|
306
|
+
and decl_node.parent.parent.type_tag
|
|
307
|
+
):
|
|
308
|
+
node_info = (
|
|
309
|
+
f"(variable) {decl_node.value}: "
|
|
310
|
+
f"{decl_node.parent.parent.type_tag.unparse()}"
|
|
311
|
+
)
|
|
312
|
+
if decl_node.parent.parent.semstr:
|
|
313
|
+
node_info += (
|
|
314
|
+
f"\n{decl_node.parent.parent.semstr.lit_value}"
|
|
315
|
+
)
|
|
316
|
+
else:
|
|
317
|
+
if decl_node.value in [
|
|
318
|
+
"str",
|
|
319
|
+
"int",
|
|
320
|
+
"float",
|
|
321
|
+
"bool",
|
|
322
|
+
"bytes",
|
|
323
|
+
"list",
|
|
324
|
+
"tuple",
|
|
325
|
+
"set",
|
|
326
|
+
"dict",
|
|
327
|
+
"type",
|
|
328
|
+
]:
|
|
329
|
+
node_info = f"({decl_node.value}) Built-in type"
|
|
330
|
+
else:
|
|
331
|
+
node_info = f"(variable) {decl_node.value}: None"
|
|
332
|
+
elif isinstance(decl_node, ast.HasVar):
|
|
333
|
+
if decl_node.type_tag:
|
|
334
|
+
node_info = f"(variable) {decl_node.name.value} {decl_node.type_tag.unparse()}"
|
|
335
|
+
else:
|
|
336
|
+
node_info = f"(variable) {decl_node.name.value}"
|
|
337
|
+
if decl_node.semstr:
|
|
338
|
+
node_info += f"\n{decl_node.semstr.lit_value}"
|
|
339
|
+
elif isinstance(decl_node, ast.ParamVar):
|
|
340
|
+
if decl_node.type_tag:
|
|
341
|
+
node_info = f"(parameter) {decl_node.name.value} {decl_node.type_tag.unparse()}"
|
|
342
|
+
else:
|
|
343
|
+
node_info = f"(parameter) {decl_node.name.value}"
|
|
344
|
+
if decl_node.semstr:
|
|
345
|
+
node_info += f"\n{decl_node.semstr.lit_value}"
|
|
346
|
+
elif isinstance(decl_node, ast.ModuleItem):
|
|
347
|
+
node_info = (
|
|
348
|
+
f"(ModuleItem) {node.value}" # TODO: Add more info
|
|
349
|
+
)
|
|
350
|
+
else:
|
|
351
|
+
node_info = f"{node.value}"
|
|
352
|
+
else:
|
|
353
|
+
node_info = f"{node.value}" # non symbol node
|
|
354
|
+
else:
|
|
355
|
+
return None
|
|
356
|
+
else:
|
|
357
|
+
return None
|
|
358
|
+
except AttributeError as e:
|
|
359
|
+
self.log_warning(f"Attribute error when accessing node attributes: {e}")
|
|
360
|
+
return node_info.strip()
|
|
361
|
+
|
|
362
|
+
def log_error(self, message: str) -> None:
|
|
363
|
+
"""Log an error message."""
|
|
364
|
+
self.show_message_log(message, lspt.MessageType.Error)
|
|
365
|
+
self.show_message(message, lspt.MessageType.Error)
|
|
366
|
+
|
|
367
|
+
def log_warning(self, message: str) -> None:
|
|
368
|
+
"""Log a warning message."""
|
|
369
|
+
self.show_message_log(message, lspt.MessageType.Warning)
|
|
370
|
+
self.show_message(message, lspt.MessageType.Warning)
|
|
371
|
+
|
|
372
|
+
def log_info(self, message: str) -> None:
|
|
373
|
+
"""Log an info message."""
|
|
374
|
+
self.show_message_log(message, lspt.MessageType.Info)
|
|
375
|
+
self.show_message(message, lspt.MessageType.Info)
|
jaclang/langserve/server.py
CHANGED
|
@@ -2,101 +2,139 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
from jaclang.
|
|
5
|
+
import threading
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from jaclang.langserve.engine import JacLangServer
|
|
9
|
+
from jaclang.langserve.utils import debounce
|
|
9
10
|
|
|
10
11
|
import lsprotocol.types as lspt
|
|
11
12
|
|
|
12
|
-
server =
|
|
13
|
+
server = JacLangServer()
|
|
14
|
+
analysis_thread: Optional[threading.Thread] = None
|
|
15
|
+
analysis_stop_event = threading.Event()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def analyze_and_publish(ls: JacLangServer, uri: str) -> None:
|
|
19
|
+
"""Analyze and publish diagnostics."""
|
|
20
|
+
global analysis_thread, analysis_stop_event
|
|
21
|
+
|
|
22
|
+
def run_analysis() -> None:
|
|
23
|
+
ls.quick_check(uri)
|
|
24
|
+
ls.push_diagnostics(uri)
|
|
25
|
+
ls.deep_check(uri)
|
|
26
|
+
ls.push_diagnostics(uri)
|
|
27
|
+
ls.type_check(uri)
|
|
28
|
+
ls.push_diagnostics(uri)
|
|
29
|
+
|
|
30
|
+
analysis_thread = threading.Thread(target=run_analysis)
|
|
31
|
+
analysis_thread.start()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def stop_analysis() -> None:
|
|
35
|
+
"""Stop analysis."""
|
|
36
|
+
global analysis_thread, analysis_stop_event
|
|
37
|
+
if analysis_thread is not None:
|
|
38
|
+
analysis_stop_event.set()
|
|
39
|
+
analysis_thread.join()
|
|
40
|
+
analysis_stop_event.clear()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@server.feature(lspt.TEXT_DOCUMENT_DID_OPEN)
|
|
44
|
+
async def did_open(ls: JacLangServer, params: lspt.DidOpenTextDocumentParams) -> None:
|
|
45
|
+
"""Check syntax on change."""
|
|
46
|
+
stop_analysis()
|
|
47
|
+
analyze_and_publish(ls, params.text_document.uri)
|
|
13
48
|
|
|
14
49
|
|
|
15
50
|
@server.feature(lspt.TEXT_DOCUMENT_DID_CHANGE)
|
|
16
|
-
@debounce(0.
|
|
51
|
+
@debounce(0.1)
|
|
17
52
|
async def did_change(
|
|
18
|
-
ls:
|
|
53
|
+
ls: JacLangServer, params: lspt.DidChangeTextDocumentParams
|
|
19
54
|
) -> None:
|
|
20
55
|
"""Check syntax on change."""
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
stop_analysis()
|
|
57
|
+
analyze_and_publish(ls, params.text_document.uri)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@server.feature(lspt.TEXT_DOCUMENT_DID_SAVE)
|
|
61
|
+
async def did_save(ls: JacLangServer, params: lspt.DidSaveTextDocumentParams) -> None:
|
|
62
|
+
"""Check syntax on save."""
|
|
63
|
+
stop_analysis()
|
|
64
|
+
analyze_and_publish(ls, params.text_document.uri)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@server.feature(
|
|
68
|
+
lspt.WORKSPACE_DID_CREATE_FILES,
|
|
69
|
+
lspt.FileOperationRegistrationOptions(
|
|
70
|
+
filters=[
|
|
71
|
+
lspt.FileOperationFilter(pattern=lspt.FileOperationPattern("**/*.jac"))
|
|
72
|
+
]
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
async def did_create_files(ls: JacLangServer, params: lspt.CreateFilesParams) -> None:
|
|
76
|
+
"""Check syntax on file creation."""
|
|
77
|
+
for file in params.files:
|
|
78
|
+
ls.quick_check(file.uri)
|
|
79
|
+
ls.push_diagnostics(file.uri)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@server.feature(
|
|
83
|
+
lspt.WORKSPACE_DID_RENAME_FILES,
|
|
84
|
+
lspt.FileOperationRegistrationOptions(
|
|
85
|
+
filters=[
|
|
86
|
+
lspt.FileOperationFilter(pattern=lspt.FileOperationPattern("**/*.jac"))
|
|
87
|
+
]
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
async def did_rename_files(ls: JacLangServer, params: lspt.RenameFilesParams) -> None:
|
|
91
|
+
"""Check syntax on file rename."""
|
|
92
|
+
new_uris = [file.new_uri for file in params.files]
|
|
93
|
+
old_uris = [file.old_uri for file in params.files]
|
|
94
|
+
for i in range(len(new_uris)):
|
|
95
|
+
ls.rename_module(old_uris[i], new_uris[i])
|
|
96
|
+
ls.quick_check(new_uris[i])
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@server.feature(
|
|
100
|
+
lspt.WORKSPACE_DID_DELETE_FILES,
|
|
101
|
+
lspt.FileOperationRegistrationOptions(
|
|
102
|
+
filters=[
|
|
103
|
+
lspt.FileOperationFilter(pattern=lspt.FileOperationPattern("**/*.jac"))
|
|
104
|
+
]
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
async def did_delete_files(ls: JacLangServer, params: lspt.DeleteFilesParams) -> None:
|
|
108
|
+
"""Check syntax on file delete."""
|
|
109
|
+
for file in params.files:
|
|
110
|
+
ls.delete_module(file.uri)
|
|
53
111
|
|
|
54
112
|
|
|
55
113
|
@server.feature(
|
|
56
114
|
lspt.TEXT_DOCUMENT_COMPLETION,
|
|
57
115
|
lspt.CompletionOptions(trigger_characters=[".", ":", ""]),
|
|
58
116
|
)
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if current_line.endswith("hello."):
|
|
65
|
-
|
|
66
|
-
items = [
|
|
67
|
-
lspt.CompletionItem(label="world"),
|
|
68
|
-
lspt.CompletionItem(label="friend"),
|
|
69
|
-
]
|
|
70
|
-
return lspt.CompletionList(is_incomplete=False, items=items)
|
|
117
|
+
async def completion(
|
|
118
|
+
ls: JacLangServer, params: lspt.CompletionParams
|
|
119
|
+
) -> lspt.CompletionList:
|
|
120
|
+
"""Provide completion."""
|
|
121
|
+
return ls.get_completion(params.text_document.uri, params.position)
|
|
71
122
|
|
|
72
123
|
|
|
73
124
|
@server.feature(lspt.TEXT_DOCUMENT_FORMATTING)
|
|
74
125
|
def formatting(
|
|
75
|
-
ls:
|
|
126
|
+
ls: JacLangServer, params: lspt.DocumentFormattingParams
|
|
76
127
|
) -> list[lspt.TextEdit]:
|
|
77
128
|
"""Format the given document."""
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
log_error(ls, f"Error during formatting: {e}")
|
|
88
|
-
formatted_text = document.source
|
|
89
|
-
return [
|
|
90
|
-
lspt.TextEdit(
|
|
91
|
-
range=lspt.Range(
|
|
92
|
-
start=lspt.Position(line=0, character=0),
|
|
93
|
-
end=lspt.Position(
|
|
94
|
-
line=len(formatted_text.splitlines()) + 1, character=0
|
|
95
|
-
),
|
|
96
|
-
),
|
|
97
|
-
new_text=formatted_text,
|
|
98
|
-
)
|
|
99
|
-
]
|
|
129
|
+
return ls.formatted_jac(params.text_document.uri)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@server.feature(lspt.TEXT_DOCUMENT_HOVER, lspt.HoverOptions(work_done_progress=True))
|
|
133
|
+
def hover(
|
|
134
|
+
ls: JacLangServer, params: lspt.TextDocumentPositionParams
|
|
135
|
+
) -> Optional[lspt.Hover]:
|
|
136
|
+
"""Provide hover information for the given hover request."""
|
|
137
|
+
return ls.get_hover_info(params.text_document.uri, params.position)
|
|
100
138
|
|
|
101
139
|
|
|
102
140
|
def run_lang_server() -> None:
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module demonstrates a simple circle class and a function to calculate
|
|
3
|
+
the area of a circle in all of Jac's glory.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import:py math;
|
|
7
|
+
# Module-level global var
|
|
8
|
+
|
|
9
|
+
glob RAD = 5;
|
|
10
|
+
|
|
11
|
+
"""Function to calculate the area of a circle."""
|
|
12
|
+
can calculate_area(radius: float) -> float {
|
|
13
|
+
return math.pi * radius * radius;
|
|
14
|
+
}
|
|
15
|
+
#* (This is multiline comments in Jac)
|
|
16
|
+
Above we have the demonstration of a function to calculate the area of a circle.
|
|
17
|
+
Below we have the demonstration of a class to calculate the area of a circle.
|
|
18
|
+
*#
|
|
19
|
+
|
|
20
|
+
"""Enum for shape types"""
|
|
21
|
+
enum ShapeType {
|
|
22
|
+
CIRCLE="Circle",
|
|
23
|
+
UNKNOWN="Unknown"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
"""Base class for a shape."""
|
|
27
|
+
obj Shape {
|
|
28
|
+
has shape_type: ShapeType;
|
|
29
|
+
|
|
30
|
+
"""Abstract method to calculate the area of a shape."""
|
|
31
|
+
can area -> float abs;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
"""Circle class inherits from Shape."""
|
|
35
|
+
obj Circle :Shape: {
|
|
36
|
+
can init(radius: float) {
|
|
37
|
+
super.init(ShapeType.CIRCLE);
|
|
38
|
+
self.radius = radius;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
"""Overridden method to calculate the area of the circle."""
|
|
42
|
+
override can area -> float {
|
|
43
|
+
return math.pi * self.radius * self.radius;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
with entry {
|
|
48
|
+
c = Circle(RAD);
|
|
49
|
+
}
|
|
50
|
+
# Global also works here
|
|
51
|
+
|
|
52
|
+
with entry:__main__ {
|
|
53
|
+
# To run the program functionality
|
|
54
|
+
print(f"Area of a circle with radius {RAD} using function: {calculate_area(RAD)}");
|
|
55
|
+
print(f"Area of a {c.shape_type.value} with radius {RAD} using class: {c.area()}");
|
|
56
|
+
}
|
|
57
|
+
# Unit Tests!
|
|
58
|
+
|
|
59
|
+
glob expected_area = 78.53981633974483;
|
|
60
|
+
|
|
61
|
+
test calc_area {
|
|
62
|
+
check.assertAlmostEqual(calculate_area(RAD), expected_area);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
test circle_area {
|
|
66
|
+
c = Circle(RAD);
|
|
67
|
+
check.assertAlmostEqual(c.area(), expected_area);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
test circle_type {
|
|
71
|
+
c = Circle(RAD);
|
|
72
|
+
check.assertEqual(c.shape_type, ShapeType.CIRCLE);
|
|
73
|
+
}
|