Parcourir la source

Rewrite the 'rewriter' + Added transformation schedule to CBD example, simplifying the rules

Joeri Exelmans il y a 1 an
Parent
commit
ad3752cd61

+ 24 - 2
api/od.py

@@ -1,6 +1,10 @@
 from services import od
 from api import cd
 from services.bottom.V0 import Bottom
+from services.primitives.boolean_type import Boolean
+from services.primitives.integer_type import Integer
+from services.primitives.string_type import String
+from services.primitives.actioncode_type import ActionCode
 from uuid import UUID
 from typing import Optional
 
@@ -12,8 +16,10 @@ def build_name_mapping(state, m):
     mapping = {}
     bottom = Bottom(state)
     for name in bottom.read_keys(m):
-        element, = bottom.read_outgoing_elements(m, name)
-        mapping[element] = name
+        elements = bottom.read_outgoing_elements(m, name)
+        if len(elements) > 1:
+            print(f"Warning: more than one element with name '{name}'")
+        mapping[elements[0]] = name
     return mapping
 
 class NoSuchSlotException(Exception):
@@ -194,6 +200,22 @@ class ODAPI:
         self.__recompute_mappings()
         return tgt
 
+    def overwrite_primitive_value(self, name: str, value: any, is_code=False):
+        referred_model = UUID(self.bottom.read_value(self.get(name)))
+        # watch out: in Python, 'bool' is subtype of 'int'
+        #  so we must check for 'bool' first
+        if isinstance(value, bool):
+            Boolean(referred_model, self.state).create(value)
+        elif isinstance(value, int):
+            Integer(referred_model, self.state).create(value)
+        elif isinstance(value, str):
+            if is_code:
+                ActionCode(referred_model, self.state).create(value)
+            else:
+                String(referred_model, self.state).create(value)
+        else:
+            raise Exception("Unimplemented type "+value)
+
     def create_link(self, link_name: Optional[str], assoc_name: str, src: UUID, tgt: UUID):
         global NEXT_ID
         types = self.bottom.read_outgoing_elements(self.mm, assoc_name) 

+ 2 - 24
examples/cbd/models/r_advance_time_nac.od

@@ -1,28 +1,6 @@
+# We cannot advance time until all outports have signals
 
