jaclang 0.7.14__py3-none-any.whl → 0.7.17__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 (131) hide show
  1. jaclang/cli/cli.py +147 -77
  2. jaclang/cli/cmdreg.py +9 -12
  3. jaclang/compiler/__init__.py +19 -53
  4. jaclang/compiler/absyntree.py +94 -16
  5. jaclang/compiler/constant.py +8 -8
  6. jaclang/compiler/jac.lark +4 -3
  7. jaclang/compiler/parser.py +41 -25
  8. jaclang/compiler/passes/ir_pass.py +4 -13
  9. jaclang/compiler/passes/main/__init__.py +1 -1
  10. jaclang/compiler/passes/main/access_modifier_pass.py +96 -147
  11. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +155 -54
  12. jaclang/compiler/passes/main/import_pass.py +99 -75
  13. jaclang/compiler/passes/main/py_collect_dep_pass.py +70 -0
  14. jaclang/compiler/passes/main/pyast_gen_pass.py +328 -565
  15. jaclang/compiler/passes/main/pyast_load_pass.py +33 -6
  16. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +7 -0
  17. jaclang/compiler/passes/main/registry_pass.py +37 -3
  18. jaclang/compiler/passes/main/schedules.py +9 -2
  19. jaclang/compiler/passes/main/sym_tab_build_pass.py +10 -6
  20. jaclang/compiler/passes/main/tests/__init__.py +1 -1
  21. jaclang/compiler/passes/main/tests/fixtures/autoimpl.empty.impl.jac +0 -0
  22. jaclang/compiler/passes/main/tests/fixtures/autoimpl.jac +1 -1
  23. jaclang/compiler/passes/main/tests/fixtures/py_imp_test.jac +29 -0
  24. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/__init__.py +3 -0
  25. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/color.py +3 -0
  26. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/constants.py +5 -0
  27. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/display.py +2 -0
  28. jaclang/compiler/passes/main/tests/test_import_pass.py +72 -13
  29. jaclang/compiler/passes/main/type_check_pass.py +22 -5
  30. jaclang/compiler/passes/tool/jac_formatter_pass.py +135 -89
  31. jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +37 -41
  32. jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +37 -42
  33. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/access_mod_check.jac +27 -0
  34. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/architype_test.jac +13 -0
  35. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/comment_alignment.jac +11 -0
  36. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/comments.jac +13 -0
  37. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/decorator_stack.jac +37 -0
  38. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/esc_keywords.jac +5 -0
  39. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/long_names.jac +19 -0
  40. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/triple_quoted_string.jac +6 -0
  41. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +11 -0
  42. jaclang/compiler/passes/tool/tests/test_unparse_validate.py +33 -39
  43. jaclang/compiler/passes/transform.py +4 -0
  44. jaclang/compiler/passes/utils/mypy_ast_build.py +45 -0
  45. jaclang/compiler/semtable.py +31 -7
  46. jaclang/compiler/symtable.py +16 -11
  47. jaclang/compiler/tests/test_importer.py +25 -10
  48. jaclang/langserve/engine.py +104 -118
  49. jaclang/langserve/sem_manager.py +379 -0
  50. jaclang/langserve/server.py +24 -11
  51. jaclang/langserve/tests/fixtures/base_module_structure.jac +27 -6
  52. jaclang/langserve/tests/fixtures/circle.jac +3 -3
  53. jaclang/langserve/tests/fixtures/circle_err.jac +3 -3
  54. jaclang/langserve/tests/fixtures/circle_pure.test.jac +3 -3
  55. jaclang/langserve/tests/fixtures/import_include_statements.jac +1 -1
  56. jaclang/langserve/tests/fixtures/rename.jac +30 -0
  57. jaclang/langserve/tests/test_sem_tokens.py +277 -0
  58. jaclang/langserve/tests/test_server.py +287 -17
  59. jaclang/langserve/utils.py +184 -98
  60. jaclang/plugin/builtin.py +1 -1
  61. jaclang/plugin/default.py +288 -92
  62. jaclang/plugin/feature.py +65 -27
  63. jaclang/plugin/spec.py +62 -23
  64. jaclang/plugin/tests/fixtures/other_root_access.jac +82 -0
  65. jaclang/plugin/tests/test_jaseci.py +414 -42
  66. jaclang/runtimelib/architype.py +650 -0
  67. jaclang/{core → runtimelib}/constructs.py +5 -8
  68. jaclang/{core → runtimelib}/context.py +86 -59
  69. jaclang/runtimelib/importer.py +361 -0
  70. jaclang/runtimelib/machine.py +158 -0
  71. jaclang/runtimelib/memory.py +158 -0
  72. jaclang/{core → runtimelib}/utils.py +30 -15
  73. jaclang/settings.py +5 -4
  74. jaclang/tests/fixtures/abc.jac +3 -3
  75. jaclang/tests/fixtures/access_checker.jac +12 -17
  76. jaclang/tests/fixtures/access_modifier.jac +88 -33
  77. jaclang/tests/fixtures/baddy.jac +3 -0
  78. jaclang/tests/fixtures/baddy.test.jac +3 -0
  79. jaclang/tests/fixtures/bar.jac +34 -0
  80. jaclang/tests/fixtures/byllmissue.jac +1 -5
  81. jaclang/tests/fixtures/chandra_bugs2.jac +11 -10
  82. jaclang/tests/fixtures/cls_method.jac +41 -0
  83. jaclang/tests/fixtures/dblhello.jac +6 -0
  84. jaclang/tests/fixtures/deep/one_lev.jac +3 -3
  85. jaclang/tests/fixtures/deep/one_lev_dup.jac +2 -3
  86. jaclang/tests/fixtures/deep_import_mods.jac +13 -0
  87. jaclang/tests/fixtures/edge_node_walk.jac +1 -1
  88. jaclang/tests/fixtures/edge_ops.jac +1 -1
  89. jaclang/tests/fixtures/edges_walk.jac +1 -1
  90. jaclang/tests/fixtures/err.impl.jac +3 -0
  91. jaclang/tests/fixtures/err.jac +4 -2
  92. jaclang/tests/fixtures/err_runtime.jac +15 -0
  93. jaclang/tests/fixtures/foo.jac +43 -0
  94. jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
  95. jaclang/tests/fixtures/hello.jac +4 -0
  96. jaclang/tests/fixtures/impl_grab.impl.jac +2 -1
  97. jaclang/tests/fixtures/impl_grab.jac +4 -1
  98. jaclang/tests/fixtures/import.jac +9 -0
  99. jaclang/tests/fixtures/index_slice.jac +30 -0
  100. jaclang/tests/fixtures/jp_importer_auto.jac +14 -0
  101. jaclang/tests/fixtures/maxfail_run_test.jac +4 -4
  102. jaclang/tests/fixtures/needs_import.jac +2 -2
  103. jaclang/tests/fixtures/pyfunc_1.py +1 -1
  104. jaclang/tests/fixtures/pyfunc_2.py +5 -2
  105. jaclang/tests/fixtures/pygame_mock/__init__.py +3 -0
  106. jaclang/tests/fixtures/pygame_mock/color.py +3 -0
  107. jaclang/tests/fixtures/pygame_mock/constants.py +5 -0
  108. jaclang/tests/fixtures/pygame_mock/display.py +2 -0
  109. jaclang/tests/fixtures/pygame_mock/inner/__init__.py +0 -0
  110. jaclang/tests/fixtures/pygame_mock/inner/iner_mod.py +2 -0
  111. jaclang/tests/fixtures/registry.jac +9 -0
  112. jaclang/tests/fixtures/run_test.jac +4 -4
  113. jaclang/tests/fixtures/semstr.jac +1 -4
  114. jaclang/tests/fixtures/simple_archs.jac +1 -1
  115. jaclang/tests/test_cli.py +109 -3
  116. jaclang/tests/test_language.py +170 -68
  117. jaclang/tests/test_reference.py +2 -3
  118. jaclang/utils/helpers.py +45 -21
  119. jaclang/utils/test.py +9 -0
  120. jaclang/utils/treeprinter.py +30 -7
  121. {jaclang-0.7.14.dist-info → jaclang-0.7.17.dist-info}/METADATA +3 -2
  122. {jaclang-0.7.14.dist-info → jaclang-0.7.17.dist-info}/RECORD +126 -90
  123. jaclang/core/architype.py +0 -502
  124. jaclang/core/importer.py +0 -344
  125. jaclang/core/memory.py +0 -99
  126. jaclang/tests/fixtures/aott_raise.jac +0 -25
  127. jaclang/tests/fixtures/package_import.jac +0 -6
  128. /jaclang/{core → runtimelib}/__init__.py +0 -0
  129. /jaclang/{core → runtimelib}/test.py +0 -0
  130. {jaclang-0.7.14.dist-info → jaclang-0.7.17.dist-info}/WHEEL +0 -0
  131. {jaclang-0.7.14.dist-info → jaclang-0.7.17.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,650 @@
1
+ """Core constructs for Jac Language."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import asdict, dataclass, field, fields, is_dataclass
6
+ from enum import IntEnum
7
+ from logging import getLogger
8
+ from pickle import dumps
9
+ from types import UnionType
10
+ from typing import Any, Callable, ClassVar, Iterable, Optional, TypeVar
11
+ from uuid import UUID, uuid4
12
+
13
+ from jaclang.compiler.constant import EdgeDir
14
+ from jaclang.runtimelib.utils import collect_node_connections
15
+
16
+ logger = getLogger(__name__)
17
+
18
+ TARCH = TypeVar("TARCH", bound="Architype")
19
+ TANCH = TypeVar("TANCH", bound="Anchor")
20
+
21
+
22
+ class AccessLevel(IntEnum):
23
+ """Access level enum."""
24
+
25
+ NO_ACCESS = -1
26
+ READ = 0
27
+ CONNECT = 1
28
+ WRITE = 2
29
+
30
+ @staticmethod
31
+ def cast(val: int | str | AccessLevel) -> AccessLevel:
32
+ """Cast access level."""
33
+ match val:
34
+ case int():
35
+ return AccessLevel(val)
36
+ case str():
37
+ return AccessLevel[val]
38
+ case _:
39
+ return val
40
+
41
+
42
+ @dataclass
43
+ class Access:
44
+ """Access Structure."""
45
+
46
+ anchors: dict[str, AccessLevel] = field(default_factory=dict)
47
+
48
+ def check(self, anchor: str) -> AccessLevel:
49
+ """Validate access."""
50
+ return self.anchors.get(anchor, AccessLevel.NO_ACCESS)
51
+
52
+
53
+ @dataclass
54
+ class Permission:
55
+ """Anchor Access Handler."""
56
+
57
+ all: AccessLevel = AccessLevel.NO_ACCESS
58
+ roots: Access = field(default_factory=Access)
59
+
60
+
61
+ @dataclass
62
+ class AnchorReport:
63
+ """Report Handler."""
64
+
65
+ id: str
66
+ context: dict[str, Any]
67
+
68
+
69
+ @dataclass(eq=False, repr=False, kw_only=True)
70
+ class Anchor:
71
+ """Object Anchor."""
72
+
73
+ architype: Architype
74
+ id: UUID = field(default_factory=uuid4)
75
+ root: Optional[UUID] = None
76
+ access: Permission = field(default_factory=Permission)
77
+ persistent: bool = False
78
+ hash: int = 0
79
+
80
+ ##########################################################################
81
+ # ACCESS CONTROL: TODO: Make Base Type #
82
+ ##########################################################################
83
+
84
+ def allow_root(
85
+ self, root_id: UUID, level: AccessLevel | int | str = AccessLevel.READ
86
+ ) -> None:
87
+ """Allow all access from target root graph to current Architype."""
88
+ level = AccessLevel.cast(level)
89
+ access = self.access.roots
90
+
91
+ _root_id = str(root_id)
92
+ if level != access.anchors.get(_root_id, AccessLevel.NO_ACCESS):
93
+ access.anchors[_root_id] = level
94
+
95
+ def disallow_root(
96
+ self, root_id: UUID, level: AccessLevel | int | str = AccessLevel.READ
97
+ ) -> None:
98
+ """Disallow all access from target root graph to current Architype."""
99
+ level = AccessLevel.cast(level)
100
+ access = self.access.roots
101
+
102
+ access.anchors.pop(str(root_id), None)
103
+
104
+ def unrestrict(self, level: AccessLevel | int | str = AccessLevel.READ) -> None:
105
+ """Allow everyone to access current Architype."""
106
+ level = AccessLevel.cast(level)
107
+ if level != self.access.all:
108
+ self.access.all = level
109
+
110
+ def restrict(self) -> None:
111
+ """Disallow others to access current Architype."""
112
+ if self.access.all > AccessLevel.NO_ACCESS:
113
+ self.access.all = AccessLevel.NO_ACCESS
114
+
115
+ def has_read_access(self, to: Anchor) -> bool:
116
+ """Read Access Validation."""
117
+ if not (access_level := self.access_level(to) > AccessLevel.NO_ACCESS):
118
+ logger.info(
119
+ f"Current root doesn't have read access to {to.__class__.__name__}[{to.id}]"
120
+ )
121
+ return access_level
122
+
123
+ def has_connect_access(self, to: Anchor) -> bool:
124
+ """Write Access Validation."""
125
+ if not (access_level := self.access_level(to) > AccessLevel.READ):
126
+ logger.info(
127
+ f"Current root doesn't have connect access to {to.__class__.__name__}[{to.id}]"
128
+ )
129
+ return access_level
130
+
131
+ def has_write_access(self, to: Anchor) -> bool:
132
+ """Write Access Validation."""
133
+ if not (access_level := self.access_level(to) > AccessLevel.CONNECT):
134
+ logger.info(
135
+ f"Current root doesn't have write access to {to.__class__.__name__}[{to.id}]"
136
+ )
137
+ return access_level
138
+
139
+ def access_level(self, to: Anchor) -> AccessLevel:
140
+ """Access validation."""
141
+ if not to.persistent:
142
+ return AccessLevel.WRITE
143
+
144
+ from jaclang.plugin.feature import JacFeature as Jac
145
+
146
+ jctx = Jac.get_context()
147
+
148
+ jroot = jctx.root
149
+
150
+ # if current root is system_root
151
+ # if current root id is equal to target anchor's root id
152
+ # if current root is the target anchor
153
+ if jroot == jctx.system_root or jroot.id == to.root or jroot == to:
154
+ return AccessLevel.WRITE
155
+
156
+ access_level = AccessLevel.NO_ACCESS
157
+
158
+ # if target anchor have set access.all
159
+ if (to_access := to.access).all > AccessLevel.NO_ACCESS:
160
+ access_level = to_access.all
161
+
162
+ # if target anchor's root have set allowed roots
163
+ # if current root is allowed to the whole graph of target anchor's root
164
+ if to.root and isinstance(to_root := jctx.mem.find_one(to.root), Anchor):
165
+ if to_root.access.all > access_level:
166
+ access_level = to_root.access.all
167
+
168
+ level = to_root.access.roots.check(str(jroot.id))
169
+ if level > AccessLevel.NO_ACCESS and access_level == AccessLevel.NO_ACCESS:
170
+ access_level = level
171
+
172
+ # if target anchor have set allowed roots
173
+ # if current root is allowed to target anchor
174
+ level = to_access.roots.check(str(jroot.id))
175
+ if level > AccessLevel.NO_ACCESS and access_level == AccessLevel.NO_ACCESS:
176
+ access_level = level
177
+
178
+ return access_level
179
+
180
+ # ---------------------------------------------------------------------- #
181
+
182
+ def save(self) -> None:
183
+ """Save Anchor."""
184
+ from jaclang.plugin.feature import JacFeature as Jac
185
+
186
+ jctx = Jac.get_context()
187
+
188
+ self.persistent = True
189
+ self.root = jctx.root.id
190
+
191
+ jctx.mem.set(self.id, self)
192
+
193
+ def destroy(self) -> None:
194
+ """Destroy Anchor."""
195
+ from jaclang.plugin.feature import JacFeature as Jac
196
+
197
+ jctx = Jac.get_context()
198
+
199
+ if jctx.root.has_write_access(self):
200
+ jctx.mem.remove(self.id)
201
+
202
+ def is_populated(self) -> bool:
203
+ """Check if state."""
204
+ return "architype" in self.__dict__
205
+
206
+ def make_stub(self: TANCH) -> TANCH:
207
+ """Return unsynced copy of anchor."""
208
+ if self.is_populated():
209
+ unloaded = object.__new__(self.__class__)
210
+ unloaded.id = self.id
211
+ return unloaded
212
+ return self
213
+
214
+ def populate(self) -> None:
215
+ """Retrieve the Architype from db and return."""
216
+ from jaclang.plugin.feature import JacFeature as Jac
217
+
218
+ jsrc = Jac.get_context().mem
219
+
220
+ if anchor := jsrc.find_by_id(self.id):
221
+ self.__dict__.update(anchor.__dict__)
222
+
223
+ def __getattr__(self, name: str) -> object:
224
+ """Trigger load if detects unloaded state."""
225
+ if not self.is_populated():
226
+ self.populate()
227
+
228
+ if not self.is_populated():
229
+ raise ValueError(
230
+ f"{self.__class__.__name__} [{self.id}] is not a valid reference!"
231
+ )
232
+
233
+ return getattr(self, name)
234
+
235
+ raise AttributeError(
236
+ f"'{self.__class__.__name__}' object has not attribute '{name}'"
237
+ )
238
+
239
+ def __getstate__(self) -> dict[str, Any]: # NOTE: May be better type hinting
240
+ """Serialize Anchor."""
241
+ if self.is_populated():
242
+ unlinked = object.__new__(self.architype.__class__)
243
+ unlinked.__dict__.update(self.architype.__dict__)
244
+ unlinked.__dict__.pop("__jac__", None)
245
+
246
+ return {
247
+ "id": self.id,
248
+ "architype": unlinked,
249
+ "root": self.root,
250
+ "access": self.access,
251
+ "persistent": self.persistent,
252
+ }
253
+ else:
254
+ return {"id": self.id}
255
+
256
+ def __setstate__(self, state: dict[str, Any]) -> None:
257
+ """Deserialize Anchor."""
258
+ self.__dict__.update(state)
259
+
260
+ if self.is_populated() and self.architype:
261
+ self.architype.__jac__ = self
262
+ self.hash = hash(dumps(self))
263
+
264
+ def __repr__(self) -> str:
265
+ """Override representation."""
266
+ if self.is_populated():
267
+ attrs = ""
268
+ for f in fields(self):
269
+ if f.name in self.__dict__:
270
+ attrs += f"{f.name}={self.__dict__[f.name]}, "
271
+ attrs = attrs[:-2]
272
+ else:
273
+ attrs = f"id={self.id}"
274
+
275
+ return f"{self.__class__.__name__}({attrs})"
276
+
277
+ def report(self) -> AnchorReport:
278
+ """Report Anchor."""
279
+ return AnchorReport(
280
+ id=self.id.hex,
281
+ context=(
282
+ asdict(self.architype)
283
+ if is_dataclass(self.architype) and not isinstance(self.architype, type)
284
+ else {}
285
+ ),
286
+ )
287
+
288
+ def __hash__(self) -> int:
289
+ """Override hash for anchor."""
290
+ return hash(self.id)
291
+
292
+ def __eq__(self, other: object) -> bool:
293
+ """Override equal implementation."""
294
+ if isinstance(other, Anchor):
295
+ return self.__class__ is other.__class__ and self.id == other.id
296
+
297
+ return False
298
+
299
+
300
+ @dataclass(eq=False, repr=False, kw_only=True)
301
+ class NodeAnchor(Anchor):
302
+ """Node Anchor."""
303
+
304
+ architype: NodeArchitype
305
+ edges: list[EdgeAnchor]
306
+
307
+ def get_edges(
308
+ self,
309
+ dir: EdgeDir,
310
+ filter_func: Optional[Callable[[list[EdgeArchitype]], list[EdgeArchitype]]],
311
+ target_obj: Optional[list[NodeArchitype]],
312
+ ) -> list[EdgeArchitype]:
313
+ """Get edges connected to this node."""
314
+ from jaclang.plugin.feature import JacFeature as Jac
315
+
316
+ root = Jac.get_root().__jac__
317
+ ret_edges: list[EdgeArchitype] = []
318
+ for anchor in self.edges:
319
+ if (
320
+ (source := anchor.source)
321
+ and (target := anchor.target)
322
+ and (not filter_func or filter_func([anchor.architype]))
323
+ and source.architype
324
+ and target.architype
325
+ ):
326
+ if (
327
+ dir in [EdgeDir.OUT, EdgeDir.ANY]
328
+ and self == source
329
+ and (not target_obj or target.architype in target_obj)
330
+ and root.has_read_access(target)
331
+ ):
332
+ ret_edges.append(anchor.architype)
333
+ if (
334
+ dir in [EdgeDir.IN, EdgeDir.ANY]
335
+ and self == target
336
+ and (not target_obj or source.architype in target_obj)
337
+ and root.has_read_access(source)
338
+ ):
339
+ ret_edges.append(anchor.architype)
340
+ return ret_edges
341
+
342
+ def edges_to_nodes(
343
+ self,
344
+ dir: EdgeDir,
345
+ filter_func: Optional[Callable[[list[EdgeArchitype]], list[EdgeArchitype]]],
346
+ target_obj: Optional[list[NodeArchitype]],
347
+ ) -> list[NodeArchitype]:
348
+ """Get set of nodes connected to this node."""
349
+ from jaclang.plugin.feature import JacFeature as Jac
350
+
351
+ root = Jac.get_root().__jac__
352
+ ret_edges: list[NodeArchitype] = []
353
+ for anchor in self.edges:
354
+ if (
355
+ (source := anchor.source)
356
+ and (target := anchor.target)
357
+ and (not filter_func or filter_func([anchor.architype]))
358
+ and source.architype
359
+ and target.architype
360
+ ):
361
+ if (
362
+ dir in [EdgeDir.OUT, EdgeDir.ANY]
363
+ and self == source
364
+ and (not target_obj or target.architype in target_obj)
365
+ and root.has_read_access(target)
366
+ ):
367
+ ret_edges.append(target.architype)
368
+ if (
369
+ dir in [EdgeDir.IN, EdgeDir.ANY]
370
+ and self == target
371
+ and (not target_obj or source.architype in target_obj)
372
+ and root.has_read_access(source)
373
+ ):
374
+ ret_edges.append(source.architype)
375
+ return ret_edges
376
+
377
+ def remove_edge(self, edge: EdgeAnchor) -> None:
378
+ """Remove reference without checking sync status."""
379
+ for idx, ed in enumerate(self.edges):
380
+ if ed.id == edge.id:
381
+ self.edges.pop(idx)
382
+ break
383
+
384
+ def gen_dot(self, dot_file: Optional[str] = None) -> str:
385
+ """Generate Dot file for visualizing nodes and edges."""
386
+ visited_nodes: set[NodeAnchor] = set()
387
+ connections: set[tuple[NodeArchitype, NodeArchitype, str]] = set()
388
+ unique_node_id_dict = {}
389
+
390
+ collect_node_connections(self, visited_nodes, connections)
391
+ dot_content = 'digraph {\nnode [style="filled", shape="ellipse", fillcolor="invis", fontcolor="black"];\n'
392
+ for idx, i in enumerate([nodes_.architype for nodes_ in visited_nodes]):
393
+ unique_node_id_dict[i] = (i.__class__.__name__, str(idx))
394
+ dot_content += f'{idx} [label="{i}"];\n'
395
+ dot_content += 'edge [color="gray", style="solid"];\n'
396
+
397
+ for pair in list(set(connections)):
398
+ dot_content += (
399
+ f"{unique_node_id_dict[pair[0]][1]} -> {unique_node_id_dict[pair[1]][1]}"
400
+ f' [label="{pair[2]}"];\n'
401
+ )
402
+ if dot_file:
403
+ with open(dot_file, "w") as f:
404
+ f.write(dot_content + "}")
405
+ return dot_content + "}"
406
+
407
+ def spawn_call(self, walk: WalkerAnchor) -> WalkerArchitype:
408
+ """Invoke data spatial call."""
409
+ return walk.spawn_call(self)
410
+
411
+ def destroy(self) -> None:
412
+ """Destroy Anchor."""
413
+ from jaclang.plugin.feature import JacFeature as Jac
414
+
415
+ jctx = Jac.get_context()
416
+
417
+ if jctx.root.has_write_access(self):
418
+ for edge in self.edges:
419
+ edge.destroy()
420
+
421
+ jctx.mem.remove(self.id)
422
+
423
+ def __getstate__(self) -> dict[str, object]:
424
+ """Serialize Node Anchor."""
425
+ state = super().__getstate__()
426
+
427
+ if self.is_populated():
428
+ state["edges"] = [edge.make_stub() for edge in self.edges]
429
+
430
+ return state
431
+
432
+
433
+ @dataclass(eq=False, repr=False, kw_only=True)
434
+ class EdgeAnchor(Anchor):
435
+ """Edge Anchor."""
436
+
437
+ architype: EdgeArchitype
438
+ source: NodeAnchor
439
+ target: NodeAnchor
440
+ is_undirected: bool
441
+
442
+ def __post_init__(self) -> None:
443
+ """Populate edge to source and target."""
444
+ self.source.edges.append(self)
445
+ self.target.edges.append(self)
446
+
447
+ def detach(self) -> None:
448
+ """Detach edge from nodes."""
449
+ self.source.remove_edge(self)
450
+ self.target.remove_edge(self)
451
+
452
+ def spawn_call(self, walk: WalkerAnchor) -> WalkerArchitype:
453
+ """Invoke data spatial call."""
454
+ return walk.spawn_call(self.target)
455
+
456
+ def destroy(self) -> None:
457
+ """Destroy Anchor."""
458
+ from jaclang.plugin.feature import JacFeature as Jac
459
+
460
+ jctx = Jac.get_context()
461
+
462
+ if jctx.root.has_write_access(self):
463
+ self.detach()
464
+ jctx.mem.remove(self.id)
465
+
466
+ def __getstate__(self) -> dict[str, object]:
467
+ """Serialize Node Anchor."""
468
+ state = super().__getstate__()
469
+
470
+ if self.is_populated():
471
+ state.update(
472
+ {
473
+ "source": self.source.make_stub(),
474
+ "target": self.target.make_stub(),
475
+ "is_undirected": self.is_undirected,
476
+ }
477
+ )
478
+
479
+ return state
480
+
481
+
482
+ @dataclass(eq=False, repr=False, kw_only=True)
483
+ class WalkerAnchor(Anchor):
484
+ """Walker Anchor."""
485
+
486
+ architype: WalkerArchitype
487
+ path: list[Anchor] = field(default_factory=list)
488
+ next: list[Anchor] = field(default_factory=list)
489
+ ignores: list[Anchor] = field(default_factory=list)
490
+ disengaged: bool = False
491
+
492
+ def visit_node(self, anchors: Iterable[NodeAnchor | EdgeAnchor]) -> bool:
493
+ """Walker visits node."""
494
+ before_len = len(self.next)
495
+ for anchor in anchors:
496
+ if anchor not in self.ignores:
497
+ if isinstance(anchor, NodeAnchor):
498
+ self.next.append(anchor)
499
+ elif isinstance(anchor, EdgeAnchor):
500
+ if target := anchor.target:
501
+ self.next.append(target)
502
+ else:
503
+ raise ValueError("Edge has no target.")
504
+ return len(self.next) > before_len
505
+
506
+ def ignore_node(self, anchors: Iterable[NodeAnchor | EdgeAnchor]) -> bool:
507
+ """Walker ignores node."""
508
+ before_len = len(self.ignores)
509
+ for anchor in anchors:
510
+ if anchor not in self.ignores:
511
+ if isinstance(anchor, NodeAnchor):
512
+ self.ignores.append(anchor)
513
+ elif isinstance(anchor, EdgeAnchor):
514
+ if target := anchor.target:
515
+ self.ignores.append(target)
516
+ else:
517
+ raise ValueError("Edge has no target.")
518
+ return len(self.ignores) > before_len
519
+
520
+ def disengage_now(self) -> None:
521
+ """Disengage walker from traversal."""
522
+ self.disengaged = True
523
+
524
+ def spawn_call(self, node: Anchor) -> WalkerArchitype:
525
+ """Invoke data spatial call."""
526
+ if walker := self.architype:
527
+ self.path = []
528
+ self.next = [node]
529
+ while len(self.next):
530
+ if current_node := self.next.pop(0).architype:
531
+ for i in current_node._jac_entry_funcs_:
532
+ if not i.trigger or isinstance(walker, i.trigger):
533
+ if i.func:
534
+ i.func(current_node, walker)
535
+ else:
536
+ raise ValueError(f"No function {i.name} to call.")
537
+ if self.disengaged:
538
+ return walker
539
+ for i in walker._jac_entry_funcs_:
540
+ if not i.trigger or isinstance(current_node, i.trigger):
541
+ if i.func:
542
+ i.func(walker, current_node)
543
+ else:
544
+ raise ValueError(f"No function {i.name} to call.")
545
+ if self.disengaged:
546
+ return walker
547
+ for i in walker._jac_exit_funcs_:
548
+ if not i.trigger or isinstance(current_node, i.trigger):
549
+ if i.func:
550
+ i.func(walker, current_node)
551
+ else:
552
+ raise ValueError(f"No function {i.name} to call.")
553
+ if self.disengaged:
554
+ return walker
555
+ for i in current_node._jac_exit_funcs_:
556
+ if not i.trigger or isinstance(walker, i.trigger):
557
+ if i.func:
558
+ i.func(current_node, walker)
559
+ else:
560
+ raise ValueError(f"No function {i.name} to call.")
561
+ if self.disengaged:
562
+ return walker
563
+ self.ignores = []
564
+ return walker
565
+ raise Exception(f"Invalid Reference {self.id}")
566
+
567
+
568
+ class Architype:
569
+ """Architype Protocol."""
570
+
571
+ _jac_entry_funcs_: ClassVar[list[DSFunc]]
572
+ _jac_exit_funcs_: ClassVar[list[DSFunc]]
573
+
574
+ def __init__(self) -> None:
575
+ """Create default architype."""
576
+ self.__jac__ = Anchor(architype=self)
577
+
578
+ def __repr__(self) -> str:
579
+ """Override repr for architype."""
580
+ return f"{self.__class__.__name__}"
581
+
582
+
583
+ class NodeArchitype(Architype):
584
+ """Node Architype Protocol."""
585
+
586
+ __jac__: NodeAnchor
587
+
588
+ def __init__(self) -> None:
589
+ """Create node architype."""
590
+ self.__jac__ = NodeAnchor(architype=self, edges=[])
591
+
592
+
593
+ class EdgeArchitype(Architype):
594
+ """Edge Architype Protocol."""
595
+
596
+ __jac__: EdgeAnchor
597
+
598
+ def __attach__(
599
+ self,
600
+ source: NodeAnchor,
601
+ target: NodeAnchor,
602
+ is_undirected: bool,
603
+ ) -> None:
604
+ """Attach EdgeAnchor properly."""
605
+ self.__jac__ = EdgeAnchor(
606
+ architype=self, source=source, target=target, is_undirected=is_undirected
607
+ )
608
+
609
+
610
+ class WalkerArchitype(Architype):
611
+ """Walker Architype Protocol."""
612
+
613
+ __jac__: WalkerAnchor
614
+
615
+ def __init__(self) -> None:
616
+ """Create walker architype."""
617
+ self.__jac__ = WalkerAnchor(architype=self)
618
+
619
+
620
+ @dataclass(eq=False)
621
+ class GenericEdge(EdgeArchitype):
622
+ """Generic Root Node."""
623
+
624
+ _jac_entry_funcs_: ClassVar[list[DSFunc]] = []
625
+ _jac_exit_funcs_: ClassVar[list[DSFunc]] = []
626
+
627
+
628
+ @dataclass(eq=False)
629
+ class Root(NodeArchitype):
630
+ """Generic Root Node."""
631
+
632
+ _jac_entry_funcs_: ClassVar[list[DSFunc]] = []
633
+ _jac_exit_funcs_: ClassVar[list[DSFunc]] = []
634
+
635
+ def __init__(self) -> None:
636
+ """Create root node."""
637
+ self.__jac__ = NodeAnchor(architype=self, persistent=True, edges=[])
638
+
639
+
640
+ @dataclass(eq=False)
641
+ class DSFunc:
642
+ """Data Spatial Function."""
643
+
644
+ name: str
645
+ trigger: type | UnionType | tuple[type | UnionType, ...] | None
646
+ func: Callable[[Any, Any], Any] | None = None
647
+
648
+ def resolve(self, cls: type) -> None:
649
+ """Resolve the function."""
650
+ self.func = getattr(cls, self.name)
@@ -4,26 +4,24 @@ from __future__ import annotations
4
4
 
5
5
 
6
6
  from .architype import (
7
+ Anchor,
7
8
  Architype,
8
9
  DSFunc,
9
10
  EdgeAnchor,
10
11
  EdgeArchitype,
11
- ElementAnchor,
12
12
  GenericEdge,
13
13
  NodeAnchor,
14
14
  NodeArchitype,
15
- ObjectAnchor,
16
15
  Root,
17
16
  WalkerAnchor,
18
17
  WalkerArchitype,
19
18
  )
20
- from .context import ExecutionContext, exec_context
21
- from .memory import Memory, ShelveStorage
19
+ from .context import ExecutionContext
20
+ from .memory import Memory, ShelfStorage
22
21
  from .test import JacTestCheck, JacTestResult, JacTextTestRunner
23
22
 
24
23
  __all__ = [
25
- "ElementAnchor",
26
- "ObjectAnchor",
24
+ "Anchor",
27
25
  "NodeAnchor",
28
26
  "EdgeAnchor",
29
27
  "WalkerAnchor",
@@ -35,9 +33,8 @@ __all__ = [
35
33
  "Root",
36
34
  "DSFunc",
37
35
  "Memory",
38
- "ShelveStorage",
36
+ "ShelfStorage",
39
37
  "ExecutionContext",
40
- "exec_context",
41
38
  "JacTestResult",
42
39
  "JacTextTestRunner",
43
40
  "JacTestCheck",