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.
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
EXTENDSorOTHERWISEphrase 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
"+"forEXTENDSand"*"forOVERRIDES. The new name is then enclosed between quotes and substitutes methodname in the translated program.The whole
EXTENDSorOVERRIDESphrases 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
EXTENDSonly); - uses the
definemethod 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
.METHODSstringtable.
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
classesmethod of theRexx:Packageclass 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 thatEnableExperimentalFeatureswill process extension methods defined "inside" classes in the (undefined) order provided by the stringtable supplier.
