From ed8aa90556273ad04a02217bde746000dce72e7f Mon Sep 17 00:00:00 2001 From: Charles Loder Date: Sat, 28 Oct 2023 18:47:51 -0400 Subject: [PATCH 01/10] Refactor return to variable --- src/syllable.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/syllable.ts b/src/syllable.ts index 8ebe6d0..f761717 100644 --- a/src/syllable.ts +++ b/src/syllable.ts @@ -268,13 +268,14 @@ export class Syllable extends Node { structure(withGemination: boolean = false): [string, string, string] { const heClusters = this.clusters.filter((c) => !c.isNotHebrew); if (heClusters.length === 0) { - return ["", "", ""]; + const structure: [string, string, string] = ["", "", ""]; + return structure; } // Initial shureq: If the syllable starts with a shureq, then it has no // onset, its nucleus is the shureq, and its coda is any remaining clusters const first = heClusters[0]; if (first.isShureq) { - return [ + const structure: [string, string, string] = [ "", first.text, heClusters @@ -282,6 +283,7 @@ export class Syllable extends Node { .map((c) => c.text) .join("") ]; + return structure; } // Furtive patah: If the syllable is final and is either a het, ayin, or he // (with dagesh) followed by a patah, then it has no onset, its nucleus is @@ -289,7 +291,8 @@ export class Syllable extends Node { if (this.isFinal && !this.isClosed) { const matchFurtive = this.text.match(/(\u{05D7}|\u{05E2}|\u{05D4}\u{05BC})(\u{05B7})(\u{05C3})?$/mu); if (matchFurtive) { - return ["", matchFurtive[2], matchFurtive[1] + (matchFurtive[3] || "")]; + const structure: [string, string, string] = ["", matchFurtive[2], matchFurtive[1] + (matchFurtive[3] || "")]; + return structure; } } // Otherwise: @@ -338,7 +341,8 @@ export class Syllable extends Node { } } } - return [onset, nucleus, coda]; + const structure: [string, string, string] = [onset, nucleus, coda]; + return structure; } /** From 3d90be20d167602cf9f4e1379864c8339215aa6f Mon Sep 17 00:00:00 2001 From: Charles Loder Date: Sat, 28 Oct 2023 18:51:49 -0400 Subject: [PATCH 02/10] Add caching --- src/syllable.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/syllable.ts b/src/syllable.ts index f761717..b794230 100644 --- a/src/syllable.ts +++ b/src/syllable.ts @@ -34,6 +34,8 @@ export class Syllable extends Node { #isAccented: boolean; #isFinal: boolean; #word: Word | null = null; + #cachedStructure: [string, string, string] | null = null; + #cachedStructureWithGemination: [string, string, string] | null = null; /** * @@ -266,9 +268,19 @@ export class Syllable extends Node { * ``` */ structure(withGemination: boolean = false): [string, string, string] { + if (withGemination && this.#cachedStructureWithGemination) { + return this.#cachedStructureWithGemination; + } + + if (this.#cachedStructure) { + return this.#cachedStructure; + } + const heClusters = this.clusters.filter((c) => !c.isNotHebrew); if (heClusters.length === 0) { const structure: [string, string, string] = ["", "", ""]; + this.#cachedStructure = structure; + this.#cachedStructureWithGemination = structure; return structure; } // Initial shureq: If the syllable starts with a shureq, then it has no @@ -283,6 +295,8 @@ export class Syllable extends Node { .map((c) => c.text) .join("") ]; + this.#cachedStructure = structure; + this.#cachedStructureWithGemination = structure; return structure; } // Furtive patah: If the syllable is final and is either a het, ayin, or he @@ -292,6 +306,8 @@ export class Syllable extends Node { const matchFurtive = this.text.match(/(\u{05D7}|\u{05E2}|\u{05D4}\u{05BC})(\u{05B7})(\u{05C3})?$/mu); if (matchFurtive) { const structure: [string, string, string] = ["", matchFurtive[2], matchFurtive[1] + (matchFurtive[3] || "")]; + this.#cachedStructure = structure; + this.#cachedStructureWithGemination = structure; return structure; } } @@ -342,6 +358,11 @@ export class Syllable extends Node { } } const structure: [string, string, string] = [onset, nucleus, coda]; + if (withGemination) { + this.#cachedStructureWithGemination = structure; + } else { + this.#cachedStructure = structure; + } return structure; } From cbe1df0fedf57abc93df9f00d0ca41f4b1f862dd Mon Sep 17 00:00:00 2001 From: Charles Loder Date: Sun, 29 Oct 2023 21:23:18 -0400 Subject: [PATCH 03/10] Update lint rules --- src/syllable.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/syllable.ts b/src/syllable.ts index b794230..81dd587 100644 --- a/src/syllable.ts +++ b/src/syllable.ts @@ -278,6 +278,8 @@ export class Syllable extends Node { const heClusters = this.clusters.filter((c) => !c.isNotHebrew); if (heClusters.length === 0) { + // eslint complains about shadowing, but I think it makes sense here + /* eslint-disable-next-line @typescript-eslint/no-shadow */ const structure: [string, string, string] = ["", "", ""]; this.#cachedStructure = structure; this.#cachedStructureWithGemination = structure; @@ -287,6 +289,8 @@ export class Syllable extends Node { // onset, its nucleus is the shureq, and its coda is any remaining clusters const first = heClusters[0]; if (first.isShureq) { + // eslint complains about shadowing, but I think it makes sense here + /* eslint-disable-next-line @typescript-eslint/no-shadow */ const structure: [string, string, string] = [ "", first.text, @@ -305,6 +309,8 @@ export class Syllable extends Node { if (this.isFinal && !this.isClosed) { const matchFurtive = this.text.match(/(\u{05D7}|\u{05E2}|\u{05D4}\u{05BC})(\u{05B7})(\u{05C3})?$/mu); if (matchFurtive) { + // eslint complains about shadowing, but I think it makes sense here + /* eslint-disable-next-line @typescript-eslint/no-shadow */ const structure: [string, string, string] = ["", matchFurtive[2], matchFurtive[1] + (matchFurtive[3] || "")]; this.#cachedStructure = structure; this.#cachedStructureWithGemination = structure; From dab0a65b22d0b96d63bd524706344379c528b7f9 Mon Sep 17 00:00:00 2001 From: Charles Loder Date: Sun, 29 Oct 2023 21:24:37 -0400 Subject: [PATCH 04/10] Update block spacing --- src/syllable.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/syllable.ts b/src/syllable.ts index 81dd587..28fc913 100644 --- a/src/syllable.ts +++ b/src/syllable.ts @@ -285,6 +285,7 @@ export class Syllable extends Node { this.#cachedStructureWithGemination = structure; return structure; } + // Initial shureq: If the syllable starts with a shureq, then it has no // onset, its nucleus is the shureq, and its coda is any remaining clusters const first = heClusters[0]; @@ -303,6 +304,7 @@ export class Syllable extends Node { this.#cachedStructureWithGemination = structure; return structure; } + // Furtive patah: If the syllable is final and is either a het, ayin, or he // (with dagesh) followed by a patah, then it has no onset, its nucleus is // the patah and its coda is the consonant @@ -317,6 +319,7 @@ export class Syllable extends Node { return structure; } } + // Otherwise: // 1. The onset is any initial consonant, ligature, dagesh, and/or rafe // (as defined in char.ts) of the first cluster @@ -363,6 +366,7 @@ export class Syllable extends Node { } } } + const structure: [string, string, string] = [onset, nucleus, coda]; if (withGemination) { this.#cachedStructureWithGemination = structure; From 977e97baf73d06e3abdfd4c6706eae27d7fa04b1 Mon Sep 17 00:00:00 2001 From: Charles Loder Date: Sun, 29 Oct 2023 21:25:24 -0400 Subject: [PATCH 05/10] Refactor vowel prop --- src/syllable.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/syllable.ts b/src/syllable.ts index 28fc913..11e6eec 100644 --- a/src/syllable.ts +++ b/src/syllable.ts @@ -1,7 +1,7 @@ import { Cluster } from "./cluster"; import { Char } from "./char"; import { CharToNameMap, charToNameMap, NameToCharMap, nameToCharMap } from "./utils/vowelMap"; -import { vowelsCaptureGroupWithSheva } from "./utils/regularExpressions"; +import { removeTaamim } from "./utils/removeTaamim"; import { Node } from "./node"; import { Word } from "./word"; @@ -106,6 +106,10 @@ export class Syllable extends Node { return this.clusters.map((cluster) => cluster.chars).flat(); } + private isVowelKeyOfSyllableCharToNameMap(vowel: string): vowel is keyof SyllableCharToNameMap { + return vowel in sylCharToNameMap; + } + /** * Returns the vowel character of the syllable * @@ -121,8 +125,13 @@ export class Syllable extends Node { * ``` */ get vowel(): keyof SyllableCharToNameMap | null { - const match = this.text.match(vowelsCaptureGroupWithSheva); - return match ? (match[0] as keyof SyllableCharToNameMap) : match; + const nucleus = this.nucleus; + const noTaamim = removeTaamim(nucleus)[0]; + if (this.isVowelKeyOfSyllableCharToNameMap(noTaamim)) { + return noTaamim; + } + + return null; } /** From 11cfae776ecce70cc32159298d09b90d4481fa08 Mon Sep 17 00:00:00 2001 From: Charles Loder Date: Sun, 29 Oct 2023 21:35:53 -0400 Subject: [PATCH 06/10] Update tests --- test/syllable.test.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/test/syllable.test.ts b/test/syllable.test.ts index 44f9917..d415bd2 100644 --- a/test/syllable.test.ts +++ b/test/syllable.test.ts @@ -25,6 +25,7 @@ describe.each` ${"syllable with sheva"} | ${"הַֽ֭יְחָבְרְךָ"} | ${1} | ${"SHEVA"} | ${true} ${"syllable with silent sheva"} | ${"הַֽ֭יְחָבְרְךָ"} | ${2} | ${"SHEVA"} | ${false} ${"syllable with qamats"} | ${"הַֽ֭יְחָבְרְךָ"} | ${2} | ${"QAMATS"} | ${true} + ${"syllable with shureq"} | ${"תִגְּע֖וּ"} | ${2} | ${"SHUREQ"} | ${true} `("hasVowelName:", ({ description, hebrew, syllableNum, vowelName, result }) => { const heb = new Text(hebrew); const syllable = heb.syllables[syllableNum]; @@ -96,11 +97,12 @@ describe.each` }); describe.each` - description | hebrew | syllableNum | vowel | allowNoNiqqud - ${"syllable with patah"} | ${"הַֽ֭יְחָבְרְךָ"} | ${0} | ${"\u{05B7}"} | ${false} - ${"syllable with sheva"} | ${"הַֽ֭יְחָבְרְךָ"} | ${1} | ${"\u{05B0}"} | ${false} - ${"syllable with silent sheva"} | ${"הַֽ֭יְחָבְרְךָ"} | ${2} | ${"\u{05B8}"} | ${false} - ${"syllable with none"} | ${"test"} | ${0} | ${null} | ${true} + description | hebrew | syllableNum | vowel | allowNoNiqqud + ${"syllable with patah"} | ${"הַֽ֭יְחָבְרְךָ"} | ${0} | ${"\u{05B7}"} | ${false} + ${"syllable with sheva"} | ${"הַֽ֭יְחָבְרְךָ"} | ${1} | ${"\u{05B0}"} | ${false} + ${"syllable with silent sheva"} | ${"הַֽ֭יְחָבְרְךָ"} | ${2} | ${"\u{05B8}"} | ${false} + ${"syllable with none"} | ${"test"} | ${0} | ${null} | ${true} + ${"syllable with shureq"} | ${"תִגְּע֖וּ"} | ${2} | ${"\u{05D5}\u{05BC}"} | ${true} `("vowel:", ({ description, hebrew, syllableNum, vowel, allowNoNiqqud }) => { // normally don't use `allowNoNiqqud` in testing, but needed to get `null` const heb = new Text(hebrew, { allowNoNiqqud }); @@ -119,6 +121,7 @@ describe.each` ${"syllable with sheva"} | ${"הַֽ֭יְחָבְרְךָ"} | ${1} | ${"SHEVA"} | ${false} ${"syllable with silent sheva"} | ${"הַֽ֭יְחָבְרְךָ"} | ${2} | ${"QAMATS"} | ${false} ${"syllable with none"} | ${"test"} | ${0} | ${null} | ${true} + ${"syllable with shureq"} | ${"תִגְּע֖וּ"} | ${2} | ${"SHUREQ"} | ${true} `("vowelName:", ({ description, hebrew, syllableNum, vowelName, allowNoNiqqud }) => { // normally don't use `allowNoNiqqud` in testing, but needed to get `null` const heb = new Text(hebrew, { allowNoNiqqud }); From 4ba42101f44a0f39a552e31edb6f724d70337f4b Mon Sep 17 00:00:00 2001 From: Charles Loder Date: Sun, 29 Oct 2023 21:52:55 -0400 Subject: [PATCH 07/10] Implement shureq in vowel map --- src/syllable.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/syllable.ts b/src/syllable.ts index 11e6eec..84904eb 100644 --- a/src/syllable.ts +++ b/src/syllable.ts @@ -8,21 +8,28 @@ import { Word } from "./word"; interface SyllableCharToNameMap extends CharToNameMap { /* eslint-disable @typescript-eslint/naming-convention */ "\u{05B0}": "SHEVA"; // HEBREW POINT HATAF SHEVA (U+05B0) + /** + * Unlike a holam vav construction which has the holam present, the shureq has no vowel character. + */ + "\u{05D5}\u{05BC}": "SHUREQ"; // HEBREW LETTER VAV (U+05D5) + HEBREW POINT DAGESH OR MAPIQ (U+05BC) } const sylCharToNameMap: SyllableCharToNameMap = { ...charToNameMap, - "\u{05B0}": "SHEVA" + "\u{05B0}": "SHEVA", + "\u{05D5}\u{05BC}": "SHUREQ" }; interface SyllableNameToCharMap extends NameToCharMap { /* eslint-disable @typescript-eslint/naming-convention */ SHEVA: "\u{05B0}"; // HEBREW POINT HATAF SHEVA (U+05B0) + SHUREQ: "\u{05D5}\u{05BC}"; // HEBREW LETTER VAV (U+05D5) + HEBREW POINT DAGESH OR MAPIQ (U+05BC) } const sylNameToCharMap: SyllableNameToCharMap = { ...nameToCharMap, - SHEVA: "\u{05B0}" + SHEVA: "\u{05B0}", + SHUREQ: "\u{05D5}\u{05BC}" }; /** @@ -123,6 +130,10 @@ export class Syllable extends Node { * text.syllables[1].vowel; * // "\u{05B0}" * ``` + * + * @description + * This returns a single vowel character, even for most mater lectionis (e.g. a holam vav would return the holam, not the vav). + * The only exception is a shureq, which returns the vav and the dagesh because there is no vowel character for a shureq. */ get vowel(): keyof SyllableCharToNameMap | null { const nucleus = this.nucleus; @@ -146,6 +157,10 @@ export class Syllable extends Node { * // "PATAH" * text.syllables[1].vowelName; * // "SHEVA" + * + * @description + * This returns the vowel name, even for most mater lectionis (e.g. a holam vav would return the HOLAM, not the vav). + * The only exception is a shureq, which returns "SHUREQ" because there is no vowel character for a shureq. * ``` */ get vowelName(): SyllableCharToNameMap[keyof SyllableCharToNameMap] | null { @@ -173,6 +188,9 @@ export class Syllable extends Node { * text.syllables[2].hasVowelName("SHEVA"); * // false * ``` + * @description + * This returns a boolean if the vowel character is present, even for most mater lectionis (e.g. in a holam vav construction, "HOLAM" would return true) + * The only exception is a shureq, because there is no vowel character for a shureq. */ hasVowelName(name: keyof SyllableNameToCharMap): boolean { if (!sylNameToCharMap[name]) throw new Error(`${name} is not a valid value`); From fd61ad554e873ebc59ea0113aa06cfe99ef012a6 Mon Sep 17 00:00:00 2001 From: Charles Loder Date: Sun, 29 Oct 2023 21:54:22 -0400 Subject: [PATCH 08/10] Add test for vav and dagesh that's not shureq --- test/syllable.test.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/syllable.test.ts b/test/syllable.test.ts index d415bd2..58a0786 100644 --- a/test/syllable.test.ts +++ b/test/syllable.test.ts @@ -20,12 +20,13 @@ describe.each` }); describe.each` - description | hebrew | syllableNum | vowelName | result - ${"syllable with patah"} | ${"הַֽ֭יְחָבְרְךָ"} | ${0} | ${"PATAH"} | ${true} - ${"syllable with sheva"} | ${"הַֽ֭יְחָבְרְךָ"} | ${1} | ${"SHEVA"} | ${true} - ${"syllable with silent sheva"} | ${"הַֽ֭יְחָבְרְךָ"} | ${2} | ${"SHEVA"} | ${false} - ${"syllable with qamats"} | ${"הַֽ֭יְחָבְרְךָ"} | ${2} | ${"QAMATS"} | ${true} - ${"syllable with shureq"} | ${"תִגְּע֖וּ"} | ${2} | ${"SHUREQ"} | ${true} + description | hebrew | syllableNum | vowelName | result + ${"syllable with patah"} | ${"הַֽ֭יְחָבְרְךָ"} | ${0} | ${"PATAH"} | ${true} + ${"syllable with sheva"} | ${"הַֽ֭יְחָבְרְךָ"} | ${1} | ${"SHEVA"} | ${true} + ${"syllable with silent sheva"} | ${"הַֽ֭יְחָבְרְךָ"} | ${2} | ${"SHEVA"} | ${false} + ${"syllable with qamats"} | ${"הַֽ֭יְחָבְרְךָ"} | ${2} | ${"QAMATS"} | ${true} + ${"syllable with shureq"} | ${"תִגְּע֖וּ"} | ${2} | ${"SHUREQ"} | ${true} + ${"syllable with vav and dagesh (not shureq)"} | ${"הַוּֽוֹת׃"} | ${1} | ${"SHUREQ"} | ${false} `("hasVowelName:", ({ description, hebrew, syllableNum, vowelName, result }) => { const heb = new Text(hebrew); const syllable = heb.syllables[syllableNum]; From 530f1af9c8aeb8eab470ebad441b31f073bd1ced Mon Sep 17 00:00:00 2001 From: Charles Loder Date: Sun, 29 Oct 2023 21:57:41 -0400 Subject: [PATCH 09/10] Update hasVowelName for vav with dagesh --- src/syllable.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/syllable.ts b/src/syllable.ts index 84904eb..f0611e2 100644 --- a/src/syllable.ts +++ b/src/syllable.ts @@ -193,7 +193,15 @@ export class Syllable extends Node { * The only exception is a shureq, because there is no vowel character for a shureq. */ hasVowelName(name: keyof SyllableNameToCharMap): boolean { - if (!sylNameToCharMap[name]) throw new Error(`${name} is not a valid value`); + if (!sylNameToCharMap[name]) { + throw new Error(`${name} is not a valid value`); + } + + if (name === "SHUREQ") { + // if any cluster has a shureq, then that should be the defacto vowel + return this.clusters.filter((c) => c.isShureq).length ? true : false; + } + const isShevaSilent = name === "SHEVA" && this.clusters.filter((c) => c.hasVowel).length ? true : false; return !isShevaSilent && this.text.indexOf(sylNameToCharMap[name]) !== -1 ? true : false; } From 56b3846fdad8db7ff4010c0a01e4209680860069 Mon Sep 17 00:00:00 2001 From: Charles Loder Date: Sun, 29 Oct 2023 22:01:49 -0400 Subject: [PATCH 10/10] Fix ilnting issue --- src/syllable.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/syllable.ts b/src/syllable.ts index f0611e2..58646c8 100644 --- a/src/syllable.ts +++ b/src/syllable.ts @@ -188,6 +188,7 @@ export class Syllable extends Node { * text.syllables[2].hasVowelName("SHEVA"); * // false * ``` + * * @description * This returns a boolean if the vowel character is present, even for most mater lectionis (e.g. in a holam vav construction, "HOLAM" would return true) * The only exception is a shureq, because there is no vowel character for a shureq.