Jaclang Internals#
This document provides a comprehensive guide to the internal design and implementation of the Jaclang compiler and runtime stack. It's intended for developers who want to contribute to the Jaclang project or understand its architecture in depth.
Architecture Overview#
Jaclang is composed of two main components:
- Compiler: Transforms Jac source code into Python bytecode through a series of passes
- Runtime: Provides the execution environment for compiled Jac programs
The system follows a modular design that separates concerns between compilation and execution, allowing for flexibility and extensibility.
flowchart LR
A[Jac Source] --> B[Compiler]
B --> C[Bytecode]
C --> D[Execution]
D --> E[Runtime]
E --> F[Results]
style A fill:#2d3748,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style B fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style C fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style D fill:#38a169,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style E fill:#38a169,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style F fill:#2d3748,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
Compiler Components#
The compiler transforms Jac source code into Python bytecode through a series of passes. Each pass performs a specific transformation on the code.
Compilation Pipeline#
The compiler transforms Jac source code into Python bytecode through a series of passes:
flowchart TD
A[Jac Source Code] --> B[Parsing]
B --> C[Symbol Table Building]
C --> D[Import Resolution]
D --> E[Type Checking]
E --> F[Python AST Generation]
F --> G[Bytecode Generation]
G --> H[Python Bytecode]
style A fill:#2d3748,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style B fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style C fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style D fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style E fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style F fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style G fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style H fill:#2d3748,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
- Parsing: Converts Jac source code into an Abstract Syntax Tree (AST)
- Symbol Table Building: Creates symbol tables for scopes and resolves symbols
- Import Resolution: Processes import statements and loads external modules
- Type Checking: Performs type checking and inference
- Python AST Generation: Converts Jac AST to Python AST
- Bytecode Generation: Generates Python bytecode from the Python AST
Key Compiler Components#
JacProgram#
JacProgram
is the central component that orchestrates the compilation process. It maintains a collection of modules and manages the compilation pipeline.
class JacProgram:
"""JacProgram to handle the Jac program-related functionalities."""
def __init__(self, main_mod: Optional[uni.ProgramModule] = None) -> None:
"""Initialize the JacProgram object."""
self.mod: uni.ProgramModule = main_mod if main_mod else uni.ProgramModule()
self.last_imported: list[Module] = []
self.py_raise_map: dict[str, str] = {}
self.errors_had: list[Alert] = []
self.warnings_had: list[Alert] = []
Key methods:
- get_bytecode()
: Retrieves bytecode for a specific module
- compile()
: Compiles a Jac file to an AST
- compile_from_str()
: Compiles Jac code from a string
- run_pass_schedule()
: Executes the compilation passes on a module
- run_whole_program_schedule()
: Runs passes that require whole-program analysis
Unitree#
The unitree
module defines the AST node types used in the compilation process. The AST is a hierarchical representation of the program's structure.
Key classes:
- UniNode
: Base class for all AST nodes
- UniScopeNode
: Represents a scope with a symbol table
- Module
: Represents a Jac module
- Archetype
: Represents a Jac archetype (object, node, edge, walker)
- Ability
: Represents a Jac ability (method)
Compiler Components Relationship#
classDiagram
class JacProgram {
+ProgramModule mod
+list[Module] last_imported
+dict py_raise_map
+list errors_had
+list warnings_had
+get_bytecode()
+compile()
+compile_from_str()
+run_pass_schedule()
+run_whole_program_schedule()
}
class UniNode {
+UniNode parent
+list[UniNode] kid
+CodeGenTarget gen
+CodeLocInfo loc
}
class Module {
+str name
+Source source
+list body
+bool stub_only
+list impl_mod
+list test_mod
+list terminals
+PyInfo py_info
}
class Transform {
+ir_in
+ir_out
+list errors_had
+list warnings_had
}
JacProgram --> Module : manages
Module --|> UniNode : inherits
Transform --> UniNode : transforms
note for JacProgram "Orchestrates the compilation process"
note for UniNode "Base class for all AST nodes"
note for Module "Represents a Jac module"
note for Transform "Base class for compiler passes"
Compiler Passes#
Compiler passes are transformations applied to the AST. Each pass is responsible for a specific aspect of the compilation process.
flowchart LR
A[JacParser] --> B[SymTabBuildPass]
B --> C[JacImportPass]
C --> D[JacTypeCheckPass]
D --> E[PyastBuildPass]
E --> F[PyBytecodeGenPass]
style A fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style B fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style C fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style D fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style E fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style F fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
Main passes:
- JacParser
: Parses Jac source code into an AST
- SymTabBuildPass
: Builds symbol tables for scopes
- JacImportPass
: Resolves import statements
- JacTypeCheckPass
: Performs type checking
- PyastBuildPass
: Converts Jac AST to Python AST
- PyBytecodeGenPass
: Generates Python bytecode
Runtime Components#
The runtime system executes compiled Jac programs and provides the necessary infrastructure for Jac's features.
classDiagram
class JacMachine {
+load_module()
+get_bytecode()
+list_modules()
+update_walker()
+get()
}
class JacMachine {
+dict loaded_modules
+str base_path
+str base_path_dir
+JacProgram jac_program
+bool interp_mode
+ExecutionContext exec_ctx
}
class ExecutionContext {
+JacMachine mach
+Memory mem
+list reports
+Any custom
+NodeAnchor system_root
+NodeAnchor root
+NodeAnchor entry_node
+init_anchor()
+set_entry_node()
+close()
+get_root()
}
JacMachine --> JacMachine : uses
JacMachine --> ExecutionContext : contains
JacMachine --> JacProgram : contains
note for JacMachine "Core runtime component"
note for JacMachine "Maintains runtime state"
note for ExecutionContext "Execution environment"
JacMachine#
JacMachine
is the core runtime component that manages the execution environment for Jac programs. It handles module loading, execution context, and provides the runtime API for Jac features.
class JacMachine(
JacClassReferences,
JacAccessValidation,
JacNode,
JacEdge,
JacWalker,
JacBuiltin,
JacCmd,
JacBasics,
JacUtils,
):
"""Jac Feature."""
Key responsibilities: 1. Module Management: Loads and manages Jac modules 2. Context Management: Maintains execution context for Jac programs 3. Runtime API: Provides the API for Jac features like node/edge operations, walker operations, etc. 4. Dynamic Updates: Enables runtime updates to components like walkers
JacMachine#
JacMachine
maintains the state of the Jac machine during execution. It includes loaded modules, the execution context, and the Jac program.
class JacMachine:
"""Jac Machine State."""
def __init__(
self,
base_path: str = "",
session: Optional[str] = None,
root: Optional[str] = None,
interp_mode: bool = False,
) -> None:
"""Initialize JacMachine."""
self.loaded_modules: dict[str, types.ModuleType] = {}
if not base_path:
base_path = os.getcwd()
# Ensure the base_path is a list rather than a string
self.base_path = base_path
self.base_path_dir = (
os.path.dirname(base_path)
if not os.path.isdir(base_path)
else os.path.abspath(base_path)
)
self.jac_program: JacProgram = JacProgram()
self.interp_mode = interp_mode
self = ExecutionContext(session=session, root=root, mach=self)
ExecutionContext#
ExecutionContext
represents the execution environment for a Jac program. It includes the memory, reports, and root nodes.
class ExecutionContext:
"""Execution Context."""
mach: JacMachine
mem: Memory
reports: list[Any]
custom: Any = MISSING
system_root: NodeAnchor
root: NodeAnchor
entry_node: NodeAnchor
Memory Management#
Jaclang uses a memory management system to store and retrieve objects during execution.
Memory#
The Memory
class is a generic memory handler that stores objects by ID. It provides methods to find, set, and remove objects.
@dataclass
class Memory(Generic[ID, TANCH]):
"""Generic Memory Handler."""
__mem__: dict[ID, TANCH] = field(default_factory=dict)
__gc__: set[TANCH] = field(default_factory=set)
ShelfStorage#
ShelfStorage
extends Memory
to provide persistent storage using Python's shelve
module. It synchronizes memory with disk storage.
@dataclass
class ShelfStorage(Memory[UUID, Anchor]):
"""Shelf Handler."""
__shelf__: Shelf[Anchor] | None = None
Archetypes and Anchors#
Jaclang uses archetypes and anchors to represent objects in the runtime system.
classDiagram
class Archetype {
+list _jac_entry_funcs_
+list _jac_exit_funcs_
+Anchor __jac__
}
class NodeArchetype {
+__jac_base__: bool
}
class EdgeArchetype {
+__jac_base__: bool
}
class WalkerArchetype {
+__jac_base__: bool
}
class ObjectArchetype {
+__jac_base__: bool
}
class Anchor {
+Archetype archetype
+UUID id
+UUID root
+Permission access
+bool persistent
+int hash
+is_populated()
+make_stub()
+populate()
}
class NodeAnchor {
+NodeArchetype archetype
+list edges
}
class EdgeAnchor {
+EdgeArchetype archetype
+NodeAnchor source
+NodeAnchor target
+bool is_undirected
}
class WalkerAnchor {
+WalkerArchetype archetype
+list path
+list next
+list ignores
+bool disengaged
}
Archetype <|-- NodeArchetype : inherits
Archetype <|-- EdgeArchetype : inherits
Archetype <|-- WalkerArchetype : inherits
Archetype <|-- ObjectArchetype : inherits
Anchor <|-- NodeAnchor : inherits
Anchor <|-- EdgeAnchor : inherits
Anchor <|-- WalkerAnchor : inherits
Archetype --> Anchor : has
NodeArchetype --> NodeAnchor : has
EdgeArchetype --> EdgeAnchor : has
WalkerArchetype --> WalkerAnchor : has
NodeAnchor --> EdgeAnchor : contains
EdgeAnchor --> NodeAnchor : references
Archetype#
Archetype
is the base class for all Jac objects. It defines the common interface for all archetypes.
@dataclass(eq=False, repr=False, kw_only=True)
class Archetype:
"""Archetype Protocol."""
_jac_entry_funcs_: ClassVar[list[DataSpatialFunction]] = []
_jac_exit_funcs_: ClassVar[list[DataSpatialFunction]] = []
@cached_property
def __jac__(self) -> Anchor:
"""Create default anchor."""
return Anchor(archetype=self)
Specialized archetypes:
- NodeArchetype
: Represents a node in a graph
- EdgeArchetype
: Represents an edge in a graph
- WalkerArchetype
: Represents a walker that traverses a graph
- ObjectArchetype
: Represents a generic object
Anchor#
Anchor
is a wrapper around an archetype that provides additional metadata and functionality.
@dataclass(eq=False, repr=False, kw_only=True)
class Anchor:
"""Object Anchor."""
archetype: Archetype
id: UUID = field(default_factory=uuid4)
root: Optional[UUID] = None
access: Permission = field(default_factory=Permission)
persistent: bool = False
hash: int = 0
Specialized anchors:
- NodeAnchor
: Wraps a NodeArchetype
and maintains a list of connected edges
- EdgeAnchor
: Wraps an EdgeArchetype
and maintains references to source and target nodes
- WalkerAnchor
: Wraps a WalkerArchetype
and maintains walker state
Module System#
Jaclang has a sophisticated module system that handles importing Jac and Python modules.
flowchart TD
A[Import Request] --> B{Is Python Module?}
B -->|Yes| C[PythonImporter]
B -->|No| D[JacImporter]
C --> E[importlib.import_module]
D --> F[Resolve Module Path]
F --> G[Create Module]
G --> H[Compile Module]
H --> I[Execute Module]
I --> J[Update Module Registry]
E --> J
style A fill:#2d3748,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style B fill:#c53030,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style C fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style D fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style E fill:#38a169,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style F fill:#38a169,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style G fill:#38a169,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style H fill:#38a169,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style I fill:#38a169,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style J fill:#2d3748,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
classDiagram
class Importer {
+JacMachine jac_machine
+ImportReturn result
+run_import()
}
class JacImporter {
+get_sys_mod_name()
+handle_directory()
+create_jac_py_module()
+run_import()
}
class PythonImporter {
+run_import()
}
class ImportPathSpec {
+str target
+str base_path
+bool absorb
+str mdl_alias
+str override_name
+str language
+dict items
+get_caller_dir()
}
class ImportReturn {
+types.ModuleType ret_mod
+list ret_items
+Importer importer
+process_items()
+load_jac_mod_as_item()
}
Importer <|-- JacImporter : inherits
Importer <|-- PythonImporter : inherits
Importer --> ImportPathSpec : uses
Importer --> ImportReturn : returns
Importers#
The module system uses importers to load modules:
JacImporter
: Loads Jac modulesPythonImporter
: Loads Python modules
class Importer:
"""Abstract base class for all importers."""
def __init__(self, jac_machine: JacMachine) -> None:
"""Initialize the Importer object."""
self.jac_machine = jac_machine
self.result: Optional[ImportReturn] = None
def run_import(self, spec: ImportPathSpec) -> ImportReturn:
"""Run the import process."""
raise NotImplementedError
Import Process#
The import process involves:
- Resolving the module path
- Loading the module
- Compiling the module (for Jac modules)
- Executing the module
- Updating the module registry
Dynamic Updates#
One of the most powerful features of JacMachine
is the ability to dynamically update components at runtime, particularly walkers.
sequenceDiagram
participant User
participant JacMachine
participant JacImporter
participant Module
participant Walker
User->>JacMachine: update_walker(module_name, items)
JacMachine->>JacMachine: Check if module exists
JacMachine->>JacImporter: Create importer
JacMachine->>JacImporter: Create ImportPathSpec
JacImporter->>JacImporter: run_import(spec, reload=True)
JacImporter->>Module: Load updated module
JacMachine->>Module: Get old module
JacMachine->>Module: Update specific items
Module->>Walker: Replace walker implementation
JacMachine-->>User: Return updated items
note over JacMachine,Walker: Only the specified walkers are updated, not the entire module
Updating Walkers#
Walkers can be updated during runtime without reloading the entire module. This is useful for debugging, live code updates, and reactive programming.
@staticmethod
def update_walker(
mach: JacMachine,
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 mach.loaded_modules:
try:
old_module = mach.loaded_modules[module_name]
importer = JacImporter(mach)
spec = ImportPathSpec(
target=module_name,
base_path=mach.base_path,
absorb=False,
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 ()
Access Control#
Jaclang has a sophisticated access control system that manages permissions for objects.
classDiagram
class AccessLevel {
<<enumeration>>
NO_ACCESS
READ
CONNECT
WRITE
+cast()
}
class Access {
+dict anchors
+check()
}
class Permission {
+AccessLevel all
+Access roots
}
class Anchor {
+Permission access
}
Permission --> AccessLevel : uses
Permission --> Access : contains
Anchor --> Permission : has
note for AccessLevel "Defines access levels"
note for Access "Maps anchors to access levels"
note for Permission "Defines permissions for an anchor"
flowchart TD
A[Access Request] --> B{Check Access Level}
B --> C{Is System Root?}
C -->|Yes| D[Grant WRITE Access]
C -->|No| E{Is Same Root?}
E -->|Yes| D
E -->|No| F{Is Target Anchor?}
F -->|Yes| D
F -->|No| G{Check access.all}
G --> H{Check target root's access.all}
H --> I{Check target root's allowed roots}
I --> J{Check target's allowed roots}
J --> K[Return Final Access Level]
style A fill:#2d3748,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style B fill:#c53030,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style C fill:#c53030,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style D fill:#38a169,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style E fill:#c53030,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style F fill:#c53030,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style G fill:#c53030,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style H fill:#c53030,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style I fill:#c53030,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style J fill:#c53030,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style K fill:#2d3748,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
Permission#
The Permission
class defines access levels for objects:
@dataclass
class Permission:
"""Anchor Access Handler."""
all: AccessLevel = AccessLevel.NO_ACCESS
roots: Access = field(default_factory=Access)
AccessLevel#
AccessLevel
defines the levels of access:
Contributing to Jaclang#
Understanding the Codebase#
The Jaclang codebase is organized into several key directories:
graph TD
A[jaclang] --> B[compiler]
A --> C[runtimelib]
A --> D[utils]
A --> E[cli]
A --> F[langserve]
B --> B1[passes]
B --> B2[unitree.py]
B --> B3[program.py]
B --> B4[parser.py]
C --> C1[archetype.py]
C --> C2[machine.py]
C --> C3[memory.py]
C --> C4[importer.py]
style A fill:#2d3748,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style B fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style C fill:#38a169,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style D fill:#805ad5,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style E fill:#805ad5,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style F fill:#805ad5,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style B1 fill:#3182ce,stroke:#e2e8f0,stroke-width:1px,color:#e2e8f0
style B2 fill:#3182ce,stroke:#e2e8f0,stroke-width:1px,color:#e2e8f0
style B3 fill:#3182ce,stroke:#e2e8f0,stroke-width:1px,color:#e2e8f0
style B4 fill:#3182ce,stroke:#e2e8f0,stroke-width:1px,color:#e2e8f0
style C1 fill:#38a169,stroke:#e2e8f0,stroke-width:1px,color:#e2e8f0
style C2 fill:#38a169,stroke:#e2e8f0,stroke-width:1px,color:#e2e8f0
style C3 fill:#38a169,stroke:#e2e8f0,stroke-width:1px,color:#e2e8f0
style C4 fill:#38a169,stroke:#e2e8f0,stroke-width:1px,color:#e2e8f0
jaclang/compiler
: Contains the compiler componentsjaclang/runtimelib
: Contains the runtime componentsjaclang/utils
: Contains utility functionsjaclang/cli
: Contains the command-line interfacejaclang/langserve
: Contains the language server components
Development Workflow#
flowchart LR
A[Setup] --> B[Testing]
B --> C[Implementation]
C --> D[Documentation]
D --> E[Pull Request]
E --> F[Code Review]
F --> G[Merge]
style A fill:#2d3748,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style B fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style C fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style D fill:#3182ce,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style E fill:#38a169,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style F fill:#38a169,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
style G fill:#38a169,stroke:#e2e8f0,stroke-width:2px,color:#e2e8f0
- Setup: Clone the repository and install dependencies
- Testing: Run tests to ensure your changes don't break existing functionality
- Implementation: Make your changes following the project's coding standards
- Documentation: Update documentation to reflect your changes
- Pull Request: Submit a pull request with your changes
- Code Review: Address feedback from reviewers
- Merge: Once approved, your changes will be merged into the main codebase
Best Practices#
- Follow the existing code style and architecture
- Write tests for new functionality
- Document your code with docstrings
- Keep changes focused and minimal
- Discuss major changes with the community before implementation
Conclusion#
This document provides an overview of the Jaclang internals. For more detailed information, refer to the source code and other documentation. If you have questions or need help, feel free to reach out to the Jaclang community.