瀏覽代碼

Changed architecture of sccd compiler to support DEVS

sampieters 1 年之前
父節點
當前提交
4956ad9ffa

+ 6 - 0
.gitignore

@@ -1,4 +1,10 @@
 pypdevs/
+sccd2devs_example/
+sccd2python_example/
+input.xml
+TrafficLight.py
+TrafficLightSimulation.py
+
 
 # Byte-compiled / optimized Python files
 __pycache__/

+ 400 - 365
input.xml

@@ -1,370 +1,405 @@
-<?xml version="1.0" ?>
-<diagram author="Simon Van Mierlo" name="Bouncing Balls - Tkinter Version ">
-	<description>
-		Tkinter frame with bouncing balls in it.
-	</description>
-	<top>
-		import time
-		import random
-		import Tkinter as tk
-		from mvk_widget import MvKWidget
-	</top>
-	<inport name="input"/>
-	<class name="MainApp" default="true">
-		<relationships>
-			<association name="fields" class="Field" />
-			<inheritance class="RuntimeClassBase" priority='1'/>
-			<inheritance class="tk.Tk" priority='0'/>
-		</relationships>
-		<method name="MainApp">
-			<body>
-				tk.Tk.__init__(self)
-				self.fixed_update_time = 20
-				self.update_self()
-				self.withdraw()
-				self.nr_of_fields = 0
-			</body>
-		</method>
-		<method name="update_self">
-			<body>
-				self.controller.update(self.fixed_update_time / 1000.0)
-				self.schedule_time = time.time()
-				self.scheduled_update_id = self.after(self.fixed_update_time, self.update_self)
-			</body>
-		</method>
-		<scxml initial="running">
-			<state id="running" initial="root">
-				<parallel id="root">
-					<state id="main_behaviour" initial="initializing">
-						<state id="initializing">
-							<transition target="../running">
-								<raise event="create_field" />
-							</transition>
-						</state>
-						<state id="running">
-							<transition target='.' event='button_pressed' cond='event_name == "create_new_field"'>
-								<parameter name="event_name" type="str" />
-								<raise event="create_field" />
-							</transition>
-						</state>
-					</state>
-					<state id="cd_behaviour" initial="waiting">
-						<state id="waiting">
-							<transition event="create_field" target="../creating">
-								<raise scope="cd" event="create_instance">
-									<parameter expr='"fields"' />
-								</raise>
-							</transition>
-							<transition event="delete_field" target='../check_nr_of_fields'>
-								<parameter name="association_name" type="str"/>
-								<raise scope="cd" event="delete_instance">
-									<parameter expr='association_name' />
-								</raise>
-								<script>
-									self.nr_of_fields -= 1
-								</script>
-							</transition>
-						</state>
-						<state id="creating">
-							<transition event="instance_created" target="../waiting">
-								<parameter name="association_name" type="string"/>
-								<raise scope="cd" event="start_instance">
-									<parameter expr="association_name" />
-								</raise>
-								<raise scope="narrow" event="set_association_name" target="association_name">
-									<parameter expr="association_name" />
-								</raise>
-								<script>
-									self.nr_of_fields += 1
-								</script>
-							</transition>
-						</state>
-						<state id="check_nr_of_fields">
-							<transition target="../../../stopped" cond="self.nr_of_fields == 0">
-								<script>
-									self.destroy()
-								</script>
-							</transition>
-							<transition target="../waiting" cond="self.nr_of_fields != 0"/>
-						</state>
-					</state>
-				</parallel>
-				<state id="stopped" />
-			</state>
-		</scxml>
-	</class>
-	<class name="Field">
-		<relationships>
-			<association name="balls" class="Ball" />
-			<association name="buttons" class="Button" />
-			<association name="parent" class="MainApp" min="1" max="1" />
-			<inheritance class="RuntimeClassBase" priority='1'/>
-			<inheritance class="tk.Toplevel" priority='0'/>
-			<inheritance class="MvKWidget" priority='-1'/>
-		</relationships>
-		<method name="Field">
-			<body>
-				<![CDATA[
-				tk.Toplevel.__init__(self)
-				self.title('BouncingBalls')
+<?xml version="1.1" ?>
+<diagram author="Simon Van Mierlo+Raphael Mannadiar" name="Bouncing_Balls_Python_Version">
+    <description>
+        Tkinter frame with bouncing balls in it.
+    </description>
+    <top>
+        from sccd.runtime.libs.ui import ui
+        from sccd.runtime.libs.utils import utils
+        import random
+    </top>
+    <inport name="ui"/>
+    <class name="MainApp" default="true">
+        <relationships>
+            <association name="fields" class="Field" />
+        </relationships>
+        <constructor>
+            <body>
+                <![CDATA[
+                self.nr_of_fields = 0
+                ]]>
+            </body>
+        </constructor>
+        <scxml initial="running">
+            <state id="running" initial="root">
+                <parallel id="root">
+                    <state id="main_behaviour" initial="initializing">
+                        <state id="initializing">
+                            <transition target="../running">
+                                <raise event="create_field" />
+                            </transition>
+                        </state>
+                        <state id="running">
+                            <transition target='.' event='button_pressed' cond='event_name == "create_new_field"'>
+                                <parameter name="event_name" type="str" />
+                                <raise event="create_field" />
+                            </transition>
+                        </state>
+                    </state>
+                    <state id="cd_behaviour" initial="waiting">
+                        <state id="waiting">
+                            <transition event="create_field" target="../creating">
+                                <raise scope="cd" event="create_instance">
+                                    <parameter expr='"fields"' />
+                                </raise>
+                            </transition>
+                            <transition event="delete_field" target='../check_nr_of_fields'>
+                                <parameter name="association_name" type="str"/>
+                                <raise scope="cd" event="delete_instance">
+                                    <parameter expr='association_name' />
+                                </raise>
+                                <script>
+                                    <![CDATA[
+                                    self.nr_of_fields -= 1
+                                    ]]>
+                                </script>
+                            </transition>
+                        </state>
+                        <state id="creating">
+                            <transition event="instance_created" target="../waiting">
+                                <parameter name="association_name" type="string"/>
+                                <raise scope="cd" event="start_instance">
+                                    <parameter expr="association_name" />
+                                </raise>
+                                <raise scope="narrow" event="set_association_name" target="association_name">
+                                    <parameter expr="association_name" />
+                                </raise>
+                                <script>
+                                    <![CDATA[
+                                    self.nr_of_fields += 1
+                                    ]]>
+                                </script>
+                            </transition>
+                        </state>
+                        <state id="check_nr_of_fields">
+                            <transition target="../stopped" cond="self.nr_of_fields == 0" after="0.05">
+                                <raise event="stop" />
+                            </transition>
+                            <transition target="../waiting" cond="self.nr_of_fields != 0"/>
+                        </state>
+                        <state id="stopped" />
+                    </state>
+                    <transition target="../stopped" event="stop">
+                        <script>
+                            <![CDATA[
+                            ui.close_window(ui.window)
+                            ]]>
+                        </script>
+                    </transition>
+                </parallel>
+                <state id="stopped" />
+            </state>
+        </scxml>
+    </class>
 
-				CANVAS_SIZE_TUPLE = (0, 0, self.winfo_screenwidth() * 2, self.winfo_screenheight() * 2)
-				self.c = tk.Canvas(self, relief=tk.RIDGE, scrollregion=CANVAS_SIZE_TUPLE)
+    <class name="Field">
+        <attribute name="canvas" />
+        <attribute name="field_window" />
+        <inport name="field_ui"/>
+        <relationships>
+            <association name="balls" class="Ball" />
+            <association name="buttons" class="Button" />
+            <association name="parent" class="MainApp" min="1" max="1" />
+        </relationships>
+        <constructor>
+            <body>
+                <![CDATA[
+                self.field_window = ui.new_window(800,600,"BouncingBalls");
+                self.canvas = ui.append_canvas(self.field_window,800,600,{'background':'#eee'});
+                ui.bind_event(self.field_window, ui.EVENTS.WINDOW_CLOSE, self.controller, 'window_close', self.inports['field_ui']);
+                ui.bind_event(self.field_window, ui.EVENTS.KEY_PRESS, self.controller, 'key_press', self.inports['field_ui']);
+                ui.bind_event(self.canvas.element, ui.EVENTS.MOUSE_RIGHT_CLICK,    self.controller, 'right_click', self.inports['field_ui']);
+                ui.bind_event(self.canvas.element, ui.EVENTS.MOUSE_MOVE, self.controller, 'mouse_move', self.inports['field_ui']);
+                ui.bind_event(self.canvas.element, ui.EVENTS.MOUSE_RELEASE, self.controller, 'mouse_release', self.inports['field_ui']);
+                ]]>
+            </body>
+        </constructor>
+        <destructor>
+            <body>
+                <![CDATA[
+                ui.close_window(self.field_window);
+                ]]>
+            </body>
+        </destructor>
+        <scxml initial="root">
+            <state id="root" initial="waiting">
+                <state id="waiting">
+                    <transition event="set_association_name" target="../initializing">
+                        <parameter name="association_name" type="str" />
+                        <script>
+                            <![CDATA[
+                            self.association_name = association_name
+                            ]]>
+                        </script>
+                    </transition>
+                </state>
+                <state id="initializing">
+                    <transition target="../creating">
+                        <raise scope="cd" event="create_instance">
+                            <parameter expr='"buttons"' />
+                            <parameter expr='"Button"' />
+                            <parameter expr="self" />
+                            <parameter expr="'create_new_field'" />
+                            <parameter expr="'Spawn New Window'" />
+                        </raise>
+                    </transition>
+                </state>
+                <state id="creating">
+                    <transition event='instance_created' target='../packing'>
+                        <parameter name="association_name" type="string"/>
+                        <raise scope="cd" event="start_instance">
+                            <parameter expr="association_name" />
+                        </raise>
+                    </transition>
+                </state>
+                <state id="packing">
+                    <transition event="button_created" target='../running'>
+                    </transition>
+                </state>
+                <parallel id="running">
+                    <transition port="field_ui" event="window_close" target="../deleting">
+                        <raise event="delete_instance" scope="cd">
+                            <parameter expr='"buttons"' />
+                        </raise>
+                        <raise event="delete_instance" scope="cd">
+                            <parameter expr='"balls"' />
+                        </raise>
+                    </transition>
+                    <state id="main_behaviour" initial="running">
+                        <state id="running">
+                            <transition port="field_ui" event="right_click" target="../creating">
+                                <parameter name="x" />
+                                <parameter name="y" />
+                                <parameter name="button" />
+                                <raise scope="cd" event="create_instance">
+                                    <parameter expr='"balls"' />
+                                    <parameter expr='"Ball"' />
+                                    <parameter expr="self.canvas" />
+                                    <parameter expr="x" />
+                                    <parameter expr="y" />
+                                </raise>
+                            </transition>
+                        </state>
+                        <state id="creating">
+                            <transition event="instance_created" target="../running">
+                                <parameter name="association_name" type="string"/>
+                                <raise scope="cd" event="start_instance">
+                                    <parameter expr="association_name" />
+                                </raise>
+                                <raise scope="narrow" event="set_association_name" target="association_name">
+                                    <parameter expr="association_name" />
+                                </raise>
+                            </transition>
+                        </state>
+                    </state>
+                    <state id="deleting_behaviour" initial="running">
+                        <state id="running">
+                            <transition event="delete_ball" target='.'>
+                                <parameter name="association_name" type="str"/>
+                                <raise scope="cd" event="delete_instance">
+                                    <parameter expr='association_name' />
+                                </raise>
+                            </transition>
+                        </state>
+                    </state>
+                    <state id="child_behaviour" initial="listening">
+                        <state id="listening">
+                            <transition event="button_pressed" target='.'>
+                                <parameter name="event_name" type="str" />
+                                <raise event="button_pressed" scope="narrow" target="'parent'">
+                                    <parameter expr='event_name' />
+                                </raise>
+                            </transition>
+                        </state>
+                    </state>
+                    <state id="deleting_balls_behaviour" initial="listening">
+                        <state id="listening">
+                            <transition port="field_ui" event="key_press" target="." cond="key == ui.KEYCODES.DELETE">
+                                <parameter name="key" />
+                                <raise event="delete_self" scope="narrow" target="'balls'" />
+                            </transition>
+                        </state>
+                    </state>
+                </parallel>
+                <state id="deleting">
+                    <transition target="../deleted">
+                        <raise event="delete_field" scope="narrow" target="'parent'">
+                            <parameter expr='self.association_name' />
+                        </raise>
+                    </transition>
+                </state>
+                <state id="deleted" />
+            </state>
+        </scxml>
+    </class>
 
-				MvKWidget.__init__(self, self.controller, self.c)
-				]]>
-			</body>
-		</method>
-		<method name="~Field">
-			<body>
-				self.destroy()
-			</body>
-		</method>
-		<scxml initial="root">
-			<state id="root" initial="waiting">
-				<state id="waiting">
-					<transition event="set_association_name" target="../initializing">
-						<parameter name="association_name" type="str" />
-						<script>
-							self.association_name = association_name
-						</script>
-					</transition>
-				</state>
-				<state id="initializing">
-					<transition target="../creating">
-						<raise scope="cd" event="create_instance">
-							<parameter expr='"buttons"' />
-							<parameter expr="self" />
-							<parameter expr="'create_new_field'" />
-							<parameter expr="'Spawn New Window'" />
-						</raise>
-					</transition>
-				</state>
-				<state id="creating">
-					<transition event='instance_created' target='../packing'>
-						<parameter name="association_name" type="string"/>
-						<raise scope="cd" event="start_instance">
-							<parameter expr="association_name" />
-						</raise>
-					</transition>
-				</state>
-				<state id="packing">
-					<transition event="button_created" target='../running'>
-						<parameter name="button" type="Button"/>
-						<script>
-							button.pack(expand=False, fill=tk.X, side=tk.TOP)
-							self.c.focus_force()
-							self.c.pack(expand=True, fill=tk.BOTH)
-						</script>
-					</transition>
-				</state>
-				<parallel id="running">
-					<transition	port="input" event="window-close" target="../deleting" cond='tagorid == id(self)'>
-						<parameter name="tagorid" type="int" default='None' />
-						<raise scope="narrow" target="'balls'" event='delete_self' />
-					</transition>
-					<state id="main_behaviour" initial="running">
-						<state id="running">
-							<transition port="input" event="right-click" target="../creating" cond='tagorid == id(self)'>
-								<parameter name='tagorid' type='int' default='None' />
-								<raise scope="cd" event="create_instance">
-									<parameter expr='"balls"' />
-									<parameter expr="self.c" />
-									<parameter expr="self.last_x" />
-									<parameter expr="self.last_y" />
-								</raise>
-							</transition>
-						</state>
-						<state id="creating">
-							<transition event="instance_created" target="../running">
-								<parameter name="association_name" type="string"/>
-								<raise scope="cd" event="start_instance">
-									<parameter expr="association_name" />
-								</raise>
-								<raise scope="narrow" event="set_association_name" target="association_name">
-									<parameter expr="association_name" />
-								</raise>
-							</transition>
-						</state>
-					</state>
-					<state id="deleting_behaviour" initial="running">
-						<state id="running">
-							<transition event="delete_ball" target='.'>
-								<parameter name="association_name" type="str"/>
-								<raise scope="cd" event="delete_instance">
-									<parameter expr='association_name' />
-								</raise>
-							</transition>
-						</state>
-					</state>
-					<state id="child_behaviour" initial="listening">
-						<state id="listening">
-							<transition event="button_pressed" target='.'>
-								<parameter name="event_name" type="str" />
-								<raise event="button_pressed" scope="narrow" target="'parent'">
-									<parameter expr='event_name' />
-								</raise>
-							</transition>
-						</state>
-					</state>
-				</parallel>
-				<state id="deleting">
-					<transition after="0.05" target="../deleted">
-						<raise event="delete_field" scope="narrow" target="'parent'">
-							<parameter expr='self.association_name' />
-						</raise>
-					</transition>
-				</state>
-				<state id="deleted" />
-			</state>
-		</scxml>
-	</class>
-	<class name="Button">
-		<relationships>
-			<association name="parent" class="Field" min="1" max="1" />
-			<inheritance class="RuntimeClassBase" priority='1'/>
-			<inheritance class="MvKWidget" priority='0'/>
-			<inheritance class="tk.Button" priority='-1'/>
-		</relationships>
-		<method name="Button">
-			<parameter name="parent" type="Field" />
-			<parameter name="event_name" type="str" />
-			<parameter name="button_text" type="str" />
-			<body>
-				tk.Button.__init__(self, parent, text=button_text)
-				MvKWidget.__init__(self, self.controller)
-				self.event_name = event_name
-			</body>
-		</method>
-		<scxml initial="initializing">
-			<state id="initializing">
-				<transition target="../running">
-					<raise event="button_created" scope="narrow" target="'parent'">
-						<parameter expr="self" />
-					</raise>
-				</transition>
-			</state>
-			<state id="running">
-				<transition port='input' event="left-click" target='.' cond='tagorid == id(self)'>
-					<parameter name='tagorid' type='int' default='None' />
-					<raise event="button_pressed" scope="narrow" target="'parent'">
-						<parameter expr="self.event_name" />
-					</raise>
-				</transition>
-			</state>
-		</scxml>
-	</class>
-	<class name="Ball">
-		<relationships>
-			<association name="parent" class="Field" min="1" max="1" />
-			<inheritance class="RuntimeClassBase" priority='1'/>
-			<inheritance class="MvKWidget" priority='0'/>
-		</relationships>
-		<attribute name="canvas" />
-		<method name="Ball">
-			<parameter name="canvas" />
-			<parameter name="x" />
-			<parameter name="y" />
-			<body>
-				self.canvas = canvas
-				self.r = 15.0
-				self.smooth = 0.4 # value between 0 and 1
-				self.vel = {'x': random.random() * 2.0 - 1.0, 'y': random.random() * 2.0 - 1.0}
-				self.id = self.canvas.create_oval(x, y, x + (self.r * 2), y + (self.r * 2), fill="black")
-				MvKWidget.__init__(self, self.controller, self.canvas, self.id)
-			</body>
-		</method>
-		<method name="~Ball">
-			<body>
-				self.canvas.delete(self.id)
-			</body>
-		</method>
-		<scxml initial="main_behaviour">
-			<state id="main_behaviour" initial="initializing">
-				<state id="initializing">
-					<transition event="set_association_name" target="../bouncing">
-						<parameter name="association_name" type="str" />
-						<script>
-							self.association_name = association_name
-						</script>
-					</transition>
-				</state>
-				<state id="bouncing">
-					<transition after="0.01" target=".">
-						<script>
-						<![CDATA[
-							pos = self.canvas.coords(self.id)
-							x = self.canvas.canvasx(pos[0])
-							y = self.canvas.canvasy(pos[1])
-							if x <= 0 or x + (self.r * 2) >= self.canvas.canvasx(self.canvas.winfo_width()):
-								self.vel['x'] = -self.vel['x']
-							if y <= 0 or y + (self.r * 2) >= self.canvas.canvasy(self.canvas.winfo_height()):
-								self.vel['y'] = -self.vel['y']
-							self.canvas.move(self.id, self.vel['x'], self.vel['y']);
-						]]>
-						</script>
-					</transition>
-					<transition port="input" event="left-click" target="../selected" cond='tagorid == id(self)'>
-						<parameter name='tagorid' type='int' default='None' />
-						<script>
-							self.canvas.itemconfig(self.id, fill="yellow")
-						</script>
-					</transition>
-				</state>
-				<state id="dragging">
-					<transition port="input" event="motion" target=".">
-						<parameter name='tagorid' type='int' default='None' />
-						<script>
-						<![CDATA[
-							coords = self.canvas.coords(self.id)
-							dx = self.canvas.canvasx(self.last_x) - self.canvas.canvasx(coords[0])
-							dy = self.canvas.canvasx(self.last_y) - self.canvas.canvasy(coords[1])
+    <class name="Button">
+        <relationships>
+            <association name="parent" class="Field" min="1" max="1" />
+        </relationships>
+        <inport name="button_ui"/>
+        <constructor>
+            <parameter name="parent" type="Field" />
+            <parameter name="event_name" type="str" />
+            <parameter name="button_text" type="str" />
+            <body>
+                <![CDATA[
+                self.event_name = event_name;
+                button = ui.append_button(parent.field_window, event_name);
+                ui.bind_event(button.element, ui.EVENTS.MOUSE_CLICK, self.controller, 'mouse_click', self.inports['button_ui']);
+                ]]>
+            </body>
+        </constructor>
+        <scxml initial="initializing">
+            <state id="initializing">
+                <transition target="../running">
+                    <raise event="button_created" scope="narrow" target="'parent'">
+                    </raise>
+                </transition>
+            </state>
+            <state id="running">
+                <transition port='button_ui' event="mouse_click" target='.' cond="button == ui.MOUSE_BUTTONS.LEFT">
+                    <parameter name="x" />
+                    <parameter name="y" />
+                    <parameter name="button" />
+                    <raise event="button_pressed" scope="narrow" target="'parent'">
+                        <parameter expr="self.event_name" />
+                    </raise>
+                </transition>
+            </state>
+        </scxml>
+    </class>
 
-							self.canvas.move(self.id, dx, dy);
+    <class name="Ball">
+        <atrribute name="element" />
+        <attribute name="canvas" />
+        <inport name="ball_ui" />
+        <relationships>
+            <association name="parent" class="Field" min="1" max="1" />
+        </relationships>
+        <constructor>
+            <parameter name="canvas" />
+            <parameter name="x" />
+            <parameter name="y" />
+            <body>
+                <![CDATA[
+                self.canvas = canvas;
+                self.r = 20.0;
+                self.vel = {'x': random.uniform(-5.0, 5.0), 'y': random.uniform(-5.0, 5.0)};
+                self.mouse_pos = {};
+                self.smooth = 0.4; # value between 0 and 1
 
-							# keep ball within boundaries
-							coords = self.canvas.coords(self.id)
-							x = self.canvas.canvasx(coords[0])
-							y = self.canvas.canvasy(coords[1])
-							if x - self.r <= 0:
-								x = 1;
-							elif x + self.r >= self.canvas.winfo_width():
-								x = self.canvas.winfo_width() - (2 * self.r) - 1
-							if y - self.r <= 0:
-								y = 1
-							elif y + self.r >= self.canvas.winfo_height():
-								y = self.canvas.winfo_height() - (2 * self.r) - 1;
-							self.canvas.coords(self.id, x, y, x + (self.r * 2), y + (self.r * 2));
-							self.vel = {
-								'x': (1 - self.smooth) * dx + self.smooth * self.vel['x'],
-								'y': (1 - self.smooth) * dy + self.smooth * self.vel['y']
-							}
-						]]>
-						</script>
-					</transition>
-					<transition port="input" event="left-release" target="../bouncing">
-						<parameter name='tagorid' type='int' default='None' />
-						<script>
-							self.canvas.itemconfig(self.id, fill="red")
-						</script>
-					</transition>
-				</state>
-				<state id='selected'>
-					<transition port="input" event="left-click" target="../dragging" cond='tagorid == id(self)'>
-						<parameter name='tagorid' type='int' default='None' />
-					</transition>
-					<transition port="input" event="delete" target=".">
-						<parameter name='tagorid' type='int' default='None' />
-						<raise event="delete_self" scope="local" />
-					</transition>
-				</state>
-				<transition event="delete_self" target='../deleted'>
-					<raise event="delete_ball" scope="narrow" target="'parent'">
-						<parameter expr='self.association_name' />
-					</raise>
-				</transition>
-			</state>
-			<state id='deleted' />
-		</scxml>
-	</class>
+                circle = self.canvas.add_circle(x, y, self.r, {'fill':'#000'});
+                ui.bind_event(circle, ui.EVENTS.MOUSE_PRESS, self.controller, 'mouse_press', self.inports["ball_ui"]);
+                ui.bind_event(circle, ui.EVENTS.MOUSE_MOVE, self.controller, 'mouse_move', self.inports['ball_ui']);
+                ui.bind_event(circle, ui.EVENTS.MOUSE_RELEASE, self.controller, 'mouse_release', self.inports['ball_ui']);
+                self.element = circle;
+                ]]>
+            </body>
+        </constructor>
+        <destructor>
+            <body>
+                <![CDATA[
+                self.canvas.remove_element(self.element);
+                ]]>
+            </body>
+        </destructor>
+        <scxml initial="main_behaviour">
+            <state id="main_behaviour" initial="initializing">
+                <state id="initializing">
+                    <transition event="set_association_name" target="../bouncing">
+                        <parameter name="association_name" type="str" />
+                        <script>
+                            <![CDATA[
+                            self.association_name = association_name
+                            ]]>
+                        </script>
+                    </transition>
+                </state>
+                <state id="bouncing">
+                    <transition after="(20 - self.getSimulatedTime() % 20) / 1000.0" target=".">
+                        <script>
+                            <![CDATA[
+                            pos = self.element.get_position();
+                            if pos.x-self.r <= 0 or pos.x+self.r >= self.canvas.get_width():
+                                self.vel['x'] = -self.vel['x'];
+                            if pos.y-self.r <= 0 or pos.y+self.r >= self.canvas.get_height():
+                                self.vel['y'] = -self.vel['y'];
+                            self.element.move(self.vel['x'], self.vel['y']);
+                            ]]>
+                        </script>
+                    </transition>
+                    <transition port="ball_ui" event="mouse_press" target="../selected" cond="button == ui.MOUSE_BUTTONS.LEFT">
+                        <parameter name="x" />
+                        <parameter name="y" />
+                        <parameter name="button" />
+                        <script>
+                            <![CDATA[
+                            self.element.set_color("#ff0");
+                            ]]>
+                        </script>
+                    </transition>
+                </state>
+                <state id="dragging">
+                    <transition port="ball_ui" event="mouse_move" target=".">
+                        <parameter name="x" />
+                        <parameter name="y" />
+                        <parameter name="button" />
+                        <script>
+                            <![CDATA[
+                            dx = x - self.mouse_pos['x'];
+                            dy = y - self.mouse_pos['y'];
+
+                            self.element.move(dx, dy);
+
+                            # keep ball within boundaries
+                            pos = self.element.get_position();
+                            if pos.x-self.r <= 0 :
+                                pos.x = self.r + 1;
+                            elif pos.x+self.r >= self.canvas.width :
+                                pos.x = self.canvas.width-self.r-1;
+                            if pos.y-self.r <= 0 :
+                                pos.y = self.r + 1;
+                            elif pos.y+self.r >= self.canvas.height :
+                                pos.y = self.canvas.height-self.r-1;
+                            self.element.set_position(pos.x, pos.y);
+                            self.mouse_pos = {'x':x, 'y':y};
+                            self.vel = {
+                                'x': (1-self.smooth)*dx + self.smooth*self.vel['x'],
+                                'y': (1-self.smooth)*dy + self.smooth*self.vel['y']
+                            };
+                            ]]>
+                        </script>
+                    </transition>
+                    <transition port="ball_ui" event="mouse_release" target="../bouncing">
+                        <parameter name="x" />
+                        <parameter name="y" />
+                        <script>
+                            <![CDATA[
+                            self.element.set_color("#f00");
+                            ]]>
+                        </script>
+                    </transition>
+                </state>
+                <state id='selected'>
+                    <transition port="ball_ui" event="mouse_press" target="../dragging" cond="button == ui.MOUSE_BUTTONS.LEFT">
+                        <parameter name="x" />
+                        <parameter name="y" />
+                        <parameter name="button" />
+                        <script>
+                            <![CDATA[
+                            self.mouse_pos = {'x':x, 'y':y};
+                            ]]>
+                        </script>
+                    </transition>
+                    <transition event="delete_self" target='../../deleted'>
+                        <raise event="delete_ball" scope="narrow" target="'parent'">
+                            <parameter expr='self.association_name' />
+                        </raise>
+                    </transition>
+                </state>
+            </state>
+            <state id='deleted' />
+        </scxml>
+    </class>
 </diagram>

+ 0 - 0
sccd/__init__.py


+ 854 - 0
sccd/compiler/DEVS_generator.py

@@ -0,0 +1,854 @@
+# DEVS Generator by Sam Pieters. Inspired on the generatic generator by Joeri Excelmans
+#
+# Visits SCCD-domain constructs (see sccd_constructs.py) and converts them
+# to a DEVS language AST (see generic_language_constructs.py), that can
+# then be visited by a target language writer (only supports python).
+
+from sccd.compiler.utils import Enum, Logger
+from sccd.compiler.visitor import Visitor
+from sccd.compiler.sccd_constructs import FormalParameter
+from sccd.compiler.stateful_writer import StatefulWriter
+import sccd.compiler.generic_language_constructs as GLC
+
+Platforms = Enum("Threads", "GameLoop", "EventLoop")
+
+
+class DEVSGenerator(Visitor):
+
+    def __init__(self, platform):
+        self.platform = platform
+        self.writer = StatefulWriter()
+
+    def generic_visit(self, node):
+        Logger.showWarning("GenericGenerator has no visit method for node of type '" + str(type(node)) + "'.")
+
+    def get(self):
+        return self.writer.get()
+
+    def visit_ClassDiagram(self, class_diagram):
+        header = ("Generated by Statechart compiler by Glenn De Jonghe, Joeri Exelmans, Simon Van Mierlo, "
+                  "and Yentl Van Tendeloo (for the inspiration) and Sam Pieters (DEVS)\n")
+        if class_diagram.name or class_diagram.author or class_diagram.description:
+            header += "\n"
+        if class_diagram.author:
+            header += "Model author: " + class_diagram.author + "\n"
+        if class_diagram.name:
+            header += "Model name:   " + class_diagram.name + "\n"
+        if class_diagram.description.strip():
+            header += "Model description:\n"
+            header += class_diagram.description.strip()
+
+        self.writer.addMultiLineComment(header)
+        self.writer.addVSpace()
+        self.writer.addInclude(([GLC.RuntimeModuleIdentifier(), "DEVS_statecharts_core"]))
+
+        # TODO: how to add just one element and not everything ('*')
+        self.writer.addInclude((["pypdevs", "DEVS"]))
+        self.writer.addInclude((["pypdevs", "infinity"]))
+        self.writer.addInclude((["pypdevs", "simulator"]))
+
+        if class_diagram.top.strip():
+            self.writer.addRawCode(class_diagram.top)
+        self.writer.addVSpace()
+
+        self.writer.beginPackage(class_diagram.name)
+
+        # visit children
+        for c in class_diagram.classes:
+            c.accept(self)
+
+        self.writer.beginClass("ObjectManagerState")
+        self.writer.beginConstructor()
+        self.writer.beginMethodBody()
+
+        self.writer.addAssignment(GLC.SelfProperty("to_send"),
+                                  f"[(\"{class_diagram.default_class.name}\", 0, \"Initialize\")]")
+        self.writer.endMethodBody()
+        self.writer.endConstructor()
+        self.writer.endClass()
+
+        self.writer.beginClass("ObjectManager", ["AtomicDEVS"])
+        self.writer.beginConstructor()
+        self.writer.addFormalParameter("name")
+        self.writer.beginMethodBody()
+        self.writer.beginSuperClassConstructorCall("AtomicDEVS")
+        self.writer.addActualParameter("name")
+        self.writer.endSuperClassConstructorCall()
+
+        self.writer.addAssignment(GLC.SelfProperty("State"), GLC.FunctionCall("ObjectManagerState"))
+        self.writer.addAssignment(GLC.SelfProperty("input"),
+                                  GLC.FunctionCall(GLC.SelfProperty("addInPort"), [GLC.String("input")]))
+
+        self.writer.addAssignment(GLC.SelfProperty("output"), "{}")
+        for class_name in class_diagram.class_names:
+            self.writer.addAssignment(GLC.SelfProperty(f"output[\"{class_name}\"]"),
+                                      GLC.FunctionCall(GLC.SelfProperty("addOutPort")))
+
+        self.writer.endMethodBody()
+        self.writer.endConstructor()
+
+        # TODO: Added the default methods for an Atomic DEVS model, need to write implementation
+        self.writer.beginMethod("extTransition")
+        self.writer.addFormalParameter("inputs")
+        self.writer.beginMethodBody()
+        self.writer.add(GLC.ReturnStatement(GLC.SelfProperty("state")))
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+        self.writer.beginMethod("intTransition")
+        self.writer.beginMethodBody()
+        self.writer.addAssignment(GLC.SelfProperty("State.to_send"), GLC.ArrayExpression())
+
+        self.writer.add(GLC.ReturnStatement(GLC.SelfProperty("State")))
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+        self.writer.beginMethod("outputFnc")
+        self.writer.beginMethodBody()
+        self.writer.addAssignment("out_dict", "{}")
+
+        self.writer.beginForLoopIterateArray(GLC.SelfProperty("State.to_send"), "(target, message)")
+        self.writer.addAssignment(f"out_dict[self.output[target]]", "[message]")
+        self.writer.endForLoopIterateArray()
+
+        self.writer.add(GLC.ReturnStatement("out_dict"))
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+        self.writer.beginMethod("timeAdvance")
+        self.writer.beginMethodBody()
+        self.writer.beginIf((GLC.SelfProperty("State.to_send")))
+        self.writer.add(GLC.ReturnStatement("0"))
+        self.writer.endIf()
+        self.writer.add(GLC.ReturnStatement("INFINITY"))
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+        self.writer.endClass()
+        # End ObjectManager Class
+
+        # Begin Controller Class
+        self.writer.beginClass("Controller", ["CoupledDEVS"])
+        self.writer.beginConstructor()
+        self.writer.addFormalParameter("name")
+        for p in class_diagram.default_class.constructors[0].parameters:
+            p.accept(self)
+
+        self.writer.beginMethodBody()
+        self.writer.beginSuperClassConstructorCall("CoupledDEVS")
+        self.writer.addActualParameter("name")
+        self.writer.endSuperClassConstructorCall()
+
+        for i in class_diagram.inports:
+            self.writer.addAssignment(GLC.SelfProperty(i),
+                                      GLC.FunctionCall(GLC.SelfProperty("addInPort"), [GLC.String(i)]))
+        for o in class_diagram.outports:
+            self.writer.add(GLC.FunctionCall(GLC.SelfProperty("addOutPort"), [GLC.String(o)]))
+
+        # Add AtomicDEVS models
+        self.writer.addAssignment(GLC.SelfProperty("objectmanager"), (GLC.FunctionCall(GLC.SelfProperty("addSubModel"),
+                                                                                       [GLC.FunctionCall(
+                                                                                           "ObjectManager",
+                                                                                           GLC.ActualParameters([
+                                                                                               GLC.String(
+                                                                                                   "ObjectManager")]))])))
+        for (i, class_name) in enumerate(class_diagram.class_names):
+            self.writer.addAssignment(GLC.SelfProperty(f"atomic{i}"),
+                                      (GLC.FunctionCall(GLC.SelfProperty("addSubModel"), [GLC.FunctionCall(class_name,
+                                                                                                           GLC.ActualParameters(
+                                                                                                               [
+                                                                                                                   GLC.String(
+                                                                                                                       class_name)]))])))
+
+        # Add links between the models
+        for (i, the_class) in enumerate(class_diagram.classes):
+            self.writer.add((GLC.FunctionCall(GLC.SelfProperty("connectPorts"),
+                                              [GLC.SelfProperty(f"atomic{i}.obj_manager_out"),
+                                               GLC.SelfProperty(f"objectmanager.input")])))
+            self.writer.add((GLC.FunctionCall(GLC.SelfProperty("connectPorts"),
+                                              [GLC.SelfProperty(f"objectmanager.output[\"{the_class.name}\"]"),
+                                               GLC.SelfProperty(f"atomic{i}.obj_manager_in")])))
+
+            for association in the_class.associations:
+                temp = class_diagram.class_names.index(association.to_class)
+                self.writer.add(GLC.FunctionCall(GLC.SelfProperty("connectPorts"),
+                                                 [GLC.SelfProperty(f"atomic{i}.outputs[\"{association.name}\"]"),
+                                                  GLC.SelfProperty(f"atomic{temp}.input")]))
+
+        # TODO: What to do with "actual_parameters"?
+        # actual_parameters = [p.getIdent() for p in class_diagram.default_class.constructors[0].parameters]
+        # self.writer.add(GLC.FunctionCall(GLC.Property(GLC.SelfProperty("object_manager"), "createInstance"), [GLC.String(class_diagram.default_class.name), GLC.ArrayExpression(actual_parameters)]))
+        self.writer.endMethodBody()
+        self.writer.endConstructor()
+        self.writer.endClass()
+        # End Controller Class
+
+        # visit test node if there is one
+        if class_diagram.test:
+            class_diagram.test.accept(self)
+
+        self.writer.endPackage()
+
+    # CLASS
+    def visit_Class(self, class_node):
+        """
+        Generate code for Class construct
+        """
+        super_classes = []
+        if not class_node.super_class_objs:
+            if class_node.statechart:
+                super_classes.append("AtomicDEVS")
+        if class_node.super_classes:
+            for super_class in class_node.super_classes:
+                super_classes.append(super_class)
+
+        self.writer.beginClass(f"{class_node.name}Instance", ["RuntimeClassBase"])
+        self.writer.beginConstructor()
+        self.writer.beginMethodBody()
+
+        self.writer.beginSuperClassConstructorCall("RuntimeClassBase")
+        self.writer.endSuperClassConstructorCall()
+
+        self.writer.addAssignment(GLC.SelfProperty("associations"), "{}")
+        for association in class_node.associations:
+            self.writer.addAssignment(
+                GLC.SelfProperty(f"associations[\"{association.name}\"]"),
+                GLC.FunctionCall("Association",
+                                 [GLC.String(association.to_class), f"{association.min}", f"{association.max}"]))
+        self.writer.endMethodBody()
+        self.writer.endConstructor()
+
+        # TODO: costructor and destructor
+
+        # visit methods
+        for i in class_node.methods:
+            i.accept(self)
+
+        # compile and initialize Statechart
+        if class_node.statechart:
+            class_node.statechart.accept(self)
+
+            self.writer.beginMethod("initializeStatechart")
+            self.writer.beginMethodBody()
+            self.writer.addComment("enter default state")
+
+            # get effective target of initial transition
+            self.writer.addAssignment(
+                GLC.SelfProperty("default_targets"),
+                GLC.FunctionCall(
+                    GLC.Property(
+                        GLC.MapIndexedExpression(
+                            GLC.SelfProperty("states"),
+                            GLC.String(class_node.statechart.root.initial)
+                        ),
+                        "getEffectiveTargetStates"
+                    )
+                )
+            )
+
+            self.writer.add(GLC.SuperClassMethodCall(
+                "RuntimeClassBase",
+                "initializeStatechart",
+                []
+            ))
+
+            self.writer.endMethodBody()
+            self.writer.endMethod()
+
+        self.writer.endClass()
+
+        self.writer.beginClass(class_node.name, super_classes)
+
+        # TODO: Added the default methods for an Atomic DEVS model, need to write implementation
+        self.writer.beginMethod("extTransition")
+        self.writer.addFormalParameter("inputs")
+        self.writer.beginMethodBody()
+
+        self.writer.addAssignment("all_inputs", "inputs[self.obj_manager_in]")
+
+        self.writer.beginForLoopIterateArray("all_inputs", "input")
+        self.writer.beginIf(GLC.EqualsExpression("input", GLC.String("Initialize")))
+
+        self.writer.add(GLC.FunctionCall("self.State.append", [GLC.FunctionCall(f"{class_node.name}Instance")]))
+        self.writer.endIf()
+        self.writer.beginElseIf(GLC.EqualsExpression("input", GLC.String("Start")))
+        self.writer.endElseIf()
+        self.writer.beginElseIf(GLC.EqualsExpression("input", GLC.String("Delete")))
+        self.writer.endElseIf()
+        self.writer.beginElseIf(GLC.EqualsExpression("input", GLC.String("Associate")))
+        self.writer.endElseIf()
+        self.writer.beginElseIf(GLC.EqualsExpression("input", GLC.String("Disassociate")))
+        self.writer.endElseIf()
+        self.writer.endForLoopIterateArray()
+
+        self.writer.add(GLC.ReturnStatement(GLC.SelfProperty("state")))
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+        self.writer.beginMethod("intTransition")
+        self.writer.beginMethodBody()
+        self.writer.add(GLC.ReturnStatement(GLC.SelfProperty("state")))
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+        self.writer.beginMethod("outputFnc")
+        self.writer.beginMethodBody()
+        self.writer.add(GLC.ReturnStatement("{}"))
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+        self.writer.beginMethod("timeAdvance")
+        self.writer.beginMethodBody()
+        self.writer.add(GLC.ReturnStatement("INFINITY"))
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+        # visit constructor
+        class_node.constructors[0].accept(self)
+
+        self.writer.endClass()
+
+    # CLASS -- CONSTRUCTOR
+    def visit_Constructor(self, constructor):
+        self.writer.beginConstructor()
+        self.writer.addFormalParameter("name")
+
+        self.writer.beginMethodBody()  # constructor body
+
+        self.writer.beginSuperClassConstructorCall("AtomicDEVS")
+        self.writer.addActualParameter("name")
+        self.writer.endSuperClassConstructorCall()
+
+        self.writer.addAssignment(GLC.SelfProperty("State"), "[]")
+
+        self.writer.addAssignment(GLC.SelfProperty("obj_manager_in"),
+                                  GLC.FunctionCall(GLC.SelfProperty("addInPort"), [GLC.String("obj_manager_in")]))
+        self.writer.addAssignment(GLC.SelfProperty("obj_manager_out"),
+                                  GLC.FunctionCall(GLC.SelfProperty("addOutPort"), [GLC.String("obj_manager_out")]))
+
+        self.writer.addAssignment(GLC.SelfProperty("input"),
+                                  GLC.FunctionCall(GLC.SelfProperty("addInPort"), [GLC.String("input")]))
+
+        self.writer.addAssignment(GLC.SelfProperty("outputs"), "{}")
+        for association in constructor.parent_class.associations:
+            self.writer.addAssignment(GLC.SelfProperty(f"outputs[\"{association.name}\"]"),
+                                      GLC.FunctionCall(GLC.SelfProperty("addOutPort"), [GLC.String(association.name)]))
+
+        for p in constructor.parent_class.inports:
+            self.writer.addAssignment(GLC.SelfProperty(p),
+                                      GLC.FunctionCall(GLC.SelfProperty("addInPort"), [GLC.String(p)]))
+
+        for p in constructor.parent_class.outports:
+            self.writer.addAssignment(
+                GLC.MapIndexedExpression(GLC.SelfProperty("outports"), GLC.String(p)),
+                GLC.FunctionCall(GLC.Property("controller", "addOutputPort"), [GLC.String(p), GLC.SelfExpression()]))
+
+        self.writer.endMethodBody()
+        self.writer.endConstructor()
+
+    def visit_FormalParameter(self, formal_parameter):
+        self.writer.addFormalParameter(formal_parameter.getIdent(), formal_parameter.getDefault())
+
+    ### CLASS -- DESTRUCTOR
+    def visit_Destructor(self, destructor):
+        self.writer.beginMethod("user_defined_destructor")
+        self.writer.beginMethodBody()
+        if destructor.body.strip():
+            self.writer.addRawCode(destructor.body)
+        if destructor.parent_class.super_classes:
+            self.writer.addComment("call super class destructors")
+            for super_class in destructor.parent_class.super_classes:
+                # begin call
+                if super_class in destructor.parent_class.super_class_objs:
+                    self.writer.beginSuperClassMethodCall(super_class, "user_defined_destructor")
+                    self.writer.endSuperClassMethodCall()
+                else:
+                    self.writer.beginSuperClassDestructorCall(super_class)
+                    self.writer.endSuperClassDestructorCall()
+                    pass
+
+                # self.writer.beginSuperClassMethodCall(super_class, "user_defined_destructor")
+                # self.writer.endSuperClassMethodCall()
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+    # CLASS -- METHOD
+    def visit_Method(self, method):
+        self.writer.addVSpace()
+        self.writer.beginMethod(method.name, "user defined method")
+        for p in method.parameters:
+            p.accept(self)
+        self.writer.beginMethodBody()
+        self.writer.addRawCode(method.body)
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+    # CLASS -- ASSOCIATION
+    def visit_Association(self, association):
+        self.writer.addAssignment(
+            GLC.MapIndexedExpression(
+                GLC.Property("instance", "associations"),
+                GLC.String(association.name)),
+            GLC.NewExpression("Association",
+                              [GLC.String(association.to_class), str(association.min), str(association.max)]))
+
+    # CLASS -- STATECHART
+    def visit_StateChart(self, statechart):
+        self.writer.addVSpace()
+        self.writer.beginMethod("build_statechart_structure", "builds Statechart structure")
+        self.writer.beginMethodBody()
+
+        def writeState(s, i):
+            self.writer.addVSpace()
+            self.writer.addComment("state %s" % ("<root>" if s.is_root else s.new_full_name))
+            index_expr = GLC.MapIndexedExpression(GLC.SelfProperty("states"), GLC.String(s.new_full_name))
+            clazz = "State"
+            if s.is_parallel_state:
+                clazz = "ParallelState"
+            elif s.is_history:
+                if s.is_history_deep:
+                    clazz = "DeepHistoryState"
+                else:
+                    clazz = "ShallowHistoryState"
+            self.writer.addAssignment(
+                index_expr,
+                GLC.NewExpression(clazz, [str(i), GLC.String(s.new_full_name), GLC.SelfExpression()])
+            )
+            if not s.is_root:
+                if s.enter_action.action or s.has_timers:
+                    self.writer.add(
+                        GLC.FunctionCall(
+                            GLC.Property(
+                                index_expr,
+                                "setEnter"
+                            ),
+                            [GLC.SelfProperty(s.friendly_name + "_enter")]
+                        )
+                    )
+                if s.exit_action.action or s.has_timers:
+                    self.writer.add(
+                        GLC.FunctionCall(
+                            GLC.Property(
+                                index_expr,
+                                "setExit"
+                            ),
+                            [GLC.SelfProperty(s.friendly_name + "_exit")]
+                        )
+                    )
+
+        # write all states
+        for (i, s) in enumerate(statechart.states):
+            writeState(s, i)
+
+        # add children to composite states
+        self.writer.addVSpace()
+        self.writer.addComment("add children")
+        for (i, s) in enumerate(statechart.composites):
+            for c in s.children:
+                self.writer.add(
+                    GLC.FunctionCall(
+                        GLC.Property(
+                            GLC.MapIndexedExpression(
+                                GLC.SelfProperty("states"),
+                                GLC.String(s.new_full_name)
+                            ),
+                            "addChild"),
+                        [GLC.MapIndexedExpression(GLC.SelfProperty("states"), GLC.String(c.new_full_name))]
+                    )
+                )
+
+        # fix tree at root, such that 'descendants' and 'ancestors' fields are filled in
+        self.writer.add(
+            GLC.FunctionCall(
+                GLC.Property(
+                    GLC.MapIndexedExpression(
+                        GLC.SelfProperty("states"),
+                        GLC.String("")
+                    ),
+                    "fixTree"
+                )
+            )
+        )
+
+        # defaults
+        for (i, s) in enumerate(statechart.composites):
+            if not s.is_parallel_state:
+                self.writer.addAssignment(
+                    GLC.Property(
+                        GLC.MapIndexedExpression(
+                            GLC.SelfProperty("states"),
+                            GLC.String(s.new_full_name)
+                        ),
+                        "default_state"
+                    ),
+                    GLC.MapIndexedExpression(
+                        GLC.SelfProperty("states"),
+                        GLC.String(s.initial)
+                    )
+                )
+
+        # transitions
+        for s in statechart.basics + statechart.composites:
+            if s.transitions:
+                self.writer.addVSpace()
+                self.writer.addComment("transition %s" % s.new_full_name)
+            for (i, t) in enumerate(s.transitions + s.else_transitions):
+                # instantiate new Transition instance
+                self.writer.addAssignment(
+                    GLC.LocalVariableDeclaration(
+                        "%s_%i" % (s.friendly_name, i)
+                    ),
+                    GLC.NewExpression(
+                        "Transition",
+                        [
+                            GLC.SelfExpression(),
+                            GLC.MapIndexedExpression(
+                                GLC.SelfProperty("states"),
+                                GLC.String(s.new_full_name),
+                            ),
+                            GLC.ArrayExpression(
+                                [
+                                    GLC.MapIndexedExpression(
+                                        GLC.SelfProperty("states"),
+                                        GLC.String(target_node.new_full_name)
+                                    )
+                                    for target_node in t.target.target_nodes
+                                ]
+                            )
+                        ]
+                    )
+                )
+                # if any action associated with transition: set executable_content to correct function (generated later)
+                if t.action.sub_actions:
+                    self.writer.add(
+                        GLC.FunctionCall(
+                            GLC.Property(
+                                "%s_%i" % (s.friendly_name, i),
+                                "setAction"
+                            ),
+                            [GLC.SelfProperty("%s_%i_exec" % (s.friendly_name, i))]
+                        )
+                    )
+                # if any trigger associated with transition: instantiate correct Event instance
+                trigger = None
+                if t.trigger.is_after:
+                    trigger = GLC.NewExpression("Event", [GLC.String("_%iafter" % (t.trigger.getAfterIndex()))])
+                elif t.trigger.event:
+                    trigger = GLC.NewExpression("Event",
+                                                [
+                                                    GLC.String(t.trigger.event),
+                                                    GLC.NoneExpression() if t.trigger.port is None else GLC.FunctionCall(
+                                                        GLC.SelfProperty("getInPortName"),
+                                                        [GLC.String(t.trigger.port)]
+                                                    )
+                                                ]
+                                                )
+                else:
+                    trigger = GLC.NoneExpression()
+                if trigger:
+                    self.writer.add(
+                        GLC.FunctionCall(
+                            GLC.Property(
+                                "%s_%i" % (s.friendly_name, i),
+                                "setTrigger"
+                            ),
+                            [trigger]
+                        )
+                    )
+                # if any guard associated with transition: set guard to correct function (generated later)
+                if t.guard:
+                    self.writer.add(
+                        GLC.FunctionCall(
+                            GLC.Property(
+                                "%s_%i" % (s.friendly_name, i),
+                                "setGuard"
+                            ),
+                            [GLC.SelfProperty("%s_%i_guard" % (s.friendly_name, i))]
+                        )
+                    )
+                self.writer.add(
+                    GLC.FunctionCall(
+                        GLC.Property(
+                            GLC.MapIndexedExpression(
+                                GLC.SelfProperty("states"),
+                                GLC.String(s.new_full_name)
+                            ),
+                            "addTransition"
+                        ),
+                        ["%s_%i" % (s.friendly_name, i)]
+                    )
+                )
+
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+        # enter/exit actions
+        for (i, s) in enumerate(statechart.composites + statechart.basics):
+            if not s.is_root:
+                if s.enter_action.action or s.has_timers:
+                    s.enter_action.accept(self)
+                if s.exit_action.action or s.has_timers:
+                    s.exit_action.accept(self)
+
+        # transition actions and guards
+        for s in statechart.composites + statechart.basics:
+            for (i, t) in enumerate(s.transitions):
+                if t.action.sub_actions:
+                    self.writeTransitionAction(t, i)
+                if t.hasGuard():
+                    self.writeTransitionGuard(t, i)
+
+    def visit_FormalEventParameter(self, formal_event_parameter):
+        self.writer.add(formal_event_parameter.name)
+
+    def writeFormalEventParameters(self, transition):
+        parameters = transition.getTrigger().getParameters()
+        if (len(parameters) > 0):
+            for index, parameter in enumerate(parameters):
+                self.writer.startRecordingExpression()
+                parameter.accept(self)
+                parameter_expr = self.writer.stopRecordingExpression()
+                self.writer.addAssignment(
+                    GLC.LocalVariableDeclaration(parameter_expr),
+                    GLC.ArrayIndexedExpression("parameters", str(index)))
+
+    def writeTransitionAction(self, transition, index):
+        self.writer.beginMethod("%s_%i_exec" % (transition.parent_node.friendly_name, index))
+
+        # parameters, start method
+        self.writer.addFormalParameter("parameters")
+        self.writer.beginMethodBody()
+
+        # handle parameters to actually use them
+        self.writeFormalEventParameters(transition)
+
+        # write action
+        transition.getAction().accept(self)
+
+        # end method
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+    def writeTransitionGuard(self, transition, index):
+        self.writer.beginMethod("%s_%i_guard" % (transition.parent_node.friendly_name, index))
+
+        # parameters, start method
+        self.writer.addFormalParameter("parameters")
+        self.writer.beginMethodBody()
+
+        # handle parameters to actually use them
+        self.writeFormalEventParameters(transition)
+
+        # get guard condition
+        self.writer.startRecordingExpression()
+        transition.getGuard().accept(self)  # --> visit_Expression
+        expr = self.writer.stopRecordingExpression()
+
+        # return statement, end method
+        self.writer.add(GLC.ReturnStatement(expr))
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+    def visit_EnterAction(self, enter_method):
+        parent_node = enter_method.parent_node
+        self.writer.beginMethod(parent_node.friendly_name + "_enter")
+        self.writer.beginMethodBody()
+
+        if enter_method.action:
+            enter_method.action.accept(self)
+
+        # take care of any AFTER events
+        for transition in parent_node.transitions:
+            trigger = transition.getTrigger()
+            if trigger.isAfter():
+                self.writer.startRecordingExpression()
+                trigger.after.accept(self)
+                after = self.writer.stopRecordingExpression()
+                self.writer.add(GLC.FunctionCall(GLC.SelfProperty("addTimer"), [str(trigger.getAfterIndex()), after]))
+
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+    def visit_ExitAction(self, exit_method):
+        parent_node = exit_method.parent_node
+        self.writer.beginMethod(parent_node.friendly_name + "_exit")
+        self.writer.beginMethodBody()
+
+        # take care of any AFTER events
+        for transition in parent_node.transitions:
+            trigger = transition.getTrigger()
+            if trigger.isAfter():
+                self.writer.add(GLC.FunctionCall(GLC.SelfProperty("removeTimer"), [str(trigger.getAfterIndex())]))
+
+        # execute user-defined exit action if present
+        if exit_method.action:
+            exit_method.action.accept(self)
+
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+        # helper method
+
+    def writeEnterHistory(self, entered_node, is_deep):
+        ### OLD CODE (TODO)
+        self.writer.beginMethod("enterHistory" + ("Deep" if is_deep else "Shallow") + "_" + entered_node.full_name)
+        self.writer.beginMethodBody()
+
+        self.writer.beginIf(GLC.EqualsExpression(
+            GLC.ArrayLength(
+                GLC.MapIndexedExpression(
+                    GLC.SelfProperty("history_state"),
+                    GLC.SelfProperty(entered_node.full_name))),
+            "0"))
+        defaults = entered_node.defaults
+
+        for node in defaults:
+            if node.is_basic:
+                self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enter_" + node.full_name)))
+            elif node.is_composite:
+                self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enterDefault_" + node.full_name)))
+
+        self.writer.endIf()
+        self.writer.beginElse()
+        children = entered_node.children
+        if entered_node.is_parallel_state:
+            for child in children:
+                if not child.is_history:
+                    self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enter_" + child.full_name)))
+                    self.writer.add(GLC.FunctionCall(
+                        GLC.SelfProperty("enterHistory" + ("Deep" if is_deep else "Shallow") + "_" + child.full_name)))
+        else:
+            for child in children:
+                if not child.is_history:
+                    self.writer.beginIf(GLC.ArrayContains(
+                        GLC.MapIndexedExpression(
+                            GLC.SelfProperty("history_state"),
+                            GLC.SelfProperty(entered_node.full_name)),
+                        GLC.SelfProperty(child.full_name)))
+                    if child.is_composite:
+                        if is_deep:
+                            self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enter_" + child.full_name)))
+                            self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enterHistoryDeep_" + child.full_name)))
+                        else:
+                            self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enterDefault_" + child.full_name)))
+                    else:
+                        self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enter_" + child.full_name)))
+                    self.writer.endIf()
+        self.writer.endElse()
+
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+    def visit_SelfReference(self, self_reference):
+        self.writer.add(GLC.SelfExpression())
+
+    def visit_StateReference(self, state_ref):
+        self.writer.beginArray()
+        for node in state_ref.getNodes():
+            self.writer.add(GLC.SelfProperty(node.full_name))
+        self.writer.endArray()
+
+    def visit_InStateCall(self, in_state_call):
+        self.writer.add(
+            GLC.FunctionCall(
+                GLC.SelfProperty("inState"),
+                [
+                    GLC.ArrayExpression(
+                        [GLC.String(target_node.new_full_name) for target in in_state_call.targets for target_node in
+                         target.target_nodes]
+                    )
+                ]
+            )
+        )
+
+    def visit_ElseGuard(self, else_guard):
+        self.writer.add(
+            GLC.String("ELSE_GUARD")
+        )
+
+    def visit_Expression(self, expression):
+        self.writer.startRecordingExpression()
+        self.writer.beginGlue()
+        for part in expression.expression_parts:
+            part.accept(self)
+        self.writer.endGlue()
+        expr = self.writer.stopRecordingExpression()
+        self.writer.add(expr)
+
+    def visit_ExpressionPartString(self, e):
+        self.writer.add(e.string)
+
+    def visit_RaiseEvent(self, raise_event):
+        self.writer.startRecordingExpression()
+        self.writer.begin(GLC.NewExpression("Event"))
+
+        self.writer.addActualParameter(GLC.String(raise_event.getEventName()))
+        if raise_event.isOutput():
+            self.writer.addActualParameter(
+                GLC.FunctionCall(
+                    GLC.SelfProperty("getOutPortName"),
+                    [GLC.String(raise_event.getPort())]
+                )
+            )
+        else:
+            self.writer.addActualParameter(GLC.NoneExpression())
+
+        self.writer.end()
+        new_event_expr = self.writer.stopRecordingExpression()
+
+        self.writer.startRecordingExpression()
+        self.writer.beginArray()
+        if raise_event.isCD():
+            self.writer.add(GLC.SelfExpression())
+        for param in raise_event.getParameters():
+            param.accept(self)  # -> visit_Expression will cause expressions to be added to array
+        self.writer.endArray()
+        parameters_array_expr = self.writer.stopRecordingExpression()
+        new_event_expr.getActualParameters().add(parameters_array_expr)
+
+        if raise_event.isNarrow():
+            self.writer.add(GLC.FunctionCall(
+                GLC.Property(GLC.SelfProperty("big_step"), "outputEventOM"), [
+                    GLC.NewExpression("Event", [
+                        GLC.String("narrow_cast"),
+                        GLC.NoneExpression(),
+                        GLC.ArrayExpression([
+                            GLC.SelfExpression(),
+                            raise_event.getTarget(),
+                            new_event_expr])])]))
+        elif raise_event.isLocal():
+            self.writer.add(GLC.FunctionCall(
+                GLC.SelfProperty("raiseInternalEvent"),
+                [new_event_expr]))
+        elif raise_event.isOutput():
+            self.writer.add(GLC.FunctionCall(
+                GLC.Property(GLC.SelfProperty("big_step"), "outputEvent"),
+                [new_event_expr]))
+        elif raise_event.isCD():
+            self.writer.add(GLC.FunctionCall(
+                GLC.Property(GLC.SelfProperty("big_step"), "outputEventOM"),
+                [new_event_expr]))
+        elif raise_event.isBroad():
+            self.writer.add(GLC.FunctionCall(
+                GLC.Property(GLC.SelfProperty("big_step"), "outputEventOM"),
+                [GLC.NewExpression("Event", [
+                    GLC.String("broad_cast"),
+                    GLC.NoneExpression(),
+                    GLC.ArrayExpression([
+                        GLC.SelfExpression(),
+                        new_event_expr])])]))
+
+    def visit_Script(self, script):
+        self.writer.addRawCode(script.code)
+
+    def visit_Log(self, log):
+        self.writer.add(GLC.LogStatement(log.message))
+
+    def visit_Assign(self, assign):
+        self.writer.startRecordingExpression()
+        assign.lvalue.accept(self)  # --> visit_Expression
+        lvalue = self.writer.stopRecordingExpression()
+        self.writer.startRecordingExpression()
+        assign.expression.accept(self)  # --> visit_Expression
+        rvalue = self.writer.stopRecordingExpression()
+        self.writer.addAssignment(lvalue, rvalue)

+ 0 - 0
sccd/compiler/__init__.py


+ 14 - 0
sccd/compiler/compiler_exceptions.py

@@ -0,0 +1,14 @@
+class CompilerException(Exception):
+	def __init__(self, message):
+		self.message = message
+	def __str__(self):
+		return repr(self.message)
+	
+class TransitionException(CompilerException):
+	pass
+
+class UnprocessedException(CompilerException):
+	pass
+
+class CodeBlockException(CompilerException):
+	pass

+ 881 - 0
sccd/compiler/generic_generator.py

@@ -0,0 +1,881 @@
+# Generic Generator by Joeri Exelmans
+#
+# Visits SCCD-domain constructs (see sccd_constructs.py) and converts them
+# to a generic language AST (see generic_language_constructs.py), that can
+# then be visited by a target language writer.
+
+import traceback
+import time
+
+from sccd.compiler.utils import Enum, Logger
+from sccd.compiler.visitor import Visitor
+from sccd.compiler.sccd_constructs import FormalParameter
+from sccd.compiler.stateful_writer import StatefulWriter
+import sccd.compiler.generic_language_constructs as GLC
+
+Platforms = Enum("Threads","GameLoop","EventLoop") 
+
+class GenericGenerator(Visitor):
+    
+    def __init__(self, platform):
+        self.platform = platform
+        self.writer = StatefulWriter()
+
+    def generic_visit(self, node):
+        Logger.showWarning("GenericGenerator has no visit method for node of type '" + str(type(node)) + "'.")
+
+    def get(self):
+        return self.writer.get()
+
+    def visit_ClassDiagram(self, class_diagram):
+        header = ("Generated by Statechart compiler by Glenn De Jonghe, Joeri Exelmans, Simon Van Mierlo, and Yentl Van Tendeloo (for the inspiration)\n")
+        if class_diagram.name or class_diagram.author or class_diagram.description:
+            header += "\n"
+        if class_diagram.author:
+            header += "Model author: " + class_diagram.author + "\n"
+        if class_diagram.name:
+            header += "Model name:   " + class_diagram.name + "\n"
+        if class_diagram.description.strip():
+            header += "Model description:\n"
+            header += class_diagram.description.strip()
+
+        self.writer.addMultiLineComment(header)
+        self.writer.addVSpace()
+        self.writer.addInclude(([GLC.RuntimeModuleIdentifier(), "statecharts_core"]))
+        if class_diagram.top.strip():
+            self.writer.addRawCode(class_diagram.top)
+        self.writer.addVSpace()
+
+        self.writer.beginPackage(class_diagram.name)
+        
+        # visit children
+        for c in class_diagram.classes :
+            c.accept(self)
+         
+        self.writer.beginClass("ObjectManager", ["ObjectManagerBase"])
+
+        self.writer.beginConstructor()
+        self.writer.addFormalParameter("controller")
+        self.writer.beginMethodBody()
+        self.writer.beginSuperClassConstructorCall("ObjectManagerBase")
+        self.writer.addActualParameter("controller")
+        self.writer.endSuperClassConstructorCall()
+        self.writer.endMethodBody()
+        self.writer.endConstructor()
+
+        self.writer.beginMethod("instantiate")
+        self.writer.addFormalParameter("class_name")
+        self.writer.addFormalParameter("construct_params")
+        self.writer.beginMethodBody()
+        for index,c in enumerate(class_diagram.classes):
+            self.writer.beginElseIf(GLC.EqualsExpression("class_name", GLC.String(c.name)))
+            if c.isAbstract():
+                # cannot instantiate abstract class
+                self.writer.add(GLC.ThrowExceptionStatement(GLC.String("Cannot instantiate abstract class \"" + c.name + "\" with unimplemented methods \"" + "\", \"".join(c.abstract_method_names) + "\".")))
+            else:
+                new_expr = GLC.NewExpression(c.name, [GLC.SelfProperty("controller")])
+                param_count = 0
+                for p in c.constructors[0].parameters:
+                    new_expr.getActualParameters().add(GLC.ArrayIndexedExpression("construct_params", str(param_count)))
+                    param_count += 1
+                self.writer.addAssignment(
+                    GLC.LocalVariableDeclaration("instance"),
+                    new_expr)
+                self.writer.addAssignment(
+                    GLC.Property("instance", "associations"),
+                    GLC.MapExpression())
+                for a in c.associations:
+                    a.accept(self)
+            self.writer.endElseIf()
+        self.writer.beginElse()
+        self.writer.add(
+            GLC.ThrowExceptionStatement(
+                GLC.AdditionExpression(
+                    GLC.String("Cannot instantiate class "),
+                    "class_name"
+                )
+            )
+        )
+        self.writer.endElse()
+        self.writer.add(GLC.ReturnStatement("instance"))
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+        self.writer.endClass() # ObjectManager
+
+        if self.platform == Platforms.Threads:
+            controller_sub_class = "ThreadsControllerBase"
+        if self.platform == Platforms.EventLoop :
+            controller_sub_class = "EventLoopControllerBase"
+        elif self.platform == Platforms.GameLoop :
+            controller_sub_class = "GameLoopControllerBase"
+
+        self.writer.beginClass("Controller", [controller_sub_class])
+        self.writer.beginConstructor()
+        for p in class_diagram.default_class.constructors[0].parameters:
+            p.accept(self)
+        if self.platform == Platforms.EventLoop:
+            self.writer.addFormalParameter("event_loop_callbacks")
+            self.writer.addFormalParameter("finished_callback", GLC.NoneExpression())
+            self.writer.addFormalParameter("behind_schedule_callback", GLC.NoneExpression())
+        elif self.platform == Platforms.Threads:
+            self.writer.addFormalParameter("keep_running", GLC.TrueExpression())
+            self.writer.addFormalParameter("behind_schedule_callback", GLC.NoneExpression())
+        self.writer.beginMethodBody()
+        self.writer.beginSuperClassConstructorCall(controller_sub_class)
+        self.writer.addActualParameter(GLC.NewExpression("ObjectManager", [GLC.SelfExpression()]))
+        if self.platform == Platforms.EventLoop:
+            self.writer.addActualParameter("event_loop_callbacks")
+            self.writer.addActualParameter("finished_callback")
+            self.writer.addActualParameter("behind_schedule_callback")
+        elif self.platform == Platforms.Threads:
+            self.writer.addActualParameter("keep_running")
+            self.writer.addActualParameter("behind_schedule_callback")
+        self.writer.endSuperClassConstructorCall()
+        for i in class_diagram.inports:
+            self.writer.add(GLC.FunctionCall(GLC.SelfProperty("addInputPort"), [GLC.String(i)]))
+        for o in class_diagram.outports:
+            self.writer.add(GLC.FunctionCall(GLC.SelfProperty("addOutputPort"), [GLC.String(o)]))
+        actual_parameters = [p.getIdent() for p in class_diagram.default_class.constructors[0].parameters]
+        self.writer.add(GLC.FunctionCall(GLC.Property(GLC.SelfProperty("object_manager"), "createInstance"), [GLC.String(class_diagram.default_class.name), GLC.ArrayExpression(actual_parameters)]))
+        self.writer.endMethodBody()
+        self.writer.endConstructor()
+        self.writer.endClass() # Controller
+
+        # visit test node if there is one
+        if class_diagram.test:
+            class_diagram.test.accept(self)
+
+        self.writer.endPackage()
+
+    ### TESTS
+    def visit_DiagramTest(self, test):
+        # helper class
+        self.writer.beginClass("InputEvent")
+        self.writer.beginConstructor()
+        self.writer.addFormalParameter("name")
+        self.writer.addFormalParameter("port")
+        self.writer.addFormalParameter("parameters")
+        self.writer.addFormalParameter("time_offset")
+        self.writer.beginMethodBody()
+        self.writer.addAssignment(GLC.SelfProperty("name"), "name")
+        self.writer.addAssignment(GLC.SelfProperty("port"), "port")
+        self.writer.addAssignment(GLC.SelfProperty("parameters"), "parameters")
+        self.writer.addAssignment(GLC.SelfProperty("time_offset"), "time_offset")
+        self.writer.endMethodBody()
+        self.writer.endConstructor()
+        self.writer.endClass()
+        self.writer.beginClass("Test")
+        if test.input:
+            test.input.accept(self)
+        else:
+            self.writer.addStaticAttribute("input_events", GLC.ArrayExpression())
+        if test.expected:
+            test.expected.accept(self)
+        else:
+            self.writer.addStaticAttribute("expected_events", GLC.ArrayExpression())
+        self.writer.endClass()
+
+    def visit_DiagramTestInput(self, test_input):
+        # write array of input events
+        self.writer.startRecordingExpression()
+        self.writer.beginArray()
+        for e in test_input.input_events:
+            e.accept(self)
+        self.writer.endArray()
+        array_expr = self.writer.stopRecordingExpression()
+        self.writer.addStaticAttribute("input_events", array_expr)
+
+    def visit_DiagramTestInputEvent(self, event):
+        self.writer.add(GLC.NewExpression("InputEvent", [GLC.String(event.name), GLC.String(event.port), GLC.ArrayExpression(event.parameters), event.time]))
+
+    def visit_DiagramTestExpected(self, test_expected):
+        # write array of slots containing expected events
+        self.writer.startRecordingExpression()
+        self.writer.beginArray()
+        for s in test_expected.slots:
+            s.accept(self)
+        self.writer.endArray()
+        array_expr = self.writer.stopRecordingExpression()
+        self.writer.addStaticAttribute("expected_events", array_expr)
+
+    def visit_DiagramTestExpectedSlot(self, slot):
+        # write slot
+        self.writer.beginArray()
+        for e in slot.expected_events:
+            e.accept(self)
+        self.writer.endArray()
+
+    def visit_DiagramTestEvent(self, event):
+        self.writer.add(GLC.NewExpression("Event", [GLC.String(event.name), GLC.String(event.port), GLC.ArrayExpression(event.parameters)]))
+
+    ### CLASS
+    def visit_Class(self, class_node):
+        """
+        Generate code for Class construct
+        """
+        super_classes = []
+        if not class_node.super_class_objs:
+            # if none of the class' super classes is defined in the diagram,
+            # we have to inherit RuntimeClassBase
+            if class_node.statechart:
+                # only inherit RuntimeClassBase if class has a statechart
+                super_classes.append("RuntimeClassBase")
+        if class_node.super_classes:
+            for super_class in class_node.super_classes:
+                super_classes.append(super_class)
+
+        self.writer.beginClass(class_node.name, super_classes)
+
+        # visit constructor
+        class_node.constructors[0].accept(self)
+
+        # visit destructor
+        class_node.destructors[0].accept(self)
+        
+        # visit methods
+        for i in class_node.methods:
+            i.accept(self)
+
+        # compile and initialize Statechart
+        if class_node.statechart:
+            class_node.statechart.accept(self)
+        
+            self.writer.beginMethod("initializeStatechart")
+            self.writer.beginMethodBody()
+            self.writer.addComment("enter default state")
+            
+            # get effective target of initial transition
+            self.writer.addAssignment(
+                GLC.SelfProperty("default_targets"),
+                GLC.FunctionCall(
+                    GLC.Property(
+                        GLC.MapIndexedExpression(
+                            GLC.SelfProperty("states"),
+                            GLC.String(class_node.statechart.root.initial)
+                        ),
+                        "getEffectiveTargetStates"
+                    )
+                )
+            )
+            
+            self.writer.add(GLC.SuperClassMethodCall(
+                "RuntimeClassBase",
+                "initializeStatechart",
+                []
+            ))
+            
+            self.writer.endMethodBody()
+            self.writer.endMethod()
+
+        self.writer.endClass()
+        
+    ### CLASS -- CONSTRUCTOR
+    def visit_Constructor(self, constructor):
+        self.writer.beginConstructor()
+        if constructor.parent_class.statechart:
+            self.writer.addFormalParameter("controller")
+        for p in constructor.getParams():
+            self.writer.addFormalParameter(p.getIdent(), p.getDefault())
+        self.writer.beginMethodBody() # constructor body
+
+        if constructor.parent_class.statechart:
+            self.writer.beginSuperClassConstructorCall("RuntimeClassBase")
+            self.writer.addActualParameter("controller")
+            self.writer.endSuperClassConstructorCall()
+
+            self.writer.addVSpace()
+            
+            for p in constructor.parent_class.inports:
+                self.writer.addAssignment(
+                    GLC.MapIndexedExpression(GLC.SelfProperty("inports"), GLC.String(p)),
+                    GLC.FunctionCall(GLC.Property("controller", "addInputPort"), [GLC.String(p), GLC.SelfExpression()]))
+
+            for p in constructor.parent_class.outports:
+                self.writer.addAssignment(
+                    GLC.MapIndexedExpression(GLC.SelfProperty("outports"), GLC.String(p)),
+                    GLC.FunctionCall(GLC.Property("controller", "addOutputPort"), [GLC.String(p), GLC.SelfExpression()]))
+
+            self.writer.addVSpace()
+
+            if constructor.parent_class.statechart.big_step_maximality == "take_one":
+                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "big_step_maximality"), GLC.Property("StatechartSemantics", "TakeOne"))
+            elif constructor.parent_class.statechart.big_step_maximality == "take_many":
+                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "big_step_maximality"), GLC.Property("StatechartSemantics", "TakeMany"))
+
+            if constructor.parent_class.statechart.internal_event_lifeline == "queue":
+                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "internal_event_lifeline"), GLC.Property("StatechartSemantics", "Queue"))
+            elif constructor.parent_class.statechart.internal_event_lifeline == "next_small_step":
+                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "internal_event_lifeline"), GLC.Property("StatechartSemantics", "NextSmallStep"))
+            elif constructor.parent_class.statechart.internal_event_lifeline == "next_combo_step":
+                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "internal_event_lifeline"), GLC.Property("StatechartSemantics", "NextComboStep"))
+
+            if constructor.parent_class.statechart.input_event_lifeline == "first_small_step":
+                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "input_event_lifeline"), GLC.Property("StatechartSemantics", "FirstSmallStep"))
+            elif constructor.parent_class.statechart.input_event_lifeline == "first_combo_step":
+                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "input_event_lifeline"), GLC.Property("StatechartSemantics", "FirstComboStep"))
+            elif constructor.parent_class.statechart.input_event_lifeline == "whole":
+                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "input_event_lifeline"), GLC.Property("StatechartSemantics", "Whole"))
+
+            if constructor.parent_class.statechart.priority == "source_parent":
+                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "priority"), GLC.Property("StatechartSemantics", "SourceParent"))
+            elif constructor.parent_class.statechart.priority == "source_child":
+                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "priority"), GLC.Property("StatechartSemantics", "SourceChild"))
+
+
+            if constructor.parent_class.statechart.concurrency == "single":
+                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "concurrency"), GLC.Property("StatechartSemantics", "Single"))
+            elif constructor.parent_class.statechart.concurrency == "many":
+                self.writer.addAssignment(GLC.Property(GLC.SelfProperty("semantics"), "concurrency"), GLC.Property("StatechartSemantics", "Many"))
+
+            self.writer.addVSpace()
+            self.writer.addComment("build Statechart structure")
+            self.writer.add(GLC.FunctionCall(GLC.SelfProperty("build_statechart_structure"), []))
+
+        if constructor.parent_class.attributes:
+            self.writer.addVSpace()
+            self.writer.addComment("user defined attributes")
+            for attribute in constructor.parent_class.attributes:
+                if attribute.init_value is None :
+                    self.writer.addAssignment(GLC.SelfProperty(attribute.name), GLC.NoneExpression())
+                else :
+                    self.writer.addAssignment(GLC.SelfProperty(attribute.name), attribute.init_value)
+
+        self.writer.addVSpace()
+        self.writer.addComment("call user defined constructor")
+        self.writer.beginSuperClassMethodCall(constructor.parent_class.name, "user_defined_constructor")
+        for p in constructor.getParams():
+            # we can't do p.accept(self) here because 'p' is a FormalParameter
+            # and we want to write it as an actual parameter
+            self.writer.addActualParameter(p.getIdent())
+        self.writer.endSuperClassMethodCall()
+        self.writer.endMethodBody()
+        self.writer.endConstructor()
+
+        # user defined constructor
+        self.writer.beginMethod("user_defined_constructor")
+        for p in constructor.getParams():
+            p.accept(self)
+        self.writer.beginMethodBody()
+        for super_class in constructor.parent_class.super_classes:
+            # begin call
+            if super_class in constructor.parent_class.super_class_objs:
+                self.writer.beginSuperClassMethodCall(super_class, "user_defined_constructor")
+            else:
+                self.writer.beginSuperClassConstructorCall(super_class)
+            # write actual parameters
+            if super_class in constructor.super_class_parameters:
+                for p in constructor.super_class_parameters[super_class]:
+                    self.writer.addActualParameter(p)
+            # end call
+            if super_class in constructor.parent_class.super_class_objs:
+                self.writer.endSuperClassMethodCall()
+            else:
+                self.writer.endSuperClassConstructorCall()
+        self.writer.addRawCode(constructor.body)
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+    def visit_FormalParameter(self, formal_parameter):
+        self.writer.addFormalParameter(formal_parameter.getIdent(), formal_parameter.getDefault())
+
+    ### CLASS -- DESTRUCTOR
+    def visit_Destructor(self, destructor):
+        self.writer.beginMethod("user_defined_destructor")
+        self.writer.beginMethodBody()
+        if destructor.body.strip():
+            self.writer.addRawCode(destructor.body)
+        if destructor.parent_class.super_classes:
+            self.writer.addComment("call super class destructors")
+            for super_class in destructor.parent_class.super_classes:
+                # begin call
+                if super_class in destructor.parent_class.super_class_objs:
+                    self.writer.beginSuperClassMethodCall(super_class, "user_defined_destructor")
+                    self.writer.endSuperClassMethodCall()
+                else:
+                    self.writer.beginSuperClassDestructorCall(super_class)
+                    self.writer.endSuperClassDestructorCall()
+                    pass
+
+                # self.writer.beginSuperClassMethodCall(super_class, "user_defined_destructor")
+                # self.writer.endSuperClassMethodCall()
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+        
+    ### CLASS -- METHOD
+    def visit_Method(self, method):
+        self.writer.addVSpace()
+        self.writer.beginMethod(method.name, "user defined method")
+        for p in method.parameters:
+            p.accept(self)
+        self.writer.beginMethodBody()
+        self.writer.addRawCode(method.body)
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+        
+    ### CLASS -- ASSOCIATION
+    def visit_Association(self, association):
+        self.writer.addAssignment(
+            GLC.MapIndexedExpression(
+                GLC.Property("instance", "associations"),
+                GLC.String(association.name)),
+            GLC.NewExpression("Association", [GLC.String(association.to_class), str(association.min), str(association.max)]))
+
+    ### CLASS -- STATECHART
+    def visit_StateChart(self, statechart):
+        self.writer.addVSpace()
+        self.writer.beginMethod("build_statechart_structure", "builds Statechart structure")
+        self.writer.beginMethodBody()
+        
+        def writeState(s, i):
+            self.writer.addVSpace()
+            self.writer.addComment("state %s" % ("<root>" if s.is_root else s.new_full_name))
+            index_expr = GLC.MapIndexedExpression(GLC.SelfProperty("states"), GLC.String(s.new_full_name))
+            clazz = "State"
+            if s.is_parallel_state:
+                clazz = "ParallelState"
+            elif s.is_history:
+                if s.is_history_deep:
+                    clazz = "DeepHistoryState"
+                else:
+                    clazz = "ShallowHistoryState"
+            self.writer.addAssignment(
+                index_expr,
+                GLC.NewExpression(clazz, [str(i), GLC.String(s.new_full_name), GLC.SelfExpression()])
+            )
+            if not s.is_root:
+                if s.enter_action.action or s.has_timers:
+                    self.writer.add(
+                        GLC.FunctionCall(
+                            GLC.Property(
+                                index_expr,
+                                "setEnter"
+                            ),
+                            [GLC.SelfProperty(s.friendly_name + "_enter")]
+                        )
+                    )
+                if s.exit_action.action or s.has_timers:
+                    self.writer.add(
+                        GLC.FunctionCall(
+                            GLC.Property(
+                                index_expr,
+                                "setExit"
+                            ),
+                            [GLC.SelfProperty(s.friendly_name + "_exit")]
+                        )
+                    )
+        
+        # write all states
+        for (i, s) in enumerate(statechart.states):
+            writeState(s, i)
+                
+        # add children to composite states
+        self.writer.addVSpace()
+        self.writer.addComment("add children")
+        for (i, s) in enumerate(statechart.composites):
+            for c in s.children:
+                self.writer.add(
+                    GLC.FunctionCall(
+                        GLC.Property(
+                            GLC.MapIndexedExpression(
+                                GLC.SelfProperty("states"),
+                                GLC.String(s.new_full_name)
+                            ),
+                        "addChild"),
+                        [GLC.MapIndexedExpression(GLC.SelfProperty("states"), GLC.String(c.new_full_name))]
+                    )
+                )
+                
+        # fix tree at root, such that 'descendants' and 'ancestors' fields are filled in
+        self.writer.add(
+            GLC.FunctionCall(
+                GLC.Property(
+                    GLC.MapIndexedExpression(
+                        GLC.SelfProperty("states"),
+                        GLC.String("")
+                    ),
+                    "fixTree"
+                )
+            )
+        )
+        
+        # defaults
+        for (i, s) in enumerate(statechart.composites):
+            if not s.is_parallel_state:
+                self.writer.addAssignment(
+                    GLC.Property(
+                        GLC.MapIndexedExpression(
+                            GLC.SelfProperty("states"),
+                            GLC.String(s.new_full_name)
+                        ),
+                        "default_state"
+                    ),
+                    GLC.MapIndexedExpression(
+                        GLC.SelfProperty("states"),
+                        GLC.String(s.initial)
+                    )
+                )
+        
+        # transitions
+        for s in statechart.basics + statechart.composites:
+            if s.transitions:
+                self.writer.addVSpace()
+                self.writer.addComment("transition %s" % s.new_full_name)
+            for (i, t) in enumerate(s.transitions + s.else_transitions):
+                # instantiate new Transition instance
+                self.writer.addAssignment(
+                    GLC.LocalVariableDeclaration(
+                        "%s_%i" % (s.friendly_name, i)
+                    ),
+                    GLC.NewExpression(
+                        "Transition",
+                        [
+                            GLC.SelfExpression(),
+                            GLC.MapIndexedExpression(
+                                GLC.SelfProperty("states"),
+                                GLC.String(s.new_full_name),
+                            ),
+                            GLC.ArrayExpression(
+                                [
+                                    GLC.MapIndexedExpression(
+                                        GLC.SelfProperty("states"),
+                                        GLC.String(target_node.new_full_name)
+                                    )
+                                    for target_node in t.target.target_nodes
+                                ]
+                            )
+                        ]
+                    )
+                )
+                # if any action associated with transition: set executable_content to correct function (generated later)
+                if t.action.sub_actions:
+                    self.writer.add(
+                        GLC.FunctionCall(
+                            GLC.Property(
+                                "%s_%i" % (s.friendly_name, i),
+                                "setAction"
+                            ),
+                            [GLC.SelfProperty("%s_%i_exec" % (s.friendly_name, i))]
+                        )
+                    )
+                # if any trigger associated with transition: instantiate correct Event instance
+                trigger = None
+                if t.trigger.is_after:
+                    trigger = GLC.NewExpression("Event", [GLC.String("_%iafter" % (t.trigger.getAfterIndex()))])
+                elif t.trigger.event:
+                    trigger = GLC.NewExpression("Event",
+                                                    [
+                                                        GLC.String(t.trigger.event),
+                                                        GLC.NoneExpression() if t.trigger.port is None else GLC.FunctionCall(
+                                                                                                                GLC.SelfProperty("getInPortName"),
+                                                                                                                [GLC.String(t.trigger.port)]
+                                                                                                            )
+                                                    ]
+                                                )
+                else:
+                    trigger = GLC.NoneExpression()
+                if trigger:
+                    self.writer.add(
+                        GLC.FunctionCall(
+                            GLC.Property(
+                                "%s_%i" % (s.friendly_name, i),
+                                "setTrigger"
+                            ),
+                            [trigger]
+                        )
+                    )
+                # if any guard associated with transition: set guard to correct function (generated later)
+                if t.guard:
+                    self.writer.add(
+                        GLC.FunctionCall(
+                            GLC.Property(
+                                "%s_%i" % (s.friendly_name, i),
+                                "setGuard"
+                            ),
+                            [GLC.SelfProperty("%s_%i_guard" % (s.friendly_name, i))]
+                        )
+                    )
+                self.writer.add(
+                    GLC.FunctionCall(
+                        GLC.Property(
+                            GLC.MapIndexedExpression(
+                                GLC.SelfProperty("states"),
+                                GLC.String(s.new_full_name)
+                            ),
+                            "addTransition"
+                        ),
+                        ["%s_%i" % (s.friendly_name, i)]
+                    )
+                )
+                    
+            
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+        
+        # enter/exit actions
+        for (i, s) in enumerate(statechart.composites + statechart.basics):
+            if not s.is_root:
+                if s.enter_action.action or s.has_timers:
+                    s.enter_action.accept(self)
+                if s.exit_action.action or s.has_timers:
+                    s.exit_action.accept(self)
+        
+        # transition actions and guards
+        for s in statechart.composites + statechart.basics:
+            for (i, t) in enumerate(s.transitions):
+                if t.action.sub_actions:
+                    self.writeTransitionAction(t, i)
+                if t.hasGuard():
+                    self.writeTransitionGuard(t, i)
+        
+    def visit_FormalEventParameter(self, formal_event_parameter):
+        self.writer.add(formal_event_parameter.name)
+        
+    def writeFormalEventParameters(self, transition):
+        parameters = transition.getTrigger().getParameters()
+        if(len(parameters) > 0) :
+            for index, parameter in enumerate(parameters):
+                self.writer.startRecordingExpression()
+                parameter.accept(self)
+                parameter_expr = self.writer.stopRecordingExpression()
+                self.writer.addAssignment(
+                    GLC.LocalVariableDeclaration(parameter_expr),
+                    GLC.ArrayIndexedExpression("parameters", str(index)))        
+        
+    def writeTransitionAction(self, transition, index):
+        self.writer.beginMethod("%s_%i_exec" % (transition.parent_node.friendly_name, index))
+        
+        # parameters, start method
+        self.writer.addFormalParameter("parameters")
+        self.writer.beginMethodBody()
+
+        # handle parameters to actually use them
+        self.writeFormalEventParameters(transition)
+        
+        # write action
+        transition.getAction().accept(self)
+        
+        # end method
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+        
+    def writeTransitionGuard(self, transition, index):
+        self.writer.beginMethod("%s_%i_guard" % (transition.parent_node.friendly_name, index))
+        
+        # parameters, start method
+        self.writer.addFormalParameter("parameters")
+        self.writer.beginMethodBody()
+        
+        # handle parameters to actually use them
+        self.writeFormalEventParameters(transition)
+        
+        # get guard condition
+        self.writer.startRecordingExpression()
+        transition.getGuard().accept(self) # --> visit_Expression
+        expr = self.writer.stopRecordingExpression()
+        
+        # return statement, end method
+        self.writer.add(GLC.ReturnStatement(expr))
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+    
+    def visit_EnterAction(self, enter_method):
+        parent_node = enter_method.parent_node
+        self.writer.beginMethod(parent_node.friendly_name + "_enter")
+        self.writer.beginMethodBody()
+
+        if enter_method.action:
+            enter_method.action.accept(self)
+
+        # take care of any AFTER events
+        for transition in parent_node.transitions :
+            trigger = transition.getTrigger()
+            if trigger.isAfter() :
+                self.writer.startRecordingExpression()
+                trigger.after.accept(self)
+                after = self.writer.stopRecordingExpression()
+                self.writer.add(GLC.FunctionCall(GLC.SelfProperty("addTimer"), [str(trigger.getAfterIndex()), after]))
+                
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+         
+    def visit_ExitAction(self, exit_method):
+        parent_node = exit_method.parent_node
+        self.writer.beginMethod(parent_node.friendly_name + "_exit")
+        self.writer.beginMethodBody()
+        
+        # take care of any AFTER events
+        for transition in parent_node.transitions:
+            trigger = transition.getTrigger()
+            if trigger.isAfter():
+                self.writer.add(GLC.FunctionCall(GLC.SelfProperty("removeTimer"), [str(trigger.getAfterIndex())]))
+                
+        # execute user-defined exit action if present
+        if exit_method.action:
+            exit_method.action.accept(self)
+        
+        self.writer.endMethodBody()
+        self.writer.endMethod()        
+            
+    # helper method
+    def writeEnterHistory(self, entered_node, is_deep):
+        ### OLD CODE (TODO)
+        self.writer.beginMethod("enterHistory" + ("Deep" if is_deep else "Shallow") + "_" + entered_node.full_name)
+        self.writer.beginMethodBody()
+
+        self.writer.beginIf(GLC.EqualsExpression(
+            GLC.ArrayLength(
+                GLC.MapIndexedExpression(
+                    GLC.SelfProperty("history_state"),
+                    GLC.SelfProperty(entered_node.full_name))),
+            "0"))
+        defaults = entered_node.defaults
+
+        for node in defaults:
+            if node.is_basic :
+                self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enter_"+node.full_name)))
+            elif node.is_composite :
+                self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enterDefault_"+node.full_name)))
+
+        self.writer.endIf()
+        self.writer.beginElse()
+        children = entered_node.children
+        if entered_node.is_parallel_state:
+            for child in children:
+                if not child.is_history :
+                    self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enter_"+child.full_name)))
+                    self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enterHistory"+("Deep" if is_deep else "Shallow")+"_"+child.full_name)))
+        else:
+            for child in children:
+                if not child.is_history :
+                    self.writer.beginIf(GLC.ArrayContains(
+                        GLC.MapIndexedExpression(
+                            GLC.SelfProperty("history_state"),
+                            GLC.SelfProperty(entered_node.full_name)),
+                        GLC.SelfProperty(child.full_name)))
+                    if child.is_composite:
+                        if is_deep :
+                            self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enter_"+child.full_name)))
+                            self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enterHistoryDeep_"+child.full_name)))
+                        else :
+                            self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enterDefault_"+child.full_name)))
+                    else:
+                        self.writer.add(GLC.FunctionCall(GLC.SelfProperty("enter_"+child.full_name)))
+                    self.writer.endIf()
+        self.writer.endElse()
+
+        self.writer.endMethodBody()
+        self.writer.endMethod()
+
+    def visit_SelfReference(self, self_reference):
+        self.writer.add(GLC.SelfExpression())
+
+    def visit_StateReference(self, state_ref):
+        self.writer.beginArray()
+        for node in state_ref.getNodes():
+            self.writer.add(GLC.SelfProperty(node.full_name))
+        self.writer.endArray()
+
+    def visit_InStateCall(self, in_state_call):
+        self.writer.add(
+            GLC.FunctionCall(
+                GLC.SelfProperty("inState"),
+                [
+                    GLC.ArrayExpression(
+                        [GLC.String(target_node.new_full_name) for target in in_state_call.targets for target_node in target.target_nodes]
+                    )
+                ]
+            )
+        )
+        
+    def visit_ElseGuard(self, else_guard):
+        self.writer.add(
+            GLC.String("ELSE_GUARD")
+        )
+
+    def visit_Expression(self, expression):
+        self.writer.startRecordingExpression()
+        self.writer.beginGlue()
+        for part in expression.expression_parts:
+            part.accept(self)
+        self.writer.endGlue()
+        expr = self.writer.stopRecordingExpression()
+        self.writer.add(expr)
+
+    def visit_ExpressionPartString(self, e):
+        self.writer.add(e.string)
+        
+    def visit_RaiseEvent(self, raise_event):
+        self.writer.startRecordingExpression()
+        self.writer.begin(GLC.NewExpression("Event"))
+
+        self.writer.addActualParameter(GLC.String(raise_event.getEventName()))
+        if raise_event.isOutput():
+            self.writer.addActualParameter(
+                                            GLC.FunctionCall(
+                                                                GLC.SelfProperty("getOutPortName"),
+                                                                [GLC.String(raise_event.getPort())]
+                                                            )
+                                            )
+        else:
+            self.writer.addActualParameter(GLC.NoneExpression())
+
+        self.writer.end()
+        new_event_expr = self.writer.stopRecordingExpression()
+
+        self.writer.startRecordingExpression()
+        self.writer.beginArray()
+        if raise_event.isCD():
+            self.writer.add(GLC.SelfExpression())
+        for param in raise_event.getParameters() :
+            param.accept(self) # -> visit_Expression will cause expressions to be added to array
+        self.writer.endArray()
+        parameters_array_expr = self.writer.stopRecordingExpression()
+        new_event_expr.getActualParameters().add(parameters_array_expr)
+
+        if raise_event.isNarrow():
+            self.writer.add(GLC.FunctionCall(
+                GLC.Property(GLC.SelfProperty("big_step"), "outputEventOM"), [
+                    GLC.NewExpression("Event", [
+                        GLC.String("narrow_cast"),
+                        GLC.NoneExpression(),
+                        GLC.ArrayExpression([
+                            GLC.SelfExpression(),
+                            raise_event.getTarget(),
+                            new_event_expr])])]))
+        elif raise_event.isLocal():
+            self.writer.add(GLC.FunctionCall(
+                GLC.SelfProperty("raiseInternalEvent"),
+                [new_event_expr]))
+        elif raise_event.isOutput():
+            self.writer.add(GLC.FunctionCall(
+                GLC.Property(GLC.SelfProperty("big_step"), "outputEvent"),
+                [new_event_expr]))
+        elif raise_event.isCD():
+            self.writer.add(GLC.FunctionCall(
+                GLC.Property(GLC.SelfProperty("big_step"), "outputEventOM"),
+                [new_event_expr]))
+        elif raise_event.isBroad():
+            self.writer.add(GLC.FunctionCall(
+                GLC.Property(GLC.SelfProperty("big_step"), "outputEventOM"),
+                [GLC.NewExpression("Event", [
+                    GLC.String("broad_cast"),
+                    GLC.NoneExpression(),
+                    GLC.ArrayExpression([
+                        GLC.SelfExpression(),
+                        new_event_expr])])]))
+            
+    def visit_Script(self, script):
+        self.writer.addRawCode(script.code)
+        
+    def visit_Log(self, log):
+        self.writer.add(GLC.LogStatement(log.message))
+        
+    def visit_Assign(self, assign):
+        self.writer.startRecordingExpression()
+        assign.lvalue.accept(self) # --> visit_Expression
+        lvalue = self.writer.stopRecordingExpression()
+        self.writer.startRecordingExpression()
+        assign.expression.accept(self) # --> visit_Expression
+        rvalue = self.writer.stopRecordingExpression()
+        self.writer.addAssignment(lvalue, rvalue)
+

File diff suppressed because it is too large
+ 1006 - 0
sccd/compiler/generic_language_constructs.py


+ 287 - 0
sccd/compiler/javascript_writer.py

@@ -0,0 +1,287 @@
+from sccd.compiler.visitor import Visitor
+from sccd.compiler.generic_language_constructs import *
+
+class JavascriptWriter(CLikeWriterBase):
+    def __init__(self, outputter):
+        self.out = outputter
+
+    ### VISIT METHODS ###
+
+    def visit_ArrayContains(self, a):
+        array = a.getArrayExpression()
+        el = a.getElementExpression()
+
+        self.out.extendWrite("(")
+        array.accept(self)
+        self.out.extendWrite(".indexOf(")
+        el.accept(self)
+        self.out.extendWrite(") !== -1)")
+
+    def visit_ArrayExpression(self, a):
+        elements = a.getElements()
+        if len(elements) == 0:
+            self.out.extendWrite("new Array()")
+        else:
+            self.out.extendWrite("[")
+            self.writeCommaSeparated(elements)
+            self.out.extendWrite("]")
+
+    def visit_ArrayIndexOf(self, a):
+        array = a.getArrayExpression()
+        el = a.getElementExpression()
+
+        array.accept(self)
+        self.out.extendWrite(".indexOf(")
+        el.accept(self)
+        self.out.extendWrite(")")
+
+    def visit_ArrayLength(self, a):
+        a.getArrayExpression().accept(self)
+        self.out.extendWrite(".length")
+
+    def visit_ArrayPushBack(self, a):
+        array = a.getArrayExpression()
+        el = a.getElementExpression()
+
+        array.accept(self)
+        self.out.extendWrite(".push(")
+        el.accept(self)
+        self.out.extendWrite(")")
+
+    def visit_AST(self, ast):
+        self.writeAll(ast.getEntries())
+
+    def visit_Class(self, c):
+        class_name = c.getIdentifier()
+        constructor = c.getConstructor()
+        super_classes = c.getSuperClassIdentifierList()
+        description = c.getDescription()
+
+        self.out.write()
+        if description:
+            self.writeComment(description)
+        constructor.accept(self)
+        if super_classes:
+            self.out.write(class_name + ".prototype = new Object();")
+            self.out.write("(function() {")
+            self.out.indent()
+            for s in super_classes:
+                # workaround for multiple inheritance
+                self.out.write("var proto = new " + s + "();")
+                self.out.write("for (prop in proto) {")
+                self.out.indent()
+                self.out.write(class_name + ".prototype[prop] = proto[prop];")
+                self.out.dedent()
+                self.out.write("}")
+            self.out.dedent()
+            self.out.write("})();")
+        self.writeAll(c.getMembers())
+
+    def visit_Constructor(self, constructor):
+        class_name = constructor.getClass().getIdentifier()
+        parameters = constructor.getFormalParameters()
+        body = constructor.getBody()
+
+        self.out.write("var " + class_name + " = function")
+        parameters.accept(self)
+        body.accept(self)
+        self.out.extendWrite(";")
+
+    def visit_EqualsOperator(self, e):
+        self.out.extendWrite(" === ")
+
+    def visit_ForLoopBody(self, body):
+        for_loop = body.getForLoop()
+        collection_expr = for_loop.getCollectionExpression()
+        iterator_identifier = for_loop.getIteratorIdentifier()
+
+        self.out.extendWrite(" {")
+        self.out.indent()
+        self.out.write("if (!")
+        collection_expr.accept(self)
+        self.out.extendWrite(".hasOwnProperty(" + iterator_identifier + "_idx)) continue;")
+        self.out.write("var " + iterator_identifier + " = ")
+        collection_expr.accept(self)
+        self.out.extendWrite("[" + iterator_identifier + "_idx]")
+        self.writeAll(body.getEntries())
+        self.out.dedent()
+        self.out.write("}")
+
+    def visit_ForLoopCurrentElement(self, el):
+        collection = el.getCollectionExpression()
+        iterator = el.getIteratorIdentifier()
+
+        collection.accept(self)
+        self.out.extendWrite("[" + iterator + "]")
+
+    def visit_ForLoopIterateArray(self, loop):
+        collection = loop.getCollectionExpression()
+        iterator = loop.getIteratorIdentifier()
+        body = loop.getBody()
+
+        self.out.write("for (var " + iterator + "_idx in ")
+        collection.accept(self)
+        self.out.extendWrite(")")
+        body.accept(self)
+
+    def visit_ForLoopIterateMapValues(self, loop):
+        collection = loop.getCollectionExpression()
+        iterator = loop.getIteratorIdentifier()
+        body = loop.getBody()
+
+        self.out.write("for (var " + iterator + "_idx in ")
+        collection.accept(self)
+        self.out.extendWrite(")")
+        body.accept(self)
+
+    def visit_FormalParameter(self, parameter):
+        self.out.extendWrite(parameter.getIdentifier())
+
+    def visit_IncludeStatement(self, i):
+        pass # javascript doesn't have an include mechanism
+
+    def visit_LocalVariableDeclaration(self, decl):
+        identifier = decl.getIdentifier()
+        init_value = decl.getInitValue()
+
+        self.out.extendWrite("var " + identifier)
+        if init_value:
+            self.out.extendWrite(" = ")
+            init_value.accept(self)
+
+    def visit_LogStatement(self, l):
+        self.out.write("console.log(\"" + l.getMessage() + "\");")
+
+    def visit_MapExpression(self, m):
+        elements = m.getElements()
+        if len(elements) == 0:
+            self.out.extendWrite("new Object()")
+        else:
+            self.out.extendWrite("{")
+            keys = list(elements.keys())
+            for i in range(len(keys)):
+                if i != 0:
+                    self.out.extendWrite(", ")            
+                self.out.extendWrite(keys[i] + " : ")
+                self.out.extendWrite(" : ")
+                elements[keys[i]].accept(self)
+            self.out.extendWrite("}")
+
+    def visit_MapIndexedExpression(self, i):
+        m = i.getMapExpression()
+        key = i.getKeyExpression()
+
+        m.accept(self)
+        self.out.extendWrite("[")
+        key.accept(self)
+        self.out.extendWrite("]")
+
+    def visit_MapRemoveElement(self, stmt):
+        map_expr = stmt.getMapExpression()
+        key_expr = stmt.getKeyExpression()
+
+        self.out.write("delete ") # this is a statement, not an expression
+        map_expr.accept(self)
+        self.out.extendWrite("[")
+        key_expr.accept(self)
+        self.out.extendWrite("];")        
+
+    def visit_Method(self, method):
+        class_name = method.getClass().getIdentifier()
+        method_name = method.getIdentifier()
+        description = method.getDescription()
+        body = method.getBody()
+        parameters = method.getFormalParameters()
+
+        self.out.write()
+        if description:
+            self.writeComment(description)
+        self.out.write(class_name + ".prototype." + method_name + " = function")
+        parameters.accept(self)
+        body.accept(self)
+        self.out.extendWrite(";")
+
+    def visit_MethodBody(self, body):
+        method = body.getMethod()
+        formal_parameters = method.getFormalParameters()
+        formal_parameter_list = formal_parameters.getParameterList()
+
+        self.out.extendWrite(" {")
+        self.out.indent()
+        # check for undefined parameters and replace them with default values
+        for p in formal_parameter_list:
+            p_id = p.getIdentifier()
+            p_default = p.getDefaultValue()
+            if p_default:
+                self.out.write("if (" + p_id + " === undefined) " + p_id + " = ")
+                p_default.accept(self)
+                self.out.extendWrite(";")
+        self.writeAll(body.getEntries())
+        self.out.dedent()
+        self.out.write("}")
+
+    def visit_NoneExpression(self, n):
+        self.out.extendWrite("null")
+
+    def visit_Package(self, package):
+        name = package.getIdentifier()
+        description = package.getDescription()
+
+        self.writeComment("package \"" + name + "\"")
+        if description:
+            self.writeComment(description)
+        self.out.write("var " + name + " = {};")
+        self.out.write("(function() {")
+        for d in package.getDeclarations():
+            d_id = d.getIdentifier()
+            d.accept(self)
+            self.out.write()
+            self.out.write("// add symbol '" + d_id + "' to package '" + name + "'")
+            self.out.write(name + "." + d_id + " = " + d_id + ";")
+        self.out.write("})();")
+
+    def visit_RuntimeModuleIdentifier(self, r):
+        self.out.extendWrite("javascript_runtime")
+
+    def visit_StaticAttribute(self, attr):
+        name = attr.getIdentifier()
+        init_value = attr.getInitValue()
+        class_name = attr.getClass().getIdentifier()
+
+        if init_value:
+            self.out.write(class_name + ".prototype." + name + " = ")
+            init_value.accept(self)
+            self.out.extendWrite(";")
+        else:
+            self.out.write(class_name + ".prototype." + name + " = null;")
+
+    def visit_SuperClassConstructorCall(self, call):
+        super_class = call.getSuperClassIdentifier()
+        params = call.getActualParameters()
+        param_list = [Literal("this")] + params.getParameterList()
+        params = ActualParameters(param_list)
+
+        self.out.extendWrite(super_class)
+        self.out.extendWrite(".call")
+        params.accept(self)
+
+    def visit_SuperClassDestructorCall(self, call):
+        pass # Javascript doesn't have destructors
+
+    def visit_SuperClassMethodCall(self, call):
+        super_class = call.getSuperClassIdentifier()
+        method_name = call.getMethodIdentifier()
+        params = call.getActualParameters()
+        param_list = [Literal("this")] + params.getParameterList()
+        params = ActualParameters(param_list)
+
+        self.out.extendWrite(super_class)
+        self.out.extendWrite(".prototype." + method_name + ".call")
+        params.accept(self)
+
+    def visit_ThrowExceptionStatement(self, stmt):
+        self.out.write("throw new Error(")
+        stmt.getExpression().accept(self)
+        self.out.extendWrite(");")
+
+

+ 171 - 0
sccd/compiler/lexer.py

@@ -0,0 +1,171 @@
+from sccd.compiler.utils import Enum
+
+TokenType = Enum("SLASH",
+                                 "LBRACKET",
+                                 "RBRACKET",
+                                 "COMMA",
+                                 "DOT",
+                                 "NUMBER",
+                                 "WORD",
+                                 "QUOTED",
+                                 "WHITESPACE",
+                                 "BINARYOPERATOR",
+                                 "UNARYOPERATOR",
+                                 "UNKNOWN"
+                                )
+
+class Token(object):
+        """ A simple Token structure. Token type, value and position.
+        """
+        def __init__(self, token_type, val, pos):
+                self.type = token_type
+                self.val = val
+                self.pos = pos
+
+        def __str__(self):
+                return '%s(%s) at %s' % (TokenType.name_of(self.type), self.val, self.pos)
+
+
+class LexerError(Exception):
+        def __init__(self, pos):
+                self.pos = pos
+                
+class Lexer(object):
+        single_rules = {
+                        '/': TokenType.SLASH,
+                        '(': TokenType.LBRACKET,
+                        ')': TokenType.RBRACKET,
+                        ',': TokenType.COMMA,
+                        '.': TokenType.DOT,
+                        '+': TokenType.BINARYOPERATOR,
+                        '-': TokenType.BINARYOPERATOR,
+                        '<': TokenType.BINARYOPERATOR,
+                        '>': TokenType.BINARYOPERATOR,
+                        '==': TokenType.BINARYOPERATOR,
+                        '<=': TokenType.BINARYOPERATOR,
+                        '>=': TokenType.BINARYOPERATOR,
+                        '=': TokenType.BINARYOPERATOR,
+                        '+=': TokenType.BINARYOPERATOR,
+                        '-=': TokenType.BINARYOPERATOR,
+                        '&&': TokenType.BINARYOPERATOR,
+                        '||': TokenType.BINARYOPERATOR,
+                        '!': TokenType.UNARYOPERATOR}
+        
+        def __init__(self, skip_white_space = True, accept_unknown_tokens = False):
+                self.skip_white_space = skip_white_space
+                self.accept_unknown_tokens = accept_unknown_tokens
+
+        def input(self, buf):
+                """ Initialize the lexer with a buffer as input.
+                """
+                self.buf = buf
+                self.pos = 0
+                self.buflen = len(buf)
+
+        def nextToken(self):
+                """ Return the next token (a Token object) found in the
+                        input buffer. None is returned if the end of the
+                        buffer was reached.
+                        In case of a lexing error (the current chunk of the
+                        buffer matches no rule), a LexerError is raised.
+                """
+                if self.skip_white_space :
+                        self.skipWhiteSpace() 
+                if self.pos >= self.buflen:
+                        return None
+
+                #c part of next token
+                c = self.buf[self.pos]
+                
+                #check if it is an operator
+                result_type = self.single_rules.get(c,None)
+                if result_type is not None :
+                        if self.pos < self.buflen-1:
+                                c2 = c+self.buf[self.pos+1]
+                                result_type2 = self.single_rules.get(c2, None)
+                                if result_type2 is not None:
+                                        c = c2
+                                        result_type = result_type2
+                                        self.pos += 1
+                        token = Token(result_type, c, self.pos)
+                        self.pos += 1
+                        return token
+                else : #not an operator
+                        if (self.isAlpha(c)) :
+                                return self.processIdentifier()
+                        elif (self.isDigit(c)) :
+                                return self.processNumber()
+                        elif ( c == "'" or c == '"') :
+                                return self.processQuote()
+                        elif (self.isWhiteSpace(c)) :
+                                return self.processWhiteSpace()
+
+                # if we're here, no rule matched
+                if self.accept_unknown_tokens :
+                        token = Token(TokenType.UNKNOWN, c, self.pos)
+                        self.pos += 1
+                        return token
+                raise LexerError("Invalid character at position " + str(self.pos) + ".")
+
+        def tokens(self):
+                """ Returns an iterator to the tokens found in the buffer.
+                """
+                while True:
+                        tok = self.nextToken()
+                        if tok is None: break
+                        yield tok
+                        
+        def skipWhiteSpace(self):
+                while (self.pos < self.buflen) : 
+                        if self.isWhiteSpace(self.buf[self.pos]) :
+                                self.pos += 1
+                        else :
+                                break     
+                        
+        def isAlpha(self, c):
+                return c.isalpha() or c == '_';
+        
+        def isAlphaNum(self, c):
+                return c.isalnum() or c == '_';
+        
+        def isDigit(self, c):
+                return c.isdigit()
+        
+        def isWhiteSpace(self, c):
+                return c == ' ' or c == '\t' or c == '\r' or c == '\n'
+        
+        def processNumber(self):
+                nextpos = self.pos + 1
+                while (nextpos < self.buflen) and (self.isDigit(self.buf[nextpos])) :
+                        nextpos += 1;
+                token = Token(TokenType.NUMBER, self.buf[self.pos:nextpos], self.pos)
+                self.pos = nextpos
+                return token
+        
+        def processIdentifier(self):
+                nextpos = self.pos + 1
+                while (nextpos < self.buflen) and (self.isAlphaNum(self.buf[nextpos])) :
+                        nextpos += 1;
+                token = Token(TokenType.WORD, self.buf[self.pos:nextpos], self.pos)
+                self.pos = nextpos
+                return token
+        
+        def processQuote(self):
+                # self.pos points at the opening quote. Find the ending quote.
+                end_index = self.buf.find(self.buf[self.pos], self.pos + 1)
+        
+                if (end_index == -1) :
+                        print("Buffer: " + str(self.buf))
+                        raise LexerError("Missing matching quote for the quote at position " + str(self.pos) + ".")
+                token = Token(TokenType.QUOTED, self.buf[self.pos:end_index+1], self.pos)
+
+                self.pos = end_index + 1;
+                return token;
+        
+        def processWhiteSpace(self):
+                nextpos = self.pos + 1
+                while (nextpos < self.buflen) and (self.isWhiteSpace(self.buf[nextpos])) :
+                        nextpos += 1;
+                token = Token(TokenType.WHITESPACE, self.buf[self.pos:nextpos], self.pos)
+                self.pos = nextpos
+                return token

+ 83 - 0
sccd/compiler/path_calculator.py

@@ -0,0 +1,83 @@
+from sccd.compiler.compiler_exceptions import *
+from sccd.compiler.visitor import Visitor
+
+class PathCalculator(Visitor):
+    """ Computes the states that must be exited and entered for a specific transition if the system is to make
+        that transition. 
+    """
+    
+    def visit_ClassDiagram(self, class_diagram): 
+        for c in class_diagram.classes :
+            c.accept(self)
+
+    def visit_Class(self, c):
+        if c.statechart:
+            c.statechart.accept(self)
+        
+    def visit_StateChart(self, statechart):
+        for node in statechart.basics + statechart.composites:
+            node.accept(self)
+                     
+    def visit_StateChartNode(self, node):
+        for transition in node.transitions :
+            transition.accept(self)
+            
+    def visit_StateChartTransition(self, transition):
+        #Find the scope of the transition (lowest common proper ancestor)
+        #TODO: Could it be made more efficient if we calculated the LCA from the source node and just one of the target nodes?
+        LCA = self.calculateLCA(transition)
+        
+        if LCA == None:
+            #raise CompilerException("Transision with source " + transition.parent_node.name + " and target " + transition.target_nodes + " has no lowest common ancestor node.")
+            raise CompilerException("Transision with source '" + transition.parent_node.name + "' and target '" + transition.target_string + "' has no lowest common ancestor node.")
+
+        #Calculate exit nodes
+        transition.exit_nodes = [transition.parent_node]
+        for node in transition.parent_node.getAncestors() :
+            if (node == LCA) :
+                break
+            transition.exit_nodes.append(node)
+            if node.is_parallel_state:
+                raise CompilerException("Transision with source '" + transition.parent_node.name + "' and target '" + transition.target_string + "' exits a parallel component.")
+       
+        #Calculate enter nodes
+        transition.enter_nodes = []
+        
+        #we then add the branching paths to the other nodes
+        for target_node in transition.target.target_nodes :
+            to_append_list = [(target_node, True)]
+            for anc in target_node.getAncestors() :
+                if anc == LCA : #If we reach the LCA in the ancestor hierarchy we break
+                    break;
+                to_add = True;  #boolean value to see if the current ancestor should be added to the result
+                for enter_node_entry in transition.enter_nodes :
+                    if anc == enter_node_entry[0] :
+                        to_add = False #If we reach an ancestor in the hierarchy that is already listed as enter node, we don't add and break
+                        break
+                if to_add:
+                    to_append_list.append((anc, False)) #Only the first from the ancestor list should get True
+                else :
+                    break
+                    
+            to_append_list.reverse() #the enter sequence should be in the reverse order of the ancestor hierarchy
+            transition.enter_nodes.extend(to_append_list)
+
+        # Calculate arena
+        current = LCA
+        while not current.is_composite:
+            current = current.parent
+        transition.arena = current
+
+    def calculateLCA(self, transition):
+        """
+        Calculates the lowest common ancestor of a transition
+        """ 
+        for anc in transition.parent_node.getAncestors() :
+            all_descendants = True 
+            for node in transition.target.getNodes() :
+                if not node.isDescendantOf(anc) :
+                    all_descendants = False
+                    break
+            if all_descendants :
+                return anc
+        return None

+ 353 - 0
sccd/compiler/python_writer.py

@@ -0,0 +1,353 @@
+from sccd.compiler.generic_language_constructs import *
+
+class PythonWriter(GenericWriterBase):
+	def __init__(self, outputter):
+		self.out = outputter
+
+
+	def writeComment(self, text):
+		self.out.write("# " + text)
+
+	def writeMultiLineComment(self, text):
+		self.out.write("\"\"\"\n" + text + "\n\"\"\"")
+
+	def visit_AndOperator(self, a):
+		self.out.extendWrite(" and ")
+
+	def visit_ArrayContains(self, a):
+		array = a.getArrayExpression()
+		el = a.getElementExpression()
+
+		el.accept(self)
+		self.out.extendWrite(" in ")
+		array.accept(self)
+
+	def visit_ArrayExpression(self, a):
+		self.out.extendWrite("[")
+		self.writeCommaSeparated(a.getElements())
+		self.out.extendWrite("]")
+
+	def visit_ArrayIndexOf(self, a):
+		array = a.getArrayExpression()
+		el = a.getElementExpression()
+
+		array.accept(self)
+		self.out.extendWrite(".index(")
+		el.accept(self)
+		self.out.extendWrite(")")
+
+	def visit_ArrayLength(self, a):
+		self.out.extendWrite("len(")
+		a.getArrayExpression().accept(self)
+		self.out.extendWrite(")")
+
+	def visit_ArrayPushBack(self, a):
+		array = a.getArrayExpression()
+		el = a.getElementExpression()
+
+		array.accept(self)
+		self.out.extendWrite(".append(")
+		el.accept(self)
+		self.out.extendWrite(")")
+
+	def visit_AST(self, ast):
+		self.writeAll(ast.getEntries())
+
+	def visit_Block(self, b):
+		self.out.indent()
+		self.writeAll(b.getEntries())
+		if b.isEmpty():
+			self.out.write("pass")
+		self.out.dedent()
+
+	def visit_BreakStatement(self, b):
+		self.out.write("break")
+
+	def visit_Class(self, c):
+		class_name = c.getIdentifier()
+		constructor = c.getConstructor()
+		super_classes = c.getSuperClassIdentifierList()
+		description = c.getDescription()
+
+		self.out.write()
+		if description:
+			self.writeComment(description)
+		self.out.write("class " + class_name)
+		if super_classes:
+			self.out.extendWrite("(" + ", ".join(super_classes) + ")")
+		self.out.extendWrite(":")
+		self.out.indent()
+		constructor.accept(self)
+		self.writeAll(c.getMembers())
+		self.out.dedent()
+
+	def visit_Constructor(self, constructor):
+		#class_name = constructor.getClass().getIdentifier()
+		parameters = constructor.getFormalParameters()
+		body = constructor.getBody()
+
+		self.out.write("def __init__")
+		parameters.accept(self)
+		self.out.extendWrite(":")
+		body.accept(self)
+
+	def visit_Destructor(self, destructor):
+		#class_name = destructor.getClass().getIdentifier()
+		parameters = destructor.getFormalParameters()
+		body = destructor.getBody()
+
+		self.out.write("def __del__")
+		parameters.accept(self)
+		self.out.extendWrite(":")
+		body.accept(self)
+
+	def visit_ElseStatement(self, else_stmt):
+		self.out.write("else:")
+		else_stmt.getBody().accept(self)
+
+	def visit_ElseIfStatement(self, else_if):
+		condition = else_if.getCondition()
+		body = else_if.getBody()
+
+		if else_if.isFirst():
+			self.out.write("if ")
+		else:
+			self.out.write("elif ")
+		condition.accept(self)
+		self.out.extendWrite(":")
+		body.accept(self)
+
+	def visit_EqualsOperator(self, e):
+		self.out.extendWrite(" == ")
+
+	def visit_ExpressionStatement(self, stmt):
+		self.out.write() # expressions don't begin with a newline
+		stmt.expression.accept(self)
+
+	def visit_FalseExpression(self, f):
+		self.out.extendWrite("False")
+
+	def visit_FormalParameter(self, parameter):
+		self.out.extendWrite(parameter.getIdentifier())
+		if parameter.getDefaultValue():
+			self.out.extendWrite(" = None") # correct default value will be assigned in function body
+
+	def visit_FormalParameters(self, p):
+		params = [Literal("self")] + p.getParameterList()
+		self.writeTuple(params)
+
+	def visit_ForLoopCurrentElement(self, el):
+		#collection = el.getCollectionExpression()
+		iterator = el.getIteratorIdentifier()
+
+		self.out.extendWrite(iterator)
+
+	def visit_ForLoopIterateArray(self, loop):
+		collection = loop.getCollectionExpression()
+		iterator = loop.getIteratorIdentifier()
+		body = loop.getBody()
+
+		self.out.write("for " + iterator + " in ")
+		collection.accept(self)
+		self.out.extendWrite(":")
+		body.accept(self)
+
+	def visit_ForLoopIterateMapValues(self, loop):
+		collection = loop.getCollectionExpression()
+		iterator = loop.getIteratorIdentifier()
+		body = loop.getBody()
+
+		self.out.write("for " + iterator + " in ")
+		collection.accept(self)
+		self.out.extendWrite(".itervalues():")
+		body.accept(self)
+
+	def visit_IfStatement(self, if_stmt):
+		condition = if_stmt.getCondition()
+		body = if_stmt.getBody()
+
+		self.out.write("if ")
+		condition.accept(self)
+		self.out.extendWrite(":")
+		body.accept(self)
+
+	def visit_IncludeStatement(self, i):
+		module_path = i.getModulePath()
+		imported_symbols = i.getImportedSymbols()
+
+		self.out.write("from ")
+		for j in range(len(module_path)):
+			if j != 0:
+				self.out.extendWrite(".")
+			module_path[j].accept(self)
+		self.out.extendWrite(" import ")
+		if imported_symbols:
+			self.writeCommaSeparated(imported_symbols)
+		else:
+			self.out.extendWrite("*")
+
+	def visit_LocalVariableDeclaration(self, decl):
+		identifier = decl.getIdentifier()
+		init_value = decl.getInitValue()
+
+		self.out.extendWrite(decl.getIdentifier())
+		if init_value:
+			self.out.extendWrite(" = ")
+			init_value.accept(self)
+
+	def visit_LogStatement(self, l):
+		self.out.write("print \"" + l.getMessage() + "\"")
+
+	def visit_MapExpression(self, m):
+		elements = m.getElements()
+		self.out.extendWrite("{")
+		keys = list(elements.keys())
+		for i in range(len(keys)):
+			if i != 0:
+				self.out.extendWrite(", ")			
+			self.out.extendWrite(keys[i] + " : ")
+			self.out.extendWrite(" : ")
+			elements[keys[i]].accept(self)
+		self.out.extendWrite("}")
+
+	def visit_MapIndexedExpression(self, i):
+		m = i.getMapExpression()
+		key = i.getKeyExpression()
+
+		m.accept(self)
+		self.out.extendWrite("[")
+		key.accept(self)
+		self.out.extendWrite("]")
+
+	def visit_MapRemoveElement(self, stmt):
+		map_expr = stmt.getMapExpression()
+		key_expr = stmt.getKeyExpression()
+
+		self.out.write() # this is a statement, not an expression
+		map_expr.accept(self)
+		self.out.extendWrite(".pop(")
+		key_expr.accept(self)
+		self.out.extendWrite(", None)")
+
+	def visit_Method(self, method):
+		class_name = method.getClass().getIdentifier()
+		method_name = method.getIdentifier()
+		description = method.getDescription()
+		body = method.getBody()
+		parameters = method.getFormalParameters()
+
+		self.out.write()
+		if description:
+			self.writeComment(description)
+		self.out.write("def " + method_name + "")
+		parameters.accept(self)
+		self.out.extendWrite(":")
+		body.accept(self)
+
+	def visit_MethodBody(self, body):
+		method = body.getMethod()
+		formal_parameters = method.getFormalParameters()
+		formal_parameter_list = formal_parameters.getParameterList()
+
+		self.out.indent()
+		# check for undefined parameters and replace them with default values
+		for p in formal_parameter_list:
+			p_id = p.getIdentifier()
+			p_default = p.getDefaultValue()
+			if p_default:
+				self.out.write("if " + p_id + " == None: " + p_id + " = ")
+				p_default.accept(self)
+		self.writeAll(body.getEntries())
+		if body.isEmpty():
+			self.out.write("pass")
+		self.out.dedent()
+
+	def visit_NewExpression(self, new):
+		type_expr = new.getTypeExpression()
+		params = new.getActualParameters()
+
+		type_expr.accept(self)
+		params.accept(self)
+
+	def visit_NoneExpression(self, n):
+		self.out.extendWrite("None")
+
+	def visit_NotOperator(self, n):
+		self.out.extendWrite("not ")
+
+	def visit_OrOperator(self, o):
+		self.out.extendWrite(" or ")
+
+	def visit_Package(self, package):
+		name = package.getIdentifier()
+		description = package.getDescription()
+
+		self.writeComment("package \"" + name + "\"")
+		if description:
+			self.writeComment(description)
+		self.writeAll(package.getDeclarations())
+
+	def visit_ReturnStatement(self, r):
+		self.out.write("return ")
+		r.getExpression().accept(self)
+
+	def visit_RuntimeModuleIdentifier(self, r):
+		self.out.extendWrite("sccd.runtime")
+
+	def visit_SelfExpression(self, s):
+		self.out.extendWrite("self")
+
+	def visit_StaticAttribute(self, attr):
+		name = attr.getIdentifier()
+		init_value = attr.getInitValue()
+		#class_name = attr.getClass().getIdentifier()
+
+		if init_value:
+			self.out.write(name + " = ")
+			init_value.accept(self)
+		else:
+			self.out.write(name + " = None")
+
+	def visit_SuperClassConstructorCall(self, call):
+		super_class = call.getSuperClassIdentifier()
+		params = call.getActualParameters()
+		param_list = [Literal("self")] + params.getParameterList()
+		params = ActualParameters(param_list)
+
+		self.out.extendWrite(super_class)
+		self.out.extendWrite(".__init__")
+		params.accept(self)
+
+	def visit_SuperClassDestructorCall(self, call):
+		super_class = call.getSuperClassIdentifier()
+		params = call.getActualParameters()
+		param_list = [Literal("self")] + params.getParameterList()
+		params = ActualParameters(param_list)
+
+		self.out.extendWrite("if hasattr(")
+		self.out.extendWrite(super_class)
+		self.out.extendWrite(", \"__del__\"):")
+		self.out.indent()
+		self.out.write(super_class)
+		self.out.extendWrite(".__del__")
+		params.accept(self)
+		self.out.dedent()
+
+	def visit_SuperClassMethodCall(self, call):
+		super_class = call.getSuperClassIdentifier()
+		method_name = call.getMethodIdentifier()
+		params = call.getActualParameters()
+		param_list = [Literal("self")] + params.getParameterList()
+		params = ActualParameters(param_list)
+
+		self.out.extendWrite(super_class + "." + method_name)
+		params.accept(self)
+
+	def visit_ThrowExceptionStatement(self, stmt):
+		self.out.write("raise Exception(")
+		stmt.getExpression().accept(self)
+		self.out.extendWrite(")")
+
+	def visit_TrueExpression(self, t):
+		self.out.extendWrite("True")
+

File diff suppressed because it is too large
+ 1169 - 0
sccd/compiler/sccd_constructs.py


+ 145 - 0
sccd/compiler/sccdc.py

@@ -0,0 +1,145 @@
+import argparse
+import os
+import sys
+
+from sccd.compiler.generic_generator import GenericGenerator, Platforms
+from sccd.compiler.DEVS_generator import DEVSGenerator
+
+from sccd.compiler.utils import Enum, Logger, FileWriter
+from sccd.compiler.super_class_linker import SuperClassLinker
+from sccd.compiler.state_linker import StateLinker
+from sccd.compiler.path_calculator import PathCalculator
+from sccd.compiler.sccd_constructs import ClassDiagram
+from sccd.compiler.generic_language_constructs import GenericConstruct
+from sccd.compiler.compiler_exceptions import CompilerException
+
+from sccd.compiler.javascript_writer import JavascriptWriter
+from sccd.compiler.python_writer import PythonWriter
+
+def generate(input_file, output_file, target_language, platform):
+	sccd = xmlToSccd(input_file)
+
+	if not target_language:
+		if sccd.language:
+			target_language = sccd.language
+		else:
+			target_language = "python" # default
+	elif sccd.language and target_language != sccd.language:
+		Logger.showError("Diagram specifies target language as \"" + sccd.language + "\", but language option of compiler has been set to \"" + target_language + "\". No output has been generated.")
+		return
+
+	if (target_language == "python" or target_language == "pypDEVS") and not output_file.endswith(".py") :
+		output_file += ".py"
+	elif target_language == "javascript" and not output_file.endswith(".js") :
+		output_file += ".js"
+
+	if target_language == "pypDEVS":
+		generic = sccdToDEVS(sccd, platform)
+	else:
+		generic = sccdToGeneric(sccd, platform)
+	genericToTarget(generic, target_language, output_file)
+
+def xmlToSccd(xml_file):
+	cd = ClassDiagram(xml_file) # create AST
+	cd.accept(SuperClassLinker())
+	# SuperClassLinker().visit(cd) # visitor linking super classs references
+	StateLinker().visit(cd) # visitor fixing state references
+	PathCalculator().visit(cd) # visitor calculating paths
+	return cd
+	
+def sccdToGeneric(sccd, platform):
+	succesfull_generation = False
+	generator = GenericGenerator(platform)
+	sccd.accept(generator)
+	generic = generator.get()
+	Logger.showInfo("Classes <" + ", ".join(sccd.class_names) + "> have been converted to generic language constructs.")
+	return generic
+
+def sccdToDEVS(sccd, platform):
+	succesfull_generation = False
+	generator = DEVSGenerator(platform)
+	sccd.accept(generator)
+	generic = generator.get()
+	Logger.showInfo("Classes <" + ", ".join(sccd.class_names) + "> have been converted to DEVS language constructs.")
+	return generic
+
+def genericToTarget(generic, target_language, output_file):
+	try:
+		f = FileWriter(output_file)
+		if target_language == "javascript":
+			writer = JavascriptWriter(f)
+		elif target_language == "python":
+			writer = PythonWriter(f)
+		elif target_language == "pypDEVS":
+			writer = PythonWriter(f)
+		else:
+			raise Exception("Language not supported")
+		generic.accept(writer)
+		Logger.showInfo("Generic language constructs have been converted to target language constructs and have been written to file '" + output_file + "'.")
+	finally:
+		f.close()
+		
+def main():
+	parser = argparse.ArgumentParser(prog="python -m sccd.compiler.sccdc")
+	parser.add_argument('input', help='The path to the XML file to be compiled.')
+	parser.add_argument('-o', '--output', type=str, help='The path to the generated code. Defaults to the same name as the input file but with matching extension.')
+	parser.add_argument('-v', '--verbose', type=int, help='2 = all output; 1 = only warnings and errors; 0 = only errors; -1 = no output.  Defaults to 2.', default = 2)
+	parser.add_argument('-p', '--platform', type=str, help="Let the compiled code run on top of threads, gameloop or eventloop. The default is eventloop.")
+	parser.add_argument('-l', '--language', type=str, help='Target language, either "javascript" or "python". Defaults to the latter.')
+	
+	args = vars(parser.parse_args())
+	#Set verbose
+	if args['verbose'] is not None:
+		if args['verbose'] in [-1, 0,1,2] :
+			Logger.verbose = args['verbose']
+		else :
+			Logger.showError("Invalid verbose argument.")
+	else :
+		Logger.verbose = 2
+
+	#Set source file
+	source = args['input']
+	if not source.endswith(".xml") :
+		Logger.showError("Input file not valid.")
+		return
+	
+	#Set target language
+	if args['language'] :
+		target_language = args['language']
+	else :
+		target_language = ""
+
+	#Set output file
+	if args['output'] :
+		output = args['output']
+	else:
+		output = os.path.splitext(os.path.split(source)[1])[0]
+		
+	#Set platform	
+	if args['platform'] :
+		args['platform'] = args['platform'].lower()
+		if args['platform'] == "threads" :
+			platform = Platforms.Threads
+		elif args['platform'] == "gameloop" :
+			platform = Platforms.GameLoop
+		elif args['platform'] == "eventloop" :
+			platform = Platforms.EventLoop
+		else :
+			Logger.showError("Invalid platform.")
+			return		  
+	else :
+		platform = Platforms.EventLoop
+		
+	#Compile	
+	try :
+		generate(source, output, target_language, platform)
+	except CompilerException as exception :
+		Logger.showError(str(exception));
+		return 1
+
+	return 0
+
+if __name__ == "__main__":
+	sys.exit(main())
+
+

+ 157 - 0
sccd/compiler/state_linker.py

@@ -0,0 +1,157 @@
+from sccd.compiler.visitor import Visitor
+from sccd.compiler.sccd_constructs import INSTATE_SEQ
+from sccd.compiler.compiler_exceptions import CompilerException
+from sccd.compiler.lexer import Lexer, Token, TokenType
+
+class StateReferenceException(CompilerException):
+    pass
+
+class StateLinker(Visitor):
+    
+    def __init__(self):
+        self.visiting_statechart = None
+        self.visiting_node = None
+        self.lexer = Lexer()
+    
+    def visit_ClassDiagram(self, class_diagram): 
+        for c in class_diagram.classes :
+            c.accept(self)
+
+    def visit_Class(self, c):
+        if c.statechart:
+            c.statechart.accept(self)
+        
+    def visit_StateChart(self, statechart):
+        self.visiting_statechart = statechart
+        for node in statechart.basics + statechart.composites:
+            node.accept(self)
+                     
+    def visit_StateChartNode(self, node):
+        self.visiting_node = node
+        node.enter_action.accept(self)
+        node.exit_action.accept(self)
+        for transition in node.transitions :
+            transition.accept(self)
+            
+    def visit_StateChartTransition(self, transition):
+        try :
+            transition.target.accept(self)
+        except StateReferenceException as exception :
+            raise StateReferenceException("Transition from <" + self.visiting_node.full_name + "> has invalid target. " + exception.message)
+        try :
+            transition.action.accept(self)
+        except StateReferenceException as exception :
+            raise StateReferenceException("Transition from <" + self.visiting_node.full_name + "> has invalid action. " + exception.message)
+        try :
+            if transition.guard :
+                transition.guard.accept(self)
+        except StateReferenceException as exception :
+            raise StateReferenceException("Transition from <" + self.visiting_node.full_name  + "> has invalid guard. " + exception.message)
+        
+    def visit_StateReference(self, state_reference):
+        state_reference.target_nodes = []
+        
+        current_node = None #Will be used to find the target state(s)
+        split_stack = [] #used for branching
+
+        self.lexer.input(state_reference.path_string)
+
+        for token in self.lexer.tokens() :
+            
+            if current_node == None : #current_node is not set yet or has been reset, the CHILD token can now have a special meaning
+                if token.type == TokenType.SLASH :
+                    #Root detected
+                    current_node = self.visiting_statechart.root
+                    #Token consumed so continue
+                    continue
+                else :
+                    current_node = self.visiting_node
+                    
+            if token.type == TokenType.DOT :
+                #Advance to next token
+                token = self.lexer.nextToken()
+                
+                if token is None or token.type == TokenType.SLASH :
+                    #CURRENT operator "." detected
+                    continue
+                elif token.type == TokenType.DOT :
+                    #Advance to next token
+                    token = self.lexer.nextToken()
+                    if token is None or token.type == TokenType.SLASH :
+                        #PARENT operator ".." detected
+                        current_node = current_node.parent
+                        if current_node is None :
+                            raise StateReferenceException("Illegal use of PARENT \"..\" operator at position " + str(token.pos) + " in state reference. Root of statechart reached.")
+                    
+                    else :
+                        raise StateReferenceException("Illegal use of PARENT \"..\" operator at position " + str(token.pos) + " in state reference.")
+    
+                else :
+                    raise StateReferenceException("Illegal use of CURRENT \".\" operator at position " + str(token.pos) + " in state reference.")
+                    
+            elif token.type == TokenType.SLASH :
+                continue
+            elif token.type == TokenType.WORD :
+                #try to advance to next child state
+                cname = token.val
+                found = False
+                for child in current_node.children :
+                    if child.name == cname : 
+                        found = True
+                        current_node = child
+                        break
+                if not found :
+                    raise StateReferenceException("Refering to non existing node " + cname + " at position " + str(token.pos) + " in state reference.")
+            elif token.type == TokenType.LBRACKET :
+                split_stack.append(current_node)
+            elif token.type == TokenType.RBRACKET :
+                if len(split_stack) > 0 :
+                    split_stack.pop()
+                else :
+                    raise StateReferenceException("Invalid token at position " + str(token.pos) + " in state reference.")
+            elif token.type == TokenType.COMMA :
+                state_reference.target_nodes.append(current_node)
+                if len(split_stack) > 0 :
+                    current_node = split_stack[-1]
+                else :
+                    current_node = None
+            
+            else :
+                raise StateReferenceException("Invalid token at position " + str(token.pos) + " in state reference.")
+        
+        if (len(split_stack) != 0) or (current_node is None) : #RB missing or extra COMMA
+            raise StateReferenceException("State reference ends unexpectedly.")
+        
+        #TODO better validation of the target! When is it a valid state configuration?
+        for node in state_reference.target_nodes :
+            if current_node == node :
+                raise StateReferenceException("A state reference can't reference the same node more than once.")
+            if node.isDescendantOrAncestorOf(current_node) :
+                raise StateReferenceException("A state reference can't reference a node and one of its descendants.");
+        
+        state_reference.target_nodes.append(current_node)
+            
+        if len(state_reference.target_nodes) == 0 :
+            raise StateReferenceException("Meaningless state reference.")
+
+    def visit_Expression(self, expression):
+        for part in expression.expression_parts :
+            part.accept(self)
+
+    def visit_EnterExitAction(self, action):
+        if action.action :
+            action.action.accept(self)
+            
+    def visit_Action(self, action):
+        for subaction in action.sub_actions :
+            subaction.accept(self)
+            
+    def visit_InStateCall(self, call):
+        try:
+            for target in call.targets:
+                target.accept(self)
+        except StateReferenceException:
+            raise StateReferenceException("Invalid state reference for " + INSTATE_SEQ + " call.")
+        
+    def visit_Assign(self, assign):
+        assign.expression.accept(self)

+ 221 - 0
sccd/compiler/stateful_writer.py

@@ -0,0 +1,221 @@
+# Used by generic_generator to create an AST of generic language constructs
+# while visiting an AST of SCCD constructs
+
+from sccd.compiler.generic_language_constructs import *
+
+class ExpressionWrapper(SimpleExpression, AbstractList):
+	def __init__(self, expr = None):
+		self.expr = expr
+
+	def add(self, expr):
+		if self.expr:
+			raise Exception("Expression can only be set once.")
+		self.expr = expr
+
+	def get(self):
+		return self.expr
+
+
+class StatefulWriter:
+	def __init__(self):
+		self.ast = AST()
+		self.last = None
+		self.stack = [self.ast]
+
+	def get(self):
+		return self.stack[-1]
+
+	def startRecordingExpression(self):
+		self.stack.append(ExpressionWrapper())
+
+	def stopRecordingExpression(self):
+		self.last = self.stack.pop()
+		if not isinstance(self.last, ExpressionWrapper):
+			raise Exception("Assymetry detected.")
+		return self.last.get()
+
+	def add(self, block_entry):
+		self.get().add(block_entry)
+        
+
+	#### SHORTHANDS ####
+
+	def addActualParameter(self, expr):
+		self.get().getActualParameters().add(expr)
+
+	def addAssignment(self, lhs, rhs):
+		self.add(AssignmentExpression(lhs, rhs))
+
+	def addInclude(self, module_path, symbols = None):
+		self.add(IncludeStatement(module_path, symbols))
+
+	def addComment(self, comment):
+		self.add(SingleLineComment(comment))
+
+	def addFormalParameter(self, parameter, default_value = None):
+		self.get().getFormalParameters().add(FormalParameter(parameter, default_value))
+
+	def addMultiLineComment(self, comment):
+		self.add(MultiLineComment(comment))
+
+	def addRawCode(self, raw):
+		self.add(RawCode(raw))
+
+	def addStaticAttribute(self, identifier, init_value):
+		self.add(StaticAttribute(self.get(), identifier, init_value))
+
+	def addVSpace(self):
+		self.add(VSpace())
+
+
+	#### STATEFUL OPERATIONS ####
+
+	def begin(self, generic_construct):
+		self.add(generic_construct)
+		self.stack.append(generic_construct)
+
+	def beginArray(self):
+		self.begin(ArrayExpression())
+
+	def beginClass(self, class_name, super_class_names = None, comment = None):
+		self.begin(Class(class_name, super_class_names, comment))
+
+	def beginConstructor(self):
+		c = self.get().getConstructor()
+		self.stack.append(c)
+
+	def beginDestructor(self):
+		d = self.get().getDestructor()
+		self.stack.append(d)
+
+	def beginElse(self):
+		self.begin(ElseStatement())
+
+	def beginElseIf(self, condition):
+		self.begin(ElseIfStatement(condition, not isinstance(self.last, ElseIfStatement)))
+
+	def beginForLoopIterateArray(self, array_expr, iterator_identifier):
+		f = ForLoopIterateArray(array_expr, iterator_identifier)
+		self.get().add(f)
+		self.stack.append(f.getBody())
+
+	def beginForLoopIterateMapValues(self, map_expr, iterator_identifier):
+		f = ForLoopIterateMapValues(map_expr, iterator_identifier)
+		self.get().add(f)
+		self.stack.append(f.getBody())
+
+	def beginFunctionCall(self, function_expr):
+		f = FunctionCall(function_expr)
+		self.get().add(f)
+		self.stack.append(f)
+
+	def beginGlue(self):
+		g = Glue()
+		self.get().add(g)
+		self.stack.append(g)
+
+	def beginIf(self, condition):
+		self.begin(IfStatement(condition))
+
+
+	def beginMethod(self, name, comment = None):
+		m = Method(self.get(), name, comment)
+		self.get().add(m)
+		self.stack.append(m)
+
+	def beginMethodBody(self):
+		b = self.get().getBody()
+		self.stack.append(b)
+
+	def beginPackage(self, package_name):
+		p = Package(package_name)
+		self.get().add(p)
+		self.stack.append(p)
+
+	def beginSuperClassConstructorCall(self, super_class_identifier):
+		c = SuperClassConstructorCall(super_class_identifier)
+		self.get().add(c)
+		self.stack.append(c)
+
+	def beginSuperClassDestructorCall(self, super_class_identifier):
+		c = SuperClassDestructorCall(super_class_identifier)
+		self.get().add(c)
+		self.stack.append(c)
+
+	def beginSuperClassMethodCall(self, super_class_identifier, method_identifier):
+		c = SuperClassMethodCall(super_class_identifier, method_identifier)
+		self.get().add(c)
+		self.stack.append(c)
+
+
+	def end(self):
+		self.stack.pop()
+
+	def endArray(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, ArrayExpression)
+
+	def endClass(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, Class)
+
+	def endConstructor(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, Constructor)
+
+	def endDestructor(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, Destructor)
+
+	def endElse(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, ElseStatement)
+
+	def endElseIf(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, ElseIfStatement)
+
+	def endForLoopIterateArray(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, ForLoopBody)
+
+	def endForLoopIterateMapValues(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, ForLoopBody)
+
+	def endFunctionCall(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, FunctionCall)
+
+	def endGlue(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, Glue)
+
+	def endIf(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, IfStatement)
+
+	def endMethod(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, Method)
+
+	def endMethodBody(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, MethodBody)
+
+	def endPackage(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, Package)
+
+	def endSuperClassConstructorCall(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, SuperClassConstructorCall)
+
+	def endSuperClassDestructorCall(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, SuperClassDestructorCall)
+
+	def endSuperClassMethodCall(self):
+		self.last = self.stack.pop()
+		assert isinstance(self.last, SuperClassMethodCall)
+

+ 52 - 0
sccd/compiler/super_class_linker.py

@@ -0,0 +1,52 @@
+from sccd.compiler.compiler_exceptions import *
+from sccd.compiler.utils import Logger
+from sccd.compiler.visitor import Visitor
+
+class SuperClassLinker(Visitor):
+	""" Computes the states that must be exited and entered for a specific transition if the system is to make
+		that transition. 
+	"""
+	
+	def visit_ClassDiagram(self, class_diagram): 
+		for c in class_diagram.classes :
+			c.accept(self)
+
+	def visit_Class(self, c):
+		# replace super class names by super class objects
+		for s in c.super_classes:
+			super_class = None
+			for clas in c.class_diagram.classes:
+				if clas.name == s:
+					super_class = clas
+			if super_class == None:
+				Logger.showWarning("Class <" + c.name + "> has undefined super class <" + s + ">.")
+			else:
+				c.super_class_objs[s] = super_class
+
+		# calculate list of abstract methods
+		c.abstract_method_names = getClassAbstractMethodNames(c)
+
+		# check if <super> tags exist for all inherited classes
+		for name,obj in list(c.super_class_objs.items()):
+			if obj:
+				if name not in c.constructors[0].super_class_parameters:
+					num_params = len(obj.constructors[0].parameters)
+					if num_params > 0:
+						raise CompilerException("Class <" + c.name + "> inherits <" + name + "> and <" + name + ">'s constructor has " + str(num_params) + " parameter(s), but there's no <super> entry for that class in <" + c.name + ">'s constructor.")
+
+def getClassAbstractMethodNames(c):
+	abstract_method_names = []
+	non_abstract_method_names = []
+	for m in c.methods:
+		if m.isAbstract():
+			abstract_method_names.append(m.name)
+		else:
+			non_abstract_method_names.append(m.name)
+	for s in c.super_classes:
+		if s in c.super_class_objs:
+			super_abstract_method_names = getClassAbstractMethodNames(c.super_class_objs[s])
+			for m_name in super_abstract_method_names:
+				if m_name not in non_abstract_method_names:
+					abstract_method_names.append(m_name)
+	return abstract_method_names
+

+ 119 - 0
sccd/compiler/utils.py

@@ -0,0 +1,119 @@
+from sys import stdout
+
+from sccd.compiler.compiler_exceptions import CodeBlockException
+
+class Logger(object):
+	verbose = 0 #-1= no output
+				#0 = only errors
+				#1 = only warnings and errors
+				#2 = all output
+				
+	@staticmethod   
+	def showError(error):
+		if(Logger.verbose > -1) :
+			print("ERROR : " + error)
+				
+	@staticmethod
+	def showWarning(warning):
+		if(Logger.verbose > 0) :
+			print("WARNING : " + warning)
+			
+	@staticmethod	
+	def showInfo(info):
+		if(Logger.verbose > 1) :
+			print("INFO : " + info)
+
+#######################
+
+class Enum():	
+	def __init__(self, *entries):
+		self._keys = entries
+		self._map = {}
+		for v,k in enumerate(self._keys) :
+			self._map[k] = v
+			
+	def __getattr__(self, name):
+		return self._map[name]
+			
+	def name_of(self, index):
+		return self._keys[index]
+
+#######################
+
+NOT_SET = 0
+SPACES_USED = 1
+TABS_USED = 2
+
+class FormattedWriter:
+
+	def __init__(self, out = stdout):
+		self.out = out
+		self.indentLevel = 0
+		self.indentSpace = "    "
+		self.first_write = True
+
+	def write(self, text = ""):
+		if self.first_write :
+			self.first_write = False
+			if text == "":
+				self.out.write(self.indentLevel*self.indentSpace)
+			else:
+				self.out.write(self.indentLevel*self.indentSpace + text)  
+		else:
+			if text == "":
+				self.out.write("\n" + self.indentLevel*self.indentSpace)
+			else:
+				self.out.write("\n" + self.indentLevel*self.indentSpace + text)
+	
+	def extendWrite(self, text = ""):
+		self.out.write(text)
+				
+	def indent(self):
+		self.indentLevel+=1
+
+	def dedent(self):
+		self.indentLevel-=1
+
+	def writeCodeCorrectIndent(self, body):
+		lines = body.split('\n')
+		while( len(lines) > 0 and lines[-1].strip() == "") :
+			del(lines[-1])
+	
+		index = 0;
+		while( len(lines) > index and lines[index].strip() == "") :	   
+			index += 1
+			
+		if index >= len(lines) :
+			return
+		#first index where valid code is present
+		to_strip_index = len(lines[index].rstrip()) - len(lines[index].strip()) 
+		indent_type = NOT_SET;
+			
+		while index < len(lines):
+			strip_part = lines[index][:to_strip_index]
+			
+			if( ('\t' in strip_part and ' ' in strip_part) or
+				(indent_type == SPACES_USED and '\t' in strip_part) or
+				(indent_type == TABS_USED and ' ' in strip_part)
+			) :
+				raise CodeBlockException("Mixed tab and space indentation!")
+			
+			if indent_type == NOT_SET :
+				if ' ' in strip_part :
+					indent_type = SPACES_USED
+				elif '\t' in strip_part :
+					indent_type = TABS_USED
+					
+			self.write(lines[index][to_strip_index:])
+			index += 1
+
+
+class FileWriter(FormattedWriter):
+
+	def __init__(self, filename):
+		FormattedWriter.__init__(self, open(filename, 'w'))
+
+	def close(self):
+		self.out.close()
+
+#######################

+ 30 - 0
sccd/compiler/visitor.py

@@ -0,0 +1,30 @@
+class Visitor(object):
+	def _visit(self, node, prepend, *args):
+		prepend = prepend + "_"
+		meth = None
+		for cls in node.__class__.__mro__:
+			meth_name = prepend + cls.__name__
+			meth = getattr(self, meth_name, None)
+			if meth:
+				break
+
+		if not meth:
+			meth = self.generic_visit
+		return meth(node, *args)
+	
+	def visit(self, node, *args):
+		self._visit(node, "visit", *args)
+	
+	def enter(self, node, *args):
+		self._visit(node, "enter", *args)
+		
+	def exit(self, node, *args):
+		self._visit(node, "exit", *args)
+
+	def generic_visit(self, node):
+		#print 'generic_visit '+node.__class__.__name__
+		pass
+		
+class Visitable(object):
+	def accept(self, visitor):
+		visitor.visit(self) 

+ 677 - 0
sccd/runtime/DEVS_statecharts_core.py

@@ -0,0 +1,677 @@
+import sys
+
+from sccd.runtime.event_queue import EventQueue
+from pypdevs.infinity import INFINITY
+
+DEBUG = False
+ELSE_GUARD = "ELSE_GUARD"
+
+def print_debug(msg):
+    if DEBUG:
+        print(msg)
+
+class RuntimeException(Exception):
+    """
+    Base class for runtime exceptions.
+    """
+    def __init__(self, message):
+        self.message = message
+    def __str__(self):
+        return repr(self.message)
+
+
+class AssociationException(RuntimeException):
+    """
+    Exception class thrown when an error occurs in a CRUD operation on associations.
+    """
+    pass
+
+
+class Association(object):
+    """
+    Class representing an SCCD association.
+    """
+
+    def __init__(self, to_class, min_card, max_card):
+        """
+        Constructor
+
+       :param to_class: the name of the target class
+       :param min_card: the minimal cardinality
+       :param max_card: the maximal cardinality
+        """
+        self.to_class = to_class
+        self.min_card = min_card
+        self.max_card = max_card
+        self.instances = {}  # maps index (as string) to instance
+        self.instances_to_ids = {}
+        self.size = 0
+        self.next_id = 0
+
+    def allowedToAdd(self):
+        return self.max_card == -1 or self.size < self.max_card
+
+    def allowedToRemove(self):
+        return self.min_card == -1 or self.size > self.min_card
+
+    def addInstance(self, instance):
+        if self.allowedToAdd():
+            new_id = self.next_id
+            self.next_id += 1
+            self.instances[new_id] = instance
+            self.instances_to_ids[instance] = new_id
+            self.size += 1
+            return new_id
+        else:
+            raise AssociationException("Not allowed to add the instance to the association.")
+
+    def removeInstance(self, instance):
+        if self.allowedToRemove():
+            index = self.instances_to_ids[instance]
+            del self.instances[index]
+            del self.instances_to_ids[instance]
+            self.size -= 1
+            return index
+        else:
+            raise AssociationException("Not allowed to remove the instance from the association.")
+
+    def getInstance(self, index):
+        try:
+            return self.instances[index]
+        except IndexError:
+            raise AssociationException("Invalid index for fetching instance(s) from association.")
+
+
+class StatechartSemantics:
+    # Big Step Maximality
+    TakeOne = 0
+    TakeMany = 1
+    # Concurrency - not implemented yet
+    Single = 0
+    Many = 1
+    # Preemption - not implemented yet
+    NonPreemptive = 0
+    Preemptive = 1
+    # Internal Event Lifeline
+    Queue = 0
+    NextSmallStep = 1
+    NextComboStep = 2
+    # Input Event Lifeline
+    Whole = 0
+    FirstSmallStep = 1
+    FirstComboStep = 2
+    # Priority
+    SourceParent = 0
+    SourceChild = 1
+
+    # TODO: add Memory Protocol options
+
+    def __init__(self):
+        # default semantics:
+        self.big_step_maximality = self.TakeMany
+        self.internal_event_lifeline = self.Queue
+        self.input_event_lifeline = self.FirstComboStep
+        self.priority = self.SourceParent
+        self.concurrency = self.Single
+
+
+class State:
+    def __init__(self, state_id, name, obj):
+        self.state_id = state_id
+        self.name = name
+        self.obj = obj
+
+        self.ancestors = []
+        self.descendants = []
+        self.descendant_bitmap = 0
+        self.children = []
+        self.parent = None
+        self.enter = None
+        self.exit = None
+        self.default_state = None
+        self.transitions = []
+        self.history = []
+        self.has_eventless_transitions = False
+
+    def getEffectiveTargetStates(self):
+        targets = [self]
+        if self.default_state:
+            targets.extend(self.default_state.getEffectiveTargetStates())
+        return targets
+
+    def fixTree(self):
+        for c in self.children:
+            if isinstance(c, HistoryState):
+                self.history.append(c)
+            c.parent = self
+            c.ancestors.append(self)
+            c.ancestors.extend(self.ancestors)
+            c.fixTree()
+        self.descendants.extend(self.children)
+        for c in self.children:
+            self.descendants.extend(c.descendants)
+        for d in self.descendants:
+            self.descendant_bitmap |= 2 ** d.state_id
+
+    def addChild(self, child):
+        self.children.append(child)
+
+    def addTransition(self, transition):
+        self.transitions.append(transition)
+
+    def setEnter(self, enter):
+        self.enter = enter
+
+    def setExit(self, exit):
+        self.exit = exit
+
+    def __repr__(self):
+        return "State(%s)" % (self.state_id)
+
+
+class HistoryState(State):
+    def __init__(self, state_id, name, obj):
+        State.__init__(self, state_id, name, obj)
+
+
+class ShallowHistoryState(HistoryState):
+    def __init__(self, state_id, name, obj):
+        HistoryState.__init__(self, state_id, name, obj)
+
+    def getEffectiveTargetStates(self):
+        if self.state_id in self.obj.history_values:
+            targets = []
+            for hv in self.obj.history_values[self.state_id]:
+                targets.extend(hv.getEffectiveTargetStates())
+            return targets
+        else:
+            # TODO: is it correct that in this case, the parent itself is also entered?
+            return self.parent.getEffectiveTargetStates()
+
+
+class DeepHistoryState(HistoryState):
+    def __init__(self, state_id, name, obj):
+        HistoryState.__init__(self, state_id, name, obj)
+
+    def getEffectiveTargetStates(self):
+        if self.state_id in self.obj.history_values:
+            return self.obj.history_values[self.state_id]
+        else:
+            # TODO: is it correct that in this case, the parent itself is also entered?
+            return self.parent.getEffectiveTargetStates()
+
+
+class ParallelState(State):
+    def __init__(self, state_id, name, obj):
+        State.__init__(self, state_id, name, obj)
+
+    def getEffectiveTargetStates(self):
+        targets = [self]
+        for c in self.children:
+            if not isinstance(c, HistoryState):
+                targets.extend(c.getEffectiveTargetStates())
+        return targets
+
+
+class Transition:
+    def __init__(self, obj, source, targets):
+        self.guard = None
+        self.action = None
+        self.trigger = None
+        self.source = source
+        self.targets = targets
+        self.obj = obj
+        self.enabled_event = None  # the event that enabled this transition
+        self.optimize()
+
+    def isEnabled(self, events, enabled_transitions):
+        if self.trigger is None:
+            self.enabled_event = None
+            return (self.guard is None) or (self.guard == ELSE_GUARD and not enabled_transitions) or self.guard([])
+        else:
+            for event in events:
+                if (self.trigger.name == event.name and (
+                        not self.trigger.port or self.trigger.port == event.port)) and (
+                        (self.guard is None) or (self.guard == ELSE_GUARD and not enabled_transitions) or self.guard(
+                        event.parameters)):
+                    self.enabled_event = event
+                    return True
+
+    # @profile
+    def fire(self):
+        # exit states...
+        exit_set = self.__exitSet()
+        for s in exit_set:
+            # remember which state(s) we were in if a history state is present
+            for h in s.history:
+                f = lambda s0: s0.ancestors and s0.parent == s
+                if isinstance(h, DeepHistoryState):
+                    f = lambda s0: not s0.descendants and s0 in s.descendants
+                self.obj.history_values[h.state_id] = list(filter(f, self.obj.configuration))
+        for s in exit_set:
+            print_debug('EXIT: %s::%s' % (self.obj.__class__.__name__, s.name))
+            self.obj.eventless_states -= s.has_eventless_transitions
+            # execute exit action(s)
+            if s.exit:
+                s.exit()
+            self.obj.configuration_bitmap &= ~2 ** s.state_id
+
+        # combo state changed area
+        self.obj.combo_step.changed_bitmap |= 2 ** self.lca.state_id
+        self.obj.combo_step.changed_bitmap |= self.lca.descendant_bitmap
+
+        # execute transition action(s)
+        if self.action:
+            self.action(self.enabled_event.parameters if self.enabled_event else [])
+
+        # enter states...
+        targets = self.__getEffectiveTargetStates()
+        enter_set = self.__enterSet(targets)
+        for s in enter_set:
+            print_debug('ENTER: %s::%s' % (self.obj.__class__.__name__, s.name))
+            self.obj.eventless_states += s.has_eventless_transitions
+            self.obj.configuration_bitmap |= 2 ** s.state_id
+            # execute enter action(s)
+            if s.enter:
+                s.enter()
+
+        if self.obj.eventless_states:
+            self.obj.controller.object_manager.eventless.add(self.obj)
+        else:
+            self.obj.controller.object_manager.eventless.discard(self.obj)
+
+        try:
+            self.obj.configuration = self.obj.config_mem[self.obj.configuration_bitmap]
+        except:
+            self.obj.configuration = self.obj.config_mem[self.obj.configuration_bitmap] = sorted(
+                [s for s in list(self.obj.states.values()) if 2 ** s.state_id & self.obj.configuration_bitmap],
+                key=lambda s: s.state_id)
+        self.enabled_event = None
+
+    def __getEffectiveTargetStates(self):
+        targets = []
+        for target in self.targets:
+            for e_t in target.getEffectiveTargetStates():
+                if not e_t in targets:
+                    targets.append(e_t)
+        return targets
+
+    def __exitSet(self):
+        return [s for s in reversed(self.lca.descendants) if (s in self.obj.configuration)]
+
+    def __enterSet(self, targets):
+        target = targets[0]
+        for a in reversed(target.ancestors):
+            if a in self.source.ancestors:
+                continue
+            else:
+                yield a
+        for target in targets:
+            yield target
+
+    def setGuard(self, guard):
+        self.guard = guard
+
+    def setAction(self, action):
+        self.action = action
+
+    def setTrigger(self, trigger):
+        self.trigger = trigger
+        if self.trigger is None:
+            self.source.has_eventless_transitions = True
+
+    def optimize(self):
+        # the least-common ancestor can be computed statically
+        if self.source in self.targets[0].ancestors:
+            self.lca = self.source
+        else:
+            self.lca = self.source.parent
+            target = self.targets[0]
+            if self.source.parent != target.parent:  # external
+                for a in self.source.ancestors:
+                    if a in target.ancestors:
+                        self.lca = a
+                        break
+
+    def __repr__(self):
+        return "Transition(%s, %s)" % (self.source, self.targets[0])
+
+
+class Event(object):
+    def __init__(self, event_name, port="", parameters=[]):
+        self.name = event_name
+        self.parameters = parameters
+        self.port = port
+
+    # for comparisons in heaps
+    def __lt__(self, other):
+        s = str(self.name) + str(self.parameters) + str(self.port)
+        return len(s)
+
+    def getName(self):
+        return self.name
+
+    def getPort(self):
+        return self.port
+
+    def getParameters(self):
+        return self.parameters
+
+    def __repr__(self):
+        representation = "(event name: " + str(self.name) + "; port: " + str(self.port)
+        if self.parameters:
+            representation += "; parameters: " + str(self.parameters)
+        representation += ")"
+        return representation
+
+
+class RuntimeClassBase(object):
+
+    def __init__(self, controller):
+        self.events = EventQueue()
+
+        self.active = False
+
+        self.controller = controller
+        self.__set_stable(True)
+        self.inports = {}
+        self.outports = {}
+        self.timers = {}
+        self.states = {}
+        self.eventless_states = 0
+        self.configuration_bitmap = 0
+        self.transition_mem = {}
+        self.config_mem = {}
+
+        self.narrow_cast_port = self.controller.addInputPort("<narrow_cast>", self)
+
+        self.semantics = StatechartSemantics()
+
+    # to break ties in the heap,
+    # compare by number of events in the list
+    def __lt__(self, other):
+        return len(self.events.event_list) < len(other.events.event_list)
+
+    def getChildren(self, link_name):
+        traversal_list = self.controller.object_manager.processAssociationReference(link_name)
+        return [i["instance"] for i in self.controller.object_manager.getInstances(self, traversal_list)]
+
+    def getSingleChild(self, link_name):
+        return self.getChildren(link_name)[0]  # assume this will return a single child...
+
+    def getOutPortName(self, port_name):
+        return self.outports[port_name] if port_name in self.outports else port_name
+
+    def getInPortName(self, port_name):
+        return self.inports[port_name] if port_name in self.inports else port_name
+
+    def start(self):
+        self.configuration = []
+
+        self.active = True
+
+        self.current_state = {}
+        self.history_values = {}
+        self.timers = {}
+        self.timers_to_add = {}
+
+        self.big_step = BigStepState()
+        self.combo_step = ComboStepState()
+        self.small_step = SmallStepState()
+
+        self.__set_stable(False)
+
+        self.initializeStatechart()
+        self.processBigStepOutput()
+
+    def stop(self):
+        self.active = False
+        self.__set_stable(True)
+
+    def sccd_yield(self):
+        return max(0, (self.controller.accurate_time.get_wct() - self.controller.simulated_time) / 1000.0)
+
+    def getSimulatedTime(self):
+        return self.controller.getSimulatedTime()
+
+    def getWallClockTime(self):
+        return self.controller.getWallClockTime()
+
+    def updateConfiguration(self, states):
+        self.configuration.extend(states)
+        self.configuration_bitmap = sum([2 ** s.state_id for s in states])
+
+    def addTimer(self, index, timeout):
+        self.timers_to_add[index] = (self.controller.simulated_time + int(timeout * 1000), Event("_%iafter" % index))
+
+    def removeTimer(self, index):
+        if index in self.timers_to_add:
+            del self.timers_to_add[index]
+        if index in self.timers:
+            self.events.remove(self.timers[index])
+            del self.timers[index]
+        self.earliest_event_time = self.events.getEarliestTime()
+
+    def addEvent(self, event_list, time_offset=0):
+        event_time = self.controller.simulated_time + time_offset
+        #if not (event_time, self) in self.controller.object_manager.instance_times:
+            #heappush(self.controller.object_manager.instance_times, (event_time, self))
+        if event_time < self.earliest_event_time:
+            self.earliest_event_time = event_time
+        if not isinstance(event_list, list):
+            event_list = [event_list]
+        for e in event_list:
+            self.events.add(event_time, e)
+
+    def processBigStepOutput(self):
+        for e in self.big_step.output_events_port:
+            self.controller.outputEvent(e)
+        for e in self.big_step.output_events_om:
+            self.controller.object_manager.addEvent(e)
+
+    def __set_stable(self, is_stable):
+        self.is_stable = is_stable
+        # self.earliest_event_time keeps track of the earliest time this instance will execute a transition
+        if not is_stable:
+            self.earliest_event_time = self.controller.simulated_time
+        elif not self.active:
+            self.earliest_event_time = INFINITY
+        else:
+            self.earliest_event_time = self.events.getEarliestTime()
+        #if self.earliest_event_time != INFINITY:
+            #if not (self.earliest_event_time, self) in self.controller.object_manager.instance_times:
+                #heappush(self.controller.object_manager.instance_times, (self.earliest_event_time, self))
+
+    def step(self):
+        is_stable = False
+        while not is_stable:
+            due = []
+            if self.events.getEarliestTime() <= self.controller.simulated_time:
+                due = [self.events.pop()]
+            is_stable = not self.bigStep(due)
+            self.processBigStepOutput()
+        for index, entry in list(self.timers_to_add.items()):
+            self.timers[index] = self.events.add(*entry)
+        self.timers_to_add = {}
+        self.__set_stable(True)
+
+    def inState(self, state_strings):
+        state_ids = [self.states[state_string].state_id for state_string in state_strings]
+        for state_id in state_ids:
+            for s in self.configuration:
+                if s.state_id == state_id:
+                    break
+            else:
+                return False
+        return True
+
+    def bigStep(self, input_events):
+        self.big_step.next(input_events)
+        self.small_step.reset()
+        self.combo_step.reset()
+        while self.comboStep():
+            self.big_step.has_stepped = True
+            if self.semantics.big_step_maximality == StatechartSemantics.TakeOne:
+                break  # Take One -> only one combo step allowed
+        return self.big_step.has_stepped
+
+    def comboStep(self):
+        self.combo_step.next()
+        while self.smallStep():
+            self.combo_step.has_stepped = True
+        return self.combo_step.has_stepped
+
+    # generate transition candidates for current small step
+    # @profile
+    def generateCandidates(self):
+        changed_bitmap = self.combo_step.changed_bitmap
+        key = (self.configuration_bitmap, changed_bitmap)
+        try:
+            transitions = self.transition_mem[key]
+        except:
+            self.transition_mem[key] = transitions = [t for s in self.configuration if
+                                                      not (2 ** s.state_id & changed_bitmap) for t in s.transitions]
+
+        enabledEvents = self.getEnabledEvents()
+        enabledTransitions = []
+        for t in transitions:
+            if t.isEnabled(enabledEvents, enabledTransitions):
+                enabledTransitions.append(t)
+        return enabledTransitions
+
+    # @profile
+    def smallStep(self):
+        def __younger_than(x, y):
+            if x.source in y.source.ancestors:
+                return 1
+            elif y.source in x.source.ancestors:
+                return -1
+            else:
+                return 0
+
+        if self.small_step.has_stepped:
+            self.small_step.next()
+        candidates = self.generateCandidates()
+        if candidates:
+            to_skip = set()
+            conflicting = []
+            for c1 in candidates:
+                if c1 not in to_skip:
+                    conflict = [c1]
+                    for c2 in candidates[candidates.index(c1):]:
+                        if c2.source in c1.source.ancestors or c1.source in c2.source.ancestors:
+                            conflict.append(c2)
+                            to_skip.add(c2)
+
+                    if sys.version_info[0] < 3:
+                        conflicting.append(sorted(conflict, cmp=__younger_than))
+                    else:
+                        import functools
+                        conflicting.append(sorted(conflict, key=functools.cmp_to_key(__younger_than)))
+
+            if self.semantics.concurrency == StatechartSemantics.Single:
+                candidate = conflicting[0]
+                if self.semantics.priority == StatechartSemantics.SourceParent:
+                    candidate[-1].fire()
+                else:
+                    candidate[0].fire()
+            elif self.semantics.concurrency == StatechartSemantics.Many:
+                pass  # TODO: implement
+            self.small_step.has_stepped = True
+        return self.small_step.has_stepped
+
+    # @profile
+    def getEnabledEvents(self):
+        result = self.small_step.current_events + self.combo_step.current_events
+        if self.semantics.input_event_lifeline == StatechartSemantics.Whole or (
+                not self.big_step.has_stepped and
+                (self.semantics.input_event_lifeline == StatechartSemantics.FirstComboStep or (
+                        not self.combo_step.has_stepped and
+                        self.semantics.input_event_lifeline == StatechartSemantics.FirstSmallStep))):
+            result += self.big_step.input_events
+        return result
+
+    def raiseInternalEvent(self, event):
+        if self.semantics.internal_event_lifeline == StatechartSemantics.NextSmallStep:
+            self.small_step.addNextEvent(event)
+        elif self.semantics.internal_event_lifeline == StatechartSemantics.NextComboStep:
+            self.combo_step.addNextEvent(event)
+        elif self.semantics.internal_event_lifeline == StatechartSemantics.Queue:
+            self.addEvent(event)
+
+    def initializeStatechart(self):
+        self.updateConfiguration(self.default_targets)
+        for state in self.default_targets:
+            self.eventless_states += state.has_eventless_transitions
+            if state.enter:
+                state.enter()
+        if self.eventless_states:
+            self.controller.object_manager.eventless.add(self)
+
+class BigStepState(object):
+    def __init__(self):
+        self.input_events = [] # input events received from environment before beginning of big step (e.g. from object manager, from input port)
+        self.output_events_port = [] # output events to be sent to output port after big step ends.
+        self.output_events_om = [] # output events to be sent to object manager after big step ends.
+        self.has_stepped = True
+
+    def next(self, input_events):
+        self.input_events = input_events
+        self.output_events_port = []
+        self.output_events_om = []
+        self.has_stepped = False
+
+    def outputEvent(self, event):
+        self.output_events_port.append(event)
+
+    def outputEventOM(self, event):
+        self.output_events_om.append(event)
+
+
+class ComboStepState(object):
+    def __init__(self):
+        self.current_events = [] # set of enabled events during combo step
+        self.next_events = [] # internal events that were raised during combo step
+        self.changed_bitmap = 0 # set of all or-states that were the arena of a triggered transition during big step.
+        self.has_stepped = True
+
+    def reset(self):
+        self.current_events = []
+        self.next_events = []
+
+    def next(self):
+        self.current_events = self.next_events
+        self.next_events = []
+        self.changed_bitmap = 0
+        self.has_stepped = False
+
+    def addNextEvent(self, event):
+        self.next_events.append(event)
+
+
+class SmallStepState(object):
+    def __init__(self):
+        self.current_events = [] # set of enabled events during small step
+        self.next_events = [] # events to become 'current' in the next small step
+        self.candidates = [] # document-ordered(!) list of transitions that can potentially be executed concurrently, or preempt each other, depending on concurrency semantics. If no concurrency is used and there are multiple candidates, the first one is chosen. Source states of candidates are *always* orthogonal to each other.
+        self.has_stepped = True
+
+    def reset(self):
+        self.current_events = []
+        self.next_events = []
+
+    def next(self):
+        self.current_events = self.next_events # raised events from previous small step
+        self.next_events = []
+        self.candidates = []
+        self.has_stepped = False
+
+    def addNextEvent(self, event):
+        self.next_events.append(event)
+
+    def addCandidate(self, t, p):
+        self.candidates.append((t, p))
+
+    def hasCandidates(self):
+        return len(self.candidates) > 0
+

