|
@@ -1,20 +1,153 @@
|
|
|
Metamodelling
|
|
|
=============
|
|
|
|
|
|
-Explain use of metamodelling
|
|
|
+Defining languages in the Modelverse is done through metamodelling.
|
|
|
+With metamodelling, a language is itself a model, which can be created through the usual operations.
|
|
|
+This explains why language engineers need to have the same knowledge of the Modelverse as modellers: language engineering is modelling in a domain-specific language for languages.
|
|
|
|
|
|
-Metamodels as Models
|
|
|
+Creating a metamodel
|
|
|
--------------------
|
|
|
|
|
|
-Metamodels are models as well, and therefore quite similar in use
|
|
|
+To create a metamodel, you have to instantiate the *SimpleClassDiagrams* metamodel.
|
|
|
+While there is no restriction that this has to be *SimpleClassDiagrams*, for now we assume that this is the only possible meta-language.
|
|
|
+Therefore, to create the PetriNets language, we execute::
|
|
|
|
|
|
-Creating a metamodel
|
|
|
---------------------
|
|
|
+ >>> model_add("PetriNets", "SimpleClassDiagrams")
|
|
|
|
|
|
-Show how to create a new metamodel
|
|
|
-And adding some concepts
|
|
|
+From then on, we can simply use the PetriNets metamodel as if it were a model.
|
|
|
+Again, the *model_add* operation can take an optional third parameter, specifying the textual representation of the model.
|
|
|
+We will go deeper into this in one of the next sections.
|
|
|
|
|
|
Instantiating a metamodel
|
|
|
-------------------------
|
|
|
|
|
|
-Show how we now create an instance of this newly created metamodel
|
|
|
+To actually define a metamodel, we need to add elements to it.
|
|
|
+These elements are tightly related to the metamodel of the model, being most likely *SimpleClassDiagrams*.
|
|
|
+In this language, there exist four major components: *Class*, *Association*, *Inheritance*, and attributes.
|
|
|
+
|
|
|
+Class
|
|
|
+^^^^^
|
|
|
+
|
|
|
+Creating a class is easy, and very similar to usual modelling.
|
|
|
+To define the *Place* concept, which can later on be used in models, we merely define this as a new class::
|
|
|
+
|
|
|
+ >>> instantiate("PetriNets", "Class", ID="Place")
|
|
|
+
|
|
|
+Note that here we must define an ID to get a sensible name to use.
|
|
|
+Of course, this ID does not need to be used, and the returned (randomized) ID can just as well be used.
|
|
|
+Nonetheless, if it is used, it becomes non-intuitive to use in future requests.
|
|
|
+In the instance of this newly created language, we can immediately use the new concept::
|
|
|
+
|
|
|
+ >>> instantiate("PetriNets", "Class", ID="Place")
|
|
|
+ >>> instantiate("my_pn", "Place")
|
|
|
+
|
|
|
+If the ID was not used, we must remember the ID that was provided to us::
|
|
|
+
|
|
|
+ >>> pn_place = instantiate("PetriNets", "Class")
|
|
|
+ >>> instantiate("my_pn", pn_place)
|
|
|
+
|
|
|
+While this is more reliable, it becomes difficult to refer to the concept in the future without any clue as to what it resembles.
|
|
|
+Later on, this can be solved with concrete syntax, in which case modellers can identify the concepts based on their visual representation.
|
|
|
+
|
|
|
+Association
|
|
|
+^^^^^^^^^^^
|
|
|
+
|
|
|
+Creating a link between two concepts is very similar.
|
|
|
+For example, to create the *P2T* association, merely create an instance of association, using the same remarks as before::
|
|
|
+
|
|
|
+ >>> instantiate("PetriNets", "Association", edge=("Place", "Transition"), ID="P2T")
|
|
|
+
|
|
|
+Inheritance
|
|
|
+^^^^^^^^^^^
|
|
|
+
|
|
|
+A useful concept is the use of inheritance, whereby the inheriting class can take on the responsibilities of the inherited class.
|
|
|
+To create this, simply instantiate the *Inheritance* association as usual.
|
|
|
+For example, it is possible to structure the *PetriNets* model (in an admittedly strange way) such that there is a generic concept of *NamedElement* and *Arc*::
|
|
|
+
|
|
|
+ >>> instantiate("PetriNets", "Class", ID="NamedElement")
|
|
|
+ >>> instantiate("PetriNets", "Class", ID="Place")
|
|
|
+ >>> instantiate("PetriNets", "Class", ID="Transition")
|
|
|
+ >>> instantiate("PetriNets", "Inheritance", edge=("Place", "NamedElement"))
|
|
|
+ >>> instantiate("PetriNets", "Inheritance", edge=("Transition", "NamedElement"))
|
|
|
+
|
|
|
+In the Modelverse, inheritance is possible between any two instances, including associations.
|
|
|
+To define the *Arc* relation, we act similar::
|
|
|
+
|
|
|
+ >>> instantiate("PetriNets", "Association", edge=("NamedElement", "NamedElement"), ID="Arc")
|
|
|
+ >>> instantiate("PetriNets", "Association", edge=("Place", "Transition"), ID="P2T")
|
|
|
+ >>> instantiate("PetriNets", "Association", edge=("Transition", "Place"), ID="T2P")
|
|
|
+ >>> instantiate("PetriNets", "Inheritance", edge=("P2T", "Arc"))
|
|
|
+ >>> instantiate("PetriNets", "Inheritance", edge=("T2P", "Arc"))
|
|
|
+
|
|
|
+Instantiating attributes
|
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
+
|
|
|
+Instantiating attributes is similar as it was before::
|
|
|
+
|
|
|
+ >>> attr_assign("PetriNets", "NamedElement", "name", "NamedElement")
|
|
|
+ >>> attr_assign("PetriNets", "Transition", "name", "Transition")
|
|
|
+ >>> attr_assign("PetriNets", "Place", "name", "Place")
|
|
|
+
|
|
|
+Note that this *name* attribute is in no way related to the *name* attributes at the *my_pn* level.
|
|
|
+Indeed, the *name* in *PetriNets* specifies the name of the class.
|
|
|
+Other attributes that can be set on classes are:
|
|
|
+
|
|
|
+1. *lower_cardinality* specifies the number (integer) of instances that must at least exist of this element.
|
|
|
+2. *upper_cardinality* specifies the number (integer) of instances that must at most exist of this element.
|
|
|
+3. *constraint* specifies additional constraints on the instances of this class.
|
|
|
+
|
|
|
+For associations, we have the same three attributes, augmented with:
|
|
|
+
|
|
|
+4. *source_lower_cardinality* specifies the number (integer) of incoming edges that are at least required for the target.
|
|
|
+5. *source_upper_cardinality* specifies the number (integer) of incoming edges that are at most required for the target.
|
|
|
+6. *target_lower_cardinality* specifies the number (integer) of outgoing edges that are at least required for the source.
|
|
|
+7. *target_upper_cardinality* specifies the number (integer) of outgoing edges that are at most required for the source.
|
|
|
+
|
|
|
+Defining attributes
|
|
|
+^^^^^^^^^^^^^^^^^^^
|
|
|
+
|
|
|
+Defining attributes is an important consideration in a metamodel.
|
|
|
+Up to now, our *Place* and *Transition* were just nodes without any attributes.
|
|
|
+A meaningful (marked) petrinet has tokens in places, and names for the places and transitions.
|
|
|
+To define these attributes, we must take two steps:
|
|
|
+
|
|
|
+1. Define the type that will be used (e.g., String, Integer)
|
|
|
+2. Define the name and type of the attribute (e.g., *tokens* is an Integer, *name* is a String)
|
|
|
+
|
|
|
+To define the type that we want to use, we must manually define it.
|
|
|
+While many other tools provide a set of primitives, which cannot be altered in any way, the Modelverse leaves all this up to the language engineer.
|
|
|
+If the language engineer wants the concepts of a *Natural*, for example, it is not required that the attribute is specified as an Integer, with an additional constraint on each and every attribute that is defined this way.
|
|
|
+It is possible to define a new attribute type as follows::
|
|
|
+
|
|
|
+ >>> instantiate("PetriNets", "SimpleAttribute", ID="Natural")
|
|
|
+
|
|
|
+But now, the attribute is constrained in no way: it is merely called Natural, but can even contain a string.
|
|
|
+The attribute type must be constrained::
|
|
|
+
|
|
|
+ >>> attr_assign_code("PetriNets", "Natural", "constraint", \
|
|
|
+ ... """
|
|
|
+ ... String function constraint(value : Element):
|
|
|
+ ... if (is_physical_integer(value)):
|
|
|
+ ... if (integer_geq(value, 0)):
|
|
|
+ ... return "OK"!
|
|
|
+ ... else:
|
|
|
+ ... return "Not a positive value"!
|
|
|
+ ... else:
|
|
|
+ ... return "Not a numeric value!"!
|
|
|
+
|
|
|
+Now, the *Natural* is correctly specified and can be used throughout the model.
|
|
|
+All uses of *Natural* will now make sure that the value is positive.
|
|
|
+This offers additional possibilities, such as defining complex attributes as if they were primitives, for example complex numbers.
|
|
|
+
|
|
|
+The next step is to define the attribute itself.
|
|
|
+This can be easily done as follows::
|
|
|
+
|
|
|
+ >>> define_attribute("PetriNets", "Place", "tokens", "Natural")
|
|
|
+
|
|
|
+For the name, *String* can be defined similarly (using *is_physical_string*).
|
|
|
+
|
|
|
+Constraints
|
|
|
+^^^^^^^^^^^
|
|
|
+
|
|
|
+A constraint specifies additional constraints on the instances of this model, which could not be expressed before.
|
|
|
+We distinguish between two types of constraints:
|