diff --git a/src/Regexer.js b/src/Regexer.js index 95c1690..f34c6e8 100644 --- a/src/Regexer.js +++ b/src/Regexer.js @@ -98,10 +98,6 @@ export default class Regexer { static equals(lhs, rhs, strict = false) { const a = lhs.getParser() const b = rhs.getParser() - if (b instanceof a.constructor && !(a instanceof b.constructor)) { - // typeof b extends typeof a, invert to take advantage of polymorphism - return b.equals(Reply.makeContext(rhs), a, strict) - } return a.equals(Reply.makeContext(lhs), b, strict) } diff --git a/src/parser/Parser.js b/src/parser/Parser.js index 3eb4c2c..8e80bf9 100644 --- a/src/parser/Parser.js +++ b/src/parser/Parser.js @@ -85,25 +85,35 @@ export default class Parser { /** * @param {Context} context - * @param {Parser} other + * @param {Parser} rhs * @param {Boolean} strict */ - equals(context, other, strict) { - let self = /** @type {Parser} */(this) - if (self === other) { + equals(context, rhs, strict) { + let lhs = /** @type {Parser} */(this) + if (lhs === rhs) { return true } if (!strict) { - self = this.actualParser() - other = other.actualParser() + lhs = this.actualParser() + rhs = rhs.actualParser() + } + if ( + rhs instanceof lhs.constructor && !(lhs instanceof rhs.constructor) + // @ts-expect-error + || rhs.resolve && !lhs.resolve + ) { + // Take advantage of polymorphism or compare a lazy against a non lazy (not the other way around) + const temp = lhs + lhs = rhs + rhs = temp } - let memoized = context.visited.get(self, other) + let memoized = context.visited.get(lhs, rhs) if (memoized !== undefined) { return memoized } else if (memoized === undefined) { - context.visited.set(self, other, true) - memoized = self.doEquals(context, other, strict) - context.visited.set(self, other, memoized) + context.visited.set(lhs, rhs, true) + memoized = lhs.doEquals(context, rhs, strict) + context.visited.set(lhs, rhs, memoized) } return memoized } diff --git a/tests/equality.spec.js b/tests/equality.spec.js index 707282b..6edd224 100644 --- a/tests/equality.spec.js +++ b/tests/equality.spec.js @@ -340,6 +340,7 @@ test("Test 12", async ({ page }) => { expect(R.equals(lhs, rhs, true)).toBeTruthy() expect(R.equals(rhs, lhs, true)).toBeTruthy() + // It's similar to rhs but with some omissions that still make it functionally equal const rhs2 = R.nonGrp(R.seq( R.str("Italy"), R.regexp(/Switzerland/).chain(fp), @@ -360,19 +361,21 @@ test("Test 12", async ({ page }) => { expect(R.equals(rhs2, lhs)).toBeTruthy() expect(R.equals(lhs, rhs2, true)).toBeFalsy() expect(R.equals(rhs2, lhs, true)).toBeFalsy() + expect(R.equals(lhs, rhs2, true)).toBeFalsy() + expect(R.equals(rhs2, lhs, true)).toBeFalsy() }) test("Test 13", async ({ page }) => { class Grammar { /** @type {Regexer>} */ static a = R.seq(R.str("a"), R.str("a"), R.lazy(() => this.a)) - static root = this.a } - expect(R.equals( - Grammar.a, - R.seq(R.str("a"), R.str("a"), R.seq(R.str("a"), R.str("a"), R.seq(R.str("a"), R.str("a"), Grammar.a))), - true - )).toBeTruthy() + const other = + R.seq(R.str("a"), R.str("a"), R.seq(R.str("a"), R.str("a"), R.seq(R.str("a"), R.str("a"), Grammar.a))) + expect(R.equals(Grammar.a, other)).toBeTruthy() + expect(R.equals(other, Grammar.a)).toBeTruthy() + expect(R.equals(Grammar.a, other, true)).toBeTruthy() + expect(R.equals(other, Grammar.a, true)).toBeTruthy() }) test("Test 14", async ({ page }) => { @@ -384,11 +387,11 @@ test("Test 14", async ({ page }) => { static d = R.seq(Grammar.c, R.str("d")) static root = this.d } - expect(R.equals( - Grammar.root, - R.seq(R.alt(R.regexp(/a/).map(f3), Grammar.b, R.seq(Grammar.c, R.str("d"))), R.str("d")), - true - )).toBeTruthy() + const other = R.seq(R.alt(R.regexp(/a/).map(f3), Grammar.b, R.seq(Grammar.c, R.str("d"))), R.str("d")) + expect(R.equals(Grammar.root, other)).toBeTruthy() + expect(R.equals(other, Grammar.root)).toBeTruthy() + expect(R.equals(Grammar.root, other, true)).toBeTruthy() + expect(R.equals(other, Grammar.root, true)).toBeTruthy() }) test("Test 15", async ({ page }) => { @@ -397,10 +400,10 @@ test("Test 15", async ({ page }) => { static a = R.seq(R.str("a"), R.str("a"), R.lazy(() => this.a)) static b = R.seq(R.str("a"), R.str("a"), R.lazy(() => this.b)) } - expect(R.equals( - Grammar.a, - Grammar.b, - )).toBeTruthy() + expect(R.equals(Grammar.a, Grammar.b)).toBeTruthy() + expect(R.equals(Grammar.b, Grammar.a)).toBeTruthy() + expect(R.equals(Grammar.a, Grammar.b, true)).toBeTruthy() + expect(R.equals(Grammar.b, Grammar.a, true)).toBeTruthy() }) test("Test 16", async ({ page }) => { @@ -409,10 +412,10 @@ test("Test 16", async ({ page }) => { static a = R.seq(R.str("a"), R.str("a"), R.lazy(() => this.a)) static b = R.seq(R.str("a"), R.lazy(() => this.b)) } - expect(R.equals( - Grammar.a, - Grammar.b, - )).toBeFalsy() + expect(R.equals(Grammar.a, Grammar.b)).toBeFalsy() + expect(R.equals(Grammar.b, Grammar.a)).toBeFalsy() + expect(R.equals(Grammar.a, Grammar.b, true)).toBeFalsy() + expect(R.equals(Grammar.b, Grammar.a, true)).toBeFalsy() }) test("Test 17", async ({ page }) => { @@ -421,8 +424,26 @@ test("Test 17", async ({ page }) => { static a = R.seq(R.str("a"), R.lazy(() => this.a)) static b = R.seq(R.str("a"), R.seq(R.str("a"), R.seq(R.str("a"), R.lazy(() => this.b)))) } - expect(R.equals( - Grammar.a, - Grammar.b, - )).toBeTruthy() + expect(R.equals(Grammar.a, Grammar.b)).toBeTruthy() + expect(R.equals(Grammar.b, Grammar.a)).toBeTruthy() + expect(R.equals(Grammar.a, Grammar.b, true)).toBeTruthy() + expect(R.equals(Grammar.b, Grammar.a, true)).toBeTruthy() +}) + +test("Test 18", async ({ page }) => { + class Grammar { + /** @type {Regexer>} */ + static a1 = R.seq(R.grp(R.str("a").many()), R.lazy(() => this.a1), R.str("x")) + /** @type {Regexer>} */ + static a2 = R.seq(R.grp(R.str("a").many()), R.lazy(() => this.a2), R.str("y")) + static b = R.seq(R.grp(R.str("a").many()), R.lazy(() => R.seq(R.grp(R.str("a").many()), R.lazy(() => this.b), R.str("y"))), R.str("y")) + } + expect(R.equals(Grammar.a2, Grammar.b)).toBeTruthy() + expect(R.equals(Grammar.b, Grammar.a2)).toBeTruthy() + expect(R.equals(Grammar.a2, Grammar.b, true)).toBeTruthy() + expect(R.equals(Grammar.b, Grammar.a2, true)).toBeTruthy() + expect(R.equals(Grammar.a1, Grammar.b)).toBeFalsy() + expect(R.equals(Grammar.b, Grammar.a1)).toBeFalsy() + expect(R.equals(Grammar.a1, Grammar.b, true)).toBeFalsy() + expect(R.equals(Grammar.b, Grammar.a1, true)).toBeFalsy() })