jaclang 0.8.0__py3-none-any.whl → 0.8.2__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 (124) hide show
  1. jaclang/__init__.py +6 -0
  2. jaclang/cli/cli.py +23 -50
  3. jaclang/compiler/codeinfo.py +0 -1
  4. jaclang/compiler/jac.lark +14 -22
  5. jaclang/compiler/larkparse/jac_parser.py +2 -2
  6. jaclang/compiler/parser.py +378 -531
  7. jaclang/compiler/passes/main/__init__.py +0 -14
  8. jaclang/compiler/passes/main/annex_pass.py +2 -8
  9. jaclang/compiler/passes/main/cfg_build_pass.py +39 -13
  10. jaclang/compiler/passes/main/def_impl_match_pass.py +14 -13
  11. jaclang/compiler/passes/main/def_use_pass.py +4 -7
  12. jaclang/compiler/passes/main/import_pass.py +6 -14
  13. jaclang/compiler/passes/main/inheritance_pass.py +2 -2
  14. jaclang/compiler/passes/main/pyast_gen_pass.py +428 -799
  15. jaclang/compiler/passes/main/pyast_load_pass.py +115 -311
  16. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +8 -7
  17. jaclang/compiler/passes/main/sym_tab_build_pass.py +3 -3
  18. jaclang/compiler/passes/main/sym_tab_link_pass.py +6 -9
  19. jaclang/compiler/passes/main/tests/fixtures/symtab_link_tests/action/actions.jac +1 -5
  20. jaclang/compiler/passes/main/tests/fixtures/symtab_link_tests/main.jac +1 -8
  21. jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +5 -9
  22. jaclang/compiler/passes/main/tests/test_decl_impl_match_pass.py +7 -8
  23. jaclang/compiler/passes/main/tests/test_import_pass.py +5 -18
  24. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +2 -6
  25. jaclang/compiler/passes/main/tests/test_sub_node_pass.py +1 -3
  26. jaclang/compiler/passes/main/tests/test_sym_tab_link_pass.py +20 -17
  27. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +425 -216
  28. jaclang/compiler/passes/tool/jac_formatter_pass.py +2 -0
  29. jaclang/compiler/passes/tool/tests/fixtures/archetype_frmt.jac +14 -0
  30. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/triple_quoted_string.jac +5 -4
  31. jaclang/compiler/passes/tool/tests/fixtures/import_fmt.jac +6 -0
  32. jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +3 -3
  33. jaclang/compiler/passes/tool/tests/fixtures/tagbreak.jac +9 -0
  34. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +18 -3
  35. jaclang/compiler/passes/tool/tests/test_unparse_validate.py +2 -2
  36. jaclang/compiler/program.py +22 -66
  37. jaclang/compiler/tests/fixtures/fam.jac +2 -2
  38. jaclang/compiler/tests/fixtures/pkg_import_lib/__init__.jac +1 -0
  39. jaclang/compiler/tests/fixtures/pkg_import_lib/sub/__init__.jac +1 -0
  40. jaclang/compiler/tests/fixtures/pkg_import_lib/sub/helper.jac +3 -0
  41. jaclang/compiler/tests/fixtures/pkg_import_lib/tools.jac +3 -0
  42. jaclang/compiler/tests/fixtures/pkg_import_lib_py/__init__.py +5 -0
  43. jaclang/compiler/tests/fixtures/pkg_import_lib_py/sub/__init__.py +3 -0
  44. jaclang/compiler/tests/fixtures/pkg_import_lib_py/sub/helper.jac +3 -0
  45. jaclang/compiler/tests/fixtures/pkg_import_lib_py/tools.jac +3 -0
  46. jaclang/compiler/tests/fixtures/pkg_import_main.jac +10 -0
  47. jaclang/compiler/tests/fixtures/pkg_import_main_py.jac +11 -0
  48. jaclang/compiler/tests/test_importer.py +30 -13
  49. jaclang/compiler/tests/test_parser.py +1 -0
  50. jaclang/compiler/unitree.py +488 -320
  51. jaclang/langserve/__init__.jac +1 -0
  52. jaclang/langserve/engine.jac +503 -0
  53. jaclang/langserve/sem_manager.jac +309 -0
  54. jaclang/langserve/server.jac +201 -0
  55. jaclang/langserve/tests/server_test/test_lang_serve.py +139 -48
  56. jaclang/langserve/tests/server_test/utils.py +35 -6
  57. jaclang/langserve/tests/session.jac +294 -0
  58. jaclang/langserve/tests/test_sem_tokens.py +2 -2
  59. jaclang/langserve/tests/test_server.py +8 -7
  60. jaclang/langserve/utils.jac +51 -30
  61. jaclang/runtimelib/archetype.py +128 -6
  62. jaclang/runtimelib/builtin.py +17 -14
  63. jaclang/runtimelib/importer.py +51 -76
  64. jaclang/runtimelib/machine.py +469 -305
  65. jaclang/runtimelib/meta_importer.py +86 -0
  66. jaclang/runtimelib/tests/fixtures/graph_purger.jac +24 -26
  67. jaclang/runtimelib/tests/fixtures/other_root_access.jac +25 -16
  68. jaclang/runtimelib/tests/fixtures/traversing_save.jac +7 -5
  69. jaclang/runtimelib/tests/test_jaseci.py +3 -1
  70. jaclang/runtimelib/utils.py +3 -3
  71. jaclang/tests/fixtures/arch_rel_import_creation.jac +23 -23
  72. jaclang/tests/fixtures/async_ability.jac +43 -10
  73. jaclang/tests/fixtures/async_function.jac +18 -0
  74. jaclang/tests/fixtures/async_walker.jac +17 -12
  75. jaclang/tests/fixtures/backward_edge_visit.jac +31 -0
  76. jaclang/tests/fixtures/builtin_printgraph.jac +85 -0
  77. jaclang/tests/fixtures/builtin_printgraph_json.jac +21 -0
  78. jaclang/tests/fixtures/builtin_printgraph_mermaid.jac +16 -0
  79. jaclang/tests/fixtures/chandra_bugs2.jac +20 -13
  80. jaclang/tests/fixtures/concurrency.jac +1 -1
  81. jaclang/tests/fixtures/create_dynamic_archetype.jac +25 -28
  82. jaclang/tests/fixtures/deep/deeper/deep_outer_import.jac +7 -4
  83. jaclang/tests/fixtures/deep/deeper/snd_lev.jac +2 -2
  84. jaclang/tests/fixtures/deep/deeper/snd_lev_dup.jac +6 -0
  85. jaclang/tests/fixtures/deep/one_lev.jac +2 -2
  86. jaclang/tests/fixtures/deep/one_lev_dup.jac +4 -3
  87. jaclang/tests/fixtures/dynamic_archetype.jac +19 -12
  88. jaclang/tests/fixtures/edge_ability.jac +49 -0
  89. jaclang/tests/fixtures/foo.jac +14 -22
  90. jaclang/tests/fixtures/guess_game.jac +1 -1
  91. jaclang/tests/fixtures/here_usage_error.jac +21 -0
  92. jaclang/tests/fixtures/here_visitor_usage.jac +21 -0
  93. jaclang/tests/fixtures/jac_from_py.py +1 -1
  94. jaclang/tests/fixtures/jp_importer.jac +6 -6
  95. jaclang/tests/fixtures/jp_importer_auto.jac +5 -3
  96. jaclang/tests/fixtures/node_del.jac +30 -36
  97. jaclang/tests/fixtures/unicode_strings.jac +24 -0
  98. jaclang/tests/fixtures/visit_traversal.jac +47 -0
  99. jaclang/tests/fixtures/walker_update.jac +5 -7
  100. jaclang/tests/test_cli.py +12 -7
  101. jaclang/tests/test_language.py +218 -145
  102. jaclang/tests/test_reference.py +9 -4
  103. jaclang/tests/test_typecheck.py +13 -26
  104. jaclang/utils/helpers.py +14 -6
  105. jaclang/utils/lang_tools.py +9 -8
  106. jaclang/utils/module_resolver.py +23 -0
  107. jaclang/utils/tests/test_lang_tools.py +2 -1
  108. jaclang/utils/treeprinter.py +3 -4
  109. {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/METADATA +4 -3
  110. {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/RECORD +112 -94
  111. {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/WHEEL +1 -1
  112. jaclang/compiler/passes/main/tests/fixtures/main_err.jac +0 -6
  113. jaclang/compiler/passes/main/tests/fixtures/second_err.jac +0 -4
  114. jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +0 -644
  115. jaclang/compiler/passes/tool/tests/test_doc_ir_gen_pass.py +0 -29
  116. jaclang/langserve/__init__.py +0 -1
  117. jaclang/langserve/engine.py +0 -553
  118. jaclang/langserve/sem_manager.py +0 -383
  119. jaclang/langserve/server.py +0 -167
  120. jaclang/langserve/tests/session.py +0 -255
  121. jaclang/tests/fixtures/builtin_dotgen.jac +0 -42
  122. jaclang/tests/fixtures/builtin_dotgen_json.jac +0 -21
  123. jaclang/tests/fixtures/deep/deeper/__init__.jac +0 -1
  124. {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/entry_points.txt +0 -0
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import ast as ast3
6
- import asyncio
7
6
  import fnmatch
8
7
  import html
9
8
  import inspect
@@ -14,12 +13,13 @@ import types
14
13
  from collections import OrderedDict
15
14
  from concurrent.futures import Future, ThreadPoolExecutor
16
15
  from dataclasses import MISSING, dataclass, field
17
- from functools import partial, wraps
16
+ from functools import wraps
18
17
  from inspect import getfile
19
18
  from logging import getLogger
20
19
  from typing import (
21
20
  Any,
22
21
  Callable,
22
+ Coroutine,
23
23
  Mapping,
24
24
  Optional,
25
25
  ParamSpec,
@@ -39,7 +39,9 @@ from jaclang.compiler.constant import Constants as Con, EdgeDir, colors
39
39
  from jaclang.compiler.passes.main.pyast_gen_pass import PyastGenPass
40
40
  from jaclang.compiler.program import JacProgram
41
41
  from jaclang.runtimelib.archetype import (
42
+ DataSpatialDestination,
42
43
  DataSpatialFunction,
44
+ DataSpatialPath,
43
45
  GenericEdge as _GenericEdge,
44
46
  Root as _Root,
45
47
  )
@@ -77,6 +79,64 @@ T = TypeVar("T")
77
79
  P = ParamSpec("P")
78
80
 
79
81
 
82
+ class ExecutionContext:
83
+ """Execution Context."""
84
+
85
+ def __init__(
86
+ self,
87
+ session: Optional[str] = None,
88
+ root: Optional[str] = None,
89
+ ) -> None:
90
+ """Initialize JacMachine."""
91
+ self.mem: Memory = ShelfStorage(session)
92
+ self.reports: list[Any] = []
93
+ sr_arch = Root()
94
+ sr_anch = sr_arch.__jac__
95
+ sr_anch.id = UUID(Con.SUPER_ROOT_UUID)
96
+ sr_anch.persistent = False
97
+ self.system_root = sr_anch
98
+ self.custom: Any = MISSING
99
+ if not isinstance(
100
+ system_root := self.mem.find_by_id(UUID(Con.SUPER_ROOT_UUID)), NodeAnchor
101
+ ):
102
+ system_root = cast(NodeAnchor, Root().__jac__) # type: ignore[attr-defined]
103
+ system_root.id = UUID(Con.SUPER_ROOT_UUID)
104
+ self.mem.set(system_root.id, system_root)
105
+
106
+ self.system_root = system_root
107
+
108
+ self.entry_node = self.root_state = self.init_anchor(root, self.system_root)
109
+
110
+ def init_anchor(
111
+ self,
112
+ anchor_id: str | None,
113
+ default: NodeAnchor,
114
+ ) -> NodeAnchor:
115
+ """Load initial anchors."""
116
+ if anchor_id:
117
+ if isinstance(anchor := self.mem.find_by_id(UUID(anchor_id)), NodeAnchor):
118
+ return anchor
119
+ raise ValueError(f"Invalid anchor id {anchor_id} !")
120
+ return default
121
+
122
+ def set_entry_node(self, entry_node: str | None) -> None:
123
+ """Override entry."""
124
+ self.entry_node = self.init_anchor(entry_node, self.root_state)
125
+
126
+ def close(self) -> None:
127
+ """Close current ExecutionContext."""
128
+ self.mem.close()
129
+ JacMachine.reset_machine()
130
+
131
+ def get_root(self) -> Root:
132
+ """Get current root."""
133
+ return cast(Root, self.root_state.archetype)
134
+
135
+ def global_system_root(self) -> NodeAnchor:
136
+ """Get global system root."""
137
+ return self.system_root
138
+
139
+
80
140
  class JacAccessValidation:
81
141
  """Jac Access Validation Specs."""
82
142
 
@@ -233,69 +293,105 @@ class JacNode:
233
293
 
234
294
  @staticmethod
235
295
  def get_edges(
236
- node: NodeAnchor,
237
- dir: EdgeDir,
238
- filter: Callable[[EdgeArchetype], bool] | None,
239
- target_obj: list[NodeArchetype] | None,
296
+ origin: list[NodeArchetype], destination: DataSpatialDestination
240
297
  ) -> list[EdgeArchetype]:
241
298
  """Get edges connected to this node."""
242
- ret_edges: list[EdgeArchetype] = []
243
- for anchor in node.edges:
244
- if (
245
- (source := anchor.source)
246
- and (target := anchor.target)
247
- and (not filter or filter(anchor.archetype))
248
- and source.archetype
249
- and target.archetype
250
- ):
299
+ edges: OrderedDict[EdgeAnchor, EdgeArchetype] = OrderedDict()
300
+ for node in origin:
301
+ nanch = node.__jac__
302
+ for anchor in nanch.edges:
251
303
  if (
252
- dir in [EdgeDir.OUT, EdgeDir.ANY]
253
- and node == source
254
- and (not target_obj or target.archetype in target_obj)
255
- and JacMachineInterface.check_read_access(target)
304
+ (source := anchor.source)
305
+ and (target := anchor.target)
306
+ and destination.edge_filter(anchor.archetype)
307
+ and source.archetype
308
+ and target.archetype
256
309
  ):
257
- ret_edges.append(anchor.archetype)
310
+ if (
311
+ destination.direction in [EdgeDir.OUT, EdgeDir.ANY]
312
+ and nanch == source
313
+ and destination.node_filter(target.archetype)
314
+ and JacMachineInterface.check_read_access(target)
315
+ ):
316
+ edges[anchor] = anchor.archetype
317
+ if (
318
+ destination.direction in [EdgeDir.IN, EdgeDir.ANY]
319
+ and nanch == target
320
+ and destination.node_filter(source.archetype)
321
+ and JacMachineInterface.check_read_access(source)
322
+ ):
323
+ edges[anchor] = anchor.archetype
324
+ return list(edges.values())
325
+
326
+ @staticmethod
327
+ def get_edges_with_node(
328
+ origin: list[NodeArchetype],
329
+ destination: DataSpatialDestination,
330
+ from_visit: bool = False,
331
+ ) -> list[EdgeArchetype | NodeArchetype]:
332
+ """Get edges connected to this node and the node."""
333
+ loc: OrderedDict[
334
+ Union[NodeAnchor, EdgeAnchor], Union[NodeArchetype, EdgeArchetype]
335
+ ] = OrderedDict()
336
+ for node in origin:
337
+ nanch = node.__jac__
338
+ for anchor in nanch.edges:
258
339
  if (
259
- dir in [EdgeDir.IN, EdgeDir.ANY]
260
- and node == target
261
- and (not target_obj or source.archetype in target_obj)
262
- and JacMachineInterface.check_read_access(source)
340
+ (source := anchor.source)
341
+ and (target := anchor.target)
342
+ and destination.edge_filter(anchor.archetype)
343
+ and source.archetype
344
+ and target.archetype
263
345
  ):
264
- ret_edges.append(anchor.archetype)
265
- return ret_edges
346
+ if (
347
+ destination.direction in [EdgeDir.OUT, EdgeDir.ANY]
348
+ and nanch == source
349
+ and destination.node_filter(target.archetype)
350
+ and JacMachineInterface.check_read_access(target)
351
+ ):
352
+ loc[anchor] = anchor.archetype
353
+ loc[target] = target.archetype
354
+ if (
355
+ destination.direction in [EdgeDir.IN, EdgeDir.ANY]
356
+ and nanch == target
357
+ and destination.node_filter(source.archetype)
358
+ and JacMachineInterface.check_read_access(source)
359
+ ):
360
+ loc[anchor] = anchor.archetype
361
+ loc[source] = source.archetype
362
+ return list(loc.values())
266
363
 
267
364
  @staticmethod
268
365
  def edges_to_nodes(
269
- node: NodeAnchor,
270
- dir: EdgeDir,
271
- filter: Callable[[EdgeArchetype], bool] | None,
272
- target_obj: list[NodeArchetype] | None,
366
+ origin: list[NodeArchetype], destination: DataSpatialDestination
273
367
  ) -> list[NodeArchetype]:
274
368
  """Get set of nodes connected to this node."""
275
- ret_edges: list[NodeArchetype] = []
276
- for anchor in node.edges:
277
- if (
278
- (source := anchor.source)
279
- and (target := anchor.target)
280
- and (not filter or filter(anchor.archetype))
281
- and source.archetype
282
- and target.archetype
283
- ):
369
+ nodes: OrderedDict[NodeAnchor, NodeArchetype] = OrderedDict()
370
+ for node in origin:
371
+ nanch = node.__jac__
372
+ for anchor in nanch.edges:
284
373
  if (
285
- dir in [EdgeDir.OUT, EdgeDir.ANY]
286
- and node == source
287
- and (not target_obj or target.archetype in target_obj)
288
- and JacMachineInterface.check_read_access(target)
289
- ):
290
- ret_edges.append(target.archetype)
291
- if (
292
- dir in [EdgeDir.IN, EdgeDir.ANY]
293
- and node == target
294
- and (not target_obj or source.archetype in target_obj)
295
- and JacMachineInterface.check_read_access(source)
374
+ (source := anchor.source)
375
+ and (target := anchor.target)
376
+ and destination.edge_filter(anchor.archetype)
377
+ and source.archetype
378
+ and target.archetype
296
379
  ):
297
- ret_edges.append(source.archetype)
298
- return ret_edges
380
+ if (
381
+ destination.direction in [EdgeDir.OUT, EdgeDir.ANY]
382
+ and nanch == source
383
+ and destination.node_filter(target.archetype)
384
+ and JacMachineInterface.check_read_access(target)
385
+ ):
386
+ nodes[target] = target.archetype
387
+ if (
388
+ destination.direction in [EdgeDir.IN, EdgeDir.ANY]
389
+ and nanch == target
390
+ and destination.node_filter(source.archetype)
391
+ and JacMachineInterface.check_read_access(source)
392
+ ):
393
+ nodes[source] = source.archetype
394
+ return list(nodes.values())
299
395
 
300
396
  @staticmethod
301
397
  def remove_edge(node: NodeAnchor, edge: EdgeAnchor) -> None:
@@ -329,23 +425,27 @@ class JacWalker:
329
425
  | NodeArchetype
330
426
  | EdgeArchetype
331
427
  ),
428
+ insert_loc: int = -1,
332
429
  ) -> bool: # noqa: ANN401
333
430
  """Jac's visit stmt feature."""
334
431
  if isinstance(walker, WalkerArchetype):
335
432
  """Walker visits node."""
336
433
  wanch = walker.__jac__
337
434
  before_len = len(wanch.next)
435
+ next = []
338
436
  for anchor in (
339
437
  (i.__jac__ for i in expr) if isinstance(expr, list) else [expr.__jac__]
340
438
  ):
341
439
  if anchor not in wanch.ignores:
342
- if isinstance(anchor, NodeAnchor):
343
- wanch.next.append(anchor)
344
- elif isinstance(anchor, EdgeAnchor):
345
- if target := anchor.target:
346
- wanch.next.append(target)
347
- else:
348
- raise ValueError("Edge has no target.")
440
+ if isinstance(anchor, (NodeAnchor, EdgeAnchor)):
441
+ next.append(anchor)
442
+ else:
443
+ raise ValueError("Anchor should be NodeAnchor or EdgeAnchor.")
444
+ if insert_loc < -len(wanch.next): # for out of index selection
445
+ insert_loc = 0
446
+ elif insert_loc < 0:
447
+ insert_loc += len(wanch.next) + 1
448
+ wanch.next = wanch.next[:insert_loc] + next + wanch.next[insert_loc:]
349
449
  return len(wanch.next) > before_len
350
450
  else:
351
451
  raise TypeError("Invalid walker object")
@@ -381,83 +481,199 @@ class JacWalker:
381
481
  raise TypeError("Invalid walker object")
382
482
 
383
483
  @staticmethod
384
- def spawn_call(walker: WalkerAnchor, node: NodeAnchor) -> WalkerArchetype:
484
+ def spawn_call(
485
+ walker: WalkerAnchor,
486
+ node: NodeAnchor | EdgeAnchor,
487
+ ) -> WalkerArchetype:
488
+ """Jac's spawn operator feature."""
489
+ warch = walker.archetype
490
+ walker.path = []
491
+ current_loc = node.archetype
492
+
493
+ # walker ability on any entry
494
+ for i in warch._jac_entry_funcs_:
495
+ if not i.trigger:
496
+ i.func(warch, current_loc)
497
+ if walker.disengaged:
498
+ return warch
499
+
500
+ while len(walker.next):
501
+ if current_loc := walker.next.pop(0).archetype:
502
+ # walker ability with loc entry
503
+ for i in warch._jac_entry_funcs_:
504
+ if (
505
+ i.trigger
506
+ and (
507
+ all_issubclass(i.trigger, NodeArchetype)
508
+ or all_issubclass(i.trigger, EdgeArchetype)
509
+ )
510
+ and isinstance(current_loc, i.trigger)
511
+ ):
512
+ i.func(warch, current_loc)
513
+ if walker.disengaged:
514
+ return warch
515
+
516
+ # loc ability with any entry
517
+ for i in current_loc._jac_entry_funcs_:
518
+ if not i.trigger:
519
+ i.func(current_loc, warch)
520
+ if walker.disengaged:
521
+ return warch
522
+
523
+ # loc ability with walker entry
524
+ for i in current_loc._jac_entry_funcs_:
525
+ if (
526
+ i.trigger
527
+ and all_issubclass(i.trigger, WalkerArchetype)
528
+ and isinstance(warch, i.trigger)
529
+ ):
530
+ i.func(current_loc, warch)
531
+ if walker.disengaged:
532
+ return warch
533
+
534
+ # loc ability with walker exit
535
+ for i in current_loc._jac_exit_funcs_:
536
+ if (
537
+ i.trigger
538
+ and all_issubclass(i.trigger, WalkerArchetype)
539
+ and isinstance(warch, i.trigger)
540
+ ):
541
+ i.func(current_loc, warch)
542
+ if walker.disengaged:
543
+ return warch
544
+
545
+ # loc ability with any exit
546
+ for i in current_loc._jac_exit_funcs_:
547
+ if not i.trigger:
548
+ i.func(current_loc, warch)
549
+ if walker.disengaged:
550
+ return warch
551
+
552
+ # walker ability with loc exit
553
+ for i in warch._jac_exit_funcs_:
554
+ if (
555
+ i.trigger
556
+ and (
557
+ all_issubclass(i.trigger, NodeArchetype)
558
+ or all_issubclass(i.trigger, EdgeArchetype)
559
+ )
560
+ and isinstance(current_loc, i.trigger)
561
+ ):
562
+ i.func(warch, current_loc)
563
+ if walker.disengaged:
564
+ return warch
565
+ # walker ability with any exit
566
+ for i in warch._jac_exit_funcs_:
567
+ if not i.trigger:
568
+ i.func(warch, current_loc)
569
+ if walker.disengaged:
570
+ return warch
571
+
572
+ walker.ignores = []
573
+ return warch
574
+
575
+ @staticmethod
576
+ async def async_spawn_call(
577
+ walker: WalkerAnchor,
578
+ node: NodeAnchor | EdgeAnchor,
579
+ ) -> WalkerArchetype:
385
580
  """Jac's spawn operator feature."""
386
581
  warch = walker.archetype
387
582
  walker.path = []
388
- walker.next = [node]
389
- current_node = node.archetype
583
+ current_loc = node.archetype
390
584
 
391
- # walker entry
585
+ # walker ability on any entry
392
586
  for i in warch._jac_entry_funcs_:
393
587
  if not i.trigger:
394
- i.func(warch, current_node)
588
+ result = i.func(warch, current_loc)
589
+ if isinstance(result, Coroutine):
590
+ await result
395
591
  if walker.disengaged:
396
592
  return warch
397
593
 
398
594
  while len(walker.next):
399
- if current_node := walker.next.pop(0).archetype:
400
- # walker entry with
595
+ if current_loc := walker.next.pop(0).archetype:
596
+ # walker ability with loc entry
401
597
  for i in warch._jac_entry_funcs_:
402
598
  if (
403
599
  i.trigger
404
- and all_issubclass(i.trigger, NodeArchetype)
405
- and isinstance(current_node, i.trigger)
600
+ and (
601
+ all_issubclass(i.trigger, NodeArchetype)
602
+ or all_issubclass(i.trigger, EdgeArchetype)
603
+ )
604
+ and isinstance(current_loc, i.trigger)
406
605
  ):
407
- i.func(warch, current_node)
606
+ result = i.func(warch, current_loc)
607
+ if isinstance(result, Coroutine):
608
+ await result
408
609
  if walker.disengaged:
409
610
  return warch
410
611
 
411
- # node entry
412
- for i in current_node._jac_entry_funcs_:
612
+ # loc ability with any entry
613
+ for i in current_loc._jac_entry_funcs_:
413
614
  if not i.trigger:
414
- i.func(current_node, warch)
615
+ result = i.func(current_loc, warch)
616
+ if isinstance(result, Coroutine):
617
+ await result
415
618
  if walker.disengaged:
416
619
  return warch
417
620
 
418
- # node entry with
419
- for i in current_node._jac_entry_funcs_:
621
+ # loc ability with walker entry
622
+ for i in current_loc._jac_entry_funcs_:
420
623
  if (
421
624
  i.trigger
422
625
  and all_issubclass(i.trigger, WalkerArchetype)
423
626
  and isinstance(warch, i.trigger)
424
627
  ):
425
- i.func(current_node, warch)
628
+ result = i.func(current_loc, warch)
629
+ if isinstance(result, Coroutine):
630
+ await result
426
631
  if walker.disengaged:
427
632
  return warch
428
633
 
429
- # node exit with
430
- for i in current_node._jac_exit_funcs_:
634
+ # loc ability with walker exit
635
+ for i in current_loc._jac_exit_funcs_:
431
636
  if (
432
637
  i.trigger
433
638
  and all_issubclass(i.trigger, WalkerArchetype)
434
639
  and isinstance(warch, i.trigger)
435
640
  ):
436
- i.func(current_node, warch)
641
+ result = i.func(current_loc, warch)
642
+ if isinstance(result, Coroutine):
643
+ await result
437
644
  if walker.disengaged:
438
645
  return warch
439
646
 
440
- # node exit
441
- for i in current_node._jac_exit_funcs_:
647
+ # loc ability with any exit
648
+ for i in current_loc._jac_exit_funcs_:
442
649
  if not i.trigger:
443
- i.func(current_node, warch)
650
+ result = i.func(current_loc, warch)
651
+ if isinstance(result, Coroutine):
652
+ await result
444
653
  if walker.disengaged:
445
654
  return warch
446
655
 
447
- # walker exit with
656
+ # walker ability with loc exit
448
657
  for i in warch._jac_exit_funcs_:
449
658
  if (
450
659
  i.trigger
451
- and all_issubclass(i.trigger, NodeArchetype)
452
- and isinstance(current_node, i.trigger)
660
+ and (
661
+ all_issubclass(i.trigger, NodeArchetype)
662
+ or all_issubclass(i.trigger, EdgeArchetype)
663
+ )
664
+ and isinstance(current_loc, i.trigger)
453
665
  ):
454
- i.func(warch, current_node)
666
+ result = i.func(warch, current_loc)
667
+ if isinstance(result, Coroutine):
668
+ await result
455
669
  if walker.disengaged:
456
670
  return warch
457
- # walker exit
671
+ # walker ability with any exit
458
672
  for i in warch._jac_exit_funcs_:
459
673
  if not i.trigger:
460
- i.func(warch, current_node)
674
+ result = i.func(warch, current_loc)
675
+ if isinstance(result, Coroutine):
676
+ await result
461
677
  if walker.disengaged:
462
678
  return warch
463
679
 
@@ -465,14 +681,16 @@ class JacWalker:
465
681
  return warch
466
682
 
467
683
  @staticmethod
468
- def spawn(op1: Archetype, op2: Archetype) -> WalkerArchetype | asyncio.Future:
684
+ def spawn(op1: Archetype, op2: Archetype) -> WalkerArchetype | Coroutine:
469
685
  """Jac's spawn operator feature."""
686
+ edge: EdgeAnchor | None = None
470
687
  if isinstance(op1, WalkerArchetype):
471
688
  warch = op1
472
689
  walker = op1.__jac__
473
690
  if isinstance(op2, NodeArchetype):
474
691
  node = op2.__jac__
475
692
  elif isinstance(op2, EdgeArchetype):
693
+ edge = op2.__jac__
476
694
  node = op2.__jac__.target
477
695
  else:
478
696
  raise TypeError("Invalid target object")
@@ -482,21 +700,24 @@ class JacWalker:
482
700
  if isinstance(op1, NodeArchetype):
483
701
  node = op1.__jac__
484
702
  elif isinstance(op1, EdgeArchetype):
703
+ edge = op1.__jac__
485
704
  node = op1.__jac__.target
486
705
  else:
487
706
  raise TypeError("Invalid target object")
488
707
  else:
489
708
  raise TypeError("Invalid walker object")
490
709
 
710
+ if edge is not None:
711
+ loc: EdgeAnchor | NodeAnchor = edge
712
+ walker.next = [edge, node]
713
+ else:
714
+ loc = node
715
+ walker.next = [node]
716
+
491
717
  if warch.__jac_async__:
492
- machine = JacMachineInterface.py_get_jac_machine()
493
- _event_loop = machine._event_loop
494
- func = partial(JacMachineInterface.spawn_call, *(walker, node))
495
- return asyncio.ensure_future(
496
- _event_loop.run_in_executor(None, func), loop=_event_loop
497
- )
718
+ return JacMachineInterface.async_spawn_call(walker=walker, node=loc)
498
719
  else:
499
- return JacMachineInterface.spawn_call(walker=walker, node=node)
720
+ return JacMachineInterface.spawn_call(walker=walker, node=loc)
500
721
 
501
722
  @staticmethod
502
723
  def disengage(walker: WalkerArchetype) -> bool:
@@ -520,12 +741,14 @@ class JacClassReferences:
520
741
  Root: TypeAlias = _Root
521
742
  GenericEdge: TypeAlias = _GenericEdge
522
743
 
744
+ Path: TypeAlias = DataSpatialPath
745
+
523
746
 
524
747
  class JacBuiltin:
525
748
  """Jac Builtins."""
526
749
 
527
750
  @staticmethod
528
- def dotgen(
751
+ def printgraph(
529
752
  node: NodeArchetype,
530
753
  depth: int,
531
754
  traverse: bool,
@@ -533,9 +756,10 @@ class JacBuiltin:
533
756
  bfs: bool,
534
757
  edge_limit: int,
535
758
  node_limit: int,
536
- dot_file: Optional[str],
759
+ file: Optional[str],
760
+ format: str,
537
761
  ) -> str:
538
- """Generate Dot file for visualizing nodes and edges."""
762
+ """Generate graph for visualizing nodes and edges."""
539
763
  edge_type = edge_type if edge_type else []
540
764
  visited_nodes: list[NodeArchetype] = []
541
765
  node_depths: dict[NodeArchetype, int] = {node: 0}
@@ -589,24 +813,32 @@ class JacBuiltin:
589
813
  'digraph {\nnode [style="filled", shape="ellipse", '
590
814
  'fillcolor="invis", fontcolor="black"];\n'
591
815
  )
816
+ mermaid_content = "flowchart LR\n"
592
817
  for source, target, edge in connections:
593
818
  edge_label = html.escape(str(edge.__jac__.archetype))
594
819
  dot_content += (
595
820
  f"{visited_nodes.index(source)} -> {visited_nodes.index(target)} "
596
821
  f' [label="{edge_label if "GenericEdge" not in edge_label else ""}"];\n'
597
822
  )
823
+ mermaid_content += (
824
+ f"{visited_nodes.index(source)} -->"
825
+ f"|{edge_label if 'GenericEdge' not in edge_label else ''}| {visited_nodes.index(target)}\n"
826
+ )
598
827
  for node_ in visited_nodes:
599
828
  color = (
600
829
  colors[node_depths[node_]] if node_depths[node_] < 25 else colors[24]
601
830
  )
831
+ label = html.escape(str(node_.__jac__.archetype))
602
832
  dot_content += (
603
- f'{visited_nodes.index(node_)} [label="{html.escape(str(node_.__jac__.archetype))}"'
833
+ f'{visited_nodes.index(node_)} [label="{label}"'
604
834
  f'fillcolor="{color}"];\n'
605
835
  )
606
- if dot_file:
607
- with open(dot_file, "w") as f:
608
- f.write(dot_content + "}")
609
- return dot_content + "}"
836
+ mermaid_content += f'{visited_nodes.index(node_)}["{label}"]\n'
837
+ output = dot_content + "}" if format == "dot" else mermaid_content
838
+ if file:
839
+ with open(file, "w") as f:
840
+ f.write(output)
841
+ return output
610
842
 
611
843
 
612
844
  class JacCmd:
@@ -625,9 +857,9 @@ class JacBasics:
625
857
  """Set Class References."""
626
858
 
627
859
  @staticmethod
628
- def get_context() -> JacMachine:
860
+ def get_context() -> ExecutionContext:
629
861
  """Get current execution context."""
630
- return JacMachineInterface.py_get_jac_machine()
862
+ return JacMachine.exec_ctx
631
863
 
632
864
  @staticmethod
633
865
  def reset_graph(root: Optional[Root] = None) -> int:
@@ -728,54 +960,8 @@ class JacBasics:
728
960
 
729
961
  return decorator
730
962
 
731
- @staticmethod
732
- def py_get_jac_machine() -> JacMachine:
733
- """Get jac machine from python context."""
734
- machine = JacBasics.py_find_jac_machine()
735
- if not machine:
736
- raise RuntimeError("Jac machine not found in python context. ")
737
- return machine
738
-
739
- @staticmethod
740
- def py_find_jac_machine() -> Optional[JacMachine]:
741
- """Get jac machine from python context."""
742
- machine = None
743
- for i in inspect.stack():
744
- machine = i.frame.f_globals.get("__jac_mach__") or i.frame.f_locals.get(
745
- "__jac_mach__"
746
- )
747
- if machine:
748
- break
749
- return machine
750
-
751
- @staticmethod
752
- def py_jac_import(
753
- target: str,
754
- base_path: str,
755
- absorb: bool = False,
756
- mdl_alias: Optional[str] = None,
757
- override_name: Optional[str] = None,
758
- items: Optional[dict[str, Union[str, Optional[str]]]] = None,
759
- reload_module: Optional[bool] = False,
760
- ) -> tuple[types.ModuleType, ...]:
761
- """Core Import Process."""
762
- machine = JacBasics.py_find_jac_machine()
763
- if not machine:
764
- machine = JacMachine(base_path=base_path)
765
- return JacMachineInterface.jac_import(
766
- mach=machine,
767
- target=target,
768
- base_path=base_path,
769
- absorb=absorb,
770
- mdl_alias=mdl_alias,
771
- override_name=override_name,
772
- items=items,
773
- reload_module=reload_module,
774
- )
775
-
776
963
  @staticmethod
777
964
  def jac_import(
778
- mach: JacMachine,
779
965
  target: str,
780
966
  base_path: str,
781
967
  absorb: bool = False,
@@ -803,13 +989,13 @@ class JacBasics:
803
989
  items,
804
990
  )
805
991
 
806
- if not mach.jac_program:
807
- JacMachineInterface.attach_program(mach, JacProgram())
992
+ if not JacMachine.program:
993
+ JacMachineInterface.attach_program(JacProgram())
808
994
 
809
995
  if lng == "py":
810
- import_result = PythonImporter(mach).run_import(spec)
996
+ import_result = PythonImporter().run_import(spec)
811
997
  else:
812
- import_result = JacImporter(mach).run_import(spec, reload_module)
998
+ import_result = JacImporter().run_import(spec, reload_module)
813
999
 
814
1000
  return (
815
1001
  (import_result.ret_mod,)
@@ -833,7 +1019,6 @@ class JacBasics:
833
1019
 
834
1020
  @staticmethod
835
1021
  def run_test(
836
- mach: JacMachine,
837
1022
  filepath: str,
838
1023
  func_name: Optional[str] = None,
839
1024
  filter: Optional[str] = None,
@@ -853,9 +1038,7 @@ class JacBasics:
853
1038
  if mod_name.endswith(".test"):
854
1039
  mod_name = mod_name[:-5]
855
1040
  JacTestCheck.reset()
856
- JacMachineInterface.jac_import(
857
- mach=mach, target=mod_name, base_path=base
858
- )
1041
+ JacMachineInterface.jac_import(target=mod_name, base_path=base)
859
1042
  JacTestCheck.run_test(
860
1043
  xit, maxfail, verbose, os.path.abspath(filepath), func_name
861
1044
  )
@@ -884,7 +1067,7 @@ class JacBasics:
884
1067
  print(f"\n\n\t\t* Inside {root_dir}" + "/" + f"{file} *")
885
1068
  JacTestCheck.reset()
886
1069
  JacMachineInterface.jac_import(
887
- mach=mach, target=file[:-4], base_path=root_dir
1070
+ target=file[:-4], base_path=root_dir
888
1071
  )
889
1072
  JacTestCheck.run_test(
890
1073
  xit, maxfail, verbose, os.path.abspath(file), func_name
@@ -919,40 +1102,31 @@ class JacBasics:
919
1102
 
920
1103
  @staticmethod
921
1104
  def refs(
922
- sources: NodeArchetype | list[NodeArchetype],
923
- targets: NodeArchetype | list[NodeArchetype] | None = None,
924
- dir: EdgeDir = EdgeDir.OUT,
925
- filter: Callable[[EdgeArchetype], bool] | None = None,
926
- edges_only: bool = False,
927
- ) -> list[NodeArchetype] | list[EdgeArchetype]:
1105
+ path: DataSpatialPath | NodeArchetype | list[NodeArchetype],
1106
+ ) -> (
1107
+ list[NodeArchetype] | list[EdgeArchetype] | list[NodeArchetype | EdgeArchetype]
1108
+ ):
928
1109
  """Jac's apply_dir stmt feature."""
929
- if isinstance(sources, NodeArchetype):
930
- sources = [sources]
931
- targ_obj_set: Optional[list[NodeArchetype]] = (
932
- [targets]
933
- if isinstance(targets, NodeArchetype)
934
- else targets if targets else None
935
- )
936
- if edges_only:
937
- connected_edges: list[EdgeArchetype] = []
938
- for node in sources:
939
- edges = JacMachineInterface.get_edges(
940
- node.__jac__, dir, filter, target_obj=targ_obj_set
941
- )
942
- connected_edges.extend(
943
- edge for edge in edges if edge not in connected_edges
944
- )
945
- return connected_edges
1110
+ if not isinstance(path, DataSpatialPath):
1111
+ path = DataSpatialPath(path, [DataSpatialDestination(EdgeDir.OUT)])
1112
+
1113
+ origin = path.origin
1114
+
1115
+ if path.edge_only:
1116
+ destinations = path.destinations[:-1]
946
1117
  else:
947
- connected_nodes: list[NodeArchetype] = []
948
- for node in sources:
949
- nodes = JacMachineInterface.edges_to_nodes(
950
- node.__jac__, dir, filter, target_obj=targ_obj_set
951
- )
952
- connected_nodes.extend(
953
- node for node in nodes if node not in connected_nodes
1118
+ destinations = path.destinations
1119
+ while destinations:
1120
+ dest = path.destinations.pop(0)
1121
+ origin = JacMachineInterface.edges_to_nodes(origin, dest)
1122
+
1123
+ if path.edge_only:
1124
+ if path.from_visit:
1125
+ return JacMachineInterface.get_edges_with_node(
1126
+ origin, path.destinations[-1]
954
1127
  )
955
- return connected_nodes
1128
+ return JacMachineInterface.get_edges(origin, path.destinations[-1])
1129
+ return origin
956
1130
 
957
1131
  @staticmethod
958
1132
  def filter(
@@ -1052,7 +1226,7 @@ class JacBasics:
1052
1226
  @staticmethod
1053
1227
  def root() -> Root:
1054
1228
  """Jac's root getter."""
1055
- return JacMachineInterface.py_get_jac_machine().get_root()
1229
+ return JacMachine.get_context().get_root()
1056
1230
 
1057
1231
  @staticmethod
1058
1232
  def build_edge(
@@ -1166,6 +1340,40 @@ class JacBasics:
1166
1340
  "mtllm is not installed. Please install it with `pip install mtllm` and run `jac clean`."
1167
1341
  )
1168
1342
 
1343
+ @staticmethod
1344
+ def gen_llm_call_override(
1345
+ _pass: PyastGenPass, node: ast.FuncCall
1346
+ ) -> list[ast3.AST]:
1347
+ """Generate python ast nodes for llm function body override syntax.
1348
+
1349
+ example:
1350
+ foo() by llm();
1351
+ """
1352
+ _pass.log_warning(
1353
+ "MT-LLM is not installed. Please install it with `pip install mtllm`."
1354
+ )
1355
+ return [
1356
+ _pass.sync(
1357
+ ast3.Raise(
1358
+ _pass.sync(
1359
+ ast3.Call(
1360
+ func=_pass.sync(
1361
+ ast3.Name(id="ImportError", ctx=ast3.Load())
1362
+ ),
1363
+ args=[
1364
+ _pass.sync(
1365
+ ast3.Constant(
1366
+ value="mtllm is not installed. Please install it with `pip install mtllm` and run `jac clean`." # noqa: E501
1367
+ )
1368
+ )
1369
+ ],
1370
+ keywords=[],
1371
+ )
1372
+ )
1373
+ )
1374
+ )
1375
+ ]
1376
+
1169
1377
  @staticmethod
1170
1378
  def gen_llm_body(_pass: PyastGenPass, node: ast.Ability) -> list[ast3.AST]:
1171
1379
  """Generate the by LLM body."""
@@ -1303,32 +1511,49 @@ class JacBasics:
1303
1511
  "exclude_info": [],
1304
1512
  }
1305
1513
 
1514
+ @staticmethod
1515
+ def get_semstr_type(
1516
+ file_loc: str, scope: str, attr: str, return_semstr: bool
1517
+ ) -> Optional[str]:
1518
+ """Jac's get_semstr_type feature."""
1519
+
1520
+ @staticmethod
1521
+ def obj_scope(file_loc: str, attr: str) -> str:
1522
+ """Jac's gather_scope feature."""
1523
+ return ""
1524
+
1525
+ @staticmethod
1526
+ def get_sem_type(file_loc: str, attr: str) -> tuple[str | None, str | None]:
1527
+ """Jac's get_semstr_type implementation."""
1528
+ return None, None
1529
+
1306
1530
 
1307
1531
  class JacUtils:
1308
1532
  """Jac Machine Utilities."""
1309
1533
 
1310
1534
  @staticmethod
1311
- def attach_program(mach: JacMachine, jac_program: JacProgram) -> None:
1535
+ def attach_program(jac_program: JacProgram) -> None:
1312
1536
  """Attach a JacProgram to the machine."""
1313
- mach.jac_program = jac_program
1537
+ JacMachine.program = jac_program
1314
1538
 
1315
1539
  @staticmethod
1316
1540
  def load_module(
1317
- mach: JacMachine, module_name: str, module: types.ModuleType
1541
+ module_name: str, module: types.ModuleType, force: bool = False
1318
1542
  ) -> None:
1319
1543
  """Load a module into the machine."""
1320
- mach.loaded_modules[module_name] = module
1321
- sys.modules[module_name] = module # TODO: May want to nuke this one day
1544
+ if module_name not in JacMachine.loaded_modules or force:
1545
+ JacMachine.loaded_modules[module_name] = module
1546
+ sys.modules[module_name] = module # TODO: May want to nuke this one day
1322
1547
 
1323
1548
  @staticmethod
1324
- def list_modules(mach: JacMachine) -> list[str]:
1549
+ def list_modules() -> list[str]:
1325
1550
  """List all loaded modules."""
1326
- return list(mach.loaded_modules.keys())
1551
+ return list(JacMachine.loaded_modules.keys())
1327
1552
 
1328
1553
  @staticmethod
1329
- def list_walkers(mach: JacMachine, module_name: str) -> list[str]:
1554
+ def list_walkers(module_name: str) -> list[str]:
1330
1555
  """List all walkers in a specific module."""
1331
- module = mach.loaded_modules.get(module_name)
1556
+ module = JacMachine.loaded_modules.get(module_name)
1332
1557
  if module:
1333
1558
  walkers = []
1334
1559
  for name, obj in inspect.getmembers(module):
@@ -1338,9 +1563,9 @@ class JacUtils:
1338
1563
  return []
1339
1564
 
1340
1565
  @staticmethod
1341
- def list_nodes(mach: JacMachine, module_name: str) -> list[str]:
1566
+ def list_nodes(module_name: str) -> list[str]:
1342
1567
  """List all nodes in a specific module."""
1343
- module = mach.loaded_modules.get(module_name)
1568
+ module = JacMachine.loaded_modules.get(module_name)
1344
1569
  if module:
1345
1570
  nodes = []
1346
1571
  for name, obj in inspect.getmembers(module):
@@ -1350,9 +1575,9 @@ class JacUtils:
1350
1575
  return []
1351
1576
 
1352
1577
  @staticmethod
1353
- def list_edges(mach: JacMachine, module_name: str) -> list[str]:
1578
+ def list_edges(module_name: str) -> list[str]:
1354
1579
  """List all edges in a specific module."""
1355
- module = mach.loaded_modules.get(module_name)
1580
+ module = JacMachine.loaded_modules.get(module_name)
1356
1581
  if module:
1357
1582
  nodes = []
1358
1583
  for name, obj in inspect.getmembers(module):
@@ -1363,7 +1588,6 @@ class JacUtils:
1363
1588
 
1364
1589
  @staticmethod
1365
1590
  def create_archetype_from_source(
1366
- mach: JacMachine,
1367
1591
  source_code: str,
1368
1592
  module_name: Optional[str] = None,
1369
1593
  base_path: Optional[str] = None,
@@ -1374,12 +1598,12 @@ class JacUtils:
1374
1598
  from jaclang.runtimelib.importer import JacImporter, ImportPathSpec
1375
1599
 
1376
1600
  if not base_path:
1377
- base_path = mach.base_path or os.getcwd()
1601
+ base_path = JacMachine.base_path_dir or os.getcwd()
1378
1602
 
1379
1603
  if base_path and not os.path.exists(base_path):
1380
1604
  os.makedirs(base_path)
1381
1605
  if not module_name:
1382
- module_name = f"_dynamic_module_{len(mach.loaded_modules)}"
1606
+ module_name = f"_dynamic_module_{len(JacMachine.loaded_modules)}"
1383
1607
  with tempfile.NamedTemporaryFile(
1384
1608
  mode="w",
1385
1609
  suffix=".jac",
@@ -1391,7 +1615,7 @@ class JacUtils:
1391
1615
  tmp_file.write(source_code)
1392
1616
 
1393
1617
  try:
1394
- importer = JacImporter(mach)
1618
+ importer = JacImporter()
1395
1619
  tmp_file_basename = os.path.basename(tmp_file_path)
1396
1620
  tmp_module_name, _ = os.path.splitext(tmp_file_basename)
1397
1621
 
@@ -1408,7 +1632,7 @@ class JacUtils:
1408
1632
  import_result = importer.run_import(spec, reload=False)
1409
1633
  module = import_result.ret_mod
1410
1634
 
1411
- mach.loaded_modules[module_name] = module
1635
+ JacMachine.loaded_modules[module_name] = module
1412
1636
  return module
1413
1637
  except Exception as e:
1414
1638
  logger.error(f"Error importing dynamic module '{module_name}': {e}")
@@ -1419,20 +1643,19 @@ class JacUtils:
1419
1643
 
1420
1644
  @staticmethod
1421
1645
  def update_walker(
1422
- mach: JacMachine,
1423
1646
  module_name: str,
1424
1647
  items: Optional[dict[str, Union[str, Optional[str]]]],
1425
1648
  ) -> tuple[types.ModuleType, ...]:
1426
1649
  """Reimport the module."""
1427
1650
  from .importer import JacImporter, ImportPathSpec
1428
1651
 
1429
- if module_name in mach.loaded_modules:
1652
+ if module_name in JacMachine.loaded_modules:
1430
1653
  try:
1431
- old_module = mach.loaded_modules[module_name]
1432
- importer = JacImporter(mach)
1654
+ old_module = JacMachine.loaded_modules[module_name]
1655
+ importer = JacImporter()
1433
1656
  spec = ImportPathSpec(
1434
1657
  target=module_name,
1435
- base_path=mach.base_path,
1658
+ base_path=JacMachine.base_path_dir,
1436
1659
  absorb=False,
1437
1660
  mdl_alias=None,
1438
1661
  override_name=None,
@@ -1461,13 +1684,12 @@ class JacUtils:
1461
1684
 
1462
1685
  @staticmethod
1463
1686
  def spawn_node(
1464
- mach: JacMachine,
1465
1687
  node_name: str,
1466
1688
  attributes: Optional[dict] = None,
1467
1689
  module_name: str = "__main__",
1468
1690
  ) -> NodeArchetype:
1469
1691
  """Spawn a node instance of the given node_name with attributes."""
1470
- node_class = JacMachineInterface.get_archetype(mach, module_name, node_name)
1692
+ node_class = JacMachineInterface.get_archetype(module_name, node_name)
1471
1693
  if isinstance(node_class, type) and issubclass(node_class, NodeArchetype):
1472
1694
  if attributes is None:
1473
1695
  attributes = {}
@@ -1478,13 +1700,12 @@ class JacUtils:
1478
1700
 
1479
1701
  @staticmethod
1480
1702
  def spawn_walker(
1481
- mach: JacMachine,
1482
1703
  walker_name: str,
1483
1704
  attributes: Optional[dict] = None,
1484
1705
  module_name: str = "__main__",
1485
1706
  ) -> WalkerArchetype:
1486
1707
  """Spawn a walker instance of the given walker_name."""
1487
- walker_class = JacMachineInterface.get_archetype(mach, module_name, walker_name)
1708
+ walker_class = JacMachineInterface.get_archetype(module_name, walker_name)
1488
1709
  if isinstance(walker_class, type) and issubclass(walker_class, WalkerArchetype):
1489
1710
  if attributes is None:
1490
1711
  attributes = {}
@@ -1494,27 +1715,17 @@ class JacUtils:
1494
1715
  raise ValueError(f"Walker {walker_name} not found.")
1495
1716
 
1496
1717
  @staticmethod
1497
- def get_archetype(
1498
- mach: JacMachine, module_name: str, archetype_name: str
1499
- ) -> Optional[Archetype]:
1718
+ def get_archetype(module_name: str, archetype_name: str) -> Optional[Archetype]:
1500
1719
  """Retrieve an archetype class from a module."""
1501
- module = mach.loaded_modules.get(module_name)
1720
+ module = JacMachine.loaded_modules.get(module_name)
1502
1721
  if module:
1503
1722
  return getattr(module, archetype_name, None)
1504
1723
  return None
1505
1724
 
1506
- @staticmethod
1507
- def await_obj(obj: Any) -> Any: # noqa: ANN401
1508
- """Await an object if it is a coroutine or async or future function."""
1509
- machine = JacMachineInterface.py_get_jac_machine()
1510
- _event_loop = machine._event_loop
1511
- return _event_loop.run_until_complete(obj)
1512
-
1513
1725
  @staticmethod
1514
1726
  def thread_run(func: Callable, *args: object) -> Future: # noqa: ANN401
1515
1727
  """Run a function in a thread."""
1516
- machine = JacMachine.py_get_jac_machine()
1517
- _executor = machine.pool
1728
+ _executor = JacMachine.pool
1518
1729
  return _executor.submit(func, *args)
1519
1730
 
1520
1731
  @staticmethod
@@ -1540,82 +1751,35 @@ class JacMachineInterface(
1540
1751
  class JacMachine(JacMachineInterface):
1541
1752
  """Jac Machine State."""
1542
1753
 
1543
- def __init__(
1544
- self,
1545
- base_path: str = "",
1546
- session: Optional[str] = None,
1547
- root: Optional[str] = None,
1548
- interp_mode: bool = False,
1549
- ) -> None:
1550
- """Initialize JacMachine."""
1551
- self.loaded_modules: dict[str, types.ModuleType] = {}
1552
- if not base_path:
1553
- base_path = os.getcwd()
1554
- # Ensure the base_path is a list rather than a string
1555
- self.base_path = base_path
1556
- self.base_path_dir = (
1557
- os.path.dirname(base_path)
1558
- if not os.path.isdir(base_path)
1559
- else os.path.abspath(base_path)
1560
- )
1561
- self.jac_program: JacProgram = JacProgram()
1562
- self.interp_mode = interp_mode
1563
- self.pool = ThreadPoolExecutor()
1564
- self._event_loop = asyncio.new_event_loop()
1565
- self.mem: Memory = ShelfStorage(session)
1566
- self.reports: list[Any] = []
1567
- sr_arch = Root()
1568
- sr_anch = sr_arch.__jac__
1569
- sr_anch.id = UUID(Con.SUPER_ROOT_UUID)
1570
- sr_anch.persistent = False
1571
- self.system_root = sr_anch
1572
- self.custom: Any = MISSING
1573
- if not isinstance(
1574
- system_root := self.mem.find_by_id(UUID(Con.SUPER_ROOT_UUID)), NodeAnchor
1575
- ):
1576
- system_root = cast(NodeAnchor, Root().__jac__) # type: ignore[attr-defined]
1577
- system_root.id = UUID(Con.SUPER_ROOT_UUID)
1578
- self.mem.set(system_root.id, system_root)
1579
-
1580
- self.system_root = system_root
1581
-
1582
- self.entry_node = self.root_state = self.init_anchor(root, self.system_root)
1583
-
1584
- def init_anchor(
1585
- self,
1586
- anchor_id: str | None,
1587
- default: NodeAnchor,
1588
- ) -> NodeAnchor:
1589
- """Load initial anchors."""
1590
- if anchor_id:
1591
- if isinstance(anchor := self.mem.find_by_id(UUID(anchor_id)), NodeAnchor):
1592
- return anchor
1593
- raise ValueError(f"Invalid anchor id {anchor_id} !")
1594
- return default
1754
+ loaded_modules: dict[str, types.ModuleType] = {}
1755
+ base_path_dir: str = os.getcwd()
1756
+ program: JacProgram = JacProgram()
1757
+ pool: ThreadPoolExecutor = ThreadPoolExecutor()
1758
+ exec_ctx: ExecutionContext = ExecutionContext()
1595
1759
 
1596
- def set_entry_node(self, entry_node: str | None) -> None:
1597
- """Override entry."""
1598
- self.entry_node = self.init_anchor(entry_node, self.root_state)
1599
-
1600
- def close(self) -> None:
1601
- """Close current ExecutionContext."""
1602
- call_jac_func_with_machine(mach=self, func=self.mem.close)
1603
-
1604
- def get_root(self) -> Root:
1605
- """Get current root."""
1606
- return cast(Root, self.root_state.archetype)
1607
-
1608
- def global_system_root(self) -> NodeAnchor:
1609
- """Get global system root."""
1610
- return self.system_root
1760
+ @staticmethod
1761
+ def set_base_path(base_path: str) -> None:
1762
+ """Set the base path for the machine."""
1763
+ JacMachine.reset_machine()
1764
+ JacMachine.base_path_dir = (
1765
+ base_path if os.path.isdir(base_path) else os.path.dirname(base_path)
1766
+ )
1611
1767
 
1768
+ @staticmethod
1769
+ def set_context(context: ExecutionContext) -> None:
1770
+ """Set the context for the machine."""
1771
+ JacMachine.exec_ctx = context
1612
1772
 
1613
- def call_jac_func_with_machine( # TODO: remove this
1614
- mach: JacMachine, func: Callable, *args: Any # noqa: ANN401
1615
- ) -> Any: # noqa: ANN401
1616
- """Call Jac function with machine context in local."""
1617
- __jac_mach__ = mach # noqa: F841
1618
- return func(*args)
1773
+ @staticmethod
1774
+ def reset_machine() -> None:
1775
+ """Reset the machine."""
1776
+ # for i in JacMachine.loaded_modules.values():
1777
+ # sys.modules.pop(i.__name__, None)
1778
+ JacMachine.loaded_modules.clear()
1779
+ JacMachine.base_path_dir = os.getcwd()
1780
+ JacMachine.program = JacProgram()
1781
+ JacMachine.pool = ThreadPoolExecutor()
1782
+ JacMachine.exec_ctx = ExecutionContext()
1619
1783
 
1620
1784
 
1621
1785
  def generate_plugin_helpers(