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
|
@@ -4,73 +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 .memory import Memory,
|
|
10
|
+
from .architype import NodeAnchor, Root
|
|
11
|
+
from .memory import Memory, ShelfStorage
|
|
12
|
+
|
|
13
|
+
|
|
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=[]
|
|
20
|
+
)
|
|
21
|
+
SUPER_ROOT_ARCHITYPE.__jac__ = SUPER_ROOT_ANCHOR
|
|
12
22
|
|
|
13
23
|
|
|
14
24
|
class ExecutionContext:
|
|
15
|
-
"""
|
|
25
|
+
"""Execution Context."""
|
|
26
|
+
|
|
27
|
+
mem: Memory
|
|
28
|
+
reports: list[Any]
|
|
29
|
+
system_root: NodeAnchor
|
|
30
|
+
root: NodeAnchor
|
|
31
|
+
entry_node: NodeAnchor
|
|
16
32
|
|
|
17
|
-
|
|
18
|
-
|
|
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)
|
|
19
57
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 = []
|
|
25
68
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if self.mem is None:
|
|
59
|
-
raise ValueError("Memory not initialized")
|
|
60
|
-
|
|
61
|
-
self.mem.save_obj(item, persistent)
|
|
62
|
-
|
|
63
|
-
def reset(self) -> None:
|
|
64
|
-
"""Reset the execution context."""
|
|
65
|
-
if self.mem:
|
|
66
|
-
self.mem.close()
|
|
67
|
-
self.mem = None
|
|
68
|
-
self.root = None
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
exec_context: ContextVar[ExecutionContext | None] = ContextVar(
|
|
72
|
-
"ExecutionContext", default=None
|
|
73
|
-
)
|
|
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
|
|
74
101
|
|
|
75
102
|
|
|
76
103
|
class JacTestResult(unittest.TextTestResult):
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
"""Special Imports for Jac Code."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import types
|
|
9
|
+
from os import getcwd, path
|
|
10
|
+
from typing import Optional, Union
|
|
11
|
+
|
|
12
|
+
from jaclang.runtimelib.machine import JacMachine
|
|
13
|
+
from jaclang.runtimelib.utils import sys_path_context
|
|
14
|
+
from jaclang.utils.helpers import dump_traceback
|
|
15
|
+
from jaclang.utils.log import logging
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ImportPathSpec:
|
|
21
|
+
"""Import Specification."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
target: str,
|
|
26
|
+
base_path: str,
|
|
27
|
+
absorb: bool,
|
|
28
|
+
cachable: bool,
|
|
29
|
+
mdl_alias: Optional[str],
|
|
30
|
+
override_name: Optional[str],
|
|
31
|
+
lng: Optional[str],
|
|
32
|
+
items: Optional[dict[str, Union[str, Optional[str]]]],
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Initialize the ImportPathSpec object."""
|
|
35
|
+
self.target = target
|
|
36
|
+
self.base_path = base_path
|
|
37
|
+
self.absorb = absorb
|
|
38
|
+
self.cachable = cachable
|
|
39
|
+
self.mdl_alias = mdl_alias
|
|
40
|
+
self.override_name = override_name
|
|
41
|
+
self.language = lng
|
|
42
|
+
self.items = items
|
|
43
|
+
self.dir_path, self.file_name = path.split(path.join(*(target.split("."))))
|
|
44
|
+
self.module_name = path.splitext(self.file_name)[0]
|
|
45
|
+
self.package_path = self.dir_path.replace(path.sep, ".")
|
|
46
|
+
self.caller_dir = self.get_caller_dir()
|
|
47
|
+
self.full_target = path.abspath(path.join(self.caller_dir, self.file_name))
|
|
48
|
+
|
|
49
|
+
def get_caller_dir(self) -> str:
|
|
50
|
+
"""Get the directory of the caller."""
|
|
51
|
+
caller_dir = (
|
|
52
|
+
self.base_path
|
|
53
|
+
if path.isdir(self.base_path)
|
|
54
|
+
else path.dirname(self.base_path)
|
|
55
|
+
)
|
|
56
|
+
caller_dir = caller_dir if caller_dir else getcwd()
|
|
57
|
+
chomp_target = self.target
|
|
58
|
+
if chomp_target.startswith("."):
|
|
59
|
+
chomp_target = chomp_target[1:]
|
|
60
|
+
while chomp_target.startswith("."):
|
|
61
|
+
caller_dir = path.dirname(caller_dir)
|
|
62
|
+
chomp_target = chomp_target[1:]
|
|
63
|
+
return path.join(caller_dir, self.dir_path)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ImportReturn:
|
|
67
|
+
"""Import Return Object."""
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
ret_mod: types.ModuleType,
|
|
72
|
+
ret_items: list[types.ModuleType],
|
|
73
|
+
importer: Importer,
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Initialize the ImportReturn object."""
|
|
76
|
+
self.ret_mod = ret_mod
|
|
77
|
+
self.ret_items = ret_items
|
|
78
|
+
self.importer = importer
|
|
79
|
+
|
|
80
|
+
def process_items(
|
|
81
|
+
self,
|
|
82
|
+
module: types.ModuleType,
|
|
83
|
+
items: dict[str, Union[str, Optional[str]]],
|
|
84
|
+
caller_dir: str,
|
|
85
|
+
lang: Optional[str],
|
|
86
|
+
cachable: bool = True,
|
|
87
|
+
) -> None:
|
|
88
|
+
"""Process items within a module by handling renaming and potentially loading missing attributes."""
|
|
89
|
+
|
|
90
|
+
def handle_item_loading(
|
|
91
|
+
item: types.ModuleType, alias: Union[str, Optional[str]]
|
|
92
|
+
) -> None:
|
|
93
|
+
if item:
|
|
94
|
+
self.ret_items.append(item)
|
|
95
|
+
setattr(module, name, item)
|
|
96
|
+
if alias and alias != name and not isinstance(alias, bool):
|
|
97
|
+
setattr(module, alias, item)
|
|
98
|
+
|
|
99
|
+
for name, alias in items.items():
|
|
100
|
+
try:
|
|
101
|
+
item = getattr(module, name)
|
|
102
|
+
handle_item_loading(item, alias)
|
|
103
|
+
except AttributeError:
|
|
104
|
+
if lang == "jac":
|
|
105
|
+
jac_file_path = (
|
|
106
|
+
os.path.join(module.__path__[0], f"{name}.jac")
|
|
107
|
+
if hasattr(module, "__path__")
|
|
108
|
+
else module.__file__
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if jac_file_path and os.path.isfile(jac_file_path):
|
|
112
|
+
item = self.load_jac_mod_as_item(
|
|
113
|
+
module=module,
|
|
114
|
+
name=name,
|
|
115
|
+
jac_file_path=jac_file_path,
|
|
116
|
+
cachable=cachable,
|
|
117
|
+
caller_dir=caller_dir,
|
|
118
|
+
)
|
|
119
|
+
handle_item_loading(item, alias)
|
|
120
|
+
else:
|
|
121
|
+
if hasattr(module, "__path__"):
|
|
122
|
+
full_module_name = f"{module.__name__}.{name}"
|
|
123
|
+
item = importlib.import_module(full_module_name)
|
|
124
|
+
handle_item_loading(item, alias)
|
|
125
|
+
|
|
126
|
+
def load_jac_mod_as_item(
|
|
127
|
+
self,
|
|
128
|
+
module: types.ModuleType,
|
|
129
|
+
name: str,
|
|
130
|
+
jac_file_path: str,
|
|
131
|
+
cachable: bool,
|
|
132
|
+
caller_dir: str,
|
|
133
|
+
) -> Optional[types.ModuleType]:
|
|
134
|
+
"""Load a single .jac file into the specified module component."""
|
|
135
|
+
try:
|
|
136
|
+
package_name = (
|
|
137
|
+
f"{module.__name__}.{name}"
|
|
138
|
+
if hasattr(module, "__path__")
|
|
139
|
+
else module.__name__
|
|
140
|
+
)
|
|
141
|
+
if isinstance(self.importer, JacImporter):
|
|
142
|
+
new_module = self.importer.jac_machine.loaded_modules.get(
|
|
143
|
+
package_name,
|
|
144
|
+
self.importer.create_jac_py_module(
|
|
145
|
+
self.importer.get_sys_mod_name(jac_file_path),
|
|
146
|
+
module.__name__,
|
|
147
|
+
jac_file_path,
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
codeobj = self.importer.jac_machine.get_bytecode(
|
|
151
|
+
name, jac_file_path, caller_dir=caller_dir, cachable=cachable
|
|
152
|
+
)
|
|
153
|
+
if not codeobj:
|
|
154
|
+
raise ImportError(f"No bytecode found for {jac_file_path}")
|
|
155
|
+
|
|
156
|
+
exec(codeobj, new_module.__dict__)
|
|
157
|
+
return getattr(new_module, name, new_module)
|
|
158
|
+
except ImportError as e:
|
|
159
|
+
logger.error(dump_traceback(e))
|
|
160
|
+
# logger.error(
|
|
161
|
+
# f"Failed to load {name} from {jac_file_path} in {module.__name__}: {str(e)}"
|
|
162
|
+
# )
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class Importer:
|
|
167
|
+
"""Abstract base class for all importers."""
|
|
168
|
+
|
|
169
|
+
def __init__(self, jac_machine: JacMachine) -> None:
|
|
170
|
+
"""Initialize the Importer object."""
|
|
171
|
+
self.jac_machine = jac_machine
|
|
172
|
+
self.result: Optional[ImportReturn] = None
|
|
173
|
+
|
|
174
|
+
def run_import(self, spec: ImportPathSpec) -> ImportReturn:
|
|
175
|
+
"""Run the import process."""
|
|
176
|
+
raise NotImplementedError
|
|
177
|
+
|
|
178
|
+
def update_sys(self, module: types.ModuleType, spec: ImportPathSpec) -> None:
|
|
179
|
+
"""Update sys.modules with the newly imported module."""
|
|
180
|
+
if spec.module_name not in self.jac_machine.loaded_modules:
|
|
181
|
+
self.jac_machine.load_module(spec.module_name, module)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class PythonImporter(Importer):
|
|
185
|
+
"""Importer for Python modules."""
|
|
186
|
+
|
|
187
|
+
def __init__(self, jac_machine: JacMachine) -> None:
|
|
188
|
+
"""Initialize the PythonImporter object."""
|
|
189
|
+
self.jac_machine = jac_machine
|
|
190
|
+
|
|
191
|
+
def run_import(self, spec: ImportPathSpec) -> ImportReturn:
|
|
192
|
+
"""Run the import process for Python modules."""
|
|
193
|
+
try:
|
|
194
|
+
loaded_items: list = []
|
|
195
|
+
if spec.target.startswith("."):
|
|
196
|
+
spec.target = spec.target.lstrip(".")
|
|
197
|
+
full_target = path.normpath(path.join(spec.caller_dir, spec.target))
|
|
198
|
+
imp_spec = importlib.util.spec_from_file_location(
|
|
199
|
+
spec.target, full_target + ".py"
|
|
200
|
+
)
|
|
201
|
+
if imp_spec and imp_spec.loader:
|
|
202
|
+
imported_module = importlib.util.module_from_spec(imp_spec)
|
|
203
|
+
sys.modules[imp_spec.name] = imported_module
|
|
204
|
+
imp_spec.loader.exec_module(imported_module)
|
|
205
|
+
else:
|
|
206
|
+
raise ImportError(
|
|
207
|
+
f"Cannot find module {spec.target} at {full_target}"
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
imported_module = importlib.import_module(name=spec.target)
|
|
211
|
+
|
|
212
|
+
main_module = __import__("__main__")
|
|
213
|
+
if spec.absorb:
|
|
214
|
+
for name in dir(imported_module):
|
|
215
|
+
if not name.startswith("_"):
|
|
216
|
+
setattr(main_module, name, getattr(imported_module, name))
|
|
217
|
+
|
|
218
|
+
elif spec.items:
|
|
219
|
+
for name, alias in spec.items.items():
|
|
220
|
+
if isinstance(alias, bool):
|
|
221
|
+
alias = name
|
|
222
|
+
try:
|
|
223
|
+
item = getattr(imported_module, name)
|
|
224
|
+
if item not in loaded_items:
|
|
225
|
+
setattr(
|
|
226
|
+
main_module,
|
|
227
|
+
alias if isinstance(alias, str) else name,
|
|
228
|
+
item,
|
|
229
|
+
)
|
|
230
|
+
loaded_items.append(item)
|
|
231
|
+
except AttributeError as e:
|
|
232
|
+
if hasattr(imported_module, "__path__"):
|
|
233
|
+
item = importlib.import_module(f"{spec.target}.{name}")
|
|
234
|
+
if item not in loaded_items:
|
|
235
|
+
setattr(
|
|
236
|
+
main_module,
|
|
237
|
+
alias if isinstance(alias, str) else name,
|
|
238
|
+
item,
|
|
239
|
+
)
|
|
240
|
+
loaded_items.append(item)
|
|
241
|
+
else:
|
|
242
|
+
raise e
|
|
243
|
+
|
|
244
|
+
else:
|
|
245
|
+
setattr(
|
|
246
|
+
__import__("__main__"),
|
|
247
|
+
spec.mdl_alias if isinstance(spec.mdl_alias, str) else spec.target,
|
|
248
|
+
imported_module,
|
|
249
|
+
)
|
|
250
|
+
self.result = ImportReturn(imported_module, loaded_items, self)
|
|
251
|
+
return self.result
|
|
252
|
+
|
|
253
|
+
except ImportError as e:
|
|
254
|
+
raise e
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class JacImporter(Importer):
|
|
258
|
+
"""Importer for Jac modules."""
|
|
259
|
+
|
|
260
|
+
def __init__(self, jac_machine: JacMachine) -> None:
|
|
261
|
+
"""Initialize the JacImporter object."""
|
|
262
|
+
self.jac_machine = jac_machine
|
|
263
|
+
|
|
264
|
+
def get_sys_mod_name(self, full_target: str) -> str:
|
|
265
|
+
"""Generate the system module name based on full target path and package path."""
|
|
266
|
+
if full_target == self.jac_machine.base_path_dir:
|
|
267
|
+
return path.basename(self.jac_machine.base_path_dir)
|
|
268
|
+
relative_path = path.relpath(full_target, start=self.jac_machine.base_path_dir)
|
|
269
|
+
base_name = path.splitext(relative_path)[0]
|
|
270
|
+
sys_mod_name = base_name.replace(os.sep, ".").strip(".")
|
|
271
|
+
return sys_mod_name
|
|
272
|
+
|
|
273
|
+
def handle_directory(
|
|
274
|
+
self, module_name: str, full_mod_path: str
|
|
275
|
+
) -> types.ModuleType:
|
|
276
|
+
"""Import from a directory that potentially contains multiple Jac modules."""
|
|
277
|
+
module_name = self.get_sys_mod_name(full_mod_path)
|
|
278
|
+
module = types.ModuleType(module_name)
|
|
279
|
+
module.__name__ = module_name
|
|
280
|
+
module.__path__ = [full_mod_path]
|
|
281
|
+
module.__file__ = None
|
|
282
|
+
|
|
283
|
+
if module_name not in self.jac_machine.loaded_modules:
|
|
284
|
+
self.jac_machine.load_module(module_name, module)
|
|
285
|
+
return module
|
|
286
|
+
|
|
287
|
+
def create_jac_py_module(
|
|
288
|
+
self,
|
|
289
|
+
module_name: str,
|
|
290
|
+
package_path: str,
|
|
291
|
+
full_target: str,
|
|
292
|
+
) -> types.ModuleType:
|
|
293
|
+
"""Create a module."""
|
|
294
|
+
module = types.ModuleType(module_name)
|
|
295
|
+
module.__file__ = full_target
|
|
296
|
+
module.__name__ = module_name
|
|
297
|
+
if package_path:
|
|
298
|
+
base_path = full_target.split(package_path.replace(".", path.sep))[0]
|
|
299
|
+
parts = package_path.split(".")
|
|
300
|
+
for i in range(len(parts)):
|
|
301
|
+
package_name = ".".join(parts[: i + 1])
|
|
302
|
+
if package_name not in self.jac_machine.loaded_modules:
|
|
303
|
+
full_mod_path = path.join(
|
|
304
|
+
base_path, package_name.replace(".", path.sep)
|
|
305
|
+
)
|
|
306
|
+
self.handle_directory(
|
|
307
|
+
module_name=package_name,
|
|
308
|
+
full_mod_path=full_mod_path,
|
|
309
|
+
)
|
|
310
|
+
self.jac_machine.load_module(module_name, module)
|
|
311
|
+
return module
|
|
312
|
+
|
|
313
|
+
def run_import(
|
|
314
|
+
self, spec: ImportPathSpec, reload: Optional[bool] = False
|
|
315
|
+
) -> ImportReturn:
|
|
316
|
+
"""Run the import process for Jac modules."""
|
|
317
|
+
unique_loaded_items: list[types.ModuleType] = []
|
|
318
|
+
module = None
|
|
319
|
+
if os.path.isfile(spec.full_target + ".jac"):
|
|
320
|
+
module_name = self.get_sys_mod_name(spec.full_target + ".jac")
|
|
321
|
+
module_name = spec.override_name if spec.override_name else module_name
|
|
322
|
+
else:
|
|
323
|
+
module_name = self.get_sys_mod_name(spec.full_target)
|
|
324
|
+
|
|
325
|
+
module = self.jac_machine.loaded_modules.get(module_name)
|
|
326
|
+
|
|
327
|
+
if not module or module.__name__ == "__main__" or reload:
|
|
328
|
+
if os.path.isdir(spec.full_target):
|
|
329
|
+
module = self.handle_directory(spec.module_name, spec.full_target)
|
|
330
|
+
else:
|
|
331
|
+
spec.full_target += ".jac" if spec.language == "jac" else ".py"
|
|
332
|
+
module = self.create_jac_py_module(
|
|
333
|
+
module_name,
|
|
334
|
+
spec.package_path,
|
|
335
|
+
spec.full_target,
|
|
336
|
+
)
|
|
337
|
+
codeobj = self.jac_machine.get_bytecode(
|
|
338
|
+
module_name,
|
|
339
|
+
spec.full_target,
|
|
340
|
+
caller_dir=spec.caller_dir,
|
|
341
|
+
cachable=spec.cachable,
|
|
342
|
+
)
|
|
343
|
+
try:
|
|
344
|
+
if not codeobj:
|
|
345
|
+
raise ImportError(f"No bytecode found for {spec.full_target}")
|
|
346
|
+
with sys_path_context(spec.caller_dir):
|
|
347
|
+
exec(codeobj, module.__dict__)
|
|
348
|
+
except Exception as e:
|
|
349
|
+
logger.error(dump_traceback(e))
|
|
350
|
+
raise e
|
|
351
|
+
import_return = ImportReturn(module, unique_loaded_items, self)
|
|
352
|
+
if spec.items:
|
|
353
|
+
import_return.process_items(
|
|
354
|
+
module=module,
|
|
355
|
+
items=spec.items,
|
|
356
|
+
caller_dir=spec.caller_dir,
|
|
357
|
+
cachable=spec.cachable,
|
|
358
|
+
lang=spec.language,
|
|
359
|
+
)
|
|
360
|
+
self.result = import_return
|
|
361
|
+
return self.result
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Jac Machine module."""
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import marshal
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import types
|
|
8
|
+
from contextvars import ContextVar
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from jaclang.compiler.absyntree import Module
|
|
12
|
+
from jaclang.compiler.compile import compile_jac
|
|
13
|
+
from jaclang.compiler.constant import Constants as Con
|
|
14
|
+
from jaclang.runtimelib.architype import EdgeArchitype, NodeArchitype, WalkerArchitype
|
|
15
|
+
from jaclang.utils.log import logging
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
JACMACHINE_CONTEXT = ContextVar["JacMachine | None"]("JacMachine")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class JacMachine:
|
|
25
|
+
"""JacMachine to handle the VM-related functionalities and loaded programs."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, base_path: str = "") -> None:
|
|
28
|
+
"""Initialize the JacMachine object."""
|
|
29
|
+
self.loaded_modules: dict[str, types.ModuleType] = {}
|
|
30
|
+
if not base_path:
|
|
31
|
+
base_path = os.getcwd()
|
|
32
|
+
self.base_path = base_path
|
|
33
|
+
self.base_path_dir = (
|
|
34
|
+
os.path.dirname(base_path)
|
|
35
|
+
if not os.path.isdir(base_path)
|
|
36
|
+
else os.path.abspath(base_path)
|
|
37
|
+
)
|
|
38
|
+
self.jac_program: Optional[JacProgram] = None
|
|
39
|
+
|
|
40
|
+
JACMACHINE_CONTEXT.set(self)
|
|
41
|
+
|
|
42
|
+
def attach_program(self, jac_program: "JacProgram") -> None:
|
|
43
|
+
"""Attach a JacProgram to the machine."""
|
|
44
|
+
self.jac_program = jac_program
|
|
45
|
+
|
|
46
|
+
def get_mod_bundle(self) -> Optional[Module]:
|
|
47
|
+
"""Retrieve the mod_bundle from the attached JacProgram."""
|
|
48
|
+
if self.jac_program:
|
|
49
|
+
return self.jac_program.mod_bundle
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
def get_bytecode(
|
|
53
|
+
self,
|
|
54
|
+
module_name: str,
|
|
55
|
+
full_target: str,
|
|
56
|
+
caller_dir: str,
|
|
57
|
+
cachable: bool = True,
|
|
58
|
+
) -> Optional[types.CodeType]:
|
|
59
|
+
"""Retrieve bytecode from the attached JacProgram."""
|
|
60
|
+
if self.jac_program:
|
|
61
|
+
return self.jac_program.get_bytecode(
|
|
62
|
+
module_name, full_target, caller_dir, cachable
|
|
63
|
+
)
|
|
64
|
+
return None
|
|
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
|
+
|
|
120
|
+
|
|
121
|
+
class JacProgram:
|
|
122
|
+
"""Class to hold the mod_bundle and bytecode for Jac modules."""
|
|
123
|
+
|
|
124
|
+
def __init__(
|
|
125
|
+
self, mod_bundle: Optional[Module], bytecode: Optional[dict[str, bytes]]
|
|
126
|
+
) -> None:
|
|
127
|
+
"""Initialize the JacProgram object."""
|
|
128
|
+
self.mod_bundle = mod_bundle
|
|
129
|
+
self.bytecode = bytecode or {}
|
|
130
|
+
|
|
131
|
+
def get_bytecode(
|
|
132
|
+
self,
|
|
133
|
+
module_name: str,
|
|
134
|
+
full_target: str,
|
|
135
|
+
caller_dir: str,
|
|
136
|
+
cachable: bool = True,
|
|
137
|
+
) -> Optional[types.CodeType]:
|
|
138
|
+
"""Get the bytecode for a specific module."""
|
|
139
|
+
if self.mod_bundle and isinstance(self.mod_bundle, Module):
|
|
140
|
+
codeobj = self.mod_bundle.mod_deps[full_target].gen.py_bytecode
|
|
141
|
+
return marshal.loads(codeobj) if isinstance(codeobj, bytes) else None
|
|
142
|
+
gen_dir = os.path.join(caller_dir, Con.JAC_GEN_DIR)
|
|
143
|
+
pyc_file_path = os.path.join(gen_dir, module_name + ".jbc")
|
|
144
|
+
if cachable and os.path.exists(pyc_file_path):
|
|
145
|
+
with open(pyc_file_path, "rb") as f:
|
|
146
|
+
return marshal.load(f)
|
|
147
|
+
|
|
148
|
+
result = compile_jac(full_target, cache_result=cachable)
|
|
149
|
+
if result.errors_had or not result.ir.gen.py_bytecode:
|
|
150
|
+
logger.error(
|
|
151
|
+
f"While importing {len(result.errors_had)} errors"
|
|
152
|
+
f" found in {full_target}"
|
|
153
|
+
)
|
|
154
|
+
return None
|
|
155
|
+
if result.ir.gen.py_bytecode is not None:
|
|
156
|
+
return marshal.loads(result.ir.gen.py_bytecode)
|
|
157
|
+
else:
|
|
158
|
+
return None
|