From 453216e6f1ebc3f07a2de3eb1853d554b191e88d Mon Sep 17 00:00:00 2001 From: adueck Date: Sat, 27 Jul 2024 21:03:56 -0400 Subject: [PATCH] added demonstrative pronouns --- package-lock.json | 4 +- package.json | 2 +- src/App.tsx | 2 +- src/components/package-lock.json | 4 +- src/components/package.json | 2 +- src/components/src/blocks/Block.tsx | 36 +- .../src/np-picker/AdjectiveManager.tsx | 221 +++--- .../src/np-picker/DemonstrativePicker.tsx | 59 ++ src/components/src/np-picker/NPNounPicker.tsx | 38 +- src/components/src/np-picker/NPPicker.tsx | 581 ++++++++------- src/demo-components/InflectionDemo.tsx | 1 - src/index.js | 21 +- src/lib/package.json | 2 +- .../src/phrase-building/make-selections.ts | 2 +- src/lib/src/phrase-building/np-tools.ts | 24 +- src/lib/src/phrase-building/render-np.ts | 67 +- src/lib/src/phrase-building/vps-reducer.ts | 671 +++++++++--------- src/types.ts | 1 + 18 files changed, 985 insertions(+), 753 deletions(-) create mode 100644 src/components/src/np-picker/DemonstrativePicker.tsx diff --git a/package-lock.json b/package-lock.json index 8b07066..1310c83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pashto-inflector", - "version": "7.2.2", + "version": "7.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pashto-inflector", - "version": "7.2.2", + "version": "7.3.0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 6ac3f07..ae923de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pashto-inflector", - "version": "7.2.2", + "version": "7.3.0", "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 d542690..d4cf3c2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -20,7 +20,7 @@ import Hider from "./components/src/Hider"; import InflectionDemo from "./demo-components/InflectionDemo"; import SpellingDemo from "./demo-components/SpellingDemo"; import ParserDemo from "./demo-components/ParserDemo"; -import InflectionTable from "./components/src/InflectionsTable"; +// import InflectionTable from "./components/src/InflectionsTable"; function App() { const [showingTextOptions, setShowingTextOptions] = useStickyState( diff --git a/src/components/package-lock.json b/src/components/package-lock.json index 3bdc567..5508f47 100644 --- a/src/components/package-lock.json +++ b/src/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@lingdocs/ps-react", - "version": "7.2.2", + "version": "7.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@lingdocs/ps-react", - "version": "7.2.2", + "version": "7.3.0", "license": "MIT", "dependencies": { "@formkit/auto-animate": "^1.0.0-beta.3", diff --git a/src/components/package.json b/src/components/package.json index 1ad779f..06319ac 100644 --- a/src/components/package.json +++ b/src/components/package.json @@ -1,6 +1,6 @@ { "name": "@lingdocs/ps-react", - "version": "7.2.2", + "version": "7.3.0", "description": "Pashto inflector library module with React components", "main": "dist/components/library.js", "module": "dist/components/library.js", diff --git a/src/components/src/blocks/Block.tsx b/src/components/src/blocks/Block.tsx index 565c0b7..c5a9842 100644 --- a/src/components/src/blocks/Block.tsx +++ b/src/components/src/blocks/Block.tsx @@ -600,6 +600,27 @@ function Sandwich({ ); } +function Demonstrative({ + opts, + script, + children, +}: { + opts: T.TextOptions; + script: "p" | "f"; + children: T.Rendered | undefined; +}) { + if (!children) { + return null; + } + return ( +
+ {children.ps[script]} +
DEM
+ {children.e} +
+ ); +} + function CompNounBlock({ opts, noun, @@ -612,7 +633,7 @@ function CompNounBlock({ return (
{flattenLengths(noun.ps)[0][script]} @@ -652,6 +673,9 @@ export function NPBlock({ , ] : []), + + {np.selection.demonstrative ? np.selection.demonstrative : undefined} + , {np.selection.adjectives} , @@ -659,12 +683,15 @@ export function NPBlock({ {" "} {flattenLengths(np.selection.ps)[0][script]}
, - ]; + ].filter((x) => { + // @ts-ignore + return x !== " "; + }); const el = script === "p" ? elements.reverse() : elements; return (
{c.map((a) => a.ps[0][script]).join(" ")} diff --git a/src/components/src/np-picker/AdjectiveManager.tsx b/src/components/src/np-picker/AdjectiveManager.tsx index ad5659e..148468e 100644 --- a/src/components/src/np-picker/AdjectiveManager.tsx +++ b/src/components/src/np-picker/AdjectiveManager.tsx @@ -1,153 +1,98 @@ import * as T from "../../../types"; import { useState } from "react"; import AdjectivePicker from "./AdjectivePicker"; -import classNames from "classnames"; function AdjectiveManager(props: { - adjectives: T.AdjectiveSelection[], - entryFeeder: T.EntryFeeder, - opts: T.TextOptions, - demonstrative: T.NounSelection["demonstrative"], - onChange: (adjs: T.AdjectiveSelection[]) => void, - onDemonstrativeChange: (dem: T.NounSelection["demonstrative"]) => void, - phraseIsComplete: boolean, + adjectives: T.AdjectiveSelection[]; + entryFeeder: T.EntryFeeder; + opts: T.TextOptions; + onChange: (adjs: T.AdjectiveSelection[]) => void; + phraseIsComplete: boolean; }) { - const [adding, setAdding] = useState(false); - const [addingDemonstrative, setAddingDemonstrative] = 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
- {adding &&
-
-
Add Adjective
-
setAdding(false)}> - -
+ 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 ( +
+ {adding && ( +
+
+
Add Adjective
+
setAdding(false)}> +
- -
} - {addingDemonstrative &&
-
-
Add Demonstrative
-
{ - setAddingDemonstrative(false); - props.onDemonstrativeChange(undefined); - }}> - -
-
- -
} - {props.adjectives.map((adj, i) =>
-
-
Adjective
-
- {!!props.adjectives.length && !adding &&
-
setAdding(true)}>+ Adj.
-
} -
-
-
-
-
- -
)} - {!adding && !props.adjectives.length &&
-
setAdding(true)}>+ Adj.
-
} - {/* {!addingDemonstrative && !props.demonstrative &&
-
setAddingDemonstrative(true)}>+ Demons.
-
} */} -
; -} +
+ +
+ )} -function DemonstrativePicker({ demonstrative, onChange }: { - demonstrative: T.NounSelection["demonstrative"], - onChange: (dem: T.NounSelection["demonstrative"]) => void, -}) { - function handleDChange(d: "daa" | "hagha" | "dagha") { - if (!demonstrative) { - onChange({ - type: "demonstrative", - demonstrative: d, - hideNoun: false, - }); - } else { - onChange({ - ...demonstrative, - demonstrative: d, - }); - } - } - return
-
- + {props.adjectives.map((adj, i) => ( +
+
+
Adjective
+
+ {!!props.adjectives.length && !adding && ( +
+
setAdding(true)}> + + Adj. +
+
+ )} +
+
+
+
+
+
-
- + ))} + {!adding && !props.adjectives.length && ( +
+
setAdding(true)}> + + Adj. +
-
- -
-
; + )} +
+ ); } function remove(arr: X[], i: number): X[] { - return [ - ...arr.slice(0, i), - ...arr.slice(i + 1), - ]; + return [...arr.slice(0, i), ...arr.slice(i + 1)]; } -export default AdjectiveManager; \ No newline at end of file +export default AdjectiveManager; diff --git a/src/components/src/np-picker/DemonstrativePicker.tsx b/src/components/src/np-picker/DemonstrativePicker.tsx new file mode 100644 index 0000000..52bf935 --- /dev/null +++ b/src/components/src/np-picker/DemonstrativePicker.tsx @@ -0,0 +1,59 @@ +import * as T from "../../../types"; +import classNames from "classnames"; + +export default function DemonstrativePicker({ + demonstrative, + onChange, +}: { + demonstrative: T.NounSelection["demonstrative"]; + onChange: (dem: T.NounSelection["demonstrative"]) => void; +}) { + function handleDChange(d: "daa" | "hagha" | "dagha") { + if (!demonstrative) { + onChange({ + type: "demonstrative", + demonstrative: d, + hideNoun: false, + }); + } else { + onChange({ + ...demonstrative, + demonstrative: d, + }); + } + } + return ( +
+
+ +
+
+ +
+
+ +
+
+ ); +} diff --git a/src/components/src/np-picker/NPNounPicker.tsx b/src/components/src/np-picker/NPNounPicker.tsx index 1a531e2..9bd01fe 100644 --- a/src/components/src/np-picker/NPNounPicker.tsx +++ b/src/components/src/np-picker/NPNounPicker.tsx @@ -6,6 +6,8 @@ import InlinePs from "../InlinePs"; // import { isFemNounEntry, isPattern1Entry, isPattern2Entry, isPattern3Entry, isPattern4Entry, isPattern5Entry, isPattern6FemEntry } from "../../lib/type-predicates"; import EntrySelect from "../EntrySelect"; import AdjectiveManager from "./AdjectiveManager"; +import { useState } from "react"; +import DemonstrativePicker from "./DemonstrativePicker"; // const filterOptions = [ // { @@ -61,6 +63,8 @@ function NPNounPicker(props: { opts: T.TextOptions; phraseIsComplete: boolean; }) { + const [addingDemonstrative, setAddingDemonstrative] = + useState(false); // const [patternFilter, setPatternFilter] = useState(undefined); // const [showFilter, setShowFilter] = useState(false) // const nounsFiltered = props.nouns @@ -84,7 +88,7 @@ function NPNounPicker(props: { }); } } - function handleDemonstrativeUpdate( + function handleDemonstrativeChange( demonstrative: undefined | T.NounSelection["demonstrative"] ) { if (props.noun) { @@ -96,6 +100,36 @@ function NPNounPicker(props: { } return (
+ {!addingDemonstrative && !props.noun?.demonstrative ? ( +
+ setAddingDemonstrative(true)} + > + + Demonstrative + +
+ ) : ( +
+
+
{!props.noun?.demonstrative ? "Add" : ""} Demonstrative
+
{ + handleDemonstrativeChange(undefined); + setAddingDemonstrative(false); + }} + > + +
+
+ +
+ )} + {/* {showFilter &&
Filter by inflection pattern
@@ -113,11 +147,9 @@ function NPNounPicker(props: { )}
Noun
diff --git a/src/components/src/np-picker/NPPicker.tsx b/src/components/src/np-picker/NPPicker.tsx index 5bae0c3..aef333e 100644 --- a/src/components/src/np-picker/NPPicker.tsx +++ b/src/components/src/np-picker/NPPicker.tsx @@ -1,272 +1,371 @@ import PronounPicker from "./NPPronounPicker"; import NounPicker from "./NPNounPicker"; import ParticiplePicker from "./NPParticiplePicker"; -import { - randomPerson, -} from "../../../lib/src/np-tools"; +import { randomPerson } from "../../../lib/src/np-tools"; import { useState, useEffect } from "react"; import * as T from "../../../types"; import { isSecondPerson } from "../../../lib/src/misc-helpers"; const npTypes: T.NPType[] = ["pronoun", "noun", "participle"]; +// TODO: BUG WITH PICKING IMPERATIVE FORMS OUTSIDE OF SECOND PERSON! + export const shrunkenBackground = "rgba(255, 206, 43, 0.15)"; function NPPicker(props: { - heading?: JSX.Element | string, - onChange: (nps: T.NPSelection | undefined) => void, - np: T.NPSelection | undefined, - counterPart: T.NPSelection | T.VerbObject | undefined, - role: "subject" | "object" | "ergative" | "possesor", - opts: T.TextOptions, - cantClear?: boolean, - is2ndPersonPicker?: boolean, - entryFeeder: T.EntryFeeder, - phraseIsComplete: boolean, - isShrunk?: boolean, - isRemoved?: boolean, + heading?: JSX.Element | string; + onChange: (nps: T.NPSelection | undefined) => void; + np: T.NPSelection | undefined; + counterPart: T.NPSelection | T.VerbObject | undefined; + role: "subject" | "object" | "ergative" | "possesor"; + opts: T.TextOptions; + cantClear?: boolean; + is2ndPersonPicker?: boolean; + entryFeeder: T.EntryFeeder; + phraseIsComplete: boolean; + isShrunk?: boolean; + isRemoved?: boolean; }) { - if (props.is2ndPersonPicker && ((props.np?.selection.type !== "pronoun") || !isSecondPerson(props.np.selection.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.selection.type : undefined); - const onChange = (np: T.NPSelection | undefined) => { - props.onChange(ensureSingleShrink(props.np, np)); - if ((np?.selection.type === "noun" || np?.selection.type === "participle") && np.selection.possesor) { - setAddingPoss(true); - } - setAddingPoss(false); + if ( + props.is2ndPersonPicker && + (props.np?.selection.type !== "pronoun" || + !isSecondPerson(props.np.selection.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.selection.type : undefined + ); + const onChange = (np: T.NPSelection | undefined) => { + props.onChange(ensureSingleShrink(props.np, np)); + if ( + (np?.selection.type === "noun" || np?.selection.type === "participle") && + np.selection.possesor + ) { + setAddingPoss(true); } - useEffect(() => { - setNpType(props.np ? props.np.selection.type : undefined); - }, [props.np]); - function handleClear() { - if (props.np && props.np.selection.type === "noun" && props.np.selection.dynamicComplement) return; - setNpType(undefined); + setAddingPoss(false); + }; + useEffect(() => { + setNpType(props.np ? props.np.selection.type : undefined); + }, [props.np]); + function handleClear() { + if ( + props.np && + props.np.selection.type === "noun" && + props.np.selection.dynamicComplement + ) + return; + setNpType(undefined); + onChange(undefined); + } + function handleNPTypeChange(ntp: T.NPType) { + if (ntp === "pronoun") { + const person = randomPerson({ counterPart: props.counterPart }); + const pronoun: T.PronounSelection = { + type: "pronoun", + person, + distance: "far", + }; + setNpType(ntp); + onChange({ type: "NP", selection: pronoun }); + } else { + if (props.np) { onChange(undefined); + } + setNpType(ntp); } - function handleNPTypeChange(ntp: T.NPType) { - if (ntp === "pronoun") { - const person = randomPerson({ counterPart: props.counterPart }); - const pronoun: T.PronounSelection = { - type: "pronoun", - person, - distance: "far", - }; - setNpType(ntp); - onChange({ type: "NP", selection: pronoun }); - } else { - if (props.np) { - onChange(undefined); - } - setNpType(ntp); - } + } + // TODO: REMOVE + function handlePossesiveChange(p: T.NPSelection | undefined) { + if (!props.np || props.np.selection.type === "pronoun") return; + if (!p) { + onChange({ + type: "NP", + selection: { + ...props.np.selection, + possesor: undefined, + }, + }); + return; } - // TODO: REMOVE - function handlePossesiveChange(p: T.NPSelection | undefined) { - if (!props.np || props.np.selection.type === "pronoun") return; - if (!p) { - onChange({ - type: "NP", - selection: { - ...props.np.selection, - possesor: undefined, - }, - }); - return; - } - const isNewPosesser = checkForNewPossesor(p, props.np.selection.possesor); - const possesor: T.PossesorSelection = { - np: p, - shrunken: (!isNewPosesser && props.np.selection.possesor) ? props.np.selection.possesor.shrunken : false, - }; - onChange({ - type: "NP", - selection: { - ...props.np.selection, - possesor, - }, - }); - } - function handleToggleShrunken() { - if (!props.np || props.np.selection.type === "pronoun" || !props.np.selection.possesor || !props.phraseIsComplete) return; - onChange({ - type: "NP", - selection: { - ...props.np.selection, - possesor: { - ...props.np.selection.possesor, - shrunken: !props.np.selection.possesor.shrunken, - }, - }, - }); - } - const isDynamicComplement = props.np && props.np.selection.type === "noun" && props.np.selection.dynamicComplement; - const clearButton = (!props.cantClear && !props.is2ndPersonPicker && !isDynamicComplement) - ? - :
; - const possesiveLabel = props.np?.selection.type === "participle" ? "Subj/Obj" : "Possesor"; - return
+ X + + ) : ( +
+ ); + const possesiveLabel = + props.np?.selection.type === "participle" ? "Subj/Obj" : "Possesor"; + return ( +
-
-
-
- {typeof props.heading === "string" - ?
{props.heading}
- : props.heading} -
-
- {npType && clearButton} -
+ }} + > +
+
+
+ {typeof props.heading === "string" ? ( +
{props.heading}
+ ) : ( + props.heading + )}
-
- {!npType &&
-
- Choose NP -
- {npTypes.map((npt) =>
- -
)} -
} - {(props.np && props.np.selection.type !== "pronoun" && (props.np.selection.possesor || addingPoss)) &&
{npType && clearButton}
+
+
+ {!npType && ( +
+
Choose NP
+ {npTypes.map((npt) => ( +
+ +
+ ))} +
+ )} + {props.np && + props.np.selection.type !== "pronoun" && + (props.np.selection.possesor || addingPoss) && ( +
-
-
{possesiveLabel}:
- {(props.np.selection.possesor && !props.isShrunk && props.phraseIsComplete) &&
- {!props.np.selection.possesor.shrunken ? "🪄" : "👶"} -
} -
{ - setAddingPoss(false); - handlePossesiveChange(undefined); - }}> - + background: + props.np.selection.possesor?.shrunken && !props.isShrunk + ? shrunkenBackground + : "inherit", + }} + > +
+
{possesiveLabel}:
+ {props.np.selection.possesor && + !props.isShrunk && + props.phraseIsComplete && ( +
+ {!props.np.selection.possesor.shrunken ? "🪄" : "👶"}
+ )} +
{ + setAddingPoss(false); + handlePossesiveChange(undefined); + }} + > +
- -
} - {(npType === "noun" || npType === "participle") && props.np && !addingPoss &&
- setAddingPoss(true)}>+ {possesiveLabel} -
} - {(npType === "pronoun" && props.np?.selection.type === "pronoun") - ? onChange({ type: "NP", selection: p })} - is2ndPersonPicker={props.is2ndPersonPicker} - opts={props.opts} - /> - : npType === "noun" - ? onChange(s ? { type: "NP", selection: s } : undefined)} - opts={props.opts} - /> - : npType === "participle" - ? onChange(s ? { type: "NP", selection: s } : undefined)} - opts={props.opts} - /> - : null +
+ +
+ )} + {(npType === "noun" || npType === "participle") && + props.np && + !addingPoss && ( +
+ setAddingPoss(true)} + > + + {possesiveLabel} + +
+ )} + {npType === "pronoun" && props.np?.selection.type === "pronoun" ? ( + onChange({ type: "NP", selection: p })} + is2ndPersonPicker={props.is2ndPersonPicker} + opts={props.opts} + /> + ) : npType === "noun" ? ( + -
; + onChange={(s) => + onChange(s ? { type: "NP", selection: s } : undefined) + } + opts={props.opts} + /> + ) : npType === "participle" ? ( + + onChange(s ? { type: "NP", selection: s } : undefined) + } + opts={props.opts} + /> + ) : null} +
+
+ ); } -function ensureSingleShrink(old: T.NPSelection | undefined, s: T.NPSelection | undefined): T.NPSelection | undefined { - if (!s) return s; - function countShrinks(np: T.NPSelection): number { - if (np.selection.type === "pronoun") return 0; - if (!np.selection.possesor) return 0; - return (np.selection.possesor.shrunken ? 1 : 0) + countShrinks(np.selection.possesor.np); +function ensureSingleShrink( + old: T.NPSelection | undefined, + s: T.NPSelection | undefined +): T.NPSelection | undefined { + if (!s) return s; + function countShrinks(np: T.NPSelection): number { + if (np.selection.type === "pronoun") return 0; + if (!np.selection.possesor) return 0; + return ( + (np.selection.possesor.shrunken ? 1 : 0) + + countShrinks(np.selection.possesor.np) + ); + } + function keepNewShrink(old: T.NPSelection, n: T.NPSelection): T.NPSelection { + if (n.selection.type === "pronoun") return n; + if ( + old.selection.type === "pronoun" || + !n.selection.possesor || + !old.selection.possesor + ) + return n; + if (n.selection.possesor.shrunken && !old.selection.possesor.shrunken) { + return { + type: "NP", + selection: { + ...n.selection, + possesor: { + ...n.selection.possesor, + np: removeShrinks(n.selection.possesor.np), + }, + }, + }; } - function keepNewShrink(old: T.NPSelection, n: T.NPSelection): T.NPSelection { - if (n.selection.type === "pronoun") return n; - if (old.selection.type === "pronoun" || !n.selection.possesor || !old.selection.possesor) return n; - if (n.selection.possesor.shrunken && !old.selection.possesor.shrunken) { - return { - type: "NP", - selection: { - ...n.selection, - possesor: { - ...n.selection.possesor, - np: removeShrinks(n.selection.possesor.np), - }, - }, - }; - } - return { - type: "NP", - selection:{ - ...n.selection, - possesor: { - shrunken: false, - np: keepNewShrink(old.selection.possesor.np, n.selection.possesor.np), - }, - }, - }; - } - function removeShrinks(n: T.NPSelection): T.NPSelection { - if (n.selection.type === "pronoun") return n; - if (!n.selection.possesor) return n; - return { - type: "NP", - selection: { - ...n.selection, - possesor: { - shrunken: false, - np: removeShrinks(n.selection.possesor.np), - }, - }, - }; - } - if (!old) return s; - if (s.selection.type === "pronoun") return s; - if (!s.selection.possesor) return s; - const numOfShrinks = countShrinks(s); - if (numOfShrinks < 2) return s; - return keepNewShrink(old, s); + return { + type: "NP", + selection: { + ...n.selection, + possesor: { + shrunken: false, + np: keepNewShrink(old.selection.possesor.np, n.selection.possesor.np), + }, + }, + }; + } + function removeShrinks(n: T.NPSelection): T.NPSelection { + if (n.selection.type === "pronoun") return n; + if (!n.selection.possesor) return n; + return { + type: "NP", + selection: { + ...n.selection, + possesor: { + shrunken: false, + np: removeShrinks(n.selection.possesor.np), + }, + }, + }; + } + if (!old) return s; + if (s.selection.type === "pronoun") return s; + if (!s.selection.possesor) return s; + const numOfShrinks = countShrinks(s); + if (numOfShrinks < 2) return s; + return keepNewShrink(old, s); } -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.selection.type === "pronoun") return false; - if (n.selection.type === "noun" && old.np.selection.type === "noun") { - return n.selection.entry.ts !== old.np.selection.entry.ts; - } - if (n.selection.type === "participle" && old.np.selection.type === "participle") { - return n.selection.verb.entry.ts !== old.np.selection.verb.entry.ts; - } - return false; +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.selection.type === "pronoun") return false; + if (n.selection.type === "noun" && old.np.selection.type === "noun") { + return n.selection.entry.ts !== old.np.selection.entry.ts; + } + if ( + n.selection.type === "participle" && + old.np.selection.type === "participle" + ) { + return n.selection.verb.entry.ts !== old.np.selection.verb.entry.ts; + } + return false; } -export default NPPicker; \ No newline at end of file +export default NPPicker; diff --git a/src/demo-components/InflectionDemo.tsx b/src/demo-components/InflectionDemo.tsx index 6b81e61..7826943 100644 --- a/src/demo-components/InflectionDemo.tsx +++ b/src/demo-components/InflectionDemo.tsx @@ -81,7 +81,6 @@ function InflectionDemo({ opts }: { opts: T.TextOptions }) { const v = e.target.value; const value = v === "all" ? v : (Number(v) as T.InflectionPattern); setPattern(value); - console.log({ word }); if (word && !tp.isPattern(value)(word)) { setWord(undefined); } diff --git a/src/index.js b/src/index.js index 092b9ae..0f30ce0 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,16 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; +import React from "react"; +import ReactDOM from "react-dom"; +import "./index.css"; +import App from "./App"; +import reportWebVitals from "./reportWebVitals"; import "@fortawesome/fontawesome-free/css/all.css"; -import 'bootstrap/dist/css/bootstrap.css'; -import './App.css'; +import "bootstrap/dist/css/bootstrap.css"; +import "./App.css"; ReactDOM.render( - - - , - document.getElementById('root') + // + , // , + document.getElementById("root") ); // If you want to start measuring performance in your app, pass a function diff --git a/src/lib/package.json b/src/lib/package.json index 0886048..db53e78 100644 --- a/src/lib/package.json +++ b/src/lib/package.json @@ -1,6 +1,6 @@ { "name": "@lingdocs/inflect", - "version": "7.2.2", + "version": "7.3.0", "description": "Pashto inflector library", "main": "dist/index.js", "types": "dist/lib/library.d.ts", diff --git a/src/lib/src/phrase-building/make-selections.ts b/src/lib/src/phrase-building/make-selections.ts index 906665b..b6fd0e4 100644 --- a/src/lib/src/phrase-building/make-selections.ts +++ b/src/lib/src/phrase-building/make-selections.ts @@ -81,6 +81,6 @@ export function makeNounSelection( possesor: !complementType ? old?.possesor : undefined, dynamicComplement: complementType === "dynamic", genStativeComplement: complementType === "generative stative", - demonstrative: undefined, + demonstrative: old?.demonstrative, }; } diff --git a/src/lib/src/phrase-building/np-tools.ts b/src/lib/src/phrase-building/np-tools.ts index cef7fb5..81b4a54 100644 --- a/src/lib/src/phrase-building/np-tools.ts +++ b/src/lib/src/phrase-building/np-tools.ts @@ -12,19 +12,26 @@ function getBaseAndAdjectives({ return getSandwichPsBaseAndAdjectives(selection); } const adjs = "adjectives" in selection && selection.adjectives; + const demons = ("demonstrative" in selection && + selection.demonstrative?.ps) || { p: "", f: "" }; if (!adjs) { - return flattenLengths(selection.ps); + // TODO: does this ever get used?? + return flattenLengths(selection.ps).map((x) => concatPsString(demons, x)); } + return flattenLengths(selection.ps).map((p) => concatPsString( + demons, + // demons ? " " : "", adjs.reduce( - (accum, curr) => - // TODO: with variations of adjs? - concatPsString( + (accum, curr) => { + // TODO: with variations of adjs? { + return concatPsString( accum, - accum.p === "" && accum.f === "" ? "" : " ", + accum.p === "" && accum.f === "" ? "" : "", //" ", curr.ps[0] - ), + ); + }, { p: "", f: "" } ), " ", @@ -199,7 +206,10 @@ function addArticlesAndAdjs( ? " (f.)" : " (m.)" : ""; - return `${articles}${adjs}${word}${genderTag}`; + const demonstrative = np.demonstrative ? ` ${np.demonstrative.e}` : ""; + return `${ + np.demonstrative ? "" : articles + }${demonstrative}${adjs}${word}${genderTag}`; } catch (e) { return undefined; } diff --git a/src/lib/src/phrase-building/render-np.ts b/src/lib/src/phrase-building/render-np.ts index 177dc83..9c8189e 100644 --- a/src/lib/src/phrase-building/render-np.ts +++ b/src/lib/src/phrase-building/render-np.ts @@ -131,34 +131,61 @@ export function renderNounSelection( ps: pashto, e: english, possesor: renderPossesor(n.possesor, role), - demonstrative: renderDemonstrative( - n.demonstrative, - inflected && n.number === "plural" - ), + demonstrative: renderDemonstrative({ + demonstrative: n.demonstrative, + inflected, + plural: n.number === "plural", + gender: n.gender, + }), }; } -function renderDemonstrative( - demonstrative: T.DemonstrativeSelection | undefined, - plurInflected: boolean -): T.Rendered | undefined { +function renderDemonstrative({ + demonstrative, + inflected, + plural, + gender, +}: { + demonstrative: T.DemonstrativeSelection | undefined; + inflected: boolean; + plural: boolean; + gender: T.Gender; +}): T.Rendered | undefined { if (!demonstrative) { return undefined; } + const ps = + demonstrative.demonstrative === "daa" + ? inflected + ? { p: "دې", f: "de" } + : { p: "دا", f: "daa" } + : demonstrative.demonstrative === "dagha" + ? inflected + ? plural + ? { p: "دغو", f: "dágho" } + : gender === "masc" + ? { p: "دغه", f: "dághu" } + : { p: "دغې", f: "dághe" } + : { p: "دغه", f: "dágha" } + : inflected + ? plural + ? { p: "هغو", f: "hágho" } + : gender === "masc" + ? { p: "هغه", f: "hághu" } + : { p: "هغې", f: "hághe" } + : { p: "هغه", f: "hágha" }; + const e = + demonstrative.demonstrative === "hagha" + ? plural + ? "those" + : "that" + : plural + ? "these" + : "this"; return { ...demonstrative, - ps: - demonstrative.demonstrative === "daa" - ? plurInflected - ? { p: "دې", f: "de" } - : { p: "دا", f: "daa" } - : demonstrative.demonstrative === "dagha" - ? plurInflected - ? { p: "دغه", f: "dágha" } - : { p: "دغو", f: "dágho" } - : plurInflected - ? { p: "هغه", f: "hágha" } - : { p: "هغو", f: "hágho" }, + ps, + e, }; } diff --git a/src/lib/src/phrase-building/vps-reducer.ts b/src/lib/src/phrase-building/vps-reducer.ts index efe433b..a196da2 100644 --- a/src/lib/src/phrase-building/vps-reducer.ts +++ b/src/lib/src/phrase-building/vps-reducer.ts @@ -1,333 +1,364 @@ import * as T from "../../../types"; -import { - isInvalidSubjObjCombo, -} from "./vp-tools"; +import { isInvalidSubjObjCombo } from "./vp-tools"; import { switchSubjObj } from "./vp-tools"; import { ensure2ndPersSubjPronounAndNoConflict } from "./vp-tools"; -import { - isPerfectTense, - isImperativeTense, -} from "../type-predicates"; +import { isPerfectTense, isImperativeTense } from "../type-predicates"; import { checkForMiniPronounsError } from "./compile"; import { - adjustObjectSelection, - adjustSubjectSelection, - getObjectSelection, - getSubjectSelection, - insertNewAP, - removeAP, - setAP, - shiftBlock, + adjustObjectSelection, + adjustSubjectSelection, + getObjectSelection, + getSubjectSelection, + insertNewAP, + removeAP, + setAP, + shiftBlock, } from "./blocks-utils"; -import { changeStatDyn, changeTransitivity, makeVPSelectionState } from "./verb-selection"; +import { + changeStatDyn, + changeTransitivity, + makeVPSelectionState, +} from "./verb-selection"; -export type VpsReducerAction = { - type: "load vps", - payload: T.VPSelectionState, -} | { - type: "set subject", - payload: { - subject: T.NPSelection | undefined, - skipPronounConflictCheck?: boolean, - }, -} | { - type: "set object", - payload: T.NPSelection | undefined, -} | { - type: "swap subj/obj", -} | { - type: "set form", - payload: T.FormVersion, -} | { - type: "set voice", - payload: "active" | "passive", -} | { - type: "set transitivity", - payload: "transitive" | "grammatically transitive", -} | { - type: "set statDyn", - payload: "stative" | "dynamic", -} | { - type: "set negativity", - payload: "true" | "false", -} | { - type: "set tense", - payload: T.VerbTense | T.PerfectTense | T.ImperativeTense | undefined, -} | { - type: "set tense category", - payload: "basic" | "modal" | "perfect" | "imperative", -} | { - type: "toggle servant shrink", -} | { - type: "toggle king remove", -} |{ - type: "set verb", - payload: T.VerbEntry, -} | { - type: "insert new AP", -} | { - type: "set AP", - payload: { - index: number, - AP: T.APSelection | undefined, - }, -} | { - type: "remove AP", - payload: number, -} | { - type: "shift block", - payload: { - index: number, - direction: "back" | "forward", - }, -} | { - type: "set externalComplement", - payload: T.ComplementSelection | undefined, -} - -export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, sendAlert?: (msg: string) => void): T.VPSelectionState { - function doReduce(): T.VPSelectionState { - if (action.type === "load vps") { - return action.payload; - } - if (action.type === "set subject") { - const { subject, skipPronounConflictCheck } = action.payload; - const object = getObjectSelection(vps.blocks).selection; - if ( - !skipPronounConflictCheck - && - hasPronounConflict(subject, object) - ) { - if (sendAlert) sendAlert("That combination of pronouns is not allowed"); - return vps; - } - return { - ...vps, - blocks: adjustSubjectSelection(vps.blocks, action.payload.subject), - }; - } - if (action.type === "set object") { - if (!vps.verb) return vps; - const objectB = getObjectSelection(vps.blocks).selection; - const subjectB = getSubjectSelection(vps.blocks).selection; - if ((objectB === "none") || (typeof objectB === "number")) { - return vps; - } - const object = action.payload; - // check for pronoun conflict - if (hasPronounConflict(subjectB, object)) { - if (sendAlert) sendAlert("That combination of pronouns is not allowed"); - return vps; - } - return { - ...vps, - blocks: adjustObjectSelection(vps.blocks, object), - }; - } - if (action.type === "swap subj/obj") { - if (vps.verb?.isCompound === "dynamic") return vps; - return switchSubjObj(vps); - } - if (action.type === "set form") { - return { - ...vps, - form: action.payload, - }; - } - if (action.type === "set voice") { - if (vps.verb && vps.verb.canChangeVoice) { - const subject = getSubjectSelection(vps.blocks).selection; - const object = getObjectSelection(vps.blocks).selection; - const voice = action.payload; - if (voice === "passive" && vps.verb.tenseCategory === "imperative") { - return vps; - } - if (voice === "passive") { - return { - ...vps, - blocks: adjustObjectSelection( - adjustSubjectSelection(vps.blocks, typeof object === "object" ? object : undefined), - "none", - ), - verb: { - ...vps.verb, - voice, - // tenseCategory: vps.verb.tenseCategory === "modal" ? "basic" : vps.verb.tenseCategory, - }, - }; - } else { - return { - ...vps, - blocks: adjustObjectSelection( - adjustSubjectSelection(vps.blocks, undefined), - typeof subject === "object" ? subject : undefined, - ), - verb: { - ...vps.verb, - voice, - }, - }; - } - } else { - return vps; - } - } - if (action.type === "set transitivity") { - if (!(vps.verb && vps.verb.canChangeTransitivity)) return vps; - return changeTransitivity(vps, action.payload); - } - if (action.type === "set statDyn") { - if (!(vps.verb && vps.verb.canChangeStatDyn)) return vps; - return changeStatDyn(vps, action.payload); - } - if (action.type === "set negativity") { - if (!vps.verb) return vps; - return { - ...vps, - verb: { - ...vps.verb, - negative: action.payload === "true", - }, - }; - } - if (action.type === "set tense") { - const tense = action.payload; - if (!(vps.verb && tense)) return vps; - if (isPerfectTense(tense)) { - return { - ...vps, - verb: { - ...vps.verb, - perfectTense: tense, - tenseCategory: "perfect", - }, - }; - } else if (isImperativeTense(tense)) { - return { - ...vps, - verb: { - ...vps.verb, - imperativeTense: tense, - tenseCategory: "imperative", - }, - }; - } else { - return { - ...vps, - verb: { - ...vps.verb, - verbTense: tense, - tenseCategory: vps.verb.tenseCategory === "perfect" - ? "basic" - : vps.verb.tenseCategory, - }, - }; - } - } - if (action.type === "set tense category") { - if (!vps.verb) return vps; - const category = action.payload; - if (category === "imperative") { - return ensure2ndPersSubjPronounAndNoConflict({ - ...vps, - verb: { - ...vps.verb, - voice: "active", - tenseCategory: category, - }, - }); - } - if (category === "modal") { - return { - ...vps, - verb: { - ...vps.verb, - tenseCategory: category, - }, - } - } - return { - ...vps, - verb: { - ...vps.verb, - tenseCategory: category, - }, - }; - } - if (action.type === "toggle servant shrink") { - return { - ...vps, - form: { - ...vps.form, - shrinkServant: !vps.form.shrinkServant, - }, - }; - } - if (action.type === "toggle king remove") { - return { - ...vps, - form: { - ...vps.form, - removeKing: !vps.form.removeKing, - }, - }; - } - if (action.type === "set verb") { - return makeVPSelectionState(action.payload, vps); - } - if (action.type === "insert new AP") { - return { - ...vps, - blocks: insertNewAP(vps.blocks), - }; - } - if (action.type === "set AP") { - const { index, AP } = action.payload; - return { - ...vps, - blocks: setAP(vps.blocks, index, AP), - }; - } - if (action.type === "remove AP") { - return { - ...vps, - blocks: removeAP(vps.blocks, action.payload), - }; - } - if (action.type === "shift block") { - const { index, direction } = action.payload; - return { - ...vps, - blocks: shiftBlock(vps.blocks, index, direction), - }; - } - if (action.type === "set externalComplement") { - const selection = action.payload; - return { - ...vps, - externalComplement: selection === undefined - // TODO: this is a bit messy - // when using the ComplementPicker with an EP - undefined means it hasn't been selected - // when using the ComplementPicker with a VP - undefined means there can be no complement - ? { type: "complement", selection: { type: "unselected" }} - : selection, - } - } - throw new Error("unknown vpsReducer state"); +export type VpsReducerAction = + | { + type: "load vps"; + payload: T.VPSelectionState; } - const modified = doReduce(); - const err = checkForMiniPronounsError(modified); - if (err) { - if (sendAlert) sendAlert(err); + | { + type: "set subject"; + payload: { + subject: T.NPSelection | undefined; + skipPronounConflictCheck?: boolean; + }; + } + | { + type: "set object"; + payload: T.NPSelection | undefined; + } + | { + type: "swap subj/obj"; + } + | { + type: "set form"; + payload: T.FormVersion; + } + | { + type: "set voice"; + payload: "active" | "passive"; + } + | { + type: "set transitivity"; + payload: "transitive" | "grammatically transitive"; + } + | { + type: "set statDyn"; + payload: "stative" | "dynamic"; + } + | { + type: "set negativity"; + payload: "true" | "false"; + } + | { + type: "set tense"; + payload: T.VerbTense | T.PerfectTense | T.ImperativeTense | undefined; + } + | { + type: "set tense category"; + payload: "basic" | "modal" | "perfect" | "imperative"; + } + | { + type: "toggle servant shrink"; + } + | { + type: "toggle king remove"; + } + | { + type: "set verb"; + payload: T.VerbEntry; + } + | { + type: "insert new AP"; + } + | { + type: "set AP"; + payload: { + index: number; + AP: T.APSelection | undefined; + }; + } + | { + type: "remove AP"; + payload: number; + } + | { + type: "shift block"; + payload: { + index: number; + direction: "back" | "forward"; + }; + } + | { + type: "set externalComplement"; + payload: T.ComplementSelection | undefined; + }; + +export function vpsReducer( + vps: T.VPSelectionState, + action: VpsReducerAction, + sendAlert?: (msg: string) => void +): T.VPSelectionState { + function doReduce(): T.VPSelectionState { + if (action.type === "load vps") { + return action.payload; + } + if (action.type === "set subject") { + const { subject, skipPronounConflictCheck } = action.payload; + const object = getObjectSelection(vps.blocks).selection; + if (!skipPronounConflictCheck && hasPronounConflict(subject, object)) { + if (sendAlert) sendAlert("That combination of pronouns is not allowed"); return vps; + } + return { + ...vps, + blocks: adjustSubjectSelection(vps.blocks, action.payload.subject), + }; } - return modified; + if (action.type === "set object") { + if (!vps.verb) return vps; + const objectB = getObjectSelection(vps.blocks).selection; + const subjectB = getSubjectSelection(vps.blocks).selection; + if (objectB === "none" || typeof objectB === "number") { + return vps; + } + const object = action.payload; + // check for pronoun conflict + if (hasPronounConflict(subjectB, object)) { + if (sendAlert) sendAlert("That combination of pronouns is not allowed"); + return vps; + } + return { + ...vps, + blocks: adjustObjectSelection(vps.blocks, object), + }; + } + if (action.type === "swap subj/obj") { + if (vps.verb?.isCompound === "dynamic") return vps; + return switchSubjObj(vps); + } + if (action.type === "set form") { + return { + ...vps, + form: action.payload, + }; + } + if (action.type === "set voice") { + if (vps.verb && vps.verb.canChangeVoice) { + const subject = getSubjectSelection(vps.blocks).selection; + const object = getObjectSelection(vps.blocks).selection; + const voice = action.payload; + if (voice === "passive" && vps.verb.tenseCategory === "imperative") { + return vps; + } + if (voice === "passive") { + return { + ...vps, + blocks: adjustObjectSelection( + adjustSubjectSelection( + vps.blocks, + typeof object === "object" ? object : undefined + ), + "none" + ), + verb: { + ...vps.verb, + voice, + // tenseCategory: vps.verb.tenseCategory === "modal" ? "basic" : vps.verb.tenseCategory, + }, + }; + } else { + return { + ...vps, + blocks: adjustObjectSelection( + adjustSubjectSelection(vps.blocks, undefined), + typeof subject === "object" ? subject : undefined + ), + verb: { + ...vps.verb, + voice, + }, + }; + } + } else { + return vps; + } + } + if (action.type === "set transitivity") { + if (!(vps.verb && vps.verb.canChangeTransitivity)) return vps; + return changeTransitivity(vps, action.payload); + } + if (action.type === "set statDyn") { + if (!(vps.verb && vps.verb.canChangeStatDyn)) return vps; + return changeStatDyn(vps, action.payload); + } + if (action.type === "set negativity") { + if (!vps.verb) return vps; + return { + ...vps, + verb: { + ...vps.verb, + negative: action.payload === "true", + }, + }; + } + if (action.type === "set tense") { + const tense = action.payload; + if (!(vps.verb && tense)) return vps; + if (isPerfectTense(tense)) { + return { + ...vps, + verb: { + ...vps.verb, + perfectTense: tense, + tenseCategory: "perfect", + }, + }; + } else if (isImperativeTense(tense)) { + return { + ...vps, + verb: { + ...vps.verb, + imperativeTense: tense, + tenseCategory: "imperative", + }, + }; + } else { + return { + ...vps, + verb: { + ...vps.verb, + verbTense: tense, + tenseCategory: + vps.verb.tenseCategory === "perfect" + ? "basic" + : vps.verb.tenseCategory, + }, + }; + } + } + if (action.type === "set tense category") { + if (!vps.verb) return vps; + const category = action.payload; + if (category === "imperative") { + return ensure2ndPersSubjPronounAndNoConflict({ + ...vps, + verb: { + ...vps.verb, + voice: "active", + tenseCategory: category, + }, + }); + } + if (category === "modal") { + return { + ...vps, + verb: { + ...vps.verb, + tenseCategory: category, + }, + }; + } + return { + ...vps, + verb: { + ...vps.verb, + tenseCategory: category, + }, + }; + } + if (action.type === "toggle servant shrink") { + return { + ...vps, + form: { + ...vps.form, + shrinkServant: !vps.form.shrinkServant, + }, + }; + } + if (action.type === "toggle king remove") { + return { + ...vps, + form: { + ...vps.form, + removeKing: !vps.form.removeKing, + }, + }; + } + if (action.type === "set verb") { + return makeVPSelectionState(action.payload, vps); + } + if (action.type === "insert new AP") { + return { + ...vps, + blocks: insertNewAP(vps.blocks), + }; + } + if (action.type === "set AP") { + const { index, AP } = action.payload; + return { + ...vps, + blocks: setAP(vps.blocks, index, AP), + }; + } + if (action.type === "remove AP") { + return { + ...vps, + blocks: removeAP(vps.blocks, action.payload), + }; + } + if (action.type === "shift block") { + const { index, direction } = action.payload; + return { + ...vps, + blocks: shiftBlock(vps.blocks, index, direction), + }; + } + if (action.type === "set externalComplement") { + const selection = action.payload; + return { + ...vps, + externalComplement: + selection === undefined + ? // TODO: this is a bit messy + // when using the ComplementPicker with an EP - undefined means it hasn't been selected + // when using the ComplementPicker with a VP - undefined means there can be no complement + { type: "complement", selection: { type: "unselected" } } + : selection, + }; + } + throw new Error("unknown vpsReducer state"); + } + const modified = doReduce(); + const err = checkForMiniPronounsError(modified); + if (err) { + if (sendAlert) sendAlert(err); + return vps; + } + return modified; } -function hasPronounConflict(subject: T.NPSelection | undefined, object: undefined | T.VerbObject): boolean { - const subjPronoun = (subject && subject.selection.type === "pronoun") ? subject.selection : undefined; - const objPronoun = (object && typeof object === "object" && object.selection.type === "pronoun") ? object.selection : undefined; - if (!subjPronoun || !objPronoun) return false; - return isInvalidSubjObjCombo(subjPronoun.person, objPronoun.person); +function hasPronounConflict( + subject: T.NPSelection | undefined, + object: undefined | T.VerbObject +): boolean { + const subjPronoun = + subject && subject.selection.type === "pronoun" + ? subject.selection + : undefined; + const objPronoun = + object && typeof object === "object" && object.selection.type === "pronoun" + ? object.selection + : undefined; + if (!subjPronoun || !objPronoun) return false; + return isInvalidSubjObjCombo(subjPronoun.person, objPronoun.person); } - diff --git a/src/types.ts b/src/types.ts index 16192e6..ff00240 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1026,6 +1026,7 @@ export type Rendered< demonstrative: DemonstrativeSelection["demonstrative"]; hideNoun: boolean; ps: PsString; + e: string; } : T extends ComplementSelection ? {