General support for something approximating method calls.


§1. 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".

§2. 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;
}

§3. 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.

enum UNUSED_METHOD_ID_MTID from 1

§4. 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.

define INT_METHOD_TYPE(id, args...)
    typedef int (*id##_type)(args);
define VOID_METHOD_TYPE(id, args...)
    typedef void (*id##_type)(args);
INT_METHOD_TYPE(UNUSED_METHOD_ID_MTID, text_stream *example, int wont_be_used)

§5. 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.)

define 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;
}

§6. Calling methods. Method calls are also done with a macro, but it has to come in four variants:

For example:

    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.

define 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;
            }
        }
}
define 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;
            }
        }
}
define 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);
define 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);