From 06cd3d2f18093ed3d34ed05bcda506318c254ab0 Mon Sep 17 00:00:00 2001 From: lingdocs <71590811+lingdocs@users.noreply.github.com> Date: Wed, 27 Apr 2022 11:38:43 +0500 Subject: [PATCH] =?UTF-8?q?added=20adjectives=20to=20Nouns=20and=20also=20?= =?UTF-8?q?fixed=20the=20the=20issue=20with=20the=20nouns=20not=20saving?= =?UTF-8?q?=20the=20gender/number=20flexibility.=20Still=20need=20to=20ref?= =?UTF-8?q?actor=20to=20keep=20the=20flexibility=20for=20the=20VerbSelecti?= =?UTF-8?q?ons=20-=20(functions=20arent=20saved=20in=20useStickyState=20?= =?UTF-8?q?=F0=9F=A4=A6=E2=80=8D=E2=99=82=EF=B8=8F)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/components/ep-explorer/EPExplorer.tsx | 17 ++-- src/components/np-picker/AdjectiveManager.tsx | 84 +++++++++++++++++++ src/components/np-picker/AdjectivePicker.tsx | 3 +- src/components/np-picker/NPNounPicker.tsx | 42 +++++++--- src/components/np-picker/NPPicker.tsx | 2 +- src/components/np-picker/picker-tools.tsx | 19 +---- .../vp-explorer/CompoundDisplay.tsx | 2 +- src/lib/phrase-building/compile-ep.ts | 19 +++-- src/lib/phrase-building/compile-vp.ts | 11 ++- src/lib/phrase-building/np-tools.ts | 42 ++++++++++ src/lib/phrase-building/render-adj.ts | 47 +++++++++++ src/lib/phrase-building/render-ep.ts | 25 +----- src/lib/phrase-building/render-np.ts | 5 +- src/lib/phrase-building/render-vp.ts | 3 +- src/lib/phrase-building/segment.ts | 16 +++- src/lib/useStickyState.ts | 2 +- src/types.ts | 17 ++-- 18 files changed, 271 insertions(+), 87 deletions(-) create mode 100644 src/components/np-picker/AdjectiveManager.tsx create mode 100644 src/lib/phrase-building/np-tools.ts create mode 100644 src/lib/phrase-building/render-adj.ts diff --git a/package.json b/package.json index f7cf63d..0a73bc9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lingdocs/pashto-inflector", - "version": "2.3.2", + "version": "2.3.3", "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/components/ep-explorer/EPExplorer.tsx b/src/components/ep-explorer/EPExplorer.tsx index a2c9eb9..1bae0f9 100644 --- a/src/components/ep-explorer/EPExplorer.tsx +++ b/src/components/ep-explorer/EPExplorer.tsx @@ -173,18 +173,21 @@ function massageSubjectChange(subject: T.NPSelection | undefined, old: T.EPSelec } if (subject.type === "pronoun" && old.predicate.type === "NP" && old.predicate.NP?.type === "noun" && isUnisexNounEntry(old.predicate.NP.entry)) { const predicate = old.predicate.NP; - const numberAdjusted = predicate.changeNumber - ? predicate.changeNumber(personNumber(subject.person)) - : predicate; - const fullyAdjusted = numberAdjusted.changeGender - ? numberAdjusted.changeGender(personGender(subject.person)) - : numberAdjusted; + const adjusted = { + ...predicate, + ...predicate.numberCanChange ? { + number: personNumber(subject.person), + } : {}, + ...predicate.genderCanChange ? { + gender: personGender(subject.person), + } : {}, + } return { ...old, subject, predicate: { ...old.predicate, - NP: fullyAdjusted, + NP: adjusted, }, }; } diff --git a/src/components/np-picker/AdjectiveManager.tsx b/src/components/np-picker/AdjectiveManager.tsx new file mode 100644 index 0000000..8aab0d4 --- /dev/null +++ b/src/components/np-picker/AdjectiveManager.tsx @@ -0,0 +1,84 @@ +import * as T from "../../types"; +import { useState } from "react"; +import AdjectivePicker from "./AdjectivePicker"; + +function AdjectiveManager(props: { + adjectives: T.AdjectiveSelection[], + entryFeeder: T.EntryFeederSingleType, + opts: T.TextOptions, + onChange: (adjs: T.AdjectiveSelection[]) => void, +}) { + const [adding, setAdding] = useState(false); + function handleChange(i: number) { + return (a: T.AdjectiveSelection | undefined) => { + if (a === undefined) return; + const updated = [...props.adjectives]; + updated[i] = a; + props.onChange( + updated.filter((x): x is T.AdjectiveSelection => !!x) + ); + }; + } + function deleteAdj(i: number) { + return () => { + props.onChange(remove(props.adjectives, i)); + }; + } + function handleAddNew(a: T.AdjectiveSelection | undefined) { + if (a === undefined) return; + setAdding(false); + props.onChange([ + a, + ...props.adjectives, + ]); + } + // const flippedList = [...props.adjectives]; + // flippedList.reverse(); + // console.log(props.adjectives); + return
+ {!!props.adjectives.length &&
+
Adjectives
+ {!adding ?
setAdding(true)}>+ Adj.
:
} +
} + {adding &&
+
+
Add Adjective
+
setAdding(false)}>Cancel
+
+ +
} + {props.adjectives.map((adj, i) => ( +
+ +
+
+
+
+ ))} + {!adding && !props.adjectives.length &&
+
setAdding(true)}>+ Adj.
+
} +
; +} + +function remove(arr: X[], i: number): X[] { + return [ + ...arr.slice(0, i), + ...arr.slice(i + 1), + ]; +} + +export default AdjectiveManager; \ No newline at end of file diff --git a/src/components/np-picker/AdjectivePicker.tsx b/src/components/np-picker/AdjectivePicker.tsx index 2fd01e5..7cb34a8 100644 --- a/src/components/np-picker/AdjectivePicker.tsx +++ b/src/components/np-picker/AdjectivePicker.tsx @@ -6,6 +6,7 @@ function AdjectivePicker(props: { adjective: T.AdjectiveSelection | undefined, onChange: (p: T.AdjectiveSelection | undefined) => void, opts: T.TextOptions, + noTitle?: boolean, }) { function onEntrySelect(entry: T.AdjectiveEntry | undefined) { if (!entry) { @@ -14,7 +15,7 @@ function AdjectivePicker(props: { props.onChange(makeAdjectiveSelection(entry)); } return
-
Adjective
+ {!props.noTitle &&
Adjective
}
, + entryFeeder: T.EntryFeeder, noun: T.NounSelection | undefined, onChange: (p: T.NounSelection | undefined) => void, opts: T.TextOptions, @@ -76,6 +76,14 @@ function NPNounPicker(props: { // setPatternFilter(undefined); // setShowFilter(false); // } + function handelAdjectivesUpdate(adjectives: T.AdjectiveSelection[]) { + if (props.noun) { + props.onChange({ + ...props.noun, + adjectives, + }); + } + } return
{/* {showFilter &&
@@ -90,11 +98,17 @@ function NPNounPicker(props: { handleChange={setPatternFilter} />
} */} + {props.noun && }
Noun
{!(props.noun && props.noun.dynamicComplement) ?
} {props.noun &&
- {props.noun.changeGender ? { - if (!props.noun || !props.noun.changeGender) return; - props.onChange(props.noun.changeGender(g)); + handleChange={(gender) => { + if (!props.noun || !props.noun.genderCanChange) return; + props.onChange({ + ...props.noun, + gender, + }); }} /> : props.noun.gender === "masc" ? "Masc." : "Fem."}
- {props.noun.changeNumber ? { - if (!props.noun || !props.noun.changeNumber) return; - props.onChange(props.noun.changeNumber(n)); + handleChange={(number) => { + if (!props.noun || !props.noun.numberCanChange) return; + props.onChange({ + ...props.noun, + number, + }); }} /> : props.noun.number === "singular" ? "Sing." : "Plur."}
diff --git a/src/components/np-picker/NPPicker.tsx b/src/components/np-picker/NPPicker.tsx index ced8422..48ddcc2 100644 --- a/src/components/np-picker/NPPicker.tsx +++ b/src/components/np-picker/NPPicker.tsx @@ -93,7 +93,7 @@ function NPPicker(props: { /> : npType === "noun" ? {info.type}
handleLinkClick(info.entry.complement?.ts) diff --git a/src/lib/phrase-building/compile-ep.ts b/src/lib/phrase-building/compile-ep.ts index 3488210..6bca117 100644 --- a/src/lib/phrase-building/compile-ep.ts +++ b/src/lib/phrase-building/compile-ep.ts @@ -11,10 +11,11 @@ import { flattenLengths, } from "./segment"; import { removeAccents } from "../accent-helpers"; +import { getEnglishFromRendered, getPashtoFromRendered } from "./np-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 [] }; -export function compileEP(EP: T.EPRendered, form: T.FormVersion, combineLengths?: true): { ps: T.SingleOrLengthOpts, e?: string [] } { +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[] }; +export function compileEP(EP: T.EPRendered, form: T.FormVersion, combineLengths?: true): { ps: T.SingleOrLengthOpts, e?: string[] } { const { kids, NPs } = getSegmentsAndKids(EP, form); const equative = EP.equative.ps; const psResult = compilePs({ @@ -36,12 +37,12 @@ function getSegmentsAndKids(EP: T.EPRendered, form: T.FormVersion): { kids: Segm } return [s]; } - const subject = makeSegment(EP.subject.ps); - const predicate = makeSegment(EP.predicate.ps); + const subject = makeSegment(getPashtoFromRendered(EP.subject)); + const predicate = makeSegment(getPashtoFromRendered(EP.predicate)); return { kids: EP.equative.hasBa - ? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])] - : [], + ? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])] + : [], NPs: [ ...ifNotRemoved(subject, "subject"), ...ifNotRemoved(predicate, "predicate"), @@ -77,8 +78,8 @@ function compileEnglish(EP: T.EPRendered): string[] | undefined { function insertEWords(e: string, { subject, predicate }: { subject: string, predicate: string }): string { return e.replace("$SUBJ", subject).replace("$PRED", predicate || ""); } - const engSubj = EP.subject.e || undefined; - const engPred = EP.predicate.e || undefined; + const engSubj = getEnglishFromRendered(EP.subject); + const engPred = getEnglishFromRendered(EP.predicate); // require all English parts for making the English phrase return (EP.englishBase && engSubj && engPred) ? EP.englishBase.map(e => insertEWords(e, { diff --git a/src/lib/phrase-building/compile-vp.ts b/src/lib/phrase-building/compile-vp.ts index 8a6f3b1..9201c0a 100644 --- a/src/lib/phrase-building/compile-vp.ts +++ b/src/lib/phrase-building/compile-vp.ts @@ -20,6 +20,7 @@ import { removeDuplicates, } from "./vp-tools"; import { isImperativeTense, isModalTense, isPerfectTense } from "../type-predicates"; +import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools"; type Form = T.FormVersion & { OSV?: boolean }; export function compileVP(VP: T.VPRendered, form: Form): { ps: T.SingleOrLengthOpts, e?: string [] }; @@ -79,8 +80,8 @@ 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: VP.subject.ps, - object: typeof VP.object === "object" ? VP.object.ps : undefined, + 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); @@ -282,8 +283,10 @@ function compileEnglish(VP: T.VPRendered): string[] | undefined { function insertEWords(e: string, { subject, object }: { subject: string, object?: string }): string { return e.replace("$SUBJ", subject).replace("$OBJ", object || ""); } - const engSubj = VP.subject.e || undefined; - const engObj = (typeof VP.object === "object" && VP.object.e) ? VP.object.e : undefined; + const engSubj = getEnglishFromRendered(VP.subject); + const engObj = typeof VP.object === "object" + ? getEnglishFromRendered(VP.object) + : undefined; // require all English parts for making the English phrase return (VP.englishBase && engSubj && (engObj || typeof VP.object !== "object")) ? VP.englishBase.map(e => insertEWords(e, { diff --git a/src/lib/phrase-building/np-tools.ts b/src/lib/phrase-building/np-tools.ts new file mode 100644 index 0000000..8b0d989 --- /dev/null +++ b/src/lib/phrase-building/np-tools.ts @@ -0,0 +1,42 @@ +import * as T from "../../types"; +import { concatPsString } from "../p-text-helpers"; + +export function getPashtoFromRendered(np: T.Rendered): T.PsString[] { + const adjs = np.adjectives; + if (!adjs) { + return np.ps; + } + return np.ps.map(p => ( + concatPsString( + adjs.reduce((accum, curr) => ( + // TODO: with variations of adjs? + concatPsString(accum, " ", curr.ps[0]) + ), { p: "", f: "" }), + " ", + p, + ) + )); +} + +export function getEnglishFromRendered(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; + } + } + return np.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 new file mode 100644 index 0000000..a38f668 --- /dev/null +++ b/src/lib/phrase-building/render-adj.ts @@ -0,0 +1,47 @@ +import * as T from "../../types"; +import { inflectWord } from "../../lib/pashto-inflector"; +import { + psStringFromEntry, + isUnisexSet, +} from "../../lib/p-text-helpers"; +import { getEnglishWord } from "../get-english-word"; +import { + personGender, + personIsPlural, +} from "../../lib/misc-helpers"; + +function chooseInflection(inflections: T.UnisexSet, pers: T.Person, inflected?: boolean): T.ArrayOneOrMore { + const gender = personGender(pers); + const plural = personIsPlural(pers); + const infNumber = (plural ? 1 : 0) + (inflected ? 1 : 0); + return inflections[gender][infNumber]; +} + +export function renderAdjectiveSelection(a: T.AdjectiveSelection, person: T.Person, inflected: boolean): T.Rendered { + const infs = inflectWord(a.entry); + const eWord = getEnglishWord(a.entry); + const e = !eWord + ? undefined + : typeof eWord === "string" + ? eWord + : (eWord.singular || undefined); + if (!infs) return { + type: "adjective", + entry: a.entry, + ps: [psStringFromEntry(a.entry)], + e, + inflected: false, + person, + } + if (!infs.inflections || !isUnisexSet(infs.inflections)) { + throw new Error("error getting inflections for adjective, looks like a noun's inflections"); + } + return { + type: "adjective", + entry: a.entry, + ps: chooseInflection(infs.inflections, person, inflected), + e, + inflected: false, + 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 3b781ed..306bbe3 100644 --- a/src/lib/phrase-building/render-ep.ts +++ b/src/lib/phrase-building/render-ep.ts @@ -7,10 +7,10 @@ import { renderNPSelection } from "./render-np"; import { getPersonFromVerbForm } from "../../lib/misc-helpers"; import { getVerbBlockPosFromPerson } from "../misc-helpers"; import { getEnglishWord } from "../get-english-word"; -import { isUnisexSet, psStringFromEntry } from "../p-text-helpers"; -import { inflectWord } from "../pashto-inflector"; +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") @@ -71,26 +71,7 @@ function renderEqCompSelection(s: T.EqCompSelection, person: T.Person): T.Render }; } if (s.type === "adjective") { - const infs = inflectWord(s.entry); - if (!infs) return { - type: "adjective", - entry: s.entry, - ps: [psStringFromEntry(s.entry)], - e, - inflected: false, - person, - } - if (!infs.inflections || !isUnisexSet(infs.inflections)) { - throw new Error("error getting inflections for adjective, looks like a noun's inflections"); - } - return { - type: "adjective", - entry: s.entry, - ps: chooseInflection(infs.inflections, person), - e, - inflected: false, - person, - }; + return renderAdjectiveSelection(s, person, false) } throw new Error("invalid EqCompSelection"); } diff --git a/src/lib/phrase-building/render-np.ts b/src/lib/phrase-building/render-np.ts index 8c41122..d1348e6 100644 --- a/src/lib/phrase-building/render-np.ts +++ b/src/lib/phrase-building/render-np.ts @@ -11,6 +11,7 @@ import { } from "../p-text-helpers"; import { parseEc } from "../misc-helpers"; import { getEnglishWord } from "../get-english-word"; +import { renderAdjectiveSelection } from "./render-adj"; 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"; @@ -48,9 +49,11 @@ function renderNounSelection(n: T.NounSelection, inflected: boolean): T.Rendered ? ps : [psStringFromEntry(n.entry)]; })(); + const person = getPersonNumber(n.gender, n.number); return { ...n, - person: getPersonNumber(n.gender, n.number), + adjectives: n.adjectives.map(a => renderAdjectiveSelection(a, person, inflected)), + person, inflected, ps: pashto, e: english, diff --git a/src/lib/phrase-building/render-vp.ts b/src/lib/phrase-building/render-vp.ts index ce1a60b..f52a8ed 100644 --- a/src/lib/phrase-building/render-vp.ts +++ b/src/lib/phrase-building/render-vp.ts @@ -43,7 +43,7 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered { const inflectSubject = isPast && isTransitive && !isMascSingAnimatePattern4(VP.subject); const inflectObject = !isPast && isFirstOrSecondPersPronoun(VP.verb.object); // Render Elements - return { + const b: T.VPRendered = { type: "VPRendered", king, servant, @@ -59,6 +59,7 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered { vs: VP.verb, }), }; + return b; } function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, objectPerson: T.Person | undefined): T.VerbRendered { diff --git a/src/lib/phrase-building/segment.ts b/src/lib/phrase-building/segment.ts index fd8d9f6..fff5cf3 100644 --- a/src/lib/phrase-building/segment.ts +++ b/src/lib/phrase-building/segment.ts @@ -25,10 +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( - ps: T.PsString | T.PsString[], + input: T.Rendered | T.PsString | T.PsString[], options?: (keyof SegmentDescriptions)[], ): 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) => ({ @@ -39,6 +52,7 @@ export function makeSegment( return { ...this, ...o.ps ? { + // TODO: is this ok with the adjectives? ps: Array.isArray(o.ps) ? o.ps : "p" in o.ps diff --git a/src/lib/useStickyState.ts b/src/lib/useStickyState.ts index 3d21b1a..3c0e2da 100644 --- a/src/lib/useStickyState.ts +++ b/src/lib/useStickyState.ts @@ -41,4 +41,4 @@ export default function useStickyState NounSelection, - /* method only present if it's possible to change number */ - changeNumber?: (number: NounNumber) => NounSelection, + adjectives: AdjectiveSelection[], }; export type AdjectiveSelection = { @@ -633,8 +628,8 @@ export type ReplaceKey = T extends Record ? export type FormVersion = { removeKing: boolean, shrinkServant: boolean }; -export type Rendered = ReplaceKey< - Omit, +export type Rendered = ReplaceKey< + Omit, "e", string > & { @@ -642,6 +637,8 @@ export type Rendered = ReplaceKey< e?: string, inflected: boolean, person: Person, + // TODO: better recursive thing + adjectives?: Rendered[], }; // TODO: recursive changing this down into the possesor etc.