Skip to main content

Defining Method and Trigger Generators

A method generator is a specific kind of method that generates its own runtime code. Similarly, a trigger generator is a trigger that generates its own runtime code.

This topic primarily discusses method generators, but the details are similar for trigger generators.

Introduction

A powerful feature of InterSystems IRIS® data platform is the ability to define method generators: small programs that are invoked by the class compiler to generate the runtime code for a method. Similarly a trigger generator is invoked by the class compiler and generates the runtime code for a trigger.

Method generators are used extensively within the InterSystems IRIS class library. For example, most of the methods of the %PersistentOpens in a new tab class are implemented as method generators. This makes it possible to give each persistent class customized storage code, instead of less efficient, generic code. Most of the InterSystems IRIS data type class methods are also implemented as method generators. Again, this gives these classes the ability to provide custom implementations that depend on the context in which they are used.

You can use method and trigger generators within your own applications. For method generators, a common usage is to define one or more utility superclasses that provide specialized methods for the subclasses that use them. The method generators within these utility classes create special code based on the definition (properties, methods, parameter values, etc.) of the class that uses them. Good examples of this technique are the %PopulateOpens in a new tab and %XML.AdaptorOpens in a new tab classes provided within the InterSystems IRIS library.

Basics

A method generator is simply a method of an InterSystems IRIS class that has its CodeMode keyword set to objectgenerator:

Class MyApp.MyClass Extends %RegisteredObject
{
Method MyMethod() [ CodeMode = objectgenerator ]
    {
        Do %code.WriteLine(" Write """ _ %class.Name _ """")
        Do %code.WriteLine(" Quit")
        Quit $$$OK
    }
}

When the class MyApp.MyClass is compiled, it ends up with a MyMethod method with the following implementation:

 Write "MyApp.MyClass"
 Quit

You can also define trigger generators. To do so, use CodeMode = objectgenerator in the definition of a trigger. The values available within your trigger are slightly different than those in a method generator.

How Generators Work

A method generator takes effect when you compile a class. The operation of a method generator is straightforward. When you compile a class definition, the class compiler does the following:

  1. It resolves inheritance for the class (builds a list of all inherited members).

  2. It makes a list of all methods specified as method generators (by looking at the CodeMode keyword of each method).

  3. It gathers the code from all method generators, copies it into one or more temporary routines, and compiles them (this makes it possible to execute the method generator code).

  4. It creates a set of transient objects that represent the definition of the class being compiled. These objects are made available to the method generator code.

  5. It executes the code for every method generator.

    If present, the compiler will arrange the order in which it invokes the method generators by looking at the value of the GenerateAfter keyword for each of the methods. This keyword gives you some control in cases where there may be compiler timing dependencies among methods.

  6. It copies the results of each method generator (lines of code plus any changes to other method keywords) into the compiled class structure (used to generate the actual code for the class).

    Note that the original method signature (arguments and return type), as well as any method keyword values, are used for the generated method. If you specify a method generator as having a return type of %IntegerOpens in a new tab, then the actual method will have a return type of %IntegerOpens in a new tab.

  7. It generates the executable code for the class by combining the code generated by the method generators along with the code from all the non-method generator methods.

The details are similar for trigger generators.

Values Available to Method Generators

The key to implementing method generators is understanding the context in which method generator code is executed. As described in the previous section, the class compiler invokes the method generator code at the point after it has resolved class inheritance but before it has generated code for the class. When it invokes method generator code, the class compiler makes the following variables available to the method generator code:

Variables Available to Method Generators
Variable Description
%code An instance of the %Stream.MethodGeneratorOpens in a new tab class. This is a stream into which you write the code for the method.
%class An instance of the %Dictionary.ClassDefinitionOpens in a new tab class. It contains the original definition of the class.
%method An instance of the %Dictionary.MethodDefinitionOpens in a new tab class. It contains the original definition of the method.
%compiledclass An instance of the %Dictionary.CompiledClassOpens in a new tab class. It contains the compiled definition of the class being compiled. Thus, it contains information about the class after inheritance has been resolved (such as the list of all properties and methods, including those inherited from superclasses).
%compiledmethod or %objcompiledmethod An instance of the %Dictionary class for the compiled method, for example,%Dictionary.CompiledMethodOpens in a new tab, %Dictionary.CompiledPropertyMethodOpens in a new tab or%Dictionary.CompiledIndexMethodOpens in a new tab. It contains the compiled definition of the method being generated.
%parameter An array that contains the values of any class parameters indexed by parameter name. For example, %parameter("MYPARAM"), contains the value of the MYPARAM class parameter for the current class. This variable is provided as an easier alternative to using the list of parameter definitions available via the %class object.
%kind or %membertype For member methods, the kind of class member that relates to this method, for example, a for property methods or i for index methods.
%mode The type of method, for example, method, propertymethod, or indexmethod.
%pqname or %member For member methods, the name of the class member that relates to this method.

Values Available to Trigger Generators

Like methods, triggers can be defined as generators. That is, you can use CodeMode = objectgenerator in the definition of a trigger. The following variables are available within the trigger generator:

Added Variables Available to Trigger Generators
Variable Description
%code, %class, %compiledclass, and %parameter See the preceding section.
%trigger An instance of the %Dictionary.TriggerDefinitionOpens in a new tab class. It contains the original definition of the trigger.
%compiledtrigger or %objcompiledmethod An instance of the %Dictionary.CompiledTriggerOpens in a new tab class. It contains the compiled definition of the trigger being generated.
%kind or %membertype For triggers, this is the value t.
%mode For triggers, this is the value trigger.
%pqname or %member The name of this trigger.

Defining Method Generators

To define a method generator, do the following:

  1. Define a method and set its CodeMode keyword to objectgenerator.

  2. In the body of the method, write code that generates the actual method code when the class is compiled. This code uses the %code object to write out the code. It will most likely use the other available objects as inputs to decide what code to generate.

The following is an example of a method generator that creates a method that lists the names of all the properties of the class it belongs to:

ClassMethod ListProperties() [ CodeMode = objectgenerator ]
{
    For i = 1:1:%compiledclass.Properties.Count() {
        Set prop = %compiledclass.Properties.GetAt(i).Name
        Do %code.WriteLine(" Write """ _ prop _ """,!")
    }
    Do %code.WriteLine(" Quit")
    Quit $$$OK
}

This generator will create a method with an implementation similar to:

 Write "Name",!
 Write "SSN",!
 Quit

Note the following about the method generator code:

  1. It uses the WriteLine method of the %code object to write lines of code to a stream containing the actual implementation for the method. (You can also use the Write method to write text without an end-of-line character).

  2. Each line of generated code has a leading space character. This is required because ObjectScript does not allow commands within the first space of a line. This would not be the case if our method generator is creating Basic or Java code.

  3. As the lines of generated code appear within strings, you have to be very careful about escaping quotation mark characters by doubling them up ("").

  4. To find the list of properties for the class, it uses the %compiledclass object. It could use the %class object, but then it would only list properties defined within the class being compiled; it would not list inherited properties.

  5. It returns a status code of $$$OK, indicating that the method generator ran successfully. This return value has nothing to do with the actual implementation of the method.

Specifying CodeMode within a Method Generator

By default, a method generator will create a code method (that is, the CodeMode keyword for the generated method is set to code). You can change this using the CodeMode property of the %code object.

For example, the following method generator will generate an ObjectScript expression method:

Method Double(%val As %Integer) As %Integer [ CodeMode = objectgenerator ]
{
    Set %code.CodeMode = "expression"
    Do %code.WriteLine("%val * 2")
}

Generators and INT Code

For method and trigger generators, it can be very useful to display the corresponding INT code in your Integrated Development Environment (IDE) after compiling the class. If using InterSystems Studio, from the View menu, select View Other Code. If using InterSystems ObjectScript extension for Visual Studio Code, right click the class file in the editor and select View Other.

Note that if the generator is simple enough to be implemented in the kernel, no .INT code is generated for it.

Effect on Subclasses

This section discusses topics specific to generator methods in subclasses of the class in which they were defined.

It is necessary, of course, to compile any subclasses after compiling the superclass.

Method Regeneration in Subclasses

When you subclass a class that defines generator methods, InterSystems IRIS uses the same compilation rules that are described earlier. InterSystems IRIS does not, however, recompile a method in a subclass if the generated code looks the same as the superclass generated code. This logic does not consider whether the include files are the same for both classes. If the method uses a macro that is defined in an include file and if the subclass uses a different include file, InterSystems IRIS would not recompile the method in the subclass. You can, however, force the generator method to be recompiled in every class. To do so, specify the method keyword ForceGenerate for that method. There may be additional scenarios where this keyword is needed.

Invoking the Method in the Superclass

If you need a subclass to use the method generated for the superclass, rather than a locally generated method, do the following in the subclass: define the generator method so that it just returns $$$OK, as in the following example:

ClassMethod Demo1() [ CodeMode = objectgenerator ]
{
    quit $$$OK
}

Removing a Generated Method

You can remove a generated method from a subclass, so that it cannot be invoked in that class. To do so, when you define the generator method in the superclass, include logic that examines the name of the current class and generates code only in the desired scenarios. For example:

ClassMethod Demo3() [ CodeMode = objectgenerator ]
{
    if %class.Name="RemovingMethod.ClassA" {
        Do %code.WriteLine(" Write !,""Hello from class: " _ %class.Name _ """")
    }
    quit $$$OK
}

If you try to invoke this method in any subclass, you receive the error <METHOD DOES NOT EXIST>.

Note that this logic is subtly different from that described in the previous section. If a generator method in a given class exists but has a null implementation, the method of the superclass, if any, is used instead. But if a generator method in a given class does not generate code for a given subclass, the method does not exist in that subclass and cannot be invoked.

See Also

FeedbackOpens in a new tab