jaclang 0.7.5__py3-none-any.whl → 0.7.6__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 (55) hide show
  1. jaclang/compiler/absyntree.py +167 -26
  2. jaclang/compiler/constant.py +98 -2
  3. jaclang/compiler/jac.lark +2 -0
  4. jaclang/compiler/parser.py +4 -0
  5. jaclang/compiler/passes/main/access_modifier_pass.py +5 -3
  6. jaclang/compiler/passes/main/def_impl_match_pass.py +3 -1
  7. jaclang/compiler/passes/main/def_use_pass.py +27 -39
  8. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +34 -12
  9. jaclang/compiler/passes/main/sub_node_tab_pass.py +0 -5
  10. jaclang/compiler/passes/main/sym_tab_build_pass.py +31 -181
  11. jaclang/compiler/passes/tool/tests/fixtures/genai/essay_review.jac +1 -1
  12. jaclang/compiler/passes/tool/tests/fixtures/genai/expert_answer.jac +1 -1
  13. jaclang/compiler/passes/tool/tests/fixtures/genai/joke_gen.jac +1 -1
  14. jaclang/compiler/passes/tool/tests/fixtures/genai/odd_word_out.jac +1 -1
  15. jaclang/compiler/passes/tool/tests/fixtures/genai/personality_finder.jac +1 -1
  16. jaclang/compiler/passes/tool/tests/fixtures/genai/text_to_type.jac +1 -1
  17. jaclang/compiler/passes/tool/tests/fixtures/genai/translator.jac +1 -1
  18. jaclang/compiler/passes/tool/tests/fixtures/genai/wikipedia.jac +1 -1
  19. jaclang/compiler/symtable.py +118 -65
  20. jaclang/core/aott.py +7 -3
  21. jaclang/core/importer.py +1 -1
  22. jaclang/langserve/engine.py +100 -36
  23. jaclang/langserve/server.py +34 -61
  24. jaclang/langserve/tests/fixtures/base_module_structure.jac +28 -0
  25. jaclang/langserve/tests/fixtures/circle_pure.test.jac +15 -0
  26. jaclang/langserve/tests/fixtures/import_include_statements.jac +6 -0
  27. jaclang/langserve/tests/fixtures/py_import.py +26 -0
  28. jaclang/langserve/tests/test_server.py +90 -6
  29. jaclang/langserve/utils.py +114 -4
  30. jaclang/plugin/default.py +2 -2
  31. jaclang/plugin/feature.py +1 -1
  32. jaclang/plugin/spec.py +1 -1
  33. jaclang/tests/fixtures/aott_raise.jac +1 -1
  34. jaclang/tests/fixtures/edgetypeissue.jac +10 -0
  35. jaclang/tests/fixtures/hello.jac +1 -1
  36. jaclang/tests/fixtures/with_llm_function.jac +1 -1
  37. jaclang/tests/fixtures/with_llm_lower.jac +1 -1
  38. jaclang/tests/fixtures/with_llm_method.jac +1 -1
  39. jaclang/tests/fixtures/with_llm_type.jac +1 -1
  40. jaclang/tests/fixtures/with_llm_vision.jac +1 -1
  41. jaclang/tests/test_language.py +106 -96
  42. {jaclang-0.7.5.dist-info → jaclang-0.7.6.dist-info}/METADATA +1 -1
  43. {jaclang-0.7.5.dist-info → jaclang-0.7.6.dist-info}/RECORD +45 -50
  44. jaclang/core/llms/__init__.py +0 -20
  45. jaclang/core/llms/anthropic.py +0 -90
  46. jaclang/core/llms/base.py +0 -206
  47. jaclang/core/llms/groq.py +0 -70
  48. jaclang/core/llms/huggingface.py +0 -76
  49. jaclang/core/llms/ollama.py +0 -81
  50. jaclang/core/llms/openai.py +0 -65
  51. jaclang/core/llms/togetherai.py +0 -63
  52. jaclang/core/llms/utils.py +0 -9
  53. jaclang/tests/fixtures/edgetypetest.jac +0 -16
  54. {jaclang-0.7.5.dist-info → jaclang-0.7.6.dist-info}/WHEEL +0 -0
  55. {jaclang-0.7.5.dist-info → jaclang-0.7.6.dist-info}/entry_points.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  import:py wikipedia;
2
- import:py from jaclang.core.llms, Anthropic;
2
+ import:py from mtllm.llms, Anthropic;
3
3
 
4
4
  glob llm = Anthropic(model_name="claude-3-sonnet-20240229");
5
5
 
@@ -2,60 +2,14 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from enum import Enum
6
- from typing import Optional, TYPE_CHECKING
5
+ import ast as ast3
6
+ from typing import Optional, Sequence
7
7
 
8
+ import jaclang.compiler.absyntree as ast
9
+ from jaclang.compiler.constant import SymbolAccess, SymbolType
8
10
  from jaclang.utils.treeprinter import dotgen_symtab_tree, print_symtab_tree
9
11
 
10
12
 
11
- if TYPE_CHECKING:
12
- import jaclang.compiler.absyntree as ast
13
-
14
-
15
- class SymbolType(Enum):
16
- """Symbol types."""
17
-
18
- MODULE = "module" # LSP: Module
19
- MOD_VAR = "mod_var" # LSP: Variable
20
- VAR = "variable" # LSP: Variable
21
- IMM_VAR = "immutable" # LSP: Constant
22
- ABILITY = "ability" # LSP: Function
23
- OBJECT_ARCH = "object" # LSP: Class
24
- NODE_ARCH = "node" # LSP: Class
25
- EDGE_ARCH = "edge" # LSP: Class
26
- WALKER_ARCH = "walker" # LSP: Class
27
- ENUM_ARCH = "enum" # LSP: Enum
28
- TEST = "test" # LSP: Function
29
- TYPE = "type" # LSP: TypeParameter
30
- IMPL = "impl" # LSP: Interface or Property
31
- HAS_VAR = "field" # LSP: Field
32
- METHOD = "method" # LSP: Method
33
- CONSTRUCTOR = "constructor" # LSP: Constructor
34
- ENUM_MEMBER = "enum_member" # LSP: EnumMember
35
- NUMBER = "number" # LSP: Number
36
- STRING = "string" # LSP: String
37
- BOOL = "bool" # LSP: Boolean
38
- SEQUENCE = "sequence" # LSP: Array
39
- NULL = "null" # LSP: Null
40
- UNKNOWN = "unknown" # LSP: Unknown
41
-
42
- def __str__(self) -> str:
43
- """Stringify."""
44
- return self.value
45
-
46
-
47
- class SymbolAccess(Enum):
48
- """Symbol types."""
49
-
50
- PRIVATE = "private"
51
- PUBLIC = "public"
52
- PROTECTED = "protected"
53
-
54
- def __str__(self) -> str:
55
- """Stringify."""
56
- return self.value
57
-
58
-
59
13
  # Symbols can have mulitple definitions but resolves decl to be the
60
14
  # first such definition in a given scope.
61
15
  class Symbol:
@@ -93,13 +47,10 @@ class Symbol:
93
47
  def sym_dotted_name(self) -> str:
94
48
  """Return a full path of the symbol."""
95
49
  out = [self.defn[0].sym_name]
96
- current_tab = self.parent_tab
50
+ current_tab: SymbolTable | None = self.parent_tab
97
51
  while current_tab is not None:
98
52
  out.append(current_tab.name)
99
- if current_tab.has_parent():
100
- current_tab = current_tab.parent
101
- else:
102
- break
53
+ current_tab = current_tab.parent
103
54
  out.reverse()
104
55
  return ".".join(out)
105
56
 
@@ -127,19 +78,13 @@ class SymbolTable:
127
78
  """Initialize."""
128
79
  self.name = name
129
80
  self.owner = owner
130
- self.parent = parent if parent else self
81
+ self.parent = parent
131
82
  self.kid: list[SymbolTable] = []
132
83
  self.tab: dict[str, Symbol] = {}
133
84
  self.inherit: list[SymbolTable] = []
134
85
 
135
- def has_parent(self) -> bool:
136
- """Check if has parent."""
137
- return self.parent != self
138
-
139
- def get_parent(self) -> SymbolTable:
86
+ def get_parent(self) -> Optional[SymbolTable]:
140
87
  """Get parent."""
141
- if self.parent == self:
142
- raise Exception("No parent")
143
88
  return self.parent
144
89
 
145
90
  def lookup(self, name: str, deep: bool = True) -> Optional[Symbol]:
@@ -150,8 +95,8 @@ class SymbolTable:
150
95
  found = i.lookup(name, deep=False)
151
96
  if found:
152
97
  return found
153
- if deep and self.has_parent():
154
- return self.get_parent().lookup(name, deep)
98
+ if deep and self.parent:
99
+ return self.parent.lookup(name, deep)
155
100
  return None
156
101
 
