PROPER SHRINKNIG IN PHRASE BUILDERS!

This commit is contained in:
lingdocs 2022-05-05 13:42:52 -05:00
parent 240fd11ed8
commit 9974eba176
24 changed files with 743 additions and 632 deletions

View File

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

View File

@ -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>
);
}

View File

@ -1,6 +1,6 @@
import * as T from "../../types"; import * as T from "../../types";
import { renderEP } from "../../lib/phrase-building/render-ep"; import { completeEPSelection, renderEP } from "../../lib/phrase-building/render-ep";
import { compileEP } from "../../lib/phrase-building/compile-ep"; import { compileEP } from "../../lib/phrase-building/compile";
import Examples from "../Examples"; import Examples from "../Examples";
import ButtonSelect from "../ButtonSelect"; import ButtonSelect from "../ButtonSelect";
@ -61,33 +61,4 @@ function VariationLayer({ vs, opts }: { vs: T.PsString[], opts: T.TextOptions })
</div>; </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; export default EPDisplay;

View File

@ -8,6 +8,8 @@ import EqCompPicker from "./eq-comp-picker/EqCompPicker";
import { roleIcon } from "../vp-explorer/VPExplorerExplanationModal"; import { roleIcon } from "../vp-explorer/VPExplorerExplanationModal";
import EqChartsDisplay from "./EqChartsDisplay"; import EqChartsDisplay from "./EqChartsDisplay";
import epsReducer from "./eps-reducer"; import epsReducer from "./eps-reducer";
import { useState } from "react";
import { completeEPSelection } from "../../lib/phrase-building/render-ep";
const blankEps: T.EPSelectionState = { const blankEps: T.EPSelectionState = {
subject: undefined, subject: undefined,
predicate: { predicate: {
@ -19,7 +21,6 @@ const blankEps: T.EPSelectionState = {
tense: "present", tense: "present",
negative: false, negative: false,
}, },
shrunkenPossesive: undefined,
omitSubject: false, omitSubject: false,
}; };
@ -30,12 +31,20 @@ function EPExplorer(props: {
entryFeeder: T.EntryFeeder, entryFeeder: T.EntryFeeder,
}) { }) {
const [mode, setMode] = useStickyState<"charts" | "phrases">("charts", "EPExplorerMode"); 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" const king = eps.subject?.type === "pronoun"
? "subject" ? "subject"
: eps.predicate.type === "Complement" : eps.predicate.type === "Complement"
? "subject" ? "subject"
: "predicate"; : "predicate";
function flashMessage(msg: string) {
setAlert(msg);
setTimeout(() => {
setAlert(undefined);
}, 1500);
}
const phraseIsComplete = !!completeEPSelection(eps);
return <div> return <div>
<div className="mt-2 mb-3 text-center"> <div className="mt-2 mb-3 text-center">
<ButtonSelect <ButtonSelect
@ -51,8 +60,7 @@ function EPExplorer(props: {
{mode === "phrases" && <> {mode === "phrases" && <>
<div className="my-2"> <div className="my-2">
<NPPicker <NPPicker
shrunkenPossesiveInPhrase={eps.shrunkenPossesive} phraseIsComplete={phraseIsComplete}
handleShrinkPossesive={payload => adjustEps({ type: "shrink possesive", payload })}
heading={<div className="h5 text-center">Subject {king === "subject" ? roleIcon.king : ""}</div>} heading={<div className="h5 text-center">Subject {king === "subject" ? roleIcon.king : ""}</div>}
entryFeeder={props.entryFeeder} entryFeeder={props.entryFeeder}
np={eps.subject} np={eps.subject}
@ -76,8 +84,7 @@ function EPExplorer(props: {
/> />
</div> </div>
{eps.predicate.type === "NP" ? <NPPicker {eps.predicate.type === "NP" ? <NPPicker
shrunkenPossesiveInPhrase={eps.shrunkenPossesive} phraseIsComplete={phraseIsComplete}
handleShrinkPossesive={payload => adjustEps({ type: "shrink possesive", payload })}
entryFeeder={props.entryFeeder} entryFeeder={props.entryFeeder}
np={eps.predicate.type === "NP" ? eps.predicate.NP : undefined} np={eps.predicate.type === "NP" ? eps.predicate.NP : undefined}
counterPart={undefined} counterPart={undefined}
@ -107,6 +114,15 @@ function EPExplorer(props: {
eps={eps} eps={eps}
setOmitSubject={payload => adjustEps({ type: "set omitSubject", payload })} 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>; </div>;
} }

View File

@ -2,7 +2,7 @@ import * as T from "../../types"
import Select from "react-select"; import Select from "react-select";
import ButtonSelect from "../ButtonSelect"; import ButtonSelect from "../ButtonSelect";
export const zIndexProps = { const zIndexProps = {
menuPortalTarget: document.body, menuPortalTarget: document.body,
styles: { menuPortal: (base: any) => ({ ...base, zIndex: 9999 }) }, styles: { menuPortal: (base: any) => ({ ...base, zIndex: 9999 }) },
}; };

View File

@ -4,8 +4,9 @@ import {
personNumber, personNumber,
} from "../../lib/misc-helpers"; } from "../../lib/misc-helpers";
import { isUnisexNounEntry } from "../../lib/type-predicates"; import { isUnisexNounEntry } from "../../lib/type-predicates";
import { checkForMiniPronounsError } from "../../lib/phrase-building/compile";
export type EpsReducerAction = { type EpsReducerAction = {
type: "set predicate type", type: "set predicate type",
payload: "NP" | "Complement", payload: "NP" | "Complement",
} | { } | {
@ -17,9 +18,6 @@ export type EpsReducerAction = {
} | { } | {
type: "set predicate comp", type: "set predicate comp",
payload: T.EqCompSelection | undefined, payload: T.EqCompSelection | undefined,
} | {
type: "shrink possesive",
payload: number | undefined,
} | { } | {
type: "set omitSubject", type: "set omitSubject",
payload: "true" | "false", payload: "true" | "false",
@ -28,7 +26,7 @@ export type EpsReducerAction = {
payload: T.EquativeSelection, 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") { if (action.type === "set predicate type") {
return { return {
...eps, ...eps,
@ -39,49 +37,15 @@ export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAc
}; };
} }
if (action.type === "set subject") { if (action.type === "set subject") {
return massageSubjectChange(action.payload, eps); const subject = action.payload;
}
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) { if (!subject) {
return { return {
...old, ...eps,
subject, subject,
}; };
} }
if (subject.type === "pronoun" && old.predicate.type === "NP" && old.predicate.NP?.type === "noun" && isUnisexNounEntry(old.predicate.NP.entry)) { if (subject.type === "pronoun" && eps.predicate.type === "NP" && eps.predicate.NP?.type === "noun" && isUnisexNounEntry(eps.predicate.NP.entry)) {
const predicate = old.predicate.NP; const predicate = eps.predicate.NP;
const adjusted = { const adjusted = {
...predicate, ...predicate,
...predicate.numberCanChange ? { ...predicate.numberCanChange ? {
@ -92,53 +56,87 @@ function massageSubjectChange(subject: T.NPSelection | undefined, old: T.EPSelec
} : {}, } : {},
} }
return { return {
...old, ...eps,
subject, subject,
predicate: { predicate: {
...old.predicate, ...eps.predicate,
NP: adjusted, NP: adjusted,
}, },
}; };
} }
return { const n: T.EPSelectionState = {
...old, ...eps,
subject, subject,
}; };
return subject ? ensureMiniPronounsOk(eps, n, sendAlert) : n;
} }
if (action.type === "set predicate NP") {
function massageNPPredicateChange(selection: T.NPSelection | undefined, old: T.EPSelectionState): T.EPSelectionState { const selection = action.payload;
if (!selection) { if (!selection) {
return { return {
...old, ...eps,
predicate: { predicate: {
...old.predicate, ...eps.predicate,
NP: selection, NP: selection,
}, },
}; };
} }
if (old.subject?.type === "pronoun" && selection.type === "noun" && isUnisexNounEntry(selection.entry)) { if (eps.subject?.type === "pronoun" && selection.type === "noun" && isUnisexNounEntry(selection.entry)) {
const { gender, number } = selection; const { gender, number } = selection;
const pronoun = old.subject.person; const pronoun = eps.subject.person;
const newPronoun = movePersonNumber(movePersonGender(pronoun, gender), number); const newPronoun = movePersonNumber(movePersonGender(pronoun, gender), number);
return { return {
...old, ...eps,
subject: { subject: {
...old.subject, ...eps.subject,
person: newPronoun, person: newPronoun,
}, },
predicate: { predicate: {
...old.predicate, ...eps.predicate,
NP: selection, NP: selection,
}, },
}; };
} }
return { const n: T.EPSelectionState = {
...old, ...eps,
predicate: { predicate: {
...old.predicate, ...eps.predicate,
NP: selection, NP: selection,
}, },
}; };
return selection ? ensureMiniPronounsOk(eps, n, sendAlert) : n;
}
if (action.type === "set predicate comp") {
return {
...eps,
predicate: {
...eps.predicate,
Complement: action.payload,
},
};
}
if (action.type === "set omitSubject") {
const n: T.EPSelectionState = {
...eps,
omitSubject: action.payload === "true",
};
return ensureMiniPronounsOk(eps, n, sendAlert);
}
// if (action.type === "set equative") {
return {
...eps,
equative: action.payload,
}
// }
}
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;
}
return eps;
} }
function movePersonGender(p: T.Person, gender: T.Gender): T.Person { function movePersonGender(p: T.Person, gender: T.Gender): T.Person {

View File

@ -10,6 +10,8 @@ import { isSecondPerson } from "../../lib/phrase-building/vp-tools";
const npTypes: T.NPType[] = ["pronoun", "noun", "participle"]; const npTypes: T.NPType[] = ["pronoun", "noun", "participle"];
export const shrunkenBackground = "rgba(255, 206, 43, 0.15)";
function NPPicker(props: { function NPPicker(props: {
heading?: JSX.Element | string, heading?: JSX.Element | string,
onChange: (nps: T.NPSelection | undefined) => void, onChange: (nps: T.NPSelection | undefined) => void,
@ -17,24 +19,27 @@ function NPPicker(props: {
counterPart: T.NPSelection | T.VerbObject | undefined, counterPart: T.NPSelection | T.VerbObject | undefined,
role: "subject" | "object" | "ergative" | "possesor", role: "subject" | "object" | "ergative" | "possesor",
opts: T.TextOptions, opts: T.TextOptions,
handleShrinkPossesive: (uid: number | undefined) => void,
shrunkenPossesiveInPhrase: number | undefined,
cantClear?: boolean, cantClear?: boolean,
is2ndPersonPicker?: boolean, is2ndPersonPicker?: boolean,
entryFeeder: T.EntryFeeder, entryFeeder: T.EntryFeeder,
phraseIsComplete: boolean,
isShrunk?: boolean,
}) { }) {
if (props.is2ndPersonPicker && ((props.np?.type !== "pronoun") || !isSecondPerson(props.np.person))) { if (props.is2ndPersonPicker && ((props.np?.type !== "pronoun") || !isSecondPerson(props.np.person))) {
throw new Error("can't use 2ndPerson NPPicker without a pronoun"); throw new Error("can't use 2ndPerson NPPicker without a pronoun");
} }
const [addingPoss, setAddingPoss] = useState<boolean>(false); const [addingPoss, setAddingPoss] = useState<boolean>(false);
const [npType, setNpType] = useState<T.NPType | undefined>(props.np ? props.np.type : undefined); 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(() => { useEffect(() => {
setNpType(props.np ? props.np.type : undefined); setNpType(props.np ? props.np.type : undefined);
}, [props.np]); }, [props.np]);
function handleClear() { function handleClear() {
if (props.np && props.np.type === "noun" && props.np.dynamicComplement) return; if (props.np && props.np.type === "noun" && props.np.dynamicComplement) return;
setNpType(undefined); setNpType(undefined);
props.onChange(undefined); onChange(undefined);
} }
function handleNPTypeChange(ntp: T.NPType) { function handleNPTypeChange(ntp: T.NPType) {
if (ntp === "pronoun") { if (ntp === "pronoun") {
@ -45,38 +50,46 @@ function NPPicker(props: {
distance: "far", distance: "far",
}; };
setNpType(ntp); setNpType(ntp);
props.onChange(pronoun); onChange(pronoun);
} else { } else {
props.onChange(undefined); onChange(undefined);
setNpType(ntp); setNpType(ntp);
} }
} }
// TODO: REMOVE
function handlePossesiveChange(p: T.NPSelection | undefined) { function handlePossesiveChange(p: T.NPSelection | undefined) {
if (!props.np || props.np.type === "pronoun") return; if (!props.np || props.np.type === "pronoun") return;
if (!p) { if (!p) {
props.onChange({ onChange({
...props.np, ...props.np,
possesor: undefined, possesor: undefined,
}); });
return; return;
} }
const isNewPosesser = checkForNewPossesor(p, props.np.possesor); const isNewPosesser = checkForNewPossesor(p, props.np.possesor);
const possesor = { const possesor: T.PossesorSelection = {
np: p, 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, ...props.np,
possesor, 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 isDynamicComplement = props.np && props.np.type === "noun" && props.np.dynamicComplement;
const clearButton = (!props.cantClear && !props.is2ndPersonPicker && !isDynamicComplement) const clearButton = (!props.cantClear && !props.is2ndPersonPicker && !isDynamicComplement)
? <button className="btn btn-sm btn-light mb-2" onClick={handleClear}>X</button> ? <button className="btn btn-sm btn-light mb-2" onClick={handleClear}>X</button>
: <div></div>; : <div></div>;
const possesiveUid = (props.np && props.np.type !== "pronoun" && props.np.possesor)
? props.np.possesor.uid
: undefined;
return <> return <>
<div className="d-flex flex-row justify-content-between"> <div className="d-flex flex-row justify-content-between">
<div></div> <div></div>
@ -105,16 +118,15 @@ function NPPicker(props: {
</button> </button>
</div>)} </div>)}
</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 className="d-flex flex-row text-muted mb-2">
<div>Possesive:</div> <div>Possesive:</div>
{props.np.possesor && <div className="clickable mx-2" onClick={() => { {(props.np.possesor && !props.isShrunk) && <div className="clickable ml-3 mr-2" onClick={handleToggleShrunken}>
props.handleShrinkPossesive(possesiveUid === props.shrunkenPossesiveInPhrase {!props.np.possesor.shrunken ? "🪄" : "👶"}
? undefined
: possesiveUid
);
}}>
{possesiveUid === props.shrunkenPossesiveInPhrase ? "👶 Shrunken" : "Shrink"}
</div>} </div>}
<div className="clickable ml-2" onClick={() => { <div className="clickable ml-2" onClick={() => {
setAddingPoss(false); setAddingPoss(false);
@ -124,12 +136,11 @@ function NPPicker(props: {
</div> </div>
</div> </div>
<NPPicker <NPPicker
phraseIsComplete={props.phraseIsComplete}
onChange={handlePossesiveChange} onChange={handlePossesiveChange}
counterPart={undefined} counterPart={undefined}
cantClear cantClear
np={props.np.possesor ? props.np.possesor.np : undefined} np={props.np.possesor ? props.np.possesor.np : undefined}
handleShrinkPossesive={props.handleShrinkPossesive}
shrunkenPossesiveInPhrase={props.shrunkenPossesiveInPhrase}
role="possesor" role="possesor"
opts={props.opts} opts={props.opts}
entryFeeder={props.entryFeeder} entryFeeder={props.entryFeeder}
@ -142,7 +153,7 @@ function NPPicker(props: {
? <PronounPicker ? <PronounPicker
role={props.role} role={props.role}
pronoun={props.np} pronoun={props.np}
onChange={props.onChange} onChange={onChange}
is2ndPersonPicker={props.is2ndPersonPicker} is2ndPersonPicker={props.is2ndPersonPicker}
opts={props.opts} opts={props.opts}
/> />
@ -150,14 +161,14 @@ function NPPicker(props: {
? <NounPicker ? <NounPicker
entryFeeder={props.entryFeeder} entryFeeder={props.entryFeeder}
noun={(props.np && props.np.type === "noun") ? props.np : undefined} noun={(props.np && props.np.type === "noun") ? props.np : undefined}
onChange={props.onChange} onChange={onChange}
opts={props.opts} opts={props.opts}
/> />
: npType === "participle" : npType === "participle"
? <ParticiplePicker ? <ParticiplePicker
entryFeeder={props.entryFeeder.verbs} entryFeeder={props.entryFeeder.verbs}
participle={(props.np && props.np.type === "participle") ? props.np : undefined} participle={(props.np && props.np.type === "participle") ? props.np : undefined}
onChange={props.onChange} onChange={onChange}
opts={props.opts} opts={props.opts}
/> />
: null : 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 { function checkForNewPossesor(n: T.NPSelection | undefined, old: T.PossesorSelection | undefined): boolean {
if (!old || !n) { if (!old || !n) {
return true; return true;
@ -183,8 +240,4 @@ function checkForNewPossesor(n: T.NPSelection | undefined, old: T.PossesorSelect
return false; return false;
} }
function makeUID() {
return Math.floor(Math.random() * 10000000);
}
export default NPPicker; export default NPPicker;

View File

@ -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 * as T from "../../types";
import AbbreviationFormSelector from "./AbbreviationFormSelector"; import AbbreviationFormSelector from "./AbbreviationFormSelector";
import {
isPastTense,
completeVPSelection,
} from "../../lib/phrase-building/vp-tools";
import useStickyState from "../../lib/useStickyState"; import useStickyState from "../../lib/useStickyState";
import Examples from "../Examples"; import Examples from "../Examples";
function VPDisplay({ VP, opts, setForm }: { function VPDisplay({ VP, opts, setForm }: {
VP: T.VPSelectionState, VP: T.VPSelectionState | T.VPRendered,
opts: T.TextOptions, opts: T.TextOptions,
setForm: (form: T.FormVersion) => void, setForm: (form: T.FormVersion) => void,
}) { }) {
const [OSV, setOSV] = useStickyState<boolean>(false, "includeOSV"); const [OSV, setOSV] = useStickyState<boolean>(false, "includeOSV");
const VPComplete = completeVPSelection(VP); if (!("type" in VP)) {
if (!VPComplete) {
return <div className="lead text-muted text-center mt-4"> return <div className="lead text-muted text-center mt-4">
{(() => { {(() => {
const twoNPs = (VP.subject === undefined) && (VP.verb.object === undefined); const twoNPs = (VP.subject === undefined) && (VP.verb.object === undefined);
@ -23,7 +18,7 @@ function VPDisplay({ VP, opts, setForm }: {
})()} })()}
</div>; </div>;
} }
const result = compileVP(renderVP(VPComplete), { ...VP.form, OSV }); const result = compileVP(VP, { ...VP.form, OSV });
return <div className="text-center mt-1"> return <div className="text-center mt-1">
{VP.verb.transitivity === "transitive" && <div className="form-check mb-2"> {VP.verb.transitivity === "transitive" && <div className="form-check mb-2">
<input <input
@ -38,7 +33,7 @@ function VPDisplay({ VP, opts, setForm }: {
</label> </label>
</div>} </div>}
<AbbreviationFormSelector <AbbreviationFormSelector
adjustable={whatsAdjustable(VPComplete)} adjustable={VP.whatsAdjustable}
form={VP.form} form={VP.form}
onChange={setForm} onChange={setForm}
/> />
@ -61,20 +56,6 @@ function VPDisplay({ VP, opts, setForm }: {
</div> </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 }) { function VariationLayer({ vs, opts }: { vs: T.PsString[], opts: T.TextOptions }) {
return <div className="mb-2"> return <div className="mb-2">
<Examples opts={opts} lineHeight={0}>{vs}</Examples> <Examples opts={opts} lineHeight={0}>{vs}</Examples>

View File

@ -1,4 +1,4 @@
import NPPicker from "../np-picker/NPPicker"; import NPPicker, { shrunkenBackground } from "../np-picker/NPPicker";
import VerbPicker from "./VerbPicker"; import VerbPicker from "./VerbPicker";
import TensePicker from "./TensePicker"; import TensePicker from "./TensePicker";
import VPDisplay from "./VPDisplay"; import VPDisplay from "./VPDisplay";
@ -8,13 +8,14 @@ import ChartDisplay from "./VPChartDisplay";
import useStickyState, { useStickyReducer } from "../../lib/useStickyState"; import useStickyState, { useStickyReducer } from "../../lib/useStickyState";
import { makeVPSelectionState } from "./verb-selection"; import { makeVPSelectionState } from "./verb-selection";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { getKingAndServant } from "../../lib/phrase-building/render-vp"; import { getKingAndServant, renderVP } from "../../lib/phrase-building/render-vp";
import { isPastTense } from "../../lib/phrase-building/vp-tools"; import { completeVPSelection, isPastTense } from "../../lib/phrase-building/vp-tools";
import VPExplorerQuiz from "./VPExplorerQuiz"; import VPExplorerQuiz from "./VPExplorerQuiz";
import VPExplorerExplanationModal, { roleIcon } from "./VPExplorerExplanationModal"; import VPExplorerExplanationModal, { roleIcon } from "./VPExplorerExplanationModal";
// @ts-ignore // @ts-ignore
import LZString from "lz-string"; import LZString from "lz-string";
import { vpsReducer } from "./vps-reducer"; import { vpsReducer } from "./vps-reducer";
import { getShrunkenServant } from "../../lib/phrase-building/compile";
const phraseURLParam = "VPPhrase"; const phraseURLParam = "VPPhrase";
@ -28,7 +29,7 @@ const phraseURLParam = "VPPhrase";
// TODO: error handling on error with rendering etc // TODO: error handling on error with rendering etc
export function VPExplorer(props: { function VPExplorer(props: {
loaded?: T.VPSelectionState, loaded?: T.VPSelectionState,
verb: T.VerbEntry, verb: T.VerbEntry,
opts: T.TextOptions, opts: T.TextOptions,
@ -41,6 +42,7 @@ export function VPExplorer(props: {
? props.loaded ? props.loaded
: savedVps => makeVPSelectionState(props.verb, savedVps), : savedVps => makeVPSelectionState(props.verb, savedVps),
"vpsState8", "vpsState8",
flashMessage,
); );
const [mode, setMode] = useStickyState<"charts" | "phrases" | "quiz">( const [mode, setMode] = useStickyState<"charts" | "phrases" | "quiz">(
savedMode => { savedMode => {
@ -51,12 +53,19 @@ export function VPExplorer(props: {
"verbExplorerMode2", "verbExplorerMode2",
); );
const [showShareClipped, setShowShareClipped] = useState<boolean>(false); 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 [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 isPast = isPastTense(vps.verb.tenseCategory === "perfect" ? vps.verb.perfectTense : vps.verb.verbTense);
const roles = getKingAndServant( const roles = getKingAndServant(
isPast, isPast,
vps.verb.transitivity !== "intransitive", vps.verb.transitivity !== "intransitive",
); );
function flashMessage(msg: string) {
setAlert(msg);
setTimeout(() => {
setAlert(undefined);
}, 1500);
}
useEffect(() => { useEffect(() => {
const VPSFromUrl = getVPSFromUrl(); const VPSFromUrl = getVPSFromUrl();
if (VPSFromUrl) { if (VPSFromUrl) {
@ -90,16 +99,10 @@ export function VPExplorer(props: {
function handleSubjObjSwap() { function handleSubjObjSwap() {
adjustVps({ type: "swap subj/obj" }); adjustVps({ type: "swap subj/obj" });
} }
function handleShrinkPossesive(shrunkenPossesive: number | undefined) {
adjustVps({
type: "shrink possesive",
payload: shrunkenPossesive,
});
}
function quizLock<T>(f: T) { function quizLock<T>(f: T) {
if (mode === "quiz") { if (mode === "quiz") {
return () => { return () => {
alert("to adjust this, get out of quiz mode"); flashMessage("to adjust this, get out of quiz mode");
return null; return null;
}; };
} }
@ -120,6 +123,15 @@ export function VPExplorer(props: {
setShowShareClipped(false); setShowShareClipped(false);
}, 1000); }, 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"}}> return <div className="mt-3" style={{ maxWidth: "950px"}}>
<VerbPicker <VerbPicker
vps={vps} vps={vps}
@ -155,40 +167,60 @@ export function VPExplorer(props: {
</div>} </div>}
{mode !== "quiz" && <div className="d-flex flex-row justify-content-around flex-wrap" style={{ marginLeft: "-0.5rem", marginRight: "-0.5rem" }}> {mode !== "quiz" && <div className="d-flex flex-row justify-content-around flex-wrap" style={{ marginLeft: "-0.5rem", marginRight: "-0.5rem" }}>
{mode === "phrases" && <> {mode === "phrases" && <>
<div className="my-2"> <div className="my-2" style={{ background: (servantIsShrunk && roles.servant === "subject") ? shrunkenBackground : "inherit" }}>
<NPPicker <NPPicker
phraseIsComplete={phraseIsComplete}
heading={roles.king === "subject" 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" 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">
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} entryFeeder={props.entryFeeder}
role={(isPast && vps.verb.transitivity !== "intransitive") role={(isPast && vps.verb.transitivity !== "intransitive")
? "ergative" ? "ergative"
: "subject" : "subject"
} }
shrunkenPossesiveInPhrase={vps.shrunkenPossesive}
is2ndPersonPicker={vps.verb.tenseCategory === "imperative"} is2ndPersonPicker={vps.verb.tenseCategory === "imperative"}
np={vps.subject} np={vps.subject}
counterPart={vps.verb ? vps.verb.object : undefined} counterPart={vps.verb ? vps.verb.object : undefined}
onChange={handleSubjectChange} onChange={handleSubjectChange}
handleShrinkPossesive={handleShrinkPossesive}
opts={props.opts} opts={props.opts}
isShrunk={(servantIsShrunk && roles.servant === "subject")}
/> />
</div> </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") {(typeof vps.verb.object === "number")
? <div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div> ? <div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div>
: <NPPicker : <NPPicker
shrunkenPossesiveInPhrase={vps.shrunkenPossesive} phraseIsComplete={phraseIsComplete}
handleShrinkPossesive={handleShrinkPossesive}
heading={roles.king === "object" 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: "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} entryFeeder={props.entryFeeder}
role="object" role="object"
np={vps.verb.object} np={vps.verb.object}
counterPart={vps.subject} counterPart={vps.subject}
onChange={handleObjectChange} onChange={handleObjectChange}
opts={props.opts} opts={props.opts}
isShrunk={(servantIsShrunk && roles.servant === "object")}
/>} />}
</div>} </div>}
</>} </>}
@ -201,7 +233,7 @@ export function VPExplorer(props: {
</div> </div>
</div>} </div>}
{mode === "phrases" && <VPDisplay {mode === "phrases" && <VPDisplay
VP={vps} VP={rendered ? rendered : vps}
opts={props.opts} opts={props.opts}
setForm={handleSetForm} setForm={handleSetForm}
/>} />}
@ -220,6 +252,15 @@ export function VPExplorer(props: {
}}> }}>
Phrase URL copied to clipboard Phrase URL copied to clipboard
</div>} </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> </div>
} }

View File

@ -7,7 +7,8 @@ import { standardizePashto } from "../../lib/standardize-pashto";
import shuffleArray from "../../lib/shuffle-array"; import shuffleArray from "../../lib/shuffle-array";
import InlinePs from "../InlinePs"; import InlinePs from "../InlinePs";
import { psStringEquals } from "../../lib/p-text-helpers"; 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 { getRandomTense } from "./TensePicker";
import { getTenseFromVerbSelection, removeBa, switchSubjObj } from "../../lib/phrase-building/vp-tools"; import { getTenseFromVerbSelection, removeBa, switchSubjObj } from "../../lib/phrase-building/vp-tools";
import playAudio from "../../lib/play-audio"; import playAudio from "../../lib/play-audio";
@ -445,7 +446,6 @@ function getRandomVPSelection(mix: MixType = "both") {
return { return {
subject: subject !== undefined ? subject : randSubj, subject: subject !== undefined ? subject : randSubj,
verb: randomizeTense(verb, true), verb: randomizeTense(verb, true),
shrunkenPossesive: undefined,
form: { removeKing: false, shrinkServant: false }, form: { removeKing: false, shrinkServant: false },
} }
} }
@ -462,7 +462,6 @@ function getRandomVPSelection(mix: MixType = "both") {
return { return {
subject: randSubj, subject: randSubj,
verb: randomizeTense(v, true), verb: randomizeTense(v, true),
shrunkenPossesive: undefined,
form: { removeKing: false, shrinkServant: false }, form: { removeKing: false, shrinkServant: false },
}; };
}; };

View File

@ -67,15 +67,6 @@ export function makeVPSelectionState(
canChangeStatDyn: "stative" in info, canChangeStatDyn: "stative" in info,
}, },
form: os ? os.form : { removeKing: false, shrinkServant: false }, 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",
}; };
} }

View File

@ -3,12 +3,13 @@ import {
isInvalidSubjObjCombo, isInvalidSubjObjCombo,
} from "../../lib/phrase-building/vp-tools"; } from "../../lib/phrase-building/vp-tools";
import { switchSubjObj } 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 { ensure2ndPersSubjPronounAndNoConflict } from "../../lib/phrase-building/vp-tools";
import { import {
isPerfectTense, isPerfectTense,
isImperativeTense, isImperativeTense,
} from "../../lib/type-predicates"; } from "../../lib/type-predicates";
import { checkForMiniPronounsError } from "../../lib/phrase-building/compile";
export type VpsReducerAction = { export type VpsReducerAction = {
type: "load vps", type: "load vps",
@ -24,9 +25,6 @@ export type VpsReducerAction = {
payload: T.NPSelection | undefined, payload: T.NPSelection | undefined,
} | { } | {
type: "swap subj/obj", type: "swap subj/obj",
} | {
type: "shrink possesive",
payload: number | undefined,
} | { } | {
type: "set form", type: "set form",
payload: T.FormVersion, payload: T.FormVersion,
@ -48,9 +46,21 @@ export type VpsReducerAction = {
} | { } | {
type: "set tense category", type: "set tense category",
payload: "basic" | "modal" | "perfect" | "imperative", payload: "basic" | "modal" | "perfect" | "imperative",
} } | {
type: "toggle servant shrink",
};
export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction): T.VPSelectionState { 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;
}
function doReduce(): T.VPSelectionState {
if (action.type === "load vps") { if (action.type === "load vps") {
return action.payload; return action.payload;
} }
@ -61,7 +71,7 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction): T
&& &&
hasPronounConflict(subject, vps.verb?.object) hasPronounConflict(subject, vps.verb?.object)
) { ) {
alert("That combination of pronouns is not allowed"); if (sendAlert) sendAlert("That combination of pronouns is not allowed");
return vps; return vps;
} }
return { return {
@ -77,7 +87,7 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction): T
const object = action.payload; const object = action.payload;
// check for pronoun conflict // check for pronoun conflict
if (hasPronounConflict(vps.subject, object)) { if (hasPronounConflict(vps.subject, object)) {
alert("That combination of pronouns is not allowed"); if (sendAlert) sendAlert("That combination of pronouns is not allowed");
return vps; return vps;
} }
return { return {
@ -92,12 +102,6 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction): T
if (vps.verb?.isCompound === "dynamic") return vps; if (vps.verb?.isCompound === "dynamic") return vps;
return switchSubjObj(vps); return switchSubjObj(vps);
} }
if (action.type === "shrink possesive") {
return {
...vps,
shrunkenPossesive: action.payload,
};
}
if (action.type === "set form") { if (action.type === "set form") {
return { return {
...vps, ...vps,
@ -110,16 +114,26 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction): T
if (voice === "passive" && vps.verb.tenseCategory === "imperative") { if (voice === "passive" && vps.verb.tenseCategory === "imperative") {
return vps; return vps;
} }
if (voice === "passive" && (typeof vps.verb.object === "object")) { if (voice === "passive") {
return { return {
...vps, ...vps,
subject: vps.verb.object, subject: typeof vps.verb.object === "object" ? vps.verb.object : undefined,
verb: changeVoice(vps.verb, voice, vps.verb.object), verb: {
...vps.verb,
object: "none",
voice,
},
}; };
} else { } else {
return { return {
...vps, ...vps,
verb: changeVoice(vps.verb, voice, voice === "active" ? vps.subject : undefined), subject: undefined,
verb: {
...vps.verb,
// TODO: is this ok??
object: typeof vps.subject === "object" ? vps.subject : undefined,
voice,
},
}; };
} }
} else { } else {
@ -184,7 +198,7 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction): T
}; };
} }
} }
// if (action.type === "set tense category") { if (action.type === "set tense category") {
if (!vps.verb) return vps; if (!vps.verb) return vps;
const category = action.payload; const category = action.payload;
if (category === "imperative") { if (category === "imperative") {
@ -204,8 +218,18 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction): T
tenseCategory: category, tenseCategory: category,
}, },
}; };
}
// if (action.type === "toggle servant shrink") {
return {
...vps,
form: {
...vps.form,
shrinkServant: !vps.form.shrinkServant,
},
};
// } // }
} }
}
function hasPronounConflict(subject: T.NPSelection | undefined, object: undefined | T.VerbObject): boolean { function hasPronounConflict(subject: T.NPSelection | undefined, object: undefined | T.VerbObject): boolean {
const subjPronoun = (subject && subject.type === "pronoun") ? subject : undefined; const subjPronoun = (subject && subject.type === "pronoun") ? subject : undefined;

View File

@ -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;
}

View File

@ -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()]);
}

View File

@ -1,6 +1,6 @@
import * as T from "../../types"; import * as T from "../../types";
import { import {
concatPsString, psStringEquals, concatPsString,
} from "../p-text-helpers"; } from "../p-text-helpers";
import { import {
Segment, Segment,
@ -20,18 +20,18 @@ import {
} from "./vp-tools"; } from "./vp-tools";
import { isImperativeTense, isModalTense, isPerfectTense } from "../type-predicates"; import { isImperativeTense, isModalTense, isPerfectTense } from "../type-predicates";
import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools"; import { getEnglishFromRendered, getPashtoFromRendered } from "./np-tools";
import { import { getVerbBlockPosFromPerson } from "../misc-helpers";
orderKidsSection, import { pronouns } from "../grammar-units";
findPossesiveToShrinkInVP, import { completeEPSelection, renderEP } from "./render-ep";
shrinkNP, import { completeVPSelection } from "./vp-tools";
} from "./compile-tools"; import { renderVP } from "./render-vp";
type Form = T.FormVersion & { OSV?: boolean }; 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): { 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.PsString[], e?: string [] };
export function compileVP(VP: T.VPRendered, form: Form, combineLengths?: true): { ps: T.SingleOrLengthOpts<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 verb = VP.verb.ps;
const { kids, NPs } = getSegmentsAndKids(VP, form); const { kids, NPs } = getVPSegmentsAndKids(VP, form);
const psResult = compilePs({ const psResult = compilePs({
NPs, NPs,
kids, 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[][] } { export function getShrunkenServant(VP: T.VPRendered): Segment | undefined {
const removeKing = form.removeKing && !(VP.isCompound === "dynamic" && VP.isPast); const shrinkServant = VP.form.shrinkServant && !(VP.isCompound === "dynamic" && !VP.isPast);
const shrinkServant = form.shrinkServant && !(VP.isCompound === "dynamic" && !VP.isPast);
const toShrinkServant = (() => { const toShrinkServant = (() => {
if (!shrinkServant) return undefined; if (!shrinkServant) return undefined;
if (!VP.servant) 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; if (typeof servant !== "object") return undefined;
return servant; return servant;
})(); })();
const shrunkenServant = toShrinkServant ? shrinkNP(toShrinkServant) : undefined; return toShrinkServant ? shrinkNP(toShrinkServant) : undefined;
const possToShrink = findPossesiveToShrinkInVP(VP); }
const shrunkenPossesive = possToShrink ? shrinkNP(possToShrink) : undefined;
const shrunkenPossAllowed = possToShrink && shrunkenPossesive && ( export function getVPSegmentsAndKids(VP: T.VPRendered, form?: Form): { kids: Segment[], NPs: Segment[][] } {
!shrunkenServant || !psStringEquals(shrunkenPossesive.ps[0], shrunkenServant.ps[0]) const removeKing = VP.form.removeKing && !(VP.isCompound === "dynamic" && VP.isPast);
) && ( const shrunkenServant = getShrunkenServant(VP);
// can only shrink the possesive if the parent of the possesive is still in full form const possToShrink = findPossesivesToShrinkInVP(VP, {
!(possToShrink.role === "king" && removeKing) shrunkServant: !!shrunkenServant,
&& removedKing: removeKing,
!(possToShrink.role === "servant" && shrinkServant) });
);
const shrinkPossUid = shrunkenPossAllowed
? VP.shrunkenPossesive
: undefined;
const SO = { const SO = {
subject: getPashtoFromRendered(VP.subject, shrinkPossUid, false), subject: getPashtoFromRendered(VP.subject, false),
object: typeof VP.object === "object" ? getPashtoFromRendered(VP.object, shrinkPossUid, VP.subject.person) : undefined, object: typeof VP.object === "object" ? getPashtoFromRendered(VP.object, VP.subject.person) : undefined,
}; };
function getSegment(t: "subject" | "object"): Segment | undefined { function getSegment(t: "subject" | "object"): Segment | undefined {
const word = (VP.servant === t) const word = (VP.servant === t)
? (!shrinkServant ? SO[t] : undefined) ? (!shrunkenServant ? SO[t] : undefined)
: (VP.king === t) : (VP.king === t)
? (!removeKing ? SO[t] : undefined) ? (!removeKing ? SO[t] : undefined)
: undefined; : undefined;
@ -128,8 +123,7 @@ function getSegmentsAndKids(VP: T.VPRendered, form: Form): { kids: Segment[], NP
? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])] : [], ? [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])] : [],
...shrunkenServant ...shrunkenServant
? [shrunkenServant] : [], ? [shrunkenServant] : [],
...(shrunkenPossesive && shrunkenPossAllowed) ...possToShrink.map(shrinkNP),
? [shrunkenPossesive] : [],
]), ]),
NPs: [ 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 ?? // TODO: make this an option to also include O S V order ??
// also show O S V if both are showing // 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 { function mergeSegments(s1: Segment, s2: Segment, noSpace?: "no space"): Segment {
if (noSpace) { if (noSpace) {
return s2.adjust({ ps: (p) => concatPsString(s1.ps[0], p) }); return s2.adjust({ ps: (p) => concatPsString(s1.ps[0], p) });
@ -309,3 +379,126 @@ function compileEnglish(VP: T.VPRendered): string[] | undefined {
})) }))
: 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()]);
}

View File

@ -1,7 +0,0 @@
import { renderVP } from "./render-vp";
import { compileVP } from "./compile-vp";
export {
renderVP,
compileVP,
};

View File

@ -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[] { function trimOffShrunkenPossesive(p: T.Rendered<T.NPSelection>): T.Rendered<T.NPSelection> {
const base = getBaseAndAdjectives(np); if (!("possesor" in p)) {
if (!np.possesor || np.possesor.uid === shrunkenPossesive) { return p;
return base; }
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 addPossesor(owner: T.Rendered<T.NPSelection>, existing: T.PsString[], subjectsPerson: false | T.Person): T.PsString[] {
function willBeReflexive(subj: T.Person, obj: T.Person): boolean { function willBeReflexive(subj: T.Person, obj: T.Person): boolean {

View File

@ -32,7 +32,6 @@ export function renderEP(EP: T.EPSelectionComplete): T.EPRendered {
: renderEqCompSelection(EP.predicate.selection, kingPerson), : renderEqCompSelection(EP.predicate.selection, kingPerson),
equative: renderEquative(EP.equative, kingPerson), equative: renderEquative(EP.equative, kingPerson),
englishBase: equativeBuilders[EP.equative.tense](kingPerson, EP.equative.negative), englishBase: equativeBuilders[EP.equative.tense](kingPerson, EP.equative.negative),
shrunkenPossesive: EP.shrunkenPossesive,
omitSubject: EP.omitSubject, omitSubject: EP.omitSubject,
}; };
} }
@ -150,3 +149,32 @@ function getEnglishConj(p: T.Person, e: string | T.EnglishBlock): string {
// return inflections[gender][plural ? 1 : 0]; // 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,
},
};
}

View File

@ -12,7 +12,7 @@ import {
import { parseEc } from "../misc-helpers"; import { parseEc } from "../misc-helpers";
import { getEnglishWord } from "../get-english-word"; import { getEnglishWord } from "../get-english-word";
import { renderAdjectiveSelection } from "./render-adj"; 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, 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"; 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; if (!possesor) return undefined;
const isSingUnisexAnim5PatternNoun = (possesor.np.type === "noun"
&& possesor.np.number === "singular"
&& isUnisexAnimNounEntry(possesor.np.entry)
&& isPattern5Entry(possesor.np.entry)
);
return { return {
uid: possesor.uid, shrunken: possesor.shrunken,
np: renderNPSelection( np: renderNPSelection(
possesor.np, possesor.np,
!(possesor.np.type === "noun" && isUnisexNounEntry(possesor.np.entry) && isPattern5Entry(possesor.np.entry)), !isSingUnisexAnim5PatternNoun,
false, false,
"subject", "subject",
possesorRole, possesorRole,

View File

@ -47,7 +47,6 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
type: "VPRendered", type: "VPRendered",
king, king,
servant, servant,
shrunkenPossesive: VP.shrunkenPossesive,
isPast, isPast,
isTransitive, isTransitive,
isCompound: VP.verb.isCompound, isCompound: VP.verb.isCompound,
@ -60,10 +59,25 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
vs: VP.verb, vs: VP.verb,
}), }),
form: VP.form, form: VP.form,
whatsAdjustable: whatsAdjustable(VP),
}; };
return b; 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 { function renderVerbSelection(vs: T.VerbSelectionComplete, person: T.Person, objectPerson: T.Person | undefined): T.VerbRendered {
const v = vs.dynAuxVerb || vs.verb; const v = vs.dynAuxVerb || vs.verb;
const conjugations = conjugateVerb(v.entry, v.complement); const conjugations = conjugateVerb(v.entry, v.complement);

View File

@ -49,6 +49,10 @@ export function isUnisexNounEntry(e: T.NounEntry | T.AdjectiveEntry): e is T.Uni
return isNounEntry(e) && e.c.includes("unisex"); 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) { export function isAdjOrUnisexNounEntry(e: T.Entry): e is (T.AdjectiveEntry | T.UnisexNounEntry) {
return isAdjectiveEntry(e) || ( return isAdjectiveEntry(e) || (
isNounEntry(e) && isUnisexNounEntry(e) isNounEntry(e) && isUnisexNounEntry(e)
@ -136,9 +140,7 @@ export function isPattern5Entry<T extends (T.NounEntry | T.AdjectiveEntry)>(e: T
return ( return (
!!(e.infap && e.infaf && e.infbp && e.infbf) !!(e.infap && e.infaf && e.infbp && e.infbf)
&& &&
(e.infaf.slice(-1) === "u") e.infap.includes("ا")
&&
!e.infap.slice(1).includes("ا")
); );
} }

View File

@ -46,15 +46,16 @@ export default function useStickyState<T extends SaveableData>(defaultValue: T |
} }
export function useStickyReducer<T extends SaveableData, A>( 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), defaultValue: T | ((old: T | undefined) => T),
key: string, 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); const [state, unsafeSetState] = useStickyState<T>(defaultValue, key);
function adjustState(action: A) { function adjustState(action: A) {
unsafeSetState(oldState => { 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

View File

@ -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 MascNounEntry = NounEntry & { __brand2: "a masc noun entry" };
export type FemNounEntry = NounEntry & { __brand2: "a fem noun entry" }; export type FemNounEntry = NounEntry & { __brand2: "a fem noun entry" };
export type UnisexNounEntry = MascNounEntry & { __brand3: "a unisex 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 AdverbEntry = DictionaryEntry & { c: string } & { __brand: "an adverb entry" };
export type LocativeAdverbEntry = AdverbEntry & { __brand2: "a locative adverb entry" }; export type LocativeAdverbEntry = AdverbEntry & { __brand2: "a locative adverb entry" };
export type AdjectiveEntry = DictionaryEntry & { c: string } & { __brand: "an adjective entry" }; export type AdjectiveEntry = DictionaryEntry & { c: string } & { __brand: "an adjective entry" };
@ -501,7 +502,6 @@ export type VPRendered = {
type: "VPRendered", type: "VPRendered",
king: "subject" | "object", king: "subject" | "object",
servant: "subject" | "object" | undefined, servant: "subject" | "object" | undefined,
shrunkenPossesive: number | undefined,
isPast: boolean, isPast: boolean,
isTransitive: boolean, isTransitive: boolean,
isCompound: "stative" | "dynamic" | false, isCompound: "stative" | "dynamic" | false,
@ -510,6 +510,7 @@ export type VPRendered = {
verb: VerbRendered, verb: VerbRendered,
englishBase?: string[], englishBase?: string[],
form: FormVersion, form: FormVersion,
whatsAdjustable: "both" | "king" | "servant",
} }
export type VerbTense = "presentVerb" export type VerbTense = "presentVerb"
@ -531,14 +532,12 @@ export type Tense = EquativeTense | VerbTense | PerfectTense | ModalTense | Impe
export type VPSelectionState = { export type VPSelectionState = {
subject: NPSelection | undefined, subject: NPSelection | undefined,
verb: VerbSelection, verb: VerbSelection,
shrunkenPossesive: undefined | number,
form: FormVersion, form: FormVersion,
}; };
export type VPSelectionComplete = { export type VPSelectionComplete = {
subject: NPSelection, subject: NPSelection,
verb: VerbSelectionComplete, verb: VerbSelectionComplete,
shrunkenPossesive: undefined | number,
form: FormVersion, form: FormVersion,
}; };
@ -592,7 +591,7 @@ export type NPType = "noun" | "pronoun" | "participle";
export type ObjectNP = "none" | Person.ThirdPlurMale; export type ObjectNP = "none" | Person.ThirdPlurMale;
export type PossesorSelection = { export type PossesorSelection = {
uid: number, shrunken: boolean,
np: NPSelection, 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 }; 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< export type Rendered<T extends NPSelection | EqCompSelection | AdjectiveSelection> = ReplaceKey<
Omit<T, "changeGender" | "changeNumber" | "changeDistance" | "adjectives" | "possesor">, Omit<T, "changeGender" | "changeNumber" | "changeDistance" | "adjectives" | "possesor">,
"e", "e",
@ -652,7 +658,7 @@ export type Rendered<T extends NPSelection | EqCompSelection | AdjectiveSelectio
// TODO: better recursive thing // TODO: better recursive thing
adjectives?: Rendered<AdjectiveSelection>[], adjectives?: Rendered<AdjectiveSelection>[],
possesor?: { possesor?: {
uid: number, shrunken: boolean,
np: Rendered<NPSelection>, np: Rendered<NPSelection>,
}, },
}; };
@ -666,7 +672,6 @@ export type EPSelectionState = {
Complement: EqCompSelection | undefined, Complement: EqCompSelection | undefined,
}, },
equative: EquativeSelection, equative: EquativeSelection,
shrunkenPossesive: undefined | number,
omitSubject: boolean, omitSubject: boolean,
}; };
@ -703,7 +708,6 @@ export type EPRendered = {
predicate: Rendered<NPSelection> | Rendered<EqCompSelection>, predicate: Rendered<NPSelection> | Rendered<EqCompSelection>,
equative: EquativeRendered, equative: EquativeRendered,
englishBase?: string[], englishBase?: string[],
shrunkenPossesive: undefined | number,
omitSubject: boolean, omitSubject: boolean,
} }