From a000731a4a8355b1d143c3ebe750b2d3abefcd11 Mon Sep 17 00:00:00 2001 From: lingdocs <71590811+lingdocs@users.noreply.github.com> Date: Tue, 15 Mar 2022 17:29:14 +0400 Subject: [PATCH] more on phrase builder --- src/components/VerbPicker.tsx | 2 - src/components/np-picker/NPNounPicker.tsx | 6 +- src/components/np-picker/picker-tools.ts | 12 +- .../phrase-builder/PhraseBuilder.tsx | 18 +- src/components/phrase-builder/VPDisplay.tsx | 20 ++ src/lib/eval-vp.ts | 256 ++++++++++++++++++ src/types/gen-g.d.ts | 38 ++- 7 files changed, 331 insertions(+), 21 deletions(-) create mode 100644 src/components/phrase-builder/VPDisplay.tsx create mode 100644 src/lib/eval-vp.ts diff --git a/src/components/VerbPicker.tsx b/src/components/VerbPicker.tsx index e2feb7e..989da49 100644 --- a/src/components/VerbPicker.tsx +++ b/src/components/VerbPicker.tsx @@ -31,7 +31,6 @@ function makeVerbSelection(verb: VerbEntry, oldVerbSelection?: VerbSelection): V : verbType === "trans" ? getTransObjFromOldVerbSelection() : "none"; - console.log({ verbType, object }); return { type: "verb", verb, @@ -52,7 +51,6 @@ function VerbPicker({ onChange, verb, verbs }: { verbs: VerbEntry[], verb: VerbS } function onTenseSelect({ value }: { label: string, value: "present" | "subjunctive" }) { if (verb) { - console.log("changing to", value) onChange({ ...verb, tense: value, diff --git a/src/components/np-picker/NPNounPicker.tsx b/src/components/np-picker/NPNounPicker.tsx index 7470b54..a63dcc8 100644 --- a/src/components/np-picker/NPNounPicker.tsx +++ b/src/components/np-picker/NPNounPicker.tsx @@ -52,15 +52,15 @@ function NPNounPicker({ onChange, noun, nouns }: { nouns: NounEntry[], noun: Nou {noun.changeNumber ? { if (!noun.changeNumber) return; onChange(noun.changeNumber(n)); }} - /> : noun.number === "sing" ? "Sing." : "Plur."} + /> : noun.number === "singular" ? "Sing." : "Plur."} } ; diff --git a/src/components/np-picker/picker-tools.ts b/src/components/np-picker/picker-tools.ts index d0663fd..138e68b 100644 --- a/src/components/np-picker/picker-tools.ts +++ b/src/components/np-picker/picker-tools.ts @@ -1,7 +1,4 @@ import { - isNounEntry, - isAdjectiveEntry, - isAdverbEntry, isPluralNounEntry, isMascNounEntry, isUnisexNounEntry, @@ -14,6 +11,7 @@ import { import { getEnglishWord, removeFVarients, + Types as T, } from "@lingdocs/pashto-inflector"; export const zIndexProps = { @@ -48,22 +46,22 @@ export function makeSelectOption(e: VerbEntry | NounEntry | AdjectiveEntry | Loc } export function makeNounSelection(entry: NounEntry): NounSelection { - const number = isPluralNounEntry(entry) ? "plur" : "sing"; + const number = isPluralNounEntry(entry) ? "plural" : "singular"; return { type: "noun", entry, gender: isMascNounEntry(entry) ? "masc" : "fem", number, ...isUnisexNounEntry(entry) ? { - changeGender: function(gender: "masc" | "fem"): NounSelection { + changeGender: function(gender: T.Gender): NounSelection { return { ...this, gender, }; }, } : {}, - ...number === "sing" ? { - changeNumber: function(number: "plur" | "sing"): NounSelection { + ...number === "singular" ? { + changeNumber: function(number: NounNumber): NounSelection { return { ...this, number, diff --git a/src/components/phrase-builder/PhraseBuilder.tsx b/src/components/phrase-builder/PhraseBuilder.tsx index 07963f2..d9fa161 100644 --- a/src/components/phrase-builder/PhraseBuilder.tsx +++ b/src/components/phrase-builder/PhraseBuilder.tsx @@ -1,9 +1,22 @@ import { useState } from "react"; import NPPicker from "../np-picker/NPPicker"; import VerbPicker from "../VerbPicker"; +import VPDisplay from "./VPDisplay"; import ObjectDisplay from "./ObjectDisplay"; import { verbs } from "../../words/words"; +function verbPhraseComplete({ subject, verb }: { subject: NPSelection | undefined, verb: VerbSelection | undefined }): VPSelection | undefined { + if (!subject) return undefined; + if (!verb) return undefined; + if (verb.object === undefined) return undefined; + return { + type: "VPSelection", + subject, + object: verb.object, + verb, + }; +} + export function PhraseBuilder() { const [subject, setSubject] = useState(undefined); const [verb, setVerb] = useState(undefined); @@ -15,7 +28,7 @@ export function PhraseBuilder() { object, }); } - console.log({ subject, verb }); + const verbPhrase: VPSelection | undefined = verbPhraseComplete({ subject, verb }); return
@@ -31,6 +44,9 @@ export function PhraseBuilder() {
+ {verbPhrase &&
+ +
}
} diff --git a/src/components/phrase-builder/VPDisplay.tsx b/src/components/phrase-builder/VPDisplay.tsx new file mode 100644 index 0000000..b44fb48 --- /dev/null +++ b/src/components/phrase-builder/VPDisplay.tsx @@ -0,0 +1,20 @@ +import { renderVP, compileVP } from "../../lib/eval-vp"; +import { + InlinePs, + defaultTextOptions as opts, +} from "@lingdocs/pashto-inflector"; + +function VPDisplay({ VP }: { VP: VPSelection }) { + const result = compileVP(renderVP(VP)); + const rPs = "long" in result.ps ? result.ps.long : result.ps; + return
+ {rPs.map((r, i) =>
+ {r} +
)} + {result.e &&
+ {result.e.map((e, i) =>
{e}
)} +
} +
+} + +export default VPDisplay; \ No newline at end of file diff --git a/src/lib/eval-vp.ts b/src/lib/eval-vp.ts new file mode 100644 index 0000000..3e84ce8 --- /dev/null +++ b/src/lib/eval-vp.ts @@ -0,0 +1,256 @@ +import { + Types as T, + getVerbBlockPosFromPerson, + grammarUnits, + getEnglishWord, + inflectWord, + parseEc, + conjugateVerb, + concatPsString, +} from "@lingdocs/pashto-inflector"; +import { + psStringFromEntry, +} from "./text-tools"; + +export function renderVP(VP: VPSelection): VPRendered { + // TODO: Will be based on past tense etc + const king = "subject"; + // const servant = VP.object ? "object" : undefined; + const verbPerson = getPersonFromNP(VP[king]); + const subjectPerson = getPersonFromNP(VP.subject); + const subject: Rendered = { + ...VP.subject, + inflected: false, + // TODO: possibility for inflecting + ...textOfNP(VP.subject, false, false), + }; + const inflectObject = isFirstOrSecondPersPronoun(VP.object); + const object: "none" | Rendered | T.Person.ThirdPlurMale = typeof VP.object === "object" + ? { + ...VP.subject, + inflected: inflectObject, + // TODO: possibility for inflecting + ...textOfNP(VP.object, inflectObject, true), + } : VP.object; + // TODO: error handle this? + const conjugations = conjugateVerb(VP.verb.verb.entry, VP.verb.verb.complement); + // TODO: option to manually select these + const conj = "grammaticallyTransitive" in conjugations + ? conjugations.grammaticallyTransitive + : "stative" in conjugations + ? conjugations.stative + : conjugations; + return { + type: "VPRendered", + subject, + object, + verb: { + ...VP.verb, + person: verbPerson, + ps: getPsVerbConjugation(conj, VP.verb.tense, verbPerson), + e: getEnglishVerbConjugation({ + subjectPerson, + object: VP.object, + v: parseEc(VP.verb.verb.entry.ec || ""), + ep: VP.verb.verb.entry.ep, + tense: VP.verb.tense, + n: false, + }), + }, + }; +} + +export function compileVP(VP: VPRendered | VPSelection): { ps: T.SingleOrLengthOpts, e?: string [] } { + if (VP.type === "VPSelection") { + return compileVP(renderVP(VP)); + } + const vPs = "long" in VP.verb.ps ? VP.verb.ps.long : VP.verb.ps; + // const vE = VP.verb.e; + const obj = typeof VP.object === "object" ? VP.object : undefined; + const ps = VP.subject.ps.flatMap(s => ( + // TODO: fix double space thing + obj ? obj.ps.flatMap(o => ( + vPs.flatMap(v => ( + concatPsString(s, " ", o, " ", v) + )) + )) : vPs.flatMap(v => ( + concatPsString(s, " ", v) + )) + )); + return { ps }; +} + +function isFirstOrSecondPersPronoun(o: "none" | NPSelection | T.Person.ThirdPlurMale): boolean { + if (typeof o !== "object") return false; + if (o.type !== "pronoun") return false; + return [0,1,2,3,6,7,8,9].includes(o.person); +} + +function getPsVerbConjugation(conj: T.VerbConjugation, tense: "present" | "subjunctive", person: T.Person): T.SingleOrLengthOpts { + const f = conj[tense === "present" ? "imperfective" : "perfective"].nonImperative; + // TODO: ability to grab the correct part of matrix + const block = "mascSing" in f + ? f.mascSing + : f; + function grabFromBlock(b: T.VerbBlock, [row, col]: [ row: number, col: number ]): T.PsString[] { + return b[row][col]; + } + const pos = getVerbBlockPosFromPerson(person); + if ("long" in block) { + return { + long: grabFromBlock(block.long, pos), + short: grabFromBlock(block.short, pos), + ...block.mini ? { + mini: grabFromBlock(block.mini, pos), + } : {}, + }; + } + return grabFromBlock(block, pos); +} + +function getEnglishVerbConjugation({ subjectPerson, object, ep, v, tense, n }: { + subjectPerson: T.Person, + object: "none" | NPSelection | T.Person.ThirdPlurMale, + v: T.EnglishVerbConjugationEc, + ep: string | undefined, + tense: "present" | "subjunctive", + n: boolean, +}): string[] { + function engEquative(tense: "past" | "present", s: T.Person): string { + const [row, col] = getVerbBlockPosFromPerson(s); + return grammarUnits.englishEquative[tense][row][col]; + } + function engPresC(s: T.Person, ec: T.EnglishVerbConjugationEc | [string, string]): string { + function isThirdPersonSing(p: T.Person): boolean { + return ( + p === T.Person.ThirdSingMale || + p === T.Person.ThirdSingFemale + ); + } + return isThirdPersonSing(s) ? ec[1] : ec[0]; + } + function isToBe(v: T.EnglishVerbConjugationEc): boolean { + return (v[2] === "being"); + } + const builders: Record< + string, + (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => string[] + > = { + present: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ + `$SUBJ ${isToBe(v) + ? `${engEquative("present", s)}${n ? " not" : ""}` + : `${n ? engPresC(s, ["don't", "doesn't"]) : ""} ${n ? v[0] : engPresC(s, v)}`}`, + `$SUBJ ${engEquative("present", s)}${n ? " not" : ""} ${v[2]}`, + ]), + subjunctive: (s: T.Person, v: T.EnglishVerbConjugationEc, n: boolean) => ([ + `that $SUBJ ${n ? " won't" : " will"} ${isToBe(v) ? "be" : v[0]}`, + `should $SUBJ ${n ? " not" : ""} ${isToBe(v) ? "be" : v[0]}`, + ]), + }; + const base = builders[tense](subjectPerson, v, n); + return base.map(b => `${b}${typeof object === "object" ? " $OBJ" : ""}${ep ? ` ${ep}` : ""}`); +} + +function getPersonFromNP(np: NPSelection | T.Person.ThirdPlurMale): T.Person { + if (typeof np === "number") return np; + if (np.type === "participle") { + return T.Person.ThirdPlurMale; + } + if (np.type === "pronoun") { + return np.person; + } + return np.number === "plural" + ? (np.gender === "masc" ? T.Person.ThirdPlurMale : T.Person.ThirdPlurFemale) + : (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") { + // TODO: Implement ability to inflect participles + return textOfParticiple(np, inflected); + } + if (np.type === "pronoun") { + return textOfPronoun(np, inflected, englishInflected); + } + // TODO: Implement ability to inflect nouns + return textOfNoun(np, inflected); +} + +function textOfParticiple({ verb: { entry }}: ParticipleSelection, inflected: boolean): { ps: T.PsString[], e: string } { + // TODO: ability to inflect participles + return { + ps: [psStringFromEntry(entry)], + e: getEnglishParticiple(entry), + }; +} + +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 getEnglishFromNoun(entry: T.DictionaryEntry, number: NounNumber): string { + const articles = { + singular: "(a/the)", + plural: "(the)", + }; + const article = articles[number]; + function addArticle(s: string) { + return `${article} ${s}`; + } + const e = getEnglishWord(entry); + if (!e) throw new Error(`unable to get english from subject ${entry.f} - ${entry.ts}`); + + if (typeof e === "string") return ` ${e}`; + if (number === "plural") return addArticle(e.plural); + if (!e.singular || e.singular === undefined) { + throw new Error(`unable to get english from subject ${entry.f} - ${entry.ts}`); + } + return addArticle(e.singular); +} + +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) + : [ + ...getInf(infs, "plural", n.gender, true), + ...getInf(infs, "arabicPlural", n.gender, true), + ...getInf(infs, "inflections", n.gender, true), + ]; + return ps.length > 0 + ? ps + : [psStringFromEntry(n.entry)]; + })(); + return { ps: pashto, e: english }; +} + +function getInf(infs: T.InflectorOutput, t: "plural" | "arabicPlural" | "inflections", gender: T.Gender, plural: boolean): T.PsString[] { + // @ts-ignore + if (infs && t in infs && infs[t] !== undefined && gender in infs[t] && infs[t][gender] !== undefined) { + // @ts-ignore + const iset = infs[t][gender] as T.InflectionSet; + const ipick = iset[(t === "inflections" && plural) ? 1 : 0]; + return ipick; + } + return []; +} + +export function getEnglishParticiple(entry: T.DictionaryEntry): string { + if (!entry.ec) { + console.log("errored participle"); + console.log(entry); + throw new Error("no english information for participle"); + } + const ec = parseEc(entry.ec); + const participle = ec[2]; + return (entry.ep) + ? `${participle} ${entry.ep}` + : participle; +} diff --git a/src/types/gen-g.d.ts b/src/types/gen-g.d.ts index ff3ad40..5ad94cf 100644 --- a/src/types/gen-g.d.ts +++ b/src/types/gen-g.d.ts @@ -4,12 +4,22 @@ // type FemPlurNounEntry = ""; // can change nothing // type UnisexNounEntry = ""; // can change number or gender -type VP = { + +type VPSelection = { + type: "VPSelection", subject: NPSelection, - object: NPSelection, - verb: VerbSelection, + object: Exclude, + verb: Exclude, }; +// TODO: make this Rendered with recursive Rendered<> +type VPRendered = { + type: "VPRendered", + subject: Rendered, + object: "none" | Rendered | import("@lingdocs/pashto-inflector").Types.Person.ThirdPlurMale, + verb: VerbRendered, +} + type VerbSelection = { type: "verb", verb: VerbEntry, @@ -17,6 +27,14 @@ type VerbSelection = { object: VerbObject, }; +type VerbRendered = Omit & { + ps: import("@lingdocs/pashto-inflector").Types.SingleOrLengthOpts< + import("@lingdocs/pashto-inflector").Types.PsString[] + >, + person: import("@lingdocs/pashto-inflector").Types.Person, + e?: string[], +}; + type VerbObject = // intransitive verb "none" | // transitive verb - object not selected yet @@ -34,16 +52,16 @@ type NPType = "noun" | "pronoun" | "participle"; type NounSelection = { type: "noun", entry: NounEntry, - gender: "masc" | "fem", - number: "sing" | "plur", + gender: import("@lingdocs/pashto-inflector").Types.Gender, + number: NounNumber, // TODO: Implement // adjectives: [], // TODO: Implement // possesor: NPSelection | undefined, /* method only present if it's possible to change gender */ - changeGender?: (gender: "masc" | "fem") => NounSelection, + changeGender?: (gender: import("@lingdocs/pashto-inflector").Types.Gender) => NounSelection, /* method only present if it's possible to change number */ - changeNumber?: (number: "sing" | "plur") => NounSelection, + changeNumber?: (number: NounNumber) => NounSelection, }; // take an argument for subject/object in rendering English @@ -68,7 +86,11 @@ type Rendered = ReplaceKey< Omit, "e", string -> & { inflected: boolean }; +> & { + ps: import("@lingdocs/pashto-inflector").Types.PsString[], + e?: string, + inflected: boolean, +}; // TODO: recursive changing this down into the possesor etc.