git.fiddlerwoaroof.com
src/genfuns.js
406b5587
 export function NoNextMethodError() {}
 NoNextMethodError.prototype = Object.create(Error);
 
 export function NoApplicableMethodError() {}
 NoNextMethodError.prototype = Object.create(Error);
 
 export function NoPrimaryMethodError() {}
 NoPrimaryMethodError.prototype = Object.create(NoApplicableMethodError);
 
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: {},
 };
 
406b5587
 export function StandardMethod(
94944213
     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) {
406b5587
         throw new NoApplicableMethodError(`no applicable methods for gf ${gf.name} with args ${JSON.stringify(args)}`);
94944213
     } 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;
 
406b5587
     let result = null;
94944213
     for (let [spec1, spec2] of m1specializers.map((el, idx) => [el, m2specializers[idx]])) {
         if (spec1 !== spec2) {
406b5587
             result = sub_specializer_p(spec1, spec2);
             break;
94944213
         }
     }
406b5587
 
     return result;
94944213
 }
 
406b5587
 export function sub_specializer_p(c1, c2) {
     let result = false;
     if (c1 instanceof Specializer) {
         result = c1.super_of(c2);
     } else if (c1.prototype !== undefined && c2.prototype !== undefined) {
         result = Object.isPrototypeOf.call(c1.prototype, c2.prototype);
     }
     return result;
94944213
 }
 
 const idS = Symbol.for('id');
 Object.prototype[idS] = function () { return this };
 
406b5587
 export function Specializer() {}
 Specializer.prototype = {
     matches(obj) { return false; },
     super_of(obj) { return false; },
 }
 
 function isSuperset(superset, subset) {
     return Array.from(subset).every(superset.has.bind(superset));
 }
 
 export function Shape(...keys) {
     if (! (this instanceof Shape) ) {
         return new Shape(...keys);
     }
     this.keys = new Set(keys);
 }
 Shape.prototype = Object.assign(new Specializer(), {
     matches(obj) {
         return Array.from(this.keys).every(key => obj[key] !== undefined);
     },
     super_of(spec) {
         // this is the super of spec
         //     if this.keys is a subset of spec.keys
         // and if this.keys != spec.keys
94944213
 
406b5587
         if (!(spec instanceof Shape)) {
             return false;
         }
 
         let this_keys_subset_of_spec_keys = isSuperset(spec.keys, this.keys);
         let not_eq = this.keys.size !== spec.keys.size;
 
         return this_keys_subset_of_spec_keys && not_eq;
     }
 });
 
 export function matches_specializer(obj, specializer) {
     let objType = typeof obj;
     let specializer_proto = specializer && specializer.prototype
     let result = obj === specializer_proto;
 
     if (obj === null && obj === specializer) {
         result = true;
     } else if (specializer && specializer.prototype !== undefined) {
         if (!result && objType === 'object') {
             result = Object.isPrototypeOf.call(specializer_proto, obj);
         } else if (objType === 'number') {
             result = matches_specializer(Number.prototype, specializer) || matches_specializer(specializer_proto, Number);
         } else if (objType === 'string') {
             result = matches_specializer(String.prototype, specializer) || matches_specializer(specializer_proto, String);
         }
     } else if (specializer instanceof Specializer) {
         result = specializer.matches(obj);
94944213
     }
 
     return result;
 }
 
 
 function compute_applicable_methods_using_classes(gf, required_classes) {
     const applicable_methods = gf.methods.filter(
406b5587
         method => method.specializers.every((specializer, idx) => matches_specializer(required_classes[idx], specializer))
         
94944213
     );
 
     applicable_methods.sort((a,b) => {
406b5587
         let result = 0;
94944213
         if (method_more_specific_p(a,b)) {
406b5587
             result = 1;
94944213
         }
         if (method_more_specific_p(b,a)) {
406b5587
             result = -1;
94944213
         }
406b5587
 
         return result;
94944213
     })
 
     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;
     }
 }
 
406b5587
 function set_eq(a1, a2) {
     if (a1.length !== a2.length) {
         return false;
     } else {
         let result = true;
         for (let elem of a1) {
             result = result && a2.has(elem);
             if (!result) break;
         }
         return result;
     }
 }
 
94944213
 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) {
406b5587
                 throw new NoPrimaryMethodError(`No primary method for ${gf.name}`);
94944213
             }
 
             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) {
406b5587
                 throw new NoNextMethodError(`no next method for genfun ${method.generic_function.name}`);
94944213
             }
 
             return method instanceof WrappedMethod
                 ? method.continuation()
                 : apply_methods(method.generic_function, cnm_args.length > 0 ? cnm_args : args, next_methods);
         },
406b5587
 
94944213
         get next_method_p() {
406b5587
             return next_methods.length !== 0
94944213
         }
     };
 
     return method.body ? method.body.bind(method_context)(...args) : method.continuation();
 }