Add ActionCode primitive type. Fix constraint checking.

This commit is contained in:
Joeri Exelmans 2024-10-07 16:08:23 +02:00
parent 0785b9218e
commit 59de61d0a3
11 changed files with 256 additions and 82 deletions

View file

@ -1,9 +1,10 @@
from state.base import State, UUID from state.base import State, UUID
from services.bottom.V0 import Bottom from services.bottom.V0 import Bottom
from services.primitives.integer_type import Integer 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) bottom = Bottom(state)
# create class # create class
class_node = bottom.create_node() # create class node 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") bottom.create_edge(model_root, min_c_node, f"{type_name}.lower_cardinality")
min_c_link = bottom.create_edge(class_node, min_c_node) min_c_link = bottom.create_edge(class_node, min_c_node)
bottom.create_edge(model_root, min_c_link, f"{type_name}_lower_cardinality") 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") 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_node, scd_node, "Morphism")
bottom.create_edge(min_c_link, scd_link, "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") bottom.create_edge(model_root, max_c_node, f"{type_name}.upper_cardinality")
max_c_link = bottom.create_edge(class_node, max_c_node) max_c_link = bottom.create_edge(class_node, max_c_node)
bottom.create_edge(model_root, max_c_link, f"{type_name}_upper_cardinality") 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") 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_node, scd_node, "Morphism")
bottom.create_edge(max_c_link, scd_link, "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 # 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") bottom.create_edge(model_root, constraint_node, f"{type_name}.constraint")
constraint_link = bottom.create_edge(class_node, constraint_node) constraint_link = bottom.create_edge(class_node, constraint_node)
bottom.create_edge(model_root, constraint_link, f"{type_name}_constraint") 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") scd_link, = bottom.read_outgoing_elements(scd_root, "Element_constraint")
bottom.create_edge(constraint_node, scd_node, "Morphism") bottom.create_edge(constraint_node, scd_node, "Morphism")
bottom.create_edge(constraint_link, scd_link, "Morphism") bottom.create_edge(constraint_link, scd_link, "Morphism")
def bootstrap_type_type(scd_root: UUID, model_root: UUID, state: State): # def bootstrap_type_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
bootstrap_type("Type", "tuple", scd_root, model_root, state)
def bootstrap_boolean_type(scd_root: UUID, model_root: UUID, state: State): # def bootstrap_boolean_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
bootstrap_type("Boolean", "bool", scd_root, model_root, state)
def bootstrap_integer_type(scd_root: UUID, model_root: UUID, state: State): # def bootstrap_integer_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
bootstrap_type("Integer", "int", scd_root, model_root, state)
def bootstrap_float_type(scd_root: UUID, model_root: UUID, state: State): # def bootstrap_float_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
bootstrap_type("Float", "float", scd_root, model_root, state)
def bootstrap_string_type(scd_root: UUID, model_root: UUID, state: State): # def bootstrap_string_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
bootstrap_type("String", "str", scd_root, model_root, 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)

View file

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

View file

@ -21,7 +21,7 @@ def render_class_diagram(state, model, prefix_ids=""):
is_abstract = False is_abstract = False
slot = model_od.get_slot(class_node, "abstract") slot = model_od.get_slot(class_node, "abstract")
if slot != None: 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: if is_abstract:
output += f"\nabstract class \"{name}\" as {make_id(class_node)}" 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: for attr_name, attr_edge in attributes:
slot = m_od.get_slot(obj_node, attr_name) slot = m_od.get_slot(obj_node, attr_name)
if slot != None: 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}'
output += '\n' output += '\n'

View file

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

View file

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

View file

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

View file

