Explorar el Código

Add ActionCode primitive type. Fix constraint checking.

Joeri Exelmans hace 1 año
padre
commit
59de61d0a3

+ 41 - 15
bootstrap/primitive.py

@@ -1,9 +1,10 @@
 from state.base import State, UUID
 from services.bottom.V0 import Bottom
 from services.primitives.integer_type import Integer
+from services.primitives.actioncode_type import ActionCode
 
 
-def bootstrap_type(type_name: str, python_type: str, scd_root: UUID, model_root: UUID, state: State):
+def bootstrap_type(type_name: str, scd_root: UUID, model_root: UUID, integer_type: UUID, state: State):
     bottom = Bottom(state)
     # create class
     class_node = bottom.create_node()  # create class node
@@ -17,7 +18,7 @@ def bootstrap_type(type_name: str, python_type: str, scd_root: UUID, model_root:
     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")
-    scd_node, = bottom.read_outgoing_elements(scd_root, "Integer")
+    scd_node = integer_type
     scd_link, = bottom.read_outgoing_elements(scd_root, "Class_lower_cardinality")
     bottom.create_edge(min_c_node, scd_node, "Morphism")
     bottom.create_edge(min_c_link, scd_link, "Morphism")
@@ -28,36 +29,61 @@ def bootstrap_type(type_name: str, python_type: str, scd_root: UUID, model_root:
     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")
-    scd_node, = bottom.read_outgoing_elements(scd_root, "Integer")
+    scd_node = integer_type
     scd_link, = bottom.read_outgoing_elements(scd_root, "Class_upper_cardinality")
     bottom.create_edge(max_c_node, scd_node, "Morphism")
     bottom.create_edge(max_c_link, scd_link, "Morphism")
+    return class_node
+
+def bootstrap_constraint(class_node, type_name: str, python_type: str, scd_root: UUID, model_root: UUID, actioncode_type: UUID, state: State):
+
     # set constraint
-    constraint_node = bottom.create_node(f"isinstance(read_value(element),{python_type})")
+    # chicken-and-egg problem: we cannot create an action-code constraint because the action-code MM doesn't exist yet
+
+    bottom = Bottom(state)
+    constraint_model = bottom.create_node()
+    ActionCode(constraint_model, state).create(f"isinstance(read_value(element),{python_type})")
+    constraint_node = bottom.create_node(str(constraint_model))
     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")
-    scd_node, = bottom.read_outgoing_elements(scd_root, "ActionCode")
+    scd_node = actioncode_type
     scd_link, = bottom.read_outgoing_elements(scd_root, "Element_constraint")
     bottom.create_edge(constraint_node, scd_node, "Morphism")
     bottom.create_edge(constraint_link, scd_link, "Morphism")
     
 
-def bootstrap_type_type(scd_root: UUID, model_root: UUID, state: State):
-    bootstrap_type("Type", "tuple", scd_root, model_root, state)
+# def bootstrap_type_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
+
+
+# def bootstrap_boolean_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
+
+
+# def bootstrap_integer_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
 
 
-def bootstrap_boolean_type(scd_root: UUID, model_root: UUID, state: State):
-    bootstrap_type("Boolean", "bool", scd_root, model_root, state)
+# def bootstrap_float_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
 
 
-def bootstrap_integer_type(scd_root: UUID, model_root: UUID, state: State):
-    bootstrap_type("Integer", "int", scd_root, model_root, state)
+# def bootstrap_string_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
 
+# def bootstrap_actioncode_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
+#     # we store action code as Python string:
 
-def bootstrap_float_type(scd_root: UUID, model_root: UUID, state: State):
-    bootstrap_type("Float", "float", scd_root, model_root, state)
+def bootstrap_primitive_types(scd_root, state, integer_type, boolean_type, float_type, string_type, type_type, actioncode_type):
+    # Order is important: Integer must come first
+    class_integer    = bootstrap_type("Integer",    scd_root, integer_type,    integer_type, state)
+    class_type       = bootstrap_type("Type",       scd_root, type_type,       integer_type, state)
+    class_boolean    = bootstrap_type("Boolean",    scd_root, boolean_type,    integer_type, state)
+    class_float      = bootstrap_type("Float",      scd_root, float_type,      integer_type, state)
+    class_string     = bootstrap_type("String",     scd_root, string_type,     integer_type, state)
+    class_actioncode = bootstrap_type("ActionCode", scd_root, actioncode_type, integer_type, state)
 
+    # Can only create constraints after ActionCode type has been created:
+    bootstrap_constraint(class_integer,    "Integer",    "int",   scd_root, integer_type,    actioncode_type, state)
+    bootstrap_constraint(class_type,       "Type",       "tuple", scd_root, type_type,       actioncode_type, state)
+    bootstrap_constraint(class_boolean,    "Boolean",    "bool",  scd_root, boolean_type,    actioncode_type, state)
+    bootstrap_constraint(class_float,      "Float",      "float", scd_root, float_type,      actioncode_type, state)
+    bootstrap_constraint(class_string,     "String",     "str",   scd_root, string_type,     actioncode_type, state)
+    bootstrap_constraint(class_actioncode, "ActionCode", "str",   scd_root, actioncode_type, actioncode_type, state)
 
-def bootstrap_string_type(scd_root: UUID, model_root: UUID, state: State):
-    bootstrap_type("String", "str", scd_root, model_root, state)

+ 27 - 14
bootstrap/scd.py

@@ -3,11 +3,13 @@ from services.bottom.V0 import Bottom
 from services.primitives.boolean_type import Boolean
 from services.primitives.string_type import String
 from bootstrap.primitive import (
-    bootstrap_boolean_type,
-    bootstrap_float_type,
-    bootstrap_integer_type,
-    bootstrap_string_type,
-    bootstrap_type_type
+    bootstrap_primitive_types
+    # bootstrap_boolean_type,
+    # bootstrap_float_type,
+    # bootstrap_integer_type,
+    # bootstrap_string_type,
+    # bootstrap_type_type,
+    # bootstrap_actioncode_type
 )
 
 
@@ -29,6 +31,7 @@ def bootstrap_scd(state: State) -> UUID:
     string_type_root = create_model_root(bottom, "String")
     float_type_root = create_model_root(bottom, "Float")
     type_type_root = create_model_root(bottom, "Type")
+    actioncode_type_root = create_model_root(bottom, "ActionCode")
 
     # create MCL, without morphism links
 
@@ -91,7 +94,7 @@ def bootstrap_scd(state: State) -> UUID:
 
     # # ATTRIBUTES, i.e. elements typed by Attribute
     # # Action Code # TODO: Update to ModelRef when action code is explicitly modelled
-    action_code_node = add_node_element("ActionCode")
+    # action_code_node = add_node_element("ActionCode")
 
     # # MODELREFS, i.e. elements typed by ModelRef
     # # Integer
@@ -100,6 +103,8 @@ def bootstrap_scd(state: State) -> UUID:
     string_node = add_node_element("String", str(string_type_root))
     # # Boolean
     boolean_node = add_node_element("Boolean", str(boolean_type_root))
+    # # ActionCode
+    actioncode_node = add_node_element("ActionCode", str(actioncode_type_root))
 
     # # ATTRIBUTE LINKS, i.e. elements typed by AttributeLink
     # # name attribute of AttributeLink
@@ -107,7 +112,7 @@ def bootstrap_scd(state: State) -> UUID:
     # # optional attribute of AttributeLink
     attr_opt_edge = add_edge_element("AttributeLink_optional", attr_link_edge, boolean_node)
     # # constraint attribute of Element
-    elem_constr_edge = add_edge_element("Element_constraint", element_node, action_code_node)
+    elem_constr_edge = add_edge_element("Element_constraint", element_node, actioncode_node)
     # # abstract attribute of Class
     class_abs_edge = add_edge_element("Class_abstract", class_node, boolean_node)
     # # multiplicity attributes of Class
@@ -120,12 +125,19 @@ def bootstrap_scd(state: State) -> UUID:
     assoc_t_u_c_edge = add_edge_element("Association_target_upper_cardinality", assoc_edge, integer_node)
 
     # # bootstrap primitive types
-    # # order is important, integer must be first
-    bootstrap_integer_type(mcl_root, integer_type_root, state)
-    bootstrap_boolean_type(mcl_root, boolean_type_root, state)
-    bootstrap_float_type(mcl_root, float_type_root, state)
-    bootstrap_string_type(mcl_root, string_type_root, state)
-    bootstrap_type_type(mcl_root, type_type_root, state)
+    bootstrap_primitive_types(mcl_root, state,
+        integer_type_root,
+        boolean_type_root,
+        float_type_root,
+        string_type_root,
+        type_type_root,
+        actioncode_type_root)
+    # bootstrap_integer_type(mcl_root, integer_type_root, integer_type_root, actioncode_type_root, state)
+    # bootstrap_boolean_type(mcl_root, boolean_type_root, integer_type_root, actioncode_type_root, state)
+    # bootstrap_float_type(mcl_root, float_type_root, integer_type_root, actioncode_type_root, state)
+    # bootstrap_string_type(mcl_root, string_type_root, integer_type_root, actioncode_type_root, state)
+    # bootstrap_type_type(mcl_root, type_type_root, integer_type_root, actioncode_type_root, state)
+    # bootstrap_actioncode_type(mcl_root, actioncode_type_root, integer_type_root, actioncode_type_root, state)
 
     # # ATTRIBUTE ATTRIBUTES, assign 'name' and 'optional' attributes to all AttributeLinks
     # # AttributeLink_name
@@ -203,11 +215,12 @@ def bootstrap_scd(state: State) -> UUID:
     add_mcl_morphism("attr_link_inh_element", "Inheritance")
     add_mcl_morphism("model_ref_inh_attr", "Inheritance")
     # Attribute
-    add_mcl_morphism("ActionCode", "Attribute")
+    # add_mcl_morphism("ActionCode", "Attribute")
     # ModelRef
     add_mcl_morphism("Integer", "ModelRef")
     add_mcl_morphism("String", "ModelRef")
     add_mcl_morphism("Boolean", "ModelRef")
+    add_mcl_morphism("ActionCode", "ModelRef")
     # AttributeLink
     add_mcl_morphism("AttributeLink_name", "AttributeLink")
     add_mcl_morphism("AttributeLink_optional", "AttributeLink")

+ 2 - 2
concrete_syntax/plantuml/renderer.py

@@ -21,7 +21,7 @@ def render_class_diagram(state, model, prefix_ids=""):
         is_abstract = False
         slot = model_od.get_slot(class_node, "abstract")
         if slot != None:
-            is_abstract = od.read_primitive_value(bottom, slot, model_od.type_model)
+            is_abstract, _ = od.read_primitive_value(bottom, slot, model_od.type_model)
 
         if is_abstract:
             output += f"\nabstract class \"{name}\" as {make_id(class_node)}"
@@ -97,7 +97,7 @@ def render_object_diagram(state, m, mm, render_attributes=True, prefix_ids=""):
                 for attr_name, attr_edge in attributes:
                     slot = m_od.get_slot(obj_node, attr_name)
                     if slot != None:
-                        output += f"\n{attr_name} => {json.dumps(od.read_primitive_value(bottom, slot, mm))}"
+                        output += f"\n{attr_name} => {json.dumps(od.read_primitive_value(bottom, slot, mm)[0])}"
             output += '\n}'
 
     output += '\n'

+ 15 - 1
concrete_syntax/textual_od/parser.py

@@ -24,11 +24,13 @@ _NL: /(\r?\n[\t ]*)+/
 literal: INT
        | STR
        | BOOL
+       | CODE
 
 INT: /[0-9]+/
 STR: /"[^"]*"/
    | /'[^']*'/
 BOOL: "True" | "False"
+CODE: /`[^`]*`/
 
 object: [IDENTIFIER] ":" IDENTIFIER [link_spec] _NL [_INDENT slot+ _DEDENT]
 link_spec: "(" IDENTIFIER "->" IDENTIFIER ")"
@@ -46,6 +48,12 @@ class TreeIndenter(Indenter):
 
 parser = Lark(grammar, parser='lalr', postlex=TreeIndenter())
 
+# internal use only
+# just a dumb wrapper to distinguish between code and string
+class _Code:
+    def __init__(self, code):
+        self.code = code
+
 # 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)
@@ -70,7 +78,10 @@ def parse_od(state, cs_text, mm):
             return token == "True"
 
         def STR(self, token):
-            return str(token[1:-1]) # strip the ""
+            return str(token[1:-1]) # strip the "" or ''
+
+        def CODE(self, token):
+            return _Code(str(token[1:-1])) # strip the ``
 
         def literal(self, el):
             return el[0]
@@ -111,9 +122,12 @@ def parse_od(state, cs_text, mm):
                     tgt = od.create_integer_value(value_name, value)
                 elif isinstance(value, str):
                     tgt = od.create_string_value(value_name, value)
+                elif isinstance(value, _Code):
+                    tgt = od.create_actioncode_value(value_name, value.code)
                 else:
                     raise Exception("Unimplemented type "+value)
                 od.create_slot(attr_name, obj_name, tgt)
+
             return obj_name
 
     t = T(visit_tokens=True).transform(tree)

+ 8 - 6
concrete_syntax/textual_od/renderer.py

@@ -4,13 +4,15 @@ from services import od
 from services.bottom.V0 import Bottom
 import json
 
-def display_value(val: any):
-    if isinstance(val, str):
+def display_value(val: any, type_name: str):
+    if type_name == "ActionCode":
+        return '`'+val+'`'
+    elif type_name == "String":
         return '"'+val+'"'
-    elif isinstance(val, int) or isinstance(val, bool):
+    elif type_name == "Integer" or type_name == "Boolean":
         return str(val)
     else:
-        raise Exception("don't know how to display value" + str(val))
+        raise Exception("don't know how to display value" + type_name)
 
 def render_od(state, m_id, mm_id, hide_names=True):
     bottom = Bottom(state)
@@ -25,8 +27,8 @@ def render_od(state, m_id, mm_id, hide_names=True):
     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"
+            value, type_name = m_od.read_slot(slot_node)
+            o += f"    {attr_name} = {display_value(value, type_name)}\n"
         return o
 
     for class_name, objects in m_od.get_all_objects().items():

+ 8 - 3
experiments/exp_scd.py

@@ -32,9 +32,9 @@ def main():
 
     conf = Conformance(state, scd_mm_id, scd_mm_id)
     print("Conformance SCD_MM -> SCD_MM?", conf.check_nominal(log=True))
-    # print("--------------------------------------")
-    # print(renderer.render_od(state, scd_mm_id, scd_mm_id, hide_names=False))
-    # print("--------------------------------------")
+    print("--------------------------------------")
+    print(renderer.render_od(state, scd_mm_id, scd_mm_id, hide_names=False))
+    print("--------------------------------------")
 
     def create_dsl_mm_api():
         # Create DSL MM with SCD API
@@ -54,6 +54,7 @@ def main():
             tgt_min_c=1,
             tgt_max_c=None,
         )
+        dsl_mm_scd.add_constraint("Man", "read_value(element) < 100")
         return dsl_mm_id
 
     def create_dsl_mm_parser():
@@ -66,13 +67,17 @@ Animal:Class
 Man:Class
     lower_cardinality = 1
     upper_cardinality = 2
+#    constraint = `get_value(get_slot(element, "weight")) < 100`
 Man_weight:AttributeLink (Man -> Integer)
     name = "weight"
     optional = False
+    constraint = `get_value(get_target(element)) < 100`
 afraidOf:Association (Man -> Animal)
     target_lower_cardinality = 1
 Man_inh_Animal:Inheritance (Man -> Animal)
 Bear_inh_Animal:Inheritance (Bear -> Animal)
+sum_of_weights:GlobalConstraint
+    constraint = `len(get_all_instances("afraidOf")) <= 1`
 """
         dsl_mm_id = parser.parse_od(state, dsl_mm_cs, mm=scd_mm_id)
         return dsl_mm_id

+ 62 - 20
framework/conformance.py

@@ -1,4 +1,6 @@
 from services.bottom.V0 import Bottom
+from services import od
+from services.primitives.actioncode_type import ActionCode
 from uuid import UUID
 from state.base import State
 from typing import Dict, Tuple, Set, Any, List
@@ -99,7 +101,7 @@ class Conformance:
             model = self.model
         try:
             attr_elem, = self.bottom.read_outgoing_elements(model, f"{element_name}.{attr_name}")
-            return self.primitive_values.get(attr_elem, self.bottom.read_value(attr_elem))
+            return self.primitive_values.get(attr_elem, self.bottom.read_value(UUID(self.bottom.read_value(attr_elem))))
         except ValueError:
             return None
 
@@ -357,42 +359,82 @@ class Conformance:
         """
         Evaluate constraint code (Python code)
         """
+
         funcs = {
-            'read_value': self.state.read_value
+            'read_value': self.state.read_value,
+            'get_value': lambda el: od.read_primitive_value(self.bottom, el, self.type_model)[0],
+            'get_target': lambda el: self.bottom.read_edge_target(el),
+            'get_slot': od.OD(self.type_model, self.model, self.state).get_slot,
+            'get_all_instances': self.get_all_instances
         }
-        return eval(
+        # print("evaluating constraint ...", code)
+        result = eval(
             code,
             {'__builtins__': {'isinstance': isinstance, 'print': print,
-                              'int': int, 'float': float, 'bool': bool, 'str': str, 'tuple': tuple}
+                              'int': int, 'float': float, 'bool': bool, 'str': str, 'tuple': tuple, 'len': len}
              },  # globals
             {**kwargs, **funcs}  # locals
         )
+        # print('result =', result)
+        return result
+
+    def get_all_instances(self, type_name: str, include_subtypes=True):
+        result = [e_name for e_name, t_name in self.type_mapping.items() if t_name == type_name]
+        if include_subtypes:
+            for subtype_name in self.sub_types[type_name]:
+                # print(subtype_name, 'is subtype of ')
+                result += [e_name for e_name, t_name in self.type_mapping.items() if t_name == subtype_name]
+        return result
 
     def check_constraints(self):
         """
         Check whether all constraints defined for a model are respected
         """
-        # local constraints
         errors = []
+
+        def get_code(tm_name):
+            constraints = self.bottom.read_outgoing_elements(self.type_model, f"{tm_name}.constraint")
+            if len(constraints) == 1:
+                constraint = constraints[0]
+                code = ActionCode(UUID(self.bottom.read_value(constraint)), self.bottom.state).read()
+                return code
+
+        def check_result(result, local_or_global, tm_name, el_name=None):
+            suffix = f"in '{el_name}'" if local_or_global == "Local" else ""
+            if not isinstance(result, bool):
+                errors.append(f"{local_or_global} constraint `{code}` of '{tm_name}'{suffix} did not return boolean, instead got {type(result)} (value = {str(result)}).")
+            elif not result:
+                errors.append(f"{local_or_global} constraint `{code}` of '{tm_name}'{suffix} not satisfied.")
+
+        # local constraints
         for m_name, tm_name in self.type_mapping.items():
-            if tm_name != "GlobalConstraint":
+            code = get_code(tm_name)
+            if code != None:
                 tm_element, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
-                code = self.read_attribute(tm_element, "constraint")
-                if code != None:
-                    morphisms = self.bottom.read_incoming_elements(tm_element, "Morphism")
-                    morphisms = [m for m in morphisms if m in self.model_names]
-                    for m_element in morphisms:
-                        if not self.evaluate_constraint(code, element=m_element):
-                            errors.append(f"Local constraint of {tm_name} not satisfied in {m_name}.")
+                morphisms = self.bottom.read_incoming_elements(tm_element, "Morphism")
+                morphisms = [m for m in morphisms if m in self.model_names]
+                for m_element in morphisms:
+                    result = self.evaluate_constraint(code, element=m_element, type_name=tm_name)
+                    check_result(result, "Local", tm_name, m_name)
 
         # global constraints
-        for m_name, tm_name in self.type_mapping.items():
-            if tm_name == "GlobalConstraint":
-                tm_element, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
-                code = self.read_attribute(tm_element, "constraint")
-                if code != None:
-                    if not self.evaluate_constraint(code, model=self.model):
-                        errors.append(f"Global constraint {tm_name} not satisfied.")
+        glob_constraints = []
+        # find global constraints...
+        glob_constraint_type, = self.bottom.read_outgoing_elements(self.scd_model, "GlobalConstraint")
+        for tm_name in self.bottom.read_keys(self.type_model):
+            tm_node, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
+            # print(key,  node)
+            for type_of_node in self.bottom.read_outgoing_elements(tm_node, "Morphism"):
+                if type_of_node == glob_constraint_type:
+                    # node is GlobalConstraint
+                    glob_constraints.append(tm_name)
+        # evaluate them
+        for tm_name in glob_constraints:
+            code = get_code(tm_name)
+            if code != None:
+                # print('glob constr:', code)
+                result = self.evaluate_constraint(code, model=self.model)
+                check_result(result, "Global", tm_name)
         return errors
 
     def precompute_structures(self):

+ 45 - 11
services/od.py

@@ -4,10 +4,12 @@ from services.bottom.V0 import Bottom
 from services.primitives.integer_type import Integer
 from services.primitives.string_type import String
 from services.primitives.boolean_type import Boolean
+from services.primitives.actioncode_type import ActionCode
+from framework.conformance import Conformance
 from typing import Optional
 
-def get_attr_link_name(class_name: str, attr_name: str):
-    return f"{class_name}_{attr_name}"
+def get_slot_link_name(obj_name: str, attr_name: str):
+    return f"{obj_name}_{attr_name}"
 
 # Object Diagrams service
 
@@ -42,7 +44,7 @@ class OD:
 
         slot = mm_od.get_slot(class_node, "abstract")
         if slot != None:
-            is_abstract = read_primitive_value(self.bottom, slot, self.type_model)
+            is_abstract, _ = read_primitive_value(self.bottom, slot, self.type_model)
             if is_abstract:
                 raise Exception("Cannot instantiate abstract class!")
 
@@ -66,17 +68,17 @@ class OD:
 
     def create_slot(self, attr_name: str, object_name: str, target_name: str):
         class_name = self.get_class_of_object(object_name)
-        attr_link_name = get_attr_link_name(class_name, attr_name)
+        attr_link_name = self.get_attr_link_name(class_name, attr_name)
         # An attribute-link is indistinguishable from an ordinary link:
         slot_id = self.create_link(
-            get_attr_link_name(object_name, attr_name),
+            get_slot_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...
         class_name = self._get_class_of_object(object_node)
-        attr_link_name = get_attr_link_name(class_name, attr_name)
+        attr_link_name = self.get_attr_link_name(class_name, attr_name)
         type_edge, = self.bottom.read_outgoing_elements(self.type_model, attr_link_name)
         for outgoing_edge in self.bottom.read_outgoing_edges(object_node):
             if type_edge in self.bottom.read_outgoing_elements(outgoing_edge, "Morphism"):
@@ -130,6 +132,16 @@ class OD:
         self.create_model_ref(name, "String", string_node)
         return name
 
+    def create_actioncode_value(self, name: str, value: str):
+        from services.primitives.actioncode_type import ActionCode
+        actioncode_node = self.bottom.create_node()
+        actioncode_t = ActionCode(actioncode_node, self.bottom.state)
+        actioncode_t.create(value)
+        # name = '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, "ActionCode", actioncode_node)
+        return name
+
     # Identical to the same SCD method:
     def create_model_ref(self, name: str, type_name: str, model: UUID):
         # create element + morphism links
@@ -137,7 +149,24 @@ class OD:
         self.bottom.create_edge(self.model, element_node, name)  # attach to model
         type_node, = self.bottom.read_outgoing_elements(self.type_model, type_name)  # retrieve type
         self.bottom.create_edge(element_node, type_node, "Morphism")  # create morphism link
-
+        # print('model ref:', name, type_name, element_node, model)
+        return element_node
+
+    # The edge connecting an object to the value of a slot must be named `{object_name}_{attr_name}`
+    def get_attr_link_name(self, class_name, attr_name):
+        assoc_name = f"{class_name}_{attr_name}"
+        type_edges = self.bottom.read_outgoing_elements(self.type_model, assoc_name)
+        if len(type_edges) == 1:
+            return assoc_name
+        else:
+            # look for attribute in the super-types
+            conf = Conformance(self.bottom.state, self.type_model, get_scd_mm(self.bottom))
+            conf.precompute_sub_types() # only need to know about subtypes
+            super_types = (s for s in conf.sub_types if class_name in conf.sub_types[s])
+            for s in super_types:
+                assoc_name = f"{s}_{attr_name}"
+                if len(self.bottom.read_outgoing_elements(self.type_model, assoc_name)) == 1:
+                    return assoc_name
 
     def create_link(self, link_name: Optional[str], assoc_name: str, src_obj_name: str, tgt_obj_name: str):
         src_obj_node, = self.bottom.read_outgoing_elements(self.model, src_obj_name)
@@ -246,6 +275,9 @@ def get_scd_mm_assoc_node(bottom: Bottom):
 def get_scd_mm_modelref_node(bottom: Bottom):
     return get_scd_mm_node(bottom, "ModelRef")
 
+def get_scd_mm_actioncode_node(bottom: Bottom):
+    return get_scd_mm_node(bottom, "ActionCode")
+
 def get_scd_mm_node(bottom: Bottom, node_name: str):
     scd_metamodel = get_scd_mm(bottom)
     node, = bottom.read_outgoing_elements(scd_metamodel, node_name)
@@ -323,15 +355,17 @@ def get_attr_name(bottom, attr_edge: UUID):
 def read_primitive_value(bottom, modelref: UUID, mm: UUID):
     typ = get_type(bottom, modelref)
     if not is_typed_by(bottom, typ, get_scd_mm_modelref_node(bottom)):
-        raise Exception("Assertion failed: argument must be typed by ModelRef")
+        raise Exception("Assertion failed: argument must be typed by ModelRef", typ)
     referred_model = UUID(bottom.read_value(modelref))
     typ_name = get_object_name(bottom, mm, typ)
     if typ_name == "Integer":
-        return Integer(referred_model, bottom.state).read()
+        return Integer(referred_model, bottom.state).read(), typ_name
     elif typ_name == "String":
-        return String(referred_model, bottom.state).read()
+        return String(referred_model, bottom.state).read(), typ_name
     elif typ_name == "Boolean":
-        return Boolean(referred_model, bottom.state).read()
+        return Boolean(referred_model, bottom.state).read(), typ_name
+    elif typ_name == "ActionCode":
+        return ActionCode(referred_model, bottom.state).read(), typ_name
     else:
         raise Exception("Unimplemented type:", typ_name)
 

+ 24 - 0
services/primitives/actioncode_type.py

@@ -0,0 +1,24 @@
+from uuid import UUID
+from state.base import State
+from services.bottom.V0 import Bottom
+
+
+class ActionCode:
+    def __init__(self, model: UUID, state: State):
+        self.model = model
+        self.bottom = Bottom(state)
+        type_model_id_node, = self.bottom.read_outgoing_elements(state.read_root(), "ActionCode")
+        self.type_model = UUID(self.bottom.read_value(type_model_id_node))
+
+    def create(self, value: str):
+        if "code" in self.bottom.read_keys(self.model):
+            instance, = self.bottom.read_outgoing_elements(self.model, "code")
+            self.bottom.delete_element(instance)
+        _instance = self.bottom.create_node(value)
+        self.bottom.create_edge(self.model, _instance, "code")
+        _type, = self.bottom.read_outgoing_elements(self.type_model, "ActionCode")
+        self.bottom.create_edge(_instance, _type, "Morphism")
+
+    def read(self):
+        instance, = self.bottom.read_outgoing_elements(self.model, "code")
+        return self.bottom.read_value(instance)

+ 23 - 9
services/scd.py

@@ -4,6 +4,7 @@ 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 services import od
 
 import re
@@ -274,15 +275,28 @@ class SCD:
             Nothing.
         """
         element_node, = self.bottom.read_outgoing_elements(self.model, element)  # retrieve element
-        # code attribute
-        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")
-        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")
-        self.bottom.create_edge(code_link, scd_link, "Morphism")
+        # # code attribute
+        # 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")
+        # 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")
+        # self.bottom.create_edge(code_link, scd_link, "Morphism")
+
+
+        constraint_model = self.bottom.create_node()
+        ActionCode(constraint_model, self.bottom.state).create(code)
+        constraint_node = self.bottom.create_node(str(constraint_model))
+        self.bottom.create_edge(self.model, constraint_node, f"{element}.constraint")
+        constraint_link = self.bottom.create_edge(element_node, constraint_node)
+        self.bottom.create_edge(self.model, constraint_link, f"{element}_constraint")
+        type_node, = self.bottom.read_outgoing_elements(self.scd_model, "ActionCode")
+        type_link, = self.bottom.read_outgoing_elements(self.scd_model, "Element_constraint")
+        self.bottom.create_edge(constraint_node, type_node, "Morphism")
+        self.bottom.create_edge(constraint_link, type_link, "Morphism")
+
 
     def list_elements(self):
         """

+ 2 - 2
transformation/rewriter.py

@@ -143,9 +143,9 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
             pass
         elif od.is_typed_by(bottom, host_type, modelref_type):
             # print(' -> is modelref')
-            old_value = od.read_primitive_value(bottom, model_el, mm)
+            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)
+            expr, _ = od.read_primitive_value(bottom, rhs_el, pattern_mm)
             result = eval(expr, {}, {'v': old_value})
             # print('eval result=', result)
             if isinstance(result, int):