Merge with Robbe's scheduling language

This commit is contained in:
Joeri Exelmans 2025-07-23 15:30:32 +02:00
commit 4ba0ed09b2
203 changed files with 8582 additions and 3886 deletions

View file

@ -52,7 +52,7 @@ def merge_models(state, mm, models: list[UUID]):
model = state.read_value(obj)
scd = SCD(merged, state)
created_obj = scd.create_model_ref(prefixed_obj_name, model)
merged_odapi._ODAPI__recompute_mappings() # dirty!!
merged_odapi.recompute_mappings() # dirty!!
else:
# create node or edge
if state.is_edge(obj):

View file

@ -149,13 +149,13 @@ def rewrite(state,
if od.is_typed_by(bottom, rhs_type, class_type):
obj_name = first_available_name(suggested_name)
host_od._create_object(obj_name, host_type)
host_odapi._ODAPI__recompute_mappings()
host_odapi.recompute_mappings()
rhs_match[rhs_name] = obj_name
elif od.is_typed_by(bottom, rhs_type, assoc_type):
_, _, host_src, host_tgt = get_src_tgt()
link_name = first_available_name(suggested_name)
host_od._create_link(link_name, host_type, host_src, host_tgt)
host_odapi._ODAPI__recompute_mappings()
host_odapi.recompute_mappings()
rhs_match[rhs_name] = link_name
elif od.is_typed_by(bottom, rhs_type, attr_link_type):
host_src_name, _, host_src, host_tgt = get_src_tgt()
@ -163,7 +163,7 @@ def rewrite(state,
host_attr_name = host_mm_odapi.get_slot_value(host_attr_link, "name")
link_name = f"{host_src_name}_{host_attr_name}" # must follow naming convention here
host_od._create_link(link_name, host_type, host_src, host_tgt)
host_odapi._ODAPI__recompute_mappings()
host_odapi.recompute_mappings()
rhs_match[rhs_name] = link_name
elif rhs_type == rhs_mm_odapi.get("ActionCode"):
# If we encounter ActionCode in our RHS, we assume that the code computes the value of an attribute...

View file

@ -0,0 +1,502 @@
import io
import os
import sys
import unittest
sys.path.insert(
0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../"))
)
from api.od import ODAPI
from bootstrap.scd import bootstrap_scd
from transformation.schedule.rule_scheduler import RuleScheduler
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 = RuleScheduler(
*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:]
if len(errors) != len(expected_substr_err.keys()):
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:
assert False
expected = expected_substr_err[key]
if (len(error_lines) - 1) != len(expected):
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
):
assert False
expected_substr_err.pop(key)
except AssertionError:
raise
except Exception as 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):
# try to load the following schedule.
# The schedules contains happy day nodes and faulty nodes.
# Use the error messages to select error location and further validate the multiple reasons of failure.
self._test_conformance(
"connections_start.od",
{
("Start", "start"): [ # locate failure (contains these two substrings), make sure other do not fully overlap -> flakey test
["input exec", "foo_in", "exist"], # 4 total reasons, a reason contains these three substrings
["output exec", "out", "multiple"], # a reason will match to exactly one subnstring list
["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):
#TODO:
# see test_connections_merge
self._test_conformance(
"connections_modify.od",
{
("Invalid source", "Conn_exec"): [],
("Invalid target", "Conn_exec"): [],
("Modify", "m_foo"): [
["input data", "foo_in", "exist"],
["output data", "foo_out", "exist"],
["input data", "in", "multiple"],
],
("Modify", "m_exec"): [
["input exec", "in", "exist"],
["input exec", "in", "exist"],
["output exec", "out", "exist"],
]
},
)
def test_connections_merge(self):
#TODO:
# mm:
# association Conn_exec [0..*] Exec -> Exec [0..*] {
# ...;
# }
# m:
# Conn_exec ( Data -> Exec) {...;} -> Invalid source type 'Merge' for link '__Conn_exec_3:Conn_exec' (1)
# -> Invalid target type 'End' for link '__Conn_exec_3:Conn_exec' (2)
# Conn_exec ( Exec -> Data) {...;} -> No error at all, inconsistent and unexpected behaviour (3)
# different combinations behave unexpected
self._test_conformance(
"connections_merge.od",
{
("Invalid source", "Conn_exec"): [], # (1), expected
("Invalid target", "Conn_exec"): [], # (2), invalid error, should not be shown
("Merge", "m_foo"): [
["input data", "foo_in", "exist"],
["input data", "in2", "multiple"],
["output data", "foo_out", "exist"],
],
("Merge", "m_exec"): [ # (3), checked in Merge itself
["input exec", "in", "exist"],
["output exec", "out", "exist"],
],
},
)
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"'): [ # included " to avoid flakey test
["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"'): [
["Invalid python code"],
["line"],
],
("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()

View file

@ -0,0 +1,43 @@
import os
import unittest
from transformation.schedule.rule_scheduler import RuleScheduler
from state.devstate import DevState
class MyTestCase(unittest.TestCase):
def setUp(self):
state = DevState()
self.generator = RuleScheduler(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()

View file

@ -0,0 +1 @@
<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel>

View file

@ -0,0 +1,24 @@
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0" version="26.2.14">
<diagram id="EvjeMC12HsgBk4t1Z8cF" name="Page-1">
<mxGraphModel dx="949" dy="540" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="ym0EkMZWyknAE99nMXu0-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="ym0EkMZWyknAE99nMXu0-7" target="ym0EkMZWyknAE99nMXu0-8">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="ym0EkMZWyknAE99nMXu0-10" value="out -&amp;gt; in" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="ym0EkMZWyknAE99nMXu0-9">
<mxGeometry x="0.1167" y="1" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="ym0EkMZWyknAE99nMXu0-7" value="Start" style="html=1;whiteSpace=wrap;" vertex="1" parent="1">
<mxGeometry x="330" y="310" width="110" height="50" as="geometry" />
</mxCell>
<mxCell id="ym0EkMZWyknAE99nMXu0-8" value="End" style="html=1;whiteSpace=wrap;" vertex="1" parent="1">
<mxGeometry x="560" y="310" width="110" height="50" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View file

@ -0,0 +1,75 @@
<mxfile>
<diagram id="prtHgNgQTEPvFCAcTncT" name="Page-1">
<mxGraphModel dx="1223" dy="645" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="dNxyNK7c78bLwvsdeMH5-19" value="Pool" style="swimlane;html=1;childLayout=stackLayout;resizeParent=1;resizeParentMax=0;horizontal=0;startSize=20;horizontalStack=0;" parent="1" vertex="1">
<mxGeometry x="120" y="120" width="450" height="360" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-27" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;dashed=1;endArrow=none;endFill=0;" parent="dNxyNK7c78bLwvsdeMH5-19" source="dNxyNK7c78bLwvsdeMH5-24" target="dNxyNK7c78bLwvsdeMH5-26" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-31" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=1;" parent="dNxyNK7c78bLwvsdeMH5-19" source="dNxyNK7c78bLwvsdeMH5-28" target="dNxyNK7c78bLwvsdeMH5-30" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-35" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=1;" parent="dNxyNK7c78bLwvsdeMH5-19" source="dNxyNK7c78bLwvsdeMH5-28" target="dNxyNK7c78bLwvsdeMH5-34" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-38" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=1;" parent="dNxyNK7c78bLwvsdeMH5-19" source="dNxyNK7c78bLwvsdeMH5-26" target="dNxyNK7c78bLwvsdeMH5-36" edge="1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="180" y="340" />
<mxPoint x="400" y="340" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-20" value="Lane 1" style="swimlane;html=1;startSize=20;horizontal=0;" parent="dNxyNK7c78bLwvsdeMH5-19" vertex="1">
<mxGeometry x="20" width="430" height="120" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-25" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="dNxyNK7c78bLwvsdeMH5-20" source="dNxyNK7c78bLwvsdeMH5-23" target="dNxyNK7c78bLwvsdeMH5-24" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-23" value="" style="ellipse;whiteSpace=wrap;html=1;" parent="dNxyNK7c78bLwvsdeMH5-20" vertex="1">
<mxGeometry x="40" y="40" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-24" value="" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Helvetica;fontSize=12;fontColor=#000000;align=center;" parent="dNxyNK7c78bLwvsdeMH5-20" vertex="1">
<mxGeometry x="120" y="30" width="80" height="60" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-33" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=1;" parent="dNxyNK7c78bLwvsdeMH5-20" source="dNxyNK7c78bLwvsdeMH5-30" target="dNxyNK7c78bLwvsdeMH5-32" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-30" value="" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Helvetica;fontSize=12;fontColor=#000000;align=center;" parent="dNxyNK7c78bLwvsdeMH5-20" vertex="1">
<mxGeometry x="240" y="30" width="80" height="60" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-32" value="" style="ellipse;whiteSpace=wrap;html=1;" parent="dNxyNK7c78bLwvsdeMH5-20" vertex="1">
<mxGeometry x="360" y="40" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-21" value="Lane 2" style="swimlane;html=1;startSize=20;horizontal=0;" parent="dNxyNK7c78bLwvsdeMH5-19" vertex="1">
<mxGeometry x="20" y="120" width="430" height="120" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-29" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=1;" parent="dNxyNK7c78bLwvsdeMH5-21" source="dNxyNK7c78bLwvsdeMH5-26" target="dNxyNK7c78bLwvsdeMH5-28" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-26" value="" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Helvetica;fontSize=12;fontColor=#000000;align=center;" parent="dNxyNK7c78bLwvsdeMH5-21" vertex="1">
<mxGeometry x="120" y="30" width="80" height="60" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-28" value="" style="rhombus;whiteSpace=wrap;html=1;fontFamily=Helvetica;fontSize=12;fontColor=#000000;align=center;" parent="dNxyNK7c78bLwvsdeMH5-21" vertex="1">
<mxGeometry x="260" y="40" width="40" height="40" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-22" value="Lane 3" style="swimlane;html=1;startSize=20;horizontal=0;" parent="dNxyNK7c78bLwvsdeMH5-19" vertex="1">
<mxGeometry x="20" y="240" width="430" height="120" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-37" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=1;" parent="dNxyNK7c78bLwvsdeMH5-22" source="dNxyNK7c78bLwvsdeMH5-34" target="dNxyNK7c78bLwvsdeMH5-36" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-34" value="" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Helvetica;fontSize=12;fontColor=#000000;align=center;" parent="dNxyNK7c78bLwvsdeMH5-22" vertex="1">
<mxGeometry x="240" y="20" width="80" height="60" as="geometry" />
</mxCell>
<mxCell id="dNxyNK7c78bLwvsdeMH5-36" value="" style="rhombus;whiteSpace=wrap;html=1;fontFamily=Helvetica;fontSize=12;fontColor=#000000;align=center;" parent="dNxyNK7c78bLwvsdeMH5-22" vertex="1">
<mxGeometry x="360" y="30" width="40" height="40" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View file

@ -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)

View file

@ -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;
}

View file

@ -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)

View file

@ -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";}

View file

@ -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";}

View file

@ -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";}

View file

@ -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";}

View file

@ -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";
}
m_exec:Merge {
ports_data_in = `["in1", "in2"]`;
}
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_exec) {from="success";to="in";}
:Conn_exec (m_exec -> 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";}

View file

@ -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_exec:Modify
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_exec) {from="success";to="in";}
:Conn_exec (m2 -> m_exec) {from="fail";to="in";}
:Conn_exec (m_exec -> 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";}

View file

@ -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";}

View file

@ -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";}

View file

@ -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";}

View file

@ -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";}

View file

@ -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";}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,5 @@
start:Start
end:End
end2:End
:Conn_exec (start -> end) {from="out";to="in";}

View file

@ -0,0 +1,5 @@
start:Start
start2:Start
end:End
:Conn_exec (start -> end) {from="out";to="in";}

View file

@ -0,0 +1 @@
start:Start

View file

@ -0,0 +1 @@
end:End

View file

@ -0,0 +1,3 @@
start:Start
end:End
:Conn_exec (start -> end) {from="out";to="in";}

View file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View file

@ -0,0 +1,260 @@
# Schedule Module
This module is used to define and execute model transformations using a schedule in the muMLE framework.
The development of this module is port of a research project of Robbe Teughels with Joeri Exelmans and Hans Vangheluwe.
## Module Structure
The entire module is wrapped in single interface [schedule.py](../rule_scheduler.py) responsible for loading, executing and other optional functionalities, such as generating dot files.
Loading modules (.py and .drawio) requires compilation. All these transformations are grouped together in [generator.py](../generator.py).
The interactions with the muMLE framework uses the custom interface: [rule_executor.py](../rule_executor.py). This reduces the dependency between the module and the framework.
Schedules are compiled to python files. These files have a fixed interface defined in [schedule.pyi](../schedule.pyi).
This interface includes functionalities that will setup the schedule structure and link patterns or other schedules from the module interface with the nodes.
The compiled files do not include any functional implementation to reduce their size and compile time. They are linked to a libary [schedule_lib](../schedule_lib) including an implementation for each node type.
This means that nodes can be treated as a black box by the schedule. This architecture allowing easier testing of the library as generation is fully independent of the core implementation.
The implementation of a given node is similar in the inheritance compared to the original meta-model to increasing traceability between the original instance and the compiled instance.
## Usage
### Running Module
```python
from state.devstate import DevState
from bootstrap.scd import bootstrap_scd
from util import loader
from transformation.ramify import ramify
from api.od import ODAPI
from transformation.schedule.rule_scheduler import RuleScheduler
state = DevState()
scd_mmm = bootstrap_scd(state)
# load model and meta-model
metamodel_cs = open('your_metamodel.od', 'r', encoding="utf-8").read()
model_cs = open('your_model.od', 'r', encoding="utf-8").read()
# Parse them
metamodel = loader.parse_and_check(state, metamodel_cs, scd_mmm, "your_metamodel")
model = loader.parse_and_check(state, model_cs, metamodel, "Example model")
# Ramified model
metamodel_ramified = ramify(state, metamodel)
# scheduler
scheduler = RuleScheduler(state, metamodel, metamodel_ramified)
# load schedule
scheduler.load_schedule("your_schedule.od")
# scheduler.load_schedule("your_schedule.py") # compiled version (without conformance checking)
# scheduler.load_schedule("your_schedule.drawio") # main page will be executed
# execute model transformation
api = ODAPI(state, model, metamodel)
scheduler.run(api)
```
#### Simple example schedules (.od format)
A schedule is executed from start to end or NullNode (reachable only from unconnected exec-gates).
Given the following basic schedule (ARule without NAC), the first match of the pre-condition_pattern is used to rewrite the host graph.
This schedule expect at least one match as the `fail' exec-gate of the match is not connected.
Zero matches leads to a NullState, resulting in early termination.
```markdown
start:Start
end:End
# match once
m:Match{
file = "your_pre-condition_pattern.od";
n = 1;
}
# rewrite
r:Rewrite{
file = "your_post-condition_pattern.od";
}
:Conn_exec (start -> m) {from="out"; to="in";}
:Conn_exec (m -> r) {from="success"; to="in";}
:Conn_exec (r -> end) {from="out"; to="in";}
:Conn_data (m -> r) {from="out"; to="in";}
```
![schedule_1](images/example_1.png)
With some small adjustments, all matches can be rewritten (FRule without NAC)
```markdown
start:Start
end:End
# match all
m:Match{
file = "your_pre-condition_pattern.od";
# n = +INF (if missing: all matches)
}
l:Loop
# rewrite
r:Rewrite{
file = "your_post-condition_pattern.od";
}
:Conn_exec (start -> m) {from="out"; to="in";}
:Conn_exec (m -> l) {from="success"; to="in";}
:Conn_exec (l -> r) {from="it"; to="in";}
:Conn_exec (r -> l) {from="out"; to="in";}
:Conn_exec (l -> end) {from="out"; to="in";}
:Conn_data (m -> l) {from="out"; to="in";}
:Conn_data (l -> r) {from="out"; to="in";}
```
![schedule_2](images/example_2.png)
Adding a NAC to this example: adding a match using the previous match and expecting it to fail. (FRule with NAC)
```markdown
start:Start
end:End
# match all
m:Match{
file = "your_pre-condition_pattern.od";
# n = +INF (if missing: all matches)
}
l:Loop
# NAC
n:Match{
file = "your_NAC_pre-condition_pattern.od";
n = 1; # one fail is enough
}
# rewrite
r:Rewrite{
file = "your_post-condition_pattern.od";
}
:Conn_exec (start -> m) {from="out"; to="in";}
:Conn_exec (m -> l) {from="success"; to="in";}
:Conn_exec (l -> n) {from="it"; to="in";}
:Conn_exec (n -> r) {from="fail"; to="in";}
:Conn_exec (r -> l) {from="out"; to="in";}
:Conn_exec (l -> end) {from="out"; to="in";}
:Conn_data (m -> l) {from="out"; to="in";}
:Conn_data (l -> n) {from="out"; to="in";}
:Conn_data (l -> r) {from="out"; to="in";}
```
![schedule_3](images/example_3.png)
## Node Types
### Start
This node indicates the start of a schedule.
It signature (additional ports) can be used to insert match sets or alternative exec-paths, increasing reusability.
[Start](schedule_lib/start.md)
### End
Counterpart to Start node. Reaching this node result in successful termination of the schedule.
It signature (additional ports) can be used to extract match sets or alternative exec-paths, increasing reusability.
[End](schedule_lib/end.md)
### Match
Matches a pre-condition pattern on the host-graph. A primitive defined in T-Core
[Match](schedule_lib/match.md)
### Rewrite
Rewrite the host-graph using a post-condition pattern. A primitive defined in T-Core
[Rewrite](schedule_lib/rewrite.md)
### Modify
Modifies the match set. This allows patterns to name elements to their linking.
This node modifies or deletes elements to be usable as pivot in another pattern with different names.
An example usage can be found in [examples/geraniums](../../../examples/geraniums).
In the following schedule, a cracked filed was matched and no longer needed.
The Modify node deletes this, allowing for the flowering flower match node to use a pattern without this element, reducing the size and making it more general.
![geraniums_main](images/geraniums-main.png)
[Modify](schedule_lib/modify.md)
### Merge
Combines multiple matches.
Allowing patterns to be split into different parts or reuse a specific part with another match without recalculating.
An example usage can be found in [examples/geraniums](../../../examples/geraniums).
In the following sub-schedule, a new pot and the flower with old pot and their connection, is combined to move the flower in a rewrite.
Replanting multiple flowers into one new pot would require markers, making the matching harder in order to combine these elements without the use of this node.
![geraniums_repot_flowers](images/geraniums-repot_flowers.png)
[Merge](schedule_lib/merge.md)
### Store
Combines matches (set) into a new match set.
Use the exec port to insert the data on the associated data-port to the set.
The direct usage of this node is limited but invaluable for libraries.
An example usage is petrinet-execution with user interface.
This requires a list of all transitions that can fire.
Matching "all transitions" followed by a loop to check the NAC leaves single matches.
This nodes allows these matches to be recombined into a set that can be used to choose a transition from.
[Store](schedule_lib/store.md)
### Loop
Iterate over a given match set.
Nodes such as Match or Rewrite uses a single match as a pivot.
Executing these nodes over all the element is possible with this node.
See the examples in [Modify](#Modify) or [Merge](#Merge) for an example view.
[Loop](schedule_lib/loop.md)
### Print
Print the input data. This is mainly used as a debugging/testing tool to validate intermediate information or state.
[Print](schedule_lib/print.md)
### Action
This node allows for code to be injected into the schedule.
This node can be used for general purpuse and even recreate all other nodes (except start and end).
Not all functionalities can be described using the current nodes. For petrinets, an example can be to generate a visual overview of the petrinet-system.
[Action.md](schedule_lib/action.md)
## Edge Types
Nodes can be connected using two different edges. The execution-edges define the execution flow of the schedule.
These connections can only connect nodes that inherit form [ExecNode](schedule_lib/exec_node.md).
Connecting nodes between execution-gates defined by the nodes, happens in a system of "one to many" for gates.
The data-edges allows information to be distributed to other [DataNode](schedule_lib/data_node.md).
This happens in the opposite way of "many to one" on data-gates.
Data changes on a gate wil notify all connected nodes of the changes, allowing propagation through the system. Note: the data received is immutable to ensure consistent and reliable execution of the schedule.
## file formats
### .od
This is the original textual file format used by the framework. The main advantage of this format is the integration with the framework that allows conformance checking of the scheduling language.
Therefore, all other formats are converted to this type for conformance checking before being compiled.
### .py
All schedules are compiled to python after conformance checking. Allowing this format provides the benefit to load schedules without expensive compilation or conformance checking, reducing computational cost.
This format is recommended in the deployment of applications where the schedule will not change.
It is not advisable to implement schedules directly in this format as conformance checking guarantees proper working of the schedule module.
### .drawio
A visual format for the drawio application.
The library includes a drawio [library](../schedule_lib/Schedule_lib.xml) that includes a representation with additional fields for easy integration with the application.
The main advantage of this format is the usage of pages that allows sub-schedules be easily created and organised within one schedule. (layers are not allowed)

View file

@ -0,0 +1 @@
# Under construction

View file

@ -0,0 +1 @@
# Under construction

View file

@ -0,0 +1 @@
# Under construction

View file

@ -0,0 +1 @@
# Under construction

View file

@ -0,0 +1 @@
# Under construction

View file

@ -0,0 +1 @@
# Under construction

View file

@ -0,0 +1 @@
# Under construction

View file

@ -0,0 +1 @@
# Under construction

View file

@ -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.

View file

@ -0,0 +1 @@
# Under construction

View file

@ -0,0 +1 @@
# Under construction

View file

@ -0,0 +1 @@
# Under construction

View file

@ -0,0 +1 @@
# Under construction

View file

@ -0,0 +1 @@
# Under construction

View file

@ -0,0 +1 @@
# Under construction

View file

@ -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)

View file

@ -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, "<string>", "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,
}

View file

@ -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: ...

View file

@ -0,0 +1,194 @@
abstract class Exec
association Conn_exec [0..*] Exec -> Exec [0..*] {
String from;
String to;
}
abstract class Data
association Conn_data [0..*] Data -> Data [0..*] {
String from;
String to;
}
class Start [1..1] (Exec, Data) {
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] (Exec, Data) {
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 (Exec, Data)
{
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 (Exec, Data)
{
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 (Data)
{
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 (Data)
{
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 (Exec, Data)
{
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 (Exec, Data)
{
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(Exec, Data)
{
```
check_all_connections(this, [
["in"],
["it", "out"],
["in"],
["out"]
])
```;
}
class Print(Exec, Data)
{
optional Boolean event;
optional String label;
optional ActionCode custom `check_jinja2_code(get_source(this), "custom")`;
```
check_all_connections(this, [
["in"],
["out"],
["in"],
[]
])
```;
}

View file

@ -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)

View file

@ -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 RuleScheduler:
__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

View file

@ -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 RuleScheduler
class Schedule:
__slots__ = {
"start",
"end",
"nodes"
}
def __init__(self): ...
@staticmethod
def get_matchers(): ...
def init_schedule(self, scheduler: RuleScheduler, rule_executor: RuleExecutor, matchers): ...
def generate_dot(self, *args, **kwargs): ...

View file

@ -0,0 +1,93 @@
<mxlibrary>[
{
"xml": "&lt;mxGraphModel&gt;&lt;root&gt;&lt;mxCell id=\"0\"/&gt;&lt;mxCell id=\"1\" parent=\"0\"/&gt;&lt;object label=\"%name%: %type%\" placeholders=\"1\" name=\"start_name\" type=\"Start\" ports_exec_out=\"[&amp;quot;out&amp;quot;]\" ports_data_out=\"[]\" id=\"2\"&gt;&lt;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\"&gt;&lt;mxGeometry width=\"160\" height=\"100\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry y=\"40\" width=\"160\" height=\"60\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;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\"&gt;&lt;mxGeometry width=\"80\" height=\"60\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;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\"&gt;&lt;mxGeometry x=\"80\" width=\"80\" height=\"60\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"out\" type=\"exec\" id=\"6\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"5\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;/root&gt;&lt;/mxGraphModel&gt;",
"w": 160,
"h": 100,
"aspect": "fixed",
"title": "Start Node"
},
{
"xml": "&lt;mxGraphModel&gt;&lt;root&gt;&lt;mxCell id=\"0\"/&gt;&lt;mxCell id=\"1\" parent=\"0\"/&gt;&lt;object label=\"%name%: %type%\" placeholders=\"1\" name=\"end_name\" type=\"End\" ports_exec_in=\"[&amp;quot;in&amp;quot;]\" ports_data_in=\"[]\" id=\"2\"&gt;&lt;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\"&gt;&lt;mxGeometry width=\"160\" height=\"100\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry y=\"40\" width=\"160\" height=\"60\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;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\"&gt;&lt;mxGeometry width=\"80\" height=\"60\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"in\" type=\"exec\" id=\"5\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry x=\"80\" width=\"80\" height=\"60\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;/root&gt;&lt;/mxGraphModel&gt;",
"w": 160,
"h": 100,
"aspect": "fixed",
"title": "End Node"
},
{
"xml": "&lt;mxGraphModel&gt;&lt;root&gt;&lt;mxCell id=\"0\"/&gt;&lt;mxCell id=\"1\" parent=\"0\"/&gt;&lt;object label=\"%name%: %type%&amp;#10;%file%&amp;#10;matches: %n%\" placeholders=\"1\" name=\"match_name\" type=\"Match\" file=\"rule_filename.od\" n=\"1\" id=\"2\"&gt;&lt;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\"&gt;&lt;mxGeometry width=\"160\" height=\"220\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry y=\"60\" width=\"160\" height=\"160\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;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\"&gt;&lt;mxGeometry width=\"80\" height=\"160\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"160\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"in\" type=\"data\" id=\"5\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"4\"&gt;&lt;mxGeometry x=\"10\" y=\"110\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;object label=\"in\" type=\"exec\" id=\"6\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry x=\"80\" width=\"80\" height=\"160\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"160\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"out\" type=\"data\" id=\"8\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"7\"&gt;&lt;mxGeometry x=\"10\" y=\"110\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;object label=\"success\" type=\"exec\" id=\"9\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"7\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;object label=\"fail\" type=\"exec\" id=\"10\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"7\"&gt;&lt;mxGeometry x=\"10\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;/root&gt;&lt;/mxGraphModel&gt;",
"w": 160,
"h": 220,
"aspect": "fixed",
"title": "Match Node"
},
{
"xml": "&lt;mxGraphModel&gt;&lt;root&gt;&lt;mxCell id=\"0\"/&gt;&lt;mxCell id=\"1\" parent=\"0\"/&gt;&lt;object label=\"%name%: %type%&amp;#10;%file%\" placeholders=\"1\" name=\"rewrite_name\" type=\"Rewrite\" file=\"rule_filename.od\" id=\"2\"&gt;&lt;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\"&gt;&lt;mxGeometry y=\"1.1368683772161603e-13\" width=\"160\" height=\"150\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry y=\"40\" width=\"160\" height=\"110\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;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\"&gt;&lt;mxGeometry width=\"80\" height=\"110\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"110\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"in\" type=\"exec\" id=\"5\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry x=\"80\" width=\"80\" height=\"110\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"110\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"out\" type=\"exec\" id=\"7\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"6\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;object label=\"in\" type=\"data\" id=\"8\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"6\"&gt;&lt;mxGeometry x=\"-70\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;object label=\"out\" type=\"data\" id=\"9\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"6\"&gt;&lt;mxGeometry x=\"10\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;/root&gt;&lt;/mxGraphModel&gt;",
"w": 160,
"h": 150,
"aspect": "fixed",
"title": "Rewrite Node"
},
{
"xml": "&lt;mxGraphModel&gt;&lt;root&gt;&lt;mxCell id=\"0\"/&gt;&lt;mxCell id=\"1\" parent=\"0\"/&gt;&lt;object label=\"%name%: %type%\" placeholders=\"1\" name=\"action_name\" type=\"Action\" ports_exec_in=\"[&amp;quot;in&amp;quot;]\" ports_exec_out=\"[&amp;quot;out&amp;quot;]\" ports_data_in=\"[]\" ports_data_out=\"[]\" action=\"print(&amp;quot;hello world&amp;quot;)\" id=\"2\"&gt;&lt;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\"&gt;&lt;mxGeometry width=\"160\" height=\"100\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry y=\"40\" width=\"160\" height=\"60\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;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\"&gt;&lt;mxGeometry width=\"80\" height=\"60\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"in\" type=\"exec\" id=\"5\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry x=\"80\" width=\"80\" height=\"60\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"out\" type=\"exec\" id=\"7\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"6\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;/root&gt;&lt;/mxGraphModel&gt;",
"w": 160,
"h": 100,
"aspect": "fixed",
"title": "Action Node"
},
{
"xml": "&lt;mxGraphModel&gt;&lt;root&gt;&lt;mxCell id=\"0\"/&gt;&lt;mxCell id=\"1\" parent=\"0\"/&gt;&lt;object label=\"%name%: %type%\" placeholders=\"1\" name=\"modify_name\" type=\"Modify\" rename=\"{&amp;quot;t&amp;quot;:&amp;quot;transition&amp;quot;}\" delete=\"[]\" id=\"2\"&gt;&lt;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\"&gt;&lt;mxGeometry width=\"160\" height=\"100\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry y=\"40\" width=\"160\" height=\"60\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;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\"&gt;&lt;mxGeometry width=\"80\" height=\"60\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"in\" type=\"data\" id=\"5\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"4\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry x=\"80\" width=\"80\" height=\"60\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"out\" type=\"data\" id=\"7\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"6\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;/root&gt;&lt;/mxGraphModel&gt;",
"w": 160,
"h": 100,
"aspect": "fixed",
"title": "Modify Node"
},
{
"xml": "&lt;mxGraphModel&gt;&lt;root&gt;&lt;mxCell id=\"0\"/&gt;&lt;mxCell id=\"1\" parent=\"0\"/&gt;&lt;object label=\"%name%: %type%\" placeholders=\"1\" name=\"merge_name\" type=\"Merge\" ports_data_in=\"[&amp;quot;input1&amp;quot;, &amp;quot;input2&amp;quot;]\" id=\"2\"&gt;&lt;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\"&gt;&lt;mxGeometry width=\"160\" height=\"150\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry y=\"40\" width=\"160\" height=\"110\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;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\"&gt;&lt;mxGeometry width=\"80\" height=\"110\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"110\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"input1\" type=\"data\" id=\"5\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"4\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;object label=\"input2\" type=\"data\" id=\"6\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"4\"&gt;&lt;mxGeometry x=\"10\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry x=\"80\" width=\"80\" height=\"110\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"110\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"out\" type=\"data\" id=\"8\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"7\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;/root&gt;&lt;/mxGraphModel&gt;",
"w": 160,
"h": 150,
"aspect": "fixed",
"title": "Merge Node"
},
{
"xml": "&lt;mxGraphModel&gt;&lt;root&gt;&lt;mxCell id=\"0\"/&gt;&lt;mxCell id=\"1\" parent=\"0\"/&gt;&lt;object label=\"%name%: %type%\" placeholders=\"1\" name=\"store_name\" type=\"Store\" ports=\"[&amp;quot;input1&amp;quot;]\" id=\"2\"&gt;&lt;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\"&gt;&lt;mxGeometry width=\"160\" height=\"200\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry y=\"40\" width=\"160\" height=\"160\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;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\"&gt;&lt;mxGeometry width=\"80\" height=\"160\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"160\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"in\" type=\"exec\" id=\"5\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;object label=\"input1\" type=\"exec\" id=\"6\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"&gt;&lt;mxGeometry x=\"10\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;object label=\"input1\" type=\"data\" id=\"7\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"4\"&gt;&lt;mxGeometry x=\"10\" y=\"110\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry x=\"80\" width=\"80\" height=\"160\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"160\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"out\" type=\"data\" id=\"9\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"8\"&gt;&lt;mxGeometry x=\"10\" y=\"110\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;object label=\"out\" type=\"exec\" id=\"10\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"8\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;object label=\"input1\" type=\"exec\" id=\"11\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"8\"&gt;&lt;mxGeometry x=\"10\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;/root&gt;&lt;/mxGraphModel&gt;",
"w": 160,
"h": 200,
"aspect": "fixed",
"title": "Store Node"
},
{
"xml": "&lt;mxGraphModel&gt;&lt;root&gt;&lt;mxCell id=\"0\"/&gt;&lt;mxCell id=\"1\" parent=\"0\"/&gt;&lt;object label=\"%name%: %type%\" placeholders=\"1\" name=\"loop_name\" type=\"Loop\" id=\"2\"&gt;&lt;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\"&gt;&lt;mxGeometry width=\"160\" height=\"200\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry y=\"40\" width=\"160\" height=\"160\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;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\"&gt;&lt;mxGeometry width=\"80\" height=\"160\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"160\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"in\" type=\"data\" id=\"5\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"4\"&gt;&lt;mxGeometry x=\"10\" y=\"110\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;object label=\"in\" type=\"exec\" id=\"6\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry x=\"80\" width=\"80\" height=\"160\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"160\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"out\" type=\"data\" id=\"8\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"7\"&gt;&lt;mxGeometry x=\"10\" y=\"110\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;object label=\"it\" type=\"exec\" id=\"9\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"7\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;object label=\"out\" type=\"exec\" id=\"10\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"7\"&gt;&lt;mxGeometry x=\"10\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;/root&gt;&lt;/mxGraphModel&gt;",
"w": 160,
"h": 200,
"aspect": "fixed",
"title": "Loop Node"
},
{
"xml": "&lt;mxGraphModel&gt;&lt;root&gt;&lt;mxCell id=\"0\"/&gt;&lt;mxCell id=\"1\" parent=\"0\"/&gt;&lt;object label=\"%name%: %type%&amp;#10;%file%\" placeholders=\"1\" name=\"schedule_name\" type=\"Schedule\" file=\"schedule_page-name\" id=\"2\"&gt;&lt;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\"&gt;&lt;mxGeometry width=\"160\" height=\"100\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry y=\"40\" width=\"160\" height=\"60\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;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\"&gt;&lt;mxGeometry width=\"80\" height=\"60\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"out\" type=\"exec\" id=\"5\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry x=\"80\" width=\"80\" height=\"60\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"60\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"in\" type=\"exec\" id=\"7\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"6\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;/root&gt;&lt;/mxGraphModel&gt;",
"w": 160,
"h": 100,
"aspect": "fixed",
"title": "Schedule Node"
},
{
"xml": "&lt;mxGraphModel&gt;&lt;root&gt;&lt;mxCell id=\"0\"/&gt;&lt;mxCell id=\"1\" parent=\"0\"/&gt;&lt;object label=\"%name%: %type%\" placeholders=\"1\" name=\"print_name\" type=\"Print\" event=\"False\" custom=\"{{ data }}\" id=\"2\"&gt;&lt;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\"&gt;&lt;mxGeometry width=\"160\" height=\"150\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry y=\"40\" width=\"160\" height=\"110\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;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\"&gt;&lt;mxGeometry width=\"80\" height=\"110\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"110\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"in\" type=\"exec\" id=\"5\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"4\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;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\"&gt;&lt;mxGeometry x=\"80\" width=\"80\" height=\"110\" as=\"geometry\"&gt;&lt;mxRectangle width=\"80\" height=\"110\" as=\"alternateBounds\"/&gt;&lt;/mxGeometry&gt;&lt;/mxCell&gt;&lt;object label=\"out\" type=\"exec\" id=\"7\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"6\"&gt;&lt;mxGeometry x=\"10\" y=\"10\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;object label=\"in\" type=\"data\" id=\"8\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"6\"&gt;&lt;mxGeometry x=\"-70\" y=\"60\" width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;/root&gt;&lt;/mxGraphModel&gt;",
"w": 160,
"h": 150,
"aspect": "fixed",
"title": "Print Node"
},
{
"xml": "&lt;mxGraphModel&gt;&lt;root&gt;&lt;mxCell id=\"0\"/&gt;&lt;mxCell id=\"1\" parent=\"0\"/&gt;&lt;object label=\"out\" type=\"exec\" id=\"2\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;\" vertex=\"1\" parent=\"1\"&gt;&lt;mxGeometry width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;/root&gt;&lt;/mxGraphModel&gt;",
"w": 60,
"h": 40,
"aspect": "fixed",
"title": "Exec Gate"
},
{
"xml": "&lt;mxGraphModel&gt;&lt;root&gt;&lt;mxCell id=\"0\"/&gt;&lt;mxCell id=\"1\" parent=\"0\"/&gt;&lt;object label=\"in\" type=\"data\" id=\"2\"&gt;&lt;mxCell style=\"rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;\" vertex=\"1\" parent=\"1\"&gt;&lt;mxGeometry width=\"60\" height=\"40\" as=\"geometry\"/&gt;&lt;/mxCell&gt;&lt;/object&gt;&lt;/root&gt;&lt;/mxGraphModel&gt;",
"w": 60,
"h": 40,
"aspect": "fixed",
"title": "Data Gate"
}
]</mxlibrary>

View file

@ -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",
]

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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(),
),
}
)

View file

@ -0,0 +1,61 @@
from abc import abstractmethod
from typing import override
from jinja2 import Template
from api.od import ODAPI
from .funcs import generate_dot_edge
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
@override
def generate_dot(
self, nodes: list[str], edges: list[str], visited: set[int], template: Template
) -> None:
for out_port, edge in self.next_node.items():
template.render()
generate_dot_edge(
self,
edge[0],
edges,
template,
kwargs={
"prefix": "e",
"from_gate": out_port,
"to_gate": edge[1],
"color": "darkblue",
},
)
for edge in self.next_node.values():
edge[0].generate_dot(nodes, edges, visited, template)

View file

@ -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}
)
)

View file

@ -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)

View file

@ -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\n{self.label}\nn = {self.n}",
"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)

View file

@ -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)

View file

@ -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)

View file

@ -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.
"""

View file

@ -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(),
),
}
)

View file

@ -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)

View file

@ -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": f"rewrite\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)

View file

@ -0,0 +1,9 @@
from abc import ABCMeta
class Singleton(ABCMeta):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]

View file

@ -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)

View file

@ -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)

View file

@ -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 RuleScheduler
class ScheduleState:
def __init__(self) -> None:
self.end_gate: str = ""
class SubSchedule(ExecNode, DataNode):
def __init__(self, scheduler: "RuleScheduler", file: str) -> None:
self.schedule = scheduler._load_schedule(file, _main=False)
self.scheduler = scheduler
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.scheduler._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)

View file

@ -0,0 +1,65 @@
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=[], debug = False) %}
subgraph cluster_{{ id }} {
label = "
{%- if debug %}
{{ id }}_
{%- endif -%}
{{ 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) %}
<<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
{% if ports_exec or ports_data %}
{% if ports_exec %}
<TR><TD>
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" COLOR="darkblue">
{% for port_e in ports_exec %}
<TR><TD PORT="e_{{ port_e }}">{{ port_e }}</TD></TR>
{% endfor %}
</TABLE>
</TD></TR>
{% endif %}
{% if ports_data %}
<TR><TD>
<TABLE BORDER="1" CELLBORDER="1" CELLSPACING="0" COLOR="green">
{% for port_d in ports_data %}
<TR><TD PORT="d_{{ port_d }}">{{ port_d }}</TD></TR>
{% endfor %}
</TABLE>
</TD></TR>
{% endif %}
{% else %}
<TR><TD>&nbsp;</TD></TR>
{% endif %}
</TABLE>>
{%- endmacro %}

View file

@ -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 -%}

View file

@ -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(scheduler, "{{ 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 %}

View file

@ -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, scheduler, 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)