diff --git a/jac/jaclang/cli/cli.py b/jac/jaclang/cli/cli.py index a16c288443..c395545072 100644 --- a/jac/jaclang/cli/cli.py +++ b/jac/jaclang/cli/cli.py @@ -172,7 +172,7 @@ def get_object( data = {} obj = Jac.get_object(id) if obj: - data = obj.__jac__.__getstate__() + data = obj.__jac__ else: print(f"Object with id {id} not found.", file=sys.stderr) diff --git a/jac/jaclang/plugin/default.py b/jac/jaclang/plugin/default.py index d1b3b63ad9..2ce474b554 100644 --- a/jac/jaclang/plugin/default.py +++ b/jac/jaclang/plugin/default.py @@ -12,7 +12,6 @@ from functools import wraps from logging import getLogger from typing import Any, Callable, Mapping, Optional, Sequence, Type, Union, cast -from uuid import UUID from jaclang.compiler.constant import colors from jaclang.compiler.semtable import SemInfo, SemRegistry, SemScope @@ -38,6 +37,7 @@ ) from jaclang.runtimelib.constructs import ( GenericEdge, + JacLangJID, JacTestCheck, ) from jaclang.runtimelib.importer import ImportPathSpec, JacImporter, PythonImporter @@ -58,13 +58,14 @@ class JacCallableImplementation: @staticmethod def get_object(id: str | JID) -> Architype | None: """Get object by id.""" + jctx = Jac.get_context() if id == "root": - return Jac.get_context().root.architype + return jctx.root.architype if isinstance(id, str): - id = JID(id) + id = JacLangJID(id) - if obj := Jac.get_context().mem.find_by_id(id): + if obj := jctx.mem.find_by_id(id): return obj.architype return None @@ -83,26 +84,25 @@ def elevate_root() -> None: @staticmethod @hookimpl def allow_root( - architype: Architype, root_id: UUID, level: AccessLevel | int | str + architype: Architype, root_id: JID, level: AccessLevel | int | str ) -> None: """Allow all access from target root graph to current Architype.""" level = AccessLevel.cast(level) access = architype.__jac__.access.roots - _root_id = str(root_id) - if level != access.anchors.get(_root_id, AccessLevel.NO_ACCESS): - access.anchors[_root_id] = level + if level != access.anchors.get(root_id, AccessLevel.NO_ACCESS): + access.anchors[root_id] = level @staticmethod @hookimpl def disallow_root( - architype: Architype, root_id: UUID, level: AccessLevel | int | str + architype: Architype, root_id: JID, level: AccessLevel | int | str ) -> None: """Disallow all access from target root graph to current Architype.""" level = AccessLevel.cast(level) access = architype.__jac__.access.roots - access.anchors.pop(str(root_id), None) + access.anchors.pop(root_id, None) @staticmethod @hookimpl @@ -165,7 +165,7 @@ def check_access_level(to: Anchor) -> AccessLevel: # if current root is system_root # if current root id is equal to target anchor's root id # if current root is the target anchor - if jroot == jctx.system_root or jroot.id == to.root or jroot == to: + if jroot == jctx.system_root or jroot.jid == to.root or jroot == to: return AccessLevel.WRITE access_level = AccessLevel.NO_ACCESS @@ -176,17 +176,17 @@ def check_access_level(to: Anchor) -> AccessLevel: # if target anchor's root have set allowed roots # if current root is allowed to the whole graph of target anchor's root - if to.root and isinstance(to_root := jctx.mem.find_one(to.root), Anchor): + if to.root and (to_root := to.root.anchor): if to_root.access.all > access_level: access_level = to_root.access.all - level = to_root.access.roots.check(str(jroot.id)) + level = to_root.access.roots.check(jroot.jid) if level > AccessLevel.NO_ACCESS and access_level == AccessLevel.NO_ACCESS: access_level = level # if target anchor have set allowed roots # if current root is allowed to target anchor - level = to_access.roots.check(str(jroot.id)) + level = to_access.roots.check(jroot.jid) if level > AccessLevel.NO_ACCESS and access_level == AccessLevel.NO_ACCESS: access_level = level @@ -231,9 +231,10 @@ def get_edges( ) -> list[EdgeArchitype]: """Get edges connected to this node.""" ret_edges: list[EdgeArchitype] = [] - for anchor in Jac.get_context().mem.find(node.edges): + for jid in node.edges: if ( - (source := anchor.source.anchor) + (anchor := jid.anchor) + and (source := anchor.source.anchor) and (target := anchor.target.anchor) and (not filter_func or filter_func([anchor.architype])) ): @@ -263,9 +264,10 @@ def edges_to_nodes( ) -> list[NodeArchitype]: """Get set of nodes connected to this node.""" ret_edges: list[NodeArchitype] = [] - for anchor in Jac.get_context().mem.find(node.edges): + for jid in node.edges: if ( - (source := anchor.source.anchor) + (anchor := jid.anchor) + and (source := anchor.source.anchor) and (target := anchor.target.anchor) and (not filter_func or filter_func([anchor.architype])) ): @@ -290,7 +292,7 @@ def edges_to_nodes( def remove_edge(node: NodeAnchor, edge: EdgeAnchor) -> None: """Remove reference without checking sync status.""" for idx, ed in enumerate(node.edges): - if ed == edge.id: + if ed == edge.jid: node.edges.pop(idx) break @@ -609,10 +611,10 @@ def reset_graph(root: Optional[Root] = None) -> int: if isinstance(anchors := mem.__shelf__, Shelf) else mem.__mem__.values() ): - if anchor == ranchor or anchor.root != ranchor.id: + if anchor == ranchor or anchor.root != ranchor.jid: continue - if loaded_anchor := mem.find_by_id(anchor.id): + if loaded_anchor := mem.find_by_id(anchor.jid): deleted_count += 1 Jac.destroy(loaded_anchor) @@ -628,7 +630,7 @@ def get_object_func() -> Callable[[str | JID], Architype | None]: @hookimpl def object_ref(obj: Architype) -> JID: """Get object's id.""" - return obj.__jac__.id + return obj.__jac__.jid @staticmethod @hookimpl @@ -995,9 +997,10 @@ def disconnect( for i in left: node = i.__jac__ - for anchor in Jac.get_context().mem.find(node.edges): + for jid in set(node.edges): if ( - (source := anchor.source.anchor) + (anchor := jid.anchor) + and (source := anchor.source.anchor) and (target := anchor.target.anchor) and (not filter_func or filter_func([anchor.architype])) ): @@ -1017,7 +1020,6 @@ def disconnect( ): Jac.destroy(anchor) if anchor.persistent else Jac.detach(anchor) disconnect_occurred = True - return disconnect_occurred @staticmethod @@ -1059,12 +1061,12 @@ def builder(source: NodeAnchor, target: NodeAnchor) -> EdgeArchitype: eanch = edge.__jac__ = EdgeAnchor( architype=edge, - source=source.id, - target=target.id, + source=source.jid, + target=target.jid, is_undirected=is_undirected, ) - source.edges.append(eanch.id) - target.edges.append(eanch.id) + source.edges.append(eanch.jid) + target.edges.append(eanch.jid) if conn_assign: for fld, val in zip(conn_assign[0], conn_assign[1]): @@ -1089,9 +1091,9 @@ def save(obj: Architype | Anchor) -> None: jctx = Jac.get_context() anchor.persistent = True - anchor.root = jctx.root.id + anchor.root = jctx.root.jid - jctx.mem.set(anchor.id, anchor) + jctx.mem.set(anchor.jid, anchor) @staticmethod @hookimpl @@ -1110,7 +1112,7 @@ def destroy(obj: Architype | Anchor) -> None: case _: pass - Jac.get_context().mem.remove(anchor.id) + Jac.get_context().mem.remove(anchor.jid) @staticmethod @hookimpl diff --git a/jac/jaclang/plugin/feature.py b/jac/jaclang/plugin/feature.py index d5535036be..2dc6fe9cd9 100644 --- a/jac/jaclang/plugin/feature.py +++ b/jac/jaclang/plugin/feature.py @@ -15,7 +15,6 @@ TypeAlias, Union, ) -from uuid import UUID from jaclang.plugin.spec import ( AccessLevel, @@ -37,6 +36,7 @@ ast, plugin_manager, ) +from jaclang.runtimelib.constructs import ObjectArchitype class JacAccessValidation: @@ -50,7 +50,7 @@ def elevate_root() -> None: @staticmethod def allow_root( architype: Architype, - root_id: UUID, + root_id: JID, level: AccessLevel | int | str = AccessLevel.READ, ) -> None: """Allow all access from target root graph to current Architype.""" @@ -61,7 +61,7 @@ def allow_root( @staticmethod def disallow_root( architype: Architype, - root_id: UUID, + root_id: JID, level: AccessLevel | int | str = AccessLevel.READ, ) -> None: """Disallow all access from target root graph to current Architype.""" @@ -197,7 +197,7 @@ class JacClassReferences: EdgeDir: ClassVar[TypeAlias] = EdgeDir DSFunc: ClassVar[TypeAlias] = DSFunc RootType: ClassVar[TypeAlias] = Root - Obj: ClassVar[TypeAlias] = Architype + Obj: ClassVar[TypeAlias] = ObjectArchitype Node: ClassVar[TypeAlias] = NodeArchitype Edge: ClassVar[TypeAlias] = EdgeArchitype Walker: ClassVar[TypeAlias] = WalkerArchitype diff --git a/jac/jaclang/plugin/spec.py b/jac/jaclang/plugin/spec.py index aecb0319b5..84bf979aca 100644 --- a/jac/jaclang/plugin/spec.py +++ b/jac/jaclang/plugin/spec.py @@ -15,7 +15,6 @@ TypeVar, Union, ) -from uuid import UUID from jaclang.compiler import absyntree as ast from jaclang.compiler.constant import EdgeDir @@ -56,7 +55,7 @@ def elevate_root() -> None: @staticmethod @hookspec(firstresult=True) def allow_root( - architype: Architype, root_id: UUID, level: AccessLevel | int | str + architype: Architype, root_id: JID, level: AccessLevel | int | str ) -> None: """Allow all access from target root graph to current Architype.""" raise NotImplementedError @@ -64,7 +63,7 @@ def allow_root( @staticmethod @hookspec(firstresult=True) def disallow_root( - architype: Architype, root_id: UUID, level: AccessLevel | int | str + architype: Architype, root_id: JID, level: AccessLevel | int | str ) -> None: """Disallow all access from target root graph to current Architype.""" raise NotImplementedError diff --git a/jac/jaclang/plugin/tests/fixtures/other_root_access.jac b/jac/jaclang/plugin/tests/fixtures/other_root_access.jac index 9b971f6622..0ecf5a744a 100644 --- a/jac/jaclang/plugin/tests/fixtures/other_root_access.jac +++ b/jac/jaclang/plugin/tests/fixtures/other_root_access.jac @@ -1,5 +1,4 @@ -import:py from jaclang.runtimelib.architype {Anchor} -import:py from uuid {UUID} +import:py from jaclang.runtimelib.architype {Anchor, JacLangJID} node A { has val: int; @@ -48,15 +47,15 @@ walker create_node { can enter with `root entry { a = A(val=self.val); here ++> a; - print(a.__jac__.id); + print(jid(a)); } } walker create_other_root { can enter with `root entry { - other_root = `root().__jac__; + other_root = `root(); Jac.save(other_root); - print(other_root.id); + print(jid(other_root)); } } @@ -67,7 +66,7 @@ walker allow_other_root_access { if self.via_all { Jac.unrestrict(here, self.level); } else { - Jac.allow_root(here, UUID(self.root_id), self.level); + Jac.allow_root(here, JacLangJID(self.root_id), self.level); } } @@ -75,7 +74,7 @@ walker allow_other_root_access { if self.via_all { Jac.unrestrict(here, self.level); } else { - Jac.allow_root(here, UUID(self.root_id), self.level); + Jac.allow_root(here, JacLangJID(self.root_id), self.level); } } } @@ -87,7 +86,7 @@ walker disallow_other_root_access { if self.via_all { Jac.restrict(here); } else { - Jac.disallow_root(here, UUID(self.root_id)); + Jac.disallow_root(here, JacLangJID(self.root_id)); } } @@ -95,7 +94,7 @@ walker disallow_other_root_access { if self.via_all { Jac.restrict(here); } else { - Jac.disallow_root(here, UUID(self.root_id)); + Jac.disallow_root(here, JacLangJID(self.root_id)); } } } \ No newline at end of file diff --git a/jac/jaclang/plugin/tests/test_jaseci.py b/jac/jaclang/plugin/tests/test_jaseci.py index a66a3c2472..dab4fdd082 100644 --- a/jac/jaclang/plugin/tests/test_jaseci.py +++ b/jac/jaclang/plugin/tests/test_jaseci.py @@ -78,7 +78,7 @@ def test_entrypoint_root(self) -> None: session=session, entrypoint="traverse", args=[], - node=str(obj["id"]), + node=str(obj.jid), ) output = self.capturedOutput.getvalue().strip() self.assertEqual(output, "node a\nnode b") @@ -100,12 +100,12 @@ def test_entrypoint_non_root(self) -> None: ) edge_obj = cli.get_object( filename=self.fixture_abs_path("simple_persistent.jac"), - id=obj["edges"][0].id.hex, + id=str(obj.edges[0]), session=session, ) a_obj = cli.get_object( filename=self.fixture_abs_path("simple_persistent.jac"), - id=edge_obj["target"].id.hex, + id=str(edge_obj.target), session=session, ) self._output2buffer() @@ -113,7 +113,7 @@ def test_entrypoint_non_root(self) -> None: filename=self.fixture_abs_path("simple_persistent.jac"), session=session, entrypoint="traverse", - node=str(a_obj["id"]), + node=str(a_obj.jid), args=[], ) output = self.capturedOutput.getvalue().strip() @@ -132,28 +132,26 @@ def test_get_edge(self) -> None: session=session, id="root", ) - self.assertEqual(len(obj["edges"]), 2) + self.assertEqual(len(obj.edges), 2) edge_objs = [ cli.get_object( filename=self.fixture_abs_path("simple_node_connection.jac"), session=session, - id=e.id.hex, + id=str(e), ) - for e in obj["edges"] + for e in obj.edges ] - node_ids = [obj["target"].id.hex for obj in edge_objs] + node_ids = [str(obj.target) for obj in edge_objs] node_objs = [ cli.get_object( filename=self.fixture_abs_path("simple_node_connection.jac"), session=session, - id=str(n_id), + id=n_id, ) for n_id in node_ids ] self.assertEqual(len(node_objs), 2) - self.assertEqual( - {obj["architype"].tag for obj in node_objs}, {"first", "second"} - ) + self.assertEqual({obj.architype.tag for obj in node_objs}, {"first", "second"}) self._del_session(session) def test_filter_on_edge_get_edge(self) -> None: @@ -698,6 +696,7 @@ def test_other_root_access(self) -> None: root=root2, ) archs = self.capturedOutput.getvalue().strip().split("\n") + self.assertEqual(2, len(archs)) self.assertTrue(archs[0], "A(val=1)") self.assertTrue(archs[1], "A(val=2)") diff --git a/jac/jaclang/runtimelib/architype.py b/jac/jaclang/runtimelib/architype.py index 419108f7eb..222403f92e 100644 --- a/jac/jaclang/runtimelib/architype.py +++ b/jac/jaclang/runtimelib/architype.py @@ -16,7 +16,7 @@ JID_REGEX = compile( - r"^(n|e|w):([^:]*):([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$", + r"^(n|e|w|o):([^:]*):([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$", IGNORECASE, ) _ANCHOR = TypeVar("_ANCHOR", bound="Anchor") @@ -24,7 +24,7 @@ @dataclass(kw_only=True) class JID(Generic[_ANCHOR]): - """Jaclang ID Implementation.""" + """Jaclang ID Interface.""" id: Any type: Type[_ANCHOR] @@ -33,7 +33,14 @@ class JID(Generic[_ANCHOR]): @cached_property def __cached_repr__(self) -> str: """Cached string representation.""" - return f"{self.type.__class__.__name__[:1].lower()}:{self.name}:{self.id}" + return f"{self.type.__name__[:1].lower()}:{self.name}:{self.id}" + + @cached_property + def anchor(self) -> _ANCHOR | None: + """Get architype.""" + from jaclang.plugin.feature import JacFeature + + return JacFeature.get_context().mem.find_by_id(self) def __repr__(self) -> str: """Override string representation.""" @@ -47,6 +54,11 @@ def __hash__(self) -> int: """Return default hasher.""" return hash(self.__cached_repr__) + +@dataclass(kw_only=True) +class JacLangJID(JID[_ANCHOR]): + """Jaclang ID Implementation.""" + def __init__( self, id: str | UUID | None = None, @@ -83,12 +95,17 @@ def __init__( self.type = type self.name = name - @cached_property - def anchor(self) -> _ANCHOR | None: - """Get architype.""" - from jaclang.plugin.feature import JacFeature + def __getstate__(self) -> str: + """Override getstate.""" + return self.__cached_repr__ - return JacFeature.get_context().mem.find_by_id(self) + def __setstate__(self, state: str) -> None: + """Override setstate.""" + self.__init__(state) # type: ignore[misc] + + def __hash__(self) -> int: + """Return default hasher.""" + return hash(self.__cached_repr__) class AccessLevel(IntEnum): @@ -115,11 +132,11 @@ def cast(val: int | str | AccessLevel) -> AccessLevel: class Access: """Access Structure.""" - anchors: dict[str, AccessLevel] = field(default_factory=dict) + anchors: dict[JID, AccessLevel] = field(default_factory=dict) - def check(self, anchor: str) -> AccessLevel: + def check(self, id: JID) -> AccessLevel: """Validate access.""" - return self.anchors.get(anchor, AccessLevel.NO_ACCESS) + return self.anchors.get(id, AccessLevel.NO_ACCESS) @dataclass @@ -134,25 +151,41 @@ class Permission: class AnchorReport: """Report Handler.""" - id: str + id: JID context: dict[str, Any] -@dataclass(kw_only=True) +@dataclass(eq=False, kw_only=True) class Anchor: """Object Anchor.""" architype: Architype - id: JID = field(default_factory=lambda: JID(type=Anchor)) + id: Any = field(default_factory=uuid4) root: Optional[JID[NodeAnchor]] = None access: Permission = field(default_factory=Permission) persistent: bool = False hash: int = 0 + @cached_property + def jid(self: _ANCHOR) -> JID[_ANCHOR]: + """Get JID representation.""" + jid = JacLangJID[_ANCHOR]( + self.id, + self.__class__, + ( + "" + if isinstance(self.architype, (GenericEdge, Root)) + else self.architype.__class__.__name__ + ), + ) + jid.anchor = self + + return jid + def report(self) -> AnchorReport: """Report Anchor.""" return AnchorReport( - id=str(self.id), + id=self.jid, context=( asdict(self.architype) if is_dataclass(self.architype) and not isinstance(self.architype, type) @@ -162,55 +195,49 @@ def report(self) -> AnchorReport: def __hash__(self) -> int: """Override hash for anchor.""" - return hash(self.id) + return hash(self.jid) + def __eq__(self, value: object) -> bool: + """Override __eq__.""" + if isinstance(value, Anchor): + return value.jid == self.jid + return False -@dataclass(kw_only=True) + +@dataclass(eq=False, kw_only=True) class NodeAnchor(Anchor): """Node Anchor.""" architype: NodeArchitype - id: JID["NodeAnchor"] = field( - default_factory=lambda: JID["NodeAnchor"](type=NodeAnchor) - ) edges: list[JID["EdgeAnchor"]] -@dataclass(kw_only=True) +@dataclass(eq=False, kw_only=True) class EdgeAnchor(Anchor): """Edge Anchor.""" architype: EdgeArchitype - id: JID["EdgeAnchor"] = field( - default_factory=lambda: JID["EdgeAnchor"](type=EdgeAnchor) - ) source: JID["NodeAnchor"] target: JID["NodeAnchor"] is_undirected: bool -@dataclass(kw_only=True) +@dataclass(eq=False, kw_only=True) class WalkerAnchor(Anchor): """Walker Anchor.""" architype: WalkerArchitype - id: JID["WalkerAnchor"] = field( - default_factory=lambda: JID["WalkerAnchor"](type=WalkerAnchor) - ) path: list[Anchor] = field(default_factory=list) next: list[Anchor] = field(default_factory=list) ignores: list[Anchor] = field(default_factory=list) disengaged: bool = False -@dataclass(kw_only=True) +@dataclass(eq=False, kw_only=True) class ObjectAnchor(Anchor): """Object Anchor.""" architype: ObjectArchitype - id: JID["ObjectAnchor"] = field( - default_factory=lambda: JID["ObjectAnchor"](type=ObjectAnchor) - ) class Architype: diff --git a/jac/jaclang/runtimelib/constructs.py b/jac/jaclang/runtimelib/constructs.py index 6f99845a3d..555af339f0 100644 --- a/jac/jaclang/runtimelib/constructs.py +++ b/jac/jaclang/runtimelib/constructs.py @@ -12,8 +12,10 @@ EdgeArchitype, GenericEdge, JID, + JacLangJID, NodeAnchor, NodeArchitype, + ObjectArchitype, Root, WalkerAnchor, WalkerArchitype, @@ -32,8 +34,10 @@ "NodeArchitype", "EdgeArchitype", "WalkerArchitype", + "ObjectArchitype", "GenericEdge", "JID", + "JacLangJID", "Root", "DSFunc", "Memory", diff --git a/jac/jaclang/runtimelib/context.py b/jac/jaclang/runtimelib/context.py index 730bc22ad7..a65e4285f2 100644 --- a/jac/jaclang/runtimelib/context.py +++ b/jac/jaclang/runtimelib/context.py @@ -6,17 +6,18 @@ from contextvars import ContextVar from dataclasses import MISSING from typing import Any, Callable, Optional, cast +from uuid import UUID -from .architype import JID, NodeAnchor, Root +from .architype import JacLangJID, NodeAnchor, Root from .memory import Memory, ShelfStorage EXECUTION_CONTEXT = ContextVar[Optional["ExecutionContext"]]("ExecutionContext") -SUPER_ROOT_JID = JID[NodeAnchor]("n::00000000-0000-0000-0000-000000000000") +SUPER_ROOT_UUID = UUID("00000000-0000-0000-0000-000000000000") SUPER_ROOT_ARCHITYPE = object.__new__(Root) SUPER_ROOT_ANCHOR = NodeAnchor( - id=SUPER_ROOT_JID, architype=SUPER_ROOT_ARCHITYPE, persistent=False, edges=[] + id=SUPER_ROOT_UUID, architype=SUPER_ROOT_ARCHITYPE, persistent=False, edges=[] ) SUPER_ROOT_ARCHITYPE.__jac__ = SUPER_ROOT_ANCHOR @@ -38,7 +39,9 @@ def init_anchor( ) -> NodeAnchor: """Load initial anchors.""" if anchor_id: - if isinstance(anchor := self.mem.find_by_id(JID(anchor_id)), NodeAnchor): + if isinstance( + anchor := self.mem.find_by_id(JacLangJID(anchor_id)), NodeAnchor + ): return anchor raise ValueError(f"Invalid anchor id {anchor_id} !") return default @@ -64,11 +67,11 @@ def create( ctx.reports = [] if not isinstance( - system_root := ctx.mem.find_by_id(SUPER_ROOT_JID), NodeAnchor + system_root := ctx.mem.find_by_id(SUPER_ROOT_ANCHOR.jid), NodeAnchor ): system_root = Root().__jac__ - system_root.id = SUPER_ROOT_JID - ctx.mem.set(system_root.id, system_root) + system_root.id = SUPER_ROOT_UUID + ctx.mem.set(system_root.jid, system_root) ctx.system_root = system_root diff --git a/jac/jaclang/runtimelib/memory.py b/jac/jaclang/runtimelib/memory.py index 828c229358..72b198fab5 100644 --- a/jac/jaclang/runtimelib/memory.py +++ b/jac/jaclang/runtimelib/memory.py @@ -45,7 +45,7 @@ def find( def find_one( self, - ids: JID | Iterable[JID], + ids: JID[_ANCHOR] | Iterable[JID[_ANCHOR]], filter: Callable[[_ANCHOR], _ANCHOR] | None = None, ) -> _ANCHOR | None: """Find one anchor from memory by ids with filter.""" @@ -87,9 +87,9 @@ def __init__(self, session: str | None = None) -> None: def close(self) -> None: """Close memory handler.""" if isinstance(self.__shelf__, Shelf): - for anchor in self.__gc__: - self.__shelf__.pop(str(anchor.id), None) - self.__mem__.pop(anchor.id, None) + for jid in self.__gc__: + self.__shelf__.pop(str(jid), None) + self.__mem__.pop(jid, None) keys = set(self.__mem__.keys()) @@ -113,7 +113,7 @@ def sync_mem_to_db(self, keys: Iterable[JID]) -> None: and d.persistent and d.hash != hash(dumps(d)) ): - _id = str(d.id) + _id = str(d.jid) if p_d := self.__shelf__.get(_id): if ( isinstance(p_d, NodeAnchor) @@ -126,11 +126,15 @@ def sync_mem_to_db(self, keys: Iterable[JID]) -> None: continue p_d.edges = d.edges - if Jac.check_write_access(d): - if hash(dumps(p_d.access)) != hash(dumps(d.access)): - p_d.access = d.access - if hash(dumps(p_d.architype)) != hash(dumps(d.architype)): - p_d.architype = d.architype + if hash(dumps(p_d.architype)) != hash( + dumps(d.architype) + ) and Jac.check_write_access(d): + p_d.architype = d.architype + + if hash(dumps(p_d.access)) != hash( + dumps(d.access) + ) and Jac.check_write_access(d): + p_d.access = d.access self.__shelf__[_id] = p_d elif not ( @@ -158,7 +162,8 @@ def find( and id not in self.__gc__ and (_anchor := self.__shelf__.get(str(id))) ): - self.__mem__[id] = anchor = _anchor + anchor = self.__mem__[id] = _anchor + anchor.architype.__jac__ = anchor if ( anchor and isinstance(anchor, id.type) @@ -179,5 +184,6 @@ def find_by_id(self, id: JID[_ANCHOR]) -> _ANCHOR | None: and isinstance(_data, id.type) ): data = self.__mem__[id] = _data + data.architype.__jac__ = data return data diff --git a/jac/jaclang/runtimelib/utils.py b/jac/jaclang/runtimelib/utils.py index 83e35ab2cf..fb907b4e05 100644 --- a/jac/jaclang/runtimelib/utils.py +++ b/jac/jaclang/runtimelib/utils.py @@ -37,14 +37,13 @@ def collect_node_connections( if current_node not in visited_nodes: visited_nodes.add(current_node) edges = current_node.edges - for edge_ in edges: - target = edge_.target - if target: + for jid in edges: + if (edge := jid.anchor) and (target := edge.target.anchor): connections.add( ( current_node.architype, target.architype, - edge_.__class__.__name__, + edge.__class__.__name__, ) ) collect_node_connections(target, visited_nodes, connections) @@ -66,16 +65,18 @@ def traverse_graph( edge_limit: int, ) -> None: """Traverse the graph using Breadth-First Search (BFS) or Depth-First Search (DFS).""" - for edge in node.__jac__.edges: - is_self_loop = id(edge.source) == id(edge.target) - is_in_edge = edge.target == node.__jac__ + for jid in node.__jac__.edges: + if not (edge := jid.anchor): + continue + is_self_loop = edge.source == edge.target + is_in_edge = edge.target == node.__jac__.jid if (traverse and is_in_edge) or edge.architype.__class__.__name__ in edge_type: continue if is_self_loop: continue # lets skip self loop for a while, need to handle it later - elif (other_nda := edge.target if not is_in_edge else edge.source) and ( - other_nd := other_nda.architype - ): + elif ( + other_nda := edge.target.anchor if not is_in_edge else edge.source.anchor + ) and (other_nd := other_nda.architype): new_con = ( (node, other_nd, edge.architype) if not is_in_edge diff --git a/jac/jaclang/tests/fixtures/edge_ops.jac b/jac/jaclang/tests/fixtures/edge_ops.jac index 9ace322c16..e024717464 100644 --- a/jac/jaclang/tests/fixtures/edge_ops.jac +++ b/jac/jaclang/tests/fixtures/edge_ops.jac @@ -22,7 +22,7 @@ edge MyEdge { for j=0 to j<3 by j+=1 { end +:MyEdge:val=random.randint(1, 15), val2=random.randint(1, 5):+> node_a(value=j + 10); } - print([(arch.val, arch.val2) for i in end.__jac__.edges if isinstance(arch := i.architype, MyEdge)]); + print([(arch.val, arch.val2) for i in end.__jac__.edges if (anch := i.anchor) and isinstance(arch := anch.architype, MyEdge)]); } } for i=0 to i<3 by i+=1 {