Fundamentally, a class is a record declaration that allows the definition of non-data fields (e.g., procedures, constants, and macros). The inclusion of other objects in the class definition dramatically expands the capabilities of a class. For example, with a class it is now possible to easily define an ADT because classes may include data and methods (procedures) that operate on that data.
The principle way to create an abstract data type in HLA is to declare a class data type. Classes in HLA always appear in the type
section and use the following syntax:
classname : class << Class declaration section >> endclass;
The class declaration section is very similar to the local declaration section for a procedure insofar as it allows const
, val
, var
, storage
, readonly
, static
, and proc
variable declaration sections. Classes also let you define macros and specify procedure, iterator,[133] and method prototypes (method declarations are legal only in classes). Conspicuously absent from this list is the type declaration section. You cannot declare new types within a class.
A method is a special type of procedure that appears only within a class. A little later you will see the difference between procedures and methods; for now you can treat them as being the same. Other than a few subtle details regarding class initialization and the use of pointers to classes, their semantics are identical.[134] Generally, if you don't know whether to use a procedure or method in a class, the safest bet is to use a method.
You do not place procedure/iterator/method code within a class. Instead you simply supply prototypes for these routines. A routine prototype consists of the procedure
, iterator
, or method
reserved word, the routine name, any parameters, and a couple of optional procedure attributes (@use
, @returns
, and external
). The actual routine definition (the body of the routine and any local declarations it needs) appears outside the class.
The following example demonstrates a typical class declaration appearing in the type
section:
TYPE TypicalClass: class Const TCconst := 5; Val TCval := 6; var TCvar : uns32; // Private field used only by TCproc. static TCstatic : int32; procedure TCproc( u:uns32 ); @returns( "eax" ); iterator TCiter( i:int32 ); external; method TCmethod( c:char ); endclass;
As you can see, classes are very similar to records in HLA. Indeed, you can think of a record as being a class that allows only var
declarations. HLA implements classes in a fashion quite similar to records insofar as it allocates sequential data fields in sequential memory locations. In fact, with only one minor exception, there is almost no difference between a record
declaration and a class
declaration that has only a var
declaration section. Later you'll see exactly how HLA implements classes, but for now you can assume that HLA implements them the same as it does records, and you won't be too far off the mark.
You can access the TCvar
and TCstatic
fields (in the class above) just like a record's fields. You access the const
and val
fields in a similar manner. If a variable of type TypicalClass
has the name obj
, you can access the fields of obj
as follows:
mov ( obj.TCconst, eax ); mov( obj.TCval, ebx ); add( obj.TCvar, eax ); add( obj.TCstatic, ebx ); obj.TCproc( 20 ); // Calls the TCproc procedure in TypicalClass. etc.
If an application program includes the class declaration above, it can create variables using the TypicalClass
type and perform operations using the mentioned methods. Unfortunately, the application program can also access the fields of the ADT with impunity. For example, if a program created a variable MyClass
of type TypicalClass
, then it could easily execute instructions like mov( MyClass.TCvar, eax );
even though this field might be private to the implementation section. Unfortunately, if you are going to allow an application to declare a variable of type TypicalClass
, the field names will have to be visible. While there are some tricks we could play with HLA's class definitions to help hide the private fields, the best solution is to thoroughly comment the private fields and then exercise some restraint when accessing the fields of that class. Specifically, this means that ADTs you create using HLA's classes cannot be "pure" ADTs because HLA allows direct access to the data fields. However, with a little discipline, you can simulate a pure ADT by simply electing not to access such fields outside the class's methods, procedures, and iterators.
Prototypes appearing in a class are effectively forward declarations. Like normal forward declarations, all procedures, iterators, and methods you define in a class must have an actual implementation later in the code. Alternately, you may attach the external
option to the end of a procedure, iterator, or method declaration within a class to inform HLA that the actual code appears in a separate module. As a general rule, class declarations appear in header files and represent the interface section of an ADT. The procedure, iterator, and method bodies appear in the implementation section, which is usually a separate source file that you compile separately and link with the modules that use the class.
The following is an example of a sample class procedure implementation:
procedure TypicalClass.TCproc( u:uns32 ); @nodisplay; << Local declarations for this procedure >> begin TCproc; << Code to implement whatever this procedure does >> end TCProc;
There are several differences between a standard procedure declaration and a class procedure declaration. First, and most obvious, the procedure name includes the class name (e.g., TypicalClass.TCproc
). This differentiates this class procedure definition from a regular procedure that just happens to have the name TCproc
. Note, however, that you do not have to repeat the class name before the procedure name in the begin
and end
clauses of the procedure (this is similar to procedures you define in HLA namespaces).
A second difference between class procedures and nonclass procedures is not obvious. Some procedure attributes (@use
, external
, @returns
, @cdecl
, @pascal
, and @stdcall
) are legal only in the prototype declaration appearing within the class, while other attributes (@noframe
, @nodisplay
, @noalignstack
, and @align
) are legal only within the procedure definition and not within the class. Fortunately, HLA provides helpful error messages if you stick the option in the wrong place, so you don't have to memorize this rule.
If a class routine's prototype does not have the external
option, the compilation unit (that is, the program or unit) containing the class declaration must also contain the routine's definition or HLA will generate an error at the end of the compilation. For small, local classes (that is, when you're embedding the class declaration and routine definitions in the same compilation unit) the convention is to place the class's procedure, iterator, and method definitions in the source file shortly after the class declaration. For larger systems (that is, when separately compiling a class's routines), the convention is to place the class declaration in a header file by itself and place all the procedure, iterator, and method definitions in a separate HLA unit and compile them by themselves.
[133] This text does not discuss iterators. See the HLA reference manual for details on this type of function.
[134] Note, however, that the difference between procedures and methods makes all the difference in the world to the object-oriented programming paradigm, hence the inclusion of methods in HLA's class definitions.