jaclang 0.7.0__py3-none-any.whl → 0.7.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jaclang might be problematic. Click here for more details.
- jaclang/compiler/absyntree.py +53 -50
- jaclang/compiler/compile.py +21 -0
- jaclang/compiler/passes/main/__init__.py +2 -2
- jaclang/compiler/passes/main/def_impl_match_pass.py +10 -8
- jaclang/compiler/passes/main/def_use_pass.py +14 -7
- jaclang/compiler/passes/main/fuse_typeinfo_pass.py +20 -1
- jaclang/compiler/passes/main/import_pass.py +60 -11
- jaclang/compiler/passes/main/pyast_gen_pass.py +65 -4
- jaclang/compiler/passes/main/pyast_load_pass.py +2 -1
- jaclang/compiler/passes/main/pyjac_ast_link_pass.py +6 -1
- jaclang/compiler/passes/main/pyout_pass.py +3 -1
- jaclang/compiler/passes/main/schedules.py +4 -3
- jaclang/compiler/passes/main/tests/fixtures/incautoimpl.jac +7 -0
- jaclang/compiler/passes/main/tests/test_decl_def_match_pass.py +4 -4
- jaclang/compiler/passes/main/tests/test_import_pass.py +21 -0
- jaclang/compiler/passes/main/tests/test_type_check_pass.py +1 -1
- jaclang/compiler/passes/tool/jac_formatter_pass.py +14 -2
- 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/symtable.py +21 -1
- jaclang/core/aott.py +107 -11
- jaclang/core/construct.py +171 -5
- 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/langserve/engine.py +193 -121
- jaclang/langserve/server.py +35 -6
- jaclang/langserve/tests/fixtures/circle.jac +73 -0
- jaclang/langserve/tests/fixtures/circle_err.jac +73 -0
- jaclang/langserve/tests/fixtures/circle_pure.impl.jac +32 -0
- jaclang/langserve/tests/fixtures/circle_pure.jac +34 -0
- jaclang/langserve/tests/fixtures/circle_pure_err.impl.jac +32 -0
- jaclang/langserve/tests/fixtures/circle_pure_err.jac +34 -0
- jaclang/langserve/tests/test_server.py +156 -1
- jaclang/langserve/utils.py +127 -2
- jaclang/plugin/default.py +25 -83
- jaclang/plugin/feature.py +10 -12
- jaclang/plugin/tests/test_features.py +0 -33
- jaclang/settings.py +1 -0
- jaclang/tests/fixtures/byllmissue.jac +3 -0
- jaclang/tests/fixtures/hash_init_check.jac +17 -0
- jaclang/tests/fixtures/math_question.jpg +0 -0
- jaclang/tests/fixtures/nosigself.jac +19 -0
- jaclang/tests/fixtures/type_info.jac +1 -1
- jaclang/tests/fixtures/walker_override.jac +21 -0
- jaclang/tests/fixtures/with_llm_vision.jac +25 -0
- jaclang/tests/test_cli.py +1 -1
- jaclang/tests/test_language.py +61 -11
- jaclang/utils/helpers.py +3 -5
- jaclang/utils/test.py +1 -1
- jaclang/utils/treeprinter.py +19 -2
- {jaclang-0.7.0.dist-info → jaclang-0.7.2.dist-info}/METADATA +3 -2
- {jaclang-0.7.0.dist-info → jaclang-0.7.2.dist-info}/RECORD +60 -48
- jaclang/core/memory.py +0 -48
- jaclang/core/shelve_storage.py +0 -55
- {jaclang-0.7.0.dist-info → jaclang-0.7.2.dist-info}/WHEEL +0 -0
- {jaclang-0.7.0.dist-info → jaclang-0.7.2.dist-info}/entry_points.txt +0 -0
jaclang/core/llms/anthropic.py
CHANGED
|
@@ -45,12 +45,41 @@ class Anthropic(BaseLLM):
|
|
|
45
45
|
self.client = anthropic.Anthropic()
|
|
46
46
|
self.verbose = verbose
|
|
47
47
|
self.max_tries = max_tries
|
|
48
|
-
self.model_name = kwargs.get("model_name", "claude-3-sonnet-20240229")
|
|
48
|
+
self.model_name = str(kwargs.get("model_name", "claude-3-sonnet-20240229"))
|
|
49
49
|
self.temperature = kwargs.get("temperature", 0.7)
|
|
50
50
|
self.max_tokens = kwargs.get("max_tokens", 1024)
|
|
51
51
|
|
|
52
|
-
def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
|
|
52
|
+
def __infer__(self, meaning_in: str | list[dict], **kwargs: dict) -> str:
|
|
53
53
|
"""Infer a response from the input meaning."""
|
|
54
|
+
if not isinstance(meaning_in, str):
|
|
55
|
+
assert self.model_name.startswith(
|
|
56
|
+
("claude-3-opus", "claude-3-sonnet", "claude-3-haiku")
|
|
57
|
+
), f"Model {self.model_name} is not multimodal, use a multimodal model instead."
|
|
58
|
+
|
|
59
|
+
import re
|
|
60
|
+
|
|
61
|
+
formatted_meaning_in = []
|
|
62
|
+
for item in meaning_in:
|
|
63
|
+
if item["type"] == "image_url":
|
|
64
|
+
# "data:image/jpeg;base64,base64_string"
|
|
65
|
+
img_match = re.match(
|
|
66
|
+
r"data:(image/[a-zA-Z]*);base64,(.*)", item["source"]
|
|
67
|
+
)
|
|
68
|
+
if img_match:
|
|
69
|
+
media_type, base64_string = img_match.groups()
|
|
70
|
+
formatted_meaning_in.append(
|
|
71
|
+
{
|
|
72
|
+
"type": "image",
|
|
73
|
+
"source": {
|
|
74
|
+
"type": "base64",
|
|
75
|
+
"media_type": media_type,
|
|
76
|
+
"data": base64_string,
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
continue
|
|
81
|
+
formatted_meaning_in.append(item)
|
|
82
|
+
meaning_in = formatted_meaning_in
|
|
54
83
|
messages = [{"role": "user", "content": meaning_in}]
|
|
55
84
|
output = self.client.messages.create(
|
|
56
85
|
model=kwargs.get("model_name", self.model_name),
|
jaclang/core/llms/base.py
CHANGED
|
@@ -112,11 +112,11 @@ class BaseLLM:
|
|
|
112
112
|
self.max_tries = max_tries
|
|
113
113
|
raise NotImplementedError
|
|
114
114
|
|
|
115
|
-
def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
|
|
115
|
+
def __infer__(self, meaning_in: str | list[dict], **kwargs: dict) -> str:
|
|
116
116
|
"""Infer a response from the input meaning."""
|
|
117
117
|
raise NotImplementedError
|
|
118
118
|
|
|
119
|
-
def __call__(self, input_text: str, **kwargs: dict) -> str:
|
|
119
|
+
def __call__(self, input_text: str | list[dict], **kwargs: dict) -> str:
|
|
120
120
|
"""Infer a response from the input text."""
|
|
121
121
|
if self.verbose:
|
|
122
122
|
logger.info(f"Meaning In\n{input_text}")
|
|
@@ -131,7 +131,7 @@ class BaseLLM:
|
|
|
131
131
|
) -> str:
|
|
132
132
|
"""Resolve the output string to return the reasoning and output."""
|
|
133
133
|
if self.verbose:
|
|
134
|
-
logger.
|
|
134
|
+
logger.info(f"Meaning Out\n{meaning_out}")
|
|
135
135
|
output_match = re.search(r"\[Output\](.*)", meaning_out)
|
|
136
136
|
output = output_match.group(1).strip() if output_match else None
|
|
137
137
|
if not output_match:
|
jaclang/core/llms/groq.py
CHANGED
|
@@ -49,8 +49,11 @@ class Groq(BaseLLM):
|
|
|
49
49
|
self.temperature = kwargs.get("temperature", 0.7)
|
|
50
50
|
self.max_tokens = kwargs.get("max_tokens", 1024)
|
|
51
51
|
|
|
52
|
-
def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
|
|
52
|
+
def __infer__(self, meaning_in: str | list[dict], **kwargs: dict) -> str:
|
|
53
53
|
"""Infer a response from the input meaning."""
|
|
54
|
+
assert isinstance(
|
|
55
|
+
meaning_in, str
|
|
56
|
+
), "Currently Multimodal models are not supported. Please provide a string input."
|
|
54
57
|
messages = [{"role": "user", "content": meaning_in}]
|
|
55
58
|
model_params = {
|
|
56
59
|
k: v
|
jaclang/core/llms/huggingface.py
CHANGED
|
@@ -61,8 +61,11 @@ class Huggingface(BaseLLM):
|
|
|
61
61
|
self.temperature = kwargs.get("temperature", 0.7)
|
|
62
62
|
self.max_tokens = kwargs.get("max_new_tokens", 1024)
|
|
63
63
|
|
|
64
|
-
def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
|
|
64
|
+
def __infer__(self, meaning_in: str | list[dict], **kwargs: dict) -> str:
|
|
65
65
|
"""Infer a response from the input meaning."""
|
|
66
|
+
assert isinstance(
|
|
67
|
+
meaning_in, str
|
|
68
|
+
), "Currently Multimodal models are not supported. Please provide a string input."
|
|
66
69
|
messages = [{"role": "user", "content": meaning_in}]
|
|
67
70
|
output = self.pipe(
|
|
68
71
|
messages,
|
jaclang/core/llms/ollama.py
CHANGED
|
@@ -51,8 +51,11 @@ class Ollama(BaseLLM):
|
|
|
51
51
|
k: v for k, v in kwargs.items() if k not in ["model_name", "host"]
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
|
|
54
|
+
def __infer__(self, meaning_in: str | list[dict], **kwargs: dict) -> str:
|
|
55
55
|
"""Infer a response from the input meaning."""
|
|
56
|
+
assert isinstance(
|
|
57
|
+
meaning_in, str
|
|
58
|
+
), "Currently Multimodal models are not supported. Please provide a string input."
|
|
56
59
|
model = str(kwargs.get("model_name", self.model_name))
|
|
57
60
|
if not self.check_model(model):
|
|
58
61
|
self.download_model(model)
|
jaclang/core/llms/openai.py
CHANGED
|
@@ -45,12 +45,16 @@ class OpenAI(BaseLLM):
|
|
|
45
45
|
self.client = openai.OpenAI()
|
|
46
46
|
self.verbose = verbose
|
|
47
47
|
self.max_tries = max_tries
|
|
48
|
-
self.model_name = kwargs.get("model_name", "gpt-3.5-turbo")
|
|
48
|
+
self.model_name = str(kwargs.get("model_name", "gpt-3.5-turbo"))
|
|
49
49
|
self.temperature = kwargs.get("temperature", 0.7)
|
|
50
50
|
self.max_tokens = kwargs.get("max_tokens", 1024)
|
|
51
51
|
|
|
52
|
-
def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
|
|
52
|
+
def __infer__(self, meaning_in: str | list[dict], **kwargs: dict) -> str:
|
|
53
53
|
"""Infer a response from the input meaning."""
|
|
54
|
+
if not isinstance(meaning_in, str):
|
|
55
|
+
assert self.model_name.startswith(
|
|
56
|
+
("gpt-4o", "gpt-4-turbo")
|
|
57
|
+
), f"Model {self.model_name} is not multimodal, use a multimodal model instead."
|
|
54
58
|
messages = [{"role": "user", "content": meaning_in}]
|
|
55
59
|
output = self.client.chat.completions.create(
|
|
56
60
|
model=kwargs.get("model_name", self.model_name),
|
jaclang/core/llms/togetherai.py
CHANGED
|
@@ -48,8 +48,11 @@ class TogetherAI(BaseLLM):
|
|
|
48
48
|
self.temperature = kwargs.get("temperature", 0.7)
|
|
49
49
|
self.max_tokens = kwargs.get("max_tokens", 1024)
|
|
50
50
|
|
|
51
|
-
def __infer__(self, meaning_in: str, **kwargs: dict) -> str:
|
|
51
|
+
def __infer__(self, meaning_in: str | list[dict], **kwargs: dict) -> str:
|
|
52
52
|
"""Infer a response from the input meaning."""
|
|
53
|
+
assert isinstance(
|
|
54
|
+
meaning_in, str
|
|
55
|
+
), "Currently Multimodal models are not supported. Please provide a string input."
|
|
53
56
|
messages = [{"role": "user", "content": meaning_in}]
|
|
54
57
|
output = self.client.chat.completions.create(
|
|
55
58
|
model=kwargs.get("model_name", self.model_name),
|
jaclang/langserve/engine.py
CHANGED
|
@@ -2,74 +2,79 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import logging
|
|
6
|
+
from enum import IntEnum
|
|
5
7
|
from hashlib import md5
|
|
6
|
-
from typing import
|
|
8
|
+
from typing import Optional, Sequence
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
import jaclang.compiler.absyntree as ast
|
|
10
|
-
from jaclang.compiler.compile import
|
|
12
|
+
from jaclang.compiler.compile import jac_ir_to_pass, jac_str_to_pass
|
|
11
13
|
from jaclang.compiler.parser import JacParser
|
|
12
14
|
from jaclang.compiler.passes import Pass
|
|
13
|
-
from jaclang.compiler.passes.main.schedules import
|
|
14
|
-
AccessCheckPass,
|
|
15
|
-
PyBytecodeGenPass,
|
|
16
|
-
py_code_gen_typed,
|
|
17
|
-
)
|
|
15
|
+
from jaclang.compiler.passes.main.schedules import type_checker_sched
|
|
18
16
|
from jaclang.compiler.passes.tool import FuseCommentsPass, JacFormatPass
|
|
19
17
|
from jaclang.compiler.passes.transform import Alert
|
|
20
|
-
from jaclang.
|
|
21
|
-
|
|
18
|
+
from jaclang.langserve.utils import (
|
|
19
|
+
collect_symbols,
|
|
20
|
+
create_range,
|
|
21
|
+
find_deepest_symbol_node_at_pos,
|
|
22
|
+
)
|
|
23
|
+
from jaclang.vendor.pygls import uris
|
|
22
24
|
from jaclang.vendor.pygls.server import LanguageServer
|
|
23
|
-
from jaclang.vendor.pygls.workspace.text_document import TextDocument
|
|
24
25
|
|
|
25
26
|
import lsprotocol.types as lspt
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
class ALev(IntEnum):
|
|
30
|
+
"""Analysis Level."""
|
|
31
|
+
|
|
32
|
+
QUICK = 1
|
|
33
|
+
DEEP = 2
|
|
34
|
+
TYPE = 3
|
|
35
|
+
|
|
36
|
+
|
|
28
37
|
class ModuleInfo:
|
|
29
38
|
"""Module IR and Stats."""
|
|
30
39
|
|
|
31
40
|
def __init__(
|
|
32
41
|
self,
|
|
33
42
|
ir: ast.Module,
|
|
34
|
-
to_pass: Pass,
|
|
35
43
|
errors: Sequence[Alert],
|
|
36
44
|
warnings: Sequence[Alert],
|
|
45
|
+
alev: ALev,
|
|
46
|
+
parent: Optional[ModuleInfo] = None,
|
|
37
47
|
) -> None:
|
|
38
48
|
"""Initialize module info."""
|
|
39
49
|
self.ir = ir
|
|
40
|
-
self.at_pass = to_pass
|
|
41
50
|
self.errors = errors
|
|
42
51
|
self.warnings = warnings
|
|
52
|
+
self.alev = alev
|
|
53
|
+
self.parent: Optional[ModuleInfo] = parent
|
|
43
54
|
self.diagnostics = self.gen_diagnostics()
|
|
44
55
|
|
|
56
|
+
@property
|
|
57
|
+
def uri(self) -> str:
|
|
58
|
+
"""Return uri."""
|
|
59
|
+
return uris.from_fs_path(self.ir.loc.mod_path)
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def has_syntax_error(self) -> bool:
|
|
63
|
+
"""Return if there are syntax errors."""
|
|
64
|
+
return len(self.errors) > 0 and self.alev == ALev.QUICK
|
|
65
|
+
|
|
45
66
|
def gen_diagnostics(self) -> list[lspt.Diagnostic]:
|
|
46
67
|
"""Return diagnostics."""
|
|
47
68
|
return [
|
|
48
69
|
lspt.Diagnostic(
|
|
49
|
-
range=
|
|
50
|
-
start=lspt.Position(
|
|
51
|
-
line=error.loc.first_line, character=error.loc.col_start
|
|
52
|
-
),
|
|
53
|
-
end=lspt.Position(
|
|
54
|
-
line=error.loc.last_line,
|
|
55
|
-
character=error.loc.col_end,
|
|
56
|
-
),
|
|
57
|
-
),
|
|
70
|
+
range=create_range(error.loc),
|
|
58
71
|
message=error.msg,
|
|
59
72
|
severity=lspt.DiagnosticSeverity.Error,
|
|
60
73
|
)
|
|
61
74
|
for error in self.errors
|
|
62
75
|
] + [
|
|
63
76
|
lspt.Diagnostic(
|
|
64
|
-
range=
|
|
65
|
-
start=lspt.Position(
|
|
66
|
-
line=warning.loc.first_line, character=warning.loc.col_start
|
|
67
|
-
),
|
|
68
|
-
end=lspt.Position(
|
|
69
|
-
line=warning.loc.last_line,
|
|
70
|
-
character=warning.loc.col_end,
|
|
71
|
-
),
|
|
72
|
-
),
|
|
77
|
+
range=create_range(warning.loc),
|
|
73
78
|
message=warning.msg,
|
|
74
79
|
severity=lspt.DiagnosticSeverity.Warning,
|
|
75
80
|
)
|
|
@@ -85,18 +90,17 @@ class JacLangServer(LanguageServer):
|
|
|
85
90
|
super().__init__("jac-lsp", "v0.1")
|
|
86
91
|
self.modules: dict[str, ModuleInfo] = {}
|
|
87
92
|
|
|
88
|
-
def module_not_diff(self,
|
|
93
|
+
def module_not_diff(self, uri: str, alev: ALev) -> bool:
|
|
89
94
|
"""Check if module was changed."""
|
|
95
|
+
doc = self.workspace.get_text_document(uri)
|
|
90
96
|
return (
|
|
91
97
|
doc.uri in self.modules
|
|
92
98
|
and self.modules[doc.uri].ir.source.hash
|
|
93
99
|
== md5(doc.source.encode()).hexdigest()
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return doc.uri in self.modules and isinstance(
|
|
99
|
-
self.modules[doc.uri].at_pass, target
|
|
100
|
+
and (
|
|
101
|
+
self.modules[doc.uri].alev >= alev
|
|
102
|
+
or self.modules[doc.uri].has_syntax_error
|
|
103
|
+
)
|
|
100
104
|
)
|
|
101
105
|
|
|
102
106
|
def push_diagnostics(self, file_path: str) -> None:
|
|
@@ -107,73 +111,101 @@ class JacLangServer(LanguageServer):
|
|
|
107
111
|
self.modules[file_path].diagnostics,
|
|
108
112
|
)
|
|
109
113
|
|
|
110
|
-
def
|
|
114
|
+
def unwind_to_parent(self, file_path: str) -> str:
|
|
115
|
+
"""Unwind to parent."""
|
|
116
|
+
orig_file_path = file_path
|
|
117
|
+
if file_path in self.modules:
|
|
118
|
+
while cur := self.modules[file_path].parent:
|
|
119
|
+
file_path = cur.uri
|
|
120
|
+
if file_path == orig_file_path and (
|
|
121
|
+
discover := self.modules[file_path].ir.annexable_by
|
|
122
|
+
):
|
|
123
|
+
file_path = uris.from_fs_path(discover)
|
|
124
|
+
self.quick_check(file_path)
|
|
125
|
+
return file_path
|
|
126
|
+
|
|
127
|
+
def update_modules(self, file_path: str, build: Pass, alev: ALev) -> None:
|
|
128
|
+
"""Update modules."""
|
|
129
|
+
if not isinstance(build.ir, ast.Module):
|
|
130
|
+
self.log_error("Error with module build.")
|
|
131
|
+
return
|
|
132
|
+
save_parent = (
|
|
133
|
+
self.modules[file_path].parent if file_path in self.modules else None
|
|
134
|
+
)
|
|
135
|
+
self.modules[file_path] = ModuleInfo(
|
|
136
|
+
ir=build.ir,
|
|
137
|
+
errors=[
|
|
138
|
+
i
|
|
139
|
+
for i in build.errors_had
|
|
140
|
+
if i.loc.mod_path == uris.to_fs_path(file_path)
|
|
141
|
+
],
|
|
142
|
+
warnings=[
|
|
143
|
+
i
|
|
144
|
+
for i in build.warnings_had
|
|
145
|
+
if i.loc.mod_path == uris.to_fs_path(file_path)
|
|
146
|
+
],
|
|
147
|
+
alev=alev,
|
|
148
|
+
)
|
|
149
|
+
self.modules[file_path].parent = save_parent
|
|
150
|
+
for p in build.ir.mod_deps.keys():
|
|
151
|
+
uri = uris.from_fs_path(p)
|
|
152
|
+
self.modules[uri] = ModuleInfo(
|
|
153
|
+
ir=build.ir.mod_deps[p],
|
|
154
|
+
errors=[i for i in build.errors_had if i.loc.mod_path == p],
|
|
155
|
+
warnings=[i for i in build.warnings_had if i.loc.mod_path == p],
|
|
156
|
+
alev=alev,
|
|
157
|
+
)
|
|
158
|
+
self.modules[uri].parent = (
|
|
159
|
+
self.modules[file_path] if file_path != uri else None
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def quick_check(self, file_path: str, force: bool = False) -> None:
|
|
111
163
|
"""Rebuild a file."""
|
|
112
|
-
|
|
113
|
-
if self.module_not_diff(document):
|
|
164
|
+
if not force and self.module_not_diff(file_path, ALev.QUICK):
|
|
114
165
|
return
|
|
115
166
|
try:
|
|
167
|
+
document = self.workspace.get_text_document(file_path)
|
|
116
168
|
build = jac_str_to_pass(
|
|
117
169
|
jac_str=document.source, file_path=document.path, schedule=[]
|
|
118
170
|
)
|
|
119
171
|
except Exception as e:
|
|
120
172
|
self.log_error(f"Error during syntax check: {e}")
|
|
121
|
-
|
|
122
|
-
self.modules[file_path] = ModuleInfo(
|
|
123
|
-
ir=build.ir,
|
|
124
|
-
to_pass=build,
|
|
125
|
-
errors=build.errors_had,
|
|
126
|
-
warnings=build.warnings_had,
|
|
127
|
-
)
|
|
173
|
+
self.update_modules(file_path, build, ALev.QUICK)
|
|
128
174
|
|
|
129
|
-
def deep_check(self, file_path: str) -> None:
|
|
175
|
+
def deep_check(self, file_path: str, force: bool = False) -> None:
|
|
130
176
|
"""Rebuild a file and its dependencies."""
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
):
|
|
177
|
+
if file_path in self.modules:
|
|
178
|
+
self.quick_check(file_path, force=force)
|
|
179
|
+
if not force and self.module_not_diff(file_path, ALev.DEEP):
|
|
135
180
|
return
|
|
136
181
|
try:
|
|
137
|
-
|
|
182
|
+
file_path = self.unwind_to_parent(file_path)
|
|
183
|
+
build = jac_ir_to_pass(ir=self.modules[file_path].ir)
|
|
138
184
|
except Exception as e:
|
|
139
185
|
self.log_error(f"Error during syntax check: {e}")
|
|
140
|
-
|
|
141
|
-
self.modules[file_path] = ModuleInfo(
|
|
142
|
-
ir=build.ir,
|
|
143
|
-
to_pass=build,
|
|
144
|
-
errors=build.errors_had,
|
|
145
|
-
warnings=build.warnings_had,
|
|
146
|
-
)
|
|
186
|
+
self.update_modules(file_path, build, ALev.DEEP)
|
|
147
187
|
|
|
148
|
-
def type_check(self, file_path: str) -> None:
|
|
188
|
+
def type_check(self, file_path: str, force: bool = False) -> None:
|
|
149
189
|
"""Rebuild a file and its dependencies."""
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
):
|
|
190
|
+
if file_path not in self.modules:
|
|
191
|
+
self.deep_check(file_path, force=force)
|
|
192
|
+
if not force and self.module_not_diff(file_path, ALev.TYPE):
|
|
154
193
|
return
|
|
155
194
|
try:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
schedule=py_code_gen_typed,
|
|
195
|
+
file_path = self.unwind_to_parent(file_path)
|
|
196
|
+
build = jac_ir_to_pass(
|
|
197
|
+
ir=self.modules[file_path].ir, schedule=type_checker_sched
|
|
160
198
|
)
|
|
161
199
|
except Exception as e:
|
|
162
200
|
self.log_error(f"Error during type check: {e}")
|
|
163
|
-
|
|
164
|
-
self.modules[file_path] = ModuleInfo(
|
|
165
|
-
ir=build.ir,
|
|
166
|
-
to_pass=build,
|
|
167
|
-
errors=build.errors_had,
|
|
168
|
-
warnings=build.warnings_had,
|
|
169
|
-
)
|
|
201
|
+
self.update_modules(file_path, build, ALev.TYPE)
|
|
170
202
|
|
|
171
203
|
def get_completion(
|
|
172
204
|
self, file_path: str, position: lspt.Position
|
|
173
205
|
) -> lspt.CompletionList:
|
|
174
206
|
"""Return completion for a file."""
|
|
175
207
|
items = []
|
|
176
|
-
document = self.workspace.
|
|
208
|
+
document = self.workspace.get_text_document(file_path)
|
|
177
209
|
current_line = document.lines[position.line].strip()
|
|
178
210
|
if current_line.endswith("hello."):
|
|
179
211
|
|
|
@@ -197,7 +229,7 @@ class JacLangServer(LanguageServer):
|
|
|
197
229
|
def formatted_jac(self, file_path: str) -> list[lspt.TextEdit]:
|
|
198
230
|
"""Return formatted jac."""
|
|
199
231
|
try:
|
|
200
|
-
document = self.workspace.
|
|
232
|
+
document = self.workspace.get_text_document(file_path)
|
|
201
233
|
format = jac_str_to_pass(
|
|
202
234
|
jac_str=document.source,
|
|
203
235
|
file_path=document.path,
|
|
@@ -224,52 +256,88 @@ class JacLangServer(LanguageServer):
|
|
|
224
256
|
)
|
|
225
257
|
]
|
|
226
258
|
|
|
227
|
-
def
|
|
228
|
-
self, file_path: str,
|
|
229
|
-
) ->
|
|
230
|
-
"""Return
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
else []
|
|
259
|
+
def get_hover_info(
|
|
260
|
+
self, file_path: str, position: lspt.Position
|
|
261
|
+
) -> Optional[lspt.Hover]:
|
|
262
|
+
"""Return hover information for a file."""
|
|
263
|
+
node_selected = find_deepest_symbol_node_at_pos(
|
|
264
|
+
self.modules[file_path].ir, position.line, position.character
|
|
265
|
+
)
|
|
266
|
+
value = self.get_node_info(node_selected) if node_selected else None
|
|
267
|
+
if value:
|
|
268
|
+
return lspt.Hover(
|
|
269
|
+
contents=lspt.MarkupContent(
|
|
270
|
+
kind=lspt.MarkupKind.PlainText, value=f"{value}"
|
|
271
|
+
),
|
|
241
272
|
)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
else
|
|
273
|
+
return None
|
|
274
|
+
|
|
275
|
+
def get_node_info(self, node: ast.AstSymbolNode) -> Optional[str]:
|
|
276
|
+
"""Extract meaningful information from the AST node."""
|
|
277
|
+
try:
|
|
278
|
+
if isinstance(node, ast.NameSpec):
|
|
279
|
+
node = node.name_of
|
|
280
|
+
access = node.sym_link.access.value + " " if node.sym_link else None
|
|
281
|
+
node_info = (
|
|
282
|
+
f"({access if access else ''}{node.sym_type.value}) {node.sym_name}"
|
|
252
283
|
)
|
|
284
|
+
if node.sym_info.clean_type:
|
|
285
|
+
node_info += f": {node.sym_info.clean_type}"
|
|
286
|
+
if isinstance(node, ast.AstSemStrNode) and node.semstr:
|
|
287
|
+
node_info += f"\n{node.semstr.value}"
|
|
288
|
+
if isinstance(node, ast.AstDocNode) and node.doc:
|
|
289
|
+
node_info += f"\n{node.doc.value}"
|
|
290
|
+
if isinstance(node, ast.Ability) and node.signature:
|
|
291
|
+
node_info += f"\n{node.signature.unparse()}"
|
|
292
|
+
self.log_py(node.pp())
|
|
293
|
+
self.log_py(f"mypy_node: {node.gen.mypy_ast}")
|
|
294
|
+
except AttributeError as e:
|
|
295
|
+
self.log_warning(f"Attribute error when accessing node attributes: {e}")
|
|
296
|
+
return node_info.strip()
|
|
253
297
|
|
|
254
|
-
def
|
|
255
|
-
"""Return
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
298
|
+
def get_document_symbols(self, file_path: str) -> list[lspt.DocumentSymbol]:
|
|
299
|
+
"""Return document symbols for a file."""
|
|
300
|
+
root_node = self.modules[file_path].ir.sym_tab
|
|
301
|
+
if root_node:
|
|
302
|
+
return collect_symbols(root_node)
|
|
303
|
+
return []
|
|
304
|
+
|
|
305
|
+
def get_definition(
|
|
306
|
+
self, file_path: str, position: lspt.Position
|
|
307
|
+
) -> Optional[lspt.Location]:
|
|
308
|
+
"""Return definition location for a file."""
|
|
309
|
+
node_selected: Optional[ast.AstSymbolNode] = find_deepest_symbol_node_at_pos(
|
|
310
|
+
self.modules[file_path].ir, position.line, position.character
|
|
311
|
+
)
|
|
312
|
+
if node_selected:
|
|
313
|
+
if isinstance(node_selected, (ast.ElementStmt, ast.BuiltinType)):
|
|
314
|
+
return None
|
|
315
|
+
decl_node = (
|
|
316
|
+
node_selected.parent.body.target
|
|
317
|
+
if node_selected.parent
|
|
318
|
+
and isinstance(node_selected.parent, ast.AstImplNeedingNode)
|
|
319
|
+
and isinstance(node_selected.parent.body, ast.AstImplOnlyNode)
|
|
320
|
+
else (
|
|
321
|
+
node_selected.sym_link.decl
|
|
322
|
+
if (node_selected.sym_link and node_selected.sym_link.decl)
|
|
323
|
+
else node_selected
|
|
324
|
+
)
|
|
325
|
+
)
|
|
326
|
+
self.log_py(f"{node_selected}, {decl_node}")
|
|
327
|
+
decl_uri = uris.from_fs_path(decl_node.loc.mod_path)
|
|
328
|
+
try:
|
|
329
|
+
decl_range = create_range(decl_node.loc)
|
|
330
|
+
except ValueError: # 'print' name has decl in 0,0,0,0
|
|
331
|
+
return None
|
|
332
|
+
decl_location = lspt.Location(
|
|
333
|
+
uri=decl_uri,
|
|
334
|
+
range=decl_range,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
return decl_location
|
|
338
|
+
else:
|
|
339
|
+
self.log_info("No declaration found for the selected node.")
|
|
340
|
+
return None
|
|
273
341
|
|
|
274
342
|
def log_error(self, message: str) -> None:
|
|
275
343
|
"""Log an error message."""
|
|
@@ -285,3 +353,7 @@ class JacLangServer(LanguageServer):
|
|
|
285
353
|
"""Log an info message."""
|
|
286
354
|
self.show_message_log(message, lspt.MessageType.Info)
|
|
287
355
|
self.show_message(message, lspt.MessageType.Info)
|
|
356
|
+
|
|
357
|
+
def log_py(self, message: str) -> None:
|
|
358
|
+
"""Log a message."""
|
|
359
|
+
logging.info(message)
|
jaclang/langserve/server.py
CHANGED
|
@@ -15,21 +15,22 @@ 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()
|
|
32
|
-
ls.log_info("Analysis restarted")
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
def stop_analysis() -> None:
|
|
@@ -130,6 +131,34 @@ def formatting(
|
|
|
130
131
|
return ls.formatted_jac(params.text_document.uri)
|
|
131
132
|
|
|
132
133
|
|
|
134
|
+
@server.feature(lspt.TEXT_DOCUMENT_HOVER, lspt.HoverOptions(work_done_progress=True))
|
|
135
|
+
def hover(
|
|
136
|
+
ls: JacLangServer, params: lspt.TextDocumentPositionParams
|
|
137
|
+
) -> Optional[lspt.Hover]:
|
|
138
|
+
"""Provide hover information for the given hover request."""
|
|
139
|
+
return ls.get_hover_info(params.text_document.uri, params.position)
|
|
140
|
+
|
|
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
|
+
|
|
133
162
|
def run_lang_server() -> None:
|
|
134
163
|
"""Run the language server."""
|
|
135
164
|
server.start_io()
|