From c5384e0ee7dea3968b603f2ea3eefe19ed481035 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 18 Jul 2022 17:08:49 -0700 Subject: [PATCH] feat: First operational instance with just add --- PocomathInstance.mjs | 90 ++++++++++++++++++++++++++++++++++++++++++++ number/add.mjs | 3 ++ package.json5 | 3 ++ pnpm-lock.yaml | 9 +++++ pocomath.mjs | 8 ++++ test/_pocomath.mjs | 10 +++++ 6 files changed, 123 insertions(+) create mode 100644 PocomathInstance.mjs create mode 100644 number/add.mjs create mode 100644 pocomath.mjs create mode 100644 test/_pocomath.mjs diff --git a/PocomathInstance.mjs b/PocomathInstance.mjs new file mode 100644 index 0000000..a28744d --- /dev/null +++ b/PocomathInstance.mjs @@ -0,0 +1,90 @@ +/* Core of pocomath: create an instance */ +import typed from 'typed-function' + +export default class PocomathInstance { + constructor(name) { + this.name = name + this._imps = {} + } + + /** + * (Partially) define one or more operations of the instance: + * + * @param {Object>} ops + * The only parameter ops gives the semantics of the operations to install. + * The keys are operation names. The value for a key is an object + * mapping (typed-function) signature strings to pairs of dependency + * lists and implementation functions. + * + * A dependency list is a list of strings. Each string can either be the + * name of a function that the corresponding implementation has to call, + * or a specification of a particular signature of a function that it has + * to call, in the form 'FN(SIGNATURE)'. Note the function name can be + * the special value 'self' to indicate a recursive call to the given + * operation (either with or without a particular signature. + * + * There are two cases for the implementation function. If the dependency + * list is empty, it should be a function taking the arguments specified + * by the signature and returning the value. Otherwise, it should be + * a function taking an object with the dependency lists as keys and the + * requested functions as values, to a function taking the arguments + * specified by the signature and returning the value + */ + install(ops) { + for (const key in ops) this._installOp(key, ops[key]) + } + + /* Used internally by install, see the documentation there */ + _installOp(name, implementations) { + // new implementations, so set the op up to lazily recreate itself + this._invalidate(name) + const opImps = this._imps[name] + for (const signature in implementations) { + if (signature in opImps) { + if (implemenatations[signature] === opImps[signature]) continue + throw new SyntaxError( + `Conflicting definitions of ${signature} for ${name}`) + } else { + opImps[signature] = implementations[signature] + } + } + } + + /** + * Reset an operation to require creation of typed-function, + * and if it has no implementations so far, set them up. + */ + _invalidate(name) { + const self = this + this[name] = function () { + return self._bundle(name).apply(self, arguments) + } + if (!(name in this._imps)) { + this._imps[name] = {} + } + } + + /** + * Create a typed-function from the signatures for the given name and + * assign it to the property with that name, returning it as well + */ + _bundle(name) { + const imps = this._imps[name] + if (!imps || Object.keys(imps).length === 0) { + throw new SyntaxError(`No implementations for ${name}`) + } + const tf_imps = {} + // TODO: handle nonempty dependencies + for (const signature in imps) { + const [deps, imp] = imps[signature] + if (deps.length === 0) { + tf_imps[signature] = imp + } else { + throw new Error('Unimplemented') + } + } + const tf = typed(name, tf_imps) + this[name] = tf + return tf + } +} diff --git a/number/add.mjs b/number/add.mjs new file mode 100644 index 0000000..49c1f40 --- /dev/null +++ b/number/add.mjs @@ -0,0 +1,3 @@ +export const add = { + '...number': [[], addends => addends.reduce((x,y) => x+y, 0)], +} diff --git a/package.json5 b/package.json5 index 4898bc0..517eefb 100644 --- a/package.json5 +++ b/package.json5 @@ -21,4 +21,7 @@ devDependencies: { mocha: '^10.0.0', }, + dependencies: { + 'typed-function': '^3.0.0', + }, } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b227fd..22e3de7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,6 +2,10 @@ lockfileVersion: 5.4 specifiers: mocha: ^10.0.0 + typed-function: ^3.0.0 + +dependencies: + typed-function: 3.0.0 devDependencies: mocha: 10.0.0 @@ -465,6 +469,11 @@ packages: is-number: 7.0.0 dev: true + /typed-function/3.0.0: + resolution: {integrity: sha512-mKJKkt2xYxJUuMD7jyfgUxfn5KCsCxkEKBVjep5yYellJJ5aEDO2QUAmIGdvcZmfQnIrplkzELIaG+5b1475qg==} + engines: {node: '>= 14'} + dev: false + /workerpool/6.2.1: resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} dev: true diff --git a/pocomath.mjs b/pocomath.mjs new file mode 100644 index 0000000..c8f5b18 --- /dev/null +++ b/pocomath.mjs @@ -0,0 +1,8 @@ +/* Core of pocomath: generates the default instance */ +import PocomathInstance from './PocomathInstance.mjs' +import * as numberAdd from './number/add.mjs' + +const math = new PocomathInstance('math') +math.install(numberAdd) + +export default math diff --git a/test/_pocomath.mjs b/test/_pocomath.mjs new file mode 100644 index 0000000..756a8c0 --- /dev/null +++ b/test/_pocomath.mjs @@ -0,0 +1,10 @@ +import assert from 'assert' +import math from '../pocomath.mjs' + +describe('The default full pocomath instance "math"', () => { + it('can add numbers', () => { + assert.strictEqual(math.add(3, 4), 7) + assert.strictEqual(math.add(1.5, 2.5, 3.5), 7.5) + assert.strictEqual(math.add(Infinity), Infinity) + }) +})