Chapter 9
Classes and User Types

A user type (see 2.8), which is ZL’s minimal notion of a class, consists of two parts: a type, generally a struct, to hold the data for the class instance, and a module, which is collection of symbols for manipulating the data.

As an example,

  class C { int i;
            int f(int j) {return i + j;} };

expands to:

  user_type C {
    struct Data {int i;};
    associate_type struct Data;
    macro i (:this ths = this) {(*(C *)ths)..i;}
    macro f(j, :this ths = this) {f‘internal(ths, j);}
    int f‘internal(C * fluid this, int j) {return i + j;}

and creates the class C.

To allow user types to behave like classes, member-access syntax gets special treatment. For example, if x is an instance of the user type above, x.i calls the i macro in the C module, and it passes a pointer to x as the this keyword argument. This protocol allows x.i to expand to something that accesses the x field of the underlying struct, which can be done using the special syntax x..i. Thus, i effectively becomes a data member of x. Methods can similarly be defined. For example, x.f(12) calls the f macro with one positional parameter and the this keyword argument.

The default value for the this keyword argument is necessary to support the implicit this variable when data members and methods are accessed inside method definitions. The function f‘internal, which implements the f method, demonstrates this. (The ‘internal simply specifies an alternative namespace for the f symbol so that it does not conflict with the f macro.) The first parameter of the function is this, which puts the symbol into the local environment. When i is called inside the function body the this keyword argument is not supplied, since we are not using the member access form. Therefore, the this keyword argument defaults to the this specified as the default value, which binds to the this in the local environment. The fluid keyword (see 6.2.2) is necessary to make the this variable visible to the i macro; with normal hygiene rules, binding forms at the call site of a macro are invisible, as symbols normally bind to whatever is visible where the macro was defined.

User types can also be declared to have a subtype relationship. The declaration specifies a macro for performing both casts to and from the subtype. Subtypes are used to implement inheritance. For example the class:

  class D : public C { int j; };

expands to something like:

  user_type D {
    import C;
    struct Data {struct C::Data parent; int j;};
    associate_type struct Data;
    macro _up_cast (ths) {&(*ths)..parent;}
    macro _down_cast (other) {(D*)other;}
    make_subtype C _up_cast _down_cast;
    macro j (:this ths = this) {(*(D*)ths)..j;}

New symbols defined in a module are allowed to shadow imported symbols, so the fact that there is also a Data in C does not create a problem. Also, note that there is no need to redefine the data member and method macros imported from C, since the existing ones will work just fine. They work because the class macro makes sure that the ths macro parameter is cast to the right type before anything is done with it. For example, if y is an instance of the type D, then y.i expands to (*(C*)&y)..i. When ZL tries to cast &y to C*, the D::_up_cast macro is called and the expression expands to ((*&(*&y)..parent))..i, which simplifies to y..parent..i. Method calls expand similarly, except that the cast is implicit when the ths macro parameter is passed into the function.

If a class contains any virtual methods, then a vtable is also created. The macro that implements the method then looks up the function in the vtable instead of calling it directly. For example, if f was a virtual function in the class C, then the macro for f would look something like:

  macro f(j, :this ths = this) {_vptr->f(ths, j);}

where _vptr is a hidden member of the class that contains a pointer to the virtual table. The vtable is also a class, so to implement inheritance with virtual methods a child’s vtable simply inherits the vtable of the parent. To override a method, the constructor for the child’s vtable simply assigns a new value to the entry for the method’s function pointer.

Converted From LaTeX using TeX4ht. PDF Version