94743407 |
export function NoNextMethodError() {}
|
406b5587 |
NoNextMethodError.prototype = Object.create(Error);
|
94743407 |
export function NoApplicableMethodError() {}
|
636eaffe |
NoApplicableMethodError.prototype = Object.create(Error);
|
406b5587 |
|
94743407 |
export function NoPrimaryMethodError() {}
|
406b5587 |
NoPrimaryMethodError.prototype = Object.create(NoApplicableMethodError);
|
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 |
}
|