Browse Source

RAMification adds 'name' attribute, giving control over the names of created objects

Joeri Exelmans 11 months ago
parent
commit
da4f1718ce

+ 6 - 3
examples/petrinet/renderer.py

@@ -24,10 +24,13 @@ def render_petri_net(od: ODAPI):
     dot += "node[fontname=Arial,fontsize=10];\n"
     dot += "subgraph places {"
     dot += "  node [shape=circle,fixedsize=true,label=\"\", height=.35,width=.35];"
-    for _, place_state in od.get_all_instances("PNPlaceState"):
-        place = od.get_target(od.get_outgoing(place_state, "pn_of")[0])
+    for _, place in od.get_all_instances("PNPlace"):
         place_name = od.get_name(place)
-        num_tokens = od.get_slot_value(place_state, "numTokens")
+        try:
+            place_state = od.get_source(od.get_incoming(place, "pn_of")[0])
+            num_tokens = od.get_slot_value(place_state, "numTokens")
+        except IndexError:
+            num_tokens = 0
         dot += f"  {place_name} [label=\"{place_name}\\n\\n{render_tokens(num_tokens)}\\n\\n­\"];\n"
     dot += "}\n"
     dot += "subgraph transitions {"

File diff suppressed because it is too large
+ 14 - 11
examples/semantics/translational/merged_mm.od


+ 3 - 1
examples/semantics/translational/regenerate_mm.py

@@ -56,7 +56,9 @@ if __name__ == "__main__":
     filename = THIS_DIR+"/merged_mm.od"
 
     with open(filename, "w") as file:
-        file.write(f"# Auto-generated by {__file__}\n\n")
+        file.write(f"# Auto-generated by {__file__}.\n\n")
+        file.write(f"# Merged run-time meta-models of 'Petri Net' and 'Port' formalisms.\n")
+        file.write(f"# An abstract 'Top'-class (superclass of everything else), and a 'generic_link'-association (which can connect everything with everything) have also been added.\n\n")
         file.write(f"# PlantUML visualization: {plantuml_url}\n\n")
         file.write(txt)
 

+ 6 - 2
transformation/matcher.py

@@ -273,11 +273,15 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm, pivot={}):
         if pattern_odapi.get_type_name(pattern_el) == "GlobalCondition":
             return False
         # Super-cheap and unreliable way of filtering out the 'condition'-attribute, added to every class:
-        return not (pattern_el_name.endswith("condition")
+        return ((not pattern_el_name.endswith("condition")
             # as an extra safety measure, if the user defined her own 'condition' attribute, RAMification turned this into 'RAM_condition', and we can detect this
             # of course this breaks if the class name already ended with 'RAM', but let's hope that never happens
             # also, we are assuming the default "RAM_" prefix is used, but the user can change this...
-            and not pattern_el_name.endswith("RAM_condition"))
+            or pattern_el_name.endswith("RAM_condition"))
+        and (
+                not pattern_el_name.endswith("name")
+                or pattern_el_name.endswith("RAM_name") # same thing here as with the condition, explained above.
+            ))
 
     g_names, guest = model_to_graph(state, pattern_m, pattern_mm,
         _filter=is_matchable)

+ 6 - 0
transformation/ramify.py

@@ -56,6 +56,9 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
         # In RHS, this is just a piece of action code
         ramified_scd._create_attribute_link(prefix+class_name, actioncode_modelref, "condition", optional=True)
 
+        # Optional: specify name of object to create
+        ramified_scd._create_attribute_link(prefix+class_name, actioncode_modelref, "name", optional=True)
+
         already_ramified.add(class_name)
 
     glob_cond = ramified_scd.create_class("GlobalCondition", abstract=None)
@@ -97,6 +100,9 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
             # Additional constraint that can be specified
             ramified_scd._create_attribute_link(prefix+assoc_name, actioncode_modelref, "condition", optional=True)
 
+            # Optional: specify name of link to create
+            ramified_scd._create_attribute_link(prefix+assoc_name, actioncode_modelref, "name", optional=True)
+
             already_ramified.add(assoc_name)
 
             # Associations can also have attributes...

+ 20 - 5
transformation/rewriter.py

@@ -22,10 +22,12 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, lhs_match: dict,
     bottom = Bottom(state)
 
     # Need to come up with a new, unique name when creating new element in host-model:
