From 901f662663c6a453d88fe0efebafb3090abe5f62 Mon Sep 17 00:00:00 2001 From: Hugo Brosnahan <101022007+hugo-t-b@users.noreply.github.com> Date: Wed, 26 Jun 2024 15:48:13 +1000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(scriptor):=20support=20adjecti?= =?UTF-8?q?ves?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scriptor/index.ts | 21 +++- scriptor/tests/adjectives.test.ts | 72 +++++------ scriptor/utils/adjective.ts | 200 ++++++++++++++++++++++++++++++ scriptor/utils/noun.ts | 2 +- scriptor/utils/parse.ts | 12 +- 5 files changed, 267 insertions(+), 40 deletions(-) create mode 100644 scriptor/utils/adjective.ts diff --git a/scriptor/index.ts b/scriptor/index.ts index 7301eac..d8f766f 100644 --- a/scriptor/index.ts +++ b/scriptor/index.ts @@ -1,9 +1,11 @@ import { assertNever } from "assert-never"; import { parseForm, parsePrincipalParts } from "./utils/parse"; -import Verb from "./utils/verb"; + +import Adjective from "./utils/adjective"; import Noun from "./utils/noun"; +import Verb from "./utils/verb"; -const validateKey = (key: string, word: Noun | Verb): key is keyof typeof word => key in word; +const validateKey = (key: string, word: Adjective | Noun | Verb): key is keyof typeof word => key in word; export default (parts: string, ...form: string[]): string => { const principalPartsParseResult = parsePrincipalParts(parts); @@ -75,6 +77,21 @@ export default (parts: string, ...form: string[]): string => { } return nounResult; + case "adjective": + const adjective = new Adjective(principalParts); + const adjectiveKey = formParseResult.data.join("_"); + + if (!validateKey(adjectiveKey, adjective)) { + throw new Error("Unsupported or invalid form"); + } + + const adjectiveResult = adjective[adjectiveKey]; + + if (typeof adjectiveResult !== "string") { + throw new Error("Invalid form"); + } + + return adjectiveResult; default: assertNever(partOfSpeech); } diff --git a/scriptor/tests/adjectives.test.ts b/scriptor/tests/adjectives.test.ts index 516edcf..ff06c7d 100644 --- a/scriptor/tests/adjectives.test.ts +++ b/scriptor/tests/adjectives.test.ts @@ -11,7 +11,7 @@ const scriptus = "scriptus, scripta, scriptum"; describe("Masculine singular", () => { const repeated = ["masculine", "singular"]; - test.todo("Nominative", () => { + test("Nominative", () => { const form = ["nominative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aeger"); @@ -22,7 +22,7 @@ describe("Masculine singular", () => { expect(scriptor(scriptus, ...form)).toBe("scriptus"); }); - test.todo("Vocative", () => { + test("Vocative", () => { const form = ["vocative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aeger"); @@ -33,7 +33,7 @@ describe("Masculine singular", () => { expect(scriptor(scriptus, ...form)).toBe("scripte"); }); - test.todo("Accusative", () => { + test("Accusative", () => { const form = ["accusative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegrum"); @@ -44,7 +44,7 @@ describe("Masculine singular", () => { expect(scriptor(scriptus, ...form)).toBe("scriptum"); }); - test.todo("Genitive", () => { + test("Genitive", () => { const form = ["genitive", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegri"); @@ -55,7 +55,7 @@ describe("Masculine singular", () => { expect(scriptor(scriptus, ...form)).toBe("scripti"); }); - test.todo("Dative", () => { + test("Dative", () => { const form = ["dative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegro"); @@ -66,7 +66,7 @@ describe("Masculine singular", () => { expect(scriptor(scriptus, ...form)).toBe("scripto"); }); - test.todo("Ablative", () => { + test("Ablative", () => { const form = ["ablative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegro"); @@ -81,7 +81,7 @@ describe("Masculine singular", () => { describe("Feminine singular", () => { const repeated = ["feminine", "singular"]; - test.todo("Nominative", () => { + test("Nominative", () => { const form = ["nominative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegra"); @@ -92,7 +92,7 @@ describe("Feminine singular", () => { expect(scriptor(scriptus, ...form)).toBe("scripta"); }); - test.todo("Vocative", () => { + test("Vocative", () => { const form = ["vocative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegra"); @@ -103,7 +103,7 @@ describe("Feminine singular", () => { expect(scriptor(scriptus, ...form)).toBe("scripta"); }); - test.todo("Accusative", () => { + test("Accusative", () => { const form = ["accusative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegram"); @@ -114,7 +114,7 @@ describe("Feminine singular", () => { expect(scriptor(scriptus, ...form)).toBe("scriptam"); }); - test.todo("Genitive", () => { + test("Genitive", () => { const form = ["genitive", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegrae"); @@ -125,7 +125,7 @@ describe("Feminine singular", () => { expect(scriptor(scriptus, ...form)).toBe("scriptae"); }); - test.todo("Dative", () => { + test("Dative", () => { const form = ["dative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegrae"); @@ -136,7 +136,7 @@ describe("Feminine singular", () => { expect(scriptor(scriptus, ...form)).toBe("scriptae"); }); - test.todo("Ablative", () => { + test("Ablative", () => { const form = ["ablative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegra"); @@ -151,7 +151,7 @@ describe("Feminine singular", () => { describe("Neuter singular", () => { const repeated = ["neuter", "singular"]; - test.todo("Nominative", () => { + test("Nominative", () => { const form = ["nominative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegrum"); @@ -162,7 +162,7 @@ describe("Neuter singular", () => { expect(scriptor(scriptus, ...form)).toBe("scriptum"); }); - test.todo("Vocative", () => { + test("Vocative", () => { const form = ["vocative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegrum"); @@ -173,7 +173,7 @@ describe("Neuter singular", () => { expect(scriptor(scriptus, ...form)).toBe("scriptum"); }); - test.todo("Accusative", () => { + test("Accusative", () => { const form = ["accusative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegrum"); @@ -184,7 +184,7 @@ describe("Neuter singular", () => { expect(scriptor(scriptus, ...form)).toBe("scriptum"); }); - test.todo("Genitive", () => { + test("Genitive", () => { const form = ["genitive", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegri"); @@ -195,7 +195,7 @@ describe("Neuter singular", () => { expect(scriptor(scriptus, ...form)).toBe("scripti"); }); - test.todo("Dative", () => { + test("Dative", () => { const form = ["dative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegro"); @@ -206,7 +206,7 @@ describe("Neuter singular", () => { expect(scriptor(scriptus, ...form)).toBe("scripto"); }); - test.todo("Ablative", () => { + test("Ablative", () => { const form = ["ablative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegro"); @@ -221,7 +221,7 @@ describe("Neuter singular", () => { describe("Masculine plural", () => { const repeated = ["masculine", "plural"]; - test.todo("Nominative", () => { + test("Nominative", () => { const form = ["nominative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegri"); @@ -232,7 +232,7 @@ describe("Masculine plural", () => { expect(scriptor(scriptus, ...form)).toBe("scripti"); }); - test.todo("Vocative", () => { + test("Vocative", () => { const form = ["vocative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegri"); @@ -243,7 +243,7 @@ describe("Masculine plural", () => { expect(scriptor(scriptus, ...form)).toBe("scripti"); }); - test.todo("Accusative", () => { + test("Accusative", () => { const form = ["accusative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegros"); @@ -254,7 +254,7 @@ describe("Masculine plural", () => { expect(scriptor(scriptus, ...form)).toBe("scriptos"); }); - test.todo("Genitive", () => { + test("Genitive", () => { const form = ["genitive", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegrorum"); @@ -265,7 +265,7 @@ describe("Masculine plural", () => { expect(scriptor(scriptus, ...form)).toBe("scriptorum"); }); - test.todo("Dative", () => { + test("Dative", () => { const form = ["dative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegris"); @@ -276,7 +276,7 @@ describe("Masculine plural", () => { expect(scriptor(scriptus, ...form)).toBe("scriptis"); }); - test.todo("Ablative", () => { + test("Ablative", () => { const form = ["ablative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegris"); @@ -291,7 +291,7 @@ describe("Masculine plural", () => { describe("Feminine plural", () => { const repeated = ["feminine", "plural"]; - test.todo("Nominative", () => { + test("Nominative", () => { const form = ["nominative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegrae"); @@ -302,7 +302,7 @@ describe("Feminine plural", () => { expect(scriptor(scriptus, ...form)).toBe("scriptae"); }); - test.todo("Vocative", () => { + test("Vocative", () => { const form = ["vocative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegrae"); @@ -313,7 +313,7 @@ describe("Feminine plural", () => { expect(scriptor(scriptus, ...form)).toBe("scriptae"); }); - test.todo("Accusative", () => { + test("Accusative", () => { const form = ["accusative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegras"); @@ -324,7 +324,7 @@ describe("Feminine plural", () => { expect(scriptor(scriptus, ...form)).toBe("scriptas"); }); - test.todo("Genitive", () => { + test("Genitive", () => { const form = ["genitive", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegrarum"); @@ -335,7 +335,7 @@ describe("Feminine plural", () => { expect(scriptor(scriptus, ...form)).toBe("scriptarum"); }); - test.todo("Dative", () => { + test("Dative", () => { const form = ["dative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegris"); @@ -346,7 +346,7 @@ describe("Feminine plural", () => { expect(scriptor(scriptus, ...form)).toBe("scriptis"); }); - test.todo("Ablative", () => { + test("Ablative", () => { const form = ["ablative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegris"); @@ -361,7 +361,7 @@ describe("Feminine plural", () => { describe("Neuter plural", () => { const repeated = ["neuter", "plural"]; - test.todo("Nominative", () => { + test("Nominative", () => { const form = ["nominative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegra"); @@ -372,7 +372,7 @@ describe("Neuter plural", () => { expect(scriptor(scriptus, ...form)).toBe("scripta"); }); - test.todo("Vocative", () => { + test("Vocative", () => { const form = ["vocative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegra"); @@ -383,7 +383,7 @@ describe("Neuter plural", () => { expect(scriptor(scriptus, ...form)).toBe("scripta"); }); - test.todo("Accusative", () => { + test("Accusative", () => { const form = ["accusative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegra"); @@ -394,7 +394,7 @@ describe("Neuter plural", () => { expect(scriptor(scriptus, ...form)).toBe("scripta"); }); - test.todo("Genitive", () => { + test("Genitive", () => { const form = ["genitive", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegrorum"); @@ -405,7 +405,7 @@ describe("Neuter plural", () => { expect(scriptor(scriptus, ...form)).toBe("scriptorum"); }); - test.todo("Dative", () => { + test("Dative", () => { const form = ["dative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegris"); @@ -416,7 +416,7 @@ describe("Neuter plural", () => { expect(scriptor(scriptus, ...form)).toBe("scriptis"); }); - test.todo("Ablative", () => { + test("Ablative", () => { const form = ["ablative", ...repeated]; expect(scriptor(aeger, ...form)).toBe("aegris"); diff --git a/scriptor/utils/adjective.ts b/scriptor/utils/adjective.ts new file mode 100644 index 0000000..1e7054b --- /dev/null +++ b/scriptor/utils/adjective.ts @@ -0,0 +1,200 @@ +import { z } from "zod"; + +export const AdjectiveForm = z.tuple([ + z.enum([ "nominative", "vocative", "accusative", "genitive", "dative", "ablative" ]), + z.enum([ "feminine", "masculine", "neuter" ]), + z.enum([ "singular", "plural" ]) +]); + +const PatternA = z.tuple([ + z.string().regex(/(us|er)$/), + z.string().endsWith("a"), + z.string().endsWith("um") +]); + +const PatternB = z.tuple([ + z.string().endsWith("is"), + z.string().endsWith("e") +]); + +const PatternC = z.tuple([ + z.string(), + z.string().endsWith("is") +]); + +const PatternD = z.tuple([ + z.string().endsWith("er"), + z.string().endsWith("is"), + z.string().endsWith("e") +]); + +export const AdjectivePrincipalParts = z.union([ PatternA, PatternB, PatternC, PatternD ]); + +export default class Adjective { + #principalParts: z.infer; + + constructor(principalParts: z.infer) { + this.#principalParts = principalParts; + } + + get #pattern() { + if (PatternA.safeParse(this.#principalParts).success) return "A"; + if (PatternD.safeParse(this.#principalParts).success) return "D"; + if (PatternB.safeParse(this.#principalParts).success) return "B"; + else return "C"; + } + + get #stem() { + let part = this.#principalParts[1]; + + if (this.#pattern === "A") part = this.#principalParts[2]!; + if (this.#pattern === "B") part = this.#principalParts[0]; + + return part.slice(0, -2); + } + + get nominative_masculine_singular() { + return this.#principalParts[0]; + } + + get nominative_feminine_singular() { + return this.#pattern === "A" || this.#pattern === "D" ? this.#principalParts[1] : this.nominative_masculine_singular; + } + + get nominative_neuter_singular() { + if (this.#pattern === "B") return this.#principalParts[1]; + return this.#pattern === "C" ? this.nominative_masculine_singular : this.#principalParts[2]!; + } + + get nominative_masculine_plural() { + return `${this.#stem}${this.#pattern === "A" ? "i" : "es"}`; + } + + get nominative_feminine_plural() { + return this.#pattern === "A" ? `${this.#stem}ae` : this.nominative_masculine_plural; + } + + get nominative_neuter_plural() { + return `${this.#stem}${this.#pattern === "A" ? "a" : "ia"}`; + } + + get vocative_masculine_singular() { + return this.#pattern === "A" && this.nominative_masculine_singular.endsWith("us") ? `${this.#stem}e` : this.nominative_masculine_singular; + } + + get vocative_feminine_singular() { + return this.nominative_feminine_singular; + } + + get vocative_neuter_singular() { + return this.nominative_neuter_singular; + } + + get vocative_masculine_plural() { + return this.nominative_masculine_plural; + } + + get vocative_feminine_plural() { + return this.nominative_feminine_plural; + } + + get vocative_neuter_plural() { + return this.nominative_neuter_plural; + } + + get accusative_masculine_singular() { + return `${this.#stem}${this.#pattern === "A" ? "um" : "em"}`; + } + + get accusative_feminine_singular() { + return this.#pattern === "A" ? `${this.#stem}am` : this.accusative_masculine_singular; + } + + get accusative_neuter_singular() { + return this.nominative_neuter_singular; + } + + get accusative_masculine_plural() { + return `${this.#stem}${this.#pattern === "A" ? "os" : "es"}`; + } + + get accusative_feminine_plural() { + return this.#pattern === "A" ? `${this.#stem}as` : this.accusative_masculine_plural; + } + + get accusative_neuter_plural() { + return this.nominative_neuter_plural; + } + + get genitive_masculine_singular() { + return `${this.#stem}${this.#pattern === "A" ? "i" : "is"}`; + } + + get genitive_feminine_singular() { + return this.#pattern === "A" ? `${this.#stem}ae` : this.genitive_masculine_singular; + } + + get genitive_neuter_singular() { + return this.genitive_masculine_singular; + } + + get genitive_masculine_plural() { + return `${this.#stem}${this.#pattern === "A" ? "orum" : "ium"}`; + } + + get genitive_feminine_plural() { + return this.#pattern === "A" ? `${this.#stem}arum` : this.genitive_masculine_plural; + } + + get genitive_neuter_plural() { + return this.genitive_masculine_plural; + } + + get dative_masculine_singular() { + return `${this.#stem}${this.#pattern === "A" ? "o" : "i"}`; + } + + get dative_feminine_singular() { + return this.#pattern === "A" ? `${this.#stem}ae` : this.dative_masculine_singular; + } + + get dative_neuter_singular() { + return this.dative_masculine_singular; + } + + get dative_masculine_plural() { + return `${this.#stem}${this.#pattern === "A" ? "is" : "ibus"}`; + } + + get dative_feminine_plural() { + return this.dative_masculine_plural; + } + + get dative_neuter_plural() { + return this.dative_masculine_plural; + } + + get ablative_masculine_singular() { + return this.dative_masculine_singular; + } + + get ablative_feminine_singular() { + return this.#pattern === "A" ? `${this.#stem}a` : this.ablative_masculine_singular; + } + + get ablative_neuter_singular() { + return this.ablative_masculine_singular; + } + + get ablative_masculine_plural() { + return this.dative_masculine_plural; + } + + get ablative_feminine_plural() { + return this.dative_feminine_plural; + } + + get ablative_neuter_plural() { + return this.ablative_masculine_plural; + } +}; diff --git a/scriptor/utils/noun.ts b/scriptor/utils/noun.ts index e4fc194..569aa7f 100644 --- a/scriptor/utils/noun.ts +++ b/scriptor/utils/noun.ts @@ -88,4 +88,4 @@ export default class Noun { get ablative_plural() { return this.dative_plural; } -} +}; diff --git a/scriptor/utils/parse.ts b/scriptor/utils/parse.ts index 5ee0760..c70c39f 100644 --- a/scriptor/utils/parse.ts +++ b/scriptor/utils/parse.ts @@ -1,9 +1,10 @@ import assertNever from "assert-never"; +import { AdjectiveForm, AdjectivePrincipalParts } from "./adjective"; import { NounForm, NounPrincipalParts } from "./noun"; import { VerbForm, VerbPrincipalParts } from "./verb"; import { z } from "zod"; -type PrincipalPartsResult = [ "verb", z.infer ] | [ "noun", z.infer ] | null; +type PrincipalPartsResult = [ "verb", z.infer ] | [ "noun", z.infer ] | [ "adjective", z.infer ] | null; type PartOfSpeech = NonNullable[0]; export const parsePrincipalParts = (parts: string): PrincipalPartsResult => { @@ -20,6 +21,12 @@ export const parsePrincipalParts = (parts: string): PrincipalPartsResult => { return [ "noun", nounParse.data ]; } + const adjectiveParse = AdjectivePrincipalParts.safeParse(principalParts); + + if (adjectiveParse.success) { + return [ "adjective", adjectiveParse.data ]; + } + return null; }; @@ -31,6 +38,9 @@ export const parseForm = (partOfSpeech: PartOfSpeech, form: string[]) => { case "noun": const nounParseResult = NounForm.safeParse(form); return nounParseResult; + case "adjective": + const adjectiveParseResult = AdjectiveForm.safeParse(form); + return adjectiveParseResult; default: assertNever(partOfSpeech); }