introduction

The purpose of the compiler is to generate code out of OCL expressions. The generated code evaluates the OCL constraint for a model instance, i.e. a program or data base, at runtime. Constraints formulated during analysis and design can then easily be checked in the implementation.

While the Java code generator that is being implemented makes certain restrictions to the OCL constructs that can be used, the other compiler modules are implemented as close to the OCL specification of UML 1.3 as possible.

modules

The compiler is constructed of modules with separated responsibilities to allow easy reuse or replacement. The architecture is flexible enough to adapt the existing modules to a variety of purposes, such as code generation for other languages or OCL interpretation.

module cooperation

parser

The parser is built using the tool SableCC. It complies to OCL 1.3, with slight changes where inconsistencies in the specification where found (e.g. the specification grammar lacks some literals and the possiblity to express calls to the Collection operation "iterate").

The parser can be examined with the demo application. Just enter an OCL expression into the text area on the tab "constraint" and click the "Parse" button.

semantic analysis

Semantic analysis consists of simple consistency checks and type checking. An example for a consistency check is that an expression must have an operation context (like context Person::getAge()) if and only if it has an pre or post stereotype. Besides assuring the correctness of an OCL expression with reference to some source of model information, the type checker adds type information to some abstract syntax nodes and to variables. This information is available to other modules.

The compiler automatically type-checks any OCL expression that is parsed. Since type checking requires model information, that will only be successful if the entered OCL expression complies to one of the UML diagrams supported by the implemented type information component unless another model information source is selected on the tab "model". The source for such model information can of course be adapted in the compiler by implementing the interfaces ModelFacade and Any (see interfaces).

As a result of type checking some of the abstract syntax tree's nodes display their type in the tree view (after the second '-'). Basically, all *Expression nodes (Expression, LogicalExpression, ...), all literals and nodes of type PostfixExpressionTail carry type information. Type checking is successful if the top expression node(s) are found to be of type Boolean. This is indicated by a green mark under "Type Check" on the tab "AST".

normalization

To pave the way for code generation, normalization steps can be applied to the abstract syntax tree. This reduces code generator complexity. Instead of defining one OCL normal form, a set of normalization steps can be fitted together to generate a normal form specific to the desired target language. Examples for normalization steps are:
Explicit qualification of names (age becomes self.age)
Insertion of iterators
Insertion of type information
Expansion of multiple iterators

The demo application normalizes the abstract syntax tree using a normalization pattern adapted for the Java code generator when the "Normalize" button on the tab "AST" is pressed. The "To Text" button then writes the normalized constraint back into the text field of tab "constraint".

code generator

A Java code generator is part of the OCL compiler. The Java code generated by it uses an class libary that represents the OCL type system. The following two tables show examples of generated code, including the original OCL expression.

context Company inv: manager.age >= 18
final OclAnyImpl tuddOclNode0=Ocl.toOclAnyImpl( Ocl.getFor(this) );
final OclAnyImpl tuddOclNode1=Ocl.toOclAnyImpl(tuddOclNode0.getFeature("manager"));
final OclInteger tuddOclNode2=Ocl.toOclInteger(tuddOclNode1.getFeature("age"));
final OclInteger tuddOclNode3=new OclInteger(18);
final OclBoolean tuddOclNode4=tuddOclNode2.isGreaterEqual(tuddOclNode3);

context Company inv: employees->isUnique(name)
final OclAnyImpl tuddOclNode0=Ocl.toOclAnyImpl( Ocl.getFor(this) );
final OclSet tuddOclNode1=Ocl.toOclSet(tuddOclNode0.getFeature("employees"));
final OclIterator tuddOclIter0=tuddOclNode1.getIterator();
final OclRootEvaluatable tuddOclEval0=new OclRootEvaluatable() {
  public OclRoot evaluate() {
    final OclString tuddOclNode2=Ocl.toOclString(Ocl.toOclAnyImpl(tuddOclIter0.getValue()).getFeature("name"));
    return tuddOclNode2;
  }
};
final OclBoolean tuddOclNode3=tuddOclNode1.isUnique(tuddOclIter0, tuddOclEval0);

The demo application allows to generate code by clicking the "Generate" button on the tab "Java".

library

The OCL class library implements the predefined OCL types and operations and allows to access application types through Java reflection (see the calls to getFeature in the example code above). It is adaptable in some respects to different code generators.

interfaces

There are three interfaces between the OCL compiler and its surrounding environment (e.g. a CASE tool).
OCL constraint: the compiler is invoked with a simple String
Model information: information from the UML model is necessary for type checking. This interface is described below.
Target code interface: The compiler will produce a String in the target language (Java, SQL, ...) and will pass this String to the CASE tool along with some other information (something like "this String is a postcondition for Person::getIncome()")

In the demo application, information for the first interface is entered into the text area. The second interface can be configured on the tab "model". By default it is implemented to return information for two default models. The demo application's implementation of the third interface displays the information on the tab "Java".

The following class diagrams shows the internal type representation classes of the type checker. To adapt this to a new source of model information (e.g. a CASE tool, or some "hard-wired" model like for the demo application) the interface ModelFacade has to be implemented to return objects fulfilling the Any interface which defines methods for access to attributes with and without parameters. An ModelFacade object is passed to the constructor of the TypeChecker class.

interfaces for model information access

An example of the communication between the OCL compiler's type checker and an implementation of these interfaces is shown in the following sequence diagram.

interfaces for model information access