Skip to content

Instantly share code, notes, and snippets.

@HunterKohler
Created July 3, 2021 14:36
Show Gist options
  • Save HunterKohler/a6b42a42166709d9c822651437750ff7 to your computer and use it in GitHub Desktop.
Save HunterKohler/a6b42a42166709d9c822651437750ff7 to your computer and use it in GitHub Desktop.
Callable classes in javascript
export function log(value, options) {
return console.dir(value, {
showHidden: true,
depth: null,
colors: true,
...options
});
}
export function isKey(key) {
return (
typeof key == "string" ||
typeof key == "symbol" ||
typeof key == "number"
);
}
export function wrapNew(ctor) {
return function wrapper() {
return new ctor(arguments);
};
}
export function rename(func, name) {
return Reflect.defineProperty(func, "name", {
value: name,
configurable: true
});
}
export function isTrivial(object) {
return (
!(typeof object == "object" || typeof object == "function") ||
object == null ||
object == Object.prototype ||
object == Function.prototype
);
}
export function bindMethod(object, key) {
const { name } = object[key];
const defined = Reflect.defineProperty(object, key, {
...Reflect.getOwnPropertyDescriptor(object, key),
value: object[key].bind(object)
});
if (defined) {
rename(object[key], name);
}
return defined;
}
export function bindAll(object) {
const properties = new Set();
const failed = []; // Properties that could not be assigned.
let temp = object;
while (!isTrivial(temp)) {
for (const key of Reflect.ownKeys(temp)) {
if (
typeof object[key] == "function" &&
key != "constructor" &&
!properties.has(key)
) {
if (!bindMethod(object, key)) {
failed.push(key);
}
}
// Non-function keys should not be bound if overriden by getter,
// property, etc..
properties.add(key);
}
temp = Reflect.getPrototypeOf(temp);
}
if (failed.length) {
return failed;
}
}
export function bindEach(object, keys) {
const failed = [];
for (const key of keys) {
if (!bindMethod(object, key)) {
failed.push(key);
}
}
if (failed.length) {
return failed;
}
}
export class AbstractError extends Error {
constructor(constructorOpt, message) {
super(message);
if (constructorOpt) {
Error.captureStackTrace(this, constructorOpt);
}
rename(this, new.target.name);
}
}
export class AbstractClassError extends AbstractError {
constructor(constructorOpt, message) {
message ||=
`Cannot instantiate abstract class` +
(constructorOpt ? ` ${String(constructorOpt.name)}` : "");
super(constructorOpt, message);
}
}
export class AbstractMethodError extends AbstractError {
constructor(constructorOpt, message) {
message ||= "Unimplemented abstract method";
super(constructorOpt, message);
}
}
export function defineInstanceProperty(cls, property, value) {
return Reflect.defineProperty(cls.prototype, property, {
value,
configurable: true,
enumerable: false,
writable: typeof value == "function"
});
}
export const Callable = new Proxy(
// Must be (anonymous), or any other name, to stop naming collisions with
// local function closures reference to the Callable proxy. Renamed
// in the static block. To reference the proxy target, use
// `this.constructor`.
class extends Function {
static #_ = (() => {
rename(this, "Callable");
defineInstanceProperty(this, Symbol.toStringTag, "Callable");
defineInstanceProperty(this, "toString", Object.prototype.toString);
})();
constructor(name = "__call__") {
super();
if (!isKey(name)) {
name = String(name);
}
rename(this, name);
if (new.target == Callable) {
throw new AbstractClassError(this.constructor);
}
if (typeof this[name] != "function") {
throw new AbstractMethodError(
this.constructor,
`Abstract method '${name}' was not implemented` +
` on '${new.target}'`
);
}
return new Proxy(this, {
apply(target, thisArg, argumentList) {
return target[target.name].apply(thisArg, argumentList);
}
});
}
},
{
apply(target, thisArg, [defaultName]) {
class RenamedCallable extends Callable {
constructor(name = defaultName) {
super(name);
if (new.target == RenamedCallable) {
throw new AbstractClassError(RenamedCallable);
}
}
}
rename(RenamedCallable, `Callable(${String(defaultName)})`);
return RenamedCallable;
}
}
);
export const Main = Callable("main");
class Main extends Callable("main") {
static #_ = () => {
defineInstanceProperty(Main, Symbol.toStringTag, "Main");
};
constructor() {
bindAll(this);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment