소스 검색

first iteration of fixing overly complex and partially wrong evolution code: check and repair conformance relationship independently of scope of change

Lucas Heer 7 년 전
부모
커밋
c2d662f004
5개의 변경된 파일134개의 추가작업 그리고 72개의 파일을 삭제
  1. 15 0
      commons.py
  2. 60 38
      evolution/node_ops.py
  3. 6 4
      sketchUI/exm_mainwindow.py
  4. 43 30
      sketchUI/exm_scene.py
  5. 10 0
      sketchUI/mvops.py

+ 15 - 0
commons.py

@@ -77,6 +77,21 @@ def all_consyn_models():
     consyn_models_full = ["models/consyn/"+csm for csm in consyn_models]
     return consyn_models_full
 
+def is_example_model(model):
+    """
+    Returns true if model is a example model, false otherwise.
+    """
+    model_id = mv.all_instances(model, "Model").pop()
+    is_example_str = mv.read_attrs(model, model_id)["is_example"]
+    return is_example_str == u"True"
+
+def is_instance_model(model):
+    """
+    Returns true if model is an instance model, false otherwise.
+    """
+    is_example = is_example_model(model)
+    return not is_example
+
 def all_nodes_with_type(model, typ):
     """ Returns a list of nodes in model model with type typ """
     all_nodes = mv.all_instances(model, "Node")

+ 60 - 38
evolution/node_ops.py

@@ -8,94 +8,116 @@ class NodeAdd(object):
     def __init__(self):
         self._node_type = ""
 
-    def execute(self, model, node_type, local, check_if_last=False):
+    def execute(self, model, node_type, local):
         """
-        Add a new node with type node_type to model model.
+        Add a new node with type node_type to model.
         If local is true, the node is only added to the model.
-        If local is false, the node will be added to all models.
+        If local is false, the node will be added to all example models.
         """
         self._node_type = node_type
         if local:
             mv.transformation_execute_MANUAL("graph_ops/add_node", {"gm":model}, {"gm":model},
                                              callback=self._callback)
-            # Local add can make a type mandatory and therefore break conformance
-            if check_if_last:
-                if commons.is_type_mandatory(node_type):
-                    print("Type {} became mandatory, adding it to instance models ...".format(node_type))
-                    # local add made type mandatory, so need to add to instance models (if not exists already)
-                    for im in commons.all_instance_models():
-                        if not commons.all_nodes_with_type(im, self._node_type):
-                            self.execute(im, node_type, local=True, check_if_last=False)
-
         else:
-            for m in commons.all_models():
+            for m in commons.all_example_models():
                 self.execute(m, node_type, local=True)
 
     def _callback(self, model):
         node_id = mv.instantiate(model, "gm/Node")
         mv.attr_assign(model, node_id, "typeID", self._node_type)
 
+    def repair(self):
+        """
+        Check if the node add invalidated any instance models by making a type mandatory.
+        If yes, repair by adding a node of the same type to it.
+        """
+        if commons.is_type_mandatory(self._node_type):
+            for im in commons.all_instance_models():
+                if not commons.all_nodes_with_type(im, self._node_type):
+                    print("Adding mandatory type {} to model {}".format(self._node_type, im))
+                    self.execute(im, self._node_type, local=True)
+
 
 class NodeDelete(object):
     def __init__(self):
         self._node = ""
         self._node_type = ""
-        self._was_last = False
+        self.was_last = False
 
-    def execute(self, model, node, local, check_if_last=False):
+    def execute(self, model, node, local):
         self._node = node
         self._node_type = commons.get_node_type(model, node)
 
         if local:
             mv.transformation_execute_MANUAL("graph_ops/del_node", {"gm":model}, {"gm":model},
                                              callback=self._callback)
-            if check_if_last:
-                # check if we just deleted the last instance of the node in all example models
-                # if yes, delete it in instance models as well to preserve their validity
-                remaining_instances = 0
-                for m in commons.all_example_models():
-                    remaining_instances += len(commons.all_nodes_with_type(m, self._node_type))
-                if remaining_instances == 0:
-                    # it was indeed the last one, delete from instance models
-                    self._was_last = True
-                    for m in commons.all_instance_models():
-                        nodes_to_delete = commons.all_nodes_with_type(m, self._node_type)
-                        for node in nodes_to_delete:
-                            self.execute(m, node, local=True)
         else:
-            for m in commons.all_models():
+            for m in commons.all_example_models():
                 nodes_to_delete = commons.all_nodes_with_type(m, self._node_type)
                 for node in nodes_to_delete:
                     self.execute(m, node, local=True)
 
-    def was_last(self):
-        return self._was_last
-
     def _callback(self, model):
         mv.delete_element(model, "gm/"+self._node)
 
+    def repair(self):
+        """
+        Check if this operation invalidated any instance model by leaving a node untyped.
+        If yes, delete untyped node as well.
+        """
+        remaining_instances = 0
+        for exm in commons.all_example_models():
+            remaining_instances += len(commons.all_nodes_with_type(exm, self._node_type))
+        if remaining_instances == 0:
+            self.was_last = True
+            print("Type {} became unavailable, deleting from all instance models ...".format(self._node_type))
+        for im in commons.all_instance_models():
+            nodes_to_delete = commons.all_nodes_with_type(im, self._node_type)
+            for node in nodes_to_delete:
+                self.execute(im, node, local=True)
+
 
 class NodeRetype(object):
     def __init__(self):
         self._node = ""
+        self._old_type = ""
         self._new_type = ""
+        self._scope = ""
 
     def execute(self, model, node, new_type, local):
         """
         Retype the node in model to new_type.
-        If local is true, the change only affects the single node.
-        If local is false, the change affects all nodes in all models.
+        If local is true, new_type must already exist and the change only affects the given node.
+        If local is false, all nodes in all example models are renamed to a new type (which must not exist yet).
         """
         self._node = node
         self._new_type = new_type
+        self._old_type = commons.get_node_type(model, node)
+        if local:
+            self._scope = "Local"
+        else:
+            self._scope = "Global"
+
         if local:
             mv.transformation_execute_MANUAL("graph_ops/retype_node", {"gm":model}, {"gm":model},
                                              callback=self._callback)
         else:
-            node_type = commons.get_node_type(model, node)
-            for m in commons.all_models():
-                for node in commons.all_nodes_with_type(m, node_type):
-                    self.execute(m, node, new_type, local=True)
+            for exm in commons.all_example_models():
+                for node in commons.all_nodes_with_type(exm, self._old_type):
+                    print("NodeRetype: retyping {} in {}".format(node, exm))
+                    self.execute(exm, node, new_type, local=True)
 
     def _callback(self, model):
         mv.attr_assign(model, "gm/"+self._node, "typeID", self._new_type)
+
+    def repair(self):
+        """
+        Check if this operation invalidated any instance models.
+        Depends on the scope: if local, need to retype all instance model nodes with the old type as well
+        """
+        if self._scope == "Global":
+            for im in commons.all_instance_models():
+                for node in commons.all_nodes_with_type(im, self._old_type):
+                    self.execute(im, node, self._new_type, local=True)
+        else:
+            pass  # TODO implement

+ 6 - 4
sketchUI/exm_mainwindow.py

@@ -263,16 +263,18 @@ class EXMMainWindow(QMainWindow, Ui_MainWindow):
         self.plainTextEdit.appendPlainText("Adding node of type {} to model".format(event.text()))
         add_handler = NodeAdd()
 
-        # ask if global or local delete
+        # ask if global or local add node
         scope, ok = QInputDialog.getItem(self.parent(), "Select scope", "Scope", ["Local", "Global"])
         if not ok:
             return
 
         if scope == "Global":
-            add_handler.execute(self._cur_model, event.text(), local=False, check_if_last=False)
+            add_handler.execute(self._cur_model, event.text(), local=False)
         else:
-            add_handler.execute(self._cur_model, event.text(), local=True, check_if_last=True)
-        # render node by reloading the whole model (since we cannot get the node id if the newly added model
+            add_handler.execute(self._cur_model, event.text(), local=True)
+        # repair any possibly invalidated instance models
+        add_handler.repair()
+        # render node by reloading the whole model (since we can not get the node id of the newly added model)
         self._load_model()
 
     def _on_attribute_edited(self, item):

+ 43 - 30
sketchUI/exm_scene.py

