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.

Files changed (106) hide show
  1. jaclang/cli/cli.py +2 -2
  2. jaclang/compiler/absyntree.py +539 -297
  3. jaclang/compiler/codeloc.py +2 -2
  4. jaclang/compiler/constant.py +100 -2
  5. jaclang/compiler/jac.lark +27 -19
  6. jaclang/compiler/parser.py +119 -92
  7. jaclang/compiler/passes/main/access_modifier_pass.py +20 -12
  8. jaclang/compiler/passes/main/def_impl_match_pass.py +32 -12
  9. jaclang/compiler/passes/main/def_use_pass.py +59 -40
  10. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +71 -30
  11. jaclang/compiler/passes/main/import_pass.py +12 -7
  12. jaclang/compiler/passes/main/pyast_gen_pass.py +110 -47
  13. jaclang/compiler/passes/main/pyast_load_pass.py +49 -13
  14. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +25 -11
  15. jaclang/compiler/passes/main/pyout_pass.py +3 -1
  16. jaclang/compiler/passes/main/registry_pass.py +6 -6
  17. jaclang/compiler/passes/main/sub_node_tab_pass.py +0 -5
  18. jaclang/compiler/passes/main/sym_tab_build_pass.py +43 -235
  19. jaclang/compiler/passes/main/tests/test_decl_def_match_pass.py +21 -4
  20. jaclang/compiler/passes/main/tests/test_def_use_pass.py +5 -10
  21. jaclang/compiler/passes/main/tests/test_import_pass.py +8 -0
  22. jaclang/compiler/passes/main/tests/test_type_check_pass.py +1 -1
  23. jaclang/compiler/passes/main/type_check_pass.py +2 -1
  24. jaclang/compiler/passes/tool/jac_formatter_pass.py +44 -11
  25. jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +16 -0
  26. jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +16 -0
  27. jaclang/compiler/passes/tool/tests/fixtures/doc_string.jac +15 -0
  28. jaclang/compiler/passes/tool/tests/fixtures/genai/essay_review.jac +1 -1
  29. jaclang/compiler/passes/tool/tests/fixtures/genai/expert_answer.jac +1 -1
  30. jaclang/compiler/passes/tool/tests/fixtures/genai/joke_gen.jac +1 -1
  31. jaclang/compiler/passes/tool/tests/fixtures/genai/odd_word_out.jac +1 -1
  32. jaclang/compiler/passes/tool/tests/fixtures/genai/personality_finder.jac +1 -1
  33. jaclang/compiler/passes/tool/tests/fixtures/genai/text_to_type.jac +1 -1
  34. jaclang/compiler/passes/tool/tests/fixtures/genai/translator.jac +1 -1
  35. jaclang/compiler/passes/tool/tests/fixtures/genai/wikipedia.jac +1 -1
  36. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +7 -5
  37. jaclang/compiler/passes/tool/tests/test_unparse_validate.py +1 -2
  38. jaclang/compiler/passes/transform.py +2 -4
  39. jaclang/{core/registry.py → compiler/semtable.py} +1 -3
  40. jaclang/compiler/symtable.py +150 -89
  41. jaclang/compiler/tests/test_parser.py +2 -2
  42. jaclang/core/aott.py +118 -18
  43. jaclang/core/{construct.py → architype.py} +44 -93
  44. jaclang/core/constructs.py +44 -0
  45. jaclang/core/context.py +157 -0
  46. jaclang/core/importer.py +18 -9
  47. jaclang/core/memory.py +53 -2
  48. jaclang/core/test.py +90 -0
  49. jaclang/core/utils.py +2 -2
  50. jaclang/langserve/engine.py +199 -138
  51. jaclang/langserve/server.py +48 -53
  52. jaclang/langserve/tests/fixtures/base_module_structure.jac +28 -0
  53. jaclang/langserve/tests/fixtures/circle.jac +16 -12
  54. jaclang/langserve/tests/fixtures/circle_err.jac +3 -3
  55. jaclang/langserve/tests/fixtures/circle_pure.impl.jac +8 -4
  56. jaclang/langserve/tests/fixtures/circle_pure.jac +2 -2
  57. jaclang/langserve/tests/fixtures/circle_pure.test.jac +15 -0
  58. jaclang/langserve/tests/fixtures/import_include_statements.jac +6 -0
  59. jaclang/langserve/tests/fixtures/py_import.py +26 -0
  60. jaclang/langserve/tests/test_server.py +200 -2
  61. jaclang/langserve/utils.py +214 -10
  62. jaclang/plugin/builtin.py +1 -1
  63. jaclang/plugin/default.py +48 -92
  64. jaclang/plugin/feature.py +33 -17
  65. jaclang/plugin/spec.py +18 -20
  66. jaclang/plugin/tests/test_features.py +0 -33
  67. jaclang/settings.py +4 -0
  68. jaclang/tests/fixtures/abc.jac +16 -12
  69. jaclang/tests/fixtures/aott_raise.jac +1 -1
  70. jaclang/tests/fixtures/byllmissue.jac +12 -0
  71. jaclang/tests/fixtures/edgetypeissue.jac +10 -0
  72. jaclang/tests/fixtures/hash_init_check.jac +17 -0
  73. jaclang/tests/fixtures/hello.jac +1 -1
  74. jaclang/tests/fixtures/impl_match_confused.impl.jac +1 -0
  75. jaclang/tests/fixtures/impl_match_confused.jac +5 -0
  76. jaclang/tests/fixtures/math_question.jpg +0 -0
  77. jaclang/tests/fixtures/maxfail_run_test.jac +17 -5
  78. jaclang/tests/fixtures/nosigself.jac +19 -0
  79. jaclang/tests/fixtures/run_test.jac +17 -5
  80. jaclang/tests/fixtures/walker_override.jac +21 -0
  81. jaclang/tests/fixtures/with_llm_function.jac +1 -1
  82. jaclang/tests/fixtures/with_llm_lower.jac +1 -1
  83. jaclang/tests/fixtures/with_llm_method.jac +1 -1
  84. jaclang/tests/fixtures/with_llm_type.jac +1 -1
  85. jaclang/tests/fixtures/with_llm_vision.jac +25 -0
  86. jaclang/tests/test_bugs.py +19 -0
  87. jaclang/tests/test_cli.py +1 -1
  88. jaclang/tests/test_language.py +197 -82
  89. jaclang/tests/test_reference.py +1 -1
  90. jaclang/utils/lang_tools.py +5 -4
  91. jaclang/utils/test.py +2 -1
  92. jaclang/utils/treeprinter.py +35 -4
  93. {jaclang-0.7.1.dist-info → jaclang-0.7.7.dist-info}/METADATA +3 -2
  94. {jaclang-0.7.1.dist-info → jaclang-0.7.7.dist-info}/RECORD +96 -88
  95. jaclang/core/llms/__init__.py +0 -20
  96. jaclang/core/llms/anthropic.py +0 -61
  97. jaclang/core/llms/base.py +0 -206
  98. jaclang/core/llms/groq.py +0 -67
  99. jaclang/core/llms/huggingface.py +0 -73
  100. jaclang/core/llms/ollama.py +0 -78
  101. jaclang/core/llms/openai.py +0 -61
  102. jaclang/core/llms/togetherai.py +0 -60
  103. jaclang/core/llms/utils.py +0 -9
  104. jaclang/core/shelve_storage.py +0 -55
  105. {jaclang-0.7.1.dist-info → jaclang-0.7.7.dist-info}/WHEEL +0 -0
  106. {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
- """Memory abstraction for jaseci plugin."""
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 jaclang.core.construct import Architype
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.core.registry import SemScope
11
+ from jaclang.compiler.semtable import SemScope
12
12
 
13
13
  if TYPE_CHECKING:
14
- from jaclang.core.construct import NodeAnchor, NodeArchitype
14
+ from jaclang.core.constructs import NodeAnchor, NodeArchitype
15
15
 
16
16
 
17
17
  @contextmanager
@@ -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 hashlib import md5
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 find_deepest_node_at_pos
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: Sequence[Alert],
39
- warnings: Sequence[Alert],
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=lspt.Range(
66
- start=lspt.Position(
67
- line=error.loc.first_line - 1, character=error.loc.col_start - 1
68
- ),
69
- end=lspt.Position(
70
- line=error.loc.last_line - 1,
71
- character=error.loc.col_end - 1,
72
- ),
73
- ),
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=lspt.Range(
81
- start=lspt.Position(
82
- line=warning.loc.first_line - 1,
83
- character=warning.loc.col_start - 1,
84
- ),
85
- end=lspt.Position(
86
- line=warning.loc.last_line - 1,
87
- character=warning.loc.col_end - 1,
88
- ),
89
- ),
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(self, file_path: str, build: Pass, alev: ALev) -> None:
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
- self.modules[file_path] = ModuleInfo(
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
- self.modules[uri] = ModuleInfo(
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) -> None:
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.get_document(file_path)
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
- self.update_modules(file_path, build, ALev.QUICK)
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) -> None:
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) -> None:
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.get_document(file_path)
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.get_document(file_path)
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 = find_deepest_node_at_pos(
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.AstNode) -> Optional[str]:
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.Token):
278
- if isinstance(node, ast.AstSymbolNode):
279
- if isinstance(node, ast.String):
280
- return None
281
- if node.sym_link and node.sym_link.decl:
282
- decl_node = node.sym_link.decl
283
- if isinstance(decl_node, ast.Architype):
284
- if decl_node.doc:
285
- node_info = f"({decl_node.arch_type.value}) {node.value} \n{decl_node.doc.lit_value}"
286
- else:
287
- node_info = (
288
- f"({decl_node.arch_type.value}) {node.value}"
289
- )
290
- if decl_node.semstr:
291
- node_info += f"\n{decl_node.semstr.lit_value}"
292
- elif isinstance(decl_node, ast.Ability):
293
- node_info = f"(ability) can {node.value}"
294
- if decl_node.signature:
295
- node_info += f" {decl_node.signature.unparse()}"
296
- if decl_node.doc:
297
- node_info += f"\n{decl_node.doc.lit_value}"
298
- if decl_node.semstr:
299
- node_info += f"\n{decl_node.semstr.lit_value}"
300
- elif isinstance(decl_node, ast.Name):
301
- if (
302
- decl_node.parent
303
- and isinstance(decl_node.parent, ast.SubNodeList)
304
- and decl_node.parent.parent
305
- and isinstance(decl_node.parent.parent, ast.Assignment)
306
- and decl_node.parent.parent.type_tag
307
- ):
308
- node_info = (
309
- f"(variable) {decl_node.value}: "
310
- f"{decl_node.parent.parent.type_tag.unparse()}"
311
- )
312
- if decl_node.parent.parent.semstr:
313
- node_info += (
314
- f"\n{decl_node.parent.parent.semstr.lit_value}"
315
- )
316
- else:
317
- if decl_node.value in [
318
- "str",
319
- "int",
320
- "float",
321
- "bool",
322
- "bytes",
323
- "list",
324
- "tuple",
325
- "set",
326
- "dict",
327
- "type",
328
- ]:
329
- node_info = f"({decl_node.value}) Built-in type"
330
- else:
331
- node_info = f"(variable) {decl_node.value}: None"
332
- elif isinstance(decl_node, ast.HasVar):
333
- if decl_node.type_tag:
334
- node_info = f"(variable) {decl_node.name.value} {decl_node.type_tag.unparse()}"
335
- else:
336
- node_info = f"(variable) {decl_node.name.value}"
337
- if decl_node.semstr:
338
- node_info += f"\n{decl_node.semstr.lit_value}"
339
- elif isinstance(decl_node, ast.ParamVar):
340
- if decl_node.type_tag:
341
- node_info = f"(parameter) {decl_node.name.value} {decl_node.type_tag.unparse()}"
342
- else:
343
- node_info = f"(parameter) {decl_node.name.value}"
344
- if decl_node.semstr:
345
- node_info += f"\n{decl_node.semstr.lit_value}"
346
- elif isinstance(decl_node, ast.ModuleItem):
347
- node_info = (
348
- f"(ModuleItem) {node.value}" # TODO: Add more info
349
- )
350
- else:
351
- node_info = f"{node.value}"
352
- else:
353
- node_info = f"{node.value}" # non symbol node
354
- else:
355
- return None
356
- else:
357
- return None
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)