git.fiddlerwoaroof.com
src/genfuns.js
0fb7ba9b
 function SubTypeError(name) {
   const cls = function () {
     const instance = Error(...arguments);
     Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
     Object.defineProperty(instance, "name", {
       value: name,
       writable: false,
     });
     return instance;
   };
   cls.prototype = Object.create(Error);
   return cls;
 }
406b5587
 
0fb7ba9b
 export const NoNextMethodError = SubTypeError("NoNextMethodError");
 export const NoApplicableMethodError = SubTypeError("NoApplicableMethodError");
 export const NoPrimaryMethodError = SubTypeError("NoPrimaryMethodError");
406b5587
 
94743407
 const before_qualifier = Symbol.for("before");
 const after_qualifier = Symbol.for("after");
 const around_qualifier = Symbol.for("around");
 
 const Method = {
   lambda_list: [],
   qualifiers: [],
   specializers: [],
   body: () => {},
cc733b08
   generic_function: null,
94743407
 };
94944213
 
 let genfun_prototype = {
94743407
   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;
cc733b08
     const lambda = function () {
94743407
       return apply_generic_function(gf, [].slice.call(arguments));
     }.bind(gf);
     return Object.defineProperties(lambda, {
       name: { value: gf.name },
       lambda_list: { value: gf.lambda_list },
cc733b08
       gf: { value: gf },
94743407
     });
cc733b08
   },
94944213
 };
 
94743407
 /**
  * @class
  * @extends genfun_prototype
  * @param {string} name
  * @param {string[]} lambda_list
  * @property {Method[]} methods
  */
94944213
 function GenericFunction(name, lambda_list) {
94743407
   if (!(this instanceof GenericFunction)) {
     return new GenericFunction(...arguments);
   }
94944213
 
94743407
   this.name = name;
   this.lambda_list = lambda_list;
   this.methods = [];
94944213
 }
 
 GenericFunction.prototype = Object.create(genfun_prototype);
 
 export function defgeneric(name, ...argument_names) {
94743407
   return GenericFunction(name, argument_names);
94944213
 }
 
a00b5a50
 // let method_prototype = {
 //     lambda_list: [],
 //     qualifiers: [],
 //     specializrs: [],
 //     body() { throw new Error('Unimplemented'); },
 //     environment: {},
 //     generic_function: {},
 // };
94944213
 
94743407
 /**
  * @class
  * @extends Method
  * @param {string[]} lambda_list
  * @param {Symbol[]} qualifiers
  * @param {Specializer | Object} specializers
  * @param {Function} body
  */
 export 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;
94944213
 }
94743407
 StandardMethod.prototype = Object.create(Method);
94944213
 
94743407
 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;
94944213
 }
 
 function add_method(gf, method) {
94743407
   method.generic_function = gf;
   gf.methods.push(method);
   return method;
94944213
 }
 
a00b5a50
 // function classes_of(args) {
 //     return args.map(Object.getPrototypeOf);
 // }
94944213
 
 const required_portion = x => x;
 
 function apply_generic_function(gf, args) {
94743407
   let applicable_methods = compute_applicable_methods_using_classes(
     gf,
     required_portion(args)
   );
   if (applicable_methods.length === 0) {
     throw new NoApplicableMethodError(
       `no applicable methods for gf ${gf.name} with args ${JSON.stringify(
         args
       )}`
     );
   } else {
     return apply_methods(gf, args, applicable_methods);
   }
94944213
 }
 
94743407
 function method_more_specific_p(m1, m2 /*, required_classes*/) {
   const m1specializers = m1.specializers;
   const m2specializers = m2.specializers;
 
   let result = null;
   for (let [spec1, spec2] of m1specializers.map((el, idx) => [
     el,
cc733b08
     m2specializers[idx],
94743407
   ])) {
     if (spec1 !== spec2) {
       result = sub_specializer_p(spec1, spec2);
       break;
94944213
     }
94743407
   }
406b5587
 
94743407
   return result;
94944213
 }
 
406b5587
 export function sub_specializer_p(c1, c2) {
94743407
   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
 }
 
94743407
 const idS = Symbol.for("id");
cc733b08
 Object.prototype[idS] = function () {
94743407
   return this;
 };
94944213
 
94743407
 export function Specializer() {}
406b5587
 Specializer.prototype = {
94743407
   matches(_obj) {
     return false;
   },
   super_of(_obj) {
     return false;
cc733b08
   },
94743407
 };
406b5587
 
 function isSuperset(superset, subset) {
94743407
   return (
     superset.size > subset.size &&
     Array.from(subset).every(superset.has.bind(superset))
   );
406b5587
 }
 
a00b5a50
 const matchShape = defgeneric("matchShape", "shape", "value")
94743407
   .primary([Array], ([name, dflt], v) => dflt !== undefined && v[name] === dflt)
   .primary([String], (name, v) => v[name] !== undefined).fn;
a00b5a50
 
 export const extractKey = defgeneric("extractKey", "key")
94743407
   .primary([Array], ([name, _]) => name)
   .primary([String], name => name).fn;
a00b5a50
 
406b5587
 export function Shape(...keys) {
94743407
   if (!(this instanceof Shape)) {
     return new Shape(...keys);
   }
   this.keys = new Set(keys);
406b5587
 }
 Shape.prototype = Object.assign(new Specializer(), {
94743407
   matches(obj) {
     return Array.from(this.keys).every(key => matchShape(key, obj));
   },
   super_of(spec) {
     // this is the super of spec
     //     if this.keys is a subset of spec.keys
     // and if this.keys != spec.keys
 
     if (!(spec instanceof Shape)) {
       const specKeys = spec && new Set(Object.getOwnPropertyNames(spec));
       return !!specKeys && isSuperset(specKeys, this.keys);
     } else {
       return isSuperset(spec.keys, this.keys);
406b5587
     }
cc733b08
   },
406b5587
 });
 
a00b5a50
 // function trace(fun) {
 //     return function (...args) {
 //         console.log(fun, `args are: thsds`, this, 'others', args);
 //         const result = fun.apply(this, args);
 //         console.log(`result`, result);
 //         return result;
 //     }
 // }
 
406b5587
 export function matches_specializer(obj, specializer) {
94743407
   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);
94944213
     }
94743407
   } else if (specializer instanceof Specializer) {
     result = specializer.matches(obj);
   }
94944213
 
94743407
   return result;
94944213
 }
 
94743407
 /**
  * @param {GenericFunction} gf
  */
94944213
 function compute_applicable_methods_using_classes(gf, required_classes) {
94743407
   const applicable_methods = gf.methods.filter(method =>
     method.specializers.every((specializer, idx) =>
       matches_specializer(required_classes[idx], specializer)
     )
   );
 
   applicable_methods.sort((a, b) => {
     let result = 0;
     if (method_more_specific_p(a, b)) {
       result = 1;
     }
     if (method_more_specific_p(b, a)) {
       result = -1;
     }
406b5587
 
94743407
     return result;
   });
94944213
 
94743407
   return applicable_methods;
94944213
 }
 
94743407
 /**
  * @param {any[]} a1
  * @param {any[]} a2
  */
94944213
 function arr_eq(a1, a2) {
94743407
   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;
94944213
         }
94743407
       } else if (a1[x] !== a2[x]) {
         return false;
       } else if (
         Object.hasOwnProperty.call(a1[x], "equals") &&
         !a1[x].equals(a2[x])
       ) {
         return false;
       } else if (
         Object.hasOwnProperty.call(a2[x], "equals") &&
         !a2[x].equals(a1[x])
       ) {
         return false;
       }
94944213
     }
94743407
     return true;
   }
94944213
 }
 
a00b5a50
 // 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;
 //     }
 // }
406b5587
 
94743407
 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]);
94944213
 
 function WrappedMethod(continuation) {
94743407
   this.continuation = continuation;
94944213
 }
 
94743407
 /**
  * @param {GenericFunction} gf
  * @param {any[]} args
  * @param {Method[]} applicable_methods
  */
94944213
 function apply_methods(gf, args, applicable_methods) {
94743407
   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(
cc733b08
     function () {
94743407
       if (primaries.length === 0) {
         throw new NoPrimaryMethodError(`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);
   }
94944213
 }
 
94743407
 /**
  * @param {Method} method
  * @param {any[]} args
  * @param {Method[]} next_methods
  */
94944213
 function apply_method(method, args, next_methods) {
94743407
   const method_context = {
     call_next_method(...cnm_args) {
       if (next_methods.length === 0) {
         throw new NoNextMethodError(
           `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;
cc733b08
     },
94743407
   };
94944213
 
94743407
   return method.body
     ? method.body.bind(method_context)(...args)
     : method.continuation();
94944213
 }