jaclang 0.7.22__py3-none-any.whl → 0.7.25__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jaclang might be problematic. Click here for more details.
- jaclang/__init__.py +5 -10
- jaclang/cli/cli.py +50 -30
- jaclang/compiler/__init__.py +2 -2
- jaclang/compiler/absyntree.py +87 -48
- jaclang/compiler/codeloc.py +7 -2
- jaclang/compiler/compile.py +10 -3
- jaclang/compiler/parser.py +26 -23
- jaclang/compiler/passes/ir_pass.py +2 -2
- jaclang/compiler/passes/main/def_impl_match_pass.py +46 -0
- jaclang/compiler/passes/main/fuse_typeinfo_pass.py +146 -123
- jaclang/compiler/passes/main/import_pass.py +6 -2
- jaclang/compiler/passes/main/pyast_load_pass.py +36 -35
- jaclang/compiler/passes/main/pyjac_ast_link_pass.py +7 -7
- jaclang/compiler/passes/main/registry_pass.py +3 -12
- jaclang/compiler/passes/main/tests/fixtures/defn_decl_mismatch.jac +19 -0
- jaclang/compiler/passes/main/tests/fixtures/fstrings.jac +2 -0
- jaclang/compiler/passes/main/tests/test_decl_def_match_pass.py +59 -0
- jaclang/compiler/passes/main/tests/test_registry_pass.py +2 -10
- jaclang/compiler/passes/main/tests/test_type_check_pass.py +1 -1
- jaclang/compiler/passes/main/type_check_pass.py +8 -6
- jaclang/compiler/passes/transform.py +27 -3
- jaclang/compiler/passes/utils/mypy_ast_build.py +246 -26
- jaclang/compiler/symtable.py +6 -0
- jaclang/compiler/tests/test_importer.py +2 -2
- jaclang/langserve/engine.py +14 -12
- jaclang/langserve/server.py +7 -2
- jaclang/langserve/tests/test_server.py +1 -1
- jaclang/langserve/utils.py +17 -3
- jaclang/plugin/builtin.py +3 -3
- jaclang/plugin/default.py +612 -236
- 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/importer.py +7 -2
- jaclang/runtimelib/machine.py +78 -6
- 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/expr_type.jac +54 -0
- jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
- jaclang/tests/fixtures/glob_multivar_statement.jac +15 -0
- jaclang/tests/fixtures/registry.jac +20 -8
- jaclang/tests/fixtures/visit_order.jac +20 -0
- jaclang/tests/foo/__init__.jac +0 -0
- jaclang/tests/main.jac +2 -0
- jaclang/tests/test_cli.py +68 -4
- jaclang/tests/test_language.py +113 -27
- jaclang/utils/helpers.py +92 -14
- jaclang/utils/lang_tools.py +6 -2
- jaclang/utils/treeprinter.py +4 -2
- {jaclang-0.7.22.dist-info → jaclang-0.7.25.dist-info}/METADATA +2 -1
- {jaclang-0.7.22.dist-info → jaclang-0.7.25.dist-info}/RECORD +65 -55
- {jaclang-0.7.22.dist-info → jaclang-0.7.25.dist-info}/WHEEL +1 -1
- {jaclang-0.7.22.dist-info → jaclang-0.7.25.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
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import unittest
|
|
6
6
|
from contextvars import ContextVar
|
|
7
|
+
from dataclasses import MISSING
|
|
7
8
|
from typing import Any, Callable, Optional, cast
|
|
8
9
|
from uuid import UUID
|
|
9
10
|
|
|
@@ -26,6 +27,7 @@ class ExecutionContext:
|
|
|
26
27
|
|
|
27
28
|
mem: Memory
|
|
28
29
|
reports: list[Any]
|
|
30
|
+
custom: Any = MISSING
|
|
29
31
|
system_root: NodeAnchor
|
|
30
32
|
root: NodeAnchor
|
|
31
33
|
entry_node: NodeAnchor
|
|
@@ -42,10 +44,6 @@ class ExecutionContext:
|
|
|
42
44
|
raise ValueError(f"Invalid anchor id {anchor_id} !")
|
|
43
45
|
return default
|
|
44
46
|
|
|
45
|
-
def validate_access(self) -> bool:
|
|
46
|
-
"""Validate access."""
|
|
47
|
-
return self.root.has_read_access(self.entry_node)
|
|
48
|
-
|
|
49
47
|
def set_entry_node(self, entry_node: str | None) -> None:
|
|
50
48
|
"""Override entry."""
|
|
51
49
|
self.entry_node = self.init_anchor(entry_node, self.root)
|
jaclang/runtimelib/importer.py
CHANGED
|
@@ -344,14 +344,19 @@ class JacImporter(Importer):
|
|
|
344
344
|
cachable=spec.cachable,
|
|
345
345
|
reload=reload if reload else False,
|
|
346
346
|
)
|
|
347
|
+
|
|
348
|
+
# Since this is a compile time error, we can safely raise an exception here.
|
|
349
|
+
if not codeobj:
|
|
350
|
+
raise ImportError(f"No bytecode found for {spec.full_target}")
|
|
351
|
+
|
|
347
352
|
try:
|
|
348
|
-
if not codeobj:
|
|
349
|
-
raise ImportError(f"No bytecode found for {spec.full_target}")
|
|
350
353
|
with sys_path_context(spec.caller_dir):
|
|
351
354
|
exec(codeobj, module.__dict__)
|
|
352
355
|
except Exception as e:
|
|
356
|
+
logger.error(e)
|
|
353
357
|
logger.error(dump_traceback(e))
|
|
354
358
|
raise e
|
|
359
|
+
|
|
355
360
|
import_return = ImportReturn(module, unique_loaded_items, self)
|
|
356
361
|
if spec.items:
|
|
357
362
|
import_return.process_items(
|
jaclang/runtimelib/machine.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"""Jac Machine module."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import inspect
|
|
4
6
|
import marshal
|
|
5
7
|
import os
|
|
6
8
|
import sys
|
|
9
|
+
import tempfile
|
|
7
10
|
import types
|
|
8
11
|
from contextvars import ContextVar
|
|
9
12
|
from typing import Optional, Union
|
|
@@ -11,6 +14,7 @@ from typing import Optional, Union
|
|
|
11
14
|
from jaclang.compiler.absyntree import Module
|
|
12
15
|
from jaclang.compiler.compile import compile_jac
|
|
13
16
|
from jaclang.compiler.constant import Constants as Con
|
|
17
|
+
from jaclang.compiler.semtable import SemRegistry
|
|
14
18
|
from jaclang.runtimelib.architype import (
|
|
15
19
|
Architype,
|
|
16
20
|
EdgeArchitype,
|
|
@@ -69,6 +73,14 @@ class JacMachine:
|
|
|
69
73
|
)
|
|
70
74
|
return None
|
|
71
75
|
|
|
76
|
+
def get_sem_ir(self, mod_sem_ir: SemRegistry | None) -> None:
|
|
77
|
+
"""Update semtable on the attached JacProgram."""
|
|
78
|
+
if self.jac_program and mod_sem_ir:
|
|
79
|
+
if self.jac_program.sem_ir:
|
|
80
|
+
self.jac_program.sem_ir.registry.update(mod_sem_ir.registry)
|
|
81
|
+
else:
|
|
82
|
+
self.jac_program.sem_ir = mod_sem_ir
|
|
83
|
+
|
|
72
84
|
def load_module(self, module_name: str, module: types.ModuleType) -> None:
|
|
73
85
|
"""Load a module into the machine."""
|
|
74
86
|
self.loaded_modules[module_name] = module
|
|
@@ -111,6 +123,62 @@ class JacMachine:
|
|
|
111
123
|
return nodes
|
|
112
124
|
return []
|
|
113
125
|
|
|
126
|
+
def create_architype_from_source(
|
|
127
|
+
self,
|
|
128
|
+
source_code: str,
|
|
129
|
+
module_name: Optional[str] = None,
|
|
130
|
+
base_path: Optional[str] = None,
|
|
131
|
+
cachable: bool = False,
|
|
132
|
+
keep_temporary_files: bool = False,
|
|
133
|
+
) -> Optional[types.ModuleType]:
|
|
134
|
+
"""Dynamically creates architypes (nodes, walkers, etc.) from Jac source code."""
|
|
135
|
+
from jaclang.runtimelib.importer import JacImporter, ImportPathSpec
|
|
136
|
+
|
|
137
|
+
if not base_path:
|
|
138
|
+
base_path = self.base_path or os.getcwd()
|
|
139
|
+
|
|
140
|
+
if base_path and not os.path.exists(base_path):
|
|
141
|
+
os.makedirs(base_path)
|
|
142
|
+
if not module_name:
|
|
143
|
+
module_name = f"_dynamic_module_{len(self.loaded_modules)}"
|
|
144
|
+
with tempfile.NamedTemporaryFile(
|
|
145
|
+
mode="w",
|
|
146
|
+
suffix=".jac",
|
|
147
|
+
prefix=module_name + "_",
|
|
148
|
+
dir=base_path,
|
|
149
|
+
delete=False,
|
|
150
|
+
) as tmp_file:
|
|
151
|
+
tmp_file_path = tmp_file.name
|
|
152
|
+
tmp_file.write(source_code)
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
importer = JacImporter(self)
|
|
156
|
+
tmp_file_basename = os.path.basename(tmp_file_path)
|
|
157
|
+
tmp_module_name, _ = os.path.splitext(tmp_file_basename)
|
|
158
|
+
|
|
159
|
+
spec = ImportPathSpec(
|
|
160
|
+
target=tmp_module_name,
|
|
161
|
+
base_path=base_path,
|
|
162
|
+
absorb=False,
|
|
163
|
+
cachable=cachable,
|
|
164
|
+
mdl_alias=None,
|
|
165
|
+
override_name=module_name,
|
|
166
|
+
lng="jac",
|
|
167
|
+
items=None,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
import_result = importer.run_import(spec, reload=False)
|
|
171
|
+
module = import_result.ret_mod
|
|
172
|
+
|
|
173
|
+
self.loaded_modules[module_name] = module
|
|
174
|
+
return module
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.error(f"Error importing dynamic module '{module_name}': {e}")
|
|
177
|
+
return None
|
|
178
|
+
finally:
|
|
179
|
+
if not keep_temporary_files and os.path.exists(tmp_file_path):
|
|
180
|
+
os.remove(tmp_file_path)
|
|
181
|
+
|
|
114
182
|
def update_walker(
|
|
115
183
|
self, module_name: str, items: Optional[dict[str, Union[str, Optional[str]]]]
|
|
116
184
|
) -> tuple[types.ModuleType, ...]:
|
|
@@ -206,14 +274,18 @@ class JacMachine:
|
|
|
206
274
|
|
|
207
275
|
|
|
208
276
|
class JacProgram:
|
|
209
|
-
"""Class to hold the mod_bundle and
|
|
277
|
+
"""Class to hold the mod_bundle bytecode and sem_ir for Jac modules."""
|
|
210
278
|
|
|
211
279
|
def __init__(
|
|
212
|
-
self,
|
|
280
|
+
self,
|
|
281
|
+
mod_bundle: Optional[Module],
|
|
282
|
+
bytecode: Optional[dict[str, bytes]],
|
|
283
|
+
sem_ir: Optional[SemRegistry],
|
|
213
284
|
) -> None:
|
|
214
285
|
"""Initialize the JacProgram object."""
|
|
215
286
|
self.mod_bundle = mod_bundle
|
|
216
287
|
self.bytecode = bytecode or {}
|
|
288
|
+
self.sem_ir = sem_ir if sem_ir else SemRegistry()
|
|
217
289
|
|
|
218
290
|
def get_bytecode(
|
|
219
291
|
self,
|
|
@@ -235,10 +307,10 @@ class JacProgram:
|
|
|
235
307
|
|
|
236
308
|
result = compile_jac(full_target, cache_result=cachable)
|
|
237
309
|
if result.errors_had or not result.ir.gen.py_bytecode:
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
310
|
+
for alrt in result.errors_had:
|
|
311
|
+
# We're not logging here, it already gets logged as the errors were added to the errors_had list.
|
|
312
|
+
# Regardless of the logging, this needs to be sent to the end user, so we'll printing it to stderr.
|
|
313
|
+
logger.error(alrt.pretty_print())
|
|
242
314
|
return None
|
|
243
315
|
if result.ir.gen.py_bytecode is not None:
|
|
244
316
|
return marshal.loads(result.ir.gen.py_bytecode)
|
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)):
|
jaclang/settings.py
CHANGED
|
@@ -58,6 +58,9 @@ class Settings:
|
|
|
58
58
|
"""Override settings from environment variables if available."""
|
|
59
59
|
for key in [f.name for f in fields(self)]:
|
|
60
60
|
env_value = os.getenv("JACLANG_" + key.upper())
|
|
61
|
+
env_value = (
|
|
62
|
+
env_value if env_value is not None else os.getenv("JAC_" + key.upper())
|
|
63
|
+
)
|
|
61
64
|
if env_value is not None:
|
|
62
65
|
setattr(self, key, self.convert_type(env_value))
|
|
63
66
|
|
|
@@ -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
|
+
}
|