diff --git a/bootstrap/primitive.py b/bootstrap/primitive.py index 33b8c2d..1934ec1 100644 --- a/bootstrap/primitive.py +++ b/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, state: State): - bootstrap_type("Boolean", "bool", scd_root, model_root, 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, state: State): - bootstrap_type("Integer", "int", scd_root, model_root, state) +# def bootstrap_integer_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State): -def bootstrap_float_type(scd_root: UUID, model_root: UUID, state: State): - bootstrap_type("Float", "float", 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_string_type(scd_root: UUID, model_root: UUID, state: State): - bootstrap_type("String", "str", 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_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) + diff --git a/bootstrap/scd.py b/bootstrap/scd.py index 172fe6b..03dd567 100644 --- a/bootstrap/scd.py +++ b/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") diff --git a/concrete_syntax/plantuml/renderer.py b/concrete_syntax/plantuml/renderer.py index 3cdeeac..56e4a54 100644 --- a/concrete_syntax/plantuml/renderer.py +++ b/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' diff --git a/concrete_syntax/textual_od/parser.py b/concrete_syntax/textual_od/parser.py index 736d64e..0aeebe8 100644 --- a/concrete_syntax/textual_od/parser.py +++ b/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) diff --git a/concrete_syntax/textual_od/renderer.py b/concrete_syntax/textual_od/renderer.py index d1dc888..8968add 100644 --- a/concrete_syntax/textual_od/renderer.py +++ b/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(): diff --git a/experiments/exp_scd.py b/experiments/exp_scd.py index c59de90..c3233eb 100644 --- a/experiments/exp_scd.py +++ b/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 diff --git a/framework/conformance.py b/framework/conformance.py index 94993b7..94395fc 100644 --- a/framework/conformance.py +++ b/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): diff --git a/services/od.py b/services/od.py index dd56143..f312a93 100644 --- a/services/od.py +++ b/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) diff --git a/services/primitives/actioncode_type.py b/services/primitives/actioncode_type.py new file mode 100644 index 0000000..9986548 --- /dev/null +++ b/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) \ No newline at end of file diff --git a/services/scd.py b/services/scd.py index af3c463..e2c1daa 100644 --- a/services/scd.py +++ b/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): """ diff --git a/transformation/rewriter.py b/transformation/rewriter.py index 4e5fd8e..5477a05 100644 --- a/transformation/rewriter.py +++ b/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):