diff --git a/src/components/phrase-builder/PhraseBuilder.tsx b/src/components/phrase-builder/PhraseBuilder.tsx index 110f340..d701b85 100644 --- a/src/components/phrase-builder/PhraseBuilder.tsx +++ b/src/components/phrase-builder/PhraseBuilder.tsx @@ -8,12 +8,13 @@ import { isInvalidSubjObjCombo, } from "../../lib/np-tools"; -function verbPhraseComplete({ subject, verb }: { subject: NPSelection | undefined, verb: VerbSelection | undefined }): VPSelection | undefined { +function verbPhraseComplete({ subject, verb, shrinkServant }: { subject: NPSelection | undefined, verb: VerbSelection | undefined, shrinkServant: boolean }): VPSelection | undefined { if (!subject) return undefined; if (!verb) return undefined; if (verb.object === undefined) return undefined; return { type: "VPSelection", + shrinkServant, subject, object: verb.object, verb, @@ -23,6 +24,7 @@ function verbPhraseComplete({ subject, verb }: { subject: NPSelection | undefine export function PhraseBuilder() { const [subject, setSubject] = useState(undefined); const [verb, setVerb] = useState(undefined); + const [shrinkServant, setShrinkServant] = useState(false); function handleSubjectChange(subject: NPSelection | undefined) { const objPronoun = (typeof verb?.object === "object" && verb.object.type === "pronoun") ? verb.object.person @@ -55,7 +57,10 @@ export function PhraseBuilder() { object, }); } - const verbPhrase: VPSelection | undefined = verbPhraseComplete({ subject, verb }); + function handleShrinkServantChange(e: React.ChangeEvent) { + setShrinkServant(e.target.checked); + } + const verbPhrase: VPSelection | undefined = verbPhraseComplete({ subject, verb, shrinkServant }); return
@@ -75,6 +80,17 @@ export function PhraseBuilder() {
+ {/* TODO: make this appear conditionally */} + {(verbPhrase?.object && typeof verbPhrase.object === "object") &&
+ + +
} {verbPhrase &&
} diff --git a/src/components/phrase-builder/VPDisplay.tsx b/src/components/phrase-builder/VPDisplay.tsx index 7a14d45..36963bc 100644 --- a/src/components/phrase-builder/VPDisplay.tsx +++ b/src/components/phrase-builder/VPDisplay.tsx @@ -5,6 +5,7 @@ import { } from "@lingdocs/pashto-inflector"; function VPDisplay({ VP }: { VP: VPSelection }) { + // TODO: Possibly put the servant shrinking in here after the render const result = compileVP(renderVP(VP)); const rPs = "long" in result.ps ? result.ps.long : result.ps; return
diff --git a/src/lib/phrase-building/compile-vp.ts b/src/lib/phrase-building/compile-vp.ts index a94824a..9657220 100644 --- a/src/lib/phrase-building/compile-vp.ts +++ b/src/lib/phrase-building/compile-vp.ts @@ -2,97 +2,136 @@ import { Types as T, concatPsString, removeAccents, + grammarUnits, + getVerbBlockPosFromPerson, } from "@lingdocs/pashto-inflector"; +type ListOfEntities = T.PsString[][]; + export function compileVP(VP: VPRendered): { ps: T.SingleOrLengthOpts, 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...] + const { kids, NPs } = shrinkEntitiesAndGatherKids(VP); return { - ps: arrangePs(subj, obj, head, rest, VP.verb.negative), + ps: compilePs(NPs, head, rest, VP.verb.negative, kids), e: compileEnglish(VP), }; } -function arrangePs( - subj: T.PsString[], - obj: T.PsString[] | undefined, +function compilePs( + nps: ListOfEntities, head: T.PsString | undefined, rest: T.PsString[], negative: boolean, + kids: ListOfEntities, ): T.PsString[]; -function arrangePs( - subj: T.PsString[], - obj: T.PsString[] | undefined, +function compilePs( + nps: ListOfEntities, head: T.PsString | undefined, rest: T.SingleOrLengthOpts, negative: boolean, + kids: ListOfEntities, ): T.SingleOrLengthOpts; -function arrangePs( - subj: T.PsString[], - obj: T.PsString[] | undefined, +function compilePs( + nps: ListOfEntities, head: T.PsString | undefined, rest: T.SingleOrLengthOpts, negative: boolean, + kids: ListOfEntities, ): T.SingleOrLengthOpts { if ("long" in rest) { return { - long: arrangePs(subj, obj, head, rest.long, negative), - short: arrangePs(subj, obj, head, rest.short, negative), + long: compilePs(nps, head, rest.long, negative, kids), + short: compilePs(nps, head, rest.short, negative, kids), ...rest.mini ? { - mini: arrangePs(subj, obj, head, rest.mini, negative), + mini: compilePs(nps, head, rest.mini, negative, kids), } : {}, }; } - 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), - )) - )) - ); + const entities: ListOfEntities = [ + ...nps, + ...compileVerbWNegative(head, rest, negative) + ]; + const entitiesWKids = putKidsInKidsSection(entities, kids); + return combineEntities(entitiesWKids); +} + +function shrinkEntitiesAndGatherKids(VP: VPRendered): { kids: ListOfEntities, NPs: ListOfEntities } { + const main = { + subject: VP.subject.ps, + object: typeof VP.object === "object" ? VP.object.ps : undefined, } - return subj.flatMap(s => ( - verbWNeg.flatMap(v => ( - concatPsString(s, " ", v) + const toShrink = (VP.shrinkServant && VP.servant) + ? VP[VP.servant] + : undefined; + if (!toShrink || typeof toShrink !== "object") { + return { + kids: [], + NPs: [main.subject, ...main.object ? [main.object] : []] + }; + } + const king = main[VP.king]; + if (!king) { + throw new Error("lost the king in the shrinking process"); + } + return { + kids: [shrink(toShrink)], + NPs: [], + } +} + +function shrink(np: Rendered): T.PsString[] { + const person: T.Person = np.type === "participle" + ? T.Person.ThirdPlurMale + // @ts-ignore + : np.person; + const [row, col] = getVerbBlockPosFromPerson(person); + return grammarUnits.pronouns.mini[row][col]; +} + +function putKidsInKidsSection(entities: ListOfEntities, kids: ListOfEntities): ListOfEntities { + const first = entities[0]; + const rest = entities.slice(1); + return [ + first, + ...kids, + ...rest, + ]; +} + +function combineEntities(loe: ListOfEntities): T.PsString[] { + const first = loe[0]; + const rest = loe.slice(1); + if (!rest.length) return first; + return combineEntities(rest).flatMap(r => ( + first.map(ps => concatPsString( + ps, + ps.p === "و" ? { p: "", f: "-" } : " ", + r, )) )); } -function arrangeVerbWNeg(head: T.PsString | undefined, rest: T.PsString[], negative: boolean): T.PsString[] { - if (!negative) { - return rest.map(ps => concatPsString(head || "", ps)); - } +function compileVerbWNegative(head: T.PsString | undefined, rest: T.PsString[], negative: boolean): ListOfEntities { + if (!negative) { + return [ + ...head ? [[head]] : [], + rest, + ]; + } const nu: T.PsString = { p: "نه", f: "nú" }; if (!head) { - return rest.map(r => concatPsString(nu, " ", removeAccents(r))); + return [ + [nu], + rest.map(r => 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), - )); + // const regularPrefix = head.p === "و" || head.p === "وا"; + // if (regularPrefix) { + // dashes for oo-nu etc return [ - ...withNuAfterHead, - ...withNuBeforeHead, + [removeAccents(head)], + rest.map(r => concatPsString(nu, " ", removeAccents(r))) ]; } diff --git a/src/lib/phrase-building/render-vp.ts b/src/lib/phrase-building/render-vp.ts index 0721234..cd6a640 100644 --- a/src/lib/phrase-building/render-vp.ts +++ b/src/lib/phrase-building/render-vp.ts @@ -12,12 +12,16 @@ import { import { psStringFromEntry, } from "../text-tools"; +import { + getPersonFromNP, +} from "./vp-tools"; +import { isPattern4Entry } from "../type-predicates"; export function renderVP(VP: VPSelection): VPRendered { // Sentence Rules Logic const isPast = isPastTense(VP.verb.tense); const isTransitive = VP.object !== "none"; - const { king, /* servant */ } = getKingAndServant(isPast, isTransitive); + const { king, servant } = getKingAndServant(isPast, isTransitive); const kingPerson = getPersonFromNP(VP[king]); // TODO: more elegant way of handling this type safety if (kingPerson === undefined) { @@ -26,11 +30,16 @@ export function renderVP(VP: VPSelection): VPRendered { const subjectPerson = getPersonFromNP(VP.subject); const objectPerson = getPersonFromNP(VP.object); // TODO: also don't inflect if it's a pattern one animate noun - const inflectSubject = isPast && isTransitive; + const inflectSubject = isPast && isTransitive && !isSingularAnimatePattern4(VP.subject); const inflectObject = !isPast && isFirstOrSecondPersPronoun(VP.object); // Render Elements return { type: "VPRendered", + king, + servant, + isPast, + isTransitive, + shrinkServant: VP.shrinkServant, subject: renderNPSelection(VP.subject, inflectSubject, false, "subject"), object: renderNPSelection(VP.object, inflectObject, true, "object"), verb: renderVerbSelection(VP.verb, kingPerson, objectPerson), @@ -292,24 +301,6 @@ function getTenseVerbForm(conj: T.VerbConjugation, tense: VerbTense): T.VerbForm throw new Error("unknown tense"); } -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") { - return undefined; - } - 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 getEnglishParticiple(entry: T.DictionaryEntry): string { if (!entry.ec) { console.log("errored participle"); @@ -349,7 +340,6 @@ function getInf(infs: T.InflectorOutput, t: "plural" | "arabicPlural" | "inflect // @ts-ignore const iset = infs[t][gender] as T.InflectionSet; const inflectionNumber = (inflected ? 1 : 0) + ((t === "inflections" && plural) ? 1 : 0); - console.log({ t, plural, inflectionNumber }); return iset[inflectionNumber]; } return []; @@ -390,3 +380,10 @@ function isPerfective(t: VerbTense): boolean { } throw new Error("tense not implemented yet"); } + +function isSingularAnimatePattern4(np: NPSelection): boolean { + if (np.type !== "noun") { + return false; + } + return isPattern4Entry(np.entry) && np.entry.c.includes("anim.") && (np.number === "singular"); +} diff --git a/src/lib/phrase-building/vp-tools.ts b/src/lib/phrase-building/vp-tools.ts new file mode 100644 index 0000000..05b4eb8 --- /dev/null +++ b/src/lib/phrase-building/vp-tools.ts @@ -0,0 +1,21 @@ +import { + Types as T, +} from "@lingdocs/pashto-inflector"; + +export function getPersonFromNP(np: NPSelection): T.Person; +export function getPersonFromNP(np: NPSelection | ObjectNP): T.Person | undefined; +export function getPersonFromNP(np: NPSelection | ObjectNP): T.Person | undefined { + if (np === "none") { + return undefined; + } + 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); +} \ No newline at end of file diff --git a/src/types/gen-g.d.ts b/src/types/gen-g.d.ts index 2bec88a..e0feb83 100644 --- a/src/types/gen-g.d.ts +++ b/src/types/gen-g.d.ts @@ -10,11 +10,17 @@ type VPSelection = { subject: NPSelection, object: Exclude, verb: Exclude, + shrinkServant: boolean, }; // TODO: make this Rendered with recursive Rendered<> type VPRendered = { type: "VPRendered", + king: "subject" | "object", + servant: "subject" | "object" | undefined, + shrinkServant: boolean, + isPast: boolean, + isTransitive: boolean, subject: Rendered, object: Rendered | ObjectNP, verb: VerbRendered,