157
102
  def insert(
@@ -197,6 +142,114 @@ class SymbolTable:
197
142
  self.kid.append(SymbolTable(name, key_node, self))
198
143
  return self.kid[-1]
199
144
 
145
+ def inherit_sym_tab(self, target_sym_tab: SymbolTable) -> None:
146
+ """Inherit symbol table."""
147
+ for i in target_sym_tab.tab.values():
148
+ self.def_insert(i.decl, access_spec=i.access)
149
+
150
+ def def_insert(
151
+ self,
152
+ node: ast.AstSymbolNode,
153
+ access_spec: Optional[ast.AstAccessNode] | SymbolAccess = None,
154
+ single_decl: Optional[str] = None,
155
+ ) -> Optional[Symbol]:
156
+ """Insert into symbol table."""
157
+ if node.sym and self == node.sym.parent_tab:
158
+ return node.sym
159
+ self.insert(node=node, single=single_decl is not None, access_spec=access_spec)
160
+ self.update_py_ctx_for_def(node)
161
+ return node.sym
162
+
163
+ def chain_def_insert(self, node_list: list[ast.AstSymbolNode]) -> None:
164
+ """Link chain of containing names to symbol."""
165
+ if not node_list:
166
+ return
167
+ cur_sym_tab: SymbolTable | None = node_list[0].sym_tab
168
+ node_list[-1].name_spec.py_ctx_func = ast3.Store
169
+ if isinstance(node_list[-1].name_spec, ast.AstSymbolNode):
170
+ node_list[-1].name_spec.py_ctx_func = ast3.Store
171
+
172
+ node_list = node_list[:-1] # Just performs lookup mappings of pre assign chain
173
+ for i in node_list:
174
+ cur_sym_tab = (
175
+ lookup.decl.sym_tab
176
+ if (
177
+ lookup := self.use_lookup(
178
+ i,
179
+ sym_table=cur_sym_tab,
180
+ )
181
+ )
182
+ else None
183
+ )
184
+
185
+ def use_lookup(
186
+ self,
187
+ node: ast.AstSymbolNode,
188
+ sym_table: Optional[SymbolTable] = None,
189
+ ) -> Optional[Symbol]:
190
+ """Link to symbol."""
191
+ if node.sym:
192
+ return node.sym
193
+ if not sym_table:
194
+ sym_table = node.sym_tab
195
+ if sym_table:
196
+ lookup = sym_table.lookup(name=node.sym_name, deep=True)
197
+ lookup.add_use(node.name_spec) if lookup else None
198
+ return node.sym
199
+
200
+ def chain_use_lookup(self, node_list: Sequence[ast.AstSymbolNode]) -> None:
201
+ """Link chain of containing names to symbol."""
202
+ if not node_list:
203
+ return
204
+ cur_sym_tab: SymbolTable | None = node_list[0].sym_tab
205
+ for i in node_list:
206
+ if cur_sym_tab is None:
207
+ break
208
+ cur_sym_tab = (
209
+ lookup.decl.sym_tab
210
+ if (
211
+ lookup := self.use_lookup(
212
+ i,
213
+ sym_table=cur_sym_tab,
214
+ )
215
+ )
216
+ else None
217
+ )
218
+
219
+ def update_py_ctx_for_def(self, node: ast.AstSymbolNode) -> None:
220
+ """Update python context for definition."""
221
+ node.name_spec.py_ctx_func = ast3.Store
222
+ if isinstance(node, (ast.TupleVal, ast.ListVal)) and node.values:
223
+ # Handling of UnaryExpr case for item is only necessary for
224
+ # the generation of Starred nodes in the AST for examples
225
+ # like `(a, *b) = (1, 2, 3, 4)`.
226
+ def fix(item: ast.TupleVal | ast.ListVal | ast.UnaryExpr) -> None:
227
+ if isinstance(item, ast.UnaryExpr):
228
+ if isinstance(item.operand, ast.AstSymbolNode):
229
+ item.operand.name_spec.py_ctx_func = ast3.Store
230
+ elif isinstance(item, (ast.TupleVal, ast.ListVal)):
231
+ for i in item.values.items if item.values else []:
232
+ if isinstance(i, ast.AstSymbolNode):
233
+ i.name_spec.py_ctx_func = ast3.Store
234
+ elif isinstance(i, ast.AtomTrailer):
235
+ self.chain_def_insert(i.as_attr_list)
236
+ if isinstance(i, (ast.TupleVal, ast.ListVal, ast.UnaryExpr)):
237
+ fix(i)
238
+
239
+ fix(node)
240
+
241
+ def inherit_baseclasses_sym(self, node: ast.Architype | ast.Enum) -> None:
242
+ """Inherit base classes symbol tables."""
243
+ if node.base_classes:
244
+ for base_cls in node.base_classes.items:
245
+ if (
246
+ isinstance(base_cls, ast.AstSymbolNode)
247
+ and (found := self.use_lookup(base_cls))
248
+ and found
249
+ ):
250
+ self.inherit.append(found.decl.sym_tab)
251
+ base_cls.name_spec.name_of = found.decl.name_of
252
+
200
253
  def pp(self, depth: Optional[int] = None) -> str:
201
254
  """Pretty print."""
202
255
  return print_symtab_tree(root=self, depth=depth)
jaclang/core/aott.py CHANGED
@@ -18,14 +18,18 @@ except ImportError:
18
18
  Image = None
19
19
 
20
20
  from jaclang.compiler.semtable import SemInfo, SemRegistry, SemScope
21
- from jaclang.core.llms.base import BaseLLM
21
+
22
+ try:
23
+ from mtllm.llms import BaseLLM
24
+ except ImportError:
25
+ BaseLLM = None
22
26
 
23
27
 
24
28
  IMG_FORMATS = ["PngImageFile", "JpegImageFile"]
25
29
 
26
30
 
27
31
  def aott_raise(
28
- model: BaseLLM,
32
+ model: BaseLLM, # type: ignore
29
33
  information: str,
30
34
  inputs_information: str | list[dict],
31
35
  output_information: str,
@@ -298,7 +302,7 @@ def get_input_information(
298
302
  return inputs_information_dict_list
299
303
 
300
304
 
301
- def image_to_base64(image: Image) -> str:
305
+ def image_to_base64(image: Image) -> str: # type: ignore
302
306
  """Convert an image to base64 expected by OpenAI."""
303
307
  if not Image:
304
308
  log = logging.getLogger(__name__)
jaclang/core/importer.py CHANGED
@@ -45,7 +45,7 @@ def jac_importer(
45
45
  if isinstance(mod_bundle, str)
46
46
  and mod_bundle in sys.modules
47
47
  and "__jac_mod_bundle__" in sys.modules[mod_bundle].__dict__
48
- else mod_bundle
48
+ else None
49
49
  )
50
50
 
51
51
  caller_dir = get_caller_dir(target, base_path, dir_path)
@@ -4,7 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  import logging
6
6
  from enum import IntEnum
7
- from hashlib import md5
8
7
  from typing import Optional
9
8
 
10
9
 
@@ -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
@@ -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,13 +65,19 @@ class ModuleInfo:
63
65
  """Return if there are syntax errors."""
64
66
  return len(self.errors) > 0 and self.alev == ALev.QUICK
65
67
 
66
- def update_with(self, new_info: ModuleInfo) -> None:
68
+ def update_with(self, new_info: ModuleInfo, refresh: bool = False) -> None:
67
69
  """Update module info."""
68
70
  self.ir = new_info.ir
69
- self.errors += [i for i in new_info.errors if i not in self.errors]
70
- self.warnings += [i for i in new_info.warnings if i not in self.warnings]
71
+ 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]
71
77
  self.alev = new_info.alev
72
78
  self.diagnostics = self.gen_diagnostics()
79
+ if self.alev == ALev.TYPE:
80
+ self.sem_tokens = self.gen_sem_tokens()
73
81
 
74
82
  def gen_diagnostics(self) -> list[lspt.Diagnostic]:
75
83
  """Return diagnostics."""
@@ -89,6 +97,27 @@ class ModuleInfo:
89
97
  for warning in self.warnings
90
98
  ]
91
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
+
92
121
 
93
122
  class JacLangServer(LanguageServer):
94
123
  """Class for managing workspace."""
@@ -98,19 +127,6 @@ class JacLangServer(LanguageServer):
98
127
  super().__init__("jac-lsp", "v0.1")
99
128
  self.modules: dict[str, ModuleInfo] = {}
100
129
 
101
- def module_not_diff(self, uri: str, alev: ALev) -> bool:
102
- """Check if module was changed."""
103
- doc = self.workspace.get_text_document(uri)
104
- return (
105
- doc.uri in self.modules
106
- and self.modules[doc.uri].ir.source.hash
107
- == md5(doc.source.encode()).hexdigest()
108
- and (
109
- self.modules[doc.uri].alev >= alev
110
- or self.modules[doc.uri].has_syntax_error
111
- )
112
- )
113
-
114
130
  def push_diagnostics(self, file_path: str) -> None:
115
131
  """Push diagnostics for a file."""
116
132
  if file_path in self.modules:
@@ -153,8 +169,8 @@ class JacLangServer(LanguageServer):
153
169
  ],
154
170
  alev=alev,
155
171
  )
156
- if not refresh and file_path in self.modules:
157
- self.modules[file_path].update_with(new_mod)
172
+ if file_path in self.modules:
173
+ self.modules[file_path].update_with(new_mod, refresh=refresh)
158
174
  else:
159
175
  self.modules[file_path] = new_mod
160
176
  for p in build.ir.mod_deps.keys():
@@ -173,10 +189,8 @@ class JacLangServer(LanguageServer):
173
189
  self.modules[file_path] if file_path != uri else None
174
190
  )
175
191
 
176
- def quick_check(self, file_path: str, force: bool = False) -> None:
192
+ def quick_check(self, file_path: str) -> bool:
177
193
  """Rebuild a file."""
178
- if not force and self.module_not_diff(file_path, ALev.QUICK):
179
- return
180
194
  try:
181
195
  document = self.workspace.get_text_document(file_path)
182
196
  build = jac_str_to_pass(
@@ -184,27 +198,27 @@ class JacLangServer(LanguageServer):
184
198
  )
185
199
  except Exception as e:
186
200
  self.log_error(f"Error during syntax check: {e}")
201
+ return False
187
202
  self.update_modules(file_path, build, ALev.QUICK, refresh=True)
203
+ return len(self.modules[file_path].errors) == 0
188
204
 
