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:
Glen Whitney 2022-08-12 09:10:53 -07:00
parent 340dbd436e
commit dc6921e768
2 changed files with 39 additions and 11 deletions

View File

@ -30,9 +30,12 @@ export default class PocomathInstance {
'install', 'install',
'installType', 'installType',
'instantiateTemplate', 'instantiateTemplate',
'isSubtypeOf',
'joinTypes', 'joinTypes',
'name', 'name',
'returnTypeOf',
'self', 'self',
'subtypesOf',
'Templates', 'Templates',
'typeOf', 'typeOf',
'Types', 'Types',
@ -56,7 +59,8 @@ export default class PocomathInstance {
this.Templates = {} this.Templates = {}
// The actual type testing functions // The actual type testing functions
this._typeTests = {} 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 /* The following gives for each type, a set of all types that could
* match in typed-function's dispatch algorithm before the given type. * match in typed-function's dispatch algorithm before the given type.
* This is important because if we instantiate a template, we must * This is important because if we instantiate a template, we must
@ -334,14 +338,14 @@ export default class PocomathInstance {
this._typeTests[type] = testFn this._typeTests[type] = testFn
this._typed.addTypes([{name: type, test: testFn}], beforeType) this._typed.addTypes([{name: type, test: testFn}], beforeType)
this.Types[type] = spec this.Types[type] = spec
this._subtypes[type] = new Set() this._subtypes[type] = []
this._priorTypes[type] = new Set() this._priorTypes[type] = new Set()
// Update all the subtype sets of supertypes up the chain // Update all the subtype sets of supertypes up the chain
let nextSuper = spec.refines let nextSuper = spec.refines
while (nextSuper) { while (nextSuper) {
this._invalidateDependents(':' + nextSuper) this._invalidateDependents(':' + nextSuper)
this._priorTypes[nextSuper].add(type) this._priorTypes[nextSuper].add(type)
this._subtypes[nextSuper].add(type) this._addSubtypeTo(nextSuper, type)
nextSuper = this.Types[nextSuper].refines nextSuper = this.Types[nextSuper].refines
} }
/* Now add conversions to this type */ /* Now add conversions to this type */
@ -369,8 +373,8 @@ export default class PocomathInstance {
for (const fromtype in this.Types[to].from) { for (const fromtype in this.Types[to].from) {
if (type == fromtype if (type == fromtype
|| (fromtype in this._subtypes || (fromtype in this._subtypes
&& this._subtypes[fromtype].has(type))) { && this.isSubtypeOf(type, fromtype))) {
if (spec.refines == to || spec.refines in this._subtypes[to]) { if (spec.refines == to || this.isSubtypeOf(spec.refines,to)) {
throw new SyntaxError( throw new SyntaxError(
`Conversion of ${type} to its supertype ${to} disallowed.`) `Conversion of ${type} to its supertype ${to} disallowed.`)
} }
@ -398,8 +402,30 @@ export default class PocomathInstance {
this._installFunctions({typeOf: imp}) this._installFunctions({typeOf: imp})
} }
/* Returns the most refined type of all the types in the array, with _addSubtypeTo(sup, sub) {
* '' standing for the empty type for convenience. If the second 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 * argument `convert` is true, a convertible type is considered a
* a subtype (defaults to false). * a subtype (defaults to false).
*/ */
@ -418,19 +444,20 @@ export default class PocomathInstance {
if (typeA === 'ground' || typeB === 'ground') return 'ground' if (typeA === 'ground' || typeB === 'ground') return 'ground'
if (typeA === typeB) return typeA if (typeA === typeB) return typeA
const subber = convert ? this._priorTypes : this._subtypes 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: /* OK, so we need the most refined supertype of A that contains B:
*/ */
let nextSuper = typeA let nextSuper = typeA
while (nextSuper) { while (nextSuper) {
if (subber[nextSuper].has(typeB)) return nextSuper if (subber[nextSuper][pick](typeB)) return nextSuper
nextSuper = this.Types[nextSuper].refines nextSuper = this.Types[nextSuper].refines
} }
/* And if conversions are allowed, we have to search the other way too */ /* And if conversions are allowed, we have to search the other way too */
if (convert) { if (convert) {
nextSuper = typeB nextSuper = typeB
while (nextSuper) { while (nextSuper) {
if (subber[nextSuper].has(typeA)) return nextSuper if (subber[nextSuper][pick](typeA)) return nextSuper
nextSuper = this.Types[nextSuper].refines nextSuper = this.Types[nextSuper].refines
} }
} }
@ -1132,7 +1159,7 @@ export default class PocomathInstance {
break break
} }
if (myType === otherType if (myType === otherType
|| this._subtypes[otherType].has(myType)) { || this.isSubtypeOf(myType, otherType)) {
continue continue
} }
if (otherType in this.Templates) { if (otherType in this.Templates) {

View File

@ -22,6 +22,7 @@ describe('The default full pocomath instance "math"', () => {
it('can determine the return types of operations', () => { it('can determine the return types of operations', () => {
assert.strictEqual(math.returnTypeOf('negate', 'number'), 'number') assert.strictEqual(math.returnTypeOf('negate', 'number'), 'number')
assert.strictEqual(math.returnTypeOf('negate', 'NumInt'), 'NumInt')
}) })
it('can subtract numbers', () => { it('can subtract numbers', () => {