jaclang 0.7.22__py3-none-any.whl → 0.7.25__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 (65) hide show
  1. jaclang/__init__.py +5 -10
  2. jaclang/cli/cli.py +50 -30
  3. jaclang/compiler/__init__.py +2 -2
  4. jaclang/compiler/absyntree.py +87 -48
  5. jaclang/compiler/codeloc.py +7 -2
  6. jaclang/compiler/compile.py +10 -3
  7. jaclang/compiler/parser.py +26 -23
  8. jaclang/compiler/passes/ir_pass.py +2 -2
  9. jaclang/compiler/passes/main/def_impl_match_pass.py +46 -0
  10. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +146 -123
  11. jaclang/compiler/passes/main/import_pass.py +6 -2
  12. jaclang/compiler/passes/main/pyast_load_pass.py +36 -35
  13. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +7 -7
  14. jaclang/compiler/passes/main/registry_pass.py +3 -12
  15. jaclang/compiler/passes/main/tests/fixtures/defn_decl_mismatch.jac +19 -0
  16. jaclang/compiler/passes/main/tests/fixtures/fstrings.jac +2 -0
  17. jaclang/compiler/passes/main/tests/test_decl_def_match_pass.py +59 -0
  18. jaclang/compiler/passes/main/tests/test_registry_pass.py +2 -10
  19. jaclang/compiler/passes/main/tests/test_type_check_pass.py +1 -1
  20. jaclang/compiler/passes/main/type_check_pass.py +8 -6
  21. jaclang/compiler/passes/transform.py +27 -3
  22. jaclang/compiler/passes/utils/mypy_ast_build.py +246 -26
  23. jaclang/compiler/symtable.py +6 -0
  24. jaclang/compiler/tests/test_importer.py +2 -2
  25. jaclang/langserve/engine.py +14 -12
  26. jaclang/langserve/server.py +7 -2
  27. jaclang/langserve/tests/test_server.py +1 -1
  28. jaclang/langserve/utils.py +17 -3
  29. jaclang/plugin/builtin.py +3 -3
  30. jaclang/plugin/default.py +612 -236
  31. jaclang/plugin/feature.py +274 -99
  32. jaclang/plugin/plugin.md +471 -0
  33. jaclang/plugin/spec.py +231 -86
  34. jaclang/plugin/tests/fixtures/other_root_access.jac +9 -9
  35. jaclang/plugin/tests/test_features.py +2 -2
  36. jaclang/runtimelib/architype.py +1 -370
  37. jaclang/runtimelib/constructs.py +2 -0
  38. jaclang/runtimelib/context.py +2 -4
  39. jaclang/runtimelib/importer.py +7 -2
  40. jaclang/runtimelib/machine.py +78 -6
  41. jaclang/runtimelib/memory.py +2 -4
  42. jaclang/settings.py +3 -0
  43. jaclang/tests/fixtures/arch_create_util.jac +7 -0
  44. jaclang/tests/fixtures/arch_rel_import_creation.jac +30 -0
  45. jaclang/tests/fixtures/builtin_dotgen.jac +6 -6
  46. jaclang/tests/fixtures/create_dynamic_architype.jac +35 -0
  47. jaclang/tests/fixtures/edge_node_walk.jac +1 -1
  48. jaclang/tests/fixtures/edges_walk.jac +1 -1
  49. jaclang/tests/fixtures/enum_inside_archtype.jac +16 -11
  50. jaclang/tests/fixtures/expr_type.jac +54 -0
  51. jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
  52. jaclang/tests/fixtures/glob_multivar_statement.jac +15 -0
  53. jaclang/tests/fixtures/registry.jac +20 -8
  54. jaclang/tests/fixtures/visit_order.jac +20 -0
  55. jaclang/tests/foo/__init__.jac +0 -0
  56. jaclang/tests/main.jac +2 -0
  57. jaclang/tests/test_cli.py +68 -4
  58. jaclang/tests/test_language.py +113 -27
  59. jaclang/utils/helpers.py +92 -14
  60. jaclang/utils/lang_tools.py +6 -2
  61. jaclang/utils/treeprinter.py +4 -2
  62. {jaclang-0.7.22.dist-info → jaclang-0.7.25.dist-info}/METADATA +2 -1
  63. {jaclang-0.7.22.dist-info → jaclang-0.7.25.dist-info}/RECORD +65 -55
  64. {jaclang-0.7.22.dist-info → jaclang-0.7.25.dist-info}/WHEEL +1 -1
  65. {jaclang-0.7.22.dist-info → jaclang-0.7.25.dist-info}/entry_points.txt +0 -0
