more refactoring and work on the phrase builder

This commit is contained in:
lingdocs 2022-03-17 10:53:49 +04:00
parent e4cf7558ee
commit 06fa7966f8
4 changed files with 270 additions and 100 deletions

View File

@ -5,7 +5,7 @@ import {
} from "./np-picker/picker-tools"; } from "./np-picker/picker-tools";
import { import {
Types as T, Types as T,
// ButtonSelect, ButtonSelect,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
const tenseOptions: { label: string, value: VerbTense }[] = [{ const tenseOptions: { label: string, value: VerbTense }[] = [{
@ -65,14 +65,14 @@ function VerbPicker({ onChange, verb, verbs }: { verbs: VerbEntry[], verb: VerbS
}); });
} }
} }
// function onPosNegSelect(value: string) { function onPosNegSelect(value: string) {
// if (verb) { if (verb) {
// onChange({ onChange({
// ...verb, ...verb,
// negative: value === "true", negative: value === "true",
// }); });
// } }
// } }
return <div style={{ maxWidth: "225px" }}> return <div style={{ maxWidth: "225px" }}>
<div>Verb:</div> <div>Verb:</div>
<Select <Select
@ -99,8 +99,7 @@ function VerbPicker({ onChange, verb, verbs }: { verbs: VerbEntry[], verb: VerbS
placeholder={verb ? tenseOptions.find(o => o.value === verb.tense)?.label : "Select Tense..."} placeholder={verb ? tenseOptions.find(o => o.value === verb.tense)?.label : "Select Tense..."}
{...zIndexProps} {...zIndexProps}
/> />
{/* The negative is not ready yet for people to use */} {verb && <div className="text-center my-3">
{/* {verb && <div className="text-center my-3">
<ButtonSelect <ButtonSelect
small small
value={verb.negative.toString()} value={verb.negative.toString()}
@ -113,7 +112,7 @@ function VerbPicker({ onChange, verb, verbs }: { verbs: VerbEntry[], verb: VerbS
}]} }]}
handleChange={onPosNegSelect} handleChange={onPosNegSelect}
/> />
</div>} */} </div>}
</div>; </div>;
} }

View File

@ -1,37 +1,110 @@
import { import {
Types as T, Types as T,
concatPsString, concatPsString,
removeAccents,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
const nu: T.PsString = { p: "نه", f: "nu" };
export function compileVP(VP: VPRendered): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string [] } { export function compileVP(VP: VPRendered): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string [] } {
console.log(VP);
const { head, rest } = VP.verb.ps;
const subj = VP.subject.ps;
const obj = typeof VP.object === "object" ? VP.object.ps : undefined;
// better: feed in array of NPs [subj, obj, etc...]
return {
ps: arrangePs(subj, obj, head, rest, VP.verb.negative),
e: compileEnglish(VP),
};
}
function arrangePs(
subj: T.PsString[],
obj: T.PsString[] | undefined,
head: T.PsString | undefined,
rest: T.PsString[],
negative: boolean,
): T.PsString[];
function arrangePs(
subj: T.PsString[],
obj: T.PsString[] | undefined,
head: T.PsString | undefined,
rest: T.SingleOrLengthOpts<T.PsString[]>,
negative: boolean,
): T.SingleOrLengthOpts<T.PsString[]>;
function arrangePs(
subj: T.PsString[],
obj: T.PsString[] | undefined,
head: T.PsString | undefined,
rest: T.SingleOrLengthOpts<T.PsString[]>,
negative: boolean,
): T.SingleOrLengthOpts<T.PsString[]> {
if ("long" in rest) {
return {
long: arrangePs(subj, obj, head, rest.long, negative),
short: arrangePs(subj, obj, head, rest.short, negative),
...rest.mini ? {
mini: arrangePs(subj, obj, head, rest.mini, negative),
} : {},
};
}
const verbWNeg = arrangeVerbWNeg(head, rest, negative);
if (obj) {
return subj.flatMap(s => (
obj.flatMap(o =>
verbWNeg.flatMap(v => (
concatPsString(s, " ", o, " ", v)
// concatPsString(o, " ", s, " ", v),
))
))
);
}
return subj.flatMap(s => (
verbWNeg.flatMap(v => (
concatPsString(s, " ", v)
))
));
}
function arrangeVerbWNeg(head: T.PsString | undefined, rest: T.PsString[], negative: boolean): T.PsString[] {
if (!negative) {
return rest.map(ps => concatPsString(head || "", ps));
}
const nu: T.PsString = { p: "نه", f: "nú" };
if (!head) {
return rest.map(r => concatPsString(nu, " ", removeAccents(r)));
}
const regularPrefix = head.p === "و" || head.p === "وا";
const withNuAfterHead = rest.map(r => concatPsString(
removeAccents(head),
{ p: "", f: "-" },
nu,
" ",
removeAccents(r),
));
if (regularPrefix) {
return withNuAfterHead;
}
const withNuBeforeHead = rest.map(r => concatPsString(
nu,
" ",
removeAccents(head),
removeAccents(r),
));
return [
...withNuAfterHead,
...withNuBeforeHead,
];
}
function compileEnglish(VP: VPRendered): string[] | undefined {
function insertEWords(e: string, { subject, object }: { subject: string, object?: string }): string { function insertEWords(e: string, { subject, object }: { subject: string, object?: string }): string {
return e.replace("$SUBJ", subject).replace("$OBJ", object || ""); return e.replace("$SUBJ", subject).replace("$OBJ", object || "");
} }
// TODO: display of short and long options etc.
const vPs = "long" in VP.verb.ps ? VP.verb.ps.long : VP.verb.ps;
const engSubj = VP.subject.e || undefined; const engSubj = VP.subject.e || undefined;
const engObj = (typeof VP.object === "object" && VP.object.e) ? VP.object.e : undefined; const engObj = (typeof VP.object === "object" && VP.object.e) ? VP.object.e : undefined;
// require all English parts for making the English phrase // require all English parts for making the English phrase
const e = (VP.englishBase && engSubj && engObj) ? VP.englishBase.map(e => insertEWords(e, { return (VP.englishBase && engSubj && engObj) ? VP.englishBase.map(e => insertEWords(e, {
subject: engSubj, subject: engSubj,
object: engObj, object: engObj,
})) : undefined; })) : undefined;
const obj = typeof VP.object === "object" ? VP.object : undefined;
const ps = VP.subject.ps.flatMap(s => (
obj ? obj.ps.flatMap(o => (
vPs.flatMap(v => (
VP.verb.negative
// this will not work yet for perfectives etc - super rough start
? concatPsString(s, " ", o, " ", nu, " ", v)
: concatPsString(s, " ", o, " ", v)
))
)) : vPs.flatMap(v => (
VP.verb.negative
? concatPsString(s, " ", nu, " ", v)
: concatPsString(s, " ", v)
))
));
return { ps, e };
} }

View File

@ -7,6 +7,7 @@ import {
parseEc, parseEc,
conjugateVerb, conjugateVerb,
concatPsString, concatPsString,
removeAccents,
} from "@lingdocs/pashto-inflector"; } from "@lingdocs/pashto-inflector";
import { import {
psStringFromEntry, psStringFromEntry,
@ -18,8 +19,12 @@ export function renderVP(VP: VPSelection): VPRendered {
const isTransitive = VP.object !== "none"; const isTransitive = VP.object !== "none";
const { king, /* servant */ } = getKingAndServant(isPast, isTransitive); const { king, /* servant */ } = getKingAndServant(isPast, isTransitive);
const kingPerson = getPersonFromNP(VP[king]); const kingPerson = getPersonFromNP(VP[king]);
// TODO: more elegant way of handling this type safety
if (kingPerson === undefined) {
throw new Error("king of sentance does not exist");
}
const subjectPerson = getPersonFromNP(VP.subject); const subjectPerson = getPersonFromNP(VP.subject);
// const objectPerson = getPersonFromNP(VP.object); const objectPerson = getPersonFromNP(VP.object);
// TODO: also don't inflect if it's a pattern one animate noun // TODO: also don't inflect if it's a pattern one animate noun
const inflectSubject = isPast && isTransitive; const inflectSubject = isPast && isTransitive;
const inflectObject = !isPast && isFirstOrSecondPersPronoun(VP.object); const inflectObject = !isPast && isFirstOrSecondPersPronoun(VP.object);
@ -28,7 +33,7 @@ export function renderVP(VP: VPSelection): VPRendered {
type: "VPRendered", type: "VPRendered",
subject: renderNPSelection(VP.subject, inflectSubject, false, "subject"), subject: renderNPSelection(VP.subject, inflectSubject, false, "subject"),
object: renderNPSelection(VP.object, inflectObject, true, "object"), object: renderNPSelection(VP.object, inflectObject, true, "object"),
verb: renderVerbSelection(VP.verb, kingPerson), verb: renderVerbSelection(VP.verb, kingPerson, objectPerson),
englishBase: renderEnglishVPBase({ englishBase: renderEnglishVPBase({
subjectPerson, subjectPerson,
object: VP.object, object: VP.object,
@ -46,14 +51,62 @@ function renderNPSelection(NP: NPSelection | ObjectNP, inflected: boolean, infle
} }
return NP; return NP;
} }
return { if (NP.type === "noun") {
...NP, return renderNounSelection(NP, inflected);
inflected, }
...textOfNP(NP, inflected, inflectEnglish), if (NP.type === "pronoun") {
}; return renderPronounSelection(NP, inflected, inflectEnglish);
}
if (NP.type === "participle") {
return renderParticipleSelection(NP, inflected)
}
throw new Error("unknown NP type");
}; };
function renderVerbSelection(vs: VerbSelection, person: T.Person): VerbRendered { function renderNounSelection(n: NounSelection, inflected: boolean): Rendered<NounSelection> {
const english = getEnglishFromNoun(n.entry, n.number);
const pashto = ((): T.PsString[] => {
const infs = inflectWord(n.entry);
const ps = n.number === "singular"
? getInf(infs, "inflections", n.gender, false, inflected)
: [
...getInf(infs, "plural", n.gender, true, inflected),
...getInf(infs, "arabicPlural", n.gender, true, inflected),
...getInf(infs, "inflections", n.gender, true, inflected),
];
return ps.length > 0
? ps
: [psStringFromEntry(n.entry)];
})();
return {
...n,
inflected,
ps: pashto,
e: english,
};
}
function renderPronounSelection(p: PronounSelection, inflected: boolean, englishInflected: boolean): Rendered<PronounSelection> {
const [row, col] = getVerbBlockPosFromPerson(p.person);
return {
...p,
inflected,
ps: grammarUnits.pronouns[p.distance][inflected ? "inflected" : "plain"][row][col],
e: grammarUnits.persons[p.person].label[englishInflected ? "object" : "subject"],
};
}
function renderParticipleSelection(p: ParticipleSelection, inflected: boolean): Rendered<ParticipleSelection> {
return {
...p,
inflected,
// TODO: More robust inflection of inflecting pariticiples - get from the conjugation engine
ps: [psStringFromEntry(p.verb.entry)].map(ps => inflected ? concatPsString(ps, { p: "و", f: "o" }) : ps),
e: getEnglishParticiple(p.verb.entry),
};
}
function renderVerbSelection(vs: VerbSelection, person: T.Person, objectPerson: T.Person | undefined): VerbRendered {
const conjugations = conjugateVerb(vs.verb.entry, vs.verb.complement); const conjugations = conjugateVerb(vs.verb.entry, vs.verb.complement);
// TODO: error handle this? // TODO: error handle this?
// TODO: option to manually select these // TODO: option to manually select these
@ -62,11 +115,11 @@ function renderVerbSelection(vs: VerbSelection, person: T.Person): VerbRendered
: "stative" in conjugations : "stative" in conjugations
? conjugations.stative ? conjugations.stative
: conjugations; : conjugations;
// TODO: get the object person from the matrix on stative compounds // TODO: deliver the perfective split!
return { return {
...vs, ...vs,
person, person,
ps: getPsVerbConjugation(conj, vs.tense, person), ps: getPsVerbConjugation(conj, vs.tense, person, objectPerson),
} }
} }
@ -126,12 +179,30 @@ function renderEnglishVPBase({ subjectPerson, object, vs }: {
return base.map(b => `${b}${typeof object === "object" ? " $OBJ" : ""}${ep ? ` ${ep}` : ""}`); return base.map(b => `${b}${typeof object === "object" ? " $OBJ" : ""}${ep ? ` ${ep}` : ""}`);
} }
function getPsVerbConjugation(conj: T.VerbConjugation, tense: VerbTense, person: T.Person): T.SingleOrLengthOpts<T.PsString[]> { function getPsVerbConjugation(conj: T.VerbConjugation, tense: VerbTense, person: T.Person, objectPerson: T.Person | undefined): {
head: T.PsString | undefined,
rest: T.SingleOrLengthOpts<T.PsString[]>,
} {
const f = getTenseVerbForm(conj, tense); const f = getTenseVerbForm(conj, tense);
// TODO: ability to grab the correct part of matrix const block = getMatrixBlock(f, objectPerson, person);
const block = "mascSing" in f const perfective = isPerfective(tense);
? f.mascSing const verbForm = getVerbFromBlock(block, person);
: f; if (perfective) {
const past = isPastTense(tense);
const splitInfo = conj.info[past ? "root" : "stem"].perfectiveSplit;
if (!splitInfo) return { head: undefined, rest: verbForm };
// TODO: Either solve this in the inflector or here, it seems silly (or redundant)
// to have a length option in the perfective split stem??
const [splitHead] = getLong(getMatrixBlock(splitInfo, objectPerson, person));
return {
head: splitHead,
rest: removeHead(splitHead, verbForm),
};
}
return { head: undefined, rest: verbForm };
}
function getVerbFromBlock(block: T.SingleOrLengthOpts<T.VerbBlock>, person: T.Person): T.SingleOrLengthOpts<T.PsString[]> {
function grabFromBlock(b: T.VerbBlock, [row, col]: [ row: number, col: number ]): T.PsString[] { function grabFromBlock(b: T.VerbBlock, [row, col]: [ row: number, col: number ]): T.PsString[] {
return b[row][col]; return b[row][col];
} }
@ -148,6 +219,63 @@ function getPsVerbConjugation(conj: T.VerbConjugation, tense: VerbTense, person:
return grabFromBlock(block, pos); return grabFromBlock(block, pos);
} }
function removeHead(head: T.PsString, rest: T.PsString[]): T.PsString[];
function removeHead(head: T.PsString, rest: T.SingleOrLengthOpts<T.PsString[]>): T.SingleOrLengthOpts<T.PsString[]>;
function removeHead(head: T.PsString, rest: T.SingleOrLengthOpts<T.PsString[]>): T.SingleOrLengthOpts<T.PsString[]> {
if ("long" in rest) {
return {
long: removeHead(head, rest.long),
short: removeHead(head, rest.short),
...rest.mini ? {
mini: removeHead(head, rest.mini),
} : {},
}
}
return rest.map((ps) => {
const pMatches = removeAccents(ps.p.slice(0, head.p.length)) === removeAccents(head.p);
const fMatches = removeAccents(ps.f.slice(0, head.f.length)) === removeAccents(head.f);
if (!(pMatches && fMatches)) {
throw new Error(`split head does not match - ${JSON.stringify(ps)} ${JSON.stringify(head)}`);
}
return {
p: ps.p.slice(head.p.length),
f: ps.f.slice(head.f.length),
}
});
}
function getLong<U>(x: T.SingleOrLengthOpts<U>): U {
if ("long" in x) {
return x.long;
}
return x;
}
function getMatrixBlock<U>(f: {
mascSing: T.SingleOrLengthOpts<U>;
mascPlur: T.SingleOrLengthOpts<U>;
femSing: T.SingleOrLengthOpts<U>;
femPlur: T.SingleOrLengthOpts<U>;
} | T.SingleOrLengthOpts<U>, objectPerson: T.Person | undefined, kingPerson: T.Person): T.SingleOrLengthOpts<U> {
if (!("mascSing" in f)) {
return f;
}
function personToLabel(p: T.Person): "mascSing" | "mascPlur" | "femSing" | "femPlur" {
if (p === T.Person.FirstSingMale || p === T.Person.SecondSingMale || p === T.Person.ThirdSingMale) {
return "mascSing";
}
if (p === T.Person.FirstSingFemale || p === T.Person.SecondSingFemale || p === T.Person.ThirdSingFemale) {
return "femSing";
}
if (p === T.Person.FirstPlurMale || p === T.Person.SecondPlurMale || p === T.Person.ThirdPlurMale) {
return "mascPlur";
}
return "femPlur";
}
// if there's an object the matrix will agree with that, otherwise with the kingPerson (subject for intransitive)
const person = (objectPerson === undefined) ? kingPerson : objectPerson;
return f[personToLabel(person)];
}
function getTenseVerbForm(conj: T.VerbConjugation, tense: VerbTense): T.VerbForm { function getTenseVerbForm(conj: T.VerbConjugation, tense: VerbTense): T.VerbForm {
if (tense === "present") { if (tense === "present") {
return conj.imperfective.nonImperative; return conj.imperfective.nonImperative;
@ -164,9 +292,11 @@ function getTenseVerbForm(conj: T.VerbConjugation, tense: VerbTense): T.VerbForm
throw new Error("unknown tense"); throw new Error("unknown tense");
} }
function getPersonFromNP(np: NPSelection | ObjectNP): T.Person { function getPersonFromNP(np: NPSelection): T.Person;
function getPersonFromNP(np: NPSelection | ObjectNP): T.Person | undefined;
function getPersonFromNP(np: NPSelection | ObjectNP): T.Person | undefined {
if (np === "none") { if (np === "none") {
throw new Error("empty entity"); return undefined;
} }
if (typeof np === "number") return np; if (typeof np === "number") return np;
if (np.type === "participle") { if (np.type === "participle") {
@ -180,25 +310,6 @@ function getPersonFromNP(np: NPSelection | ObjectNP): T.Person {
: (np.gender === "masc" ? T.Person.ThirdSingMale : T.Person.ThirdSingFemale); : (np.gender === "masc" ? T.Person.ThirdSingMale : T.Person.ThirdSingFemale);
} }
function textOfNP(np: NPSelection, inflected: boolean, englishInflected: boolean): { ps: T.PsString[], e: string } {
if (np.type === "participle") {
return textOfParticiple(np, inflected);
}
if (np.type === "pronoun") {
return textOfPronoun(np, inflected, englishInflected);
}
return textOfNoun(np, inflected);
}
function textOfParticiple({ verb: { entry }}: ParticipleSelection, inflected: boolean): { ps: T.PsString[], e: string } {
// TODO: ability to inflect participles
return {
// TODO: More robust inflection of inflecting pariticiples - get from the conjugation engine
ps: [psStringFromEntry(entry)].map(ps => inflected ? concatPsString(ps, { p: "و", f: "o" }) : ps),
e: getEnglishParticiple(entry),
};
}
function getEnglishParticiple(entry: T.DictionaryEntry): string { function getEnglishParticiple(entry: T.DictionaryEntry): string {
if (!entry.ec) { if (!entry.ec) {
console.log("errored participle"); console.log("errored participle");
@ -212,33 +323,6 @@ function getEnglishParticiple(entry: T.DictionaryEntry): string {
: participle; : participle;
} }
function textOfPronoun(p: PronounSelection, inflected: boolean, englishInflected: boolean): { ps: T.PsString[], e: string } {
// TODO: Will need to handle inflecting and inflecting english pronouns etc.
const [row, col] = getVerbBlockPosFromPerson(p.person);
return {
ps: grammarUnits.pronouns[p.distance][inflected ? "inflected" : "plain"][row][col],
e: grammarUnits.persons[p.person].label[englishInflected ? "object" : "subject"],
};
}
function textOfNoun(n: NounSelection, inflected: boolean): { ps: T.PsString[], e: string } {
const english = getEnglishFromNoun(n.entry, n.number);
const pashto = ((): T.PsString[] => {
const infs = inflectWord(n.entry);
const ps = n.number === "singular"
? getInf(infs, "inflections", n.gender, false, inflected)
: [
...getInf(infs, "plural", n.gender, true, inflected),
...getInf(infs, "arabicPlural", n.gender, true, inflected),
...getInf(infs, "inflections", n.gender, true, inflected),
];
return ps.length > 0
? ps
: [psStringFromEntry(n.entry)];
})();
return { ps: pashto, e: english };
}
function getEnglishFromNoun(entry: T.DictionaryEntry, number: NounNumber): string { function getEnglishFromNoun(entry: T.DictionaryEntry, number: NounNumber): string {
const articles = { const articles = {
singular: "(a/the)", singular: "(a/the)",
@ -296,3 +380,13 @@ function isFirstOrSecondPersPronoun(o: "none" | NPSelection | T.Person.ThirdPlur
if (o.type !== "pronoun") return false; if (o.type !== "pronoun") return false;
return [0,1,2,3,6,7,8,9].includes(o.person); return [0,1,2,3,6,7,8,9].includes(o.person);
} }
function isPerfective(t: VerbTense): boolean {
if (t === "present" || t === "imperfectivePast") {
return false;
}
if (t === "perfectivePast" || t === "subjunctive") {
return true;
}
throw new Error("tense not implemented yet");
}

10
src/types/gen-g.d.ts vendored
View File

@ -28,13 +28,17 @@ type VerbSelection = {
verb: VerbEntry, verb: VerbEntry,
tense: VerbTense, tense: VerbTense,
object: VerbObject, object: VerbObject,
// TODO: add in perfective element here??
negative: boolean, negative: boolean,
}; };
type VerbRendered = Omit<VerbSelection, "object"> & { type VerbRendered = Omit<VerbSelection, "object"> & {
ps: import("@lingdocs/pashto-inflector").Types.SingleOrLengthOpts< ps: {
import("@lingdocs/pashto-inflector").Types.PsString[] head: import("@lingdocs/pashto-inflector").Types.PsString | undefined,
>, rest: import("@lingdocs/pashto-inflector").Types.SingleOrLengthOpts<
import("@lingdocs/pashto-inflector").Types.PsString[]
>,
},
person: import("@lingdocs/pashto-inflector").Types.Person, person: import("@lingdocs/pashto-inflector").Types.Person,
}; };