refactoring the EP and VP explorers - hoping to get proper robust mini pronoun stuff happening soon. also fixed a problem with the EPExplorers, in EPs you omit the SUBJECT always not the king

This commit is contained in:
lingdocs 2022-05-04 10:47:27 -05:00
parent b75ddb9689
commit 240fd11ed8
14 changed files with 564 additions and 352 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@lingdocs/pashto-inflector",
"version": "2.3.8",
"version": "2.3.9",
"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",

View File

@ -1,13 +1,13 @@
import * as T from "../../types";
import { renderEP } from "../../lib/phrase-building/render-ep";
import { compileEP } from "../../lib/phrase-building/compile-ep";
import AbbreviationFormSelector from "../vp-explorer/AbbreviationFormSelector";
import Examples from "../Examples";
import ButtonSelect from "../ButtonSelect";
function EPDisplay({ eps, opts, setForm }: {
function EPDisplay({ eps, opts, setOmitSubject }: {
eps: T.EPSelectionState,
opts: T.TextOptions,
setForm: (form: T.FormVersion) => void,
setOmitSubject: (value: "true" | "false") => void,
}) {
const EP = completeEPSelection(eps);
if (!EP) {
@ -22,13 +22,19 @@ function EPDisplay({ eps, opts, setForm }: {
</div>
}
const rendered = renderEP(EP);
const result = compileEP(rendered, EP.form);
const result = compileEP(rendered);
return <div className="text-center pt-3">
<AbbreviationFormSelector
adjustable="king"
form={EP.form}
onChange={setForm}
<div className="mb-2">
<ButtonSelect
small
value={(eps.omitSubject ? "true" : "false") as "true" | "false"}
options={[
{ value: "false", label: "Full"},
{ value: "true", label: "No Subj."},
]}
handleChange={setOmitSubject}
/>
</div>
{"long" in result.ps ?
<div>
<VariationLayer vs={result.ps.long} opts={opts} />

View File

@ -1,26 +1,14 @@
import * as T from "../../types";
import useStickyState from "../../lib/useStickyState";
import useStickyState, { useStickyReducer } from "../../lib/useStickyState";
import NPPicker from "../np-picker/NPPicker";
import EquativePicker from "./EquativePicker";
import EPDisplay from "./EPDisplay";
import ButtonSelect from "../ButtonSelect";
import EqCompPicker from "./eq-comp-picker/EqCompPicker";
import { roleIcon } from "../vp-explorer/VPExplorerExplanationModal";
import { isUnisexNounEntry } from "../../lib/type-predicates";
import {
personGender,
personNumber,
} from "../../lib/misc-helpers";
import EqChartsDisplay from "./EqChartsDisplay";
// TODO: put the clear button beside the title in the predicate picker?
function EPExplorer(props: {
opts: T.TextOptions,
entryFeeder: T.EntryFeeder,
}) {
const [mode, setMode] = useStickyState<"charts" | "phrases">("charts", "EPExplorerMode");
const [eps, setEps] = useStickyState<T.EPSelectionState>({
import epsReducer from "./eps-reducer";
const blankEps: T.EPSelectionState = {
subject: undefined,
predicate: {
type: "Complement",
@ -32,44 +20,17 @@ function EPExplorer(props: {
negative: false,
},
shrunkenPossesive: undefined,
form: { removeKing: false, shrinkServant: false },
}, "EPSelectionState10");
function handlePredicateTypeChange(type: "NP" | "Complement") {
setEps(o => ({
...o,
predicate: {
...o.predicate,
type,
},
}));
}
function handleSetSubject(subject: T.NPSelection | undefined) {
setEps(old => massageSubjectChange(subject, old));
}
function setPredicateNP(selection: T.NPSelection | undefined) {
setEps(o => massageNPPredicateChange(selection, o))
}
function setPredicateComp(selection: T.EqCompSelection | undefined) {
setEps(o => ({
...o,
predicate: {
...o.predicate,
Complement: selection
},
}));
}
function handleShrinkPossesive(shrunkenPossesive: number | undefined) {
setEps(o => ({
...o,
shrunkenPossesive,
}));
}
function handleSetForm(form: T.FormVersion) {
setEps(o => ({
...o,
form,
}));
}
omitSubject: false,
};
// TODO: put the clear button beside the title in the predicate picker?
function EPExplorer(props: {
opts: T.TextOptions,
entryFeeder: T.EntryFeeder,
}) {
const [mode, setMode] = useStickyState<"charts" | "phrases">("charts", "EPExplorerMode");
const [eps, adjustEps] = useStickyReducer(epsReducer, blankEps, "EPSelectionState10");
const king = eps.subject?.type === "pronoun"
? "subject"
: eps.predicate.type === "Complement"
@ -91,13 +52,13 @@ function EPExplorer(props: {
<div className="my-2">
<NPPicker
shrunkenPossesiveInPhrase={eps.shrunkenPossesive}
handleShrinkPossesive={handleShrinkPossesive}
handleShrinkPossesive={payload => adjustEps({ type: "shrink possesive", payload })}
heading={<div className="h5 text-center">Subject {king === "subject" ? roleIcon.king : ""}</div>}
entryFeeder={props.entryFeeder}
np={eps.subject}
counterPart={undefined}
role="subject"
onChange={handleSetSubject}
onChange={payload => adjustEps({ type: "set subject", payload })}
opts={props.opts}
/>
</div>
@ -111,21 +72,21 @@ function EPExplorer(props: {
{ value: "Complement", label: "Complement" },
]}
value={eps.predicate.type}
handleChange={handlePredicateTypeChange}
handleChange={payload => adjustEps({ type: "set predicate type", payload })}
/>
</div>
{eps.predicate.type === "NP" ? <NPPicker
shrunkenPossesiveInPhrase={eps.shrunkenPossesive}
handleShrinkPossesive={handleShrinkPossesive}
handleShrinkPossesive={payload => adjustEps({ type: "shrink possesive", payload })}
entryFeeder={props.entryFeeder}
np={eps.predicate.type === "NP" ? eps.predicate.NP : undefined}
counterPart={undefined}
role="subject"
onChange={setPredicateNP}
onChange={payload => adjustEps({ type: "set predicate NP", payload })}
opts={props.opts}
/> : <EqCompPicker
comp={eps.predicate.type === "Complement" ? eps.predicate.Complement : undefined}
onChange={setPredicateComp}
onChange={payload => adjustEps({ type: "set predicate comp", payload })}
opts={props.opts}
entryFeeder={props.entryFeeder}
/>}
@ -135,7 +96,7 @@ function EPExplorer(props: {
<div className="h5 text-center clickable">Equative</div>
<EquativePicker
equative={eps.equative}
onChange={(equative) => setEps(o => ({ ...o, equative }))}
onChange={payload => adjustEps({ type: "set equative", payload })}
hideNegative={mode === "charts"}
/>
</div>
@ -144,95 +105,9 @@ function EPExplorer(props: {
{mode === "phrases" && <EPDisplay
opts={props.opts}
eps={eps}
setForm={handleSetForm}
setOmitSubject={payload => adjustEps({ type: "set omitSubject", payload })}
/>}
</div>;
}
export default EPExplorer;
function massageNPPredicateChange(selection: T.NPSelection | undefined, old: T.EPSelectionState): T.EPSelectionState {
if (!selection) {
return {
...old,
predicate: {
...old.predicate,
NP: selection,
},
};
}
if (old.subject?.type === "pronoun" && selection.type === "noun" && isUnisexNounEntry(selection.entry)) {
const { gender, number } = selection;
const pronoun = old.subject.person;
const newPronoun = movePersonNumber(movePersonGender(pronoun, gender), number);
return {
...old,
subject: {
...old.subject,
person: newPronoun,
},
predicate: {
...old.predicate,
NP: selection,
},
};
}
return {
...old,
predicate: {
...old.predicate,
NP: selection,
},
};
}
function massageSubjectChange(subject: T.NPSelection | undefined, old: T.EPSelectionState): T.EPSelectionState {
if (!subject) {
return {
...old,
subject,
};
}
if (subject.type === "pronoun" && old.predicate.type === "NP" && old.predicate.NP?.type === "noun" && isUnisexNounEntry(old.predicate.NP.entry)) {
const predicate = old.predicate.NP;
const adjusted = {
...predicate,
...predicate.numberCanChange ? {
number: personNumber(subject.person),
} : {},
...predicate.genderCanChange ? {
gender: personGender(subject.person),
} : {},
}
return {
...old,
subject,
predicate: {
...old.predicate,
NP: adjusted,
},
};
}
return {
...old,
subject,
};
}
function movePersonGender(p: T.Person, gender: T.Gender): T.Person {
const pGender = personGender(p);
if (gender === pGender) {
return p;
}
return (gender === "masc") ? (p - 1) : (p + 1);
}
function movePersonNumber(p: T.Person, number: T.NounNumber): T.Person {
const pNumber = personNumber(p);
if (pNumber === number) {
return p;
}
return (number === "plural")
? (p + 6)
: (p - 6);
}

View File

@ -0,0 +1,160 @@
import * as T from "../../types";
import {
personGender,
personNumber,
} from "../../lib/misc-helpers";
import { isUnisexNounEntry } from "../../lib/type-predicates";
export type EpsReducerAction = {
type: "set predicate type",
payload: "NP" | "Complement",
} | {
type: "set subject",
payload: T.NPSelection | undefined,
} | {
type: "set predicate NP",
payload: T.NPSelection | undefined,
} | {
type: "set predicate comp",
payload: T.EqCompSelection | undefined,
} | {
type: "shrink possesive",
payload: number | undefined,
} | {
type: "set omitSubject",
payload: "true" | "false",
} | {
type: "set equative",
payload: T.EquativeSelection,
};
export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAction): T.EPSelectionState {
if (action.type === "set predicate type") {
return {
...eps,
predicate: {
...eps.predicate,
type: action.payload,
},
};
}
if (action.type === "set subject") {
return massageSubjectChange(action.payload, eps);
}
if (action.type === "set predicate NP") {
return massageNPPredicateChange(action.payload, eps);
}
if (action.type === "set predicate comp") {
return {
...eps,
predicate: {
...eps.predicate,
Complement: action.payload,
},
};
}
if (action.type === "shrink possesive") {
return {
...eps,
shrunkenPossesive: action.payload,
};
}
if (action.type === "set omitSubject") {
return {
...eps,
omitSubject: action.payload === "true",
};
}
// if (action.type === "set equative") {
return {
...eps,
equative: action.payload,
}
// }
}
function massageSubjectChange(subject: T.NPSelection | undefined, old: T.EPSelectionState): T.EPSelectionState {
if (!subject) {
return {
...old,
subject,
};
}
if (subject.type === "pronoun" && old.predicate.type === "NP" && old.predicate.NP?.type === "noun" && isUnisexNounEntry(old.predicate.NP.entry)) {
const predicate = old.predicate.NP;
const adjusted = {
...predicate,
...predicate.numberCanChange ? {
number: personNumber(subject.person),
} : {},
...predicate.genderCanChange ? {
gender: personGender(subject.person),
} : {},
}
return {
...old,
subject,
predicate: {
...old.predicate,
NP: adjusted,
},
};
}
return {
...old,
subject,
};
}
function massageNPPredicateChange(selection: T.NPSelection | undefined, old: T.EPSelectionState): T.EPSelectionState {
if (!selection) {
return {
...old,
predicate: {
...old.predicate,
NP: selection,
},
};
}
if (old.subject?.type === "pronoun" && selection.type === "noun" && isUnisexNounEntry(selection.entry)) {
const { gender, number } = selection;
const pronoun = old.subject.person;
const newPronoun = movePersonNumber(movePersonGender(pronoun, gender), number);
return {
...old,
subject: {
...old.subject,
person: newPronoun,
},
predicate: {
...old.predicate,
NP: selection,
},
};
}
return {
...old,
predicate: {
...old.predicate,
NP: selection,
},
};
}
function movePersonGender(p: T.Person, gender: T.Gender): T.Person {
const pGender = personGender(p);
if (gender === pGender) {
return p;
}
return (gender === "masc") ? (p - 1) : (p + 1);
}
function movePersonNumber(p: T.Person, number: T.NounNumber): T.Person {
const pNumber = personNumber(p);
if (pNumber === number) {
return p;
}
return (number === "plural")
? (p + 6)
: (p - 6);
}

View File

@ -2,9 +2,11 @@ import Select from "react-select";
import * as T from "../../types";
import ButtonSelect from "../ButtonSelect";
import { isImperativeTense, isModalTense, isPerfectTense, isVerbTense } from "../../lib/type-predicates";
import { ensure2ndPersSubjPronounAndNoConflict } from "../../lib/phrase-building/vp-tools";
import useStickyState from "../../lib/useStickyState";
import { customStyles } from "../EntrySelect";
import {
VpsReducerAction
} from "./vps-reducer";
const verbTenseOptions: { label: string | JSX.Element, value: T.VerbTense, formula: string, modalFormula: string, }[] = [{
label: <div><i className="fas fa-video mr-2" />present</div>,
@ -113,45 +115,17 @@ function TensePicker(props: ({
} | {
vpsComplete: T.VPSelectionComplete,
}) & {
onChange: (p: T.VPSelectionState) => void,
onChange: (p: VpsReducerAction) => void,
mode: "charts" | "phrases" | "quiz",
}) {
const [showFormula, setShowFormula] = useStickyState<boolean>(false, "showFormula");
function onTenseSelect(o: { value: T.VerbTense | T.PerfectTense | T.ImperativeTense } | null) {
if ("vpsComplete" in props) return;
const value = o?.value ? o.value : undefined;
if (props.vps.verb && value) {
if (isPerfectTense(value)) {
const tense = o?.value ? o.value : undefined;
props.onChange({
...props.vps,
verb: {
...props.vps.verb,
perfectTense: value,
tenseCategory: "perfect",
},
type: "set tense",
payload: tense,
});
} else if (isImperativeTense(value)) {
props.onChange({
...props.vps,
verb: {
...props.vps.verb,
imperativeTense: value,
tenseCategory: "imperative",
},
});
} else {
props.onChange({
...props.vps,
verb: {
...props.vps.verb,
verbTense: value,
tenseCategory: props.vps.verb.tenseCategory === "perfect"
? "basic"
: props.vps.verb.tenseCategory,
},
});
}
}
}
function moveTense(dir: "forward" | "back") {
if ("vpsComplete" in props) return;
@ -182,41 +156,20 @@ function TensePicker(props: ({
onTenseSelect(newTense);
};
}
function onPosNegSelect(value: string) {
function onPosNegSelect(payload: "true" | "false") {
if ("vpsComplete" in props) return;
if (props.vps.verb) {
props.onChange({
...props.vps,
verb: {
...props.vps.verb,
negative: value === "true",
},
type: "set negativity",
payload,
});
}
}
function onTenseCategorySelect(value: "basic" | "modal" | "perfect" | "imperative") {
function onTenseCategorySelect(payload: "basic" | "modal" | "perfect" | "imperative") {
if ("vpsComplete" in props) return;
if (props.vps.verb) {
if (value === "imperative") {
props.onChange(ensure2ndPersSubjPronounAndNoConflict({
...props.vps,
verb: {
...props.vps.verb,
voice: "active",
tenseCategory: value,
},
}));
return;
}
props.onChange({
...props.vps,
verb: {
...props.vps.verb,
tenseCategory: value,
},
type: "set tense category",
payload,
});
}
}
const tOptions = ("vps" in props && (props.vps.verb?.tenseCategory === "perfect"))
? perfectTenseOptions
: ("vps" in props && (props.vps.verb?.tenseCategory === "imperative"))
@ -292,7 +245,7 @@ function TensePicker(props: ({
</div>
{props.mode === "phrases" && <ButtonSelect
small
value={props.vps.verb.negative.toString()}
value={props.vps.verb.negative.toString() as "true" | "false"}
options={[{
label: "Pos.",
value: "false",

View File

@ -3,21 +3,18 @@ import VerbPicker from "./VerbPicker";
import TensePicker from "./TensePicker";
import VPDisplay from "./VPDisplay";
import ButtonSelect from "../ButtonSelect";
import {
isInvalidSubjObjCombo,
} from "../../lib/phrase-building/vp-tools";
import * as T from "../../types";
import ChartDisplay from "./VPChartDisplay";
import useStickyState from "../../lib/useStickyState";
import useStickyState, { useStickyReducer } from "../../lib/useStickyState";
import { makeVPSelectionState } from "./verb-selection";
import { useEffect, useState } from "react";
import { getKingAndServant } from "../../lib/phrase-building/render-vp";
import { isPastTense } from "../../lib/phrase-building/vp-tools";
import VPExplorerQuiz from "./VPExplorerQuiz";
import { switchSubjObj } from "../../lib/phrase-building/vp-tools";
import VPExplorerExplanationModal, { roleIcon } from "./VPExplorerExplanationModal";
// @ts-ignore
import LZString from "lz-string";
import { vpsReducer } from "./vps-reducer";
const phraseURLParam = "VPPhrase";
@ -38,7 +35,8 @@ export function VPExplorer(props: {
handleLinkClick: ((ts: number) => void) | "none",
entryFeeder: T.EntryFeeder,
}) {
const [vps, setVps] = useStickyState<T.VPSelectionState>(
const [vps, adjustVps] = useStickyReducer(
vpsReducer,
props.loaded
? props.loaded
: savedVps => makeVPSelectionState(props.verb, savedVps),
@ -62,51 +60,41 @@ export function VPExplorer(props: {
useEffect(() => {
const VPSFromUrl = getVPSFromUrl();
if (VPSFromUrl) {
setVps(VPSFromUrl);
adjustVps({
type: "load vps",
payload: VPSFromUrl
});
}
// eslint-disable-next-line
}, []);
useEffect(() => {
setVps(oldVps => {
if (mode === "quiz") {
setMode("phrases");
}
return makeVPSelectionState(props.verb, oldVps);
const newVps = makeVPSelectionState(props.verb, vps);
adjustVps({
type: "load vps",
payload: newVps,
});
// eslint-disable-next-line
}, [props.verb]);
function handleSubjectChange(subject: T.NPSelection | undefined, skipPronounConflictCheck?: boolean) {
if (!skipPronounConflictCheck && hasPronounConflict(subject, vps.verb?.object)) {
alert("That combination of pronouns is not allowed");
return;
}
setVps(o => ({ ...o, subject }));
adjustVps({
type: "set subject",
payload: { subject, skipPronounConflictCheck },
});
}
function handleObjectChange(object: T.NPSelection | undefined) {
if (!vps.verb) return;
if ((vps.verb.object === "none") || (typeof vps.verb.object === "number")) return;
// check for pronoun conflict
if (hasPronounConflict(vps.subject, object)) {
alert("That combination of pronouns is not allowed");
return;
}
setVps(o => ({
...o,
verb: {
...o.verb,
object,
},
}));
adjustVps({
type: "set object",
payload: object,
});
}
function handleSubjObjSwap() {
if (vps.verb?.isCompound === "dynamic") return;
setVps(switchSubjObj)
adjustVps({ type: "swap subj/obj" });
}
function handleShrinkPossesive(shrunkenPossesive: number | undefined) {
setVps(o => ({
...o,
shrunkenPossesive,
}));
adjustVps({
type: "shrink possesive",
payload: shrunkenPossesive,
});
}
function quizLock<T>(f: T) {
if (mode === "quiz") {
@ -118,10 +106,10 @@ export function VPExplorer(props: {
return f;
}
function handleSetForm(form: T.FormVersion) {
setVps(o => ({
...o,
form,
}));
adjustVps({
type: "set form",
payload: form,
});
}
// for some crazy reason I can't get the URI share thing to encode and decode properly
function handleCopyShareLink() {
@ -135,7 +123,7 @@ export function VPExplorer(props: {
return <div className="mt-3" style={{ maxWidth: "950px"}}>
<VerbPicker
vps={vps}
onChange={quizLock(setVps)}
onChange={quizLock(adjustVps)}
opts={props.opts}
handleLinkClick={props.handleLinkClick}
/>
@ -207,7 +195,7 @@ export function VPExplorer(props: {
<div className="my-2">
<TensePicker
vps={vps}
onChange={quizLock(setVps)}
onChange={quizLock(adjustVps)}
mode={mode}
/>
</div>
@ -237,13 +225,6 @@ export function VPExplorer(props: {
export default VPExplorer;
function hasPronounConflict(subject: T.NPSelection | undefined, object: undefined | T.VerbObject): boolean {
const subjPronoun = (subject && subject.type === "pronoun") ? subject : undefined;
const objPronoun = (object && typeof object === "object" && object.type === "pronoun") ? object : undefined;
if (!subjPronoun || !objPronoun) return false;
return isInvalidSubjObjCombo(subjPronoun.person, objPronoun.person);
}
function getShareUrl(vps: T.VPSelectionState): string {
const stringJSON = JSON.stringify(vps);
const encoded = LZString.compressToEncodedURIComponent(stringJSON);

View File

@ -5,13 +5,15 @@ import { getVerbInfo } from "../../lib/verb-info";
import Hider from "../Hider";
import useStickyState from "../../lib/useStickyState";
import CompoundDisplay from "./CompoundDisplay";
import { changeStatDyn, changeTransitivity, changeVoice } from "./verb-selection";
import {
VpsReducerAction
} from "./vps-reducer";
// TODO: dark on past tense selecitons
function VerbPicker(props: {
vps: T.VPSelectionState,
onChange: (p: T.VPSelectionState) => void,
onChange: (a: VpsReducerAction) => void,
opts: T.TextOptions,
handleLinkClick: ((ts: number) => void) | "none",
}) {
@ -28,43 +30,26 @@ function VerbPicker(props: {
return <div>ERROR: Verb version should be select first</div>;
}
function onVoiceSelect(value: "active" | "passive") {
if (props.vps.verb && props.vps.verb.canChangeVoice) {
if (value === "passive" && props.vps.verb.tenseCategory === "imperative") {
return;
}
if (value === "passive" && (typeof props.vps.verb.object === "object")) {
props.onChange({
...props.vps,
subject: props.vps.verb.object,
verb: changeVoice(props.vps.verb, value, props.vps.verb.object),
type: "set voice",
payload: value,
});
} else {
props.onChange({
...props.vps,
verb: changeVoice(props.vps.verb, value, value === "active" ? props.vps.subject : undefined),
});
}
}
}
function notInstransitive(t: "transitive" | "intransitive" | "grammatically transitive"): "transitive" | "grammatically transitive" {
return t === "intransitive" ? "transitive" : t;
}
function handleChangeTransitivity(t: "transitive" | "grammatically transitive") {
if (props.vps.verb && props.vps.verb.canChangeTransitivity) {
function handleChangeTransitivity(payload: "transitive" | "grammatically transitive") {
props.onChange({
...props.vps,
verb: changeTransitivity(props.vps.verb, t),
type: "set transitivity",
payload,
});
}
}
function handleChangeStatDyn(c: "stative" | "dynamic") {
if (props.vps.verb && props.vps.verb.canChangeStatDyn) {
function handleChangeStatDyn(payload: "stative" | "dynamic") {
props.onChange({
...props.vps,
verb: changeStatDyn(props.vps.verb, c),
type: "set statDyn",
payload,
});
}
}
return <div className="mb-3">
{info && <CompoundDisplay
info={info}

View File

@ -0,0 +1,215 @@
import * as T from "../../types";
import {
isInvalidSubjObjCombo,
} from "../../lib/phrase-building/vp-tools";
import { switchSubjObj } from "../../lib/phrase-building/vp-tools";
import { changeStatDyn, changeTransitivity, changeVoice } from "./verb-selection";
import { ensure2ndPersSubjPronounAndNoConflict } from "../../lib/phrase-building/vp-tools";
import {
isPerfectTense,
isImperativeTense,
} from "../../lib/type-predicates";
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: "shrink possesive",
payload: number | undefined,
} | {
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",
}
export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction): T.VPSelectionState {
if (action.type === "load vps") {
return action.payload;
}
if (action.type === "set subject") {
const { subject, skipPronounConflictCheck } = action.payload;
if (
!skipPronounConflictCheck
&&
hasPronounConflict(subject, vps.verb?.object)
) {
alert("That combination of pronouns is not allowed");
return vps;
}
return {
...vps,
subject: action.payload.subject,
};
}
if (action.type === "set object") {
if (!vps.verb) return vps;
if ((vps.verb.object === "none") || (typeof vps.verb.object === "number")) {
return vps;
}
const object = action.payload;
// check for pronoun conflict
if (hasPronounConflict(vps.subject, object)) {
alert("That combination of pronouns is not allowed");
return vps;
}
return {
...vps,
verb: {
...vps.verb,
object,
},
};
}
if (action.type === "swap subj/obj") {
if (vps.verb?.isCompound === "dynamic") return vps;
return switchSubjObj(vps);
}
if (action.type === "shrink possesive") {
return {
...vps,
shrunkenPossesive: action.payload,
};
}
if (action.type === "set form") {
return {
...vps,
form: action.payload,
};
}
if (action.type === "set voice") {
if (vps.verb && vps.verb.canChangeVoice) {
const voice = action.payload;
if (voice === "passive" && vps.verb.tenseCategory === "imperative") {
return vps;
}
if (voice === "passive" && (typeof vps.verb.object === "object")) {
return {
...vps,
subject: vps.verb.object,
verb: changeVoice(vps.verb, voice, vps.verb.object),
};
} else {
return {
...vps,
verb: changeVoice(vps.verb, voice, voice === "active" ? vps.subject : undefined),
};
}
} else {
return vps;
}
}
if (action.type === "set transitivity") {
if (!(vps.verb && vps.verb.canChangeTransitivity)) return vps;
return {
...vps,
verb: changeTransitivity(vps.verb, action.payload),
};
}
if (action.type === "set statDyn") {
if (!(vps.verb && vps.verb.canChangeStatDyn)) return vps;
return {
...vps,
verb: changeStatDyn(vps.verb, 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,
},
});
}
return {
...vps,
verb: {
...vps.verb,
tenseCategory: category,
},
};
// }
}
function hasPronounConflict(subject: T.NPSelection | undefined, object: undefined | T.VerbObject): boolean {
const subjPronoun = (subject && subject.type === "pronoun") ? subject : undefined;
const objPronoun = (object && typeof object === "object" && object.type === "pronoun") ? object : undefined;
if (!subjPronoun || !objPronoun) return false;
return isInvalidSubjObjCombo(subjPronoun.person, objPronoun.person);
}

View File

@ -14,14 +14,14 @@ import { removeAccents } from "../accent-helpers";
import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools";
import {
orderKidsSection,
findPossesiveToShrink,
findPossesiveToShrinkInEP,
shrinkNP,
} from "./compile-tools";
export function compileEP(EP: T.EPRendered, form: T.FormVersion): { ps: T.SingleOrLengthOpts<T.PsString[]>, 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<T.PsString[]>, e?: string[] } {
const { kids, NPs } = getSegmentsAndKids(EP, form);
export function compileEP(EP: T.EPRendered): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string[] };
export function compileEP(EP: T.EPRendered, combineLengths: true): { ps: T.PsString[], e?: string[] };
export function compileEP(EP: T.EPRendered, combineLengths?: true): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string[] } {
const { kids, NPs } = getSegmentsAndKids(EP);
const equative = EP.equative.ps;
const psResult = compilePs({
NPs,
@ -35,15 +35,9 @@ export function compileEP(EP: T.EPRendered, form: T.FormVersion, combineLengths?
};
}
function getSegmentsAndKids(EP: T.EPRendered, form: Omit<T.FormVersion, "shrinkServant">): { kids: Segment[], NPs: Segment[] } {
function ifNotRemoved(s: Segment, role: "subject" | "predicate"): Segment[] {
if (form.removeKing && EP.king === role) {
return [];
}
return [s];
}
const possToShrink = findPossesiveToShrink(EP);
const shrunkenPossAllowed = !(form.removeKing && possToShrink?.role === "king");
function getSegmentsAndKids(EP: T.EPRendered): { kids: Segment[], NPs: Segment[] } {
const possToShrink = findPossesiveToShrinkInEP(EP);
const shrunkenPossAllowed = !((possToShrink?.from === "subject") && EP.omitSubject);
const possUid = shrunkenPossAllowed ? EP.shrunkenPossesive : undefined;
const subject = makeSegment(getPashtoFromRendered(EP.subject, possUid, false));
const predicate = makeSegment(getPashtoFromRendered(EP.predicate, possUid, false));
@ -53,12 +47,12 @@ function getSegmentsAndKids(EP: T.EPRendered, form: Omit<T.FormVersion, "shrinkS
? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])]
: [],
...(possToShrink && shrunkenPossAllowed)
? [shrinkNP(possToShrink)]
? [shrinkNP(possToShrink.np)]
: [],
]),
NPs: [
...ifNotRemoved(subject, "subject"),
...ifNotRemoved(predicate, "predicate"),
...EP.omitSubject ? [] : [subject],
predicate
],
};
}

View File

@ -26,28 +26,55 @@ export function orderKidsSection(kids: Segment[]): Segment[] {
});
}
export function findPossesiveToShrink(VP: T.VPRendered | T.EPRendered): T.Rendered<T.NPSelection> | undefined {
const uid = VP.shrunkenPossesive;
function findPossesiveInNP(NP: T.Rendered<T.NPSelection> | T.ObjectNP | undefined): T.Rendered<T.NPSelection> | undefined {
function findPossesiveInNP(NP: T.Rendered<T.NPSelection> | T.ObjectNP | undefined, uid: number): T.Rendered<T.NPSelection> | 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);
return findPossesiveInNP(NP.possesor.np, uid);
}
export function findPossesiveToShrinkInEP(EP: T.EPRendered): {
np: T.Rendered<T.NPSelection>,
from: "subject" | "predicate",
} | undefined {
const uid = EP.shrunkenPossesive;
if (uid === undefined) return undefined;
const objPred: T.Rendered<T.NPSelection> | 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<T.NPSelection>
const inSubject = findPossesiveInNP(EP.subject, uid);
if (inSubject) {
return {
np: inSubject,
from: "subject",
};
}
if (EP.predicate.type === "adjective" || EP.predicate.type === "loc. adv.") {
return undefined;
}
// ts being stupid
const predicate = EP.predicate as T.Rendered<T.NPSelection>;
const inPredicate = findPossesiveInNP(predicate, uid);
if (inPredicate) {
return {
np: inPredicate,
from: "predicate",
};
}
return undefined;
}
export function findPossesiveToShrinkInVP(VP: T.VPRendered): T.Rendered<T.NPSelection> | undefined {
const uid = VP.shrunkenPossesive;
if (uid === undefined) return undefined;
const obj: T.Rendered<T.NPSelection> | undefined = ("object" in VP && typeof VP.object === "object")
? VP.object
: undefined;
return (
findPossesiveInNP(VP.subject)
findPossesiveInNP(VP.subject, uid)
||
findPossesiveInNP(objPred)
findPossesiveInNP(obj, uid)
);
}

View File

@ -22,7 +22,7 @@ import { isImperativeTense, isModalTense, isPerfectTense } from "../type-predica
import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools";
import {
orderKidsSection,
findPossesiveToShrink,
findPossesiveToShrinkInVP,
shrinkNP,
} from "./compile-tools";
@ -93,7 +93,7 @@ function getSegmentsAndKids(VP: T.VPRendered, form: Form): { kids: Segment[], NP
return servant;
})();
const shrunkenServant = toShrinkServant ? shrinkNP(toShrinkServant) : undefined;
const possToShrink = findPossesiveToShrink(VP);
const possToShrink = findPossesiveToShrinkInVP(VP);
const shrunkenPossesive = possToShrink ? shrinkNP(possToShrink) : undefined;
const shrunkenPossAllowed = possToShrink && shrunkenPossesive && (
!shrunkenServant || !psStringEquals(shrunkenPossesive.ps[0], shrunkenServant.ps[0])

View File

@ -33,7 +33,7 @@ export function renderEP(EP: T.EPSelectionComplete): T.EPRendered {
equative: renderEquative(EP.equative, kingPerson),
englishBase: equativeBuilders[EP.equative.tense](kingPerson, EP.equative.negative),
shrunkenPossesive: EP.shrunkenPossesive,
form: EP.form,
omitSubject: EP.omitSubject,
};
}

View File

@ -1,5 +1,7 @@
import { useEffect, useState } from "react";
type SaveableData = string | number | object | boolean | undefined | null
/**
* replacement from the React useState hook that will persist the state in local storage
*
@ -8,7 +10,7 @@ import { useEffect, useState } from "react";
* @param key a key for saving the state in locolStorage
* @returns
*/
export default function useStickyState<T extends string | number | object | boolean | undefined | null>(defaultValue: T | ((old: T | undefined) => T), key: string): [
export default function useStickyState<T extends SaveableData>(defaultValue: T | ((old: T | undefined) => T), key: string): [
value: T,
setValue: React.Dispatch<React.SetStateAction<T>>,
] {
@ -42,3 +44,17 @@ export default function useStickyState<T extends string | number | object | bool
return [value, setValue];
}
export function useStickyReducer<T extends SaveableData, A>(
reducer: (state: T, dispatch: A) => T,
defaultValue: T | ((old: T | undefined) => T),
key: string,
): [T, (action: A) => void] {
const [state, unsafeSetState] = useStickyState<T>(defaultValue, key);
function adjustState(action: A) {
unsafeSetState(oldState => {
return reducer(oldState, action);
});
}
return [state, adjustState];
}

View File

@ -667,7 +667,7 @@ export type EPSelectionState = {
},
equative: EquativeSelection,
shrunkenPossesive: undefined | number,
form: FormVersion,
omitSubject: boolean,
};
export type EPSelectionComplete = Omit<EPSelectionState, "subject" | "predicate"> & {
@ -679,7 +679,7 @@ export type EPSelectionComplete = Omit<EPSelectionState, "subject" | "predicate"
type: "Complement",
selection: EqCompSelection,
},
form: FormVersion,
omitSubject: boolean,
};
export type EqCompType = "adjective" | "loc. adv."; // TODO: - more
@ -700,11 +700,11 @@ export type EPRendered = {
type: "EPRendered",
king: "subject" | "predicate",
subject: Rendered<NPSelection>,
predicate: Rendered<NPSelection | EqCompSelection>,
predicate: Rendered<NPSelection> | Rendered<EqCompSelection>,
equative: EquativeRendered,
englishBase?: string[],
shrunkenPossesive: undefined | number,
form: FormVersion,
omitSubject: boolean,
}
export type EntryFeeder = {