A fully working version of the scheduling language with added examples

This commit is contained in:
robbe 2025-06-27 12:21:41 +02:00
parent ec42f74960
commit ebfd85a666
126 changed files with 7235 additions and 981 deletions

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,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,35 @@
from abc import abstractmethod
from api.od import ODAPI
from .node import Node
class ExecNode(Node):
def __init__(self) -> None:
super().__init__()
from .null_node import NullNode
self.next_node: dict[str, tuple[ExecNode, str]] = {}
for port in self.get_exec_output_gates():
self.next_node[port] = (NullNode(), "in")
def nextState(self, exec_id: int) -> tuple["ExecNode", str]:
return self.next_node["out"]
@staticmethod
def get_exec_input_gates():
return ["in"]
@staticmethod
def get_exec_output_gates():
return ["out"]
def connect(self, next_state: "ExecNode", from_gate: str, to_gate: str) -> None:
if from_gate not in self.get_exec_output_gates():
raise Exception(f"from_gate {from_gate} is not a valid port")
if to_gate not in next_state.get_exec_input_gates():
raise Exception(f"to_gate {to_gate} is not a valid port")
self.next_node[from_gate] = (next_state, to_gate)
@abstractmethod
def execute(self, port: str, exec_id: int, od: ODAPI) -> tuple[int, any] | None:
return None

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_{self.n}\n{self.label}",
"ports_exec": (
self.get_exec_input_gates(),
self.get_exec_output_gates(),
),
"ports_data": (
self.get_data_input_gates(),
self.get_data_output_gates(),
),
},
)
ExecNode.generate_dot(self, nodes, edges, visited, template)
DataNode.generate_dot(self, nodes, edges, visited, template)

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": "rewrite",
"ports_exec": (
self.get_exec_input_gates(),
self.get_exec_output_gates(),
),
"ports_data": (
self.get_data_input_gates(),
self.get_data_output_gates(),
),
},
)
ExecNode.generate_dot(self, nodes, edges, visited, template)
DataNode.generate_dot(self, nodes, edges, visited, template)

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 RuleSchedular
class ScheduleState:
def __init__(self) -> None:
self.end_gate: str = ""
class SubSchedule(ExecNode, DataNode):
def __init__(self, schedular: "RuleSchedular", file: str) -> None:
self.schedule = schedular._load_schedule(file, _main=False)
self.schedular = schedular
super().__init__()
self.state: dict[int, ScheduleState] = {}
@override
def nextState(self, exec_id: int) -> tuple["ExecNode", str]:
return self.next_node[self.get_state(exec_id).end_gate]
@override
def get_exec_input_gates(self) -> "List[ExecNode]":
return self.schedule.start.get_exec_output_gates()
@override
def get_exec_output_gates(self) -> "List[ExecNode]":
return [*self.schedule.end.get_exec_input_gates()]
@override
def get_data_input_gates(self) -> "List[ExecNode]":
return self.schedule.start.get_data_output_gates()
@override
def get_data_output_gates(self) -> "List[ExecNode]":
return self.schedule.end.get_data_input_gates()
def get_state(self, exec_id) -> ScheduleState:
return self.state[exec_id]
@override
def generate_stack_frame(self, exec_id: int) -> None:
super().generate_stack_frame(exec_id)
self.state[exec_id] = ScheduleState()
@override
def delete_stack_frame(self, exec_id: int) -> None:
super().delete_stack_frame(exec_id)
self.state.pop(exec_id)
@override
def execute(self, port: str, exec_id: int, od: ODAPI) -> tuple[int, any] | None:
runstatus, result = self.schedular._runner(
od,
self.schedule,
port,
IdGenerator.generate_exec_id(),
{
port: self.get_input_data(port, exec_id)
for port, value in self.data_in.items()
if value is not None and not value.empty(exec_id)
},
)
if runstatus != 1:
return runstatus, result
self.get_state(exec_id).end_gate = result["exec_gate"]
results_data = result["data_out"]
for port, data in self.data_out.items():
if port in results_data:
self.data_out[port].replace(exec_id, results_data[port])
DataNode.input_event(self, port, exec_id)
continue
if not data.empty(exec_id):
data.clear(exec_id)
DataNode.input_event(self, port, exec_id)
return None
@not_visited
def generate_dot(
self, nodes: List[str], edges: List[str], visited: set[int], template: Template
) -> None:
generate_dot_node(
self,
nodes,
template,
**{
"label": "rrrrrrrrrr",
"ports_exec": (
self.get_exec_input_gates(),
self.get_exec_output_gates(),
),
"ports_data": (
self.get_data_input_gates(),
self.get_data_output_gates(),
),
}
)
super().generate_dot(nodes, edges, visited, template)