diff --git a/package.json b/package.json index 2f27906..5fe4212 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lingdocs/pashto-inflector", - "version": "2.3.4", + "version": "2.3.5", "author": "lingdocs.com", "description": "A Pashto inflection and verb conjugation engine, inculding React components for displaying Pashto text, inflections, and conjugations", "homepage": "https://verbs.lingdocs.com", diff --git a/src/App.tsx b/src/App.tsx index f3b9d6d..6683e73 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,6 +27,7 @@ import { isAdjectiveEntry, isLocativeAdverbEntry, isNounEntry } from "./lib/type import defualtTextOptions from "./lib/default-text-options"; import PhraseBuilder from "./components/vp-explorer/VPExplorer"; import useStickyState from "./lib/useStickyState"; +import { EPExplorer } from "./library"; type VerbType = "simple" | "stative compound" | "dynamic compound"; const verbTypes: VerbType[] = [ "simple", @@ -276,6 +277,11 @@ function App() { opts={textOptions} /> } +

🆕 Equative Phrase Builder

+ setShowingTextOptions(false)}> diff --git a/src/components/ConjugationViewer.tsx b/src/components/ConjugationViewer.tsx index 911760b..05cb77e 100644 --- a/src/components/ConjugationViewer.tsx +++ b/src/components/ConjugationViewer.tsx @@ -15,12 +15,14 @@ import { getForms } from "../lib/conjugation-forms"; import { conjugateVerb } from "../lib/verb-conjugation"; import PersonSelection from "./PersonSelection"; import { - personIsAllowed, - randomPerson, incrementPerson, parseEc, } from "../lib/misc-helpers"; import * as T from "../types"; +import { + randomPerson, + isInvalidSubjObjCombo, +} from "../lib/np-tools"; const VerbChoiceWarning = () => ( <> @@ -100,7 +102,7 @@ function reducer(state: State, action: Action): State { let newPerson = person; let otherPerson = state[oppositeRole(setting)]; let otherSetting = oppositeRole(setting); - while (!personIsAllowed(newPerson, otherPerson)) { + while (isInvalidSubjObjCombo(newPerson, otherPerson)) { otherPerson = incrementPerson(otherPerson); } return { ...state, [setting]: newPerson, [otherSetting]: otherPerson }; @@ -127,9 +129,9 @@ function reducer(state: State, action: Action): State { case "randomPerson": return { ...state, - [action.payload]: randomPerson( - state[action.payload === "subject" ? "object" : "subject"] - ), + [action.payload]: randomPerson({ + prev: state[action.payload === "subject" ? "object" : "subject"] + }), }; case "setShowingFormInfo": return { diff --git a/src/components/ep-explorer/EPExplorer.tsx b/src/components/ep-explorer/EPExplorer.tsx index 55724a8..1f684db 100644 --- a/src/components/ep-explorer/EPExplorer.tsx +++ b/src/components/ep-explorer/EPExplorer.tsx @@ -31,7 +31,8 @@ function EPExplorer(props: { tense: "present", negative: false, }, - }, "EPSelectionState2"); + shrunkenPossesive: undefined, + }, "EPSelectionState3"); function handlePredicateTypeChange(type: "NP" | "Complement") { setEps(o => ({ ...o, @@ -56,6 +57,12 @@ function EPExplorer(props: { }, })); } + function handleShrinkPossesive(shrunkenPossesive: number | undefined) { + setEps(o => ({ + ...o, + shrunkenPossesive, + })); + } const king = eps.subject?.type === "pronoun" ? "subject" : eps.predicate.type === "Complement" @@ -76,6 +83,8 @@ function EPExplorer(props: { {mode === "phrases" && <>
Subject {king === "subject" ? roleIcon.king : ""}
} entryFeeder={props.entryFeeder} np={eps.subject} @@ -99,6 +108,8 @@ function EPExplorer(props: { /> {eps.predicate.type === "NP" ? - {!!props.adjectives.length &&
-
Adjectives
- {!adding ?
setAdding(true)}>+ Adj.
:
} -
} {adding &&
-
+
Add Adjective
-
setAdding(false)}>Cancel
+
setAdding(false)}> + +
} - {props.adjectives.map((adj, i) => ( -
- -
-
+ {props.adjectives.map((adj, i) =>
+
+
Adjective
+
+ {!!props.adjectives.length && !adding &&
+
setAdding(true)}>+ Adj.
+
} +
+
+
- ))} + +
)} {!adding && !props.adjectives.length &&
setAdding(true)}>+ Adj.
} diff --git a/src/components/np-picker/NPParticiplePicker.tsx b/src/components/np-picker/NPParticiplePicker.tsx index 8b8e363..b8e50b3 100644 --- a/src/components/np-picker/NPParticiplePicker.tsx +++ b/src/components/np-picker/NPParticiplePicker.tsx @@ -5,6 +5,7 @@ function makeParticipleSelection(verb: T.VerbEntry): T.ParticipleSelection { return { type: "participle", verb, + possesor: undefined, }; } diff --git a/src/components/np-picker/NPPicker.tsx b/src/components/np-picker/NPPicker.tsx index 48ddcc2..b143e4f 100644 --- a/src/components/np-picker/NPPicker.tsx +++ b/src/components/np-picker/NPPicker.tsx @@ -18,8 +18,10 @@ function NPPicker(props: { onChange: (nps: T.NPSelection | undefined) => void, np: T.NPSelection | undefined, counterPart: T.NPSelection | T.VerbObject | undefined, - role: "subject" | "object" | "ergative", + role: "subject" | "object" | "ergative" | "possesor", opts: T.TextOptions, + handleShrinkPossesive: (uid: number | undefined) => void, + shrunkenPossesiveInPhrase: number | undefined, cantClear?: boolean, is2ndPersonPicker?: boolean, entryFeeder: T.EntryFeeder, @@ -27,6 +29,7 @@ function NPPicker(props: { if (props.is2ndPersonPicker && ((props.np?.type !== "pronoun") || !isSecondPerson(props.np.person))) { throw new Error("can't use 2ndPerson NPPicker without a pronoun"); } + const [addingPoss, setAddingPoss] = useState(false); const [npType, setNpType] = useState(props.np ? props.np.type : undefined); useEffect(() => { setNpType(props.np ? props.np.type : undefined); @@ -51,10 +54,32 @@ function NPPicker(props: { setNpType(ntp); } } + function handlePossesiveChange(p: T.NPSelection | undefined) { + if (!props.np || props.np.type === "pronoun") return; + if (!p) { + props.onChange({ + ...props.np, + possesor: undefined, + }); + return; + } + const isNewPosesser = checkForNewPossesor(p, props.np.possesor); + const possesor = { + np: p, + uid: (!isNewPosesser && props.np.possesor) ? props.np.possesor.uid : makeUID(), + }; + props.onChange({ + ...props.np, + possesor, + }); + } const isDynamicComplement = props.np && props.np.type === "noun" && props.np.dynamicComplement; const clearButton = (!props.cantClear && !props.is2ndPersonPicker && !isDynamicComplement) ? :
; + const possesiveUid = (props.np && props.np.type !== "pronoun" && props.np.possesor) + ? props.np.possesor.uid + : undefined; return <>
@@ -68,21 +93,54 @@ function NPPicker(props: {
- {!npType &&
-
- Choose NP -
- {npTypes.map((npt) =>
- + {!npType &&
+
+ Choose NP +
+ {npTypes.map((npt) =>
+
)}
} + {(props.np && props.np.type !== "pronoun" && (props.np.possesor || addingPoss)) &&
+
+
Possesive:
+ {props.np.possesor &&
{ + props.handleShrinkPossesive(possesiveUid === props.shrunkenPossesiveInPhrase + ? undefined + : possesiveUid + ); + }}> + {possesiveUid === props.shrunkenPossesiveInPhrase ? "👶 Shrunken" : "Shrink"} +
} +
{ + setAddingPoss(false); + handlePossesiveChange(undefined); + }}> + +
+
+ +
} + {(npType === "noun" || npType === "participle") && props.np && !addingPoss &&
+ setAddingPoss(true)}>+ Possesive +
} {(npType === "pronoun" && props.np?.type === "pronoun") ? ; } -// {(npType && !isDynamicComplement) && } +function checkForNewPossesor(n: T.NPSelection | undefined, old: T.PossesorSelection | undefined): boolean { + if (!old || !n) { + return true; + } + if (n.type !== old.np.type) { + return true; + } + if (n.type === "pronoun") return false; + if (n.type === "noun" && old.np.type === "noun") { + return n.entry.ts !== old.np.entry.ts; + } + if (n.type === "participle" && old.np.type === "participle") { + return n.verb.entry.ts !== old.np.verb.entry.ts; + } + return false; +} + +// TODO: BETTER UID +function makeUID() { + return Math.floor(Math.random() * 50000); +} export default NPPicker; \ No newline at end of file diff --git a/src/components/np-picker/NPPronounPicker.tsx b/src/components/np-picker/NPPronounPicker.tsx index 1d3fce2..2bf8d3a 100644 --- a/src/components/np-picker/NPPronounPicker.tsx +++ b/src/components/np-picker/NPPronounPicker.tsx @@ -12,7 +12,7 @@ const gColors = { }; // TODO: better logic on this -const labels = (role: "subject" | "object" | "ergative") => ({ +const labels = (role: "subject" | "object" | "ergative" | "possesor") => ({ // persons: [ // ["1st", "1st pl."], // ["2nd", "2nd pl."], @@ -20,11 +20,15 @@ const labels = (role: "subject" | "object" | "ergative") => ({ // ], e: role === "object" ? [ ["me", "us"], - ["you", "you pl."], + ["you", "y'all"], [{ masc: "him/it", fem: "her/it"}, "them"], + ] : role === "possesor" ? [ + ["my", "our"], + ["your", "y'all's"], + [{ masc: "his/its", fem: "her/its"}, "their"], ] : [ ["I", "We"], - ["You", "You pl."], + ["You", "Y'all"], [{ masc: "He/It", fem: "She/It"}, "They"], ], p: role === "subject" ? { @@ -49,6 +53,17 @@ const labels = (role: "subject" | "object" | "ergative") => ({ ["ته", "تاسو"], [{ masc: "دهٔ", fem: "دې" }, "دوي"], ], + } : role === "possesor" ? { + far: [ + ["زما", "زمونږ"], + ["ستا", "ستاسو"], + [{ masc: "د هغهٔ", fem: "د هغې" }, "د هغوي"], + ], + near: [ + ["زما", "زمونږ"], + ["ستا", "ستاسو"], + [{ masc: "د دهٔ", fem: "د دې" }, "د دوي"], + ], } : { far: [ ["ما", "مونږ"], @@ -79,11 +94,10 @@ function pickerStateToPerson(s: PickerState): T.Person { + (6 * s.col); } -function NPPronounPicker({ onChange, pronoun, role, clearButton, opts, is2ndPersonPicker }: { +function NPPronounPicker({ onChange, pronoun, role, opts, is2ndPersonPicker }: { pronoun: T.PronounSelection, onChange: (p: T.PronounSelection) => void, - role: "object" | "subject" | "ergative", - clearButton?: JSX.Element, + role: "object" | "subject" | "ergative" | "possesor", opts: T.TextOptions, is2ndPersonPicker?: boolean, }) { diff --git a/src/components/np-picker/picker-tools.tsx b/src/components/np-picker/picker-tools.tsx index c846fd2..f7d780d 100644 --- a/src/components/np-picker/picker-tools.tsx +++ b/src/components/np-picker/picker-tools.tsx @@ -106,6 +106,7 @@ export function makeNounSelection(entry: T.NounEntry, dynamicComplement?: true): number, numberCanChange: number === "singular", adjectives: [], + possesor: undefined, dynamicComplement, }; } \ No newline at end of file diff --git a/src/components/vp-explorer/VPExplorer.tsx b/src/components/vp-explorer/VPExplorer.tsx index 7f0dbfb..478b2b6 100644 --- a/src/components/vp-explorer/VPExplorer.tsx +++ b/src/components/vp-explorer/VPExplorer.tsx @@ -45,7 +45,7 @@ export function VPExplorer(props: { }) { const [vps, setVps] = useStickyState( savedVps => makeVPSelectionState(props.verb, savedVps), - "vpsState5", + "vpsState6", ); const [mode, setMode] = useStickyState<"charts" | "phrases" | "quiz">( savedMode => { @@ -97,6 +97,12 @@ export function VPExplorer(props: { if (vps.verb?.isCompound === "dynamic") return; setVps(switchSubjObj) } + function handleShrinkPossesive(shrunkenPossesive: number | undefined) { + setVps(o => ({ + ...o, + shrunkenPossesive, + })); + } function quizLock(f: T) { if (mode === "quiz") { return () => { @@ -142,10 +148,12 @@ export function VPExplorer(props: { ? "ergative" : "subject" } + shrunkenPossesiveInPhrase={vps.shrunkenPossesive} is2ndPersonPicker={vps.verb.tenseCategory === "imperative"} np={vps.subject} counterPart={vps.verb ? vps.verb.object : undefined} onChange={handleSubjectChange} + handleShrinkPossesive={handleShrinkPossesive} opts={props.opts} />
@@ -153,6 +161,8 @@ export function VPExplorer(props: { {(typeof vps.verb.object === "number") ?
Unspoken 3rd Pers. Masc. Plur.
: setShowingExplanation({ role: "king", item: "object" })}>Object {roleIcon.king}
:
setShowingExplanation({ role: "servant", item: "object" })}>Object {roleIcon.servant}
} diff --git a/src/components/vp-explorer/VPExplorerQuiz.tsx b/src/components/vp-explorer/VPExplorerQuiz.tsx index 945b50f..73fd8e4 100644 --- a/src/components/vp-explorer/VPExplorerQuiz.tsx +++ b/src/components/vp-explorer/VPExplorerQuiz.tsx @@ -445,6 +445,7 @@ function getRandomVPSelection(mix: MixType = "both") { return { subject: subject !== undefined ? subject : randSubj, verb: randomizeTense(verb, true), + shrunkenPossesive: undefined, } } const v: T.VerbSelectionComplete = { @@ -460,6 +461,7 @@ function getRandomVPSelection(mix: MixType = "both") { return { subject: randSubj, verb: randomizeTense(v, true), + shrunkenPossesive: undefined, }; }; }; diff --git a/src/components/vp-explorer/verb-selection.ts b/src/components/vp-explorer/verb-selection.ts index 7fe32ca..b7d3e3a 100644 --- a/src/components/vp-explorer/verb-selection.ts +++ b/src/components/vp-explorer/verb-selection.ts @@ -95,5 +95,6 @@ export function makeVPSelectionState( }, } : {}, }, + shrunkenPossesive: os ? os.shrunkenPossesive : undefined, }; } diff --git a/src/lib/misc-helpers.ts b/src/lib/misc-helpers.ts index 125531d..e5a5e9a 100644 --- a/src/lib/misc-helpers.ts +++ b/src/lib/misc-helpers.ts @@ -162,41 +162,8 @@ export function randFromArray(arr: M[]): M { ]; } -// TODO: deprecate this because we have it in np-tools? -/** - * Sees if a possiblePerson (for subject/object) is possible, given the other person - * - * @param possiblePerson - * @param existingPerson - */ -export function personIsAllowed(possiblePerson: T.Person, existingPerson?: T.Person): boolean { - const isFirstPerson = (p: T.Person) => [0, 1, 6, 7].includes(p); - const isSecondPerson = (p: T.Person) => [2, 3, 8, 9].includes(p); - // can't have both subject and object be 1st person - if (isFirstPerson(possiblePerson) && (existingPerson && isFirstPerson(existingPerson))) { - return false; - } - // can't have both subject and object be 2nd person - if (isSecondPerson(possiblePerson) && (existingPerson && isSecondPerson(existingPerson))) { - return false; - } - // otherwise it's ok - return true; -} - -// TODO: deprecate this because we have it in np-tools? -/** - * Picks a random person while assuring that the other person is not in conflict - * - * @param other - */ -export function randomPerson(other?: T.Person): T.Person { - let newPerson: T.Person; - do { - newPerson = randomNumber(0, 12); - } while(!personIsAllowed(newPerson, other)); - return newPerson; -} +export const isFirstPerson = (p: T.Person) => [0, 1, 6, 7].includes(p); +export const isSecondPerson = (p: T.Person) => [2, 3, 8, 9].includes(p); export function incrementPerson(p: T.Person): T.Person { return (p + 1) % 12; diff --git a/src/lib/np-tools.ts b/src/lib/np-tools.ts index 57ed9e4..68cb3b4 100644 --- a/src/lib/np-tools.ts +++ b/src/lib/np-tools.ts @@ -1,5 +1,6 @@ import * as T from "../types"; -import { parseEc } from "../lib/misc-helpers"; +import { isFirstPerson, parseEc } from "../lib/misc-helpers"; +import { isSecondPerson } from "./phrase-building/vp-tools"; function getRandPers(): T.Person { return Math.floor(Math.random() * 12); @@ -31,22 +32,10 @@ export function randomPerson(a?: { prev?: T.Person, counterPart?: T.VerbObject | } export function isInvalidSubjObjCombo(subj: T.Person, obj: T.Person): boolean { - const firstPeople = [ - T.Person.FirstSingMale, - T.Person.FirstSingFemale, - T.Person.FirstPlurMale, - T.Person.FirstPlurFemale, - ]; - const secondPeople = [ - T.Person.SecondSingMale, - T.Person.SecondSingFemale, - T.Person.SecondPlurMale, - T.Person.SecondPlurFemale, - ]; return ( - (firstPeople.includes(subj) && firstPeople.includes(obj)) + (isFirstPerson(subj) && isFirstPerson(obj)) || - (secondPeople.includes(subj) && secondPeople.includes(obj)) + (isSecondPerson(subj) && isSecondPerson(obj)) ); } diff --git a/src/lib/phrase-building/compile-ep.ts b/src/lib/phrase-building/compile-ep.ts index 6bca117..fcbc38d 100644 --- a/src/lib/phrase-building/compile-ep.ts +++ b/src/lib/phrase-building/compile-ep.ts @@ -12,6 +12,11 @@ import { } from "./segment"; import { removeAccents } from "../accent-helpers"; import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools"; +import { + orderKidsSection, + findPossesiveToShrink, + shrinkNP, +} from "./compile-tools"; export function compileEP(EP: T.EPRendered, form: T.FormVersion): { ps: T.SingleOrLengthOpts, e?: string[] }; export function compileEP(EP: T.EPRendered, form: T.FormVersion, combineLengths: true): { ps: T.PsString[], e?: string[] }; @@ -37,12 +42,20 @@ function getSegmentsAndKids(EP: T.EPRendered, form: T.FormVersion): { kids: Segm } return [s]; } - const subject = makeSegment(getPashtoFromRendered(EP.subject)); - const predicate = makeSegment(getPashtoFromRendered(EP.predicate)); + const possToShrink = findPossesiveToShrink(EP); + const shrunkenPossAllowed = !(form.removeKing && possToShrink?.role === "king"); + const possUid = shrunkenPossAllowed ? EP.shrunkenPossesive : undefined; + const subject = makeSegment(getPashtoFromRendered(EP.subject, possUid, false)); + const predicate = makeSegment(getPashtoFromRendered(EP.predicate, possUid, false)); return { - kids: EP.equative.hasBa - ? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])] - : [], + kids: orderKidsSection([ + ...EP.equative.hasBa + ? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])] + : [], + ...(possToShrink && shrunkenPossAllowed) + ? [shrinkNP(possToShrink)] + : [], + ]), NPs: [ ...ifNotRemoved(subject, "subject"), ...ifNotRemoved(predicate, "predicate"), diff --git a/src/lib/phrase-building/compile-tools.ts b/src/lib/phrase-building/compile-tools.ts new file mode 100644 index 0000000..d5d5a5f --- /dev/null +++ b/src/lib/phrase-building/compile-tools.ts @@ -0,0 +1,63 @@ +import * as T from "../../types"; +import { + Segment, + makeSegment, +} from "./segment"; +import { getVerbBlockPosFromPerson } from "../misc-helpers"; +import { pronouns } from "../grammar-units"; + +export function orderKidsSection(kids: Segment[]): Segment[] { + const sorted = [...kids]; + return sorted.sort((a, b) => { + // ba first + if (a.isBa) return -1; + // kinds lined up 1st 2nd 3rd person + if (a.isMiniPronoun && b.isMiniPronoun) { + if (a.isMiniPronoun < b.isMiniPronoun) { + return -1; + } + if (a.isMiniPronoun > b.isMiniPronoun) { + return 1; + } + // TODO: is this enough? + return 0; + } + return 0; + }); +} + +export function findPossesiveToShrink(VP: T.VPRendered | T.EPRendered): T.Rendered | undefined { + const uid = VP.shrunkenPossesive; + function findPossesiveInNP(NP: T.Rendered | T.ObjectNP | undefined): T.Rendered | undefined { + if (NP === undefined) return undefined; + if (typeof NP !== "object") return undefined; + if (!NP.possesor) return undefined; + if (NP.possesor.uid === uid) { + return NP.possesor.np; + } + return findPossesiveInNP(NP.possesor.np); + } + if (uid === undefined) return undefined; + const objPred: T.Rendered | undefined = ("object" in VP) + ? (typeof VP.object === "object" ? VP.object : undefined) + : (VP.predicate.type === "noun" || VP.predicate.type === "participle" || VP.predicate.type === "pronoun") + // typescript is dumb here; + ? VP.predicate as T.Rendered + : undefined; + return ( + findPossesiveInNP(VP.subject) + || + findPossesiveInNP(objPred) + ); +} + +export function shrinkNP(np: T.Rendered): Segment { + function getFirstSecThird(): 1 | 2 | 3 { + if ([0, 1, 6, 7].includes(np.person)) return 1; + if ([2, 3, 8, 9].includes(np.person)) return 2; + return 3; + } + const [row, col] = getVerbBlockPosFromPerson(np.person); + return makeSegment(pronouns.mini[row][col], ["isKid", getFirstSecThird()]); +} + diff --git a/src/lib/phrase-building/compile-vp.ts b/src/lib/phrase-building/compile-vp.ts index 9201c0a..a03d72a 100644 --- a/src/lib/phrase-building/compile-vp.ts +++ b/src/lib/phrase-building/compile-vp.ts @@ -1,6 +1,6 @@ import * as T from "../../types"; import { - concatPsString, + concatPsString, psStringEquals, } from "../p-text-helpers"; import { Segment, @@ -13,7 +13,6 @@ import { import { removeAccents, } from "../accent-helpers"; -import { getVerbBlockPosFromPerson } from "../misc-helpers"; import * as grammarUnits from "../grammar-units"; import { removeBa, @@ -21,6 +20,11 @@ import { } from "./vp-tools"; import { isImperativeTense, isModalTense, isPerfectTense } from "../type-predicates"; import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools"; +import { + orderKidsSection, + findPossesiveToShrink, + shrinkNP, +} from "./compile-tools"; type Form = T.FormVersion & { OSV?: boolean }; export function compileVP(VP: T.VPRendered, form: Form): { ps: T.SingleOrLengthOpts, e?: string [] }; @@ -79,20 +83,33 @@ function compilePs({ NPs, kids, verb: { head, rest }, VP }: CompilePsInput): T.S } function getSegmentsAndKids(VP: T.VPRendered, form: Form): { kids: Segment[], NPs: Segment[][] } { - const SO = { - subject: getPashtoFromRendered(VP.subject), - object: typeof VP.object === "object" ? getPashtoFromRendered(VP.object) : undefined, - } const removeKing = form.removeKing && !(VP.isCompound === "dynamic" && VP.isPast); const shrinkServant = form.shrinkServant && !(VP.isCompound === "dynamic" && !VP.isPast); - - const toShrink = (() => { + const toShrinkServant = (() => { if (!shrinkServant) return undefined; if (!VP.servant) return undefined; const servant = VP[VP.servant]; if (typeof servant !== "object") return undefined; return servant; })(); + const shrunkenServant = toShrinkServant ? shrinkNP(toShrinkServant) : undefined; + const possToShrink = findPossesiveToShrink(VP); + const shrunkenPossesive = possToShrink ? shrinkNP(possToShrink) : undefined; + const shrunkenPossAllowed = possToShrink && shrunkenPossesive && ( + !shrunkenServant || !psStringEquals(shrunkenPossesive.ps[0], shrunkenServant.ps[0]) + ) && ( + // can only shrink the possesive if the parent of the possesive is still in full form + !(possToShrink.role === "king" && removeKing) + && + !(possToShrink.role === "servant" && shrinkServant) + ); + const shrinkPossUid = shrunkenPossAllowed + ? VP.shrunkenPossesive + : undefined; + const SO = { + subject: getPashtoFromRendered(VP.subject, shrinkPossUid, false), + object: typeof VP.object === "object" ? getPashtoFromRendered(VP.object, shrinkPossUid, VP.subject.person) : undefined, + }; function getSegment(t: "subject" | "object"): Segment | undefined { const word = (VP.servant === t) ? (!shrinkServant ? SO[t] : undefined) @@ -106,12 +123,14 @@ function getSegmentsAndKids(VP: T.VPRendered, form: Form): { kids: Segment[], NP const object = getSegment("object"); return { - kids: [ + kids: orderKidsSection([ ...VP.verb.hasBa ? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])] : [], - ...toShrink - ? [shrinkNP(toShrink)] : [], - ], + ...shrunkenServant + ? [shrunkenServant] : [], + ...(shrunkenPossesive && shrunkenPossAllowed) + ? [shrunkenPossesive] : [], + ]), NPs: [ [ ...subject ? [subject] : [], @@ -236,11 +255,6 @@ function arrangeVerbWNegative(head: T.PsString | undefined, restRaw: T.PsString[ ]; } -function shrinkNP(np: T.Rendered): Segment { - const [row, col] = getVerbBlockPosFromPerson(np.person); - return makeSegment(grammarUnits.pronouns.mini[row][col], ["isKid", "isMiniPronoun"]); -} - function mergeSegments(s1: Segment, s2: Segment, noSpace?: "no space"): Segment { if (noSpace) { return s2.adjust({ ps: (p) => concatPsString(s1.ps[0], p) }); @@ -295,4 +309,3 @@ function compileEnglish(VP: T.VPRendered): string[] | undefined { })) : undefined; } - diff --git a/src/lib/phrase-building/np-tools.ts b/src/lib/phrase-building/np-tools.ts index 8b0d989..f08550e 100644 --- a/src/lib/phrase-building/np-tools.ts +++ b/src/lib/phrase-building/np-tools.ts @@ -1,7 +1,11 @@ +import { + isFirstPerson, + isSecondPerson, +} from "../../lib/misc-helpers"; import * as T from "../../types"; import { concatPsString } from "../p-text-helpers"; -export function getPashtoFromRendered(np: T.Rendered): T.PsString[] { +function getBaseAndAdjectives(np: T.Rendered): T.PsString[] { const adjs = np.adjectives; if (!adjs) { return np.ps; @@ -18,25 +22,106 @@ export function getPashtoFromRendered(np: T.Rendered): string | undefined { - if (!np.e) return undefined; - if (np.type === "noun") { - try { - // split out the atricles so adjectives can be stuck inbetween them and the word - const chunks = np.e.split("the)"); - const [articles, word] = chunks.length === 1 - ? ["", np.e] - : [chunks[0] + "the) ", chunks[1]]; - const adjs = !np.adjectives - ? "" - : np.adjectives.reduce((accum, curr): string => { - if (!curr.e) throw new Error("no english for adjective"); - return accum + curr.e + " "; - }, ""); - return `${articles}${adjs}${word}`; - } catch (e) { - return undefined; - } +export function getPashtoFromRendered(np: T.Rendered, shrunkenPossesive: number | undefined, subjectsPerson: false | T.Person): T.PsString[] { + const base = getBaseAndAdjectives(np); + if (!np.possesor || np.possesor.uid === shrunkenPossesive) { + return base; } - return np.e; + return addPossesor(np.possesor.np, base, subjectsPerson); +} + + +function addPossesor(owner: T.Rendered, existing: T.PsString[], subjectsPerson: false | T.Person): T.PsString[] { + function willBeReflexive(subj: T.Person, obj: T.Person): boolean { + return ( + ([0, 1].includes(subj) && [0, 1].includes(obj)) + || + ([2, 3].includes(subj) && [8, 9].includes(obj)) + ); + } + const wPossesor = existing.flatMap(ps => ( + getBaseAndAdjectives(owner).map(v => ( + (owner.type === "pronoun" && subjectsPerson !== false && willBeReflexive(subjectsPerson, owner.person)) + ? concatPsString({ p: "خپل", f: "khpul" }, " ", ps) + : (owner.type === "pronoun" && isFirstPerson(owner.person)) + ? concatPsString({ p: "ز", f: "z" }, v, " ", ps) + : (owner.type === "pronoun" && isSecondPerson(owner.person)) + ? concatPsString({ p: "س", f: "s" }, v, " ", ps) + : concatPsString({ p: "د", f: "du" }, " ", v, " ", ps) + )) + )); + if (!owner.possesor) { + return wPossesor; + } + return addPossesor(owner.possesor.np, wPossesor, subjectsPerson); +} + +function addArticlesAndAdjs(np: T.Rendered): string | undefined { + if (!np.e) return undefined; + try { + // split out the atricles so adjectives can be stuck inbetween them and the word + const chunks = np.e.split("the)"); + const [articles, word] = chunks.length === 1 + ? ["", np.e] + : [chunks[0] + "the) ", chunks[1]]; + const adjs = !np.adjectives + ? "" + : np.adjectives.reduce((accum, curr): string => { + if (!curr.e) throw new Error("no english for adjective"); + return accum + curr.e + " "; + }, ""); + return `${articles}${adjs}${word}`; + } catch (e) { + return undefined; + } +} + +function addPossesors(possesor: T.Rendered | undefined, base: string | undefined): string | undefined { + function removeArticles(s: string): string { + return s.replace("(the) ", "").replace("(a/the) ", ""); + } + if (!base) return undefined; + if (!possesor) return base; + if (possesor.type === "pronoun") { + return `${pronounPossEng(possesor.person)} ${removeArticles(base)}`; + } + const possesorE = getEnglishFromRendered(possesor); + if (!possesorE) return undefined; + return `${possesorE}'s ${removeArticles(base)}`; +} + +function pronounPossEng(p: T.Person): string { + if (p === T.Person.FirstSingMale || p === T.Person.FirstSingFemale) { + return "my"; + } + if (p === T.Person.FirstPlurMale || p === T.Person.FirstPlurFemale) { + return "our"; + } + if (p === T.Person.SecondSingMale || p === T.Person.SecondSingFemale) { + return "your"; + } + if (p === T.Person.SecondPlurMale || p === T.Person.SecondPlurFemale) { + return "your (pl.)"; + } + if (p === T.Person.ThirdSingMale) { + return "his/its"; + } + if (p === T.Person.ThirdSingFemale) { + return "her/its"; + } + return "their"; +} + +export function getEnglishFromRendered(r: T.Rendered): string | undefined { + if (!r.e) return undefined; + if (r.type === "loc. adv." || r.type === "adjective") { + return r.e; + } + if (r.type === "noun") { + // TODO: shouldn't have to do this 'as' - should be automatically narrowing + const np = r as T.Rendered; + return addPossesors(np.possesor?.np, addArticlesAndAdjs(np)); + } + // TODO: possesives in English for participles and pronouns too! + return r.e; } \ No newline at end of file diff --git a/src/lib/phrase-building/render-adj.ts b/src/lib/phrase-building/render-adj.ts index a38f668..f5d2b49 100644 --- a/src/lib/phrase-building/render-adj.ts +++ b/src/lib/phrase-building/render-adj.ts @@ -17,7 +17,7 @@ function chooseInflection(inflections: T.UnisexSet, pers: T.Per return inflections[gender][infNumber]; } -export function renderAdjectiveSelection(a: T.AdjectiveSelection, person: T.Person, inflected: boolean): T.Rendered { +export function renderAdjectiveSelection(a: T.AdjectiveSelection, person: T.Person, inflected: boolean, role: "king" | "servant" | "none"): T.Rendered { const infs = inflectWord(a.entry); const eWord = getEnglishWord(a.entry); const e = !eWord @@ -31,6 +31,7 @@ export function renderAdjectiveSelection(a: T.AdjectiveSelection, person: T.Pers ps: [psStringFromEntry(a.entry)], e, inflected: false, + role, person, } if (!infs.inflections || !isUnisexSet(infs.inflections)) { @@ -42,6 +43,7 @@ export function renderAdjectiveSelection(a: T.AdjectiveSelection, person: T.Pers ps: chooseInflection(infs.inflections, person, inflected), e, inflected: false, + role, person, }; } \ No newline at end of file diff --git a/src/lib/phrase-building/render-ep.ts b/src/lib/phrase-building/render-ep.ts index 306bbe3..6307cf0 100644 --- a/src/lib/phrase-building/render-ep.ts +++ b/src/lib/phrase-building/render-ep.ts @@ -8,12 +8,17 @@ import { getPersonFromVerbForm } from "../../lib/misc-helpers"; import { getVerbBlockPosFromPerson } from "../misc-helpers"; import { getEnglishWord } from "../get-english-word"; import { psStringFromEntry } from "../p-text-helpers"; -import { personGender, personIsPlural } from "../../library"; import { isLocativeAdverbEntry } from "../type-predicates"; import { renderAdjectiveSelection } from "./render-adj"; export function renderEP(EP: T.EPSelectionComplete): T.EPRendered { - const kingPerson = (EP.subject.type === "pronoun") + const king = (EP.subject.type === "pronoun") + ? "subject" + : EP.predicate.type === "NP" + ? "predicate" + : "subject"; + // TODO: less repetative logic + const kingPerson = king === "subject" ? getPersonFromNP(EP.subject) : EP.predicate.type === "NP" ? getPersonFromNP(EP.predicate.selection) @@ -21,12 +26,13 @@ export function renderEP(EP: T.EPSelectionComplete): T.EPRendered { return { type: "EPRendered", king: EP.predicate.type === "Complement" ? "subject" : "predicate", - subject: renderNPSelection(EP.subject, false, false, "subject"), + subject: renderNPSelection(EP.subject, false, false, "subject", king === "subject" ? "king" : "none"), predicate: EP.predicate.type === "NP" - ? renderNPSelection(EP.predicate.selection, false, true, "subject") + ? renderNPSelection(EP.predicate.selection, false, true, "subject", "king") : renderEqCompSelection(EP.predicate.selection, kingPerson), equative: renderEquative(EP.equative, kingPerson), englishBase: equativeBuilders[EP.equative.tense](kingPerson, EP.equative.negative), + shrunkenPossesive: EP.shrunkenPossesive, }; } @@ -68,10 +74,11 @@ function renderEqCompSelection(s: T.EqCompSelection, person: T.Person): T.Render e, inflected: false, person, + role: "none", }; } if (s.type === "adjective") { - return renderAdjectiveSelection(s, person, false) + return renderAdjectiveSelection(s, person, false, "none") } throw new Error("invalid EqCompSelection"); } @@ -136,9 +143,9 @@ function getEnglishConj(p: T.Person, e: string | T.EnglishBlock): string { return e[row][col]; } -function chooseInflection(inflections: T.UnisexSet, pers: T.Person): T.ArrayOneOrMore { - const gender = personGender(pers); - const plural = personIsPlural(pers); - return inflections[gender][plural ? 1 : 0]; -} +// function chooseInflection(inflections: T.UnisexSet, pers: T.Person): T.ArrayOneOrMore { +// const gender = personGender(pers); +// const plural = personIsPlural(pers); +// return inflections[gender][plural ? 1 : 0]; +// } diff --git a/src/lib/phrase-building/render-np.ts b/src/lib/phrase-building/render-np.ts index d1348e6..c64d909 100644 --- a/src/lib/phrase-building/render-np.ts +++ b/src/lib/phrase-building/render-np.ts @@ -12,10 +12,11 @@ import { import { parseEc } from "../misc-helpers"; import { getEnglishWord } from "../get-english-word"; import { renderAdjectiveSelection } from "./render-adj"; +import { isPattern5Entry, isUnisexNounEntry } from "../type-predicates"; -export function renderNPSelection(NP: T.NPSelection, inflected: boolean, inflectEnglish: boolean, role: "subject"): T.Rendered -export function renderNPSelection(NP: T.NPSelection | T.ObjectNP, inflected: boolean, inflectEnglish: boolean, role: "object"): T.Rendered | T.Person.ThirdPlurMale | "none"; -export function renderNPSelection(NP: T.NPSelection | T.ObjectNP, inflected: boolean, inflectEnglish: boolean, role: "subject" | "object"): T.Rendered | T.Person.ThirdPlurMale | "none" { +export function renderNPSelection(NP: T.NPSelection, inflected: boolean, inflectEnglish: boolean, role: "subject", soRole: "servant" | "king" | "none"): T.Rendered; +export function renderNPSelection(NP: T.NPSelection | T.ObjectNP, inflected: boolean, inflectEnglish: boolean, role: "object", soRole: "servant" | "king" | "none"): T.Rendered | T.Person.ThirdPlurMale | "none"; +export function renderNPSelection(NP: T.NPSelection | T.ObjectNP, inflected: boolean, inflectEnglish: boolean, role: "subject" | "object", soRole: "servant" | "king" | "none"): T.Rendered | T.Person.ThirdPlurMale | "none" { if (typeof NP !== "object") { if (role !== "object") { throw new Error("ObjectNP only allowed for objects"); @@ -23,18 +24,18 @@ export function renderNPSelection(NP: T.NPSelection | T.ObjectNP, inflected: boo return NP; } if (NP.type === "noun") { - return renderNounSelection(NP, inflected); + return renderNounSelection(NP, inflected, soRole); } if (NP.type === "pronoun") { - return renderPronounSelection(NP, inflected, inflectEnglish); + return renderPronounSelection(NP, inflected, inflectEnglish, soRole); } if (NP.type === "participle") { - return renderParticipleSelection(NP, inflected) + return renderParticipleSelection(NP, inflected, soRole) } throw new Error("unknown NP type"); }; -function renderNounSelection(n: T.NounSelection, inflected: boolean): T.Rendered { +function renderNounSelection(n: T.NounSelection, inflected: boolean, role: "servant" | "king" | "none"): T.Rendered { const english = getEnglishFromNoun(n.entry, n.number); const pashto = ((): T.PsString[] => { const infs = inflectWord(n.entry); @@ -52,32 +53,51 @@ function renderNounSelection(n: T.NounSelection, inflected: boolean): T.Rendered const person = getPersonNumber(n.gender, n.number); return { ...n, - adjectives: n.adjectives.map(a => renderAdjectiveSelection(a, person, inflected)), + adjectives: n.adjectives.map(a => renderAdjectiveSelection(a, person, inflected, role)), person, inflected, + role, ps: pashto, e: english, + possesor: renderPossesor(n.possesor, role), }; } -function renderPronounSelection(p: T.PronounSelection, inflected: boolean, englishInflected: boolean): T.Rendered { +function renderPronounSelection(p: T.PronounSelection, inflected: boolean, englishInflected: boolean, role: "servant" | "king" | "none"): T.Rendered { const [row, col] = getVerbBlockPosFromPerson(p.person); return { ...p, inflected, + role, ps: grammarUnits.pronouns[p.distance][inflected ? "inflected" : "plain"][row][col], e: grammarUnits.persons[p.person].label[englishInflected ? "object" : "subject"], }; } -function renderParticipleSelection(p: T.ParticipleSelection, inflected: boolean): T.Rendered { +function renderParticipleSelection(p: T.ParticipleSelection, inflected: boolean, role: "servant" | "king" | "none"): T.Rendered { return { ...p, inflected, + role, person: T.Person.ThirdPlurMale, // 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), + possesor: renderPossesor(p.possesor, role), + }; +} + +function renderPossesor(possesor: { np: T.NPSelection, uid: number } | undefined, possesorRole: "servant" | "king" | "none"): { np: T.Rendered, uid: number } | undefined { + if (!possesor) return undefined; + return { + uid: possesor.uid, + np: renderNPSelection( + possesor.np, + !(possesor.np.type === "noun" && isUnisexNounEntry(possesor.np.entry) && isPattern5Entry(possesor.np.entry)), + false, + "subject", + possesorRole, + ), }; } diff --git a/src/lib/phrase-building/render-vp.ts b/src/lib/phrase-building/render-vp.ts index f52a8ed..c582884 100644 --- a/src/lib/phrase-building/render-vp.ts +++ b/src/lib/phrase-building/render-vp.ts @@ -47,11 +47,12 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered { type: "VPRendered", king, servant, + shrunkenPossesive: VP.shrunkenPossesive, isPast, isTransitive, isCompound: VP.verb.isCompound, - subject: renderNPSelection(VP.subject, inflectSubject, false, "subject"), - object: renderNPSelection(VP.verb.object, inflectObject, true, "object"), + subject: renderNPSelection(VP.subject, inflectSubject, false, "subject", king === "subject" ? "king" : "servant"), + object: renderNPSelection(VP.verb.object, inflectObject, true, "object", king === "object" ? "king" : "servant"), verb: renderVerbSelection(VP.verb, kingPerson, objectPerson), englishBase: renderEnglishVPBase({ subjectPerson, diff --git a/src/lib/phrase-building/segment.ts b/src/lib/phrase-building/segment.ts index fff5cf3..a0c3da2 100644 --- a/src/lib/phrase-building/segment.ts +++ b/src/lib/phrase-building/segment.ts @@ -12,7 +12,7 @@ type SegmentDescriptions = { isVerbHead?: boolean, isOoOrWaaHead?: boolean, isVerbRest?: boolean, - isMiniPronoun?: boolean, + isMiniPronoun?: number, isKid?: boolean, // TODO: Simplify to just isKidAfterHead? isKidBetweenHeadAndRest?: boolean, @@ -25,28 +25,23 @@ export type Segment = { ps: T.PsString[] } & SegmentDescriptions & { adjust: (o: { ps?: T.PsString | T.PsString[] | ((ps: T.PsString) => T.PsString), desc?: SDT[] }) => Segment, }; -function addAdjectives(ps: T.PsString[], adjectives?: T.Rendered[]): T.PsString[] { - if (!adjectives) return ps; - return ps.map(p => { - // TODO: more thorough use of all possible variends? - return concatPsString(...adjectives.map(a => a.ps[0]), p); - }); -} export function makeSegment( - input: T.Rendered | T.PsString | T.PsString[], - options?: (keyof SegmentDescriptions)[], + input: T.PsString | T.PsString[], + options?: (keyof SegmentDescriptions | 1 | 2 | 3)[], ): Segment { const ps: T.PsString[] = Array.isArray(input) ? input - : "type" in input - ? addAdjectives(input.ps, input.adjectives) : [input]; return { ps: Array.isArray(ps) ? ps : [ps], ...options && options.reduce((all, curr) => ({ ...all, - [curr]: true, + ...typeof curr === "number" ? { + isMiniPronoun: curr, + } : { + [curr]: true, + }, }), {}), adjust: function(o): Segment { return { diff --git a/src/types.ts b/src/types.ts index 769382b..49aa2bc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -501,6 +501,7 @@ export type VPRendered = { type: "VPRendered", king: "subject" | "object", servant: "subject" | "object" | undefined, + shrunkenPossesive: number | undefined, isPast: boolean, isTransitive: boolean, isCompound: "stative" | "dynamic" | false, @@ -529,11 +530,13 @@ export type Tense = EquativeTense | VerbTense | PerfectTense | ModalTense | Impe export type VPSelectionState = { subject: NPSelection | undefined, verb: VerbSelection, + shrunkenPossesive: undefined | number, }; export type VPSelectionComplete = { subject: NPSelection, verb: VerbSelectionComplete, + shrunkenPossesive: undefined | number, }; export type VerbSelectionComplete = Omit & { @@ -587,6 +590,11 @@ export type NPType = "noun" | "pronoun" | "participle"; export type ObjectNP = "none" | Person.ThirdPlurMale; +export type PossesorSelection = { + uid: number, + np: NPSelection, +} + // TODO require/import Person and PsString export type NounSelection = { type: "noun", @@ -597,6 +605,7 @@ export type NounSelection = { numberCanChange: boolean, dynamicComplement?: boolean, adjectives: AdjectiveSelection[], + possesor: undefined | PossesorSelection, }; export type AdjectiveSelection = { @@ -619,6 +628,7 @@ export type PronounSelection = { export type ParticipleSelection = { type: "participle", verb: VerbEntry, + possesor: undefined | PossesorSelection, }; // not object @@ -629,7 +639,7 @@ export type ReplaceKey = T extends Record ? export type FormVersion = { removeKing: boolean, shrinkServant: boolean }; export type Rendered = ReplaceKey< - Omit, + Omit, "e", string > & { @@ -637,8 +647,13 @@ export type Rendered[], + possesor?: { + uid: number, + np: Rendered, + }, }; // TODO: recursive changing this down into the possesor etc. @@ -650,6 +665,7 @@ export type EPSelectionState = { Complement: EqCompSelection | undefined, }, equative: EquativeSelection, + shrunkenPossesive: undefined | number, }; export type EPSelectionComplete = Omit & { @@ -684,6 +700,7 @@ export type EPRendered = { predicate: Rendered, equative: EquativeRendered, englishBase?: string[], + shrunkenPossesive: undefined | number, } export type EntryFeeder = {