I got a NSERC award to do this project on UML object constraint language in meta-modeling under the supervision of Prof. Hans Vangheluwe in summer, 2002. Here below is a brief description of the project.
The formalisms used in ATOM3, such as ER diagram, DEVS, PetriNet, are graphical languages. So they
are not sufficient to specify complicated constraints for the model. Textual constraints needs to be
added in.
AToM3 is a tool for multi-paradigm modelling under development at the Modelling, Simulation and
Design Lab (MSDL) in the School of Computer Science of McGill University. It is developed in close
collaboration with Prof. Juan de Lara of the School of Computer Science,
Universidad Autónoma de Madrid (UAM), Spain. AToM3 stands for A Tool for Multi-formalism and Meta-Modelling.
The two main tasks of AToM3 are meta-modelling and model-transforming. Meta-modelling refers to
the description, or modelling of different kinds of formalisms used to model systems (although
we have focused on formalisms for simulation of dynamical systems, AToM3's capabilities are not
restricted to these.) Model-transforming refers to the (automatic) process of converting, translating
or modifying a model of a given formalism, into another model that might or might not be in the same formalism.
In AToM3, formalisms and models are described as graphs. From a meta-specification (in the ER formalism)
of a formalism, AToM3 generates a tool to manipulate (create and edit) models described in the specified
formalism. Model transformations are performed by graph rewriting. The transformations themselves can thus
be declaratively expressed as graph-grammar models.
Some of the meta-models currently available are: Entity-Relationship, GPSS, Deterministic Finite state Automata,
Non-Deterministic Finite state Automata, Petri Nets, Data Flow Diagrams and Structure Charts. Typical model
transformations include model simplification (e.g., state reduction in Finite State Automata), code generation,
generation of executable simulators based on the operational semantics of formalisms, as well as behaviour-preserving
transformations between models in different formalisms.
At the time being, users of ATOM3, the model designers, need to write Python code fragments
to specified the constraints such that these code fragments will be run and tested as pre-conditions
during the building of the model and be tested as post-conditions when the model is saved.
But there are several drawbacks to specified constraints in this way. First of all, it requires the user
be comfortable with programming in Python. This will prevent a lot of potential user from actually
using ATOM3. Secondly, the user must know how the model info is stored in ATOM3 and
directly manipulates the internal data structures. And this is really bad, because it violate the rule
of object-oriented design, infomation and implementation hiding. If the designer of ATOM3 later
find a better format in which the model info to be stored, it adds extra burdens to either the user
or the designer, since there is no easy way to make the former Python codes work in the new
ATOM3 environment. Third, this will distract the users from focusing on the model ( or meta-model )
they are designing.
We do have the following benefits using OCL to specified constraints:
We will study the syntax and semantic of UML object constraint language in a formal way.
We will also define a mapping between the most declarative OCL constructs, like forAll,
and the Python codes which carry out the actual checking. Then, we will design and implement
an OCL to Python translator. Finally, we will integrated this translator into ATOM3 meta-modeling
environment.
A process of translating an OCL constraints to the equivalent Python codes that will carry out
the actual checking is illustrated below.
The usual way to do type check and code generation is showed figure 2.
A new way to do type check and code generation in ATOM3 with graph grammar is showed in figure 3
In this project, we followed the second path.
By choosing the second way, we entail more risks to the project. But we do have the following
reasons:
First, I carefully studied the OCL2.0 specification and part of the UML specification and gave a presentation on OCL. Second, I designed prototypes of meta-model of OCL expression with ER model in ATOM3 environment. Third, I implemented a series of prototypes of an OCL parser and test it with cycle test.
This presentation is on what UML object constraint language is and how OCL can be used in modeling and meta-modeling. You can refer to my presentation webpage for detailed info. Hopefully, I can update that HTML file with a nicer layout in the near future.
The abstract syntax of OCL is defined via UML class diagram. At the time being, ATOM3 does not support inheritance, an association between two superclasses in the UML class diagram will explode a m to n relationship in the ATOM3 ER diagram. If we build the meta-model for the whole OCL in one time, the model will easily become totally unmanageable. But my colleges are working on improving ATOM3, and hopefully in a few weeks, ATOM3 will support inheritance. So, the meta-model will also be built by prototyping.
Requirements: To build an abstract syntax tree representing the following OCL expressions: --No instance of G will be named as "stop" context G inv: self.name <> "stop"
Requirements: To build an abstract syntax tree representing the following OCL expressions: --Not any two instances of type G have the same name context G inv: G.allInstances()->forAll(e | self <> e implies e.name <> self.name) OR: G.allInstances()->forAll(e1,e2 | e1 <> e2 implies e1.name <> e2.name) The above is just syntactic sugar for: G.allInstances()->forAll(e1 | G.allInstances() ->forAll(e2 | e1 <> e2 implies e1.name <> e2.name))
In the ATOM3 meta-model, all the relation objects have two attributes: roleName
and roleName2 representing the role of the two ends of the association. Both of
them may be blank in the OCL expression meta-model, but I add them to simplify
the future code generation.
According to the abstract syntax of OCL, each OperationCallExp has a ordered list of arguments. We have two options to satisfy this requirement. One is to add an attribute to the Argument entity indicating the position of the current argument in the argument list. But this makes it harder to use graph grammar to do graph transformation. So, I chose the second option, modified prototype 2 a little bit to have this new prototype.
Since I had not got any experience in writing a parser before, I iteratively built up prototypes of my parser to work toward the goal.
In this prototype, we implemented an OCL parser which parses an OCL expression and builds
up an AST. It also outputs a python file which contains a model of the graphical appearance
of the AST. This model can be loaded in ATOM3 environment.
But even a syntactically correct OCL expression may be semantically non-sense. So, further
checking should and will be done via graph grammar after the abstract syntax tree is build up.
All these will be done in the next prototype.
An complete OCL grammar is given in the OCL2.0 specification. But I will have to modify it for the following two reasons:
The above figure shows the dependency of all the modules that compose prototype1 of the OCL parser. In the dependentcy chart:
Since we have only a small subset of OCL, we can only generate graphical AST of OCL expression in that subset.
But this prototype can actually parse the whole set of OCL. For debugging reason, the file ßcreen.out" which
contains info on how the OCL source is parsed.
Here is some OCL constraints examples:
-- This is the first example of ocl expression inv: self.name <> "stop" -- Second inv: not (self.mode = "idle") -- Third -- Not handled yet -- The source of an OperationCall must be specified explicitely -- inv: name() <> "stop" -- Fourth inv: a+b*c>d and c<>d implies (a+d)/e>c-a
After we parsed the above OCL constraints, we had an AST as showed below.
In this prototype, the parser will get input from a string rather than from a file. And we will
also interface Python and C language.
Since the ultimate goal is to integrate this OCL2Python translator into ATOM3, and the core of
ATOM3 is implemented in Python, the parser must be able to be called from within Python. But
the parser generated with Flex and Yacc is in C language. So, we must be able to extend Python with
C modules. On the other hand, we may like to embed in C some Python callbacks which will be called
during the parse of OCL constraints. Fortunately, the Python interpreter is also written in C, thus with
some extra efforts, we can jump back and forth between C and Python.
In the previous prototypes of the parser, the abstract syntax tree is implemented in C. But it is more
natural to implement the abstract syntax tree in an object-oriented language because the syntax
itself is defined in UML class diagram. And the tree will also be easier to traverse.
So, we implemented a Python module containing the data structures of the AST, and this module(absyn.py)
replaces the C module(absyn.c) in prototype1.
In this prototype, we also tested the parser with cycle test.
Here below is some sample codes I used in my test:
print '--------------test complicated Ocl expression with cycle test--------------' print '## test case1: OclExpression with letExp and ifExp' print "The oclSrc" oclSrc = "\ inv:\n\ Let signal:Signal = typedAst.target.type.lookupsignal(sgn) in ( \n\ if signal->isEmpty() then \n\ typedAst.sentSignal->isEmpty() \n\ else\ (typedAst.sentSignal->notEmpty() and \n\ typedAst.sentSignal.signal = signal) \n\ endif) " print oclSrc print for i in range(10): a = ocl_Parse(oclSrc) oclSrc = "inv:\n" + a.traverse() print "The output after 10 passes" print oclSrc print
And the output after we run the above test:
--------------test complicated Ocl expression with cycle test-------------- ## test case1: OclExpression with letExp and ifExp The oclSrc inv: Let signal:Signal = typedAst.target.type.lookupsignal(sgn) in ( if signal->isEmpty() then typedAst.sentSignal->isEmpty() else (typedAst.sentSignal->notEmpty() and typedAst.sentSignal.signal = signal) endif) The output after 10 passes inv: Let signal:Signal=typedAst.target.type.lookupsignal(sgn) in ( if (signal->isEmpty()) then typedAst.sentSignal->isEmpty() else (typedAst.sentSignal->notEmpty() and (typedAst.sentSignal.signal = signal)) endif )
Note: The test should be re-written with PyUnit. I just didn't have enough time to carefully construct the expected output string of OCL constraints.
Type check and code generation should be done next. Unfortunately, ATOM3 environment does not currently
support inheritance and ATOM3 is strongly typed. It would be a nightmere to design the transformation
rules for the type check and code generation even we had the logic to do so in our minds. So, we may not
do type check and code generation with graph grammar until the next version of ATOM3 which hopefully
will support inheritance. Before that, we will just write pure Python methods to traverse the AST,
build up symbol table and perform type check and code generation.
Since the OCL constraints does not stand alone, they are always stick to some models or meta-models we are
building, the biggist issue for type checking will be how to retrieve info from these models. Currently,
all the models built in ATOM3 will be sitting in some Python files. To get info from a model, it requires
ATOM3 to load the model at run time and query its run time environment. To make it easy to do, I will
discuss with Prof. Juan de Lara, and develop an API for achieving info from models.
But even we did not complete writing the whole OCL2Python translator, the experiences we gained in parsing the OCL constraints and creating an ATOM3 model of the AST(the graphical AST) can be ported to some other projects, such as processing a set of differential equations. For this instance, we build a meta-model in ATOM3 for differential equations, then we can graphically build a model for each differential equation and use constant folding and other mechanisms to transform these graphical models. But manually building models for, say, a hundred differential equations could be quite painful and time consuming. So, we can apply the parsing techniques and everything will be done quickly and automatically.
Great thanks to Prof. Hans Vangheluwe for his thorough guidance throughout the project.
I also want to thank all MSDL members for their excellent presentations on modelling and simulation. I really learn a lot from these guys.