/* Totally minimal "typed functions" */ /* helper: dest is really a TF and imps is either nothing, an imp, or * an array of implementations. author is the entity responsible for these * implementations. */ const addImps = (dest, imps, author) => { if (imps) { if (!Array.isArray(imps[0])) imps = [imps] } else { imps = [] } if (author) { if (dest.authors.has(author)) { const [count, index] = dest.authors.get(author) if (count) dest.imps.splice(index, count) } dest.authors.set(author, [imps.length, dest.imps.length]) } for (const imp of imps) dest.imps.push(imp) } /* Create a TF, optionally adding some initial imps and further optionally * registering them to an author. There are two advantages to author * registration: (1) if the same author adds implementations, the prior imps * will be deleted, and (2) the author can be invalidated, and the TF will * lazily call the author back with the given data the next time it is * called, so that it can re-add the imps. */ export default function poortf (name, imps, author, data) { /* This is the (function) object we will return */ function fn () { for (const imp of fn.imps) { if (imp[0](arguments)) return imp[1].apply(null, arguments) } throw new TypeError( `TF ${fn.name}: No match for ${arguments[0]}, ${arguments[1]}, ...`) } /* Now dress it up for use */ Object.defineProperty(fn, 'name', {value: name}) fn.imps = [] fn.authors = new Map() // tracks who made each implementation fn.authorData = new Map() // tracks author callback data addImps(fn, imps, author) if (author) fn.authorData.set(author, data) fn.addImps = (newI, author, data) => { addImps(fn, newI, author) if (author) fn.authorData.set(author, data) } fn.invalidate = author => { addImps(fn, null, author) // deletes the author's imps fn.unshiftImp([() => true, (...args) => fn.reloadAndRecall(args)]) } fn.unshiftImp = newImp => { fn.imps.unshift(newImp) for (const record in fn.authors.values()) { record[1] += 1 } } fn.shiftImp = () => { const oldImp = fn.imps.shift() for (const record in fn.authors.values()) { record[1] -= 1 } } fn.reloadAndRecall = (argArray) => { fn.shiftImp() // throw ourself away, ha ha for (const author of fn.authorData.keys()) { if (fn.authors.get(author)[0] === 0) { // imps count of 0 means the author was invalidated author(fn.authorData.get(author)) } } return fn.apply(null, argArray) } return fn } export function isTF(x) { return typeof x === 'function' && 'imps' in x && Array.isArray(x.imps) }