Class diagram can be rendered as object diagram textual syntax, and parsed back, without information loss
This commit is contained in:
parent
f45872d3f7
commit
175edb64d9
14 changed files with 505 additions and 249 deletions
|
|
@ -16,7 +16,7 @@ def bootstrap_type(type_name: str, python_type: str, scd_root: UUID, model_root:
|
|||
min_c_node = bottom.create_node(str(min_c_model))
|
||||
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_link")
|
||||
bottom.create_edge(model_root, min_c_link, f"{type_name}_lower_cardinality")
|
||||
scd_node, = bottom.read_outgoing_elements(scd_root, "Integer")
|
||||
scd_link, = bottom.read_outgoing_elements(scd_root, "Class_lower_cardinality")
|
||||
bottom.create_edge(min_c_node, scd_node, "Morphism")
|
||||
|
|
@ -27,7 +27,7 @@ def bootstrap_type(type_name: str, python_type: str, scd_root: UUID, model_root:
|
|||
max_c_node = bottom.create_node(str(max_c_model))
|
||||
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_link")
|
||||
bottom.create_edge(model_root, max_c_link, f"{type_name}_upper_cardinality")
|
||||
scd_node, = bottom.read_outgoing_elements(scd_root, "Integer")
|
||||
scd_link, = bottom.read_outgoing_elements(scd_root, "Class_upper_cardinality")
|
||||
bottom.create_edge(max_c_node, scd_node, "Morphism")
|
||||
|
|
@ -36,7 +36,7 @@ def bootstrap_type(type_name: str, python_type: str, scd_root: UUID, model_root:
|
|||
constraint_node = bottom.create_node(f"isinstance(read_value(element),{python_type})")
|
||||
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_link")
|
||||
bottom.create_edge(model_root, constraint_link, f"{type_name}_constraint")
|
||||
scd_node, = bottom.read_outgoing_elements(scd_root, "ActionCode")
|
||||
scd_link, = bottom.read_outgoing_elements(scd_root, "Element_constraint")
|
||||
bottom.create_edge(constraint_node, scd_node, "Morphism")
|
||||
|
|
|
|||
|
|
@ -47,10 +47,10 @@ def bootstrap_scd(state: State) -> UUID:
|
|||
def add_attribute_attributes(attribute_element_name, attribute_element):
|
||||
_name_model = bottom.create_node()
|
||||
_name_node = add_node_element(f"{attribute_element_name}.name", str(_name_model))
|
||||
_name_edge = add_edge_element(f"{attribute_element_name}.name_link", attribute_element, _name_node)
|
||||
_name_edge = add_edge_element(f"{attribute_element_name}_name", attribute_element, _name_node)
|
||||
_optional_model = bottom.create_node()
|
||||
_optional_node = add_node_element(f"{attribute_element_name}.optional", str(_optional_model))
|
||||
_optional_edge = add_edge_element(f"{attribute_element_name}.optional_link", attribute_element, _optional_node)
|
||||
_optional_edge = add_edge_element(f"{attribute_element_name}_optional", attribute_element, _optional_node)
|
||||
return _name_model, _optional_model
|
||||
|
||||
##### SCD META-MODEL #####
|
||||
|
|
@ -171,7 +171,7 @@ def bootstrap_scd(state: State) -> UUID:
|
|||
# # Make Element abstract
|
||||
abs_model = bottom.create_node()
|
||||
abs_node = add_node_element(f"Element.abstract", str(abs_model))
|
||||
abs_edge = add_edge_element(f"Element.abstract_link", element_node, abs_node)
|
||||
abs_edge = add_edge_element(f"Element_abstract", element_node, abs_node)
|
||||
Boolean(abs_model, state).create(True)
|
||||
|
||||
# create phi(SCD,SCD) to type MCL with itself
|
||||
|
|
@ -220,27 +220,27 @@ def bootstrap_scd(state: State) -> UUID:
|
|||
add_mcl_morphism("Association_target_lower_cardinality", "AttributeLink")
|
||||
add_mcl_morphism("Association_target_upper_cardinality", "AttributeLink")
|
||||
# AttributeLink_name
|
||||
add_mcl_morphism("AttributeLink_name.name_link", "AttributeLink_name")
|
||||
add_mcl_morphism("AttributeLink_optional.name_link", "AttributeLink_name")
|
||||
add_mcl_morphism("Element_constraint.name_link", "AttributeLink_name")
|
||||
add_mcl_morphism("Class_abstract.name_link", "AttributeLink_name")
|
||||
add_mcl_morphism("Class_lower_cardinality.name_link", "AttributeLink_name")
|
||||
add_mcl_morphism("Class_upper_cardinality.name_link", "AttributeLink_name")
|
||||
add_mcl_morphism("Association_source_lower_cardinality.name_link", "AttributeLink_name")
|
||||
add_mcl_morphism("Association_source_upper_cardinality.name_link", "AttributeLink_name")
|
||||
add_mcl_morphism("Association_target_lower_cardinality.name_link", "AttributeLink_name")
|
||||
add_mcl_morphism("Association_target_upper_cardinality.name_link", "AttributeLink_name")
|
||||
add_mcl_morphism("AttributeLink_name_name", "AttributeLink_name")
|
||||
add_mcl_morphism("AttributeLink_optional_name", "AttributeLink_name")
|
||||
add_mcl_morphism("Element_constraint_name", "AttributeLink_name")
|
||||
add_mcl_morphism("Class_abstract_name", "AttributeLink_name")
|
||||
add_mcl_morphism("Class_lower_cardinality_name", "AttributeLink_name")
|
||||
add_mcl_morphism("Class_upper_cardinality_name", "AttributeLink_name")
|
||||
add_mcl_morphism("Association_source_lower_cardinality_name", "AttributeLink_name")
|
||||
add_mcl_morphism("Association_source_upper_cardinality_name", "AttributeLink_name")
|
||||
add_mcl_morphism("Association_target_lower_cardinality_name", "AttributeLink_name")
|
||||
add_mcl_morphism("Association_target_upper_cardinality_name", "AttributeLink_name")
|
||||
# AttributeLink_optional
|
||||
add_mcl_morphism("AttributeLink_name.optional_link", "AttributeLink_optional")
|
||||
add_mcl_morphism("AttributeLink_optional.optional_link", "AttributeLink_optional")
|
||||
add_mcl_morphism("Element_constraint.optional_link", "AttributeLink_optional")
|
||||
add_mcl_morphism("Class_abstract.optional_link", "AttributeLink_optional")
|
||||
add_mcl_morphism("Class_lower_cardinality.optional_link", "AttributeLink_optional")
|
||||
add_mcl_morphism("Class_upper_cardinality.optional_link", "AttributeLink_optional")
|
||||
add_mcl_morphism("Association_source_lower_cardinality.optional_link", "AttributeLink_optional")
|
||||
add_mcl_morphism("Association_source_upper_cardinality.optional_link", "AttributeLink_optional")
|
||||
add_mcl_morphism("Association_target_lower_cardinality.optional_link", "AttributeLink_optional")
|
||||
add_mcl_morphism("Association_target_upper_cardinality.optional_link", "AttributeLink_optional")
|
||||
add_mcl_morphism("AttributeLink_name_optional", "AttributeLink_optional")
|
||||
add_mcl_morphism("AttributeLink_optional_optional", "AttributeLink_optional")
|
||||
add_mcl_morphism("Element_constraint_optional", "AttributeLink_optional")
|
||||
add_mcl_morphism("Class_abstract_optional", "AttributeLink_optional")
|
||||
add_mcl_morphism("Class_lower_cardinality_optional", "AttributeLink_optional")
|
||||
add_mcl_morphism("Class_upper_cardinality_optional", "AttributeLink_optional")
|
||||
add_mcl_morphism("Association_source_lower_cardinality_optional", "AttributeLink_optional")
|
||||
add_mcl_morphism("Association_source_upper_cardinality_optional", "AttributeLink_optional")
|
||||
add_mcl_morphism("Association_target_lower_cardinality_optional", "AttributeLink_optional")
|
||||
add_mcl_morphism("Association_target_upper_cardinality_optional", "AttributeLink_optional")
|
||||
# String
|
||||
add_mcl_morphism("AttributeLink_name.name", "String")
|
||||
add_mcl_morphism("AttributeLink_optional.name", "String")
|
||||
|
|
@ -265,7 +265,7 @@ def bootstrap_scd(state: State) -> UUID:
|
|||
add_mcl_morphism("Association_target_upper_cardinality.optional", "Boolean")
|
||||
add_mcl_morphism("Element.abstract", "Boolean")
|
||||
# Class_abstract
|
||||
add_mcl_morphism("Element.abstract_link", "Class_abstract")
|
||||
add_mcl_morphism("Element_abstract", "Class_abstract")
|
||||
|
||||
return mcl_root
|
||||
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ def render_trace_match(state, name_mapping: dict, pattern_m: UUID, host_m: UUID,
|
|||
render_suffix = f"#line:{color};line.dotted;text:{color} : matchedWith"
|
||||
|
||||
for pattern_el_name, host_el_name in name_mapping.items():
|
||||
print(pattern_el_name, host_el_name)
|
||||
# print(pattern_el_name, host_el_name)
|
||||
try:
|
||||
pattern_el, = bottom.read_outgoing_elements(pattern_m, pattern_el_name)
|
||||
host_el, = bottom.read_outgoing_elements(host_m, host_el_name)
|
||||
121
concrete_syntax/textual_od/parser.py
Normal file
121
concrete_syntax/textual_od/parser.py
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
# Parser for Object Diagrams textual concrete syntax
|
||||
|
||||
from lark import Lark, logger, Transformer
|
||||
from lark.indenter import Indenter
|
||||
from services.od import OD
|
||||
from services.scd import SCD
|
||||
from uuid import UUID
|
||||
|
||||
grammar = r"""
|
||||
%import common.WS_INLINE
|
||||
%ignore WS_INLINE
|
||||
%ignore COMMENT
|
||||
|
||||
%declare _INDENT _DEDENT
|
||||
|
||||
?start: (_NL | object )*
|
||||
|
||||
IDENTIFIER: /[A-Za-z_][A-Za-z_0-9]*/
|
||||
COMMENT: /#.*/
|
||||
|
||||
# newline
|
||||
_NL: /(\r?\n[\t ]*)+/
|
||||
|
||||
literal: INT
|
||||
| STR
|
||||
| BOOL
|
||||
|
||||
INT: /[0-9]+/
|
||||
STR: /"[^"]*"/
|
||||
| /'[^']*'/
|
||||
BOOL: "True" | "False"
|
||||
|
||||
object: [IDENTIFIER] ":" IDENTIFIER [link] _NL [_INDENT slot+ _DEDENT]
|
||||
link: "(" IDENTIFIER "->" IDENTIFIER ")"
|
||||
slot: IDENTIFIER "=" literal _NL
|
||||
"""
|
||||
|
||||
|
||||
class TreeIndenter(Indenter):
|
||||
NL_type = '_NL'
|
||||
OPEN_PAREN_types = []
|
||||
CLOSE_PAREN_types = []
|
||||
INDENT_type = '_INDENT'
|
||||
DEDENT_type = '_DEDENT'
|
||||
tab_len = 4
|
||||
|
||||
parser = Lark(grammar, parser='lalr', postlex=TreeIndenter())
|
||||
|
||||
# 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)
|
||||
|
||||
m = state.create_node()
|
||||
od = OD(mm, m, state)
|
||||
|
||||
int_mm_id = UUID(state.read_value(state.read_dict(state.read_root(), "Integer")))
|
||||
|
||||
class T(Transformer):
|
||||
def __init__(self, visit_tokens):
|
||||
super().__init__(visit_tokens)
|
||||
self.obj_counter = 0
|
||||
|
||||
def IDENTIFIER(self, token):
|
||||
return str(token)
|
||||
|
||||
def INT(self, token):
|
||||
return int(token)
|
||||
|
||||
def BOOL(self, token):
|
||||
return token == "True"
|
||||
|
||||
def STR(self, token):
|
||||
return str(token[1:-1]) # strip the ""
|
||||
|
||||
def literal(self, el):
|
||||
return el[0]
|
||||
|
||||
def link(self, el):
|
||||
[src, tgt] = el
|
||||
return (src, tgt)
|
||||
|
||||
def slot(self, el):
|
||||
[attr_name, value] = el
|
||||
return (attr_name, value)
|
||||
|
||||
def object(self, el):
|
||||
[obj_name, type_name, link] = el[0:3]
|
||||
if obj_name == None:
|
||||
# object/link names are optional
|
||||
# generate a unique name if no name given
|
||||
obj_name = f"__o{self.obj_counter}"
|
||||
self.obj_counter += 1
|
||||
if link == None:
|
||||
obj_node = od.create_object(obj_name, type_name)
|
||||
else:
|
||||
src, tgt = link
|
||||
if tgt == "Integer":
|
||||
if state.read_dict(m, "Integer") == None:
|
||||
scd = SCD(m, state)
|
||||
scd.create_model_ref("Integer", int_mm_id)
|
||||
od.create_link(obj_name, type_name, src, tgt)
|
||||
# Create slots
|
||||
slots = el[3:]
|
||||
for attr_name, value in slots:
|
||||
value_name = f"{obj_name}.{attr_name}"
|
||||
# watch out: in Python, 'bool' is subtype of 'int'
|
||||
# so we must check for 'bool' first
|
||||
if isinstance(value, bool):
|
||||
tgt = od.create_boolean_value(value_name, value)
|
||||
elif isinstance(value, int):
|
||||
tgt = od.create_integer_value(value_name, value)
|
||||
elif isinstance(value, str):
|
||||
tgt = od.create_string_value(value_name, value)
|
||||
else:
|
||||
raise Exception("Unimplemented type "+value)
|
||||
od.create_slot(attr_name, obj_name, tgt)
|
||||
return obj_name
|
||||
|
||||
t = T(visit_tokens=True).transform(tree)
|
||||
|
||||
return m
|
||||
1
concrete_syntax/textual_od/readme.txt
Normal file
1
concrete_syntax/textual_od/readme.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
This directory contains the parser and renderer for the textual concrete syntax for Object Diagrams.
|
||||
43
concrete_syntax/textual_od/renderer.py
Normal file
43
concrete_syntax/textual_od/renderer.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Renderer for Object Diagrams textual concrete syntax
|
||||
|
||||
from services import od
|
||||
from services.bottom.V0 import Bottom
|
||||
import json
|
||||
|
||||
def display_value(val: any):
|
||||
if isinstance(val, str):
|
||||
return '"'+val+'"'
|
||||
elif isinstance(val, int) or isinstance(val, bool):
|
||||
return str(val)
|
||||
else:
|
||||
raise Exception("don't know how to display value" + str(val))
|
||||
|
||||
def render_od(state, m_id, mm_id, hide_names=True):
|
||||
bottom = Bottom(state)
|
||||
output = ""
|
||||
|
||||
m_od = od.OD(mm_id, m_id, state)
|
||||
|
||||
def display_name(name: str):
|
||||
# object names that start with "__" are hidden
|
||||
return name if (name[0:2] != "__" or not hide_names) else ""
|
||||
|
||||
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"
|
||||
return o
|
||||
|
||||
for class_name, objects in m_od.get_all_objects().items():
|
||||
for object_name, object_node in objects.items():
|
||||
output += f"{display_name(object_name)}:{class_name}\n"
|
||||
output += write_attributes(object_node)
|
||||
|
||||
for assoc_name, links in m_od.get_all_links().items():
|
||||
for link_name, (link_edge, src_name, tgt_name) in links.items():
|
||||
output += f"{display_name(link_name)}:{assoc_name} ({src_name} -> {tgt_name})\n"
|
||||
# links can also have slots:
|
||||
output += write_attributes(link_edge)
|
||||
|
||||
return output
|
||||
|
|
@ -12,7 +12,8 @@ from services.bottom.V0 import Bottom
|
|||
from services.primitives.integer_type import Integer
|
||||
from pattern_matching import mvs_adapter
|
||||
from pattern_matching.matcher import MatcherVF2
|
||||
from renderer import plantuml
|
||||
from concrete_syntax import plantuml
|
||||
from concrete_syntax.textual_od import parser, renderer
|
||||
|
||||
import sys
|
||||
|
||||
|
|
@ -30,121 +31,138 @@ def main():
|
|||
int_mm_id = UUID(state.read_value(state.read_dict(state.read_root(), "Integer")))
|
||||
string_mm_id = UUID(state.read_value(state.read_dict(state.read_root(), "String")))
|
||||
|
||||
# def print_tree(root, max_depth, depth=0):
|
||||
# print(" "*depth, "root=", root, "value=", state.read_value(root))
|
||||
# src,tgt = state.read_edge(root)
|
||||
# if src != None:
|
||||
# print(" "*depth, "src...")
|
||||
# print_tree(src, max_depth, depth+1)
|
||||
# if tgt != None:
|
||||
# print(" "*depth, "tgt...")
|
||||
# print_tree(tgt, max_depth, depth+1)
|
||||
# for edge in state.read_outgoing(root):
|
||||
# for edge_label in state.read_outgoing(edge):
|
||||
# [_,tgt] = state.read_edge(edge_label)
|
||||
# label = state.read_value(tgt)
|
||||
# print(" "*depth, " key:", label)
|
||||
# [_, tgt] = state.read_edge(edge)
|
||||
# value = state.read_value(tgt)
|
||||
# if value != None:
|
||||
# print(" "*depth, " ->", tgt, " (value:", value, ")")
|
||||
# else:
|
||||
# print(" "*depth, " ->", tgt)
|
||||
# if depth < max_depth:
|
||||
# if isinstance(value, str) and len(value) == 36:
|
||||
# i = None
|
||||
# try:
|
||||
# i = UUID(value)
|
||||
# except ValueError as e:
|
||||
# # print("invalid UUID:", value)
|
||||
# pass
|
||||
# if i != None:
|
||||
# print_tree(i, max_depth, depth+1)
|
||||
# print_tree(tgt, max_depth, depth+1)
|
||||
def create_dsl_mm_api():
|
||||
# Create DSL MM with SCD API
|
||||
dsl_mm_id = state.create_node()
|
||||
dsl_mm_scd = SCD(dsl_mm_id, state)
|
||||
dsl_mm_scd.create_class("Animal", abstract=True)
|
||||
dsl_mm_scd.create_class("Man", min_c=1, max_c=2)
|
||||
dsl_mm_scd.create_inheritance("Man", "Animal")
|
||||
dsl_mm_scd.create_model_ref("Integer", int_mm_id)
|
||||
dsl_mm_scd.create_attribute_link("Man", "Integer", "weight", optional=False)
|
||||
dsl_mm_scd.create_class("Bear")
|
||||
dsl_mm_scd.create_inheritance("Bear", "Animal")
|
||||
dsl_mm_scd.create_association("afraidOf", "Man", "Animal",
|
||||
# Every Man afraid of at least one Animal:
|
||||
src_min_c=0,
|
||||
src_max_c=None,
|
||||
tgt_min_c=1,
|
||||
tgt_max_c=None,
|
||||
)
|
||||
return dsl_mm_id
|
||||
|
||||
# Meta-model for our DSL
|
||||
dsl_mm_id = state.create_node()
|
||||
dsl_mm_scd = SCD(dsl_mm_id, state)
|
||||
dsl_mm_scd.create_class("Animal", abstract=True)
|
||||
dsl_mm_scd.create_class("Man", min_c=1, max_c=2)
|
||||
dsl_mm_scd.create_inheritance("Man", "Animal")
|
||||
dsl_mm_scd.create_model_ref("Integer", int_mm_id)
|
||||
dsl_mm_scd.create_attribute_link("Man", "Integer", "weight", optional=False)
|
||||
dsl_mm_scd.create_class("Bear")
|
||||
dsl_mm_scd.create_inheritance("Bear", "Animal")
|
||||
dsl_mm_scd.create_association("afraidOf", "Man", "Animal",
|
||||
# Every Man afraid of at least one Animal:
|
||||
src_min_c=0,
|
||||
src_max_c=None,
|
||||
tgt_min_c=1,
|
||||
tgt_max_c=None,
|
||||
)
|
||||
def create_dsl_mm_parser():
|
||||
# Create DSL MM with parser
|
||||
dsl_mm_cs = """
|
||||
# Integer:ModelRef
|
||||
Bear:Class
|
||||
Animal:Class
|
||||
abstract = True
|
||||
Man:Class
|
||||
lower_cardinality = 1
|
||||
upper_cardinality = 2
|
||||
Man_weight:AttributeLink (Man -> Integer)
|
||||
name = "weight"
|
||||
optional = False
|
||||
afraidOf:Association (Man -> Animal)
|
||||
source_lower_cardinality = 0
|
||||
target_lower_cardinality = 1
|
||||
Man_inh_Animal:Inheritance (Man -> Animal)
|
||||
Bear_inh_Animal:Inheritance (Bear -> Animal)
|
||||
"""
|
||||
dsl_mm_id = parser.parse_od(state, dsl_mm_cs, mm=scd_mm_id)
|
||||
return dsl_mm_id
|
||||
|
||||
def create_dsl_m_api():
|
||||
# Create DSL M with OD API
|
||||
dsl_m_id = state.create_node()
|
||||
dsl_m_od = OD(dsl_mm_id, dsl_m_id, state)
|
||||
dsl_m_od.create_object("george", "Man")
|
||||
dsl_m_od.create_slot("weight", "george",
|
||||
dsl_m_od.create_integer_value("george.weight", 80))
|
||||
dsl_m_od.create_object("bear1", "Bear")
|
||||
dsl_m_od.create_object("bear2", "Bear")
|
||||
dsl_m_od.create_link("georgeAfraidOfBear1", "afraidOf", "george", "bear1")
|
||||
dsl_m_od.create_link("georgeAfraidOfBear2", "afraidOf", "george", "bear2")
|
||||
return dsl_m_id
|
||||
|
||||
print(dsl_mm_scd.list_elements())
|
||||
def create_dsl_m_parser():
|
||||
# Create DSL M with parser
|
||||
dsl_m_cs = """
|
||||
george :Man
|
||||
weight = 80
|
||||
|
||||
bear1 :Bear
|
||||
bear2 :Bear
|
||||
|
||||
:afraidOf (george -> bear1)
|
||||
:afraidOf (george -> bear2)
|
||||
"""
|
||||
dsl_m_id = parser.parse_od(state, dsl_m_cs, mm=dsl_mm_id)
|
||||
return dsl_m_id
|
||||
|
||||
|
||||
# dsl_mm_id = create_dsl_mm_api()
|
||||
dsl_mm_id = create_dsl_mm_parser()
|
||||
|
||||
print("DSL MM:")
|
||||
print("--------------------------------------")
|
||||
print(renderer.render_od(state, dsl_mm_id, scd_mm_id, hide_names=False))
|
||||
print("--------------------------------------")
|
||||
|
||||
conf = Conformance(state, dsl_mm_id, scd_mm_id)
|
||||
print("conforms?", conf.check_nominal(log=True))
|
||||
print("Conformance DSL_MM -> SCD_MM?", conf.check_nominal(log=True))
|
||||
|
||||
# Model in our DSL
|
||||
dsl_m_id = state.create_node()
|
||||
dsl_m_od = OD(dsl_mm_id, dsl_m_id, state)
|
||||
# dsl_m_id = create_dsl_m_api()
|
||||
dsl_m_id = create_dsl_m_parser()
|
||||
print("DSL M:")
|
||||
print("--------------------------------------")
|
||||
print(renderer.render_od(state, dsl_m_id, dsl_mm_id, hide_names=False))
|
||||
print("--------------------------------------")
|
||||
|
||||
# dsl_m_od.create_object("animal", "Animal")
|
||||
dsl_m_od.create_object("george", "Man")
|
||||
dsl_m_od.create_slot("weight", "george",
|
||||
dsl_m_od.create_integer_value("george.weight", 80))
|
||||
|
||||
# "george_weight"
|
||||
|
||||
dsl_m_od.create_object("bear1", "Bear")
|
||||
dsl_m_od.create_object("bear2", "Bear")
|
||||
dsl_m_od.create_link("georgeAfraidOfBear1", "afraidOf", "george", "bear1")
|
||||
dsl_m_od.create_link("georgeAfraidOfBear2", "afraidOf", "george", "bear2")
|
||||
|
||||
|
||||
conf2 = Conformance(state, dsl_m_id, dsl_mm_id)
|
||||
print("DSL instance conforms?", conf2.check_nominal(log=True))
|
||||
print(conf2.type_mapping)
|
||||
conf = Conformance(state, dsl_m_id, dsl_mm_id)
|
||||
print("Conformance DSL_M -> DSL_MM?", conf.check_nominal(log=True))
|
||||
|
||||
# RAMify MM
|
||||
prefix = "RAM_" # all ramified types can be prefixed to distinguish them a bit more
|
||||
ramified_mm_id = ramify(state, dsl_mm_id, prefix)
|
||||
ramified_int_mm_id = ramify(state, int_mm_id, prefix)
|
||||
|
||||
# LHS of our rule
|
||||
lhs_id = state.create_node()
|
||||
lhs_od = OD(ramified_mm_id, lhs_id, state)
|
||||
|
||||
lhs_od.create_object("man", prefix+"Man")
|
||||
lhs_od.create_slot(prefix+"weight", "man", lhs_od.create_string_value(f"man.{prefix}weight", 'v < 99'))
|
||||
lhs_od.create_object("scaryAnimal", prefix+"Animal")
|
||||
lhs_od.create_link("manAfraidOfAnimal", prefix+"afraidOf", "man", "scaryAnimal")
|
||||
|
||||
conf3 = Conformance(state, lhs_id, ramified_mm_id)
|
||||
print("LHS conforms?", conf3.check_nominal(log=True))
|
||||
conf = Conformance(state, lhs_id, ramified_mm_id)
|
||||
print("Conformance LHS_M -> RAM_DSL_MM?", conf.check_nominal(log=True))
|
||||
|
||||
# RHS of our rule
|
||||
rhs_id = state.create_node()
|
||||
rhs_od = OD(ramified_mm_id, rhs_id, state)
|
||||
|
||||
rhs_od.create_object("man", prefix+"Man")
|
||||
rhs_od.create_slot(prefix+"weight", "man", rhs_od.create_string_value(f"man.{prefix}weight", 'v + 5'))
|
||||
|
||||
rhs_od.create_object("bill", prefix+"Man")
|
||||
rhs_od.create_slot(prefix+"weight", "bill", rhs_od.create_string_value(f"bill.{prefix}weight", '100'))
|
||||
|
||||
rhs_od.create_link("billAfraidOfMan", prefix+"afraidOf", "bill", "man")
|
||||
|
||||
conf4 = Conformance(state, rhs_id, ramified_mm_id)
|
||||
print("RHS conforms?", conf4.check_nominal(log=True))
|
||||
conf = Conformance(state, rhs_id, ramified_mm_id)
|
||||
print("Conformance RHS_M -> RAM_DSL_MM?", conf.check_nominal(log=True))
|
||||
|
||||
def render_ramification():
|
||||
uml = (""
|
||||
# Render original and RAMified meta-models
|
||||
+ plantuml.render_package("Meta-Model", plantuml.render_class_diagram(state, dsl_mm_id))
|
||||
+ plantuml.render_package("RAMified Meta-Model", plantuml.render_class_diagram(state, ramified_mm_id))
|
||||
+ plantuml.render_package("DSL Meta-Model", plantuml.render_class_diagram(state, dsl_mm_id))
|
||||
+ plantuml.render_package("Int Meta-Model", plantuml.render_class_diagram(state, int_mm_id))
|
||||
+ plantuml.render_package("RAMified DSL Meta-Model", plantuml.render_class_diagram(state, ramified_mm_id))
|
||||
+ plantuml.render_package("RAMified Int Meta-Model", plantuml.render_class_diagram(state, ramified_int_mm_id))
|
||||
|
||||
# Render RAMification traceability links
|
||||
+ plantuml.render_trace_ramifies(state, dsl_mm_id, ramified_mm_id)
|
||||
+ plantuml.render_trace_ramifies(state, int_mm_id, ramified_int_mm_id)
|
||||
)
|
||||
|
||||
# Render pattern
|
||||
|
|
@ -182,8 +200,11 @@ def main():
|
|||
for name_mapping in generator:
|
||||
rewriter.rewrite(state, lhs_id, rhs_id, ramified_mm_id, name_mapping, dsl_m_id, dsl_mm_id)
|
||||
|
||||
conf = Conformance(state, dsl_m_id, dsl_mm_id)
|
||||
print("Conformance DSL_M (after rewrite) -> DSL_MM?", conf.check_nominal(log=True))
|
||||
|
||||
# Render match
|
||||
uml_match = plantuml.render_trace_match(state, name_mapping, rhs_id, dsl_m_id)
|
||||
uml_match = plantuml.render_trace_match(state, name_mapping, rhs_id, dsl_m_id, 'orange')
|
||||
|
||||
# Stop matching after rewrite
|
||||
break
|
||||
|
|
@ -197,14 +218,13 @@ def main():
|
|||
|
||||
return uml
|
||||
|
||||
conf5 = Conformance(state, dsl_m_id, dsl_mm_id)
|
||||
print("Updated model conforms?", conf5.check_nominal(log=True))
|
||||
# plantuml_str = render_all_matches()
|
||||
# plantuml_str = render_rewrite()
|
||||
|
||||
print()
|
||||
print("==============================================")
|
||||
# print()
|
||||
# print("==============================================")
|
||||
|
||||
print(render_all_matches())
|
||||
# print(render_rewrite())
|
||||
# print(plantuml_str)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -556,7 +556,7 @@ class Conformance:
|
|||
attr_type_element, = self.bottom.read_outgoing_elements(self.type_model, attr_type)
|
||||
self.bottom.create_edge(attr_element, attr_type_element, "Morphism")
|
||||
# attribute link
|
||||
attr_link_element, = self.bottom.read_outgoing_elements(self.model, f"{m_name}.{attr_name}_link")
|
||||
attr_link_element, = self.bottom.read_outgoing_elements(self.model, f"{m_name}_{attr_name}")
|
||||
attr_link_type_element, = self.bottom.read_outgoing_elements(self.type_model, f"{tm_name}_{attr_name}")
|
||||
self.bottom.create_edge(attr_link_element, attr_link_type_element, "Morphism")
|
||||
except ValueError:
|
||||
|
|
|
|||
|
|
@ -133,10 +133,10 @@ class MatcherVF2:
|
|||
self.guest = guest
|
||||
self.compare_fn = compare_fn
|
||||
|
||||
with Timer("find_connected_components - guest"):
|
||||
self.guest_vtx_to_component, self.guest_component_to_vtxs = find_connected_components(guest)
|
||||
# with Timer("find_connected_components - guest"):
|
||||
self.guest_vtx_to_component, self.guest_component_to_vtxs = find_connected_components(guest)
|
||||
|
||||
print("number of guest connected components:", len(self.guest_component_to_vtxs))
|
||||
# print("number of guest connected components:", len(self.guest_component_to_vtxs))
|
||||
|
||||
def match(self):
|
||||
yield from self._match(
|
||||
|
|
|
|||
|
|
@ -28,12 +28,12 @@ class _is_modelref:
|
|||
return "REF"
|
||||
IS_MODELREF = _is_modelref()
|
||||
|
||||
class IS_TYPE:
|
||||
def __init__(self, type):
|
||||
# mvs-node of the type
|
||||
self.type = type
|
||||
def __repr__(self):
|
||||
return f"TYPE({str(self.type)[-4:]})"
|
||||
# class IS_TYPE:
|
||||
# def __init__(self, type):
|
||||
# # mvs-node of the type
|
||||
# self.type = type
|
||||
# def __repr__(self):
|
||||
# return f"TYPE({str(self.type)[-4:]})"
|
||||
|
||||
class NamedNode(Vertex):
|
||||
def __init__(self, value, name):
|
||||
|
|
@ -74,7 +74,7 @@ UUID_REGEX = re.compile(r"[0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-
|
|||
# Converts an object diagram in MVS state to the pattern matcher graph type
|
||||
# ModelRefs are flattened
|
||||
def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
|
||||
with Timer("model_to_graph"):
|
||||
# with Timer("model_to_graph"):
|
||||
od = OD(model, metamodel, state)
|
||||
scd = SCD(model, state)
|
||||
scd_mm = SCD(metamodel, state)
|
||||
|
|
@ -107,7 +107,7 @@ def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
|
|||
if isinstance(value, str):
|
||||
if UUID_REGEX.match(value) != None:
|
||||
# side-effect
|
||||
modelrefs[el] = (UUID(value),name)
|
||||
modelrefs[el] = (UUID(value), name)
|
||||
return MVSNode(IS_MODELREF, el, name)
|
||||
return MVSNode(value, el, name)
|
||||
|
||||
|
|
@ -131,29 +131,37 @@ def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
|
|||
label="tgt"))
|
||||
|
||||
|
||||
for node, (ref, name) in modelrefs.items():
|
||||
for node, (ref_m, name) in modelrefs.items():
|
||||
vtx = uuid_to_vtx[node]
|
||||
|
||||
# Get MM of ref'ed model
|
||||
type_node, = bottom.read_outgoing_elements(node, "Morphism")
|
||||
print("modelref type node:", type_node)
|
||||
ref_mm, = bottom.read_outgoing_elements(node, "Morphism")
|
||||
# print("modelref type node:", type_node)
|
||||
|
||||
# Recursively convert ref'ed model to graph
|
||||
ref_model = model_to_graph(state, ref, type_node, prefix=name+'/')
|
||||
# ref_graph = model_to_graph(state, ref_m, ref_mm, prefix=name+'/')
|
||||
|
||||
# Flatten and create link to ref'ed model
|
||||
graph.vtxs += ref_model.vtxs
|
||||
graph.edges += ref_model.edges
|
||||
graph.edges.append(Edge(
|
||||
src=uuid_to_vtx[node],
|
||||
tgt=ref_model.vtxs[0], # which node to link to?? dirty
|
||||
label="modelref"))
|
||||
vtx.modelref = (ref_m, ref_mm)
|
||||
|
||||
# We no longer flatten:
|
||||
|
||||
# # Flatten and create link to ref'ed model
|
||||
# graph.vtxs += ref_model.vtxs
|
||||
# graph.edges += ref_model.edges
|
||||
# graph.edges.append(Edge(
|
||||
# src=uuid_to_vtx[node],
|
||||
# tgt=ref_model.vtxs[0], # which node to link to?? dirty
|
||||
# label="modelref"))
|
||||
|
||||
def add_types(node):
|
||||
vtx = uuid_to_vtx[node]
|
||||
type_node, = bottom.read_outgoing_elements(node, "Morphism")
|
||||
|
||||
# Put the type straigt into the Vertex-object
|
||||
uuid_to_vtx[node].typ = type_node
|
||||
# Put the type straight into the Vertex-object
|
||||
# The benefit is that our Vertex-matching callback can then be coded cleverly, look at the types first, resulting in better performance
|
||||
vtx.typ = type_node
|
||||
|
||||
# We used to put the types in separate nodes, but we no longer do this:
|
||||
# The old approach (creating special vertices containing the types), commented out:
|
||||
|
||||
# print('node', node, 'has type', type_node)
|
||||
# We create a Vertex storing the type
|
||||
|
|
@ -190,6 +198,7 @@ def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
|
|||
|
||||
|
||||
def match_od(state, host_m, host_mm, pattern_m, pattern_mm):
|
||||
|
||||
# Function object for pattern matching. Decides whether to match host and guest vertices, where guest is a RAMified instance (e.g., the attributes are all strings with Python expressions), and the host is an instance (=object diagram) of the original model (=class diagram)
|
||||
class RAMCompare:
|
||||
def __init__(self, bottom, host_od):
|
||||
|
|
@ -239,6 +248,20 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm):
|
|||
return False
|
||||
return self.match_types(g_vtx.typ, h_vtx.typ)
|
||||
|
||||
if hasattr(g_vtx, 'modelref'):
|
||||
if not hasattr(h_vtx, 'modelref'):
|
||||
return False
|
||||
g_ref_m, g_ref_mm = g_vtx.modelref
|
||||
h_ref_m, h_ref_mm = h_vtx.modelref
|
||||
nested_matches = [m for m in match_od(state, h_ref_m, h_ref_mm, g_ref_m, g_ref_mm)]
|
||||
# print('nested_matches:', nested_matches)
|
||||
if len(nested_matches) == 0:
|
||||
return False
|
||||
elif len(nested_matches) == 1:
|
||||
return True
|
||||
else:
|
||||
raise Exception("We have a problem: there is more than 1 match in the nested models.")
|
||||
|
||||
# Then, match by value
|
||||
|
||||
if g_vtx.value == None:
|
||||
|
|
@ -257,20 +280,20 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm):
|
|||
if h_vtx.value == IS_MODELREF:
|
||||
return False
|
||||
|
||||
# print(g_vtx.value, h_vtx.value)
|
||||
def get_slot(h_vtx, slot_name: str):
|
||||
slot_node = self.host_od.get_slot(h_vtx.node_id, slot_name)
|
||||
return slot_node
|
||||
# # print(g_vtx.value, h_vtx.value)
|
||||
# def get_slot(h_vtx, slot_name: str):
|
||||
# slot_node = self.host_od.get_slot(h_vtx.node_id, slot_name)
|
||||
# return slot_node
|
||||
|
||||
def read_int(slot: UUID):
|
||||
i = Integer(slot, self.bottom.state)
|
||||
return i.read()
|
||||
# def read_int(slot: UUID):
|
||||
# i = Integer(slot, self.bottom.state)
|
||||
# return i.read()
|
||||
|
||||
try:
|
||||
return eval(g_vtx.value, {}, {
|
||||
'v': h_vtx.value,
|
||||
'get_slot': functools.partial(get_slot, h_vtx),
|
||||
'read_int': read_int,
|
||||
# 'get_slot': functools.partial(get_slot, h_vtx),
|
||||
# 'read_int': read_int,
|
||||
})
|
||||
except Exception as e:
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -52,11 +52,6 @@ class OD:
|
|||
|
||||
return object_node
|
||||
|
||||
# def read_slot_boolean(self, obj_node: str, attr_name: str):
|
||||
# slot = self.get_slot(obj_node, attr_name)
|
||||
# if slot != None:
|
||||
# return Boolean(slot, self.bottom.state).read()
|
||||
|
||||
def get_class_of_object(self, object_name: str):
|
||||
object_node, = self.bottom.read_outgoing_elements(self.model, object_name) # get the object
|
||||
return self._get_class_of_object(object_node)
|
||||
|
|
@ -73,9 +68,10 @@ class OD:
|
|||
class_name = self.get_class_of_object(object_name)
|
||||
attr_link_name = get_attr_link_name(class_name, attr_name)
|
||||
# An attribute-link is indistinguishable from an ordinary link:
|
||||
return self.create_link(
|
||||
slot_id = self.create_link(
|
||||
get_attr_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...
|
||||
|
|
@ -86,8 +82,23 @@ class OD:
|
|||
if type_edge in self.bottom.read_outgoing_elements(outgoing_edge, "Morphism"):
|
||||
slot_ref = self.bottom.read_edge_target(outgoing_edge)
|
||||
return slot_ref
|
||||
# slot_node = UUID(self.bottom.read_value(slot_ref))
|
||||
# return slot_node
|
||||
|
||||
def get_slots(self, object_node):
|
||||
attrlink_node = get_scd_mm_attributelink_node(self.bottom)
|
||||
slots = []
|
||||
outgoing_links = self.bottom.read_outgoing_edges(object_node)
|
||||
for l in outgoing_links:
|
||||
for type_of_link in self.bottom.read_outgoing_elements(l, "Morphism"):
|
||||
for type_of_type_of_link in self.bottom.read_outgoing_elements(type_of_link, "Morphism"):
|
||||
if type_of_type_of_link == attrlink_node:
|
||||
# hooray, we have a slot
|
||||
attr_name = get_attr_name(self.bottom, type_of_link)
|
||||
slots.append((attr_name, l))
|
||||
return slots
|
||||
|
||||
def read_slot(self, slot_id):
|
||||
tgt = self.bottom.read_edge_target(slot_id)
|
||||
return read_primitive_value(self.bottom, tgt, self.type_model)
|
||||
|
||||
def create_integer_value(self, name: str, value: int):
|
||||
from services.primitives.integer_type import Integer
|
||||
|
|
@ -99,6 +110,16 @@ class OD:
|
|||
self.create_model_ref(name, "Integer", int_node)
|
||||
return name
|
||||
|
||||
def create_boolean_value(self, name: str, value: bool):
|
||||
from services.primitives.boolean_type import Boolean
|
||||
bool_node = self.bottom.create_node()
|
||||
bool_service = Boolean(bool_node, self.bottom.state)
|
||||
bool_service.create(value)
|
||||
# name = 'int'+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, "Boolean", bool_node)
|
||||
return name
|
||||
|
||||
def create_string_value(self, name: str, value: str):
|
||||
from services.primitives.string_type import String
|
||||
string_node = self.bottom.create_node()
|
||||
|
|
@ -132,10 +153,10 @@ class OD:
|
|||
i += 1
|
||||
|
||||
type_edge, = self.bottom.read_outgoing_elements(self.type_model, assoc_name)
|
||||
link_id = self._create_link(link_name, type_edge, src_obj_node, tgt_obj_node)
|
||||
return link_id
|
||||
|
||||
return self._create_link(link_name, type_edge, src_obj_node, tgt_obj_node)
|
||||
|
||||
def _create_link(self, link_name: str, type_edge: str, src_obj_node: UUID, tgt_obj_node: UUID):
|
||||
def _create_link(self, link_name: str, type_edge: UUID, src_obj_node: UUID, tgt_obj_node: UUID):
|
||||
# the link itself is unlabeled:
|
||||
link_edge = self.bottom.create_edge(src_obj_node, tgt_obj_node)
|
||||
# it is only in the context of the model, that the link has a name:
|
||||
|
|
@ -146,6 +167,33 @@ class OD:
|
|||
def get_objects(self, class_node):
|
||||
return get_typed_by(self.bottom, self.model, class_node)
|
||||
|
||||
def get_all_objects(self):
|
||||
scd_mm = get_scd_mm(self.bottom)
|
||||
class_node = get_scd_mm_class_node(self.bottom)
|
||||
all_classes = OD(scd_mm, self.type_model, self.bottom.state).get_objects(class_node)
|
||||
result = {}
|
||||
for class_name, class_node in all_classes.items():
|
||||
objects = self.get_objects(class_node)
|
||||
result[class_name] = objects
|
||||
return result
|
||||
|
||||
def get_all_links(self):
|
||||
scd_mm = get_scd_mm(self.bottom)
|
||||
assoc_node = get_scd_mm_assoc_node(self.bottom)
|
||||
all_classes = OD(scd_mm, self.type_model, self.bottom.state).get_objects(assoc_node)
|
||||
result = {}
|
||||
for assoc_name, assoc_node in all_classes.items():
|
||||
links = self.get_objects(assoc_node)
|
||||
m = {}
|
||||
for link_name, link_edge in links.items():
|
||||
src_node = self.bottom.read_edge_source(link_edge)
|
||||
tgt_node = self.bottom.read_edge_target(link_edge)
|
||||
src_name = get_object_name(self.bottom, self.model, src_node)
|
||||
tgt_name = get_object_name(self.bottom, self.model, tgt_node)
|
||||
m[link_name] = (link_edge, src_name, tgt_name)
|
||||
result[assoc_name] = m
|
||||
return result
|
||||
|
||||
def get_object_name(self, obj: UUID):
|
||||
for key in self.bottom.read_keys(self.model):
|
||||
for el in self.bottom.read_outgoing_elements(self.model, key):
|
||||
|
|
@ -224,6 +272,10 @@ def get_object_name(bottom: Bottom, model: UUID, object_node: UUID):
|
|||
if el == object_node:
|
||||
return key
|
||||
|
||||
def get_type2(bottom: Bottom, mm: UUID, object_node: UUID):
|
||||
type_node, = bottom.read_outgoing_elements(object_node, "Morphism")
|
||||
return type_node, get_object_name(bottom, mm, type_node)
|
||||
|
||||
def find_outgoing_typed_by(bottom, src: UUID, type_node: UUID):
|
||||
edges = []
|
||||
for outgoing_edge in bottom.read_outgoing_edges(src):
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class SCD:
|
|||
_c_node = self.bottom.create_node(str(_c_model)) # store UUID of primitive value model
|
||||
self.bottom.create_edge(self.model, _c_node, f"{name}.{bound}_cardinality") # link to model root
|
||||
_c_link = self.bottom.create_edge(class_node, _c_node) # link class to attribute
|
||||
self.bottom.create_edge(self.model, _c_link, f"{name}.{bound}_cardinality_link") # link attr link to model root
|
||||
self.bottom.create_edge(self.model, _c_link, f"{name}_{bound}_cardinality") # link attr link to model root
|
||||
# retrieve types from metamodel
|
||||
_scd_node, = self.bottom.read_outgoing_elements(self.scd_model, "Integer")
|
||||
_scd_link, = self.bottom.read_outgoing_elements(self.scd_model, f"Class_{bound}_cardinality")
|
||||
|
|
@ -63,7 +63,7 @@ class SCD:
|
|||
abstract_node = self.bottom.create_node(str(abstract_model))
|
||||
self.bottom.create_edge(self.model, abstract_node, f"{name}.abstract")
|
||||
abstract_link = self.bottom.create_edge(class_node, abstract_node)
|
||||
self.bottom.create_edge(self.model, abstract_link, f"{name}.abstract_link")
|
||||
self.bottom.create_edge(self.model, abstract_link, f"{name}_abstract")
|
||||
scd_node, = self.bottom.read_outgoing_elements(self.scd_model, "Boolean")
|
||||
scd_link, = self.bottom.read_outgoing_elements(self.scd_model, "Class_abstract")
|
||||
self.bottom.create_edge(abstract_node, scd_node, "Morphism")
|
||||
|
|
@ -110,7 +110,7 @@ class SCD:
|
|||
_c_node = self.bottom.create_node(str(_c_model))
|
||||
self.bottom.create_edge(self.model, _c_node, f"{name}.{bound}_cardinality")
|
||||
_c_link = self.bottom.create_edge(assoc_edge, _c_node)
|
||||
self.bottom.create_edge(self.model, _c_link, f"{name}.{bound}_cardinality_link")
|
||||
self.bottom.create_edge(self.model, _c_link, f"{name}_{bound}_cardinality")
|
||||
_scd_node, = self.bottom.read_outgoing_elements(self.scd_model, "Integer")
|
||||
_scd_link, = self.bottom.read_outgoing_elements(self.scd_model, f"Association_{bound}_cardinality")
|
||||
self.bottom.create_edge(_c_node, _scd_node, "Morphism")
|
||||
|
|
@ -193,7 +193,7 @@ class SCD:
|
|||
name_node = self.bottom.create_node(str(name_model))
|
||||
self.bottom.create_edge(self.model, name_node, f"{source}_{name}.name")
|
||||
name_link = self.bottom.create_edge(assoc_edge, name_node)
|
||||
self.bottom.create_edge(self.model, name_link, f"{source}_{name}.name_link")
|
||||
self.bottom.create_edge(self.model, name_link, f"{source}_{name}_name")
|
||||
scd_node, = self.bottom.read_outgoing_elements(self.scd_model, "String")
|
||||
scd_link, = self.bottom.read_outgoing_elements(self.scd_model, "AttributeLink_name")
|
||||
self.bottom.create_edge(name_node, scd_node, "Morphism")
|
||||
|
|
@ -205,7 +205,7 @@ class SCD:
|
|||
optional_node = self.bottom.create_node(str(optional_model))
|
||||
self.bottom.create_edge(self.model, optional_node, f"{source}_{name}.optional")
|
||||
optional_link = self.bottom.create_edge(assoc_edge, optional_node)
|
||||
self.bottom.create_edge(self.model, optional_link, f"{source}_{name}.optional_link")
|
||||
self.bottom.create_edge(self.model, optional_link, f"{source}_{name}_optional")
|
||||
scd_node, = self.bottom.read_outgoing_elements(self.scd_model, "Boolean")
|
||||
scd_link, = self.bottom.read_outgoing_elements(self.scd_model, "AttributeLink_optional")
|
||||
self.bottom.create_edge(optional_node, scd_node, "Morphism")
|
||||
|
|
@ -278,7 +278,7 @@ class SCD:
|
|||
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_link")
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
|
|||
# - min-card: 0
|
||||
# - max-card: same as original
|
||||
upper_card = od.find_cardinality(bottom, class_node, od.get_scd_mm_class_uppercard_node(bottom))
|
||||
print('creating class', class_name, "with card 0 ..", upper_card)
|
||||
# print('creating class', class_name, "with card 0 ..", upper_card)
|
||||
ramified_class = ramified_scd.create_class(prefix+class_name, abstract=None, max_c=upper_card)
|
||||
# traceability link
|
||||
bottom.create_edge(ramified_class, class_node, RAMIFIES_LABEL)
|
||||
|
|
@ -41,7 +41,7 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
|
|||
# ramified_scd._create_attribute_link(prefix+class_name, string_modelref, "constraint", optional=True)
|
||||
|
||||
for (attr_name, attr_edge) in od.get_attributes(bottom, class_node):
|
||||
print(' creating attribute', attr_name, "with type String")
|
||||
# print(' creating attribute', attr_name, "with type String")
|
||||
# Every attribute becomes 'string' type
|
||||
# The string will be a Python expression
|
||||
ramified_attr_link = ramified_scd._create_attribute_link(prefix+class_name, string_modelref, prefix+attr_name, optional=True)
|
||||
|
|
@ -58,7 +58,7 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
|
|||
_, src_upper_card, _, tgt_upper_card = m_scd.get_assoc_cardinalities(assoc_node)
|
||||
src = m_scd.get_class_name(bottom.read_edge_source(assoc_node))
|
||||
tgt = m_scd.get_class_name(bottom.read_edge_target(assoc_node))
|
||||
print('creating assoc', src, "->", tgt, ", name =", assoc_name, ", src card = 0 ..", src_upper_card, "and tgt card = 0 ..", tgt_upper_card)
|
||||
# print('creating assoc', src, "->", tgt, ", name =", assoc_name, ", src card = 0 ..", src_upper_card, "and tgt card = 0 ..", tgt_upper_card)
|
||||
ramified_assoc = ramified_scd.create_association(
|
||||
prefix+assoc_name, prefix+src, prefix+tgt,
|
||||
src_max_c=src_upper_card,
|
||||
|
|
@ -70,15 +70,13 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
|
|||
# Re-create inheritance links like in our original model:
|
||||
src = m_scd.get_class_name(bottom.read_edge_source(inh_node))
|
||||
tgt = m_scd.get_class_name(bottom.read_edge_target(inh_node))
|
||||
print('creating inheritance link', prefix+src, '->', prefix+tgt)
|
||||
# print('creating inheritance link', prefix+src, '->', prefix+tgt)
|
||||
ramified_inh_link = ramified_scd.create_inheritance(prefix+src, prefix+tgt)
|
||||
|
||||
# Double-check: The RAMified meta-model should also conform to 'SCD':
|
||||
conf = Conformance(state, ramified, scd_metamodel)
|
||||
if not conf.check_nominal(log=True):
|
||||
raise Exception("Unexpected error: RAMified MM does not conform to SCD MM")
|
||||
else:
|
||||
print("RAMification successful.")
|
||||
|
||||
return ramified
|
||||
|
||||
|
|
|
|||
|
|
@ -13,19 +13,18 @@ from services.primitives.integer_type import Integer
|
|||
def process_rule(state, lhs: UUID, rhs: UUID):
|
||||
bottom = Bottom(state)
|
||||
|
||||
# : bottom.read_outgoing_elements(rhs, name)[0]
|
||||
to_delete = { name for name in bottom.read_keys(lhs) if name not in bottom.read_keys(rhs) }
|
||||
to_create = { name for name in bottom.read_keys(rhs) if name not in bottom.read_keys(lhs) }
|
||||
common = { name for name in bottom.read_keys(lhs) if name in bottom.read_keys(rhs) }
|
||||
|
||||
print("to_delete:", to_delete)
|
||||
print("to_create:", to_create)
|
||||
# print("to_delete:", to_delete)
|
||||
# print("to_create:", to_create)
|
||||
|
||||
return to_delete, to_create, common
|
||||
|
||||
# Rewrite is performed in-place
|
||||
# Also updates the mapping in-place, to become RHS -> host
|
||||
def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to_transform: UUID, mm: UUID):
|
||||
# Rewrite is performed in-place (modifying `host_m`)
|
||||
# Also updates the `mapping` in-place, to become RHS -> host
|
||||
def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dict, host_m: UUID, mm: UUID):
|
||||
bottom = Bottom(state)
|
||||
|
||||
scd_metamodel_id = state.read_dict(state.read_root(), "SCD")
|
||||
|
|
@ -36,125 +35,124 @@ def rewrite(state, lhs: UUID, rhs: UUID, rhs_mm: UUID, match_mapping: dict, m_to
|
|||
assoc_type = od.get_scd_mm_assoc_node(bottom)
|
||||
modelref_type = od.get_scd_mm_modelref_node(bottom)
|
||||
|
||||
m_od = od.OD(mm, m_to_transform, bottom.state)
|
||||
rhs_od = od.OD(rhs_mm, rhs, bottom.state)
|
||||
m_od = od.OD(mm, host_m, bottom.state)
|
||||
rhs_od = od.OD(pattern_mm, rhs_m, bottom.state)
|
||||
|
||||
print('rhs type:', od.get_type(bottom, rhs))
|
||||
|
||||
to_delete, to_create, common = process_rule(state, lhs, rhs)
|
||||
to_delete, to_create, common = process_rule(state, lhs_m, rhs_m)
|
||||
|
||||
# Perform deletions
|
||||
for pattern_name_to_delete in to_delete:
|
||||
# For every name in `to_delete`, look up the name of the matched element in the host graph
|
||||
model_element_name_to_delete = match_mapping[pattern_name_to_delete]
|
||||
print('deleting', model_element_name_to_delete)
|
||||
model_el_name_to_delete = name_mapping[pattern_name_to_delete]
|
||||
# print('deleting', model_el_name_to_delete)
|
||||
# Look up the matched element in the host graph
|
||||
element_to_delete, = bottom.read_outgoing_elements(m_to_transform, model_element_name_to_delete)
|
||||
el_to_delete, = bottom.read_outgoing_elements(host_m, model_el_name_to_delete)
|
||||
# Delete
|
||||
bottom.delete_element(element_to_delete)
|
||||
bottom.delete_element(el_to_delete)
|
||||
# Remove from mapping
|
||||
del match_mapping[pattern_name_to_delete]
|
||||
del name_mapping[pattern_name_to_delete]
|
||||
|
||||
# extended_mapping = dict(match_mapping) # will be extended with created elements
|
||||
# extended_mapping = dict(name_mapping) # will be extended with created elements
|
||||
edges_to_create = [] # postpone creation of edges after creation of nodes
|
||||
|
||||
# Perform creations
|
||||
for pattern_name_to_create in to_create:
|
||||
print('creating', pattern_name_to_create)
|
||||
# print('creating', pattern_name_to_create)
|
||||
# We have to come up with a name for the element-to-create in the host graph
|
||||
i = 0
|
||||
while True:
|
||||
model_element_name_to_create = pattern_name_to_create + str(i) # use the label of the element in the RHS as a basis
|
||||
if len(bottom.read_outgoing_elements(m_to_transform, model_element_name_to_create)) == 0:
|
||||
model_el_name_to_create = pattern_name_to_create + str(i) # use the label of the element in the RHS as a basis
|
||||
if len(bottom.read_outgoing_elements(host_m, model_el_name_to_create)) == 0:
|
||||
break # found an available name
|
||||
|
||||
# Determine the type of the thing to create
|
||||
rhs_element_to_create, = bottom.read_outgoing_elements(rhs, pattern_name_to_create)
|
||||
rhs_type = od.get_type(bottom, rhs_element_to_create)
|
||||
rhs_el_to_create, = bottom.read_outgoing_elements(rhs_m, pattern_name_to_create)
|
||||
rhs_type = od.get_type(bottom, rhs_el_to_create)
|
||||
original_type = ramify.get_original_type(bottom, rhs_type)
|
||||
if original_type != None:
|
||||
# Now get the type of the type
|
||||
if od.is_typed_by(bottom, original_type, class_type):
|
||||
# It's type is typed by Class -> it's an object
|
||||
print(' -> creating object')
|
||||
o = m_od._create_object(model_element_name_to_create, original_type)
|
||||
match_mapping[pattern_name_to_create] = model_element_name_to_create
|
||||
# print(' -> creating object')
|
||||
o = m_od._create_object(model_el_name_to_create, original_type)
|
||||
name_mapping[pattern_name_to_create] = model_el_name_to_create
|
||||
elif od.is_typed_by(bottom, original_type, attr_link_type):
|
||||
print(' -> postpone (is attribute link)')
|
||||
edges_to_create.append((pattern_name_to_create, rhs_element_to_create, original_type, 'attribute link', rhs_type, model_element_name_to_create))
|
||||
# print(' -> postpone (is attribute link)')
|
||||
edges_to_create.append((pattern_name_to_create, rhs_el_to_create, original_type, 'attribute link', model_el_name_to_create))
|
||||
elif od.is_typed_by(bottom, original_type, assoc_type):
|
||||
print(' -> postpone (is link)')
|
||||
edges_to_create.append((pattern_name_to_create, rhs_element_to_create, original_type, 'link', rhs_type, model_element_name_to_create))
|
||||
# print(' -> postpone (is link)')
|
||||
edges_to_create.append((pattern_name_to_create, rhs_el_to_create, original_type, 'link', model_el_name_to_create))
|
||||
else:
|
||||
original_type_name = od.get_object_name(bottom, mm, original_type)
|
||||
print(" -> warning: don't know about", original_type_name)
|
||||
else:
|
||||
print(" -> no original (un-RAMified) type")
|
||||
# print(" -> no original (un-RAMified) type")
|
||||
# assume the type of the object is already the original type
|
||||
# this is because primitive types (e.g., Integer) are not RAMified
|
||||
type_name = od.get_object_name(bottom, rhs_mm, rhs_type)
|
||||
type_name = od.get_object_name(bottom, pattern_mm, rhs_type)
|
||||
if type_name == "String":
|
||||
s_model = UUID(bottom.read_value(rhs_element_to_create))
|
||||
python_expr = String(s_model, bottom.state).read()
|
||||
# Assume the string is a Python expression to evaluate
|
||||
python_expr = String(UUID(bottom.read_value(rhs_el_to_create)), bottom.state).read()
|
||||
result = eval(python_expr, {}, {})
|
||||
print('result:', result)
|
||||
# Write the result into the host model.
|
||||
# This will be the *value* of an attribute. The attribute-link (connecting an object to the attribute) will be created as an edge later.
|
||||
if isinstance(result, int):
|
||||
m_od.create_integer_value(model_element_name_to_create, result)
|
||||
m_od.create_integer_value(model_el_name_to_create, result)
|
||||
elif isinstance(result, str):
|
||||
m_od.create_string_value(model_element_name_to_create, result)
|
||||
match_mapping[pattern_name_to_create] = model_element_name_to_create
|
||||
m_od.create_string_value(model_el_name_to_create, result)
|
||||
name_mapping[pattern_name_to_create] = model_el_name_to_create
|
||||
else:
|
||||
raise Exception(f"RHS element '{pattern_name_to_create}' needs to be created in host, but has no un-RAMified type, and I don't know what to do with it. It's type is", type_name)
|
||||
|
||||
|
||||
print('match_mapping:', match_mapping)
|
||||
|
||||
print("create edges....")
|
||||
for pattern_name_to_create, rhs_element_to_create, original_type, original_type_name, rhs_type, model_element_name_to_create in edges_to_create:
|
||||
print('creating', pattern_name_to_create)
|
||||
# print("create edges....")
|
||||
for pattern_name_to_create, rhs_el_to_create, original_type, original_type_name, model_el_name_to_create in edges_to_create:
|
||||
# print('creating', pattern_name_to_create)
|
||||
if original_type_name == 'attribute link':
|
||||
print(' -> creating attribute link')
|
||||
src = bottom.read_edge_source(rhs_element_to_create)
|
||||
src_name = od.get_object_name(bottom, rhs, src)
|
||||
tgt = bottom.read_edge_target(rhs_element_to_create)
|
||||
tgt_name = od.get_object_name(bottom, rhs, tgt)
|
||||
obj_name = match_mapping[src_name] # name of object in host graph to create slot for
|
||||
attr_name = od.get_object_name(bottom, mm, original_type)
|
||||
class_name = m_od.get_class_of_object(obj_name)
|
||||
# Just when you thought the code couldn't get any dirtier:
|
||||
attribute_name = attr_name[len(class_name)+1:]
|
||||
# print(attribute_name, obj_name, match_mapping[tgt_name])
|
||||
m_od.create_slot(attribute_name, obj_name, match_mapping[tgt_name])
|
||||
# print(' -> creating attribute link')
|
||||
src = bottom.read_edge_source(rhs_el_to_create)
|
||||
src_name = od.get_object_name(bottom, rhs_m, src)
|
||||
tgt = bottom.read_edge_target(rhs_el_to_create)
|
||||
tgt_name = od.get_object_name(bottom, rhs_m, tgt)
|
||||
obj_name = name_mapping[src_name] # name of object in host graph to create slot for
|
||||
orig_attr_name = od.get_attr_name(bottom, original_type)
|
||||
m_od.create_slot(orig_attr_name, obj_name, name_mapping[tgt_name])
|
||||
elif original_type_name == 'link':
|
||||
print(' -> creating link')
|
||||
src = bottom.read_edge_source(rhs_element_to_create)
|
||||
src_name = od.get_object_name(bottom, rhs, src)
|
||||
tgt = bottom.read_edge_target(rhs_element_to_create)
|
||||
tgt_name = od.get_object_name(bottom, rhs, tgt)
|
||||
obj_name = match_mapping[src_name] # name of object in host graph to create slot for
|
||||
# print(' -> creating link')
|
||||
src = bottom.read_edge_source(rhs_el_to_create)
|
||||
src_name = od.get_object_name(bottom, rhs_m, src)
|
||||
tgt = bottom.read_edge_target(rhs_el_to_create)
|
||||
tgt_name = od.get_object_name(bottom, rhs_m, tgt)
|
||||
obj_name = name_mapping[src_name] # name of object in host graph to create slot for
|
||||
attr_name = od.get_object_name(bottom, mm, original_type)
|
||||
class_name = m_od.get_class_of_object(obj_name)
|
||||
# print(attr_name, obj_name, match_mapping[tgt_name])
|
||||
m_od.create_link(model_element_name_to_create, attr_name, obj_name, match_mapping[tgt_name])
|
||||
m_od.create_link(model_el_name_to_create, attr_name, obj_name, name_mapping[tgt_name])
|
||||
|
||||
# Perform updates
|
||||
for pattern_element_name in common:
|
||||
model_element_name = match_mapping[pattern_element_name]
|
||||
print('updating', model_element_name)
|
||||
model_element, = bottom.read_outgoing_elements(m_to_transform, model_element_name)
|
||||
host_type = od.get_type(bottom, model_element)
|
||||
|
||||
# Perform updates (only on values)
|
||||
for pattern_el_name in common:
|
||||
model_el_name = name_mapping[pattern_el_name]
|
||||
# print('updating', model_el_name)
|
||||
model_el, = bottom.read_outgoing_elements(host_m, model_el_name)
|
||||
host_type = od.get_type(bottom, model_el)
|
||||
if od.is_typed_by(bottom, host_type, class_type):
|
||||
print(' -> is classs')
|
||||
# print(' -> is classs')
|
||||
# nothing to do
|
||||
pass
|
||||
elif od.is_typed_by(bottom, host_type, attr_link_type):
|
||||
print(' -> is attr link')
|
||||
# print(' -> is attr link')
|
||||
# nothing to do
|
||||
pass
|
||||
elif od.is_typed_by(bottom, host_type, modelref_type):
|
||||
print(' -> is modelref')
|
||||
old_value = od.read_primitive_value(bottom, model_element, mm)
|
||||
rhs_element, = bottom.read_outgoing_elements(rhs, pattern_element_name)
|
||||
expr = od.read_primitive_value(bottom, rhs_element, rhs_mm)
|
||||
# print(' -> is modelref')
|
||||
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)
|
||||
result = eval(expr, {}, {'v': old_value})
|
||||
# print('eval result=', result)
|
||||
if isinstance(result, int):
|
||||
# overwrite the old value, in-place
|
||||
referred_model_id = UUID(bottom.read_value(model_element))
|
||||
referred_model_id = UUID(bottom.read_value(model_el))
|
||||
Integer(referred_model_id, state).create(result)
|
||||
else:
|
||||
raise Exception("Unimplemented type. Value:", result)
|
||||
else:
|
||||
raise Exception("Don't know what to do with element of type", host_type)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue