jaclang 0.7.22__py3-none-any.whl → 0.7.24__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 (33) hide show
  1. jaclang/__init__.py +5 -10
  2. jaclang/cli/cli.py +4 -1
  3. jaclang/compiler/absyntree.py +6 -4
  4. jaclang/compiler/parser.py +4 -2
  5. jaclang/compiler/passes/main/tests/fixtures/fstrings.jac +2 -0
  6. jaclang/compiler/passes/main/type_check_pass.py +8 -6
  7. jaclang/plugin/builtin.py +3 -3
  8. jaclang/plugin/default.py +588 -206
  9. jaclang/plugin/feature.py +274 -99
  10. jaclang/plugin/plugin.md +471 -0
  11. jaclang/plugin/spec.py +231 -86
  12. jaclang/plugin/tests/fixtures/other_root_access.jac +9 -9
  13. jaclang/plugin/tests/test_features.py +2 -2
  14. jaclang/runtimelib/architype.py +1 -370
  15. jaclang/runtimelib/constructs.py +2 -0
  16. jaclang/runtimelib/context.py +2 -4
  17. jaclang/runtimelib/machine.py +57 -0
  18. jaclang/runtimelib/memory.py +2 -4
  19. jaclang/settings.py +3 -0
  20. jaclang/tests/fixtures/arch_create_util.jac +7 -0
  21. jaclang/tests/fixtures/arch_rel_import_creation.jac +30 -0
  22. jaclang/tests/fixtures/builtin_dotgen.jac +6 -6
  23. jaclang/tests/fixtures/create_dynamic_architype.jac +35 -0
  24. jaclang/tests/fixtures/edge_node_walk.jac +1 -1
  25. jaclang/tests/fixtures/edges_walk.jac +1 -1
  26. jaclang/tests/fixtures/enum_inside_archtype.jac +16 -11
  27. jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
  28. jaclang/tests/fixtures/visit_order.jac +20 -0
  29. jaclang/tests/test_language.py +55 -1
  30. {jaclang-0.7.22.dist-info → jaclang-0.7.24.dist-info}/METADATA +2 -1
  31. {jaclang-0.7.22.dist-info → jaclang-0.7.24.dist-info}/RECORD +33 -28
  32. {jaclang-0.7.22.dist-info → jaclang-0.7.24.dist-info}/WHEEL +1 -1
  33. {jaclang-0.7.22.dist-info → jaclang-0.7.24.dist-info}/entry_points.txt +0 -0
jaclang/plugin/default.py CHANGED
@@ -11,59 +11,549 @@ 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
45
69
 
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:
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
+ edge_label = html.escape(str(edge.__jac__.architype))
514
+ dot_content += (
515
+ f"{visited_nodes.index(source)} -> {visited_nodes.index(target)} "
516
+ f' [label="{edge_label if "GenericEdge" not in edge_label else ""}"];\n'
517
+ )
518
+ for node_ in visited_nodes:
519
+ color = (
520
+ colors[node_depths[node_]] if node_depths[node_] < 25 else colors[24]
521
+ )
522
+ dot_content += (
523
+ f'{visited_nodes.index(node_)} [label="{html.escape(str(node_.__jac__.architype))}"'
524
+ f'fillcolor="{color}"];\n'
525
+ )
526
+ if dot_file:
527
+ with open(dot_file, "w") as f:
528
+ f.write(dot_content + "}")
529
+ return dot_content + "}"
530
+
531
+
532
+ class JacCmdImpl:
533
+ """Jac CLI command."""
534
+
535
+ @staticmethod
536
+ @hookimpl
537
+ def create_cmd() -> None:
538
+ """Create Jac CLI cmds."""
539
+ pass
540
+
541
+
542
+ class JacFeatureImpl(
543
+ JacAccessValidationImpl,
544
+ JacNodeImpl,
545
+ JacEdgeImpl,
546
+ JacWalkerImpl,
547
+ JacBuiltinImpl,
548
+ JacCmdImpl,
549
+ ):
64
550
  """Jac Feature."""
65
551
 
66
- pm = pluggy.PluginManager("jac")
552
+ @staticmethod
553
+ @hookimpl
554
+ def setup() -> None:
555
+ """Set Class References."""
556
+ pass
67
557
 
68
558
  @staticmethod
69
559
  @hookimpl
@@ -74,6 +564,7 @@ class JacFeatureDefaults:
74
564
  @staticmethod
75
565
  @hookimpl
76
566
  def get_object(id: str) -> Architype | None:
567
+ """Get object by id."""
77
568
  if id == "root":
78
569
  return Jac.get_context().root.architype
79
570
  elif obj := Jac.get_context().mem.find_by_id(UUID(id)):
@@ -84,6 +575,7 @@ class JacFeatureDefaults:
84
575
  @staticmethod
85
576
  @hookimpl
86
577
  def object_ref(obj: Architype) -> str:
578
+ """Get object's id."""
87
579
  return obj.__jac__.id.hex
88
580
 
89
581
  @staticmethod
@@ -363,66 +855,13 @@ class JacFeatureDefaults:
363
855
 
364
856
  @staticmethod
365
857
  @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
- @staticmethod
376
- @hookimpl
377
- def report(expr: Any) -> Any: # noqa: ANN401
858
+ def report(expr: Any, custom: bool) -> None: # noqa: ANN401
378
859
  """Jac's report stmt feature."""
379
-
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
- )
860
+ ctx = Jac.get_context()
861
+ if custom:
862
+ ctx.custom = expr
417
863
  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
864
+ ctx.reports.append(expr)
426
865
 
427
866
  @staticmethod
428
867
  @hookimpl
@@ -444,19 +883,23 @@ class JacFeatureDefaults:
444
883
  if edges_only:
445
884
  connected_edges: list[EdgeArchitype] = []
446
885
  for node in node_obj:
447
- connected_edges += node.__jac__.get_edges(
448
- dir, filter_func, target_obj=targ_obj_set
886
+ edges = Jac.get_edges(
887
+ node.__jac__, dir, filter_func, target_obj=targ_obj_set
888
+ )
889
+ connected_edges.extend(
890
+ edge for edge in edges if edge not in connected_edges
449
891
  )
450
- return list(set(connected_edges))
892
+ return connected_edges
451
893
  else:
452
894
  connected_nodes: list[NodeArchitype] = []
453
895
  for node in node_obj:
896
+ nodes = Jac.edges_to_nodes(
897
+ node.__jac__, dir, filter_func, target_obj=targ_obj_set
898
+ )
454
899
  connected_nodes.extend(
455
- node.__jac__.edges_to_nodes(
456
- dir, filter_func, target_obj=targ_obj_set
457
- )
900
+ node for node in nodes if node not in connected_nodes
458
901
  )
459
- return list(set(connected_nodes))
902
+ return connected_nodes
460
903
 
461
904
  @staticmethod
462
905
  @hookimpl
@@ -474,14 +917,12 @@ class JacFeatureDefaults:
474
917
  right = [right] if isinstance(right, NodeArchitype) else right
475
918
  edges = []
476
919
 
477
- root = Jac.get_root().__jac__
478
-
479
920
  for i in left:
480
921
  _left = i.__jac__
481
- if root.has_connect_access(_left):
922
+ if Jac.check_connect_access(_left):
482
923
  for j in right:
483
924
  _right = j.__jac__
484
- if root.has_connect_access(_right):
925
+ if Jac.check_connect_access(_right):
485
926
  edges.append(edge_spec(_left, _right))
486
927
  return right if not edges_only else edges
487
928
 
@@ -498,8 +939,6 @@ class JacFeatureDefaults:
498
939
  left = [left] if isinstance(left, NodeArchitype) else left
499
940
  right = [right] if isinstance(right, NodeArchitype) else right
500
941
 
501
- root = Jac.get_root().__jac__
502
-
503
942
  for i in left:
504
943
  node = i.__jac__
505
944
  for anchor in set(node.edges):
@@ -514,17 +953,17 @@ class JacFeatureDefaults:
514
953
  dir in [EdgeDir.OUT, EdgeDir.ANY]
515
954
  and node == source
516
955
  and target.architype in right
517
- and root.has_write_access(target)
956
+ and Jac.check_write_access(target)
518
957
  ):
519
- anchor.destroy() if anchor.persistent else anchor.detach()
958
+ Jac.destroy(anchor) if anchor.persistent else Jac.detach(anchor)
520
959
  disconnect_occurred = True
521
960
  if (
522
961
  dir in [EdgeDir.IN, EdgeDir.ANY]
523
962
  and node == target
524
963
  and source.architype in right
525
- and root.has_write_access(source)
964
+ and Jac.check_write_access(source)
526
965
  ):
527
- anchor.destroy() if anchor.persistent else anchor.detach()
966
+ Jac.destroy(anchor) if anchor.persistent else Jac.detach(anchor)
528
967
  disconnect_occurred = True
529
968
 
530
969
  return disconnect_occurred
@@ -565,7 +1004,16 @@ class JacFeatureDefaults:
565
1004
 
566
1005
  def builder(source: NodeAnchor, target: NodeAnchor) -> EdgeArchitype:
567
1006
  edge = conn_type() if isinstance(conn_type, type) else conn_type
568
- edge.__attach__(source, target, is_undirected)
1007
+
1008
+ eanch = edge.__jac__ = EdgeAnchor(
1009
+ architype=edge,
1010
+ source=source,
1011
+ target=target,
1012
+ is_undirected=is_undirected,
1013
+ )
1014
+ source.edges.append(eanch)
1015
+ target.edges.append(eanch)
1016
+
569
1017
  if conn_assign:
570
1018
  for fld, val in zip(conn_assign[0], conn_assign[1]):
571
1019
  if hasattr(edge, fld):
@@ -573,13 +1021,44 @@ class JacFeatureDefaults:
573
1021
  else:
574
1022
  raise ValueError(f"Invalid attribute: {fld}")
575
1023
  if source.persistent or target.persistent:
576
- edge.__jac__.save()
577
- target.save()
578
- source.save()
1024
+ Jac.save(eanch)
1025
+ Jac.save(target)
1026
+ Jac.save(source)
579
1027
  return edge
580
1028
 
581
1029
  return builder
582
1030
 
1031
+ @staticmethod
1032
+ @hookimpl
1033
+ def save(obj: Architype | Anchor) -> None:
1034
+ """Destroy object."""
1035
+ anchor = obj.__jac__ if isinstance(obj, Architype) else obj
1036
+
1037
+ jctx = Jac.get_context()
1038
+
1039
+ anchor.persistent = True
1040
+ anchor.root = jctx.root.id
1041
+
1042
+ jctx.mem.set(anchor.id, anchor)
1043
+
1044
+ @staticmethod
1045
+ @hookimpl
1046
+ def destroy(obj: Architype | Anchor) -> None:
1047
+ """Destroy object."""
1048
+ anchor = obj.__jac__ if isinstance(obj, Architype) else obj
1049
+
1050
+ if Jac.check_write_access(anchor):
1051
+ match anchor:
1052
+ case NodeAnchor():
1053
+ for edge in anchor.edges:
1054
+ Jac.destroy(edge)
1055
+ case EdgeAnchor():
1056
+ Jac.detach(anchor)
1057
+ case _:
1058
+ pass
1059
+
1060
+ Jac.get_context().mem.remove(anchor.id)
1061
+
583
1062
  @staticmethod
584
1063
  @hookimpl
585
1064
  def get_semstr_type(
@@ -645,6 +1124,7 @@ class JacFeatureDefaults:
645
1124
  @staticmethod
646
1125
  @hookimpl
647
1126
  def get_sem_type(file_loc: str, attr: str) -> tuple[str | None, str | None]:
1127
+ """Jac's get_semstr_type implementation."""
648
1128
  with open(
649
1129
  os.path.join(
650
1130
  os.path.dirname(file_loc),
@@ -843,101 +1323,3 @@ class JacFeatureDefaults:
843
1323
  "include_info": [],
844
1324
  "exclude_info": [],
845
1325
  }
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