jaclang 0.7.27__py3-none-any.whl → 0.7.30__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 (56) hide show
  1. jaclang/cli/cli.py +3 -0
  2. jaclang/compiler/__init__.py +1 -1
  3. jaclang/compiler/absyntree.py +30 -5
  4. jaclang/compiler/compile.py +1 -1
  5. jaclang/compiler/constant.py +0 -1
  6. jaclang/compiler/jac.lark +1 -5
  7. jaclang/compiler/parser.py +2 -10
  8. jaclang/compiler/passes/main/__init__.py +1 -1
  9. jaclang/compiler/passes/main/access_modifier_pass.py +1 -1
  10. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +62 -33
  11. jaclang/compiler/passes/main/import_pass.py +285 -64
  12. jaclang/compiler/passes/main/inheritance_pass.py +103 -0
  13. jaclang/compiler/passes/main/py_collect_dep_pass.py +5 -5
  14. jaclang/compiler/passes/main/pyast_gen_pass.py +0 -19
  15. jaclang/compiler/passes/main/pyast_load_pass.py +1 -1
  16. jaclang/compiler/passes/main/schedules.py +2 -0
  17. jaclang/compiler/passes/main/sym_tab_build_pass.py +17 -0
  18. jaclang/compiler/passes/main/tests/fixtures/data_spatial_types.jac +130 -0
  19. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/__init__.py +3 -3
  20. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/__init__.pyi +3 -3
  21. jaclang/compiler/passes/main/tests/test_import_pass.py +16 -17
  22. jaclang/compiler/passes/main/tests/test_type_check_pass.py +24 -0
  23. jaclang/compiler/passes/main/type_check_pass.py +3 -2
  24. jaclang/compiler/passes/tool/jac_formatter_pass.py +0 -1
  25. jaclang/compiler/py_info.py +22 -0
  26. jaclang/compiler/symtable.py +9 -2
  27. jaclang/compiler/tests/test_importer.py +45 -1
  28. jaclang/langserve/tests/test_server.py +2 -2
  29. jaclang/plugin/default.py +86 -62
  30. jaclang/plugin/feature.py +2 -5
  31. jaclang/plugin/spec.py +1 -6
  32. jaclang/runtimelib/architype.py +20 -16
  33. jaclang/runtimelib/importer.py +26 -3
  34. jaclang/runtimelib/machine.py +2 -2
  35. jaclang/runtimelib/test.py +59 -4
  36. jaclang/runtimelib/utils.py +15 -0
  37. jaclang/settings.py +3 -0
  38. jaclang/tests/fixtures/base_class1.jac +11 -0
  39. jaclang/tests/fixtures/base_class2.jac +11 -0
  40. jaclang/tests/fixtures/import_all.jac +7 -0
  41. jaclang/tests/fixtures/import_all_py.py +8 -0
  42. jaclang/tests/fixtures/jactest_imported.jac +6 -0
  43. jaclang/tests/fixtures/jactest_main.jac +22 -0
  44. jaclang/tests/fixtures/multi_dim_array_split.jac +2 -6
  45. jaclang/tests/fixtures/test_py.py +12 -0
  46. jaclang/tests/fixtures/visit_sequence.jac +50 -0
  47. jaclang/tests/test_cli.py +83 -1
  48. jaclang/tests/test_language.py +24 -9
  49. jaclang/utils/helpers.py +9 -1
  50. jaclang/utils/test.py +2 -2
  51. jaclang/utils/tests/test_lang_tools.py +4 -2
  52. jaclang/utils/treeprinter.py +6 -3
  53. {jaclang-0.7.27.dist-info → jaclang-0.7.30.dist-info}/METADATA +3 -3
  54. {jaclang-0.7.27.dist-info → jaclang-0.7.30.dist-info}/RECORD +56 -45
  55. {jaclang-0.7.27.dist-info → jaclang-0.7.30.dist-info}/WHEEL +1 -1
  56. {jaclang-0.7.27.dist-info → jaclang-0.7.30.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,130 @@
1
+ node NodeA {
2
+ has value: int = 10;
3
+ }
4
+
5
+ node NodeC {
6
+ has value: int = 10;
7
+ }
8
+
9
+ edge EdgeB {
10
+ has value: int = 20;
11
+ }
12
+
13
+ with entry {
14
+ node_1 = a(value=5);
15
+ node_2 = a();
16
+ node_3 = a(value=15);
17
+ node_4 = a(value=20);
18
+ node_5 = c(value=25);
19
+ print(type(root ++> node_1));
20
+ node_1 +:edge_1 := b(value=5):+> node_2;
21
+ node_1 +:edge_2 := b(value=10):+> node_3;
22
+ node_1 +:edge_3 := b(value=15):+> node_4;
23
+ node_1 +:edge_4 := b():+> node_5;
24
+ node_1 del--> node_2;
25
+ del node_3;
26
+ }
27
+
28
+
29
+ node A {
30
+ has val: int = 0;
31
+ }
32
+
33
+ edge a {}
34
+
35
+ walker W {
36
+ can create with `root entry;
37
+ }
38
+
39
+ :walker:W:can:create {
40
+ Start = A(5);
41
+ here +:a:+> Start;
42
+ Start +:a:+> A(10) +:a:+> A(15);
43
+ Start +:a:+> A(20) +:a:+> A(25);
44
+ }
45
+
46
+ with entry {
47
+ root spawn W();
48
+ print([root-->-->(`?A)]);
49
+ print([root-->-->-->(`?A)]);
50
+ }
51
+
52
+ """Bubble sort using DS Features of Jac (somparision and swapping happens in inner nodes)."""
53
+ glob list1 = [80, 79, 60, 59, 40, 35, 19, 1];
54
+
55
+ node main_node {
56
+ has no: int = 0;
57
+ }
58
+
59
+ node inner_node {
60
+ has main: int = 0,
61
+ sub: int = 0;
62
+ }
63
+
64
+ walker walker1 {
65
+ can create_main_node with `root entry;
66
+ }
67
+
68
+ :walker:walker1:can:create_main_node {
69
+ end = here;
70
+ for i=0 to i<len(list1)+1 by i+=1 {
71
+ end ++> (end := main_node(no=i + 1));
72
+ visit [-->];
73
+ }
74
+ }
75
+
76
+ walker walker2 {
77
+ can skip_root with `root entry {
78
+ visit [-->];
79
+ }
80
+
81
+ can process with main_node entry;
82
+ }
83
+
84
+ :walker:walker2:can:process {
85
+ :g: list1 ;
86
+
87
+ for j in range(0, len(list1) - (here.no)) {
88
+ here ++> inner_node(main=here.no, sub=j + 1);
89
+ }
90
+ visit [-->];
91
+ }
92
+
93
+ walker walker3 {
94
+ can skiproot with `root entry {
95
+ visit [-->];
96
+ }
97
+
98
+ can adjust with main_node entry;
99
+ }
100
+
101
+ :walker:walker3:can:adjust {
102
+ here spawn walker4();
103
+ }
104
+
105
+ walker walker4 {
106
+ can skipmain with main_node entry {
107
+ visit [-->];# print(f"iteration {here.no} started {list1}");
108
+ }
109
+
110
+ can skipin with inner_node entry {
111
+ :g: list1 ;
112
+
113
+ j = here.sub - 1;
114
+ if list1[j] > list1[j + 1] {
115
+ x = list1[j];
116
+ list1[j] = list1[j + 1];
117
+ list1[j + 1] = x;
118
+ }
119
+ #uncomment below to see the swap in each inner nodes
120
+ # print(list1);
121
+
122
+ }
123
+ }
124
+
125
+ with entry {
126
+ root spawn walker1();
127
+ root spawn walker2();
128
+ root spawn walker3();
129
+ print(Jac.node_dot(root));
130
+ }
@@ -1,3 +1,3 @@
1
- from .display import *
2
- from .color import *
3
- from .constants import *
1
+ from .display import set_mode
2
+ from .color import Color
3
+ from .constants import CONST_VALUE, CL
@@ -1,3 +1,3 @@
1
- from .display import *
2
- from .color import *
3
- from .constants import *
1
+ from .display import set_mode
2
+ from .color import Color
3
+ from .constants import CONST_VALUE, CL
@@ -81,39 +81,38 @@ class ImportPassPassTests(TestCase):
81
81
  "genericpath": r"jaclang/vendor/mypy/typeshed/stdlib/genericpath.pyi$",
82
82
  }
83
83
  for i in p:
84
- self.assertIn(i, build.ir.py_raise_map)
85
- self.assertRegex(re.sub(r".*fixtures/", "", build.ir.py_raise_map[i]), p[i])
84
+ self.assertIn(i, build.ir.py_info.py_raise_map)
85
+ self.assertRegex(
86
+ re.sub(r".*fixtures/", "", build.ir.py_info.py_raise_map[i]).replace(
87
+ "\\", "/"
88
+ ),
89
+ p[i],
90
+ )
86
91
 
87
92
  def test_py_raised_mods(self) -> None:
88
93
  """Basic test for pass."""
89
94
  state = jac_file_to_pass(
90
95
  self.fixture_abs_path("py_imp_test.jac"), schedule=py_code_gen_typed
91
96
  )
97
+ for i in list(
98
+ filter(
99
+ lambda x: x.py_info.is_raised_from_py,
100
+ state.ir.get_all_sub_nodes(ast.Module),
101
+ )
102
+ ):
103
+ print(ast.Module.get_href_path(i))
92
104
  self.assertEqual(
93
105
  len(
94
106
  list(
95
107
  filter(
96
- lambda x: x.is_raised_from_py,
108
+ lambda x: x.py_info.is_raised_from_py,
97
109
  state.ir.get_all_sub_nodes(ast.Module),
98
110
  )
99
111
  )
100
112
  ),
101
- 7,
113
+ 11, # TODO: Need to only link the modules one time
102
114
  )
103
115
 
104
- # def test_py_resolve_list(self) -> None:
105
- # """Basic test for pass."""
106
- # state: JacImportPass = jac_file_to_pass(
107
- # self.examples_abs_path("rpg_game/jac_impl/jac_impl_5/main.jac"),
108
- # JacImportPass,
109
- # )
110
- # self.assertGreater(len(state.py_resolve_list), 20)
111
- # self.assertIn("pygame.sprite.Sprite.__init__", state.py_resolve_list)
112
- # self.assertIn("pygame.mouse.get_pressed", state.py_resolve_list)
113
- # self.assertIn("pygame.K_SPACE", state.py_resolve_list)
114
- # self.assertIn("random.randint", state.py_resolve_list)
115
- # self.assertIn("pygame.font.Font", state.py_resolve_list)
116
-
117
116
  def test_double_empty_anx(self) -> None:
118
117
  """Test importing python."""
119
118
  captured_output = io.StringIO()
@@ -62,3 +62,27 @@ class MypyTypeCheckPassTests(TestCase):
62
62
  self.assertEqual(out.count("Type: builtins.str"), 35)
63
63
  for i in lis:
64
64
  self.assertNotIn(i, out)
65
+
66
+ def test_data_spatial_type_info(self) -> None:
67
+ """Testing for type info for dataspatial constructs."""
68
+ out = AstTool().ir(
69
+ ["ast", f"{self.fixture_abs_path('data_spatial_types.jac')}"]
70
+ )
71
+ self.assertRegex(
72
+ out,
73
+ r"129:24 - 129:28.*SpecialVarRef - _Jac.get_root\(\) \- Type\: jaclang.runtimelib.architype.Root",
74
+ )
75
+ self.assertRegex(out, r"129:11 - 129:29.*FuncCall \- Type\: builtins\.str")
76
+ self.assertRegex(
77
+ out,
78
+ r"129:15 - 129:23.*Name \- node_dot \- Type\: builtins.str, SymbolTable\: str",
79
+ )
80
+ self.assertRegex(
81
+ out,
82
+ r"128:5 - 128:25.*BinaryExpr \- Type\: jaclang.runtimelib.architype.WalkerArchitype",
83
+ )
84
+ self.assertRegex(
85
+ out,
86
+ r"48:11 - 48:28.*EdgeRefTrailer \- Type\: builtins.list\[data_spatial_types.A\]",
87
+ )
88
+ self.assertRegex(out, r"24:5 - 24:25.*BinaryExpr \- Type\: builtins.bool", out)
@@ -115,13 +115,14 @@ class JacTypeCheckPass(Pass):
115
115
  for k, v in mypy_graph.items()
116
116
  if (
117
117
  k.startswith("jaclang.plugin")
118
+ or k.startswith("jaclang.runtimelib")
118
119
  or not (k.startswith("jaclang.") or k.startswith("mypy."))
119
120
  )
120
121
  }
