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/__init__.py +5 -10
- jaclang/cli/cli.py +4 -1
- jaclang/compiler/absyntree.py +28 -3
- jaclang/compiler/passes/main/pyast_load_pass.py +8 -6
- jaclang/compiler/passes/main/tests/fixtures/fstrings.jac +44 -1
- jaclang/compiler/passes/main/type_check_pass.py +8 -6
- jaclang/plugin/builtin.py +3 -3
- jaclang/plugin/default.py +582 -206
- jaclang/plugin/feature.py +273 -98
- jaclang/plugin/spec.py +229 -85
- jaclang/plugin/tests/fixtures/other_root_access.jac +9 -9
- jaclang/plugin/tests/test_features.py +2 -2
- jaclang/runtimelib/architype.py +1 -370
- jaclang/runtimelib/constructs.py +2 -0
- jaclang/runtimelib/context.py +0 -4
- jaclang/runtimelib/machine.py +57 -0
- jaclang/runtimelib/memory.py +2 -4
- jaclang/tests/fixtures/arch_create_util.jac +7 -0
- jaclang/tests/fixtures/arch_rel_import_creation.jac +30 -0
- jaclang/tests/fixtures/create_dynamic_architype.jac +35 -0
- jaclang/tests/fixtures/edge_node_walk.jac +1 -1
- jaclang/tests/fixtures/edges_walk.jac +1 -1
- jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
- jaclang/tests/fixtures/visit_order.jac +20 -0
- jaclang/tests/test_language.py +72 -7
- {jaclang-0.7.21.dist-info → jaclang-0.7.23.dist-info}/METADATA +1 -1
- {jaclang-0.7.21.dist-info → jaclang-0.7.23.dist-info}/RECORD +29 -25
- {jaclang-0.7.21.dist-info → jaclang-0.7.23.dist-info}/WHEEL +0 -0
- {jaclang-0.7.21.dist-info → jaclang-0.7.23.dist-info}/entry_points.txt +0 -0
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
|
-
|
|
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.
|
|
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
|
-
|
|
28
|
-
JacTestCheck,
|
|
29
|
+
JacFeature as Jac,
|
|
29
30
|
NodeAnchor,
|
|
30
31
|
NodeArchitype,
|
|
32
|
+
P,
|
|
33
|
+
PyastGenPass,
|
|
31
34
|
Root,
|
|
32
|
-
|
|
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
|
-
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
916
|
+
if Jac.check_connect_access(_left):
|
|
482
917
|
for j in right:
|
|
483
918
|
_right = j.__jac__
|
|
484
|
-
if
|
|
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
|
|
950
|
+
and Jac.check_write_access(target)
|
|
518
951
|
):
|
|
519
|
-
|
|
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
|
|
958
|
+
and Jac.check_write_access(source)
|
|
526
959
|
):
|
|
527
|
-
|
|
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
|
-
|
|
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
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|