refactor: avoid inheritance in src/core/TypePatterns
All checks were successful
/ test (pull_request) Successful in 17s

This commit is contained in:
Glen Whitney 2025-12-12 02:15:50 -08:00
parent b32d4a2058
commit d89e64709d

View file

@ -5,7 +5,7 @@ const tpBrand = Symbol()
export class TypePattern { export class TypePattern {
constructor() { constructor() {
this[tpBrand] = true throw new Error('Cannot construct an abstract TypePattern')
} }
match(_typeSequence, _options={}) { match(_typeSequence, _options={}) {
throw new Error('Specific TypePatterns must implement match') throw new Error('Specific TypePatterns must implement match')
@ -13,16 +13,15 @@ export class TypePattern {
sampleTypes() { sampleTypes() {
throw new Error('Specific TypePatterns must implement sampleTypes') throw new Error('Specific TypePatterns must implement sampleTypes')
} }
equal(other) {return other.constructor === this.constructor}
toString() {return 'Abstract Pattern (?!)'} toString() {return 'Abstract Pattern (?!)'}
// Returns true if entity is a TypePattern // Returns true if entity is a TypePattern
static holds(entity) {return entity[tpBrand]} static holds(entity) {return entity[tpBrand]}
} }
class MatchTypePattern extends TypePattern { class MatchTypePattern {
constructor(typeToMatch) { constructor(typeToMatch) {
super() this[tpBrand] = true
this.type = typeToMatch this.type = typeToMatch
} }
match(typeSequence, options={}) { match(typeSequence, options={}) {
@ -42,13 +41,15 @@ class MatchTypePattern extends TypePattern {
return [-1, Undefined] return [-1, Undefined]
} }
sampleTypes() {return [this.type]} sampleTypes() {return [this.type]}
equal(other) {return super.equal(other) && this.type === other.type} equal(other) {
return this.constructor === other.constructor && this.type === other.type
}
toString() {return `Match(${this.type})`} toString() {return `Match(${this.type})`}
} }
class SequencePattern extends TypePattern { class SequencePattern {
constructor(itemsToMatch) { constructor(itemsToMatch) {
super() this[tpBrand] = true
this.patterns = itemsToMatch.map(pattern) this.patterns = itemsToMatch.map(pattern)
} }
match(typeSequence, options={_internal: true}) { match(typeSequence, options={_internal: true}) {
@ -70,16 +71,16 @@ class SequencePattern extends TypePattern {
return this.patterns.map(pat => pat.sampleTypes()).flat() return this.patterns.map(pat => pat.sampleTypes()).flat()
} }
equal(other) { equal(other) {
return super.equal(other) return this.constructor === other.constructor
&& this.patterns.length === other.patterns.length && this.patterns.length === other.patterns.length
&& this.patterns.every((elt, ix) => elt.equal(other.patterns[ix])) && this.patterns.every((elt, ix) => elt.equal(other.patterns[ix]))
} }
toString() {return `[${this.patterns}]`} toString() {return `[${this.patterns}]`}
} }
class PredicatePattern extends TypePattern { class PredicatePattern {
constructor(predicate) { constructor(predicate) {
super() this[tpBrand] = true
this.predicate = predicate this.predicate = predicate
} }
match(typeSequence, options={}) { match(typeSequence, options={}) {
@ -93,7 +94,8 @@ class PredicatePattern extends TypePattern {
throw new Error('sampleTypes() not yet implemented for PredicatePattern') throw new Error('sampleTypes() not yet implemented for PredicatePattern')
} }
equal(other) { equal(other) {
return super.equal(other) && this.predicate === other.predicate return this.constructor === other.constructor
&& this.predicate === other.predicate
} }
toString() {return `Test(${this.predicate})`} toString() {return `Test(${this.predicate})`}
} }
@ -110,7 +112,8 @@ export const pattern = patternOrSpec => {
throw new TypeError(`Can't interpret '${patternOrSpec}' as a type pattern`) throw new TypeError(`Can't interpret '${patternOrSpec}' as a type pattern`)
} }
class AnyPattern extends TypePattern { class AnyPattern {
constructor () {this[tpBrand] = true}
match(typeSequence, options={}) { match(typeSequence, options={}) {
const position = options.position ?? 0 const position = options.position ?? 0
return position < typeSequence.length return position < typeSequence.length
@ -118,14 +121,15 @@ class AnyPattern extends TypePattern {
: [-1, Undefined] : [-1, Undefined]
} }
sampleTypes() {return [Undefined]} sampleTypes() {return [Undefined]}
equal(other) {return this.constructor === other.constructor}
toString() {return 'Any'} toString() {return 'Any'}
} }
export const Any = new AnyPattern() export const Any = new AnyPattern()
class OptionalPattern extends TypePattern { class OptionalPattern {
constructor(item) { constructor(item) {
super() this[tpBrand] = true
this.pattern = pattern(item) this.pattern = pattern(item)
} }
match(typeSequence, options={_internal: true}) { match(typeSequence, options={_internal: true}) {
@ -143,16 +147,17 @@ class OptionalPattern extends TypePattern {
} }
sampleTypes() {return []} sampleTypes() {return []}
equal(other) { equal(other) {
return super.equal(other) && this.pattern.equal(other.pattern) return this.constructor === other.constructor
&& this.pattern.equal(other.pattern)
} }
toString() {return `?${this.pattern}`} toString() {return `?${this.pattern}`}
} }
export const Optional = item => new OptionalPattern(item) export const Optional = item => new OptionalPattern(item)
class MultiPattern extends TypePattern { class MultiPattern {
constructor(item) { constructor(item) {
super() this[tpBrand] = true
this.pattern = pattern(item) this.pattern = pattern(item)
} }
match(typeSequence, options={_internal: true}) { match(typeSequence, options={_internal: true}) {
@ -170,7 +175,8 @@ class MultiPattern extends TypePattern {
} }
sampleTypes() {return []} sampleTypes() {return []}
equal(other) { equal(other) {
return super.equal(other) && this.pattern.equal(other.pattern) return this.constructor === other.constructor
&& this.pattern.equal(other.pattern)
} }
toString() {return `${this.pattern}*`} toString() {return `${this.pattern}*`}
} }
@ -179,13 +185,15 @@ export const Multiple = item => new MultiPattern(item)
// Like Multiple(Any) except leaves the argument list alone; it doesn't // Like Multiple(Any) except leaves the argument list alone; it doesn't
// chunk it into a single Array of all arguments // chunk it into a single Array of all arguments
class PassthruPattern extends TypePattern { class PassthruPattern {
constructor () {this[tpBrand] = true}
match(typeSequence, options={}) { match(typeSequence, options={}) {
const position = options.position ?? 0 const position = options.position ?? 0
return [typeSequence.length, typeSequence.slice(position)] return [typeSequence.length, typeSequence.slice(position)]
} }
sampleTypes() {return []} sampleTypes() {return []}
toString() {return 'Passthru'} toString() {return 'Passthru'}
equal(other) {return this.constructor === other.constructor}
} }
export const Passthru = new PassthruPattern() export const Passthru = new PassthruPattern()