+ 0 - 0
sccd/runtime/__init__.py


+ 18 - 0
sccd/runtime/accurate_time.py

@@ -0,0 +1,18 @@
+import time as t
+import os
+
+class AccurateTime:
+    def __init__(self):
+        if os.name == 'posix':
+            self._get_wct_time = lambda self: int((t.time() - self.start_time) * 1000)
+        elif os.name == 'nt':
+            self._get_wct_time = lambda self: int((t.clock() - self.start_time) * 1000)
+        
+    def set_start_time(self):
+        if os.name == 'posix':
+            self.start_time = t.time()
+        elif os.name == 'nt':
+            self.start_time = t.clock()
+            
+    def get_wct(self):
+        return self._get_wct_time(self)

+ 45 - 0
sccd/runtime/event_queue.py

@@ -0,0 +1,45 @@
+from sccd.runtime.infinity import INFINITY
+from heapq import heappush, heappop
+
+class EventQueue(object):
+    def __init__(self):
+        self.event_list = []
+        self.event_time_numbers = {}
+        self.removed = set()
+    
+    def __str__(self):
+        return str([entry for entry in self.event_list if entry not in self.removed])
+    
+    def isEmpty(self):
+        return not [item for item in self.event_list if not item in self.removed]
+    
+    def getEarliestTime(self):
+        while self.event_list and (self.event_list[0] in self.removed):
+            item = heappop(self.event_list)
+            self.removed.remove(item)
+        try:
+            return self.event_list[0][0]
+        except IndexError:
+            return INFINITY
+    
+    def add(self, event_time, event):
+        self.event_time_numbers[event_time] = self.event_time_numbers.setdefault(event_time, 0) + 1
+        def_event = (event_time, self.event_time_numbers[event_time], event)
+        heappush(self.event_list, def_event)
+        return def_event
+    
+    def remove(self, event):
+        self.removed.add(event)
+        if len(self.removed) > 100:
+            self.event_list = [x for x in self.event_list if x not in self.removed]
+            self.removed = set()
+    
+    def pop(self):
+        while 1:
+            item = heappop(self.event_list)
+            event_time = item[0]
+            self.event_time_numbers[event_time] -= 1
+            if not self.event_time_numbers[event_time]:
+                del self.event_time_numbers[event_time]
+            if item not in self.removed:
+                return item[2]

+ 3 - 0
sccd/runtime/infinity.py

@@ -0,0 +1,3 @@
+# instantiate singleton     
+INFINITY = float('inf')
+

+ 1 - 0
sccd/runtime/libs/__init__.py

@@ -0,0 +1 @@
+

+ 64 - 0
sccd/runtime/libs/drawing.py

@@ -0,0 +1,64 @@
+"""
+ *REALLY* Small framework for creating/manipulating/deleting Tkinter Canvas elements.
+ 
+ NOTE: keep this synced with svg.js
+ 
+ Author: Raphael Mannadiar
+ Date: 2014/08/21
+"""
+
+from sccd.runtime.libs.utils import utils
+
+class drawing:
+    class canvas_wrapper:
+        def __init__(self, element):
+            self.element = element
+            self.width = int(element.cget("width"))
+            self.height = int(element.cget("height"))
+
+        def add_circle(self, x, y, r, style):
+            new_element_id = self.element.create_oval(x-r, y-r, x+r, y+r, **style)
+            return drawing.ui_element_wrapper(self, new_element_id, x, y)
+
+        def add_rectangle(self, x, y, w, h, style):
+            new_element_id = self.element.create_rectangle(x-w/2.0, y-h/2.0, x+w/2.0, y+h/2.0, **style)
+            return drawing.ui_element_wrapper(self, new_element_id, x, y)
+
+        def remove_element(self, element):
+            self.element.delete(element.element_id)
+            
+        def get_width(self):
+            return self.element.winfo_width()
+            
+        def get_height(self):
+            return self.element.winfo_height()
+
+
+    class ui_element_wrapper:
+        def __init__(self, canvas_wrapper, element_id, x, y):
+            self.canvas_wrapper = canvas_wrapper
+            self.element_id = element_id
+            self.a = 0
+            self.x = x
+            self.y = y
+
+        def set_position(self, x, y):
+            self.move(x-self.x, y-self.y)
+
+        def get_position(self):
+            return utils._bunch(x=self.x, y=self.y)
+
+        def move(self, dx, dy):
+            self.x += dx
+            self.y += dy
+            self.canvas_wrapper.element.move(self.element_id, dx, dy)
+
+        def set_rotation(self, a):
+            raise Exception("Not implemented yet")
+
+        def rotate(self, a):
+            raise Exception("Not implemented yet")
+
+        def set_color(self, color):
+            self.canvas_wrapper.element.itemconfig(self.element_id, fill=color)
+

+ 198 - 0
sccd/runtime/libs/ordered_set.py

@@ -0,0 +1,198 @@
+"""
+An OrderedSet is a custom MutableSet that remembers its order, so that every
+entry has an index that can be looked up.
+
+Based on a recipe originally posted to ActiveState Recipes by Raymond Hettiger,
+and released under the MIT license.
+
+Rob Speer's changes are as follows:
+
+    - changed the content from a doubly-linked list to a regular Python list.
+      Seriously, who wants O(1) deletes but O(N) lookups by index?
+    - add() returns the index of the added item
+    - index() just returns the index of an item
+    - added a __getstate__ and __setstate__ so it can be pickled
+    - added __getitem__
+"""
+import collections
+
+SLICE_ALL = slice(None)
+__version__ = '2.0.1'
+
+
+def is_iterable(obj):
+    """
+    Are we being asked to look up a list of things, instead of a single thing?
+    We check for the `__iter__` attribute so that this can cover types that
+    don't have to be known by this module, such as NumPy arrays.
+
+    Strings, however, should be considered as atomic values to look up, not
+    iterables. The same goes for tuples, since they are immutable and therefore
+    valid entries.
+
+    We don't need to check for the Python 2 `unicode` type, because it doesn't
+    have an `__iter__` attribute anyway.
+    """
+    return hasattr(obj, '__iter__') and not isinstance(obj, str) and not isinstance(obj, tuple)
+
+
+class OrderedSet(collections.MutableSet):
+    """
+    An OrderedSet is a custom MutableSet that remembers its order, so that
+    every entry has an index that can be looked up.
+    """
+    def __init__(self, iterable=None):
+        self.items = []
+        self.map = {}
+        if iterable is not None:
+            self |= iterable
+
+    def __len__(self):
+        return len(self.items)
+
+    def __getitem__(self, index):
+        """
+        Get the item at a given index.
+
+        If `index` is a slice, you will get back that slice of items. If it's
+        the slice [:], exactly the same object is returned. (If you want an
+        independent copy of an OrderedSet, use `OrderedSet.copy()`.)
+
+        If `index` is an iterable, you'll get the OrderedSet of items
+        corresponding to those indices. This is similar to NumPy's
+        "fancy indexing".
+        """
+        if index == SLICE_ALL:
+            return self
+        elif hasattr(index, '__index__') or isinstance(index, slice):
+            result = self.items[index]
+            if isinstance(result, list):
+                return OrderedSet(result)
+            else:
+                return result
+        elif is_iterable(index):
+            return OrderedSet([self.items[i] for i in index])
+        else:
+            raise TypeError("Don't know how to index an OrderedSet by %r" %
+                    index)
+
+    def copy(self):
+        return OrderedSet(self)
+
+    def __getstate__(self):
+        if len(self) == 0:
+            # The state can't be an empty list.
+            # We need to return a truthy value, or else __setstate__ won't be run.
+            #
+            # This could have been done more gracefully by always putting the state
+            # in a tuple, but this way is backwards- and forwards- compatible with
+            # previous versions of OrderedSet.
+            return (None,)
+        else:
+            return list(self)
+
+    def __setstate__(self, state):
+        if state == (None,):
+            self.__init__([])
+        else:
+            self.__init__(state)
+
+    def __contains__(self, key):
+        return key in self.map
+
+    def add(self, key):
+        """
+        Add `key` as an item to this OrderedSet, then return its index.
+
+        If `key` is already in the OrderedSet, return the index it already
+        had.
+        """
+        if key not in self.map:
+            self.map[key] = len(self.items)
+            self.items.append(key)
+        return self.map[key]
+    append = add
+
+    def update(self, sequence):
+        """
+        Update the set with the given iterable sequence, then return the index
+        of the last element inserted.
+        """
+        item_index = None
+        try:
+            for item in sequence:
+                item_index = self.add(item)
+        except TypeError:
+            raise ValueError('Argument needs to be an iterable, got %s' % type(sequence))
+        return item_index
+
+    def index(self, key):
+        """
+        Get the index of a given entry, raising an IndexError if it's not
+        present.
+
+        `key` can be an iterable of entries that is not a string, in which case
+        this returns a list of indices.
+        """
+        if is_iterable(key):
+            return [self.index(subkey) for subkey in key]
+        return self.map[key]
+
+    def pop(self):
+        """
+        Remove and return the last element from the set.
+
+        Raises KeyError if the set is empty.
+        """
+        if not self.items:
+            raise KeyError('Set is empty')
+
+        elem = self.items[-1]
+        del self.items[-1]
+        del self.map[elem]
+        return elem
+
+    def discard(self, key):
+        """
+        Remove an element.  Do not raise an exception if absent.
+
+        The MutableSet mixin uses this to implement the .remove() method, which
+        *does* raise an error when asked to remove a non-existent item.
+        """
+        if key in self:
+            i = self.items.index(key)
+            del self.items[i]
+            del self.map[key]
+            for k, v in list(self.map.items()):
+                if v >= i:
+                    self.map[k] = v - 1
+
+    def clear(self):
+        """
+        Remove all items from this OrderedSet.
+        """
+        del self.items[:]
+        self.map.clear()
+
+    def __iter__(self):
+        return iter(self.items)
+
+    def __reversed__(self):
+        return reversed(self.items)
+
+    def __repr__(self):
+        if not self:
+            return '%s()' % (self.__class__.__name__,)
+        return '%s(%r)' % (self.__class__.__name__, list(self))
+
+    def __eq__(self, other):
+        if isinstance(other, OrderedSet):
+            return len(self) == len(other) and self.items == other.items
+        try:
+            other_as_set = set(other)
+        except TypeError:
+            # If `other` can't be converted into a set, it's not equal.
+            return False
+        else:
+            return set(self) == other_as_set
+

+ 121 - 0
sccd/runtime/libs/ui.py

@@ -0,0 +1,121 @@
+"""
+ *REALLY* Small framework for creating/manipulating/deleting gui elements in Tkinter.
+
+ NOTE: keep this synced with ui.js
+
+ Author: Raphael Mannadiar
+ Date: 2014/08/21
+"""
+
+import sys
+try:
+    import Tkinter as tk
+except ImportError:
+    import tkinter as tk
+
+from sccd.runtime.libs.drawing import drawing
+from sccd.runtime.libs.utils import utils
+
+from sccd.runtime.statecharts_core import Event
+
+
+class ui:
+    window = None
+    __nextWindowId = 0
+
+    EVENTS = utils._bunch(
+        KEY_PRESS =             '<Key>',
+        MOUSE_CLICK =             '<Button>',
+        MOUSE_MOVE =             '<Motion>',
+        MOUSE_PRESS =            '<ButtonPress>',
+        MOUSE_RELEASE =        '<ButtonRelease>',
+        MOUSE_RIGHT_CLICK =    '<Button-2>' if sys.platform == "darwin" else '<Button-3>',
+        WINDOW_CLOSE =         'WM_DELETE_WINDOW');
+
+    if sys.platform == "darwin":
+        MOUSE_BUTTONS = utils._bunch(
+            LEFT        = 1,
+            MIDDLE    = 3,
+            RIGHT        = 2);
+    else:
+        MOUSE_BUTTONS = utils._bunch(
+            LEFT        = 1,
+            MIDDLE    = 2,
+            RIGHT        = 3);
+
+    KEYCODES    = utils._bunch(
+        DELETE    = 46);
+
+    @staticmethod
+    def append_button(_window,text):
+        button = tk.Button(_window, text=text)
+        button.pack(fill=tk.BOTH, expand=1)
+        return ui.wrap_element(button)
+
+
+    @staticmethod
+    def append_canvas(_window,width,height,style):
+        canvas = tk.Canvas(_window,width=width,height=height)
+        canvas.config(**style)
+        canvas.pack(fill=tk.BOTH, expand=1)
+        return drawing.canvas_wrapper(canvas)
+
+
+    @staticmethod
+    def bind_event(source,event,controller,raise_name,port="ui"):
+
+        def __handle_event(ev=None):
+            if event == ui.EVENTS.KEY_PRESS :
+                controller.addInput(Event(raise_name, port, [ev.keycode,source]))
+
+            elif event == ui.EVENTS.MOUSE_CLICK or \
+                  event == ui.EVENTS.MOUSE_MOVE or \
+                  event == ui.EVENTS.MOUSE_PRESS or \
+                  event == ui.EVENTS.MOUSE_RELEASE or \
+                    event == ui.EVENTS.MOUSE_RIGHT_CLICK :
+                controller.addInput(Event(raise_name, port, [ev.x, ev.y, ev.num]))
+
+            elif event == ui.EVENTS.WINDOW_CLOSE :
+                controller.addInput(Event(raise_name, port, [source]))
+
+            else :
+                raise Exception('Unsupported event');
+
+        if event == ui.EVENTS.WINDOW_CLOSE :
+            source.protocol(event, __handle_event)
+
+        elif issubclass(drawing.ui_element_wrapper,source.__class__) :
+            source.canvas_wrapper.element.tag_bind(source.element_id, event, __handle_event)
+
+        else :
+            source.bind(event, __handle_event)
+
+
+    @staticmethod
+    def close_window(_window):
+        _window.destroy()
+
+
+    @staticmethod
+    def log(value):
+        print(value)
+
+
+    @staticmethod
+    def new_window(width,height,title=None):
+        _window = tk.Toplevel(ui.window)
+        if title:
+            _window.title(title)
+        _window.geometry(str(width)+"x"+str(height)+"+300+300")
+        return _window
+
+
+    @staticmethod
+    def println(value,target):
+        raise Exception('Not implemented yet');
+
+
+    @staticmethod
+    def wrap_element(element):
+        return utils._bunch(element=element)
+

+ 19 - 0
sccd/runtime/libs/utils.py

@@ -0,0 +1,19 @@
+import random
+
+class utils:
+
+	@staticmethod
+	def random():
+		return random.random()
+
+
+	"""
+		provide "." access to dictionaries
+
+		example: d = {'a':1}
+			before: d['a'] => 1, d.a => error
+			after:  d['a'] = d.a
+	"""
+	class _bunch:
+		def __init__(self, **kwds):
+			self.__dict__.update(kwds)

+ 145 - 0
sccd/runtime/socket2event.py

@@ -0,0 +1,145 @@
+"""
+Socket 2 Event wrapper
+
+Author: Yentl Van Tendeloo
+
+This maps socket communication to events, and vice versa, allowing for a
+Statechart to use (blocking) sockets. It sends events to the socket_in port,
+and listens for commands on the socket_out port.  As this runs on its own
+thread, you will need to start the code by running
+"boot_translation_service(controller)" before using the ports.
+"""
+
+import threading
+from sccd.runtime.statecharts_core import Event
+import socket
+import sys
+
+send_data_queues = {}
+send_events = {}
+recv_events = {}
+run_sockets = {}
+
+def start_socket_threads(controller, sock):
+    recv_events[sock] = recv_event = threading.Event()
+    send_events[sock] = send_event = threading.Event()
+    send_data_queues[sock] = send_data_queue = []
+    run_sockets[sock] = True
+
+    thrd = threading.Thread(target=receive_from_socket, args=[controller, sock, recv_event])
+    thrd.daemon = True
+    thrd.start()
+
+    thrd = threading.Thread(target=send_to_socket, args=[controller, sock, send_data_queue, send_event])
+    thrd.daemon = True
+    thrd.start()
+
+def receive_from_socket(controller, sock, recv_event):
+    try:
+        while 1:
+            recv_event.wait()
+            recv_event.clear()
+            if not run_sockets[sock]:
+                break
+            data = sock.recv(2**16)
+            controller.addInput(Event("received_socket", "socket_in", [sock, data]))
+    except socket.error as e:
+        controller.addInput(Event("error_socket", "socket_in", [sock, e]))
+        
+
+def send_to_socket(controller, sock, data_queue, send_event):
+    while run_sockets[sock]:
+        send_event.wait()
+        send_event.clear()
+        while data_queue:
+            data = data_queue.pop(0)
+            if sys.version_info[0] > 2:
+                if isinstance(data, str):
+                    data = data.encode()
+            send = sock.send(data)
+
+            controller.addInput(Event("sent_socket", "socket_in", [sock, send]))
+        if not run_sockets[sock]:
+            break
+
+def _accept(controller, sock):
+    conn, addr = sock.accept()
+    start_socket_threads(controller, conn)
+    controller.addInput(Event("accepted_socket", "socket_in", [sock, conn]))
+
+def _connect(controller, sock, destination):
+    sock.connect(destination)
+    controller.addInput(Event("connected_socket", "socket_in", [sock]))
+
+def _close(controller, sock):
+    run_sockets[sock] = False
+    send_events[sock].set()
+    recv_events[sock].set()
+    sock.close()
+    controller.addInput(Event("closed_socket", "socket_in", [sock]))
+
+def _bind(controller, sock, addr):
+    sock.bind(addr)
+    controller.addInput(Event("bound_socket", "socket_in", [sock]))
+
+def _listen(controller, sock):
+    sock.listen(1)
+    controller.addInput(Event("listened_socket", "socket_in", [sock]))
+
+def _wrapper_func(*args):
+    func = args[0]
+    controller = args[1]
+    sock = args[2]
+    try:
+        func(*args[1:])
+    except socket.error as e:
+        #print("ERROR " + str(e))
+        controller.addInput(Event("error_socket", "socket_in", [sock, e]))
+    except Exception as e:
+        print("UNKNOWN ERROR " + str(e))
+        controller.addInput(Event("unknown_error_socket", "socket_in", [sock, e]))
+        raise
+
+def _start_on_daemon_thread(func, args):
+    new_args = [func]
+    new_args.extend(args)
+    args = new_args
+    thrd = threading.Thread(target=_wrapper_func, args=args)
+    thrd.daemon = True
+    thrd.start()
+
+def boot_translation_service(controller):
+    socket_out = controller.addOutputListener("socket_out")
+    _start_on_daemon_thread(_poll, [controller, socket_out])
+
+def _poll(controller, socket_out):
+    while 1:
+        evt = socket_out.fetch(-1)
+        name, params = evt.getName(), evt.getParameters()
+        if name == "accept_socket":
+            _start_on_daemon_thread(_accept, [controller, params[0]])
+        elif name == "recv_socket":
+            recv_events[params[0]].set()
+        elif name == "connect_socket":
+            _start_on_daemon_thread(_connect, [controller, params[0], params[1]])
+        elif name == "create_socket":
+            sock = socket.socket()
+            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+            start_socket_threads(controller, sock)
+            if len(params) == 1:
+                # In case we get an ID to prove which socket it is
+                controller.addInput(Event("created_socket", "socket_in", [sock, params[0]]))
+            else:
+                # Don't care and just send out the socket
+                controller.addInput(Event("created_socket", "socket_in", [sock]))
+        elif name == "close_socket":
+            _start_on_daemon_thread(_close, [controller, params[0]])
+        elif name == "send_socket":
+            send_data_queues[params[0]].append(params[1])
+            send_events[params[0]].set()
+        elif name == "bind_socket":
+            _start_on_daemon_thread(_bind, [controller, params[0], params[1]])
+        elif name == "listen_socket":
+            _start_on_daemon_thread(_listen, [controller, params[0]])
+        elif name == "stop":
+            break

File diff suppressed because it is too large
+ 1315 - 0
sccd/runtime/statecharts_core.py


+ 48 - 0
sccd/runtime/tkinter_eventloop.py

@@ -0,0 +1,48 @@
+"""
+Yentl added many patches to make this code TkInter compatible.
+TkInter is NOT thread-safe, and this applies to the after operations as well.
+Therefore, calling tk.after (or after_cancel) should NOT be done from any thread apart from the thread running TkInter itself.
+See https://mail.python.org/pipermail/tkinter-discuss/2013-November/003522.html for a discussion...
+What actually happens in this code, is that we check whether we are on the main thread or not.
+If we are on the main thread, we invoke the code as usual.
+If we are not on the main thread, we force the *run* operation to execute, even though it might not have been scheduled.
+This operation, however, will run on the main thread, and from there we can then call this schedule function again.
+"""
+
+# If we are not on the main thread, we force the *run* operation
+
+from sccd.runtime.statecharts_core import EventLoop
+
+import math
+import threading as thread
+
+class TkEventLoop(EventLoop):
+    def __init__(self, tk):
+        self.ctr = 0
+        self.tk = tk
+        self.main_thread = thread.get_ident()
+
+        # bind scheduler callback
+        def schedule(callback, timeout, behind = False):
+            if self.main_thread != thread.get_ident():
+                # Use events, as Tk operations are far from thread safe...
+                # Should there be a timeout, event_generate will automatically schedule this for real from inside the main loop
+                tk.event_generate("<<TriggerSCCDEvent>>", when="tail")
+            else:
+                # As usual, use Tk after events
+                if behind:
+                    tk.update_idletasks()
+                return tk.after(timeout, callback)
+
+        def cancel(evt):
+            if self.main_thread != thread.get_ident():
+                # This will also remove the pending events, while also triggering a run first
+                # That initial run, however, will not execute anything
+                tk.event_generate("<<TriggerSCCDEvent>>", when="tail")
+            else:
+                tk.after_cancel(evt)
+
+        EventLoop.__init__(self, schedule, cancel)
+
+    def bind_controller(self, controller):
+        self.tk.bind("<<TriggerSCCDEvent>>", controller.run)