121
122
  for i in mypy_graph:
122
- self.ir.py_mod_dep_map[i] = mypy_graph[i].xpath
123
+ self.ir.py_info.py_mod_dep_map[i] = mypy_graph[i].xpath
123
124
  for j in mypy_graph[i].dependencies:
124
- self.ir.py_mod_dep_map[j] = str(
125
+ self.ir.py_info.py_mod_dep_map[j] = str(
125
126
  myab.find_module_with_reason(j, manager)
126
127
  )
127
128
  myab.process_graph(mypy_graph, manager)
@@ -973,7 +973,6 @@ class JacFormatPass(Pass):
973
973
  Tok.PIPE_FWD,
974
974
  Tok.KW_SPAWN,
975
975
  Tok.A_PIPE_FWD,
976
- Tok.ELVIS_OP,
977
976
  ]
978
977
  or (
979
978
  node.op.name == Tok.PIPE_FWD
@@ -0,0 +1,22 @@
1
+ """Code location info for AST nodes."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class PyInfo:
7
+ """Python info related to python imports."""
8
+
9
+ def __init__(self) -> None:
10
+ """Object Initialization."""
11
+ # Module dependacy map used to store all module dependacies detected
12
+ # by mypy. The dependacies are computed using the mypy graph in
13
+ # TypeCheck pass
14
+ self.py_mod_dep_map: dict[str, str] = {}
15
+
16
+ # Get all the modules that we really need to raise inorder to make
17
+ # all Jac types correct (FuseTypeInfo works). This is computed using
18
+ # PyCollectDepsPass.
19
+ self.py_raise_map: dict[str, str] = {}
20
+
21
+ # Flag for python modules raised into jac
22
+ self.is_raised_from_py: bool = False
@@ -115,6 +115,7 @@ class SymbolTable:
115
115
  node: ast.AstSymbolNode,
116
116
  access_spec: Optional[ast.AstAccessNode] | SymbolAccess = None,
117
117
  single: bool = False,
118
+ force_overwrite: bool = False,
118
119
  ) -> Optional[ast.AstNode]:
119
120
  """Set a variable in the symbol table.
120
121
 
@@ -126,7 +127,7 @@ class SymbolTable:
126
127
  if single and node.sym_name in self.tab
127
128
  else None
128
129
  )
