jaclang 0.7.2__py3-none-any.whl → 0.7.8__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/__init__.py +11 -12
- jaclang/compiler/absyntree.py +499 -294
- jaclang/compiler/codeloc.py +2 -2
- jaclang/compiler/constant.py +100 -2
- jaclang/compiler/jac.lark +27 -19
- jaclang/compiler/parser.py +119 -92
- jaclang/compiler/passes/main/access_modifier_pass.py +20 -12
- jaclang/compiler/passes/main/def_impl_match_pass.py +28 -14
- jaclang/compiler/passes/main/def_use_pass.py +59 -40
- jaclang/compiler/passes/main/fuse_typeinfo_pass.py +65 -43
- jaclang/compiler/passes/main/import_pass.py +8 -6
- jaclang/compiler/passes/main/pyast_gen_pass.py +97 -42
- jaclang/compiler/passes/main/pyast_load_pass.py +47 -12
- jaclang/compiler/passes/main/pyjac_ast_link_pass.py +19 -10
- jaclang/compiler/passes/main/registry_pass.py +6 -6
- jaclang/compiler/passes/main/sub_node_tab_pass.py +0 -5
- jaclang/compiler/passes/main/sym_tab_build_pass.py +43 -235
- 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/type_check_pass.py +2 -1
- jaclang/compiler/passes/tool/jac_formatter_pass.py +30 -9
- 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/genai/essay_review.jac +1 -1
- jaclang/compiler/passes/tool/tests/fixtures/genai/expert_answer.jac +1 -1
- jaclang/compiler/passes/tool/tests/fixtures/genai/joke_gen.jac +1 -1
- jaclang/compiler/passes/tool/tests/fixtures/genai/odd_word_out.jac +1 -1
- jaclang/compiler/passes/tool/tests/fixtures/genai/personality_finder.jac +1 -1
- jaclang/compiler/passes/tool/tests/fixtures/genai/text_to_type.jac +1 -1
- jaclang/compiler/passes/tool/tests/fixtures/genai/translator.jac +1 -1
- jaclang/compiler/passes/tool/tests/fixtures/genai/wikipedia.jac +1 -1
- jaclang/compiler/passes/transform.py +2 -4
- jaclang/compiler/passes/utils/mypy_ast_build.py +1 -8
- jaclang/{core/registry.py → compiler/semtable.py} +1 -3
- jaclang/compiler/symtable.py +142 -101
- jaclang/compiler/tests/test_parser.py +2 -2
- jaclang/core/{construct.py → architype.py} +25 -240
- jaclang/core/constructs.py +44 -0
- jaclang/core/context.py +157 -0
- jaclang/core/importer.py +18 -9
- jaclang/core/memory.py +99 -0
- jaclang/core/test.py +90 -0
- jaclang/core/utils.py +2 -2
- jaclang/langserve/engine.py +127 -50
- jaclang/langserve/server.py +34 -61
- jaclang/langserve/tests/fixtures/base_module_structure.jac +28 -0
- jaclang/langserve/tests/fixtures/circle.jac +16 -12
- jaclang/langserve/tests/fixtures/circle_err.jac +3 -3
- jaclang/langserve/tests/fixtures/circle_pure.test.jac +15 -0
- jaclang/langserve/tests/fixtures/import_include_statements.jac +6 -0
- jaclang/langserve/tests/fixtures/py_import.py +26 -0
- jaclang/langserve/tests/test_server.py +93 -18
- jaclang/langserve/utils.py +124 -10
- jaclang/plugin/builtin.py +1 -1
- jaclang/plugin/default.py +25 -77
- jaclang/plugin/feature.py +25 -7
- jaclang/plugin/spec.py +18 -20
- jaclang/settings.py +3 -0
- jaclang/tests/fixtures/abc.jac +16 -12
- jaclang/tests/fixtures/aott_raise.jac +1 -1
- jaclang/tests/fixtures/byllmissue.jac +9 -0
- jaclang/tests/fixtures/edgetypeissue.jac +10 -0
- jaclang/tests/fixtures/hello.jac +1 -1
- jaclang/tests/fixtures/impl_match_confused.impl.jac +1 -0
- jaclang/tests/fixtures/impl_match_confused.jac +5 -0
- jaclang/tests/fixtures/maxfail_run_test.jac +17 -5
- jaclang/tests/fixtures/run_test.jac +17 -5
- jaclang/tests/test_bugs.py +19 -0
- jaclang/tests/test_cli.py +1 -1
- jaclang/tests/test_language.py +65 -100
- jaclang/tests/test_reference.py +1 -1
- jaclang/utils/lang_tools.py +5 -4
- jaclang/utils/test.py +2 -1
- jaclang/utils/treeprinter.py +22 -8
- {jaclang-0.7.2.dist-info → jaclang-0.7.8.dist-info}/METADATA +1 -1
- {jaclang-0.7.2.dist-info → jaclang-0.7.8.dist-info}/RECORD +79 -83
- jaclang/core/aott.py +0 -310
- jaclang/core/llms/__init__.py +0 -20
- jaclang/core/llms/anthropic.py +0 -90
- jaclang/core/llms/base.py +0 -206
- jaclang/core/llms/groq.py +0 -70
- jaclang/core/llms/huggingface.py +0 -76
- jaclang/core/llms/ollama.py +0 -81
- jaclang/core/llms/openai.py +0 -65
- jaclang/core/llms/togetherai.py +0 -63
- jaclang/core/llms/utils.py +0 -9
- jaclang/tests/fixtures/math_question.jpg +0 -0
- jaclang/tests/fixtures/with_llm_function.jac +0 -33
- jaclang/tests/fixtures/with_llm_lower.jac +0 -45
- jaclang/tests/fixtures/with_llm_method.jac +0 -51
- jaclang/tests/fixtures/with_llm_type.jac +0 -52
- jaclang/tests/fixtures/with_llm_vision.jac +0 -25
- {jaclang-0.7.2.dist-info → jaclang-0.7.8.dist-info}/WHEEL +0 -0
- {jaclang-0.7.2.dist-info → jaclang-0.7.8.dist-info}/entry_points.txt +0 -0
jaclang/core/test.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Core constructs for Jac Language."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
from typing import Callable, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class JacTestResult(unittest.TextTestResult):
|
|
10
|
+
"""Jac test result class."""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
stream, # noqa
|
|
15
|
+
descriptions, # noqa
|
|
16
|
+
verbosity: int,
|
|
17
|
+
max_failures: Optional[int] = None,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Initialize FailFastTestResult object."""
|
|
20
|
+
super().__init__(stream, descriptions, verbosity) # noqa
|
|
21
|
+
self.failures_count = JacTestCheck.failcount
|
|
22
|
+
self.max_failures = max_failures
|
|
23
|
+
|
|
24
|
+
def addFailure(self, test, err) -> None: # noqa
|
|
25
|
+
"""Count failures and stop."""
|
|
26
|
+
super().addFailure(test, err)
|
|
27
|
+
self.failures_count += 1
|
|
28
|
+
if self.max_failures is not None and self.failures_count >= self.max_failures:
|
|
29
|
+
self.stop()
|
|
30
|
+
|
|
31
|
+
def stop(self) -> None:
|
|
32
|
+
"""Stop the test execution."""
|
|
33
|
+
self.shouldStop = True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class JacTextTestRunner(unittest.TextTestRunner):
|
|
37
|
+
"""Jac test runner class."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, max_failures: Optional[int] = None, **kwargs) -> None: # noqa
|
|
40
|
+
"""Initialize JacTextTestRunner object."""
|
|
41
|
+
self.max_failures = max_failures
|
|
42
|
+
super().__init__(**kwargs)
|
|
43
|
+
|
|
44
|
+
def _makeResult(self) -> JacTestResult: # noqa
|
|
45
|
+
"""Override the method to return an instance of JacTestResult."""
|
|
46
|
+
return JacTestResult(
|
|
47
|
+
self.stream,
|
|
48
|
+
self.descriptions,
|
|
49
|
+
self.verbosity,
|
|
50
|
+
max_failures=self.max_failures,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class JacTestCheck:
|
|
55
|
+
"""Jac Testing and Checking."""
|
|
56
|
+
|
|
57
|
+
test_case = unittest.TestCase()
|
|
58
|
+
test_suite = unittest.TestSuite()
|
|
59
|
+
breaker = False
|
|
60
|
+
failcount = 0
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def reset() -> None:
|
|
64
|
+
"""Clear the test suite."""
|
|
65
|
+
JacTestCheck.test_case = unittest.TestCase()
|
|
66
|
+
JacTestCheck.test_suite = unittest.TestSuite()
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def run_test(xit: bool, maxfail: int | None, verbose: bool) -> None:
|
|
70
|
+
"""Run the test suite."""
|
|
71
|
+
verb = 2 if verbose else 1
|
|
72
|
+
runner = JacTextTestRunner(max_failures=maxfail, failfast=xit, verbosity=verb)
|
|
73
|
+
result = runner.run(JacTestCheck.test_suite)
|
|
74
|
+
if result.wasSuccessful():
|
|
75
|
+
print("Passed successfully.")
|
|
76
|
+
else:
|
|
77
|
+
fails = len(result.failures)
|
|
78
|
+
JacTestCheck.failcount += fails
|
|
79
|
+
JacTestCheck.breaker = (
|
|
80
|
+
(JacTestCheck.failcount >= maxfail) if maxfail else True
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def add_test(test_fun: Callable) -> None:
|
|
85
|
+
"""Create a new test."""
|
|
86
|
+
JacTestCheck.test_suite.addTest(unittest.FunctionTestCase(test_fun))
|
|
87
|
+
|
|
88
|
+
def __getattr__(self, name: str) -> object:
|
|
89
|
+
"""Make convenient check.Equal(...) etc."""
|
|
90
|
+
return getattr(JacTestCheck.test_case, name)
|
jaclang/core/utils.py
CHANGED
|
@@ -8,10 +8,10 @@ from contextlib import contextmanager
|
|
|
8
8
|
from typing import Callable, Iterator, TYPE_CHECKING
|
|
9
9
|
|
|
10
10
|
import jaclang.compiler.absyntree as ast
|
|
11
|
-
from jaclang.
|
|
11
|
+
from jaclang.compiler.semtable import SemScope
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
|
-
from jaclang.core.
|
|
14
|
+
from jaclang.core.constructs import NodeAnchor, NodeArchitype
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@contextmanager
|
jaclang/langserve/engine.py
CHANGED
|
@@ -4,8 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
from enum import IntEnum
|
|
7
|
-
from
|
|
8
|
-
from typing import Optional, Sequence
|
|
7
|
+
from typing import Optional
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
import jaclang.compiler.absyntree as ast
|
|
@@ -19,6 +18,8 @@ from jaclang.langserve.utils import (
|
|
|
19
18
|
collect_symbols,
|
|
20
19
|
create_range,
|
|
21
20
|
find_deepest_symbol_node_at_pos,
|
|
21
|
+
get_item_path,
|
|
22
|
+
get_mod_path,
|
|
22
23
|
)
|
|
23
24
|
from jaclang.vendor.pygls import uris
|
|
24
25
|
from jaclang.vendor.pygls.server import LanguageServer
|
|
@@ -27,7 +28,7 @@ import lsprotocol.types as lspt
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
class ALev(IntEnum):
|
|
30
|
-
"""Analysis Level."""
|
|
31
|
+
"""Analysis Level successfully completed."""
|
|
31
32
|
|
|
32
33
|
QUICK = 1
|
|
33
34
|
DEEP = 2
|
|
@@ -40,8 +41,8 @@ class ModuleInfo:
|
|
|
40
41
|
def __init__(
|
|
41
42
|
self,
|
|
42
43
|
ir: ast.Module,
|
|
43
|
-
errors:
|
|
44
|
-
warnings:
|
|
44
|
+
errors: list[Alert],
|
|
45
|
+
warnings: list[Alert],
|
|
45
46
|
alev: ALev,
|
|
46
47
|
parent: Optional[ModuleInfo] = None,
|
|
47
48
|
) -> None:
|
|
@@ -52,6 +53,7 @@ class ModuleInfo:
|
|
|
52
53
|
self.alev = alev
|
|
53
54
|
self.parent: Optional[ModuleInfo] = parent
|
|
54
55
|
self.diagnostics = self.gen_diagnostics()
|
|
56
|
+
self.sem_tokens: list[int] = self.gen_sem_tokens()
|
|
55
57
|
|
|
56
58
|
@property
|
|
57
59
|
def uri(self) -> str:
|
|
@@ -63,6 +65,20 @@ class ModuleInfo:
|
|
|
63
65
|
"""Return if there are syntax errors."""
|
|
64
66
|
return len(self.errors) > 0 and self.alev == ALev.QUICK
|
|
65
67
|
|
|
68
|
+
def update_with(self, new_info: ModuleInfo, refresh: bool = False) -> None:
|
|
69
|
+
"""Update module info."""
|
|
70
|
+
self.ir = new_info.ir
|
|
71
|
+
if refresh:
|
|
72
|
+
self.errors = new_info.errors
|
|
73
|
+
self.warnings = new_info.warnings
|
|
74
|
+
else:
|
|
75
|
+
self.errors += [i for i in new_info.errors if i not in self.errors]
|
|
76
|
+
self.warnings += [i for i in new_info.warnings if i not in self.warnings]
|
|
77
|
+
self.alev = new_info.alev
|
|
78
|
+
self.diagnostics = self.gen_diagnostics()
|
|
79
|
+
if self.alev == ALev.TYPE:
|
|
80
|
+
self.sem_tokens = self.gen_sem_tokens()
|
|
81
|
+
|
|
66
82
|
def gen_diagnostics(self) -> list[lspt.Diagnostic]:
|
|
67
83
|
"""Return diagnostics."""
|
|
68
84
|
return [
|
|
@@ -81,6 +97,27 @@ class ModuleInfo:
|
|
|
81
97
|
for warning in self.warnings
|
|
82
98
|
]
|
|
83
99
|
|
|
100
|
+
def gen_sem_tokens(self) -> list[int]:
|
|
101
|
+
"""Return semantic tokens."""
|
|
102
|
+
tokens = []
|
|
103
|
+
prev_line, prev_col = 0, 0
|
|
104
|
+
for node in self.ir._in_mod_nodes:
|
|
105
|
+
if isinstance(node, ast.NameAtom) and node.sem_token:
|
|
106
|
+
line, col_start, col_end = (
|
|
107
|
+
node.loc.first_line - 1,
|
|
108
|
+
node.loc.col_start - 1,
|
|
109
|
+
node.loc.col_end - 1,
|
|
110
|
+
)
|
|
111
|
+
length = col_end - col_start
|
|
112
|
+
tokens += [
|
|
113
|
+
line - prev_line,
|
|
114
|
+
col_start if line != prev_line else col_start - prev_col,
|
|
115
|
+
length,
|
|
116
|
+
*node.sem_token,
|
|
117
|
+
]
|
|
118
|
+
prev_line, prev_col = line, col_start
|
|
119
|
+
return tokens
|
|
120
|
+
|
|
84
121
|
|
|
85
122
|
class JacLangServer(LanguageServer):
|
|
86
123
|
"""Class for managing workspace."""
|
|
@@ -90,19 +127,6 @@ class JacLangServer(LanguageServer):
|
|
|
90
127
|
super().__init__("jac-lsp", "v0.1")
|
|
91
128
|
self.modules: dict[str, ModuleInfo] = {}
|
|
92
129
|
|
|
93
|
-
def module_not_diff(self, uri: str, alev: ALev) -> bool:
|
|
94
|
-
"""Check if module was changed."""
|
|
95
|
-
doc = self.workspace.get_text_document(uri)
|
|
96
|
-
return (
|
|
97
|
-
doc.uri in self.modules
|
|
98
|
-
and self.modules[doc.uri].ir.source.hash
|
|
99
|
-
== md5(doc.source.encode()).hexdigest()
|
|
100
|
-
and (
|
|
101
|
-
self.modules[doc.uri].alev >= alev
|
|
102
|
-
or self.modules[doc.uri].has_syntax_error
|
|
103
|
-
)
|
|
104
|
-
)
|
|
105
|
-
|
|
106
130
|
def push_diagnostics(self, file_path: str) -> None:
|
|
107
131
|
"""Push diagnostics for a file."""
|
|
108
132
|
if file_path in self.modules:
|
|
@@ -124,15 +148,14 @@ class JacLangServer(LanguageServer):
|
|
|
124
148
|
self.quick_check(file_path)
|
|
125
149
|
return file_path
|
|
126
150
|
|
|
127
|
-
def update_modules(
|
|
151
|
+
def update_modules(
|
|
152
|
+
self, file_path: str, build: Pass, alev: ALev, refresh: bool = False
|
|
153
|
+
) -> None:
|
|
128
154
|
"""Update modules."""
|
|
129
155
|
if not isinstance(build.ir, ast.Module):
|
|
130
156
|
self.log_error("Error with module build.")
|
|
131
157
|
return
|
|
132
|
-
|
|
133
|
-
self.modules[file_path].parent if file_path in self.modules else None
|
|
134
|
-
)
|
|
135
|
-
self.modules[file_path] = ModuleInfo(
|
|
158
|
+
new_mod = ModuleInfo(
|
|
136
159
|
ir=build.ir,
|
|
137
160
|
errors=[
|
|
138
161
|
i
|
|
@@ -146,23 +169,28 @@ class JacLangServer(LanguageServer):
|
|
|
146
169
|
],
|
|
147
170
|
alev=alev,
|
|
148
171
|
)
|
|
149
|
-
self.modules
|
|
172
|
+
if file_path in self.modules:
|
|
173
|
+
self.modules[file_path].update_with(new_mod, refresh=refresh)
|
|
174
|
+
else:
|
|
175
|
+
self.modules[file_path] = new_mod
|
|
150
176
|
for p in build.ir.mod_deps.keys():
|
|
151
177
|
uri = uris.from_fs_path(p)
|
|
152
|
-
|
|
178
|
+
new_mod = ModuleInfo(
|
|
153
179
|
ir=build.ir.mod_deps[p],
|
|
154
180
|
errors=[i for i in build.errors_had if i.loc.mod_path == p],
|
|
155
181
|
warnings=[i for i in build.warnings_had if i.loc.mod_path == p],
|
|
156
182
|
alev=alev,
|
|
157
183
|
)
|
|
184
|
+
if not refresh and uri in self.modules:
|
|
185
|
+
self.modules[uri].update_with(new_mod)
|
|
186
|
+
else:
|
|
187
|
+
self.modules[uri] = new_mod
|
|
158
188
|
self.modules[uri].parent = (
|
|
159
189
|
self.modules[file_path] if file_path != uri else None
|
|
160
190
|
)
|
|
161
191
|
|
|
162
|
-
def quick_check(self, file_path: str
|
|
192
|
+
def quick_check(self, file_path: str) -> bool:
|
|
163
193
|
"""Rebuild a file."""
|
|
164
|
-
if not force and self.module_not_diff(file_path, ALev.QUICK):
|
|
165
|
-
return
|
|
166
194
|
try:
|
|
167
195
|
document = self.workspace.get_text_document(file_path)
|
|
168
196
|
build = jac_str_to_pass(
|
|
@@ -170,27 +198,27 @@ class JacLangServer(LanguageServer):
|
|
|
170
198
|
)
|
|
171
199
|
except Exception as e:
|
|
172
200
|
self.log_error(f"Error during syntax check: {e}")
|
|
173
|
-
|
|
201
|
+
return False
|
|
202
|
+
self.update_modules(file_path, build, ALev.QUICK, refresh=True)
|
|
203
|
+
return len(self.modules[file_path].errors) == 0
|
|
174
204
|
|
|
175
|
-
def deep_check(self, file_path: str
|
|
205
|
+
def deep_check(self, file_path: str) -> bool:
|
|
176
206
|
"""Rebuild a file and its dependencies."""
|
|
177
|
-
if file_path in self.modules:
|
|
178
|
-
self.quick_check(file_path
|
|
179
|
-
if not force and self.module_not_diff(file_path, ALev.DEEP):
|
|
180
|
-
return
|
|
207
|
+
if file_path not in self.modules:
|
|
208
|
+
self.quick_check(file_path)
|
|
181
209
|
try:
|
|
182
210
|
file_path = self.unwind_to_parent(file_path)
|
|
183
211
|
build = jac_ir_to_pass(ir=self.modules[file_path].ir)
|
|
184
212
|
except Exception as e:
|
|
185
213
|
self.log_error(f"Error during syntax check: {e}")
|
|
214
|
+
return False
|
|
186
215
|
self.update_modules(file_path, build, ALev.DEEP)
|
|
216
|
+
return len(self.modules[file_path].errors) == 0
|
|
187
217
|
|
|
188
|
-
def type_check(self, file_path: str
|
|
218
|
+
def type_check(self, file_path: str) -> bool:
|
|
189
219
|
"""Rebuild a file and its dependencies."""
|
|
190
220
|
if file_path not in self.modules:
|
|
191
|
-
self.deep_check(file_path
|
|
192
|
-
if not force and self.module_not_diff(file_path, ALev.TYPE):
|
|
193
|
-
return
|
|
221
|
+
self.deep_check(file_path)
|
|
194
222
|
try:
|
|
195
223
|
file_path = self.unwind_to_parent(file_path)
|
|
196
224
|
build = jac_ir_to_pass(
|
|
@@ -198,7 +226,21 @@ class JacLangServer(LanguageServer):
|
|
|
198
226
|
)
|
|
199
227
|
except Exception as e:
|
|
200
228
|
self.log_error(f"Error during type check: {e}")
|
|
229
|
+
return False
|
|
201
230
|
self.update_modules(file_path, build, ALev.TYPE)
|
|
231
|
+
return len(self.modules[file_path].errors) == 0
|
|
232
|
+
|
|
233
|
+
def analyze_and_publish(self, uri: str, level: int = 2) -> None:
|
|
234
|
+
"""Analyze and publish diagnostics."""
|
|
235
|
+
self.log_py(f"Analyzing {uri}...")
|
|
236
|
+
success = self.quick_check(uri)
|
|
237
|
+
self.push_diagnostics(uri)
|
|
238
|
+
if success and level > 0:
|
|
239
|
+
success = self.deep_check(uri)
|
|
240
|
+
self.push_diagnostics(uri)
|
|
241
|
+
if level > 1:
|
|
242
|
+
self.type_check(uri)
|
|
243
|
+
self.push_diagnostics(uri)
|
|
202
244
|
|
|
203
245
|
def get_completion(
|
|
204
246
|
self, file_path: str, position: lspt.Position
|
|
@@ -275,21 +317,20 @@ class JacLangServer(LanguageServer):
|
|
|
275
317
|
def get_node_info(self, node: ast.AstSymbolNode) -> Optional[str]:
|
|
276
318
|
"""Extract meaningful information from the AST node."""
|
|
277
319
|
try:
|
|
278
|
-
if isinstance(node, ast.
|
|
320
|
+
if isinstance(node, ast.NameAtom):
|
|
279
321
|
node = node.name_of
|
|
280
|
-
access = node.
|
|
322
|
+
access = node.sym.access.value + " " if node.sym else None
|
|
281
323
|
node_info = (
|
|
282
|
-
f"({access if access else ''}{node.
|
|
324
|
+
f"({access if access else ''}{node.sym_category.value}) {node.sym_name}"
|
|
283
325
|
)
|
|
284
|
-
if node.
|
|
285
|
-
node_info += f": {node.
|
|
326
|
+
if node.name_spec.clean_type:
|
|
327
|
+
node_info += f": {node.name_spec.clean_type}"
|
|
286
328
|
if isinstance(node, ast.AstSemStrNode) and node.semstr:
|
|
287
329
|
node_info += f"\n{node.semstr.value}"
|
|
288
330
|
if isinstance(node, ast.AstDocNode) and node.doc:
|
|
289
331
|
node_info += f"\n{node.doc.value}"
|
|
290
332
|
if isinstance(node, ast.Ability) and node.signature:
|
|
291
333
|
node_info += f"\n{node.signature.unparse()}"
|
|
292
|
-
self.log_py(node.pp())
|
|
293
334
|
self.log_py(f"mypy_node: {node.gen.mypy_ast}")
|
|
294
335
|
except AttributeError as e:
|
|
295
336
|
self.log_warning(f"Attribute error when accessing node attributes: {e}")
|
|
@@ -297,8 +338,7 @@ class JacLangServer(LanguageServer):
|
|
|
297
338
|
|
|
298
339
|
def get_document_symbols(self, file_path: str) -> list[lspt.DocumentSymbol]:
|
|
299
340
|
"""Return document symbols for a file."""
|
|
300
|
-
root_node
|
|
301
|
-
if root_node:
|
|
341
|
+
if root_node := self.modules[file_path].ir._sym_tab:
|
|
302
342
|
return collect_symbols(root_node)
|
|
303
343
|
return []
|
|
304
344
|
|
|
@@ -310,7 +350,39 @@ class JacLangServer(LanguageServer):
|
|
|
310
350
|
self.modules[file_path].ir, position.line, position.character
|
|
311
351
|
)
|
|
312
352
|
if node_selected:
|
|
313
|
-
if
|
|
353
|
+
if (
|
|
354
|
+
isinstance(node_selected, ast.Name)
|
|
355
|
+
and node_selected.parent
|
|
356
|
+
and isinstance(node_selected.parent, ast.ModulePath)
|
|
357
|
+
):
|
|
358
|
+
spec = get_mod_path(node_selected.parent, node_selected)
|
|
359
|
+
if spec:
|
|
360
|
+
return lspt.Location(
|
|
361
|
+
uri=uris.from_fs_path(spec),
|
|
362
|
+
range=lspt.Range(
|
|
363
|
+
start=lspt.Position(line=0, character=0),
|
|
364
|
+
end=lspt.Position(line=0, character=0),
|
|
365
|
+
),
|
|
366
|
+
)
|
|
367
|
+
else:
|
|
368
|
+
return None
|
|
369
|
+
elif node_selected.parent and isinstance(
|
|
370
|
+
node_selected.parent, ast.ModuleItem
|
|
371
|
+
):
|
|
372
|
+
path_range = get_item_path(node_selected.parent)
|
|
373
|
+
if path_range:
|
|
374
|
+
path, range = path_range
|
|
375
|
+
if path and range:
|
|
376
|
+
return lspt.Location(
|
|
377
|
+
uri=uris.from_fs_path(path),
|
|
378
|
+
range=lspt.Range(
|
|
379
|
+
start=lspt.Position(line=range[0], character=0),
|
|
380
|
+
end=lspt.Position(line=range[1], character=5),
|
|
381
|
+
),
|
|
382
|
+
)
|
|
383
|
+
else:
|
|
384
|
+
return None
|
|
385
|
+
elif isinstance(node_selected, (ast.ElementStmt, ast.BuiltinType)):
|
|
314
386
|
return None
|
|
315
387
|
decl_node = (
|
|
316
388
|
node_selected.parent.body.target
|
|
@@ -318,8 +390,8 @@ class JacLangServer(LanguageServer):
|
|
|
318
390
|
and isinstance(node_selected.parent, ast.AstImplNeedingNode)
|
|
319
391
|
and isinstance(node_selected.parent.body, ast.AstImplOnlyNode)
|
|
320
392
|
else (
|
|
321
|
-
node_selected.
|
|
322
|
-
if (node_selected.
|
|
393
|
+
node_selected.sym.decl
|
|
394
|
+
if (node_selected.sym and node_selected.sym.decl)
|
|
323
395
|
else node_selected
|
|
324
396
|
)
|
|
325
397
|
)
|
|
@@ -336,9 +408,14 @@ class JacLangServer(LanguageServer):
|
|
|
336
408
|
|
|
337
409
|
return decl_location
|
|
338
410
|
else:
|
|
339
|
-
self.log_info("No declaration found for the selected node.")
|
|
340
411
|
return None
|
|
341
412
|
|
|
413
|
+
def get_semantic_tokens(self, file_path: str) -> lspt.SemanticTokens:
|
|
414
|
+
"""Return semantic tokens for a file."""
|
|
415
|
+
if file_path not in self.modules:
|
|
416
|
+
return lspt.SemanticTokens(data=[])
|
|
417
|
+
return lspt.SemanticTokens(data=self.modules[file_path].sem_tokens)
|
|
418
|
+
|
|
342
419
|
def log_error(self, message: str) -> None:
|
|
343
420
|
"""Log an error message."""
|
|
344
421
|
self.show_message_log(message, lspt.MessageType.Error)
|
jaclang/langserve/server.py
CHANGED
|
@@ -2,68 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import threading
|
|
6
5
|
from typing import Optional
|
|
7
6
|
|
|
7
|
+
from jaclang.compiler.constant import (
|
|
8
|
+
JacSemTokenModifier as SemTokMod,
|
|
9
|
+
JacSemTokenType as SemTokType,
|
|
10
|
+
)
|
|
8
11
|
from jaclang.langserve.engine import JacLangServer
|
|
9
12
|
from jaclang.langserve.utils import debounce
|
|
10
13
|
|
|
11
14
|
import lsprotocol.types as lspt
|
|
12
15
|
|
|
13
16
|
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, level: int = 2) -> 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
|
-
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)
|
|
31
|
-
|
|
32
|
-
analysis_thread = threading.Thread(target=run_analysis)
|
|
33
|
-
analysis_thread.start()
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def stop_analysis() -> None:
|
|
37
|
-
"""Stop analysis."""
|
|
38
|
-
global analysis_thread, analysis_stop_event
|
|
39
|
-
if analysis_thread is not None:
|
|
40
|
-
analysis_stop_event.set()
|
|
41
|
-
analysis_thread.join()
|
|
42
|
-
analysis_stop_event.clear()
|
|
43
17
|
|
|
44
18
|
|
|
45
19
|
@server.feature(lspt.TEXT_DOCUMENT_DID_OPEN)
|
|
46
|
-
|
|
20
|
+
@server.feature(lspt.TEXT_DOCUMENT_DID_SAVE)
|
|
21
|
+
def did_open(ls: JacLangServer, params: lspt.DidOpenTextDocumentParams) -> None:
|
|
47
22
|
"""Check syntax on change."""
|
|
48
|
-
|
|
49
|
-
|
|
23
|
+
ls.analyze_and_publish(params.text_document.uri)
|
|
24
|
+
# token_params = lspt.SemanticTokensParams(
|
|
25
|
+
# text_document=lspt.TextDocumentIdentifier(uri=params.text_document.uri)
|
|
26
|
+
# )
|
|
27
|
+
# tokens = semantic_tokens_full(ls, token_params)
|
|
28
|
+
# ls.send_notification("textDocument/publishSemanticTokens", tokens)
|
|
50
29
|
|
|
51
30
|
|
|
52
31
|
@server.feature(lspt.TEXT_DOCUMENT_DID_CHANGE)
|
|
53
32
|
@debounce(0.1)
|
|
54
|
-
async def did_change(
|
|
55
|
-
ls: JacLangServer, params: lspt.DidChangeTextDocumentParams
|
|
56
|
-
) -> None:
|
|
33
|
+
async def did_change(ls: JacLangServer, params: lspt.DidOpenTextDocumentParams) -> None:
|
|
57
34
|
"""Check syntax on change."""
|
|
58
|
-
|
|
59
|
-
analyze_and_publish(ls, params.text_document.uri)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@server.feature(lspt.TEXT_DOCUMENT_DID_SAVE)
|
|
63
|
-
async def did_save(ls: JacLangServer, params: lspt.DidSaveTextDocumentParams) -> None:
|
|
64
|
-
"""Check syntax on save."""
|
|
65
|
-
stop_analysis()
|
|
66
|
-
analyze_and_publish(ls, params.text_document.uri)
|
|
35
|
+
ls.analyze_and_publish(params.text_document.uri, level=0)
|
|
67
36
|
|
|
68
37
|
|
|
69
38
|
@server.feature(
|
|
@@ -74,11 +43,8 @@ async def did_save(ls: JacLangServer, params: lspt.DidSaveTextDocumentParams) ->
|
|
|
74
43
|
]
|
|
75
44
|
),
|
|
76
45
|
)
|
|
77
|
-
|
|
46
|
+
def did_create_files(ls: JacLangServer, params: lspt.CreateFilesParams) -> None:
|
|
78
47
|
"""Check syntax on file creation."""
|
|
79
|
-
for file in params.files:
|
|
80
|
-
ls.quick_check(file.uri)
|
|
81
|
-
ls.push_diagnostics(file.uri)
|
|
82
48
|
|
|
83
49
|
|
|
84
50
|
@server.feature(
|
|
@@ -89,13 +55,12 @@ async def did_create_files(ls: JacLangServer, params: lspt.CreateFilesParams) ->
|
|
|
89
55
|
]
|
|
90
56
|
),
|
|
91
57
|
)
|
|
92
|
-
|
|
58
|
+
def did_rename_files(ls: JacLangServer, params: lspt.RenameFilesParams) -> None:
|
|
93
59
|
"""Check syntax on file rename."""
|
|
94
60
|
new_uris = [file.new_uri for file in params.files]
|
|
95
61
|
old_uris = [file.old_uri for file in params.files]
|
|
96
62
|
for i in range(len(new_uris)):
|
|
97
63
|
ls.rename_module(old_uris[i], new_uris[i])
|
|
98
|
-
ls.quick_check(new_uris[i])
|
|
99
64
|
|
|
100
65
|
|
|
101
66
|
@server.feature(
|
|
@@ -106,7 +71,7 @@ async def did_rename_files(ls: JacLangServer, params: lspt.RenameFilesParams) ->
|
|
|
106
71
|
]
|
|
107
72
|
),
|
|
108
73
|
)
|
|
109
|
-
|
|
74
|
+
def did_delete_files(ls: JacLangServer, params: lspt.DeleteFilesParams) -> None:
|
|
110
75
|
"""Check syntax on file delete."""
|
|
111
76
|
for file in params.files:
|
|
112
77
|
ls.delete_module(file.uri)
|
|
@@ -116,9 +81,7 @@ async def did_delete_files(ls: JacLangServer, params: lspt.DeleteFilesParams) ->
|
|
|
116
81
|
lspt.TEXT_DOCUMENT_COMPLETION,
|
|
117
82
|
lspt.CompletionOptions(trigger_characters=[".", ":", ""]),
|
|
118
83
|
)
|
|
119
|
-
|
|
120
|
-
ls: JacLangServer, params: lspt.CompletionParams
|
|
121
|
-
) -> lspt.CompletionList:
|
|
84
|
+
def completion(ls: JacLangServer, params: lspt.CompletionParams) -> lspt.CompletionList:
|
|
122
85
|
"""Provide completion."""
|
|
123
86
|
return ls.get_completion(params.text_document.uri, params.position)
|
|
124
87
|
|
|
@@ -140,25 +103,35 @@ def hover(
|
|
|
140
103
|
|
|
141
104
|
|
|
142
105
|
@server.feature(lspt.TEXT_DOCUMENT_DOCUMENT_SYMBOL)
|
|
143
|
-
|
|
106
|
+
def document_symbol(
|
|
144
107
|
ls: JacLangServer, params: lspt.DocumentSymbolParams
|
|
145
108
|
) -> list[lspt.DocumentSymbol]:
|
|
146
109
|
"""Provide document symbols."""
|
|
147
|
-
stop_analysis()
|
|
148
|
-
analyze_and_publish(ls, params.text_document.uri)
|
|
149
110
|
return ls.get_document_symbols(params.text_document.uri)
|
|
150
111
|
|
|
151
112
|
|
|
152
113
|
@server.feature(lspt.TEXT_DOCUMENT_DEFINITION)
|
|
153
|
-
|
|
114
|
+
def definition(
|
|
154
115
|
ls: JacLangServer, params: lspt.TextDocumentPositionParams
|
|
155
116
|
) -> Optional[lspt.Location]:
|
|
156
117
|
"""Provide definition."""
|
|
157
|
-
stop_analysis()
|
|
158
|
-
analyze_and_publish(ls, params.text_document.uri, level=1)
|
|
159
118
|
return ls.get_definition(params.text_document.uri, params.position)
|
|
160
119
|
|
|
161
120
|
|
|
121
|
+
@server.feature(
|
|
122
|
+
lspt.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL,
|
|
123
|
+
lspt.SemanticTokensLegend(
|
|
124
|
+
token_types=SemTokType.as_str_list(),
|
|
125
|
+
token_modifiers=SemTokMod.as_str_list(),
|
|
126
|
+
),
|
|
127
|
+
)
|
|
128
|
+
def semantic_tokens_full(
|
|
129
|
+
ls: JacLangServer, params: lspt.SemanticTokensParams
|
|
130
|
+
) -> lspt.SemanticTokens:
|
|
131
|
+
"""Provide semantic tokens."""
|
|
132
|
+
return ls.get_semantic_tokens(params.text_document.uri)
|
|
133
|
+
|
|
134
|
+
|
|
162
135
|
def run_lang_server() -> None:
|
|
163
136
|
"""Run the language server."""
|
|
164
137
|
server.start_io()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""A Docstring can be added the head of any module.
|
|
2
|
+
|
|
3
|
+
Any element in the module can also have a docstring.
|
|
4
|
+
If there is only one docstring before the first element,
|
|
5
|
+
it is assumed to be a module docstring.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
"""A docstring for add function"""
|
|
9
|
+
can add(a: int, b: int) -> int {
|
|
10
|
+
return a + b;
|
|
11
|
+
}
|
|
12
|
+
# No docstring for subtract function
|
|
13
|
+
|
|
14
|
+
can subtract(a: int, b: int) -> int {
|
|
15
|
+
return a - b;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
with entry:__main__ {
|
|
19
|
+
print(add(1, subtract(3, 1)));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
glob x: int = 10;
|
|
23
|
+
|
|
24
|
+
enum Color {
|
|
25
|
+
RED,
|
|
26
|
+
GREEN,
|
|
27
|
+
BLUE
|
|
28
|
+
}
|
|
@@ -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
|
}
|