feat: Template types #45
@ -1,8 +1,8 @@
|
|||||||
/* Core of pocomath: create an instance */
|
/* Core of pocomath: create an instance */
|
||||||
import typed from 'typed-function'
|
import typed from 'typed-function'
|
||||||
import dependencyExtractor from './dependencyExtractor.mjs'
|
import {dependencyExtractor, generateTypeExtractor} from './extractors.mjs'
|
||||||
import {makeChain} from './Chain.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
|
const anySpec = {} // fixed dummy specification of 'any' type
|
||||||
|
|
||||||
@ -461,9 +461,12 @@ export default class PocomathInstance {
|
|||||||
this._addAffect(depname, name)
|
this._addAffect(depname, name)
|
||||||
}
|
}
|
||||||
for (const type of typesOfSignature(signature)) {
|
for (const type of typesOfSignature(signature)) {
|
||||||
if (this._templateParam(type)) continue
|
for (const word of type.split(/[<>]/)) {
|
||||||
this._usedTypes.add(type)
|
if (word.length == 0) continue
|
||||||
this._addAffect(':' + type, name)
|
if (this._templateParam(word)) continue
|
||||||
|
this._usedTypes.add(word)
|
||||||
|
this._addAffect(':' + word, name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -526,25 +529,40 @@ export default class PocomathInstance {
|
|||||||
if (!imps) {
|
if (!imps) {
|
||||||
throw new SyntaxError(`No implementations for ${name}`)
|
throw new SyntaxError(`No implementations for ${name}`)
|
||||||
}
|
}
|
||||||
const usableEntries = Object.entries(imps).filter(
|
/* Collect the entries we know the types for */
|
||||||
([signature]) => subsetOfKeys(typesOfSignature(signature), this.Types))
|
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) {
|
if (usableEntries.length === 0) {
|
||||||
throw new SyntaxError(
|
throw new SyntaxError(
|
||||||
`Every implementation for ${name} uses an undefined type;\n`
|
`Every implementation for ${name} uses an undefined type;\n`
|
||||||
+ ` signatures: ${Object.keys(imps)}`)
|
+ ` 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'})
|
Object.defineProperty(this, name, {configurable: true, value: 'limbo'})
|
||||||
const tf_imps = {}
|
const tf_imps = {}
|
||||||
for (const [rawSignature, behavior] of usableEntries) {
|
for (const [rawSignature, behavior] of usableEntries) {
|
||||||
/* Check if it's an ordinary non-template signature */
|
/* Check if it's an ordinary non-template signature */
|
||||||
let explicit = true
|
let explicit = true
|
||||||
for (const type of typesOfSignature(rawSignature)) {
|
for (const type of typesOfSignature(rawSignature)) {
|
||||||
if (this._templateParam(type)) { // template types need better check
|
for (const word of type.split(/[<>]/)) {
|
||||||
|
if (this._templateParam(word)) {
|
||||||
explicit = false
|
explicit = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (explicit) {
|
if (explicit) {
|
||||||
this._addTFimplementation(tf_imps, rawSignature, behavior)
|
this._addTFimplementation(tf_imps, rawSignature, behavior)
|
||||||
continue
|
continue
|
||||||
@ -591,36 +609,31 @@ export default class PocomathInstance {
|
|||||||
innerRefs[dep] = refs[outerName]
|
innerRefs[dep] = refs[outerName]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const original = behavior.does(innerRefs)
|
|
||||||
return behavior.does(innerRefs)
|
return behavior.does(innerRefs)
|
||||||
}
|
}
|
||||||
this._addTFimplementation(tf_imps, signature, {uses, does: patch})
|
this._addTFimplementation(tf_imps, signature, {uses, does: patch})
|
||||||
}
|
}
|
||||||
/* Now add the catchall signature */
|
/* Now add the catchall signature */
|
||||||
|
let templateCall = `<${theTemplateParam}>`
|
||||||
|
/* Relying here that the base of 'Foo<T>' is 'Foo': */
|
||||||
|
let baseSignature = substituteInSig(trimSignature, templateCall, '')
|
||||||
|
/* Any remaining template params are top-level */
|
||||||
const signature = substituteInSig(
|
const signature = substituteInSig(
|
||||||
trimSignature, theTemplateParam, 'any')
|
baseSignature, theTemplateParam, 'any')
|
||||||
/* The catchall signature has to detect the actual type of the call
|
/* The catchall signature has to detect the actual type of the call
|
||||||
* and add the new instantiations. We should really be using the
|
* and add the new instantiations.
|
||||||
* typed-function parser to do the manipulations below, but we don't
|
* First, prepare the type inference data:
|
||||||
* have access. The firs section prepares the type inference data:
|
|
||||||
*/
|
*/
|
||||||
const parTypes = trimSignature.split(',')
|
const parTypes = trimSignature.split(',')
|
||||||
const inferences = []
|
const topTyper = entity => this.typeOf(entity)
|
||||||
const typer = entity => this.typeOf(entity)
|
const inferences = parTypes.map(
|
||||||
let ambiguous = true
|
type => generateTypeExtractor(
|
||||||
for (let parType of parTypes) {
|
type,
|
||||||
parType = parType.trim()
|
theTemplateParam,
|
||||||
if (parType.slice(0,3) === '...') {
|
topTyper,
|
||||||
parType = parType.slice(3).trim()
|
this.joinTypes.bind(this),
|
||||||
}
|
this._templateTypes))
|
||||||
if (parType === theTemplateParam) {
|
if (inferences.every(x => !x)) { // all false
|
||||||
inferences.push(typer)
|
|
||||||
ambiguous = false
|
|
||||||
} else {
|
|
||||||
inferences.push(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ambiguous) {
|
|
||||||
throw new SyntaxError(
|
throw new SyntaxError(
|
||||||
`Cannot find template parameter in ${rawSignature}`)
|
`Cannot find template parameter in ${rawSignature}`)
|
||||||
}
|
}
|
||||||
@ -654,9 +667,24 @@ export default class PocomathInstance {
|
|||||||
usedConversions = true
|
usedConversions = true
|
||||||
instantiateFor = self.joinTypes(argTypes, usedConversions)
|
instantiateFor = self.joinTypes(argTypes, usedConversions)
|
||||||
if (instantiateFor === 'any') {
|
if (instantiateFor === 'any') {
|
||||||
|
// Need a more informative error message here
|
||||||
throw TypeError('No common type for arguments to ' + name)
|
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: */
|
/* Transform the arguments if we used any conversions: */
|
||||||
if (usedConversions) {
|
if (usedConversions) {
|
||||||
i = - 1
|
i = - 1
|
||||||
@ -774,4 +802,12 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
imps[signature] = does(refs)
|
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) {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
41
src/core/extractors.mjs
Normal file
41
src/core/extractors.mjs
Normal file
@ -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
|
||||||
|
})
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import dependencyExtractor from '../../src/core/dependencyExtractor.mjs'
|
import {dependencyExtractor} from '../../src/core/extractors.mjs'
|
||||||
|
|
||||||
describe('dependencyExtractor', () => {
|
describe('dependencyExtractor', () => {
|
||||||
it('will record the keys of a destructuring function', () => {
|
it('will record the keys of a destructuring function', () => {
|
Loading…
Reference in New Issue
Block a user