jaclang/plugin/default.py CHANGED
@@ -6,64 +6,553 @@ import ast as ast3
6
6
  import fnmatch
7
7
  import html
8
8
  import os
9
- import pickle
10
9
  import types
11
10
  from collections import OrderedDict
12
11
  from dataclasses import field
13
12
  from functools import wraps
13
+ from logging import getLogger
14
14
  from typing import Any, Callable, Mapping, Optional, Sequence, Type, Union
15
15
  from uuid import UUID
16
16
 
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
17
+ from jaclang.compiler.constant import colors
20
18
  from jaclang.compiler.semtable import SemInfo, SemRegistry, SemScope
21
- from jaclang.runtimelib.constructs import (
19
+ from jaclang.plugin.feature import (
20
+ AccessLevel,
21
+ Anchor,
22
22
  Architype,
23
23
  DSFunc,
24
24
  EdgeAnchor,
25
25
  EdgeArchitype,
26
+ EdgeDir,
26
27
  ExecutionContext,
27
- GenericEdge,
28
- JacTestCheck,
28
+ JacFeature as Jac,
29
29
  NodeAnchor,
30
30
  NodeArchitype,
31
+ P,
32
+ PyastGenPass,
31
33
  Root,
32
- WalkerAnchor,
34
+ T,
33
35
  WalkerArchitype,
36
+ ast,
37
+ )
38
+ from jaclang.runtimelib.constructs import (
39
+ GenericEdge,
40
+ JacTestCheck,
34
41
  )
35
42
  from jaclang.runtimelib.importer import ImportPathSpec, JacImporter, PythonImporter
36
43
  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
44
+ from jaclang.runtimelib.utils import collect_node_connections, traverse_graph
40
45
 
41
46
 
42
47
  import pluggy
43
48
 
44
49
  hookimpl = pluggy.HookimplMarker("jac")
50
+ logger = getLogger(__name__)
51
+
52
+
53
+ class JacAccessValidationImpl:
54
+ """Jac Access Validation Implementations."""
55
+
56
+ @staticmethod
57
+ @hookimpl
58
+ def allow_root(
59
+ architype: Architype, root_id: UUID, level: AccessLevel | int | str
60
+ ) -> None:
61
+ """Allow all access from target root graph to current Architype."""
62
+ level = AccessLevel.cast(level)
63
+ access = architype.__jac__.access.roots
64
+
65
+ _root_id = str(root_id)
66
+ if level != access.anchors.get(_root_id, AccessLevel.NO_ACCESS):
67
+ access.anchors[_root_id] = level
68
+
69
+ @staticmethod
70
+ @hookimpl
71
+ def disallow_root(
72
+ architype: Architype, root_id: UUID, level: AccessLevel | int | str
73
+ ) -> None:
74
+ """Disallow all access from target root graph to current Architype."""
75
+ level = AccessLevel.cast(level)
76
+ access = architype.__jac__.access.roots
77
+
78
+ access.anchors.pop(str(root_id), None)
79
+
80
+ @staticmethod
81
+ @hookimpl
82
+ def unrestrict(architype: Architype, level: AccessLevel | int | str) -> None:
83
+ """Allow everyone to access current Architype."""
84
+ anchor = architype.__jac__
85
+ level = AccessLevel.cast(level)
86
+ if level != anchor.access.all:
87
+ anchor.access.all = level
88
+
89
+ @staticmethod
90
+ @hookimpl
91
+ def restrict(architype: Architype) -> None:
92
+ """Disallow others to access current Architype."""
93
+ anchor = architype.__jac__
94
+ if anchor.access.all > AccessLevel.NO_ACCESS:
95
+ anchor.access.all = AccessLevel.NO_ACCESS
96
+
97
+ @staticmethod
98
+ @hookimpl
99
+ def check_read_access(to: Anchor) -> bool:
100
+ """Read Access Validation."""
101
+ if not (access_level := Jac.check_access_level(to) > AccessLevel.NO_ACCESS):
102
+ logger.info(
103
+ f"Current root doesn't have read access to {to.__class__.__name__}[{to.id}]"
104
+ )
105
+ return access_level
106
+
107
+ @staticmethod
108
+ @hookimpl
109
+ def check_connect_access(to: Anchor) -> bool:
110
+ """Write Access Validation."""
111
+ if not (access_level := Jac.check_access_level(to) > AccessLevel.READ):
112
+ logger.info(
113
+ f"Current root doesn't have connect access to {to.__class__.__name__}[{to.id}]"
114
+ )
115
+ return access_level
116
+
117
+ @staticmethod
118
+ @hookimpl
119
+ def check_write_access(to: Anchor) -> bool:
120
+ """Write Access Validation."""
121
+ if not (access_level := Jac.check_access_level(to) > AccessLevel.CONNECT):
122
+ logger.info(
123
+ f"Current root doesn't have write access to {to.__class__.__name__}[{to.id}]"
124
+ )
125
+ return access_level
126
+
127
+ @staticmethod
128
+ @hookimpl
129
+ def check_access_level(to: Anchor) -> AccessLevel:
130
+ """Access validation."""
131
+ if not to.persistent:
132
+ return AccessLevel.WRITE
133
+
134
+ jctx = Jac.get_context()
135
+
136
+ jroot = jctx.root
137
+
138
+ # if current root is system_root
139
+ # if current root id is equal to target anchor's root id
140
+ # if current root is the target anchor
141
+ if jroot == jctx.system_root or jroot.id == to.root or jroot == to:
142
+ return AccessLevel.WRITE
143
+
144
+ access_level = AccessLevel.NO_ACCESS
145
+
146
+ # if target anchor have set access.all
147
+ if (to_access := to.access).all > AccessLevel.NO_ACCESS:
148
+ access_level = to_access.all
149
+
150
+ # if target anchor's root have set allowed roots
151
+ # if current root is allowed to the whole graph of target anchor's root
152
+ if to.root and isinstance(to_root := jctx.mem.find_one(to.root), Anchor):
153
+ if to_root.access.all > access_level:
154
+ access_level = to_root.access.all
155
+
156
+ level = to_root.access.roots.check(str(jroot.id))
157
+ if level > AccessLevel.NO_ACCESS and access_level == AccessLevel.NO_ACCESS:
158
+ access_level = level
159
+
160
+ # if target anchor have set allowed roots
161
+ # if current root is allowed to target anchor
162
+ level = to_access.roots.check(str(jroot.id))
163
+ if level > AccessLevel.NO_ACCESS and access_level == AccessLevel.NO_ACCESS:
164
+ access_level = level
165
+
166
+ return access_level
167
+
168
+
169
+ class JacNodeImpl:
170
+ """Jac Node Operations."""
171
+
172
+ @staticmethod
173
+ @hookimpl
174
+ def node_dot(node: NodeArchitype, dot_file: Optional[str]) -> str:
175
+ """Generate Dot file for visualizing nodes and edges."""
176
+ visited_nodes: set[NodeAnchor] = set()
177
+ connections: set[tuple[NodeArchitype, NodeArchitype, str]] = set()
178
+ unique_node_id_dict = {}
179
+
180
+ collect_node_connections(node.__jac__, visited_nodes, connections)
181
+ dot_content = 'digraph {\nnode [style="filled", shape="ellipse", fillcolor="invis", fontcolor="black"];\n'
182
+ for idx, i in enumerate([nodes_.architype for nodes_ in visited_nodes]):
183
+ unique_node_id_dict[i] = (i.__class__.__name__, str(idx))
184
+ dot_content += f'{idx} [label="{i}"];\n'
185
+ dot_content += 'edge [color="gray", style="solid"];\n'
186
+
187
+ for pair in list(set(connections)):
188
+ dot_content += (
189
+ f"{unique_node_id_dict[pair[0]][1]} -> {unique_node_id_dict[pair[1]][1]}"
190
+ f' [label="{pair[2]}"];\n'
191
+ )
192
+ if dot_file:
193
+ with open(dot_file, "w") as f:
194
+ f.write(dot_content + "}")
195
+ return dot_content + "}"
196
+
197
+ @staticmethod
198
+ @hookimpl
199
+ def get_edges(
200
+ node: NodeAnchor,
201
+ dir: EdgeDir,
202
+ filter_func: Optional[Callable[[list[EdgeArchitype]], list[EdgeArchitype]]],
203
+ target_obj: Optional[list[NodeArchitype]],
204
+ ) -> list[EdgeArchitype]:
205
+ """Get edges connected to this node."""
206
+ ret_edges: list[EdgeArchitype] = []
207
+ for anchor in node.edges:
208
+ if (
209
+ (source := anchor.source)
210
+ and (target := anchor.target)
211
+ and (not filter_func or filter_func([anchor.architype]))
212
+ and source.architype
213
+ and target.architype
214
+ ):
215
+ if (
216
+ dir in [EdgeDir.OUT, EdgeDir.ANY]
217
+ and node == source
218
+ and (not target_obj or target.architype in target_obj)
219
+ and Jac.check_read_access(target)
220
+ ):
221
+ ret_edges.append(anchor.architype)
222
+ if (
223
+ dir in [EdgeDir.IN, EdgeDir.ANY]
224
+ and node == target
225
+ and (not target_obj or source.architype in target_obj)
226
+ and Jac.check_read_access(source)
227
+ ):
228
+ ret_edges.append(anchor.architype)
229
+ return ret_edges
230
+
231
+ @staticmethod
232
+ @hookimpl
233
+ def edges_to_nodes(
234
+ node: NodeAnchor,
235
+ dir: EdgeDir,
236
+ filter_func: Optional[Callable[[list[EdgeArchitype]], list[EdgeArchitype]]],
237
+ target_obj: Optional[list[NodeArchitype]],
238
+ ) -> list[NodeArchitype]:
239
+ """Get set of nodes connected to this node."""
240
+ ret_edges: list[NodeArchitype] = []
241
+ for anchor in node.edges:
242
+ if (
243
+ (source := anchor.source)
244
+ and (target := anchor.target)
245
+ and (not filter_func or filter_func([anchor.architype]))
246
+ and source.architype
247
+ and target.architype
248
+ ):
249
+ if (
250
+ dir in [EdgeDir.OUT, EdgeDir.ANY]
251
+ and node == source
252
+ and (not target_obj or target.architype in target_obj)
253
+ and Jac.check_read_access(target)
254
+ ):
255
+ ret_edges.append(target.architype)
256
+ if (
257
+ dir in [EdgeDir.IN, EdgeDir.ANY]
258
+ and node == target
259
+ and (not target_obj or source.architype in target_obj)
260
+ and Jac.check_read_access(source)
261
+ ):
262
+ ret_edges.append(source.architype)
263
+ return ret_edges
264
+
265
+ @staticmethod
266
+ @hookimpl
267
+ def remove_edge(node: NodeAnchor, edge: EdgeAnchor) -> None:
268
+ """Remove reference without checking sync status."""
269
+ for idx, ed in enumerate(node.edges):
270
+ if ed.id == edge.id:
271
+ node.edges.pop(idx)
272
+ break
273
+
274
+
275
+ class JacEdgeImpl:
276
+ """Jac Edge Operations."""
277
+
278
+ @staticmethod
279
+ @hookimpl
280
+ def detach(edge: EdgeAnchor) -> None:
281
+ """Detach edge from nodes."""
282
+ Jac.remove_edge(node=edge.source, edge=edge)
283
+ Jac.remove_edge(node=edge.target, edge=edge)
284
+
285
+
286
+ class JacWalkerImpl:
287
+ """Jac Edge Operations."""
288
+
289
+ @staticmethod
290
+ @hookimpl
291
+ def visit_node(
292
+ walker: WalkerArchitype,
293
+ expr: (
294
+ list[NodeArchitype | EdgeArchitype]
295
+ | list[NodeArchitype]
296
+ | list[EdgeArchitype]
297
+ | NodeArchitype
298
+ | EdgeArchitype
299
+ ),
300
+ ) -> bool:
301
+ """Jac's visit stmt feature."""
302
+ if isinstance(walker, WalkerArchitype):
303
+ """Walker visits node."""
304
+ wanch = walker.__jac__
305
+ before_len = len(wanch.next)
306
+ for anchor in (
307
+ (i.__jac__ for i in expr) if isinstance(expr, list) else [expr.__jac__]
308
+ ):
309
+ if anchor not in wanch.ignores:
310
+ if isinstance(anchor, NodeAnchor):
311
+ wanch.next.append(anchor)
312
+ elif isinstance(anchor, EdgeAnchor):
313
+ if target := anchor.target:
314
+ wanch.next.append(target)
315
+ else:
316
+ raise ValueError("Edge has no target.")
317
+ return len(wanch.next) > before_len
318
+ else:
319
+ raise TypeError("Invalid walker object")
320
+
321
+ @staticmethod
322
+ @hookimpl
323
+ def ignore(
324
+ walker: WalkerArchitype,
325
+ expr: (
326
+ list[NodeArchitype | EdgeArchitype]
327
+ | list[NodeArchitype]
328
+ | list[EdgeArchitype]
329
+ | NodeArchitype
330
+ | EdgeArchitype
331
+ ),
332
+ ) -> bool:
333
+ """Jac's ignore stmt feature."""
334
+ if isinstance(walker, WalkerArchitype):
335
+ wanch = walker.__jac__
336
+ before_len = len(wanch.ignores)
337
+ for anchor in (
338
+ (i.__jac__ for i in expr) if isinstance(expr, list) else [expr.__jac__]
339
+ ):
340
+ if anchor not in wanch.ignores:
341
+ if isinstance(anchor, NodeAnchor):
342
+ wanch.ignores.append(anchor)
343
+ elif isinstance(anchor, EdgeAnchor):
344
+ if target := anchor.target:
345
+ wanch.ignores.append(target)
346
+ else:
347
+ raise ValueError("Edge has no target.")
348
+ return len(wanch.ignores) > before_len
349
+ else:
350
+ raise TypeError("Invalid walker object")
351
+
352
+ @staticmethod
353
+ @hookimpl
354
+ def spawn_call(op1: Architype, op2: Architype) -> WalkerArchitype:
355
+ """Invoke data spatial call."""
356
+ if isinstance(op1, WalkerArchitype):
357
+ warch = op1
358
+ walker = op1.__jac__
359
+ if isinstance(op2, NodeArchitype):
360
+ node = op2.__jac__
361
+ elif isinstance(op2, EdgeArchitype):
362
+ node = op2.__jac__.target
363
+ else:
364
+ raise TypeError("Invalid target object")
365
+ elif isinstance(op2, WalkerArchitype):
366
+ warch = op2
367
+ walker = op2.__jac__
368
+ if isinstance(op1, NodeArchitype):
369
+ node = op1.__jac__
370
+ elif isinstance(op1, EdgeArchitype):
371
+ node = op1.__jac__.target
372
+ else:
373
+ raise TypeError("Invalid target object")
374
+ else:
375
+ raise TypeError("Invalid walker object")
376
+
377
+ walker.path = []
378
+ walker.next = [node]
379
+ if walker.next:
380
+ current_node = walker.next[-1].architype
381
+ for i in warch._jac_entry_funcs_:
382
+ if not i.trigger:
383
+ if i.func:
384
+ i.func(warch, current_node)
385
+ else:
386
+ raise ValueError(f"No function {i.name} to call.")
387
+ while len(walker.next):
388
+ if current_node := walker.next.pop(0).architype:
389
+ for i in current_node._jac_entry_funcs_:
390
+ if not i.trigger or isinstance(warch, i.trigger):
391
+ if i.func:
392
+ i.func(current_node, warch)
393
+ else:
394
+ raise ValueError(f"No function {i.name} to call.")
395
+ if walker.disengaged:
396
+ return warch
397
+ for i in warch._jac_entry_funcs_:
398
+ if not i.trigger or isinstance(current_node, i.trigger):
399
+ if i.func and i.trigger:
400
+ i.func(warch, current_node)
401
+ elif not i.trigger:
402
+ continue
403
+ else:
404
+ raise ValueError(f"No function {i.name} to call.")
405
+ if walker.disengaged:
406
+ return warch
407
+ for i in warch._jac_exit_funcs_:
408
+ if not i.trigger or isinstance(current_node, i.trigger):
409
+ if i.func and i.trigger:
410
+ i.func(warch, current_node)
411
+ elif not i.trigger:
412
+ continue
413
+ else:
414
+ raise ValueError(f"No function {i.name} to call.")
415
+ if walker.disengaged:
416
+ return warch
417
+ for i in current_node._jac_exit_funcs_:
418
+ if not i.trigger or isinstance(warch, i.trigger):
419
+ if i.func:
420
+ i.func(current_node, warch)
421
+ else:
422
+ raise ValueError(f"No function {i.name} to call.")
423
+ if walker.disengaged:
424
+ return warch
425
+ for i in warch._jac_exit_funcs_:
426
+ if not i.trigger:
427
+ if i.func:
428
+ i.func(warch, current_node)
429
+ else:
430
+ raise ValueError(f"No function {i.name} to call.")
431
+ walker.ignores = []
432
+ return warch
433
+
434
+ @staticmethod
435
+ @hookimpl
436
+ def disengage(walker: WalkerArchitype) -> bool: # noqa: ANN401
437
+ """Jac's disengage stmt feature."""
438
+ walker.__jac__.disengaged = True
439
+ return True
440
+
45
441
 
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:
442
+ class JacBuiltinImpl:
443
+ """Jac Builtins."""
444
+
445
+ @staticmethod
446
+ @hookimpl
447
+ def dotgen(
448
+ node: NodeArchitype,
449
+ depth: int,
450
+ traverse: bool,
451
+ edge_type: Optional[list[str]],
452
+ bfs: bool,
453
+ edge_limit: int,
454
+ node_limit: int,
455
+ dot_file: Optional[str],
456
+ ) -> str:
457
+ """Generate Dot file for visualizing nodes and edges."""
458
+ edge_type = edge_type if edge_type else []
459
+ visited_nodes: list[NodeArchitype] = []
460
+ node_depths: dict[NodeArchitype, int] = {node: 0}
461
+ queue: list = [[node, 0]]
462
+ connections: list[tuple[NodeArchitype, NodeArchitype, EdgeArchitype]] = []
463
+
464
+ def dfs(node: NodeArchitype, cur_depth: int) -> None:
465
+ """Depth first search."""
466
+ if node not in visited_nodes:
467
+ visited_nodes.append(node)
468
+ traverse_graph(
469
+ node,
470
+ cur_depth,
471
+ depth,
472
+ edge_type,
473
+ traverse,
474
+ connections,
475
+ node_depths,
476
+ visited_nodes,
477
+ queue,
478
+ bfs,
479
+ dfs,
480
+ node_limit,
481
+ edge_limit,
482
+ )
483
+
484
+ if bfs:
485
+ cur_depth = 0
486
+ while queue:
487
+ current_node, cur_depth = queue.pop(0)
488
+ if current_node not in visited_nodes:
489
+ visited_nodes.append(current_node)
490
+ traverse_graph(
491
+ current_node,
492
+ cur_depth,
493
+ depth,
494
+ edge_type,
495
+ traverse,
496
+ connections,
497
+ node_depths,
498
+ visited_nodes,
499
+ queue,
500
+ bfs,
501
+ dfs,
502
+ node_limit,
503
+ edge_limit,
504
+ )
505
+ else:
506
+ dfs(node, cur_depth=0)
507
+ dot_content = (
508
+ 'digraph {\nnode [style="filled", shape="ellipse", '
509
+ 'fillcolor="invis", fontcolor="black"];\n'
510
+ )
511
+ for source, target, edge in connections:
512
+ edge_label = html.escape(str(edge.__jac__.architype))
513
+ dot_content += (
514
+ f"{visited_nodes.index(source)} -> {visited_nodes.index(target)} "
515
+ f' [label="{edge_label if "GenericEdge" not in edge_label else ""}"];\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
+
530
+
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
@@ -260,7 +751,9 @@ class JacFeatureDefaults:
260
751
 
261
752
  jac_machine = JacMachine.get(base_path)
262
753
  if not jac_machine.jac_program:
263
- jac_machine.attach_program(JacProgram(mod_bundle=None, bytecode=None))
754
+ jac_machine.attach_program(
755
+ JacProgram(mod_bundle=None, bytecode=None, sem_ir=None)
756
+ )
264
757
 
265
758
  if lng == "py":
266
759
  import_result = PythonImporter(JacMachine.get()).run_import(spec)
@@ -309,7 +802,7 @@ class JacFeatureDefaults:
309
802
  if mod_name.endswith(".test"):
310
803
  mod_name = mod_name[:-5]
311
804
  JacTestCheck.reset()
312
- Jac.jac_import(target=mod_name, base_path=base)
805
+ Jac.jac_import(target=mod_name, base_path=base, cachable=False)
313
806
  JacTestCheck.run_test(xit, maxfail, verbose)
314
807
  ret_count = JacTestCheck.failcount
315
808
  else:
@@ -363,66 +856,13 @@ class JacFeatureDefaults:
363
856
 
364
857
  @staticmethod
365
858
  @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
859
+ def report(expr: Any, custom: bool) -> None: # noqa: ANN401
378
860
  """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
- )
861
+ ctx = Jac.get_context()
862
+ if custom:
863
+ ctx.custom = expr
397
864
  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
865
+ ctx.reports.append(expr)
426
866
 
427
867
  @staticmethod
428
868
  @hookimpl
@@ -444,19 +884,23 @@ class JacFeatureDefaults:
444
884
  if edges_only:
445
885
  connected_edges: list[EdgeArchitype] = []
446
886
  for node in node_obj:
447
- connected_edges += node.__jac__.get_edges(
448
- dir, filter_func, target_obj=targ_obj_set
887
+ edges = Jac.get_edges(
888
+ node.__jac__, dir, filter_func, target_obj=targ_obj_set
449
889
  )
450
- return list(set(connected_edges))
890
+ connected_edges.extend(
891
+ edge for edge in edges if edge not in connected_edges
892
+ )
893
+ return connected_edges
451
894
  else:
452
895
  connected_nodes: list[NodeArchitype] = []
453
896
  for node in node_obj:
897
+ nodes = Jac.edges_to_nodes(
898
+ node.__jac__, dir, filter_func, target_obj=targ_obj_set
899
+ )
454
900
  connected_nodes.extend(
455
- node.__jac__.edges_to_nodes(
456
- dir, filter_func, target_obj=targ_obj_set
457
- )
901
+ node for node in nodes if node not in connected_nodes
458
902
  )
459
- return list(set(connected_nodes))
903
+ return connected_nodes
460
904
 
461
905
  @staticmethod
462
906
  @hookimpl
@@ -474,14 +918,12 @@ class JacFeatureDefaults:
474
918
  right = [right] if isinstance(right, NodeArchitype) else right
475
919
  edges = []
476
920
 
477
- root = Jac.get_root().__jac__
478
-
479
921
  for i in left:
480
922
  _left = i.__jac__
481
- if root.has_connect_access(_left):
923
+ if Jac.check_connect_access(_left):
482
924
  for j in right:
483
925
  _right = j.__jac__
484
- if root.has_connect_access(_right):
926
+ if Jac.check_connect_access(_right):
485
927
  edges.append(edge_spec(_left, _right))
486
928
  return right if not edges_only else edges
487
929
 
@@ -498,8 +940,6 @@ class JacFeatureDefaults:
498
940
  left = [left] if isinstance(left, NodeArchitype) else left
499
941
  right = [right] if isinstance(right, NodeArchitype) else right
500
942
 
501
- root = Jac.get_root().__jac__
502
-
503
943
  for i in left:
504
944
  node = i.__jac__
505
945
  for anchor in set(node.edges):
@@ -514,17 +954,17 @@ class JacFeatureDefaults:
514
954
  dir in [EdgeDir.OUT, EdgeDir.ANY]
515
955
  and node == source
516
956
  and target.architype in right
517
- and root.has_write_access(target)
957
+ and Jac.check_write_access(target)
518
958
  ):
519
- anchor.destroy() if anchor.persistent else anchor.detach()
959
+ Jac.destroy(anchor) if anchor.persistent else Jac.detach(anchor)
520
960
  disconnect_occurred = True
521
961
  if (
522
962
  dir in [EdgeDir.IN, EdgeDir.ANY]
523
963
  and node == target
524
964
  and source.architype in right
525
- and root.has_write_access(source)
965
+ and Jac.check_write_access(source)
526
966
  ):
527
- anchor.destroy() if anchor.persistent else anchor.detach()
967
+ Jac.destroy(anchor) if anchor.persistent else Jac.detach(anchor)
528
968
  disconnect_occurred = True
529
969
 
530
970
  return disconnect_occurred
@@ -565,7 +1005,16 @@ class JacFeatureDefaults:
565
1005
 
566
1006
  def builder(source: NodeAnchor, target: NodeAnchor) -> EdgeArchitype:
567
1007
  edge = conn_type() if isinstance(conn_type, type) else conn_type
568
- edge.__attach__(source, target, is_undirected)
1008
+
1009
+ eanch = edge.__jac__ = EdgeAnchor(
1010
+ architype=edge,
1011
+ source=source,
1012
+ target=target,
1013
+ is_undirected=is_undirected,
1014
+ )
1015
+ source.edges.append(eanch)
1016
+ target.edges.append(eanch)
1017
+
569
1018
  if conn_assign:
570
1019
  for fld, val in zip(conn_assign[0], conn_assign[1]):
571
1020
  if hasattr(edge, fld):
@@ -573,29 +1022,58 @@ class JacFeatureDefaults:
573
1022
  else:
574
1023
  raise ValueError(f"Invalid attribute: {fld}")
575
1024
  if source.persistent or target.persistent:
576
- edge.__jac__.save()
577
- target.save()
578
- source.save()
1025
+ Jac.save(eanch)
1026
+ Jac.save(target)
1027
+ Jac.save(source)
579
1028
  return edge
580
1029
 
581
1030
  return builder
582
1031
 
1032
+ @staticmethod
1033
+ @hookimpl
1034
+ def save(obj: Architype | Anchor) -> None:
1035
+ """Destroy object."""
1036
+ anchor = obj.__jac__ if isinstance(obj, Architype) else obj
1037
+
1038
+ jctx = Jac.get_context()
1039
+
1040
+ anchor.persistent = True
1041
+ anchor.root = jctx.root.id
1042
+
1043
+ jctx.mem.set(anchor.id, anchor)
1044
+
1045
+ @staticmethod
1046
+ @hookimpl
1047
+ def destroy(obj: Architype | Anchor) -> None:
1048
+ """Destroy object."""
1049
+ anchor = obj.__jac__ if isinstance(obj, Architype) else obj
1050
+
1051
+ if Jac.check_write_access(anchor):
1052
+ match anchor:
1053
+ case NodeAnchor():
1054
+ for edge in anchor.edges:
1055
+ Jac.destroy(edge)
1056
+ case EdgeAnchor():
1057
+ Jac.detach(anchor)
1058
+ case _:
1059
+ pass
1060
+
1061
+ Jac.get_context().mem.remove(anchor.id)
1062
+
583
1063
  @staticmethod
584
1064
  @hookimpl
585
1065
  def get_semstr_type(
586
1066
  file_loc: str, scope: str, attr: str, return_semstr: bool
587
1067
  ) -> Optional[str]:
588
1068
  """Jac's get_semstr_type feature."""
1069
+ from jaclang.compiler.semtable import SemInfo, SemScope, SemRegistry
1070
+ from jaclang.runtimelib.machine import JacMachine
1071
+
589
1072
  _scope = SemScope.get_scope_from_str(scope)
590
- with open(
591
- os.path.join(
592
- os.path.dirname(file_loc),
593
- "__jac_gen__",
594
- os.path.basename(file_loc).replace(".jac", ".registry.pkl"),
595
- ),
596
- "rb",
597
- ) as f:
598
- mod_registry: SemRegistry = pickle.load(f)
1073
+ jac_program = JacMachine.get().jac_program
1074
+ mod_registry: SemRegistry = (
1075
+ jac_program.sem_ir if jac_program is not None else SemRegistry()
1076
+ )
599
1077
  _, attr_seminfo = mod_registry.lookup(_scope, attr)
600
1078
  if attr_seminfo and isinstance(attr_seminfo, SemInfo):
601
1079
  return attr_seminfo.semstr if return_semstr else attr_seminfo.type
@@ -605,15 +1083,12 @@ class JacFeatureDefaults:
605
1083
  @hookimpl
606
1084
  def obj_scope(file_loc: str, attr: str) -> str:
607
1085
  """Jac's gather_scope feature."""
608
- with open(
609
- os.path.join(
610
- os.path.dirname(file_loc),
611
- "__jac_gen__",
612
- os.path.basename(file_loc).replace(".jac", ".registry.pkl"),
613
- ),
614
- "rb",
615
- ) as f:
616
- mod_registry: SemRegistry = pickle.load(f)
1086
+ from jaclang.runtimelib.machine import JacMachine
1087
+
1088
+ jac_program = JacMachine.get().jac_program
1089
+ mod_registry: SemRegistry = (
1090
+ jac_program.sem_ir if jac_program is not None else SemRegistry()
1091
+ )
617
1092
 
618
1093
  attr_scope = None
619
1094
  for x in attr.split("."):
@@ -645,15 +1120,14 @@ class JacFeatureDefaults:
645
1120
  @staticmethod
646
1121
  @hookimpl
647
1122
  def get_sem_type(file_loc: str, attr: str) -> tuple[str | None, str | None]:
648
- with open(
649
- os.path.join(
650
- os.path.dirname(file_loc),
651
- "__jac_gen__",
652
- os.path.basename(file_loc).replace(".jac", ".registry.pkl"),
653
- ),
654
- "rb",
655
- ) as f:
656
- mod_registry: SemRegistry = pickle.load(f)
1123
+ """Jac's get_semstr_type implementation."""
1124
+ from jaclang.runtimelib.machine import JacMachine
1125
+ from jaclang.compiler.semtable import SemInfo, SemScope
1126
+
1127
+ jac_program = JacMachine.get().jac_program
1128
+ mod_registry: SemRegistry = (
1129
+ jac_program.sem_ir if jac_program is not None else SemRegistry()
1130
+ )
657
1131
 
658
1132
  attr_scope = None
659
1133
  for x in attr.split("."):
@@ -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