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.

Files changed (131) hide show
  1. jaclang/cli/cli.py +147 -77
  2. jaclang/cli/cmdreg.py +9 -12
  3. jaclang/compiler/__init__.py +19 -53
  4. jaclang/compiler/absyntree.py +94 -16
  5. jaclang/compiler/constant.py +8 -8
  6. jaclang/compiler/jac.lark +4 -3
  7. jaclang/compiler/parser.py +41 -25
  8. jaclang/compiler/passes/ir_pass.py +4 -13
  9. jaclang/compiler/passes/main/__init__.py +1 -1
  10. jaclang/compiler/passes/main/access_modifier_pass.py +96 -147
  11. jaclang/compiler/passes/main/fuse_typeinfo_pass.py +155 -54
  12. jaclang/compiler/passes/main/import_pass.py +99 -75
  13. jaclang/compiler/passes/main/py_collect_dep_pass.py +70 -0
  14. jaclang/compiler/passes/main/pyast_gen_pass.py +328 -565
  15. jaclang/compiler/passes/main/pyast_load_pass.py +33 -6
  16. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +7 -0
  17. jaclang/compiler/passes/main/registry_pass.py +37 -3
  18. jaclang/compiler/passes/main/schedules.py +9 -2
  19. jaclang/compiler/passes/main/sym_tab_build_pass.py +10 -6
  20. jaclang/compiler/passes/main/tests/__init__.py +1 -1
  21. jaclang/compiler/passes/main/tests/fixtures/autoimpl.empty.impl.jac +0 -0
  22. jaclang/compiler/passes/main/tests/fixtures/autoimpl.jac +1 -1
  23. jaclang/compiler/passes/main/tests/fixtures/py_imp_test.jac +29 -0
  24. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/__init__.py +3 -0
  25. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/color.py +3 -0
  26. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/constants.py +5 -0
  27. jaclang/compiler/passes/main/tests/fixtures/pygame_mock/display.py +2 -0
  28. jaclang/compiler/passes/main/tests/test_import_pass.py +72 -13
  29. jaclang/compiler/passes/main/type_check_pass.py +22 -5
  30. jaclang/compiler/passes/tool/jac_formatter_pass.py +135 -89
  31. jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +37 -41
  32. jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +37 -42
  33. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/access_mod_check.jac +27 -0
  34. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/architype_test.jac +13 -0
  35. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/comment_alignment.jac +11 -0
  36. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/comments.jac +13 -0
  37. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/decorator_stack.jac +37 -0
  38. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/esc_keywords.jac +5 -0
  39. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/long_names.jac +19 -0
  40. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/triple_quoted_string.jac +6 -0
  41. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +11 -0
  42. jaclang/compiler/passes/tool/tests/test_unparse_validate.py +33 -39
  43. jaclang/compiler/passes/transform.py +4 -0
  44. jaclang/compiler/passes/utils/mypy_ast_build.py +45 -0
  45. jaclang/compiler/semtable.py +31 -7
  46. jaclang/compiler/symtable.py +16 -11
  47. jaclang/compiler/tests/test_importer.py +25 -10
  48. jaclang/langserve/engine.py +104 -118
  49. jaclang/langserve/sem_manager.py +379 -0
  50. jaclang/langserve/server.py +24 -11
  51. jaclang/langserve/tests/fixtures/base_module_structure.jac +27 -6
  52. jaclang/langserve/tests/fixtures/circle.jac +3 -3
  53. jaclang/langserve/tests/fixtures/circle_err.jac +3 -3
  54. jaclang/langserve/tests/fixtures/circle_pure.test.jac +3 -3
  55. jaclang/langserve/tests/fixtures/import_include_statements.jac +1 -1
  56. jaclang/langserve/tests/fixtures/rename.jac +30 -0
  57. jaclang/langserve/tests/test_sem_tokens.py +277 -0
  58. jaclang/langserve/tests/test_server.py +287 -17
  59. jaclang/langserve/utils.py +184 -98
  60. jaclang/plugin/builtin.py +1 -1
  61. jaclang/plugin/default.py +288 -92
  62. jaclang/plugin/feature.py +65 -27
  63. jaclang/plugin/spec.py +62 -23
  64. jaclang/plugin/tests/fixtures/other_root_access.jac +82 -0
  65. jaclang/plugin/tests/test_jaseci.py +414 -42
  66. jaclang/runtimelib/architype.py +650 -0
  67. jaclang/{core → runtimelib}/constructs.py +5 -8
  68. jaclang/{core → runtimelib}/context.py +86 -59
  69. jaclang/runtimelib/importer.py +361 -0
  70. jaclang/runtimelib/machine.py +158 -0
  71. jaclang/runtimelib/memory.py +158 -0
  72. jaclang/{core → runtimelib}/utils.py +30 -15
  73. jaclang/settings.py +5 -4
  74. jaclang/tests/fixtures/abc.jac +3 -3
  75. jaclang/tests/fixtures/access_checker.jac +12 -17
  76. jaclang/tests/fixtures/access_modifier.jac +88 -33
  77. jaclang/tests/fixtures/baddy.jac +3 -0
  78. jaclang/tests/fixtures/baddy.test.jac +3 -0
  79. jaclang/tests/fixtures/bar.jac +34 -0
  80. jaclang/tests/fixtures/byllmissue.jac +1 -5
  81. jaclang/tests/fixtures/chandra_bugs2.jac +11 -10
  82. jaclang/tests/fixtures/cls_method.jac +41 -0
  83. jaclang/tests/fixtures/dblhello.jac +6 -0
  84. jaclang/tests/fixtures/deep/one_lev.jac +3 -3
  85. jaclang/tests/fixtures/deep/one_lev_dup.jac +2 -3
  86. jaclang/tests/fixtures/deep_import_mods.jac +13 -0
  87. jaclang/tests/fixtures/edge_node_walk.jac +1 -1
  88. jaclang/tests/fixtures/edge_ops.jac +1 -1
  89. jaclang/tests/fixtures/edges_walk.jac +1 -1
  90. jaclang/tests/fixtures/err.impl.jac +3 -0
  91. jaclang/tests/fixtures/err.jac +4 -2
  92. jaclang/tests/fixtures/err_runtime.jac +15 -0
  93. jaclang/tests/fixtures/foo.jac +43 -0
  94. jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
  95. jaclang/tests/fixtures/hello.jac +4 -0
  96. jaclang/tests/fixtures/impl_grab.impl.jac +2 -1
  97. jaclang/tests/fixtures/impl_grab.jac +4 -1
  98. jaclang/tests/fixtures/import.jac +9 -0
  99. jaclang/tests/fixtures/index_slice.jac +30 -0
  100. jaclang/tests/fixtures/jp_importer_auto.jac +14 -0
  101. jaclang/tests/fixtures/maxfail_run_test.jac +4 -4
  102. jaclang/tests/fixtures/needs_import.jac +2 -2
  103. jaclang/tests/fixtures/pyfunc_1.py +1 -1
  104. jaclang/tests/fixtures/pyfunc_2.py +5 -2
  105. jaclang/tests/fixtures/pygame_mock/__init__.py +3 -0
  106. jaclang/tests/fixtures/pygame_mock/color.py +3 -0
  107. jaclang/tests/fixtures/pygame_mock/constants.py +5 -0
  108. jaclang/tests/fixtures/pygame_mock/display.py +2 -0
  109. jaclang/tests/fixtures/pygame_mock/inner/__init__.py +0 -0
  110. jaclang/tests/fixtures/pygame_mock/inner/iner_mod.py +2 -0
  111. jaclang/tests/fixtures/registry.jac +9 -0
  112. jaclang/tests/fixtures/run_test.jac +4 -4
  113. jaclang/tests/fixtures/semstr.jac +1 -4
  114. jaclang/tests/fixtures/simple_archs.jac +1 -1
  115. jaclang/tests/test_cli.py +109 -3
  116. jaclang/tests/test_language.py +170 -68
  117. jaclang/tests/test_reference.py +2 -3
  118. jaclang/utils/helpers.py +45 -21
  119. jaclang/utils/test.py +9 -0
  120. jaclang/utils/treeprinter.py +30 -7
  121. {jaclang-0.7.14.dist-info → jaclang-0.7.17.dist-info}/METADATA +3 -2
  122. {jaclang-0.7.14.dist-info → jaclang-0.7.17.dist-info}/RECORD +126 -90
  123. jaclang/core/architype.py +0 -502
  124. jaclang/core/importer.py +0 -344
  125. jaclang/core/memory.py +0 -99
  126. jaclang/tests/fixtures/aott_raise.jac +0 -25
  127. jaclang/tests/fixtures/package_import.jac +0 -6
  128. /jaclang/{core → runtimelib}/__init__.py +0 -0
  129. /jaclang/{core → runtimelib}/test.py +0 -0
  130. {jaclang-0.7.14.dist-info → jaclang-0.7.17.dist-info}/WHEEL +0 -0
  131. {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 Architype, Root
11
- from .memory import Memory, ShelveStorage
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
- """Default Execution Context implementation."""
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
- mem: Optional[Memory]
18
- root: Optional[Root]
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
- def __init__(self) -> None:
21
- """Create execution context."""
22
- super().__init__()
23
- self.mem = ShelveStorage()
24
- self.root = None
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
- def init_memory(self, session: str = "") -> None:
27
- """Initialize memory."""
28
- if session:
29
- self.mem = ShelveStorage(session)
30
- else:
31
- self.mem = Memory()
32
-
33
- def get_root(self) -> Root:
34
- """Get the root object."""
35
- if self.mem is None:
36
- raise ValueError("Memory not initialized")
37
-
38
- if not self.root:
39
- root = self.mem.get_obj(UUID(int=0))
40
- if root is None:
41
- self.root = Root()
42
- self.mem.save_obj(self.root, persistent=self.root._jac_.persistent)
43
- elif not isinstance(root, Root):
44
- raise ValueError(f"Invalid root object: {root}")
45
- else:
46
- self.root = root
47
- return self.root
48
-
49
- def get_obj(self, obj_id: UUID) -> Architype | None:
50
- """Get object from memory."""
51
- if self.mem is None:
52
- raise ValueError("Memory not initialized")
53
-
54
- return self.mem.get_obj(obj_id)
55
-
56
- def save_obj(self, item: Architype, persistent: bool) -> None:
57
- """Save object to memory."""
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