@ -1,4 +1,6 @@
from services.bottom.V0 import Bottom from services.bottom.V0 import Bottom
from services import od
from services.primitives.actioncode_type import ActionCode
from uuid import UUID from uuid import UUID
from state.base import State from state.base import State
from typing import Dict, Tuple, Set, Any, List from typing import Dict, Tuple, Set, Any, List
@ -99,7 +101,7 @@ class Conformance:
model = self.model model = self.model
try: try:
attr_elem, = self.bottom.read_outgoing_elements(model, f"{element_name}.{attr_name}") 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: except ValueError:
return None return None
@ -357,42 +359,82 @@ class Conformance:
""" """
Evaluate constraint code (Python code) Evaluate constraint code (Python code)
""" """
funcs = { 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, code,
{'__builtins__': {'isinstance': isinstance, 'print': print, {'__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 }, # globals
{**kwargs, **funcs} # locals {**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): def check_constraints(self):
""" """
Check whether all constraints defined for a model are respected Check whether all constraints defined for a model are respected
""" """
# local constraints
errors = [] 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(): 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) tm_element, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
code = self.read_attribute(tm_element, "constraint") morphisms = self.bottom.read_incoming_elements(tm_element, "Morphism")
if code != None: morphisms = [m for m in morphisms if m in self.model_names]
morphisms = self.bottom.read_incoming_elements(tm_element, "Morphism") for m_element in morphisms:
morphisms = [m for m in morphisms if m in self.model_names] result = self.evaluate_constraint(code, element=m_element, type_name=tm_name)
for m_element in morphisms: check_result(result, "Local", tm_name, m_name)
if not self.evaluate_constraint(code, element=m_element):
errors.append(f"Local constraint of {tm_name} not satisfied in {m_name}.")
# global constraints # global constraints
for m_name, tm_name in self.type_mapping.items(): glob_constraints = []
if tm_name == "GlobalConstraint": # find global constraints...
tm_element, = self.bottom.read_outgoing_elements(self.type_model, tm_name) glob_constraint_type, = self.bottom.read_outgoing_elements(self.scd_model, "GlobalConstraint")
code = self.read_attribute(tm_element, "constraint") for tm_name in self.bottom.read_keys(self.type_model):
if code != None: tm_node, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
if not self.evaluate_constraint(code, model=self.model): # print(key, node)
errors.append(f"Global constraint {tm_name} not satisfied.") 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 return errors
def precompute_structures(self): def precompute_structures(self):

View file

@ -4,10 +4,12 @@ from services.bottom.V0 import Bottom
from services.primitives.integer_type import Integer from services.primitives.integer_type import Integer
from services.primitives.string_type import String from services.primitives.string_type import String
from services.primitives.boolean_type import Boolean from services.primitives.boolean_type import Boolean
from services.primitives.actioncode_type import ActionCode
from framework.conformance import Conformance
from typing import Optional from typing import Optional
def get_attr_link_name(class_name: str, attr_name: str): def get_slot_link_name(obj_name: str, attr_name: str):
return f"{class_name}_{attr_name}" return f"{obj_name}_{attr_name}"
# Object Diagrams service # Object Diagrams service
@ -42,7 +44,7 @@ class OD:
slot = mm_od.get_slot(class_node, "abstract") slot = mm_od.get_slot(class_node, "abstract")
if slot != None: 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: if is_abstract:
raise Exception("Cannot instantiate abstract class!") 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): def create_slot(self, attr_name: str, object_name: str, target_name: str):
class_name = self.get_class_of_object(object_name) 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: # An attribute-link is indistinguishable from an ordinary link:
slot_id = self.create_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) attr_link_name, object_name, target_name)
return slot_id return slot_id
def get_slot(self, object_node: UUID, attr_name: str): 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... # 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) 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) type_edge, = self.bottom.read_outgoing_elements(self.type_model, attr_link_name)
for outgoing_edge in self.bottom.read_outgoing_edges(object_node): for outgoing_edge in self.bottom.read_outgoing_edges(object_node):
if type_edge in self.bottom.read_outgoing_elements(outgoing_edge, "Morphism"): 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) self.create_model_ref(name, "String", string_node)
return name 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: # Identical to the same SCD method:
def create_model_ref(self, name: str, type_name: str, model: UUID): def create_model_ref(self, name: str, type_name: str, model: UUID):
# create element + morphism links # create element + morphism links
@ -137,7 +149,24 @@ class OD:
self.bottom.create_edge(self.model, element_node, name) # attach to model 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 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 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): 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) 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): def get_scd_mm_modelref_node(bottom: Bottom):
return get_scd_mm_node(bottom, "ModelRef") 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): def get_scd_mm_node(bottom: Bottom, node_name: str):
scd_metamodel = get_scd_mm(bottom) scd_metamodel = get_scd_mm(bottom)
node, = bottom.read_outgoing_elements(scd_metamodel, node_name) 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): def read_primitive_value(bottom, modelref: UUID, mm: UUID):
typ = get_type(bottom, modelref) typ = get_type(bottom, modelref)
if not is_typed_by(bottom, typ, get_scd_mm_modelref_node(bottom)): 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)) referred_model = UUID(bottom.read_value(modelref))
typ_name = get_object_name(bottom, mm, typ) typ_name = get_object_name(bottom, mm, typ)
if typ_name == "Integer": if typ_name == "Integer":
return Integer(referred_model, bottom.state).read() return Integer(referred_model, bottom.state).read(), typ_name
elif typ_name == "String": elif typ_name == "String":
return String(referred_model, bottom.state).read() return String(referred_model, bottom.state).read(), typ_name
elif typ_name == "Boolean": 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: else:
raise Exception("Unimplemented type:", typ_name) raise Exception("Unimplemented type:", typ_name)

View file

@ -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)

View file

@ -4,6 +4,7 @@ from services.bottom.V0 import Bottom
from services.primitives.boolean_type import Boolean from services.primitives.boolean_type import Boolean
from services.primitives.integer_type import Integer from services.primitives.integer_type import Integer
from services.primitives.string_type import String from services.primitives.string_type import String
from services.primitives.actioncode_type import ActionCode
from services import od from services import od
import re import re
@ -274,15 +275,28 @@ class SCD:
Nothing. Nothing.
""" """
element_node, = self.bottom.read_outgoing_elements(self.model, element) # retrieve element element_node, = self.bottom.read_outgoing_elements(self.model, element) # retrieve element
# code attribute # # code attribute
code_node = self.bottom.create_node(code) # code_node = self.bottom.create_node(code)
self.bottom.create_edge(self.model, code_node, f"{element}.constraint") # self.bottom.create_edge(self.model, code_node, f"{element}.constraint")
code_link = self.bottom.create_edge(element_node, code_node) # code_link = self.bottom.create_edge(element_node, code_node)
self.bottom.create_edge(self.model, code_link, f"{element}_constraint") # self.bottom.create_edge(self.model, code_link, f"{element}_constraint")
scd_node, = self.bottom.read_outgoing_elements(self.scd_model, "ActionCode") # scd_node, = self.bottom.read_outgoing_elements(self.scd_model, "ActionCode")
scd_link, = self.bottom.read_outgoing_elements(self.scd_model, "Element_constraint") # 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_node, scd_node, "Morphism")
self.bottom.create_edge(code_link, scd_link, "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): def list_elements(self):
""" """

View file

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