jaclang 0.7.16__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 (77) hide show
  1. jaclang/cli/cli.py +140 -75
  2. jaclang/compiler/absyntree.py +9 -4
  3. jaclang/compiler/constant.py +8 -8
  4. jaclang/compiler/parser.py +10 -2
  5. jaclang/compiler/passes/main/__init__.py +1 -1
  6. jaclang/compiler/passes/main/access_modifier_pass.py +96 -147
  7. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +152 -50
  8. jaclang/compiler/passes/main/import_pass.py +88 -59
  9. jaclang/compiler/passes/main/py_collect_dep_pass.py +70 -0
  10. jaclang/compiler/passes/main/pyast_gen_pass.py +21 -6
  11. jaclang/compiler/passes/main/pyast_load_pass.py +1 -0
  12. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +7 -0
  13. jaclang/compiler/passes/main/schedules.py +9 -2
  14. jaclang/compiler/passes/main/sym_tab_build_pass.py +9 -5
  15. jaclang/compiler/passes/main/tests/fixtures/autoimpl.empty.impl.jac +0 -0
  16. jaclang/compiler/passes/main/tests/fixtures/autoimpl.jac +1 -1
  17. jaclang/compiler/passes/main/tests/fixtures/py_imp_test.jac +29 -0
  18. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/__init__.py +3 -0
  19. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/color.py +3 -0
  20. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/constants.py +5 -0
  21. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/display.py +2 -0
  22. jaclang/compiler/passes/main/tests/test_import_pass.py +72 -13
  23. jaclang/compiler/passes/main/type_check_pass.py +15 -5
  24. jaclang/compiler/passes/tool/jac_formatter_pass.py +11 -3
  25. jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +37 -41
  26. jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +37 -41
  27. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/access_mod_check.jac +27 -0
  28. jaclang/compiler/passes/utils/mypy_ast_build.py +45 -0
  29. jaclang/compiler/symtable.py +16 -11
  30. jaclang/compiler/tests/test_importer.py +17 -9
  31. jaclang/langserve/engine.py +39 -0
  32. jaclang/langserve/server.py +16 -1
  33. jaclang/langserve/tests/fixtures/rename.jac +30 -0
  34. jaclang/langserve/tests/test_server.py +216 -2
  35. jaclang/langserve/utils.py +21 -2
  36. jaclang/plugin/default.py +78 -70
  37. jaclang/plugin/feature.py +7 -17
  38. jaclang/plugin/spec.py +6 -19
  39. jaclang/plugin/tests/fixtures/other_root_access.jac +82 -0
  40. jaclang/plugin/tests/test_jaseci.py +414 -42
  41. jaclang/runtimelib/architype.py +481 -333
  42. jaclang/runtimelib/constructs.py +5 -8
  43. jaclang/runtimelib/context.py +89 -69
  44. jaclang/runtimelib/importer.py +15 -15
  45. jaclang/runtimelib/machine.py +66 -2
  46. jaclang/runtimelib/memory.py +134 -75
  47. jaclang/runtimelib/utils.py +17 -10
  48. jaclang/settings.py +2 -4
  49. jaclang/tests/fixtures/access_checker.jac +12 -17
  50. jaclang/tests/fixtures/access_modifier.jac +88 -33
  51. jaclang/tests/fixtures/baddy.jac +3 -0
  52. jaclang/tests/fixtures/bar.jac +34 -0
  53. jaclang/tests/fixtures/edge_node_walk.jac +1 -1
  54. jaclang/tests/fixtures/edge_ops.jac +1 -1
  55. jaclang/tests/fixtures/edges_walk.jac +1 -1
  56. jaclang/tests/fixtures/foo.jac +43 -0
  57. jaclang/tests/fixtures/game1.jac +1 -1
  58. jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
  59. jaclang/tests/fixtures/import.jac +9 -0
  60. jaclang/tests/fixtures/index_slice.jac +30 -0
  61. jaclang/tests/fixtures/pyfunc_1.py +1 -1
  62. jaclang/tests/fixtures/pyfunc_2.py +2 -2
  63. jaclang/tests/fixtures/pygame_mock/__init__.py +3 -0
  64. jaclang/tests/fixtures/pygame_mock/color.py +3 -0
  65. jaclang/tests/fixtures/pygame_mock/constants.py +5 -0
  66. jaclang/tests/fixtures/pygame_mock/display.py +2 -0
  67. jaclang/tests/fixtures/pygame_mock/inner/__init__.py +0 -0
  68. jaclang/tests/fixtures/pygame_mock/inner/iner_mod.py +2 -0
  69. jaclang/tests/test_cli.py +49 -6
  70. jaclang/tests/test_language.py +113 -78
  71. jaclang/tests/test_reference.py +2 -9
  72. jaclang/utils/treeprinter.py +30 -3
  73. {jaclang-0.7.16.dist-info → jaclang-0.7.17.dist-info}/METADATA +1 -1
  74. {jaclang-0.7.16.dist-info → jaclang-0.7.17.dist-info}/RECORD +77 -56
  75. /jaclang/tests/fixtures/{err.test.jac → baddy.test.jac} +0 -0
  76. {jaclang-0.7.16.dist-info → jaclang-0.7.17.dist-info}/WHEEL +0 -0
  77. {jaclang-0.7.16.dist-info → jaclang-0.7.17.dist-info}/entry_points.txt +0 -0
@@ -2,76 +2,307 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import types
6
- from dataclasses import dataclass, field
7
- from typing import Any, Callable, Optional
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
8
11
  from uuid import UUID, uuid4
9
12
 
10
13
  from jaclang.compiler.constant import EdgeDir
11
14
  from jaclang.runtimelib.utils import collect_node_connections
12
15
 
16
+ logger = getLogger(__name__)
13
17
 
14
- @dataclass(eq=False)
15
- class ElementAnchor:
16
- """Element Anchor."""
18
+ TARCH = TypeVar("TARCH", bound="Architype")
19
+ TANCH = TypeVar("TANCH", bound="Anchor")
17
20
 
18
- obj: Architype
19
- id: UUID = field(default_factory=uuid4)
20
21
 
22
+ class AccessLevel(IntEnum):
23
+ """Access level enum."""
21
24
 
22
- @dataclass(eq=False)
23
- class ObjectAnchor(ElementAnchor):
24
- """Object Anchor."""
25
+ NO_ACCESS = -1
26
+ READ = 0
27
+ CONNECT = 1
28
+ WRITE = 2
25
29
 
26
- def spawn_call(self, walk: WalkerArchitype) -> WalkerArchitype:
27
- """Invoke data spatial call."""
28
- return walk._jac_.spawn_call(self.obj)
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
29
40
 
30
41
 
31
- @dataclass(eq=False)
32
- class NodeAnchor(ObjectAnchor):
33
- """Node Anchor."""
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."""
34
56
 
35
- obj: NodeArchitype
36
- edges: list[EdgeArchitype] = field(default_factory=lambda: [])
37
- edge_ids: list[UUID] = field(default_factory=lambda: [])
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)
38
77
  persistent: bool = False
78
+ hash: int = 0
39
79
 
40
- def __getstate__(self) -> dict:
41
- """Override getstate for pickle and shelve."""
42
- state = self.__dict__.copy()
43
- state.pop("obj")
44
- if self.edges and "edges" in state:
45
- edges = state.pop("edges")
46
- state["edge_ids"] = [e._jac_.id for e in edges]
80
+ ##########################################################################
81
+ # ACCESS CONTROL: TODO: Make Base Type #
82
+ ##########################################################################
47
83
 
48
- return state
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
49
90
 
50
- def __setstate__(self, state: dict) -> None:
51
- """Override setstate for pickle and shelve."""
52
- self.__dict__.update(state)
53
- if "edge_ids" in state:
54
- self.edge_ids = state.pop("edge_ids")
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
+ # ---------------------------------------------------------------------- #
55
181
 
56
- def populate_edges(self) -> None:
57
- """Populate edges from edge ids."""
182
+ def save(self) -> None:
183
+ """Save Anchor."""
58
184
  from jaclang.plugin.feature import JacFeature as Jac
59
185
 
60
- if len(self.edges) == 0 and len(self.edge_ids) > 0:
61
- for e_id in self.edge_ids:
62
- edge = Jac.context().get_obj(e_id)
63
- if edge is None:
64
- raise ValueError(f"Edge with id {e_id} not found.")
65
- elif not isinstance(edge, EdgeArchitype):
66
- raise ValueError(f"Object with id {e_id} is not an edge.")
67
- else:
68
- self.edges.append(edge)
69
- self.edge_ids.clear()
70
-
71
- def connect_node(self, nd: NodeArchitype, edg: EdgeArchitype) -> NodeArchitype:
72
- """Connect a node with given edge."""
73
- edg._jac_.attach(self.obj, nd)
74
- return self.obj
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]
75
306
 
76
307
  def get_edges(
77
308
  self,
@@ -80,27 +311,32 @@ class NodeAnchor(ObjectAnchor):
80
311
  target_obj: Optional[list[NodeArchitype]],
81
312
  ) -> list[EdgeArchitype]:
82
313
  """Get edges connected to this node."""
83
- self.populate_edges()
314
+ from jaclang.plugin.feature import JacFeature as Jac
84
315
 
85
- edge_list: list[EdgeArchitype] = [*self.edges]
316
+ root = Jac.get_root().__jac__
86
317
  ret_edges: list[EdgeArchitype] = []
87
- edge_list = filter_func(edge_list) if filter_func else edge_list
88
- for e in edge_list:
318
+ for anchor in self.edges:
89
319
  if (
90
- e._jac_.target
91
- and e._jac_.source
92
- and (
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 (
93
327
  dir in [EdgeDir.OUT, EdgeDir.ANY]
94
- and self.obj == e._jac_.source
95
- and (not target_obj or e._jac_.target in target_obj)
96
- )
97
- or (
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 (
98
334
  dir in [EdgeDir.IN, EdgeDir.ANY]
99
- and self.obj == e._jac_.target
100
- and (not target_obj or e._jac_.source in target_obj)
101
- )
102
- ):
103
- ret_edges.append(e)
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)
104
340
  return ret_edges
105
341
 
106
342
  def edges_to_nodes(
@@ -110,27 +346,40 @@ class NodeAnchor(ObjectAnchor):
110
346
  target_obj: Optional[list[NodeArchitype]],
111
347
  ) -> list[NodeArchitype]:
112
348
  """Get set of nodes connected to this node."""
113
- self.populate_edges()
114
- for edge in self.edges:
115
- edge.populate_nodes()
116
- edge_list: list[EdgeArchitype] = [*self.edges]
117
- node_list: list[NodeArchitype] = []
118
- edge_list = filter_func(edge_list) if filter_func else edge_list
119
- for e in edge_list:
120
- if e._jac_.target and e._jac_.source:
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
+ ):
121
361
  if (
122
362
  dir in [EdgeDir.OUT, EdgeDir.ANY]
123
- and self.obj == e._jac_.source
124
- and (not target_obj or e._jac_.target in target_obj)
363
+ and self == source
364
+ and (not target_obj or target.architype in target_obj)
365
+ and root.has_read_access(target)
125
366
  ):
126
- node_list.append(e._jac_.target)
367
+ ret_edges.append(target.architype)
127
368
  if (
128
369
  dir in [EdgeDir.IN, EdgeDir.ANY]
129
- and self.obj == e._jac_.target
130
- and (not target_obj or e._jac_.source in target_obj)
370
+ and self == target
371
+ and (not target_obj or source.architype in target_obj)
372
+ and root.has_read_access(source)
131
373
  ):
132
- node_list.append(e._jac_.source)
133
- return node_list
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
134
383
 
135
384
  def gen_dot(self, dot_file: Optional[str] = None) -> str:
136
385
  """Generate Dot file for visualizing nodes and edges."""
@@ -140,7 +389,7 @@ class NodeAnchor(ObjectAnchor):
140
389
 
141
390
  collect_node_connections(self, visited_nodes, connections)
142
391
  dot_content = 'digraph {\nnode [style="filled", shape="ellipse", fillcolor="invis", fontcolor="black"];\n'
143
- for idx, i in enumerate([nodes_.obj for nodes_ in visited_nodes]):
392
+ for idx, i in enumerate([nodes_.architype for nodes_ in visited_nodes]):
144
393
  unique_node_id_dict[i] = (i.__class__.__name__, str(idx))
145
394
  dot_content += f'{idx} [label="{i}"];\n'
146
395
  dot_content += 'edge [color="gray", style="solid"];\n'
@@ -155,130 +404,115 @@ class NodeAnchor(ObjectAnchor):
155
404
  f.write(dot_content + "}")
156
405
  return dot_content + "}"
157
406
 
407
+ def spawn_call(self, walk: WalkerAnchor) -> WalkerArchitype:
408
+ """Invoke data spatial call."""
409
+ return walk.spawn_call(self)
158
410
 
159
- @dataclass(eq=False)
160
- class EdgeAnchor(ObjectAnchor):
161
- """Edge Anchor."""
411
+ def destroy(self) -> None:
412
+ """Destroy Anchor."""
413
+ from jaclang.plugin.feature import JacFeature as Jac
162
414
 
163
- obj: EdgeArchitype
164
- source: Optional[NodeArchitype] = None
165
- target: Optional[NodeArchitype] = None
166
- source_id: Optional[UUID] = None
167
- target_id: Optional[UUID] = None
168
- is_undirected: bool = False
169
- persistent: bool = False
415
+ jctx = Jac.get_context()
170
416
 
171
- def __getstate__(self) -> dict:
172
- """Override getstate for pickle and shelve."""
173
- state = self.__dict__.copy()
174
- state.pop("obj")
417
+ if jctx.root.has_write_access(self):
418
+ for edge in self.edges:
419
+ edge.destroy()
175
420
 
176
- if self.source:
177
- state["source_id"] = self.source._jac_.id
178
- state.pop("source")
421
+ jctx.mem.remove(self.id)
179
422
 
180
- if self.target:
181
- state["target_id"] = self.target._jac_.id
182
- state.pop("target")
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]
183
429
 
184
430
  return state
185
431
 
186
- def __setstate__(self, state: dict) -> None:
187
- """Override setstate for pickle and shelve."""
188
- self.__dict__.update(state)
189
432
 
190
- def attach(
191
- self, src: NodeArchitype, trg: NodeArchitype, is_undirected: bool = False
192
- ) -> EdgeAnchor:
193
- """Attach edge to nodes."""
194
- self.source = src
195
- self.target = trg
196
- self.is_undirected = is_undirected
197
- src._jac_.edges.append(self.obj)
198
- trg._jac_.edges.append(self.obj)
199
- return self
433
+ @dataclass(eq=False, repr=False, kw_only=True)
434
+ class EdgeAnchor(Anchor):
435
+ """Edge Anchor."""
200
436
 
201
- def detach(
202
- self, src: NodeArchitype, trg: NodeArchitype, is_undirected: bool = False
203
- ) -> None:
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:
204
448
  """Detach edge from nodes."""
205
- self.is_undirected = is_undirected
206
- src._jac_.edges.remove(self.obj)
207
- trg._jac_.edges.remove(self.obj)
208
- self.source = None
209
- self.target = None
210
- del self
211
-
212
- def spawn_call(self, walk: WalkerArchitype) -> WalkerArchitype:
449
+ self.source.remove_edge(self)
450
+ self.target.remove_edge(self)
451
+
452
+ def spawn_call(self, walk: WalkerAnchor) -> WalkerArchitype:
213
453
  """Invoke data spatial call."""
214
- if self.target:
215
- return walk._jac_.spawn_call(self.target)
216
- else:
217
- raise ValueError("Edge has no target.")
454
+ return walk.spawn_call(self.target)
218
455
 
456
+ def destroy(self) -> None:
457
+ """Destroy Anchor."""
458
+ from jaclang.plugin.feature import JacFeature as Jac
219
459
 
220
- @dataclass(eq=False)
221
- class WalkerAnchor(ObjectAnchor):
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):
222
484
  """Walker Anchor."""
223
485
 
224
- obj: WalkerArchitype
225
- path: list[Architype] = field(default_factory=lambda: [])
226
- next: list[Architype] = field(default_factory=lambda: [])
227
- ignores: list[Architype] = field(default_factory=lambda: [])
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)
228
490
  disengaged: bool = False
229
491
 
230
- def visit_node(
231
- self,
232
- nds: (
233
- list[NodeArchitype | EdgeArchitype]
234
- | list[NodeArchitype]
235
- | list[EdgeArchitype]
236
- | NodeArchitype
237
- | EdgeArchitype
238
- ),
239
- ) -> bool:
492
+ def visit_node(self, anchors: Iterable[NodeAnchor | EdgeAnchor]) -> bool:
240
493
  """Walker visits node."""
241
- nd_list: list[NodeArchitype | EdgeArchitype]
242
- if not isinstance(nds, list):
243
- nd_list = [nds]
244
- else:
245
- nd_list = list(nds)
246
494
  before_len = len(self.next)
247
- for i in nd_list:
248
- if i not in self.ignores:
249
- if isinstance(i, NodeArchitype):
250
- self.next.append(i)
251
- elif isinstance(i, EdgeArchitype):
252
- if i._jac_.target:
253
- self.next.append(i._jac_.target)
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)
254
502
  else:
255
503
  raise ValueError("Edge has no target.")
256
504
  return len(self.next) > before_len
257
505
 
258
- def ignore_node(
259
- self,
260
- nds: (
261
- list[NodeArchitype | EdgeArchitype]
262
- | list[NodeArchitype]
263
- | list[EdgeArchitype]
264
- | NodeArchitype
265
- | EdgeArchitype
266
- ),
267
- ) -> bool:
506
+ def ignore_node(self, anchors: Iterable[NodeAnchor | EdgeAnchor]) -> bool:
268
507
  """Walker ignores node."""
269
- nd_list: list[NodeArchitype | EdgeArchitype]
270
- if not isinstance(nds, list):
271
- nd_list = [nds]
272
- else:
273
- nd_list = list(nds)
274
508
  before_len = len(self.ignores)
275
- for i in nd_list:
276
- if i not in self.ignores:
277
- if isinstance(i, NodeArchitype):
278
- self.ignores.append(i)
279
- elif isinstance(i, EdgeArchitype):
280
- if i._jac_.target:
281
- self.ignores.append(i._jac_.target)
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)
282
516
  else:
283
517
  raise ValueError("Edge has no target.")
284
518
  return len(self.ignores) > before_len
@@ -287,206 +521,120 @@ class WalkerAnchor(ObjectAnchor):
287
521
  """Disengage walker from traversal."""
288
522
  self.disengaged = True
289
523
 
290
- def spawn_call(self, nd: Architype) -> WalkerArchitype:
524
+ def spawn_call(self, node: Anchor) -> WalkerArchitype:
291
525
  """Invoke data spatial call."""
292
- self.path = []
293
- self.next = [nd]
294
- while len(self.next):
295
- nd = self.next.pop(0)
296
- for i in nd._jac_entry_funcs_:
297
- if not i.trigger or isinstance(self.obj, i.trigger):
298
- if i.func:
299
- i.func(nd, self.obj)
300
- else:
301
- raise ValueError(f"No function {i.name} to call.")
302
- if self.disengaged:
303
- return self.obj
304
- for i in self.obj._jac_entry_funcs_:
305
- if not i.trigger or isinstance(nd, i.trigger):
306
- if i.func:
307
- i.func(self.obj, nd)
308
- else:
309
- raise ValueError(f"No function {i.name} to call.")
310
- if self.disengaged:
311
- return self.obj
312
- for i in self.obj._jac_exit_funcs_:
313
- if not i.trigger or isinstance(nd, i.trigger):
314
- if i.func:
315
- i.func(self.obj, nd)
316
- else:
317
- raise ValueError(f"No function {i.name} to call.")
318
- if self.disengaged:
319
- return self.obj
320
- for i in nd._jac_exit_funcs_:
321
- if not i.trigger or isinstance(self.obj, i.trigger):
322
- if i.func:
323
- i.func(nd, self.obj)
324
- else:
325
- raise ValueError(f"No function {i.name} to call.")
326
- if self.disengaged:
327
- return self.obj
328
- self.ignores = []
329
- return self.obj
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}")
330
566
 
331
567
 
332
568
  class Architype:
333
569
  """Architype Protocol."""
334
570
 
335
- _jac_entry_funcs_: list[DSFunc]
336
- _jac_exit_funcs_: list[DSFunc]
571
+ _jac_entry_funcs_: ClassVar[list[DSFunc]]
572
+ _jac_exit_funcs_: ClassVar[list[DSFunc]]
337
573
 
338
574
  def __init__(self) -> None:
339
575
  """Create default architype."""
340
- self._jac_: ObjectAnchor = ObjectAnchor(obj=self)
341
-
342
- def __hash__(self) -> int:
343
- """Override hash for architype."""
344
- return hash(self._jac_.id)
345
-
346
- def __eq__(self, other: object) -> bool:
347
- """Override equality for architype."""
348
- if not isinstance(other, Architype):
349
- return False
350
- else:
351
- return self._jac_.id == other._jac_.id
576
+ self.__jac__ = Anchor(architype=self)
352
577
 
353
578
  def __repr__(self) -> str:
354
579
  """Override repr for architype."""
355
580
  return f"{self.__class__.__name__}"
356
581
 
357
- def __getstate__(self) -> dict:
358
- """Override getstate for pickle and shelve."""
359
- raise NotImplementedError
360
-
361
582
 
362
583
  class NodeArchitype(Architype):
363
584
  """Node Architype Protocol."""
364
585
 
365
- _jac_: NodeAnchor
586
+ __jac__: NodeAnchor
366
587
 
367
588
  def __init__(self) -> None:
368
589
  """Create node architype."""
369
- from jaclang.plugin.feature import JacFeature as Jac
370
-
371
- self._jac_: NodeAnchor = NodeAnchor(obj=self)
372
- Jac.context().save_obj(self, persistent=self._jac_.persistent)
373
-
374
- def save(self) -> None:
375
- """Save the node to the memory/storage hierarchy."""
376
- from jaclang.plugin.feature import JacFeature as Jac
377
-
378
- self._jac_.persistent = True
379
- Jac.context().save_obj(self, persistent=True)
380
-
381
- def __getstate__(self) -> dict:
382
- """Override getstate for pickle and shelve."""
383
- state = self.__dict__.copy()
384
- state["_jac_"] = self._jac_.__getstate__()
385
- return state
386
-
387
- def __setstate__(self, state: dict) -> None:
388
- """Override setstate for pickle and shelve."""
389
- self.__dict__.update(state)
390
- self._jac_ = NodeAnchor(obj=self)
391
- self._jac_.__setstate__(state["_jac_"])
590
+ self.__jac__ = NodeAnchor(architype=self, edges=[])
392
591
 
393
592
 
394
593
  class EdgeArchitype(Architype):
395
594
  """Edge Architype Protocol."""
396
595
 
397
- _jac_: EdgeAnchor
398
- persistent: bool = False
399
-
400
- def __init__(self) -> None:
401
- """Create edge architype."""
402
- from jaclang.plugin.feature import JacFeature as Jac
403
-
404
- self._jac_: EdgeAnchor = EdgeAnchor(obj=self)
405
- Jac.context().save_obj(self, persistent=self.persistent)
406
-
407
- def save(self) -> None:
408
- """Save the edge to the memory/storage hierarchy."""
409
- from jaclang.plugin.feature import JacFeature as Jac
410
-
411
- self.persistent = True
412
- Jac.context().save_obj(self, persistent=True)
596
+ __jac__: EdgeAnchor
413
597
 
414
- def __getstate__(self) -> dict:
415
- """Override getstate for pickle and shelve."""
416
- state = self.__dict__.copy()
417
- state["_jac_"] = self._jac_.__getstate__()
418
- return state
419
-
420
- def __setstate__(self, state: dict) -> None:
421
- """Override setstate for pickle and shelve."""
422
- self.__dict__.update(state)
423
- self._jac_ = EdgeAnchor(obj=self)
424
- self._jac_.__setstate__(state["_jac_"])
425
-
426
- def populate_nodes(self) -> None:
427
- """Populate nodes for the edges from node ids."""
428
- from jaclang.plugin.feature import JacFeature as Jac
429
-
430
- if self._jac_.source_id:
431
- obj = Jac.context().get_obj(self._jac_.source_id)
432
- if obj is None:
433
- raise ValueError(f"Node with id {self._jac_.source_id} not found.")
434
- elif not isinstance(obj, NodeArchitype):
435
- raise ValueError(
436
- f"Object with id {self._jac_.source_id} is not a node."
437
- )
438
- else:
439
- self._jac_.source = obj
440
- self._jac_.source_id = None
441
- if self._jac_.target_id:
442
- obj = Jac.context().get_obj(self._jac_.target_id)
443
- if obj is None:
444
- raise ValueError(f"Node with id {self._jac_.target_id} not found.")
445
- elif not isinstance(obj, NodeArchitype):
446
- raise ValueError(
447
- f"Object with id {self._jac_.target_id} is not a node."
448
- )
449
- else:
450
- self._jac_.target = obj
451
- self._jac_.target_id = None
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
+ )
452
608
 
453
609
 
454
610
  class WalkerArchitype(Architype):
455
611
  """Walker Architype Protocol."""
456
612
 
457
- _jac_: WalkerAnchor
613
+ __jac__: WalkerAnchor
458
614
 
459
615
  def __init__(self) -> None:
460
616
  """Create walker architype."""
461
- self._jac_: WalkerAnchor = WalkerAnchor(obj=self)
617
+ self.__jac__ = WalkerAnchor(architype=self)
462
618
 
463
619
 
620
+ @dataclass(eq=False)
464
621
  class GenericEdge(EdgeArchitype):
465
622
  """Generic Root Node."""
466
623
 
467
- _jac_entry_funcs_ = []
468
- _jac_exit_funcs_ = []
624
+ _jac_entry_funcs_: ClassVar[list[DSFunc]] = []
625
+ _jac_exit_funcs_: ClassVar[list[DSFunc]] = []
469
626
 
470
627
 
628
+ @dataclass(eq=False)
471
629
  class Root(NodeArchitype):
472
630
  """Generic Root Node."""
473
631
 
474
- _jac_entry_funcs_ = []
475
- _jac_exit_funcs_ = []
476
- reachable_nodes: list[NodeArchitype] = []
477
- connections: set[tuple[NodeArchitype, NodeArchitype, EdgeArchitype]] = set()
632
+ _jac_entry_funcs_: ClassVar[list[DSFunc]] = []
633
+ _jac_exit_funcs_: ClassVar[list[DSFunc]] = []
478
634
 
479
635
  def __init__(self) -> None:
480
636
  """Create root node."""
481
- super().__init__()
482
- self._jac_.id = UUID(int=0)
483
- self._jac_.persistent = True
484
-
485
- def reset(self) -> None:
486
- """Reset the root."""
487
- self.reachable_nodes = []
488
- self.connections = set()
489
- self._jac_.edges = []
637
+ self.__jac__ = NodeAnchor(architype=self, persistent=True, edges=[])
490
638
 
491
639
 
492
640
  @dataclass(eq=False)
@@ -494,7 +642,7 @@ class DSFunc:
494
642
  """Data Spatial Function."""
495
643
 
496
644
  name: str
497
- trigger: type | types.UnionType | tuple[type | types.UnionType, ...] | None
645
+ trigger: type | UnionType | tuple[type | UnionType, ...] | None
498
646
  func: Callable[[Any, Any], Any] | None = None
499
647
 
500
648
  def resolve(self, cls: type) -> None: