From c9e3d13c4302a7e4289e956e268380ab3fbf3e87 Mon Sep 17 00:00:00 2001 From: adueck Date: Fri, 31 Mar 2023 17:45:06 +0400 Subject: [PATCH] lots more towards the new more correct and concise verb conjugator --- src/App.tsx | 11 +- src/components/src/Examples.tsx | 1 - src/demo-components/VPBuilderDemo.tsx | 61 +++ src/lib/library.ts | 4 + src/lib/src/grammar-units.ts | 16 +- src/lib/src/misc-helpers.ts | 14 + src/lib/src/misc-text.ts | 3 + src/lib/src/p-text-helpers.ts | 10 +- src/lib/src/phrase-building/vp-tools.ts | 16 + src/lib/src/render-verb.ts | 483 +++++++++++++++++++++--- src/lib/src/type-predicates.ts | 5 + src/types.ts | 4 +- 12 files changed, 549 insertions(+), 79 deletions(-) create mode 100644 src/lib/src/misc-text.ts diff --git a/src/App.tsx b/src/App.tsx index 24d70ba..64a939e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,7 +21,6 @@ import { entryFeeder } from "./demo-components/entryFeeder"; import { Hider } from "./components/library"; import InflectionDemo from "./demo-components/InflectionDemo"; import SpellingDemo from "./demo-components/SpellingDemo"; -import { renderVerb } from "./lib/src/render-verb"; function App() { const [showingTextOptions, setShowingTextOptions] = useStickyState(false, "showTextOpts1"); @@ -36,17 +35,9 @@ function App() { useEffect(() => { document.documentElement.setAttribute("data-theme", theme); }, [theme]); - const rv = renderVerb({ - verb: { entry: {"ts":1527815399,"i":15035,"p":"وهل","f":"wahul","g":"wahul","e":"to hit","r":4,"c":"v. trans.","tppp":"واهه","tppf":"waahu","ec":"hit,hits,hitting,hit,hit"} as T.VerbDictionaryEntry}, - aspect: "imperfective", - tense: "habitualPast", - person: 9, - }); + return <>
-
-                {JSON.stringify(rv, null, "  ")}
-            
(
diff --git a/src/demo-components/VPBuilderDemo.tsx b/src/demo-components/VPBuilderDemo.tsx index 2c565ea..c656bc9 100644 --- a/src/demo-components/VPBuilderDemo.tsx +++ b/src/demo-components/VPBuilderDemo.tsx @@ -12,6 +12,8 @@ import { randomNumber, } from "../lib/src/misc-helpers"; import { entryFeeder } from "./entryFeeder"; +import { renderVerb } from "../lib/src/render-verb"; +import NPPronounPicker from "../components/src/np-picker/NPPronounPicker"; const transitivities: T.Transitivity[] = [ @@ -32,12 +34,46 @@ const verbTypes: VerbType[] = [ "dynamic compound", ]; +const testVerbTenses: T.VerbTense[] = [ + "presentVerb", + "subjunctiveVerb", + "imperfectiveFuture", + "perfectiveFuture", + "imperfectivePast", + "perfectivePast", + "habitualImperfectivePast", + "habitualPerfectivePast", +]; + +const testPerfectTenses: T.PerfectTense[] = [ + "presentPerfect", + "pastPerfect", + "subjunctivePerfect", + "wouldBePerfect", + "wouldHaveBeenPerfect", +]; + +const testAbilityTenses: T.ModalTense[] = testVerbTenses.map(t => `${t}Modal`); + +const testTenses = [ + ...testVerbTenses, + ...testPerfectTenses, + ...testAbilityTenses, +]; + function VPBuilderDemo({ opts }: { opts: T.TextOptions, }) { const [verbTs, setVerbTs] = useStickyState(0, "verbTs1"); const [verbTypeShowing, setVerbTypeShowing] = useStickyState("simple", "vTypeShowing"); const [transitivityShowing, setTransitivityShowing] = useStickyState("intransitive", "transitivityShowing1"); + const [testPerson, setTestPerson] = useStickyState({ + type: "pronoun", + distance: "far", + person: 0, + }, "testPronoun"); + const [testVoice, setTestVoice] = useStickyState("active", "testVoice"); + const [testTense, setTestTense] = useStickyState("presentVerb", "testTense"); // const onlyGrammTrans = (arr: Transitivity[]) => ( // arr.length === 1 && arr[0] === "grammatically transitive" // ); @@ -105,6 +141,14 @@ function VPBuilderDemo({ opts }: { const makeVerbLabel = (entry: T.DictionaryEntry): string => ( `${entry.p} - ${clamp(entry.e, 20)}` ); + const rv = v ? renderVerb({ + // verb: { entry: {"ts":1527815399,"i":15035,"p":"وهل","f":"wahul","g":"wahul","e":"to hit","r":4,"c":"v. trans.","tppp":"واهه","tppf":"waahu","ec":"hit,hits,hitting,hit,hit"} as T.VerbDictionaryEntry}, + // verb: { entry: {"ts":1527814596,"i":8648,"p":"شرمول","f":"shărmawul","g":"sharmawul","e":"to shame, to disgrace, to dishonor, to embarrass","r":4,"c":"v. trans.","ec":"embarrass"} as T.VerbDictionaryEntry }, + verb: v.verb as T.VerbEntry, + tense: testTense, + person: testPerson.person, + voice: testVoice, + }) : undefined; return
@@ -189,6 +233,23 @@ function VPBuilderDemo({ opts }: {
+ + + +
+            {JSON.stringify(rv, null, "  ")}
+        
{v?.verb.entry &&
(x: T.SingleOrLengthOpts, f: (y: U) => F): T.SingleOrLengthOpts { + if ("long" in x) { + return { + long: f(x.long), + short: f(x.short), + ...("mini" in x && x.mini) ? { + mini: f(x.mini), + } : {}, + }; + } + return f(x); +} + +// TODO: deprecated using new verb rendering thing export function chooseParticipleInflection( pPartInfs: T.SingleOrLengthOpts | T.SingleOrLengthOpts, person: T.Person, diff --git a/src/lib/src/misc-text.ts b/src/lib/src/misc-text.ts new file mode 100644 index 0000000..fba366f --- /dev/null +++ b/src/lib/src/misc-text.ts @@ -0,0 +1,3 @@ +import { PsString } from "../../types"; + +export const joiningTails: PsString = { p: "ـ", f: "–"}; \ No newline at end of file diff --git a/src/lib/src/p-text-helpers.ts b/src/lib/src/p-text-helpers.ts index 0ecff65..0026213 100644 --- a/src/lib/src/p-text-helpers.ts +++ b/src/lib/src/p-text-helpers.ts @@ -451,7 +451,7 @@ export function concatInflections( comp: T.PsString | T.SingleOrLengthOpts, infs: T.SingleOrLengthOpts ): T.SingleOrLengthOpts { const containsLengthOptions = "long" in infs || "long" in comp; - const ensureL = (x: T.SingleOrLengthOpts, length: "short" | "long"): T => ( + const ensureL = (x: T.SingleOrLengthOpts, length: "short" | "long"): T => ( ("long" in x) ? x[length] : x ); if (containsLengthOptions) { @@ -509,7 +509,7 @@ export function allOnePersonInflection( return block; } -export function choosePersInf(x: T.FullForm, persInf: T.PersonInflectionsField): T.SingleOrLengthOpts { +export function choosePersInf(x: T.FullForm, persInf: T.PersonInflectionsField): T.SingleOrLengthOpts { if ("mascSing" in x) { return x[persInf]; } @@ -999,7 +999,7 @@ export function psStringFromEntry(entry: T.PsString): T.PsString { }; } -export function getLength(x: T.SingleOrLengthOpts, length: "long" | "short" | "mini"): U { +export function getLength(x: T.SingleOrLengthOpts, length: "long" | "short" | "mini"): U { if ("long" in x) { const s = x[length]; return s ? s : x.short; @@ -1007,14 +1007,14 @@ export function getLength(x: T.SingleOrLengthOpts, length: "long" | "short return x; } -export function getLong(x: T.SingleOrLengthOpts): U { +export function getLong(x: T.SingleOrLengthOpts): U { if ("long" in x) { return x.long; } return x; } -export function getShort(a: T.SingleOrLengthOpts): U { +export function getShort(a: T.SingleOrLengthOpts): U { if ("long" in a) { return a.short; } diff --git a/src/lib/src/phrase-building/vp-tools.ts b/src/lib/src/phrase-building/vp-tools.ts index fcfaac4..13ee43a 100644 --- a/src/lib/src/phrase-building/vp-tools.ts +++ b/src/lib/src/phrase-building/vp-tools.ts @@ -198,6 +198,22 @@ export function isPastTense(tense: T.Tense): boolean { return tense.toLowerCase().includes("past"); } +export function tenseHasBa(tense: T.VerbTense | T.PerfectTense | T.ModalTense | T.ImperativeTense): boolean { + return [ + "imperfectiveFuture", + "perfectiveFuture", + "habitualPerfectivePast", + "habitualImperfectivePast", + "imperfectiveFutureModal", + "perfectiveFutureModal", + "habitualPerfectivePastModal", + "habitualImperfectivePastModal", + "futurePerfect", + "wouldBePerfect", + "wouldBeHaveBeenPerfect", + ].includes(tense); +} + export function removeDuplicates(psv: T.PsString[]): T.PsString[] { return psv.filter((ps, i, arr) => ( i === arr.findIndex(t => ( diff --git a/src/lib/src/render-verb.ts b/src/lib/src/render-verb.ts index 6041786..466cb61 100644 --- a/src/lib/src/render-verb.ts +++ b/src/lib/src/render-verb.ts @@ -1,13 +1,38 @@ import { + functionOnOptLengths, + getPersonInflectionsKey, getVerbBlockPosFromPerson, noPersInfs, + personGender, + personIsPlural, } from "./misc-helpers"; +import { + yulEndingInfinitive, +} from "./p-text-helpers"; import * as T from "../../types"; -import { concatPsString, getVerbInfo } from "../library"; +import { + concatPsString, + getLength, +} from "./p-text-helpers"; import { presentEndings, pastEndings, + equativeEndings, } from "./grammar-units"; +import { isKawulVerb, isModalTense, isPerfectTense, isTlulVerb } from "./type-predicates"; +import { tenseHasBa } from "./phrase-building/vp-tools"; +import { inflectYey } from "./pashto-inflector"; +import { + getVerbInfo, +} from "./verb-info"; +import { isPastTense } from "./phrase-building/vp-tools"; +import { makePsString, removeFVarients } from "./accent-and-ps-utils"; +import { pashtoConsonants } from "./pashto-consonants"; +import { accentOnNFromEnd, removeAccents } from "./accent-helpers"; + +const kedulStatVerb: T.VerbEntry = { + entry: {"ts":1581086654898,"i":11100,"p":"کېدل","f":"kedul","g":"kedul","e":"to become _____","r":2,"c":"v. intrans.","ssp":"ش","ssf":"sh","prp":"شول","prf":"shwul","pprtp":"شوی","pprtf":"shúwey","noOo":true,"ec":"become"} as T.VerbDictionaryEntry, +}; // export type RenderedVerbB = VerbRenderedBlock // | PerfectiveHeadBlock @@ -27,47 +52,111 @@ import { // }, // }; -type PerfectiveHeadBlock = { - type: "perfectiveHead", +// TODO the welded block with passive is the same as the stative compounds + +type VB = PH | VA | VPlain | PT | EQ | Welded; + +type PH = { + type: "perfectiveHeadBlock", ps: T.PsString, }; -type VerbBlock = { - type: "verb", - hasBa: boolean, +type VA = { + type: "verbBlockWithAgreement", ps: T.SingleOrLengthOpts, person: T.Person, - aspect: T.Aspect, - tense: keyof T.AspectContent, }; -export function renderVerb({ verb, aspect, tense, person }: { - verb: T.VerbEntry, - aspect: T.Aspect, - tense: keyof T.AspectContent, +type VPlain = { + type: "verbBlockWithoutAgreement", + ps: T.SingleOrLengthOpts, +}; + +type PT = { + type: "participleBlock", + ps: T.SingleOrLengthOpts, + inflection: T.PersonInflectionsField, +}; + +type EQ = { + type: "equativeBlock", + ps: T.SingleOrLengthOpts, person: T.Person, +}; + +type Welded = { + type: "weldedBlock", + left: VPlain, // TODO - will get more complex with compounds + right: VA | PT | VPlain, +} + +// TODO: problem with laaR - no perfective split + +export function renderVerb({ verb, tense, person, voice }: { + verb: T.VerbEntry, + tense: T.VerbTense | T.PerfectTense | T.ModalTense, // TODO: make T.Tense + person: T.Person, + voice: T.Voice, }): { hasBa: boolean, - verbBlocks: [PerfectiveHeadBlock, VerbBlock] | [VerbBlock] + verbBlocks: VB[], } { // WARNING: this only works with simple verbs - const isPast = tense === "past" || tense === "habitualPast"; - const hasBa = tense === "future" || tense === "habitualPast"; - const { perfectiveHead, rootStem } = getRootStem(verb, aspect, isPast); - const ending = getEnding(person, isPast); - const verbPs = addEnding(rootStem, ending); - const verbBlock: VerbBlock = { - type: "verb", - hasBa, - ps: verbPs, - person, - aspect, - tense, - }; - const perfectiveHeadBlock: PerfectiveHeadBlock | undefined = perfectiveHead ? { - type: "perfectiveHead", - ps: noPersInfs(perfectiveHead), + const hasBa = tenseHasBa(tense); + if (isPerfectTense(tense)) { + return { + hasBa, + verbBlocks: getPerfectBlocks({ verb, tense, person, voice }), + }; + } + const isPast = isPastTense(tense); + const aspect = getAspect(tense); + const isAbility = isModalTense(tense); + const { perfectiveHead, rootStem } = getRootStem({ + verb, aspect, isPast, isAbility, person, voice + }); + const perfectiveHeadBlock: PH | undefined = perfectiveHead ? { + type: "perfectiveHeadBlock", + // should only need this for tlul and Daaredul? + ps: fromPersInfls(perfectiveHead, person), } : undefined; + // if (voice === "passive") { + // const kedulPart = getPassiveVerbBlocks(tense, person, aspect, rootStem); + // return { + // hasBa, + // // @ts-ignore + // verbBlocks: perfectiveHeadBlock + // ? [perfectiveHeadBlock, kedulPart] + // : [kedulPart], + // } + // } + const ending = getEnding(person, isPast); + if (isAbility) { + const [vb, shPart] = getAbilityVerbBlocks({ verb, isPast, person, rootStem, aspect, voice }); + return { + hasBa, + verbBlocks: perfectiveHeadBlock + ? [perfectiveHeadBlock, vb, shPart] + : [vb, shPart], + }; + } + if (voice === "passive") { + const vbs = getPassiveVerbBlocks({ root: rootStem, tense, person }); + return { + hasBa, + verbBlocks: [ + ...perfectiveHeadBlock ? [perfectiveHeadBlock] : [], + vbs, + ], + }; + } + const verbBlock: VA = { + type: "verbBlockWithAgreement", + ps: addEnding({ + rootStem, ending, person, isPast, verb, aspect, + }), + person, + }; return { hasBa, verbBlocks: perfectiveHeadBlock ? [ @@ -76,45 +165,151 @@ export function renderVerb({ verb, aspect, tense, person }: { } } -function addEnding(rootStem: T.FullForm, ending: T.SingleOrLengthOpts): T.SingleOrLengthOpts { - const rs = noPersInfs(rootStem); - const end = noPersInfs(ending); - if ("long" in rs) { - if ("long" in end) { - return { - long: end.long.map((e) => concatPsString(rs.long, e)), - short: end.short.map((e) => concatPsString(rs.short, e)), - }; - } else { - return { - long: end.map((e) => concatPsString(rs.long, e)), - short: end.map((e) => concatPsString(rs.short, e)), - }; - } +function getPassiveVerbBlocks({ root, tense, person }: { + root: T.SingleOrLengthOpts, + tense: T.VerbTense, + person: T.Person, +}): Welded { + if (!("long" in root)) { + throw new Error("should have length versions in roots for passive"); } - if ("long" in end) { - throw new Error("should not be using verb stems with long and short endings"); - } - return end.map((e) => concatPsString(rs, e)); + const { verbBlocks: [auxVerb] } = renderVerb({ + verb: kedulStatVerb, + tense, + person, + voice: "active", + }) as { hasBa: boolean, verbBlocks: [VA] }; + return weld( + { + type: "verbBlockWithoutAgreement", + ps: [root.long], + }, + auxVerb, + ); } -function getRootStem(verb: T.VerbEntry, aspect: T.Aspect, isPast: boolean): { +function getAbilityVerbBlocks({ verb, isPast, person, rootStem, aspect, voice }: { + verb: T.VerbEntry, + isPast: boolean, + person: T.Person, + rootStem: T.SingleOrLengthOpts, + aspect: T.Aspect, + voice: T.Voice, +}): VB[] { + const noPerfective = isTlulVerb(verb) || isKedul(verb); + const shBlock = getAbilityShPart(isPast, person); + // TODO: this is redundant, we did it in another part of the program? + const verbBlock: VPlain = { + type: "verbBlockWithoutAgreement", + ps: addAbilityTailsToRs(rootStem, aspect, noPerfective), + }; + return [verbBlock, shBlock]; + function getAbilityShPart(isPast: boolean, person: T.Person): VA { + // TODO: optimized shortcut version of this + const { verbBlocks: [shBlock] } = renderVerb({ + verb: kedulStatVerb, + tense: isPast ? "perfectivePast" : "subjunctiveVerb", + person, + voice: "active", + }) as { + hasBa: boolean, + verbBlocks: [VA], + }; + return { + type: "verbBlockWithAgreement", + ps: shBlock.ps, + person, + }; + } +} + +function getPerfectBlocks({ verb, tense, person, voice }: { + verb: T.VerbEntry, + tense: T.PerfectTense, + person: T.Person, + voice: T.Voice, +}): VB[] { + const vInfo = getVerbInfo(verb.entry) as T.SimpleVerbInfo; + + // TODO: put this in a seperate function? + + if (voice === "passive") { + const [pt, eq] = getKedulStatPerfect(person, tense); + const passiveRoot: VPlain = { + type: "verbBlockWithoutAgreement", + ps: [noPersInfs(vInfo.root.imperfective).long], + }; + const welded: Welded = weld(passiveRoot, pt); + return [welded, eq]; + } + + const equative = equativeEndings[perfectTenseToEquative(tense)]; + const [row, col] = getVerbBlockPosFromPerson(person); + const equativeBlock: EQ = { + type: "equativeBlock", + person, + ps: "long" in equative ? { + long: equative.long[row][col], + short: equative.short[row][col], + } : equative[row][col], + } + + + + + const participleBlock: PT = { + type: "participleBlock", + inflection: getPersonInflectionsKey(person), + ps: chooseParticipleInflection(inflectYey(noPersInfs(vInfo.participle.past)), person) + } + return [participleBlock, equativeBlock]; +} + +function weld(left: VPlain, right: VA | PT | VPlain): Welded { + return { + type: "weldedBlock", + left: { + ...left, + ps: functionOnOptLengths(left.ps, removeAccents), + }, + right, + }; +} + +function getRootStem({ verb, aspect, isPast, isAbility, voice, person }: { + verb: T.VerbEntry, + aspect: T.Aspect, + isPast: boolean, + isAbility: boolean, + person: T.Person, + voice: T.Voice, +}): { perfectiveHead: undefined | T.OptionalPersonInflections, - rootStem: T.OptionalPersonInflections>, + rootStem: T.SingleOrLengthOpts, } { const vInfo = getVerbInfo(verb.entry) as T.SimpleVerbInfo; - const rs = vInfo[isPast ? "root" : "stem"]; + const noPerfective = isTlulVerb(verb) || isKedul(verb); + const rs = vInfo[(isPast || isAbility || voice === "passive") ? "root" : "stem"]; + if (noPerfective && isAbility) { + // exception with tlul verbs for ability stems + return { + perfectiveHead: undefined, + rootStem: noPersInfs(rs.imperfective), + }; + } if (aspect === "perfective" && rs.perfectiveSplit) { return extractPerfectiveSplit(rs.perfectiveSplit); } return { perfectiveHead: undefined, - rootStem: rs[aspect], + // because the persInfs only happen with stative compound verbs,j + // which we are handling differently now + rootStem: noPersInfs(rs[aspect]), } function extractPerfectiveSplit(splitInfo: T.SplitInfo): ReturnType { - // TODO: allow for infs - const si = noPersInfs(splitInfo); + // this is just for tlul and Daredul ? + const si = fromPersInfls(splitInfo, person); if ("long" in si) { return { perfectiveHead: si.long[0], @@ -132,6 +327,42 @@ function getRootStem(verb: T.VerbEntry, aspect: T.Aspect, isPast: boolean): { } } +function addEnding({ rootStem, ending, person, isPast, verb, aspect }:{ + rootStem: T.SingleOrLengthOpts, + ending: T.SingleOrLengthOpts, + person: T.Person, + isPast: boolean, + verb: T.VerbEntry, + aspect: T.Aspect, +}): T.SingleOrLengthOpts { + // TODO: no need for useless abbreviation now + const rs = rootStem; + const end = ending; + const idiosyncratic3rdPast = isPast && person === T.Person.ThirdSingMale; + const safeEndAdd = (rs: T.PsString) => (ending: T.PsString): T.PsString => ( + (ending.p === "ل" && rs.p.slice(-1) === "ل") + ? rs + : concatPsString(rs, ending) + ); + if ("long" in rs) { + const endLong = getLength(end, "long"); + const endShort = getLength(end, "short"); + const shortForm = idiosyncratic3rdPast + ? ensure3rdPast(endShort, rs.short, verb) + : endShort.map(e => concatPsString(rs.short, e)); + return { + long: endLong.map(safeEndAdd(rs.long)), + short: aspect === "imperfective" + ? applyImperfectiveShortAccent(shortForm, yulEndingInfinitive(removeFVarients(verb.entry))) + : shortForm, + }; + } + if ("long" in end) { + throw new Error("should not be using verb stems with long and short endings"); + } + return end.map((e) => concatPsString(rs, e)); +} + function getEnding(person: T.Person, isPast: boolean) { const [row, col] = getVerbBlockPosFromPerson(person); return isPast ? { @@ -140,3 +371,147 @@ function getEnding(person: T.Person, isPast: boolean) { } : presentEndings[row][col]; } +function perfectTenseToEquative(t: T.PerfectTense): keyof typeof equativeEndings { + return t === "presentPerfect" + ? "present" + : t === "futurePerfect" + ? "habitual" + : t === "habitualPerfect" + ? "habitual" + : t === "pastPerfect" + ? "past" + : t === "pastSubjunctivePerfect" + ? "pastSubjunctive" + : t === "subjunctivePerfect" + ? "subjunctive" + : t === "wouldBePerfect" + ? "past" + : "subjunctive" +} + +function chooseParticipleInflection(pinf: T.SingleOrLengthOpts, p: T.Person): T.SingleOrLengthOpts { + if ("long" in pinf) { + return { + short: chooseParticipleInflection(pinf.short, p) as T.PsString[], + long: chooseParticipleInflection(pinf.long, p) as T.PsString[], + }; + } + if ("masc" in pinf) { + const gender = personGender(p); + const infNum = personIsPlural(p) ? 1 : 0; + return pinf[gender][infNum]; + } + return pinf; // already just one thing +} + +function addAbilityTailsToRs(rs: T.SingleOrLengthOpts, aspect: T.Aspect, noPerfective: boolean): T.SingleOrLengthOpts { + if (!("long" in rs)) { + throw new Error("rootStem for ability verb should have short and long versions"); + } + const tails: T.PsString[] = [ + { p: "ی", f: "ey" }, + { p: "ای", f: "aay" }, + ]; + const accentedTails: T.PsString[] = [ + { p: "ی", f: "éy" }, + { p: "ای", f: "áay" }, + ]; + // for single syllable long verb stems like tlul - ensure the accent + const psLong = (aspect === "perfective" && !noPerfective) + ? removeAccents(rs.long) + : ensureAccentLongStem(rs.long); + return { + long: tails.map(t => concatPsString(psLong, t)), + short: (aspect === "perfective" && !noPerfective ? tails : accentedTails) + .map(t => concatPsString(rs.short, t)), + }; +} + +function applyImperfectiveShortAccent(form: T.PsString[], yulEnding: boolean): T.PsString[] { + return form.map(f => { + return accentOnNFromEnd(f, yulEnding ? 1 : 0); + }); +} + +function ensure3rdPast(ending: T.PsString[], rs: T.PsString, verb: T.VerbEntry): T.PsString[] { + if (isKedul(verb) && rs.p === "شو") { + return [{ p: "شو", f: "sho" }]; + } + if (isKawulVerb(verb) && rs.p === "کړ") { + return [ + { p: "کړ", f: "kuR" }, + { p: "کړه", f: "kRu" }, + { p: "کړو", f: "kRo" }, + ]; + } + if (isTlulVerb(verb) && rs.p === "غل") { + return [{ p: "غی", f: "ghey" }]; + } + if (verb.entry.tppp && verb.entry.tppf) { + const tip = makePsString(verb.entry.tppp, verb.entry.tppf) + // if it ends in a consonant, the special form will also have another + // variation ending with a ه - u + const endsInAConsonant = (pashtoConsonants.includes(tip.p.slice(-1)) || tip.f.slice(-1) === "w"); + return [ + tip, + ...endsInAConsonant ? [ + concatPsString(tip, { p: "ه", f: "u" }), + concatPsString(tip, { p: "و", f: "o" }), + ] : [], + ]; + } + const endsInAwul = ( + (["awul", "awúl"].includes(removeFVarients(verb.entry.f).slice(-4))) + && + (verb.entry.p.slice(-2) === "ول") + ); + // TODO: check about verbs like tawul (if they exist) + if (endsInAwul) { + const base = { p: rs.p.slice(0, -1), f: rs.f.slice(0, -2) }; + return [concatPsString(base, { p: "اوه", f: "aawu" })]; + } + + // nothing special or idiosyncratic needed for 3rd pers masc sing past + return ending.map(e => concatPsString(rs, e)); +} + +function getAspect(tense: T.VerbTense | T.ModalTense): T.Aspect { + const t = tense.replace("Modal", ""); + if (["presentVerb", "imperfectiveFuture", "imperfectivePast", "habitualImperfectivePast"].includes(t)) { + return "imperfective"; + } else { + return "perfective"; + } +} + +function isKedul(v: T.VerbEntry): boolean { + return v.entry.p === "کېدل"; +} + +function fromPersInfls(x: T.OptionalPersonInflections, person: T.Person): U { + if ("mascSing" in x) { + return x[getPersonInflectionsKey(person)]; + } else { + return x; + } +} + +function ensureAccentLongStem(ps: T.PsString): T.PsString { + if (ps.f.charAt(ps.f.length - 2) === "ú") { + return ps; + } + return { + p: ps.p, + f: ps.f.slice(0, -2) + "úl", + }; +} + +function getKedulStatPerfect(person: T.Person, tense: T.PerfectTense): [PT, EQ] { + const { verbBlocks: [pt, eq] } = renderVerb({ + verb: kedulStatVerb, + tense, + person, + voice: "active", + }) as { hasBa: true, verbBlocks: [PT, EQ] }; + return [pt, eq]; +} \ No newline at end of file diff --git a/src/lib/src/type-predicates.ts b/src/lib/src/type-predicates.ts index eea1dbf..4527993 100644 --- a/src/lib/src/type-predicates.ts +++ b/src/lib/src/type-predicates.ts @@ -8,6 +8,11 @@ export function isTlulVerb(e: T.VerbEntry | T.VerbDictionaryEntry): boolean { return entry.f === "tlul" || entry.p === "راتلل" || entry.p === "درتلل" || entry.p === "ورتلل"; } +export function isKawulVerb(e: T.VerbEntry | T.VerbDictionaryEntry): boolean { + const entry = "entry" in e ? e.entry : e; + return ["کول", "راکول", "درکول", "ورکول"].includes(entry.p); +} + export function isNounEntry(e: T.Entry | T.DictionaryEntry): e is T.NounEntry { if ("entry" in e) return false; return !!(e.c && (e.c.includes("n. m.") || e.c.includes("n. f."))); diff --git a/src/types.ts b/src/types.ts index 7f98f17..a9cefba 100644 --- a/src/types.ts +++ b/src/types.ts @@ -661,6 +661,8 @@ export type VerbSelectionComplete = Omit