jaclang 0.7.1__py3-none-any.whl → 0.7.5__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/cli/cli.py +2 -2
- jaclang/compiler/absyntree.py +378 -277
- jaclang/compiler/codeloc.py +2 -2
- jaclang/compiler/constant.py +2 -0
- jaclang/compiler/jac.lark +25 -19
- jaclang/compiler/parser.py +115 -92
- jaclang/compiler/passes/main/access_modifier_pass.py +15 -9
- jaclang/compiler/passes/main/def_impl_match_pass.py +29 -11
- jaclang/compiler/passes/main/def_use_pass.py +48 -17
- jaclang/compiler/passes/main/fuse_typeinfo_pass.py +49 -30
- jaclang/compiler/passes/main/import_pass.py +12 -7
- jaclang/compiler/passes/main/pyast_gen_pass.py +110 -47
- jaclang/compiler/passes/main/pyast_load_pass.py +49 -13
- jaclang/compiler/passes/main/pyjac_ast_link_pass.py +25 -11
- jaclang/compiler/passes/main/pyout_pass.py +3 -1
- jaclang/compiler/passes/main/registry_pass.py +6 -6
- jaclang/compiler/passes/main/sym_tab_build_pass.py +30 -72
- jaclang/compiler/passes/main/tests/test_decl_def_match_pass.py +21 -4
- jaclang/compiler/passes/main/tests/test_def_use_pass.py +5 -10
- jaclang/compiler/passes/main/tests/test_import_pass.py +8 -0
- jaclang/compiler/passes/main/tests/test_type_check_pass.py +1 -1
- jaclang/compiler/passes/main/type_check_pass.py +2 -1
- jaclang/compiler/passes/tool/jac_formatter_pass.py +44 -11
- jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +16 -0
- jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +16 -0
- jaclang/compiler/passes/tool/tests/fixtures/doc_string.jac +15 -0
- jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +7 -5
- jaclang/compiler/passes/tool/tests/test_unparse_validate.py +1 -2
- jaclang/compiler/passes/transform.py +2 -4
- jaclang/{core/registry.py → compiler/semtable.py} +1 -3
- jaclang/compiler/symtable.py +39 -31
- jaclang/compiler/tests/test_parser.py +2 -2
- jaclang/core/aott.py +112 -16
- jaclang/core/{construct.py → architype.py} +44 -93
- jaclang/core/constructs.py +44 -0
- jaclang/core/context.py +157 -0
- jaclang/core/importer.py +18 -9
- jaclang/core/llms/anthropic.py +31 -2
- jaclang/core/llms/base.py +3 -3
- jaclang/core/llms/groq.py +4 -1
- jaclang/core/llms/huggingface.py +4 -1
- jaclang/core/llms/ollama.py +4 -1
- jaclang/core/llms/openai.py +6 -2
- jaclang/core/llms/togetherai.py +4 -1
- jaclang/core/memory.py +53 -2
- jaclang/core/test.py +90 -0
- jaclang/core/utils.py +2 -2
- jaclang/langserve/engine.py +119 -122
- jaclang/langserve/server.py +27 -5
- jaclang/langserve/tests/fixtures/circle.jac +16 -12
- jaclang/langserve/tests/fixtures/circle_err.jac +3 -3
- jaclang/langserve/tests/fixtures/circle_pure.impl.jac +8 -4
- jaclang/langserve/tests/fixtures/circle_pure.jac +2 -2
- jaclang/langserve/tests/test_server.py +114 -0
- jaclang/langserve/utils.py +104 -10
- jaclang/plugin/builtin.py +1 -1
- jaclang/plugin/default.py +46 -90
- jaclang/plugin/feature.py +32 -16
- jaclang/plugin/spec.py +17 -19
- jaclang/plugin/tests/test_features.py +0 -33
- jaclang/settings.py +4 -0
- jaclang/tests/fixtures/abc.jac +16 -12
- jaclang/tests/fixtures/byllmissue.jac +12 -0
- jaclang/tests/fixtures/edgetypetest.jac +16 -0
- jaclang/tests/fixtures/hash_init_check.jac +17 -0
- jaclang/tests/fixtures/impl_match_confused.impl.jac +1 -0
- jaclang/tests/fixtures/impl_match_confused.jac +5 -0
- jaclang/tests/fixtures/math_question.jpg +0 -0
- jaclang/tests/fixtures/maxfail_run_test.jac +17 -5
- jaclang/tests/fixtures/nosigself.jac +19 -0
- jaclang/tests/fixtures/run_test.jac +17 -5
- jaclang/tests/fixtures/walker_override.jac +21 -0
- jaclang/tests/fixtures/with_llm_vision.jac +25 -0
- jaclang/tests/test_bugs.py +19 -0
- jaclang/tests/test_cli.py +1 -1
- jaclang/tests/test_language.py +116 -11
- jaclang/tests/test_reference.py +1 -1
- jaclang/utils/lang_tools.py +5 -4
- jaclang/utils/test.py +2 -1
- jaclang/utils/treeprinter.py +35 -4
- {jaclang-0.7.1.dist-info → jaclang-0.7.5.dist-info}/METADATA +3 -2
- {jaclang-0.7.1.dist-info → jaclang-0.7.5.dist-info}/RECORD +84 -71
- jaclang/core/shelve_storage.py +0 -55
- {jaclang-0.7.1.dist-info → jaclang-0.7.5.dist-info}/WHEEL +0 -0
- {jaclang-0.7.1.dist-info → jaclang-0.7.5.dist-info}/entry_points.txt +0 -0
jaclang/langserve/engine.py
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import logging
|
|
5
6
|
from enum import IntEnum
|
|
6
7
|
from hashlib import md5
|
|
7
|
-
from typing import Optional
|
|
8
|
+
from typing import Optional
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
import jaclang.compiler.absyntree as ast
|
|
@@ -14,7 +15,11 @@ from jaclang.compiler.passes import Pass
|
|
|
14
15
|
from jaclang.compiler.passes.main.schedules import type_checker_sched
|
|
15
16
|
from jaclang.compiler.passes.tool import FuseCommentsPass, JacFormatPass
|
|
16
17
|
from jaclang.compiler.passes.transform import Alert
|
|
17
|
-
from jaclang.langserve.utils import
|
|
18
|
+
from jaclang.langserve.utils import (
|
|
19
|
+
collect_symbols,
|
|
20
|
+
create_range,
|
|
21
|
+
find_deepest_symbol_node_at_pos,
|
|
22
|
+
)
|
|
18
23
|
from jaclang.vendor.pygls import uris
|
|
19
24
|
from jaclang.vendor.pygls.server import LanguageServer
|
|
20
25
|
|
|
@@ -35,8 +40,8 @@ class ModuleInfo:
|
|
|
35
40
|
def __init__(
|
|
36
41
|
self,
|
|
37
42
|
ir: ast.Module,
|
|
38
|
-
errors:
|
|
39
|
-
warnings:
|
|
43
|
+
errors: list[Alert],
|
|
44
|
+
warnings: list[Alert],
|
|
40
45
|
alev: ALev,
|
|
41
46
|
parent: Optional[ModuleInfo] = None,
|
|
42
47
|
) -> None:
|
|
@@ -58,35 +63,26 @@ class ModuleInfo:
|
|
|
58
63
|
"""Return if there are syntax errors."""
|
|
59
64
|
return len(self.errors) > 0 and self.alev == ALev.QUICK
|
|
60
65
|
|
|
66
|
+
def update_with(self, new_info: ModuleInfo) -> None:
|
|
67
|
+
"""Update module info."""
|
|
68
|
+
self.ir = new_info.ir
|
|
69
|
+
self.errors += [i for i in new_info.errors if i not in self.errors]
|
|
70
|
+
self.warnings += [i for i in new_info.warnings if i not in self.warnings]
|
|
71
|
+
self.alev = new_info.alev
|
|
72
|
+
self.diagnostics = self.gen_diagnostics()
|
|
73
|
+
|
|
61
74
|
def gen_diagnostics(self) -> list[lspt.Diagnostic]:
|
|
62
75
|
"""Return diagnostics."""
|
|
63
76
|
return [
|
|
64
77
|
lspt.Diagnostic(
|
|
65
|
-
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
|
-
),
|
|
78
|
+
range=create_range(error.loc),
|
|
74
79
|
message=error.msg,
|
|
75
80
|
severity=lspt.DiagnosticSeverity.Error,
|
|
76
81
|
)
|
|
77
82
|
for error in self.errors
|
|
78
83
|
] + [
|
|
79
84
|
lspt.Diagnostic(
|
|
80
|
-
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
|
-
),
|
|
85
|
+
range=create_range(warning.loc),
|
|
90
86
|
message=warning.msg,
|
|
91
87
|
severity=lspt.DiagnosticSeverity.Warning,
|
|
92
88
|
)
|
|
@@ -104,7 +100,7 @@ class JacLangServer(LanguageServer):
|
|
|
104
100
|
|
|
105
101
|
def module_not_diff(self, uri: str, alev: ALev) -> bool:
|
|
106
102
|
"""Check if module was changed."""
|
|
107
|
-
doc = self.workspace.
|
|
103
|
+
doc = self.workspace.get_text_document(uri)
|
|
108
104
|
return (
|
|
109
105
|
doc.uri in self.modules
|
|
110
106
|
and self.modules[doc.uri].ir.source.hash
|
|
@@ -125,17 +121,25 @@ class JacLangServer(LanguageServer):
|
|
|
125
121
|
|
|
126
122
|
def unwind_to_parent(self, file_path: str) -> str:
|
|
127
123
|
"""Unwind to parent."""
|
|
124
|
+
orig_file_path = file_path
|
|
128
125
|
if file_path in self.modules:
|
|
129
126
|
while cur := self.modules[file_path].parent:
|
|
130
127
|
file_path = cur.uri
|
|
128
|
+
if file_path == orig_file_path and (
|
|
129
|
+
discover := self.modules[file_path].ir.annexable_by
|
|
130
|
+
):
|
|
131
|
+
file_path = uris.from_fs_path(discover)
|
|
132
|
+
self.quick_check(file_path)
|
|
131
133
|
return file_path
|
|
132
134
|
|
|
133
|
-
def update_modules(
|
|
135
|
+
def update_modules(
|
|
136
|
+
self, file_path: str, build: Pass, alev: ALev, refresh: bool = False
|
|
137
|
+
) -> None:
|
|
134
138
|
"""Update modules."""
|
|
135
139
|
if not isinstance(build.ir, ast.Module):
|
|
136
140
|
self.log_error("Error with module build.")
|
|
137
141
|
return
|
|
138
|
-
|
|
142
|
+
new_mod = ModuleInfo(
|
|
139
143
|
ir=build.ir,
|
|
140
144
|
errors=[
|
|
141
145
|
i
|
|
@@ -149,33 +153,44 @@ class JacLangServer(LanguageServer):
|
|
|
149
153
|
],
|
|
150
154
|
alev=alev,
|
|
151
155
|
)
|
|
156
|
+
if not refresh and file_path in self.modules:
|
|
157
|
+
self.modules[file_path].update_with(new_mod)
|
|
158
|
+
else:
|
|
159
|
+
self.modules[file_path] = new_mod
|
|
152
160
|
for p in build.ir.mod_deps.keys():
|
|
153
161
|
uri = uris.from_fs_path(p)
|
|
154
|
-
|
|
162
|
+
new_mod = ModuleInfo(
|
|
155
163
|
ir=build.ir.mod_deps[p],
|
|
156
164
|
errors=[i for i in build.errors_had if i.loc.mod_path == p],
|
|
157
165
|
warnings=[i for i in build.warnings_had if i.loc.mod_path == p],
|
|
158
166
|
alev=alev,
|
|
159
167
|
)
|
|
168
|
+
if not refresh and uri in self.modules:
|
|
169
|
+
self.modules[uri].update_with(new_mod)
|
|
170
|
+
else:
|
|
171
|
+
self.modules[uri] = new_mod
|
|
172
|
+
self.modules[uri].parent = (
|
|
173
|
+
self.modules[file_path] if file_path != uri else None
|
|
174
|
+
)
|
|
160
175
|
|
|
161
|
-
def quick_check(self, file_path: str) -> None:
|
|
176
|
+
def quick_check(self, file_path: str, force: bool = False) -> None:
|
|
162
177
|
"""Rebuild a file."""
|
|
163
|
-
if self.module_not_diff(file_path, ALev.QUICK):
|
|
178
|
+
if not force and self.module_not_diff(file_path, ALev.QUICK):
|
|
164
179
|
return
|
|
165
180
|
try:
|
|
166
|
-
document = self.workspace.
|
|
181
|
+
document = self.workspace.get_text_document(file_path)
|
|
167
182
|
build = jac_str_to_pass(
|
|
168
183
|
jac_str=document.source, file_path=document.path, schedule=[]
|
|
169
184
|
)
|
|
170
185
|
except Exception as e:
|
|
171
186
|
self.log_error(f"Error during syntax check: {e}")
|
|
172
|
-
self.update_modules(file_path, build, ALev.QUICK)
|
|
187
|
+
self.update_modules(file_path, build, ALev.QUICK, refresh=True)
|
|
173
188
|
|
|
174
|
-
def deep_check(self, file_path: str) -> None:
|
|
189
|
+
def deep_check(self, file_path: str, force: bool = False) -> None:
|
|
175
190
|
"""Rebuild a file and its dependencies."""
|
|
176
191
|
if file_path in self.modules:
|
|
177
|
-
self.quick_check(file_path)
|
|
178
|
-
if self.module_not_diff(file_path, ALev.DEEP):
|
|
192
|
+
self.quick_check(file_path, force=force)
|
|
193
|
+
if not force and self.module_not_diff(file_path, ALev.DEEP):
|
|
179
194
|
return
|
|
180
195
|
try:
|
|
181
196
|
file_path = self.unwind_to_parent(file_path)
|
|
@@ -184,11 +199,11 @@ class JacLangServer(LanguageServer):
|
|
|
184
199
|
self.log_error(f"Error during syntax check: {e}")
|
|
185
200
|
self.update_modules(file_path, build, ALev.DEEP)
|
|
186
201
|
|
|
187
|
-
def type_check(self, file_path: str) -> None:
|
|
202
|
+
def type_check(self, file_path: str, force: bool = False) -> None:
|
|
188
203
|
"""Rebuild a file and its dependencies."""
|
|
189
204
|
if file_path not in self.modules:
|
|
190
|
-
self.deep_check(file_path)
|
|
191
|
-
if self.module_not_diff(file_path, ALev.TYPE):
|
|
205
|
+
self.deep_check(file_path, force=force)
|
|
206
|
+
if not force and self.module_not_diff(file_path, ALev.TYPE):
|
|
192
207
|
return
|
|
193
208
|
try:
|
|
194
209
|
file_path = self.unwind_to_parent(file_path)
|
|
@@ -204,7 +219,7 @@ class JacLangServer(LanguageServer):
|
|
|
204
219
|
) -> lspt.CompletionList:
|
|
205
220
|
"""Return completion for a file."""
|
|
206
221
|
items = []
|
|
207
|
-
document = self.workspace.
|
|
222
|
+
document = self.workspace.get_text_document(file_path)
|
|
208
223
|
current_line = document.lines[position.line].strip()
|
|
209
224
|
if current_line.endswith("hello."):
|
|
210
225
|
|
|
@@ -228,7 +243,7 @@ class JacLangServer(LanguageServer):
|
|
|
228
243
|
def formatted_jac(self, file_path: str) -> list[lspt.TextEdit]:
|
|
229
244
|
"""Return formatted jac."""
|
|
230
245
|
try:
|
|
231
|
-
document = self.workspace.
|
|
246
|
+
document = self.workspace.get_text_document(file_path)
|
|
232
247
|
format = jac_str_to_pass(
|
|
233
248
|
jac_str=document.source,
|
|
234
249
|
file_path=document.path,
|
|
@@ -259,7 +274,7 @@ class JacLangServer(LanguageServer):
|
|
|
259
274
|
self, file_path: str, position: lspt.Position
|
|
260
275
|
) -> Optional[lspt.Hover]:
|
|
261
276
|
"""Return hover information for a file."""
|
|
262
|
-
node_selected =
|
|
277
|
+
node_selected = find_deepest_symbol_node_at_pos(
|
|
263
278
|
self.modules[file_path].ir, position.line, position.character
|
|
264
279
|
)
|
|
265
280
|
value = self.get_node_info(node_selected) if node_selected else None
|
|
@@ -271,94 +286,72 @@ class JacLangServer(LanguageServer):
|
|
|
271
286
|
)
|
|
272
287
|
return None
|
|
273
288
|
|
|
274
|
-
def get_node_info(self, node: ast.
|
|
289
|
+
def get_node_info(self, node: ast.AstSymbolNode) -> Optional[str]:
|
|
275
290
|
"""Extract meaningful information from the AST node."""
|
|
276
291
|
try:
|
|
277
|
-
if isinstance(node, ast.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
|
292
|
+
if isinstance(node, ast.NameAtom):
|
|
293
|
+
node = node.name_of
|
|
294
|
+
access = node.sym.access.value + " " if node.sym else None
|
|
295
|
+
node_info = (
|
|
296
|
+
f"({access if access else ''}{node.sym_category.value}) {node.sym_name}"
|
|
297
|
+
)
|
|
298
|
+
if node.name_spec.clean_type:
|
|
299
|
+
node_info += f": {node.name_spec.clean_type}"
|
|
300
|
+
if isinstance(node, ast.AstSemStrNode) and node.semstr:
|
|
301
|
+
node_info += f"\n{node.semstr.value}"
|
|
302
|
+
if isinstance(node, ast.AstDocNode) and node.doc:
|
|
303
|
+
node_info += f"\n{node.doc.value}"
|
|
304
|
+
if isinstance(node, ast.Ability) and node.signature:
|
|
305
|
+
node_info += f"\n{node.signature.unparse()}"
|
|
306
|
+
self.log_py(f"mypy_node: {node.gen.mypy_ast}")
|
|
358
307
|
except AttributeError as e:
|
|
359
308
|
self.log_warning(f"Attribute error when accessing node attributes: {e}")
|
|
360
309
|
return node_info.strip()
|
|
361
310
|
|
|
311
|
+
def get_document_symbols(self, file_path: str) -> list[lspt.DocumentSymbol]:
|
|
312
|
+
"""Return document symbols for a file."""
|
|
313
|
+
root_node = self.modules[file_path].ir.sym_tab
|
|
314
|
+
if root_node:
|
|
315
|
+
return collect_symbols(root_node)
|
|
316
|
+
return []
|
|
317
|
+
|
|
318
|
+
def get_definition(
|
|
319
|
+
self, file_path: str, position: lspt.Position
|
|
320
|
+
) -> Optional[lspt.Location]:
|
|
321
|
+
"""Return definition location for a file."""
|
|
322
|
+
node_selected: Optional[ast.AstSymbolNode] = find_deepest_symbol_node_at_pos(
|
|
323
|
+
self.modules[file_path].ir, position.line, position.character
|
|
324
|
+
)
|
|
325
|
+
if node_selected:
|
|
326
|
+
if isinstance(node_selected, (ast.ElementStmt, ast.BuiltinType)):
|
|
327
|
+
return None
|
|
328
|
+
decl_node = (
|
|
329
|
+
node_selected.parent.body.target
|
|
330
|
+
if node_selected.parent
|
|
331
|
+
and isinstance(node_selected.parent, ast.AstImplNeedingNode)
|
|
332
|
+
and isinstance(node_selected.parent.body, ast.AstImplOnlyNode)
|
|
333
|
+
else (
|
|
334
|
+
node_selected.sym.decl
|
|
335
|
+
if (node_selected.sym and node_selected.sym.decl)
|
|
336
|
+
else node_selected
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
self.log_py(f"{node_selected}, {decl_node}")
|
|
340
|
+
decl_uri = uris.from_fs_path(decl_node.loc.mod_path)
|
|
341
|
+
try:
|
|
342
|
+
decl_range = create_range(decl_node.loc)
|
|
343
|
+
except ValueError: # 'print' name has decl in 0,0,0,0
|
|
344
|
+
return None
|
|
345
|
+
decl_location = lspt.Location(
|
|
346
|
+
uri=decl_uri,
|
|
347
|
+
range=decl_range,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
return decl_location
|
|
351
|
+
else:
|
|
352
|
+
self.log_info("No declaration found for the selected node.")
|
|
353
|
+
return None
|
|
354
|
+
|
|
362
355
|
def log_error(self, message: str) -> None:
|
|
363
356
|
"""Log an error message."""
|
|
364
357
|
self.show_message_log(message, lspt.MessageType.Error)
|
|
@@ -373,3 +366,7 @@ class JacLangServer(LanguageServer):
|
|
|
373
366
|
"""Log an info message."""
|
|
374
367
|
self.show_message_log(message, lspt.MessageType.Info)
|
|
375
368
|
self.show_message(message, lspt.MessageType.Info)
|
|
369
|
+
|
|
370
|
+
def log_py(self, message: str) -> None:
|
|
371
|
+
"""Log a message."""
|
|
372
|
+
logging.info(message)
|
jaclang/langserve/server.py
CHANGED
|
@@ -15,17 +15,19 @@ analysis_thread: Optional[threading.Thread] = None
|
|
|
15
15
|
analysis_stop_event = threading.Event()
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def analyze_and_publish(ls: JacLangServer, uri: str) -> None:
|
|
18
|
+
def analyze_and_publish(ls: JacLangServer, uri: str, level: int = 2) -> None:
|
|
19
19
|
"""Analyze and publish diagnostics."""
|
|
20
20
|
global analysis_thread, analysis_stop_event
|
|
21
21
|
|
|
22
22
|
def run_analysis() -> None:
|
|
23
23
|
ls.quick_check(uri)
|
|
24
24
|
ls.push_diagnostics(uri)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
if not analysis_stop_event.is_set() and level > 0:
|
|
26
|
+
ls.deep_check(uri)
|
|
27
|
+
ls.push_diagnostics(uri)
|
|
28
|
+
if not analysis_stop_event.is_set() and level > 1:
|
|
29
|
+
ls.type_check(uri)
|
|
30
|
+
ls.push_diagnostics(uri)
|
|
29
31
|
|
|
30
32
|
analysis_thread = threading.Thread(target=run_analysis)
|
|
31
33
|
analysis_thread.start()
|
|
@@ -137,6 +139,26 @@ def hover(
|
|
|
137
139
|
return ls.get_hover_info(params.text_document.uri, params.position)
|
|
138
140
|
|
|
139
141
|
|
|
142
|
+
@server.feature(lspt.TEXT_DOCUMENT_DOCUMENT_SYMBOL)
|
|
143
|
+
async def document_symbol(
|
|
144
|
+
ls: JacLangServer, params: lspt.DocumentSymbolParams
|
|
145
|
+
) -> list[lspt.DocumentSymbol]:
|
|
146
|
+
"""Provide document symbols."""
|
|
147
|
+
stop_analysis()
|
|
148
|
+
analyze_and_publish(ls, params.text_document.uri)
|
|
149
|
+
return ls.get_document_symbols(params.text_document.uri)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@server.feature(lspt.TEXT_DOCUMENT_DEFINITION)
|
|
153
|
+
async def definition(
|
|
154
|
+
ls: JacLangServer, params: lspt.TextDocumentPositionParams
|
|
155
|
+
) -> Optional[lspt.Location]:
|
|
156
|
+
"""Provide definition."""
|
|
157
|
+
stop_analysis()
|
|
158
|
+
analyze_and_publish(ls, params.text_document.uri, level=1)
|
|
159
|
+
return ls.get_definition(params.text_document.uri, params.position)
|
|
160
|
+
|
|
161
|
+
|
|
140
162
|
def run_lang_server() -> None:
|
|
141
163
|
"""Run the language server."""
|
|
142
164
|
server.start_io()
|
|
@@ -18,9 +18,9 @@ Below we have the demonstration of a class to calculate the area of a circle.
|
|
|
18
18
|
*#
|
|
19
19
|
|
|
20
20
|
"""Enum for shape types"""
|
|
21
|
-
enum ShapeType
|
|
22
|
-
CIRCLE="Circle",
|
|
23
|
-
UNKNOWN="Unknown"
|
|
21
|
+
enum ShapeType {
|
|
22
|
+
CIRCLE = "Circle",
|
|
23
|
+
UNKNOWN = "Unknown"
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
"""Base class for a shape."""
|
|
@@ -50,24 +50,28 @@ with entry {
|
|
|
50
50
|
# Global also works here
|
|
51
51
|
|
|
52
52
|
with entry:__main__ {
|
|
53
|
-
# To run the program functionality
|
|
54
|
-
print(
|
|
55
|
-
|
|
53
|
+
# To run the program functionality
|
|
54
|
+
print(
|
|
55
|
+
f"Area of a circle with radius {RAD} using function: {calculate_area(RAD)}"
|
|
56
|
+
);
|
|
57
|
+
print(
|
|
58
|
+
f"Area of a {c.shape_type.value} with radius {RAD} using class: {c.area()}"
|
|
59
|
+
);
|
|
56
60
|
}
|
|
57
61
|
# Unit Tests!
|
|
58
62
|
|
|
59
63
|
glob expected_area = 78.53981633974483;
|
|
60
64
|
|
|
61
|
-
test calc_area
|
|
62
|
-
check
|
|
65
|
+
test calc_area {
|
|
66
|
+
check assertAlmostEqual(calculate_area(RAD), expected_area);
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
test circle_area
|
|
69
|
+
test circle_area {
|
|
66
70
|
c = Circle(RAD);
|
|
67
|
-
check
|
|
71
|
+
check assertAlmostEqual(c.area(), expected_area);
|
|
68
72
|
}
|
|
69
73
|
|
|
70
|
-
test circle_type
|
|
74
|
+
test circle_type {
|
|
71
75
|
c = Circle(RAD);
|
|
72
|
-
check
|
|
76
|
+
check assertEqual(c.shape_type, ShapeType.CIRCLE);
|
|
73
77
|
}
|
|
@@ -59,15 +59,15 @@ print(f"Area of a {c.shape_type.value} with radius {RAD} using class: {c.area()}
|
|
|
59
59
|
glob expected_area = 78.53981633974483;
|
|
60
60
|
|
|
61
61
|
test calc_area {
|
|
62
|
-
check
|
|
62
|
+
check assertAlmostEqual(calculate_area(RAD), expected_area);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
test circle_area {
|
|
66
66
|
c = Circle(RAD);
|
|
67
|
-
check
|
|
67
|
+
check assertAlmostEqual(c.area(), expected_area);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
test circle_type {
|
|
71
71
|
c = Circle(RAD);
|
|
72
|
-
check
|
|
72
|
+
check assertEqual(c.shape_type, ShapeType.CIRCLE);
|
|
73
73
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""Enum for shape types"""
|
|
2
2
|
|
|
3
3
|
:enum:ShapeType {
|
|
4
|
-
CIRCLE="Circle",
|
|
5
|
-
UNKNOWN="Unknown"
|
|
4
|
+
CIRCLE = "Circle",
|
|
5
|
+
UNKNOWN = "Unknown"
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
"""Function to calculate the area of a circle."""
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
:can:main_run {
|
|
26
|
-
print(
|
|
27
|
-
|
|
26
|
+
print(
|
|
27
|
+
f"Area of a circle with radius {RAD} using function: {calculate_area(RAD)}"
|
|
28
|
+
);
|
|
29
|
+
print(
|
|
30
|
+
f"Area of a {c.shape_type.value} with radius {RAD} using class: {c.area()}"
|
|
31
|
+
);
|
|
28
32
|
}
|
|
@@ -11,7 +11,7 @@ can calculate_area(radius: float) -> float;
|
|
|
11
11
|
can main_run;
|
|
12
12
|
|
|
13
13
|
"""Base class for a shape."""
|
|
14
|
-
obj Shape {
|
|
14
|
+
obj : priv Shape {
|
|
15
15
|
has shape_type: ShapeType;
|
|
16
16
|
|
|
17
17
|
can area -> float abs;
|
|
@@ -22,7 +22,7 @@ obj Circle :Shape: {
|
|
|
22
22
|
has radius: float;
|
|
23
23
|
|
|
24
24
|
can init(radius: float);
|
|
25
|
-
can area -> float;
|
|
25
|
+
override can area -> float;
|
|
26
26
|
}
|
|
27
27
|
# Radius of the demo circle
|
|
28
28
|
|
|
@@ -4,6 +4,8 @@ from jaclang.vendor.pygls.workspace import Workspace
|
|
|
4
4
|
from jaclang.langserve.engine import JacLangServer
|
|
5
5
|
from .session import LspSession
|
|
6
6
|
|
|
7
|
+
import lsprotocol.types as lspt
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
class TestJacLangServer(TestCase):
|
|
9
11
|
|
|
@@ -66,3 +68,115 @@ class TestJacLangServer(TestCase):
|
|
|
66
68
|
lsp.type_check(circle_file)
|
|
67
69
|
self.assertEqual(len(lsp.modules), 1)
|
|
68
70
|
self.assertEqual(lsp.modules[circle_file].diagnostics[0].range.start.line, 22)
|
|
71
|
+
|
|
72
|
+
def test_impl_stay_connected(self) -> None:
|
|
73
|
+
"""Test that the server doesn't run if there is a syntax error."""
|
|
74
|
+
lsp = JacLangServer()
|
|
75
|
+
# Set up the workspace path to "fixtures/"
|
|
76
|
+
workspace_path = self.fixture_abs_path("")
|
|
77
|
+
workspace = Workspace(workspace_path, lsp)
|
|
78
|
+
lsp.lsp._workspace = workspace
|
|
79
|
+
circle_file = uris.from_fs_path(self.fixture_abs_path("circle_pure.jac"))
|
|
80
|
+
circle_impl_file = uris.from_fs_path(
|
|
81
|
+
self.fixture_abs_path("circle_pure.impl.jac")
|
|
82
|
+
)
|
|
83
|
+
lsp.quick_check(circle_file)
|
|
84
|
+
lsp.deep_check(circle_file)
|
|
85
|
+
lsp.type_check(circle_file)
|
|
86
|
+
pos = lspt.Position(20, 8)
|
|
87
|
+
self.assertIn(
|
|
88
|
+
"Circle class inherits from Shape.",
|
|
89
|
+
lsp.get_hover_info(circle_file, pos).contents.value,
|
|
90
|
+
)
|
|
91
|
+
lsp.type_check(circle_impl_file, force=True)
|
|
92
|
+
pos = lspt.Position(8, 11)
|
|
93
|
+
self.assertIn(
|
|
94
|
+
"ability) calculate_area: float",
|
|
95
|
+
lsp.get_hover_info(circle_impl_file, pos).contents.value,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def test_impl_auto_discover(self) -> None:
|
|
99
|
+
"""Test that the server doesn't run if there is a syntax error."""
|
|
100
|
+
lsp = JacLangServer()
|
|
101
|
+
# Set up the workspace path to "fixtures/"
|
|
102
|
+
workspace_path = self.fixture_abs_path("")
|
|
103
|
+
workspace = Workspace(workspace_path, lsp)
|
|
104
|
+
lsp.lsp._workspace = workspace
|
|
105
|
+
circle_impl_file = uris.from_fs_path(
|
|
106
|
+
self.fixture_abs_path("circle_pure.impl.jac")
|
|
107
|
+
)
|
|
108
|
+
lsp.quick_check(circle_impl_file, force=True)
|
|
109
|
+
lsp.deep_check(circle_impl_file, force=True)
|
|
110
|
+
lsp.type_check(circle_impl_file, force=True)
|
|
111
|
+
pos = lspt.Position(8, 11)
|
|
112
|
+
self.assertIn(
|
|
113
|
+
"ability) calculate_area: float",
|
|
114
|
+
lsp.get_hover_info(circle_impl_file, pos).contents.value,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def test_show_type_impl(self) -> None:
|
|
118
|
+
"""Test that the server doesn't run if there is a syntax error."""
|
|
119
|
+
lsp = JacLangServer()
|
|
120
|
+
# Set up the workspace path to "fixtures/"
|
|
121
|
+
workspace_path = self.fixture_abs_path("")
|
|
122
|
+
workspace = Workspace(workspace_path, lsp)
|
|
123
|
+
lsp.lsp._workspace = workspace
|
|
124
|
+
target = uris.from_fs_path(
|
|
125
|
+
self.fixture_abs_path("../../../../examples/guess_game/guess_game4.jac")
|
|
126
|
+
)
|
|
127
|
+
lsp.quick_check(target)
|
|
128
|
+
lsp.deep_check(target)
|
|
129
|
+
lsp.type_check(target)
|
|
130
|
+
pos = lspt.Position(43, 18)
|
|
131
|
+
self.assertIn(
|
|
132
|
+
"attempts: int",
|
|
133
|
+
lsp.get_hover_info(target, pos).contents.value,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def test_outline_symbols(self) -> None:
|
|
137
|
+
"""Test that the outline symbols are correct."""
|
|
138
|
+
lsp = JacLangServer()
|
|
139
|
+
workspace_path = self.fixture_abs_path("")
|
|
140
|
+
workspace = Workspace(workspace_path, lsp)
|
|
141
|
+
lsp.lsp._workspace = workspace
|
|
142
|
+
circle_file = uris.from_fs_path(self.fixture_abs_path("circle_pure.jac"))
|
|
143
|
+
lsp.quick_check(circle_file)
|
|
144
|
+
lsp.deep_check(circle_file)
|
|
145
|
+
lsp.type_check(circle_file)
|
|
146
|
+
self.assertEqual(8, len(lsp.get_document_symbols(circle_file)))
|
|
147
|
+
|
|
148
|
+
def test_go_to_definition(self) -> None:
|
|
149
|
+
"""Test that the go to definition is correct."""
|
|
150
|
+
lsp = JacLangServer()
|
|
151
|
+
workspace_path = self.fixture_abs_path("")
|
|
152
|
+
workspace = Workspace(workspace_path, lsp)
|
|
153
|
+
lsp.lsp._workspace = workspace
|
|
154
|
+
circle_file = uris.from_fs_path(self.fixture_abs_path("circle_pure.jac"))
|
|
155
|
+
lsp.quick_check(circle_file)
|
|
156
|
+
lsp.deep_check(circle_file)
|
|
157
|
+
lsp.type_check(circle_file)
|
|
158
|
+
self.assertIn(
|
|
159
|
+
"fixtures/circle_pure.impl.jac:8:0-8:19",
|
|
160
|
+
str(lsp.get_definition(circle_file, lspt.Position(9, 16))),
|
|
161
|
+
)
|
|
162
|
+
self.assertIn(
|
|
163
|
+
"fixtures/circle_pure.jac:13:11-13:16",
|
|
164
|
+
str(lsp.get_definition(circle_file, lspt.Position(20, 17))),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def test_go_to_definition_method(self) -> None:
|
|
168
|
+
"""Test that the go to definition is correct."""
|
|
169
|
+
lsp = JacLangServer()
|
|
170
|
+
workspace_path = self.fixture_abs_path("")
|
|
171
|
+
workspace = Workspace(workspace_path, lsp)
|
|
172
|
+
lsp.lsp._workspace = workspace
|
|
173
|
+
guess_game_file = uris.from_fs_path(
|
|
174
|
+
self.fixture_abs_path("../../../../examples/guess_game/guess_game4.jac")
|
|
175
|
+
)
|
|
176
|
+
lsp.quick_check(guess_game_file)
|
|
177
|
+
lsp.deep_check(guess_game_file)
|
|
178
|
+
lsp.type_check(guess_game_file)
|
|
179
|
+
self.assertIn(
|
|
180
|
+
"guess_game4.jac:27:8-27:21",
|
|
181
|
+
str(lsp.get_definition(guess_game_file, lspt.Position(46, 45))),
|
|
182
|
+
)
|