jaclang 0.7.13__py3-none-any.whl → 0.7.16__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 (96) hide show
  1. jaclang/cli/cli.py +15 -10
  2. jaclang/cli/cmdreg.py +9 -12
  3. jaclang/compiler/__init__.py +19 -53
  4. jaclang/compiler/absyntree.py +95 -17
  5. jaclang/compiler/jac.lark +4 -3
  6. jaclang/compiler/parser.py +35 -23
  7. jaclang/compiler/passes/ir_pass.py +4 -13
  8. jaclang/compiler/passes/main/access_modifier_pass.py +1 -1
  9. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +4 -5
  10. jaclang/compiler/passes/main/import_pass.py +19 -23
  11. jaclang/compiler/passes/main/pyast_gen_pass.py +308 -567
  12. jaclang/compiler/passes/main/pyast_load_pass.py +33 -6
  13. jaclang/compiler/passes/main/registry_pass.py +37 -3
  14. jaclang/compiler/passes/main/sym_tab_build_pass.py +1 -1
  15. jaclang/compiler/passes/main/tests/__init__.py +1 -1
  16. jaclang/compiler/passes/main/tests/test_import_pass.py +5 -1
  17. jaclang/compiler/passes/main/type_check_pass.py +7 -0
  18. jaclang/compiler/passes/tool/fuse_comments_pass.py +14 -2
  19. jaclang/compiler/passes/tool/jac_formatter_pass.py +144 -94
  20. jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +0 -1
  21. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/architype_test.jac +13 -0
  22. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/comment_alignment.jac +11 -0
  23. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/comments.jac +13 -0
  24. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/decorator_stack.jac +37 -0
  25. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/esc_keywords.jac +5 -0
  26. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/long_names.jac +19 -0
  27. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/triple_quoted_string.jac +6 -0
  28. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +11 -0
  29. jaclang/compiler/passes/tool/tests/test_unparse_validate.py +33 -39
  30. jaclang/compiler/passes/transform.py +4 -0
  31. jaclang/compiler/semtable.py +31 -7
  32. jaclang/compiler/tests/test_importer.py +12 -5
  33. jaclang/langserve/engine.py +82 -143
  34. jaclang/langserve/sem_manager.py +379 -0
  35. jaclang/langserve/server.py +8 -10
  36. jaclang/langserve/tests/fixtures/base_module_structure.jac +27 -6
  37. jaclang/langserve/tests/fixtures/circle.jac +3 -3
  38. jaclang/langserve/tests/fixtures/circle_err.jac +3 -3
  39. jaclang/langserve/tests/fixtures/circle_pure.test.jac +3 -3
  40. jaclang/langserve/tests/fixtures/import_include_statements.jac +1 -1
  41. jaclang/langserve/tests/test_sem_tokens.py +277 -0
  42. jaclang/langserve/tests/test_server.py +96 -16
  43. jaclang/langserve/utils.py +163 -96
  44. jaclang/plugin/builtin.py +1 -1
  45. jaclang/plugin/default.py +214 -24
  46. jaclang/plugin/feature.py +59 -11
  47. jaclang/plugin/spec.py +58 -6
  48. jaclang/{core → runtimelib}/architype.py +1 -1
  49. jaclang/{core → runtimelib}/context.py +8 -1
  50. jaclang/runtimelib/importer.py +361 -0
  51. jaclang/runtimelib/machine.py +94 -0
  52. jaclang/{core → runtimelib}/utils.py +13 -5
  53. jaclang/settings.py +4 -1
  54. jaclang/tests/fixtures/abc.jac +3 -3
  55. jaclang/tests/fixtures/blankwithentry.jac +3 -0
  56. jaclang/tests/fixtures/byllmissue.jac +1 -5
  57. jaclang/tests/fixtures/chandra_bugs2.jac +11 -10
  58. jaclang/tests/fixtures/cls_method.jac +41 -0
  59. jaclang/tests/fixtures/dblhello.jac +6 -0
  60. jaclang/tests/fixtures/deep/one_lev.jac +3 -3
  61. jaclang/tests/fixtures/deep/one_lev_dup.jac +2 -3
  62. jaclang/tests/fixtures/deep_import_mods.jac +13 -0
  63. jaclang/tests/fixtures/err.impl.jac +3 -0
  64. jaclang/tests/fixtures/err.jac +4 -2
  65. jaclang/tests/fixtures/err.test.jac +3 -0
  66. jaclang/tests/fixtures/err_runtime.jac +15 -0
  67. jaclang/tests/fixtures/game1.jac +1 -1
  68. jaclang/tests/fixtures/hello.jac +4 -0
  69. jaclang/tests/fixtures/impl_grab.impl.jac +2 -1
  70. jaclang/tests/fixtures/impl_grab.jac +4 -1
  71. jaclang/tests/fixtures/jp_importer_auto.jac +14 -0
  72. jaclang/tests/fixtures/maxfail_run_test.jac +4 -4
  73. jaclang/tests/fixtures/needs_import.jac +2 -2
  74. jaclang/tests/fixtures/pyfunc_2.py +3 -0
  75. jaclang/tests/fixtures/registry.jac +9 -0
  76. jaclang/tests/fixtures/run_test.jac +4 -4
  77. jaclang/tests/fixtures/semstr.jac +1 -4
  78. jaclang/tests/fixtures/simple_archs.jac +1 -1
  79. jaclang/tests/test_cli.py +65 -2
  80. jaclang/tests/test_language.py +83 -7
  81. jaclang/tests/test_man_code.py +17 -0
  82. jaclang/tests/test_reference.py +6 -0
  83. jaclang/utils/helpers.py +45 -21
  84. jaclang/utils/test.py +9 -0
  85. jaclang/utils/treeprinter.py +0 -4
  86. {jaclang-0.7.13.dist-info → jaclang-0.7.16.dist-info}/METADATA +3 -2
  87. {jaclang-0.7.13.dist-info → jaclang-0.7.16.dist-info}/RECORD +93 -77
  88. jaclang/core/importer.py +0 -344
  89. jaclang/tests/fixtures/aott_raise.jac +0 -25
  90. jaclang/tests/fixtures/package_import.jac +0 -6
  91. /jaclang/{core → runtimelib}/__init__.py +0 -0
  92. /jaclang/{core → runtimelib}/constructs.py +0 -0
  93. /jaclang/{core → runtimelib}/memory.py +0 -0
  94. /jaclang/{core → runtimelib}/test.py +0 -0
  95. {jaclang-0.7.13.dist-info → jaclang-0.7.16.dist-info}/WHEEL +0 -0
  96. {jaclang-0.7.13.dist-info → jaclang-0.7.16.dist-info}/entry_points.txt +0 -0
@@ -14,7 +14,6 @@ from jaclang.compiler.codeloc import CodeLocInfo
14
14
  from jaclang.compiler.constant import SymbolType
15
15
  from jaclang.compiler.passes.transform import Alert
16
16
  from jaclang.compiler.symtable import Symbol, SymbolTable
17
- from jaclang.utils.helpers import import_target_to_relative_path
18
17
  from jaclang.vendor.pygls import uris
19
18
 
20
19
  import lsprotocol.types as lspt
@@ -130,12 +129,25 @@ def position_within_node(node: ast.AstNode, line: int, character: int) -> bool:
130
129
  return False
131
130
 
132
131
 
133
- def collect_symbols(node: SymbolTable) -> list[lspt.DocumentSymbol]:
132
+ def find_index(
133
+ sem_tokens: list[int],
134
+ line: int,
135
+ char: int,
136
+ ) -> Optional[int]:
137
+ """Find index."""
138
+ index = None
139
+ for i, j in enumerate(
140
+ [get_token_start(i, sem_tokens) for i in range(0, len(sem_tokens), 5)]
141
+ ):
142
+ if j[0] == line and j[1] <= char <= j[2]:
143
+ return i
144
+
145
+ return index
146
+
147
+
148
+ def get_symbols_for_outline(node: SymbolTable) -> list[lspt.DocumentSymbol]:
134
149
  """Recursively collect symbols from the AST."""
135
150
  symbols = []
136
- if node is None:
137
- return symbols
138
-
139
151
  for key, item in node.tab.items():
140
152
  if (
141
153
  key in dir(builtins)
@@ -143,23 +155,20 @@ def collect_symbols(node: SymbolTable) -> list[lspt.DocumentSymbol]:
143
155
  or item.decl.loc.mod_path != node.owner.loc.mod_path
144
156
  ):