-# If there is a Delay-block whose input signal differs from its state, we cannot yet advance time:
-
-delay:RAM_Delay
-
-delay_in:RAM_InPort
-
-delay_has_input:RAM_hasInPort (delay -> delay_in)
-
-some_outport:RAM_OutPort
-
-delay_in_conn:RAM_link (some_outport -> delay_in)
-
-in_signal:RAM_Signal
-
-port_has_signal:RAM_hasSignal (some_outport -> in_signal)
-
-state:RAM_State {
-  RAM_x = `get_slot_value(matched('in_signal'), 'x') != get_value(this)`;
-}
-
-delay_to_state:RAM_delay2State (delay -> state)
-
-
-# Also, we cannot advance time until all outports have signals:
+# BTW, this NAC is not really necessary, because our schedule already will only try to match 'advance_time' when no other actions are enabled
 
 :GlobalCondition {
   condition = ```

+ 2 - 0
examples/cbd/models/r_advance_time_rhs.od

@@ -2,6 +2,8 @@ clock:RAM_Clock {
   RAM_time = `get_value(this) + 1`;
 }
 
+# Delete all Signals:
+
 :GlobalCondition {
   condition = ```
     for _, signal in get_all_instances("Signal"):

+ 19 - 6
examples/cbd/models/r_delay_in_lhs.od

@@ -2,17 +2,22 @@
 
 delay:RAM_Delay
 
-delay_in:RAM_InPort
+delay_inport:RAM_InPort
 
-delay_has_input:RAM_hasInPort (delay -> delay_in)
+delay_has_inport:RAM_hasInPort (delay -> delay_inport)
 
 some_outport:RAM_OutPort
 
-delay_in_conn:RAM_link (some_outport -> delay_in)
+delay_in_conn:RAM_link (some_outport -> delay_inport)
 
-in_signal:RAM_Signal
+in_signal:RAM_Signal {
+  # If the signal is already equal to the state, don't match:
+  # (without this, the rule could keep firing)
 
-port_has_signal:RAM_hasSignal (some_outport -> in_signal)
+  RAM_x = `get_value(this) != get_slot_value(matched('state'), 'x')`;
+}
+
+port_in_signal:RAM_hasSignal (some_outport -> in_signal)
 
 
 
@@ -20,8 +25,16 @@ port_has_signal:RAM_hasSignal (some_outport -> in_signal)
 
 state:RAM_State {
   # Attention: you MUST match the existing attribute, in order to force an UDPATE of the attribute, rather than CREATION
-  
   RAM_x = `True`;
 }
 
 delay_to_state:RAM_delay2State (delay -> state)
+
+
+
+# Only update Delay block state IF after its output signal has been computed:
+
+delay_outport:RAM_OutPort
+delay_has_outport:RAM_hasOutPort (delay -> delay_outport)
+out_signal:RAM_Signal
+delay_out_signal:RAM_hasSignal (delay_outport -> out_signal)

+ 4 - 5
examples/cbd/models/r_delay_in_nac.od

@@ -1,6 +1,5 @@
-state:RAM_State # <- must repeat elements from LHS that we refer to
 
-in_signal:RAM_Signal {
-  # If the signal is already equal to the state, the NAC holds:
-  RAM_x = `get_value(this) == get_slot_value(matched('state'), 'x')`;
-}
+:GlobalCondition {
+  # No NAC
+  condition = `False`;
+}

+ 14 - 5
examples/cbd/models/r_delay_in_rhs.od

@@ -2,17 +2,20 @@
 
 delay:RAM_Delay
 
-delay_in:RAM_InPort
+delay_inport:RAM_InPort
 
-delay_has_input:RAM_hasOutPort (delay -> delay_in)
+delay_has_inport:RAM_hasOutPort (delay -> delay_inport)
 
 some_outport:RAM_OutPort
 
-delay_in_conn:RAM_link (some_outport -> delay_in)
+delay_in_conn:RAM_link (some_outport -> delay_inport)
 
-in_signal:RAM_Signal
+in_signal:RAM_Signal {
+  # Need to repeat this slot, otherwise it will be deleted:
+  RAM_x = `get_value(this)`;
+}
 
-port_has_signal:RAM_hasSignal (some_outport -> in_signal)
+port_in_signal:RAM_hasSignal (some_outport -> in_signal)
 
 state:RAM_State {
   # Update:
@@ -24,3 +27,9 @@ state:RAM_State {
 }
 
 delay_to_state:RAM_delay2State (delay -> state)
+
+
+delay_outport:RAM_OutPort
+delay_has_outport:RAM_hasOutPort (delay -> delay_outport)
+out_signal:RAM_Signal
+delay_out_signal:RAM_hasSignal (delay_outport -> out_signal)

+ 0 - 2
examples/cbd/models/r_delay_out_lhs.od

@@ -10,5 +10,3 @@ delay_has_output:RAM_hasOutPort (delay -> delay_out)
 state:RAM_State
 
 delay_to_state:RAM_delay2State (delay -> state)
-
-clock:RAM_Clock

+ 0 - 2
examples/cbd/models/r_delay_out_rhs.od

@@ -10,8 +10,6 @@ state:RAM_State
 
 delay_to_state:RAM_delay2State (delay -> state)
 
-clock:RAM_Clock
-
 # To create:
 
 new_signal:RAM_Signal {

+ 0 - 2
examples/cbd/models/r_function_out_lhs.od

@@ -18,5 +18,3 @@ f:RAM_Function {
 f_outport:RAM_OutPort
 
 f_has_outport:RAM_hasOutPort (f -> f_outport)
-
-clock:RAM_Clock

+ 0 - 2
examples/cbd/models/r_function_out_rhs.od

@@ -6,8 +6,6 @@ f_outport:RAM_OutPort
 
 f_has_outport:RAM_hasOutPort (f -> f_outport)
 
-clock:RAM_Clock
-
 # To create:
 
 f_out_signal:RAM_Signal {

+ 92 - 78
examples/cbd/runner.py

@@ -1,7 +1,10 @@
+import functools
+import pprint
+
 from state.devstate import DevState
 from bootstrap.scd import bootstrap_scd
 
-from framework.conformance import Conformance, render_conformance_check_result
+from api.od import ODAPI
 
 from concrete_syntax.common import indent
 from concrete_syntax.textual_od import renderer as od_renderer
@@ -14,98 +17,109 @@ from transformation.matcher.mvs_adapter import match_od
 from transformation.rewriter import rewrite
 from transformation.cloner import clone_od
 
+from examples.semantics.operational import simulator
+
 import models
 
-state = DevState()
-scd_mmm = bootstrap_scd(state)
 
-print("Parsing models...")
-mm, mm_rt, m, m_rt_initial = models.get_fibonacci(state, scd_mmm)
-mm_rt_ram, rules = models.get_rules(state, mm_rt)
-
-# print("RT-MM")
-# print(make_plantuml_url(plantuml.render_class_diagram(state, mm_rt)))
+def match_rule(rule_name, od: ODAPI, lhs, nac):
+    lhs_matcher = match_od(state,
+        host_m=od.m,
+        host_mm=od.mm,
+        pattern_m=lhs,
+        pattern_mm=mm_rt_ram)
 
+    try:
+        for i, lhs_match in enumerate(lhs_matcher):
+            nac_matcher = match_od(state,
+                host_m=od.m,
+                host_mm=od.mm,
+                pattern_m=nac,
+                pattern_mm=mm_rt_ram,
+                pivot=lhs_match)
+
+            try:
+                for j, nac_match in enumerate(nac_matcher):
+                    break # there may be more NAC-matches, but we already now enough -> proceed to next lhs_match
+                else:
+                    yield lhs_match # got match
+            except Exception as e:
+                # Make exceptions raised in eval'ed code easier to trace:
+                e.add_note(f"while matching NAC of '{rule_name}'")
+                raise
 
-# print("RAMIFIED RT-MM")
-# print(make_plantuml_url(plantuml.render_class_diagram(state, mm_rt_ram)))
+    except Exception as e:
+        # Make exceptions raised in eval'ed code easier to trace:
+        e.add_note(f"while matching LHS of '{rule_name}'")
+        raise
 
-m_rt = m_rt_initial
-
-def get_matches():
-    for rule_name, rule in rules.items():
-        lhs = rule["lhs"]
-
-        lhs_matcher = match_od(state,
-            host_m=m_rt,
-            host_mm=mm_rt,
-            pattern_m=lhs,
-            pattern_mm=mm_rt_ram)
-
-        try:
-            for i, lhs_match in enumerate(lhs_matcher):
-                nac_matcher = match_od(state,
-                    host_m=m_rt,
-                    host_mm=mm_rt,
-                    pattern_m=rule["nac"],
-                    pattern_mm=mm_rt_ram,
-                    pivot=lhs_match)
-
-                try:
-                    for j, nac_match in enumerate(nac_matcher):
-                        break # there may be more NAC-matches, but we already now enough
-                    else:
-                        # We got a match!
-                        yield (rule_name, lhs, rule["rhs"], lhs_match)
-                except Exception as e:
-                    # Make exceptions raised in eval'ed code easier to trace:
-                    e.add_note(f"while matching NAC of '{rule_name}'")
-                    raise
-
-        except Exception as e:
-            # Make exceptions raised in eval'ed code easier to trace:
-            e.add_note(f"while matching LHS of '{rule_name}'")
-            raise
-
-while True:
-    # print(make_graphviz_url(graphviz.render_object_diagram(state, m_rt, mm_rt)))
-    cs = od_renderer.render_od(state, m_rt, mm_rt, hide_names=False)
-    print(indent(cs, 6))
-    conf = Conformance(state, m_rt, mm_rt)
-    print(render_conformance_check_result(conf.check_nominal()))
-
-    matches = list(get_matches())
-
-    print(f"There are {len(matches)} matches.")
-    if len(matches) == 0:
-        break
-    rule_name, lhs, rhs, lhs_match = matches[0]
-
-
-                # txt = graphviz.render_package("Host", graphviz.render_object_diagram(state, m_rt, mm_rt))
-                # txt += graphviz.render_package("LHS", graphviz.render_object_diagram(state, lhs, mm_rt_ram))
-                # txt += graphviz.render_trace_match(state, lhs_match, lhs, m_rt, color="orange")
-                # match_urls.append(make_graphviz_url(txt))
-
-    print(f"executing rule '{rule_name}' ", lhs_match)
-
-    # copy or will be overwritten in-place
-    m_rt = clone_od(state, m_rt, mm_rt)
+def exec_action(rule_name, od: ODAPI, lhs, rhs, lhs_match):
+    # copy these, will be overwritten in-place
+    cloned_m = clone_od(state, od.m, od.mm)
     rhs_match = dict(lhs_match)
+
     try:
         rewrite(state,
             lhs_m=lhs,
             rhs_m=rhs,
             pattern_mm=mm_rt_ram,
-            name_mapping=rhs_match,
-            host_m=m_rt,
-            host_mm=mm_rt)
+            lhs_name_mapping=rhs_match,
+            host_m=cloned_m,
+            host_mm=od.mm)
     except Exception as e:
         # Make exceptions raised in eval'ed code easier to trace:
         e.add_note(f"while executing RHS of '{rule_name}'")
         raise
 
-    # import subprocess
-    # subprocess.run(["firefox", "--new-window", *match_urls])
+    print("Updated match:\n" + indent(pp.pformat(rhs_match), 6))
+
+    return (ODAPI(state, cloned_m, od.mm), [f"executed rule '{rule_name}'"])
+
+pp = pprint.PrettyPrinter(depth=4)
+
+def attempt_rules(od: ODAPI, rule_dict):
+    at_least_one_match = False
+    for rule_name, rule in rule_dict.items():
+        for lhs_match in match_rule(rule_name, od, rule["lhs"], rule["nac"]):
+            # We got a match!
+            yield (rule_name + '\n' + indent(pp.pformat(lhs_match), 6),
+                functools.partial(exec_action,
+                    rule_name, od, rule["lhs"], rule["rhs"], lhs_match))
+            at_least_one_match = True
+    return at_least_one_match
+
+def get_actions(od: ODAPI):
+    # transformation schedule
+    rule_advance_time = rules["advance_time"]
+    rules_not_advancing_time = { rule_name: rule for rule_name, rule in rules.items() if rule_name != "advance_time" }
+    
+    at_least_one_match = yield from attempt_rules(od, rules_not_advancing_time)
+    if not at_least_one_match:
+        yield from attempt_rules(od, {"advance_time": rule_advance_time})
+
+
+
+state = DevState()
+scd_mmm = bootstrap_scd(state)
+
+print("Parsing models...")
+mm, mm_rt, m, m_rt_initial = models.get_fibonacci(state, scd_mmm)
+mm_rt_ram, rules = models.get_rules(state, mm_rt)
+
+# print("RT-MM")
+# print(make_plantuml_url(plantuml.render_class_diagram(state, mm_rt)))
+
+# print("RAMIFIED RT-MM")
+# print(make_plantuml_url(plantuml.render_class_diagram(state, mm_rt_ram)))
 
-# get_actions(state, rules, m_rt_initial, mm_rt)
+sim = simulator.Simulator(
+    action_generator=get_actions,
+    # decision_maker=simulator.InteractiveDecisionMaker(auto_proceed=False),
+    decision_maker=simulator.RandomDecisionMaker(seed=0),
+    termination_condition=lambda od: "Time is up" if od.get_slot_value(od.get_all_instances("Clock")[0][1], "time") >= 10 else None,
+    check_conformance=True,
+    verbose=True,
+    renderer=lambda od: od_renderer.render_od(state, od.m, od.mm, hide_names=False),
+)
+
+sim.run(ODAPI(state, m_rt_initial, mm_rt))

+ 6 - 2
examples/semantics/operational/simulator.py

@@ -36,7 +36,7 @@ class Simulator:
             print(*args)
 
     # Run simulation until termination condition satisfied
-    def run(self, od):
+    def run(self, od: ODAPI):
         self.__print("Start simulation")
         self.__print(f"Decision maker: {self.decision_maker}")
         step_counter = 0
@@ -112,8 +112,10 @@ class RandomDecisionMaker(DecisionMaker):
         return arr[i]
 
 class InteractiveDecisionMaker(DecisionMaker):
-    def __init__(self, msg="Select action:"):
+    # auto_proceed: whether to prompt if there is only one enabled action
+    def __init__(self, msg="Select action:", auto_proceed=False):
         self.msg = msg
+        self.auto_proceed = auto_proceed
 
     def __str__(self):
         return f"InteractiveDecisionMaker()"
@@ -125,6 +127,8 @@ class InteractiveDecisionMaker(DecisionMaker):
            arr.append(result)
         if len(arr) == 0:
            return
+        if len(arr) == 1 and self.auto_proceed:
+            return arr[0]
 
         def __choose():
            sys.stdout.write(f"{self.msg} ")

+ 131 - 116
transformation/rewriter.py

@@ -13,6 +13,7 @@ from services.primitives.actioncode_type import ActionCode
 from services.primitives.integer_type import Integer
 from util.eval import exec_then_eval, simply_exec
 
+
 def preprocess_rule(state, lhs: UUID, rhs: UUID, name_mapping):
     bottom = Bottom(state)
 
@@ -22,21 +23,27 @@ def preprocess_rule(state, lhs: UUID, rhs: UUID, name_mapping):
         and "GlobalCondition" not in name }
     common = { name for name in bottom.read_keys(lhs) if name in bottom.read_keys(rhs) and name in name_mapping }
 
-    print("to_delete:", to_delete)
-    print("to_create:", to_create)
-
     return to_delete, to_create, common
 
+class TryAgainNextRound(Exception):
+    pass
+
 # 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, host_mm: UUID):
+def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, lhs_name_mapping: dict, host_m: UUID, host_mm: UUID):
     bottom = Bottom(state)
 
-    orig_name_mapping = dict(name_mapping)
+    # Need to come up with a new, unique name when creating new element in host-model:
+    def first_available_name(prefix: str):
+        i = 0
+        while True:
+            name = prefix + str(i)
+            if len(bottom.read_outgoing_elements(host_m, name)) == 0:
+                return name # found unique name
+            i += 1
 
     # function that can be called from within RHS action code
     def matched_callback(pattern_name: str):
-        host_name = orig_name_mapping[pattern_name]
+        host_name = lhs_name_mapping[pattern_name]
         return bottom.read_outgoing_elements(host_m, host_name)[0]
 
     scd_metamodel_id = state.read_dict(state.read_root(), "SCD")
@@ -45,155 +52,163 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
     class_type = od.get_scd_mm_class_node(bottom)
     attr_link_type = od.get_scd_mm_attributelink_node(bottom)
     assoc_type = od.get_scd_mm_assoc_node(bottom)
+    actioncode_type = od.get_scd_mm_actioncode_node(bottom)
     modelref_type = od.get_scd_mm_modelref_node(bottom)
 
-    m_od = od.OD(host_mm, host_m, bottom.state)
+    # To be replaced by ODAPI (below)
+    host_od = od.OD(host_mm, host_m, bottom.state)
     rhs_od = od.OD(pattern_mm, rhs_m, bottom.state)
 
-    to_delete, to_create, common = preprocess_rule(state, lhs_m, rhs_m, orig_name_mapping)
+    host_odapi = ODAPI(state, host_m, host_mm)
+    host_mm_odapi = ODAPI(state, host_mm, scd_metamodel)
+    rhs_odapi = ODAPI(state, rhs_m, pattern_mm)
+    rhs_mm_odapi = ODAPI(state, pattern_mm, scd_metamodel)
+
+    to_delete, to_create, common = preprocess_rule(state, lhs_m, rhs_m, lhs_name_mapping)
+
+    print("to_delete:", to_delete)
+    print("to_create:", to_create)
+
+    # to be grown
+    rhs_name_mapping = { name : lhs_name_mapping[name] for name in common }
 
-    odapi = ODAPI(state, host_m, host_mm)
 
     # 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_el_name_to_delete = name_mapping[pattern_name_to_delete]
+        model_el_name_to_delete = lhs_name_mapping[pattern_name_to_delete]
         # print('deleting', model_el_name_to_delete)
         # Look up the matched element in the host graph
         el_to_delete, = bottom.read_outgoing_elements(host_m, model_el_name_to_delete)
         # Delete
         bottom.delete_element(el_to_delete)
-        # Remove from mapping
-        del name_mapping[pattern_name_to_delete]
 
-    # 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)
-        # We have to come up with a name for the element-to-create in the host graph
-        i = 0
-        while True:
-            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
-            i += 1
-        
-        # Determine the type of the thing 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_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_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_el_to_create, original_type, 'link', model_el_name_to_create))
+    # Perform creations - in the right order!
+    remaining_to_create = list(to_create)
+    while len(remaining_to_create) > 0:
+        next_round = []
+        for rhs_name in remaining_to_create:
+            # Determine the type of the thing to create
+            rhs_obj = rhs_odapi.get(rhs_name)
+            rhs_type = rhs_odapi.get_type(rhs_obj)
+            host_type = ramify.get_original_type(bottom, rhs_type)
+            # for debugging:
+            if host_type != None:
+                host_type_name = host_odapi.get_name(host_type)
             else:
-                original_type_name = od.get_object_name(bottom, host_mm, original_type)
-                print(" -> warning: don't know about", original_type_name)
-        else:
-            # print(" -> no original (un-RAMified) type")
-            # assume the type of the object is already the original type
-            # this is because primitive types (e.g., Integer) are not RAMified
-            type_name = od.get_object_name(bottom, pattern_mm, rhs_type)
-            if type_name == "ActionCode":
-                # Assume the string is a Python expression to evaluate
-                python_expr = ActionCode(UUID(bottom.read_value(rhs_el_to_create)), bottom.state).read()
-
-                result = exec_then_eval(python_expr, _globals={
-                    **bind_api(odapi),
-                    'matched': matched_callback,
-                })
-
-                # 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_el_name_to_create, result)
-                elif isinstance(result, str):
-                    m_od.create_string_value(model_el_name_to_create, result)
-                name_mapping[pattern_name_to_create] = model_el_name_to_create
-            else:
-                msg = 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}'"
-                raise Exception(msg)
-
-    # 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_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_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, host_mm, original_type)
-            m_od.create_link(model_el_name_to_create, attr_name, obj_name, name_mapping[tgt_name])
-
+                host_type_name = ""
+
+            def get_src_tgt():
+                src = rhs_odapi.get_source(rhs_obj)
+                tgt = rhs_odapi.get_target(rhs_obj)
+                src_name = rhs_odapi.get_name(src)
+                tgt_name = rhs_odapi.get_name(tgt)
+                try:
+                    host_src_name = rhs_name_mapping[src_name]
+                    host_tgt_name = rhs_name_mapping[tgt_name]
+                except KeyError:
+                    # some creations (e.g., edges) depend on other creations
+                    raise TryAgainNextRound()
+                host_src = host_odapi.get(host_src_name)
+                host_tgt = host_odapi.get(host_tgt_name)
+                return (host_src_name, host_tgt_name, host_src, host_tgt)
+
+            try:
+                if od.is_typed_by(bottom, rhs_type, class_type):
+                    obj_name = first_available_name(rhs_name)
+                    host_od._create_object(obj_name, host_type)
+                    host_odapi._ODAPI__recompute_mappings()
+                    rhs_name_mapping[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)
+                    host_od._create_link(link_name, host_type, host_src, host_tgt)
+                    host_odapi._ODAPI__recompute_mappings()
+                    rhs_name_mapping[rhs_name] = link_name
+                elif od.is_typed_by(bottom, rhs_type, attr_link_type):
+                    host_src_name, _, host_src, host_tgt = get_src_tgt()
+                    host_attr_link = ramify.get_original_type(bottom, rhs_type)
+                    host_attr_name = host_mm_odapi.get_slot_value(host_attr_link, "name")
+                    link_name = f"{host_src_name}_{host_attr_name}" # must follow naming convention here
+                    host_od._create_link(link_name, host_type, host_src, host_tgt)
+                    host_odapi._ODAPI__recompute_mappings()
+                    rhs_name_mapping[rhs_name] = link_name
+                elif rhs_type == rhs_mm_odapi.get("ActionCode"):
+                    # If we encounter ActionCode in our RHS, we assume that the code computes the value of an attribute...
+                    # This will be the *value* of an attribute. The attribute-link (connecting an object to the attribute) will be created as an edge later.
+
+                    # Problem: attributes must follow the naming pattern '<obj_name>.<attr_name>'
+                    # So we must know the host-object-name, and the host-attribute-name.
+                    # However, all we have access to here is the name of the attribute in the RHS.
+                    # We cannot even see the link to the RHS-object.
+                    # But, assuming the RHS-attribute is also named '<RAMified_obj_name>.<RAMified_attr_name>', we can:
+                    rhs_src_name, rhs_attr_name = rhs_name.split('.')
+                    try:
+                        host_src_name = rhs_name_mapping[rhs_src_name]
+                    except KeyError:
+                        # unmet dependency - object to which attribute belongs not created yet
+                        raise TryAgainNextRound()
+                    rhs_src_type = rhs_odapi.get_type(rhs_odapi.get(rhs_src_name))
+                    rhs_src_type_name = rhs_mm_odapi.get_name(rhs_src_type)
+                    rhs_attr_link_name = f"{rhs_src_type_name}_{rhs_attr_name}"
+                    rhs_attr_link = rhs_mm_odapi.get(rhs_attr_link_name)
+                    host_attr_link = ramify.get_original_type(bottom, rhs_attr_link)
+                    host_attr_name = host_mm_odapi.get_slot_value(host_attr_link, "name")
+                    val_name = f"{host_src_name}.{host_attr_name}"
+                    python_expr = ActionCode(UUID(bottom.read_value(rhs_obj)), bottom.state).read()
+                    result = exec_then_eval(python_expr, _globals={
+                        **bind_api(host_odapi),
+                        'matched': matched_callback,
+                    })
+                    host_odapi.create_primitive_value(val_name, result, is_code=False)
+                    host_odapi._ODAPI__recompute_mappings()
+                    rhs_name_mapping[rhs_name] = val_name
+                else:
+                    rhs_type_name = rhs_odapi.get_name(rhs_type)
+                    raise Exception(f"Host type {host_type_name} of pattern element '{rhs_name}:{rhs_type_name}' is not a class, association or attribute link. Don't know what to do with it :(")
+            except TryAgainNextRound:
+                next_round.append(rhs_name)
+
+        if len(next_round) == len(remaining_to_create):
+            raise Exception("Creation of objects did not make any progress - there must be some kind of cyclic dependency?!")
+
+        remaining_to_create = next_round
 
     # Perform updates (only on values)
-    for pattern_el_name in common:
-        host_el_name = name_mapping[pattern_el_name]
-        host_el, = bottom.read_outgoing_elements(host_m, host_el_name)
-        # print('updating', host_el_name, host_el)
-        host_type = od.get_type(bottom, host_el)
-        # print('we have', pattern_el_name, '->', host_el_name, 'of type', type_name)
+    for common_name in common:
+        host_obj_name = rhs_name_mapping[common_name]
+        host_obj = host_odapi.get(host_obj_name)
+        host_type = host_odapi.get_type(host_obj)
         if od.is_typed_by(bottom, host_type, class_type):
-            # print(' -> is classs')
             # nothing to do
             pass
         elif od.is_typed_by(bottom, host_type, assoc_type):
-            # print(' -> is association')
             # nothing to do
             pass
         elif od.is_typed_by(bottom, host_type, attr_link_type):
-            # 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, host_el, host_mm)
-            rhs_el, = bottom.read_outgoing_elements(rhs_m, pattern_el_name)
-            python_expr, _ = od.read_primitive_value(bottom, rhs_el, pattern_mm)
+            rhs_obj = rhs_odapi.get(common_name)
+            python_expr = ActionCode(UUID(bottom.read_value(rhs_obj)), bottom.state).read()
             result = exec_then_eval(python_expr,
                 _globals={
-                    **bind_api(odapi),
+                    **bind_api(host_odapi),
                     'matched': matched_callback,
                 },
-                _locals={'this': host_el})
-            # print('eval result=', result)
-            if isinstance(result, int):
-                # overwrite the old value, in-place
-                referred_model_id = UUID(bottom.read_value(host_el))
-                Integer(referred_model_id, state).create(result)
-            else:
-                raise Exception("Unimplemented type. Value:", result)
+                _locals={'this': host_obj}) # 'this' can be used to read the previous value of the slot
+            host_odapi.overwrite_primitive_value(host_obj_name, result, is_code=False)
         else:
-            msg = f"Don't know what to do with element '{pattern_el_name}'->'{host_el_name}' of type ({host_type})"
+            msg = f"Don't know what to do with element '{common_name}' -> '{host_obj_name}:{host_type}')"
             # print(msg)
             raise Exception(msg)
 
-    rhs_odapi = ODAPI(state, rhs_m, pattern_mm)
+    # Execute global conditions
     for cond_name, cond in rhs_odapi.get_all_instances("GlobalCondition"):
         python_code = rhs_odapi.get_slot_value(cond, "condition")
         simply_exec(python_code, _globals={
-            **bind_api(odapi),
+            **bind_api(host_odapi),
             'matched': matched_callback,
         })