Skip to content
This repository has been archived by the owner on Sep 12, 2024. It is now read-only.

Commit

Permalink
Merge pull request #182 from Jaseci-Labs/import_as_feature
Browse files Browse the repository at this point in the history
yet to be modified
  • Loading branch information
marsninja authored Jan 31, 2024
2 parents 5f6ab7a + 1e0b094 commit 73bc24a
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 105 deletions.
3 changes: 2 additions & 1 deletion jaclang/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "vendor"))

from jaclang.compiler.importer import jac_import # noqa: E402
from jaclang.plugin.feature import JacFeature # noqa: E402
from jaclang.vendor import lark # noqa: E402
from jaclang.vendor import mypy # noqa: E402
from jaclang.vendor import pluggy # noqa: E402

jac_import = JacFeature.jac_import

__all__ = [
"jac_import",
"lark",
Expand Down
4 changes: 2 additions & 2 deletions jaclang/compiler/tests/test_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ def setUp(self) -> None:

def test_import_basic_python(self) -> None:
"""Test basic self loading."""
h = jac_import("fixtures.hello_world")
h = jac_import("fixtures.hello_world", base_path=__file__)
self.assertEqual(h.hello(), "Hello World!") # type: ignore

def test_modules_correct(self) -> None:
"""Test basic self loading."""
jac_import("fixtures.hello_world")
jac_import("fixtures.hello_world", base_path=__file__)
self.assertIn("module 'hello_world'", str(sys.modules))
self.assertIn("/tests/fixtures/hello_world.jac", str(sys.modules))
165 changes: 80 additions & 85 deletions jaclang/compiler/importer.py → jaclang/core/importer.py
Original file line number Diff line number Diff line change
@@ -1,85 +1,80 @@
"""Special Imports for Jac Code."""
import inspect
import marshal
import sys
import types
from os import path
from typing import Optional

from jaclang.compiler.constant import Constants as Con
from jaclang.compiler.transpiler import transpile_jac
from jaclang.utils.log import logging


def jac_import(
target: str,
base_path: Optional[str] = None,
cachable: bool = True,
override_name: Optional[str] = None,
) -> Optional[types.ModuleType]:
"""Core Import Process."""
dir_path, file_name = path.split(path.join(*(target.split("."))) + ".jac")

module_name = path.splitext(file_name)[0]
package_path = dir_path.replace(path.sep, ".")

if package_path and f"{package_path}.{module_name}" in sys.modules:
return sys.modules[f"{package_path}.{module_name}"]
elif not package_path and module_name in sys.modules:
return sys.modules[module_name]

if base_path:
caller_dir = path.dirname(base_path) if not path.isdir(base_path) else base_path
else:
frame = inspect.stack()[1]
caller_dir = path.dirname(path.abspath(frame[0].f_code.co_filename))
caller_dir = path.dirname(caller_dir) if target.startswith("..") else caller_dir
caller_dir = path.join(caller_dir, dir_path)

gen_dir = path.join(caller_dir, Con.JAC_GEN_DIR)
full_target = path.normpath(path.join(caller_dir, file_name))

py_file_path = path.join(gen_dir, module_name + ".py")
pyc_file_path = path.join(gen_dir, module_name + ".jbc")
if (
cachable
and path.exists(py_file_path)
and path.getmtime(py_file_path) > path.getmtime(full_target)
):
with open(pyc_file_path, "rb") as f:
codeobj = marshal.load(f)
else:
if error := transpile_jac(full_target):
if error:
for e in error:
print(e)
logging.error(e)
return None
with open(pyc_file_path, "rb") as f:
codeobj = marshal.load(f)

module_name = override_name if override_name else module_name
module = types.ModuleType(module_name)
module.__file__ = full_target
module.__name__ = module_name

if package_path:
parts = package_path.split(".")
for i in range(len(parts)):
package_name = ".".join(parts[: i + 1])
if package_name not in sys.modules:
sys.modules[package_name] = types.ModuleType(package_name)

setattr(sys.modules[package_path], module_name, module)
sys.modules[f"{package_path}.{module_name}"] = module
sys.modules[module_name] = module

path_added = False
if caller_dir not in sys.path:
sys.path.append(caller_dir)
path_added = True
exec(codeobj, module.__dict__)
if path_added:
sys.path.remove(caller_dir)

return module
"""Special Imports for Jac Code."""
import marshal
import sys
import types
from os import path
from typing import Optional

from jaclang.compiler.constant import Constants as Con
from jaclang.compiler.transpiler import transpile_jac
from jaclang.utils.log import logging


def jac_importer(
target: str,
base_path: str,
cachable: bool = True,
override_name: Optional[str] = None,
) -> Optional[types.ModuleType]:
"""Core Import Process."""
dir_path, file_name = path.split(path.join(*(target.split("."))) + ".jac")

module_name = path.splitext(file_name)[0]
package_path = dir_path.replace(path.sep, ".")

if package_path and f"{package_path}.{module_name}" in sys.modules:
return sys.modules[f"{package_path}.{module_name}"]
elif not package_path and module_name in sys.modules:
return sys.modules[module_name]

caller_dir = path.dirname(base_path) if not path.isdir(base_path) else base_path
caller_dir = path.dirname(caller_dir) if target.startswith("..") else caller_dir
caller_dir = path.join(caller_dir, dir_path)

gen_dir = path.join(caller_dir, Con.JAC_GEN_DIR)
full_target = path.normpath(path.join(caller_dir, file_name))

py_file_path = path.join(gen_dir, module_name + ".py")
pyc_file_path = path.join(gen_dir, module_name + ".jbc")
if (
cachable
and path.exists(py_file_path)
and path.getmtime(py_file_path) > path.getmtime(full_target)
):
with open(pyc_file_path, "rb") as f:
codeobj = marshal.load(f)
else:
if error := transpile_jac(full_target):
if error:
for e in error:
print(e)
logging.error(e)
return None
with open(pyc_file_path, "rb") as f:
codeobj = marshal.load(f)

module_name = override_name if override_name else module_name
module = types.ModuleType(module_name)
module.__file__ = full_target
module.__name__ = module_name

if package_path:
parts = package_path.split(".")
for i in range(len(parts)):
package_name = ".".join(parts[: i + 1])
if package_name not in sys.modules:
sys.modules[package_name] = types.ModuleType(package_name)

setattr(sys.modules[package_path], module_name, module)
sys.modules[f"{package_path}.{module_name}"] = module
sys.modules[module_name] = module

path_added = False
if caller_dir not in sys.path:
sys.path.append(caller_dir)
path_added = True
exec(codeobj, module.__dict__)
if path_added:
sys.path.remove(caller_dir)

return module
23 changes: 22 additions & 1 deletion jaclang/plugin/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
from __future__ import annotations

import os
import types
from dataclasses import dataclass, field
from functools import wraps
from typing import Any, Callable, Optional, Type

from jaclang import jac_import
from jaclang.plugin.spec import (
ArchBound,
Architype,
Expand All @@ -19,12 +19,14 @@
NodeArchitype,
T,
WalkerArchitype,
jac_importer,
root,
)


import pluggy


hookimpl = pluggy.HookimplMarker("jac")


Expand Down Expand Up @@ -147,6 +149,23 @@ def new_init(self: ArchBound, *args: object, **kwargs: object) -> None:

return decorator

@staticmethod
@hookimpl
def jac_import(
target: str,
base_path: str,
cachable: bool,
override_name: Optional[str],
) -> Optional[types.ModuleType]:
"""Core Import Process."""
result = jac_importer(
target=target,
base_path=base_path,
cachable=cachable,
override_name=override_name,
)
return result

@staticmethod
@hookimpl
def create_test(test_fun: Callable) -> Callable:
Expand All @@ -167,6 +186,8 @@ def run_test(filename: str) -> None:
:param filename: The path to the .jac file.
"""
from jaclang import jac_import

if filename.endswith(".jac"):
base, mod_name = os.path.split(filename)
base = base if base else "./"
Expand Down
16 changes: 16 additions & 0 deletions jaclang/plugin/feature.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Jac Language Features."""
from __future__ import annotations

import types
from typing import Any, Callable, Optional, Type

from jaclang.plugin.default import JacFeatureDefaults
Expand Down Expand Up @@ -59,6 +60,21 @@ def make_walker(
"""Create a walker architype."""
return JacFeature.pm.hook.make_walker(on_entry=on_entry, on_exit=on_exit)

@staticmethod
def jac_import(
target: str,
base_path: str,
cachable: bool = True,
override_name: Optional[str] = None,
) -> Optional[types.ModuleType]:
"""Core Import Process."""
return JacFeature.pm.hook.jac_import(
target=target,
base_path=base_path,
cachable=cachable,
override_name=override_name,
)

@staticmethod
def create_test(test_fun: Callable) -> Callable:
"""Create a test."""
Expand Down
16 changes: 15 additions & 1 deletion jaclang/plugin/spec.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Jac Language Features."""
from __future__ import annotations


import types
from typing import Any, Callable, Optional, Type, TypeVar


from jaclang.core.construct import (
Architype,
DSFunc,
Expand All @@ -20,6 +21,7 @@
WalkerArchitype,
root,
)
from jaclang.core.importer import jac_importer

__all__ = [
"EdgeAnchor",
Expand All @@ -36,6 +38,7 @@
"EdgeDir",
"root",
"Root",
"jac_importer",
]

import pluggy
Expand Down Expand Up @@ -82,6 +85,17 @@ def make_walker(
"""Create a walker architype."""
raise NotImplementedError

@staticmethod
@hookspec(firstresult=True)
def jac_import(
target: str,
base_path: str,
cachable: bool,
override_name: Optional[str],
) -> Optional[types.ModuleType]:
"""Core Import Process."""
raise NotImplementedError

@staticmethod
@hookspec(firstresult=True)
def create_test(test_fun: Callable) -> Callable:
Expand Down
8 changes: 8 additions & 0 deletions jaclang/plugin/tests/test_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ def get_methods(self, cls: Type) -> List[str]:

# Get the signature using inspect
signature = inspect.signature(value)
new_parameters = []
for (
_,
param,
) in signature.parameters.items(): # to strip defaults
new_param = param.replace(default=inspect.Parameter.empty)
new_parameters.append(new_param)
signature = signature.replace(parameters=new_parameters)
methods.append(f"{name}{signature}")
except ValueError:
# This can happen if the method is not a Python function (e.g., built-in function)
Expand Down
Loading

0 comments on commit 73bc24a

Please sign in to comment.