jaclang 0.7.14__py3-none-any.whl → 0.7.17__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/cli/cli.py +147 -77
- jaclang/cli/cmdreg.py +9 -12
- jaclang/compiler/__init__.py +19 -53
- jaclang/compiler/absyntree.py +94 -16
- jaclang/compiler/constant.py +8 -8
- jaclang/compiler/jac.lark +4 -3
- jaclang/compiler/parser.py +41 -25
- jaclang/compiler/passes/ir_pass.py +4 -13
- jaclang/compiler/passes/main/__init__.py +1 -1
- jaclang/compiler/passes/main/access_modifier_pass.py +96 -147
- jaclang/compiler/passes/main/fuse_typeinfo_pass.py +155 -54
- jaclang/compiler/passes/main/import_pass.py +99 -75
- jaclang/compiler/passes/main/py_collect_dep_pass.py +70 -0
- jaclang/compiler/passes/main/pyast_gen_pass.py +328 -565
- jaclang/compiler/passes/main/pyast_load_pass.py +33 -6
- jaclang/compiler/passes/main/pyjac_ast_link_pass.py +7 -0
- jaclang/compiler/passes/main/registry_pass.py +37 -3
- jaclang/compiler/passes/main/schedules.py +9 -2
- jaclang/compiler/passes/main/sym_tab_build_pass.py +10 -6
- jaclang/compiler/passes/main/tests/__init__.py +1 -1
- jaclang/compiler/passes/main/tests/fixtures/autoimpl.empty.impl.jac +0 -0
- jaclang/compiler/passes/main/tests/fixtures/autoimpl.jac +1 -1
- jaclang/compiler/passes/main/tests/fixtures/py_imp_test.jac +29 -0
- jaclang/compiler/passes/main/tests/fixtures/pygame_mock/__init__.py +3 -0
- jaclang/compiler/passes/main/tests/fixtures/pygame_mock/color.py +3 -0
- jaclang/compiler/passes/main/tests/fixtures/pygame_mock/constants.py +5 -0
- jaclang/compiler/passes/main/tests/fixtures/pygame_mock/display.py +2 -0
- jaclang/compiler/passes/main/tests/test_import_pass.py +72 -13
- jaclang/compiler/passes/main/type_check_pass.py +22 -5
- jaclang/compiler/passes/tool/jac_formatter_pass.py +135 -89
- jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +37 -41
- jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +37 -42
- jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/access_mod_check.jac +27 -0
- jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/architype_test.jac +13 -0
- jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/comment_alignment.jac +11 -0
- jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/comments.jac +13 -0
- jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/decorator_stack.jac +37 -0
- jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/esc_keywords.jac +5 -0
- jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/long_names.jac +19 -0
- jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/triple_quoted_string.jac +6 -0
- jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +11 -0
- jaclang/compiler/passes/tool/tests/test_unparse_validate.py +33 -39
- jaclang/compiler/passes/transform.py +4 -0
- jaclang/compiler/passes/utils/mypy_ast_build.py +45 -0
- jaclang/compiler/semtable.py +31 -7
- jaclang/compiler/symtable.py +16 -11
- jaclang/compiler/tests/test_importer.py +25 -10
- jaclang/langserve/engine.py +104 -118
- jaclang/langserve/sem_manager.py +379 -0
- jaclang/langserve/server.py +24 -11
- jaclang/langserve/tests/fixtures/base_module_structure.jac +27 -6
- jaclang/langserve/tests/fixtures/circle.jac +3 -3
- jaclang/langserve/tests/fixtures/circle_err.jac +3 -3
- jaclang/langserve/tests/fixtures/circle_pure.test.jac +3 -3
- jaclang/langserve/tests/fixtures/import_include_statements.jac +1 -1
- jaclang/langserve/tests/fixtures/rename.jac +30 -0
- jaclang/langserve/tests/test_sem_tokens.py +277 -0
- jaclang/langserve/tests/test_server.py +287 -17
- jaclang/langserve/utils.py +184 -98
- jaclang/plugin/builtin.py +1 -1
- jaclang/plugin/default.py +288 -92
- jaclang/plugin/feature.py +65 -27
- jaclang/plugin/spec.py +62 -23
- jaclang/plugin/tests/fixtures/other_root_access.jac +82 -0
- jaclang/plugin/tests/test_jaseci.py +414 -42
- jaclang/runtimelib/architype.py +650 -0
- jaclang/{core → runtimelib}/constructs.py +5 -8
- jaclang/{core → runtimelib}/context.py +86 -59
- jaclang/runtimelib/importer.py +361 -0
- jaclang/runtimelib/machine.py +158 -0
- jaclang/runtimelib/memory.py +158 -0
- jaclang/{core → runtimelib}/utils.py +30 -15
- jaclang/settings.py +5 -4
- jaclang/tests/fixtures/abc.jac +3 -3
- jaclang/tests/fixtures/access_checker.jac +12 -17
- jaclang/tests/fixtures/access_modifier.jac +88 -33
- jaclang/tests/fixtures/baddy.jac +3 -0
- jaclang/tests/fixtures/baddy.test.jac +3 -0
- jaclang/tests/fixtures/bar.jac +34 -0
- jaclang/tests/fixtures/byllmissue.jac +1 -5
- jaclang/tests/fixtures/chandra_bugs2.jac +11 -10
- jaclang/tests/fixtures/cls_method.jac +41 -0
- jaclang/tests/fixtures/dblhello.jac +6 -0
- jaclang/tests/fixtures/deep/one_lev.jac +3 -3
- jaclang/tests/fixtures/deep/one_lev_dup.jac +2 -3
- jaclang/tests/fixtures/deep_import_mods.jac +13 -0
- jaclang/tests/fixtures/edge_node_walk.jac +1 -1
- jaclang/tests/fixtures/edge_ops.jac +1 -1
- jaclang/tests/fixtures/edges_walk.jac +1 -1
- jaclang/tests/fixtures/err.impl.jac +3 -0
- jaclang/tests/fixtures/err.jac +4 -2
- jaclang/tests/fixtures/err_runtime.jac +15 -0
- jaclang/tests/fixtures/foo.jac +43 -0
- jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
- jaclang/tests/fixtures/hello.jac +4 -0
- jaclang/tests/fixtures/impl_grab.impl.jac +2 -1
- jaclang/tests/fixtures/impl_grab.jac +4 -1
- jaclang/tests/fixtures/import.jac +9 -0
- jaclang/tests/fixtures/index_slice.jac +30 -0
- jaclang/tests/fixtures/jp_importer_auto.jac +14 -0
- jaclang/tests/fixtures/maxfail_run_test.jac +4 -4
- jaclang/tests/fixtures/needs_import.jac +2 -2
- jaclang/tests/fixtures/pyfunc_1.py +1 -1
- jaclang/tests/fixtures/pyfunc_2.py +5 -2
- jaclang/tests/fixtures/pygame_mock/__init__.py +3 -0
- jaclang/tests/fixtures/pygame_mock/color.py +3 -0
- jaclang/tests/fixtures/pygame_mock/constants.py +5 -0
- jaclang/tests/fixtures/pygame_mock/display.py +2 -0
- jaclang/tests/fixtures/pygame_mock/inner/__init__.py +0 -0
- jaclang/tests/fixtures/pygame_mock/inner/iner_mod.py +2 -0
- jaclang/tests/fixtures/registry.jac +9 -0
- jaclang/tests/fixtures/run_test.jac +4 -4
- jaclang/tests/fixtures/semstr.jac +1 -4
- jaclang/tests/fixtures/simple_archs.jac +1 -1
- jaclang/tests/test_cli.py +109 -3
- jaclang/tests/test_language.py +170 -68
- jaclang/tests/test_reference.py +2 -3
- jaclang/utils/helpers.py +45 -21
- jaclang/utils/test.py +9 -0
- jaclang/utils/treeprinter.py +30 -7
- {jaclang-0.7.14.dist-info → jaclang-0.7.17.dist-info}/METADATA +3 -2
- {jaclang-0.7.14.dist-info → jaclang-0.7.17.dist-info}/RECORD +126 -90
- jaclang/core/architype.py +0 -502
- jaclang/core/importer.py +0 -344
- jaclang/core/memory.py +0 -99
- jaclang/tests/fixtures/aott_raise.jac +0 -25
- jaclang/tests/fixtures/package_import.jac +0 -6
- /jaclang/{core → runtimelib}/__init__.py +0 -0
- /jaclang/{core → runtimelib}/test.py +0 -0
- {jaclang-0.7.14.dist-info → jaclang-0.7.17.dist-info}/WHEEL +0 -0
- {jaclang-0.7.14.dist-info → jaclang-0.7.17.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
"""Core constructs for Jac Language."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import asdict, dataclass, field, fields, is_dataclass
|
|
6
|
+
from enum import IntEnum
|
|
7
|
+
from logging import getLogger
|
|
8
|
+
from pickle import dumps
|
|
9
|
+
from types import UnionType
|
|
10
|
+
from typing import Any, Callable, ClassVar, Iterable, Optional, TypeVar
|
|
11
|
+
from uuid import UUID, uuid4
|
|
12
|
+
|
|
13
|
+
from jaclang.compiler.constant import EdgeDir
|
|
14
|
+
from jaclang.runtimelib.utils import collect_node_connections
|
|
15
|
+
|
|
16
|
+
logger = getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
TARCH = TypeVar("TARCH", bound="Architype")
|
|
19
|
+
TANCH = TypeVar("TANCH", bound="Anchor")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AccessLevel(IntEnum):
|
|
23
|
+
"""Access level enum."""
|
|
24
|
+
|
|
25
|
+
NO_ACCESS = -1
|
|
26
|
+
READ = 0
|
|
27
|
+
CONNECT = 1
|
|
28
|
+
WRITE = 2
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def cast(val: int | str | AccessLevel) -> AccessLevel:
|
|
32
|
+
"""Cast access level."""
|
|
33
|
+
match val:
|
|
34
|
+
case int():
|
|
35
|
+
return AccessLevel(val)
|
|
36
|
+
case str():
|
|
37
|
+
return AccessLevel[val]
|
|
38
|
+
case _:
|
|
39
|
+
return val
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class Access:
|
|
44
|
+
"""Access Structure."""
|
|
45
|
+
|
|
46
|
+
anchors: dict[str, AccessLevel] = field(default_factory=dict)
|
|
47
|
+
|
|
48
|
+
def check(self, anchor: str) -> AccessLevel:
|
|
49
|
+
"""Validate access."""
|
|
50
|
+
return self.anchors.get(anchor, AccessLevel.NO_ACCESS)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class Permission:
|
|
55
|
+
"""Anchor Access Handler."""
|
|
56
|
+
|
|
57
|
+
all: AccessLevel = AccessLevel.NO_ACCESS
|
|
58
|
+
roots: Access = field(default_factory=Access)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class AnchorReport:
|
|
63
|
+
"""Report Handler."""
|
|
64
|
+
|
|
65
|
+
id: str
|
|
66
|
+
context: dict[str, Any]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(eq=False, repr=False, kw_only=True)
|
|
70
|
+
class Anchor:
|
|
71
|
+
"""Object Anchor."""
|
|
72
|
+
|
|
73
|
+
architype: Architype
|
|
74
|
+
id: UUID = field(default_factory=uuid4)
|
|
75
|
+
root: Optional[UUID] = None
|
|
76
|
+
access: Permission = field(default_factory=Permission)
|
|
77
|
+
persistent: bool = False
|
|
78
|
+
hash: int = 0
|
|
79
|
+
|
|
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
|
+
def is_populated(self) -> bool:
|
|
203
|
+
"""Check if state."""
|
|
204
|
+
return "architype" in self.__dict__
|
|
205
|
+
|
|
206
|
+
def make_stub(self: TANCH) -> TANCH:
|
|
207
|
+
"""Return unsynced copy of anchor."""
|
|
208
|
+
if self.is_populated():
|
|
209
|
+
unloaded = object.__new__(self.__class__)
|
|
210
|
+
unloaded.id = self.id
|
|
211
|
+
return unloaded
|
|
212
|
+
return self
|
|
213
|
+
|
|
214
|
+
def populate(self) -> None:
|
|
215
|
+
"""Retrieve the Architype from db and return."""
|
|
216
|
+
from jaclang.plugin.feature import JacFeature as Jac
|
|
217
|
+
|
|
218
|
+
jsrc = Jac.get_context().mem
|
|
219
|
+
|
|
220
|
+
if anchor := jsrc.find_by_id(self.id):
|
|
221
|
+
self.__dict__.update(anchor.__dict__)
|
|
222
|
+
|
|
223
|
+
def __getattr__(self, name: str) -> object:
|
|
224
|
+
"""Trigger load if detects unloaded state."""
|
|
225
|
+
if not self.is_populated():
|
|
226
|
+
self.populate()
|
|
227
|
+
|
|
228
|
+
if not self.is_populated():
|
|
229
|
+
raise ValueError(
|
|
230
|
+
f"{self.__class__.__name__} [{self.id}] is not a valid reference!"
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return getattr(self, name)
|
|
234
|
+
|
|
235
|
+
raise AttributeError(
|
|
236
|
+
f"'{self.__class__.__name__}' object has not attribute '{name}'"
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
def __getstate__(self) -> dict[str, Any]: # NOTE: May be better type hinting
|
|
240
|
+
"""Serialize Anchor."""
|
|
241
|
+
if self.is_populated():
|
|
242
|
+
unlinked = object.__new__(self.architype.__class__)
|
|
243
|
+
unlinked.__dict__.update(self.architype.__dict__)
|
|
244
|
+
unlinked.__dict__.pop("__jac__", None)
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
"id": self.id,
|
|
248
|
+
"architype": unlinked,
|
|
249
|
+
"root": self.root,
|
|
250
|
+
"access": self.access,
|
|
251
|
+
"persistent": self.persistent,
|
|
252
|
+
}
|
|
253
|
+
else:
|
|
254
|
+
return {"id": self.id}
|
|
255
|
+
|
|
256
|
+
def __setstate__(self, state: dict[str, Any]) -> None:
|
|
257
|
+
"""Deserialize Anchor."""
|
|
258
|
+
self.__dict__.update(state)
|
|
259
|
+
|
|
260
|
+
if self.is_populated() and self.architype:
|
|
261
|
+
self.architype.__jac__ = self
|
|
262
|
+
self.hash = hash(dumps(self))
|
|
263
|
+
|
|
264
|
+
def __repr__(self) -> str:
|
|
265
|
+
"""Override representation."""
|
|
266
|
+
if self.is_populated():
|
|
267
|
+
attrs = ""
|
|
268
|
+
for f in fields(self):
|
|
269
|
+
if f.name in self.__dict__:
|
|
270
|
+
attrs += f"{f.name}={self.__dict__[f.name]}, "
|
|
271
|
+
attrs = attrs[:-2]
|
|
272
|
+
else:
|
|
273
|
+
attrs = f"id={self.id}"
|
|
274
|
+
|
|
275
|
+
return f"{self.__class__.__name__}({attrs})"
|
|
276
|
+
|
|
277
|
+
def report(self) -> AnchorReport:
|
|
278
|
+
"""Report Anchor."""
|
|
279
|
+
return AnchorReport(
|
|
280
|
+
id=self.id.hex,
|
|
281
|
+
context=(
|
|
282
|
+
asdict(self.architype)
|
|
283
|
+
if is_dataclass(self.architype) and not isinstance(self.architype, type)
|
|
284
|
+
else {}
|
|
285
|
+
),
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
def __hash__(self) -> int:
|
|
289
|
+
"""Override hash for anchor."""
|
|
290
|
+
return hash(self.id)
|
|
291
|
+
|
|
292
|
+
def __eq__(self, other: object) -> bool:
|
|
293
|
+
"""Override equal implementation."""
|
|
294
|
+
if isinstance(other, Anchor):
|
|
295
|
+
return self.__class__ is other.__class__ and self.id == other.id
|
|
296
|
+
|
|
297
|
+
return False
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@dataclass(eq=False, repr=False, kw_only=True)
|
|
301
|
+
class NodeAnchor(Anchor):
|
|
302
|
+
"""Node Anchor."""
|
|
303
|
+
|
|
304
|
+
architype: NodeArchitype
|
|
305
|
+
edges: list[EdgeAnchor]
|
|
306
|
+
|
|
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
|
+
def __getstate__(self) -> dict[str, object]:
|
|
424
|
+
"""Serialize Node Anchor."""
|
|
425
|
+
state = super().__getstate__()
|
|
426
|
+
|
|
427
|
+
if self.is_populated():
|
|
428
|
+
state["edges"] = [edge.make_stub() for edge in self.edges]
|
|
429
|
+
|
|
430
|
+
return state
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
@dataclass(eq=False, repr=False, kw_only=True)
|
|
434
|
+
class EdgeAnchor(Anchor):
|
|
435
|
+
"""Edge Anchor."""
|
|
436
|
+
|
|
437
|
+
architype: EdgeArchitype
|
|
438
|
+
source: NodeAnchor
|
|
439
|
+
target: NodeAnchor
|
|
440
|
+
is_undirected: bool
|
|
441
|
+
|
|
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
|
+
def __getstate__(self) -> dict[str, object]:
|
|
467
|
+
"""Serialize Node Anchor."""
|
|
468
|
+
state = super().__getstate__()
|
|
469
|
+
|
|
470
|
+
if self.is_populated():
|
|
471
|
+
state.update(
|
|
472
|
+
{
|
|
473
|
+
"source": self.source.make_stub(),
|
|
474
|
+
"target": self.target.make_stub(),
|
|
475
|
+
"is_undirected": self.is_undirected,
|
|
476
|
+
}
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
return state
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
@dataclass(eq=False, repr=False, kw_only=True)
|
|
483
|
+
class WalkerAnchor(Anchor):
|
|
484
|
+
"""Walker Anchor."""
|
|
485
|
+
|
|
486
|
+
architype: WalkerArchitype
|
|
487
|
+
path: list[Anchor] = field(default_factory=list)
|
|
488
|
+
next: list[Anchor] = field(default_factory=list)
|
|
489
|
+
ignores: list[Anchor] = field(default_factory=list)
|
|
490
|
+
disengaged: bool = False
|
|
491
|
+
|
|
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
|
+
while len(self.next):
|
|
530
|
+
if current_node := self.next.pop(0).architype:
|
|
531
|
+
for i in current_node._jac_entry_funcs_:
|
|
532
|
+
if not i.trigger or isinstance(walker, i.trigger):
|
|
533
|
+
if i.func:
|
|
534
|
+
i.func(current_node, walker)
|
|
535
|
+
else:
|
|
536
|
+
raise ValueError(f"No function {i.name} to call.")
|
|
537
|
+
if self.disengaged:
|
|
538
|
+
return walker
|
|
539
|
+
for i in walker._jac_entry_funcs_:
|
|
540
|
+
if not i.trigger or isinstance(current_node, i.trigger):
|
|
541
|
+
if i.func:
|
|
542
|
+
i.func(walker, current_node)
|
|
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_exit_funcs_:
|
|
548
|
+
if not i.trigger or isinstance(current_node, i.trigger):
|
|
549
|
+
if i.func:
|
|
550
|
+
i.func(walker, current_node)
|
|
551
|
+
else:
|
|
552
|
+
raise ValueError(f"No function {i.name} to call.")
|
|
553
|
+
if self.disengaged:
|
|
554
|
+
return walker
|
|
555
|
+
for i in current_node._jac_exit_funcs_:
|
|
556
|
+
if not i.trigger or isinstance(walker, i.trigger):
|
|
557
|
+
if i.func:
|
|
558
|
+
i.func(current_node, walker)
|
|
559
|
+
else:
|
|
560
|
+
raise ValueError(f"No function {i.name} to call.")
|
|
561
|
+
if self.disengaged:
|
|
562
|
+
return walker
|
|
563
|
+
self.ignores = []
|
|
564
|
+
return walker
|
|
565
|
+
raise Exception(f"Invalid Reference {self.id}")
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
class Architype:
|
|
569
|
+
"""Architype Protocol."""
|
|
570
|
+
|
|
571
|
+
_jac_entry_funcs_: ClassVar[list[DSFunc]]
|
|
572
|
+
_jac_exit_funcs_: ClassVar[list[DSFunc]]
|
|
573
|
+
|
|
574
|
+
def __init__(self) -> None:
|
|
575
|
+
"""Create default architype."""
|
|
576
|
+
self.__jac__ = Anchor(architype=self)
|
|
577
|
+
|
|
578
|
+
def __repr__(self) -> str:
|
|
579
|
+
"""Override repr for architype."""
|
|
580
|
+
return f"{self.__class__.__name__}"
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
class NodeArchitype(Architype):
|
|
584
|
+
"""Node Architype Protocol."""
|
|
585
|
+
|
|
586
|
+
__jac__: NodeAnchor
|
|
587
|
+
|
|
588
|
+
def __init__(self) -> None:
|
|
589
|
+
"""Create node architype."""
|
|
590
|
+
self.__jac__ = NodeAnchor(architype=self, edges=[])
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
class EdgeArchitype(Architype):
|
|
594
|
+
"""Edge Architype Protocol."""
|
|
595
|
+
|
|
596
|
+
__jac__: EdgeAnchor
|
|
597
|
+
|
|
598
|
+
def __attach__(
|
|
599
|
+
self,
|
|
600
|
+
source: NodeAnchor,
|
|
601
|
+
target: NodeAnchor,
|
|
602
|
+
is_undirected: bool,
|
|
603
|
+
) -> None:
|
|
604
|
+
"""Attach EdgeAnchor properly."""
|
|
605
|
+
self.__jac__ = EdgeAnchor(
|
|
606
|
+
architype=self, source=source, target=target, is_undirected=is_undirected
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
class WalkerArchitype(Architype):
|
|
611
|
+
"""Walker Architype Protocol."""
|
|
612
|
+
|
|
613
|
+
__jac__: WalkerAnchor
|
|
614
|
+
|
|
615
|
+
def __init__(self) -> None:
|
|
616
|
+
"""Create walker architype."""
|
|
617
|
+
self.__jac__ = WalkerAnchor(architype=self)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
@dataclass(eq=False)
|
|
621
|
+
class GenericEdge(EdgeArchitype):
|
|
622
|
+
"""Generic Root Node."""
|
|
623
|
+
|
|
624
|
+
_jac_entry_funcs_: ClassVar[list[DSFunc]] = []
|
|
625
|
+
_jac_exit_funcs_: ClassVar[list[DSFunc]] = []
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
@dataclass(eq=False)
|
|
629
|
+
class Root(NodeArchitype):
|
|
630
|
+
"""Generic Root Node."""
|
|
631
|
+
|
|
632
|
+
_jac_entry_funcs_: ClassVar[list[DSFunc]] = []
|
|
633
|
+
_jac_exit_funcs_: ClassVar[list[DSFunc]] = []
|
|
634
|
+
|
|
635
|
+
def __init__(self) -> None:
|
|
636
|
+
"""Create root node."""
|
|
637
|
+
self.__jac__ = NodeAnchor(architype=self, persistent=True, edges=[])
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
@dataclass(eq=False)
|
|
641
|
+
class DSFunc:
|
|
642
|
+
"""Data Spatial Function."""
|
|
643
|
+
|
|
644
|
+
name: str
|
|
645
|
+
trigger: type | UnionType | tuple[type | UnionType, ...] | None
|
|
646
|
+
func: Callable[[Any, Any], Any] | None = None
|
|
647
|
+
|
|
648
|
+
def resolve(self, cls: type) -> None:
|
|
649
|
+
"""Resolve the function."""
|
|
650
|
+
self.func = getattr(cls, self.name)
|
|
@@ -4,26 +4,24 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
from .architype import (
|
|
7
|
+
Anchor,
|
|
7
8
|
Architype,
|
|
8
9
|
DSFunc,
|
|
9
10
|
EdgeAnchor,
|
|
10
11
|
EdgeArchitype,
|
|
11
|
-
ElementAnchor,
|
|
12
12
|
GenericEdge,
|
|
13
13
|
NodeAnchor,
|
|
14
14
|
NodeArchitype,
|
|
15
|
-
ObjectAnchor,
|
|
16
15
|
Root,
|
|
17
16
|
WalkerAnchor,
|
|
18
17
|
WalkerArchitype,
|
|
19
18
|
)
|
|
20
|
-
from .context import ExecutionContext
|
|
21
|
-
from .memory import Memory,
|
|
19
|
+
from .context import ExecutionContext
|
|
20
|
+
from .memory import Memory, ShelfStorage
|
|
22
21
|
from .test import JacTestCheck, JacTestResult, JacTextTestRunner
|
|
23
22
|
|
|
24
23
|
__all__ = [
|
|
25
|
-
"
|
|
26
|
-
"ObjectAnchor",
|
|
24
|
+
"Anchor",
|
|
27
25
|
"NodeAnchor",
|
|
28
26
|
"EdgeAnchor",
|
|
29
27
|
"WalkerAnchor",
|
|
@@ -35,9 +33,8 @@ __all__ = [
|
|
|
35
33
|
"Root",
|
|
36
34
|
"DSFunc",
|
|
37
35
|
"Memory",
|
|
38
|
-
"
|
|
36
|
+
"ShelfStorage",
|
|
39
37
|
"ExecutionContext",
|
|
40
|
-
"exec_context",
|
|
41
38
|
"JacTestResult",
|
|
42
39
|
"JacTextTestRunner",
|
|
43
40
|
"JacTestCheck",
|