lots more towards the new more correct and concise verb conjugator

This commit is contained in:
adueck 2023-03-31 17:45:06 +04:00
parent 68189ea4ba
commit c9e3d13c43
12 changed files with 549 additions and 79 deletions

View File

@ -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<boolean>(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 <>
<main className="flex-shrink-0 mb-4">
<pre>
{JSON.stringify(rv, null, " ")}
</pre>
<div className="container" style={{ maxWidth: "800px" }}>
<div style={{ position: "absolute", top: "1.5rem", right: "1.5rem", display: "flex", flexDirection: "row" }}>
<div

View File

@ -31,7 +31,6 @@ function Examples(props: ({
opts: T.TextOptions,
lineHeight?: 0 | 1 | 2 | 3 | 4,
}) {
console.log({ props });
const examples = "children" in props ? props.children : props.ex;
const Example = ({ children: text }: { children: PsStringWSub }) => (
<div className={props.lineHeight !== undefined ? `mb-${props.lineHeight}` : `mt-1 mb-3`}>

View File

@ -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.ModalTense>(t => `${t}Modal`);
const testTenses = [
...testVerbTenses,
...testPerfectTenses,
...testAbilityTenses,
];
function VPBuilderDemo({ opts }: {
opts: T.TextOptions,
}) {
const [verbTs, setVerbTs] = useStickyState<number>(0, "verbTs1");
const [verbTypeShowing, setVerbTypeShowing] = useStickyState<VerbType>("simple", "vTypeShowing");
const [transitivityShowing, setTransitivityShowing] = useStickyState<T.Transitivity>("intransitive", "transitivityShowing1");
const [testPerson, setTestPerson] = useStickyState<T.PronounSelection>({
type: "pronoun",
distance: "far",
person: 0,
}, "testPronoun");
const [testVoice, setTestVoice] = useStickyState<T.Voice>("active", "testVoice");
const [testTense, setTestTense] = useStickyState<T.VerbTense | T.PerfectTense | T.ModalTense>("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 <div className="mt-4">
<div className="d-block mx-auto card" style={{ maxWidth: "700px", background: "var(--closer)"}}>
<div className="card-body">
@ -189,6 +233,23 @@ function VPBuilderDemo({ opts }: {
</div>
</div>
</div>
<button onClick={() => setTestVoice(v => v === "active" ? "passive" : "active")}>
{testVoice}
</button>
<select value={testTense} onChange={e => setTestTense(e.target.value as any)}>
{testTenses.map(t => (
<option key={t} value={t}>{t}</option>
))}
</select>
<NPPronounPicker
onChange={setTestPerson}
pronoun={testPerson}
role="subject"
opts={opts}
/>
<pre>
{JSON.stringify(rv, null, " ")}
</pre>
{v?.verb.entry && <div style={{ paddingBottom: "20px" }}>
<PhraseBuilder
handleLinkClick="none"

View File

@ -46,6 +46,9 @@ import {
getLength,
psStringEquals,
} from "./src/p-text-helpers";
import {
joiningTails,
} from "./src/misc-text";
import {
getEnglishWord,
} from "./src/get-english-word";
@ -249,6 +252,7 @@ export {
makeVPSelectionState,
getLength,
psStringEquals,
joiningTails,
blockUtils,
blank,
kidsBlank,

View File

@ -679,26 +679,26 @@ const baseInflectedPronouns = [
const plainPronounsFar = [
...basePlainPronouns,
[[{ p: "هغه", f: "haghá" }], [{ p: "هغوي", f: "haghwée" }]],
[[{ p: "هغه", f: "haghá" }], [{ p: "هغوي", f: "haghwée" }]],
[[{ p: "هغه", f: "haghá" }], [{ p: "هغوی", f: "haghwée" }]],
[[{ p: "هغه", f: "haghá" }], [{ p: "هغوی", f: "haghwée" }]],
] as T.VerbBlock;
const plainPronounsNear = [
...basePlainPronouns,
[[{ p: "دی", f: "dey" }], [{ p: "دوي", f: "dwee" }]],
[[{ p: "دا", f: "daa" }], [{ p: "دوي", f: "dwee" }]],
[[{ p: "دی", f: "dey" }], [{ p: "دوی", f: "dwee" }]],
[[{ p: "دا", f: "daa" }], [{ p: "دوی", f: "dwee" }]],
] as T.VerbBlock;
const inflectedPronounsFar = [
...baseInflectedPronouns,
[[{ p: "هغهٔ", f: "haghú" }], [{ p: "هغوي", f: "haghwée" }]],
[[{ p: "هغې", f: "haghé" }], [{ p: "هغوي", f: "haghwée" }]],
[[{ p: "هغهٔ", f: "haghú" }], [{ p: "هغوی", f: "haghwée" }]],
[[{ p: "هغې", f: "haghé" }], [{ p: "هغوی", f: "haghwée" }]],
] as T.VerbBlock;
const inflectedPronounsNear = [
...baseInflectedPronouns,
[[{ p: "دهٔ", f: "du" }], [{ p: "دوي", f: "dwee" }]],
[[{ p: "دې", f: "de" }], [{ p: "دوي", f: "dwee" }]],
[[{ p: "دهٔ", f: "du" }], [{ p: "دوی", f: "dwee" }]],
[[{ p: "دې", f: "de" }], [{ p: "دوی", f: "dwee" }]],
] as T.VerbBlock;
const miniPronouns: T.VerbBlock = [

View File

@ -96,6 +96,20 @@ export function hasPersInfs(info: T.NonComboVerbInfo | T.PassiveRootsAndStems |
);
}
export function functionOnOptLengths<U extends object, F extends object>(x: T.SingleOrLengthOpts<U>, f: (y: U) => F): T.SingleOrLengthOpts<F> {
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.UnisexInflections> | T.SingleOrLengthOpts<T.PsString>,
person: T.Person,

3
src/lib/src/misc-text.ts Normal file
View File

@ -0,0 +1,3 @@
import { PsString } from "../../types";
export const joiningTails: PsString = { p: "ـ", f: ""};

View File

@ -451,7 +451,7 @@ export function concatInflections(
comp: T.PsString | T.SingleOrLengthOpts<T.UnisexInflections>, infs: T.SingleOrLengthOpts<T.UnisexInflections>
): T.SingleOrLengthOpts<T.UnisexInflections> {
const containsLengthOptions = "long" in infs || "long" in comp;
const ensureL = <T>(x: T.SingleOrLengthOpts<T>, length: "short" | "long"): T => (
const ensureL = <T extends object>(x: T.SingleOrLengthOpts<T>, length: "short" | "long"): T => (
("long" in x) ? x[length] : x
);
if (containsLengthOptions) {
@ -509,7 +509,7 @@ export function allOnePersonInflection(
return block;
}
export function choosePersInf<T>(x: T.FullForm<T>, persInf: T.PersonInflectionsField): T.SingleOrLengthOpts<T> {
export function choosePersInf<T extends object>(x: T.FullForm<T>, persInf: T.PersonInflectionsField): T.SingleOrLengthOpts<T> {
if ("mascSing" in x) {
return x[persInf];
}
@ -999,7 +999,7 @@ export function psStringFromEntry(entry: T.PsString): T.PsString {
};
}
export function getLength<U>(x: T.SingleOrLengthOpts<U>, length: "long" | "short" | "mini"): U {
export function getLength<U extends object>(x: T.SingleOrLengthOpts<U>, 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<U>(x: T.SingleOrLengthOpts<U>, length: "long" | "short
return x;
}
export function getLong<U>(x: T.SingleOrLengthOpts<U>): U {
export function getLong<U extends object>(x: T.SingleOrLengthOpts<U>): U {
if ("long" in x) {
return x.long;
}
return x;
}
export function getShort<U>(a: T.SingleOrLengthOpts<U>): U {
export function getShort<U extends object>(a: T.SingleOrLengthOpts<U>): U {
if ("long" in a) {
return a.short;
}

View File

@ -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 => (

View File

@ -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<T.PsString[]>,
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<T.PsString[]>,
};
type PT = {
type: "participleBlock",
ps: T.SingleOrLengthOpts<T.PsString[]>,
inflection: T.PersonInflectionsField,
};
type EQ = {
type: "equativeBlock",
ps: T.SingleOrLengthOpts<T.PsString[]>,
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",
const hasBa = tenseHasBa(tense);
if (isPerfectTense(tense)) {
return {
hasBa,
ps: verbPs,
person,
aspect,
tense,
verbBlocks: getPerfectBlocks({ verb, tense, person, voice }),
};
const perfectiveHeadBlock: PerfectiveHeadBlock | undefined = perfectiveHead ? {
type: "perfectiveHead",
ps: noPersInfs(perfectiveHead),
}
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<T.PsString>, ending: T.SingleOrLengthOpts<T.PsString[]>): T.SingleOrLengthOpts<T.PsString[]> {
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<T.PsString>,
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<T.PsString>,
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<T.PsString>,
rootStem: T.OptionalPersonInflections<T.SingleOrLengthOpts<T.PsString>>,
rootStem: T.SingleOrLengthOpts<T.PsString>,
} {
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<typeof getRootStem> {
// 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<T.PsString>,
ending: T.SingleOrLengthOpts<T.PsString[]>,
person: T.Person,
isPast: boolean,
verb: T.VerbEntry,
aspect: T.Aspect,
}): T.SingleOrLengthOpts<T.PsString[]> {
// 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<T.UnisexInflections>, p: T.Person): T.SingleOrLengthOpts<T.PsString[]> {
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<T.PsString>, aspect: T.Aspect, noPerfective: boolean): T.SingleOrLengthOpts<T.PsString[]> {
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<U extends object>(x: T.OptionalPersonInflections<U>, 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];
}

View File

@ -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.")));

View File

@ -661,6 +661,8 @@ export type VerbSelectionComplete = Omit<VerbSelection, "object" | "verbTense" |
tense: VerbFormName,
}
export type Voice = "active" | "passive";
export type VerbSelection = {
type: "verb",
verb: VerbEntry,
@ -669,7 +671,7 @@ export type VerbSelection = {
canChangeTransitivity: boolean,
canChangeStatDyn: boolean,
isCompound: "stative" | "dynamic" | false,
voice: "active" | "passive",
voice: Voice,
canChangeVoice: boolean,
negative: boolean,
verbTense: VerbTense,