jaclang 0.7.11__py3-none-any.whl → 0.7.13__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.

@@ -5,23 +5,27 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  import logging
7
7
  from concurrent.futures import ThreadPoolExecutor
8
- from enum import IntEnum
9
- from typing import Optional
8
+ from typing import Callable, Optional
10
9
 
11
10
 
12
11
  import jaclang.compiler.absyntree as ast
13
- from jaclang.compiler.compile import jac_ir_to_pass, jac_str_to_pass
12
+ from jaclang.compiler.compile import jac_str_to_pass
14
13
  from jaclang.compiler.parser import JacParser
15
14
  from jaclang.compiler.passes import Pass
16
- from jaclang.compiler.passes.main.schedules import type_checker_sched
15
+ from jaclang.compiler.passes.main.schedules import py_code_gen_typed
17
16
  from jaclang.compiler.passes.tool import FuseCommentsPass, JacFormatPass
18
17
  from jaclang.compiler.passes.transform import Alert
19
18
  from jaclang.langserve.utils import (
19
+ collect_all_symbols_in_scope,
20
20
  collect_symbols,
21
21
  create_range,
22
22
  find_deepest_symbol_node_at_pos,
23
+ gen_diagnostics,
23
24
  get_item_path,
24
25
  get_mod_path,
26
+ locate_affected_token,
27
+ parse_symbol_path,
28
+ resolve_completion_symbol_table,
25
29
  )
26
30
  from jaclang.vendor.pygls import uris
27
31
  from jaclang.vendor.pygls.server import LanguageServer
@@ -29,14 +33,6 @@ from jaclang.vendor.pygls.server import LanguageServer
29
33
  import lsprotocol.types as lspt
30
34
 
31
35
 
32
- class ALev(IntEnum):
33
- """Analysis Level successfully completed."""
34
-
35
- QUICK = 1
36
- DEEP = 2
37
- TYPE = 3
38
-
39
-
40
36
  class ModuleInfo:
41
37
  """Module IR and Stats."""
42
38
 
@@ -45,16 +41,11 @@ class ModuleInfo:
45
41
  ir: ast.Module,
46
42
  errors: list[Alert],
47
43
  warnings: list[Alert],
48
- alev: ALev,
49
44
  parent: Optional[ModuleInfo] = None,
50
45
  ) -> None:
51
46
  """Initialize module info."""
52
47
  self.ir = ir
53
- self.errors = errors
54
- self.warnings = warnings
55
- self.alev = alev
56
48
  self.parent: Optional[ModuleInfo] = parent
57
- self.diagnostics = self.gen_diagnostics()
58
49
  self.sem_tokens: list[int] = self.gen_sem_tokens()
59
50
 
60
51
  @property
@@ -62,57 +53,6 @@ class ModuleInfo:
62
53
  """Return uri."""
63
54
  return uris.from_fs_path(self.ir.loc.mod_path)
64
55
 
65
- def update_with(
66
- self,
67
- build: Pass,
68
- alev: ALev,
69
- refresh: bool = False,
70
- mod_override: Optional[ast.Module] = None,
71
- ) -> None:
72
- """Update module info."""
73
- target_mod = mod_override if mod_override else build.ir
74
- if not isinstance(target_mod, ast.Module):
75
- return
76
- self.ir = target_mod # if alev > ALev.QUICK else self.ir
77
- if refresh:
78
- self.errors = build.errors_had
79
- self.warnings = build.warnings_had
80
- else:
81
- self.errors += [
82
- i
83
- for i in build.errors_had
84
- if i not in self.errors
85
- if i.loc.mod_path == target_mod.loc.mod_path
86
- ]
87
- self.warnings += [
88
- i
89
- for i in build.warnings_had
90
- if i not in self.warnings
91
- if i.loc.mod_path == target_mod.loc.mod_path
92
- ]
93
- self.alev = alev
94
- self.diagnostics = self.gen_diagnostics()
95
- if self.alev == ALev.TYPE:
96
- self.sem_tokens = self.gen_sem_tokens()
97
-
98
- def gen_diagnostics(self) -> list[lspt.Diagnostic]:
99
- """Return diagnostics."""
100
- return [
101
- lspt.Diagnostic(
102
- range=create_range(error.loc),
103
- message=error.msg,
104
- severity=lspt.DiagnosticSeverity.Error,
105
- )
106
- for error in self.errors
107
- ] + [
108
- lspt.Diagnostic(
109
- range=create_range(warning.loc),
110
- message=warning.msg,
111
- severity=lspt.DiagnosticSeverity.Warning,
112
- )
113
- for warning in self.warnings
114
- ]
115
-
116
56
  def gen_sem_tokens(self) -> list[int]:
117
57
  """Return semantic tokens."""
118
58
  tokens = []
@@ -134,6 +74,76 @@ class ModuleInfo:
134
74
  prev_line, prev_col = line, col_start
135
75
  return tokens
136
76
 
77
+ def update_sem_tokens(
78
+ self, content_changes: lspt.DidChangeTextDocumentParams
79
+ ) -> list[int]:
80
+ """Update semantic tokens on change."""
81
+ for change in [
82
+ x
83
+ for x in content_changes.content_changes
84
+ if isinstance(x, lspt.TextDocumentContentChangeEvent_Type1)
85
+ ]:
86
+ change_start_line = change.range.start.line
87
+ change_start_char = change.range.start.character
88
+ change_end_line = change.range.end.line
89
+ change_end_char = change.range.end.character
90
+
91
+ line_delta = change.text.count("\n") - (change_end_line - change_start_line)
92
+ if line_delta == 0:
93
+ char_delta = len(change.text) - (change_end_char - change_start_char)
94
+ else:
95
+ last_newline_index = change.text.rfind("\n")
96
+ char_delta = (
97
+ len(change.text)
98
+ - last_newline_index
99
+ - 1
100
+ - change_end_char
101
+ + change_start_char
102
+ )
103
+
104
+ changed_token_index = locate_affected_token(
105
+ self.sem_tokens,
106
+ change_start_line,
107
+ change_start_char,
108
+ change_end_line,
109
+ change_end_char,
110
+ )
111
+ if changed_token_index:
112
+ self.sem_tokens[changed_token_index + 2] = max(
113
+ 1, self.sem_tokens[changed_token_index + 2] + char_delta
114
+ )
115
+ if (
116
+ len(self.sem_tokens) > changed_token_index + 5
117
+ and self.sem_tokens[changed_token_index + 5] == 0
118
+ ):
119
+ next_token_index = changed_token_index + 5
120
+ self.sem_tokens[next_token_index + 1] = max(
121
+ 0, self.sem_tokens[next_token_index + 1] + char_delta
122
+ )
123
+ return self.sem_tokens
124
+
125
+ current_token_index = 0
126
+ line_offset = 0
127
+ while current_token_index < len(self.sem_tokens):
128
+ token_line_number = self.sem_tokens[current_token_index] + line_offset
129
+ token_start_pos = self.sem_tokens[current_token_index + 1]
130
+
131
+ if token_line_number > change_start_line or (
132
+ token_line_number == change_start_line
133
+ and token_start_pos >= change_start_char
134
+ ):
135
+ self.sem_tokens[current_token_index] += line_delta
136
+ if token_line_number == change_start_line:
137
+ self.sem_tokens[current_token_index + 1] += char_delta
138
+ if token_line_number > change_end_line or (
139
+ token_line_number == change_end_line
140
+ and token_start_pos >= change_end_char
141
+ ):
142
+ break
143
+ line_offset += self.sem_tokens[current_token_index]
144
+ current_token_index += 5
145
+ return self.sem_tokens
146
+
137
147
 
138
148
  class JacLangServer(LanguageServer):
139
149
  """Class for managing workspace."""
@@ -143,65 +153,35 @@ class JacLangServer(LanguageServer):
143
153
  super().__init__("jac-lsp", "v0.1")
144
154
  self.modules: dict[str, ModuleInfo] = {}
145
155
  self.executor = ThreadPoolExecutor()
146
-
147
- async def push_diagnostics(self, file_path: str) -> None:
148
- """Push diagnostics for a file."""
149
- if file_path in self.modules:
150
- self.publish_diagnostics(
151
- file_path,
152
- self.modules[file_path].diagnostics,
153
- )
154
-
155
- def unwind_to_parent(self, file_path: str) -> str:
156
- """Unwind to parent."""
157
- orig_file_path = file_path
158
- if file_path in self.modules:
159
- while cur := self.modules[file_path].parent:
160
- file_path = cur.uri
161
- if file_path == orig_file_path and (
162
- discover := self.modules[file_path].ir.annexable_by
163
- ):
164
- file_path = uris.from_fs_path(discover)
165
- self.quick_check(file_path)
166
- return file_path
156
+ self.tasks: dict[str, asyncio.Task] = {}
167
157
 
168
158
  def update_modules(
169
- self, file_path: str, build: Pass, alev: ALev, refresh: bool = False
159
+ self, file_path: str, build: Pass, refresh: bool = False
170
160
  ) -> None:
171
161
  """Update modules."""
172
162
  if not isinstance(build.ir, ast.Module):
173
163
  self.log_error("Error with module build.")
174
164
  return
175
- if file_path in self.modules:
176
- self.modules[file_path].update_with(build, alev, refresh=refresh)
177
- else:
178
- self.modules[file_path] = ModuleInfo(
179
- ir=build.ir,
180
- errors=[
181
- i
182
- for i in build.errors_had
183
- if i.loc.mod_path == uris.to_fs_path(file_path)
184
- ],
185
- warnings=[
186
- i
187
- for i in build.warnings_had
188
- if i.loc.mod_path == uris.to_fs_path(file_path)
189
- ],
190
- alev=alev,
191
- )
165
+ self.modules[file_path] = ModuleInfo(
166
+ ir=build.ir,
167
+ errors=[
168
+ i
169
+ for i in build.errors_had
170
+ if i.loc.mod_path == uris.to_fs_path(file_path)
171
+ ],
172
+ warnings=[
173
+ i
174
+ for i in build.warnings_had
175
+ if i.loc.mod_path == uris.to_fs_path(file_path)
176
+ ],
177
+ )
192
178
  for p in build.ir.mod_deps.keys():
193
179
  uri = uris.from_fs_path(p)
194
- if not refresh and uri in self.modules:
195
- self.modules[uri].update_with(
196
- build, alev, mod_override=build.ir.mod_deps[p], refresh=refresh
197
- )
198
- else:
199
- self.modules[uri] = ModuleInfo(
200
- ir=build.ir.mod_deps[p],
201
- errors=[i for i in build.errors_had if i.loc.mod_path == p],
202
- warnings=[i for i in build.warnings_had if i.loc.mod_path == p],
203
- alev=alev,
204
- )
180
+ self.modules[uri] = ModuleInfo(
181
+ ir=build.ir.mod_deps[p],
182
+ errors=[i for i in build.errors_had if i.loc.mod_path == p],
183
+ warnings=[i for i in build.warnings_had if i.loc.mod_path == p],
184
+ )
205
185
  self.modules[uri].parent = (
206
186
  self.modules[file_path] if file_path != uri else None
207
187
  )
@@ -213,72 +193,100 @@ class JacLangServer(LanguageServer):
213
193
  build = jac_str_to_pass(
214
194
  jac_str=document.source, file_path=document.path, schedule=[]
215
195
  )
196
+ self.publish_diagnostics(
197
+ file_path,
198
+ gen_diagnostics(file_path, build.errors_had, build.warnings_had),
199
+ )
200
+ return len(build.errors_had) == 0
216
201
  except Exception as e:
217
202
  self.log_error(f"Error during syntax check: {e}")
218
203
  return False
