feat(PocomathInstance): Add subtypesOf and isSubtypeOf methods
Note this involves refeactoring the internal subtype tracker to keep subtypes in a list sorted in topological order.
This commit is contained in:
parent
340dbd436e
commit
dc6921e768
@ -30,9 +30,12 @@ export default class PocomathInstance {
|
||||
'install',
|
||||
'installType',
|
||||
'instantiateTemplate',
|
||||
'isSubtypeOf',
|
||||
'joinTypes',
|
||||
'name',
|
||||
'returnTypeOf',
|
||||
'self',
|
||||
'subtypesOf',
|
||||
'Templates',
|
||||
'typeOf',
|
||||
'Types',
|
||||
@ -56,7 +59,8 @@ export default class PocomathInstance {
|
||||
this.Templates = {}
|
||||
// The actual type testing functions
|
||||
this._typeTests = {}
|
||||
this._subtypes = {} // For each type, gives all of its (in)direct subtypes
|
||||
// For each type, gives all of its (in)direct subtypes in topo order:
|
||||
this._subtypes = {}
|
||||
/* 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
|
||||
@ -334,14 +338,14 @@ export default class PocomathInstance {
|
||||
this._typeTests[type] = testFn
|
||||
this._typed.addTypes([{name: type, test: testFn}], beforeType)
|
||||
this.Types[type] = spec
|
||||
this._subtypes[type] = new Set()
|
||||
this._subtypes[type] = []
|
||||
this._priorTypes[type] = new Set()
|
||||
// Update all the subtype sets of supertypes up the chain
|
||||
let nextSuper = spec.refines
|
||||
while (nextSuper) {
|
||||
this._invalidateDependents(':' + nextSuper)
|
||||
this._priorTypes[nextSuper].add(type)
|
||||
this._subtypes[nextSuper].add(type)
|
||||
this._addSubtypeTo(nextSuper, type)
|
||||
nextSuper = this.Types[nextSuper].refines
|
||||
}
|
||||
/* Now add conversions to this type */
|
||||
@ -369,8 +373,8 @@ export default class PocomathInstance {
|
||||
for (const fromtype in this.Types[to].from) {
|
||||
if (type == fromtype
|
||||
|| (fromtype in this._subtypes
|
||||
&& this._subtypes[fromtype].has(type))) {
|
||||
if (spec.refines == to || spec.refines in this._subtypes[to]) {
|
||||
&& this.isSubtypeOf(type, fromtype))) {
|
||||
if (spec.refines == to || this.isSubtypeOf(spec.refines,to)) {
|
||||
throw new SyntaxError(
|
||||
`Conversion of ${type} to its supertype ${to} disallowed.`)
|
||||
}
|
||||
@ -398,8 +402,30 @@ export default class PocomathInstance {
|
||||
this._installFunctions({typeOf: imp})
|
||||
}
|
||||
|
||||
/* Returns the most refined type of all the types in the array, with
|
||||
* '' standing for the empty type for convenience. If the second
|
||||
_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 is typeA is a subtype of type B */
|
||||
isSubtypeOf(typeA, typeB) {
|
||||
return this._subtypes[typeB].includes(typeA)
|
||||
}
|
||||
|
||||
/* Returns a list of the subtypes of a given type, in topological sorted
|
||||
* order (i.e, no type on the list contains one that comes after it).
|
||||
*/
|
||||
subtypesOf(type) {
|
||||
// HERE! For this to work, have to maintain subtypes as a sorted list.
|
||||
return this._subtypes[type] // should we clone?
|
||||
}
|
||||
/* Returns the most refined type containing all the types in the array,
|
||||
* with '' standing for the empty type for convenience. If the second
|
||||
* argument `convert` is true, a convertible type is considered a
|
||||
* a subtype (defaults to false).
|
||||
*/
|
||||
@ -418,19 +444,20 @@ export default class PocomathInstance {
|
||||
if (typeA === 'ground' || typeB === 'ground') return 'ground'
|
||||
if (typeA === typeB) return typeA
|
||||
const subber = convert ? this._priorTypes : this._subtypes
|
||||
if (subber[typeB].has(typeA)) return typeB
|
||||
const pick = convert ? 'has' : 'includes'
|
||||
if (subber[typeB][pick](typeA)) return typeB
|
||||
/* OK, so we need the most refined supertype of A that contains B:
|
||||
*/
|
||||
let nextSuper = typeA
|
||||
while (nextSuper) {
|
||||
if (subber[nextSuper].has(typeB)) return nextSuper
|
||||
if (subber[nextSuper][pick](typeB)) return nextSuper
|
||||
nextSuper = this.Types[nextSuper].refines
|
||||
}
|
||||
/* And if conversions are allowed, we have to search the other way too */
|
||||
if (convert) {
|
||||
nextSuper = typeB
|
||||
while (nextSuper) {
|
||||
if (subber[nextSuper].has(typeA)) return nextSuper
|
||||
if (subber[nextSuper][pick](typeA)) return nextSuper
|
||||
nextSuper = this.Types[nextSuper].refines
|
||||
}
|
||||
}
|
||||
@ -1132,7 +1159,7 @@ export default class PocomathInstance {
|
||||
break
|
||||
}
|
||||
if (myType === otherType
|
||||
|| this._subtypes[otherType].has(myType)) {
|
||||
|| this.isSubtypeOf(myType, otherType)) {
|
||||
continue
|
||||
}
|
||||
if (otherType in this.Templates) {
|
||||
|
@ -22,6 +22,7 @@ describe('The default full pocomath instance "math"', () => {
|
||||
|
||||
it('can determine the return types of operations', () => {
|
||||
assert.strictEqual(math.returnTypeOf('negate', 'number'), 'number')
|
||||
assert.strictEqual(math.returnTypeOf('negate', 'NumInt'), 'NumInt')
|
||||
})
|
||||
|
||||
it('can subtract numbers', () => {
|
||||
|
Loading…
Reference in New Issue
Block a user