Extensions to CodeWarrior Pascal
Marcel Achim, Pascal Reanimator



Extensions to CodeWarrior Pascal

Version 1.0, June 1997
by Marcel Achim

Metrowerks Corporation
2201 Donley Drive, Suite 310
Austin, TX 78758

Contents

Section 1 - Extensions to ObjectPascal

Section 2 - Extensions Providing Compatibility with Objective C

Overview

ObjectPascal was introduced about 15 years ago and hasn't been updated since then. The adoption of Objective-C by Apple has prompted the need to revise the current ObjectPascal language definition. Since Objective-C leaves most of the object oriented support to the runtime initializations, most functionalities cannot be performed by the current ObjectPascal runtime. This document presents extensions to ObjectPascal to provide semantical compatibility with Objective-C syntactic contructs and runtime. Other constructs are added, theyíre collected from other languages and the Extended Pascal Standard.

This document assumes that the reader has a basic knowledge of ObjectPascal and Objective-C semantics, and of object-oriented concepts.

Download this document in Word format





1. Extensions to ObjectPascal

Visibility of data fields

One of the aim of object-oriented programming is to provide data encapsulation and information hiding. To this respect, the scope of data fields can be restricted to the defining class (private), the class hierarchy (protected) or be made visible by everyone (public).

    DataFields  ::= [ Scope ] DataField ;
    Scope       ::= protected | private | public
    DataField   ::= id { , id } : TypeReference 

The default visibility is set to protected. Once the scope is set it remains active until it's changed or the end of the class declaration is reached. These directives are not treated as reserved words.

Class declaration layout

The ObjectPascal class declaration forced data fields to be grouped together and the methods declarations to be grouped together at the end of the class. This restriction is lifted, data fields and methods can be intermixed.

Class forward declarations

ObjectPascal automatically assumed that undeclared identifiers found in a type context were forward class declarations, that lead to problems with truely undeclared identifiers. The introduction of the forward directive didn't solve all the problems as TCL code assumes undeclared identifiers as forward class declarations, so the compiler kept this functionality until now. The default behaviour is set to report errors on undeclared identifiers, this can be changed using {$pragma forward_declarations on|off|reset}.

Return directive

A return statement is added to ObjectPascal, this statement jumps directly to the end of the routine. For functions, an expression associated with the directive sets the return value.

ReturnStat ::= return [ expression ] ;

Anonymous types

It is possible to declare an anonymous type in the interface part of a unit and leave the actual declaration of the type for the implementation. The corresponding type declaration in the implementation needs to be a pointer to some type or an object. The content of the opaque type is unavailable to any unit except to the defining implementation.

Schemata

A schema determines a collection of similar types. Types can be selected statically or dynamically from schemata.

Statically selected types are used as any other types are used.

Dynamically selected types subsume all the functionality of, and provide functional capability beyond, conformant arrays.

The allocation procedure NEW may dynamically select the type (and thus the size) of the allocated variable.

A schematic formal-parameter adjusts to the bounds of its actual-parameters.

The declaration of a local variable may dynamically select the type (and thus the size) of the variable.

The with-statement is extended to work with schemata.

Formal schema discriminants can be used as variant selectors.

Type
    Swidth = 0..1023;
    Sheight = 0..2047;

    Screen(width: Swidth;
           height: Sheight) = array [0..height, 0..width] of boolean;

    Matrix(M,N: integer) = array [1..M,1..N] of real;
    Vector(M: integer) = array [1..M] of real;

    Color = (red,yellow);
    Color_Map(formal_discriminant: color) = record
        case formal_discriminant of
            red: (red_field : integer);
            yellow : (yellow_field : integer);
            end;

Function bound : integer;

Var
    s : integer;

    begin
    write('How big?');
    readln(s);
    bound := s;
    end;

Var
    My_Matrix : Matrix(10,10);
    My_Vector : Vector(bound); { Notice the run-time expression! }
    Matrix_Ptr : ^Matrix;
    X,Y : integer;

begin
readln(x,y);
new(Matrix_Ptr,X,Y);
end

Unit initialization

