From 035c0058b9afca1cd997716c76b5710b7c75f8ed Mon Sep 17 00:00:00 2001 From: Andrius Simanaitis Date: Mon, 13 Apr 2026 23:15:05 +0300 Subject: [PATCH 01/12] release version 0.0.5 --- deno.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deno.json b/deno.json index 3670529..f33a9de 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@voldemortas/flection", - "version": "0.0.4", + "version": "0.0.5", "exports": "./src/index.ts", "tasks": { "dev": "deno test --watch" From 9d3597b28296d3fe9ca60f37723a4b74a3f56fbe Mon Sep 17 00:00:00 2001 From: Andrius Simanaitis Date: Mon, 13 Apr 2026 23:16:01 +0300 Subject: [PATCH 02/12] add normaliseAccents (#3) --- deno.json | 3 ++- src/helpers/index.ts | 43 +++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 3 ++- test/helpers/index.ts | 13 +++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 src/helpers/index.ts create mode 100644 test/helpers/index.ts diff --git a/deno.json b/deno.json index f33a9de..65344b7 100644 --- a/deno.json +++ b/deno.json @@ -12,7 +12,8 @@ "~src/": "./src/", "~test/": "./test/", "~conjugators/": "./src/flectors/conjugators/", - "~decliners/": "./src/flectors/decliners/" + "~decliners/": "./src/flectors/decliners/", + "~helpers/": "./src/helpers/" }, "fmt": { "useTabs": false, diff --git a/src/helpers/index.ts b/src/helpers/index.ts new file mode 100644 index 0000000..189568e --- /dev/null +++ b/src/helpers/index.ts @@ -0,0 +1,43 @@ +/** + * changes accented letters into simple letter + diacritic + * @param text - the text full of diacritics + */ +export function normaliseAccents(text: string): string { + return text + .replaceAll(`á`, `a\u0301`) + .replaceAll(`é`, `e\u0301`) + .replaceAll(`ó`, `o\u0301`) + .replaceAll(`í`, `i\u0301`) + .replaceAll(`ú`, `u\u0301`) + .replaceAll(`ý`, `y\u0301`) + .replaceAll(`Á`, `A\u0301`) + .replaceAll(`É`, `E\u0301`) + .replaceAll(`Ó`, `O\u0301`) + .replaceAll(`Í`, `I\u0301`) + .replaceAll(`Ú`, `U\u0301`) + .replaceAll(`Ý`, `Y\u0301`) + .replaceAll(`à`, `a\u0300`) + .replaceAll(`è`, `e\u0300`) + .replaceAll(`ò`, `o\u0300`) + .replaceAll(`ì`, `i\u0300`) + .replaceAll(`ù`, `u\u0300`) + .replaceAll(`À`, `A\u0300`) + .replaceAll(`È`, `E\u0300`) + .replaceAll(`Ò`, `O\u0300`) + .replaceAll(`Ì`, `I\u0300`) + .replaceAll(`Ù`, `U\u0300`) + .replaceAll(`ã`, `a\u0303`) + .replaceAll(`ẽ`, `e\u0303`) + .replaceAll(`õ`, `o\u0303`) + .replaceAll(`ĩ`, `i\u0303`) + .replaceAll(`ũ`, `u\u0303`) + .replaceAll(`ỹ`, `y\u0303`) + .replaceAll(`Ã`, `A\u0303`) + .replaceAll(`Ẽ`, `E\u0303`) + .replaceAll(`Õ`, `O\u0303`) + .replaceAll(`Ĩ`, `I\u0303`) + .replaceAll(`Ũ`, `U\u0303`) + .replaceAll(`Ỹ`, `Y\u0303`) + .replaceAll(`ñ`, `n\u0303`) + .replaceAll(`Ñ`, `N\u0303`) +} diff --git a/src/index.ts b/src/index.ts index 83e336a..fcd38de 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import Verb from './Verb.ts' +import { normaliseAccents } from '~helpers/index.ts' import type { ConjugationType, DeclinedType, @@ -8,7 +9,7 @@ import type { PadalyvisType } from '~conjugators/PadalyvisInflector.ts' import type { PusdalyvisType } from '~conjugators/PusdalyvisDecliner.ts' import type { BudinysType } from '~conjugators/BudinysInflector.ts' -export { Verb } +export { normaliseAccents, Verb } export type { BudinysType, ConjugationType, diff --git a/test/helpers/index.ts b/test/helpers/index.ts new file mode 100644 index 0000000..60ee7ff --- /dev/null +++ b/test/helpers/index.ts @@ -0,0 +1,13 @@ +import { normaliseAccents } from '~helpers/index.ts' +import { describe, it } from '@std/testing/bdd' +import { expect } from '@std/expect' + +describe('helpers', () => { + describe('normaliseAccents', () => { + it('normalises a text full of diacritics', () => { + const textWithDiacritics = `močiùtė suválgė dìdelę mótinos kañčią` + const normalisedText = `močiùtė suválgė dìdelę mótinos kañčią` + expect(normaliseAccents(textWithDiacritics)).toStrictEqual(normalisedText) + }) + }) +}) From daf8cb78975150f1e0f249b14500ca2177cc7691 Mon Sep 17 00:00:00 2001 From: Andrius Simanaitis Date: Mon, 13 Apr 2026 23:46:00 +0300 Subject: [PATCH 03/12] fix dot diacritic on i --- src/helpers/index.ts | 1 + test/helpers/index.ts | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 189568e..36f0588 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -4,6 +4,7 @@ */ export function normaliseAccents(text: string): string { return text + .replaceAll(/(i)\u0307/gi, `$1`) .replaceAll(`á`, `a\u0301`) .replaceAll(`é`, `e\u0301`) .replaceAll(`ó`, `o\u0301`) diff --git a/test/helpers/index.ts b/test/helpers/index.ts index 60ee7ff..3b3a498 100644 --- a/test/helpers/index.ts +++ b/test/helpers/index.ts @@ -5,8 +5,9 @@ import { expect } from '@std/expect' describe('helpers', () => { describe('normaliseAccents', () => { it('normalises a text full of diacritics', () => { - const textWithDiacritics = `močiùtė suválgė dìdelę mótinos kañčią` - const normalisedText = `močiùtė suválgė dìdelę mótinos kañčią` + const textWithDiacritics = + `močiùtė suválgė dìdelę di\u0307\u0300delę mótinos kañčią` + const normalisedText = `močiùtė suválgė dìdelę dìdelę mótinos kañčią` expect(normaliseAccents(textWithDiacritics)).toStrictEqual(normalisedText) }) }) From 6fffc565be7777c3c22806c352a7460f9f4ee888 Mon Sep 17 00:00:00 2001 From: Andrius Simanaitis Date: Wed, 22 Apr 2026 17:38:20 +0300 Subject: [PATCH 04/12] fix present indicative tense prefix not getting stress --- .../PresentIndicativeConjugator.ts | 13 +++-- src/utils.ts | 37 +++++++++++++- .../PresentIndicativeConjugator.test.ts | 48 +++++++++++++++++++ test/utils.test.ts | 17 ++++++- 4 files changed, 107 insertions(+), 8 deletions(-) diff --git a/src/flectors/conjugators/PresentIndicativeConjugator.ts b/src/flectors/conjugators/PresentIndicativeConjugator.ts index 3368096..bf4a093 100644 --- a/src/flectors/conjugators/PresentIndicativeConjugator.ts +++ b/src/flectors/conjugators/PresentIndicativeConjugator.ts @@ -129,23 +129,26 @@ function isAlternatingCircumflexIWithE( const presentRoot = getPresentRoot(principalParts).root const pastRoot = getPastRoot(principalParts).root const roots = [infinitiveRoot, presentRoot, pastRoot] + const IE_LMRN_Regex = /^.*([ie][lmnr]\u0303).*$/ if (new RegExp(`[^${vowels}]ie\u0303`).test(principalParts[1])) { return false } - if (roots.filter((r) => /^.*([ie][lmnr]\u0303).*$/.test(r)).length === 3) { - const iRER = roots.map((r) => r.replace(/^.*([ie][lmnr]\u0303).*$/, '$1')) + if (roots.filter((r) => IE_LMRN_Regex.test(r)).length > 1) { + const iRER = roots.map((r) => r.replace(IE_LMRN_Regex, '$1')).filter((r) => + IE_LMRN_Regex.test(r) + ) if ( - iRER.length === 3 && !isEverythingEqual(iRER) && + iRER.length > 1 && !isEverythingEqual(iRER) && isEverythingEqual(iRER.map((r) => r.replace(/[ie]/, ''))) ) { return true } } - if (roots.filter((r) => /^.*(i\u0300|en\u0303).*$/.test(r)).length === 3) { + if (roots.filter((r) => /^.*(i\u0300|en\u0303).*$/.test(r)).length > 1) { const iEN = roots.map((r) => r.replace(/^.*(i\u0300|en\u0303).*$/, '$1')) - return iEN.length === 3 && !isEverythingEqual(iEN) + return iEN.length > 1 && !isEverythingEqual(iEN) } return false } diff --git a/src/utils.ts b/src/utils.ts index 5524e4f..53221ed 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -31,7 +31,9 @@ export function hasAnyAccent(word: string) { } export function hasAcuteAccent(word: string) { - return /\u0301/.test(word) || acuteIULMNR.test(word) + const accentedSyllable = getStressedSyllable(word) + return !!accentedSyllable && + (/\u0301/.test(accentedSyllable!.text) || acuteIULMNR.test(accentedSyllable!.text)) } export function hasCircumflexOrShortAccent(word: string) { @@ -39,7 +41,7 @@ export function hasCircumflexOrShortAccent(word: string) { } export function getUnpalatalizedRoot(root: string) { - return root.replace(/či$/, 't').replace(/dži$/, 'd') + return root.replace(/či$/, 't').replace(/dži$/, 'd').replace(/i$/, '') } export function getPalatalizedRoot(root: string) { @@ -113,6 +115,37 @@ export function getLastSyllable(text: string): string { return text.replace(SYLLABLE_REGEX, '$2') } +/** + * @description Finds the stressed syllable (both text and its position counting from behind). + * @param string - the word to look for stress + * @return returns `null` if no stress exists, otherwise returns the syllable and its position starting from behind + * @example + * ```ts + * getStressedSyllable(`dėti`) // null + * getStressedSyllable(`padėti\u0300`) // {text: `ti\u0300`, position: 1} + * ``` + */ +export function getStressedSyllable( + string: string, +): { position: number; text: string } | null { + if (!hasAnyAccent(string)) { + return null + } + let wordToUse = string + let thisSyllable = '' + let currentSyllable = 0 + do { + currentSyllable++ + thisSyllable = getLastSyllable(wordToUse) + const nextWordToUse = wordToUse.replace(SYLLABLE_REGEX, '$1') + if (nextWordToUse === wordToUse) { + throw cannotParseSyllableError + } + wordToUse = wordToUse.replace(SYLLABLE_REGEX, '$1') + } while (/[aąeęėiįyouųū]/.test(wordToUse) && !hasAnyAccent(thisSyllable)) + return { text: thisSyllable, position: currentSyllable } +} + export function putAccentOnString( string: string, syllableFromEnd: number, diff --git a/test/flectors/conjugators/PresentIndicativeConjugator.test.ts b/test/flectors/conjugators/PresentIndicativeConjugator.test.ts index df66e0e..fda8a2e 100644 --- a/test/flectors/conjugators/PresentIndicativeConjugator.test.ts +++ b/test/flectors/conjugators/PresentIndicativeConjugator.test.ts @@ -70,6 +70,16 @@ const VARGTI: PrincipalPartsType = [ `var\u0303gsta`, `var\u0303go`, ] +const VILTI: PrincipalPartsType = [ + `vi\u0300lti`, + `vi\u0300lia`, + `vy\u0301lė`, +] +const LISTI: PrincipalPartsType = [ + `lį\u0303sti`, + `len\u0303da`, + `lin\u0303do`, +] const COPULA = makeConjugatedFromArray([ [`esu\u0300`, `esi\u0300`, `yra\u0300`], [ @@ -214,6 +224,22 @@ const VARGSTA = makeConjugatedFromArray([ `var\u0303gsta`, ], ]) +const VILIA = makeConjugatedFromArray([ + [`viliu\u0300`, `vili\u0300`, `vi\u0300lia`], + [ + `vi\u0300liame vi\u0300liam`, + `vi\u0300liate vi\u0300liat`, + `vi\u0300lia`, + ], +]) +const LENDA = makeConjugatedFromArray([ + [`lendu\u0300`, `lendi\u0300`, `len\u0303da`], + [ + `len\u0303dame len\u0303dam`, + `len\u0303date len\u0303dat`, + `len\u0303da`, + ], +]) const NEG_COPULA = makeConjugatedFromArray([ [`nesu\u0300`, `nesi\u0300`, `nėra\u0300`], [ @@ -355,6 +381,22 @@ const NEG_VARGSTA = makeConjugatedFromArray([ `nevar\u0303gsta`, ], ]) +const NEG_VILIA = makeConjugatedFromArray([ + [`ne\u0300viliu`, `ne\u0300vili`, `ne\u0300vilia`], + [ + `ne\u0300viliame ne\u0300viliam`, + `ne\u0300viliate ne\u0300viliat`, + `ne\u0300vilia`, + ], +]) +const NEG_LENDA = makeConjugatedFromArray([ + [`ne\u0300lendu`, `ne\u0300lendi`, `ne\u0300lenda`], + [ + `ne\u0300lendame ne\u0300lendam`, + `ne\u0300lendate ne\u0300lendat`, + `ne\u0300lenda`, + ], +]) const DATA: [PrincipalPartsType[], ConjugationType[], ConjugationType[]] = [ [ @@ -376,6 +418,8 @@ const DATA: [PrincipalPartsType[], ConjugationType[], ConjugationType[]] = [ VILKTI, KALBETI, VARGTI, + VILTI, + LISTI, ], [ COPULA, @@ -396,6 +440,8 @@ const DATA: [PrincipalPartsType[], ConjugationType[], ConjugationType[]] = [ VELKA, KALBA, VARGSTA, + VILIA, + LENDA, ], [ NEG_COPULA, @@ -416,6 +462,8 @@ const DATA: [PrincipalPartsType[], ConjugationType[], ConjugationType[]] = [ NEG_VELKA, NEG_KALBA, NEG_VARGSTA, + NEG_VILIA, + NEG_LENDA, ], ] diff --git a/test/utils.test.ts b/test/utils.test.ts index 20fabd5..563f1e7 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -8,7 +8,7 @@ import { getNthLast, getPalatalizedRoot, getPastRoot, - getPresentRoot, + getPresentRoot, getStressedSyllable, getUnpalatalizedRoot, hasAcuteAccent, hasAnyAccent, @@ -605,4 +605,19 @@ describe('utils', () => { expect(isStemWithoutSuffix(`gaute\u0301i`)).toBeFalsy() }) }) + describe('getStressedSyllable', () => { + it('returns null when there is no stress', () => { + expect(getStressedSyllable(`dėti`)).toBeNull() + }) + it(`returns correct stressed syllable and its position`, () => { + expect(getStressedSyllable(`padė\u0301ti`)).toMatchObject({text: `dė\u0301`, position: 2}) + expect(getStressedSyllable(`padėti\u0300`)).toMatchObject({text: `ti\u0300`, position: 1}) + expect(getStressedSyllable(`pe\u0301rdėti`)).toMatchObject({text: `pe\u0301r`, position: 3}) + }) + it('throws when loop get stuck', () => { + expect(() => getStressedSyllable(`pažы\u0303ti`)).toThrow( + cannotParseSyllableError, + ) + }) + }) }) From 873276803d7264e7600927792a7ee99ad4f498fe Mon Sep 17 00:00:00 2001 From: Andrius Simanaitis Date: Thu, 23 Apr 2026 10:15:04 +0300 Subject: [PATCH 05/12] fix acute verbs retracting stress to prefix in past passive participles --- .../PassivePastParticipleDecliner.ts | 4 +- .../PassivePastParticipleDecliner.test.ts | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/flectors/conjugators/PassivePastParticipleDecliner.ts b/src/flectors/conjugators/PassivePastParticipleDecliner.ts index 21024e0..6c0dfec 100644 --- a/src/flectors/conjugators/PassivePastParticipleDecliner.ts +++ b/src/flectors/conjugators/PassivePastParticipleDecliner.ts @@ -2,6 +2,7 @@ import type { DeclinedType, PrincipalPartsType } from '~src/types.ts' import { countAccentedSyllable, getInfinitiveRoot, + hasAcuteAccent, hasAnyAccent, hasCircumflexOrShortAccent, isRootMonosyllabic, @@ -33,7 +34,8 @@ export default class PassivePastParticipleDecliner extends ParticipleDecliner { getBasicInflected: (principalParts: PrincipalPartsType) => ParticipleType, ): ParticipleType { const { root } = getInfinitiveRoot(principalParts) - const isStemImmobile = !hasAnyAccent(root) || !isRootMonosyllabic(root) + const isStemImmobile = !hasAnyAccent(root) || !isRootMonosyllabic(root) || + hasAcuteAccent(root) if (isStemImmobile) { return this.getBasicImmobilePrefixed( prefix, diff --git a/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts b/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts index 3e6be45..641c5e8 100644 --- a/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts +++ b/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts @@ -26,6 +26,11 @@ const DAINUOTI: PrincipalPartsType = [ `dainu\u0301oja`, `daina\u0303vo`, ] +const KURTI: PrincipalPartsType = [ + `ku\u0300rti`, + `ku\u0300ria`, + `kū\u0303vo`, +] const RINKTAS = makeDeclinedFromArray( `riñktas riñkto rinktám riñktą rinktù rinktamè rinktam\u0303 riñktas @@ -132,6 +137,27 @@ nedainúotosios nedainúotųjų nedainúotosioms nedainúotosiom nedainu .split(/\s\s/), ) +const NEKURTAS = makeDeclinedFromArray( + `nekùrtas nekùrto nekurtám nekùrtą nekùrtu nekurtamè nekurtam̃ nekùrtas +nekurtì nekurtų̃ nekurtíems nekurtíem nekùrtus nekurtaĩs nekurtuosè nekurtuõs nekurtì` + .split(/\s\s/), +) +const NEKURTASIS = makeDeclinedFromArray( + `nekurtàsis nekùrtojo nekurta\u0301jam nekùrtąjį nekurtúoju nekurtãjame nekurta\u0303jam nekurtàsis +nekurtíeji nekurtų̃jų nekurtíesiems nekurtíesiem nekurtúosius nekurtaĩsiais nekurtuõsiuose nekurtuõsiuos nekurtíeji` + .split(/\s\s/), +) +const NEKURTA = makeDeclinedFromArray( + `nekurtà nekurtõs nekùrtai nekùrtą nekùrta nekurtojè nekurtoj\u0303 nekurtà +nekùrtos nekurtų̃ nekurtóms nekurtóm nekùrtas nekurtomìs nekurtõm nekurtosè nekùrtos` + .split(/\s\s/), +) +const NEKURTOJI = makeDeclinedFromArray( + `nekurtóji nekurtõsios nekùrtajai nekùrtąją nekurtą́ja nekurtõjoje nekurtõjoj nekurtóji +nekùrtosios nekurtų̃jų nekurtósioms nekurtósiom nekurtą́sias nekurtõsiomis nekurtõsiom nekurtõsiose nekùrtosios` + .split(/\s\s/), +) + describe('PassivePastParticipleDecliner', () => { const decliner = new PassivePastParticipleDecliner() describe('rinkti', () => { @@ -266,6 +292,22 @@ describe('PassivePastParticipleDecliner', () => { }) }) }) + describe('nekurti', () => { + it('conjugates default', () => { + expect(decliner.getPrefixed(KURTI, 'ne')).toMatchObject({ + masculine: NEKURTAS, + feminine: NEKURTA, + neuter: `neku\u0300rta`, + }) + }) + it('conjugates pronominal', () => { + expect(decliner.getPrefixedPronominal(KURTI, 'ne')) + .toMatchObject({ + masculine: NEKURTASIS, + feminine: NEKURTOJI, + }) + }) + }) describe('neiti', () => { it('conjugates default', () => { expect(decliner.getPrefixed(EITI, 'ne').masculine.sgNom).toStrictEqual( From 778ea039ae200d8878a86fd7e57449e93a565737 Mon Sep 17 00:00:00 2001 From: Andrius Simanaitis Date: Thu, 23 Apr 2026 10:39:01 +0300 Subject: [PATCH 06/12] fix circumflex prefix in past passive feminine participles --- .../PassivePastParticipleDecliner.ts | 2 + src/flectors/decliners/AccentedInflector.ts | 17 ++++++-- src/flectors/decliners/utils.ts | 4 +- .../PassivePastParticipleDecliner.test.ts | 42 +++++++++++++++++++ 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/flectors/conjugators/PassivePastParticipleDecliner.ts b/src/flectors/conjugators/PassivePastParticipleDecliner.ts index 6c0dfec..0bc79b0 100644 --- a/src/flectors/conjugators/PassivePastParticipleDecliner.ts +++ b/src/flectors/conjugators/PassivePastParticipleDecliner.ts @@ -80,6 +80,7 @@ export default class PassivePastParticipleDecliner extends ParticipleDecliner { syllable: syllable!, isAcute, }, + hasCircumflexOrShortAccent(prefixedRoot), ) } } @@ -111,6 +112,7 @@ export default class PassivePastParticipleDecliner extends ParticipleDecliner { syllable: syllable!, isAcute, }, + hasCircumflexOrShortAccent(prefixedRoot), ) } return { diff --git a/src/flectors/decliners/AccentedInflector.ts b/src/flectors/decliners/AccentedInflector.ts index 4d66465..8eddfca 100644 --- a/src/flectors/decliners/AccentedInflector.ts +++ b/src/flectors/decliners/AccentedInflector.ts @@ -37,6 +37,7 @@ export default class AccentedInflector { * declines the word for 1st and 2nd accentuation based on the stress found on the unpalatalisedRoot or the given type * @param {string} unpalatalisedRoot - unpalatalisedRoot without ending; can carry stress * @param {AccentuationType=undefined} type - **optional** accentuation type if the unpalatalisedRoot lacks stress; if the unpalatalisedRoot is stressed this part is left ignored + * @param {boolean=false} mandatoryShort - **optional** whether the non-acute stressed syllable should be short (used for prefixes) * @example * ```ts * //let's assume that the static inflector is the nominal -as inflector @@ -55,6 +56,7 @@ export default class AccentedInflector { public inflectStatic( unpalatalisedRoot: string, type?: AccentuationType, + mandatoryShort = false, ): Record { if ( this.#staticPattern === undefined && @@ -67,12 +69,14 @@ export default class AccentedInflector { unpalatalisedRoot, type, stripAllAccentsFromParadigm(this.#dynamicPattern!), + mandatoryShort, ) } return inflect( unpalatalisedRoot, type, this.#staticPattern, + mandatoryShort, ) } @@ -80,6 +84,7 @@ export default class AccentedInflector { * declines the word for 3rd and 4th accentuation based on the stress found on the root or the given type * @param {string} unpalatalisedRoot - root without ending; can carry stress * @param {AccentuationType=undefined} type - **mandatory** accentuation type if the root lacks stress; if the root is stressed this part is left ignored + * @param {boolean=false} mandatoryShort - **optional** whether the non-acute stressed syllable should be short (used for prefixes) * @example * ```ts * //let's assume that the static inflector is the nominal -as inflector @@ -98,6 +103,7 @@ export default class AccentedInflector { public inflectDynamic( unpalatalisedRoot: string, type?: AccentuationType, + mandatoryShort = false, ): Record { if (this.#dynamicPattern === undefined) { throw notAttestedInLanguageError @@ -109,6 +115,7 @@ export default class AccentedInflector { unpalatalisedRoot, type, this.#dynamicPattern, + mandatoryShort, ) } } @@ -117,13 +124,15 @@ function inflect( root: string, type: AccentuationType | undefined, paradigm: Record, + mandatoryShort = false, ): Record { const accentedRoot = hasAnyAccent(root) || type === undefined ? root - : moveThirdAccentuation(root + TEMPORARY_VOWEL, type).replace( - /a\u0303?$/, - '', - ) + : moveThirdAccentuation(root + TEMPORARY_VOWEL, type, mandatoryShort) + .replace( + /a\u0303?$/, + '', + ) return Object.fromEntries( Object.entries(paradigm).map( ([key, value]) => [ diff --git a/src/flectors/decliners/utils.ts b/src/flectors/decliners/utils.ts index 869d126..9996bd4 100644 --- a/src/flectors/decliners/utils.ts +++ b/src/flectors/decliners/utils.ts @@ -21,6 +21,7 @@ export const SECOND_LAST_ACUTE = '2a' * moves accent to stem * @param {string} word - full word with a flectional ending * @param {AccentuationType} type='2a' + * @param {boolean=false} mandatoryShort - **optional** whether the non-acute stressed syllable should be short (used for prefixes) * * `'2'` - 2nd accentuation - 1st last is circumflex * * `'3'` - 3rd accentuation - 2nd last is acute * * `'4'` - 4th accentuation - 2nd last is circumflex/short @@ -34,11 +35,12 @@ export const SECOND_LAST_ACUTE = '2a' export function moveThirdAccentuation( word: string, type: AccentuationType, + mandatoryShort = false, ): string { const { isAcute, syllable } = (typeof type === 'string') ? getThirdAccentuationType(type) : type - return putAccentOnString(word, syllable, isAcute) + return putAccentOnString(word, syllable, isAcute, mandatoryShort) } /** diff --git a/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts b/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts index 641c5e8..ebc94b3 100644 --- a/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts +++ b/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts @@ -31,6 +31,11 @@ const KURTI: PrincipalPartsType = [ `ku\u0300ria`, `kū\u0303vo`, ] +const BRISTI: PrincipalPartsType = [ + `bri\u0300sti`, + `bren\u0303da`, + `bri\u0300do`, +] const RINKTAS = makeDeclinedFromArray( `riñktas riñkto rinktám riñktą rinktù rinktamè rinktam\u0303 riñktas @@ -158,6 +163,27 @@ nekùrtosios nekurtų̃jų nekurtósioms nekurtósiom nekurtą́sias neku .split(/\s\s/), ) +const NEBRISTAS = makeDeclinedFromArray( + `nèbristas nèbristo nebristám nèbristą nèbristu nebristamè nebristam̃ nèbristas +nebristì nebristų̃ nebristíems nebristíem nèbristus nebristaĩs nebristuosè nebristuõs nebristì` + .split(/\s\s/), +) +const NEBRISTASIS = makeDeclinedFromArray( + `nebristàsis nèbristojo nebristájam nèbristąjį nebristúoju nebristãjame nebristãjam nebristàsis +nebristíeji nebristų̃jų nebristíesiems nebristíesiem nebristúosius nebristaĩsiais nebristuõsiuose nebristuõsiuos nebristíeji` + .split(/\s\s/), +) +const NEBRISTA = makeDeclinedFromArray( + `nebristà nebristõs nèbristai nèbristą nèbrista nebristojè nebristoj̃ nebristà +nèbristos nebristų̃ nebristóms nebristóm nèbristas nebristomìs nebristõm nebristosè nèbristos` + .split(/\s\s/), +) +const NEBRISTOJI = makeDeclinedFromArray( + `nebristóji nebristõsios nèbristajai nèbristąją nebristą́ja nebristõjoje nebristõjoj nebristóji +nèbristosios nebristų̃jų nebristósioms nebristósiom nebristą́sias nebristõsiomis nebristõsiom nebristõsiose nèbristosios` + .split(/\s\s/), +) + describe('PassivePastParticipleDecliner', () => { const decliner = new PassivePastParticipleDecliner() describe('rinkti', () => { @@ -308,6 +334,22 @@ describe('PassivePastParticipleDecliner', () => { }) }) }) + describe('bristi', () => { + it('conjugates default', () => { + expect(decliner.getPrefixed(BRISTI, 'ne')).toMatchObject({ + masculine: NEBRISTAS, + feminine: NEBRISTA, + neuter: `ne\u0300brista`, + }) + }) + it('conjugates pronominal', () => { + expect(decliner.getPrefixedPronominal(BRISTI, 'ne')) + .toMatchObject({ + masculine: NEBRISTASIS, + feminine: NEBRISTOJI, + }) + }) + }) describe('neiti', () => { it('conjugates default', () => { expect(decliner.getPrefixed(EITI, 'ne').masculine.sgNom).toStrictEqual( From f07e7ffc6ccfea1f6289c1dc6bda76932e9bc2aa Mon Sep 17 00:00:00 2001 From: Andrius Simanaitis Date: Thu, 23 Apr 2026 11:34:01 +0300 Subject: [PATCH 07/12] fix locative circumflex being `oj\u0303` instead of `o\u0303j` --- src/flectors/decliners/commons.ts | 2 +- .../PassiveFutureParticipleDecliner.test.ts | 2 +- .../PassivePastParticipleDecliner.test.ts | 10 +++++----- .../PassivePresentParticipleDecliner.test.ts | 4 ++-- test/flectors/decliners/commons.test.ts | 14 +++++++------- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/flectors/decliners/commons.ts b/src/flectors/decliners/commons.ts index df22ac5..56e51a8 100644 --- a/src/flectors/decliners/commons.ts +++ b/src/flectors/decliners/commons.ts @@ -170,7 +170,7 @@ const aNounDynamic: AccentuatedDeclinedType = { ...aNounStatic, sgNom: [`a\u0300`], sgGen: [`o\u0303s`], - sgLoc: [[`oje\u0300`], [`oj\u0303`]], + sgLoc: [[`oje\u0300`], [`o\u0303j`]], plGen: [`ų\u0303`], plDat: [[`o\u0301ms`], [`o\u0301m`]], plInst: [[`omi\u0300s`], [`o\u0303m`]], diff --git a/test/flectors/conjugators/PassiveFutureParticipleDecliner.test.ts b/test/flectors/conjugators/PassiveFutureParticipleDecliner.test.ts index 269dfe9..6c3e401 100644 --- a/test/flectors/conjugators/PassiveFutureParticipleDecliner.test.ts +++ b/test/flectors/conjugators/PassiveFutureParticipleDecliner.test.ts @@ -52,7 +52,7 @@ const BEGSIMA = { sgDat: 'bė\u0301gsimai', sgAcc: 'bė\u0301gsimą', sgInst: 'bė\u0301gsima', - sgLoc: 'bėgsimoje\u0300 bėgsimoj\u0303', + sgLoc: 'bėgsimoje\u0300 bėgsimo\u0303j', sgVoc: 'bėgsima\u0300', plNom: 'bė\u0301gsimos', plGen: 'bėgsimų\u0303', diff --git a/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts b/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts index ebc94b3..322b80f 100644 --- a/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts +++ b/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts @@ -48,7 +48,7 @@ rinktíeji rinktų̃jų rinktíesiems rinktíesiem rinktúosius rinktai .split(/\s\s/), ) const RINKTA = makeDeclinedFromArray( - `rinktà rinktõs riñktai riñktą rinktà rinktojè rinktoj\u0303 rinktà + `rinktà rinktõs riñktai riñktą rinktà rinktojè rinkto\u0303j rinktà riñktos rinktų̃ rinktóms rinktóm rinktàs rinktomìs rinkto\u0303m rinktosè riñktos` .split(/\s\s/), ) @@ -69,7 +69,7 @@ nesirinktíeji nesirinktų̃jų nesirinktíesiems nesirinktíesiem nesirin .split(/\s\s/), ) const NESIRINKTA = makeDeclinedFromArray( - `nesirinktà nesirinktõs nesìrinktai nesìrinktą nesìrinkta nesirinktojè nesirinktoj\u0303 nesirinktà + `nesirinktà nesirinktõs nesìrinktai nesìrinktą nesìrinkta nesirinktojè nesirinkto\u0303j nesirinktà nesìrinktos nesirinktų̃ nesirinktóms nesirinktóm nesìrinktas nesirinktomìs nesirinkto\u0303m nesirinktosè nesìrinktos` .split(/\s\s/), ) @@ -111,7 +111,7 @@ bėgtíeji bėgtų̃jų bėgtíesiems bėgtíesiem bėgtúosius bėgtai .split(/\s\s/), ) const BEGTA = makeDeclinedFromArray( - `bėgtà bėgtõs bė́gtai bė́gtą bė́gta bėgtojè bėgtoj\u0303 bėgtà + `bėgtà bėgtõs bė́gtai bė́gtą bė́gta bėgtojè bėgto\u0303j bėgtà bė́gtos bėgtų̃ bėgtóms bėgtóm bė́gtas bėgtomìs bėgto\u0303m bėgtosè bė́gtos` .split(/\s\s/), ) @@ -153,7 +153,7 @@ nekurtíeji nekurtų̃jų nekurtíesiems nekurtíesiem nekurtúosius nek .split(/\s\s/), ) const NEKURTA = makeDeclinedFromArray( - `nekurtà nekurtõs nekùrtai nekùrtą nekùrta nekurtojè nekurtoj\u0303 nekurtà + `nekurtà nekurtõs nekùrtai nekùrtą nekùrta nekurtojè nekurto\u0303j nekurtà nekùrtos nekurtų̃ nekurtóms nekurtóm nekùrtas nekurtomìs nekurtõm nekurtosè nekùrtos` .split(/\s\s/), ) @@ -174,7 +174,7 @@ nebristíeji nebristų̃jų nebristíesiems nebristíesiem nebristúosius .split(/\s\s/), ) const NEBRISTA = makeDeclinedFromArray( - `nebristà nebristõs nèbristai nèbristą nèbrista nebristojè nebristoj̃ nebristà + `nebristà nebristõs nèbristai nèbristą nèbrista nebristojè nebristõj nebristà nèbristos nebristų̃ nebristóms nebristóm nèbristas nebristomìs nebristõm nebristosè nèbristos` .split(/\s\s/), ) diff --git a/test/flectors/conjugators/PassivePresentParticipleDecliner.test.ts b/test/flectors/conjugators/PassivePresentParticipleDecliner.test.ts index 530e048..e77431b 100644 --- a/test/flectors/conjugators/PassivePresentParticipleDecliner.test.ts +++ b/test/flectors/conjugators/PassivePresentParticipleDecliner.test.ts @@ -54,7 +54,7 @@ const RENKAMA: DeclinedType = { sgDat: `ren\u0303kamai`, sgAcc: `ren\u0303kamą`, sgInst: `ren\u0303kama`, - sgLoc: `renkamoje\u0300 renkamoj\u0303`, + sgLoc: `renkamoje\u0300 renkamo\u0303j`, sgVoc: `renkama\u0300`, plNom: `ren\u0303kamos`, plGen: `renkamų\u0303`, @@ -118,7 +118,7 @@ const NERENKAMA: DeclinedType = { sgDat: `ne\u0300renkamai`, sgAcc: `ne\u0300renkamą`, sgInst: `ne\u0300renkama`, - sgLoc: `nerenkamoje\u0300 nerenkamoj\u0303`, + sgLoc: `nerenkamoje\u0300 nerenkamo\u0303j`, sgVoc: `nerenkama\u0300`, plNom: `ne\u0300renkamos`, plGen: `nerenkamų\u0303`, diff --git a/test/flectors/decliners/commons.test.ts b/test/flectors/decliners/commons.test.ts index 033e31b..00e1575 100644 --- a/test/flectors/decliners/commons.test.ts +++ b/test/flectors/decliners/commons.test.ts @@ -163,7 +163,7 @@ const KLAIDA = makeDeclinedFromArray([ [`klai\u0303dai`, `klaido\u0301ms klaido\u0301m`], [`klai\u0303dą`, `klaida\u0300s`], [`klaida\u0300`, `klaidomi\u0300s klaido\u0303m`], - [`klaidoje\u0300 klaidoj\u0303`, `klaidose\u0300`], + [`klaidoje\u0300 klaido\u0303j`, `klaidose\u0300`], [`klai\u0303da`, `klai\u0303dos`], ]) const ATRAMA = makeDeclinedFromArray([ @@ -172,7 +172,7 @@ const ATRAMA = makeDeclinedFromArray([ [`a\u0303tramai`, `atramo\u0301ms atramo\u0301m`], [`a\u0303tramą`, `a\u0303tramas`], [`a\u0303trama`, `atramomi\u0300s atramo\u0303m`], - [`atramoje\u0300 atramoj\u0303`, `atramose\u0300`], + [`atramoje\u0300 atramo\u0303j`, `atramose\u0300`], [`a\u0303trama`, `a\u0303tramos`], ]) const VALIA = makeDeclinedFromArray([ @@ -190,7 +190,7 @@ const VALDZIA = makeDeclinedFromArray([ [`val\u0303džiai`, `valdžio\u0301ms valdžio\u0301m`], [`val\u0303džią`, `valdžia\u0300s`], [`valdžia\u0300`, `valdžiomi\u0300s valdžio\u0303m`], - [`valdžioje\u0300 valdžioj\u0303`, `valdžiose\u0300`], + [`valdžioje\u0300 valdžio\u0303j`, `valdžiose\u0300`], [`val\u0303džia`, `val\u0303džios`], ]) const JUODA = makeDeclinedFromArray([ @@ -199,7 +199,7 @@ const JUODA = makeDeclinedFromArray([ [`ju\u0301odai`, `juodo\u0301ms juodo\u0301m`], [`ju\u0301odą`, `ju\u0301odas`], [`ju\u0301oda`, `juodomi\u0300s juodo\u0303m`], - [`juodoje\u0300 juodoj\u0303`, `juodose\u0300`], + [`juodoje\u0300 juodo\u0303j`, `juodose\u0300`], [`juoda\u0300`, `ju\u0301odos`], ]) const TIKRA = makeDeclinedFromArray([ @@ -208,7 +208,7 @@ const TIKRA = makeDeclinedFromArray([ [`ti\u0300krai`, `tikro\u0301ms tikro\u0301m`], [`ti\u0300krą`, `tikra\u0300s`], [`tikra\u0300`, `tikromi\u0300s tikro\u0303m`], - [`tikroje\u0300 tikroj\u0303`, `tikrose\u0300`], + [`tikroje\u0300 tikro\u0303j`, `tikrose\u0300`], [`tikra\u0300`, `ti\u0300kros`], ]) const TIKROJI = makeDeclinedFromArray([ @@ -649,7 +649,7 @@ const AISKI = makeDeclinedFromArray([ [`a\u0301iškiai`, `aiškio\u0301ms aiškio\u0301m`], [`a\u0301iškią`, `a\u0301iškias`], [`a\u0301iškia`, `aiškiomi\u0300s aiškio\u0303m`], - [`aiškioje\u0300 aiškioj\u0303`, `aiškiose\u0300`], + [`aiškioje\u0300 aiškio\u0303j`, `aiškiose\u0300`], [`a\u0301iški`, `a\u0301iškios`], ]) const PRAVARTI = makeDeclinedFromArray([ @@ -658,7 +658,7 @@ const PRAVARTI = makeDeclinedFromArray([ [`pravar\u0303čiai`, `pravarčio\u0301ms pravarčio\u0301m`], [`pravar\u0303čią`, `pravarčia\u0300s`], [`pravarčia\u0300`, `pravarčiomi\u0300s pravarčio\u0303m`], - [`pravarčioje\u0300 pravarčioj\u0303`, `pravarčiose\u0300`], + [`pravarčioje\u0300 pravarčio\u0303j`, `pravarčiose\u0300`], [`pravarti\u0300`, `pravar\u0303čios`], ]) const SOTI = makeDeclinedFromArray([ From c0f184314ab139e72119dafdf1a78c4dec539a76 Mon Sep 17 00:00:00 2001 From: Andrius Simanaitis Date: Thu, 23 Apr 2026 22:49:42 +0300 Subject: [PATCH 08/12] fix PassiveFutureParticipleDecliner --- .../PassiveFutureParticipleDecliner.ts | 101 ++++--- src/utils.ts | 9 +- .../PassiveFutureParticipleDecliner.test.ts | 269 +++++++++++++++--- test/utils.test.ts | 23 +- 4 files changed, 326 insertions(+), 76 deletions(-) diff --git a/src/flectors/conjugators/PassiveFutureParticipleDecliner.ts b/src/flectors/conjugators/PassiveFutureParticipleDecliner.ts index 3b19d30..598cc43 100644 --- a/src/flectors/conjugators/PassiveFutureParticipleDecliner.ts +++ b/src/flectors/conjugators/PassiveFutureParticipleDecliner.ts @@ -1,9 +1,11 @@ import type { DeclinedType, PrincipalPartsType } from '~src/types.ts' import { appendSuffixWithAssimilation, + countAccentedSyllable, getInfinitiveRoot, hasAcuteAccent, hasAnyAccent, + hasCircumflexOrShortAccent, isRootMonosyllabic, stripAllAccents, } from '~src/utils.ts' @@ -17,10 +19,18 @@ import { AsAdjectiveDecliner, AsPronominalDecliner, } from '~decliners/commons.ts' -import { NOMINAL_EMPTY } from '~src/commons.ts' +import { NOMINAL_EMPTY, PREFIX_SEPARATOR } from '~src/commons.ts' const FUTURE_SUFFIX = 's' const PASSIVE_FUTURE_SUFFIX = 'im' +const ASSIMILATION_MAP: [RegExp, string][] = [ + [/[sz]s$/, 's'], + [/[šž]s$/, 'š'], +] +const PREFIX_REGEX = new RegExp( + `^(([^${PREFIX_SEPARATOR}]+)${PREFIX_SEPARATOR}.+|.+)$`, +) +const ROOT_REGEX = new RegExp(`^(.+${PREFIX_SEPARATOR})`) export default class PassiveFutureParticipleDecliner extends ParticipleDecliner { @@ -30,7 +40,8 @@ export default class PassiveFutureParticipleDecliner getBasicInflected: (principalParts: PrincipalPartsType) => ParticipleType, ): ParticipleType { const { root } = getInfinitiveRoot(principalParts) - const isStemImmobile = !hasAnyAccent(root) || !isRootMonosyllabic(root) + const isStemImmobile = !hasAnyAccent(root) || !isRootMonosyllabic(root) || + hasAcuteAccent(root) if (isStemImmobile) { return this.getBasicImmobilePrefixed( prefix, @@ -40,8 +51,8 @@ export default class PassiveFutureParticipleDecliner } return getBasicInflected( - principalParts.map((principalPart) => - `${prefix}${principalPart}` + principalParts.map((part) => + `${prefix}${PREFIX_SEPARATOR}${part}` ) as PrincipalPartsType, ) } @@ -49,20 +60,24 @@ export default class PassiveFutureParticipleDecliner getDefault(principalParts: PrincipalPartsType): ParticipleType { let masculine: DeclinedType let feminine: DeclinedType - const { root } = getInfinitiveRoot(principalParts) - const stem = appendSuffixWithAssimilation(root, FUTURE_SUFFIX, [ - [/[sz]s$/, 's'], - [/[šž]s$/, 'š'], - ]) + PASSIVE_FUTURE_SUFFIX - if (isRootStatic(root)) { - masculine = AsAdjectiveDecliner.inflectStatic(stem) - feminine = AAdjectiveDecliner.inflectStatic(stem) + const { isStemImmobile, prefixedRoot, isAcute, syllable } = + getRootAndPrefix(principalParts) + + if (isStemImmobile) { + masculine = AsAdjectiveDecliner.inflectStatic( + prefixedRoot, + ) + feminine = AAdjectiveDecliner.inflectStatic(prefixedRoot) } else { - masculine = AsAdjectiveDecliner.inflectDynamic(stem) - feminine = AAdjectiveDecliner.inflectDynamic(stem, { - syllable: 3, - isAcute: hasAcuteAccent(root), - }) + masculine = AsAdjectiveDecliner.inflectDynamic(prefixedRoot) + feminine = AAdjectiveDecliner.inflectDynamic( + stripAllAccents(prefixedRoot), + { + syllable: syllable!, + isAcute, + }, + hasCircumflexOrShortAccent(prefixedRoot), + ) } return { masculine, @@ -76,20 +91,24 @@ export default class PassiveFutureParticipleDecliner ): ComplementingParticipleType { let masculine: DeclinedType let feminine: DeclinedType - const { root } = getInfinitiveRoot(principalParts) - const stem = appendSuffixWithAssimilation(root, FUTURE_SUFFIX, [ - [/[sz]s$/, 's'], - [/[šž]s$/, 'š'], - ]) + PASSIVE_FUTURE_SUFFIX - if (isRootStatic(root)) { - masculine = AsPronominalDecliner.inflectStatic(stem) - feminine = APronominalDecliner.inflectStatic(stem) + const { isStemImmobile, prefixedRoot, isAcute, syllable } = + getRootAndPrefix(principalParts) + + if (isStemImmobile) { + masculine = AsPronominalDecliner.inflectStatic( + prefixedRoot, + ) + feminine = APronominalDecliner.inflectStatic(prefixedRoot) } else { - masculine = AsPronominalDecliner.inflectDynamic(stem) - feminine = APronominalDecliner.inflectDynamic(stripAllAccents(stem), { - syllable: 3, - isAcute: hasAcuteAccent(stem), - }) + masculine = AsPronominalDecliner.inflectDynamic(prefixedRoot) + feminine = APronominalDecliner.inflectDynamic( + stripAllAccents(prefixedRoot), + { + syllable: syllable!, + isAcute, + }, + hasCircumflexOrShortAccent(prefixedRoot), + ) } return { masculine, @@ -115,6 +134,24 @@ export default class PassiveFutureParticipleDecliner } } -function isRootStatic(root: string) { - return !isRootMonosyllabic(root) || !hasAnyAccent(root) +function getRootAndPrefix(principalParts: PrincipalPartsType) { + const parsedRoot = getInfinitiveRoot(principalParts).root + const prefix = parsedRoot.replace(PREFIX_REGEX, '$2') + const root = parsedRoot.replace(ROOT_REGEX, '') + const prefixedRoot = prefix + appendSuffixWithAssimilation( + root, + FUTURE_SUFFIX, + ASSIMILATION_MAP, + ) + PASSIVE_FUTURE_SUFFIX + const { hasAccentedSyllable, syllable, type } = countAccentedSyllable( + prefixedRoot + 'as', + ) + const isStemImmobile = !isRootMonosyllabic(root) || !hasAccentedSyllable + return { + isStemImmobile, + prefixedRoot, + type, + isAcute: type === 'acute', + syllable, + } } diff --git a/src/utils.ts b/src/utils.ts index 53221ed..692eb64 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -33,7 +33,8 @@ export function hasAnyAccent(word: string) { export function hasAcuteAccent(word: string) { const accentedSyllable = getStressedSyllable(word) return !!accentedSyllable && - (/\u0301/.test(accentedSyllable!.text) || acuteIULMNR.test(accentedSyllable!.text)) + (/\u0301/.test(accentedSyllable!.text) || + acuteIULMNR.test(accentedSyllable!.text)) } export function hasCircumflexOrShortAccent(word: string) { @@ -227,7 +228,11 @@ export function countAccentedSyllable( do { currentSyllable++ const thisSyllable = wordToUse.replace(SYLLABLE_REGEX, '$2') - wordToUse = wordToUse.replace(SYLLABLE_REGEX, '$1') + const nextWord = wordToUse.replace(SYLLABLE_REGEX, '$1') + if (nextWord === wordToUse) { + throw cannotParseSyllableError + } + wordToUse = nextWord if (thisSyllable.includes(`\u0303`)) { return { hasAccentedSyllable: true, diff --git a/test/flectors/conjugators/PassiveFutureParticipleDecliner.test.ts b/test/flectors/conjugators/PassiveFutureParticipleDecliner.test.ts index 6c3e401..c9b367d 100644 --- a/test/flectors/conjugators/PassiveFutureParticipleDecliner.test.ts +++ b/test/flectors/conjugators/PassiveFutureParticipleDecliner.test.ts @@ -9,6 +9,11 @@ const BEGTI: PrincipalPartsType = [ `bė\u0303ga`, `bė\u0303go`, ] +const BAUSTI: PrincipalPartsType = [ + `bau\u0303sti`, + `bau\u0303džia`, + `bau\u0303dė`, +] const DAINUOTI: PrincipalPartsType = [ `dainu\u0301oti`, `dainu\u0301oja`, @@ -81,68 +86,197 @@ const BEGSIMOJI = { const NESIBEGSIMAS = { sgNom: 'nesibė\u0301gsimas', sgGen: 'nesibė\u0301gsimo', - sgDat: 'nesibė\u0301gsimam', + sgDat: 'nesibėgsima\u0301m', sgAcc: 'nesibė\u0301gsimą', sgInst: 'nesibė\u0301gsimu', - sgLoc: 'nesibė\u0301gsimame nesibė\u0301gsimam', + sgLoc: 'nesibėgsimame\u0300 nesibėgsimam\u0303', sgVoc: 'nesibė\u0301gsimas', - plNom: 'nesibė\u0301gsimi', - plGen: 'nesibė\u0301gsimų', - plDat: 'nesibė\u0301gsimiems nesibė\u0301gsimiem', + plNom: 'nesibėgsimi\u0300', + plGen: 'nesibėgsimų\u0303', + plDat: 'nesibėgsimi\u0301ems nesibėgsimi\u0301em', plAcc: 'nesibė\u0301gsimus', - plInst: 'nesibė\u0301gsimais', - plLoc: 'nesibė\u0301gsimuose nesibė\u0301gsimuos', - plVoc: 'nesibė\u0301gsimi', + plInst: 'nesibėgsimai\u0303s', + plLoc: 'nesibėgsimuose\u0300 nesibėgsimuo\u0303s', + plVoc: 'nesibėgsimi\u0300', } const NESIBEGSIMASIS = { - sgNom: 'nesibė\u0301gsimasis', + sgNom: 'nesibėgsima\u0300sis', sgGen: 'nesibė\u0301gsimojo', - sgDat: 'nesibė\u0301gsimajam', + sgDat: 'nesibėgsima\u0301jam', sgAcc: 'nesibė\u0301gsimąjį', - sgInst: 'nesibė\u0301gsimuoju', - sgLoc: 'nesibė\u0301gsimajame nesibė\u0301gsimajam', - sgVoc: 'nesibė\u0301gsimasis', - plNom: 'nesibė\u0301gsimieji', - plGen: 'nesibė\u0301gsimųjų', - plDat: 'nesibė\u0301gsimiesiems nesibė\u0301gsimiesiem', - plAcc: 'nesibė\u0301gsimuosius', - plInst: 'nesibė\u0301gsimaisiais', - plLoc: 'nesibė\u0301gsimuosiuose nesibė\u0301gsimuosiuos', - plVoc: 'nesibė\u0301gsimieji', + sgInst: 'nesibėgsimu\u0301oju', + sgLoc: 'nesibėgsima\u0303jame nesibėgsima\u0303jam', + sgVoc: 'nesibėgsima\u0300sis', + plNom: 'nesibėgsimi\u0301eji', + plGen: 'nesibėgsimų\u0303jų', + plDat: 'nesibėgsimi\u0301esiems nesibėgsimi\u0301esiem', + plAcc: 'nesibėgsimu\u0301osius', + plInst: 'nesibėgsimai\u0303siais', + plLoc: 'nesibėgsimuo\u0303siuose nesibėgsimuo\u0303siuos', + plVoc: 'nesibėgsimi\u0301eji', } const NESIBEGSIMA = { - sgNom: 'nesibė\u0301gsima', - sgGen: 'nesibė\u0301gsimos', + sgNom: 'nesibėgsima\u0300', + sgGen: 'nesibėgsimo\u0303s', sgDat: 'nesibė\u0301gsimai', sgAcc: 'nesibė\u0301gsimą', sgInst: 'nesibė\u0301gsima', - sgLoc: 'nesibė\u0301gsimoje nesibė\u0301gsimoj', - sgVoc: 'nesibė\u0301gsima', + sgLoc: 'nesibėgsimoje\u0300 nesibėgsimo\u0303j', + sgVoc: 'nesibėgsima\u0300', plNom: 'nesibė\u0301gsimos', - plGen: 'nesibė\u0301gsimų', - plDat: 'nesibė\u0301gsimoms nesibė\u0301gsimom', + plGen: 'nesibėgsimų\u0303', + plDat: 'nesibėgsimo\u0301ms nesibėgsimo\u0301m', plAcc: 'nesibė\u0301gsimas', - plInst: 'nesibė\u0301gsimomis nesibė\u0301gsimom', - plLoc: 'nesibė\u0301gsimose', + plInst: 'nesibėgsimomi\u0300s nesibėgsimo\u0303m', + plLoc: 'nesibėgsimose\u0300', plVoc: 'nesibė\u0301gsimos', } const NESIBEGSIMOJI = { - sgNom: 'nesibė\u0301gsimoji', - sgGen: 'nesibė\u0301gsimosios', + sgNom: 'nesibėgsimo\u0301ji', + sgGen: 'nesibėgsimo\u0303sios', sgDat: 'nesibė\u0301gsimajai', sgAcc: 'nesibė\u0301gsimąją', - sgInst: 'nesibė\u0301gsimąja', - sgLoc: 'nesibė\u0301gsimojoje nesibė\u0301gsimojoj', - sgVoc: 'nesibė\u0301gsimoji', + sgInst: 'nesibėgsimą\u0301ja', + sgLoc: 'nesibėgsimo\u0303joje nesibėgsimo\u0303joj', + sgVoc: 'nesibėgsimo\u0301ji', plNom: 'nesibė\u0301gsimosios', - plGen: 'nesibė\u0301gsimųjų', - plDat: 'nesibė\u0301gsimosioms nesibė\u0301gsimosiom', - plAcc: 'nesibė\u0301gsimąsias', - plInst: 'nesibė\u0301gsimosiomis nesibė\u0301gsimosiom', - plLoc: 'nesibė\u0301gsimosiose', + plGen: 'nesibėgsimų\u0303jų', + plDat: 'nesibėgsimo\u0301sioms nesibėgsimo\u0301siom', + plAcc: 'nesibėgsimą\u0301sias', + plInst: 'nesibėgsimo\u0303siomis nesibėgsimo\u0303siom', + plLoc: 'nesibėgsimo\u0303siose', plVoc: 'nesibė\u0301gsimosios', } +const BAUSIMAS = { + sgNom: 'bau\u0303simas', + sgGen: 'bau\u0303simo', + sgDat: 'bausima\u0301m', + sgAcc: 'bau\u0303simą', + sgInst: 'bau\u0303simu', + sgLoc: 'bausimame\u0300 bausimam\u0303', + sgVoc: 'bau\u0303simas', + plNom: 'bausimi\u0300', + plGen: 'bausimų\u0303', + plDat: 'bausimi\u0301ems bausimi\u0301em', + plAcc: 'bau\u0303simus', + plInst: 'bausimai\u0303s', + plLoc: 'bausimuose\u0300 bausimuo\u0303s', + plVoc: 'bausimi\u0300', +} +const BAUSIMASIS = { + sgNom: 'bausima\u0300sis', + sgGen: 'bau\u0303simojo', + sgDat: 'bausima\u0301jam', + sgAcc: 'bau\u0303simąjį', + sgInst: 'bausimu\u0301oju', + sgLoc: 'bausima\u0303jame bausima\u0303jam', + sgVoc: 'bausima\u0300sis', + plNom: 'bausimi\u0301eji', + plGen: 'bausimų\u0303jų', + plDat: 'bausimi\u0301esiems bausimi\u0301esiem', + plAcc: 'bausimu\u0301osius', + plInst: 'bausimai\u0303siais', + plLoc: 'bausimuo\u0303siuose bausimuo\u0303siuos', + plVoc: 'bausimi\u0301eji', +} +const BAUSIMA = { + sgNom: 'bausima\u0300', + sgGen: 'bausimo\u0303s', + sgDat: 'bau\u0303simai', + sgAcc: 'bau\u0303simą', + sgInst: 'bau\u0303sima', + sgLoc: 'bausimoje\u0300 bausimo\u0303j', + sgVoc: 'bausima\u0300', + plNom: 'bau\u0303simos', + plGen: 'bausimų\u0303', + plDat: 'bausimo\u0301ms bausimo\u0301m', + plAcc: 'bau\u0303simas', + plInst: 'bausimomi\u0300s bausimo\u0303m', + plLoc: 'bausimose\u0300', + plVoc: 'bau\u0303simos', +} +const BAUSIMOJI = { + sgNom: 'bausimo\u0301ji', + sgGen: 'bausimo\u0303sios', + sgDat: 'bau\u0303simajai', + sgAcc: 'bau\u0303simąją', + sgInst: 'bausimą\u0301ja', + sgLoc: 'bausimo\u0303joje bausimo\u0303joj', + sgVoc: 'bausimo\u0301ji', + plNom: 'bau\u0303simosios', + plGen: 'bausimų\u0303jų', + plDat: 'bausimo\u0301sioms bausimo\u0301siom', + plAcc: 'bausimą\u0301sias', + plInst: 'bausimo\u0303siomis bausimo\u0303siom', + plLoc: 'bausimo\u0303siose', + plVoc: 'bau\u0303simosios', +} +const NESIBAUSIMAS = { + sgNom: 'nesibau\u0303simas', + sgGen: 'nesibau\u0303simo', + sgDat: 'nesibausima\u0301m', + sgAcc: 'nesibau\u0303simą', + sgInst: 'nesibau\u0303simu', + sgLoc: 'nesibausimame\u0300 nesibausimam\u0303', + sgVoc: 'nesibau\u0303simas', + plNom: 'nesibausimi\u0300', + plGen: 'nesibausimų\u0303', + plDat: 'nesibausimi\u0301ems nesibausimi\u0301em', + plAcc: 'nesibau\u0303simus', + plInst: 'nesibausimai\u0303s', + plLoc: 'nesibausimuose\u0300 nesibausimuo\u0303s', + plVoc: 'nesibausimi\u0300', +} +const NESIBAUSIMASIS = { + sgNom: 'nesibausima\u0300sis', + sgGen: 'nesibau\u0303simojo', + sgDat: 'nesibausima\u0301jam', + sgAcc: 'nesibau\u0303simąjį', + sgInst: 'nesibausimu\u0301oju', + sgLoc: 'nesibausima\u0303jame nesibausima\u0303jam', + sgVoc: 'nesibausima\u0300sis', + plNom: 'nesibausimi\u0301eji', + plGen: 'nesibausimų\u0303jų', + plDat: 'nesibausimi\u0301esiems nesibausimi\u0301esiem', + plAcc: 'nesibausimu\u0301osius', + plInst: 'nesibausimai\u0303siais', + plLoc: 'nesibausimuo\u0303siuose nesibausimuo\u0303siuos', + plVoc: 'nesibausimi\u0301eji', +} +const NESIBAUSIMA = { + sgNom: 'nesibausima\u0300', + sgGen: 'nesibausimo\u0303s', + sgDat: 'nesibau\u0303simai', + sgAcc: 'nesibau\u0303simą', + sgInst: 'nesibau\u0303sima', + sgLoc: 'nesibausimoje\u0300 nesibausimo\u0303j', + sgVoc: 'nesibausima\u0300', + plNom: 'nesibau\u0303simos', + plGen: 'nesibausimų\u0303', + plDat: 'nesibausimo\u0301ms nesibausimo\u0301m', + plAcc: 'nesibau\u0303simas', + plInst: 'nesibausimomi\u0300s nesibausimo\u0303m', + plLoc: 'nesibausimose\u0300', + plVoc: 'nesibau\u0303simos', +} +const NESIBAUSIMOJI = { + sgNom: 'nesibausimo\u0301ji', + sgGen: 'nesibausimo\u0303sios', + sgDat: 'nesibau\u0303simajai', + sgAcc: 'nesibau\u0303simąją', + sgInst: 'nesibausimą\u0301ja', + sgLoc: 'nesibausimo\u0303joje nesibausimo\u0303joj', + sgVoc: 'nesibausimo\u0301ji', + plNom: 'nesibau\u0303simosios', + plGen: 'nesibausimų\u0303jų', + plDat: 'nesibausimo\u0301sioms nesibausimo\u0301siom', + plAcc: 'nesibausimą\u0301sias', + plInst: 'nesibausimo\u0303siomis nesibausimo\u0303siom', + plLoc: 'nesibausimo\u0303siose', + plVoc: 'nesibau\u0303simosios', +} + const DAINUOSIMAS = { sgNom: `dainu\u0301osimas`, sgGen: `dainu\u0301osimo`, @@ -309,6 +443,11 @@ describe('PassiveFutureParticipleDecliner', () => { feminine: NESIBEGSIMA, neuter: `nesibė\u0301gsima`, }) + expect(decliner.getPrefixed(BEGTI, 'nesi')).toMatchObject({ + masculine: NESIBEGSIMAS, + feminine: NESIBEGSIMA, + neuter: `nesibė\u0301gsima`, + }) }) it('conjugates reflexive pronominal', () => { expect(decliner.getPrefixedReflexivePronominal(BEGTI, 'ne')) @@ -353,6 +492,11 @@ describe('PassiveFutureParticipleDecliner', () => { feminine: NESIDAINUOSIMA, neuter: `nesidainu\u0301osima`, }) + expect(decliner.getPrefixed(DAINUOTI, 'nesi')).toMatchObject({ + masculine: NESIDAINUOSIMAS, + feminine: NESIDAINUOSIMA, + neuter: `nesidainu\u0301osima`, + }) }) it('conjugates reflexive pronominal', () => { expect(decliner.getPrefixedReflexivePronominal(DAINUOTI, 'ne')) @@ -362,4 +506,53 @@ describe('PassiveFutureParticipleDecliner', () => { }) }) }) + describe('bausti', () => { + it('conjugates default', () => { + expect(decliner.getDefault(BAUSTI)).toMatchObject({ + masculine: BAUSIMAS, + feminine: BAUSIMA, + neuter: `bau\u0303sima`, + }) + }) + it('conjugates pronominal', () => { + expect(decliner.getPronominal(BAUSTI)).toMatchObject({ + masculine: BAUSIMASIS, + feminine: BAUSIMOJI, + }) + }) + it('conjugates reflexive', () => { + expect(decliner.getReflexive(BAUSTI)).toMatchObject({ + masculine: NOMINAL_EMPTY, + feminine: NOMINAL_EMPTY, + neuter: `bau\u0303simasi`, + }) + }) + it('conjugates reflexive pronominal', () => { + expect(decliner.getReflexivePronominal(BAUSTI)).toMatchObject({ + masculine: NOMINAL_EMPTY, + feminine: NOMINAL_EMPTY, + }) + }) + }) + describe('nesibausti', () => { + it('conjugates reflexive', () => { + expect(decliner.getPrefixedReflexive(BAUSTI, 'ne')).toMatchObject({ + masculine: NESIBAUSIMAS, + feminine: NESIBAUSIMA, + neuter: `nesibau\u0303sima`, + }) + expect(decliner.getPrefixed(BAUSTI, 'nesi')).toMatchObject({ + masculine: NESIBAUSIMAS, + feminine: NESIBAUSIMA, + neuter: `nesibau\u0303sima`, + }) + }) + it('conjugates reflexive pronominal', () => { + expect(decliner.getPrefixedReflexivePronominal(BAUSTI, 'ne')) + .toMatchObject({ + masculine: NESIBAUSIMASIS, + feminine: NESIBAUSIMOJI, + }) + }) + }) }) diff --git a/test/utils.test.ts b/test/utils.test.ts index 563f1e7..3c9b749 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -8,7 +8,8 @@ import { getNthLast, getPalatalizedRoot, getPastRoot, - getPresentRoot, getStressedSyllable, + getPresentRoot, + getStressedSyllable, getUnpalatalizedRoot, hasAcuteAccent, hasAnyAccent, @@ -506,6 +507,11 @@ describe('utils', () => { syllable: 3, }) }) + it("throws when loop get stuck'", () => { + expect(() => countAccentedSyllable(`bau\u0301simas`)).toThrow( + cannotParseSyllableError, + ) + }) }) describe('isRootMonosyllabic', () => { const data: [string, boolean][] = [ @@ -610,9 +616,18 @@ describe('utils', () => { expect(getStressedSyllable(`dėti`)).toBeNull() }) it(`returns correct stressed syllable and its position`, () => { - expect(getStressedSyllable(`padė\u0301ti`)).toMatchObject({text: `dė\u0301`, position: 2}) - expect(getStressedSyllable(`padėti\u0300`)).toMatchObject({text: `ti\u0300`, position: 1}) - expect(getStressedSyllable(`pe\u0301rdėti`)).toMatchObject({text: `pe\u0301r`, position: 3}) + expect(getStressedSyllable(`padė\u0301ti`)).toMatchObject({ + text: `dė\u0301`, + position: 2, + }) + expect(getStressedSyllable(`padėti\u0300`)).toMatchObject({ + text: `ti\u0300`, + position: 1, + }) + expect(getStressedSyllable(`pe\u0301rdėti`)).toMatchObject({ + text: `pe\u0301r`, + position: 3, + }) }) it('throws when loop get stuck', () => { expect(() => getStressedSyllable(`pažы\u0303ti`)).toThrow( From 2b14551896939924b4fc3bc305524ff12fe8c3a0 Mon Sep 17 00:00:00 2001 From: Andrius Simanaitis Date: Thu, 23 Apr 2026 23:51:22 +0300 Subject: [PATCH 09/12] fix PassivePresentParticipleDecliner --- .../PassivePresentParticipleDecliner.ts | 4 +- .../PassivePresentParticipleDecliner.test.ts | 176 ++++++++++++++++++ 2 files changed, 179 insertions(+), 1 deletion(-) diff --git a/src/flectors/conjugators/PassivePresentParticipleDecliner.ts b/src/flectors/conjugators/PassivePresentParticipleDecliner.ts index 184b1c1..4ab9e41 100644 --- a/src/flectors/conjugators/PassivePresentParticipleDecliner.ts +++ b/src/flectors/conjugators/PassivePresentParticipleDecliner.ts @@ -1,5 +1,6 @@ import type { DeclinedType, PrincipalPartsType } from '~src/types.ts' import { + getPresentRoot, joinInflections, putAccentOnPrefix, stripAllAccents, @@ -101,6 +102,7 @@ export default class PassivePresentParticipleDecliner } function getRootAndPrefix(principalParts: PrincipalPartsType) { + const { pattern } = getPresentRoot(principalParts) const parsedRoot = principalParts[1] const prefix = parsedRoot.replace(PREFIX_REGEX, '$2') const root = parsedRoot.replace(ROOT_REGEX, '') @@ -113,7 +115,7 @@ function getRootAndPrefix(principalParts: PrincipalPartsType) { ? putAccentOnPrefix(prefix) + stripAllAccents(root) : parsedRoot.replace(PREFIX_SEPARATOR, '') return { - isStemImmobile, + isStemImmobile: pattern === 'o', prefixedRoot: prefixedRoot + PASSIVE_PRESENT_SUFFIX, } } diff --git a/test/flectors/conjugators/PassivePresentParticipleDecliner.test.ts b/test/flectors/conjugators/PassivePresentParticipleDecliner.test.ts index e77431b..d7f1f0d 100644 --- a/test/flectors/conjugators/PassivePresentParticipleDecliner.test.ts +++ b/test/flectors/conjugators/PassivePresentParticipleDecliner.test.ts @@ -15,6 +15,11 @@ const DARYTI: PrincipalPartsType = [ `da\u0303ro`, `da\u0303rė`, ] +const NORETI: PrincipalPartsType = [ + `norė\u0301ti`, + `no\u0301ri`, + `norė\u0301jo`, +] const RENKAMAS: DeclinedType = { sgNom: `ren\u0303kamas`, @@ -144,6 +149,7 @@ const NERENKAMOJI: DeclinedType = { plLoc: `nerenkamo\u0303siose`, plVoc: `ne\u0300renkamosios`, } + const DAROMAS: DeclinedType = { sgNom: `da\u0303romas`, sgGen: `da\u0303romo`, @@ -283,6 +289,135 @@ const NEDAROMOJI: DeclinedType = { plVoc: `neda\u0303romosios`, } +const NORIMAS: DeclinedType = { + sgNom: `no\u0301rimas`, + sgGen: `no\u0301rimo`, + sgDat: `norima\u0301m`, + sgAcc: `no\u0301rimą`, + sgInst: `no\u0301rimu`, + sgLoc: `norimame\u0300 norimam\u0303`, + sgVoc: `no\u0301rimas`, + plNom: `norimi\u0300`, + plGen: `norimų\u0303`, + plDat: `norimi\u0301ems norimi\u0301em`, + plAcc: `no\u0301rimus`, + plInst: `norimai\u0303s`, + plLoc: `norimuose\u0300 norimuo\u0303s`, + plVoc: `norimi\u0300`, +} +const NORIMASIS: DeclinedType = { + sgNom: `norima\u0300sis`, + sgGen: `no\u0301rimojo`, + sgDat: `norima\u0301jam`, + sgAcc: `no\u0301rimąjį`, + sgInst: `norimu\u0301oju`, + sgLoc: `norima\u0303jame norima\u0303jam`, + sgVoc: `norima\u0300sis`, + plNom: `norimi\u0301eji`, + plGen: `norimų\u0303jų`, + plDat: `norimi\u0301esiems norimi\u0301esiem`, + plAcc: `norimu\u0301osius`, + plInst: `norimai\u0303siais`, + plLoc: `norimuo\u0303siuose norimuo\u0303siuos`, + plVoc: `norimi\u0301eji`, +} +const NORIMA: DeclinedType = { + sgNom: `norima\u0300`, + sgGen: `norimo\u0303s`, + sgDat: `no\u0301rimai`, + sgAcc: `no\u0301rimą`, + sgInst: `no\u0301rima`, + sgLoc: `norimoje\u0300 norimo\u0303j`, + sgVoc: `norima\u0300`, + plNom: `no\u0301rimos`, + plGen: `norimų\u0303`, + plDat: `norimo\u0301ms norimo\u0301m`, + plAcc: `no\u0301rimas`, + plInst: `norimomi\u0300s norimo\u0303m`, + plLoc: `norimose\u0300`, + plVoc: `no\u0301rimos`, +} +const NORIMOJI: DeclinedType = { + sgNom: `norimo\u0301ji`, + sgGen: `norimo\u0303sios`, + sgDat: `no\u0301rimajai`, + sgAcc: `no\u0301rimąją`, + sgInst: `norimą\u0301ja`, + sgLoc: `norimo\u0303joje norimo\u0303joj`, + sgVoc: `norimo\u0301ji`, + plNom: `no\u0301rimosios`, + plGen: `norimų\u0303jų`, + plDat: `norimo\u0301sioms norimo\u0301siom`, + plAcc: `norimą\u0301sias`, + plInst: `norimo\u0303siomis norimo\u0303siom`, + plLoc: `norimo\u0303siose`, + plVoc: `no\u0301rimosios`, +} +const NENORIMAS = { + sgNom: `neno\u0301rimas`, + sgGen: `neno\u0301rimo`, + sgDat: `nenorima\u0301m`, + sgAcc: `neno\u0301rimą`, + sgInst: `neno\u0301rimu`, + sgLoc: `nenorimame\u0300 nenorimam\u0303`, + sgVoc: `neno\u0301rimas`, + plNom: `nenorimi\u0300`, + plGen: `nenorimų\u0303`, + plDat: `nenorimi\u0301ems nenorimi\u0301em`, + plAcc: `neno\u0301rimus`, + plInst: `nenorimai\u0303s`, + plLoc: `nenorimuose\u0300 nenorimuo\u0303s`, + plVoc: `nenorimi\u0300`, +} +const NENORIMASIS = { + sgNom: `nenorima\u0300sis`, + sgGen: `neno\u0301rimojo`, + sgDat: `nenorima\u0301jam`, + sgAcc: `neno\u0301rimąjį`, + sgInst: `nenorimu\u0301oju`, + sgLoc: `nenorima\u0303jame nenorima\u0303jam`, + sgVoc: `nenorima\u0300sis`, + plNom: `nenorimi\u0301eji`, + plGen: `nenorimų\u0303jų`, + plDat: `nenorimi\u0301esiems nenorimi\u0301esiem`, + plAcc: `nenorimu\u0301osius`, + plInst: `nenorimai\u0303siais`, + plLoc: `nenorimuo\u0303siuose nenorimuo\u0303siuos`, + plVoc: `nenorimi\u0301eji`, +} +const NENORIMA: DeclinedType = { + sgNom: `nenorima\u0300`, + sgGen: `nenorimo\u0303s`, + sgDat: `neno\u0301rimai`, + sgAcc: `neno\u0301rimą`, + sgInst: `neno\u0301rima`, + sgLoc: `nenorimoje\u0300 nenorimo\u0303j`, + sgVoc: `nenorima\u0300`, + plNom: `neno\u0301rimos`, + plGen: `nenorimų\u0303`, + plDat: `nenorimo\u0301ms nenorimo\u0301m`, + plAcc: `neno\u0301rimas`, + plInst: `nenorimomi\u0300s nenorimo\u0303m`, + plLoc: `nenorimose\u0300`, + plVoc: `neno\u0301rimos`, +} +const NENORIMOJI: DeclinedType = { + sgNom: `nenorimo\u0301ji`, + sgGen: `nenorimo\u0303sios`, + sgDat: `neno\u0301rimajai`, + sgAcc: `neno\u0301rimąją`, + sgInst: `nenorimą\u0301ja`, + sgLoc: `nenorimo\u0303joje nenorimo\u0303joj`, + sgVoc: `nenorimo\u0301ji`, + plNom: `neno\u0301rimosios`, + plGen: `nenorimų\u0303jų`, + plDat: `nenorimo\u0301sioms nenorimo\u0301siom`, + plAcc: `nenorimą\u0301sias`, + plInst: `nenorimo\u0303siomis nenorimo\u0303siom`, + plLoc: `nenorimo\u0303siose`, + plVoc: `neno\u0301rimosios`, +} + describe('PassivePresentParticipleDecliner', () => { const decliner = new PassivePresentParticipleDecliner() describe('rinkt', () => { @@ -367,4 +502,45 @@ describe('PassivePresentParticipleDecliner', () => { }) }) }) + describe('norėti', () => { + it('conjugates default', () => { + expect(decliner.getDefault(NORETI)).toMatchObject({ + masculine: NORIMAS, + feminine: NORIMA, + neuter: `no\u0301rima`, + }) + }) + it('conjugates reflexive', () => { + expect(decliner.getReflexive(NORETI)).toMatchObject({ + masculine: NOMINAL_EMPTY, + feminine: NOMINAL_EMPTY, + neuter: `no\u0301rimasi`, + }) + }) + it('conjugates prefixed', () => { + expect(decliner.getPrefixed(NORETI, PREFIX)).toMatchObject({ + masculine: NENORIMAS, + feminine: NENORIMA, + neuter: `neno\u0301rima`, + }) + }) + it('conjugates pronominal', () => { + expect(decliner.getPronominal(NORETI)).toMatchObject({ + masculine: NORIMASIS, + feminine: NORIMOJI, + }) + }) + it('conjugates reflexive pronominal', () => { + expect(decliner.getReflexivePronominal(NORETI)).toMatchObject({ + masculine: NOMINAL_EMPTY, + feminine: NOMINAL_EMPTY, + }) + }) + it('conjugates prefixed pronominal', () => { + expect(decliner.getPrefixedPronominal(NORETI, PREFIX)).toMatchObject({ + masculine: NENORIMASIS, + feminine: NENORIMOJI, + }) + }) + }) }) From f90494d1ca4ebc91b6afc7bc501418b4cc99562e Mon Sep 17 00:00:00 2001 From: Andrius Simanaitis Date: Fri, 24 Apr 2026 09:44:12 +0300 Subject: [PATCH 10/12] fix past passive participles having short stress in roots when the enlongation happens --- .../PassivePastParticipleDecliner.ts | 69 +++++++++++----- .../PassivePastParticipleDecliner.test.ts | 80 ++++++++++++++++++- 2 files changed, 127 insertions(+), 22 deletions(-) diff --git a/src/flectors/conjugators/PassivePastParticipleDecliner.ts b/src/flectors/conjugators/PassivePastParticipleDecliner.ts index 0bc79b0..2282e91 100644 --- a/src/flectors/conjugators/PassivePastParticipleDecliner.ts +++ b/src/flectors/conjugators/PassivePastParticipleDecliner.ts @@ -54,8 +54,13 @@ export default class PassivePastParticipleDecliner extends ParticipleDecliner { getDefault(principalParts: PrincipalPartsType): ParticipleType { let masculine: DeclinedType let feminine: DeclinedType - const { isStemImmobile, prefixedRoot, isAcute, syllable } = - getRootAndPrefix(principalParts) + const { + isStemImmobile, + prefixedRoot, + isAcute, + syllable, + isMandatoryShort, + } = getRootAndPrefix(principalParts) if (isStemImmobile) { masculine = AsAdjectiveDecliner.inflectStatic( @@ -63,24 +68,34 @@ export default class PassivePastParticipleDecliner extends ParticipleDecliner { ) feminine = AAdjectiveDecliner.inflectStatic(prefixedRoot) } else { + const accentlessRoot = stripAllAccents(prefixedRoot) if ( isRootMonosyllabic(prefixedRoot) && hasCircumflexOrShortAccent(prefixedRoot) ) { - masculine = AsAdjectiveDecliner.inflectDynamic(prefixedRoot) + const type = '4' + masculine = AsAdjectiveDecliner.inflectDynamic( + accentlessRoot, + type, + ) feminine = AAdjectiveDecliner.inflectDynamic( - stripAllAccents(prefixedRoot), - '4', + accentlessRoot, + type, ) } else { - masculine = AsAdjectiveDecliner.inflectDynamic(prefixedRoot) + const type = { + syllable: syllable!, + isAcute, + } + masculine = AsAdjectiveDecliner.inflectDynamic( + accentlessRoot, + type, + isMandatoryShort, + ) feminine = AAdjectiveDecliner.inflectDynamic( - stripAllAccents(prefixedRoot), - { - syllable: syllable!, - isAcute, - }, - hasCircumflexOrShortAccent(prefixedRoot), + accentlessRoot, + type, + isMandatoryShort, ) } } @@ -96,8 +111,13 @@ export default class PassivePastParticipleDecliner extends ParticipleDecliner { ): ComplementingParticipleType { let masculine: DeclinedType let feminine: DeclinedType - const { isStemImmobile, prefixedRoot, isAcute, syllable } = - getRootAndPrefix(principalParts) + const { + isStemImmobile, + prefixedRoot, + isAcute, + syllable, + isMandatoryShort, + } = getRootAndPrefix(principalParts) if (isStemImmobile) { masculine = AsPronominalDecliner.inflectStatic( @@ -105,14 +125,20 @@ export default class PassivePastParticipleDecliner extends ParticipleDecliner { ) feminine = APronominalDecliner.inflectStatic(prefixedRoot) } else { - masculine = AsPronominalDecliner.inflectDynamic(prefixedRoot) + const accentlessRoot = stripAllAccents(prefixedRoot) + const type = { + syllable: syllable!, + isAcute, + } + masculine = AsPronominalDecliner.inflectDynamic( + accentlessRoot, + type, + isMandatoryShort, + ) feminine = APronominalDecliner.inflectDynamic( - stripAllAccents(prefixedRoot), - { - syllable: syllable!, - isAcute, - }, - hasCircumflexOrShortAccent(prefixedRoot), + accentlessRoot, + type, + isMandatoryShort, ) } return { @@ -157,5 +183,6 @@ function getRootAndPrefix(principalParts: PrincipalPartsType) { type, isAcute: type === 'acute', syllable, + isMandatoryShort: prefix !== '', } } diff --git a/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts b/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts index 322b80f..e6c4d9c 100644 --- a/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts +++ b/test/flectors/conjugators/PassivePastParticipleDecliner.test.ts @@ -36,6 +36,11 @@ const BRISTI: PrincipalPartsType = [ `bren\u0303da`, `bri\u0300do`, ] +const KASTI: PrincipalPartsType = [ + `ka\u0300sti`, + `ka\u0303sa`, + `ka\u0300sė`, +] const RINKTAS = makeDeclinedFromArray( `riñktas riñkto rinktám riñktą rinktù rinktamè rinktam\u0303 riñktas @@ -184,6 +189,47 @@ nèbristosios nebristų̃jų nebristósioms nebristósiom nebristą́sias .split(/\s\s/), ) +const KASTAS = makeDeclinedFromArray( + `kãstas kãsto kastám kãstą kastù kastamè kastam̃ kãstas +kastì kastų̃ kastíems kastíem kastùs kastaĩs kastuosè kastuõs kastì` + .split(/\s\s/), +) +const KASTASIS = makeDeclinedFromArray( + `kastàsis kãstojo kastájam kãstąjį kastúoju kastãjame kastãjam kastàsis +kastíeji kastų̃jų kastíesiems kastíesiem kastúosius kastaĩsiais kastuõsiuose kastuõsiuos kastíeji` + .split(/\s\s/), +) +const KASTA = makeDeclinedFromArray( + `kastà kastõs kãstai kãstą kastà kastojè kastõj kastà +kãstos kastų̃ kastóms kastóm kastàs kastomìs kastõm kastosè kãstos` + .split(/\s\s/), +) +const KASTOJI = makeDeclinedFromArray( + `kastóji kastõsios kãstajai kãstąją kastą́ja kastõjoje kastõjoj kastóji +kãstosios kastų̃jų kastósioms kastósiom kastą́sias kastõsiomis kastõsiom kastõsiose kãstosios` + .split(/\s\s/), +) +const NEKASTAS = makeDeclinedFromArray( + `nèkastas nèkasto nekastám nèkastą nèkastu nekastamè nekastam̃ nèkastas +nekastì nekastų̃ nekastíems nekastíem nèkastus nekastaĩs nekastuosè nekastuõs nekastì` + .split(/\s\s/), +) +const NEKASTASIS = makeDeclinedFromArray( + `nekastàsis nèkastojo nekastájam nèkastąjį nekastúoju nekastãjame nekastãjam nekastàsis +nekastíeji nekastų̃jų nekastíesiems nekastíesiem nekastúosius nekastaĩsiais nekastuõsiuose nekastuõsiuos nekastíeji` + .split(/\s\s/), +) +const NEKASTA = makeDeclinedFromArray( + `nekastà nekastõs nèkastai nèkastą nèkasta nekastojè nekastõj nekastà +nèkastos nekastų̃ nekastóms nekastóm nèkastas nekastomìs nekastõm nekastosè nèkastos` + .split(/\s\s/), +) +const NEKASTOJI = makeDeclinedFromArray( + `nekastóji nekastõsios nèkastajai nèkastąją nekastą́ja nekastõjoje nekastõjoj nekastóji +nèkastosios nekastų̃jų nekastósioms nekastósiom nekastą́sias nekastõsiomis nekastõsiom nekastõsiose nèkastosios` + .split(/\s\s/), +) + describe('PassivePastParticipleDecliner', () => { const decliner = new PassivePastParticipleDecliner() describe('rinkti', () => { @@ -334,7 +380,7 @@ describe('PassivePastParticipleDecliner', () => { }) }) }) - describe('bristi', () => { + describe('nebristi', () => { it('conjugates default', () => { expect(decliner.getPrefixed(BRISTI, 'ne')).toMatchObject({ masculine: NEBRISTAS, @@ -361,4 +407,36 @@ describe('PassivePastParticipleDecliner', () => { .toStrictEqual('neitasis') }) }) + describe('kasti', () => { + it('conjugates default', () => { + expect(decliner.getDefault(KASTI)).toMatchObject({ + masculine: KASTAS, + feminine: KASTA, + neuter: `ka\u0303sta`, + }) + }) + it('conjugates pronominal', () => { + expect(decliner.getPronominal(KASTI)) + .toMatchObject({ + masculine: KASTASIS, + feminine: KASTOJI, + }) + }) + }) + describe('nekasti', () => { + it('conjugates default', () => { + expect(decliner.getPrefixed(KASTI, 'ne')).toMatchObject({ + masculine: NEKASTAS, + feminine: NEKASTA, + neuter: `ne\u0300kasta`, + }) + }) + it('conjugates pronominal', () => { + expect(decliner.getPrefixedPronominal(KASTI, 'ne')) + .toMatchObject({ + masculine: NEKASTASIS, + feminine: NEKASTOJI, + }) + }) + }) }) From 4db46ab1f62e8183ee8e8beb13e26e7a74846d21 Mon Sep 17 00:00:00 2001 From: Andrius Simanaitis Date: Fri, 24 Apr 2026 09:56:41 +0300 Subject: [PATCH 11/12] add necessity participle --- src/Verb.ts | 19 + .../NecessityParticipleDecliner.ts | 146 +++++ test/Verb.test.ts | 10 + .../NecessityParticipleDecliner.test.ts | 558 ++++++++++++++++++ 4 files changed, 733 insertions(+) create mode 100644 src/flectors/conjugators/NecessityParticipleDecliner.ts create mode 100644 test/flectors/conjugators/NecessityParticipleDecliner.test.ts diff --git a/src/Verb.ts b/src/Verb.ts index b634507..a502c2c 100644 --- a/src/Verb.ts +++ b/src/Verb.ts @@ -32,6 +32,7 @@ import PadalyvisInflector from '~conjugators/PadalyvisInflector.ts' import type { PadalyvisType } from '~conjugators/PadalyvisInflector.ts' import BudinysInflector from '~conjugators/BudinysInflector.ts' import type { BudinysType } from '~conjugators/BudinysInflector.ts' +import NecessityParticipleDecliner from './flectors/conjugators/NecessityParticipleDecliner.ts' /** * Class which lets you derive various forms such as various moods, -imas action deverbal and various @@ -83,6 +84,8 @@ export default class Verb extends Verbal { public static readonly presentPadalyvis: InflectorInterface = new PadalyvisInflector(Verb.activePresentParticiple) public static readonly budinys: BudinysInflector = new BudinysInflector() + public static readonly necessityParticiple: ParticipleDecliner = + new NecessityParticipleDecliner() /** * Wrapper to call all the static methods with the same options @@ -374,6 +377,22 @@ export default class Verb extends Verbal { public conjugateBudinys(): BudinysType { return Verb.budinys.getDefault(this.principalParts) } + /** + * declines necessity participle based on the data passed to the verb's constructor + * @param {boolean=false} isPronominal - whether the declined participle should be pronominal, defaults `false` + * @example + * ``` + * const prefixedPronominalParticiple = new Verb('eiti-eina-ėjo', {prefix: 'per'}).declineNecessityParticiple(true) + * ``` + */ + public declineNecessityParticiple( + isPronominal: boolean = false, + ): ParticipleType { + return this.#inflectBasedOnOptions( + Verb.necessityParticiple, + isPronominal, + ) + } #inflectBasedOnOptions< T extends Record>, diff --git a/src/flectors/conjugators/NecessityParticipleDecliner.ts b/src/flectors/conjugators/NecessityParticipleDecliner.ts new file mode 100644 index 0000000..34c20bd --- /dev/null +++ b/src/flectors/conjugators/NecessityParticipleDecliner.ts @@ -0,0 +1,146 @@ +import type { DeclinedType, PrincipalPartsType } from '~src/types.ts' +import { + countAccentedSyllable, + getInfinitiveRoot, + hasAcuteAccent, + hasAnyAccent, + hasCircumflexOrShortAccent, + isRootMonosyllabic, + stripAllAccents, +} from '~src/utils.ts' +import ParticipleDecliner, { + type ComplementingParticipleType, + type ParticipleType, +} from './ParticipleDecliner.ts' +import { + AAdjectiveDecliner, + APronominalDecliner, + AsAdjectiveDecliner, + AsPronominalDecliner, +} from '~decliners/commons.ts' +import { NOMINAL_EMPTY, PREFIX_SEPARATOR } from '~src/commons.ts' + +const NECESSITY_SUFFIX = 'tin' +const PREFIX_REGEX = new RegExp( + `^(([^${PREFIX_SEPARATOR}]+)${PREFIX_SEPARATOR}.+|.+)$`, +) +const ROOT_REGEX = new RegExp(`^(.+${PREFIX_SEPARATOR})`) + +export default class NecessityParticipleDecliner extends ParticipleDecliner { + protected getBasicPrefixed( + principalParts: PrincipalPartsType, + prefix: string, + getBasicInflected: (principalParts: PrincipalPartsType) => ParticipleType, + ): ParticipleType { + const { root } = getInfinitiveRoot(principalParts) + const isStemImmobile = !hasAnyAccent(root) || !isRootMonosyllabic(root) || + hasAcuteAccent(root) + if (isStemImmobile) { + return this.getBasicImmobilePrefixed( + prefix, + principalParts, + getBasicInflected, + ) + } + + return getBasicInflected( + principalParts.map((part) => + `${prefix}${PREFIX_SEPARATOR}${part}` + ) as PrincipalPartsType, + ) + } + + getDefault(principalParts: PrincipalPartsType): ParticipleType { + let masculine: DeclinedType + let feminine: DeclinedType + const { isStemImmobile, prefixedRoot, isAcute, syllable } = + getRootAndPrefix(principalParts) + + if (isStemImmobile) { + masculine = AsAdjectiveDecliner.inflectStatic( + prefixedRoot, + ) + feminine = AAdjectiveDecliner.inflectStatic(prefixedRoot) + } else { + masculine = AsAdjectiveDecliner.inflectDynamic(prefixedRoot) + feminine = AAdjectiveDecliner.inflectDynamic( + stripAllAccents(prefixedRoot), + { + syllable: syllable!, + isAcute, + }, + hasCircumflexOrShortAccent(prefixedRoot), + ) + } + return { + masculine, + feminine, + neuter: masculine.sgNom.replace(/s$/, ''), + } as unknown as ParticipleType + } + + getPronominal( + principalParts: PrincipalPartsType, + ): ComplementingParticipleType { + let masculine: DeclinedType + let feminine: DeclinedType + const { isStemImmobile, prefixedRoot, isAcute, syllable } = + getRootAndPrefix(principalParts) + + if (isStemImmobile) { + masculine = AsPronominalDecliner.inflectStatic( + prefixedRoot, + ) + feminine = APronominalDecliner.inflectStatic(prefixedRoot) + } else { + masculine = AsPronominalDecliner.inflectDynamic(prefixedRoot) + feminine = APronominalDecliner.inflectDynamic( + stripAllAccents(prefixedRoot), + { + syllable: syllable!, + isAcute, + }, + hasCircumflexOrShortAccent(prefixedRoot), + ) + } + return { + masculine, + feminine, + } as ParticipleType + } + + public override getReflexive( + principalParts: PrincipalPartsType, + ): ParticipleType { + return { + masculine: NOMINAL_EMPTY, + feminine: NOMINAL_EMPTY, + neuter: this.getDefault(principalParts).masculine.sgNom + 'i', + } as unknown as ParticipleType + } + + override getReflexivePronominal( + principalParts: PrincipalPartsType, + ): ComplementingParticipleType { + const { masculine, feminine } = this.getReflexive(principalParts) + return { masculine, feminine } + } +} + +function getRootAndPrefix(principalParts: PrincipalPartsType) { + const parsedRoot = getInfinitiveRoot(principalParts).root + const prefix = parsedRoot.replace(PREFIX_REGEX, '$2') + const root = parsedRoot.replace(ROOT_REGEX, '') + const prefixedRoot = prefix + root + NECESSITY_SUFFIX + const { hasAccentedSyllable, syllable, type } = countAccentedSyllable( + prefixedRoot + 'as', + ) + const isStemImmobile = !isRootMonosyllabic(root) || !hasAccentedSyllable + return { + isStemImmobile, + prefixedRoot, + type, + isAcute: type === 'acute', + syllable, + } +} diff --git a/test/Verb.test.ts b/test/Verb.test.ts index 0a34b5f..0413f9a 100644 --- a/test/Verb.test.ts +++ b/test/Verb.test.ts @@ -155,6 +155,16 @@ describe('Verb', () => { ) }) }) + assertTense( + Verb.necessityParticiple, + 'declineNecessityParticiple', + 'necessityParticiple', + ) + assertPronominal( + Verb.necessityParticiple, + 'declineNecessityParticiple', + 'necessityParticiple', + ) function assertTense< T extends Record>, diff --git a/test/flectors/conjugators/NecessityParticipleDecliner.test.ts b/test/flectors/conjugators/NecessityParticipleDecliner.test.ts new file mode 100644 index 0000000..942ca38 --- /dev/null +++ b/test/flectors/conjugators/NecessityParticipleDecliner.test.ts @@ -0,0 +1,558 @@ +import { expect } from '@std/expect' +import { describe, it } from '@std/testing/bdd' +import type { PrincipalPartsType } from '~src/types.ts' +import NecessityParticipleDecliner from '~conjugators/NecessityParticipleDecliner.ts' +import { NOMINAL_EMPTY } from '~src/commons.ts' + +const BEGTI: PrincipalPartsType = [ + `bė\u0301gti`, + `bė\u0303ga`, + `bė\u0303go`, +] +const BAUSTI: PrincipalPartsType = [ + `bau\u0303sti`, + `bau\u0303džia`, + `bau\u0303dė`, +] +const DAINUOTI: PrincipalPartsType = [ + `dainu\u0301oti`, + `dainu\u0301oja`, + `daina\u0303vo`, +] +const BEGTINAS = { + sgNom: 'bė\u0301gtinas', + sgGen: 'bė\u0301gtino', + sgDat: 'bėgtina\u0301m', + sgAcc: 'bė\u0301gtiną', + sgInst: 'bė\u0301gtinu', + sgLoc: 'bėgtiname\u0300 bėgtinam\u0303', + sgVoc: 'bė\u0301gtinas', + plNom: 'bėgtini\u0300', + plGen: 'bėgtinų\u0303', + plDat: 'bėgtini\u0301ems bėgtini\u0301em', + plAcc: 'bė\u0301gtinus', + plInst: 'bėgtinai\u0303s', + plLoc: 'bėgtinuose\u0300 bėgtinuo\u0303s', + plVoc: 'bėgtini\u0300', +} +const BEGTINASIS = { + sgNom: 'bėgtina\u0300sis', + sgGen: 'bė\u0301gtinojo', + sgDat: 'bėgtina\u0301jam', + sgAcc: 'bė\u0301gtinąjį', + sgInst: 'bėgtinu\u0301oju', + sgLoc: 'bėgtina\u0303jame bėgtina\u0303jam', + sgVoc: 'bėgtina\u0300sis', + plNom: 'bėgtini\u0301eji', + plGen: 'bėgtinų\u0303jų', + plDat: 'bėgtini\u0301esiems bėgtini\u0301esiem', + plAcc: 'bėgtinu\u0301osius', + plInst: 'bėgtinai\u0303siais', + plLoc: 'bėgtinuo\u0303siuose bėgtinuo\u0303siuos', + plVoc: 'bėgtini\u0301eji', +} +const BEGTINA = { + sgNom: 'bėgtina\u0300', + sgGen: 'bėgtino\u0303s', + sgDat: 'bė\u0301gtinai', + sgAcc: 'bė\u0301gtiną', + sgInst: 'bė\u0301gtina', + sgLoc: 'bėgtinoje\u0300 bėgtino\u0303j', + sgVoc: 'bėgtina\u0300', + plNom: 'bė\u0301gtinos', + plGen: 'bėgtinų\u0303', + plDat: 'bėgtino\u0301ms bėgtino\u0301m', + plAcc: 'bė\u0301gtinas', + plInst: 'bėgtinomi\u0300s bėgtino\u0303m', + plLoc: 'bėgtinose\u0300', + plVoc: 'bė\u0301gtinos', +} +const BEGTINOJI = { + sgNom: 'bėgtino\u0301ji', + sgGen: 'bėgtino\u0303sios', + sgDat: 'bė\u0301gtinajai', + sgAcc: 'bė\u0301gtinąją', + sgInst: 'bėgtiną\u0301ja', + sgLoc: 'bėgtino\u0303joje bėgtino\u0303joj', + sgVoc: 'bėgtino\u0301ji', + plNom: 'bė\u0301gtinosios', + plGen: 'bėgtinų\u0303jų', + plDat: 'bėgtino\u0301sioms bėgtino\u0301siom', + plAcc: 'bėgtiną\u0301sias', + plInst: 'bėgtino\u0303siomis bėgtino\u0303siom', + plLoc: 'bėgtino\u0303siose', + plVoc: 'bė\u0301gtinosios', +} +const NESIBEGTINAS = { + sgNom: 'nesibė\u0301gtinas', + sgGen: 'nesibė\u0301gtino', + sgDat: 'nesibėgtina\u0301m', + sgAcc: 'nesibė\u0301gtiną', + sgInst: 'nesibė\u0301gtinu', + sgLoc: 'nesibėgtiname\u0300 nesibėgtinam\u0303', + sgVoc: 'nesibė\u0301gtinas', + plNom: 'nesibėgtini\u0300', + plGen: 'nesibėgtinų\u0303', + plDat: 'nesibėgtini\u0301ems nesibėgtini\u0301em', + plAcc: 'nesibė\u0301gtinus', + plInst: 'nesibėgtinai\u0303s', + plLoc: 'nesibėgtinuose\u0300 nesibėgtinuo\u0303s', + plVoc: 'nesibėgtini\u0300', +} +const NESIBEGTINASIS = { + sgNom: 'nesibėgtina\u0300sis', + sgGen: 'nesibė\u0301gtinojo', + sgDat: 'nesibėgtina\u0301jam', + sgAcc: 'nesibė\u0301gtinąjį', + sgInst: 'nesibėgtinu\u0301oju', + sgLoc: 'nesibėgtina\u0303jame nesibėgtina\u0303jam', + sgVoc: 'nesibėgtina\u0300sis', + plNom: 'nesibėgtini\u0301eji', + plGen: 'nesibėgtinų\u0303jų', + plDat: 'nesibėgtini\u0301esiems nesibėgtini\u0301esiem', + plAcc: 'nesibėgtinu\u0301osius', + plInst: 'nesibėgtinai\u0303siais', + plLoc: 'nesibėgtinuo\u0303siuose nesibėgtinuo\u0303siuos', + plVoc: 'nesibėgtini\u0301eji', +} +const NESIBEGTINA = { + sgNom: 'nesibėgtina\u0300', + sgGen: 'nesibėgtino\u0303s', + sgDat: 'nesibė\u0301gtinai', + sgAcc: 'nesibė\u0301gtiną', + sgInst: 'nesibė\u0301gtina', + sgLoc: 'nesibėgtinoje\u0300 nesibėgtino\u0303j', + sgVoc: 'nesibėgtina\u0300', + plNom: 'nesibė\u0301gtinos', + plGen: 'nesibėgtinų\u0303', + plDat: 'nesibėgtino\u0301ms nesibėgtino\u0301m', + plAcc: 'nesibė\u0301gtinas', + plInst: 'nesibėgtinomi\u0300s nesibėgtino\u0303m', + plLoc: 'nesibėgtinose\u0300', + plVoc: 'nesibė\u0301gtinos', +} +const NESIBEGTINOJI = { + sgNom: 'nesibėgtino\u0301ji', + sgGen: 'nesibėgtino\u0303sios', + sgDat: 'nesibė\u0301gtinajai', + sgAcc: 'nesibė\u0301gtinąją', + sgInst: 'nesibėgtiną\u0301ja', + sgLoc: 'nesibėgtino\u0303joje nesibėgtino\u0303joj', + sgVoc: 'nesibėgtino\u0301ji', + plNom: 'nesibė\u0301gtinosios', + plGen: 'nesibėgtinų\u0303jų', + plDat: 'nesibėgtino\u0301sioms nesibėgtino\u0301siom', + plAcc: 'nesibėgtiną\u0301sias', + plInst: 'nesibėgtino\u0303siomis nesibėgtino\u0303siom', + plLoc: 'nesibėgtino\u0303siose', + plVoc: 'nesibė\u0301gtinosios', +} + +const BAUSTINAS = { + sgNom: 'bau\u0303stinas', + sgGen: 'bau\u0303stino', + sgDat: 'baustina\u0301m', + sgAcc: 'bau\u0303stiną', + sgInst: 'bau\u0303stinu', + sgLoc: 'baustiname\u0300 baustinam\u0303', + sgVoc: 'bau\u0303stinas', + plNom: 'baustini\u0300', + plGen: 'baustinų\u0303', + plDat: 'baustini\u0301ems baustini\u0301em', + plAcc: 'bau\u0303stinus', + plInst: 'baustinai\u0303s', + plLoc: 'baustinuose\u0300 baustinuo\u0303s', + plVoc: 'baustini\u0300', +} +const BAUSTINASIS = { + sgNom: 'baustina\u0300sis', + sgGen: 'bau\u0303stinojo', + sgDat: 'baustina\u0301jam', + sgAcc: 'bau\u0303stinąjį', + sgInst: 'baustinu\u0301oju', + sgLoc: 'baustina\u0303jame baustina\u0303jam', + sgVoc: 'baustina\u0300sis', + plNom: 'baustini\u0301eji', + plGen: 'baustinų\u0303jų', + plDat: 'baustini\u0301esiems baustini\u0301esiem', + plAcc: 'baustinu\u0301osius', + plInst: 'baustinai\u0303siais', + plLoc: 'baustinuo\u0303siuose baustinuo\u0303siuos', + plVoc: 'baustini\u0301eji', +} +const BAUSTINA = { + sgNom: 'baustina\u0300', + sgGen: 'baustino\u0303s', + sgDat: 'bau\u0303stinai', + sgAcc: 'bau\u0303stiną', + sgInst: 'bau\u0303stina', + sgLoc: 'baustinoje\u0300 baustino\u0303j', + sgVoc: 'baustina\u0300', + plNom: 'bau\u0303stinos', + plGen: 'baustinų\u0303', + plDat: 'baustino\u0301ms baustino\u0301m', + plAcc: 'bau\u0303stinas', + plInst: 'baustinomi\u0300s baustino\u0303m', + plLoc: 'baustinose\u0300', + plVoc: 'bau\u0303stinos', +} +const BAUSTINOJI = { + sgNom: 'baustino\u0301ji', + sgGen: 'baustino\u0303sios', + sgDat: 'bau\u0303stinajai', + sgAcc: 'bau\u0303stinąją', + sgInst: 'baustiną\u0301ja', + sgLoc: 'baustino\u0303joje baustino\u0303joj', + sgVoc: 'baustino\u0301ji', + plNom: 'bau\u0303stinosios', + plGen: 'baustinų\u0303jų', + plDat: 'baustino\u0301sioms baustino\u0301siom', + plAcc: 'baustiną\u0301sias', + plInst: 'baustino\u0303siomis baustino\u0303siom', + plLoc: 'baustino\u0303siose', + plVoc: 'bau\u0303stinosios', +} +const NESIBAUSTINAS = { + sgNom: 'nesibau\u0303stinas', + sgGen: 'nesibau\u0303stino', + sgDat: 'nesibaustina\u0301m', + sgAcc: 'nesibau\u0303stiną', + sgInst: 'nesibau\u0303stinu', + sgLoc: 'nesibaustiname\u0300 nesibaustinam\u0303', + sgVoc: 'nesibau\u0303stinas', + plNom: 'nesibaustini\u0300', + plGen: 'nesibaustinų\u0303', + plDat: 'nesibaustini\u0301ems nesibaustini\u0301em', + plAcc: 'nesibau\u0303stinus', + plInst: 'nesibaustinai\u0303s', + plLoc: 'nesibaustinuose\u0300 nesibaustinuo\u0303s', + plVoc: 'nesibaustini\u0300', +} +const NESIBAUSTINASIS = { + sgNom: 'nesibaustina\u0300sis', + sgGen: 'nesibau\u0303stinojo', + sgDat: 'nesibaustina\u0301jam', + sgAcc: 'nesibau\u0303stinąjį', + sgInst: 'nesibaustinu\u0301oju', + sgLoc: 'nesibaustina\u0303jame nesibaustina\u0303jam', + sgVoc: 'nesibaustina\u0300sis', + plNom: 'nesibaustini\u0301eji', + plGen: 'nesibaustinų\u0303jų', + plDat: 'nesibaustini\u0301esiems nesibaustini\u0301esiem', + plAcc: 'nesibaustinu\u0301osius', + plInst: 'nesibaustinai\u0303siais', + plLoc: 'nesibaustinuo\u0303siuose nesibaustinuo\u0303siuos', + plVoc: 'nesibaustini\u0301eji', +} +const NESIBAUSTINA = { + sgNom: 'nesibaustina\u0300', + sgGen: 'nesibaustino\u0303s', + sgDat: 'nesibau\u0303stinai', + sgAcc: 'nesibau\u0303stiną', + sgInst: 'nesibau\u0303stina', + sgLoc: 'nesibaustinoje\u0300 nesibaustino\u0303j', + sgVoc: 'nesibaustina\u0300', + plNom: 'nesibau\u0303stinos', + plGen: 'nesibaustinų\u0303', + plDat: 'nesibaustino\u0301ms nesibaustino\u0301m', + plAcc: 'nesibau\u0303stinas', + plInst: 'nesibaustinomi\u0300s nesibaustino\u0303m', + plLoc: 'nesibaustinose\u0300', + plVoc: 'nesibau\u0303stinos', +} +const NESIBAUSTINOJI = { + sgNom: 'nesibaustino\u0301ji', + sgGen: 'nesibaustino\u0303sios', + sgDat: 'nesibau\u0303stinajai', + sgAcc: 'nesibau\u0303stinąją', + sgInst: 'nesibaustiną\u0301ja', + sgLoc: 'nesibaustino\u0303joje nesibaustino\u0303joj', + sgVoc: 'nesibaustino\u0301ji', + plNom: 'nesibau\u0303stinosios', + plGen: 'nesibaustinų\u0303jų', + plDat: 'nesibaustino\u0301sioms nesibaustino\u0301siom', + plAcc: 'nesibaustiną\u0301sias', + plInst: 'nesibaustino\u0303siomis nesibaustino\u0303siom', + plLoc: 'nesibaustino\u0303siose', + plVoc: 'nesibau\u0303stinosios', +} + +const DAINUOTINAS = { + sgNom: `dainu\u0301otinas`, + sgGen: `dainu\u0301otino`, + sgDat: `dainu\u0301otinam`, + sgAcc: `dainu\u0301otiną`, + sgInst: `dainu\u0301otinu`, + sgLoc: `dainu\u0301otiname dainu\u0301otinam`, + sgVoc: `dainu\u0301otinas`, + plNom: `dainu\u0301otini`, + plGen: `dainu\u0301otinų`, + plDat: `dainu\u0301otiniems dainu\u0301otiniem`, + plAcc: `dainu\u0301otinus`, + plInst: `dainu\u0301otinais`, + plLoc: `dainu\u0301otinuose dainu\u0301otinuos`, + plVoc: `dainu\u0301otini`, +} +const DAINUOTINASIS = { + sgNom: `dainu\u0301otinasis`, + sgGen: `dainu\u0301otinojo`, + sgDat: `dainu\u0301otinajam`, + sgAcc: `dainu\u0301otinąjį`, + sgInst: `dainu\u0301otinuoju`, + sgLoc: `dainu\u0301otinajame dainu\u0301otinajam`, + sgVoc: `dainu\u0301otinasis`, + plNom: `dainu\u0301otinieji`, + plGen: `dainu\u0301otinųjų`, + plDat: `dainu\u0301otiniesiems dainu\u0301otiniesiem`, + plAcc: `dainu\u0301otinuosius`, + plInst: `dainu\u0301otinaisiais`, + plLoc: `dainu\u0301otinuosiuose dainu\u0301otinuosiuos`, + plVoc: `dainu\u0301otinieji`, +} +const DAINUOTINA = { + sgNom: `dainu\u0301otina`, + sgGen: `dainu\u0301otinos`, + sgDat: `dainu\u0301otinai`, + sgAcc: `dainu\u0301otiną`, + sgInst: `dainu\u0301otina`, + sgLoc: `dainu\u0301otinoje dainu\u0301otinoj`, + sgVoc: `dainu\u0301otina`, + plNom: `dainu\u0301otinos`, + plGen: `dainu\u0301otinų`, + plDat: `dainu\u0301otinoms dainu\u0301otinom`, + plAcc: `dainu\u0301otinas`, + plInst: `dainu\u0301otinomis dainu\u0301otinom`, + plLoc: `dainu\u0301otinose`, + plVoc: `dainu\u0301otinos`, +} +const DAINUOTINOJI = { + sgNom: `dainu\u0301otinoji`, + sgGen: `dainu\u0301otinosios`, + sgDat: `dainu\u0301otinajai`, + sgAcc: `dainu\u0301otinąją`, + sgInst: `dainu\u0301otinąja`, + sgLoc: `dainu\u0301otinojoje dainu\u0301otinojoj`, + sgVoc: `dainu\u0301otinoji`, + plNom: `dainu\u0301otinosios`, + plGen: `dainu\u0301otinųjų`, + plDat: `dainu\u0301otinosioms dainu\u0301otinosiom`, + plAcc: `dainu\u0301otinąsias`, + plInst: `dainu\u0301otinosiomis dainu\u0301otinosiom`, + plLoc: `dainu\u0301otinosiose`, + plVoc: `dainu\u0301otinosios`, +} +const NESIDAINUOTINAS = { + sgNom: `nesidainu\u0301otinas`, + sgGen: `nesidainu\u0301otino`, + sgDat: `nesidainu\u0301otinam`, + sgAcc: `nesidainu\u0301otiną`, + sgInst: `nesidainu\u0301otinu`, + sgLoc: `nesidainu\u0301otiname nesidainu\u0301otinam`, + sgVoc: `nesidainu\u0301otinas`, + plNom: `nesidainu\u0301otini`, + plGen: `nesidainu\u0301otinų`, + plDat: `nesidainu\u0301otiniems nesidainu\u0301otiniem`, + plAcc: `nesidainu\u0301otinus`, + plInst: `nesidainu\u0301otinais`, + plLoc: `nesidainu\u0301otinuose nesidainu\u0301otinuos`, + plVoc: `nesidainu\u0301otini`, +} +const NESIDAINUOTINASIS = { + sgNom: `nesidainu\u0301otinasis`, + sgGen: `nesidainu\u0301otinojo`, + sgDat: `nesidainu\u0301otinajam`, + sgAcc: `nesidainu\u0301otinąjį`, + sgInst: `nesidainu\u0301otinuoju`, + sgLoc: `nesidainu\u0301otinajame nesidainu\u0301otinajam`, + sgVoc: `nesidainu\u0301otinasis`, + plNom: `nesidainu\u0301otinieji`, + plGen: `nesidainu\u0301otinųjų`, + plDat: `nesidainu\u0301otiniesiems nesidainu\u0301otiniesiem`, + plAcc: `nesidainu\u0301otinuosius`, + plInst: `nesidainu\u0301otinaisiais`, + plLoc: `nesidainu\u0301otinuosiuose nesidainu\u0301otinuosiuos`, + plVoc: `nesidainu\u0301otinieji`, +} +const NESIDAINUOTINA = { + sgNom: `nesidainu\u0301otina`, + sgGen: `nesidainu\u0301otinos`, + sgDat: `nesidainu\u0301otinai`, + sgAcc: `nesidainu\u0301otiną`, + sgInst: `nesidainu\u0301otina`, + sgLoc: `nesidainu\u0301otinoje nesidainu\u0301otinoj`, + sgVoc: `nesidainu\u0301otina`, + plNom: `nesidainu\u0301otinos`, + plGen: `nesidainu\u0301otinų`, + plDat: `nesidainu\u0301otinoms nesidainu\u0301otinom`, + plAcc: `nesidainu\u0301otinas`, + plInst: `nesidainu\u0301otinomis nesidainu\u0301otinom`, + plLoc: `nesidainu\u0301otinose`, + plVoc: `nesidainu\u0301otinos`, +} +const NESIDAINUOTINOJI = { + sgNom: `nesidainu\u0301otinoji`, + sgGen: `nesidainu\u0301otinosios`, + sgDat: `nesidainu\u0301otinajai`, + sgAcc: `nesidainu\u0301otinąją`, + sgInst: `nesidainu\u0301otinąja`, + sgLoc: `nesidainu\u0301otinojoje nesidainu\u0301otinojoj`, + sgVoc: `nesidainu\u0301otinoji`, + plNom: `nesidainu\u0301otinosios`, + plGen: `nesidainu\u0301otinųjų`, + plDat: `nesidainu\u0301otinosioms nesidainu\u0301otinosiom`, + plAcc: `nesidainu\u0301otinąsias`, + plInst: `nesidainu\u0301otinosiomis nesidainu\u0301otinosiom`, + plLoc: `nesidainu\u0301otinosiose`, + plVoc: `nesidainu\u0301otinosios`, +} + +describe('NecessityParticipleDecliner', () => { + const decliner = new NecessityParticipleDecliner() + describe('bėgti', () => { + it('conjugates default', () => { + expect(decliner.getDefault(BEGTI)).toMatchObject({ + masculine: BEGTINAS, + feminine: BEGTINA, + neuter: `bė\u0301gtina`, + }) + }) + it('conjugates pronominal', () => { + expect(decliner.getPronominal(BEGTI)).toMatchObject({ + masculine: BEGTINASIS, + feminine: BEGTINOJI, + }) + }) + it('conjugates reflexive', () => { + expect(decliner.getReflexive(BEGTI)).toMatchObject({ + masculine: NOMINAL_EMPTY, + feminine: NOMINAL_EMPTY, + neuter: `bė\u0301gtinasi`, + }) + }) + it('conjugates reflexive pronominal', () => { + expect(decliner.getReflexivePronominal(BEGTI)).toMatchObject({ + masculine: NOMINAL_EMPTY, + feminine: NOMINAL_EMPTY, + }) + }) + }) + describe('nesibėgti', () => { + it('conjugates reflexive', () => { + expect(decliner.getPrefixedReflexive(BEGTI, 'ne')).toMatchObject({ + masculine: NESIBEGTINAS, + feminine: NESIBEGTINA, + neuter: `nesibė\u0301gtina`, + }) + expect(decliner.getPrefixed(BEGTI, 'nesi')).toMatchObject({ + masculine: NESIBEGTINAS, + feminine: NESIBEGTINA, + neuter: `nesibė\u0301gtina`, + }) + }) + it('conjugates reflexive pronominal', () => { + expect(decliner.getPrefixedReflexivePronominal(BEGTI, 'ne')) + .toMatchObject({ + masculine: NESIBEGTINASIS, + feminine: NESIBEGTINOJI, + }) + }) + }) + describe('dainuoti', () => { + it('conjugates default', () => { + expect(decliner.getDefault(DAINUOTI)).toMatchObject({ + masculine: DAINUOTINAS, + feminine: DAINUOTINA, + neuter: `dainu\u0301otina`, + }) + }) + it('conjugates pronominal', () => { + expect(decliner.getPronominal(DAINUOTI)).toMatchObject({ + masculine: DAINUOTINASIS, + feminine: DAINUOTINOJI, + }) + }) + it('conjugates reflexive', () => { + expect(decliner.getReflexive(DAINUOTI)).toMatchObject({ + masculine: NOMINAL_EMPTY, + feminine: NOMINAL_EMPTY, + neuter: `dainu\u0301otinasi`, + }) + }) + it('conjugates reflexive pronominal', () => { + expect(decliner.getReflexivePronominal(DAINUOTI)).toMatchObject({ + masculine: NOMINAL_EMPTY, + feminine: NOMINAL_EMPTY, + }) + }) + }) + describe('nesidainuoti', () => { + it('conjugates reflexive', () => { + expect(decliner.getPrefixedReflexive(DAINUOTI, 'ne')).toMatchObject({ + masculine: NESIDAINUOTINAS, + feminine: NESIDAINUOTINA, + neuter: `nesidainu\u0301otina`, + }) + expect(decliner.getPrefixed(DAINUOTI, 'nesi')).toMatchObject({ + masculine: NESIDAINUOTINAS, + feminine: NESIDAINUOTINA, + neuter: `nesidainu\u0301otina`, + }) + }) + it('conjugates reflexive pronominal', () => { + expect(decliner.getPrefixedReflexivePronominal(DAINUOTI, 'ne')) + .toMatchObject({ + masculine: NESIDAINUOTINASIS, + feminine: NESIDAINUOTINOJI, + }) + }) + }) + describe('bausti', () => { + it('conjugates default', () => { + expect(decliner.getDefault(BAUSTI)).toMatchObject({ + masculine: BAUSTINAS, + feminine: BAUSTINA, + neuter: `bau\u0303stina`, + }) + }) + it('conjugates pronominal', () => { + expect(decliner.getPronominal(BAUSTI)).toMatchObject({ + masculine: BAUSTINASIS, + feminine: BAUSTINOJI, + }) + }) + it('conjugates reflexive', () => { + expect(decliner.getReflexive(BAUSTI)).toMatchObject({ + masculine: NOMINAL_EMPTY, + feminine: NOMINAL_EMPTY, + neuter: `bau\u0303stinasi`, + }) + }) + it('conjugates reflexive pronominal', () => { + expect(decliner.getReflexivePronominal(BAUSTI)).toMatchObject({ + masculine: NOMINAL_EMPTY, + feminine: NOMINAL_EMPTY, + }) + }) + }) + describe('nesibausti', () => { + it('conjugates reflexive', () => { + expect(decliner.getPrefixedReflexive(BAUSTI, 'ne')).toMatchObject({ + masculine: NESIBAUSTINAS, + feminine: NESIBAUSTINA, + neuter: `nesibau\u0303stina`, + }) + expect(decliner.getPrefixed(BAUSTI, 'nesi')).toMatchObject({ + masculine: NESIBAUSTINAS, + feminine: NESIBAUSTINA, + neuter: `nesibau\u0303stina`, + }) + }) + it('conjugates reflexive pronominal', () => { + expect(decliner.getPrefixedReflexivePronominal(BAUSTI, 'ne')) + .toMatchObject({ + masculine: NESIBAUSTINASIS, + feminine: NESIBAUSTINOJI, + }) + }) + }) +}) From 5149d0e387602eaa55fc6e8a4074d96f329ef159 Mon Sep 17 00:00:00 2001 From: Andrius Simanaitis Date: Fri, 24 Apr 2026 10:00:00 +0300 Subject: [PATCH 12/12] update some documentation --- readme.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 6019d8b..e587cad 100644 --- a/readme.md +++ b/readme.md @@ -18,7 +18,7 @@ endpoint shall be made public in the near future as well. ## Release notes -There is version 0.0.1 release which features Verb conjugation that includes +There is version 0.0.5 release which features Verb conjugation that includes inflection of the following: - infinitive @@ -30,12 +30,13 @@ inflection of the following: - pusdalyvis (-dam-) - -imas deverbial noun - būdinys (-te) +- necessity participle (-tin-) all of them can be inflected for reflexivness, carry prefixes; the adjectival participles can also be pronominal. > [!NOTE]\ -> As this is version 0.0.1 nothing is yet set in stone and things may change! +> As this is version 0.0.5 nothing is yet set in stone and things may change! ---