129
- if node.sym_name not in self.tab:
130
+ if force_overwrite or node.sym_name not in self.tab:
130
131
  self.tab[node.sym_name] = Symbol(
131
132
  defn=node.name_spec,
132
133
  access=(
@@ -163,11 +164,17 @@ class SymbolTable:
163
164
  node: ast.AstSymbolNode,
164
165
  access_spec: Optional[ast.AstAccessNode] | SymbolAccess = None,
165
166
  single_decl: Optional[str] = None,
167
+ force_overwrite: bool = False,
166
168
  ) -> Optional[Symbol]:
167
169
  """Insert into symbol table."""
168
170
  if node.sym and self == node.sym.parent_tab:
169
171
  return node.sym
170
- self.insert(node=node, single=single_decl is not None, access_spec=access_spec)
172
+ self.insert(
173
+ node=node,
174
+ single=single_decl is not None,
175
+ access_spec=access_spec,
176
+ force_overwrite=force_overwrite,
177
+ )
171
178
  self.update_py_ctx_for_def(node)
172
179
  return node.sym
173
180
 
@@ -33,7 +33,7 @@ class TestLoader(TestCase):
33
33
  )
34
34
  self.assertIn(
35
35
  "/tests/fixtures/hello_world.jac",
36
- str(JacMachine.get().loaded_modules),
36
+ str(JacMachine.get().loaded_modules).replace("\\\\", "/"),
37
37
  )
38
38
  JacMachine.detach()
39
39
 
@@ -62,3 +62,47 @@ class TestLoader(TestCase):
62
62
  "{SomeObj(a=10): 'check'} [MyObj(apple=5, banana=7), MyObj(apple=5, banana=7)]",
63
63
  stdout_value,
64
64
  )
65
+
66
+ def test_import_with_jacpath(self) -> None:
67
+ """Test module import using JACPATH."""
68
+ # Set up a temporary JACPATH environment variable
69
+ import os
70
+ import tempfile
71
+
72
+ jacpath_dir = tempfile.TemporaryDirectory()
73
+ os.environ["JACPATH"] = jacpath_dir.name
74
+
75
+ # Create a mock Jac file in the JACPATH directory
76
+ module_name = "test_module"
77
+ jac_file_path = os.path.join(jacpath_dir.name, f"{module_name}.jac")
78
+ with open(jac_file_path, "w") as f:
79
+ f.write(
80
+ """
81
+ with entry {
82
+ "Hello from JACPATH!" :> print;
83
+ }
84
+ """
85
+ )
86
+
87
+ # Capture the output
88
+ captured_output = io.StringIO()
89
+ sys.stdout = captured_output
90
+
91
+ try:
92
+ JacMachine(self.fixture_abs_path(__file__)).attach_program(
93
+ JacProgram(mod_bundle=None, bytecode=None, sem_ir=None)
94
+ )
95
+ jac_import(module_name, base_path=__file__)
96
+ cli.run(jac_file_path)
97
+
98
+ # Reset stdout and get the output
99
+ sys.stdout = sys.__stdout__
100
+ stdout_value = captured_output.getvalue()
101
+
102
+ self.assertIn("Hello from JACPATH!", stdout_value)
103
+
104
+ finally:
105
+ captured_output.close()
106
+ JacMachine.detach()
107
+ os.environ.pop("JACPATH", None)
108
+ jacpath_dir.cleanup()
@@ -330,7 +330,7 @@ class TestJacLangServer(TestCase):
330
330
  "doubleinner",
331
331
  "apply_red",
332
332
  ],
333
- 8,
333
+ 11,
334
334
  ),
335
335
  (
336
336
  lspt.Position(65, 23),
@@ -359,7 +359,7 @@ class TestJacLangServer(TestCase):
359
359
  "doubleinner",
360
360
  "apply_red",
361
361
  ],
362
- 8,
362
+ 11,
363
363
  ),
364
364
  (
365
365
  lspt.Position(73, 22),
jaclang/plugin/default.py CHANGED
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import ast as ast3
6
6
  import fnmatch
7
7
  import html
8
+ import inspect
8
9
  import os
9
10
  import types
10
11
  from collections import OrderedDict
@@ -42,8 +43,11 @@ from jaclang.runtimelib.constructs import (
42
43
  from jaclang.runtimelib.importer import ImportPathSpec, JacImporter, PythonImporter
43
44
  from jaclang.runtimelib.machine import JacMachine, JacProgram
44
45
  from jaclang.runtimelib.memory import Shelf, ShelfStorage
45
- from jaclang.runtimelib.utils import collect_node_connections, traverse_graph
46
-
46
+ from jaclang.runtimelib.utils import (
47
+ all_issubclass,
48
+ collect_node_connections,
49
+ traverse_graph,
50
+ )
47
51
 
48
52
  import pluggy
49
53
 
@@ -175,14 +179,12 @@ class JacAccessValidationImpl:
175
179
  if to_root.access.all > access_level:
176
180
  access_level = to_root.access.all
177
181
 
178
- level = to_root.access.roots.check(str(jroot.id))
179
- if level > AccessLevel.NO_ACCESS and access_level == AccessLevel.NO_ACCESS:
182
+ if (level := to_root.access.roots.check(str(jroot.id))) is not None:
180
183
  access_level = level
181
184
 
182
185
  # if target anchor have set allowed roots
183
186
  # if current root is allowed to target anchor
184
- level = to_access.roots.check(str(jroot.id))
185
- if level > AccessLevel.NO_ACCESS and access_level == AccessLevel.NO_ACCESS:
187
+ if (level := to_access.roots.check(str(jroot.id))) is not None:
186
188
  access_level = level
187
189
 
188
190
  return access_level
@@ -398,64 +400,85 @@ class JacWalkerImpl:
398
400
 
399
401
  walker.path = []
400
402
  walker.next = [node]
401
- if walker.next:
402
- current_node = walker.next[-1].architype
403
- for i in warch._jac_entry_funcs_:
404
- trigger = i.get_funcparam_annotations(i.func)
405
- if not trigger:
406
- if i.func:
407
- i.func(warch, current_node)
408
- else:
409
- raise ValueError(f"No function {i.name} to call.")
403
+ current_node = node.architype
404
+
405
+ # walker entry
406
+ for i in warch._jac_entry_funcs_:
407
+ if i.func and not i.trigger:
408
+ i.func(warch, current_node)
409
+ if walker.disengaged:
410
+ return warch
411
+
410
412
  while len(walker.next):
411
413
  if current_node := walker.next.pop(0).architype:
414
+ # walker entry with
415
+ for i in warch._jac_entry_funcs_:
416
+ if (
417
+ i.func
418
+ and i.trigger
419
+ and all_issubclass(i.trigger, NodeArchitype)
420
+ and isinstance(current_node, i.trigger)
421
+ ):
422
+ i.func(warch, current_node)
423
+ if walker.disengaged:
424
+ return warch
425
+
426
+ # node entry
412
427
  for i in current_node._jac_entry_funcs_:
413
- trigger = i.get_funcparam_annotations(i.func)
414
- if not trigger or isinstance(warch, trigger):
415
- if i.func:
416
- i.func(current_node, warch)
417
- else:
418
- raise ValueError(f"No function {i.name} to call.")
428
+ if i.func and not i.trigger:
429
+ i.func(current_node, warch)
419
430
  if walker.disengaged:
420
431
  return warch
421
- for i in warch._jac_entry_funcs_:
422
- trigger = i.get_funcparam_annotations(i.func)
423
- if not trigger or isinstance(current_node, trigger):
424
- if i.func and trigger:
425
- i.func(warch, current_node)
426
- elif not trigger:
427
- continue
428
- else:
429
- raise ValueError(f"No function {i.name} to call.")
432
+
433
+ # node entry with
434
+ for i in current_node._jac_entry_funcs_:
435
+ if (
436
+ i.func
437
+ and i.trigger
438
+ and all_issubclass(i.trigger, WalkerArchitype)
439
+ and isinstance(warch, i.trigger)
440
+ ):
441
+ i.func(current_node, warch)
430
442
  if walker.disengaged:
431
443
  return warch
432
- for i in warch._jac_exit_funcs_:
433
- trigger = i.get_funcparam_annotations(i.func)
434
- if not trigger or isinstance(current_node, trigger):
435
- if i.func and trigger:
436
- i.func(warch, current_node)
437
- elif not trigger:
438
- continue
439
- else:
440
- raise ValueError(f"No function {i.name} to call.")
444
+
445
+ # node exit with
446
+ for i in current_node._jac_exit_funcs_:
447
+ if (
448
+ i.func
449
+ and i.trigger
450
+ and all_issubclass(i.trigger, WalkerArchitype)
451
+ and isinstance(warch, i.trigger)
452
+ ):
453
+ i.func(current_node, warch)
441
454
  if walker.disengaged:
442
455
  return warch
456
+
457
+ # node exit
443
458
  for i in current_node._jac_exit_funcs_:
444
- trigger = i.get_funcparam_annotations(i.func)
445
- if not trigger or isinstance(warch, trigger):
446
- if i.func:
447
- i.func(current_node, warch)
448
- else:
449
- raise ValueError(f"No function {i.name} to call.")
459
+ if i.func and not i.trigger:
460
+ i.func(current_node, warch)
461
+ if walker.disengaged:
462
+ return warch
463
+
464
+ # walker exit with
465
+ for i in warch._jac_exit_funcs_:
466
+ if (
467
+ i.func
468
+ and i.trigger
469
+ and all_issubclass(i.trigger, NodeArchitype)
470
+ and isinstance(current_node, i.trigger)
471
+ ):
472
+ i.func(warch, current_node)
450
473
  if walker.disengaged:
451
474
  return warch
475
+ # walker exit
452
476
  for i in warch._jac_exit_funcs_:
453
- trigger = i.get_funcparam_annotations(i.func)
454
- if not trigger:
455
- if i.func:
456
- i.func(warch, current_node)
457
- else:
458
- raise ValueError(f"No function {i.name} to call.")
477
+ if i.func and not i.trigger:
478
+ i.func(warch, current_node)
479
+ if walker.disengaged:
480
+ return warch
481
+
459
482
  walker.ignores = []
460
483
  return warch
461
484
 
@@ -818,12 +841,14 @@ class JacFeatureImpl(
818
841
  @hookimpl
819
842
  def create_test(test_fun: Callable) -> Callable:
820
843
  """Create a new test."""
844
+ file_path = inspect.getfile(test_fun)
845
+ func_name = test_fun.__name__
821
846
 
822
847
  def test_deco() -> None:
823
848
  test_fun(JacTestCheck())
824
849
 
825
850
  test_deco.__name__ = test_fun.__name__
826
- JacTestCheck.add_test(test_deco)
851
+ JacTestCheck.add_test(file_path, func_name, test_deco)
827
852
 
828
853
  return test_deco
829
854
 
@@ -831,6 +856,7 @@ class JacFeatureImpl(
831
856
  @hookimpl
832
857
  def run_test(
833
858
  filepath: str,
859
+ func_name: Optional[str],
834
860
  filter: Optional[str],
835
861
  xit: bool,
836
862
  maxfail: Optional[int],
@@ -849,7 +875,9 @@ class JacFeatureImpl(
849
875
  mod_name = mod_name[:-5]
850
876
  JacTestCheck.reset()
851
877
  Jac.jac_import(target=mod_name, base_path=base, cachable=False)
852
- JacTestCheck.run_test(xit, maxfail, verbose)
878
+ JacTestCheck.run_test(
879
+ xit, maxfail, verbose, os.path.abspath(filepath), func_name
880
+ )
853
881
  ret_count = JacTestCheck.failcount
854
882
  else:
855
883
  print("Not a .jac file.")
@@ -875,7 +903,9 @@ class JacFeatureImpl(
875
903
  print(f"\n\n\t\t* Inside {root_dir}" + "/" + f"{file} *")
876
904
  JacTestCheck.reset()
877
905
  Jac.jac_import(target=file[:-4], base_path=root_dir)
878
- JacTestCheck.run_test(xit, maxfail, verbose)
906
+ JacTestCheck.run_test(
907
+ xit, maxfail, verbose, os.path.abspath(file), func_name
908
+ )
879
909
 
880
910
  if JacTestCheck.breaker and (xit or maxfail):
881
911
  break
@@ -888,12 +918,6 @@ class JacFeatureImpl(
888
918
 
889
919
  return ret_count
890
920
 
891
- @staticmethod
892
- @hookimpl
893
- def elvis(op1: Optional[T], op2: T) -> T:
894
- """Jac's elvis operator feature."""
895
- return ret if (ret := op1) is not None else op2
896
-
897
921
  @staticmethod
898
922
  @hookimpl
899
923
  def has_instance_default(gen_func: Callable[[], T]) -> T:
@@ -1000,7 +1024,7 @@ class JacFeatureImpl(
1000
1024
  dir in [EdgeDir.OUT, EdgeDir.ANY]
1001
1025
  and node == source
1002
1026
  and target.architype in right
1003
- and Jac.check_write_access(target)
1027
+ and Jac.check_connect_access(target)
1004
1028
  ):
1005
1029
  Jac.destroy(anchor) if anchor.persistent else Jac.detach(anchor)
1006
1030
  disconnect_occurred = True
@@ -1008,7 +1032,7 @@ class JacFeatureImpl(
1008
1032
  dir in [EdgeDir.IN, EdgeDir.ANY]
1009
1033
  and node == target
1010
1034
  and source.architype in right
1011
- and Jac.check_write_access(source)
1035
+ and Jac.check_connect_access(source)
1012
1036
  ):
1013
1037
  Jac.destroy(anchor) if anchor.persistent else Jac.detach(anchor)
1014
1038
  disconnect_occurred = True
jaclang/plugin/feature.py CHANGED
@@ -359,6 +359,7 @@ class JacFeature(
359
359
  @staticmethod
360
360
  def run_test(
361
361
  filepath: str,
362
+ func_name: Optional[str] = None,
362
363
  filter: Optional[str] = None,
363
364
  xit: bool = False,
364
365
  maxfail: Optional[int] = None,
@@ -368,6 +369,7 @@ class JacFeature(
368
369
  """Run the test suite in the specified .jac file."""
369
370
  return plugin_manager.hook.run_test(
370
371
  filepath=filepath,
372
+ func_name=func_name,
371
373
  filter=filter,
372
374
  xit=xit,
373
375
  maxfail=maxfail,
@@ -375,11 +377,6 @@ class JacFeature(
375
377
  verbose=verbose,
376
378
  )
377
379
 
378
- @staticmethod
379
- def elvis(op1: Optional[T], op2: T) -> T:
380
- """Jac's elvis operator feature."""
381
- return plugin_manager.hook.elvis(op1=op1, op2=op2)
382
-
383
380
  @staticmethod
384
381
  def has_instance_default(gen_func: Callable[[], T]) -> T:
385
382
  """Jac's has container default feature."""