diff --git a/bootstrap/primitive.py b/bootstrap/primitive.py index 177c085..33b8c2d 100644 --- a/bootstrap/primitive.py +++ b/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") diff --git a/bootstrap/scd.py b/bootstrap/scd.py index 3d85e4a..172fe6b 100644 --- a/bootstrap/scd.py +++ b/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 diff --git a/renderer/plantuml.py b/concrete_syntax/plantuml.py similarity index 99% rename from renderer/plantuml.py rename to concrete_syntax/plantuml.py index 1d0cf55..b291007 100644 --- a/renderer/plantuml.py +++ b/concrete_syntax/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) diff --git a/concrete_syntax/textual_od/parser.py b/concrete_syntax/textual_od/parser.py new file mode 100644 index 0000000..f55f78b --- /dev/null +++ b/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 diff --git a/concrete_syntax/textual_od/readme.txt b/concrete_syntax/textual_od/readme.txt new file mode 100644 index 0000000..eac4f12 --- /dev/null +++ b/concrete_syntax/textual_od/readme.txt @@ -0,0 +1 @@ +This directory contains the parser and renderer for the textual concrete syntax for Object Diagrams. \ No newline at end of file diff --git a/concrete_syntax/textual_od/renderer.py b/concrete_syntax/textual_od/renderer.py new file mode 100644 index 0000000..d1dc888 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/experiments/exp_scd.py b/experiments/exp_scd.py index 462feee..8092339 100644 --- a/experiments/exp_scd.py +++ b/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) + 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 - # 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, - ) + 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 - print(dsl_mm_scd.list_elements()) + 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)) + print("Conformance DSL_MM -> SCD_MM?", 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_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("--------------------------------------") - # 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") - - - 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__": diff --git a/framework/conformance.py b/framework/conformance.py index dcd8604..4309f15 100644 --- a/framework/conformance.py +++ b/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: diff --git a/pattern_matching/matcher.py b/pattern_matching/matcher.py index 9c1a966..de242ed 100644 --- a/pattern_matching/matcher.py +++ b/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( diff --git a/pattern_matching/mvs_adapter.py b/pattern_matching/mvs_adapter.py index 8d51393..6ffaabd 100644 --- a/pattern_matching/mvs_adapter.py +++ b/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+'/') - # 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")) + 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")) 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 diff --git a/services/od.py b/services/od.py index e12b8b4..d0898ae 100644 --- a/services/od.py +++ b/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): diff --git a/services/scd.py b/services/scd.py index 1233c46..af3c463 100644 --- a/services/scd.py +++ b/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") diff --git a/transformation/ramify.py b/transformation/ramify.py index b7b2613..39d245f 100644 --- a/transformation/ramify.py +++ b/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 diff --git a/transformation/rewriter.py b/transformation/rewriter.py index 5054da7..4e5fd8e 100644 --- a/transformation/rewriter.py +++ b/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 + 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('match_mapping:', match_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) + # 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]) + m_od.create_link(model_el_name_to_create, attr_name, obj_name, name_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) + + # 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)