diff --git a/src/core/PocomathInstance.mjs b/src/core/PocomathInstance.mjs index 22720d0..1fce6b3 100644 --- a/src/core/PocomathInstance.mjs +++ b/src/core/PocomathInstance.mjs @@ -1,8 +1,8 @@ /* Core of pocomath: create an instance */ import typed from 'typed-function' -import dependencyExtractor from './dependencyExtractor.mjs' +import {dependencyExtractor, generateTypeExtractor} from './extractors.mjs' import {makeChain} from './Chain.mjs' -import {subsetOfKeys, typesOfSignature} from './utils.mjs' +import {typesOfSignature, subsetOfKeys} from './utils.mjs' const anySpec = {} // fixed dummy specification of 'any' type @@ -461,9 +461,12 @@ export default class PocomathInstance { this._addAffect(depname, name) } for (const type of typesOfSignature(signature)) { - if (this._templateParam(type)) continue - this._usedTypes.add(type) - this._addAffect(':' + type, name) + for (const word of type.split(/[<>]/)) { + if (word.length == 0) continue + if (this._templateParam(word)) continue + this._usedTypes.add(word) + this._addAffect(':' + word, name) + } } } } @@ -526,23 +529,38 @@ export default class PocomathInstance { if (!imps) { throw new SyntaxError(`No implementations for ${name}`) } - const usableEntries = Object.entries(imps).filter( - ([signature]) => subsetOfKeys(typesOfSignature(signature), this.Types)) + /* Collect the entries we know the types for */ + const usableEntries = [] + for (const entry of Object.entries(imps)) { + let keep = true + for (const type of typesOfSignature(entry[0])) { + if (type in this.Types) continue + const baseType = type.split('<')[0] + if (baseType in this._templateTypes) continue + keep = false + break + } + if (keep) usableEntries.push(entry) + } if (usableEntries.length === 0) { throw new SyntaxError( `Every implementation for ${name} uses an undefined type;\n` + ` signatures: ${Object.keys(imps)}`) } - // Mark this method as being in the midst of being reassembled + /* Initial error checking done; mark this method as being + * in the midst of being reassembled + */ Object.defineProperty(this, name, {configurable: true, value: 'limbo'}) const tf_imps = {} for (const [rawSignature, behavior] of usableEntries) { /* Check if it's an ordinary non-template signature */ let explicit = true for (const type of typesOfSignature(rawSignature)) { - if (this._templateParam(type)) { // template types need better check - explicit = false - break + for (const word of type.split(/[<>]/)) { + if (this._templateParam(word)) { + explicit = false + break + } } } if (explicit) { @@ -591,36 +609,31 @@ export default class PocomathInstance { innerRefs[dep] = refs[outerName] } } - const original = behavior.does(innerRefs) return behavior.does(innerRefs) } this._addTFimplementation(tf_imps, signature, {uses, does: patch}) } /* Now add the catchall signature */ + let templateCall = `<${theTemplateParam}>` + /* Relying here that the base of 'Foo' is 'Foo': */ + let baseSignature = substituteInSig(trimSignature, templateCall, '') + /* Any remaining template params are top-level */ const signature = substituteInSig( - trimSignature, theTemplateParam, 'any') + baseSignature, theTemplateParam, 'any') /* The catchall signature has to detect the actual type of the call - * and add the new instantiations. We should really be using the - * typed-function parser to do the manipulations below, but we don't - * have access. The firs section prepares the type inference data: + * and add the new instantiations. + * First, prepare the type inference data: */ const parTypes = trimSignature.split(',') - const inferences = [] - const typer = entity => this.typeOf(entity) - let ambiguous = true - for (let parType of parTypes) { - parType = parType.trim() - if (parType.slice(0,3) === '...') { - parType = parType.slice(3).trim() - } - if (parType === theTemplateParam) { - inferences.push(typer) - ambiguous = false - } else { - inferences.push(false) - } - } - if (ambiguous) { + const topTyper = entity => this.typeOf(entity) + const inferences = parTypes.map( + type => generateTypeExtractor( + type, + theTemplateParam, + topTyper, + this.joinTypes.bind(this), + this._templateTypes)) + if (inferences.every(x => !x)) { // all false throw new SyntaxError( `Cannot find template parameter in ${rawSignature}`) } @@ -654,9 +667,24 @@ export default class PocomathInstance { usedConversions = true instantiateFor = self.joinTypes(argTypes, usedConversions) if (instantiateFor === 'any') { + // Need a more informative error message here throw TypeError('No common type for arguments to ' + name) } } + /* Generate the list of actual wanted types */ + const wantTypes = parTypes.map(type => substituteInSig( + type, theTemplateParam, instantiateFor)) + /* Now we have to add any actual types that are relevant + * to this invocation. Namely, that would be every formal parameter + * type in the invocation, with the parameter template instantiated + * by instantiateFor, and for all of instantiateFor's "prior types" + */ + for (j = 0; j < parTypes.length; ++j) { + if (wantTypes[i] !== parTypes[i] && wantTypes.includes('<')) { + // actually used the param and is a template + self._ensureTemplateTypes(parTypes[i], instantiateFor) + } + } /* Transform the arguments if we used any conversions: */ if (usedConversions) { i = - 1 @@ -774,4 +802,12 @@ export default class PocomathInstance { } imps[signature] = does(refs) } + + /* HERE!! This function needs to analyze the template and make sure the + * instantiations of it for type and all prior types of type are present + * in the instance + */ + _ensureTemplateTypes(template, type) { + } + } diff --git a/src/core/dependencyExtractor.mjs b/src/core/dependencyExtractor.mjs deleted file mode 100644 index 1b1091c..0000000 --- a/src/core/dependencyExtractor.mjs +++ /dev/null @@ -1,12 +0,0 @@ -/* Call this with an empty Set object S, and it returns an entity E - * from which properties can be extracted, and at any time S will - * contain all of the property names that have been extracted from E. - */ -export default function dependencyExtractor(destinationSet) { - return new Proxy({}, { - get: (target, property) => { - destinationSet.add(property) - return {} - } - }) -} diff --git a/src/core/extractors.mjs b/src/core/extractors.mjs new file mode 100644 index 0000000..ff63d9d --- /dev/null +++ b/src/core/extractors.mjs @@ -0,0 +1,41 @@ +/* Call this with an empty Set object S, and it returns an entity E + * from which properties can be extracted, and at any time S will + * contain all of the property names that have been extracted from E. + */ +export function dependencyExtractor(destinationSet) { + return new Proxy({}, { + get: (target, property) => { + destinationSet.add(property) + return {} + } + }) +} + +/* Given a (template) type name, what the template parameter is, + * a top level typer, and a library of templates, + * produces a function that will extract the instantantion type from an + * instance. Currently relies heavily on there being only unary templates. + * + * We should really be using the typed-function parser to do the + * manipulations below, but at the moment we don't have access. + */ +export function generateTypeExtractor( + type, param, topTyper, typeJoiner, templates) +{ + type = type.trim() + if (type.slice(0,3) === '...') { + type = type.slice(3).trim() + } + if (type === param) return topTyper + if (!(type.includes('<'))) return false // no template type to extract + const base = type.split('<',1)[0] + if (!(base in templates)) return false // unknown template + const arg = type.slice(base.length+1, -1) + const argExtractor = generateTypeExtractor( + arg, param, topTyper, typeJointer, templates) + if (!argExtractor) return false + return templates[base].infer({ + typeOf: argExtractor, + joinTypes: typeJoiner + }) +} diff --git a/test/core/_dependencyExtractor.mjs b/test/core/dependencyExtractor.mjs similarity index 89% rename from test/core/_dependencyExtractor.mjs rename to test/core/dependencyExtractor.mjs index 91e0e40..bc1683f 100644 --- a/test/core/_dependencyExtractor.mjs +++ b/test/core/dependencyExtractor.mjs @@ -1,5 +1,5 @@ import assert from 'assert' -import dependencyExtractor from '../../src/core/dependencyExtractor.mjs' +import {dependencyExtractor} from '../../src/core/extractors.mjs' describe('dependencyExtractor', () => { it('will record the keys of a destructuring function', () => {