219
- self.update_modules(file_path, build, ALev.QUICK, refresh=True)
220
- return len(self.modules[file_path].errors) == 0
221
-
222
- def deep_check(self, file_path: str) -> bool:
223
- """Rebuild a file and its dependencies."""
224
- if file_path not in self.modules:
225
- self.quick_check(file_path)
226
- try:
227
- file_path = self.unwind_to_parent(file_path)
228
- build = jac_ir_to_pass(ir=self.modules[file_path].ir)
229
- except Exception as e:
230
- self.log_error(f"Error during syntax check: {e}")
231
- return False
232
- self.update_modules(file_path, build, ALev.DEEP)
233
- return len(self.modules[file_path].errors) == 0
234
204
 
235
- def type_check(self, file_path: str) -> bool:
205
+ def deep_check(self, file_path: str, annex_view: Optional[str] = None) -> bool:
236
206
  """Rebuild a file and its dependencies."""
237
- if file_path not in self.modules:
238
- self.deep_check(file_path)
239
207
  try:
240
- file_path = self.unwind_to_parent(file_path)
241
- build = jac_ir_to_pass(
242
- ir=self.modules[file_path].ir, schedule=type_checker_sched
208
+ document = self.workspace.get_text_document(file_path)
209
+ build = jac_str_to_pass(
210
+ jac_str=document.source,
211
+ file_path=document.path,
212
+ schedule=py_code_gen_typed,
213
+ )
214
+ self.update_modules(file_path, build)
215
+ if discover := self.modules[file_path].ir.annexable_by:
216
+ return self.deep_check(
217
+ uris.from_fs_path(discover), annex_view=file_path
218
+ )
219
+ self.publish_diagnostics(
220
+ file_path,
221
+ gen_diagnostics(
222
+ annex_view if annex_view else file_path,
223
+ build.errors_had,
224
+ build.warnings_had,
225
+ ),
243
226
  )
227
+ return len(build.errors_had) == 0
244
228
  except Exception as e:
245
- self.log_error(f"Error during type check: {e}")
229
+ self.log_error(f"Error during deep check: {e}")
246
230
  return False
247
- self.update_modules(file_path, build, ALev.TYPE)
248
- return len(self.modules[file_path].errors) == 0
249
231
 
250
- async def analyze_and_publish(self, uri: str, level: int = 2) -> None:
232
+ async def launch_quick_check(self, uri: str) -> None:
251
233
  """Analyze and publish diagnostics."""
252
- self.log_py(f"Analyzing {uri}...")
253
- success = await asyncio.get_event_loop().run_in_executor(
234
+ await asyncio.get_event_loop().run_in_executor(
254
235
  self.executor, self.quick_check, uri
255
236
  )
256
- await self.push_diagnostics(uri)
257
- if success and level > 0:
258
- success = await asyncio.get_event_loop().run_in_executor(
259
- self.executor, self.deep_check, uri
260
- )
261
- await self.push_diagnostics(uri)
262
- if level > 1:
263
- await asyncio.get_event_loop().run_in_executor(
264
- self.executor, self.type_check, uri
265
- )
266
- await self.push_diagnostics(uri)
237
+
238
+ async def launch_deep_check(self, uri: str) -> None:
239
+ """Analyze and publish diagnostics."""
240
+
241
+ async def run_in_executor(
242
+ func: Callable[[str, Optional[str]], bool],
243
+ file_path: str,
244
+ annex_view: Optional[str] = None,
245
+ ) -> None:
246
+ loop = asyncio.get_event_loop()
247
+ await loop.run_in_executor(self.executor, func, file_path, annex_view)
248
+
249
+ if uri in self.tasks and not self.tasks[uri].done():
250
+ self.log_py(f"Canceling {uri} deep check...")
251
+ self.tasks[uri].cancel()
252
+ del self.tasks[uri]
253
+ self.log_py(f"Analyzing {uri}...")
254
+ task = asyncio.create_task(run_in_executor(self.deep_check, uri))
255
+ self.tasks[uri] = task
256
+ await task
267
257
 
268
258
  def get_completion(
269
- self, file_path: str, position: lspt.Position
259
+ self, file_path: str, position: lspt.Position, completion_trigger: Optional[str]
270
260
  ) -> lspt.CompletionList:
271
261
  """Return completion for a file."""
272
- items = []
262
+ completion_items = []
273
263
  document = self.workspace.get_text_document(file_path)
274
- current_line = document.lines[position.line].strip()
275
- if current_line.endswith("hello."):
264
+ current_line = document.lines[position.line]
265
+ current_pos = position.character
266
+ current_symbol_path = parse_symbol_path(current_line, current_pos)
267
+ node_selected = find_deepest_symbol_node_at_pos(
268
+ self.modules[file_path].ir,
269
+ position.line,
270
+ position.character - 2,
271
+ )
276
272
 
277
- items = [
278
- lspt.CompletionItem(label="world"),
279
- lspt.CompletionItem(label="friend"),
280
- ]
281
- return lspt.CompletionList(is_incomplete=False, items=items)
273
+ mod_tab = (
274
+ self.modules[file_path].ir.sym_tab
275
+ if not node_selected
276
+ else node_selected.sym_tab
277
+ )
278
+ current_tab = self.modules[file_path].ir._sym_tab
279
+ current_symbol_table = mod_tab
280
+ if completion_trigger == ".":
281
+ completion_items = resolve_completion_symbol_table(
282
+ mod_tab, current_symbol_path, current_tab
283
+ )
284
+ else:
285
+ try: # noqa SIM105
286
+ completion_items = collect_all_symbols_in_scope(current_symbol_table)
287
+ except AttributeError:
288
+ pass
289
+ return lspt.CompletionList(is_incomplete=False, items=completion_items)
282
290
 
283
291
  def rename_module(self, old_path: str, new_path: str) -> None:
284
292
  """Rename module."""
@@ -325,6 +333,8 @@ class JacLangServer(LanguageServer):
325
333
  self, file_path: str, position: lspt.Position
326
334
  ) -> Optional[lspt.Hover]:
327
335
  """Return hover information for a file."""
336
+ if file_path not in self.modules:
337
+ return None
328
338
  node_selected = find_deepest_symbol_node_at_pos(
329
339
  self.modules[file_path].ir, position.line, position.character
330
340
  )
@@ -371,6 +381,8 @@ class JacLangServer(LanguageServer):
371
381
  self, file_path: str, position: lspt.Position
372
382
  ) -> Optional[lspt.Location]:
