jaclang 0.7.16__py3-none-any.whl → 0.7.18__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 +140 -77
- jaclang/compiler/absyntree.py +9 -4
- jaclang/compiler/constant.py +8 -8
- jaclang/compiler/parser.py +10 -2
- 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 +152 -50
- jaclang/compiler/passes/main/import_pass.py +88 -59
- jaclang/compiler/passes/main/py_collect_dep_pass.py +70 -0
- jaclang/compiler/passes/main/pyast_gen_pass.py +46 -6
- jaclang/compiler/passes/main/pyast_load_pass.py +1 -0
- jaclang/compiler/passes/main/pyjac_ast_link_pass.py +7 -0
- jaclang/compiler/passes/main/schedules.py +9 -2
- jaclang/compiler/passes/main/sym_tab_build_pass.py +9 -5
- 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 +15 -5
- jaclang/compiler/passes/tool/jac_formatter_pass.py +13 -3
- jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +37 -41
- jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +37 -41
- jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/access_mod_check.jac +27 -0
- jaclang/compiler/passes/utils/mypy_ast_build.py +45 -0
- jaclang/compiler/symtable.py +16 -11
- jaclang/compiler/tests/test_importer.py +17 -9
- jaclang/langserve/engine.py +64 -16
- jaclang/langserve/server.py +16 -1
- jaclang/langserve/tests/fixtures/import_include_statements.jac +3 -3
- jaclang/langserve/tests/fixtures/rename.jac +30 -0
- jaclang/langserve/tests/test_server.py +224 -6
- jaclang/langserve/utils.py +28 -98
- jaclang/plugin/builtin.py +8 -4
- jaclang/plugin/default.py +86 -64
- jaclang/plugin/feature.py +13 -13
- jaclang/plugin/spec.py +10 -11
- jaclang/plugin/tests/fixtures/other_root_access.jac +82 -0
- jaclang/plugin/tests/test_jaseci.py +414 -42
- jaclang/runtimelib/architype.py +481 -333
- jaclang/runtimelib/constructs.py +5 -8
- jaclang/runtimelib/context.py +89 -69
- jaclang/runtimelib/importer.py +16 -15
- jaclang/runtimelib/machine.py +66 -2
- jaclang/runtimelib/memory.py +134 -75
- jaclang/runtimelib/utils.py +17 -10
- jaclang/settings.py +2 -4
- 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/bar.jac +34 -0
- jaclang/tests/fixtures/builtin_dotgen.jac +1 -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/foo.jac +43 -0
- jaclang/tests/fixtures/game1.jac +1 -1
- jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
- jaclang/tests/fixtures/import.jac +9 -0
- jaclang/tests/fixtures/index_slice.jac +30 -0
- jaclang/tests/fixtures/objref.jac +12 -0
- jaclang/tests/fixtures/pyfunc_1.py +1 -1
- jaclang/tests/fixtures/pyfunc_2.py +2 -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/test_cli.py +49 -6
- jaclang/tests/test_language.py +126 -80
- jaclang/tests/test_reference.py +2 -9
- jaclang/utils/treeprinter.py +30 -3
- {jaclang-0.7.16.dist-info → jaclang-0.7.18.dist-info}/METADATA +3 -1
- {jaclang-0.7.16.dist-info → jaclang-0.7.18.dist-info}/RECORD +81 -59
- /jaclang/tests/fixtures/{err.test.jac → baddy.test.jac} +0 -0
- {jaclang-0.7.16.dist-info → jaclang-0.7.18.dist-info}/WHEEL +0 -0
- {jaclang-0.7.16.dist-info → jaclang-0.7.18.dist-info}/entry_points.txt +0 -0
jaclang/runtimelib/constructs.py
CHANGED
|
@@ -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",
|
jaclang/runtimelib/context.py
CHANGED
|
@@ -4,80 +4,100 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import unittest
|
|
6
6
|
from contextvars import ContextVar
|
|
7
|
-
from typing import Callable, Optional
|
|
7
|
+
from typing import Any, Callable, Optional, cast
|
|
8
8
|
from uuid import UUID
|
|
9
9
|
|
|
10
|
-
from .architype import
|
|
11
|
-
from .
|
|
12
|
-
from .memory import Memory, ShelveStorage
|
|
10
|
+
from .architype import NodeAnchor, Root
|
|
11
|
+
from .memory import Memory, ShelfStorage
|
|
13
12
|
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def __init__(self) -> None:
|
|
22
|
-
"""Create execution context."""
|
|
23
|
-
super().__init__()
|
|
24
|
-
self.mem = ShelveStorage()
|
|
25
|
-
self.root = None
|
|
26
|
-
self.jac_machine = JacMachine()
|
|
27
|
-
jac_program = JacProgram(mod_bundle=None, bytecode=None)
|
|
28
|
-
self.jac_machine.attach_program(jac_program)
|
|
29
|
-
|
|
30
|
-
def init_memory(self, base_path: str = "", session: str = "") -> None:
|
|
31
|
-
"""Initialize memory."""
|
|
32
|
-
if session:
|
|
33
|
-
self.mem = ShelveStorage(session)
|
|
34
|
-
else:
|
|
35
|
-
self.mem = Memory()
|
|
36
|
-
self.jac_machine = JacMachine(base_path)
|
|
37
|
-
jac_program = JacProgram(mod_bundle=None, bytecode=None)
|
|
38
|
-
self.jac_machine.attach_program(jac_program)
|
|
39
|
-
|
|
40
|
-
def get_root(self) -> Root:
|
|
41
|
-
"""Get the root object."""
|
|
42
|
-
if self.mem is None:
|
|
43
|
-
raise ValueError("Memory not initialized")
|
|
44
|
-
|
|
45
|
-
if not self.root:
|
|
46
|
-
root = self.mem.get_obj(UUID(int=0))
|
|
47
|
-
if root is None:
|
|
48
|
-
self.root = Root()
|
|
49
|
-
self.mem.save_obj(self.root, persistent=self.root._jac_.persistent)
|
|
50
|
-
elif not isinstance(root, Root):
|
|
51
|
-
raise ValueError(f"Invalid root object: {root}")
|
|
52
|
-
else:
|
|
53
|
-
self.root = root
|
|
54
|
-
return self.root
|
|
55
|
-
|
|
56
|
-
def get_obj(self, obj_id: UUID) -> Architype | None:
|
|
57
|
-
"""Get object from memory."""
|
|
58
|
-
if self.mem is None:
|
|
59
|
-
raise ValueError("Memory not initialized")
|
|
60
|
-
|
|
61
|
-
return self.mem.get_obj(obj_id)
|
|
62
|
-
|
|
63
|
-
def save_obj(self, item: Architype, persistent: bool) -> None:
|
|
64
|
-
"""Save object to memory."""
|
|
65
|
-
if self.mem is None:
|
|
66
|
-
raise ValueError("Memory not initialized")
|
|
67
|
-
|
|
68
|
-
self.mem.save_obj(item, persistent)
|
|
69
|
-
|
|
70
|
-
def reset(self) -> None:
|
|
71
|
-
"""Reset the execution context."""
|
|
72
|
-
if self.mem:
|
|
73
|
-
self.mem.close()
|
|
74
|
-
self.mem = None
|
|
75
|
-
self.root = None
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
exec_context: ContextVar[ExecutionContext | None] = ContextVar(
|
|
79
|
-
"ExecutionContext", default=None
|
|
14
|
+
EXECUTION_CONTEXT = ContextVar[Optional["ExecutionContext"]]("ExecutionContext")
|
|
15
|
+
|
|
16
|
+
SUPER_ROOT_UUID = UUID("00000000-0000-0000-0000-000000000000")
|
|
17
|
+
SUPER_ROOT_ARCHITYPE = object.__new__(Root)
|
|
18
|
+
SUPER_ROOT_ANCHOR = NodeAnchor(
|
|
19
|
+
id=SUPER_ROOT_UUID, architype=SUPER_ROOT_ARCHITYPE, persistent=False, edges=[]
|
|
80
20
|
)
|
|
21
|
+
SUPER_ROOT_ARCHITYPE.__jac__ = SUPER_ROOT_ANCHOR
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ExecutionContext:
|
|
25
|
+
"""Execution Context."""
|
|
26
|
+
|
|
27
|
+
mem: Memory
|
|
28
|
+
reports: list[Any]
|
|
29
|
+
system_root: NodeAnchor
|
|
30
|
+
root: NodeAnchor
|
|
31
|
+
entry_node: NodeAnchor
|
|
32
|
+
|
|
33
|
+
def init_anchor(
|
|
34
|
+
self,
|
|
35
|
+
anchor_id: str | None,
|
|
36
|
+
default: NodeAnchor,
|
|
37
|
+
) -> NodeAnchor:
|
|
38
|
+
"""Load initial anchors."""
|
|
39
|
+
if anchor_id:
|
|
40
|
+
if isinstance(anchor := self.mem.find_by_id(UUID(anchor_id)), NodeAnchor):
|
|
41
|
+
return anchor
|
|
42
|
+
raise ValueError(f"Invalid anchor id {anchor_id} !")
|
|
43
|
+
return default
|
|
44
|
+
|
|
45
|
+
def validate_access(self) -> bool:
|
|
46
|
+
"""Validate access."""
|
|
47
|
+
return self.root.has_read_access(self.entry_node)
|
|
48
|
+
|
|
49
|
+
def set_entry_node(self, entry_node: str | None) -> None:
|
|
50
|
+
"""Override entry."""
|
|
51
|
+
self.entry_node = self.init_anchor(entry_node, self.root)
|
|
52
|
+
|
|
53
|
+
def close(self) -> None:
|
|
54
|
+
"""Close current ExecutionContext."""
|
|
55
|
+
self.mem.close()
|
|
56
|
+
EXECUTION_CONTEXT.set(None)
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def create(
|
|
60
|
+
session: Optional[str] = None,
|
|
61
|
+
root: Optional[str] = None,
|
|
62
|
+
auto_close: bool = True,
|
|
63
|
+
) -> ExecutionContext:
|
|
64
|
+
"""Create ExecutionContext."""
|
|
65
|
+
ctx = ExecutionContext()
|
|
66
|
+
ctx.mem = ShelfStorage(session)
|
|
67
|
+
ctx.reports = []
|
|
68
|
+
|
|
69
|
+
if not isinstance(
|
|
70
|
+
system_root := ctx.mem.find_by_id(SUPER_ROOT_UUID), NodeAnchor
|
|
71
|
+
):
|
|
72
|
+
system_root = Root().__jac__
|
|
73
|
+
system_root.id = SUPER_ROOT_UUID
|
|
74
|
+
ctx.mem.set(system_root.id, system_root)
|
|
75
|
+
|
|
76
|
+
ctx.system_root = system_root
|
|
77
|
+
|
|
78
|
+
ctx.entry_node = ctx.root = ctx.init_anchor(root, ctx.system_root)
|
|
79
|
+
|
|
80
|
+
if auto_close and (old_ctx := EXECUTION_CONTEXT.get(None)):
|
|
81
|
+
old_ctx.close()
|
|
82
|
+
|
|
83
|
+
EXECUTION_CONTEXT.set(ctx)
|
|
84
|
+
|
|
85
|
+
return ctx
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def get() -> ExecutionContext:
|
|
89
|
+
"""Get current ExecutionContext."""
|
|
90
|
+
if ctx := EXECUTION_CONTEXT.get(None):
|
|
91
|
+
return ctx
|
|
92
|
+
raise Exception("ExecutionContext is not yet available!")
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def get_root() -> Root:
|
|
96
|
+
"""Get current root."""
|
|
97
|
+
if ctx := EXECUTION_CONTEXT.get(None):
|
|
98
|
+
return cast(Root, ctx.root.architype)
|
|
99
|
+
|
|
100
|
+
return SUPER_ROOT_ARCHITYPE
|
|
81
101
|
|
|
82
102
|
|
|
83
103
|
class JacTestResult(unittest.TextTestResult):
|
jaclang/runtimelib/importer.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import importlib
|
|
6
|
+
import importlib.util
|
|
6
7
|
import os
|
|
7
8
|
import sys
|
|
8
9
|
import types
|
|
@@ -139,7 +140,7 @@ class ImportReturn:
|
|
|
139
140
|
else module.__name__
|
|
140
141
|
)
|
|
141
142
|
if isinstance(self.importer, JacImporter):
|
|
142
|
-
new_module =
|
|
143
|
+
new_module = self.importer.jac_machine.loaded_modules.get(
|
|
143
144
|
package_name,
|
|
144
145
|
self.importer.create_jac_py_module(
|
|
145
146
|
self.importer.get_sys_mod_name(jac_file_path),
|
|
@@ -177,8 +178,8 @@ class Importer:
|
|
|
177
178
|
|
|
178
179
|
def update_sys(self, module: types.ModuleType, spec: ImportPathSpec) -> None:
|
|
179
180
|
"""Update sys.modules with the newly imported module."""
|
|
180
|
-
if spec.module_name not in
|
|
181
|
-
|
|
181
|
+
if spec.module_name not in self.jac_machine.loaded_modules:
|
|
182
|
+
self.jac_machine.load_module(spec.module_name, module)
|
|
182
183
|
|
|
183
184
|
|
|
184
185
|
class PythonImporter(Importer):
|
|
@@ -280,8 +281,8 @@ class JacImporter(Importer):
|
|
|
280
281
|
module.__path__ = [full_mod_path]
|
|
281
282
|
module.__file__ = None
|
|
282
283
|
|
|
283
|
-
if module_name not in
|
|
284
|
-
|
|
284
|
+
if module_name not in self.jac_machine.loaded_modules:
|
|
285
|
+
self.jac_machine.load_module(module_name, module)
|
|
285
286
|
return module
|
|
286
287
|
|
|
287
288
|
def create_jac_py_module(
|
|
@@ -299,7 +300,7 @@ class JacImporter(Importer):
|
|
|
299
300
|
parts = package_path.split(".")
|
|
300
301
|
for i in range(len(parts)):
|
|
301
302
|
package_name = ".".join(parts[: i + 1])
|
|
302
|
-
if package_name not in
|
|
303
|
+
if package_name not in self.jac_machine.loaded_modules:
|
|
303
304
|
full_mod_path = path.join(
|
|
304
305
|
base_path, package_name.replace(".", path.sep)
|
|
305
306
|
)
|
|
@@ -307,7 +308,7 @@ class JacImporter(Importer):
|
|
|
307
308
|
module_name=package_name,
|
|
308
309
|
full_mod_path=full_mod_path,
|
|
309
310
|
)
|
|
310
|
-
|
|
311
|
+
self.jac_machine.load_module(module_name, module)
|
|
311
312
|
return module
|
|
312
313
|
|
|
313
314
|
def run_import(
|
|
@@ -322,7 +323,7 @@ class JacImporter(Importer):
|
|
|
322
323
|
else:
|
|
323
324
|
module_name = self.get_sys_mod_name(spec.full_target)
|
|
324
325
|
|
|
325
|
-
module =
|
|
326
|
+
module = self.jac_machine.loaded_modules.get(module_name)
|
|
326
327
|
|
|
327
328
|
if not module or module.__name__ == "__main__" or reload:
|
|
328
329
|
if os.path.isdir(spec.full_target):
|
|
@@ -340,14 +341,14 @@ class JacImporter(Importer):
|
|
|
340
341
|
caller_dir=spec.caller_dir,
|
|
341
342
|
cachable=spec.cachable,
|
|
342
343
|
)
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
344
|
+
try:
|
|
345
|
+
if not codeobj:
|
|
346
|
+
raise ImportError(f"No bytecode found for {spec.full_target}")
|
|
347
|
+
with sys_path_context(spec.caller_dir):
|
|
347
348
|
exec(codeobj, module.__dict__)
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
349
|
+
except Exception as e:
|
|
350
|
+
logger.error(dump_traceback(e))
|
|
351
|
+
raise e
|
|
351
352
|
import_return = ImportReturn(module, unique_loaded_items, self)
|
|
352
353
|
if spec.items:
|
|
353
354
|
import_return.process_items(
|
jaclang/runtimelib/machine.py
CHANGED
|
@@ -1,25 +1,33 @@
|
|
|
1
1
|
"""Jac Machine module."""
|
|
2
2
|
|
|
3
|
+
import inspect
|
|
3
4
|
import marshal
|
|
4
5
|
import os
|
|
6
|
+
import sys
|
|
5
7
|
import types
|
|
8
|
+
from contextvars import ContextVar
|
|
6
9
|
from typing import Optional
|
|
7
10
|
|
|
8
11
|
from jaclang.compiler.absyntree import Module
|
|
9
12
|
from jaclang.compiler.compile import compile_jac
|
|
10
13
|
from jaclang.compiler.constant import Constants as Con
|
|
14
|
+
from jaclang.runtimelib.architype import EdgeArchitype, NodeArchitype, WalkerArchitype
|
|
11
15
|
from jaclang.utils.log import logging
|
|
12
16
|
|
|
17
|
+
|
|
13
18
|
logger = logging.getLogger(__name__)
|
|
14
19
|
|
|
15
20
|
|
|
21
|
+
JACMACHINE_CONTEXT = ContextVar["JacMachine | None"]("JacMachine")
|
|
22
|
+
|
|
23
|
+
|
|
16
24
|
class JacMachine:
|
|
17
25
|
"""JacMachine to handle the VM-related functionalities and loaded programs."""
|
|
18
26
|
|
|
19
27
|
def __init__(self, base_path: str = "") -> None:
|
|
20
28
|
"""Initialize the JacMachine object."""
|
|
21
|
-
|
|
22
|
-
if not
|
|
29
|
+
self.loaded_modules: dict[str, types.ModuleType] = {}
|
|
30
|
+
if not base_path:
|
|
23
31
|
base_path = os.getcwd()
|
|
24
32
|
self.base_path = base_path
|
|
25
33
|
self.base_path_dir = (
|
|
@@ -29,6 +37,8 @@ class JacMachine:
|
|
|
29
37
|
)
|
|
30
38
|
self.jac_program: Optional[JacProgram] = None
|
|
31
39
|
|
|
40
|
+
JACMACHINE_CONTEXT.set(self)
|
|
41
|
+
|
|
32
42
|
def attach_program(self, jac_program: "JacProgram") -> None:
|
|
33
43
|
"""Attach a JacProgram to the machine."""
|
|
34
44
|
self.jac_program = jac_program
|
|
@@ -53,6 +63,60 @@ class JacMachine:
|
|
|
53
63
|
)
|
|
54
64
|
return None
|
|
55
65
|
|
|
66
|
+
def load_module(self, module_name: str, module: types.ModuleType) -> None:
|
|
67
|
+
"""Load a module into the machine."""
|
|
68
|
+
self.loaded_modules[module_name] = module
|
|
69
|
+
sys.modules[module_name] = module
|
|
70
|
+
|
|
71
|
+
def list_modules(self) -> list[str]:
|
|
72
|
+
"""List all loaded modules."""
|
|
73
|
+
return list(self.loaded_modules.keys())
|
|
74
|
+
|
|
75
|
+
def list_walkers(self, module_name: str) -> list[str]:
|
|
76
|
+
"""List all walkers in a specific module."""
|
|
77
|
+
module = self.loaded_modules.get(module_name)
|
|
78
|
+
if module:
|
|
79
|
+
walkers = []
|
|
80
|
+
for name, obj in inspect.getmembers(module):
|
|
81
|
+
if isinstance(obj, type) and issubclass(obj, WalkerArchitype):
|
|
82
|
+
walkers.append(name)
|
|
83
|
+
return walkers
|
|
84
|
+
return []
|
|
85
|
+
|
|
86
|
+
def list_nodes(self, module_name: str) -> list[str]:
|
|
87
|
+
"""List all nodes in a specific module."""
|
|
88
|
+
module = self.loaded_modules.get(module_name)
|
|
89
|
+
if module:
|
|
90
|
+
nodes = []
|
|
91
|
+
for name, obj in inspect.getmembers(module):
|
|
92
|
+
if isinstance(obj, type) and issubclass(obj, NodeArchitype):
|
|
93
|
+
nodes.append(name)
|
|
94
|
+
return nodes
|
|
95
|
+
return []
|
|
96
|
+
|
|
97
|
+
def list_edges(self, module_name: str) -> list[str]:
|
|
98
|
+
"""List all edges in a specific module."""
|
|
99
|
+
module = self.loaded_modules.get(module_name)
|
|
100
|
+
if module:
|
|
101
|
+
nodes = []
|
|
102
|
+
for name, obj in inspect.getmembers(module):
|
|
103
|
+
if isinstance(obj, type) and issubclass(obj, EdgeArchitype):
|
|
104
|
+
nodes.append(name)
|
|
105
|
+
return nodes
|
|
106
|
+
return []
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def get(base_path: str = "") -> "JacMachine":
|
|
110
|
+
"""Get current jac machine."""
|
|
111
|
+
if (jac_machine := JACMACHINE_CONTEXT.get(None)) is None:
|
|
112
|
+
jac_machine = JacMachine(base_path)
|
|
113
|
+
return jac_machine
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def detach() -> None:
|
|
117
|
+
"""Detach current jac machine."""
|
|
118
|
+
JACMACHINE_CONTEXT.set(None)
|
|
119
|
+
|
|
56
120
|
|
|
57
121
|
class JacProgram:
|
|
58
122
|
"""Class to hold the mod_bundle and bytecode for Jac modules."""
|
jaclang/runtimelib/memory.py
CHANGED
|
@@ -2,98 +2,157 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pickle import dumps
|
|
7
|
+
from shelve import Shelf, open
|
|
8
|
+
from typing import Callable, Generator, Generic, Iterable, TypeVar
|
|
6
9
|
from uuid import UUID
|
|
7
10
|
|
|
8
|
-
from .architype import
|
|
11
|
+
from .architype import Anchor, NodeAnchor, Root, TANCH
|
|
9
12
|
|
|
13
|
+
ID = TypeVar("ID")
|
|
10
14
|
|
|
11
|
-
class Memory:
|
|
12
|
-
"""Memory module interface."""
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
@dataclass
|
|
17
|
+
class Memory(Generic[ID, TANCH]):
|
|
18
|
+
"""Generic Memory Handler."""
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
pass
|
|
20
|
+
__mem__: dict[ID, TANCH] = field(default_factory=dict)
|
|
21
|
+
__gc__: set[TANCH] = field(default_factory=set)
|
|
20
22
|
|
|
21
|
-
def
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
def close(self) -> None:
|
|
24
|
+
"""Close memory handler."""
|
|
25
|
+
self.__mem__.clear()
|
|
26
|
+
self.__gc__.clear()
|
|
27
|
+
|
|
28
|
+
def find(
|
|
29
|
+
self,
|
|
30
|
+
ids: ID | Iterable[ID],
|
|
31
|
+
filter: Callable[[TANCH], TANCH] | None = None,
|
|
32
|
+
) -> Generator[TANCH, None, None]:
|
|
33
|
+
"""Find anchors from memory by ids with filter."""
|
|
34
|
+
if not isinstance(ids, Iterable):
|
|
35
|
+
ids = [ids]
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
anchor
|
|
39
|
+
for id in ids
|
|
40
|
+
if (anchor := self.__mem__.get(id)) and (not filter or filter(anchor))
|
|
41
|
+
)
|
|
29
42
|
|
|
30
|
-
def
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
def find_one(
|
|
44
|
+
self,
|
|
45
|
+
ids: ID | Iterable[ID],
|
|
46
|
+
filter: Callable[[TANCH], TANCH] | None = None,
|
|
47
|
+
) -> TANCH | None:
|
|
48
|
+
"""Find one anchor from memory by ids with filter."""
|
|
49
|
+
return next(self.find(ids, filter), None)
|
|
33
50
|
|
|
34
|
-
def
|
|
35
|
-
"""
|
|
36
|
-
return
|
|
51
|
+
def find_by_id(self, id: ID) -> TANCH | None:
|
|
52
|
+
"""Find one by id."""
|
|
53
|
+
return self.__mem__.get(id)
|
|
37
54
|
|
|
38
|
-
def
|
|
39
|
-
"""Save
|
|
40
|
-
self.
|
|
41
|
-
if persistent:
|
|
42
|
-
# TODO: check if it needs to be saved, i.e. dirty or not
|
|
43
|
-
self.save_obj_list[item._jac_.id] = item
|
|
55
|
+
def set(self, id: ID, data: TANCH) -> None:
|
|
56
|
+
"""Save anchor to memory."""
|
|
57
|
+
self.__mem__[id] = data
|
|
44
58
|
|
|
45
|
-
def
|
|
46
|
-
"""
|
|
47
|
-
|
|
59
|
+
def remove(self, ids: ID | Iterable[ID]) -> None:
|
|
60
|
+
"""Remove anchor/s from memory."""
|
|
61
|
+
if not isinstance(ids, Iterable):
|
|
62
|
+
ids = [ids]
|
|
48
63
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
64
|
+
for id in ids:
|
|
65
|
+
if anchor := self.__mem__.pop(id, None):
|
|
66
|
+
self.__gc__.add(anchor)
|
|
52
67
|
|
|
53
68
|
|
|
54
|
-
|
|
55
|
-
|
|
69
|
+
@dataclass
|
|
70
|
+
class ShelfStorage(Memory[UUID, Anchor]):
|
|
71
|
+
"""Shelf Handler."""
|
|
56
72
|
|
|
57
|
-
|
|
73
|
+
__shelf__: Shelf[Anchor] | None = None
|
|
58
74
|
|
|
59
|
-
def __init__(self, session: str =
|
|
60
|
-
"""
|
|
75
|
+
def __init__(self, session: str | None = None) -> None:
|
|
76
|
+
"""Initialize memory handler."""
|
|
61
77
|
super().__init__()
|
|
62
|
-
if session:
|
|
63
|
-
self.connect(session)
|
|
64
|
-
|
|
65
|
-
def get_obj_from_store(self, obj_id: UUID) -> Architype | None:
|
|
66
|
-
"""Get object from the underlying store."""
|
|
67
|
-
obj = super().get_obj_from_store(obj_id)
|
|
68
|
-
if obj is None and self.storage:
|
|
69
|
-
obj = self.storage.get(str(obj_id))
|
|
70
|
-
if obj is not None:
|
|
71
|
-
self.mem[obj_id] = obj
|
|
72
|
-
|
|
73
|
-
return obj
|
|
74
|
-
|
|
75
|
-
def has_obj_in_store(self, obj_id: UUID | str) -> bool:
|
|
76
|
-
"""Check if the object exists in the underlying store."""
|
|
77
|
-
return obj_id in self.mem or (
|
|
78
|
-
str(obj_id) in self.storage if self.storage else False
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
def commit(self) -> None:
|
|
82
|
-
"""Commit changes to persistent storage."""
|
|
83
|
-
if self.storage is not None:
|
|
84
|
-
for obj_id, obj in self.save_obj_list.items():
|
|
85
|
-
self.storage[str(obj_id)] = obj
|
|
86
|
-
self.save_obj_list.clear()
|
|
87
|
-
|
|
88
|
-
def connect(self, session: str) -> None:
|
|
89
|
-
"""Connect to storage."""
|
|
90
|
-
self.session = session
|
|
91
|
-
self.storage = shelve.open(session)
|
|
78
|
+
self.__shelf__ = open(session) if session else None # noqa: SIM115
|
|
92
79
|
|
|
93
80
|
def close(self) -> None:
|
|
94
|
-
"""Close
|
|
81
|
+
"""Close memory handler."""
|
|
82
|
+
if isinstance(self.__shelf__, Shelf):
|
|
83
|
+
from jaclang.plugin.feature import JacFeature as Jac
|
|
84
|
+
|
|
85
|
+
root = Jac.get_root().__jac__
|
|
86
|
+
|
|
87
|
+
for anchor in self.__gc__:
|
|
88
|
+
self.__shelf__.pop(str(anchor.id), None)
|
|
89
|
+
self.__mem__.pop(anchor.id, None)
|
|
90
|
+
|
|
91
|
+
for d in self.__mem__.values():
|
|
92
|
+
if d.persistent and d.hash != hash(dumps(d)):
|
|
93
|
+
_id = str(d.id)
|
|
94
|
+
if p_d := self.__shelf__.get(_id):
|
|
95
|
+
if (
|
|
96
|
+
isinstance(p_d, NodeAnchor)
|
|
97
|
+
and isinstance(d, NodeAnchor)
|
|
98
|
+
and p_d.edges != d.edges
|
|
99
|
+
and root.has_connect_access(d)
|
|
100
|
+
):
|
|
101
|
+
if not d.edges:
|
|
102
|
+
self.__shelf__.pop(_id, None)
|
|
103
|
+
continue
|
|
104
|
+
p_d.edges = d.edges
|
|
105
|
+
|
|
106
|
+
if root.has_write_access(d):
|
|
107
|
+
if hash(dumps(p_d.access)) != hash(dumps(d.access)):
|
|
108
|
+
p_d.access = d.access
|
|
109
|
+
if hash(dumps(d.architype)) != hash(dumps(d.architype)):
|
|
110
|
+
p_d.architype = d.architype
|
|
111
|
+
|
|
112
|
+
self.__shelf__[_id] = p_d
|
|
113
|
+
elif not (
|
|
114
|
+
isinstance(d, NodeAnchor)
|
|
115
|
+
and not isinstance(d.architype, Root)
|
|
116
|
+
and not d.edges
|
|
117
|
+
):
|
|
118
|
+
self.__shelf__[_id] = d
|
|
119
|
+
|
|
120
|
+
self.__shelf__.close()
|
|
95
121
|
super().close()
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
122
|
+
|
|
123
|
+
def find(
|
|
124
|
+
self,
|
|
125
|
+
ids: UUID | Iterable[UUID],
|
|
126
|
+
filter: Callable[[Anchor], Anchor] | None = None,
|
|
127
|
+
) -> Generator[Anchor, None, None]:
|
|
128
|
+
"""Find anchors from datasource by ids with filter."""
|
|
129
|
+
if not isinstance(ids, Iterable):
|
|
130
|
+
ids = [ids]
|
|
131
|
+
|
|
132
|
+
if isinstance(self.__shelf__, Shelf):
|
|
133
|
+
for id in ids:
|
|
134
|
+
anchor = self.__mem__.get(id)
|
|
135
|
+
|
|
136
|
+
if (
|
|
137
|
+
not anchor
|
|
138
|
+
and id not in self.__gc__
|
|
139
|
+
and (_anchor := self.__shelf__.get(str(id)))
|
|
140
|
+
):
|
|
141
|
+
self.__mem__[id] = anchor = _anchor
|
|
142
|
+
if anchor and (not filter or filter(anchor)):
|
|
143
|
+
yield anchor
|
|
144
|
+
else:
|
|
145
|
+
yield from super().find(ids, filter)
|
|
146
|
+
|
|
147
|
+
def find_by_id(self, id: UUID) -> Anchor | None:
|
|
148
|
+
"""Find one by id."""
|
|
149
|
+
data = super().find_by_id(id)
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
not data
|
|
153
|
+
and isinstance(self.__shelf__, Shelf)
|
|
154
|
+
and (data := self.__shelf__.get(str(id)))
|
|
155
|
+
):
|
|
156
|
+
self.__mem__[id] = data
|
|
157
|
+
|
|
158
|
+
return data
|
jaclang/runtimelib/utils.py
CHANGED
|
@@ -37,12 +37,16 @@ def collect_node_connections(
|
|
|
37
37
|
visited_nodes.add(current_node)
|
|
38
38
|
edges = current_node.edges
|
|
39
39
|
for edge_ in edges:
|
|
40
|
-
target = edge_.
|
|
40
|
+
target = edge_.target
|
|
41
41
|
if target:
|
|
42
42
|
connections.add(
|
|
43
|
-
(
|
|
43
|
+
(
|
|
44
|
+
current_node.architype,
|
|
45
|
+
target.architype,
|
|
46
|
+
edge_.__class__.__name__,
|
|
47
|
+
)
|
|
44
48
|
)
|
|
45
|
-
collect_node_connections(target
|
|
49
|
+
collect_node_connections(target, visited_nodes, connections)
|
|
46
50
|
|
|
47
51
|
|
|
48
52
|
def traverse_graph(
|
|
@@ -61,17 +65,20 @@ def traverse_graph(
|
|
|
61
65
|
edge_limit: int,
|
|
62
66
|
) -> None:
|
|
63
67
|
"""Traverse the graph using Breadth-First Search (BFS) or Depth-First Search (DFS)."""
|
|
64
|
-
for edge in node.
|
|
65
|
-
is_self_loop = id(edge.
|
|
66
|
-
is_in_edge = edge.
|
|
67
|
-
if (traverse and is_in_edge) or edge.
|
|
68
|
+
for edge in node.__jac__.edges:
|
|
69
|
+
is_self_loop = id(edge.source) == id(edge.target)
|
|
70
|
+
is_in_edge = edge.target == node.__jac__
|
|
71
|
+
if (traverse and is_in_edge) or edge.architype.__class__.__name__ in edge_type:
|
|
68
72
|
continue
|
|
69
73
|
if is_self_loop:
|
|
70
74
|
continue # lets skip self loop for a while, need to handle it later
|
|
71
|
-
else
|
|
72
|
-
other_nd
|
|
75
|
+
elif (other_nda := edge.target if not is_in_edge else edge.source) and (
|
|
76
|
+
other_nd := other_nda.architype
|
|
77
|
+
):
|
|
73
78
|
new_con = (
|
|
74
|
-
(node, other_nd, edge)
|
|
79
|
+
(node, other_nd, edge.architype)
|
|
80
|
+
if not is_in_edge
|
|
81
|
+
else (other_nd, node, edge.architype)
|
|
75
82
|
)
|
|
76
83
|
if node in node_depths and node_depths[node] is not None:
|
|
77
84
|
if other_nd in node_depths:
|