jaclang 0.8.5__py3-none-any.whl → 0.8.7__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 (70) hide show
  1. jaclang/cli/cli.md +4 -3
  2. jaclang/cli/cli.py +63 -29
  3. jaclang/cli/cmdreg.py +1 -140
  4. jaclang/compiler/passes/main/__init__.py +2 -0
  5. jaclang/compiler/passes/main/cfg_build_pass.py +21 -1
  6. jaclang/compiler/passes/main/inheritance_pass.py +8 -1
  7. jaclang/compiler/passes/main/pyast_gen_pass.py +70 -11
  8. jaclang/compiler/passes/main/pyast_load_pass.py +14 -20
  9. jaclang/compiler/passes/main/sym_tab_build_pass.py +4 -0
  10. jaclang/compiler/passes/main/tests/fixtures/cfg_has_var.jac +12 -0
  11. jaclang/compiler/passes/main/tests/fixtures/cfg_if_no_else.jac +11 -0
  12. jaclang/compiler/passes/main/tests/fixtures/cfg_return.jac +9 -0
  13. jaclang/compiler/passes/main/tests/fixtures/checker_binary_op.jac +21 -0
  14. jaclang/compiler/passes/main/tests/fixtures/checker_call_expr_class.jac +12 -0
  15. jaclang/compiler/passes/main/tests/fixtures/checker_cyclic_symbol.jac +4 -0
  16. jaclang/compiler/passes/main/tests/fixtures/checker_expr_call.jac +9 -0
  17. jaclang/compiler/passes/main/tests/fixtures/checker_import_missing_module.jac +13 -0
  18. jaclang/compiler/passes/main/tests/fixtures/checker_imported.jac +2 -0
  19. jaclang/compiler/passes/main/tests/fixtures/checker_importer.jac +6 -0
  20. jaclang/compiler/passes/main/tests/fixtures/checker_magic_call.jac +17 -0
  21. jaclang/compiler/passes/main/tests/fixtures/checker_mod_path.jac +8 -0
  22. jaclang/compiler/passes/main/tests/fixtures/data_spatial_types.jac +1 -1
  23. jaclang/compiler/passes/main/tests/fixtures/import_symbol_type_infer.jac +11 -0
  24. jaclang/compiler/passes/main/tests/fixtures/infer_type_assignment.jac +5 -0
  25. jaclang/compiler/passes/main/tests/fixtures/member_access_type_inferred.jac +13 -0
  26. jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +11 -0
  27. jaclang/compiler/passes/main/tests/fixtures/type_annotation_assignment.jac +8 -0
  28. jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +62 -24
  29. jaclang/compiler/passes/main/tests/test_checker_pass.py +161 -0
  30. jaclang/compiler/passes/main/type_checker_pass.py +147 -0
  31. jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +1 -4
  32. jaclang/compiler/program.py +17 -3
  33. jaclang/compiler/type_system/__init__.py +1 -0
  34. jaclang/compiler/type_system/operations.py +104 -0
  35. jaclang/compiler/type_system/type_evaluator.py +560 -0
  36. jaclang/compiler/type_system/type_utils.py +41 -0
  37. jaclang/compiler/type_system/types.py +240 -0
  38. jaclang/compiler/unitree.py +15 -9
  39. jaclang/langserve/dev_engine.jac +645 -0
  40. jaclang/langserve/dev_server.jac +201 -0
  41. jaclang/langserve/engine.jac +135 -91
  42. jaclang/langserve/server.jac +21 -14
  43. jaclang/langserve/tests/server_test/test_lang_serve.py +2 -5
  44. jaclang/langserve/tests/test_dev_server.py +80 -0
  45. jaclang/langserve/tests/test_server.py +9 -2
  46. jaclang/langserve/utils.jac +44 -48
  47. jaclang/runtimelib/builtin.py +28 -39
  48. jaclang/runtimelib/importer.py +1 -1
  49. jaclang/runtimelib/machine.py +48 -64
  50. jaclang/runtimelib/memory.py +23 -5
  51. jaclang/runtimelib/tests/fixtures/savable_object.jac +10 -2
  52. jaclang/runtimelib/utils.py +13 -6
  53. jaclang/tests/fixtures/edge_node_walk.jac +1 -1
  54. jaclang/tests/fixtures/edges_walk.jac +1 -1
  55. jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
  56. jaclang/tests/fixtures/jac_run_py_bugs.py +18 -0
  57. jaclang/tests/fixtures/jac_run_py_import.py +13 -0
  58. jaclang/tests/fixtures/lambda_arg_annotation.jac +15 -0
  59. jaclang/tests/fixtures/lambda_self.jac +18 -0
  60. jaclang/tests/fixtures/py_run.jac +8 -0
  61. jaclang/tests/fixtures/py_run.py +23 -0
  62. jaclang/tests/fixtures/pyfunc.py +2 -0
  63. jaclang/tests/test_cli.py +103 -14
  64. jaclang/tests/test_language.py +10 -4
  65. jaclang/utils/lang_tools.py +3 -0
  66. jaclang/utils/module_resolver.py +1 -1
  67. {jaclang-0.8.5.dist-info → jaclang-0.8.7.dist-info}/METADATA +4 -2
  68. {jaclang-0.8.5.dist-info → jaclang-0.8.7.dist-info}/RECORD +70 -37
  69. {jaclang-0.8.5.dist-info → jaclang-0.8.7.dist-info}/WHEEL +1 -1
  70. {jaclang-0.8.5.dist-info → jaclang-0.8.7.dist-info}/entry_points.txt +0 -0