373
383
  """Return definition location for a file."""
384
+ if file_path not in self.modules:
385
+ return None
374
386
  node_selected: Optional[ast.AstSymbolNode] = find_deepest_symbol_node_at_pos(
375
387
  self.modules[file_path].ir, position.line, position.character
376
388
  )
@@ -9,7 +9,6 @@ from jaclang.compiler.constant import (
9
9
  JacSemTokenType as SemTokType,
10
10
  )
11
11
  from jaclang.langserve.engine import JacLangServer
12
- from jaclang.langserve.utils import debounce
13
12
 
14
13
  import lsprotocol.types as lspt
15
14
 
@@ -20,18 +19,19 @@ server = JacLangServer()
20
19
  @server.feature(lspt.TEXT_DOCUMENT_DID_SAVE)
21
20
  async def did_open(ls: JacLangServer, params: lspt.DidOpenTextDocumentParams) -> None:
22
21
  """Check syntax on change."""
23
- await ls.analyze_and_publish(params.text_document.uri)
22
+ await ls.launch_deep_check(params.text_document.uri)
24
23
  ls.lsp.send_request(lspt.WORKSPACE_SEMANTIC_TOKENS_REFRESH)
25
24
 
26
25
 
27
26
  @server.feature(lspt.TEXT_DOCUMENT_DID_CHANGE)
28
- @debounce(0.3)
29
27
  async def did_change(
30
28
  ls: JacLangServer, params: lspt.DidChangeTextDocumentParams
31
29
  ) -> None:
32
30
  """Check syntax on change."""
33
- await ls.analyze_and_publish(params.text_document.uri, level=1)
34
- ls.lsp.send_request(lspt.WORKSPACE_SEMANTIC_TOKENS_REFRESH)
31
+ await ls.launch_quick_check(file_path := params.text_document.uri)
32
+ if file_path in ls.modules:
33
+ ls.modules[file_path].update_sem_tokens(params)
34
+ ls.lsp.send_request(lspt.WORKSPACE_SEMANTIC_TOKENS_REFRESH)
35
35
 
36
36
 
37
37
  @server.feature(lspt.TEXT_DOCUMENT_FORMATTING)
@@ -86,11 +86,15 @@ def did_delete_files(ls: JacLangServer, params: lspt.DeleteFilesParams) -> None:
86
86
 
87
87
  @server.feature(
88
88
  lspt.TEXT_DOCUMENT_COMPLETION,
89
- lspt.CompletionOptions(trigger_characters=[".", ":", ""]),
89
+ lspt.CompletionOptions(trigger_characters=[".", ":", "a-zA-Z0-9"]),
90
90
  )
91
91
  def completion(ls: JacLangServer, params: lspt.CompletionParams) -> lspt.CompletionList:
92
92
  """Provide completion."""
93
- return ls.get_completion(params.text_document.uri, params.position)
93
+ return ls.get_completion(
94
+ params.text_document.uri,
95
+ params.position,
96
+ params.context.trigger_character if params.context else None,
97
+ )
94
98
 
95
99
 
96
100
  @server.feature(lspt.TEXT_DOCUMENT_HOVER, lspt.HoverOptions(work_done_progress=True))
@@ -134,6 +138,14 @@ def semantic_tokens_full(
134
138
  ls: JacLangServer, params: lspt.SemanticTokensParams
135
139
  ) -> lspt.SemanticTokens:
136
140
  """Provide semantic tokens."""
141
+ # import logging
142
+
143
+ # logging.info("\nGetting semantic tokens\n")
144
+ # # logging.info(ls.get_semantic_tokens(params.text_document.uri))
145
+ # i = 0
146
+ # while i < len(ls.get_semantic_tokens(params.text_document.uri).data):
147
+ # logging.info(ls.get_semantic_tokens(params.text_document.uri).data[i : i + 5])
148
+ # i += 5
137
149
  return ls.get_semantic_tokens(params.text_document.uri)
138
150
 
139
151
 
@@ -21,8 +21,34 @@ with entry:__main__ {
21
21
 
22
22
  glob x: int = 10;
23
23
 
24
- enum Color {
24
+ enum Colorenum {
25
25
  RED,
26
26
  GREEN,
27
27
  BLUE
28
- }
28
+ }
29
+
30
+ obj Colour1 {
31
+ has color1: Colorenum,
32
+ point1: int;
33
+
34
+ can get_color1 -> Colorenum;
35
+ }
36
+
37
+ :obj:Colour1:can:get_color1 -> Colorenum {
38
+ return self.color;
39
+ }
40
+
41
+ obj red :Colour1: {
42
+ has base_colorred: Colorenum = Color.RED,
43
+ pointred: int = 10;
44
+ obj color2 {
45
+ has color22: Color = Colorenum.BLUE,
46
+ point22: int = 20;
47
+ }
48
+ }
49
+
50
+ with entry:__main__ {
51
+ r = red(color1=Color.GREEN, point1=20);
52
+ print(r.get_color1());
53
+ print(r.color2.color22);
54
+ }
@@ -1,6 +1,6 @@
1
1
  import:py os;
2
2
  import:py from math, sqrt as square_root;
3
3
  import:py datetime as dt;
4
- import:jac from base_module_structure, add as adsd, subtract,x,Color as clr;
4
+ import:jac from base_module_structure, add_numbers as adsd, subtract,x,Colorenum as clr;
5
5
  import:jac base_module_structure as base_module_structure;
6
6
  import:py from py_import,add1 as ss, sub1 as subtract1,apple,Orange1;