189
- def deep_check(self, file_path: str, force: bool = False) -> None:
205
+ def deep_check(self, file_path: str) -> bool:
190
206
  """Rebuild a file and its dependencies."""
191
- if file_path in self.modules:
192
- self.quick_check(file_path, force=force)
193
- if not force and self.module_not_diff(file_path, ALev.DEEP):
194
- return
207
+ if file_path not in self.modules:
208
+ self.quick_check(file_path)
195
209
  try:
196
210
  file_path = self.unwind_to_parent(file_path)
197
211
  build = jac_ir_to_pass(ir=self.modules[file_path].ir)
198
212
  except Exception as e:
199
213
  self.log_error(f"Error during syntax check: {e}")
214
+ return False
200
215
  self.update_modules(file_path, build, ALev.DEEP)
216
+ return len(self.modules[file_path].errors) == 0
201
217
 
202
- def type_check(self, file_path: str, force: bool = False) -> None:
218
+ def type_check(self, file_path: str) -> bool:
203
219
  """Rebuild a file and its dependencies."""
204
220
  if file_path not in self.modules:
205
- self.deep_check(file_path, force=force)
206
- if not force and self.module_not_diff(file_path, ALev.TYPE):
207
- return
221
+ self.deep_check(file_path)
208
222
  try:
209
223
  file_path = self.unwind_to_parent(file_path)
210
224
  build = jac_ir_to_pass(
@@ -212,7 +226,21 @@ class JacLangServer(LanguageServer):
212
226
  )
213
227
  except Exception as e:
214
228
  self.log_error(f"Error during type check: {e}")
229
+ return False
215
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)
216
244
 
217
245
  def get_completion(
218
246
  self, file_path: str, position: lspt.Position
@@ -310,8 +338,7 @@ class JacLangServer(LanguageServer):
310
338
 
311
339
  def get_document_symbols(self, file_path: str) -> list[lspt.DocumentSymbol]:
312
340
  """Return document symbols for a file."""
313
- root_node = self.modules[file_path].ir.sym_tab
314
- if root_node:
341
+ if root_node := self.modules[file_path].ir._sym_tab:
315
342
  return collect_symbols(root_node)
316
343
  return []
317
344
 
@@ -323,7 +350,39 @@ class JacLangServer(LanguageServer):
323
350
  self.modules[file_path].ir, position.line, position.character
324
351
  )
325
352
  if node_selected:
326
- if isinstance(node_selected, (ast.ElementStmt, ast.BuiltinType)):
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)):
327
386
  return None
328
387
  decl_node = (
329
388
  node_selected.parent.body.target
@@ -349,9 +408,14 @@ class JacLangServer(LanguageServer):
349
408
 
350
409
  return decl_location
351
410
  else:
352
- self.log_info("No declaration found for the selected node.")
353
411
  return None
354
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
+
355
419
  def log_error(self, message: str) -> None:
356
420
  """Log an error message."""
357
421
  self.show_message_log(message, lspt.MessageType.Error)