PROPER SHRINKNIG IN PHRASE BUILDERS!
This commit is contained in:
parent
240fd11ed8
commit
9974eba176
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@lingdocs/pashto-inflector",
|
||||
"version": "2.3.9",
|
||||
"version": "2.4.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",
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2021 lingdocs.com
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
*/
|
||||
|
||||
type Props = {
|
||||
label: string,
|
||||
options: { label: any, name: string, disabled?: boolean }[],
|
||||
checked: string[],
|
||||
handleChange: (p: { name: string, checked: boolean }) => void;
|
||||
}
|
||||
|
||||
export default function (props: Props) {
|
||||
function handleCheck(e: any) {
|
||||
props.handleChange({
|
||||
name: e.target.name as string,
|
||||
checked: e.target.checked as boolean,
|
||||
});
|
||||
}
|
||||
return (
|
||||
<div className="text-center">
|
||||
<span className="mr-2">{props.label}</span>
|
||||
{props.options.map((option) => (
|
||||
<div className="form-check form-check-inline">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
name={option.name}
|
||||
onChange={handleCheck}
|
||||
checked={props.checked.includes(option.name)}
|
||||
disabled={option.disabled}
|
||||
/>
|
||||
<label className="form-check-label">{option.label}</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import * as T from "../../types";
|
||||
import { renderEP } from "../../lib/phrase-building/render-ep";
|
||||
import { compileEP } from "../../lib/phrase-building/compile-ep";
|
||||
import { completeEPSelection, renderEP } from "../../lib/phrase-building/render-ep";
|
||||
import { compileEP } from "../../lib/phrase-building/compile";
|
||||
import Examples from "../Examples";
|
||||
import ButtonSelect from "../ButtonSelect";
|
||||
|
||||
|
@ -61,33 +61,4 @@ function VariationLayer({ vs, opts }: { vs: T.PsString[], opts: T.TextOptions })
|
|||
</div>;
|
||||
}
|
||||
|
||||
function completeEPSelection(eps: T.EPSelectionState): T.EPSelectionComplete | undefined {
|
||||
if (!eps.subject) {
|
||||
return undefined;
|
||||
}
|
||||
if (eps.predicate.type === "Complement") {
|
||||
const selection = eps.predicate.Complement;
|
||||
if (!selection) return undefined;
|
||||
return {
|
||||
...eps,
|
||||
subject: eps.subject,
|
||||
predicate: {
|
||||
type: "Complement",
|
||||
selection,
|
||||
},
|
||||
};
|
||||
}
|
||||
// predicate is NP
|
||||
const selection = eps.predicate.NP;
|
||||
if (!selection) return undefined;
|
||||
return {
|
||||
...eps,
|
||||
subject: eps.subject,
|
||||
predicate: {
|
||||
type: "NP",
|
||||
selection,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default EPDisplay;
|
|
@ -8,6 +8,8 @@ import EqCompPicker from "./eq-comp-picker/EqCompPicker";
|
|||
import { roleIcon } from "../vp-explorer/VPExplorerExplanationModal";
|
||||
import EqChartsDisplay from "./EqChartsDisplay";
|
||||
import epsReducer from "./eps-reducer";
|
||||
import { useState } from "react";
|
||||
import { completeEPSelection } from "../../lib/phrase-building/render-ep";
|
||||
const blankEps: T.EPSelectionState = {
|
||||
subject: undefined,
|
||||
predicate: {
|
||||
|
@ -19,7 +21,6 @@ const blankEps: T.EPSelectionState = {
|
|||
tense: "present",
|
||||
negative: false,
|
||||
},
|
||||
shrunkenPossesive: undefined,
|
||||
omitSubject: false,
|
||||
};
|
||||
|
||||
|
@ -30,12 +31,20 @@ function EPExplorer(props: {
|
|||
entryFeeder: T.EntryFeeder,
|
||||
}) {
|
||||
const [mode, setMode] = useStickyState<"charts" | "phrases">("charts", "EPExplorerMode");
|
||||
const [eps, adjustEps] = useStickyReducer(epsReducer, blankEps, "EPSelectionState10");
|
||||
const [eps, adjustEps] = useStickyReducer(epsReducer, blankEps, "EPState", flashMessage);
|
||||
const [alert, setAlert] = useState<string | undefined>(undefined);
|
||||
const king = eps.subject?.type === "pronoun"
|
||||
? "subject"
|
||||
: eps.predicate.type === "Complement"
|
||||
? "subject"
|
||||
: "predicate";
|
||||
function flashMessage(msg: string) {
|
||||
setAlert(msg);
|
||||
setTimeout(() => {
|
||||
setAlert(undefined);
|
||||
}, 1500);
|
||||
}
|
||||
const phraseIsComplete = !!completeEPSelection(eps);
|
||||
return <div>
|
||||
<div className="mt-2 mb-3 text-center">
|
||||
<ButtonSelect
|
||||
|
@ -51,8 +60,7 @@ function EPExplorer(props: {
|
|||
{mode === "phrases" && <>
|
||||
<div className="my-2">
|
||||
<NPPicker
|
||||
shrunkenPossesiveInPhrase={eps.shrunkenPossesive}
|
||||
handleShrinkPossesive={payload => adjustEps({ type: "shrink possesive", payload })}
|
||||
phraseIsComplete={phraseIsComplete}
|
||||
heading={<div className="h5 text-center">Subject {king === "subject" ? roleIcon.king : ""}</div>}
|
||||
entryFeeder={props.entryFeeder}
|
||||
np={eps.subject}
|
||||
|
@ -76,8 +84,7 @@ function EPExplorer(props: {
|
|||
/>
|
||||
</div>
|
||||
{eps.predicate.type === "NP" ? <NPPicker
|
||||
shrunkenPossesiveInPhrase={eps.shrunkenPossesive}
|
||||
handleShrinkPossesive={payload => adjustEps({ type: "shrink possesive", payload })}
|
||||
phraseIsComplete={phraseIsComplete}
|
||||
entryFeeder={props.entryFeeder}
|
||||
np={eps.predicate.type === "NP" ? eps.predicate.NP : undefined}
|
||||
counterPart={undefined}
|
||||
|
@ -107,6 +114,15 @@ function EPExplorer(props: {
|
|||
eps={eps}
|
||||
setOmitSubject={payload => adjustEps({ type: "set omitSubject", payload })}
|
||||
/>}
|
||||
{alert && <div className="alert alert-warning text-center" role="alert" style={{
|
||||
position: "fixed",
|
||||
top: "30%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
zIndex: 9999999999999,
|
||||
}}>
|
||||
{alert}
|
||||
</div>}
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as T from "../../types"
|
|||
import Select from "react-select";
|
||||
import ButtonSelect from "../ButtonSelect";
|
||||
|
||||
export const zIndexProps = {
|
||||
const zIndexProps = {
|
||||
menuPortalTarget: document.body,
|
||||
styles: { menuPortal: (base: any) => ({ ...base, zIndex: 9999 }) },
|
||||
};
|
||||
|
|
|
@ -4,8 +4,9 @@ import {
|
|||
personNumber,
|
||||
} from "../../lib/misc-helpers";
|
||||
import { isUnisexNounEntry } from "../../lib/type-predicates";
|
||||
import { checkForMiniPronounsError } from "../../lib/phrase-building/compile";
|
||||
|
||||
export type EpsReducerAction = {
|
||||
type EpsReducerAction = {
|
||||
type: "set predicate type",
|
||||
payload: "NP" | "Complement",
|
||||
} | {
|
||||
|
@ -17,9 +18,6 @@ export type EpsReducerAction = {
|
|||
} | {
|
||||
type: "set predicate comp",
|
||||
payload: T.EqCompSelection | undefined,
|
||||
} | {
|
||||
type: "shrink possesive",
|
||||
payload: number | undefined,
|
||||
} | {
|
||||
type: "set omitSubject",
|
||||
payload: "true" | "false",
|
||||
|
@ -28,7 +26,7 @@ export type EpsReducerAction = {
|
|||
payload: T.EquativeSelection,
|
||||
};
|
||||
|
||||
export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAction): T.EPSelectionState {
|
||||
export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAction, sendAlert?: (msg: string) => void): T.EPSelectionState {
|
||||
if (action.type === "set predicate type") {
|
||||
return {
|
||||
...eps,
|
||||
|
@ -39,10 +37,74 @@ export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAc
|
|||
};
|
||||
}
|
||||
if (action.type === "set subject") {
|
||||
return massageSubjectChange(action.payload, eps);
|
||||
const subject = action.payload;
|
||||
if (!subject) {
|
||||
return {
|
||||
...eps,
|
||||
subject,
|
||||
};
|
||||
}
|
||||
if (subject.type === "pronoun" && eps.predicate.type === "NP" && eps.predicate.NP?.type === "noun" && isUnisexNounEntry(eps.predicate.NP.entry)) {
|
||||
const predicate = eps.predicate.NP;
|
||||
const adjusted = {
|
||||
...predicate,
|
||||
...predicate.numberCanChange ? {
|
||||
number: personNumber(subject.person),
|
||||
} : {},
|
||||
...predicate.genderCanChange ? {
|
||||
gender: personGender(subject.person),
|
||||
} : {},
|
||||
}
|
||||
return {
|
||||
...eps,
|
||||
subject,
|
||||
predicate: {
|
||||
...eps.predicate,
|
||||
NP: adjusted,
|
||||
},
|
||||
};
|
||||
}
|
||||
const n: T.EPSelectionState = {
|
||||
...eps,
|
||||
subject,
|
||||
};
|
||||
return subject ? ensureMiniPronounsOk(eps, n, sendAlert) : n;
|
||||
}
|
||||
if (action.type === "set predicate NP") {
|
||||
return massageNPPredicateChange(action.payload, eps);
|
||||
const selection = action.payload;
|
||||
if (!selection) {
|
||||
return {
|
||||
...eps,
|
||||
predicate: {
|
||||
...eps.predicate,
|
||||
NP: selection,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (eps.subject?.type === "pronoun" && selection.type === "noun" && isUnisexNounEntry(selection.entry)) {
|
||||
const { gender, number } = selection;
|
||||
const pronoun = eps.subject.person;
|
||||
const newPronoun = movePersonNumber(movePersonGender(pronoun, gender), number);
|
||||
return {
|
||||
...eps,
|
||||
subject: {
|
||||
...eps.subject,
|
||||
person: newPronoun,
|
||||
},
|
||||
predicate: {
|
||||
...eps.predicate,
|
||||
NP: selection,
|
||||
},
|
||||
};
|
||||
}
|
||||
const n: T.EPSelectionState = {
|
||||
...eps,
|
||||
predicate: {
|
||||
...eps.predicate,
|
||||
NP: selection,
|
||||
},
|
||||
};
|
||||
return selection ? ensureMiniPronounsOk(eps, n, sendAlert) : n;
|
||||
}
|
||||
if (action.type === "set predicate comp") {
|
||||
return {
|
||||
|
@ -53,17 +115,12 @@ export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAc
|
|||
},
|
||||
};
|
||||
}
|
||||
if (action.type === "shrink possesive") {
|
||||
return {
|
||||
...eps,
|
||||
shrunkenPossesive: action.payload,
|
||||
};
|
||||
}
|
||||
if (action.type === "set omitSubject") {
|
||||
return {
|
||||
const n: T.EPSelectionState = {
|
||||
...eps,
|
||||
omitSubject: action.payload === "true",
|
||||
};
|
||||
return ensureMiniPronounsOk(eps, n, sendAlert);
|
||||
}
|
||||
// if (action.type === "set equative") {
|
||||
return {
|
||||
|
@ -73,72 +130,13 @@ export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAc
|
|||
// }
|
||||
}
|
||||
|
||||
function massageSubjectChange(subject: T.NPSelection | undefined, old: T.EPSelectionState): T.EPSelectionState {
|
||||
if (!subject) {
|
||||
return {
|
||||
...old,
|
||||
subject,
|
||||
};
|
||||
function ensureMiniPronounsOk(old: T.EPSelectionState, eps: T.EPSelectionState, sendAlert?: (msg: string) => void): T.EPSelectionState {
|
||||
const error = checkForMiniPronounsError(eps);
|
||||
if (error) {
|
||||
if (sendAlert) sendAlert(error);
|
||||
return old;
|
||||
}
|
||||
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,
|
||||
},
|
||||
};
|
||||
return eps;
|
||||
}
|
||||
|
||||
function movePersonGender(p: T.Person, gender: T.Gender): T.Person {
|
||||
|
|
|
@ -10,6 +10,8 @@ import { isSecondPerson } from "../../lib/phrase-building/vp-tools";
|
|||
|
||||
const npTypes: T.NPType[] = ["pronoun", "noun", "participle"];
|
||||
|
||||
export const shrunkenBackground = "rgba(255, 206, 43, 0.15)";
|
||||
|
||||
function NPPicker(props: {
|
||||
heading?: JSX.Element | string,
|
||||
onChange: (nps: T.NPSelection | undefined) => void,
|
||||
|
@ -17,24 +19,27 @@ function NPPicker(props: {
|
|||
counterPart: T.NPSelection | T.VerbObject | undefined,
|
||||
role: "subject" | "object" | "ergative" | "possesor",
|
||||
opts: T.TextOptions,
|
||||
handleShrinkPossesive: (uid: number | undefined) => void,
|
||||
shrunkenPossesiveInPhrase: number | undefined,
|
||||
cantClear?: boolean,
|
||||
is2ndPersonPicker?: boolean,
|
||||
entryFeeder: T.EntryFeeder,
|
||||
phraseIsComplete: boolean,
|
||||
isShrunk?: boolean,
|
||||
}) {
|
||||
if (props.is2ndPersonPicker && ((props.np?.type !== "pronoun") || !isSecondPerson(props.np.person))) {
|
||||
throw new Error("can't use 2ndPerson NPPicker without a pronoun");
|
||||
}
|
||||
}
|
||||
const [addingPoss, setAddingPoss] = useState<boolean>(false);
|
||||
const [npType, setNpType] = useState<T.NPType | undefined>(props.np ? props.np.type : undefined);
|
||||
const onChange = (np: T.NPSelection | undefined) => {
|
||||
props.onChange(ensureSingleShrink(props.np, np))
|
||||
}
|
||||
useEffect(() => {
|
||||
setNpType(props.np ? props.np.type : undefined);
|
||||
}, [props.np]);
|
||||
function handleClear() {
|
||||
if (props.np && props.np.type === "noun" && props.np.dynamicComplement) return;
|
||||
setNpType(undefined);
|
||||
props.onChange(undefined);
|
||||
onChange(undefined);
|
||||
}
|
||||
function handleNPTypeChange(ntp: T.NPType) {
|
||||
if (ntp === "pronoun") {
|
||||
|
@ -45,38 +50,46 @@ function NPPicker(props: {
|
|||
distance: "far",
|
||||
};
|
||||
setNpType(ntp);
|
||||
props.onChange(pronoun);
|
||||
onChange(pronoun);
|
||||
} else {
|
||||
props.onChange(undefined);
|
||||
onChange(undefined);
|
||||
setNpType(ntp);
|
||||
}
|
||||
}
|
||||
// TODO: REMOVE
|
||||
function handlePossesiveChange(p: T.NPSelection | undefined) {
|
||||
if (!props.np || props.np.type === "pronoun") return;
|
||||
if (!p) {
|
||||
props.onChange({
|
||||
onChange({
|
||||
...props.np,
|
||||
possesor: undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const isNewPosesser = checkForNewPossesor(p, props.np.possesor);
|
||||
const possesor = {
|
||||
const possesor: T.PossesorSelection = {
|
||||
np: p,
|
||||
uid: (!isNewPosesser && props.np.possesor) ? props.np.possesor.uid : makeUID(),
|
||||
shrunken: (!isNewPosesser && props.np.possesor) ? props.np.possesor.shrunken : false,
|
||||
};
|
||||
props.onChange({
|
||||
onChange({
|
||||
...props.np,
|
||||
possesor,
|
||||
});
|
||||
}
|
||||
function handleToggleShrunken() {
|
||||
if (!props.np || props.np.type === "pronoun" || !props.np.possesor || !props.phraseIsComplete) return;
|
||||
onChange({
|
||||
...props.np,
|
||||
possesor: {
|
||||
...props.np.possesor,
|
||||
shrunken: !props.np.possesor.shrunken,
|
||||
},
|
||||
});
|
||||
}
|
||||
const isDynamicComplement = props.np && props.np.type === "noun" && props.np.dynamicComplement;
|
||||
const clearButton = (!props.cantClear && !props.is2ndPersonPicker && !isDynamicComplement)
|
||||
? <button className="btn btn-sm btn-light mb-2" onClick={handleClear}>X</button>
|
||||
: <div></div>;
|
||||
const possesiveUid = (props.np && props.np.type !== "pronoun" && props.np.possesor)
|
||||
? props.np.possesor.uid
|
||||
: undefined;
|
||||
return <>
|
||||
<div className="d-flex flex-row justify-content-between">
|
||||
<div></div>
|
||||
|
@ -105,16 +118,15 @@ function NPPicker(props: {
|
|||
</button>
|
||||
</div>)}
|
||||
</div>}
|
||||
{(props.np && props.np.type !== "pronoun" && (props.np.possesor || addingPoss)) && <div className="mb-3" style={{ paddingLeft: "0.5rem", borderLeft: "1px solid grey" }}>
|
||||
{(props.np && props.np.type !== "pronoun" && (props.np.possesor || addingPoss)) && <div className="mb-3" style={{
|
||||
paddingLeft: "0.65rem",
|
||||
borderLeft: "2px solid grey",
|
||||
background: (props.np.possesor?.shrunken && !props.isShrunk) ? shrunkenBackground : "inherit",
|
||||
}}>
|
||||
<div className="d-flex flex-row text-muted mb-2">
|
||||
<div>Possesive:</div>
|
||||
{props.np.possesor && <div className="clickable mx-2" onClick={() => {
|
||||
props.handleShrinkPossesive(possesiveUid === props.shrunkenPossesiveInPhrase
|
||||
? undefined
|
||||
: possesiveUid
|
||||
);
|
||||
}}>
|
||||
{possesiveUid === props.shrunkenPossesiveInPhrase ? "👶 Shrunken" : "Shrink"}
|
||||
{(props.np.possesor && !props.isShrunk) && <div className="clickable ml-3 mr-2" onClick={handleToggleShrunken}>
|
||||
{!props.np.possesor.shrunken ? "🪄" : "👶"}
|
||||
</div>}
|
||||
<div className="clickable ml-2" onClick={() => {
|
||||
setAddingPoss(false);
|
||||
|
@ -124,12 +136,11 @@ function NPPicker(props: {
|
|||
</div>
|
||||
</div>
|
||||
<NPPicker
|
||||
phraseIsComplete={props.phraseIsComplete}
|
||||
onChange={handlePossesiveChange}
|
||||
counterPart={undefined}
|
||||
cantClear
|
||||
np={props.np.possesor ? props.np.possesor.np : undefined}
|
||||
handleShrinkPossesive={props.handleShrinkPossesive}
|
||||
shrunkenPossesiveInPhrase={props.shrunkenPossesiveInPhrase}
|
||||
role="possesor"
|
||||
opts={props.opts}
|
||||
entryFeeder={props.entryFeeder}
|
||||
|
@ -142,7 +153,7 @@ function NPPicker(props: {
|
|||
? <PronounPicker
|
||||
role={props.role}
|
||||
pronoun={props.np}
|
||||
onChange={props.onChange}
|
||||
onChange={onChange}
|
||||
is2ndPersonPicker={props.is2ndPersonPicker}
|
||||
opts={props.opts}
|
||||
/>
|
||||
|
@ -150,14 +161,14 @@ function NPPicker(props: {
|
|||
? <NounPicker
|
||||
entryFeeder={props.entryFeeder}
|
||||
noun={(props.np && props.np.type === "noun") ? props.np : undefined}
|
||||
onChange={props.onChange}
|
||||
onChange={onChange}
|
||||
opts={props.opts}
|
||||
/>
|
||||
: npType === "participle"
|
||||
? <ParticiplePicker
|
||||
entryFeeder={props.entryFeeder.verbs}
|
||||
participle={(props.np && props.np.type === "participle") ? props.np : undefined}
|
||||
onChange={props.onChange}
|
||||
onChange={onChange}
|
||||
opts={props.opts}
|
||||
/>
|
||||
: null
|
||||
|
@ -166,6 +177,52 @@ function NPPicker(props: {
|
|||
</>;
|
||||
}
|
||||
|
||||
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.type === "pronoun") return 0;
|
||||
if (!np.possesor) return 0;
|
||||
return (np.possesor.shrunken ? 1 : 0) + countShrinks(np.possesor.np);
|
||||
}
|
||||
function keepNewShrink(old: T.NPSelection, n: T.NPSelection): T.NPSelection {
|
||||
if (n.type === "pronoun") return n;
|
||||
if (old.type === "pronoun" || !n.possesor || !old.possesor) return n;
|
||||
if (n.possesor.shrunken && !old.possesor.shrunken) {
|
||||
return {
|
||||
...n,
|
||||
possesor: {
|
||||
...n.possesor,
|
||||
np: removeShrinks(n.possesor.np),
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
...n,
|
||||
possesor: {
|
||||
shrunken: false,
|
||||
np: keepNewShrink(old.possesor.np, n.possesor.np),
|
||||
},
|
||||
}
|
||||
}
|
||||
function removeShrinks(n: T.NPSelection): T.NPSelection {
|
||||
if (n.type === "pronoun") return n;
|
||||
if (!n.possesor) return n;
|
||||
return {
|
||||
...n,
|
||||
possesor: {
|
||||
shrunken: false,
|
||||
np: removeShrinks(n.possesor.np),
|
||||
},
|
||||
};
|
||||
}
|
||||
if (!old) return s;
|
||||
if (s.type === "pronoun") return s;
|
||||
if (!s.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;
|
||||
|
@ -183,8 +240,4 @@ function checkForNewPossesor(n: T.NPSelection | undefined, old: T.PossesorSelect
|
|||
return false;
|
||||
}
|
||||
|
||||
function makeUID() {
|
||||
return Math.floor(Math.random() * 10000000);
|
||||
}
|
||||
|
||||
export default NPPicker;
|
|
@ -1,21 +1,16 @@
|
|||
import { renderVP, compileVP } from "../../lib/phrase-building/index";
|
||||
import { compileVP } from "../../lib/phrase-building/compile";
|
||||
import * as T from "../../types";
|
||||
import AbbreviationFormSelector from "./AbbreviationFormSelector";
|
||||
import {
|
||||
isPastTense,
|
||||
completeVPSelection,
|
||||
} from "../../lib/phrase-building/vp-tools";
|
||||
import useStickyState from "../../lib/useStickyState";
|
||||
import Examples from "../Examples";
|
||||
|
||||
function VPDisplay({ VP, opts, setForm }: {
|
||||
VP: T.VPSelectionState,
|
||||
VP: T.VPSelectionState | T.VPRendered,
|
||||
opts: T.TextOptions,
|
||||
setForm: (form: T.FormVersion) => void,
|
||||
}) {
|
||||
const [OSV, setOSV] = useStickyState<boolean>(false, "includeOSV");
|
||||
const VPComplete = completeVPSelection(VP);
|
||||
if (!VPComplete) {
|
||||
if (!("type" in VP)) {
|
||||
return <div className="lead text-muted text-center mt-4">
|
||||
{(() => {
|
||||
const twoNPs = (VP.subject === undefined) && (VP.verb.object === undefined);
|
||||
|
@ -23,7 +18,7 @@ function VPDisplay({ VP, opts, setForm }: {
|
|||
})()}
|
||||
</div>;
|
||||
}
|
||||
const result = compileVP(renderVP(VPComplete), { ...VP.form, OSV });
|
||||
const result = compileVP(VP, { ...VP.form, OSV });
|
||||
return <div className="text-center mt-1">
|
||||
{VP.verb.transitivity === "transitive" && <div className="form-check mb-2">
|
||||
<input
|
||||
|
@ -38,7 +33,7 @@ function VPDisplay({ VP, opts, setForm }: {
|
|||
</label>
|
||||
</div>}
|
||||
<AbbreviationFormSelector
|
||||
adjustable={whatsAdjustable(VPComplete)}
|
||||
adjustable={VP.whatsAdjustable}
|
||||
form={VP.form}
|
||||
onChange={setForm}
|
||||
/>
|
||||
|
@ -61,20 +56,6 @@ function VPDisplay({ VP, opts, setForm }: {
|
|||
</div>
|
||||
}
|
||||
|
||||
function whatsAdjustable(VP: T.VPSelectionComplete): "both" | "king" | "servant" {
|
||||
// TODO: intransitive dynamic compounds?
|
||||
return (VP.verb.isCompound === "dynamic" && VP.verb.transitivity === "transitive")
|
||||
? (isPastTense(VP.verb.tense) ? "servant" : "king")
|
||||
: VP.verb.transitivity === "transitive"
|
||||
? "both"
|
||||
: VP.verb.transitivity === "intransitive"
|
||||
? "king"
|
||||
// grammTrans
|
||||
: isPastTense(VP.verb.tense)
|
||||
? "servant"
|
||||
: "king";
|
||||
}
|
||||
|
||||
function VariationLayer({ vs, opts }: { vs: T.PsString[], opts: T.TextOptions }) {
|
||||
return <div className="mb-2">
|
||||
<Examples opts={opts} lineHeight={0}>{vs}</Examples>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import NPPicker from "../np-picker/NPPicker";
|
||||
import NPPicker, { shrunkenBackground } from "../np-picker/NPPicker";
|
||||
import VerbPicker from "./VerbPicker";
|
||||
import TensePicker from "./TensePicker";
|
||||
import VPDisplay from "./VPDisplay";
|
||||
|
@ -8,13 +8,14 @@ import ChartDisplay from "./VPChartDisplay";
|
|||
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 { getKingAndServant, renderVP } from "../../lib/phrase-building/render-vp";
|
||||
import { completeVPSelection, isPastTense } from "../../lib/phrase-building/vp-tools";
|
||||
import VPExplorerQuiz from "./VPExplorerQuiz";
|
||||
import VPExplorerExplanationModal, { roleIcon } from "./VPExplorerExplanationModal";
|
||||
// @ts-ignore
|
||||
import LZString from "lz-string";
|
||||
import { vpsReducer } from "./vps-reducer";
|
||||
import { getShrunkenServant } from "../../lib/phrase-building/compile";
|
||||
|
||||
const phraseURLParam = "VPPhrase";
|
||||
|
||||
|
@ -28,7 +29,7 @@ const phraseURLParam = "VPPhrase";
|
|||
|
||||
// TODO: error handling on error with rendering etc
|
||||
|
||||
export function VPExplorer(props: {
|
||||
function VPExplorer(props: {
|
||||
loaded?: T.VPSelectionState,
|
||||
verb: T.VerbEntry,
|
||||
opts: T.TextOptions,
|
||||
|
@ -40,7 +41,8 @@ export function VPExplorer(props: {
|
|||
props.loaded
|
||||
? props.loaded
|
||||
: savedVps => makeVPSelectionState(props.verb, savedVps),
|
||||
"vpsState8",
|
||||
"vpsState8",
|
||||
flashMessage,
|
||||
);
|
||||
const [mode, setMode] = useStickyState<"charts" | "phrases" | "quiz">(
|
||||
savedMode => {
|
||||
|
@ -51,12 +53,19 @@ export function VPExplorer(props: {
|
|||
"verbExplorerMode2",
|
||||
);
|
||||
const [showShareClipped, setShowShareClipped] = useState<boolean>(false);
|
||||
const [alert, setAlert] = useState<string | undefined>(undefined);
|
||||
const [showingExplanation, setShowingExplanation] = useState<{ role: "servant" | "king", item: "subject" | "object" } | false>(false);
|
||||
const isPast = isPastTense(vps.verb.tenseCategory === "perfect" ? vps.verb.perfectTense : vps.verb.verbTense);
|
||||
const roles = getKingAndServant(
|
||||
isPast,
|
||||
vps.verb.transitivity !== "intransitive",
|
||||
);
|
||||
function flashMessage(msg: string) {
|
||||
setAlert(msg);
|
||||
setTimeout(() => {
|
||||
setAlert(undefined);
|
||||
}, 1500);
|
||||
}
|
||||
useEffect(() => {
|
||||
const VPSFromUrl = getVPSFromUrl();
|
||||
if (VPSFromUrl) {
|
||||
|
@ -90,16 +99,10 @@ export function VPExplorer(props: {
|
|||
function handleSubjObjSwap() {
|
||||
adjustVps({ type: "swap subj/obj" });
|
||||
}
|
||||
function handleShrinkPossesive(shrunkenPossesive: number | undefined) {
|
||||
adjustVps({
|
||||
type: "shrink possesive",
|
||||
payload: shrunkenPossesive,
|
||||
});
|
||||
}
|
||||
function quizLock<T>(f: T) {
|
||||
if (mode === "quiz") {
|
||||
return () => {
|
||||
alert("to adjust this, get out of quiz mode");
|
||||
flashMessage("to adjust this, get out of quiz mode");
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
@ -120,6 +123,15 @@ export function VPExplorer(props: {
|
|||
setShowShareClipped(false);
|
||||
}, 1000);
|
||||
}
|
||||
const VPS = completeVPSelection(vps);
|
||||
const phraseIsComplete = !!VPS;
|
||||
const rendered = VPS ? renderVP(VPS) : undefined;
|
||||
const servantIsShrunk = !!(rendered ? getShrunkenServant(rendered) : undefined);
|
||||
function toggleServantShrink() {
|
||||
adjustVps({
|
||||
type: "toggle servant shrink",
|
||||
});
|
||||
}
|
||||
return <div className="mt-3" style={{ maxWidth: "950px"}}>
|
||||
<VerbPicker
|
||||
vps={vps}
|
||||
|
@ -155,40 +167,60 @@ export function VPExplorer(props: {
|
|||
</div>}
|
||||
{mode !== "quiz" && <div className="d-flex flex-row justify-content-around flex-wrap" style={{ marginLeft: "-0.5rem", marginRight: "-0.5rem" }}>
|
||||
{mode === "phrases" && <>
|
||||
<div className="my-2">
|
||||
<div className="my-2" style={{ background: (servantIsShrunk && roles.servant === "subject") ? shrunkenBackground : "inherit" }}>
|
||||
<NPPicker
|
||||
phraseIsComplete={phraseIsComplete}
|
||||
heading={roles.king === "subject"
|
||||
? <div className="h5 text-center clickable" onClick={() => setShowingExplanation({ role: "king", item: "subject" })}>Subject {roleIcon.king}</div>
|
||||
: <div className="h5 text-center clickable" onClick={() => setShowingExplanation({ role: "servant", item: "subject" })}>Subject {roleIcon.servant}</div>}
|
||||
? <div className="h5 text-center" onClick={() => setShowingExplanation({ role: "king", item: "subject" })}>Subject {roleIcon.king}</div>
|
||||
: <div className="h5 text-center">
|
||||
Subject
|
||||
{` `}
|
||||
<span className="clickable" onClick={() => setShowingExplanation({ role: "servant", item: "subject" })}>{roleIcon.servant}</span>
|
||||
{` `}
|
||||
{(rendered && rendered.whatsAdjustable !== "king") &&
|
||||
<span onClick={toggleServantShrink} className="ml-3">
|
||||
{!servantIsShrunk ? "🪄" : "👶"}
|
||||
</span>
|
||||
}
|
||||
</div>}
|
||||
entryFeeder={props.entryFeeder}
|
||||
role={(isPast && vps.verb.transitivity !== "intransitive")
|
||||
? "ergative"
|
||||
: "subject"
|
||||
}
|
||||
shrunkenPossesiveInPhrase={vps.shrunkenPossesive}
|
||||
is2ndPersonPicker={vps.verb.tenseCategory === "imperative"}
|
||||
np={vps.subject}
|
||||
counterPart={vps.verb ? vps.verb.object : undefined}
|
||||
onChange={handleSubjectChange}
|
||||
handleShrinkPossesive={handleShrinkPossesive}
|
||||
opts={props.opts}
|
||||
isShrunk={(servantIsShrunk && roles.servant === "subject")}
|
||||
/>
|
||||
</div>
|
||||
{vps.verb && (vps.verb.object !== "none") && <div className="my-2">
|
||||
{vps.verb && (vps.verb.object !== "none") && <div className="my-2" style={{ background: (servantIsShrunk && roles.servant === "object") ? shrunkenBackground : "inherit" }}>
|
||||
{(typeof vps.verb.object === "number")
|
||||
? <div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div>
|
||||
: <NPPicker
|
||||
shrunkenPossesiveInPhrase={vps.shrunkenPossesive}
|
||||
handleShrinkPossesive={handleShrinkPossesive}
|
||||
phraseIsComplete={phraseIsComplete}
|
||||
heading={roles.king === "object"
|
||||
? <div className="h5 text-center clickable" onClick={() => setShowingExplanation({ role: "king", item: "object" })}>Object {roleIcon.king}</div>
|
||||
: <div className="h5 text-center clickable" onClick={() => setShowingExplanation({ role: "servant", item: "object" })}>Object {roleIcon.servant}</div>}
|
||||
: <div className="h5 text-center">
|
||||
Object
|
||||
{` `}
|
||||
<span className="clickable" onClick={() => setShowingExplanation({ role: "servant", item: "object" })}>{roleIcon.servant}</span>
|
||||
{` `}
|
||||
{(rendered && rendered.whatsAdjustable !== "king") &&
|
||||
<span onClick={toggleServantShrink} className="ml-3">
|
||||
{!servantIsShrunk ? "🪄" : "👶"}
|
||||
</span>
|
||||
}
|
||||
</div>}
|
||||
entryFeeder={props.entryFeeder}
|
||||
role="object"
|
||||
np={vps.verb.object}
|
||||
counterPart={vps.subject}
|
||||
onChange={handleObjectChange}
|
||||
opts={props.opts}
|
||||
isShrunk={(servantIsShrunk && roles.servant === "object")}
|
||||
/>}
|
||||
</div>}
|
||||
</>}
|
||||
|
@ -201,7 +233,7 @@ export function VPExplorer(props: {
|
|||
</div>
|
||||
</div>}
|
||||
{mode === "phrases" && <VPDisplay
|
||||
VP={vps}
|
||||
VP={rendered ? rendered : vps}
|
||||
opts={props.opts}
|
||||
setForm={handleSetForm}
|
||||
/>}
|
||||
|
@ -220,6 +252,15 @@ export function VPExplorer(props: {
|
|||
}}>
|
||||
Phrase URL copied to clipboard
|
||||
</div>}
|
||||
{alert && <div className="alert alert-warning text-center" role="alert" style={{
|
||||
position: "fixed",
|
||||
top: "30%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
zIndex: 9999999999999,
|
||||
}}>
|
||||
{alert}
|
||||
</div>}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@ import { standardizePashto } from "../../lib/standardize-pashto";
|
|||
import shuffleArray from "../../lib/shuffle-array";
|
||||
import InlinePs from "../InlinePs";
|
||||
import { psStringEquals } from "../../lib/p-text-helpers";
|
||||
import { renderVP, compileVP } from "../../lib/phrase-building/index";
|
||||
import { renderVP } from "../../lib/phrase-building/render-vp";
|
||||
import { compileVP } from "../../lib/phrase-building/compile";
|
||||
import { getRandomTense } from "./TensePicker";
|
||||
import { getTenseFromVerbSelection, removeBa, switchSubjObj } from "../../lib/phrase-building/vp-tools";
|
||||
import playAudio from "../../lib/play-audio";
|
||||
|
@ -445,7 +446,6 @@ function getRandomVPSelection(mix: MixType = "both") {
|
|||
return {
|
||||
subject: subject !== undefined ? subject : randSubj,
|
||||
verb: randomizeTense(verb, true),
|
||||
shrunkenPossesive: undefined,
|
||||
form: { removeKing: false, shrinkServant: false },
|
||||
}
|
||||
}
|
||||
|
@ -462,7 +462,6 @@ function getRandomVPSelection(mix: MixType = "both") {
|
|||
return {
|
||||
subject: randSubj,
|
||||
verb: randomizeTense(v, true),
|
||||
shrunkenPossesive: undefined,
|
||||
form: { removeKing: false, shrinkServant: false },
|
||||
};
|
||||
};
|
||||
|
|
|
@ -67,15 +67,6 @@ export function makeVPSelectionState(
|
|||
canChangeStatDyn: "stative" in info,
|
||||
},
|
||||
form: os ? os.form : { removeKing: false, shrinkServant: false },
|
||||
shrunkenPossesive: os ? os.shrunkenPossesive : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function changeVoice(v: T.VerbSelection, voice: "active" | "passive", s: T.NPSelection | undefined): T.VerbSelection {
|
||||
return {
|
||||
...v,
|
||||
voice,
|
||||
object: voice === "active" ? s : "none",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,13 @@ import {
|
|||
isInvalidSubjObjCombo,
|
||||
} from "../../lib/phrase-building/vp-tools";
|
||||
import { switchSubjObj } from "../../lib/phrase-building/vp-tools";
|
||||
import { changeStatDyn, changeTransitivity, changeVoice } from "./verb-selection";
|
||||
import { changeStatDyn, changeTransitivity } from "./verb-selection";
|
||||
import { ensure2ndPersSubjPronounAndNoConflict } from "../../lib/phrase-building/vp-tools";
|
||||
import {
|
||||
isPerfectTense,
|
||||
isImperativeTense,
|
||||
} from "../../lib/type-predicates";
|
||||
import { checkForMiniPronounsError } from "../../lib/phrase-building/compile";
|
||||
|
||||
export type VpsReducerAction = {
|
||||
type: "load vps",
|
||||
|
@ -24,9 +25,6 @@ export type VpsReducerAction = {
|
|||
payload: T.NPSelection | undefined,
|
||||
} | {
|
||||
type: "swap subj/obj",
|
||||
} | {
|
||||
type: "shrink possesive",
|
||||
payload: number | undefined,
|
||||
} | {
|
||||
type: "set form",
|
||||
payload: T.FormVersion,
|
||||
|
@ -48,163 +46,189 @@ export type VpsReducerAction = {
|
|||
} | {
|
||||
type: "set tense category",
|
||||
payload: "basic" | "modal" | "perfect" | "imperative",
|
||||
}
|
||||
} | {
|
||||
type: "toggle servant shrink",
|
||||
};
|
||||
|
||||
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;
|
||||
export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, sendAlert?: (msg: string) => void): T.VPSelectionState {
|
||||
return ensureMiniPronounsOk(vps, doReduce());
|
||||
function ensureMiniPronounsOk(old: T.VPSelectionState, vps: T.VPSelectionState): T.VPSelectionState {
|
||||
const error = checkForMiniPronounsError(vps);
|
||||
if (error) {
|
||||
if (sendAlert) sendAlert(error);
|
||||
return old;
|
||||
}
|
||||
return {
|
||||
...vps,
|
||||
subject: action.payload.subject,
|
||||
};
|
||||
return vps;
|
||||
}
|
||||
if (action.type === "set object") {
|
||||
if (!vps.verb) return vps;
|
||||
if ((vps.verb.object === "none") || (typeof vps.verb.object === "number")) {
|
||||
return vps;
|
||||
function doReduce(): T.VPSelectionState {
|
||||
if (action.type === "load vps") {
|
||||
return action.payload;
|
||||
}
|
||||
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") {
|
||||
if (action.type === "set subject") {
|
||||
const { subject, skipPronounConflictCheck } = action.payload;
|
||||
if (
|
||||
!skipPronounConflictCheck
|
||||
&&
|
||||
hasPronounConflict(subject, vps.verb?.object)
|
||||
) {
|
||||
if (sendAlert) sendAlert("That combination of pronouns is not allowed");
|
||||
return vps;
|
||||
}
|
||||
if (voice === "passive" && (typeof vps.verb.object === "object")) {
|
||||
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)) {
|
||||
if (sendAlert) sendAlert("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 === "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") {
|
||||
return {
|
||||
...vps,
|
||||
subject: typeof vps.verb.object === "object" ? vps.verb.object : undefined,
|
||||
verb: {
|
||||
...vps.verb,
|
||||
object: "none",
|
||||
voice,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...vps,
|
||||
subject: undefined,
|
||||
verb: {
|
||||
...vps.verb,
|
||||
// TODO: is this ok??
|
||||
object: typeof vps.subject === "object" ? vps.subject : undefined,
|
||||
voice,
|
||||
},
|
||||
};
|
||||
}
|
||||
} 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,
|
||||
subject: vps.verb.object,
|
||||
verb: changeVoice(vps.verb, voice, vps.verb.object),
|
||||
verb: {
|
||||
...vps.verb,
|
||||
perfectTense: tense,
|
||||
tenseCategory: "perfect",
|
||||
},
|
||||
};
|
||||
} else if (isImperativeTense(tense)) {
|
||||
return {
|
||||
...vps,
|
||||
verb: {
|
||||
...vps.verb,
|
||||
imperativeTense: tense,
|
||||
tenseCategory: "imperative",
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...vps,
|
||||
verb: changeVoice(vps.verb, voice, voice === "active" ? vps.subject : undefined),
|
||||
verb: {
|
||||
...vps.verb,
|
||||
verbTense: tense,
|
||||
tenseCategory: vps.verb.tenseCategory === "perfect"
|
||||
? "basic"
|
||||
: vps.verb.tenseCategory,
|
||||
},
|
||||
};
|
||||
}
|
||||
} 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)) {
|
||||
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,
|
||||
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 (action.type === "toggle servant shrink") {
|
||||
return {
|
||||
...vps,
|
||||
verb: {
|
||||
...vps.verb,
|
||||
tenseCategory: category,
|
||||
form: {
|
||||
...vps.form,
|
||||
shrinkServant: !vps.form.shrinkServant,
|
||||
},
|
||||
};
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
function hasPronounConflict(subject: T.NPSelection | undefined, object: undefined | T.VerbObject): boolean {
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
import * as T from "../../types";
|
||||
import * as grammarUnits from "../grammar-units";
|
||||
import {
|
||||
removeDuplicates,
|
||||
} from "./vp-tools";
|
||||
import {
|
||||
combineSegments,
|
||||
makeSegment,
|
||||
putKidsInKidsSection,
|
||||
Segment,
|
||||
flattenLengths,
|
||||
} from "./segment";
|
||||
import { removeAccents } from "../accent-helpers";
|
||||
import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools";
|
||||
import {
|
||||
orderKidsSection,
|
||||
findPossesiveToShrinkInEP,
|
||||
shrinkNP,
|
||||
} from "./compile-tools";
|
||||
|
||||
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,
|
||||
kids,
|
||||
equative,
|
||||
negative: EP.equative.negative,
|
||||
});
|
||||
return {
|
||||
ps: combineLengths ? flattenLengths(psResult) : psResult,
|
||||
e: compileEnglish(EP),
|
||||
};
|
||||
}
|
||||
|
||||
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));
|
||||
return {
|
||||
kids: orderKidsSection([
|
||||
...EP.equative.hasBa
|
||||
? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])]
|
||||
: [],
|
||||
...(possToShrink && shrunkenPossAllowed)
|
||||
? [shrinkNP(possToShrink.np)]
|
||||
: [],
|
||||
]),
|
||||
NPs: [
|
||||
...EP.omitSubject ? [] : [subject],
|
||||
predicate
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function compilePs({ NPs, kids, equative, negative }: {
|
||||
NPs: Segment[],
|
||||
kids: Segment[],
|
||||
equative: T.SingleOrLengthOpts<T.PsString[]>,
|
||||
negative: boolean,
|
||||
}): T.SingleOrLengthOpts<T.PsString[]> {
|
||||
if ("long" in equative) {
|
||||
return {
|
||||
long: compilePs({ NPs, kids, equative: equative.long, negative }) as T.PsString[],
|
||||
short: compilePs({ NPs, kids, equative: equative.short, negative }) as T.PsString[],
|
||||
};
|
||||
}
|
||||
const allSegments = putKidsInKidsSection([
|
||||
...NPs,
|
||||
...negative ? [
|
||||
makeSegment({ p: "نه", f: "nú" }),
|
||||
makeSegment(removeAccents(equative))
|
||||
] : [
|
||||
makeSegment(equative),
|
||||
],
|
||||
], kids);
|
||||
return removeDuplicates(combineSegments(allSegments, "spaces"));
|
||||
}
|
||||
|
||||
function compileEnglish(EP: T.EPRendered): string[] | undefined {
|
||||
function insertEWords(e: string, { subject, predicate }: { subject: string, predicate: string }): string {
|
||||
return e.replace("$SUBJ", subject).replace("$PRED", predicate || "");
|
||||
}
|
||||
const engSubj = getEnglishFromRendered(EP.subject);
|
||||
const engPred = getEnglishFromRendered(EP.predicate);
|
||||
// require all English parts for making the English phrase
|
||||
return (EP.englishBase && engSubj && engPred)
|
||||
? EP.englishBase.map(e => insertEWords(e, {
|
||||
subject: engSubj,
|
||||
predicate: engPred,
|
||||
}))
|
||||
: undefined;
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
import * as T from "../../types";
|
||||
import {
|
||||
Segment,
|
||||
makeSegment,
|
||||
} from "./segment";
|
||||
import { getVerbBlockPosFromPerson } from "../misc-helpers";
|
||||
import { pronouns } from "../grammar-units";
|
||||
|
||||
export function orderKidsSection(kids: Segment[]): Segment[] {
|
||||
const sorted = [...kids];
|
||||
return sorted.sort((a, b) => {
|
||||
// ba first
|
||||
if (a.isBa) return -1;
|
||||
// kinds lined up 1st 2nd 3rd person
|
||||
if (a.isMiniPronoun && b.isMiniPronoun) {
|
||||
if (a.isMiniPronoun < b.isMiniPronoun) {
|
||||
return -1;
|
||||
}
|
||||
if (a.isMiniPronoun > b.isMiniPronoun) {
|
||||
return 1;
|
||||
}
|
||||
// TODO: is this enough?
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
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, 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 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, uid)
|
||||
||
|
||||
findPossesiveInNP(obj, uid)
|
||||
);
|
||||
}
|
||||
|
||||
export function shrinkNP(np: T.Rendered<T.NPSelection>): Segment {
|
||||
function getFirstSecThird(): 1 | 2 | 3 {
|
||||
if ([0, 1, 6, 7].includes(np.person)) return 1;
|
||||
if ([2, 3, 8, 9].includes(np.person)) return 2;
|
||||
return 3;
|
||||
}
|
||||
const [row, col] = getVerbBlockPosFromPerson(np.person);
|
||||
return makeSegment(pronouns.mini[row][col], ["isKid", getFirstSecThird()]);
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import * as T from "../../types";
|
||||
import {
|
||||
concatPsString, psStringEquals,
|
||||
concatPsString,
|
||||
} from "../p-text-helpers";
|
||||
import {
|
||||
Segment,
|
||||
|
@ -20,18 +20,18 @@ import {
|
|||
} from "./vp-tools";
|
||||
import { isImperativeTense, isModalTense, isPerfectTense } from "../type-predicates";
|
||||
import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools";
|
||||
import {
|
||||
orderKidsSection,
|
||||
findPossesiveToShrinkInVP,
|
||||
shrinkNP,
|
||||
} from "./compile-tools";
|
||||
import { getVerbBlockPosFromPerson } from "../misc-helpers";
|
||||
import { pronouns } from "../grammar-units";
|
||||
import { completeEPSelection, renderEP } from "./render-ep";
|
||||
import { completeVPSelection } from "./vp-tools";
|
||||
import { renderVP } from "./render-vp";
|
||||
|
||||
type Form = T.FormVersion & { OSV?: boolean };
|
||||
export function compileVP(VP: T.VPRendered, form: Form): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string [] };
|
||||
export function compileVP(VP: T.VPRendered, form: Form, combineLengths: true): { ps: T.PsString[], e?: string [] };
|
||||
export function compileVP(VP: T.VPRendered, form: Form, combineLengths?: true): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string [] } {
|
||||
const verb = VP.verb.ps;
|
||||
const { kids, NPs } = getSegmentsAndKids(VP, form);
|
||||
const { kids, NPs } = getVPSegmentsAndKids(VP, form);
|
||||
const psResult = compilePs({
|
||||
NPs,
|
||||
kids,
|
||||
|
@ -82,9 +82,8 @@ function compilePs({ NPs, kids, verb: { head, rest }, VP }: CompilePsInput): T.S
|
|||
)));
|
||||
}
|
||||
|
||||
function getSegmentsAndKids(VP: T.VPRendered, form: Form): { kids: Segment[], NPs: Segment[][] } {
|
||||
const removeKing = form.removeKing && !(VP.isCompound === "dynamic" && VP.isPast);
|
||||
const shrinkServant = form.shrinkServant && !(VP.isCompound === "dynamic" && !VP.isPast);
|
||||
export function getShrunkenServant(VP: T.VPRendered): Segment | undefined {
|
||||
const shrinkServant = VP.form.shrinkServant && !(VP.isCompound === "dynamic" && !VP.isPast);
|
||||
const toShrinkServant = (() => {
|
||||
if (!shrinkServant) return undefined;
|
||||
if (!VP.servant) return undefined;
|
||||
|
@ -92,27 +91,23 @@ function getSegmentsAndKids(VP: T.VPRendered, form: Form): { kids: Segment[], NP
|
|||
if (typeof servant !== "object") return undefined;
|
||||
return servant;
|
||||
})();
|
||||
const shrunkenServant = toShrinkServant ? shrinkNP(toShrinkServant) : undefined;
|
||||
const possToShrink = findPossesiveToShrinkInVP(VP);
|
||||
const shrunkenPossesive = possToShrink ? shrinkNP(possToShrink) : undefined;
|
||||
const shrunkenPossAllowed = possToShrink && shrunkenPossesive && (
|
||||
!shrunkenServant || !psStringEquals(shrunkenPossesive.ps[0], shrunkenServant.ps[0])
|
||||
) && (
|
||||
// can only shrink the possesive if the parent of the possesive is still in full form
|
||||
!(possToShrink.role === "king" && removeKing)
|
||||
&&
|
||||
!(possToShrink.role === "servant" && shrinkServant)
|
||||
);
|
||||
const shrinkPossUid = shrunkenPossAllowed
|
||||
? VP.shrunkenPossesive
|
||||
: undefined;
|
||||
return toShrinkServant ? shrinkNP(toShrinkServant) : undefined;
|
||||
}
|
||||
|
||||
export function getVPSegmentsAndKids(VP: T.VPRendered, form?: Form): { kids: Segment[], NPs: Segment[][] } {
|
||||
const removeKing = VP.form.removeKing && !(VP.isCompound === "dynamic" && VP.isPast);
|
||||
const shrunkenServant = getShrunkenServant(VP);
|
||||
const possToShrink = findPossesivesToShrinkInVP(VP, {
|
||||
shrunkServant: !!shrunkenServant,
|
||||
removedKing: removeKing,
|
||||
});
|
||||
const SO = {
|
||||
subject: getPashtoFromRendered(VP.subject, shrinkPossUid, false),
|
||||
object: typeof VP.object === "object" ? getPashtoFromRendered(VP.object, shrinkPossUid, VP.subject.person) : undefined,
|
||||
subject: getPashtoFromRendered(VP.subject, false),
|
||||
object: typeof VP.object === "object" ? getPashtoFromRendered(VP.object, VP.subject.person) : undefined,
|
||||
};
|
||||
function getSegment(t: "subject" | "object"): Segment | undefined {
|
||||
const word = (VP.servant === t)
|
||||
? (!shrinkServant ? SO[t] : undefined)
|
||||
? (!shrunkenServant ? SO[t] : undefined)
|
||||
: (VP.king === t)
|
||||
? (!removeKing ? SO[t] : undefined)
|
||||
: undefined;
|
||||
|
@ -128,8 +123,7 @@ function getSegmentsAndKids(VP: T.VPRendered, form: Form): { kids: Segment[], NP
|
|||
? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])] : [],
|
||||
...shrunkenServant
|
||||
? [shrunkenServant] : [],
|
||||
...(shrunkenPossesive && shrunkenPossAllowed)
|
||||
? [shrunkenPossesive] : [],
|
||||
...possToShrink.map(shrinkNP),
|
||||
]),
|
||||
NPs: [
|
||||
[
|
||||
|
@ -138,7 +132,7 @@ function getSegmentsAndKids(VP: T.VPRendered, form: Form): { kids: Segment[], NP
|
|||
],
|
||||
// TODO: make this an option to also include O S V order ??
|
||||
// also show O S V if both are showing
|
||||
...(subject && object && form.OSV) ? [[object, subject]] : [],
|
||||
...(subject && object && (form && form.OSV)) ? [[object, subject]] : [],
|
||||
],
|
||||
};
|
||||
}
|
||||
|
@ -255,6 +249,82 @@ function arrangeVerbWNegative(head: T.PsString | undefined, restRaw: T.PsString[
|
|||
];
|
||||
}
|
||||
|
||||
|
||||
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 } = getEPSegmentsAndKids(EP);
|
||||
const equative = EP.equative.ps;
|
||||
const psResult = compileEPPs({
|
||||
NPs,
|
||||
kids,
|
||||
equative,
|
||||
negative: EP.equative.negative,
|
||||
});
|
||||
return {
|
||||
ps: combineLengths ? flattenLengths(psResult) : psResult,
|
||||
e: compileEPEnglish(EP),
|
||||
};
|
||||
}
|
||||
|
||||
export function getEPSegmentsAndKids(EP: T.EPRendered): { kids: Segment[], NPs: Segment[] } {
|
||||
const possToShrink = findPossesivesToShrinkInEP(EP);
|
||||
const subject = makeSegment(getPashtoFromRendered(EP.subject, false));
|
||||
const predicate = makeSegment(getPashtoFromRendered(EP.predicate, false));
|
||||
return {
|
||||
kids: orderKidsSection([
|
||||
...EP.equative.hasBa
|
||||
? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])]
|
||||
: [],
|
||||
...possToShrink.map(a => shrinkNP(a.np)),
|
||||
]),
|
||||
NPs: [
|
||||
...EP.omitSubject ? [] : [subject],
|
||||
predicate
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function compileEPPs({ NPs, kids, equative, negative }: {
|
||||
NPs: Segment[],
|
||||
kids: Segment[],
|
||||
equative: T.SingleOrLengthOpts<T.PsString[]>,
|
||||
negative: boolean,
|
||||
}): T.SingleOrLengthOpts<T.PsString[]> {
|
||||
if ("long" in equative) {
|
||||
return {
|
||||
long: compileEPPs({ NPs, kids, equative: equative.long, negative }) as T.PsString[],
|
||||
short: compileEPPs({ NPs, kids, equative: equative.short, negative }) as T.PsString[],
|
||||
};
|
||||
}
|
||||
const allSegments = putKidsInKidsSection([
|
||||
...NPs,
|
||||
...negative ? [
|
||||
makeSegment({ p: "نه", f: "nú" }),
|
||||
makeSegment(removeAccents(equative))
|
||||
] : [
|
||||
makeSegment(equative),
|
||||
],
|
||||
], kids);
|
||||
return removeDuplicates(combineSegments(allSegments, "spaces"));
|
||||
}
|
||||
|
||||
function compileEPEnglish(EP: T.EPRendered): string[] | undefined {
|
||||
function insertEWords(e: string, { subject, predicate }: { subject: string, predicate: string }): string {
|
||||
return e.replace("$SUBJ", subject).replace("$PRED", predicate || "");
|
||||
}
|
||||
const engSubj = getEnglishFromRendered(EP.subject);
|
||||
const engPred = getEnglishFromRendered(EP.predicate);
|
||||
// require all English parts for making the English phrase
|
||||
return (EP.englishBase && engSubj && engPred)
|
||||
? EP.englishBase.map(e => insertEWords(e, {
|
||||
subject: engSubj,
|
||||
predicate: engPred,
|
||||
}))
|
||||
: undefined;
|
||||
}
|
||||
|
||||
|
||||
function mergeSegments(s1: Segment, s2: Segment, noSpace?: "no space"): Segment {
|
||||
if (noSpace) {
|
||||
return s2.adjust({ ps: (p) => concatPsString(s1.ps[0], p) });
|
||||
|
@ -309,3 +379,126 @@ function compileEnglish(VP: T.VPRendered): string[] | undefined {
|
|||
}))
|
||||
: undefined;
|
||||
}
|
||||
|
||||
|
||||
export function orderKidsSection(kids: Segment[]): Segment[] {
|
||||
const sorted = [...kids];
|
||||
return sorted.sort((a, b) => {
|
||||
// ba first
|
||||
if (a.isBa) return -1;
|
||||
// kinds lined up 1st 2nd 3rd person
|
||||
if (a.isMiniPronoun && b.isMiniPronoun) {
|
||||
if (a.isMiniPronoun < b.isMiniPronoun) {
|
||||
return -1;
|
||||
}
|
||||
if (a.isMiniPronoun > b.isMiniPronoun) {
|
||||
return 1;
|
||||
}
|
||||
// TODO: is this enough?
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
export function checkForMiniPronounsError(s: T.EPSelectionState | T.VPSelectionState): undefined | string {
|
||||
function findDuplicateMiniPronoun(mp: Segment[]): Segment | undefined {
|
||||
const duplicates = mp.filter((item, index) => (
|
||||
mp.findIndex(m => item.ps[0].p === m.ps[0].p) !== index
|
||||
));
|
||||
if (duplicates.length === 0) return undefined;
|
||||
return duplicates[0];
|
||||
}
|
||||
const kids = (() => {
|
||||
if ("predicate" in s) {
|
||||
const EPS = completeEPSelection(s);
|
||||
if (!EPS) return undefined;
|
||||
const { kids } = getEPSegmentsAndKids(renderEP(EPS));
|
||||
return kids;
|
||||
}
|
||||
const VPS = completeVPSelection(s);
|
||||
if (!VPS) return undefined;
|
||||
const { kids } = getVPSegmentsAndKids(renderVP(VPS));
|
||||
return kids;
|
||||
})();
|
||||
if (!kids) return undefined;
|
||||
const miniPronouns = kids.filter(x => x.isMiniPronoun);
|
||||
if (miniPronouns.length > 2) {
|
||||
return "can't add another mini-pronoun, there are alread two";
|
||||
}
|
||||
const duplicateMiniPronoun = findDuplicateMiniPronoun(miniPronouns);
|
||||
if (duplicateMiniPronoun) {
|
||||
return `there's already a ${duplicateMiniPronoun.ps[0].p} - ${duplicateMiniPronoun.ps[0].f} mini-pronoun in use, can't have two of those`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function findPossesivesToShrinkInVP(VP: T.VPRendered, f: {
|
||||
shrunkServant: boolean,
|
||||
removedKing: boolean,
|
||||
}): T.Rendered<T.NPSelection>[] {
|
||||
return findPossesives(VP.subject, VP.object).filter(x => (
|
||||
// only give the possesive to shrink if it's not alread in a shrunken servant
|
||||
!(f.shrunkServant && x.role === "servant")
|
||||
&& // or in a removed king
|
||||
!(f.removedKing && x.role === "king")
|
||||
));
|
||||
}
|
||||
|
||||
function findPossesives(...nps: (T.Rendered<T.NPSelection> | T.ObjectNP | undefined)[]): T.Rendered<T.NPSelection>[] {
|
||||
return nps.reduce((accum, curr) => {
|
||||
const res = findPossesiveInNP(curr);
|
||||
if (res) return [...accum, res];
|
||||
return accum;
|
||||
}, [] as T.Rendered<T.NPSelection>[]);
|
||||
}
|
||||
|
||||
function findPossesiveInNP(NP: T.Rendered<T.NPSelection> | T.ObjectNP | undefined): T.Rendered<T.NPSelection> | undefined {
|
||||
if (NP === undefined) return undefined;
|
||||
if (typeof NP !== "object") return undefined;
|
||||
if (!NP.possesor) return undefined;
|
||||
if (NP.possesor.shrunken) {
|
||||
return NP.possesor.np;
|
||||
}
|
||||
return findPossesiveInNP(NP.possesor.np);
|
||||
}
|
||||
|
||||
type FoundNP = {
|
||||
np: T.Rendered<T.NPSelection>,
|
||||
from: "subject" | "predicate",
|
||||
};
|
||||
export function findPossesivesToShrinkInEP(EP: T.EPRendered): FoundNP[] {
|
||||
const inSubject = findPossesiveInNP(EP.subject);
|
||||
const inPredicate = (EP.predicate.type === "adjective" || EP.predicate.type === "loc. adv.")
|
||||
? undefined
|
||||
: findPossesiveInNP(
|
||||
// @ts-ignore - ts being dumb
|
||||
EP.predicate as T.NPSelection
|
||||
);
|
||||
return [
|
||||
...inSubject ? [{ np: inSubject, from: "subject"} as FoundNP] : [],
|
||||
...inPredicate ? [{ np: inPredicate, from: "predicate" } as FoundNP] : [],
|
||||
].filter(found => !(found.from === "subject" && EP.omitSubject));
|
||||
}
|
||||
|
||||
export function findPossesiveToShrinkInVP(VP: T.VPRendered): T.Rendered<T.NPSelection> | undefined {
|
||||
const obj: T.Rendered<T.NPSelection> | undefined = ("object" in VP && typeof VP.object === "object")
|
||||
? VP.object
|
||||
: undefined;
|
||||
return (
|
||||
findPossesiveInNP(VP.subject)
|
||||
||
|
||||
findPossesiveInNP(obj)
|
||||
);
|
||||
}
|
||||
|
||||
export function shrinkNP(np: T.Rendered<T.NPSelection>): Segment {
|
||||
function getFirstSecThird(): 1 | 2 | 3 {
|
||||
if ([0, 1, 6, 7].includes(np.person)) return 1;
|
||||
if ([2, 3, 8, 9].includes(np.person)) return 2;
|
||||
return 3;
|
||||
}
|
||||
const [row, col] = getVerbBlockPosFromPerson(np.person);
|
||||
return makeSegment(pronouns.mini[row][col], ["isKid", getFirstSecThird()]);
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { renderVP } from "./render-vp";
|
||||
import { compileVP } from "./compile-vp";
|
||||
|
||||
export {
|
||||
renderVP,
|
||||
compileVP,
|
||||
};
|
|
@ -22,14 +22,39 @@ function getBaseAndAdjectives(np: T.Rendered<T.NPSelection | T.EqCompSelection>)
|
|||
));
|
||||
}
|
||||
|
||||
export function getPashtoFromRendered(np: T.Rendered<T.NPSelection | T.EqCompSelection>, shrunkenPossesive: number | undefined, subjectsPerson: false | T.Person): T.PsString[] {
|
||||
const base = getBaseAndAdjectives(np);
|
||||
if (!np.possesor || np.possesor.uid === shrunkenPossesive) {
|
||||
return base;
|
||||
function trimOffShrunkenPossesive(p: T.Rendered<T.NPSelection>): T.Rendered<T.NPSelection> {
|
||||
if (!("possesor" in p)) {
|
||||
return p;
|
||||
}
|
||||
if (!p.possesor) {
|
||||
return p;
|
||||
}
|
||||
if (p.possesor.shrunken) {
|
||||
return {
|
||||
...p,
|
||||
possesor: undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
...p,
|
||||
possesor: {
|
||||
...p.possesor,
|
||||
np: trimOffShrunkenPossesive(p.possesor.np),
|
||||
}
|
||||
}
|
||||
return addPossesor(np.possesor.np, base, subjectsPerson);
|
||||
}
|
||||
|
||||
export function getPashtoFromRendered(np: T.Rendered<T.NPSelection> | T.Rendered<T.EqCompSelection>, subjectsPerson: false | T.Person): T.PsString[] {
|
||||
const base = getBaseAndAdjectives(np);
|
||||
if (np.type !== "loc. adv." && np.type !== "adjective") {
|
||||
// ts being dumb
|
||||
const trimmed = trimOffShrunkenPossesive(np as T.Rendered<T.NPSelection>);
|
||||
if (trimmed.possesor) {
|
||||
return addPossesor(trimmed.possesor.np, base, subjectsPerson);
|
||||
}
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
function addPossesor(owner: T.Rendered<T.NPSelection>, existing: T.PsString[], subjectsPerson: false | T.Person): T.PsString[] {
|
||||
function willBeReflexive(subj: T.Person, obj: T.Person): boolean {
|
||||
|
|
|
@ -32,7 +32,6 @@ export function renderEP(EP: T.EPSelectionComplete): T.EPRendered {
|
|||
: renderEqCompSelection(EP.predicate.selection, kingPerson),
|
||||
equative: renderEquative(EP.equative, kingPerson),
|
||||
englishBase: equativeBuilders[EP.equative.tense](kingPerson, EP.equative.negative),
|
||||
shrunkenPossesive: EP.shrunkenPossesive,
|
||||
omitSubject: EP.omitSubject,
|
||||
};
|
||||
}
|
||||
|
@ -150,3 +149,32 @@ function getEnglishConj(p: T.Person, e: string | T.EnglishBlock): string {
|
|||
// return inflections[gender][plural ? 1 : 0];
|
||||
// }
|
||||
|
||||
|
||||
export function completeEPSelection(eps: T.EPSelectionState): T.EPSelectionComplete | undefined {
|
||||
if (!eps.subject) {
|
||||
return undefined;
|
||||
}
|
||||
if (eps.predicate.type === "Complement") {
|
||||
const selection = eps.predicate.Complement;
|
||||
if (!selection) return undefined;
|
||||
return {
|
||||
...eps,
|
||||
subject: eps.subject,
|
||||
predicate: {
|
||||
type: "Complement",
|
||||
selection,
|
||||
},
|
||||
};
|
||||
}
|
||||
// predicate is NP
|
||||
const selection = eps.predicate.NP;
|
||||
if (!selection) return undefined;
|
||||
return {
|
||||
...eps,
|
||||
subject: eps.subject,
|
||||
predicate: {
|
||||
type: "NP",
|
||||
selection,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
import { parseEc } from "../misc-helpers";
|
||||
import { getEnglishWord } from "../get-english-word";
|
||||
import { renderAdjectiveSelection } from "./render-adj";
|
||||
import { isPattern5Entry, isUnisexNounEntry } from "../type-predicates";
|
||||
import { isPattern5Entry, isUnisexAnimNounEntry } from "../type-predicates";
|
||||
|
||||
export function renderNPSelection(NP: T.NPSelection, inflected: boolean, inflectEnglish: boolean, role: "subject", soRole: "servant" | "king" | "none"): T.Rendered<T.NPSelection>;
|
||||
export function renderNPSelection(NP: T.NPSelection | T.ObjectNP, inflected: boolean, inflectEnglish: boolean, role: "object", soRole: "servant" | "king" | "none"): T.Rendered<T.NPSelection> | T.Person.ThirdPlurMale | "none";
|
||||
|
@ -87,13 +87,18 @@ function renderParticipleSelection(p: T.ParticipleSelection, inflected: boolean,
|
|||
};
|
||||
}
|
||||
|
||||
function renderPossesor(possesor: { np: T.NPSelection, uid: number } | undefined, possesorRole: "servant" | "king" | "none"): { np: T.Rendered<T.NPSelection>, uid: number } | undefined {
|
||||
function renderPossesor(possesor: T.PossesorSelection | undefined, possesorRole: "servant" | "king" | "none"): T.RenderedPossesorSelection | undefined {
|
||||
if (!possesor) return undefined;
|
||||
const isSingUnisexAnim5PatternNoun = (possesor.np.type === "noun"
|
||||
&& possesor.np.number === "singular"
|
||||
&& isUnisexAnimNounEntry(possesor.np.entry)
|
||||
&& isPattern5Entry(possesor.np.entry)
|
||||
);
|
||||
return {
|
||||
uid: possesor.uid,
|
||||
shrunken: possesor.shrunken,
|
||||
np: renderNPSelection(
|
||||
possesor.np,
|
||||
!(possesor.np.type === "noun" && isUnisexNounEntry(possesor.np.entry) && isPattern5Entry(possesor.np.entry)),
|
||||
!isSingUnisexAnim5PatternNoun,
|
||||
false,
|
||||
"subject",
|
||||
possesorRole,
|
||||
|
|
|
@ -47,7 +47,6 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
|
|||
type: "VPRendered",
|
||||
king,
|
||||
servant,
|
||||
shrunkenPossesive: VP.shrunkenPossesive,
|
||||
isPast,
|
||||
isTransitive,
|
||||
isCompound: VP.verb.isCompound,
|
||||
|
@ -60,10 +59,25 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
|
|||
vs: VP.verb,
|
||||
}),
|
||||
form: VP.form,
|
||||
whatsAdjustable: whatsAdjustable(VP),
|
||||
};
|
||||
return b;
|
||||
}
|
||||
|
||||
function whatsAdjustable(VP: T.VPSelectionComplete): "both" | "king" | "servant" {
|
||||
// TODO: intransitive dynamic compounds?
|
||||
return (VP.verb.isCompound === "dynamic" && VP.verb.transitivity === "transitive")
|
||||
? (isPastTense(VP.verb.tense) ? "servant" : "king")
|
||||
: VP.verb.transitivity === "transitive"
|
||||
? (VP.verb.voice === "active" ? "both" : "king")
|
||||
: VP.verb.transitivity === "intransitive"
|
||||
? "king"
|
||||
// grammTrans
|
||||
: isPastTense(VP.verb.tense)
|
||||
? "servant"
|
||||
: "king";
|
||||
}
|
||||
|
||||
function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, objectPerson: T.Person | undefined): T.VerbRendered {
|
||||
const v = vs.dynAuxVerb || vs.verb;
|
||||
const conjugations = conjugateVerb(v.entry, v.complement);
|
||||
|
|
|
@ -49,6 +49,10 @@ export function isUnisexNounEntry(e: T.NounEntry | T.AdjectiveEntry): e is T.Uni
|
|||
return isNounEntry(e) && e.c.includes("unisex");
|
||||
}
|
||||
|
||||
export function isUnisexAnimNounEntry(e: T.NounEntry | T.AdjectiveEntry): e is T.UnisexAnimNounEntry {
|
||||
return isUnisexNounEntry(e) && e.c.includes("anim.");
|
||||
}
|
||||
|
||||
export function isAdjOrUnisexNounEntry(e: T.Entry): e is (T.AdjectiveEntry | T.UnisexNounEntry) {
|
||||
return isAdjectiveEntry(e) || (
|
||||
isNounEntry(e) && isUnisexNounEntry(e)
|
||||
|
@ -136,9 +140,7 @@ export function isPattern5Entry<T extends (T.NounEntry | T.AdjectiveEntry)>(e: T
|
|||
return (
|
||||
!!(e.infap && e.infaf && e.infbp && e.infbf)
|
||||
&&
|
||||
(e.infaf.slice(-1) === "u")
|
||||
&&
|
||||
!e.infap.slice(1).includes("ا")
|
||||
e.infap.includes("ا")
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -46,15 +46,16 @@ export default function useStickyState<T extends SaveableData>(defaultValue: T |
|
|||
}
|
||||
|
||||
export function useStickyReducer<T extends SaveableData, A>(
|
||||
reducer: (state: T, dispatch: A) => T,
|
||||
reducer: (state: T, dispatch: A, sendAlert?: (msg: string) => void) => T,
|
||||
defaultValue: T | ((old: T | undefined) => T),
|
||||
key: string,
|
||||
): [T, (action: A) => void] {
|
||||
sendAlert?: (msg: string) => void,
|
||||
): [T, (action: A) => void, ((msg: string) => void) | undefined] {
|
||||
const [state, unsafeSetState] = useStickyState<T>(defaultValue, key);
|
||||
function adjustState(action: A) {
|
||||
unsafeSetState(oldState => {
|
||||
return reducer(oldState, action);
|
||||
return reducer(oldState, action, sendAlert);
|
||||
});
|
||||
}
|
||||
return [state, adjustState];
|
||||
return [state, adjustState, sendAlert];
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
18
src/types.ts
18
src/types.ts
|
@ -473,6 +473,7 @@ export type NounEntry = DictionaryEntry & { c: string } & { __brand: "a noun ent
|
|||
export type MascNounEntry = NounEntry & { __brand2: "a masc noun entry" };
|
||||
export type FemNounEntry = NounEntry & { __brand2: "a fem noun entry" };
|
||||
export type UnisexNounEntry = MascNounEntry & { __brand3: "a unisex noun entry" };
|
||||
export type UnisexAnimNounEntry = UnisexNounEntry & { __brand4: "an anim unisex noun entry" };
|
||||
export type AdverbEntry = DictionaryEntry & { c: string } & { __brand: "an adverb entry" };
|
||||
export type LocativeAdverbEntry = AdverbEntry & { __brand2: "a locative adverb entry" };
|
||||
export type AdjectiveEntry = DictionaryEntry & { c: string } & { __brand: "an adjective entry" };
|
||||
|
@ -501,7 +502,6 @@ export type VPRendered = {
|
|||
type: "VPRendered",
|
||||
king: "subject" | "object",
|
||||
servant: "subject" | "object" | undefined,
|
||||
shrunkenPossesive: number | undefined,
|
||||
isPast: boolean,
|
||||
isTransitive: boolean,
|
||||
isCompound: "stative" | "dynamic" | false,
|
||||
|
@ -510,6 +510,7 @@ export type VPRendered = {
|
|||
verb: VerbRendered,
|
||||
englishBase?: string[],
|
||||
form: FormVersion,
|
||||
whatsAdjustable: "both" | "king" | "servant",
|
||||
}
|
||||
|
||||
export type VerbTense = "presentVerb"
|
||||
|
@ -531,14 +532,12 @@ export type Tense = EquativeTense | VerbTense | PerfectTense | ModalTense | Impe
|
|||
export type VPSelectionState = {
|
||||
subject: NPSelection | undefined,
|
||||
verb: VerbSelection,
|
||||
shrunkenPossesive: undefined | number,
|
||||
form: FormVersion,
|
||||
};
|
||||
|
||||
export type VPSelectionComplete = {
|
||||
subject: NPSelection,
|
||||
verb: VerbSelectionComplete,
|
||||
shrunkenPossesive: undefined | number,
|
||||
form: FormVersion,
|
||||
};
|
||||
|
||||
|
@ -592,7 +591,7 @@ export type NPType = "noun" | "pronoun" | "participle";
|
|||
export type ObjectNP = "none" | Person.ThirdPlurMale;
|
||||
|
||||
export type PossesorSelection = {
|
||||
uid: number,
|
||||
shrunken: boolean,
|
||||
np: NPSelection,
|
||||
}
|
||||
|
||||
|
@ -639,6 +638,13 @@ export type ReplaceKey<T, K extends string, R> = T extends Record<K, unknown> ?
|
|||
|
||||
export type FormVersion = { removeKing: boolean, shrinkServant: boolean };
|
||||
|
||||
// TODO: rendered should would for rendering T.PossesorSelection etc
|
||||
// look recursively down on something
|
||||
export type RenderedPossesorSelection = {
|
||||
np: Rendered<NPSelection>,
|
||||
shrunken: boolean,
|
||||
};
|
||||
|
||||
export type Rendered<T extends NPSelection | EqCompSelection | AdjectiveSelection> = ReplaceKey<
|
||||
Omit<T, "changeGender" | "changeNumber" | "changeDistance" | "adjectives" | "possesor">,
|
||||
"e",
|
||||
|
@ -652,7 +658,7 @@ export type Rendered<T extends NPSelection | EqCompSelection | AdjectiveSelectio
|
|||
// TODO: better recursive thing
|
||||
adjectives?: Rendered<AdjectiveSelection>[],
|
||||
possesor?: {
|
||||
uid: number,
|
||||
shrunken: boolean,
|
||||
np: Rendered<NPSelection>,
|
||||
},
|
||||
};
|
||||
|
@ -666,7 +672,6 @@ export type EPSelectionState = {
|
|||
Complement: EqCompSelection | undefined,
|
||||
},
|
||||
equative: EquativeSelection,
|
||||
shrunkenPossesive: undefined | number,
|
||||
omitSubject: boolean,
|
||||
};
|
||||
|
||||
|
@ -703,7 +708,6 @@ export type EPRendered = {
|
|||
predicate: Rendered<NPSelection> | Rendered<EqCompSelection>,
|
||||
equative: EquativeRendered,
|
||||
englishBase?: string[],
|
||||
shrunkenPossesive: undefined | number,
|
||||
omitSubject: boolean,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue