sandwiches in eqcomplement and adjectives

This commit is contained in:
lingdocs 2022-05-21 17:00:16 -05:00
parent e8ec806ecb
commit 5c9b7b7c3e
13 changed files with 156 additions and 48 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@lingdocs/pashto-inflector", "name": "@lingdocs/pashto-inflector",
"version": "2.5.6", "version": "2.5.7",
"author": "lingdocs.com", "author": "lingdocs.com",
"description": "A Pashto inflection and verb conjugation engine, inculding React components for displaying Pashto text, inflections, and conjugations", "description": "A Pashto inflection and verb conjugation engine, inculding React components for displaying Pashto text, inflections, and conjugations",
"homepage": "https://verbs.lingdocs.com", "homepage": "https://verbs.lingdocs.com",

View File

@ -146,6 +146,7 @@ export function SandwichSelect<E extends T.Sandwich>(props: {
props.onChange(s); props.onChange(s);
} }
return <div style={divStyle}> return <div style={divStyle}>
<div>Sandwich Base</div>
<Select <Select
styles={customStyles} styles={customStyles}
isSearchable={true} isSearchable={true}

View File

@ -92,6 +92,7 @@ function EPExplorer(props: {
onChange={payload => adjustEps({ type: "set predicate NP", payload })} onChange={payload => adjustEps({ type: "set predicate NP", payload })}
opts={props.opts} opts={props.opts}
/> : <EqCompPicker /> : <EqCompPicker
phraseIsComplete={phraseIsComplete}
comp={eps.predicate.type === "Complement" ? eps.predicate.Complement : undefined} comp={eps.predicate.type === "Complement" ? eps.predicate.Complement : undefined}
onChange={payload => adjustEps({ type: "set predicate comp", payload })} onChange={payload => adjustEps({ type: "set predicate comp", payload })}
opts={props.opts} opts={props.opts}

View File

@ -6,6 +6,7 @@ import SandwichPicker from "../../np-picker/SandwichPicker";
const compTypes: T.EqCompType[] = ["adjective", "loc. adv.", "sandwich"]; const compTypes: T.EqCompType[] = ["adjective", "loc. adv.", "sandwich"];
function EqCompPicker(props: { function EqCompPicker(props: {
phraseIsComplete: boolean,
onChange: (comp: T.EqCompSelection | undefined) => void, onChange: (comp: T.EqCompSelection | undefined) => void,
comp: T.EqCompSelection | undefined, comp: T.EqCompSelection | undefined,
opts: T.TextOptions, opts: T.TextOptions,
@ -62,10 +63,11 @@ function EqCompPicker(props: {
<div style={{ minWidth: "9rem" }}> <div style={{ minWidth: "9rem" }}>
{compType === "adjective" ? {compType === "adjective" ?
<AdjectivePicker <AdjectivePicker
entryFeeder={props.entryFeeder.adjectives} entryFeeder={props.entryFeeder}
adjective={props.comp?.type === "adjective" ? props.comp : undefined} adjective={props.comp?.type === "adjective" ? props.comp : undefined}
opts={props.opts} opts={props.opts}
onChange={props.onChange} onChange={props.onChange}
phraseIsComplete={props.phraseIsComplete}
/> />
: compType === "loc. adv." : compType === "loc. adv."
? <LocativeAdverbPicker ? <LocativeAdverbPicker
@ -81,7 +83,7 @@ function EqCompPicker(props: {
sandwich={props.comp?.type === "sandwich" ? props.comp : undefined} sandwich={props.comp?.type === "sandwich" ? props.comp : undefined}
entryFeeder={props.entryFeeder} entryFeeder={props.entryFeeder}
// TODO: get phraseIsComplete working here // TODO: get phraseIsComplete working here
phraseIsComplete={false} phraseIsComplete={props.phraseIsComplete}
/> />
: null} : null}
</div> </div>

View File

@ -4,9 +4,10 @@ import AdjectivePicker from "./AdjectivePicker";
function AdjectiveManager(props: { function AdjectiveManager(props: {
adjectives: T.AdjectiveSelection[], adjectives: T.AdjectiveSelection[],
entryFeeder: T.EntryFeederSingleType<T.AdjectiveEntry>, entryFeeder: T.EntryFeeder,
opts: T.TextOptions, opts: T.TextOptions,
onChange: (adjs: T.AdjectiveSelection[]) => void, onChange: (adjs: T.AdjectiveSelection[]) => void,
phraseIsComplete: boolean,
}) { }) {
const [adding, setAdding] = useState<boolean>(false); const [adding, setAdding] = useState<boolean>(false);
function handleChange(i: number) { function handleChange(i: number) {
@ -44,6 +45,7 @@ function AdjectiveManager(props: {
</div> </div>
</div> </div>
<AdjectivePicker <AdjectivePicker
phraseIsComplete={props.phraseIsComplete}
noTitle noTitle
adjective={undefined} adjective={undefined}
entryFeeder={props.entryFeeder} entryFeeder={props.entryFeeder}
@ -64,6 +66,7 @@ function AdjectiveManager(props: {
</div> </div>
</div> </div>
<AdjectivePicker <AdjectivePicker
phraseIsComplete={props.phraseIsComplete}
noTitle noTitle
key={`adj${i}`} key={`adj${i}`}
adjective={adj} adjective={adj}

View File

@ -1,25 +1,54 @@
import { useState } from "react";
import * as T from "../../types"; import * as T from "../../types";
import EntrySelect from "../EntrySelect"; import EntrySelect from "../EntrySelect";
import SandwichPicker from "./SandwichPicker";
function AdjectivePicker(props: { function AdjectivePicker(props: {
entryFeeder: T.EntryFeederSingleType<T.AdjectiveEntry>, entryFeeder: T.EntryFeeder,
adjective: T.AdjectiveSelection | undefined, adjective: T.AdjectiveSelection | undefined,
onChange: (p: T.AdjectiveSelection | undefined) => void, onChange: (p: T.AdjectiveSelection | undefined) => void,
opts: T.TextOptions, opts: T.TextOptions,
noTitle?: boolean, noTitle?: boolean,
phraseIsComplete: boolean,
}) { }) {
const [addingSandwich, setAddingSandwich] = useState<boolean>(false);
function onEntrySelect(entry: T.AdjectiveEntry | undefined) { function onEntrySelect(entry: T.AdjectiveEntry | undefined) {
if (!entry) { if (!entry) {
return props.onChange(undefined); return props.onChange(undefined);
} }
props.onChange(makeAdjectiveSelection(entry)); props.onChange(makeAdjectiveSelection(entry));
} }
function handleSandwichChange(s: T.SandwichSelection<T.Sandwich> | undefined) {
if (!props.adjective) return;
props.onChange({
...props.adjective,
sandwich: s,
});
if (!s) {
setAddingSandwich(false);
}
}
return <div style={{ maxWidth: "225px", minWidth: "125px" }}> return <div style={{ maxWidth: "225px", minWidth: "125px" }}>
{!props.noTitle && <h6>Adjective</h6>} {(props.adjective?.sandwich || addingSandwich) && <SandwichPicker
<div> onChange={handleSandwichChange}
opts={props.opts}
sandwich={props.adjective?.sandwich}
entryFeeder={props.entryFeeder}
// TODO: get phraseIsComplete working here
phraseIsComplete={props.phraseIsComplete}
/>}
<div className="d-flex flex-row justify-content-between align-items-baseline">
{!props.noTitle && <div>
<h6>Adjective</h6>
</div>}
{(!addingSandwich && props.adjective && !props.adjective?.sandwich)
? <div className="clickable" onClick={() => setAddingSandwich(true)}>+ Sandwich</div>
: <div></div>}
</div>
<div className="mt-1">
<EntrySelect <EntrySelect
value={props.adjective?.entry} value={props.adjective?.entry}
entryFeeder={props.entryFeeder} entryFeeder={props.entryFeeder.adjectives}
onChange={onEntrySelect} onChange={onEntrySelect}
name="Adjective" name="Adjective"
opts={props.opts} opts={props.opts}
@ -32,6 +61,7 @@ function makeAdjectiveSelection(entry: T.AdjectiveEntry): T.AdjectiveSelection {
return { return {
type: "adjective", type: "adjective",
entry: entry, entry: entry,
sandwich: undefined,
}; };
} }

View File

@ -60,6 +60,7 @@ function NPNounPicker(props: {
noun: T.NounSelection | undefined, noun: T.NounSelection | undefined,
onChange: (p: T.NounSelection | undefined) => void, onChange: (p: T.NounSelection | undefined) => void,
opts: T.TextOptions, opts: T.TextOptions,
phraseIsComplete: boolean,
}) { }) {
// const [patternFilter, setPatternFilter] = useState<FilterPattern | undefined>(undefined); // const [patternFilter, setPatternFilter] = useState<FilterPattern | undefined>(undefined);
// const [showFilter, setShowFilter] = useState<boolean>(false) // const [showFilter, setShowFilter] = useState<boolean>(false)
@ -99,8 +100,9 @@ function NPNounPicker(props: {
/> />
</div>} */} </div>} */}
{props.noun && <AdjectiveManager {props.noun && <AdjectiveManager
phraseIsComplete={props.phraseIsComplete}
adjectives={props.noun?.adjectives} adjectives={props.noun?.adjectives}
entryFeeder={props.entryFeeder.adjectives} entryFeeder={props.entryFeeder}
opts={props.opts} opts={props.opts}
onChange={handelAdjectivesUpdate} onChange={handelAdjectivesUpdate}
/>} />}

View File

@ -52,7 +52,9 @@ function NPPicker(props: {
setNpType(ntp); setNpType(ntp);
onChange(pronoun); onChange(pronoun);
} else { } else {
onChange(undefined); if (props.np) {
onChange(undefined);
}
setNpType(ntp); setNpType(ntp);
} }
} }
@ -126,7 +128,7 @@ function NPPicker(props: {
}}> }}>
<div className="d-flex flex-row text-muted mb-2"> <div className="d-flex flex-row text-muted mb-2">
<div>{possesiveLabel}:</div> <div>{possesiveLabel}:</div>
{(props.np.possesor && !props.isShrunk) && <div className="clickable ml-3 mr-2" onClick={handleToggleShrunken}> {(props.np.possesor && !props.isShrunk && props.phraseIsComplete) && <div className="clickable ml-3 mr-2" onClick={handleToggleShrunken}>
{!props.np.possesor.shrunken ? "🪄" : "👶"} {!props.np.possesor.shrunken ? "🪄" : "👶"}
</div>} </div>}
<div className="clickable ml-2" onClick={() => { <div className="clickable ml-2" onClick={() => {
@ -160,6 +162,7 @@ function NPPicker(props: {
/> />
: npType === "noun" : npType === "noun"
? <NounPicker ? <NounPicker
phraseIsComplete={props.phraseIsComplete}
entryFeeder={props.entryFeeder} entryFeeder={props.entryFeeder}
noun={(props.np && props.np.type === "noun") ? props.np : undefined} noun={(props.np && props.np.type === "noun") ? props.np : undefined}
onChange={onChange} onChange={onChange}

View File

@ -37,6 +37,14 @@ function SandwichPicker(props: {
}); });
} }
return <div> return <div>
<div className="d-flex flex-row justify-content-between">
<div></div>
<div className="text-center">🥪 Sandwich</div>
<div className="clickable" onClick={() => props.onChange(undefined)}>
<i className="fas fa-trash" />
</div>
</div>
<div style={{ border: "1px #6C757D solid", padding: "3px" }}>
{sandwichBase && <div className="mb-2" style={{ margin: "0 auto" }}> {sandwichBase && <div className="mb-2" style={{ margin: "0 auto" }}>
<NPPicker <NPPicker
onChange={handleNounChange} onChange={handleNounChange}
@ -46,7 +54,9 @@ function SandwichPicker(props: {
role="object" role="object"
cantClear={true} cantClear={true}
entryFeeder={props.entryFeeder} entryFeeder={props.entryFeeder}
phraseIsComplete={props.phraseIsComplete} // TODO: the shrinking of possesives in sandwiches gets messed up with compilinig 😩
// disabling it for now
phraseIsComplete={false}
/> />
</div>} </div>}
<SandwichSelect <SandwichSelect
@ -56,6 +66,7 @@ function SandwichPicker(props: {
value={sandwichBase} value={sandwichBase}
onChange={handleSandwichChange} onChange={handleSandwichChange}
/> />
</div>
</div>; </div>;
} }

View File

@ -456,50 +456,71 @@ export function findPossesivesToShrinkInVP(VP: T.VPRendered, f: {
function findPossesives(...nps: (T.Rendered<T.NPSelection> | T.ObjectNP | undefined)[]): T.Rendered<T.NPSelection>[] { function findPossesives(...nps: (T.Rendered<T.NPSelection> | T.ObjectNP | undefined)[]): T.Rendered<T.NPSelection>[] {
return nps.reduce((accum, curr) => { return nps.reduce((accum, curr) => {
const res = findPossesiveInNP(curr); const res = findPossesivesInNP(curr);
if (res) return [...accum, res]; if (res) return [...accum, ...res];
return accum; return accum;
}, [] as T.Rendered<T.NPSelection>[]); }, [] as T.Rendered<T.NPSelection>[]);
} }
function findPossesiveInNP(NP: T.Rendered<T.NPSelection> | T.ObjectNP | undefined): T.Rendered<T.NPSelection> | undefined { function findPossesivesInNP(NP: T.Rendered<T.NPSelection> | T.ObjectNP | undefined): T.Rendered<T.NPSelection>[] {
if (NP === undefined) return undefined; if (NP === undefined) return [];
if (typeof NP !== "object") return undefined; if (typeof NP !== "object") return [];
if (!NP.possesor) return undefined; if (!NP.possesor) return [];
if (NP.possesor.shrunken) { if (NP.adjectives) {
return NP.possesor.np; const { adjectives, ...rest } = NP;
return [
...findPossesivesInAdjectives(adjectives),
...findPossesivesInNP(rest),
];
} }
return findPossesiveInNP(NP.possesor.np); if (NP.possesor.shrunken) {
return [NP.possesor.np];
}
return findPossesivesInNP(NP.possesor.np);
}
function findPossesivesInAdjectives(a: T.Rendered<T.AdjectiveSelection>[]): T.Rendered<T.NPSelection>[] {
return a.reduce((accum, curr): T.Rendered<T.NPSelection>[] => ([
...accum,
...findPossesivesInAdjective(curr),
]), [] as T.Rendered<T.NPSelection>[])
}
function findPossesivesInAdjective(a: T.Rendered<T.AdjectiveSelection>): T.Rendered<T.NPSelection>[] {
if (!a.sandwich) return [];
return findPossesivesInNP(a.sandwich.inside);
} }
type FoundNP = { type FoundNP = {
np: T.Rendered<T.NPSelection>, np: T.Rendered<T.NPSelection>,
from: "subject" | "predicate", from: "subject" | "predicate" | "AP",
}; };
export function findPossesivesToShrinkInEP(EP: T.EPRendered): FoundNP[] { export function findPossesivesToShrinkInEP(EP: T.EPRendered): FoundNP[] {
const inSubject = findPossesiveInNP(EP.subject); const inSubject: FoundNP[] = findPossesivesInNP(EP.subject).map(np => ({ np, from: "subject" }));
const inPredicate = (EP.predicate.type === "adjective" || EP.predicate.type === "loc. adv.") const inPredicate: FoundNP[] = ((EP.predicate.type === "loc. adv.")
? undefined ? []
: findPossesiveInNP( : (EP.predicate.type === "adjective")
? findPossesivesInAdjective(EP.predicate)
: findPossesivesInNP(
// @ts-ignore - ts being dumb // @ts-ignore - ts being dumb
EP.predicate as T.NPSelection EP.predicate as T.NPSelection
); )).map(np => ({ np, from: "predicate" }));
return [ return [
...inSubject ? [{ np: inSubject, from: "subject"} as FoundNP] : [], ...inSubject,
...inPredicate ? [{ np: inPredicate, from: "predicate" } as FoundNP] : [], ...inPredicate,
].filter(found => !(found.from === "subject" && EP.omitSubject)); ].filter(found => !(found.from === "subject" && EP.omitSubject));
} }
export function findPossesiveToShrinkInVP(VP: T.VPRendered): T.Rendered<T.NPSelection> | undefined { // export function findPossesiveToShrinkInVP(VP: T.VPRendered): T.Rendered<T.NPSelection>[] {
const obj: T.Rendered<T.NPSelection> | undefined = ("object" in VP && typeof VP.object === "object") // const obj: T.Rendered<T.NPSelection> | undefined = ("object" in VP && typeof VP.object === "object")
? VP.object // ? VP.object
: undefined; // : undefined;
return ( // return [
findPossesiveInNP(VP.subject) // ...findPossesivesInNP(VP.subject),
|| // ...findPossesivesInNP(obj),
findPossesiveInNP(obj) // ];
); // }
}
export function shrinkNP(np: T.Rendered<T.NPSelection>): Segment { export function shrinkNP(np: T.Rendered<T.NPSelection>): Segment {
function getFirstSecThird(): 1 | 2 | 3 { function getFirstSecThird(): 1 | 2 | 3 {

View File

@ -9,7 +9,7 @@ function getBaseAndAdjectives(np: T.Rendered<T.NPSelection | T.EqCompSelection>)
if (np.type === "sandwich") { if (np.type === "sandwich") {
return getSandwichPsBaseAndAdjectives(np); return getSandwichPsBaseAndAdjectives(np);
} }
const adjs = np.adjectives; const adjs = "adjectives" in np && np.adjectives;
if (!adjs) { if (!adjs) {
return np.ps; return np.ps;
} }
@ -75,9 +75,20 @@ function trimOffShrunkenPossesive(p: T.Rendered<T.NPSelection>): T.Rendered<T.NP
export function getPashtoFromRendered(np: T.Rendered<T.NPSelection> | T.Rendered<T.EqCompSelection>, subjectsPerson: false | T.Person): T.PsString[] { export function getPashtoFromRendered(np: T.Rendered<T.NPSelection> | T.Rendered<T.EqCompSelection>, subjectsPerson: false | T.Person): T.PsString[] {
const base = getBaseAndAdjectives(np); const base = getBaseAndAdjectives(np);
if (np.type === "loc. adv." || np.type === "adjective") { if (np.type === "loc. adv.") {
return base; return base;
} }
if (np.type === "adjective") {
if (!np.sandwich) {
return base
}
const sandwichPs = getPashtoFromRendered(np.sandwich, false);
return base.flatMap(p => (
sandwichPs.flatMap(s => (
concatPsString(s, " ", p)
))
));
}
const trimmed = np.type === "sandwich" ? { const trimmed = np.type === "sandwich" ? {
...np, ...np,
inside: trimOffShrunkenPossesive(np.inside), inside: trimOffShrunkenPossesive(np.inside),
@ -184,9 +195,12 @@ export function getEnglishFromRendered(r: T.Rendered<T.NPSelection | T.EqCompSel
return getEnglishFromRenderedSandwich(r); return getEnglishFromRenderedSandwich(r);
} }
if (!r.e) return undefined; if (!r.e) return undefined;
if (r.type === "loc. adv." || r.type === "adjective") { if (r.type === "loc. adv.") {
return r.e; return r.e;
} }
if (r.type === "adjective") {
return getEnglishFromRenderedAdjective(r);
}
if (r.type !== "pronoun") { if (r.type !== "pronoun") {
// TODO: shouldn't have to do this 'as' - should be automatically narrowing // TODO: shouldn't have to do this 'as' - should be automatically narrowing
const np = r as T.Rendered<T.NounSelection>; const np = r as T.Rendered<T.NounSelection>;
@ -200,4 +214,12 @@ function getEnglishFromRenderedSandwich(r: T.Rendered<T.SandwichSelection<T.Sand
const insideE = getEnglishFromRendered(r.inside); const insideE = getEnglishFromRendered(r.inside);
if (!insideE) return undefined; if (!insideE) return undefined;
return `${r.e} ${insideE}`; return `${r.e} ${insideE}`;
} }
function getEnglishFromRenderedAdjective(a: T.Rendered<T.AdjectiveSelection>): string | undefined {
if (!a.sandwich) {
return a.e;
}
if (!a.e) return undefined;
return `${a.e} ${getEnglishFromRenderedSandwich(a.sandwich)}`;
}

View File

@ -9,6 +9,7 @@ import {
personGender, personGender,
personIsPlural, personIsPlural,
} from "../../lib/misc-helpers"; } from "../../lib/misc-helpers";
import { renderSandwich } from "./render-sandwich";
function chooseInflection(inflections: T.UnisexSet<T.InflectionSet>, pers: T.Person, inflected?: boolean): T.ArrayOneOrMore<T.PsString> { function chooseInflection(inflections: T.UnisexSet<T.InflectionSet>, pers: T.Person, inflected?: boolean): T.ArrayOneOrMore<T.PsString> {
const gender = personGender(pers); const gender = personGender(pers);
@ -30,8 +31,8 @@ export function renderAdjectiveSelection(a: T.AdjectiveSelection, person: T.Pers
entry: a.entry, entry: a.entry,
ps: [psStringFromEntry(a.entry)], ps: [psStringFromEntry(a.entry)],
e, e,
inflected: false, inflected,
role, sandwich: a.sandwich ? renderSandwich(a.sandwich) : undefined,
person, person,
} }
if (!infs.inflections || !isUnisexSet(infs.inflections)) { if (!infs.inflections || !isUnisexSet(infs.inflections)) {
@ -42,8 +43,8 @@ export function renderAdjectiveSelection(a: T.AdjectiveSelection, person: T.Pers
entry: a.entry, entry: a.entry,
ps: chooseInflection(infs.inflections, person, inflected), ps: chooseInflection(infs.inflections, person, inflected),
e, e,
inflected: false, inflected,
role,
person, person,
sandwich: a.sandwich ? renderSandwich(a.sandwich) : undefined,
}; };
} }

View File

@ -641,6 +641,7 @@ export type AdverbSelection = {
export type AdjectiveSelection = { export type AdjectiveSelection = {
type: "adjective", type: "adjective",
entry: AdjectiveEntry, entry: AdjectiveEntry,
sandwich: SandwichSelection<Sandwich> | undefined,
} }
export type LocativeAdverbSelection = { export type LocativeAdverbSelection = {
@ -679,6 +680,16 @@ export type Rendered<T extends NPSelection | EqCompSelection | AdjectiveSelectio
? Omit<SandwichSelection<Sandwich>, "inside"> & { ? Omit<SandwichSelection<Sandwich>, "inside"> & {
inside: Rendered<NPSelection>, inside: Rendered<NPSelection>,
} }
: T extends AdjectiveSelection
? {
type: "adjective",
entry: AdjectiveEntry,
ps: PsString[],
e?: string,
sandwich: Rendered<SandwichSelection<Sandwich>> | undefined,
inflected: boolean,
person: Person,
}
: ReplaceKey< : ReplaceKey<
Omit<T, "changeGender" | "changeNumber" | "changeDistance" | "adjectives" | "possesor">, Omit<T, "changeGender" | "changeNumber" | "changeDistance" | "adjectives" | "possesor">,
"e", "e",