2022-07-19 00:08:49 +00:00
|
|
|
/* Core of pocomath: create an instance */
|
|
|
|
import typed from 'typed-function'
|
2022-08-05 12:48:57 +00:00
|
|
|
import {dependencyExtractor, generateTypeExtractor} from './extractors.mjs'
|
2022-08-01 23:24:20 +00:00
|
|
|
import {makeChain} from './Chain.mjs'
|
2022-08-05 12:48:57 +00:00
|
|
|
import {typeListOfSignature, typesOfSignature, subsetOfKeys} from './utils.mjs'
|
2022-07-23 05:06:48 +00:00
|
|
|
|
2022-07-28 05:28:40 +00:00
|
|
|
const anySpec = {} // fixed dummy specification of 'any' type
|
|
|
|
|
2022-08-01 10:09:32 +00:00
|
|
|
const theTemplateParam = 'T' // First pass: only allow this one exact parameter
|
2022-08-05 12:48:57 +00:00
|
|
|
const templateFromParam = 'U' // For defining covariant conversions
|
2022-08-01 10:09:32 +00:00
|
|
|
|
|
|
|
/* Returns a new signature just like sig but with the parameter replaced by
|
|
|
|
* the type
|
|
|
|
*/
|
|
|
|
function substituteInSig(sig, parameter, type) {
|
|
|
|
const pattern = new RegExp("\\b" + parameter + "\\b", 'g')
|
|
|
|
return sig.replaceAll(pattern, type)
|
|
|
|
}
|
|
|
|
|
2022-07-19 00:08:49 +00:00
|
|
|
export default class PocomathInstance {
|
2022-07-22 21:25:26 +00:00
|
|
|
/* Disallowed names for ops; beware, this is slightly non-DRY
|
|
|
|
* in that if a new top-level PocomathInstance method is added, its name
|
|
|
|
* must be added to this list.
|
|
|
|
*/
|
2022-07-25 18:56:12 +00:00
|
|
|
static reserved = new Set([
|
2022-08-01 23:24:20 +00:00
|
|
|
'chain',
|
2022-07-28 05:28:40 +00:00
|
|
|
'config',
|
|
|
|
'importDependencies',
|
|
|
|
'install',
|
|
|
|
'installType',
|
2022-08-06 15:27:44 +00:00
|
|
|
'instantiateTemplate',
|
2022-08-05 12:48:57 +00:00
|
|
|
'joinTypes',
|
2022-07-28 05:28:40 +00:00
|
|
|
'name',
|
2022-08-05 12:48:57 +00:00
|
|
|
'self',
|
|
|
|
'Templates',
|
2022-08-01 10:09:32 +00:00
|
|
|
'typeOf',
|
2022-07-30 02:43:24 +00:00
|
|
|
'Types',
|
|
|
|
'undefinedTypes'
|
|
|
|
])
|
2022-07-22 21:25:26 +00:00
|
|
|
|
2022-07-19 00:08:49 +00:00
|
|
|
constructor(name) {
|
|
|
|
this.name = name
|
|
|
|
this._imps = {}
|
2022-07-19 18:54:22 +00:00
|
|
|
this._affects = {}
|
2022-07-22 20:49:14 +00:00
|
|
|
this._typed = typed.create()
|
|
|
|
this._typed.clear()
|
2022-08-05 12:48:57 +00:00
|
|
|
this._typed.addTypes([{name: 'ground', test: () => true}])
|
2022-08-01 10:09:32 +00:00
|
|
|
/* List of types installed in the instance. We start with just dummies
|
|
|
|
* for the 'any' type and for type parameters:
|
|
|
|
*/
|
|
|
|
this.Types = {any: anySpec}
|
|
|
|
this.Types[theTemplateParam] = anySpec
|
2022-08-05 12:48:57 +00:00
|
|
|
this.Types.ground = anySpec
|
|
|
|
// All the template types that have been defined
|
|
|
|
this.Templates = {}
|
|
|
|
// The actual type testing functions
|
|
|
|
this._typeTests = {}
|
2022-07-30 11:59:04 +00:00
|
|
|
this._subtypes = {} // For each type, gives all of its (in)direct subtypes
|
2022-08-01 10:09:32 +00:00
|
|
|
/* The following gives for each type, a set of all types that could
|
|
|
|
* match in typed-function's dispatch algorithm before the given type.
|
|
|
|
* This is important because if we instantiate a template, we must
|
|
|
|
* instantiate it for all prior types as well, or else the wrong instance
|
|
|
|
* might match.
|
|
|
|
*/
|
|
|
|
this._priorTypes = {}
|
2022-08-05 12:48:57 +00:00
|
|
|
this._seenTypes = new Set() // all types that have occurred in a signature
|
feat(quaternion): Add convenience quaternion creator function
Even in the setup just prior to this commit, a quaternion with entries
of type `number` is simply a `Complex<Complex<number>>`
So if we provide a convenience wrapper to create sucha thing, we
instantly have a quaternion data type. All of the operations come for
"free" if they were properly defined for the `Complex` template.
Multiplication already was, `abs` needed a little tweak, but there is
absolutely no "extra" code to support quaternions. (This commit
does not go through and check all arithmetic functions for proper operation
and tweak those that still need some generalization.)
Note that with the recursive template instantiation, a limit had to be placed
on template instantiation depth. The limit moves deeper as actual arguments
that are deeper nested instantiations are seen, so as long as one doesn't
immediately invoke a triply-nested template, for example, the limit will
never prevent an actual computation. It just prevents a runaway in the types
that Pocomath thinks it needs to know about. (Basically before, using the
quaternion creator would produce `Complex<Complex<number>>`. Then when you
called it again, Pocomath would think "Maybe I will need
`Complex<Complex<Complex<number>>>`?!" and create that, even though it had
never seen that, and then another level next time, and so on. The limit
just stops this progression one level beyond any nesting depth that's
actually been observed.
2022-08-07 03:13:50 +00:00
|
|
|
this._maxDepthSeen = 1 // deepest template nesting we've actually encountered
|
2022-08-05 12:48:57 +00:00
|
|
|
this._invalid = new Set() // methods that are currently invalid
|
2022-08-01 10:09:32 +00:00
|
|
|
this._config = {predictable: false, epsilon: 1e-12}
|
2022-07-25 11:20:13 +00:00
|
|
|
const self = this
|
|
|
|
this.config = new Proxy(this._config, {
|
|
|
|
get: (target, property) => target[property],
|
|
|
|
set: (target, property, value) => {
|
|
|
|
if (value !== target[property]) {
|
|
|
|
target[property] = value
|
|
|
|
self._invalidateDependents('config')
|
|
|
|
}
|
|
|
|
return true // successful
|
|
|
|
}
|
|
|
|
})
|
2022-08-02 07:47:37 +00:00
|
|
|
this._plainFunctions = new Set() // the names of the plain functions
|
2022-08-01 23:24:20 +00:00
|
|
|
this._chainRepository = {} // place to store chainified functions
|
2022-08-05 12:48:57 +00:00
|
|
|
|
|
|
|
this._installFunctions({
|
|
|
|
typeOf: {ground: {uses: new Set(), does: () => () => 'any'}}
|
|
|
|
})
|
|
|
|
|
|
|
|
this.joinTypes = this.joinTypes.bind(this)
|
2022-07-19 00:08:49 +00:00
|
|
|
}
|
2022-07-24 19:52:05 +00:00
|
|
|
|
2022-07-19 00:08:49 +00:00
|
|
|
/**
|
|
|
|
* (Partially) define one or more operations of the instance:
|
|
|
|
*
|
2022-07-28 05:28:40 +00:00
|
|
|
* The sole parameter can be another Pocomath instance, in which case all
|
|
|
|
* of the types and operations of the other instance are installed in this
|
|
|
|
* one, or it can be a plain object as described below.
|
|
|
|
*
|
|
|
|
* @param {Object<string,
|
|
|
|
* PocomathInstance
|
|
|
|
* | Object<Signature, ({deps})=> implementation>>} ops
|
2022-07-19 00:08:49 +00:00
|
|
|
* The only parameter ops gives the semantics of the operations to install.
|
2022-07-28 05:28:40 +00:00
|
|
|
* The keys are operation names. The value for a key could be
|
|
|
|
* a PocomathInstance, in which case it is simply merged into this
|
|
|
|
* instance.
|
|
|
|
*
|
|
|
|
* Otherwise, ops must be an object
|
2022-07-23 16:55:02 +00:00
|
|
|
* mapping each desired (typed-function) signature to a function taking
|
|
|
|
* a dependency object to an implementation.
|
2022-07-23 05:06:48 +00:00
|
|
|
*
|
2022-07-23 16:55:02 +00:00
|
|
|
* For more detail, such functions should have the format
|
|
|
|
* ```
|
|
|
|
* ({depA, depB, depC: aliasC, ...}) => (opArg1, opArg2) => <result>
|
|
|
|
* ```
|
|
|
|
* where the `depA`, `depB` etc. are the names of the
|
|
|
|
* operations this implementation depends on; those operations can
|
|
|
|
* then be referred to directly by the identifiers `depA` and `depB`
|
|
|
|
* in the code for the '<result>`, or when an alias has been given
|
|
|
|
* as in the case of `depC`, by the identifier `aliasC`.
|
|
|
|
* Given an object that has these dependencies with these keys, the
|
|
|
|
* function returns a function taking the operation arguments to the
|
|
|
|
* desired result of the operation.
|
2022-07-23 05:06:48 +00:00
|
|
|
*
|
2022-07-23 16:55:02 +00:00
|
|
|
* You can specify that an operation depends on itself by using the
|
|
|
|
* special dependency identifier 'self'.
|
2022-07-23 05:06:48 +00:00
|
|
|
*
|
2022-07-23 16:55:02 +00:00
|
|
|
* You can specify that an implementation depends on just a specific
|
|
|
|
* signature of the given operation by suffixing the dependency name
|
|
|
|
* with the signature in parentheses, e.g. `add(number,number)` to
|
|
|
|
* refer to just adding two numbers. In this case, it is of course
|
|
|
|
* necessary to specify an alias to be able to refer to the supplied
|
2022-07-28 05:28:40 +00:00
|
|
|
* operation in the body of the implementation.
|
2022-08-01 10:09:32 +00:00
|
|
|
*
|
|
|
|
* You can specify template implementations. If any item in the signature
|
|
|
|
* contains the word 'T' (currently the only allowed type parameter) then
|
|
|
|
* the signature/implementation is a template. The T can match any type
|
|
|
|
* of argument, and it may appear in the dependencies, where it is
|
|
|
|
* replaced by the matching type. A bare 'T' in the dependencies will be
|
|
|
|
* supplied with the name of the type as its value. See the implementation
|
|
|
|
* of `subtract` for an example.
|
|
|
|
* Usually templates are instantiated as needed, but for some heavily
|
|
|
|
* used functions, or functions with non-template signatures that refer
|
|
|
|
* to signatures generated from a template, it makes more sense to just
|
|
|
|
* instantiate the template immediately for all known types. This eager
|
|
|
|
* instantiation can be accomplished by prefixin the signature with an
|
|
|
|
* exclamation point.
|
2022-07-19 00:08:49 +00:00
|
|
|
*/
|
|
|
|
install(ops) {
|
2022-07-28 05:28:40 +00:00
|
|
|
if (ops instanceof PocomathInstance) {
|
|
|
|
return _installInstance(ops)
|
|
|
|
}
|
|
|
|
/* Standardize the format of all implementations, weeding out
|
|
|
|
* any other instances as we go
|
|
|
|
*/
|
|
|
|
const stdFunctions = {}
|
2022-07-24 19:52:05 +00:00
|
|
|
for (const [item, spec] of Object.entries(ops)) {
|
2022-07-28 05:28:40 +00:00
|
|
|
if (spec instanceof PocomathInstance) {
|
|
|
|
this._installInstance(spec)
|
2022-08-02 07:47:37 +00:00
|
|
|
} else if (typeof spec === 'function') {
|
|
|
|
stdFunctions[item] = spec
|
2022-07-24 19:52:05 +00:00
|
|
|
} else {
|
2022-07-28 05:28:40 +00:00
|
|
|
if (item.charAt(0) === '_') {
|
|
|
|
throw new SyntaxError(
|
|
|
|
`Pocomath: Cannot install ${item}, `
|
|
|
|
+ 'initial _ reserved for internal use.')
|
|
|
|
}
|
|
|
|
if (PocomathInstance.reserved.has(item)) {
|
|
|
|
throw new SyntaxError(
|
|
|
|
`Pocomath: reserved function '${item}' cannot be modified.`)
|
|
|
|
}
|
|
|
|
const stdimps = {}
|
|
|
|
for (const [signature, does] of Object.entries(spec)) {
|
|
|
|
const uses = new Set()
|
|
|
|
does(dependencyExtractor(uses))
|
|
|
|
stdimps[signature] = {uses, does}
|
|
|
|
}
|
|
|
|
stdFunctions[item] = stdimps
|
2022-07-24 19:52:05 +00:00
|
|
|
}
|
|
|
|
}
|
2022-07-28 05:28:40 +00:00
|
|
|
this._installFunctions(stdFunctions)
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Merge any number of PocomathInstances or modules: */
|
|
|
|
static merge(name, ...pieces) {
|
|
|
|
const result = new PocomathInstance(name)
|
|
|
|
for (const piece of pieces) {
|
|
|
|
result.install(piece)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2022-08-01 23:24:20 +00:00
|
|
|
/* Return a chain object for this instance with a given value: */
|
|
|
|
chain(value) {
|
|
|
|
return makeChain(value, this, this._chainRepository)
|
|
|
|
}
|
|
|
|
|
2022-07-28 05:28:40 +00:00
|
|
|
_installInstance(other) {
|
|
|
|
for (const [type, spec] of Object.entries(other.Types)) {
|
2022-08-05 12:48:57 +00:00
|
|
|
if (spec === anySpec) continue
|
2022-07-28 05:28:40 +00:00
|
|
|
this.installType(type, spec)
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
for (const [base, info] of Object.entries(other.Templates)) {
|
|
|
|
this._installTemplateType(info.type, info.spec)
|
|
|
|
}
|
2022-08-01 10:09:32 +00:00
|
|
|
const migrateImps = {}
|
|
|
|
for (const operator in other._imps) {
|
|
|
|
if (operator != 'typeOf') { // skip the builtin, we already have it
|
|
|
|
migrateImps[operator] = other._imps[operator]
|
|
|
|
}
|
|
|
|
}
|
2022-08-02 07:47:37 +00:00
|
|
|
for (const plain of other._plainFunctions) {
|
|
|
|
migrateImps[plain] = other[plain]
|
|
|
|
}
|
2022-08-01 10:09:32 +00:00
|
|
|
this._installFunctions(migrateImps)
|
2022-07-19 00:08:49 +00:00
|
|
|
}
|
|
|
|
|
2022-07-23 02:41:59 +00:00
|
|
|
/**
|
|
|
|
* Import (and install) all dependencies of previously installed functions,
|
|
|
|
* for the specified types.
|
|
|
|
*
|
|
|
|
* @param {string[]} types A list of type names
|
|
|
|
*/
|
|
|
|
async importDependencies(types) {
|
2022-07-25 11:20:13 +00:00
|
|
|
const typeSet = new Set(types)
|
|
|
|
typeSet.add('generic')
|
2022-07-23 02:41:59 +00:00
|
|
|
const doneSet = new Set(['self']) // nothing to do for self dependencies
|
|
|
|
while (true) {
|
|
|
|
const requiredSet = new Set()
|
|
|
|
/* Grab all of the known deps */
|
|
|
|
for (const func in this._imps) {
|
|
|
|
if (func === 'Types') continue
|
2022-07-23 16:55:02 +00:00
|
|
|
for (const {uses} of Object.values(this._imps[func])) {
|
|
|
|
for (const dependency of uses) {
|
2022-07-23 02:41:59 +00:00
|
|
|
const depName = dependency.split('(',1)[0]
|
|
|
|
if (doneSet.has(depName)) continue
|
|
|
|
requiredSet.add(depName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (requiredSet.size === 0) break
|
|
|
|
for (const name of requiredSet) {
|
2022-07-25 11:20:13 +00:00
|
|
|
for (const type of typeSet) {
|
2022-07-23 02:41:59 +00:00
|
|
|
try {
|
|
|
|
const modName = `../${type}/${name}.mjs`
|
|
|
|
const mod = await import(modName)
|
|
|
|
this.install(mod)
|
|
|
|
} catch (err) {
|
2022-08-01 10:09:32 +00:00
|
|
|
if (!(err.message.includes('find'))) {
|
|
|
|
// Not just a error because module doesn't exist
|
|
|
|
// So take it seriously
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
// We don't care if a module doesn't exist, so merely proceed
|
2022-07-23 02:41:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
doneSet.add(name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-07-24 19:52:05 +00:00
|
|
|
|
2022-07-28 05:28:40 +00:00
|
|
|
/* Used to install a type in a PocomathInstance.
|
|
|
|
*
|
|
|
|
* @param {string} name The name of the type
|
|
|
|
* @param {{test: any => bool, // the predicate for the type
|
|
|
|
* from: Record<string, <that type> => <type name>> // conversions
|
|
|
|
* before: string[] // lower priority types
|
2022-08-07 16:19:27 +00:00
|
|
|
* refines: string // The type this is a subtype of
|
2022-07-28 05:28:40 +00:00
|
|
|
* }} specification
|
|
|
|
*
|
|
|
|
* The second parameter of this function specifies the structure of the
|
|
|
|
* type via a plain
|
|
|
|
* object with the following properties:
|
|
|
|
*
|
|
|
|
* - test: the predicate for the type
|
|
|
|
* - from: a plain object mapping the names of types that can be converted
|
|
|
|
* **to** this type to the corresponding conversion functions
|
|
|
|
* - before: [optional] a list of types this should be added
|
|
|
|
* before, in priority order
|
2022-07-30 11:59:04 +00:00
|
|
|
* - refines: [optional] the name of a type that this is a subtype
|
|
|
|
* of. This means the test is the conjunction of the given test and
|
|
|
|
* the supertype test, and that it must come before the supertype.
|
2022-07-24 19:52:05 +00:00
|
|
|
*/
|
2022-07-28 05:28:40 +00:00
|
|
|
/*
|
|
|
|
* Implementation note: unlike _installFunctions below, we can make
|
|
|
|
* the corresponding changes to the _typed object immediately
|
|
|
|
*/
|
|
|
|
installType(type, spec) {
|
2022-08-05 12:48:57 +00:00
|
|
|
const parts = type.split(/[<,>]/)
|
|
|
|
if (this._templateParam(parts[0])) {
|
2022-08-01 10:09:32 +00:00
|
|
|
throw new SyntaxError(
|
|
|
|
`Type name '${type}' reserved for template parameter`)
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
if (parts.some(this._templateParam.bind(this))) {
|
|
|
|
// It's a template, deal with it separately
|
|
|
|
return this._installTemplateType(type, spec)
|
|
|
|
}
|
2022-07-25 18:56:12 +00:00
|
|
|
if (type in this.Types) {
|
|
|
|
if (spec !== this.Types[type]) {
|
|
|
|
throw new SyntaxError(`Conflicting definitions of type ${type}`)
|
2022-07-24 19:52:05 +00:00
|
|
|
}
|
2022-07-25 18:56:12 +00:00
|
|
|
return
|
|
|
|
}
|
2022-07-30 11:59:04 +00:00
|
|
|
if (spec.refines && !(spec.refines in this.Types)) {
|
|
|
|
throw new SyntaxError(
|
|
|
|
`Cannot install ${type} before its supertype ${spec.refines}`)
|
|
|
|
}
|
|
|
|
let beforeType = spec.refines
|
|
|
|
if (!beforeType) {
|
2022-08-05 12:48:57 +00:00
|
|
|
beforeType = 'ground'
|
2022-07-30 11:59:04 +00:00
|
|
|
for (const other of spec.before || []) {
|
|
|
|
if (other in this.Types) {
|
|
|
|
beforeType = other
|
|
|
|
break
|
|
|
|
}
|
2022-07-24 19:52:05 +00:00
|
|
|
}
|
2022-07-25 18:56:12 +00:00
|
|
|
}
|
2022-07-30 11:59:04 +00:00
|
|
|
let testFn = spec.test
|
|
|
|
if (spec.refines) {
|
|
|
|
const supertypeTest = this.Types[spec.refines].test
|
|
|
|
testFn = entity => supertypeTest(entity) && spec.test(entity)
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
this._typeTests[type] = testFn
|
2022-07-30 11:59:04 +00:00
|
|
|
this._typed.addTypes([{name: type, test: testFn}], beforeType)
|
|
|
|
this.Types[type] = spec
|
2022-08-05 12:48:57 +00:00
|
|
|
this._subtypes[type] = new Set()
|
2022-08-01 10:09:32 +00:00
|
|
|
this._priorTypes[type] = new Set()
|
2022-08-05 12:48:57 +00:00
|
|
|
// Update all the subtype sets of supertypes up the chain
|
|
|
|
let nextSuper = spec.refines
|
|
|
|
while (nextSuper) {
|
|
|
|
this._invalidateDependents(':' + nextSuper)
|
|
|
|
this._priorTypes[nextSuper].add(type)
|
|
|
|
this._subtypes[nextSuper].add(type)
|
|
|
|
nextSuper = this.Types[nextSuper].refines
|
|
|
|
}
|
2022-07-25 18:56:12 +00:00
|
|
|
/* Now add conversions to this type */
|
|
|
|
for (const from in (spec.from || {})) {
|
|
|
|
if (from in this.Types) {
|
2022-07-30 11:59:04 +00:00
|
|
|
// add conversions from "from" to this one and all its supertypes:
|
|
|
|
let nextSuper = type
|
|
|
|
while (nextSuper) {
|
2022-08-05 12:48:57 +00:00
|
|
|
if (this._priorTypes[nextSuper].has(from)) break
|
feat(quaternion): Add convenience quaternion creator function
Even in the setup just prior to this commit, a quaternion with entries
of type `number` is simply a `Complex<Complex<number>>`
So if we provide a convenience wrapper to create sucha thing, we
instantly have a quaternion data type. All of the operations come for
"free" if they were properly defined for the `Complex` template.
Multiplication already was, `abs` needed a little tweak, but there is
absolutely no "extra" code to support quaternions. (This commit
does not go through and check all arithmetic functions for proper operation
and tweak those that still need some generalization.)
Note that with the recursive template instantiation, a limit had to be placed
on template instantiation depth. The limit moves deeper as actual arguments
that are deeper nested instantiations are seen, so as long as one doesn't
immediately invoke a triply-nested template, for example, the limit will
never prevent an actual computation. It just prevents a runaway in the types
that Pocomath thinks it needs to know about. (Basically before, using the
quaternion creator would produce `Complex<Complex<number>>`. Then when you
called it again, Pocomath would think "Maybe I will need
`Complex<Complex<Complex<number>>>`?!" and create that, even though it had
never seen that, and then another level next time, and so on. The limit
just stops this progression one level beyond any nesting depth that's
actually been observed.
2022-08-07 03:13:50 +00:00
|
|
|
if (from === nextSuper) break
|
2022-07-30 11:59:04 +00:00
|
|
|
this._typed.addConversion(
|
|
|
|
{from, to: nextSuper, convert: spec.from[from]})
|
2022-08-01 10:09:32 +00:00
|
|
|
this._invalidateDependents(':' + nextSuper)
|
|
|
|
this._priorTypes[nextSuper].add(from)
|
2022-08-05 12:48:57 +00:00
|
|
|
/* And all of the subtypes of from are now prior as well: */
|
|
|
|
for (const subtype of this._subtypes[from]) {
|
|
|
|
this._priorTypes[nextSuper].add(subtype)
|
|
|
|
}
|
2022-07-30 11:59:04 +00:00
|
|
|
nextSuper = this.Types[nextSuper].refines
|
|
|
|
}
|
2022-07-24 19:52:05 +00:00
|
|
|
}
|
2022-07-25 18:56:12 +00:00
|
|
|
}
|
|
|
|
/* And add conversions from this type */
|
|
|
|
for (const to in this.Types) {
|
2022-08-05 12:48:57 +00:00
|
|
|
for (const fromtype in this.Types[to].from) {
|
|
|
|
if (type == fromtype
|
|
|
|
|| (fromtype in this._subtypes
|
|
|
|
&& this._subtypes[fromtype].has(type))) {
|
|
|
|
if (spec.refines == to || spec.refines in this._subtypes[to]) {
|
|
|
|
throw new SyntaxError(
|
|
|
|
`Conversion of ${type} to its supertype ${to} disallowed.`)
|
|
|
|
}
|
|
|
|
let nextSuper = to
|
|
|
|
while (nextSuper) {
|
feat(quaternion): Add convenience quaternion creator function
Even in the setup just prior to this commit, a quaternion with entries
of type `number` is simply a `Complex<Complex<number>>`
So if we provide a convenience wrapper to create sucha thing, we
instantly have a quaternion data type. All of the operations come for
"free" if they were properly defined for the `Complex` template.
Multiplication already was, `abs` needed a little tweak, but there is
absolutely no "extra" code to support quaternions. (This commit
does not go through and check all arithmetic functions for proper operation
and tweak those that still need some generalization.)
Note that with the recursive template instantiation, a limit had to be placed
on template instantiation depth. The limit moves deeper as actual arguments
that are deeper nested instantiations are seen, so as long as one doesn't
immediately invoke a triply-nested template, for example, the limit will
never prevent an actual computation. It just prevents a runaway in the types
that Pocomath thinks it needs to know about. (Basically before, using the
quaternion creator would produce `Complex<Complex<number>>`. Then when you
called it again, Pocomath would think "Maybe I will need
`Complex<Complex<Complex<number>>>`?!" and create that, even though it had
never seen that, and then another level next time, and so on. The limit
just stops this progression one level beyond any nesting depth that's
actually been observed.
2022-08-07 03:13:50 +00:00
|
|
|
if (type === nextSuper) break
|
|
|
|
try { // may already be a conversion, and no way to ask
|
|
|
|
this._typed.addConversion({
|
|
|
|
from: type,
|
|
|
|
to: nextSuper,
|
|
|
|
convert: this.Types[to].from[fromtype]
|
|
|
|
})
|
|
|
|
this._invalidateDependents(':' + nextSuper)
|
|
|
|
} catch {
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
this._priorTypes[nextSuper].add(type)
|
|
|
|
nextSuper = this.Types[nextSuper].refines
|
|
|
|
}
|
2022-07-30 11:59:04 +00:00
|
|
|
}
|
2022-07-24 19:52:05 +00:00
|
|
|
}
|
|
|
|
}
|
2022-08-01 10:09:32 +00:00
|
|
|
// update the typeOf function
|
|
|
|
const imp = {}
|
|
|
|
imp[type] = {uses: new Set(), does: () => () => type}
|
|
|
|
this._installFunctions({typeOf: imp})
|
2022-07-24 19:52:05 +00:00
|
|
|
}
|
2022-07-25 18:56:12 +00:00
|
|
|
|
2022-08-05 12:48:57 +00:00
|
|
|
/* Returns the most refined type of all the types in the array, with
|
|
|
|
* '' standing for the empty type for convenience. If the second
|
|
|
|
* argument `convert` is true, a convertible type is considered a
|
|
|
|
* a subtype (defaults to false).
|
|
|
|
*/
|
|
|
|
joinTypes(types, convert) {
|
|
|
|
let join = ''
|
|
|
|
for (const type of types) {
|
|
|
|
join = this._joinTypes(join, type, convert)
|
|
|
|
}
|
|
|
|
return join
|
|
|
|
}
|
|
|
|
/* helper for above */
|
|
|
|
_joinTypes(typeA, typeB, convert) {
|
|
|
|
if (!typeA) return typeB
|
|
|
|
if (!typeB) return typeA
|
|
|
|
if (typeA === 'any' || typeB === 'any') return 'any'
|
|
|
|
if (typeA === 'ground' || typeB === 'ground') return 'ground'
|
|
|
|
if (typeA === typeB) return typeA
|
|
|
|
const subber = convert ? this._priorTypes : this._subtypes
|
|
|
|
if (subber[typeB].has(typeA)) return typeB
|
|
|
|
/* OK, so we need the most refined supertype of A that contains B:
|
|
|
|
*/
|
|
|
|
let nextSuper = typeA
|
|
|
|
while (nextSuper) {
|
|
|
|
if (subber[nextSuper].has(typeB)) return nextSuper
|
|
|
|
nextSuper = this.Types[nextSuper].refines
|
|
|
|
}
|
|
|
|
/* And if conversions are allowed, we have to search the other way too */
|
|
|
|
if (convert) {
|
|
|
|
nextSuper = typeB
|
|
|
|
while (nextSuper) {
|
|
|
|
if (subber[nextSuper].has(typeA)) return nextSuper
|
|
|
|
nextSuper = this.Types[nextSuper].refines
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 'any'
|
|
|
|
}
|
|
|
|
|
2022-07-30 02:43:24 +00:00
|
|
|
/* Returns a list of all types that have been mentioned in the
|
|
|
|
* signatures of operations, but which have not actually been installed:
|
|
|
|
*/
|
|
|
|
undefinedTypes() {
|
2022-08-05 12:48:57 +00:00
|
|
|
return Array.from(this._seenTypes).filter(t => !(t in this.Types))
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Used internally to install a template type */
|
|
|
|
_installTemplateType(type, spec) {
|
|
|
|
const base = type.split('<')[0]
|
|
|
|
/* For now, just allow a single template per base type; that
|
|
|
|
* might need to change later:
|
|
|
|
*/
|
|
|
|
if (base in this.Templates) {
|
|
|
|
if (spec !== this.Templates[base].spec) {
|
|
|
|
throw new SyntaxError(
|
|
|
|
`Conflicting definitions of template type ${type}`)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2022-08-06 15:27:44 +00:00
|
|
|
// update the typeOf function
|
|
|
|
const imp = {}
|
|
|
|
imp[type] = {uses: new Set(['T']), does: ({T}) => () => `${base}<${T}>`}
|
|
|
|
this._installFunctions({typeOf: imp})
|
|
|
|
|
|
|
|
// Nothing else actually happens until we match a template parameter
|
2022-08-05 12:48:57 +00:00
|
|
|
this.Templates[base] = {type, spec}
|
2022-07-30 02:43:24 +00:00
|
|
|
}
|
|
|
|
|
2022-07-19 00:08:49 +00:00
|
|
|
/* Used internally by install, see the documentation there */
|
2022-07-28 05:28:40 +00:00
|
|
|
_installFunctions(functions) {
|
|
|
|
for (const [name, spec] of Object.entries(functions)) {
|
2022-08-02 07:47:37 +00:00
|
|
|
if (typeof spec === 'function') {
|
|
|
|
if (name in this) {
|
|
|
|
if (spec === this[name]) continue
|
|
|
|
throw new SyntaxError(`Attempt to redefine function ${name}`)
|
|
|
|
}
|
|
|
|
this._plainFunctions.add(name)
|
|
|
|
this[name] = spec
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// new implementations, first check the name isn't taken
|
|
|
|
if (this._plainFunctions.has(name)) {
|
|
|
|
throw new SyntaxError(
|
|
|
|
`Can't add implementations to function ${name}`)
|
|
|
|
}
|
|
|
|
// All clear, so set the op up to lazily recreate itself
|
2022-07-28 05:28:40 +00:00
|
|
|
this._invalidate(name)
|
|
|
|
const opImps = this._imps[name]
|
|
|
|
for (const [signature, behavior] of Object.entries(spec)) {
|
|
|
|
if (signature in opImps) {
|
|
|
|
if (behavior.does !== opImps[signature].does) {
|
|
|
|
throw new SyntaxError(
|
|
|
|
`Conflicting definitions of ${signature} for ${name}`)
|
|
|
|
}
|
|
|
|
} else {
|
2022-08-01 10:09:32 +00:00
|
|
|
// Must avoid aliasing into another instance:
|
|
|
|
opImps[signature] = {uses: behavior.uses, does: behavior.does}
|
2022-07-28 05:28:40 +00:00
|
|
|
for (const dep of behavior.uses) {
|
|
|
|
const depname = dep.split('(', 1)[0]
|
2022-08-01 10:09:32 +00:00
|
|
|
if (depname === 'self' || this._templateParam(depname)) {
|
|
|
|
continue
|
|
|
|
}
|
2022-07-28 05:28:40 +00:00
|
|
|
this._addAffect(depname, name)
|
|
|
|
}
|
|
|
|
for (const type of typesOfSignature(signature)) {
|
2022-08-05 12:48:57 +00:00
|
|
|
for (const word of type.split(/[<>]/)) {
|
|
|
|
if (word.length == 0) continue
|
|
|
|
if (this._templateParam(word)) continue
|
|
|
|
this._seenTypes.add(word)
|
|
|
|
this._addAffect(':' + word, name)
|
|
|
|
}
|
2022-07-28 05:28:40 +00:00
|
|
|
}
|
2022-07-19 18:54:22 +00:00
|
|
|
}
|
2022-07-19 00:08:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-01 10:09:32 +00:00
|
|
|
/* returns a boolean indicating whether t denotes a template parameter.
|
|
|
|
* We will start this out very simply: the special string `T` is always
|
|
|
|
* a template parameter, and that's the only one
|
|
|
|
*/
|
|
|
|
_templateParam(t) { return t === theTemplateParam }
|
|
|
|
|
2022-07-24 19:52:05 +00:00
|
|
|
_addAffect(dependency, dependent) {
|
|
|
|
if (dependency in this._affects) {
|
|
|
|
this._affects[dependency].add(dependent)
|
|
|
|
} else {
|
|
|
|
this._affects[dependency] = new Set([dependent])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-19 00:08:49 +00:00
|
|
|
/**
|
|
|
|
* Reset an operation to require creation of typed-function,
|
|
|
|
* and if it has no implementations so far, set them up.
|
|
|
|
*/
|
|
|
|
_invalidate(name) {
|
2022-08-05 12:48:57 +00:00
|
|
|
if (this._invalid.has(name)) return
|
2022-07-25 11:20:13 +00:00
|
|
|
if (!(name in this._imps)) {
|
|
|
|
this._imps[name] = {}
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
this._invalid.add(name)
|
2022-07-25 11:20:13 +00:00
|
|
|
this._invalidateDependents(name)
|
2022-07-19 00:08:49 +00:00
|
|
|
const self = this
|
2022-07-22 22:06:56 +00:00
|
|
|
Object.defineProperty(this, name, {
|
|
|
|
configurable: true,
|
2022-08-05 12:48:57 +00:00
|
|
|
get: () => {
|
|
|
|
const result = self._bundle(name)
|
|
|
|
self._invalid.delete(name)
|
|
|
|
return result
|
|
|
|
}
|
2022-07-22 22:06:56 +00:00
|
|
|
})
|
2022-07-25 11:20:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Invalidate all the dependents of a given property of the instance
|
|
|
|
*/
|
|
|
|
_invalidateDependents(name) {
|
2022-07-19 18:54:22 +00:00
|
|
|
if (name in this._affects) {
|
|
|
|
for (const ancestor of this._affects[name]) {
|
|
|
|
this._invalidate(ancestor)
|
|
|
|
}
|
|
|
|
}
|
2022-07-19 00:08:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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]
|
2022-07-24 19:52:05 +00:00
|
|
|
if (!imps) {
|
2022-07-19 00:08:49 +00:00
|
|
|
throw new SyntaxError(`No implementations for ${name}`)
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
/* 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.Templates) continue
|
|
|
|
keep = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if (keep) usableEntries.push(entry)
|
|
|
|
}
|
2022-07-24 19:52:05 +00:00
|
|
|
if (usableEntries.length === 0) {
|
|
|
|
throw new SyntaxError(
|
|
|
|
`Every implementation for ${name} uses an undefined type;\n`
|
|
|
|
+ ` signatures: ${Object.keys(imps)}`)
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
/* Initial error checking done; mark this method as being
|
|
|
|
* in the midst of being reassembled
|
|
|
|
*/
|
2022-07-25 11:20:13 +00:00
|
|
|
Object.defineProperty(this, name, {configurable: true, value: 'limbo'})
|
2022-07-19 00:08:49 +00:00
|
|
|
const tf_imps = {}
|
2022-08-01 10:09:32 +00:00
|
|
|
for (const [rawSignature, behavior] of usableEntries) {
|
|
|
|
/* Check if it's an ordinary non-template signature */
|
|
|
|
let explicit = true
|
|
|
|
for (const type of typesOfSignature(rawSignature)) {
|
2022-08-05 12:48:57 +00:00
|
|
|
for (const word of type.split(/[<>]/)) {
|
|
|
|
if (this._templateParam(word)) {
|
|
|
|
explicit = false
|
|
|
|
break
|
|
|
|
}
|
2022-08-01 10:09:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (explicit) {
|
|
|
|
this._addTFimplementation(tf_imps, rawSignature, behavior)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
/* It's a template, have to instantiate */
|
|
|
|
/* First, add the known instantiations, gathering all types needed */
|
|
|
|
if (!('instantiations' in behavior)) {
|
|
|
|
behavior.instantiations = new Set()
|
|
|
|
}
|
|
|
|
let instantiationSet = new Set()
|
2022-08-05 12:48:57 +00:00
|
|
|
for (const instType of behavior.instantiations) {
|
|
|
|
instantiationSet.add(instType)
|
|
|
|
for (const other of this._priorTypes[instType]) {
|
|
|
|
instantiationSet.add(other)
|
2022-08-01 10:09:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const instType of instantiationSet) {
|
2022-08-01 15:28:21 +00:00
|
|
|
if (!(instType in this.Types)) continue
|
2022-08-01 10:09:32 +00:00
|
|
|
if (this.Types[instType] === anySpec) continue
|
|
|
|
const signature =
|
2022-08-05 12:48:57 +00:00
|
|
|
substituteInSig(rawSignature, theTemplateParam, instType)
|
2022-08-01 10:09:32 +00:00
|
|
|
/* Don't override an explicit implementation: */
|
|
|
|
if (signature in imps) continue
|
feat(quaternion): Add convenience quaternion creator function
Even in the setup just prior to this commit, a quaternion with entries
of type `number` is simply a `Complex<Complex<number>>`
So if we provide a convenience wrapper to create sucha thing, we
instantly have a quaternion data type. All of the operations come for
"free" if they were properly defined for the `Complex` template.
Multiplication already was, `abs` needed a little tweak, but there is
absolutely no "extra" code to support quaternions. (This commit
does not go through and check all arithmetic functions for proper operation
and tweak those that still need some generalization.)
Note that with the recursive template instantiation, a limit had to be placed
on template instantiation depth. The limit moves deeper as actual arguments
that are deeper nested instantiations are seen, so as long as one doesn't
immediately invoke a triply-nested template, for example, the limit will
never prevent an actual computation. It just prevents a runaway in the types
that Pocomath thinks it needs to know about. (Basically before, using the
quaternion creator would produce `Complex<Complex<number>>`. Then when you
called it again, Pocomath would think "Maybe I will need
`Complex<Complex<Complex<number>>>`?!" and create that, even though it had
never seen that, and then another level next time, and so on. The limit
just stops this progression one level beyond any nesting depth that's
actually been observed.
2022-08-07 03:13:50 +00:00
|
|
|
/* Don't go too deep */
|
|
|
|
let maxdepth = 0
|
|
|
|
for (const argType in typeListOfSignature(signature)) {
|
|
|
|
const depth = argType.split('<').length
|
|
|
|
if (depth > maxdepth) maxdepth = depth
|
|
|
|
}
|
|
|
|
if (maxdepth > this._maxDepthSeen + 1) continue
|
|
|
|
/* All right, go ahead and instantiate */
|
2022-08-01 10:09:32 +00:00
|
|
|
const uses = new Set()
|
|
|
|
for (const dep of behavior.uses) {
|
|
|
|
if (this._templateParam(dep)) continue
|
|
|
|
uses.add(substituteInSig(dep, theTemplateParam, instType))
|
|
|
|
}
|
|
|
|
const patch = (refs) => {
|
|
|
|
const innerRefs = {}
|
|
|
|
for (const dep of behavior.uses) {
|
|
|
|
if (this._templateParam(dep)) {
|
|
|
|
innerRefs[dep] = instType
|
2022-07-25 11:20:13 +00:00
|
|
|
} else {
|
2022-08-01 10:09:32 +00:00
|
|
|
const outerName = substituteInSig(
|
|
|
|
dep, theTemplateParam, instType)
|
|
|
|
innerRefs[dep] = refs[outerName]
|
2022-07-25 11:20:13 +00:00
|
|
|
}
|
2022-07-19 18:54:22 +00:00
|
|
|
}
|
2022-08-01 10:09:32 +00:00
|
|
|
return behavior.does(innerRefs)
|
2022-07-19 18:54:22 +00:00
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
this._addTFimplementation(
|
|
|
|
tf_imps, signature, {uses, does: patch})
|
2022-08-01 10:09:32 +00:00
|
|
|
}
|
|
|
|
/* Now add the catchall signature */
|
2022-08-05 12:48:57 +00:00
|
|
|
let templateCall = `<${theTemplateParam}>`
|
|
|
|
/* Relying here that the base of 'Foo<T>' is 'Foo': */
|
|
|
|
let baseSignature = rawSignature.replaceAll(templateCall, '')
|
|
|
|
/* Any remaining template params are top-level */
|
2022-08-01 10:09:32 +00:00
|
|
|
const signature = substituteInSig(
|
2022-08-05 12:48:57 +00:00
|
|
|
baseSignature, theTemplateParam, 'ground')
|
2022-08-01 10:09:32 +00:00
|
|
|
/* The catchall signature has to detect the actual type of the call
|
2022-08-05 12:48:57 +00:00
|
|
|
* and add the new instantiations.
|
|
|
|
* First, prepare the type inference data:
|
2022-08-01 10:09:32 +00:00
|
|
|
*/
|
2022-08-05 12:48:57 +00:00
|
|
|
const parTypes = rawSignature.split(',')
|
|
|
|
const restParam = (parTypes[parTypes.length-1].slice(0,3) === '...')
|
|
|
|
const topTyper = entity => this.typeOf(entity)
|
|
|
|
const inferences = parTypes.map(
|
|
|
|
type => generateTypeExtractor(
|
|
|
|
type,
|
|
|
|
theTemplateParam,
|
|
|
|
topTyper,
|
|
|
|
this.joinTypes.bind(this),
|
|
|
|
this.Templates))
|
|
|
|
if (inferences.every(x => !x)) { // all false
|
2022-08-01 10:09:32 +00:00
|
|
|
throw new SyntaxError(
|
|
|
|
`Cannot find template parameter in ${rawSignature}`)
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
/* And eliminate template parameters from the dependencies */
|
|
|
|
const simplifiedUses = {}
|
|
|
|
for (const dep of behavior.uses) {
|
|
|
|
let [func, needsig] = dep.split(/[()]/)
|
|
|
|
if (needsig) {
|
|
|
|
const subsig = substituteInSig(needsig, theTemplateParam, '')
|
|
|
|
if (subsig === needsig) {
|
|
|
|
simplifiedUses[dep] = dep
|
|
|
|
} else {
|
|
|
|
simplifiedUses[dep] = func
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
simplifiedUses[dep] = dep
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Now build the catchall implementation */
|
2022-08-01 10:09:32 +00:00
|
|
|
const self = this
|
|
|
|
const patch = (refs) => (...args) => {
|
2022-08-05 12:48:57 +00:00
|
|
|
/* We unbundle the rest arg if there is one */
|
|
|
|
const regLength = args.length - 1
|
|
|
|
if (restParam) {
|
|
|
|
const restArgs = args.pop()
|
|
|
|
args = args.concat(restArgs)
|
|
|
|
}
|
|
|
|
/* Now infer the type we actually should have been called for */
|
|
|
|
let i = -1
|
|
|
|
let j = -1
|
|
|
|
/* collect the arg types */
|
|
|
|
const argTypes = []
|
|
|
|
for (const arg of args) {
|
|
|
|
++j
|
|
|
|
// in case of rest parameter, reuse last parameter type:
|
|
|
|
if (i < inferences.length - 1) ++i
|
|
|
|
if (inferences[i]) {
|
|
|
|
const argType = inferences[i](arg)
|
|
|
|
if (!argType) {
|
|
|
|
throw TypeError(
|
|
|
|
`Type inference failed for argument ${j} of ${name}`)
|
|
|
|
}
|
|
|
|
if (argType === 'any') {
|
|
|
|
throw TypeError(
|
|
|
|
`In call to ${name}, incompatible template arguments: `
|
2022-08-07 16:19:27 +00:00
|
|
|
// + args.map(a => JSON.stringify(a)).join(', ')
|
|
|
|
// unfortunately barfs on bigints. Need a better formatter
|
|
|
|
// wish we could just use the one that console.log uses;
|
|
|
|
// is that accessible somehow?
|
|
|
|
+ args.map(a => a.toString()).join(', ')
|
|
|
|
+ ' of types ' + argTypes.join(', ') + argType)
|
2022-08-05 12:48:57 +00:00
|
|
|
}
|
|
|
|
argTypes.push(argType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (argTypes.length === 0) {
|
|
|
|
throw TypeError('Type inference failed for' + name)
|
|
|
|
}
|
|
|
|
let usedConversions = false
|
|
|
|
let instantiateFor = self.joinTypes(argTypes)
|
|
|
|
if (instantiateFor === 'any') {
|
|
|
|
usedConversions = true
|
|
|
|
instantiateFor = self.joinTypes(argTypes, usedConversions)
|
|
|
|
if (instantiateFor === 'any') {
|
|
|
|
throw TypeError(
|
|
|
|
`In call to ${name}, no type unifies arguments `
|
|
|
|
+ args.toString() + '; of types ' + argTypes.toString()
|
|
|
|
+ '; note each consecutive pair must unify to a '
|
|
|
|
+ 'supertype of at least one of them')
|
|
|
|
}
|
|
|
|
}
|
feat(quaternion): Add convenience quaternion creator function
Even in the setup just prior to this commit, a quaternion with entries
of type `number` is simply a `Complex<Complex<number>>`
So if we provide a convenience wrapper to create sucha thing, we
instantly have a quaternion data type. All of the operations come for
"free" if they were properly defined for the `Complex` template.
Multiplication already was, `abs` needed a little tweak, but there is
absolutely no "extra" code to support quaternions. (This commit
does not go through and check all arithmetic functions for proper operation
and tweak those that still need some generalization.)
Note that with the recursive template instantiation, a limit had to be placed
on template instantiation depth. The limit moves deeper as actual arguments
that are deeper nested instantiations are seen, so as long as one doesn't
immediately invoke a triply-nested template, for example, the limit will
never prevent an actual computation. It just prevents a runaway in the types
that Pocomath thinks it needs to know about. (Basically before, using the
quaternion creator would produce `Complex<Complex<number>>`. Then when you
called it again, Pocomath would think "Maybe I will need
`Complex<Complex<Complex<number>>>`?!" and create that, even though it had
never seen that, and then another level next time, and so on. The limit
just stops this progression one level beyond any nesting depth that's
actually been observed.
2022-08-07 03:13:50 +00:00
|
|
|
const depth = instantiateFor.split('<').length
|
|
|
|
if (depth > self._maxDepthSeen) {
|
|
|
|
self._maxDepthSeen = depth
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
/* 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[j] !== parTypes[j] && parTypes[j].includes('<')) {
|
|
|
|
// actually used the param and is a template
|
|
|
|
self._ensureTemplateTypes(parTypes[j], instantiateFor)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Transform the arguments if we used any conversions: */
|
|
|
|
if (usedConversions) {
|
|
|
|
i = - 1
|
|
|
|
for (j = 0; j < args.length; ++j) {
|
|
|
|
if (i < parTypes.length - 1) ++i
|
|
|
|
let wantType = parTypes[i]
|
|
|
|
if (wantType.slice(0,3) === '...') {
|
|
|
|
wantType = wantType.slice(3)
|
|
|
|
}
|
|
|
|
wantType = substituteInSig(
|
|
|
|
wantType, theTemplateParam, instantiateFor)
|
|
|
|
if (wantType !== parTypes[i]) {
|
|
|
|
args[j] = self._typed.convert(args[j], wantType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Finally reassemble the rest args if there were any */
|
|
|
|
if (restParam) {
|
|
|
|
const restArgs = args.slice(regLength)
|
|
|
|
args = args.slice(0,regLength)
|
|
|
|
args.push(restArgs)
|
|
|
|
}
|
|
|
|
/* Arrange that the desired instantiation will be there next
|
|
|
|
* time so we don't have to go through that again for this type
|
|
|
|
*/
|
2022-08-01 10:09:32 +00:00
|
|
|
refs[theTemplateParam] = instantiateFor
|
|
|
|
behavior.instantiations.add(instantiateFor)
|
|
|
|
self._invalidate(name)
|
2022-08-05 12:48:57 +00:00
|
|
|
// And update refs because we now know the type we're instantiating
|
|
|
|
// for:
|
|
|
|
const innerRefs = {}
|
|
|
|
for (const dep in simplifiedUses) {
|
|
|
|
const simplifiedDep = simplifiedUses[dep]
|
|
|
|
if (dep === simplifiedDep) {
|
|
|
|
innerRefs[dep] = refs[dep]
|
|
|
|
} else {
|
|
|
|
let [func, needsig] = dep.split(/[()]/)
|
|
|
|
if (self._typed.isTypedFunction(refs[simplifiedDep])) {
|
|
|
|
const subsig = substituteInSig(
|
|
|
|
needsig, theTemplateParam, instantiateFor)
|
|
|
|
let resname = simplifiedDep
|
2022-08-06 15:27:44 +00:00
|
|
|
if (resname == 'self') resname = name
|
|
|
|
innerRefs[dep] = self._pocoresolve(
|
|
|
|
resname, subsig, refs[simplifiedDep])
|
2022-08-05 12:48:57 +00:00
|
|
|
} else {
|
|
|
|
innerRefs[dep] = refs[simplifiedDep]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Finally ready to make the call.
|
|
|
|
return behavior.does(innerRefs)(...args)
|
2022-08-01 10:09:32 +00:00
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
// The actual uses value needs to be a set:
|
|
|
|
const outerUses = new Set(Object.values(simplifiedUses))
|
2022-08-01 10:09:32 +00:00
|
|
|
this._addTFimplementation(
|
2022-08-05 12:48:57 +00:00
|
|
|
tf_imps, signature, {uses: outerUses, does: patch})
|
2022-07-19 00:08:49 +00:00
|
|
|
}
|
2022-08-06 15:27:44 +00:00
|
|
|
this._correctPartialSelfRefs(name, tf_imps)
|
feat(quaternion): Add convenience quaternion creator function
Even in the setup just prior to this commit, a quaternion with entries
of type `number` is simply a `Complex<Complex<number>>`
So if we provide a convenience wrapper to create sucha thing, we
instantly have a quaternion data type. All of the operations come for
"free" if they were properly defined for the `Complex` template.
Multiplication already was, `abs` needed a little tweak, but there is
absolutely no "extra" code to support quaternions. (This commit
does not go through and check all arithmetic functions for proper operation
and tweak those that still need some generalization.)
Note that with the recursive template instantiation, a limit had to be placed
on template instantiation depth. The limit moves deeper as actual arguments
that are deeper nested instantiations are seen, so as long as one doesn't
immediately invoke a triply-nested template, for example, the limit will
never prevent an actual computation. It just prevents a runaway in the types
that Pocomath thinks it needs to know about. (Basically before, using the
quaternion creator would produce `Complex<Complex<number>>`. Then when you
called it again, Pocomath would think "Maybe I will need
`Complex<Complex<Complex<number>>>`?!" and create that, even though it had
never seen that, and then another level next time, and so on. The limit
just stops this progression one level beyond any nesting depth that's
actually been observed.
2022-08-07 03:13:50 +00:00
|
|
|
// Make sure we have all of the needed (template) types; and if they
|
|
|
|
// can't be added (because they have been instantiated too deep),
|
|
|
|
// ditch the signature:
|
|
|
|
const badSigs = new Set()
|
|
|
|
for (const sig in tf_imps) {
|
|
|
|
for (const type of typeListOfSignature(sig)) {
|
|
|
|
if (type.includes('<')) {
|
|
|
|
// it's a template type, turn it into a template and an arg
|
|
|
|
let base = type.split('<',1)[0]
|
|
|
|
const arg = type.slice(base.length+1, -1)
|
|
|
|
if (base.slice(0,3) === '...') {
|
|
|
|
base = base.slice(3)
|
|
|
|
}
|
|
|
|
if (this.instantiateTemplate(base, arg) === undefined) {
|
|
|
|
badSigs.add(sig)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const badSig of badSigs) {
|
|
|
|
delete tf_imps[badSig]
|
|
|
|
}
|
2022-07-22 20:49:14 +00:00
|
|
|
const tf = this._typed(name, tf_imps)
|
2022-07-22 22:06:56 +00:00
|
|
|
Object.defineProperty(this, name, {configurable: true, value: tf})
|
2022-07-19 00:08:49 +00:00
|
|
|
return tf
|
|
|
|
}
|
2022-07-19 03:10:55 +00:00
|
|
|
|
2022-08-01 10:09:32 +00:00
|
|
|
/* Adapts Pocomath-style behavior specification (uses, does) for signature
|
|
|
|
* to typed-function implementations and inserts the result into plain object
|
|
|
|
* imps
|
|
|
|
*/
|
|
|
|
_addTFimplementation(imps, signature, behavior) {
|
|
|
|
const {uses, does} = behavior
|
|
|
|
if (uses.length === 0) {
|
|
|
|
imps[signature] = does()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const refs = {}
|
|
|
|
let full_self_referential = false
|
|
|
|
let part_self_references = []
|
|
|
|
for (const dep of uses) {
|
|
|
|
let [func, needsig] = dep.split(/[()]/)
|
2022-08-05 12:48:57 +00:00
|
|
|
/* Safety check that can perhaps be removed:
|
|
|
|
* Verify that the desired signature has been fully grounded:
|
|
|
|
*/
|
|
|
|
if (needsig) {
|
|
|
|
const trysig = substituteInSig(needsig, theTemplateParam, '')
|
|
|
|
if (trysig !== needsig) {
|
|
|
|
throw new Error(
|
|
|
|
'Attempt to add a template implementation: ' +
|
|
|
|
`${signature} with dependency ${dep}`)
|
|
|
|
}
|
|
|
|
}
|
2022-08-01 10:09:32 +00:00
|
|
|
if (func === 'self') {
|
|
|
|
if (needsig) {
|
|
|
|
if (full_self_referential) {
|
|
|
|
throw new SyntaxError(
|
|
|
|
'typed-function does not support mixed full and '
|
|
|
|
+ 'partial self-reference')
|
|
|
|
}
|
|
|
|
if (subsetOfKeys(typesOfSignature(needsig), this.Types)) {
|
|
|
|
part_self_references.push(needsig)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (part_self_references.length) {
|
|
|
|
throw new SyntaxError(
|
|
|
|
'typed-function does not support mixed full and '
|
|
|
|
+ 'partial self-reference')
|
|
|
|
}
|
|
|
|
full_self_referential = true
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (this[func] === 'limbo') {
|
|
|
|
/* We are in the midst of bundling func, so have to use
|
|
|
|
* an indirect reference to func. And given that, there's
|
|
|
|
* really no helpful way to extract a specific signature
|
|
|
|
*/
|
|
|
|
const self = this
|
|
|
|
refs[dep] = function () { // is this the most efficient?
|
|
|
|
return self[func].apply(this, arguments)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// can bundle up func, and grab its signature if need be
|
|
|
|
let destination = this[func]
|
2022-08-06 15:27:44 +00:00
|
|
|
if (destination &&needsig) {
|
2022-08-05 12:48:57 +00:00
|
|
|
destination = this._pocoresolve(func, needsig)
|
2022-08-01 10:09:32 +00:00
|
|
|
}
|
|
|
|
refs[dep] = destination
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (full_self_referential) {
|
|
|
|
imps[signature] = this._typed.referToSelf(self => {
|
|
|
|
refs.self = self
|
|
|
|
return does(refs)
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (part_self_references.length) {
|
2022-08-05 12:48:57 +00:00
|
|
|
/* There is an obstruction here. The list part_self_references
|
|
|
|
* might contain a signature that requires conversion for self to
|
|
|
|
* handle. But I advocated this not be allowed in typed.referTo, which
|
|
|
|
* made sense for human-written functions, but is unfortunate now.
|
|
|
|
* So we have to defer creating these and correct them later, at
|
|
|
|
* least until we can add an option to typed-function.
|
|
|
|
*/
|
|
|
|
imps[signature] = {
|
|
|
|
deferred: true,
|
|
|
|
builtRefs: refs,
|
|
|
|
sigDoes: does,
|
|
|
|
psr: part_self_references
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
imps[signature] = does(refs)
|
|
|
|
}
|
|
|
|
|
2022-08-06 15:27:44 +00:00
|
|
|
_correctPartialSelfRefs(name, imps) {
|
2022-08-05 12:48:57 +00:00
|
|
|
for (const aSignature in imps) {
|
|
|
|
if (!(imps[aSignature].deferred)) continue
|
|
|
|
const part_self_references = imps[aSignature].psr
|
|
|
|
const corrected_self_references = []
|
|
|
|
for (const neededSig of part_self_references) {
|
|
|
|
// Have to find a match for neededSig among the other signatures
|
|
|
|
// of this function. That's a job for typed-function, but we will
|
|
|
|
// try here:
|
|
|
|
if (neededSig in imps) { // the easy case
|
|
|
|
corrected_self_references.push(neededSig)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// No exact match, try to get one that matches with
|
|
|
|
// subtypes since the whole conversion thing in typed-function
|
|
|
|
// is too complicated to reproduce
|
2022-08-06 15:27:44 +00:00
|
|
|
const foundSig = this._findSubtypeImpl(name, imps, neededSig)
|
2022-08-05 12:48:57 +00:00
|
|
|
if (foundSig) {
|
|
|
|
corrected_self_references.push(foundSig)
|
|
|
|
} else {
|
|
|
|
throw new Error(
|
|
|
|
'Implement inexact self-reference in typed-function for '
|
2022-08-07 16:19:27 +00:00
|
|
|
+ `${name}(${neededSig})`)
|
2022-08-05 12:48:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
const refs = imps[aSignature].builtRefs
|
|
|
|
const does = imps[aSignature].sigDoes
|
|
|
|
imps[aSignature] = this._typed.referTo(
|
|
|
|
...corrected_self_references, (...impls) => {
|
2022-08-01 10:09:32 +00:00
|
|
|
for (let i = 0; i < part_self_references.length; ++i) {
|
|
|
|
refs[`self(${part_self_references[i]})`] = impls[i]
|
|
|
|
}
|
|
|
|
return does(refs)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
|
|
|
|
/* This function analyzes the template and makes sure the
|
|
|
|
* instantiations of it for type and all prior types of type are present
|
|
|
|
* in the instance.
|
|
|
|
*/
|
|
|
|
_ensureTemplateTypes(template, type) {
|
feat(quaternion): Add convenience quaternion creator function
Even in the setup just prior to this commit, a quaternion with entries
of type `number` is simply a `Complex<Complex<number>>`
So if we provide a convenience wrapper to create sucha thing, we
instantly have a quaternion data type. All of the operations come for
"free" if they were properly defined for the `Complex` template.
Multiplication already was, `abs` needed a little tweak, but there is
absolutely no "extra" code to support quaternions. (This commit
does not go through and check all arithmetic functions for proper operation
and tweak those that still need some generalization.)
Note that with the recursive template instantiation, a limit had to be placed
on template instantiation depth. The limit moves deeper as actual arguments
that are deeper nested instantiations are seen, so as long as one doesn't
immediately invoke a triply-nested template, for example, the limit will
never prevent an actual computation. It just prevents a runaway in the types
that Pocomath thinks it needs to know about. (Basically before, using the
quaternion creator would produce `Complex<Complex<number>>`. Then when you
called it again, Pocomath would think "Maybe I will need
`Complex<Complex<Complex<number>>>`?!" and create that, even though it had
never seen that, and then another level next time, and so on. The limit
just stops this progression one level beyond any nesting depth that's
actually been observed.
2022-08-07 03:13:50 +00:00
|
|
|
const base = template.split('<', 1)[0]
|
|
|
|
const arg = template.slice(base.length + 1, -1)
|
2022-08-05 12:48:57 +00:00
|
|
|
if (!arg) {
|
|
|
|
throw new Error(
|
|
|
|
'Implementation error in _ensureTemplateTypes', template, type)
|
|
|
|
}
|
|
|
|
let instantiations
|
|
|
|
if (this._templateParam(arg)) { // 1st-level template
|
|
|
|
instantiations = new Set(this._priorTypes[type])
|
|
|
|
instantiations.add(type)
|
|
|
|
} else { // nested template
|
|
|
|
instantiations = this._ensureTemplateTypes(arg, type)
|
|
|
|
}
|
|
|
|
const resultingTypes = new Set()
|
|
|
|
for (const iType of instantiations) {
|
2022-08-06 15:27:44 +00:00
|
|
|
const resultType = this.instantiateTemplate(base, iType)
|
2022-08-05 12:48:57 +00:00
|
|
|
if (resultType) resultingTypes.add(resultType)
|
|
|
|
}
|
|
|
|
return resultingTypes
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Maybe add the instantiation of template type base with argument tyoe
|
|
|
|
* instantiator to the Types of this instance, if it hasn't happened already.
|
feat(quaternion): Add convenience quaternion creator function
Even in the setup just prior to this commit, a quaternion with entries
of type `number` is simply a `Complex<Complex<number>>`
So if we provide a convenience wrapper to create sucha thing, we
instantly have a quaternion data type. All of the operations come for
"free" if they were properly defined for the `Complex` template.
Multiplication already was, `abs` needed a little tweak, but there is
absolutely no "extra" code to support quaternions. (This commit
does not go through and check all arithmetic functions for proper operation
and tweak those that still need some generalization.)
Note that with the recursive template instantiation, a limit had to be placed
on template instantiation depth. The limit moves deeper as actual arguments
that are deeper nested instantiations are seen, so as long as one doesn't
immediately invoke a triply-nested template, for example, the limit will
never prevent an actual computation. It just prevents a runaway in the types
that Pocomath thinks it needs to know about. (Basically before, using the
quaternion creator would produce `Complex<Complex<number>>`. Then when you
called it again, Pocomath would think "Maybe I will need
`Complex<Complex<Complex<number>>>`?!" and create that, even though it had
never seen that, and then another level next time, and so on. The limit
just stops this progression one level beyond any nesting depth that's
actually been observed.
2022-08-07 03:13:50 +00:00
|
|
|
* Returns the name of the type if added, false if it was already there,
|
|
|
|
* and undefined if the type is declined (because of being nested too deep).
|
2022-08-05 12:48:57 +00:00
|
|
|
*/
|
2022-08-06 15:27:44 +00:00
|
|
|
instantiateTemplate(base, instantiator) {
|
feat(quaternion): Add convenience quaternion creator function
Even in the setup just prior to this commit, a quaternion with entries
of type `number` is simply a `Complex<Complex<number>>`
So if we provide a convenience wrapper to create sucha thing, we
instantly have a quaternion data type. All of the operations come for
"free" if they were properly defined for the `Complex` template.
Multiplication already was, `abs` needed a little tweak, but there is
absolutely no "extra" code to support quaternions. (This commit
does not go through and check all arithmetic functions for proper operation
and tweak those that still need some generalization.)
Note that with the recursive template instantiation, a limit had to be placed
on template instantiation depth. The limit moves deeper as actual arguments
that are deeper nested instantiations are seen, so as long as one doesn't
immediately invoke a triply-nested template, for example, the limit will
never prevent an actual computation. It just prevents a runaway in the types
that Pocomath thinks it needs to know about. (Basically before, using the
quaternion creator would produce `Complex<Complex<number>>`. Then when you
called it again, Pocomath would think "Maybe I will need
`Complex<Complex<Complex<number>>>`?!" and create that, even though it had
never seen that, and then another level next time, and so on. The limit
just stops this progression one level beyond any nesting depth that's
actually been observed.
2022-08-07 03:13:50 +00:00
|
|
|
const depth = instantiator.split('<').length
|
|
|
|
if (depth > this._maxDepthSeen ) {
|
|
|
|
// don't bother with types much deeper thant we have seen
|
|
|
|
return undefined
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
const wantsType = `${base}<${instantiator}>`
|
|
|
|
if (wantsType in this.Types) return false
|
|
|
|
// OK, need to generate the type from the template
|
|
|
|
// Set up refines, before, test, and from
|
|
|
|
const newTypeSpec = {refines: base}
|
|
|
|
const maybeFrom = {}
|
|
|
|
const template = this.Templates[base].spec
|
|
|
|
if (!template) {
|
|
|
|
throw new Error(
|
2022-08-06 15:27:44 +00:00
|
|
|
`Implementor error in instantiateTemplate(${base}, ${instantiator})`)
|
2022-08-05 12:48:57 +00:00
|
|
|
}
|
|
|
|
const instantiatorSpec = this.Types[instantiator]
|
|
|
|
let beforeTypes = []
|
|
|
|
if (instantiatorSpec.before) {
|
|
|
|
beforeTypes = instantiatorSpec.before.map(type => `${base}<${type}>`)
|
|
|
|
}
|
|
|
|
if (template.before) {
|
|
|
|
for (const beforeTmpl of template.before) {
|
|
|
|
beforeTypes.push(
|
|
|
|
substituteInSig(beforeTmpl, theTemplateParam, instantiator))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (beforeTypes.length > 0) {
|
|
|
|
newTypeSpec.before = beforeTypes
|
|
|
|
}
|
|
|
|
newTypeSpec.test = template.test(this._typeTests[instantiator])
|
|
|
|
if (template.from) {
|
|
|
|
for (let source in template.from) {
|
|
|
|
const instSource = substituteInSig(
|
|
|
|
source, theTemplateParam, instantiator)
|
|
|
|
let usesFromParam = false
|
|
|
|
for (const word of instSource.split(/[<>]/)) {
|
|
|
|
if (word === templateFromParam) {
|
|
|
|
usesFromParam = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (usesFromParam) {
|
|
|
|
for (const iFrom in instantiatorSpec.from) {
|
|
|
|
const finalSource = substituteInSig(
|
|
|
|
instSource, templateFromParam, iFrom)
|
|
|
|
maybeFrom[finalSource] = template.from[source](
|
|
|
|
instantiatorSpec.from[iFrom])
|
|
|
|
}
|
|
|
|
// Assuming all templates are covariant here, I guess...
|
|
|
|
for (const subType of this._subtypes[instantiator]) {
|
|
|
|
const finalSource = substituteInSig(
|
|
|
|
instSource, templateFromParam, subType)
|
|
|
|
maybeFrom[finalSource] = template.from[source](x => x)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
maybeFrom[instSource] = template.from[source]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Object.keys(maybeFrom).length > 0) {
|
|
|
|
newTypeSpec.from = maybeFrom
|
|
|
|
}
|
|
|
|
this.installType(wantsType, newTypeSpec)
|
|
|
|
return wantsType
|
|
|
|
}
|
|
|
|
|
2022-08-06 15:27:44 +00:00
|
|
|
_findSubtypeImpl(name, imps, neededSig) {
|
2022-08-05 12:48:57 +00:00
|
|
|
if (neededSig in imps) return neededSig
|
|
|
|
let foundSig = false
|
|
|
|
const typeList = typeListOfSignature(neededSig)
|
|
|
|
for (const otherSig in imps) {
|
|
|
|
const otherTypeList = typeListOfSignature(otherSig)
|
|
|
|
if (typeList.length !== otherTypeList.length) continue
|
|
|
|
let allMatch = true
|
|
|
|
for (let k = 0; k < typeList.length; ++k) {
|
|
|
|
let myType = typeList[k]
|
|
|
|
let otherType = otherTypeList[k]
|
|
|
|
if (otherType === theTemplateParam) {
|
|
|
|
otherTypeList[k] = 'ground'
|
|
|
|
otherType = 'ground'
|
|
|
|
}
|
|
|
|
if (otherType === '...T') {
|
|
|
|
otherTypeList[k] = '...ground'
|
|
|
|
otherType = 'ground'
|
|
|
|
}
|
|
|
|
const adjustedOtherType = otherType.replaceAll(
|
|
|
|
`<${theTemplateParam}>`, '')
|
|
|
|
if (adjustedOtherType !== otherType) {
|
|
|
|
otherTypeList[k] = adjustedOtherType
|
|
|
|
otherType = adjustedOtherType
|
|
|
|
}
|
|
|
|
if (myType.slice(0,3) === '...') myType = myType.slice(3)
|
|
|
|
if (otherType.slice(0,3) === '...') otherType = otherType.slice(3)
|
|
|
|
if (otherType === 'any') continue
|
|
|
|
if (otherType === 'ground') continue
|
|
|
|
if (!(otherType in this.Types)) {
|
|
|
|
allMatch = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if (myType === otherType
|
|
|
|
|| this._subtypes[otherType].has(myType)) {
|
|
|
|
continue
|
|
|
|
}
|
2022-08-06 15:27:44 +00:00
|
|
|
if (otherType in this.Templates) {
|
|
|
|
if (this.instantiateTemplate(otherType, myType)) {
|
|
|
|
let dummy
|
|
|
|
dummy = this[name] // for side effects
|
|
|
|
return this._findSubtypeImpl(name, this._imps[name], neededSig)
|
|
|
|
}
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
allMatch = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if (allMatch) {
|
|
|
|
foundSig = otherTypeList.join(',')
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return foundSig
|
|
|
|
}
|
|
|
|
|
2022-08-06 15:27:44 +00:00
|
|
|
_pocoresolve(name, sig, typedFunction) {
|
|
|
|
if (!this._typed.isTypedFunction(typedFunction)) {
|
|
|
|
typedFunction = this[name]
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
let result = undefined
|
|
|
|
try {
|
2022-08-06 15:27:44 +00:00
|
|
|
result = this._typed.find(typedFunction, sig, {exact: true})
|
|
|
|
} catch {
|
|
|
|
}
|
|
|
|
if (result) return result
|
|
|
|
const foundsig = this._findSubtypeImpl(name, this._imps[name], sig)
|
|
|
|
if (foundsig) return this._typed.find(typedFunction, foundsig)
|
|
|
|
// Make sure bundle is up-to-date:
|
|
|
|
typedFunction = this[name]
|
|
|
|
try {
|
|
|
|
result = this._typed.find(typedFunction, sig)
|
2022-08-05 12:48:57 +00:00
|
|
|
} catch {
|
|
|
|
}
|
|
|
|
if (result) return result
|
2022-08-06 15:27:44 +00:00
|
|
|
// total punt, revert to typed-function resolution on every call;
|
|
|
|
// hopefully this happens rarely:
|
|
|
|
return typedFunction
|
2022-08-05 12:48:57 +00:00
|
|
|
}
|
|
|
|
|
2022-07-19 00:08:49 +00:00
|
|
|
}
|