git.fiddlerwoaroof.com
Raw Blame History
let genfun_prototype = {
    name: "(placeholder)",
    lambda_list: [],
    methods: [],
};

function GenericFunction(name, lambda_list, methods) {
    if (! (this instanceof GenericFunction) ) {
        return new GenericFunction(name, lambda_list, methods);
    }

    this.name = name;
    this.lambda_list = lambda_list;
    this.methods = methods;
}

GenericFunction.prototype = Object.assign(
    Object.create(genfun_prototype), {
    }
);

let method_prototype = {
    lambda_list: [],
    qualifiers: [],
    specializrs: [],
    body() { throw new Error('Unimplemented'); },
    environment: {},
    generic_function: {},
};

function StandardMethod(
    lambda_list, qualifiers, specializers, body, environment, generic_function
) {
    if (! (this instanceof StandardMethod) ) {
        return new StandardMethod(...arguments);
    }

    this.lambda_list = lambda_list;
    this.qualifiers = qualifiers;
    this.specializers = specializers;
    this.body = body;
    this.environment = environment;
    this.generic_function = generic_function;
}

function ensure_method(gf, ...rest) {
    let new_method = StandardMethod(...rest);
    add_method(gf, new_method);
    return new_method;
}

function add_method(gf, method) {
    method.generic_function = gf;
    gf.methods.push(method);
    return method;
}

function classes_of(args) {
    return args.map(Object.getPrototypeOf);
}

const required_portion = x => x;

function apply_generic_function(gf, args) {
    let applicable_methods =
        compute_applicable_methods_using_classes(gf, required_portion(args));
    if (applicable_methods.length === 0) {
        throw new Error(`no applicable methods for gf ${gf.name} with args ${args}`);
    } else {
        return apply_methods(gf, args, applicable_methods);
    }
}

function method_more_specific_p(m1, m2, required_classes) {
    const m1specializers = m1.specializers;
    const m2specializers = m2.specializers;

    for ([spec1, spec2] of m1specializers.map((el, idx) => [el, m2specializers[idx]])) {
        if (spec1 !== spec2) {
            return sub_specializer_p(spec1, spec2);
        }
    }
}

function sub_specializer_p(c1, c2) {
    return c1.isPrototypeOf(c2);
}

const idS = Symbol.for('id');
Object.prototype[idS] = function () { return this };

function compute_applicable_methods_using_classes(gf, required_classes) {
    const applicable_methods = gf.methods.filter(
        method =>
            method.specializers.every((specializer,idx) => (console.info(specializer),
                                                            specializer.prototype.isPrototypeOf(required_classes[idx])
                                                            || specializer.prototype.isPrototypeOf(required_classes[idx][idS]())))
    );

    applicable_methods.sort((a,b) => {
        if (method_more_specific_p(a,b)) {
            return -1;
        }
        if (method_more_specific_p(b,a)) {
            return 1;
        }
        return 0;
    })

    return applicable_methods;
}

const before_qualifier = Symbol.for('before');
const after_qualifier = Symbol.for('after');

function arr_eq(a1, a2) {
    if (a1.length !== a2.length) {
        return false;
    } else {
        for (let x = 0; x < a1.length; x++) {
            if (a1[x] instanceof Array && a2[x] instanceof Array) {
                if (!arr_eq(a1[x], a2[x])) {
                    return false;
                }
            } else if (a1[x] !== a2[x]) {
                return false;
            }
        }
        return true;
    }
}

const primary_method_p = method => method.qualifiers.length === 0;
const before_method_p = method => arr_eq(method.qualifiers, [before_qualifier]);
const after_method_p = method => arr_eq(method.qualifiers, [after_qualifier]);

function apply_methods(gf, args, applicable_methods) {
    const primaries = applicable_methods.filter(primary_method_p);
    const befores = applicable_methods.filter(before_method_p);
    const afters = applicable_methods.filter(after_method_p);
    afters.reverse();

    if (primaries.length === 0) {
        throw new Error(`No primary method for ${gf.name}`);
    }

    for (let before of befores) {
        apply_method(before, args, []);
    }

    const result = apply_method(primaries[0], args, primaries.slice(1));

    for (let after of afters) {
        apply_method(after, args, []);
    }

    return result;
}

function apply_method(method, args, next_methods) {
    return Function('call_next_method', 'next_method_p', `return ${method.body.toString()}`)(
        (...cnm_args) => {
            if (next_methods.length === 0) {
                throw new Error(`no next method for genfun ${method.generic_function.name}`);
            }

            return apply_methods(method.generic_function, cnm_args.length > 0 ? cnm_args : args, next_methods);
        },
        () => next_methods.length === 0
    )(...args);
}

const gf = GenericFunction("foobar", ["a", "b"], []);
ensure_method(
    gf,
    [], [], [Object, Array], (thing, arr) => [thing, ...arr], null
);
ensure_method(
    gf,
    [], [], [Object, Object], (thing, single) => [thing, single], null
);

console.info(apply_generic_function(gf, [["asdf"],2]));