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/runtimelib/architype.py
CHANGED
|
@@ -7,12 +7,9 @@ from enum import IntEnum
|
|
|
7
7
|
from logging import getLogger
|
|
8
8
|
from pickle import dumps
|
|
9
9
|
from types import UnionType
|
|
10
|
-
from typing import Any, Callable, ClassVar,
|
|
10
|
+
from typing import Any, Callable, ClassVar, Optional, TypeVar
|
|
11
11
|
from uuid import UUID, uuid4
|
|
12
12
|
|
|
13
|
-
from jaclang.compiler.constant import EdgeDir
|
|
14
|
-
from jaclang.runtimelib.utils import collect_node_connections
|
|
15
|
-
|
|
16
13
|
logger = getLogger(__name__)
|
|
17
14
|
|
|
18
15
|
TARCH = TypeVar("TARCH", bound="Architype")
|
|
@@ -77,128 +74,6 @@ class Anchor:
|
|
|
77
74
|
persistent: bool = False
|
|
78
75
|
hash: int = 0
|
|
79
76
|
|
|
80
|
-
##########################################################################
|
|
81
|
-
# ACCESS CONTROL: TODO: Make Base Type #
|
|
82
|
-
##########################################################################
|
|
83
|
-
|
|
84
|
-
def allow_root(
|
|
85
|
-
self, root_id: UUID, level: AccessLevel | int | str = AccessLevel.READ
|
|
86
|
-
) -> None:
|
|
87
|
-
"""Allow all access from target root graph to current Architype."""
|
|
88
|
-
level = AccessLevel.cast(level)
|
|
89
|
-
access = self.access.roots
|
|
90
|
-
|
|
91
|
-
_root_id = str(root_id)
|
|
92
|
-
if level != access.anchors.get(_root_id, AccessLevel.NO_ACCESS):
|
|
93
|
-
access.anchors[_root_id] = level
|
|
94
|
-
|
|
95
|
-
def disallow_root(
|
|
96
|
-
self, root_id: UUID, level: AccessLevel | int | str = AccessLevel.READ
|
|
97
|
-
) -> None:
|
|
98
|
-
"""Disallow all access from target root graph to current Architype."""
|
|
99
|
-
level = AccessLevel.cast(level)
|
|
100
|
-
access = self.access.roots
|
|
101
|
-
|
|
102
|
-
access.anchors.pop(str(root_id), None)
|
|
103
|
-
|
|
104
|
-
def unrestrict(self, level: AccessLevel | int | str = AccessLevel.READ) -> None:
|
|
105
|
-
"""Allow everyone to access current Architype."""
|
|
106
|
-
level = AccessLevel.cast(level)
|
|
107
|
-
if level != self.access.all:
|
|
108
|
-
self.access.all = level
|
|
109
|
-
|
|
110
|
-
def restrict(self) -> None:
|
|
111
|
-
"""Disallow others to access current Architype."""
|
|
112
|
-
if self.access.all > AccessLevel.NO_ACCESS:
|
|
113
|
-
self.access.all = AccessLevel.NO_ACCESS
|
|
114
|
-
|
|
115
|
-
def has_read_access(self, to: Anchor) -> bool:
|
|
116
|
-
"""Read Access Validation."""
|
|
117
|
-
if not (access_level := self.access_level(to) > AccessLevel.NO_ACCESS):
|
|
118
|
-
logger.info(
|
|
119
|
-
f"Current root doesn't have read access to {to.__class__.__name__}[{to.id}]"
|
|
120
|
-
)
|
|
121
|
-
return access_level
|
|
122
|
-
|
|
123
|
-
def has_connect_access(self, to: Anchor) -> bool:
|
|
124
|
-
"""Write Access Validation."""
|
|
125
|
-
if not (access_level := self.access_level(to) > AccessLevel.READ):
|
|
126
|
-
logger.info(
|
|
127
|
-
f"Current root doesn't have connect access to {to.__class__.__name__}[{to.id}]"
|
|
128
|
-
)
|
|
129
|
-
return access_level
|
|
130
|
-
|
|
131
|
-
def has_write_access(self, to: Anchor) -> bool:
|
|
132
|
-
"""Write Access Validation."""
|
|
133
|
-
if not (access_level := self.access_level(to) > AccessLevel.CONNECT):
|
|
134
|
-
logger.info(
|
|
135
|
-
f"Current root doesn't have write access to {to.__class__.__name__}[{to.id}]"
|
|
136
|
-
)
|
|
137
|
-
return access_level
|
|
138
|
-
|
|
139
|
-
def access_level(self, to: Anchor) -> AccessLevel:
|
|
140
|
-
"""Access validation."""
|
|
141
|
-
if not to.persistent:
|
|
142
|
-
return AccessLevel.WRITE
|
|
143
|
-
|
|
144
|
-
from jaclang.plugin.feature import JacFeature as Jac
|
|
145
|
-
|
|
146
|
-
jctx = Jac.get_context()
|
|
147
|
-
|
|
148
|
-
jroot = jctx.root
|
|
149
|
-
|
|
150
|
-
# if current root is system_root
|
|
151
|
-
# if current root id is equal to target anchor's root id
|
|
152
|
-
# if current root is the target anchor
|
|
153
|
-
if jroot == jctx.system_root or jroot.id == to.root or jroot == to:
|
|
154
|
-
return AccessLevel.WRITE
|
|
155
|
-
|
|
156
|
-
access_level = AccessLevel.NO_ACCESS
|
|
157
|
-
|
|
158
|
-
# if target anchor have set access.all
|
|
159
|
-
if (to_access := to.access).all > AccessLevel.NO_ACCESS:
|
|
160
|
-
access_level = to_access.all
|
|
161
|
-
|
|
162
|
-
# if target anchor's root have set allowed roots
|
|
163
|
-
# if current root is allowed to the whole graph of target anchor's root
|
|
164
|
-
if to.root and isinstance(to_root := jctx.mem.find_one(to.root), Anchor):
|
|
165
|
-
if to_root.access.all > access_level:
|
|
166
|
-
access_level = to_root.access.all
|
|
167
|
-
|
|
168
|
-
level = to_root.access.roots.check(str(jroot.id))
|
|
169
|
-
if level > AccessLevel.NO_ACCESS and access_level == AccessLevel.NO_ACCESS:
|
|
170
|
-
access_level = level
|
|
171
|
-
|
|
172
|
-
# if target anchor have set allowed roots
|
|
173
|
-
# if current root is allowed to target anchor
|
|
174
|
-
level = to_access.roots.check(str(jroot.id))
|
|
175
|
-
if level > AccessLevel.NO_ACCESS and access_level == AccessLevel.NO_ACCESS:
|
|
176
|
-
access_level = level
|
|
177
|
-
|
|
178
|
-
return access_level
|
|
179
|
-
|
|
180
|
-
# ---------------------------------------------------------------------- #
|
|
181
|
-
|
|
182
|
-
def save(self) -> None:
|
|
183
|
-
"""Save Anchor."""
|
|
184
|
-
from jaclang.plugin.feature import JacFeature as Jac
|
|
185
|
-
|
|
186
|
-
jctx = Jac.get_context()
|
|
187
|
-
|
|
188
|
-
self.persistent = True
|
|
189
|
-
self.root = jctx.root.id
|
|
190
|
-
|
|
191
|
-
jctx.mem.set(self.id, self)
|
|
192
|
-
|
|
193
|
-
def destroy(self) -> None:
|
|
194
|
-
"""Destroy Anchor."""
|
|
195
|
-
from jaclang.plugin.feature import JacFeature as Jac
|
|
196
|
-
|
|
197
|
-
jctx = Jac.get_context()
|
|
198
|
-
|
|
199
|
-
if jctx.root.has_write_access(self):
|
|
200
|
-
jctx.mem.remove(self.id)
|
|
201
|
-
|
|
202
77
|
def is_populated(self) -> bool:
|
|
203
78
|
"""Check if state."""
|
|
204
79
|
return "architype" in self.__dict__
|
|
@@ -304,122 +179,6 @@ class NodeAnchor(Anchor):
|
|
|
304
179
|
architype: NodeArchitype
|
|
305
180
|
edges: list[EdgeAnchor]
|
|
306
181
|
|
|
307
|
-
def get_edges(
|
|
308
|
-
self,
|
|
309
|
-
dir: EdgeDir,
|
|
310
|
-
filter_func: Optional[Callable[[list[EdgeArchitype]], list[EdgeArchitype]]],
|
|
311
|
-
target_obj: Optional[list[NodeArchitype]],
|
|
312
|
-
) -> list[EdgeArchitype]:
|
|
313
|
-
"""Get edges connected to this node."""
|
|
314
|
-
from jaclang.plugin.feature import JacFeature as Jac
|
|
315
|
-
|
|
316
|
-
root = Jac.get_root().__jac__
|
|
317
|
-
ret_edges: list[EdgeArchitype] = []
|
|
318
|
-
for anchor in self.edges:
|
|
319
|
-
if (
|
|
320
|
-
(source := anchor.source)
|
|
321
|
-
and (target := anchor.target)
|
|
322
|
-
and (not filter_func or filter_func([anchor.architype]))
|
|
323
|
-
and source.architype
|
|
324
|
-
and target.architype
|
|
325
|
-
):
|
|
326
|
-
if (
|
|
327
|
-
dir in [EdgeDir.OUT, EdgeDir.ANY]
|
|
328
|
-
and self == source
|
|
329
|
-
and (not target_obj or target.architype in target_obj)
|
|
330
|
-
and root.has_read_access(target)
|
|
331
|
-
):
|
|
332
|
-
ret_edges.append(anchor.architype)
|
|
333
|
-
if (
|
|
334
|
-
dir in [EdgeDir.IN, EdgeDir.ANY]
|
|
335
|
-
and self == target
|
|
336
|
-
and (not target_obj or source.architype in target_obj)
|
|
337
|
-
and root.has_read_access(source)
|
|
338
|
-
):
|
|
339
|
-
ret_edges.append(anchor.architype)
|
|
340
|
-
return ret_edges
|
|
341
|
-
|
|
342
|
-
def edges_to_nodes(
|
|
343
|
-
self,
|
|
344
|
-
dir: EdgeDir,
|
|
345
|
-
filter_func: Optional[Callable[[list[EdgeArchitype]], list[EdgeArchitype]]],
|
|
346
|
-
target_obj: Optional[list[NodeArchitype]],
|
|
347
|
-
) -> list[NodeArchitype]:
|
|
348
|
-
"""Get set of nodes connected to this node."""
|
|
349
|
-
from jaclang.plugin.feature import JacFeature as Jac
|
|
350
|
-
|
|
351
|
-
root = Jac.get_root().__jac__
|
|
352
|
-
ret_edges: list[NodeArchitype] = []
|
|
353
|
-
for anchor in self.edges:
|
|
354
|
-
if (
|
|
355
|
-
(source := anchor.source)
|
|
356
|
-
and (target := anchor.target)
|
|
357
|
-
and (not filter_func or filter_func([anchor.architype]))
|
|
358
|
-
and source.architype
|
|
359
|
-
and target.architype
|
|
360
|
-
):
|
|
361
|
-
if (
|
|
362
|
-
dir in [EdgeDir.OUT, EdgeDir.ANY]
|
|
363
|
-
and self == source
|
|
364
|
-
and (not target_obj or target.architype in target_obj)
|
|
365
|
-
and root.has_read_access(target)
|
|
366
|
-
):
|
|
367
|
-
ret_edges.append(target.architype)
|
|
368
|
-
if (
|
|
369
|
-
dir in [EdgeDir.IN, EdgeDir.ANY]
|
|
370
|
-
and self == target
|
|
371
|
-
and (not target_obj or source.architype in target_obj)
|
|
372
|
-
and root.has_read_access(source)
|
|
373
|
-
):
|
|
374
|
-
ret_edges.append(source.architype)
|
|
375
|
-
return ret_edges
|
|
376
|
-
|
|
377
|
-
def remove_edge(self, edge: EdgeAnchor) -> None:
|
|
378
|
-
"""Remove reference without checking sync status."""
|
|
379
|
-
for idx, ed in enumerate(self.edges):
|
|
380
|
-
if ed.id == edge.id:
|
|
381
|
-
self.edges.pop(idx)
|
|
382
|
-
break
|
|
383
|
-
|
|
384
|
-
def gen_dot(self, dot_file: Optional[str] = None) -> str:
|
|
385
|
-
"""Generate Dot file for visualizing nodes and edges."""
|
|
386
|
-
visited_nodes: set[NodeAnchor] = set()
|
|
387
|
-
connections: set[tuple[NodeArchitype, NodeArchitype, str]] = set()
|
|
388
|
-
unique_node_id_dict = {}
|
|
389
|
-
|
|
390
|
-
collect_node_connections(self, visited_nodes, connections)
|
|
391
|
-
dot_content = 'digraph {\nnode [style="filled", shape="ellipse", fillcolor="invis", fontcolor="black"];\n'
|
|
392
|
-
for idx, i in enumerate([nodes_.architype for nodes_ in visited_nodes]):
|
|
393
|
-
unique_node_id_dict[i] = (i.__class__.__name__, str(idx))
|
|
394
|
-
dot_content += f'{idx} [label="{i}"];\n'
|
|
395
|
-
dot_content += 'edge [color="gray", style="solid"];\n'
|
|
396
|
-
|
|
397
|
-
for pair in list(set(connections)):
|
|
398
|
-
dot_content += (
|
|
399
|
-
f"{unique_node_id_dict[pair[0]][1]} -> {unique_node_id_dict[pair[1]][1]}"
|
|
400
|
-
f' [label="{pair[2]}"];\n'
|
|
401
|
-
)
|
|
402
|
-
if dot_file:
|
|
403
|
-
with open(dot_file, "w") as f:
|
|
404
|
-
f.write(dot_content + "}")
|
|
405
|
-
return dot_content + "}"
|
|
406
|
-
|
|
407
|
-
def spawn_call(self, walk: WalkerAnchor) -> WalkerArchitype:
|
|
408
|
-
"""Invoke data spatial call."""
|
|
409
|
-
return walk.spawn_call(self)
|
|
410
|
-
|
|
411
|
-
def destroy(self) -> None:
|
|
412
|
-
"""Destroy Anchor."""
|
|
413
|
-
from jaclang.plugin.feature import JacFeature as Jac
|
|
414
|
-
|
|
415
|
-
jctx = Jac.get_context()
|
|
416
|
-
|
|
417
|
-
if jctx.root.has_write_access(self):
|
|
418
|
-
for edge in self.edges:
|
|
419
|
-
edge.destroy()
|
|
420
|
-
|
|
421
|
-
jctx.mem.remove(self.id)
|
|
422
|
-
|
|
423
182
|
def __getstate__(self) -> dict[str, object]:
|
|
424
183
|
"""Serialize Node Anchor."""
|
|
425
184
|
state = super().__getstate__()
|
|
@@ -439,30 +198,6 @@ class EdgeAnchor(Anchor):
|
|
|
439
198
|
target: NodeAnchor
|
|
440
199
|
is_undirected: bool
|
|
441
200
|
|
|
442
|
-
def __post_init__(self) -> None:
|
|
443
|
-
"""Populate edge to source and target."""
|
|
444
|
-
self.source.edges.append(self)
|
|
445
|
-
self.target.edges.append(self)
|
|
446
|
-
|
|
447
|
-
def detach(self) -> None:
|
|
448
|
-
"""Detach edge from nodes."""
|
|
449
|
-
self.source.remove_edge(self)
|
|
450
|
-
self.target.remove_edge(self)
|
|
451
|
-
|
|
452
|
-
def spawn_call(self, walk: WalkerAnchor) -> WalkerArchitype:
|
|
453
|
-
"""Invoke data spatial call."""
|
|
454
|
-
return walk.spawn_call(self.target)
|
|
455
|
-
|
|
456
|
-
def destroy(self) -> None:
|
|
457
|
-
"""Destroy Anchor."""
|
|
458
|
-
from jaclang.plugin.feature import JacFeature as Jac
|
|
459
|
-
|
|
460
|
-
jctx = Jac.get_context()
|
|
461
|
-
|
|
462
|
-
if jctx.root.has_write_access(self):
|
|
463
|
-
self.detach()
|
|
464
|
-
jctx.mem.remove(self.id)
|
|
465
|
-
|
|
466
201
|
def __getstate__(self) -> dict[str, object]:
|
|
467
202
|
"""Serialize Node Anchor."""
|
|
468
203
|
state = super().__getstate__()
|
|
@@ -489,99 +224,6 @@ class WalkerAnchor(Anchor):
|
|
|
489
224
|
ignores: list[Anchor] = field(default_factory=list)
|
|
490
225
|
disengaged: bool = False
|
|
491
226
|
|
|
492
|
-
def visit_node(self, anchors: Iterable[NodeAnchor | EdgeAnchor]) -> bool:
|
|
493
|
-
"""Walker visits node."""
|
|
494
|
-
before_len = len(self.next)
|
|
495
|
-
for anchor in anchors:
|
|
496
|
-
if anchor not in self.ignores:
|
|
497
|
-
if isinstance(anchor, NodeAnchor):
|
|
498
|
-
self.next.append(anchor)
|
|
499
|
-
elif isinstance(anchor, EdgeAnchor):
|
|
500
|
-
if target := anchor.target:
|
|
501
|
-
self.next.append(target)
|
|
502
|
-
else:
|
|
503
|
-
raise ValueError("Edge has no target.")
|
|
504
|
-
return len(self.next) > before_len
|
|
505
|
-
|
|
506
|
-
def ignore_node(self, anchors: Iterable[NodeAnchor | EdgeAnchor]) -> bool:
|
|
507
|
-
"""Walker ignores node."""
|
|
508
|
-
before_len = len(self.ignores)
|
|
509
|
-
for anchor in anchors:
|
|
510
|
-
if anchor not in self.ignores:
|
|
511
|
-
if isinstance(anchor, NodeAnchor):
|
|
512
|
-
self.ignores.append(anchor)
|
|
513
|
-
elif isinstance(anchor, EdgeAnchor):
|
|
514
|
-
if target := anchor.target:
|
|
515
|
-
self.ignores.append(target)
|
|
516
|
-
else:
|
|
517
|
-
raise ValueError("Edge has no target.")
|
|
518
|
-
return len(self.ignores) > before_len
|
|
519
|
-
|
|
520
|
-
def disengage_now(self) -> None:
|
|
521
|
-
"""Disengage walker from traversal."""
|
|
522
|
-
self.disengaged = True
|
|
523
|
-
|
|
524
|
-
def spawn_call(self, node: Anchor) -> WalkerArchitype:
|
|
525
|
-
"""Invoke data spatial call."""
|
|
526
|
-
if walker := self.architype:
|
|
527
|
-
self.path = []
|
|
528
|
-
self.next = [node]
|
|
529
|
-
if self.next:
|
|
530
|
-
current_node = self.next[-1].architype
|
|
531
|
-
for i in walker._jac_entry_funcs_:
|
|
532
|
-
if not i.trigger:
|
|
533
|
-
if i.func:
|
|
534
|
-
i.func(walker, current_node)
|
|
535
|
-
else:
|
|
536
|
-
raise ValueError(f"No function {i.name} to call.")
|
|
537
|
-
while len(self.next):
|
|
538
|
-
if current_node := self.next.pop(0).architype:
|
|
539
|
-
for i in current_node._jac_entry_funcs_:
|
|
540
|
-
if not i.trigger or isinstance(walker, i.trigger):
|
|
541
|
-
if i.func:
|
|
542
|
-
i.func(current_node, walker)
|
|
543
|
-
else:
|
|
544
|
-
raise ValueError(f"No function {i.name} to call.")
|
|
545
|
-
if self.disengaged:
|
|
546
|
-
return walker
|
|
547
|
-
for i in walker._jac_entry_funcs_:
|
|
548
|
-
if not i.trigger or isinstance(current_node, i.trigger):
|
|
549
|
-
if i.func and i.trigger:
|
|
550
|
-
i.func(walker, current_node)
|
|
551
|
-
elif not i.trigger:
|
|
552
|
-
continue
|
|
553
|
-
else:
|
|
554
|
-
raise ValueError(f"No function {i.name} to call.")
|
|
555
|
-
if self.disengaged:
|
|
556
|
-
return walker
|
|
557
|
-
for i in walker._jac_exit_funcs_:
|
|
558
|
-
if not i.trigger or isinstance(current_node, i.trigger):
|
|
559
|
-
if i.func and i.trigger:
|
|
560
|
-
i.func(walker, current_node)
|
|
561
|
-
elif not i.trigger:
|
|
562
|
-
continue
|
|
563
|
-
else:
|
|
564
|
-
raise ValueError(f"No function {i.name} to call.")
|
|
565
|
-
if self.disengaged:
|
|
566
|
-
return walker
|
|
567
|
-
for i in current_node._jac_exit_funcs_:
|
|
568
|
-
if not i.trigger or isinstance(walker, i.trigger):
|
|
569
|
-
if i.func:
|
|
570
|
-
i.func(current_node, walker)
|
|
571
|
-
else:
|
|
572
|
-
raise ValueError(f"No function {i.name} to call.")
|
|
573
|
-
if self.disengaged:
|
|
574
|
-
return walker
|
|
575
|
-
for i in walker._jac_exit_funcs_:
|
|
576
|
-
if not i.trigger:
|
|
577
|
-
if i.func:
|
|
578
|
-
i.func(walker, current_node)
|
|
579
|
-
else:
|
|
580
|
-
raise ValueError(f"No function {i.name} to call.")
|
|
581
|
-
self.ignores = []
|
|
582
|
-
return walker
|
|
583
|
-
raise Exception(f"Invalid Reference {self.id}")
|
|
584
|
-
|
|
585
227
|
|
|
586
228
|
class Architype:
|
|
587
229
|
"""Architype Protocol."""
|
|
@@ -613,17 +255,6 @@ class EdgeArchitype(Architype):
|
|
|
613
255
|
|
|
614
256
|
__jac__: EdgeAnchor
|
|
615
257
|
|
|
616
|
-
def __attach__(
|
|
617
|
-
self,
|
|
618
|
-
source: NodeAnchor,
|
|
619
|
-
target: NodeAnchor,
|
|
620
|
-
is_undirected: bool,
|
|
621
|
-
) -> None:
|
|
622
|
-
"""Attach EdgeAnchor properly."""
|
|
623
|
-
self.__jac__ = EdgeAnchor(
|
|
624
|
-
architype=self, source=source, target=target, is_undirected=is_undirected
|
|
625
|
-
)
|
|
626
|
-
|
|
627
258
|
|
|
628
259
|
class WalkerArchitype(Architype):
|
|
629
260
|
"""Walker Architype Protocol."""
|
jaclang/runtimelib/constructs.py
CHANGED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
from .architype import (
|
|
7
|
+
AccessLevel,
|
|
7
8
|
Anchor,
|
|
8
9
|
Architype,
|
|
9
10
|
DSFunc,
|
|
@@ -21,6 +22,7 @@ from .memory import Memory, ShelfStorage
|
|
|
21
22
|
from .test import JacTestCheck, JacTestResult, JacTextTestRunner
|
|
22
23
|
|
|
23
24
|
__all__ = [
|
|
25
|
+
"AccessLevel",
|
|
24
26
|
"Anchor",
|
|
25
27
|
"NodeAnchor",
|
|
26
28
|
"EdgeAnchor",
|
jaclang/runtimelib/context.py
CHANGED
|
@@ -42,10 +42,6 @@ class ExecutionContext:
|
|
|
42
42
|
raise ValueError(f"Invalid anchor id {anchor_id} !")
|
|
43
43
|
return default
|
|
44
44
|
|
|
45
|
-
def validate_access(self) -> bool:
|
|
46
|
-
"""Validate access."""
|
|
47
|
-
return self.root.has_read_access(self.entry_node)
|
|
48
|
-
|
|
49
45
|
def set_entry_node(self, entry_node: str | None) -> None:
|
|
50
46
|
"""Override entry."""
|
|
51
47
|
self.entry_node = self.init_anchor(entry_node, self.root)
|
jaclang/runtimelib/machine.py
CHANGED
|
@@ -4,6 +4,7 @@ import inspect
|
|
|
4
4
|
import marshal
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
|
+
import tempfile
|
|
7
8
|
import types
|
|
8
9
|
from contextvars import ContextVar
|
|
9
10
|
from typing import Optional, Union
|
|
@@ -111,6 +112,62 @@ class JacMachine:
|
|
|
111
112
|
return nodes
|
|
112
113
|
return []
|
|
113
114
|
|
|
115
|
+
def create_architype_from_source(
|
|
116
|
+
self,
|
|
117
|
+
source_code: str,
|
|
118
|
+
module_name: Optional[str] = None,
|
|
119
|
+
base_path: Optional[str] = None,
|
|
120
|
+
cachable: bool = False,
|
|
121
|
+
keep_temporary_files: bool = False,
|
|
122
|
+
) -> Optional[types.ModuleType]:
|
|
123
|
+
"""Dynamically creates architypes (nodes, walkers, etc.) from Jac source code."""
|
|
124
|
+
from jaclang.runtimelib.importer import JacImporter, ImportPathSpec
|
|
125
|
+
|
|
126
|
+
if not base_path:
|
|
127
|
+
base_path = self.base_path or os.getcwd()
|
|
128
|
+
|
|
129
|
+
if base_path and not os.path.exists(base_path):
|
|
130
|
+
os.makedirs(base_path)
|
|
131
|
+
if not module_name:
|
|
132
|
+
module_name = f"_dynamic_module_{len(self.loaded_modules)}"
|
|
133
|
+
with tempfile.NamedTemporaryFile(
|
|
134
|
+
mode="w",
|
|
135
|
+
suffix=".jac",
|
|
136
|
+
prefix=module_name + "_",
|
|
137
|
+
dir=base_path,
|
|
138
|
+
delete=False,
|
|
139
|
+
) as tmp_file:
|
|
140
|
+
tmp_file_path = tmp_file.name
|
|
141
|
+
tmp_file.write(source_code)
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
importer = JacImporter(self)
|
|
145
|
+
tmp_file_basename = os.path.basename(tmp_file_path)
|
|
146
|
+
tmp_module_name, _ = os.path.splitext(tmp_file_basename)
|
|
147
|
+
|
|
148
|
+
spec = ImportPathSpec(
|
|
149
|
+
target=tmp_module_name,
|
|
150
|
+
base_path=base_path,
|
|
151
|
+
absorb=False,
|
|
152
|
+
cachable=cachable,
|
|
153
|
+
mdl_alias=None,
|
|
154
|
+
override_name=module_name,
|
|
155
|
+
lng="jac",
|
|
156
|
+
items=None,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
import_result = importer.run_import(spec, reload=False)
|
|
160
|
+
module = import_result.ret_mod
|
|
161
|
+
|
|
162
|
+
self.loaded_modules[module_name] = module
|
|
163
|
+
return module
|
|
164
|
+
except Exception as e:
|
|
165
|
+
logger.error(f"Error importing dynamic module '{module_name}': {e}")
|
|
166
|
+
return None
|
|
167
|
+
finally:
|
|
168
|
+
if not keep_temporary_files and os.path.exists(tmp_file_path):
|
|
169
|
+
os.remove(tmp_file_path)
|
|
170
|
+
|
|
114
171
|
def update_walker(
|
|
115
172
|
self, module_name: str, items: Optional[dict[str, Union[str, Optional[str]]]]
|
|
116
173
|
) -> tuple[types.ModuleType, ...]:
|
jaclang/runtimelib/memory.py
CHANGED
|
@@ -82,8 +82,6 @@ class ShelfStorage(Memory[UUID, Anchor]):
|
|
|
82
82
|
if isinstance(self.__shelf__, Shelf):
|
|
83
83
|
from jaclang.plugin.feature import JacFeature as Jac
|
|
84
84
|
|
|
85
|
-
root = Jac.get_root().__jac__
|
|
86
|
-
|
|
87
85
|
for anchor in self.__gc__:
|
|
88
86
|
self.__shelf__.pop(str(anchor.id), None)
|
|
89
87
|
self.__mem__.pop(anchor.id, None)
|
|
@@ -96,14 +94,14 @@ class ShelfStorage(Memory[UUID, Anchor]):
|
|
|
96
94
|
isinstance(p_d, NodeAnchor)
|
|
97
95
|
and isinstance(d, NodeAnchor)
|
|
98
96
|
and p_d.edges != d.edges
|
|
99
|
-
and
|
|
97
|
+
and Jac.check_connect_access(d)
|
|
100
98
|
):
|
|
101
99
|
if not d.edges:
|
|
102
100
|
self.__shelf__.pop(_id, None)
|
|
103
101
|
continue
|
|
104
102
|
p_d.edges = d.edges
|
|
105
103
|
|
|
106
|
-
if
|
|
104
|
+
if Jac.check_write_access(d):
|
|
107
105
|
if hash(dumps(p_d.access)) != hash(dumps(d.access)):
|
|
108
106
|
p_d.access = d.access
|
|
109
107
|
if hash(dumps(p_d.architype)) != hash(dumps(d.architype)):
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import:py from jaclang.runtimelib.machine { JacMachine }
|
|
2
|
+
|
|
3
|
+
glob dynamic_module_source = """
|
|
4
|
+
import from .arch_create_util {UtilityNode}
|
|
5
|
+
|
|
6
|
+
walker DynamicWalker {
|
|
7
|
+
can start with entry {
|
|
8
|
+
print("DynamicWalker Started");
|
|
9
|
+
here ++> UtilityNode(data=42);
|
|
10
|
+
visit [-->](`?UtilityNode);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
can UtilityNode {
|
|
14
|
+
here.display_data();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
""";
|
|
18
|
+
|
|
19
|
+
with entry {
|
|
20
|
+
node_arch = JacMachine.get().create_architype_from_source(
|
|
21
|
+
dynamic_module_source,
|
|
22
|
+
module_name="dynamic_module"
|
|
23
|
+
);
|
|
24
|
+
walker_obj = JacMachine.get().spawn_walker(
|
|
25
|
+
'DynamicWalker',
|
|
26
|
+
module_name="dynamic_module",
|
|
27
|
+
|
|
28
|
+
);
|
|
29
|
+
root spawn walker_obj;
|
|
30
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import:py from jaclang.runtimelib.machine { JacMachine }
|
|
2
|
+
# Dynamically create a node architype
|
|
3
|
+
glob source_code = """
|
|
4
|
+
node dynamic_node {
|
|
5
|
+
has value:int;
|
|
6
|
+
can print_value with entry {
|
|
7
|
+
print("Dynamic Node Value:", f'{self.value}');
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
""";
|
|
11
|
+
|
|
12
|
+
# Create a new walker architype dynamically
|
|
13
|
+
glob walker_code = """
|
|
14
|
+
walker dynamic_walker {
|
|
15
|
+
can visit_nodes with entry {
|
|
16
|
+
visit [-->](`?dynamic_node);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
""";
|
|
20
|
+
|
|
21
|
+
with entry {
|
|
22
|
+
node_arch = JacMachine.get().create_architype_from_source(source_code);
|
|
23
|
+
walker_arch = JacMachine.get().create_architype_from_source(walker_code);
|
|
24
|
+
|
|
25
|
+
node_obj = JacMachine.get().spawn_node(
|
|
26
|
+
'dynamic_node',
|
|
27
|
+
{'value': 99},
|
|
28
|
+
node_arch.__name__
|
|
29
|
+
);
|
|
30
|
+
walker_obj = JacMachine.get().spawn_walker(
|
|
31
|
+
'dynamic_walker',
|
|
32
|
+
module_name=walker_arch.__name__
|
|
33
|
+
);
|
|
34
|
+
node_obj spawn walker_obj;
|
|
35
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
node MyNode{
|
|
2
|
+
has Name:str;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
edge a{}
|
|
6
|
+
|
|
7
|
+
edge b{}
|
|
8
|
+
|
|
9
|
+
with entry{
|
|
10
|
+
Start = MyNode("Start");
|
|
11
|
+
End = MyNode("End");
|
|
12
|
+
mid = MyNode("Middle");
|
|
13
|
+
root <+:a:+ Start;
|
|
14
|
+
root +:a:+> End;
|
|
15
|
+
root +:b:+> mid;
|
|
16
|
+
root +:a:+> mid;
|
|
17
|
+
|
|
18
|
+
print([root-->]);
|
|
19
|
+
|
|
20
|
+
}
|