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

Commit

Permalink
Update: a working version
Browse files Browse the repository at this point in the history
  • Loading branch information
AshishMahendra committed Aug 29, 2024
1 parent ba31cee commit 69b970f
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 23 deletions.
6 changes: 4 additions & 2 deletions jaclang/runtimelib/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import os
import sys
import types
from importlib import util
from os import getcwd, path
from typing import Optional, Union

Expand Down Expand Up @@ -195,11 +196,11 @@ def run_import(self, spec: ImportPathSpec) -> ImportReturn:
if spec.target.startswith("."):
spec.target = spec.target.lstrip(".")
full_target = path.normpath(path.join(spec.caller_dir, spec.target))
imp_spec = importlib.util.spec_from_file_location(
imp_spec = util.spec_from_file_location(
spec.target, full_target + ".py"
)
if imp_spec and imp_spec.loader:
imported_module = importlib.util.module_from_spec(imp_spec)
imported_module = util.module_from_spec(imp_spec)
sys.modules[imp_spec.name] = imported_module
imp_spec.loader.exec_module(imported_module)
else:
Expand Down Expand Up @@ -339,6 +340,7 @@ def run_import(
spec.full_target,
caller_dir=spec.caller_dir,
cachable=spec.cachable,
reload=reload if reload else False,
)
try:

Expand Down
42 changes: 21 additions & 21 deletions jaclang/runtimelib/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import os
import sys
import types
from typing import Optional
from typing import Optional, Union

from jaclang.compiler.absyntree import Module
from jaclang.compiler.compile import compile_jac
Expand Down Expand Up @@ -49,11 +49,12 @@ def get_bytecode(
full_target: str,
caller_dir: str,
cachable: bool = True,
reload: bool = False,
) -> Optional[types.CodeType]:
"""Retrieve bytecode from the attached JacProgram."""
if self.jac_program:
return self.jac_program.get_bytecode(
module_name, full_target, caller_dir, cachable
module_name, full_target, caller_dir, cachable, reload=reload
)
return None

Expand Down Expand Up @@ -99,20 +100,15 @@ def list_edges(self, module_name: str) -> list[str]:
return nodes
return []

def update_module(
def update_walker(
self, module_name: str, items: Optional[dict[str, Union[str, Optional[str]]]]
) -> tuple[types.ModuleType, ...]:
"""Reimport the module."""
from .importer import JacImporter, ImportPathSpec

if module_name in self.loaded_modules:
try:
# Unload the existing module
del self.loaded_modules[module_name]
if module_name in sys.modules:
del sys.modules[module_name]

# Re-import the module
old_module = self.loaded_modules[module_name]
importer = JacImporter(self)
spec = ImportPathSpec(
target=module_name,
Expand All @@ -125,21 +121,24 @@ def update_module(
items=items,
)
import_result = importer.run_import(spec, reload=True)
# print(f"received items: {import_result.ret_items}")
# Load the updated module into JacMachine
self.load_module(module_name, import_result.ret_mod)

# print(f"Module {module_name} successfully updated.")
# print(f"imported items: {import_result.ret_items}")
return (
(import_result.ret_mod,)
if not items
else tuple(import_result.ret_items)
)
ret_items = []
if items:
for item_name in items:
if hasattr(old_module, item_name):
new_attr = getattr(import_result.ret_mod, item_name, None)
if new_attr:
ret_items.append(new_attr)
setattr(
old_module,
item_name,
new_attr,
)
return (old_module,) if not items else tuple(ret_items)
except Exception as e:
logger.error(f"Failed to update module {module_name}: {e}")
else:
logger.warning(f"Module {module_name} not found in loaded modules.")
return ()


class JacProgram:
Expand All @@ -158,14 +157,15 @@ def get_bytecode(
full_target: str,
caller_dir: str,
cachable: bool = True,
reload: bool = False,
) -> Optional[types.CodeType]:
"""Get the bytecode for a specific module."""
if self.mod_bundle and isinstance(self.mod_bundle, Module):
codeobj = self.mod_bundle.mod_deps[full_target].gen.py_bytecode
return marshal.loads(codeobj) if isinstance(codeobj, bytes) else None
gen_dir = os.path.join(caller_dir, Con.JAC_GEN_DIR)
pyc_file_path = os.path.join(gen_dir, module_name + ".jbc")
if cachable and os.path.exists(pyc_file_path):
if cachable and os.path.exists(pyc_file_path) and not reload:
with open(pyc_file_path, "rb") as f:
return marshal.load(f)

Expand Down
34 changes: 34 additions & 0 deletions jaclang/tests/fixtures/walker_reload/bar.jac
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Define a simple node type called `Item`
node Item {
has value: int = 0;
}

# Define an edge type called `Link`
edge Link {}

# Define the `bar` walker
walker bar_walk {
has count: int = 0;

# Start walking from the root node or an Item node
can start with `root | Item entry {
here ++> Item();
if self.count < 5 {
visit [-->];
} else {
"Created 5 items." |> print;
disengage;
}
}

# Walk over Item nodes and update their values
can walk with Item entry {
here.value = self.count;
f"Item value: {here.value}" |> print;
self.count += 1;
visit [-->] else {
"Finished walking over all items." |> print;
disengage;
}
}
}
56 changes: 56 additions & 0 deletions jaclang/tests/fixtures/walker_reload/foo.jac
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import:py from jaclang.plugin.feature {JacFeature as Jac}
import:jac from bar {bar_walk}
import:py from time {sleep}


can update_bar_walker {
new_behavior = '''
# New behavior added during runtime
can end with `root exit {
"bar_walk has been updated with new behavior!" |> print;
disengage;
}
}
''';
bar_file_path = '/home/ubuntu/jaclang/jaclang/tests/fixtures/walker_reload/bar.jac';
with open(bar_file_path, 'r') as bar_file{
original_content = bar_file.read();
}

with open(bar_file_path, 'r+') as bar_file { # Specify the correct path to bar.jac
content = bar_file.read();

# Replace the last occurrence of "}" with the new behavior
last_brace_index = content.rfind('}');
if last_brace_index != -1{
updated_content = content[:last_brace_index] + new_behavior;
bar_file.seek(0);
bar_file.write(updated_content);
bar_file.truncate();
}
}
"Updated bar.jac with new behavior." |> print;

(bar_walk_new,)=Jac.context().jac_machine.update_walker("bar", items={'bar_walk': None});
"Running bar_walk after update..." |> print;
root spawn bar_walk_new();
print(f"bar_walk: {bar_walk_new.__dict__}");
with open(bar_file_path, 'w') as bar_file{
bar_file.write(original_content);
}
}

# Initialize the walker
can initial_run {
root spawn bar_walk();
print(f"bar_walk: {bar_walk.__dict__}");

}

# Define the entry point to run the test
with entry {
initial_run();

# Update the walker
update_bar_walker();
}
15 changes: 15 additions & 0 deletions jaclang/tests/test_language.py
Original file line number Diff line number Diff line change
Expand Up @@ -977,3 +977,18 @@ def test_list_methods(self) -> None:
)
self.assertIn("Item value: 0", stdout_value)
self.assertIn("Created 5 items.", stdout_value)

def test_walker_dynamic_update(self) -> None:
"""Test dynamic update of a walker during runtime."""
Jac.get_root().__jac__.edges.clear()
Jac.context().init_memory(base_path=self.fixture_abs_path("./"))
captured_output = io.StringIO()
sys.stdout = captured_output

cli.run(
filename=self.fixture_abs_path("walker_reload/foo.jac"),
)
sys.stdout = sys.__stdout__
stdout_value = captured_output.getvalue()
expected_output = "bar_walk has been updated with new behavior!"
self.assertIn(expected_output, stdout_value.split("\n"))

0 comments on commit 69b970f

Please sign in to comment.