151 lines
5.2 KiB
OpenEdge ABL
151 lines
5.2 KiB
OpenEdge ABL
[Methods::] Methods.
|
|
|
|
General support for something approximating method calls.
|
|
|
|
@h Method sets.
|
|
This section provides a very rudimentary implementation of method calls,
|
|
ordinarily not available in C, but doesn't pretend to offer the full
|
|
functionality of an object-oriented language.
|
|
|
|
Instead, it's really intended for protocol-based coding patterns. Suppose that
|
|
we have objects of several different structure types, but all of them can
|
|
serve a given purpose -- say, all of them contribute an adjective to the
|
|
Inform language. What we want is the ability to take a pointer, which might be
|
|
to an object of any of these types, and to tell the object to do something, or
|
|
ask it a question.
|
|
|
|
Alternatively, we may have a situation where there are multiple objects of the
|
|
same type which each represent a different way of doing something: for
|
|
example, in the Inweb source code, each different supported programming
|
|
language is represented by an object. These objects need to encapsulate all
|
|
the ways that one language differs from another, and they can do that by
|
|
providing "methods".
|
|
|
|
@ A "method set" is simply a linked list of methods:
|
|
|
|
=
|
|
typedef struct method_set {
|
|
struct method *first_method;
|
|
CLASS_DEFINITION
|
|
} method_set;
|
|
|
|
method_set *Methods::new_set(void) {
|
|
method_set *S = CREATE(method_set);
|
|
S->first_method = NULL;
|
|
return S;
|
|
}
|
|
|
|
@h Declaring methods.
|
|
Each method is a function, though we don't know its type -- which is why we
|
|
resort to the desperate measure of storing it as a |void *| -- with an ID
|
|
number attached to it. IDs should be from the |*_MTID| enumeration set.
|
|
|
|
@e UNUSED_METHOD_ID_MTID from 1
|
|
|
|
@ The type of a method must neverthess be specified, and we do it with one
|
|
of two macros: one for methods returning an integer, one for void methods,
|
|
i.e., those returning no value.
|
|
|
|
What these do is to use typedef to give the name |X_type| to the type of all
|
|
functions sharing the method ID |X|.
|
|
|
|
@d INT_METHOD_TYPE(id, args...)
|
|
typedef int (*id##_type)(args);
|
|
@d VOID_METHOD_TYPE(id, args...)
|
|
typedef void (*id##_type)(args);
|
|
|
|
=
|
|
INT_METHOD_TYPE(UNUSED_METHOD_ID_MTID, text_stream *example, int wont_be_used)
|
|
|
|
@h Adding methods.
|
|
Provided a function has the right type for the ID we're using, we can now
|
|
attach it to an object with a method set, using the |METHOD_ADD| macro.
|
|
(If the type is wrong, the C compiler will throw errors here.)
|
|
|
|
@d METHOD_ADD(upon, id, func)
|
|
Methods::add(upon->methods, id, (void *) &func);
|
|
|
|
=
|
|
typedef struct method {
|
|
int method_id;
|
|
void *method_function;
|
|
struct method *next_method;
|
|
CLASS_DEFINITION
|
|
} method;
|
|
|
|
void Methods::add(method_set *S, int ID, void *function) {
|
|
method *M = CREATE(method);
|
|
M->method_id = ID;
|
|
M->method_function = function;
|
|
M->next_method = NULL;
|
|
|
|
if (S->first_method == NULL) S->first_method = M;
|
|
else {
|
|
method *existing = S->first_method;
|
|
while ((existing) && (existing->next_method)) existing = existing->next_method;
|
|
existing->next_method = M;
|
|
}
|
|
}
|
|
|
|
int Methods::provided(method_set *S, int ID) {
|
|
if (S == NULL) return FALSE;
|
|
for (method *M = S->first_method; M; M = M->next_method)
|
|
if (M->method_id == ID)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
@h Calling methods.
|
|
Method calls are also done with a macro, but it has to come in four variants:
|
|
|
|
(a) |INT_METHOD_CALL| for a method taking arguments and returning an |int|,
|
|
(b) |INT_METHOD_CALL_WITHOUT_ARGUMENTS| for a method without arguments which returns an |int|,
|
|
(c) |VOID_METHOD_CALL| for a method taking arguments and returning nothing,
|
|
(d) |VOID_METHOD_CALL_WITHOUT_ARGUMENTS| for a method without arguments which returns nothing.
|
|
|
|
For example:
|
|
= (text as code)
|
|
INT_METHOD_CALL(some_object, UNUSED_METHOD_ID_MTID, I"Hello", 17)
|
|
=
|
|
Note that it's entirely possible for the |upon| object to have multiple methods
|
|
added for the same ID -- or none. In the |V| (void) cases, what we then do is
|
|
to call each of them in turn. In the |I| (int) cases, we call each in turn, but
|
|
stop the moment any of them returns something other than |FALSE|, and then
|
|
we put that value into the specified result variable |rval|.
|
|
|
|
If |some_object| has no methods for the given ID, then nothing happens, and
|
|
in the |I| case, the return value is |FALSE|.
|
|
|
|
It will, however, produce a compilation error if |some_object| is not a pointer
|
|
to a structure which has a |methods| element as part of its definition.
|
|
|
|
@d INT_METHOD_CALL(rval, upon, id, args...) {
|
|
rval = FALSE;
|
|
for (method *M = upon?(upon->methods->first_method):NULL; M; M = M->next_method)
|
|
if (M->method_id == id) {
|
|
int method_rval_ = (*((id##_type) (M->method_function)))(upon, args);
|
|
if (method_rval_) {
|
|
rval = method_rval_;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
@d INT_METHOD_CALL_WITHOUT_ARGUMENTS(rval, upon, id) {
|
|
rval = FALSE;
|
|
for (method *M = upon?(upon->methods->first_method):NULL; M; M = M->next_method)
|
|
if (M->method_id == id) {
|
|
int method_rval_ = (*((id##_type) (M->method_function)))(upon);
|
|
if (method_rval_) {
|
|
rval = method_rval_;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
@d VOID_METHOD_CALL(upon, id, args...)
|
|
for (method *M = upon?(upon->methods->first_method):NULL; M; M = M->next_method)
|
|
if (M->method_id == id)
|
|
(*((id##_type) (M->method_function)))(upon, args);
|
|
@d VOID_METHOD_CALL_WITHOUT_ARGUMENTS(upon, id)
|
|
for (method *M = upon?(upon->methods->first_method):NULL; M; M = M->next_method)
|
|
if (M->method_id == id)
|
|
(*((id##_type) (M->method_function)))(upon);
|