@@ -16,7 +16,6 @@ from lsprotocol.types import (
16
16
  from jaclang.langserve.tests.server_test.utils import (
17
17
  create_temp_jac_file,
18
18
  get_code,
19
- get_jac_file_path,
20
19
  get_simple_code,
21
20
  create_ls_with_workspace, # new helper
22
21
  )
@@ -26,8 +25,6 @@ from jaclang import JacMachineInterface as _
26
25
  from jaclang.langserve.engine import JacLangServer
27
26
  from jaclang.langserve.server import did_open, did_save, did_change, formatting
28
27
 
29
- JAC_FILE = get_jac_file_path()
30
-
31
28
 
32
29
  class TestLangServe:
33
30
  """Test class for Jac language server features."""
@@ -190,7 +187,7 @@ class TestLangServe:
190
187
  await did_save(ls, params)
191
188
  sem_tokens = ls.get_semantic_tokens(uri)
192
189
  # semantic tokens should still be present even if there is a syntax error
193
- assert len(sem_tokens.data) == 300
190
+ assert len(sem_tokens.data) == 340
194
191
  diagnostics = ls.diagnostics.get(uri, [])
195
192
  assert isinstance(diagnostics, list)
196
193
  assert len(diagnostics) == 1
@@ -254,7 +251,7 @@ class TestLangServe:
254
251
  await did_change(ls, params)
255
252
  sem_tokens = ls.get_semantic_tokens(uri)
256
253
  # semantic tokens should still be present even if there is a syntax error
257
- assert len(sem_tokens.data) == 300
254
+ assert len(sem_tokens.data) == 340
258
255
  diagnostics = ls.diagnostics.get(uri, [])
259
256
  assert isinstance(diagnostics, list)
260
257
  assert len(diagnostics) == 1
@@ -0,0 +1,80 @@
1
+ from jaclang.utils.test import TestCase
2
+ from jaclang.vendor.pygls import uris
3
+ from jaclang.vendor.pygls.workspace import Workspace
4
+
5
+ import lsprotocol.types as lspt
6
+ from jaclang.langserve.engine import JacLangServer
7
+
8
+
9
+ class TestJacLangServer(TestCase):
10
+
11
+ def test_type_annotation_assignment_server(self) -> None:
12
+ """Test that the server doesn't run if there is a syntax error."""
13
+ lsp = JacLangServer()
14
+ workspace_path = self.fixture_abs_path("")
15
+ workspace = Workspace(workspace_path, lsp)
16
+ lsp.lsp._workspace = workspace
17
+ circle_file = uris.from_fs_path(
18
+ self.fixture_abs_path(
19
+ "../../../../jaclang/compiler/passes/main/tests/fixtures/type_annotation_assignment.jac"
20
+ )
21
+ )
22
+ lsp.deep_check(circle_file)
23
+ self.assertIn(
24
+ "(public variable) should_fail1: int",
25
+ lsp.get_hover_info(circle_file, lspt.Position(1, 15)).contents.value,
26
+ )
27
+ self.assertIn(
28
+ "(public variable) should_pass2: str",
29
+ lsp.get_hover_info(circle_file, lspt.Position(2, 15)).contents.value,
30
+ )
31
+ self.assertIn(
32
+ "(public variable) should_fail2: str",
33
+ lsp.get_hover_info(circle_file, lspt.Position(3, 15)).contents.value,
34
+ )
35
+ diagnostics_list = list(lsp.diagnostics.values())[0]
36
+ self.assertEqual(len(diagnostics_list), 2)
37
+ self.assertIn(
38
+ "Cannot assign",
39
+ diagnostics_list[0].message,
40
+ )
41
+ self.assertEqual(
42
+ "1:5-1:30",
43
+ str(diagnostics_list[0].range),
44
+ )
45
+ self.assertEqual(
46
+ "3:5-3:27",
47
+ str(diagnostics_list[1].range),
48
+ )
49
+
50
+
51
+ def test_member_access_type_inferred_server(self) -> None:
52
+ """Test that the server doesn't run if there is a syntax error."""
53
+ lsp = JacLangServer()
54
+ workspace_path = self.fixture_abs_path("")
55
+ workspace = Workspace(workspace_path, lsp)
56
+ lsp.lsp._workspace = workspace
57
+ circle_file = uris.from_fs_path(
58
+ self.fixture_abs_path(
59
+ "../../../../jaclang/compiler/passes/main/tests/fixtures/member_access_type_inferred.jac"
60
+ )
61
+ )
62
+ lsp.deep_check(circle_file)
63
+ self.assertIn(
64
+ "(public variable) i: int",
65
+ lsp.get_hover_info(circle_file, lspt.Position(10, 2)).contents.value,
66
+ )
67
+ self.assertIn(
68
+ "(public variable) s: str",
69
+ lsp.get_hover_info(circle_file, lspt.Position(11, 2)).contents.value,
70
+ )
71
+ diagnostics_list = list(lsp.diagnostics.values())[0]
72
+ self.assertEqual(len(diagnostics_list), 1)
73
+ self.assertIn(
74
+ "Cannot assign",
75
+ diagnostics_list[0].message,
76
+ )
77
+ self.assertEqual(
78
+ "11:2-11:12",
79
+ str(diagnostics_list[0].range),
80
+ )
@@ -127,6 +127,9 @@ class TestJacLangServer(TestCase):
127
127
  str(lsp.get_definition(circle_file, lspt.Position(20, 16))),
128
128
  )