@@ -246,29 +246,46 @@ class SketchScene(QGraphicsScene):
         type an already typed node = retype it
         """
         old_type = item.get_type()
-        node_type, ok = QInputDialog.getText(self._parent, "Retype node", "New type", text=item.get_type())
-        if not ok or not node_type:
+        new_type, ok = QInputDialog.getText(self._parent, "Retype node", "New type", text=item.get_type())
+        if not ok or not new_type:
             # user canceled or node type empty
             return
 
-        if node_type in commons.get_available_types():
-            self._parent.plainTextEdit.appendPlainText("Error: Already such a type: {}".format(node_type))
+        # local or global?
+        scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
+        if not ok:
             return
 
-        self._parent.plainTextEdit.appendPlainText("Performing retype of node {}".format(node_type))
-        QApplication.setOverrideCursor(Qt.WaitCursor)
         retype_handler = NodeRetype()
-        retype_handler.execute(self._cur_model, item.node_id, node_type, local=False)
+        QApplication.setOverrideCursor(Qt.WaitCursor)
 
-        # rename on screen
-        for node_item in self.items():
-            if not isinstance(node_item, GraphicsNodeItem):
-                continue
-            if node_item.get_type() == old_type:
-                node_item.set_type(node_type)
+        if scope == "Global":
+            # global retype -> retype all existing nodes to new type
+            if new_type in commons.get_available_types():
+                self._parent.plainTextEdit.appendPlainText("Error: Already such a type: {}".format(new_type))
+                return
+
+            self._parent.plainTextEdit.appendPlainText("Performing global retype of node {}".format(new_type))
+            retype_handler.execute(self._cur_model, item.node_id, new_type, local=False)
 
-        # update list widget
-        self._parent.populate_types()
+            # also rename the concrete syntax
+            mvops.move_consyn_model("models/consyn/"+old_type, "models/consyn/"+new_type)
+
+            # rename all types on canvas
+            for node_item in self.items():
+                if not isinstance(node_item, GraphicsNodeItem):
+                    continue
+                if node_item.get_type() == old_type:
+                    node_item.set_type(new_type)
+
+            # update list widget
+            self._parent.populate_types()
+
+        else:
+            # local rename -> type must exist
+            pass  # TODO implement
+
+        retype_handler.repair()
         QApplication.restoreOverrideCursor()
 
     def _handle_keypress_type_on_group(self, group):
@@ -298,19 +315,12 @@ class SketchScene(QGraphicsScene):
             else:
                 return
 
-        # perform add local or global?
-        scope, ok = QInputDialog.getItem(self._parent, "Select scope", "Scope", ["Local", "Global"])
-        if not ok:
-            return
-
         self._parent.plainTextEdit.appendPlainText("Typing group to type {}".format(node_type))
         QApplication.setOverrideCursor(Qt.WaitCursor)
         # add the node to the model
         add_handler = NodeAdd()
-        if scope == "Global":
-            add_handler.execute(self._cur_model, node_type, local=False, check_if_last=False)
-        else:
-            add_handler.execute(self._cur_model, node_type, local=True, check_if_last=True)
+        add_handler.execute(self._cur_model, node_type, local=True)
+
         # Get node id of newly added node in current model
         nodeid = commons.all_nodes_with_type(self._cur_model, node_type)[0]
 
@@ -412,7 +422,7 @@ class SketchScene(QGraphicsScene):
             QApplication.restoreOverrideCursor()
 
         elif len(selected) == 1 and isinstance(selected[0], GraphicsNodeItem):
-            del_hander = NodeDelete()
+            del_handler = NodeDelete()
             node = selected[0]
             # a node is to be deleted
             self._parent.plainTextEdit.appendPlainText("Deleting node of type {}".format(node.get_type()))
@@ -423,7 +433,7 @@ class SketchScene(QGraphicsScene):
             QApplication.setOverrideCursor(Qt.WaitCursor)
             if scope == "Global":
                 # global language evolution, so delete node with same type everywhere
-                del_hander.execute(self._cur_model, node.node_id, local=False, check_if_last=False)
+                del_handler.execute(self._cur_model, node.node_id, local=False)
                 # also delete its associated CS model
                 mvops.del_concrete_syntax_model(node.get_type())
 
@@ -436,13 +446,16 @@ class SketchScene(QGraphicsScene):
 
             else:
                 # just local, delete from this model only
-                del_hander.execute(self._cur_model, node.node_id, local=True, check_if_last=True)
-                if del_hander.was_last():
-                    # it was the last node in the language, so delete its CS model as well
-                    mvops.del_concrete_syntax_model(node.get_type())
+                del_handler.execute(self._cur_model, node.node_id, local=True)
                 # delete this node from view
                 self.removeItem(node)
 
+            # repair any possibly invalidated instance models
+            del_handler.repair()
+            if scope == "Local" and del_handler.was_last:
+                # local delete removed type completely, so remove the dangling concrete syntax model as well
+                mvops.del_concrete_syntax_model(node.get_type())
+
             # in view, delete edges that were connected to this node as well
             # modelverse does this on its own so do not delete edges explicitly here
             if scope == "Local":

+ 10 - 0
sketchUI/mvops.py

@@ -150,3 +150,13 @@ def delete_example_model(model):
         return False
     mv.model_delete(model)
     return True
+
+def move_consyn_model(old_model, new_model):
+    """
+    Move a concrete syntax model from old_model to new_model.
+    Returns true on success, false otherwise.
+    """
+    if not old_model in commons.all_consyn_models():
+        return False
+    mv.model_move(old_model, new_model)
+    return True