git.fiddlerwoaroof.com
src/genfuns.js
94944213
 const before_qualifier = Symbol.for('before');
 const after_qualifier = Symbol.for('after');
 const around_qualifier = Symbol.for('around');
 
 
 let genfun_prototype = {
     name: "(placeholder)",
     lambda_list: [],
     methods: [],
     method(qualifiers, specializers, body) {
         ensure_method(this, this.lambda_list, qualifiers, specializers, body)
         return this;
     },
     primary(specializers, body) {
         return this.method([], specializers, body);
     },
     before(specializers, body) {
         return this.method([before_qualifier], specializers, body);
     },
     after(specializers, body) {
         return this.method([after_qualifier], specializers, body);
     },
     around(specializers, body) {
         return this.method([around_qualifier], specializers, body);
     },
     get fn() {
         const gf = this;
         const lambda = (function() {
             return apply_generic_function(gf, [].slice.call(arguments));
         }).bind(gf);
         return Object.defineProperties(lambda, {
             'name': {value: gf.name},
             'lambda_list': {value: gf.lambda_list},
             'gf': {value: gf},
         });
     }
 };
 
 function GenericFunction(name, lambda_list) {
     if (! (this instanceof GenericFunction) ) {
         return new GenericFunction(...arguments);
     }
 
     this.name = name;
     this.lambda_list = lambda_list;
     this.methods = [];
 }
 
 GenericFunction.prototype = Object.create(genfun_prototype);
 
 export function defgeneric(name, ...argument_names) {
     return GenericFunction(name, argument_names);
 }
 
 let method_prototype = {
     lambda_list: [],
     qualifiers: [],
     specializrs: [],
     body() { throw new Error('Unimplemented'); },
     environment: {},
     generic_function: {},
 };
 
 function StandardMethod(
     lambda_list, qualifiers, specializers, body
 ) {
     if (! (this instanceof StandardMethod) ) {
         return new StandardMethod(...arguments);
     }
 
     this.lambda_list = lambda_list;
     this.qualifiers = qualifiers;
     this.specializers = specializers;
     this.body = body;
     this.generic_function = null;
 }
 
 function ensure_method(gf, lambda_list, qualifiers, specializers, body) {
     let new_method = StandardMethod(...[].slice.call(arguments, 1));
     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 ${JSON.stringify(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 (let [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 };
 
 export function matchesSpecializer(obj, specializer) {
     let result = obj === specializer.prototype;
     let objType = typeof obj;
 
     if (!result && objType === 'object') {
         result = Object.isPrototypeOf.call(specializer.prototype, obj);
     } else if (objType === 'number') {
         result = matchesSpecializer(Number.prototype, specializer) || matchesSpecializer(specializer.prototype, Number);
     } else if (objType === 'string') {
         result = matchesSpecializer(String.prototype, specializer) || matchesSpecializer(specializer.prototype, String);
     }
 
     return result;
 }
 
 
 function compute_applicable_methods_using_classes(gf, required_classes) {
     const applicable_methods = gf.methods.filter(
         method => method.specializers.every((specializer, idx) => matchesSpecializer(required_classes[idx], specializer))
     );
 
     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;
 }
 
 
 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 instanceof WrappedMethod || method.qualifiers.length === 0;
 const before_method_p =
       method => !(method instanceof WrappedMethod) && arr_eq(method.qualifiers, [before_qualifier]);
 const after_method_p =
       method => !(method instanceof WrappedMethod) && arr_eq(method.qualifiers, [after_qualifier]);
 const around_method_p =
       method => !(method instanceof WrappedMethod) && arr_eq(method.qualifiers, [around_qualifier]);
 
 function WrappedMethod(continuation) {
     this.continuation = continuation;
 }
 
 function apply_methods(gf, args, applicable_methods) {
     const primaries = applicable_methods.filter(primary_method_p);
     const befores = applicable_methods.filter(before_method_p);
     const arounds = applicable_methods.filter(around_method_p);
     const afters = applicable_methods.filter(after_method_p);
     afters.reverse();
 
     const main_call = Object.defineProperty(
         function() {
             if (primaries.length === 0) {
                 throw new Error(`No primary method for ${gf.name}`);
             }
 
             for (let before of befores) {
                 apply_method(before, args, []);
             }
 
             try {
                 return apply_method(primaries[0], args, primaries.slice(1));
             } finally {
                 for (let after of afters) {
                     apply_method(after, args, []);
                 }
             }
         },
         'name', {value: `main_call_${gf.name}`}, 
     );
 
     if (arounds.length === 0) {
         return main_call();
     } else {
         const wrapped_main_call = new WrappedMethod(main_call);
         const next_methods = arounds.slice(1).concat([wrapped_main_call]);
         return apply_method(arounds[0], args, next_methods);
     }
 }
 
 function apply_method(method, args, next_methods) {
     const method_context = {
         call_next_method(...cnm_args) {
             if (next_methods.length === 0) {
                 throw new Error(`no next method for genfun ${method.generic_function.name}`);
             }
 
             return method instanceof WrappedMethod
                 ? method.continuation()
                 : apply_methods(method.generic_function, cnm_args.length > 0 ? cnm_args : args, next_methods);
         },
         get next_method_p() {
             return next_methods.length === 0
         }
     };
 
     return method.body ? method.body.bind(method_context)(...args) : method.continuation();
 }