diff --git a/examples/schedule/RuleExecuter.py b/examples/schedule/RuleExecuter.py new file mode 100644 index 0000000..8566d10 --- /dev/null +++ b/examples/schedule/RuleExecuter.py @@ -0,0 +1,49 @@ +from concrete_syntax.textual_od.renderer import render_od + +import pprint +from typing import Generator, Callable, Any +from uuid import UUID +import functools + +from api.od import ODAPI +from concrete_syntax.common import indent +from transformation.matcher import match_od +from transformation.rewriter import rewrite +from transformation.cloner import clone_od +from util.timer import Timer +from util.loader import parse_and_check + +class RuleExecuter: + def __init__(self, state, mm: UUID, mm_ramified: UUID, eval_context={}): + self.state = state + self.mm = mm + self.mm_ramified = mm_ramified + self.eval_context = eval_context + + # Generates matches. + # Every match is a dictionary with entries LHS_element_name -> model_element_name + def match_rule(self, m: UUID, lhs: UUID, *, pivot:dict[Any, Any]): + lhs_matcher = match_od(self.state, + host_m=m, + host_mm=self.mm, + pattern_m=lhs, + pattern_mm=self.mm_ramified, + eval_context=self.eval_context, + pivot= pivot, + ) + return lhs_matcher + + def rewrite_rule(self, m: UUID, rhs: UUID, *, pivot:dict[Any, Any]): + yield rewrite(self.state, + rhs_m=rhs, + pattern_mm=self.mm_ramified, + lhs_match=pivot, + host_m=m, + host_mm=self.mm, + eval_context=self.eval_context, + ) + + + def load_match(self, file: str): + with open(file, "r") as f: + return parse_and_check(self.state, f.read(), self.mm_ramified, file) diff --git a/examples/schedule/ScheduledActionGenerator.py b/examples/schedule/ScheduledActionGenerator.py new file mode 100644 index 0000000..34b1b96 --- /dev/null +++ b/examples/schedule/ScheduledActionGenerator.py @@ -0,0 +1,104 @@ +import importlib.util +import io +import os + +from jinja2 import FileSystemLoader, Environment + +from concrete_syntax.textual_od import parser as parser_od +from concrete_syntax.textual_cd import parser as parser_cd +from api.od import ODAPI +from bootstrap.scd import bootstrap_scd +from examples.petrinet.schedule import Schedule +from examples.schedule.generator import schedule_generator +from examples.schedule.schedule_lib import End, NullNode +from framework.conformance import Conformance, render_conformance_check_result +from state.devstate import DevState + +class ScheduleActionGenerator: + def __init__(self, rule_executer, schedulefile:str): + self.rule_executer = rule_executer + self.rule_dict = {} + self.schedule: Schedule + + + self.state = DevState() + self.load_schedule(schedulefile) + + def load_schedule(self, filename): + print("Loading schedule ...") + scd_mmm = bootstrap_scd(self.state) + with open("../schedule/models/scheduling_MM.od", "r") as f_MM: + mm_cs = f_MM.read() + with open(f"{filename}", "r") as f_M: + m_cs = f_M.read() + print("OK") + + print("\nParsing models") + + print(f"\tParsing meta model") + scheduling_mm = parser_cd.parse_cd( + self.state, + m_text=mm_cs, + ) + print(f"\tParsing '{filename}_M.od' model") + scheduling_m = parser_od.parse_od( + self.state, + m_text=m_cs, + mm=scheduling_mm + ) + print(f"OK") + + print("\tmeta-meta-model a valid class diagram") + conf = Conformance(self.state, scd_mmm, scd_mmm) + print(render_conformance_check_result(conf.check_nominal())) + print(f"Is our '{filename}_M.od' model a valid '{filename}_MM.od' diagram?") + conf = Conformance(self.state, scheduling_m, scheduling_mm) + print(render_conformance_check_result(conf.check_nominal())) + print("OK") + + od = ODAPI(self.state, scheduling_m, scheduling_mm) + g = schedule_generator(od) + + output_buffer = io.StringIO() + g.generate_schedule(output_buffer) + open(f"schedule.py", "w").write(output_buffer.getvalue()) + spec = importlib.util.spec_from_file_location("schedule", "schedule.py") + scedule_module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(scedule_module) + self.schedule = scedule_module.Schedule(self.rule_executer) + self.load_matchers() + + def load_matchers(self): + matchers = dict() + for file in self.schedule.get_matchers(): + matchers[file] = self.rule_executer.load_match(file) + self.schedule.init_schedule(matchers) + + def __call__(self, api: ODAPI): + exec_op = self.schedule(api) + yield from exec_op + + def termination_condition(self, api: ODAPI): + if type(self.schedule.cur) == End: + return "jay" + if type(self.schedule.cur) == NullNode: + return "RRRR" + return None + + def generate_dot(self): + env = Environment(loader=FileSystemLoader(os.path.join(os.path.dirname(__file__), 'templates'))) + env.trim_blocks = True + env.lstrip_blocks = True + template_dot = env.get_template('schedule_dot.j2') + + nodes = [] + edges = [] + visit = set() + self.schedule.generate_dot(nodes, edges, visit) + print("Nodes:") + print(nodes) + print("\nEdges:") + print(edges) + + with open("test.dot", "w") as f_dot: + f_dot.write(template_dot.render({"nodes": nodes, "edges": edges})) \ No newline at end of file diff --git a/examples/schedule/__init__.py b/examples/schedule/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/schedule/generator.py b/examples/schedule/generator.py new file mode 100644 index 0000000..ed8a111 --- /dev/null +++ b/examples/schedule/generator.py @@ -0,0 +1,129 @@ +import sys +import os +import json +from uuid import UUID + +from jinja2.runtime import Macro + +from api.od import ODAPI +from jinja2 import Environment, FileSystemLoader, meta + + +class schedule_generator: + def __init__(self, odApi:ODAPI): + self.env = Environment(loader=FileSystemLoader(os.path.join(os.path.dirname(__file__), 'templates'))) + self.env.trim_blocks = True + self.env.lstrip_blocks = True + self.template = self.env.get_template('schedule_template.j2') + self.template_wrap = self.env.get_template('schedule_template_wrap.j2') + self.api = odApi + + def get_slot_value_default(item: UUID, slot:str, default): + if slot in self.api.get_slots(item): + return self.api.get_slot_value(item, slot) + return default + + name_dict = lambda item: {"name": self.api.get_name(item)} + conn_dict = lambda item: {"name_from": self.api.get_name(self.api.get_source(item)), + "name_to": self.api.get_name(self.api.get_target(item)), + "gate_from": self.api.get_slot_value(item, "gate_from"), + "gate_to": self.api.get_slot_value(item, "gate_to"), + } + + conn_data_event = {"Match": lambda item: False, + "Rewrite": lambda item: False, + "Data_modify": lambda item: True, + "Loop": lambda item: True, + "Print": lambda item: get_slot_value_default(item, "event", False) + } + conn_data_dict = lambda item: {"name_from": self.api.get_name(self.api.get_source(item)), + "name_to": self.api.get_name(self.api.get_target(item)), + "event": conn_data_event[self.api.get_type_name(target := self.api.get_target(item))](target) + } + rewrite_dict = lambda item: {"name": self.api.get_name(item), + "file": self.api.get_slot_value(item, "file"), + } + match_dict = lambda item: {"name": self.api.get_name(item), + "file": self.api.get_slot_value(item, "file"), + "n": self.api.get_slot_value(item, "n") \ + if "n" in self.api.get_slots(item) else 'float("inf")' + } + data_modify_dict = lambda item: {"name": self.api.get_name(item), + "dict": json.loads(self.api.get_slot_value(item, "modify_dict")) + } + loop_dict = lambda item: {"name": self.api.get_name(item), + "choise": get_slot_value_default(item, "choise", False)} + print_dict = lambda item: {"name": self.api.get_name(item), + "label": get_slot_value_default(item, "label", "")} + arg_map = {"Start": name_dict, "End": name_dict, + "Match": match_dict, "Rewrite": rewrite_dict, + "Data_modify": data_modify_dict, "Loop": loop_dict, + "Exec_con": conn_dict, "Data_con": conn_data_dict, + "Print": print_dict} + self.macro_args = {tp: (macro, arg_map.get(tp)) for tp, macro in self.template.module.__dict__.items() + if type(macro) == Macro} + + def _render(self, item): + type_name = self.api.get_type_name(item) + macro, arg_gen = self.macro_args[type_name] + return macro(**arg_gen(item)) + + def generate_schedule(self, stream = sys.stdout): + start = self.api.get_all_instances("Start")[0][1] + stack = [start] + out = {"blocks":[], "exec_conn":[], "data_conn":[], "match_files":set(), "matchers":[], "start":self.api.get_name(start)} + execBlocks = set() + exec_conn = list() + + while len(stack) > 0: + exec_obj = stack.pop() + if exec_obj in execBlocks: + continue + execBlocks.add(exec_obj) + for conn in self.api.get_outgoing(exec_obj, "Exec_con"): + exec_conn.append(conn) + stack.append(self.api.get_target(conn)) + + stack = list(execBlocks) + data_blocks = set() + for name, p in self.api.get_all_instances("Print"): + if "event" in (event := self.api.get_slots(p)) and event: + stack.append(p) + execBlocks.add(p) + + + data_conn = set() + while len(stack) > 0: + obj = stack.pop() + for data_c in self.api.get_incoming(obj, "Data_con"): + data_conn.add(data_c) + source = self.api.get_source(data_c) + if not self.api.is_instance(source, "Exec") and \ + source not in execBlocks and \ + source not in data_blocks: + stack.append(source) + data_blocks.add(source) + + for exec_item in execBlocks: + out["blocks"].append(self._render(exec_item)) + if self.api.is_instance(exec_item, "Rule"): + d = self.macro_args[self.api.get_type_name(exec_item)][1](exec_item) + out["match_files"].add(d["file"]) + out["matchers"].append(d) + for exec_c in exec_conn: + out["exec_conn"].append(self._render(exec_c)) + + for data_c in data_conn: + out["data_conn"].append(self._render(data_c)) + + for data_b in data_blocks: + out["blocks"].append(self._render(data_b)) + + print(self.template_wrap.render(out), file=stream) + + + + + + # print("with open('test.dot', 'w') as f:", file=stream) + # print(f"\tf.write({self.api.get_name(start)}.generate_dot())", file=stream) \ No newline at end of file diff --git a/examples/schedule/models/README.md b/examples/schedule/models/README.md new file mode 100644 index 0000000..5767d48 --- /dev/null +++ b/examples/schedule/models/README.md @@ -0,0 +1,26 @@ + +### association Exec_con + Integer gate_from; + Integer gate_to; + +### association Data_con + +### class Start [1..1] +### class End [1..*] + + +### class Match + optional Integer n; + +### class Rewrite + +### class Data_modify + String modify_dict; + +### class Loop + optional Boolean choise; + +## debugging tools + +### class Print(In_Exec, Out_Exec, In_Data) + optional Boolean event; \ No newline at end of file diff --git a/examples/schedule/models/scheduling_MM.od b/examples/schedule/models/scheduling_MM.od new file mode 100644 index 0000000..533d8bc --- /dev/null +++ b/examples/schedule/models/scheduling_MM.od @@ -0,0 +1,46 @@ +abstract class Exec +abstract class In_Exec(Exec) +abstract class Out_Exec(Exec) + +association Exec_con [0..*] Out_Exec -> In_Exec [0..*] { + Integer gate_from; + Integer gate_to; +} + +abstract class Data +abstract class In_Data(Data) +abstract class Out_Data(Data) +association Data_con [0..*] Out_Data -> In_Data [0..*] + +class Start [1..1] (Out_Exec) +class End [1..*] (In_Exec) + + +abstract class Rule (In_Exec, Out_Exec, In_Data, Out_Data) +{ + String file; +} +class Match (Rule) +{ + optional Integer n; +} + +class Rewrite (Rule) + +class Data_modify(In_Data, Out_Data) +{ + String modify_dict; +} + +class Loop(In_Exec, Out_Exec, In_Data, Out_Data) +{ + optional Boolean choise; +} + +# debugging tools + +class Print(In_Exec, Out_Exec, In_Data) +{ + optional Boolean event; + optional String label; +} \ No newline at end of file diff --git a/examples/schedule/schedule_lib/__init__.py b/examples/schedule/schedule_lib/__init__.py new file mode 100644 index 0000000..0b826ab --- /dev/null +++ b/examples/schedule/schedule_lib/__init__.py @@ -0,0 +1,12 @@ +from .data_node import DataNode +from .data_modify import DataModify +from .end import End +from .exec_node import ExecNode +from .loop import Loop +from .match import Match +from .null_node import NullNode +from .print import Print +from .rewrite import Rewrite +from .start import Start + +__all__ = ["DataNode", "End", "ExecNode", "Loop", "Match", "NullNode", "Rewrite", "Print", "DataModify", "Start"] \ No newline at end of file diff --git a/examples/schedule/schedule_lib/data.py b/examples/schedule/schedule_lib/data.py new file mode 100644 index 0000000..88bcb42 --- /dev/null +++ b/examples/schedule/schedule_lib/data.py @@ -0,0 +1,63 @@ +import functools +from typing import Any, Generator, Callable + + +class Data: + def __init__(self, super) -> None: + self.data: list[dict[Any, Any]] = list() + self.success: bool = False + self.super = super + + @staticmethod + def store_output(func: Callable) -> Callable: + def wrapper(self, *args, **kwargs) -> Any: + output = func(self, *args, **kwargs) + self.success = output + return output + return wrapper + + @store_output + def store_data(self, data_gen: Generator, n: int) -> bool: + self.data.clear() + if n == 0: + return True + i: int = 0 + while (match := next(data_gen, None)) is not None: + self.data.append(match) + i+=1 + if i >= n: + break + else: + if n == float("inf"): + return bool(len(self.data)) + self.data.clear() + return False + return True + + def get_super(self) -> int: + return self.super + + def replace(self, data: "Data") -> None: + self.data.clear() + self.data.extend(data.data) + + def append(self, data: Any) -> None: + self.data.append(data) + + def clear(self) -> None: + self.data.clear() + + def pop(self, index = -1) -> Any: + return self.data.pop(index) + + def empty(self) -> bool: + return len(self.data) == 0 + + def __getitem__(self, index): + return self.data[index] + + def __iter__(self): + return self.data.__iter__() + + def __len__(self): + return self.data.__len__() \ No newline at end of file diff --git a/examples/schedule/schedule_lib/data_modify.py b/examples/schedule/schedule_lib/data_modify.py new file mode 100644 index 0000000..0df6cba --- /dev/null +++ b/examples/schedule/schedule_lib/data_modify.py @@ -0,0 +1,26 @@ +import functools +from typing import TYPE_CHECKING, Callable, List + +from api.od import ODAPI +from examples.schedule.RuleExecuter import RuleExecuter +from .exec_node import ExecNode +from .data_node import DataNode + + +class DataModify(DataNode): + def __init__(self, modify_dict: dict[str,str]) -> None: + DataNode.__init__(self) + self.modify_dict: dict[str,str] = modify_dict + + def input_event(self, success: bool) -> None: + if success or self.data_out.success: + self.data_out.data.clear() + for data in self.data_in.data: + self.data_out.append({self.modify_dict[key]: value for key, value in data.items() if key in self.modify_dict.keys()}) + DataNode.input_event(self, success) + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=modify]") + super().generate_dot(nodes, edges, visited) diff --git a/examples/schedule/schedule_lib/data_node.py b/examples/schedule/schedule_lib/data_node.py new file mode 100644 index 0000000..557f297 --- /dev/null +++ b/examples/schedule/schedule_lib/data_node.py @@ -0,0 +1,47 @@ +from typing import Any, Generator, List + +from examples.schedule.schedule_lib.id_generator import IdGenerator +from .data import Data + +class DataNode: + def __init__(self) -> None: + if not hasattr(self, 'id'): + self.id = IdGenerator().generate_id() + self.data_out : Data = Data(self) + self.data_in: Data | None = None + self.eventsub: list[DataNode] = list() + + def connect_data(self, data_node: "DataNode", eventsub=True) -> None: + data_node.data_in = self.data_out + if eventsub: + self.eventsub.append(data_node) + + def store_data(self, data_gen: Generator, n: int) -> None: + success: bool = self.data_out.store_data(data_gen, n) + for sub in self.eventsub: + sub.input_event(success) + + def get_input_data(self) -> list[dict[Any, Any]]: + if not self.data_in.success: + raise Exception("Invalid input data: matching has failed") + data = self.data_in.data + if len(data) == 0: + raise Exception("Invalid input data: no data present") + return data + + def input_event(self, success: bool) -> None: + self.data_out.success = success + for sub in self.eventsub: + sub.input_event(success) + + def get_id(self) -> int: + return self.id + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + visited.add(self.id) + if self.data_in is not None: + edges.append(f"{self.data_in.get_super().get_id()} -> {self.get_id()} [color = green]") + self.data_in.get_super().generate_dot(nodes, edges, visited) + for sub in self.eventsub: + sub.generate_dot(nodes, edges, visited) + diff --git a/examples/schedule/schedule_lib/end.py b/examples/schedule/schedule_lib/end.py new file mode 100644 index 0000000..2a008c4 --- /dev/null +++ b/examples/schedule/schedule_lib/end.py @@ -0,0 +1,21 @@ +import functools +from typing import TYPE_CHECKING, List, Callable, Generator + +from api.od import ODAPI +from .exec_node import ExecNode + +class End(ExecNode): + def __init__(self) -> None: + super().__init__(out_connections=1) + + def execute(self, od: ODAPI) -> Generator | None: + return self.terminate(od) + + @staticmethod + def terminate(od: ODAPI) -> Generator: + yield f"end:", functools.partial(lambda od:(od, ""), od) + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=end]") \ No newline at end of file diff --git a/examples/schedule/schedule_lib/exec_node.py b/examples/schedule/schedule_lib/exec_node.py new file mode 100644 index 0000000..c5d2d04 --- /dev/null +++ b/examples/schedule/schedule_lib/exec_node.py @@ -0,0 +1,34 @@ +from typing import TYPE_CHECKING, List, Callable, Generator +from api.od import ODAPI + +from .id_generator import IdGenerator + +class ExecNode: + def __init__(self, out_connections: int = 1) -> None: + from .null_node import NullNode + self.next_state: list[ExecNode] = [] + if out_connections > 0: + self.next_state = [NullNode()]*out_connections + self.id: int = IdGenerator().generate_id() + + def nextState(self) -> "ExecNode": + return self.next_state[0] + + def connect(self, next_state: "ExecNode", from_gate: int = 0, to_gate: int = 0) -> None: + if from_gate >= len(self.next_state): + raise IndexError + self.next_state[from_gate] = next_state + + def execute(self, od: ODAPI) -> Generator | None: + return None + + def get_id(self) -> int: + return self.id + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + visited.add(self.id) + for edge in self.next_state: + edges.append(f"{self.id} -> {edge.get_id()}") + for next in self.next_state: + next.generate_dot(nodes, edges, visited) + diff --git a/examples/schedule/schedule_lib/funcs.py b/examples/schedule/schedule_lib/funcs.py new file mode 100644 index 0000000..0b19b99 --- /dev/null +++ b/examples/schedule/schedule_lib/funcs.py @@ -0,0 +1,10 @@ +from typing import Callable + +def generate_dot_wrap(func) -> Callable: + def wrapper(self, *args, **kwargs) -> str: + nodes = [] + edges = [] + self.reset_visited() + func(self, nodes, edges, *args, **kwargs) + return f"digraph G {{\n\t{"\n\t".join(nodes)}\n\t{"\n\t".join(edges)}\n}}" + return wrapper diff --git a/examples/schedule/schedule_lib/id_generator.py b/examples/schedule/schedule_lib/id_generator.py new file mode 100644 index 0000000..d1f4b25 --- /dev/null +++ b/examples/schedule/schedule_lib/id_generator.py @@ -0,0 +1,8 @@ +from .singleton import Singleton + +class IdGenerator(metaclass=Singleton): + def __init__(self): + self.id = -1 + def generate_id(self) -> int: + self.id += 1 + return self.id \ No newline at end of file diff --git a/examples/schedule/schedule_lib/loop.py b/examples/schedule/schedule_lib/loop.py new file mode 100644 index 0000000..44ec5c5 --- /dev/null +++ b/examples/schedule/schedule_lib/loop.py @@ -0,0 +1,57 @@ +import functools +from random import choice +from typing import TYPE_CHECKING, Callable, List, Generator + +from api.od import ODAPI +from examples.schedule.RuleExecuter import RuleExecuter +from .exec_node import ExecNode +from .data_node import DataNode +from .data_node import Data + + +class Loop(ExecNode, DataNode): + def __init__(self, choice) -> None: + ExecNode.__init__(self, out_connections=2) + DataNode.__init__(self) + self.choice: bool = choice + self.cur_data: Data = Data(-1) + + def nextState(self) -> ExecNode: + return self.next_state[not self.data_out.success] + + def execute(self, od: ODAPI) -> Generator | None: + if self.cur_data.empty(): + self.data_out.clear() + self.data_out.success = False + DataNode.input_event(self, False) + return None + + if self.choice: + def select_data() -> Generator: + for i in range(len(self.cur_data)): + yield f"choice: {self.cur_data[i]}", functools.partial(self.select_next,od, i) + return select_data() + else: + self.select_next(od, -1) + return None + + def input_event(self, success: bool) -> None: + if (b := self.data_out.success) or success: + self.cur_data.replace(self.data_in) + self.data_out.clear() + self.data_out.success = False + if b: + DataNode.input_event(self, False) + + def select_next(self,od: ODAPI, index: int) -> tuple[ODAPI, list[str]]: + self.data_out.clear() + self.data_out.append(self.cur_data.pop(index)) + DataNode.input_event(self, True) + return (od, ["data selected"]) + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=Loop]") + ExecNode.generate_dot(self, nodes, edges, visited) + DataNode.generate_dot(self, nodes, edges, visited) \ No newline at end of file diff --git a/examples/schedule/schedule_lib/match.py b/examples/schedule/schedule_lib/match.py new file mode 100644 index 0000000..f350ba6 --- /dev/null +++ b/examples/schedule/schedule_lib/match.py @@ -0,0 +1,42 @@ +import functools +from typing import TYPE_CHECKING, Callable, List, Generator + +from api.od import ODAPI +from examples.schedule.RuleExecuter import RuleExecuter +from .exec_node import ExecNode +from .data_node import DataNode + + +class Match(ExecNode, DataNode): + def __init__(self, label: str, n: int | float) -> None: + ExecNode.__init__(self, out_connections=2) + DataNode.__init__(self) + self.label: str = label + self.n:int = n + self.rule = None + self.rule_executer : RuleExecuter + + def nextState(self) -> ExecNode: + return self.next_state[not self.data_out.success] + + def execute(self, od: ODAPI) -> Generator | None: + self.match(od) + return None + + def init_rule(self, rule, rule_executer): + self.rule = rule + self.rule_executer = rule_executer + + def match(self, od: ODAPI) -> None: + pivot = {} + if self.data_in is not None: + pivot = self.get_input_data()[0] + print(f"matching: {self.label}\n\tpivot: {pivot}") + self.store_data(self.rule_executer.match_rule(od.m, self.rule, pivot=pivot), self.n) + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=M_{self.label.split("/")[-1]}_{self.n}]") + ExecNode.generate_dot(self, nodes, edges, visited) + DataNode.generate_dot(self, nodes, edges, visited) \ No newline at end of file diff --git a/examples/schedule/schedule_lib/null_node.py b/examples/schedule/schedule_lib/null_node.py new file mode 100644 index 0000000..2d322bb --- /dev/null +++ b/examples/schedule/schedule_lib/null_node.py @@ -0,0 +1,25 @@ +import functools +from symtable import Function +from typing import List, Callable, Generator + +from api.od import ODAPI +from .singleton import Singleton + +from .exec_node import ExecNode + +class NullNode(ExecNode, metaclass=Singleton): + def __init__(self): + ExecNode.__init__(self, out_connections=0) + + def execute(self, od: ODAPI) -> Generator | None: + raise Exception('Null node should already have terminated the schedule') + + @staticmethod + def terminate(od: ODAPI): + return None + yield # verrrry important line, dont remove this unreachable code + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=Null]") \ No newline at end of file diff --git a/examples/schedule/schedule_lib/print.py b/examples/schedule/schedule_lib/print.py new file mode 100644 index 0000000..ed0bbc6 --- /dev/null +++ b/examples/schedule/schedule_lib/print.py @@ -0,0 +1,28 @@ +import functools +from typing import TYPE_CHECKING, Callable, List, Generator + +from api.od import ODAPI +from examples.schedule.RuleExecuter import RuleExecuter +from .exec_node import ExecNode +from .data_node import DataNode + + +class Print(ExecNode, DataNode): + def __init__(self, label: str = "") -> None: + ExecNode.__init__(self, out_connections=1) + DataNode.__init__(self) + self.label = label + + def execute(self, od: ODAPI) -> Generator | None: + self.input_event(True) + return None + + def input_event(self, success: bool) -> None: + print(f"{self.label}{self.data_in.data}") + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=Print_{self.label.replace(":", "")}]") + ExecNode.generate_dot(self, nodes, edges, visited) + DataNode.generate_dot(self, nodes, edges, visited) \ No newline at end of file diff --git a/examples/schedule/schedule_lib/rewrite.py b/examples/schedule/schedule_lib/rewrite.py new file mode 100644 index 0000000..c00ee8e --- /dev/null +++ b/examples/schedule/schedule_lib/rewrite.py @@ -0,0 +1,38 @@ +import functools +from typing import List, Callable, Generator + +from api.od import ODAPI +from .exec_node import ExecNode +from .data_node import DataNode +from ..RuleExecuter import RuleExecuter + + +class Rewrite(ExecNode, DataNode): + def __init__(self, label: str) -> None: + ExecNode.__init__(self, out_connections=1) + DataNode.__init__(self) + self.label = label + self.rule = None + self.rule_executer : RuleExecuter + + def init_rule(self, rule, rule_executer): + self.rule = rule + self.rule_executer= rule_executer + + def execute(self, od: ODAPI) -> Generator | None: + yield "ghello", functools.partial(self.rewrite, od) + + def rewrite(self, od): + print("rewrite" + self.label) + pivot = {} + if self.data_in is not None: + pivot = self.get_input_data()[0] + self.store_data(self.rule_executer.rewrite_rule(od.m, self.rule, pivot=pivot), 1) + return ODAPI(od.state, od.m, od.mm),[f"rewrite {self.label}\n\tpivot: {pivot}\n\t{"success" if self.data_out.success else "failure"}\n"] + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=R_{self.label.split("/")[-1]}]") + ExecNode.generate_dot(self, nodes, edges, visited) + DataNode.generate_dot(self, nodes, edges, visited) \ No newline at end of file diff --git a/examples/schedule/schedule_lib/singleton.py b/examples/schedule/schedule_lib/singleton.py new file mode 100644 index 0000000..31955e3 --- /dev/null +++ b/examples/schedule/schedule_lib/singleton.py @@ -0,0 +1,8 @@ +from abc import ABCMeta + +class Singleton(ABCMeta): + _instances = {} + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] diff --git a/examples/schedule/schedule_lib/start.py b/examples/schedule/schedule_lib/start.py new file mode 100644 index 0000000..44ed1e1 --- /dev/null +++ b/examples/schedule/schedule_lib/start.py @@ -0,0 +1,16 @@ +from typing import TYPE_CHECKING, Callable, List, Any + +from .funcs import generate_dot_wrap + +from .exec_node import ExecNode + + +class Start(ExecNode): + def __init__(self) -> None: + ExecNode.__init__(self, out_connections=1) + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=start]") + super().generate_dot(nodes, edges, visited) \ No newline at end of file diff --git a/examples/schedule/templates/schedule_dot.j2 b/examples/schedule/templates/schedule_dot.j2 new file mode 100644 index 0000000..39d2672 --- /dev/null +++ b/examples/schedule/templates/schedule_dot.j2 @@ -0,0 +1,9 @@ +digraph G { +{% for node in nodes %} + {{ node }} +{% endfor %} + +{% for edge in edges %} + {{ edge }} +{% endfor %} +} \ No newline at end of file diff --git a/examples/schedule/templates/schedule_template.j2 b/examples/schedule/templates/schedule_template.j2 new file mode 100644 index 0000000..a0c251c --- /dev/null +++ b/examples/schedule/templates/schedule_template.j2 @@ -0,0 +1,35 @@ +{% macro Start(name) %} +{{ name }} = Start() +{%- endmacro %} + +{% macro End(name) %} +{{ name }} = End() +{%- endmacro %} + +{% macro Match(name, file, n) %} +{{ name }} = Match("{{ file }}", {{ n }}) +{%- endmacro %} + +{% macro Rewrite(name, file) %} +{{ name }} = Rewrite("{{ file }}") +{%- endmacro %} + +{% macro Data_modify(name, dict) %} +{{ name }} = DataModify({{ dict }}) +{%- endmacro %} + +{% macro Exec_con(name_from, name_to, gate_from, gate_to) %} +{{ name_from }}.connect({{ name_to }},{{ gate_from }},{{ gate_to }}) +{%- endmacro %} + +{% macro Data_con(name_from, name_to, event) %} +{{ name_from }}.connect_data({{ name_to }}, {{ event }}) +{%- endmacro %} + +{% macro Loop(name, choise) %} +{{ name }} = Loop({{ choise }}) +{%- endmacro %} + +{% macro Print(name, label) %} +{{ name }} = Print("{{ label }}") +{%- endmacro %} \ No newline at end of file diff --git a/examples/schedule/templates/schedule_template_wrap.j2 b/examples/schedule/templates/schedule_template_wrap.j2 new file mode 100644 index 0000000..389f2c2 --- /dev/null +++ b/examples/schedule/templates/schedule_template_wrap.j2 @@ -0,0 +1,47 @@ +from examples.schedule.schedule_lib import * + +class Schedule: + def __init__(self, rule_executer): + self.start: Start + self.cur: ExecNode = None + self.rule_executer = rule_executer + + def __call__(self, od): + self.cur = self.cur.nextState() + while not isinstance(self.cur, NullNode): + action_gen = self.cur.execute(od) + if action_gen is not None: + # if (action_gen := self.cur.execute(od)) is not None: + return action_gen + self.cur = self.cur.nextState() + return NullNode.terminate(od) + + @staticmethod + def get_matchers(): + return [ + {% for file in match_files %} + "{{ file }}.od", + {% endfor %} + ] + + def init_schedule(self, matchers): + {% for block in blocks%} + {{ block }} + {% endfor %} + + {% for conn in exec_conn%} + {{ conn }} + {% endfor %} + {% for conn_d in data_conn%} + {{ conn_d }} + {% endfor %} + self.start = {{ start }} + self.cur = {{ start }} + + {% for match in matchers %} + {{ match["name"] }}.init_rule(matchers["{{ match["file"] }}.od"], self.rule_executer) + {% endfor %} + return None + + def generate_dot(self, *args, **kwargs): + return self.start.generate_dot(*args, **kwargs) \ No newline at end of file