Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
035c005
release version 0.0.5
Voldemortas Apr 13, 2026
9d3597b
add normaliseAccents (#3)
Voldemortas Apr 13, 2026
daf8cb7
fix dot diacritic on i
Voldemortas Apr 13, 2026
6a45026
Merge pull request #4 from Voldemortas/fix/normaliseDiacritics
Voldemortas Apr 13, 2026
6fffc56
fix present indicative tense prefix not getting stress
Voldemortas Apr 22, 2026
08468f4
Merge pull request #5 from Voldemortas/fix/presentTensePrefix
Voldemortas Apr 22, 2026
8732768
fix acute verbs retracting stress to prefix in past passive participles
Voldemortas Apr 23, 2026
778ea03
fix circumflex prefix in past passive feminine participles
Voldemortas Apr 23, 2026
5f6b821
Merge pull request #6 from Voldemortas/fix/pastPassivePrefixes
Voldemortas Apr 23, 2026
f07e7ff
fix locative circumflex being `oj\u0303` instead of `o\u0303j`
Voldemortas Apr 23, 2026
297ddfc
Merge pull request #7 from Voldemortas/fix/locative-circumflex
Voldemortas Apr 23, 2026
c0f1843
fix PassiveFutureParticipleDecliner
Voldemortas Apr 23, 2026
e082540
Merge pull request #8 from Voldemortas/fix/passivePassiveFuturePartic…
Voldemortas Apr 23, 2026
2b14551
fix PassivePresentParticipleDecliner
Voldemortas Apr 23, 2026
e449792
Merge pull request #9 from Voldemortas/fix/passivePassivePresentParti…
Voldemortas Apr 23, 2026
f90494d
fix past passive participles having short stress in roots when the en…
Voldemortas Apr 24, 2026
4db46ab
add necessity participle
Voldemortas Apr 24, 2026
f6cd645
Merge pull request #10 from Voldemortas/feat/pastPassiveShort
Voldemortas Apr 24, 2026
981d011
Merge pull request #11 from Voldemortas/feat/necessityParticiple
Voldemortas Apr 24, 2026
5149d0e
update some documentation
Voldemortas Apr 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@voldemortas/flection",
"version": "0.0.4",
"version": "0.0.5",
"exports": "./src/index.ts",
"tasks": {
"dev": "deno test --watch"
Expand All @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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!

---

Expand Down
19 changes: 19 additions & 0 deletions src/Verb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -83,6 +84,8 @@ export default class Verb extends Verbal {
public static readonly presentPadalyvis: InflectorInterface<PadalyvisType> =
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
Expand Down Expand Up @@ -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<string, string | Record<string, string>>,
Expand Down
146 changes: 146 additions & 0 deletions src/flectors/conjugators/NecessityParticipleDecliner.ts
Original file line number Diff line number Diff line change
@@ -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,
}
}
101 changes: 69 additions & 32 deletions src/flectors/conjugators/PassiveFutureParticipleDecliner.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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 {
Expand All @@ -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,
Expand All @@ -40,29 +51,33 @@ export default class PassiveFutureParticipleDecliner
}

return getBasicInflected(
principalParts.map((principalPart) =>
`${prefix}${principalPart}`
principalParts.map((part) =>
`${prefix}${PREFIX_SEPARATOR}${part}`
) as PrincipalPartsType,
)
}

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,
Expand All @@ -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,
Expand All @@ -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,
}
}
Loading