.
contact contact


Eloquence Classes

 
.
  Eloquence B.08.20 substantially enhances the user defined types by adding class methods, externally defined classes and automatic management of class methods.



Overview

A class consists of member variables, which hold the data, and methods, functions and subroutines which define the behavior. This terminology may be new to you but the functionality is not.

A class is the equivalent of a blueprint, it is the plan for building something. A class specifies both contents and behavior. Creating an instance is equivalent to the actual construction. Classes, like blueprints, may be used to build multiple structures (instances).

Eloquence classes are implemented by use of the Eloquence programming language.



Defining Classes

A class consists of member variables which hold the data and methods, functions or subroutines which define behavior.

A class is defined using Eloquence keywords TYPE and END TYPE. A class may be derived from another class. When a class is derived it inherits all the properties (both member variables and methods) of the base class and may extend or add to them. This is also called specializing since you add more specific properties to something more general.

Confused? Let's take a look at a simple analogy:
A vehicle is a general form. A car and a bicycle are more specialized vehicles. A vehicle is a base class while cars and bicycles are derived. Both the car and the bicycle have something in common: the vehicle.

TYPE Vehicle
  INTEGER Wheels
  INTEGER Max_speed
  ...
END TYPE

TYPE Car EXTENDS Vehicle
  DIM Engine$[..]
  DIM Steering_wheel$[..]
  ...
END TYPE

TYPE Bicycle EXTENDS Vehicle
  DIM Handle_bar$[..]
  ...
END TYPE
The Bicycle shares common information (number of wheels, maximum speed) but also adds information (handle bar). The Car also has wheels and adds an engine and a steering wheel.

A derived class may be used as if it were a base class since it includes the properties of the base class. For example, you can use the derived type to call functions or subprograms which were defined in the base type.


Defining a class in the program

A class definition is enclosed in the TYPE .. END TYPE keywords.
[EXPORT] TYPE Class_name [EXTENDS Base_class_name]
 ...
END TYPE
All variable declarations between the TYPE and END TYPE keywords are part of the new class (they are subsequently called member variables). The class name is specified as Class_name and follows the usual rules for variable names.

Class names do not conflict with other variables, function or subprogram names (they have their own name space) but a class instance may not have the same name as a class.

Please note that names of member variables are more restricted than other variables. The name of the member variable must be unique, you cannot use the same name for another member variable, an array or a method (function or subroutine) of that class. Also, when extending a class, a name used in the base class may not be re-defined in the derived class. On the other hand, member variables may be named identically to variables contained in other classes and in your program code. There is no conflict among the following Max_speed variables, for instance,

  • Max_speed=4 !program segment variable
  • My_car.Max_speed=100 !Car instance variable
  • My_bicycle.Max_speed=20 !Bicycle instance variable

If the EXTENDS keyword is present, the new class is derived from the given base class. Base_class_name is the class name of the base class. It is valid to define a base class after a derived type. It must be defined, however, before a derived type can be instantiated.

The example below defines the data type Tcontact, containing the member variables Id, Name$ and Phone$.

TYPE Tcontact
   INTEGER Id
   DIM Name$[30],Phone$[20]
END TYPE
The example below defines the data type Tcontact_ex. Since it is derived from the type Tcontact, it includes all member variables of the base type. In addition, it adds the new member variable Note$.
TYPE Tcontact_ex EXTENDS Tcontact
   DIM Note$[40]
END TYPE

If the EXPORT keyword is present, the class will be made available for use in other program segments (function or subroutine), called from the current program segment.

A class definition (type) is local by default and is only visible within the local program segment. When a type is EXPORTed, it becomes visible to subsequently called levels. When a program segment defines a type with a name which has been exported from a higher level, it will temporarily replace the exported type in the current function or subroutine. When a segment defines and EXPORTs a type with a name which has been exported from a higher level, the newly exported type permanently replaces the former type for all subsequently called lower level segments. If the type which is most recently defined is not EXPORTed, the previously exported type again becomes available to lower called segments.

Classes used in a COM statement must be "exported" in order to be available in a subprogram. Otherwise a runtime error message is issued.


Defining a class from the database schema

Data types may be defined with program lines and they may also be be defined from the database schema.
IN DATA SET data_set DEFINE TYPE Class_name [EXTENDS Base_class_name]
IN DATA SET data_set EXPORT TYPE Class_name [EXTENDS Base_class_name]
Where data_set is either a data set name or number and Class_name is the name you select for the data type. If the EXTENDS keyword is present, the new class is derived from the given Base_class_name. If the EXPORT syntax is used the class definition will be visible to subsequently called segments.

The example below defines the data type Tcustomer from the data set CUSTOMER.

DBOPEN(Db$,"",1,S(*))
...
IN DATA SET "CUSTOMER" DEFINE TYPE Tcustomer

Defining a class in another program file

Classes may be defined in separate program files. The LOAD CLASS statement may be used to explicitly load a class definition. Alternatively, the Eloquence class loader may be used to automatically load a class definition on reference.

Please refer to documentation on Class Loader for details.


Member variables

All variable declarations enclosed within the TYPE and END TYPE keywords are member variables for that class. They will be available to any object of that type.

Class member variables may be any native Eloquence variable type including arrays. They are then embedded in the resulting object and can be used like other program variables.

Names of member variables must be unique within the class. Using the same variable name again results in a run-time error. This also applies if variables have a different types (something otherwise allowed in the Eloquence language).

If a class is derived from another class, any added member variable must have a unique name which is not already used in any of its base classes.

Incidentally, member variables are optional. That is, you may define a class without any member variables at all. A class could, instead, merely define a set of methods.


Class methods

Functions or subroutines specific to a class are called methods. They are similar to normal functions or subroutines with the addition that their names include the instance name. Thus, you could reuse the same method name in different classes.

SUB Class:Method [ ( arguments ) ]
DEF FN Class:Method [ ( arguments ) ]
DEF FN Class:Method$ [ ( arguments ) ]
A method is called with the an implied a reference to its object. All member variables of the object are accessible in a class method using the THIS keyword to refer to the object.

Please note that it is undefined behavior to use the same name for a member function and a member subroutine. While this may not cause a problem with the current release, it might in a future version. It would be wise to avoid naming any member function with the same name as a member subroutine.



Using Objects

Creating an object

Since a class definition is merely a blueprint rather than a real object, it must be instantiated before it can be used. This can either be done prior to execution by using the COM or DIM statement or at runtime with the NEW statement.

DIM and COM statements:

COM Obj_name:Class_name
COM Obj_name AS Class_name
DIM Obj_name:Class_name
DIM Obj_name AS Class_name
The NEW statement:
NEW Obj_name:Class_name
NEW Obj_name AS Class_name
The Obj_name is the object name and the Class_name is the name of the data type. The object name and class name are either separated by a colon or the keyword AS.

The example below defines the objects Phone1 and Phone2. Each instantiates type Tphone.

DIM Phone1:Tphone
NEW Phone2 AS Tphone

In addition, the NEW STRUCT statement can be used to create a new, identical copy of an existing (previously instantiated) object.

NEW STRUCT Obj_name2=Obj_name
The example below creates a new object named "Clone". It will be an exact copy of the referenced object "Entry", including the values already assigned to its member variables.
DIM Entry:Tphone
Entry.Name$="Joe Sample"
Entry.Phone$="(202) 243 1440"
NEW STRUCT Clone=Entry


Using member variables

Member variables may be used like any other variables. A member variable is specified by joining the object name with the name of the member variable, separated by a period (commonly called a dot).
Phone1.Comment$="*Fancy Comment*"
PRINT Phone1.Comment$
When calling a class method the contents of the object are passed implicitly. To enhance readability and reduce chances for error, you may access the implied object with the THIS keyword.
SUB Class:Print
  PRINT THIS.Name$
  ...
When the method Print is called the THIS refers to an object of type Class and prints the value of the member variable Name$. This usage applies to instances of both base and derived classes.


The STRUCT keyword

The STRUCT keyword indicates that the object should be referenced as a whole. The example below prints all member variables of Entry.
PRINT STRUCT Entry

The STRUCT statement may also be used to copy the value of an object:

STRUCT A=B
When copying an object to another, both must be compatible.
  • If both objects are instantiated from the same data type, all member variables are copied.
  • If both objects have a common base type, the common member variables are copied.
The STRUCT keyword may be used with some statements to operate on the entire object instead of only one of its member variables. This is similar to the Array(*) notation in Eloquence which causes an operation on the whole array instead of a single element.

The following statements may be used with STRUCT:

  • PRINT
  • READ
  • PRINT #, READ #
  • IN DATA SET USE, IN DATA SET LIST
  • PACK USING
  • XPACK, XUNPACK


Passing an object to a subprogram or function

When calling a subprogram or function with a STRUCT argument, you may pass an object either anonymously or by specifying a class name. If a class name is specified the argument must be of a compatible type.
CALL Sub(STRUCT A)
...
SUB Sub(A {AS|:} TypeName)
SUB Sub(STRUCT A)
As with ordinary variables, types can either be passed by value or by reference. When passed by value, a copy is passed to the subroutine. When passed by reference, the object itself is available for the subroutine to permanently change. Passing by value is accomplished by enclosing the object name in parentheses, just as with an ordinary variable.

For example:

   TYPE Type
      INTEGER I
   END TYPE
!
   DIM Inst:Type
   Inst.I=0
   CALL Sub(STRUCT Inst)   ! Pass by reference, value of I will change
   PRINT Inst.I
   CALL Sub((STRUCT Inst)) ! Pass by copy, value of I will not change
   PRINT Inst.I
   STOP
!
   SUB Sub(STRUCT A)
      A.I=A.I+1
   SUBEND
A STRUCT may be passed to a subprogram or function in two manners:
  • When no type is specified in the SUBprogram or function definition, the struct is passed "anonymously" as an argument. Any type is valid and no validation is performed on the argument. Runtime type indicators (TYPEOF$, IS A) may be used to operate on it.

  • When a type is specified in the SUBprogram or function definition, only objects of the given (or derived) type are accepted, else a runtime error 8 is issued. The type must have been exported previously.
For example:
CALL X(STRUCT X)
SUB X(STRUCT Any)             ! Anonymous

CALL Y(STRUCT X)
SUB Y(Known:Tknown)           ! Passed object must be of type Tknown
                              ! or derived from it. Tknown must have
                              ! been previously defined.
SUB Y(Known AS Tknown)
A more complete example:
! Define type Tsample
   EXPORT TYPE Tsample
      INTEGER I
      DIM X$[20]
   END TYPE

! Instantiate object A with type Tsample
   DIM A AS Tsample

! Pass A to SUB
   CALL Sub1(STRUCT A)
   CALL Sub2(STRUCT A)
   STOP

! Sub1 requires a type Tsample (or a type derived from Tsample)
   SUB Sub1(A AS Tsample)
      PRINT A.I;A.X$
   SUBEND

! Sub2 accepts an anonymous object
   SUB Sub2(STRUCT A)
      PRINT A.I;A.X$
   SUBEND

Relative access to member variables and class methods

Member variables and class methods may be referred to without specifically naming their objects. This is done by defining their context. Once a context has been defined, instead of stating the full name, a leading dot (.) will be sufficient to indicate a member variable or a class method.

The WITH and END WITH keywords define a context.

WITH variable
 ...
END WITH
The argument to the WITH keyword specifies the name of an object (class instance) to use when relative access is desired. Any relative access in this context would then start from the point specified by the context. The variable must be a class.

The END WITH statement marks the end of a context. A context will also terminate implicitly if the surrounding environment ends. Such an environment might be defined by GOSUB / RETURN, FOR / NEXT, LOOP / END LOOP, etc. Good programming practice, however, requires the END WITH statement for clarity and human readability.

Accessing member variables and methods relative to a context can save a lot of typing and make your programs more concise.

Both examples below have the same result, printing the value of the member variable Name$ for the class instance Obj.

PRINT Obj.Name$
CALL Obj.Print

WITH Obj
  PRINT .Name$
  CALL .Print
END WITH


Access to member variables in a method

There are various options to access member variables or methods in a class method.
  • The THIS keyword may be used to indicate a member variable or method.
  • The SUPER keyword may be used to indicate a member method in a base class.
  • In a member function an implicit WITH THIS is performed. Consequently, a leading dot (relative access) may be used to indicate a member variable or method unless the context was changed.
TYPE T
   INTEGER I
END TYPE

SUB T:Method
  PRINT TYPEOF$(THIS)
  PRINT "I=";THIS.I
  PRINT "I=";.I
SUBEND

Returning an object

A function may return an object as demonstrated in the examples below. The first example uses a global type.
  EXPORT TYPE T
     INTEGER I
  END TYPE
  DIM A:T

  STRUCT A=FNX
  PRINT STRUCT A
  STOP

  DEF FNX
    NEW V:T
    V.I=1234
    RETURN STRUCT V
  FNEND
The second example uses a type defined in the function.
  DIM STRUCT A
  STRUCT A=FNX
  PRINT TYPEOF$(A);A.I
  STOP

  DEF FNX
     TYPE T
	INTEGER I
     END TYPE
     NEW V:T
     V.I=1234
     RETURN STRUCT V
  FNEND
The following examples demonstrates dynamically creating an object. The class definition is loaded as needed when a clspath is defined.
  STRUCT A=FNFactory("Sample")
  
  DEF FNFactory(Type$)
    COMMAND "NEW A AS "&Type$
    RETURN STRUCT A
  FN END

Runtime type identification

The TYPEOF$ function may be used to identify the type of an object.
X$=TYPEOF$(instance_name)
In this example, X$ will contain the type name of the given instance. This is useful when either passing an anonymous class or to determine if a type is derived from a particular class.

The IS A operator may be used to categorize an instance.

IF Obj_name IS A Class_name THEN ...
If the object is either of the specified class or derived from it, the IS A operator returns a nonzero (TRUE) value. For example:
   TYPE Tbase
      INTEGER A
   END TYPE
   TYPE Tderived EXTENDS Tbase
      INTEGER Q
   END TYPE
!
   DIM Derived:Tderived,Base:Tbase
   DISP "Base is of type ";TYPEOF$(Base)
   DISP "Inst is of type ";TYPEOF$(Derived)
   DISP "Base    is a Tbase    =";Base IS A Tbase
   DISP "Base    is a Tderived =";Base IS A Tderived
   DISP "Derived is a Tbase    =";Derived IS A Tbase
   DISP "Derived is a Tderived =";Derived IS A Tderived
   STOP

Even though TYPEOF$ and IS A are available in Eloquence, you are encouraged to use class methods instead of non-class independent functions or subprograms which, in turn, check for different types.


Data base integration

Classes may be used with your Eloquence databases. The IN DATA SET ... DEFINE TYPE statement will define a class from the data base schema at runtime. The PACK USING, IN DATA SET LIST, and IN DATA SET ... USE commands also support classes. For example:
DBOPEN(Db$,"",1,S(*))
...
IN DATA SET "CUSTOMER" DEFINE TYPE Tcust
NEW Cust:Tcust
IN DATA SET "CUSTOMER" USE STRUCT Cust
...
DBGET(Db$,"CUSTOMER",7,S(*),"@",Buf$,Key$)
...
Alternatively, a class may also be defined in the program and then used with the database:
   TYPE Tcust
      DIM No$[6]
      DIM Name$[30]
      ...
   END TYPE
   DIM Cust:Tcust
!
   DBOPEN(Db$,"",1,S(*))
   ...
   IN DATA SET "CUSTOMER" USE STRUCT Cust
   ...
   DBGET(Db$,"CUSTOMER",7,S(*),"@",Buf$,Key$)
   ...

XPACK and XUNPACK

The XPACK and XUNPACK statements can operate on an object. With these commands, the object is treated as a list of variables. The XPACK statement packs each member variable in a buffer. The XUNPACK statement unpacks to an object if it is passed as an argument to XUNPACK and if the name of the member variable matches the name in the buffer.



Example programs

This section demonstrates how user defined types might be used to enhance or replace the current usage of the COMmon block.
! common block
EXPORT TYPE Tglobal
  INTEGER Iv
  DIM Xv$[18]
  INTEGER A(1:2)
END TYPE
!
COM Global:Tglobal
READ STRUCT Global
DATA 123,"COMMON",1,2
!
PRINT "Global.Iv=";Global.Iv
PRINT "Global.Xv$=";Global.Xv$
PRINT "Global.A(*)=";Global.A(1);Global.A(2)
CALL Sub
STOP
!
SUB Sub
   COM Global:Tglobal
   PRINT "Global.Iv=";Global.Iv
   PRINT "Global.Xv$=";Global.Xv$
   PRINT "Global.A(*)=";Global.A(1);Global.A(2)
SUBEND


The following example "Custex" demonstrates the use of object-oriented techniques.

! This defines the Custex type. It contains a database name and the dialog name.
! Any member variables are availabe methods.
! 
     TYPE Custex
       DIM Dlg$[40]
       DIM Db$[40]
    END TYPE
! 
! We'll define two label types, each responsible for a particular
! layout. The second one has an additional member (a label counter).
! 
    TYPE Label
    END TYPE
    TYPE Label2 EXTENDS Label
       INTEGER Cnt
    END TYPE
! 
    ON ERROR CALL Error
! 
! Let the user choose which layout should be used,
! then invoke the Cutex Main method.
! 
    NEW A AS Custex
    POPUP BOX "[Label Type][Please choose the|Output layout][Line|Label|Cancel]",R
    SELECT R
    CASE 1
       NEW L AS Label
    CASE 2
       NEW L AS Label2
    CASE ELSE
       GOTO Done
    END SELECT
    CALL A.Main("SAMPLE",STRUCT L)
    DELETE A
Done:! 
    STOP
! 
! Handle any kind of failure
! 
    SUB Error
       OFF ERROR
       PRINT ERRM$
       PRINT ERRMSG$(ERRN)
       STOP
    SUBEND
! 
! This is Custy Main method.
! It knows nothing about how to print.
! It simply calls the Print method of the Out object.
! 
     SUB Custex:Main(Db$,STRUCT Out)
       INTEGER S(1:10)
       DIM Buf$[1024]
!       
       THIS.Db$="  "&Db$
       DBOPEN (THIS.Db$,"",9,S(*))
       DBASE IS THIS.Db$
       IN DATA SET "CUSTOMERS" DEFINE TYPE Cust_t
       NEW Cust AS Cust_t
       IN DATA SET "CUSTOMERS" USE STRUCT Cust
! 
       DLG LOAD "Custex.dlg"
       THIS.Dlg$="Cust"
       WHILE FNTHIS.Search
          DISP CHR$(146)&CHR$(175)&CHR$(185)&CHR$(148);  ! Clear screen
          REFRESH ON                             ! DLG to see screen buffer
          LOOP
             DBGET (THIS.Db$,"CUSTOMERS",5,S(*),"@",Buf$,"")
             EXIT IF S(1)
             CALL Out.Print(STRUCT Cust)
          END LOOP
       END WHILE
       DLG DEL THIS.Dlg$
    SUBEND
! 
! The Custex Search method handles the matchcode dialog.
! It uses the dialog name contained in the Class.
! 
     DEF FNCustex:Search
       DIM Mcode$[18]
       INTEGER Rc,S(1:10)
! 
       LOOP
          DLG DO THIS.Dlg$&".Mcode",Rc
          IF Rc=1 THEN RETURN 0
! 
          DLG GET .Dlg$&".Mcode.content",Mcode$
! 
          DBFIND (.Db$,"CUSTOMERS",2,S(*),"IMATCHCODE",Mcode$)
          IF S(1)=0 THEN RETURN 1
       END LOOP
    FNEND
! 
! This is the Print method of the first Label type
! responsible for layout #1.
! 
    SUB Label:Print(STRUCT R)
       WITH R
          PRINT USING "6A,X,30A";.Custno$,.Name1$
       END WITH
    SUBEND
! 
! This is the Print method of the second Label2 type
! responsible for layout #2.
! 
    SUB Label2:Print(STRUCT R)
       THIS.Cnt=THIS.Cnt+1
       WITH R
          PRINT "-------------------------------------"
          PRINT "| #";THIS.Cnt;" ";.Custno$;TAB(36);" |"
          PRINT "| ";.Name1$;TAB(36);" |"
          PRINT "| ";.Name2$;TAB(36);" |"
          PRINT "-------------------------------------"
       END WITH
    SUBEND

This is the accompanying Custex.dlg file:

Dialog Cust {
 .w = 40
 .h = 7
 .x = -1
 .y = -1
 .title = "Customer search"

 Statictext Prompt {
  .x = 2
  .y = 2
  .text = "Matchcode"
 }

 Edittext Mcode {
  .x = 2
  .y = 3
  .w = 18
 }

 Pushbutton OK {
  .x = 20
  .y = 5
  .w = 8
  .text = "OK"
  .rule = 2
 }

 Pushbutton Cancel {
  .x = 30
  .y = 5
  .w = 8
  .text = "Cancel"
  .rule = 1
 }
}


Error Messages

The following error messages are used in conjunction with Eloquence classes:

Code Error message
2 Memory allocation failed. The operating system failed to allocate memory.
12 Attempt to re-declare a variable or label.
13 Array dimensions not specified or undefined type.
900 Undefined base class.
901 Nested types are not supported.
902 Statement not allowed in type definition.
903 Illegal or incomplete type definition.
904 Duplicate member variable or method.
905 No such member variable. A member variable cannot be found.
906 No value returned. A method function has been called but it did not return a value (use CALL instead of FN).
907 Undefined base type.
908 Incompatible types.


 
 
.
 
 
  Privacy | Webmaster | Terms of use | Impressum Revision:  2011-04-02  
  Copyright © 2012 Marxmeier Software AG