diff --git a/examples/geraniums/geraniums_renderer.j2 b/examples/geraniums/geraniums_renderer.j2 new file mode 100644 index 0000000..1ef47cc --- /dev/null +++ b/examples/geraniums/geraniums_renderer.j2 @@ -0,0 +1,34 @@ +digraph G { + rankdir=LR; + center=true; + margin=1; + nodesep=1; + + node [fontname="Arial", fontsize=10, shape=box, style=filled, fillcolor=white]; + + // Geraniums + {% for id, name, flowering in geraniums %} + g{{ id }} [ + label="geranium: {{ name }}\n({{ 'flowering' if flowering else 'not flowering' }})", + shape=ellipse, + fillcolor="{{ 'lightpink' if flowering else 'lightgray' }}", + fontcolor=black + ]; + {% endfor %} + + // Pots + {% for id, name, cracked in pots %} + p{{ id }} [ + label="pot: {{ name }}\n({{ 'cracked' if cracked else 'pristine' }})", + shape=box, + fillcolor="{{ 'mistyrose' if cracked else 'lightgreen' }}", + fontcolor=black, + style="filled,bold" + ]; + {% endfor %} + + // Connections: geranium -> pot + {% for source, target in planted %} + g{{ source }} -> p{{ target }}; + {% endfor %} +} diff --git a/examples/geraniums/metamodels/mm.od b/examples/geraniums/metamodels/mm.od new file mode 100644 index 0000000..f6f6962 --- /dev/null +++ b/examples/geraniums/metamodels/mm.od @@ -0,0 +1,9 @@ +class Geranium { + Boolean flowering; +} + +class Pot { + Boolean cracked; +} + +association Planted [0..*] Geranium -> Pot [1..1] diff --git a/examples/geraniums/models/eval_context.py b/examples/geraniums/models/eval_context.py new file mode 100644 index 0000000..d8dfcd8 --- /dev/null +++ b/examples/geraniums/models/eval_context.py @@ -0,0 +1,44 @@ +import os + +from jinja2 import Environment, FileSystemLoader + +from api.od import ODAPI +from framework.conformance import eval_context_decorator + + +@eval_context_decorator +def _render_geraniums_dot(od: ODAPI, file: str) -> str: + __DIR__ = os.path.dirname(__file__) + env = Environment( + loader=FileSystemLoader( + __DIR__ + ) + ) + env.trim_blocks = True + env.lstrip_blocks = True + template_dot = env.get_template("geraniums_renderer.j2") + + id_count = 0 + id_map = {} + render = {"geraniums": [], "pots": [], "planted": []} + + for name, uuid in od.get_all_instances("Geranium"): + render["geraniums"].append((id_count, name, od.get_slot_value(uuid, "flowering"))) + id_map[uuid] = id_count + id_count += 1 + + for name, uuid in od.get_all_instances("Pot"): + render["pots"].append((id_count, name, od.get_slot_value(uuid, "cracked"))) + id_map[uuid] = id_count + id_count += 1 + + for name, uuid in od.get_all_instances("Planted"): + render["planted"].append((id_map[od.get_source(uuid)], id_map[od.get_target(uuid)])) + + with open(file, "w", encoding="utf-8") as f_dot: + f_dot.write(template_dot.render(**render)) + return "" + +eval_context = { + "render_geraniums_dot": _render_geraniums_dot, +} diff --git a/examples/geraniums/models/example1.od b/examples/geraniums/models/example1.od new file mode 100644 index 0000000..db5bc32 --- /dev/null +++ b/examples/geraniums/models/example1.od @@ -0,0 +1,17 @@ +f1:Geranium { + flowering = True; +} +f2:Geranium { + flowering = False; +} +f3:Geranium { + flowering = True; +} + +p1:Pot { + cracked = True; +} + +:Planted (f1 -> p1) +:Planted (f2 -> p1) +:Planted (f3 -> p1) \ No newline at end of file diff --git a/examples/geraniums/models/example2.od b/examples/geraniums/models/example2.od new file mode 100644 index 0000000..9c4e0f4 --- /dev/null +++ b/examples/geraniums/models/example2.od @@ -0,0 +1,47 @@ +f1:Geranium { + flowering = True; +} +f2:Geranium { + flowering = True; +} +f3:Geranium { + flowering = False; +} + +p1:Pot { + cracked = True; +} + +:Planted (f1 -> p1) +:Planted (f2 -> p1) +:Planted (f3 -> p1) + + + + +f4:Geranium { + flowering = True; +} +p2:Pot { + cracked = True; +} +:Planted (f4 -> p2) + + + +f5:Geranium { + flowering = True; +} +p3:Pot { + cracked = False; +} +:Planted (f5 -> p3) + + +f6:Geranium { + flowering = False; +} +p4:Pot { + cracked = True; +} +:Planted (f6 -> p4) \ No newline at end of file diff --git a/examples/geraniums/renderer.py b/examples/geraniums/renderer.py new file mode 100644 index 0000000..3ac50f5 --- /dev/null +++ b/examples/geraniums/renderer.py @@ -0,0 +1,45 @@ +import os + +from jinja2 import Environment, FileSystemLoader + +from api.od import ODAPI +from concrete_syntax.graphviz.make_url import show_graphviz +from concrete_syntax.graphviz.renderer import make_graphviz_id + +try: + import graphviz + HAVE_GRAPHVIZ = True +except ImportError: + HAVE_GRAPHVIZ = False + +def render_geraniums_dot(od: ODAPI, file: str) -> str: + __DIR__ = os.path.dirname(__file__) + env = Environment( + loader=FileSystemLoader( + __DIR__ + ) + ) + env.trim_blocks = True + env.lstrip_blocks = True + template_dot = env.get_template("geraniums_renderer.j2") + + id_count = 0 + id_map = {} + render = {"geraniums": [], "pots": [], "planted": []} + + for name, uuid in od.get_all_instances("Geranium"): + render["geraniums"].append((id_count, name, od.get_slot_value(uuid, "flowering"))) + id_map[uuid] = id_count + id_count += 1 + + for name, uuid in od.get_all_instances("Pot"): + render["pots"].append((id_count, name, od.get_slot_value(uuid, "cracked"))) + id_map[uuid] = id_count + id_count += 1 + + for name, uuid in od.get_all_instances("Planted"): + render["planted"].append((id_map[od.get_source(uuid)], id_map[od.get_target(uuid)])) + + with open(file, "w", encoding="utf-8") as f_dot: + f_dot.write(template_dot.render(**render)) + return "" \ No newline at end of file diff --git a/examples/geraniums/rules/cracked_pots.od b/examples/geraniums/rules/cracked_pots.od new file mode 100644 index 0000000..61ef57f --- /dev/null +++ b/examples/geraniums/rules/cracked_pots.od @@ -0,0 +1,3 @@ +pot:RAM_Pot { + RAM_cracked = `get_value(this)`; +} \ No newline at end of file diff --git a/examples/geraniums/rules/create_pot.od b/examples/geraniums/rules/create_pot.od new file mode 100644 index 0000000..c6ef5d0 --- /dev/null +++ b/examples/geraniums/rules/create_pot.od @@ -0,0 +1,3 @@ +pot:RAM_Pot { + RAM_cracked = `False`; +} \ No newline at end of file diff --git a/examples/geraniums/rules/flowering_flowers_in_pot.od b/examples/geraniums/rules/flowering_flowers_in_pot.od new file mode 100644 index 0000000..591c123 --- /dev/null +++ b/examples/geraniums/rules/flowering_flowers_in_pot.od @@ -0,0 +1,7 @@ +pot:RAM_Pot + +flower:RAM_Geranium { + RAM_flowering = `get_value(this)`; +} + +:RAM_Planted (flower -> pot) \ No newline at end of file diff --git a/examples/geraniums/rules/repot_flower_in_pot.od b/examples/geraniums/rules/repot_flower_in_pot.od new file mode 100644 index 0000000..134813f --- /dev/null +++ b/examples/geraniums/rules/repot_flower_in_pot.od @@ -0,0 +1,8 @@ +pot:RAM_Pot +new_pot:RAM_Pot + +flower:RAM_Geranium { + RAM_flowering = `get_value(this)`; +} + +replant:RAM_Planted (flower -> new_pot) \ No newline at end of file diff --git a/examples/geraniums/runner.py b/examples/geraniums/runner.py new file mode 100644 index 0000000..fdaaa68 --- /dev/null +++ b/examples/geraniums/runner.py @@ -0,0 +1,48 @@ +from examples.geraniums.renderer import render_geraniums_dot +from transformation.ramify import ramify + +from models.eval_context import eval_context + +from transformation.schedule.rule_scheduler import * + +if __name__ == "__main__": + import os + THIS_DIR = os.path.dirname(__file__) + + # get file contents as string + def read_file(filename): + with open(THIS_DIR+'/'+filename) as file: + return file.read() + + + state = DevState() + scd_mmm = bootstrap_scd(state) + + mm_cs = read_file('metamodels/mm.od') + m_cs = read_file('models/example2.od') + + mm = parser_cd.parse_cd( + state, + m_text=mm_cs, + ) + m = parser_od.parse_od( + state, m_text=m_cs, mm=mm + ) + conf_err = Conformance( + state, m, mm + ).check_nominal() + print(render_conformance_check_result(conf_err)) + mm_ramified = ramify(state, mm) + + action_generator = RuleSchedular(state, mm, mm_ramified, verbose=True, directory="examples/geraniums", eval_context=eval_context) + od = ODAPI(state, m, mm) + render_geraniums_dot(od, f"{THIS_DIR}/geraniums.dot") + + # if action_generator.load_schedule(f"petrinet.od"): + # if action_generator.load_schedule("schedules/combinatory.drawio"): + if action_generator.load_schedule("schedules/schedule.drawio"): + + action_generator.generate_dot("../dot.dot") + code, message = action_generator.run(od) + print(f"{code}: {message}") + render_geraniums_dot(od, f"{THIS_DIR}/geraniums_final.dot") \ No newline at end of file diff --git a/examples/geraniums/schedules/schedule.drawio b/examples/geraniums/schedules/schedule.drawio new file mode 100644 index 0000000..41437fa --- /dev/null +++ b/examples/geraniums/schedules/schedule.drawio @@ -0,0 +1,645 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/geraniums/schedules/schedule.od b/examples/geraniums/schedules/schedule.od new file mode 100644 index 0000000..e69de29 diff --git a/examples/petrinet/models/m_example_simple.od b/examples/petrinet/models/m_example_simple.od index a3eee8d..d7dd1ea 100644 --- a/examples/petrinet/models/m_example_simple.od +++ b/examples/petrinet/models/m_example_simple.od @@ -1,5 +1,8 @@ p0:PNPlace p1:PNPlace +p2:PNPlace +p3:PNPlace +p4:PNPlace t0:PNTransition :arc (p0 -> t0) @@ -7,4 +10,12 @@ t0:PNTransition t1:PNTransition :arc (p1 -> t1) -:arc (t1 -> p0) \ No newline at end of file +:arc (t1 -> p2) + +t2:PNTransition +:arc (p2 -> t2) +:arc (t2 -> p0) + + +t3:PNTransition +:arc (t3 -> p4) \ No newline at end of file diff --git a/examples/petrinet/models/m_example_simple_rt_initial.od b/examples/petrinet/models/m_example_simple_rt_initial.od index fa93f4e..64fc3b7 100644 --- a/examples/petrinet/models/m_example_simple_rt_initial.od +++ b/examples/petrinet/models/m_example_simple_rt_initial.od @@ -9,3 +9,21 @@ p1s:PNPlaceState { } :pn_of (p1s -> p1) + +p2s:PNPlaceState { + numTokens = 0; +} + +:pn_of (p2s -> p2) + +p3s:PNPlaceState { + numTokens = 0; +} + +:pn_of (p3s -> p3) + +p4s:PNPlaceState { + numTokens = 0; +} + +:pn_of (p4s -> p4) diff --git a/examples/petrinet/models/rules/all_incoming.od b/examples/petrinet/models/rules/all_incoming.od new file mode 100644 index 0000000..1b87f1d --- /dev/null +++ b/examples/petrinet/models/rules/all_incoming.od @@ -0,0 +1,13 @@ +# A place with no tokens: + +p:RAM_PNPlace +ps:RAM_PNPlaceState { + RAM_numTokens = `True`; +} +:RAM_pn_of (ps -> p) + +# An incoming arc from that place to our transition: + +t:RAM_PNTransition + +:RAM_arc (p -> t) diff --git a/examples/petrinet/models/rules/all_incomming.od b/examples/petrinet/models/rules/all_incomming.od new file mode 100644 index 0000000..1b87f1d --- /dev/null +++ b/examples/petrinet/models/rules/all_incomming.od @@ -0,0 +1,13 @@ +# A place with no tokens: + +p:RAM_PNPlace +ps:RAM_PNPlaceState { + RAM_numTokens = `True`; +} +:RAM_pn_of (ps -> p) + +# An incoming arc from that place to our transition: + +t:RAM_PNTransition + +:RAM_arc (p -> t) diff --git a/examples/petrinet/models/rules/all_outgoing.od b/examples/petrinet/models/rules/all_outgoing.od new file mode 100644 index 0000000..ab431cc --- /dev/null +++ b/examples/petrinet/models/rules/all_outgoing.od @@ -0,0 +1,13 @@ +# A place with no tokens: + +p:RAM_PNPlace +ps:RAM_PNPlaceState { + RAM_numTokens = `True`; +} +:RAM_pn_of (ps -> p) + +# An incoming arc from that place to our transition: + +t:RAM_PNTransition + +:RAM_arc (t -> p) diff --git a/examples/petrinet/models/rules/increase_outgoing.od b/examples/petrinet/models/rules/increase_outgoing.od new file mode 100644 index 0000000..1fa1acb --- /dev/null +++ b/examples/petrinet/models/rules/increase_outgoing.od @@ -0,0 +1,13 @@ +# A place with no tokens: + +p:RAM_PNPlace +ps:RAM_PNPlaceState { + RAM_numTokens = `get_value(this) + 1`; +} +:RAM_pn_of (ps -> p) + +# An outgoing arc from that place to our transition: + +t:RAM_PNTransition + +:RAM_arc (t -> p) diff --git a/examples/petrinet/operational_semantics/all_input_have_token.od b/examples/petrinet/models/rules/input_without_token.od similarity index 100% rename from examples/petrinet/operational_semantics/all_input_have_token.od rename to examples/petrinet/models/rules/input_without_token.od diff --git a/examples/petrinet/models/rules/places.od b/examples/petrinet/models/rules/places.od new file mode 100644 index 0000000..923fb03 --- /dev/null +++ b/examples/petrinet/models/rules/places.od @@ -0,0 +1,3 @@ +# A place with no tokens: + +p:RAM_PNPlace \ No newline at end of file diff --git a/examples/petrinet/models/rules/reduce_incoming.od b/examples/petrinet/models/rules/reduce_incoming.od new file mode 100644 index 0000000..b85a2db --- /dev/null +++ b/examples/petrinet/models/rules/reduce_incoming.od @@ -0,0 +1,13 @@ +# A place with no tokens: + +p:RAM_PNPlace +ps:RAM_PNPlaceState { + RAM_numTokens = `get_value(this) -1`; +} +:RAM_pn_of (ps -> p) + +# An incoming arc from that place to our transition: + +t:RAM_PNTransition + +:RAM_arc (p -> t) \ No newline at end of file diff --git a/examples/petrinet/models/rules/reduce_incomming.od b/examples/petrinet/models/rules/reduce_incomming.od new file mode 100644 index 0000000..b85a2db --- /dev/null +++ b/examples/petrinet/models/rules/reduce_incomming.od @@ -0,0 +1,13 @@ +# A place with no tokens: + +p:RAM_PNPlace +ps:RAM_PNPlaceState { + RAM_numTokens = `get_value(this) -1`; +} +:RAM_pn_of (ps -> p) + +# An incoming arc from that place to our transition: + +t:RAM_PNTransition + +:RAM_arc (p -> t) \ No newline at end of file diff --git a/examples/petrinet/models/rules/transition.od b/examples/petrinet/models/rules/transition.od new file mode 100644 index 0000000..c3bd82c --- /dev/null +++ b/examples/petrinet/models/rules/transition.od @@ -0,0 +1 @@ +t:RAM_PNTransition \ No newline at end of file diff --git a/examples/petrinet/models/schedule.od b/examples/petrinet/models/schedule.od deleted file mode 100644 index 1584a7c..0000000 --- a/examples/petrinet/models/schedule.od +++ /dev/null @@ -1,66 +0,0 @@ -start:Start -end:End - -transitions:Match{ - file = "operational_semantics/transition"; -} - - -d:Data_modify -{ - modify_dict = ' - { - "tr": "t" - }'; -} - -nac_input_without:Match{ - file = "operational_semantics/all_input_have_token"; - n = "1"; -} - -inputs:Match{ - file = "operational_semantics/all_inputs"; -} - -rewrite_incoming:Rewrite -{ - file = "operational_semantics/remove_incoming"; -} - -loop_trans:Loop -loop_input:Loop - -p:Print -{ -event = True; -label = "transition: "; -} - -p2:Print -{ -event = True; -label = "inputs: "; -} - -:Exec_con(start -> transitions){gate_from = 0;gate_to = 0;} -:Exec_con(transitions -> end){gate_from = 1;gate_to = 0;} -:Exec_con(transitions -> loop_trans){gate_from = 0;gate_to = 0;} -:Exec_con(loop_trans -> nac_input_without){gate_from = 0;gate_to = 0;} - -[//]: # (:Exec_con(nac_input_without -> loop_trans){gate_from = 0;gate_to = 0;}) -:Exec_con(nac_input_without -> inputs){gate_from = 1;gate_to = 0;} -:Exec_con(inputs -> loop_input){gate_from = 0;gate_to = 0;} -:Exec_con(inputs -> loop_trans){gate_from = 1;gate_to = 0;} - -:Exec_con(loop_trans -> end){gate_from = 1;gate_to = 0;} - -:Data_con(transitions -> loop_trans) -:Data_con(nac_input_without -> p) -:Data_con(d -> nac_input_without) -:Data_con(loop_trans -> d) -:Data_con(loop_trans -> rewrite_incoming) - - - - diff --git a/examples/petrinet/models/schedules/combinatory.drawio b/examples/petrinet/models/schedules/combinatory.drawio new file mode 100644 index 0000000..c22b5ce --- /dev/null +++ b/examples/petrinet/models/schedules/combinatory.drawio @@ -0,0 +1,526 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/petrinet/models/schedules/foo.od b/examples/petrinet/models/schedules/foo.od new file mode 100644 index 0000000..7acc7a8 --- /dev/null +++ b/examples/petrinet/models/schedules/foo.od @@ -0,0 +1,23 @@ +start:Start { + ports_exec = `["F","FF"]`; +} +end:End { + ports_exec = `["F"]`; +} + +p1:Print{ + custom = "Foo"; +} + +p2:Print{ + custom = "FooFoo"; +} + +p3:Print{ + custom = "FooFooFoo"; +} + +:Conn_exec (start -> p1) {from="F";to="in";} +:Conn_exec (p1 -> end) {from="out";to="F";} +:Conn_exec (start -> p2) {from="FF";to="in";} +:Conn_exec (p2 -> end) {from="out";to="F";} diff --git a/examples/petrinet/models/schedules/petrinet.od b/examples/petrinet/models/schedules/petrinet.od new file mode 100644 index 0000000..386c3ed --- /dev/null +++ b/examples/petrinet/models/schedules/petrinet.od @@ -0,0 +1,66 @@ +start:Start +end:End + +m:Match{ + file = "operational_semantics/transition"; +} + +nac1:Match{ + file = "operational_semantics/all_input_have_token"; + n = "1"; +} + +inputs:Match{ + file = "operational_semantics/all_inputs"; +} +rinput:Rewrite{ + file = "operational_semantics/all_inputs_reduced"; +} + +outputs:Match{ + file = "operational_semantics/all_outputs"; +} +routput:Rewrite{ + file = "operational_semantics/all_outputs_increased"; +} + +p:Print{ + event = True; +} +p2:Print{ + event = False; + custom = `"succesfully execuded a petrinet transition"`; +} + +l:Loop +l2:Loop +l3:Loop + + +:Conn_exec (start -> m) {from="out"; to="in";} +:Conn_exec (m -> l) {from="success"; to="in";} +:Conn_exec (l -> nac1) {from="it"; to="in";} +:Conn_exec (l -> end) {from="out"; to="in";} +:Conn_exec (nac1 -> l) {from="success"; to="in";} +:Conn_exec (nac1 -> inputs) {from="fail"; to="in";} +:Conn_exec (inputs -> l2) {from="success"; to="in";} +:Conn_exec (inputs -> l2) {from="fail"; to="in";} +:Conn_exec (l2 -> rinput) {from="it"; to="in";} +:Conn_exec (rinput -> l2) {from="out"; to="in";} +:Conn_exec (l2 -> outputs) {from="out"; to="in";} +:Conn_exec (outputs -> l3) {from="success"; to="in";} +:Conn_exec (outputs -> l3) {from="fail"; to="in";} +:Conn_exec (l3 -> routput) {from="it"; to="in";} +:Conn_exec (routput -> l3) {from="out"; to="in";} +:Conn_exec (l3 -> p2) {from="out"; to="in";} +:Conn_exec (p2 -> end) {from="out"; to="in";} + + +:Conn_data (m -> l) {from="out"; to="in";} +:Conn_data (l -> nac1) {from="out"; to="in";} +:Conn_data (l -> inputs) {from="out"; to="in";} +:Conn_data (inputs -> l2) {from="out"; to="in";} +:Conn_data (l2 -> rinput) {from="out"; to="in";} +:Conn_data (l -> outputs) {from="out"; to="in";} +:Conn_data (outputs -> l3) {from="out"; to="in";} +:Conn_data (l3 -> routput) {from="out"; to="in";} \ No newline at end of file diff --git a/examples/petrinet/models/schedules/petrinet2.drawio b/examples/petrinet/models/schedules/petrinet2.drawio new file mode 100644 index 0000000..6294d7f --- /dev/null +++ b/examples/petrinet/models/schedules/petrinet2.drawio @@ -0,0 +1,1160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/petrinet/models/schedules/recursion.drawio b/examples/petrinet/models/schedules/recursion.drawio new file mode 100644 index 0000000..f82cabd --- /dev/null +++ b/examples/petrinet/models/schedules/recursion.drawio @@ -0,0 +1,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/petrinet/models/schedules/schedule.od b/examples/petrinet/models/schedules/schedule.od new file mode 100644 index 0000000..8c8f816 --- /dev/null +++ b/examples/petrinet/models/schedules/schedule.od @@ -0,0 +1,4 @@ +start: Start +end: End + +:Conn_exec (start -> end) {from="tfuy"; to="in";} \ No newline at end of file diff --git a/examples/petrinet/operational_semantics/all_inputs_reduced.od b/examples/petrinet/operational_semantics/all_inputs_reduced.od new file mode 100644 index 0000000..a6bfdd4 --- /dev/null +++ b/examples/petrinet/operational_semantics/all_inputs_reduced.od @@ -0,0 +1,13 @@ +# A place with no tokens: + +p:RAM_PNPlace +ps:RAM_PNPlaceState { + RAM_numTokens = `get_value(this) -1`; +} +:RAM_pn_of (ps -> p) + +# An incoming arc from that place to our transition: + +t:RAM_PNTransition + +:RAM_arc (t -> p) diff --git a/examples/petrinet/operational_semantics/all_outputs.od b/examples/petrinet/operational_semantics/all_outputs.od new file mode 100644 index 0000000..ce5efd0 --- /dev/null +++ b/examples/petrinet/operational_semantics/all_outputs.od @@ -0,0 +1,13 @@ +# A place with no tokens: + +p:RAM_PNPlace +ps:RAM_PNPlaceState { + RAM_numTokens = `True`; +} +:RAM_pn_of (ps -> p) + +# An outgoing arc from that place to our transition: + +t:RAM_PNTransition + +:RAM_arc (t -> p) diff --git a/examples/petrinet/operational_semantics/all_outputs_increased.od b/examples/petrinet/operational_semantics/all_outputs_increased.od new file mode 100644 index 0000000..1fa1acb --- /dev/null +++ b/examples/petrinet/operational_semantics/all_outputs_increased.od @@ -0,0 +1,13 @@ +# A place with no tokens: + +p:RAM_PNPlace +ps:RAM_PNPlaceState { + RAM_numTokens = `get_value(this) + 1`; +} +:RAM_pn_of (ps -> p) + +# An outgoing arc from that place to our transition: + +t:RAM_PNTransition + +:RAM_arc (t -> p) diff --git a/examples/petrinet/operational_semantics/input_without_token.od b/examples/petrinet/operational_semantics/input_without_token.od new file mode 100644 index 0000000..9207ce2 --- /dev/null +++ b/examples/petrinet/operational_semantics/input_without_token.od @@ -0,0 +1,13 @@ +# A place with no tokens: + +p:RAM_PNPlace +ps:RAM_PNPlaceState { + RAM_numTokens = `get_value(this) == 0`; +} +:RAM_pn_of (ps -> p) + +# An incoming arc from that place to our transition: + +t:RAM_PNTransition + +:RAM_arc (p -> t) diff --git a/examples/petrinet/operational_semantics/transition.od b/examples/petrinet/operational_semantics/transition.od index c7c8203..c3bd82c 100644 --- a/examples/petrinet/operational_semantics/transition.od +++ b/examples/petrinet/operational_semantics/transition.od @@ -1 +1 @@ -tr:RAM_PNTransition \ No newline at end of file +t:RAM_PNTransition \ No newline at end of file diff --git a/examples/petrinet/petrinet_renderer.j2 b/examples/petrinet/petrinet_renderer.j2 new file mode 100644 index 0000000..0ace22b --- /dev/null +++ b/examples/petrinet/petrinet_renderer.j2 @@ -0,0 +1,12 @@ +digraph G { + rankdir=LR; + center=true; + margin=1; + nodesep=1; + subgraph places { + node [fontname=Arial,fontsize=10,shape=circle,fixedsize=true,label="", height=.35,width=.35]; + {% for place in places %} + {{ place[0] }} [label="{{ place[1] }}_{{ place[2] }}"] + {% endfor %} + } +} \ No newline at end of file diff --git a/examples/petrinet/renderer.py b/examples/petrinet/renderer.py index 278376a..3916311 100644 --- a/examples/petrinet/renderer.py +++ b/examples/petrinet/renderer.py @@ -1,3 +1,7 @@ +import os + +from jinja2 import Environment, FileSystemLoader + from api.od import ODAPI from concrete_syntax.graphviz.make_url import show_graphviz from concrete_syntax.graphviz.renderer import make_graphviz_id @@ -16,13 +20,24 @@ def render_tokens(num_tokens: int): return str(num_tokens) def render_petri_net_to_dot(od: ODAPI) -> str: + env = Environment( + loader=FileSystemLoader( + os.path.dirname(__file__) + ) + ) + env.trim_blocks = True + env.lstrip_blocks = True + template_dot = env.get_template("petrinet_renderer.j2") + with open("test_pet.dot", "w", encoding="utf-8") as f_dot: + places = [(make_graphviz_id(place), place_name, render_tokens(od.get_slot_value(od.get_source(od.get_incoming(place, "pn_of")[0]), "numTokens"))) for place_name, place in od.get_all_instances("PNPlace")] + f_dot.write(template_dot.render({"places": places})) dot = "" - dot += "rankdir=LR;" - dot += "center=true;" - dot += "margin=1;" - dot += "nodesep=1;" - dot += "subgraph places {" - dot += " node [fontname=Arial,fontsize=10,shape=circle,fixedsize=true,label=\"\", height=.35,width=.35];" + dot += "rankdir=LR;\n" + dot += "center=true;\n" + dot += "margin=1;\n" + dot += "nodesep=1;\n" + dot += "subgraph places {\n" + dot += " node [fontname=Arial,fontsize=10,shape=circle,fixedsize=true,label=\"\", height=.35,width=.35];\n" for place_name, place in od.get_all_instances("PNPlace"): # place_name = od.get_name(place) try: diff --git a/examples/petrinet/runner.py b/examples/petrinet/runner.py index df8692e..6df572a 100644 --- a/examples/petrinet/runner.py +++ b/examples/petrinet/runner.py @@ -1,19 +1,12 @@ -from examples.schedule.RuleExecuter import RuleExecuter -from state.devstate import DevState -from api.od import ODAPI +from icecream import ic + from concrete_syntax.textual_od.renderer import render_od -# from concrete_syntax.textual_od.renderer_jinja2 import render_od_jinja2 -from bootstrap.scd import bootstrap_scd +from transformation.schedule.Tests import Test_xmlparser from util import loader -from transformation.rule import RuleMatcherRewriter, ActionGenerator from transformation.ramify import ramify -from examples.semantics.operational import simulator from examples.petrinet.renderer import show_petri_net -from examples.schedule.ScheduledActionGenerator import * -from examples.schedule.RuleExecuter import * - - +from transformation.schedule.rule_scheduler import * if __name__ == "__main__": import os @@ -35,40 +28,26 @@ if __name__ == "__main__": # m_rt_initial_cs = m_cs + read_file('models/m_example_simple_rt_initial.od') # m_cs = read_file('models/m_example_mutex.od') # m_rt_initial_cs = m_cs + read_file('models/m_example_mutex_rt_initial.od') - m_cs = read_file('models/m_example_inharc.od') - m_rt_initial_cs = m_cs + read_file('models/m_example_inharc_rt_initial.od') + m_cs = read_file('models/m_example_simple.od') + m_rt_initial_cs = m_cs + read_file('models/m_example_simple_rt_initial.od') # Parse them mm = loader.parse_and_check(state, mm_cs, scd_mmm, "Petri-Net Design meta-model") mm_rt = loader.parse_and_check(state, mm_rt_cs, scd_mmm, "Petri-Net Runtime meta-model") m = loader.parse_and_check(state, m_cs, mm, "Example model") m_rt_initial = loader.parse_and_check(state, m_rt_initial_cs, mm_rt, "Example model initial state") - mm_rt_ramified = ramify(state, mm_rt) - rules = loader.load_rules(state, - lambda rule_name, kind: f"{THIS_DIR}/operational_semantics/r_{rule_name}_{kind}.od", - mm_rt_ramified, - ["fire_transition"]) # only 1 rule :( - # matcher_rewriter = RuleMatcherRewriter(state, mm_rt, mm_rt_ramified) - # action_generator = ActionGenerator(matcher_rewriter, rules) - matcher_rewriter2 = RuleExecuter(state, mm_rt, mm_rt_ramified) - action_generator = ScheduleActionGenerator(matcher_rewriter2, f"models/schedule.od") - def render_callback(od): - show_petri_net(od) - return render_od(state, od.m, od.mm) + action_generator = RuleSchedular(state, mm_rt, mm_rt_ramified, verbose=True, directory="models") - action_generator.generate_dot() + # if action_generator.load_schedule(f"petrinet.od"): + # if action_generator.load_schedule("schedules/combinatory.drawio"): + if action_generator.load_schedule("schedules/petrinet3.drawio"): - sim = simulator.MinimalSimulator( - action_generator=action_generator, - decision_maker=simulator.InteractiveDecisionMaker(auto_proceed=False), - # decision_maker=simulator.RandomDecisionMaker(seed=0), - termination_condition=action_generator.termination_condition, - # renderer=lambda od: render_od(state, od.m, od.mm), - ) - sim.run(ODAPI(state, m_rt_initial, mm_rt)) \ No newline at end of file + action_generator.generate_dot("../dot.dot") + code, message = action_generator.run(ODAPI(state, m_rt_initial, mm_rt)) + print(f"{code}: {message}") diff --git a/examples/schedule/RuleExecuter.py b/examples/schedule/RuleExecuter.py deleted file mode 100644 index 8566d10..0000000 --- a/examples/schedule/RuleExecuter.py +++ /dev/null @@ -1,49 +0,0 @@ -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 deleted file mode 100644 index 0f91121..0000000 --- a/examples/schedule/ScheduledActionGenerator.py +++ /dev/null @@ -1,104 +0,0 @@ -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.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/generator.py b/examples/schedule/generator.py deleted file mode 100644 index ed8a111..0000000 --- a/examples/schedule/generator.py +++ /dev/null @@ -1,129 +0,0 @@ -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 deleted file mode 100644 index 5767d48..0000000 --- a/examples/schedule/models/README.md +++ /dev/null @@ -1,26 +0,0 @@ - -### 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 deleted file mode 100644 index 533d8bc..0000000 --- a/examples/schedule/models/scheduling_MM.od +++ /dev/null @@ -1,46 +0,0 @@ -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 deleted file mode 100644 index 0b826ab..0000000 --- a/examples/schedule/schedule_lib/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 88bcb42..0000000 --- a/examples/schedule/schedule_lib/data.py +++ /dev/null @@ -1,63 +0,0 @@ -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 deleted file mode 100644 index 0df6cba..0000000 --- a/examples/schedule/schedule_lib/data_modify.py +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index 557f297..0000000 --- a/examples/schedule/schedule_lib/data_node.py +++ /dev/null @@ -1,47 +0,0 @@ -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 deleted file mode 100644 index 2a008c4..0000000 --- a/examples/schedule/schedule_lib/end.py +++ /dev/null @@ -1,21 +0,0 @@ -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 deleted file mode 100644 index c5d2d04..0000000 --- a/examples/schedule/schedule_lib/exec_node.py +++ /dev/null @@ -1,34 +0,0 @@ -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 deleted file mode 100644 index 0b19b99..0000000 --- a/examples/schedule/schedule_lib/funcs.py +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index d1f4b25..0000000 --- a/examples/schedule/schedule_lib/id_generator.py +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index 44ec5c5..0000000 --- a/examples/schedule/schedule_lib/loop.py +++ /dev/null @@ -1,57 +0,0 @@ -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 deleted file mode 100644 index f350ba6..0000000 --- a/examples/schedule/schedule_lib/match.py +++ /dev/null @@ -1,42 +0,0 @@ -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 deleted file mode 100644 index 2d322bb..0000000 --- a/examples/schedule/schedule_lib/null_node.py +++ /dev/null @@ -1,25 +0,0 @@ -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 deleted file mode 100644 index ed0bbc6..0000000 --- a/examples/schedule/schedule_lib/print.py +++ /dev/null @@ -1,28 +0,0 @@ -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 deleted file mode 100644 index c00ee8e..0000000 --- a/examples/schedule/schedule_lib/rewrite.py +++ /dev/null @@ -1,38 +0,0 @@ -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/start.py b/examples/schedule/schedule_lib/start.py deleted file mode 100644 index 44ed1e1..0000000 --- a/examples/schedule/schedule_lib/start.py +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index 39d2672..0000000 --- a/examples/schedule/templates/schedule_dot.j2 +++ /dev/null @@ -1,9 +0,0 @@ -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 deleted file mode 100644 index a0c251c..0000000 --- a/examples/schedule/templates/schedule_template.j2 +++ /dev/null @@ -1,35 +0,0 @@ -{% 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 deleted file mode 100644 index 389f2c2..0000000 --- a/examples/schedule/templates/schedule_template_wrap.j2 +++ /dev/null @@ -1,47 +0,0 @@ -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 diff --git a/requirements.txt b/requirements.txt index 2105b38..179f66d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ lark==1.1.9 -jinja2==3.1.4 \ No newline at end of file +jinja2==3.1.4 +git+https://msdl.uantwerpen.be/git/jexelmans/drawio2py \ No newline at end of file diff --git a/transformation/schedule/Tests/Test_meta_model.py b/transformation/schedule/Tests/Test_meta_model.py new file mode 100644 index 0000000..47392c1 --- /dev/null +++ b/transformation/schedule/Tests/Test_meta_model.py @@ -0,0 +1,489 @@ +import io +import os +import sys +import unittest + +sys.path.insert( + 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../")) +) + +from icecream import ic + +from api.od import ODAPI +from bootstrap.scd import bootstrap_scd +from examples.schedule import rule_schedular +from examples.schedule.rule_schedular import ScheduleActionGenerator +from state.devstate import DevState +from transformation.ramify import ramify +from util import loader + + +class Test_Meta_Model(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.dir = os.path.dirname(__file__) + state = DevState() + scd_mmm = bootstrap_scd(state) + with open(f"{cls.dir}/models/mm_petrinet.od") as file: + mm_s = file.read() + with open(f"{cls.dir}/models/m_petrinet.od") as file: + m_s = file.read() + mm = loader.parse_and_check(state, mm_s, scd_mmm, "mm") + m = loader.parse_and_check(state, m_s, mm, "m") + mm_rt_ramified = ramify(state, mm) + cls.model_param = (state, m, mm) + cls.generator_param = (state, mm, mm_rt_ramified) + + def setUp(self): + self.model = ODAPI(*self.model_param) + self.out = io.StringIO() + self.generator = ScheduleActionGenerator( + *self.generator_param, + directory=self.dir + "/models", + verbose=True, + outstream=self.out, + ) + + def _test_conformance( + self, file: str, expected_substr_err: dict[tuple[str, str], list[list[str]]] + ) -> None: + try: + self.generator.load_schedule(f"schedule/{file}") + errors = self.out.getvalue().split("\u25b8")[1:] + ic(errors) + if len(errors) != len(expected_substr_err.keys()): + ic("len total errors") + assert len(errors) == len(expected_substr_err.keys()) + for err in errors: + error_lines = err.strip().split("\n") + line = error_lines[0] + for key_pattern in expected_substr_err.keys(): + if (key_pattern[0] in line) and (key_pattern[1] in line): + key = key_pattern + break + else: + ic("no matching key") + ic(line) + assert False + expected = expected_substr_err[key] + if (len(error_lines) - 1) != len(expected): + ic("len substr errors") + ic(line) + assert (len(error_lines) - 1) == len(expected) + it = error_lines.__iter__() + it.__next__() + for err_line in it: + if not any( + all(exp in err_line for exp in line_exp) + for line_exp in expected + ): + ic("wrong substr error") + ic(line) + ic(error_lines) + assert False + expected_substr_err.pop(key) + except AssertionError: + raise + except Exception as e: + ic(e) + assert False + + def test_no_start(self): + self._test_conformance("no_start.od", {("Start", "Cardinality"): []}) + + def test_no_end(self): + self._test_conformance("no_end.od", {("End", "Cardinality"): []}) + + def test_multiple_start(self): + self._test_conformance("multiple_start.od", {("Start", "Cardinality"): []}) + + def test_multiple_end(self): + self._test_conformance("multiple_end.od", {("End", "Cardinality"): []}) + + def test_connections_start(self): + self._test_conformance( + "connections_start.od", + { + ("Start", "start"): [ + ["input exec", "foo_in", "exist"], + ["output exec", "out", "multiple"], + ["output exec", "foo_out", "exist"], + ["input data", "in", "exist"], + ] + }, + ) + + def test_connections_end(self): + self._test_conformance( + "connections_end.od", + { + ("End", "end"): [ + ["input exec", "foo_in", "exist"], + ["output exec", "foo_out", "exist"], + ["input data", "in", "multiple"], + ["input data", "out2", "exist"], + ["output data", "out", "exist"], + ] + }, + ) + + def test_connections_match(self): + self._test_conformance( + "connections_match.od", + { + ("Match", "m_foo"): [ + ["input exec", "foo_in", "exist"], + ["output exec", "foo", "exist"], + ["output exec", "fail", "multiple"], + ["input data", "foo_in", "exist"], + ["input data", "in", "multiple"], + ["output data", "foo_out", "exist"], + ] + }, + ) + + def test_connections_rewrite(self): + self._test_conformance( + "connections_rewrite.od", + { + ("Rewrite", "r_foo1"): [ + ["input exec", "foo_in", "exist"], + ["output exec", "foo", "exist"], + ], + ("Rewrite", "r_foo2"): [ + ["output exec", "out", "multiple"], + ["input data", "foo_in", "exist"], + ["input data", "in", "multiple"], + ["output data", "foo_out", "exist"], + ], + }, + ) + + def test_connections_action(self): + self._test_conformance( + "connections_action.od", + { + ("Action", "a_foo1"): [ + ["input exec", "foo_in", "exist"], + ["output exec", "out", "multiple"], + ["output exec", "foo", "exist"], + ["input data", "in1", "multiple"], + ], + ("Action", "a_foo2"): [ + ["input exec", "in", "exist"], + ["output exec", "out3", "multiple"], + ["output exec", "out", "exist"], + ["input data", "in", "exist"], + ["output data", "out", "exist"], + ], + }, + ) + + def test_connections_modify(self): + self._test_conformance( + "connections_modify.od", + { + ("Modify", "m_foo"): [ + ["input exec", "in", "exist"], + ["input exec", "in", "exist"], + ["output exec", "out", "exist"], + ["input data", "foo_in", "exist"], + ["output data", "foo_out", "exist"], + ["input data", "in", "multiple"], + ] + }, + ) + + def test_connections_merge(self): + self._test_conformance( + "connections_merge.od", + { + ("Merge", "m_foo"): [ + ["input exec", "in", "exist"], + ["input exec", "in", "exist"], + ["output exec", "out", "exist"], + ["input data", "foo_in", "exist"], + ["output data", "foo_out", "exist"], + ["input data", "in2", "multiple"], + ] + }, + ) + + def test_connections_store(self): + self._test_conformance( + "connections_store.od", + { + ("Store", "s_foo"): [ + ["input exec", "foo", "exist"], + ["output exec", "out", "multiple"], + ["output exec", "foo", "exist"], + ["input data", "foo_in", "exist"], + ["output data", "foo_out", "exist"], + ["input data", "2", "multiple"], + ], + }, + ) + + def test_connections_schedule(self): + self._test_conformance( + "connections_schedule.od", + { + ("Schedule", "s_foo"): [ + ["output exec", "out", "multiple"], + ["input data", "in2", "multiple"], + ] + }, + ) + + def test_connections_loop(self): + self._test_conformance( + "connections_loop.od", + { + ("Loop", "l_foo"): [ + ["input exec", "foo_in", "exist"], + ["output exec", "out", "multiple"], + ["output exec", "foo", "exist"], + ["input data", "foo_in", "exist"], + ["output data", "foo_out", "exist"], + ["input data", "in", "multiple"], + ] + }, + ) + + def test_connections_print(self): + self._test_conformance( + "connections_print.od", + { + ("Print", "p_foo"): [ + ["input exec", "foo_in", "exist"], + ["output exec", "out", "multiple"], + ["output exec", "foo", "exist"], + ["input data", "foo_in", "exist"], + ["output data", "out", "exist"], + ["input data", "in", "multiple"], + ] + }, + ) + + def test_fields_start(self): + self._test_conformance( + "fields_start.od", + { + ("Start", "Cardinality"): [], + ("Start", "string"): [ + ["Unexpected type", "ports_exec_out", "str"], + ["Unexpected type", "ports_data_out", "str"], + ], + ("Start", '"int"'): [ + ["Unexpected type", "ports_exec_out", "int"], + ["Unexpected type", "ports_data_out", "int"], + ], + ("Start", "tuple"): [ + ["Unexpected type", "ports_exec_out", "tuple"], + ["Unexpected type", "ports_data_out", "tuple"], + ], + ("Start", "dict"): [ + ["Unexpected type", "ports_exec_out", "dict"], + ["Unexpected type", "ports_data_out", "dict"], + ], + ("Start", "none"): [ + ["Unexpected type", "ports_exec_out", "NoneType"], + ["Unexpected type", "ports_data_out", "NoneType"], + ], + ("Start", "invalid"): [ + ["Invalid python", "ports_exec_out"], + ["Invalid python", "ports_data_out"], + ], + ("Start", "subtype"): [ + ["Unexpected type", "ports_exec_out", "list"], + ["Unexpected type", "ports_data_out", "list"], + ], + ("Start", "code"): [ + ["Unexpected type", "ports_exec_out"], + ["Unexpected type", "ports_data_out"], + ], + }, + ) + + def test_fields_end(self): + self._test_conformance( + "fields_end.od", + { + ("End", "Cardinality"): [], + ("End", "string"): [ + ["Unexpected type", "ports_exec_in", "str"], + ["Unexpected type", "ports_data_in", "str"], + ], + ("End", '"int"'): [ + ["Unexpected type", "ports_exec_in", "int"], + ["Unexpected type", "ports_data_in", "int"], + ], + ("End", "tuple"): [ + ["Unexpected type", "ports_exec_in", "tuple"], + ["Unexpected type", "ports_data_in", "tuple"], + ], + ("End", "dict"): [ + ["Unexpected type", "ports_exec_in", "dict"], + ["Unexpected type", "ports_data_in", "dict"], + ], + ("End", "none"): [ + ["Unexpected type", "ports_exec_in", "NoneType"], + ["Unexpected type", "ports_data_in", "NoneType"], + ], + ("End", "invalid"): [ + ["Invalid python", "ports_exec_in"], + ["Invalid python", "ports_data_in"], + ], + ("End", "subtype"): [ + ["Unexpected type", "ports_exec_in", "list"], + ["Unexpected type", "ports_data_in", "list"], + ], + ("End", "code"): [ + ["Unexpected type", "ports_exec_in"], + ["Unexpected type", "ports_data_in"], + ], + }, + ) + + def test_fields_action(self): + self._test_conformance( + "fields_action.od", + { + ("cardinality", "Action_action"): [], + ("Action", "string"): [ + ["Unexpected type", "ports_exec_out", "str"], + ["Unexpected type", "ports_exec_in", "str"], + ["Unexpected type", "ports_data_out", "str"], + ["Unexpected type", "ports_data_in", "str"], + ], + ("Action", '"int"'): [ + ["Unexpected type", "ports_exec_out", "int"], + ["Unexpected type", "ports_exec_in", "int"], + ["Unexpected type", "ports_data_out", "int"], + ["Unexpected type", "ports_data_in", "int"], + ], + ("Action", "tuple"): [ + ["Unexpected type", "ports_exec_out", "tuple"], + ["Unexpected type", "ports_exec_in", "tuple"], + ["Unexpected type", "ports_data_out", "tuple"], + ["Unexpected type", "ports_data_in", "tuple"], + ], + ("Action", "dict"): [ + ["Unexpected type", "ports_exec_out", "dict"], + ["Unexpected type", "ports_exec_in", "dict"], + ["Unexpected type", "ports_data_out", "dict"], + ["Unexpected type", "ports_data_in", "dict"], + ], + ("Action", "none"): [ + ["Unexpected type", "ports_exec_out", "NoneType"], + ["Unexpected type", "ports_exec_in", "NoneType"], + ["Unexpected type", "ports_data_out", "NoneType"], + ["Unexpected type", "ports_data_in", "NoneType"], + ], + ("Action", '"invalid"'): [ + ["Invalid python", "ports_exec_out"], + ["Invalid python", "ports_exec_in"], + ["Invalid python", "ports_data_out"], + ["Invalid python", "ports_data_in"], + ], + ("Action_action", "invalid_action"): [], + ("Action", "subtype"): [ + ["Unexpected type", "ports_exec_out", "list"], + ["Unexpected type", "ports_exec_in", "list"], + ["Unexpected type", "ports_data_out", "list"], + ["Unexpected type", "ports_data_in", "list"], + ], + ("Action", "code"): [ + ["Unexpected type", "ports_exec_out"], + ["Unexpected type", "ports_exec_in"], + ["Unexpected type", "ports_data_out"], + ["Unexpected type", "ports_data_in"], + ], + }, + ) + + def test_fields_modify(self): + self._test_conformance( + "fields_modify.od", + { + ("Modify", "string"): [ + ["Unexpected type", "rename", "str"], + ["Unexpected type", "delete", "str"], + ], + ("Modify", "list"): [["Unexpected type", "rename", "list"]], + ("Modify", "set"): [["Unexpected type", "rename", "set"]], + ("Modify", "tuple"): [ + ["Unexpected type", "rename", "tuple"], + ["Unexpected type", "delete", "tuple"], + ], + ("Modify", "dict"): [["Unexpected type", "delete", "dict"]], + ("Modify", "none"): [ + ["Unexpected type", "rename", "NoneType"], + ["Unexpected type", "delete", "NoneType"], + ], + ("Modify", "invalid"): [ + ["Invalid python", "rename"], + ["Invalid python", "delete"], + ], + ("Modify", "subtype"): [ + ["Unexpected type", "rename", "dict"], + ["Unexpected type", "delete", "list"], + ], + ("Modify", "code"): [ + ["Unexpected type", "rename"], + ["Unexpected type", "delete"], + ], + ("Modify", "joined"): [["rename", "delete", "disjoint"]], + }, + ) + + def test_fields_merge(self): + self._test_conformance( + "fields_merge.od", + { + ("cardinality", "Merge_ports_data_in"): [], + ("Merge", "string"): [["Unexpected type", "ports_data_in", "str"]], + ("Merge", "tuple"): [["Unexpected type", "ports_data_in", "tuple"]], + ("Merge", "dict"): [["Unexpected type", "ports_data_in", "dict"]], + ("Merge", "none"): [["Unexpected type", "ports_data_in", "NoneType"]], + ("Merge", "invalid"): [["Invalid python", "ports_data_in"]], + ("Merge", "subtype"): [["Unexpected type", "ports_data_in", "list"]], + ("Merge", "code"): [["Unexpected type", "ports_data_in"]], + ("Merge", "no"): [["Missing", "slot", "ports_data_in"]], + }, + ) + + def test_fields_store(self): + self._test_conformance( + "fields_store.od", + { + ("cardinality", "Store_ports"): [], + ("Store", "string"): [["Unexpected type", "ports", "str"]], + ("Store", "tuple"): [["Unexpected type", "ports", "tuple"]], + ("Store", "dict"): [["Unexpected type", "ports", "dict"]], + ("Store", "none"): [["Unexpected type", "ports", "NoneType"]], + ("Store", "invalid"): [["Invalid python", "ports"]], + ("Store", "subtype"): [["Unexpected type", "ports", "list"]], + ("Store", "code"): [["Unexpected type", "ports"]], + ("Store", "no"): [["Missing", "slot", "ports"]], + }, + ) + + def test_fields_print(self): + self._test_conformance( + "fields_print.od", + { + ("Print_custom", "list_custom"): [["Unexpected type", "custom", "list"]], + ("Print_custom", "set_custom"): [["Unexpected type", "custom", "set"]], + ("Print_custom", "tuple_custom"): [["Unexpected type", "custom", "tuple"]], + ("Print_custom", "dict_custom"): [["Unexpected type", "custom", "dict"]], + ("Print_custom", "none_custom"): [["Unexpected type", "custom", "NoneType"]], + ("Print_custom", "invalid_custom"): [["Invalid python", "custom"]], + ("Print_custom", "subtype_custom"): [["Unexpected type", "custom", "list"]], + ("Print_custom", "code_custom"): [["Unexpected type", "custom"]], + }, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/transformation/schedule/Tests/Test_xmlparser.py b/transformation/schedule/Tests/Test_xmlparser.py new file mode 100644 index 0000000..0530ee7 --- /dev/null +++ b/transformation/schedule/Tests/Test_xmlparser.py @@ -0,0 +1,45 @@ +import io +import os +import unittest + +from transformation.schedule import rule_scheduler +from transformation.schedule.rule_scheduler import RuleSchedular +from state.devstate import DevState + + +class MyTestCase(unittest.TestCase): + def setUp(self): + state = DevState() + self.generator = RuleSchedular(state, "", "") + + def test_empty(self): + try: + self.generator.generate_schedule( + f"{os.path.dirname(__file__)}/drawio/Empty.drawio" + ) + # buffer = io.BytesIO() + # self.generator.generate_dot(buffer) + except Exception as e: + assert False + + def test_simple(self): + try: + self.generator.generate_schedule( + f"{os.path.dirname(__file__)}/drawio/StartToEnd.drawio" + ) + # buffer = io.BytesIO() + # self.generator.generate_dot(buffer) + except Exception as e: + assert False + + # def test_unsupported(self): + # try: + # self.generator.generate_schedule("Tests/drawio/Unsupported.drawio") + # # buffer = io.BytesIO() + # # self.generator.generate_dot(buffer) + # except Exception as e: + # assert(False) + + +if __name__ == "__main__": + unittest.main() diff --git a/transformation/schedule/Tests/drawio/Empty.drawio b/transformation/schedule/Tests/drawio/Empty.drawio new file mode 100644 index 0000000..b025fbc --- /dev/null +++ b/transformation/schedule/Tests/drawio/Empty.drawio @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/transformation/schedule/Tests/drawio/StartToEnd.drawio b/transformation/schedule/Tests/drawio/StartToEnd.drawio new file mode 100644 index 0000000..c381120 --- /dev/null +++ b/transformation/schedule/Tests/drawio/StartToEnd.drawio @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/transformation/schedule/Tests/drawio/Unsupported.drawio b/transformation/schedule/Tests/drawio/Unsupported.drawio new file mode 100644 index 0000000..a9cf0fb --- /dev/null +++ b/transformation/schedule/Tests/drawio/Unsupported.drawio @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/transformation/schedule/Tests/models/m_petrinet.od b/transformation/schedule/Tests/models/m_petrinet.od new file mode 100644 index 0000000..f93a58b --- /dev/null +++ b/transformation/schedule/Tests/models/m_petrinet.od @@ -0,0 +1,22 @@ +p0:PNPlace +p1:PNPlace + +t0:PNTransition +:arc (p0 -> t0) +:arc (t0 -> p1) + +t1:PNTransition +:arc (p1 -> t1) +:arc (t1 -> p0) + +p0s:PNPlaceState { + numTokens = 1; +} + +:pn_of (p0s -> p0) + +p1s:PNPlaceState { + numTokens = 0; +} + +:pn_of (p1s -> p1) diff --git a/transformation/schedule/Tests/models/mm_petrinet.od b/transformation/schedule/Tests/models/mm_petrinet.od new file mode 100644 index 0000000..22986c3 --- /dev/null +++ b/transformation/schedule/Tests/models/mm_petrinet.od @@ -0,0 +1,31 @@ +# Places, transitions, arcs (and only one kind of arc) + +PNConnectable:Class { abstract = True; } + +arc:Association (PNConnectable -> PNConnectable) + +PNPlace:Class +PNTransition:Class + +# inhibitor arc +inh_arc:Association (PNPlace -> PNTransition) + +:Inheritance (PNPlace -> PNConnectable) +:Inheritance (PNTransition -> PNConnectable) + +# A place has a number of tokens, and that's it. + +PNPlaceState:Class +PNPlaceState_numTokens:AttributeLink (PNPlaceState -> Integer) { + name = "numTokens"; + optional = False; + constraint = `"numTokens cannot be negative" if get_value(get_target(this)) < 0 else None`; +} + +pn_of:Association (PNPlaceState -> PNPlace) { + # one-to-one + source_lower_cardinality = 1; + source_upper_cardinality = 1; + target_lower_cardinality = 1; + target_upper_cardinality = 1; +} \ No newline at end of file diff --git a/transformation/schedule/Tests/models/rules/transitions.od b/transformation/schedule/Tests/models/rules/transitions.od new file mode 100644 index 0000000..1b87f1d --- /dev/null +++ b/transformation/schedule/Tests/models/rules/transitions.od @@ -0,0 +1,13 @@ +# A place with no tokens: + +p:RAM_PNPlace +ps:RAM_PNPlaceState { + RAM_numTokens = `True`; +} +:RAM_pn_of (ps -> p) + +# An incoming arc from that place to our transition: + +t:RAM_PNTransition + +:RAM_arc (p -> t) diff --git a/transformation/schedule/Tests/models/schedule/connections_action.od b/transformation/schedule/Tests/models/schedule/connections_action.od new file mode 100644 index 0000000..02cb3cf --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/connections_action.od @@ -0,0 +1,62 @@ +start:Start { + ports_data_out = `["1", "2", "3"]`; +} + +m:Match{ + file="rules/transition.od"; +} +m2:Match{ + file="rules/transition.od"; +} +m3:Match{ + file="rules/transition.od"; +} + +a_void:Action{ + ports_data_in = `["in1", "in2"]`; + ports_data_out = `["out1", "out2"]`; + action=`print("hello foo1")`; +} + +a_foo1:Action{ + ports_data_in = `["in1", "in2"]`; + ports_data_out = `["out1", "out2"]`; + action=`print("hello foo1")`; +} + +a_foo2:Action{ + ports_exec_in = `["in2"]`; + ports_exec_out = `["out2", "out3"]`; + action=`print("hello foo2")`; +} + +end:End { + ports_data_in = `["1", "2", "3"]`; +} + +:Conn_exec (start -> m) {from="out";to="in";} +:Conn_exec (m -> m2) {from="fail";to="in";} +:Conn_exec (m -> m3) {from="success";to="in";} + +:Conn_exec (m2 -> a_foo1) {from="success";to="in";} +:Conn_exec (m2 -> a_foo1) {from="fail";to="in";} +:Conn_exec (m3 -> a_foo1) {from="success";to="foo_in";} +:Conn_exec (m3 -> a_foo2) {from="fail";to="in2";} + +:Conn_exec (a_foo1 -> a_foo2) {from="out";to="in";} +:Conn_exec (a_foo1 -> a_foo2) {from="out";to="in2";} +:Conn_exec (a_foo1 -> a_foo2) {from="foo";to="in2";} +:Conn_exec (a_foo2 -> end) {from="out";to="in";} +:Conn_exec (a_foo2 -> end) {from="out2";to="in";} +:Conn_exec (a_foo2 -> end) {from="out3";to="in";} +:Conn_exec (a_foo2 -> end) {from="out3";to="in";} + +:Conn_data (start -> a_foo2) {from="1";to="in";} +:Conn_data (a_foo2-> m2) {from="out";to="in";} + +:Conn_data (start -> a_foo1) {from="1";to="in1";} +:Conn_data (start -> a_foo1) {from="2";to="in1";} +:Conn_data (start -> a_foo1) {from="3";to="in2";} +:Conn_data (a_foo1 -> end) {from="out1";to="1";} +:Conn_data (a_foo1 -> end) {from="out1";to="2";} +:Conn_data (a_foo1 -> end) {from="out2";to="3";} \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/connections_end.od b/transformation/schedule/Tests/models/schedule/connections_end.od new file mode 100644 index 0000000..0bc355e --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/connections_end.od @@ -0,0 +1,31 @@ +start:Start + +m:Match{ + file="rules/transition.od"; +} +m2:Match{ + file="rules/transition.od"; +} +m3:Match{ + file="rules/transition.od"; +} +end:End { + ports_exec_in = `["out", "in"]`; + ports_data_in = `["out", "in"]`; +} + +:Conn_exec (start -> m) {from="out";to="in";} +:Conn_exec (m -> m2) {from="fail";to="in";} +:Conn_exec (m -> m3) {from="success";to="in";} + +:Conn_exec (m2 -> end) {from="success";to="in";} +:Conn_exec (m2 -> end) {from="fail";to="out";} +:Conn_exec (m3 -> end) {from="success";to="out";} +:Conn_exec (m3 -> end) {from="fail";to="foo_in";} +:Conn_exec (end -> m) {from="foo_out";to="in";} + +:Conn_data (m -> end) {from="out";to="in";} +:Conn_data (m2 -> end) {from="out";to="in";} +:Conn_data (m3 -> end) {from="out";to="out";} +:Conn_data (m3 -> end) {from="out";to="out2";} +:Conn_data (end -> m) {from="out";to="in";} \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/connections_loop.od b/transformation/schedule/Tests/models/schedule/connections_loop.od new file mode 100644 index 0000000..922281a --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/connections_loop.od @@ -0,0 +1,44 @@ +start:Start { + ports_data_out = `["1", "2", "3"]`; +} + +m:Match{ + file="rules/transition.od"; +} +m2:Match{ + file="rules/transition.od"; +} +m3:Match{ + file="rules/transition.od"; +} + +l:Loop +l_foo:Loop +l_void:Loop + +end:End { + ports_data_in = `["1", "2", "3"]`; +} + +:Conn_exec (start -> m) {from="out";to="in";} +:Conn_exec (m -> m2) {from="fail";to="in";} +:Conn_exec (m -> m3) {from="success";to="in";} + +:Conn_exec (m2 -> l_foo) {from="success";to="in";} +:Conn_exec (m2 -> l_foo) {from="fail";to="in";} +:Conn_exec (m3 -> l_foo) {from="success";to="foo_in";} + +:Conn_exec (l_foo -> l_foo) {from="out";to="in";} +:Conn_exec (l_foo -> end) {from="out";to="in";} +:Conn_exec (l_foo -> end) {from="it";to="in";} +:Conn_exec (l_foo -> end) {from="foo";to="in";} + +:Conn_data (start -> l) {from="1";to="in";} +:Conn_data (l -> m2) {from="out";to="in";} + +:Conn_data (start -> l_foo) {from="1";to="in";} +:Conn_data (start -> l_foo) {from="2";to="in";} +:Conn_data (start -> l_foo) {from="3";to="foo_in";} +:Conn_data (l_foo -> end) {from="out";to="1";} +:Conn_data (l_foo -> end) {from="out";to="2";} +:Conn_data (l_foo -> end) {from="foo_out";to="3";} \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/connections_match.od b/transformation/schedule/Tests/models/schedule/connections_match.od new file mode 100644 index 0000000..63a7f44 --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/connections_match.od @@ -0,0 +1,49 @@ +start:Start { + ports_data_out = `["1", "2", "3"]`; +} + +m:Match{ + file="rules/transition.od"; +} +m2:Match{ + file="rules/transition.od"; +} +m3:Match{ + file="rules/transition.od"; +} + +m_foo:Match{ + file="rules/transition.od"; +} + +m_void:Match{ + file="rules/transition.od"; +} + +end:End { + ports_data_in = `["1", "2", "3"]`; +} + +:Conn_exec (start -> m) {from="out";to="in";} +:Conn_exec (m -> m2) {from="fail";to="in";} +:Conn_exec (m -> m3) {from="success";to="in";} + +:Conn_exec (m2 -> m_foo) {from="success";to="in";} +:Conn_exec (m2 -> m_foo) {from="fail";to="in";} +:Conn_exec (m3 -> m_foo) {from="success";to="foo_in";} +:Conn_exec (m3 -> m_foo) {from="fail";to="in";} + +:Conn_exec (m_foo -> end) {from="fail";to="in";} +:Conn_exec (m_foo -> end) {from="success";to="in";} +:Conn_exec (m_foo -> end) {from="fail";to="in";} +:Conn_exec (m_foo -> end) {from="foo";to="in";} + +:Conn_data (start -> m) {from="1";to="in";} +:Conn_data (m -> m2) {from="out";to="in";} + +:Conn_data (start -> m_foo) {from="1";to="in";} +:Conn_data (start -> m_foo) {from="2";to="in";} +:Conn_data (start -> m_foo) {from="3";to="foo_in";} +:Conn_data (m_foo -> end) {from="out";to="1";} +:Conn_data (m_foo -> end) {from="out";to="2";} +:Conn_data (m_foo -> end) {from="foo_out";to="3";} \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/connections_merge.od b/transformation/schedule/Tests/models/schedule/connections_merge.od new file mode 100644 index 0000000..b564525 --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/connections_merge.od @@ -0,0 +1,42 @@ +start:Start { + ports_data_out = `["1", "2", "3"]`; +} + +m:Match{ + file="rules/transition.od"; +} +m2:Match{ + file="rules/transition.od"; +} +m3:Match{ + file="rules/transition.od"; +} + +m_foo:Merge { + ports_data_in = `["in1", "in2"]`; +} + +m_void:Merge { + ports_data_in = `["in1", "in2"]`; +} + +end:End { + ports_data_in = `["1", "2", "3"]`; +} + +:Conn_exec (start -> m) {from="out";to="in";} +:Conn_exec (m -> m2) {from="fail";to="in";} +:Conn_exec (m -> m3) {from="success";to="in";} + +:Conn_exec (m2 -> m_foo) {from="success";to="in";} +:Conn_exec (m2 -> m_foo) {from="fail";to="in";} + +:Conn_exec (m_foo -> end) {from="out";to="in";} + +:Conn_data (start -> m_foo) {from="1";to="in1";} +:Conn_data (start -> m_foo) {from="1";to="in2";} +:Conn_data (start -> m_foo) {from="2";to="in2";} +:Conn_data (start -> m_foo) {from="3";to="foo_in";} +:Conn_data (m_foo -> end) {from="out";to="1";} +:Conn_data (m_foo -> end) {from="out";to="2";} +:Conn_data (m_foo -> end) {from="foo_out";to="3";} \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/connections_modify.od b/transformation/schedule/Tests/models/schedule/connections_modify.od new file mode 100644 index 0000000..f3eebdc --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/connections_modify.od @@ -0,0 +1,41 @@ +start:Start { + ports_data_out = `["1", "2", "3"]`; +} + +m:Match{ + file="rules/transition.od"; +} +m2:Match{ + file="rules/transition.od"; +} +m3:Match{ + file="rules/transition.od"; +} + +m_foo:Modify +m_void:Modify + +mo:Modify + +end:End { + ports_data_in = `["1", "2", "3"]`; +} + +:Conn_exec (start -> m) {from="out";to="in";} +:Conn_exec (m -> m2) {from="fail";to="in";} +:Conn_exec (m -> m3) {from="success";to="in";} + +:Conn_exec (m2 -> m_foo) {from="success";to="in";} +:Conn_exec (m2 -> m_foo) {from="fail";to="in";} + +:Conn_exec (m_foo -> end) {from="out";to="in";} + +:Conn_data (start -> mo) {from="1";to="in";} +:Conn_data (mo -> m2) {from="out";to="in";} + +:Conn_data (start -> m_foo) {from="1";to="in";} +:Conn_data (start -> m_foo) {from="2";to="in";} +:Conn_data (start -> m_foo) {from="3";to="foo_in";} +:Conn_data (m_foo -> end) {from="out";to="1";} +:Conn_data (m_foo -> end) {from="out";to="2";} +:Conn_data (m_foo -> end) {from="foo_out";to="3";} \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/connections_print.od b/transformation/schedule/Tests/models/schedule/connections_print.od new file mode 100644 index 0000000..9bf9126 --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/connections_print.od @@ -0,0 +1,41 @@ +start:Start { + ports_data_out = `["1", "2", "3"]`; +} + +m:Match{ + file="rules/transition.od"; +} +m2:Match{ + file="rules/transition.od"; +} +m3:Match{ + file="rules/transition.od"; +} + +p_foo:Print +p_void:Print + +p:Print + +end:End + +:Conn_exec (start -> m) {from="out";to="in";} +:Conn_exec (m -> m2) {from="fail";to="in";} +:Conn_exec (m -> m3) {from="success";to="in";} + +:Conn_exec (m2 -> p_foo) {from="success";to="in";} +:Conn_exec (m2 -> p_foo) {from="fail";to="in";} +:Conn_exec (m3 -> p_foo) {from="success";to="foo_in";} +:Conn_exec (m3 -> p) {from="fail";to="in";} +:Conn_exec (p -> end) {from="out";to="in";} + +:Conn_exec (p_foo -> p_foo) {from="out";to="in";} +:Conn_exec (p_foo -> end) {from="out";to="in";} +:Conn_exec (p_foo -> end) {from="foo";to="in";} + +:Conn_data (start -> p) {from="1";to="in";} + +:Conn_data (start -> p_foo) {from="1";to="in";} +:Conn_data (start -> p_foo) {from="2";to="in";} +:Conn_data (start -> p_foo) {from="3";to="foo_in";} +:Conn_data (p_foo -> m2) {from="out";to="in";} \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/connections_rewrite.od b/transformation/schedule/Tests/models/schedule/connections_rewrite.od new file mode 100644 index 0000000..7e1b018 --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/connections_rewrite.od @@ -0,0 +1,52 @@ +start:Start { + ports_data_out = `["1", "2", "3"]`; +} +m:Match{ + file="rules/transition.od"; +} +m2:Match{ + file="rules/transition.od"; +} +m3:Match{ + file="rules/transition.od"; +} + +r_foo1:Rewrite{ + file="rules/transition.od"; +} + +r_foo2:Rewrite{ + file="rules/transition.od"; +} +r_void:Rewrite{ + file="rules/transition.od"; +} + +end:End { + ports_data_in = `["1", "2", "3"]`; +} + + +:Conn_exec (start -> m) {from="out";to="in";} +:Conn_exec (m -> m2) {from="fail";to="in";} +:Conn_exec (m -> m3) {from="success";to="in";} + +:Conn_exec (m2 -> r_foo1) {from="success";to="in";} +:Conn_exec (m2 -> r_foo1) {from="fail";to="in";} +:Conn_exec (m3 -> r_foo1) {from="success";to="foo_in";} +:Conn_exec (m3 -> r_foo1) {from="fail";to="in";} + +:Conn_exec (r_foo1 -> r_foo2) {from="out";to="in";} +:Conn_exec (r_foo1 -> end) {from="foo";to="in";} +:Conn_exec (r_foo2 -> end) {from="out";to="in";} +:Conn_exec (r_foo2 -> end) {from="out";to="in";} + +:Conn_data (start -> r_foo1) {from="1";to="in";} +:Conn_data (r_foo1-> m2) {from="out";to="in";} + +:Conn_data (start -> r_foo2) {from="1";to="in";} +:Conn_data (start -> r_foo2) {from="2";to="in";} +:Conn_data (start -> r_foo2) {from="3";to="foo_in";} +:Conn_data (r_foo2 -> end) {from="out";to="1";} +:Conn_data (r_foo2 -> end) {from="out";to="2";} +:Conn_data (r_foo2 -> end) {from="foo_out";to="3";} \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/connections_schedule.od b/transformation/schedule/Tests/models/schedule/connections_schedule.od new file mode 100644 index 0000000..a2e3c25 --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/connections_schedule.od @@ -0,0 +1,50 @@ +start:Start { + ports_data_out = `["1", "2", "3"]`; +} + +m:Match{ + file="rules/transition.od"; +} +m2:Match{ + file="rules/transition.od"; +} +m3:Match{ + file="rules/transition.od"; +} + +s_foo:Schedule{ + file="hello.od"; +} + +s_void:Schedule{ + file="hello.od"; +} + +end:End { + ports_data_in = `["1", "2", "3"]`; +} + +:Conn_exec (start -> m) {from="out";to="in";} +:Conn_exec (m -> m2) {from="fail";to="in";} +:Conn_exec (m -> m3) {from="success";to="in";} + +:Conn_exec (m2 -> s_foo) {from="success";to="in";} +:Conn_exec (m2 -> s_foo) {from="fail";to="in";} +:Conn_exec (m3 -> s_foo) {from="success";to="foo";} +:Conn_exec (m3 -> s_foo) {from="fail";to="foo2";} + +:Conn_exec (s_foo -> s_foo) {from="out";to="in";} +:Conn_exec (s_foo -> s_foo) {from="out";to="in2";} +:Conn_exec (s_foo -> s_foo) {from="foo";to="foo3";} +:Conn_exec (s_foo -> end) {from="out4";to="in";} +:Conn_exec (s_foo -> end) {from="out2";to="in";} +:Conn_exec (s_foo -> end) {from="out5";to="in";} +:Conn_exec (s_foo -> end) {from="out3";to="in";} + +:Conn_data (start -> s_foo) {from="1";to="in1";} +:Conn_data (start -> s_foo) {from="1";to="in2";} +:Conn_data (start -> s_foo) {from="2";to="in2";} +:Conn_data (start -> s_foo) {from="3";to="foo_in";} +:Conn_data (s_foo -> end) {from="out";to="1";} +:Conn_data (s_foo -> end) {from="out";to="2";} +:Conn_data (s_foo -> end) {from="foo_out";to="3";} \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/connections_start.od b/transformation/schedule/Tests/models/schedule/connections_start.od new file mode 100644 index 0000000..2ade389 --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/connections_start.od @@ -0,0 +1,27 @@ +start:Start { + ports_exec_out = `["out", "in"]`; + ports_data_out = `["out", "in"]`; +} + +m:Match{ + file="rules/transition.od"; +} +m2:Match{ + file="rules/transition.od"; +} +m3:Match{ + file="rules/transition.od"; +} +end:End + +:Conn_exec (start -> m) {from="out";to="in";} +:Conn_exec (start -> m) {from="out";to="in";} +:Conn_exec (start -> m) {from="in";to="in";} +:Conn_exec (start -> m) {from="foo_out";to="in";} +:Conn_exec (m -> start) {from="fail";to="foo_in";} +:Conn_exec (m -> end) {from="success";to="in";} + +:Conn_data (start -> m) {from="out";to="in";} +:Conn_data (start -> m2) {from="out";to="in";} +:Conn_data (start -> m3) {from="in";to="in";} +:Conn_data (m -> start) {from="out";to="in";} \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/connections_store.od b/transformation/schedule/Tests/models/schedule/connections_store.od new file mode 100644 index 0000000..a3e4477 --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/connections_store.od @@ -0,0 +1,47 @@ +start:Start { + ports_data_out = `["1", "2", "3"]`; +} + +m:Match{ + file="rules/transition.od"; +} +m2:Match{ + file="rules/transition.od"; +} +m3:Match{ + file="rules/transition.od"; +} + +s_foo:Store { + ports = `["1", "2", "3"]`; +} + +s_void:Store { + ports = `["1", "2", "3"]`; +} + +end:End { + ports_data_in = `["1", "2", "3"]`; +} + +:Conn_exec (start -> m) {from="out";to="in";} +:Conn_exec (m -> m2) {from="fail";to="in";} +:Conn_exec (m -> m3) {from="success";to="in";} + +:Conn_exec (m2 -> s_foo) {from="success";to="in";} +:Conn_exec (m2 -> s_foo) {from="fail";to="in";} +:Conn_exec (m3 -> s_foo) {from="success";to="1";} +:Conn_exec (m3 -> s_foo) {from="fail";to="foo";} + +:Conn_exec (s_foo -> end) {from="out";to="in";} +:Conn_exec (s_foo -> s_foo) {from="1";to="2";} +:Conn_exec (s_foo -> end) {from="out";to="in";} +:Conn_exec (s_foo -> s_foo) {from="foo";to="2";} + +:Conn_data (start -> s_foo) {from="1";to="1";} +:Conn_data (start -> s_foo) {from="1";to="2";} +:Conn_data (start -> s_foo) {from="2";to="2";} +:Conn_data (start -> s_foo) {from="3";to="foo_in";} +:Conn_data (s_foo -> end) {from="out";to="1";} +:Conn_data (s_foo -> end) {from="out";to="2";} +:Conn_data (s_foo -> end) {from="foo_out";to="3";} \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/fields_action.od b/transformation/schedule/Tests/models/schedule/fields_action.od new file mode 100644 index 0000000..6770059 --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/fields_action.od @@ -0,0 +1,83 @@ +string:Action { + ports_exec_in = `'["out", "in"]'`; + ports_exec_out = `'["out", "in"]'`; + ports_data_in = `'["out", "in"]'`; + ports_data_out = `'["out", "in"]'`; + action = `'["out", "in"]'`; +} + +int:Action { + ports_exec_in = `123`; + ports_exec_out = `123`; + ports_data_in = `123`; + ports_data_out = `123`; + action = `123`; +} + +list:Action { + ports_exec_out = `["out", "in"]`; + ports_exec_in = `["out", "in"]`; + ports_data_out = `["out", "in"]`; + ports_data_in = `["out", "in"]`; + action = `["out", "in"]`; +} +set:Action { + ports_exec_in = `{"out", "in"}`; + ports_exec_out = `{"out", "in"}`; + ports_data_in = `{"out", "in"}`; + ports_data_out = `{"out", "in"}`; + action = `{"out", "in"}`; +} + +tuple:Action { + ports_exec_in = `("out", "in")`; + ports_exec_out = `("out", "in")`; + ports_data_in = `("out", "in")`; + ports_data_out = `("out", "in")`; + action = `("out", "in")`; +} + +dict:Action { + ports_exec_in = `{"out": "in"}`; + ports_exec_out = `{"out": "in"}`; + ports_data_in = `{"out": "in"}`; + ports_data_out = `{"out": "in"}`; + action = `{"out": "in"}`; +} + +none:Action { + ports_exec_in = `None`; + ports_exec_out = `None`; + ports_data_in = `None`; + ports_data_out = `None`; + action = `None`; +} + +invalid:Action { + ports_exec_in = `[{a(0)['qkja("fyvka`; + ports_exec_out = `[{a(0)['qkja("fyvka`; + ports_data_in = `["", [{]]`; + ports_data_out = `["", [{]]`; + action = `hu(ja&{]8}]`; +} + +subtype:Action { + ports_exec_in = `[1, 2]`; + ports_exec_out = `[1, 2]`; + ports_data_in = `[1, 2]`; + ports_data_out = `[1, 2]`; + action = `[1, 2]`; +} + +code:Action { + ports_exec_in = `print("hello world")`; + ports_exec_out = `print("hello world")`; + ports_data_in = `print("hello world")`; + ports_data_out = `print("hello world")`; + action = `print("hello world")`; +} + +no:Action + +start:Start +end:End \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/fields_end.od b/transformation/schedule/Tests/models/schedule/fields_end.od new file mode 100644 index 0000000..22a26ee --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/fields_end.od @@ -0,0 +1,52 @@ +start:Start + +string:End { + ports_exec_in = `'["out", "in"]'`; + ports_data_in = `'["out", "in"]'`; +} + +int:End { + ports_exec_in = `123`; + ports_data_in = `123`; +} + +list:End { + ports_exec_in = `["out", "in"]`; + ports_data_in = `["out", "in"]`; +} +set:End { + ports_exec_in = `{"out", "in"}`; + ports_data_in = `{"out", "in"}`; +} + +tuple:End { + ports_exec_in = `("out", "in")`; + ports_data_in = `("out", "in")`; +} + +dict:End { + ports_exec_in = `{"out": "in"}`; + ports_data_in = `{"out": "in"}`; +} + +none:End { + ports_exec_in = `None`; + ports_data_in = `None`; +} + +invalid:End { + ports_exec_in = `[{a(0)['qkja("fyvka`; + ports_data_in = `["", [{]]`; +} + +subtype:End { + ports_exec_in = `[1, 2]`; + ports_data_in = `[1, 2]`; +} + +code:End { + ports_exec_in = `print("hello world")`; + ports_data_in = `print("hello world")`; +} + +no:End \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/fields_merge.od b/transformation/schedule/Tests/models/schedule/fields_merge.od new file mode 100644 index 0000000..18e3307 --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/fields_merge.od @@ -0,0 +1,39 @@ +string:Merge { + ports_data_in = `'["out", "in"]'`; +} + +list:Merge { + ports_data_in = `["out", "in"]`; +} +set:Merge { + ports_data_in = `{"out", "in"}`; +} + +tuple:Merge { + ports_data_in = `("out", "in")`; +} + +dict:Merge { + ports_data_in = `{"out": "in"}`; +} + +none:Merge { + ports_data_in = `None`; +} + +invalid:Merge { + ports_data_in = `["", [{]]`; +} + +subtype:Merge { + ports_data_in = `[1, 2]`; +} + +code:Merge { + ports_data_in = `print("hello world")`; +} + +no:Merge + +start:Start +end:End \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/fields_modify.od b/transformation/schedule/Tests/models/schedule/fields_modify.od new file mode 100644 index 0000000..5730efb --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/fields_modify.od @@ -0,0 +1,51 @@ +string:Modify { + rename = `'["out", "in"]'`; + delete = `'["out", "in"]'`; +} + +list:Modify { + rename = `["out", "in"]`; + delete = `["out", "in"]`; +} +set:Modify { + rename = `{"out", "in"}`; + delete = `{"out", "in"}`; +} + +tuple:Modify { + rename = `("out", "in")`; + delete = `("out", "in")`; +} + +dict:Modify { + rename = `{"out": "in"}`; + delete = `{"out": "in"}`; +} + +none:Modify { + rename = `None`; + delete = `None`; +} + +invalid:Modify { + rename = `[{a(0)['qkja("fyvka`; + delete = `["", [{]]`; +} + +subtype:Modify { + rename = `{1: 2}`; + delete = `[1, 2]`; +} + +code:Modify { + rename = `print("hello world")`; + delete = `print("hello world")`; +} + +joined:Modify { + rename = `{"a":"1", "b":"2", "c":"3"}`; + delete = `{"a", "d"}`; +} + +start:Start +end:End \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/fields_print.od b/transformation/schedule/Tests/models/schedule/fields_print.od new file mode 100644 index 0000000..d520e44 --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/fields_print.od @@ -0,0 +1,39 @@ +string:Print { + custom = `'["port_out", "port_in"]'`; +} + +list:Print { + custom = `["port_out", "port_in"]`; +} +set:Print { + custom = `{"port_out", "port_in"}`; +} + +tuple:Print { + custom = `("port_out", "port_in")`; +} + +dict:Print { + custom = `{"port_out": "port_in"}`; +} + +none:Print { + custom = `None`; +} + +invalid:Print { + custom = `["", [{]]`; +} + +subtype:Print { + custom = `[1, 2]`; +} + +code:Print { + custom = `print("hello world")`; +} + +no:Print + +start:Start +end:End \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/fields_start.od b/transformation/schedule/Tests/models/schedule/fields_start.od new file mode 100644 index 0000000..c82ea91 --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/fields_start.od @@ -0,0 +1,52 @@ +string:Start { + ports_exec_out = `'["out", "in"]'`; + ports_data_out = `'["out", "in"]'`; +} + +int:Start { + ports_exec_out = `123`; + ports_data_out = `123`; +} + +list:Start { + ports_exec_out = `["out", "in"]`; + ports_data_out = `["out", "in"]`; +} +set:Start { + ports_exec_out = `{"out", "in"}`; + ports_data_out = `{"out", "in"}`; +} + +tuple:Start { + ports_exec_out = `("out", "in")`; + ports_data_out = `("out", "in")`; +} + +dict:Start { + ports_exec_out = `{"out": "in"}`; + ports_data_out = `{"out": "in"}`; +} + +none:Start { + ports_exec_out = `None`; + ports_data_out = `None`; +} + +invalid:Start { + ports_exec_out = `[{a(0)['qkja("fyvka`; + ports_data_out = `["", [{]]`; +} + +subtype:Start { + ports_exec_out = `[1, 2]`; + ports_data_out = `[1, 2]`; +} + +code:Start { + ports_exec_out = `print("hello world")`; + ports_data_out = `print("hello world")`; +} + +no:Start + +end:End \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/fields_store.od b/transformation/schedule/Tests/models/schedule/fields_store.od new file mode 100644 index 0000000..ec1f38c --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/fields_store.od @@ -0,0 +1,39 @@ +string:Store { + ports = `'["port_out", "port_in"]'`; +} + +list:Store { + ports = `["port_out", "port_in"]`; +} +set:Store { + ports = `{"port_out", "port_in"}`; +} + +tuple:Store { + ports = `("port_out", "port_in")`; +} + +dict:Store { + ports = `{"port_out": "port_in"}`; +} + +none:Store { + ports = `None`; +} + +invalid:Store { + ports = `["", [{]]`; +} + +subtype:Store { + ports = `[1, 2]`; +} + +code:Store { + ports = `print("hello world")`; +} + +no:Store + +start:Start +end:End \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/multiple_end.od b/transformation/schedule/Tests/models/schedule/multiple_end.od new file mode 100644 index 0000000..ae3651f --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/multiple_end.od @@ -0,0 +1,5 @@ +start:Start +end:End +end2:End + +:Conn_exec (start -> end) {from="out";to="in";} \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/multiple_start.od b/transformation/schedule/Tests/models/schedule/multiple_start.od new file mode 100644 index 0000000..0a869c8 --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/multiple_start.od @@ -0,0 +1,5 @@ +start:Start +start2:Start +end:End + +:Conn_exec (start -> end) {from="out";to="in";} \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/no_end.od b/transformation/schedule/Tests/models/schedule/no_end.od new file mode 100644 index 0000000..e58e470 --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/no_end.od @@ -0,0 +1 @@ +start:Start \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/no_start.od b/transformation/schedule/Tests/models/schedule/no_start.od new file mode 100644 index 0000000..36a7d96 --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/no_start.od @@ -0,0 +1 @@ +end:End \ No newline at end of file diff --git a/transformation/schedule/Tests/models/schedule/start_end.od b/transformation/schedule/Tests/models/schedule/start_end.od new file mode 100644 index 0000000..bf51e88 --- /dev/null +++ b/transformation/schedule/Tests/models/schedule/start_end.od @@ -0,0 +1,3 @@ +start:Start +end:End +:Conn_exec (start -> end) {from="out";to="in";} \ No newline at end of file diff --git a/transformation/schedule/__init__.py b/transformation/schedule/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/transformation/schedule/generator.py b/transformation/schedule/generator.py new file mode 100644 index 0000000..9fd08a0 --- /dev/null +++ b/transformation/schedule/generator.py @@ -0,0 +1,197 @@ +import sys +import os +from uuid import UUID + +from black.trans import Callable +from jinja2.runtime import Macro + +from api.od import ODAPI +from jinja2 import Environment, FileSystemLoader + + +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 + + conn_data_event = { + "Match": lambda item: False, + "Rewrite": lambda item: False, + "Modify": lambda item: True, + "Merge": lambda item: True, + "Loop": lambda item: True, + "Action": lambda item: _get_slot_value_default(item, "event", False), + "Print": lambda item: _get_slot_value_default(item, "event", False), + "Store": lambda item: False, + "Schedule": lambda item: False, + "End": lambda item: False, + } + + arg_map = { + "Loop": (name_dict := lambda item: {"name": self.api.get_name(item)}), + "Start": lambda item: { + **name_dict(item), + "ports_exec_out": eval( + self.api.get_slot_value_default(item, "ports_exec_out", "['out']") + ), + "ports_data_out": eval( + self.api.get_slot_value_default(item, "ports_data_out", "[]") + ), + }, + "End": lambda item: { + **name_dict(item), + "ports_exec_in": eval( + self.api.get_slot_value_default(item, "ports_exec_in", "['in']") + ), + "ports_data_in": eval( + self.api.get_slot_value_default(item, "ports_data_in", "[]") + ), + }, + "Rewrite": ( + file_dict := lambda item: { + **name_dict(item), + "file": self.api.get_slot_value(item, "file"), + } + ), + "Match": lambda item: { + **file_dict(item), + "n": self.api.get_slot_value_default(item, "n", 'float("inf")'), + }, + "Action": lambda item: { + **name_dict(item), + "ports_exec_in": self.api.get_slot_value_default(item, "ports_exec_in", ["in"]), + "ports_exec_out": self.api.get_slot_value_default(item, "ports_exec_out", ["out"]), + "ports_data_in": self.api.get_slot_value_default(item, "ports_data_in", []), + "ports_data_out": self.api.get_slot_value_default(item, "ports_data_out", []), + "action": repr(self.api.get_slot_value(item, "action")), + "init": repr( + self.api.get_slot_value_default(item, "init", "") + ), + }, + "Modify": lambda item: { + **name_dict(item), + "rename": eval(self.api.get_slot_value_default(item, "rename", "{}")), + "delete": eval(self.api.get_slot_value_default(item, "delete", "{}")), + }, + "Merge": lambda item: { + **name_dict(item), + "ports_data_in": eval( + self.api.get_slot_value_default(item, "ports_data_in", "[]") + ), + }, + "Store": lambda item: { + **name_dict(item), + "ports": eval(self.api.get_slot_value_default(item, "ports", "[]")), + }, + "Schedule": file_dict, + "Print": lambda item: { + **name_dict(item), + "label": self.api.get_slot_value_default(item, "label", ""), + "custom": self.api.get_slot_value_default(item, "custom", ""), + }, + "Conn_exec": ( + 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)), + "from": self.api.get_slot_value_default(item, "from", 0), + "to": self.api.get_slot_value_default(item, "to", 0), + } + ), + "Conn_data": lambda item: { + **conn_dict(item), + "event": conn_data_event[ + self.api.get_type_name(target := self.api.get_target(item)) + ](target), + }, + } + 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 _dfs( + self, stack: list[UUID], get_links: Callable, get_next_node: Callable + ) -> tuple[set[UUID], list[UUID]]: + visited = set() + connections = list() + while len(stack) > 0: + obj = stack.pop() + if obj in visited: + continue + visited.add(obj) + for conn in get_links(self.api, obj): + connections.append(conn) + stack.append(get_next_node(self.api, conn)) + return visited, connections + + def generate_schedule(self, stream=sys.stdout): + start = self.api.get_all_instances("Start")[0][1] + end = self.api.get_all_instances("End")[0][1] + out = { + "blocks": [], + "blocks_name": [], + "blocks_start_end": [], + "exec_conn": [], + "data_conn": [], + "match_files": set(), + "matchers": [], + "start": self.api.get_name(start), + "end": self.api.get_name(end), + } + + stack = [start, end] + exec_blocks, conn_exec = self._dfs( + stack, + lambda api, node: api.get_outgoing(node, "Conn_exec"), + lambda api, conn: api.get_target(conn), + ) + + for name, p in self.api.get_all_instances("Print"): + if self.api.has_slot(p, "event") and self.api.get_slot_value(p, "event"): + exec_blocks.add(p) + + stack = list(exec_blocks) + blocks, conn_data = self._dfs( + stack, + lambda api, node: api.get_incoming(node, "Conn_data"), + lambda api, conn: api.get_source(conn), + ) + + for exec_c in conn_exec: + out["exec_conn"].append(self._render(exec_c)) + + for data_c in conn_data: + out["data_conn"].append(self._render(data_c)) + + for block in blocks: + out["blocks_name"].append(self.api.get_name(block)) + if block in [start, end]: + out["blocks_start_end"].append(self._render(block)) + continue + out["blocks"].append(self._render(block)) + if self.api.is_instance(block, "Rule"): + d = self.macro_args[self.api.get_type_name(block)][1](block) + out["match_files"].add(d["file"]) + out["matchers"].append(d) + + print(self.template_wrap.render(out), file=stream) diff --git a/transformation/schedule/models/eval_context.py b/transformation/schedule/models/eval_context.py new file mode 100644 index 0000000..061b4f6 --- /dev/null +++ b/transformation/schedule/models/eval_context.py @@ -0,0 +1,151 @@ +from typing import TYPE_CHECKING, get_origin, get_args +from types import UnionType +from uuid import UUID + +from jinja2 import Template + +from framework.conformance import eval_context_decorator +from services.primitives.string_type import String + +if TYPE_CHECKING: + from api.od_stub_readonly import get_outgoing, get_incoming, get_slot_value, get_value, get_target, has_slot + from eval_context_stub import * + + +@eval_context_decorator +def _check_all_connections(this, labels: list[list[str] | str]) -> list[str]: + err = [] + check_incoming_exec(this, err, labels[0]) + check_outgoing_exec(this, err, labels[1]) + check_incoming_data(this, err, labels[2]) + check_outgoing_data(this, err, labels[3]) + return err + +@eval_context_decorator +def _check_outgoing_exec(this, err: list[str], labels: list[str]) -> None: + l = set(labels) + gates = set() + for y in get_outgoing(this, "Conn_exec"): + if (x := get_slot_value(y, "from")) not in l: + err.append(f"output exec gate '{x}' does not exist. Gates: {', '.join(labels)}.") + if x in gates: + err.append(f"output exec gate '{x}' is connected to multiple gates.") + gates.add(x) + + +@eval_context_decorator +def _check_incoming_exec(this, err: list[str], labels: list[str]) -> None: + l = set(labels) + for y in get_incoming(this, "Conn_exec"): + if (x := get_slot_value(y, "to")) not in l: + err.append(f"input exec gate gate '{x}' does not exist. Gates: {', '.join(labels)}.") + + +@eval_context_decorator +def _check_outgoing_data(this, err: list[str], labels: list[str]) -> None: + l = set(labels) + for y in get_outgoing(this, "Conn_data"): + if (x := get_slot_value(y, "from")) not in l: + err.append(f"output data gate '{x}' does not exist. Gates: {', '.join(labels)}.") + + +@eval_context_decorator +def _check_incoming_data(this, err: list[str], labels: list[str]) -> None: + l = set(labels) + gates = set() + for y in get_incoming(this, "Conn_data"): + if (x := get_slot_value(y, "to")) not in l: + err.append(f"input data gate '{x}' does not exist. Gates: {', '.join(labels)}.") + if x in gates: + err.append(f"input data gate '{x}' is connected to multiple gates.") + gates.add(x) + +def check_type(x: any, typ2: any) -> bool: + origin = get_origin(typ2) + if origin is None: + return isinstance(x, typ2) + args = get_args(typ2) + if origin is UnionType: + for tp in args: + if check_type(x, tp): + return True + return False + if not isinstance(x, origin): + return False + if origin in [list, set]: + for value in x: + if not check_type(value, args[0]): + return False + elif origin is tuple: + if len(args) != len(x): + return False + for i, value in enumerate(x): + if not check_type(value, args[i]): + return False + elif origin is dict: + for key, value in x.items(): + if not (check_type(key, args[0]) and check_type(value, args[1])): + return False + return True + +@eval_context_decorator +def _check_slot_code_type(this: UUID, slot: str, typ: type, unique = False, *, mandatory: bool = False, blacklist: list[str] | None = None) -> list[str]: + err = [] + if not (has_slot(this, slot)): + if mandatory: + err.append(f"Missing mandatory slot: '{slot}'.") + return err + try: + try: + x = eval(get_slot_value(this, slot)) + except Exception as _: + err.append(f"Invalid python code for {slot}: {get_slot_value(this, slot)}") + return err + + if not check_type(x, typ): + try: + typ_rep = typ.__name__ + except AttributeError: + typ_rep = str(typ) + err.append(f"Unexpected type for {slot}: {type(x).__name__}, expected type: {typ_rep}") + return err + + if unique and len(set(x)) != len(x): + err.append(f"elements must be unique") + return err + except Exception as e: + err.append(f"Unexpected error: {e}") + return err + + +@eval_context_decorator +def _check_jinja2_code(this: UUID, slot: str) -> list[str]: + if len(err:= check_slot_code_type(this, slot, str, mandatory=True)) != 0: + return err + s = eval(get_slot_value(this, slot)) + try: + template = Template(s) + template.render(**{"data":[{}]}) + return [] + except Exception as e: + return [f"Invalid Jinja2 syntax for '{slot}':\n{e}\n{s}"] + + +@eval_context_decorator +def _check_code_syntax(code) -> list[str]: + try: + compile(code, "", "exec") + return [] + except SyntaxError as e: + return [f"Invalid python code for: `{code}` :\n{e}"] + +mm_eval_context = { + "check_all_connections": _check_all_connections, + "check_outgoing_exec": _check_outgoing_exec, + "check_incoming_exec": _check_incoming_exec, + "check_outgoing_data": _check_outgoing_data, + "check_incoming_data": _check_incoming_data, + "check_slot_code_type": _check_slot_code_type, + "check_code_syntax": _check_code_syntax, + "check_jinja2_code": _check_jinja2_code, +} diff --git a/transformation/schedule/models/eval_context_stub.pyi b/transformation/schedule/models/eval_context_stub.pyi new file mode 100644 index 0000000..9811909 --- /dev/null +++ b/transformation/schedule/models/eval_context_stub.pyi @@ -0,0 +1,6 @@ +def check_outgoing_exec(this, err: list[str], labels: list[str]) -> bool: ... +def check_incoming_exec(this, err: list[str], labels: list[str]) -> bool: ... +def check_outgoing_data(this, err: list[str], labels: list[str]) -> bool: ... +def check_incoming_data(this, err: list[str], labels: list[str]) -> bool: ... +def check_is_type(s: str, typ: any) -> bool: ... +def check_code_syntax(code) -> bool: ... diff --git a/transformation/schedule/models/scheduling_MM.od b/transformation/schedule/models/scheduling_MM.od new file mode 100644 index 0000000..edae0b9 --- /dev/null +++ b/transformation/schedule/models/scheduling_MM.od @@ -0,0 +1,195 @@ +abstract class Exec + +association Conn_exec [0..*] Exec -> Exec [0..*] { + String from; + String to; +} + +abstract class Data +association Conn_data [0..*] Data -> Data [0..*] { + Integer from; + Integer to; +} +abstract class Node (Exec, Data) + +class Start [1..1] (Node) { + optional ActionCode ports_exec_out; + optional ActionCode ports_data_out; + ``` + err = check_slot_code_type(this, "ports_exec_out", list[str] | set[str], True) + err.extend(check_slot_code_type(this, "ports_data_out", list[str] | set[str], True)) + if len(err) == 0: + err = check_all_connections(this, [ + [], + eval(get_slot_value_default(this, "ports_exec_out", "['out']")), + [], + eval(get_slot_value_default(this, "ports_data_out", "[]")) + ]) + err + ```; +} +class End [1..1] (Node) { + optional ActionCode ports_exec_in; + optional ActionCode ports_data_in; + ``` + err = check_slot_code_type(this, "ports_exec_in", list[str] | set[str], True) + err.extend(check_slot_code_type(this, "ports_data_in", list[str] | set[str], True)) + if len(err) == 0: + err = check_all_connections(this, [ + eval(get_slot_value_default(this, "ports_exec_in", "['in']")), + [], + eval(get_slot_value_default(this, "ports_data_in", "[]")), + [] + ]) + err + ```; +} + +abstract class Rule (Node) +{ + String file; +} + +class Match (Rule) +{ + optional Integer n; + ``` + check_all_connections(this, [ + ["in"], + ["success", "fail"], + ["in"], + ["out"] + ]) + ```; +} + +class Rewrite (Rule) +{ + ``` + check_all_connections(this, [ + ["in"], + ["out"], + ["in"], + ["out"] + ]) + ```; +} + +class Action (Node) +{ + optional ActionCode ports_exec_in; + optional ActionCode ports_exec_out; + optional ActionCode ports_data_in; + optional ActionCode ports_data_out; + optional ActionCode init `check_code_syntax(get_value(get_target(this)))`; + ActionCode action `check_code_syntax(get_value(get_target(this)))`; + ``` + err = check_slot_code_type(this, "ports_exec_in", list[str] | set[str], True) + err.extend(check_slot_code_type(this, "ports_exec_out", list[str] | set[str], True)) + err.extend(check_slot_code_type(this, "ports_data_in", list[str] | set[str], True)) + err.extend(check_slot_code_type(this, "ports_data_out", list[str] | set[str], True)) + if len(err) == 0: + err = check_all_connections(this, [ + eval(get_slot_value_default(this, "ports_exec_in", "['in']")), + eval(get_slot_value_default(this, "ports_exec_out", "['out']")), + eval(get_slot_value_default(this, "ports_data_in", "[]")), + eval(get_slot_value_default(this, "ports_data_out", "[]")) + ]) + err + ```; + +} + +class Modify (Node) +{ + optional ActionCode rename; + optional ActionCode delete; + ``` + err = check_slot_code_type(this, "rename", dict[str,str]) + err.extend(check_slot_code_type(this, "delete", list[str] | set[str])) + if len(err) == 0: + if not (eval(get_slot_value_default(this, "rename", "dict()")).keys().isdisjoint( + eval(get_slot_value_default(this, "delete", "set()"))) + ): + err.append("rename and delete should be disjoint.") + err.extend(check_all_connections(this, [ + [], + [], + ["in"], + ["out"] + ])) + err + ```; +} + +class Merge (Node) +{ + ActionCode ports_data_in; + ``` + err = check_slot_code_type(this, "ports_data_in", list[str] | set[str], True, mandatory = True) + if len(err) == 0: + err = check_all_connections(this, [ + [], + [], + eval(get_slot_value(this, "ports_data_in")), + ["out"] + ]) + err + ```; +} + +class Store (Node) +{ + ActionCode ports; + ``` + err = check_slot_code_type(this, "ports", list[str] | set[str], True, mandatory = True, blacklist = ["in", "out"]) + if len(err) == 0: + err = check_all_connections(this, [ + [*(ports:= eval(get_slot_value(this, "ports"))), "in"], + [*ports, "out"], + ports, + ["out"] + ]) + err + ```; +} + +class Schedule (Node) +{ + String file; + ``` + check_all_connections(this, [ + {get_slot_value(conn, "to") for conn in get_incoming(this, "Conn_exec")}, + {get_slot_value(conn, "from") for conn in get_outgoing(this, "Conn_exec")}, + {get_slot_value(conn, "to") for conn in get_incoming(this, "Conn_data")}, + {get_slot_value(conn, "from") for conn in get_outgoing(this, "Conn_data")} + ]) + ```; +} + +class Loop(Node) +{ + ``` + check_all_connections(this, [ + ["in"], + ["it", "out"], + ["in"], + ["out"] + ]) + ```; +} + +class Print(Node) +{ + optional Boolean event; + optional String label; + optional ActionCode custom `check_jinja2_code(get_source(this), "custom")`; + ``` + check_all_connections(this, [ + ["in"], + ["out"], + ["in"], + [] + ]) + ```; +} \ No newline at end of file diff --git a/transformation/schedule/rule_executor.py b/transformation/schedule/rule_executor.py new file mode 100644 index 0000000..da97b2f --- /dev/null +++ b/transformation/schedule/rule_executor.py @@ -0,0 +1,46 @@ +from typing import Any +from uuid import UUID + +from api.od import ODAPI +from transformation.matcher import match_od +from transformation.rewriter import rewrite +from util.loader import parse_and_check + + +class RuleExecutor: + 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, od: ODAPI, rhs: UUID, *, pivot: dict[Any, Any]): + rhs = rewrite( + self.state, + rhs_m=rhs, + pattern_mm=self.mm_ramified, + lhs_match=pivot, + host_m=od.m, + host_mm=od.mm, + eval_context=self.eval_context, + ) + od.recompute_mappings() + yield rhs + + 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/transformation/schedule/rule_scheduler.py b/transformation/schedule/rule_scheduler.py new file mode 100644 index 0000000..474a6ab --- /dev/null +++ b/transformation/schedule/rule_scheduler.py @@ -0,0 +1,338 @@ +from __future__ import annotations + +import importlib.util +import io +import os +import re +import sys + +from pathlib import Path +from time import time +from typing import cast, TYPE_CHECKING + +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 transformation.schedule.rule_executor import RuleExecutor +from transformation.schedule.generator import schedule_generator +from transformation.schedule.models.eval_context import mm_eval_context +from transformation.schedule.schedule_lib import ExecNode, Start +from framework.conformance import Conformance, render_conformance_check_result, eval_context_decorator +from state.devstate import DevState +from examples.petrinet.renderer import render_petri_net_to_dot + +from drawio2py import parser +from drawio2py.abstract_syntax import DrawIOFile, Edge, Vertex, Cell +from icecream import ic + +from transformation.schedule.schedule_lib.funcs import IdGenerator + +if TYPE_CHECKING: + from transformation.schedule.schedule import Schedule + + +class RuleSchedular: + __slots__ = ( + "rule_executor", + "schedule_main", + "loaded", + "out", + "verbose", + "conformance", + "directory", + "eval_context", + "_state", + "_mmm_cs", + "sub_schedules", + "end_time", + ) + + def __init__( + self, + state, + mm_rt, + mm_rt_ramified, + *, + outstream=sys.stdout, + verbose: bool = False, + conformance: bool = True, + directory: str = "", + eval_context: dict[str, any] = None, + ): + self.rule_executor: RuleExecutor = RuleExecutor(state, mm_rt, mm_rt_ramified) + self.schedule_main: Schedule | None = None + self.out = outstream + self.verbose: bool = verbose + self.conformance: bool = conformance + self.directory: Path = Path.cwd() / directory + if eval_context is None: + eval_context = {} + self.eval_context: dict[str, any] = eval_context + + self.loaded: dict[str, dict[str, any]] = {"od": {}, "py": {}, "drawio": {}, "rules": {}} + + + self._state = DevState() + self._mmm_cs = bootstrap_scd(self._state) + + self.end_time = float("inf") + self.sub_schedules = float("inf") + + def load_schedule(self, filename): + return self._load_schedule(filename, _main=True) is not None + + + def _load_schedule(self, filename: str, *, _main = True) -> Schedule | None: + if filename.endswith(".drawio"): + if (filename := self._generate_schedule_drawio(filename)) is None: + return None + + if filename.endswith(".od"): + if (filename := self._generate_schedule_od(filename)) is None: + return None + if filename.endswith(".py"): + s = self._load_schedule_py(filename, _main=_main) + return s + + raise Exception(f"Error unknown file: {filename}") + + def _load_schedule_py(self, filename: str, *, _main = True) -> "Schedule": + if (s:= self.loaded["py"].get(filename, None)) is not None: + return s + + spec = importlib.util.spec_from_file_location(filename, str(self.directory / filename)) + schedule_module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(schedule_module) + self.loaded["py"][filename] = (s:= schedule_module.Schedule()) + if _main: + self.schedule_main = s + self.load_matchers(s) + return s + + def _generate_schedule_od(self, filename: str) -> str | None: + if (s:= self.loaded.get(("od", filename), None)) is not None: + return s + file = str(self.directory / filename) + self._print("Generating schedule ...") + with open(f"{os.path.dirname(__file__)}/models/scheduling_MM.od", "r") as f_MM: + mm_cs = f_MM.read() + try: + with open(file, "r") as f_M: + m_cs = f_M.read() + except FileNotFoundError: + self._print(f"File not found: {file}") + return None + + self._print("OK\n\nParsing models\n\tParsing meta model") + try: + scheduling_mm = parser_cd.parse_cd( + self._state, + m_text=mm_cs, + ) + except Exception as e: + self._print( + f"Error while parsing meta-model: scheduling_MM.od\n\t{e}" + ) + return None + self._print(f"\tParsing '{filename}' model") + try: + scheduling_m = parser_od.parse_od( + self._state, m_text=m_cs, mm=scheduling_mm + ) + except Exception as e: + self._print(f"\033[91mError while parsing model: {filename}\n\t{e}\033[0m") + return None + if self.conformance: + success = True + self._print("OK\n\tmeta-meta-model a valid class diagram") + conf_err = Conformance( + self._state, self._mmm_cs, self._mmm_cs + ).check_nominal() + b = len(conf_err) + success = success and not b + self._print( + f"\t\t{'\033[91m' if b else ''}{render_conformance_check_result(conf_err)}{'\033[0m' if b else ''}" + ) + self._print( + f"Is our '{filename}' model a valid 'scheduling_MM.od' diagram?" + ) + conf_err = Conformance( + self._state, scheduling_m, scheduling_mm, eval_context=mm_eval_context + ).check_nominal() + b = len(conf_err) + success = success and not b + self._print( + f"\t\t{'\033[91m' if b else ''}{render_conformance_check_result(conf_err)}{'\033[0m' if b else ''}" + ) + if not success: + return None + od = ODAPI(self._state, scheduling_m, scheduling_mm) + g = schedule_generator(od) + + output_buffer = io.StringIO() + g.generate_schedule(output_buffer) + outfilename = f"{".".join(filename.split(".")[:-1])}.py" + open(self.directory / outfilename, "w", encoding='utf-8').write(output_buffer.getvalue()) + self._print("Schedule generated") + self.loaded[("od", filename)] = outfilename + return outfilename + + def _print(self, *args) -> None: + if self.verbose: + print(*args, file=self.out) + + def load_matchers(self, schedule: "Schedule") -> None: + matchers = dict() + for file in schedule.get_matchers(): + if (r:= self.loaded.get(("rule", file), None)) is None: + self.loaded[("rule", file)] = (r:= self.rule_executor.load_match(self.directory / file)) + matchers[file] = r + schedule.init_schedule(self, self.rule_executor, matchers) + + def generate_dot(self, file: str) -> None: + 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() + for schedule in self.loaded["py"].values(): + schedule.generate_dot(nodes, edges, visit, template_dot) + with open(self.directory / file, "w") as f_dot: + f_dot.write(template_dot.render(nodes=nodes, edges=edges)) + + def run(self, model) -> tuple[int, str]: + self._print("Start simulation") + if 'pydevd' in sys.modules: + self.end_time = time() + 1000 + else: + self.end_time = time() + 10000 + return self._runner(model, self.schedule_main, "out", IdGenerator.generate_exec_id(), {}) + + def _runner(self, model, schedule: Schedule, exec_port: str, exec_id: int, data: dict[str, any]) -> tuple[int, any]: + self._generate_stackframe(schedule, exec_id) + cur_node = schedule.start + cur_node.run_init(exec_port, exec_id, data) + while self.end_time > time(): + cur_node, port = cur_node.nextState(exec_id) + termination_reason = cur_node.execute(port, exec_id, model) + if termination_reason is not None: + self._delete_stackframe(schedule, exec_id) + return termination_reason + + self._delete_stackframe(schedule, exec_id) + return -1, "limit reached" + + + def _generate_stackframe(self, schedule: Schedule, exec_id: int) -> None: + for node in schedule.nodes: + node.generate_stack_frame(exec_id) + + def _delete_stackframe(self, schedule: Schedule, exec_id: int) -> None: + for node in schedule.nodes: + node.delete_stack_frame(exec_id) + + + def _generate_schedule_drawio(self, filename:str) -> str | None: + if (s:= self.loaded["drawio"].get(filename, None)) is not None: + return s + env = Environment( + loader=FileSystemLoader( + os.path.join(os.path.dirname(__file__), "templates") + ) + ) + env.trim_blocks = True + env.lstrip_blocks = True + template = env.get_template("schedule_muMLE.j2") + main: bool = False + + node_map: dict[str, list[str | dict[str,str]]] + id_counter: int + def _get_node_id_map(elem: Cell) -> list[str | dict[str,str]]: + nonlocal node_map, id_counter + if (e_id := node_map.get(elem.id, None)) is None: + e_id = [f"{re.sub(r'[^a-zA-Z1-9_]', '', elem.properties["name"])}_{id_counter}", {}] + id_counter += 1 + node_map[elem.id] = e_id + return e_id + + edges: list[tuple[tuple[str, str, str, str], tuple[str,str,str,str]]] = [] + def _parse_edge(elem: Edge): + nonlocal edges + try: + edges.append(( + ( + _get_node_id_map(elem.source.parent.parent.parent)[0], + elem.source.properties["label"], + elem.source.properties["type"], + elem.source.parent.value + ), + ( + _get_node_id_map(elem.target.parent.parent.parent)[0], + elem.target.properties["label"], + elem.target.properties["type"], + elem.target.parent.value + ) + )) + except AttributeError as e: + raise Exception(f"Missing attribute {e}") + return + + def _parse_vertex(elem: Vertex): + nonlocal edges + try: + elem_map = _get_node_id_map(elem) + elem_map[1] = elem.properties + properties = elem_map[1] + properties.pop("label") + properties.pop("name") + properties.pop("placeholders") + if properties.get("type") == "Schedule": + if not re.search(r'\.(py|od)$', properties["file"]): + properties["file"] = f"{filename}/{properties["file"]}.od" + except AttributeError as e: + raise Exception(f"Missing attribute {e}") + return + + + abstract_syntax: DrawIOFile = parser.Parser.parse(str(self.directory / filename)) + filename = filename.removesuffix(".drawio") + (self.directory / filename).mkdir(parents=False, exist_ok=True) + for page in abstract_syntax.pages: + if page.name == "main": + main = True + if len(page.root.children) != 1: + raise Exception(f"Only 1 layer allowed (keybind: ctr+shift+L)") + edges = [] + id_counter = 1 + node_map = {} + + for element in page.root.children[0].children: + match element.__class__.__name__: + case "Edge": + _parse_edge(cast(Edge, element)) + case "Vertex": + _parse_vertex(cast(Vertex, element)) + for elem in element.children[0].children: + if elem.__class__.__name__ == "Edge": + _parse_edge(cast(Edge, elem)) + continue + case _: + raise Exception(f"Unexpected element: {element}") + with open(self.directory / f"{filename}/{page.name}.od", "w", encoding="utf-8") as f: + f.write(template.render(nodes=node_map, edges=edges)) + if main: + self.loaded["drawio"][filename] = (filename_out := f"{filename}/main.od") + return filename_out + + self._print("drawio schedule requires main page to automatically load.") + return None diff --git a/transformation/schedule/schedule.pyi b/transformation/schedule/schedule.pyi new file mode 100644 index 0000000..0e6547f --- /dev/null +++ b/transformation/schedule/schedule.pyi @@ -0,0 +1,18 @@ +from typing import TYPE_CHECKING +from transformation.schedule.schedule_lib import * +if TYPE_CHECKING: + from transformation.schedule.rule_executor import RuleExecutor + from rule_scheduler import RuleSchedular + +class Schedule: + __slots__ = { + "start", + "end", + "nodes" + } + def __init__(self): ... + + @staticmethod + def get_matchers(): ... + def init_schedule(self, schedular: RuleSchedular, rule_executor: RuleExecutor, matchers): ... + def generate_dot(self, *args, **kwargs): ... \ No newline at end of file diff --git a/transformation/schedule/schedule_lib/README.md b/transformation/schedule/schedule_lib/README.md new file mode 100644 index 0000000..078e00f --- /dev/null +++ b/transformation/schedule/schedule_lib/README.md @@ -0,0 +1,41 @@ +## Node Module + +Defines the abstract base Node class for graph-based structures. Each Node is assigned +a unique identifier via an external IdGenerator. The class provides an interface for +managing execution state and generating DOT graph representations using Jinja2 templates. + +### Class: `Node` + +- **Attributes** + - `id: int`: A unique identifier assigned to each instance upon initialization. + +- **Methods** + - `get_id` + - returns: `int`, The unique node ID + + Retrieves the unique identifier of the node. + + - `generate_stack_frame` + - exec_id: `int`, The ID of the execution context. + - returns: `None` + + Initializes a new state frame for a specific execution context. + Designed to be overridden in subclasses that use execution state. + + - `delete_stack_frame` + - exec_id: `int`, The ID of the execution context. + - returns: `None` + + Deletes the state frame for a specific execution context. + Designed to be overridden in subclasses that use execution state. + + - `generate_dot` + - nodes: `list[str]`, A list to append DOT node definitions to. + - edges: `list[str]`, A list to append DOT edges definitions to. + - visited: `set[str]`, A set of already visited node IDs to avoid duplicates or recursion. + - template: `list[str]`, A Jinja2 template used to format the node's DOT representation. + - returns: `None` + + Generates the DOT graph representation for this node and its relationships. + Must be implemented in subclasses. + \ No newline at end of file diff --git a/transformation/schedule/schedule_lib/Schedule_lib.xml b/transformation/schedule/schedule_lib/Schedule_lib.xml new file mode 100644 index 0000000..5dd1480 --- /dev/null +++ b/transformation/schedule/schedule_lib/Schedule_lib.xml @@ -0,0 +1,93 @@ +[ + { + "xml": "<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/><object label=\"%name%: %type%\" placeholders=\"1\" name=\"start_name\" type=\"Start\" ports_exec_out=\"[&quot;out&quot;]\" ports_data_out=\"[]\" id=\"2\"><mxCell style=\"shape=table;childLayout=tableLayout;startSize=40;collapsible=0;recursiveResize=1;expand=0;fontStyle=1;editable=1;movable=1;resizable=1;rotatable=0;deletable=1;locked=0;connectable=0;allowArrows=0;pointerEvents=0;perimeter=rectanglePerimeter;rounded=1;container=1;dropTarget=0;swimlaneHead=1;swimlaneBody=1;top=1;noLabel=0;autosize=0;resizeHeight=0;spacing=2;metaEdit=1;resizeWidth=0;arcSize=10;\" vertex=\"1\" parent=\"1\"><mxGeometry width=\"160\" height=\"100\" as=\"geometry\"/></mxCell></object><mxCell id=\"3\" value=\"\" style=\"shape=tableRow;horizontal=0;swimlaneHead=0;swimlaneBody=0;top=0;left=0;strokeColor=inherit;bottom=0;right=0;dropTarget=0;fontStyle=0;fillColor=none;points=[[0,0.5],[1,0.5]];startSize=0;collapsible=0;recursiveResize=1;expand=0;rounded=0;allowArrows=0;connectable=0;autosize=1;resizeHeight=1;rotatable=0;\" vertex=\"1\" parent=\"2\"><mxGeometry y=\"40\" width=\"160\" height=\"60\" as=\"geometry\"/></mxCell><mxCell id=\"4\" value=\"Input\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=40;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry width=\"80\" height=\"60\" as=\"geometry\"><mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/></mxGeometry></mxCell><mxCell id=\"5\" value=\"Output\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=40;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry x=\"80\" width=\"80\" height=\"60\" as=\"geometry\"><mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"out\" type=\"exec\" id=\"6\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"5\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object></root></mxGraphModel>", + "w": 160, + "h": 100, + "aspect": "fixed", + "title": "Start Node" + }, + { + "xml": "<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/><object label=\"%name%: %type%\" placeholders=\"1\" name=\"end_name\" type=\"End\" ports_exec_in=\"[&quot;in&quot;]\" ports_data_in=\"[]\" id=\"2\"><mxCell style=\"shape=table;childLayout=tableLayout;startSize=40;collapsible=0;recursiveResize=1;expand=0;fontStyle=1;editable=1;movable=1;resizable=1;rotatable=0;deletable=1;locked=0;connectable=0;allowArrows=0;pointerEvents=0;perimeter=rectanglePerimeter;rounded=1;container=1;dropTarget=0;swimlaneHead=1;swimlaneBody=1;top=1;noLabel=0;autosize=0;resizeHeight=0;spacing=2;metaEdit=1;resizeWidth=0;arcSize=10;\" vertex=\"1\" parent=\"1\"><mxGeometry width=\"160\" height=\"100\" as=\"geometry\"/></mxCell></object><mxCell id=\"3\" value=\"\" style=\"shape=tableRow;horizontal=0;swimlaneHead=0;swimlaneBody=0;top=0;left=0;strokeColor=inherit;bottom=0;right=0;dropTarget=0;fontStyle=0;fillColor=none;points=[[0,0.5],[1,0.5]];startSize=0;collapsible=0;recursiveResize=1;expand=0;rounded=0;allowArrows=0;connectable=0;autosize=1;resizeHeight=1;rotatable=0;\" vertex=\"1\" parent=\"2\"><mxGeometry y=\"40\" width=\"160\" height=\"60\" as=\"geometry\"/></mxCell><mxCell id=\"4\" value=\"Input\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=40;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry width=\"80\" height=\"60\" as=\"geometry\"><mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"in\" type=\"exec\" id=\"5\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><mxCell id=\"6\" value=\"Output\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=40;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry x=\"80\" width=\"80\" height=\"60\" as=\"geometry\"><mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/></mxGeometry></mxCell></root></mxGraphModel>", + "w": 160, + "h": 100, + "aspect": "fixed", + "title": "End Node" + }, + { + "xml": "<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/><object label=\"%name%: %type%&#10;%file%&#10;matches: %n%\" placeholders=\"1\" name=\"match_name\" type=\"Match\" file=\"rule_filename.od\" n=\"1\" id=\"2\"><mxCell style=\"shape=table;childLayout=tableLayout;startSize=60;collapsible=0;recursiveResize=1;expand=0;fontStyle=1;editable=1;movable=1;resizable=1;rotatable=0;deletable=1;locked=0;connectable=0;allowArrows=0;pointerEvents=0;perimeter=rectanglePerimeter;rounded=1;container=1;dropTarget=0;swimlaneHead=1;swimlaneBody=1;top=1;noLabel=0;autosize=0;resizeHeight=0;spacing=2;metaEdit=1;resizeWidth=0;arcSize=10;\" vertex=\"1\" parent=\"1\"><mxGeometry width=\"160\" height=\"220\" as=\"geometry\"/></mxCell></object><mxCell id=\"3\" value=\"\" style=\"shape=tableRow;horizontal=0;swimlaneHead=0;swimlaneBody=0;top=0;left=0;strokeColor=inherit;bottom=0;right=0;dropTarget=0;fontStyle=0;fillColor=none;points=[[0,0.5],[1,0.5]];startSize=0;collapsible=0;recursiveResize=1;expand=0;rounded=0;allowArrows=0;connectable=0;autosize=1;resizeHeight=1;rotatable=0;\" vertex=\"1\" parent=\"2\"><mxGeometry y=\"60\" width=\"160\" height=\"160\" as=\"geometry\"/></mxCell><mxCell id=\"4\" value=\"Input\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=60;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry width=\"80\" height=\"160\" as=\"geometry\"><mxRectangle width=\"80\" height=\"160\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"in\" type=\"data\" id=\"5\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"4\"><mxGeometry x=\"10\" y=\"110\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><object label=\"in\" type=\"exec\" id=\"6\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><mxCell id=\"7\" value=\"Output\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=40;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry x=\"80\" width=\"80\" height=\"160\" as=\"geometry\"><mxRectangle width=\"80\" height=\"160\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"out\" type=\"data\" id=\"8\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"7\"><mxGeometry x=\"10\" y=\"110\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><object label=\"success\" type=\"exec\" id=\"9\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"7\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><object label=\"fail\" type=\"exec\" id=\"10\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"7\"><mxGeometry x=\"10\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object></root></mxGraphModel>", + "w": 160, + "h": 220, + "aspect": "fixed", + "title": "Match Node" + }, + { + "xml": "<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/><object label=\"%name%: %type%&#10;%file%\" placeholders=\"1\" name=\"rewrite_name\" type=\"Rewrite\" file=\"rule_filename.od\" id=\"2\"><mxCell style=\"shape=table;childLayout=tableLayout;startSize=40;collapsible=0;recursiveResize=1;expand=0;fontStyle=1;editable=1;movable=1;resizable=1;rotatable=0;deletable=1;locked=0;connectable=0;allowArrows=0;pointerEvents=0;perimeter=rectanglePerimeter;rounded=1;container=1;dropTarget=0;swimlaneHead=1;swimlaneBody=1;top=1;noLabel=0;autosize=0;resizeHeight=0;spacing=2;metaEdit=1;resizeWidth=0;arcSize=10;\" vertex=\"1\" parent=\"1\"><mxGeometry y=\"1.1368683772161603e-13\" width=\"160\" height=\"150\" as=\"geometry\"/></mxCell></object><mxCell id=\"3\" value=\"\" style=\"shape=tableRow;horizontal=0;swimlaneHead=0;swimlaneBody=0;top=0;left=0;strokeColor=inherit;bottom=0;right=0;dropTarget=0;fontStyle=0;fillColor=none;points=[[0,0.5],[1,0.5]];startSize=0;collapsible=0;recursiveResize=1;expand=0;rounded=0;allowArrows=0;connectable=0;autosize=1;resizeHeight=1;rotatable=0;\" vertex=\"1\" parent=\"2\"><mxGeometry y=\"40\" width=\"160\" height=\"110\" as=\"geometry\"/></mxCell><mxCell id=\"4\" value=\"Input\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=60;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry width=\"80\" height=\"110\" as=\"geometry\"><mxRectangle width=\"80\" height=\"110\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"in\" type=\"exec\" id=\"5\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><mxCell id=\"6\" value=\"Output\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=40;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry x=\"80\" width=\"80\" height=\"110\" as=\"geometry\"><mxRectangle width=\"80\" height=\"110\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"out\" type=\"exec\" id=\"7\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"6\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><object label=\"in\" type=\"data\" id=\"8\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"6\"><mxGeometry x=\"-70\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><object label=\"out\" type=\"data\" id=\"9\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"6\"><mxGeometry x=\"10\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object></root></mxGraphModel>", + "w": 160, + "h": 150, + "aspect": "fixed", + "title": "Rewrite Node" + }, + { + "xml": "<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/><object label=\"%name%: %type%\" placeholders=\"1\" name=\"action_name\" type=\"Action\" ports_exec_in=\"[&quot;in&quot;]\" ports_exec_out=\"[&quot;out&quot;]\" ports_data_in=\"[]\" ports_data_out=\"[]\" action=\"print(&quot;hello world&quot;)\" id=\"2\"><mxCell style=\"shape=table;childLayout=tableLayout;startSize=40;collapsible=0;recursiveResize=1;expand=0;fontStyle=1;editable=1;movable=1;resizable=1;rotatable=0;deletable=1;locked=0;connectable=0;allowArrows=0;pointerEvents=0;perimeter=rectanglePerimeter;rounded=1;container=1;dropTarget=0;swimlaneHead=1;swimlaneBody=1;top=1;noLabel=0;autosize=0;resizeHeight=0;spacing=2;metaEdit=1;resizeWidth=0;arcSize=10;\" vertex=\"1\" parent=\"1\"><mxGeometry width=\"160\" height=\"100\" as=\"geometry\"/></mxCell></object><mxCell id=\"3\" value=\"\" style=\"shape=tableRow;horizontal=0;swimlaneHead=0;swimlaneBody=0;top=0;left=0;strokeColor=inherit;bottom=0;right=0;dropTarget=0;fontStyle=0;fillColor=none;points=[[0,0.5],[1,0.5]];startSize=0;collapsible=0;recursiveResize=1;expand=0;rounded=0;allowArrows=0;connectable=0;autosize=1;resizeHeight=1;rotatable=0;\" vertex=\"1\" parent=\"2\"><mxGeometry y=\"40\" width=\"160\" height=\"60\" as=\"geometry\"/></mxCell><mxCell id=\"4\" value=\"Input\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=60;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry width=\"80\" height=\"60\" as=\"geometry\"><mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"in\" type=\"exec\" id=\"5\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><mxCell id=\"6\" value=\"Output\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=40;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry x=\"80\" width=\"80\" height=\"60\" as=\"geometry\"><mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"out\" type=\"exec\" id=\"7\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"6\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object></root></mxGraphModel>", + "w": 160, + "h": 100, + "aspect": "fixed", + "title": "Action Node" + }, + { + "xml": "<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/><object label=\"%name%: %type%\" placeholders=\"1\" name=\"modify_name\" type=\"Modify\" rename=\"{&quot;t&quot;:&quot;transition&quot;}\" delete=\"[]\" id=\"2\"><mxCell style=\"shape=table;childLayout=tableLayout;startSize=40;collapsible=0;recursiveResize=1;expand=0;fontStyle=1;editable=1;movable=1;resizable=1;rotatable=0;deletable=1;locked=0;connectable=0;allowArrows=0;pointerEvents=0;perimeter=rectanglePerimeter;rounded=1;container=1;dropTarget=0;swimlaneHead=1;swimlaneBody=1;top=1;noLabel=0;autosize=0;resizeHeight=0;spacing=2;metaEdit=1;resizeWidth=0;arcSize=10;\" vertex=\"1\" parent=\"1\"><mxGeometry width=\"160\" height=\"100\" as=\"geometry\"/></mxCell></object><mxCell id=\"3\" value=\"\" style=\"shape=tableRow;horizontal=0;swimlaneHead=0;swimlaneBody=0;top=0;left=0;strokeColor=inherit;bottom=0;right=0;dropTarget=0;fontStyle=0;fillColor=none;points=[[0,0.5],[1,0.5]];startSize=0;collapsible=0;recursiveResize=1;expand=0;rounded=0;allowArrows=0;connectable=0;autosize=1;resizeHeight=1;rotatable=0;\" vertex=\"1\" parent=\"2\"><mxGeometry y=\"40\" width=\"160\" height=\"60\" as=\"geometry\"/></mxCell><mxCell id=\"4\" value=\"Input\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=60;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry width=\"80\" height=\"60\" as=\"geometry\"><mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"in\" type=\"data\" id=\"5\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"4\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><mxCell id=\"6\" value=\"Output\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=40;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry x=\"80\" width=\"80\" height=\"60\" as=\"geometry\"><mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"out\" type=\"data\" id=\"7\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"6\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object></root></mxGraphModel>", + "w": 160, + "h": 100, + "aspect": "fixed", + "title": "Modify Node" + }, + { + "xml": "<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/><object label=\"%name%: %type%\" placeholders=\"1\" name=\"merge_name\" type=\"Merge\" ports_data_in=\"[&quot;input1&quot;, &quot;input2&quot;]\" id=\"2\"><mxCell style=\"shape=table;childLayout=tableLayout;startSize=40;collapsible=0;recursiveResize=1;expand=0;fontStyle=1;editable=1;movable=1;resizable=1;rotatable=0;deletable=1;locked=0;connectable=0;allowArrows=0;pointerEvents=0;perimeter=rectanglePerimeter;rounded=1;container=1;dropTarget=0;swimlaneHead=1;swimlaneBody=1;top=1;noLabel=0;autosize=0;resizeHeight=0;spacing=2;metaEdit=1;resizeWidth=0;arcSize=10;\" vertex=\"1\" parent=\"1\"><mxGeometry width=\"160\" height=\"150\" as=\"geometry\"/></mxCell></object><mxCell id=\"3\" value=\"\" style=\"shape=tableRow;horizontal=0;swimlaneHead=0;swimlaneBody=0;top=0;left=0;strokeColor=inherit;bottom=0;right=0;dropTarget=0;fontStyle=0;fillColor=none;points=[[0,0.5],[1,0.5]];startSize=0;collapsible=0;recursiveResize=1;expand=0;rounded=0;allowArrows=0;connectable=0;autosize=1;resizeHeight=1;rotatable=0;\" vertex=\"1\" parent=\"2\"><mxGeometry y=\"40\" width=\"160\" height=\"110\" as=\"geometry\"/></mxCell><mxCell id=\"4\" value=\"Input\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=60;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry width=\"80\" height=\"110\" as=\"geometry\"><mxRectangle width=\"80\" height=\"110\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"input1\" type=\"data\" id=\"5\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"4\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><object label=\"input2\" type=\"data\" id=\"6\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"4\"><mxGeometry x=\"10\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><mxCell id=\"7\" value=\"Output\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=40;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry x=\"80\" width=\"80\" height=\"110\" as=\"geometry\"><mxRectangle width=\"80\" height=\"110\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"out\" type=\"data\" id=\"8\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"7\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object></root></mxGraphModel>", + "w": 160, + "h": 150, + "aspect": "fixed", + "title": "Merge Node" + }, + { + "xml": "<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/><object label=\"%name%: %type%\" placeholders=\"1\" name=\"store_name\" type=\"Store\" ports=\"[&quot;input1&quot;]\" id=\"2\"><mxCell style=\"shape=table;childLayout=tableLayout;startSize=40;collapsible=0;recursiveResize=1;expand=0;fontStyle=1;editable=1;movable=1;resizable=1;rotatable=0;deletable=1;locked=0;connectable=0;allowArrows=0;pointerEvents=0;perimeter=rectanglePerimeter;rounded=1;container=1;dropTarget=0;swimlaneHead=1;swimlaneBody=1;top=1;noLabel=0;autosize=0;resizeHeight=0;spacing=2;metaEdit=1;resizeWidth=0;arcSize=10;\" vertex=\"1\" parent=\"1\"><mxGeometry width=\"160\" height=\"200\" as=\"geometry\"/></mxCell></object><mxCell id=\"3\" value=\"\" style=\"shape=tableRow;horizontal=0;swimlaneHead=0;swimlaneBody=0;top=0;left=0;strokeColor=inherit;bottom=0;right=0;dropTarget=0;fontStyle=0;fillColor=none;points=[[0,0.5],[1,0.5]];startSize=0;collapsible=0;recursiveResize=1;expand=0;rounded=0;allowArrows=0;connectable=0;autosize=1;resizeHeight=1;rotatable=0;\" vertex=\"1\" parent=\"2\"><mxGeometry y=\"40\" width=\"160\" height=\"160\" as=\"geometry\"/></mxCell><mxCell id=\"4\" value=\"Input\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=60;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry width=\"80\" height=\"160\" as=\"geometry\"><mxRectangle width=\"80\" height=\"160\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"in\" type=\"exec\" id=\"5\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><object label=\"input1\" type=\"exec\" id=\"6\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"><mxGeometry x=\"10\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><object label=\"input1\" type=\"data\" id=\"7\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"4\"><mxGeometry x=\"10\" y=\"110\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><mxCell id=\"8\" value=\"Output\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=40;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry x=\"80\" width=\"80\" height=\"160\" as=\"geometry\"><mxRectangle width=\"80\" height=\"160\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"out\" type=\"data\" id=\"9\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"8\"><mxGeometry x=\"10\" y=\"110\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><object label=\"out\" type=\"exec\" id=\"10\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"8\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><object label=\"input1\" type=\"exec\" id=\"11\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"8\"><mxGeometry x=\"10\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object></root></mxGraphModel>", + "w": 160, + "h": 200, + "aspect": "fixed", + "title": "Store Node" + }, + { + "xml": "<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/><object label=\"%name%: %type%\" placeholders=\"1\" name=\"loop_name\" type=\"Loop\" id=\"2\"><mxCell style=\"shape=table;childLayout=tableLayout;startSize=40;collapsible=0;recursiveResize=1;expand=0;fontStyle=1;editable=1;movable=1;resizable=1;rotatable=0;deletable=1;locked=0;connectable=0;allowArrows=0;pointerEvents=0;perimeter=rectanglePerimeter;rounded=1;container=1;dropTarget=0;swimlaneHead=1;swimlaneBody=1;top=1;noLabel=0;autosize=0;resizeHeight=0;spacing=2;metaEdit=1;resizeWidth=0;arcSize=10;\" vertex=\"1\" parent=\"1\"><mxGeometry width=\"160\" height=\"200\" as=\"geometry\"/></mxCell></object><mxCell id=\"3\" value=\"\" style=\"shape=tableRow;horizontal=0;swimlaneHead=0;swimlaneBody=0;top=0;left=0;strokeColor=inherit;bottom=0;right=0;dropTarget=0;fontStyle=0;fillColor=none;points=[[0,0.5],[1,0.5]];startSize=0;collapsible=0;recursiveResize=1;expand=0;rounded=0;allowArrows=0;connectable=0;autosize=1;resizeHeight=1;rotatable=0;\" vertex=\"1\" parent=\"2\"><mxGeometry y=\"40\" width=\"160\" height=\"160\" as=\"geometry\"/></mxCell><mxCell id=\"4\" value=\"Input\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=60;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry width=\"80\" height=\"160\" as=\"geometry\"><mxRectangle width=\"80\" height=\"160\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"in\" type=\"data\" id=\"5\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"4\"><mxGeometry x=\"10\" y=\"110\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><object label=\"in\" type=\"exec\" id=\"6\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><mxCell id=\"7\" value=\"Output\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=40;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry x=\"80\" width=\"80\" height=\"160\" as=\"geometry\"><mxRectangle width=\"80\" height=\"160\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"out\" type=\"data\" id=\"8\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"7\"><mxGeometry x=\"10\" y=\"110\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><object label=\"it\" type=\"exec\" id=\"9\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"7\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><object label=\"out\" type=\"exec\" id=\"10\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"7\"><mxGeometry x=\"10\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object></root></mxGraphModel>", + "w": 160, + "h": 200, + "aspect": "fixed", + "title": "Loop Node" + }, + { + "xml": "<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/><object label=\"%name%: %type%&#10;%file%\" placeholders=\"1\" name=\"schedule_name\" type=\"Schedule\" file=\"schedule_page-name\" id=\"2\"><mxCell style=\"shape=table;childLayout=tableLayout;startSize=40;collapsible=0;recursiveResize=1;expand=0;fontStyle=1;editable=1;movable=1;resizable=1;rotatable=0;deletable=1;locked=0;connectable=0;allowArrows=0;pointerEvents=0;perimeter=rectanglePerimeter;rounded=1;container=1;dropTarget=0;swimlaneHead=1;swimlaneBody=1;top=1;noLabel=0;autosize=0;resizeHeight=0;spacing=2;metaEdit=1;resizeWidth=0;arcSize=10;\" vertex=\"1\" parent=\"1\"><mxGeometry width=\"160\" height=\"100\" as=\"geometry\"/></mxCell></object><mxCell id=\"3\" value=\"\" style=\"shape=tableRow;horizontal=0;swimlaneHead=0;swimlaneBody=0;top=0;left=0;strokeColor=inherit;bottom=0;right=0;dropTarget=0;fontStyle=0;fillColor=none;points=[[0,0.5],[1,0.5]];startSize=0;collapsible=0;recursiveResize=1;expand=0;rounded=0;allowArrows=0;connectable=0;autosize=1;resizeHeight=1;rotatable=0;\" vertex=\"1\" parent=\"2\"><mxGeometry y=\"40\" width=\"160\" height=\"60\" as=\"geometry\"/></mxCell><mxCell id=\"4\" value=\"Input\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=60;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry width=\"80\" height=\"60\" as=\"geometry\"><mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"out\" type=\"exec\" id=\"5\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><mxCell id=\"6\" value=\"Output\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=40;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry x=\"80\" width=\"80\" height=\"60\" as=\"geometry\"><mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"in\" type=\"exec\" id=\"7\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"6\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object></root></mxGraphModel>", + "w": 160, + "h": 100, + "aspect": "fixed", + "title": "Schedule Node" + }, + { + "xml": "<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/><object label=\"%name%: %type%\" placeholders=\"1\" name=\"print_name\" type=\"Print\" event=\"False\" custom=\"{{ data }}\" id=\"2\"><mxCell style=\"shape=table;childLayout=tableLayout;startSize=40;collapsible=0;recursiveResize=1;expand=0;fontStyle=1;editable=1;movable=1;resizable=1;rotatable=0;deletable=1;locked=0;connectable=0;allowArrows=0;pointerEvents=0;perimeter=rectanglePerimeter;rounded=1;container=1;dropTarget=0;swimlaneHead=1;swimlaneBody=1;top=1;noLabel=0;autosize=0;resizeHeight=0;spacing=2;metaEdit=1;resizeWidth=0;arcSize=10;\" vertex=\"1\" parent=\"1\"><mxGeometry width=\"160\" height=\"150\" as=\"geometry\"/></mxCell></object><mxCell id=\"3\" value=\"\" style=\"shape=tableRow;horizontal=0;swimlaneHead=0;swimlaneBody=0;top=0;left=0;strokeColor=inherit;bottom=0;right=0;dropTarget=0;fontStyle=0;fillColor=none;points=[[0,0.5],[1,0.5]];startSize=0;collapsible=0;recursiveResize=1;expand=0;rounded=0;allowArrows=0;connectable=0;autosize=1;resizeHeight=1;rotatable=0;\" vertex=\"1\" parent=\"2\"><mxGeometry y=\"40\" width=\"160\" height=\"110\" as=\"geometry\"/></mxCell><mxCell id=\"4\" value=\"Input\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=60;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry width=\"80\" height=\"110\" as=\"geometry\"><mxRectangle width=\"80\" height=\"110\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"in\" type=\"exec\" id=\"5\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><mxCell id=\"6\" value=\"Output\" style=\"swimlane;swimlaneHead=0;swimlaneBody=0;fontStyle=0;strokeColor=inherit;connectable=0;fillColor=none;startSize=40;collapsible=0;recursiveResize=1;expand=0;allowArrows=0;autosize=1;rotatable=0;noLabel=1;overflow=hidden;swimlaneLine=0;editable=0;\" vertex=\"1\" parent=\"3\"><mxGeometry x=\"80\" width=\"80\" height=\"110\" as=\"geometry\"><mxRectangle width=\"80\" height=\"110\" as=\"alternateBounds\"/></mxGeometry></mxCell><object label=\"out\" type=\"exec\" id=\"7\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"6\"><mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object><object label=\"in\" type=\"data\" id=\"8\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"6\"><mxGeometry x=\"-70\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object></root></mxGraphModel>", + "w": 160, + "h": 150, + "aspect": "fixed", + "title": "Print Node" + }, + { + "xml": "<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/><object label=\"out\" type=\"exec\" id=\"2\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"1\"><mxGeometry width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object></root></mxGraphModel>", + "w": 60, + "h": 40, + "aspect": "fixed", + "title": "Exec Gate" + }, + { + "xml": "<mxGraphModel><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/><object label=\"in\" type=\"data\" id=\"2\"><mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"1\"><mxGeometry width=\"60\" height=\"40\" as=\"geometry\"/></mxCell></object></root></mxGraphModel>", + "w": 60, + "h": 40, + "aspect": "fixed", + "title": "Data Gate" + } +] \ No newline at end of file diff --git a/transformation/schedule/schedule_lib/__init__.py b/transformation/schedule/schedule_lib/__init__.py new file mode 100644 index 0000000..4df5a3d --- /dev/null +++ b/transformation/schedule/schedule_lib/__init__.py @@ -0,0 +1,31 @@ +from .action import Action +from .data_node import DataNode +from .end import End +from .exec_node import ExecNode +from .loop import Loop +from .match import Match +from .merge import Merge +from .modify import Modify +from .null_node import NullNode +from .print import Print +from .rewrite import Rewrite +from .start import Start +from .store import Store +from .sub_schedule import SubSchedule + +__all__ = [ + "Action", + "DataNode", + "End", + "ExecNode", + "Loop", + "Match", + "Merge", + "Modify", + "NullNode", + "Rewrite", + "Print", + "Start", + "Store", + "SubSchedule", +] diff --git a/transformation/schedule/schedule_lib/action.py b/transformation/schedule/schedule_lib/action.py new file mode 100644 index 0000000..9f10406 --- /dev/null +++ b/transformation/schedule/schedule_lib/action.py @@ -0,0 +1,106 @@ +from typing import List, override, Type + +from jinja2 import Template + +from api.od import ODAPI +from .funcs import not_visited, generate_dot_node +from .exec_node import ExecNode +from .data_node import DataNode + +class ActionState: + def __init__(self): + self.var = {"output_gate": "out"} + +class Action(ExecNode, DataNode): + def __init__( + self, + ports_exec_in: list[str], + ports_exec_out: list[str], + ports_data_in: list[str], + ports_data_out: list[str], + code: str = "", + init: str = "", + ) -> None: + self.gates: tuple[list[str], list[str], list[str], list[str]] = (ports_exec_in, ports_exec_out, ports_data_in, ports_data_out) + super().__init__() + self.state: dict[int, ActionState] = {} + self.var_globals = {} + self.code = code + self.init = init + + @override + def get_exec_input_gates(self) -> list[str]: + return self.gates[0] + + @override + def get_exec_output_gates(self) -> list[str]: + return self.gates[1] + + @override + def get_data_input_gates(self) -> list[str]: + return self.gates[2] + + @override + def get_data_output_gates(self) -> list[str]: + return self.gates[3] + + @override + def nextState(self, exec_id: int) -> tuple["ExecNode", str]: + state = self.get_state(exec_id) + return self.next_node[state.var["output_gate"]] + + def get_state(self, exec_id) -> ActionState: + return self.state[exec_id] + + @override + def generate_stack_frame(self, exec_id: int) -> None: + super().generate_stack_frame(exec_id) + self.state[exec_id] = (state := ActionState()) + if self.init: + exec (self.init, {"var": state.var}, {"globals": self.var_globals}) + @override + def delete_stack_frame(self, exec_id: int) -> None: + super().generate_stack_frame(exec_id) + self.state.pop(exec_id) + + def execute(self, port: str, exec_id: int, od: ODAPI) -> tuple[int, any] | None: + state = self.get_state(exec_id) + exec( + self.code, + { + "api": od, + "var": state.var, + "data_in": {port: value.get_data(exec_id) for port, value in self.data_in.items() if value is not None}, + "data_out": {port: value.get_data(exec_id) for port, value in self.data_out.items() if value is not None}, + "globals": self.var_globals, + }, + ) + for gate, d in self.data_out.items(): + DataNode.input_event(self, gate, exec_id) + return None + + def input_event(self, gate: str, exec_id: int) -> None: + return + + @not_visited + def generate_dot( + self, nodes: List[str], edges: List[str], visited: set[int], template: Template + ) -> None: + generate_dot_node( + self, + nodes, + template, + **{ + "label": f"action", + "ports_exec": ( + self.get_exec_input_gates(), + self.get_exec_output_gates(), + ), + "ports_data": ( + self.get_data_input_gates(), + self.get_data_output_gates(), + ), + }, + ) + ExecNode.generate_dot(self, nodes, edges, visited, template) + DataNode.generate_dot(self, nodes, edges, visited, template) diff --git a/transformation/schedule/schedule_lib/data.py b/transformation/schedule/schedule_lib/data.py new file mode 100644 index 0000000..7cafc5b --- /dev/null +++ b/transformation/schedule/schedule_lib/data.py @@ -0,0 +1,83 @@ +from symtable import Class +from typing import Any, Generator, Callable, Iterator, TYPE_CHECKING, override + +if TYPE_CHECKING: + from transformation.schedule.schedule_lib import DataNode + + +class DataState: + def __init__(self, data: Any): + self.data: list[dict[Any, Any]] = [] + +class Data: + __slots__ = ("state", "_parent") + + def __init__(self, parent: "DataNode") -> None: + self.state: dict[int, DataState] = dict() + self._parent = parent + + def __dir__(self): + return [attr for attr in super().__dir__() if attr != "_super"] + + def get_data(self, exec_id: int) -> list[dict[str, str]]: + state = self.get_state(exec_id) + return state.data + + def get_state(self, exec_id) -> DataState: + return self.state[exec_id] + + def store_data(self, exec_id: int, data_gen: Generator, n: int) -> bool: + state = self.get_state(exec_id) + state.data.clear() + if n == 0: + return True + i: int = 0 + while (match := next(data_gen, None)) is not None: + state.data.append(match) + i += 1 + if i >= n: + break + else: + if n == float("inf"): + return bool(len(state.data)) + state.data.clear() + return False + return True + + def get_parent(self) -> "DataNode": + return self._parent + + def replace(self, exec_id: int, data: list[dict[str, str]]) -> None: + state = self.get_state(exec_id) + state.data.clear() + state.data.extend(data) + + def append(self, exec_id: int, data: dict[str, str]) -> None: + self.get_state(exec_id).data.append(data) + + def extend(self, exec_id: int, data: list[dict[str, str]]) -> None: + self.get_state(exec_id).data.extend(data) + + def clear(self, exec_id: int) -> None: + self.get_state(exec_id).data.clear() + + def pop(self, exec_id: int, index: int =-1) -> Any: + return self.get_state(exec_id).data.pop(index) + + def empty(self, exec_id: int) -> bool: + return len(self.get_state(exec_id).data) == 0 + + def __getitem__(self, index): + raise NotImplementedError + + def __iter__(self, exec_id: int) -> Iterator[dict[str, str]]: + return self.get_state(exec_id).data.__iter__() + + def __len__(self, exec_id: int) -> int: + return self.get_state(exec_id).data.__len__() + + def generate_stack_frame(self, exec_id: int) -> None: + self.state[exec_id] = DataState(exec_id) + + def delete_stack_frame(self, exec_id: int) -> None: + self.state.pop(exec_id) \ No newline at end of file diff --git a/transformation/schedule/schedule_lib/data_node.py b/transformation/schedule/schedule_lib/data_node.py new file mode 100644 index 0000000..01e9b76 --- /dev/null +++ b/transformation/schedule/schedule_lib/data_node.py @@ -0,0 +1,101 @@ +from abc import abstractmethod +from typing import Any, Generator, List, override + +from jinja2 import Template + +from .data import Data +from .funcs import generate_dot_edge +from .node import Node + + +class DataNodeState: + def __init__(self) -> None: + super().__init__() + + +class DataNode(Node): + def __init__(self) -> None: + super().__init__() + self.eventsub: dict[str, list[tuple[DataNode, str]]] = { + gate: [] for gate in self.get_data_output_gates() + } + self.data_out: dict[str, Data] = { + name: Data(self) for name in self.get_data_output_gates() + } + self.data_in: dict[str, Data | None] = { + name: None for name in self.get_data_input_gates() + } + + @staticmethod + def get_data_input_gates() -> List[str]: + return ["in"] + + @staticmethod + def get_data_output_gates() -> List[str]: + return ["out"] + + @override + def generate_stack_frame(self, exec_id: int) -> None: + super().generate_stack_frame(exec_id) + for d in self.data_out.values(): + d.generate_stack_frame(exec_id) + + @override + def delete_stack_frame(self, exec_id: int) -> None: + super().delete_stack_frame(exec_id) + for d in self.data_out.values(): + d.delete_stack_frame(exec_id) + + def connect_data( + self, data_node: "DataNode", from_gate: str, to_gate: str, eventsub=True + ) -> None: + if from_gate not in self.get_data_output_gates(): + raise Exception(f"from_gate {from_gate} is not a valid port") + if to_gate not in data_node.get_data_input_gates(): + raise Exception(f"to_gate {to_gate} is not a valid port") + data_node.data_in[to_gate] = self.data_out[from_gate] + if eventsub: + self.eventsub[from_gate].append((data_node, to_gate)) + + def store_data(self, exec_id, data_gen: Generator, port: str, n: int) -> None: + self.data_out[port].store_data(exec_id, data_gen, n) + for sub, gate in self.eventsub[port]: + sub.input_event(gate, exec_id) + + def get_input_data(self, gate: str, exec_id: int) -> list[dict[Any, Any]]: + data = self.data_in[gate] + if data is None: + return [{}] + return data.get_data(exec_id) + + @abstractmethod + def input_event(self, gate: str, exec_id: int) -> None: + for sub, gate_sub in self.eventsub[gate]: + sub.input_event(gate_sub, exec_id) + + def generate_dot( + self, nodes: List[str], edges: List[str], visited: set[int], template: Template + ) -> None: + for port, data in self.data_in.items(): + if data is not None: + source = data.get_parent() + generate_dot_edge( + source, + self, + edges, + template, + kwargs={ + "prefix": "d", + "from_gate": [ + port + for port, value in source.data_out.items() + if value == data + ][0], + "to_gate": port, + "color": "green", + }, + ) + data.get_parent().generate_dot(nodes, edges, visited, template) + for gate_form, subs in self.eventsub.items(): + for sub, gate in subs: + sub.generate_dot(nodes, edges, visited, template) diff --git a/transformation/schedule/schedule_lib/end.py b/transformation/schedule/schedule_lib/end.py new file mode 100644 index 0000000..a0218d8 --- /dev/null +++ b/transformation/schedule/schedule_lib/end.py @@ -0,0 +1,80 @@ +from typing import List, override, Type + +from jinja2 import Template + +from api.od import ODAPI +from . import DataNode +from .exec_node import ExecNode +from .funcs import not_visited, generate_dot_node + +class EndState: + def __init__(self) -> None: + self.end_gate: str = "" + +class End(ExecNode, DataNode): + @override + def input_event(self, gate: str, exec_id: int) -> None: + pass + + def __init__(self, ports_exec: List[str], ports_data: List[str]) -> None: + self.ports_exec = ports_exec + self.ports_data = ports_data + super().__init__() + self.state: dict[int, EndState] = {} + + @override + def get_exec_input_gates(self): + return self.ports_exec + + @staticmethod + @override + def get_exec_output_gates(): + return [] + + @override + def get_data_input_gates(self): + return self.ports_data + + @staticmethod + @override + def get_data_output_gates(): + return [] + + def execute(self, port: str, exec_id: int, od: ODAPI) -> tuple[int, any] | None: + state = self.get_state(exec_id) + state.end_gate = port + return 1, {"exec_gate": state.end_gate, "data_out": {port: data.get_data(exec_id) for port, data in self.data_in.items()}} + + def get_state(self, exec_id) -> EndState: + return self.state[exec_id] + + @override + def generate_stack_frame(self, exec_id: int) -> None: + super().generate_stack_frame(exec_id) + self.state[exec_id] = EndState() + + @override + def delete_stack_frame(self, exec_id: int) -> None: + super().delete_stack_frame(exec_id) + self.state.pop(exec_id) + + @not_visited + def generate_dot( + self, nodes: List[str], edges: List[str], visited: set[int], template: Template + ) -> None: + generate_dot_node( + self, + nodes, + template, + **{ + "label": "end", + "ports_exec": ( + self.get_exec_input_gates(), + self.get_exec_output_gates(), + ), + "ports_data": ( + self.get_data_input_gates(), + self.get_data_output_gates(), + ), + } + ) diff --git a/transformation/schedule/schedule_lib/exec_node.py b/transformation/schedule/schedule_lib/exec_node.py new file mode 100644 index 0000000..c46125f --- /dev/null +++ b/transformation/schedule/schedule_lib/exec_node.py @@ -0,0 +1,35 @@ +from abc import abstractmethod +from api.od import ODAPI +from .node import Node + + +class ExecNode(Node): + def __init__(self) -> None: + super().__init__() + + from .null_node import NullNode + self.next_node: dict[str, tuple[ExecNode, str]] = {} + for port in self.get_exec_output_gates(): + self.next_node[port] = (NullNode(), "in") + + def nextState(self, exec_id: int) -> tuple["ExecNode", str]: + return self.next_node["out"] + + @staticmethod + def get_exec_input_gates(): + return ["in"] + + @staticmethod + def get_exec_output_gates(): + return ["out"] + + def connect(self, next_state: "ExecNode", from_gate: str, to_gate: str) -> None: + if from_gate not in self.get_exec_output_gates(): + raise Exception(f"from_gate {from_gate} is not a valid port") + if to_gate not in next_state.get_exec_input_gates(): + raise Exception(f"to_gate {to_gate} is not a valid port") + self.next_node[from_gate] = (next_state, to_gate) + + @abstractmethod + def execute(self, port: str, exec_id: int, od: ODAPI) -> tuple[int, any] | None: + return None diff --git a/transformation/schedule/schedule_lib/funcs.py b/transformation/schedule/schedule_lib/funcs.py new file mode 100644 index 0000000..6a01eb0 --- /dev/null +++ b/transformation/schedule/schedule_lib/funcs.py @@ -0,0 +1,56 @@ +from typing import Callable, List + +from jinja2 import Template + +from .singleton import Singleton + + +class IdGenerator(metaclass=Singleton): + exec_id = -1 + node_id = -1 + + @classmethod + def generate_node_id(cls) -> int: + cls.node_id +=1 + return cls.node_id + + @classmethod + def generate_exec_id(cls) -> int: + cls.exec_id += 1 + return cls.exec_id + +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 + + +def not_visited(func) -> Callable: + def wrapper( + self, nodes: List[str], edges: List[str], visited: set[int], *args, **kwargs + ) -> None: + if self in visited: + return + visited.add(self) + func(self, nodes, edges, visited, *args, **kwargs) + + return wrapper + + +def generate_dot_node(self, nodes: List[str], template: Template, **kwargs) -> None: + nodes.append(template.module.__getattribute__("Node")(**{**kwargs, "id": self.id})) + + +def generate_dot_edge( + self, target, edges: List[str], template: Template, kwargs +) -> None: + edges.append( + template.module.__getattribute__("Edge")( + **{**kwargs, "from_id": self.id, "to_id": target.id} + ) + ) diff --git a/transformation/schedule/schedule_lib/loop.py b/transformation/schedule/schedule_lib/loop.py new file mode 100644 index 0000000..8837080 --- /dev/null +++ b/transformation/schedule/schedule_lib/loop.py @@ -0,0 +1,74 @@ +import functools +from typing import List, Generator, override, Type + +from jinja2 import Template + +from api.od import ODAPI +from .exec_node import ExecNode +from .data_node import DataNode +from .data_node import Data +from .funcs import not_visited, generate_dot_node + +class Loop(ExecNode, DataNode): + def __init__(self) -> None: + super().__init__() + self.cur_data: Data = Data(self) + + @staticmethod + @override + def get_exec_output_gates(): + return ["it", "out"] + + @override + def generate_stack_frame(self, exec_id: int) -> None: + super().generate_stack_frame(exec_id) + self.cur_data.generate_stack_frame(exec_id) + + @override + def delete_stack_frame(self, exec_id: int) -> None: + super().delete_stack_frame(exec_id) + self.cur_data.delete_stack_frame(exec_id) + + @override + def nextState(self, exec_id: int) -> tuple[ExecNode, str]: + return self.next_node["out" if self.data_out["out"].empty(exec_id) else "it"] + + def execute(self, port: str, exec_id: int, od: ODAPI) -> tuple[int, any] | None: + self.data_out["out"].clear(exec_id) + + if not self.cur_data.empty(exec_id): + self.data_out["out"].append(exec_id, self.cur_data.pop(exec_id,0)) + DataNode.input_event(self, "out", exec_id) + return None + + def input_event(self, gate: str, exec_id: int) -> None: + self.cur_data.replace(exec_id, self.get_input_data(gate, exec_id)) + data_o = self.data_out["out"] + if data_o.empty(exec_id): + return + data_o.clear(exec_id) + DataNode.input_event(self, "out", exec_id) + + + @not_visited + def generate_dot( + self, nodes: List[str], edges: List[str], visited: set[int], template: Template + ) -> None: + generate_dot_node( + self, + nodes, + template, + **{ + "label": f"loop", + "ports_exec": ( + self.get_exec_input_gates(), + self.get_exec_output_gates(), + ), + "ports_data": ( + self.get_data_input_gates(), + self.get_data_output_gates(), + ), + }, + ) + ExecNode.generate_dot(self, nodes, edges, visited, template) + DataNode.generate_dot(self, nodes, edges, visited, template) diff --git a/transformation/schedule/schedule_lib/match.py b/transformation/schedule/schedule_lib/match.py new file mode 100644 index 0000000..f3da3f2 --- /dev/null +++ b/transformation/schedule/schedule_lib/match.py @@ -0,0 +1,67 @@ +from typing import List, override, Type + +from jinja2 import Template + +from api.od import ODAPI +from transformation.schedule.rule_executor import RuleExecutor +from .exec_node import ExecNode +from .data_node import DataNode +from .funcs import not_visited, generate_dot_node + +class Match(ExecNode, DataNode): + def input_event(self, gate: str, exec_id: int) -> None: + pass + + def __init__(self, label: str, n: int | float) -> None: + super().__init__() + self.label: str = label + self.n: int = n + self.rule = None + self.rule_executer: RuleExecutor | None = None + + @override + def nextState(self, exec_id: int) -> tuple[ExecNode, str]: + return self.next_node["fail" if self.data_out["out"].empty(exec_id) else "success"] + + @staticmethod + @override + def get_exec_output_gates(): + return ["success", "fail"] + + def execute(self, port: str, exec_id: int, od: ODAPI) -> tuple[int, any] | None: + pivot = {} + if self.data_in is not None: + pivot = self.get_input_data("in", exec_id)[0] + # TODO: remove this print + print(f"matching: {self.label}\n\tpivot: {pivot}") + self.store_data( exec_id, + self.rule_executer.match_rule(od.m, self.rule, pivot=pivot), "out", self.n + ) + return None + + def init_rule(self, rule, rule_executer): + self.rule = rule + self.rule_executer = rule_executer + + @not_visited + def generate_dot( + self, nodes: List[str], edges: List[str], visited: set[int], template: Template + ) -> None: + generate_dot_node( + self, + nodes, + template, + **{ + "label": f"match_{self.n}\n{self.label}", + "ports_exec": ( + self.get_exec_input_gates(), + self.get_exec_output_gates(), + ), + "ports_data": ( + self.get_data_input_gates(), + self.get_data_output_gates(), + ), + }, + ) + ExecNode.generate_dot(self, nodes, edges, visited, template) + DataNode.generate_dot(self, nodes, edges, visited, template) diff --git a/transformation/schedule/schedule_lib/merge.py b/transformation/schedule/schedule_lib/merge.py new file mode 100644 index 0000000..d31b809 --- /dev/null +++ b/transformation/schedule/schedule_lib/merge.py @@ -0,0 +1,57 @@ +from typing import List, override, Type + +from jinja2 import Template + +from api.od import ODAPI +from transformation.schedule.rule_executor import RuleExecutor +from . import ExecNode +from .exec_node import ExecNode +from .data_node import DataNode, DataNodeState +from .funcs import not_visited, generate_dot_node + +class Merge(DataNode): + def __init__(self, ports: list[str]) -> None: + self.in_data_ports = ports # ports must be defined before super.__init__ + super().__init__() + self.in_data_ports.reverse() + + @override + def get_data_input_gates(self) -> list[str]: + return self.in_data_ports + + @override + def input_event(self, gate: str, exec_id: int) -> None: + out = self.data_out["out"] + b = (not out.empty(exec_id)) and (self.data_in[gate].empty(exec_id)) + out.clear(exec_id) + if b: + DataNode.input_event(self, "out", exec_id) + return + + # TODO: only first element or all? + if any(data.empty(exec_id) for data in self.data_in.values()): + return + d: dict[str, str] = dict() + for gate in self.in_data_ports: + for key, value in self.data_in[gate].get_data(exec_id)[0].items(): + d[key] = value + out.append(exec_id, d) + DataNode.input_event(self, "out", exec_id) + + @not_visited + def generate_dot( + self, nodes: List[str], edges: List[str], visited: set[int], template: Template + ) -> None: + generate_dot_node( + self, + nodes, + template, + **{ + "label": f"merge", + "ports_data": ( + self.get_data_input_gates()[::-1], + self.get_data_output_gates(), + ), + }, + ) + DataNode.generate_dot(self, nodes, edges, visited, template) diff --git a/transformation/schedule/schedule_lib/modify.py b/transformation/schedule/schedule_lib/modify.py new file mode 100644 index 0000000..ad4859e --- /dev/null +++ b/transformation/schedule/schedule_lib/modify.py @@ -0,0 +1,49 @@ +from typing import List, override + +from jinja2 import Template + +from transformation.schedule.schedule_lib.funcs import not_visited, generate_dot_node +from .data_node import DataNode + + +class Modify(DataNode): + def __init__(self, rename: dict[str, str], delete: dict[str, str]) -> None: + super().__init__() + self.rename: dict[str, str] = rename + self.delete: set[str] = set(delete) + + @override + def input_event(self, gate: str, exec_id: int) -> None: + data_i = self.get_input_data(gate, exec_id) + if len(data_i): + self.data_out["out"].clear(exec_id) + for data in data_i: + self.data_out["out"].append(exec_id, + { + self.rename.get(key, key): value + for key, value in data.items() + if key not in self.delete + } + ) + else: + if self.data_out["out"].empty(exec_id): + return + super().input_event("out", exec_id) + + @not_visited + def generate_dot( + self, nodes: List[str], edges: List[str], visited: set[int], template: Template + ) -> None: + generate_dot_node( + self, + nodes, + template, + **{ + "label": f"modify", + "ports_data": ( + self.get_data_input_gates(), + self.get_data_output_gates(), + ), + }, + ) + DataNode.generate_dot(self, nodes, edges, visited, template) diff --git a/transformation/schedule/schedule_lib/node.py b/transformation/schedule/schedule_lib/node.py new file mode 100644 index 0000000..022c73c --- /dev/null +++ b/transformation/schedule/schedule_lib/node.py @@ -0,0 +1,70 @@ +""" +node.py + +Defines the abstract base Node class for graph-based structures. Each Node is assigned +a unique identifier via an external IdGenerator. The class provides an interface for +managing execution state and generating DOT graph representations. +""" + +from abc import abstractmethod +from jinja2 import Template +from .funcs import IdGenerator + + +class Node: + """ + Abstract base class for graph nodes. Each Node has a unique ID and supports + context-dependent state management for execution scenarios. Subclasses must + implement the DOT graph generation logic. + """ + + @abstractmethod + def __init__(self) -> None: + """ + Initializes the Node instance with a unique ID. + + Attributes: + id (int): A unique identifier assigned by IdGenerator. + """ + self.id: int = IdGenerator.generate_node_id() + + def get_id(self) -> int: + """ + Retrieves the unique identifier of the node. + + Returns: + int: The unique node ID. + """ + return self.id + + def generate_stack_frame(self, exec_id: int) -> None: + """ + Initializes a new state frame for a specific execution context. + Designed to be overridden in subclasses that use execution state. + + Args: + exec_id (int): The ID of the execution context. + """ + + def delete_stack_frame(self, exec_id: int) -> None: + """ + Deletes the state frame for a specific execution context. + Designed to be overridden in subclasses that use execution state. + + Args: + exec_id (int): The ID of the execution context. + """ + + @abstractmethod + def generate_dot( + self, nodes: list[str], edges: list[str], visited: set[int], template: Template + ) -> None: + """ + Generates the DOT graph representation for this node and its relationships. + + Args: + nodes (list[str]): A list to append DOT node definitions to. + edges (list[str]): A list to append DOT edge definitions to. + visited (set[int]): A set of already visited node IDs to avoid duplicates or recursion. + template (Template): A Jinja2 template used to format the node's DOT representation. + """ diff --git a/transformation/schedule/schedule_lib/null_node.py b/transformation/schedule/schedule_lib/null_node.py new file mode 100644 index 0000000..f7c44ad --- /dev/null +++ b/transformation/schedule/schedule_lib/null_node.py @@ -0,0 +1,80 @@ +""" +null_node.py + +Defines the NullNode class, a no-op singleton execution node used for open execution pins +in the object diagram execution graph. +""" + +from abc import ABC +from typing import List, Type +from jinja2 import Template +from api.od import ODAPI +from .funcs import generate_dot_node +from .singleton import Singleton +from .exec_node import ExecNode + +class NullNode(ExecNode, metaclass=Singleton): + """ + A no-op execution node representing a null operation. + + This node is typically used to represent a placeholder or open execution pin. + It always returns a fixed result and does not perform any operation. + """ + + def __init__(self): + """ + Initializes the NullNode instance. + Inherits unique ID and state behavior from ExecNode. + """ + super().__init__() + + def execute(self, port: str, exec_id: int, od: ODAPI) -> tuple[int, any] | None: + """ + Simulates execution by returning a static result indicating an open pin. + + Args: + port (str): The name of the input port. + exec_id (int): The current execution ID. + od (ODAPI): The Object Diagram API instance providing execution context. + + Returns: + tuple[int, str] | None: A tuple (-1, "open pin reached") indicating a no-op. + """ + return -1, "open pin reached" + + @staticmethod + def get_exec_output_gates(): + """ + Returns the list of output gates for execution. + + Returns: + list: An empty list, as NullNode has no output gates. + """ + return [] + + def generate_dot( + self, nodes: List[str], edges: List[str], visited: set[int], template: Template + ) -> None: + """ + Generates DOT graph representation for this node if it hasn't been visited. + + Args: + nodes (List[str]): A list to accumulate DOT node definitions. + edges (List[str]): A list to accumulate DOT edge definitions. + visited (set[int]): Set of already visited node IDs to avoid cycles. + template (Template): A Jinja2 template used to render the node's DOT representation. + """ + if self.id in visited: + return + generate_dot_node( + self, + nodes, + template, + **{ + "label": "null", + "ports_exec": ( + self.get_exec_input_gates(), + self.get_exec_output_gates(), + ), + } + ) diff --git a/transformation/schedule/schedule_lib/print.py b/transformation/schedule/schedule_lib/print.py new file mode 100644 index 0000000..3b237a2 --- /dev/null +++ b/transformation/schedule/schedule_lib/print.py @@ -0,0 +1,60 @@ +from typing import List, override + +from jinja2 import Template + +from api.od import ODAPI +from transformation.schedule.schedule_lib.funcs import not_visited, generate_dot_node +from .exec_node import ExecNode +from .data_node import DataNode + + +class Print(ExecNode, DataNode): + def __init__(self, label: str = "", custom: str = "") -> None: + super().__init__() + self.label = label + + if custom: + template = Template(custom, trim_blocks=True, lstrip_blocks=True) + self._print = ( + lambda self_, exec_id: print(template.render(data=self.get_input_data("in", exec_id))) + ).__get__(self, Print) + + @staticmethod + @override + def get_data_output_gates(): + return [] + + def execute(self, port: str, exec_id: int, od: ODAPI) -> tuple[int, any] | None: + self._print(exec_id) + return + + @override + def input_event(self, gate: str, exec_id: int) -> None: + if not self.data_in[gate].empty(exec_id): + self._print(exec_id) + + def _print(self, exec_id: int) -> None: + print(f"{self.label}{self.get_input_data("in", exec_id)}") + + @not_visited + def generate_dot( + self, nodes: List[str], edges: List[str], visited: set[int], template: Template + ) -> None: + generate_dot_node( + self, + nodes, + template, + **{ + "label": f"print", + "ports_exec": ( + self.get_exec_input_gates(), + self.get_exec_output_gates(), + ), + "ports_data": ( + self.get_data_input_gates(), + self.get_data_output_gates(), + ), + }, + ) + ExecNode.generate_dot(self, nodes, edges, visited, template) + DataNode.generate_dot(self, nodes, edges, visited, template) diff --git a/transformation/schedule/schedule_lib/rewrite.py b/transformation/schedule/schedule_lib/rewrite.py new file mode 100644 index 0000000..ba2be83 --- /dev/null +++ b/transformation/schedule/schedule_lib/rewrite.py @@ -0,0 +1,56 @@ +import functools +from typing import List, Type + +from jinja2 import Template + +from api.od import ODAPI +from .exec_node import ExecNode +from .data_node import DataNode +from .funcs import not_visited, generate_dot_node +from ..rule_executor import RuleExecutor + +class Rewrite(ExecNode, DataNode): + + def __init__(self, label: str) -> None: + super().__init__() + self.label = label + self.rule = None + self.rule_executor: RuleExecutor | None = None + + def init_rule(self, rule, rule_executer): + self.rule = rule + self.rule_executor = rule_executer + + def execute(self, port: str, exec_id: int, od: ODAPI) -> tuple[int, any] | None: + pivot = {} + if self.data_in is not None: + pivot = self.get_input_data("in", exec_id)[0] + # TODO: remove print + print(f"rewrite: {self.label}\n\tpivot: {pivot}") + self.store_data( exec_id, + self.rule_executor.rewrite_rule(od, self.rule, pivot=pivot), "out", 1 + ) + return None + + @not_visited + def generate_dot( + self, nodes: List[str], edges: List[str], visited: set[int], template: Template + ) -> None: + generate_dot_node( + self, + nodes, + template, + **{ + "label": "rewrite", + "ports_exec": ( + self.get_exec_input_gates(), + self.get_exec_output_gates(), + ), + "ports_data": ( + self.get_data_input_gates(), + self.get_data_output_gates(), + ), + }, + ) + ExecNode.generate_dot(self, nodes, edges, visited, template) + DataNode.generate_dot(self, nodes, edges, visited, template) diff --git a/examples/schedule/schedule_lib/singleton.py b/transformation/schedule/schedule_lib/singleton.py similarity index 99% rename from examples/schedule/schedule_lib/singleton.py rename to transformation/schedule/schedule_lib/singleton.py index 31955e3..91ac5cf 100644 --- a/examples/schedule/schedule_lib/singleton.py +++ b/transformation/schedule/schedule_lib/singleton.py @@ -2,6 +2,7 @@ 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) diff --git a/transformation/schedule/schedule_lib/start.py b/transformation/schedule/schedule_lib/start.py new file mode 100644 index 0000000..441e95f --- /dev/null +++ b/transformation/schedule/schedule_lib/start.py @@ -0,0 +1,83 @@ +from typing import List, override + +from jinja2 import Template + +from . import DataNode +from .exec_node import ExecNode +from .funcs import not_visited, generate_dot_node + +class StartState: + def __init__(self) -> None: + super().__init__() + self.start_gate: str = "" + +class Start(ExecNode, DataNode): + def __init__(self, ports_exec: List[str], ports_data: List[str]) -> None: + self.state: dict[int, StartState] = {} + self.ports_exec = ports_exec + self.ports_data = ports_data + super().__init__() + + def run_init(self, gate: str, exec_id: int, data: dict[str, any]) -> None: + state = self.get_state(exec_id) + state.start_gate = gate + for port, d in data.items(): + self.data_out[port].replace(exec_id, d) + DataNode.input_event(self, port, exec_id) + + def nextState(self, exec_id: int) -> tuple["ExecNode", str]: + state = self.get_state(exec_id) + return self.next_node[state.start_gate] + + def get_state(self, exec_id) -> StartState: + return self.state[exec_id] + + @override + def generate_stack_frame(self, exec_id: int) -> None: + super().generate_stack_frame(exec_id) + self.state[exec_id] = StartState() + + @override + def delete_stack_frame(self, exec_id: int) -> None: + super().generate_stack_frame(exec_id) + self.state.pop(exec_id) + + @staticmethod + @override + def get_exec_input_gates(): + return [] + + @override + def get_exec_output_gates(self): + return self.ports_exec + + @staticmethod + @override + def get_data_input_gates(): + return [] + + @override + def get_data_output_gates(self): + return self.ports_data + + @not_visited + def generate_dot( + self, nodes: List[str], edges: List[str], visited: set[int], template: Template + ) -> None: + generate_dot_node( + self, + nodes, + template, + **{ + "label": "start", + "ports_exec": ( + self.get_exec_input_gates(), + self.get_exec_output_gates(), + ), + "ports_data": ( + self.get_data_input_gates(), + self.get_data_output_gates(), + ), + } + ) + super().generate_dot(nodes, edges, visited, template) diff --git a/transformation/schedule/schedule_lib/store.py b/transformation/schedule/schedule_lib/store.py new file mode 100644 index 0000000..4aced26 --- /dev/null +++ b/transformation/schedule/schedule_lib/store.py @@ -0,0 +1,92 @@ +from typing import List, override + +from jinja2 import Template + +from api.od import ODAPI +from .data import Data +from .exec_node import ExecNode +from .data_node import DataNode +from .funcs import not_visited, generate_dot_node + +class StoreState: + def __init__(self) -> None: + self.last_port: str = "in" + +class Store(ExecNode, DataNode): + def __init__(self, ports: list[str]) -> None: + self.ports = ports + super().__init__() + self.state: dict[int, StoreState] = {} + self.cur_data: Data = Data(self) + + @override + def get_exec_input_gates(self) -> list[str]: + return [*self.ports, "in"] + + @override + def get_exec_output_gates(self) -> list[str]: + return [*self.ports, "out"] + + @override + def get_data_input_gates(self) -> list[str]: + return self.ports + + @override + def nextState(self, exec_id: int) -> tuple[ExecNode, str]: + return self.next_node[self.get_state(exec_id).last_port] + + @override + def input_event(self, gate: str, exec_id: int) -> None: + return + + def get_state(self, exec_id) -> StoreState: + return self.state[exec_id] + + @override + def generate_stack_frame(self, exec_id: int) -> None: + super().generate_stack_frame(exec_id) + self.state[exec_id] = StoreState() + self.cur_data.generate_stack_frame(exec_id) + + @override + def delete_stack_frame(self, exec_id: int) -> None: + super().generate_stack_frame(exec_id) + self.state.pop(exec_id) + self.cur_data.delete_stack_frame(exec_id) + + + @override + def execute(self, port: str, exec_id: int, od: ODAPI) -> tuple[int, any] | None: + state = self.get_state(exec_id) + if port == "in": + self.data_out["out"].replace(exec_id, self.cur_data.get_data(exec_id)) + self.cur_data.clear(exec_id) + DataNode.input_event(self, "out", True) + state.last_port = "out" + return None + self.cur_data.extend(exec_id, self.get_input_data(port, exec_id)) + state.last_port = port + return None + + @not_visited + def generate_dot( + self, nodes: List[str], edges: List[str], visited: set[int], template: Template + ) -> None: + generate_dot_node( + self, + nodes, + template, + **{ + "label": f"store", + "ports_exec": ( + self.get_exec_input_gates(), + self.get_exec_output_gates(), + ), + "ports_data": ( + self.get_data_input_gates(), + self.get_data_output_gates(), + ), + }, + ) + ExecNode.generate_dot(self, nodes, edges, visited, template) + DataNode.generate_dot(self, nodes, edges, visited, template) diff --git a/transformation/schedule/schedule_lib/sub_schedule.py b/transformation/schedule/schedule_lib/sub_schedule.py new file mode 100644 index 0000000..7b97e43 --- /dev/null +++ b/transformation/schedule/schedule_lib/sub_schedule.py @@ -0,0 +1,107 @@ +from typing import List, override, TYPE_CHECKING + +from jinja2 import Template + +from api.od import ODAPI +from . import DataNode +from .exec_node import ExecNode +from .funcs import not_visited, generate_dot_node, IdGenerator + +if TYPE_CHECKING: + from ..rule_scheduler import RuleSchedular + + +class ScheduleState: + def __init__(self) -> None: + self.end_gate: str = "" + +class SubSchedule(ExecNode, DataNode): + def __init__(self, schedular: "RuleSchedular", file: str) -> None: + self.schedule = schedular._load_schedule(file, _main=False) + self.schedular = schedular + super().__init__() + self.state: dict[int, ScheduleState] = {} + + @override + def nextState(self, exec_id: int) -> tuple["ExecNode", str]: + return self.next_node[self.get_state(exec_id).end_gate] + + @override + def get_exec_input_gates(self) -> "List[ExecNode]": + return self.schedule.start.get_exec_output_gates() + + @override + def get_exec_output_gates(self) -> "List[ExecNode]": + return [*self.schedule.end.get_exec_input_gates()] + + @override + def get_data_input_gates(self) -> "List[ExecNode]": + return self.schedule.start.get_data_output_gates() + + @override + def get_data_output_gates(self) -> "List[ExecNode]": + return self.schedule.end.get_data_input_gates() + + def get_state(self, exec_id) -> ScheduleState: + return self.state[exec_id] + + @override + def generate_stack_frame(self, exec_id: int) -> None: + super().generate_stack_frame(exec_id) + self.state[exec_id] = ScheduleState() + + @override + def delete_stack_frame(self, exec_id: int) -> None: + super().delete_stack_frame(exec_id) + self.state.pop(exec_id) + + + @override + def execute(self, port: str, exec_id: int, od: ODAPI) -> tuple[int, any] | None: + runstatus, result = self.schedular._runner( + od, + self.schedule, + port, + IdGenerator.generate_exec_id(), + { + port: self.get_input_data(port, exec_id) + for port, value in self.data_in.items() + if value is not None and not value.empty(exec_id) + }, + ) + if runstatus != 1: + return runstatus, result + self.get_state(exec_id).end_gate = result["exec_gate"] + results_data = result["data_out"] + for port, data in self.data_out.items(): + if port in results_data: + self.data_out[port].replace(exec_id, results_data[port]) + DataNode.input_event(self, port, exec_id) + continue + + if not data.empty(exec_id): + data.clear(exec_id) + DataNode.input_event(self, port, exec_id) + return None + + @not_visited + def generate_dot( + self, nodes: List[str], edges: List[str], visited: set[int], template: Template + ) -> None: + generate_dot_node( + self, + nodes, + template, + **{ + "label": "rrrrrrrrrr", + "ports_exec": ( + self.get_exec_input_gates(), + self.get_exec_output_gates(), + ), + "ports_data": ( + self.get_data_input_gates(), + self.get_data_output_gates(), + ), + } + ) + super().generate_dot(nodes, edges, visited, template) diff --git a/transformation/schedule/templates/schedule_dot.j2 b/transformation/schedule/templates/schedule_dot.j2 new file mode 100644 index 0000000..7937884 --- /dev/null +++ b/transformation/schedule/templates/schedule_dot.j2 @@ -0,0 +1,60 @@ +digraph G { + rankdir=LR; + compound=true; + node [shape=rect]; +{% for node in nodes %} + {{ node }} +{% endfor %} + +{% for edge in edges %} + {{ edge }} +{% endfor %} +} + +{% macro Node(label, id, ports_exec=[], ports_data=[]) %} +subgraph cluster_{{ id }} { + label = "{{ id }}__{{ label }}"; + style = rounded; + input_{{ id }} [ + shape=rect; + label= {{ Gate_Table(ports_exec[0], ports_data[0]) }} + ]; + output_{{ id }} [ + shape=rect; + label= {{ Gate_Table(ports_exec[1], ports_data[1]) }} + ]; + input_{{ id }}->output_{{ id }} [style=invis]; + } +{%- endmacro %} + +{%- macro Edge(from_id, to_id, from_gate, to_gate, prefix, color) %} +output_{{ from_id }}:{{ prefix }}_{{ from_gate }} -> input_{{ to_id }}:{{ prefix }}_{{ to_gate }} [color = {{ color }}] +{%- endmacro %} + +{%- macro Gate_Table(ports_exec, ports_data) %} + + < + {% if ports_exec or ports_data %} + {% if ports_exec %} + + {% endif %} + {% if ports_data %} + + {% endif %} + {% else %} + + {% endif %} +
+ + {% for port_e in ports_exec %} + + {% endfor %} +
{{ port_e }}
+
+ + {% for port_d in ports_data %} + + {% endfor %} +
{{ port_d }}
+
X
> +{%- endmacro %} \ No newline at end of file diff --git a/transformation/schedule/templates/schedule_muMLE.j2 b/transformation/schedule/templates/schedule_muMLE.j2 new file mode 100644 index 0000000..624b203 --- /dev/null +++ b/transformation/schedule/templates/schedule_muMLE.j2 @@ -0,0 +1,28 @@ +{% for id, param in nodes.items() -%} + {{ param[0] }}:{{ param[1].pop("type") }} + {%- if param[1] %} + { + {% for key, value in param[1].items() %} + {% if value %} + {% if key in ["file"] %} + {% set value = '"' ~ value ~ '"' %} + {% elif key in ["custom"] %} + {% set value = '`"' ~ value.replace('\n', '\\n') ~ '"`' %} + {% elif key in ["action", "init"] %} + {% set value = '\n```\n' ~ value ~ '\n```' %} + {% elif key in ["ports", "ports_exec_in", "ports_exec_out", "ports_data_in", "ports_data_out", "rename", "delete"] %} + {% set value = '`' ~ value.replace('\n', '\\n') ~ '`' %} + {% endif %} + {{ key }} = {{ value }}; + {% endif %} + {% endfor %} +} + {% endif %} + +{% endfor %} + +{%- for edge in edges %} + {% set source = edge[0] %} + {% set target = edge[1] %} +:Conn_{{ source[2] }} ({{ source[0] }} -> {{ target[0] }}) {from="{{ source[1] }}"; to="{{ target[1] }}";} +{% endfor -%} \ No newline at end of file diff --git a/transformation/schedule/templates/schedule_template.j2 b/transformation/schedule/templates/schedule_template.j2 new file mode 100644 index 0000000..6075b61 --- /dev/null +++ b/transformation/schedule/templates/schedule_template.j2 @@ -0,0 +1,51 @@ +{% macro Start(name, ports_exec_out, ports_data_out) %} +{{ name }} = Start({{ ports_exec_out }}, {{ ports_data_out }}) +{%- endmacro %} + +{% macro End(name, ports_exec_in, ports_data_in) %} +{{ name }} = End({{ ports_exec_in }}, {{ ports_data_in }}) +{%- endmacro %} + +{% macro Match(name, file, n) %} +{{ name }} = Match("{{ file }}", {{ n }}) +{%- endmacro %} + +{% macro Rewrite(name, file) %} +{{ name }} = Rewrite("{{ file }}") +{%- endmacro %} + +{% macro Action(name, ports_exec_in, ports_exec_out, ports_data_in, ports_data_out, action, init) %} +{{ name }} = Action({{ ports_exec_in }}, {{ ports_exec_out }}, {{ ports_data_in }}, {{ ports_data_out }}, {{ action }}, {{ init }}) +{%- endmacro %} + +{% macro Modify(name, rename, delete) %} +{{ name }} = Modify({{ rename }}, {{ delete }}) +{%- endmacro %} + +{% macro Merge(name, ports_data_in) %} +{{ name }} = Merge({{ ports_data_in }}) +{%- endmacro %} + +{% macro Store(name, ports) %} +{{ name }} = Store({{ ports }}) +{%- endmacro %} + +{% macro Schedule(name, file) %} +{{ name }} = SubSchedule(schedular, "{{ file }}") +{%- endmacro %} + +{% macro Loop(name) %} +{{ name }} = Loop() +{%- endmacro %} + +{% macro Print(name, label, custom) %} +{{ name }} = Print("{{ label }}", {{ custom }}) +{%- endmacro %} + +{% macro Conn_exec(name_from, name_to, from, to) %} +{{ name_from }}.connect({{ name_to }},"{{ from }}","{{ to }}") +{%- endmacro %} + +{% macro Conn_data(name_from, name_to, from, to, event) %} +{{ name_from }}.connect_data({{ name_to }}, "{{ from }}", "{{ to }}", {{ event }}) +{%- endmacro %} \ No newline at end of file diff --git a/transformation/schedule/templates/schedule_template_wrap.j2 b/transformation/schedule/templates/schedule_template_wrap.j2 new file mode 100644 index 0000000..59bb425 --- /dev/null +++ b/transformation/schedule/templates/schedule_template_wrap.j2 @@ -0,0 +1,48 @@ +#generated from somewhere i do not now but it here so live with it + +from transformation.schedule.schedule_lib import * + +class Schedule: + def __init__(self): + self.start: Start | None = None + self.end: End | None = None + self.nodes: list[DataNode] = [] + + @staticmethod + def get_matchers(): + return [ + {% for file in match_files %} + "{{ file }}", + {% endfor %} + ] + + def init_schedule(self, schedular, rule_executer, matchers): + {% for block in blocks_start_end%} + {{ block }} + {% endfor %} + self.start = {{ start }} + self.end = {{ end }} + {% for block in blocks%} + {{ block }} + {% endfor %} + + {% for conn in exec_conn%} + {{ conn }} + {% endfor %} + {% for conn_d in data_conn%} + {{ conn_d }} + {% endfor %} + + {% for match in matchers %} + {{ match["name"] }}.init_rule(matchers["{{ match["file"] }}"], rule_executer) + {% endfor %} + + self.nodes = [ + {% for name in blocks_name%} + {{ name }}, + {% endfor %} + ] + return None + + def generate_dot(self, *args, **kwargs): + return self.start.generate_dot(*args, **kwargs) \ No newline at end of file