From 49dfa74613dd9f6288f9fecf33631eec6002ea9a Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Tue, 3 Dec 2024 17:47:28 +0530 Subject: [PATCH] jac2py codegen refactored --- jac/examples/reference/architypes.py | 11 +- .../data_spatial_walker_statements.py | 11 +- .../reference/disengage_statements.py | 13 +- .../reference/match_class_patterns.py | 11 +- .../reference/special_comprehensions.py | 13 +- jac/examples/reference/visit_statements.py | 13 +- jac/jaclang/__init__.py | 349 +++++- jac/jaclang/compiler/constant.py | 3 +- .../compiler/passes/main/pyast_gen_pass.py | 1012 +++++++++-------- .../passes/main/tests/test_import_pass.py | 5 + jac/jaclang/plugin/default.py | 14 +- jac/jaclang/runtimelib/architype.py | 36 +- 12 files changed, 970 insertions(+), 521 deletions(-) diff --git a/jac/examples/reference/architypes.py b/jac/examples/reference/architypes.py index b41e3ee0b1..3350a4c4de 100644 --- a/jac/examples/reference/architypes.py +++ b/jac/examples/reference/architypes.py @@ -6,8 +6,17 @@ def print_base_classes(cls: type) -> type: return cls +# Since the Animal class cannot be inherit from object, (cause the base class will be changed at run time) +# we need a base class. +# +# reference: https://stackoverflow.com/a/9639512/10846399 +# +class Base: + pass + + @jac.make_obj(on_entry=[], on_exit=[]) -class Animal: +class Animal(Base): pass diff --git a/jac/examples/reference/data_spatial_walker_statements.py b/jac/examples/reference/data_spatial_walker_statements.py index 44591a51bb..254b000e02 100644 --- a/jac/examples/reference/data_spatial_walker_statements.py +++ b/jac/examples/reference/data_spatial_walker_statements.py @@ -2,8 +2,17 @@ from jaclang.plugin.feature import JacFeature as _Jac +# Since the Animal class cannot be inherit from object, (cause the base class will be changed at run time) +# we need a base class. +# +# reference: https://stackoverflow.com/a/9639512/10846399 +# +class Base: + pass + + @_Jac.make_walker(on_entry=[_Jac.DSFunc("self_destruct", None)], on_exit=[]) -class Visitor: +class Visitor(Base): def self_destruct(self, _jac_here_) -> None: print("get's here") _Jac.disengage(self) diff --git a/jac/examples/reference/disengage_statements.py b/jac/examples/reference/disengage_statements.py index a8b2266d93..4797d2d6fd 100644 --- a/jac/examples/reference/disengage_statements.py +++ b/jac/examples/reference/disengage_statements.py @@ -2,8 +2,17 @@ from jaclang.plugin.feature import JacFeature as _Jac +# Since the Animal class cannot be inherit from object, (cause the base class will be changed at run time) +# we need a base class. +# +# reference: https://stackoverflow.com/a/9639512/10846399 +# +class Base: + pass + + @_Jac.make_walker(on_entry=[_Jac.DSFunc("travel", _Jac.get_root_type())], on_exit=[]) -class Visitor: +class Visitor(Base): def travel(self, _jac_here_: _Jac.get_root_type()) -> None: if _Jac.visit_node( self, _Jac.edge_ref(_jac_here_, None, _Jac.EdgeDir.OUT, None, None) @@ -14,7 +23,7 @@ def travel(self, _jac_here_: _Jac.get_root_type()) -> None: @_Jac.make_node(on_entry=[_Jac.DSFunc("speak", Visitor)], on_exit=[]) -class item: +class item(Base): def speak(self, _jac_here_: Visitor) -> None: print("Hey There!!!") _Jac.disengage(_jac_here_) diff --git a/jac/examples/reference/match_class_patterns.py b/jac/examples/reference/match_class_patterns.py index e1128d7719..15f3590693 100644 --- a/jac/examples/reference/match_class_patterns.py +++ b/jac/examples/reference/match_class_patterns.py @@ -3,9 +3,18 @@ from dataclasses import dataclass as dataclass +# Since the Animal class cannot be inherit from object, (cause the base class will be changed at run time) +# we need a base class. +# +# reference: https://stackoverflow.com/a/9639512/10846399 +# +class Base: + pass + + @Jac.make_obj(on_entry=[], on_exit=[]) @dataclass(eq=False) -class Point: +class Point(Base): x: float y: float diff --git a/jac/examples/reference/special_comprehensions.py b/jac/examples/reference/special_comprehensions.py index 10472bca99..488a199e1a 100644 --- a/jac/examples/reference/special_comprehensions.py +++ b/jac/examples/reference/special_comprehensions.py @@ -4,9 +4,18 @@ import random +# Since the Animal class cannot be inherit from object, (cause the base class will be changed at run time) +# we need a base class. +# +# reference: https://stackoverflow.com/a/9639512/10846399 +# +class Base: + pass + + @Jac.make_obj(on_entry=[], on_exit=[]) @dataclass(eq=False) -class TestObj: +class TestObj(Base): x: int = Jac.has_instance_default(gen_func=lambda: random.randint(0, 15)) y: int = Jac.has_instance_default(gen_func=lambda: random.randint(0, 15)) z: int = Jac.has_instance_default(gen_func=lambda: random.randint(0, 15)) @@ -23,7 +32,7 @@ class TestObj: @Jac.make_obj(on_entry=[], on_exit=[]) @dataclass(eq=False) -class MyObj: +class MyObj(Base): apple: int = Jac.has_instance_default(gen_func=lambda: 0) banana: int = Jac.has_instance_default(gen_func=lambda: 0) diff --git a/jac/examples/reference/visit_statements.py b/jac/examples/reference/visit_statements.py index 7a7a53363f..3bb6a35580 100644 --- a/jac/examples/reference/visit_statements.py +++ b/jac/examples/reference/visit_statements.py @@ -2,8 +2,17 @@ from jaclang.plugin.feature import JacFeature as _Jac +# Since the Animal class cannot be inherit from object, (cause the base class will be changed at run time) +# we need a base class. +# +# reference: https://stackoverflow.com/a/9639512/10846399 +# +class Base: + pass + + @_Jac.make_walker(on_entry=[_Jac.DSFunc("travel", _Jac.get_root_type())], on_exit=[]) -class Visitor: +class Visitor(Base): def travel(self, _jac_here_: _Jac.get_root_type()) -> None: if _Jac.visit_node( self, _Jac.edge_ref(_jac_here_, None, _Jac.EdgeDir.OUT, None, None) @@ -14,7 +23,7 @@ def travel(self, _jac_here_: _Jac.get_root_type()) -> None: @_Jac.make_node(on_entry=[_Jac.DSFunc("speak", Visitor)], on_exit=[]) -class item: +class item(Base): def speak(self, _jac_here_: Visitor) -> None: print("Hey There!!!") diff --git a/jac/jaclang/__init__.py b/jac/jaclang/__init__.py index 157fe1ab21..7af2354e7b 100644 --- a/jac/jaclang/__init__.py +++ b/jac/jaclang/__init__.py @@ -1,12 +1,355 @@ """The Jac Programming Language.""" +import inspect +import types +import typing +from abc import ABC, ABCMeta, abstractmethod +from dataclasses import dataclass, field as dc_field +from typing import Any, Callable, ClassVar, Dict, Tuple, Type, TypeVar, override + +from jaclang.plugin.builtin import dotgen, jid # noqa: F401 from jaclang.plugin.default import JacFeatureImpl -from jaclang.plugin.feature import JacFeature, plugin_manager +from jaclang.plugin.feature import JacFeature as _Jac, plugin_manager +from jaclang.plugin.spec import EdgeDir, Root +from jaclang.runtimelib.architype import Root as RootType + +__all__ = [ + "JacObj", + "JacWalker", + "JacNode", + "JacEdge", + "EdgeDir", + "RootType", + "jac_import", + "with_entry", + "with_exit", + "jac_test", + "abstract", + "override", + "field", + "static", + "root", + # Builtin-functions. + "dotgen", + "jid", + "jobj", + # This is not part of the jaclib for a python user but used internally to generate by jac compiler. + "_impl_patch_filename", +] -jac_import = JacFeature.jac_import +# ---------------------------------------------------------------------------- +# Plugin Initialization. +# ---------------------------------------------------------------------------- plugin_manager.register(JacFeatureImpl) plugin_manager.load_setuptools_entrypoints("jac") -__all__ = ["jac_import"] +T = TypeVar("T") + +# ---------------------------------------------------------------------------- +# Meta classes. +# ---------------------------------------------------------------------------- + + +# https://stackoverflow.com/a/9639512/10846399 +class _JacArchiTypeBase: + pass + + +class JacMetaCommon(ABCMeta): + """Common metaclass for Jac types.""" + + def __new__( # noqa: D102 + cls, + name: str, + bases: Tuple[Type, ...], + dct: Dict[str, Any], + make_func: Callable[[list, list], Callable[[type], type]], + ) -> "JacMetaCommon": + + # We have added this "__init__" to the jac base class just to make the type checkers happy. + # Actually the dataclass decorator will create an __init__ function and assign it here bellow. + if bases == (_JacArchiTypeBase,) and "__init__" in dct: + del dct["__init__"] + + on_entry, on_exit = [], [] + for value in dct.values(): + if hasattr(value, "__jac_entry"): + entry_node = getattr(value, "__jac_entry") # noqa: B009 + on_entry.append(_Jac.DSFunc(value.__name__, entry_node)) + if hasattr(value, "__jac_exit"): + exit_node = getattr(value, "__jac_exit") # noqa: B009 + on_exit.append(_Jac.DSFunc(value.__name__, exit_node)) + + inst = super().__new__(cls, name, bases, dct) + inst = dataclass(eq=False)(inst) # type: ignore [arg-type, assignment] + inst = make_func(on_entry, on_exit)(inst) # type: ignore [assignment] + return inst + + +class JacMetaObj(JacMetaCommon, ABC): # noqa: D101 + def __new__( # noqa: D102 + cls, name: str, bases: Tuple[Type, ...], dct: Dict[str, Any] + ) -> "JacMetaCommon": + return super().__new__(cls, name, bases, dct, _Jac.make_obj) + + +class JacMetaWalker(JacMetaCommon, ABC): # noqa: D101 + def __new__( # noqa: D102 + cls, name: str, bases: Tuple[Type, ...], dct: Dict[str, Any] + ) -> "JacMetaCommon": + return super().__new__(cls, name, bases, dct, _Jac.make_walker) + + +class JacMetaNode(JacMetaCommon, ABC): # noqa: D101 + def __new__( # noqa: D102 + cls, name: str, bases: Tuple[Type, ...], dct: Dict[str, Any] + ) -> "JacMetaCommon": + return super().__new__(cls, name, bases, dct, _Jac.make_node) + + +class JacMetaEdge(JacMetaCommon, ABC): # noqa: D101 + def __new__( # noqa: D102 + cls, name: str, bases: Tuple[Type, ...], dct: Dict[str, Any] + ) -> "JacMetaCommon": + return super().__new__(cls, name, bases, dct, _Jac.make_edge) + + +# ---------------------------------------------------------------------------- +# Base classes. +# ---------------------------------------------------------------------------- + + +class JacObj(_JacArchiTypeBase, metaclass=JacMetaObj): + """Base class for all the jac object types.""" + + def __init__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 + """Initialize Jac architype base.""" + + +class JacWalker(_JacArchiTypeBase, metaclass=JacMetaWalker): + """Base class for all the jac walker types.""" + + def __init__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 + """Initialize Jac architype base.""" + + def spawn(self, node: "JacNode") -> "JacWalker": + """Spawn a new node from the walker.""" + return _Jac.spawn_call(self, node) # type: ignore [arg-type, return-value] + + def ignore( + self, + expr: """( + list[JacNode | JacEdge] + | JacNodeList + | JacEdgeList + | JacNode + | JacEdge + )""", + ) -> bool: + """Ignore statement.""" + return _Jac.ignore(self, expr) # type: ignore [arg-type] + + def visit( + self, + expr: """( + Root + | list[JacNode | JacEdge] + | JacNodeList + | JacEdgeList + | JacNode + | JacEdge + )""", + ) -> bool: + """Visit statement.""" + return _Jac.visit_node(self, expr) # type: ignore [arg-type] + + def disengage(self) -> None: + """Disengage statement.""" + _Jac.disengage(self) # type: ignore [arg-type] + + +class JacNode(_JacArchiTypeBase, metaclass=JacMetaNode): + """Base class for all the jac node types.""" + + def __init__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 + """Initialize Jac architype base.""" + + def spawn(self, walker: JacWalker) -> "JacWalker": + """Spawn a new node from the walker.""" + return _Jac.spawn_call(self, walker) # type: ignore [arg-type, return-value] + + def connect( + self, + node: "JacNode | JacNodeList", + edge: "type[JacEdge] | JacEdge | None" = None, + unidir: bool = False, + conn_assign: tuple[tuple, tuple] | None = None, + edges_only: bool = False, + ) -> "JacNodeList | JacEdgeList": + """Connect the current node to another node.""" + # TODO: The above edge type should be reviewed, as the bellow can also take None, Edge, type[Edge]. + ret = _Jac.connect( + left=self, # type: ignore [arg-type] + right=node, # type: ignore [arg-type] + edge_spec=_Jac.build_edge( + is_undirected=unidir, conn_type=edge, conn_assign=conn_assign # type: ignore [arg-type] + ), + edges_only=edges_only, + ) + if edges_only: + return JacEdgeList(ret) # type: ignore [arg-type] + return JacNodeList(ret) # type: ignore [arg-type] + + def disconnect( + self, + node: "JacNode | JacNodeList", + edge: "type[JacEdge] | None" = None, + dir: EdgeDir = EdgeDir.OUT, + ) -> bool: + """Disconnect the current node from the graph.""" + filter_func = None + if edge: + filter_func = lambda edges: [ # noqa: E731 + ed for ed in edges if isinstance(ed, edge) + ] + return _Jac.disconnect(self, node, dir=dir, filter_func=filter_func) # type: ignore [arg-type] + + def refs( + self, + edge: "type[JacEdge] | None" = None, + cond: "Callable[[JacEdge], bool] | None" = None, + target: "JacNode | JacNodeList | None" = None, + dir: EdgeDir = EdgeDir.OUT, + edges_only: bool = False, + ) -> "JacNodeList | JacEdgeList": + """Return all the connected nodes / edges.""" + filter_func = ( + ( + lambda edges: ( # noqa: E731 + [ed for ed in edges if isinstance(ed, edge) if not cond or cond(ed)] + ) + ) + if edge + else None + ) + ret = plugin_manager.hook.edge_ref( + node_obj=self, + target_obj=target, + dir=dir, + filter_func=filter_func, + edges_only=edges_only, + ) + if edges_only: + return JacEdgeList(ret) + return JacNodeList(ret) + + +class JacEdge(_JacArchiTypeBase, metaclass=JacMetaEdge): + """Base class for all the jac edge types.""" + + def __init__(self, *args, **kwargs) -> None: # noqa: ANN002, ANN003 + """Initialize Jac architype base.""" + + +class JacEdgeList(list[JacEdge]): + """List of jac edges.""" + + def filter_type(self, ty: "type[JacEdge]") -> "JacEdgeList": + """Filter the list with a type.""" + return JacEdgeList([elem for elem in self if isinstance(elem, ty)]) + + def filter_func(self, fn: Callable) -> "JacEdgeList": + """Filter the list with a function.""" + return JacEdgeList([elem for elem in self if fn(elem)]) + + +class JacNodeList(list[JacNode]): + """List of jac nodes.""" + + # Reuse the methods. + connect = JacNode.connect + disconnect = JacNode.disconnect + refs = JacNode.refs + + def filter_type(self, ty: "type[JacNode]") -> "JacNodeList": + """Filter the list with a type.""" + return JacNodeList([elem for elem in self if isinstance(elem, ty)]) + + def filter_func(self, fn: Callable) -> "JacNodeList": + """Filter the list with a function.""" + return JacNodeList([elem for elem in self if fn(elem)]) + + +# ---------------------------------------------------------------------------- +# Decorators. +# ---------------------------------------------------------------------------- + + +def with_entry(func: Callable) -> Callable: + """Mark a method as jac entry with this decorator.""" + # Ensure the functioin has 2 parameters (self, here). + sig = inspect.signature(func, eval_str=True) + param_count = len(sig.parameters) + if param_count != 2: + raise ValueError("Jac entry function must have exactly 2 parameters.") + + # Get the entry node from the type hints. + second_param_name = list(sig.parameters.keys())[1] + entry_node = typing.get_type_hints(func).get(second_param_name) + + # Mark the function as jac entry. + setattr(func, "__jac_entry", entry_node) # noqa: B010 + return func + + +def with_exit(func: Callable) -> Callable: + """Mark a method as jac exit with this decorator.""" + # Ensure the functioin has 2 parameters (self, here). + sig = inspect.signature(func, eval_str=True) + param_count = len(sig.parameters) + if param_count != 2: + raise ValueError("Jac exit function must have exactly 2 parameters.") + + # Get the entry node from the type hints. + second_param_name = list(sig.parameters.keys())[1] + exit_node = typing.get_type_hints(func).get(second_param_name) + + # Mark the function as jac entry. + setattr(func, "__jac_exit", exit_node) # noqa: B010 + return func + + +# ---------------------------------------------------------------------------- +# Functions. +# ---------------------------------------------------------------------------- + + +def field( + value: T | None = None, + gen: None | Callable[[], T] = None, + postinit: bool = False, +) -> T: + """Set the default value to jac architype dataclass.""" + if postinit: + return dc_field(init=False) + if value is not None: + gen = lambda: value # noqa: E731 + assert gen is not None + return _Jac.has_instance_default(gen_func=gen) + + +static = ClassVar +abstract = abstractmethod + +jac_import = _Jac.jac_import +jac_test = _Jac.create_test +root = _Jac.get_root() +jobj = _Jac.get_object +_impl_patch_filename = _Jac.impl_patch_filename + +root.spawn = types.MethodType(JacNode.spawn, root) +root.connect = types.MethodType(JacNode.connect, root) +root.disconnect = types.MethodType(JacNode.disconnect, root) +root.refs = types.MethodType(JacNode.refs, root) diff --git a/jac/jaclang/compiler/constant.py b/jac/jaclang/compiler/constant.py index b5fd1b6f47..c5c5a3ba1e 100644 --- a/jac/jaclang/compiler/constant.py +++ b/jac/jaclang/compiler/constant.py @@ -91,7 +91,8 @@ class Constants(StrEnum): """Token constants for Jac.""" JAC_LANG_IMP = "jac" - HERE = "_jac_here_" + HERE = "here" # "_jac_here_" + JAC_CHECK = "_check" JAC_FEATURE = "_Jac" ROOT = f"{JAC_FEATURE}.get_root()" EDGES_TO_NODE = "__jac__.edges_to_nodes" diff --git a/jac/jaclang/compiler/passes/main/pyast_gen_pass.py b/jac/jaclang/compiler/passes/main/pyast_gen_pass.py index d8c9dc8f7a..ed4beb6d68 100644 --- a/jac/jaclang/compiler/passes/main/pyast_gen_pass.py +++ b/jac/jaclang/compiler/passes/main/pyast_gen_pass.py @@ -15,6 +15,17 @@ T = TypeVar("T", bound=ast3.AST) +JACLIB_IMPORT_ALL = True # TODO(thakee): Move this to settings. +JACLIB_ALIAS = "jl" # Will import jaclib as jl + +# (thakee): This is not the best place to declare it, find some other place. +# A list of jac builtin functions. These function are available directly in jac source files. +jac_builtin_funcs = [ + "dotgen", + "jid", + "jobj", +] + class PyastGenPass(Pass): """Jac blue transpilation to python pass.""" @@ -44,16 +55,41 @@ def before_pass(self) -> None: self.debuginfo: dict[str, list[str]] = {"jac_mods": []} self.already_added: list[str] = [] self.preamble: list[ast3.AST] = [ - self.sync( - ast3.ImportFrom( - module="__future__", - names=[self.sync(ast3.alias(name="annotations", asname=None))], - level=0, - ), - jac_node=self.ir, + ( + self.sync( + ast3.ImportFrom( + module="jaclang", + names=[self.sync(ast3.alias(name="*", asname=None))], + level=0, + ), + jac_node=self.ir, + ) + if JACLIB_IMPORT_ALL + else self.sync( + ast3.Import( + names=[ + self.sync(ast3.alias(name="jaclang", asname=JACLIB_ALIAS)) + ] + ), + jac_node=self.ir, + ) ), ] + if not JACLIB_IMPORT_ALL: + self.preamble += [ + self.sync( + ast3.ImportFrom( + module="jaclang", + names=[ + self.sync(ast3.alias(name=func_name)) + for func_name in jac_builtin_funcs + ], + level=0, + ) + ) + ] + def enter_node(self, node: ast.AstNode) -> None: """Enter node.""" if node.gen.py_ast: @@ -71,6 +107,18 @@ def exit_node(self, node: ast.AstNode) -> None: # if isinstance(i, ast3.AST): # i.jac_link = node + def jaclib_obj(self, obj_name: str) -> ast3.AST: + """Return the object from jaclib as ast node based on the import config.""" + if JACLIB_IMPORT_ALL: + return self.sync(ast3.Name(id=obj_name, ctx=ast3.Load())) + return self.sync( + ast3.Attribute( + value=self.sync(ast3.Name(id=JACLIB_ALIAS, ctx=ast3.Load())), + attr=obj_name, + ctx=ast3.Load(), + ) + ) + def needs_jac_import(self) -> None: """Check if import is needed.""" if self.needs_jac_import.__name__ in self.already_added: @@ -110,25 +158,6 @@ def needs_typing(self) -> None: ) self.already_added.append(self.needs_typing.__name__) - def needs_abc(self) -> None: - """Check if enum is needed.""" - if self.needs_abc.__name__ in self.already_added: - return - self.preamble.append( - self.sync( - ast3.Import( - names=[ - self.sync( - ast3.alias(name="abc", asname="_jac_abc"), - jac_node=self.ir, - ), - ] - ), - jac_node=self.ir, - ) - ) - self.already_added.append(self.needs_abc.__name__) - def needs_enum(self) -> None: """Check if enum is needed.""" if self.needs_enum.__name__ in self.already_added: @@ -138,8 +167,8 @@ def needs_enum(self) -> None: ast3.ImportFrom( module="enum", names=[ - self.sync(ast3.alias(name="Enum", asname="__jac_Enum__")), - self.sync(ast3.alias(name="auto", asname="__jac_auto__")), + self.sync(ast3.alias(name="Enum", asname=None)), + self.sync(ast3.alias(name="auto", asname=None)), ], level=0, ), @@ -178,44 +207,6 @@ def needs_jac_feature(self) -> None: ) self.already_added.append(self.needs_jac_feature.__name__) - def needs_dataclass(self) -> None: - """Check if enum is needed.""" - if self.needs_dataclass.__name__ in self.already_added: - return - self.preamble.append( - self.sync( - ast3.ImportFrom( - module="dataclasses", - names=[ - self.sync( - ast3.alias(name="dataclass", asname="__jac_dataclass__") - ), - ], - level=0, - ), - jac_node=self.ir, - ) - ) - self.already_added.append(self.needs_dataclass.__name__) - - def needs_dataclass_field(self) -> None: - """Check if enum is needed.""" - if self.needs_dataclass_field.__name__ in self.already_added: - return - self.preamble.append( - self.sync( - ast3.ImportFrom( - module="dataclasses", - names=[ - self.sync(ast3.alias(name="field", asname="__jac_field__")), - ], - level=0, - ), - jac_node=self.ir, - ) - ) - self.already_added.append(self.needs_dataclass_field.__name__) - def flatten(self, body: list[T | list[T] | None]) -> list[T]: """Flatten ast list.""" new_body = [] @@ -404,7 +395,6 @@ def exit_test(self, node: ast.Test) -> None: body: SubNodeList[CodeBlockStmt], doc: Optional[String], """ - self.needs_jac_feature() test_name = node.name.sym_name func = self.sync( ast3.FunctionDef( @@ -412,7 +402,11 @@ def exit_test(self, node: ast.Test) -> None: args=self.sync( ast3.arguments( posonlyargs=[], - args=[self.sync(ast3.arg(arg="_jac_check", annotation=None))], + args=[ + self.sync( + ast3.arg(arg=Con.JAC_CHECK.value, annotation=None) + ) + ], kwonlyargs=[], vararg=None, kwargs=None, @@ -421,17 +415,7 @@ def exit_test(self, node: ast.Test) -> None: ) ), body=self.resolve_stmt_block(node.body, doc=node.doc), - decorator_list=[ - self.sync( - ast3.Attribute( - value=self.sync( - ast3.Name(id=Con.JAC_FEATURE.value, ctx=ast3.Load()) - ), - attr="create_test", - ctx=ast3.Load(), - ) - ) - ], + decorator_list=[self.jaclib_obj("jac_test")], returns=self.sync(ast3.Constant(value=None)), type_comment=None, type_params=[], @@ -935,87 +919,10 @@ def exit_architype(self, node: ast.Architype) -> None: else [] ) - ds_on_entry, ds_on_exit = self.collect_events(node) - if node.arch_type.name != Tok.KW_CLASS: - self.needs_jac_feature() - self.needs_dataclass() - decorators.append( - self.sync( - ast3.Call( - func=self.sync( - ast3.Attribute( - value=self.sync( - ast3.Name(id=Con.JAC_FEATURE.value, ctx=ast3.Load()) - ), - attr=f"make_{node.arch_type.value}", - ctx=ast3.Load(), - ) - ), - args=[], - keywords=[ - self.sync( - ast3.keyword( - arg="on_entry", - value=self.sync( - ast3.List(elts=ds_on_entry, ctx=ast3.Load()) - ), - ) - ), - self.sync( - ast3.keyword( - arg="on_exit", - value=self.sync( - ast3.List(elts=ds_on_exit, ctx=ast3.Load()) - ), - ) - ), - ], - ) - ) - ) - decorators.append( - self.sync( - ast3.Call( - func=self.sync( - ast3.Name(id="__jac_dataclass__", ctx=ast3.Load()) - ), - args=[], - keywords=[ - self.sync( - ast3.keyword( - arg="eq", - value=self.sync( - ast3.Constant(value=False), - ), - ) - ) - ], - ) - ) - ) base_classes = node.base_classes.gen.py_ast if node.base_classes else [] if node.arch_type.name != Tok.KW_CLASS: base_classes.append( - self.sync( - ast3.Attribute( - value=self.sync( - ast3.Name(id=Con.JAC_FEATURE.value, ctx=ast3.Load()) - ), - attr=node.arch_type.value.capitalize(), - ctx=ast3.Load(), - ) - ) - ) - if node.is_abstract: - self.needs_abc() - base_classes.append( - self.sync( - ast3.Attribute( - value=self.sync(ast3.Name(id="_jac_abc", ctx=ast3.Load())), - attr="ABC", - ctx=ast3.Load(), - ) - ) + self.jaclib_obj("Jac" + node.arch_type.value.capitalize()) ) node.gen.py_ast = [ self.sync( @@ -1030,42 +937,6 @@ def exit_architype(self, node: ast.Architype) -> None: ) ] - def collect_events( - self, node: ast.Architype - ) -> tuple[list[ast3.AST], list[ast3.AST]]: - """Collect events.""" - ds_on_entry: list[ast3.AST] = [] - ds_on_exit: list[ast3.AST] = [] - for i in ( - node.body.body.items - if isinstance(node.body, ast.ArchDef) - else node.body.items if node.body else [] - ): - if isinstance(i, ast.Ability) and isinstance( - i.signature, ast.EventSignature - ): - func_spec = self.sync( - ast3.Call( - func=self.sync( - ast3.Attribute( - value=self.sync( - ast3.Name(id=Con.JAC_FEATURE.value, ctx=ast3.Load()) - ), - attr="DSFunc", - ctx=ast3.Load(), - ) - ), - args=[self.sync(ast3.Constant(value=i.sym_name))], - keywords=[], - ) - ) - ( - ds_on_entry.append(func_spec) - if i.signature.event.name == Tok.KW_ENTRY - else ds_on_exit.append(func_spec) - ) - return ds_on_entry, ds_on_exit - def exit_arch_def(self, node: ast.ArchDef) -> None: """Sub objects. @@ -1110,9 +981,7 @@ def exit_enum(self, node: ast.Enum) -> None: ) base_classes = node.base_classes.gen.py_ast if node.base_classes else [] if isinstance(base_classes, list): - base_classes.append( - self.sync(ast3.Name(id="__jac_Enum__", ctx=ast3.Load())) - ) + base_classes.append(self.sync(ast3.Name(id="Enum", ctx=ast3.Load()))) else: raise self.ice() node.gen.py_ast = [ @@ -1207,56 +1076,30 @@ def exit_ability(self, node: ast.Ability) -> None: node, ) decorator_list = node.decorators.gen.py_ast if node.decorators else [] - if isinstance(node.body, ast.AstImplOnlyNode): - self.needs_jac_feature() + if isinstance(node.signature, ast.EventSignature): decorator_list.append( - self.sync( - ast3.Call( - func=self.sync( - ast3.Attribute( - value=self.sync( - ast3.Name(id=Con.JAC_FEATURE.value, ctx=ast3.Load()) - ), - attr="impl_patch_filename", - ctx=ast3.Load(), - ) - ), - args=[], - keywords=[ - self.sync( - ast3.keyword( - arg="file_loc", - value=self.sync( - ast3.Constant(value=node.body.loc.mod_path) - ), - ) - ), - ], - ) + self.jaclib_obj( + "with_entry" + if node.signature.event.name == Tok.KW_ENTRY + else "with_exit" ) ) - if node.is_abstract: - self.needs_abc() + + # TODO(thakee): Check the bellow code? + if isinstance(node.body, ast.AstImplOnlyNode): decorator_list.append( self.sync( - ast3.Attribute( - value=self.sync(ast3.Name(id="_jac_abc", ctx=ast3.Load())), - attr="abstractmethod", - ctx=ast3.Load(), + ast3.Call( + func=self.jaclib_obj("_impl_patch_filename"), + args=[self.sync(ast3.Constant(value=node.body.loc.mod_path))], + keywords=[], ) ) ) + if node.is_abstract: + decorator_list.append(self.jaclib_obj("abstract")) if node.is_override: - self.needs_typing() - decorator_list.append( - self.sync( - ast3.Attribute( - value=self.sync(ast3.Name(id="_jac_typ", ctx=ast3.Load())), - attr="override", - ctx=ast3.Load(), - ) - ) - ) + decorator_list.append(self.jaclib_obj("override")) if node.is_static: decorator_list.insert( 0, self.sync(ast3.Name(id="staticmethod", ctx=ast3.Load())) @@ -1381,18 +1224,9 @@ def exit_arch_ref(self, node: ast.ArchRef) -> None: isinstance(node.arch_name, ast.SpecialVarRef) and node.arch_name.orig.name == Tok.KW_ROOT ): - node.gen.py_ast = [ - self.sync( - ast3.Attribute( - value=self.sync( - ast3.Name(id=Con.JAC_FEATURE.value, ctx=ast3.Load()) - ), - attr="RootType", - ctx=ast3.Load(), - ) - ) - ] + node.gen.py_ast = [self.jaclib_obj("RootType")] else: + # TODO (thakee): Check what is this? self.needs_typing() node.gen.py_ast = [ self.sync( @@ -1498,25 +1332,15 @@ def exit_has_var(self, node: ast.HasVar) -> None: ) ) if is_static_var: - self.needs_typing() annotation = self.sync( ast3.Subscript( - value=self.sync( - ast3.Attribute( - value=self.sync(ast3.Name(id="_jac_typ", ctx=ast3.Load())), - attr="ClassVar", - ctx=ast3.Load(), - ) - ), + value=self.jaclib_obj("static"), slice=annotation, ctx=ast3.Load(), ) ) - ( - self.needs_dataclass_field() - if node.defer and not (is_static_var or is_in_class) - else None - ) + + default_field_fn_name = "field" node.gen.py_ast = [ ( self.sync( @@ -1526,42 +1350,45 @@ def exit_has_var(self, node: ast.HasVar) -> None: value=( self.sync( ast3.Call( - func=self.sync( - ast3.Attribute( - value=self.sync( - ast3.Name( - id=Con.JAC_FEATURE.value, - ctx=ast3.Load(), - ) - ), - attr="has_instance_default", - ctx=ast3.Load(), + func=self.jaclib_obj(default_field_fn_name), + args=( + [node.value.gen.py_ast[0]] + if isinstance( + node.value.gen.py_ast[0], ast3.Constant ) + else [] ), - args=[], - keywords=[ - self.sync( - ast3.keyword( - arg="gen_func", - value=self.sync( - ast3.Lambda( - args=self.sync( - ast3.arguments( - posonlyargs=[], - args=[], - kwonlyargs=[], - vararg=None, - kwargs=None, - kw_defaults=[], - defaults=[], - ) - ), - body=node.value.gen.py_ast[0], - ) - ), + keywords=( + [ + self.sync( + ast3.keyword( + arg="gen", + value=self.sync( + ast3.Lambda( + args=self.sync( + ast3.arguments( + posonlyargs=[], + args=[], + kwonlyargs=[], + vararg=None, + kwargs=None, + kw_defaults=[], + defaults=[], + ) + ), + body=node.value.gen.py_ast[ + 0 + ], + ) + ), + ) ) + ] + if not isinstance( + node.value.gen.py_ast[0], ast3.Constant ) - ], + else [] + ), ) ) if node.value @@ -1569,19 +1396,14 @@ def exit_has_var(self, node: ast.HasVar) -> None: else ( self.sync( ast3.Call( - func=self.sync( - ast3.Name( - id="__jac_field__", - ctx=ast3.Load(), - ) - ), + func=self.jaclib_obj(default_field_fn_name), args=[], keywords=[ self.sync( ast3.keyword( - arg="init", + arg="postinit", value=self.sync( - ast3.Constant(value=False) + ast3.Constant(value=True) ), ) ) @@ -1983,16 +1805,16 @@ def check_node_isinstance_call( for param in node.target.params.items: assert_args_list.append(param.gen.py_ast[0]) - # assert_func_expr = "_jac_check.assertXXX" + # assert_func_expr = "Con.JAC_CHECK.value.assertXXX" assert_func_expr: ast3.Attribute = self.sync( ast3.Attribute( - value=self.sync(ast3.Name(id="_jac_check", ctx=ast3.Load())), + value=self.sync(ast3.Name(id=Con.JAC_CHECK.value, ctx=ast3.Load())), attr=assert_func_name, ctx=ast3.Load(), ) ) - # assert_call_expr = "(_jac_check.assertXXX)(args)" + # assert_call_expr = "(Con.JAC_CHECK.value.assertXXX)(args)" assert_call_expr: ast3.Call = self.sync( ast3.Call(func=assert_func_expr, args=assert_args_list, keywords=[]) ) @@ -2092,11 +1914,12 @@ def exit_ignore_stmt(self, node: ast.IgnoreStmt) -> None: target: ExprType, """ - loc = self.sync( + walker = self.sync( ast3.Name(id="self", ctx=ast3.Load()) if node.from_walker else ast3.Name(id=Con.HERE.value, ctx=ast3.Load()) ) + node.gen.py_ast = [ self.sync( ast3.Expr( @@ -2104,16 +1927,12 @@ def exit_ignore_stmt(self, node: ast.IgnoreStmt) -> None: ast3.Call( func=self.sync( ast3.Attribute( - value=self.sync( - ast3.Name( - id=Con.JAC_FEATURE.value, ctx=ast3.Load() - ) - ), + value=walker, attr="ignore", ctx=ast3.Load(), ) ), - args=[loc, node.target.gen.py_ast[0]], + args=[node.target.gen.py_ast[0]], keywords=[], ) ) @@ -2133,29 +1952,37 @@ def exit_visit_stmt(self, node: ast.VisitStmt) -> None: if node.from_walker else ast3.Name(id=Con.HERE.value, ctx=ast3.Load()) ) + + visit_call = self.sync( + ast3.Call( + func=self.sync( + ast3.Attribute( + value=loc, + attr="visit", + ctx=ast3.Load(), + ) + ), + args=[node.target.gen.py_ast[0]], + keywords=[], + ) + ) + node.gen.py_ast = [ - self.sync( - ast3.If( - test=self.sync( - ast3.Call( - func=self.sync( - ast3.Attribute( - value=self.sync( - ast3.Name( - id=Con.JAC_FEATURE.value, ctx=ast3.Load() - ) - ), - attr="visit_node", - ctx=ast3.Load(), - ) - ), - args=[loc, node.target.gen.py_ast[0]], - keywords=[], - ) - ), - body=[self.sync(ast3.Pass())], - orelse=node.else_body.gen.py_ast if node.else_body else [], + ( + self.sync( + ast3.If( + test=self.sync( + ast3.UnaryOp( + op=self.sync(ast3.Not()), + operand=visit_call, + ) + ), + body=node.else_body.gen.py_ast, + orelse=[], + ) ) + if node.else_body + else self.sync(ast3.Expr(value=visit_call)) ) ] @@ -2179,30 +2006,24 @@ def exit_disengage_stmt(self, node: ast.DisengageStmt) -> None: ) node.gen.py_ast = [ self.sync( - ast3.Expr( - value=self.sync( + ast3.Return( + self.sync( self.sync( ast3.Call( func=self.sync( ast3.Attribute( - value=self.sync( - ast3.Name( - id=Con.JAC_FEATURE.value, - ctx=ast3.Load(), - ) - ), + value=loc, attr="disengage", ctx=ast3.Load(), ) ), - args=[loc], + args=[], keywords=[], ) ) ) ) ), - self.sync(ast3.Return()), ] def exit_await_expr(self, node: ast.AwaitExpr) -> None: @@ -2256,7 +2077,7 @@ def exit_assignment(self, node: ast.Assignment) -> None: else ( self.sync( ast3.Call( - func=self.sync(ast3.Name(id="__jac_auto__", ctx=ast3.Load())), + func=self.sync(ast3.Name(id="auto", ctx=ast3.Load())), args=[], keywords=[], ) @@ -2299,91 +2120,120 @@ def exit_binary_expr(self, node: ast.BinaryExpr) -> None: op: Token | DisconnectOp | ConnectOp, """ if isinstance(node.op, ast.ConnectOp): + + left = ( + node.right.gen.py_ast[0] + if node.op.edge_dir == EdgeDir.IN + else node.left.gen.py_ast[0] + ) + right = ( + node.left.gen.py_ast[0] + if node.op.edge_dir == EdgeDir.IN + else node.right.gen.py_ast[0] + ) + conn_type = ( + node.op.conn_type.gen.py_ast[0] + if node.op.conn_type + else self.sync(ast3.Constant(value=None)) + ) + undir = self.sync(ast3.Constant(value=node.op.edge_dir == EdgeDir.ANY)) + conn_assign = ( + node.op.conn_assign.gen.py_ast[0] + if node.op.conn_assign + else self.sync(ast3.Constant(value=None)) + ) + + keywords = [] + if not isinstance(conn_type, ast3.Constant) or conn_type.value is not None: + keywords.append( + self.sync( + ast3.keyword( + arg="edge", + value=conn_type, + ) + ) + ) + if undir.value: # default is Fault. + keywords.append( + self.sync( + ast3.keyword( + arg="undir", + value=undir, + ) + ) + ) + if ( + not isinstance(conn_assign, ast3.Constant) + or conn_assign.value is not None + ): + keywords.append( + self.sync( + ast3.keyword( + arg="conn_assign", + value=conn_assign, + ) + ) + ) + node.gen.py_ast = [ self.sync( ast3.Call( func=self.sync( ast3.Attribute( - value=self.sync( - ast3.Name(id=Con.JAC_FEATURE.value, ctx=ast3.Load()) - ), + value=left, attr="connect", ctx=ast3.Load(), - ) - ), - args=[], - keywords=[ - self.sync( - ast3.keyword( - arg="left", - value=( - node.right.gen.py_ast[0] - if node.op.edge_dir == EdgeDir.IN - else node.left.gen.py_ast[0] - ), - ) ), - self.sync( - ast3.keyword( - arg="right", - value=( - node.left.gen.py_ast[0] - if node.op.edge_dir == EdgeDir.IN - else node.right.gen.py_ast[0] - ), - ) + ), + args=[right], + keywords=keywords, + ) + ) + ] + + elif isinstance(node.op, ast.DisconnectOp): + keywords = [] + + if node.op.edge_spec.filter_cond and node.op.edge_spec.filter_cond.f_type: + keywords.append( + self.sync( + ast3.keyword( + arg="edge", + value=self.sync( + node.op.edge_spec.filter_cond.f_type.gen.py_ast[0] ), - self.sync( - ast3.keyword( - arg="edge_spec", - value=node.op.gen.py_ast[0], + ) + ) + ) + + if node.op.edge_spec.edge_dir != EdgeDir.OUT: + keywords.append( + self.sync( + ast3.keyword( + arg="dir", + value=self.sync( + ast3.Attribute( + value=self.jaclib_obj("EdgeDir"), + attr=node.op.edge_spec.edge_dir.name, + ctx=ast3.Load(), ) ), - ], + ) ) ) - ] - elif isinstance(node.op, ast.DisconnectOp): + node.gen.py_ast = [ self.sync( ast3.Call( func=self.sync( ast3.Attribute( - value=self.sync( - ast3.Name(id=Con.JAC_FEATURE.value, ctx=ast3.Load()) - ), + value=node.left.gen.py_ast[0], attr="disconnect", ctx=ast3.Load(), ) ), - args=[ - node.left.gen.py_ast[0], - node.right.gen.py_ast[0], - self.sync( - ast3.Attribute( - value=self.sync( - ast3.Attribute( - value=self.sync( - ast3.Name( - id=Con.JAC_FEATURE.value, - ctx=ast3.Load(), - ) - ), - attr="EdgeDir", - ctx=ast3.Load(), - ) - ), - attr=node.op.edge_spec.edge_dir.name, - ctx=ast3.Load(), - ) - ), - ( - node.op.edge_spec.filter_cond.gen.py_ast[0] - if node.op.edge_spec.filter_cond is not None - else self.sync(ast3.Constant(value=None)) - ), - ], - keywords=[], + args=[node.right.gen.py_ast[0]], + keywords=keywords, ) ) ] @@ -2444,20 +2294,17 @@ def translate_jac_bin_op(self, node: ast.BinaryExpr) -> list[ast3.AST]: self.exit_func_call(func_node) return func_node.gen.py_ast elif node.op.name in [Tok.KW_SPAWN]: - self.needs_jac_feature() return [ self.sync( ast3.Call( func=self.sync( ast3.Attribute( - value=self.sync( - ast3.Name(id=Con.JAC_FEATURE.value, ctx=ast3.Load()) - ), - attr="spawn_call", + value=node.left.gen.py_ast[0], + attr="spawn", ctx=ast3.Load(), ) ), - args=[node.left.gen.py_ast[0], node.right.gen.py_ast[0]], + args=[node.right.gen.py_ast[0]], keywords=[], ) ) @@ -2638,15 +2485,7 @@ def exit_unary_expr(self, node: ast.UnaryExpr) -> None: node.gen.py_ast = [ self.sync( ast3.Call( - func=self.sync( - ast3.Attribute( - value=self.sync( - ast3.Name(id=Con.JAC_FEATURE.value, ctx=ast3.Load()) - ), - attr="get_object", - ctx=ast3.Load(), - ) - ), + func=self.jaclib_obj("jobj"), args=[], keywords=[ self.sync( @@ -2933,11 +2772,18 @@ def exit_atom_trailer(self, node: ast.AtomTrailer) -> None: else: self.error("Invalid attribute access") elif isinstance(node.right, ast.FilterCompr): + assert node.right.f_type is not None # (thakee): Could it be none? node.gen.py_ast = [ self.sync( ast3.Call( - func=node.right.gen.py_ast[0], - args=[node.target.gen.py_ast[0]], + func=self.sync( + ast3.Attribute( + value=node.target.gen.py_ast[0], + attr="filter_type", + ctx=ast3.Load(), + ) + ), + args=[node.right.f_type.gen.py_ast[0]], keywords=[], ) ) @@ -3141,26 +2987,7 @@ def exit_special_var_ref(self, node: ast.SpecialVarRef) -> None: ) ] elif node.name == Tok.KW_ROOT: - node.gen.py_ast = [ - self.sync( - ast3.Call( - func=self.sync( - ast3.Attribute( - value=self.sync( - ast3.Name( - id=Con.JAC_FEATURE.value, - ctx=ast3.Load(), - ) - ), - attr="get_root", - ctx=ast3.Load(), - ) - ), - args=[], - keywords=[], - ) - ) - ] + node.gen.py_ast = [self.jaclib_obj("root")] else: node.gen.py_ast = [ @@ -3200,13 +3027,119 @@ def exit_edge_ref_trailer(self, node: ast.EdgeRefTrailer) -> None: edges_only=node.edges_only and cur == last_edge, ) if next_i and isinstance(next_i, ast.FilterCompr): - pynode = self.sync( - ast3.Call( - func=next_i.gen.py_ast[0], - args=[pynode], - keywords=[], + if next_i.f_type: + pynode = self.sync( + ast3.Call( + func=self.sync( + ast3.Attribute( + value=pynode, + attr="filter_type", + ctx=ast3.Load(), + ) + ), + args=[next_i.f_type.gen.py_ast[0]], + keywords=[], + ) + ) + elif next_i.compares: + iter_name = "item" # (thakee): I don't know what to name. + expr: ast3.expr | None = None + comp = next_i.compares.items[0] + if ( + len(next_i.compares.items) == 1 + and isinstance(comp.gen.py_ast[0], ast3.Compare) + and isinstance(comp.gen.py_ast[0].left, ast3.Name) + ): + expr = self.sync( + ast3.Compare( + left=self.sync( + ast3.Attribute( + value=self.sync( + ast3.Name( + id=iter_name, + ctx=ast3.Load(), + ), + jac_node=comp, + ), + attr=comp.gen.py_ast[0].left.id, + ctx=ast3.Load(), + ), + jac_node=comp, + ), + ops=comp.gen.py_ast[0].ops, + comparators=comp.gen.py_ast[0].comparators, + ), + jac_node=comp, + ) + else: + expr = self.sync( + ast3.BoolOp( + op=self.sync(ast3.And()), + values=[ + self.sync( + ast3.Compare( + left=self.sync( + ast3.Attribute( + value=self.sync( + ast3.Name( + id=iter_name, + ctx=ast3.Load(), + ), + jac_node=comp, + ), + attr=comp.gen.py_ast[0].left.id, + ctx=ast3.Load(), + ), + jac_node=comp, + ), + ops=comp.gen.py_ast[0].ops, + comparators=comp.gen.py_ast[ + 0 + ].comparators, + ), + jac_node=comp, + ) + for comp in next_i.compares.items + if isinstance(comp.gen.py_ast[0], ast3.Compare) + and isinstance( + comp.gen.py_ast[0].left, ast3.Name + ) + ], + ), + ) + assert expr is not None + pynode = self.sync( + ast3.Call( + func=self.sync( + ast3.Attribute( + value=pynode, + attr="filter_func", + ctx=ast3.Load(), + ) + ), + args=[ + self.sync( + ast3.Lambda( + args=self.sync( + ast3.arguments( + posonlyargs=[], + args=[ + self.sync( + ast3.arg(arg=iter_name) + ) + ], + kwonlyargs=[], + kw_defaults=[], + defaults=[], + ) + ), + body=expr, + ) + ) + ], + keywords=[], + ) ) - ) chomp = chomp[1:] if next_i else chomp elif isinstance(cur, ast.EdgeOpRef) and isinstance(next_i, ast.EdgeOpRef): pynode = self.translate_edge_op_ref( @@ -3238,71 +3171,145 @@ def translate_edge_op_ref( self, loc: ast3.AST, node: ast.EdgeOpRef, - targ: Optional[ast3.AST], + targ: ast3.AST | None, edges_only: bool, ) -> ast3.AST: """Generate ast for edge op ref call.""" - return self.sync( - ast3.Call( - func=self.sync( - ast3.Attribute( - value=self.sync( - ast3.Name(id=Con.JAC_FEATURE.value, ctx=ast3.Load()) - ), - attr="edge_ref", - ctx=ast3.Load(), - ) - ), - args=[loc], - keywords=[ - self.sync( - ast3.keyword( - arg="target_obj", - value=( - targ if targ else self.sync(ast3.Constant(value=None)) - ), - ) - ), - self.sync( - ast3.keyword( - arg="dir", - value=self.sync( + args = [] + keywords = [] + + if node.filter_cond and node.filter_cond.f_type: + args.append(self.sync(node.filter_cond.f_type.gen.py_ast[0])) + + edge_iter_name = "edge" + if node.filter_cond.compares: + + expr: ast3.expr | None = None + comp = node.filter_cond.compares.items[0] + if ( + len(node.filter_cond.compares.items) == 1 + and isinstance(comp.gen.py_ast[0], ast3.Compare) + and isinstance(comp.gen.py_ast[0].left, ast3.Name) + ): + expr = self.sync( + ast3.Compare( + left=self.sync( ast3.Attribute( value=self.sync( - ast3.Attribute( - value=self.sync( - ast3.Name( - id=Con.JAC_FEATURE.value, - ctx=ast3.Load(), - ) - ), - attr="EdgeDir", + ast3.Name( + id=edge_iter_name, ctx=ast3.Load(), - ) + ), + jac_node=comp, ), - attr=node.edge_dir.name, + attr=comp.gen.py_ast[0].left.id, ctx=ast3.Load(), - ) + ), + jac_node=comp, ), - ) - ), + ops=comp.gen.py_ast[0].ops, + comparators=comp.gen.py_ast[0].comparators, + ), + jac_node=comp, + ) + else: + expr = self.sync( + ast3.BoolOp( + op=self.sync(ast3.And()), + values=[ + self.sync( + ast3.Compare( + left=self.sync( + ast3.Attribute( + value=self.sync( + ast3.Name( + id=edge_iter_name, + ctx=ast3.Load(), + ), + jac_node=comp, + ), + attr=comp.gen.py_ast[0].left.id, + ctx=ast3.Load(), + ), + jac_node=comp, + ), + ops=comp.gen.py_ast[0].ops, + comparators=comp.gen.py_ast[0].comparators, + ), + jac_node=comp, + ) + for comp in node.filter_cond.compares.items + if isinstance(comp.gen.py_ast[0], ast3.Compare) + and isinstance(comp.gen.py_ast[0].left, ast3.Name) + ], + ), + ) + assert expr is not None + + args.append( self.sync( - ast3.keyword( - arg="filter_func", - value=self.sync( - node.filter_cond.gen.py_ast[0] - if node.filter_cond - else self.sync(ast3.Constant(value=None)) + ast3.Lambda( + args=self.sync( + ast3.arguments( + posonlyargs=[], + args=[self.sync(ast3.arg(arg=edge_iter_name))], + kwonlyargs=[], + kw_defaults=[], + defaults=[], + ) ), + body=expr, ) - ), - self.sync( - ast3.keyword( - arg="edges_only", - value=self.sync(ast3.Constant(value=edges_only)), - ) - ), - ], + ) + ) + + if targ is not None: + keywords.append( + self.sync( + ast3.keyword( + arg="target", + value=self.sync(ast3.Constant(value=None)), + ) + ) + ) + + if node.edge_dir != EdgeDir.OUT: + keywords.append( + self.sync( + ast3.keyword( + arg="dir", + value=self.sync( + ast3.Attribute( + value=self.jaclib_obj("EdgeDir"), + attr=node.edge_dir.name, + ctx=ast3.Load(), + ) + ), + ) + ) + ) + + if edges_only: + keywords.append( + self.sync( + ast3.keyword( + arg="edges_only", + value=self.sync(ast3.Constant(value=edges_only)), + ) + ) + ) + + return self.sync( + ast3.Call( + func=self.sync( + ast3.Attribute( + value=loc, + attr="refs", + ctx=ast3.Load(), + ) + ), + args=args, + keywords=keywords, ) ) @@ -3372,6 +3379,7 @@ def exit_filter_compr(self, node: ast.FilterCompr) -> None: compares: SubNodeList[BinaryExpr], """ + # FIXME (thakee): Do I have to remove this? node.gen.py_ast = [ self.sync( ast3.Lambda( diff --git a/jac/jaclang/compiler/passes/main/tests/test_import_pass.py b/jac/jaclang/compiler/passes/main/tests/test_import_pass.py index 5d04100292..97977e1db2 100644 --- a/jac/jaclang/compiler/passes/main/tests/test_import_pass.py +++ b/jac/jaclang/compiler/passes/main/tests/test_import_pass.py @@ -81,10 +81,15 @@ def test_py_raise_map(self) -> None: "genericpath": r"jaclang/vendor/mypy/typeshed/stdlib/genericpath.pyi$", } for i in p: +<<<<<<< HEAD self.assertIn(i, build.ir.py_info.py_raise_map) self.assertRegex( re.sub(r".*fixtures/", "", build.ir.py_info.py_raise_map[i]), p[i] ) +======= + self.assertIn(i, build.ir.py_raise_map) + self.assertRegex(re.sub(r".*fixtures/", "", build.ir.py_raise_map[i]).replace("\\", "/"), p[i]) +>>>>>>> 64d095e50 (jac2py codegen refactored) def test_py_raised_mods(self) -> None: """Basic test for pass.""" diff --git a/jac/jaclang/plugin/default.py b/jac/jaclang/plugin/default.py index 7f43232de2..f68143b589 100644 --- a/jac/jaclang/plugin/default.py +++ b/jac/jaclang/plugin/default.py @@ -662,11 +662,15 @@ def make_architype( if not hasattr(cls, "_jac_entry_funcs_") or not hasattr( cls, "_jac_exit_funcs_" ): - # Saving the module path and reassign it after creating cls - # So the jac modules are part of the correct module - cur_module = cls.__module__ - cls = type(cls.__name__, (cls, arch_base), {}) - cls.__module__ = cur_module + # If a class only inherit from object (ie. Doesn't inherit from a class), we cannot modify + # the __bases__ property of it, so it's necessary to make sure the class is not a direct child of object. + assert cls.__bases__ != (object,) + bases = ( + (cls.__bases__ + (arch_base,)) + if arch_base not in cls.__bases__ + else cls.__bases__ + ) + cls.__bases__ = bases cls._jac_entry_funcs_ = on_entry # type: ignore cls._jac_exit_funcs_ = on_exit # type: ignore else: diff --git a/jac/jaclang/runtimelib/architype.py b/jac/jaclang/runtimelib/architype.py index 25c19a7d88..a93d833674 100644 --- a/jac/jaclang/runtimelib/architype.py +++ b/jac/jaclang/runtimelib/architype.py @@ -8,7 +8,7 @@ from functools import cached_property from logging import getLogger from pickle import dumps -from types import UnionType +from types import MethodType, UnionType from typing import Any, Callable, ClassVar, Optional, TypeVar from uuid import UUID, uuid4 @@ -297,12 +297,21 @@ class GenericEdge(EdgeArchitype): class Root(NodeArchitype): """Generic Root Node.""" + # We define the 'spawn' and 'connect' here which will be added to the root instance + # as method bound. This slots definition here will allow the type checker to + # assign dynamic attributes. + __slots__ = ("__jac__", "spawn", "connect", "disconnect", "refs") + _jac_entry_funcs_: ClassVar[list[DSFunc]] = [] _jac_exit_funcs_: ClassVar[list[DSFunc]] = [] def __init__(self) -> None: """Create root node.""" self.__jac__ = NodeAnchor(architype=self, persistent=True, edges=[]) + self.spawn: MethodType = MethodType(lambda _: None, self) + self.connect: MethodType = MethodType(lambda _: None, self) + self.disconnect: MethodType = MethodType(lambda _: None, self) + self.refs: MethodType = MethodType(lambda _: None, self) @dataclass(eq=False) @@ -329,3 +338,28 @@ def trigger(self) -> type | UnionType | tuple[type | UnionType, ...] | None: def resolve(self, cls: type) -> None: """Resolve the function.""" self.func = getattr(cls, self.name) +<<<<<<< HEAD +======= + + def get_funcparam_annotations( + self, func: Callable[[Any, Any], Any] | None + ) -> type | UnionType | tuple[type | UnionType, ...] | None: + """Get function parameter annotations.""" + if not func: + return None + + sig = inspect.signature(func, eval_str=True) + param_count = len(sig.parameters) + + if param_count < 2: + return None + + second_param_name = list(sig.parameters.keys())[1] # "_jac_here_" + + annotation = ( + inspect.signature(func, eval_str=True) + .parameters[second_param_name] + .annotation + ) + return annotation if annotation != inspect._empty else None +>>>>>>> 64d095e50 (jac2py codegen refactored)