jaclang 0.7.21__py3-none-any.whl → 0.7.23__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.

jaclang/plugin/default.py CHANGED
@@ -11,59 +11,548 @@ import types
11
11
  from collections import OrderedDict
12
12
  from dataclasses import field
13
13
  from functools import wraps
14
+ from logging import getLogger
14
15
  from typing import Any, Callable, Mapping, Optional, Sequence, Type, Union
15
16
  from uuid import UUID
16
17
 
17
- import jaclang.compiler.absyntree as ast
18
- from jaclang.compiler.constant import EdgeDir, colors
19
- from jaclang.compiler.passes.main.pyast_gen_pass import PyastGenPass
18
+ from jaclang.compiler.constant import colors
20
19
  from jaclang.compiler.semtable import SemInfo, SemRegistry, SemScope
21
- from jaclang.runtimelib.constructs import (
20
+ from jaclang.plugin.feature import (
21
+ AccessLevel,
22
+ Anchor,
22
23
  Architype,
23
24
  DSFunc,
24
25
  EdgeAnchor,
25
26
  EdgeArchitype,
27
+ EdgeDir,
26
28
  ExecutionContext,
27
- GenericEdge,
28
- JacTestCheck,
29
+ JacFeature as Jac,
29
30
  NodeAnchor,
30
31
  NodeArchitype,
32
+ P,
33
+ PyastGenPass,
31
34
  Root,
32
- WalkerAnchor,
35
+ T,
33
36
  WalkerArchitype,
37
+ ast,
38
+ )
39
+ from jaclang.runtimelib.constructs import (
40
+ GenericEdge,
41
+ JacTestCheck,
34
42
  )
35
43
  from jaclang.runtimelib.importer import ImportPathSpec, JacImporter, PythonImporter
36
44
  from jaclang.runtimelib.machine import JacMachine, JacProgram
37
- from jaclang.runtimelib.utils import traverse_graph
38
- from jaclang.plugin.feature import JacFeature as Jac # noqa: I100
39
- from jaclang.plugin.spec import P, T
45
+ from jaclang.runtimelib.utils import collect_node_connections, traverse_graph
40
46
 
41
47
 
42
48
  import pluggy
43
49
 
44
50
  hookimpl = pluggy.HookimplMarker("jac")
51
+ logger = getLogger(__name__)
52
+
53
+
54
+ class JacAccessValidationImpl:
55
+ """Jac Access Validation Implementations."""
56
+
57
+ @staticmethod
58
+ @hookimpl
59
+ def allow_root(
60
+ architype: Architype, root_id: UUID, level: AccessLevel | int | str
61
+ ) -> None:
62
+ """Allow all access from target root graph to current Architype."""
63
+ level = AccessLevel.cast(level)
64
+ access = architype.__jac__.access.roots
65
+
66
+ _root_id = str(root_id)
67
+ if level != access.anchors.get(_root_id, AccessLevel.NO_ACCESS):
68
+ access.anchors[_root_id] = level
69
+
70
+ @staticmethod
71
+ @hookimpl
72
+ def disallow_root(
73
+ architype: Architype, root_id: UUID, level: AccessLevel | int | str
74
+ ) -> None:
75
+ """Disallow all access from target root graph to current Architype."""
76
+ level = AccessLevel.cast(level)
77
+ access = architype.__jac__.access.roots
78
+
79
+ access.anchors.pop(str(root_id), None)
80
+
81
+ @staticmethod
82
+ @hookimpl
83
+ def unrestrict(architype: Architype, level: AccessLevel | int | str) -> None:
84
+ """Allow everyone to access current Architype."""
85
+ anchor = architype.__jac__
86
+ level = AccessLevel.cast(level)
87
+ if level != anchor.access.all:
88
+ anchor.access.all = level
89
+
90
+ @staticmethod
91
+ @hookimpl
92
+ def restrict(architype: Architype) -> None:
93
+ """Disallow others to access current Architype."""
94
+ anchor = architype.__jac__
95
+ if anchor.access.all > AccessLevel.NO_ACCESS:
96
+ anchor.access.all = AccessLevel.NO_ACCESS
97
+
98
+ @staticmethod
99
+ @hookimpl
100
+ def check_read_access(to: Anchor) -> bool:
101
+ """Read Access Validation."""
102
+ if not (access_level := Jac.check_access_level(to) > AccessLevel.NO_ACCESS):
103
+ logger.info(
104
+ f"Current root doesn't have read access to {to.__class__.__name__}[{to.id}]"
105
+ )
106
+ return access_level
107
+
108
+ @staticmethod
109
+ @hookimpl
110
+ def check_connect_access(to: Anchor) -> bool:
111
+ """Write Access Validation."""
112
+ if not (access_level := Jac.check_access_level(to) > AccessLevel.READ):
113
+ logger.info(
114
+ f"Current root doesn't have connect access to {to.__class__.__name__}[{to.id}]"
115
+ )
116
+ return access_level
117
+
118
+ @staticmethod
119
+ @hookimpl
120
+ def check_write_access(to: Anchor) -> bool:
121
+ """Write Access Validation."""
122
+ if not (access_level := Jac.check_access_level(to) > AccessLevel.CONNECT):
123
+ logger.info(
124
+ f"Current root doesn't have write access to {to.__class__.__name__}[{to.id}]"
125
+ )
126
+ return access_level
127
+
128
+ @staticmethod
129
+ @hookimpl
130
+ def check_access_level(to: Anchor) -> AccessLevel:
131
+ """Access validation."""
132
+ if not to.persistent:
133
+ return AccessLevel.WRITE
134
+
135
+ jctx = Jac.get_context()
136
+
137
+ jroot = jctx.root
138
+
139
+ # if current root is system_root
140
+ # if current root id is equal to target anchor's root id
141
+ # if current root is the target anchor
142
+ if jroot == jctx.system_root or jroot.id == to.root or jroot == to:
143
+ return AccessLevel.WRITE
144
+
145
+ access_level = AccessLevel.NO_ACCESS
146
+
147
+ # if target anchor have set access.all
148
+ if (to_access := to.access).all > AccessLevel.NO_ACCESS:
149
+ access_level = to_access.all
150
+
151
+ # if target anchor's root have set allowed roots
152
+ # if current root is allowed to the whole graph of target anchor's root
153
+ if to.root and isinstance(to_root := jctx.mem.find_one(to.root), Anchor):
154
+ if to_root.access.all > access_level:
155
+ access_level = to_root.access.all
156
+
157
+ level = to_root.access.roots.check(str(jroot.id))
158
+ if level > AccessLevel.NO_ACCESS and access_level == AccessLevel.NO_ACCESS:
159
+ access_level = level
160
+
161
+ # if target anchor have set allowed roots
162
+ # if current root is allowed to target anchor
163
+ level = to_access.roots.check(str(jroot.id))
164
+ if level > AccessLevel.NO_ACCESS and access_level == AccessLevel.NO_ACCESS:
165
+ access_level = level
166
+
167
+ return access_level
168
+
169
+
170
+ class JacNodeImpl:
171
+ """Jac Node Operations."""
172
+
173
+ @staticmethod
174
+ @hookimpl
175
+ def node_dot(node: NodeArchitype, dot_file: Optional[str]) -> str:
176
+ """Generate Dot file for visualizing nodes and edges."""
177
+ visited_nodes: set[NodeAnchor] = set()
178
+ connections: set[tuple[NodeArchitype, NodeArchitype, str]] = set()
179
+ unique_node_id_dict = {}
180
+
181
+ collect_node_connections(node.__jac__, visited_nodes, connections)
182
+ dot_content = 'digraph {\nnode [style="filled", shape="ellipse", fillcolor="invis", fontcolor="black"];\n'
183
+ for idx, i in enumerate([nodes_.architype for nodes_ in visited_nodes]):
184
+ unique_node_id_dict[i] = (i.__class__.__name__, str(idx))
185
+ dot_content += f'{idx} [label="{i}"];\n'
186
+ dot_content += 'edge [color="gray", style="solid"];\n'
187
+
188
+ for pair in list(set(connections)):
189
+ dot_content += (
190
+ f"{unique_node_id_dict[pair[0]][1]} -> {unique_node_id_dict[pair[1]][1]}"
191
+ f' [label="{pair[2]}"];\n'
192
+ )
193
+ if dot_file:
194
+ with open(dot_file, "w") as f:
195
+ f.write(dot_content + "}")
196
+ return dot_content + "}"
197
+
198
+ @staticmethod
199
+ @hookimpl
200
+ def get_edges(
201
+ node: NodeAnchor,
202
+ dir: EdgeDir,
203
+ filter_func: Optional[Callable[[list[EdgeArchitype]], list[EdgeArchitype]]],
204
+ target_obj: Optional[list[NodeArchitype]],
205
+ ) -> list[EdgeArchitype]:
206
+ """Get edges connected to this node."""
207
+ ret_edges: list[EdgeArchitype] = []
208
+ for anchor in node.edges:
209
+ if (
210
+ (source := anchor.source)
211
+ and (target := anchor.target)
212
+ and (not filter_func or filter_func([anchor.architype]))
213
+ and source.architype
214
+ and target.architype
215
+ ):
216
+ if (
217
+ dir in [EdgeDir.OUT, EdgeDir.ANY]
218
+ and node == source
219
+ and (not target_obj or target.architype in target_obj)
220
+ and Jac.check_read_access(target)
221
+ ):
222
+ ret_edges.append(anchor.architype)
223
+ if (
224
+ dir in [EdgeDir.IN, EdgeDir.ANY]
225
+ and node == target
226
+ and (not target_obj or source.architype in target_obj)
227
+ and Jac.check_read_access(source)
228
+ ):
229
+ ret_edges.append(anchor.architype)
230
+ return ret_edges
231
+
232
+ @staticmethod
233
+ @hookimpl
234
+ def edges_to_nodes(
235
+ node: NodeAnchor,
236
+ dir: EdgeDir,
237
+ filter_func: Optional[Callable[[list[EdgeArchitype]], list[EdgeArchitype]]],
238
+ target_obj: Optional[list[NodeArchitype]],
239
+ ) -> list[NodeArchitype]:
240
+ """Get set of nodes connected to this node."""
241
+ ret_edges: list[NodeArchitype] = []
242
+ for anchor in node.edges:
243
+ if (
244
+ (source := anchor.source)
245
+ and (target := anchor.target)
246
+ and (not filter_func or filter_func([anchor.architype]))
247
+ and source.architype
248
+ and target.architype
249
+ ):
250
+ if (
251
+ dir in [EdgeDir.OUT, EdgeDir.ANY]
252
+ and node == source
253
+ and (not target_obj or target.architype in target_obj)
254
+ and Jac.check_read_access(target)
255
+ ):
256
+ ret_edges.append(target.architype)
257
+ if (
258
+ dir in [EdgeDir.IN, EdgeDir.ANY]
259
+ and node == target
260
+ and (not target_obj or source.architype in target_obj)
261
+ and Jac.check_read_access(source)
262
+ ):
263
+ ret_edges.append(source.architype)
264
+ return ret_edges
265
+
266
+ @staticmethod
267
+ @hookimpl
268
+ def remove_edge(node: NodeAnchor, edge: EdgeAnchor) -> None:
269
+ """Remove reference without checking sync status."""
270
+ for idx, ed in enumerate(node.edges):
271
+ if ed.id == edge.id:
272
+ node.edges.pop(idx)
273
+ break
274
+
275
+
276
+ class JacEdgeImpl:
277
+ """Jac Edge Operations."""
278
+
279
+ @staticmethod
280
+ @hookimpl
281
+ def detach(edge: EdgeAnchor) -> None:
282
+ """Detach edge from nodes."""
283
+ Jac.remove_edge(node=edge.source, edge=edge)
284
+ Jac.remove_edge(node=edge.target, edge=edge)
285
+
286
+
287
+ class JacWalkerImpl:
288
+ """Jac Edge Operations."""
289
+
290
+ @staticmethod
291
+ @hookimpl
292
+ def visit_node(
293
+ walker: WalkerArchitype,
294
+ expr: (
295
+ list[NodeArchitype | EdgeArchitype]
296
+ | list[NodeArchitype]
297
+ | list[EdgeArchitype]
298
+ | NodeArchitype
299
+ | EdgeArchitype
300
+ ),
301
+ ) -> bool:
302
+ """Jac's visit stmt feature."""
303
+ if isinstance(walker, WalkerArchitype):
304
+ """Walker visits node."""
305
+ wanch = walker.__jac__
306
+ before_len = len(wanch.next)
307
+ for anchor in (
308
+ (i.__jac__ for i in expr) if isinstance(expr, list) else [expr.__jac__]
309
+ ):
310
+ if anchor not in wanch.ignores:
311
+ if isinstance(anchor, NodeAnchor):
312
+ wanch.next.append(anchor)
313
+ elif isinstance(anchor, EdgeAnchor):
314
+ if target := anchor.target:
315
+ wanch.next.append(target)
316
+ else:
317
+ raise ValueError("Edge has no target.")
318
+ return len(wanch.next) > before_len
319
+ else:
320
+ raise TypeError("Invalid walker object")
321
+
322
+ @staticmethod
323
+ @hookimpl
324
+ def ignore(
325
+ walker: WalkerArchitype,
326
+ expr: (
327
+ list[NodeArchitype | EdgeArchitype]
328
+ | list[NodeArchitype]
329
+ | list[EdgeArchitype]
330
+ | NodeArchitype
331
+ | EdgeArchitype
332
+ ),
333
+ ) -> bool:
334
+ """Jac's ignore stmt feature."""
335
+ if isinstance(walker, WalkerArchitype):
336
+ wanch = walker.__jac__
337
+ before_len = len(wanch.ignores)
338
+ for anchor in (
339
+ (i.__jac__ for i in expr) if isinstance(expr, list) else [expr.__jac__]
340
+ ):
341
+ if anchor not in wanch.ignores:
342
+ if isinstance(anchor, NodeAnchor):
343
+ wanch.ignores.append(anchor)
344
+ elif isinstance(anchor, EdgeAnchor):
345
+ if target := anchor.target:
346
+ wanch.ignores.append(target)
347
+ else:
348
+ raise ValueError("Edge has no target.")
349
+ return len(wanch.ignores) > before_len
350
+ else:
351
+ raise TypeError("Invalid walker object")
352
+
353
+ @staticmethod
354
+ @hookimpl
355
+ def spawn_call(op1: Architype, op2: Architype) -> WalkerArchitype:
356
+ """Invoke data spatial call."""
357
+ if isinstance(op1, WalkerArchitype):
358
+ warch = op1
359
+ walker = op1.__jac__
360
+ if isinstance(op2, NodeArchitype):
361
+ node = op2.__jac__
362
+ elif isinstance(op2, EdgeArchitype):
363
+ node = op2.__jac__.target
364
+ else:
365
+ raise TypeError("Invalid target object")
366
+ elif isinstance(op2, WalkerArchitype):
367
+ warch = op2
368
+ walker = op2.__jac__
369
+ if isinstance(op1, NodeArchitype):
370
+ node = op1.__jac__
371
+ elif isinstance(op1, EdgeArchitype):
372
+ node = op1.__jac__.target
373
+ else:
374
+ raise TypeError("Invalid target object")
375
+ else:
376
+ raise TypeError("Invalid walker object")
377
+
378
+ walker.path = []
379
+ walker.next = [node]
380
+ if walker.next:
381
+ current_node = walker.next[-1].architype
382
+ for i in warch._jac_entry_funcs_:
383
+ if not i.trigger:
384
+ if i.func:
385
+ i.func(warch, current_node)
386
+ else:
387
+ raise ValueError(f"No function {i.name} to call.")
388
+ while len(walker.next):
389
+ if current_node := walker.next.pop(0).architype:
390
+ for i in current_node._jac_entry_funcs_:
391
+ if not i.trigger or isinstance(warch, i.trigger):
392
+ if i.func:
393
+ i.func(current_node, warch)
394
+ else:
395
+ raise ValueError(f"No function {i.name} to call.")
396
+ if walker.disengaged:
397
+ return warch
398
+ for i in warch._jac_entry_funcs_:
399
+ if not i.trigger or isinstance(current_node, i.trigger):
400
+ if i.func and i.trigger:
401
+ i.func(warch, current_node)
402
+ elif not i.trigger:
403
+ continue
404
+ else:
405
+ raise ValueError(f"No function {i.name} to call.")
406
+ if walker.disengaged:
407
+ return warch
408
+ for i in warch._jac_exit_funcs_:
409
+ if not i.trigger or isinstance(current_node, i.trigger):
410
+ if i.func and i.trigger:
411
+ i.func(warch, current_node)
412
+ elif not i.trigger:
413
+ continue
414
+ else:
415
+ raise ValueError(f"No function {i.name} to call.")
416
+ if walker.disengaged:
417
+ return warch
418
+ for i in current_node._jac_exit_funcs_:
419
+ if not i.trigger or isinstance(warch, i.trigger):
420
+ if i.func:
421
+ i.func(current_node, warch)
422
+ else:
423
+ raise ValueError(f"No function {i.name} to call.")
424
+ if walker.disengaged:
425
+ return warch
426
+ for i in warch._jac_exit_funcs_:
427
+ if not i.trigger:
428
+ if i.func:
429
+ i.func(warch, current_node)
430
+ else:
431
+ raise ValueError(f"No function {i.name} to call.")
432
+ walker.ignores = []
433
+ return warch
434
+
435
+ @staticmethod
436
+ @hookimpl
437
+ def disengage(walker: WalkerArchitype) -> bool: # noqa: ANN401
438
+ """Jac's disengage stmt feature."""
439
+ walker.__jac__.disengaged = True
440
+ return True
441
+
442
+
443
+ class JacBuiltinImpl:
444
+ """Jac Builtins."""
445
+
446
+ @staticmethod
447
+ @hookimpl
448
+ def dotgen(
449
+ node: NodeArchitype,
450
+ depth: int,
451
+ traverse: bool,
452
+ edge_type: Optional[list[str]],
453
+ bfs: bool,
454
+ edge_limit: int,
455
+ node_limit: int,
456
+ dot_file: Optional[str],
457
+ ) -> str:
458
+ """Generate Dot file for visualizing nodes and edges."""
459
+ edge_type = edge_type if edge_type else []
460
+ visited_nodes: list[NodeArchitype] = []
461
+ node_depths: dict[NodeArchitype, int] = {node: 0}
462
+ queue: list = [[node, 0]]
463
+ connections: list[tuple[NodeArchitype, NodeArchitype, EdgeArchitype]] = []
464
+
465
+ def dfs(node: NodeArchitype, cur_depth: int) -> None:
466
+ """Depth first search."""
467
+ if node not in visited_nodes:
468
+ visited_nodes.append(node)
469
+ traverse_graph(
470
+ node,
471
+ cur_depth,
472
+ depth,
473
+ edge_type,
474
+ traverse,
475
+ connections,
476
+ node_depths,
477
+ visited_nodes,
478
+ queue,
479
+ bfs,
480
+ dfs,
481
+ node_limit,
482
+ edge_limit,
483
+ )
484
+
485
+ if bfs:
486
+ cur_depth = 0
487
+ while queue:
488
+ current_node, cur_depth = queue.pop(0)
489
+ if current_node not in visited_nodes:
490
+ visited_nodes.append(current_node)
491
+ traverse_graph(
492
+ current_node,
493
+ cur_depth,
494
+ depth,
495
+ edge_type,
496
+ traverse,
497
+ connections,
498
+ node_depths,
499
+ visited_nodes,
500
+ queue,
501
+ bfs,
502
+ dfs,
503
+ node_limit,
504
+ edge_limit,
505
+ )
506
+ else:
507
+ dfs(node, cur_depth=0)
508
+ dot_content = (
509
+ 'digraph {\nnode [style="filled", shape="ellipse", '
510
+ 'fillcolor="invis", fontcolor="black"];\n'
511
+ )
512
+ for source, target, edge in connections:
513
+ dot_content += (
514
+ f"{visited_nodes.index(source)} -> {visited_nodes.index(target)} "
515
+ f' [label="{html.escape(str(edge.__jac__.architype))} "];\n'
516
+ )
517
+ for node_ in visited_nodes:
518
+ color = (
519
+ colors[node_depths[node_]] if node_depths[node_] < 25 else colors[24]
520
+ )
521
+ dot_content += (
522
+ f'{visited_nodes.index(node_)} [label="{html.escape(str(node_.__jac__.architype))}"'
523
+ f'fillcolor="{color}"];\n'
524
+ )
525
+ if dot_file:
526
+ with open(dot_file, "w") as f:
527
+ f.write(dot_content + "}")
528
+ return dot_content + "}"
529
+
45
530
 
46
- __all__ = [
47
- "EdgeAnchor",
48
- "GenericEdge",
49
- "hookimpl",
50
- "JacTestCheck",
51
- "NodeAnchor",
52
- "WalkerAnchor",
53
- "NodeArchitype",
54
- "EdgeArchitype",
55
- "Root",
56
- "WalkerArchitype",
57
- "Architype",
58
- "DSFunc",
59
- "T",
60
- ]
61
-
62
-
63
- class JacFeatureDefaults:
531
+ class JacCmdImpl:
532
+ """Jac CLI command."""
533
+
534
+ @staticmethod
535
+ @hookimpl
536
+ def create_cmd() -> None:
537
+ """Create Jac CLI cmds."""
538
+ pass
539
+
540
+
541
+ class JacFeatureImpl(
542
+ JacAccessValidationImpl,
543
+ JacNodeImpl,
544
+ JacEdgeImpl,
545
+ JacWalkerImpl,
546
+ JacBuiltinImpl,
547
+ JacCmdImpl,
548
+ ):
64
549
  """Jac Feature."""
65
550
 
66
- pm = pluggy.PluginManager("jac")
551
+ @staticmethod
552
+ @hookimpl
553
+ def setup() -> None:
554
+ """Set Class References."""
555
+ pass
67
556
 
68
557
  @staticmethod
69
558
  @hookimpl
@@ -74,6 +563,7 @@ class JacFeatureDefaults:
74
563
  @staticmethod
75
564
  @hookimpl
76
565
  def get_object(id: str) -> Architype | None:
566
+ """Get object by id."""
77
567
  if id == "root":
78
568
  return Jac.get_context().root.architype
79
569
  elif obj := Jac.get_context().mem.find_by_id(UUID(id)):
@@ -84,6 +574,7 @@ class JacFeatureDefaults:
84
574
  @staticmethod
85
575
  @hookimpl
86
576
  def object_ref(obj: Architype) -> str:
577
+ """Get object's id."""
87
578
  return obj.__jac__.id.hex
88
579
 
89
580
  @staticmethod
@@ -361,69 +852,11 @@ class JacFeatureDefaults:
361
852
  """Jac's has container default feature."""
362
853
  return field(default_factory=lambda: gen_func())
363
854
 
364
- @staticmethod
365
- @hookimpl
366
- def spawn_call(op1: Architype, op2: Architype) -> WalkerArchitype:
367
- """Jac's spawn operator feature."""
368
- if isinstance(op1, WalkerArchitype):
369
- return op1.__jac__.spawn_call(op2.__jac__)
370
- elif isinstance(op2, WalkerArchitype):
371
- return op2.__jac__.spawn_call(op1.__jac__)
372
- else:
373
- raise TypeError("Invalid walker object")
374
-
375
855
  @staticmethod
376
856
  @hookimpl
377
857
  def report(expr: Any) -> Any: # noqa: ANN401
378
858
  """Jac's report stmt feature."""
379
859
 
380
- @staticmethod
381
- @hookimpl
382
- def ignore(
383
- walker: WalkerArchitype,
384
- expr: (
385
- list[NodeArchitype | EdgeArchitype]
386
- | list[NodeArchitype]
387
- | list[EdgeArchitype]
388
- | NodeArchitype
389
- | EdgeArchitype
390
- ),
391
- ) -> bool:
392
- """Jac's ignore stmt feature."""
393
- if isinstance(walker, WalkerArchitype):
394
- return walker.__jac__.ignore_node(
395
- (i.__jac__ for i in expr) if isinstance(expr, list) else [expr.__jac__]
396
- )
397
- else:
398
- raise TypeError("Invalid walker object")
399
-
400
- @staticmethod
401
- @hookimpl
402
- def visit_node(
403
- walker: WalkerArchitype,
404
- expr: (
405
- list[NodeArchitype | EdgeArchitype]
406
- | list[NodeArchitype]
407
- | list[EdgeArchitype]
408
- | NodeArchitype
409
- | EdgeArchitype
410
- ),
411
- ) -> bool:
412
- """Jac's visit stmt feature."""
413
- if isinstance(walker, WalkerArchitype):
414
- return walker.__jac__.visit_node(
415
- (i.__jac__ for i in expr) if isinstance(expr, list) else [expr.__jac__]
416
- )
417
- else:
418
- raise TypeError("Invalid walker object")
419
-
420
- @staticmethod
421
- @hookimpl
422
- def disengage(walker: WalkerArchitype) -> bool: # noqa: ANN401
423
- """Jac's disengage stmt feature."""
424
- walker.__jac__.disengage_now()
425
- return True
426
-
427
860
  @staticmethod
428
861
  @hookimpl
429
862
  def edge_ref(
@@ -444,19 +877,23 @@ class JacFeatureDefaults:
444
877
  if edges_only:
445
878
  connected_edges: list[EdgeArchitype] = []
446
879
  for node in node_obj:
447
- connected_edges += node.__jac__.get_edges(
448
- dir, filter_func, target_obj=targ_obj_set
880
+ edges = Jac.get_edges(
881
+ node.__jac__, dir, filter_func, target_obj=targ_obj_set
882
+ )
883
+ connected_edges.extend(
884
+ edge for edge in edges if edge not in connected_edges
449
885
  )
450
- return list(set(connected_edges))
886
+ return connected_edges
451
887
  else:
452
888
  connected_nodes: list[NodeArchitype] = []
453
889
  for node in node_obj:
890
+ nodes = Jac.edges_to_nodes(
891
+ node.__jac__, dir, filter_func, target_obj=targ_obj_set
892
+ )
454
893
  connected_nodes.extend(
455
- node.__jac__.edges_to_nodes(
456
- dir, filter_func, target_obj=targ_obj_set
457
- )
894
+ node for node in nodes if node not in connected_nodes
458
895
  )
459
- return list(set(connected_nodes))
896
+ return connected_nodes
460
897
 
461
898
  @staticmethod
462
899
  @hookimpl
@@ -474,14 +911,12 @@ class JacFeatureDefaults:
474
911
  right = [right] if isinstance(right, NodeArchitype) else right
475
912
  edges = []
476
913
 
477
- root = Jac.get_root().__jac__
478
-
479
914
  for i in left:
480
915
  _left = i.__jac__
481
- if root.has_connect_access(_left):
916
+ if Jac.check_connect_access(_left):
482
917
  for j in right:
483
918
  _right = j.__jac__
484
- if root.has_connect_access(_right):
919
+ if Jac.check_connect_access(_right):
485
920
  edges.append(edge_spec(_left, _right))
486
921
  return right if not edges_only else edges
487
922
 
@@ -498,8 +933,6 @@ class JacFeatureDefaults:
498
933
  left = [left] if isinstance(left, NodeArchitype) else left
499
934
  right = [right] if isinstance(right, NodeArchitype) else right
500
935
 
501
- root = Jac.get_root().__jac__
502
-
503
936
  for i in left:
504
937
  node = i.__jac__
505
938
  for anchor in set(node.edges):
@@ -514,17 +947,17 @@ class JacFeatureDefaults:
514
947
  dir in [EdgeDir.OUT, EdgeDir.ANY]
515
948
  and node == source
516
949
  and target.architype in right
517
- and root.has_write_access(target)
950
+ and Jac.check_write_access(target)
518
951
  ):
519
- anchor.destroy() if anchor.persistent else anchor.detach()
952
+ Jac.destroy(anchor) if anchor.persistent else Jac.detach(anchor)
520
953
  disconnect_occurred = True
521
954
  if (
522
955
  dir in [EdgeDir.IN, EdgeDir.ANY]
523
956
  and node == target
524
957
  and source.architype in right
525
- and root.has_write_access(source)
958
+ and Jac.check_write_access(source)
526
959
  ):
527
- anchor.destroy() if anchor.persistent else anchor.detach()
960
+ Jac.destroy(anchor) if anchor.persistent else Jac.detach(anchor)
528
961
  disconnect_occurred = True
529
962
 
530
963
  return disconnect_occurred
@@ -565,7 +998,16 @@ class JacFeatureDefaults:
565
998
 
566
999
  def builder(source: NodeAnchor, target: NodeAnchor) -> EdgeArchitype:
567
1000
  edge = conn_type() if isinstance(conn_type, type) else conn_type
568
- edge.__attach__(source, target, is_undirected)
1001
+
1002
+ eanch = edge.__jac__ = EdgeAnchor(
1003
+ architype=edge,
1004
+ source=source,
1005
+ target=target,
1006
+ is_undirected=is_undirected,
1007
+ )
1008
+ source.edges.append(eanch)
1009
+ target.edges.append(eanch)
1010
+
569
1011
  if conn_assign:
570
1012
  for fld, val in zip(conn_assign[0], conn_assign[1]):
571
1013
  if hasattr(edge, fld):
@@ -573,13 +1015,44 @@ class JacFeatureDefaults:
573
1015
  else:
574
1016
  raise ValueError(f"Invalid attribute: {fld}")
575
1017
  if source.persistent or target.persistent:
576
- edge.__jac__.save()
577
- target.save()
578
- source.save()
1018
+ Jac.save(eanch)
1019
+ Jac.save(target)
1020
+ Jac.save(source)
579
1021
  return edge
580
1022
 
581
1023
  return builder
582
1024
 
1025
+ @staticmethod
1026
+ @hookimpl
1027
+ def save(obj: Architype | Anchor) -> None:
1028
+ """Destroy object."""
1029
+ anchor = obj.__jac__ if isinstance(obj, Architype) else obj
1030
+
1031
+ jctx = Jac.get_context()
1032
+
1033
+ anchor.persistent = True
1034
+ anchor.root = jctx.root.id
1035
+
1036
+ jctx.mem.set(anchor.id, anchor)
1037
+
1038
+ @staticmethod
1039
+ @hookimpl
1040
+ def destroy(obj: Architype | Anchor) -> None:
1041
+ """Destroy object."""
1042
+ anchor = obj.__jac__ if isinstance(obj, Architype) else obj
1043
+
1044
+ if Jac.check_write_access(anchor):
1045
+ match anchor:
1046
+ case NodeAnchor():
1047
+ for edge in anchor.edges:
1048
+ Jac.destroy(edge)
1049
+ case EdgeAnchor():
1050
+ Jac.detach(anchor)
1051
+ case _:
1052
+ pass
1053
+
1054
+ Jac.get_context().mem.remove(anchor.id)
1055
+
583
1056
  @staticmethod
584
1057
  @hookimpl
585
1058
  def get_semstr_type(
@@ -645,6 +1118,7 @@ class JacFeatureDefaults:
645
1118
  @staticmethod
646
1119
  @hookimpl
647
1120
  def get_sem_type(file_loc: str, attr: str) -> tuple[str | None, str | None]:
1121
+ """Jac's get_semstr_type implementation."""
648
1122
  with open(
649
1123
  os.path.join(
650
1124
  os.path.dirname(file_loc),
@@ -843,101 +1317,3 @@ class JacFeatureDefaults:
843
1317
  "include_info": [],
844
1318
  "exclude_info": [],
845
1319
  }
846
-
847
-
848
- class JacBuiltin:
849
- """Jac Builtins."""
850
-
851
- @staticmethod
852
- @hookimpl
853
- def dotgen(
854
- node: NodeArchitype,
855
- depth: int,
856
- traverse: bool,
857
- edge_type: list[str],
858
- bfs: bool,
859
- edge_limit: int,
860
- node_limit: int,
861
- dot_file: Optional[str],
862
- ) -> str:
863
- """Generate Dot file for visualizing nodes and edges."""
864
- edge_type = edge_type if edge_type else []
865
- visited_nodes: list[NodeArchitype] = []
866
- node_depths: dict[NodeArchitype, int] = {node: 0}
867
- queue: list = [[node, 0]]
868
- connections: list[tuple[NodeArchitype, NodeArchitype, EdgeArchitype]] = []
869
-
870
- def dfs(node: NodeArchitype, cur_depth: int) -> None:
871
- """Depth first search."""
872
- if node not in visited_nodes:
873
- visited_nodes.append(node)
874
- traverse_graph(
875
- node,
876
- cur_depth,
877
- depth,
878
- edge_type,
879
- traverse,
880
- connections,
881
- node_depths,
882
- visited_nodes,
883
- queue,
884
- bfs,
885
- dfs,
886
- node_limit,
887
- edge_limit,
888
- )
889
-
890
- if bfs:
891
- cur_depth = 0
892
- while queue:
893
- current_node, cur_depth = queue.pop(0)
894
- if current_node not in visited_nodes:
895
- visited_nodes.append(current_node)
896
- traverse_graph(
897
- current_node,
898
- cur_depth,
899
- depth,
900
- edge_type,
901
- traverse,
902
- connections,
903
- node_depths,
904
- visited_nodes,
905
- queue,
906
- bfs,
907
- dfs,
908
- node_limit,
909
- edge_limit,
910
- )
911
- else:
912
- dfs(node, cur_depth=0)
913
- dot_content = (
914
- 'digraph {\nnode [style="filled", shape="ellipse", '
915
- 'fillcolor="invis", fontcolor="black"];\n'
916
- )
917
- for source, target, edge in connections:
918
- dot_content += (
919
- f"{visited_nodes.index(source)} -> {visited_nodes.index(target)} "
920
- f' [label="{html.escape(str(edge.__jac__.architype))} "];\n'
921
- )
922
- for node_ in visited_nodes:
923
- color = (
924
- colors[node_depths[node_]] if node_depths[node_] < 25 else colors[24]
925
- )
926
- dot_content += (
927
- f'{visited_nodes.index(node_)} [label="{html.escape(str(node_.__jac__.architype))}"'
928
- f'fillcolor="{color}"];\n'
929
- )
930
- if dot_file:
931
- with open(dot_file, "w") as f:
932
- f.write(dot_content + "}")
933
- return dot_content + "}"
934
-
935
-
936
- class JacCmdDefaults:
937
- """Jac CLI command."""
938
-
939
- @staticmethod
940
- @hookimpl
941
- def create_cmd() -> None:
942
- """Create Jac CLI cmds."""
943
- pass