APs and moveable blocks for verb phrases!

This commit is contained in:
lingdocs 2022-05-25 18:47:19 -05:00
parent 46fcc5cbb5
commit 9f82e6d085
22 changed files with 633 additions and 372 deletions

View File

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

View File

@ -195,11 +195,10 @@ function ConjugationViewer({ entry, complement, textOptions, showOnly, highlight
try {
return conjugateVerb(entry, complement);
} catch(e) {
console.log("conjugation error", e);
console.error("conjugation error", e);
return undefined;
}
})();
console.log(conjugation);
if (conjugation === undefined) {
// don't show the conjugation viewer if the verb can't be conjugated
return null;

View File

@ -40,7 +40,7 @@ function EPExplorer(props: {
const parent = useRef<HTMLDivElement>(null);
useEffect(() => {
parent.current && autoAnimate(parent.current);
}, [parent])
}, [parent]);
const subject = getSubjectSelection(eps.blocks).selection;
const king = subject?.type === "pronoun"
? "subject"
@ -65,7 +65,7 @@ function EPExplorer(props: {
handleChange={setMode}
/>
</div>
<div className="clickable h5" onClick={() => adjustEps({ type: "insert new AP" })}>+ AP</div>
{mode === "phrases" && <div className="clickable h5" onClick={() => adjustEps({ type: "insert new AP" })}>+ AP</div>}
<div ref={parent} className="d-flex flex-row justify-content-around flex-wrap" style={{ marginLeft: "-0.5rem", marginRight: "-0.5rem" }}>
{mode === "phrases" && <>
{eps.blocks.map(({ block, key }, i) => (

View File

@ -5,8 +5,7 @@ import {
} from "../../lib/misc-helpers";
import { isUnisexNounEntry } from "../../lib/type-predicates";
import { checkForMiniPronounsError } from "../../lib/phrase-building/compile";
import { adjustSubjectSelection, getSubjectSelection, makeAPBlock } from "../../lib/phrase-building/blocks-utils";
import { assertNever } from "assert-never";
import { adjustSubjectSelection, getSubjectSelection, insertNewAP, removeAP, setAP, shiftBlock } from "../../lib/phrase-building/blocks-utils";
type EpsReducerAction = {
type: "set predicate type",
@ -151,50 +150,30 @@ export default function epsReducer(eps: T.EPSelectionState, action: EpsReducerAc
if (action.type === "insert new AP") {
return {
...eps,
blocks: [makeAPBlock(), ...eps.blocks],
blocks: insertNewAP(eps.blocks),
};
// const index = action.payload;
// const newAP = undefined;
// return {
// ...eps,
// blocks: [...eps.blocks].splice(index, 0, newAP),
// };
}
if (action.type === "set AP") {
const blocks = [...eps.blocks];
const { index, AP } = action.payload;
blocks[index].block = AP;
return {
...eps,
blocks,
blocks: setAP(eps.blocks, index, AP),
};
}
if (action.type === "remove AP") {
const blocks = [...eps.blocks];
blocks.splice(action.payload, 1);
return {
...eps,
blocks,
blocks: removeAP(eps.blocks, action.payload),
};
}
if (action.type === "shift block") {
const { index, direction } = action.payload;
const newIndex = index + (direction === "forward" ? 1 : -1);
const blocks = [...eps.blocks];
if (newIndex >= blocks.length || newIndex < 0) {
return eps;
// var k = newIndex - blocks.length + 1;
// while (k--) {
// blocks.push(undefined);
// }
}
blocks.splice(newIndex, 0, blocks.splice(index, 1)[0]);
return {
...eps,
blocks,
blocks: shiftBlock(eps.blocks, index, direction),
};
}
assertNever(action);
throw new Error("unknown epsReducer action");
}
function ensureMiniPronounsOk(old: T.EPSelectionState, eps: T.EPSelectionState, sendAlert?: (msg: string) => void): T.EPSelectionState {

View File

@ -58,7 +58,7 @@ function AdjectiveManager(props: {
<div>Adjective</div>
<div className="d-flex flex-row align-items-baseline">
{!!props.adjectives.length && !adding && <div>
<h6 onClick={() => setAdding(true)}>+ Adj.</h6>
<h6 className="clickable" onClick={() => setAdding(true)}>+ Adj.</h6>
</div>}
<div onClick={deleteAdj(i)} className="ml-4">
<div className="fas fa-trash" />
@ -76,7 +76,7 @@ function AdjectiveManager(props: {
/>
</div>)}
{!adding && !props.adjectives.length && <h6 className="clickable" style={{ float: "right" }}>
<div onClick={() => setAdding(true)}>+ Adj.</div>
<div className="clickable" onClick={() => setAdding(true)}>+ Adj.</div>
</h6>}
</div>;
}

View File

@ -46,9 +46,11 @@ function AdjectivePicker(props: {
{!props.noTitle && <div>
<h6>Adjective</h6>
</div>}
{(!addingSandwich && props.adjective && !props.adjective?.sandwich)
{/* not ready for sandwiches on adjectives */}
{/* {(!addingSandwich && props.adjective && !props.adjective?.sandwich)
? <div className="clickable" onClick={() => setAddingSandwich(true)}>+ Sandwich</div>
: <div></div>}
: <div></div>} */}
<div />
</div>
<div className="mt-1">
<EntrySelect

View File

@ -24,7 +24,13 @@ import {
} from "../../lib/translate-phonetics";
export function makeVerbSelectOption(e: T.VerbEntry, opts: T.TextOptions): { value: string, label: string | JSX.Element } {
const engV = getEnglishVerb(e.entry);
const engV = (() => {
try {
return getEnglishVerb(e.entry);
} catch(e) {
console.error("no english conjugations for verb");
}
})();
const eng = engV || truncateEnglish(e.entry.e);
const ps = plainTextPsAdjustment(
{ p: e.entry.p, f: removeFVarients(e.entry.f) },

View File

@ -1,37 +1,28 @@
import { compileVP } from "../../lib/phrase-building/compile";
import * as T from "../../types";
import AbbreviationFormSelector from "./AbbreviationFormSelector";
import useStickyState from "../../lib/useStickyState";
import Examples from "../Examples";
import { getObjectSelection, getSubjectSelection } from "../../lib/phrase-building/blocks-utils";
function VPDisplay({ VP, opts, setForm }: {
VP: T.VPSelectionState | T.VPRendered,
opts: T.TextOptions,
setForm: (form: T.FormVersion) => void,
}) {
const [OSV, setOSV] = useStickyState<boolean>(false, "includeOSV");
if (!("type" in VP)) {
return <div className="lead text-muted text-center mt-4">
{(() => {
const twoNPs = (VP.subject === undefined) && (VP.object === undefined);
return `Choose NP${twoNPs ? "s " : ""} to make a phrase`;
const subject = getSubjectSelection(VP.blocks).selection;
const object = getObjectSelection(VP.blocks).selection;
if (subject === undefined || object || undefined) {
return `Choose NP${((subject === undefined) && (object === undefined)) ? "s " : ""} to make a phrase`;
}
return `Choose/remove AP to complete the phrase`;
})()}
</div>;
}
const result = compileVP(VP, { ...VP.form, OSV });
const result = compileVP(VP, { ...VP.form });
return <div className="text-center mt-1">
{VP.verb.transitivity === "transitive" && <div className="form-check mb-2">
<input
className="form-check-input"
type="checkbox"
checked={OSV}
id="OSVCheckbox"
onChange={e => setOSV(e.target.checked)}
/>
<label className="form-check-label text-muted" htmlFor="OSVCheckbox">
Include O S V
</label>
</div>}
<AbbreviationFormSelector
adjustable={VP.whatsAdjustable}
form={VP.form}

View File

@ -7,7 +7,7 @@ import * as T from "../../types";
import ChartDisplay from "./VPChartDisplay";
import useStickyState, { useStickyReducer } from "../../lib/useStickyState";
import { makeVPSelectionState } from "./verb-selection";
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { getKingAndServant, renderVP } from "../../lib/phrase-building/render-vp";
import { completeVPSelection, isPastTense } from "../../lib/phrase-building/vp-tools";
import VPExplorerQuiz from "./VPExplorerQuiz";
@ -16,6 +16,9 @@ import VPExplorerExplanationModal, { roleIcon } from "./VPExplorerExplanationMod
import LZString from "lz-string";
import { vpsReducer } from "./vps-reducer";
import { getShrunkenServant } from "../../lib/phrase-building/compile";
import APPicker from "../ap-picker/APPicker";
import autoAnimate from "@formkit/auto-animate";
import { getObjectSelection, getSubjectSelection, isNoObject } from "../../lib/phrase-building/blocks-utils";
const phraseURLParam = "VPPhrase";
@ -41,7 +44,7 @@ function VPExplorer(props: {
props.loaded
? props.loaded
: savedVps => makeVPSelectionState(props.verb, savedVps),
"vpsState9",
"vpsState12",
flashMessage,
);
const [mode, setMode] = useStickyState<"charts" | "phrases" | "quiz">(
@ -55,6 +58,10 @@ function VPExplorer(props: {
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 parent = useRef<HTMLDivElement>(null);
useEffect(() => {
parent.current && autoAnimate(parent.current);
}, [parent]);
const isPast = isPastTense(vps.verb.tenseCategory === "perfect" ? vps.verb.perfectTense : vps.verb.verbTense);
const roles = getKingAndServant(
isPast,
@ -75,7 +82,6 @@ function VPExplorer(props: {
}, [props.verb]);
useEffect(() => {
const VPSFromUrl = getVPSFromUrl();
console.log({ VPSFromUrl });
if (VPSFromUrl) {
setMode("phrases");
adjustVps({
@ -124,6 +130,8 @@ function VPExplorer(props: {
setShowShareClipped(false);
}, 1250);
}
const object = getObjectSelection(vps.blocks).selection;
const subject = getSubjectSelection(vps.blocks).selection;
const VPS = completeVPSelection(vps);
const phraseIsComplete = !!VPS;
const rendered = VPS ? renderVP(VPS) : undefined;
@ -160,54 +168,57 @@ function VPExplorer(props: {
{mode === "phrases" ? <i className="fas fa-share-alt" /> : ""}
</div>
</div>
{(vps.verb && (typeof vps.object === "object") && (vps.verb.isCompound !== "dynamic") && (vps.verb.tenseCategory !== "imperative") &&(mode === "phrases")) &&
{(vps.verb && (typeof object === "object") && (vps.verb.isCompound !== "dynamic") && (vps.verb.tenseCategory !== "imperative") &&(mode === "phrases")) &&
<div className="text-center my-2">
<button onClick={handleSubjObjSwap} className="btn btn-sm btn-light">
<i className="fas fa-exchange-alt mr-2" /> subj/obj
</button>
</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="clickable h5" onClick={() => adjustVps({ type: "insert new AP" })}>+ AP</div>}
{mode !== "quiz" && <div ref={parent} className="d-flex flex-row justify-content-around flex-wrap" style={{ marginLeft: "-0.5rem", marginRight: "-0.5rem" }}>
{mode === "phrases" && <>
<div className="my-2" style={{ background: (servantIsShrunk && roles.servant === "subject") ? shrunkenBackground : "inherit" }}>
<NPPicker
phraseIsComplete={phraseIsComplete}
heading={roles.king === "subject"
? <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="mx-2 clickable">
{!servantIsShrunk ? "🪄" : "👶"}
</span>
}
</div>}
entryFeeder={props.entryFeeder}
role={(isPast && vps.verb.transitivity !== "intransitive")
? "ergative"
: "subject"
}
is2ndPersonPicker={vps.verb.tenseCategory === "imperative"}
np={vps.subject}
counterPart={vps.verb ? vps.object : undefined}
onChange={handleSubjectChange}
opts={props.opts}
isShrunk={(servantIsShrunk && roles.servant === "subject")}
/>
</div>
{vps.verb && (vps.object !== "none") && <div className="my-2" style={{ background: (servantIsShrunk && roles.servant === "object") ? shrunkenBackground : "inherit" }}>
{(typeof vps.object === "number")
? <div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div>
: <NPPicker
phraseIsComplete={phraseIsComplete}
heading={roles.king === "object"
? <div className="h5 text-center clickable" onClick={() => setShowingExplanation({ role: "king", item: "object" })}>Object {roleIcon.king}</div>
{vps.blocks.map(({ block, key }, i, blocks) => {
if (isNoObject(block)) return null;
return <div className="my-2 card block-card p-1 mx-1" key={key} style={{
background: (servantIsShrunk && (
(roles.servant === "subject" && block?.type === "subjectSelection")
||
(roles.servant === "object" && block?.type === "objectSelection")
)) ? shrunkenBackground : "inherit",
}}>
<div className="d-flex flex-row justify-content-between mb-1" style={{ height: "1rem" }}>
{(i > 0 && !isNoObject(blocks[i - 1].block)) ? <div
className="small clickable ml-1"
onClick={() => adjustVps({ type: "shift block", payload: { index: i, direction: "back" }})}
>
<i className="fas fa-chevron-left" />
</div> : <div/>}
{(i < vps.blocks.length - 1 && !isNoObject(blocks[i + 1].block)) ? <div
className="small clickable mr-1"
onClick={() => adjustVps({ type: "shift block", payload: { index: i, direction: "forward" }})}
>
<i className="fas fa-chevron-right" />
</div> : <div/>}
</div>
{(!block || block.type === "adverb" || block.type === "sandwich")
? <APPicker
phraseIsComplete={phraseIsComplete}
heading="AP"
entryFeeder={props.entryFeeder}
AP={block}
opts={props.opts}
onChange={AP => adjustVps({ type: "set AP", payload: { index: i, AP } })}
onRemove={() => adjustVps({ type: "remove AP", payload: i })}
/>
: (block?.type === "subjectSelection")
? <NPPicker
phraseIsComplete={phraseIsComplete}
heading={roles.king === "subject"
? <div className="h5 text-center" onClick={() => setShowingExplanation({ role: "king", item: "subject" })}>Subject {roleIcon.king}</div>
: <div className="h5 text-center">
Object
Subject
{` `}
<span className="clickable" onClick={() => setShowingExplanation({ role: "servant", item: "object" })}>{roleIcon.servant}</span>
<span className="clickable" onClick={() => setShowingExplanation({ role: "servant", item: "subject" })}>{roleIcon.servant}</span>
{` `}
{(rendered && rendered.whatsAdjustable !== "king") &&
<span onClick={toggleServantShrink} className="mx-2 clickable">
@ -215,15 +226,48 @@ function VPExplorer(props: {
</span>
}
</div>}
entryFeeder={props.entryFeeder}
role="object"
np={vps.object}
counterPart={vps.subject}
onChange={handleObjectChange}
opts={props.opts}
isShrunk={(servantIsShrunk && roles.servant === "object")}
/>}
</div>}
entryFeeder={props.entryFeeder}
np={block.selection}
counterPart={vps.verb ? object : undefined}
role={(isPast && vps.verb.transitivity !== "intransitive")
? "ergative"
: "subject"
}
onChange={handleSubjectChange}
opts={props.opts}
isShrunk={(servantIsShrunk && roles.servant === "subject")}
/>
: (vps.verb && block?.type === "objectSelection" && block.selection !== "none")
? <div className="my-2" style={{ background: (servantIsShrunk && roles.servant === "object") ? shrunkenBackground : "inherit" }}>
{(typeof block.selection === "number")
? <div className="text-muted">Unspoken 3rd Pers. Masc. Plur.</div>
: <NPPicker
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">
Object
{` `}
<span className="clickable" onClick={() => setShowingExplanation({ role: "servant", item: "object" })}>{roleIcon.servant}</span>
{` `}
{(rendered && rendered.whatsAdjustable !== "king") &&
<span onClick={toggleServantShrink} className="mx-2 clickable">
{!servantIsShrunk ? "🪄" : "👶"}
</span>
}
</div>}
entryFeeder={props.entryFeeder}
role="object"
np={block.selection}
counterPart={subject}
onChange={handleObjectChange}
opts={props.opts}
isShrunk={(servantIsShrunk && roles.servant === "object")}
/>}
</div>
: null}
</div>;
})}
</>}
<div className="my-2">
<TensePicker

View File

@ -18,6 +18,7 @@ import energyDrink from "./energy-drink.jpg";
import { flattenLengths } from "../../lib/phrase-building/segment";
import { concatPsString } from "../../lib/p-text-helpers";
import { isImperativeTense } from "../../lib/type-predicates";
import { adjustObjectSelection, adjustSubjectSelection, getObjectSelection, getRenderedObjectSelection, getRenderedSubjectSelection, getSubjectSelection } from "../../lib/phrase-building/blocks-utils";
const correctEmoji = ["✅", '🤓', "✅", '😊', "🌹", "✅", "✅", '🥳', "👏", "✅", "💯", "😎", "✅", "👍"];
@ -97,7 +98,8 @@ function VPExplorerQuiz(props: {
}
}
const rendered = renderVP(quizState.vps);
const { subject, object } = rendered;
const subject = getRenderedSubjectSelection(rendered.blocks).selection;
const object = getRenderedObjectSelection(rendered.blocks).selection;
const { e } = compileVP(rendered, { removeKing: false, shrinkServant: false });
function handleRestart() {
setWithBa(false);
@ -177,9 +179,10 @@ function VPExplorerQuiz(props: {
type="checkbox"
checked={withBa}
onChange={e => setWithBa(e.target.checked)}
id="addBa"
/>
<label className="form-check-label text-muted" htmlFor="OSVCheckbox">
add <InlinePs opts={props.opts}>{baParticle}</InlinePs> in phrase
<label className="form-check-label text-muted" htmlFor="addBa">
add <InlinePs opts={props.opts}>{baParticle}</InlinePs> in kids' section
</label>
</div>
<button type="submit" className="btn btn-primary">
@ -372,11 +375,13 @@ function getOptionFromResult(r: {
}
function completeVPs(vps: T.VPSelectionState): T.VPSelectionComplete {
const oldSubj = vps.subject?.type === "pronoun"
? vps.subject.person
const vpsSubj = getSubjectSelection(vps.blocks).selection;
const vpsObj = getObjectSelection(vps.blocks).selection;
const oldSubj = vpsSubj?.type === "pronoun"
? vpsSubj.person
: undefined;
const oldObj = (typeof vps.object === "object" && vps.object.type === "pronoun")
? vps.object.person
const oldObj = (typeof vpsObj === "object" && vpsObj.type === "pronoun")
? vpsObj.person
: undefined;
const { subj, obj } = randomSubjObj(
oldSubj === undefined
@ -393,29 +398,34 @@ function completeVPs(vps: T.VPSelectionState): T.VPSelectionComplete {
};
return {
...vps,
subject: {
type: "pronoun",
distance: "far",
person: subj,
},
object: (
(typeof vps.object === "object" && !(vps.object.type === "noun" && vps.object.dynamicComplement))
||
vps.object === undefined
)
? {
blocks: adjustObjectSelection(
adjustSubjectSelection(vps.blocks, {
type: "pronoun",
distance: "far",
person: obj,
}
: vps.object,
person: subj,
}),
(
(typeof vpsObj === "object" && !(vpsObj.type === "noun" && vpsObj.dynamicComplement))
||
vpsObj === undefined
)
? {
type: "pronoun",
distance: "far",
person: obj,
}
: vpsObj,
),
verb,
};
}
function getRandomVPSelection(mix: MixType = "both") {
// TODO: Type safety to make sure it's safe?
return ({ subject, verb, object }: T.VPSelectionComplete): T.VPSelectionComplete => {
return (VPS: T.VPSelectionComplete): T.VPSelectionComplete => {
const subject = getSubjectSelection(VPS.blocks).selection;
const object = getObjectSelection(VPS.blocks).selection;
const verb = VPS.verb;
const oldSubj = (subject.type === "pronoun")
? subject.person
: undefined;
@ -444,27 +454,39 @@ function getRandomVPSelection(mix: MixType = "both") {
// ensure that the verb selection is complete
if (mix === "tenses") {
return {
subject: subject !== undefined ? subject : randSubj,
object: object !== undefined ? object : randObj,
blocks: possibleShuffleArray(adjustObjectSelection(
adjustSubjectSelection(VPS.blocks, subject !== undefined ? subject : randSubj),
object !== undefined ? object : randObj,
)),
verb: randomizeTense(verb, true),
form: { removeKing: false, shrinkServant: false },
}
}
return {
subject: randSubj,
object: (
(typeof object === "object" && !(object.type === "noun" && object.dynamicComplement))
||
object === undefined
)
? randObj
: object,
blocks: possibleShuffleArray(adjustObjectSelection(
adjustSubjectSelection(VPS.blocks, randSubj),
(
(typeof object === "object" && !(object.type === "noun" && object.dynamicComplement))
||
object === undefined
)
? randObj
: object,
)),
verb: randomizeTense(verb, true),
form: { removeKing: false, shrinkServant: false },
};
};
};
function possibleShuffleArray<X>(arr: X[]): X[] {
const willShuffle = randFromArray([true, false, false]);
if (willShuffle) {
return shuffleArray(arr);
}
return arr;
}
function randomizeTense(verb: T.VerbSelectionComplete, dontRepeatTense: boolean): T.VerbSelectionComplete {
return {
...verb,

View File

@ -3,6 +3,7 @@ import {
} from "../np-picker/picker-tools";
import * as T from "../../types";
import { getVerbInfo } from "../../lib/verb-info";
import { adjustObjectSelection, getObjectSelection, getSubjectSelection, makeObjectSelection, makeSubjectSelection } from "../../lib/phrase-building/blocks-utils";
export function makeVPSelectionState(
verb: T.VerbEntry,
@ -11,16 +12,17 @@ export function makeVPSelectionState(
const info = getVerbInfo(verb.entry, verb.complement);
const subject = (os?.verb.voice === "passive" && info.type === "dynamic compound")
? makeNounSelection(info.objComplement.entry as T.NounEntry, undefined, true)
: (os?.subject || undefined);
: (os?.blocks ? getSubjectSelection(os.blocks) : undefined);
function getTransObjFromos() {
const osObj = os ? getObjectSelection(os.blocks).selection : undefined;
if (
!os ||
os.object === "none" ||
typeof os.object === "number" ||
osObj === "none" ||
typeof osObj === "number" ||
os.verb.isCompound === "dynamic" ||
(os.object?.type === "noun" && os.object.dynamicComplement)
(osObj?.type === "noun" && osObj.dynamicComplement)
) return undefined;
return os.object;
return osObj;
}
const transitivity: T.Transitivity = "grammaticallyTransitive" in info
? "transitive"
@ -45,9 +47,12 @@ export function makeVPSelectionState(
: "dynamic" in info
? { entry: info.dynamic.auxVerb } as T.VerbEntry
: undefined;
const blocks = [
{ key: Math.random(), block: makeSubjectSelection(subject) },
{ key: Math.random(), block: makeObjectSelection(object) },
];
return {
subject,
object,
blocks,
verb: {
type: "verb",
verb: verb,
@ -77,9 +82,12 @@ export function changeStatDyn(v: T.VPSelectionState, s: "dynamic" | "stative"):
}
return {
...v,
object: s === "dynamic"
? makeNounSelection(info.dynamic.objComplement.entry as T.NounEntry, undefined, true)
: undefined,
blocks: adjustObjectSelection(
v.blocks,
s === "dynamic"
? makeNounSelection(info.dynamic.objComplement.entry as T.NounEntry, undefined, true)
: undefined,
),
verb: {
...v.verb,
isCompound: s,
@ -93,7 +101,7 @@ export function changeStatDyn(v: T.VPSelectionState, s: "dynamic" | "stative"):
export function changeTransitivity(v: T.VPSelectionState, transitivity: "transitive" | "grammatically transitive"): T.VPSelectionState {
return {
...v,
object: transitivity === "grammatically transitive" ? T.Person.ThirdPlurMale : undefined,
blocks: adjustObjectSelection(v.blocks, transitivity === "grammatically transitive" ? T.Person.ThirdPlurMale : undefined),
verb: {
...v.verb,
transitivity,

View File

@ -13,6 +13,7 @@ import { checkForMiniPronounsError } from "../../lib/phrase-building/compile";
import {
makeVPSelectionState,
} from "./verb-selection";
import { adjustObjectSelection, adjustSubjectSelection, getObjectSelection, getSubjectSelection, insertNewAP, removeAP, setAP, shiftBlock } from "../../lib/phrase-building/blocks-utils";
export type VpsReducerAction = {
type: "load vps",
@ -54,7 +55,24 @@ export type VpsReducerAction = {
} | {
type: "set verb",
payload: T.VerbEntry,
}
} | {
type: "insert new AP",
} | {
type: "set AP",
payload: {
index: number,
AP: T.APSelection | undefined,
},
} | {
type: "remove AP",
payload: number,
} | {
type: "shift block",
payload: {
index: number,
direction: "back" | "forward",
},
};
export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, sendAlert?: (msg: string) => void): T.VPSelectionState {
return ensureMiniPronounsOk(vps, doReduce());
@ -68,38 +86,40 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, se
}
function doReduce(): T.VPSelectionState {
if (action.type === "load vps") {
console.log("doing loading", action.payload);
return action.payload;
}
if (action.type === "set subject") {
const { subject, skipPronounConflictCheck } = action.payload;
const object = getObjectSelection(vps.blocks).selection;
if (
!skipPronounConflictCheck
&&
hasPronounConflict(subject, vps.object)
hasPronounConflict(subject, object)
) {
if (sendAlert) sendAlert("That combination of pronouns is not allowed");
return vps;
}
return {
...vps,
subject: action.payload.subject,
blocks: adjustSubjectSelection(vps.blocks, action.payload.subject),
};
}
if (action.type === "set object") {
if (!vps.verb) return vps;
if ((vps.object === "none") || (typeof vps.object === "number")) {
const objectB = getObjectSelection(vps.blocks).selection;
const subjectB = getSubjectSelection(vps.blocks).selection;
if ((objectB === "none") || (typeof objectB === "number")) {
return vps;
}
const object = action.payload;
// check for pronoun conflict
if (hasPronounConflict(vps.subject, object)) {
if (hasPronounConflict(subjectB, object)) {
if (sendAlert) sendAlert("That combination of pronouns is not allowed");
return vps;
}
return {
...vps,
object,
blocks: adjustObjectSelection(vps.blocks, object),
};
}
if (action.type === "swap subj/obj") {
@ -114,6 +134,8 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, se
}
if (action.type === "set voice") {
if (vps.verb && vps.verb.canChangeVoice) {
const subject = getSubjectSelection(vps.blocks).selection;
const object = getObjectSelection(vps.blocks).selection;
const voice = action.payload;
if (voice === "passive" && vps.verb.tenseCategory === "imperative") {
return vps;
@ -121,8 +143,10 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, se
if (voice === "passive") {
return {
...vps,
subject: typeof vps.object === "object" ? vps.object : undefined,
object: "none",
blocks: adjustObjectSelection(
adjustSubjectSelection(vps.blocks, typeof object === "object" ? object : undefined),
"none",
),
verb: {
...vps.verb,
voice,
@ -131,8 +155,10 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, se
} else {
return {
...vps,
subject: undefined,
object: typeof vps.subject === "object" ? vps.subject : undefined,
blocks: adjustObjectSelection(
adjustSubjectSelection(vps.blocks, undefined),
typeof subject === "object" ? subject : undefined,
),
verb: {
...vps.verb,
voice,
@ -225,9 +251,36 @@ export function vpsReducer(vps: T.VPSelectionState, action: VpsReducerAction, se
},
};
}
// if (action.type === "set verb") {
if (action.type === "set verb") {
return makeVPSelectionState(action.payload, vps);
// }
}
if (action.type === "insert new AP") {
return {
...vps,
blocks: insertNewAP(vps.blocks),
};
}
if (action.type === "set AP") {
const { index, AP } = action.payload;
return {
...vps,
blocks: setAP(vps.blocks, index, AP),
};
}
if (action.type === "remove AP") {
return {
...vps,
blocks: removeAP(vps.blocks, action.payload),
};
}
if (action.type === "shift block") {
const { index, direction } = action.payload;
return {
...vps,
blocks: shiftBlock(vps.blocks, index, direction),
};
}
throw new Error("unknown vpsReducer state");
}
}

View File

@ -54,8 +54,8 @@ export function randomSubjObj(old?: { subj: T.Person, obj?: T.Person }): { subj:
}
export function getEnglishVerb(entry: T.DictionaryEntry): string {
if (!entry.ec) {
console.log("errored verb");
console.log(entry);
console.error("errored verb");
console.error(entry);
throw new Error("no english information for verb");
}
if (entry.ep) {

View File

@ -1,8 +1,8 @@
import * as T from "../../types";
export function getSubjectSelection(blocks: T.EPSBlockComplete[]): T.SubjectSelectionComplete;
export function getSubjectSelection(blocks: T.EPSBlock[]): T.SubjectSelection;
export function getSubjectSelection(blocks: T.EPSBlock[] | T.EPSBlockComplete[]): T.SubjectSelection | T.SubjectSelectionComplete {
export function getSubjectSelection(blocks: T.EPSBlockComplete[] | T.VPSBlockComplete[]): T.SubjectSelectionComplete;
export function getSubjectSelection(blocks: T.EPSBlock[] | T.VPSBlock[]): T.SubjectSelection;
export function getSubjectSelection(blocks: T.EPSBlock[] | T.EPSBlockComplete[] | T.VPSBlockComplete[] | T.VPSBlock[]): T.SubjectSelection | T.SubjectSelectionComplete {
const b = blocks.find(f => f.block?.type === "subjectSelection");
if (!b || !b.block || b.block.type !== "subjectSelection") {
throw new Error("subjectSelection not found in blocks");
@ -10,14 +10,33 @@ export function getSubjectSelection(blocks: T.EPSBlock[] | T.EPSBlockComplete[])
return b.block;
}
export function getRenderedSubjectSelection(blocks: (T.Rendered<T.SubjectSelectionComplete> | T.Rendered<T.APSelection>)[]): T.Rendered<T.SubjectSelectionComplete> {
const b = blocks.find(f => f.type === "subjectSelection");
if (!b || b.type !== "subjectSelection") {
export function getObjectSelection(blocks: T.VPSBlockComplete[]): T.ObjectSelectionComplete;
export function getObjectSelection(blocks: T.VPSBlock[]): T.ObjectSelection;
export function getObjectSelection(blocks: T.VPSBlock[] | T.VPSBlockComplete[]): T.ObjectSelection | T.ObjectSelectionComplete {
const b = blocks.find(f => f.block?.type === "objectSelection");
if (!b || !b.block || b.block.type !== "objectSelection") {
throw new Error("objectSelection not found in blocks");
}
return b.block;
}
export function getRenderedSubjectSelection(blocks: (T.Rendered<T.SubjectSelectionComplete> | T.Rendered<T.NPSelection> | T.Rendered<T.APSelection> | T.RenderedVPSBlock)[]): T.Rendered<T.SubjectSelectionComplete> {
const b = blocks.find(f => typeof f === "object" && f.type === "subjectSelection");
if (!b || typeof b !== "object" || b.type !== "subjectSelection") {
throw new Error("subjectSelection not found in blocks");
}
return b;
}
export function getRenderedObjectSelection(blocks: T.RenderedVPSBlock[]): T.Rendered<T.ObjectSelectionComplete> {
const b = blocks.find(f => typeof f === "object" && f.type === "objectSelection");
if (!b || typeof b !== "object" || b.type !== "objectSelection") {
throw new Error("objectSelection not found in blocks");
}
return b;
}
export function makeEPSBlocks(): T.EPSBlock[] {
return [
{
@ -30,20 +49,33 @@ export function makeEPSBlocks(): T.EPSBlock[] {
];
}
export function makeAPBlock(): T.EPSBlock {
export function makeAPBlock(): { key: number, block: undefined } {
return {
key: Math.random(),
block: undefined,
};
}
export function makeSubjectSelection(selection: T.NPSelection | undefined): T.SubjectSelection {
export function makeSubjectSelection(selection: T.SubjectSelection | T.NPSelection | undefined): T.SubjectSelection {
if (selection?.type === "subjectSelection") {
return selection;
}
return {
type: "subjectSelection",
selection,
};
}
export function makeObjectSelection(selection: T.ObjectSelection | T.ObjectNP | T.NPSelection | undefined): T.ObjectSelection {
if (selection && typeof selection === "object" && selection.type === "objectSelection") {
return selection;
}
return {
type: "objectSelection",
selection: selection,
};
}
export function EPSBlocksAreComplete(blocks: T.EPSBlock[]): blocks is T.EPSBlockComplete[] {
if (blocks.some(block => block.block === undefined)) {
return false;
@ -52,9 +84,20 @@ export function EPSBlocksAreComplete(blocks: T.EPSBlock[]): blocks is T.EPSBlock
return !!subject.selection;
}
export function adjustSubjectSelection(blocks: T.EPSBlock[], subject: T.SubjectSelectionComplete | T.NPSelection): T.EPSBlockComplete[];
export function adjustSubjectSelection(blocks: T.EPSBlock[], subject: T.SubjectSelection | undefined): T.EPSBlock[];
export function adjustSubjectSelection(blocks: T.EPSBlock[], subject: T.SubjectSelection | T.SubjectSelectionComplete | T.NPSelection | undefined): T.EPSBlock[] | T.EPSBlockComplete[] {
export function VPSBlocksAreComplete(blocks: T.VPSBlock[]): blocks is T.VPSBlockComplete[] {
if (blocks.some(block => block.block === undefined)) {
return false;
}
const subject = getSubjectSelection(blocks);
if (!subject.selection) return false;
const object = getObjectSelection(blocks);
if (!object.selection) return false;
return true;
}
export function adjustSubjectSelection(blocks: T.EPSBlock[], subject: T.SubjectSelection | T.NPSelection | undefined): T.EPSBlock[];
export function adjustSubjectSelection(blocks: T.VPSBlock[], subject: T.SubjectSelection | T.NPSelection | undefined): T.VPSBlock[];
export function adjustSubjectSelection(blocks: T.VPSBlock[] | T.EPSBlock[], subject: T.SubjectSelection | T.NPSelection | undefined): T.VPSBlock[] | T.EPSBlock[] {
const nb = [...blocks];
const i = nb.findIndex(b => b.block && b.block.type === "subjectSelection");
if (i === -1) {
@ -62,4 +105,57 @@ export function adjustSubjectSelection(blocks: T.EPSBlock[], subject: T.SubjectS
}
nb[i].block = subject?.type === "subjectSelection" ? subject : makeSubjectSelection(subject);
return nb;
}
}
export function adjustObjectSelection(blocks: T.VPSBlock[], object: T.ObjectSelectionComplete | T.NPSelection | T.VerbObject | T.ObjectSelectionComplete): T.VPSBlockComplete[];
export function adjustObjectSelection(blocks: T.VPSBlock[], object: T.ObjectSelection | T.NPSelection | T.VerbObject | T.ObjectSelection | undefined): T.EPSBlock[];
export function adjustObjectSelection(blocks: T.VPSBlock[], object: T.ObjectSelection | T.ObjectSelectionComplete | T.VerbObject | T.NPSelection | undefined): T.VPSBlock[] | T.VPSBlockComplete[] {
const nb = [...blocks];
const i = nb.findIndex(b => b.block && b.block.type === "objectSelection");
if (i === -1) {
throw new Error("couldn't find objectSelection to modify");
}
nb[i].block = typeof object === "object" && object?.type === "objectSelection"
? object
: makeObjectSelection(object);
return nb;
}
export function shiftBlock<B extends T.VPSBlock[] | T.EPSBlock[]>(blocks: B, index: number, direction: "back" | "forward"): B {
const newIndex = index + (direction === "forward"
? 1 // (isNoObject(blocks[index + 1].block) ? 2 : 1)
: -1 // (isNoObject(blocks[index - 1].block) ? -2 : -2)
);
return arrayMove(blocks, index, newIndex) as B;
}
export function insertNewAP<B extends T.VPSBlock[] | T.EPSBlock[]>(blocks: B): B {
return [makeAPBlock(), ...blocks] as B;
}
export function setAP<B extends T.VPSBlock[] | T.EPSBlock[]>(blocks: B, index: number, AP: T.APSelection | undefined): B {
const nBlocks = [...blocks] as B;
nBlocks[index].block = AP;
return nBlocks;
}
export function removeAP<B extends T.VPSBlock[] | T.EPSBlock[]>(blocks: B, index: number): B {
const nBlocks = [...blocks] as B;
blocks.splice(index, 1);
return nBlocks;
}
export function isNoObject(b: T.VPSBlock["block"] | T.EPSBlock["block"]): b is { type: "objectSelection", selection: "none" } {
return !!(b && b.type === "objectSelection" && b.selection === "none");
}
function arrayMove<X>(ar: X[], old_index: number, new_index: number): X[] {
const arr = [...ar];
const new_i = (new_index >= arr.length)
? (arr.length - 1)
: (new_index < 0)
? 0
: new_index;
arr.splice(new_i, 0, arr.splice(old_index, 1)[0]);
return arr;
};

View File

@ -25,7 +25,7 @@ import { pronouns } from "../grammar-units";
import { completeEPSelection, renderEP } from "./render-ep";
import { completeVPSelection } from "./vp-tools";
import { renderVP } from "./render-vp";
import { getRenderedSubjectSelection } from "./blocks-utils";
import { getRenderedObjectSelection, getRenderedSubjectSelection } from "./blocks-utils";
const blank: T.PsString = {
p: "______",
@ -35,14 +35,13 @@ type BlankoutOptions = { equative?: boolean, ba?: boolean, kidsSection?: boolean
const kidsBlank = makeSegment({ p: "___", f: "___" }, ["isKid"]);
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 [] } {
export function compileVP(VP: T.VPRendered, form: T.FormVersion): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string [] };
export function compileVP(VP: T.VPRendered, form: T.FormVersion, combineLengths: true): { ps: T.PsString[], e?: string [] };
export function compileVP(VP: T.VPRendered, form: T.FormVersion, combineLengths?: true): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string [] } {
const verb = VP.verb.ps;
const { kids, NPs } = getVPSegmentsAndKids(VP, form);
const { kids, blocks } = getVPSegmentsAndKids(VP, form);
const psResult = compilePs({
NPs,
blocks,
kids,
verb,
VP,
@ -50,12 +49,12 @@ export function compileVP(VP: T.VPRendered, form: Form, combineLengths?: true):
return {
ps: combineLengths ? flattenLengths(psResult) : psResult,
// TODO: English doesn't quite work for dynamic compounds in passive voice
e: (VP.verb.voice === "passive" && VP.isCompound === "dynamic") ? undefined : compileEnglish(VP),
e: (VP.verb.voice === "passive" && VP.isCompound === "dynamic") ? undefined : compileEnglishVP(VP),
};
}
type CompilePsInput = {
NPs: Segment[][],
blocks: Segment[],
kids: Segment[],
verb: {
head: T.PsString | undefined,
@ -64,13 +63,13 @@ type CompilePsInput = {
VP: T.VPRendered,
}
function compilePs({ NPs, kids, verb: { head, rest }, VP }: CompilePsInput): T.SingleOrLengthOpts<T.PsString[]> {
function compilePs({ blocks, kids, verb: { head, rest }, VP }: CompilePsInput): T.SingleOrLengthOpts<T.PsString[]> {
if ("long" in rest) {
return {
long: compilePs({ NPs, verb: { head, rest: rest.long }, VP, kids }) as T.PsString[],
short: compilePs({ NPs, verb: { head, rest: rest.short }, VP, kids }) as T.PsString[],
long: compilePs({ blocks, verb: { head, rest: rest.long }, VP, kids }) as T.PsString[],
short: compilePs({ blocks, verb: { head, rest: rest.short }, VP, kids }) as T.PsString[],
...rest.mini ? {
mini: compilePs({ NPs, verb: { head, rest: rest.mini }, VP, kids }) as T.PsString[],
mini: compilePs({ blocks, verb: { head, rest: rest.mini }, VP, kids }) as T.PsString[],
} : {},
};
}
@ -78,55 +77,62 @@ function compilePs({ NPs, kids, verb: { head, rest }, VP }: CompilePsInput): T.S
// put together all the different possible permutations based on:
// a. potential different versions of where the nu goes
return removeDuplicates(verbWNegativeVersions.flatMap((verbSegments) => (
// b. potential reordering of NPs
NPs.flatMap(NP => {
// for each permutation of the possible ordering of NPs and Verb + nu
// 1. put in kids in the kids section
const segments = putKidsInKidsSection([...NP, ...verbSegments], kids);
// 2. space out the words properly
const withProperSpaces = addSpacesBetweenSegments(segments);
// 3. throw it all together into a PsString for each permutation
return combineSegments(withProperSpaces);
})
)));
return removeDuplicates(verbWNegativeVersions.flatMap((verbSegments) => {
// for each permutation of the possible ordering of NPs and Verb + nu
// 1. put in kids in the kids section
const segments = putKidsInKidsSection([...blocks, ...verbSegments], kids);
// 2. space out the words properly
const withProperSpaces = addSpacesBetweenSegments(segments);
// 3. throw it all together into a PsString for each permutation
return combineSegments(withProperSpaces);
}));
}
export function getShrunkenServant(VP: T.VPRendered): Segment | undefined {
const shrinkServant = VP.form.shrinkServant && !(VP.isCompound === "dynamic" && !VP.isPast);
const toShrinkServant = (() => {
const toShrinkServant: T.Rendered<T.NPSelection> | undefined = (() => {
if (!shrinkServant) return undefined;
if (!VP.servant) return undefined;
const servant = VP[VP.servant];
const servant = VP.servant === "subject"
? getRenderedSubjectSelection(VP.blocks).selection
: getRenderedObjectSelection(VP.blocks).selection;
if (typeof servant !== "object") return undefined;
return servant;
})();
return toShrinkServant ? shrinkNP(toShrinkServant) : undefined;
}
export function getVPSegmentsAndKids(VP: T.VPRendered, form?: Form): { kids: Segment[], NPs: Segment[][] } {
export function getVPSegmentsAndKids(VP: T.VPRendered, form?: T.FormVersion): { kids: Segment[], blocks: 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, 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)
? (!shrunkenServant ? SO[t] : undefined)
: (VP.king === t)
? (!removeKing ? SO[t] : undefined)
: undefined;
if (!word) return undefined;
return makeSegment(word);
}
const subject = getSegment("subject");
const object = getSegment("object");
const subject = getRenderedSubjectSelection(VP.blocks).selection;
const blocks = VP.blocks.reduce((accum, block) => {
if (block.type === "subjectSelection") {
if (VP.servant === "subject" && shrunkenServant) return accum;
if (VP.king === "subject" && removeKing) return accum;
return [
...accum,
makeSegment(getPashtoFromRendered(block.selection, false)),
];
}
if (block.type === "objectSelection") {
if (VP.servant === "object" && shrunkenServant) return accum;
if (VP.king === "object" && removeKing) return accum;
if (typeof block.selection !== "object") return accum;
return [
...accum,
makeSegment(getPashtoFromRendered(block.selection, subject.person)),
]
}
return [
...accum,
makeSegment(getPashtoFromRendered(block, false)),
];
}, [] as Segment[]);
return {
kids: orderKidsSection([
...VP.verb.hasBa
@ -135,14 +141,37 @@ export function getVPSegmentsAndKids(VP: T.VPRendered, form?: Form): { kids: Seg
? [shrunkenServant] : [],
...possToShrink.map(shrinkNP),
]),
NPs: [
[
...subject ? [subject] : [],
...object ? [object] : [],
],
// TODO: make this an option to also include O S V order ??
// also show O S V if both are showing
...(subject && object && (form && form.OSV)) ? [[object, subject]] : [],
blocks: blocks,
};
}
export function getEPSegmentsAndKids(EP: T.EPRendered, blankOut?: BlankoutOptions): { kids: Segment[], blocks: Segment[] } {
const possToShrink = findPossesivesToShrinkInEP(EP);
const blocks = EP.blocks.reduce((accum, block) => {
if (block.type === "subjectSelection") {
if (EP.omitSubject) return accum;
return [
...accum,
makeSegment(getPashtoFromRendered(block.selection, false)),
];
}
return [
...accum,
makeSegment(getPashtoFromRendered(block, false)),
];
}, [] as Segment[]);
const predicate = makeSegment(getPashtoFromRendered(EP.predicate, false));
return {
kids: blankOut?.kidsSection ? [kidsBlank] : orderKidsSection([
...EP.equative.hasBa
? (
blankOut?.ba ? [kidsBlank] : [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])])
: [],
...possToShrink.map(a => shrinkNP(a.np)),
]),
blocks: [
...blocks,
predicate
],
};
}
@ -263,65 +292,34 @@ 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, blankOut?: BlankoutOptions): { ps: T.PsString[], e?: string[] };
export function compileEP(EP: T.EPRendered, combineLengths?: boolean, blankOut?: BlankoutOptions): { ps: T.SingleOrLengthOpts<T.PsString[]>, e?: string[] } {
const { kids, NPs } = getEPSegmentsAndKids(EP, blankOut);
const { kids, blocks } = getEPSegmentsAndKids(EP, blankOut);
const equative = EP.equative.ps;
const psResult = compileEPPs({
NPs,
blocks,
kids,
equative: blankOut?.equative ? [blank] : equative,
negative: EP.equative.negative,
});
return {
ps: combineLengths ? flattenLengths(psResult) : psResult,
e: compileEPEnglish(EP),
e: compileEnglishEP(EP),
};
}
export function getEPSegmentsAndKids(EP: T.EPRendered, blankOut?: BlankoutOptions): { kids: Segment[], NPs: Segment[] } {
const possToShrink = findPossesivesToShrinkInEP(EP);
const blocks = EP.blocks.reduce((accum, block) => {
if (block.type === "subjectSelection") {
if (EP.omitSubject) return accum;
return [
...accum,
makeSegment(getPashtoFromRendered(block.selection, false)),
];
}
return [
...accum,
makeSegment(getPashtoFromRendered(block, false)),
];
}, [] as Segment[]);
const predicate = makeSegment(getPashtoFromRendered(EP.predicate, false));
return {
kids: blankOut?.kidsSection ? [kidsBlank] : orderKidsSection([
...EP.equative.hasBa
? (
blankOut?.ba ? [kidsBlank] : [makeSegment(grammarUnits.baParticle, ["isBa", "isKid"])])
: [],
...possToShrink.map(a => shrinkNP(a.np)),
]),
NPs: [
...blocks,
predicate
],
};
}
function compileEPPs({ NPs, kids, equative, negative }: {
NPs: Segment[],
function compileEPPs({ blocks, kids, equative, negative }: {
blocks: 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[],
long: compileEPPs({ blocks, kids, equative: equative.long, negative }) as T.PsString[],
short: compileEPPs({ blocks, kids, equative: equative.short, negative }) as T.PsString[],
};
}
const allSegments = putKidsInKidsSection([
...NPs,
...blocks,
...negative ? [
makeSegment({ p: "نه", f: "nú" }),
makeSegment(removeAccents(equative))
@ -332,25 +330,7 @@ function compileEPPs({ NPs, kids, equative, negative }: {
return removeDuplicates(combineSegments(allSegments, "spaces"));
}
function compileEPEnglish(EP: T.EPRendered): string[] | undefined {
function insertEWords(e: string, { subject, predicate, APs }: { subject: string, predicate: string, APs: string }): string {
return e.replace("$SUBJ", subject).replace("$PRED", predicate || "") + APs;
}
const engSubj = getRenderedSubjectSelection(EP.blocks).selection.e;
const engPred = getEnglishFromRendered(EP.predicate);
const engAPs = getEnglishAPs(EP.blocks);
// require all English parts for making the English phrase
const b = (EP.englishBase && engSubj && engPred)
? EP.englishBase.map(e => insertEWords(e, {
subject: engSubj,
predicate: engPred,
APs: engAPs,
}))
: undefined;
return b;
}
function getEnglishAPs(blocks: (T.Rendered<T.SubjectSelectionComplete> | T.Rendered<T.APSelection>)[]) {
function getEnglishAPs(blocks: (T.Rendered<T.SubjectSelectionComplete> | T.Rendered<T.APSelection> | T.RenderedVPSBlock)[]) {
const APs = blocks.filter(x => x.type !== "subjectSelection") as T.Rendered<T.APSelection>[];
return APs.reduce((accum, curr) => {
const e = getEnglishFromRendered(curr);
@ -398,23 +378,42 @@ function addSpacesBetweenSegments(segments: Segment[]): (Segment | " " | "" | T.
return o;
}
function compileEnglish(VP: T.VPRendered): string[] | undefined {
function insertEWords(e: string, { subject, object }: { subject: string, object?: string }): string {
return e.replace("$SUBJ", subject).replace("$OBJ", object || "");
function compileEnglishVP(VP: T.VPRendered): string[] | undefined {
function insertEWords(e: string, { subject, object, APs }: { subject: string, object?: string, APs: string }): string {
return e.replace("$SUBJ", subject).replace("$OBJ", object || "") + APs;
}
const engSubj = getEnglishFromRendered(VP.subject);
const engObj = typeof VP.object === "object"
? getEnglishFromRendered(VP.object)
: undefined;
const engSubj = getRenderedSubjectSelection(VP.blocks).selection;
const obj = getRenderedObjectSelection(VP.blocks).selection;
const engObj = typeof obj === "object" ? obj.e : "";
const engAPs = getEnglishAPs(VP.blocks);
// require all English parts for making the English phrase
return (VP.englishBase && engSubj && (engObj || typeof VP.object !== "object"))
return (VP.englishBase && engSubj && engObj !== undefined)
? VP.englishBase.map(e => insertEWords(e, {
subject: engSubj,
object: engObj,
// TODO: make sure we actually have the english
subject: getEnglishFromRendered(engSubj) || "",
object: getEnglishFromRendered(engSubj) || "",
APs: engAPs,
}))
: undefined;
}
function compileEnglishEP(EP: T.EPRendered): string[] | undefined {
function insertEWords(e: string, { subject, predicate, APs }: { subject: string, predicate: string, APs: string }): string {
return e.replace("$SUBJ", subject).replace("$PRED", predicate || "") + APs;
}
const engSubj = getRenderedSubjectSelection(EP.blocks).selection.e;
const engPred = getEnglishFromRendered(EP.predicate);
const engAPs = getEnglishAPs(EP.blocks);
// require all English parts for making the English phrase
const b = (EP.englishBase && engSubj && engPred)
? EP.englishBase.map(e => insertEWords(e, {
subject: engSubj,
predicate: engPred,
APs: engAPs,
}))
: undefined;
return b;
}
export function orderKidsSection(kids: Segment[]): Segment[] {
const sorted = [...kids];
@ -472,19 +471,34 @@ 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 = findPossesivesInNP(curr);
if (res) return [...accum, ...res];
return accum;
return VP.blocks.reduce((found, block) => {
if (block.type === "subjectSelection") {
if (block.selection.role === "king" && f.removedKing) return found;
if (block.selection.role === "servant" && f.shrunkServant) return found;
return [
...findPossesivesInNP(block.selection),
...found,
];
}
if (block.type === "objectSelection") {
if (typeof block.selection !== "object") return found;
if (block.selection.role === "king" && f.removedKing) return found;
if (block.selection.role === "servant" && f.shrunkServant) return found;
return [
...findPossesivesInNP(block.selection),
...found,
];
}
if (block.type === "sandwich") {
if (block.inside.type === "pronoun") {
return found;
}
return [
...findPossesivesInNP(block.inside),
...found,
];
}
return found;
}, [] as T.Rendered<T.NPSelection>[]);
}

View File

@ -206,7 +206,6 @@ export function getEnglishFromRendered(r: T.Rendered<T.NPSelection | T.EqCompSel
const np = r as T.Rendered<T.NounSelection>;
return addPossesors(np.possesor?.np, addArticlesAndAdjs(np), r.type);
}
// TODO: possesives in English for participles and pronouns too!
return r.e;
}

View File

@ -84,10 +84,9 @@ function renderEquative(es: T.EquativeSelection, person: T.Person): T.EquativeRe
};
}
function renderAdverbSelection(a: T.AdverbSelection): T.Rendered<T.AdverbSelection> {
export function renderAdverbSelection(a: T.AdverbSelection): T.Rendered<T.AdverbSelection> {
const e = getEnglishWord(a.entry);
if (!e || typeof e !== "string") {
console.log(e);
throw new Error("error getting english for compliment");
}
return {
@ -104,7 +103,6 @@ function renderEqCompSelection(s: T.EqCompSelection, person: T.Person): T.Render
}
const e = getEnglishWord(s.entry);
if (!e || typeof e !== "string") {
console.log(e);
throw new Error("error getting english for compliment");
}
if (isLocativeAdverbEntry(s.entry)) {

View File

@ -22,26 +22,31 @@ import {
import { renderEnglishVPBase } from "./english-vp-rendering";
import { personGender } from "../../lib/misc-helpers";
import { renderNPSelection } from "./render-np";
import { getObjectSelection, getSubjectSelection } from "./blocks-utils";
import { renderSandwich } from "./render-sandwich";
import { renderAdverbSelection } from "./render-ep";
// TODO: ISSUE GETTING SPLIT HEAD NOT MATCHING WITH FUTURE VERBS
export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
const subject = getSubjectSelection(VP.blocks).selection;
const object = getObjectSelection(VP.blocks).selection;
// Sentence Rules Logic
const isPast = isPastTense(VP.verb.tense);
const isTransitive = VP.object !== "none";
const isTransitive = object !== "none";
const { king, servant } = getKingAndServant(isPast, isTransitive);
const kingPerson = getPersonFromNP(
king === "subject" ? VP.subject : VP.object,
king === "subject" ? subject : object,
);
// TODO: more elegant way of handling this type safety
if (kingPerson === undefined) {
throw new Error("king of sentance does not exist");
}
const subjectPerson = getPersonFromNP(VP.subject);
const objectPerson = getPersonFromNP(VP.object);
const subjectPerson = getPersonFromNP(subject);
const objectPerson = getPersonFromNP(object);
// TODO: also don't inflect if it's a pattern one animate noun
const inflectSubject = isPast && isTransitive && !isMascSingAnimatePattern4(VP.subject);
const inflectObject = !isPast && isFirstOrSecondPersPronoun(VP.object);
const inflectSubject = isPast && isTransitive && !isMascSingAnimatePattern4(subject);
const inflectObject = !isPast && isFirstOrSecondPersPronoun(object);
// Render Elements
const b: T.VPRendered = {
type: "VPRendered",
@ -50,12 +55,15 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
isPast,
isTransitive,
isCompound: VP.verb.isCompound,
subject: renderNPSelection(VP.subject, inflectSubject, false, "subject", king === "subject" ? "king" : "servant"),
object: renderNPSelection(VP.object, inflectObject, true, "object", king === "object" ? "king" : "servant"),
blocks: renderVPBlocks(VP.blocks, {
inflectSubject,
inflectObject,
king,
}),
verb: renderVerbSelection(VP.verb, kingPerson, objectPerson),
englishBase: renderEnglishVPBase({
subjectPerson,
object: VP.verb.isCompound === "dynamic" ? "none" : VP.object,
object: VP.verb.isCompound === "dynamic" ? "none" : object,
vs: VP.verb,
}),
form: VP.form,
@ -64,6 +72,35 @@ export function renderVP(VP: T.VPSelectionComplete): T.VPRendered {
return b;
}
function renderVPBlocks(blocks: T.VPSBlockComplete[], config: {
inflectSubject: boolean,
inflectObject: boolean,
king: "subject" | "object",
}): T.RenderedVPSBlock[] {
return blocks.map(({ block }): T.RenderedVPSBlock => {
if (block.type === "sandwich") {
return renderSandwich(block);
}
if (block.type === "adverb") {
return renderAdverbSelection(block);
}
if (block.type === "subjectSelection") {
return {
type: "subjectSelection",
selection: renderNPSelection(block.selection, config.inflectSubject, false, "subject", config.king === "subject" ? "king" : "servant"),
}
}
// if (block.type === "objectSelection") {
const object = typeof block === "object" ? block.selection : block;
const selection = renderNPSelection(object, config.inflectObject, true, "object", config.king === "object" ? "king" : "servant");
return {
type: "objectSelection",
selection,
};
// }
});
}
function whatsAdjustable(VP: T.VPSelectionComplete): "both" | "king" | "servant" {
// TODO: intransitive dynamic compounds?
return (VP.verb.isCompound === "dynamic" && VP.verb.transitivity === "transitive")

View File

@ -7,6 +7,7 @@ import {
import { isImperativeTense, isPerfectTense } from "../type-predicates";
import * as grammarUnits from "../../lib/grammar-units";
import { randomNumber } from "../../lib/misc-helpers";
import { adjustObjectSelection, adjustSubjectSelection, getObjectSelection, getSubjectSelection, VPSBlocksAreComplete } from "./blocks-utils";
export function isInvalidSubjObjCombo(subj: T.Person, obj: T.Person): boolean {
const firstPeople = [
@ -193,34 +194,35 @@ export function removeDuplicates(psv: T.PsString[]): T.PsString[] {
));
}
export function switchSubjObj(vps: T.VPSelectionState): T.VPSelectionState;
export function switchSubjObj(vps: T.VPSelectionComplete): T.VPSelectionComplete;
export function switchSubjObj(vps: T.VPSelectionState | T.VPSelectionComplete): T.VPSelectionState | T.VPSelectionComplete {
export function switchSubjObj<V extends T.VPSelectionState | T.VPSelectionComplete>(vps: V): V {
const subject = getSubjectSelection(vps.blocks).selection;
const object = getObjectSelection(vps.blocks).selection;
if ("tenseCategory" in vps.verb) {
if (!vps.subject || !(typeof vps.object === "object") || (vps.verb.tenseCategory === "imperative")) {
if (!subject || !(typeof object === "object") || (vps.verb.tenseCategory === "imperative")) {
return vps;
}
return {
...vps,
subject: vps.object,
object: vps.subject,
blocks: adjustObjectSelection(
adjustSubjectSelection(vps.blocks, object),
subject,
),
};
}
if (!vps.subject|| !vps.verb || !(typeof vps.object === "object")) {
if (!subject|| !vps.verb || !(typeof object === "object")) {
return vps;
}
return {
...vps,
subject: vps.object,
object: vps.subject,
blocks: adjustObjectSelection(
adjustSubjectSelection(vps.blocks, object),
subject,
),
};
}
export function completeVPSelection(vps: T.VPSelectionState): T.VPSelectionComplete | undefined {
if (vps.subject === undefined) {
return undefined;
}
if (vps.object === undefined) {
if (!VPSBlocksAreComplete(vps.blocks)) {
return undefined;
}
// necessary for this version on typscript ...
@ -228,13 +230,10 @@ export function completeVPSelection(vps: T.VPSelectionState): T.VPSelectionCompl
...vps.verb,
tense: getTenseFromVerbSelection(vps.verb),
};
const subject = vps.subject;
const object = vps.object
return {
...vps,
subject,
object,
verb,
blocks: vps.blocks,
};
}
@ -257,12 +256,12 @@ export function isThirdPerson(p: T.Person): boolean {
}
export function ensure2ndPersSubjPronounAndNoConflict(vps: T.VPSelectionState): T.VPSelectionState {
console.log("checking more...", vps);
const subjIs2ndPerson = (vps.subject?.type === "pronoun") && isSecondPerson(vps.subject.person);
const objIs2ndPerson = (typeof vps.object === "object")
&& (vps.object.type === "pronoun")
&& isSecondPerson(vps.object.person);
console.log({ subjIs2ndPerson, objIs2ndPerson });
const subject = getSubjectSelection(vps.blocks).selection;
const object = getObjectSelection(vps.blocks).selection;
const subjIs2ndPerson = (subject?.type === "pronoun") && isSecondPerson(subject.person);
const objIs2ndPerson = (typeof object === "object")
&& (object.type === "pronoun")
&& isSecondPerson(object.person);
const default2ndPersSubject: T.PronounSelection = {
type: "pronoun",
distance: "far",
@ -279,41 +278,39 @@ export function ensure2ndPersSubjPronounAndNoConflict(vps: T.VPSelectionState):
return vps;
}
if (subjIs2ndPerson && objIs2ndPerson) {
if (typeof vps.object !== "object" || vps.object.type !== "pronoun") {
if (typeof object !== "object" || object.type !== "pronoun") {
return vps;
}
return {
...vps,
object: {
...vps.object,
blocks: adjustObjectSelection(vps.blocks, {
...object,
person: getNon2ndPersPronoun(),
},
}),
};
}
if (!subjIs2ndPerson && objIs2ndPerson) {
if (typeof vps.object !== "object" || vps.object.type !== "pronoun") {
if (typeof object !== "object" || object.type !== "pronoun") {
return {
...vps,
subject: default2ndPersSubject,
blocks: adjustSubjectSelection(vps.blocks, default2ndPersSubject),
};
}
return {
...vps,
subject: default2ndPersSubject,
object: {
...vps.object,
person: getNon2ndPersPronoun(),
},
blocks: adjustObjectSelection(
adjustSubjectSelection(vps.blocks, default2ndPersSubject),
{
...object,
person: getNon2ndPersPronoun(),
},
),
};
}
if (!subjIs2ndPerson && !objIs2ndPerson) {
console.log("returning last");
return {
...vps,
subject: default2ndPersSubject,
verb: {
...vps.verb,
},
blocks: adjustSubjectSelection(vps.blocks, default2ndPersSubject),
};
}
throw new Error("error ensuring compatible VPSelection for imperative verb");

View File

@ -98,6 +98,12 @@ export const sandwiches: T.Sandwich[] = [
after: { p: "په حیث", f: "pu heys" },
e: "as",
},
{
type: "sandwich",
before: { p: "د", f: "du" },
after: { p: "په لور", f: "pu lor" },
e: "towards",
},
];
export default sandwiches;

File diff suppressed because one or more lines are too long

View File

@ -497,7 +497,7 @@ export type Pattern6FemEntry<T extends FemNounEntry> = T & { __brand3: "non anim
export type NonInflecting<T> = T & { __brand3: "non-inflecting" };
export type Entry = NounEntry | AdjectiveEntry | AdverbEntry | VerbEntry;
export type RenderedVPSBlock = (Rendered<SubjectSelectionComplete> | Rendered<ObjectSelectionComplete> | Rendered<APSelection>);
// TODO: make this Rendered<VPSelectionComplete> with recursive Rendered<>
export type VPRendered = {
type: "VPRendered",
@ -506,8 +506,7 @@ export type VPRendered = {
isPast: boolean,
isTransitive: boolean,
isCompound: "stative" | "dynamic" | false,
subject: Rendered<NPSelection>,
object: Rendered<NPSelection> | ObjectNP,
blocks: RenderedVPSBlock[],
verb: VerbRendered,
englishBase?: string[],
form: FormVersion,
@ -551,17 +550,13 @@ export type ObjectSelectionComplete = {
};
export type VPSelectionState = {
// blocks: (SubjectSelection | ObjectSelection)[]
subject: NPSelection | undefined,
object: NPSelection | ObjectNP | undefined,
blocks: VPSBlock[]
verb: VerbSelection,
form: FormVersion,
};
export type VPSelectionComplete = {
// blocks: (SubjectSelectionComplete | ObjectSelectionComplete)[]
subject: NPSelection,
object: NPSelection | ObjectNP,
blocks: VPSBlockComplete[]
verb: VerbSelectionComplete,
form: FormVersion,
};
@ -676,7 +671,7 @@ export type RenderedPossesorSelection = {
shrunken: boolean,
};
export type Rendered<T extends NPSelection | EqCompSelection | AdjectiveSelection | SandwichSelection<Sandwich> | APSelection | SubjectSelectionComplete> = T extends SandwichSelection<Sandwich>
export type Rendered<T extends NPSelection | EqCompSelection | AdjectiveSelection | SandwichSelection<Sandwich> | APSelection | SubjectSelectionComplete | ObjectSelectionComplete> = T extends SandwichSelection<Sandwich>
? Omit<SandwichSelection<Sandwich>, "inside"> & {
inside: Rendered<NPSelection>,
}
@ -702,6 +697,11 @@ export type Rendered<T extends NPSelection | EqCompSelection | AdjectiveSelectio
type: "subjectSelection",
selection: Rendered<NPSelection>,
}
: T extends ObjectSelectionComplete
? {
type: "objectSelection",
selection: Rendered<NPSelection> | Person.ThirdPlurMale | "none",
}
: ReplaceKey<
Omit<T, "changeGender" | "changeNumber" | "changeDistance" | "adjectives" | "possesor">,
"e",
@ -741,6 +741,16 @@ export type EPSBlockComplete = {
block: SubjectSelectionComplete | APSelection,
};
export type VPSBlock = {
key: number,
// TODO: confusing use of APSelection / should be like APSelection s APSelection complete like the others
block: SubjectSelection | ObjectSelection | (APSelection | undefined),
};
export type VPSBlockComplete = {
key: number,
block: SubjectSelectionComplete | ObjectSelectionComplete | APSelection,
};
export type EPSelectionComplete = Omit<EPSelectionState, "predicate" | "blocks"> & {
blocks: EPSBlockComplete[],
predicate: {