jaclang 0.7.1__py3-none-any.whl → 0.7.7__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 +539 -297
- 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 +32 -12
- jaclang/compiler/passes/main/def_use_pass.py +59 -40
- jaclang/compiler/passes/main/fuse_typeinfo_pass.py +71 -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/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/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/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/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 +150 -89
- jaclang/compiler/tests/test_parser.py +2 -2
- jaclang/core/aott.py +118 -18
- 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/memory.py +53 -2
- jaclang/core/test.py +90 -0
- jaclang/core/utils.py +2 -2
- jaclang/langserve/engine.py +199 -138
- jaclang/langserve/server.py +48 -53
- 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.impl.jac +8 -4
- jaclang/langserve/tests/fixtures/circle_pure.jac +2 -2
- 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 +200 -2
- jaclang/langserve/utils.py +214 -10
- jaclang/plugin/builtin.py +1 -1
- jaclang/plugin/default.py +48 -92
- jaclang/plugin/feature.py +33 -17
- jaclang/plugin/spec.py +18 -20
- jaclang/plugin/tests/test_features.py +0 -33
- jaclang/settings.py +4 -0
- jaclang/tests/fixtures/abc.jac +16 -12
- jaclang/tests/fixtures/aott_raise.jac +1 -1
- jaclang/tests/fixtures/byllmissue.jac +12 -0
- jaclang/tests/fixtures/edgetypeissue.jac +10 -0
- jaclang/tests/fixtures/hash_init_check.jac +17 -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/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_function.jac +1 -1
- jaclang/tests/fixtures/with_llm_lower.jac +1 -1
- jaclang/tests/fixtures/with_llm_method.jac +1 -1
- jaclang/tests/fixtures/with_llm_type.jac +1 -1
- 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 +197 -82
- 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.7.dist-info}/METADATA +3 -2
- {jaclang-0.7.1.dist-info → jaclang-0.7.7.dist-info}/RECORD +96 -88
- jaclang/core/llms/__init__.py +0 -20
- jaclang/core/llms/anthropic.py +0 -61
- jaclang/core/llms/base.py +0 -206
- jaclang/core/llms/groq.py +0 -67
- jaclang/core/llms/huggingface.py +0 -73
- jaclang/core/llms/ollama.py +0 -78
- jaclang/core/llms/openai.py +0 -61
- jaclang/core/llms/togetherai.py +0 -60
- jaclang/core/llms/utils.py +0 -9
- jaclang/core/shelve_storage.py +0 -55
- {jaclang-0.7.1.dist-info → jaclang-0.7.7.dist-info}/WHEEL +0 -0
- {jaclang-0.7.1.dist-info → jaclang-0.7.7.dist-info}/entry_points.txt +0 -0
jaclang/core/memory.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Core constructs for Jac Language."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shelve
|
|
3
6
|
from uuid import UUID
|
|
4
7
|
|
|
5
|
-
from
|
|
8
|
+
from .architype import Architype
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
class Memory:
|
|
@@ -46,3 +49,51 @@ class Memory:
|
|
|
46
49
|
def close(self) -> None:
|
|
47
50
|
"""Close any connection, if applicable."""
|
|
48
51
|
self.mem.clear()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ShelveStorage(Memory):
|
|
55
|
+
"""Shelve storage for jaclang runtime object."""
|
|
56
|
+
|
|
57
|
+
storage: shelve.Shelf | None = None
|
|
58
|
+
|
|
59
|
+
def __init__(self, session: str = "") -> None:
|
|
60
|
+
"""Init shelve storage."""
|
|
61
|
+
super().__init__()
|
|
62
|
+
if session:
|
|
63
|
+
self.connect(session)
|
|
64
|
+
|
|
65
|
+
def get_obj_from_store(self, obj_id: UUID) -> Architype | None:
|
|
66
|
+
"""Get object from the underlying store."""
|
|
67
|
+
obj = super().get_obj_from_store(obj_id)
|
|
68
|
+
if obj is None and self.storage:
|
|
69
|
+
obj = self.storage.get(str(obj_id))
|
|
70
|
+
if obj is not None:
|
|
71
|
+
self.mem[obj_id] = obj
|
|
72
|
+
|
|
73
|
+
return obj
|
|
74
|
+
|
|
75
|
+
def has_obj_in_store(self, obj_id: UUID | str) -> bool:
|
|
76
|
+
"""Check if the object exists in the underlying store."""
|
|
77
|
+
return obj_id in self.mem or (
|
|
78
|
+
str(obj_id) in self.storage if self.storage else False
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def commit(self) -> None:
|
|
82
|
+
"""Commit changes to persistent storage."""
|
|
83
|
+
if self.storage is not None:
|
|
84
|
+
for obj_id, obj in self.save_obj_list.items():
|
|
85
|
+
self.storage[str(obj_id)] = obj
|
|
86
|
+
self.save_obj_list.clear()
|
|
87
|
+
|
|
88
|
+
def connect(self, session: str) -> None:
|
|
89
|
+
"""Connect to storage."""
|
|
90
|
+
self.session = session
|
|
91
|
+
self.storage = shelve.open(session)
|
|
92
|
+
|
|
93
|
+
def close(self) -> None:
|
|
94
|
+
"""Close the storage."""
|
|
95
|
+
super().close()
|
|
96
|
+
self.commit()
|
|
97
|
+
if self.storage:
|
|
98
|
+
self.storage.close()
|
|
99
|
+
self.storage = None
|
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
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import logging
|
|
5
6
|
from enum import IntEnum
|
|
6
|
-
from
|
|
7
|
-
from typing import Optional, Sequence
|
|
7
|
+
from typing import Optional
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
import jaclang.compiler.absyntree as ast
|
|
@@ -14,7 +14,13 @@ from jaclang.compiler.passes import Pass
|
|
|
14
14
|
from jaclang.compiler.passes.main.schedules import type_checker_sched
|
|
15
15
|
from jaclang.compiler.passes.tool import FuseCommentsPass, JacFormatPass
|
|
16
16
|
from jaclang.compiler.passes.transform import Alert
|
|
17
|
-
from jaclang.langserve.utils import
|
|
17
|
+
from jaclang.langserve.utils import (
|
|
18
|
+
collect_symbols,
|
|
19
|
+
create_range,
|
|
20
|
+
find_deepest_symbol_node_at_pos,
|
|
21
|
+
get_item_path,
|
|
22
|
+
get_mod_path,
|
|
23
|
+
)
|
|
18
24
|
from jaclang.vendor.pygls import uris
|
|
19
25
|
from jaclang.vendor.pygls.server import LanguageServer
|
|
20
26
|
|
|
@@ -22,7 +28,7 @@ import lsprotocol.types as lspt
|
|
|
22
28
|
|
|
23
29
|
|
|
24
30
|
class ALev(IntEnum):
|
|
25
|
-
"""Analysis Level."""
|
|
31
|
+
"""Analysis Level successfully completed."""
|
|
26
32
|
|
|
27
33
|
QUICK = 1
|
|
28
34
|
DEEP = 2
|
|
@@ -35,8 +41,8 @@ class ModuleInfo:
|
|
|
35
41
|
def __init__(
|
|
36
42
|
self,
|
|
37
43
|
ir: ast.Module,
|
|
38
|
-
errors:
|
|
39
|
-
warnings:
|
|
44
|
+
errors: list[Alert],
|
|
45
|
+
warnings: list[Alert],
|
|
40
46
|
alev: ALev,
|
|
41
47
|
parent: Optional[ModuleInfo] = None,
|
|
42
48
|
) -> None:
|
|
@@ -47,6 +53,7 @@ class ModuleInfo:
|
|
|
47
53
|
self.alev = alev
|
|
48
54
|
self.parent: Optional[ModuleInfo] = parent
|
|
49
55
|
self.diagnostics = self.gen_diagnostics()
|
|
56
|
+
self.sem_tokens: list[int] = self.gen_sem_tokens()
|
|
50
57
|
|
|
51
58
|
@property
|
|
52
59
|
def uri(self) -> str:
|
|
@@ -58,41 +65,59 @@ class ModuleInfo:
|
|
|
58
65
|
"""Return if there are syntax errors."""
|
|
59
66
|
return len(self.errors) > 0 and self.alev == ALev.QUICK
|
|
60
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
|
+
|
|
61
82
|
def gen_diagnostics(self) -> list[lspt.Diagnostic]:
|
|
62
83
|
"""Return diagnostics."""
|
|
63
84
|
return [
|
|
64
85
|
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
|
-
),
|
|
86
|
+
range=create_range(error.loc),
|
|
74
87
|
message=error.msg,
|
|
75
88
|
severity=lspt.DiagnosticSeverity.Error,
|
|
76
89
|
)
|
|
77
90
|
for error in self.errors
|
|
78
91
|
] + [
|
|
79
92
|
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
|
-
),
|
|
93
|
+
range=create_range(warning.loc),
|
|
90
94
|
message=warning.msg,
|
|
91
95
|
severity=lspt.DiagnosticSeverity.Warning,
|
|
92
96
|
)
|
|
93
97
|
for warning in self.warnings
|
|
94
98
|
]
|
|
95
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
|
+
|
|
96
121
|
|
|
97
122
|
class JacLangServer(LanguageServer):
|
|
98
123
|
"""Class for managing workspace."""
|
|
@@ -102,19 +127,6 @@ class JacLangServer(LanguageServer):
|
|
|
102
127
|
super().__init__("jac-lsp", "v0.1")
|
|
103
128
|
self.modules: dict[str, ModuleInfo] = {}
|
|
104
129
|
|
|
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
130
|
def push_diagnostics(self, file_path: str) -> None:
|
|
119
131
|
"""Push diagnostics for a file."""
|
|
120
132
|
if file_path in self.modules:
|
|
@@ -125,17 +137,25 @@ class JacLangServer(LanguageServer):
|
|
|
125
137
|
|
|
126
138
|
def unwind_to_parent(self, file_path: str) -> str:
|
|
127
139
|
"""Unwind to parent."""
|
|
140
|
+
orig_file_path = file_path
|
|
128
141
|
if file_path in self.modules:
|
|
129
142
|
while cur := self.modules[file_path].parent:
|
|
130
143
|
file_path = cur.uri
|
|
144
|
+
if file_path == orig_file_path and (
|
|
145
|
+
discover := self.modules[file_path].ir.annexable_by
|
|
146
|
+
):
|
|
147
|
+
file_path = uris.from_fs_path(discover)
|
|
148
|
+
self.quick_check(file_path)
|
|
131
149
|
return file_path
|
|
132
150
|
|
|
133
|
-
def update_modules(
|
|
151
|
+
def update_modules(
|
|
152
|
+
self, file_path: str, build: Pass, alev: ALev, refresh: bool = False
|
|
153
|
+
) -> None:
|
|
134
154
|
"""Update modules."""
|
|
135
155
|
if not isinstance(build.ir, ast.Module):
|
|
136
156
|
self.log_error("Error with module build.")
|
|
137
157
|
return
|
|
138
|
-
|
|
158
|
+
new_mod = ModuleInfo(
|
|
139
159
|
ir=build.ir,
|
|
140
160
|
errors=[
|
|
141
161
|
i
|
|
@@ -149,47 +169,56 @@ class JacLangServer(LanguageServer):
|
|
|
149
169
|
],
|
|
150
170
|
alev=alev,
|
|
151
171
|
)
|
|
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
|
|
152
176
|
for p in build.ir.mod_deps.keys():
|
|
153
177
|
uri = uris.from_fs_path(p)
|
|
154
|
-
|
|
178
|
+
new_mod = ModuleInfo(
|
|
155
179
|
ir=build.ir.mod_deps[p],
|
|
156
180
|
errors=[i for i in build.errors_had if i.loc.mod_path == p],
|
|
157
181
|
warnings=[i for i in build.warnings_had if i.loc.mod_path == p],
|
|
158
182
|
alev=alev,
|
|
159
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
|
|
188
|
+
self.modules[uri].parent = (
|
|
189
|
+
self.modules[file_path] if file_path != uri else None
|
|
190
|
+
)
|
|
160
191
|
|
|
161
|
-
def quick_check(self, file_path: str) ->
|
|
192
|
+
def quick_check(self, file_path: str) -> bool:
|
|
162
193
|
"""Rebuild a file."""
|
|
163
|
-
if self.module_not_diff(file_path, ALev.QUICK):
|
|
164
|
-
return
|
|
165
194
|
try:
|
|
166
|
-
document = self.workspace.
|
|
195
|
+
document = self.workspace.get_text_document(file_path)
|
|
167
196
|
build = jac_str_to_pass(
|
|
168
197
|
jac_str=document.source, file_path=document.path, schedule=[]
|
|
169
198
|
)
|
|
170
199
|
except Exception as e:
|
|
171
200
|
self.log_error(f"Error during syntax check: {e}")
|
|
172
|
-
|
|
201
|
+
return False
|
|
202
|
+
self.update_modules(file_path, build, ALev.QUICK, refresh=True)
|
|
203
|
+
return len(self.modules[file_path].errors) == 0
|
|
173
204
|
|
|
174
|
-
def deep_check(self, file_path: str) ->
|
|
205
|
+
def deep_check(self, file_path: str) -> bool:
|
|
175
206
|
"""Rebuild a file and its dependencies."""
|
|
176
|
-
if file_path in self.modules:
|
|
207
|
+
if file_path not in self.modules:
|
|
177
208
|
self.quick_check(file_path)
|
|
178
|
-
if self.module_not_diff(file_path, ALev.DEEP):
|
|
179
|
-
return
|
|
180
209
|
try:
|
|
181
210
|
file_path = self.unwind_to_parent(file_path)
|
|
182
211
|
build = jac_ir_to_pass(ir=self.modules[file_path].ir)
|
|
183
212
|
except Exception as e:
|
|
184
213
|
self.log_error(f"Error during syntax check: {e}")
|
|
214
|
+
return False
|
|
185
215
|
self.update_modules(file_path, build, ALev.DEEP)
|
|
216
|
+
return len(self.modules[file_path].errors) == 0
|
|
186
217
|
|
|
187
|
-
def type_check(self, file_path: str) ->
|
|
218
|
+
def type_check(self, file_path: str) -> bool:
|
|
188
219
|
"""Rebuild a file and its dependencies."""
|
|
189
220
|
if file_path not in self.modules:
|
|
190
221
|
self.deep_check(file_path)
|
|
191
|
-
if self.module_not_diff(file_path, ALev.TYPE):
|
|
192
|
-
return
|
|
193
222
|
try:
|
|
194
223
|
file_path = self.unwind_to_parent(file_path)
|
|
195
224
|
build = jac_ir_to_pass(
|
|
@@ -197,14 +226,28 @@ class JacLangServer(LanguageServer):
|
|
|
197
226
|
)
|
|
198
227
|
except Exception as e:
|
|
199
228
|
self.log_error(f"Error during type check: {e}")
|
|
229
|
+
return False
|
|
200
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)
|
|
201
244
|
|
|
202
245
|
def get_completion(
|
|
203
246
|
self, file_path: str, position: lspt.Position
|
|
204
247
|
) -> lspt.CompletionList:
|
|
205
248
|
"""Return completion for a file."""
|
|
206
249
|
items = []
|
|
207
|
-
document = self.workspace.
|
|
250
|
+
document = self.workspace.get_text_document(file_path)
|
|
208
251
|
current_line = document.lines[position.line].strip()
|
|
209
252
|
if current_line.endswith("hello."):
|
|
210
253
|
|
|
@@ -228,7 +271,7 @@ class JacLangServer(LanguageServer):
|
|
|
228
271
|
def formatted_jac(self, file_path: str) -> list[lspt.TextEdit]:
|
|
229
272
|
"""Return formatted jac."""
|
|
230
273
|
try:
|
|
231
|
-
document = self.workspace.
|
|
274
|
+
document = self.workspace.get_text_document(file_path)
|
|
232
275
|
format = jac_str_to_pass(
|
|
233
276
|
jac_str=document.source,
|
|
234
277
|
file_path=document.path,
|
|
@@ -259,7 +302,7 @@ class JacLangServer(LanguageServer):
|
|
|
259
302
|
self, file_path: str, position: lspt.Position
|
|
260
303
|
) -> Optional[lspt.Hover]:
|
|
261
304
|
"""Return hover information for a file."""
|
|
262
|
-
node_selected =
|
|
305
|
+
node_selected = find_deepest_symbol_node_at_pos(
|
|
263
306
|
self.modules[file_path].ir, position.line, position.character
|
|
264
307
|
)
|
|
265
308
|
value = self.get_node_info(node_selected) if node_selected else None
|
|
@@ -271,94 +314,108 @@ class JacLangServer(LanguageServer):
|
|
|
271
314
|
)
|
|
272
315
|
return None
|
|
273
316
|
|
|
274
|
-
def get_node_info(self, node: ast.
|
|
317
|
+
def get_node_info(self, node: ast.AstSymbolNode) -> Optional[str]:
|
|
275
318
|
"""Extract meaningful information from the AST node."""
|
|
276
319
|
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
|
|
320
|
+
if isinstance(node, ast.NameAtom):
|
|
321
|
+
node = node.name_of
|
|
322
|
+
access = node.sym.access.value + " " if node.sym else None
|
|
323
|
+
node_info = (
|
|
324
|
+
f"({access if access else ''}{node.sym_category.value}) {node.sym_name}"
|
|
325
|
+
)
|
|
326
|
+
if node.name_spec.clean_type:
|
|
327
|
+
node_info += f": {node.name_spec.clean_type}"
|
|
328
|
+
if isinstance(node, ast.AstSemStrNode) and node.semstr:
|
|
329
|
+
node_info += f"\n{node.semstr.value}"
|
|
330
|
+
if isinstance(node, ast.AstDocNode) and node.doc:
|
|
331
|
+
node_info += f"\n{node.doc.value}"
|
|
332
|
+
if isinstance(node, ast.Ability) and node.signature:
|
|
333
|
+
node_info += f"\n{node.signature.unparse()}"
|
|
334
|
+
self.log_py(f"mypy_node: {node.gen.mypy_ast}")
|
|
358
335
|
except AttributeError as e:
|
|
359
336
|
self.log_warning(f"Attribute error when accessing node attributes: {e}")
|
|
360
337
|
return node_info.strip()
|
|
361
338
|
|
|
339
|
+
def get_document_symbols(self, file_path: str) -> list[lspt.DocumentSymbol]:
|
|
340
|
+
"""Return document symbols for a file."""
|
|
341
|
+
if root_node := self.modules[file_path].ir._sym_tab:
|
|
342
|
+
return collect_symbols(root_node)
|
|
343
|
+
return []
|
|
344
|
+
|
|
345
|
+
def get_definition(
|
|
346
|
+
self, file_path: str, position: lspt.Position
|
|
347
|
+
) -> Optional[lspt.Location]:
|
|
348
|
+
"""Return definition location for a file."""
|
|
349
|
+
node_selected: Optional[ast.AstSymbolNode] = find_deepest_symbol_node_at_pos(
|
|
350
|
+
self.modules[file_path].ir, position.line, position.character
|
|
351
|
+
)
|
|
352
|
+
if node_selected:
|
|
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)):
|
|
386
|
+
return None
|
|
387
|
+
decl_node = (
|
|
388
|
+
node_selected.parent.body.target
|
|
389
|
+
if node_selected.parent
|
|
390
|
+
and isinstance(node_selected.parent, ast.AstImplNeedingNode)
|
|
391
|
+
and isinstance(node_selected.parent.body, ast.AstImplOnlyNode)
|
|
392
|
+
else (
|
|
393
|
+
node_selected.sym.decl
|
|
394
|
+
if (node_selected.sym and node_selected.sym.decl)
|
|
395
|
+
else node_selected
|
|
396
|
+
)
|
|
397
|
+
)
|
|
398
|
+
self.log_py(f"{node_selected}, {decl_node}")
|
|
399
|
+
decl_uri = uris.from_fs_path(decl_node.loc.mod_path)
|
|
400
|
+
try:
|
|
401
|
+
decl_range = create_range(decl_node.loc)
|
|
402
|
+
except ValueError: # 'print' name has decl in 0,0,0,0
|
|
403
|
+
return None
|
|
404
|
+
decl_location = lspt.Location(
|
|
405
|
+
uri=decl_uri,
|
|
406
|
+
range=decl_range,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
return decl_location
|
|
410
|
+
else:
|
|
411
|
+
return None
|
|
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
|
+
|
|
362
419
|
def log_error(self, message: str) -> None:
|
|
363
420
|
"""Log an error message."""
|
|
364
421
|
self.show_message_log(message, lspt.MessageType.Error)
|
|
@@ -373,3 +430,7 @@ class JacLangServer(LanguageServer):
|
|
|
373
430
|
"""Log an info message."""
|
|
374
431
|
self.show_message_log(message, lspt.MessageType.Info)
|
|
375
432
|
self.show_message(message, lspt.MessageType.Info)
|
|
433
|
+
|
|
434
|
+
def log_py(self, message: str) -> None:
|
|
435
|
+
"""Log a message."""
|
|
436
|
+
logging.info(message)
|