From da4f1718ce6cf41818fc43ebd8ef2c67fe5852ce Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Tue, 3 Dec 2024 20:28:16 +0100 Subject: [PATCH] RAMification adds 'name' attribute, giving control over the names of created objects --- examples/petrinet/renderer.py | 9 ++++--- examples/semantics/translational/merged_mm.od | 25 +++++++++++-------- .../semantics/translational/regenerate_mm.py | 4 ++- transformation/matcher.py | 8 ++++-- transformation/ramify.py | 6 +++++ transformation/rewriter.py | 25 +++++++++++++++---- .../topify/rules/r_create_top_rhs.od | 16 ++++++------ util/loader.py | 3 ++- 8 files changed, 64 insertions(+), 32 deletions(-) diff --git a/examples/petrinet/renderer.py b/examples/petrinet/renderer.py index 835432e..9f2e93f 100644 --- a/examples/petrinet/renderer.py +++ b/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 {" diff --git a/examples/semantics/translational/merged_mm.od b/examples/semantics/translational/merged_mm.od index f8e8c44..512940e 100644 --- a/examples/semantics/translational/merged_mm.od +++ b/examples/semantics/translational/merged_mm.od @@ -1,6 +1,9 @@ -# Auto-generated by /home/maestro/repos/MV2/examples/semantics/translational/regenerate_mm.py +# Auto-generated by /home/maestro/repos/MV2/examples/semantics/translational/regenerate_mm.py. -# PlantUML visualization: https://deemz.org/plantuml/pdf/hPT1Zzem48Nl-HKMnqeWqcG1HLKFkuUg5nO9f1wHSSPiHB2HOofLxVxtsYOWTXKfByWSkCtdcNdFuqaYQjuqRFJ2JrnKzi-BLeqrl5AMZHXlsBJzRJl-_2-ZajZVXB7chJfT8QnWFvMbFPdaFMaFM2rNDHUqjjmIYgQdW5RduqOVI3LTt5_Q7CYi2SwNn1Lw2Usa3afJPexudl2Txvomx9uXppMCuTqOVJO2pKMcym2vgbfhSM3fP9A2uLaUccEh8tMrvPcCVHlI6pcRNzpXOiw-qsjhAhNlA7EZJoXpaT_N66o5XlBqFlJcdK4bySKz8xG43fNteJz8aU5M6pHyD_jG-79Zk6egMsa54yfEZwsMxjuh4fRlQhWF8k_sQwKEA88-oD7cuCePf8Uy3A2Z_asbSzYprZLnzSaW0uZvT7gREsitrJe7H3lEOA88XIQzrVWfyEtVUDpF_4fvFyxV5GZdvtXCd9CMmBdh2EAuZDWx_mW0sRbPYcO75EkV2KnPvx-eoieighBfF2ekYsjZoMCQ1L9sGB42A1OsYd-AWEm8lsJznORX2E9cCOsIPpcWh7-KmEnsPLGfPDJnwLRVg0DgDujxAu1f34lXNqVePSpOA6MJ2NFBx7ZytJsz8sohBfW6y_N8W9xwSxu0V1zLC6w0zyH_UTmEIE43tCvOCC7LwyalYuZBd2qUACID2NVERGL3wdaQBaXOXGfsKbj8ayL3cYoy9dk_NLTYMxjz55b55e-S5CIfceis_iEclsibFUI2e4xxVVqg_mC= +# Merged run-time meta-models of 'Petri Net' and 'Port' formalisms. +# An abstract 'Top'-class (superclass of everything else), and a 'generic_link'-association (which can connect everything with everything) have also been added. + +# PlantUML visualization: https://deemz.org/plantuml/pdf/hPTDZzem48Rl-HKMnqgWa0sKeAg7tKFL2ui4KezeuXWs8jYHOofLxVxtZiE7s5KatY3dm8bvF3Flp7WSoOgQHWnUg2PPkZylHZVEKgcT60XgH7p-DXq__alZMIh-Ha8qRsLzWOYv-AcTsYaRlKVd0vQBPKLIhHmv1QFp5gsFXxNPAzrqSNyPkrTsfM1_i-G2FPbsKdkvcMLCV8yezvcJJjmojiSAnL3SZJ57As5VygA5N5IjZDoByMWq1iqBQfFZoeFgIikpikwjJsx6SN6g3hOv-aold2trhYFCjQbHPaAtCRPbXPgcNszDhxNJAwHKtJBQbA2caycjwG-bbILdB6mkFmI-M5lIJUbAer72zAcpnfOBxdkjfAEyWlCmfUvwBVKUHSm-o77sWSFffGUT1j31_5O5LzYpCPKY_Qb0-X6lSsV5KwrpG9p76KhCapRGwEu__HJuzcyulCzCPtGVvti5m_4S3ubZQKFYqcqCuBYxGUycav1Iy9q2u7WqWzwbGExyhGFYA0zQA5aM59SNGN55sAuWeExmGe6KxT5aKo3O7eMIAi2x6TnaKB0yQq5SZ1JA5C_TKzWc2pe-UVKDEb4cCcWP8-EpXnHvWjoChCLWB4OZmSliSFYThzu1jbNFXTbY_dfaYuxzELy0lhUQ2x98VldbSJrW31_0E-DSIDAljLyMWGxrPV508DpNVCvr1GFEuUTPVq7yAZGNjTv0cYGFQP9uJNP-koxbfk9z5DaTr2CdjUAKpKaR_x01ifO-KWLfgyxvVVsw_Gy= CapacityConstraint:Class @@ -74,16 +77,17 @@ operatingCapacities:GlobalConstraint { ```; } WorkerSet_numWorkers:AttributeLink (WorkerSet -> Integer) { + constraint = `get_value(get_target(this)) >= 0`; optional = False; name = "numWorkers"; - constraint = `get_value(get_target(this)) >= 0`; } PlaceState_numShips:AttributeLink (PlaceState -> Integer) { + optional = False; name = "numShips"; constraint = `get_value(get_target(this)) >= 0`; - optional = False; } ConnectionState_moved:AttributeLink (ConnectionState -> Boolean) { + name = "moved"; constraint = ``` result = True all_successors_moved = True @@ -101,26 +105,25 @@ ConnectionState_moved:AttributeLink (ConnectionState -> Boolean) { result ```; optional = False; - name = "moved"; } BerthState_status:AttributeLink (BerthState -> String) { + name = "status"; constraint = ``` ( get_value(get_target(this)) in { "empty", "unserved", "served" } ) ```; optional = False; - name = "status"; } PNPlaceState_numTokens:AttributeLink (PNPlaceState -> Integer) { - constraint = `"numTokens cannot be negative" if get_value(get_target(this)) < 0 else None`; optional = False; name = "numTokens"; + constraint = `"numTokens cannot be negative" if get_value(get_target(this)) < 0 else None`; } Clock_time:AttributeLink (Clock -> Integer) { + name = "time"; constraint = `get_value(get_target(this)) >= 0`; optional = False; - name = "time"; } CapacityConstraint_shipCapacity:AttributeLink (CapacityConstraint -> Integer) { constraint = `get_value(get_target(this)) >= 0`; @@ -128,10 +131,10 @@ CapacityConstraint_shipCapacity:AttributeLink (CapacityConstraint -> Integer) { name = "shipCapacity"; } of:Association (State -> Stateful) { + target_lower_cardinality = 1; source_upper_cardinality = 1; source_lower_cardinality = 1; target_upper_cardinality = 1; - target_lower_cardinality = 1; } arc:Association (PNConnectable -> PNConnectable) canOperate:Association (WorkerSet -> Berth) { @@ -139,10 +142,10 @@ canOperate:Association (WorkerSet -> Berth) { } connection:Association (Source -> Sink) pn_of:Association (PNPlaceState -> PNPlace) { - source_lower_cardinality = 1; target_upper_cardinality = 1; target_lower_cardinality = 1; source_upper_cardinality = 1; + source_lower_cardinality = 1; } generic_link:Association (Top -> Top) isOperating:Association (WorkerSetState -> Berth) { @@ -171,6 +174,7 @@ capacityOf:Association (CapacityConstraint -> Place) { :Inheritance (connection -> Stateful) :Inheritance (CapacityConstraint -> Top) :Inheritance (Sink -> Top) +:Inheritance (generic_link -> Top) :Inheritance (Berth -> Place) :Inheritance (WorkerSet -> Stateful) :Inheritance (Place -> Source) @@ -184,7 +188,6 @@ capacityOf:Association (CapacityConstraint -> Place) { :Inheritance (WorkerSetState -> State) :Inheritance (Place -> Sink) :Inheritance (BerthState -> PlaceState) -:Inheritance (generic_link -> Top) :Inheritance (PNTransition -> PNConnectable) :Inheritance (ConnectionState -> State) :Inheritance (PNPlaceState -> Top) diff --git a/examples/semantics/translational/regenerate_mm.py b/examples/semantics/translational/regenerate_mm.py index b76eae1..0a9b6dc 100644 --- a/examples/semantics/translational/regenerate_mm.py +++ b/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) diff --git a/transformation/matcher.py b/transformation/matcher.py index 0bb2e01..9c5b2da 100644 --- a/transformation/matcher.py +++ b/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) diff --git a/transformation/ramify.py b/transformation/ramify.py index fc3201e..4950b72 100644 --- a/transformation/ramify.py +++ b/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... diff --git a/transformation/rewriter.py b/transformation/rewriter.py index 1b8c7dd..f1e2e71 100644 --- a/transformation/rewriter.py +++ b/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 diff --git a/transformation/topify/rules/r_create_top_rhs.od b/transformation/topify/rules/r_create_top_rhs.od index bff25c0..13a6e51 100644 --- a/transformation/topify/rules/r_create_top_rhs.od +++ b/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) - ```; -} \ No newline at end of file +Top:RAM_Class { + RAM_abstract = `True`; +} + +generic_link:RAM_Association (Top -> Top) + +:RAM_Inheritance (generic_link -> Top) diff --git a/util/loader.py b/util/loader.py index db48422..37b026d 100644 --- a/util/loader.py +++ b/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