Cirrus Overview

From The Oxygene Language Wiki

Jump to:navigation, search

This is a Language topic
Feel free to add your notes to this topic below.



What is Cirrus

Cirrus is a way of interacting with the compiler during the compiling process by the use of aspect attributes or method aspects. Aspect attributes are like regular .NET attributes with an aspect: prefix. Cirrus consists of a small library which contains interfaces and simple classes but no logic, and the implementation in the compiler library. Users use the Cirrus library so they don't have to depend on a specific version of the compiler since the Cirrus library does not change versions as often as the compiler does. A Cirrus attribute is always defined in a different assembly than what it's used from, so the compiler can execute the code in the aspect library as part of the compile process. Since Cirrus is part of the compiler, it does the weaving during the compile process, not after it, making it possible to interact with the compiler on multiple levels, for example by introducing new members on classes or even new classes and letting the user access those members directly, as if they were defined in code.

Aspect Attributes

How does it work

When compiling, the compiler will try to load the library containing an aspect when it's referenced with the [aspect: ] syntax and create an instance of the actual aspect attribute class. It will pass the parameter values as they are defined by the user and after the constructor it will assign the named parameters to properties by those names on the aspect.

Callbacks

Attribute classes in Cirrus have to implement the interfaces they want callbacks for. There are several interfaces:

Interface Description
ITypeInterfaceDecorator

When implemented by an aspect attribute, it will trigger after the interface section of the file has been parsed and all types and members have been resolved. Implement the HandleInterface method of this interface to add new members to types. Type decorators will only be called when the aspect is applied to a type or to the assembly.

ITypeImplementationDecorator

When implemented by an aspect attribute, it will trigger after the implementation section of the file has been parsed and all types and members have been resolved and the class is ready to be emitted to the output file. Implement the HandleImplementation method of this interface to modify members of the type or to validate the type. Type decorators will only be called when the aspect is applied to a type or to the assembly.

IFieldInterfaceDecorator

When implemented by an aspect attribute, it will trigger after the field has been parsed and has been fully resolved. Implement the HandleInterface method of this interface to check or change the type of the field. Field decorators will only be called when the aspect is applied to a field, type or to the assembly.

IMethodInterfaceDecorator

When implemented by an aspect attribute, it will trigger after the method has been parsed and has been fully resolved. Implement the HandleInterface method of this interface to validate or change the methods arguments, add attributes or new members to the parent class. Method decorators will only be called when the aspect is applied to a method, type or to the assembly.

IMethodImplementationDecorator

When implemented by an aspect attribute, it will trigger after the method has been implemented, when the body of the method has been completely processed and is available for changes . Implement the HandleImplementation method of this interface to modify the method body, add extra checks or remove it completely. Method decorators will only be called when the aspect is applied to a method, type or to the assembly.

IPropertyInterfaceDecorator

When implemented by an aspect attribute, it will trigger after the property has been parsed and fully resolved. Implement the HandleInterface method of this interface to validate or change the properties arguments, read/write member, add attributes or new members to the parent class. Property decorators will only be called when the aspect is applied to a property, type or to the assembly.

IPropertyImplementationDecorator

When implemented by an aspect attribute, it will trigger after the property has been implemented and the body for the read and write members has been generated, and are available for changes. Implement the HandleImplementation method of this interface to modify the property read and write method body, add extra checks or remove it completely. Property decorators will only be called when the aspect is applied to a property, type or to the assembly.

IEventInterfaceDecorator

When implemented by an aspect attribute, it will trigger after the event has been parsed and has been fully resolved. Implement the HandleInterface method of this interface to validate or change the event type, add, remove or raise member. Event decorators will only be called when the aspect is applied to an event, type or to the assembly.

IEventImplementationDecorator

When implemented by an aspect attribute, it will trigger after the event has been implemented and the body for the add, remove and raise members has been generated (where applicable), and are available for changes. Implement the HandleImplementation method of this interface to modify the event, add, remove and raise method body, add extra checks or remove it completely. Event decorators will only be called when the aspect is applied to an event, type or to the assembly.

IAutoGenMethodImplementationDecorator

This interface is automatically implemented when using the "aspect:AutoInjectIntoTargetAttribute" on a class member on the attribute. It's called for all the above events, but only once for the AutoGenHandleImplementation and once for the AutoGenHandleInterface method per attribute instance. Note that these methods should not be implemented at all when using the aspect, as that will create conflicts.

