From dc6921e768561cfd95ad0db05f52f2c667be3624 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Fri, 12 Aug 2022 09:10:53 -0700 Subject: [PATCH] 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. --- src/core/PocomathInstance.mjs | 49 +++++++++++++++++++++++++++-------- test/_pocomath.mjs | 1 + 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/core/PocomathInstance.mjs b/src/core/PocomathInstance.mjs index f8d9c5a..398bcee 100644 --- a/src/core/PocomathInstance.mjs +++ b/src/core/PocomathInstance.mjs @@ -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) { diff --git a/test/_pocomath.mjs b/test/_pocomath.mjs index 9e52a25..c5e8969 100644 --- a/test/_pocomath.mjs +++ b/test/_pocomath.mjs @@ -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', () => {