Cirrus Overview
From The Oxygene Language Wiki
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
- Cirrus
- Introduction
- Overview
- Namespaces: Cirrus – Cirrus Values – Cirrus Statements
- Classes
- Interfaces
- Enums
- External Links:
- Prism Aspects to Help with Monobjc Development (RemObjects Blogs)
Area: Oxygene Language
Compiler version: Oxygene 5
Language Glossary — Keywords — Types — FAQ — How To