Services

All the Handle* call back methods include a Services instance that can be used to find and create new types, and an instance of the member they can change. The services interface also has methods to emit errors, hints and warnings to the compiler. The GetType api will raise an exception on failure, the FindType api will return nil, both require the full type name, including the `count for generic types, like "System.Collections.Generic.IList`1", the ITypeDefinition and IMemberDefinition interfaces have a FindContextType/GetContextType method that allows passing "array of " and adding generic parameters to classes to instantiate them.

Values and Statements

The Cirrus library contains a class for every kind of value and statement the compiler supports. These classes are defined in the "RemObjects.Oxygene.Cirrus.Values" and "RemObjects.Oxygene.Cirrus.Statements" namespaces and they are used to replace or modify values (like the read or write expression of a property) or replace the body of a method. There is one special kind of statement class called the PlaceHolderStatement, when replacing the body of a method the original body will be inserted in the location of the place holder.

Quoting and Unquoting

While the statement and value classes provide a very powerful way of accessing and replacing code in the compiler, they require rather a lot of code to be written to do simple things. That's why the compiler supports a special kind of anonymous method that will automatically be converted to Statement and Value trees by the compiler. The compiler will automatically do this conversion when the target of the anonymous method is a StatementDelegate. One of the places this delegate call is used is on the SetBody method for the IMethodDeclaration class:

aMethod.SetBody(Services, method begin
      try
        Aspects.OriginalBody;
      finally
        LogMessage('Exiting '+unquote<string>(new DataValue(aMethod.Name)));
      end;
    end);

When the compiler encounters this, it will generate a new BeginStatement, with a TryStatement, a PlaceHolderStatement and ProcValue to call the LogMessage method. In this method you'll notice the use of the unquote compiler function. If I had used "aMethod.Name" directly here, I would have gotten a compiler error, since the method whose body will be replaced won't have a parameter called "aMethod", which is the parameter for the aspect handler. The unquote compiler function will insert a new instance of DataValue which contains the value of aMethodName – the method name as string – in the tree instead, which can be safely inserted here. The generic parameter for this method is what the compiler will use as the type of the expression. Unquote can also be used to insert raw statements into the tree; to do this, use unquote(SomeStatement) as a standalone command.

Special Functions inside aspect anonymous method bodies

The "Aspects" class is a class with methods that are treated specially by the compiler when it converts an anonymous method body to a statement tree. It has several useful methods to make writing aspects easier:

Method Description
ClassName Returns a string containing the current methods parent class. Equivalent to unquote<string>(aMethod.GetOwnerTypeName).
MethodName Returns a string containing the current methods name. Equivalent to unquote<string>(aMethod.Name).
GetParameters Returns an array of objects containing all parameters for the current method. Useful when logging method parameters. Equivalent to unquote<array of object>(aMethod.GetParameterArrayValue).
OriginalBody Returns a new PlaceHolderStatement, equivalent to unquote(new PlaceHolderStatement()).

RequireLocal<T>

RequireLocal

The result of these calls has to be assigned directly to a newly created local like:

 var aName := Aspects.RequireLocal;

The variable name will be the same name this local was named in the original code, if a type is specified for T, the type will be required to match, otherwise Cirrus will treat it as an Object.

RequireParameter<T>

RequireParameter

The result of these calls has to be assigned directly to a newly created local like:

 var aUsername := Aspects.RequireParameter<string>;

The variable name will be the name this parameter was named in the original code, if a type is specified for T the type, will be required to match, otherwise Cirrus will treat it as an Object.

RequireResult<T>

RequireResult

The result of these calls has to be assigned directly to a newly created local like:

 var aName := Aspects.RequireResult;

The variable name does not matter for the result, however this will fail with an error when the function has no result. If a type is specified for T, the type will be required to match, otherwise Cirrus will treat it as an Object.

Method Call Aspects

Method Call Aspects are aspects that change the behavior of a call to a specific method (inside or outside the target assembly), all methods on a specific type or all methods on all types in a given namespace. A method aspect implements the IMethodCallDecorator interface. The ProcessMethodCall method is called each time the target method(s) are called and will allow the implementation to change the call in any way.

See Also


Oxygene-48.png

Area: Oxygene Language
Compiler version: Oxygene 5

Language GlossaryKeywordsTypesFAQHow To

Navigation
Areas
More
Toolbox