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.
- jaclang/__init__.py +5 -10
- jaclang/cli/cli.py +4 -1
- jaclang/compiler/absyntree.py +6 -4
- jaclang/compiler/parser.py +4 -2
- jaclang/compiler/passes/main/tests/fixtures/fstrings.jac +2 -0
- jaclang/compiler/passes/main/type_check_pass.py +8 -6
- jaclang/plugin/builtin.py +3 -3
- jaclang/plugin/default.py +588 -206
- jaclang/plugin/feature.py +274 -99
- jaclang/plugin/plugin.md +471 -0
- jaclang/plugin/spec.py +231 -86
- 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 +2 -4
- jaclang/runtimelib/machine.py +57 -0
- jaclang/runtimelib/memory.py +2 -4
- jaclang/settings.py +3 -0
- jaclang/tests/fixtures/arch_create_util.jac +7 -0
- jaclang/tests/fixtures/arch_rel_import_creation.jac +30 -0
- jaclang/tests/fixtures/builtin_dotgen.jac +6 -6
- 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/enum_inside_archtype.jac +16 -11
- jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
- jaclang/tests/fixtures/visit_order.jac +20 -0
- jaclang/tests/test_language.py +55 -1
- {jaclang-0.7.22.dist-info → jaclang-0.7.24.dist-info}/METADATA +2 -1
- {jaclang-0.7.22.dist-info → jaclang-0.7.24.dist-info}/RECORD +33 -28
- {jaclang-0.7.22.dist-info → jaclang-0.7.24.dist-info}/WHEEL +1 -1
- {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
|
-
|
|
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
|
|
45
69
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
381
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
922
|
+
if Jac.check_connect_access(_left):
|
|
482
923
|
for j in right:
|
|
483
924
|
_right = j.__jac__
|
|
484
|
-
if
|
|
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
|
|
956
|
+
and Jac.check_write_access(target)
|
|
518
957
|
):
|
|
519
|
-
|
|
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
|
|
964
|
+
and Jac.check_write_access(source)
|
|
526
965
|
):
|
|
527
|
-
|
|
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
|
-
|
|
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
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|