145
157
  continue
146
- else:
147
-
148
- pos = create_range(item.decl.loc)
149
- symbol = lspt.DocumentSymbol(
150
- name=key,
151
- kind=kind_map(item.decl),
152
- range=pos,
153
- selection_range=pos,
154
- children=[],
155
- )
156
- symbols.append(symbol)
158
+ pos = create_range(item.decl.loc)
159
+ symbol = lspt.DocumentSymbol(
160
+ name=key,
161
+ kind=kind_map(item.decl),
162
+ range=pos,
163
+ selection_range=pos,
164
+ children=[],
165
+ )
166
+ symbols.append(symbol)
157
167
 
158
168
  for sub_tab in [
159
169
  i for i in node.kid if i.owner.loc.mod_path == node.owner.loc.mod_path
160
170
  ]:
161
- sub_symbols = collect_symbols(sub_tab)
162
-
171
+ sub_symbols = get_symbols_for_outline(sub_tab)
163
172
  if isinstance(
164
173
  sub_tab.owner,
165
174
  (ast.IfStmt, ast.ElseStmt, ast.WhileStmt, ast.IterForStmt, ast.InForStmt),
@@ -267,16 +276,15 @@ def label_map(sub_tab: SymbolType) -> lspt.CompletionItemKind:
267
276
  def get_mod_path(mod_path: ast.ModulePath, name_node: ast.Name) -> str | None:
268
277
  """Get path for a module import name."""
269
278
  ret_target = None
270
- module_location_path = mod_path.loc.mod_path
271
279
  if mod_path.parent and (
272
280
  (
273
281
  isinstance(mod_path.parent.parent, ast.Import)
274
- and mod_path.parent.parent.hint.tag.value == "py"
282
+ and mod_path.parent.parent.is_py
275
283
  )
276
284
  or (
277
285
  isinstance(mod_path.parent, ast.Import)
278
286
  and mod_path.parent.from_loc
279
- and mod_path.parent.hint.tag.value == "py"
287
+ and mod_path.parent.is_py
280
288
  )
281
289
  ):
282
290
  if mod_path.path and name_node in mod_path.path:
@@ -287,38 +295,34 @@ def get_mod_path(mod_path: ast.ModulePath, name_node: ast.Name) -> str | None:
287
295
  )
288
296
  else:
289
297
  temporary_path_str = mod_path.path_str
290
- sys.path.append(os.path.dirname(module_location_path))
298
+ sys.path.append(os.path.dirname(mod_path.loc.mod_path))
291
299
  spec = importlib.util.find_spec(temporary_path_str)
292
- sys.path.remove(os.path.dirname(module_location_path))
300
+ sys.path.remove(os.path.dirname(mod_path.loc.mod_path))
293
301
  if spec and spec.origin and spec.origin.endswith(".py"):
294
302
  ret_target = spec.origin
295
303
  elif mod_path.parent and (
296
304
  (
297
305
  isinstance(mod_path.parent.parent, ast.Import)
298
- and mod_path.parent.parent.hint.tag.value == "jac"
306
+ and mod_path.parent.parent.is_jac
299
307
  )
300
308
  or (
301
309
  isinstance(mod_path.parent, ast.Import)
302
310
  and mod_path.parent.from_loc
303
- and mod_path.parent.hint.tag.value == "jac"
311
+ and mod_path.parent.is_jac
304
312
  )
305
313
  ):
306
- ret_target = import_target_to_relative_path(
307
- level=mod_path.level,
308
- target=mod_path.path_str,
309
- base_path=os.path.dirname(module_location_path),
310
- )
314
+ ret_target = mod_path.resolve_relative_path()
311
315
  return ret_target
312
316
 
313
317
 
314
318
  def get_item_path(mod_item: ast.ModuleItem) -> tuple[str, tuple[int, int]] | None:
315
319
  """Get path."""
316
320
  item_name = mod_item.name.value
317
- if mod_item.from_parent.hint.tag.value == "py" and mod_item.from_parent.from_loc:
321
+ if mod_item.from_parent.is_py and mod_item.from_parent.from_loc:
318
322
  path = get_mod_path(mod_item.from_parent.from_loc, mod_item.name)
319
323
  if path:
320
324
  return get_definition_range(path, item_name)
321
- elif mod_item.from_parent.hint.tag.value == "jac":
325
+ elif mod_item.from_parent.is_jac:
322
326
  mod_node = mod_item.from_mod_path
323
327
  if mod_node.sub_module and mod_node.sub_module._sym_tab:
324
328
  for symbol_name, symbol in mod_node.sub_module._sym_tab.tab.items():
@@ -364,50 +368,6 @@ def get_definition_range(
364
368
  return None
365
369
 
366
370
 
367
- def locate_affected_token(
368
- tokens: list[int],
369
- change_start_line: int,
370
- change_start_char: int,
371
- change_end_line: int,
372
- change_end_char: int,
373
- ) -> Optional[int]:
374
- """Find in which token change is occurring."""
375
- token_index = 0
376
- current_line = 0
377
- line_char_offset = 0
378
-
379
- while token_index < len(tokens):
380
- token_line_delta = tokens[token_index]
381
- token_start_char = tokens[token_index + 1]
382
- token_length = tokens[token_index + 2]
383
-
384
- if token_line_delta > 0:
385
- current_line += token_line_delta
386
- line_char_offset = 0
387
- token_abs_start_char = line_char_offset + token_start_char
388
- token_abs_end_char = token_abs_start_char + token_length
389
- if (
390
- current_line == change_start_line == change_end_line
391
- and token_abs_start_char <= change_start_char
392
- and change_end_char <= token_abs_end_char
393
- ):
394
- return token_index
395
- if (
396
- current_line == change_start_line
397
- and token_abs_start_char <= change_start_char < token_abs_end_char
398
- ):
399
- return token_index
400
- if (
401
- current_line == change_end_line
402
- and token_abs_start_char < change_end_char <= token_abs_end_char
403
- ):
404
- return token_index
405
-
406
- line_char_offset += token_start_char
407
- token_index += 5
408
- return None
409
-
410
-
411
371
  def collect_all_symbols_in_scope(
412
372
  sym_tab: SymbolTable, up_tree: bool = True
413
373
  ) -> list[lspt.CompletionItem]:
@@ -419,7 +379,7 @@ def collect_all_symbols_in_scope(
419
379
  while current_tab is not None and current_tab not in visited:
420
380
  visited.add(current_tab)
421
381
  for name, symbol in current_tab.tab.items():
422
- if name not in dir(builtins):
382
+ if name not in dir(builtins) and symbol.sym_type != SymbolType.IMPL:
423
383
  symbols.append(
424
384
  lspt.CompletionItem(label=name, kind=label_map(symbol.sym_type))
425
385
  )
@@ -431,26 +391,32 @@ def collect_all_symbols_in_scope(
431
391
 
432
392
  def parse_symbol_path(text: str, dot_position: int) -> list[str]:
433
393
  """Parse text and return a list of symbols."""
434
- text = text[:dot_position].strip()
435
- pattern = re.compile(r"\b\w+\(\)?|\b\w+\b")
436
- matches = pattern.findall(text)
437
- if text.endswith("."):
438
- matches.append("")
439
- symbol_path = []
440
- i = 0
441
- while i < len(matches):
442
- if matches[i].endswith("("):
443
- i += 1
444
- continue
445
- elif "(" in matches[i]:
446
- symbol_path.append(matches[i])
447
- elif matches[i] == "":
448
- pass
394
+ text = text[:dot_position][:-1].strip()
395
+ valid_character_pattern = re.compile(r"[a-zA-Z0-9_]")
396
+
397
+ reversed_text = text[::-1]
398
+ all_words = []
399
+ current_word = []
400
+ for char in reversed_text:
401
+ if valid_character_pattern.fullmatch(char):
402
+ current_word.append(char)
403
+ elif char == ".":
404
+ if current_word:
405
+ all_words.append("".join(current_word[::-1]))
406
+ current_word = []
449
407
  else:
450
- symbol_path.append(matches[i])
451
- i += 1
408
+ if current_word:
409
+ all_words.append("".join(current_word[::-1]))
410
+ current_word = []
411
+ break
412
+
413
+ all_words = (
414
+ all_words[::-1]
415
+ if not current_word
416
+ else ["".join(current_word[::-1])] + all_words[::-1]
417
+ )
452
418
 
453
- return symbol_path
419
+ return all_words
454
420
 
455
421
 
456
422
  def resolve_symbol_path(sym_name: str, node_tab: SymbolTable) -> str:
@@ -463,6 +429,11 @@ def resolve_symbol_path(sym_name: str, node_tab: SymbolTable) -> str:
463
429
  for name, symbol in current_tab.tab.items():
464
430
  if name not in dir(builtins) and name == sym_name:
465
431
  path = symbol.defn[0]._sym_type
432
+ if symbol.sym_type == SymbolType.ENUM_ARCH:
433
+ if isinstance(current_tab.owner, ast.Module):
434
+ return current_tab.owner.name + "." + sym_name
435
+ elif isinstance(current_tab.owner, ast.AstSymbolNode):
436
+ return current_tab.owner.name_spec._sym_type + "." + sym_name
466
437
  return path
467
438
  current_tab = current_tab.parent if current_tab.parent != current_tab else None
468
439
  return ""
@@ -553,8 +524,104 @@ def resolve_completion_symbol_table(
553
524
  )
554
525
  else:
555
526
  completion_items = []
556
-
527
+ if isinstance(current_symbol_table.owner, (ast.Ability, ast.AbilityDef)):
528
+ return completion_items
557
529
  completion_items.extend(
558
530
  collect_all_symbols_in_scope(current_symbol_table, up_tree=False)
559
531
  )
560
532
  return completion_items
533
+
534
+
535
+ def get_token_start(
536
+ token_index: int | None, sem_tokens: list[int]
537
+ ) -> tuple[int, int, int]:
538
+ """Return the starting position of a token."""
539
+ if token_index is None or token_index >= len(sem_tokens):
540
+ return 0, 0, 0
541
+
542
+ current_line = 0
543
+ current_char = 0
544
+ current_tok_index = 0
545
+
546
+ while current_tok_index < len(sem_tokens):
547
+ token_line_delta = sem_tokens[current_tok_index]
548
+ token_start_char = sem_tokens[current_tok_index + 1]
549
+
550
+ if token_line_delta > 0:
551
+ current_line += token_line_delta
552
+ current_char = 0
553
+ if current_tok_index == token_index:
554
+ if token_line_delta > 0:
555
+ return (
556
+ current_line,
557
+ token_start_char,
558
+ token_start_char + sem_tokens[current_tok_index + 2],
559
+ )
560
+ return (
561
+ current_line,
562
+ current_char + token_start_char,
563
+ current_char + token_start_char + sem_tokens[current_tok_index + 2],
564
+ )
565
+
566
+ current_char += token_start_char
567
+ current_tok_index += 5
568
+
569
+ return (
570
+ current_line,
571
+ current_char,
572
+ current_char + sem_tokens[current_tok_index + 2],
573
+ )
574
+
575
+
576
+ def find_surrounding_tokens(
577
+ change_start_line: int,
578
+ change_start_char: int,
579
+ change_end_line: int,
580
+ change_end_char: int,
581
+ sem_tokens: list[int],
582
+ ) -> tuple[int | None, int | None, bool]:
583
+ """Find the indices of the previous and next tokens surrounding the change."""
584
+ prev_token_index = None
585
+ next_token_index = None
586
+ inside_tok = False
587
+ for i, tok in enumerate(
588
+ [get_token_start(i, sem_tokens) for i in range(0, len(sem_tokens), 5)][0:]
589
+ ):
590
+ if (not (prev_token_index is None or next_token_index is None)) and (
591
+ tok[0] > change_end_line
592
+ or (tok[0] == change_end_line and tok[1] > change_end_char)
593
+ ):
594
+ prev_token_index = i * 5
595
+ break
596
+ elif (
597
+ change_start_line == tok[0] == change_end_line
598
+ and tok[1] <= change_start_char
599
+ and tok[2] >= change_end_char
600
+ ):
601
+ prev_token_index = i * 5
602
+ inside_tok = True
603
+ break
604
+ elif (tok[0] < change_start_line) or (
605
+ tok[0] == change_start_line and tok[1] < change_start_char
606
+ ):
607
+ prev_token_index = i * 5
608
+ elif (tok[0] > change_end_line) or (
609
+ tok[0] == change_end_line and tok[1] > change_end_char
610
+ ):
611
+ next_token_index = i * 5
612
+ break
613
+
614
+ return prev_token_index, next_token_index, inside_tok
615
+
616
+
617
+ def get_line_of_code(line_number: int, lines: list[str]) -> Optional[tuple[str, int]]:
618
+ """Get the line of code, and the first non-space character index."""
619
+ if 0 <= line_number < len(lines):
620
+ line = lines[line_number].rstrip("\n")
621
+ first_non_space = len(line) - len(line.lstrip())
622
+ return line, (
623
+ first_non_space + 4
624
+ if line.strip().endswith(("(", "{", "["))
625
+ else first_non_space
626
+ )
627
+ return None
jaclang/plugin/builtin.py CHANGED
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from typing import Optional
9
- from jaclang.core.constructs import NodeArchitype
9
+ from jaclang.runtimelib.constructs import NodeArchitype
10
10
 
11
11
 
12
12
  def dotgen(
jaclang/plugin/default.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import ast as ast3
5
6
  import fnmatch
6
7
  import html
7
8
  import os
@@ -10,12 +11,13 @@ import types
10
11
  from collections import OrderedDict
11
12
  from dataclasses import field
12
13
  from functools import wraps
13
- from typing import Any, Callable, Optional, Type, Union
14
+ from typing import Any, Callable, Mapping, Optional, Sequence, Type, Union
14
15
 
15
- from jaclang.compiler.absyntree import Module
16
+ import jaclang.compiler.absyntree as ast
16
17
  from jaclang.compiler.constant import EdgeDir, colors
18
+ from jaclang.compiler.passes.main.pyast_gen_pass import PyastGenPass
17
19
  from jaclang.compiler.semtable import SemInfo, SemRegistry, SemScope
18
- from jaclang.core.constructs import (
20
+ from jaclang.runtimelib.constructs import (
19
21
  Architype,
20
22
  DSFunc,
21
23
  EdgeAnchor,
@@ -32,18 +34,20 @@ from jaclang.core.constructs import (
32
34
  WalkerArchitype,
33
35
  exec_context,
34
36
  )
35
- from jaclang.core.importer import jac_importer
36
- from jaclang.core.utils import traverse_graph
37
- from jaclang.plugin.feature import JacFeature as Jac
38
- from jaclang.plugin.spec import T
37
+ from jaclang.runtimelib.importer import ImportPathSpec, JacImporter, PythonImporter
38
+ from jaclang.runtimelib.utils import traverse_graph
39
+ from jaclang.plugin.feature import JacFeature as Jac # noqa: I100
40
+ from jaclang.plugin.spec import P, T
39
41
 
40
42
 
41
43
  import pluggy
42
44
 
45
+ hookimpl = pluggy.HookimplMarker("jac")
43
46
 
44
47
  __all__ = [
45
48
  "EdgeAnchor",
46
49
  "GenericEdge",
50
+ "hookimpl",
47
51
  "JacTestCheck",
48
52
  "NodeAnchor",
49
53
  "ObjectAnchor",
@@ -54,14 +58,10 @@ __all__ = [
54
58
  "WalkerArchitype",
55
59
  "Architype",
56
60
  "DSFunc",
57
- "jac_importer",
58
61
  "T",
59
62
  ]
60
63
 
61
64
 
62
- hookimpl = pluggy.HookimplMarker("jac")
63
-
64
-
65
65
  class JacFeatureDefaults:
66
66
  """Jac Feature."""
67
67
 
@@ -202,6 +202,43 @@ class JacFeatureDefaults:
202
202
 
203
203
  return decorator
204
204
 
205
+ @staticmethod
206
+ @hookimpl
207
+ def impl_patch_filename(
208
+ file_loc: str,
209
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]:
210
+ """Update impl file location."""
211
+
212
+ def decorator(func: Callable[P, T]) -> Callable[P, T]:
213
+ try:
214
+ code = func.__code__
215
+ new_code = types.CodeType(
216
+ code.co_argcount,
217
+ code.co_posonlyargcount,
218
+ code.co_kwonlyargcount,
219
+ code.co_nlocals,
220
+ code.co_stacksize,
221
+ code.co_flags,
222
+ code.co_code,
223
+ code.co_consts,
224
+ code.co_names,
225
+ code.co_varnames,
226
+ file_loc,
227
+ code.co_name,
228
+ code.co_qualname,
229
+ code.co_firstlineno,
230
+ code.co_linetable,
231
+ code.co_exceptiontable,
232
+ code.co_freevars,
233
+ code.co_cellvars,
234
+ )
235
+ func.__code__ = new_code
236
+ except AttributeError:
237
+ pass
238
+ return func
239
+
240
+ return decorator
241
+
205
242
  @staticmethod
206
243
  @hookimpl
207
244
  def jac_import(
@@ -211,23 +248,32 @@ class JacFeatureDefaults:
211
248
  cachable: bool,
212
249
  mdl_alias: Optional[str],
213
250
  override_name: Optional[str],
214
- mod_bundle: Optional[Module | str],
215
251
  lng: Optional[str],
216
252
  items: Optional[dict[str, Union[str, Optional[str]]]],
253
+ reload_module: Optional[bool],
217
254
  ) -> tuple[types.ModuleType, ...]:
218
255
  """Core Import Process."""
219
- result = jac_importer(
220
- target=target,
221
- base_path=base_path,
222
- absorb=absorb,
223
- cachable=cachable,
224
- mdl_alias=mdl_alias,
225
- override_name=override_name,
226
- mod_bundle=mod_bundle,
227
- lng=lng,
228
- items=items,
256
+ spec = ImportPathSpec(
257
+ target,
258
+ base_path,
259
+ absorb,
260
+ cachable,
261
+ mdl_alias,
262
+ override_name,
263
+ lng,
264
+ items,
265
+ )
266
+ if lng == "py":
267
+ import_result = PythonImporter(Jac.context().jac_machine).run_import(spec)
268
+ else:
269
+ import_result = JacImporter(Jac.context().jac_machine).run_import(
270
+ spec, reload_module
271
+ )
272
+ return (
273
+ (import_result.ret_mod,)
274
+ if absorb or not items
275
+ else tuple(import_result.ret_items)
229
276
  )
230
- return result
231
277
 
232
278
  @staticmethod
233
279
  @hookimpl
@@ -260,6 +306,8 @@ class JacFeatureDefaults:
260
306
  base, mod_name = os.path.split(filepath)
261
307
  base = base if base else "./"
262
308
  mod_name = mod_name[:-4]
309
+ if mod_name.endswith(".test"):
310
+ mod_name = mod_name[:-5]
263
311
  JacTestCheck.reset()
264
312
  Jac.jac_import(target=mod_name, base_path=base)
265
313
  JacTestCheck.run_test(xit, maxfail, verbose)
@@ -626,11 +674,153 @@ class JacFeatureDefaults:
626
674
  inputs: list[tuple[str, str, str, Any]],
627
675
  outputs: tuple,
628
676
  action: str,
677
+ _globals: dict,
678
+ _locals: Mapping,
629
679
  ) -> Any: # noqa: ANN401
630
680
  """Jac's with_llm feature."""
631
681
  raise ImportError(
632
- "mtllm is not installed. Please install it with `pip install mtllm`."
682
+ "mtllm is not installed. Please install it with `pip install mtllm` and run `jac clean`."
683
+ )
684
+
685
+ @staticmethod
686
+ @hookimpl
687
+ def gen_llm_body(_pass: PyastGenPass, node: ast.Ability) -> list[ast3.AST]:
688
+ """Generate the by LLM body."""
689
+ _pass.log_warning(
690
+ "MT-LLM is not installed. Please install it with `pip install mtllm`."
691
+ )
692
+ return [
693
+ _pass.sync(
694
+ ast3.Raise(
695
+ _pass.sync(
696
+ ast3.Call(
697
+ func=_pass.sync(
698
+ ast3.Name(id="ImportError", ctx=ast3.Load())
699
+ ),
700
+ args=[
701
+ _pass.sync(
702
+ ast3.Constant(
703
+ value="mtllm is not installed. Please install it with `pip install mtllm` and run `jac clean`." # noqa: E501
704
+ )
705
+ )
706
+ ],
707
+ keywords=[],
708
+ )
709
+ )
710
+ )
711
+ )
712
+ ]
713
+
714
+ @staticmethod
715
+ @hookimpl
716
+ def by_llm_call(
717
+ _pass: PyastGenPass,
718
+ model: ast3.AST,
719
+ model_params: dict[str, ast.Expr],
720
+ scope: ast3.AST,
721
+ inputs: Sequence[Optional[ast3.AST]],
722
+ outputs: Sequence[Optional[ast3.AST]] | ast3.Call,
723
+ action: Optional[ast3.AST],
724
+ include_info: list[tuple[str, ast3.AST]],
725
+ exclude_info: list[tuple[str, ast3.AST]],
726
+ ) -> ast3.Call:
727
+ """Return the LLM Call, e.g. _Jac.with_llm()."""
728
+ _pass.log_warning(
729
+ "MT-LLM is not installed. Please install it with `pip install mtllm`."
633
730
  )
731
+ return ast3.Call(
732
+ func=_pass.sync(
733
+ ast3.Attribute(
734
+ value=_pass.sync(ast3.Name(id="_Jac", ctx=ast3.Load())),
735
+ attr="with_llm",
736
+ ctx=ast3.Load(),
737
+ )
738
+ ),
739
+ args=[],
740
+ keywords=[
741
+ _pass.sync(
742
+ ast3.keyword(
743
+ arg="file_loc",
744
+ value=_pass.sync(ast3.Constant(value="None")),
745
+ )
746
+ ),
747
+ _pass.sync(
748
+ ast3.keyword(
749
+ arg="model",
750
+ value=_pass.sync(ast3.Constant(value="None")),
751
+ )
752
+ ),
753
+ _pass.sync(
754
+ ast3.keyword(
755
+ arg="model_params",
756
+ value=_pass.sync(ast3.Dict(keys=[], values=[])),
757
+ )
758
+ ),
759
+ _pass.sync(
760
+ ast3.keyword(
761
+ arg="scope",
762
+ value=_pass.sync(ast3.Constant(value="None")),
763
+ )
764
+ ),
765
+ _pass.sync(
766
+ ast3.keyword(
767
+ arg="incl_info",
768
+ value=_pass.sync(ast3.List(elts=[], ctx=ast3.Load())),
769
+ )
770
+ ),
771
+ _pass.sync(
772
+ ast3.keyword(
773
+ arg="excl_info",
774
+ value=_pass.sync(ast3.List(elts=[], ctx=ast3.Load())),
775
+ )
776
+ ),
777
+ _pass.sync(
778
+ ast3.keyword(
779
+ arg="inputs",
780
+ value=_pass.sync(ast3.List(elts=[], ctx=ast3.Load())),
781
+ )
782
+ ),
783
+ _pass.sync(
784
+ ast3.keyword(
785
+ arg="outputs",
786
+ value=_pass.sync(ast3.List(elts=[], ctx=ast3.Load())),
787
+ )
788
+ ),
789
+ _pass.sync(
790
+ ast3.keyword(
791
+ arg="action",
792
+ value=_pass.sync(ast3.Constant(value="None")),
793
+ )
794
+ ),
795
+ _pass.sync(
796
+ ast3.keyword(
797
+ arg="_globals",
798
+ value=_pass.sync(ast3.Constant(value="None")),
799
+ )
800
+ ),
801
+ _pass.sync(
802
+ ast3.keyword(
803
+ arg="_locals",
804
+ value=_pass.sync(ast3.Constant(value="None")),
805
+ )
806
+ ),
807
+ ],
808
+ )
809
+
810
+ @staticmethod
811
+ @hookimpl
812
+ def get_by_llm_call_args(_pass: PyastGenPass, node: ast.FuncCall) -> dict:
813
+ """Get the by LLM call args."""
814
+ return {
815
+ "model": None,
816
+ "model_params": {},
817
+ "scope": None,
818
+ "inputs": [],
819
+ "outputs": [],
820
+ "action": None,
821
+ "include_info": [],
822
+ "exclude_info": [],
823
+ }
634
824
 
635
825
 
636
826
  class JacBuiltin: