The class extension mechanism


The class extension mechanism


Introduction and specification

A number of mechanisms have been proposed to extend both user-defined and built-in Rexx classes. JLF has created a very detailed synopsys of these mechanisms.

The Rexx Parser implements a class extension mechanism suggested by Gil Barmwater, with some additions suggested by Josep Maria Blasco. Extending a class consists in adding or replacing some methods to that class.

To add a new method to a class, use the EXTENDS subkeyword:

::Method methodname Extends namespace:classname

The namespace qualifier is optional. If a method called methodname already exists in class classname, a SYNTAX condition is raised.

To replace an existing method, or to add a new method when no method called methodname exists, use the OVERRIDES subkeyword:

::Method methodname Overrides namespace:classname

The namespace qualifier is optional. You can specify one or none of EXTENDS or OVERRIDES, but not both in the same directive.

Extension methods can be placed anywhere after the prolog in the source program, that is, they do not need to be placed before the first class definition, if any.

Here is the complete and updated syntax diagram for the ::METHOD directive.

Methods

Implementation

EXTENDS and OVERRIDES options are recognized by the Parser only when the Experimental option has been turned on:

  /* allOptions contains the allowed options for the ::METHOD directive */
  If .Options.Experimental Then Do
    allOptions[] = "EXTENDS"
    allOptions[] = "OVERRIDES"
  End

The Rexx Parser stores the specified option, the namespace and the classname in the parse tree.

To make use of the enhanced parse tree, we need to transform our program into standard ooRexx. To this effect, we will be using a small compile module, classextensions.cls. Compile modules build over the predefined identity compiler module, compile.cls. The identity compiler module compiles a program into a perfect copy of itself. Specialized compiler modules add new compile methods to one of more of the Rexx Parser classes to provide particular compilation features in a very simple way. In our case, we only have to add a compile method to the Method.Directive class.

The task of our new compile method is to translate the new, experimental, Rexx features into standard ooRexx. We do that as follows:

  • When no EXTENDS or OTHERWISE phrase is present, the directive is copied without modification to the translated program.

  • Otherwise, the methodname is combined with the namespace (if present) and the classname to form a new name as follows:

      newname ::= methodname'<'separator'>'[namespace':']classname,

    where the separator is "+" for EXTENDS and "*" for OVERRIDES. The new name is then enclosed between quotes and substitutes methodname in the translated program.

  • The whole EXTENDS or OVERRIDES phrases are commented out.

The whole compilation process is governed by erexx.rex, the Experimental compiler. erexx compiles the source program and then adds a call to EnableExperimentalFeatures at the beginning of the program (we have to pass .METHODS as a parameter because of ooRexx bug no. 2037).

  -- Add a call to the enabler for experimental features
  If output[1][1] == "#" Then insertAt = 2 -- A shebang
  Else                        insertAt = 1
  output[insertAt] = "Call EnableExperimentalFeatures .Methods; "output[insertAt]

EnableExperimentalFeatures first inspects the .METHODS stringtable of the caller, to see if any of the methods names contain the substring "<+>" or "<*>". When such a method is found, the program

  • breaks the new method name into its components, to get back methodname, namespace and classname;
  • resolves the namespace, if present, and the classname, to get a Class object;
  • checks that an existing method called methodname does not exist in the Class object (for EXTENDS only);
  • uses the define method of the Class object to add a new method called methodname to the class; and
  • deletes the entry indexed by the new name in the .METHODS stringtable.

When processing of .METHODS has ended, a similar process is started with all of the classes defined in the caller package (classes method of the package class). In that case, methods are first added or replaced in the corresponding class, and later deleted in the class that defined them.

Example

The following is extends.rex, located in the tests/experimental directory:


Say ".Methods[Floating]=".Methods[Floating]
Say "Method A has disappeared from the .METHODS stringtable"
Say ".Methods[A]       =".Methods[A]
Say "The translated method name has also disappeared"
Say ".Methods['M<+>X:CLASS1']=".Methods['M<+>X:CLASS1']
Say "Class AClass does not have methods called M or O"
Say "M:" HasMethod(.AClass, "M")
Say "O:" HasMethod(.AClass, "O")
Say "Creating a CLASS1 object..."
o = .Class1~new
Say "Invoking CLASS1 method A..."
o~a
Say "Invoking CLASS1 method M..."
o~m
Say "Invoking CLASS1 method O..."
o~o

Exit

HasMethod: Signal On Syntax Name StrangeAPI

  throwAway = Arg(1)~method( Arg(2) )
  Return 1

StrangeAPI: Return 0

-- A floating method
::Method Floating

-- A floating extension method. It will be removed from .METHODS
::Method A Extends Class1
  Say "Class1 method A called"

::Class AClass

-- An extension method. It will be removed from class AClass
::Method M Extends X:Class1
  Say "Class1 method M called"

-- An overriding extension method. It will be removed from class AClass
::Method O Overrides Class1
  Say "Replaced method called"

::Requires "extended.cls" Namespace X

And here is extended.cls, located in the same directory.

::Class Class1 Public
::Method O
  Say "Original method"

When erexx extends is executed, this is the translated program that gets called:

Call EnableExperimentalFeatures .Methods;
Say ".Methods[Floating]=".Methods[Floating]
Say "Method A has disappeared from the .METHODS stringtable"
Say ".Methods[A]       =".Methods[A]
Say "The translated method name has also disappeared"
Say ".Methods['M<+>X:CLASS1']=".Methods['M<+>X:CLASS1']
Say "Class AClass does not have methods called M or O"
Say "M:" HasMethod(.AClass, "M")
Say "O:" HasMethod(.AClass, "O")
Say "Creating a CLASS1 object..."
o = .Class1~new
Say "Invoking CLASS1 method A..."
o~a
Say "Invoking CLASS1 method M..."
o~m
Say "Invoking CLASS1 method O..."
o~o

Exit

HasMethod: Signal On Syntax Name StrangeAPI

  throwAway = Arg(1)~method( Arg(2) )
  Return 1

StrangeAPI: Return 0

-- A floating method
::Method Floating

-- A floating extension method. It will be removed from .METHODS
::Method /*A*/"A<+>CLASS1" /*Extends*/ /*Class1*/
  Say "Class1 method A called"

::Class AClass

-- An extension method. It will be removed from class AClass
::Method /*M*/"M<+>X:CLASS1" /*Extends*/ /*X*//*:*//*Class1*/
  Say "Class1 method M called"

-- An overriding extension method. It will be removed from class AClass
::Method /*O*/"O<*>CLASS1" /*Overrides*/ /*Class1*/
  Say "Replaced method called"

::Requires "extended.cls" Namespace X

And this is the output from running the program:

.Methods[Floating]=a Method
Method A has disappeared from the .METHODS stringtable
.Methods[A]       =The NIL object
The translated method name has also disappeared
.Methods['M<+>X:CLASS1']=The NIL object
Class AClass does not have methods called M or O
M: 0
O: 0
Creating a CLASS1 object...
Invoking CLASS1 method A...
Class1 method A called
Invoking CLASS1 method M...
Class1 method M called
Invoking CLASS1 method O...
Replaced method called

Limitations

  • Because of the way we use to encode methodname, namespace and classname in a single string, we cannot handle methods that contain "<+>" or "<*>" as part of their name, or class names which contain a colon ":".
  • The classes method of the Rexx:Package class returns a stringtable indexed by the class names, which does not store the order in which these classes have been defined in the source package. This means that EnableExperimentalFeatures will process extension methods defined "inside" classes in the (undefined) order provided by the stringtable supplier.