瀏覽代碼

Hacked together graph rewriter

Joeri Exelmans 1 年之前
父節點
當前提交
89b7c83440

+ 22 - 11
experiments/exp_scd.py

@@ -79,6 +79,8 @@ def main():
         tgt_max_c=None,
     )
 
+    print(dsl_mm_scd.list_elements())
+
     conf = Conformance(state, dsl_mm_id, scd_mm_id)
     print("conforms?", conf.check_nominal(log=True))
 
@@ -86,17 +88,22 @@ def main():
     dsl_m_id = state.create_node()
     dsl_m_od = OD(dsl_mm_id, dsl_m_id, state)
 
+    # dsl_m_od.create_object("animal", "Animal")
     dsl_m_od.create_object("george", "Man")
+    dsl_m_od.create_slot("weight", "george",
+        dsl_m_od.create_integer_value("george.weight", 80))
+
+    # "george_weight"
+
     dsl_m_od.create_object("bear1", "Bear")
     dsl_m_od.create_object("bear2", "Bear")
     dsl_m_od.create_link("georgeAfraidOfBear1", "afraidOf", "george", "bear1")
     dsl_m_od.create_link("georgeAfraidOfBear2", "afraidOf", "george", "bear2")
 
-    dsl_m_od.create_slot("weight", "george",
-        dsl_m_od.create_integer_value("george.weight", 80))
 
     conf2 = Conformance(state, dsl_m_id, dsl_mm_id)
-    print("Model conforms?", conf2.check_nominal(log=True))
+    print("DSL instance conforms?", conf2.check_nominal(log=True))
+    print(conf2.type_mapping)
 
     # RAMify MM
     ramified_mm_id = ramify(state, dsl_mm_id)
@@ -105,10 +112,10 @@ def main():
     lhs_id = state.create_node()
     lhs_od = OD(ramified_mm_id, lhs_id, state)
 
-    lhs_od.create_object("man", "RAM_Man")
-    lhs_od.create_slot("RAM_weight", "man", lhs_od.create_string_value("man.RAM_weight", 'v < 99'))
-    lhs_od.create_object("scaryAnimal", "RAM_Animal")
-    lhs_od.create_link("manAfraidOfAnimal", "RAM_afraidOf", "man", "scaryAnimal")
+    lhs_od.create_object("man", "Man")
+    lhs_od.create_slot("weight", "man", lhs_od.create_string_value("man.weight", 'v < 99'))
+    lhs_od.create_object("scaryAnimal", "Animal")
+    lhs_od.create_link("manAfraidOfAnimal", "afraidOf", "man", "scaryAnimal")
 
     conf3 = Conformance(state, lhs_id, ramified_mm_id)
     print("LHS conforms?", conf3.check_nominal(log=True))
@@ -117,8 +124,13 @@ def main():
     rhs_id = state.create_node()
     rhs_od = OD(ramified_mm_id, rhs_id, state)
 
-    rhs_od.create_object("man", "RAM_Man")
-    rhs_od.create_slot("RAM_weight", "man", rhs_od.create_string_value("man.RAM_weight", 'v + 5'))
+    rhs_od.create_object("man", "Man")
+    rhs_od.create_slot("weight", "man", rhs_od.create_string_value("man.weight", 'v + 5'))
+
+    rhs_od.create_object("bill", "Man")
+    rhs_od.create_slot("weight", "bill", rhs_od.create_string_value("bill.weight", '100'))
+
+    rhs_od.create_link("billAfraidOfMan", "afraidOf", "bill", "man")
 
     conf4 = Conformance(state, rhs_id, ramified_mm_id)
     print("RHS conforms?", conf4.check_nominal(log=True))
@@ -137,7 +149,6 @@ def main():
 
     print("matching...")
     matcher = MatcherVF2(host, guest, mvs_adapter.RAMCompare(Bottom(state), dsl_m_od))
-    prev = None
     for m in matcher.match():
         print("\nMATCH:\n", m)
         name_to_matched = {}
@@ -145,7 +156,7 @@ def main():
             if isinstance(guest_vtx, mvs_adapter.NamedNode) and isinstance(host_vtx, mvs_adapter.NamedNode):
                 name_to_matched[guest_vtx.name] = host_vtx.name
         print(name_to_matched)
-        rewriter.rewrite(state, lhs_id, rhs_id, name_to_matched, dsl_m_id)
+        rewriter.rewrite(state, lhs_id, rhs_id, ramified_mm_id, name_to_matched, dsl_m_id, dsl_mm_id)
         break
     print("DONE")
 

+ 1 - 0
pattern_matching/mvs_adapter.py

@@ -227,6 +227,7 @@ class RAMCompare:
 
     # Memoizing the result of comparison gives a huge performance boost!
     # Especially `is_subtype_of` is very slow, and will be performed many times over on the same pair of nodes during the matching process.
+    # Assuming the model is not altered *during* matching, this is safe.
     @functools.cache
     def __call__(self, g_vtx, h_vtx):
         # First check if the types match (if we have type-information)

+ 73 - 11
services/od.py

@@ -3,6 +3,7 @@ from state.base import State
 from services.bottom.V0 import Bottom
 from services.primitives.integer_type import Integer
 from services.primitives.string_type import String
+from services.primitives.boolean_type import Boolean
 from typing import Optional
 
 def get_attr_link_name(class_name: str, attr_name: str):
@@ -29,13 +30,21 @@ class OD:
 
     def create_object(self, name: str, class_name: str):
         class_node, = self.bottom.read_outgoing_elements(self.type_model, class_name)
-
-
         abstract_nodes = self.bottom.read_outgoing_elements(self.type_model, f"{class_name}.abstract")
-        if len(abstract_nodes) == 1:
-            is_abstract = self.bottom.read_value(abstract_node)
+        return self._create_object(name, class_node)
+
+    def _create_object(self, name: str, class_node: UUID):
+        # Look at our `type_model` as if it's an object diagram:
+        mm_od = OD(
+            get_scd_mm(self.bottom), # the type model of our type model
+            self.type_model,
+            self.bottom.state)
+        # # Read the 'abstract' slot of the class
+        abstract_slot = mm_od.get_slot(class_node, "abstract")
+        print('abstract_slot:', abstract_slot)
+        if abstract_slot != None:
+            is_abstract = Boolean(abstract_slot, self.bottom.state).read()
         else:
-            # 'abstract' is optional attribute, default is False
             is_abstract = False
 
         if is_abstract:
@@ -51,6 +60,7 @@ class OD:
         object_node, = self.bottom.read_outgoing_elements(self.model, object_name) # get the object
         return self._get_class_of_object(object_node)
 
+
     def _get_class_of_object(self, object_node: UUID):
         type_el, = self.bottom.read_outgoing_elements(object_node, "Morphism")
         for key in self.bottom.read_keys(self.type_model):
@@ -61,8 +71,11 @@ class OD:
     def create_slot(self, attr_name: str, object_name: str, target_name: str):
         class_name = self.get_class_of_object(object_name)
         attr_link_name = get_attr_link_name(class_name, attr_name)
+        print('attr_link_name:', attr_link_name)
         # An attribute-link is indistinguishable from an ordinary link:
-        return self.create_link(None, attr_link_name, object_name, target_name)
+        return self.create_link(
+            get_attr_link_name(object_name, attr_name),
+            attr_link_name, object_name, target_name)
 
     def get_slot(self, object_node: UUID, attr_name: str):
         # I really don't like how complex and inefficient it is to read an attribute of an object...
@@ -109,8 +122,6 @@ class OD:
         src_obj_node, = self.bottom.read_outgoing_elements(self.model, src_obj_name)
         tgt_obj_node, = self.bottom.read_outgoing_elements(self.model, tgt_obj_name)
 
-        link_edge = self.bottom.create_edge(src_obj_node, tgt_obj_node)
-
         # generate a unique name for the link
         if link_name == None:
             i = 0;
@@ -119,9 +130,60 @@ class OD:
                 if len(self.bottom.read_outgoing_elements(self.model, link_name)) == 0:
                     break
                 i += 1
-
-        self.bottom.create_edge(self.model, link_edge, link_name)
+        print('link_name:', link_name)
 
         type_edge, = self.bottom.read_outgoing_elements(self.type_model, assoc_name)
-        self.bottom.create_edge(link_edge, type_edge, "Morphism")
 
+        return self._create_link(link_name, type_edge, src_obj_node, tgt_obj_node)
+
+    def _create_link(self, link_name: str, type_edge: str, src_obj_node: UUID, tgt_obj_node: UUID):
+        # the link itself is unlabeled:
+        link_edge = self.bottom.create_edge(src_obj_node, tgt_obj_node)
+        # it is only in the context of the model, that the link has a name:
+        self.bottom.create_edge(self.model, link_edge, link_name) # add to model
+        self.bottom.create_edge(link_edge, type_edge, "Morphism")
+        return link_edge
+
+def get_types(bottom: Bottom, obj: UUID):
+    return bottom.read_outgoing_elements(obj, "Morphism")
+
+def get_type(bottom: Bottom, obj: UUID):
+    types = get_types(bottom, obj)
+    if len(types) == 1:
+        return types[0]
+    elif len(types) > 1:
+        raise Exception(f"Expected at most one type. Instead got {len(types)}.")
+
+def is_typed_by(bottom, el: UUID, typ: UUID):
+    for typed_by in get_types(bottom, el):
+        if typed_by == typ:
+            return True
+    return False
+
+def get_scd_mm(bottom):
+    scd_metamodel_id = bottom.state.read_dict(bottom.state.read_root(), "SCD")
+    scd_metamodel = UUID(bottom.state.read_value(scd_metamodel_id))
+    return scd_metamodel    
+
+def get_scd_mm_class_node(bottom: Bottom):
+    return get_scd_mm_node(bottom, "Class")
+
+def get_scd_mm_attributelink_node(bottom: Bottom):
+    return get_scd_mm_node(bottom, "AttributeLink")
+
+def get_scd_mm_assoc_node(bottom: Bottom):
+    return get_scd_mm_node(bottom, "Association")
+
+def get_scd_mm_modelref_node(bottom: Bottom):
+    return get_scd_mm_node(bottom, "ModelRef")
+
+def get_scd_mm_node(bottom: Bottom, node_name: str):
+    scd_metamodel = get_scd_mm(bottom)
+    node, = bottom.read_outgoing_elements(scd_metamodel, node_name)
+    return node
+
+def get_object_name(bottom: Bottom, model: UUID, object_node: UUID):
+    for key in bottom.read_keys(model):
+        for el in bottom.read_outgoing_elements(model, key):
+            if el == object_node:
+                return key

+ 4 - 0
services/primitives/boolean_type.py

@@ -18,3 +18,7 @@ class Boolean:
         self.bottom.create_edge(self.model, _instance, "boolean")
         _type, = self.bottom.read_outgoing_elements(self.type_model, "Boolean")
         self.bottom.create_edge(_instance, _type, "Morphism")
+
+    def read(self):
+        instance, = self.bottom.read_outgoing_elements(self.model, "boolean")
+        return self.bottom.read_value(instance)

+ 4 - 0
services/primitives/string_type.py

@@ -18,3 +18,7 @@ class String:
         self.bottom.create_edge(self.model, _instance, "string")
         _type, = self.bottom.read_outgoing_elements(self.type_model, "String")
         self.bottom.create_edge(_instance, _type, "Morphism")
+
+    def read(self):
+        instance, = self.bottom.read_outgoing_elements(self.model, "string")
+        return self.bottom.read_value(instance)

+ 4 - 1
services/scd.py

@@ -327,8 +327,11 @@ class SCD:
 
 
     def get_attributes(self, class_name: str):
-        attr_link_node, = self.bottom.read_outgoing_elements(self.scd_model, "AttributeLink")
         class_node, = self.bottom.read_outgoing_elements(self.model, class_name)
+        return self._get_attributes(class_node)
+
+    def _get_attributes(self, class_node: UUID):
+        attr_link_node, = self.bottom.read_outgoing_elements(self.scd_model, "AttributeLink")
         name_to_attr = {}
         for name in self.bottom.read_keys(class_node):
             edges = self.bottom.read_outgoing_edges(class_node, name)

+ 4 - 4
transformation/ramify.py

@@ -6,7 +6,7 @@ from framework.conformance import Conformance
 
 RAMIFIES_LABEL = "RAMifies"
 
-def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
+def ramify(state: State, model: UUID, prefix = "") -> UUID:
 
     # def print_tree(root, max_depth, depth=0):
     #     print("  "*depth, "root=", root, "value=", state.read_value(root))
@@ -174,7 +174,7 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
 # Every RAMified type has a link to its original type
 def get_original_type(bottom, typ: UUID):
     original_types = bottom.read_outgoing_elements(typ, RAMIFIES_LABEL)
-    if len(original_types) != 1:
-        raise Exception("Expected 1 original type, got " + str(len(original_types)))
-    else:
+    if len(original_types) > 1:
+        raise Exception("Expected at most 1 original type, got " + str(len(original_types)))
+    elif len(original_types) == 1:
         return original_types[0]

+ 149 - 5
transformation/rewriter.py

@@ -5,6 +5,10 @@
 
 from uuid import UUID
 from services.bottom.V0 import Bottom
+from transformation import ramify
+from services import od
+from services.primitives.string_type import String
+from services.primitives.integer_type import Integer
 
 def process_rule(state, lhs: UUID, rhs: UUID):
     bottom = Bottom(state)
@@ -12,25 +16,165 @@ def process_rule(state, lhs: UUID, rhs: UUID):
     # : bottom.read_outgoing_elements(rhs, name)[0]
     to_delete = { name for name in bottom.read_keys(lhs) if name not in bottom.read_keys(rhs) }
     to_create = { name for name in bottom.read_keys(rhs) if name not in bottom.read_keys(lhs) }
+    common = { name for name in bottom.read_keys(lhs) if name in bottom.read_keys(rhs) }
 
     print("to_delete:", to_delete)
     print("to_create:", to_create)
 
-    return to_delete, to_create
+    return to_delete, to_create, common
 
-def rewrite(state, lhs: UUID, rhs: UUID, match_mapping: dict, model_to_transform: UUID) -> UUID:
+def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to_transform: UUID, mm: UUID) -> UUID:
     bottom = Bottom(state)
 
-    to_delete, to_create = process_rule(state, lhs, rhs)
+    scd_metamodel_id = state.read_dict(state.read_root(), "SCD")
+    scd_metamodel = UUID(state.read_value(scd_metamodel_id))
 
+    class_type = od.get_scd_mm_class_node(bottom)
+    attr_link_type = od.get_scd_mm_attributelink_node(bottom)
+    assoc_type = od.get_scd_mm_assoc_node(bottom)
+    modelref_type = od.get_scd_mm_modelref_node(bottom)
+
+    m_od = od.OD(mm, m_to_transform, bottom.state)
+    rhs_od = od.OD(rhs_mm, rhs, bottom.state)
+
+    print('rhs type:', od.get_type(bottom, rhs))
+
+    to_delete, to_create, common = process_rule(state, lhs, rhs)
+
+    # Perform deletions
     for pattern_name_to_delete in to_delete:
         # For every name in `to_delete`, look up the name of the matched element in the host graph
         model_element_name_to_delete = match_mapping[pattern_name_to_delete]
         print('deleting', model_element_name_to_delete)
         # Look up the matched element in the host graph
-        element_to_delete, = bottom.read_outgoing_elements(model_to_transform, model_element_name_to_delete)
+        element_to_delete, = bottom.read_outgoing_elements(m_to_transform, model_element_name_to_delete)
         # Delete
         bottom.delete_element(element_to_delete)
 
+    extended_mapping = dict(match_mapping) # will be extended with created elements
+    edges_to_create = [] # postpone creation of edges after creation of nodes
+
+    # Perform creations
     for pattern_name_to_create in to_create:
-        pass
+        print('creating', pattern_name_to_create)
+        # We have to come up with a name for the element-to-create in the host graph
+        i = 0
+        while True:
+            model_element_name_to_create = pattern_name_to_create + str(i) # use the label of the element in the RHS as a basis
+            if len(bottom.read_outgoing_elements(m_to_transform, model_element_name_to_create)) == 0:
+                break # found an available name
+        
+        # Determine the type of the thing to create
+        rhs_element_to_create, = bottom.read_outgoing_elements(rhs, pattern_name_to_create)
+        rhs_type = od.get_type(bottom, rhs_element_to_create)
+        original_type = ramify.get_original_type(bottom, rhs_type)
+        if original_type != None:
+            # Now get the type of the type
+            if od.is_typed_by(bottom, original_type, class_type):
+                # It's type is typed by Class -> it's an object
+                print(' -> creating object')
+                o = m_od._create_object(model_element_name_to_create, original_type)
+                extended_mapping[pattern_name_to_create] = model_element_name_to_create
+            elif od.is_typed_by(bottom, original_type, attr_link_type):
+                print(' -> postpone (is attribute link)')
+                edges_to_create.append((pattern_name_to_create, rhs_element_to_create, original_type, 'attribute link', rhs_type, model_element_name_to_create))
+            elif od.is_typed_by(bottom, original_type, assoc_type):
+                print(' -> postpone (is link)')
+                edges_to_create.append((pattern_name_to_create, rhs_element_to_create, original_type, 'link', rhs_type, model_element_name_to_create))
+            else:
+                original_type_name = od.get_object_name(bottom, mm, original_type)
+                print(" -> warning: don't know about", original_type_name)
+        else:
+            print(" -> no original (un-RAMified) type")
+            # assume the type of the object is already the original type
+            # this is because primitive types (e.g., Integer) are not RAMified
+            type_name = od.get_object_name(bottom, rhs_mm, rhs_type)
+            if type_name == "String":
+                s_model = UUID(bottom.read_value(rhs_element_to_create))
+                python_expr = String(s_model, bottom.state).read()
+                result = eval(python_expr, {}, {})
+                print('result:', result)
+                if isinstance(result, int):
+                    m_od.create_integer_value(model_element_name_to_create, result)
+                elif isinstance(result, str):
+                    m_od.create_string_value(model_element_name_to_create, result)
+                extended_mapping[pattern_name_to_create] = model_element_name_to_create
+
+
+    print('extended_mapping:', extended_mapping)
+
+    print("create edges....")
+    for pattern_name_to_create, rhs_element_to_create, original_type, original_type_name, rhs_type, model_element_name_to_create in edges_to_create:
+        print('creating', pattern_name_to_create)
+        if original_type_name == 'attribute link':
+            print(' -> creating attribute link')
+            src = bottom.read_edge_source(rhs_element_to_create)
+            src_name = od.get_object_name(bottom, rhs, src)
+            tgt = bottom.read_edge_target(rhs_element_to_create)
+            tgt_name = od.get_object_name(bottom, rhs, tgt)
+            obj_name = extended_mapping[src_name] # name of object in host graph to create slot for
+            attr_name = od.get_object_name(bottom, mm, original_type)
+            class_name = m_od.get_class_of_object(obj_name)
+            # Just when you thought the code couldn't get any dirtier:
+            attribute_name = attr_name[len(class_name)+1:]
+            # print(attribute_name, obj_name, extended_mapping[tgt_name])
+            m_od.create_slot(attribute_name, obj_name, extended_mapping[tgt_name])
+        elif original_type_name == 'link':
+            print(' -> creating link')
+            src = bottom.read_edge_source(rhs_element_to_create)
+            src_name = od.get_object_name(bottom, rhs, src)
+            tgt = bottom.read_edge_target(rhs_element_to_create)
+            tgt_name = od.get_object_name(bottom, rhs, tgt)
+            obj_name = extended_mapping[src_name] # name of object in host graph to create slot for
+            attr_name = od.get_object_name(bottom, mm, original_type)
+            class_name = m_od.get_class_of_object(obj_name)
+            # print(attr_name, obj_name, extended_mapping[tgt_name])
+            m_od.create_link(model_element_name_to_create, attr_name, obj_name, extended_mapping[tgt_name])
+
+    # Perform updates
+    for pattern_element_name in common:
+        model_element_name = match_mapping[pattern_element_name]
+        print('updating', model_element_name)
+        model_element, = bottom.read_outgoing_elements(m_to_transform, model_element_name)
+        old_value = bottom.read_value(model_element)
+        print('old value:', old_value)
+        host_type = od.get_type(bottom, model_element)
+        if od.is_typed_by(bottom, host_type, class_type):
+            print(' -> is classs')
+        elif od.is_typed_by(bottom, host_type, attr_link_type):
+            print(' -> is attr link')
+        elif od.is_typed_by(bottom, host_type, modelref_type):
+            print(' -> is modelref')
+            referred_model_id = UUID(bottom.read_value(model_element))
+            # referred_model_type = od.get_type(bottom, referred_model_id) # None
+            # print('referred_model_type:', referred_model_type)
+
+            host_type_name = od.get_object_name(bottom, mm, host_type)
+            print('host_type_name:', host_type_name)
+            if host_type_name == "Integer":
+                v = Integer(UUID(old_value), state).read()
+            elif host_type_name == "String":
+                v = String(UUID(old_value), state).read()
+            else:
+                raise Exception("Unimplemented type:", host_type_name)
+
+            # the referred model itself doesn't have a type, so we have to look at the type of the ModelRef element in the RHS-MM:
+            rhs_element, = bottom.read_outgoing_elements(rhs, pattern_element_name)
+            rhs_type = od.get_type(bottom, rhs_element)
+            rhs_type_name = od.get_object_name(bottom, rhs_mm, rhs_type)
+            print("rhs_type_name:", rhs_type_name)
+
+            print(od.get_object_name(bottom, mm, model_element))
+
+            if rhs_type_name == "String":
+                python_expr = String(UUID(bottom.read_value(rhs_element)), state).read()
+                result = eval(python_expr, {}, {'v': v})
+                print('eval result=', result)
+                if isinstance(result, int):
+                    # overwrite the old value
+                    Integer(UUID(old_value), state).create(result)
+                else:
+                    raise Exception("Unimplemented type. Value:", result)
+
+
+        # type_name = od.get_object_name()