(WIP) implementing CBD language... Meta-meta-model: Association inherits from Class. Matcher accepts pivot. Add generic graphviz renderer.
This commit is contained in:
parent
a26ceef10f
commit
1eb8a84553
25 changed files with 542 additions and 170 deletions
18
api/od.py
18
api/od.py
|
|
@ -118,7 +118,13 @@ class ODAPI:
|
|||
)[0]
|
||||
|
||||
def get(self, name: str):
|
||||
return self.bottom.read_outgoing_elements(self.m, name)[0]
|
||||
results = self.bottom.read_outgoing_elements(self.m, name)
|
||||
if len(results) == 1:
|
||||
return results[0]
|
||||
elif len(results) >= 2:
|
||||
raise Exception("this should never happen")
|
||||
else:
|
||||
raise Exception(f"No such element in model: '{name}'")
|
||||
|
||||
def get_type_name(self, obj: UUID):
|
||||
return self.get_name(self.get_type(obj))
|
||||
|
|
@ -144,6 +150,9 @@ class ODAPI:
|
|||
slot = self.get_slot(obj, attr_name)
|
||||
return self.get_value(slot)
|
||||
|
||||
# Returns the given default value if the slot does not exist on the object.
|
||||
# The attribute must exist in the object's class, or an exception will be thrown.
|
||||
# The slot may not exist however, if the attribute is defined as 'optional' in the class.
|
||||
def get_slot_value_default(self, obj: UUID, attr_name: str, default: any):
|
||||
try:
|
||||
return self.get_slot_value(obj, attr_name)
|
||||
|
|
@ -187,7 +196,12 @@ class ODAPI:
|
|||
|
||||
def create_link(self, link_name: Optional[str], assoc_name: str, src: UUID, tgt: UUID):
|
||||
global NEXT_ID
|
||||
typ, = self.bottom.read_outgoing_elements(self.mm, assoc_name)
|
||||
types = self.bottom.read_outgoing_elements(self.mm, assoc_name)
|
||||
if len(types) == 0:
|
||||
raise Exception(f"No such association: '{assoc_name}'")
|
||||
elif len(types) >= 2:
|
||||
raise Exception(f"More than one association exists with name '{assoc_name}' - this means the MM is invalid.")
|
||||
typ = types[0]
|
||||
if link_name == None:
|
||||
link_name = f"__{assoc_name}{NEXT_ID}"
|
||||
NEXT_ID += 1
|
||||
|
|
|
|||
|
|
@ -86,7 +86,8 @@ def bootstrap_scd(state: State) -> UUID:
|
|||
# # Attribute inherits from Element
|
||||
add_edge_element("attr_inh_element", attr_node, element_node)
|
||||
# # Association inherits from Element
|
||||
add_edge_element("assoc_inh_element", assoc_edge, element_node)
|
||||
# add_edge_element("assoc_inh_element", assoc_edge, element_node)
|
||||
add_edge_element("assoc_inh_element", assoc_edge, class_node)
|
||||
# # AttributeLink inherits from Element
|
||||
add_edge_element("attr_link_inh_element", attr_link_edge, element_node)
|
||||
# # ModelRef inherits from Attribute
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ def display_value(val: any, type_name: str, indentation=0):
|
|||
else:
|
||||
raise Exception("don't know how to display value" + type_name)
|
||||
|
||||
def display_name(raw_name: str) -> str:
|
||||
if raw_name[0:2] == "__":
|
||||
return "" # hide names that start with '__', they are anonymous (by convention)
|
||||
else:
|
||||
return raw_name
|
||||
|
||||
# internal use only
|
||||
# just a dumb wrapper to distinguish between code and string
|
||||
|
|
|
|||
12
concrete_syntax/graphviz/make_url.py
Normal file
12
concrete_syntax/graphviz/make_url.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from concrete_syntax.common import indent
|
||||
import urllib.parse
|
||||
|
||||
def make_url(graphviz_txt: str) -> str:
|
||||
|
||||
as_digraph = f"digraph {{\n{indent(graphviz_txt, 2)}\n}}"
|
||||
|
||||
# This one seems much faster:
|
||||
return "https://edotor.net/?engine=dot#"+urllib.parse.quote(as_digraph)
|
||||
|
||||
# Keeping this one here just in case:
|
||||
# return "https://dreampuf.github.io/GraphvizOnline/#"+urllib.parse.quote(graphviz)
|
||||
88
concrete_syntax/graphviz/renderer.py
Normal file
88
concrete_syntax/graphviz/renderer.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
from uuid import UUID
|
||||
from services import scd, od
|
||||
from services.bottom.V0 import Bottom
|
||||
from concrete_syntax.common import display_value, display_name, indent
|
||||
|
||||
|
||||
def render_object_diagram(state, m, mm, render_attributes=True, prefix_ids=""):
|
||||
bottom = Bottom(state)
|
||||
mm_scd = scd.SCD(mm, state)
|
||||
m_od = od.OD(mm, m, state)
|
||||
|
||||
def make_id(uuid) -> str:
|
||||
return 'n'+(prefix_ids+str(uuid).replace('-',''))[24:]
|
||||
|
||||
output = ""
|
||||
|
||||
# Render objects
|
||||
for class_name, class_node in mm_scd.get_classes().items():
|
||||
if render_attributes:
|
||||
attributes = od.get_attributes(bottom, class_node)
|
||||
|
||||
for obj_name, obj_node in m_od.get_objects(class_node).items():
|
||||
output += f"\n{make_id(obj_node)} [label=\"{display_name(obj_name)} : {class_name}\", shape=rect] ;"
|
||||
#" {{"
|
||||
|
||||
# if render_attributes:
|
||||
# for attr_name, attr_edge in attributes:
|
||||
# slot = m_od.get_slot(obj_node, attr_name)
|
||||
# if slot != None:
|
||||
# val, type_name = od.read_primitive_value(bottom, slot, mm)
|
||||
# output += f"\n{attr_name} => {display_value(val, type_name)}"
|
||||
# output += '\n}'
|
||||
|
||||
output += '\n'
|
||||
|
||||
# Render links
|
||||
for assoc_name, assoc_edge in mm_scd.get_associations().items():
|
||||
for link_name, link_edge in m_od.get_objects(assoc_edge).items():
|
||||
src_obj = bottom.read_edge_source(link_edge)
|
||||
tgt_obj = bottom.read_edge_target(link_edge)
|
||||
src_name = m_od.get_object_name(src_obj)
|
||||
tgt_name = m_od.get_object_name(tgt_obj)
|
||||
|
||||
output += f"\n{make_id(src_obj)} -> {make_id(tgt_obj)} [label=\"{display_name(link_name)}:{assoc_name}\"] ;"
|
||||
|
||||
return output
|
||||
|
||||
def render_trace_match(state, name_mapping: dict, pattern_m: UUID, host_m: UUID, color="grey", prefix_pattern_ids="", prefix_host_ids=""):
|
||||
bottom = Bottom(state)
|
||||
class_type = od.get_scd_mm_class_node(bottom)
|
||||
attr_link_type = od.get_scd_mm_attributelink_node(bottom)
|
||||
|
||||
def make_pattern_id(uuid) -> str:
|
||||
return 'n'+(prefix_pattern_ids+str(uuid).replace('-',''))[24:]
|
||||
def make_host_id(uuid) -> str:
|
||||
return 'n'+(prefix_host_ids+str(uuid).replace('-',''))[24:]
|
||||
|
||||
output = ""
|
||||
|
||||
# render_suffix = f"#line:{color};line.dotted;text:{color} : matchedWith"
|
||||
render_suffix = f"[label=\"\",style=dashed,color={color}] ;"
|
||||
|
||||
for pattern_el_name, host_el_name in name_mapping.items():
|
||||
# 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)
|
||||
except:
|
||||
continue
|
||||
# only render 'match'-edges between objects (= those elements where the type of the type is 'Class'):
|
||||
pattern_el_type = od.get_type(bottom, pattern_el)
|
||||
pattern_el_type_type = od.get_type(bottom, pattern_el_type)
|
||||
if pattern_el_type_type == class_type:
|
||||
output += f"\n{make_pattern_id(pattern_el)} -> {make_host_id(host_el)} {render_suffix}"
|
||||
# elif pattern_el_type_type == attr_link_type:
|
||||
# pattern_obj = bottom.read_edge_source(pattern_el)
|
||||
# pattern_attr_name = od.get_attr_name(bottom, pattern_el_type)
|
||||
# host_obj = bottom.read_edge_source(host_el)
|
||||
# host_el_type = od.get_type(bottom, host_el)
|
||||
# host_attr_name = od.get_attr_name(bottom, host_el_type)
|
||||
# output += f"\n{make_pattern_id(pattern_obj)}::{pattern_attr_name} -> {make_host_id(host_obj)}::{host_attr_name} {render_suffix}"
|
||||
return output
|
||||
|
||||
def render_package(name, contents):
|
||||
output = f"subgraph cluster_{name} {{\n label=\"{name}\";"
|
||||
output += indent(contents, 2)
|
||||
output += "\n}\n"
|
||||
return output
|
||||
21
concrete_syntax/plantuml/make_url.py
Normal file
21
concrete_syntax/plantuml/make_url.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
from zlib import compress
|
||||
import base64
|
||||
import string
|
||||
|
||||
maketrans = bytes.maketrans
|
||||
|
||||
# Includes code fragments from: https://github.com/dougn/python-plantuml/blob/bb5407e87aabbac9e8baef5a6726b03f72afca16/plantuml.py
|
||||
# Copyright (c) 2013, Doug Napoleone and then Copyright (c) 2015, Samuel Marks
|
||||
|
||||
plantuml_alphabet = string.digits + string.ascii_uppercase + string.ascii_lowercase + '-_'
|
||||
base64_alphabet = string.ascii_uppercase + string.ascii_lowercase + string.digits + '+/'
|
||||
b64_to_plantuml = maketrans(base64_alphabet.encode('utf-8'), plantuml_alphabet.encode('utf-8'))
|
||||
|
||||
def encode(plantuml_text: str) -> str:
|
||||
zlibbed_str = compress(plantuml_text.encode('utf-8'))
|
||||
compressed_string = zlibbed_str[2:-4]
|
||||
return base64.b64encode(compressed_string).translate(b64_to_plantuml).decode('utf-8')
|
||||
|
||||
def make_url(plantuml_text: str) -> str:
|
||||
encoded = encode(plantuml_text)
|
||||
return f"https://deemz.org/plantuml/pdf/{encoded}"
|
||||
|
|
@ -3,9 +3,10 @@
|
|||
from services import scd, od
|
||||
from services.bottom.V0 import Bottom
|
||||
from transformation import ramify
|
||||
from concrete_syntax.common import display_value
|
||||
from concrete_syntax.common import display_value, display_name
|
||||
from uuid import UUID
|
||||
|
||||
|
||||
def render_class_diagram(state, model, prefix_ids=""):
|
||||
bottom = Bottom(state)
|
||||
model_scd = scd.SCD(model, state)
|
||||
|
|
@ -102,7 +103,7 @@ def render_object_diagram(state, m, mm, render_attributes=True, prefix_ids=""):
|
|||
attributes = od.get_attributes(bottom, class_node)
|
||||
|
||||
for obj_name, obj_node in m_od.get_objects(class_node).items():
|
||||
output += f"\nmap \"{obj_name} : {class_name}\" as {make_id(obj_node)} {{"
|
||||
output += f"\nmap \"{display_name(obj_name)} : {class_name}\" as {make_id(obj_node)} {{"
|
||||
|
||||
if render_attributes:
|
||||
for attr_name, attr_edge in attributes:
|
||||
|
|
@ -122,7 +123,7 @@ def render_object_diagram(state, m, mm, render_attributes=True, prefix_ids=""):
|
|||
src_name = m_od.get_object_name(src_obj)
|
||||
tgt_name = m_od.get_object_name(tgt_obj)
|
||||
|
||||
output += f"\n{make_id(src_obj)} -> {make_id(tgt_obj)} : :{assoc_name}"
|
||||
output += f"\n{make_id(src_obj)} -> {make_id(tgt_obj)} : {display_name(link_name)}:{assoc_name}"
|
||||
|
||||
return output
|
||||
|
||||
|
|
@ -229,4 +230,3 @@ def render_trace_match(state, name_mapping: dict, pattern_m: UUID, host_m: UUID,
|
|||
host_attr_name = od.get_attr_name(bottom, host_el_type)
|
||||
output += f"\n{make_pattern_id(pattern_obj)}::{pattern_attr_name} ..> {make_host_id(host_obj)}::{host_attr_name} {render_suffix}"
|
||||
return output
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
# This module loads all the models (including the transformation rules) and performs a conformance-check on them.
|
||||
|
||||
import os
|
||||
from framework.conformance import Conformance, render_conformance_check_result
|
||||
from concrete_syntax.textual_od import parser
|
||||
|
||||
from transformation.ramify import ramify
|
||||
|
||||
# get file contents as string
|
||||
def read_file(filename):
|
||||
|
|
@ -9,15 +11,6 @@ def read_file(filename):
|
|||
with open(dir+'/'+filename) as file:
|
||||
return file.read()
|
||||
|
||||
# def parse_and_check(state, cs_file, mm):
|
||||
# m_cs = read_file(cs_file)
|
||||
# try:
|
||||
# _parse_and_check(state, m_cs, mm)
|
||||
# except Exception as e:
|
||||
# e.add_note(f"While parsing '{cs_file}'")
|
||||
# raise
|
||||
# return m
|
||||
|
||||
def parse_and_check(state, m_cs, mm, descr: str):
|
||||
try:
|
||||
m = parser.parse_od(
|
||||
|
|
@ -25,12 +18,16 @@ def parse_and_check(state, m_cs, mm, descr: str):
|
|||
m_text=m_cs,
|
||||
mm=mm,
|
||||
)
|
||||
except Exception as e:
|
||||
e.add_note("While parsing model " + descr)
|
||||
raise
|
||||
try:
|
||||
conf = Conformance(state, m, mm)
|
||||
errors = conf.check_nominal()
|
||||
if len(errors) > 0:
|
||||
raise Exception(render_conformance_check_result(errors))
|
||||
print(render_conformance_check_result(errors))
|
||||
except Exception as e:
|
||||
e.add_note("While parsing model " + descr)
|
||||
e.add_note("In model " + descr)
|
||||
raise
|
||||
return m
|
||||
|
||||
|
|
@ -53,3 +50,22 @@ def get_fibonacci(state, scd_mmm):
|
|||
m_rt_initial = parse_and_check(state, m_rt_initial_cs, mm_rt, "Fibonacci initial state")
|
||||
|
||||
return (mm, mm_rt, m, m_rt_initial)
|
||||
|
||||
RULE_NAMES = ["delay"]
|
||||
KINDS = ["nac", "lhs", "rhs"]
|
||||
|
||||
def get_rules(state, rt_mm):
|
||||
rt_mm_ramified = ramify(state, rt_mm)
|
||||
|
||||
rules = {} # e.g., { "delay": {"nac": <UUID>, "lhs": <UUID>, ...}, ...}
|
||||
|
||||
for rule_name in RULE_NAMES:
|
||||
rule = {}
|
||||
for kind in KINDS:
|
||||
filename = f"models/r_{rule_name}_{kind}.od";
|
||||
cs = read_file(filename)
|
||||
rule_m = parse_and_check(state, cs, rt_mm_ramified, descr=f"'{filename}'")
|
||||
rule[kind] = rule_m
|
||||
rules[rule_name] = rule
|
||||
|
||||
return (rt_mm_ramified, rules)
|
||||
|
|
@ -1,34 +1,34 @@
|
|||
# Adder, two inputs, one output
|
||||
adder:Function {
|
||||
func = ```
|
||||
n2_out = in0 + in1
|
||||
n2_out = n0_in + n1_in
|
||||
```;
|
||||
}
|
||||
n0_in:IntInPort
|
||||
n1_in:IntInPort
|
||||
n2_out:IntOutPort
|
||||
n0_in:InPort
|
||||
n1_in:InPort
|
||||
n2_out:OutPort
|
||||
:hasInPort (adder -> n0_in)
|
||||
:hasInPort (adder -> n1_in)
|
||||
:hasOutPort (adder -> n2_out)
|
||||
|
||||
|
||||
|
||||
# Delay block 0
|
||||
d0:Delay
|
||||
d0_in:IntInPort
|
||||
d0_out:IntOutPort
|
||||
d0_in:InPort
|
||||
d0_out:OutPort
|
||||
:hasInPort (d0 -> d0_in)
|
||||
:hasOutPort (d0 -> d0_out)
|
||||
|
||||
|
||||
|
||||
# Delay block 1
|
||||
d1:Delay
|
||||
d1_in:IntInPort
|
||||
d1_out:IntOutPort
|
||||
d1_in:InPort
|
||||
d1_out:OutPort
|
||||
:hasInPort (d1 -> d1_in)
|
||||
:hasOutPort (d1 -> d1_out)
|
||||
|
||||
|
||||
|
||||
:intLink (n2_out -> d1_in)
|
||||
:intLink (d1_out -> n1_in)
|
||||
:intLink (d1_out -> d0_in)
|
||||
:intLink (d1_out -> n0_in)
|
||||
# Connections
|
||||
conn0:link (n2_out -> d1_in) # n2 becomes n1 in next step
|
||||
conn1:link (d1_out -> d0_in) # n1 becomes n0 in next step
|
||||
conn2:link (d1_out -> n1_in) # n1 input to adder
|
||||
conn3:link (d0_out -> n0_in) # n0 input to adder
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
d0s:IntState {
|
||||
# Initial state for both delay blocks:
|
||||
d0s:State {
|
||||
state = 0;
|
||||
}
|
||||
d1s:IntState {
|
||||
d1s:State {
|
||||
state = 1;
|
||||
}
|
||||
:delay2State (d0 -> d0s)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ Block:Class {
|
|||
}
|
||||
|
||||
InPort:Class {
|
||||
abstract = True;
|
||||
# abstract = True;
|
||||
}
|
||||
OutPort:Class {
|
||||
abstract = True;
|
||||
# abstract = True;
|
||||
}
|
||||
|
||||
hasInPort:Association (Block -> InPort) {
|
||||
|
|
@ -77,8 +77,8 @@ Delay:Class {
|
|||
out_type = None
|
||||
else:
|
||||
out_type = get_type_name(get_target(get_outgoing(this, "hasOutPort")[0]))
|
||||
if in_type != None and out_type != None and in_type[0:3] != out_type[0:3]:
|
||||
errors.append(f"Inport type ({in_type}) differs from outport type ({out_type})")
|
||||
# if in_type != None and out_type != None and in_type[0:3] != out_type[0:3]:
|
||||
# errors.append(f"Inport type ({in_type}) differs from outport type ({out_type})")
|
||||
errors
|
||||
```;
|
||||
}
|
||||
|
|
@ -90,40 +90,40 @@ Delay:Class {
|
|||
# Object Diagrams are statically typed, so we must create in/out-ports, and MemorySlots for all primitive types:
|
||||
|
||||
|
||||
# Port types
|
||||
# # Port types
|
||||
|
||||
BoolInPort:Class
|
||||
IntInPort:Class
|
||||
StrInPort:Class
|
||||
# BoolInPort:Class
|
||||
# IntInPort:Class
|
||||
# StrInPort:Class
|
||||
|
||||
BoolOutPort:Class
|
||||
IntOutPort:Class
|
||||
StrOutPort:Class
|
||||
# BoolOutPort:Class
|
||||
# IntOutPort:Class
|
||||
# StrOutPort:Class
|
||||
|
||||
:Inheritance (BoolInPort -> InPort)
|
||||
:Inheritance (IntInPort -> InPort)
|
||||
:Inheritance (StrInPort -> InPort)
|
||||
# :Inheritance (BoolInPort -> InPort)
|
||||
# :Inheritance (IntInPort -> InPort)
|
||||
# :Inheritance (StrInPort -> InPort)
|
||||
|
||||
:Inheritance (BoolOutPort -> OutPort)
|
||||
:Inheritance (IntOutPort -> OutPort)
|
||||
:Inheritance (StrOutPort -> OutPort)
|
||||
# :Inheritance (BoolOutPort -> OutPort)
|
||||
# :Inheritance (IntOutPort -> OutPort)
|
||||
# :Inheritance (StrOutPort -> OutPort)
|
||||
|
||||
# Link types
|
||||
# # Link types
|
||||
|
||||
boolLink:Association (BoolOutPort -> BoolInPort)
|
||||
intLink:Association (IntOutPort -> IntInPort)
|
||||
strLink:Association (StrOutPort -> StrInPort)
|
||||
# boolLink:Association (BoolOutPort -> BoolInPort)
|
||||
# intLink:Association (IntOutPort -> IntInPort)
|
||||
# strLink:Association (StrOutPort -> StrInPort)
|
||||
|
||||
:Inheritance (boolLink -> link)
|
||||
:Inheritance (intLink -> link)
|
||||
:Inheritance (strLink -> link)
|
||||
# :Inheritance (boolLink -> link)
|
||||
# :Inheritance (intLink -> link)
|
||||
# :Inheritance (strLink -> link)
|
||||
|
||||
# Delay block types
|
||||
# # Delay block types
|
||||
|
||||
BoolDelay:Class
|
||||
IntDelay:Class
|
||||
StrDelay:Class
|
||||
# BoolDelay:Class
|
||||
# IntDelay:Class
|
||||
# StrDelay:Class
|
||||
|
||||
:Inheritance (BoolDelay -> Delay)
|
||||
:Inheritance (IntDelay -> Delay)
|
||||
:Inheritance (StrDelay -> Delay)
|
||||
# :Inheritance (BoolDelay -> Delay)
|
||||
# :Inheritance (IntDelay -> Delay)
|
||||
# :Inheritance (StrDelay -> Delay)
|
||||
|
|
|
|||
|
|
@ -1,28 +1,58 @@
|
|||
# Link state ("signal")
|
||||
# is optional: absent for yet-to-compute signals
|
||||
|
||||
intLink_signal:AttributeLink (intLink -> Integer) {
|
||||
name = "signal";
|
||||
optional = True;
|
||||
Signal:Class {
|
||||
# abstract = True;
|
||||
}
|
||||
boolLink_signal:AttributeLink (boolLink -> Boolean) {
|
||||
|
||||
Signal_signal:AttributeLink (Signal -> Integer) {
|
||||
name = "signal";
|
||||
optional = True;
|
||||
optional = False;
|
||||
}
|
||||
strLink_signal:AttributeLink (strLink -> String) {
|
||||
name = "signal";
|
||||
optional = True;
|
||||
|
||||
hasSignal:Association (link -> Signal) {
|
||||
# every Signal has 1 link
|
||||
source_lower_cardinality = 1;
|
||||
source_upper_cardinality = 1;
|
||||
# every link has 0..1 Signals:
|
||||
target_upper_cardinality = 1;
|
||||
}
|
||||
|
||||
# BoolSignal:Class
|
||||
# IntSignal:Class
|
||||
# StrSignal:Class
|
||||
|
||||
# :Inheritance (BoolSignal -> Signal)
|
||||
# :Inheritance (IntSignal -> Signal)
|
||||
# :Inheritance (StrSignal -> Signal)
|
||||
|
||||
# BoolSignal_signal:AttributeLink (BoolSignal -> Boolean) {
|
||||
# name = "signal";
|
||||
# optional = False;
|
||||
# }
|
||||
# IntSignal_signal:AttributeLink (IntSignal -> Integer) {
|
||||
# name = "signal";
|
||||
# optional = False;
|
||||
# }
|
||||
# StrSignal_signal:AttributeLink (StrSignal -> String) {
|
||||
# name = "signal";
|
||||
# optional = False;
|
||||
# }
|
||||
|
||||
|
||||
|
||||
# Delay block state
|
||||
# mandatory - otherwise we cannot determine the output signal of a delay block
|
||||
|
||||
State:Class {
|
||||
abstract = True;
|
||||
# abstract = True;
|
||||
}
|
||||
|
||||
State_state:AttributeLink (State -> Integer) {
|
||||
name = "state";
|
||||
optional = False;
|
||||
}
|
||||
|
||||
|
||||
delay2State:Association (Delay -> State) {
|
||||
source_lower_cardinality = 1;
|
||||
source_upper_cardinality = 1;
|
||||
|
|
@ -30,26 +60,26 @@ delay2State:Association (Delay -> State) {
|
|||
target_upper_cardinality = 1;
|
||||
}
|
||||
|
||||
BoolState:Class
|
||||
IntState:Class
|
||||
StrState:Class
|
||||
# BoolState:Class
|
||||
# IntState:Class
|
||||
# StrState:Class
|
||||
|
||||
:Inheritance (BoolState -> State)
|
||||
:Inheritance (IntState -> State)
|
||||
:Inheritance (StrState -> State)
|
||||
# :Inheritance (BoolState -> State)
|
||||
# :Inheritance (IntState -> State)
|
||||
# :Inheritance (StrState -> State)
|
||||
|
||||
|
||||
BoolState_state:AttributeLink (BoolState -> Boolean) {
|
||||
name = "state";
|
||||
optional = False;
|
||||
}
|
||||
# BoolState_state:AttributeLink (BoolState -> Boolean) {
|
||||
# name = "state";
|
||||
# optional = False;
|
||||
# }
|
||||
|
||||
IntState_state:AttributeLink (IntState -> Integer) {
|
||||
name = "state";
|
||||
optional = False;
|
||||
}
|
||||
# IntState_state:AttributeLink (IntState -> Integer) {
|
||||
# name = "state";
|
||||
# optional = False;
|
||||
# }
|
||||
|
||||
StrState_state:AttributeLink (StrState -> String) {
|
||||
name = "state";
|
||||
optional = False;
|
||||
}
|
||||
# StrState_state:AttributeLink (StrState -> String) {
|
||||
# name = "state";
|
||||
# optional = False;
|
||||
# }
|
||||
|
|
|
|||
16
examples/cbd/models/r_delay_lhs.od
Normal file
16
examples/cbd/models/r_delay_lhs.od
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# We look for a Delay-block, its outgoing connection, and its State
|
||||
|
||||
delay:RAM_Delay
|
||||
|
||||
delay_out:RAM_OutPort # abstract
|
||||
|
||||
delay_has_output:RAM_hasOutPort (delay -> delay_out)
|
||||
|
||||
some_inport:RAM_InPort # abstract
|
||||
|
||||
delay_out_conn:RAM_link (delay_out -> some_inport)
|
||||
|
||||
|
||||
state:RAM_State
|
||||
|
||||
delay_to_state:RAM_delay2State (delay -> state)
|
||||
14
examples/cbd/models/r_delay_nac.od
Normal file
14
examples/cbd/models/r_delay_nac.od
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# From our LHS:
|
||||
|
||||
delay_out:RAM_OutPort # abstract
|
||||
|
||||
some_inport:RAM_InPort # abstract
|
||||
|
||||
delay_out_conn:RAM_link (delay_out -> some_inport) # abstract
|
||||
|
||||
|
||||
# The delay block's outgoing connection already has a signal:
|
||||
|
||||
some_signal:RAM_Signal
|
||||
|
||||
:RAM_hasSignal (delay_out_conn -> some_signal)
|
||||
23
examples/cbd/models/r_delay_rhs.od
Normal file
23
examples/cbd/models/r_delay_rhs.od
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Our entire LHS (don't delete anything):
|
||||
|
||||
delay:RAM_Delay
|
||||
|
||||
delay_out:RAM_OutPort # abstract
|
||||
|
||||
delay_has_output:RAM_hasOutPort (delay -> delay_out)
|
||||
|
||||
some_inport:RAM_InPort # abstract
|
||||
|
||||
delay_out_conn:RAM_link (delay_out -> some_inport) # abstract
|
||||
|
||||
state:RAM_State
|
||||
|
||||
delay_to_state:RAM_delay2State (delay -> state)
|
||||
|
||||
|
||||
# To create:
|
||||
|
||||
new_signal:RAM_Signal {
|
||||
RAM_signal = `get_slot_value(match('state'), 'state')`;
|
||||
}
|
||||
:RAM_hasSignal (delay_out_conn -> new_signal)
|
||||
|
|
@ -1,8 +1,97 @@
|
|||
from state.devstate import DevState
|
||||
from bootstrap.scd import bootstrap_scd
|
||||
|
||||
from concrete_syntax.common import indent
|
||||
from concrete_syntax.textual_od import renderer as od_renderer
|
||||
from concrete_syntax.plantuml import renderer as plantuml
|
||||
from concrete_syntax.plantuml.make_url import make_url as make_plantuml_url
|
||||
from concrete_syntax.graphviz.make_url import make_url as make_graphviz_url
|
||||
from concrete_syntax.graphviz import renderer as graphviz
|
||||
|
||||
from transformation.matcher.mvs_adapter import match_od
|
||||
from transformation.rewriter import rewrite
|
||||
from transformation.cloner import clone_od
|
||||
|
||||
import models
|
||||
|
||||
state = DevState()
|
||||
scd_mmm = bootstrap_scd(state)
|
||||
|
||||
mm, mm_rt, m, m_rt_initial = models.get_fibonacci(state, scd_mmm)
|
||||
|
||||
mm_rt_ram, rules = models.get_rules(state, mm_rt)
|
||||
|
||||
|
||||
|
||||
# print("RT-MM")
|
||||
# print(make_plantuml_url(plantuml.render_class_diagram(state, mm_rt)))
|
||||
|
||||
|
||||
# print("RAMIFIED RT-MM")
|
||||
# print(make_plantuml_url(plantuml.render_class_diagram(state, mm_rt_ram)))
|
||||
|
||||
m_rt = m_rt_initial
|
||||
|
||||
def get_matches():
|
||||
for rule_name, rule in rules.items():
|
||||
lhs = rule["lhs"]
|
||||
|
||||
lhs_matcher = match_od(state,
|
||||
host_m=m_rt,
|
||||
host_mm=mm_rt,
|
||||
pattern_m=lhs,
|
||||
pattern_mm=mm_rt_ram)
|
||||
|
||||
for i, lhs_match in enumerate(lhs_matcher):
|
||||
|
||||
nac_matcher = match_od(state,
|
||||
host_m=m_rt,
|
||||
host_mm=mm_rt,
|
||||
pattern_m=rule["nac"],
|
||||
pattern_mm=mm_rt_ram,
|
||||
pivot=lhs_match)
|
||||
|
||||
for j, nac_match in enumerate(nac_matcher):
|
||||
break # there may be more NAC-matches, but we already now enough
|
||||
else:
|
||||
# We got a match!
|
||||
yield (rule_name, lhs, rule["rhs"], lhs_match)
|
||||
|
||||
while True:
|
||||
# print(make_graphviz_url(graphviz.render_object_diagram(state, m_rt, mm_rt)))
|
||||
cs = od_renderer.render_od(state, m_rt, mm_rt, hide_names=False)
|
||||
print(indent(cs, 6))
|
||||
|
||||
|
||||
matches = list(get_matches())
|
||||
print(f"There are {len(matches)} matches.")
|
||||
if len(matches) == 0:
|
||||
break
|
||||
rule_name, lhs, rhs, lhs_match = matches[0]
|
||||
|
||||
|
||||
# txt = graphviz.render_package("Host", graphviz.render_object_diagram(state, m_rt, mm_rt))
|
||||
# txt += graphviz.render_package("LHS", graphviz.render_object_diagram(state, lhs, mm_rt_ram))
|
||||
# txt += graphviz.render_trace_match(state, lhs_match, lhs, m_rt, color="orange")
|
||||
# match_urls.append(make_graphviz_url(txt))
|
||||
|
||||
print('picking', lhs_match)
|
||||
|
||||
print('rewriting')
|
||||
|
||||
# copy or will be overwritten in-place
|
||||
m_rt = clone_od(state, m_rt, mm_rt)
|
||||
rhs_match = dict(lhs_match)
|
||||
|
||||
rewrite(state,
|
||||
lhs_m=lhs,
|
||||
rhs_m=rhs,
|
||||
pattern_mm=mm_rt_ram,
|
||||
name_mapping=rhs_match,
|
||||
host_m=m_rt,
|
||||
mm=mm_rt)
|
||||
|
||||
# import subprocess
|
||||
# subprocess.run(["firefox", "--new-window", *match_urls])
|
||||
|
||||
# get_actions(state, rules, m_rt_initial, mm_rt)
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import urllib
|
||||
from concrete_syntax.common import indent
|
||||
from concrete_syntax.graphviz.make_url import make_url
|
||||
from examples.semantics.operational.port.helpers import design_to_state, state_to_design, get_time, get_num_ships
|
||||
|
||||
def render_port_graphviz(od):
|
||||
|
|
@ -53,9 +53,7 @@ def render_port_graphviz(od):
|
|||
if berth not in already_have:
|
||||
txt += f"{name} -> {od.get_name(berth)} [style=dotted, arrowhead=none, color=chocolate];\n"
|
||||
|
||||
graphviz = f"digraph {{\n{indent(txt, 2)}}}"
|
||||
|
||||
return "https://dreampuf.github.io/GraphvizOnline/#"+urllib.parse.quote(graphviz)
|
||||
return make_url(txt)
|
||||
|
||||
def render_port_textual(od):
|
||||
txt = ""
|
||||
|
|
|
|||
|
|
@ -7,22 +7,14 @@ from pprint import pprint
|
|||
import traceback
|
||||
from concrete_syntax.common import indent
|
||||
|
||||
from util.eval import exec_then_eval
|
||||
|
||||
from api.cd import CDAPI
|
||||
from api.od import ODAPI
|
||||
|
||||
import functools
|
||||
|
||||
|
||||
# based on https://stackoverflow.com/a/39381428
|
||||
# Parses and executes a block of Python code, and returns the eval result of the last statement
|
||||
import ast
|
||||
def exec_then_eval(code, _globals, _locals):
|
||||
block = ast.parse(code, mode='exec')
|
||||
# assumes last node is an expression
|
||||
last = ast.Expression(block.body.pop().value)
|
||||
exec(compile(block, '<string>', mode='exec'), _globals, _locals)
|
||||
return eval(compile(last, '<string>', mode='eval'), _globals, _locals)
|
||||
|
||||
def render_conformance_check_result(error_list):
|
||||
if len(error_list) == 0:
|
||||
return "CONFORM"
|
||||
|
|
|
|||
BIN
state.p
BIN
state.p
Binary file not shown.
|
|
@ -1,8 +1,10 @@
|
|||
from uuid import UUID
|
||||
from concrete_syntax.textual_od import parser, renderer
|
||||
from concrete_syntax.common import indent
|
||||
|
||||
# Clones an object diagram
|
||||
def clone_od(state, m: UUID, mm: UUID):
|
||||
# cheap-ass implementation: render and parse
|
||||
cs = renderer.render_od(state, m, mm, hide_names=False)
|
||||
# print(indent(cs, 6))
|
||||
return parser.parse_od(state, cs, mm)
|
||||
|
|
@ -76,10 +76,12 @@ class MatcherState:
|
|||
self.boundary = None
|
||||
|
||||
@staticmethod
|
||||
def make_initial(host, guest):
|
||||
def make_initial(host, guest, pivot):
|
||||
state = MatcherState()
|
||||
state.h_unmatched_vtxs = host.vtxs
|
||||
state.g_unmatched_vtxs = guest.vtxs
|
||||
state.h_unmatched_vtxs = [vtx for vtx in host.vtxs if vtx not in pivot.values()]
|
||||
state.g_unmatched_vtxs = [vtx for vtx in guest.vtxs if vtx not in pivot.keys()]
|
||||
state.mapping_vtxs = pivot
|
||||
state.r_mapping_vtxs = { v: k for k,v in state.mapping_vtxs.items() }
|
||||
return state
|
||||
|
||||
# Grow the match set (creating a new copy)
|
||||
|
|
@ -138,9 +140,9 @@ class MatcherVF2:
|
|||
|
||||
# print("number of guest connected components:", len(self.guest_component_to_vtxs))
|
||||
|
||||
def match(self):
|
||||
def match(self, pivot={}):
|
||||
yield from self._match(
|
||||
state=MatcherState.make_initial(self.host, self.guest),
|
||||
state=MatcherState.make_initial(self.host, self.guest, pivot),
|
||||
already_visited=set())
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from api.cd import CDAPI
|
||||
from state.base import State
|
||||
from uuid import UUID
|
||||
from services.bottom.V0 import Bottom
|
||||
|
|
@ -87,6 +88,8 @@ def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
|
|||
modelrefs = {}
|
||||
# constraints = {}
|
||||
|
||||
names = {}
|
||||
|
||||
def to_vtx(el, name):
|
||||
# print("name:", name)
|
||||
if bottom.is_edge(el):
|
||||
|
|
@ -101,7 +104,9 @@ def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
|
|||
# except:
|
||||
# pass
|
||||
mvs_edges.append(el)
|
||||
return MVSEdge(el, name)
|
||||
edge = MVSEdge(el, name)
|
||||
names[name] = edge
|
||||
return edge
|
||||
# If the value of the el is a ModelRef (only way to detect this is to match a regex - not very clean), then extract it. We'll create a link to the referred model later.
|
||||
value = bottom.read_value(el)
|
||||
if isinstance(value, str):
|
||||
|
|
@ -109,13 +114,15 @@ def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
|
|||
# side-effect
|
||||
modelrefs[el] = (UUID(value), name)
|
||||
return MVSNode(IS_MODELREF, el, name)
|
||||
return MVSNode(value, el, name)
|
||||
node = MVSNode(value, el, name)
|
||||
names[name] = node
|
||||
return node
|
||||
|
||||
# MVS-Nodes become vertices
|
||||
# Objects and Links become vertices
|
||||
uuid_to_vtx = { node: to_vtx(node, prefix+key) for key in bottom.read_keys(model) for node in bottom.read_outgoing_elements(model, key) }
|
||||
graph.vtxs = [ vtx for vtx in uuid_to_vtx.values() ]
|
||||
|
||||
# For every MSV-Edge, two edges are created (for src and tgt)
|
||||
# For every Link, two edges are created (for src and tgt)
|
||||
for mvs_edge in mvs_edges:
|
||||
mvs_src = bottom.read_edge_source(mvs_edge)
|
||||
if mvs_src in uuid_to_vtx:
|
||||
|
|
@ -194,10 +201,13 @@ def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
|
|||
for link_name, link_node in objects.items():
|
||||
add_types(link_node)
|
||||
|
||||
return graph
|
||||
return names, graph
|
||||
|
||||
|
||||
def match_od(state, host_m, host_mm, pattern_m, pattern_mm):
|
||||
def match_od(state, host_m, host_mm, pattern_m, pattern_mm, pivot={}):
|
||||
|
||||
# compute subtype relations and such:
|
||||
cdapi = CDAPI(state, host_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:
|
||||
|
|
@ -208,33 +218,23 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm):
|
|||
type_model_id = bottom.state.read_dict(bottom.state.read_root(), "SCD")
|
||||
self.scd_model = UUID(bottom.state.read_value(type_model_id))
|
||||
|
||||
def is_subtype_of(self, supposed_subtype: UUID, supposed_supertype: UUID):
|
||||
if supposed_subtype == supposed_supertype:
|
||||
# reflexive:
|
||||
return True
|
||||
|
||||
inheritance_node, = self.bottom.read_outgoing_elements(self.scd_model, "Inheritance")
|
||||
|
||||
for outgoing in self.bottom.read_outgoing_edges(supposed_subtype):
|
||||
if inheritance_node in self.bottom.read_outgoing_elements(outgoing, "Morphism"):
|
||||
# 'outgoing' is an inheritance link
|
||||
supertype = self.bottom.read_edge_target(outgoing)
|
||||
if supertype != supposed_subtype:
|
||||
if self.is_subtype_of(supertype, supposed_supertype):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def match_types(self, g_vtx_type, h_vtx_type):
|
||||
# types only match with their supertypes
|
||||
# we assume that 'RAMifies'-traceability links have been created between guest and host types
|
||||
try:
|
||||
g_vtx_original_type = ramify.get_original_type(self.bottom, g_vtx_type)
|
||||
g_vtx_unramified_type = ramify.get_original_type(self.bottom, g_vtx_type)
|
||||
except:
|
||||
return False
|
||||
|
||||
return self.is_subtype_of(h_vtx_type, g_vtx_original_type)
|
||||
try:
|
||||
host_type_name = cdapi.type_model_names[h_vtx_type]
|
||||
guest_type_name_unramified = cdapi.type_model_names[g_vtx_unramified_type]
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
return cdapi.is_subtype(
|
||||
super_type_name=guest_type_name_unramified,
|
||||
sub_type_name=host_type_name)
|
||||
|
||||
# Memoizing the result of comparison gives a huge performance boost!
|
||||
# Especially `is_subtype_of` is very slow, and will be performed many times over on the same pair of nodes during the matching process.
|
||||
|
|
@ -299,11 +299,18 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm):
|
|||
return False
|
||||
|
||||
# Convert to format understood by matching algorithm
|
||||
host = model_to_graph(state, host_m, host_mm)
|
||||
guest = model_to_graph(state, pattern_m, pattern_mm)
|
||||
h_names, host = model_to_graph(state, host_m, host_mm)
|
||||
g_names, guest = model_to_graph(state, pattern_m, pattern_mm)
|
||||
|
||||
|
||||
graph_pivot = {
|
||||
g_names[guest_name] : h_names[host_name]
|
||||
for guest_name, host_name in pivot.items()
|
||||
if guest_name in g_names
|
||||
}
|
||||
|
||||
matcher = MatcherVF2(host, guest, RAMCompare(Bottom(state), OD(host_mm, host_m, state)))
|
||||
for m in matcher.match():
|
||||
for m in matcher.match(graph_pivot):
|
||||
# print("\nMATCH:\n", m)
|
||||
# Convert mapping
|
||||
name_mapping = {}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
|
|||
string_modelref = ramified_scd.create_model_ref("String", string_type)
|
||||
actioncode_modelref = ramified_scd.create_model_ref("ActionCode", actioncode_type)
|
||||
|
||||
already_ramified = set() # for correct order of ramification
|
||||
|
||||
classes = m_scd.get_classes()
|
||||
for class_name, class_node in classes.items():
|
||||
# For every class in our original model, create a class:
|
||||
|
|
@ -49,26 +51,48 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
|
|||
# Every attribute becomes 'string' type
|
||||
# The string will be a Python expression
|
||||
ramified_attr_link = ramified_scd._create_attribute_link(prefix+class_name, actioncode_modelref, prefix+attr_name, optional=True)
|
||||
# traceability link
|
||||
# create traceability link
|
||||
bottom.create_edge(ramified_attr_link, attr_edge, RAMIFIES_LABEL)
|
||||
|
||||
associations = m_scd.get_associations()
|
||||
for assoc_name, assoc_node in associations.items():
|
||||
# For every association in our original model, create an association:
|
||||
# - src-min-card: 0
|
||||
# - src-max-card: same as original
|
||||
# - tgt-min-card: 0
|
||||
# - tgt-max-card: same as original
|
||||
_, 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)
|
||||
ramified_assoc = ramified_scd.create_association(
|
||||
prefix+assoc_name, prefix+src, prefix+tgt,
|
||||
src_max_c=src_upper_card,
|
||||
tgt_max_c=tgt_upper_card)
|
||||
# traceability link
|
||||
bottom.create_edge(ramified_assoc, assoc_node, RAMIFIES_LABEL)
|
||||
already_ramified.add(class_name)
|
||||
|
||||
|
||||
assocs_to_ramify = m_scd.get_associations()
|
||||
|
||||
while len(assocs_to_ramify) > 0:
|
||||
ramify_later = {}
|
||||
for assoc_name, assoc_node in assocs_to_ramify.items():
|
||||
# For every association in our original model, create an association:
|
||||
# - src-min-card: 0
|
||||
# - src-max-card: same as original
|
||||
# - tgt-min-card: 0
|
||||
# - tgt-max-card: same as original
|
||||
|
||||
if assoc_name in already_ramified:
|
||||
raise Exception("Assertion failed: did not expect this to ever happen!")
|
||||
continue
|
||||
|
||||
_, 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))
|
||||
|
||||
if src not in already_ramified or tgt not in already_ramified:
|
||||
ramify_later[assoc_name] = assoc_node
|
||||
continue
|
||||
|
||||
# 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(name=prefix+assoc_name,
|
||||
source=prefix+src, target=prefix+tgt,
|
||||
src_max_c=src_upper_card,
|
||||
tgt_max_c=tgt_upper_card)
|
||||
|
||||
# create traceability link
|
||||
bottom.create_edge(ramified_assoc, assoc_node, RAMIFIES_LABEL)
|
||||
|
||||
already_ramified.add(assoc_name)
|
||||
|
||||
assocs_to_ramify = ramify_later
|
||||
|
||||
for inh_name, inh_node in m_scd.get_inheritances().items():
|
||||
# Re-create inheritance links like in our original model:
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
|
|||
model_el_name_to_create = pattern_name_to_create + str(i) # use the label of the element in the RHS as a basis
|
||||
if len(bottom.read_outgoing_elements(host_m, model_el_name_to_create)) == 0:
|
||||
break # found an available name
|
||||
i += 1
|
||||
|
||||
# Determine the type of the thing to create
|
||||
rhs_el_to_create, = bottom.read_outgoing_elements(rhs_m, pattern_name_to_create)
|
||||
|
|
@ -130,30 +131,37 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
|
|||
|
||||
# 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)
|
||||
host_el_name = name_mapping[pattern_el_name]
|
||||
host_el, = bottom.read_outgoing_elements(host_m, host_el_name)
|
||||
# print('updating', host_el_name, host_el)
|
||||
host_type = od.get_type(bottom, host_el)
|
||||
# print('we have', pattern_el_name, '->', host_el_name, 'of type', type_name)
|
||||
if od.is_typed_by(bottom, host_type, class_type):
|
||||
# print(' -> is classs')
|
||||
# nothing to do
|
||||
pass
|
||||
elif od.is_typed_by(bottom, host_type, assoc_type):
|
||||
print(' -> is association')
|
||||
# nothing to do
|
||||
pass
|
||||
elif od.is_typed_by(bottom, host_type, attr_link_type):
|
||||
# print(' -> is attr link')
|
||||
# nothing to do
|
||||
pass
|
||||
elif od.is_typed_by(bottom, host_type, modelref_type):
|
||||
# print(' -> is modelref')
|
||||
old_value, _ = od.read_primitive_value(bottom, model_el, mm)
|
||||
print(' -> is modelref')
|
||||
old_value, _ = od.read_primitive_value(bottom, host_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_el))
|
||||
referred_model_id = UUID(bottom.read_value(host_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)
|
||||
msg = f"Don't know what to do with element '{pattern_el_name}'->'{host_el_name}' of type ({host_type})"
|
||||
# print(msg)
|
||||
raise Exception(msg)
|
||||
|
|
|
|||
9
util/eval.py
Normal file
9
util/eval.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# based on https://stackoverflow.com/a/39381428
|
||||
# Parses and executes a block of Python code, and returns the eval result of the last statement
|
||||
import ast
|
||||
def exec_then_eval(code, _globals, _locals):
|
||||
block = ast.parse(code, mode='exec')
|
||||
# assumes last node is an expression
|
||||
last = ast.Expression(block.body.pop().value)
|
||||
exec(compile(block, '<string>', mode='exec'), _globals, _locals)
|
||||
return eval(compile(last, '<string>', mode='eval'), _globals, _locals)
|
||||
Loading…
Add table
Add a link
Reference in a new issue