Class Metaobjects

Class metaobjects are the most significant metaobjects of the MOP. Although other metaobjects only represent a structural aspect of the program, the class metaobjects not only represent such a structural aspect but also allow programmers to define a subclass and alter the behavior of the program.

The default class for the class metaobjects is Class, which provides member functions for accessing the class definition. To alter a behavioral aspect of the class, the programmer define a subclass of Class that overrides virtual functions controlling source-to-source translation involved with the class.



Public Members


Selecting a Metaclass

In general, the class of a metaobject is selected by the metaclass declaration at the base level. For example:

metaclass PersistentClass Point;

declares that the metaclass for Point is PersistentClass. This means that the compiler instantiates PersistentClass and makes the instantiated object be the class metaobject representing Point. Since PersistentClass is a regular C++ class but its instance is a class (metaobject), PersistentClass is called ``metaclass''. This might look weird, but regard a class metaobject as being identical to the class.

Programmers may specify a metaclass in a way other than the metaclass declaration. The exact algorithm to select a metaclass is as described below:

  1. The metaclass specified by the metaclass declaration.
  2. The metaclass specified by the keyword attached to the class declaration if exists.
  3. Or else, the metaclass for the base classes. If they are different, an error is caused.
  4. Otherwise, the default metaclass Class is selected.

Programmers may specify a metaclass by a user-defined keyword. For example,

distribute class Dictionary { ... };

This means that the metaclass associated with the user-defined keyword distribute is selected for Dictionary. If there is also a metaclass declaration for Dictionary, then an error occurs.

Although the default metaclass is Class, programmers can change it to another metaclass:

Constructor

Class metaobjects may receive a meta argument when they are initialized. The meta argument is specified by programmers, for example, as follows:

metaclass PersistentClass Point("db", 5001);

The Ptree metaobject ["db" , 5001] is a meta argument to the class metaobject for Point. Also, the programmers may specify a meta argument in this syntax:

distribute("db", 5001) class Dictionary { ... };

The user-defined keyword distribute can lead a meta argument. The class metaobject for Dictionary receives the same meta argument that the class metaobject for Point receives in the example above.

The member function InitializeInstance() on Class (and its subclasses) is responsible to deal with the meta argument. By default, the meta argument is simply ignored:

Another constructor is provided for the programmers to produce a new class. This is an example of the use of this constructor:

void MyClass::TranslateClass(Environment* e)
{
    Member m;
    Class* c = new Class(e, "Bike");
    LookupMember("move", m);
    c->AppendMember(m);
    AppendAfterToplevel(e, c);
}

A new class named Bike is created, a member named move is retrieved from the class represented by this class metaobject, and the retrieved member is copied to that new class. The created class Bike is then inserted in the source code after the declaration of the class represented by this class metaobject.

Introspection

Since a class metaobject is the meta representation of a class, programmers can access details of the class definition through the class metaobject. The followings are member functions on class metaobjects. The subclasses of Class cannot override them.

Translation

Class metaobjects control source-to-source translation of the program. Expressions involving a class are translated from OpenC++ to C++ by a member function on the class metaobject.(In the current version, the translated code is not recursively translated again. So the metaobjects have to translate code from OpenC++ to C++ rather than from OpenC++ to (less-extended) OpenC++. This limitation will be fixed in future.) Programmers may define a subclass of Class to override such a member function to tailor the translation.

The effective class metaobject that is actually responsible for the translation is the static type of the object involved by the expression. For example, suppose:

class Point { public: int x, y; };
class ColoredPoint : public Point { public: int color; };
        :
Point* p = new ColoredPoint;

Then, an expression for data member read, p->x, is translated by the class metaobject for Point because the variable p is a pointer to not ColoredPoint but Point. Although this might seem wrong design, we believe that it is a reasonable way since only static type analysis is available at compile time.

Class Definition

The class definition is translated by TranslateClass(). For example, if a member function f() is renamed g(), the member function TranslateClass() should be overridden to be this:

void MyClass::TranslateClass(Environment* e)
{
    Member m;
    LookupMember("f", m);
    m.SetName(Ptree::Make("g"));
    ChangeMember(m);
}

First, the member metaobject for f() is obtained and the new name g() is given to that member metaobject. Then, this change is reflected on the class by ChangeMember(). The class Class provides several member functions, such as ChangeMember(), for translating a class definition. Programmers can override TranslateClass() to call these functions and implement the translation they want.

The implementation of member functions is translated by TranslateMemberFunction(). For example,

void Point::Move(int rx, int ry)
{
    x += rx;
    y += ry;
}

To translate this function implementation, the compiler calls TranslateMemberFunction() on the class metaobject for Point. The arguments are an environment and a member metaobject for Move(). If this member metaobject is changed by member functions such as SetName(), the change is reflected on the program. Unlike class declarations, no explicit function call for the reflection is not needed. For example,

void MyClass::TranslateMemberFunction(Environment* env, Member& m)
{
    m.SetFunctionBody(Ptree::Make("{}"));
}

This customizes the member function so that it has an empty body.

Expressions

Class metaobjects also control the translation of expressions. An expressions, such as member calls, are translated by one of the following virtual functions on the class metaobject involved with the expression. For example, if the expression is a member call on a Point object, it is translated by the class metaobject for the Point class.

The MOP does not allow programmers to customize array access or pointer operations. Suppose that p is a pointer to a class A. Then the class metaobject for A cannot translate expressions such as *p or p[3]. This design decision is based on C++'s one. For example, C++'s operator overloading on [] does not change the meaning of array access. It changes the meaning of the operator [] applied to not an array of objects but an object.

If the MOP allows programmers to customize array access and pointer operations, they could implement an inconsistent extension. For example, they want to translate an expression p[2] into p->get(2), where p is a pointer to a class X. Then, what should this expression *(p + 2) be translated into? Should the MOP regard it as an array access or a pointer dereference? Because C++ provides strong pointer arithmetic, designing an interface to consistently customize array access and pointer operations is difficult.

The class Class also provides functions for translating expressions and actual arguments. These functions are not overridden but rather called by other functions shown above:

Registering Keywords

To make user-defined keywords available at the base level, programmers must register the keywords by the static member functions on Class shown below. Those member functions should be called by Initialize().

The translation of the registered keyword for the while-, the for-, or the closure-style statement is the responsibility of the class metaobject. It is processed by TranslateUserStatement() and TranslateStaticUserStatement().

Initialization and Finalization

The MOP provides functions to initialize and finalize class metaobjects:

Inserting Statements

Class metaobjects can not only replace expressions but also insert statements into the translated source code:

Command Line Options

Class metaobjects can receive command line options. For example, if the user specify the -M option:

% occ -Mclient -Mmode=tcp sample.cc

Then the class metaobjects can receive the command line options client and mode (the value is tcp) by the following functions:

Error Message

The following functions reports an error that occurs during the source-to-source translation.

Metaclass for Class

Since OpenC++ is a self-reflective language, the meta-level programs are also in OpenC++. They must be compiled by the OpenC++ compiler. Because of this self-reflection, metaclasses also have their metaclasses. The metaclass for Class and its subclasses must be Metaclass. However, programmers do not have to explicitly declare the metaclass for their metaclasses because the subclasses of Class inherit the metaclass from Class.

Metaclass makes it easy to define a subclass of Class. It automatically inserts the definition of MetaclassName() of that subclass and also generates house-keeping code internally used by the compiler.

Since Metaclass is a subclass of Class, its metaclass is Metaclass itself.

the instance-of relationship


[First | Prev | Next]