129
129
 
130
+ @pytest.mark.xfail(
131
+ reason="Incorrect handling of 'self' in DefUsePass.TODO: Fix chain_use_lookup."
132
+ )
130
133
  def test_go_to_definition_method(self) -> None:
131
134
  """Test that the go to definition is correct."""
132
135
  lsp = JacLangServer()
@@ -595,9 +598,13 @@ class TestJacLangServer(TestCase):
595
598
  workspace_path = self.fixture_abs_path("")
596
599
  workspace = Workspace(workspace_path, lsp)
597
600
  lsp.lsp._workspace = workspace
598
- guess_game_file = uris.from_fs_path(self.fixture_abs_path('../../../compiler/passes/main/tests/fixtures/sym_binder.jac'))
601
+ guess_game_file = uris.from_fs_path(
602
+ self.fixture_abs_path(
603
+ "../../../compiler/passes/main/tests/fixtures/sym_binder.jac"
604
+ )
605
+ )
599
606
  lsp.deep_check(guess_game_file)
600
607
  self.assertIn(
601
608
  "/tests/fixtures/M1.jac:0:0-0:0",
602
609
  str(lsp.get_definition(guess_game_file, lspt.Position(29, 9))),
603
- )
610
+ )
@@ -31,18 +31,18 @@ glob T = TypeVar('T', bound=Callable[(P, Coroutine[(Any, Any, Any)])]);
31
31
  """Return diagnostics."""
32
32
  def gen_diagnostics(
33
33
  from_path: str,
34
- errors: <>list[Alert],
35
- warnings: <>list[Alert]
36
- ) -> <>list[lspt.Diagnostic] {
34
+ errors: list[Alert],
35
+ warnings: list[Alert]
36
+ ) -> list[lspt.Diagnostic] {
37
37
  return [ lspt.Diagnostic(
38
38
  range=create_range(error.loc),
39
39
  message=error.msg,
40
40
  severity=lspt.DiagnosticSeverity.Error
41
- ) for error in errors if error.loc.mod_path == uris.to_fs_path(from_path) ] + [ lspt.Diagnostic(
41
+ ) for error in errors if error.loc.mod_path == from_path ] + [ lspt.Diagnostic(
42
42
  range=create_range(warning.loc),
43
43
  message=warning.msg,
44
44
  severity=lspt.DiagnosticSeverity.Warning
45
- ) for warning in warnings if warning.loc.mod_path == uris.to_fs_path(from_path) ];
45
+ ) for warning in warnings if warning.loc.mod_path == from_path ];
46
46
  }
47
47
 
48
48
 
@@ -75,7 +75,7 @@ def debounce(<>wait: float) -> Callable[[T], Callable[(..., Awaitable[None])]] {
75
75
 
76
76
 
77
77
  """Iterate through symbol table."""
78
- def sym_tab_list(sym_tab: UniScopeNode, file_path: str) -> <>list[UniScopeNode] {
78
+ def sym_tab_list(sym_tab: UniScopeNode, file_path: str) -> list[UniScopeNode] {
79
79
  sym_tabs = [sym_tab]
80
80
  if not (isinstance(sym_tab, uni.Module) and sym_tab.loc.mod_path != file_path )
81
81
  else []
@@ -135,7 +135,7 @@ def position_within_node(<>node: uni.UniNode, line: int, character: int) -> bool
135
135
 
136
136
 
137
137
  """Find index."""
138
- def find_index(sem_tokens: <>list[int], line: int, char: int) -> Optional[int] {
138
+ def find_index(sem_tokens: list[int], line: int, char: int) -> Optional[int] {
139
139
  index = None;
140
140
  token_start_list = [ get_token_start(i, sem_tokens) for i in range(0, len(sem_tokens), 5) ];
141
141
  for (i, j) in enumerate(token_start_list) {
@@ -148,7 +148,7 @@ def find_index(sem_tokens: <>list[int], line: int, char: int) -> Optional[int] {
148
148
 
149
149
 
150
150
  """Recursively collect symbols from the AST."""
151
- def get_symbols_for_outline(<>node: UniScopeNode) -> <>list[lspt.DocumentSymbol] {
151
+ def get_symbols_for_outline(<>node: UniScopeNode) -> list[lspt.DocumentSymbol] {
152
152
  symbols = [];
153
153
  for (key, item) in <>node.names_in_scope.items() {
154
154
  if key in dir(builtins)
@@ -234,34 +234,30 @@ def kind_map(sub_tab: uni.UniNode) -> lspt.SymbolKind {
234
234
 
235
235
  """Map the symbol node to an lspt.CompletionItemKind."""
236
236
  def label_map(sub_tab: SymbolType) -> lspt.CompletionItemKind {
237
- return lspt.CompletionItemKind.Function
238
- if sub_tab in [SymbolType.ABILITY, SymbolType.TEST]
239
- else lspt.CompletionItemKind.Class
240
- if sub_tab in [SymbolType.OBJECT_ARCH,
241
- SymbolType.NODE_ARCH,
242
- SymbolType.EDGE_ARCH,
243
- SymbolType.WALKER_ARCH]
244
- else lspt.CompletionItemKind.Module
245
- if sub_tab == SymbolType.MODULE
246
- else lspt.CompletionItemKind.Enum
247
- if sub_tab == SymbolType.ENUM_ARCH
248
- else lspt.CompletionItemKind.Field
249
- if sub_tab == SymbolType.HAS_VAR
250
- else lspt.CompletionItemKind.Method
251
- if sub_tab == SymbolType.METHOD
252
- else lspt.CompletionItemKind.EnumMember
253
- if sub_tab == SymbolType.ENUM_MEMBER
254
- else lspt.CompletionItemKind.Interface
255
- if sub_tab == SymbolType.IMPL
256
- else lspt.CompletionItemKind.Variable
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
- ;
237
+ return (
238
+ lspt.CompletionItemKind.Function
239
+ if sub_tab in [SymbolType.ABILITY, SymbolType.TEST]
240
+ else lspt.CompletionItemKind.Class
241
+ if sub_tab in [
242
+ SymbolType.OBJECT_ARCH,
243
+ SymbolType.NODE_ARCH,
244
+ SymbolType.EDGE_ARCH,
245
+ SymbolType.WALKER_ARCH
246
+ ]
247
+ else lspt.CompletionItemKind.Module
248
+ if sub_tab == SymbolType.MODULE
249
+ else lspt.CompletionItemKind.Enum
250
+ if sub_tab == SymbolType.ENUM_ARCH
251
+ else lspt.CompletionItemKind.Field
252
+ if sub_tab == SymbolType.HAS_VAR
253
+ else lspt.CompletionItemKind.Method
254
+ if sub_tab == SymbolType.METHOD
255
+ else lspt.CompletionItemKind.EnumMember
256
+ if sub_tab == SymbolType.ENUM_MEMBER
257
+ else lspt.CompletionItemKind.Interface
258
+ if sub_tab == SymbolType.IMPL
259
+ else lspt.CompletionItemKind.Variable
260
+ );
265
261
  }
266
262
 
267
263
 
@@ -269,9 +265,9 @@ def label_map(sub_tab: SymbolType) -> lspt.CompletionItemKind {
269
265
  def collect_all_symbols_in_scope(
270
266
  sym_tab: UniScopeNode,
271
267
  up_tree: bool = True
272
- ) -> <>list[lspt.CompletionItem] {
268
+ ) -> list[lspt.CompletionItem] {
273
269
  symbols = [];
274
- visited = <>set();
270
+ visited = set();
275
271
  current_tab : Optional[UniScopeNode] = sym_tab;
276
272
  while current_tab is not None and current_tab not in visited {
277
273
  visited.add(current_tab);
@@ -295,8 +291,8 @@ def collect_all_symbols_in_scope(
295
291
 
296
292
 
297
293
  """Return all child tab's as completion items."""
298
- def collect_child_tabs(sym_tab: UniScopeNode) -> <>list[lspt.CompletionItem] {
299
- symbols : <>list[lspt.CompletionItem] = [];
294
+ def collect_child_tabs(sym_tab: UniScopeNode) -> list[lspt.CompletionItem] {
295
+ symbols : list[lspt.CompletionItem] = [];
300
296
  for tab in sym_tab.kid_scope {
301
297
  if tab.scope_name not in [ i.label for i in symbols ] {
302
298
  symbols.append(
@@ -312,7 +308,7 @@ def collect_child_tabs(sym_tab: UniScopeNode) -> <>list[lspt.CompletionItem] {
312
308
 
313
309
 
314
310
  """Parse text and return a list of symbols."""
315
- def parse_symbol_path(text: str, dot_position: int) -> <>list[str] {
311
+ def parse_symbol_path(text: str, dot_position: int) -> list[str] {
316
312
  text = text[ : dot_position ][ : -1 ].strip();
317
313
  valid_character_pattern = re.compile('[a-zA-Z0-9_]');
318
314
  reversed_text = text[ : : -1 ];
@@ -342,8 +338,8 @@ def parse_symbol_path(text: str, dot_position: int) -> <>list[str] {
342
338
  """Return the starting position of a token."""
343
339
  def get_token_start(
344
340
  token_index: (int | None),
345
- sem_tokens: <>list[int]
346
- ) -> <>tuple[int, int, int] {
341
+ sem_tokens: list[int]
342
+ ) -> tuple[int, int, int] {
347
343
  if token_index is None or token_index >= len(sem_tokens) {
348
344
  return (0, 0, 0);
349
345
  }
@@ -382,8 +378,8 @@ def find_surrounding_tokens(
382
378
  change_start_char: int,
383
379
  change_end_line: int,
384
380
  change_end_char: int,
385
- sem_tokens: <>list[int]
386
- ) -> <>tuple[(int | None), (int | None), bool] {
381
+ sem_tokens: list[int]
382
+ ) -> tuple[(int | None), (int | None), bool] {
387
383
  prev_token_index = None;
388
384
  next_token_index = None;
389
385
  inside_tok = False;
@@ -422,8 +418,8 @@ def find_surrounding_tokens(
422
418
  """Get the line of code, and the first non-space character index."""
423
419
  def get_line_of_code(
424
420
  line_number: int,
425
- lines: <>list[str]
426
- ) -> Optional[<>tuple[(str, int)]] {
421
+ lines: list[str]
422
+ ) -> Optional[tuple[(str, int)]] {
427
423
  if 0 <= line_number < len(lines) {
428
424
  line = lines[line_number].rstrip('\n');
429
425
  first_non_space = (len(line) - len(line.lstrip()));
@@ -439,7 +435,7 @@ def get_line_of_code(
439
435
 
440
436
  """Add a new text edit to the changes dictionary if it is unique."""
441
437
  def add_unique_text_edit(
442
- changes: <>dict[(str, <>list[lspt.TextEdit])],
438
+ changes: dict[(str, list[lspt.TextEdit])],
443
439
  key: str,
444
440
  new_edit: lspt.TextEdit
445
441
  ) -> None {
@@ -7,8 +7,9 @@ from abc import abstractmethod
7
7
  from enum import Enum
8
8
  from typing import ClassVar, Optional, override
9
9
 
10
- from jaclang.runtimelib.constructs import Archetype, NodeArchetype, Root
10
+ from jaclang.runtimelib.constructs import NodeArchetype
11
11
  from jaclang.runtimelib.machine import JacMachineInterface as Jac
12
+ from jaclang.runtimelib.utils import collect_node_connections
12
13
 
13
14
 
14
15
  class AccessLevelEnum(Enum):
@@ -58,53 +59,39 @@ def printgraph(
58
59
  )
59
60
 
60
61
 
61
- def jid(obj: Archetype) -> str:
62
- """Get the id of the object."""
63
- return Jac.object_ref(obj)
64
-
65
-
66
- def jobj(id: str) -> Archetype | None:
67
- """Get the object from the id."""
68
- return Jac.get_object(id)
69
-
70
-
71
- def grant(obj: Archetype, level: AccessLevelEnum) -> None:
72
- """Grant permission for the object."""
73
- assert isinstance(level, AccessLevelEnum), f'Use {ConnectPerm} instead of "CONNECT"'
74
- Jac.perm_grant(obj, level=level.value)
75
-
76
-
77
- def revoke(obj: Archetype) -> None:
78
- """Revoke permission for the object."""
79
- Jac.perm_revoke(obj)
80
-
81
-
82
- def allroots() -> list[Jac.Root]:
83
- """Get all the roots."""
84
- return Jac.get_all_root()
62
+ jid = Jac.object_ref
63
+ jobj = Jac.get_object
64
+ grant = Jac.perm_grant
65
+ revoke = Jac.perm_revoke
66
+ allroots = Jac.get_all_root
67
+ save = Jac.save
68
+ commit = Jac.commit
85
69
 
86
70
 
87
71
  def _jac_graph_json(file: Optional[str] = None) -> str:
88
72
  """Get the graph in json string."""
89
- processed: list[Root | NodeArchetype] = []
73
+ visited_nodes: set = set()
74
+ connections: set = set()
75
+ edge_ids: set = set()
90
76
  nodes: list[dict] = []
91
77
  edges: list[dict] = []
92
- working_set: list[tuple] = []
93
-
94
78
  root = Jac.root()
79
+
80
+ collect_node_connections(root, visited_nodes, connections, edge_ids)
81
+
82
+ # Create nodes list from visited nodes
95
83
  nodes.append({"id": id(root), "label": "root"})
84
+ for node_arch in visited_nodes:
85
+ if node_arch != root:
86
+ nodes.append({"id": id(node_arch), "label": repr(node_arch)})
87
+
88
+ # Create edges list with labels from connections
89
+ for _, source_node, target_node, edge_arch in connections:
90
+ edge_data = {"from": str(id(source_node)), "to": str(id(target_node))}
91
+ if repr(edge_arch) != "GenericEdge()":
92
+ edge_data["label"] = repr(edge_arch)
93
+ edges.append(edge_data)
96
94
 
97
- processed.append(root)
98
- working_set = [(root, ref) for ref in Jac.refs(root)]
99
-
100
- while working_set:
101
- start, end = working_set.pop(0)
102
- edges.append({"from": id(start), "to": id(end)})
103
- nodes.append({"id": id(end), "label": repr(end)})
104
- processed.append(end)
105
- for ref in Jac.refs(end):
106
- if ref not in processed:
107
- working_set.append((end, ref))
108
95
  output = json.dumps(
109
96
  {
110
97
  "version": "1.0",
@@ -128,6 +115,8 @@ __all__ = [
128
115
  "grant",
129
116
  "revoke",
130
117
  "allroots",
118
+ "save",
119
+ "commit",
131
120
  "NoPerm",
132
121
  "ReadPerm",
133
122
  "ConnectPerm",
@@ -322,7 +322,7 @@ class JacImporter(Importer):
322
322
  if os.path.isdir(spec.full_target):
323
323
  module = self.handle_directory(spec.module_name, spec.full_target)
324
324
  else:
325
- spec.full_target += ".jac" if spec.language == "jac" else ".py"
325
+ spec.full_target += ".jac" if spec.language in ["jac", "jir"] else ".py"
326
326
  module = self.create_jac_py_module(
327
327
  module_name,
328
328
  spec.package_path,
@@ -57,7 +57,6 @@ from jaclang.runtimelib.constructs import (
57
57
  from jaclang.runtimelib.memory import Memory, Shelf, ShelfStorage
58
58
  from jaclang.runtimelib.utils import (
59
59
  all_issubclass,
60
- collect_node_connections,
61
60
  traverse_graph,
62
61
  )
63
62
  from jaclang.utils import infer_language
@@ -266,30 +265,6 @@ class JacAccessValidation:
266
265
  class JacNode:
267
266
  """Jac Node Operations."""
268
267
 
269
- @staticmethod
270
- def node_dot(node: NodeArchetype, dot_file: Optional[str] = None) -> str:
271
- """Generate Dot file for visualizing nodes and edges."""
272
- visited_nodes: set[NodeAnchor] = set()
273
- connections: set[tuple[NodeArchetype, NodeArchetype, str]] = set()
274
- unique_node_id_dict = {}
275
-
276
- collect_node_connections(node.__jac__, visited_nodes, connections)
277
- dot_content = 'digraph {\nnode [style="filled", shape="ellipse", fillcolor="invis", fontcolor="black"];\n'
278
- for idx, i in enumerate([nodes_.archetype for nodes_ in visited_nodes]):
279
- unique_node_id_dict[i] = (i.__class__.__name__, str(idx))
280
- dot_content += f'{idx} [label="{i}"];\n'
281
- dot_content += 'edge [color="gray", style="solid"];\n'
282
-
283
- for pair in list(set(connections)):
284
- dot_content += (
285
- f"{unique_node_id_dict[pair[0]][1]} -> {unique_node_id_dict[pair[1]][1]}"
286
- f' [label="{pair[2]}"];\n'
287
- )
288
- if dot_file:
289
- with open(dot_file, "w") as f:
290
- f.write(dot_content + "}")
291
- return dot_content + "}"
292
-
293
268
  @staticmethod
294
269
  def get_edges(
295
270
  origin: list[NodeArchetype], destination: DataSpatialDestination
@@ -879,6 +854,15 @@ class JacBasics:
879
854
  """Get current execution context."""
880
855
  return JacMachine.exec_ctx
881
856
 
857
+ @staticmethod
858
+ def commit(anchor: Anchor | Archetype | None = None) -> None:
859
+ """Commit all data from memory to datasource."""
860
+ if isinstance(anchor, Archetype):
861
+ anchor = anchor.__jac__
862
+
863
+ mem = JacMachineInterface.get_context().mem
864
+ mem.commit(anchor)
865
+
882
866
  @staticmethod
883
867
  def reset_graph(root: Optional[Root] = None) -> int:
884
868
  """Purge current or target graph."""
@@ -987,6 +971,7 @@ class JacBasics:
987
971
  override_name: Optional[str] = None,
988
972
  items: Optional[dict[str, Union[str, Optional[str]]]] = None,
989
973
  reload_module: Optional[bool] = False,
974
+ lng: Optional[str] = None,
990
975
  ) -> tuple[types.ModuleType, ...]:
991
976
  """Core Import Process."""
992
977
  from jaclang.runtimelib.importer import (
@@ -995,7 +980,8 @@ class JacBasics:
995
980
  PythonImporter,
996
981
  )
997
982
 
998
- lng = infer_language(target, base_path)
983
+ if lng is None:
984
+ lng = infer_language(target, base_path)
999
985
 
1000
986
  spec = ImportPathSpec(
1001
987
  target,
@@ -1358,12 +1344,10 @@ class JacBasics:
1358
1344
  return decorator
1359
1345
 
1360
1346
  @staticmethod
1361
- def call_llm(
1362
- model: object, caller: Callable, args: dict[str | int, object]
1363
- ) -> Any: # noqa: ANN401
1347
+ def call_llm(model: object, mtir: object) -> Any: # noqa: ANN401
1364
1348
  """Call the LLM model."""
1365
1349
  raise ImportError(
1366
- "mtllm is not installed. Please install it with `pip install mtllm` and run `jac clean`."
1350
+ "byLLM is not installed. Please install it with `pip install byllm` and run `jac clean`."
1367
1351
  )
1368
1352
 
1369
1353
 
@@ -1587,40 +1571,6 @@ class JacMachineInterface(
1587
1571
  """Jac Feature."""
1588
1572
 
1589
1573
 
1590
- class JacMachine(JacMachineInterface):
1591
- """Jac Machine State."""
1592
-
1593
- loaded_modules: dict[str, types.ModuleType] = {}
1594
- base_path_dir: str = os.getcwd()
1595
- program: JacProgram = JacProgram()
1596
- pool: ThreadPoolExecutor = ThreadPoolExecutor()
1597
- exec_ctx: ExecutionContext = ExecutionContext()
1598
-
1599
- @staticmethod
1600
- def set_base_path(base_path: str) -> None:
1601
- """Set the base path for the machine."""
1602
- JacMachine.reset_machine()
1603
- JacMachine.base_path_dir = (
1604
- base_path if os.path.isdir(base_path) else os.path.dirname(base_path)
1605
- )
1606
-
1607
- @staticmethod
1608
- def set_context(context: ExecutionContext) -> None:
1609
- """Set the context for the machine."""
1610
- JacMachine.exec_ctx = context
1611
-
1612
- @staticmethod
1613
- def reset_machine() -> None:
1614
- """Reset the machine."""
1615
- # for i in JacMachine.loaded_modules.values():
1616
- # sys.modules.pop(i.__name__, None)
1617
- JacMachine.loaded_modules.clear()
1618
- JacMachine.base_path_dir = os.getcwd()
1619
- JacMachine.program = JacProgram()
1620
- JacMachine.pool = ThreadPoolExecutor()
1621
- JacMachine.exec_ctx = ExecutionContext()
1622
-
1623
-
1624
1574
  def generate_plugin_helpers(
1625
1575
  plugin_class: Type[Any],
1626
1576
  ) -> tuple[Type[Any], Type[Any], Type[Any]]:
@@ -1726,3 +1676,37 @@ def generate_plugin_helpers(
1726
1676
 
1727
1677
  JacMachineSpec, JacMachineImpl, JacMachineInterface = generate_plugin_helpers(JacMachineInterface) # type: ignore[misc]
1728
1678
  plugin_manager.add_hookspecs(JacMachineSpec)
1679
+
1680
+
1681
+ class JacMachine(JacMachineInterface):
1682
+ """Jac Machine State."""
1683
+
1684
+ loaded_modules: dict[str, types.ModuleType] = {}
1685
+ base_path_dir: str = os.getcwd()
1686
+ program: JacProgram = JacProgram()
1687
+ pool: ThreadPoolExecutor = ThreadPoolExecutor()
1688
+ exec_ctx: ExecutionContext = ExecutionContext()
1689
+
1690
+ @staticmethod
1691
+ def set_base_path(base_path: str) -> None:
1692
+ """Set the base path for the machine."""
1693
+ JacMachine.reset_machine()
1694
+ JacMachine.base_path_dir = (
1695
+ base_path if os.path.isdir(base_path) else os.path.dirname(base_path)
1696
+ )
1697
+
1698
+ @staticmethod
1699
+ def set_context(context: ExecutionContext) -> None:
1700
+ """Set the context for the machine."""
1701
+ JacMachine.exec_ctx = context
1702
+
1703
+ @staticmethod
1704
+ def reset_machine() -> None:
1705
+ """Reset the machine."""
1706
+ # for i in JacMachine.loaded_modules.values():
1707
+ # sys.modules.pop(i.__name__, None)
1708
+ JacMachine.loaded_modules.clear()
1709
+ JacMachine.base_path_dir = os.getcwd()
1710
+ JacMachine.program = JacProgram()
1711
+ JacMachine.pool = ThreadPoolExecutor()
1712
+ JacMachine.exec_ctx = ExecutionContext()
@@ -82,6 +82,9 @@ class Memory(Generic[ID, TANCH]):
82
82
  if anchor := self.__mem__.pop(id, None):
83
83
  self.__gc__.add(anchor)
84
84
 
85
+ def commit(self, anchor: TANCH | None = None) -> None:
86
+ """Commit all data from memory to datasource."""
87
+
85
88
 
86
89
  @dataclass
87
90
  class ShelfStorage(Memory[UUID, Anchor]):
@@ -94,12 +97,21 @@ class ShelfStorage(Memory[UUID, Anchor]):
94
97
  super().__init__()
95
98
  self.__shelf__ = open(session) if session else None # noqa: SIM115
96
99
 
97
- def close(self) -> None:
98
- """Close memory handler."""
100
+ def commit(self, anchor: Anchor | None = None) -> None:
101
+ """Commit all data from memory to datasource."""
99
102
  if isinstance(self.__shelf__, Shelf):
100
- for anchor in self.__gc__:
101
- self.__shelf__.pop(str(anchor.id), None)
102
- self.__mem__.pop(anchor.id, None)
103
+ if anchor:
104
+ if anchor in self.__gc__:
105
+ self.__shelf__.pop(str(anchor.id), None)
106
+ self.__mem__.pop(anchor.id, None)
107
+ self.__gc__.remove(anchor)
108
+ else:
109
+ self.sync_mem_to_db([anchor.id])
110
+ return
111
+
112
+ for anc in self.__gc__:
113
+ self.__shelf__.pop(str(anc.id), None)
114
+ self.__mem__.pop(anc.id, None)
103
115
 
104
116
  keys = set(self.__mem__.keys())
105
117
 
@@ -109,7 +121,13 @@ class ShelfStorage(Memory[UUID, Anchor]):
109
121
  # additional after memory sync
110
122
  self.sync_mem_to_db(set(self.__mem__.keys() - keys))
111
123
 
124
+ def close(self) -> None:
125
+ """Close memory handler."""
126
+ self.commit()
127
+
128
+ if isinstance(self.__shelf__, Shelf):
112
129
  self.__shelf__.close()
130
+
113
131
  super().close()
114
132
 
115
133
  def sync_mem_to_db(self, keys: Iterable[UUID]) -> None:
@@ -18,7 +18,7 @@ obj SavableObject {
18
18
 
19
19
  walker create_custom_object {
20
20
  can enter1 with `root entry {
21
- o = SavableObject(
21
+ self.obj = SavableObject(
22
22
  val=0,
23
23
  arr=[],
24
24
  json={},
@@ -36,7 +36,15 @@ walker create_custom_object {
36
36
  ),
37
37
  enum_field = Enum.A
38
38
  );
39
- _.save(o);
39
+ save(self.obj);
40
+
41
+ # commit the object to db
42
+ commit(self.obj);
43
+ }
44
+
45
+ can exit1 with `root exit {
46
+ # get directly from shelf
47
+ o = _.get_context().mem.__shelf__.get(str(self.obj.__jac__.id)).archetype;
40
48
  print(jid(o));
41
49
  print(o);
42
50
  }