-    def first_available_name(prefix: str):
+    def first_available_name(suggested_name: str):
+        if len(bottom.read_outgoing_elements(host_m, suggested_name)) == 0:
+            return suggested_name # already unique :)
         i = 0
         while True:
-            name = prefix + str(i)
+            name = suggested_name + str(i)
             if len(bottom.read_outgoing_elements(host_m, name)) == 0:
                 return name # found unique name
             i += 1
@@ -56,7 +58,10 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, lhs_match: dict,
     lhs_keys = lhs_match.keys()
     rhs_keys = set(k for k in bottom.read_keys(rhs_m)
         # extremely dirty - should think of a better way
-        if "GlobalCondition" not in k and not k.endswith("_condition") and not k.endswith(".condition"))
+        if "GlobalCondition" not in k and not k.endswith("_condition") and not k.endswith(".condition")
+        and (not k.endswith("_name") or k.endswith("RAM_name")) and (not k.endswith(".name")))
+
+    print('filtered out:', set(k for k in bottom.read_keys(rhs_m) if k.endswith(".name") or k.endswith("_name")))
 
     common = lhs_keys & rhs_keys
     to_delete = lhs_keys - common
@@ -75,6 +80,16 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, lhs_match: dict,
         for rhs_name in remaining_to_create:
             # Determine the type of the thing to create
             rhs_obj = rhs_odapi.get(rhs_name)
+            # what to name our new object?
+            try:
+                name_expr = rhs_odapi.get_slot_value(rhs_obj, "name")
+            except:
+                name_expr = f'"{rhs_name}"' # <- if the 'name' slot doesnt exist, use the pattern element name
+            suggested_name = exec_then_eval(name_expr,
+                _globals={
+                    **bind_api(host_odapi),
+                    'matched': matched_callback,
+                })
             rhs_type = rhs_odapi.get_type(rhs_obj)
             host_type = ramify.get_original_type(bottom, rhs_type)
             # for debugging:
@@ -100,13 +115,13 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, lhs_match: dict,
 
             try:
                 if od.is_typed_by(bottom, rhs_type, class_type):
-                    obj_name = first_available_name(rhs_name)
+                    obj_name = first_available_name(suggested_name)
                     host_od._create_object(obj_name, host_type)
                     host_odapi._ODAPI__recompute_mappings()
                     rhs_match[rhs_name] = obj_name
                 elif od.is_typed_by(bottom, rhs_type, assoc_type):
                     _, _, host_src, host_tgt = get_src_tgt()
-                    link_name = first_available_name(rhs_name)
+                    link_name = first_available_name(suggested_name)
                     host_od._create_link(link_name, host_type, host_src, host_tgt)
                     host_odapi._ODAPI__recompute_mappings()
                     rhs_match[rhs_name] = link_name

+ 7 - 9
transformation/topify/rules/r_create_top_rhs.od

@@ -1,11 +1,9 @@
 # We create the 'Top'-class with a GlobalCondition, because that's the only way we can control the name of the object to be created.
 
-:GlobalCondition {
-  condition = ```
-    top = create_object("Top", "Class")
-    set_slot_value(top, "abstract", True)
-    lnk = create_link("generic_link", "Association", top, top)
-    # lnk also inherits top:
-    create_link(None, "Inheritance", lnk, top)
-  ```;
-}
+Top:RAM_Class {
+  RAM_abstract = `True`;
+}
+
+generic_link:RAM_Association (Top -> Top)
+
+:RAM_Inheritance (generic_link -> Top)

+ 2 - 1
util/loader.py

@@ -1,6 +1,7 @@
 import os.path
 from framework.conformance import Conformance, render_conformance_check_result
 from concrete_syntax.textual_od import parser
+from concrete_syntax.common import indent
 from transformation.rule import Rule
 
 # parse model and check conformance
@@ -77,6 +78,6 @@ def load_rules(state, get_filename, rt_mm_ramified, rule_names, check_conformanc
 
         rules[rule_name] = Rule(*(parse(kind) for kind in KINDS))
 
-    print("Rules loaded:\n" + '\n'.join(files_read))
+    print("Rules loaded:\n" + indent('\n'.join(files_read), 4))
 
     return rules