2022-07-19 00:08:49 +00:00
|
|
|
/* Core of pocomath: create an instance */
|
|
|
|
import typed from 'typed-function'
|
2022-08-01 23:24:20 +00:00
|
|
|
import {makeChain} from './Chain.mjs'
|
2022-08-30 19:36:44 +00:00
|
|
|
import {dependencyExtractor, generateTypeExtractor} from './extractors.mjs'
|
|
|
|
import {Returns, returnTypeOf} from './Returns.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-12-01 17:47:20 +00:00
|
|
|
/* Like `.some(predicate)` but for collections */
|
|
|
|
function exists(collection, predicate) {
|
|
|
|
for (const item of collection) if (predicate(item)) return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-08-30 19:36:44 +00:00
|
|
|
/* Template/signature parsing stuff; should probably be moved to a
|
|
|
|
* separate file, but it's a bit interleaved at the moment
|
|
|
|
*/
|
|
|
|
|
2022-08-01 10:09:32 +00:00
|
|
|
const theTemplateParam = 'T' // First pass: only allow this one exact parameter
|
2022-08-30 19:36:44 +00:00
|
|
|
const restTemplateParam = `...${theTemplateParam}`
|
|
|
|
const templateCall = `<${theTemplateParam}>`
|
2022-08-05 12:48:57 +00:00
|
|
|
const templateFromParam = 'U' // For defining covariant conversions
|
2022-08-01 10:09:32 +00:00
|
|
|
|
2022-08-30 19:36:44 +00:00
|
|
|
/* returns the pair [base, instance] for a template type. If the type
|
|
|
|
* is not a template, instance is undefined
|
|
|
|
*/
|
|
|
|
const templatePattern = /^\s*([^<\s]*)\s*<\s*(\S*)\s*>\s*$/
|
|
|
|
function splitTemplate(type) {
|
|
|
|
if (!(type.includes('<'))) return [type, undefined]
|
|
|
|
const results = templatePattern.exec(type)
|
|
|
|
return [results[1], results[2]]
|
|
|
|
}
|
|
|
|
/* Returns the instance such that type is template instantiated for that
|
|
|
|
* instance.
|
|
|
|
*/
|
|
|
|
function whichInstance(type, template) {
|
|
|
|
if (template === theTemplateParam) return type
|
|
|
|
if (type === template) return ''
|
|
|
|
if (!(template.includes(templateCall))) {
|
|
|
|
throw new TypeError(
|
|
|
|
`Type ${template} is not a template, so can't produce ${type}`)
|
|
|
|
}
|
|
|
|
const [typeBase, typeInstance] = splitTemplate(type)
|
|
|
|
if (!typeInstance) {
|
|
|
|
throw new TypeError(
|
|
|
|
`Type ${type} not from a template, so isn't instance of ${template}`)
|
|
|
|
}
|
|
|
|
const [tempBase, tempInstance] = splitTemplate(template)
|
|
|
|
if (typeBase !== tempBase) {
|
|
|
|
throw new TypeError(
|
|
|
|
`Type ${type} has wrong top-level base to be instance of ${template}`)
|
|
|
|
}
|
|
|
|
return whichInstance(typeInstance, tempInstance)
|
|
|
|
}
|
|
|
|
/* Same as above, but for signatures */
|
|
|
|
function whichSigInstance(sig, tempsig) {
|
|
|
|
const sigTypes = typeListOfSignature(sig)
|
|
|
|
const tempTypes = typeListOfSignature(tempsig)
|
|
|
|
const sigLength = sigTypes.length
|
|
|
|
if (sigLength === 0) {
|
|
|
|
throw new TypeError("No types in signature, so can't determine instance")
|
|
|
|
}
|
|
|
|
if (sigLength !== tempTypes.length) {
|
|
|
|
throw new TypeError(`Signatures ${sig} and ${tempsig} differ in length`)
|
|
|
|
}
|
|
|
|
let maybeInstance = whichInstance(sigTypes[0], tempTypes[0])
|
|
|
|
for (let i = 1; i < sigLength; ++i) {
|
|
|
|
const currInstance = whichInstance(sigTypes[i], tempTypes[i])
|
|
|
|
if (maybeInstance) {
|
|
|
|
if (currInstance && currInstance !== maybeInstance) {
|
|
|
|
throw new TypeError(
|
|
|
|
`Inconsistent instantiation of ${sig} from ${tempsig}`)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
maybeInstance = currInstance
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!maybeInstance) {
|
|
|
|
throw new TypeError(
|
|
|
|
`Signature ${sig} identical to ${tempsig}, not an instance`)
|
|
|
|
}
|
|
|
|
return maybeInstance
|
|
|
|
}
|
|
|
|
|
2022-08-01 10:09:32 +00:00
|
|
|
/* Returns a new signature just like sig but with the parameter replaced by
|
|
|
|
* the type
|
|
|
|
*/
|
2022-08-30 19:36:44 +00:00
|
|
|
const upperBounds = /\s*(\S*)\s*:\s*(\w*)\s*/g
|
|
|
|
function substituteInSignature(signature, parameter, type) {
|
|
|
|
const sig = signature.replaceAll(upperBounds, '$1')
|
2022-08-01 10:09:32 +00:00
|
|
|
const pattern = new RegExp("\\b" + parameter + "\\b", 'g')
|
|
|
|
return sig.replaceAll(pattern, type)
|
|
|
|
}
|
|
|
|
|
2022-12-01 17:47:20 +00:00
|
|
|
const UniversalType = 'ground' // name for a type that matches anything
|
2022-08-30 19:36:44 +00:00
|
|
|
let lastWhatToDo = null // used in an infinite descent check
|
|
|
|
|
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',
|
2022-12-01 17:47:20 +00:00
|
|
|
'convert',
|
2022-07-28 05:28:40 +00:00
|
|
|
'importDependencies',
|
|
|
|
'install',
|
|
|
|
'installType',
|
2022-08-06 15:27:44 +00:00
|
|
|
'instantiateTemplate',
|
2022-08-30 19:36:44 +00:00
|
|
|
'isPriorTo',
|
|
|
|
'isSubtypeOf',
|
2022-08-05 12:48:57 +00:00
|
|
|
'joinTypes',
|
2022-07-28 05:28:40 +00:00
|
|
|
'name',
|
2022-08-30 19:36:44 +00:00
|
|
|
'returnTypeOf',
|
|
|
|
'resolve',
|
2022-08-05 12:48:57 +00:00
|
|
|
'self',
|
2022-08-30 19:36:44 +00:00
|
|
|
'subtypesOf',
|
|
|
|
'supertypesOf',
|
2022-08-05 12:48:57 +00:00
|
|
|
'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
|
2022-08-30 19:36:44 +00:00
|
|
|
this._imps = {} // Pocomath implementations, with dependencies
|
|
|
|
this._TFimps = {} // typed-function implementations, dependencies resolved
|
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-30 19:36:44 +00:00
|
|
|
// The following is an additional typed-function universe for resolving
|
|
|
|
// uninstantiated template instances. It is linked to the main one in
|
|
|
|
// its onMismatch function, below:
|
|
|
|
this._metaTyped = typed.create()
|
|
|
|
this._metaTyped.clear()
|
2022-12-01 17:47:20 +00:00
|
|
|
this._metaTyped.addTypes([{name: UniversalType, test: () => true}])
|
|
|
|
|
2022-08-30 19:36:44 +00:00
|
|
|
// And these are the meta bindings: (I think we don't need separate
|
|
|
|
// invalidation for them as they are only accessed through a main call.)
|
|
|
|
this._meta = {} // The resulting typed-functions
|
|
|
|
this._metaTFimps = {} // and their implementations
|
|
|
|
const me = this
|
|
|
|
this._typed.onMismatch = (name, args, sigs) => {
|
|
|
|
if (me._invalid.has(name)) {
|
2022-12-01 17:47:20 +00:00
|
|
|
if (this._fixing === name) {
|
|
|
|
this._fixingCount += 1
|
|
|
|
if (this._fixingCount > this._maxDepthSeen + 2) {
|
|
|
|
throw new ReferenceError(
|
|
|
|
`Infinite descent rebuilding ${name} on ${args}`)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this._fixingCount = 0
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
// rebuild implementation and try again
|
2022-12-01 17:47:20 +00:00
|
|
|
const lastFixing = this._fixing
|
|
|
|
this._fixing = name
|
|
|
|
const value = me[name](...args)
|
|
|
|
this._fix = lastFixing
|
|
|
|
return value
|
2022-08-30 19:36:44 +00:00
|
|
|
}
|
|
|
|
const metaversion = me._meta[name]
|
|
|
|
if (metaversion) {
|
|
|
|
return me._meta[name](...args)
|
|
|
|
}
|
|
|
|
me._typed.throwMismatchError(name, args, sigs)
|
|
|
|
}
|
|
|
|
// List of types installed in the instance: (We start with just dummies
|
|
|
|
// for the 'any' type and for type parameters.)
|
2022-08-01 10:09:32 +00:00
|
|
|
this.Types = {any: anySpec}
|
|
|
|
this.Types[theTemplateParam] = anySpec
|
2022-08-30 19:36:44 +00:00
|
|
|
// Types that have been moved into the metaverse:
|
|
|
|
this._metafiedTypes = new Set()
|
|
|
|
// All the template types that have been defined:
|
2022-08-05 12:48:57 +00:00
|
|
|
this.Templates = {}
|
2022-08-30 19:36:44 +00:00
|
|
|
// And their instantiations:
|
|
|
|
this._instantiationsOf = {}
|
|
|
|
// The actual type testing functions:
|
2022-08-05 12:48:57 +00:00
|
|
|
this._typeTests = {}
|
2022-08-30 19:36:44 +00:00
|
|
|
// For each type, gives all of its (in)direct subtypes in topo order:
|
|
|
|
this._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
|
|
|
this.config = new Proxy(this._config, {
|
|
|
|
get: (target, property) => target[property],
|
|
|
|
set: (target, property, value) => {
|
|
|
|
if (value !== target[property]) {
|
|
|
|
target[property] = value
|
2022-08-30 19:36:44 +00:00
|
|
|
me._invalidateDependents('config')
|
2022-07-25 11:20:13 +00:00
|
|
|
}
|
|
|
|
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-30 19:36:44 +00:00
|
|
|
this.joinTypes = this.joinTypes.bind(me)
|
2022-12-01 17:47:20 +00:00
|
|
|
// Provide access to typed function conversion:
|
|
|
|
this.convert = this._typed.convert.bind(this._typed)
|
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
|
|
|
*/
|
2022-08-30 19:36:44 +00:00
|
|
|
install = Returns('void', function(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()
|
2022-08-30 19:36:44 +00:00
|
|
|
try {
|
|
|
|
does(dependencyExtractor(uses))
|
|
|
|
} catch {
|
|
|
|
}
|
2022-07-28 05:28:40 +00:00
|
|
|
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)
|
2022-08-30 19:36:44 +00:00
|
|
|
})
|
2022-07-28 05:28:40 +00:00
|
|
|
|
|
|
|
/* 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-30 19:36:44 +00:00
|
|
|
/* Determine the return type of an operation given an input signature */
|
|
|
|
returnTypeOf = Returns('string', function(operation, signature) {
|
|
|
|
for (const type of typeListOfSignature(signature)) {
|
|
|
|
this._maybeInstantiate(type)
|
|
|
|
}
|
|
|
|
if (typeof operation !== 'string') {
|
|
|
|
operation = operation.name
|
|
|
|
}
|
|
|
|
const details = this._pocoFindSignature(operation, signature)
|
|
|
|
if (details) {
|
|
|
|
return returnTypeOf(details.fn, signature, this)
|
|
|
|
}
|
|
|
|
return returnTypeOf(this[operation], signature, this)
|
|
|
|
})
|
|
|
|
|
2022-08-01 23:24:20 +00:00
|
|
|
/* Return a chain object for this instance with a given value: */
|
2022-08-30 19:36:44 +00:00
|
|
|
chain = Returns(
|
|
|
|
sig => `Chain<${sig}>`,
|
|
|
|
function(value) {
|
|
|
|
return makeChain(value, this, this._chainRepository)
|
|
|
|
}
|
|
|
|
)
|
2022-08-01 23:24:20 +00:00
|
|
|
|
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 {
|
2022-08-30 19:36:44 +00:00
|
|
|
const moduleName = `../${type}/${name}.mjs`
|
|
|
|
const module = await import(moduleName)
|
|
|
|
this.install(module)
|
2022-07-23 02:41:59 +00:00
|
|
|
} 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
|
|
|
|
*/
|
2022-08-30 19:36:44 +00:00
|
|
|
installType = Returns('void', function(type, spec) {
|
|
|
|
const parts = type.split(/[<,>]/).map(s => s.trim())
|
2022-08-05 12:48:57 +00:00
|
|
|
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))) {
|
2022-08-30 19:36:44 +00:00
|
|
|
// It's an uninstantiated template, deal with it separately
|
2022-08-05 12:48:57 +00:00
|
|
|
return this._installTemplateType(type, spec)
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
const base = parts[0]
|
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-30 19:36:44 +00:00
|
|
|
beforeType = 'any'
|
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-30 19:36:44 +00:00
|
|
|
this._subtypes[type] = []
|
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)
|
2022-08-30 19:36:44 +00:00
|
|
|
this._addSubtypeTo(nextSuper, type)
|
2022-08-05 12:48:57 +00:00
|
|
|
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-08-30 19:36:44 +00:00
|
|
|
|
|
|
|
/* Add the conversion in the metaverse if need be: */
|
|
|
|
const [toBase, toInstance] = splitTemplate(nextSuper)
|
|
|
|
if (toInstance) {
|
|
|
|
const [fromBase, fromInstance] = splitTemplate(from)
|
|
|
|
if (!fromBase || fromBase !== toBase) {
|
|
|
|
this._metafy(from)
|
|
|
|
try {
|
|
|
|
this._metaTyped.addConversion(
|
|
|
|
{from, to: toBase, convert: spec.from[from]})
|
|
|
|
} catch {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
2022-08-30 19:36:44 +00:00
|
|
|
&& this.isSubtypeOf(type, fromtype))) {
|
|
|
|
if (spec.refines == to || this.isSubtypeOf(spec.refines,to)) {
|
2022-08-05 12:48:57 +00:00
|
|
|
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)
|
2022-08-30 19:36:44 +00:00
|
|
|
/* Add the conversion in the metaverse if need be: */
|
|
|
|
const [toBase, toInstance] = splitTemplate(nextSuper)
|
|
|
|
if (toInstance && base !== toBase) {
|
|
|
|
this._metafy(type)
|
|
|
|
this._metaTyped.addConversion({
|
|
|
|
from: type,
|
|
|
|
to: toBase,
|
|
|
|
convert: this.Types[to].from[fromtype]
|
|
|
|
})
|
|
|
|
}
|
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
|
|
|
} 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-12-01 17:47:20 +00:00
|
|
|
// Need to metafy ground types
|
|
|
|
if (type === base) {
|
|
|
|
this._metafy(type)
|
|
|
|
}
|
2022-08-01 10:09:32 +00:00
|
|
|
// update the typeOf function
|
|
|
|
const imp = {}
|
2022-08-30 19:36:44 +00:00
|
|
|
imp[type] = {uses: new Set(), does: () => Returns('string', () => type)}
|
2022-08-01 10:09:32 +00:00
|
|
|
this._installFunctions({typeOf: imp})
|
2022-08-30 19:36:44 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
_metafy(type) {
|
|
|
|
if (this._metafiedTypes.has(type)) return
|
|
|
|
this._metaTyped.addTypes([{name: type, test: this._typeTests[type]}])
|
|
|
|
this._metafiedTypes.add(type)
|
2022-07-24 19:52:05 +00:00
|
|
|
}
|
2022-07-25 18:56:12 +00:00
|
|
|
|
2022-08-30 19:36:44 +00:00
|
|
|
_addSubtypeTo(sup, sub) {
|
|
|
|
if (this.isSubtypeOf(sub, sup)) return
|
|
|
|
const supSubs = this._subtypes[sup]
|
|
|
|
let i
|
|
|
|
for (i = 0; i < supSubs.length; ++i) {
|
|
|
|
if (this.isSubtypeOf(sub, supSubs[i])) break
|
|
|
|
}
|
|
|
|
supSubs.splice(i, 0, sub)
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Returns true if typeA is a strict subtype of type B */
|
|
|
|
isSubtypeOf = Returns('boolean', function(typeA, typeB) {
|
|
|
|
// Currently not considering types to be a subtype of 'any'
|
|
|
|
if (typeB === 'any' || typeA === 'any') return false
|
|
|
|
return this._subtypes[typeB].includes(typeA)
|
|
|
|
})
|
|
|
|
|
|
|
|
/* Returns true if typeA is a subtype of or converts to type B */
|
|
|
|
isPriorTo = Returns('boolean', function(typeA, typeB) {
|
|
|
|
if (!(typeB in this._priorTypes)) return false
|
|
|
|
return this._priorTypes[typeB].has(typeA)
|
|
|
|
})
|
|
|
|
|
|
|
|
/* Returns a list of the strict ubtypes of a given type, in topological
|
|
|
|
* sorted order (i.e, no type on the list contains one that comes after it).
|
|
|
|
*/
|
|
|
|
subtypesOf = Returns('Array<string>', function(type) {
|
|
|
|
return this._subtypes[type] // should we clone?
|
|
|
|
})
|
|
|
|
|
|
|
|
/* Returns a list of the supertypes of a given type, starting with itself,
|
|
|
|
* in topological order
|
|
|
|
*/
|
|
|
|
supertypesOf = Returns('Array<string>', function(type) {
|
|
|
|
const supList = []
|
|
|
|
while (type) {
|
|
|
|
supList.push(type)
|
|
|
|
type = this.Types[type].refines
|
|
|
|
}
|
|
|
|
return supList
|
|
|
|
})
|
|
|
|
|
|
|
|
/* Returns the most refined type containing all the types in the array,
|
|
|
|
* with '' standing for the empty type for convenience. If the second
|
2022-08-05 12:48:57 +00:00
|
|
|
* argument `convert` is true, a convertible type is considered a
|
|
|
|
* a subtype (defaults to false).
|
|
|
|
*/
|
2022-08-30 19:36:44 +00:00
|
|
|
joinTypes = Returns('string', function(types, convert) {
|
2022-08-05 12:48:57 +00:00
|
|
|
let join = ''
|
|
|
|
for (const type of types) {
|
|
|
|
join = this._joinTypes(join, type, convert)
|
|
|
|
}
|
|
|
|
return join
|
2022-08-30 19:36:44 +00:00
|
|
|
})
|
|
|
|
|
2022-08-05 12:48:57 +00:00
|
|
|
/* helper for above */
|
|
|
|
_joinTypes(typeA, typeB, convert) {
|
|
|
|
if (!typeA) return typeB
|
|
|
|
if (!typeB) return typeA
|
|
|
|
if (typeA === 'any' || typeB === 'any') return 'any'
|
|
|
|
if (typeA === typeB) return typeA
|
|
|
|
const subber = convert ? this._priorTypes : this._subtypes
|
2022-08-30 19:36:44 +00:00
|
|
|
const pick = convert ? 'has' : 'includes'
|
|
|
|
if (subber[typeB][pick](typeA)) return typeB
|
2022-08-05 12:48:57 +00:00
|
|
|
/* OK, so we need the most refined supertype of A that contains B:
|
|
|
|
*/
|
|
|
|
let nextSuper = typeA
|
|
|
|
while (nextSuper) {
|
2022-08-30 19:36:44 +00:00
|
|
|
if (subber[nextSuper][pick](typeB)) return nextSuper
|
2022-08-05 12:48:57 +00:00
|
|
|
nextSuper = this.Types[nextSuper].refines
|
|
|
|
}
|
|
|
|
/* And if conversions are allowed, we have to search the other way too */
|
|
|
|
if (convert) {
|
|
|
|
nextSuper = typeB
|
|
|
|
while (nextSuper) {
|
2022-08-30 19:36:44 +00:00
|
|
|
if (subber[nextSuper][pick](typeA)) return nextSuper
|
2022-08-05 12:48:57 +00:00
|
|
|
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:
|
|
|
|
*/
|
2022-08-30 19:36:44 +00:00
|
|
|
undefinedTypes = Returns('Array<string>', function() {
|
|
|
|
return Array.from(this._seenTypes).filter(
|
|
|
|
t => !(t in this.Types || t in this.Templates))
|
|
|
|
})
|
2022-08-05 12:48:57 +00:00
|
|
|
|
|
|
|
/* Used internally to install a template type */
|
|
|
|
_installTemplateType(type, spec) {
|
2022-08-30 19:36:44 +00:00
|
|
|
const [base] = splitTemplate(type)
|
2022-08-05 12:48:57 +00:00
|
|
|
/* 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-30 19:36:44 +00:00
|
|
|
|
|
|
|
// install the "base type" in the meta universe:
|
2022-12-01 17:47:20 +00:00
|
|
|
let beforeType = UniversalType
|
2022-08-30 19:36:44 +00:00
|
|
|
for (const other of spec.before || []) {
|
|
|
|
if (other in this.templates) {
|
|
|
|
beforeType = other
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this._metaTyped.addTypes([{name: base, test: spec.base}], beforeType)
|
2022-12-01 17:47:20 +00:00
|
|
|
// Add conversions to the base type:
|
|
|
|
if (spec.from && spec.from[theTemplateParam]) {
|
|
|
|
for (const ground of this._metafiedTypes) {
|
|
|
|
this._metaTyped.addConversion(
|
|
|
|
{from: ground, to: base, convert: spec.from[theTemplateParam]})
|
|
|
|
}
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
this._instantiationsOf[base] = new Set()
|
|
|
|
|
2022-08-06 15:27:44 +00:00
|
|
|
// update the typeOf function
|
|
|
|
const imp = {}
|
2022-08-30 19:36:44 +00:00
|
|
|
imp[type] = {
|
|
|
|
uses: new Set(['T']),
|
|
|
|
does: ({T}) => Returns('string', () => `${base}<${T}>`)
|
|
|
|
}
|
2022-08-06 15:27:44 +00:00
|
|
|
this._installFunctions({typeOf: imp})
|
|
|
|
|
2022-08-30 19:36:44 +00:00
|
|
|
// Invalidate any functions that reference this template type:
|
|
|
|
this._invalidateDependents(':' + base)
|
|
|
|
|
2022-08-06 15:27:44 +00:00
|
|
|
// 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-30 19:36:44 +00:00
|
|
|
/* Check if it's an ordinary non-template signature */
|
|
|
|
let explicit = true
|
|
|
|
for (const type of typesOfSignature(signature)) {
|
|
|
|
for (const word of type.split(/[<>:\s]/)) {
|
|
|
|
if (this._templateParam(word)) {
|
|
|
|
explicit = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!explicit) break
|
|
|
|
}
|
|
|
|
opImps[signature] = {
|
|
|
|
explicit,
|
|
|
|
resolved: false,
|
|
|
|
uses: behavior.uses,
|
|
|
|
does: behavior.does
|
|
|
|
}
|
|
|
|
if (!explicit) {
|
|
|
|
opImps[signature].hasInstantiations = {}
|
|
|
|
opImps[signature].needsInstantiations = new Set()
|
|
|
|
}
|
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.
|
2022-12-01 17:47:20 +00:00
|
|
|
* name is the name of the operation, badType is a type that has been
|
|
|
|
* invalidated, and reasons is a set of specific operations/signatures
|
|
|
|
* that have been invalidated
|
2022-07-19 00:08:49 +00:00
|
|
|
*/
|
2022-12-01 17:47:20 +00:00
|
|
|
_invalidate(name, badType = '', reasons = new Set()) {
|
2022-07-25 11:20:13 +00:00
|
|
|
if (!(name in this._imps)) {
|
|
|
|
this._imps[name] = {}
|
2022-08-30 19:36:44 +00:00
|
|
|
this._TFimps[name] = {}
|
|
|
|
this._metaTFimps[name] = {}
|
2022-07-25 11:20:13 +00:00
|
|
|
}
|
2022-12-01 17:47:20 +00:00
|
|
|
// Go through each TF imp and invalidate it if need be
|
|
|
|
for (const [signature, imp] of Object.entries(this._TFimps[name])) {
|
|
|
|
if (imp.deferred
|
|
|
|
|| (badType && signature.includes(badType))
|
|
|
|
|| exists(imp.uses, dep => {
|
|
|
|
const [func, sig] = dep.split(/[()]/)
|
|
|
|
return reasons.has(dep)
|
|
|
|
|| (reasons.has(func) && !(sig in this._TFimps[func]))
|
|
|
|
})) {
|
|
|
|
// Invalidate this implementation:
|
|
|
|
delete this._TFimps[name][signature]
|
|
|
|
const behavior = imp.fromBehavior
|
|
|
|
if (behavior.explicit) {
|
|
|
|
behavior.resolved = false
|
2022-08-30 19:36:44 +00:00
|
|
|
} else {
|
2022-12-01 17:47:20 +00:00
|
|
|
delete behavior.hasInstantiations[imp.instance]
|
2022-08-30 19:36:44 +00:00
|
|
|
}
|
2022-12-01 17:47:20 +00:00
|
|
|
reasons.add(`${name}(${signature})`)
|
2022-08-30 19:36:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (this._invalid.has(name)) return
|
2022-08-05 12:48:57 +00:00
|
|
|
this._invalid.add(name)
|
2022-12-01 17:47:20 +00:00
|
|
|
this._invalidateDependents(name, badType, reasons)
|
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
|
2022-12-01 17:47:20 +00:00
|
|
|
* reasons is a set of invalidated signatures
|
2022-07-25 11:20:13 +00:00
|
|
|
*/
|
2022-12-01 17:47:20 +00:00
|
|
|
_invalidateDependents(name, badType, reasons = new Set()) {
|
|
|
|
if (name.charAt(0) === ':') badType = name.slice(1)
|
|
|
|
else reasons.add(name)
|
2022-07-19 18:54:22 +00:00
|
|
|
if (name in this._affects) {
|
|
|
|
for (const ancestor of this._affects[name]) {
|
2022-12-01 17:47:20 +00:00
|
|
|
this._invalidate(ancestor, badType, reasons)
|
2022-07-19 18:54:22 +00:00
|
|
|
}
|
|
|
|
}
|
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-30 19:36:44 +00:00
|
|
|
const tf_imps = this._TFimps[name]
|
|
|
|
const meta_imps = this._metaTFimps[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
|
2022-08-30 19:36:44 +00:00
|
|
|
const [baseType] = splitTemplate(type)
|
2022-08-05 12:48:57 +00:00
|
|
|
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-08-01 10:09:32 +00:00
|
|
|
for (const [rawSignature, behavior] of usableEntries) {
|
2022-08-30 19:36:44 +00:00
|
|
|
if (behavior.explicit) {
|
|
|
|
if (!(behavior.resolved)) {
|
2022-12-01 17:47:20 +00:00
|
|
|
this._addTFimplementation(name, tf_imps, rawSignature, behavior)
|
2022-08-30 19:36:44 +00:00
|
|
|
tf_imps[rawSignature]._pocoSignature = rawSignature
|
|
|
|
behavior.resolved = true
|
2022-08-01 10:09:32 +00:00
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
/* It's a template, have to instantiate */
|
2022-08-30 19:36:44 +00:00
|
|
|
/* First, find any upper bounds on the instantation */
|
|
|
|
/* TODO: handle multiple upper bounds */
|
|
|
|
upperBounds.lastIndex = 0
|
|
|
|
let ubType = upperBounds.exec(rawSignature)
|
|
|
|
if (ubType) {
|
|
|
|
ubType = ubType[2]
|
|
|
|
if (!ubType in this.Types) {
|
|
|
|
throw new TypeError(
|
|
|
|
`Unknown type upper bound '${ubType}' in '${rawSignature}'`)
|
|
|
|
}
|
2022-08-01 10:09:32 +00:00
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
/* First, add the known instantiations, gathering all types needed */
|
|
|
|
if (ubType) behavior.needsInstantiations.add(ubType)
|
2022-12-01 17:47:20 +00:00
|
|
|
const nargs = typeListOfSignature(rawSignature).length
|
2022-08-01 10:09:32 +00:00
|
|
|
let instantiationSet = new Set()
|
2022-08-30 19:36:44 +00:00
|
|
|
const ubTypes = new Set()
|
|
|
|
if (!ubType) {
|
|
|
|
// Collect all upper-bound types for this signature
|
|
|
|
for (const othersig in imps) {
|
2022-12-01 17:47:20 +00:00
|
|
|
const otherNargs = typeListOfSignature(othersig).length
|
|
|
|
if (nargs !== otherNargs) {
|
|
|
|
// crude criterion that it won't match, that ignores
|
|
|
|
// rest args, but hopefully OK for prototype
|
|
|
|
continue
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
const thisUB = upperBounds.exec(othersig)
|
|
|
|
if (thisUB) ubTypes.add(thisUB[2])
|
|
|
|
let basesig = othersig.replaceAll(templateCall, '')
|
|
|
|
if (basesig !== othersig) {
|
|
|
|
// A template
|
|
|
|
const testsig = substituteInSignature(
|
|
|
|
basesig, theTemplateParam, '')
|
|
|
|
if (testsig === basesig) {
|
|
|
|
// that is not also top-level
|
2022-12-01 17:47:20 +00:00
|
|
|
for (let templateType of typeListOfSignature(basesig)) {
|
2022-08-30 19:36:44 +00:00
|
|
|
if (templateType.slice(0,3) === '...') {
|
|
|
|
templateType = templateType.slice(3)
|
|
|
|
}
|
|
|
|
ubTypes.add(templateType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const instType of behavior.needsInstantiations) {
|
2022-08-05 12:48:57 +00:00
|
|
|
instantiationSet.add(instType)
|
2022-08-30 19:36:44 +00:00
|
|
|
const otherTypes =
|
2022-12-01 17:47:20 +00:00
|
|
|
ubType ? this.subtypesOf(instType) : this._priorTypes[instType]
|
2022-08-30 19:36:44 +00:00
|
|
|
for (const other of otherTypes) {
|
|
|
|
if (!(this._atOrBelowSomeType(other, ubTypes))) {
|
|
|
|
instantiationSet.add(other)
|
|
|
|
}
|
2022-08-01 10:09:32 +00:00
|
|
|
}
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
/* Prevent other existing signatures from blocking use of top-level
|
|
|
|
* templates via conversions:
|
|
|
|
*/
|
|
|
|
let baseSignature = rawSignature.replaceAll(templateCall, '')
|
|
|
|
/* Any remaining template params are top-level */
|
|
|
|
const signature = substituteInSignature(
|
2022-12-01 17:47:20 +00:00
|
|
|
baseSignature, theTemplateParam, UniversalType)
|
2022-08-30 19:36:44 +00:00
|
|
|
const hasTopLevel = (signature !== baseSignature)
|
|
|
|
if (!ubType && hasTopLevel) {
|
|
|
|
for (const othersig in imps) {
|
|
|
|
let basesig = othersig.replaceAll(templateCall, '')
|
|
|
|
const testsig = substituteInSignature(
|
|
|
|
basesig, theTemplateParam, '')
|
|
|
|
if (testsig !== basesig) continue // a top-level template
|
|
|
|
for (let othertype of typeListOfSignature(othersig)) {
|
|
|
|
if (othertype.slice(0,3) === '...') {
|
|
|
|
othertype = othertype.slice(3)
|
|
|
|
}
|
|
|
|
if (this.Types[othertype] === anySpec) continue
|
|
|
|
const testType = substituteInSignature(
|
|
|
|
othertype, theTemplateParam, '')
|
|
|
|
let otherTypeCollection = [othertype]
|
|
|
|
if (testType !== othertype) {
|
|
|
|
const [base] = splitTemplate(othertype)
|
|
|
|
otherTypeCollection = this._instantiationsOf[base]
|
|
|
|
}
|
|
|
|
for (const possibility of otherTypeCollection) {
|
|
|
|
for (const convtype of this._priorTypes[possibility]) {
|
|
|
|
if (this.isSubtypeOf(convtype, possibility)) continue
|
|
|
|
if (!(this._atOrBelowSomeType(convtype, ubTypes))) {
|
|
|
|
instantiationSet.add(convtype)
|
|
|
|
}
|
|
|
|
}
|
2022-07-25 11:20:13 +00:00
|
|
|
}
|
2022-07-19 18:54:22 +00:00
|
|
|
}
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
}
|
|
|
|
for (const instType of instantiationSet) {
|
2022-12-01 17:47:20 +00:00
|
|
|
this._instantiateTemplateImplementation(
|
|
|
|
name, rawSignature, instType)
|
2022-08-01 10:09:32 +00:00
|
|
|
}
|
|
|
|
/* Now add the catchall signature */
|
2022-08-30 19:36:44 +00:00
|
|
|
/* (Not needed if if it's a bounded template) */
|
|
|
|
if (ubType) continue
|
|
|
|
if (behavior.resolved) continue
|
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-30 19:36:44 +00:00
|
|
|
|
2022-08-05 12:48:57 +00:00
|
|
|
/* Now build the catchall implementation */
|
2022-08-01 10:09:32 +00:00
|
|
|
const self = this
|
2022-08-30 19:36:44 +00:00
|
|
|
/* For return type annotation, we may have to fix this to
|
|
|
|
propagate the return type. At the moment we are just bagging
|
|
|
|
*/
|
|
|
|
const patch = () => {
|
|
|
|
const patchFunc = (...tfBundledArgs) => {
|
|
|
|
/* We unbundle the rest arg if there is one */
|
|
|
|
let args = Array.from(tfBundledArgs)
|
|
|
|
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') {
|
2022-12-01 17:47:20 +00:00
|
|
|
console.log('INCOMPATIBLE ARGUMENTS are', args)
|
2022-08-30 19:36:44 +00:00
|
|
|
throw TypeError(
|
|
|
|
`In call to ${name}, `
|
|
|
|
+ 'incompatible template arguments:'
|
|
|
|
// + args.map(a => JSON.stringify(a)).join(', ')
|
|
|
|
// unfortunately barfs on bigints. Need a better
|
|
|
|
// formatter. I 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)
|
|
|
|
}
|
|
|
|
argTypes.push(argType)
|
2022-08-05 12:48:57 +00:00
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
}
|
|
|
|
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') {
|
2022-12-01 17:47:20 +00:00
|
|
|
let argDisplay = args.map(toString).join(', ')
|
2022-08-05 12:48:57 +00:00
|
|
|
throw TypeError(
|
2022-08-30 19:36:44 +00:00
|
|
|
`In call to ${name}, no type unifies arguments `
|
2022-12-01 17:47:20 +00:00
|
|
|
+ argDisplay + '; of types ' + argTypes.toString()
|
2022-08-30 19:36:44 +00:00
|
|
|
+ '; note each consecutive pair must unify to a '
|
|
|
|
+ 'supertype of at least one of them')
|
2022-08-05 12:48:57 +00:00
|
|
|
}
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
const depth = instantiateFor.split('<').length
|
|
|
|
if (depth > self._maxDepthSeen) {
|
|
|
|
self._maxDepthSeen = depth
|
2022-08-05 12:48:57 +00:00
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
/* Generate the list of actual wanted types */
|
|
|
|
const wantTypes = parTypes.map(type => substituteInSignature(
|
|
|
|
type, theTemplateParam, instantiateFor))
|
|
|
|
const wantSig = wantTypes.join(',')
|
|
|
|
/* 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
|
2022-12-01 17:47:20 +00:00
|
|
|
const strippedType = parTypes[j].substr(
|
|
|
|
parTypes[j].lastIndexOf('.') + 1)
|
|
|
|
self._ensureTemplateTypes(strippedType, instantiateFor)
|
2022-08-05 12:48:57 +00:00
|
|
|
}
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
|
|
|
|
/* Request the desired instantiation: */
|
|
|
|
// But possibly since this resolution was grabbed, the proper
|
|
|
|
// instantiation has been added (like if there are multiple
|
|
|
|
// uses in the implementation of another method.
|
2022-12-01 17:47:20 +00:00
|
|
|
let whatToDo
|
|
|
|
if (!(instantiateFor in behavior.hasInstantiations)) {
|
|
|
|
const newImp = self._instantiateTemplateImplementation(
|
|
|
|
name, rawSignature, instantiateFor)
|
|
|
|
if (newImp) {
|
|
|
|
whatToDo = {fn: newImp, implementation: newImp}
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
self._invalidate(name)
|
|
|
|
}
|
|
|
|
const brandNewMe = self[name]
|
2022-12-01 17:47:20 +00:00
|
|
|
const betterToDo = self._typed.resolve(brandNewMe, args)
|
|
|
|
whatToDo = betterToDo || whatToDo
|
|
|
|
|
2022-08-30 19:36:44 +00:00
|
|
|
// We can access return type information here
|
|
|
|
// And in particular, if it might be a template, we should try to
|
|
|
|
// instantiate it:
|
|
|
|
const returnType = returnTypeOf(whatToDo.fn, wantSig, self)
|
|
|
|
for (const possibility of returnType.split('|')) {
|
|
|
|
const instantiated = self._maybeInstantiate(possibility)
|
|
|
|
if (instantiated) {
|
|
|
|
const [tempBase] = splitTemplate(instantiated)
|
|
|
|
self._invalidateDependents(':' + tempBase)
|
2022-08-05 12:48:57 +00:00
|
|
|
}
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
if (whatToDo === lastWhatToDo) {
|
|
|
|
throw new Error(
|
2022-12-01 17:47:20 +00:00
|
|
|
`Infinite recursion in resolving ${name} called on `
|
|
|
|
+ args.map(x =>
|
|
|
|
(typeof x === 'object'
|
|
|
|
? JSON.stringify(x)
|
|
|
|
: x.toString())
|
|
|
|
).join(', ')
|
|
|
|
+ ` inferred to be ${wantSig}`)
|
2022-08-30 19:36:44 +00:00
|
|
|
}
|
|
|
|
lastWhatToDo = whatToDo
|
|
|
|
const retval = whatToDo.implementation(...args)
|
|
|
|
lastWhatToDo = null
|
|
|
|
return retval
|
2022-08-05 12:48:57 +00:00
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
Object.defineProperty(
|
|
|
|
patchFunc, 'name', {value: `${name}(${signature})`})
|
|
|
|
patchFunc._pocoSignature = rawSignature
|
|
|
|
return patchFunc
|
2022-08-01 10:09:32 +00:00
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
Object.defineProperty(
|
|
|
|
patch, 'name', {value: `generate[${name}(${signature})]`})
|
|
|
|
// TODO?: Decorate patch with a function that calculates the
|
|
|
|
// correct return type a priori. Deferring because unclear what
|
|
|
|
// aspects will be merged into typed-function.
|
2022-08-01 10:09:32 +00:00
|
|
|
this._addTFimplementation(
|
2022-12-01 17:47:20 +00:00
|
|
|
name, meta_imps, signature,
|
|
|
|
{uses: new Set(), does: patch},
|
|
|
|
behavior)
|
2022-08-30 19:36:44 +00:00
|
|
|
behavior.resolved = true
|
2022-07-19 00:08:49 +00:00
|
|
|
}
|
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) {
|
2022-12-01 17:47:20 +00:00
|
|
|
if (!tf_imps[sig].uses) {
|
|
|
|
throw new ReferenceError(`MONKEY WRENCH: ${name} ${sig}`)
|
|
|
|
}
|
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
|
|
|
for (const type of typeListOfSignature(sig)) {
|
2022-08-30 19:36:44 +00:00
|
|
|
if (this._maybeInstantiate(type) === undefined) {
|
|
|
|
badSigs.add(sig)
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const badSig of badSigs) {
|
2022-08-30 19:36:44 +00:00
|
|
|
const imp = tf_imps[badSig]
|
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
|
|
|
delete tf_imps[badSig]
|
2022-08-30 19:36:44 +00:00
|
|
|
const fromBehavior = this._imps[name][imp._pocoSignature]
|
|
|
|
if (fromBehavior.explicit) {
|
|
|
|
fromBehavior.resolved = false
|
|
|
|
} else {
|
|
|
|
delete fromBehavior.hasInstantiations[imp._pocoInstance]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let tf
|
|
|
|
if (Object.keys(tf_imps).length > 0) {
|
|
|
|
tf = this._typed(name, tf_imps)
|
|
|
|
tf.fromInstance = this
|
2022-12-01 17:47:20 +00:00
|
|
|
tf.isMeta = false
|
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
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
let metaTF
|
|
|
|
if (Object.keys(meta_imps).length > 0) {
|
|
|
|
metaTF = this._metaTyped(name, meta_imps)
|
|
|
|
metaTF.fromInstance = this
|
2022-12-01 17:47:20 +00:00
|
|
|
metaTF.isMeta = true
|
2022-08-30 19:36:44 +00:00
|
|
|
}
|
|
|
|
this._meta[name] = metaTF
|
|
|
|
|
|
|
|
tf = tf || metaTF
|
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-30 19:36:44 +00:00
|
|
|
/* Takes a type and a set of types and returns true if the type
|
|
|
|
* is a subtype of some type in the set.
|
|
|
|
*/
|
|
|
|
_atOrBelowSomeType(type, typeSet) {
|
|
|
|
if (typeSet.has(type)) return true
|
|
|
|
let belowSome = false
|
|
|
|
for (const anUB of typeSet) {
|
|
|
|
if (anUB in this.Templates) {
|
|
|
|
if (type.slice(0, anUB.length) === anUB) {
|
|
|
|
belowSome = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (this.isSubtypeOf(type, anUB)) {
|
|
|
|
belowSome = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return belowSome
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Takes an arbitrary type and performs an instantiation if necessary.
|
|
|
|
* @param {string} type The type to instantiate
|
|
|
|
* @param {string | bool | undefined }
|
|
|
|
* Returns the name of the type if an instantiation occurred, false
|
|
|
|
* if the type was already present, and undefined if the type can't
|
|
|
|
* be satisfied (because it is not the name of a type or it is nested
|
|
|
|
* too deep.
|
|
|
|
*/
|
|
|
|
_maybeInstantiate(type) {
|
|
|
|
if (type.slice(0,3) === '...') {
|
|
|
|
type = type.slice(3)
|
|
|
|
}
|
|
|
|
if (!(type.includes('<'))) {
|
|
|
|
// Not a template, so just check if type exists
|
|
|
|
if (type in this.Types) return false // already there
|
|
|
|
return undefined // no such type
|
|
|
|
}
|
|
|
|
// it's a template type, turn it into a template and an arg
|
|
|
|
let [base, arg] = splitTemplate(type)
|
|
|
|
return this.instantiateTemplate(base, arg)
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Generate and include a template instantiation for operation name
|
|
|
|
* for the template signature templateSignature instantiated for
|
|
|
|
* instanceType, returning the resulting implementation.
|
|
|
|
*/
|
|
|
|
_instantiateTemplateImplementation(name, templateSignature, instanceType) {
|
|
|
|
if (!(instanceType in this.Types)) return undefined
|
|
|
|
if (this.Types[instanceType] === anySpec) return undefined
|
|
|
|
const imps = this._imps[name]
|
|
|
|
const behavior = imps[templateSignature]
|
|
|
|
if (instanceType in behavior.hasInstantiations) return undefined
|
|
|
|
const signature = substituteInSignature(
|
|
|
|
templateSignature, theTemplateParam, instanceType)
|
|
|
|
/* Don't override an explicit implementation: */
|
|
|
|
if (signature in imps) return undefined
|
|
|
|
/* 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) return undefined
|
|
|
|
/* All right, go ahead and instantiate */
|
|
|
|
const uses = new Set()
|
|
|
|
for (const dep of behavior.uses) {
|
|
|
|
if (this._templateParam(dep)) continue
|
|
|
|
uses.add(substituteInSignature(dep, theTemplateParam, instanceType))
|
|
|
|
}
|
|
|
|
const patch = (refs) => {
|
|
|
|
const innerRefs = {}
|
|
|
|
for (const dep of behavior.uses) {
|
|
|
|
if (this._templateParam(dep)) {
|
|
|
|
innerRefs[dep] = instanceType
|
|
|
|
} else {
|
|
|
|
const outerName = substituteInSignature(
|
|
|
|
dep, theTemplateParam, instanceType)
|
|
|
|
innerRefs[dep] = refs[outerName]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return behavior.does(innerRefs)
|
|
|
|
}
|
|
|
|
const tf_imps = this._TFimps[name]
|
2022-12-01 17:47:20 +00:00
|
|
|
this._addTFimplementation(
|
|
|
|
name, tf_imps, signature, {uses, does: patch}, behavior, instanceType)
|
2022-08-30 19:36:44 +00:00
|
|
|
tf_imps[signature]._pocoSignature = templateSignature
|
|
|
|
tf_imps[signature]._pocoInstance = instanceType
|
|
|
|
behavior.hasInstantiations[instanceType] = signature
|
2022-12-01 17:47:20 +00:00
|
|
|
behavior.needsInstantiations.add(instanceType) // once we have it, keep it
|
2022-08-30 19:36:44 +00:00
|
|
|
return tf_imps[signature]
|
|
|
|
}
|
|
|
|
|
2022-08-01 10:09:32 +00:00
|
|
|
/* Adapts Pocomath-style behavior specification (uses, does) for signature
|
2022-08-30 19:36:44 +00:00
|
|
|
* to typed-function implementations and inserts the result into plain
|
|
|
|
* object imps
|
2022-08-01 10:09:32 +00:00
|
|
|
*/
|
2022-12-01 17:47:20 +00:00
|
|
|
_addTFimplementation(
|
|
|
|
name, imps, signature, specificBehavior, fromImp, asInstance)
|
|
|
|
{
|
|
|
|
if (!fromImp) fromImp = specificBehavior
|
|
|
|
const {uses, does} = specificBehavior
|
2022-08-01 10:09:32 +00:00
|
|
|
if (uses.length === 0) {
|
2022-08-30 19:36:44 +00:00
|
|
|
const implementation = does()
|
2022-12-01 17:47:20 +00:00
|
|
|
implementation.uses = uses
|
|
|
|
implementation.fromInstance = this
|
|
|
|
implementation.fromBehavior = fromImp
|
|
|
|
implementation.instance = asInstance
|
2022-08-30 19:36:44 +00:00
|
|
|
// could do something with return type information here
|
|
|
|
imps[signature] = implementation
|
2022-08-01 10:09:32 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
const refs = {}
|
|
|
|
let full_self_referential = false
|
|
|
|
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) {
|
2022-08-30 19:36:44 +00:00
|
|
|
const trysig = substituteInSignature(needsig, theTemplateParam, '')
|
2022-08-05 12:48:57 +00:00
|
|
|
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) {
|
2022-12-01 17:47:20 +00:00
|
|
|
/* We now resolve all specific-signature self references
|
|
|
|
* here, without resorting to the facility in typed-function:
|
2022-08-30 19:36:44 +00:00
|
|
|
*/
|
|
|
|
if (needsig in imps && typeof imps[needsig] == 'function') {
|
|
|
|
refs[dep] = imps[needsig]
|
2022-12-01 17:47:20 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
const needTypes = typesOfSignature(needsig)
|
|
|
|
const mergedTypes = Object.assign(
|
|
|
|
{}, this.Types, this.Templates)
|
|
|
|
if (subsetOfKeys(needTypes, mergedTypes)) {
|
|
|
|
func = name // just resolve it in limbo
|
2022-08-30 19:36:44 +00:00
|
|
|
} else {
|
2022-12-01 17:47:20 +00:00
|
|
|
// uses an unknown type, so will get an undefined impl
|
|
|
|
console.log(
|
|
|
|
'WARNING: partial self-reference for', name, 'to',
|
|
|
|
needsig, 'uses an unknown type')
|
|
|
|
refs[dep] = undefined
|
|
|
|
continue
|
2022-08-01 10:09:32 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
full_self_referential = true
|
2022-12-01 17:47:20 +00:00
|
|
|
continue
|
2022-08-01 10:09:32 +00:00
|
|
|
}
|
2022-12-01 17:47:20 +00:00
|
|
|
}
|
|
|
|
if (this[func] === 'limbo') {
|
|
|
|
/* We are in the midst of bundling func (which may be ourself) */
|
|
|
|
/* So the first thing we can do is try the tf_imps we are
|
|
|
|
* accumulating:
|
|
|
|
*/
|
|
|
|
if (needsig) {
|
|
|
|
const candidate = this.resolve(func, needsig)
|
|
|
|
if (typeof candidate === 'function') {
|
|
|
|
refs[dep] = candidate
|
|
|
|
continue
|
2022-08-01 10:09:32 +00:00
|
|
|
}
|
|
|
|
}
|
2022-12-01 17:47:20 +00:00
|
|
|
/* Either we need the whole function or the signature
|
|
|
|
* we need is not available yet, so we 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
|
|
|
|
const redirect = function () { // is this the most efficient?
|
|
|
|
return self[func].apply(this, arguments)
|
|
|
|
}
|
|
|
|
Object.defineProperty(redirect, 'name', {value: func})
|
|
|
|
Object.defineProperty(redirect, 'fromInstance', {value: this})
|
|
|
|
refs[dep] = redirect
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// can bundle up func, and grab its signature if need be
|
|
|
|
let destination = this[func]
|
|
|
|
if (needsig) {
|
|
|
|
destination = this.resolve(func, needsig)
|
|
|
|
}
|
|
|
|
if (!destination) {
|
|
|
|
// Unresolved reference. This is allowed so that
|
|
|
|
// you can bundle up just some portions of the library,
|
|
|
|
// but let's warn.
|
|
|
|
console.log(
|
|
|
|
'WARNING: No definition found for dependency',
|
|
|
|
dep, 'needed by', name, '(', signature, ')')
|
2022-08-01 10:09:32 +00:00
|
|
|
}
|
2022-12-01 17:47:20 +00:00
|
|
|
refs[dep] = destination
|
2022-08-01 10:09:32 +00:00
|
|
|
}
|
|
|
|
if (full_self_referential) {
|
|
|
|
imps[signature] = this._typed.referToSelf(self => {
|
|
|
|
refs.self = self
|
2022-08-30 19:36:44 +00:00
|
|
|
const implementation = does(refs)
|
|
|
|
Object.defineProperty(implementation, 'name', {value: does.name})
|
|
|
|
implementation.fromInstance = this
|
2022-12-01 17:47:20 +00:00
|
|
|
implementation.uses = uses
|
|
|
|
implementation.instance = asInstance
|
|
|
|
implementation.fromBehavior = fromImp
|
2022-08-30 19:36:44 +00:00
|
|
|
// What are we going to do with the return type info in here?
|
|
|
|
return implementation
|
2022-08-01 10:09:32 +00:00
|
|
|
})
|
2022-12-01 17:47:20 +00:00
|
|
|
imps[signature].uses = uses
|
|
|
|
imps[signature].fromInstance = this
|
|
|
|
imps[signature].instance = asInstance
|
|
|
|
imps[signature].fromBehavior = fromImp
|
2022-08-05 12:48:57 +00:00
|
|
|
return
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
const implementation = does(refs)
|
|
|
|
implementation.fromInstance = this
|
2022-12-01 17:47:20 +00:00
|
|
|
implementation.fromBehavior = fromImp
|
|
|
|
implementation.instance = asInstance
|
|
|
|
implementation.uses = uses
|
2022-08-30 19:36:44 +00:00
|
|
|
// could do something with return type information here?
|
|
|
|
imps[signature] = implementation
|
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) {
|
2022-08-30 19:36:44 +00:00
|
|
|
const [base, arg] = splitTemplate(template)
|
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
|
|
|
|
}
|
|
|
|
|
2022-08-30 19:36:44 +00:00
|
|
|
/* Maybe add the instantiation of template type base with argument type
|
|
|
|
* 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-30 19:36:44 +00:00
|
|
|
instantiateTemplate = Returns('void', function(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
|
2022-08-30 19:36:44 +00:00
|
|
|
const newTypeSpec = {}
|
2022-08-05 12:48:57 +00:00
|
|
|
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]
|
2022-08-30 19:36:44 +00:00
|
|
|
if (instantiatorSpec.refines) {
|
|
|
|
this.instantiateTemplate(base, instantiatorSpec.refines)
|
|
|
|
// Assuming our templates are covariant, I guess
|
|
|
|
newTypeSpec.refines = `${base}<${instantiatorSpec.refines}>`
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
let beforeTypes = []
|
|
|
|
if (instantiatorSpec.before) {
|
|
|
|
beforeTypes = instantiatorSpec.before.map(type => `${base}<${type}>`)
|
|
|
|
}
|
|
|
|
if (template.before) {
|
|
|
|
for (const beforeTmpl of template.before) {
|
|
|
|
beforeTypes.push(
|
2022-08-30 19:36:44 +00:00
|
|
|
substituteInSignature(beforeTmpl, theTemplateParam, instantiator))
|
2022-08-05 12:48:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (beforeTypes.length > 0) {
|
|
|
|
newTypeSpec.before = beforeTypes
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
const templateTest = template.test(this._typeTests[instantiator])
|
|
|
|
newTypeSpec.test = x => (template.base(x) && templateTest(x))
|
2022-08-05 12:48:57 +00:00
|
|
|
if (template.from) {
|
|
|
|
for (let source in template.from) {
|
2022-08-30 19:36:44 +00:00
|
|
|
const instSource = substituteInSignature(
|
2022-08-05 12:48:57 +00:00
|
|
|
source, theTemplateParam, instantiator)
|
2022-08-30 19:36:44 +00:00
|
|
|
const testSource = substituteInSignature(
|
|
|
|
instSource, templateFromParam, instantiator)
|
|
|
|
const usesFromParam = (testSource !== instSource)
|
2022-08-05 12:48:57 +00:00
|
|
|
if (usesFromParam) {
|
|
|
|
for (const iFrom in instantiatorSpec.from) {
|
2022-08-30 19:36:44 +00:00
|
|
|
const finalSource = substituteInSignature(
|
2022-08-05 12:48:57 +00:00
|
|
|
instSource, templateFromParam, iFrom)
|
|
|
|
maybeFrom[finalSource] = template.from[source](
|
|
|
|
instantiatorSpec.from[iFrom])
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
if (testSource !== wantsType) { // subtypes handled with refines
|
|
|
|
// Assuming all templates are covariant here, I guess...
|
|
|
|
for (const subType of this._subtypes[instantiator]) {
|
|
|
|
const finalSource = substituteInSignature(
|
|
|
|
instSource, templateFromParam, subType)
|
|
|
|
maybeFrom[finalSource] = template.from[source](x => x)
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
maybeFrom[instSource] = template.from[source]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Object.keys(maybeFrom).length > 0) {
|
|
|
|
newTypeSpec.from = maybeFrom
|
|
|
|
}
|
|
|
|
this.installType(wantsType, newTypeSpec)
|
2022-08-30 19:36:44 +00:00
|
|
|
this._instantiationsOf[base].add(wantsType)
|
2022-08-05 12:48:57 +00:00
|
|
|
return wantsType
|
2022-08-30 19:36:44 +00:00
|
|
|
})
|
2022-08-05 12:48:57 +00:00
|
|
|
|
2022-12-01 17:47:20 +00:00
|
|
|
_findSubtypeImpl(name, imps, neededSig, raw = false) {
|
|
|
|
const detemplate = !raw
|
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
|
2022-12-01 17:47:20 +00:00
|
|
|
let paramBound = UniversalType
|
2022-08-05 12:48:57 +00:00
|
|
|
for (let k = 0; k < typeList.length; ++k) {
|
|
|
|
let myType = typeList[k]
|
|
|
|
let otherType = otherTypeList[k]
|
|
|
|
if (otherType === theTemplateParam) {
|
2022-12-01 17:47:20 +00:00
|
|
|
if (detemplate) otherTypeList[k] = paramBound
|
2022-08-30 19:36:44 +00:00
|
|
|
otherType = paramBound
|
2022-08-05 12:48:57 +00:00
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
if (otherType === restTemplateParam) {
|
2022-12-01 17:47:20 +00:00
|
|
|
if (detemplate) otherTypeList[k] = `...${paramBound}`
|
2022-08-30 19:36:44 +00:00
|
|
|
otherType = paramBound
|
2022-08-05 12:48:57 +00:00
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
const adjustedOtherType = otherType.replaceAll(templateCall, '')
|
2022-08-05 12:48:57 +00:00
|
|
|
if (adjustedOtherType !== otherType) {
|
2022-12-01 17:47:20 +00:00
|
|
|
if (detemplate) otherTypeList[k] = adjustedOtherType
|
2022-08-05 12:48:57 +00:00
|
|
|
otherType = adjustedOtherType
|
|
|
|
}
|
|
|
|
if (myType.slice(0,3) === '...') myType = myType.slice(3)
|
|
|
|
if (otherType.slice(0,3) === '...') otherType = otherType.slice(3)
|
2022-08-30 19:36:44 +00:00
|
|
|
const otherBound = upperBounds.exec(otherType)
|
|
|
|
if (otherBound) {
|
|
|
|
paramBound = otherBound[2]
|
|
|
|
otherType = paramBound
|
2022-12-01 17:47:20 +00:00
|
|
|
if (detemplate) {
|
|
|
|
otherTypeList[k] = otherBound[1].replaceAll(
|
|
|
|
theTemplateParam, paramBound)
|
|
|
|
}
|
2022-08-05 12:48:57 +00:00
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
if (otherType === 'any') continue
|
2022-12-01 17:47:20 +00:00
|
|
|
if (otherType === UniversalType) continue
|
2022-08-30 19:36:44 +00:00
|
|
|
if (myType === otherType) continue
|
2022-08-06 15:27:44 +00:00
|
|
|
if (otherType in this.Templates) {
|
2022-08-30 19:36:44 +00:00
|
|
|
const [myBase] = splitTemplate(myType)
|
|
|
|
if (myBase === otherType) continue
|
2022-08-06 15:27:44 +00:00
|
|
|
if (this.instantiateTemplate(otherType, myType)) {
|
|
|
|
let dummy
|
|
|
|
dummy = this[name] // for side effects
|
2022-12-01 17:47:20 +00:00
|
|
|
return this._findSubtypeImpl(name, this._imps[name], neededSig, raw)
|
2022-08-06 15:27:44 +00:00
|
|
|
}
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
if (!(otherType in this.Types)) {
|
|
|
|
allMatch = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if (this.isSubtypeOf(myType, otherType)) continue
|
2022-08-05 12:48:57 +00:00
|
|
|
allMatch = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if (allMatch) {
|
|
|
|
foundSig = otherTypeList.join(',')
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return foundSig
|
|
|
|
}
|
|
|
|
|
2022-08-30 19:36:44 +00:00
|
|
|
_pocoFindSignature(name, sig, typedFunction) {
|
2022-08-06 15:27:44 +00:00
|
|
|
if (!this._typed.isTypedFunction(typedFunction)) {
|
|
|
|
typedFunction = this[name]
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
const haveTF = this._typed.isTypedFunction(typedFunction)
|
2022-12-01 17:47:20 +00:00
|
|
|
&& !(typedFunction.isMeta)
|
2022-08-30 19:36:44 +00:00
|
|
|
if (haveTF) {
|
|
|
|
// First try a direct match
|
|
|
|
let result
|
|
|
|
try {
|
|
|
|
result = this._typed.findSignature(typedFunction, sig, {exact: true})
|
|
|
|
} catch {
|
|
|
|
}
|
|
|
|
if (result) return result
|
|
|
|
// Next, look ourselves but with subtypes:
|
|
|
|
const wantTypes = typeListOfSignature(sig)
|
|
|
|
for (const [implSig, details]
|
|
|
|
of typedFunction._typedFunctionData.signatureMap) {
|
|
|
|
let allMatched = true
|
|
|
|
const implTypes = typeListOfSignature(implSig)
|
2022-12-01 17:47:20 +00:00
|
|
|
if (implTypes.length > wantTypes.length) {
|
|
|
|
// Not enough arguments for that implementation
|
|
|
|
continue
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
for (let i = 0; i < wantTypes.length; ++i) {
|
|
|
|
const implIndex = Math.min(i, implTypes.length - 1)
|
|
|
|
let implType = implTypes[implIndex]
|
|
|
|
if (implIndex < i) {
|
|
|
|
if (implType.slice(0,3) !== '...') {
|
|
|
|
// ran out of arguments in impl
|
|
|
|
allMatched = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (implType.slice(0,3) === '...') {
|
|
|
|
implType = implType.slice(3)
|
|
|
|
}
|
|
|
|
const hasMatch = implType.split('|').some(
|
|
|
|
t => (wantTypes[i] === t || this.isSubtypeOf(wantTypes[i], t)))
|
|
|
|
if (!hasMatch) {
|
|
|
|
allMatched = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (allMatched) return details
|
|
|
|
}
|
2022-08-06 15:27:44 +00:00
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
if (!(this._imps[name])) return undefined
|
2022-12-01 17:47:20 +00:00
|
|
|
const foundsig = this._findSubtypeImpl(name, this._imps[name], sig, 'raw')
|
2022-08-30 19:36:44 +00:00
|
|
|
if (foundsig) {
|
|
|
|
if (haveTF) {
|
|
|
|
try {
|
|
|
|
return this._typed.findSignature(typedFunction, foundsig)
|
|
|
|
} catch {
|
|
|
|
}
|
|
|
|
}
|
2022-12-01 17:47:20 +00:00
|
|
|
const instantiationMatcher =
|
|
|
|
'^'
|
|
|
|
+ substituteInSignature(foundsig, theTemplateParam, '(.*)')
|
|
|
|
.replaceAll(UniversalType, '(.*)')
|
|
|
|
+ '$'
|
|
|
|
const instanceMatch = sig.match(instantiationMatcher)
|
|
|
|
let possibleInstantiator = false
|
|
|
|
if (instanceMatch) {
|
|
|
|
possibleInstantiator = instanceMatch[1]
|
|
|
|
for (let i = 2; i < instanceMatch.length; ++i) {
|
|
|
|
if (possibleInstantiator !== instanceMatch[i]) {
|
|
|
|
possibleInstantiator = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (possibleInstantiator) {
|
|
|
|
const behavior = this._imps[name][foundsig]
|
|
|
|
let newInstance
|
|
|
|
if (behavior) {
|
|
|
|
if (!(possibleInstantiator in behavior.hasInstantiations)) {
|
|
|
|
newInstance = this._instantiateTemplateImplementation(
|
|
|
|
name, foundsig, possibleInstantiator)
|
|
|
|
} else {
|
|
|
|
// OK, so we actually have the instantiation. Let's get it
|
|
|
|
newInstance = this._TFimps[name][sig]
|
|
|
|
}
|
|
|
|
// But we may not have taken advantage of conversions
|
|
|
|
this._invalidate(name)
|
|
|
|
const tryAgain = this[name]
|
|
|
|
let betterInstance
|
|
|
|
if (this._typed.isTypedFunction(tryAgain)) {
|
|
|
|
betterInstance = this._typed.findSignature(tryAgain, sig)
|
|
|
|
}
|
|
|
|
if (betterInstance) {
|
|
|
|
newInstance = betterInstance
|
|
|
|
} else {
|
|
|
|
newInstance = {
|
|
|
|
fn: newInstance,
|
|
|
|
implementation: newInstance
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (newInstance) return newInstance
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const catchallSig = this._findSubtypeImpl(name, this._imps[name], sig)
|
|
|
|
if (catchallSig !== foundsig) {
|
|
|
|
try {
|
|
|
|
return this._metaTyped.findSignature(
|
|
|
|
this._meta[name], catchallSig)
|
|
|
|
} catch {
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
}
|
|
|
|
// We have an implementation but not a typed function. Do the best
|
|
|
|
// we can:
|
2022-12-01 17:47:20 +00:00
|
|
|
const restoredSig = foundsig.replaceAll('ground', theTemplateParam)
|
|
|
|
const foundImpl = this._imps[name][restoredSig]
|
2022-08-30 19:36:44 +00:00
|
|
|
const needs = {}
|
|
|
|
for (const dep of foundImpl.uses) {
|
2022-12-01 17:47:20 +00:00
|
|
|
const [base, sig] = dep.split(/[()]/)
|
|
|
|
if (sig) {
|
|
|
|
needs[dep] = this.resolve(base, sig)
|
|
|
|
} else {
|
|
|
|
needs[dep] = this[dep]
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
}
|
|
|
|
const pseudoImpl = foundImpl.does(needs)
|
2022-12-01 17:47:20 +00:00
|
|
|
pseudoImpl.fromInstance = this
|
2022-08-30 19:36:44 +00:00
|
|
|
return {fn: pseudoImpl, implementation: pseudoImpl}
|
|
|
|
}
|
|
|
|
// Hmm, no luck. Make sure bundle is up-to-date and retry:
|
|
|
|
let result = undefined
|
2022-08-06 15:27:44 +00:00
|
|
|
typedFunction = this[name]
|
|
|
|
try {
|
2022-08-30 19:36:44 +00:00
|
|
|
result = this._typed.findSignature(typedFunction, sig)
|
2022-08-05 12:48:57 +00:00
|
|
|
} catch {
|
|
|
|
}
|
2022-08-30 19:36:44 +00:00
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Returns a function that implements the operation with the given name
|
|
|
|
* when called with the given signature. The optional third argument is
|
|
|
|
* the typed function that provides the operation name, which can be
|
|
|
|
* passed in for efficiency if it is already available.
|
|
|
|
*/
|
|
|
|
resolve = Returns('function', function (name, sig, typedFunction) {
|
|
|
|
if (!this._typed.isTypedFunction(typedFunction)) {
|
|
|
|
typedFunction = this[name]
|
|
|
|
}
|
|
|
|
const result = this._pocoFindSignature(name, sig, typedFunction)
|
|
|
|
if (result) return result.implementation
|
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-30 19:36:44 +00:00
|
|
|
})
|
2022-08-05 12:48:57 +00:00
|
|
|
|
2022-07-19 00:08:49 +00:00
|
|
|
}
|