Add conformance checking example
This commit is contained in:
parent
b69efc9af0
commit
17bff66e8e
8 changed files with 227 additions and 16 deletions
|
|
@ -1,14 +1,12 @@
|
||||||
grammar = r"""
|
grammar = r"""
|
||||||
%import common.WS_INLINE
|
%import common.WS
|
||||||
%ignore WS_INLINE
|
%ignore WS
|
||||||
%ignore COMMENT
|
%ignore COMMENT
|
||||||
|
|
||||||
%declare _INDENT _DEDENT
|
?start: object*
|
||||||
|
|
||||||
?start: (_NL | object )*
|
|
||||||
|
|
||||||
IDENTIFIER: /[A-Za-z_][A-Za-z_0-9]*/
|
IDENTIFIER: /[A-Za-z_][A-Za-z_0-9]*/
|
||||||
COMMENT: /#.*/
|
COMMENT: /#[^\n]*\n/
|
||||||
|
|
||||||
# newline
|
# newline
|
||||||
_NL: /(\r?\n[\t ]*)+/
|
_NL: /(\r?\n[\t ]*)+/
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ class _Code:
|
||||||
self.code = code
|
self.code = code
|
||||||
|
|
||||||
# given a concrete syntax text string, and a meta-model, parses the CS
|
# given a concrete syntax text string, and a meta-model, parses the CS
|
||||||
def parse_od(state, cs_text, mm):
|
def parse_od(state, m_text, mm):
|
||||||
tree = parser.parse(cs_text)
|
tree = parser.parse(m_text)
|
||||||
|
|
||||||
m = state.create_node()
|
m = state.create_node()
|
||||||
od = OD(mm, m, state)
|
od = OD(mm, m, state)
|
||||||
|
|
|
||||||
199
examples/conformance/woods.py
Normal file
199
examples/conformance/woods.py
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
from state.devstate import DevState
|
||||||
|
from bootstrap.scd import bootstrap_scd
|
||||||
|
from framework.conformance import Conformance, render_conformance_check_result
|
||||||
|
from concrete_syntax.textual_od import parser, renderer
|
||||||
|
from concrete_syntax.common import indent
|
||||||
|
from concrete_syntax.plantuml import renderer as plantuml
|
||||||
|
from util.prompt import yes_no, pause
|
||||||
|
|
||||||
|
state = DevState()
|
||||||
|
|
||||||
|
print("Loading meta-meta-model...")
|
||||||
|
scd_mmm = bootstrap_scd(state)
|
||||||
|
print("OK")
|
||||||
|
|
||||||
|
print("Is our meta-meta-model a valid class diagram?")
|
||||||
|
conf = Conformance(state, scd_mmm, scd_mmm)
|
||||||
|
print(render_conformance_check_result(conf.check_nominal()))
|
||||||
|
|
||||||
|
# If you are curious, you can serialize the meta-meta-model:
|
||||||
|
# print("--------------")
|
||||||
|
# print(indent(
|
||||||
|
# renderer.render_od(state,
|
||||||
|
# m_id=scd_mmm,
|
||||||
|
# mm_id=scd_mmm),
|
||||||
|
# 4))
|
||||||
|
# print("--------------")
|
||||||
|
|
||||||
|
|
||||||
|
# Change this:
|
||||||
|
woods_mm_cs = """
|
||||||
|
Animal:Class {
|
||||||
|
# The class Animal is an abstract class:
|
||||||
|
abstract = True;
|
||||||
|
}
|
||||||
|
|
||||||
|
# A class without attributes
|
||||||
|
# The `abstract` attribute shown above is optional (default: False)
|
||||||
|
Bear:Class
|
||||||
|
|
||||||
|
# Inheritance between two Classes is expressed as follows:
|
||||||
|
:Inheritance (Bear -> Animal) # meaning: Bear is an Animal
|
||||||
|
|
||||||
|
Man:Class {
|
||||||
|
# We can define lower and upper cardinalities on Classes
|
||||||
|
# (if unspecified, the lower-card is 0, and upper-card is infinity)
|
||||||
|
lower_cardinality = 1; # there must be at least one Man in every model
|
||||||
|
upper_cardinality = 2; # there must be at most two Men in every model
|
||||||
|
|
||||||
|
constraint = ```
|
||||||
|
# Python code
|
||||||
|
# the last statement must be a boolean expression
|
||||||
|
|
||||||
|
# When conformance checking, this code will be run for every Man-object.
|
||||||
|
# The variable 'this' refers to the current Man-object.
|
||||||
|
|
||||||
|
# Every man weighs at least '20'
|
||||||
|
# (the attribute 'weight' is added further down)
|
||||||
|
get_value(get_slot(this, "weight")) > 20
|
||||||
|
```;
|
||||||
|
}
|
||||||
|
# Note that we can only declare the inheritance link after having declared both Man and Animal: We can only refer to earlier objects
|
||||||
|
:Inheritance (Man -> Animal) # Man is also an Animal
|
||||||
|
|
||||||
|
|
||||||
|
# BTW, we could also give the Inheritance-link a name, for instance:
|
||||||
|
# man_is_animal:Inheritance (Man -> Animal)
|
||||||
|
#
|
||||||
|
# Likewise, Classes, Associations, ... can also be nameless, for instance:
|
||||||
|
# :Class { ... }
|
||||||
|
# :Association (Man -> Man) { ... }
|
||||||
|
# However, we typically want to give names to classes and associations, because we want to refer to them later.
|
||||||
|
|
||||||
|
|
||||||
|
# We now add an attribute to 'Man'
|
||||||
|
# Attributes are not that different from Associations: both are represented by links
|
||||||
|
Man_weight:AttributeLink (Man -> Integer) {
|
||||||
|
name = "weight"; # mandatory!
|
||||||
|
optional = False; # <- meaning: every Man *must* have a weight
|
||||||
|
|
||||||
|
# We can also define constraints on attributes
|
||||||
|
constraint = ```
|
||||||
|
# Python code
|
||||||
|
# Here, 'this' refers to the LINK that connects a Man-object to an Integer
|
||||||
|
tgt = get_target(this) # <- we get the target of the LINK (an Integer-object)
|
||||||
|
weight = get_value(tgt) # <- get the Integer-value (e.g., 80)
|
||||||
|
weight > 20
|
||||||
|
```;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create an Association from Man to Animal
|
||||||
|
afraidOf:Association (Man -> Animal) {
|
||||||
|
# An association has the following (optional) attributes:
|
||||||
|
# - source_lower_cardinality (default: 0)
|
||||||
|
# - source_upper_cardinality (default: infinity)
|
||||||
|
# - target_lower_cardinality (default: 0)
|
||||||
|
# - target_upper_cardinality (default: infinity)
|
||||||
|
|
||||||
|
# Every Man is afraid of at least one Animal:
|
||||||
|
target_lower_cardinality = 1;
|
||||||
|
|
||||||
|
# No more than 6 Men are afraid of the same Animal:
|
||||||
|
source_upper_cardinality = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a GlobalConstraint
|
||||||
|
total_weight_small_enough:GlobalConstraint {
|
||||||
|
# Note: for GlobalConstraints, there is no 'this'-variable
|
||||||
|
constraint = ```
|
||||||
|
# Python code
|
||||||
|
# compute sum of all weights
|
||||||
|
total_weight = 0
|
||||||
|
for man_name, man_id in get_all_instances("Man"):
|
||||||
|
total_weight += get_value(get_slot(man_id, "weight"))
|
||||||
|
|
||||||
|
# as usual, the last statement is a boolean expression that we think should be satisfied
|
||||||
|
total_weight < 85
|
||||||
|
```;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("Parsing 'woods' meta-model...")
|
||||||
|
woods_mm = parser.parse_od(
|
||||||
|
state,
|
||||||
|
m_text=woods_mm_cs, # the string of text to parse
|
||||||
|
mm=scd_mmm, # the meta-model of class diagrams (= our meta-meta-model)
|
||||||
|
)
|
||||||
|
print("OK")
|
||||||
|
|
||||||
|
# As a double-check, you can serialize the parsed model:
|
||||||
|
# print("--------------")
|
||||||
|
# print(indent(
|
||||||
|
# renderer.render_od(state,
|
||||||
|
# m_id=woods_mm,
|
||||||
|
# mm_id=scd_mmm),
|
||||||
|
# 4))
|
||||||
|
# print("--------------")
|
||||||
|
|
||||||
|
print("Is our 'woods' meta-model a valid class diagram?")
|
||||||
|
conf = Conformance(state, woods_mm, scd_mmm)
|
||||||
|
print(render_conformance_check_result(conf.check_nominal()))
|
||||||
|
|
||||||
|
# Change this:
|
||||||
|
woods_m_cs = """
|
||||||
|
george:Man {
|
||||||
|
weight = 15;
|
||||||
|
}
|
||||||
|
billy:Man {
|
||||||
|
weight = 100;
|
||||||
|
}
|
||||||
|
bear1:Bear
|
||||||
|
bear2:Bear
|
||||||
|
:afraidOf (george -> bear1)
|
||||||
|
:afraidOf (george -> bear2)
|
||||||
|
"""
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("Parsing 'woods' model...")
|
||||||
|
woods_m = parser.parse_od(
|
||||||
|
state,
|
||||||
|
m_text=woods_m_cs,
|
||||||
|
mm=woods_mm, # this time, the meta-model is the previous model we parsed
|
||||||
|
)
|
||||||
|
print("OK")
|
||||||
|
|
||||||
|
# As a double-check, you can serialize the parsed model:
|
||||||
|
# print("--------------")
|
||||||
|
# print(indent(
|
||||||
|
# renderer.render_od(state,
|
||||||
|
# m_id=woods_m,
|
||||||
|
# mm_id=woods_mm),
|
||||||
|
# 4))
|
||||||
|
# print("--------------")
|
||||||
|
|
||||||
|
print("Is our model a valid woods-diagram?")
|
||||||
|
conf = Conformance(state, woods_m, woods_mm)
|
||||||
|
print(render_conformance_check_result(conf.check_nominal()))
|
||||||
|
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("==================================")
|
||||||
|
if yes_no("Print PlantUML?"):
|
||||||
|
print_mm = yes_no(" ▸ Print meta-model?")
|
||||||
|
print_m = yes_no(" ▸ Print model?")
|
||||||
|
print_conf = print_mm and print_m and yes_no(" ▸ Print conformance links?")
|
||||||
|
|
||||||
|
uml = ""
|
||||||
|
if print_mm:
|
||||||
|
uml += plantuml.render_package("Meta-model", plantuml.render_class_diagram(state, woods_mm))
|
||||||
|
if print_m:
|
||||||
|
uml += plantuml.render_package("Model", plantuml.render_object_diagram(state, woods_m, woods_mm))
|
||||||
|
if print_conf:
|
||||||
|
uml += plantuml.render_trace_conformance(state, woods_m, woods_mm)
|
||||||
|
|
||||||
|
print("==================================")
|
||||||
|
print(uml)
|
||||||
|
print("==================================")
|
||||||
|
print("Go to http://www.plantuml.com/plantuml/uml/")
|
||||||
|
print("and paste the above string.")
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Simple Class Diagram experiment
|
# Model transformation experiment
|
||||||
|
|
||||||
from state.devstate import DevState
|
from state.devstate import DevState
|
||||||
from bootstrap.scd import bootstrap_scd
|
from bootstrap.scd import bootstrap_scd
|
||||||
|
|
@ -15,12 +15,6 @@ from services.primitives.integer_type import Integer
|
||||||
from concrete_syntax.plantuml import renderer as plantuml
|
from concrete_syntax.plantuml import renderer as plantuml
|
||||||
from concrete_syntax.textual_od import parser, renderer
|
from concrete_syntax.textual_od import parser, renderer
|
||||||
|
|
||||||
def create_integer_node(state, i: int):
|
|
||||||
node = state.create_node()
|
|
||||||
integer_t = Integer(node, state)
|
|
||||||
integer_t.create(i)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
state = DevState()
|
state = DevState()
|
||||||
root = state.read_root() # id: 0
|
root = state.read_root() # id: 0
|
||||||
|
|
@ -181,7 +181,10 @@ class OD:
|
||||||
break
|
break
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
type_edge, = self.bottom.read_outgoing_elements(self.type_model, assoc_name)
|
type_edges = self.bottom.read_outgoing_elements(self.type_model, assoc_name)
|
||||||
|
if len(type_edges) == 0:
|
||||||
|
raise Exception(f"No such attribute/association: {assoc_name}")
|
||||||
|
type_edge = type_edges[0]
|
||||||
link_id = self._create_link(link_name, type_edge, src_obj_node, tgt_obj_node)
|
link_id = self._create_link(link_name, type_edge, src_obj_node, tgt_obj_node)
|
||||||
return link_id
|
return link_id
|
||||||
|
|
||||||
|
|
|
||||||
17
util/prompt.py
Normal file
17
util/prompt.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def yes_no(msg: str):
|
||||||
|
sys.stdout.write(f"{msg} <Y/n>")
|
||||||
|
|
||||||
|
choice = input()
|
||||||
|
if choice in {'Y','y',''}:
|
||||||
|
return True
|
||||||
|
elif choice in {'N','n'}:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print("Please respond with 'y' or 'n'")
|
||||||
|
return yes_no(msg)
|
||||||
|
|
||||||
|
def pause():
|
||||||
|
print("press any key...")
|
||||||
|
input()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue