Pārlūkot izejas kodu

Add conformance checking example

Joeri Exelmans 10 mēneši atpakaļ
vecāks
revīzija
17bff66e8e

+ 4 - 6
concrete_syntax/textual_cd/parser.py

@@ -1,14 +1,12 @@
 grammar = r"""
-%import common.WS_INLINE
-%ignore WS_INLINE
+%import common.WS
+%ignore WS
 %ignore COMMENT
 
-%declare _INDENT _DEDENT
-
-?start: (_NL | object )*
+?start: object*
 
 IDENTIFIER: /[A-Za-z_][A-Za-z_0-9]*/
-COMMENT: /#.*/
+COMMENT: /#[^\n]*\n/
 
 # newline
 _NL: /(\r?\n[\t ]*)+/

+ 2 - 2
concrete_syntax/textual_od/parser.py

@@ -46,8 +46,8 @@ class _Code:
         self.code = code
 
 # given a concrete syntax text string, and a meta-model, parses the CS
-def parse_od(state, cs_text, mm):
-    tree = parser.parse(cs_text)
+def parse_od(state, m_text, mm):
+    tree = parser.parse(m_text)
 
     m = state.create_node()
     od = OD(mm, m, state)

experiments/exp_metacircular.py → examples/conformance/metacircularity.py


+ 199 - 0
examples/conformance/woods.py

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

experiments/exp_scd.plantuml → examples/model_transformation/woods.plantuml


+ 1 - 7
experiments/exp_woods.py

@@ -1,4 +1,4 @@
-# Simple Class Diagram experiment
+# Model transformation experiment
 
 from state.devstate import DevState
 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.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():
     state = DevState()
     root = state.read_root() # id: 0

+ 4 - 1
services/od.py

@@ -181,7 +181,10 @@ class OD:
                     break
                 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)
         return link_id
 

+ 17 - 0
util/prompt.py

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