It is possible to add an initialization section to a unit. This section can be used to perform intialization tasks for the unit. Thereís is no predefined order for the initialization bodies to be executed at run time.

UnitDeclaration ::= UnitHeading 
                        InterfacePart 
                            ImplementationPart 
                                [ begin Statements ] 
                                    end.

Complex data type

The Complex data type library is added ObjectPascal. Operations are defined in Complex.p.



2. Extensions providing compatibility with Objective C

Method calls and data fields

Access to methods and data fields is done essentially the same way as itís done in standard ObjectPascal.

Type 
    aclass = Object ( NSObject )
                public
                    size : integer;

                private
                    itsdata : integer;

                procedure set_its_data( p: integer);
                end;

Procedure aclass.set_its_data( p : integer );

    begin
    itsdata := p;
    end;

Var 
    aninstance : aclass;

begin
    ...
    aninstance.amethod(aninstance.size);
    ...
end;

Generic types

All classes using the ObjectiveC runtime share a common root ancestor class named NSObject, this class provides common functionalities shared by all classes and object instances.

Object instances are identified using a generic data type that is compatible with all class type definitions, this type is ID. A generic type exists also for object classes, this type is Class. The generic type denoting protocols is Protocol. The generic selector type is SEL. Note that these types are case insensitive and are not treated as keywords.

Type checking

Generic types relieve the compiler from performing static type checking. It is often necessary to perform type introspection at runtime to prevent problems, to this effect a method called isMemberOfClass, declared in NSObject, checks whether the object instance is an instance of a particular class.

Static type checking can only be performed when a class is explicitly declared. It doesnít change anything to the dispatching mecanism. It only permits the compiler to ensure at compile time that objects assigned are compatible, ie. that the assigned object instanceís type is part of the inheritance hierarchy. Using explicit class type for object instances allows you to use data fields directly without using accessor methods.

Type introspection

Object instances can reveal their type at run time. NSObject defines isMemberOfClass and isKindOfClass methods. The former checks whether the object is an instance of a class while the later checks onwership to an inheritance hierarchy.

Var 
    shape : Ellipsis;

begin
    if shape.isMemberOfClass(Ellipsis) then
        ...
    if shape.isKindOfClass(Shape) then
        ...
end


In this example, both methods would return true, knowing that Ellipse derives from Shape.

Class objects and methods

A method ordinarily performs actions on the data fields of the object instances that were created. In some cases it's necessary to have methods to perform class oriented tasks. Allocating a new instance is such an example. To declare a method to be specific to the class, append the 'class' directive after the method header in the class declaration as you would for 'override'. In the source code, class objects are represented by the class name. Class objects can also be represented by variables of the Class generic type. The class object is returned by the class method.

In the following example, the Rectangle class returns the class version number using a method inherited from the NSObject class :

Var 
    vers : integer;
    rec_class : Class;

begin
    ...
    rec_class := Rectangle.class;
    vers := Rectangle.version;
    writeln(vers);
    vers := rec_class.version;
    writeln(vers);
    ...
end

Allocating new objects

Allocation of new object instances is similar to ObjectPascalís allocation. NSObject declares an allocation method alloc and an initialization method init. To provide compatibility with ObjectPascal code, the standard procedures New and Dispose are kept to manage object instances existance. Calling New is equivalent to performing the alloc method. Since NSObject declares an init method, this might cause some name conflicts with existing ObjectPascal code, changing the method name will solve the problem.

Data fields of newly allocated object instances are zeroed except for the isa field denoting the class owning the instance.

Type 
    Shape = Object ( NSObject )
                haxis,
                vaxis : integer;
                procedure initShape (h,v: integer);
                end;
	Ellipsis = Object ( Shape )
                ....
                end;

Var 
    shape : Ellipsis;

begin
    ...
    New(shape);
    shape.initShape(30, 60);
    ...
end

Selectors

At run time, methods are represented by unique identifiers called selectors. These selector can be extracted at compile time using the SELECTOR directive.

Var 
    s : SEL;

begin
    ...
    s := Selector(initShape:integer:integer:);
    ...
end


Objective-C methods names are composed by the method name followed by the parameters types to ensure run time dectection of the right method. The names are separered by colons.

Categories

A category adds methods to a class declared elsewhere, it doesn't declare a new class. You can use categories as an alternative to subclassing. The methods declared by a category are added to the class it extends, there's no difference at runtime between methods declared in categories and methods declared in classes. The methods declared by the category are also inherited by all the class's subclass. A method cannot override a method declared in another category of the same class. A category cannot add data fields to a class. The data fields are all visible within the category, including private ones.

Category ::= category ( id )
                [ ProtocolList ]
                { MethodDeclarations }
                end


Protocols

Classes and categories declare methods that are identified to a particular class. Protocols on the other hand declare methods that are not associated to any class in particular, but which any class can implement, and perhaps many classes. A protocol is a list of method declarations. Any class can adopt a protocol and implement the methods the protocol declares. Note that it doesn't make any sense to declare a method in a protocol with the class or override attribute.

Protocol ::= protocol
                [ ProtocolList ]
                { MethodDeclarations }
                end


Protocol objects are refered to in source using a regular assignment to a variable of the generic Protocol type. There is no ambiguity using protocol both in a type declaration and as a variableís type definition because anonymous protocol declarations are not allowed.

Type 
    aprotocol = Protocol
            procedure yetanothermethod( p: integer);
            end;

Var 
    aproto : Protocol;

begin
    ...
    aproto := aprotocol;
    ...
end;


It is possible to declare a forward protocol. This is suitable to break cicular references between declarations.

    Type thatprotocol = Protocol; forward;

Protocol lists

Categories, classes and protocols can adopt protocols by naming them in their declarations. A class or category adopting a protocol must have this protocolís declaration visible either via a uses or a declaration in the that unit.

The adoption of a protocol by a class or category forces it to define all methods declared by the protocol.

Type 
    aconfClass = Object
                    <aprotocol>
                    somedata : integer;
                    ...
                    end;

Conformance to a protocol

A class is said to conform to a protocol if it adopts the protocol or inherits from a class that adopts it. An object instance of that class is said to conform to the protocol. Conformance to a protocol can be checked at runtime by calling the conformsTo method.

Var 
    proto : ID;

begin
    ...
    if (proto.conformsTo(aprotocol) then
        yetanothermethod(5);
    ...
end;


Static type checking with protocols can be enforced by deriving a type declaration from the generic ID type. This derivation ensures that only objects conforming to the protocol are assigned to the type.

Var 
    aproto   : ID <aprotocol>;
    anobject : aconfClass;

begin
    ...
    aproto := anobject;
    aproto.yetanothermethod(5);
    ...
end;

Remote messaging

Two objects residing in different address spaces can pass messages to each other throught the use of remote messaging. Interprocess communications between or cimmunication between separate threads of a single process is achieved throught use of the NSProxy and the NSConnection classes.

Passing parameters in a remote messaging context requires some adjustements to the parameter lists and method declarations essentially to reduce the overhead in passing reference parameters. To cut down on this overhead, itís possible to use one of three parameter modifiers: const specifies that the argument will be read only for the remote object, out specifies a parameter that will be returned by the remote object and var specifies that the argument will be modified by the remote object.

Passing objects as parameters requires the use of proxies. In situations where proxies are unneccessarily inefficient, a copy of the object being passed can instead be sent to the remote process so it can interact with the object directly in its own address space. Two modifiers are added to add this functionality: bycopy sends a copy of the object, byref uses the proxyís services, this is the default.

In situations where a sender does not wish to wait for a reply from the receiver before resuming processing, asynchronous messaging can be used. In order to specify that a method will be used to perform asynchronous messaging, it requires that all parameters are incomong parameters and the method declaration is followed by the oneway directive.

Type 
    clientClass = Object(SomeClass)
            procedure sendsomedata(const i: integer); oneway;
            procedure passobj(bycopy o : ID);
            end;


Copyright ©1997 Marcel Achim, Metrowerks Corporation.

Web page by Bill Catambay
Updated: 20-June-1997