Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/Jaseci-Labs/jaseci
Browse files Browse the repository at this point in the history
  • Loading branch information
marsninja committed Sep 11, 2024
2 parents dbde439 + 3a3d989 commit 48b6315
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 55 deletions.
15 changes: 12 additions & 3 deletions jac/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,22 @@ repos:
hooks:
- id: flake8
args: ["--config=jac/.flake8"]
additional_dependencies: [pep8-naming, flake8_import_order, flake8_docstrings, flake8_comprehensions, flake8_bugbear, flake8_annotations, flake8_simplify]
exclude: "examples|vendor|langserve/tests|pygame_mock"
additional_dependencies:
[
pep8-naming,
flake8_import_order,
flake8_docstrings,
flake8_comprehensions,
flake8_bugbear,
flake8_annotations,
flake8_simplify,
]
exclude: "examples|vendor|langserve/tests|pygame_mock|generated|__jac_gen__"
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.1
hooks:
- id: mypy
exclude: 'venv|__jac_gen__|tests|stubs|support|vendor|examples/reference|setup.py|generated'
exclude: "venv|__jac_gen__|tests|stubs|support|vendor|examples/reference|setup.py|generated"
args:
- --follow-imports=silent
- --ignore-missing-imports
11 changes: 10 additions & 1 deletion jac/jaclang/compiler/passes/tool/jac_formatter_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,16 @@ def exit_sub_node_list(self, node: ast.SubNodeList) -> None:
and isinstance(prev_token.kid[-1], ast.SubNodeList)
and isinstance(prev_token.kid[-1].kid[-1], ast.CommentToken)
):
self.emit(node, "")
if (
prev_token
and stmt.loc.first_line - prev_token.kid[-1].kid[-1].line_no
> 1
):
self.indent_level -= 1
self.emit_ln(node, "")
self.indent_level += 1
else:
self.emit(node, "")
else:
self.indent_level -= 1
self.emit_ln(node, "")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import:py math;
glob RAD = 5;
glob DIA = 10;

# this comment is for walker
walker decorator_walk {
can hash(func: Any) {
can inner(a: Any) {
Expand Down Expand Up @@ -36,12 +37,16 @@ walker decorator_walk {
}

# Entry point for the walker

can start with entry {
# Apply decorators to greeter
decorated_greeter = hash(exclaim(tilde(greeter)));

# Call the decorated greeter function
decorated_greeter("World");

# this is another comment

}
}

Expand Down
1 change: 1 addition & 0 deletions jac/jaclang/runtimelib/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ def run_import(
spec.full_target,
caller_dir=spec.caller_dir,
cachable=spec.cachable,
reload=reload if reload else False,
)
try:
if not codeobj:
Expand Down
48 changes: 45 additions & 3 deletions jac/jaclang/runtimelib/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import sys
import types
from contextvars import ContextVar
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 @@ -55,11 +55,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 @@ -105,6 +106,46 @@ def list_edges(self, module_name: str) -> list[str]:
return nodes
return []

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:
old_module = self.loaded_modules[module_name]
importer = JacImporter(self)
spec = ImportPathSpec(
target=module_name,
base_path=self.base_path,
absorb=False,
cachable=True,
mdl_alias=None,
override_name=None,
lng="jac",
items=items,
)
import_result = importer.run_import(spec, reload=True)
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 ()

@staticmethod
def get(base_path: str = "") -> "JacMachine":
"""Get current jac machine."""
Expand Down Expand Up @@ -134,14 +175,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
2 changes: 1 addition & 1 deletion jac/jaclang/tests/fixtures/bar.jac
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ walker bar_walk {
disengage;
}
}
}
}
1 change: 0 additions & 1 deletion jac/jaclang/tests/fixtures/foo.jac
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import:py from jaclang.plugin.feature, JacFeature as Jac;
import:py from jaclang.runtimelib.machine, JacMachine;
import:jac from bar, bar_walk;
# Test runner to initialize the walker
Expand Down
19 changes: 19 additions & 0 deletions jac/jaclang/tests/fixtures/walker_update.jac
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import:jac from bar { bar_walk }
import:py from jaclang.runtimelib.machine { JacMachine }
import:py os;

can update_bar_walker {
"Updating bar.jac with new behavior." |> print;
(bar_walk_new, ) = JacMachine.get().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 entry {
update_bar_walker();
}
58 changes: 58 additions & 0 deletions jac/jaclang/tests/test_language.py
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,64 @@ 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."""
session = self.fixture_abs_path("bar_walk.session")
bar_file_path = self.fixture_abs_path("bar.jac")
update_file_path = self.fixture_abs_path("walker_update.jac")
captured_output = io.StringIO()
sys.stdout = captured_output
cli.enter(
filename=bar_file_path,
session=session,
entrypoint="bar_walk",
args=[],
)
sys.stdout = sys.__stdout__
stdout_value = captured_output.getvalue()
expected_output = "Created 5 items."
self.assertIn(expected_output, stdout_value.split("\n"))
# Define the new behavior to be added
new_behavior = """
# New behavior added during runtime
can end with `root exit {
"bar_walk has been updated with new behavior!" |> print;
disengage;
}
}
"""

# Backup the original file content
with open(bar_file_path, "r") as bar_file:
original_content = bar_file.read()

# Update the bar.jac file with new behavior
with open(bar_file_path, "r+") as bar_file:
content = bar_file.read()
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()

captured_output = io.StringIO()
sys.stdout = captured_output

try:
cli.run(
filename=update_file_path,
)
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"))
finally:
# Restore the original content of bar.jac
with open(bar_file_path, "w") as bar_file:

bar_file.write(original_content)

def test_object_ref_interface(self) -> None:
"""Test class method output."""
captured_output = io.StringIO()
Expand Down
53 changes: 53 additions & 0 deletions jac/support/jac-lang.org/docs/learn/internals.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Jac Internals

This document has various notes and details related to the internal design and implementation of Jac and the Jaseci stack in general.

## Modules at Runtime

### JacMachine
`JacMachine` is responsible for managing the virtual machine (VM) functionalities for Jac. It handles the loading and execution of Jac modules, keeps track of them at runtime, and interacts with the Jac compiler to manage bytecode and program execution.

**Key Responsibilities:**
1. **Module Management**:
- Loads Jac modules into an internal dictionary and synchronizes them with `sys.modules` for Python integration.
2. **Program Management**:
- Attaches a `JacProgram` that handles compiled bytecode and manages module dependencies.
3. **Context Management**:
- Utilizes a `ContextVar` to manage the current instance of the machine, allowing multiple independent Jac environments to run simultaneously.
4. **Dynamic Updates**:
- Enables reloading and updating specific components such as walkers without the need to reload the entire module.

**Key Methods:**
- `load_module()`: Adds modules to the internal state and `sys.modules`.
- `get_bytecode()`: Fetches the compiled bytecode for a module or recompiles Jac code if necessary.
- `list_modules()`: Lists all currently loaded modules in the machine.
- `update_walker()`: Reimports and updates specific items, such as walkers, within a module.
- `get()`: Retrieves or creates a `JacMachine` instance in the current context.

### JacProgram

`JacProgram` works in tandem with `JacMachine` by storing compiled Jac modules and their bytecode. It provides methods to retrieve or recompile Jac code as needed, ensuring efficient management of compiled modules.

### Internal Interfaces for Architypes

#### Listing Architypes
`JacMachine` offers internal interfaces for listing various types of architypes, such as walkers, nodes, and edges, within a module.
#### Replacing Walkers
One of the most powerful features of `JacMachine` is the ability to dynamically **replace walkers** at runtime. Walkers, being specialized functions that traverse and interact with nodes and edges, can be updated or modified while the program is running, without needing to reload the entire module.

**Key Concepts:**
1. **Dynamic Updates**: Through the `update_walker()` method, individual walkers can be redefined and updated during runtime. This allows developers to modify the behavior of specific walkers without disrupting the rest of the program.

2. **Selective Reimport**: Instead of reimporting the entire module, `JacMachine` focuses on reloading only the specified walker(s). This ensures minimal disruption and efficient updates. The old walker’s functionality is replaced with the new implementation.

3. **Smooth Transition**: `JacMachine` ensures that replacing a walker is seamless, meaning that running processes are not interrupted. This allows for continuous operation in long-running programs or live environments.

**Example Workflow for Replacing Walkers:**
1. **Identify the Walker**: The specific walker that needs updating is identified by its name within the module.
2. **Reimport the Module**: The module is selectively reimported, targeting the specific walker(s) to ensure only the necessary parts of the module are affected.
3. **Replace the Walker**: The old walker is swapped out for the new version, with state preserved where necessary to maintain continuity.

This capability makes `JacMachine` highly flexible, particularly useful in scenarios such as:
- **Debugging**: Fix and update walkers without halting execution.
- **Live Code Updates**: Modify behavior in real-time during active program execution.
- **Reactive Programming**: Adapt workflows dynamically based on real-time conditions.
Loading

0 comments on commit 48b6315

Please sign in to comment.