소스 검색

Class diagram can be rendered as object diagram textual syntax, and parsed back, without information loss

Joeri Exelmans 1 년 전
부모
커밋
175edb64d9

+ 3 - 3
bootstrap/primitive.py

@@ -16,7 +16,7 @@ def bootstrap_type(type_name: str, python_type: str, scd_root: UUID, model_root:
     min_c_node = bottom.create_node(str(min_c_model))
     bottom.create_edge(model_root, min_c_node, f"{type_name}.lower_cardinality")
     min_c_link = bottom.create_edge(class_node, min_c_node)
-    bottom.create_edge(model_root, min_c_link, f"{type_name}.lower_cardinality_link")
+    bottom.create_edge(model_root, min_c_link, f"{type_name}_lower_cardinality")
     scd_node, = bottom.read_outgoing_elements(scd_root, "Integer")
     scd_link, = bottom.read_outgoing_elements(scd_root, "Class_lower_cardinality")
     bottom.create_edge(min_c_node, scd_node, "Morphism")
@@ -27,7 +27,7 @@ def bootstrap_type(type_name: str, python_type: str, scd_root: UUID, model_root:
     max_c_node = bottom.create_node(str(max_c_model))
     bottom.create_edge(model_root, max_c_node, f"{type_name}.upper_cardinality")
     max_c_link = bottom.create_edge(class_node, max_c_node)
-    bottom.create_edge(model_root, max_c_link, f"{type_name}.upper_cardinality_link")
+    bottom.create_edge(model_root, max_c_link, f"{type_name}_upper_cardinality")
     scd_node, = bottom.read_outgoing_elements(scd_root, "Integer")
     scd_link, = bottom.read_outgoing_elements(scd_root, "Class_upper_cardinality")
     bottom.create_edge(max_c_node, scd_node, "Morphism")
@@ -36,7 +36,7 @@ def bootstrap_type(type_name: str, python_type: str, scd_root: UUID, model_root:
     constraint_node = bottom.create_node(f"isinstance(read_value(element),{python_type})")
     bottom.create_edge(model_root, constraint_node, f"{type_name}.constraint")
     constraint_link = bottom.create_edge(class_node, constraint_node)
-    bottom.create_edge(model_root, constraint_link, f"{type_name}.constraint_link")
+    bottom.create_edge(model_root, constraint_link, f"{type_name}_constraint")
     scd_node, = bottom.read_outgoing_elements(scd_root, "ActionCode")
     scd_link, = bottom.read_outgoing_elements(scd_root, "Element_constraint")
     bottom.create_edge(constraint_node, scd_node, "Morphism")

+ 24 - 24
bootstrap/scd.py

@@ -47,10 +47,10 @@ def bootstrap_scd(state: State) -> UUID:
     def add_attribute_attributes(attribute_element_name, attribute_element):
         _name_model = bottom.create_node()
         _name_node = add_node_element(f"{attribute_element_name}.name", str(_name_model))
-        _name_edge = add_edge_element(f"{attribute_element_name}.name_link", attribute_element, _name_node)
+        _name_edge = add_edge_element(f"{attribute_element_name}_name", attribute_element, _name_node)
         _optional_model = bottom.create_node()
         _optional_node = add_node_element(f"{attribute_element_name}.optional", str(_optional_model))
-        _optional_edge = add_edge_element(f"{attribute_element_name}.optional_link", attribute_element, _optional_node)
+        _optional_edge = add_edge_element(f"{attribute_element_name}_optional", attribute_element, _optional_node)
         return _name_model, _optional_model
 
     ##### SCD META-MODEL #####
@@ -171,7 +171,7 @@ def bootstrap_scd(state: State) -> UUID:
     # # Make Element abstract
     abs_model = bottom.create_node()
     abs_node = add_node_element(f"Element.abstract", str(abs_model))
-    abs_edge = add_edge_element(f"Element.abstract_link", element_node, abs_node)
+    abs_edge = add_edge_element(f"Element_abstract", element_node, abs_node)
     Boolean(abs_model, state).create(True)
 
     # create phi(SCD,SCD) to type MCL with itself
@@ -220,27 +220,27 @@ def bootstrap_scd(state: State) -> UUID:
     add_mcl_morphism("Association_target_lower_cardinality", "AttributeLink")
     add_mcl_morphism("Association_target_upper_cardinality", "AttributeLink")
     # AttributeLink_name
-    add_mcl_morphism("AttributeLink_name.name_link", "AttributeLink_name")
-    add_mcl_morphism("AttributeLink_optional.name_link", "AttributeLink_name")
-    add_mcl_morphism("Element_constraint.name_link", "AttributeLink_name")
-    add_mcl_morphism("Class_abstract.name_link", "AttributeLink_name")
-    add_mcl_morphism("Class_lower_cardinality.name_link", "AttributeLink_name")
-    add_mcl_morphism("Class_upper_cardinality.name_link", "AttributeLink_name")
-    add_mcl_morphism("Association_source_lower_cardinality.name_link", "AttributeLink_name")
-    add_mcl_morphism("Association_source_upper_cardinality.name_link", "AttributeLink_name")
-    add_mcl_morphism("Association_target_lower_cardinality.name_link", "AttributeLink_name")
-    add_mcl_morphism("Association_target_upper_cardinality.name_link", "AttributeLink_name")
+    add_mcl_morphism("AttributeLink_name_name", "AttributeLink_name")
+    add_mcl_morphism("AttributeLink_optional_name", "AttributeLink_name")
+    add_mcl_morphism("Element_constraint_name", "AttributeLink_name")
+    add_mcl_morphism("Class_abstract_name", "AttributeLink_name")
+    add_mcl_morphism("Class_lower_cardinality_name", "AttributeLink_name")
+    add_mcl_morphism("Class_upper_cardinality_name", "AttributeLink_name")
+    add_mcl_morphism("Association_source_lower_cardinality_name", "AttributeLink_name")
+    add_mcl_morphism("Association_source_upper_cardinality_name", "AttributeLink_name")
+    add_mcl_morphism("Association_target_lower_cardinality_name", "AttributeLink_name")
+    add_mcl_morphism("Association_target_upper_cardinality_name", "AttributeLink_name")
     # AttributeLink_optional
-    add_mcl_morphism("AttributeLink_name.optional_link", "AttributeLink_optional")
-    add_mcl_morphism("AttributeLink_optional.optional_link", "AttributeLink_optional")
-    add_mcl_morphism("Element_constraint.optional_link", "AttributeLink_optional")
-    add_mcl_morphism("Class_abstract.optional_link", "AttributeLink_optional")
-    add_mcl_morphism("Class_lower_cardinality.optional_link", "AttributeLink_optional")
-    add_mcl_morphism("Class_upper_cardinality.optional_link", "AttributeLink_optional")
-    add_mcl_morphism("Association_source_lower_cardinality.optional_link", "AttributeLink_optional")
-    add_mcl_morphism("Association_source_upper_cardinality.optional_link", "AttributeLink_optional")
-    add_mcl_morphism("Association_target_lower_cardinality.optional_link", "AttributeLink_optional")
-    add_mcl_morphism("Association_target_upper_cardinality.optional_link", "AttributeLink_optional")
+    add_mcl_morphism("AttributeLink_name_optional", "AttributeLink_optional")
+    add_mcl_morphism("AttributeLink_optional_optional", "AttributeLink_optional")
+    add_mcl_morphism("Element_constraint_optional", "AttributeLink_optional")
+    add_mcl_morphism("Class_abstract_optional", "AttributeLink_optional")
+    add_mcl_morphism("Class_lower_cardinality_optional", "AttributeLink_optional")
+    add_mcl_morphism("Class_upper_cardinality_optional", "AttributeLink_optional")
+    add_mcl_morphism("Association_source_lower_cardinality_optional", "AttributeLink_optional")
+    add_mcl_morphism("Association_source_upper_cardinality_optional", "AttributeLink_optional")
+    add_mcl_morphism("Association_target_lower_cardinality_optional", "AttributeLink_optional")
+    add_mcl_morphism("Association_target_upper_cardinality_optional", "AttributeLink_optional")
     # String
     add_mcl_morphism("AttributeLink_name.name", "String")
     add_mcl_morphism("AttributeLink_optional.name", "String")
@@ -265,7 +265,7 @@ def bootstrap_scd(state: State) -> UUID:
     add_mcl_morphism("Association_target_upper_cardinality.optional", "Boolean")
     add_mcl_morphism("Element.abstract", "Boolean")
     # Class_abstract
-    add_mcl_morphism("Element.abstract_link", "Class_abstract")
+    add_mcl_morphism("Element_abstract", "Class_abstract")
 
     return mcl_root
 

+ 1 - 1
renderer/plantuml.py

@@ -196,7 +196,7 @@ def render_trace_match(state, name_mapping: dict, pattern_m: UUID, host_m: UUID,
     render_suffix = f"#line:{color};line.dotted;text:{color} : matchedWith"
 
     for pattern_el_name, host_el_name in name_mapping.items():
-        print(pattern_el_name, host_el_name)
+        # print(pattern_el_name, host_el_name)
         try:
             pattern_el, = bottom.read_outgoing_elements(pattern_m, pattern_el_name)
             host_el, = bottom.read_outgoing_elements(host_m, host_el_name)

+ 121 - 0
concrete_syntax/textual_od/parser.py

@@ -0,0 +1,121 @@
+# Parser for Object Diagrams textual concrete syntax
+
+from lark import Lark, logger, Transformer
+from lark.indenter import Indenter
+from services.od import OD
+from services.scd import SCD
+from uuid import UUID
+
+grammar = r"""
+%import common.WS_INLINE
+%ignore WS_INLINE
+%ignore COMMENT
+
+%declare _INDENT _DEDENT
+
+?start: (_NL | object )*
+
+IDENTIFIER: /[A-Za-z_][A-Za-z_0-9]*/
+COMMENT: /#.*/
+
+# newline
+_NL: /(\r?\n[\t ]*)+/
+
+literal: INT
+       | STR
+       | BOOL
+
+INT: /[0-9]+/
+STR: /"[^"]*"/
+   | /'[^']*'/
+BOOL: "True" | "False"
+
+object: [IDENTIFIER] ":" IDENTIFIER [link] _NL [_INDENT slot+ _DEDENT]
+link: "(" IDENTIFIER "->" IDENTIFIER ")"
+slot: IDENTIFIER "=" literal _NL
+"""
+
+
+class TreeIndenter(Indenter):
+    NL_type = '_NL'
+    OPEN_PAREN_types = []
+    CLOSE_PAREN_types = []
+    INDENT_type = '_INDENT'
+    DEDENT_type = '_DEDENT'
+    tab_len = 4
+
+parser = Lark(grammar, parser='lalr', postlex=TreeIndenter())
+
+# given a concrete syntax text string, and a meta-model, parses the CS
+def parse_od(state, cs_text, mm):
+    tree = parser.parse(cs_text)
+
+    m = state.create_node()
+    od = OD(mm, m, state)
+
+    int_mm_id = UUID(state.read_value(state.read_dict(state.read_root(), "Integer")))
+
+    class T(Transformer):
+        def __init__(self, visit_tokens):
+            super().__init__(visit_tokens)
+            self.obj_counter = 0
+
+        def IDENTIFIER(self, token):
+            return str(token)
+        
+        def INT(self, token):
+            return int(token)
+
+        def BOOL(self, token):
+            return token == "True"
+
+        def STR(self, token):
+            return str(token[1:-1]) # strip the ""
+
+        def literal(self, el):
+            return el[0]
+
+        def link(self, el):
+            [src, tgt] = el
+            return (src, tgt)
+        
+        def slot(self, el):
+            [attr_name, value] = el
+            return (attr_name, value)
+        
+        def object(self, el):
+            [obj_name, type_name, link] = el[0:3]
+            if obj_name == None:
+                # object/link names are optional
+                #  generate a unique name if no name given
+                obj_name = f"__o{self.obj_counter}"
+                self.obj_counter += 1
+            if link == None:
+                obj_node = od.create_object(obj_name, type_name)
+            else:
+                src, tgt = link
+                if tgt == "Integer":
+                    if state.read_dict(m, "Integer") == None:
+                        scd = SCD(m, state)
+                        scd.create_model_ref("Integer", int_mm_id)
+                od.create_link(obj_name, type_name, src, tgt)
+            # Create slots
+            slots = el[3:]
+            for attr_name, value in slots:
+                value_name = f"{obj_name}.{attr_name}"
+                # watch out: in Python, 'bool' is subtype of 'int'
+                #  so we must check for 'bool' first
+                if isinstance(value, bool):
+                    tgt = od.create_boolean_value(value_name, value)
+                elif isinstance(value, int):
+                    tgt = od.create_integer_value(value_name, value)
+                elif isinstance(value, str):
+                    tgt = od.create_string_value(value_name, value)
+                else:
+                    raise Exception("Unimplemented type "+value)
+                od.create_slot(attr_name, obj_name, tgt)
+            return obj_name
+
+    t = T(visit_tokens=True).transform(tree)
+
+    return m

+ 1 - 0
concrete_syntax/textual_od/readme.txt

@@ -0,0 +1 @@
+This directory contains the parser and renderer for the textual concrete syntax for Object Diagrams.

+ 43 - 0
concrete_syntax/textual_od/renderer.py

@@ -0,0 +1,43 @@
+# Renderer for Object Diagrams textual concrete syntax
+
+from services import od
+from services.bottom.V0 import Bottom
+import json
+
+def display_value(val: any):
+    if isinstance(val, str):
+        return '"'+val+'"'
+    elif isinstance(val, int) or isinstance(val, bool):
+        return str(val)
+    else:
+        raise Exception("don't know how to display value" + str(val))
+
+def render_od(state, m_id, mm_id, hide_names=True):
+    bottom = Bottom(state)
+    output = ""
+    
+    m_od = od.OD(mm_id, m_id, state)
+
+    def display_name(name: str):
+        # object names that start with "__" are hidden
+        return name if (name[0:2] != "__" or not hide_names) else ""
+
+    def write_attributes(object_node):
+        o = ""
+        for attr_name, slot_node in m_od.get_slots(object_node):
+            value = m_od.read_slot(slot_node)
+            o += f"    {attr_name} = {display_value(value)}\n"
+        return o
+
+    for class_name, objects in m_od.get_all_objects().items():
+        for object_name, object_node in objects.items():
+            output += f"{display_name(object_name)}:{class_name}\n"
+            output += write_attributes(object_node)
+
+    for assoc_name, links in m_od.get_all_links().items():
+        for link_name, (link_edge, src_name, tgt_name) in links.items():
+            output += f"{display_name(link_name)}:{assoc_name} ({src_name} -> {tgt_name})\n"
+            # links can also have slots:
+            output += write_attributes(link_edge)
+
+    return output

+ 108 - 88
experiments/exp_scd.py

@@ -12,7 +12,8 @@ from services.bottom.V0 import Bottom
 from services.primitives.integer_type import Integer
 from pattern_matching import mvs_adapter
 from pattern_matching.matcher import MatcherVF2
-from renderer import plantuml
+from concrete_syntax import plantuml
+from concrete_syntax.textual_od import parser, renderer
 
 import sys
 
@@ -30,121 +31,138 @@ def main():
     int_mm_id = UUID(state.read_value(state.read_dict(state.read_root(), "Integer")))
     string_mm_id = UUID(state.read_value(state.read_dict(state.read_root(), "String")))
 
-    # def print_tree(root, max_depth, depth=0):
-    #     print("  "*depth, "root=", root, "value=", state.read_value(root))
-    #     src,tgt = state.read_edge(root)
-    #     if src != None:
-    #         print("  "*depth, "src...")
-    #         print_tree(src, max_depth, depth+1)
-    #     if tgt != None:
-    #         print("  "*depth, "tgt...")
-    #         print_tree(tgt, max_depth, depth+1)
-    #     for edge in state.read_outgoing(root):
-    #         for edge_label in state.read_outgoing(edge):
-    #             [_,tgt] = state.read_edge(edge_label)
-    #             label = state.read_value(tgt)
-    #             print("  "*depth, " key:", label)
-    #         [_, tgt] = state.read_edge(edge)
-    #         value = state.read_value(tgt)
-    #         if value != None:
-    #             print("  "*depth, " ->", tgt, " (value:", value, ")")
-    #         else:
-    #             print("  "*depth, " ->", tgt)
-    #         if depth < max_depth:
-    #             if isinstance(value, str) and len(value) == 36:
-    #                 i = None
-    #                 try:
-    #                     i = UUID(value)
-    #                 except ValueError as e:
-    #                     # print("invalid UUID:", value)
-    #                     pass
-    #                 if i != None:
-    #                     print_tree(i, max_depth, depth+1)
-    #             print_tree(tgt, max_depth, depth+1)
-
-    # Meta-model for our DSL
-    dsl_mm_id = state.create_node()
-    dsl_mm_scd = SCD(dsl_mm_id, state)
-    dsl_mm_scd.create_class("Animal", abstract=True)
-    dsl_mm_scd.create_class("Man", min_c=1, max_c=2)
-    dsl_mm_scd.create_inheritance("Man", "Animal")
-    dsl_mm_scd.create_model_ref("Integer", int_mm_id)
-    dsl_mm_scd.create_attribute_link("Man", "Integer", "weight", optional=False)
-    dsl_mm_scd.create_class("Bear")
-    dsl_mm_scd.create_inheritance("Bear", "Animal")
-    dsl_mm_scd.create_association("afraidOf", "Man", "Animal",
-        # Every Man afraid of at least one Animal:
-        src_min_c=0,
-        src_max_c=None,
-        tgt_min_c=1,
-        tgt_max_c=None,
-    )
-
-    print(dsl_mm_scd.list_elements())
+    def create_dsl_mm_api():
+        # Create DSL MM with SCD API
+        dsl_mm_id = state.create_node()
+        dsl_mm_scd = SCD(dsl_mm_id, state)
+        dsl_mm_scd.create_class("Animal", abstract=True)
+        dsl_mm_scd.create_class("Man", min_c=1, max_c=2)
+        dsl_mm_scd.create_inheritance("Man", "Animal")
+        dsl_mm_scd.create_model_ref("Integer", int_mm_id)
+        dsl_mm_scd.create_attribute_link("Man", "Integer", "weight", optional=False)
+        dsl_mm_scd.create_class("Bear")
+        dsl_mm_scd.create_inheritance("Bear", "Animal")
+        dsl_mm_scd.create_association("afraidOf", "Man", "Animal",
+            # Every Man afraid of at least one Animal:
+            src_min_c=0,
+            src_max_c=None,
+            tgt_min_c=1,
+            tgt_max_c=None,
+        )
+        return dsl_mm_id
+
+    def create_dsl_mm_parser():
+        # Create DSL MM with parser
+        dsl_mm_cs = """
+# Integer:ModelRef
+Bear:Class
+Animal:Class
+    abstract = True
+Man:Class
+    lower_cardinality = 1
+    upper_cardinality = 2
+Man_weight:AttributeLink (Man -> Integer)
+    name = "weight"
+    optional = False
+afraidOf:Association (Man -> Animal)
+    source_lower_cardinality = 0
+    target_lower_cardinality = 1
+Man_inh_Animal:Inheritance (Man -> Animal)
+Bear_inh_Animal:Inheritance (Bear -> Animal)
+"""
+        dsl_mm_id = parser.parse_od(state, dsl_mm_cs, mm=scd_mm_id)
+        return dsl_mm_id
+    
+    def create_dsl_m_api():
+        # Create DSL M with OD API
+        dsl_m_id = state.create_node()
+        dsl_m_od = OD(dsl_mm_id, dsl_m_id, state)
+        dsl_m_od.create_object("george", "Man")
+        dsl_m_od.create_slot("weight", "george",
+            dsl_m_od.create_integer_value("george.weight", 80))
+        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")
+        return dsl_m_id
+
+    def create_dsl_m_parser():
+        # Create DSL M with parser
+        dsl_m_cs = """
+george :Man 
+    weight = 80
+
+bear1 :Bear
+bear2 :Bear
+
+:afraidOf (george -> bear1)
+:afraidOf (george -> bear2)
+"""
+        dsl_m_id = parser.parse_od(state, dsl_m_cs, mm=dsl_mm_id)
+        return dsl_m_id
+
+
+    # dsl_mm_id = create_dsl_mm_api()
+    dsl_mm_id = create_dsl_mm_parser()
+
+    print("DSL MM:")
+    print("--------------------------------------")
+    print(renderer.render_od(state, dsl_mm_id, scd_mm_id, hide_names=False))
+    print("--------------------------------------")
 
     conf = Conformance(state, dsl_mm_id, scd_mm_id)
-    print("conforms?", conf.check_nominal(log=True))
-
-    # Model in our DSL
-    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")
+    print("Conformance DSL_MM -> SCD_MM?", conf.check_nominal(log=True))
 
+    # dsl_m_id = create_dsl_m_api()
+    dsl_m_id = create_dsl_m_parser()
+    print("DSL M:")
+    print("--------------------------------------")
+    print(renderer.render_od(state, dsl_m_id, dsl_mm_id, hide_names=False))
+    print("--------------------------------------")
 
-    conf2 = Conformance(state, dsl_m_id, dsl_mm_id)
-    print("DSL instance conforms?", conf2.check_nominal(log=True))
-    print(conf2.type_mapping)
+    conf = Conformance(state, dsl_m_id, dsl_mm_id)
+    print("Conformance DSL_M -> DSL_MM?", conf.check_nominal(log=True))
 
     # RAMify MM
     prefix = "RAM_" # all ramified types can be prefixed to distinguish them a bit more
     ramified_mm_id = ramify(state, dsl_mm_id, prefix)
+    ramified_int_mm_id = ramify(state, int_mm_id, prefix)
 
     # LHS of our rule
     lhs_id = state.create_node()
     lhs_od = OD(ramified_mm_id, lhs_id, state)
-
     lhs_od.create_object("man", prefix+"Man")
     lhs_od.create_slot(prefix+"weight", "man", lhs_od.create_string_value(f"man.{prefix}weight", 'v < 99'))
     lhs_od.create_object("scaryAnimal", prefix+"Animal")
     lhs_od.create_link("manAfraidOfAnimal", prefix+"afraidOf", "man", "scaryAnimal")
 
-    conf3 = Conformance(state, lhs_id, ramified_mm_id)
-    print("LHS conforms?", conf3.check_nominal(log=True))
+    conf = Conformance(state, lhs_id, ramified_mm_id)
+    print("Conformance LHS_M -> RAM_DSL_MM?", conf.check_nominal(log=True))
 
     # RHS of our rule
     rhs_id = state.create_node()
     rhs_od = OD(ramified_mm_id, rhs_id, state)
-
     rhs_od.create_object("man", prefix+"Man")
     rhs_od.create_slot(prefix+"weight", "man", rhs_od.create_string_value(f"man.{prefix}weight", 'v + 5'))
-
     rhs_od.create_object("bill", prefix+"Man")
     rhs_od.create_slot(prefix+"weight", "bill", rhs_od.create_string_value(f"bill.{prefix}weight", '100'))
 
     rhs_od.create_link("billAfraidOfMan", prefix+"afraidOf", "bill", "man")
 
-    conf4 = Conformance(state, rhs_id, ramified_mm_id)
-    print("RHS conforms?", conf4.check_nominal(log=True))
+    conf = Conformance(state, rhs_id, ramified_mm_id)
+    print("Conformance RHS_M -> RAM_DSL_MM?", conf.check_nominal(log=True))
 
     def render_ramification():
         uml = (""
             # Render original and RAMified meta-models
-            + plantuml.render_package("Meta-Model", plantuml.render_class_diagram(state, dsl_mm_id))
-            + plantuml.render_package("RAMified Meta-Model", plantuml.render_class_diagram(state, ramified_mm_id))
+            + plantuml.render_package("DSL Meta-Model", plantuml.render_class_diagram(state, dsl_mm_id))
+            + plantuml.render_package("Int Meta-Model", plantuml.render_class_diagram(state, int_mm_id))
+            + plantuml.render_package("RAMified DSL Meta-Model", plantuml.render_class_diagram(state, ramified_mm_id))
+            + plantuml.render_package("RAMified Int Meta-Model", plantuml.render_class_diagram(state, ramified_int_mm_id))
 
             # Render RAMification traceability links
             + plantuml.render_trace_ramifies(state, dsl_mm_id, ramified_mm_id)
+            + plantuml.render_trace_ramifies(state, int_mm_id, ramified_int_mm_id)
         )
 
         # Render pattern
@@ -182,8 +200,11 @@ def main():
         for name_mapping in generator:
             rewriter.rewrite(state, lhs_id, rhs_id, ramified_mm_id, name_mapping, dsl_m_id, dsl_mm_id)
 
+            conf = Conformance(state, dsl_m_id, dsl_mm_id)
+            print("Conformance DSL_M (after rewrite) -> DSL_MM?", conf.check_nominal(log=True))
+
             # Render match
-            uml_match = plantuml.render_trace_match(state, name_mapping, rhs_id, dsl_m_id)
+            uml_match = plantuml.render_trace_match(state, name_mapping, rhs_id, dsl_m_id, 'orange')
 
             # Stop matching after rewrite
             break
@@ -197,14 +218,13 @@ def main():
 
         return uml
 
-    conf5 = Conformance(state, dsl_m_id, dsl_mm_id)
-    print("Updated model conforms?", conf5.check_nominal(log=True))
+    # plantuml_str = render_all_matches()
+    # plantuml_str = render_rewrite()
 
-    print()
-    print("==============================================")
+    # print()
+    # print("==============================================")
 
-    print(render_all_matches())
-    # print(render_rewrite())
+    # print(plantuml_str)
 
 
 if __name__ == "__main__":

+ 1 - 1
framework/conformance.py

@@ -556,7 +556,7 @@ class Conformance:
                     attr_type_element, = self.bottom.read_outgoing_elements(self.type_model, attr_type)
                     self.bottom.create_edge(attr_element, attr_type_element, "Morphism")
                     # attribute link
-                    attr_link_element, = self.bottom.read_outgoing_elements(self.model, f"{m_name}.{attr_name}_link")
+                    attr_link_element, = self.bottom.read_outgoing_elements(self.model, f"{m_name}_{attr_name}")
                     attr_link_type_element, = self.bottom.read_outgoing_elements(self.type_model, f"{tm_name}_{attr_name}")
                     self.bottom.create_edge(attr_link_element, attr_link_type_element, "Morphism")
                 except ValueError:

+ 3 - 3
pattern_matching/matcher.py

@@ -133,10 +133,10 @@ class MatcherVF2:
         self.guest = guest
         self.compare_fn = compare_fn
 
-        with Timer("find_connected_components - guest"):
-            self.guest_vtx_to_component, self.guest_component_to_vtxs = find_connected_components(guest)
+        # with Timer("find_connected_components - guest"):
+        self.guest_vtx_to_component, self.guest_component_to_vtxs = find_connected_components(guest)
 
-        print("number of guest connected components:", len(self.guest_component_to_vtxs))
+        # print("number of guest connected components:", len(self.guest_component_to_vtxs))
 
     def match(self):
         yield from self._match(

+ 54 - 31
pattern_matching/mvs_adapter.py

@@ -28,12 +28,12 @@ class _is_modelref:
         return "REF"
 IS_MODELREF = _is_modelref()
 
-class IS_TYPE:
-    def __init__(self, type):
-        # mvs-node of the type
-        self.type = type
-    def __repr__(self):
-        return f"TYPE({str(self.type)[-4:]})"
+# class IS_TYPE:
+#     def __init__(self, type):
+#         # mvs-node of the type
+#         self.type = type
+#     def __repr__(self):
+#         return f"TYPE({str(self.type)[-4:]})"
 
 class NamedNode(Vertex):
     def __init__(self, value, name):
@@ -74,7 +74,7 @@ UUID_REGEX = re.compile(r"[0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-
 # Converts an object diagram in MVS state to the pattern matcher graph type
 # ModelRefs are flattened
 def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
-    with Timer("model_to_graph"):
+    # with Timer("model_to_graph"):
         od = OD(model, metamodel, state)
         scd = SCD(model, state)
         scd_mm = SCD(metamodel, state)
@@ -107,7 +107,7 @@ def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
             if isinstance(value, str):
                 if UUID_REGEX.match(value) != None:
                     # side-effect
-                    modelrefs[el] = (UUID(value),name)
+                    modelrefs[el] = (UUID(value), name)
                     return MVSNode(IS_MODELREF, el, name)
             return MVSNode(value, el, name)
 
@@ -131,29 +131,37 @@ def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
                     label="tgt"))
 
 
-        for node, (ref, name) in modelrefs.items():
+        for node, (ref_m, name) in modelrefs.items():
+            vtx = uuid_to_vtx[node]
+
             # Get MM of ref'ed model
-            type_node, = bottom.read_outgoing_elements(node, "Morphism")
-            print("modelref type node:", type_node)
+            ref_mm, = bottom.read_outgoing_elements(node, "Morphism")
+            # print("modelref type node:", type_node)
 
             # Recursively convert ref'ed model to graph
-            ref_model = model_to_graph(state, ref, type_node, prefix=name+'/')
+            # ref_graph = model_to_graph(state, ref_m, ref_mm, prefix=name+'/')
+
+            vtx.modelref = (ref_m, ref_mm)
+
+            # We no longer flatten:
 
-            # Flatten and create link to ref'ed model
-            graph.vtxs += ref_model.vtxs
-            graph.edges += ref_model.edges
-            graph.edges.append(Edge(
-                src=uuid_to_vtx[node],
-                tgt=ref_model.vtxs[0], # which node to link to?? dirty
-                label="modelref"))
+            # # Flatten and create link to ref'ed model
+            # graph.vtxs += ref_model.vtxs
+            # graph.edges += ref_model.edges
+            # graph.edges.append(Edge(
+            #     src=uuid_to_vtx[node],
+            #     tgt=ref_model.vtxs[0], # which node to link to?? dirty
+            #     label="modelref"))
 
         def add_types(node):
+            vtx = uuid_to_vtx[node]
             type_node, = bottom.read_outgoing_elements(node, "Morphism")
 
-            # Put the type straigt into the Vertex-object
-            uuid_to_vtx[node].typ = type_node
+            # Put the type straight into the Vertex-object
+            # The benefit is that our Vertex-matching callback can then be coded cleverly, look at the types first, resulting in better performance
+            vtx.typ = type_node
 
-            # We used to put the types in separate nodes, but we no longer do this:
+            # The old approach (creating special vertices containing the types), commented out:
 
             # print('node', node, 'has type', type_node)
             # We create a Vertex storing the type
@@ -190,6 +198,7 @@ def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
 
 
 def match_od(state, host_m, host_mm, pattern_m, pattern_mm):
+
     # Function object for pattern matching. Decides whether to match host and guest vertices, where guest is a RAMified instance (e.g., the attributes are all strings with Python expressions), and the host is an instance (=object diagram) of the original model (=class diagram)
     class RAMCompare:
         def __init__(self, bottom, host_od):
@@ -239,6 +248,20 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm):
                     return False
                 return self.match_types(g_vtx.typ, h_vtx.typ)
 
+            if hasattr(g_vtx, 'modelref'):
+                if not hasattr(h_vtx, 'modelref'):
+                    return False
+                g_ref_m, g_ref_mm = g_vtx.modelref
+                h_ref_m, h_ref_mm = h_vtx.modelref
+                nested_matches = [m for m in match_od(state, h_ref_m, h_ref_mm, g_ref_m, g_ref_mm)]
+                # print('nested_matches:', nested_matches)
+                if len(nested_matches) == 0:
+                    return False
+                elif len(nested_matches) == 1:
+                    return True
+                else:
+                    raise Exception("We have a problem: there is more than 1 match in the nested models.")
+
             # Then, match by value
 
             if g_vtx.value == None:
@@ -257,20 +280,20 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm):
             if h_vtx.value == IS_MODELREF:
                 return False
 
-            # print(g_vtx.value, h_vtx.value)
-            def get_slot(h_vtx, slot_name: str):
-                slot_node = self.host_od.get_slot(h_vtx.node_id, slot_name)
-                return slot_node
+            # # print(g_vtx.value, h_vtx.value)
+            # def get_slot(h_vtx, slot_name: str):
+            #     slot_node = self.host_od.get_slot(h_vtx.node_id, slot_name)
+            #     return slot_node
 
-            def read_int(slot: UUID):
-                i = Integer(slot, self.bottom.state)
-                return i.read()
+            # def read_int(slot: UUID):
+            #     i = Integer(slot, self.bottom.state)
+            #     return i.read()
 
             try:
                 return eval(g_vtx.value, {}, {
                     'v': h_vtx.value,
-                    'get_slot': functools.partial(get_slot, h_vtx),
-                    'read_int': read_int,
+                    # 'get_slot': functools.partial(get_slot, h_vtx),
+                    # 'read_int': read_int,
                 })
             except Exception as e:
                 return False

+ 63 - 11
services/od.py

@@ -52,11 +52,6 @@ class OD:
 
         return object_node
 
-    # def read_slot_boolean(self, obj_node: str, attr_name: str):
-    #     slot = self.get_slot(obj_node, attr_name)
-    #     if slot != None:
-    #         return Boolean(slot, self.bottom.state).read()
-
     def get_class_of_object(self, object_name: str):
         object_node, = self.bottom.read_outgoing_elements(self.model, object_name) # get the object
         return self._get_class_of_object(object_node)
@@ -73,9 +68,10 @@ class OD:
         class_name = self.get_class_of_object(object_name)
         attr_link_name = get_attr_link_name(class_name, attr_name)
         # An attribute-link is indistinguishable from an ordinary link:
-        return self.create_link(
+        slot_id = self.create_link(
             get_attr_link_name(object_name, attr_name),
             attr_link_name, object_name, target_name)
+        return slot_id
 
     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...
@@ -86,8 +82,23 @@ class OD:
             if type_edge in self.bottom.read_outgoing_elements(outgoing_edge, "Morphism"):
                 slot_ref = self.bottom.read_edge_target(outgoing_edge)
                 return slot_ref
-                # slot_node = UUID(self.bottom.read_value(slot_ref))
-                # return slot_node
+
+    def get_slots(self, object_node):
+        attrlink_node = get_scd_mm_attributelink_node(self.bottom)
+        slots = []
+        outgoing_links = self.bottom.read_outgoing_edges(object_node)
+        for l in outgoing_links:
+            for type_of_link in self.bottom.read_outgoing_elements(l, "Morphism"):
+                for type_of_type_of_link in self.bottom.read_outgoing_elements(type_of_link, "Morphism"):
+                    if type_of_type_of_link == attrlink_node:
+                        # hooray, we have a slot
+                        attr_name = get_attr_name(self.bottom, type_of_link)
+                        slots.append((attr_name, l))
+        return slots
+
+    def read_slot(self, slot_id):
+        tgt = self.bottom.read_edge_target(slot_id)
+        return read_primitive_value(self.bottom, tgt, self.type_model)
 
     def create_integer_value(self, name: str, value: int):
         from services.primitives.integer_type import Integer
@@ -99,6 +110,16 @@ class OD:
         self.create_model_ref(name, "Integer", int_node)
         return name
 
+    def create_boolean_value(self, name: str, value: bool):
+        from services.primitives.boolean_type import Boolean
+        bool_node = self.bottom.create_node()
+        bool_service = Boolean(bool_node, self.bottom.state)
+        bool_service.create(value)
+        # name = 'int'+str(value) # name of the ref to the created integer
+        # By convention, the type model must have a ModelRef named "Integer"
+        self.create_model_ref(name, "Boolean", bool_node)
+        return name
+
     def create_string_value(self, name: str, value: str):
         from services.primitives.string_type import String
         string_node = self.bottom.create_node()
@@ -132,10 +153,10 @@ class OD:
                 i += 1
 
         type_edge, = self.bottom.read_outgoing_elements(self.type_model, assoc_name)
+        link_id = self._create_link(link_name, type_edge, src_obj_node, tgt_obj_node)
+        return link_id
 
-        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):
+    def _create_link(self, link_name: str, type_edge: UUID, 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:
@@ -146,6 +167,33 @@ class OD:
     def get_objects(self, class_node):
         return get_typed_by(self.bottom, self.model, class_node)
 
+    def get_all_objects(self):
+        scd_mm = get_scd_mm(self.bottom)
+        class_node = get_scd_mm_class_node(self.bottom)
+        all_classes = OD(scd_mm, self.type_model, self.bottom.state).get_objects(class_node)
+        result = {}
+        for class_name, class_node in all_classes.items():
+            objects = self.get_objects(class_node)
+            result[class_name] = objects
+        return result
+
+    def get_all_links(self):
+        scd_mm = get_scd_mm(self.bottom)
+        assoc_node = get_scd_mm_assoc_node(self.bottom)
+        all_classes = OD(scd_mm, self.type_model, self.bottom.state).get_objects(assoc_node)
+        result = {}
+        for assoc_name, assoc_node in all_classes.items():
+            links = self.get_objects(assoc_node)
+            m = {}
+            for link_name, link_edge in links.items():
+                src_node = self.bottom.read_edge_source(link_edge)
+                tgt_node = self.bottom.read_edge_target(link_edge)
+                src_name = get_object_name(self.bottom, self.model, src_node)
+                tgt_name = get_object_name(self.bottom, self.model, tgt_node)
+                m[link_name] = (link_edge, src_name, tgt_name)
+            result[assoc_name] = m
+        return result
+
     def get_object_name(self, obj: UUID):
         for key in self.bottom.read_keys(self.model):
             for el in self.bottom.read_outgoing_elements(self.model, key):
@@ -224,6 +272,10 @@ def get_object_name(bottom: Bottom, model: UUID, object_node: UUID):
             if el == object_node:
                 return key
 
+def get_type2(bottom: Bottom, mm: UUID, object_node: UUID):
+    type_node, = bottom.read_outgoing_elements(object_node, "Morphism")
+    return type_node, get_object_name(bottom, mm, type_node)
+
 def find_outgoing_typed_by(bottom, src: UUID, type_node: UUID):
     edges = []
     for outgoing_edge in bottom.read_outgoing_edges(src):

+ 6 - 6
services/scd.py

@@ -43,7 +43,7 @@ class SCD:
             _c_node = self.bottom.create_node(str(_c_model))  # store UUID of primitive value model
             self.bottom.create_edge(self.model, _c_node, f"{name}.{bound}_cardinality")  # link to model root
             _c_link = self.bottom.create_edge(class_node, _c_node)  # link class to attribute
-            self.bottom.create_edge(self.model, _c_link, f"{name}.{bound}_cardinality_link")  # link attr link to model root
+            self.bottom.create_edge(self.model, _c_link, f"{name}_{bound}_cardinality")  # link attr link to model root
             # retrieve types from metamodel
             _scd_node, = self.bottom.read_outgoing_elements(self.scd_model, "Integer")
             _scd_link, = self.bottom.read_outgoing_elements(self.scd_model, f"Class_{bound}_cardinality")
@@ -63,7 +63,7 @@ class SCD:
             abstract_node = self.bottom.create_node(str(abstract_model))
             self.bottom.create_edge(self.model, abstract_node, f"{name}.abstract")
             abstract_link = self.bottom.create_edge(class_node, abstract_node)
-            self.bottom.create_edge(self.model, abstract_link, f"{name}.abstract_link")
+            self.bottom.create_edge(self.model, abstract_link, f"{name}_abstract")
             scd_node, = self.bottom.read_outgoing_elements(self.scd_model, "Boolean")
             scd_link, = self.bottom.read_outgoing_elements(self.scd_model, "Class_abstract")
             self.bottom.create_edge(abstract_node, scd_node, "Morphism")
@@ -110,7 +110,7 @@ class SCD:
             _c_node = self.bottom.create_node(str(_c_model))
             self.bottom.create_edge(self.model, _c_node, f"{name}.{bound}_cardinality")
             _c_link = self.bottom.create_edge(assoc_edge, _c_node)
-            self.bottom.create_edge(self.model, _c_link, f"{name}.{bound}_cardinality_link")
+            self.bottom.create_edge(self.model, _c_link, f"{name}_{bound}_cardinality")
             _scd_node, = self.bottom.read_outgoing_elements(self.scd_model, "Integer")
             _scd_link, = self.bottom.read_outgoing_elements(self.scd_model, f"Association_{bound}_cardinality")
             self.bottom.create_edge(_c_node, _scd_node, "Morphism")
@@ -193,7 +193,7 @@ class SCD:
         name_node = self.bottom.create_node(str(name_model))
         self.bottom.create_edge(self.model, name_node, f"{source}_{name}.name")
         name_link = self.bottom.create_edge(assoc_edge, name_node)
-        self.bottom.create_edge(self.model, name_link, f"{source}_{name}.name_link")
+        self.bottom.create_edge(self.model, name_link, f"{source}_{name}_name")
         scd_node, = self.bottom.read_outgoing_elements(self.scd_model, "String")
         scd_link, = self.bottom.read_outgoing_elements(self.scd_model, "AttributeLink_name")
         self.bottom.create_edge(name_node, scd_node, "Morphism")
@@ -205,7 +205,7 @@ class SCD:
         optional_node = self.bottom.create_node(str(optional_model))
         self.bottom.create_edge(self.model, optional_node, f"{source}_{name}.optional")
         optional_link = self.bottom.create_edge(assoc_edge, optional_node)
-        self.bottom.create_edge(self.model, optional_link, f"{source}_{name}.optional_link")
+        self.bottom.create_edge(self.model, optional_link, f"{source}_{name}_optional")
         scd_node, = self.bottom.read_outgoing_elements(self.scd_model, "Boolean")
         scd_link, = self.bottom.read_outgoing_elements(self.scd_model, "AttributeLink_optional")
         self.bottom.create_edge(optional_node, scd_node, "Morphism")
@@ -278,7 +278,7 @@ class SCD:
         code_node = self.bottom.create_node(code)
         self.bottom.create_edge(self.model, code_node, f"{element}.constraint")
         code_link = self.bottom.create_edge(element_node, code_node)
-        self.bottom.create_edge(self.model, code_link, f"{element}.constraint_link")
+        self.bottom.create_edge(self.model, code_link, f"{element}_constraint")
         scd_node, = self.bottom.read_outgoing_elements(self.scd_model, "ActionCode")
         scd_link, = self.bottom.read_outgoing_elements(self.scd_model, "Element_constraint")
         self.bottom.create_edge(code_node, scd_node, "Morphism")

+ 4 - 6
transformation/ramify.py

@@ -29,7 +29,7 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
         #   - min-card: 0
         #   - max-card: same as original
         upper_card = od.find_cardinality(bottom, class_node, od.get_scd_mm_class_uppercard_node(bottom))
-        print('creating class', class_name, "with card 0 ..", upper_card)
+        # print('creating class', class_name, "with card 0 ..", upper_card)
         ramified_class = ramified_scd.create_class(prefix+class_name, abstract=None, max_c=upper_card)
         # traceability link
         bottom.create_edge(ramified_class, class_node, RAMIFIES_LABEL)
@@ -41,7 +41,7 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
         # ramified_scd._create_attribute_link(prefix+class_name, string_modelref, "constraint", optional=True)
 
         for (attr_name, attr_edge) in od.get_attributes(bottom, class_node):
-            print('  creating attribute', attr_name, "with type String")
+            # print('  creating attribute', attr_name, "with type String")
             # Every attribute becomes 'string' type
             # The string will be a Python expression
             ramified_attr_link = ramified_scd._create_attribute_link(prefix+class_name, string_modelref, prefix+attr_name, optional=True)
@@ -58,7 +58,7 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
         _, src_upper_card, _, tgt_upper_card = m_scd.get_assoc_cardinalities(assoc_node)
         src = m_scd.get_class_name(bottom.read_edge_source(assoc_node))
         tgt = m_scd.get_class_name(bottom.read_edge_target(assoc_node))
-        print('creating assoc', src, "->", tgt, ", name =", assoc_name, ", src card = 0 ..", src_upper_card, "and tgt card = 0 ..", tgt_upper_card)
+        # print('creating assoc', src, "->", tgt, ", name =", assoc_name, ", src card = 0 ..", src_upper_card, "and tgt card = 0 ..", tgt_upper_card)
         ramified_assoc = ramified_scd.create_association(
             prefix+assoc_name, prefix+src, prefix+tgt,
             src_max_c=src_upper_card,
@@ -70,15 +70,13 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
         # Re-create inheritance links like in our original model:
         src = m_scd.get_class_name(bottom.read_edge_source(inh_node))
         tgt = m_scd.get_class_name(bottom.read_edge_target(inh_node))
-        print('creating inheritance link', prefix+src, '->', prefix+tgt)
+        # print('creating inheritance link', prefix+src, '->', prefix+tgt)
         ramified_inh_link = ramified_scd.create_inheritance(prefix+src, prefix+tgt)
 
     # Double-check: The RAMified meta-model should also conform to 'SCD':
     conf = Conformance(state, ramified, scd_metamodel)
     if not conf.check_nominal(log=True):
         raise Exception("Unexpected error: RAMified MM does not conform to SCD MM")
-    else:
-        print("RAMification successful.")
 
     return ramified
 

+ 76 - 78
transformation/rewriter.py

@@ -13,19 +13,18 @@ from services.primitives.integer_type import Integer
 def process_rule(state, lhs: UUID, rhs: UUID):
     bottom = Bottom(state)
 
-    # : 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)
+    # print("to_delete:", to_delete)
+    # print("to_create:", to_create)
 
     return to_delete, to_create, common
 
-# Rewrite is performed in-place
-# Also updates the mapping in-place, to become RHS -> host
-def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to_transform: UUID, mm: UUID):
+# Rewrite is performed in-place (modifying `host_m`)
+# Also updates the `mapping` in-place, to become RHS -> host
+def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dict, host_m: UUID, mm: UUID):
     bottom = Bottom(state)
 
     scd_metamodel_id = state.read_dict(state.read_root(), "SCD")
@@ -36,125 +35,124 @@ def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to
     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)
+    m_od = od.OD(mm, host_m, bottom.state)
+    rhs_od = od.OD(pattern_mm, rhs_m, bottom.state)
 
-    print('rhs type:', od.get_type(bottom, rhs))
-
-    to_delete, to_create, common = process_rule(state, lhs, rhs)
+    to_delete, to_create, common = process_rule(state, lhs_m, rhs_m)
 
     # 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)
+        model_el_name_to_delete = name_mapping[pattern_name_to_delete]
+        # print('deleting', model_el_name_to_delete)
         # Look up the matched element in the host graph
-        element_to_delete, = bottom.read_outgoing_elements(m_to_transform, model_element_name_to_delete)
+        el_to_delete, = bottom.read_outgoing_elements(host_m, model_el_name_to_delete)
         # Delete
-        bottom.delete_element(element_to_delete)
+        bottom.delete_element(el_to_delete)
         # Remove from mapping
-        del match_mapping[pattern_name_to_delete]
+        del name_mapping[pattern_name_to_delete]
 
-    # extended_mapping = dict(match_mapping) # will be extended with created elements
+    # extended_mapping = dict(name_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:
-        print('creating', pattern_name_to_create)
+        # 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:
+            model_el_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(host_m, model_el_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)
+        rhs_el_to_create, = bottom.read_outgoing_elements(rhs_m, pattern_name_to_create)
+        rhs_type = od.get_type(bottom, rhs_el_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)
-                match_mapping[pattern_name_to_create] = model_element_name_to_create
+                # print(' -> creating object')
+                o = m_od._create_object(model_el_name_to_create, original_type)
+                name_mapping[pattern_name_to_create] = model_el_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))
+                # print(' -> postpone (is attribute link)')
+                edges_to_create.append((pattern_name_to_create, rhs_el_to_create, original_type, 'attribute link', model_el_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))
+                # print(' -> postpone (is link)')
+                edges_to_create.append((pattern_name_to_create, rhs_el_to_create, original_type, 'link', model_el_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")
+            # 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)
+            type_name = od.get_object_name(bottom, pattern_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()
+                # Assume the string is a Python expression to evaluate
+                python_expr = String(UUID(bottom.read_value(rhs_el_to_create)), bottom.state).read()
                 result = eval(python_expr, {}, {})
-                print('result:', result)
+                # Write the result into the host model.
+                # This will be the *value* of an attribute. The attribute-link (connecting an object to the attribute) will be created as an edge later.
                 if isinstance(result, int):
-                    m_od.create_integer_value(model_element_name_to_create, result)
+                    m_od.create_integer_value(model_el_name_to_create, result)
                 elif isinstance(result, str):
-                    m_od.create_string_value(model_element_name_to_create, result)
-                match_mapping[pattern_name_to_create] = model_element_name_to_create
-
-
-    print('match_mapping:', match_mapping)
+                    m_od.create_string_value(model_el_name_to_create, result)
+                name_mapping[pattern_name_to_create] = model_el_name_to_create
+            else:
+                raise Exception(f"RHS element '{pattern_name_to_create}' needs to be created in host, but has no un-RAMified type, and I don't know what to do with it. It's type is", type_name)
 
-    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)
+    # print("create edges....")
+    for pattern_name_to_create, rhs_el_to_create, original_type, original_type_name, model_el_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 = match_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, match_mapping[tgt_name])
-            m_od.create_slot(attribute_name, obj_name, match_mapping[tgt_name])
+            # print(' -> creating attribute link')
+            src = bottom.read_edge_source(rhs_el_to_create)
+            src_name = od.get_object_name(bottom, rhs_m, src)
+            tgt = bottom.read_edge_target(rhs_el_to_create)
+            tgt_name = od.get_object_name(bottom, rhs_m, tgt)
+            obj_name = name_mapping[src_name] # name of object in host graph to create slot for
+            orig_attr_name = od.get_attr_name(bottom, original_type)
+            m_od.create_slot(orig_attr_name, obj_name, name_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 = match_mapping[src_name] # name of object in host graph to create slot for
+            # print(' -> creating link')
+            src = bottom.read_edge_source(rhs_el_to_create)
+            src_name = od.get_object_name(bottom, rhs_m, src)
+            tgt = bottom.read_edge_target(rhs_el_to_create)
+            tgt_name = od.get_object_name(bottom, rhs_m, tgt)
+            obj_name = name_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, match_mapping[tgt_name])
-            m_od.create_link(model_element_name_to_create, attr_name, obj_name, match_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)
-        host_type = od.get_type(bottom, model_element)
+            m_od.create_link(model_el_name_to_create, attr_name, obj_name, name_mapping[tgt_name])
+
+
+    # Perform updates (only on values)
+    for pattern_el_name in common:
+        model_el_name = name_mapping[pattern_el_name]
+        # print('updating', model_el_name)
+        model_el, = bottom.read_outgoing_elements(host_m, model_el_name)
+        host_type = od.get_type(bottom, model_el)
         if od.is_typed_by(bottom, host_type, class_type):
-            print(' -> is classs')
+            # print(' -> is classs')
+            # nothing to do
+            pass
         elif od.is_typed_by(bottom, host_type, attr_link_type):
-            print(' -> is attr link')
+            # print(' -> is attr link')
+            # nothing to do
+            pass
         elif od.is_typed_by(bottom, host_type, modelref_type):
-            print(' -> is modelref')
-            old_value = od.read_primitive_value(bottom, model_element, mm)
-            rhs_element, = bottom.read_outgoing_elements(rhs, pattern_element_name)
-            expr = od.read_primitive_value(bottom, rhs_element, rhs_mm)
+            # print(' -> is modelref')
+            old_value = od.read_primitive_value(bottom, model_el, mm)
+            rhs_el, = bottom.read_outgoing_elements(rhs_m, pattern_el_name)
+            expr = od.read_primitive_value(bottom, rhs_el, pattern_mm)
             result = eval(expr, {}, {'v': old_value})
             # print('eval result=', result)
             if isinstance(result, int):
                 # overwrite the old value, in-place
-                referred_model_id = UUID(bottom.read_value(model_element))
+                referred_model_id = UUID(bottom.read_value(model_el))
                 Integer(referred_model_id, state).create(result)
             else:
                 raise Exception("Unimplemented type. Value:", result)
+        else:
+            raise Exception("Don't know what to do with element of type", host_type)